From 1f7af8fcb6142ee2fbdf9ec8a69f7afabd9d3d31 Mon Sep 17 00:00:00 2001 From: rszyma Date: Wed, 22 Nov 2023 08:59:13 +0100 Subject: [PATCH] refactor: validate defcfg values in parser (#624) 1. Move validation of cfg values to `parse_defcfg` instead of validating them all over the codebase. Reason: before kanata didn't show location when parsing of a cfg value failed, now it does. 2. Replace `HashMap` with `CfgOptions`, which holds only validated cfg values. Reason: Allows to easily parse lists as values, since now the actual value parsing is no longer delayed after parser function exited. This was done with #121 in mind. 3. Move cfg options defaults to a `CfgOptions::default`. Reason: it's good to have similar code in one place. --- docs/config.adoc | 2 +- keyberon/src/action/switch.rs | 234 +++++++----------- parser/src/cfg/defcfg.rs | 376 +++++++++++++++++++++++++++++ parser/src/cfg/mod.rs | 215 +++++------------ parser/src/cfg/tests.rs | 51 ++++ src/kanata/linux.rs | 30 +-- src/kanata/mod.rs | 164 +++---------- src/kanata/windows/interception.rs | 19 +- src/kanata/windows/mod.rs | 38 +-- src/oskbd/linux.rs | 36 +-- 10 files changed, 623 insertions(+), 542 deletions(-) create mode 100644 parser/src/cfg/defcfg.rs diff --git a/docs/config.adoc b/docs/config.adoc index d35637ef5..79774470a 100644 --- a/docs/config.adoc +++ b/docs/config.adoc @@ -707,7 +707,7 @@ a non-applicable operating system. linux-dev /dev/input/dev1:/dev/input/dev2 linux-dev-names-include "Name 1:Name 2" linux-dev-names-exclude "Name 3:Name 4" - linux-continue-if-no-dev-found yes + linux-continue-if-no-devs-found yes linux-unicode-u-code v linux-unicode-termination space linux-x11-repeat-delay-rate 400,50 diff --git a/keyberon/src/action/switch.rs b/keyberon/src/action/switch.rs index e6983d160..fa0c0359a 100644 --- a/keyberon/src/action/switch.rs +++ b/keyberon/src/action/switch.rs @@ -278,14 +278,11 @@ fn bool_evaluation_test_0() { OpCode::new_key(KeyCode::F), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - true - ); + assert!(evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] @@ -309,14 +306,11 @@ fn bool_evaluation_test_1() { KeyCode::E, KeyCode::F, ]; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - true - ); + assert!(evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] @@ -333,14 +327,11 @@ fn bool_evaluation_test_2() { OpCode(KeyCode::F as u16), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::E, KeyCode::F]; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - false - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] @@ -357,28 +348,22 @@ fn bool_evaluation_test_3() { OpCode(KeyCode::F as u16), ]; let keycodes = [KeyCode::B, KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F]; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - false - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] fn bool_evaluation_test_4() { let opcodes = []; let keycodes = []; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - true - ); + assert!(evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] @@ -392,14 +377,11 @@ fn bool_evaluation_test_5() { KeyCode::E, KeyCode::F, ]; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - true - ); + assert!(evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] @@ -413,28 +395,22 @@ fn bool_evaluation_test_6() { KeyCode::E, KeyCode::F, ]; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - true - ); + assert!(evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] fn bool_evaluation_test_7() { let opcodes = [OpCode(KeyCode::A as u16), OpCode(KeyCode::B as u16)]; let keycodes = [KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F]; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - false - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] @@ -446,14 +422,11 @@ fn bool_evaluation_test_9() { OpCode(KeyCode::C as u16), ]; let keycodes = [KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F]; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - true - ); + assert!(evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] @@ -465,14 +438,11 @@ fn bool_evaluation_test_10() { OpCode(KeyCode::C as u16), ]; let keycodes = [KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F]; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - false - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] @@ -483,14 +453,11 @@ fn bool_evaluation_test_11() { OpCode(KeyCode::B as u16), ]; let keycodes = [KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F]; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - false - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] @@ -503,14 +470,11 @@ fn bool_evaluation_test_12() { OpCode(KeyCode::C as u16), ]; let keycodes = [KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F]; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - true - ); + assert!(evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] @@ -526,14 +490,11 @@ fn bool_evaluation_test_max_depth_does_not_panic() { OpCode(0x1008), ]; let keycodes = []; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - true - ); + assert!(evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] @@ -551,14 +512,11 @@ fn bool_evaluation_test_more_than_max_depth_panics() { OpCode(0x1009), ]; let keycodes = []; - assert_eq!( - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied() - ), - true - ); + assert!(evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied() + )); } #[test] @@ -632,38 +590,26 @@ fn switch_historical_1() { KeyCode::G, KeyCode::H, ]; - assert_eq!( - evaluate_boolean( - opcode_true.as_slice(), - [].iter().copied(), - hist_keycodes.iter().copied(), - ), - true - ); - assert_eq!( - evaluate_boolean( - opcode_true2.as_slice(), - [].iter().copied(), - hist_keycodes.iter().copied(), - ), - true - ); - assert_eq!( - evaluate_boolean( - opcode_false.as_slice(), - [].iter().copied(), - hist_keycodes.iter().copied(), - ), - false - ); - assert_eq!( - evaluate_boolean( - opcode_false2.as_slice(), - [].iter().copied(), - hist_keycodes.iter().copied(), - ), - false - ); + assert!(evaluate_boolean( + opcode_true.as_slice(), + [].iter().copied(), + hist_keycodes.iter().copied(), + )); + assert!(evaluate_boolean( + opcode_true2.as_slice(), + [].iter().copied(), + hist_keycodes.iter().copied(), + )); + assert!(!evaluate_boolean( + opcode_false.as_slice(), + [].iter().copied(), + hist_keycodes.iter().copied(), + )); + assert!(!evaluate_boolean( + opcode_false2.as_slice(), + [].iter().copied(), + hist_keycodes.iter().copied(), + )); } #[test] diff --git a/parser/src/cfg/defcfg.rs b/parser/src/cfg/defcfg.rs new file mode 100644 index 000000000..75757804d --- /dev/null +++ b/parser/src/cfg/defcfg.rs @@ -0,0 +1,376 @@ +use super::error::*; +use super::sexpr::SExpr; +use super::HashSet; +use crate::cfg::check_first_expr; +use crate::custom_action::*; +#[allow(unused)] +use crate::{anyhow_expr, anyhow_span, bail, bail_expr}; + +#[derive(Debug)] +pub struct CfgOptions { + pub process_unmapped_keys: bool, + pub enable_cmd: bool, + pub sequence_timeout: u16, + pub sequence_input_mode: SequenceInputMode, + pub sequence_backtrack_modcancel: bool, + pub log_layer_changes: bool, + pub delegate_to_first_layer: bool, + pub movemouse_inherit_accel_state: bool, + pub movemouse_smooth_diagonals: bool, + pub dynamic_macro_max_presses: u16, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + pub linux_dev: Vec, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + pub linux_dev_names_include: Option>, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + pub linux_dev_names_exclude: Option>, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + pub linux_continue_if_no_devs_found: bool, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + pub linux_unicode_u_code: crate::keys::OsCode, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + pub linux_unicode_termination: UnicodeTermination, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + pub linux_x11_repeat_delay_rate: Option, + #[cfg(any(target_os = "windows", target_os = "unknown"))] + pub windows_altgr: AltGrBehaviour, + #[cfg(any( + all(feature = "interception_driver", target_os = "windows"), + target_os = "unknown" + ))] + pub windows_interception_mouse_hwid: Option<[u8; HWID_ARR_SZ]>, +} + +impl Default for CfgOptions { + fn default() -> Self { + Self { + process_unmapped_keys: false, + enable_cmd: false, + sequence_timeout: 1000, + sequence_input_mode: SequenceInputMode::HiddenSuppressed, + sequence_backtrack_modcancel: true, + log_layer_changes: true, + delegate_to_first_layer: false, + movemouse_inherit_accel_state: false, + movemouse_smooth_diagonals: false, + dynamic_macro_max_presses: 128, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + linux_dev: vec![], + #[cfg(any(target_os = "linux", target_os = "unknown"))] + linux_dev_names_include: None, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + linux_dev_names_exclude: None, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + linux_continue_if_no_devs_found: false, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + // historically was the only option, so make KEY_U the default + linux_unicode_u_code: crate::keys::OsCode::KEY_U, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + // historically was the only option, so make Enter the default + linux_unicode_termination: UnicodeTermination::Enter, + #[cfg(any(target_os = "linux", target_os = "unknown"))] + linux_x11_repeat_delay_rate: None, + #[cfg(any(target_os = "windows", target_os = "unknown"))] + windows_altgr: AltGrBehaviour::default(), + #[cfg(any( + all(feature = "interception_driver", target_os = "windows"), + target_os = "unknown" + ))] + windows_interception_mouse_hwid: None, + } + } +} + +/// Parse configuration entries from an expression starting with defcfg. +pub fn parse_defcfg(expr: &[SExpr]) -> Result { + let mut seen_keys = HashSet::default(); + let mut cfg = CfgOptions::default(); + let mut exprs = check_first_expr(expr.iter(), "defcfg")?; + // Read k-v pairs from the configuration + loop { + let key = match exprs.next() { + Some(k) => k, + None => return Ok(cfg), + }; + let val = match exprs.next() { + Some(v) => v, + None => bail_expr!(key, "Found a defcfg option missing a value"), + }; + match (&key, &val) { + (SExpr::Atom(k), SExpr::Atom(v)) => { + if !seen_keys.insert(&k.t) { + bail_expr!(key, "Duplicate defcfg option {}", k.t); + } + match k.t.as_str() { + k @ "sequence-timeout" => { + cfg.sequence_timeout = parse_cfg_val_u16(val, k, true)?; + } + "sequence-input-mode" => { + cfg.sequence_input_mode = + SequenceInputMode::try_from_str(&v.t.trim_matches('"')) + .map_err(|e| anyhow_expr!(val, "{}", e.to_string()))?; + } + k @ "dynamic-macro-max-presses" => { + cfg.dynamic_macro_max_presses = parse_cfg_val_u16(val, k, false)?; + } + "linux-dev" => { + #[cfg(any(target_os = "linux", target_os = "unknown"))] + { + let paths = v.t.trim_matches('"'); + cfg.linux_dev = parse_colon_separated_text(paths); + } + } + "linux-dev-names-include" => { + #[cfg(any(target_os = "linux", target_os = "unknown"))] + { + let paths = v.t.trim_matches('"'); + cfg.linux_dev_names_include = Some(parse_colon_separated_text(paths)); + } + } + "linux-dev-names-exclude" => { + #[cfg(any(target_os = "linux", target_os = "unknown"))] + { + let paths = v.t.trim_matches('"'); + cfg.linux_dev_names_exclude = Some(parse_colon_separated_text(paths)); + } + } + _k @ "linux-unicode-u-code" => { + #[cfg(any(target_os = "linux", target_os = "unknown"))] + { + cfg.linux_unicode_u_code = crate::keys::str_to_oscode( + v.t.trim_matches('"'), + ) + .ok_or_else(|| anyhow_expr!(val, "unknown code for {_k}: {}", v.t))?; + } + } + _k @ "linux-unicode-termination" => { + #[cfg(any(target_os = "linux", target_os = "unknown"))] + { + cfg.linux_unicode_termination = match v.t.trim_matches('"') { + "enter" => UnicodeTermination::Enter, + "space" => UnicodeTermination::Space, + "enter-space" => UnicodeTermination::EnterSpace, + "space-enter" => UnicodeTermination::SpaceEnter, + _ => bail_expr!( + val, + "{_k} got {}. It accepts: enter|space|enter-space|space-enter", + v.t + ), + } + } + } + "linux-x11-repeat-delay-rate" => { + #[cfg(any(target_os = "linux", target_os = "unknown"))] + { + let delay_rate = v.t.trim_matches('"').split(',').collect::>(); + const ERRMSG: &str = "Invalid value for linux-x11-repeat-delay-rate.\nExpected two numbers 0-65535 separated by a comma, e.g. 200,25"; + if delay_rate.len() != 2 { + bail_expr!(val, "{}", ERRMSG) + } + cfg.linux_x11_repeat_delay_rate = Some(KeyRepeatSettings { + delay: match str::parse::(delay_rate[0]) { + Ok(delay) => delay, + Err(_) => bail_expr!(val, "{}", ERRMSG), + }, + rate: match str::parse::(delay_rate[1]) { + Ok(rate) => rate, + Err(_) => bail_expr!(val, "{}", ERRMSG), + }, + }); + } + } + _k @ "windows-altgr" => { + #[cfg(any(target_os = "windows", target_os = "unknown"))] + { + const CANCEL: &str = "cancel-lctl-press"; + const ADD: &str = "add-lctl-release"; + cfg.windows_altgr = match v.t.trim_matches('"') { + CANCEL => AltGrBehaviour::CancelLctlPress, + ADD => AltGrBehaviour::AddLctlRelease, + _ => bail_expr!( + val, + "Invalid value for {_k}: {}. Valid values are {},{}", + v.t, + CANCEL, + ADD + ), + } + } + } + _k @ "windows-interception-mouse-hwid" => { + #[cfg(any( + all(feature = "interception_driver", target_os = "windows"), + target_os = "unknown" + ))] + { + let hwid = v.t.trim_matches('"'); + log::trace!("win hwid: {hwid}"); + let hwid_vec = hwid + .split(',') + .try_fold(vec![], |mut hwid_bytes, hwid_byte| { + hwid_byte.trim_matches(' ').parse::().map(|b| { + hwid_bytes.push(b); + hwid_bytes + }) + }).map_err(|_| anyhow_expr!(val, "{_k} format is invalid. It should consist of integers separated by commas"))?; + let hwid_slice = hwid_vec.iter().copied().enumerate() + .try_fold([0u8; HWID_ARR_SZ], |mut hwid, idx_byte| { + let (i, b) = idx_byte; + if i > HWID_ARR_SZ { + bail_expr!(val, "{_k} is too long; it should be up to {HWID_ARR_SZ} 8-bit unsigned integers") + } + hwid[i] = b; + Ok(hwid) + }); + cfg.windows_interception_mouse_hwid = Some(hwid_slice?); + } + } + + "process-unmapped-keys" => { + cfg.process_unmapped_keys = parse_defcfg_val_bool(val, &k.t)? + } + "danger-enable-cmd" => cfg.enable_cmd = parse_defcfg_val_bool(val, &k.t)?, + "sequence-backtrack-modcancel" => { + cfg.sequence_backtrack_modcancel = parse_defcfg_val_bool(val, &k.t)? + } + "log-layer-changes" => { + cfg.log_layer_changes = parse_defcfg_val_bool(val, &k.t)? + } + "delegate-to-first-layer" => { + cfg.delegate_to_first_layer = parse_defcfg_val_bool(val, &k.t)?; + if cfg.delegate_to_first_layer { + log::info!("delegating transparent keys on other layers to first defined layer"); + } + } + "linux-continue-if-no-devs-found" => { + #[cfg(any(target_os = "linux", target_os = "unknown"))] + { + cfg.linux_continue_if_no_devs_found = parse_defcfg_val_bool(val, &k.t)? + } + } + "movemouse-smooth-diagonals" => { + cfg.movemouse_smooth_diagonals = parse_defcfg_val_bool(val, &k.t)? + } + "movemouse-inherit-accel-state" => { + cfg.movemouse_inherit_accel_state = parse_defcfg_val_bool(val, &k.t)? + } + _ => bail_expr!(key, "Unknown defcfg option {}", &k.t), + }; + } + (SExpr::List(_), _) => { + bail_expr!(key, "Lists are not allowed in defcfg"); + } + (_, SExpr::List(_)) => { + bail_expr!(val, "Lists are not allowed in defcfg"); + } + } + } +} + +pub const FALSE_VALUES: [&str; 3] = ["no", "false", "0"]; +pub const TRUE_VALUES: [&str; 3] = ["yes", "true", "1"]; +pub const BOOLEAN_VALUES: [&str; 6] = ["yes", "true", "1", "no", "false", "0"]; + +fn parse_defcfg_val_bool(expr: &SExpr, label: &str) -> Result { + match &expr { + SExpr::Atom(v) => { + let val = v.t.trim_matches('"').to_ascii_lowercase(); + if TRUE_VALUES.contains(&val.as_str()) { + Ok(true) + } else if FALSE_VALUES.contains(&val.as_str()) { + Ok(false) + } else { + bail_expr!( + expr, + "The value for {label} must be one of: {}", + BOOLEAN_VALUES.join(", ") + ); + } + } + SExpr::List(_) => { + bail_expr!( + expr, + "The value for {label} cannot be a list, it must be one of: {}", + BOOLEAN_VALUES.join(", "), + ) + } + } +} + +fn parse_cfg_val_u16(expr: &SExpr, label: &str, exclude_zero: bool) -> Result { + let start = if exclude_zero { 1 } else { 0 }; + match &expr { + SExpr::Atom(v) => Ok(str::parse::(&v.t.trim_matches('"')) + .ok() + .and_then(|u| { + if exclude_zero && u == 0 { + None + } else { + Some(u) + } + }) + .ok_or_else(|| anyhow_expr!(expr, "{label} must be {start}-65535"))?), + SExpr::List(_) => { + bail_expr!( + expr, + "The value for {label} cannot be a list, it must be a number {start}-65535", + ) + } + } +} + +pub fn parse_colon_separated_text(paths: &str) -> Vec { + let mut all_paths = vec![]; + let mut full_dev_path = String::new(); + let mut dev_path_iter = paths.split(':').peekable(); + while let Some(dev_path) = dev_path_iter.next() { + if dev_path.ends_with('\\') && dev_path_iter.peek().is_some() { + full_dev_path.push_str(dev_path.trim_end_matches('\\')); + full_dev_path.push(':'); + continue; + } else { + full_dev_path.push_str(dev_path); + } + all_paths.push(full_dev_path.clone()); + full_dev_path.clear(); + } + all_paths.shrink_to_fit(); + all_paths +} + +#[cfg(any(target_os = "linux", target_os = "unknown"))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct KeyRepeatSettings { + pub delay: u16, + pub rate: u16, +} + +#[cfg(any(target_os = "linux", target_os = "unknown"))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum UnicodeTermination { + Enter, + Space, + SpaceEnter, + EnterSpace, +} + +#[cfg(any(target_os = "windows", target_os = "unknown"))] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AltGrBehaviour { + DoNothing, + CancelLctlPress, + AddLctlRelease, +} + +#[cfg(any(target_os = "windows", target_os = "unknown"))] +impl Default for AltGrBehaviour { + fn default() -> Self { + Self::DoNothing + } +} + +#[cfg(any( + all(feature = "interception_driver", target_os = "windows"), + target_os = "unknown" +))] +pub const HWID_ARR_SZ: usize = 128; diff --git a/parser/src/cfg/mod.rs b/parser/src/cfg/mod.rs index d6d1a871d..7d4a7a737 100644 --- a/parser/src/cfg/mod.rs +++ b/parser/src/cfg/mod.rs @@ -51,6 +51,9 @@ use custom_tap_hold::*; mod list_actions; use list_actions::*; +mod defcfg; +pub use defcfg::*; + use crate::custom_action::*; use crate::keys::*; use crate::layers::*; @@ -81,15 +84,17 @@ mod tests; #[cfg(test)] pub use sexpr::parse; +#[macro_export] macro_rules! bail { ($err:expr $(,)?) => { - return Err(ParseError::from(anyhow!($err))) + return Err(ParseError::from(anyhow::anyhow!($err))) }; ($fmt:expr, $($arg:tt)*) => { - return Err(ParseError::from(anyhow!($fmt, $($arg)*))) + return Err(ParseError::from(anyhow::anyhow!($fmt, $($arg)*))) }; } +#[macro_export] macro_rules! bail_expr { ($expr:expr, $fmt:expr $(,)?) => { return Err(ParseError::from_expr($expr, format!($fmt))) @@ -99,6 +104,7 @@ macro_rules! bail_expr { }; } +#[macro_export] macro_rules! bail_span { ($expr:expr, $fmt:expr $(,)?) => { return Err(ParseError::from_spanned($expr, format!($fmt))) @@ -108,6 +114,7 @@ macro_rules! bail_span { }; } +#[macro_export] macro_rules! anyhow_expr { ($expr:expr, $fmt:expr $(,)?) => { ParseError::from_expr($expr, format!($fmt)) @@ -117,6 +124,7 @@ macro_rules! anyhow_expr { }; } +#[macro_export] macro_rules! anyhow_span { ($expr:expr, $fmt:expr $(,)?) => { ParseError::from_spanned($expr, format!($fmt)) @@ -190,7 +198,7 @@ pub struct Cfg { /// Layer info used for printing to the logs. pub layer_info: Vec, /// Configuration items in `defcfg`. - pub items: HashMap, + pub items: CfgOptions, /// The keyberon layout state machine struct. pub layout: KanataLayout, /// Sequences defined in `defseq`. @@ -230,7 +238,7 @@ pub struct LayerInfo { fn parse_cfg( p: &Path, ) -> MResult<( - HashMap, + CfgOptions, MappedKeys, Vec, KeyOutputs, @@ -251,10 +259,6 @@ fn parse_cfg( )) } -pub const FALSE_VALUES: [&str; 3] = ["no", "false", "0"]; -pub const TRUE_VALUES: [&str; 3] = ["yes", "true", "1"]; -pub const BOOLEAN_VALUES: [&str; 6] = ["yes", "true", "1", "no", "false", "0"]; - #[cfg(all(not(feature = "interception_driver"), target_os = "windows"))] const DEF_LOCAL_KEYS: &str = "deflocalkeys-win"; #[cfg(all(feature = "interception_driver", target_os = "windows"))] @@ -267,7 +271,7 @@ fn parse_cfg_raw( p: &Path, s: &mut ParsedState, ) -> MResult<( - HashMap, + CfgOptions, MappedKeys, Vec, Box, @@ -371,7 +375,7 @@ pub fn parse_cfg_raw_string( file_content_provider: &mut FileContentProvider, def_local_keys_variant_to_apply: &str, ) -> Result<( - HashMap, + CfgOptions, MappedKeys, Vec, Box, @@ -392,23 +396,6 @@ pub fn parse_cfg_raw_string( error_on_unknown_top_level_atoms(&spanned_root_exprs)?; - let cfg = root_exprs - .iter() - .find(gen_first_atom_filter("defcfg")) - .map(|cfg| parse_defcfg(cfg)) - .transpose()? - .unwrap_or_default(); - if let Some(spanned) = spanned_root_exprs - .iter() - .filter(gen_first_atom_filter_spanned("defcfg")) - .nth(1) - { - bail_span!( - spanned, - "Only one defcfg is allowed, found more. Delete the extras." - ) - } - let mut local_keys: Option> = None; clear_custom_str_oscode_mapping(); for def_local_keys_variant in [ @@ -425,7 +412,7 @@ pub fn parse_cfg_raw_string( if def_local_keys_variant == def_local_keys_variant_to_apply { assert!( local_keys.is_none(), - ">1 mutually exclusive deflocalkeys variants was parsed" + ">1 mutually exclusive deflocalkeys variants were parsed" ); local_keys = Some(mapping); } @@ -444,6 +431,23 @@ pub fn parse_cfg_raw_string( } replace_custom_str_oscode_mapping(&local_keys.unwrap_or_default()); + let cfg = root_exprs + .iter() + .find(gen_first_atom_filter("defcfg")) + .map(|cfg| parse_defcfg(cfg)) + .transpose()? + .unwrap_or_default(); + if let Some(spanned) = spanned_root_exprs + .iter() + .filter(gen_first_atom_filter_spanned("defcfg")) + .nth(1) + { + bail_span!( + spanned, + "Only one defcfg is allowed, found more. Delete the extras." + ) + } + let src_expr = root_exprs .iter() .find(gen_first_atom_filter("defsrc")) @@ -535,14 +539,12 @@ pub fn parse_cfg_raw_string( is_cmd_enabled: { #[cfg(feature = "cmd")] { - cfg.get("danger-enable-cmd").map_or(false, |s| { - if TRUE_VALUES.contains(&s.to_lowercase().as_str()) { - log::warn!("DANGER! cmd action is enabled."); - true - } else { - false - } - }) + if cfg.enable_cmd { + log::warn!("DANGER! cmd action is enabled."); + true + } else { + false + } } #[cfg(not(feature = "cmd"))] { @@ -550,25 +552,9 @@ pub fn parse_cfg_raw_string( false } }, - delegate_to_first_layer: cfg.get("delegate-to-first-layer").map_or(false, |s| { - if TRUE_VALUES.contains(&s.to_lowercase().as_str()) { - log::info!("delegating transparent keys on other layers to first defined layer"); - true - } else { - false - } - }), - default_sequence_timeout: cfg - .get(SEQUENCE_TIMEOUT_CFG_NAME) - .map(|s| match str::parse::(s) { - Ok(0) | Err(_) => Err(anyhow!("{SEQUENCE_TIMEOUT_ERR}")), - Ok(t) => Ok(t), - }) - .unwrap_or(Ok(SEQUENCE_TIMEOUT_DEFAULT))?, - default_sequence_input_mode: cfg - .get(SEQUENCE_INPUT_MODE_CFG_NAME) - .map(|s| SequenceInputMode::try_from_str(s.as_str())) - .unwrap_or(Ok(SequenceInputMode::HiddenSuppressed))?, + delegate_to_first_layer: cfg.delegate_to_first_layer, + default_sequence_timeout: cfg.sequence_timeout, + default_sequence_input_mode: cfg.sequence_input_mode, ..Default::default() }; @@ -734,85 +720,12 @@ fn check_first_expr<'a>( Ok(exprs) } -/// Parse configuration entries from an expression starting with defcfg. -fn parse_defcfg(expr: &[SExpr]) -> Result> { - let non_bool_cfg_keys = &[ - "sequence-timeout", - "sequence-input-mode", - "dynamic-macro-max-presses", - "linux-dev", - "linux-dev-names-include", - "linux-dev-names-exclude", - "linux-unicode-u-code", - "linux-unicode-termination", - "linux-x11-repeat-delay-rate", - "windows-altgr", - "windows-interception-mouse-hwid", - ]; - let bool_cfg_keys = &[ - "process-unmapped-keys", - "danger-enable-cmd", - "sequence-backtrack-modcancel", - "log-layer-changes", - "delegate-to-first-layer", - "linux-continue-if-no-devs-found", - "movemouse-smooth-diagonals", - "movemouse-inherit-accel-state", - ]; - let mut cfg = HashMap::default(); - let mut exprs = check_first_expr(expr.iter(), "defcfg")?; - // Read k-v pairs from the configuration - loop { - let key = match exprs.next() { - Some(k) => k, - None => return Ok(cfg), - }; - let val = match exprs.next() { - Some(v) => v, - None => bail_expr!(key, "Found a defcfg option missing a value"), - }; - match (&key, &val) { - (SExpr::Atom(k), SExpr::Atom(v)) => { - if non_bool_cfg_keys.contains(&&*k.t) { - // nothing to do - } else if bool_cfg_keys.contains(&&*k.t) { - if !BOOLEAN_VALUES.contains(&&*v.t) { - bail_expr!( - val, - "The value for {} must be one of: {}", - k.t, - BOOLEAN_VALUES.join(", ") - ); - } - } else { - bail_expr!(key, "Unknown defcfg option {}", k.t); - } - if cfg - .insert( - k.t.trim_matches('"').to_owned(), - v.t.trim_matches('"').to_owned(), - ) - .is_some() - { - bail_expr!(key, "Duplicate defcfg option {}", k.t); - } - } - (SExpr::List(_), _) => { - bail_expr!(key, "Lists are not allowed in defcfg"); - } - (_, SExpr::List(_)) => { - bail_expr!(val, "Lists are not allowed in defcfg"); - } - } - } -} - /// Parse custom keys from an expression starting with deflocalkeys. fn parse_deflocalkeys( def_local_keys_variant: &str, expr: &[SExpr], ) -> Result> { - let mut cfg = HashMap::default(); + let mut localkeys = HashMap::default(); let mut exprs = check_first_expr(expr.iter(), def_local_keys_variant)?; // Read k-v pairs from the configuration while let Some(key_expr) = exprs.next() { @@ -824,7 +737,7 @@ fn parse_deflocalkeys( key_expr, "Cannot use {key} in {def_local_keys_variant} because it is a default key name" ); - } else if cfg.contains_key(key) { + } else if localkeys.contains_key(key) { bail_expr!( key_expr, "Duplicate {key} found in {def_local_keys_variant}" @@ -847,18 +760,15 @@ fn parse_deflocalkeys( None => bail_expr!(key_expr, "Key without a number in {def_local_keys_variant}"), }; log::debug!("custom mapping: {key} {}", osc.as_u16()); - cfg.insert(key.to_owned(), osc); + localkeys.insert(key.to_owned(), osc); } - Ok(cfg) + Ok(localkeys) } /// Parse mapped keys from an expression starting with defsrc. Returns the key mapping as well as /// a vec of the indexes in order. The length of the returned vec should be matched by the length /// of all layer declarations. -fn parse_defsrc( - expr: &[SExpr], - defcfg: &HashMap, -) -> Result<(MappedKeys, Vec)> { +fn parse_defsrc(expr: &[SExpr], defcfg: &CfgOptions) -> Result<(MappedKeys, Vec)> { let exprs = check_first_expr(expr.iter(), "defsrc")?; let mut mkeys = MappedKeys::default(); let mut ordered_codes = Vec::new(); @@ -876,12 +786,8 @@ fn parse_defsrc( ordered_codes.push(oscode.into()); } - let process_unmapped_keys = defcfg - .get("process-unmapped-keys") - .map(|s| TRUE_VALUES.contains(&s.to_lowercase().as_str())) - .unwrap_or(false); - log::info!("process unmapped keys: {process_unmapped_keys}"); - if process_unmapped_keys { + log::info!("process unmapped keys: {}", defcfg.process_unmapped_keys); + if defcfg.process_unmapped_keys { for osc in 0..KEYS_IN_ROW as u16 { if let Some(osc) = OsCode::from_u16(osc) { match KeyCode::from(osc) { @@ -942,11 +848,11 @@ pub struct ParsedState { fake_keys: HashMap, chord_groups: HashMap, defsrc_layer: [KanataAction; KEYS_IN_ROW], + vars: HashMap, is_cmd_enabled: bool, delegate_to_first_layer: bool, default_sequence_timeout: u16, default_sequence_input_mode: SequenceInputMode, - vars: HashMap, a: Arc, } @@ -956,14 +862,9 @@ impl ParsedState { } } -const SEQUENCE_TIMEOUT_CFG_NAME: &str = "sequence-timeout"; -const SEQUENCE_INPUT_MODE_CFG_NAME: &str = "sequence-input-mode"; -const SEQUENCE_TIMEOUT_ERR: &str = "sequence-timeout should be a number (1-65535)"; -const SEQUENCE_TIMEOUT_DEFAULT: u16 = 1000; -const SEQUENCE_INPUT_MODE_DEFAULT: SequenceInputMode = SequenceInputMode::HiddenSuppressed; - impl Default for ParsedState { fn default() -> Self { + let default_cfg = CfgOptions::default(); Self { layer_exprs: Default::default(), aliases: Default::default(), @@ -972,11 +873,11 @@ impl Default for ParsedState { defsrc_layer: [KanataAction::Trans; KEYS_IN_ROW], fake_keys: Default::default(), chord_groups: Default::default(), - is_cmd_enabled: false, - delegate_to_first_layer: false, vars: Default::default(), - default_sequence_timeout: SEQUENCE_TIMEOUT_DEFAULT, - default_sequence_input_mode: SEQUENCE_INPUT_MODE_DEFAULT, + is_cmd_enabled: default_cfg.enable_cmd, + delegate_to_first_layer: default_cfg.delegate_to_first_layer, + default_sequence_timeout: default_cfg.sequence_timeout, + default_sequence_input_mode: default_cfg.sequence_input_mode, a: unsafe { Allocations::new() }, } } @@ -2360,14 +2261,13 @@ fn parse_fake_key_op_coord_action( }; let action = ac_params[1] .atom(s.vars()) - .map(|a| match a { + .and_then(|a| match a { "tap" => Some(FakeKeyAction::Tap), "press" => Some(FakeKeyAction::Press), "release" => Some(FakeKeyAction::Release), "toggle" => Some(FakeKeyAction::Toggle), _ => None, }) - .flatten() .ok_or_else(|| { anyhow_expr!( &ac_params[1], @@ -2937,7 +2837,7 @@ fn parse_sequence_start(ac_params: &[SExpr], s: &ParsedState) -> Result<&'static let input_mode = if ac_params.len() > 1 { if let Some(Ok(input_mode)) = ac_params[1] .atom(s.vars()) - .map(|config_str| SequenceInputMode::try_from_str(config_str)) + .map(SequenceInputMode::try_from_str) { input_mode } else { @@ -3106,13 +3006,12 @@ fn parse_on_idle_fakekey(ac_params: &[SExpr], s: &ParsedState) -> Result<&'stati }; let action = ac_params[1] .atom(s.vars()) - .map(|a| match a { + .and_then(|a| match a { "tap" => Some(FakeKeyAction::Tap), "press" => Some(FakeKeyAction::Press), "release" => Some(FakeKeyAction::Release), _ => None, }) - .flatten() .ok_or_else(|| { anyhow_expr!( &ac_params[1], diff --git a/parser/src/cfg/tests.rs b/parser/src/cfg/tests.rs index fc69003f2..fa55ac4f6 100644 --- a/parser/src/cfg/tests.rs +++ b/parser/src/cfg/tests.rs @@ -1198,3 +1198,54 @@ fn list_action_not_in_list_error_message_is_good() { }) .unwrap_err(); } + +#[test] +fn parse_device_paths() { + assert_eq!(parse_colon_separated_text("h:w"), ["h", "w"]); + assert_eq!(parse_colon_separated_text("h\\:w"), ["h:w"]); + assert_eq!(parse_colon_separated_text("h\\:w\\"), ["h:w\\"]); +} + +#[test] +fn parse_all_defcfg() { + let _lk = match CFG_PARSE_LOCK.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + }; + let source = r#" +(defcfg + process-unmapped-keys yes + danger-enable-cmd yes + sequence-timeout 2000 + sequence-input-mode visible-backspaced + sequence-backtrack-modcancel no + log-layer-changes no + delegate-to-first-layer yes + movemouse-inherit-accel-state yes + movemouse-smooth-diagonals yes + dynamic-macro-max-presses 1000 + linux-dev /dev/input/dev1:/dev/input/dev2 + linux-dev-names-include "Name 1:Name 2" + linux-dev-names-exclude "Name 3:Name 4" + linux-continue-if-no-devs-found yes + linux-unicode-u-code v + linux-unicode-termination space + linux-x11-repeat-delay-rate 400,50 + windows-altgr add-lctl-release + windows-interception-mouse-hwid "70, 0, 60, 0" +) +(defsrc a) +(deflayer base a) +"#; + let mut s = ParsedState::default(); + parse_cfg_raw_string( + source, + &mut s, + &PathBuf::from("test"), + &mut FileContentProvider { + get_file_content_fn: &mut |_| unimplemented!(), + }, + DEF_LOCAL_KEYS, + ) + .expect("succeeds"); +} diff --git a/src/kanata/linux.rs b/src/kanata/linux.rs index 0497b2269..8be4cee86 100644 --- a/src/kanata/linux.rs +++ b/src/kanata/linux.rs @@ -29,7 +29,7 @@ impl Kanata { // In some environments, this needs to be done after the input device grab otherwise it // does not work on kanata startup. - Kanata::set_repeat_rate(&k.defcfg_items)?; + Kanata::set_repeat_rate(k.x11_repeat_rate)?; drop(k); loop { @@ -83,28 +83,20 @@ impl Kanata { Ok(()) } - pub fn set_repeat_rate(cfg_items: &HashMap) -> Result<()> { - if let Some(x11_rpt_str) = cfg_items.get("linux-x11-repeat-delay-rate") { - let delay_rate = x11_rpt_str.split(',').collect::>(); - let errmsg = format!("Invalid value for linux-x11-repeat-delay-rate: \"{x11_rpt_str}\".\nExpected two numbers 0-65535 separated by a comma, e.g. 200,25"); - if delay_rate.len() != 2 { - log::error!("{errmsg}"); - } - str::parse::(delay_rate[0]).map_err(|e| { - log::error!("{errmsg}"); - e - })?; - str::parse::(delay_rate[1]).map_err(|e| { - log::error!("{errmsg}"); - e - })?; + pub fn set_repeat_rate(s: Option) -> Result<()> { + if let Some(s) = s { log::info!( "Using xset to set X11 repeat delay to {} and repeat rate to {}", - delay_rate[0], - delay_rate[1] + s.delay, + s.rate, ); let cmd_output = std::process::Command::new("xset") - .args(["r", "rate", delay_rate[0], delay_rate[1]]) + .args([ + "r", + "rate", + s.delay.to_string().as_str(), + s.rate.to_string().as_str(), + ]) .output() .map_err(|e| { log::error!("failed to run xset: {e:?}"); diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index b4d35dfda..957f71f99 100644 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -1,6 +1,6 @@ //! Implements the glue between OS input/output and keyberon state management. -use anyhow::{anyhow, bail, Result}; +use anyhow::{bail, Result}; use log::{error, info}; use parking_lot::Mutex; use std::sync::mpsc::{Receiver, SyncSender as Sender, TryRecvError}; @@ -132,14 +132,14 @@ pub struct Kanata { #[cfg(all(feature = "interception_driver", target_os = "windows"))] /// Used to know which input device to treat as a mouse for intercepting and processing inputs /// by kanata. - intercept_mouse_hwid: Option>, + intercept_mouse_hwid: Option<[u8; HWID_ARR_SZ]>, /// User configuration to do logging of layer changes or not. log_layer_changes: bool, /// Tracks the caps-word state. Is Some(...) if caps-word is active and None otherwise. pub caps_word: Option, /// Config items from `defcfg`. #[cfg(target_os = "linux")] - pub defcfg_items: HashMap, + pub x11_repeat_rate: Option, /// Fake key actions that are waiting for a certain duration of keyboard idling. pub waiting_for_idle: HashSet, /// Number of ticks since kanata was idle. @@ -259,23 +259,6 @@ impl Kanata { } }; - #[cfg(all(feature = "interception_driver", target_os = "windows"))] - let intercept_mouse_hwid = cfg - .items - .get("windows-interception-mouse-hwid") - .map(|hwid: &String| { - log::trace!("win hwid: {hwid}"); - hwid.split_whitespace() - .try_fold(vec![], |mut hwid_bytes, hwid_byte| { - hwid_byte.trim_matches(',').parse::().map(|b| { - hwid_bytes.push(b); - hwid_bytes - }) - }) - .ok() - }) - .unwrap_or_default(); - let kbd_out = match KbdOut::new( #[cfg(target_os = "linux")] &args.symlink_path, @@ -287,26 +270,6 @@ impl Kanata { } }; - #[cfg(target_os = "linux")] - let kbd_in_paths = cfg - .items - .get("linux-dev") - .cloned() - .map(|paths| parse_colon_separated_text(&paths)) - .unwrap_or_default(); - #[cfg(target_os = "linux")] - let include_names = cfg - .items - .get("linux-dev-names-include") - .cloned() - .map(|paths| parse_colon_separated_text(&paths)); - #[cfg(target_os = "linux")] - let exclude_names = cfg - .items - .get("linux-dev-names-exclude") - .cloned() - .map(|paths| parse_colon_separated_text(&paths)); - #[cfg(target_os = "windows")] unsafe { log::info!("Asking Windows to improve timer precision"); @@ -325,18 +288,9 @@ impl Kanata { } update_kbd_out(&cfg.items, &kbd_out)?; - set_altgr_behaviour(&cfg)?; - - let sequence_backtrack_modcancel = cfg - .items - .get("sequence-backtrack-modcancel") - .map(|s| !FALSE_VALUES.contains(&s.to_lowercase().as_str())) - .unwrap_or(true); - let log_layer_changes = cfg - .items - .get("log-layer-changes") - .map(|s| !FALSE_VALUES.contains(&s.to_lowercase().as_str())) - .unwrap_or(true); + + #[cfg(target_os = "windows")] + set_win_altgr_behaviour(cfg.items.windows_altgr); *MAPPED_KEYS.lock() = cfg.mapped_keys; @@ -355,7 +309,7 @@ impl Kanata { move_mouse_state_vertical: None, move_mouse_state_horizontal: None, move_mouse_speed_modifiers: Vec::new(), - sequence_backtrack_modcancel, + sequence_backtrack_modcancel: cfg.items.sequence_backtrack_modcancel, sequence_state: None, sequences: cfg.sequences, last_tick: time::Instant::now(), @@ -364,42 +318,25 @@ impl Kanata { overrides: cfg.overrides, override_states: OverrideStates::new(), #[cfg(target_os = "linux")] - kbd_in_paths, + kbd_in_paths: cfg.items.linux_dev, #[cfg(target_os = "linux")] - continue_if_no_devices: cfg - .items - .get("linux-continue-if-no-devs-found") - .map(|s| TRUE_VALUES.contains(&s.to_lowercase().as_str())) - .unwrap_or_default(), + continue_if_no_devices: cfg.items.linux_continue_if_no_devs_found, #[cfg(target_os = "linux")] - include_names, + include_names: cfg.items.linux_dev_names_include, #[cfg(target_os = "linux")] - exclude_names, + exclude_names: cfg.items.linux_dev_names_exclude, #[cfg(all(feature = "interception_driver", target_os = "windows"))] - intercept_mouse_hwid, + intercept_mouse_hwid: cfg.items.windows_interception_mouse_hwid, dynamic_macro_replay_state: None, dynamic_macro_record_state: None, dynamic_macros: Default::default(), - log_layer_changes, + log_layer_changes: cfg.items.log_layer_changes, caps_word: None, - movemouse_smooth_diagonals: cfg - .items - .get("movemouse-smooth-diagonals") - .map(|s| TRUE_VALUES.contains(&s.to_lowercase().as_str())) - .unwrap_or_default(), - movemouse_inherit_accel_state: cfg - .items - .get("movemouse-inherit-accel-state") - .map(|s| TRUE_VALUES.contains(&s.to_lowercase().as_str())) - .unwrap_or_default(), - dynamic_macro_max_presses: cfg - .items - .get("dynamic-macro-max-presses") - .map(|s| s.parse::()) - .unwrap_or(Ok(128)) - .map_err(|e| anyhow!("dynamic-macro-max-presses must be 0-65535: {e}"))?, + movemouse_smooth_diagonals: cfg.items.movemouse_smooth_diagonals, + movemouse_inherit_accel_state: cfg.items.movemouse_inherit_accel_state, + dynamic_macro_max_presses: cfg.items.dynamic_macro_max_presses, #[cfg(target_os = "linux")] - defcfg_items: cfg.items, + x11_repeat_rate: cfg.items.linux_x11_repeat_delay_rate, waiting_for_idle: HashSet::default(), ticks_since_idle: 0, movemouse_buffer: None, @@ -422,41 +359,22 @@ impl Kanata { } }; update_kbd_out(&cfg.items, &self.kbd_out)?; - set_altgr_behaviour(&cfg).map_err(|e| anyhow!("failed to set altgr behaviour {e})"))?; - let log_layer_changes = cfg - .items - .get("log-layer-changes") - .map(|s| !FALSE_VALUES.contains(&s.to_lowercase().as_str())) - .unwrap_or(true); - self.sequence_backtrack_modcancel = cfg - .items - .get("sequence-backtrack-modcancel") - .map(|s| !FALSE_VALUES.contains(&s.to_lowercase().as_str())) - .unwrap_or(true); + #[cfg(target_os = "windows")] + set_win_altgr_behaviour(cfg.items.windows_altgr); + self.sequence_backtrack_modcancel = cfg.items.sequence_backtrack_modcancel; self.layout = cfg.layout; self.key_outputs = cfg.key_outputs; self.layer_info = cfg.layer_info; self.sequences = cfg.sequences; self.overrides = cfg.overrides; - self.log_layer_changes = log_layer_changes; - self.movemouse_smooth_diagonals = cfg - .items - .get("movemouse-smooth-diagonals") - .map(|s| TRUE_VALUES.contains(&s.to_lowercase().as_str())) - .unwrap_or_default(); - self.movemouse_inherit_accel_state = cfg - .items - .get("movemouse-inherit-accel-state") - .map(|s| TRUE_VALUES.contains(&s.to_lowercase().as_str())) - .unwrap_or_default(); - self.dynamic_macro_max_presses = cfg - .items - .get("dynamic-macro-max-presses") - .map(|s| s.parse::()) - .unwrap_or(Ok(128)) - .map_err(|_| anyhow!("dynamic-macro-max-presses must be 0-65535"))?; + self.log_layer_changes = cfg.items.log_layer_changes; + self.movemouse_smooth_diagonals = cfg.items.movemouse_smooth_diagonals; + self.movemouse_inherit_accel_state = cfg.items.movemouse_inherit_accel_state; + self.dynamic_macro_max_presses = cfg.items.dynamic_macro_max_presses; + *MAPPED_KEYS.lock() = cfg.mapped_keys; - Kanata::set_repeat_rate(&cfg.items)?; + #[cfg(target_os = "linux")] + Kanata::set_repeat_rate(cfg.items.linux_x11_repeat_delay_rate)?; log::info!("Live reload successful"); Ok(()) } @@ -1836,12 +1754,6 @@ impl Kanata { } } -fn set_altgr_behaviour(_cfg: &cfg::Cfg) -> Result<()> { - #[cfg(target_os = "windows")] - set_win_altgr_behaviour(_cfg)?; - Ok(()) -} - #[cfg(feature = "cmd")] fn run_multi_cmd(cmds: Vec>) { std::thread::spawn(move || { @@ -1929,27 +1841,11 @@ fn check_for_exit(event: &KeyEvent) { } } -fn update_kbd_out(_cfg: &HashMap, _kbd_out: &KbdOut) -> Result<()> { +fn update_kbd_out(_cfg: &CfgOptions, _kbd_out: &KbdOut) -> Result<()> { #[cfg(target_os = "linux")] { - _kbd_out.update_unicode_termination( - _cfg.get("linux-unicode-termination").map(|s| { - match s.as_str() { - "enter" => Ok(UnicodeTermination::Enter), - "space" => Ok(UnicodeTermination::Space), - "enter-space" => Ok(UnicodeTermination::EnterSpace), - "space-enter" => Ok(UnicodeTermination::SpaceEnter), - _ => Err(anyhow!("linux-unicode-termination got {s}. It accepts: enter|space|enter-space|space-enter")), - } - }).unwrap_or(Ok(_kbd_out.unicode_termination.get()))?); - _kbd_out.update_unicode_u_code( - _cfg.get("linux-unicode-u-code") - .map(|s| { - str_to_oscode(s) - .ok_or_else(|| anyhow!("unknown code for linux-unicode-u-code {s}")) - }) - .unwrap_or(Ok(_kbd_out.unicode_u_code.get()))?, - ); + _kbd_out.update_unicode_termination(_cfg.linux_unicode_termination); + _kbd_out.update_unicode_u_code(_cfg.linux_unicode_u_code); } Ok(()) } diff --git a/src/kanata/windows/interception.rs b/src/kanata/windows/interception.rs index 88682dce2..56a3487ea 100644 --- a/src/kanata/windows/interception.rs +++ b/src/kanata/windows/interception.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use kanata_interception as ic; use parking_lot::Mutex; use std::sync::mpsc::SyncSender as Sender; @@ -9,8 +9,6 @@ use crate::kanata::*; use crate::oskbd::KeyValue; use kanata_parser::keys::OsCode; -const HWID_ARR_SZ: usize = 128; - impl Kanata { pub fn event_loop(kanata: Arc>, tx: Sender) -> Result<()> { let intrcptn = ic::Interception::new().ok_or_else(|| anyhow!("interception driver should init: have you completed the interception driver installation?"))?; @@ -21,20 +19,7 @@ impl Kanata { information: 0, }; 32]; - let mouse_to_intercept_hwid: Option<[u8; HWID_ARR_SZ]> = kanata - .lock() - .intercept_mouse_hwid.as_ref() - .map(|hwid| { - hwid.iter().copied().enumerate() - .fold([0u8; HWID_ARR_SZ], |mut hwid, idx_byte| { - let (i, b) = idx_byte; - if i > HWID_ARR_SZ { - panic!("windows-interception-mouse-hwid is too long; it should be up to {HWID_ARR_SZ} 8-bit unsigned integers"); - } - hwid[i] = b; - hwid - }) - }); + let mouse_to_intercept_hwid: Option<[u8; HWID_ARR_SZ]> = kanata.lock().intercept_mouse_hwid; if mouse_to_intercept_hwid.is_some() { intrcptn.set_filter( ic::is_mouse, diff --git a/src/kanata/windows/mod.rs b/src/kanata/windows/mod.rs index ce6d26ee9..609ecc72b 100644 --- a/src/kanata/windows/mod.rs +++ b/src/kanata/windows/mod.rs @@ -1,9 +1,8 @@ -use anyhow::{bail, Result}; +use anyhow::Result; use parking_lot::Mutex; use crate::kanata::*; -use kanata_parser::cfg; #[cfg(not(feature = "interception_driver"))] mod llhook; @@ -15,37 +14,13 @@ mod interception; #[cfg(feature = "interception_driver")] pub use self::interception::*; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum AltGrBehaviour { - DoNothing, - CancelLctlPress, - AddLctlRelease, -} - static PRESSED_KEYS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::default())); pub static ALTGR_BEHAVIOUR: Lazy> = - Lazy::new(|| Mutex::new(AltGrBehaviour::DoNothing)); + Lazy::new(|| Mutex::new(AltGrBehaviour::default())); -pub fn set_win_altgr_behaviour(cfg: &cfg::Cfg) -> Result<()> { - *ALTGR_BEHAVIOUR.lock() = { - const CANCEL: &str = "cancel-lctl-press"; - const ADD: &str = "add-lctl-release"; - match cfg.items.get("windows-altgr") { - None => AltGrBehaviour::DoNothing, - Some(cfg_val) => match cfg_val.as_str() { - CANCEL => AltGrBehaviour::CancelLctlPress, - ADD => AltGrBehaviour::AddLctlRelease, - _ => bail!( - "Invalid value for windows-altgr: {}. Valid values are {},{}", - cfg_val, - CANCEL, - ADD - ), - }, - } - }; - Ok(()) +pub fn set_win_altgr_behaviour(b: AltGrBehaviour) { + *ALTGR_BEHAVIOUR.lock() = b; } impl Kanata { @@ -130,9 +105,4 @@ impl Kanata { pub fn check_release_non_physical_shift(&mut self) -> Result<()> { Ok(()) } - - pub fn set_repeat_rate(_cfg_items: &HashMap) -> Result<()> { - // TODO: no-op right now - Ok(()) - } } diff --git a/src/oskbd/linux.rs b/src/oskbd/linux.rs index 98b2d8075..7ecc245dd 100644 --- a/src/oskbd/linux.rs +++ b/src/oskbd/linux.rs @@ -19,8 +19,8 @@ use std::thread; use super::*; use crate::{kanata::CalculatedMouseMove, oskbd::KeyEvent}; -use kanata_parser::custom_action::*; use kanata_parser::keys::*; +use kanata_parser::{cfg::UnicodeTermination, custom_action::*}; pub struct KbdIn { devices: HashMap, @@ -303,14 +303,6 @@ impl From for InputEvent { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum UnicodeTermination { - Enter, - Space, - SpaceEnter, - EnterSpace, -} - use std::cell::Cell; pub struct KbdOut { @@ -708,25 +700,6 @@ impl Symlink { } } -pub fn parse_colon_separated_text(paths: &str) -> Vec { - let mut all_paths = vec![]; - let mut full_dev_path = String::new(); - let mut dev_path_iter = paths.split(':').peekable(); - while let Some(dev_path) = dev_path_iter.next() { - if dev_path.ends_with('\\') && dev_path_iter.peek().is_some() { - full_dev_path.push_str(dev_path.trim_end_matches('\\')); - full_dev_path.push(':'); - continue; - } else { - full_dev_path.push_str(dev_path); - } - all_paths.push(full_dev_path.clone()); - full_dev_path.clear(); - } - all_paths.shrink_to_fit(); - all_paths -} - // Note for allow: the ioctl_read_buf triggers this clippy lint. // Note: CI does not yet support this lint, so also allowing unknown lints. #[allow(unknown_lints)] @@ -756,13 +729,6 @@ fn wait_for_all_keys_unpressed(dev: &Device) -> Result<(), io::Error> { Ok(()) } -#[test] -fn test_parse_dev_paths() { - assert_eq!(parse_colon_separated_text("h:w"), ["h", "w"]); - assert_eq!(parse_colon_separated_text("h\\:w"), ["h:w"]); - assert_eq!(parse_colon_separated_text("h\\:w\\"), ["h:w\\"]); -} - impl Drop for Symlink { fn drop(&mut self) { let _ = fs::remove_file(&self.dest);