Skip to content

Commit b1bedad

Browse files
authored
macos: Check if the application has the permission to simulate input (#324)
Check if the application has the permission to simulate input and ask for it if it does not. The default of asking for the permissions can be overwritten via the Settings struct
1 parent 50b605e commit b1bedad

File tree

5 files changed

+54
-4
lines changed

5 files changed

+54
-4
lines changed

CHANGES.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
## Added
66
- all: `Key::PrintScr`
77
- all: There finally are some tests in the CI to increase the development speed and prevent regressions
8-
- win, macOS: Allow marking events that were created by enigo. Have a look at the additional field of the `Settings` struct and the new method `get_marker_value` of the `enigo` struct (only available on Windows and macOS)
8+
- win, macOS: Allow marking events that were created by enigo. Have a look at the additional field of the `Settings` struct and the new method `get_marker_value` of the `Enigo` struct (only available on Windows and macOS)
99
- macOS: Fallback to ASCII-capable keyboard layout for handling non-standard input sources
10+
- macOS: Check if the application has the neccessary permissions. If they are missing, `enigo` will ask the user to grant them. You can change this default behavior with the `Settings` when constructing an `Enigo` struct.
1011

1112
## Fixed
1213
win: Respect the language of the current window to determine the which scancodes to send
@@ -18,7 +19,7 @@ win: Send the virtual key and its scan code in the events to work with programs
1819

1920
## Added
2021
- all: Serialized tokens can be less verbose because serde aliases were added
21-
- win, macOS: Allow marking events that were created by enigo. Have a look at the additional field of the `Settings` struct and the new method `get_marker_value` of the `enigo` struct (only available on Windows and macOS)
22+
- win, macOS: Allow marking events that were created by enigo. Have a look at the additional field of the `Settings` struct and the new method `get_marker_value` of the `Enigo` struct (only available on Windows and macOS)
2223
- all: The enums `Button`, `Direction`, `Axis` and `Coordinate` implement `Default`
2324

2425
## Fixed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ windows = { version = "0.58", features = [
5353
] }
5454

5555
[target.'cfg(target_os = "macos")'.dependencies]
56+
core-foundation = "0.10"
5657
core-graphics = { version = "0.24", features = ["highsierra"] }
5758
objc2 = { version = "0.5", features = ["relax-void-encoding"] }
5859
objc2-app-kit = { version = "0.2", features = ["NSEvent", "NSGraphicsContext"] }

Permissions.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ No elevated privileges are needed
77
[UIPI](https://en.wikipedia.org/wiki/User_Interface_Privilege_Isolation) is a security measure that "prevents processes with a lower "integrity level" (IL) from sending messages to higher IL processes". If your program does not have elevated privileges, you won't be able to use `enigo` is some situations. It won't be possible to use it with the task manager for example. Run your program as an admin, if you need to use `enigo` with processes with a higher "integrity level".
88

99
## macOS
10-
You need to grant the application to access your Mac. You can find official instructions [here](https://web.archive.org/web/20231005204542/https://support.apple.com/guide/mac-help/allow-accessibility-apps-to-access-your-mac-mh43185/mac).
10+
The user needs to grant the application the permission to access their Mac. Official instructions on how to do that can be found [here](https://web.archive.org/web/20231005204542/https://support.apple.com/guide/mac-help/allow-accessibility-apps-to-access-your-mac-mh43185/mac). Enigo will check if the application has the needed permissions and ask the user to grant them if the permissions are missing. You can change this behavior with the settings when creating the Enigo struct.

src/lib.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ impl Error for InputError {}
396396
pub enum NewConError {
397397
/// Error while creating the connection
398398
EstablishCon(&'static str),
399+
/// The application does not have the permission to simulate input
400+
NoPermission,
399401
/// Error when receiving a reply
400402
Reply,
401403
/// The keymap is full, so there was no space to map any keycodes to keysyms
@@ -406,6 +408,9 @@ impl Display for NewConError {
406408
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
407409
let string = match self {
408410
NewConError::EstablishCon(e) => format!("no connection could be established: ({e})"),
411+
NewConError::NoPermission => {
412+
format!("the application does not have the permission to simulate input")
413+
}
409414
NewConError::Reply => {
410415
"there was an error with the reply from the display server. this should not happen"
411416
.to_string()
@@ -441,8 +446,11 @@ pub struct Settings {
441446
/// `EVENT_SOURCE_USER_DATA` field
442447
pub event_source_user_data: Option<i64>,
443448
/// Set this to true if you want all held keys to get released when Enigo
444-
/// gets dropped
449+
/// gets dropped. The default is true.
445450
pub release_keys_when_dropped: bool,
451+
/// Open a prompt to ask the user for the permission to simulate input if
452+
/// they are missing. This only works on macOS. The default is true.
453+
pub open_prompt_to_get_permissions: bool,
446454
}
447455

448456
impl Default for Settings {
@@ -456,6 +464,7 @@ impl Default for Settings {
456464
windows_dw_extra_info: None,
457465
event_source_user_data: None,
458466
release_keys_when_dropped: true,
467+
open_prompt_to_get_permissions: true,
459468
}
460469
}
461470
}

src/macos/macos_impl.rs

+39
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use std::{
44
time::{Duration, Instant},
55
};
66

7+
use core_foundation::base::TCFType;
8+
use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
79
use core_graphics::{
810
display::{CFIndex, CGDisplay, CGPoint},
911
event::{
@@ -557,9 +559,16 @@ impl Enigo {
557559
mac_delay: delay,
558560
release_keys_when_dropped,
559561
event_source_user_data,
562+
open_prompt_to_get_permissions,
560563
..
561564
} = settings;
562565

566+
if !has_permission(*open_prompt_to_get_permissions) {
567+
error!("The application does not have the permission to simulate input!");
568+
return Err(NewConError::NoPermission);
569+
}
570+
info!("The application has the permission to simulate input");
571+
563572
let held = (Vec::new(), Vec::new());
564573

565574
let double_click_delay = Duration::from_secs(1);
@@ -886,6 +895,36 @@ fn create_string_for_key(keycode: u16, modifier: u32) -> CFStringRef {
886895
unsafe { CFStringCreateWithCharacters(kCFAllocatorDefault, &chars, 1) }
887896
}
888897

898+
#[link(name = "ApplicationServices", kind = "framework")]
899+
extern "C" {
900+
pub fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> bool;
901+
static kAXTrustedCheckOptionPrompt: core_foundation::string::CFStringRef;
902+
}
903+
904+
/// Check if the currently running application has the permissions to simulate
905+
/// input
906+
///
907+
/// Returns true if the application has the permission and is allowed to
908+
/// simulate input
909+
pub fn has_permission(open_prompt_to_get_permissions: bool) -> bool {
910+
use core_foundation::string::CFString;
911+
912+
let key = unsafe { kAXTrustedCheckOptionPrompt };
913+
let key = unsafe { CFString::wrap_under_create_rule(key) };
914+
915+
let value = if open_prompt_to_get_permissions {
916+
debug!("Open the system prompt if the permissions are missing.");
917+
core_foundation::boolean::CFBoolean::true_value()
918+
} else {
919+
debug!("Do not open the system prompt if the permissions are missing.");
920+
core_foundation::boolean::CFBoolean::false_value()
921+
};
922+
923+
let options = CFDictionary::from_CFType_pairs(&[(key, value)]);
924+
let options = options.as_concrete_TypeRef();
925+
unsafe { AXIsProcessTrustedWithOptions(options) }
926+
}
927+
889928
impl Drop for Enigo {
890929
// Release the held keys before the connection is dropped
891930
fn drop(&mut self) {

0 commit comments

Comments
 (0)