MicGuard

Prevents Bluetooth audio devices from hijacking the default macOS microphone

View the Project on GitHub pszypowicz/MicGuard

Distributed Notifications

MicGuard posts macOS distributed notifications that any app or script can observe to react to mic state changes.

Home · CLI Reference · Debugging · Integrations · Releasing

Notification reference

Notification Direction Posted when
com.pszypowicz.MicGuard.statusChanged Outbound Volume change, mute toggle, enabled toggle, device plug/unplug, default device switch, app launch, CLI command response
com.pszypowicz.MicGuard.appTerminated Outbound The app is about to quit
com.pszypowicz.MicGuard.requestStatus Inbound CLI ping command (or any external process) asks the daemon to re-broadcast status

CLI commands perform direct work (CoreAudio calls, config writes) and then post a requestStatus notification so the daemon re-reads state and broadcasts statusChanged. To request a status broadcast from an external integration, use mic-guard ping.

Payload schema

statusChanged userInfo

Key Type Values
info String JSON string containing the unified payload (see below)

The info value is a JSON-serialized string with the following structure:

{
  "enabled": true,
  "mode": "auto",
  "devices": [
    {
      "name": "MacBook Pro Microphone",
      "current": true,
      "volume": 75,
      "muted": false,
      "available": true,
      "preferred": true
    }
  ]
}
Field Type Description
enabled Boolean Whether MicGuard device enforcement is active
mode String Current mode: "auto" or "manual"
devices Array All input devices, sorted alphabetically by name
devices[].name String Device name
devices[].current Boolean true if this is the active input device
devices[].volume Integer Input volume 0–100
devices[].muted Boolean Native mute flag state
devices[].available Boolean true if the device is currently connected (the preferred device appears with false when disconnected)
devices[].preferred Boolean true if this is the configured preferred device

Volume and mute changes are debounced (100ms) before posting.

The appTerminated notification carries no userInfo payload.

API stability

MicGuard is pre-1.0. Notification names and payload schema may change before version 1.0.0.

Observing notifications

Swift

import Foundation

let center = DistributedNotificationCenter.default()

center.addObserver(
    forName: NSNotification.Name("com.pszypowicz.MicGuard.statusChanged"),
    object: nil,
    queue: .main
) { notification in
    let info = notification.userInfo as? [String: String] ?? [:]
    guard let jsonString = info["info"],
          let data = jsonString.data(using: .utf8),
          let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return }
    let enabled = payload["enabled"] as? Bool ?? false
    let devices = payload["devices"] as? [[String: Any]] ?? []
    for device in devices {
        let name = device["name"] as? String ?? ""
        let current = device["current"] as? Bool ?? false
        let volume = device["volume"] as? Int ?? 0
        let muted = device["muted"] as? Bool ?? false
        print("  \(current ? "▶" : " ") \(name) vol=\(volume) muted=\(muted)")
    }
    print("MicGuard: enabled=\(enabled) devices=\(devices.count)")
}

Shell (SketchyBar)

SketchyBar can subscribe to distributed notifications as custom events:

# Register the distributed notification as a SketchyBar event
sketchybar --add event mic_status_changed "com.pszypowicz.MicGuard.statusChanged"

# Subscribe an item to the event
sketchybar --subscribe mic mic_status_changed

Shell (generic)

You can observe notifications from the command line using notificationlistener or similar tools, but the most practical approach is through an app that supports distributed notification subscriptions (like SketchyBar, Hammerspoon, or a custom Swift script).

Use cases