diff --git a/espanso-config/src/config/mod.rs b/espanso-config/src/config/mod.rs index 47096b6..c73d394 100644 --- a/espanso-config/src/config/mod.rs +++ b/espanso-config/src/config/mod.rs @@ -30,6 +30,7 @@ pub(crate) mod store; pub trait Config { fn label(&self) -> &str; fn match_paths(&self) -> &[String]; + fn backend(&self) -> Backend; fn is_match(&self, app: &AppProperties) -> bool; } @@ -47,6 +48,13 @@ pub struct AppProperties<'a> { pub exec: Option<&'a str>, } +#[derive(Debug, Copy, Clone)] +pub enum Backend { + Inject, + Clipboard, + Auto, +} + pub fn load_store(config_dir: &Path) -> Result { store::DefaultConfigStore::load(config_dir) } diff --git a/espanso-config/src/config/parse/mod.rs b/espanso-config/src/config/parse/mod.rs index 5815c4e..c68602a 100644 --- a/espanso-config/src/config/parse/mod.rs +++ b/espanso-config/src/config/parse/mod.rs @@ -27,6 +27,8 @@ mod yaml; pub(crate) struct ParsedConfig { pub label: Option, + pub backend: Option, + // Includes pub includes: Option>, pub excludes: Option>, diff --git a/espanso-config/src/config/parse/yaml.rs b/espanso-config/src/config/parse/yaml.rs index ff8085f..73be307 100644 --- a/espanso-config/src/config/parse/yaml.rs +++ b/espanso-config/src/config/parse/yaml.rs @@ -30,6 +30,9 @@ pub(crate) struct YAMLConfig { #[serde(default)] pub label: Option, + #[serde(default)] + pub backend: Option, + #[serde(default)] pub includes: Option>, @@ -78,7 +81,7 @@ impl TryFrom for ParsedConfig { fn try_from(yaml_config: YAMLConfig) -> Result { Ok(Self { label: yaml_config.label, - + backend: yaml_config.backend, use_standard_includes: yaml_config.use_standard_includes, includes: yaml_config.includes, extra_includes: yaml_config.extra_includes, @@ -103,6 +106,7 @@ mod tests { let config = YAMLConfig::parse_from_str( r#" label: "test" + backend: clipboard use_standard_includes: true includes: ["test1"] @@ -123,6 +127,9 @@ mod tests { parsed_config, ParsedConfig { label: Some("test".to_string()), + + backend: Some("clipboard".to_string()), + use_standard_includes: Some(true), includes: Some(vec!["test1".to_string()]), extra_includes: Some(vec!["test2".to_string()]), diff --git a/espanso-config/src/config/resolve.rs b/espanso-config/src/config/resolve.rs index 25127be..8da4f8f 100644 --- a/espanso-config/src/config/resolve.rs +++ b/espanso-config/src/config/resolve.rs @@ -17,9 +17,10 @@ * along with espanso. If not, see . */ -use super::{parse::ParsedConfig, path::calculate_paths, util::os_matches, AppProperties, Config}; +use super::{AppProperties, Backend, Config, parse::ParsedConfig, path::calculate_paths, util::os_matches}; use crate::merge; use anyhow::Result; +use log::error; use regex::Regex; use std::iter::FromIterator; use std::{collections::HashSet, path::Path}; @@ -109,6 +110,18 @@ impl Config for ResolvedConfig { // All the filters that have been specified must be true to define a match is_os_match && is_exec_match && is_title_match && is_class_match } + + fn backend(&self) -> Backend { + match self.parsed.backend.as_deref().map(|b| b.to_lowercase()).as_deref() { + Some("clipboard") => Backend::Clipboard, + Some("inject") => Backend::Inject, + Some("auto") => Backend::Auto, + err => { + error!("invalid backend specified {:?}, falling back to Auto", err); + Backend::Auto + } + } + } } impl ResolvedConfig { @@ -164,6 +177,7 @@ impl ResolvedConfig { parent, // Fields label, + backend, includes, excludes, extra_includes, diff --git a/espanso-config/src/config/store.rs b/espanso-config/src/config/store.rs index 4a0318c..c424a0d 100644 --- a/espanso-config/src/config/store.rs +++ b/espanso-config/src/config/store.rs @@ -145,6 +145,10 @@ mod tests { fn is_match(&self, _: &crate::config::AppProperties) -> bool { self.is_match } + + fn backend(&self) -> crate::config::Backend { + unimplemented!() + } } #[test] diff --git a/espanso-config/src/legacy/config.rs b/espanso-config/src/legacy/config.rs index cad182d..a02f7df 100644 --- a/espanso-config/src/legacy/config.rs +++ b/espanso-config/src/legacy/config.rs @@ -509,7 +509,7 @@ impl LegacyConfig { fn triggers_for_match(m: &Value) -> Vec { if let Some(triggers) = m.get("triggers").and_then(|v| v.as_sequence()) { - triggers.into_iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect() + triggers.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect() } else if let Some(trigger) = m.get("trigger").and_then(|v| v.as_str()) { vec![trigger.to_string()] } else { @@ -517,6 +517,7 @@ fn triggers_for_match(m: &Value) -> Vec { } } +#[allow(dead_code)] fn replace_for_match(m: &Value) -> String { m.get("replace").and_then(|v| v.as_str()).expect("match is missing replace field").to_string() } @@ -752,6 +753,7 @@ impl LegacyConfigSet { // Error handling #[derive(Debug, PartialEq)] +#[allow(dead_code)] pub enum ConfigLoadError { FileNotFound, UnableToReadFile, diff --git a/espanso-config/src/legacy/mod.rs b/espanso-config/src/legacy/mod.rs index 9b1b23d..a6863c0 100644 --- a/espanso-config/src/legacy/mod.rs +++ b/espanso-config/src/legacy/mod.rs @@ -92,9 +92,10 @@ fn split_config(config: LegacyConfig) -> (LegacyInteropConfig, LegacyMatchGroup) struct LegacyInteropConfig { pub name: String, - match_paths: Vec, + config: LegacyConfig, + filter_title: Option, filter_class: Option, filter_exec: Option, @@ -103,6 +104,7 @@ struct LegacyInteropConfig { impl From for LegacyInteropConfig { fn from(config: config::LegacyConfig) -> Self { Self { + config: config.clone(), name: config.name.clone(), match_paths: vec![config.name], filter_title: if !config.filter_title.is_empty() { @@ -126,7 +128,15 @@ impl From for LegacyInteropConfig { impl Config for LegacyInteropConfig { fn label(&self) -> &str { - &self.name + &self.config.name + } + + fn backend(&self) -> crate::config::Backend { + match self.config.backend { + config::BackendType::Inject => crate::config::Backend::Inject, + config::BackendType::Clipboard => crate::config::Backend::Clipboard, + config::BackendType::Auto => crate::config::Backend::Auto, + } } fn match_paths(&self) -> &[String] { @@ -237,7 +247,9 @@ mod tests { #[test] fn load_legacy_works_correctly() { use_test_directory(|base, user, packages| { - std::fs::write(base.join("default.yml"), r#" + std::fs::write( + base.join("default.yml"), + r#" backend: Clipboard global_vars: @@ -247,25 +259,35 @@ mod tests { matches: - trigger: "hello" replace: "world" - "#).unwrap(); + "#, + ) + .unwrap(); - std::fs::write(user.join("specific.yml"), r#" + std::fs::write( + user.join("specific.yml"), + r#" name: specific parent: default matches: - trigger: "foo" replace: "bar" - "#).unwrap(); + "#, + ) + .unwrap(); - std::fs::write(user.join("separate.yml"), r#" + std::fs::write( + user.join("separate.yml"), + r#" name: separate filter_title: "Google" matches: - trigger: "eren" replace: "mikasa" - "#).unwrap(); + "#, + ) + .unwrap(); let (config_store, match_store) = load(base, packages).unwrap(); @@ -286,44 +308,91 @@ mod tests { }); assert_eq!(default_fallback.match_paths().len(), 1); - assert_eq!(match_store.query(default_config.match_paths()).matches.len(), 2); - assert_eq!(match_store.query(default_config.match_paths()).global_vars.len(), 1); - - assert_eq!(match_store.query(active_config.match_paths()).matches.len(), 3); - assert_eq!(match_store.query(active_config.match_paths()).global_vars.len(), 1); + assert_eq!( + match_store + .query(default_config.match_paths()) + .matches + .len(), + 2 + ); + assert_eq!( + match_store + .query(default_config.match_paths()) + .global_vars + .len(), + 1 + ); - assert_eq!(match_store.query(default_fallback.match_paths()).matches.len(), 2); - assert_eq!(match_store.query(default_fallback.match_paths()).global_vars.len(), 1); + assert_eq!( + match_store.query(active_config.match_paths()).matches.len(), + 3 + ); + assert_eq!( + match_store + .query(active_config.match_paths()) + .global_vars + .len(), + 1 + ); + + assert_eq!( + match_store + .query(default_fallback.match_paths()) + .matches + .len(), + 2 + ); + assert_eq!( + match_store + .query(default_fallback.match_paths()) + .global_vars + .len(), + 1 + ); }); } #[test] fn load_legacy_with_packages() { - use_test_directory(|base, user, packages| { - std::fs::write(base.join("default.yml"), r#" + use_test_directory(|base, _, packages| { + std::fs::write( + base.join("default.yml"), + r#" backend: Clipboard matches: - trigger: "hello" replace: "world" - "#).unwrap(); + "#, + ) + .unwrap(); create_dir_all(packages.join("test-package")).unwrap(); - std::fs::write(packages.join("test-package").join("package.yml"), r#" + std::fs::write( + packages.join("test-package").join("package.yml"), + r#" name: test-package parent: default matches: - trigger: "foo" replace: "bar" - "#).unwrap(); + "#, + ) + .unwrap(); let (config_store, match_store) = load(base, packages).unwrap(); let default_config = config_store.default(); assert_eq!(default_config.match_paths().len(), 1); - assert_eq!(match_store.query(default_config.match_paths()).matches.len(), 2); + assert_eq!( + match_store + .query(default_config.match_paths()) + .matches + .len(), + 2 + ); }); } }