diff --git a/waybar/escape-mpc-pango.sh b/waybar/escape-mpc-pango.sh new file mode 100755 index 0000000..418ef70 --- /dev/null +++ b/waybar/escape-mpc-pango.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +CMD="$1" +BEFORE_SPACE="${CMD%% *}" +if command -v "$BEFORE_SPACE" &> /dev/null; then + eval "$CMD" | sed 's/&/\&/g; s//\>/g; s/'\''/\'/g' +else + echo "$CMD" | sed 's/&/\&/g; s//\>/g; s/'\''/\'/g' +fi diff --git a/waybar/firefox-status.sh b/waybar/firefox-status.sh new file mode 100755 index 0000000..4d3defa --- /dev/null +++ b/waybar/firefox-status.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +STATUS="$(playerctl -p firefox status)" + +if [ -z "$STATUS" ] || [ "$STATUS" = "Stopped" ]; then + exit 0 +elif [ "$STATUS" = "Paused" ]; then + STATUS=" " +elif [ "$STATUS" = "Playing" ]; then + STATUS=" " +else + exit 0 +fi + +TITLE="$(playerctl -p firefox metadata title)" +ARTIST="$(playerctl -p firefox metadata artist)" + +printf "%s\n" "$STATUS$TITLE - $ARTIST" diff --git a/waybar/mediaplayer.py b/waybar/mediaplayer.py new file mode 100755 index 0000000..75122a8 --- /dev/null +++ b/waybar/mediaplayer.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +import gi + +gi.require_version("Playerctl", "2.0") +import argparse +import json +import logging +import os +import signal +import sys +from typing import List + +import gi +from gi.repository import GLib, Playerctl +from gi.repository.Playerctl import Player + +logger = logging.getLogger(__name__) +is_plain = False + + +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}") + + if not is_plain: + output = { + "text": text, + "class": "custom-" + player.props.player_name, + "alt": player.props.player_name, + } + + sys.stdout.write(json.dumps(output)) + sys.stdout.flush() + else: + sys.stdout.write(text + "\n") + # sys.stdout.write(text) + 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() + title = title.replace("&", "&") + + 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 = "playing " + track_info + else: + track_info = "paused " + 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.name in self.excluded_player: + logger.debug( + "New player appeared, but it's in exclude player list, skipping" + ) + return + 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") + parser.add_argument( + "-p", "--plain", action="store_true", help="Plain text output", default=False + ) + + # 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(): + global is_plain + 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}") + if arguments.plain: + logger.info("Plain text output") + is_plain = arguments.plain + + player = PlayerManager(arguments.player, arguments.exclude) + player.run() + + +if __name__ == "__main__": + + main() diff --git a/waybar/pipewire.sh b/waybar/pipewire.sh new file mode 100755 index 0000000..125a77e --- /dev/null +++ b/waybar/pipewire.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -e + +# https://blog.dhampir.no/content/sleeping-without-a-subprocess-in-bash-and-how-to-sleep-forever +snore() { + local IFS + [[ -n "${_snore_fd:-}" ]] || exec {_snore_fd}<> <(:) + read -r ${1:+-t "$1"} -u $_snore_fd || : +} + +DELAY=0.2 + +while snore $DELAY; do + WP_OUTPUT=$(wpctl get-volume @DEFAULT_AUDIO_SINK@) + + if [[ $WP_OUTPUT =~ ^Volume:[[:blank:]]([0-9]+)\.([0-9]{2})([[:blank:]].MUTED.)?$ ]]; then + if [[ -n ${BASH_REMATCH[3]} ]]; then + printf "MUTE\n" + else + VOLUME=$((10#${BASH_REMATCH[1]}${BASH_REMATCH[2]})) + ICON=( + "" + "" + "" + ) + + if [[ $VOLUME -gt 50 ]]; then + printf "%s" "${ICON[0]} " + elif [[ $VOLUME -gt 25 ]]; then + printf "%s" "${ICON[1]} " + elif [[ $VOLUME -ge 0 ]]; then + printf "%s" "${ICON[2]} " + fi + + printf "$VOLUME%%\n" + fi + fi +done + +exit 0 diff --git a/waybar/scroll-firefox.sh b/waybar/scroll-firefox.sh new file mode 100755 index 0000000..2f634e2 --- /dev/null +++ b/waybar/scroll-firefox.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +zscroll -p ' | ' --delay 0.2 \ + --length 30 \ + --match-command "$HOME/.config/waybar/scripts/firefox-status.sh" \ + --match-text ' ' "" \ + --match-text ' ' "--scroll 0" \ + --update-interval 1 \ + --update-check true "$HOME/.config/waybar/scripts/firefox-status.sh" & +wait diff --git a/waybar/scroll-mpd.sh b/waybar/scroll-mpd.sh new file mode 100755 index 0000000..39b9046 --- /dev/null +++ b/waybar/scroll-mpd.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +zscroll -p ' | ' --delay 0.2 \ + --length 30 \ + --match-command "$HOME/.config/waybar/scripts/escape-mpc-pango.sh 'mpc status'" \ + --match-text "playing" "--before-text ' '" \ + --match-text "paused" "--before-text ' ' --scroll 0" \ + --match-text "^volume:" "--before-text '' --scroll 0 --after-text ''" \ + --update-interval 1 \ + --update-check true "$HOME/.config/waybar/scripts/escape-mpc-pango.sh 'mpc current'" & +wait diff --git a/waybar/wttr.sh b/waybar/wttr.sh new file mode 100755 index 0000000..51c6faf --- /dev/null +++ b/waybar/wttr.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +for _ in {1..5}; do + text="$(curl -s "https://wttr.in/$1?format=1" | sed -E "s/\s+/ /g")" + if [[ "$?" = 0 ]]; then + # text="$(echo "$text" | awk '{print $2}')" + tooltip=$(curl -s "https://wttr.in/$1?format=4") + if [ "$?" = 0 ]; then + tooltip=$(echo "$tooltip" | sed -E "s/\s+/ /g") + echo "{\"text\":\"$text\", \"tooltip\":\"$tooltip\"}" + exit + fi + fi + sleep 2 +done +echo "{\"text\":\"error\", \"tooltip\":\"error\"}"