From 091900500d5995d5c49bb668376e45bc3fd8d709 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sat, 25 Dec 2021 21:31:09 -0800 Subject: [PATCH] Support multi-purpose keys in modmap --- README.md | 14 ++- example/test.yml | 15 ++- src/config/action.rs | 3 +- src/config/key_action.rs | 102 +++++++++++++++++++ src/config/mod.rs | 1 + src/config/modmap.rs | 14 +-- src/event_handler.rs | 208 ++++++++++++++++++++++++++++++--------- 7 files changed, 293 insertions(+), 64 deletions(-) create mode 100644 src/config/key_action.rs diff --git a/README.md b/README.md index 7013d03..6c2cba0 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ * You can remap any keys, e.g. Ctrl or CapsLock. * You can remap any key combination to another, even to a key sequence. -* You can also remap a key sequence as well. You could do something like Emacs's `C-x C-c`. +* You can remap a key sequence as well. You could do something like Emacs's `C-x C-c`. +* You can remap a key to two different keys depending on whether it's pressed alone or held. * Application-specific remapping. Even if it's not supported by your application, xremap can. ## Prerequisite @@ -105,7 +106,12 @@ is supported only in `modmap` since `keymap` handles modifier keys differently. modmap: - name: Name # Required remap: # Required - KEY_XXX: KEY_YYY + KEY_XXX: KEY_YYY # Required + # or + KEY_XXX: + held: KEY_YYY # Required + alone: KEY_ZZZ # Required + alone_timeout_millis: 1000 # Optional application: # Optional not: [Application, ...] # or @@ -116,6 +122,10 @@ For `KEY_XXX` and `KEY_YYY`, use [these names](https://github.com/emberian/evdev You can skip `KEY_` and the name is case-insensitive. So `KEY_CAPSLOCK`, `CAPSLOCK`, and `CapsLock` are the same thing. Some [custom aliases](src/config/key.rs) like `SHIFT_R`, `CONTROL_L`, etc. are provided. +If you specify a map containing `held` and `alone`, you can use the key for two purposes. +The key is considered `alone` if it's pressed and released within `alone_timeout_millis` (default: 1000) +before any other key is pressed. Otherwise it's considered `held`. + ### keymap `keymap` is for remapping a sequence of key combinations to another sequence of key combinations or other actions. diff --git a/example/test.yml b/example/test.yml index 065ec12..89380ff 100644 --- a/example/test.yml +++ b/example/test.yml @@ -1,10 +1,7 @@ -# modmap: -# - name: Global -# remap: -# a: b -keymap: - - name: Global - application: - only: Slack +modmap: + - name: SandS remap: - C-i: C-u + Space: + held: Shift_L + alone: Space + alone_timeout_millis: 500 diff --git a/src/config/action.rs b/src/config/action.rs index 04876b2..22079aa 100644 --- a/src/config/action.rs +++ b/src/config/action.rs @@ -7,6 +7,7 @@ use serde::de::{MapAccess, Visitor}; use serde::{Deserialize, Deserializer}; use std::fmt::{Debug, Formatter}; +// Values in `keymap.remap` #[derive(Clone, Debug)] pub enum Action { KeyPress(KeyPress), @@ -70,7 +71,7 @@ impl<'de> Deserialize<'de> for Action { } } -fn serde_error<'de, V, M>(message: &str) -> Result +pub fn serde_error<'de, V, M>(message: &str) -> Result where M: MapAccess<'de>, { diff --git a/src/config/key_action.rs b/src/config/key_action.rs new file mode 100644 index 0000000..4b95797 --- /dev/null +++ b/src/config/key_action.rs @@ -0,0 +1,102 @@ +use crate::config::action::serde_error; +use crate::config::key::parse_key; +use evdev::Key; +use serde::de::{MapAccess, Visitor}; +use serde::{Deserialize, Deserializer}; +use std::fmt::Formatter; +use std::time::Duration; + +static DEFAULT_ALONE_TIMEOUT_MILLIS: u64 = 1000; + +// Values in `modmap.remap` +#[derive(Clone, Debug)] +pub enum KeyAction { + Key(Key), + MultiPurposeKey(MultiPurposeKey), +} + +#[derive(Clone, Debug)] +pub struct MultiPurposeKey { + pub held: Key, + pub alone: Key, + pub alone_timeout: Duration, +} + +impl<'de> Deserialize<'de> for KeyAction { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct KeyActionVisitor; + + impl<'de> Visitor<'de> for KeyActionVisitor { + type Value = KeyAction; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("string or map") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + let key = parse_key(value).map_err(serde::de::Error::custom)?; + Ok(KeyAction::Key(key)) + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut held: Option = None; + let mut alone: Option = None; + let mut alone_timeout_millis: u64 = DEFAULT_ALONE_TIMEOUT_MILLIS; + + while let Some(key) = map.next_key::()? { + match &key[..] { + "held" => { + let value: String = map.next_value()?; + held = Some(parse_key(&value).map_err(serde::de::Error::custom)?) + } + "alone" => { + let value: String = map.next_value()?; + alone = Some(parse_key(&value).map_err(serde::de::Error::custom)?) + } + "alone_timeout_millis" => alone_timeout_millis = map.next_value()?, + key => { + return serde_error::(&format!( + "held, alone, or alone_timeout_ms is expected, but got: {}", + key + )) + } + } + } + + let held = match held { + Some(held) => held, + None => { + return serde_error::( + "held is not specified in a multi-purpose remap of modmap", + ) + } + }; + let alone = match alone { + Some(alone) => alone, + None => { + return serde_error::( + "alone is not specified in a multi-purpose remap of modmap", + ) + } + }; + let multi_purpose_key = MultiPurposeKey { + held, + alone, + alone_timeout: Duration::from_millis(alone_timeout_millis), + }; + Ok(KeyAction::MultiPurposeKey(multi_purpose_key)) + } + } + + deserializer.deserialize_any(KeyActionVisitor) + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs index d47e6e5..83ca05a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,6 +2,7 @@ pub mod action; mod actions; pub mod application; mod key; +pub mod key_action; pub mod key_press; mod keymap; mod modmap; diff --git a/src/config/modmap.rs b/src/config/modmap.rs index 8ae59d2..fd355ee 100644 --- a/src/config/modmap.rs +++ b/src/config/modmap.rs @@ -1,5 +1,6 @@ use crate::config::application::Application; use crate::config::key::parse_key; +use crate::config::key_action::KeyAction; use evdev::Key; use serde::de::{value, Error, MapAccess, Visitor}; use serde::{Deserialize, Deserializer}; @@ -11,18 +12,18 @@ use std::fmt; pub struct Modmap { pub name: String, #[serde(deserialize_with = "modmap_remap")] - pub remap: HashMap, + pub remap: HashMap, pub application: Option, } -fn modmap_remap<'de, D>(deserializer: D) -> Result, D::Error> +fn modmap_remap<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { struct ModmapRemap; impl<'de> Visitor<'de> for ModmapRemap { - type Value = HashMap; + type Value = HashMap; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("map from string to string") @@ -32,13 +33,12 @@ where where M: MapAccess<'de>, { - let remap: HashMap = Deserialize::deserialize(value::MapAccessDeserializer::new(map))?; + let remap: HashMap = Deserialize::deserialize(value::MapAccessDeserializer::new(map))?; let mut modmap = HashMap::new(); - for (from, to) in remap.iter() { + for (from, to) in remap.into_iter() { let from_key = parse_key(&from).map_err(M::Error::custom)?; - let to_key = parse_key(&to).map_err(M::Error::custom)?; - modmap.insert(from_key, to_key); + modmap.insert(from_key, to); } Ok(modmap) diff --git a/src/event_handler.rs b/src/event_handler.rs index 2024c5e..517b3bf 100644 --- a/src/event_handler.rs +++ b/src/event_handler.rs @@ -1,6 +1,7 @@ use crate::client::{build_client, WMClient}; use crate::config::action::Action; use crate::config::application::Application; +use crate::config::key_action::KeyAction; use crate::config::key_press::{KeyPress, Modifier}; use crate::Config; use evdev::uinput::VirtualDevice; @@ -9,10 +10,12 @@ use lazy_static::lazy_static; use log::debug; use std::collections::HashMap; use std::error::Error; +use std::time::Instant; pub struct EventHandler { device: VirtualDevice, wm_client: WMClient, + multi_purpose_keys: HashMap, override_remap: Option>>, application_cache: Option, shift: PressState, @@ -26,6 +29,7 @@ impl EventHandler { EventHandler { device, wm_client: build_client(), + multi_purpose_keys: HashMap::new(), override_remap: None, application_cache: None, shift: PressState::new(false), @@ -38,32 +42,31 @@ impl EventHandler { // Handle EventType::KEY pub fn on_event(&mut self, event: InputEvent, config: &Config) -> Result<(), Box> { self.application_cache = None; // expire cache - let mut key = Key::new(event.code()); + let key = Key::new(event.code()); debug!("=> {}: {:?}", event.value(), &key); // Apply modmap - for modmap in &config.modmap { - if let Some(modmap_key) = modmap.remap.get(&key) { - if let Some(wm_class_matcher) = &modmap.application { - if !self.match_wm_class(wm_class_matcher) { - continue; - } - } - key = modmap_key.clone(); - break; - } + let mut key_values = if let Some(key_action) = self.find_modmap(&config, &key) { + self.dispatch_keys(key_action, key, event.value()) + } else { + vec![(key, event.value())] + }; + if !self.multi_purpose_keys.is_empty() { + key_values = self.flush_timeout_keys(key_values); } // Apply keymap - if MODIFIER_KEYS.contains(&key.code()) { - self.update_modifier(key.code(), event.value()); - } else if let Some(actions) = self.find_keymap(config, &key, event.value()) { - for action in &actions { - self.dispatch_action(action)?; + for (key, value) in key_values.into_iter() { + if MODIFIER_KEYS.contains(&key.code()) { + self.update_modifier(key.code(), value); + } else if let Some(actions) = self.find_keymap(config, &key, value) { + for action in &actions { + self.dispatch_action(action)?; + } + return Ok(()); } - return Ok(()); + self.send_key(&key, value)?; } - self.send_key(&key, event.value())?; Ok(()) } @@ -79,6 +82,72 @@ impl EventHandler { self.send_event(event) } + fn dispatch_keys(&mut self, key_action: KeyAction, key: Key, value: i32) -> Vec<(Key, i32)> { + match key_action { + KeyAction::Key(modmap_key) => vec![(modmap_key.clone(), value)], + KeyAction::MultiPurposeKey(multi_purpose_key) => { + if value == PRESS { + self.multi_purpose_keys.insert( + key.clone(), + MultiPurposeKeyState { + held: multi_purpose_key.held, + alone: multi_purpose_key.alone, + alone_timeout_at: Some(Instant::now() + multi_purpose_key.alone_timeout), + }, + ); + return vec![]; // delay the press + } else if value == REPEAT { + if let Some(state) = self.multi_purpose_keys.get_mut(&key) { + return state.repeat(); + } + } else if value == RELEASE { + if let Some(state) = self.multi_purpose_keys.remove(&key) { + return state.release(); + } + } else { + panic!("unexpected key event value: {}", value); + } + // fallthrough on state discrepancy + vec![(key, value)] + } + } + } + + fn flush_timeout_keys(&mut self, key_values: Vec<(Key, i32)>) -> Vec<(Key, i32)> { + let mut flush = false; + for (_, value) in key_values.iter() { + if *value == PRESS { + flush = true; + break; + } + } + + if flush { + let mut flushed: Vec<(Key, i32)> = vec![]; + for (_, state) in self.multi_purpose_keys.iter_mut() { + flushed.extend(state.force_held()); + } + flushed.extend(key_values); + flushed + } else { + key_values + } + } + + fn find_modmap(&mut self, config: &Config, key: &Key) -> Option { + for modmap in &config.modmap { + if let Some(key_action) = modmap.remap.get(&key) { + if let Some(application_matcher) = &modmap.application { + if !self.match_application(application_matcher) { + continue; + } + } + return Some(key_action.clone()); + } + } + None + } + fn find_keymap(&mut self, config: &Config, key: &Key, value: i32) -> Option> { if !is_pressed(value) { return None; @@ -100,8 +169,8 @@ impl EventHandler { } for keymap in &config.keymap { if let Some(actions) = keymap.remap.get(&key_press) { - if let Some(wm_class_matcher) = &keymap.application { - if !self.match_wm_class(wm_class_matcher) { + if let Some(application_matcher) = &keymap.application { + if !self.match_application(application_matcher) { continue; } } @@ -212,7 +281,7 @@ impl EventHandler { } } - fn match_wm_class(&mut self, application_matcher: &Application) -> bool { + fn match_application(&mut self, application_matcher: &Application) -> bool { // Lazily fill the wm_class cache if let None = self.application_cache { match self.wm_client.current_application() { @@ -255,30 +324,6 @@ impl EventHandler { } } -#[derive(Clone)] -struct PressState { - left: bool, - right: bool, -} - -impl PressState { - fn new(pressed: bool) -> PressState { - PressState { - left: pressed, - right: pressed, - } - } -} - -fn is_pressed(value: i32) -> bool { - value == PRESS || value == REPEAT -} - -// InputEvent#value -static RELEASE: i32 = 0; -static PRESS: i32 = 1; -static REPEAT: i32 = 2; - lazy_static! { static ref MODIFIER_KEYS: [u16; 8] = [ // Shift @@ -312,3 +357,76 @@ lazy_static! { Key::new(Key::KEY_RIGHTMETA.code()), ]; } + +//--- + +#[derive(Clone)] +struct PressState { + left: bool, + right: bool, +} + +impl PressState { + fn new(pressed: bool) -> PressState { + PressState { + left: pressed, + right: pressed, + } + } +} + +fn is_pressed(value: i32) -> bool { + value == PRESS || value == REPEAT +} + +// InputEvent#value +static RELEASE: i32 = 0; +static PRESS: i32 = 1; +static REPEAT: i32 = 2; + +//--- + +#[derive(Debug)] +struct MultiPurposeKeyState { + held: Key, + alone: Key, + alone_timeout_at: Option, +} + +impl MultiPurposeKeyState { + fn repeat(&mut self) -> Vec<(Key, i32)> { + if let Some(alone_timeout_at) = &self.alone_timeout_at { + if Instant::now() < *alone_timeout_at { + vec![] // still delay the press + } else { + self.alone_timeout_at = None; // timeout + vec![(self.held.clone(), PRESS)] + } + } else { + vec![(self.held.clone(), REPEAT)] + } + } + + fn release(&self) -> Vec<(Key, i32)> { + if let Some(alone_timeout_at) = &self.alone_timeout_at { + if Instant::now() < *alone_timeout_at { + // dispatch the delayed press and this release + vec![(self.alone.clone(), PRESS), (self.alone.clone(), RELEASE)] + } else { + // too late. dispatch the held key + vec![(self.held.clone(), PRESS), (self.held.clone(), RELEASE)] + } + } else { + vec![(self.held.clone(), RELEASE)] + } + } + + fn force_held(&mut self) -> Vec<(Key, i32)> { + if self.alone_timeout_at.is_some() { + self.alone_timeout_at = None; + vec![(self.held.clone(), PRESS)] + } else { + vec![] + } + } +}