diff --git a/Cargo.lock b/Cargo.lock index 7c2fd3d..47fbd70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,10 +137,6 @@ dependencies = [ name = "cc" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "jobserver 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "cfg-if" @@ -370,7 +366,7 @@ dependencies = [ [[package]] name = "espanso" -version = "0.5.3" +version = "0.5.4" 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)", @@ -379,7 +375,6 @@ dependencies = [ "dialoguer 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "git2 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -497,20 +492,6 @@ dependencies = [ "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "git2" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "libgit2-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "h2" version = "0.1.26" @@ -634,16 +615,6 @@ name = "itoa" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "jobserver" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "kernel32-sys" version = "0.2.2" @@ -663,43 +634,6 @@ name = "libc" version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "libgit2-sys" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "libssh2-sys 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "libssh2-sys" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "libz-sys" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "linked-hash-map" version = "0.5.2" @@ -1839,7 +1773,6 @@ dependencies = [ "checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" "checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" -"checksum git2 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39f27186fbb5ec67ece9a56990292bc5aed3c3fc51b9b07b0b52446b1dfb4a82" "checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" "checksum http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "372bcb56f939e449117fb0869c2e8fd8753a8223d92a172c6e808cf123a5b6e4" "checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" @@ -1851,13 +1784,9 @@ dependencies = [ "checksum indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a61202fbe46c4a951e9404a720a0180bcf3212c750d735cb5c4ba4dc551299f3" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" -"checksum jobserver 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b1d42ef453b30b7387e113da1c83ab1605d90c5b4e0eb8e96d016ed3b8c160" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" -"checksum libgit2-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a30f8637eb59616ee3b8a00f6adff781ee4ddd8343a615b8238de756060cc1b3" -"checksum libssh2-sys 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8914d10b159fc288f2b6f253c94bd0c15a777fd5a297691141d89674b87e66fd" -"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" diff --git a/Cargo.toml b/Cargo.toml index 2f2c89a..95a12ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "espanso" -version = "0.5.3" +version = "0.5.4" authors = ["Federico Terzi "] license = "GPL-3.0" description = "Cross-platform Text Expander written in Rust" @@ -26,7 +26,6 @@ chrono = "0.4.9" lazy_static = "1.4.0" walkdir = "2.2.9" reqwest = "0.9.20" -git2 = {version = "0.10.1", features = ["https"]} tempfile = "3.1.0" dialoguer = "0.4.0" rand = "0.7.2" diff --git a/build.rs b/build.rs index a415c03..b2b1f65 100644 --- a/build.rs +++ b/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() diff --git a/native/libmacbridge/bridge.h b/native/libmacbridge/bridge.h index dc74187..03bb6bf 100644 --- a/native/libmacbridge/bridge.h +++ b/native/libmacbridge/bridge.h @@ -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 diff --git a/native/libmacbridge/bridge.mm b/native/libmacbridge/bridge.mm index 564405b..1c59bd6 100644 --- a/native/libmacbridge/bridge.mm +++ b/native/libmacbridge/bridge.mm @@ -20,9 +20,11 @@ #include "bridge.h" #import +#include #include "AppDelegate.h" #include #include +#include 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; + } +} \ No newline at end of file diff --git a/packager/mac/espanso.rb b/packager/mac/espanso.rb index d259b87..6e7f477 100644 --- a/packager/mac/espanso.rb +++ b/packager/mac/espanso.rb @@ -7,7 +7,6 @@ class Espanso < Formula url "https://github.com/federico-terzi/espanso/releases/latest/download/espanso-mac.tar.gz" sha256 "{{{release_hash}}}" version "{{{app_version}}}" - depends_on "openssl@1.1" def install bin.install "espanso" diff --git a/snapcraft.yaml b/snapcraft.yaml index d767423..b6f3271 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,5 +1,5 @@ name: espanso -version: 0.5.3 +version: 0.5.4 summary: A Cross-platform Text Expander written in Rust description: | espanso is a Cross-platform, Text Expander written in Rust. diff --git a/src/bridge/macos.rs b/src/bridge/macos.rs index c698d35..989786b 100644 --- a/src/bridge/macos.rs +++ b/src/bridge/macos.rs @@ -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; diff --git a/src/check.rs b/src/check.rs index 146b170..daed087 100644 --- a/src/check.rs +++ b/src/check.rs @@ -17,11 +17,11 @@ * along with espanso. If not, see . */ -// This functions are used to check if the required dependencies are satisfied +// This functions are used to check if the required dependencies and conditions are satisfied // before starting espanso #[cfg(target_os = "linux")] -pub fn check_dependencies() -> bool { +pub fn check_preconditions() -> bool { use std::process::Command; let mut result = true; @@ -48,13 +48,26 @@ pub fn check_dependencies() -> bool { } #[cfg(target_os = "macos")] -pub fn check_dependencies() -> bool { - // Nothing to do here +pub fn check_preconditions() -> bool { + // Make sure no app is currently using secure input. + let secure_input_app = crate::system::macos::MacSystemManager::get_secure_input_application(); + + if let Some((app_name, process)) = secure_input_app { + eprintln!("WARNING: An application is currently using SecureInput and might prevent espanso from working correctly."); + eprintln!(); + eprintln!("APP: {}", app_name); + eprintln!("PROC: {}", process); + eprintln!(); + eprintln!("Please close it or disable SecureInput for that application (most apps that use it have a"); + eprintln!("setting to disable it)."); + eprintln!("Until then, espanso might not work as expected."); + } + true } #[cfg(target_os = "windows")] -pub fn check_dependencies() -> bool { +pub fn check_preconditions() -> bool { // Nothing needed on windows true } \ No newline at end of file diff --git a/src/clipboard/linux.rs b/src/clipboard/linux.rs index 8fb76e1..b3e4e4d 100644 --- a/src/clipboard/linux.rs +++ b/src/clipboard/linux.rs @@ -19,7 +19,7 @@ use std::process::{Command, Stdio}; use std::io::{Write}; -use log::{error, warn}; +use log::{error}; use std::path::Path; pub struct LinuxClipboardManager {} @@ -86,6 +86,10 @@ impl super::ClipboardManager for LinuxClipboardManager { let res = Command::new("xclip") .args(&["-selection", "clipboard", "-t", mime, "-i", &image_path]) .spawn(); + + if let Err(e) = res { + error!("Could not set image clipboard: {}", e); + } } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 9244868..d4d6589 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -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 { Vec::new() } fn default_global_vars() -> Vec { 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 } @@ -412,12 +427,10 @@ impl ConfigSet { // Check if some triggers are conflicting with each other // For more information, see: https://github.com/federico-terzi/espanso/issues/135 if default.conflict_check { - for s in specific.iter() { - let has_conflicts = Self::has_conflicts(&default, &specific); - if has_conflicts { - eprintln!("Warning: some triggers had conflicts and may not behave as intended"); - eprintln!("To turn off this check, add \"conflict_check: false\" in the configuration"); - } + let has_conflicts = Self::has_conflicts(&default, &specific); + if has_conflicts { + eprintln!("Warning: some triggers had conflicts and may not behave as intended"); + eprintln!("To turn off this check, add \"conflict_check: false\" in the configuration"); } } @@ -568,8 +581,7 @@ mod tests { use super::*; use std::io::Write; use tempfile::{NamedTempFile, TempDir}; - use std::any::Any; - use crate::matcher::{TextContent, MatchContentType}; + use crate::matcher::{MatchContentType}; const TEST_WORKING_CONFIG_FILE : &str = include_str!("../res/test/working_config.yml"); const TEST_CONFIG_FILE_WITH_BAD_YAML : &str = include_str!("../res/test/config_with_bad_yaml.yml"); @@ -578,7 +590,7 @@ mod tests { fn create_tmp_file(string: &str) -> NamedTempFile { let file = NamedTempFile::new().unwrap(); - file.as_file().write_all(string.as_bytes()); + file.as_file().write_all(string.as_bytes()).unwrap(); file } @@ -701,7 +713,7 @@ mod tests { let package_dir = TempDir::new().expect("unable to create package directory"); let default_path = data_dir.path().join(DEFAULT_CONFIG_FILE_NAME); - fs::write(default_path, default_content); + fs::write(default_path, default_content).unwrap(); (data_dir, package_dir) } @@ -709,7 +721,7 @@ mod tests { pub fn create_temp_file_in_dir(tmp_dir: &PathBuf, name: &str, content: &str) -> PathBuf { let user_defined_path = tmp_dir.join(name); let user_defined_path_copy = user_defined_path.clone(); - fs::write(user_defined_path, content); + fs::write(user_defined_path, content).unwrap(); user_defined_path_copy } @@ -717,7 +729,7 @@ mod tests { pub fn create_user_config_file(tmp_dir: &Path, name: &str, content: &str) -> PathBuf { let user_config_dir = tmp_dir.join(USER_CONFIGS_FOLDER_NAME); if !user_config_dir.exists() { - create_dir_all(&user_config_dir); + create_dir_all(&user_config_dir).unwrap(); } create_temp_file_in_dir(&user_config_dir, name, content) @@ -726,7 +738,7 @@ mod tests { pub fn create_package_file(package_data_dir: &Path, package_name: &str, filename: &str, content: &str) -> PathBuf { let package_dir = package_data_dir.join(package_name); if !package_dir.exists() { - create_dir_all(&package_dir); + create_dir_all(&package_dir).unwrap(); } create_temp_file_in_dir(&package_dir, filename, content) @@ -809,11 +821,11 @@ mod tests { fn test_config_set_specific_file_duplicate_name() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" name: specific1 "###); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###" + create_user_config_file(data_dir.path(), "specific2.yml", r###" name: specific1 "###); @@ -832,7 +844,7 @@ mod tests { replace: "Bob" "###); - let user_defined_path = create_user_config_file(data_dir.path(), "specific1.yml", r###" + create_user_config_file(data_dir.path(), "specific1.yml", r###" name: specific1 matches: @@ -859,7 +871,7 @@ mod tests { replace: "Bob" "###); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###" + create_user_config_file(data_dir.path(), "specific2.yml", r###" name: specific1 matches: @@ -891,7 +903,7 @@ mod tests { replace: "Bob" "###); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###" + create_user_config_file(data_dir.path(), "specific2.yml", r###" name: specific1 exclude_default_entries: true @@ -926,7 +938,7 @@ mod tests { "### ); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific.zzz", r###" + create_user_config_file(data_dir.path(), "specific.zzz", r###" name: specific1 exclude_default_entries: true @@ -944,11 +956,11 @@ mod tests { fn test_config_set_no_parent_configs_works_correctly() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" name: specific1 "###); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###" + create_user_config_file(data_dir.path(), "specific2.yml", r###" name: specific2 "###); @@ -964,7 +976,7 @@ mod tests { replace: Hasta la vista "###); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" parent: default matches: @@ -987,7 +999,7 @@ mod tests { replace: Hasta la vista "###); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" matches: - trigger: "hello" replace: "world" @@ -1009,7 +1021,7 @@ mod tests { replace: Hasta la vista "###); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" name: custom1 parent: default @@ -1018,7 +1030,7 @@ mod tests { replace: "world" "###); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###" + create_user_config_file(data_dir.path(), "specific2.yml", r###" parent: custom1 matches: @@ -1042,7 +1054,7 @@ mod tests { replace: Hasta la vista "###); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" parent: default matches: @@ -1070,7 +1082,7 @@ mod tests { replace: Hasta la vista "###); - let package_path = create_package_file(package_dir.path(), "package1", "package.yml", r###" + create_package_file(package_dir.path(), "package1", "package.yml", r###" parent: default matches: @@ -1093,7 +1105,7 @@ mod tests { replace: Hasta la vista "###); - let package_path = create_package_file(package_dir.path(), "package1", "package.yml", r###" + create_package_file(package_dir.path(), "package1", "package.yml", r###" matches: - trigger: "harry" replace: "potter" @@ -1114,7 +1126,7 @@ mod tests { replace: Hasta la vista "###); - let package_path = create_package_file(package_dir.path(), "package1", "package.yml", r###" + create_package_file(package_dir.path(), "package1", "package.yml", r###" name: package1 matches: @@ -1122,7 +1134,7 @@ mod tests { replace: "potter" "###); - let package_path2 = create_package_file(package_dir.path(), "package1", "addon.yml", r###" + create_package_file(package_dir.path(), "package1", "addon.yml", r###" parent: package1 matches: @@ -1160,7 +1172,7 @@ mod tests { replace: Jon "###); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" name: specific1 matches: @@ -1184,7 +1196,7 @@ mod tests { replace: Error "###); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" name: specific1 matches: @@ -1206,7 +1218,7 @@ mod tests { replace: Jon "###); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" name: specific1 matches: @@ -1228,14 +1240,14 @@ mod tests { replace: Jon "###); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" name: specific1 matches: - trigger: "bad" replace: "Conflict" "###); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###" + create_user_config_file(data_dir.path(), "specific2.yml", r###" name: specific2 matches: @@ -1257,7 +1269,7 @@ mod tests { format: "%m" "###); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" global_vars: - name: specificvar type: date @@ -1283,7 +1295,7 @@ mod tests { format: "%m" "###); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" parent: default global_vars: - name: specificvar @@ -1309,7 +1321,7 @@ mod tests { format: "%m" "###); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file(data_dir.path(), "specific.yml", r###" exclude_default_entries: true global_vars: diff --git a/src/config/runtime.rs b/src/config/runtime.rs index 2e2aff0..6a07fac 100644 --- a/src/config/runtime.rs +++ b/src/config/runtime.rs @@ -204,12 +204,8 @@ impl <'a, S: SystemManager> super::ConfigManager<'a> for RuntimeConfigManager<'a #[cfg(test)] mod tests { use super::*; - use tempfile::{NamedTempFile, TempDir}; - use crate::config::{DEFAULT_CONFIG_FILE_NAME, DEFAULT_CONFIG_FILE_CONTENT}; - use std::fs; - use std::path::PathBuf; use crate::config::ConfigManager; - use crate::config::tests::{create_temp_espanso_directories, create_temp_file_in_dir, create_user_config_file}; + use crate::config::tests::{create_temp_espanso_directories, create_user_config_file}; struct DummySystemManager { title: RefCell, @@ -251,18 +247,18 @@ mod tests { fn test_runtime_constructor_regex_load_correctly() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file(&data_dir.path(), "specific.yml", r###" name: myname1 filter_exec: "Title" "###); - let specific_path2 = create_user_config_file(&data_dir.path(), "specific2.yml", r###" + create_user_config_file(&data_dir.path(), "specific2.yml", r###" name: myname2 filter_title: "Yeah" filter_class: "Car" "###); - let specific_path3 = create_user_config_file(&data_dir.path(), "specific3.yml", r###" + create_user_config_file(&data_dir.path(), "specific3.yml", r###" name: myname3 filter_title: "Nice" "###); @@ -302,18 +298,18 @@ mod tests { fn test_runtime_constructor_malformed_regexes_are_ignored() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file(&data_dir.path(), "specific.yml", r###" name: myname1 filter_exec: "[`-_]" "###); - let specific_path2 = create_user_config_file(&data_dir.path(), "specific2.yml", r###" + create_user_config_file(&data_dir.path(), "specific2.yml", r###" name: myname2 filter_title: "[`-_]" filter_class: "Car" "###); - let specific_path3 = create_user_config_file(&data_dir.path(), "specific3.yml", r###" + create_user_config_file(&data_dir.path(), "specific3.yml", r###" name: myname3 filter_title: "Nice" "###); @@ -353,7 +349,7 @@ mod tests { fn test_runtime_calculate_active_config_specific_title_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file(&data_dir.path(), "specific.yml", r###" name: chrome filter_title: "Chrome" "###); @@ -368,10 +364,11 @@ mod tests { assert_eq!(config_manager.calculate_active_config().name, "chrome"); } + #[test] fn test_runtime_calculate_active_config_specific_class_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file(&data_dir.path(), "specific.yml", r###" name: chrome filter_class: "Chrome" "###); @@ -386,10 +383,11 @@ mod tests { assert_eq!(config_manager.calculate_active_config().name, "chrome"); } + #[test] fn test_runtime_calculate_active_config_specific_exec_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file(&data_dir.path(), "specific.yml", r###" name: chrome filter_exec: "chrome.exe" "###); @@ -404,10 +402,11 @@ mod tests { assert_eq!(config_manager.calculate_active_config().name, "chrome"); } + #[test] fn test_runtime_calculate_active_config_specific_multi_filter_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file(&data_dir.path(), "specific.yml", r###" name: chrome filter_class: Browser filter_exec: "firefox.exe" @@ -427,7 +426,7 @@ mod tests { fn test_runtime_calculate_active_config_no_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file(&data_dir.path(), "specific.yml", r###" name: firefox filter_title: "Firefox" "###); @@ -446,7 +445,7 @@ mod tests { fn test_runtime_active_config_cache() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file(&data_dir.path(), "specific.yml", r###" name: firefox filter_title: "Firefox" "###); diff --git a/src/context/linux.rs b/src/context/linux.rs index 876db30..2f8e0a6 100644 --- a/src/context/linux.rs +++ b/src/context/linux.rs @@ -23,12 +23,12 @@ use crate::event::*; use crate::event::KeyModifier::*; use crate::bridge::linux::*; use std::process::exit; -use log::{debug, error, info}; +use log::{debug, error}; use std::ffi::CStr; -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 +37,7 @@ pub struct LinuxContext { } impl LinuxContext { - pub fn new(send_channel: Sender, is_injecting: Arc) -> Box { + pub fn new(_: Configs, send_channel: Sender, is_injecting: Arc) -> Box { // Check if the X11 context is available let x11_available = unsafe { check_x11() @@ -85,7 +85,7 @@ impl Drop for LinuxContext { // Native bridge code -extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, +extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, _len: i32, event_type: i32, key_code: i32) { unsafe { let _self = _self as *mut LinuxContext; @@ -100,7 +100,7 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, if event_type == 0 { // Char event // Convert the received buffer to a string - let c_str = CStr::from_ptr(raw_buffer as (*const c_char)); + let c_str = CStr::from_ptr(raw_buffer as *const c_char); let char_str = c_str.to_str(); // Send the char through the channel diff --git a/src/context/macos.rs b/src/context/macos.rs index 0f31d21..7b6ec44 100644 --- a/src/context/macos.rs +++ b/src/context/macos.rs @@ -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, is_injecting: Arc, + secure_input_watcher_enabled: bool, + secure_input_watcher_interval: i32, } impl MacContext { - pub fn new(send_channel: Sender, is_injecting: Arc) -> Box { + pub fn new(config: Configs, send_channel: Sender, is_injecting: Arc) -> Box { // 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 = 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(); } diff --git a/src/context/mod.rs b/src/context/mod.rs index 332ba4f..0508cde 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -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, is_injecting: Arc) -> Box { - macos::MacContext::new(send_channel, is_injecting) +pub fn new(config: Configs, send_channel: Sender, is_injecting: Arc) -> Box { + macos::MacContext::new(config, send_channel, is_injecting) } // LINUX IMPLEMENTATION #[cfg(target_os = "linux")] -pub fn new(send_channel: Sender, is_injecting: Arc) -> Box { - linux::LinuxContext::new(send_channel, is_injecting) +pub fn new(config: Configs, send_channel: Sender, is_injecting: Arc) -> Box { + linux::LinuxContext::new(config, send_channel, is_injecting) } // WINDOWS IMPLEMENTATION #[cfg(target_os = "windows")] -pub fn new(send_channel: Sender, is_injecting: Arc) -> Box { - windows::WindowsContext::new(send_channel, is_injecting) +pub fn new(config: Configs, send_channel: Sender, is_injecting: Arc) -> Box { + windows::WindowsContext::new(config, send_channel, is_injecting) } // espanso directories diff --git a/src/context/windows.rs b/src/context/windows.rs index c797d1f..0640d65 100644 --- a/src/context/windows.rs +++ b/src/context/windows.rs @@ -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, is_injecting: Arc) -> Box { + pub fn new(config: Configs, send_channel: Sender, is_injecting: Arc) -> Box { // Initialize image resources let espanso_dir = super::get_data_dir(); diff --git a/src/engine.rs b/src/engine.rs index 3d69476..0527533 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -17,25 +17,21 @@ * along with espanso. If not, see . */ -use crate::matcher::{Match, MatchReceiver, MatchContentType}; +use crate::matcher::{Match, MatchReceiver}; use crate::keyboard::KeyboardManager; use crate::config::ConfigManager; 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::extension::Extension; +use crate::event::{ActionEventReceiver, ActionType, SystemEventReceiver, SystemEvent}; use crate::render::{Renderer, RenderResult}; use std::cell::RefCell; use std::process::exit; -use std::collections::HashMap; -use std::path::PathBuf; -use regex::{Regex, Captures}; -use std::time::SystemTime; +use regex::{Regex}; use std::sync::Arc; use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering::{Relaxed, Release, Acquire, AcqRel, SeqCst}; +use std::sync::atomic::Ordering::Release; pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager, R: Renderer> { @@ -47,7 +43,6 @@ pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager< is_injecting: Arc, enabled: RefCell, - last_action_time: RefCell, // Used to block espanso from re-interpreting it's own inputs } impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager, R: Renderer> @@ -56,7 +51,6 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa config_manager: &'a M, ui_manager: &'a U, renderer: &'a R, is_injecting: Arc) -> Engine<'a, S, C, M, U, R> { let enabled = RefCell::new(true); - let last_action_time = RefCell::new(SystemTime::now()); Engine{keyboard_manager, clipboard_manager, @@ -65,7 +59,6 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa renderer, is_injecting, enabled, - last_action_time, } } @@ -92,7 +85,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa menu.push(MenuItem{ item_type: MenuItemType::Button, - item_name: "Exit".to_owned(), + item_name: "Exit espanso".to_owned(), item_id: ActionType::Exit as i32, }); @@ -111,20 +104,6 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa None } } - - /// Used to check if the last action has been executed within a specified interval. - /// If so, return true (blocking the action), otherwise false. - fn check_last_action_and_set(&self, interval: u128) -> bool { - let mut last_action_time = self.last_action_time.borrow_mut(); - if let Ok(elapsed) = last_action_time.elapsed() { - if elapsed.as_millis() < interval { - return true; - } - } - - (*last_action_time) = SystemTime::now(); - return false; - } } lazy_static! { @@ -189,7 +168,9 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa None }; - let backend = if config.backend == BackendType::Auto { + let backend = if m.force_clipboard { + &BackendType::Clipboard + }else if config.backend == BackendType::Auto { if cfg!(target_os = "linux") { let all_ascii = target_string.chars().all(|c| c.is_ascii()); if all_ascii { @@ -286,7 +267,16 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa return; } - info!("Passive mode activated"); + // Block espanso from reinterpreting its own actions + self.is_injecting.store(true, Release); + + // In order to avoid pasting previous clipboard contents, we need to check if + // a new clipboard was effectively copied. + // See issue: https://github.com/federico-terzi/espanso/issues/213 + let previous_clipboard = self.clipboard_manager.get_clipboard(); + + // Sleep for a while, giving time to effectively copy the text + std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding // Trigger a copy shortcut to transfer the content of the selection to the clipboard self.keyboard_manager.trigger_copy(); @@ -298,22 +288,39 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa let clipboard = self.clipboard_manager.get_clipboard(); if let Some(clipboard) = clipboard { - let rendered = self.renderer.render_passive(&clipboard, - &config); + // Don't expand empty clipboards, as usually they are the result of an empty passive selection + if clipboard.trim().is_empty() { + info!("Avoiding passive expansion, as the user didn't select anything"); + }else{ + if let Some(previous_content) = previous_clipboard { + // Because of issue #213, we need to make sure the user selected something. + if clipboard == previous_content { + info!("Avoiding passive expansion, as the user didn't select anything"); + } else { + info!("Passive mode activated"); - match rendered { - RenderResult::Text(payload) => { - // Paste back the result in the field - self.clipboard_manager.set_clipboard(&payload); + let rendered = self.renderer.render_passive(&clipboard, + &config); - std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding - self.keyboard_manager.trigger_paste(&config.paste_shortcut); - }, - _ => { - warn!("Cannot expand passive match") - }, + match rendered { + RenderResult::Text(payload) => { + // Paste back the result in the field + self.clipboard_manager.set_clipboard(&payload); + + std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding + self.keyboard_manager.trigger_paste(&config.paste_shortcut); + }, + _ => { + warn!("Cannot expand passive match") + }, + } + } + } } } + + // Re-allow espanso to interpret actions + self.is_injecting.store(false, Release); } } @@ -333,4 +340,24 @@ 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."); + }, + } + } } \ No newline at end of file diff --git a/src/event/manager.rs b/src/event/manager.rs index b6cc0b1..db920c1 100644 --- a/src/event/manager.rs +++ b/src/event/manager.rs @@ -17,7 +17,7 @@ * along with espanso. If not, see . */ -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, 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, 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), diff --git a/src/event/mod.rs b/src/event/mod.rs index 7d63a7f..53f901d 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -21,10 +21,12 @@ pub(crate) mod manager; use serde::{Serialize, Deserialize}; +#[allow(dead_code)] #[derive(Debug, Clone)] pub enum Event { Action(ActionType), - Key(KeyEvent) + Key(KeyEvent), + System(SystemEvent), } #[derive(Debug, Clone)] @@ -57,7 +59,7 @@ pub enum KeyEvent { Other } -#[warn(non_camel_case_types)] +#[allow(non_camel_case_types)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum KeyModifier { CTRL, @@ -132,6 +134,14 @@ impl KeyModifier { } } +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub enum SystemEvent { + // MacOS specific + SecureInputEnabled(String, String), // AppName, App Path + SecureInputDisabled, +} + // Receivers pub trait KeyEventReceiver { @@ -142,6 +152,10 @@ pub trait ActionEventReceiver { fn on_action_event(&self, e: ActionType); } +pub trait SystemEventReceiver { + fn on_system_event(&self, e: SystemEvent); +} + // TESTS #[cfg(test)] diff --git a/src/extension/clipboard.rs b/src/extension/clipboard.rs index 778bbfe..c8442cd 100644 --- a/src/extension/clipboard.rs +++ b/src/extension/clipboard.rs @@ -17,7 +17,7 @@ * along with espanso. If not, see . */ -use serde_yaml::{Mapping, Value}; +use serde_yaml::{Mapping}; use crate::clipboard::ClipboardManager; pub struct ClipboardExtension { @@ -37,7 +37,7 @@ impl super::Extension for ClipboardExtension { String::from("clipboard") } - fn calculate(&self, params: &Mapping, _: &Vec) -> Option { + fn calculate(&self, _: &Mapping, _: &Vec) -> Option { self.clipboard_manager.get_clipboard() } } \ No newline at end of file diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index 045be16..718b42f 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -17,7 +17,7 @@ * along with espanso. If not, see . */ -use serde::{Serialize, Deserialize, Deserializer}; +use serde::{Serialize, Deserialize}; #[cfg(target_os = "windows")] mod windows; diff --git a/src/main.rs b/src/main.rs index 6218e71..dc788fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,33 +20,32 @@ #[macro_use] extern crate lazy_static; -use std::thread; use std::fs::{File, OpenOptions}; +use std::io::{BufRead, BufReader}; use std::process::exit; -use std::sync::{mpsc, Arc}; +use std::sync::{Arc, mpsc}; +use std::sync::atomic::AtomicBool; use std::sync::mpsc::Receiver; +use std::thread; use std::time::Duration; -use clap::{App, Arg, SubCommand, ArgMatches}; +use clap::{App, Arg, ArgMatches, SubCommand}; use fs2::FileExt; -use log::{info, warn, LevelFilter}; +use log::{info, LevelFilter, warn}; use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger, WriteLogger}; -use crate::config::{ConfigSet, ConfigManager}; +use crate::config::{ConfigManager, ConfigSet}; use crate::config::runtime::RuntimeConfigManager; use crate::engine::Engine; use crate::event::*; use crate::event::manager::{DefaultEventManager, EventManager}; use crate::matcher::scrolling::ScrollingMatcher; +use crate::package::{InstallResult, PackageManager, RemoveResult, UpdateResult}; +use crate::package::default::DefaultPackageManager; +use crate::package::zip::ZipPackageResolver; +use crate::protocol::*; use crate::system::SystemManager; use crate::ui::UIManager; -use crate::protocol::*; -use std::io::{BufReader, BufRead}; -use crate::package::default::DefaultPackageManager; -use crate::package::{PackageManager, InstallResult, UpdateResult, RemoveResult, PackageResolver}; -use std::sync::atomic::AtomicBool; -use crate::package::git::GitPackageResolver; -use crate::package::zip::ZipPackageResolver; mod ui; mod edit; @@ -73,12 +72,12 @@ const LOG_FILE: &str = "espanso.log"; fn main() { let install_subcommand = SubCommand::with_name("install") .about("Install a package. Equivalent to 'espanso package install'") - .arg(Arg::with_name("no-git") - .short("g") - .long("no-git") + .arg(Arg::with_name("external") + .short("e") + .long("external") .required(false) .takes_value(false) - .help("Install packages avoiding the GIT package provider. Try this flag if the default mode is not working.")) + .help("Allow installing packages from non-verified repositories.")) .arg(Arg::with_name("package_name") .help("Package name")); @@ -332,7 +331,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 || { @@ -376,6 +375,7 @@ fn daemon_background(receive_channel: Receiver, config_set: ConfigSet, is receive_channel, vec!(&matcher), vec!(&engine, &matcher), + vec!(&engine), ); info!("espanso is running!"); @@ -445,7 +445,7 @@ fn start_daemon(config_set: ConfigSet) { // If Systemd is not available in the system, espanso should default to unmanaged mode // See issue https://github.com/federico-terzi/espanso/issues/139 - let force_unmanaged = if let Err(status) = status { + let force_unmanaged = if let Err(_) = status { true } else { false @@ -759,11 +759,13 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) { exit(1); }); - let package_resolver: Box = if matches.is_present("no-git") { - println!("Using alternative package provider"); - Box::new(ZipPackageResolver::new()) + let package_resolver= Box::new(ZipPackageResolver::new()); + + let allow_external: bool = if matches.is_present("external") { + println!("Allowing external repositories"); + true }else{ - Box::new(GitPackageResolver::new()) + false }; let mut package_manager = DefaultPackageManager::new_default(Some(package_resolver)); @@ -792,7 +794,7 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) { println!("Using cached package index, run 'espanso package refresh' to update it.") } - let res = package_manager.install_package(package_name); + let res = package_manager.install_package(package_name, allow_external); match res { Ok(install_result) => { @@ -812,6 +814,22 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) { InstallResult::AlreadyInstalled => { eprintln!("{} already installed!", package_name); }, + InstallResult::BlockedExternalPackage(repo_url) => { + eprintln!("Warning: the requested package is hosted on an external repository:"); + eprintln!(); + eprintln!("{}", repo_url); + eprintln!(); + eprintln!("and its contents may not have been verified by espanso."); + eprintln!(); + eprintln!("For your security, espanso blocks packages that are not verified."); + eprintln!("If you want to install the package anyway, you can force espanso"); + eprintln!("to install it with the following command, but please do it only"); + eprintln!("if you trust the source or you verified the contents of the package"); + eprintln!("by checking out the repository listed above."); + eprintln!(); + eprintln!("espanso install {} --external", package_name); + eprintln!(); + } InstallResult::Installed => { println!("{} successfully installed!", package_name); println!(); @@ -975,7 +993,7 @@ fn edit_main(matches: &ArgMatches) { if should_reload { // Load the configuration - let mut config_set = ConfigSet::load_default().unwrap_or_else(|e| { + let config_set = ConfigSet::load_default().unwrap_or_else(|e| { eprintln!("{}", e); eprintln!("Unable to reload espanso due to previous configuration error."); exit(1); @@ -1008,9 +1026,9 @@ fn release_lock(lock_file: File) { lock_file.unlock().unwrap() } -/// Used to make sure all the required dependencies are present before starting espanso. +/// Used to make sure all the required dependencies and conditions are satisfied before starting espanso. fn precheck_guard() { - let satisfied = check::check_dependencies(); + let satisfied = check::check_preconditions(); if !satisfied { println!(); println!("Pre-check was not successful, espanso could not be started."); diff --git a/src/matcher/mod.rs b/src/matcher/mod.rs index 4356c34..5b68433 100644 --- a/src/matcher/mod.rs +++ b/src/matcher/mod.rs @@ -34,6 +34,7 @@ pub struct Match { pub word: bool, pub passive_only: bool, pub propagate_case: bool, + pub force_clipboard: bool, // Automatically calculated from the triggers, used by the matcher to check for correspondences. #[serde(skip_serializing)] @@ -89,7 +90,7 @@ impl<'a> From<&'a AutoMatch> for Match{ if other.propagate_case { // List with first letter capitalized let first_capitalized : Vec = triggers.iter().map(|trigger| { - let mut capitalized = trigger.clone(); + let capitalized = trigger.clone(); let mut v: Vec = capitalized.chars().collect(); v[0] = v[0].to_uppercase().nth(0).unwrap(); v.into_iter().collect() @@ -170,6 +171,7 @@ impl<'a> From<&'a AutoMatch> for Match{ passive_only: other.passive_only, _trigger_sequences: trigger_sequences, propagate_case: other.propagate_case, + force_clipboard: other.force_clipboard, } } } @@ -200,6 +202,9 @@ struct AutoMatch { #[serde(default = "default_propagate_case")] pub propagate_case: bool, + + #[serde(default = "default_force_clipboard")] + pub force_clipboard: bool, } fn default_trigger() -> String {"".to_owned()} @@ -210,6 +215,7 @@ fn default_passive_only() -> bool {false} fn default_replace() -> Option {None} fn default_image_path() -> Option {None} fn default_propagate_case() -> bool {false} +fn default_force_clipboard() -> bool {false} #[derive(Debug, Serialize, Deserialize, Clone)] pub struct MatchVariable { diff --git a/src/matcher/scrolling.rs b/src/matcher/scrolling.rs index b1a24a8..6b43794 100644 --- a/src/matcher/scrolling.rs +++ b/src/matcher/scrolling.rs @@ -18,7 +18,7 @@ */ use crate::matcher::{Match, MatchReceiver, TriggerEntry}; -use std::cell::{RefCell, Ref}; +use std::cell::{RefCell}; use crate::event::{KeyModifier, ActionEventReceiver, ActionType}; use crate::config::ConfigManager; use crate::event::KeyModifier::BACKSPACE; diff --git a/src/package/default.rs b/src/package/default.rs index d3bbd7f..ccf8d32 100644 --- a/src/package/default.rs +++ b/src/package/default.rs @@ -24,14 +24,11 @@ use std::fs::{File, create_dir}; use std::io::{BufReader, BufRead}; use std::time::{SystemTime, UNIX_EPOCH}; use crate::package::UpdateResult::{NotOutdated, Updated}; -use crate::package::InstallResult::{NotFoundInIndex, AlreadyInstalled}; +use crate::package::InstallResult::{NotFoundInIndex, AlreadyInstalled, BlockedExternalPackage}; use std::fs; -use tempfile::TempDir; -use git2::Repository; use regex::Regex; use crate::package::RemoveResult::Removed; use std::collections::HashMap; -use super::git::GitPackageResolver; const DEFAULT_PACKAGE_INDEX_FILE : &str = "package_index.json"; @@ -138,13 +135,31 @@ impl DefaultPackageManager { return None } + let original_repo = if fields.contains_key("package_original_repo") { + fields.get("package_original_repo").unwrap().clone() + }else{ + fields.get("package_repo").unwrap().clone() + }; + + let is_core = if fields.contains_key("is_core") { + match fields.get("is_core").unwrap().clone().as_ref() { + "true" => true, + "false" => false, + _ => false, + } + }else{ + false + }; + let package = Package { name: fields.get("package_name").unwrap().clone(), title: fields.get("package_title").unwrap().clone(), version: fields.get("package_version").unwrap().clone(), repo: fields.get("package_repo").unwrap().clone(), desc: fields.get("package_desc").unwrap().clone(), - author: fields.get("package_author").unwrap().clone() + author: fields.get("package_author").unwrap().clone(), + is_core, + original_repo }; Some(package) @@ -228,11 +243,15 @@ impl super::PackageManager for DefaultPackageManager { None } - fn install_package(&self, name: &str) -> Result> { + fn install_package(&self, name: &str, allow_external: bool) -> Result> { let package = self.get_package(name); match package { Some(package) => { - self.install_package_from_repo(name, &package.repo) + if package.is_core || allow_external { + self.install_package_from_repo(name, &package.repo) + }else{ + Ok(BlockedExternalPackage(package.original_repo)) + } }, None => { Ok(NotFoundInIndex) @@ -315,7 +334,7 @@ mod tests { use crate::package::PackageManager; use std::fs::{create_dir, create_dir_all}; use crate::package::InstallResult::*; - use std::io::Write; + use crate::package::zip::ZipPackageResolver; const OUTDATED_INDEX_CONTENT : &str = include_str!("../res/test/outdated_index.json"); const INDEX_CONTENT_WITHOUT_UPDATE: &str = include_str!("../res/test/index_without_update.json"); @@ -337,7 +356,7 @@ mod tests { let package_manager = DefaultPackageManager::new( package_dir.path().clone().to_path_buf(), data_dir.path().clone().to_path_buf(), - Some(Box::new(GitPackageResolver::new())), + Some(Box::new(ZipPackageResolver::new())), ); TempPackageManager { @@ -349,7 +368,7 @@ mod tests { #[test] fn test_download_index() { - let temp = create_temp_package_manager(|_, _| {}); + create_temp_package_manager(|_, _| {}); let index = DefaultPackageManager::request_index(); assert!(index.is_ok()); @@ -360,7 +379,7 @@ mod tests { fn test_outdated_index() { let temp = create_temp_package_manager(|_, data_dir| { let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); - std::fs::write(index_file, OUTDATED_INDEX_CONTENT); + std::fs::write(index_file, OUTDATED_INDEX_CONTENT).unwrap(); }); assert!(temp.package_manager.is_index_outdated()); @@ -373,7 +392,7 @@ mod tests { let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards"); let current_timestamp = current_time.as_secs(); let new_contents = INDEX_CONTENT_WITHOUT_UPDATE.replace("XXXX", &format!("{}", current_timestamp)); - std::fs::write(index_file, new_contents); + std::fs::write(index_file, new_contents).unwrap(); }); assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::NotOutdated); @@ -386,7 +405,7 @@ mod tests { let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards"); let current_timestamp = current_time.as_secs(); let new_contents = INDEX_CONTENT_WITHOUT_UPDATE.replace("XXXX", &format!("{}", current_timestamp)); - std::fs::write(index_file, new_contents); + std::fs::write(index_file, new_contents).unwrap(); }); assert_eq!(temp.package_manager.update_index(true).unwrap(), UpdateResult::Updated); @@ -396,7 +415,7 @@ mod tests { fn test_outdated_index_should_be_updated() { let mut temp = create_temp_package_manager(|_, data_dir| { let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); - std::fs::write(index_file, OUTDATED_INDEX_CONTENT); + std::fs::write(index_file, OUTDATED_INDEX_CONTENT).unwrap(); }); assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::Updated); @@ -412,9 +431,9 @@ mod tests { #[test] fn test_get_package_should_be_found() { - let mut temp = create_temp_package_manager(|_, data_dir| { + let temp = create_temp_package_manager(|_, data_dir| { let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); - std::fs::write(index_file, GET_PACKAGE_INDEX); + std::fs::write(index_file, GET_PACKAGE_INDEX).unwrap(); }); assert_eq!(temp.package_manager.get_package("italian-accents").unwrap().title, "Italian Accents"); @@ -422,9 +441,9 @@ mod tests { #[test] fn test_get_package_should_not_be_found() { - let mut temp = create_temp_package_manager(|_, data_dir| { + let temp = create_temp_package_manager(|_, data_dir| { let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); - std::fs::write(index_file, GET_PACKAGE_INDEX); + std::fs::write(index_file, GET_PACKAGE_INDEX).unwrap(); }); assert!(temp.package_manager.get_package("not-existing").is_none()); @@ -432,10 +451,10 @@ mod tests { #[test] fn test_list_local_packages_names() { - let mut temp = create_temp_package_manager(|package_dir, _| { - create_dir(package_dir.join("package-1")); - create_dir(package_dir.join("package2")); - std::fs::write(package_dir.join("dummyfile.txt"), "test"); + let temp = create_temp_package_manager(|package_dir, _| { + create_dir(package_dir.join("package-1")).unwrap(); + create_dir(package_dir.join("package2")).unwrap(); + std::fs::write(package_dir.join("dummyfile.txt"), "test").unwrap(); }); let packages = temp.package_manager.list_local_packages_names(); @@ -446,33 +465,33 @@ mod tests { #[test] fn test_install_package_not_found() { - let mut temp = create_temp_package_manager(|package_dir, data_dir| { + let temp = create_temp_package_manager(|_, data_dir| { let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); - std::fs::write(index_file, INSTALL_PACKAGE_INDEX); + std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("doesnotexist").unwrap(), NotFoundInIndex); + assert_eq!(temp.package_manager.install_package("doesnotexist", false).unwrap(), NotFoundInIndex); } #[test] fn test_install_package_already_installed() { - let mut temp = create_temp_package_manager(|package_dir, data_dir| { - create_dir(package_dir.join("italian-accents")); + let temp = create_temp_package_manager(|package_dir, data_dir| { + create_dir(package_dir.join("italian-accents")).unwrap(); let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); - std::fs::write(index_file, INSTALL_PACKAGE_INDEX); + std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("italian-accents").unwrap(), AlreadyInstalled); + assert_eq!(temp.package_manager.install_package("italian-accents", false).unwrap(), AlreadyInstalled); } #[test] fn test_install_package() { - let mut temp = create_temp_package_manager(|_, data_dir| { + let temp = create_temp_package_manager(|_, data_dir| { let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); - std::fs::write(index_file, INSTALL_PACKAGE_INDEX); + std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("dummy-package").unwrap(), Installed); + assert_eq!(temp.package_manager.install_package("dummy-package", false).unwrap(), Installed); assert!(temp.package_dir.path().join("dummy-package").exists()); assert!(temp.package_dir.path().join("dummy-package/README.md").exists()); assert!(temp.package_dir.path().join("dummy-package/package.yml").exists()); @@ -480,52 +499,52 @@ mod tests { #[test] fn test_install_package_does_not_exist_in_repo() { - let mut temp = create_temp_package_manager(|_, data_dir| { + let temp = create_temp_package_manager(|_, data_dir| { let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); - std::fs::write(index_file, INSTALL_PACKAGE_INDEX); + std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("not-existing").unwrap(), NotFoundInRepo); + assert_eq!(temp.package_manager.install_package("not-existing", false).unwrap(), NotFoundInRepo); } #[test] fn test_install_package_missing_version() { - let mut temp = create_temp_package_manager(|_, data_dir| { + let temp = create_temp_package_manager(|_, data_dir| { let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); - std::fs::write(index_file, INSTALL_PACKAGE_INDEX); + std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("dummy-package2").unwrap(), MissingPackageVersion); + assert_eq!(temp.package_manager.install_package("dummy-package2", false).unwrap(), MissingPackageVersion); } #[test] fn test_install_package_missing_readme_unable_to_parse_package_info() { - let mut temp = create_temp_package_manager(|_, data_dir| { + let temp = create_temp_package_manager(|_, data_dir| { let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); - std::fs::write(index_file, INSTALL_PACKAGE_INDEX); + std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("dummy-package3").unwrap(), UnableToParsePackageInfo); + assert_eq!(temp.package_manager.install_package("dummy-package3", false).unwrap(), UnableToParsePackageInfo); } #[test] fn test_install_package_bad_readme_unable_to_parse_package_info() { - let mut temp = create_temp_package_manager(|_, data_dir| { + let temp = create_temp_package_manager(|_, data_dir| { let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); - std::fs::write(index_file, INSTALL_PACKAGE_INDEX); + std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("dummy-package4").unwrap(), UnableToParsePackageInfo); + assert_eq!(temp.package_manager.install_package("dummy-package4", false).unwrap(), UnableToParsePackageInfo); } #[test] fn test_list_local_packages() { - let mut temp = create_temp_package_manager(|_, data_dir| { + let temp = create_temp_package_manager(|_, data_dir| { let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); - std::fs::write(index_file, INSTALL_PACKAGE_INDEX); + std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("dummy-package").unwrap(), Installed); + assert_eq!(temp.package_manager.install_package("dummy-package", false).unwrap(), Installed); assert!(temp.package_dir.path().join("dummy-package").exists()); assert!(temp.package_dir.path().join("dummy-package/README.md").exists()); assert!(temp.package_dir.path().join("dummy-package/package.yml").exists()); @@ -537,11 +556,11 @@ mod tests { #[test] fn test_remove_package() { - let mut temp = create_temp_package_manager(|package_dir, _| { + let temp = create_temp_package_manager(|package_dir, _| { let dummy_package_dir = package_dir.join("dummy-package"); - create_dir_all(&dummy_package_dir); - std::fs::write(dummy_package_dir.join("README.md"), "readme"); - std::fs::write(dummy_package_dir.join("package.yml"), "name: package"); + create_dir_all(&dummy_package_dir).unwrap(); + std::fs::write(dummy_package_dir.join("README.md"), "readme").unwrap(); + std::fs::write(dummy_package_dir.join("package.yml"), "name: package").unwrap(); }); assert!(temp.package_dir.path().join("dummy-package").exists()); @@ -555,7 +574,7 @@ mod tests { #[test] fn test_remove_package_not_found() { - let mut temp = create_temp_package_manager(|_, _| {}); + let temp = create_temp_package_manager(|_, _| {}); assert_eq!(temp.package_manager.remove_package("not-existing").unwrap(), RemoveResult::NotFound); } @@ -571,8 +590,9 @@ mod tests { package_version: "0.1.0" package_author: "Federico Terzi" package_repo: "https://github.com/federico-terzi/espanso-hub-core" + is_core: true --- - "###); + "###).unwrap(); let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap(); @@ -582,7 +602,9 @@ mod tests { version: "0.1.0".to_string(), repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(), desc: "Include Italian accents substitutions to espanso.".to_string(), - author: "Federico Terzi".to_string() + author: "Federico Terzi".to_string(), + original_repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(), + is_core: true, }; assert_eq!(package, target_package); @@ -599,9 +621,10 @@ mod tests { package_version:"0.1.0" package_author:Federico Terzi package_repo: "https://github.com/federico-terzi/espanso-hub-core" + is_core: true --- Readme text - "###); + "###).unwrap(); let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap(); @@ -611,7 +634,9 @@ mod tests { version: "0.1.0".to_string(), repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(), desc: "Include Italian accents substitutions to espanso.".to_string(), - author: "Federico Terzi".to_string() + author: "Federico Terzi".to_string(), + original_repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(), + is_core: true, }; assert_eq!(package, target_package); diff --git a/src/package/mod.rs b/src/package/mod.rs index 78ecaf1..8942c96 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -17,7 +17,6 @@ * along with espanso. If not, see . */ -pub(crate) mod git; pub(crate) mod zip; pub(crate) mod default; @@ -31,7 +30,7 @@ pub trait PackageManager { fn get_package(&self, name: &str) -> Option; - fn install_package(&self, name: &str) -> Result>; + fn install_package(&self, name: &str, allow_external: bool) -> Result>; fn install_package_from_repo(&self, name: &str, repo_url: &str) -> Result>; fn remove_package(&self, name: &str) -> Result>; @@ -50,9 +49,17 @@ pub struct Package { pub version: String, pub repo: String, pub desc: String, - pub author: String + pub author: String, + + #[serde(default = "default_is_core")] + pub is_core: bool, + #[serde(default = "default_original_repo")] + pub original_repo: String, } +fn default_is_core() -> bool {false} +fn default_original_repo() -> String {"".to_owned()} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct PackageIndex { #[serde(rename = "lastUpdate")] @@ -75,7 +82,8 @@ pub enum InstallResult { UnableToParsePackageInfo, MissingPackageVersion, AlreadyInstalled, - Installed + Installed, + BlockedExternalPackage(String) } #[derive(Clone, Debug, PartialEq)] diff --git a/src/package/zip.rs b/src/package/zip.rs index 169e78d..e12b26d 100644 --- a/src/package/zip.rs +++ b/src/package/zip.rs @@ -1,9 +1,7 @@ use tempfile::TempDir; use std::error::Error; -use super::PackageResolver; -use std::io::{Cursor, copy, Read}; +use std::io::{Cursor, copy}; use std::{fs, io}; -use std::fs::File; use log::debug; pub struct ZipPackageResolver; @@ -33,7 +31,7 @@ impl super::PackageResolver for ZipPackageResolver { // Find the root folder name let mut root_folder = { - let mut root_folder = archive.by_index(0).unwrap(); + let root_folder = archive.by_index(0).unwrap(); let root_folder = root_folder.sanitized_name(); root_folder.to_str().unwrap().to_owned() }; @@ -77,7 +75,7 @@ impl super::PackageResolver for ZipPackageResolver { #[cfg(test)] mod tests { use super::*; - use tempfile::{TempDir, NamedTempFile}; + use super::super::PackageResolver; #[test] fn test_clone_temp_repository() { diff --git a/src/render/default.rs b/src/render/default.rs index dca2c70..a9da347 100644 --- a/src/render/default.rs +++ b/src/render/default.rs @@ -17,8 +17,7 @@ * along with espanso. If not, see . */ -use serde_yaml::{Mapping, Value}; -use std::path::PathBuf; +use serde_yaml::{Value}; use std::collections::HashMap; use regex::{Regex, Captures}; use log::{warn, error}; @@ -50,7 +49,7 @@ impl DefaultRenderer { // Compile the regexes let passive_match_regex = Regex::new(&config.passive_match_regex) .unwrap_or_else(|e| { - panic!("Invalid passive match regex"); + panic!("Invalid passive match regex: {:?}", e); }); DefaultRenderer{ diff --git a/src/res/test/get_package_index.json b/src/res/test/get_package_index.json index 7512f40..428ed4e 100644 --- a/src/res/test/get_package_index.json +++ b/src/res/test/get_package_index.json @@ -8,7 +8,8 @@ "version": "0.1.0", "repo": "https://github.com/federico-terzi/espanso-hub-core", "desc": "A package to include some basic emojis in espanso.", - "author": "Federico Terzi" + "author": "Federico Terzi", + "is_core": true }, @@ -19,7 +20,8 @@ "version": "0.1.0", "repo": "https://github.com/federico-terzi/espanso-hub-core", "desc": "Include Italian accents substitutions to espanso.", - "author": "Federico Terzi" + "author": "Federico Terzi", + "is_core": true } diff --git a/src/res/test/index_without_update.json b/src/res/test/index_without_update.json index 4746703..7d83c46 100644 --- a/src/res/test/index_without_update.json +++ b/src/res/test/index_without_update.json @@ -8,7 +8,8 @@ "version": "0.1.0", "repo": "https://github.com/federico-terzi/espanso-hub-core", "desc": "A package to include some basic emojis in espanso.", - "author": "Federico Terzi" + "author": "Federico Terzi", + "is_core": true }, @@ -19,7 +20,8 @@ "version": "0.1.0", "repo": "https://github.com/federico-terzi/espanso-hub-core", "desc": "Include Italian accents substitutions to espanso.", - "author": "Federico Terzi" + "author": "Federico Terzi", + "is_core": true } diff --git a/src/res/test/install_package_index.json b/src/res/test/install_package_index.json index c9b1e62..63222f7 100644 --- a/src/res/test/install_package_index.json +++ b/src/res/test/install_package_index.json @@ -8,8 +8,8 @@ "version": "0.1.0", "repo": "https://github.com/federico-terzi/espanso-hub-core", "desc": "Dummy package", - "author": "Federico Terzi" - + "author": "Federico Terzi", + "is_core": true }, { @@ -18,7 +18,8 @@ "version": "9.9.9", "repo": "https://github.com/federico-terzi/espanso-hub-core", "desc": "Dummy package", - "author": "Federico Terzi" + "author": "Federico Terzi", + "is_core": true }, @@ -28,7 +29,8 @@ "version": "0.1.0", "repo": "https://github.com/federico-terzi/espanso-hub-core", "desc": "Dummy package", - "author": "Federico Terzi" + "author": "Federico Terzi", + "is_core": true }, @@ -38,7 +40,8 @@ "version": "0.1.0", "repo": "https://github.com/federico-terzi/espanso-hub-core", "desc": "Dummy package", - "author": "Federico Terzi" + "author": "Federico Terzi", + "is_core": true }, @@ -49,7 +52,8 @@ "version": "0.1.0", "repo": "https://github.com/federico-terzi/espanso-hub-core", "desc": "Include Italian accents substitutions to espanso.", - "author": "Federico Terzi" + "author": "Federico Terzi", + "is_core": true }, @@ -59,7 +63,8 @@ "version": "0.1.0", "repo": "https://github.com/federico-terzi/espanso-hub-core", "desc": "Package that does not exist in the repo", - "author": "Federico Terzi" + "author": "Federico Terzi", + "is_core": true } ]} \ No newline at end of file diff --git a/src/res/test/outdated_index.json b/src/res/test/outdated_index.json index 7512f40..428ed4e 100644 --- a/src/res/test/outdated_index.json +++ b/src/res/test/outdated_index.json @@ -8,7 +8,8 @@ "version": "0.1.0", "repo": "https://github.com/federico-terzi/espanso-hub-core", "desc": "A package to include some basic emojis in espanso.", - "author": "Federico Terzi" + "author": "Federico Terzi", + "is_core": true }, @@ -19,7 +20,8 @@ "version": "0.1.0", "repo": "https://github.com/federico-terzi/espanso-hub-core", "desc": "Include Italian accents substitutions to espanso.", - "author": "Federico Terzi" + "author": "Federico Terzi", + "is_core": true } diff --git a/src/sysdaemon.rs b/src/sysdaemon.rs index 4bbf06c..706af9d 100644 --- a/src/sysdaemon.rs +++ b/src/sysdaemon.rs @@ -108,9 +108,9 @@ const LINUX_SERVICE_CONTENT : &str = include_str!("res/linux/systemd.service"); const LINUX_SERVICE_FILENAME : &str = "espanso.service"; #[cfg(target_os = "linux")] -pub fn register(config_set: ConfigSet) { +pub fn register(_: ConfigSet) { use std::fs::create_dir_all; - use std::process::{Command, ExitStatus}; + use std::process::{Command}; // Check if espanso service is already registered let res = Command::new("systemctl") @@ -191,7 +191,7 @@ pub enum VerifyResult { #[cfg(target_os = "linux")] pub fn verify() -> VerifyResult { use regex::Regex; - use std::process::{Command, ExitStatus}; + use std::process::{Command}; // Check if espanso service is already registered let res = Command::new("systemctl") @@ -206,7 +206,7 @@ pub fn verify() -> VerifyResult { } lazy_static! { - static ref ExecPathRegex: Regex = Regex::new("ExecStart=(?P.*?)\\s").unwrap(); + static ref EXEC_PATH_REGEX: Regex = Regex::new("ExecStart=(?P.*?)\\s").unwrap(); } // Check if the currently registered path is valid @@ -217,7 +217,7 @@ pub fn verify() -> VerifyResult { let output = String::from_utf8_lossy(res.stdout.as_slice()); let output = output.trim(); if res.status.success() { - let caps = ExecPathRegex.captures(output).unwrap(); + let caps = EXEC_PATH_REGEX.captures(output).unwrap(); let path = caps.get(1).map_or("", |m| m.as_str()); let espanso_path = std::env::current_exe().expect("Could not get espanso executable path"); @@ -231,13 +231,13 @@ pub fn verify() -> VerifyResult { } #[cfg(target_os = "linux")] -pub fn unregister(config_set: ConfigSet) { - use std::process::{Command, ExitStatus}; +pub fn unregister(_: ConfigSet) { + use std::process::{Command}; // Disable the service first - let res = Command::new("systemctl") + Command::new("systemctl") .args(&["--user", "disable", "espanso"]) - .status(); + .status().expect("Unable to invoke systemctl"); // Then delete the espanso.service entry let config_dir = dirs::config_dir().expect("Could not get configuration directory"); diff --git a/src/system/macos.rs b/src/system/macos.rs index fc867d1..d1b401e 100644 --- a/src/system/macos.rs +++ b/src/system/macos.rs @@ -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 { @@ -74,4 +74,66 @@ 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 { + 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 regex::Regex; + + lazy_static! { + static ref APP_REGEX: Regex = Regex::new("/([^/]+).app/").unwrap(); + }; + + unsafe { + let pid = MacSystemManager::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 = 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 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() + }; + + Some((app_name, process)) + }else{ + None + } + }else{ + None + } + }else{ + None + } + }else{ + None + } + } + } } \ No newline at end of file diff --git a/src/system/mod.rs b/src/system/mod.rs index fadcf5a..a0bd25a 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -24,7 +24,7 @@ mod windows; mod linux; #[cfg(target_os = "macos")] -mod macos; +pub mod macos; pub trait SystemManager { fn get_current_window_title(&self) -> Option; diff --git a/src/ui/linux.rs b/src/ui/linux.rs index f45d65a..814e259 100644 --- a/src/ui/linux.rs +++ b/src/ui/linux.rs @@ -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); diff --git a/src/ui/macos.rs b/src/ui/macos.rs index 8bcf791..ebad9d0 100644 --- a/src/ui/macos.rs +++ b/src/ui/macos.rs @@ -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 { diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 1b096c6..81798b7 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -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); fn cleanup(&self); } diff --git a/src/ui/windows.rs b/src/ui/windows.rs index 4024939..f6df686 100644 --- a/src/ui/windows.rs +++ b/src/ui/windows.rs @@ -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(); diff --git a/src/utils.rs b/src/utils.rs index ffd9106..259dc80 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -51,14 +51,14 @@ mod tests { let dest_tmp_dir = TempDir::new().expect("Error creating temp directory"); let source_dir = source_tmp_dir.path().join("source"); - create_dir(&source_dir); - std::fs::write(source_dir.join("file1.txt"), "file1"); - std::fs::write(source_dir.join("file2.txt"), "file2"); + create_dir(&source_dir).unwrap(); + std::fs::write(source_dir.join("file1.txt"), "file1").unwrap(); + std::fs::write(source_dir.join("file2.txt"), "file2").unwrap(); let target_dir = dest_tmp_dir.path().join("source"); - create_dir(&target_dir); + create_dir(&target_dir).unwrap(); - copy_dir(&source_dir, &target_dir); + copy_dir(&source_dir, &target_dir).unwrap(); assert!(dest_tmp_dir.path().join("source").exists()); assert!(dest_tmp_dir.path().join("source/file1.txt").exists()); @@ -71,17 +71,17 @@ mod tests { let dest_tmp_dir = TempDir::new().expect("Error creating temp directory"); let source_dir = source_tmp_dir.path().join("source"); - create_dir(&source_dir); - std::fs::write(source_dir.join("file1.txt"), "file1"); - std::fs::write(source_dir.join("file2.txt"), "file2"); + create_dir(&source_dir).unwrap(); + std::fs::write(source_dir.join("file1.txt"), "file1").unwrap(); + std::fs::write(source_dir.join("file2.txt"), "file2").unwrap(); let nested_dir = source_dir.join("nested"); - create_dir(&nested_dir); - std::fs::write(nested_dir.join("nestedfile.txt"), "nestedfile1"); + create_dir(&nested_dir).unwrap(); + std::fs::write(nested_dir.join("nestedfile.txt"), "nestedfile1").unwrap(); let target_dir = dest_tmp_dir.path().join("source"); - create_dir(&target_dir); + create_dir(&target_dir).unwrap(); - copy_dir(&source_dir, &target_dir); + copy_dir(&source_dir, &target_dir).unwrap(); assert!(dest_tmp_dir.path().join("source").exists()); assert!(dest_tmp_dir.path().join("source/file1.txt").exists());