From ade50488d91554e77e0cde7275b846fc35788098 Mon Sep 17 00:00:00 2001 From: EdisonJwa Date: Sat, 16 May 2026 14:09:47 +0900 Subject: [PATCH] feat(audio,macos): live IOHIDCheckAccess for Input Monitoring permission Replaces the macOS PTT backend's query_permission() stub (which had returned Undetermined unconditionally) with a real IOKit call: extern "C" { fn IOHIDCheckAccess(request_type: u32) -> u32; } IOHIDCheckAccess(kIOHIDRequestTypeListenEvent = 1) Returns Granted (0), Denied (1), or Unknown (2). The existing 1.5 s re-query worker now drives real descriptor transitions when the user grants or revokes Input Monitoring in System Settings: the watch sender republishes the descriptor, ChanoraSession forwards BridgeEvent::PttCapability, and the Flutter capability badge updates within ~1.5 s without an app restart. Verified live on the M1 Mac: rustc /tmp/check_perm.rs && ./check_perm IOHIDCheckAccess(ListenEvent) = 2 (Unknown) This is the expected initial state on a fresh box where Chanora has not yet attempted CGEventTapCreate; once the next commit lands the event-tap worker, the macOS Input Monitoring prompt will fire on first audio start and the value transitions to Granted/Denied. Tests: chanora_audio 28 / 0 / 0 on macOS (Linux had 32; the 4-test delta is the Linux-only portal probe tests). The existing 7 macOS backend unit tests still cover the descriptor builder + state machine purely; they don't exercise the live IOKit call (which would need a TCC-aware test harness). SDD-085 reference: macOS Event Tap backend / L2 / L3 capability; SRS-198 honest capability advertising. --- .../chanora_audio/src/ptt_backends/macos.rs | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/crates/chanora_audio/src/ptt_backends/macos.rs b/crates/chanora_audio/src/ptt_backends/macos.rs index 8733e2e..eb31e2c 100644 --- a/crates/chanora_audio/src/ptt_backends/macos.rs +++ b/crates/chanora_audio/src/ptt_backends/macos.rs @@ -58,15 +58,31 @@ impl PermissionState { } fn query_permission() -> PermissionState { - // Live `IOHIDCheckAccess` query lands in the macOS platform - // verification commit (it needs the IOKit framework link). - // Until then we report `Undetermined` so the descriptor stays - // at `L0Focused` and the audio engine continues with the - // Focused widget — honest reporting, no over-claim. The - // periodic re-query loop below still exercises the watch - // sender path so a future replacement of this function - // automatically engages the runtime upgrade. - PermissionState::Undetermined + // Live IOHIDCheckAccess query against the IOKit HID subsystem. + // The Input Monitoring permission ("Listen Event" access type) + // is what gates CGEventTap from receiving global key/mouse + // events. Returns the live permission state every call; the + // periodic re-query loop in `MacOSEventTapBackend::start` + // republishes the descriptor whenever this value transitions. + // + // SAFETY: IOHIDCheckAccess is a thread-safe synchronous IOKit + // entry point with no side effects beyond reading a system + // preference. The `#[link]` attribute on the extern block + // resolves at build time against the IOKit framework. The u32 + // return value maps directly onto the documented + // IOHIDAccessType enumerator { Granted=0, Denied=1, Unknown=2 }. + #[link(name = "IOKit", kind = "framework")] + extern "C" { + fn IOHIDCheckAccess(request_type: u32) -> u32; + } + // kIOHIDRequestTypeListenEvent == 1 per IOHIDLib.h. + const REQUEST_TYPE_LISTEN_EVENT: u32 = 1; + let raw = unsafe { IOHIDCheckAccess(REQUEST_TYPE_LISTEN_EVENT) }; + match raw { + 0 => PermissionState::Granted, + 1 => PermissionState::Denied, + _ => PermissionState::Undetermined, + } } /// Try to construct the macOS event-tap backend. Returns `None`