pragma Singleton import Quickshell import Quickshell.Services.Pipewire import qs.config // The default audio sink. Quickshell talks to pipewire directly, so there is no // wpctl or pactl to shell out to and no polling: the properties below change as // the graph does, whoever moved them. // // Everything is expressed against the default sink rather than a remembered // one, so switching outputs — plugging in a headset, say — moves the panel with // the sound. Singleton { id: root readonly property PwNode sink: Pipewire.defaultAudioSink // Pipewire objects only carry live properties while something is tracking // them, so `audio` is null until the tracker below binds the node. readonly property bool available: Config.showVolume && root.sink !== null && root.sink.ready && root.sink.audio !== null // 0-1 for the usable range, though pipewire allows more and another client // may well have set more. Reported as-is; the slider is what clamps. readonly property real volume: root.available ? root.sink.audio.volume : 0 readonly property bool muted: root.available ? root.sink.audio.muted : false function setVolume(value: real): void { if (!root.available) return; root.sink.audio.volume = Math.max(0, Math.min(1, value)); } // Raising the volume from silence unmutes: a volume key that appears to do // nothing is worse than one that does slightly more than it said. function step(delta: real): void { if (!root.available) return; if (delta > 0 && root.muted) root.setMuted(false); root.setVolume(root.volume + delta); } function setMuted(muted: bool): void { if (root.available) root.sink.audio.muted = muted; } function toggleMute(): void { root.setMuted(!root.muted); } PwObjectTracker { objects: root.sink ? [root.sink] : [] } }