diff --git a/Cargo.lock b/Cargo.lock index 70c31d9..b2b3835 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -379,6 +379,7 @@ dependencies = [ "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)", "log-panics 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.20 (registry+https://github.com/rust-lang/crates.io-index)", @@ -412,6 +413,17 @@ dependencies = [ "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "filetime" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "flate2" version = "1.0.11" @@ -449,6 +461,23 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fsevent" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent-sys 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fsevent-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -601,6 +630,24 @@ name = "indexmap" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "inotify" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "inotify-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "iovec" version = "0.1.2" @@ -629,6 +676,11 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lazycell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" version = "0.2.62" @@ -722,6 +774,17 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "miow" version = "0.2.1" @@ -765,6 +828,23 @@ name = "nodrop" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "notify" +version = "4.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent-sys 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "inotify 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-extras 2.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-integer" version = "0.1.41" @@ -1762,11 +1842,14 @@ dependencies = [ "checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum filetime 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f59efc38004c988e4201d11d263b8171f49a2e7ec0bdbb71773433f271504a5e" "checksum flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "2adaffba6388640136149e18ed080b77a78611c1e1d6de75aedcdf78df5d4682" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" "checksum fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +"checksum fsevent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" +"checksum fsevent-sys 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" @@ -1782,10 +1865,13 @@ dependencies = [ "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" "checksum indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a61202fbe46c4a951e9404a720a0180bcf3212c750d735cb5c4ba4dc551299f3" +"checksum inotify 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24e40d6fd5d64e2082e0c796495c8ef5ad667a96d03e5aaa0becfd9d47bcbfb8" +"checksum inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" "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 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 lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "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" @@ -1798,10 +1884,12 @@ dependencies = [ "checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599" "checksum miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7108aff85b876d06f22503dcce091e29f76733b2bfdd91eebce81f5e68203a10" "checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" +"checksum mio-extras 2.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" diff --git a/Cargo.toml b/Cargo.toml index 8b28d94..4360002 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ tempfile = "3.1.0" dialoguer = "0.4.0" rand = "0.7.2" zip = "0.5.3" +notify = "4.0.13" [target.'cfg(unix)'.dependencies] libc = "0.2.62" diff --git a/native/libwinbridge/bridge.cpp b/native/libwinbridge/bridge.cpp index 75b0597..fcc4d9e 100644 --- a/native/libwinbridge/bridge.cpp +++ b/native/libwinbridge/bridge.cpp @@ -710,6 +710,35 @@ int32_t start_daemon_process() { return 1; } + +int32_t start_process(wchar_t * _cmd) { + wchar_t cmd[MAX_PATH]; + swprintf(cmd, MAX_PATH, _cmd); + + STARTUPINFO si = { sizeof(si) }; + PROCESS_INFORMATION pi; + + // Documentation: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw + BOOL res = CreateProcess( + NULL, + cmd, + NULL, + NULL, + FALSE, + DETACHED_PROCESS, + NULL, + NULL, + &si, + &pi + ); + + if (!res) { + return -1; + } + + return 1; +} + // CLIPBOARD int32_t set_clipboard(wchar_t *text) { diff --git a/native/libwinbridge/bridge.h b/native/libwinbridge/bridge.h index e27c872..8b7984b 100644 --- a/native/libwinbridge/bridge.h +++ b/native/libwinbridge/bridge.h @@ -164,4 +164,8 @@ extern "C" int32_t set_clipboard(wchar_t * text); */ extern "C" int32_t set_clipboard_image(wchar_t * path); +// PROCESSES + +extern "C" int32_t start_process(wchar_t * cmd); + #endif //ESPANSO_BRIDGE_H \ No newline at end of file diff --git a/src/bridge/windows.rs b/src/bridge/windows.rs index 59ea5a7..70c71a4 100644 --- a/src/bridge/windows.rs +++ b/src/bridge/windows.rs @@ -60,4 +60,8 @@ extern { pub fn delete_string(count: i32, delay: i32); pub fn trigger_paste(); pub fn trigger_copy(); + + // PROCESSES + + pub fn start_process(cmd: *const u16) -> i32; } \ No newline at end of file diff --git a/src/config/mod.rs b/src/config/mod.rs index 1c18e15..2cc5d39 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -69,6 +69,7 @@ fn default_exclude_default_entries() -> bool {false} fn default_secure_input_watcher_enabled() -> bool {true} fn default_secure_input_notification() -> bool {true} fn default_show_notifications() -> bool {true} +fn default_auto_restart() -> bool {false} fn default_show_icon() -> bool {true} fn default_fast_inject() -> bool {true} fn default_secure_input_watcher_interval() -> i32 {5000} @@ -176,6 +177,9 @@ pub struct Configs { #[serde(default = "default_inject_delay")] pub inject_delay: i32, + #[serde(default = "default_auto_restart")] + pub auto_restart: bool, + #[serde(default = "default_matches")] pub matches: Vec, diff --git a/src/main.rs b/src/main.rs index 8365c2d..a8d029e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,7 @@ use std::sync::atomic::AtomicBool; use std::sync::mpsc::Receiver; use std::thread; use std::time::Duration; +use std::process::{Command, Stdio}; use clap::{App, Arg, ArgMatches, SubCommand}; use fs2::FileExt; @@ -59,6 +60,7 @@ mod render; mod system; mod context; mod matcher; +mod process; mod package; mod keyboard; mod protocol; @@ -161,6 +163,8 @@ fn main() { .subcommand(SubCommand::with_name("refresh") .about("Update espanso package index")) ) + .subcommand(SubCommand::with_name("watch") + .about("Wait until one of the config files is changed, then restart espanso.")) .subcommand(install_subcommand) .subcommand(uninstall_subcommand); @@ -275,6 +279,11 @@ fn main() { } } + if matches.subcommand_matches("watch").is_some() { + watch_main(config_set); + return; + } + // Defaults help print clap_instance.print_long_help().expect("Unable to print help"); println!(); @@ -345,6 +354,12 @@ fn daemon_main(config_set: ConfigSet) { daemon_background(receive_channel, config_set_copy, is_injecting); }).expect("Unable to spawn daemon background thread"); + if config_set.default.auto_restart { + info!("starting auto-restart daemon..."); + let espanso_path = std::env::current_exe().expect("unable to obtain espanso path location"); + crate::process::spawn_process(&espanso_path.to_string_lossy().to_string(), &vec!("watch".to_owned())); + } + let ipc_server = protocol::get_ipc_server(config_set, send_channel.clone()); ipc_server.start(); @@ -1019,9 +1034,80 @@ fn edit_main(matches: &ArgMatches) { } } +fn watch_main(_: ConfigSet) { + // Make sure only one watch is running + let lock_file = acquire_custom_lock("watcher.lock"); + if lock_file.is_none() { + eprintln!("Another watcher is already running, terminating..."); + std::process::exit(2); + } + + use notify::{RecommendedWatcher, Watcher, RecursiveMode, DebouncedEvent}; + use std::sync::mpsc::channel; + use std::time::Duration; + + // Create a channel to receive the events. + let (tx, rx) = channel(); + + let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(1)).expect("unable to create file watcher"); + + let config_path = crate::context::get_config_dir(); + watcher.watch(&config_path, RecursiveMode::Recursive).expect("unable to start watcher"); + + println!("Watching for changes in path: {:?}", config_path); + + loop { + let should_reload = match rx.recv() { + Ok(event) => { + let path = match event { + DebouncedEvent::Create(path) => Some(path), + DebouncedEvent::Write(path) => Some(path), + DebouncedEvent::Remove(path) => Some(path), + DebouncedEvent::Rename(_, path) => Some(path), + _ => None, + }; + + if let Some(path) = path { + if path.extension().unwrap_or_default() == "yml" { // Only load yml files + true + }else{ + false + } + }else{ + false + } + }, + Err(e) => { + eprintln!("error while watching files: {:?}", e); + false + } + }; + + if should_reload { + println!("change detected, restarting espanso"); + + let mut config_set = ConfigSet::load_default(); + + match config_set { + Ok(config_set) => { + restart_main(config_set); + std::process::exit(0); + }, + Err(error) => { + // TODO: send notification to user + } + } + } + } +} + fn acquire_lock() -> Option { + acquire_custom_lock("espanso.lock") +} + +fn acquire_custom_lock(name: &str) -> Option { let espanso_dir = context::get_data_dir(); - let lock_file_path = espanso_dir.join("espanso.lock"); + let lock_file_path = espanso_dir.join(name); let file = OpenOptions::new() .read(true) .write(true) diff --git a/src/process.rs b/src/process.rs new file mode 100644 index 0000000..ce8191d --- /dev/null +++ b/src/process.rs @@ -0,0 +1,48 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2020 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use log::warn; +use widestring::WideCString; + +#[cfg(target_os = "windows")] +pub fn spawn_process(cmd: &str, args: &Vec) { + let quoted_args: Vec = args.iter().map(|arg| { + format!("\"{}\"", arg) + }).collect(); + let quoted_args = quoted_args.join(" "); + let final_cmd = format!("\"{}\" {}", cmd, quoted_args); + unsafe { + let cmd_wstr = WideCString::from_str(&final_cmd); + if let Ok(string) = cmd_wstr { + let res = crate::bridge::windows::start_process(string.as_ptr()); + if res < 0 { + warn!("unable to start process: {}", final_cmd); + } + }else{ + warn!("unable to convert process string into wide format") + } + } +} + +#[cfg(not(target_os = "windows"))] +pub fn spawn_process(cmd: &str, args: &Vec) { + use std::process::{Command, Stdio}; + + Command::new(cmd).args(args).stdout(Stdio::null()).spawn(); +}