fix(examples): report failed status actions on stderr
- commit
- 64974ce51a7c595b525a8f527d7c45d2480d379b
- author
- brice · · 2026-10-09 07:40 UTC
- parent
- ac13ee3
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +21 -5
M examples/modules/status.py +21 -5
| @@ -23,6 +23,7 @@ import re | |||
| 23 | 23 | import select | |
| 24 | 24 | import shutil | |
| 25 | 25 | import subprocess | |
| 26 | + | import sys | |
| 26 | 27 | import time | |
| 27 | 28 | ||
| 28 | 29 | TERMINAL = os.environ.get("TERMINAL", "kitty") | |
| @@ -56,6 +57,19 @@ def run(*cmd): | |||
| 56 | 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 | 73 | def launch(*cmd): | |
| 60 | 74 | """Start a program detached from the bar (its output never reaches the bar).""" | |
| 61 | 75 | try: | |
| @@ -141,10 +155,10 @@ class Volume: | |||
| 141 | 155 | run("wpctl", "set-volume", "-l", "1.0", SINK, f"{steps * VOLUME_STEP}%{sign}") | |
| 142 | 156 | ||
| 143 | 157 | def mute(self): | |
| 144 | - | run("wpctl", "set-mute", SINK, "toggle") | |
| 158 | + | act("wpctl", "set-mute", SINK, "toggle") | |
| 145 | 159 | ||
| 146 | 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 | 164 | # --- wifi -------------------------------------------------------------------- | |
| @@ -210,11 +224,11 @@ class Wifi: | |||
| 210 | 224 | pass | |
| 211 | 225 | ||
| 212 | 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 | 229 | def connect(self, ssid): | |
| 216 | 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 | 232 | self.settings() | |
| 219 | 233 | ||
| 220 | 234 | def settings(self): | |
| @@ -277,7 +291,9 @@ class Battery: | |||
| 277 | 291 | def menu(self): | |
| 278 | 292 | current = run("powerprofilesctl", "get").strip() | |
| 279 | 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 | 297 | for p in profiles] | |
| 282 | 298 | ||
| 283 | 299 | def click(self, button): | |
| @@ -23,6 +23,7 @@ import re | |||||
| 23 | import select | 23 | import select | ||
| 24 | import shutil | 24 | import shutil | ||
| 25 | import subprocess | 25 | import subprocess | ||
| 26 | + | import sys | |||
| 26 | import time | 27 | 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 | pass | 224 | 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): | ||