Skip to content

Skip a USB headset dongle while no headset is linked to it - #32

Open
jerrison wants to merge 1 commit into
tobi:mainfrom
jerrison:skip-dongle-when-headset-not-linked
Open

Skip a USB headset dongle while no headset is linked to it#32
jerrison wants to merge 1 commit into
tobi:mainfrom
jerrison:skip-dongle-when-headset-not-linked

Conversation

@jerrison

Copy link
Copy Markdown

The problem

A wireless headset dongle publishes its CoreAudio devices whenever it is plugged in, whether or not the headset is powered on and linked to it. Auto-switching ranks by presence, so the dongle wins and audio goes into a dead link — it shows as Active while nothing comes out. The only recovery is picking another device by hand every time the headset is off, which is the problem priorities are meant to solve.

CoreAudio gives you nothing to work with here. Measured on a Jabra Link 390, headset off and then on:

Signal Value Usable?
kAudioDevicePropertyJackIsConnected never published by the driver No
DeviceIsAlive 1 either way No
Channel counts 2 out / 1 in either way No
Available sample rates 16k/32k/48k out, 16k in either way No
Stream format, terminal type 48kHz 2ch, hdph / hmic either way No
CanBeDefaultDevice 1 either way No

The fix

The dongle does report it over HID. WirelessLinkMonitor reads that bit for dongles it recognises, and applyHighestPriorityInput/applyHighestPriorityOutput skip a device whose link is down.

For the Jabra Link 390 it is HID Telephony usage 0x2A ("Line"), report 0x04 byte 1 bit 3, which reads 1 only while the headset is linked to the dongle. Adding another dongle is one entry in WirelessLinkMonitor.knownDongles.

That the bit is link state and not a button press was confirmed against the hardware: powering the headset on over Bluetooth left it at 0 while the headset's Bluetooth devices appeared, and when the headset later moved its link to the dongle it went to 1 and the Bluetooth devices were removed 1.7 seconds later.

Why this should not affect anyone else

  • Unrecognised devices are always reported usable. The monitor only ever returns down for a dongle in knownDongles whose bit it actually read. Every other device takes exactly the path it takes today.
  • No new permissions. Reading the bit needs no Input Monitoring grant and works from an unsigned build — verified.
  • Debounced. A link must stay down 3 seconds before the device is skipped, so a momentary drop cannot pull audio away mid-call.
  • Backstop poll. HID reports drive it; a 2-second poll covers a missed report so a device cannot stay stuck.

Verified on the hardware

A/B on identical state — dongle plugged in, headset not linked, dongle at priority 1, default output seeded to the dongle before each run:

Build Result
main keeps Jabra Link 390 — the dead link
this branch moves off it to the next usable device

Also checked: with the headset linked the dongle is selected normally, and xcodebuild -scheme AudioPriorityBar -configuration Release succeeds with no new warnings.

UI

A device in this state is greyed with an antenna.radiowaves.left.and.right.slash icon and a not linked label, so a skipped device does not read as a bug. It follows the existing wifi.slash / nosign status-icon pattern.

Notes

  • One new file, Services/WirelessLinkMonitor.swift, registered in the Xcode project (the project uses a classic group list, so project.pbxproj needed the four usual entries).
  • Tested on macOS 26.5.2, Apple silicon, with a Jabra Link 390 and an Evolve2 75. I only have the one dongle family to test against, so the knownDongles list starts with one entry.

A wireless headset dongle publishes its CoreAudio devices whenever it is
plugged in, whether or not the headset is powered on and linked. Because
auto-switching ranks by presence, the dongle wins and audio goes into a dead
link: it shows as Active while nothing comes out. Recovering means picking
another device by hand every time the headset is off, which is the problem
priorities are supposed to solve.

CoreAudio cannot tell the two states apart. Measured on a Jabra Link 390 with
the headset off and then on, the driver never publishes
kAudioDevicePropertyJackIsConnected, and DeviceIsAlive, channel counts,
available sample rates, stream formats and CanBeDefaultDevice are identical.

The dongle does report it over HID. WirelessLinkMonitor reads that bit for
dongles it knows about and applyHighestPriorityInput/Output skip a device whose
link is down. For the Jabra Link 390 the bit is HID Telephony usage 0x2A
("Line"), report 0x04 byte 1 bit 3, which reads 1 only while the headset is
linked to the dongle.

Any device the monitor does not recognise is always reported as usable, so
behaviour is unchanged for every other piece of hardware. Reading the bit needs
no Input Monitoring permission and works from an unsigned build. A link must
stay down for 3 seconds before the device is skipped, so a momentary drop
cannot pull audio away mid-call.

Rows for a device in this state are greyed and marked "not linked", so a
skipped device does not look like a bug.
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