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.
This commit is contained in:
EdisonJwa
2026-05-16 14:09:47 +09:00
parent 4d57d189c9
commit ade50488d9
+25 -9
View File
@@ -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`