From e242147340994dbb48ded3273a34079a841af25b Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Thu, 15 Oct 2026 13:06:36 +0200 Subject: feat(sound): per-app volume on the drawer page, output to 150% The island spec put per-app volume on the drawer page, and it was never built. Each application playing audio now gets a row with its name, what it is playing (two browser tabs are otherwise identical rows), a click-to-mute level and its own slider. Streams are matched on PwNodeType.AudioOutStream. Quickshell reports isSink true for a playback stream, so the obvious isStream && !isSink filter found nothing while Firefox played. Their properties are empty until bound, so the page carries a PwObjectTracker for them, which stops with the drawer. speech-dispatcher-dummy is always present and never audible, and is hidden. The output slider runs to 150%, the ceiling the volume keybind already uses (wpctl -l 1.5), with a tick at 100%. Input and apps stop at 100%: a boosted microphone only clips. The meter became an inline component shared by both, and wheel-up never lowers a level already past its maximum. Now playing is dropped from the page: the OSD already carries it. Co-Authored-By: Claude Opus 5.5 --- desktop/modules/sound/Service.qml | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'desktop/modules/sound/Service.qml') diff --git a/desktop/modules/sound/Service.qml b/desktop/modules/sound/Service.qml index 22a4520..5f10c34 100644 --- a/desktop/modules/sound/Service.qml +++ b/desktop/modules/sound/Service.qml @@ -36,6 +36,14 @@ Scope { Pipewire.nodes.values.filter(n => n.audio && !n.isStream && !n.isSink && !String(n.name).endsWith(".monitor")) + // Applications playing audio. Matched on the node type: `isSink` reads + // true for a playback stream (Firefox, measured), the reverse of what it + // means for a device, so it cannot tell playback from recording here. + readonly property var streams: + Pipewire.nodes.values.filter(n => n.type === PwNodeType.AudioOutStream + // always present, never audible + && n.name !== "speech-dispatcher-dummy") + // Which node changed last, and whether it was the input. The OSD draws // this one; null means nothing to show. property PwNode active: null -- cgit v1.2.3