From eb0e8366bcfcef798546733d29aab3d0800decdf Mon Sep 17 00:00:00 2001 From: Hampus Date: Sat, 3 Oct 2026 02:14:06 +0200 Subject: [PATCH] fix(voice): keep saved linux audio apps in the source picker (#3150) --- .../components/AudioSourcePickerLinux.tsx | 25 ++++++++----------- .../voice/utils/LinuxAudioSourceRules.ts | 21 ++++++++++++++++ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/fluxer_app/src/features/voice/components/AudioSourcePickerLinux.tsx b/fluxer_app/src/features/voice/components/AudioSourcePickerLinux.tsx index da214012d..22a6d8709 100644 --- a/fluxer_app/src/features/voice/components/AudioSourcePickerLinux.tsx +++ b/fluxer_app/src/features/voice/components/AudioSourcePickerLinux.tsx @@ -15,8 +15,10 @@ import { type LinuxAudioSourceFilterOptions, type LinuxAudioSourceItem, linuxAudioSourceItemKey, + linuxAudioSourcePatternsEqual, mapLinuxAudioNodeToItems, uniqueLinuxAudioSourceItems, + withSelectedLinuxAudioSources, } from '@app/features/voice/utils/LinuxAudioSourceRules'; import { formatScreenShareAudioSummary, @@ -78,15 +80,8 @@ const EMPTY_SNAPSHOT: AudioSourceSnapshot = { error: null, }; -function nodesEqual(a: VirtmicNode, b: VirtmicNode): boolean { - const keysA = Object.keys(a); - const keysB = Object.keys(b); - if (keysA.length !== keysB.length) return false; - return keysA.every((key) => a[key] === b[key]); -} - function isItemSelected(value: VirtmicNode, sources: Array): boolean { - return sources.some((source) => nodesEqual(source, value)); + return sources.some((source) => linuxAudioSourcePatternsEqual(source, value)); } async function fetchAudioSources(options: LinuxAudioSourceFilterOptions): Promise { @@ -188,7 +183,7 @@ export const AudioSourcePickerLinuxSubmenu = observer((props: AudioSourcePickerL (item: LinuxAudioSourceItem) => { const isSelected = isItemSelected(item.value, includeSources); const nextSources = isSelected - ? includeSources.filter((source) => !nodesEqual(source, item.value)) + ? includeSources.filter((source) => !linuxAudioSourcePatternsEqual(source, item.value)) : [...includeSources, item.value]; VoiceSettingsCommands.update({ screenShareAudioSourceMode: nextSources.length > 0 ? 'specific' : 'system', @@ -203,7 +198,7 @@ export const AudioSourcePickerLinuxSubmenu = observer((props: AudioSourcePickerL (item: LinuxAudioSourceItem) => { const isSelected = isItemSelected(item.value, excludeSources); const nextSources = isSelected - ? excludeSources.filter((source) => !nodesEqual(source, item.value)) + ? excludeSources.filter((source) => !linuxAudioSourcePatternsEqual(source, item.value)) : [...excludeSources, item.value]; VoiceSettingsCommands.update({ screenShareAudioExcludeSources: nextSources, @@ -230,6 +225,8 @@ export const AudioSourcePickerLinuxSubmenu = observer((props: AudioSourcePickerL usesDeviceMicrophone, }); const showsWideSourceLists = !offersWindowScope || resolvedScope === 'system'; + const includeItems = withSelectedLinuxAudioSources(snapshot.items, includeSources); + const excludeItems = withSelectedLinuxAudioSources(snapshot.items, excludeSources); const wideSourceIsSelected = isDeviceShare ? usesDeviceMicrophone : sourceMode === 'system'; if (!snapshot.available && !snapshot.loading) { return null; @@ -269,12 +266,12 @@ export const AudioSourcePickerLinuxSubmenu = observer((props: AudioSourcePickerL )} - {showsWideSourceLists && snapshot.items.length > 0 && ( + {showsWideSourceLists && includeItems.length > 0 && ( {i18n._(INCLUDE_APPS_DESCRIPTOR)} - {snapshot.items.map((item) => ( + {includeItems.map((item) => ( )} - {!isDeviceShare && showsWideSourceLists && sourceMode === 'system' && snapshot.items.length > 0 && ( + {!isDeviceShare && showsWideSourceLists && sourceMode === 'system' && excludeItems.length > 0 && ( {i18n._(EXCLUDE_FROM_SYSTEM_DESCRIPTOR)} - {snapshot.items.map((item) => ( + {excludeItems.map((item) => ( a[key] === b[key]); +} + +export function withSelectedLinuxAudioSources( + items: ReadonlyArray, + selected: ReadonlyArray, +): Array { + const missing = selected.filter((source) => !items.some((item) => linuxAudioSourcePatternsEqual(item.value, source))); + return [ + ...items, + ...missing.map((source) => ({ + name: getLinuxAudioSourceDisplayName(source) ?? JSON.stringify(source), + value: source, + })), + ]; +} + export function uniqueLinuxAudioSourceItems(items: Array): Array { const seen = new Set(); const result: Array = [];