Repository: espanso/espanso (language: rust)

Issue:
Espanso 2.0.1 alpha doesn't start automatically after reboot
I've installed espanso 2.0.1 alpha following the installation steps for linux (however i didn't installed in the $HOME/opt folder, I used $HOME/Documents instead). I registered in the path (and indeed espanso is available from any terminal), and also I register the service with the command `espanso service register` (the documentation says `espanso register` but it didn't work).

However I need to start espanso after every reboot of the system. I have kubuntu 21.04.

P.D.: anyway I can fix this behavior making an script that start espanso automatically when boots.


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 493e4f2f..761f6f46 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -572,7 +572,7 @@ dependencies = [
 
 [[package]]
 name = "espanso"
-version = "2.0.1"
+version = "2.0.2-alpha"
 dependencies = [
  "anyhow",
  "caps",
@@ -787,7 +787,7 @@ dependencies = [
  "test-case",
  "thiserror",
  "walkdir",
- "yaml-rust 0.4.5 (git+https://github.com/federico-terzi/yaml-rust)",
+ "yaml-rust 0.4.6",
 ]
 
 [[package]]
@@ -2399,7 +2399,7 @@ dependencies = [
  "dtoa",
  "linked-hash-map",
  "serde",
- "yaml-rust 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "yaml-rust 0.4.5",
 ]
 
 [[package]]
@@ -3312,8 +3312,8 @@ dependencies = [
 
 [[package]]
 name = "yaml-rust"
-version = "0.4.5"
-source = "git+https://github.com/federico-terzi/yaml-rust#b1a195252fcdabf743f68d03f4d84d151a5a3f62"
+version = "0.4.6"
+source = "git+https://github.com/federico-terzi/yaml-rust#454221bebabc93307bbf7aa7f556407dd3027363"
 dependencies = [
  "linked-hash-map",
 ]
diff --git a/espanso-migrate/Cargo.toml b/espanso-migrate/Cargo.toml
index 16aaab82..07d513c3 100644
--- a/espanso-migrate/Cargo.toml
+++ b/espanso-migrate/Cargo.toml
@@ -13,7 +13,7 @@ regex = "1.4.3"
 lazy_static = "1.4.0"
 dunce = "1.0.1"
 walkdir = "2.3.1"
-yaml-rust = { git = "https://github.com/federico-terzi/yaml-rust" }
+yaml-rust = { version = "0.4.6", git = "https://github.com/federico-terzi/yaml-rust" }
 path-slash = "0.1.4"
 tempdir = "0.3.7"
 fs_extra = "1.2.0"
diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml
index 0a5e80cf..afd07786 100644
--- a/espanso/Cargo.toml
+++ b/espanso/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "espanso"
-version = "2.0.1"
+version = "2.0.2-alpha"
 authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
 license = "GPL-3.0"
 description = "Cross-platform Text Expander written in Rust"
diff --git a/espanso/src/cli/daemon/keyboard_layout_watcher.rs b/espanso/src/cli/daemon/keyboard_layout_watcher.rs
index 8f778928..f5bb0348 100644
--- a/espanso/src/cli/daemon/keyboard_layout_watcher.rs
+++ b/espanso/src/cli/daemon/keyboard_layout_watcher.rs
@@ -19,7 +19,7 @@
 
 use anyhow::Result;
 use crossbeam::channel::Sender;
-use log::{debug, error};
+use log::{debug, error, warn};
 
 const WATCHER_INTERVAL: u64 = 1000;
 
@@ -39,34 +39,28 @@ pub fn initialize_and_spawn(watcher_notify: Sender<()>) -> Result<()> {
 }
 
 fn watcher_main(watcher_notify: &Sender<()>) {
-  let layout = espanso_detect::get_active_layout();
+  let mut 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;
+    warn!("keyboard layout watcher couldn't determine active layout.")
   }
 
-  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);
-        }
+    let current_layout = espanso_detect::get_active_layout();
+    if current_layout != layout {
+      debug!(
+        "detected keyboard layout change: from '{}' to '{}'",
+        layout.as_deref().unwrap_or_default(),
+        current_layout.as_deref().unwrap_or_default(),
+      );
 
-        layout = current_layout;
+      if let Err(error) = watcher_notify.send(()) {
+        error!("unable to send keyboard layout changed event: {}", error);
       }
-    } else {
-      error!("keyboard layout watcher couldn't determine active layout");
-      break;
+
+      layout = current_layout;
     }
   }
 }
diff --git a/espanso/src/cli/daemon/watcher.rs b/espanso/src/cli/daemon/watcher.rs
index 9bf817a2..ce3f29c8 100644
--- a/espanso/src/cli/daemon/watcher.rs
+++ b/espanso/src/cli/daemon/watcher.rs
@@ -17,36 +17,42 @@
  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{
-  path::Path,
-  time::{Duration, Instant},
-};
+use std::{path::Path, time::Duration};
 
 use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
 
 use anyhow::Result;
-use crossbeam::channel::Sender;
+use crossbeam::{channel::Sender, select};
 use log::{error, info, warn};
 
-const WATCHER_DEBOUNCE_DURATION: u64 = 1;
+const WATCHER_NOTIFY_DELAY_MS: u64 = 500;
+const WATCHER_DEBOUNCE_DURATION_MS: u64 = 1000;
 
 pub fn initialize_and_spawn(config_dir: &Path, watcher_notify: Sender<()>) -> Result<()> {
   let config_dir = config_dir.to_path_buf();
 
+  let (debounce_tx, debounce_rx) = crossbeam::channel::unbounded();
+
   std::thread::Builder::new()
     .name("watcher".to_string())
     .spawn(move || {
-      watcher_main(&config_dir, &watcher_notify);
+      watcher_main(&config_dir, debounce_tx);
+    })?;
+
+  std::thread::Builder::new()
+    .name("watcher-debouncer".to_string())
+    .spawn(move || {
+      debouncer_main(debounce_rx, &watcher_notify);
     })?;
 
   Ok(())
 }
 
-fn watcher_main(config_dir: &Path, watcher_notify: &Sender<()>) {
+fn watcher_main(config_dir: &Path, debounce_tx: crossbeam::channel::Sender<()>) {
   let (tx, rx) = std::sync::mpsc::channel();
 
   let mut watcher: RecommendedWatcher =
-    Watcher::new(tx, Duration::from_secs(WATCHER_DEBOUNCE_DURATION))
+    Watcher::new(tx, Duration::from_millis(WATCHER_NOTIFY_DELAY_MS))
       .expect("unable to create file watcher");
 
   watcher
@@ -55,8 +61,6 @@ fn watcher_main(config_dir: &Path, watcher_notify: &Sender<()>) {
 
   info!("watching for changes in path: {:?}", config_dir);
 
-  let mut last_event_arrival = Instant::now();
-
   loop {
     let should_reload = match rx.recv() {
       Ok(event) => {
@@ -92,16 +96,35 @@ fn watcher_main(config_dir: &Path, watcher_notify: &Sender<()>) {
       }
     };
 
-    // Send only one event, otherwise we could run the risk of useless reloads or even race conditions.
-    if should_reload
-      && last_event_arrival.elapsed() > std::time::Duration::from_secs(WATCHER_DEBOUNCE_DURATION)
-    {
-      if let Err(error) = watcher_notify.send(()) {
-        error!("unable to send watcher file changed event: {}", error);
+    if should_reload {
+      if let Err(error) = debounce_tx.send(()) {
+        error!(
+          "unable to send watcher file changed event to debouncer: {}",
+          error
+        );
       }
     }
+  }
+}
+
+fn debouncer_main(debounce_rx: crossbeam::channel::Receiver<()>, watcher_notify: &Sender<()>) {
+  let mut has_received_event = false;
 
-    last_event_arrival = Instant::now();
+  loop {
+    select! {
+      recv(debounce_rx) -> _ => {
+        has_received_event = true;
+      },
+      default(Duration::from_millis(WATCHER_DEBOUNCE_DURATION_MS)) => {
+        if has_received_event {
+          if let Err(error) = watcher_notify.send(()) {
+            error!("unable to send watcher file changed event: {}", error);
+          }
+        }
+
+        has_received_event = false;
+      },
+    }
   }
 }
 
diff --git a/espanso/src/cli/launcher/mod.rs b/espanso/src/cli/launcher/mod.rs
index 9be664ab..aa77a199 100644
--- a/espanso/src/cli/launcher/mod.rs
+++ b/espanso/src/cli/launcher/mod.rs
@@ -94,7 +94,8 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
       },
     });
 
-  let is_auto_start_page_enabled = !preferences.has_selected_auto_start_option();
+  let is_auto_start_page_enabled =
+    !preferences.has_selected_auto_start_option() && !cfg!(target_os = "linux");
   let preferences_clone = preferences.clone();
   let auto_start_handler = Box::new(move |auto_start| {
     preferences_clone.set_has_selected_auto_start_option(true);
diff --git a/espanso/src/cli/service/mod.rs b/espanso/src/cli/service/mod.rs
index 0e710cb1..c80e4186 100644
--- a/espanso/src/cli/service/mod.rs
+++ b/espanso/src/cli/service/mod.rs
@@ -95,6 +95,8 @@ fn service_main(args: CliModuleArgs) -> i32 {
     return start_main(&paths, &paths_overrides, sub_args);
   } else if cli_args.subcommand_matches("stop").is_some() {
     return stop_main(&paths);
+  } else if cli_args.subcommand_matches("status").is_some() {
+    return status_main(&paths);
   } else if let Some(sub_args) = cli_args.subcommand_matches("restart") {
     stop_main(&paths);
     return start_main(&paths, &paths_overrides, sub_args);
@@ -152,3 +154,15 @@ fn stop_main(paths: &Paths) -> i32 {
 
   SERVICE_SUCCESS
 }
+
+fn status_main(paths: &Paths) -> i32 {
+  let lock_file = acquire_worker_lock(&paths.runtime);
+  if lock_file.is_some() {
+    error_eprintln!("espanso is not running");
+    return SERVICE_NOT_RUNNING;
+  }
+  drop(lock_file);
+
+  info_println!("espanso is running");
+  SERVICE_SUCCESS
+}
diff --git a/espanso/src/exit_code.rs b/espanso/src/exit_code.rs
index 81eda519..f99d1b0b 100644
--- a/espanso/src/exit_code.rs
+++ b/espanso/src/exit_code.rs
@@ -21,8 +21,8 @@ pub const WORKER_SUCCESS: i32 = 0;
 pub const WORKER_ALREADY_RUNNING: i32 = 1;
 pub const WORKER_GENERAL_ERROR: i32 = 2;
 pub const WORKER_LEGACY_ALREADY_RUNNING: i32 = 3;
-pub const WORKER_EXIT_ALL_PROCESSES: i32 = 101;
-pub const WORKER_RESTART: i32 = 102;
+pub const WORKER_EXIT_ALL_PROCESSES: i32 = 50;
+pub const WORKER_RESTART: i32 = 51;
 
 pub const DAEMON_SUCCESS: i32 = 0;
 pub const DAEMON_ALREADY_RUNNING: i32 = 1;
@@ -36,7 +36,7 @@ pub const MIGRATE_LEGACY_INSTANCE_RUNNING: i32 = 2;
 pub const MIGRATE_USER_ABORTED: i32 = 3;
 pub const MIGRATE_CLEAN_FAILURE: i32 = 50;
 pub const MIGRATE_DIRTY_FAILURE: i32 = 51;
-pub const MIGRATE_UNEXPECTED_FAILURE: i32 = 101;
+pub const MIGRATE_UNEXPECTED_FAILURE: i32 = 52;
 
 pub const ADD_TO_PATH_SUCCESS: i32 = 0;
 pub const ADD_TO_PATH_FAILURE: i32 = 1;
diff --git a/espanso/src/main.rs b/espanso/src/main.rs
index 02b56711..d0897ed5 100644
--- a/espanso/src/main.rs
+++ b/espanso/src/main.rs
@@ -85,6 +85,10 @@ lazy_static! {
       subcommand: "stop".to_owned(),
       forward_into: "service".to_owned(),
     },
+    CliAlias {
+      subcommand: "status".to_owned(),
+      forward_into: "service".to_owned(),
+    },
     CliAlias {
       subcommand: "install".to_owned(),
       forward_into: "package".to_owned(),
@@ -171,6 +175,8 @@ fn main() {
     .about("Restart the espanso service")
     .name("restart");
   let stop_subcommand = SubCommand::with_name("stop").about("Stop espanso service");
+  let status_subcommand =
+    SubCommand::with_name("status").about("Check if the espanso daemon is running or not.");
 
   let mut clap_instance = App::new("espanso")
     .version(VERSION)
@@ -299,12 +305,6 @@ For example, specifying 'email' is equivalent to 'match/email.yml'."#))
             ),
         ),
     )
-    // .subcommand(SubCommand::with_name("start")
-    //     .about("Start the daemon spawning a new process in the background."))
-    // .subcommand(SubCommand::with_name("stop")
-    //     .about("Stop the espanso daemon."))
-    // .subcommand(SubCommand::with_name("restart")
-    //     .about("Restart the espanso daemon."))
     // .subcommand(SubCommand::with_name("status")
     //     .about("Check if the espanso daemon is running or not."))
     .subcommand(
@@ -346,11 +346,13 @@ For example, specifying 'email' is equivalent to 'match/email.yml'."#))
         .subcommand(start_subcommand.clone())
         .subcommand(restart_subcommand.clone())
         .subcommand(stop_subcommand.clone())
+        .subcommand(status_subcommand.clone())
         .about("Register and manage 'espanso' as a system service."),
     )
     .subcommand(start_subcommand)
     .subcommand(restart_subcommand)
     .subcommand(stop_subcommand)
+    .subcommand(status_subcommand)
     // .subcommand(SubCommand::with_name("match")
     //     .about("List and execute matches from the CLI")
     //     .subcommand(SubCommand::with_name("list")
diff --git a/espanso/src/patch/mod.rs b/espanso/src/patch/mod.rs
index 3582aaea..e4991817 100644
--- a/espanso/src/patch/mod.rs
+++ b/espanso/src/patch/mod.rs
@@ -50,6 +50,7 @@ fn get_builtin_patches() -> Vec<PatchDefinition> {
     patches::linux::simple_terminal_2_x11::patch(),
     patches::linux::terminator_terminal_x11::patch(),
     patches::linux::termite_terminal_x11::patch(),
+    patches::linux::thunderbird_x11::patch(),
     patches::linux::tilix_terminal_x11::patch(),
     patches::linux::urxvt_terminal_x11::patch(),
     patches::linux::xterm_terminal_x11::patch(),
diff --git a/espanso/src/patch/patches/linux/mod.rs b/espanso/src/patch/patches/linux/mod.rs
index 6dd311b2..d145f160 100644
--- a/espanso/src/patch/patches/linux/mod.rs
+++ b/espanso/src/patch/patches/linux/mod.rs
@@ -27,6 +27,7 @@ pub mod simple_terminal_2_x11;
 pub mod simple_terminal_x11;
 pub mod terminator_terminal_x11;
 pub mod termite_terminal_x11;
+pub mod thunderbird_x11;
 pub mod tilix_terminal_x11;
 pub mod urxvt_terminal_x11;
 pub mod xterm_terminal_x11;
diff --git a/espanso/src/patch/patches/linux/thunderbird_x11.rs b/espanso/src/patch/patches/linux/thunderbird_x11.rs
new file mode 100644
index 00000000..eeb366ab
--- /dev/null
+++ b/espanso/src/patch/patches/linux/thunderbird_x11.rs
@@ -0,0 +1,46 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+use std::sync::Arc;
+
+use espanso_config::config::Backend;
+
+use crate::patch::patches::{PatchedConfig, Patches};
+use crate::patch::PatchDefinition;
+
+pub fn patch() -> PatchDefinition {
+  PatchDefinition {
+    name: module_path!().split(':').last().unwrap_or("unknown"),
+    is_enabled: || cfg!(target_os = "linux") && !super::util::is_wayland(),
+    should_patch: |app| app.class.unwrap_or_default().contains("Thunderbird"),
+    apply: |base, name| {
+      Arc::new(PatchedConfig::patch(
+        base,
+        name,
+        Patches {
+          paste_shortcut: Some(Some("CTRL+SHIFT+V".to_string())),
+          backend: Some(Backend::Clipboard),
+          key_delay: Some(Some(15)),
+          inject_delay: Some(Some(15)),
+          ..Default::default()
+        },
+      ))
+    },
+  }
+}


Test patch: