Adds polybar
This commit is contained in:
parent
56921bfdcc
commit
0f0b9089a4
12 changed files with 1090 additions and 0 deletions
189
.config/polybar/config.ini
Normal file
189
.config/polybar/config.ini
Normal file
|
|
@ -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
|
||||||
14
.config/polybar/launch.sh
Executable file
14
.config/polybar/launch.sh
Executable file
|
|
@ -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..."
|
||||||
554
.config/polybar/scripts/player-mpris-tail.py
Executable file
554
.config/polybar/scripts/player-mpris-tail.py
Executable file
|
|
@ -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<tag>.*?)(:(?P<format>[wt])(?P<formatlen>\d+))?:(?P<text>.*?):\})', re.I)
|
||||||
|
FORMAT_TAG_REGEX = re.compile(r'(?P<format>[wt])(?P<formatlen>\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\1p\2p\3p', 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()
|
||||||
127
.config/polybar/scripts/polywins/polywins.awk
Executable file
127
.config/polybar/scripts/polywins/polywins.awk
Executable file
|
|
@ -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()
|
||||||
|
}
|
||||||
72
.config/polybar/scripts/polywins/polywins.sh
Executable file
72
.config/polybar/scripts/polywins/polywins.sh
Executable file
|
|
@ -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"
|
||||||
13
.config/polybar/scripts/storage/Display.py
Normal file
13
.config/polybar/scripts/storage/Display.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import string
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
|
||||||
|
class Display:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
drives
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def render(self, displayInterval: timedelta) -> string:
|
||||||
|
pass
|
||||||
47
.config/polybar/scripts/storage/SingleDisplay.py
Normal file
47
.config/polybar/scripts/storage/SingleDisplay.py
Normal file
|
|
@ -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
|
||||||
8
.config/polybar/scripts/storage/Utils.py
Normal file
8
.config/polybar/scripts/storage/Utils.py
Normal file
|
|
@ -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("'")
|
||||||
18
.config/polybar/scripts/storage/config.json
Normal file
18
.config/polybar/scripts/storage/config.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"label": "Drive",
|
||||||
|
"path": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Games",
|
||||||
|
"path": "/mnt/games"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Programming",
|
||||||
|
"path": "/mnt/programming"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "RPI 5",
|
||||||
|
"path": "/mnt/smb/storage"
|
||||||
|
}
|
||||||
|
]
|
||||||
5
.config/polybar/scripts/storage/open-ranger.sh
Executable file
5
.config/polybar/scripts/storage/open-ranger.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
startPoint = $1
|
||||||
|
|
||||||
|
xfce4-terminal --window --working-directory="$startPoint" --title="File Manager" -x ranger
|
||||||
31
.config/polybar/scripts/storage/overview.py
Executable file
31
.config/polybar/scripts/storage/overview.py
Executable file
|
|
@ -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()
|
||||||
12
.config/polybar/scripts/storage/storage.sh
Executable file
12
.config/polybar/scripts/storage/storage.sh
Executable file
|
|
@ -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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue