use super::*;
fn matcher() -> HotkeyMatcher {
HotkeyMatcher::new(Bindings {
trigger_mode: TriggerMode::DoubleTap,
hold: Duration::from_millis(u64::from(HOLD_DEFAULT)),
double_tap: DoubleTap::parse("right_alt").unwrap(),
home: vec![
Chord::parse("scroll_lock").unwrap(),
Chord::parse("ctrl+alt+shift+home").unwrap(),
],
lock: Chord::parse("ctrl+alt+shift+l").unwrap(),
jump_mods: Mods::parse("ctrl+alt+shift").unwrap(),
})
}
#[test]
fn parse_key_table_accepts_known_names_and_rejects_bad_tokens() {
let names = [
("A", 0x04),
("z", 0x1d),
("0", 0x27),
("1", 0x1e),
("9", 0x26),
("f1", 0x3a),
("F12", 0x45),
("home", 0x4a),
("end", 0x4d),
("insert", 0x49),
("delete", 0x4c),
("page_up", 0x4b),
("page_down", 0x4e),
("scroll_lock", 0x47),
("pause", 0x48),
("escape", 0x29),
];
for (name, usage) in names {
let chord = Chord::parse(&format!("CTRL+alt+Shift+{name}")).unwrap();
assert_eq!(chord.key, KEYBOARD_PAGE | usage);
assert_eq!(chord.mods, Mods::parse("ctrl+alt+shift").unwrap());
}
for (text, bad) in [
("foo", "foo"),
("a+b", "b"),
("", ""),
("ctrl", "ctrl"),
("ctrl+ctrl+a", "ctrl"),
("a+", ""),
] {
let error = Chord::parse(text).unwrap_err();
assert!(error.to_string().contains(bad));
}
}
#[test]
fn on_key_chords_consume_down_repeat_and_matching_up() {
let now = Instant::now();
for (key, mods, action) in [
(0x47, &[][..], Hotkey::Home),
(0x4a, &[0xe0, 0xe2, 0xe1][..], Hotkey::Home),
(0x0f, &[0xe4, 0xe6, 0xe5][..], Hotkey::Lock),
(0x26, &[0xe0, 0xe2, 0xe1][..], Hotkey::Jump(9)),
] {
let mut matcher = matcher();
for modifier in mods {
assert!(matcher.on_key(KEYBOARD_PAGE | modifier, true, now).forward);
}
let down = matcher.on_key(KEYBOARD_PAGE | key, true, now);
let repeat = matcher.on_key(KEYBOARD_PAGE | key, true, now);
for modifier in mods {
matcher.on_key(KEYBOARD_PAGE | modifier, false, now);
}
let up = matcher.on_key(KEYBOARD_PAGE | key, false, now);
assert_eq!(
down,
Verdict {
forward: false,
action: Some(action)
}
);
assert_eq!(
repeat,
Verdict {
forward: false,
action: None
}
);
assert_eq!(
up,
Verdict {
forward: false,
action: None
}
);
}
}
#[test]
fn on_key_extra_modifier_forwards_chord() {
let mut matcher = matcher();
let now = Instant::now();
for modifier in [0xe0, 0xe1, 0xe2, 0xe3] {
matcher.on_key(KEYBOARD_PAGE | modifier, true, now);
}
let verdict = matcher.on_key(KEYBOARD_PAGE | 0x4a, true, now);
assert_eq!(
verdict,
Verdict {
forward: true,
action: None
}
);
}
#[test]
fn on_key_double_tap_timing_and_interruption_control_home() {
for (delay, interrupt, expected) in [
(399, None, Some(Hotkey::Home)),
(400, None, Some(Hotkey::Home)),
(401, None, None),
(200, Some(0x04), None),
(200, Some(0xe0), None),
(200, Some(0x000c_0001), None),
] {
let mut matcher = matcher();
let now = Instant::now();
let alt = KEYBOARD_PAGE | 0xe6;
let first = matcher.on_key(alt, true, now);
let release = matcher.on_key(alt, false, now);
if let Some(key) = interrupt {
matcher.on_key(if key < 256 { KEYBOARD_PAGE | key } else { key }, true, now);
}
let second = matcher.on_key(alt, true, now + Duration::from_millis(delay));
let second_release = matcher.on_key(alt, false, now + Duration::from_millis(delay));
assert!(first.forward && release.forward && second.forward && second_release.forward);
assert_eq!(first.action, None);
assert_eq!(release.action, None);
assert_eq!(second.action, expected);
assert_eq!(second_release.action, None);
}
}
#[test]
fn on_key_expired_second_tap_starts_new_window() {
let mut matcher = matcher();
let now = Instant::now();
let alt = KEYBOARD_PAGE | 0xe6;
for delay in [0, 401] {
matcher.on_key(alt, true, now + Duration::from_millis(delay));
matcher.on_key(alt, false, now + Duration::from_millis(delay));
}
let verdict = matcher.on_key(alt, true, now + Duration::from_millis(500));
assert_eq!(verdict.action, Some(Hotkey::Home));
}
#[test]
fn on_key_modifier_repeat_requires_release_before_second_tap() {
let mut matcher = matcher();
let now = Instant::now();
matcher.on_key(KEYBOARD_PAGE | 0xe6, true, now);
let repeat = matcher.on_key(KEYBOARD_PAGE | 0xe6, true, now + Duration::from_millis(100));
assert_eq!(
repeat,
Verdict {
forward: true,
action: None
}
);
}
#[test]
fn on_key_double_tap_sides_match_configured_modifier() {
for side in ["left", "right", "either"] {
for (name, offset) in [("ctrl", 0), ("shift", 1), ("alt", 2), ("meta", 3)] {
for right in [false, true] {
let mut matcher = matcher();
matcher.bindings.double_tap = DoubleTap::parse(&format!("{side}_{name}")).unwrap();
let usage = KEYBOARD_PAGE | (MODIFIER_START + offset + if right { 4 } else { 0 });
let now = Instant::now();
matcher.on_key(usage, true, now);
matcher.on_key(usage, false, now);
let verdict = matcher.on_key(usage, true, now + Duration::from_millis(100));
assert_eq!(
verdict.action,
(side == "either" || (side == "right") == right).then_some(Hotkey::Home)
);
}
}
}
}
mod hold;