Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

macos: Check if the application has the permission to simulate input #324

Merged
merged 4 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
## Added
- all: `Key::PrintScr`
- all: There finally are some tests in the CI to increase the development speed and prevent regressions
- 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)
- 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)
- macOS: Fallback to ASCII-capable keyboard layout for handling non-standard input sources
- 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.

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

## Added
- all: Serialized tokens can be less verbose because serde aliases were added
- 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)
- 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)
- all: The enums `Button`, `Direction`, `Axis` and `Coordinate` implement `Default`

## Fixed
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ windows = { version = "0.58", features = [
] }

[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.10"
core-graphics = { version = "0.24", features = ["highsierra"] }
objc2 = { version = "0.5", features = ["relax-void-encoding"] }
objc2-app-kit = { version = "0.2", features = ["NSEvent", "NSGraphicsContext"] }
Expand Down
2 changes: 1 addition & 1 deletion Permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ No elevated privileges are needed
[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".

## macOS
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).
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.
11 changes: 10 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ impl Error for InputError {}
pub enum NewConError {
/// Error while creating the connection
EstablishCon(&'static str),
/// The application does not have the permission to simulate input
NoPermission,
/// Error when receiving a reply
Reply,
/// The keymap is full, so there was no space to map any keycodes to keysyms
Expand All @@ -406,6 +408,9 @@ impl Display for NewConError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let string = match self {
NewConError::EstablishCon(e) => format!("no connection could be established: ({e})"),
NewConError::NoPermission => {
format!("the application does not have the permission to simulate input")
}
NewConError::Reply => {
"there was an error with the reply from the display server. this should not happen"
.to_string()
Expand Down Expand Up @@ -441,8 +446,11 @@ pub struct Settings {
/// `EVENT_SOURCE_USER_DATA` field
pub event_source_user_data: Option<i64>,
/// Set this to true if you want all held keys to get released when Enigo
/// gets dropped
/// gets dropped. The default is true.
pub release_keys_when_dropped: bool,
/// Open a prompt to ask the user for the permission to simulate input if
/// they are missing. This only works on macOS. The default is true.
pub open_prompt_to_get_permissions: bool,
}

impl Default for Settings {
Expand All @@ -456,6 +464,7 @@ impl Default for Settings {
windows_dw_extra_info: None,
event_source_user_data: None,
release_keys_when_dropped: true,
open_prompt_to_get_permissions: true,
}
}
}
Expand Down
39 changes: 39 additions & 0 deletions src/macos/macos_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::{
time::{Duration, Instant},
};

use core_foundation::base::TCFType;
use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
use core_graphics::{
display::{CFIndex, CGDisplay, CGPoint},
event::{
Expand Down Expand Up @@ -557,9 +559,16 @@ impl Enigo {
mac_delay: delay,
release_keys_when_dropped,
event_source_user_data,
open_prompt_to_get_permissions,
..
} = settings;

if !has_permission(*open_prompt_to_get_permissions) {
error!("The application does not have the permission to simulate input!");
return Err(NewConError::NoPermission);
}
info!("The application has the permission to simulate input");

let held = (Vec::new(), Vec::new());

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

#[link(name = "ApplicationServices", kind = "framework")]
extern "C" {
pub fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> bool;
static kAXTrustedCheckOptionPrompt: core_foundation::string::CFStringRef;
}

/// Check if the currently running application has the permissions to simulate
/// input
///
/// Returns true if the application has the permission and is allowed to
/// simulate input
pub fn has_permission(open_prompt_to_get_permissions: bool) -> bool {
use core_foundation::string::CFString;

let key = unsafe { kAXTrustedCheckOptionPrompt };
let key = unsafe { CFString::wrap_under_create_rule(key) };

let value = if open_prompt_to_get_permissions {
debug!("Open the system prompt if the permissions are missing.");
core_foundation::boolean::CFBoolean::true_value()
} else {
debug!("Do not open the system prompt if the permissions are missing.");
core_foundation::boolean::CFBoolean::false_value()
};

let options = CFDictionary::from_CFType_pairs(&[(key, value)]);
let options = options.as_concrete_TypeRef();
unsafe { AXIsProcessTrustedWithOptions(options) }
}

impl Drop for Enigo {
// Release the held keys before the connection is dropped
fn drop(&mut self) {
Expand Down