diff --git a/.bashrc b/.bashrc index 5c19e69..797e96e 100644 --- a/.bashrc +++ b/.bashrc @@ -11,4 +11,10 @@ alias ls='ls --color=auto' alias grep='grep --color=auto' PS1='[\u@\h \W]\$ ' +export HOMEBREW_PREFIX="/home/linuxbrew/.linuxbrew" +export HOMEBREW_CELLAR="/home/linuxbrew/.linuxbrew/Cellar" +export HOMEBREW_REPOSITORY="/home/linuxbrew/.linuxbrew/Homebrew" +export PATH="/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin${PATH+:$PATH}" +export MANPATH="/home/linuxbrew/.linuxbrew/share/man${MANPATH+:$MANPATH}:" +export INFOPATH="/home/linuxbrew/.linuxbrew/share/info:${INFOPATH:-}" export EDITOR=nvim diff --git a/.gitmodules b/.gitmodules index 9542823..80df3c1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,9 @@ [submodule "nvim"] path = nvim url = https://git.palko.ca/baobeld/nvim.git +[submodule "ags"] + path = ags + url = https://git.palko.ca/baobeld/ags.git [submodule "lux-shell"] path = quickshell url = https://git.palko.ca/baobeld/lux-shell.git diff --git a/.zshrc b/.zshrc index c139937..b0b70e2 100644 --- a/.zshrc +++ b/.zshrc @@ -75,6 +75,8 @@ cat ~/.cache/wal/sequences # Add wisely, as too many plugins slow down shell startup. plugins=(git gh bun npm yarn mise) +source $ZSH/oh-my-zsh.sh + # User configuration # export MANPATH="/usr/local/man:$MANPATH" @@ -119,13 +121,9 @@ compinit # End of lines added by compinstall eval "$(mise activate zsh)" +source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh -source $ZSH/oh-my-zsh.sh export EDITOR=nvim -export QML_IMPORT_PATH=/usr/lib/qt6/qml -export QML2_IMPORT_PATH=/usr/lib/qt6/qml neofetch --ascii ~/dotfiles/ascii.txt - -source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh diff --git a/README.md b/README.md index 1f2ac3c..3c922e9 100755 --- a/README.md +++ b/README.md @@ -43,13 +43,33 @@ Install... Include = /etc/pacman.d/mirrorlist ``` +### TODO + +- [ ] zsh Configuration +- [ ] scripted setup + - [ ] pacman packages + - [ ] yay packages + - [ ] brew packages + - [ ] aliases + - [ ] symlinks +- [ ] hyprland Configuration + - [ ] waybar + - [ ] hyprpaper + - [ ] swaylock/hyprlock + - [ ] swayidle/hypridle + - [ ] keybindings + - [ ] wlogout + ## Packages This is a list of packages sorted by the package manager used to install them along with a small description of what they do as well as a link to their website/docs ### Pacman +- Thunar - file manager - zsh - Shell +- Bitwarden - Password manager +- Firefox - web browser - git - Version control - github-cli - Github CLI - lazygit - CLI git client @@ -61,18 +81,24 @@ This is a list of packages sorted by the package manager used to install them al - xclip - Clipboard util - ripgrep - CLI grep tool (used for neovim text search across files) - btop - resource monitor +- dunst - notification daemon - cmatrix - THE MATRIX - swappy - screenshot tool - spotify - Music App - discord - Messaging +- caprine - Facebook messenger - obsidian - markdown and stuff #### Hyprland +- waybar - wayland status bar - swww - wallpaper animations - hypridle - hyprland idle daemon - hyprlock - hyprland lock screen +- wlogout - logout manu - pywal - generates color palettes from wallpaper +- hyprland-plugins - self explanitory +- SwayNotificationCenter - notification UI ### Yay diff --git a/ags b/ags new file mode 160000 index 0000000..27a2631 --- /dev/null +++ b/ags @@ -0,0 +1 @@ +Subproject commit 27a263142ff9b44151e81f57075dae704f43e272 diff --git a/hypr/hyprland/env.conf b/hypr/hyprland/env.conf index b3b0423..b2cda83 100644 --- a/hypr/hyprland/env.conf +++ b/hypr/hyprland/env.conf @@ -29,9 +29,9 @@ env = ICON_THEME,WhiteSur-Dark env = COLOR_SCHEME,prefer-dark #Cursors -env = XCURSOR_THEME,Volantes Cursors +env = XCURSOR_THEME,Catppuccin-Macchiato-Dark env = XCURSOR_SIZE,24 -env = HYPRCURSOR_THEME,Volantes Cursors +env = HYPRCURSOR_THEME,Catppuccin-Macchiato-Dark env = HYPRCURSOR_SIZE,24 diff --git a/hypr/hyprland/execs.conf b/hypr/hyprland/execs.conf index 80fcfd9..34abb96 100644 --- a/hypr/hyprland/execs.conf +++ b/hypr/hyprland/execs.conf @@ -1,10 +1,13 @@ -# Key ring -exec-once = gnome-keyring-daemon --start --components=secrets +# Set cursor +exec-once=hyprctl setcursor volantes_cursors 24 +# Notification Daemon +exec-once = bash ~/.config/hypr/hyprland/scripts/start-swaync.sh # Idle Daemon exec-once = hypridle -# Lux-shell -exec-once = quickshell +# Status-bar +# exec-once = bash ~/.config/hypr/scripts/start-waybar.sh +exec-once = bash ~/dotfiles/hypr/hyprland/scripts/run-ags.sh # Emotes exec-once = emote # Wallpaper Daemon diff --git a/hypr/hyprland/keybinds.conf b/hypr/hyprland/keybinds.conf index 2edf954..48fef57 100644 --- a/hypr/hyprland/keybinds.conf +++ b/hypr/hyprland/keybinds.conf @@ -1,7 +1,4 @@ -# Lux Keybinds -bind = $mainMod, ENTER, global, lux:launcher - # Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more bind = $mainMod, T, exec, $terminal bind = $mainMod, X, killactive, @@ -12,7 +9,7 @@ bind = $mainMod, R, exec, $menu bind = $mainMod, P, pseudo, # dwindle bind = $mainMod, V, togglesplit, # dwindle bind = $mainMod, L, exec, $lockScreen # hyprlock -bind = $mainMod, period, exec, gnome-characters +bind = $mainMod, period, exec, emote bind = $mainMod, B, exec, zen-browser bind = $mainMod CTRL, F, fullscreen bind = , PRINT, exec, grim -g "$(slurp)" - | swappy -f - diff --git a/hypr/hyprland/scripts/run-ags.sh b/hypr/hyprland/scripts/run-ags.sh new file mode 100644 index 0000000..e7ac9a6 --- /dev/null +++ b/hypr/hyprland/scripts/run-ags.sh @@ -0,0 +1,8 @@ +#!/bin/sh +CONFIG_FILES="$HOME/dotfiles/ags" +while true; do + sleep 1.6 + ags run --gtk4 -d "$CONFIG_FILES" & + inotifywait -e create,modify -r "$CONFIG_FILES" + killall gjs +done diff --git a/hypr/hyprland/scripts/start-swaync.sh b/hypr/hyprland/scripts/start-swaync.sh new file mode 100644 index 0000000..e90446d --- /dev/null +++ b/hypr/hyprland/scripts/start-swaync.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +CONFIG_FILES="$HOME/.config/swaync/ $HOME/.cache/wal/" + +trap "killall swaync" EXIT +swaync & + +while true; do + inotifywait -e create,modify -r $CONFIG_FILES + swaync-client -R & swaync-client -rs +done diff --git a/nvim b/nvim index 57de95c..4c428c3 160000 --- a/nvim +++ b/nvim @@ -1 +1 @@ -Subproject commit 57de95c811e74ea92d41b6de7363b6f8552f4666 +Subproject commit 4c428c3c2082f93e47e400be8d44fba1ab07e46f diff --git a/quickshell b/quickshell index 3e3275a..579af75 160000 --- a/quickshell +++ b/quickshell @@ -1 +1 @@ -Subproject commit 3e3275a84dc88021656a9bae816a37324a117bbc +Subproject commit 579af752fa88747229969612ec3fb8ba10067001 diff --git a/swaync/config.json b/swaync/config.json new file mode 100755 index 0000000..4201027 --- /dev/null +++ b/swaync/config.json @@ -0,0 +1,100 @@ +{ + "$schema": "/etc/xdg/swaync/configSchema.json", + "positionX": "right", + "positionY": "top", + "layer": "overlay", + "control-center-layer": "right", + "layer-shell": true, + "cssPriority": "application", + "control-center-margin-top": 5, + "control-center-margin-bottom": 0, + "control-center-margin-right": 0, + "control-center-margin-left": 0, + "notification-2fa-action": true, + "notification-inline-replies": false, + "notification-icon-size": 24, + "notification-body-image-height": 100, + "notification-body-image-width": 200, + "timeout": 6, + "timeout-low": 3, + "timeout-critical": 0, + "fit-to-screen": false, + "control-center-width": 350, + "control-center-height": 720, + "notification-window-width": 400, + "keyboard-shortcuts": true, + "image-visibility": "when available", + "transition-time": 200, + "hide-on-clear": false, + "hide-on-action": true, + "script-fail-notify": true, + "widgets": [ + "dnd", + "buttons-grid", + "mpris", + "volume", + "backlight", + "title", + "notifications" + ], + "widget-config": { + "title": { + "text": "Notifications", + "clear-all-button": true, + "button-text": "Clear" + }, + "dnd": { + "text": "Do Not Disturb" + }, + "label": { + "max-lines": 1, + "text": "Notification" + }, + "mpris": { + "image-size": 48, + "image-radius": 9999 + }, + "volume": { + "label": "󰕾" + }, + "backlight": { + "label": "󰃟" + }, + "buttons-grid": { + "actions": [ + { + "label": "󰐥", + "command": "systemctl poweroff" + }, + { + "label": "󰜉", + "command": "systemctl reboot" + }, + { + "label": "󰌾", + "command": "hyprlock" + }, + { + "label": "󰍃", + "command": "hyprctl dispatch exit" + }, + { + "label": "󰀝", + "command": "~/.config/hypr/scripts/AirplaneMode.sh" + }, + { + "label": "󰕾", + "command": "pactl set-sink-mute @DEFAULT_SINK@ toggle" + }, + { + "label": "󰍬", + "command": "pactl set-source-mute @DEFAULT_SOURCE@ toggle" + }, + { + "label": "󰂯", + "command": "blueman-manager" + } + ] + } + } +} diff --git a/swaync/style.css b/swaync/style.css new file mode 100644 index 0000000..59f9ff7 --- /dev/null +++ b/swaync/style.css @@ -0,0 +1,451 @@ +* { + all: unset; + font-size: 14px; + font-family: "Jetbrains Nerd Font"; + transition: 200ms; +} + +trough highlight { + background: #cdd6f4; +} + +scale trough { + margin: 0rem 1rem; + background-color: #313244; + min-height: 8px; + min-width: 70px; +} + +slider { + background-color: #89b4fa; +} + +.floating-notifications.background .notification-row .notification-background { + box-shadow: + 0 0 8px 0 rgba(0, 0, 0, 0.8), + inset 0 0 0 1px #313244; + border-radius: 12.6px; + margin: 18px; + background-color: #1e1e2e; + color: #cdd6f4; + padding: 0; +} + +.floating-notifications.background + .notification-row + .notification-background + .notification { + padding: 7px; + border-radius: 12.6px; +} + +.floating-notifications.background + .notification-row + .notification-background + .notification.critical { + box-shadow: inset 0 0 7px 0 #f38ba8; +} + +.floating-notifications.background + .notification-row + .notification-background + .notification + .notification-content { + margin: 7px; +} + +.floating-notifications.background + .notification-row + .notification-background + .notification + .notification-content + .summary { + color: #cdd6f4; +} + +.floating-notifications.background + .notification-row + .notification-background + .notification + .notification-content + .time { + color: #a6adc8; +} + +.floating-notifications.background + .notification-row + .notification-background + .notification + .notification-content + .body { + color: #cdd6f4; +} + +.floating-notifications.background + .notification-row + .notification-background + .notification + > *:last-child + > * { + min-height: 3.4em; +} + +.floating-notifications.background + .notification-row + .notification-background + .notification + > *:last-child + > * + .notification-action { + border-radius: 7px; + color: #cdd6f4; + background-color: #313244; + box-shadow: inset 0 0 0 1px #45475a; + margin: 7px; +} + +.floating-notifications.background + .notification-row + .notification-background + .notification + > *:last-child + > * + .notification-action:hover { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #313244; + color: #cdd6f4; +} + +.floating-notifications.background + .notification-row + .notification-background + .notification + > *:last-child + > * + .notification-action:active { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #74c7ec; + color: #cdd6f4; +} + +.floating-notifications.background + .notification-row + .notification-background + .close-button { + margin: 7px; + padding: 2px; + border-radius: 6.3px; + color: #1e1e2e; + background-color: #f38ba8; +} + +.floating-notifications.background + .notification-row + .notification-background + .close-button:hover { + background-color: #eba0ac; + color: #1e1e2e; +} + +.floating-notifications.background + .notification-row + .notification-background + .close-button:active { + background-color: #f38ba8; + color: #1e1e2e; +} + +.control-center { + box-shadow: + 0 0 8px 0 rgba(0, 0, 0, 0.8), + inset 0 0 0 1px #313244; + border-radius: 12.6px; + margin: 18px; + background-color: #1e1e2e; + color: #cdd6f4; + padding: 14px; +} + +.control-center .widget-title > label { + color: #cdd6f4; + font-size: 1.3em; +} + +.control-center .widget-title button { + border-radius: 7px; + color: #cdd6f4; + background-color: #313244; + box-shadow: inset 0 0 0 1px #45475a; + padding: 8px; +} + +.control-center .widget-title button:hover { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #585b70; + color: #cdd6f4; +} + +.control-center .widget-title button:active { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #74c7ec; + color: #1e1e2e; +} + +.control-center .notification-row .notification-background { + border-radius: 7px; + color: #cdd6f4; + background-color: #313244; + box-shadow: inset 0 0 0 1px #45475a; + margin-top: 14px; +} + +.control-center .notification-row .notification-background .notification { + padding: 7px; + border-radius: 7px; +} + +.control-center + .notification-row + .notification-background + .notification.critical { + box-shadow: inset 0 0 7px 0 #f38ba8; +} + +.control-center + .notification-row + .notification-background + .notification + .notification-content { + margin: 7px; +} + +.control-center + .notification-row + .notification-background + .notification + .notification-content + .summary { + color: #cdd6f4; +} + +.control-center + .notification-row + .notification-background + .notification + .notification-content + .time { + color: #a6adc8; +} + +.control-center + .notification-row + .notification-background + .notification + .notification-content + .body { + color: #cdd6f4; +} + +.control-center + .notification-row + .notification-background + .notification + > *:last-child + > * { + min-height: 3.4em; +} + +.control-center + .notification-row + .notification-background + .notification + > *:last-child + > * + .notification-action { + border-radius: 7px; + color: #cdd6f4; + background-color: #11111b; + box-shadow: inset 0 0 0 1px #45475a; + margin: 7px; +} + +.control-center + .notification-row + .notification-background + .notification + > *:last-child + > * + .notification-action:hover { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #313244; + color: #cdd6f4; +} + +.control-center + .notification-row + .notification-background + .notification + > *:last-child + > * + .notification-action:active { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #74c7ec; + color: #cdd6f4; +} + +.control-center .notification-row .notification-background .close-button { + margin: 7px; + padding: 2px; + border-radius: 6.3px; + color: #1e1e2e; + background-color: #eba0ac; +} + +.close-button { + border-radius: 6.3px; +} + +.control-center .notification-row .notification-background .close-button:hover { + background-color: #f38ba8; + color: #1e1e2e; +} + +.control-center + .notification-row + .notification-background + .close-button:active { + background-color: #f38ba8; + color: #1e1e2e; +} + +.control-center .notification-row .notification-background:hover { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #7f849c; + color: #cdd6f4; +} + +.control-center .notification-row .notification-background:active { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #74c7ec; + color: #cdd6f4; +} + +.notification.critical progress { + background-color: #f38ba8; +} + +.notification.low progress, +.notification.normal progress { + background-color: #89b4fa; +} + +.control-center-dnd { + margin-top: 5px; + border-radius: 8px; + background: #313244; + border: 1px solid #45475a; + box-shadow: none; +} + +.control-center-dnd:checked { + background: #313244; +} + +.control-center-dnd slider { + background: #45475a; + border-radius: 8px; +} + +.widget-dnd { + margin: 0px; + font-size: 1.1rem; +} + +.widget-dnd > switch { + font-size: initial; + border-radius: 8px; + background: #313244; + border: 1px solid #45475a; + box-shadow: none; +} + +.widget-dnd > switch:checked { + background: #313244; +} + +.widget-dnd > switch slider { + background: #45475a; + border-radius: 8px; + border: 1px solid #6c7086; +} + +.widget-mpris .widget-mpris-player { + background: #313244; + padding: 7px; +} + +.widget-mpris .widget-mpris-title { + font-size: 1.2rem; +} + +.widget-mpris .widget-mpris-subtitle { + font-size: 0.8rem; +} + +.widget-menubar > box > .menu-button-bar > button > label { + font-size: 3rem; + padding: 0.5rem 2rem; +} + +.widget-menubar > box > .menu-button-bar > :last-child { + color: #f38ba8; +} + +.power-buttons button:hover, +.powermode-buttons button:hover, +.screenshot-buttons button:hover { + background: #313244; +} + +.control-center .widget-label > label { + color: #cdd6f4; + font-size: 2rem; +} + +.widget-buttons-grid { + padding-top: 1rem; +} + +.widget-buttons-grid > flowbox > flowboxchild > button label { + font-size: 2.5rem; +} + +.widget-volume { + padding-top: 1rem; +} + +.widget-volume label { + font-size: 1.5rem; + color: #74c7ec; +} + +.widget-volume trough highlight { + background: #74c7ec; +} + +.widget-backlight trough highlight { + background: #f9e2af; +} + +.widget-backlight label { + font-size: 1.5rem; + color: #f9e2af; +} + +.widget-backlight .KB { + padding-bottom: 1rem; +} + +.image { + padding-right: 0.5rem; +} diff --git a/waybar/config.jsonc b/waybar/config.jsonc new file mode 100644 index 0000000..066e918 --- /dev/null +++ b/waybar/config.jsonc @@ -0,0 +1,140 @@ +// -*- mode: jsonc -*- +{ + "layer": "top", // Waybar at top layer + // "position": "bottom", // Waybar position (top|bottom|left|right) + "height": 36, // Waybar height (to be removed for auto height) + // "width": 1280, // Waybar width + "spacing": 8, // Gaps between modules (4px) + // Choose the order of the modules + "modules-left": [ + "custom/os", + "hyprland/workspaces", + "tray", + "hyprland/window", + ], + "modules-center": ["mpris"], + "modules-right": [ + "custom/swww", + "wireplumber", + "network", + "cpu", + "memory", + "temperature", + "idle_inhibitor", + "clock", + "custom/swaync", + ], + "custom/os": { + "format": "󰣇", + "on-click": "~/.config/rofi/scripts/powermenu_t1", + }, + "idle_inhibitor": { + "format": "{icon}", + "tooltip-format-activated": "On", + "tooltip-format-deactivated": "Off", + "format-icons": { + "activated": "󱫔", + "deactivated": "󱫐", + }, + }, + "hyprland/workspaces": { + "active-only": false, + "format": "{icon}", + "format-icons": { + "default": "", + "1": "", + "2": "󰭹", + "3": "󰈹", + "4": "", + }, + "persistent-workspaces": { + "*": 4, // 5 workspaces by default on every monitor + }, + }, + "hyprland/window": { + "format": "{}", + "rewrite": { + "(.*) — Mozilla Firefox": "󰈹 $1", + "~(.*)": "󰅬 $1", + "nv": " neovim", + }, + "separate-outputs": true, + "max-length": "40", + }, + "tray": { + "icon-size": 18, + "spacing": 10, + }, + "clock": { + "timezone": "America/New_York", + "tooltip-format": "{:%Y %B}\n{calendar}", + "format-alt": "{:%Y-%m-%d}", + "on-click": "", + }, + "cpu": { + "format": "󰻠 {usage}%", + "tooltip": true, + }, + "memory": { + "format": " {}%", + }, + "temperature": { + // "thermal-zone": 2, + // "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input", + "critical-threshold": 80, + // "format-critical": "{temperatureC}°C {icon}", + "format": "{icon} {temperatureC}°C", + "format-icons": ["", "", "", "", ""], + }, + "wireplumber": { + "format": " {volume}%", + "format-mute": " {volume}%", + "on-click": "~/.config/rofi/applets/bin/volume.sh", + }, + "network": { + // "interface": "wlp2*", // (Optional) To force the use of this interface + "tooltip-format": "{essid}", + "format-wifi": " {signalStrength}%", + "format-ethernet": "󰈀", + "format-linked": "{ifname} (No IP) ", + "format-disconnected": "󰖪", + //"format-alt": "{ifname}: {ipaddr}/{cidr}" + }, + "mpris": { + "format": "{player_icon} {dynamic}", + "format-paused": "{status_icon} {dynamic}", + "player-icons": { + "default": "▶", + "mpv": "🎵", + "spotify": "", + "firefox": "󰈹", + }, + "interval": 1, + "status-icons": { + "paused": "⏸", + }, + "dynamic-len": 36, + "on-click-middle": "playerctld shift", + }, + "custom/swaync": { + "format": "{icon}", + "exec": "swaync-client -swb", + "return-type": "json", + "on-click": "sleep 0.1 && swaync-client -t -sw", + "format-icons": { + "notification": "", + "none": " ", + "dnd-notification": "", + "dnd-none": " ", + }, + }, + "custom/swww": { + "format": "{icon}", + "tooltip": false, + "tooltip-format": "Change Wallpaper", + "on-click": "~/dotfiles/.scripts/pywal-swww.sh", + "format-icons": { + "default": "", + }, + }, +} diff --git a/waybar/modules/mediaplayer.py b/waybar/modules/mediaplayer.py new file mode 100755 index 0000000..e473697 --- /dev/null +++ b/waybar/modules/mediaplayer.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +import gi +gi.require_version("Playerctl", "2.0") +from gi.repository import Playerctl, GLib +from gi.repository.Playerctl import Player +import argparse +import logging +import sys +import signal +import gi +import json +import os +from typing import List + +logger = logging.getLogger(__name__) + +def signal_handler(sig, frame): + logger.info("Received signal to stop, exiting") + sys.stdout.write("\n") + sys.stdout.flush() + # loop.quit() + sys.exit(0) + + +class PlayerManager: + def __init__(self, selected_player=None, excluded_player=[]): + self.manager = Playerctl.PlayerManager() + self.loop = GLib.MainLoop() + self.manager.connect( + "name-appeared", lambda *args: self.on_player_appeared(*args)) + self.manager.connect( + "player-vanished", lambda *args: self.on_player_vanished(*args)) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + self.selected_player = selected_player + self.excluded_player = excluded_player.split(',') if excluded_player else [] + + self.init_players() + + def init_players(self): + for player in self.manager.props.player_names: + if player.name in self.excluded_player: + continue + if self.selected_player is not None and self.selected_player != player.name: + logger.debug(f"{player.name} is not the filtered player, skipping it") + continue + self.init_player(player) + + def run(self): + logger.info("Starting main loop") + self.loop.run() + + def init_player(self, player): + logger.info(f"Initialize new player: {player.name}") + player = Playerctl.Player.new_from_name(player) + player.connect("playback-status", + self.on_playback_status_changed, None) + player.connect("metadata", self.on_metadata_changed, None) + self.manager.manage_player(player) + self.on_metadata_changed(player, player.props.metadata) + + def get_players(self) -> List[Player]: + return self.manager.props.players + + def write_output(self, text, player): + logger.debug(f"Writing output: {text}") + + output = {"text": text, + "class": "custom-" + player.props.player_name, + "alt": player.props.player_name} + + sys.stdout.write(json.dumps(output) + "\n") + sys.stdout.flush() + + def clear_output(self): + sys.stdout.write("\n") + sys.stdout.flush() + + def on_playback_status_changed(self, player, status, _=None): + logger.debug(f"Playback status changed for player {player.props.player_name}: {status}") + self.on_metadata_changed(player, player.props.metadata) + + def get_first_playing_player(self): + players = self.get_players() + logger.debug(f"Getting first playing player from {len(players)} players") + if len(players) > 0: + # if any are playing, show the first one that is playing + # reverse order, so that the most recently added ones are preferred + for player in players[::-1]: + if player.props.status == "Playing": + return player + # if none are playing, show the first one + return players[0] + else: + logger.debug("No players found") + return None + + def show_most_important_player(self): + logger.debug("Showing most important player") + # show the currently playing player + # or else show the first paused player + # or else show nothing + current_player = self.get_first_playing_player() + if current_player is not None: + self.on_metadata_changed(current_player, current_player.props.metadata) + else: + self.clear_output() + + def on_metadata_changed(self, player, metadata, _=None): + logger.debug(f"Metadata changed for player {player.props.player_name}") + player_name = player.props.player_name + artist = player.get_artist() + title = player.get_title() + + track_info = "" + if player_name == "spotify" and "mpris:trackid" in metadata.keys() and ":ad:" in player.props.metadata["mpris:trackid"]: + track_info = "Advertisement" + elif artist is not None and title is not None: + track_info = f"{artist} - {title}" + else: + track_info = title + + if track_info: + if player.props.status == "Playing": + track_info = " " + track_info + else: + track_info = " " + track_info + # only print output if no other player is playing + current_playing = self.get_first_playing_player() + if current_playing is None or current_playing.props.player_name == player.props.player_name: + self.write_output(track_info, player) + else: + logger.debug(f"Other player {current_playing.props.player_name} is playing, skipping") + + def on_player_appeared(self, _, player): + logger.info(f"Player has appeared: {player.name}") + if player is not None and (self.selected_player is None or player.name == self.selected_player): + self.init_player(player) + else: + logger.debug( + "New player appeared, but it's not the selected player, skipping") + + def on_player_vanished(self, _, player): + logger.info(f"Player {player.props.player_name} has vanished") + self.show_most_important_player() + +def parse_arguments(): + parser = argparse.ArgumentParser() + + # Increase verbosity with every occurrence of -v + parser.add_argument("-v", "--verbose", action="count", default=0) + + parser.add_argument("-x", "--exclude", "- Comma-separated list of excluded player") + + # Define for which player we"re listening + parser.add_argument("--player") + + parser.add_argument("--enable-logging", action="store_true") + + return parser.parse_args() + + +def main(): + arguments = parse_arguments() + + # Initialize logging + if arguments.enable_logging: + logfile = os.path.join(os.path.dirname( + os.path.realpath(__file__)), "media-player.log") + logging.basicConfig(filename=logfile, level=logging.DEBUG, + format="%(asctime)s %(name)s %(levelname)s:%(lineno)d %(message)s") + + # Logging is set by default to WARN and higher. + # With every occurrence of -v it's lowered by one + logger.setLevel(max((3 - arguments.verbose) * 10, 0)) + + logger.info("Creating player manager") + if arguments.player: + logger.info(f"Filtering for player: {arguments.player}") + if arguments.exclude: + logger.info(f"Exclude player {arguments.exclude}") + + player = PlayerManager(arguments.player, arguments.exclude) + player.run() + + +if __name__ == "__main__": + main() diff --git a/waybar/style.css b/waybar/style.css new file mode 100644 index 0000000..616818b --- /dev/null +++ b/waybar/style.css @@ -0,0 +1,118 @@ +/* +* Color Palette +*/ +@import url("../../.cache/wal/colors-waybar.css"); + +* { + /* `otf-font-awesome` is required to be installed for icons */ + font-family: "JetBrainsMono Nerd Font", "Iosevka Nerd Font", FontAwesome, + Roboto, Helvetica, Arial, sans-serif; + text-shadow: none; + transition: color 0.5s ease-in-out; + transition: background-color 0.5s ease-in-out; + font-size: 16px; +} + +@define-color button @color1; +@define-color button-hover @color3; +@define-color button-active @color2; + +/* +* General +*/ +@import url("./styles/waybar.css"); + +/* + * Workspaces + */ +#workspaces { + border-radius: 9999px; + background: @button; +} + +#workspaces button { + padding: 0 9px 0 4px; + box-shadow: none; + border: none; + border-radius: 9999px; + color: @color7; + font-size: 24px; +} + +#workspaces button.active { + color: @button-active; +} + +#workspaces button:hover { + background: transparent; + color: @button-hover; +} + +#workspaces button.focused { + color: @button-active; +} + +#workspaces button.urgent { + color: #bf616a; +} + +/* +* Modules +*/ +#cpu, +#memory, +#network, +#temperature { + color: @color7; +} + +#clock, +#idle_inhibitor, +#mpris, +#wireplumber { + background: @button; +} + +#clock:hover, +#idle_inhibitor:hover, +#mpris:hover, +#custom-swaync:hover, +#wireplumber:hover, +#custom-swww:hover, +#custom-os:hover { + background: @button-hover; +} + +#idle_inhibitor.activated { + background-color: @button-active; +} + +#idle_inhibitor { + padding: 0 16px 0 12px; +} + +#mpris.spotify { + background-color: #1db954; +} + +#network.disconnected { + color: #f53c3c; +} + +#temperature.critical { + color: #bf616a; +} + +#wireplumber.muted { + color: #f53c3c; +} + +#custom-swww { + padding: 0 16px 0 10px; + font-size: 18px; +} + +#custom-os { + padding: 0 14px 0 8px; + font-size: 24px; +} diff --git a/waybar/styles/waybar.css b/waybar/styles/waybar.css new file mode 100644 index 0000000..3a7e09d --- /dev/null +++ b/waybar/styles/waybar.css @@ -0,0 +1,51 @@ +window#waybar { + background: @background; + animation: fadeIn 2.5s; + padding: 2px 0 2px 0; + border-radius: 0 0 20px 20px; +} + +button { + text-shadow: 2px 2px; +} + +window#waybar.hidden { + opacity: 0.2; +} + +@keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +.module { + border-radius: 9999px; + margin: 8px 0; +} + +label.module { + padding: 0 10px; + /* box-shadow: inset 0 -2px; */ +} + +box.module { + padding: 0 10px; +} + +.modules-left { + padding: 0 3px; + margin-left: 10px; +} + +.modules-center { + padding: 0 3px; +} + +.modules-right { + padding: 0 3px; + margin-right: 10px; +}