summaryrefslogtreecommitdiff
path: root/panel.py
diff options
context:
space:
mode:
Diffstat (limited to 'panel.py')
-rwxr-xr-xpanel.py141
1 files changed, 81 insertions, 60 deletions
diff --git a/panel.py b/panel.py
index 5ef9cbb..7130b3f 100755
--- a/panel.py
+++ b/panel.py
@@ -2,32 +2,62 @@
#
# Copyright 2024 David Vazgenovich Shakaryan
-import glob
import os
import queue
import subprocess
import threading
-import time
-from datetime import datetime, timezone
+from datetime import datetime
from zoneinfo import ZoneInfo
-def fmt_label(s):
- return f'%{{F#7b51ca}}{s}%{{F-}}'
+class Fmt:
+ @classmethod
+ def fg(cls, col, s):
+ return f'%{{F{col}}}{s}%{{F-}}'
-def spacing(n = 1):
- return f'%{{O{n * 7}}}'
+ @classmethod
+ def bg(cls, col, s):
+ return f'%{{B{col}}}{s}%{{B-}}'
+
+ @classmethod
+ def ul(cls, col, s):
+ return f'%{{U{col}}}%{{+u}}{s}%{{-u}}%{{U-}}'
+
+ @classmethod
+ def bold(cls, s):
+ return f'%{{T2}}{s}%{{T-}}'
+
+ @classmethod
+ def label(cls, s):
+ return cls.fg('#7b51ca', s)
+
+ @classmethod
+ def spacer(cls, n=1):
+ return f'%{{O{n * 7}}}'
+
+ @classmethod
+ def pad(cls, s, n=1):
+ sp = cls.spacer(n)
+ return f'{sp}{s}{sp}'
+
+ @classmethod
+ def clickable(cls, btn, cmd, s):
+ return f'%{{A{btn}:{cmd}:}}{s}%{{A}}'
class Mod:
def __init__(self):
- self.cv = None
- self.t = None
+ self.e_repaint = None
+
+ # changes should be applied atomically, as the main thread may read
+ # this at any time.
self.out = None
+ def repaint(self):
+ self.e_repaint.set()
+
def run(self):
if callable(getattr(self, 'work', None)):
- self.t = threading.Thread(target=self.work, daemon=True)
- self.t.start()
+ threading.Thread(target=self.work, daemon=True).start()
def process_cmd(self, cmd):
pass
@@ -38,7 +68,7 @@ class ModRight(Mod):
class ModSpacer(Mod):
def __init__(self, n=1):
- self.out = spacing(n)
+ self.out = Fmt.spacer(n)
class ModDate(Mod):
def __init__(self, fmts=None, tzs=None):
@@ -60,14 +90,15 @@ class ModDate(Mod):
dt = datetime.now().astimezone(ZoneInfo(tz_id) if tz_id else None)
label = tz_label or dt.strftime('%Z')
- buf = (f'%{{A4:{id(self)} tz +1:}}%{{A5:{id(self)} tz -1:}}'
- f'%{{A:{id(self)} tz:}}{fmt_label(label)}%{{A}}{spacing()}'
- f'%{{A:{id(self)} fmt:}}{dt.strftime(fmt)}%{{A}}'
- '%{A}%{A}')
-
- with self.cv:
- self.out = buf
- self.cv.notify()
+ self.out = Fmt.clickable(
+ 4, f'{id(self)} tz +1',
+ Fmt.clickable(
+ 5, f'{id(self)} tz -1',
+ Fmt.clickable('', f'{id(self)} tz', Fmt.label(label))
+ + Fmt.spacer()
+ + Fmt.clickable(
+ '', f'{id(self)} fmt', dt.strftime(fmt))))
+ self.repaint()
self.e.wait(1 - (dt.microsecond / 1000000))
self.e.clear()
@@ -150,37 +181,33 @@ class ModHLWMTags(ModHLWMBase):
self.refresh()
def fmt_tag(self, sym, tag, tagstr):
- buf = f'%{{A:{id(self)} use {tag}:}}'
+ disp = Fmt.pad(tag)
match sym:
case '.':
- buf += f'%{{F#777777}}%{{O7}}{tag}%{{O7}}%{{F-}}'
+ buf = Fmt.fg('#777777', disp)
case '#':
- buf += (f'%{{B#333333}}%{{U#7b51ca}}%{{+u}}%{{O7}}%{{T2}}'
- f'{tag}%{{T-}}%{{O7}}%{{-u}}%{{U-}}%{{B-}}')
+ buf = Fmt.bg('#333333', Fmt.ul('#7b51ca', Fmt.bold(disp)))
case '!':
if '#' in tagstr:
- buf += (f'%{{B#a03000}}%{{U#000000}}%{{+u}}%{{O7}}'
- f'{tag}%{{O7}}%{{-u}}%{{U-}}%{{B-}}')
+ buf = Fmt.bg('#a03000', Fmt.ul('#000000', disp))
else:
- buf += (f'%{{B#a03000}}%{{U#7b51ca}}%{{+u}}%{{O7}}%{{T2}}'
- f'{tag}%{{T-}}%{{O7}}%{{-u}}%{{U-}}%{{B-}}')
+ buf = Fmt.bg('#a03000', Fmt.ul('#7b51ca', Fmt.bold(disp)))
case ':':
- buf += f'%{{O7}}{tag}%{{O7}}'
- buf += '%{A}'
- return buf
+ buf = disp
+
+ return Fmt.clickable('', f'{id(self)} use {tag}', buf)
def refresh(self):
tagstr = self.hc('tag_status')
tags = tagstr.lstrip('\t').split('\t')
- buf = (f'%{{A4:{id(self)} use_index +1:}}'
- f'%{{A5:{id(self)} use_index -1:}}')
- for tag in tags:
- buf += self.fmt_tag(tag[0], tag[1:], tagstr)
- buf += '%{A}%{A}'
- with self.cv:
- self.out = buf
- self.cv.notify()
+ self.out = Fmt.clickable(
+ 4, f'{id(self)} use_index +1',
+ Fmt.clickable(
+ 5, f'{id(self)} use_index -1',
+ ''.join(
+ self.fmt_tag(tag[0], tag[1:], tagstr) for tag in tags)))
+ self.repaint()
def process_cmd(self, cmd):
subprocess.run(('herbstclient', *cmd.split()))
@@ -196,11 +223,8 @@ class ModHLWMTitle(ModHLWMBase):
def refresh(self):
title = self.hc('attr', 'clients.focus.title')
- buf = title if len(title) < 65 else title[0:63] + '…'
-
- with self.cv:
- self.out = buf
- self.cv.notify()
+ self.out = title if len(title) < 65 else title[0:63] + '…'
+ self.repaint()
class ModInputUnavail(Mod):
def __init__(self, path, label=''):
@@ -241,20 +265,17 @@ class ModInputUnavail(Mod):
def _update_state(self, avail):
if avail:
- buf = None
+ self.out = None
else:
- buf = f'%{{B#a03000}}{spacing()}{self.label}{spacing()}%{{B-}}'
-
- with self.cv:
- self.out = buf
- self.cv.notify()
+ self.out = Fmt.bg('#a03000', Fmt.pad(self.label))
+ self.repaint()
-class Bar:
+class Panel:
def __init__(self, *mods):
- self.cv = threading.Condition()
+ self.e_repaint = threading.Event()
self.mods = mods
for m in self.mods:
- m.cv = self.cv
+ m.e_repaint = self.e_repaint
self.mod_by_id = {id(m): m for m in self.mods}
def process_cmds(self, pipe):
@@ -270,7 +291,7 @@ class Bar:
for mod in self.mods:
mod.run()
- self.bar = subprocess.Popen(
+ self.panel = subprocess.Popen(
('/home/david/lemonbar-xft/lemonbar',
'-bf', 'Monospace:size=10:dpi=96',
'-f', 'Monospace:size=10:dpi=96:bold',
@@ -281,17 +302,17 @@ class Bar:
threading.Thread(
target=self.process_cmds,
- args=(self.bar.stdout,),
+ args=(self.panel.stdout,),
daemon=True
).start()
while True:
- with self.cv:
- self.cv.wait()
+ self.e_repaint.wait()
+ self.e_repaint.clear()
print(''.join([m.out for m in self.mods if m.out]),
- file=self.bar.stdin, flush=True)
+ file=self.panel.stdin, flush=True)
-Bar(
+Panel(
ModHLWMTags(),
ModSpacer(),
ModHLWMTitle(),
@@ -299,7 +320,7 @@ Bar(
ModInputUnavail(
'/dev/input/by-id/usb-HID_Keyboard_HID_Keyboard-event-kbd',
label='NO KEYBOARD'),
- ModSpacer(n=2),
+ ModSpacer(2),
ModDate(
fmts=('%Y-%m-%d %H:%M:%S', '%H:%M'),
tzs=({'id': None}, {'id': 'UTC'},