From c93226718fc2ca5e46cd15b382256282bd5e86a1 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sat, 17 Jan 2026 21:23:25 -0600 Subject: [PATCH] Fix duplicate device names in dropdown - append device ID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UX Problem: - 4 devices all showed "Cambridge Audio USB Audio 2.0" - Selecting one would select all (Map key collision) - User couldn't distinguish between hw:4,0, front:CARD=C20, iec958:CARD=C20 Solution: - Detect duplicate display names in deviceOptions - Append [device_id] to duplicates for uniqueness - Example: "Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect) [hw:4,0]" Now User Sees: - Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect) [hw:4,0] ← TRUE bit-perfect - Cambridge Audio USB Audio 2.0 [front:CARD=C20,DEV=0] ← Front output with plugins - Cambridge Audio USB Audio 2.0 [iec958:CARD=C20,DEV=0] ← S/PDIF with plugins - Cambridge Audio USB Audio 2.0 [sysdefault:CARD=C20] ← System default with dmix Each option is now unique and selectable. The hw:4,0 option is clearly marked as "Direct Hardware - Bit-perfect" for audiophiles. --- src/lib/components/views/SettingsView.svelte | 47 +++++++++++++++----- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/lib/components/views/SettingsView.svelte b/src/lib/components/views/SettingsView.svelte index 3cbc806a7..390113e74 100644 --- a/src/lib/components/views/SettingsView.svelte +++ b/src/lib/components/views/SettingsView.svelte @@ -231,27 +231,54 @@ // For ALSA: use description from aplay -L if available, otherwise translate // For PipeWire/PulseAudio: names are already friendly let deviceOptions = $derived.by(() => { - const names = backendDevices.map(d => { - // If backend provides description (ALSA via aplay -L), use it + // First pass: generate display names + const displayNames = backendDevices.map(d => { if (d.description && selectedBackend === 'ALSA Direct') { return d.description; } - // Otherwise apply translation if needed return needsTranslation(d.name) ? getDevicePrettyName(d.name) : d.name; }); - return ['System Default', ...names]; + + // Second pass: check for duplicates and make unique if needed + const counts = new Map(); + const uniqueNames = displayNames.map((name, idx) => { + const count = counts.get(name) || 0; + counts.set(name, count + 1); + + // If duplicate, append device ID to make it unique + if (displayNames.filter(n => n === name).length > 1) { + const device = backendDevices[idx]; + return `${name} [${device.name}]`; + } + return name; + }); + + return ['System Default', ...uniqueNames]; }); // Map display name -> device for lookup when user selects let deviceByDisplayName = $derived.by(() => { const map = new Map(); - for (const device of backendDevices) { - // Same logic: prefer description for ALSA - const displayName = (device.description && selectedBackend === 'ALSA Direct') - ? device.description - : (needsTranslation(device.name) ? getDevicePrettyName(device.name) : device.name); + + // Use same logic as deviceOptions to generate unique names + const displayNames = backendDevices.map(d => { + if (d.description && selectedBackend === 'ALSA Direct') { + return d.description; + } + return needsTranslation(d.name) ? getDevicePrettyName(d.name) : d.name; + }); + + backendDevices.forEach((device, idx) => { + let displayName = displayNames[idx]; + + // If duplicate, append device ID + if (displayNames.filter(n => n === displayName).length > 1) { + displayName = `${displayName} [${device.name}]`; + } + map.set(displayName, device); - } + }); + return map; });