Support multi-purpose keys in modmap

This commit is contained in:
Takashi Kokubun 2021-12-25 21:31:09 -08:00
parent 2557edb190
commit 091900500d
No known key found for this signature in database
GPG Key ID: 6FFC433B12EE23DD
7 changed files with 293 additions and 64 deletions

View File

@ -15,7 +15,8 @@
* You can remap any keys, e.g. Ctrl or CapsLock. * 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 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. * Application-specific remapping. Even if it's not supported by your application, xremap can.
## Prerequisite ## Prerequisite
@ -105,7 +106,12 @@ is supported only in `modmap` since `keymap` handles modifier keys differently.
modmap: modmap:
- name: Name # Required - name: Name # Required
remap: # 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 application: # Optional
not: [Application, ...] not: [Application, ...]
# or # 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. 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. 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
`keymap` is for remapping a sequence of key combinations to another sequence of key combinations or other actions. `keymap` is for remapping a sequence of key combinations to another sequence of key combinations or other actions.

View File

@ -1,10 +1,7 @@
# modmap: modmap:
# - name: Global - name: SandS
# remap:
# a: b
keymap:
- name: Global
application:
only: Slack
remap: remap:
C-i: C-u Space:
held: Shift_L
alone: Space
alone_timeout_millis: 500

View File

@ -7,6 +7,7 @@ use serde::de::{MapAccess, Visitor};
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
// Values in `keymap.remap`
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Action { pub enum Action {
KeyPress(KeyPress), KeyPress(KeyPress),
@ -70,7 +71,7 @@ impl<'de> Deserialize<'de> for Action {
} }
} }
fn serde_error<'de, V, M>(message: &str) -> Result<V, M::Error> pub fn serde_error<'de, V, M>(message: &str) -> Result<V, M::Error>
where where
M: MapAccess<'de>, M: MapAccess<'de>,
{ {

102
src/config/key_action.rs Normal file
View File

@ -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<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let key = parse_key(value).map_err(serde::de::Error::custom)?;
Ok(KeyAction::Key(key))
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut held: Option<Key> = None;
let mut alone: Option<Key> = None;
let mut alone_timeout_millis: u64 = DEFAULT_ALONE_TIMEOUT_MILLIS;
while let Some(key) = map.next_key::<String>()? {
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::<Self::Value, M>(&format!(
"held, alone, or alone_timeout_ms is expected, but got: {}",
key
))
}
}
}
let held = match held {
Some(held) => held,
None => {
return serde_error::<Self::Value, M>(
"held is not specified in a multi-purpose remap of modmap",
)
}
};
let alone = match alone {
Some(alone) => alone,
None => {
return serde_error::<Self::Value, M>(
"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)
}
}

View File

@ -2,6 +2,7 @@ pub mod action;
mod actions; mod actions;
pub mod application; pub mod application;
mod key; mod key;
pub mod key_action;
pub mod key_press; pub mod key_press;
mod keymap; mod keymap;
mod modmap; mod modmap;

View File

@ -1,5 +1,6 @@
use crate::config::application::Application; use crate::config::application::Application;
use crate::config::key::parse_key; use crate::config::key::parse_key;
use crate::config::key_action::KeyAction;
use evdev::Key; use evdev::Key;
use serde::de::{value, Error, MapAccess, Visitor}; use serde::de::{value, Error, MapAccess, Visitor};
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
@ -11,18 +12,18 @@ use std::fmt;
pub struct Modmap { pub struct Modmap {
pub name: String, pub name: String,
#[serde(deserialize_with = "modmap_remap")] #[serde(deserialize_with = "modmap_remap")]
pub remap: HashMap<Key, Key>, pub remap: HashMap<Key, KeyAction>,
pub application: Option<Application>, pub application: Option<Application>,
} }
fn modmap_remap<'de, D>(deserializer: D) -> Result<HashMap<Key, Key>, D::Error> fn modmap_remap<'de, D>(deserializer: D) -> Result<HashMap<Key, KeyAction>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
struct ModmapRemap; struct ModmapRemap;
impl<'de> Visitor<'de> for ModmapRemap { impl<'de> Visitor<'de> for ModmapRemap {
type Value = HashMap<Key, Key>; type Value = HashMap<Key, KeyAction>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("map from string to string") formatter.write_str("map from string to string")
@ -32,13 +33,12 @@ where
where where
M: MapAccess<'de>, M: MapAccess<'de>,
{ {
let remap: HashMap<String, String> = Deserialize::deserialize(value::MapAccessDeserializer::new(map))?; let remap: HashMap<String, KeyAction> = Deserialize::deserialize(value::MapAccessDeserializer::new(map))?;
let mut modmap = HashMap::new(); 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 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);
modmap.insert(from_key, to_key);
} }
Ok(modmap) Ok(modmap)

View File

@ -1,6 +1,7 @@
use crate::client::{build_client, WMClient}; use crate::client::{build_client, WMClient};
use crate::config::action::Action; use crate::config::action::Action;
use crate::config::application::Application; use crate::config::application::Application;
use crate::config::key_action::KeyAction;
use crate::config::key_press::{KeyPress, Modifier}; use crate::config::key_press::{KeyPress, Modifier};
use crate::Config; use crate::Config;
use evdev::uinput::VirtualDevice; use evdev::uinput::VirtualDevice;
@ -9,10 +10,12 @@ use lazy_static::lazy_static;
use log::debug; use log::debug;
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::time::Instant;
pub struct EventHandler { pub struct EventHandler {
device: VirtualDevice, device: VirtualDevice,
wm_client: WMClient, wm_client: WMClient,
multi_purpose_keys: HashMap<Key, MultiPurposeKeyState>,
override_remap: Option<HashMap<KeyPress, Vec<Action>>>, override_remap: Option<HashMap<KeyPress, Vec<Action>>>,
application_cache: Option<String>, application_cache: Option<String>,
shift: PressState, shift: PressState,
@ -26,6 +29,7 @@ impl EventHandler {
EventHandler { EventHandler {
device, device,
wm_client: build_client(), wm_client: build_client(),
multi_purpose_keys: HashMap::new(),
override_remap: None, override_remap: None,
application_cache: None, application_cache: None,
shift: PressState::new(false), shift: PressState::new(false),
@ -38,32 +42,31 @@ impl EventHandler {
// Handle EventType::KEY // Handle EventType::KEY
pub fn on_event(&mut self, event: InputEvent, config: &Config) -> Result<(), Box<dyn Error>> { pub fn on_event(&mut self, event: InputEvent, config: &Config) -> Result<(), Box<dyn Error>> {
self.application_cache = None; // expire cache self.application_cache = None; // expire cache
let mut key = Key::new(event.code()); let key = Key::new(event.code());
debug!("=> {}: {:?}", event.value(), &key); debug!("=> {}: {:?}", event.value(), &key);
// Apply modmap // Apply modmap
for modmap in &config.modmap { let mut key_values = if let Some(key_action) = self.find_modmap(&config, &key) {
if let Some(modmap_key) = modmap.remap.get(&key) { self.dispatch_keys(key_action, key, event.value())
if let Some(wm_class_matcher) = &modmap.application { } else {
if !self.match_wm_class(wm_class_matcher) { vec![(key, event.value())]
continue; };
} if !self.multi_purpose_keys.is_empty() {
} key_values = self.flush_timeout_keys(key_values);
key = modmap_key.clone();
break;
}
} }
// Apply keymap // Apply keymap
if MODIFIER_KEYS.contains(&key.code()) { for (key, value) in key_values.into_iter() {
self.update_modifier(key.code(), event.value()); if MODIFIER_KEYS.contains(&key.code()) {
} else if let Some(actions) = self.find_keymap(config, &key, event.value()) { self.update_modifier(key.code(), value);
for action in &actions { } else if let Some(actions) = self.find_keymap(config, &key, value) {
self.dispatch_action(action)?; for action in &actions {
self.dispatch_action(action)?;
}
return Ok(());
} }
return Ok(()); self.send_key(&key, value)?;
} }
self.send_key(&key, event.value())?;
Ok(()) Ok(())
} }
@ -79,6 +82,72 @@ impl EventHandler {
self.send_event(event) 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<KeyAction> {
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<Vec<Action>> { fn find_keymap(&mut self, config: &Config, key: &Key, value: i32) -> Option<Vec<Action>> {
if !is_pressed(value) { if !is_pressed(value) {
return None; return None;
@ -100,8 +169,8 @@ impl EventHandler {
} }
for keymap in &config.keymap { for keymap in &config.keymap {
if let Some(actions) = keymap.remap.get(&key_press) { if let Some(actions) = keymap.remap.get(&key_press) {
if let Some(wm_class_matcher) = &keymap.application { if let Some(application_matcher) = &keymap.application {
if !self.match_wm_class(wm_class_matcher) { if !self.match_application(application_matcher) {
continue; 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 // Lazily fill the wm_class cache
if let None = self.application_cache { if let None = self.application_cache {
match self.wm_client.current_application() { 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! { lazy_static! {
static ref MODIFIER_KEYS: [u16; 8] = [ static ref MODIFIER_KEYS: [u16; 8] = [
// Shift // Shift
@ -312,3 +357,76 @@ lazy_static! {
Key::new(Key::KEY_RIGHTMETA.code()), 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<Instant>,
}
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![]
}
}
}