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=static=macbridge");
|
||||
println!("cargo:rustc-link-lib=framework=Cocoa");
|
||||
println!("cargo:rustc-link-lib=framework=IOKit");
|
||||
}
|
||||
|
||||
fn main()
|
||||
|
|
|
@ -158,6 +158,15 @@ int32_t set_clipboard(char * text);
|
|||
*/
|
||||
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
|
||||
|
|
|
@ -20,9 +20,11 @@
|
|||
#include "bridge.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include "AppDelegate.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <libproc.h>
|
||||
extern "C" {
|
||||
|
||||
}
|
||||
|
@ -334,3 +336,47 @@ void open_settings_panel() {
|
|||
[[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 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_secure_input_process(pid:*mut i64) -> i32;
|
||||
pub fn get_path_from_pid(pid:i64, buffer: *mut c_char, size: i32) -> i32;
|
||||
|
||||
// Clipboard
|
||||
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_restore_clipboard_delay() -> i32 { 300 }
|
||||
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_global_vars() -> Vec<MatchVariable> { Vec::new() }
|
||||
|
||||
|
@ -138,6 +141,15 @@ pub struct Configs {
|
|||
#[serde(default = "default_restore_clipboard_delay")]
|
||||
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)]
|
||||
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_key, default_passive_key());
|
||||
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
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ use std::{thread, time};
|
|||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering::Acquire;
|
||||
use crate::config::Configs;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct LinuxContext {
|
||||
|
@ -37,7 +38,7 @@ pub struct 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
|
||||
let x11_available = unsafe {
|
||||
check_x11()
|
||||
|
|
|
@ -20,25 +20,30 @@
|
|||
use std::sync::mpsc::Sender;
|
||||
use std::os::raw::{c_void, c_char};
|
||||
use crate::bridge::macos::*;
|
||||
use crate::event::{Event, KeyEvent, KeyModifier, ActionType};
|
||||
use crate::event::{Event, KeyEvent, KeyModifier, ActionType, SystemEvent};
|
||||
use crate::event::KeyModifier::*;
|
||||
use std::ffi::{CString, CStr};
|
||||
use std::fs;
|
||||
use std::{fs, thread};
|
||||
use log::{info, error, debug};
|
||||
use std::process::exit;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
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");
|
||||
|
||||
pub struct MacContext {
|
||||
pub send_channel: Sender<Event>,
|
||||
is_injecting: Arc<AtomicBool>,
|
||||
secure_input_watcher_enabled: bool,
|
||||
secure_input_watcher_interval: i32,
|
||||
}
|
||||
|
||||
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
|
||||
unsafe {
|
||||
let res = prompt_accessibility();
|
||||
|
@ -53,7 +58,9 @@ impl MacContext {
|
|||
|
||||
let context = Box::new(MacContext {
|
||||
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
|
||||
|
@ -81,10 +88,59 @@ impl MacContext {
|
|||
|
||||
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 {
|
||||
fn eventloop(&self) {
|
||||
// Start the SecureInput watcher thread
|
||||
if self.secure_input_watcher_enabled {
|
||||
self.start_secure_input_watcher();
|
||||
}
|
||||
|
||||
unsafe {
|
||||
eventloop();
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ use std::path::PathBuf;
|
|||
use std::fs::create_dir_all;
|
||||
use std::sync::{Once, Arc};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use crate::config::Configs;
|
||||
|
||||
pub trait Context {
|
||||
fn eventloop(&self);
|
||||
|
@ -39,20 +40,20 @@ pub trait Context {
|
|||
|
||||
// MAC IMPLEMENTATION
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
|
||||
macos::MacContext::new(send_channel, is_injecting)
|
||||
pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
|
||||
macos::MacContext::new(config, send_channel, is_injecting)
|
||||
}
|
||||
|
||||
// LINUX IMPLEMENTATION
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
|
||||
linux::LinuxContext::new(send_channel, is_injecting)
|
||||
pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
|
||||
linux::LinuxContext::new(config, send_channel, is_injecting)
|
||||
}
|
||||
|
||||
// WINDOWS IMPLEMENTATION
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
|
||||
windows::WindowsContext::new(send_channel, is_injecting)
|
||||
pub fn new(config: Configs, send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
|
||||
windows::WindowsContext::new(config, send_channel, is_injecting)
|
||||
}
|
||||
|
||||
// espanso directories
|
||||
|
|
|
@ -28,6 +28,7 @@ use log::{info, error, debug};
|
|||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering::Acquire;
|
||||
use crate::config::Configs;
|
||||
|
||||
const BMP_BINARY : &[u8] = include_bytes!("../res/win/espanso.bmp");
|
||||
const ICO_BINARY : &[u8] = include_bytes!("../res/win/espanso.ico");
|
||||
|
@ -38,7 +39,7 @@ pub struct 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
|
||||
|
||||
let espanso_dir = super::get_data_dir();
|
||||
|
|
|
@ -24,7 +24,7 @@ use crate::config::BackendType;
|
|||
use crate::clipboard::ClipboardManager;
|
||||
use log::{info, warn, debug, error};
|
||||
use crate::ui::{UIManager, MenuItem, MenuItemType};
|
||||
use crate::event::{ActionEventReceiver, ActionType};
|
||||
use crate::event::{ActionEventReceiver, ActionType, SystemEventReceiver, SystemEvent};
|
||||
use crate::extension::Extension;
|
||||
use crate::render::{Renderer, RenderResult};
|
||||
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/>.
|
||||
*/
|
||||
|
||||
use crate::event::{KeyEventReceiver, ActionEventReceiver, Event};
|
||||
use crate::event::{KeyEventReceiver, ActionEventReceiver, Event, SystemEventReceiver};
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
pub trait EventManager {
|
||||
|
@ -28,15 +28,18 @@ pub struct DefaultEventManager<'a> {
|
|||
receive_channel: Receiver<Event>,
|
||||
key_receivers: Vec<&'a dyn KeyEventReceiver>,
|
||||
action_receivers: Vec<&'a dyn ActionEventReceiver>,
|
||||
system_receivers: Vec<&'a dyn SystemEventReceiver>,
|
||||
}
|
||||
|
||||
impl<'a> DefaultEventManager<'a> {
|
||||
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 {
|
||||
receive_channel,
|
||||
key_receivers,
|
||||
action_receivers,
|
||||
system_receivers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +56,9 @@ impl <'a> EventManager for DefaultEventManager<'a> {
|
|||
Event::Action(action_event) => {
|
||||
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),
|
||||
|
|
|
@ -24,7 +24,8 @@ use serde::{Serialize, Deserialize};
|
|||
#[derive(Debug, Clone)]
|
||||
pub enum Event {
|
||||
Action(ActionType),
|
||||
Key(KeyEvent)
|
||||
Key(KeyEvent),
|
||||
System(SystemEvent),
|
||||
}
|
||||
|
||||
#[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
|
||||
|
||||
pub trait KeyEventReceiver {
|
||||
|
@ -142,6 +150,10 @@ pub trait ActionEventReceiver {
|
|||
fn on_action_event(&self, e: ActionType);
|
||||
}
|
||||
|
||||
pub trait SystemEventReceiver {
|
||||
fn on_system_event(&self, e: SystemEvent);
|
||||
}
|
||||
|
||||
// TESTS
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -338,7 +338,7 @@ fn daemon_main(config_set: ConfigSet) {
|
|||
// we could reinterpret the characters we are injecting
|
||||
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();
|
||||
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,
|
||||
vec!(&matcher),
|
||||
vec!(&engine, &matcher),
|
||||
vec!(&engine),
|
||||
);
|
||||
|
||||
info!("espanso is running!");
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
use std::os::raw::c_char;
|
||||
|
||||
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 {
|
||||
|
||||
|
@ -75,70 +75,65 @@ 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.
|
||||
/// Return None if no application has claimed SecureInput, Some((AppName, AppPath)) otherwise.
|
||||
pub fn get_secure_input_application() -> Option<(String, String)> {
|
||||
use std::process::Command;
|
||||
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! {
|
||||
static ref APP_REGEX: Regex = Regex::new("/([^/]+).app/").unwrap();
|
||||
};
|
||||
|
||||
if let Ok(output) = output {
|
||||
let output_str = String::from_utf8_lossy(output.stdout.as_slice());
|
||||
let caps = PID_REGEX.captures(&output_str);
|
||||
unsafe {
|
||||
let pid = MacSystemManager::get_secure_input_pid();
|
||||
|
||||
if let Some(caps) = caps {
|
||||
// Get the PID of the process that is handling SecureInput
|
||||
let pid_str = caps.get(1).map_or("", |m| m.as_str());
|
||||
let pid = pid_str.parse::<i32>().expect("Invalid pid value");
|
||||
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 = get_path_from_pid(pid, buffer.as_mut_ptr(), buffer.len() as i32);
|
||||
|
||||
// Find the process that is handling the SecureInput
|
||||
let output = Command::new("ps")
|
||||
.arg("-p")
|
||||
.arg(pid.to_string())
|
||||
.arg("-o")
|
||||
.arg("command=")
|
||||
.output();
|
||||
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 caps = APP_REGEX.captures(&process);
|
||||
let app_name = if let Some(caps) = caps {
|
||||
caps.get(1).map_or("", |m| m.as_str()).to_owned()
|
||||
}else{
|
||||
process.to_owned()
|
||||
};
|
||||
|
||||
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 app_name = if let Some(caps) = caps {
|
||||
caps.get(1).map_or("", |m| m.as_str()).to_owned()
|
||||
Some((app_name, process))
|
||||
}else{
|
||||
process.to_owned()
|
||||
};
|
||||
|
||||
Some((app_name, process))
|
||||
None
|
||||
}
|
||||
}else{
|
||||
None
|
||||
}
|
||||
}else{ // Can't obtain process name
|
||||
}else{
|
||||
None
|
||||
}
|
||||
}else{ // No process is holding SecureInput
|
||||
}else{
|
||||
None
|
||||
}
|
||||
}else{ // Can't execute the query to the IOKit registry
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,10 +30,14 @@ pub struct LinuxUIManager {
|
|||
|
||||
impl super::UIManager for LinuxUIManager {
|
||||
fn notify(&self, message: &str) {
|
||||
self.notify_delay(message, 2000);
|
||||
}
|
||||
|
||||
fn notify_delay(&self, message: &str, duration: i32) {
|
||||
let res = Command::new("notify-send")
|
||||
.args(&["-i", self.icon_path.to_str().unwrap_or_default(),
|
||||
"-t", "2000", "espanso", message])
|
||||
.output();
|
||||
.args(&["-i", self.icon_path.to_str().unwrap_or_default(),
|
||||
"-t", &duration.to_string(), "espanso", message])
|
||||
.output();
|
||||
|
||||
if let Err(e) = res {
|
||||
error!("Could not send a notification, error: {}", e);
|
||||
|
|
|
@ -29,7 +29,6 @@ use std::os::raw::c_char;
|
|||
use crate::context;
|
||||
|
||||
const NOTIFY_HELPER_BINARY : &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip");
|
||||
const DEFAULT_NOTIFICATION_DELAY : f64 = 1.5;
|
||||
|
||||
pub struct MacUIManager {
|
||||
notify_helper_path: PathBuf
|
||||
|
@ -37,12 +36,18 @@ pub struct MacUIManager {
|
|||
|
||||
impl super::UIManager for MacUIManager {
|
||||
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 = executable_path.join("MacOS");
|
||||
let executable_path = executable_path.join("EspansoNotifyHelper");
|
||||
|
||||
let duration_float = duration as f64 / 1000.0;
|
||||
|
||||
let res = Command::new(executable_path)
|
||||
.args(&["espanso", message, &DEFAULT_NOTIFICATION_DELAY.to_string()])
|
||||
.args(&["espanso", message, &duration_float.to_string()])
|
||||
.spawn();
|
||||
|
||||
if let Err(e) = res {
|
||||
|
|
|
@ -28,6 +28,7 @@ mod macos;
|
|||
|
||||
pub trait UIManager {
|
||||
fn notify(&self, message: &str);
|
||||
fn notify_delay(&self, message: &str, duration: i32);
|
||||
fn show_menu(&self, menu: Vec<MenuItem>);
|
||||
fn cleanup(&self);
|
||||
}
|
||||
|
|
|
@ -31,17 +31,23 @@ pub struct WindowsUIManager {
|
|||
|
||||
impl super::UIManager for WindowsUIManager {
|
||||
fn notify(&self, message: &str) {
|
||||
self.notify_delay(message, 2000);
|
||||
}
|
||||
|
||||
fn notify_delay(&self, message: &str, duration: i32) {
|
||||
let current_id: i32 = {
|
||||
let mut id = self.id.lock().unwrap();
|
||||
*id += 1;
|
||||
*id
|
||||
};
|
||||
|
||||
let step = duration / 10;
|
||||
|
||||
// Setup a timeout to close the notification
|
||||
let id = Arc::clone(&self.id);
|
||||
let _ = thread::Builder::new().name("notification_thread".to_string()).spawn(move || {
|
||||
for _ in 1..10 {
|
||||
let duration = time::Duration::from_millis(200);
|
||||
let duration = time::Duration::from_millis(step as u64);
|
||||
thread::sleep(duration);
|
||||
|
||||
let new_id = id.lock().unwrap();
|
||||
|
|
Loading…
Reference in New Issue
Block a user