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;