diff --git a/espanso/src/cli/daemon/keyboard_layout_watcher.rs b/espanso/src/cli/daemon/keyboard_layout_watcher.rs
new file mode 100644
index 0000000..9224352
--- /dev/null
+++ b/espanso/src/cli/daemon/keyboard_layout_watcher.rs
@@ -0,0 +1,72 @@
+/*
+ * This file is part of espanso.
+ *
+ * Copyright (C) 2019-2021 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 anyhow::Result;
+use crossbeam::channel::Sender;
+use log::{error, debug};
+
+const WATCHER_INTERVAL: u64 = 1000;
+
+pub fn initialize_and_spawn(watcher_notify: Sender<()>) -> Result<()> {
+ // On Windows and macOS we don't need to restart espanso when the layout changes
+ if !cfg!(target_os = "linux") {
+ return Ok(());
+ }
+
+ std::thread::Builder::new()
+ .name("keyboard_layout_watcher".to_string())
+ .spawn(move || {
+ watcher_main(&watcher_notify);
+ })?;
+
+ Ok(())
+}
+
+fn watcher_main(watcher_notify: &Sender<()>) {
+ let layout = espanso_detect::get_active_layout();
+
+ if layout.is_none() {
+ error!("unable to start keyboard layout watcher, as espanso couldn't determine active layout.");
+ return;
+ }
+
+ let mut layout = layout.expect("missing active layout");
+
+ loop {
+ std::thread::sleep(std::time::Duration::from_millis(WATCHER_INTERVAL));
+
+ if let Some(current_layout) = espanso_detect::get_active_layout() {
+ if current_layout != layout {
+ debug!(
+ "detected keyboard layout change: from {} to {}",
+ layout, current_layout
+ );
+
+ if let Err(error) = watcher_notify.send(()) {
+ error!("unable to send keyboard layout changed event: {}", error);
+ }
+
+ layout = current_layout;
+ }
+ } else {
+ error!("keyboard layout watcher couldn't determine active layout");
+ break;
+ }
+ }
+}
diff --git a/espanso/src/cli/daemon/mod.rs b/espanso/src/cli/daemon/mod.rs
index e212a45..7355a69 100644
--- a/espanso/src/cli/daemon/mod.rs
+++ b/espanso/src/cli/daemon/mod.rs
@@ -24,10 +24,12 @@ use crossbeam::{
select,
};
use espanso_ipc::IPCClient;
+use espanso_path::Paths;
use log::{error, info, warn};
use crate::{
cli::util::CommandExt,
+ common_flags::{*},
exit_code::{
DAEMON_ALREADY_RUNNING, DAEMON_FATAL_CONFIG_ERROR, DAEMON_GENERAL_ERROR,
DAEMON_LEGACY_ALREADY_RUNNING, DAEMON_SUCCESS, WORKER_EXIT_ALL_PROCESSES, WORKER_RESTART,
@@ -41,6 +43,7 @@ use crate::{
use super::{CliModule, CliModuleArgs, PathsOverrides};
mod ipc;
+mod keyboard_layout_watcher;
mod troubleshoot;
mod watcher;
@@ -91,6 +94,10 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
watcher::initialize_and_spawn(&paths.config, watcher_notify)
.expect("unable to initialize config watcher thread");
+ let (keyboard_layout_watcher_notify, keyboard_layout_watcher_signal) = unbounded::<()>();
+ keyboard_layout_watcher::initialize_and_spawn(keyboard_layout_watcher_notify)
+ .expect("unable to initialize keyboard layout watcher thread");
+
let config_store =
match troubleshoot::load_config_or_troubleshoot_until_config_is_correct_or_abort(
&paths,
@@ -116,13 +123,10 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
// TODO: register signals to terminate the worker if the daemon terminates
- let mut worker_run_count = 0;
-
spawn_worker(
&paths_overrides,
exit_notify.clone(),
- &mut worker_run_count,
- false,
+ None
);
ipc::initialize_and_spawn(&paths.runtime, exit_notify.clone())
@@ -156,40 +160,13 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
};
if should_restart_worker {
- match create_ipc_client_to_worker(&paths.runtime) {
- Ok(mut worker_ipc) => {
- if let Err(err) = worker_ipc.send_async(IPCEvent::Exit) {
- error!(
- "unable to send termination signal to worker process: {}",
- err
- );
- }
- }
- Err(err) => {
- error!("could not establish IPC connection with worker: {}", err);
- }
- }
-
- // Wait until the worker process has terminated
- let start = Instant::now();
- let mut has_timed_out = true;
- while start.elapsed() < std::time::Duration::from_secs(30) {
- let lock_file = acquire_worker_lock(&paths.runtime);
- if lock_file.is_some() {
- has_timed_out = false;
- break;
- }
-
- std::thread::sleep(std::time::Duration::from_millis(100));
- }
-
- if !has_timed_out {
- spawn_worker(&paths_overrides, exit_notify.clone(), &mut worker_run_count, false);
- } else {
- error!("could not restart worker, as the exit process has timed out");
- }
+ restart_worker(&paths, &paths_overrides, exit_notify.clone(), Some(WORKER_START_REASON_CONFIG_CHANGED.to_string()));
}
}
+ recv(keyboard_layout_watcher_signal) -> _ => {
+ info!("keyboard layout change detected, restarting worker...");
+ restart_worker(&paths, &paths_overrides, exit_notify.clone(), Some(WORKER_START_REASON_KEYBOARD_LAYOUT_CHANGED.to_string()));
+ }
recv(exit_signal) -> code => {
match code {
Ok(code) => {
@@ -200,7 +177,7 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
}
WORKER_RESTART => {
info!("worker requested a restart, spawning a new one...");
- spawn_worker(&paths_overrides, exit_notify.clone(), &mut worker_run_count, true);
+ spawn_worker(&paths_overrides, exit_notify.clone(), Some(WORKER_START_REASON_MANUAL.to_string()));
}
_ => {
error!("received unexpected exit code from worker {}, exiting", code);
@@ -260,8 +237,7 @@ fn terminate_worker_if_already_running(runtime_dir: &Path) {
fn spawn_worker(
paths_overrides: &PathsOverrides,
exit_notify: Sender,
- worker_run_count: &mut i32,
- manual_start: bool,
+ start_reason: Option,
) {
info!("spawning the worker process...");
@@ -270,23 +246,19 @@ fn spawn_worker(
let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string());
- let string_worker_run_count = format!("{}", worker_run_count);
let mut args = vec![
"worker",
"--monitor-daemon",
- "--run-count",
- &string_worker_run_count,
];
- if manual_start {
- args.push("--manual");
+ if let Some(start_reason) = &start_reason {
+ args.push("--start-reason");
+ args.push(&start_reason);
}
command.args(&args);
command.with_paths_overrides(paths_overrides);
let mut child = command.spawn().expect("unable to spawn worker process");
- *worker_run_count += 1;
-
// Create a monitor thread that will exit with the same non-zero code if
// the worker thread exits
std::thread::Builder::new()
@@ -305,3 +277,47 @@ fn spawn_worker(
})
.expect("Unable to spawn worker monitor thread");
}
+
+fn restart_worker(
+ paths: &Paths,
+ paths_overrides: &PathsOverrides,
+ exit_notify: Sender,
+ start_reason: Option,
+) {
+ match create_ipc_client_to_worker(&paths.runtime) {
+ Ok(mut worker_ipc) => {
+ if let Err(err) = worker_ipc.send_async(IPCEvent::Exit) {
+ error!(
+ "unable to send termination signal to worker process: {}",
+ err
+ );
+ }
+ }
+ Err(err) => {
+ error!("could not establish IPC connection with worker: {}", err);
+ }
+ }
+
+ // Wait until the worker process has terminated
+ let start = Instant::now();
+ let mut has_timed_out = true;
+ while start.elapsed() < std::time::Duration::from_secs(30) {
+ let lock_file = acquire_worker_lock(&paths.runtime);
+ if lock_file.is_some() {
+ has_timed_out = false;
+ break;
+ }
+
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ }
+
+ if !has_timed_out {
+ spawn_worker(
+ &paths_overrides,
+ exit_notify,
+ start_reason,
+ );
+ } else {
+ error!("could not restart worker, as the exit process has timed out");
+ }
+}
diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs
index b4f53a5..e3cd7bf 100644
--- a/espanso/src/cli/worker/engine/mod.rs
+++ b/espanso/src/cli/worker/engine/mod.rs
@@ -41,7 +41,7 @@ use crate::{cli::worker::{context::Context, engine::{dispatch::executor::{clipbo
extension::{clipboard::ClipboardAdapter, form::FormProviderAdapter},
RendererAdapter,
},
- }}, match_cache::{CombinedMatchCache, MatchCache}}, engine::event::ExitMode, preferences::Preferences};
+ }}, match_cache::{CombinedMatchCache, MatchCache}}, common_flags::WORKER_START_REASON_CONFIG_CHANGED, engine::event::ExitMode, preferences::Preferences};
use super::secure_input::SecureInputEvent;
@@ -60,8 +60,7 @@ pub fn initialize_and_spawn(
ui_event_receiver: Receiver,
secure_input_receiver: Receiver,
use_evdev_backend: bool,
- run_count: i32,
- has_been_started_manually: bool,
+ start_reason: Option,
) -> Result> {
let handle = std::thread::Builder::new()
.name("engine thread".to_string())
@@ -213,22 +212,24 @@ pub fn initialize_and_spawn(
}
// TODO: check config
- match run_count {
- 0 => {
+ match start_reason.as_deref() {
+ Some(flag) if flag == WORKER_START_REASON_CONFIG_CHANGED => {
+ ui_remote.show_notification("Configuration reloaded! Espanso automatically loads new changes as soon as you save them.");
+ }
+ Some("manual_restart") => {
+ ui_remote.show_notification("Configuration reloaded!");
+ }
+ Some("keyboard_layout_changed") => {
+ ui_remote.show_notification("Updated keyboard layout!");
+ }
+ _ => {
ui_remote.show_notification("Espanso is running!");
if !preferences.has_displayed_welcome() {
super::ui::welcome::show_welcome_screen();
preferences.set_has_displayed_welcome(true);
}
- },
- n => {
- if has_been_started_manually {
- ui_remote.show_notification("Configuration reloaded!");
- } else if n == 1 {
- ui_remote.show_notification("Configuration reloaded! Espanso automatically loads new changes as soon as you save them.");
- }
- },
+ }
}
let mut engine = crate::engine::Engine::new(&funnel, &mut processor, &dispatcher);
diff --git a/espanso/src/cli/worker/mod.rs b/espanso/src/cli/worker/mod.rs
index 0420479..b7c0b26 100644
--- a/espanso/src/cli/worker/mod.rs
+++ b/espanso/src/cli/worker/mod.rs
@@ -61,15 +61,11 @@ fn worker_main(args: CliModuleArgs) -> i32 {
let paths = args.paths.expect("missing paths in worker main");
let cli_args = args.cli_args.expect("missing cli_args in worker main");
- // This number is passed by the daemon and incremented at each worker
- // restart.
- let run_count = cli_args
- .value_of("run-count")
- .map(|val| val.parse::().unwrap_or(0))
- .unwrap_or(0);
- debug!("starting with run-count = {:?}", cli_args);
-
- let has_been_started_manually = cli_args.is_present("manual");
+ // When restarted, the daemon passes the reason why the worker was restarted (config_change, etc)
+ let start_reason = cli_args
+ .value_of("start-reason")
+ .map(String::from);
+ debug!("starting with start-reason = {:?}", start_reason);
// Avoid running multiple worker instances
let lock_file = acquire_worker_lock(&paths.runtime);
@@ -132,8 +128,7 @@ fn worker_main(args: CliModuleArgs) -> i32 {
engine_ui_event_receiver,
engine_secure_input_receiver,
use_evdev_backend,
- run_count,
- has_been_started_manually,
+ start_reason,
)
.expect("unable to initialize engine");
diff --git a/espanso/src/common_flags.rs b/espanso/src/common_flags.rs
new file mode 100644
index 0000000..3255d6b
--- /dev/null
+++ b/espanso/src/common_flags.rs
@@ -0,0 +1,22 @@
+/*
+ * This file is part of espanso.
+ *
+ * Copyright (C) 2019-2021 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 .
+ */
+
+pub const WORKER_START_REASON_MANUAL: &str = "manual_restart";
+pub const WORKER_START_REASON_CONFIG_CHANGED: &str = "config_changed";
+pub const WORKER_START_REASON_KEYBOARD_LAYOUT_CHANGED: &str = "keyboard_layout_changed";
\ No newline at end of file
diff --git a/espanso/src/main.rs b/espanso/src/main.rs
index 11dce8e..0a1f90c 100644
--- a/espanso/src/main.rs
+++ b/espanso/src/main.rs
@@ -40,6 +40,7 @@ use crate::{
mod capabilities;
mod cli;
+mod common_flags;
mod config;
mod engine;
mod exit_code;
@@ -357,17 +358,11 @@ fn main() {
SubCommand::with_name("worker")
.setting(AppSettings::Hidden)
.arg(
- Arg::with_name("run-count")
- .long("run-count")
+ Arg::with_name("start-reason")
+ .long("start-reason")
.required(false)
.takes_value(true),
)
- .arg(
- Arg::with_name("manual")
- .long("manual")
- .required(false)
- .takes_value(false),
- )
.arg(
Arg::with_name("monitor-daemon")
.long("monitor-daemon")