From 0f0b9089a4fd6072773f4c0b19be73701fd0c203 Mon Sep 17 00:00:00 2001 From: Michel Date: Sat, 2 Nov 2024 15:57:09 +0100 Subject: [PATCH] Adds polybar --- .config/polybar/config.ini | 189 ++++++ .config/polybar/launch.sh | 14 + .config/polybar/scripts/player-mpris-tail.py | 554 ++++++++++++++++++ .config/polybar/scripts/polywins/polywins.awk | 127 ++++ .config/polybar/scripts/polywins/polywins.sh | 72 +++ .config/polybar/scripts/storage/Display.py | 13 + .../polybar/scripts/storage/SingleDisplay.py | 47 ++ .config/polybar/scripts/storage/Utils.py | 8 + .config/polybar/scripts/storage/config.json | 18 + .../polybar/scripts/storage/open-ranger.sh | 5 + .config/polybar/scripts/storage/overview.py | 31 + .config/polybar/scripts/storage/storage.sh | 12 + 12 files changed, 1090 insertions(+) create mode 100644 .config/polybar/config.ini create mode 100755 .config/polybar/launch.sh create mode 100755 .config/polybar/scripts/player-mpris-tail.py create mode 100755 .config/polybar/scripts/polywins/polywins.awk create mode 100755 .config/polybar/scripts/polywins/polywins.sh create mode 100644 .config/polybar/scripts/storage/Display.py create mode 100644 .config/polybar/scripts/storage/SingleDisplay.py create mode 100644 .config/polybar/scripts/storage/Utils.py create mode 100644 .config/polybar/scripts/storage/config.json create mode 100755 .config/polybar/scripts/storage/open-ranger.sh create mode 100755 .config/polybar/scripts/storage/overview.py create mode 100755 .config/polybar/scripts/storage/storage.sh diff --git a/.config/polybar/config.ini b/.config/polybar/config.ini new file mode 100644 index 0000000..fb4404e --- /dev/null +++ b/.config/polybar/config.ini @@ -0,0 +1,189 @@ +;========================================================== +; +; +; ██████╗ ██████╗ ██╗ ██╗ ██╗██████╗ █████╗ ██████╗ +; ██╔══██╗██╔═══██╗██║ ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗ +; ██████╔╝██║ ██║██║ ╚████╔╝ ██████╔╝███████║██████╔╝ +; ██╔═══╝ ██║ ██║██║ ╚██╔╝ ██╔══██╗██╔══██║██╔══██╗ +; ██║ ╚██████╔╝███████╗██║ ██████╔╝██║ ██║██║ ██║ +; ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ +; +; +; To learn more about how to configure Polybar +; go to https://github.com/polybar/polybar +; +; The README contains a lot of information +; +;========================================================== + +[colors] +background = #282A2E +background-alt = #373B41 +foreground = #C5C8C6 +primary = #F0C674 +secondary = #8ABEB7 +alert = #A54242 +disabled = #707880 + +bg-primary = #0D6EFD +bg-secondary = #343a40 +bg-orange = #fd7e14 + +[bar/main] +monitor = ${env:MONITOR:DisplayPort-3} +height = 30.5pt +width = 100% + +fixed-center = true + +; dpi = 96 + +module-margin = 0 + +background = #55000000 +foreground = ${colors.foreground} + +line-size = 3pt + +padding-left = 0 + +font-0 = JetBrains Mono:style=Regular;2 +font-1 = Font Awesome 5 Free,Font Awesome 5 Free Solid:style=Solid;2 +font-2 = Material Icons:style=Regular;3 +font-3 = Font Awesome 5 Brands,Font Awesome 5 Brands Regular:style=Regular;2 + +modules-left = music-player line systray +modules-center = xworkspaces +modules-right = pulseaudio-control-output fs-check memory cpu date + +cursor-click = pointer +cursor-scroll = ns-resize + +enable-ipc = true + +[bar/side-monitor] +inherit = bar/main + +modules-left = rofi +modules-right = date + +[module/systray] +type = internal/tray + +format-padding = 8pt +tray-spacing = 16pt +format-background = ${colors.bg-secondary} + +[module/xworkspaces] +type = internal/xworkspaces + +label-active = %name% +label-active-underline= ${colors.bg-secondary} +label-active-padding = 1 + +label-occupied = %name% +label-occupied-padding = 1 + +label-urgent = %name% +label-urgent-padding = 1 + +label-empty = %name% +label-empty-foreground = ${colors.disabled} +label-empty-padding = 1 + +format-foreground = ${colors.bg-secondary} +format-background = ${colors.bg-orange} +format-padding = 0 + +[module/memory] +type = internal/memory +interval = 2 +format-prefix = " " +format-prefix-foreground = ${colors.primary} +label-font = 2 +label = %percentage_used:2%% + +[module/cpu] +type = internal/cpu +interval = 2 +format-prefix = " " +format-prefix-font = 2 +format-prefix-foreground = ${colors.primary} +label = %percentage:2%% + +[module/date] +type = custom/script +exec = date +%r +interval = 1 + +padding = 1 + +label = %output% +label-foreground = ${colors.primary} + +format-background = ${colors.bg-secondary} +click-left = ~/.local/share/pages/dashboard/Dashboard + +[module/polywins] +type = custom/script +exec = ~/.config/polybar/scripts/polywins/polywins.sh +tail = true + +format-background = ${colors.bg-orange} + +[module/rofi] +type = custom/text +label =  +label-font = 2 +format-background = ${colors.background-alt} + +click-left = rofi -show drun + +[module/fs-check] +type = custom/script +exec = ~/.config/polybar/scripts/storage/overview.py +tail = true + +label = %output% +format-prefix =  +format-prefix-font = 2 +click-left = ~/.config/polybar/scripts/storage/open-ranger.sh ~ + +[module/pulseaudio-control-output] +type = custom/script +tail = true + +# Icons mixed from Font Awesome 5 and Material Icons +# You can copy-paste your options for each possible action, which is more +# trouble-free but repetitive, or apply only the relevant ones (for example +# --node-blacklist is only needed for next-node). +exec = pulseaudio-control --icons-volume " , " --icon-muted " " --node-nicknames-from "device.description" --node-nickname "alsa_output.usb-Lautsprecher_Teufel_GmbH_Teufel_CINEBAR_ONE_ABCDEF0123456789-00.analog-stereo: " --node-nickname "alsa_output.usb-Razer_Razer_Nari-00.pro-output-1: " listen +click-middle = exec pavucontrol & +click-right = pulseaudio-control togmute +click-left = pulseaudio-control --node-blacklist "alsa_output.usb-Razer_Razer_Nari-00.pro-output-0" next-node +scroll-up = pulseaudio-control --volume-max 100 up +scroll-down = pulseaudio-control --volume-max 100 down + +[module/music-player] +type = custom/script +exec = ~/.config/polybar/scripts/player-mpris-tail.py -f '{icon} {:artist:{artist}:}{:artist: - :}{title}' --icon-playing  --icon-paused  --icon-stopped  --icon-none  +tail = true +click-left = ~/.config/polybar/scripts/player-mpris-tail.py play-pause & +click-right = ~/.config/polybar/scripts/player-mpris-tail.py next & + +format-background = ${colors.bg-secondary} + +[module/line] +type = custom/text +label = | +format-padding = 0 +label-foreground = ${colors.bg-primary} + +[settings] +screenchange-reload = true +pseudo-transparency = true + +format-background = ${colors.bg-primary} +format-padding = 1 + +; vim:ft=dosini diff --git a/.config/polybar/launch.sh b/.config/polybar/launch.sh new file mode 100755 index 0000000..2596a16 --- /dev/null +++ b/.config/polybar/launch.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Terminate already running bar instances +# If all your bars have ipc enabled, you can use +polybar-msg cmd quit +# Otherwise you can use the nuclear option: +# killall -q polybar + +# Launch bar1 and bar2 +echo "---" | tee -a /tmp/polybar1.log /tmp/polybar2.log +polybar main 2>&1 | tee -a /tmp/polybar1.log & disown +MONITOR=DisplayPort-1 polybar side-monitor 2>&1 | tee -a /tmp/polybar2.log & disown + +echo "Bars launched..." diff --git a/.config/polybar/scripts/player-mpris-tail.py b/.config/polybar/scripts/player-mpris-tail.py new file mode 100755 index 0000000..93e7c27 --- /dev/null +++ b/.config/polybar/scripts/player-mpris-tail.py @@ -0,0 +1,554 @@ +#!/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, filter_list, block_mode = True, connect = True): + self.filter_list = filter_list + self.block_mode = block_mode + 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) + if bus_name is None: + return + 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): + if bus_name.startswith('org.mpris.MediaPlayer2') is False: + return False + name = bus_name.split('.')[3] + if self.block_mode is True: + return name not in self.filter_list + return name in self.filter_list + + 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: + # Obtain properties from _metadata + _artist = _getProperty(self._metadata, 'xesam:artist', ['']) + _album = _getProperty(self._metadata, 'xesam:album', '') + _title = _getProperty(self._metadata, 'xesam:title', '') + _track = _getProperty(self._metadata, 'xesam:trackNumber', '') + _genre = _getProperty(self._metadata, 'xesam:genre', ['']) + _disc = _getProperty(self._metadata, 'xesam:discNumber', '') + _length = _getProperty(self._metadata, 'xesam:length', 0) or _getProperty(self._metadata, 'mpris:length', 0) + _length_int = _length if type(_length) is int else int(float(_length)) + _fmt_length = ( # Formats using h:mm:ss if length > 1 hour, else m:ss + f'{_length_int/1e6//60:.0f}:{_length_int/1e6%60:02.0f}' + if _length_int < 3600*1e6 else + f'{_length_int/1e6//3600:.0f}:{_length_int/1e6%3600//60:02.0f}:{_length_int/1e6%60:02.0f}' + ) + _date = _getProperty(self._metadata, 'xesam:contentCreated', '') + _year = _date[0:4] if len(_date) else '' + _url = _getProperty(self._metadata, 'xesam:url', '') + _cover = _getProperty(self._metadata, 'xesam:artUrl', '') or _getProperty(self._metadata, 'mpris:artUrl', '') + _duration = _getDuration(_length_int) + # Update metadata + self.metadata['artist'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _metadataGetFirstItem(_artist)) + self.metadata['album'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _metadataGetFirstItem(_album)) + self.metadata['title'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _metadataGetFirstItem(_title)) + self.metadata['track'] = _track + self.metadata['genre'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _metadataGetFirstItem(_genre)) + self.metadata['disc'] = _disc + self.metadata['date'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _date) + self.metadata['year'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _year) + self.metadata['url'] = _url + self.metadata['filename'] = os.path.basename(_url) + self.metadata['length'] = _length_int + self.metadata['fmt-length'] = _fmt_length + self.metadata['cover'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _metadataGetFirstItem(_cover)) + self.metadata['duration'] = _duration + + 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)) + +def _metadataGetFirstItem(_value): + if type(_value) is list: + # Returns the string representation of the first item on _value if it has at least one item. + # Returns an empty string if _value is empty. + return str(_value[0]) if len(_value) else '' + else: + # If _value isn't a list just return the string representation of _value. + return str(_value) + +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 given multiple times (e.g. -b vlc -b audacious)", + action='append', + metavar="BUS_NAME", + default=[]) +parser.add_argument('-w', '--whitelist', help="permit a player by it's bus name like --blacklist. will block --blacklist if given", + 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 + +block_mode = len(args.whitelist) == 0 +filter_list = args.blacklist if block_mode else args.whitelist + +if args.command is None: + PlayerManager(filter_list = filter_list, block_mode = block_mode) +else: + player_manager = PlayerManager(filter_list = filter_list, block_mode = block_mode, 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/.config/polybar/scripts/polywins/polywins.awk b/.config/polybar/scripts/polywins/polywins.awk new file mode 100755 index 0000000..79185db --- /dev/null +++ b/.config/polybar/scripts/polywins/polywins.awk @@ -0,0 +1,127 @@ +#!/usr/bin/awk -f + +BEGIN { + # Setup + active_left="%{F"active_text_color"}" + active_right="%{F-}" + inactive_left="%{F"inactive_text_color"}" + inactive_right="%{F-}" + separator="%{F"inactive_text_color"}"separator"%{F-}" + + if (active_underline == "true") { + active_left=active_left"%{+u}%{u"active_underline_color"}" + active_right="%{-u}"active_right + } + + if (inactive_underline == "true") { + inactive_left=inactive_left"%{+u}%{u"inactive_underline_color"}" + inactive_right="%{-u}"inactive_right + } + + split(ignore_windows, ignored, ":") + + cmd = "wmctrl -lx" +} + +function update_windows() +{ + window_count = 0 + hidden_windows = 0 + + while (cmd | getline) { + if ($2 != active_workspace && $2 != "-1") { continue } + + is_ignored = 0 + for (window in ignored) { + if ($3 ~ ignored[window]) { + is_ignored = 1 + break + } + } + + if (is_ignored) { + continue + } + + if (window_count != 0) { + # only on non-first items + if (add_spaces == "true") + printf " %s ", separator + else + printf "%s", separator + } + + if (window_count >= max_windows) { + do ++hidden_windows + while (cmd | getline) + + printf "+%s", hidden_windows + break + } + + if (show == "window_class") { + displayed_name = $3 + sub(/.+\./, "", displayed_name) + } + else if (show == "window_classname") { + displayed_name = $3 + sub(/\..+/, "", displayed_name) + } + else if (show == "window_title") { + # format window title from wmctrl output + title = "" + + for (i = 5; i <= NF; i++) { + title = title $i + if (i != NF) title = title " " + } + + displayed_name = title + } + + if (char_case == "lower") + displayed_name = tolower(displayed_name) + else if (char_case == "upper") + displayed_name = toupper(displayed_name) + + if (length(displayed_name) > char_limit) + displayed_name = substr(displayed_name, 1, char_limit)"…" + + if ($1 == active_window) + displayed_name=active_left displayed_name active_right + else + displayed_name=inactive_left displayed_name inactive_right + + printf "%s%s%s%s%s%s%s", + "%{A1:"on_click" raise_or_minimize "$1" "active_window":}", + "%{A2:"on_click" close "$1":}", + "%{A3:"on_click" slop_resize "$1":}", + "%{A4:"on_click" increment_size "$1":}", + "%{A5:"on_click" decrement_size "$1":}", + displayed_name, + "%{A}%{A}%{A}%{A}%{A}" + + ++window_count + } close(cmd) + + printf "\n" + fflush(stdout) +} + +$1 == "_NET_CURRENT_DESKTOP" { + active_workspace = $3 + update_windows() +} + +$1 == "_NET_ACTIVE_WINDOW:" && ($5 != "0x0") { + # makes $5 long at least 10 characters if it is not already + if (length($5) < 10) + $5 = sprintf("0x%0" (10 - length($5)) "d%s", 0, substr($5, 3)) + + active_window = $5 + update_windows() +} + +$1 == "_NET_CURRENT_DESKTOP:" { + update_windows() +} diff --git a/.config/polybar/scripts/polywins/polywins.sh b/.config/polybar/scripts/polywins/polywins.sh new file mode 100755 index 0000000..14b4dc4 --- /dev/null +++ b/.config/polybar/scripts/polywins/polywins.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# POLYWINS + +# SETTINGS {{{ --- + +active_text_color="#250F0B" +active_underline="true" +active_underline_color="#000000" +inactive_text_color="#250F0B" +inactive_underline="false" +inactive_underline_color="#F1EF7D" +separator="|" +show="window_title" # options: window_title, window_class, window_classname +char_limit=20 # useful with window_title +max_windows=15 # maximum number of displayed windows +char_case="normal" # options: normal, upper, lower +add_spaces="true" +resize_increment=30 +wm_border_width=0 # setting this might be required for accurate resize position +ignore_windows="polybar:yad" # :-separated list of windows we want to ignore (bars, desktop managers, etc.) + +# --- }}} + +case "$1" in +raise_or_minimize) + if [ "$3" = "$2" ]; then + wmctrl -ir "$2" -b toggle,hidden + else + wmctrl -ia "$2" + fi + ;; +close) + wmctrl -ic "$2" + ;; +slop_resize) + wmctrl -ia "$2" + wmctrl -ir "$2" -e "$(slop -f 0,%x,%y,%w,%h)" + ;; +increment_size) + wmctrl -ir "$2" -e "$(wmctrl -G -l | + awk -v i="$resize_increment" \ + -v b="$wm_border_width" \ + -v win="$2" \ + '$1 ~ win {print "0,"$3-b*2-i/2","$4-b*2-i/2","$5+i","$6+i}')" + ;; +decrement_size) + wmctrl -ir "$2" -e "$(wmctrl -G -l | + awk -v i="$resize_increment" \ + -v b="$wm_border_width" \ + -v win="$2" \ + '$1 ~ win {print "0,"$3-b*2+i/2","$4-b*2+i/2","$5-i","$6-i}')" + ;; +esac + +if [ -n "$2" ]; then exit; fi + +xprop -root -notype -spy _NET_ACTIVE_WINDOW _NET_CURRENT_DESKTOP _NET_CLIENT_LIST | \ +"${0%.*}.awk" \ + -v active_text_color="$active_text_color" \ + -v active_underline_color="$active_underline_color" \ + -v active_underline="$active_underline" \ + -v inactive_text_color="$inactive_text_color" \ + -v inactive_underline_color="$inactive_underline_color" \ + -v inactive_underline="$inactive_underline" \ + -v separator="$separator" \ + -v show="$show" \ + -v char_case="$char_case" \ + -v char_limit="$char_limit" \ + -v add_spaces="$add_spaces" \ + -v on_click="$0" \ + -v max_windows="$max_windows" \ + -v ignore_windows="$ignore_windows" diff --git a/.config/polybar/scripts/storage/Display.py b/.config/polybar/scripts/storage/Display.py new file mode 100644 index 0000000..52f0dc3 --- /dev/null +++ b/.config/polybar/scripts/storage/Display.py @@ -0,0 +1,13 @@ +import string +from datetime import timedelta + + +class Display: + def __init__( + self, + drives + ): + pass + + def render(self, displayInterval: timedelta) -> string: + pass \ No newline at end of file diff --git a/.config/polybar/scripts/storage/SingleDisplay.py b/.config/polybar/scripts/storage/SingleDisplay.py new file mode 100644 index 0000000..2c7da3a --- /dev/null +++ b/.config/polybar/scripts/storage/SingleDisplay.py @@ -0,0 +1,47 @@ +import string +from array import array +from datetime import timedelta + +from Display import Display +from Utils import find_storage_usage + + +class SingleDisplay(Display): + SWITCH_INTERVAL: timedelta = timedelta(seconds=10) + drives: array + + maxLabelWidth: int + currentTime: float + currentIndex: int + + def __init__(self, drives): + self.drives = drives + self.maxLabelWidth = self.getMaxLabelWidth() + self.currentIndex = 0 + self.currentTime = 0 + + def render(self, displayInterval: timedelta) -> string: + self.currentTime = self.currentTime + displayInterval.total_seconds() + if self.currentTime < self.SWITCH_INTERVAL.total_seconds(): + return self.renderCurrentDrive() + + self.currentTime = 0 + self.currentIndex = (self.currentIndex + 1) % len(self.drives) + + return self.renderCurrentDrive() + + def renderCurrentDrive(self): + currentDrive = self.drives[self.currentIndex] + label = currentDrive['label'] + path = currentDrive['path'] + + usage = find_storage_usage(path) + + formatString = "{:>"+ self.maxLabelWidth.__str__() +"} {}" + return formatString.format(label, usage) + + def getMaxLabelWidth(self): + maxLabelWidth = 0 + for i, drive in enumerate(self.drives): + maxLabelWidth = max(maxLabelWidth, len(drive['label'])) + return maxLabelWidth diff --git a/.config/polybar/scripts/storage/Utils.py b/.config/polybar/scripts/storage/Utils.py new file mode 100644 index 0000000..d70e21d --- /dev/null +++ b/.config/polybar/scripts/storage/Utils.py @@ -0,0 +1,8 @@ +import string +import subprocess + + +def find_storage_usage(path: string) -> string: + output = subprocess.check_output(['df', '-h', '--output=pcent', path]) + percentage: string = output.strip().__str__().split()[1] + return percentage.strip("'") \ No newline at end of file diff --git a/.config/polybar/scripts/storage/config.json b/.config/polybar/scripts/storage/config.json new file mode 100644 index 0000000..910ba8f --- /dev/null +++ b/.config/polybar/scripts/storage/config.json @@ -0,0 +1,18 @@ +[ + { + "label": "Drive", + "path": "/" + }, + { + "label": "Games", + "path": "/mnt/games" + }, + { + "label": "Programming", + "path": "/mnt/programming" + }, + { + "label": "RPI 5", + "path": "/mnt/smb/storage" + } +] \ No newline at end of file diff --git a/.config/polybar/scripts/storage/open-ranger.sh b/.config/polybar/scripts/storage/open-ranger.sh new file mode 100755 index 0000000..92d450f --- /dev/null +++ b/.config/polybar/scripts/storage/open-ranger.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +startPoint = $1 + +xfce4-terminal --window --working-directory="$startPoint" --title="File Manager" -x ranger diff --git a/.config/polybar/scripts/storage/overview.py b/.config/polybar/scripts/storage/overview.py new file mode 100755 index 0000000..af96f88 --- /dev/null +++ b/.config/polybar/scripts/storage/overview.py @@ -0,0 +1,31 @@ +#!/usr/bin/python3 -u +import json +import time +from datetime import timedelta + +import Display +import SingleDisplay + +CONFIG_PATH = '/home/michel/.config/polybar/scripts/storage/config.json' +REFRESH_INTERVAL: timedelta = timedelta(seconds=1) + + +def main(): + with open(CONFIG_PATH) as config: + drives = json.load(config) + + isRunning = True + + currentDisplay: Display = SingleDisplay.SingleDisplay(drives) + while isRunning: + start = time.time() + + renderedText = currentDisplay.render(REFRESH_INTERVAL) + print(" " + renderedText) + + duration: float = time.time() - start + time.sleep(max(REFRESH_INTERVAL.total_seconds() - duration, 0)) + + +if __name__ == '__main__': + main() diff --git a/.config/polybar/scripts/storage/storage.sh b/.config/polybar/scripts/storage/storage.sh new file mode 100755 index 0000000..7be210d --- /dev/null +++ b/.config/polybar/scripts/storage/storage.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +mountPoint=$1 +label=$2 + +echo Checking mount values for "$1" + +while : +do + echo $label $(df -h --output=pcent "$mountPoint" | grep "[0-9]%") + sleep 5 +done