Merge pull request #79 from k0kubun/command-key

Support executing commands on press/release events
pull/144/head
Takashi Kokubun 2 years ago committed by GitHub
commit 5462b1604d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -20,6 +20,7 @@
* Application-specific remapping. Even if it's not supported by your application, xremap can.
* Automatically remap newly connected devices by starting xremap with `--watch`.
* Support [Emacs-like key remapping](example/emacs.yml), including the mark mode.
* Trigger commands on key press/release events.
## Installation
@ -153,11 +154,14 @@ modmap:
- name: Name # Optional
remap: # Required
KEY_XXX: KEY_YYY # Required
# or
KEY_XXX:
held: KEY_YYY # Required
alone: KEY_ZZZ # Required
alone_timeout_millis: 1000 # Optional
# Trigger `keymap` action on key press/release events.
KEY_XXX:
press: { launch: ["xdotool", "mousemove", "0", "7200"] } # Required
release: { launch: ["xdotool", "mousemove", "0", "0"] } # Required
application: # Optional
not: [Application, ...]
# or
@ -180,25 +184,25 @@ before any other key is pressed. Otherwise it's considered `held`.
keymap:
- name: Name # Optional
remap: # Required
# key press -> key press
# Key press -> Key press
MOD1-KEY_XXX: MOD2-KEY_YYY
# sequence (MOD1-KEY_XXX, MOD2-KEY_YYY) -> key press (MOD3-KEY_ZZZ)
# Sequence (MOD1-KEY_XXX, MOD2-KEY_YYY) -> Key press (MOD3-KEY_ZZZ)
MOD1-KEY_XXX:
remap:
MOD2-KEY_YYY: MOD3-KEY_ZZZ
timeout_millis: 200 # Optional. No timeout by default.
# key press (MOD1-KEY_XXX) -> sequence (MOD2-KEY_YYY, MOD3-KEY_ZZZ)
# Key press (MOD1-KEY_XXX) -> Sequence (MOD2-KEY_YYY, MOD3-KEY_ZZZ)
MOD1-KEY_XXX: [MOD2-KEY_YYY, MOD3-KEY_ZZZ]
# execute a command
# Execute a command
MOD1-KEY_XXX:
launch: ["bash", "-c", "echo hello > /tmp/test"]
# let `with_mark` also press a Shift key (useful for Emacs emulation)
# Let `with_mark` also press a Shift key (useful for Emacs emulation)
MOD1-KEY_XXX: { set_mark: true } # use { set_mark: false } to disable it
# also press Shift only when { set_mark: true } is used before
# Also press Shift only when { set_mark: true } is used before
MOD1-KEY_XXX: { with_mark: MOD2-KEY_YYY }
# the next key press will ignore keymap
# The next key press will ignore keymap
MOD1-KEY_XXX: { escape_next_key: true }
# set mode to configure Vim-like modal remapping
# Set mode to configure Vim-like modal remapping
MOD1-KEY_XXX: { set_mode: default }
application: # Optional
not: [Application, ...]

@ -1,9 +1,11 @@
use crate::config::key::deserialize_key;
use evdev::Key;
use serde::Deserialize;
use serde::{Deserialize, Deserializer};
use serde_with::{serde_as, DurationMilliSeconds};
use std::time::Duration;
use super::action::{Action, Actions};
// Values in `modmap.remap`
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
@ -11,6 +13,7 @@ pub enum KeyAction {
#[serde(deserialize_with = "deserialize_key")]
Key(Key),
MultiPurposeKey(MultiPurposeKey),
PressReleaseKey(PressReleaseKey),
}
#[serde_as]
@ -25,6 +28,23 @@ pub struct MultiPurposeKey {
pub alone_timeout: Duration,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
pub struct PressReleaseKey {
#[serde(deserialize_with = "deserialize_actions")]
pub press: Vec<Action>,
#[serde(deserialize_with = "deserialize_actions")]
pub release: Vec<Action>,
}
pub fn deserialize_actions<'de, D>(deserializer: D) -> Result<Vec<Action>, D::Error>
where
D: Deserializer<'de>,
{
let actions = Actions::deserialize(deserializer)?;
return Ok(actions.into_vec());
}
fn default_alone_timeout() -> Duration {
Duration::from_millis(1000)
}

@ -66,6 +66,17 @@ fn test_modmap_multi_purpose_key() {
"})
}
#[test]
fn test_modmap_press_release_key() {
assert_parse(indoc! {r#"
modmap:
- remap:
Space:
press: { launch: ["wmctrl", "-x", "-a", "code.Code"] }
release: { launch: ["wmctrl", "-x", "-a", "nocturn.Nocturn"] }
"#})
}
#[test]
fn test_keymap_basic() {
assert_parse(indoc! {"

@ -1,7 +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_action::{KeyAction, MultiPurposeKey, PressReleaseKey};
use crate::config::key_press::{KeyPress, Modifier, ModifierState};
use crate::config::keymap::expand_modifiers;
use crate::Config;
@ -78,7 +78,7 @@ impl EventHandler {
// Apply modmap
let mut key_values = if let Some(key_action) = self.find_modmap(config, &key) {
self.dispatch_keys(key_action, key, event.value())
self.dispatch_keys(key_action, key, event.value())?
} else {
vec![(key, event.value())]
};
@ -95,9 +95,7 @@ impl EventHandler {
if self.escape_next_key {
self.escape_next_key = false
} else if let Some(actions) = self.find_keymap(config, &key)? {
for action in &actions {
self.dispatch_action(action, &key)?;
}
self.dispatch_actions(&actions, &key)?;
continue;
}
}
@ -153,27 +151,36 @@ impl EventHandler {
}
}
fn dispatch_keys(&mut self, key_action: KeyAction, key: Key, value: i32) -> Vec<(Key, i32)> {
match key_action {
fn dispatch_keys(
&mut self,
key_action: KeyAction,
key: Key,
value: i32,
) -> Result<Vec<(Key, i32)>, Box<dyn Error>> {
let keys = match key_action {
KeyAction::Key(modmap_key) => vec![(modmap_key, value)],
KeyAction::MultiPurposeKey(multi_purpose_key) => {
KeyAction::MultiPurposeKey(MultiPurposeKey {
held,
alone,
alone_timeout,
}) => {
if value == PRESS {
self.multi_purpose_keys.insert(
key,
MultiPurposeKeyState {
held: multi_purpose_key.held,
alone: multi_purpose_key.alone,
alone_timeout_at: Some(Instant::now() + multi_purpose_key.alone_timeout),
held,
alone,
alone_timeout_at: Some(Instant::now() + alone_timeout),
},
);
return vec![]; // delay the press
return Ok(vec![]); // delay the press
} else if value == REPEAT {
if let Some(state) = self.multi_purpose_keys.get_mut(&key) {
return state.repeat();
return Ok(state.repeat());
}
} else if value == RELEASE {
if let Some(state) = self.multi_purpose_keys.remove(&key) {
return state.release();
return Ok(state.release());
}
} else {
panic!("unexpected key event value: {}", value);
@ -181,7 +188,18 @@ impl EventHandler {
// fallthrough on state discrepancy
vec![(key, value)]
}
KeyAction::PressReleaseKey(PressReleaseKey { press, release }) => {
if value == PRESS {
self.dispatch_actions(&press, &key)?;
}
if value == RELEASE {
self.dispatch_actions(&release, &key)?;
}
// While triggered by modmap, PressReleaseKey is not remapped by keymap.
vec![]
}
};
Ok(keys)
}
fn flush_timeout_keys(&mut self, key_values: Vec<(Key, i32)>) -> Vec<(Key, i32)> {
@ -253,6 +271,13 @@ impl EventHandler {
Ok(None)
}
fn dispatch_actions(&mut self, actions: &Vec<Action>, key: &Key) -> Result<(), Box<dyn Error>> {
for action in actions {
self.dispatch_action(action, key)?;
}
Ok(())
}
fn dispatch_action(&mut self, action: &Action, key: &Key) -> Result<(), Box<dyn Error>> {
match action {
Action::KeyPress(key_press) => self.send_key_press(key_press)?,

Loading…
Cancel
Save