import QtQuick import Quickshell import Quickshell.Services.Pipewire import qs.config import qs.widgets import qs.services import qs.modules.quicksettings Rectangle { id: root required property var node property bool output: true property bool selected: false implicitHeight: root.output || !root.selected ? 88 : 105 radius: Theme.cardRadius color: root.selected ? Theme.alpha(Theme.accent, 0.09) : Theme.alpha(Theme.fg, 0.025) border.width: 1 border.color: root.selected ? Theme.alpha(Theme.accent, 0.34) : Theme.alpha(Theme.fg, 0.07) PwObjectTracker { objects: root.node ? [root.node] : [] } PwNodePeakMonitor { id: inputPeak node: root.node enabled: !root.output && root.selected } readonly property real volume: root.node?.audio?.volume ?? 0 readonly property bool muted: root.node?.audio?.muted ?? false function iconName(): string { if (!root.output) return root.muted ? "microphone-sensitivity-muted-symbolic" : "audio-input-microphone-symbolic"; 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"; } Row { id: heading anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.leftMargin: 12 anchors.rightMargin: 10 anchors.topMargin: 8 height: 28 spacing: 10 ThemedIcon { anchors.verticalCenter: parent.verticalCenter size: 20 icon: root.output ? "audio-speakers-symbolic" : "audio-input-microphone-symbolic" iconFallback: "audio-card-symbolic" tint: root.selected ? Theme.accent : Theme.fg } Column { width: Math.max(0, parent.width - 20 - useButton.width - parent.spacing * 2) anchors.verticalCenter: parent.verticalCenter spacing: 1 Text { width: parent.width text: AudioDevices.label(root.node) color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSize font.weight: root.selected ? Font.DemiBold : Font.Medium elide: Text.ElideRight } Text { width: parent.width visible: root.node.nickname && root.node.nickname !== AudioDevices.label(root.node) text: root.node.nickname ?? "" color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall elide: Text.ElideRight } } Row { anchors.verticalCenter: parent.verticalCenter spacing: 7 // Outputs only. Testing an input would mean recording and playing // it back, which is a different thing than this button implies. SettingsButton { anchors.verticalCenter: parent.verticalCenter visible: root.output text: "Test" onClicked: SoundTest.play(root.node) } SettingsButton { id: useButton anchors.verticalCenter: parent.verticalCenter width: root.selected ? 78 : 64 text: root.selected ? "Default" : "Use" enabled: !root.selected onClicked: AudioDevices.select(root.output, root.node) } } } IconButton { id: muteButton anchors.left: parent.left anchors.leftMargin: 8 anchors.top: heading.bottom anchors.topMargin: 7 size: 30 iconSize: 17 icon: root.iconName() iconFallback: root.output ? "audio-volume-high-symbolic" : "audio-input-microphone-symbolic" onClicked: { if (root.node?.audio) root.node.audio.muted = !root.node.audio.muted; } } ValueSlider { id: volumeSlider anchors.left: muteButton.right anchors.leftMargin: 7 anchors.right: volumeText.left anchors.rightMargin: 10 anchors.verticalCenter: muteButton.verticalCenter value: root.muted ? 0 : root.volume onMoved: value => { if (!root.node?.audio) return; root.node.audio.muted = false; root.node.audio.volume = value; } } Text { id: volumeText anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: muteButton.verticalCenter width: 38 text: Math.round(root.volume * 100) + "%" color: Theme.fgDim font.family: Theme.fontMono font.features: Theme.tabularFigures font.pixelSize: Theme.fontSizeSmall horizontalAlignment: Text.AlignRight } Rectangle { anchors.left: volumeSlider.left anchors.right: volumeText.right anchors.top: muteButton.bottom anchors.topMargin: 5 height: 5 radius: height / 2 visible: !root.output && root.selected color: Theme.alpha(Theme.fg, 0.10) Rectangle { anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom width: parent.width * Math.max(0, Math.min(1, inputPeak.peak)) radius: parent.radius color: inputPeak.peak > 0.88 ? Theme.danger : Theme.accentSecondary } } }