commit
						195edbe9d2
					
				
							
								
								
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -366,7 +366,7 @@ dependencies = [
 | 
			
		|||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "espanso"
 | 
			
		||||
version = "0.6.0"
 | 
			
		||||
version = "0.6.1"
 | 
			
		||||
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)",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "espanso"
 | 
			
		||||
version = "0.6.0"
 | 
			
		||||
version = "0.6.1"
 | 
			
		||||
authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
 | 
			
		||||
license = "GPL-3.0"
 | 
			
		||||
description = "Cross-platform Text Expander written in Rust"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
name: espanso
 | 
			
		||||
version: 0.6.0
 | 
			
		||||
version: 0.6.1
 | 
			
		||||
summary: A Cross-platform Text Expander written in Rust
 | 
			
		||||
description: |
 | 
			
		||||
  espanso is a Cross-platform, Text Expander written in Rust.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,7 +31,7 @@ use std::fs;
 | 
			
		|||
use std::fs::{create_dir_all, File};
 | 
			
		||||
use std::io::Read;
 | 
			
		||||
use std::path::{Path, PathBuf};
 | 
			
		||||
use walkdir::WalkDir;
 | 
			
		||||
use walkdir::{DirEntry, WalkDir};
 | 
			
		||||
 | 
			
		||||
pub(crate) mod runtime;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -409,7 +409,7 @@ impl Configs {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn merge_config(&mut self, new_config: Configs) {
 | 
			
		||||
    fn merge_overwrite(&mut self, new_config: Configs) {
 | 
			
		||||
        // Merge matches
 | 
			
		||||
        let mut merged_matches = new_config.matches;
 | 
			
		||||
        let mut match_trigger_set = HashSet::new();
 | 
			
		||||
| 
						 | 
				
			
			@ -447,7 +447,7 @@ impl Configs {
 | 
			
		|||
        self.global_vars = merged_global_vars;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn merge_default(&mut self, default: &Configs) {
 | 
			
		||||
    fn merge_no_overwrite(&mut self, default: &Configs) {
 | 
			
		||||
        // Merge matches
 | 
			
		||||
        let mut match_trigger_set = HashSet::new();
 | 
			
		||||
        self.matches.iter().for_each(|m| {
 | 
			
		||||
| 
						 | 
				
			
			@ -503,7 +503,7 @@ impl ConfigSet {
 | 
			
		|||
            eprintln!("Warning: Using Auto backend is only supported on Linux, falling back to Inject backend.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Analyze which config files has to be loaded
 | 
			
		||||
        // Analyze which config files have to be loaded
 | 
			
		||||
 | 
			
		||||
        let mut target_files = Vec::new();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -513,82 +513,108 @@ impl ConfigSet {
 | 
			
		|||
            target_files.extend(dir_entry);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if package_dir.exists() {
 | 
			
		||||
        let package_files = if package_dir.exists() {
 | 
			
		||||
            let dir_entry = WalkDir::new(package_dir);
 | 
			
		||||
            target_files.extend(dir_entry);
 | 
			
		||||
        }
 | 
			
		||||
            dir_entry.into_iter().collect()
 | 
			
		||||
        } else {
 | 
			
		||||
            vec![]
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Load the user defined config files
 | 
			
		||||
 | 
			
		||||
        let mut name_set = HashSet::new();
 | 
			
		||||
        let mut children_map: HashMap<String, Vec<Configs>> = HashMap::new();
 | 
			
		||||
        let mut package_map: HashMap<String, Vec<Configs>> = HashMap::new();
 | 
			
		||||
        let mut root_configs = Vec::new();
 | 
			
		||||
        root_configs.push(default);
 | 
			
		||||
 | 
			
		||||
        for entry in target_files {
 | 
			
		||||
            if let Ok(entry) = entry {
 | 
			
		||||
                let path = entry.path();
 | 
			
		||||
        let mut file_loader = |entry: walkdir::Result<DirEntry>,
 | 
			
		||||
                               dest_map: &mut HashMap<String, Vec<Configs>>|
 | 
			
		||||
         -> Result<(), ConfigLoadError> {
 | 
			
		||||
            match entry {
 | 
			
		||||
                Ok(entry) => {
 | 
			
		||||
                    let path = entry.path();
 | 
			
		||||
 | 
			
		||||
                // Skip non-yaml config files
 | 
			
		||||
                if path
 | 
			
		||||
                    .extension()
 | 
			
		||||
                    .unwrap_or_default()
 | 
			
		||||
                    .to_str()
 | 
			
		||||
                    .unwrap_or_default()
 | 
			
		||||
                    != "yml"
 | 
			
		||||
                {
 | 
			
		||||
                    continue;
 | 
			
		||||
                    // Skip non-yaml config files
 | 
			
		||||
                    if path
 | 
			
		||||
                        .extension()
 | 
			
		||||
                        .unwrap_or_default()
 | 
			
		||||
                        .to_str()
 | 
			
		||||
                        .unwrap_or_default()
 | 
			
		||||
                        != "yml"
 | 
			
		||||
                    {
 | 
			
		||||
                        return Ok(());
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Skip hidden files
 | 
			
		||||
                    if path
 | 
			
		||||
                        .file_name()
 | 
			
		||||
                        .unwrap_or_default()
 | 
			
		||||
                        .to_str()
 | 
			
		||||
                        .unwrap_or_default()
 | 
			
		||||
                        .starts_with(".")
 | 
			
		||||
                    {
 | 
			
		||||
                        return Ok(());
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    let mut config = Configs::load_config(&path)?;
 | 
			
		||||
 | 
			
		||||
                    // Make sure the config does not contain reserved fields
 | 
			
		||||
                    if !config.validate_user_defined_config() {
 | 
			
		||||
                        return Err(ConfigLoadError::InvalidParameter(path.to_owned()));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // No name specified, defaulting to the path name
 | 
			
		||||
                    if config.name == "default" {
 | 
			
		||||
                        config.name = path.to_str().unwrap_or_default().to_owned();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if name_set.contains(&config.name) {
 | 
			
		||||
                        return Err(ConfigLoadError::NameDuplicate(path.to_owned()));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    name_set.insert(config.name.clone());
 | 
			
		||||
 | 
			
		||||
                    if config.parent == "self" {
 | 
			
		||||
                        // No parent, root config
 | 
			
		||||
                        root_configs.push(config);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // Children config
 | 
			
		||||
                        let children_vec = dest_map.entry(config.parent.clone()).or_default();
 | 
			
		||||
                        children_vec.push(config);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Skip hidden files
 | 
			
		||||
                if path
 | 
			
		||||
                    .file_name()
 | 
			
		||||
                    .unwrap_or_default()
 | 
			
		||||
                    .to_str()
 | 
			
		||||
                    .unwrap_or_default()
 | 
			
		||||
                    .starts_with(".")
 | 
			
		||||
                {
 | 
			
		||||
                    continue;
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    eprintln!("Warning: Unable to read config file: {}", e);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let mut config = Configs::load_config(&path)?;
 | 
			
		||||
 | 
			
		||||
                // Make sure the config does not contain reserved fields
 | 
			
		||||
                if !config.validate_user_defined_config() {
 | 
			
		||||
                    return Err(ConfigLoadError::InvalidParameter(path.to_owned()));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // No name specified, defaulting to the path name
 | 
			
		||||
                if config.name == "default" {
 | 
			
		||||
                    config.name = path.to_str().unwrap_or_default().to_owned();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if name_set.contains(&config.name) {
 | 
			
		||||
                    return Err(ConfigLoadError::NameDuplicate(path.to_owned()));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                name_set.insert(config.name.clone());
 | 
			
		||||
 | 
			
		||||
                if config.parent == "self" {
 | 
			
		||||
                    // No parent, root config
 | 
			
		||||
                    root_configs.push(config);
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Children config
 | 
			
		||||
                    let children_vec = children_map.entry(config.parent.clone()).or_default();
 | 
			
		||||
                    children_vec.push(config);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                eprintln!(
 | 
			
		||||
                    "Warning: Unable to read config file: {}",
 | 
			
		||||
                    entry.unwrap_err()
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Ok(())
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Load the default and user specific configs
 | 
			
		||||
        for entry in target_files {
 | 
			
		||||
            file_loader(entry, &mut children_map)?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Load the package related configs
 | 
			
		||||
        for entry in package_files {
 | 
			
		||||
            file_loader(entry, &mut package_map)?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Merge the children config files
 | 
			
		||||
        let mut configs = Vec::new();
 | 
			
		||||
        let mut configs_without_packages = Vec::new();
 | 
			
		||||
        for root_config in root_configs {
 | 
			
		||||
            let config = ConfigSet::reduce_configs(root_config, &children_map);
 | 
			
		||||
            let config = ConfigSet::reduce_configs(root_config, &children_map, true);
 | 
			
		||||
            configs_without_packages.push(config);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Merge package files
 | 
			
		||||
        // Note: we need two different steps as the packages have a lower priority
 | 
			
		||||
        //       than configs.
 | 
			
		||||
        let mut configs = Vec::new();
 | 
			
		||||
        for root_config in configs_without_packages {
 | 
			
		||||
            let config = ConfigSet::reduce_configs(root_config, &package_map, false);
 | 
			
		||||
            configs.push(config);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -599,7 +625,7 @@ impl ConfigSet {
 | 
			
		|||
        // Add default entries to specific configs when needed
 | 
			
		||||
        for config in specific.iter_mut() {
 | 
			
		||||
            if !config.exclude_default_entries {
 | 
			
		||||
                config.merge_default(&default);
 | 
			
		||||
                config.merge_no_overwrite(&default);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -618,12 +644,21 @@ impl ConfigSet {
 | 
			
		|||
        Ok(ConfigSet { default, specific })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn reduce_configs(target: Configs, children_map: &HashMap<String, Vec<Configs>>) -> Configs {
 | 
			
		||||
    fn reduce_configs(
 | 
			
		||||
        target: Configs,
 | 
			
		||||
        children_map: &HashMap<String, Vec<Configs>>,
 | 
			
		||||
        higher_priority: bool,
 | 
			
		||||
    ) -> Configs {
 | 
			
		||||
        if children_map.contains_key(&target.name) {
 | 
			
		||||
            let mut target = target;
 | 
			
		||||
            for children in children_map.get(&target.name).unwrap() {
 | 
			
		||||
                let children = Self::reduce_configs(children.clone(), children_map);
 | 
			
		||||
                target.merge_config(children);
 | 
			
		||||
                let children =
 | 
			
		||||
                    Self::reduce_configs(children.clone(), children_map, higher_priority);
 | 
			
		||||
                if higher_priority {
 | 
			
		||||
                    target.merge_overwrite(children);
 | 
			
		||||
                } else {
 | 
			
		||||
                    target.merge_no_overwrite(&children);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            target
 | 
			
		||||
        } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -1480,6 +1515,40 @@ mod tests {
 | 
			
		|||
            .any(|m| m.triggers[0] == "harry"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_config_set_package_configs_lower_priority_than_user() {
 | 
			
		||||
        let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
 | 
			
		||||
            r###"
 | 
			
		||||
        matches:
 | 
			
		||||
            - trigger: hasta
 | 
			
		||||
              replace: Hasta la vista
 | 
			
		||||
        "###,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        create_package_file(
 | 
			
		||||
            package_dir.path(),
 | 
			
		||||
            "package1",
 | 
			
		||||
            "package.yml",
 | 
			
		||||
            r###"
 | 
			
		||||
        parent: default
 | 
			
		||||
 | 
			
		||||
        matches:
 | 
			
		||||
            - trigger: "hasta"
 | 
			
		||||
              replace: "potter"
 | 
			
		||||
        "###,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
 | 
			
		||||
        assert_eq!(config_set.specific.len(), 0);
 | 
			
		||||
        assert_eq!(config_set.default.matches.len(), 1);
 | 
			
		||||
        if let MatchContentType::Text(content) = config_set.default.matches[0].content.clone() {
 | 
			
		||||
            assert_eq!(config_set.default.matches[0].triggers[0], "hasta");
 | 
			
		||||
            assert_eq!(content.replace, "Hasta la vista")
 | 
			
		||||
        } else {
 | 
			
		||||
            panic!("invalid content");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_config_set_package_configs_without_merge() {
 | 
			
		||||
        let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -75,10 +75,15 @@ impl super::Extension for ScriptExtension {
 | 
			
		|||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            let mut command = Command::new(&str_args[0]);
 | 
			
		||||
 | 
			
		||||
            // Inject the $CONFIG variable
 | 
			
		||||
            command.env("CONFIG", crate::context::get_config_dir());
 | 
			
		||||
 | 
			
		||||
            let output = if str_args.len() > 1 {
 | 
			
		||||
                Command::new(&str_args[0]).args(&str_args[1..]).output()
 | 
			
		||||
                command.args(&str_args[1..]).output()
 | 
			
		||||
            } else {
 | 
			
		||||
                Command::new(&str_args[0]).output()
 | 
			
		||||
                command.output()
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            match output {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,15 +40,38 @@ pub enum Shell {
 | 
			
		|||
 | 
			
		||||
impl Shell {
 | 
			
		||||
    fn execute_cmd(&self, cmd: &str) -> std::io::Result<Output> {
 | 
			
		||||
        match self {
 | 
			
		||||
            Shell::Cmd => Command::new("cmd").args(&["/C", &cmd]).output(),
 | 
			
		||||
            Shell::Powershell => Command::new("powershell")
 | 
			
		||||
                .args(&["-Command", &cmd])
 | 
			
		||||
                .output(),
 | 
			
		||||
            Shell::WSL => Command::new("wsl").args(&["bash", "-c", &cmd]).output(),
 | 
			
		||||
            Shell::Bash => Command::new("bash").args(&["-c", &cmd]).output(),
 | 
			
		||||
            Shell::Sh => Command::new("sh").args(&["-c", &cmd]).output(),
 | 
			
		||||
        }
 | 
			
		||||
        let mut command = match self {
 | 
			
		||||
            Shell::Cmd => {
 | 
			
		||||
                let mut command = Command::new("cmd");
 | 
			
		||||
                command.args(&["/C", &cmd]);
 | 
			
		||||
                command
 | 
			
		||||
            },
 | 
			
		||||
            Shell::Powershell => {
 | 
			
		||||
                let mut command = Command::new("powershell");
 | 
			
		||||
                command.args(&["-Command", &cmd]);
 | 
			
		||||
                command
 | 
			
		||||
            },
 | 
			
		||||
            Shell::WSL => {
 | 
			
		||||
                let mut command = Command::new("wsl");
 | 
			
		||||
                command.args(&["bash", "-c", &cmd]);
 | 
			
		||||
                command
 | 
			
		||||
            },
 | 
			
		||||
            Shell::Bash => {
 | 
			
		||||
                let mut command = Command::new("bash");
 | 
			
		||||
                command.args(&["-c", &cmd]);
 | 
			
		||||
                command
 | 
			
		||||
            },
 | 
			
		||||
            Shell::Sh => {
 | 
			
		||||
                let mut command = Command::new("sh");
 | 
			
		||||
                command.args(&["-c", &cmd]);
 | 
			
		||||
                command
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Inject the $CONFIG variable
 | 
			
		||||
        command.env("CONFIG", crate::context::get_config_dir());
 | 
			
		||||
 | 
			
		||||
        command.output()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn from_string(shell: &str) -> Option<Shell> {
 | 
			
		||||
| 
						 | 
				
			
			@ -143,13 +166,15 @@ impl super::Extension for ShellExtension {
 | 
			
		|||
 | 
			
		||||
                // If specified, trim the output
 | 
			
		||||
                let trim_opt = params.get(&Value::from("trim"));
 | 
			
		||||
                if let Some(value) = trim_opt {
 | 
			
		||||
                let should_trim = if let Some(value) = trim_opt {
 | 
			
		||||
                    let val = value.as_bool();
 | 
			
		||||
                    if let Some(val) = val {
 | 
			
		||||
                        if val {
 | 
			
		||||
                            output_str = output_str.trim().to_owned()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    val.unwrap_or(true)
 | 
			
		||||
                }else{
 | 
			
		||||
                    true
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                if should_trim {
 | 
			
		||||
                    output_str = output_str.trim().to_owned()
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Some(output_str)
 | 
			
		||||
| 
						 | 
				
			
			@ -168,9 +193,10 @@ mod tests {
 | 
			
		|||
    use crate::extension::Extension;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_shell_basic() {
 | 
			
		||||
    fn test_shell_not_trimmed() {
 | 
			
		||||
        let mut params = Mapping::new();
 | 
			
		||||
        params.insert(Value::from("cmd"), Value::from("echo \"hello world\""));
 | 
			
		||||
        params.insert(Value::from("trim"), Value::from(false));
 | 
			
		||||
 | 
			
		||||
        let extension = ShellExtension::new();
 | 
			
		||||
        let output = extension.calculate(¶ms, &vec![]);
 | 
			
		||||
| 
						 | 
				
			
			@ -185,10 +211,9 @@ mod tests {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_shell_trimmed() {
 | 
			
		||||
    fn test_shell_basic() {
 | 
			
		||||
        let mut params = Mapping::new();
 | 
			
		||||
        params.insert(Value::from("cmd"), Value::from("echo \"hello world\""));
 | 
			
		||||
        params.insert(Value::from("trim"), Value::from(true));
 | 
			
		||||
 | 
			
		||||
        let extension = ShellExtension::new();
 | 
			
		||||
        let output = extension.calculate(¶ms, &vec![]);
 | 
			
		||||
| 
						 | 
				
			
			@ -205,8 +230,6 @@ mod tests {
 | 
			
		|||
            Value::from("echo \"   hello world     \""),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        params.insert(Value::from("trim"), Value::from(true));
 | 
			
		||||
 | 
			
		||||
        let extension = ShellExtension::new();
 | 
			
		||||
        let output = extension.calculate(¶ms, &vec![]);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -224,11 +247,7 @@ mod tests {
 | 
			
		|||
        let output = extension.calculate(¶ms, &vec![]);
 | 
			
		||||
 | 
			
		||||
        assert!(output.is_some());
 | 
			
		||||
        if cfg!(target_os = "windows") {
 | 
			
		||||
            assert_eq!(output.unwrap(), "hello world\r\n");
 | 
			
		||||
        } else {
 | 
			
		||||
            assert_eq!(output.unwrap(), "hello world\n");
 | 
			
		||||
        }
 | 
			
		||||
        assert_eq!(output.unwrap(), "hello world");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
| 
						 | 
				
			
			@ -256,7 +275,7 @@ mod tests {
 | 
			
		|||
 | 
			
		||||
        assert!(output.is_some());
 | 
			
		||||
 | 
			
		||||
        assert_eq!(output.unwrap(), "hello\n");
 | 
			
		||||
        assert_eq!(output.unwrap(), "hello");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
| 
						 | 
				
			
			@ -270,6 +289,6 @@ mod tests {
 | 
			
		|||
 | 
			
		||||
        assert!(output.is_some());
 | 
			
		||||
 | 
			
		||||
        assert_eq!(output.unwrap(), "hello\r\n");
 | 
			
		||||
        assert_eq!(output.unwrap(), "hello");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										25
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								src/main.rs
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -381,10 +381,31 @@ fn daemon_main(config_set: ConfigSet) {
 | 
			
		|||
    info!("spawning worker process...");
 | 
			
		||||
 | 
			
		||||
    let espanso_path = std::env::current_exe().expect("unable to obtain espanso path location");
 | 
			
		||||
    crate::process::spawn_process(
 | 
			
		||||
    let mut child = crate::process::spawn_process(
 | 
			
		||||
        &espanso_path.to_string_lossy().to_string(),
 | 
			
		||||
        &vec!["worker".to_owned()],
 | 
			
		||||
    );
 | 
			
		||||
    )
 | 
			
		||||
    .expect("unable to create worker process");
 | 
			
		||||
 | 
			
		||||
    // Create a monitor thread that will exit with the same non-zero code if
 | 
			
		||||
    // the worker thread exits
 | 
			
		||||
    thread::Builder::new()
 | 
			
		||||
        .name("worker monitor".to_string())
 | 
			
		||||
        .spawn(move || {
 | 
			
		||||
            let result = child.wait();
 | 
			
		||||
            if let Ok(status) = result {
 | 
			
		||||
                if let Some(code) = status.code() {
 | 
			
		||||
                    if code != 0 {
 | 
			
		||||
                        error!(
 | 
			
		||||
                            "worker process exited with non-zero code: {}, exiting",
 | 
			
		||||
                            code
 | 
			
		||||
                        );
 | 
			
		||||
                        std::process::exit(code);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        .expect("Unable to spawn worker monitor thread");
 | 
			
		||||
 | 
			
		||||
    std::thread::sleep(Duration::from_millis(200));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,29 +18,19 @@
 | 
			
		|||
 */
 | 
			
		||||
 | 
			
		||||
use log::warn;
 | 
			
		||||
use widestring::WideCString;
 | 
			
		||||
use std::io;
 | 
			
		||||
use std::process::{Child, Command, Stdio};
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "windows")]
 | 
			
		||||
pub fn spawn_process(cmd: &str, args: &Vec<String>) {
 | 
			
		||||
    let quoted_args: Vec<String> = 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")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
pub fn spawn_process(cmd: &str, args: &Vec<String>) -> io::Result<Child> {
 | 
			
		||||
    use std::os::windows::process::CommandExt;
 | 
			
		||||
    Command::new(cmd)
 | 
			
		||||
        .creation_flags(0x00000008) // Detached Process
 | 
			
		||||
        .args(args)
 | 
			
		||||
        .spawn()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(not(target_os = "windows"))]
 | 
			
		||||
pub fn spawn_process(cmd: &str, args: &Vec<String>) {
 | 
			
		||||
    use std::process::{Command, Stdio};
 | 
			
		||||
 | 
			
		||||
    Command::new(cmd).args(args).spawn();
 | 
			
		||||
pub fn spawn_process(cmd: &str, args: &Vec<String>) -> io::Result<Child> {
 | 
			
		||||
    Command::new(cmd).args(args).spawn()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user