From 2f8f1c30add5aa44b5f170dff06c053499368f88 Mon Sep 17 00:00:00 2001 From: bbedward Date: Mon, 23 Feb 2026 13:21:04 -0500 Subject: [PATCH] audio: fix cycle output, improve icon resolution for sink fixes #1808 --- .../Details/AudioOutputDetail.qml | 11 +--- quickshell/Modules/OSD/AudioOutputOSD.qml | 17 +----- quickshell/Services/AudioService.qml | 58 +++++++++++++++---- 3 files changed, 49 insertions(+), 37 deletions(-) diff --git a/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml b/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml index f6949535..781a6c93 100644 --- a/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml +++ b/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml @@ -234,16 +234,7 @@ Rectangle { spacing: Theme.spacingS DankIcon { - name: { - if (modelData.name.includes("bluez")) - return "headset"; - else if (modelData.name.includes("hdmi")) - return "tv"; - else if (modelData.name.includes("usb")) - return "headset"; - else - return "speaker"; - } + name: AudioService.sinkIcon(modelData) size: Theme.iconSize - 4 color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText anchors.verticalCenter: parent.verticalCenter diff --git a/quickshell/Modules/OSD/AudioOutputOSD.qml b/quickshell/Modules/OSD/AudioOutputOSD.qml index 33a36acb..1bea1e5e 100644 --- a/quickshell/Modules/OSD/AudioOutputOSD.qml +++ b/quickshell/Modules/OSD/AudioOutputOSD.qml @@ -22,27 +22,14 @@ DankOSD { text: root.deviceName } - function getIconForSink(sink) { - if (!sink) - return "speaker"; - const name = sink.name || ""; - if (name.includes("bluez")) - return "headset"; - if (name.includes("hdmi")) - return "tv"; - if (name.includes("usb")) - return "headset"; - return "speaker"; - } - Connections { target: AudioService - function onAudioOutputCycled(name) { + function onAudioOutputCycled(name, icon) { if (!SettingsData.osdAudioOutputEnabled) return; root.deviceName = name; - root.deviceIcon = getIconForSink(AudioService.sink); + root.deviceIcon = icon; root.show(); } } diff --git a/quickshell/Services/AudioService.qml b/quickshell/Services/AudioService.qml index 653f66d4..20293d95 100644 --- a/quickshell/Services/AudioService.qml +++ b/quickshell/Services/AudioService.qml @@ -43,7 +43,7 @@ Singleton { } signal micMuteChanged - signal audioOutputCycled(string deviceName) + signal audioOutputCycled(string deviceName, string deviceIcon) signal deviceAliasChanged(string nodeName, string newAlias) signal wireplumberReloadStarted signal wireplumberReloadCompleted(bool success) @@ -67,7 +67,8 @@ Singleton { } function getAvailableSinks() { - return Pipewire.nodes.values.filter(node => node.audio && node.isSink && !node.isStream); + const hidden = SessionData.hiddenOutputDeviceNames ?? []; + return Pipewire.nodes.values.filter(node => node.audio && node.isSink && !node.isStream && !hidden.includes(node.name)); } function cycleAudioOutput() { @@ -75,20 +76,13 @@ Singleton { if (sinks.length < 2) return null; - const currentSink = root.sink; - let currentIndex = -1; - for (let i = 0; i < sinks.length; i++) { - if (sinks[i] === currentSink) { - currentIndex = i; - break; - } - } - + const currentName = root.sink?.name ?? ""; + const currentIndex = sinks.findIndex(s => s.name === currentName); const nextIndex = (currentIndex + 1) % sinks.length; const nextSink = sinks[nextIndex]; Pipewire.preferredDefaultAudioSink = nextSink; const name = displayName(nextSink); - audioOutputCycled(name); + audioOutputCycled(name, sinkIcon(nextSink)); return name; } @@ -690,6 +684,46 @@ EOFCONFIG } } + function sinkIcon(node) { + if (!node) + return "speaker"; + + const props = node.properties || {}; + const formFactor = (props["device.form-factor"] || "").toLowerCase(); + + switch (formFactor) { + case "headphone": + case "headset": + case "hands-free": + case "handset": + return "headset"; + case "tv": + case "monitor": + return "tv"; + case "speaker": + case "computer": + case "hifi": + case "portable": + case "car": + return "speaker"; + } + + const bus = (props["device.bus"] || "").toLowerCase(); + if (bus === "bluetooth") + return "headset"; + + const name = (node.name || "").toLowerCase(); + if (name.includes("hdmi")) + return "tv"; + if (name.includes("iec958") || name.includes("spdif")) + return "speaker"; + + if (bus === "usb") + return "headset"; + + return "speaker"; + } + function displayName(node) { if (!node) { return "";