Switch macOS default audio devices when specific USB devices connect or disconnect.
Typical use case: docking a MacBook to a hub should move audio output to a pair of Bluetooth speakers and input to a webcam microphone; undocking should revert to the built-in speakers and microphone.
- Event-driven, one-shot. The switch happens once, at (dis)connect time. Nothing polls or re-enforces afterwards, so a manual override in Audio MIDI Setup or the Sound settings always sticks.
- Per-device rules. Any USB device can trigger its own output/input combination. USB devices without a rule are ignored.
- Persistent. Runs as a LaunchAgent, starting at every login — with one command to stop or remove it.
- Single binary, zero dependencies. Swift + IOKit + CoreAudio.
macOS only, macOS 13 or newer.
Requires the Xcode Command Line Tools (xcode-select --install).
git clone <this repo> && cd audioswitch
make install # builds and copies the binary to /usr/local/bin (may need sudo)
audioswitch init # creates ~/.config/audioswitch/config.json
# ... edit the config (see below) ...
audioswitch install # installs the LaunchAgent and starts the daemonUSB devices (for vendorId / productId) — connect the hub, then:
audioswitch list-usbAlternative built-in command: system_profiler SPUSBDataType.
Note: docks/hubs enumerate as several USB sub-devices, sometimes with different
IDs for the USB 2 and USB 3 personalities. Pick one entry — ideally the dock
itself rather than a device plugged into it.
Audio devices (for output / input names):
audioswitch list-audioAlternative built-in command: system_profiler SPAudioDataType. The names must
match what Audio MIDI Setup shows (case-insensitive).
~/.config/audioswitch/config.json (honors XDG_CONFIG_HOME):
{
"devices": [
{
"name": "OWC Thunderbolt Hub",
"vendorId": "0x1e91",
"productId": "0xa4b7",
"onConnect": {
"output": "Soundsticks Wireless",
"input": "External Camera Microphone",
"soundEffects": "Soundsticks Wireless"
},
"onDisconnect": {
"output": "builtin",
"input": "builtin",
"soundEffects": "builtin"
},
"graceSeconds": 30,
"notify": false
}
]
}| Field | Meaning |
|---|---|
name |
Optional label used in logs. |
vendorId, productId |
USB IDs from list-usb. Hex string ("0x1e91") or decimal number. |
onConnect |
Devices to select when the USB device appears. Any of output, input, soundEffects — omit one to leave it alone. |
onDisconnect |
Devices to select when it disappears. If omitted, everything onConnect touched reverts to builtin. |
soundEffects |
The alert/notification sound device — "Play sound effects through" in Sound settings. Separate from output; any output-capable device works. |
graceSeconds |
How long to wait for a target audio device to show up after the event (default 30). Bluetooth speakers and USB microphones often enumerate a few seconds late. |
notify |
true shows a macOS notification banner on every switch (default false). |
"builtin" is a special device name that resolves to the built-in speakers or
microphone regardless of what your Mac model calls them.
After editing the config, apply it with audioswitch start (restarts the daemon).
audioswitch status # is it installed/running, which config is loaded
audioswitch stop # stop until next login
audioswitch start # start again (also reloads the config)
audioswitch uninstall # stop and remove the LaunchAgent for goodTo remove everything including the binary: audioswitch uninstall && sudo make uninstall.
- Manual overrides win. After the one-shot switch, change devices freely in Audio MIDI Setup or Sound settings; audioswitch will not touch them again until the next connect/disconnect event.
- Login while docked counts as a connect. Devices already present when the
daemon starts fire their
onConnectrule (a no-op if the devices are already selected). Caveat: some docks re-enumerate on wake from sleep, which fires the rule again and re-applies the mapping over any manual override. - Grace window, not enforcement. If a target device never appears within
graceSeconds, the daemon logs it and gives up until the next event. - Missing devices at disconnect. Reverting to
builtinalways works; a named revert target gets the same grace window.
The daemon logs to ~/Library/Logs/audioswitch.log: every switch, every device it
waited for, and every give-up, with timestamps.
Run audioswitch daemon in a terminal to watch events live (stop the background
daemon first with audioswitch stop, or the two will race).