diff --git a/espanso-config/src/config/mod.rs b/espanso-config/src/config/mod.rs index 88be928..c8c0208 100644 --- a/espanso-config/src/config/mod.rs +++ b/espanso-config/src/config/mod.rs @@ -107,6 +107,10 @@ pub trait Config: Send + Sync { // If false, avoid applying the built-in patches to the current config. fn apply_patch(&self) -> bool; + // On Wayland, overrides the auto-detected keyboard configuration (RMLVO) + // which is used both for the detection and injection process. + fn keyboard_layout(&self) -> Option; + fn is_match<'a>(&self, app: &AppProperties<'a>) -> bool; fn pretty_dump(&self) -> String { @@ -192,6 +196,15 @@ pub enum ToggleKey { LeftMeta, } +#[derive(Debug, Clone)] +pub struct RMLVOConfig { + pub rules: Option, + pub model: Option, + pub layout: Option, + pub variant: Option, + pub options: Option, +} + pub fn load_store(config_dir: &Path) -> Result<(impl ConfigStore, Vec)> { store::DefaultConfigStore::load(config_dir) } diff --git a/espanso-config/src/config/parse/mod.rs b/espanso-config/src/config/parse/mod.rs index d7507f9..1edcd65 100644 --- a/espanso-config/src/config/parse/mod.rs +++ b/espanso-config/src/config/parse/mod.rs @@ -18,7 +18,7 @@ */ use anyhow::Result; -use std::{convert::TryInto, path::Path}; +use std::{collections::BTreeMap, convert::TryInto, path::Path}; use thiserror::Error; mod yaml; @@ -43,6 +43,7 @@ pub(crate) struct ParsedConfig { pub paste_shortcut_event_delay: Option, pub inject_delay: Option, pub key_delay: Option, + pub keyboard_layout: Option>, // Includes @@ -73,4 +74,4 @@ impl ParsedConfig { pub enum ParsedConfigError { #[error("can't load config `{0}`")] LoadFailed(#[from] anyhow::Error), -} +} \ No newline at end of file diff --git a/espanso-config/src/config/parse/yaml.rs b/espanso-config/src/config/parse/yaml.rs index 5c30bcf..f16daff 100644 --- a/espanso-config/src/config/parse/yaml.rs +++ b/espanso-config/src/config/parse/yaml.rs @@ -19,6 +19,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; +use serde_yaml::Mapping; use std::convert::TryFrom; use crate::util::is_yaml_empty; @@ -53,7 +54,7 @@ pub(crate) struct YAMLConfig { #[serde(default)] pub paste_shortcut_event_delay: Option, - + #[serde(default)] pub paste_shortcut: Option, @@ -78,6 +79,9 @@ pub(crate) struct YAMLConfig { #[serde(default)] pub apply_patch: Option, + #[serde(default)] + pub keyboard_layout: Option, + // Include/Exclude #[serde(default)] pub includes: Option>, @@ -139,6 +143,18 @@ impl TryFrom for ParsedConfig { word_separators: yaml_config.word_separators, backspace_limit: yaml_config.backspace_limit, apply_patch: yaml_config.apply_patch, + keyboard_layout: yaml_config.keyboard_layout.map(|mapping| { + mapping + .into_iter() + .filter_map(|(key, value)| { + if let (Some(key), Some(value)) = (key.as_str(), value.as_str()) { + Some((key.to_string(), value.to_string())) + } else { + None + } + }) + .collect() + }), pre_paste_delay: yaml_config.pre_paste_delay, restore_clipboard_delay: yaml_config.restore_clipboard_delay, @@ -161,7 +177,7 @@ impl TryFrom for ParsedConfig { #[cfg(test)] mod tests { use super::*; - use std::convert::TryInto; + use std::{collections::BTreeMap, convert::TryInto}; #[test] fn conversion_to_parsed_config_works_correctly() { @@ -183,6 +199,13 @@ mod tests { backspace_delay: 30 word_separators: ["'", "."] backspace_limit: 10 + apply_patch: false + keyboard_layout: + rules: test_rule + model: test_model + layout: test_layout + variant: test_variant + options: test_options use_standard_includes: true includes: ["test1"] @@ -199,6 +222,15 @@ mod tests { .unwrap(); let parsed_config: ParsedConfig = config.try_into().unwrap(); + let keyboard_layout: BTreeMap = + vec![ + ("rules".to_string(), "test_rule".to_string()), + ("model".to_string(), "test_model".to_string()), + ("layout".to_string(), "test_layout".to_string()), + ("variant".to_string(), "test_variant".to_string()), + ("options".to_string(), "test_options".to_string()), + ].into_iter().collect(); + assert_eq!( parsed_config, ParsedConfig { @@ -216,9 +248,10 @@ mod tests { key_delay: Some(20), backspace_limit: Some(10), apply_patch: Some(false), + keyboard_layout: Some(keyboard_layout), pre_paste_delay: Some(300), - + toggle_key: Some("CTRL".to_string()), word_separators: Some(vec!["'".to_owned(), ".".to_owned()]), diff --git a/espanso-config/src/config/resolve.rs b/espanso-config/src/config/resolve.rs index 7034b45..9666e91 100644 --- a/espanso-config/src/config/resolve.rs +++ b/espanso-config/src/config/resolve.rs @@ -25,14 +25,14 @@ use super::{ parse::ParsedConfig, path::calculate_paths, util::os_matches, - AppProperties, Backend, Config, ToggleKey, + AppProperties, Backend, Config, RMLVOConfig, ToggleKey, }; use crate::{counter::next_id, merge}; use anyhow::Result; use log::error; use regex::Regex; -use std::{iter::FromIterator, path::PathBuf}; use std::{collections::HashSet, path::Path}; +use std::{iter::FromIterator, path::PathBuf}; use thiserror::Error; const STANDARD_INCLUDES: &[&str] = &["../match/**/*.yml"]; @@ -79,7 +79,7 @@ impl Config for ResolvedConfig { if let Some(source_path) = self.source_path.as_ref() { if let Some(source_path) = source_path.to_str() { - return source_path + return source_path; } } @@ -264,6 +264,16 @@ impl Config for ResolvedConfig { fn apply_patch(&self) -> bool { self.parsed.apply_patch.unwrap_or(true) } + + fn keyboard_layout(&self) -> Option { + self.parsed.keyboard_layout.as_ref().map(|layout| RMLVOConfig { + rules: layout.get("rules").map(String::from), + model: layout.get("model").map(String::from), + layout: layout.get("layout").map(String::from), + variant: layout.get("variant").map(String::from), + options: layout.get("options").map(String::from), + }) + } } impl ResolvedConfig { @@ -336,6 +346,7 @@ impl ResolvedConfig { key_delay, word_separators, backspace_limit, + keyboard_layout, includes, excludes, extra_includes, diff --git a/espanso-config/src/legacy/mod.rs b/espanso-config/src/legacy/mod.rs index b6e028d..ffbb48e 100644 --- a/espanso-config/src/legacy/mod.rs +++ b/espanso-config/src/legacy/mod.rs @@ -23,7 +23,13 @@ use regex::Regex; use std::{collections::HashMap, path::Path, sync::Arc}; use self::config::LegacyConfig; -use crate::matches::{MatchEffect, group::loader::yaml::{parse::{YAMLMatch, YAMLVariable}, try_convert_into_match, try_convert_into_variable}}; +use crate::matches::{ + group::loader::yaml::{ + parse::{YAMLMatch, YAMLVariable}, + try_convert_into_match, try_convert_into_variable, + }, + MatchEffect, +}; use crate::{config::store::DefaultConfigStore, counter::StructId}; use crate::{ config::Config, @@ -333,16 +339,25 @@ impl Config for LegacyInteropConfig { } fn word_separators(&self) -> Vec { - self.config.word_separators.iter().map(|c| String::from(*c)).collect() + self + .config + .word_separators + .iter() + .map(|c| String::from(*c)) + .collect() } fn backspace_limit(&self) -> usize { self.config.backspace_limit.try_into().unwrap() } - + fn apply_patch(&self) -> bool { true } + + fn keyboard_layout(&self) -> Option { + None + } } struct LegacyMatchGroup {