Context
MicGuard currently uses two IPC mechanisms between the CLI (mic-guard) and the running daemon:
- Config file writes + DispatchSource file watcher — for
enable, disable, set commands. The CLI writes to ~/.config/mic-guard/, the daemon watches the directory with a DispatchSource and reacts.
- DistributedNotificationCenter — for
ping (and soon mute/setVolume). The CLI posts a notification, the daemon observes it and acts.
Both work reliably today because the app is not sandboxed (distributed via Homebrew, not the Mac App Store).
Why consider XPC
Apple's developer documentation recommends XPC (NSXPCConnection) as the standard macOS pattern for inter-process communication between a CLI tool and a daemon/agent. Key advantages:
- Bidirectional communication — strongly typed interfaces via
NSXPCInterface and Swift protocols. Currently we can't get a response back from the daemon (e.g., confirming a mute toggle succeeded).
- Reliable delivery — DistributedNotificationCenter docs explicitly warn that notifications may be dropped if the server's queue fills up and have "unbounded latency". XPC guarantees delivery.
- Structured messages — typed protocol methods vs. parsing string dictionaries from
userInfo.
- Security — built-in process isolation. DistributedNotificationCenter docs note it is "NOT a secure communications protocol".
- Sandboxing ready — if MicGuard ever moves to the Mac App Store, sandboxed apps cannot send distributed notifications with
userInfo (must be nil). XPC works seamlessly with sandboxing.
Current assessment (March 2025)
DistributedNotificationCenter is adequate for now:
- The app is not sandboxed and has no plans to be
- All commands are fire-and-forget (the daemon broadcasts
statusChanged back, closing the feedback loop naturally via CoreAudio listeners)
- The existing pattern is simple and proven
- Adding XPC would require: a Mach service, a launchd plist, a separate XPC service target in the Xcode project, and an
NSXPCInterface protocol — significant complexity
What an XPC migration would look like
- Define a Swift protocol for the daemon's interface:
@objc protocol MicGuardDaemonProtocol {
func toggleMute(reply: @escaping (Bool) -> Void)
func setVolume(_ volume: Int, reply: @escaping (Bool) -> Void)
func setPreferredDevice(_ name: String, reply: @escaping (Bool) -> Void)
func setEnabled(_ enabled: Bool)
func requestStatus(reply: @escaping ([String: String]) -> Void)
}
- Register a Mach service in the daemon (via
NSXPCListener)
- Have the CLI connect via
NSXPCConnection(machServiceName:)
- Replace all file-write and notification-based IPC with XPC calls
- Keep DistributedNotificationCenter only for outbound broadcasts to external consumers (sketchybar, etc.) — those are genuinely one-to-many and appropriate for notifications
When to pursue this
- If the app needs request/response IPC (e.g., CLI needs to confirm an action succeeded)
- If sandboxing becomes a requirement (Mac App Store distribution)
- If notification drops become a problem in practice
- As a general architecture improvement when the IPC surface grows
References
Context
MicGuard currently uses two IPC mechanisms between the CLI (
mic-guard) and the running daemon:enable,disable,setcommands. The CLI writes to~/.config/mic-guard/, the daemon watches the directory with aDispatchSourceand reacts.ping(and soonmute/setVolume). The CLI posts a notification, the daemon observes it and acts.Both work reliably today because the app is not sandboxed (distributed via Homebrew, not the Mac App Store).
Why consider XPC
Apple's developer documentation recommends XPC (
NSXPCConnection) as the standard macOS pattern for inter-process communication between a CLI tool and a daemon/agent. Key advantages:NSXPCInterfaceand Swift protocols. Currently we can't get a response back from the daemon (e.g., confirming a mute toggle succeeded).userInfo.userInfo(must benil). XPC works seamlessly with sandboxing.Current assessment (March 2025)
DistributedNotificationCenter is adequate for now:
statusChangedback, closing the feedback loop naturally via CoreAudio listeners)NSXPCInterfaceprotocol — significant complexityWhat an XPC migration would look like
NSXPCListener)NSXPCConnection(machServiceName:)When to pursue this
References