"""Default-sink volume via pactl — same tool scripts/getvol already shells out to. Simple synchronous subprocess calls on click/scroll, no continuous polling: the bar only needs to reflect what IT changed, and hardware volume keys are rare enough on a device that also runs Hyprland that a poll loop isn't worth the cost. """ from __future__ import annotations import re import subprocess _STEP = 5 # percent per scroll tick, matches typical bar/WM conventions _VOL_RE = re.compile(r"(\d+)%") def get_percent() -> int: try: out = subprocess.run(["pactl", "get-sink-volume", "@DEFAULT_SINK@"], capture_output=True, text=True, timeout=1.0, check=True).stdout m = _VOL_RE.search(out) return int(m.group(1)) if m else 0 except (subprocess.SubprocessError, OSError): return 0 def is_muted() -> bool: try: out = subprocess.run(["pactl", "get-sink-mute", "@DEFAULT_SINK@"], capture_output=True, text=True, timeout=1.0, check=True).stdout return "yes" in out.lower() except (subprocess.SubprocessError, OSError): return False def _set(percent: int) -> None: percent = max(0, min(100, percent)) subprocess.Popen(["pactl", "set-sink-volume", "@DEFAULT_SINK@", f"{percent}%"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) def adjust(delta_ticks: int) -> None: """delta_ticks > 0 raises, < 0 lowers, in _STEP-sized increments.""" _set(get_percent() + delta_ticks * _STEP) def toggle_mute() -> None: subprocess.Popen(["pactl", "set-sink-mute", "@DEFAULT_SINK@", "toggle"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)