From 9227c301ebdd5bf1c0c0f6349ffb02364561ffaf Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Thu, 3 Mar 2022 16:17:27 +0800 Subject: [PATCH 01/19] [feat] load multiple config files (very unstable) --- src/config.rs | 81 ++++++++++++++++++++++++++++++++++++++--------- src/tests.rs | 87 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 151 insertions(+), 17 deletions(-) diff --git a/src/config.rs b/src/config.rs index 96d52a2..f9bb37a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -62,32 +62,82 @@ pub struct Hotkey { // TODO: make the commented-out modifiers available pub enum Modifier { Super, - // Hyper, - // Meta, Alt, Control, Shift, - // ModeSwitch, - // Lock, - // Mod1, - // Mod2, - // Mod3, - // Mod4, - // Mod5, } -pub fn load(path: path::PathBuf) -> Result, Error> { - let file_contents = load_file_contents(path)?; - parse_contents(file_contents) +pub const IMPORT_STATEMENTS: [&str; 4] = ["use", "import", "include", "source"]; + +#[derive(Debug, PartialEq, Clone)] +pub struct Config { + pub path: path::PathBuf, + pub contents: String, + pub imports: Vec, } -pub fn load_file_contents(path: path::PathBuf) -> Result { +pub fn load_file_contents(path: &path::Path) -> Result { let mut file = File::open(path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) } +impl Config { + pub fn get_imports(contents: &str) -> Result, Error> { + let mut imports = Vec::new(); + for line in contents.lines() { + if IMPORT_STATEMENTS.contains(&line.split(' ').next().unwrap()) { + if let Some(import_path) = line.split(' ').nth(1) { + imports.push(path::Path::new(import_path).to_path_buf()); + } + } + } + Ok(imports) + } + pub fn new(path: path::PathBuf) -> Result { + let contents = load_file_contents(&path)?; + let imports = Self::get_imports(&contents)?; + Ok(Config { path, contents, imports }) + } + pub fn load_to_configs(&self) -> Result, Error> { + let mut configs = Vec::new(); + for import in &self.imports { + configs.push(Self::new(import.to_path_buf())?) + } + Ok(configs) + } + pub fn load_and_merge(mut configs: Vec) -> Result, Error> { + let mut prev_count = 0; + let mut current_count = configs.len(); + while prev_count != current_count { + prev_count = configs.len(); + for config in configs.clone() { + for import in Self::load_to_configs(&config)? { + if !configs.contains(&import) { + configs.push(import); + } + } + } + current_count = configs.len(); + } + Ok(configs) + } +} + +pub fn load(path: path::PathBuf) -> Result, Error> { + let mut hotkeys = Vec::new(); + let configs = vec![Config::new(path)?]; + for config in Config::load_and_merge(configs)? { + for hotkey in parse_contents(config.contents)? { + if !hotkeys.contains(&hotkey) { + hotkeys.push(hotkey); + } + } + } + Ok(hotkeys) +} + pub fn parse_contents(contents: String) -> Result, Error> { let key_to_evdev_key: HashMap<&str, evdev::Key> = HashMap::from([ ("q", evdev::Key::KEY_Q), @@ -220,7 +270,10 @@ pub fn parse_contents(contents: String) -> Result, Error> { // line number in a vector. let mut lines_with_types: Vec<(&str, u32)> = Vec::new(); for (line_number, line) in lines.iter().enumerate() { - if line.trim().starts_with('#') || line.trim().is_empty() { + if line.trim().starts_with('#') + || IMPORT_STATEMENTS.contains(&line.split(' ').next().unwrap()) + || line.trim().is_empty() + { continue; } if line.starts_with(' ') || line.starts_with('\t') { diff --git a/src/tests.rs b/src/tests.rs index 587a451..d4f1000 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,6 +1,6 @@ mod test_config { use crate::config::{ - extract_curly_brace, load_file_contents, parse_contents, Error, Hotkey, Modifier, + extract_curly_brace, load, load_file_contents, parse_contents, Error, Hotkey, Modifier, ParseError, }; use std::fs; @@ -110,7 +110,7 @@ mod test_config { fn test_nonexistent_file() { let path = path::PathBuf::from(r"This File Doesn't Exist"); - let result = load_file_contents(path); + let result = load_file_contents(&path); assert!(result.is_err()); @@ -136,11 +136,92 @@ q bspc node -q", )?; - let result = load_file_contents(setup.path()); + let result = load_file_contents(&setup.path()); assert!(result.is_ok()); Ok(()) } + #[test] + fn test_load_multiple_config() -> std::io::Result<()> { + let setup = TestPath::new("/tmp/swhkd-test-file2"); + let mut f = File::create(setup.path())?; + f.write_all( + b" +use /tmp/swhkd-test-file3 +super + b + firefox", + )?; + + let setup2 = TestPath::new("/tmp/swhkd-test-file3"); + let mut f2 = File::create(setup2.path())?; + f2.write_all( + b" +super + c + hello", + )?; + + let hotkeys = load(setup.path()); + assert_eq!( + hotkeys.unwrap(), + vec!( + Hotkey::new(evdev::Key::KEY_B, vec![Modifier::Super], String::from("firefox")), + Hotkey::new(evdev::Key::KEY_C, vec![Modifier::Super], String::from("hello")) + ) + ); + Ok(()) + } + + #[test] + fn test_more_configs() -> std::io::Result<()> { + let setup = TestPath::new("/tmp/swhkd-test-file4"); + let mut f = File::create(setup.path())?; + f.write_all( + b" +a + a", + )?; + + let setup2 = TestPath::new("/tmp/swhkd-test-file5"); + let mut f2 = File::create(setup2.path())?; + f2.write_all( + b" +use /tmp/swhkd-test-file4 +b + b", + )?; + let setup3 = TestPath::new("/tmp/swhkd-test-file6"); + let mut f3 = File::create(setup3.path())?; + f3.write_all( + b" +use /tmp/swhkd-test-file4 +use /tmp/swhkd-test-file5 +use /tmp/swhkd-test-file6 +use /tmp/swhkd-test-file7 +c + c", + )?; + let setup4 = TestPath::new("/tmp/swhkd-test-file7"); + let mut f4 = File::create(setup4.path())?; + f4.write_all( + b" +use /tmp/swhkd-test-file6 +d + d", + )?; + + let hotkeys = load(setup4.path()).unwrap(); + assert_eq!( + hotkeys, + vec!( + Hotkey::new(evdev::Key::KEY_D, vec![], String::from("d")), + Hotkey::new(evdev::Key::KEY_C, vec![], String::from("c")), + Hotkey::new(evdev::Key::KEY_A, vec![], String::from("a")), + Hotkey::new(evdev::Key::KEY_B, vec![], String::from("b")), + ) + ); + Ok(()) + } + #[test] fn test_basic_keybind() -> std::io::Result<()> { let contents = " From e5e60d5e48c88dea95d6dea8a5bd86b18d29b5c5 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Thu, 3 Mar 2022 16:41:00 +0800 Subject: [PATCH 02/19] [remove] remove alias patch cuz it won't work after the refactor --- src/alias_patch.diff | 144 ------------------------------------------- 1 file changed, 144 deletions(-) delete mode 100644 src/alias_patch.diff diff --git a/src/alias_patch.diff b/src/alias_patch.diff deleted file mode 100644 index aa09699..0000000 --- a/src/alias_patch.diff +++ /dev/null @@ -1,144 +0,0 @@ ---- config.rs 2022-02-15 14:03:04.796951155 +0800 -+++ config.rs.alias 2022-02-15 14:02:58.122950964 +0800 -@@ -214,12 +214,20 @@ - // as commands, and mark the other lines as keysyms. Mark means storing a line's type and the - // line number in a vector. - let mut lines_with_types: Vec<(&str, u32)> = Vec::new(); -+ let mut aliases: Vec<(&str, &str)> = Vec::new(); - for (line_number, line) in lines.iter().enumerate() { - if line.trim().starts_with('#') || line.trim().is_empty() { - continue; - } - if line.starts_with(' ') || line.starts_with('\t') { - lines_with_types.push(("command", line_number as u32)); -+ } else if line.starts_with("alias ") && line.contains('=') { -+ // things behind the alias and before the first equal sign are the alias name -+ // everything behind the first equal sign is the alias value -+ // store each name and value in aliases -+ let alias_name = &line[6..line.find('=').unwrap()].trim(); -+ let alias_value = &line[line.find('=').unwrap() + 1..]; -+ aliases.push((alias_name, alias_value)); - } else { - lines_with_types.push(("keysym", line_number as u32)); - } -@@ -260,7 +268,21 @@ - for (i, item) in actual_lines.iter().enumerate() { - let line_type = item.0; - let line_number = item.1; -- let line = &item.2; -+ let line = item.2.to_owned(); -+ // match all the aliases for the line -+ let match_aliases = |mut line: String| { -+ for alias in aliases.iter().rev() { -+ if line.contains(alias.0) { -+ line.replace_range( -+ line.find(alias.0).unwrap()..line.find(alias.0).unwrap() + alias.0.len(), -+ alias.1, -+ ); -+ } -+ } -+ line -+ }; -+ -+ let line = match_aliases(line.to_owned()); - - if line_type != "keysym" { - continue; -@@ -270,13 +292,14 @@ - if next_line.is_none() { - break; - } -- let next_line = next_line.unwrap(); -+ let mut next_line = next_line.unwrap().to_owned(); -+ next_line.2 = match_aliases(next_line.2.to_owned()); - - if next_line.0 != "command" { - continue; // this should ignore keysyms that are not followed by a command - } - -- let extracted_keys = extract_curly_brace(line); -+ let extracted_keys = extract_curly_brace(&line); - let extracted_commands = extract_curly_brace(&next_line.2); - - 'hotkey_parse: for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) { -@@ -1264,6 +1287,80 @@ - ], - ) - } -+ -+ #[test] -+ fn test_alias() -> std::io::Result<()> { -+ let contents = " -+alias ctal = control + alt -+alias pls =doas -+ctal + t -+ pls firefox # why always firefox -+alias ctals = ctal + shift -+ctals + t -+ brave"; -+ eval_config_test( -+ contents, -+ vec![ -+ Hotkey::new( -+ evdev::Key::KEY_T, -+ vec![Modifier::Control, Modifier::Alt], -+ "doas firefox # why always firefox".to_string(), -+ ), -+ Hotkey::new( -+ evdev::Key::KEY_T, -+ vec![Modifier::Control, Modifier::Alt, Modifier::Shift], -+ "brave".to_string(), -+ ), -+ ], -+ ) -+ } -+ -+ #[test] -+ fn test_invalid_alias() -> std::io::Result<()> { -+ let contents = " -+alias ctal = control + alt = -+ctal +t -+ firefox"; -+ eval_invalid_config_test(contents, ParseError::UnknownSymbol(3)) -+ } -+ -+ #[test] -+ fn test_not_an_alias() -> std::io::Result<()> { -+ let contents = " -+alias ctal # nothing here -+ctal + t -+ firefox"; -+ eval_invalid_config_test(contents, ParseError::UnknownSymbol(2)) -+ } -+ -+ #[test] -+ fn test_longer_alias_example() -> std::io::Result<()> { -+ let contents = " -+# Longer example -+alias pls=doas -+alias ctal = control + alt -+alias ctals = ctal + shift -+alias hello = #hello -+ctal + f hello -+ firefox -+ctals + b -+ brave hello"; -+ eval_config_test( -+ contents, -+ vec![ -+ Hotkey::new( -+ evdev::Key::KEY_F, -+ vec![Modifier::Control, Modifier::Alt], -+ "firefox".to_string(), -+ ), -+ Hotkey::new( -+ evdev::Key::KEY_B, -+ vec![Modifier::Control, Modifier::Alt, Modifier::Shift], -+ "brave #hello".to_string(), -+ ), -+ ], -+ ) -+ } - } - - #[cfg(test)] From 3b4c3b515dfcf581dd9718177f1fa894100eb319 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Thu, 3 Mar 2022 17:16:36 +0800 Subject: [PATCH 03/19] [update] config: use fs::canonicalize to handle relative paths --- Cargo.lock | 2 +- src/config.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f41124c..9c90eaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "Simple-Wayland-HotKey-Daemon" -version = "1.0.0" +version = "1.1.0" dependencies = [ "clap", "env_logger", diff --git a/src/config.rs b/src/config.rs index f9bb37a..b40beb7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ use itertools::Itertools; use std::collections::HashMap; -use std::fs::File; +use std::fs::{canonicalize, File}; use std::io::Read; use std::{fmt, path}; @@ -59,7 +59,6 @@ pub struct Hotkey { } #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] -// TODO: make the commented-out modifiers available pub enum Modifier { Super, Alt, @@ -89,7 +88,7 @@ impl Config { for line in contents.lines() { if IMPORT_STATEMENTS.contains(&line.split(' ').next().unwrap()) { if let Some(import_path) = line.split(' ').nth(1) { - imports.push(path::Path::new(import_path).to_path_buf()); + imports.push(canonicalize(path::Path::new(import_path))?); } } } From 23f22d657370d23ae41cccb16755b860453816e4 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Thu, 3 Mar 2022 17:37:55 +0800 Subject: [PATCH 04/19] [revert] config: still can't handle relative path --- src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index b40beb7..f10d8b4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ use itertools::Itertools; use std::collections::HashMap; -use std::fs::{canonicalize, File}; +use std::fs::File; use std::io::Read; use std::{fmt, path}; @@ -88,7 +88,7 @@ impl Config { for line in contents.lines() { if IMPORT_STATEMENTS.contains(&line.split(' ').next().unwrap()) { if let Some(import_path) = line.split(' ').nth(1) { - imports.push(canonicalize(path::Path::new(import_path))?); + imports.push(path::Path::new(import_path).to_path_buf()); } } } From c41c5a20740ca4849f4eeded54d0f6375ee7eb23 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sat, 5 Mar 2022 22:30:58 +0800 Subject: [PATCH 05/19] [refactor] begin to switch to oop style starting from some preparations for prefix feature --- src/config.rs | 61 +++++++++++++++++++++++++++++++++++++-------------- src/daemon.rs | 22 +++++++++---------- src/tests.rs | 10 +-------- 3 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/config.rs b/src/config.rs index f10d8b4..d112d4e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -51,21 +51,6 @@ impl fmt::Display for Error { } } -#[derive(Debug, Clone, PartialEq)] -pub struct Hotkey { - pub keysym: evdev::Key, - pub modifiers: Vec, - pub command: String, -} - -#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] -pub enum Modifier { - Super, - Alt, - Control, - Shift, -} - pub const IMPORT_STATEMENTS: [&str; 4] = ["use", "import", "include", "source"]; #[derive(Debug, PartialEq, Clone)] @@ -137,6 +122,48 @@ pub fn load(path: path::PathBuf) -> Result, Error> { Ok(hotkeys) } +#[derive(Debug, PartialEq, Clone)] +pub struct KeyBinding { + pub keysym: evdev::Key, + pub modifiers: Vec, + pub send: bool, + pub on_release: bool, +} + +impl KeyBinding { + pub fn new(keysym: evdev::Key, modifiers: Vec) -> Self { + KeyBinding { keysym, modifiers, send: false, on_release: false } + } + pub fn send(mut self) -> Self { + self.send = true; + self + } + pub fn on_release(mut self) -> Self { + self.on_release = true; + self + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Hotkey { + pub keybinding: KeyBinding, + pub command: String, +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] +pub enum Modifier { + Super, + Alt, + Control, + Shift, +} + +impl Hotkey { + pub fn new(keysym: evdev::Key, modifiers: Vec, command: String) -> Self { + Hotkey { keybinding: KeyBinding::new(keysym, modifiers), command } + } +} + pub fn parse_contents(contents: String) -> Result, Error> { let key_to_evdev_key: HashMap<&str, evdev::Key> = HashMap::from([ ("q", evdev::Key::KEY_Q), @@ -357,11 +384,11 @@ pub fn parse_contents(contents: String) -> Result, Error> { 'hotkey_parse: for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) { let (keysym, modifiers) = parse_keybind(key, line_number + 1, &key_to_evdev_key, &mod_to_mod_enum)?; - let hotkey = Hotkey { keysym, modifiers, command: command.to_string() }; + let hotkey = Hotkey::new(keysym, modifiers, command.to_string()); // Ignore duplicate hotkeys for i in hotkeys.iter() { - if i.keysym == hotkey.keysym && i.modifiers == hotkey.modifiers { + if i.keybinding == hotkey.keybinding { continue 'hotkey_parse; } } diff --git a/src/daemon.rs b/src/daemon.rs index e24e23f..e759c58 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -220,21 +220,21 @@ async fn main() -> Result<(), Box> { 1 => { if let Some(modifier) = modifiers_map.get(&key) { keyboard_state.state_modifiers.insert(*modifier); - } else { - keyboard_state.state_keysyms.insert(key); + // } else { + // keyboard_state.state_keysyms.insert(key); } } 0 => { if let Some(modifier) = modifiers_map.get(&key) { if let Some(hotkey) = &last_hotkey { - if hotkey.modifiers.contains(modifier) { + if hotkey.keybinding.modifiers.contains(modifier) { last_hotkey = None; } } keyboard_state.state_modifiers.remove(modifier); } else if keyboard_state.state_keysyms.contains(key) { if let Some(hotkey) = &last_hotkey { - if key == hotkey.keysym { + if key == hotkey.keybinding.keysym { last_hotkey = None; } } @@ -245,15 +245,15 @@ async fn main() -> Result<(), Box> { } let possible_hotkeys: Vec<&config::Hotkey> = hotkeys.iter() - .filter(|hotkey| hotkey.modifiers.len() == keyboard_state.state_modifiers.len()) + .filter(|hotkey| hotkey.keybinding.modifiers.len() == keyboard_state.state_modifiers.len()) .collect(); let event_in_hotkeys = hotkeys.iter().any(|hotkey| { - hotkey.keysym.code() == event.code() && + hotkey.keybinding.keysym.code() == event.code() && keyboard_state.state_modifiers .iter() - .all(|x| hotkey.modifiers.contains(x)) && - keyboard_state.state_modifiers.len() == hotkey.modifiers.len() + .all(|x| hotkey.keybinding.modifiers.contains(x)) && + keyboard_state.state_modifiers.len() == hotkey.keybinding.modifiers.len() }); @@ -285,9 +285,9 @@ async fn main() -> Result<(), Box> { for hotkey in possible_hotkeys { // this should check if state_modifiers and hotkey.modifiers have the same elements - if keyboard_state.state_modifiers.iter().all(|x| hotkey.modifiers.contains(x)) - && keyboard_state.state_modifiers.len() == hotkey.modifiers.len() - && keyboard_state.state_keysyms.contains(hotkey.keysym) + if keyboard_state.state_modifiers.iter().all(|x| hotkey.keybinding.modifiers.contains(x)) + && keyboard_state.state_modifiers.len() == hotkey.keybinding.modifiers.len() + && keyboard_state.state_keysyms.contains(hotkey.keybinding.keysym) { last_hotkey = Some(hotkey.clone()); send_command(hotkey.clone()); diff --git a/src/tests.rs b/src/tests.rs index d4f1000..63a5d4e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -34,12 +34,6 @@ mod test_config { } } - impl Hotkey { - fn new(keysym: evdev::Key, modifiers: Vec, command: String) -> Self { - Hotkey { keysym, modifiers, command } - } - } - // Wrapper for config tests fn eval_config_test(contents: &str, expected_hotkeys: Vec) -> std::io::Result<()> { let result = parse_contents(contents.to_string()); @@ -59,9 +53,7 @@ mod test_config { // to make sure that order does not matter for hotkey in actual_hotkeys { if let Some(index) = expected_hotkeys_mut.iter().position(|key| { - key.keysym == hotkey.keysym - && key.command == hotkey.command - && key.modifiers == hotkey.modifiers + key.keybinding == hotkey.keybinding && key.command == hotkey.command }) { expected_hotkeys_mut.remove(index); } else { From b82bd9813bc9443abb8aa6753a30e0a4b6557632 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sat, 5 Mar 2022 22:50:46 +0800 Subject: [PATCH 06/19] [refactor] added check for @ and ~ prefix --- src/config.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/config.rs b/src/config.rs index d112d4e..b8e9c9c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -159,6 +159,10 @@ pub enum Modifier { } impl Hotkey { + pub fn from_keybinding(keybinding: KeyBinding, command: String) -> Self { + Hotkey { keybinding, command } + } + #[cfg(test)] pub fn new(keysym: evdev::Key, modifiers: Vec, command: String) -> Self { Hotkey { keybinding: KeyBinding::new(keysym, modifiers), command } } @@ -382,9 +386,9 @@ pub fn parse_contents(contents: String) -> Result, Error> { let extracted_commands = extract_curly_brace(&next_line.2); 'hotkey_parse: for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) { - let (keysym, modifiers) = + let keybinding = parse_keybind(key, line_number + 1, &key_to_evdev_key, &mod_to_mod_enum)?; - let hotkey = Hotkey::new(keysym, modifiers, command.to_string()); + let hotkey = Hotkey::from_keybinding(keybinding, command.to_string()); // Ignore duplicate hotkeys for i in hotkeys.iter() { @@ -407,7 +411,7 @@ fn parse_keybind( line_nr: u32, key_to_evdev_key: &HashMap<&str, evdev::Key>, mod_to_mod_enum: &HashMap<&str, Modifier>, -) -> Result<(evdev::Key, Vec), Error> { +) -> Result { let line = line.split('#').next().unwrap(); let tokens: Vec = line.split('+').map(|s| s.trim().to_lowercase()).filter(|s| s != "_").collect(); @@ -422,6 +426,10 @@ fn parse_keybind( let last_token = tokens_new.last().unwrap().trim(); + let send: bool = last_token.starts_with('~'); + + let on_release: bool = last_token.starts_with('@'); + // Check if each token is valid for token in &tokens_new { if key_to_evdev_key.contains_key(token.as_str()) { @@ -447,7 +455,14 @@ fn parse_keybind( .map(|token| *mod_to_mod_enum.get(token.as_str()).unwrap()) .collect(); - Ok((*keysym, modifiers)) + let mut keybinding = KeyBinding::new(*keysym, modifiers); + if send { + keybinding = keybinding.send(); + } + if on_release { + keybinding = keybinding.on_release(); + } + Ok(keybinding) } pub fn extract_curly_brace(line: &str) -> Vec { From b0ff442d92664003570d6a290dc55cabc05c572f Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 6 Mar 2022 13:43:06 +0800 Subject: [PATCH 07/19] [update] tests for prefix --- src/config.rs | 39 +++++++++++++++++++++++++++++++++++---- src/daemon.rs | 23 +++++++++++++++-------- src/tests.rs | 28 +++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/src/config.rs b/src/config.rs index b8e9c9c..0c693a3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -130,15 +130,27 @@ pub struct KeyBinding { pub on_release: bool, } +pub trait Prefix { + fn send(self) -> Self; + fn on_release(self) -> Self; +} + impl KeyBinding { pub fn new(keysym: evdev::Key, modifiers: Vec) -> Self { KeyBinding { keysym, modifiers, send: false, on_release: false } } - pub fn send(mut self) -> Self { + pub fn on_release(mut self) -> Self { + self.on_release = true; + self + } +} + +impl Prefix for KeyBinding { + fn send(mut self) -> Self { self.send = true; self } - pub fn on_release(mut self) -> Self { + fn on_release(mut self) -> Self { self.on_release = true; self } @@ -168,6 +180,17 @@ impl Hotkey { } } +impl Prefix for Hotkey { + fn send(mut self) -> Self { + self.keybinding.send = true; + self + } + fn on_release(mut self) -> Self { + self.keybinding.on_release = true; + self + } +} + pub fn parse_contents(contents: String) -> Result, Error> { let key_to_evdev_key: HashMap<&str, evdev::Key> = HashMap::from([ ("q", evdev::Key::KEY_Q), @@ -432,7 +455,9 @@ fn parse_keybind( // Check if each token is valid for token in &tokens_new { - if key_to_evdev_key.contains_key(token.as_str()) { + if key_to_evdev_key.contains_key( + token.strip_prefix('@').unwrap_or_else(|| token.strip_prefix('~').unwrap_or(token)), + ) { // Can't have a key that's like a modifier if token != last_token { return Err(Error::InvalidConfig(ParseError::InvalidModifier(line_nr))); @@ -448,7 +473,13 @@ fn parse_keybind( } // Translate keypress into evdev key - let keysym = key_to_evdev_key.get(last_token).unwrap(); + let keysym = key_to_evdev_key + .get( + last_token + .strip_prefix('@') + .unwrap_or_else(|| last_token.strip_prefix('~').unwrap_or(last_token)), + ) + .unwrap(); let modifiers: Vec = tokens_new[0..(tokens_new.len() - 1)] .iter() diff --git a/src/daemon.rs b/src/daemon.rs index f049d5d..89fab59 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -143,14 +143,14 @@ async fn main() -> Result<(), Box> { 250 }; - fn send_command(hotkey: config::Hotkey) { + let send_command = |hotkey: config::Hotkey| { log::info!("Hotkey pressed: {:#?}", hotkey); if let Err(e) = sock_send(&hotkey.command) { log::error!("Failed to send command over IPC."); log::error!("Is swhks running?"); log::error!("{:#?}", e) } - } + }; let mut signals = Signals::new(&[ SIGUSR1, SIGUSR2, SIGHUP, SIGABRT, SIGBUS, SIGCHLD, SIGCONT, SIGINT, SIGPIPE, SIGQUIT, @@ -220,8 +220,8 @@ async fn main() -> Result<(), Box> { 1 => { if let Some(modifier) = modifiers_map.get(&key) { keyboard_state.state_modifiers.insert(*modifier); - // } else { - // keyboard_state.state_keysyms.insert(key); + } else { + keyboard_state.state_keysyms.insert(key); } } 0 => { @@ -243,6 +243,12 @@ async fn main() -> Result<(), Box> { } _ => {} } + for hotkey in &hotkeys { if hotkey.keybinding.keysym.code() == event.code() && + hotkey.keybinding.send { + uinput_device.emit(&[evdev::InputEvent::new(evdev::EventType::KEY, hotkey.keybinding.keysym.code(), event.value())]).unwrap(); + } + } + let possible_hotkeys: Vec<&config::Hotkey> = hotkeys.iter() .filter(|hotkey| hotkey.keybinding.modifiers.len() == keyboard_state.state_modifiers.len()) @@ -256,7 +262,6 @@ async fn main() -> Result<(), Box> { keyboard_state.state_modifiers.len() == hotkey.keybinding.modifiers.len() }); - // Don't emit event to virtual device if it's from a valid hotkey if !event_in_hotkeys { uinput_device.emit(&[event]).unwrap(); @@ -290,9 +295,11 @@ async fn main() -> Result<(), Box> { && keyboard_state.state_keysyms.contains(hotkey.keybinding.keysym) { last_hotkey = Some(hotkey.clone()); - send_command(hotkey.clone()); - hotkey_repeat_timer.as_mut().reset(Instant::now() + Duration::from_millis(repeat_cooldown_duration)); - break; + if !hotkey.keybinding.on_release { + send_command(hotkey.clone()); + hotkey_repeat_timer.as_mut().reset(Instant::now() + Duration::from_millis(repeat_cooldown_duration)); + break; + } } } } diff --git a/src/tests.rs b/src/tests.rs index 63a5d4e..77361b8 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,7 +1,7 @@ mod test_config { use crate::config::{ extract_curly_brace, load, load_file_contents, parse_contents, Error, Hotkey, Modifier, - ParseError, + ParseError, Prefix, }; use std::fs; use std::io::Write; @@ -1074,6 +1074,32 @@ super + {\\,, .} ], ) } + + #[test] + fn test_prefix_on_release() -> std::io::Result<()> { + let contents = " +super + @1 + 1"; + + eval_config_test( + contents, + vec![ + Hotkey::new(evdev::Key::KEY_1, vec![Modifier::Super], "1".to_string()).on_release() + ], + ) + } + + #[test] + fn test_prefix_send() -> std::io::Result<()> { + let contents = " +super + ~1 + 1"; + + eval_config_test( + contents, + vec![Hotkey::new(evdev::Key::KEY_1, vec![Modifier::Super], "1".to_string()).send()], + ) + } } mod test_config_display { From b33b3ae1ce1338b3de63218d923187fe4893abdb Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 6 Mar 2022 14:27:49 +0800 Subject: [PATCH 08/19] [update] revert implementations in daemon; add value trait --- src/config.rs | 37 +++++++++++++++++++++++++++++++++++++ src/daemon.rs | 38 ++++++++++++++++---------------------- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/config.rs b/src/config.rs index 0c693a3..b4ee680 100644 --- a/src/config.rs +++ b/src/config.rs @@ -135,6 +135,13 @@ pub trait Prefix { fn on_release(self) -> Self; } +pub trait Value { + fn keysym(&self) -> evdev::Key; + fn modifiers(&self) -> Vec; + fn is_send(&self) -> bool; + fn is_on_release(&self) -> bool; +} + impl KeyBinding { pub fn new(keysym: evdev::Key, modifiers: Vec) -> Self { KeyBinding { keysym, modifiers, send: false, on_release: false } @@ -156,6 +163,21 @@ impl Prefix for KeyBinding { } } +impl Value for KeyBinding { + fn keysym(&self) -> evdev::Key { + self.keysym + } + fn modifiers(&self) -> Vec { + self.clone().modifiers + } + fn is_send(&self) -> bool { + self.send + } + fn is_on_release(&self) -> bool { + self.on_release + } +} + #[derive(Debug, Clone, PartialEq)] pub struct Hotkey { pub keybinding: KeyBinding, @@ -191,6 +213,21 @@ impl Prefix for Hotkey { } } +impl Value for &Hotkey { + fn keysym(&self) -> evdev::Key { + self.keybinding.keysym + } + fn modifiers(&self) -> Vec { + self.keybinding.clone().modifiers + } + fn is_send(&self) -> bool { + self.keybinding.send + } + fn is_on_release(&self) -> bool { + self.keybinding.on_release + } +} + pub fn parse_contents(contents: String) -> Result, Error> { let key_to_evdev_key: HashMap<&str, evdev::Key> = HashMap::from([ ("q", evdev::Key::KEY_Q), diff --git a/src/daemon.rs b/src/daemon.rs index 89fab59..4f3bad8 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -19,6 +19,7 @@ use tokio_stream::{StreamExt, StreamMap}; use signal_hook::consts::signal::*; mod config; +use crate::config::Value; mod uinput; #[cfg(test)] @@ -143,14 +144,14 @@ async fn main() -> Result<(), Box> { 250 }; - let send_command = |hotkey: config::Hotkey| { + fn send_command(hotkey: config::Hotkey) { log::info!("Hotkey pressed: {:#?}", hotkey); if let Err(e) = sock_send(&hotkey.command) { log::error!("Failed to send command over IPC."); log::error!("Is swhks running?"); log::error!("{:#?}", e) } - }; + } let mut signals = Signals::new(&[ SIGUSR1, SIGUSR2, SIGHUP, SIGABRT, SIGBUS, SIGCHLD, SIGCONT, SIGINT, SIGPIPE, SIGQUIT, @@ -227,14 +228,14 @@ async fn main() -> Result<(), Box> { 0 => { if let Some(modifier) = modifiers_map.get(&key) { if let Some(hotkey) = &last_hotkey { - if hotkey.keybinding.modifiers.contains(modifier) { + if hotkey.modifiers().contains(modifier) { last_hotkey = None; } } keyboard_state.state_modifiers.remove(modifier); } else if keyboard_state.state_keysyms.contains(key) { if let Some(hotkey) = &last_hotkey { - if key == hotkey.keybinding.keysym { + if key == hotkey.keysym() { last_hotkey = None; } } @@ -243,25 +244,20 @@ async fn main() -> Result<(), Box> { } _ => {} } - for hotkey in &hotkeys { if hotkey.keybinding.keysym.code() == event.code() && - hotkey.keybinding.send { - uinput_device.emit(&[evdev::InputEvent::new(evdev::EventType::KEY, hotkey.keybinding.keysym.code(), event.value())]).unwrap(); - } - } - let possible_hotkeys: Vec<&config::Hotkey> = hotkeys.iter() - .filter(|hotkey| hotkey.keybinding.modifiers.len() == keyboard_state.state_modifiers.len()) + .filter(|hotkey| hotkey.modifiers().len() == keyboard_state.state_modifiers.len()) .collect(); let event_in_hotkeys = hotkeys.iter().any(|hotkey| { - hotkey.keybinding.keysym.code() == event.code() && + hotkey.keysym().code() == event.code() && keyboard_state.state_modifiers .iter() - .all(|x| hotkey.keybinding.modifiers.contains(x)) && - keyboard_state.state_modifiers.len() == hotkey.keybinding.modifiers.len() + .all(|x| hotkey.modifiers().contains(x)) && + keyboard_state.state_modifiers.len() == hotkey.modifiers().len() }); + // Don't emit event to virtual device if it's from a valid hotkey if !event_in_hotkeys { uinput_device.emit(&[event]).unwrap(); @@ -290,16 +286,14 @@ async fn main() -> Result<(), Box> { for hotkey in possible_hotkeys { // this should check if state_modifiers and hotkey.modifiers have the same elements - if keyboard_state.state_modifiers.iter().all(|x| hotkey.keybinding.modifiers.contains(x)) - && keyboard_state.state_modifiers.len() == hotkey.keybinding.modifiers.len() - && keyboard_state.state_keysyms.contains(hotkey.keybinding.keysym) + if keyboard_state.state_modifiers.iter().all(|x| hotkey.modifiers().contains(x)) + && keyboard_state.state_modifiers.len() == hotkey.modifiers().len() + && keyboard_state.state_keysyms.contains(hotkey.keysym()) { last_hotkey = Some(hotkey.clone()); - if !hotkey.keybinding.on_release { - send_command(hotkey.clone()); - hotkey_repeat_timer.as_mut().reset(Instant::now() + Duration::from_millis(repeat_cooldown_duration)); - break; - } + send_command(hotkey.clone()); + hotkey_repeat_timer.as_mut().reset(Instant::now() + Duration::from_millis(repeat_cooldown_duration)); + break; } } } From 134bc0c090247e5b8f4e78a4b719bd8ef4d44722 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 6 Mar 2022 15:18:32 +0800 Subject: [PATCH 09/19] [update] config: error display specific config file path --- src/config.rs | 51 ++++++++++++++++++++++++++++++--------------------- src/daemon.rs | 2 +- src/tests.rs | 37 +++++++++++++++++++------------------ 3 files changed, 50 insertions(+), 40 deletions(-) diff --git a/src/config.rs b/src/config.rs index b4ee680..0111a51 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,9 +14,9 @@ pub enum Error { #[derive(Debug, PartialEq)] pub enum ParseError { // u32 is the line number where an error occured - UnknownSymbol(u32), - InvalidModifier(u32), - InvalidKeysym(u32), + UnknownSymbol(path::PathBuf, u32), + InvalidModifier(path::PathBuf, u32), + InvalidKeysym(path::PathBuf, u32), } impl From for Error { @@ -37,14 +37,17 @@ impl fmt::Display for Error { Error::Io(io_err) => format!("I/O Error while parsing config file: {}", io_err).fmt(f), Error::InvalidConfig(parse_err) => match parse_err { - ParseError::UnknownSymbol(line_nr) => { - format!("Unknown symbol at line {}.", line_nr).fmt(f) + ParseError::UnknownSymbol(path, line_nr) => { + format!("Config file path: {:?}. Unknown symbol at line {}.", path, line_nr) + .fmt(f) } - ParseError::InvalidKeysym(line_nr) => { - format!("Invalid keysym at line {}.", line_nr).fmt(f) + ParseError::InvalidKeysym(path, line_nr) => { + format!("Config file path: {:?}. Invalid keysym at line {}.", path, line_nr) + .fmt(f) } - ParseError::InvalidModifier(line_nr) => { - format!("Invalid modifier at line {}.", line_nr).fmt(f) + ParseError::InvalidModifier(path, line_nr) => { + format!("Config file path: {:?}. Invalid modifier at line {}.", path, line_nr) + .fmt(f) } }, } @@ -79,15 +82,15 @@ impl Config { } Ok(imports) } - pub fn new(path: path::PathBuf) -> Result { + pub fn new(path: &path::PathBuf) -> Result { let contents = load_file_contents(&path)?; let imports = Self::get_imports(&contents)?; - Ok(Config { path, contents, imports }) + Ok(Config { path: path.to_path_buf(), contents, imports }) } pub fn load_to_configs(&self) -> Result, Error> { let mut configs = Vec::new(); for import in &self.imports { - configs.push(Self::new(import.to_path_buf())?) + configs.push(Self::new(&import.to_path_buf())?) } Ok(configs) } @@ -109,11 +112,11 @@ impl Config { } } -pub fn load(path: path::PathBuf) -> Result, Error> { +pub fn load(path: &path::PathBuf) -> Result, Error> { let mut hotkeys = Vec::new(); - let configs = vec![Config::new(path)?]; + let configs = vec![Config::new(&path)?]; for config in Config::load_and_merge(configs)? { - for hotkey in parse_contents(config.contents)? { + for hotkey in parse_contents(path.to_path_buf(), config.contents)? { if !hotkeys.contains(&hotkey) { hotkeys.push(hotkey); } @@ -228,7 +231,7 @@ impl Value for &Hotkey { } } -pub fn parse_contents(contents: String) -> Result, Error> { +pub fn parse_contents(path: path::PathBuf, contents: String) -> Result, Error> { let key_to_evdev_key: HashMap<&str, evdev::Key> = HashMap::from([ ("q", evdev::Key::KEY_Q), ("w", evdev::Key::KEY_W), @@ -446,8 +449,13 @@ pub fn parse_contents(contents: String) -> Result, Error> { let extracted_commands = extract_curly_brace(&next_line.2); 'hotkey_parse: for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) { - let keybinding = - parse_keybind(key, line_number + 1, &key_to_evdev_key, &mod_to_mod_enum)?; + let keybinding = parse_keybind( + path.clone(), + key, + line_number + 1, + &key_to_evdev_key, + &mod_to_mod_enum, + )?; let hotkey = Hotkey::from_keybinding(keybinding, command.to_string()); // Ignore duplicate hotkeys @@ -467,6 +475,7 @@ pub fn parse_contents(contents: String) -> Result, Error> { // and mod_to_mod enum instead of recreating them // after each function call because it's too expensive fn parse_keybind( + path: path::PathBuf, line: &str, line_nr: u32, key_to_evdev_key: &HashMap<&str, evdev::Key>, @@ -497,15 +506,15 @@ fn parse_keybind( ) { // Can't have a key that's like a modifier if token != last_token { - return Err(Error::InvalidConfig(ParseError::InvalidModifier(line_nr))); + return Err(Error::InvalidConfig(ParseError::InvalidModifier(path, line_nr))); } } else if mod_to_mod_enum.contains_key(token.as_str()) { // Can't have a modifier that's like a modifier if token == last_token { - return Err(Error::InvalidConfig(ParseError::InvalidKeysym(line_nr))); + return Err(Error::InvalidConfig(ParseError::InvalidKeysym(path, line_nr))); } } else { - return Err(Error::InvalidConfig(ParseError::UnknownSymbol(line_nr))); + return Err(Error::InvalidConfig(ParseError::UnknownSymbol(path, line_nr))); } } diff --git a/src/daemon.rs b/src/daemon.rs index 4f3bad8..0ba7768 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -93,7 +93,7 @@ async fn main() -> Result<(), Box> { exit(1); } - let hotkeys = match config::load(config_file_path) { + let hotkeys = match config::load(&config_file_path) { Err(e) => { log::error!("Config Error: {}", e); exit(1); diff --git a/src/tests.rs b/src/tests.rs index 77361b8..a884e0b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -36,7 +36,7 @@ mod test_config { // Wrapper for config tests fn eval_config_test(contents: &str, expected_hotkeys: Vec) -> std::io::Result<()> { - let result = parse_contents(contents.to_string()); + let result = parse_contents(path::PathBuf::new(), contents.to_string()); let mut expected_hotkeys_mut = expected_hotkeys; @@ -79,7 +79,7 @@ mod test_config { contents: &str, parse_error_type: ParseError, ) -> std::io::Result<()> { - let result = parse_contents(contents.to_string()); + let result = parse_contents(path::PathBuf::new(), contents.to_string()); assert!(result.is_err()); let result = result.unwrap_err(); @@ -152,7 +152,7 @@ super + c hello", )?; - let hotkeys = load(setup.path()); + let hotkeys = load(&setup.path()); assert_eq!( hotkeys.unwrap(), vec!( @@ -201,7 +201,7 @@ d d", )?; - let hotkeys = load(setup4.path()).unwrap(); + let hotkeys = load(&setup4.path()).unwrap(); assert_eq!( hotkeys, vec!( @@ -288,7 +288,7 @@ shift + k + m notify-send 'Hello world!' "; - eval_invalid_config_test(contents, ParseError::InvalidModifier(2)) + eval_invalid_config_test(contents, ParseError::InvalidModifier(path::PathBuf::new(), 2)) } #[test] @@ -298,7 +298,7 @@ shift + k + alt notify-send 'Hello world!' "; - eval_invalid_config_test(contents, ParseError::InvalidModifier(2)) + eval_invalid_config_test(contents, ParseError::InvalidModifier(path::PathBuf::new(), 2)) } #[test] @@ -310,7 +310,7 @@ shift + alt + notify-send 'Hello world!' "; - eval_invalid_config_test(contents, ParseError::UnknownSymbol(4)) + eval_invalid_config_test(contents, ParseError::UnknownSymbol(path::PathBuf::new(), 4)) } #[test] @@ -320,7 +320,7 @@ shift + alt + notify-send 'Hello world!' "; - eval_invalid_config_test(contents, ParseError::UnknownSymbol(2)) + eval_invalid_config_test(contents, ParseError::UnknownSymbol(path::PathBuf::new(), 2)) } #[test] @@ -391,7 +391,7 @@ pesto xterm "; - eval_invalid_config_test(contents, ParseError::UnknownSymbol(5)) + eval_invalid_config_test(contents, ParseError::UnknownSymbol(path::PathBuf::new(), 5)) } #[test] @@ -820,7 +820,7 @@ super + {a-c} super + {a-是} {firefox, brave} "; - eval_invalid_config_test(contents, ParseError::UnknownSymbol(2)) + eval_invalid_config_test(contents, ParseError::UnknownSymbol(path::PathBuf::new(), 2)) } #[test] @@ -829,7 +829,7 @@ super + {a-是} super + {bc-ad} {firefox, brave} "; - eval_invalid_config_test(contents, ParseError::UnknownSymbol(2)) + eval_invalid_config_test(contents, ParseError::UnknownSymbol(path::PathBuf::new(), 2)) } #[test] @@ -837,7 +837,7 @@ super + {bc-ad} let contents = " super + {a-} {firefox, brave}"; - eval_invalid_config_test(contents, ParseError::UnknownSymbol(2)) + eval_invalid_config_test(contents, ParseError::UnknownSymbol(path::PathBuf::new(), 2)) } #[test] @@ -1105,6 +1105,7 @@ super + ~1 mod test_config_display { use crate::config::{Error, ParseError}; use std::io; + use std::path; #[test] fn test_display_config_not_found_error() { @@ -1124,22 +1125,22 @@ mod test_config_display { #[test] fn test_display_unknown_symbol_error() { - let error = Error::InvalidConfig(ParseError::UnknownSymbol(10)); + let error = Error::InvalidConfig(ParseError::UnknownSymbol(path::PathBuf::new(), 10)); - assert_eq!(format!("{}", error), "Unknown symbol at line 10."); + assert_eq!(format!("{}", error), "Config file path: \"\". Unknown symbol at line 10."); } #[test] fn test_display_invalid_modifier_error() { - let error = Error::InvalidConfig(ParseError::InvalidModifier(25)); + let error = Error::InvalidConfig(ParseError::InvalidModifier(path::PathBuf::new(), 25)); - assert_eq!(format!("{}", error), "Invalid modifier at line 25."); + assert_eq!(format!("{}", error), "Config file path: \"\". Invalid modifier at line 25."); } #[test] fn test_invalid_keysm_error() { - let error = Error::InvalidConfig(ParseError::InvalidKeysym(7)); + let error = Error::InvalidConfig(ParseError::InvalidKeysym(path::PathBuf::new(), 7)); - assert_eq!(format!("{}", error), "Invalid keysym at line 7."); + assert_eq!(format!("{}", error), "Config file path: \"\". Invalid keysym at line 7."); } } From 54a768dd4881ead5922b93dff134a561508ab395 Mon Sep 17 00:00:00 2001 From: Aakash Sen Sharma <60808802+Shinyzenith@users.noreply.github.com> Date: Tue, 8 Mar 2022 15:35:32 +0530 Subject: [PATCH 10/19] Delete build.yml --- .github/workflows/build.yml | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 5cffdd8..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Build Swhkd - -on: [push] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - runs-on: ubuntu-latest - - permissions: - contents: write - - steps: - - uses: actions/checkout@v2 - - - name: Version tag. - run: echo "VERSION=$(grep -E '^version' ./Cargo.toml| sed 's/version = \"\(.*\)\"/\1/g')">>$GITHUB_ENV - - - name: Setup. - run: make setup - - - name: ZIP. - run: ./release.sh - - - name: Release - uses: "marvinpinto/action-automatic-releases@latest" - with: - repo_token: "${{ secrets.GITHUB_TOKEN }}" - automatic_release_tag: ${{ env.VERSION }} - prerelease: false - title: "Swhkd ${{ env.VERSION }}" - files: | - "*.zip" From ac4d7dfbe9bcb354357bea058a361e2f52c968b5 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Tue, 8 Mar 2022 19:59:53 +0800 Subject: [PATCH 11/19] merge --- src/config.rs | 73 +++++++++++++++++++--------------- src/tests.rs | 106 ++++++++++++++++++++++++++++++++------------------ 2 files changed, 110 insertions(+), 69 deletions(-) diff --git a/src/config.rs b/src/config.rs index 0111a51..13a30bc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,7 +2,10 @@ use itertools::Itertools; use std::collections::HashMap; use std::fs::File; use std::io::Read; -use std::{fmt, path}; +use std::{ + fmt, + path::{Path, PathBuf}, +}; #[derive(Debug)] pub enum Error { @@ -14,9 +17,9 @@ pub enum Error { #[derive(Debug, PartialEq)] pub enum ParseError { // u32 is the line number where an error occured - UnknownSymbol(path::PathBuf, u32), - InvalidModifier(path::PathBuf, u32), - InvalidKeysym(path::PathBuf, u32), + UnknownSymbol(PathBuf, u32), + InvalidModifier(PathBuf, u32), + InvalidKeysym(PathBuf, u32), } impl From for Error { @@ -35,35 +38,37 @@ impl fmt::Display for Error { Error::ConfigNotFound => "Config file not found.".fmt(f), Error::Io(io_err) => format!("I/O Error while parsing config file: {}", io_err).fmt(f), - Error::InvalidConfig(parse_err) => match parse_err { - ParseError::UnknownSymbol(path, line_nr) => { - format!("Config file path: {:?}. Unknown symbol at line {}.", path, line_nr) - .fmt(f) - } - ParseError::InvalidKeysym(path, line_nr) => { - format!("Config file path: {:?}. Invalid keysym at line {}.", path, line_nr) - .fmt(f) - } - ParseError::InvalidModifier(path, line_nr) => { - format!("Config file path: {:?}. Invalid modifier at line {}.", path, line_nr) - .fmt(f) - } + ParseError::UnknownSymbol(path, line_nr) => format!( + "Error parsing config file {:?}. Unknown symbol at line {}.", + path, line_nr + ) + .fmt(f), + ParseError::InvalidKeysym(path, line_nr) => format!( + "Error parsing config file {:?}. Invalid keysym at line {}.", + path, line_nr + ) + .fmt(f), + ParseError::InvalidModifier(path, line_nr) => format!( + "Error parsing config file {:?}. Invalid modifier at line {}.", + path, line_nr + ) + .fmt(f), }, } } } -pub const IMPORT_STATEMENTS: [&str; 4] = ["use", "import", "include", "source"]; +pub const IMPORT_STATEMENT: &str = "import"; #[derive(Debug, PartialEq, Clone)] pub struct Config { - pub path: path::PathBuf, + pub path: PathBuf, pub contents: String, - pub imports: Vec, + pub imports: Vec, } -pub fn load_file_contents(path: &path::Path) -> Result { +pub fn load_file_contents(path: &Path) -> Result { let mut file = File::open(path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; @@ -71,29 +76,32 @@ pub fn load_file_contents(path: &path::Path) -> Result { } impl Config { - pub fn get_imports(contents: &str) -> Result, Error> { + pub fn get_imports(contents: &str) -> Result, Error> { let mut imports = Vec::new(); for line in contents.lines() { - if IMPORT_STATEMENTS.contains(&line.split(' ').next().unwrap()) { + if line.split(' ').next().unwrap() == IMPORT_STATEMENT { if let Some(import_path) = line.split(' ').nth(1) { - imports.push(path::Path::new(import_path).to_path_buf()); + imports.push(Path::new(import_path).to_path_buf()); } } } Ok(imports) } - pub fn new(path: &path::PathBuf) -> Result { - let contents = load_file_contents(&path)?; + + pub fn new(path: &Path) -> Result { + let contents = load_file_contents(path)?; let imports = Self::get_imports(&contents)?; Ok(Config { path: path.to_path_buf(), contents, imports }) } + pub fn load_to_configs(&self) -> Result, Error> { let mut configs = Vec::new(); for import in &self.imports { - configs.push(Self::new(&import.to_path_buf())?) + configs.push(Self::new(import)?) } Ok(configs) } + pub fn load_and_merge(mut configs: Vec) -> Result, Error> { let mut prev_count = 0; let mut current_count = configs.len(); @@ -112,9 +120,9 @@ impl Config { } } -pub fn load(path: &path::PathBuf) -> Result, Error> { +pub fn load(path: &Path) -> Result, Error> { let mut hotkeys = Vec::new(); - let configs = vec![Config::new(&path)?]; + let configs = vec![Config::new(path)?]; for config in Config::load_and_merge(configs)? { for hotkey in parse_contents(path.to_path_buf(), config.contents)? { if !hotkeys.contains(&hotkey) { @@ -231,7 +239,7 @@ impl Value for &Hotkey { } } -pub fn parse_contents(path: path::PathBuf, contents: String) -> Result, Error> { +pub fn parse_contents(path: PathBuf, contents: String) -> Result, Error> { let key_to_evdev_key: HashMap<&str, evdev::Key> = HashMap::from([ ("q", evdev::Key::KEY_Q), ("w", evdev::Key::KEY_W), @@ -364,7 +372,7 @@ pub fn parse_contents(path: path::PathBuf, contents: String) -> Result = Vec::new(); for (line_number, line) in lines.iter().enumerate() { if line.trim().starts_with('#') - || IMPORT_STATEMENTS.contains(&line.split(' ').next().unwrap()) + || line.split(' ').next().unwrap() == IMPORT_STATEMENT || line.trim().is_empty() { continue; @@ -468,6 +476,7 @@ pub fn parse_contents(path: path::PathBuf, contents: String) -> Result Result, diff --git a/src/tests.rs b/src/tests.rs index a884e0b..f92dd8a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -5,23 +5,23 @@ mod test_config { }; use std::fs; use std::io::Write; - use std::{fs::File, path}; + use std::{fs::File, path::PathBuf}; // Implement a struct for a path used in tests // so that the test file will be automatically removed // no matter how the test goes struct TestPath { - path: path::PathBuf, + path: PathBuf, } impl TestPath { fn new(path: &str) -> Self { - TestPath { path: path::PathBuf::from(path) } + TestPath { path: PathBuf::from(path) } } // Create a path method for a more succinct way // to deal with borrowing the path value - fn path(&self) -> path::PathBuf { + fn path(&self) -> PathBuf { self.path.clone() } } @@ -36,7 +36,7 @@ mod test_config { // Wrapper for config tests fn eval_config_test(contents: &str, expected_hotkeys: Vec) -> std::io::Result<()> { - let result = parse_contents(path::PathBuf::new(), contents.to_string()); + let result = parse_contents(PathBuf::new(), contents.to_string()); let mut expected_hotkeys_mut = expected_hotkeys; @@ -79,7 +79,7 @@ mod test_config { contents: &str, parse_error_type: ParseError, ) -> std::io::Result<()> { - let result = parse_contents(path::PathBuf::new(), contents.to_string()); + let result = parse_contents(PathBuf::new(), contents.to_string()); assert!(result.is_err()); let result = result.unwrap_err(); @@ -100,7 +100,7 @@ mod test_config { #[test] fn test_nonexistent_file() { - let path = path::PathBuf::from(r"This File Doesn't Exist"); + let path = PathBuf::from(r"This File Doesn't Exist"); let result = load_file_contents(&path); @@ -139,7 +139,7 @@ q let mut f = File::create(setup.path())?; f.write_all( b" -use /tmp/swhkd-test-file3 +import /tmp/swhkd-test-file3 super + b firefox", )?; @@ -164,7 +164,37 @@ super + c } #[test] - fn test_more_configs() -> std::io::Result<()> { + fn test_relative_import() -> std::io::Result<()> { + let setup = TestPath::new("/tmp/swhkd-relative-file1"); + let mut f = File::create(setup.path())?; + f.write_all( + b" +import swhkd-relative-file2 +super + b + firefox", + )?; + + let setup2 = TestPath::new("swhkd-relative-file2"); + let mut f2 = File::create(setup2.path())?; + f2.write_all( + b" +super + c + hello", + )?; + + let hotkeys = load(&setup.path()); + assert_eq!( + hotkeys.unwrap(), + vec!( + Hotkey::new(evdev::Key::KEY_B, vec![Modifier::Super], String::from("firefox")), + Hotkey::new(evdev::Key::KEY_C, vec![Modifier::Super], String::from("hello")) + ) + ); + Ok(()) + } + + #[test] + fn test_more_multiple_configs() -> std::io::Result<()> { let setup = TestPath::new("/tmp/swhkd-test-file4"); let mut f = File::create(setup.path())?; f.write_all( @@ -177,7 +207,7 @@ a let mut f2 = File::create(setup2.path())?; f2.write_all( b" -use /tmp/swhkd-test-file4 +import /tmp/swhkd-test-file4 b b", )?; @@ -185,10 +215,10 @@ b let mut f3 = File::create(setup3.path())?; f3.write_all( b" -use /tmp/swhkd-test-file4 -use /tmp/swhkd-test-file5 -use /tmp/swhkd-test-file6 -use /tmp/swhkd-test-file7 +import /tmp/swhkd-test-file4 +import /tmp/swhkd-test-file5 +import /tmp/swhkd-test-file6 +import /tmp/swhkd-test-file7 c c", )?; @@ -196,7 +226,7 @@ c let mut f4 = File::create(setup4.path())?; f4.write_all( b" -use /tmp/swhkd-test-file6 +import /tmp/swhkd-test-file6 d d", )?; @@ -288,7 +318,7 @@ shift + k + m notify-send 'Hello world!' "; - eval_invalid_config_test(contents, ParseError::InvalidModifier(path::PathBuf::new(), 2)) + eval_invalid_config_test(contents, ParseError::InvalidModifier(PathBuf::new(), 2)) } #[test] @@ -298,7 +328,7 @@ shift + k + alt notify-send 'Hello world!' "; - eval_invalid_config_test(contents, ParseError::InvalidModifier(path::PathBuf::new(), 2)) + eval_invalid_config_test(contents, ParseError::InvalidModifier(PathBuf::new(), 2)) } #[test] @@ -310,7 +340,7 @@ shift + alt + notify-send 'Hello world!' "; - eval_invalid_config_test(contents, ParseError::UnknownSymbol(path::PathBuf::new(), 4)) + eval_invalid_config_test(contents, ParseError::UnknownSymbol(PathBuf::new(), 4)) } #[test] @@ -320,7 +350,7 @@ shift + alt + notify-send 'Hello world!' "; - eval_invalid_config_test(contents, ParseError::UnknownSymbol(path::PathBuf::new(), 2)) + eval_invalid_config_test(contents, ParseError::UnknownSymbol(PathBuf::new(), 2)) } #[test] @@ -391,7 +421,7 @@ pesto xterm "; - eval_invalid_config_test(contents, ParseError::UnknownSymbol(path::PathBuf::new(), 5)) + eval_invalid_config_test(contents, ParseError::UnknownSymbol(PathBuf::new(), 5)) } #[test] @@ -820,7 +850,7 @@ super + {a-c} super + {a-是} {firefox, brave} "; - eval_invalid_config_test(contents, ParseError::UnknownSymbol(path::PathBuf::new(), 2)) + eval_invalid_config_test(contents, ParseError::UnknownSymbol(PathBuf::new(), 2)) } #[test] @@ -829,7 +859,7 @@ super + {a-是} super + {bc-ad} {firefox, brave} "; - eval_invalid_config_test(contents, ParseError::UnknownSymbol(path::PathBuf::new(), 2)) + eval_invalid_config_test(contents, ParseError::UnknownSymbol(PathBuf::new(), 2)) } #[test] @@ -837,7 +867,7 @@ super + {bc-ad} let contents = " super + {a-} {firefox, brave}"; - eval_invalid_config_test(contents, ParseError::UnknownSymbol(path::PathBuf::new(), 2)) + eval_invalid_config_test(contents, ParseError::UnknownSymbol(PathBuf::new(), 2)) } #[test] @@ -1105,14 +1135,7 @@ super + ~1 mod test_config_display { use crate::config::{Error, ParseError}; use std::io; - use std::path; - - #[test] - fn test_display_config_not_found_error() { - let error = Error::ConfigNotFound; - - assert_eq!(format!("{}", error), "Config file not found."); - } + use std::path::PathBuf; #[test] fn test_display_io_error() { @@ -1125,22 +1148,31 @@ mod test_config_display { #[test] fn test_display_unknown_symbol_error() { - let error = Error::InvalidConfig(ParseError::UnknownSymbol(path::PathBuf::new(), 10)); + let error = Error::InvalidConfig(ParseError::UnknownSymbol(PathBuf::new(), 10)); - assert_eq!(format!("{}", error), "Config file path: \"\". Unknown symbol at line 10."); + assert_eq!( + format!("{}", error), + "Error parsing config file \"\". Unknown symbol at line 10." + ); } #[test] fn test_display_invalid_modifier_error() { - let error = Error::InvalidConfig(ParseError::InvalidModifier(path::PathBuf::new(), 25)); + let error = Error::InvalidConfig(ParseError::InvalidModifier(PathBuf::new(), 25)); - assert_eq!(format!("{}", error), "Config file path: \"\". Invalid modifier at line 25."); + assert_eq!( + format!("{}", error), + "Error parsing config file \"\". Invalid modifier at line 25." + ); } #[test] fn test_invalid_keysm_error() { - let error = Error::InvalidConfig(ParseError::InvalidKeysym(path::PathBuf::new(), 7)); + let error = Error::InvalidConfig(ParseError::InvalidKeysym(PathBuf::new(), 7)); - assert_eq!(format!("{}", error), "Config file path: \"\". Invalid keysym at line 7."); + assert_eq!( + format!("{}", error), + "Error parsing config file \"\". Invalid keysym at line 7." + ); } } From 5d1ee70296ea07143d8b6dacc21673e106b50709 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Thu, 10 Mar 2022 20:38:50 +0800 Subject: [PATCH 12/19] [update] config: better handle prefixes --- src/config.rs | 47 ++++++++++++++++++++++++++++++++++------------- src/tests.rs | 29 +++++++++++++++-------------- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/src/config.rs b/src/config.rs index 13a30bc..8f7a03b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,6 +2,7 @@ use itertools::Itertools; use std::collections::HashMap; use std::fs::File; use std::io::Read; +// use std::str::pattern::Pattern; use std::{ fmt, path::{Path, PathBuf}, @@ -504,20 +505,46 @@ fn parse_keybind( let last_token = tokens_new.last().unwrap().trim(); - let send: bool = last_token.starts_with('~'); + // Check if last_token is prefixed with @ or ~ or even both. + // If prefixed @, on_release = true; if prefixed ~, send = true + let send = last_token.starts_with('~') || last_token.starts_with("@~"); + let on_release = last_token.starts_with('@') || last_token.starts_with("~@"); + + // Delete the @ and ~ in the last token + fn strip_at(token: &str) -> &str { + if token.starts_with('@') { + let token = token.strip_prefix('@').unwrap(); + strip_tilde(token) + } else if token.starts_with('~') { + strip_tilde(token) + } else { + token + } + } + + fn strip_tilde(token: &str) -> &str { + if token.starts_with('~') { + let token = token.strip_prefix('~').unwrap(); + strip_at(token) + } else if token.starts_with('@') { + strip_at(token) + } else { + token + } + } - let on_release: bool = last_token.starts_with('@'); + let last_token = strip_at(last_token); // Check if each token is valid for token in &tokens_new { - if key_to_evdev_key.contains_key( - token.strip_prefix('@').unwrap_or_else(|| token.strip_prefix('~').unwrap_or(token)), - ) { + let token = strip_at(token); + println!("{}", token); + if key_to_evdev_key.contains_key(token) { // Can't have a key that's like a modifier if token != last_token { return Err(Error::InvalidConfig(ParseError::InvalidModifier(path, line_nr))); } - } else if mod_to_mod_enum.contains_key(token.as_str()) { + } else if mod_to_mod_enum.contains_key(token) { // Can't have a modifier that's like a modifier if token == last_token { return Err(Error::InvalidConfig(ParseError::InvalidKeysym(path, line_nr))); @@ -528,13 +555,7 @@ fn parse_keybind( } // Translate keypress into evdev key - let keysym = key_to_evdev_key - .get( - last_token - .strip_prefix('@') - .unwrap_or_else(|| last_token.strip_prefix('~').unwrap_or(last_token)), - ) - .unwrap(); + let keysym = key_to_evdev_key.get(last_token).unwrap(); let modifiers: Vec = tokens_new[0..(tokens_new.len() - 1)] .iter() diff --git a/src/tests.rs b/src/tests.rs index f92dd8a..0685e4b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1109,27 +1109,28 @@ super + {\\,, .} fn test_prefix_on_release() -> std::io::Result<()> { let contents = " super + @1 - 1"; + 1 +super + ~2 + 2 +super + ~@3 + 3 +super + @~4 + 4"; eval_config_test( contents, vec![ - Hotkey::new(evdev::Key::KEY_1, vec![Modifier::Super], "1".to_string()).on_release() + Hotkey::new(evdev::Key::KEY_1, vec![Modifier::Super], "1".to_string()).on_release(), + Hotkey::new(evdev::Key::KEY_2, vec![Modifier::Super], "2".to_string()).send(), + Hotkey::new(evdev::Key::KEY_3, vec![Modifier::Super], "3".to_string()) + .on_release() + .send(), + Hotkey::new(evdev::Key::KEY_4, vec![Modifier::Super], "4".to_string()) + .on_release() + .send(), ], ) } - - #[test] - fn test_prefix_send() -> std::io::Result<()> { - let contents = " -super + ~1 - 1"; - - eval_config_test( - contents, - vec![Hotkey::new(evdev::Key::KEY_1, vec![Modifier::Super], "1".to_string()).send()], - ) - } } mod test_config_display { From 90a58d7e226505eaa56c16862522d526931e8758 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Thu, 10 Mar 2022 21:20:40 +0800 Subject: [PATCH 13/19] [feat] daemon: emit keysym to virtual device if it's prefixed with '~' --- src/config.rs | 1 - src/daemon.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8f7a03b..7935971 100644 --- a/src/config.rs +++ b/src/config.rs @@ -538,7 +538,6 @@ fn parse_keybind( // Check if each token is valid for token in &tokens_new { let token = strip_at(token); - println!("{}", token); if key_to_evdev_key.contains_key(token) { // Can't have a key that's like a modifier if token != last_token { diff --git a/src/daemon.rs b/src/daemon.rs index 0ba7768..bb4a67d 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -255,9 +255,9 @@ async fn main() -> Result<(), Box> { .iter() .all(|x| hotkey.modifiers().contains(x)) && keyboard_state.state_modifiers.len() == hotkey.modifiers().len() + && !hotkey.is_send() }); - // Don't emit event to virtual device if it's from a valid hotkey if !event_in_hotkeys { uinput_device.emit(&[event]).unwrap(); From 84bd11a0b6c4f45db0e428bcf192027a68231171 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Thu, 10 Mar 2022 22:09:42 +0800 Subject: [PATCH 14/19] [feat] daemon: run command when key is released if it's prefixed with '@' --- src/daemon.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/daemon.rs b/src/daemon.rs index bb4a67d..58a55b9 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -161,6 +161,7 @@ async fn main() -> Result<(), Box> { let mut temp_paused = false; let mut last_hotkey: Option = None; + let mut pending_release: Option = None; let mut keyboard_states: Vec = Vec::new(); let mut keyboard_stream_map = StreamMap::new(); @@ -179,6 +180,9 @@ async fn main() -> Result<(), Box> { select! { _ = &mut hotkey_repeat_timer, if &last_hotkey.is_some() => { let hotkey = last_hotkey.clone().unwrap(); + if hotkey.keybinding.on_release { + continue; + } send_command(hotkey.clone()); hotkey_repeat_timer.as_mut().reset(Instant::now() + Duration::from_millis(repeat_cooldown_duration)); } @@ -217,6 +221,11 @@ async fn main() -> Result<(), Box> { Some((i, Ok(event))) = keyboard_stream_map.next() => { let keyboard_state = &mut keyboard_states[i]; if let InputEventKind::Key(key) = event.kind() { + if last_hotkey.is_some() && pending_release.is_some() && event.value() == 0 && event.code() == pending_release.as_ref().unwrap().keysym().code() { + pending_release = None; + send_command(last_hotkey.clone().unwrap()); + last_hotkey = None; + } match event.value() { 1 => { if let Some(modifier) = modifiers_map.get(&key) { @@ -291,6 +300,10 @@ async fn main() -> Result<(), Box> { && keyboard_state.state_keysyms.contains(hotkey.keysym()) { last_hotkey = Some(hotkey.clone()); + if hotkey.is_on_release() { + pending_release = Some(hotkey.clone()); + break; + } send_command(hotkey.clone()); hotkey_repeat_timer.as_mut().reset(Instant::now() + Duration::from_millis(repeat_cooldown_duration)); break; From 1d4e190d07b29e5a8b34395802eaf0f96a8ce1a3 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Fri, 11 Mar 2022 20:57:00 +0800 Subject: [PATCH 15/19] [improve] Option -> bool, move if inside match --- src/daemon.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index 58a55b9..33a801a 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -161,7 +161,7 @@ async fn main() -> Result<(), Box> { let mut temp_paused = false; let mut last_hotkey: Option = None; - let mut pending_release: Option = None; + let mut pending_release: bool = false; let mut keyboard_states: Vec = Vec::new(); let mut keyboard_stream_map = StreamMap::new(); @@ -221,11 +221,6 @@ async fn main() -> Result<(), Box> { Some((i, Ok(event))) = keyboard_stream_map.next() => { let keyboard_state = &mut keyboard_states[i]; if let InputEventKind::Key(key) = event.kind() { - if last_hotkey.is_some() && pending_release.is_some() && event.value() == 0 && event.code() == pending_release.as_ref().unwrap().keysym().code() { - pending_release = None; - send_command(last_hotkey.clone().unwrap()); - last_hotkey = None; - } match event.value() { 1 => { if let Some(modifier) = modifiers_map.get(&key) { @@ -235,6 +230,11 @@ async fn main() -> Result<(), Box> { } } 0 => { + if last_hotkey.is_some() && pending_release { + pending_release = false; + send_command(last_hotkey.clone().unwrap()); + last_hotkey = None; + } if let Some(modifier) = modifiers_map.get(&key) { if let Some(hotkey) = &last_hotkey { if hotkey.modifiers().contains(modifier) { @@ -300,8 +300,9 @@ async fn main() -> Result<(), Box> { && keyboard_state.state_keysyms.contains(hotkey.keysym()) { last_hotkey = Some(hotkey.clone()); + if pending_release { break; } if hotkey.is_on_release() { - pending_release = Some(hotkey.clone()); + pending_release = true; break; } send_command(hotkey.clone()); From f762ef91fb55e7495e0e5f071d6ab6e3e80bd5e7 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Fri, 11 Mar 2022 21:18:14 +0800 Subject: [PATCH 16/19] [format] manually format --- src/daemon.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index 33a801a..9d06f28 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -230,11 +230,11 @@ async fn main() -> Result<(), Box> { } } 0 => { - if last_hotkey.is_some() && pending_release { - pending_release = false; - send_command(last_hotkey.clone().unwrap()); - last_hotkey = None; - } + if last_hotkey.is_some() && pending_release { + pending_release = false; + send_command(last_hotkey.clone().unwrap()); + last_hotkey = None; + } if let Some(modifier) = modifiers_map.get(&key) { if let Some(hotkey) = &last_hotkey { if hotkey.modifiers().contains(modifier) { From 894ca68ad07f8ff45c2ab63ba0fd31217302e0ec Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sat, 12 Mar 2022 20:56:17 +0800 Subject: [PATCH 17/19] [update] settle import statement to include --- src/config.rs | 2 +- src/tests.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/config.rs b/src/config.rs index 7935971..830418f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -60,7 +60,7 @@ impl fmt::Display for Error { } } -pub const IMPORT_STATEMENT: &str = "import"; +pub const IMPORT_STATEMENT: &str = "include"; #[derive(Debug, PartialEq, Clone)] pub struct Config { diff --git a/src/tests.rs b/src/tests.rs index 0685e4b..ef3be13 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -139,7 +139,7 @@ q let mut f = File::create(setup.path())?; f.write_all( b" -import /tmp/swhkd-test-file3 +include /tmp/swhkd-test-file3 super + b firefox", )?; @@ -169,7 +169,7 @@ super + c let mut f = File::create(setup.path())?; f.write_all( b" -import swhkd-relative-file2 +include swhkd-relative-file2 super + b firefox", )?; @@ -207,7 +207,7 @@ a let mut f2 = File::create(setup2.path())?; f2.write_all( b" -import /tmp/swhkd-test-file4 +include /tmp/swhkd-test-file4 b b", )?; @@ -215,10 +215,10 @@ b let mut f3 = File::create(setup3.path())?; f3.write_all( b" -import /tmp/swhkd-test-file4 -import /tmp/swhkd-test-file5 -import /tmp/swhkd-test-file6 -import /tmp/swhkd-test-file7 +include /tmp/swhkd-test-file4 +include /tmp/swhkd-test-file5 +include /tmp/swhkd-test-file6 +include /tmp/swhkd-test-file7 c c", )?; @@ -226,7 +226,7 @@ c let mut f4 = File::create(setup4.path())?; f4.write_all( b" -import /tmp/swhkd-test-file6 +include /tmp/swhkd-test-file6 d d", )?; From beb81732a0d54f4d7c1d3e6802fefc413a30924e Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sat, 12 Mar 2022 21:01:25 +0800 Subject: [PATCH 18/19] [improve] change test function name --- src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests.rs b/src/tests.rs index ef3be13..83f433c 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1106,7 +1106,7 @@ super + {\\,, .} } #[test] - fn test_prefix_on_release() -> std::io::Result<()> { + fn test_prefix() -> std::io::Result<()> { let contents = " super + @1 1 From 8a11ff5c1677a9e7b44294120d00f794dbc2c477 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 13 Mar 2022 07:52:37 +0800 Subject: [PATCH 19/19] [fix] manually impl PartialEq for KeyBinding so that modifiers' order doesn't matter --- src/config.rs | 12 +++++++++++- src/tests.rs | 10 +++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/config.rs b/src/config.rs index 97d8b05..7a1b805 100644 --- a/src/config.rs +++ b/src/config.rs @@ -134,7 +134,7 @@ pub fn load(path: &Path) -> Result, Error> { Ok(hotkeys) } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub struct KeyBinding { pub keysym: evdev::Key, pub modifiers: Vec, @@ -142,6 +142,16 @@ pub struct KeyBinding { pub on_release: bool, } +impl PartialEq for KeyBinding { + fn eq(&self, other: &Self) -> bool { + self.keysym == other.keysym + && self.modifiers.iter().all(|modifier| other.modifiers.contains(modifier)) + && self.modifiers.len() == other.modifiers.len() + && self.send == other.send + && self.on_release == other.on_release + } +} + pub trait Prefix { fn send(self) -> Self; fn on_release(self) -> Self; diff --git a/src/tests.rs b/src/tests.rs index 83f433c..de031c1 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -643,9 +643,9 @@ ReTurn #[test] fn test_duplicate_hotkeys() -> std::io::Result<()> { let contents = " -super + a +super + shift + a st -suPer + A +shift + suPer + A ts b st @@ -655,7 +655,11 @@ B eval_config_test( contents, vec![ - Hotkey::new(evdev::Key::KEY_A, vec![Modifier::Super], "st".to_string()), + Hotkey::new( + evdev::Key::KEY_A, + vec![Modifier::Super, Modifier::Shift], + "st".to_string(), + ), Hotkey::new(evdev::Key::KEY_B, vec![], "st".to_string()), ], )