diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 8b7516362..5a41af794 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -229,6 +229,7 @@ Singleton { property bool showWeekNumber: false property string calendarBackend: "auto" property string defaultTaskCalendarId: "" + property bool audioShowStreamDevices: false property string clockFormat: "auto" readonly property bool localeUses24Hour: { const fmt = Qt.locale().timeFormat(Locale.ShortFormat).replace(/'[^']*'/g, ""); diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index 47fe985b3..5c95b79a9 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -79,6 +79,7 @@ var SPEC = { showWeekNumber: { def: false }, calendarBackend: { def: "auto" }, defaultTaskCalendarId: { def: "" }, + audioShowStreamDevices: { def: false }, clockFormat: { def: "auto", onChange: "markGreeterSyncPending" }, showSeconds: { def: false, onChange: "markGreeterSyncPending" }, padHours12Hour: { def: false, onChange: "markGreeterSyncPending" }, diff --git a/quickshell/Modules/Settings/AudioTab.qml b/quickshell/Modules/Settings/AudioTab.qml index 042e0ddcf..2ec221aec 100644 --- a/quickshell/Modules/Settings/AudioTab.qml +++ b/quickshell/Modules/Settings/AudioTab.qml @@ -50,12 +50,12 @@ Item { }; const outputs = allNodes.filter(node => { - return node.audio && node.isSink && !node.isStream; + return node.audio && node.isSink && (SettingsData.audioShowStreamDevices || !node.isStream); }); outputDevices = outputs.sort(sortDevices); const inputs = allNodes.filter(node => { - return node.audio && !node.isSink && !node.isStream; + return node.audio && !node.isSink && (SettingsData.audioShowStreamDevices || !node.isStream); }); const sortInputs = (a, b) => { @@ -84,6 +84,13 @@ Item { } } + Connections { + target: SettingsData + function onAudioShowStreamDevicesChanged() { + root.updateDeviceList(); + } + } + Connections { target: AudioService function onWireplumberReloadStarted() { @@ -133,6 +140,16 @@ Item { width: parent.width spacing: Theme.spacingM + SettingsToggleRow { + tab: "audio" + tags: ["audio", "virtual", "stream", "obs", "loopback", "device"] + settingKey: "audioShowStreamDevices" + text: I18n.tr("Show Virtual Devices") + description: I18n.tr("Include application streams and virtual sinks in the device lists") + checked: SettingsData.audioShowStreamDevices + onToggled: checked => SettingsData.set("audioShowStreamDevices", checked) + } + StyledText { width: parent.width text: I18n.tr("Set custom names for your audio output devices", "Audio settings description") diff --git a/quickshell/Services/AudioService.qml b/quickshell/Services/AudioService.qml index b88af4d3f..5b8d6d267 100644 --- a/quickshell/Services/AudioService.qml +++ b/quickshell/Services/AudioService.qml @@ -99,7 +99,7 @@ Singleton { function getAvailableSinks() { const hidden = SessionData.hiddenOutputDeviceNames ?? []; - return Pipewire.nodes.values.filter(node => node.audio && node.isSink && !node.isStream && !hidden.includes(node.name)); + return Pipewire.nodes.values.filter(node => node.audio && node.isSink && (SettingsData.audioShowStreamDevices || !node.isStream) && !hidden.includes(node.name)); } property list typedSinks: [] @@ -109,7 +109,7 @@ Singleton { const newSinks = []; const newSources = []; for (const node of Pipewire.nodes.values) { - if (!node?.audio || node.isStream) + if (!node?.audio || (node.isStream && !SettingsData.audioShowStreamDevices)) continue; if (node.isSink) newSinks.push(node); @@ -127,6 +127,13 @@ Singleton { } } + Connections { + target: SettingsData + function onAudioShowStreamDevicesChanged() { + root.rebuildTypedNodeLists(); + } + } + function setSink(node: PwNode): bool { if (!node) return false; @@ -958,7 +965,7 @@ EOFCONFIG } PwObjectTracker { - objects: Pipewire.nodes.values.filter(node => node.audio && !node.isStream) + objects: Pipewire.nodes.values.filter(node => node.audio && (SettingsData.audioShowStreamDevices || !node.isStream)) } function setVolume(percentage) { diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 3ca29ca76..8cef84ee3 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -9025,18 +9025,57 @@ "tabIndex": 29, "category": "Audio", "keywords": [ + "app", + "application", "audio", "device", "devices", "headphones", + "include", + "lists", "microphone", "output", "pipewire", + "program", + "sinks", "sound", "speaker", + "streams", + "virtual", "volume" ], - "icon": "volume_up" + "icon": "volume_up", + "description": "Include application streams and virtual sinks in the device lists" + }, + { + "section": "audioShowStreamDevices", + "label": "Show Virtual Devices", + "tabIndex": 29, + "category": "Audio", + "keywords": [ + "app", + "application", + "audio", + "device", + "devices", + "headphones", + "include", + "lists", + "loopback", + "microphone", + "obs", + "pipewire", + "program", + "show", + "sinks", + "sound", + "speaker", + "stream", + "streams", + "virtual", + "volume" + ], + "description": "Include application streams and virtual sinks in the device lists" }, { "section": "_tab_29",