// Volume row: mute button, slider, and a chevron that opens the device picker. // // PwObjectTracker is mandatory — without it node.audio.volume reads 0 and // writes are dropped on the floor with no error. import QtQuick import Quickshell import Quickshell.Services.Pipewire import qs.widgets import qs.config Item { id: root property var node: null // Output vs input, purely for icon selection. property bool output: true property bool expanded: false signal toggleExpanded implicitHeight: 32 visible: root.node !== null readonly property real volume: root.node && root.node.audio ? root.node.audio.volume : 0 readonly property bool muted: root.node && root.node.audio ? root.node.audio.muted : false PwObjectTracker { objects: root.node ? [root.node] : [] } function iconName(): string { if (root.output) { if (root.muted || root.volume <= 0.001) return "audio-volume-muted-symbolic"; if (root.volume < 0.34) return "audio-volume-low-symbolic"; if (root.volume < 0.67) return "audio-volume-medium-symbolic"; return "audio-volume-high-symbolic"; } return root.muted ? "microphone-sensitivity-muted-symbolic" : "audio-input-microphone-symbolic"; } IconButton { id: muteButton anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter size: 30 iconSize: 17 icon: root.iconName() iconFallback: root.output ? "audio-volume-high-symbolic" : "audio-input-microphone-symbolic" onClicked: { if (root.node && root.node.audio) root.node.audio.muted = !root.node.audio.muted; } } ValueSlider { anchors.left: muteButton.right anchors.leftMargin: 6 anchors.right: chevron.left anchors.rightMargin: 4 anchors.verticalCenter: parent.verticalCenter value: root.muted ? 0 : root.volume onMoved: v => { if (!root.node || !root.node.audio) return; // Nudging the slider is also how you unmute, same as GNOME. root.node.audio.muted = false; root.node.audio.volume = v; } } IconButton { id: chevron anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter size: 28 iconSize: 14 icon: root.expanded ? "pan-up-symbolic" : "pan-down-symbolic" iconFallback: "go-next-symbolic" onClicked: root.toggleExpanded() } }