From 90f6908a8194eccd9b2993779e3ae733df55adf7 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Tue, 2 Jun 2020 21:27:17 +0200 Subject: [PATCH 01/10] Version bump 0.6.2 --- Cargo.lock | 2 +- Cargo.toml | 2 +- snapcraft.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0943f5f..ee65471 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,7 +366,7 @@ dependencies = [ [[package]] name = "espanso" -version = "0.6.1" +version = "0.6.2" dependencies = [ "backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index f13de11..de59500 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "espanso" -version = "0.6.1" +version = "0.6.2" authors = ["Federico Terzi "] license = "GPL-3.0" description = "Cross-platform Text Expander written in Rust" diff --git a/snapcraft.yaml b/snapcraft.yaml index 967e543..275a752 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,5 +1,5 @@ name: espanso -version: 0.6.1 +version: 0.6.2 summary: A Cross-platform Text Expander written in Rust description: | espanso is a Cross-platform, Text Expander written in Rust. From 24641b6cad9cfc465f4bea220bc48bb92091acf2 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Tue, 2 Jun 2020 21:37:53 +0200 Subject: [PATCH 02/10] Fix bug #306 that prevented ALT key from working correctly on Windows. --- native/libwinbridge/bridge.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/native/libwinbridge/bridge.cpp b/native/libwinbridge/bridge.cpp index fcc4d9e..febcb60 100644 --- a/native/libwinbridge/bridge.cpp +++ b/native/libwinbridge/bridge.cpp @@ -210,16 +210,18 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR // Convert the input data RAWINPUT* raw = reinterpret_cast(lpb.data()); - // Make sure it's a keyboard type event, relative to a key press. if (raw->header.dwType == RIM_TYPEKEYBOARD) { // We only want KEY UP AND KEY DOWN events - if (raw->data.keyboard.Message != WM_KEYDOWN && raw->data.keyboard.Message != WM_KEYUP) { + if (raw->data.keyboard.Message != WM_KEYDOWN && raw->data.keyboard.Message != WM_KEYUP && + raw->data.keyboard.Message != WM_SYSKEYDOWN) { return 0; } - int is_key_down = raw->data.keyboard.Message == WM_KEYDOWN; + // The alt key sends a SYSKEYDOWN instead of KEYDOWN event + int is_key_down = raw->data.keyboard.Message == WM_KEYDOWN || + raw->data.keyboard.Message == WM_SYSKEYDOWN; DWORD currentTick = GetTickCount(); From fe745ae815a7da7996b192ce09f85947dfe8ff83 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Tue, 9 Jun 2020 20:48:03 +0200 Subject: [PATCH 03/10] Turn on auto_restart by default. Fix #303 --- src/config/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 351b639..645e7c7 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -129,7 +129,7 @@ fn default_show_notifications() -> bool { true } fn default_auto_restart() -> bool { - false + true } fn default_show_icon() -> bool { true From 7d694494e5461212a7311e6019cd27374a712600 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Tue, 9 Jun 2020 20:53:24 +0200 Subject: [PATCH 04/10] Add trim option to Script Extension. Fix #295 --- src/extension/script.rs | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/extension/script.rs b/src/extension/script.rs index e25a6b1..8d68c46 100644 --- a/src/extension/script.rs +++ b/src/extension/script.rs @@ -88,7 +88,7 @@ impl super::Extension for ScriptExtension { match output { Ok(output) => { - let output_str = String::from_utf8_lossy(output.stdout.as_slice()); + let mut output_str = String::from_utf8_lossy(output.stdout.as_slice()).to_string(); let error_str = String::from_utf8_lossy(output.stderr.as_slice()); let error_str = error_str.to_string(); let error_str = error_str.trim(); @@ -98,7 +98,20 @@ impl super::Extension for ScriptExtension { warn!("Script command reported error: \n{}", error_str); } - return Some(output_str.into_owned()); + // If specified, trim the output + let trim_opt = params.get(&Value::from("trim")); + let should_trim = if let Some(value) = trim_opt { + let val = value.as_bool(); + val.unwrap_or(true) + }else{ + true + }; + + if should_trim { + output_str = output_str.trim().to_owned() + } + + return Some(output_str); } Err(e) => { error!("Could not execute script '{:?}', error: {}", args, e); @@ -129,6 +142,26 @@ mod tests { let extension = ScriptExtension::new(); let output = extension.calculate(¶ms, &vec![]); + assert!(output.is_some()); + assert_eq!(output.unwrap(), "hello world"); + } + + #[test] + #[cfg(not(target_os = "windows"))] + fn test_script_basic_no_trim() { + let mut params = Mapping::new(); + params.insert( + Value::from("args"), + Value::from(vec!["echo", "hello world"]), + ); + params.insert( + Value::from("trim"), + Value::from(false), + ); + + let extension = ScriptExtension::new(); + let output = extension.calculate(¶ms, &vec![]); + assert!(output.is_some()); assert_eq!(output.unwrap(), "hello world\n"); } @@ -146,7 +179,7 @@ mod tests { let output = extension.calculate(¶ms, &vec!["jon".to_owned()]); assert!(output.is_some()); - assert_eq!(output.unwrap(), "hello world\n"); + assert_eq!(output.unwrap(), "hello world"); } #[test] @@ -163,6 +196,6 @@ mod tests { let output = extension.calculate(¶ms, &vec!["jon".to_owned()]); assert!(output.is_some()); - assert_eq!(output.unwrap(), "hello world jon\n"); + assert_eq!(output.unwrap(), "hello world jon"); } } From 0d2a6fe95d0820c7425b1ead36faf8d3e991e0e8 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Tue, 9 Jun 2020 21:35:45 +0200 Subject: [PATCH 05/10] Propagate termination signals from daemon to worker. Fix #302 --- Cargo.lock | 27 +++++++++++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ee65471..6e97ecb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,11 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "arc-swap" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "arrayref" version = "0.3.5" @@ -386,6 +391,7 @@ dependencies = [ "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", + "signal-hook 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", "simplelog 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1349,6 +1355,24 @@ dependencies = [ "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "signal-hook" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "signal-hook-registry" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arc-swap 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "simplelog" version = "0.7.1" @@ -1800,6 +1824,7 @@ dependencies = [ "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum arc-swap 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" "checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" @@ -1947,6 +1972,8 @@ dependencies = [ "checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704" "checksum serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" "checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582" +"checksum signal-hook 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff2db2112d6c761e12522c65f7768548bd6e8cd23d2a9dae162520626629bd6" +"checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" "checksum simplelog 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbe8c881061cce7ee205784634eda7a61922925e7cc2833188467d3a560e027" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" diff --git a/Cargo.toml b/Cargo.toml index de59500..8824c5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ notify = "4.0.13" [target.'cfg(unix)'.dependencies] libc = "0.2.62" +signal-hook = "0.1.15" [build-dependencies] cmake = "0.1.31" diff --git a/src/main.rs b/src/main.rs index 78e3267..8ef5638 100644 --- a/src/main.rs +++ b/src/main.rs @@ -407,6 +407,32 @@ fn daemon_main(config_set: ConfigSet) { }) .expect("Unable to spawn worker monitor thread"); + if !cfg!(target_os = "windows") { + // On Unix, also listen for signals so that we can terminate the + // worker if the daemon receives a signal + use signal_hook::{iterator::Signals, SIGTERM, SIGINT}; + let signals = Signals::new(&[SIGTERM, SIGINT]).expect("unable to register for signals"); + let config_set = config_set.clone(); + thread::Builder::new() + .name("signal monitor".to_string()) + .spawn(move || { + for signal in signals.forever() { + info!("Received signal: {:?}, terminating worker", signal); + send_command_or_warn( + Service::Worker, + config_set.default.clone(), + IPCCommand::exit_worker(), + ); + + std::thread::sleep(Duration::from_millis(200)); + + info!("terminating espanso."); + std::process::exit(0); + } + }) + .expect("Unable to spawn worker monitor thread"); + } + std::thread::sleep(Duration::from_millis(200)); if config_set.default.auto_restart { From e98daedd6b7e96b6a773822f3a5e7f7413876a78 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Tue, 9 Jun 2020 21:45:17 +0200 Subject: [PATCH 06/10] Fix compilation error on Windows --- src/context/windows.rs | 1 + src/main.rs | 55 +++++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/context/windows.rs b/src/context/windows.rs index ec92b57..667d349 100644 --- a/src/context/windows.rs +++ b/src/context/windows.rs @@ -160,6 +160,7 @@ extern "C" fn keypress_callback( // Send the char through the channel match string { Ok(string) => { + println!("{}", string); let event = Event::Key(KeyEvent::Char(string)); (*_self).send_channel.send(event).unwrap(); } diff --git a/src/main.rs b/src/main.rs index 8ef5638..5f394df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -407,31 +407,7 @@ fn daemon_main(config_set: ConfigSet) { }) .expect("Unable to spawn worker monitor thread"); - if !cfg!(target_os = "windows") { - // On Unix, also listen for signals so that we can terminate the - // worker if the daemon receives a signal - use signal_hook::{iterator::Signals, SIGTERM, SIGINT}; - let signals = Signals::new(&[SIGTERM, SIGINT]).expect("unable to register for signals"); - let config_set = config_set.clone(); - thread::Builder::new() - .name("signal monitor".to_string()) - .spawn(move || { - for signal in signals.forever() { - info!("Received signal: {:?}, terminating worker", signal); - send_command_or_warn( - Service::Worker, - config_set.default.clone(), - IPCCommand::exit_worker(), - ); - - std::thread::sleep(Duration::from_millis(200)); - - info!("terminating espanso."); - std::process::exit(0); - } - }) - .expect("Unable to spawn worker monitor thread"); - } + register_signals(config_set.default.clone()); std::thread::sleep(Duration::from_millis(200)); @@ -497,6 +473,35 @@ fn daemon_main(config_set: ConfigSet) { } } +#[cfg(target_os = "windows")] +fn register_signals(_: Configs) {} + +#[cfg(not(target_os = "windows"))] +fn register_signals(config: Configs) { + // On Unix, also listen for signals so that we can terminate the + // worker if the daemon receives a signal + use signal_hook::{iterator::Signals, SIGTERM, SIGINT}; + let signals = Signals::new(&[SIGTERM, SIGINT]).expect("unable to register for signals"); + thread::Builder::new() + .name("signal monitor".to_string()) + .spawn(move || { + for signal in signals.forever() { + info!("Received signal: {:?}, terminating worker", signal); + send_command_or_warn( + Service::Worker, + config, + IPCCommand::exit_worker(), + ); + + std::thread::sleep(Duration::from_millis(200)); + + info!("terminating espanso."); + std::process::exit(0); + } + }) + .expect("Unable to spawn signal monitor thread"); +} + fn watcher_background(sender: Sender) { // Create a channel to receive the events. let (tx, rx) = channel(); From a7d10cbed7f8ba3488bc47fe993627dd1108ef73 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Tue, 9 Jun 2020 21:47:55 +0200 Subject: [PATCH 07/10] Remove unwanted print --- src/context/windows.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/context/windows.rs b/src/context/windows.rs index 667d349..ec92b57 100644 --- a/src/context/windows.rs +++ b/src/context/windows.rs @@ -160,7 +160,6 @@ extern "C" fn keypress_callback( // Send the char through the channel match string { Ok(string) => { - println!("{}", string); let event = Event::Key(KeyEvent::Char(string)); (*_self).send_channel.send(event).unwrap(); } From 232e80f55a5fd1e1ab94a14f10cb22fe091a2493 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Wed, 10 Jun 2020 20:07:06 +0200 Subject: [PATCH 08/10] Fix app name extraction that returned garbage with certain apps on macOS. Fix #291 --- src/system/macos.rs | 63 ++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/src/system/macos.rs b/src/system/macos.rs index c3ccf43..584c26e 100644 --- a/src/system/macos.rs +++ b/src/system/macos.rs @@ -91,12 +91,6 @@ impl MacSystemManager { /// 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 regex::Regex; - - lazy_static! { - static ref APP_REGEX: Regex = Regex::new("/([^/]+).app/").unwrap(); - }; - unsafe { let pid = MacSystemManager::get_secure_input_pid(); @@ -112,26 +106,59 @@ impl MacSystemManager { 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() + let app_name = if let Some(name) = Self::get_app_name_from_path(&process) { + name } else { process.to_owned() }; - Some((app_name, process)) - } else { - None + return Some((app_name, process)); } - } else { - None } - } else { - None } - } else { - None } + + 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 = MacSystemManager::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 = MacSystemManager::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 = MacSystemManager::get_app_name_from_path("/System/Library/Frameworks/Security.framework/Versions/A/MachServices/SecurityAgent.bundle/Contents/MacOS/SecurityAgent"); + assert_eq!(app_name.unwrap(), "SecurityAgent") + } +} + From 470f8d62217b519faa7aeb7dd6e9e7444b97c8f4 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Wed, 10 Jun 2020 20:21:18 +0200 Subject: [PATCH 09/10] Inject user path in Plist file when registering on macOS. Fix #233 --- src/res/mac/com.federicoterzi.espanso.plist | 5 +++++ src/sysdaemon.rs | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/res/mac/com.federicoterzi.espanso.plist b/src/res/mac/com.federicoterzi.espanso.plist index 0728b20..e537367 100644 --- a/src/res/mac/com.federicoterzi.espanso.plist +++ b/src/res/mac/com.federicoterzi.espanso.plist @@ -4,6 +4,11 @@ Label com.federicoterzi.espanso + EnvironmentVariables + + PATH + {{{PATH}}} + ProgramArguments {{{espanso_path}}} diff --git a/src/sysdaemon.rs b/src/sysdaemon.rs index 4d4c055..de54983 100644 --- a/src/sysdaemon.rs +++ b/src/sysdaemon.rs @@ -61,6 +61,12 @@ pub fn register(_config_set: ConfigSet) { espanso_path.to_str().unwrap_or_default(), ); + // Copy the user PATH variable and inject it in the Plist file so that + // it gets loaded by Launchd. + // To see why this is necessary: https://github.com/federico-terzi/espanso/issues/233 + let user_path = std::env::var("PATH").unwrap_or("".to_owned()); + let plist_content = plist_content.replace("{{{PATH}}}", &user_path); + std::fs::write(plist_file.clone(), plist_content).expect("Unable to write plist file"); println!("Entry created correctly!") From e6bbf08a9dfa555059b6dfb30470f09a409565b9 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Wed, 10 Jun 2020 20:32:46 +0200 Subject: [PATCH 10/10] Release SHIFT key if pressed when expanding on macOS. Fix #279 --- native/libmacbridge/bridge.mm | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/native/libmacbridge/bridge.mm b/native/libmacbridge/bridge.mm index 3e2d7e5..1ddd549 100644 --- a/native/libmacbridge/bridge.mm +++ b/native/libmacbridge/bridge.mm @@ -87,6 +87,16 @@ void send_string(const char * string) { // Send the event + // Check if the shift key is down, and if so, release it + // To see why: https://github.com/federico-terzi/espanso/issues/279 + if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, 0x38)) { + CGEventRef e2 = CGEventCreateKeyboardEvent(NULL, 0x38, false); + CGEventPost(kCGHIDEventTap, e2); + CFRelease(e2); + + usleep(2000); + } + // Because of a bug ( or undocumented limit ) of the CGEventKeyboardSetUnicodeString method // the string gets truncated after 20 characters, so we need to send multiple events.