1414
1515/// Returns `true` when at least one required permission has not been granted.
1616///
17- /// All three permissions must be granted for Thuki to function fully:
18- /// - Accessibility: required to create the CGEventTap
19- /// - Input Monitoring: required for the tap to receive events from other apps
20- /// - Screen Recording: required for the /screen command
21- ///
22- /// If any is missing the onboarding screen is shown instead of the normal overlay.
23- pub fn needs_onboarding(
24- accessibility: bool,
25- screen_recording: bool,
26- input_monitoring: bool,
27- ) -> bool {
28- !accessibility || !screen_recording || !input_monitoring
17+ /// Both Accessibility (hotkey listener) and Screen Recording (/screen command)
18+ /// must be granted for Thuki to function fully. If either is missing the
19+ /// onboarding screen is shown instead of the normal overlay.
20+ pub fn needs_onboarding(accessibility: bool, screen_recording: bool) -> bool {
21+ !accessibility || !screen_recording
2922}
3023
3124// ─── macOS Permission Checks ─────────────────────────────────────────────────
@@ -36,47 +29,13 @@ extern "C" {
3629 fn AXIsProcessTrusted() -> bool;
3730}
3831
39- /// IOKit constants for Input Monitoring permission checks.
40- #[cfg(target_os = "macos")]
41- const IOHID_REQUEST_TYPE_LISTEN_EVENT: u32 = 1;
42- #[cfg(target_os = "macos")]
43- const IOHID_ACCESS_TYPE_GRANTED: u32 = 1;
44-
45- #[cfg(target_os = "macos")]
46- #[link(name = "IOKit", kind = "framework")]
47- extern "C" {
48- /// Checks whether the process has Input Monitoring access.
49- /// Returns kIOHIDAccessTypeGranted (1) if granted, 0 if not determined,
50- /// 2 if denied.
51- fn IOHIDCheckAccess(request_type: u32) -> u32;
52-
53- /// Requests Input Monitoring access, showing the system permission dialog
54- /// on first call. Returns true if access was granted.
55- fn IOHIDRequestAccess(request_type: u32) -> bool;
56- }
57-
5832/// Returns whether the process currently has Accessibility permission.
5933#[cfg(target_os = "macos")]
6034#[cfg_attr(coverage_nightly, coverage(off))]
6135pub fn is_accessibility_granted() -> bool {
6236 unsafe { AXIsProcessTrusted() }
6337}
6438
65- /// Returns whether the process currently has Input Monitoring permission.
66- ///
67- /// Input Monitoring (`kTCCServiceListenEvent`) is required for a CGEventTap at
68- /// HID level to receive keyboard events from other applications. Without it,
69- /// the tap only sees events generated within the Thuki process itself, making
70- /// the double-tap hotkey invisible when the user's focus is elsewhere.
71- ///
72- /// Unlike Screen Recording, Input Monitoring does not require a process restart
73- /// after being granted; the CGEventTap immediately begins receiving cross-app events.
74- #[cfg(target_os = "macos")]
75- #[cfg_attr(coverage_nightly, coverage(off))]
76- pub fn is_input_monitoring_granted() -> bool {
77- unsafe { IOHIDCheckAccess(IOHID_REQUEST_TYPE_LISTEN_EVENT) == IOHID_ACCESS_TYPE_GRANTED }
78- }
79-
8039/// Returns whether the process currently has Screen Recording permission.
8140///
8241/// Uses `CGPreflightScreenCaptureAccess`, which only returns `true` after
@@ -102,44 +61,6 @@ pub fn check_accessibility_permission() -> bool {
10261 is_accessibility_granted()
10362}
10463
105- /// Returns whether Input Monitoring permission has been granted.
106- #[tauri::command]
107- #[cfg(target_os = "macos")]
108- #[cfg_attr(coverage_nightly, coverage(off))]
109- pub fn check_input_monitoring_permission() -> bool {
110- is_input_monitoring_granted()
111- }
112-
113- /// Triggers the macOS Input Monitoring permission dialog.
114- ///
115- /// `IOHIDRequestAccess` registers the app in TCC and shows the system-level
116- /// "X would like to monitor input events" prompt. If the user previously denied
117- /// the permission, the dialog is skipped and the call returns false; the
118- /// onboarding UI then directs the user to System Settings directly.
119- #[tauri::command]
120- #[cfg(target_os = "macos")]
121- #[cfg_attr(coverage_nightly, coverage(off))]
122- pub fn request_input_monitoring_access() {
123- unsafe {
124- IOHIDRequestAccess(IOHID_REQUEST_TYPE_LISTEN_EVENT);
125- }
126- }
127-
128- /// Opens System Settings to the Input Monitoring privacy pane.
129- #[tauri::command]
130- #[cfg(target_os = "macos")]
131- #[cfg_attr(coverage_nightly, coverage(off))]
132- pub fn open_input_monitoring_settings() -> Result<(), String> {
133- std::process::Command::new("open")
134- .arg(
135- "x-apple.systempreferences:com.apple.preference.security\
136- ?Privacy_ListenEvent",
137- )
138- .spawn()
139- .map(|_| ())
140- .map_err(|e| e.to_string())
141- }
142-
14364/// Opens System Settings to the Accessibility privacy pane so the user can
14465/// enable the permission without encountering the native system popup.
14566///
@@ -244,27 +165,22 @@ mod tests {
244165 use super::*;
245166
246167 #[test]
247- fn needs_onboarding_false_when_all_granted () {
248- assert!(!needs_onboarding(true, true, true ));
168+ fn needs_onboarding_false_when_both_granted () {
169+ assert!(!needs_onboarding(true, true));
249170 }
250171
251172 #[test]
252173 fn needs_onboarding_true_when_accessibility_missing() {
253- assert!(needs_onboarding(false, true, true ));
174+ assert!(needs_onboarding(false, true));
254175 }
255176
256177 #[test]
257178 fn needs_onboarding_true_when_screen_recording_missing() {
258- assert!(needs_onboarding(true, false, true));
259- }
260-
261- #[test]
262- fn needs_onboarding_true_when_input_monitoring_missing() {
263- assert!(needs_onboarding(true, true, false));
179+ assert!(needs_onboarding(true, false));
264180 }
265181
266182 #[test]
267- fn needs_onboarding_true_when_all_missing () {
268- assert!(needs_onboarding(false, false, false ));
183+ fn needs_onboarding_true_when_both_missing () {
184+ assert!(needs_onboarding(false, false));
269185 }
270186}
0 commit comments