Prevents Bluetooth audio devices from hijacking the default macOS microphone
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 | 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.
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.
MicGuard is pre-1.0. Notification names and payload schema may change before version 1.0.0.
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)")
}
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
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).