From f5452cac5d3342bcee09e4c1e7f57155f923a1e2 Mon Sep 17 00:00:00 2001 From: cyber-sushi Date: Thu, 18 Apr 2024 13:41:06 +0200 Subject: [PATCH] Added support for custom key modifiers --- src/config.rs | 159 ++++++++++++++++++++++---------------------- src/event_reader.rs | 126 ++++++++++++++++++++++++++--------- src/udev_monitor.rs | 23 ++----- 3 files changed, 180 insertions(+), 128 deletions(-) diff --git a/src/config.rs b/src/config.rs index 10f2307..f3cc0f3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, BTreeMap}; +use std::collections::HashMap; use std::str::FromStr; use evdev::Key; use serde; @@ -14,21 +14,12 @@ pub struct Bindings { #[derive(Default, Debug, Clone)] pub struct Combinations { - pub keys: HashMap>>, - pub axis: HashMap>>, - pub keys_sh: HashMap>>, - pub axis_sh: HashMap>>, + pub keys: HashMap, Vec>>, + pub axis: HashMap, Vec>>, + pub keys_sh: HashMap, Vec>>, + pub axis_sh: HashMap, Vec>>, } -#[derive(Default, Debug, Clone)] -pub struct Modifiers { - pub keys: HashMap, HashMap>>, - pub axis: HashMap, HashMap>>, - pub keys_sh: HashMap, HashMap>>, - pub axis_sh: HashMap, HashMap>>, -} - - #[derive(serde::Deserialize, Debug, Clone)] pub struct RawConfig { #[serde(default)] @@ -38,6 +29,13 @@ pub struct RawConfig { pub settings: HashMap, } +#[derive(Debug, Clone)] +pub struct MappedModifiers { + pub default: Vec, + pub custom: Vec, + pub all: Vec, +} + impl RawConfig { fn new_from_file(file: &str) -> Self { println!("Parsing config file:\n{:?}\n", file.rsplit_once("/").unwrap().1); @@ -59,32 +57,55 @@ impl RawConfig { pub struct Config { pub name: String, pub bindings: Bindings, - pub modifiers: Modifiers, + pub combinations: Combinations, pub settings: HashMap, + pub mapped_modifiers: MappedModifiers, } impl Config { pub fn new_from_file(file: &str, file_name: String) -> Self { let raw_config = RawConfig::new_from_file(file); - let (bindings, combinations, settings) = parse_raw_config(raw_config); + let (bindings, combinations, settings, mapped_modifiers) = parse_raw_config(raw_config); let bindings: Bindings = merge_axis_bindings(bindings); - let modifiers: Modifiers = parse_modifiers(combinations); Self { name: file_name, bindings, - modifiers, + combinations, settings, + mapped_modifiers, } } } -fn parse_raw_config(raw_config: RawConfig) -> (Bindings, Combinations, HashMap) { +fn parse_raw_config(raw_config: RawConfig) -> (Bindings, Combinations, HashMap, MappedModifiers) { let remap: HashMap> = raw_config.remap; let commands: HashMap> = raw_config.commands; let settings: HashMap = raw_config.settings; let mut bindings: Bindings = Default::default(); let mut combinations: Combinations = Default::default(); + let default_modifiers = vec![ + Key::KEY_LEFTSHIFT, + Key::KEY_LEFTCTRL, + Key::KEY_LEFTALT, + Key::KEY_RIGHTSHIFT, + Key::KEY_RIGHTCTRL, + Key::KEY_RIGHTALT, + Key::KEY_LEFTMETA, + ]; + let mut mapped_modifiers = MappedModifiers { + default: default_modifiers.clone(), + custom: Vec::new(), + all: Vec::new(), + }; + let custom_modifiers: Vec = match settings.get(&"CUSTOM_MODIFIERS".to_string()) { + Some(modifiers) => { + modifiers.split("-").collect::>().iter() + .map(|key_str| Key::from_str(key_str).expect("Invalid KEY value used as modifier in CUSTOM_MODIFIERS.")).collect() + }, + None => Vec::new(), + }; + mapped_modifiers.custom.extend(custom_modifiers); let abs = [ "DPAD_UP", @@ -108,24 +129,32 @@ fn parse_raw_config(raw_config: RawConfig) -> (Bindings, Combinations, HashMap = mods.split("-").collect::>().iter().map(|key_str| Key::from_str(key_str).expect("Invalid KEY value used as modifier.")).collect(); + modifiers.sort(); + modifiers.dedup(); + for modifier in &modifiers { + if !mapped_modifiers.default.contains(&modifier) { + mapped_modifiers.custom.push(modifier.clone()); + } + } if abs.contains(&key) { - if !combinations.axis.contains_key(&mods.to_string()) { - combinations.axis.insert(mods.to_string(), HashMap::from([(key.to_string(), output)])); + if !combinations.axis.contains_key(&key.to_string()) { + combinations.axis.insert(key.to_string(), HashMap::from([(modifiers , output)])); } else { - combinations.axis.get_mut(mods).unwrap().insert(key.to_string(), output); + combinations.axis.get_mut(key).unwrap().insert(modifiers, output); } } else { - if !combinations.keys.contains_key(&mods.to_string()) { - combinations.keys.insert(mods.to_string(), HashMap::from([(Key::from_str(key).expect("Invalid KEY value."), output)])); + if !combinations.keys.contains_key(&Key::from_str(key).unwrap()) { + combinations.keys.insert(Key::from_str(key).unwrap(), HashMap::from([(modifiers, output)])); } else { - combinations.keys.get_mut(mods).unwrap().insert(Key::from_str(key).expect("Invalid KEY value."), output); + combinations.keys.get_mut(&Key::from_str(key).unwrap()).unwrap().insert(modifiers, output); } } } else { if abs.contains(&input.as_str()) { bindings.axis.insert(input, output); } else { - bindings.keys.insert(Key::from_str(input.as_str()).expect("Invalid KEY value."), output); + bindings.keys.insert(Key::from_str(input.as_str()).expect("Invalid KEY value used for rebinding."), output); } } } @@ -133,74 +162,44 @@ fn parse_raw_config(raw_config: RawConfig) -> (Bindings, Combinations, HashMap = mods.split("-").collect::>().iter().map(|key_str| Key::from_str(key_str).expect("Invalid KEY value used as modifier.")).collect(); + modifiers.sort(); + modifiers.dedup(); + for modifier in &modifiers { + if !mapped_modifiers.default.contains(&modifier) { + mapped_modifiers.custom.push(modifier.clone()); + } + } if abs.contains(&key) { - if !combinations.axis_sh.contains_key(&mods.to_string()) { - combinations.axis_sh.insert(mods.to_string(), HashMap::from([(key.to_string(), output)])); + if !combinations.axis_sh.contains_key(&key.to_string()) { + combinations.axis_sh.insert(key.to_string(), HashMap::from([(modifiers, output)])); } else { - combinations.axis_sh.get_mut(mods).unwrap().insert(key.to_string(), output); + combinations.axis_sh.get_mut(key).unwrap().insert(modifiers, output); } } else { - if !combinations.keys_sh.contains_key(&mods.to_string()) { - combinations.keys_sh.insert(mods.to_string(), HashMap::from([(Key::from_str(key).expect("Invalid KEY value."), output)])); + if !combinations.keys_sh.contains_key(&Key::from_str(key).unwrap()) { + combinations.keys_sh.insert(Key::from_str(key).unwrap(), HashMap::from([(modifiers, output)])); } else { - combinations.keys_sh.get_mut(mods).unwrap().insert(Key::from_str(key).expect("Invalid KEY value."), output); + combinations.keys_sh.get_mut(&Key::from_str(key).unwrap()).unwrap().insert(modifiers, output); } } } else { if abs.contains(&input.as_str()) { bindings.axis_sh.insert(input, output); } else { - bindings.keys_sh.insert(Key::from_str(input.as_str()).expect("Invalid KEY value."), output); + bindings.keys_sh.insert(Key::from_str(input.as_str()).expect("Invalid KEY value used for rebinding."), output); } } } - (bindings, combinations, settings) -} -fn parse_modifiers(combinations: Combinations) -> Modifiers { - let empty_modmap = BTreeMap::from ([ - (Key::KEY_LEFTSHIFT, 0), - (Key::KEY_LEFTCTRL, 0), - (Key::KEY_LEFTALT, 0), - (Key::KEY_RIGHTSHIFT, 0), - (Key::KEY_RIGHTCTRL, 0), - (Key::KEY_RIGHTALT, 0), - (Key::KEY_LEFTMETA, 0) - ]); - let mut modifiers: Modifiers = Default::default(); - for (mods, key) in combinations.keys.iter() { - let mods_vector = mods.split("-").map(str::to_string).collect::>(); - let mut modmap = empty_modmap.clone(); - for modifier in mods_vector { - modmap.insert(Key::from_str(&modifier).unwrap(), 1); - } - modifiers.keys.insert(modmap, key.clone()); - } - for (mods, key) in combinations.axis.iter() { - let mods_vector = mods.split("-").map(str::to_string).collect::>(); - let mut modmap = empty_modmap.clone(); - for modifier in mods_vector { - modmap.insert(Key::from_str(&modifier).unwrap(), 1); - } - modifiers.axis.insert(modmap, key.clone()); - } - for (mods, key) in combinations.keys_sh.iter() { - let mods_vector = mods.split("-").map(str::to_string).collect::>(); - let mut modmap = empty_modmap.clone(); - for modifier in mods_vector { - modmap.insert(Key::from_str(&modifier).unwrap(), 1); - } - modifiers.keys_sh.insert(modmap, key.clone()); - } - for (mods, key) in combinations.axis_sh.iter() { - let mods_vector = mods.split("-").map(str::to_string).collect::>(); - let mut modmap = empty_modmap.clone(); - for modifier in mods_vector { - modmap.insert(Key::from_str(&modifier).unwrap(), 1); - } - modifiers.axis_sh.insert(modmap, key.clone()); - } - modifiers + mapped_modifiers.custom.sort(); + mapped_modifiers.custom.dedup(); + mapped_modifiers.all.extend(mapped_modifiers.default.clone()); + mapped_modifiers.all.extend(mapped_modifiers.custom.clone()); + mapped_modifiers.all.sort(); + mapped_modifiers.all.dedup(); + + (bindings, combinations, settings, mapped_modifiers) } fn merge_axis_bindings(mut bindings: Bindings) -> Bindings { @@ -238,3 +237,5 @@ fn merge_axis_bindings(mut bindings: Bindings) -> Bindings { bindings.axis.insert("RSTICK_Y".to_string(), rstick_y); bindings } + + diff --git a/src/event_reader.rs b/src/event_reader.rs index 624d913..59f755c 100644 --- a/src/event_reader.rs +++ b/src/event_reader.rs @@ -1,4 +1,4 @@ -use std::{collections::{HashMap, BTreeMap}, sync::Arc, option::Option, process::Command}; +use std::{collections::HashMap, sync::Arc, option::Option, process::Command}; use tokio::sync::Mutex; use tokio_stream::StreamExt; use evdev::{EventStream, Key, RelativeAxisType, AbsoluteAxisType, EventType, InputEvent}; @@ -25,7 +25,8 @@ pub struct EventReader { virt_dev: Arc>, lstick_position: Arc>>, rstick_position: Arc>>, - modifiers: Arc>>, + modifiers: Arc>>, + modifier_was_activated: Arc>, device_is_connected: Arc>, current_desktop: Option, settings: Settings, @@ -35,7 +36,8 @@ impl EventReader { pub fn new( config: HashMap, stream: Arc>, - modifiers: Arc>>, + modifiers: Arc>>, + modifier_was_activated: Arc>, current_desktop: Option, ) -> Self { let mut position_vector: Vec = Vec::new(); @@ -83,6 +85,7 @@ impl EventReader { lstick_position, rstick_position, modifiers, + modifier_was_activated, device_is_connected, current_desktop, settings, @@ -252,13 +255,13 @@ impl EventReader { async fn convert_key_events(&self, event: InputEvent) { let path = self.config.get(&get_active_window(&self.current_desktop, &self.config).await).unwrap(); let modifiers = self.modifiers.lock().await.clone(); - if let Some(event_hashmap) = path.modifiers.keys.get(&modifiers) { - if let Some(event_list) = event_hashmap.get(&Key(event.code())) { + if let Some(event_hashmap) = path.combinations.keys.get(&Key(event.code())) { + if let Some(event_list) = event_hashmap.get(&modifiers) { self.emit_event_without_modifiers(event_list, &modifiers, event.value()).await; return } - } else if let Some(command_hashmap) = path.modifiers.keys_sh.get(&modifiers) { - if let Some(command_list) = command_hashmap.get(&Key(event.code())) { + } else if let Some(command_hashmap) = path.combinations.keys_sh.get(&Key(event.code())) { + if let Some(command_list) = command_hashmap.get(&modifiers) { spawn_subprocess(command_list).await; return } @@ -275,16 +278,16 @@ impl EventReader { async fn convert_axis_events(&self, event: InputEvent, event_string: &String, send_zero: bool) { let path = self.config.get(&get_active_window(&self.current_desktop, &self.config).await).unwrap(); let modifiers = self.modifiers.lock().await.clone(); - if let Some(event_hashmap) = path.modifiers.axis.get(&modifiers) { - if let Some(event_list) = event_hashmap.get(event_string) { + if let Some(event_hashmap) = path.combinations.axis.get(event_string) { + if let Some(event_list) = event_hashmap.get(&modifiers) { self.emit_event_without_modifiers(event_list, &modifiers, event.value()).await; if send_zero { self.emit_event_without_modifiers(event_list, &modifiers, 0).await; } return } - } else if let Some(command_hashmap) = path.modifiers.axis_sh.get(&modifiers) { - if let Some(command_list) = command_hashmap.get(event_string) { + } else if let Some(command_hashmap) = path.combinations.axis_sh.get(event_string) { + if let Some(command_list) = command_hashmap.get(&modifiers) { spawn_subprocess(command_list).await; return } @@ -302,8 +305,10 @@ impl EventReader { } async fn emit_event(&self, event_list: &Vec, value: i32) { + let path = self.config.get(&get_active_window(&self.current_desktop, &self.config).await).unwrap(); let mut virt_dev = self.virt_dev.lock().await; let modifiers = self.modifiers.lock().await.clone(); + let mut modifier_was_activated = self.modifier_was_activated.lock().await; let released_keys: Vec = self.released_keys(&modifiers).await; for key in released_keys { self.toggle_modifiers(key, 0).await; @@ -312,13 +317,28 @@ impl EventReader { } for key in event_list { self.toggle_modifiers(*key, value).await; - let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), value); - virt_dev.keys.emit(&[virtual_event]).unwrap(); + if path.mapped_modifiers.custom.contains(&key) { + if value == 0 && !*modifier_was_activated { + let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), 1); + virt_dev.keys.emit(&[virtual_event]).unwrap(); + let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), 0); + virt_dev.keys.emit(&[virtual_event]).unwrap(); + *modifier_was_activated = true; + } else if value == 1 { + *modifier_was_activated = false; + } + } else { + let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), value); + virt_dev.keys.emit(&[virtual_event]).unwrap(); + *modifier_was_activated = true; + } } } async fn emit_default_event(&self, event: InputEvent) { + let path = self.config.get(&get_active_window(&self.current_desktop, &self.config).await).unwrap(); let mut virt_dev = self.virt_dev.lock().await; + let mut modifier_was_activated = self.modifier_was_activated.lock().await; match event.event_type() { EventType::KEY => { let modifiers = self.modifiers.lock().await.clone(); @@ -329,27 +349,53 @@ impl EventReader { virt_dev.keys.emit(&[virtual_event]).unwrap() } self.toggle_modifiers(Key(event.code()), event.value()).await; - virt_dev.keys.emit(&[event]).unwrap(); + if path.mapped_modifiers.custom.contains(&Key(event.code())) { + if event.value() == 0 && !*modifier_was_activated { + let virtual_event: InputEvent = InputEvent::new_now(event.event_type(), event.code(), 1); + virt_dev.keys.emit(&[virtual_event]).unwrap(); + let virtual_event: InputEvent = InputEvent::new_now(event.event_type(), event.code(), 0); + virt_dev.keys.emit(&[virtual_event]).unwrap(); + *modifier_was_activated = true; + } else if event.value() == 1 { + *modifier_was_activated = false; + } + } else { + virt_dev.keys.emit(&[event]).unwrap(); + *modifier_was_activated = true; + } + }, + EventType::RELATIVE => { + virt_dev.axis.emit(&[event]).unwrap(); + *modifier_was_activated = true; }, - EventType::RELATIVE => virt_dev.axis.emit(&[event]).unwrap(), _ => {} } } - async fn emit_event_without_modifiers(&self, event_list: &Vec, modifiers: &BTreeMap, value: i32) { - let modifiers_list = modifiers.iter() - .filter(|(_key, value)| value == &&1) - .collect::>() - .into_keys().copied() - .collect::>(); + async fn emit_event_without_modifiers(&self, event_list: &Vec, modifiers: &Vec, value: i32) { + let path = self.config.get(&get_active_window(&self.current_desktop, &self.config).await).unwrap(); let mut virt_dev = self.virt_dev.lock().await; - for key in modifiers_list { + let mut modifier_was_activated = self.modifier_was_activated.lock().await; + for key in modifiers { let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), 0); virt_dev.keys.emit(&[virtual_event]).unwrap(); } for key in event_list { - let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), value); - virt_dev.keys.emit(&[virtual_event]).unwrap(); + if path.mapped_modifiers.custom.contains(&key) { + if value == 0 && !*modifier_was_activated { + let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), 1); + virt_dev.keys.emit(&[virtual_event]).unwrap(); + let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), 0); + virt_dev.keys.emit(&[virtual_event]).unwrap(); + *modifier_was_activated = true; + } else if value == 1 { + *modifier_was_activated = false; + } + } else { + let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), value); + virt_dev.keys.emit(&[virtual_event]).unwrap(); + *modifier_was_activated = true; + } } } @@ -366,20 +412,33 @@ impl EventReader { } async fn toggle_modifiers(&self, key: Key, value: i32) { + let path = self.config.get(&get_active_window(&self.current_desktop, &self.config).await).unwrap(); let mut modifiers = self.modifiers.lock().await; - if modifiers.contains_key(&key) && vec![0, 1].contains(&value) { - modifiers.insert(key, value).unwrap(); + if path.mapped_modifiers.all.contains(&key) { + match value { + 1 => { + modifiers.push(key); + modifiers.sort(); + modifiers.dedup(); + }, + 0 => modifiers.retain(|&x| x != key), + _ => {}, + } } } - - async fn released_keys(&self, modifiers: &BTreeMap) -> Vec { + + async fn released_keys(&self, modifiers: &Vec) -> Vec { let path = self.config.get(&get_active_window(&self.current_desktop, &self.config).await).unwrap(); let mut released_keys: Vec = Vec::new(); - if let Some(event_hashmap) = path.modifiers.keys.get(&modifiers) { - event_hashmap.iter().for_each(|(_modifiers, event_list)| released_keys.extend(event_list)); + for (_key, hashmap) in path.combinations.keys.iter() { + if let Some(event_list) = hashmap.get(modifiers) { + released_keys.extend(event_list); + } } - if let Some(event_hashmap) = path.modifiers.axis.get(&modifiers) { - event_hashmap.iter().for_each(|(_modifiers, event_list)| released_keys.extend(event_list)); + for (_key, hashmap) in path.combinations.axis.iter() { + if let Some(event_list) = hashmap.get(modifiers) { + released_keys.extend(event_list); + } } released_keys } @@ -460,3 +519,6 @@ async fn spawn_subprocess(command_list: &Vec) { .expect("Failed to run command."); } } + + + diff --git a/src/udev_monitor.rs b/src/udev_monitor.rs index c266242..aaaab23 100644 --- a/src/udev_monitor.rs +++ b/src/udev_monitor.rs @@ -1,4 +1,4 @@ -use std::{collections::{HashMap, BTreeMap}, sync::Arc, path::Path, process::Command, env}; +use std::{collections::HashMap, sync::Arc, path::Path, process::Command, env}; use tokio::sync::Mutex; use tokio::task::JoinHandle; use tokio_stream::StreamExt; @@ -16,7 +16,7 @@ pub async fn start_monitoring_udev(config_files: Vec, mut tasks: Vec, mut tasks: Vec, tasks: &mut Vec>) { - let modifiers: Arc>> = Arc::new ( - Mutex::new ( - BTreeMap::from ([ - (Key::KEY_LEFTSHIFT, 0), - (Key::KEY_LEFTCTRL, 0), - (Key::KEY_LEFTALT, 0), - (Key::KEY_RIGHTSHIFT, 0), - (Key::KEY_RIGHTCTRL, 0), - (Key::KEY_RIGHTALT, 0), - (Key::KEY_LEFTMETA, 0) - ]) - ) - ); + let modifiers: Arc>> = Arc::new(Mutex::new(Default::default())); + let modifier_was_activated: Arc> = Arc::new(Mutex::new(true)); let current_desktop: Option = match (env::var("XDG_SESSION_TYPE"), env::var("XDG_CURRENT_DESKTOP")) { (Ok(session), Ok(desktop)) if session == "wayland".to_string() && vec!["Hyprland".to_string(), "sway".to_string()].contains(&desktop) => { println!(">> Running on {}, active window detection enabled.\n", desktop); @@ -68,7 +57,7 @@ pub fn launch_tasks(config_files: &Vec, tasks: &mut Vec>) }; let user_has_access = match Command::new("groups").output() { Ok(groups) if std::str::from_utf8(&groups.stdout.as_slice()).unwrap().contains("input") => { - println!("Running with evdev permissions.\nScanning for event devices with a matching config file...\n"); //todo: make the config dir customizable through env variable + println!("Running with evdev permissions.\nScanning for event devices with a matching config file...\n"); true }, Ok(groups) if std::str::from_utf8(&groups.stdout.as_slice()).unwrap().contains("root") => { @@ -105,7 +94,7 @@ pub fn launch_tasks(config_files: &Vec, tasks: &mut Vec>) let event_device = device.0.as_path().to_str().unwrap().to_string(); if !config_map.is_empty() { let stream = Arc::new(Mutex::new(get_event_stream(Path::new(&event_device), config_map.clone()))); - let reader = EventReader::new(config_map.clone(), stream, modifiers.clone(), current_desktop.clone()); + let reader = EventReader::new(config_map.clone(), stream, modifiers.clone(), modifier_was_activated.clone(), current_desktop.clone()); tasks.push(tokio::spawn(start_reader(reader))); devices_found += 1 }