From 6ae3a19e1ed75b23d8f12bfcb8d8867a89c86720 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Tue, 11 Nov 2025 16:25:52 +0100 Subject: [PATCH] AudioService: properly filter outputs with no inputs AudioCard & OSD: use said detection --- Modules/OSD/OSD.qml | 6 +++ .../Panels/ControlCenter/Cards/AudioCard.qml | 37 ++++++++++++-- Services/Media/AudioService.qml | 48 +++++++++++++++---- 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 248f6ac8..786b5200 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -511,6 +511,9 @@ Variants { if (!inputAudioInitialized) { return } + if (!AudioService.hasInput) { + return + } showOSD("inputVolume") } @@ -518,6 +521,9 @@ Variants { if (!inputAudioInitialized) { return } + if (!AudioService.hasInput) { + return + } showOSD("inputVolume") } } diff --git a/Modules/Panels/ControlCenter/Cards/AudioCard.qml b/Modules/Panels/ControlCenter/Cards/AudioCard.qml index 47c35711..05d8b0ec 100644 --- a/Modules/Panels/ControlCenter/Cards/AudioCard.qml +++ b/Modules/Panels/ControlCenter/Cards/AudioCard.qml @@ -9,12 +9,19 @@ import qs.Widgets NBox { id: root - property real localOutputVolume: AudioService.volume || 0 + property real localOutputVolume: 0 property bool localOutputVolumeChanging: false - property real localInputVolume: AudioService.inputVolume || 0 + property real localInputVolume: 0 property bool localInputVolumeChanging: false + Component.onCompleted: { + var vol = AudioService.volume + localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0 + var inputVol = AudioService.inputVolume + localInputVolume = (inputVol !== undefined && !isNaN(inputVol)) ? inputVol : 0 + } + // Timer to debounce volume changes Timer { interval: 100 @@ -31,11 +38,32 @@ NBox { } // Connections to update local volumes when AudioService changes + Connections { + target: AudioService + function onVolumeChanged() { + if (!localOutputVolumeChanging) { + var vol = AudioService.volume + localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0 + } + } + } + Connections { target: AudioService.sink?.audio ? AudioService.sink?.audio : null function onVolumeChanged() { if (!localOutputVolumeChanging) { - localOutputVolume = AudioService.volume + var vol = AudioService.volume + localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0 + } + } + } + + Connections { + target: AudioService + function onInputVolumeChanged() { + if (!localInputVolumeChanging) { + var vol = AudioService.inputVolume + localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0 } } } @@ -44,7 +72,8 @@ NBox { target: AudioService.source?.audio ? AudioService.source?.audio : null function onVolumeChanged() { if (!localInputVolumeChanging) { - localInputVolume = AudioService.inputVolume + var vol = AudioService.inputVolume + localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0 } } } diff --git a/Services/Media/AudioService.qml b/Services/Media/AudioService.qml index 8f09a77b..60526eb3 100644 --- a/Services/Media/AudioService.qml +++ b/Services/Media/AudioService.qml @@ -24,7 +24,11 @@ Singleton { }) readonly property PwNode sink: Pipewire.defaultAudioSink - readonly property PwNode source: Pipewire.defaultAudioSource + readonly property PwNode rawSource: Pipewire.defaultAudioSource + readonly property PwNode source: (rawSource && !rawSource.isSink && (!rawSource.mediaClass || rawSource.mediaClass.startsWith("Audio/Source"))) ? rawSource : null + readonly property bool hasInput: !!source + Component.onCompleted: updateInputVolume() + readonly property list sinks: nodes.sinks readonly property list sources: nodes.sources @@ -37,13 +41,33 @@ Singleton { // Input volume [0..1] is readonly from outside readonly property alias inputVolume: root._inputVolume - property real _inputVolume: source?.audio?.volume ?? 0 + property real _inputVolume: 0 readonly property alias inputMuted: root._inputMuted property bool _inputMuted: !!source?.audio?.muted readonly property real stepVolume: Settings.data.audio.volumeStep / 100.0 + function updateInputVolume() { + if (source && source.audio) { + var vol = source.audio.volume + if (vol !== undefined && !isNaN(vol)) { + root._inputVolume = vol + } else { + root._inputVolume = 0 + } + root._inputMuted = !!source.audio.muted + } else { + root._inputVolume = 0 + root._inputMuted = true + } + } + + // Update input volume when source property changes + onSourceChanged: { + updateInputVolume() + } + PwObjectTracker { objects: [...root.sinks, ...root.sources] } @@ -76,8 +100,9 @@ Singleton { target: source?.audio ? source?.audio : null function onVolumeChanged() { - var vol = (source?.audio.volume ?? 0) - if (isNaN(vol)) { + var vol = source?.audio?.volume + if (vol === undefined || isNaN(vol)) { + root._inputVolume = 0 return } // Only update if the value actually changed to prevent spurious signals @@ -91,10 +116,18 @@ Singleton { // Only update if the value actually changed if (root._inputMuted !== newMuted) { root._inputMuted = newMuted - Logger.i("AudioService", "OnInputMuteChanged:", root._inputMuted) } } } + Connections { + target: Pipewire + + function onDefaultAudioSinkChanged() {} + + function onDefaultAudioSourceChanged() { + updateInputVolume() + } + } function increaseVolume() { setVolume(volume + stepVolume) @@ -158,9 +191,8 @@ Singleton { function setAudioSource(newSource: PwNode): void { Pipewire.preferredDefaultAudioSource = newSource - // Volume is changed by the source change - root._inputVolume = newSource?.audio?.volume ?? 0 - root._inputMuted = !!newSource?.audio?.muted + // The source property will update automatically, which triggers onSourceChanged + // which calls updateInputVolume() } function getOutputIcon() {