aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/sound/Service.qml
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/modules/sound/Service.qml')
-rw-r--r--desktop/modules/sound/Service.qml75
1 files changed, 75 insertions, 0 deletions
diff --git a/desktop/modules/sound/Service.qml b/desktop/modules/sound/Service.qml
new file mode 100644
index 0000000..e509a94
--- /dev/null
+++ b/desktop/modules/sound/Service.qml
@@ -0,0 +1,75 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import Quickshell
+import Quickshell.Services.Pipewire
+import QtQuick
+
+// The PipeWire half of what used to be VolumeOsd.qml. Always active: the OSD
+// has to react to a volume keypress with no drawer open.
+Scope {
+ id: root
+
+ readonly property PwNode sink: Pipewire.defaultAudioSink
+ readonly property PwNode source: Pipewire.defaultAudioSource
+
+ readonly property real volume: sink?.audio?.volume ?? 0
+ readonly property bool muted: sink?.audio?.muted ?? false
+
+ // Which node changed last, and whether it was the input. The OSD draws
+ // this one; null means nothing to show.
+ property PwNode active: null
+ property bool isInput: false
+
+ // Keeping the nodes bound is what makes volume/muted actually update.
+ // Without the tracker the value reads once and goes stale.
+ PwObjectTracker { objects: [root.sink, root.source].filter(n => n !== null) }
+
+ signal changed()
+
+ // A node reports its initial volume while binding, before `ready` goes
+ // true, so the `ready` check alone suppresses the startup values. Nothing
+ // else may be swallowed: the next signal after that is the user's first
+ // keypress, and eating it costs the OSD its first appearance.
+ function show(node, input) {
+ if (!node?.ready || !node.audio) return;
+ root.active = node;
+ root.isInput = input;
+ root.changed();
+ }
+
+ function showTrack() {
+ if (!Player.active) return;
+ root.active = root.sink;
+ root.isInput = false;
+ root.changed();
+ }
+
+ Connections {
+ target: root.sink?.audio ?? null
+ function onVolumeChanged() { root.show(root.sink, false); }
+ function onMutedChanged() { root.show(root.sink, false); }
+ }
+
+ Connections {
+ target: root.source?.audio ?? null
+ function onVolumeChanged() { root.show(root.source, true); }
+ function onMutedChanged() { root.show(root.source, true); }
+ }
+
+ // A track change shows the OSD as well, so the row is not something you
+ // only see when you happen to touch the volume.
+ Connections {
+ target: Player.current ?? null
+ function onTrackTitleChanged() { if (Player.title) root.showTrack(); }
+ function onPlaybackStateChanged() { root.showTrack(); }
+ }
+}