Skip to content

Commit c41bf47

Browse files
committed
refactor: remove Input Monitoring permission requirement from onboarding
Simplify the onboarding permission flow from 3 steps (Accessibility, Input Monitoring, Screen Recording) to 2 steps (Accessibility, Screen Recording). Input Monitoring checks and IOKit FFI bindings are removed from permissions.rs and PermissionsStep.tsx. The CGEventTap at HID level receives cross-app keyboard events without needing a TCC entry. Signed-off-by: Logan Nguyen <lg.131.dev@gmail.com>
1 parent 6d44097 commit c41bf47

File tree

6 files changed

+103
-659
lines changed

6 files changed

+103
-659
lines changed

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Never commit files generated by superpowers skills (design specs, implementation
110110

111111
- **macOS only** — uses NSPanel, Core Graphics event taps, macOS Control key
112112
- **Privacy-first** — Ollama runs locally; Docker sandbox drops all capabilities and isolates network
113-
- **Three permissions required** — Accessibility (CGEventTap creation), Input Monitoring (cross-app key delivery), Screen Recording (/screen command)
113+
- **Two permissions required** — Accessibility (CGEventTap creation), Screen Recording (/screen command)
114114

115115
### CGEventTap configuration — DO NOT CHANGE these two settings
116116

src-tauri/src/activator.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -247,12 +247,6 @@ fn try_initialize_tap<F>(is_active: &Arc<AtomicBool>, on_activation: &Arc<F>) ->
247247
where
248248
F: Fn() + Send + Sync + 'static,
249249
{
250-
let im_granted = crate::permissions::is_input_monitoring_granted();
251-
eprintln!(
252-
"thuki: [activator] Input Monitoring permission: granted={im_granted} \
253-
(cross-app hotkey requires this)"
254-
);
255-
256250
let state = Arc::new(Mutex::new(ActivationState {
257251
last_trigger: None,
258252
is_pressed: false,

src-tauri/src/lib.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -402,9 +402,8 @@ fn notify_frontend_ready(app_handle: tauri::AppHandle, db: tauri::State<history:
402402
// screen again on the next launch.
403403
let ax = permissions::is_accessibility_granted();
404404
let sr = permissions::is_screen_recording_granted();
405-
let im = permissions::is_input_monitoring_granted();
406405

407-
if !ax || !sr || !im {
406+
if !ax || !sr {
408407
let _ = onboarding::set_stage(&conn, &onboarding::OnboardingStage::Permissions);
409408
show_onboarding_window(&app_handle, onboarding::OnboardingStage::Permissions);
410409
return;
@@ -762,12 +761,6 @@ pub fn run() {
762761
#[cfg(not(coverage))]
763762
permissions::open_accessibility_settings,
764763
#[cfg(not(coverage))]
765-
permissions::check_input_monitoring_permission,
766-
#[cfg(not(coverage))]
767-
permissions::request_input_monitoring_access,
768-
#[cfg(not(coverage))]
769-
permissions::open_input_monitoring_settings,
770-
#[cfg(not(coverage))]
771764
permissions::check_screen_recording_permission,
772765
#[cfg(not(coverage))]
773766
permissions::open_screen_recording_settings,

src-tauri/src/permissions.rs

Lines changed: 11 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,11 @@
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))]
6135
pub 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

Comments
 (0)