Introduce keymap level `exact_match` option (#209)

* Introduce keymap level `exact_match` option.

Example:

  keymap:
    - exact_match: true
      remap:
        M-f: C-right

Given the above, and M-Shift-f is pressed:

- If `exact_match` is false or unset, the existing behaviour will be used,
  which will translate M-Shift-f to C-Shift-right.

- If `exact_match` is true, M-Shift-f will be sent as is, i.e. not matched.

* Perform exact match first.
pull/212/head
Lae Chen 2 years ago committed by GitHub
parent 1082271759
commit 14ca00cae3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -157,6 +157,7 @@ is supported only in `modmap` since `keymap` handles modifier keys differently.
```yml
modmap:
- name: Name # Optional
exact_match: false # Optional, defaults to false
remap: # Required
# Replace a key with another
KEY_XXX: KEY_YYY # Required
@ -235,6 +236,12 @@ remapping is triggered only on a left or right modifier, e.g. `Ctrl_L-a`.
If you use `virtual_modifiers` explained below, you can use it in the `MOD1-` part too.
`exact_match` defines whether to use exact match when matching key presses. For
example, given a mapping of `C-n: down` and `exact_match: false` (default), and
you pressed <kbd>C-Shift-n</kbd>, it will automatically be remapped to
<kbd>Shift-down</kbd>, without you having to define a mapping for
<kbd>C-Shift-n</kbd>, which you would have to do if you use `exact_match: true`.
### application
`application` can be used for both `modmap` and `keymap`, which allows you to specify application-specific remapping.

@ -19,6 +19,8 @@ pub struct Keymap {
pub application: Option<Application>,
#[serde(default, deserialize_with = "deserialize_string_or_vec")]
pub mode: Option<Vec<String>>,
#[serde(default)]
pub exact_match: bool,
}
fn deserialize_remap<'de, D>(deserializer: D) -> Result<HashMap<KeyPress, Vec<KeymapAction>>, D::Error>
@ -39,6 +41,7 @@ pub struct KeymapEntry {
pub modifiers: Vec<Modifier>,
pub application: Option<Application>,
pub mode: Option<Vec<String>>,
pub exact_match: bool,
}
// Convert an array of keymaps to a single hashmap whose key is a triggering key.
@ -60,6 +63,7 @@ pub fn build_keymap_table(keymaps: &Vec<Keymap>) -> HashMap<Key, Vec<KeymapEntry
modifiers: key_press.modifiers.clone(),
application: keymap.application.clone(),
mode: keymap.mode.clone(),
exact_match: keymap.exact_match,
});
table.insert(key_press.key, entries);
}

@ -31,6 +31,8 @@ pub struct EventHandler {
multi_purpose_keys: HashMap<Key, MultiPurposeKeyState>,
// Current nested remaps
override_remap: Option<HashMap<Key, Vec<OverrideEntry>>>,
// Whether to use exact match for override remaps
override_remap_exact_match: bool,
// Key triggered on a timeout of nested remaps
override_timeout_key: Option<Key>,
// Trigger a timeout of nested remaps through select(2)
@ -57,6 +59,7 @@ impl EventHandler {
application_cache: None,
multi_purpose_keys: HashMap::new(),
override_remap: None,
override_remap_exact_match: false,
override_timeout_key: None,
override_timer: timer,
mode: mode.to_string(),
@ -257,6 +260,9 @@ impl EventHandler {
if let Some(entries) = override_remap.clone().get(key) {
self.remove_override()?;
for exact_match in [true, false] {
if self.override_remap_exact_match && !exact_match {
continue;
}
for entry in entries {
let (extra_modifiers, missing_modifiers) = self.diff_modifiers(&entry.modifiers);
if (exact_match && extra_modifiers.len() > 0) || missing_modifiers.len() > 0 {
@ -272,6 +278,9 @@ impl EventHandler {
if let Some(entries) = config.keymap_table.get(key) {
for exact_match in [true, false] {
for entry in entries {
if entry.exact_match && !exact_match {
continue;
}
let (extra_modifiers, missing_modifiers) = self.diff_modifiers(&entry.modifiers);
if (exact_match && extra_modifiers.len() > 0) || missing_modifiers.len() > 0 {
continue;
@ -286,6 +295,7 @@ impl EventHandler {
continue;
}
}
self.override_remap_exact_match = entry.exact_match;
return Ok(Some(with_extra_modifiers(&entry.actions, &extra_modifiers)));
}
}

@ -58,6 +58,145 @@ fn test_interleave_modifiers() {
)
}
#[test]
fn test_exact_match_true() {
assert_actions(
indoc! {"
keymap:
- exact_match: true
remap:
M-f: C-right
"},
vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_F, KeyValue::Press)),
],
vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_F, KeyValue::Press)),
],
)
}
#[test]
fn test_exact_match_false() {
assert_actions(
indoc! {"
keymap:
- exact_match: false
remap:
M-f: C-right
"},
vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_F, KeyValue::Press)),
],
vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Release)),
Action::KeyEvent(KeyEvent::new(Key::KEY_RIGHT, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_RIGHT, KeyValue::Release)),
Action::Delay(Duration::from_nanos(0)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
],
)
}
#[test]
fn test_exact_match_default() {
assert_actions(
indoc! {"
keymap:
- remap:
M-f: C-right
"},
vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_F, KeyValue::Press)),
],
vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Release)),
Action::KeyEvent(KeyEvent::new(Key::KEY_RIGHT, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_RIGHT, KeyValue::Release)),
Action::Delay(Duration::from_nanos(0)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTALT, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
],
)
}
#[test]
fn test_exact_match_true_nested() {
assert_actions(
indoc! {"
keymap:
- exact_match: true
remap:
C-x:
remap:
h: C-a
"},
vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_H, KeyValue::Press)),
],
vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_H, KeyValue::Press)),
],
)
}
#[test]
fn test_exact_match_false_nested() {
assert_actions(
indoc! {"
keymap:
- exact_match: false
remap:
C-x:
remap:
h: C-a
"},
vec![
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
Event::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Event::KeyEvent(KeyEvent::new(Key::KEY_H, KeyValue::Press)),
],
vec![
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_X, KeyValue::Release)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTSHIFT, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_A, KeyValue::Press)),
Action::KeyEvent(KeyEvent::new(Key::KEY_A, KeyValue::Release)),
Action::Delay(Duration::from_nanos(0)),
Action::KeyEvent(KeyEvent::new(Key::KEY_LEFTCTRL, KeyValue::Release)),
],
)
}
fn assert_actions(config_yaml: &str, events: Vec<Event>, actions: Vec<Action>) {
let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap();
let mut config: Config = serde_yaml::from_str(config_yaml).unwrap();

Loading…
Cancel
Save