detached 64974ce

fix(examples): report failed status actions on stderr

commit
64974ce51a7c595b525a8f527d7c45d2480d379b
author
brice · · 2026-10-09 07:40 UTC
parent
ac13ee3
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +21 -5
M examples/modules/status.py +21 -5
@@ -23,6 +23,7 @@ import re
2323 import select
2424 import shutil
2525 import subprocess
26+import sys
2627 import time
2728
2829 TERMINAL = os.environ.get("TERMINAL", "kitty")
@@ -56,6 +57,19 @@ def run(*cmd):
5657 return ""
5758
5859
60+def act(*cmd):
61+ """Run an action the user asked for. A failure goes to stderr, which the
62+ bar writes to its log."""
63+ try:
64+ done = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
65+ except (OSError, subprocess.SubprocessError) as err:
66+ print(f"{cmd[0]}: {err}", file=sys.stderr, flush=True)
67+ return False
68+ if done.returncode != 0:
69+ print(f"{' '.join(cmd)}: {done.stderr.strip()}", file=sys.stderr, flush=True)
70+ return done.returncode == 0
71+
72+
5973 def launch(*cmd):
6074 """Start a program detached from the bar (its output never reaches the bar)."""
6175 try:
@@ -141,10 +155,10 @@ class Volume:
141155 run("wpctl", "set-volume", "-l", "1.0", SINK, f"{steps * VOLUME_STEP}%{sign}")
142156
143157 def mute(self):
144- run("wpctl", "set-mute", SINK, "toggle")
158+ act("wpctl", "set-mute", SINK, "toggle")
145159
146160 def set(self, percent):
147- run("wpctl", "set-volume", SINK, f"{percent}%")
161+ act("wpctl", "set-volume", SINK, f"{percent}%")
148162
149163
150164 # --- wifi --------------------------------------------------------------------
@@ -210,11 +224,11 @@ class Wifi:
210224 pass
211225
212226 def toggle(self):
213- run("nmcli", "radio", "wifi", "off" if self.enabled else "on")
227+ act("nmcli", "radio", "wifi", "off" if self.enabled else "on")
214228
215229 def connect(self, ssid):
216230 # Uses the saved profile; a new secured network needs the settings TUI.
217- if not run("nmcli", "device", "wifi", "connect", ssid):
231+ if not act("nmcli", "device", "wifi", "connect", ssid):
218232 self.settings()
219233
220234 def settings(self):
@@ -277,7 +291,9 @@ class Battery:
277291 def menu(self):
278292 current = run("powerprofilesctl", "get").strip()
279293 profiles = re.findall(r"^\*?\s*([\w-]+):$", run("powerprofilesctl", "list"), re.M)
280- return [(p, "radio", p == current, lambda p=p: run("powerprofilesctl", "set", p))
294+ # Switching profiles needs an active session (polkit): run the bar
295+ # from the compositor, not from an ssh shell.
296+ return [(p, "radio", p == current, lambda p=p: act("powerprofilesctl", "set", p))
281297 for p in profiles]
282298
283299 def click(self, button):
@@ -23,6 +23,7 @@ import re
23 import select23 import select
24 import shutil24 import shutil
25 import subprocess25 import subprocess
26+import sys
26 import time27 import time
27 28
28 TERMINAL = os.environ.get("TERMINAL", "kitty")29 TERMINAL = os.environ.get("TERMINAL", "kitty")
@@ -56,6 +57,19 @@ def run(*cmd):
56 return ""57 return ""
57 58
58 59
60+def act(*cmd):
61+ """Run an action the user asked for. A failure goes to stderr, which the
62+ bar writes to its log."""
63+ try:
64+ done = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
65+ except (OSError, subprocess.SubprocessError) as err:
66+ print(f"{cmd[0]}: {err}", file=sys.stderr, flush=True)
67+ return False
68+ if done.returncode != 0:
69+ print(f"{' '.join(cmd)}: {done.stderr.strip()}", file=sys.stderr, flush=True)
70+ return done.returncode == 0
71+
72+
59 def launch(*cmd):73 def launch(*cmd):
60 """Start a program detached from the bar (its output never reaches the bar)."""74 """Start a program detached from the bar (its output never reaches the bar)."""
61 try:75 try:
@@ -141,10 +155,10 @@ class Volume:
141 run("wpctl", "set-volume", "-l", "1.0", SINK, f"{steps * VOLUME_STEP}%{sign}")155 run("wpctl", "set-volume", "-l", "1.0", SINK, f"{steps * VOLUME_STEP}%{sign}")
142 156
143 def mute(self):157 def mute(self):
144- run("wpctl", "set-mute", SINK, "toggle")158+ act("wpctl", "set-mute", SINK, "toggle")
145 159
146 def set(self, percent):160 def set(self, percent):
147- run("wpctl", "set-volume", SINK, f"{percent}%")161+ act("wpctl", "set-volume", SINK, f"{percent}%")
148 162
149 163
150 # --- wifi --------------------------------------------------------------------164 # --- wifi --------------------------------------------------------------------
@@ -210,11 +224,11 @@ class Wifi:
210 pass224 pass
211 225
212 def toggle(self):226 def toggle(self):
213- run("nmcli", "radio", "wifi", "off" if self.enabled else "on")227+ act("nmcli", "radio", "wifi", "off" if self.enabled else "on")
214 228
215 def connect(self, ssid):229 def connect(self, ssid):
216 # Uses the saved profile; a new secured network needs the settings TUI.230 # Uses the saved profile; a new secured network needs the settings TUI.
217- if not run("nmcli", "device", "wifi", "connect", ssid):231+ if not act("nmcli", "device", "wifi", "connect", ssid):
218 self.settings()232 self.settings()
219 233
220 def settings(self):234 def settings(self):
@@ -277,7 +291,9 @@ class Battery:
277 def menu(self):291 def menu(self):
278 current = run("powerprofilesctl", "get").strip()292 current = run("powerprofilesctl", "get").strip()
279 profiles = re.findall(r"^\*?\s*([\w-]+):$", run("powerprofilesctl", "list"), re.M)293 profiles = re.findall(r"^\*?\s*([\w-]+):$", run("powerprofilesctl", "list"), re.M)
280- return [(p, "radio", p == current, lambda p=p: run("powerprofilesctl", "set", p))294+ # Switching profiles needs an active session (polkit): run the bar
295+ # from the compositor, not from an ssh shell.
296+ return [(p, "radio", p == current, lambda p=p: act("powerprofilesctl", "set", p))
281 for p in profiles]297 for p in profiles]
282 298
283 def click(self, button):299 def click(self, button):

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 5

Tags 4

↑↓ moveenter openesc close