Skip to content

Device list identity + unhide clearing both hidden lists#1

Merged
coherent-cache merged 2 commits into
mainfrom
fix/device-selection-and-hidden-list-bugs
Apr 17, 2026
Merged

Device list identity + unhide clearing both hidden lists#1
coherent-cache merged 2 commits into
mainfrom
fix/device-selection-and-hidden-list-bugs

Conversation

@coherent-cache

Copy link
Copy Markdown
Owner

Two small, independent fixes on top of upstream PR #22 (cherry-picked into this fork's `main`).

1. Key device `ForEach` by `AudioDevice` instead of `AudioObjectID`

`AudioDeviceService.getDevices()` creates two `AudioDevice` values from the same `AudioObjectID` when a device has both input and output streams — the loopback drivers (ZoomAudioDevice, Microsoft Teams Audio, BlackHole 16ch) and aggregates hit this. Disconnected placeholders all use `id = 0`. SwiftUI emits `ForEach<…>: the ID occurs multiple times` and collapses the list. Keying by the full `AudioDevice` (Hashable, includes `type`) disambiguates.

Affected sites:

  • `HiddenDevicesToggleView` (`MenuBarView.swift:376`) — `allHiddenDevices` concatenates all three hidden arrays; a loopback hidden in two categories appears twice with the same `.id`.
  • `DeviceListView` (`DeviceListView.swift:24`) — in edit mode, two or more disconnected placeholders share `id = 0`.

2. `unhideDevice(_:)` clears both hidden lists for outputs

The previous code consulted `hiddenKey(for:)` and cleared one list. Two existing paths put an output device into a list that doesn't match its current category:

  • `hideDeviceEntirely(_:)` adds the device to both `hiddenSpeakers` and `hiddenHeadphones`.
  • The "Ignore as headphone" / "Ignore as speaker" context menu can hide a device in the non-current category.

Result: clicking the eye in the ignored popover silently leaves the device hidden in the other list. Fix: clear `hiddenMics` for inputs and both output lists for outputs.

Test plan

  • Builds cleanly via `./build.sh`
  • Loopback devices (Zoom, Teams) hidden in multiple categories no longer trigger duplicate-ID warnings
  • "Stop ignoring" from the ignored popover always makes the device reappear, regardless of current mode

A CoreAudio device with both input and output streams (virtual loopback
drivers like ZoomAudioDevice, Microsoft Teams Audio, BlackHole 16ch, or
aggregates) is created twice by AudioDeviceService.getDevices() - once
per scope - and both AudioDevice values share the same AudioObjectID.
Disconnected placeholders built via AudioDevice.disconnected(...) all use
id = 0. With ForEach(..., id: \.id) SwiftUI emits "ID occurs multiple
times" and collapses the list to undefined output. Keying by the full
AudioDevice disambiguates input vs output and distinct placeholders.
unhideDevice(_:) previously consulted hiddenKey(for:) (which picks a list
based on the device's current category) and cleared a single list. But
two existing paths put an output device into a list that does not match
its current category:

- hideDeviceEntirely(_:) explicitly adds the device to both
  hiddenSpeakers and hiddenHeadphones.
- "Ignore as headphone" / "Ignore as speaker" can hide a device in the
  non-current category.

Clicking the eye in the ignored popover then appears to do nothing - the
device is removed from one list but remains hidden in the other. Clear
hiddenMics for inputs and both output lists for outputs so "stop
ignoring" always succeeds.
@coherent-cache coherent-cache merged commit 182c7d0 into main Apr 17, 2026
@coherent-cache coherent-cache deleted the fix/device-selection-and-hidden-list-bugs branch April 17, 2026 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant