diff --git a/assets/entitlements.plist b/assets/entitlements.plist new file mode 100644 index 0000000..cf673fc --- /dev/null +++ b/assets/entitlements.plist @@ -0,0 +1,13 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.device.input-monitoring + + com.apple.security.temporary-exception.accessibility + + + diff --git a/scripts/sign-macos.sh b/scripts/sign-macos.sh index 881f50f..3b1c162 100755 --- a/scripts/sign-macos.sh +++ b/scripts/sign-macos.sh @@ -1,5 +1,7 @@ #!/usr/bin/env -S bash -e +ENTITLEMENTS_PATH="assets/entitlements.plist" + APP_BUNDLE_PATH="${APP_BUNDLE_PATH:?APP_BUNDLE_PATH not set}" # 1. Create a temporary keychain and import certificate @@ -29,6 +31,7 @@ security set-key-partition-list -S apple-tool:,apple:,codesign: \ # 2. Sign app bundle codesign --deep --force --options runtime --timestamp \ + --entitlements $ENTITLEMENTS_PATH \ --sign "$MACOS_CERTIFICATE_NAME" \ "$APP_BUNDLE_PATH" diff --git a/src/platform/macos/accessibility.rs b/src/platform/macos/accessibility.rs new file mode 100644 index 0000000..38b4b73 --- /dev/null +++ b/src/platform/macos/accessibility.rs @@ -0,0 +1,90 @@ +/// Taken from: +use std::ffi::c_void; +use std::thread; +use std::time::{Duration, Instant}; + +use log::info; +use objc2::rc::autoreleasepool; +use objc2::runtime::AnyObject; +use objc2::{class, msg_send}; + +#[link(name = "ApplicationServices", kind = "framework")] +unsafe extern "C" { + fn AXIsProcessTrustedWithOptions(options: *const c_void) -> bool; + + static kAXTrustedCheckOptionPrompt: *const c_void; +} + +#[link(name = "CoreFoundation", kind = "framework")] +unsafe extern "C" { + static kCFBooleanTrue: *const c_void; + static kCFBooleanFalse: *const c_void; +} + +const AX_POLL_INTERVAL: Duration = Duration::from_millis(250); +const AX_POLL_TIMEOUT: Duration = Duration::from_secs(30); + +#[inline] +fn ax_is_trusted() -> bool { + unsafe { + autoreleasepool(|_| { + let keys: [*mut AnyObject; 1] = [kAXTrustedCheckOptionPrompt as *mut AnyObject]; + let vals: [*mut AnyObject; 1] = [kCFBooleanFalse as *mut AnyObject]; + let dict: *mut AnyObject = msg_send![ + class!(NSDictionary), + dictionaryWithObjects: vals.as_ptr(), + forKeys: keys.as_ptr(), + count: 1usize + ]; + + AXIsProcessTrustedWithOptions(dict.cast()) + }) + } +} + +#[allow(unsafe_op_in_unsafe_fn)] +unsafe fn prompt_ax_trust_dialog() { + autoreleasepool(|_| { + let keys: [*mut AnyObject; 1] = [kAXTrustedCheckOptionPrompt as *mut AnyObject]; + let vals: [*mut AnyObject; 1] = [kCFBooleanTrue as *mut AnyObject]; + + let dict: *mut AnyObject = msg_send![ + class!(NSDictionary), + dictionaryWithObjects: vals.as_ptr(), + forKeys: keys.as_ptr(), + count: 1usize + ]; + + let _ = AXIsProcessTrustedWithOptions(dict.cast()); + }); +} + +pub fn ensure_accessibility_permission() { + if ax_is_trusted() { + return; + } + + info!("Accessibility permission is not granted; prompting user for permission now."); + + unsafe { prompt_ax_trust_dialog() }; + + let start = Instant::now(); + loop { + if ax_is_trusted() { + info!("Accessibility permission granted"); + return; + } + + if start.elapsed() >= AX_POLL_TIMEOUT { + break; + } + + thread::sleep(AX_POLL_INTERVAL); + } + + println!( + "Rustcast still does not have accessibility permission. Enable it in System Settings > Privacy & Security > Accessibility, then restart Rustcast." + ); + + std::process::exit(1); +} diff --git a/src/platform/macos/launching.rs b/src/platform/macos/launching.rs index 5ef49b4..e6d22a9 100644 --- a/src/platform/macos/launching.rs +++ b/src/platform/macos/launching.rs @@ -3,9 +3,13 @@ use std::sync::{Arc, Mutex}; use block2::RcBlock; use objc2_app_kit::{NSEvent, NSEventMask, NSEventModifierFlags, NSEventType}; -use crate::app::{Message, tile::ExtSender}; +use crate::{ + app::{Message, tile::ExtSender}, + platform::macos::accessibility::ensure_accessibility_permission, +}; pub fn global_handler(sender: ExtSender) { + ensure_accessibility_permission(); local_handler(sender.clone()); let mask = NSEventMask::KeyDown | NSEventMask::FlagsChanged; let sender = Arc::new(Mutex::new(sender.0.clone())); diff --git a/src/platform/macos/login.rs b/src/platform/macos/login.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index 0441f05..4a6a340 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -1,4 +1,5 @@ //! Macos specific logic, such as window settings, etc. +pub mod accessibility; pub mod discovery; pub mod haptics; pub mod launching;