diff options
| author | Arpit Chakladar <arpitchakladar+git@gmail.com> | 2026-08-28 03:54:49 +0530 |
|---|---|---|
| committer | Arpit Chakladar <arpitchakladar+git@gmail.com> | 2026-08-29 03:50:31 +0530 |
| commit | ec0606538442fc10f9f57c67f235176f139a1408 (patch) | |
| tree | c723ecbe29c3b3fe4d22e6d08f77600b0c0ff565 /modules/desktop/eww/system-stats.py | |
| parent | 112d93af69e922b779b5dd3f1be9fb407211bb0a (diff) | |
| download | home-manager-config-ec0606538442fc10f9f57c67f235176f139a1408.tar.gz home-manager-config-ec0606538442fc10f9f57c67f235176f139a1408.zip | |
desktop: added audio section to eww system section
Diffstat (limited to 'modules/desktop/eww/system-stats.py')
| -rw-r--r-- | modules/desktop/eww/system-stats.py | 73 |
1 files changed, 72 insertions, 1 deletions
diff --git a/modules/desktop/eww/system-stats.py b/modules/desktop/eww/system-stats.py index b85ffd4..fdc1503 100644 --- a/modules/desktop/eww/system-stats.py +++ b/modules/desktop/eww/system-stats.py @@ -35,13 +35,22 @@ net is an object: { ] } -Requires `iw` (for wifi link speed) and `iproute2` (for route/ip lookup) on PATH. +sound is an object: { + "percent": 58, # default sink volume, 0-100 + "mute": false, # whether the default sink is muted + "sink": "Rockerz 430", # description of the default sink, or null + "tooltip": "Volume: 58%\\nSink: Rockerz 430" +} + +Requires `iw` (for wifi link speed), `iproute2` (for route/ip lookup) +and `pamixer` (for volume/mute) on PATH. RAM speed detection requires `dmidecode`, which typically needs root privileges (run as root, grant CAP_SYS_RAWIO, or add a passwordless sudo rule) — if it's unavailable or unreadable, "speed_mhz" is simply reported as null. """ import json import os +import re import subprocess import threading import time @@ -71,6 +80,12 @@ STATE = { "tooltip": "Network Offline", "routes": [], }, + "sound": { + "percent": None, + "mute": False, + "sink": None, + "tooltip": "Volume: unknown", + }, } # Module-level tracking for throughput calculations @@ -244,6 +259,52 @@ def build_ram_tooltip(ram_pct, used, total, available, swap_pct, swap_used, swap return "\n".join(lines) +def build_sound_tooltip(percent, mute, sink): + vol = f"{percent}%" if percent is not None else "unknown" + lines = [f"Muted ({vol})" if mute else f"Volume: {vol}"] + if sink: + lines.append(f"Sink: {sink}") + return "\n".join(lines) + + +def get_sound_state(): + """Query the default sink's volume, mute state, and description via + pamixer. Any unavailable field is reported as None/False.""" + def pamixer_flag(flag): + try: + return subprocess.check_output( + ["pamixer", flag], text=True, stderr=subprocess.DEVNULL + ).strip() + except (subprocess.CalledProcessError, FileNotFoundError): + return None + + percent, mute, sink = None, None, None + + out = pamixer_flag("--get-volume") + if out: + try: + percent = max(0, min(100, int(out))) + except ValueError: + percent = None + + out = pamixer_flag("--get-mute") + if out is not None: + mute = out.lower() == "true" + + out = pamixer_flag("--get-default-sink") + if out: + # Output ends with: INDEX "name" "description" + names = re.findall(r'"([^"]*)"', out) + sink = names[-1] if names else out.splitlines()[-1] + + return { + "percent": percent, + "mute": mute, + "sink": sink, + "tooltip": build_sound_tooltip(percent, mute, sink), + } + + def cpu_ram_loop(interval=2.0): prev_times = read_cpu_times() # Static for the life of the process — no need to shell out every tick. @@ -497,6 +558,15 @@ def net_loop(): emit() +def sound_loop(interval=1.0): + """Poll the default audio sink for volume/mute changes.""" + while True: + time.sleep(interval) + with STATE_LOCK: + STATE["sound"] = get_sound_state() + emit() + + def emit(): with STATE_LOCK: print(json.dumps(STATE), flush=True) @@ -509,6 +579,7 @@ def main(): threading.Thread(target=cpu_ram_loop, daemon=True).start() threading.Thread(target=net_loop, daemon=True).start() + threading.Thread(target=sound_loop, daemon=True).start() threading.Event().wait() |
