mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-05-08 15:18:05 +03:00
macos: per-pane TCC navigation and Sequoia-tolerant permission flow
On macOS the three TCC grants (Accessibility, Input Monitoring, Post Event) live in separate Privacy panes. Before this change the "Reenable" row sent the user to Accessibility regardless of which grant was actually missing, and the daemon's own permission checks re-fired the Accessibility prompt on every retry. - lan-mouse-gtk/src/macos_privacy.rs: new module that exposes silent preflight checks (AXIsProcessTrusted, CGPreflightListenEventAccess, CGPreflightPostEventAccess), per-pane URL-scheme navigation, and a Once-guarded fire_initial_prompts() called from build_ui. The initial-prompt path only fires the Accessibility prompt if AX is missing and then returns; secondary registrations run only after AX is granted, which prevents a double Accessibility alert on Sequoia where Post Event is nested under Accessibility. - Input Monitoring registration attempts CGEventTapCreate at kCGSessionEventTap (not kCGHIDEventTap) so a failure surfaces as an Input Monitoring signal rather than triggering an Accessibility prompt as a side effect. - lan-mouse-gtk/src/window/imp.rs: handle_capture / handle_emulation switch on the missing-pane enum and navigate to the specific pane via x-apple.systempreferences:... URLs before re-requesting. - lan-mouse-gtk/resources/window.ui: pill class on the Reenable buttons so the hover padding matches the rest of libadwaita. - input-capture/src/macos.rs, input-emulation/src/macos.rs: make request_*_permission() a silent preflight (AXIsProcessTrusted / CGPreflightListenEventAccess / CGPreflightPostEventAccess), so the daemon no longer fires TCC prompts on retry — all prompting is owned by the GUI. - input-capture/src/error.rs, input-emulation/src/error.rs: new error variants so the GUI can distinguish missing-AX from missing-IM / missing-PostEvent for pane routing. Verified on macOS 15.5: first launch fires a single AX prompt; second launch (AX granted) registers under Input Monitoring via the session-tap attempt and requests Post Event. Sequoia auto-grants the listen-only path via AX so the IM list may stay empty, which is the intended OS behavior and no longer blocks capture. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
Ferdinand Schober
parent
903b0504e0
commit
5e79743bd0
@@ -149,6 +149,10 @@ pub enum MacosCaptureCreationError {
|
||||
#[cfg(target_os = "macos")]
|
||||
#[error("event tap creation failed")]
|
||||
EventTapCreation,
|
||||
#[error("accessibility permission is required")]
|
||||
AccessibilityPermission,
|
||||
#[error("input monitoring permission is required")]
|
||||
InputMonitoringPermission,
|
||||
#[error("failed to set CG Cursor property")]
|
||||
CGCursorProperty,
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -527,6 +527,8 @@ pub struct MacOSInputCapture {
|
||||
|
||||
impl MacOSInputCapture {
|
||||
pub async fn new() -> Result<Self, MacosCaptureCreationError> {
|
||||
request_macos_capture_permissions()?;
|
||||
|
||||
let state = Arc::new(Mutex::new(InputCaptureState::new()?));
|
||||
let (event_tx, event_rx) = mpsc::channel(32);
|
||||
let (notify_tx, mut notify_rx) = mpsc::channel(32);
|
||||
@@ -580,6 +582,38 @@ impl MacOSInputCapture {
|
||||
}
|
||||
}
|
||||
|
||||
fn request_macos_capture_permissions() -> Result<(), MacosCaptureCreationError> {
|
||||
// Call both request functions unconditionally so macOS surfaces both
|
||||
// TCC prompts on the very first launch. TCC always returns `false` the
|
||||
// first time a permission is requested (the grant only becomes visible
|
||||
// on the next process launch), so returning early on the first failure
|
||||
// would skip the second prompt and force the user through an extra
|
||||
// relaunch just to see it.
|
||||
let accessibility = request_accessibility_permission();
|
||||
let input_monitoring = request_input_monitoring_permission();
|
||||
|
||||
if !accessibility {
|
||||
return Err(MacosCaptureCreationError::AccessibilityPermission);
|
||||
}
|
||||
if !input_monitoring {
|
||||
return Err(MacosCaptureCreationError::InputMonitoringPermission);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn request_accessibility_permission() -> bool {
|
||||
// Silent check. The GUI owns the one-time user-visible prompt at
|
||||
// startup (see lan_mouse_gtk::macos_privacy) so retries triggered by
|
||||
// clicking the "Reenable" button don't pop a fresh Accessibility
|
||||
// alert every time.
|
||||
unsafe { AXIsProcessTrusted() }
|
||||
}
|
||||
|
||||
fn request_input_monitoring_permission() -> bool {
|
||||
// Silent check, same reasoning as above.
|
||||
unsafe { CGPreflightListenEventAccess() }
|
||||
}
|
||||
|
||||
impl Drop for MacOSInputCapture {
|
||||
fn drop(&mut self) {
|
||||
self.run_loop.stop();
|
||||
@@ -651,6 +685,12 @@ extern "C" {
|
||||
event_source: CGEventSource,
|
||||
seconds: CFTimeInterval,
|
||||
);
|
||||
fn CGPreflightListenEventAccess() -> bool;
|
||||
}
|
||||
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
fn AXIsProcessTrusted() -> bool;
|
||||
}
|
||||
|
||||
unsafe fn configure_cf_settings() -> Result<(), MacosCaptureCreationError> {
|
||||
|
||||
Reference in New Issue
Block a user