From dc3a426d417cd8f0ba7202101d1a41130ff72f09 Mon Sep 17 00:00:00 2001 From: Ryan Kes Date: Tue, 10 Dec 2019 13:40:20 +0100 Subject: [PATCH] Now use bspwm as window manager --- README.org | 5 +- {dwm => bspwm}/.Xresources.desktop | 0 {dwm => bspwm}/.Xresources.thinkpad | 0 bspwm/.config/bspwm/bspwmrc | 78 +++ {dwm => bspwm}/.local/bin/lock | 0 {dwm => bspwm}/.local/bin/maimpick | 0 bspwm/.local/bin/scratch | 2 + {dwm => bspwm}/.local/bin/screen_desktop | 0 {dwm => bspwm}/.xinitrc | 2 +- dwm/.dwm/autostart.sh | 61 -- .../polybar/bin/isactive-bluetooth/README.md | 13 + .../isactive-bluetooth/isactive-bluetooth.sh | 7 + .../polybar/bin/player-mpris-tail/README.md | 164 ++++++ .../player-mpris-tail/player-mpris-tail.py | 532 ++++++++++++++++++ .../bin/player-mpris-tail/screenshots/1.png | Bin 0 -> 9199 bytes .../bin/player-mpris-tail/screenshots/2.png | Bin 0 -> 6192 bytes .../bin/updates-arch-combined/README.md | 24 + .../updates-arch-combined/screenshots/1.png | Bin 0 -> 3039 bytes .../updates-arch-combined.sh | 16 + polybar/.config/polybar/config | 422 ++++++++++++++ polybar/.local/bin/polybar-desktop | 13 + polybar/.local/bin/polybar-laptop | 13 + sxhkd/.config/sxhkd/sxhkdrc | 132 ++++- zsh/.zshenv | 5 +- 24 files changed, 1415 insertions(+), 74 deletions(-) rename {dwm => bspwm}/.Xresources.desktop (100%) rename {dwm => bspwm}/.Xresources.thinkpad (100%) create mode 100755 bspwm/.config/bspwm/bspwmrc rename {dwm => bspwm}/.local/bin/lock (100%) rename {dwm => bspwm}/.local/bin/maimpick (100%) create mode 100755 bspwm/.local/bin/scratch rename {dwm => bspwm}/.local/bin/screen_desktop (100%) rename {dwm => bspwm}/.xinitrc (95%) delete mode 100755 dwm/.dwm/autostart.sh create mode 100644 polybar/.config/polybar/bin/isactive-bluetooth/README.md create mode 100755 polybar/.config/polybar/bin/isactive-bluetooth/isactive-bluetooth.sh create mode 100644 polybar/.config/polybar/bin/player-mpris-tail/README.md create mode 100755 polybar/.config/polybar/bin/player-mpris-tail/player-mpris-tail.py create mode 100644 polybar/.config/polybar/bin/player-mpris-tail/screenshots/1.png create mode 100644 polybar/.config/polybar/bin/player-mpris-tail/screenshots/2.png create mode 100644 polybar/.config/polybar/bin/updates-arch-combined/README.md create mode 100644 polybar/.config/polybar/bin/updates-arch-combined/screenshots/1.png create mode 100755 polybar/.config/polybar/bin/updates-arch-combined/updates-arch-combined.sh create mode 100644 polybar/.config/polybar/config create mode 100755 polybar/.local/bin/polybar-desktop create mode 100755 polybar/.local/bin/polybar-laptop diff --git a/README.org b/README.org index b7e423c..d4e5ec9 100644 --- a/README.org +++ b/README.org @@ -12,6 +12,7 @@ A repository of my personal configuration files. ./clean-dead-symlinks #+END_SRC ** Configured Packages +- [[https://github.com/baskerville/bspwm][bspwm]] - [[https://dunst-project.org/][dunst]] - [[https://firejail.wordpress.com/][firejail]] - [[https://git-scm.com/][git]] @@ -33,8 +34,4 @@ A repository of my personal configuration files. - [[https://github.com/denysdovhan/spaceship-prompt][spaceship zsh theme]] ** Custom packages -These are custom packages I use (mostly -[[https://suckless.org/][suckless]]). -- [[https://github.com/alrayyes/dwm][dwm]] -- [[https://github.com/alrayyes/slstatus][slstatus]] - [[https://github.com/alrayyes/st][st]] diff --git a/dwm/.Xresources.desktop b/bspwm/.Xresources.desktop similarity index 100% rename from dwm/.Xresources.desktop rename to bspwm/.Xresources.desktop diff --git a/dwm/.Xresources.thinkpad b/bspwm/.Xresources.thinkpad similarity index 100% rename from dwm/.Xresources.thinkpad rename to bspwm/.Xresources.thinkpad diff --git a/bspwm/.config/bspwm/bspwmrc b/bspwm/.config/bspwm/bspwmrc new file mode 100755 index 0000000..9aab0d7 --- /dev/null +++ b/bspwm/.config/bspwm/bspwmrc @@ -0,0 +1,78 @@ +#! /bin/sh + +sxhkd & + +# Set screen orientation if second monitor is connected +SCREENCOUNT=$(xrandr | grep -c "\*") + +# If multi screen add special mode for monitor so it supports 1440p over HDMI +if [ "$SCREENCOUNT" -eq 2 ]; then + if [ -f "$HOME/.local/bin/screen_desktop" ]; then + screen_desktop + # Make sure mouse is on main screen so windows are launched there + # xdotool mousemove 3360 1080 + bspc monitor DP-1 -d 2 3 4 5 6 7 8 9 10 + bspc monitor HDMI-2 -d 1 + fi + if [ -f "$HOME/.local/bin/polybar-desktop" ]; then + polybar-desktop + fi +else + if [ -f "$HOME/.local/bin/screen" ]; then + screen + fi + if [ -f "$HOME/.local/bin/polybar-laptop" ]; then + bspc monitor -d 1 2 3 4 5 6 7 8 9 0 + polybar-laptop + fi +fi + +# lock screen after x minutes and on laptop close lid +xautolock -time 10 -locker ~/.local/bin/lock & +xss-lock -- ~/.local/bin/lock & +xcompmgr & + +if [ -e ~/.cache/wall1.png ] && [ -e ~/.cache/wall2.png ]; then + xwallpaper --output HDMI-2 --zoom ~/.cache/wall2.png --output DP-1 --zoom ~/.cache/wall1.png & +elif [ -e ~/.cache/wall1.png ]; then + xwallpaper --output eDP1 --zoom ~/.cache/wall1.png & +fi + +# switch off microphones +amixer -c 2 set Mic nocap +amixer -c 3 set Mic nocap + +unclutter & +dunst & +slstatus & + +redshift-gtk & +nm-applet & +syncthing-gtk & +gpodder & + +spotify & + +if [ -e /usr/bin/firefox ]; then + firefox & +elif [ -e /usr/bin/iceweasel ]; then + iceweasel & +fi + +bspc config border_width 2 +bspc config window_gap 12 + +bspc config split_ratio 0.52 +bspc config borderless_monocle true +bspc config gapless_monocle true + +bspc rule -a "Syncthing GTK" state=floating +bspc rule -a Gimp state=floating +bspc rule -a Gpodder desktop='^4' +bspc rule -a Spotify desktop='^4' +bspc rule -a Slack desktop='^4' +bspc rule -a Emacs state=tiled +bspc rule -a iceweasel desktop='^9' +bspc rule -a Firefox desktop='^9' +bspc rule -a scratchpad sticky=on state=floating +bspc rule -a scratchmacs sticky=on state=floating diff --git a/dwm/.local/bin/lock b/bspwm/.local/bin/lock similarity index 100% rename from dwm/.local/bin/lock rename to bspwm/.local/bin/lock diff --git a/dwm/.local/bin/maimpick b/bspwm/.local/bin/maimpick similarity index 100% rename from dwm/.local/bin/maimpick rename to bspwm/.local/bin/maimpick diff --git a/bspwm/.local/bin/scratch b/bspwm/.local/bin/scratch new file mode 100755 index 0000000..890b3f4 --- /dev/null +++ b/bspwm/.local/bin/scratch @@ -0,0 +1,2 @@ +#!/usr/bin/sh +st -c scratchpad diff --git a/dwm/.local/bin/screen_desktop b/bspwm/.local/bin/screen_desktop similarity index 100% rename from dwm/.local/bin/screen_desktop rename to bspwm/.local/bin/screen_desktop diff --git a/dwm/.xinitrc b/bspwm/.xinitrc similarity index 95% rename from dwm/.xinitrc rename to bspwm/.xinitrc index a2d37ff..e726a86 100644 --- a/dwm/.xinitrc +++ b/bspwm/.xinitrc @@ -11,4 +11,4 @@ export _JAVA_AWT_WM_NONREPARENTING=1 export AWT_TOOLKIT=MToolkit wmname LG3D -exec dwm +exec bspwm diff --git a/dwm/.dwm/autostart.sh b/dwm/.dwm/autostart.sh deleted file mode 100755 index 8bfe46e..0000000 --- a/dwm/.dwm/autostart.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/sh - -# Set working directory to home -cd ~ - -# Set screen orientation if second monitor is connected -SCREENCOUNT=$(xrandr | grep -c "\*") - -# If multi screen add special mode for monitor so it supports 1440p over HDMI -if [ "$SCREENCOUNT" -eq 2 ] -then - if [ -f "$HOME/.local/bin/screen_desktop" ] - then - screen_desktop - # Make sure mouse is on main screen so windows are launched there - xdotool mousemove 3360 1080 - fi -else - if [ -f "$HOME/.local/bin/screen" ] - then - screen - fi -fi - -# lock screen after x minutes and on laptop close lid -xautolock -time 10 -locker ~/.local/bin/lock & -xss-lock -- ~/.local/bin/lock & -xcompmgr & -sxhkd & - -if [ -e ~/.cache/wall1.png ] && [ -e ~/.cache/wall2.png ] -then - xwallpaper --output HDMI-2 --zoom ~/.cache/wall2.png --output DP-1 --zoom ~/.cache/wall1.png & -elif [ -e ~/.cache/wall1.png ] -then - xwallpaper --output eDP1 --zoom ~/.cache/wall1.png & -fi - -# switch off microphones -amixer -c 2 set Mic nocap -amixer -c 3 set Mic nocap - -unclutter & -dunst & -slstatus & - -redshift-gtk & -nm-applet & -syncthing-gtk & -gpodder & - -exec st -c tmux -e tmux & -exec spotify & - -if [ -e /usr/bin/firefox ] -then - exec firefox & -elif [ -e /usr/bin/iceweasel ] -then - exec iceweasel & -fi diff --git a/polybar/.config/polybar/bin/isactive-bluetooth/README.md b/polybar/.config/polybar/bin/isactive-bluetooth/README.md new file mode 100644 index 0000000..a97abb0 --- /dev/null +++ b/polybar/.config/polybar/bin/isactive-bluetooth/README.md @@ -0,0 +1,13 @@ +# Script: isactive-bluetooth + +A script that shows if bluetooth is on or off. + + +## Module + +```ini +[module/isactive-bluetooth] +type = custom/script +exec = ~/polybar-scripts/isactive-bluetooth.sh +interval = 10 +``` diff --git a/polybar/.config/polybar/bin/isactive-bluetooth/isactive-bluetooth.sh b/polybar/.config/polybar/bin/isactive-bluetooth/isactive-bluetooth.sh new file mode 100755 index 0000000..f16935f --- /dev/null +++ b/polybar/.config/polybar/bin/isactive-bluetooth/isactive-bluetooth.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ "$(systemctl is-active bluetooth.service)" = "active" ]; then + echo "" +else + echo "" +fi diff --git a/polybar/.config/polybar/bin/player-mpris-tail/README.md b/polybar/.config/polybar/bin/player-mpris-tail/README.md new file mode 100644 index 0000000..dc1fe25 --- /dev/null +++ b/polybar/.config/polybar/bin/player-mpris-tail/README.md @@ -0,0 +1,164 @@ +# Script: player-mpris-tail + +This script displays the current track and the play-pause status without polling. Information is obtained by listening to MPRIS events, so it is updated instantaneously on change. + +![player-mpris-tail](screenshots/1.png) ![player-mpris-tail](screenshots/2.png) + + +## Dependencies + +* `python-dbus` +* `python-gobject` +* `python-gi` + + +## Configuration + +The format of the output can be defined by passing an `-f` or `--format` argument. This argument supports metadata replacement using `{tag}` (e.g. `{title}`) as well as more advanced formatting, described below. + +Players can be blacklisted by passing a `-b` or `--blacklist` argument. As an example, VLC can be blacklisted by passing `-b vlc`. To get a list of the current running players (and their status), run the script as `player-mpris-tail.py list`. + + +### Commands + +The current player can be controlled by passing one of the following commands: + +Command | Description +---|--- +play | Play the current track +pause | Pause the currently playing track +play-pause | Play the current track or unpause it if currently paused +stop | Stop playback +previous | Move to the previous track +next | Move to the next track +raise | Tell the current player to focus its window + +General information about the current state can be printed using the following commands: + +Command | Description +---|--- +status | Print the normal output and exit immediately +current | Print the currently detected player and its status +list | List the detected players and their status +metadata | Print the metadata object for the current track + + +### Arguments + +The following arguments are supported: + +Argument | Description | Default +---|---|--- +-b, --blacklist | Blacklist / Ignore the given player +-f, --format | Use the given `format` string | `{icon} {artist} - {title}` +--truncate-text | Use the given string as the end of truncated text | `…` +--icon-playing | Use the given text as the playing icon | `⏵` +--icon-paused | Use the given text as the paused icon | `⏸` +--icon-stopped | Use the given text as the stopped icon | `⏹` +--icon-none | Use the given text as the icon for when no player is active | `` + + +### Formatting + +Tags can be printed by surrounding them with `{` and `}`. Polybar formatting can also be given and will be passed through, including substituted tags and formatters. + + +### Tags + +The supported tags are: + +Tag | Description +---|--- +artist | The artist of the current track +album | The album of the current track +title | The title of the current track +track | The track number of the current track +length | The length of the current track +genre | The genre of the current track +disc | The disc number of the current track +date | The date of the current track +year | The year of the current track +cover | The URL of the cover of the current track +icon | The icon for the current status (playing / paused / stopped / none) +icon-reversed | The pause icon when playing, else the play icon + + +### String formatters + +Parts of the `format` string can be manipulated by surrounding them with `{:` and `:}` and prepending a formatter followed by a `:` (e.g. `{:t20:by {artist}:}`) + +The following formatters are supported: + +Formatter | Argument | Description | Example | Output +---|---|---|---|--- +`tag` | | Only print the string if `tag` exists | `{:album: on {album}:}` | ` on Album Name` +w | Number | Limit the width of the string to `number` | `{:w3:Hello:}` | `Hel` +t | Number | Truncate width of the string to `number`. If the string is shorter than or equal to `number` it is printed as given, else the string is truncated and appended a truncator text | `{:t3:Hello:}` | `He…` + + +## Module + +### Basic output + +```ini +[module/player-mpris-tail] +type = custom/script +exec = ~/polybar-scripts/player-mpris-tail.py -f '{icon} {artist} - {title}' +tail = true +``` + +Example: `⏵ Artist - Title` + + +### Basic output and mouse controls + +```ini +[module/player-mpris-tail] +type = custom/script +exec = ~/polybar-scripts/player-mpris-tail.py -f '{icon} {artist} - {title}' +tail = true +click-left = ~/polybar-scripts/player-mpris-tail.py previous & +click-right = ~/polybar-scripts/player-mpris-tail.py next & +click-middle = ~/polybar-scripts/player-mpris-tail.py play-pause & +``` + +Example: `⏵ Artist - Title` + + +### Output using formatters + +```ini +[module/player-mpris-tail] +type = custom/script +exec = ~/polybar-scripts/player-mpris-tail.py -f '{icon} {:artist:t5:{artist}:}{:artist: - :}{:t4:{title}:}' +tail = true +click-left = ~/polybar-scripts/player-mpris-tail.py previous & +click-right = ~/polybar-scripts/player-mpris-tail.py next & +click-middle = ~/polybar-scripts/player-mpris-tail.py play-pause & +``` + +Example: `⏵ Artis… - Titl…` or `⏵ Titl…` + + +### Output using formatters and Polybar action handlers + +```ini +[module/player-mpris-tail] +type = custom/script +exec = ~/polybar-scripts/player-mpris-tail.py -f '{icon} {:artist:t18:{artist}:}{:artist: - :}{:t20:{title}:} %{A1:~/polybar-scripts/player-mpris-tail.py previous:} ⏮ %{A} %{A1:~/polybar-scripts/player-mpris-tail.py play-pause:} {icon-reversed} %{A} %{A1:~/polybar-scripts/player-mpris-tail.py next:} ⏭ %{A}' +tail = true +``` + +Example: `⏵ Artis… - Titl… ⏮ ⏸ ⏭ ` or `⏵ Titl… ⏮ ⏸ ⏭ ` or `⏸ Titl… ⏮ ⏵ ⏭ ` + + +### Output using formatters, Polybar action handlers and blacklisting + +```ini +[module/player-mpris-tail] +type = custom/script +exec = ~/polybar-scripts/player-mpris-tail.py -f '{icon} {:artist:t18:{artist}:}{:artist: - :}{:t20:{title}:} %{A1:~/polybar-scripts/player-mpris-tail.py previous -b vlc -b plasma-browser-integration:} ⏮ %{A} %{A1:~/polybar-scripts/player-mpris-tail.py play-pause -b vlc -b plasma-browser-integration:} {icon-reversed} %{A} %{A1:~/polybar-scripts/player-mpris-tail.py next -b vlc -b plasma-browser-integration:} ⏭ %{A}' -b vlc -b plasma-browser-integration +tail = true +``` + +Example: `⏵ Artis… - Titl… ⏮ ⏸ ⏭ ` or `⏵ Titl… ⏮ ⏸ ⏭ ` or `⏸ Titl… ⏮ ⏵ ⏭ ` diff --git a/polybar/.config/polybar/bin/player-mpris-tail/player-mpris-tail.py b/polybar/.config/polybar/bin/player-mpris-tail/player-mpris-tail.py new file mode 100755 index 0000000..3f6a2c4 --- /dev/null +++ b/polybar/.config/polybar/bin/player-mpris-tail/player-mpris-tail.py @@ -0,0 +1,532 @@ +#!/usr/bin/env python3 + +import sys +import dbus +import os +from operator import itemgetter +import argparse +import re +from urllib.parse import unquote +import time +from dbus.mainloop.glib import DBusGMainLoop +from gi.repository import GLib +DBusGMainLoop(set_as_default=True) + + +FORMAT_STRING = '{icon} {artist} - {title}' +FORMAT_REGEX = re.compile(r'(\{:(?P.*?)(:(?P[wt])(?P\d+))?:(?P.*?):\})', re.I) +FORMAT_TAG_REGEX = re.compile(r'(?P[wt])(?P\d+)') +SAFE_TAG_REGEX = re.compile(r'[{}]') + +class PlayerManager: + def __init__(self, blacklist = [], connect = True): + self.blacklist = blacklist + self._connect = connect + self._session_bus = dbus.SessionBus() + self.players = {} + + self.print_queue = [] + self.connected = False + self.player_states = {} + + self.refreshPlayerList() + + if self._connect: + self.connect() + loop = GLib.MainLoop() + try: + loop.run() + except KeyboardInterrupt: + print("interrupt received, stopping…") + + def connect(self): + self._session_bus.add_signal_receiver(self.onOwnerChangedName, 'NameOwnerChanged') + self._session_bus.add_signal_receiver(self.onChangedProperties, 'PropertiesChanged', + path = '/org/mpris/MediaPlayer2', + sender_keyword='sender') + + def onChangedProperties(self, interface, properties, signature, sender = None): + if sender in self.players: + player = self.players[sender] + # If we know this player, but haven't been able to set up a signal handler + if 'properties_changed' not in player._signals: + # Then trigger the signal handler manually + player.onPropertiesChanged(interface, properties, signature) + else: + # If we don't know this player, get its name and add it + bus_name = self.getBusNameFromOwner(sender) + self.addPlayer(bus_name, sender) + player = self.players[sender] + player.onPropertiesChanged(interface, properties, signature) + + def onOwnerChangedName(self, bus_name, old_owner, new_owner): + if self.busNameIsAPlayer(bus_name): + if new_owner and not old_owner: + self.addPlayer(bus_name, new_owner) + elif old_owner and not new_owner: + self.removePlayer(old_owner) + else: + self.changePlayerOwner(bus_name, old_owner, new_owner) + + def getBusNameFromOwner(self, owner): + player_bus_names = [ bus_name for bus_name in self._session_bus.list_names() if self.busNameIsAPlayer(bus_name) ] + for player_bus_name in player_bus_names: + player_bus_owner = self._session_bus.get_name_owner(player_bus_name) + if owner == player_bus_owner: + return player_bus_name + + def busNameIsAPlayer(self, bus_name): + return bus_name.startswith('org.mpris.MediaPlayer2') and bus_name.split('.')[3] not in self.blacklist + + def refreshPlayerList(self): + player_bus_names = [ bus_name for bus_name in self._session_bus.list_names() if self.busNameIsAPlayer(bus_name) ] + for player_bus_name in player_bus_names: + self.addPlayer(player_bus_name) + if self.connected != True: + self.connected = True + self.printQueue() + + def addPlayer(self, bus_name, owner = None): + player = Player(self._session_bus, bus_name, owner = owner, connect = self._connect, _print = self.print) + self.players[player.owner] = player + + def removePlayer(self, owner): + if owner in self.players: + self.players[owner].disconnect() + del self.players[owner] + # If there are no more players, clear the output + if len(self.players) == 0: + _printFlush(ICON_NONE) + # Else, print the output of the next active player + else: + players = self.getSortedPlayerOwnerList() + if len(players) > 0: + self.players[players[0]].printStatus() + + def changePlayerOwner(self, bus_name, old_owner, new_owner): + player = Player(self._session_bus, bus_name, owner = new_owner, connect = self._connect, _print = self.print) + self.players[new_owner] = player + del self.players[old_owner] + + # Get a list of player owners sorted by current status and age + def getSortedPlayerOwnerList(self): + players = [ + { + 'number': int(owner.split('.')[-1]), + 'status': 2 if player.status == 'playing' else 1 if player.status == 'paused' else 0, + 'owner': owner + } + for owner, player in self.players.items() + ] + return [ info['owner'] for info in reversed(sorted(players, key=itemgetter('status', 'number'))) ] + + # Get latest player that's currently playing + def getCurrentPlayer(self): + playing_players = [ + player_owner for player_owner in self.getSortedPlayerOwnerList() + if + self.players[player_owner].status == 'playing' or + self.players[player_owner].status == 'paused' + ] + return self.players[playing_players[0]] if playing_players else None + + def print(self, status, player): + self.player_states[player.bus_name] = status + + if self.connected: + current_player = self.getCurrentPlayer() + if current_player != None: + _printFlush(self.player_states[current_player.bus_name]) + else: + _printFlush(ICON_STOPPED) + else: + self.print_queue.append([status, player]) + + def printQueue(self): + for args in self.print_queue: + self.print(args[0], args[1]) + self.print_queue.clear() + + +class Player: + def __init__(self, session_bus, bus_name, owner = None, connect = True, _print = None): + self._session_bus = session_bus + self.bus_name = bus_name + self._disconnecting = False + self.__print = _print + + self.metadata = { + 'artist' : '', + 'album' : '', + 'title' : '', + 'track' : 0 + } + + self._rate = 1. + self._positionAtLastUpdate = 0. + self._timeAtLastUpdate = time.time() + self._positionTimerRunning = False + + self._metadata = None + self.status = 'stopped' + self.icon = ICON_NONE + self.icon_reversed = ICON_PLAYING + if owner is not None: + self.owner = owner + else: + self.owner = self._session_bus.get_name_owner(bus_name) + self._obj = self._session_bus.get_object(self.bus_name, '/org/mpris/MediaPlayer2') + self._properties_interface = dbus.Interface(self._obj, dbus_interface='org.freedesktop.DBus.Properties') + self._introspect_interface = dbus.Interface(self._obj, dbus_interface='org.freedesktop.DBus.Introspectable') + self._media_interface = dbus.Interface(self._obj, dbus_interface='org.mpris.MediaPlayer2') + self._player_interface = dbus.Interface(self._obj, dbus_interface='org.mpris.MediaPlayer2.Player') + self._introspect = self._introspect_interface.get_dbus_method('Introspect', dbus_interface=None) + self._getProperty = self._properties_interface.get_dbus_method('Get', dbus_interface=None) + self._playerPlay = self._player_interface.get_dbus_method('Play', dbus_interface=None) + self._playerPause = self._player_interface.get_dbus_method('Pause', dbus_interface=None) + self._playerPlayPause = self._player_interface.get_dbus_method('PlayPause', dbus_interface=None) + self._playerStop = self._player_interface.get_dbus_method('Stop', dbus_interface=None) + self._playerPrevious = self._player_interface.get_dbus_method('Previous', dbus_interface=None) + self._playerNext = self._player_interface.get_dbus_method('Next', dbus_interface=None) + self._playerRaise = self._media_interface.get_dbus_method('Raise', dbus_interface=None) + self._signals = {} + + self.refreshPosition() + self.refreshStatus() + self.refreshMetadata() + + if connect: + self.printStatus() + self.connect() + + def play(self): + self._playerPlay() + def pause(self): + self._playerPause() + def playpause(self): + self._playerPlayPause() + def stop(self): + self._playerStop() + def previous(self): + self._playerPrevious() + def next(self): + self._playerNext() + def raisePlayer(self): + self._playerRaise() + + def connect(self): + if self._disconnecting is not True: + introspect_xml = self._introspect(self.bus_name, '/') + if 'TrackMetadataChanged' in introspect_xml: + self._signals['track_metadata_changed'] = self._session_bus.add_signal_receiver(self.onMetadataChanged, 'TrackMetadataChanged', self.bus_name) + self._signals['seeked'] = self._player_interface.connect_to_signal('Seeked', self.onSeeked) + self._signals['properties_changed'] = self._properties_interface.connect_to_signal('PropertiesChanged', self.onPropertiesChanged) + + def disconnect(self): + self._disconnecting = True + for signal_name, signal_handler in list(self._signals.items()): + signal_handler.remove() + del self._signals[signal_name] + + def refreshStatus(self): + # Some clients (VLC) will momentarily create a new player before removing it again + # so we can't be sure the interface still exists + try: + self.status = str(self._getProperty('org.mpris.MediaPlayer2.Player', 'PlaybackStatus')).lower() + self.updateIcon() + self.checkPositionTimer() + except dbus.exceptions.DBusException: + self.disconnect() + + def refreshMetadata(self): + # Some clients (VLC) will momentarily create a new player before removing it again + # so we can't be sure the interface still exists + try: + self._metadata = self._getProperty('org.mpris.MediaPlayer2.Player', 'Metadata') + self._parseMetadata() + except dbus.exceptions.DBusException: + self.disconnect() + + def updateIcon(self): + self.icon = ( + ICON_PLAYING if self.status == 'playing' else + ICON_PAUSED if self.status == 'paused' else + ICON_STOPPED if self.status == 'stopped' else + ICON_NONE + ) + self.icon_reversed = ( + ICON_PAUSED if self.status == 'playing' else + ICON_PLAYING + ) + + def _print(self, status): + self.__print(status, self) + + def _parseMetadata(self): + if self._metadata != None: + artist = _getProperty(self._metadata, 'xesam:artist', ['']) + if artist != None and len(artist): + self.metadata['artist'] = re.sub(SAFE_TAG_REGEX, """\1\1""", artist[0]) + else: + artists = _getProperty(self._metadata, 'xesam:artists', ['']) + if artists != None and len(artists): + # Note: This only grabs the first artist + self.metadata['artist'] = re.sub(SAFE_TAG_REGEX, """\1\1""", artists[0]) + else: + self.metadata['artist'] = ''; + self.metadata['album'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _getProperty(self._metadata, 'xesam:album', '')) + self.metadata['title'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _getProperty(self._metadata, 'xesam:title', '')) + self.metadata['track'] = _getProperty(self._metadata, 'xesam:trackNumber', '') + length = str(_getProperty(self._metadata, 'xesam:length', '')) + if not len(length): + length = str(_getProperty(self._metadata, 'mpris:length', '')) + if len(length): + self.metadata['length'] = int(length) + else: + self.metadata['length'] = 0 + self.metadata['genre'] = _getProperty(self._metadata, 'xesam:genre', '') + self.metadata['disc'] = _getProperty(self._metadata, 'xesam:discNumber', '') + self.metadata['date'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _getProperty(self._metadata, 'xesam:contentCreated', '')) + self.metadata['year'] = re.sub(SAFE_TAG_REGEX, """\1\1""", self.metadata['date'][0:4]) + self.metadata['url'] = _getProperty(self._metadata, 'xesam:url', '') + self.metadata['filename'] = os.path.basename(self.metadata['url']) + cover = _getProperty(self._metadata, 'xesam:artUrl', '') + if not len(cover): + cover = _getProperty(self._metadata, 'mpris:artUrl', '') + if len(cover): + self.metadata['cover'] = re.sub(SAFE_TAG_REGEX, """\1\1""", cover) + else: + self.metadata['cover'] = '' + + self.metadata['duration'] = _getDuration(self.metadata['length']) + + def onMetadataChanged(self, track_id, metadata): + self.refreshMetadata() + self.printStatus() + + def onPropertiesChanged(self, interface, properties, signature): + updated = False + if dbus.String('Metadata') in properties: + _metadata = properties[dbus.String('Metadata')] + if _metadata != self._metadata: + self._metadata = _metadata + self._parseMetadata() + updated = True + if dbus.String('PlaybackStatus') in properties: + status = str(properties[dbus.String('PlaybackStatus')]).lower() + if status != self.status: + self.status = status + self.checkPositionTimer() + self.updateIcon() + updated = True + if dbus.String('Rate') in properties and dbus.String('PlaybackStatus') not in properties: + self.refreshStatus() + if NEEDS_POSITION and dbus.String('Rate') in properties: + rate = properties[dbus.String('Rate')] + if rate != self._rate: + self._rate = rate + self.refreshPosition() + + if updated: + self.refreshPosition() + self.printStatus() + + def checkPositionTimer(self): + if NEEDS_POSITION and self.status == 'playing' and not self._positionTimerRunning: + self._positionTimerRunning = True + GLib.timeout_add_seconds(1, self._positionTimer) + + def onSeeked(self, position): + self.refreshPosition() + self.printStatus() + + def _positionTimer(self): + self.printStatus() + self._positionTimerRunning = self.status == 'playing' + return self._positionTimerRunning + + def refreshPosition(self): + try: + time_us = self._getProperty('org.mpris.MediaPlayer2.Player', 'Position') + except dbus.exceptions.DBusException: + time_us = 0 + + self._timeAtLastUpdate = time.time() + self._positionAtLastUpdate = time_us / 1000000 + + def _getPosition(self): + if self.status == 'playing': + return self._positionAtLastUpdate + self._rate * (time.time() - self._timeAtLastUpdate) + else: + return self._positionAtLastUpdate + + def _statusReplace(self, match, metadata): + tag = match.group('tag') + format = match.group('format') + formatlen = match.group('formatlen') + text = match.group('text') + tag_found = False + reversed_tag = False + + if tag.startswith('-'): + tag = tag[1:] + reversed_tag = True + + if format is None: + tag_is_format_match = re.match(FORMAT_TAG_REGEX, tag) + if tag_is_format_match: + format = tag_is_format_match.group('format') + formatlen = tag_is_format_match.group('formatlen') + tag_found = True + if format is not None: + text = text.format_map(CleanSafeDict(**metadata)) + if format == 'w': + formatlen = int(formatlen) + text = text[:formatlen] + elif format == 't': + formatlen = int(formatlen) + if len(text) > formatlen: + text = text[:max(formatlen - len(TRUNCATE_STRING), 0)] + TRUNCATE_STRING + if tag_found is False and tag in metadata and len(metadata[tag]): + tag_found = True + + if reversed_tag: + tag_found = not tag_found + + if tag_found: + return text + else: + return '' + + def printStatus(self): + if self.status in [ 'playing', 'paused' ]: + metadata = { **self.metadata, 'icon': self.icon, 'icon-reversed': self.icon_reversed } + if NEEDS_POSITION: + metadata['position'] = time.strftime("%M:%S", time.gmtime(self._getPosition())) + # replace metadata tags in text + text = re.sub(FORMAT_REGEX, lambda match: self._statusReplace(match, metadata), FORMAT_STRING) + # restore polybar tag formatting and replace any remaining metadata tags after that + try: + text = re.sub(r'􏿿p􏿿(.*?)􏿿p􏿿(.*?)􏿿p􏿿(.*?)􏿿p􏿿', r'%{\1}\2%{\3}', text.format_map(CleanSafeDict(**metadata))) + except: + print("Invalid format string") + self._print(text) + else: + self._print(ICON_STOPPED) + + +def _dbusValueToPython(value): + if isinstance(value, dbus.Dictionary): + return {_dbusValueToPython(key): _dbusValueToPython(value) for key, value in value.items()} + elif isinstance(value, dbus.Array): + return [ _dbusValueToPython(item) for item in value ] + elif isinstance(value, dbus.Boolean): + return int(value) == 1 + elif ( + isinstance(value, dbus.Byte) or + isinstance(value, dbus.Int16) or + isinstance(value, dbus.UInt16) or + isinstance(value, dbus.Int32) or + isinstance(value, dbus.UInt32) or + isinstance(value, dbus.Int64) or + isinstance(value, dbus.UInt64) + ): + return int(value) + elif isinstance(value, dbus.Double): + return float(value) + elif ( + isinstance(value, dbus.ObjectPath) or + isinstance(value, dbus.Signature) or + isinstance(value, dbus.String) + ): + return unquote(str(value)) + +def _getProperty(properties, property, default = None): + value = default + if not isinstance(property, dbus.String): + property = dbus.String(property) + if property in properties: + value = properties[property] + return _dbusValueToPython(value) + else: + return value + +def _getDuration(t: int): + seconds = t / 1000000 + return time.strftime("%M:%S", time.gmtime(seconds)) + + +class CleanSafeDict(dict): + def __missing__(self, key): + return '{{{}}}'.format(key) + + +""" +Seems to assure print() actually prints when no terminal is connected +""" + +_last_status = '' +def _printFlush(status, **kwargs): + global _last_status + if status != _last_status: + print(status, **kwargs) + sys.stdout.flush() + _last_status = status + + + +parser = argparse.ArgumentParser() +parser.add_argument('command', help="send the given command to the active player", + choices=[ 'play', 'pause', 'play-pause', 'stop', 'previous', 'next', 'status', 'list', 'current', 'metadata', 'raise' ], + default=None, + nargs='?') +parser.add_argument('-b', '--blacklist', help="ignore a player by it's bus name. Can be be given multiple times (e.g. -b vlc -b audacious)", + action='append', + metavar="BUS_NAME", + default=[]) +parser.add_argument('-f', '--format', default='{icon} {:artist:{artist} - :}{:title:{title}:}{:-title:{filename}:}') +parser.add_argument('--truncate-text', default='…') +parser.add_argument('--icon-playing', default='⏵') +parser.add_argument('--icon-paused', default='⏸') +parser.add_argument('--icon-stopped', default='⏹') +parser.add_argument('--icon-none', default='') +args = parser.parse_args() + +FORMAT_STRING = re.sub(r'%\{(.*?)\}(.*?)%\{(.*?)\}', r'􏿿p􏿿\1􏿿p􏿿\2􏿿p􏿿\3􏿿p􏿿', args.format) +NEEDS_POSITION = "{position}" in FORMAT_STRING + +TRUNCATE_STRING = args.truncate_text +ICON_PLAYING = args.icon_playing +ICON_PAUSED = args.icon_paused +ICON_STOPPED = args.icon_stopped +ICON_NONE = args.icon_none + +if args.command is None: + PlayerManager(blacklist = args.blacklist) +else: + player_manager = PlayerManager(blacklist = args.blacklist, connect = False) + current_player = player_manager.getCurrentPlayer() + if args.command == 'play' and current_player: + current_player.play() + elif args.command == 'pause' and current_player: + current_player.pause() + elif args.command == 'play-pause' and current_player: + current_player.playpause() + elif args.command == 'stop' and current_player: + current_player.stop() + elif args.command == 'previous' and current_player: + current_player.previous() + elif args.command == 'next' and current_player: + current_player.next() + elif args.command == 'status' and current_player: + current_player.printStatus() + elif args.command == 'list': + print("\n".join(sorted([ + "{} : {}".format(player.bus_name.split('.')[3], player.status) + for player in player_manager.players.values() ]))) + elif args.command == 'current' and current_player: + print("{} : {}".format(current_player.bus_name.split('.')[3], current_player.status)) + elif args.command == 'metadata' and current_player: + print(_dbusValueToPython(current_player._metadata)) + elif args.command == 'raise' and current_player: + current_player.raisePlayer() diff --git a/polybar/.config/polybar/bin/player-mpris-tail/screenshots/1.png b/polybar/.config/polybar/bin/player-mpris-tail/screenshots/1.png new file mode 100644 index 0000000000000000000000000000000000000000..686c7d619441e233715934fa19792f4ea7b1fc83 GIT binary patch literal 9199 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 FV1jO{*L(l~ literal 0 HcmV?d00001 diff --git a/polybar/.config/polybar/bin/player-mpris-tail/screenshots/2.png b/polybar/.config/polybar/bin/player-mpris-tail/screenshots/2.png new file mode 100644 index 0000000000000000000000000000000000000000..7aeabf3e7d5a9f08cdee1a8d786254a46b0786ae GIT binary patch literal 6192 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 O0000Or>J?c6WCNg64nG z=~RIp6fT#`0=MiMjk+5h9dl>SoYB>g5h?;ch}LfsSG*Rg>5F5HFizTsu2EJ&GPjG1h?+Mn^~8<`+E5Oq&YnGM^Ya~L0T9DNA6`MY z*;hBnR#60CB(AdN7?G^OApnT)$7!qq#a+C3!QH%h)6LAxi0Veo5diz+$B*vyn>TLt z(*_Zhn5=-MxF)l&j05NKODvxxqs3-n~0A=)s!+8lxW2g1i>G zV4aPPjU=kt-tz@KQGu5u1CGJ(bY!6XaY09x^XJc-{tFAg8tANr*@>#NipYj&4F_45 z69#JQ0)M#7ECl`1rAzM4ojbNzv$^e_J%8?2R#x1seq)Ra5@<>bKX~xKeg6D;fb#5c zV!c>f`c$FRaEr1mkiC5Q(!!ovdQ$zSr>6|~{B~HF0D4GNcrcX0 znuKr+z)H2^%SjpRj4tL$H^vp4RG`X^vSc6?(^zrD;bHfmdePYOdbV2R08IeQVfqt zhY-)XxKH?USXI#$ogHPY8OK`o!vp&tP{qcc)O{l8sS+oSL6vgt+I1@)zy%&(5!iqH z=MT$oI|p=lIB%upkAi;CNt3gHUAh^#t*y|eJlYp{=~l#O7>VYSyB{0!M>k4(XW83h zEq0pTvtzEr%Z8B=zk39bbR-xT58XtENp2(Zjl(5NXy%MkBzQjMuAK+z%4<>3_ca+@ z#iYTj5df~m`Wu@YN>W?qxhP26+m{hMn*G^(g3MWt8NTh83-1QKEeI?3r;GT#7+h5Wqu;7uPKtb{=)x%4=6gvIh(aXKw{PE?rB{_a2sgIjl#y&;IE%p$!!`q2 zO7NL(gt9LM{n2e~Za-c}xdH%>#5rhCpgGPFs0FGU8yoe&O7HKgSlDp_IG7+Qryvs5jhIh0?xw_1(MonzQ5PL>udJq_Z%_$F zpmHNBgKGpXuT=c8bJl5M^G4|fAn_3DQY_3)^j97DU6G*@1Jo zM`U7XHjZ4D=k$#A@~8zlqM-AT!Jh;$T*hXIvj&cMS!`Ab#vg(;-O|#MZ6I0##JAuJ z{rESoKHvl@2wsDS)%6BxqBCI>2gL-XI0gWC3<9?@hB?B{)KkmRoWSRjmTiPy{rRWq zd_iYTyh3DiNTd;A;DBSUtb7u!tQIEGLeS}eRY{Qd@88#%@JPbX*8Z6ikY6TG%t%9fS%hzckXD#7CbE0zNWk<<1c(h;Y7`0$~d zpPzR(bg=i!FSiA}KjXncSce*YY3aG~eNp8y5a>sYLa%VHkJ zyp(u&v6Y?jGS=ljWh9<%$G!?}-tGtLL20hQy(o?GkYy0%WAdc$law*Gy)aP;iSbG3 zL08r%qPQ^hAgvJeK_)qFpRglEkpmAE=(aZyt-$3q{Ts%Jl=r(u&A+ zSNM8N)U2svYiIj0U+CMFJhqCzpY50Zoj=+?T^3b7QzFmX9YjIDqonmuz3&?$6b6xL z=suhpgyuQC{}f%{(6^+cfu20zqi1|-UYjv%&0!F?$-P|vRziO@IOx4nV2x2-9JQW! z8&-sv%z5)I@JA#26%1SjptMXLN?KCi@fZBKM?^)JO^kJH4B*99l- zA@o0(AQ^1;`e7Mv#uoAZb1?E?x>SGjGT;1N{Y_-ZF6rOT>aKN6&ykibJ1Q%87}s4F zK5ua#m!204QruR$dSfBAV(+A3$;_DS%F*8kBKOg)@1U%XX)H{L>?nUDeY>}g?>_0? zkCdm`sZ+v-uf5Bs=C-lER~OqJ;d?-jCQ_ye!}1bXR@Q#M2Ob|5%z}?BL|rm@ukyVs z#*QLl_xs6f_foRh*Bm8$*Iuc5RLG3Y1wAUzfDiJ($( /dev/null | wc -l); then +if ! updates_aur=$(trizen -Su --aur --quiet | wc -l); then + updates_aur=0 +fi + +updates=$(("$updates_arch" + "$updates_aur")) + +if [ "$updates" -gt 0 ]; then + echo " $updates" +else + echo "" +fi diff --git a/polybar/.config/polybar/config b/polybar/.config/polybar/config new file mode 100644 index 0000000..ad9025d --- /dev/null +++ b/polybar/.config/polybar/config @@ -0,0 +1,422 @@ +;===================================================== +; +; To learn more about how to configure Polybar +; go to https://github.com/jaagr/polybar +; +; The README contains alot of information +; +;===================================================== + +[colors] +;background = ${xrdb:color0:#222} +background = #222 +background-alt = #444 +;foreground = ${xrdb:color7:#222} +foreground = #dfdfdf +foreground-alt = #555 +primary = #ffb52a +secondary = #e60053 +alert = #bd2c40 + +[bar/base] +width = 100% + +height = 27 +radius = 6.0 +fixed-center = false + +background = ${colors.background} +foreground = ${colors.foreground} + +line-size = 3 +line-color = #f00 + +border-size = 0 +border-color = #00000000 + +padding-left = 0 +padding-right = 2 + +module-margin-left = 1 +module-margin-right = 2 + +scroll-up = bspwm-desknext +scroll-down = bspwm-deskprev + +wm-restack = bspwm + +[bar/base-laptop] +inherit = "bar/base" +font-0 = FuraCode Nerd Font:pixelsize=14 + +[bar/base-desktop] +inherit = "bar/base" +font-0 = FuraCode Nerd Font:pixelsize=10 + +[bar/laptop-bottom] +inherit = bar/base-laptop +bottom = true +monitor = ${env:MONITOR:e-DP1} + +modules-center = mpd player-mpris-tail + +[bar/laptop-top] +inherit = bar/base-laptop +monitor = ${env:MONITOR:e-DP1} + +modules-left = bspwm +modules-center = tun0 tun1 wlan bluetooth +modules-right = github updates-arch-combined battery xbacklight filesystem volume memory cpu temperature date + +tray-position = right +tray-padding = 0 + +[bar/monitor1] +inherit = bar/base-desktop +monitor = ${env:MONITOR:DP-1} + +modules-left = bspwm +modules-center = mpd player-mpris-tail +modules-right = github updates-arch-combined bluetooth eth filesystem volume memory cpu temperature date + +tray-position = right +tray-padding = 0 + +[bar/monitor2] +inherit = bar/base-desktop +monitor = ${env:MONITOR:HDMI-2} +modules-left = bspwm + +[module/xwindow] +type = internal/xwindow +label = %title:0:30:...% + +[module/xkeyboard] +type = internal/xkeyboard +blacklist-0 = num lock + +format-prefix = " " +format-prefix-foreground = ${colors.foreground-alt} +format-prefix-underline = ${colors.secondary} + +label-layout = %layout% +label-layout-underline = ${colors.secondary} + +label-indicator-padding = 2 +label-indicator-margin = 1 +label-indicator-background = ${colors.secondary} +label-indicator-underline = ${colors.secondary} + +[module/filesystem] +type = internal/fs +interval = 25 + +mount-0 = / +mount-1 = /home + +label-mounted = %{F#0a81f5}%mountpoint%%{F-}: %percentage_used%% +label-unmounted = %mountpoint% not mounted +label-unmounted-foreground = ${colors.foreground-alt} + +[module/bspwm] +type = internal/bspwm + +label-focused-background = ${colors.background-alt} +label-focused-underline= ${colors.primary} +label-focused-padding = 2 + +label-occupied-padding = 2 + +label-urgent-background = ${colors.alert} +label-urgent-padding = 2 + +label-empty-foreground = ${colors.foreground-alt} +label-empty-padding = 2 + +pin-workspaces = true + +[module/mpd] +type = internal/mpd +format-online = + +icon-prev =  +icon-stop =  +icon-play =  +icon-pause =  +icon-next =  + +label-song-maxlen = 25 +label-song-ellipsis = true + +[module/xbacklight] +type = internal/xbacklight + +format =