Skip to content

Fix mute on Aggregate Devices (use CoreAudio mute property)#27

Open
super-cache-money wants to merge 1 commit into
satrik:mainfrom
super-cache-money:fix/aggregate-device-mute
Open

Fix mute on Aggregate Devices (use CoreAudio mute property)#27
super-cache-money wants to merge 1 commit into
satrik:mainfrom
super-cache-money:fix/aggregate-device-mute

Conversation

@super-cache-money

Copy link
Copy Markdown

Summary

Pressing the toggleMute hotkey while an Aggregate Device is the default input doesn't actually mute the mic. The menu bar icon briefly flashes red, then reverts to unmuted ~1 second later. This PR fixes that.

Why this matters

A common macOS workaround for the auto-switching-input-to-Bluetooth-headphones annoyance is to create an Aggregate Device that wraps your built-in mic, and set that as the system input — so the system never switches away from your real mic when AirPods/headphones connect. See e.g. this r/MacOS comment which is the recipe a lot of people land on. Anyone using that workaround currently can't use toggleMute.

Root cause

Two things conspire:

  1. AppleScript set volume input volume 0 is a no-op on Aggregate Devices. Aggregates don't expose a writable master volume — they delegate to their sub-devices — so the AppleScript silently does nothing. A regular USB/built-in mic implements the property directly, which is why those work.
  2. The 1-second watchdog timer in AppDelegate.runTimedCode overrides the UI. It reads input volume via the same broken AppleScript path; for aggregates the read returns a stale ~100, so it flips the icon back to "unmuted" right after you tried to mute.

Fix

New AudioInputController (Support/) that drives mute and volume via CoreAudio:

  • Resolves the default input device, detects whether it's an aggregate, and dispatches kAudioDevicePropertyMute / kAudioDevicePropertyVolumeScalar to each input-bearing sub-device.
  • Tries the master element first, then per-channel; if no writable mute property exists on a device, falls back to driving volume to 0.
  • TouchBarController.toggleMuteStateHard and setNewVolume now go through AudioInputController instead of AppleScript.
  • AppDelegate.runTimedCode and MainController.getCurrentVolume read real mute/volume state via CoreAudio. If the state can't be read, the watchdog leaves the UI alone (no more flip-back).

No change to the behaviour with non-aggregate devices — those paths still work as before.

Tested

Built and ran locally on macOS (Apple Silicon), with an Aggregate Device set as default input. Confirmed:

  • Hotkey now mutes and stays muted; menu bar icon stays red.
  • Hotkey unmutes correctly.
  • Restoring the user's preferred input volume on unmute still works.
  • Regular built-in mic still works as before.

Test plan

  • Set the default input to a regular device (built-in mic, USB mic); hotkey mutes/unmutes as before.
  • Open Audio MIDI Setup → create an Aggregate Device → set it as default input. Hotkey should now mute and stay muted, and unmute restores volume.
  • Slider in the popover still adjusts input volume.
  • Mute toggled externally (e.g. via system menu) is reflected in the menu bar icon within ~1s.

AppleScript `set volume input volume 0` is a no-op on Aggregate
Devices, so the menu bar icon would briefly flash red and then revert
to unmuted within a second (the watchdog timer reads the still-high
volume back and forces the UI to "unmuted").

Replace the mute path with CoreAudio: resolve the default input
device, and for an aggregate, dispatch `kAudioDevicePropertyMute`
(and `VolumeScalar` for the slider) to each input-bearing sub-device.
Master element first, then per-channel fallback; if the device
exposes no writable mute property, fall back to driving volume to 0.

Also update the 1-second watchdog and `getCurrentVolume` to read the
real CoreAudio mute/volume state instead of the AppleScript value
(which on aggregates is often a stale 100).
@satrik

satrik commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Hey @super-cache-money, thanks for your PR but I already have more or less the same changes (plus a lot more) on my local setup. It will be part of the v2.
I will double check your code and compare it with my code to make sure to not forget any feature you wanted to bring in.

@super-cache-money

super-cache-money commented Jun 17, 2026 via email

Copy link
Copy Markdown
Author

@Finkregh

Copy link
Copy Markdown
Contributor

Could you create a (draft) PR? @satrik

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.

3 participants