import QtQuick import QtQuick.Layouts import QtQuick.Controls.Fusion import Quickshell import Quickshell.Services.Pipewire import Quickshell.Widgets Item { height: parent.height width: volumeIcon.width + 8 anchors.verticalCenter: parent.verticalCenter PwObjectTracker { objects: [Pipewire.defaultAudioSink] } WrapperMouseArea { id: widgetMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { if (!Pipewire.defaultAudioSink) return; Pipewire.defaultAudioSink.audio.muted = !Pipewire.defaultAudioSink.audio.muted; } property real sensitivity: (1 / 40) / 120 onWheel: event => { if (!Pipewire.defaultAudioSink) return; Pipewire.defaultAudioSink.audio.volume += event.angleDelta.y * sensitivity; } Rectangle { id: volumeWidget anchors.fill: parent color: { if (widgetMouseArea.containsMouse) return "#33808080"; return "transparent"; } Item { anchors.fill: parent MaterialDesignIcon { id: volumeIcon width: 16 height: 16 anchors.centerIn: parent icon: { if (!Pipewire.defaultAudioSink || Pipewire.defaultAudioSink.audio.muted) return "volume-off"; if (Pipewire.defaultAudioSink.audio.volume <= 0.33) return "volume-low"; if (Pipewire.defaultAudioSink.audio.volume <= 0.67) return "volume-medium"; return "volume-high"; } color: "#555" } } } } Loader { id: tooltipLoader active: false Connections { target: widgetMouseArea function onContainsMouseChanged() { if (widgetMouseArea.containsMouse) tooltipLoader.active = true; } } PwObjectTracker { objects: Pipewire.devices } PwObjectTracker { objects: Pipewire.nodes } sourceComponent: PopupWindow { id: tooltip property bool openPopup: false property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse || openPopup anchor { item: widgetMouseArea edges: Edges.Bottom | Edges.Left } visible: false onNextVisibleChanged: hangTimer.restart() Timer { id: hangTimer interval: 100 onTriggered: { tooltip.visible = tooltip.nextVisible; if (!tooltip.visible) tooltipLoader.active = false; } } implicitWidth: tooltipContent.width implicitHeight: tooltipContent.height color: "black" WrapperMouseArea { id: tooltipMouseArea hoverEnabled: true enabled: true anchors.fill: parent WrapperItem { id: tooltipContent margin: 8 bottomMargin: 8 + Math.max(0, 200 - tooltipLayout.implicitHeight) GridLayout { id: tooltipLayout columns: 4 Repeater { model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device") Item { id: descItem required property var modelData required property int index Layout.column: 0 Layout.row: index implicitWidth: descText.contentWidth implicitHeight: descText.contentHeight Text { id: descText color: "white" font.pointSize: 10 font.family: "Fira Sans" text: descItem.modelData.description } } } Repeater { id: defaultSinkRepeater model: { Array.from(Pipewire.devices.values) .filter(dev => dev.type == "Audio/Device") .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSink && node.device?.id == device.id )); } Item { id: defaultSinkItem required property var modelData required property int index PwObjectTracker { objects: [defaultSinkItem.modelData] } Layout.column: 1 Layout.row: index Layout.fillHeight: true implicitWidth: 16 + 8 WrapperMouseArea { id: defaultSinkMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { Pipewire.preferredDefaultAudioSink = defaultSinkItem.modelData } Rectangle { id: defaultSinkWidget anchors.fill: parent color: { if (defaultSinkMouseArea.containsMouse) return "#33808080"; return "transparent"; } MaterialDesignIcon { width: 16 height: 16 anchors.centerIn: parent icon: { if (defaultSinkItem.modelData?.id == Pipewire.defaultAudioSink?.id) return "speaker"; return "speaker-off"; } color: icon == "speaker" ? "white" : "#555" } } } } } Repeater { id: defaultSourceRepeater model: { Array.from(Pipewire.devices.values) .filter(dev => dev.type == "Audio/Device") .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSource && node.device?.id == device.id )); } Item { id: defaultSourceItem required property var modelData required property int index PwObjectTracker { objects: [defaultSourceItem.modelData] } Layout.column: 2 Layout.row: index Layout.fillHeight: true implicitWidth: 16 + 8 WrapperMouseArea { id: defaultSourceMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { Pipewire.preferredDefaultAudioSource = defaultSourceItem.modelData } Rectangle { id: defaultSourceWidget anchors.fill: parent color: { if (defaultSourceMouseArea.containsMouse) return "#33808080"; return "transparent"; } MaterialDesignIcon { width: 16 height: 16 anchors.centerIn: parent icon: { if (defaultSourceItem.modelData?.id == Pipewire.defaultAudioSource?.id) return "microphone"; return "microphone-off"; } color: icon == "microphone" ? "white" : "#555" } } } } } Repeater { id: profileRepeater model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device") Item { id: profileItem required property var modelData required property int index PwObjectTracker { objects: [profileItem.modelData] } Layout.column: 3 Layout.row: index Layout.fillWidth: true implicitWidth: Math.max(profileBox.implicitWidth, 300) implicitHeight: profileBox.height ComboBox { id: profileBox model: profileItem.modelData.profiles textRole: "description" valueRole: "index" onActivated: profileItem.modelData.setProfile(currentValue) anchors.fill: parent implicitContentWidthPolicy: ComboBox.WidestText Connections { target: profileItem.modelData function onCurrentProfileChanged() { profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile); } } Component.onCompleted: { profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile); } Connections { target: profileBox.popup function onVisibleChanged() { tooltip.openPopup = profileBox.popup.visible } } } } } } } } } } }