diff --git a/espanso-mac-utils/Cargo.toml b/espanso-mac-utils/Cargo.toml new file mode 100644 index 0000000..6172e9c --- /dev/null +++ b/espanso-mac-utils/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "espanso-mac-utils" +version = "0.1.0" +authors = ["Federico Terzi "] +edition = "2018" +build="build.rs" + +[dependencies] +log = "0.4.14" +lazycell = "1.3.0" +anyhow = "1.0.38" +thiserror = "1.0.23" +lazy_static = "1.4.0" +regex = "1.4.3" + +[build-dependencies] +cc = "1.0.66" \ No newline at end of file diff --git a/espanso-mac-utils/build.rs b/espanso-mac-utils/build.rs new file mode 100644 index 0000000..90c93de --- /dev/null +++ b/espanso-mac-utils/build.rs @@ -0,0 +1,41 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#[cfg(not(target_os = "macos"))] +fn cc_config() { + // Do nothing on Linux and Windows +} + +#[cfg(target_os = "macos")] +fn cc_config() { + println!("cargo:rerun-if-changed=src/native.mm"); + println!("cargo:rerun-if-changed=src/native.h"); + cc::Build::new() + .cpp(true) + .include("src/native.h") + .file("src/native.mm") + .compile("espansomacutils"); + println!("cargo:rustc-link-lib=dylib=c++"); + println!("cargo:rustc-link-lib=static=espansomacutils"); + println!("cargo:rustc-link-lib=framework=Cocoa"); +} + +fn main() { + cc_config(); +} diff --git a/espanso-mac-utils/src/ffi.rs b/espanso-mac-utils/src/ffi.rs new file mode 100644 index 0000000..e590558 --- /dev/null +++ b/espanso-mac-utils/src/ffi.rs @@ -0,0 +1,26 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::os::raw::c_char; + +#[link(name = "espansomacutils", kind = "static")] +extern "C" { + pub fn mac_utils_get_secure_input_process(pid: *mut i64) -> i32; + pub fn mac_utils_get_path_from_pid(pid: i64, buffer: *mut c_char, size: i32) -> i32; +} diff --git a/espanso-mac-utils/src/lib.rs b/espanso-mac-utils/src/lib.rs new file mode 100644 index 0000000..cc879d7 --- /dev/null +++ b/espanso-mac-utils/src/lib.rs @@ -0,0 +1,112 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::{ffi::CStr, os::raw::c_char}; + +#[macro_use] +extern crate lazy_static; + +mod ffi; + +/// Check whether an application is currently holding the Secure Input. +/// Return None if no application has claimed SecureInput, its PID otherwise. +pub fn get_secure_input_pid() -> Option { + unsafe { + let mut pid: i64 = -1; + let res = ffi::mac_utils_get_secure_input_process(&mut pid as *mut i64); + + if res > 0 { + Some(pid) + } else { + None + } + } +} + +/// Check whether an application is currently holding the Secure Input. +/// Return None if no application has claimed SecureInput, Some((AppName, AppPath)) otherwise. +pub fn get_secure_input_application() -> Option<(String, String)> { + unsafe { + let pid = get_secure_input_pid(); + + if let Some(pid) = pid { + // Size of the buffer is ruled by the PROC_PIDPATHINFO_MAXSIZE constant. + // the underlying proc_pidpath REQUIRES a buffer of that dimension, otherwise it fail silently. + let mut buffer: [c_char; 4096] = [0; 4096]; + let res = ffi::mac_utils_get_path_from_pid(pid, buffer.as_mut_ptr(), buffer.len() as i32); + + if res > 0 { + let c_string = CStr::from_ptr(buffer.as_ptr()); + let string = c_string.to_str(); + if let Ok(path) = string { + if !path.trim().is_empty() { + let process = path.trim().to_string(); + let app_name = if let Some(name) = get_app_name_from_path(&process) { + name + } else { + process.to_owned() + }; + + return Some((app_name, process)); + } + } + } + } + + None + } +} + +fn get_app_name_from_path(path: &str) -> Option { + use regex::Regex; + + lazy_static! { + static ref APP_REGEX: Regex = Regex::new("/([^/]+).(app|bundle)/").unwrap(); + }; + + let caps = APP_REGEX.captures(&path); + if let Some(caps) = caps { + Some(caps.get(1).map_or("", |m| m.as_str()).to_owned()) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_app_name_from_path() { + let app_name = get_app_name_from_path("/Applications/iTerm.app/Contents/MacOS/iTerm2"); + assert_eq!(app_name.unwrap(), "iTerm") + } + + #[test] + fn test_get_app_name_from_path_no_app_name() { + let app_name = get_app_name_from_path("/another/directory"); + assert!(app_name.is_none()) + } + + #[test] + fn test_get_app_name_from_path_security_bundle() { + let app_name = get_app_name_from_path("/System/Library/Frameworks/Security.framework/Versions/A/MachServices/SecurityAgent.bundle/Contents/MacOS/SecurityAgent"); + assert_eq!(app_name.unwrap(), "SecurityAgent") + } +} diff --git a/espanso-mac-utils/src/native.h b/espanso-mac-utils/src/native.h new file mode 100644 index 0000000..8d045d0 --- /dev/null +++ b/espanso-mac-utils/src/native.h @@ -0,0 +1,31 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#ifndef ESPANSO_MAC_UTILS_H +#define ESPANSO_MAC_UTILS_H + +#include + +// If a process is currently holding SecureInput, then return 1 and set the pid pointer to the corresponding PID. +extern "C" int32_t mac_utils_get_secure_input_process(int64_t *pid); + +// Find the executable path corresponding to the given PID, return 0 if no process was found. +extern "C" int32_t mac_utils_get_path_from_pid(int64_t pid, char *buff, int buff_size); + +#endif //ESPANSO_MAC_UTILS_H \ No newline at end of file diff --git a/espanso-mac-utils/src/native.mm b/espanso-mac-utils/src/native.mm new file mode 100644 index 0000000..47cf28a --- /dev/null +++ b/espanso-mac-utils/src/native.mm @@ -0,0 +1,68 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#include "native.h" +#include +#import +#import + +// Taken (with a few modifications) from the MagicKeys project: https://github.com/zsszatmari/MagicKeys +int32_t mac_utils_get_secure_input_process(int64_t *pid) { + NSArray *consoleUsersArray; + io_service_t rootService; + int32_t result = 0; + + if ((rootService = IORegistryGetRootEntry(kIOMasterPortDefault)) != 0) + { + if ((consoleUsersArray = (NSArray *)IORegistryEntryCreateCFProperty((io_registry_entry_t)rootService, CFSTR("IOConsoleUsers"), kCFAllocatorDefault, 0)) != nil) + { + if ([consoleUsersArray isKindOfClass:[NSArray class]]) // Be careful - ensure this really is an array + { + for (NSDictionary *consoleUserDict in consoleUsersArray) { + NSNumber *secureInputPID; + + if ((secureInputPID = [consoleUserDict objectForKey:@"kCGSSessionSecureInputPID"]) != nil) + { + if ([secureInputPID isKindOfClass:[NSNumber class]]) + { + *pid = ((UInt64) [secureInputPID intValue]); + result = 1; + break; + } + } + } + } + + CFRelease((CFTypeRef)consoleUsersArray); + } + + IOObjectRelease((io_object_t) rootService); + } + + return result; +} + +int32_t mac_utils_get_path_from_pid(int64_t pid, char *buff, int buff_size) { + int res = proc_pidpath((pid_t) pid, buff, buff_size); + if ( res <= 0 ) { + return 0; + } else { + return 1; + } +} \ No newline at end of file