Add secure input notification on macOS
This commit is contained in:
		
							parent
							
								
									2c8c28087d
								
							
						
					
					
						commit
						dd8e5c8f9c
					
				
							
								
								
									
										1
									
								
								build.rs
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								build.rs
									
									
									
									
									
								
							|  | @ -44,6 +44,7 @@ fn print_config() { | ||||||
|     println!("cargo:rustc-link-lib=dylib=c++"); |     println!("cargo:rustc-link-lib=dylib=c++"); | ||||||
|     println!("cargo:rustc-link-lib=static=macbridge"); |     println!("cargo:rustc-link-lib=static=macbridge"); | ||||||
|     println!("cargo:rustc-link-lib=framework=Cocoa"); |     println!("cargo:rustc-link-lib=framework=Cocoa"); | ||||||
|  |     println!("cargo:rustc-link-lib=framework=IOKit"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn main() | fn main() | ||||||
|  |  | ||||||
|  | @ -158,6 +158,15 @@ int32_t set_clipboard(char * text); | ||||||
|  */ |  */ | ||||||
| int32_t set_clipboard_image(char * path); | int32_t set_clipboard_image(char * path); | ||||||
| 
 | 
 | ||||||
|  | /*
 | ||||||
|  |  * If a process is currently holding SecureInput, then return 1 and set the pid pointer to the corresponding PID. | ||||||
|  |  */ | ||||||
|  | int32_t get_secure_input_process(int64_t *pid); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Find the executable path corresponding to the given PID, return 0 if no process was found. | ||||||
|  |  */ | ||||||
|  | int32_t get_path_from_pid(int64_t pid, char *buff, int buff_size); | ||||||
| 
 | 
 | ||||||
| }; | }; | ||||||
| #endif //ESPANSO_BRIDGE_H
 | #endif //ESPANSO_BRIDGE_H
 | ||||||
|  |  | ||||||
|  | @ -20,9 +20,11 @@ | ||||||
| #include "bridge.h" | #include "bridge.h" | ||||||
| 
 | 
 | ||||||
| #import <Foundation/Foundation.h> | #import <Foundation/Foundation.h> | ||||||
|  | #include <IOKit/IOKitLib.h> | ||||||
| #include "AppDelegate.h" | #include "AppDelegate.h" | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
| #include <string.h> | #include <string.h> | ||||||
|  | #include <libproc.h> | ||||||
| extern "C" { | extern "C" { | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | @ -334,3 +336,47 @@ void open_settings_panel() { | ||||||
|     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]]; |     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Taken (with a few modifications) from the MagicKeys project: https://github.com/zsszatmari/MagicKeys | ||||||
|  | int32_t 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 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; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -39,6 +39,8 @@ extern { | ||||||
|     pub fn open_settings_panel(); |     pub fn open_settings_panel(); | ||||||
|     pub fn get_active_app_bundle(buffer: *mut c_char, size: i32) -> i32; |     pub fn get_active_app_bundle(buffer: *mut c_char, size: i32) -> i32; | ||||||
|     pub fn get_active_app_identifier(buffer: *mut c_char, size: i32) -> i32; |     pub fn get_active_app_identifier(buffer: *mut c_char, size: i32) -> i32; | ||||||
|  |     pub fn get_secure_input_process(pid:*mut i64) -> i32; | ||||||
|  |     pub fn get_path_from_pid(pid:i64, buffer: *mut c_char, size: i32) -> i32; | ||||||
| 
 | 
 | ||||||
|     // Clipboard
 |     // Clipboard
 | ||||||
|     pub fn get_clipboard(buffer: *mut c_char, size: i32) -> i32; |     pub fn get_clipboard(buffer: *mut c_char, size: i32) -> i32; | ||||||
|  |  | ||||||
|  | @ -64,6 +64,9 @@ fn default_enable_active() -> bool { true } | ||||||
| fn default_backspace_limit() -> i32 { 3 } | fn default_backspace_limit() -> i32 { 3 } | ||||||
| fn default_restore_clipboard_delay() -> i32 { 300 } | fn default_restore_clipboard_delay() -> i32 { 300 } | ||||||
| fn default_exclude_default_entries() -> bool {false} | fn default_exclude_default_entries() -> bool {false} | ||||||
|  | fn default_secure_input_watcher_enabled() -> bool {true} | ||||||
|  | fn default_secure_input_notification() -> bool {true} | ||||||
|  | fn default_secure_input_watcher_interval() -> i32 {5000} | ||||||
| fn default_matches() -> Vec<Match> { Vec::new() } | fn default_matches() -> Vec<Match> { Vec::new() } | ||||||
| fn default_global_vars() -> Vec<MatchVariable> { Vec::new() } | fn default_global_vars() -> Vec<MatchVariable> { Vec::new() } | ||||||
| 
 | 
 | ||||||
|  | @ -138,6 +141,15 @@ pub struct Configs { | ||||||
|     #[serde(default = "default_restore_clipboard_delay")] |     #[serde(default = "default_restore_clipboard_delay")] | ||||||
|     pub restore_clipboard_delay: i32, |     pub restore_clipboard_delay: i32, | ||||||
| 
 | 
 | ||||||
|  |     #[serde(default = "default_secure_input_watcher_enabled")] | ||||||
|  |     pub secure_input_watcher_enabled: bool, | ||||||
|  | 
 | ||||||
|  |     #[serde(default = "default_secure_input_watcher_interval")] | ||||||
|  |     pub secure_input_watcher_interval: i32, | ||||||
|  | 
 | ||||||
|  |     #[serde(default = "default_secure_input_notification")] | ||||||
|  |     pub secure_input_notification: bool, | ||||||
|  | 
 | ||||||
|     #[serde(default)] |     #[serde(default)] | ||||||
|     pub backend: BackendType, |     pub backend: BackendType, | ||||||
| 
 | 
 | ||||||
|  | @ -190,6 +202,9 @@ impl Configs { | ||||||
|         validate_field!(result, self.passive_arg_escape, default_passive_arg_escape()); |         validate_field!(result, self.passive_arg_escape, default_passive_arg_escape()); | ||||||
|         validate_field!(result, self.passive_key, default_passive_key()); |         validate_field!(result, self.passive_key, default_passive_key()); | ||||||
|         validate_field!(result, self.restore_clipboard_delay, default_restore_clipboard_delay()); |         validate_field!(result, self.restore_clipboard_delay, default_restore_clipboard_delay()); | ||||||
|  |         validate_field!(result, self.secure_input_watcher_enabled, default_secure_input_watcher_enabled()); | ||||||
|  |         validate_field!(result, self.secure_input_watcher_interval, default_secure_input_watcher_interval()); | ||||||
|  |         validate_field!(result, self.secure_input_notification, default_secure_input_notification()); | ||||||
| 
 | 
 | ||||||
|         result |         result | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ use std::{thread, time}; | ||||||
| use std::sync::atomic::AtomicBool; | use std::sync::atomic::AtomicBool; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| use std::sync::atomic::Ordering::Acquire; | use std::sync::atomic::Ordering::Acquire; | ||||||
|  | use crate::config::Configs; | ||||||
| 
 | 
 | ||||||
| #[repr(C)] | #[repr(C)] | ||||||
| pub struct LinuxContext { | pub struct LinuxContext { | ||||||
|  | @ -37,7 +38,7 @@ pub struct LinuxContext { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl LinuxContext { | impl LinuxContext { | ||||||
|     pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<LinuxContext> { |     pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<LinuxContext> { | ||||||
|         // Check if the X11 context is available
 |         // Check if the X11 context is available
 | ||||||
|         let x11_available = unsafe { |         let x11_available = unsafe { | ||||||
|             check_x11() |             check_x11() | ||||||
|  |  | ||||||
|  | @ -20,25 +20,30 @@ | ||||||
| use std::sync::mpsc::Sender; | use std::sync::mpsc::Sender; | ||||||
| use std::os::raw::{c_void, c_char}; | use std::os::raw::{c_void, c_char}; | ||||||
| use crate::bridge::macos::*; | use crate::bridge::macos::*; | ||||||
| use crate::event::{Event, KeyEvent, KeyModifier, ActionType}; | use crate::event::{Event, KeyEvent, KeyModifier, ActionType, SystemEvent}; | ||||||
| use crate::event::KeyModifier::*; | use crate::event::KeyModifier::*; | ||||||
| use std::ffi::{CString, CStr}; | use std::ffi::{CString, CStr}; | ||||||
| use std::fs; | use std::{fs, thread}; | ||||||
| use log::{info, error, debug}; | use log::{info, error, debug}; | ||||||
| use std::process::exit; | use std::process::exit; | ||||||
| use std::sync::atomic::AtomicBool; | use std::sync::atomic::AtomicBool; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| use std::sync::atomic::Ordering::Acquire; | use std::sync::atomic::Ordering::Acquire; | ||||||
|  | use crate::config::Configs; | ||||||
|  | use std::cell::RefCell; | ||||||
|  | use crate::system::macos::MacSystemManager; | ||||||
| 
 | 
 | ||||||
| const STATUS_ICON_BINARY : &[u8] = include_bytes!("../res/mac/icon.png"); | const STATUS_ICON_BINARY : &[u8] = include_bytes!("../res/mac/icon.png"); | ||||||
| 
 | 
 | ||||||
| pub struct MacContext { | pub struct MacContext { | ||||||
|     pub send_channel: Sender<Event>, |     pub send_channel: Sender<Event>, | ||||||
|     is_injecting: Arc<AtomicBool>, |     is_injecting: Arc<AtomicBool>, | ||||||
|  |     secure_input_watcher_enabled: bool, | ||||||
|  |     secure_input_watcher_interval: i32, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl MacContext { | impl MacContext { | ||||||
|     pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<MacContext> { |     pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<MacContext> { | ||||||
|         // Check accessibility
 |         // Check accessibility
 | ||||||
|         unsafe { |         unsafe { | ||||||
|             let res = prompt_accessibility(); |             let res = prompt_accessibility(); | ||||||
|  | @ -53,7 +58,9 @@ impl MacContext { | ||||||
| 
 | 
 | ||||||
|         let context = Box::new(MacContext { |         let context = Box::new(MacContext { | ||||||
|             send_channel, |             send_channel, | ||||||
|             is_injecting |             is_injecting, | ||||||
|  |             secure_input_watcher_enabled: config.secure_input_watcher_enabled, | ||||||
|  |             secure_input_watcher_interval: config.secure_input_watcher_interval, | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Initialize the status icon path
 |         // Initialize the status icon path
 | ||||||
|  | @ -81,10 +88,59 @@ impl MacContext { | ||||||
| 
 | 
 | ||||||
|         context |         context | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     fn start_secure_input_watcher(&self) { | ||||||
|  |         let send_channel = self.send_channel.clone(); | ||||||
|  |         let secure_input_watcher_interval = self.secure_input_watcher_interval as u64; | ||||||
|  | 
 | ||||||
|  |         let secure_input_watcher = thread::Builder::new().name("secure_input_watcher".to_string()).spawn(move || { | ||||||
|  |             let mut last_secure_input_pid: Option<i64> = None; | ||||||
|  |             loop { | ||||||
|  |                 let pid = MacSystemManager::get_secure_input_pid(); | ||||||
|  | 
 | ||||||
|  |                 if let Some(pid) = pid { // Some application is currently on SecureInput
 | ||||||
|  |                     let should_notify = if let Some(old_pid) = last_secure_input_pid { // We already detected a SecureInput app
 | ||||||
|  |                         if old_pid != pid { // The old app is different from the current one, we should take action
 | ||||||
|  |                             true | ||||||
|  |                         }else{ // We already notified this application before
 | ||||||
|  |                             false | ||||||
|  |                         } | ||||||
|  |                     }else{ // First time we see this SecureInput app, we should take action
 | ||||||
|  |                         true | ||||||
|  |                     }; | ||||||
|  | 
 | ||||||
|  |                     if should_notify { | ||||||
|  |                         let secure_input_app = crate::system::macos::MacSystemManager::get_secure_input_application(); | ||||||
|  | 
 | ||||||
|  |                         if let Some((app_name, path)) = secure_input_app { | ||||||
|  |                             let event = Event::System(SystemEvent::SecureInputEnabled(app_name, path)); | ||||||
|  |                             send_channel.send(event); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     last_secure_input_pid = Some(pid); | ||||||
|  |                 }else{  // No app is currently keeping SecureInput
 | ||||||
|  |                     if let Some(old_pid) = last_secure_input_pid {  // If there was an app with SecureInput, notify that is now free
 | ||||||
|  |                         let event = Event::System(SystemEvent::SecureInputDisabled); | ||||||
|  |                         send_channel.send(event); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     last_secure_input_pid = None | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 thread::sleep(std::time::Duration::from_millis(secure_input_watcher_interval)); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl super::Context for MacContext { | impl super::Context for MacContext { | ||||||
|     fn eventloop(&self) { |     fn eventloop(&self) { | ||||||
|  |         // Start the SecureInput watcher thread
 | ||||||
|  |         if self.secure_input_watcher_enabled { | ||||||
|  |             self.start_secure_input_watcher(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         unsafe { |         unsafe { | ||||||
|             eventloop(); |             eventloop(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -32,6 +32,7 @@ use std::path::PathBuf; | ||||||
| use std::fs::create_dir_all; | use std::fs::create_dir_all; | ||||||
| use std::sync::{Once, Arc}; | use std::sync::{Once, Arc}; | ||||||
| use std::sync::atomic::AtomicBool; | use std::sync::atomic::AtomicBool; | ||||||
|  | use crate::config::Configs; | ||||||
| 
 | 
 | ||||||
| pub trait Context { | pub trait Context { | ||||||
|     fn eventloop(&self); |     fn eventloop(&self); | ||||||
|  | @ -39,20 +40,20 @@ pub trait Context { | ||||||
| 
 | 
 | ||||||
| // MAC IMPLEMENTATION
 | // MAC IMPLEMENTATION
 | ||||||
| #[cfg(target_os = "macos")] | #[cfg(target_os = "macos")] | ||||||
| pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> { | pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> { | ||||||
|     macos::MacContext::new(send_channel, is_injecting) |     macos::MacContext::new(config, send_channel, is_injecting) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LINUX IMPLEMENTATION
 | // LINUX IMPLEMENTATION
 | ||||||
| #[cfg(target_os = "linux")] | #[cfg(target_os = "linux")] | ||||||
| pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> { | pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> { | ||||||
|     linux::LinuxContext::new(send_channel, is_injecting) |     linux::LinuxContext::new(config, send_channel, is_injecting) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // WINDOWS IMPLEMENTATION
 | // WINDOWS IMPLEMENTATION
 | ||||||
| #[cfg(target_os = "windows")] | #[cfg(target_os = "windows")] | ||||||
| pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> { | pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> { | ||||||
|     windows::WindowsContext::new(send_channel, is_injecting) |     windows::WindowsContext::new(config, send_channel, is_injecting) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // espanso directories
 | // espanso directories
 | ||||||
|  |  | ||||||
|  | @ -28,6 +28,7 @@ use log::{info, error, debug}; | ||||||
| use std::sync::atomic::AtomicBool; | use std::sync::atomic::AtomicBool; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| use std::sync::atomic::Ordering::Acquire; | use std::sync::atomic::Ordering::Acquire; | ||||||
|  | use crate::config::Configs; | ||||||
| 
 | 
 | ||||||
| const BMP_BINARY : &[u8] = include_bytes!("../res/win/espanso.bmp"); | const BMP_BINARY : &[u8] = include_bytes!("../res/win/espanso.bmp"); | ||||||
| const ICO_BINARY : &[u8] = include_bytes!("../res/win/espanso.ico"); | const ICO_BINARY : &[u8] = include_bytes!("../res/win/espanso.ico"); | ||||||
|  | @ -38,7 +39,7 @@ pub struct WindowsContext { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl WindowsContext { | impl WindowsContext { | ||||||
|     pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<WindowsContext> { |     pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<WindowsContext> { | ||||||
|         // Initialize image resources
 |         // Initialize image resources
 | ||||||
| 
 | 
 | ||||||
|         let espanso_dir = super::get_data_dir(); |         let espanso_dir = super::get_data_dir(); | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ use crate::config::BackendType; | ||||||
| use crate::clipboard::ClipboardManager; | use crate::clipboard::ClipboardManager; | ||||||
| use log::{info, warn, debug, error}; | use log::{info, warn, debug, error}; | ||||||
| use crate::ui::{UIManager, MenuItem, MenuItemType}; | use crate::ui::{UIManager, MenuItem, MenuItemType}; | ||||||
| use crate::event::{ActionEventReceiver, ActionType}; | use crate::event::{ActionEventReceiver, ActionType, SystemEventReceiver, SystemEvent}; | ||||||
| use crate::extension::Extension; | use crate::extension::Extension; | ||||||
| use crate::render::{Renderer, RenderResult}; | use crate::render::{Renderer, RenderResult}; | ||||||
| use std::cell::RefCell; | use std::cell::RefCell; | ||||||
|  | @ -334,3 +334,23 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl <'a, S: KeyboardManager, C: ClipboardManager, | ||||||
|  |     M: ConfigManager<'a>, U: UIManager, R: Renderer> SystemEventReceiver for Engine<'a, S, C, M, U, R>{ | ||||||
|  | 
 | ||||||
|  |     fn on_system_event(&self, e: SystemEvent) { | ||||||
|  |         match e { | ||||||
|  |             // MacOS specific
 | ||||||
|  |             SystemEvent::SecureInputEnabled(app_name, path) => { | ||||||
|  |                 info!("SecureInput has been acquired by {}, preventing espanso from working correctly. Full path: {}", app_name, path); | ||||||
|  | 
 | ||||||
|  |                 if self.config_manager.default_config().secure_input_notification { | ||||||
|  |                     self.ui_manager.notify_delay(&format!("{} has activated SecureInput. Espanso won't work until you disable it.", app_name), 5000); | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             SystemEvent::SecureInputDisabled => { | ||||||
|  |                 info!("SecureInput has been disabled."); | ||||||
|  |             }, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -17,7 +17,7 @@ | ||||||
|  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 |  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| use crate::event::{KeyEventReceiver, ActionEventReceiver, Event}; | use crate::event::{KeyEventReceiver, ActionEventReceiver, Event, SystemEventReceiver}; | ||||||
| use std::sync::mpsc::Receiver; | use std::sync::mpsc::Receiver; | ||||||
| 
 | 
 | ||||||
| pub trait EventManager { | pub trait EventManager { | ||||||
|  | @ -28,15 +28,18 @@ pub struct DefaultEventManager<'a> { | ||||||
|     receive_channel: Receiver<Event>, |     receive_channel: Receiver<Event>, | ||||||
|     key_receivers: Vec<&'a dyn KeyEventReceiver>, |     key_receivers: Vec<&'a dyn KeyEventReceiver>, | ||||||
|     action_receivers: Vec<&'a dyn ActionEventReceiver>, |     action_receivers: Vec<&'a dyn ActionEventReceiver>, | ||||||
|  |     system_receivers: Vec<&'a dyn SystemEventReceiver>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<'a> DefaultEventManager<'a> { | impl<'a> DefaultEventManager<'a> { | ||||||
|     pub fn new(receive_channel: Receiver<Event>, key_receivers: Vec<&'a dyn KeyEventReceiver>, |     pub fn new(receive_channel: Receiver<Event>, key_receivers: Vec<&'a dyn KeyEventReceiver>, | ||||||
|                action_receivers: Vec<&'a dyn ActionEventReceiver>) -> DefaultEventManager<'a> { |                action_receivers: Vec<&'a dyn ActionEventReceiver>, | ||||||
|  |                system_receivers: Vec<&'a dyn SystemEventReceiver>) -> DefaultEventManager<'a> { | ||||||
|         DefaultEventManager { |         DefaultEventManager { | ||||||
|             receive_channel, |             receive_channel, | ||||||
|             key_receivers, |             key_receivers, | ||||||
|             action_receivers, |             action_receivers, | ||||||
|  |             system_receivers | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -53,6 +56,9 @@ impl <'a> EventManager for DefaultEventManager<'a> { | ||||||
|                         Event::Action(action_event) => { |                         Event::Action(action_event) => { | ||||||
|                             self.action_receivers.iter().for_each(|&receiver| receiver.on_action_event(action_event.clone())); |                             self.action_receivers.iter().for_each(|&receiver| receiver.on_action_event(action_event.clone())); | ||||||
|                         } |                         } | ||||||
|  |                         Event::System(system_event) => { | ||||||
|  |                             self.system_receivers.iter().for_each(move |&receiver| receiver.on_system_event(system_event.clone())); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 Err(e) => panic!("Broken event channel {}", e), |                 Err(e) => panic!("Broken event channel {}", e), | ||||||
|  |  | ||||||
|  | @ -24,7 +24,8 @@ use serde::{Serialize, Deserialize}; | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
| pub enum Event { | pub enum Event { | ||||||
|     Action(ActionType), |     Action(ActionType), | ||||||
|     Key(KeyEvent) |     Key(KeyEvent), | ||||||
|  |     System(SystemEvent), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
|  | @ -132,6 +133,13 @@ impl KeyModifier { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
|  | pub enum SystemEvent { | ||||||
|  |     // MacOS specific
 | ||||||
|  |     SecureInputEnabled(String, String),  // AppName, App Path
 | ||||||
|  |     SecureInputDisabled, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Receivers
 | // Receivers
 | ||||||
| 
 | 
 | ||||||
| pub trait KeyEventReceiver { | pub trait KeyEventReceiver { | ||||||
|  | @ -142,6 +150,10 @@ pub trait ActionEventReceiver { | ||||||
|     fn on_action_event(&self, e: ActionType); |     fn on_action_event(&self, e: ActionType); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | pub trait SystemEventReceiver { | ||||||
|  |     fn on_system_event(&self, e: SystemEvent); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // TESTS
 | // TESTS
 | ||||||
| 
 | 
 | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
|  |  | ||||||
|  | @ -338,7 +338,7 @@ fn daemon_main(config_set: ConfigSet) { | ||||||
|     // we could reinterpret the characters we are injecting
 |     // we could reinterpret the characters we are injecting
 | ||||||
|     let is_injecting = Arc::new(std::sync::atomic::AtomicBool::new(false)); |     let is_injecting = Arc::new(std::sync::atomic::AtomicBool::new(false)); | ||||||
| 
 | 
 | ||||||
|     let context = context::new(send_channel.clone(), is_injecting.clone()); |     let context = context::new(config_set.default.clone(), send_channel.clone(), is_injecting.clone()); | ||||||
| 
 | 
 | ||||||
|     let config_set_copy = config_set.clone(); |     let config_set_copy = config_set.clone(); | ||||||
|     thread::Builder::new().name("daemon_background".to_string()).spawn(move || { |     thread::Builder::new().name("daemon_background".to_string()).spawn(move || { | ||||||
|  | @ -382,6 +382,7 @@ fn daemon_background(receive_channel: Receiver<Event>, config_set: ConfigSet, is | ||||||
|         receive_channel, |         receive_channel, | ||||||
|         vec!(&matcher), |         vec!(&matcher), | ||||||
|         vec!(&engine, &matcher), |         vec!(&engine, &matcher), | ||||||
|  |         vec!(&engine), | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     info!("espanso is running!"); |     info!("espanso is running!"); | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ | ||||||
| use std::os::raw::c_char; | use std::os::raw::c_char; | ||||||
| 
 | 
 | ||||||
| use std::ffi::CStr; | use std::ffi::CStr; | ||||||
| use crate::bridge::macos::{get_active_app_bundle, get_active_app_identifier}; | use crate::bridge::macos::{get_active_app_bundle, get_active_app_identifier, get_secure_input_process, get_path_from_pid}; | ||||||
| 
 | 
 | ||||||
| pub struct MacSystemManager { | pub struct MacSystemManager { | ||||||
| 
 | 
 | ||||||
|  | @ -75,51 +75,45 @@ impl MacSystemManager { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /// 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<i64> { | ||||||
|  |         unsafe { | ||||||
|  |             let mut pid: i64 = -1; | ||||||
|  |             let res = 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.
 |     /// Check whether an application is currently holding the Secure Input.
 | ||||||
|     /// Return None if no application has claimed SecureInput, Some((AppName, AppPath)) otherwise.
 |     /// Return None if no application has claimed SecureInput, Some((AppName, AppPath)) otherwise.
 | ||||||
|     pub fn get_secure_input_application() -> Option<(String, String)> { |     pub fn get_secure_input_application() -> Option<(String, String)> { | ||||||
|         use std::process::Command; |  | ||||||
|         use regex::Regex; |         use regex::Regex; | ||||||
| 
 | 
 | ||||||
|         let output = Command::new("ioreg") |  | ||||||
|             .arg("-d") |  | ||||||
|             .arg("1") |  | ||||||
|             .arg("-k") |  | ||||||
|             .arg("IOConsoleUsers") |  | ||||||
|             .arg("-w") |  | ||||||
|             .arg("0") |  | ||||||
|             .output(); |  | ||||||
| 
 |  | ||||||
|         lazy_static! { |  | ||||||
|             static ref PID_REGEX: Regex = Regex::new("\"kCGSSessionSecureInputPID\"=(\\d+)").unwrap(); |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         lazy_static! { |         lazy_static! { | ||||||
|             static ref APP_REGEX: Regex = Regex::new("/([^/]+).app/").unwrap(); |             static ref APP_REGEX: Regex = Regex::new("/([^/]+).app/").unwrap(); | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         if let Ok(output) = output { |         unsafe { | ||||||
|             let output_str = String::from_utf8_lossy(output.stdout.as_slice()); |             let pid = MacSystemManager::get_secure_input_pid(); | ||||||
|             let caps = PID_REGEX.captures(&output_str); |  | ||||||
| 
 | 
 | ||||||
|             if let Some(caps) = caps { |             if let Some(pid) = pid { | ||||||
|                 // Get the PID of the process that is handling SecureInput
 |                 // Size of the buffer is ruled by the PROC_PIDPATHINFO_MAXSIZE constant.
 | ||||||
|                 let pid_str = caps.get(1).map_or("", |m| m.as_str()); |                 // the underlying proc_pidpath REQUIRES a buffer of that dimension, otherwise it fail silently.
 | ||||||
|                 let pid = pid_str.parse::<i32>().expect("Invalid pid value"); |                 let mut buffer : [c_char; 4096] = [0; 4096]; | ||||||
|  |                 let res = get_path_from_pid(pid, buffer.as_mut_ptr(), buffer.len() as i32); | ||||||
| 
 | 
 | ||||||
|                 // Find the process that is handling the SecureInput
 |                 if res > 0 { | ||||||
|                 let output = Command::new("ps") |                     let c_string = CStr::from_ptr(buffer.as_ptr()); | ||||||
|                     .arg("-p") |                     let string = c_string.to_str(); | ||||||
|                     .arg(pid.to_string()) |                     if let Ok(path) = string { | ||||||
|                     .arg("-o") |                         if !path.trim().is_empty() { | ||||||
|                     .arg("command=") |                             let process = path.trim().to_string(); | ||||||
|                     .output(); |  | ||||||
| 
 |  | ||||||
|                 if let Ok(output) = output { |  | ||||||
|                     let output_str = String::from_utf8_lossy(output.stdout.as_slice()); |  | ||||||
| 
 |  | ||||||
|                     if !output_str.trim().is_empty() { |  | ||||||
|                         let process = output_str.trim().to_string(); |  | ||||||
|                             let caps = APP_REGEX.captures(&process); |                             let caps = APP_REGEX.captures(&process); | ||||||
|                             let app_name = if let Some(caps) = caps { |                             let app_name = if let Some(caps) = caps { | ||||||
|                                 caps.get(1).map_or("", |m| m.as_str()).to_owned() |                                 caps.get(1).map_or("", |m| m.as_str()).to_owned() | ||||||
|  | @ -131,14 +125,15 @@ impl MacSystemManager { | ||||||
|                         }else{ |                         }else{ | ||||||
|                             None |                             None | ||||||
|                         } |                         } | ||||||
|                 }else{  // Can't obtain process name
 |                     }else{ | ||||||
|                         None |                         None | ||||||
|                     } |                     } | ||||||
|             }else{ // No process is holding SecureInput
 |                 }else{ | ||||||
|                     None |                     None | ||||||
|                 } |                 } | ||||||
|         }else{  // Can't execute the query to the IOKit registry
 |             }else{ | ||||||
|                 None |                 None | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | @ -30,9 +30,13 @@ pub struct LinuxUIManager { | ||||||
| 
 | 
 | ||||||
| impl super::UIManager for LinuxUIManager { | impl super::UIManager for LinuxUIManager { | ||||||
|     fn notify(&self, message: &str) { |     fn notify(&self, message: &str) { | ||||||
|  |         self.notify_delay(message, 2000); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn notify_delay(&self, message: &str, duration: i32) { | ||||||
|         let res = Command::new("notify-send") |         let res = Command::new("notify-send") | ||||||
|             .args(&["-i", self.icon_path.to_str().unwrap_or_default(), |             .args(&["-i", self.icon_path.to_str().unwrap_or_default(), | ||||||
|                             "-t", "2000", "espanso", message]) |                 "-t", &duration.to_string(), "espanso", message]) | ||||||
|             .output(); |             .output(); | ||||||
| 
 | 
 | ||||||
|         if let Err(e) = res { |         if let Err(e) = res { | ||||||
|  |  | ||||||
|  | @ -29,7 +29,6 @@ use std::os::raw::c_char; | ||||||
| use crate::context; | use crate::context; | ||||||
| 
 | 
 | ||||||
| const NOTIFY_HELPER_BINARY : &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip"); | const NOTIFY_HELPER_BINARY : &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip"); | ||||||
| const DEFAULT_NOTIFICATION_DELAY : f64 = 1.5; |  | ||||||
| 
 | 
 | ||||||
| pub struct MacUIManager { | pub struct MacUIManager { | ||||||
|     notify_helper_path: PathBuf |     notify_helper_path: PathBuf | ||||||
|  | @ -37,12 +36,18 @@ pub struct MacUIManager { | ||||||
| 
 | 
 | ||||||
| impl super::UIManager for MacUIManager { | impl super::UIManager for MacUIManager { | ||||||
|     fn notify(&self, message: &str) { |     fn notify(&self, message: &str) { | ||||||
|  |         self.notify_delay(message, 1500); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn notify_delay(&self, message: &str, duration: i32) { | ||||||
|         let executable_path = self.notify_helper_path.join("Contents"); |         let executable_path = self.notify_helper_path.join("Contents"); | ||||||
|         let executable_path = executable_path.join("MacOS"); |         let executable_path = executable_path.join("MacOS"); | ||||||
|         let executable_path = executable_path.join("EspansoNotifyHelper"); |         let executable_path = executable_path.join("EspansoNotifyHelper"); | ||||||
| 
 | 
 | ||||||
|  |         let duration_float = duration as f64 / 1000.0; | ||||||
|  | 
 | ||||||
|         let res = Command::new(executable_path) |         let res = Command::new(executable_path) | ||||||
|             .args(&["espanso", message, &DEFAULT_NOTIFICATION_DELAY.to_string()]) |             .args(&["espanso", message, &duration_float.to_string()]) | ||||||
|             .spawn(); |             .spawn(); | ||||||
| 
 | 
 | ||||||
|         if let Err(e) = res { |         if let Err(e) = res { | ||||||
|  |  | ||||||
|  | @ -28,6 +28,7 @@ mod macos; | ||||||
| 
 | 
 | ||||||
| pub trait UIManager { | pub trait UIManager { | ||||||
|     fn notify(&self, message: &str); |     fn notify(&self, message: &str); | ||||||
|  |     fn notify_delay(&self, message: &str, duration: i32); | ||||||
|     fn show_menu(&self, menu: Vec<MenuItem>); |     fn show_menu(&self, menu: Vec<MenuItem>); | ||||||
|     fn cleanup(&self); |     fn cleanup(&self); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -31,17 +31,23 @@ pub struct WindowsUIManager { | ||||||
| 
 | 
 | ||||||
| impl super::UIManager for WindowsUIManager { | impl super::UIManager for WindowsUIManager { | ||||||
|     fn notify(&self, message: &str) { |     fn notify(&self, message: &str) { | ||||||
|  |         self.notify_delay(message, 2000); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn notify_delay(&self, message: &str, duration: i32) { | ||||||
|         let current_id: i32 = { |         let current_id: i32 = { | ||||||
|             let mut id = self.id.lock().unwrap(); |             let mut id = self.id.lock().unwrap(); | ||||||
|             *id += 1; |             *id += 1; | ||||||
|             *id |             *id | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|  |         let step = duration / 10; | ||||||
|  | 
 | ||||||
|         // Setup a timeout to close the notification
 |         // Setup a timeout to close the notification
 | ||||||
|         let id = Arc::clone(&self.id); |         let id = Arc::clone(&self.id); | ||||||
|         let _ = thread::Builder::new().name("notification_thread".to_string()).spawn(move || { |         let _ = thread::Builder::new().name("notification_thread".to_string()).spawn(move || { | ||||||
|             for _ in 1..10 { |             for _ in 1..10 { | ||||||
|                 let duration = time::Duration::from_millis(200); |                 let duration = time::Duration::from_millis(step as u64); | ||||||
|                 thread::sleep(duration); |                 thread::sleep(duration); | ||||||
| 
 | 
 | ||||||
|                 let new_id = id.lock().unwrap(); |                 let new_id = id.lock().unwrap(); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user