diff --git a/hyprland/.config/hypr/hyprland.conf b/hyprland/.config/hypr/hyprland.conf index f21df01..b40e8d6 100644 --- a/hyprland/.config/hypr/hyprland.conf +++ b/hyprland/.config/hypr/hyprland.conf @@ -12,6 +12,7 @@ monitor=,preferred,auto,auto # Execute your favorite apps at launch # exec-once = waybar & hyprpaper & firefox exec-once = dunst & waybar & /usr/lib/polkit-kde-authentication-agent-1 +exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP # Source a file (multi-file configs) # source = ~/.config/hypr/myColors.conf @@ -134,6 +135,11 @@ device:epic-mouse-v1 { # See https://wiki.hyprland.org/Configuring/Window-Rules/ for more windowrulev2 = nomaximizerequest, class:.* # You'll probably like this. +# Make xwaylandvideobridge work - for screensharing wayland apps to x11 apps +windowrulev2 = opacity 0.0 override 0.0 override,class:^(xwaylandvideobridge)$ +windowrulev2 = noanim,class:^(xwaylandvideobridge)$ +windowrulev2 = nofocus,class:^(xwaylandvideobridge)$ +windowrulev2 = noinitialfocus,class:^(xwaylandvideobridge)$ # Workspace rules workspace=1, monitor:eDP-2, persistent:true @@ -219,3 +225,15 @@ bind = ALT, TAB, cyclenext bind = ALT, TAB, bringactivetotop bind = ALT SHIFT, TAB, cyclenext, prev bind = ALT SHIFT, TAB, bringactivetotop + +# Media keys +bind = , XF86AudioPlay, exec, ~/.scripts/playerctl-fast play-pause +bind = , XF86AudioStop, exec, ~/.scripts/playerctl-fast play-pause +bind = , XF86AudioPrev, exec, ~/.scripts/playerctl-fast previous +bind = , XF86AudioNext, exec, ~/.scripts/playerctl-fast next + + +# Things to execute on launch +# exec-once = waybar & hyprpaper & firefox +exec-once = dunst & waybar & /usr/lib/polkit-kde-authentication-agent-1 +exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP diff --git a/setup b/setup index f92e5bf..f44c5ad 100755 --- a/setup +++ b/setup @@ -18,7 +18,7 @@ setup_hyprland_arch() { sudo pacman -S grim slurp swappy wl-clipboard # wdisplays is a decent looking GUI similar to arandr but for Wayland - paru -S wdisplays + paru -S wdisplays xwaylandvideobridge-bin } # AwesomeWM (X11) - not a bad tiling WM @@ -105,7 +105,7 @@ else echo "Updating Arch" sudo pacman -Syyu echo "Installing tools/utils" - sudo pacman -S ripgrep fd neovim make stow tmux dmenu arandr autorandr volumeicon picom nitrogen network-manager-applet lxsession thunar lxappearance-gtk3 power-profiles-daemon acpi arc-icon-theme playerctl xorg-xsetroot ttf-jetbrains-mono-nerd ttf-mononoki-nerd ttf-nerd-fonts-symbols-mono ttf-nerd-fonts-symbols ttf-nerd-fonts-symbols-common starship breeze breeze-gtk breeze-icons pass nushell neofetch man-db + sudo pacman -S ripgrep fd neovim make stow tmux dmenu arandr autorandr volumeicon picom nitrogen network-manager-applet lxsession thunar lxappearance-gtk3 power-profiles-daemon acpi arc-icon-theme playerctl xorg-xsetroot ttf-jetbrains-mono-nerd ttf-mononoki-nerd ttf-nerd-fonts-symbols-mono ttf-nerd-fonts-symbols ttf-nerd-fonts-symbols-common starship breeze breeze-gtk breeze-icons pass nushell neofetch man-db python sudo systemctl enable --now power-profiles-daemon diff --git a/waybar/.config/waybar/config b/waybar/.config/waybar/config index 0b22e02..fcba12f 100644 --- a/waybar/.config/waybar/config +++ b/waybar/.config/waybar/config @@ -5,9 +5,9 @@ // "width": 1280, // Waybar width "spacing": 4, // Gaps between modules (4px) // Choose the order of the modules - "modules-left": ["hyprland/workspaces", "sway/mode", "sway/scratchpad", "custom/media"], + "modules-left": ["hyprland/workspaces", "custom/media"], "modules-center": ["hyprland/window"], - "modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "keyboard-state", "hyprland/language", "battery", "battery#bat2", "clock", "tray"], + "modules-right": ["gamemode", "pulseaudio", "network", "cpu", "memory", "temperature", "battery", "clock", "tray"], // Modules configuration "hyprland/workspaces": { "format": "{icon}", @@ -18,22 +18,6 @@ "max-length": 200, "separate-outputs": true }, - // "sway/workspaces": { - // "disable-scroll": true, - // "all-outputs": true, - // "warp-on-scroll": false, - // "format": "{name}: {icon}", - // "format-icons": { - // "1": "", - // "2": "", - // "3": "", - // "4": "", - // "5": "", - // "urgent": "", - // "focused": "", - // "default": "" - // } - // }, "keyboard-state": { "numlock": true, "capslock": true, @@ -43,16 +27,6 @@ "unlocked": "" } }, - "sway/mode": { - "format": "{}" - }, - "sway/scratchpad": { - "format": "{icon} {count}", - "show-empty": false, - "format-icons": ["", ""], - "tooltip": true, - "tooltip-format": "{app}: {title}" - }, "mpd": { "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ", "format-disconnected": "Disconnected ", @@ -132,6 +106,18 @@ "battery#bat2": { "bat": "BAT2" }, + "gamemode": { + "format": "{glyph}", + "format-alt": "{glyph} {count}", + "glyph": "", + "hide-not-running": true, + "use-icon": true, + "icon-name": "input-gaming-symbolic", + "icon-spacing": 4, + "icon-size": 20, + "tooltip": true, + "tooltip-format": "Games running: {count}" + }, "network": { // "interface": "wlp2*", // (Optional) To force the use of this interface "format-wifi": "{essid} ({signalStrength}%) ", @@ -169,8 +155,7 @@ "default": "🎜" }, "escape": true, - "exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder - // "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name + "exec": "python $HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Filter player based on name } } diff --git a/waybar/.config/waybar/mediaplayer.py b/waybar/.config/waybar/mediaplayer.py new file mode 100644 index 0000000..51a4837 --- /dev/null +++ b/waybar/.config/waybar/mediaplayer.py @@ -0,0 +1,182 @@ +#!/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): + 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.init_players() + + def init_players(self): + for player in self.manager.props.player_names: + 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) + + # 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}") + player = PlayerManager(arguments.player) + player.run() + + +if __name__ == "__main__": + main() diff --git a/waybar/.config/waybar/mocha.css b/waybar/.config/waybar/mocha.css new file mode 100644 index 0000000..98e218a --- /dev/null +++ b/waybar/.config/waybar/mocha.css @@ -0,0 +1,37 @@ +/* +* +* Catppuccin Mocha palette +* Maintainer: rubyowo +* +*/ + +@define-color base #1e1e2e; +@define-color mantle #181825; +@define-color crust #11111b; + +@define-color text #cdd6f4; +@define-color subtext0 #a6adc8; +@define-color subtext1 #bac2de; + +@define-color surface0 #313244; +@define-color surface1 #45475a; +@define-color surface2 #585b70; + +@define-color overlay0 #6c7086; +@define-color overlay1 #7f849c; +@define-color overlay2 #9399b2; + +@define-color blue #89b4fa; +@define-color lavender #b4befe; +@define-color sapphire #74c7ec; +@define-color sky #89dceb; +@define-color teal #94e2d5; +@define-color green #a6e3a1; +@define-color yellow #f9e2af; +@define-color peach #fab387; +@define-color maroon #eba0ac; +@define-color red #f38ba8; +@define-color mauve #cba6f7; +@define-color pink #f5c2e7; +@define-color flamingo #f2cdcd; +@define-color rosewater #f5e0dc; diff --git a/waybar/.config/waybar/style.css b/waybar/.config/waybar/style.css index 8474bb9..952ffc8 100644 --- a/waybar/.config/waybar/style.css +++ b/waybar/.config/waybar/style.css @@ -1,75 +1,23 @@ +@import "mocha.css"; + * { /* `otf-font-awesome` is required to be installed for icons */ font-family: FontAwesome, Roboto, Helvetica, Arial, sans-serif; font-size: 13px; + color: @text; } window#waybar { - background-color: rgba(43, 48, 59, 0.5); - border-bottom: 3px solid rgba(100, 114, 125, 0.5); + background-color: @crust; + border: 2px solid alpha(@mantle, 0.3); color: #ffffff; transition-property: background-color; transition-duration: .5s; } -window#waybar.hidden { - opacity: 0.2; -} - -/* -window#waybar.empty { - background-color: transparent; -} -window#waybar.solo { - background-color: #FFFFFF; -} -*/ - -window#waybar.termite { - background-color: #3F3F3F; -} - -window#waybar.chromium { - background-color: #000000; - border: none; -} - -button { - /* Use box-shadow instead of border so the text isn't offset */ - box-shadow: inset 0 -3px transparent; - /* Avoid rounded borders under each button name */ - border: none; - border-radius: 0; -} - -/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */ -button:hover { - background: inherit; - box-shadow: inset 0 -3px #ffffff; -} - -#workspaces button { - padding: 0 5px; - background-color: transparent; - color: #ffffff; -} - -#workspaces button:hover { - background: rgba(0, 0, 0, 0.2); -} - #workspaces button.active { - background-color: #64727D; - box-shadow: inset 0 -3px #ffffff; -} - -#workspaces button.urgent { - background-color: #eb4d4b; -} - -#mode { - background-color: #64727D; - border-bottom: 3px solid #ffffff; + box-shadow: inset 0 -4px @green; + background-color: @overlay1; } #clock, @@ -87,194 +35,7 @@ button:hover { #mode, #idle_inhibitor, #scratchpad, +#gamemode, #mpd { padding: 0 10px; - color: #ffffff; -} - -#window, -#workspaces { - margin: 0 4px; -} - -/* If workspaces is the leftmost module, omit left margin */ -.modules-left > widget:first-child > #workspaces { - margin-left: 0; -} - -/* If workspaces is the rightmost module, omit right margin */ -.modules-right > widget:last-child > #workspaces { - margin-right: 0; -} - -#clock { - background-color: #64727D; -} - -#battery { - background-color: #ffffff; - color: #000000; -} - -#battery.charging, #battery.plugged { - color: #ffffff; - background-color: #26A65B; -} - -@keyframes blink { - to { - background-color: #ffffff; - color: #000000; - } -} - -#battery.critical:not(.charging) { - background-color: #f53c3c; - color: #ffffff; - animation-name: blink; - animation-duration: 0.5s; - animation-timing-function: linear; - animation-iteration-count: infinite; - animation-direction: alternate; -} - -label:focus { - background-color: #000000; -} - -#cpu { - background-color: #2ecc71; - color: #000000; -} - -#memory { - background-color: #9b59b6; -} - -#disk { - background-color: #964B00; -} - -#backlight { - background-color: #90b1b1; -} - -#network { - background-color: #2980b9; -} - -#network.disconnected { - background-color: #f53c3c; -} - -#pulseaudio { - background-color: #f1c40f; - color: #000000; -} - -#pulseaudio.muted { - background-color: #90b1b1; - color: #2a5c45; -} - -#wireplumber { - background-color: #fff0f5; - color: #000000; -} - -#wireplumber.muted { - background-color: #f53c3c; -} - -#custom-media { - background-color: #66cc99; - color: #2a5c45; - min-width: 100px; -} - -#custom-media.custom-spotify { - background-color: #66cc99; -} - -#custom-media.custom-vlc { - background-color: #ffa000; -} - -#temperature { - background-color: #f0932b; -} - -#temperature.critical { - background-color: #eb4d4b; -} - -#tray { - background-color: #2980b9; -} - -#tray > .passive { - -gtk-icon-effect: dim; -} - -#tray > .needs-attention { - -gtk-icon-effect: highlight; - background-color: #eb4d4b; -} - -#idle_inhibitor { - background-color: #2d3436; -} - -#idle_inhibitor.activated { - background-color: #ecf0f1; - color: #2d3436; -} - -#mpd { - background-color: #66cc99; - color: #2a5c45; -} - -#mpd.disconnected { - background-color: #f53c3c; -} - -#mpd.stopped { - background-color: #90b1b1; -} - -#mpd.paused { - background-color: #51a37a; -} - -#language { - background: #00b093; - color: #740864; - padding: 0 5px; - margin: 0 5px; - min-width: 16px; -} - -#keyboard-state { - background: #97e1ad; - color: #000000; - padding: 0 0px; - margin: 0 5px; - min-width: 16px; -} - -#keyboard-state > label { - padding: 0 5px; -} - -#keyboard-state > label.locked { - background: rgba(0, 0, 0, 0.2); -} - -#scratchpad { - background: rgba(0, 0, 0, 0.2); -} - -#scratchpad.empty { - background-color: transparent; }