From 4c5a313088358a4001fe8a2a7c2ac14641585a04 Mon Sep 17 00:00:00 2001 From: jtroo Date: Tue, 23 Apr 2024 07:37:27 -0700 Subject: [PATCH] feat: unblock and forward buttons properly (#972) Closes #968. For now there is no way to block mouse buttons that are being forwarded to kanata. If someone requests it or opens a PR, the feature may be included, but for now I don't see a reason why it's necessary. It seems much more sensible for someone to want to block their unused keyboard buttons to force the learning of new typing habits compared to blocking mouse buttons for a similar reason. --- justfile | 2 +- keyberon/src/action/switch.rs | 204 ++++++++++-------------- keyberon/src/chord.rs | 6 +- keyberon/src/key_code.rs | 4 + keyberon/src/layout.rs | 82 +++++----- parser/src/cfg/is_a_button.rs | 178 +++++++++++++++++++++ parser/src/cfg/mod.rs | 7 +- parser/src/cfg/tests.rs | 6 +- parser/src/cfg/tests/ambiguous.rs | 32 ++++ parser/src/keys/linux.rs | 4 + parser/src/keys/macos.rs | 4 + parser/src/keys/mappings.rs | 8 + parser/src/keys/mod.rs | 4 - parser/src/keys/windows.rs | 14 +- src/kanata/mod.rs | 48 +----- src/kanata/output_logic.rs | 93 +++++++++++ src/oskbd/simulated.rs | 1 + src/tests/sim_tests/block_keys_tests.rs | 35 ++++ src/tests/sim_tests/mod.rs | 1 + 19 files changed, 509 insertions(+), 224 deletions(-) create mode 100644 parser/src/cfg/is_a_button.rs create mode 100644 parser/src/cfg/tests/ambiguous.rs create mode 100644 src/kanata/output_logic.rs create mode 100644 src/tests/sim_tests/block_keys_tests.rs diff --git a/justfile b/justfile index 7797f0c38..f4de382b2 100644 --- a/justfile +++ b/justfile @@ -26,7 +26,7 @@ sha256sums output_dir: cd {{output_dir}}; sha256sum * > sha256sums test: - cargo test --verbose -p kanata -p kanata-parser -p kanata-keyberon -- --nocapture + cargo test -p kanata -p kanata-parser -p kanata-keyberon -- --nocapture cargo clippy --all fmt: diff --git a/keyberon/src/action/switch.rs b/keyberon/src/action/switch.rs index 6a00d370a..c5750b41b 100644 --- a/keyberon/src/action/switch.rs +++ b/keyberon/src/action/switch.rs @@ -1094,16 +1094,13 @@ fn bool_evaluation_test_not_0() { OpCode::new_key(KeyCode::F), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - false, - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied(), - [].iter().copied(), - [].iter().copied(), - ) - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied(), + [].iter().copied(), + [].iter().copied(), + )); } #[test] @@ -1115,16 +1112,13 @@ fn bool_evaluation_test_not_1() { OpCode::new_key(KeyCode::B), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - false, - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied(), - [].iter().copied(), - [].iter().copied() - ) - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied(), + [].iter().copied(), + [].iter().copied() + )); } #[test] @@ -1136,16 +1130,13 @@ fn bool_evaluation_test_not_2() { OpCode::new_key(KeyCode::Y), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - true, - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied(), - [].iter().copied(), - [].iter().copied() - ) - ); + assert!(evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied(), + [].iter().copied(), + [].iter().copied() + )); } #[test] @@ -1156,16 +1147,13 @@ fn bool_evaluation_test_not_3() { OpCode::new_key(KeyCode::D), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - false, - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied(), - [].iter().copied(), - [].iter().copied() - ) - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied(), + [].iter().copied(), + [].iter().copied() + )); } #[test] @@ -1183,16 +1171,13 @@ fn bool_evaluation_test_not_4() { OpCode::new_key(KeyCode::F), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - false, - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied(), - [].iter().copied(), - [].iter().copied() - ) - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied(), + [].iter().copied(), + [].iter().copied() + )); } #[test] @@ -1204,16 +1189,13 @@ fn bool_evaluation_test_not_5() { OpCode::new_key(KeyCode::D), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - true, - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied(), - [].iter().copied(), - [].iter().copied() - ) - ); + assert!(evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied(), + [].iter().copied(), + [].iter().copied() + )); } #[test] @@ -1226,16 +1208,13 @@ fn bool_evaluation_test_not_6() { OpCode::new_key(KeyCode::D), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - false, - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied(), - [].iter().copied(), - [].iter().copied() - ) - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied(), + [].iter().copied(), + [].iter().copied() + )); } #[test] @@ -1247,16 +1226,13 @@ fn bool_evaluation_test_or_equivalency_not_6() { OpCode::new_key(KeyCode::D), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - false, - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied(), - [].iter().copied(), - [].iter().copied() - ) - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied(), + [].iter().copied(), + [].iter().copied() + )); } #[test] @@ -1268,16 +1244,13 @@ fn bool_evaluation_test_not_7() { OpCode::new_key(KeyCode::E), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - false, - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied(), - [].iter().copied(), - [].iter().copied() - ) - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied(), + [].iter().copied(), + [].iter().copied() + )); } #[test] @@ -1289,16 +1262,13 @@ fn bool_evaluation_test_or_equivalency_not_7() { OpCode::new_key(KeyCode::E), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - false, - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied(), - [].iter().copied(), - [].iter().copied() - ) - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied(), + [].iter().copied(), + [].iter().copied() + )); } #[test] @@ -1310,16 +1280,13 @@ fn bool_evaluation_test_not_8() { OpCode::new_key(KeyCode::A), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - false, - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied(), - [].iter().copied(), - [].iter().copied() - ) - ); + assert!(!evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied(), + [].iter().copied(), + [].iter().copied() + )); } #[test] @@ -1331,16 +1298,13 @@ fn bool_evaluation_test_not_9() { OpCode::new_key(KeyCode::C), ]; let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F]; - assert_eq!( - true, - evaluate_boolean( - opcodes.as_slice(), - keycodes.iter().copied(), - [].iter().copied(), - [].iter().copied(), - [].iter().copied() - ) - ); + assert!(evaluate_boolean( + opcodes.as_slice(), + keycodes.iter().copied(), + [].iter().copied(), + [].iter().copied(), + [].iter().copied() + )); } #[test] diff --git a/keyberon/src/chord.rs b/keyberon/src/chord.rs index 4340e1885..620385199 100644 --- a/keyberon/src/chord.rs +++ b/keyberon/src/chord.rs @@ -310,7 +310,6 @@ impl<'a, T> ChordsV2<'a, T> { return; }; let Some(possible_chords) = self.chords.mapping.get(starting_press) else { - dbg!("no chord activations"); no_chord_activations!(self, "no chords for key"); return; }; @@ -403,7 +402,6 @@ impl<'a, T> ChordsV2<'a, T> { .iter() .all(|pk| accumulated_presses.contains(pk)) { - dbg!("got exact chord match"); let ach = get_active_chord(cch, since, coord, relevant_release_found); let overflow = self.active_chords.push(ach); assert!(overflow.is_ok(), "active chords has room"); @@ -448,7 +446,7 @@ impl<'a, T> ChordsV2<'a, T> { } _ => {} } - self.ticks_until_next_state_change = dbg!(min_timeout.saturating_sub(since)); + self.ticks_until_next_state_change = min_timeout.saturating_sub(since); prev_count = count_possible; } if self.ticks_until_next_state_change == 0 || relevant_release_found { @@ -553,6 +551,6 @@ fn get_active_chord<'a, T>( } else { ActiveChordStatus::Unread }, - delay: dbg!(since), + delay: since, } } diff --git a/keyberon/src/key_code.rs b/keyberon/src/key_code.rs index 84412edc6..24ee730b0 100644 --- a/keyberon/src/key_code.rs +++ b/keyberon/src/key_code.rs @@ -817,6 +817,10 @@ pub enum KeyCode { K742, K743, K744, + MWU, + MWD, + MWL, + MWR, KeyMax, } diff --git a/keyberon/src/layout.rs b/keyberon/src/layout.rs index 6f0f23c81..bd8c4abb5 100644 --- a/keyberon/src/layout.rs +++ b/keyberon/src/layout.rs @@ -1631,7 +1631,7 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< let waiting: WaitingState = WaitingState { coord, timeout: if self.quick_tap_hold_timeout { - dbg!(timeout.saturating_sub(delay)) + timeout.saturating_sub(delay) } else { *timeout }, @@ -2050,7 +2050,7 @@ mod test { ]], [[Trans, MultipleKeyCodes(&[LCtrl, Enter].as_slice())]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); assert_eq!(CustomEvent::NoEvent, layout.tick()); assert_keys(&[], layout.keycodes()); layout.event(Press(0, 1)); @@ -2102,7 +2102,7 @@ mod test { ]], [[Trans, MultipleKeyCodes(&[LCtrl, Enter].as_slice())]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); assert_eq!(CustomEvent::NoEvent, layout.tick()); assert_keys(&[], layout.keycodes()); layout.event(Press(0, 1)); @@ -2151,7 +2151,7 @@ mod test { tap_hold_interval: 0, }), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); assert_eq!(CustomEvent::NoEvent, layout.tick()); assert_keys(&[], layout.keycodes()); layout.event(Press(0, 0)); @@ -2193,7 +2193,7 @@ mod test { }), k(Enter), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); // Press another key before timeout assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -2251,7 +2251,7 @@ mod test { }), k(Enter), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); // Press and release another key before timeout assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -2306,7 +2306,7 @@ mod test { tap_hold_interval: 0, }), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.quick_tap_hold_timeout = true; // Press and release another key before timeout @@ -2344,7 +2344,7 @@ mod test { [[MultipleActions(&[l(1), k(LShift)].as_slice()), k(F)]], [[Trans, k(E)]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); assert_eq!(CustomEvent::NoEvent, layout.tick()); assert_keys(&[], layout.keycodes()); layout.event(Press(0, 0)); @@ -2364,7 +2364,7 @@ mod test { #[test] fn custom() { static LAYERS: Layers<1, 1, i32> = &[[[Action::Custom(42)]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); assert_eq!(CustomEvent::NoEvent, layout.tick()); assert_keys(&[], layout.keycodes()); @@ -2391,7 +2391,7 @@ mod test { [[l(0), k(B)]], [[k(C), k(D)]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); assert_eq!(CustomEvent::NoEvent, layout.tick()); assert_eq!(0, layout.current_layer()); assert_keys(&[], layout.keycodes()); @@ -2494,7 +2494,7 @@ mod test { tap_hold_interval: 0, }), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); assert_eq!(CustomEvent::NoEvent, layout.tick()); assert_keys(&[], layout.keycodes()); @@ -2568,7 +2568,7 @@ mod test { }), k(Enter), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); // press and release the HT key, expect tap action assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -2631,7 +2631,7 @@ mod test { tap_hold_interval: 200, }), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); // press and release the HT key, expect tap action assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -2743,7 +2743,7 @@ mod test { config: HoldTapConfig::Default, tap_hold_interval: 200, })]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); // press and hold the HT key, expect hold action assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -2795,7 +2795,7 @@ mod test { tap_hold_interval: 200, }), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); // press HT1, press HT2, release HT1 after hold timeout, release HT2, press HT2 layout.event(Press(0, 0)); @@ -2836,7 +2836,7 @@ mod test { k(A), k(B), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.oneshot.on_press_release_delay = 1; // Test: @@ -2948,7 +2948,7 @@ mod test { k(A), k(B), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.oneshot.on_press_release_delay = 1; // Test: @@ -3102,7 +3102,7 @@ mod test { k(A), k(B), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); // Test: // 1. press one-shot @@ -3259,7 +3259,7 @@ mod test { ]], [[k(A), k(B), k(C), k(D)]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.oneshot.on_press_release_delay = 1; layout.event(Press(0, 0)); @@ -3315,7 +3315,7 @@ mod test { ]], [[k(A), k(B), k(C)]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.oneshot.on_press_release_delay = 1; layout.event(Press(0, 0)); @@ -3383,7 +3383,7 @@ mod test { ], [k(B), k(C)], ]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); // Test: tap-dance first key, timeout layout.event(Press(0, 0)); @@ -3508,7 +3508,7 @@ mod test { ], [k(B), k(C)], ]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); // Test: tap-dance-eager first key layout.event(Press(0, 0)); @@ -3597,7 +3597,7 @@ mod test { ]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 1)); assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -3652,7 +3652,7 @@ mod test { Chords(&GROUP), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 2)); // timeout on non-terminal chord for _ in 0..50 { @@ -3781,7 +3781,7 @@ mod test { Chords(&GROUP), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 2)); // timeout on non-terminal chord for _ in 0..50 { @@ -3833,7 +3833,7 @@ mod test { }; static LAYERS: Layers<2, 1> = &[[[Chords(&GROUP), Chords(&GROUP)]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.quick_tap_hold_timeout = true; layout.event(Press(0, 0)); layout.event(Press(0, 1)); @@ -3867,7 +3867,7 @@ mod test { }), k(Space), ]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 0)); assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -3908,7 +3908,7 @@ mod test { Layer(1), ]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); // Press a key layout.event(Press(0, 0)); @@ -3998,7 +3998,7 @@ mod test { #[test] fn test_clear_multiple_keycodes() { static LAYERS: Layers<2, 1> = &[[[k(A), MultipleKeyCodes(&[LCtrl, Enter].as_slice())]]]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 1)); assert_eq!(CustomEvent::NoEvent, layout.tick()); assert_keys(&[LCtrl, Enter], layout.keycodes()); @@ -4018,7 +4018,7 @@ mod test { [[NoOp, NoOp, Layer(3), Trans]], [[NoOp, NoOp, NoOp, Trans]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); // change to layer 2 layout.event(Press(0, 0)); @@ -4049,7 +4049,7 @@ mod test { [[Layer(1), Trans]], [[NoOp, MultipleActions(&[Trans].as_slice())]], ]; - let mut layout = Layout::new_with_trans_action_settings(&DEFSRC_LAYER, &LAYERS, true, true); + let mut layout = Layout::new_with_trans_action_settings(&DEFSRC_LAYER, LAYERS, true, true); layout.event(Press(0, 0)); assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -4080,7 +4080,7 @@ mod test { }), ]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 0)); assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -4127,7 +4127,7 @@ mod test { }), ]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 0)); assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -4164,7 +4164,7 @@ mod test { }), ]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 0)); assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -4203,7 +4203,7 @@ mod test { }), ]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 0)); assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -4228,7 +4228,7 @@ mod test { [[NoOp, Layer(2), k(B)]], [[NoOp, NoOp, MultipleActions(&[Trans, k(X)].as_slice())]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 0)); assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -4258,7 +4258,7 @@ mod test { [[NoOp, Layer(2), k(C), k(D)]], [[NoOp, NoOp, Chords(&GROUP), Chords(&GROUP)]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 0)); assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -4293,7 +4293,7 @@ mod test { }), ]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 0)); assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -4322,7 +4322,7 @@ mod test { }), ]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 0)); assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -4387,7 +4387,7 @@ mod test { }), ]], ]; - let mut layout = Layout::new(&LAYERS); + let mut layout = Layout::new(LAYERS); layout.event(Press(0, 0)); assert_eq!(CustomEvent::NoEvent, layout.tick()); @@ -4428,7 +4428,7 @@ mod test { for &do_layer_switch in &[false, true] { let mut layout = Layout::new_with_trans_action_settings( &DEFSRC_LAYER, - &LAYERS, + LAYERS, trans_v2, delegate_to_1st, ); diff --git a/parser/src/cfg/is_a_button.rs b/parser/src/cfg/is_a_button.rs new file mode 100644 index 000000000..a5a753454 --- /dev/null +++ b/parser/src/cfg/is_a_button.rs @@ -0,0 +1,178 @@ +pub(crate) fn is_a_button(osc: u16) -> bool { + if cfg!(target_os = "windows") { + matches!(osc, 1..=6 | 256..) + } else { + osc >= 256 + } +} + +#[test] +fn mouse_inputs_most_care_about_are_considered_buttons() { + use crate::keys::{OsCode, OsCode::*}; + const MOUSE_INPUTS: &[OsCode] = &[ + MouseWheelUp, + MouseWheelDown, + MouseWheelLeft, + MouseWheelRight, + BTN_LEFT, + BTN_RIGHT, + BTN_MIDDLE, + BTN_SIDE, + BTN_EXTRA, + BTN_FORWARD, + BTN_BACK, + ]; + for input in MOUSE_INPUTS.iter().copied() { + println!("{input}"); + assert!(is_a_button(input.into())); + } +} + +#[test] +fn standard_keys_are_not_considered_buttons() { + use crate::keys::{OsCode, OsCode::*}; + const KEY_INPUTS: &[OsCode] = &[ + KEY_0, + KEY_1, + KEY_2, + KEY_3, + KEY_4, + KEY_5, + KEY_6, + KEY_7, + KEY_8, + KEY_9, + KEY_A, + KEY_B, + KEY_C, + KEY_D, + KEY_E, + KEY_F, + KEY_G, + KEY_H, + KEY_I, + KEY_J, + KEY_K, + KEY_L, + KEY_M, + KEY_N, + KEY_O, + KEY_P, + KEY_Q, + KEY_R, + KEY_S, + KEY_T, + KEY_U, + KEY_V, + KEY_W, + KEY_X, + KEY_Y, + KEY_Z, + KEY_SEMICOLON, + KEY_SLASH, + KEY_GRAVE, + KEY_LEFTBRACE, + KEY_BACKSLASH, + KEY_RIGHTBRACE, + KEY_APOSTROPHE, + KEY_MINUS, + KEY_DOT, + KEY_EQUAL, + KEY_BACKSPACE, + KEY_ESC, + KEY_TAB, + KEY_ENTER, + KEY_LEFTCTRL, + KEY_LEFTSHIFT, + KEY_COMMA, + KEY_RIGHTSHIFT, + KEY_KPASTERISK, + KEY_LEFTALT, + KEY_SPACE, + KEY_CAPSLOCK, + KEY_F1, + KEY_F2, + KEY_F3, + KEY_F4, + KEY_F5, + KEY_F6, + KEY_F7, + KEY_F8, + KEY_F9, + KEY_F10, + KEY_F11, + KEY_F12, + KEY_NUMLOCK, + KEY_SCROLLLOCK, + KEY_KP0, + KEY_KP1, + KEY_KP2, + KEY_KP3, + KEY_KP4, + KEY_KP5, + KEY_KP6, + KEY_KP7, + KEY_KP8, + KEY_KP9, + KEY_KPMINUS, + KEY_KPPLUS, + KEY_KPDOT, + KEY_KPENTER, + KEY_RIGHTCTRL, + KEY_KPSLASH, + KEY_RIGHTALT, + KEY_HOME, + KEY_UP, + KEY_PAGEUP, + KEY_LEFT, + KEY_RIGHT, + KEY_END, + KEY_DOWN, + KEY_PAGEDOWN, + KEY_INSERT, + KEY_DELETE, + KEY_MUTE, + KEY_VOLUMEDOWN, + KEY_VOLUMEUP, + KEY_PAUSE, + KEY_LEFTMETA, + KEY_RIGHTMETA, + KEY_COMPOSE, + KEY_BACK, + KEY_FORWARD, + KEY_NEXTSONG, + KEY_PLAYPAUSE, + KEY_PREVIOUSSONG, + KEY_STOP, + KEY_HOMEPAGE, + KEY_MAIL, + KEY_MEDIA, + KEY_REFRESH, + KEY_F13, + KEY_F14, + KEY_F15, + KEY_F16, + KEY_F17, + KEY_F18, + KEY_F19, + KEY_F20, + KEY_F21, + KEY_F22, + KEY_F23, + KEY_F24, + KEY_HANGEUL, + KEY_HANJA, + KEY_252, + KEY_102ND, + KEY_PLAY, + KEY_PRINT, + KEY_SEARCH, + KEY_RO, + KEY_HENKAN, + KEY_MUHENKAN, + ]; + for input in KEY_INPUTS.iter().copied() { + println!("{input}"); + assert!(!is_a_button(input.into())); + } +} diff --git a/parser/src/cfg/mod.rs b/parser/src/cfg/mod.rs index 778051a37..352eb7f54 100755 --- a/parser/src/cfg/mod.rs +++ b/parser/src/cfg/mod.rs @@ -78,6 +78,9 @@ pub use fake_key::{FAKE_KEY_ROW, NORMAL_KEY_ROW}; mod os_dependent; use os_dependent::*; +mod is_a_button; +use is_a_button::*; + use crate::trie::Trie; use anyhow::anyhow; use std::cell::Cell; @@ -2936,8 +2939,8 @@ fn parse_layers( } } } - for layer_action in layers_cfg[layer_level][0].iter_mut() { - if *layer_action == Action::Trans && s.block_unmapped_keys { + for (osc, layer_action) in layers_cfg[layer_level][0].iter_mut().enumerate() { + if s.block_unmapped_keys && *layer_action == Action::Trans && !is_a_button(osc as u16) { *layer_action = Action::NoOp; } } diff --git a/parser/src/cfg/tests.rs b/parser/src/cfg/tests.rs index 2cbd015bb..d42fd88f7 100644 --- a/parser/src/cfg/tests.rs +++ b/parser/src/cfg/tests.rs @@ -5,6 +5,8 @@ use kanata_keyberon::action::BooleanOperator::*; use std::sync::{Mutex, MutexGuard}; +mod ambiguous; + static CFG_PARSE_LOCK: Mutex<()> = Mutex::new(()); fn lock(lk: &Mutex) -> MutexGuard { @@ -17,7 +19,7 @@ fn lock(lk: &Mutex) -> MutexGuard { fn parse_cfg(cfg: &str) -> Result { let _lk = lock(&CFG_PARSE_LOCK); let mut s = ParserState::default(); - Ok(parse_cfg_raw_string( + parse_cfg_raw_string( cfg, &mut s, &PathBuf::from("test"), @@ -26,7 +28,7 @@ fn parse_cfg(cfg: &str) -> Result { }, DEF_LOCAL_KEYS, Err("env vars not implemented".into()), - )?) + ) } #[test] diff --git a/parser/src/cfg/tests/ambiguous.rs b/parser/src/cfg/tests/ambiguous.rs new file mode 100644 index 000000000..330ab84f5 --- /dev/null +++ b/parser/src/cfg/tests/ambiguous.rs @@ -0,0 +1,32 @@ +use super::*; + +#[test] +fn parse_double_dollar_var() { + let source = r#" +(defsrc) +(deflayer base) +(defvar $$num 100 + $num 99 + num not-a-number-or-key) +(defalias test + (movemouse-accel-up $$num $$$num $$num $$$num)) +"#; + parse_cfg(source) + .map_err(|e| eprintln!("{:?}", miette::Error::from(e))) + .expect("parses"); +} + +#[test] +fn parse_double_at_alias() { + let source = r#" +(defsrc) +(deflayer base) + ;; alias cannot be used in macro, @alias can +(defalias @alias 0 + alias (tap-hold 9 9 a b) + test (macro @@alias)) +"#; + parse_cfg(source) + .map_err(|e| eprintln!("{:?}", miette::Error::from(e))) + .expect("parses"); +} diff --git a/parser/src/keys/linux.rs b/parser/src/keys/linux.rs index 43ae238a0..dbec816f0 100644 --- a/parser/src/keys/linux.rs +++ b/parser/src/keys/linux.rs @@ -753,6 +753,10 @@ impl OsCode { 742 => Some(OsCode::BTN_TRIGGER_HAPPY39), 743 => Some(OsCode::BTN_TRIGGER_HAPPY40), 744 => Some(OsCode::BTN_MAX), + 745 => Some(OsCode::MouseWheelUp), + 746 => Some(OsCode::MouseWheelDown), + 747 => Some(OsCode::MouseWheelLeft), + 748 => Some(OsCode::MouseWheelRight), 767 => Some(OsCode::KEY_MAX), _ => None, } diff --git a/parser/src/keys/macos.rs b/parser/src/keys/macos.rs index b747dc321..3abdfc8e5 100644 --- a/parser/src/keys/macos.rs +++ b/parser/src/keys/macos.rs @@ -2011,6 +2011,10 @@ impl OsCode { 742 => Some(OsCode::BTN_TRIGGER_HAPPY39), 743 => Some(OsCode::BTN_TRIGGER_HAPPY40), 744 => Some(OsCode::BTN_MAX), + 745 => Some(OsCode::MouseWheelUp), + 746 => Some(OsCode::MouseWheelDown), + 747 => Some(OsCode::MouseWheelLeft), + 748 => Some(OsCode::MouseWheelRight), 767 => Some(OsCode::KEY_MAX), _ => None, } diff --git a/parser/src/keys/mappings.rs b/parser/src/keys/mappings.rs index b45e3f81c..904420836 100644 --- a/parser/src/keys/mappings.rs +++ b/parser/src/keys/mappings.rs @@ -851,6 +851,10 @@ fn haphazard_osc_to_kc_mappings(osc: OsCode) -> KeyCode { OsCode::BTN_TRIGGER_HAPPY38 => KeyCode::K741, OsCode::BTN_TRIGGER_HAPPY39 => KeyCode::K742, OsCode::BTN_TRIGGER_HAPPY40 => KeyCode::K743, + OsCode::MouseWheelUp => KeyCode::MWU, + OsCode::MouseWheelDown => KeyCode::MWD, + OsCode::MouseWheelLeft => KeyCode::MWL, + OsCode::MouseWheelRight => KeyCode::MWR, OsCode::BTN_MAX => KeyCode::K744, _ => KeyCode::No, } @@ -1351,6 +1355,10 @@ fn haphazard_kc_to_osc_mappings(kc: KeyCode) -> OsCode { KeyCode::K742 => OsCode::BTN_TRIGGER_HAPPY39, KeyCode::K743 => OsCode::BTN_TRIGGER_HAPPY40, KeyCode::K744 => OsCode::BTN_MAX, + KeyCode::MWU => OsCode::MouseWheelUp, + KeyCode::MWD => OsCode::MouseWheelDown, + KeyCode::MWL => OsCode::MouseWheelLeft, + KeyCode::MWR => OsCode::MouseWheelRight, _ => OsCode::KEY_UNKNOWN, } } diff --git a/parser/src/keys/mod.rs b/parser/src/keys/mod.rs index 3991c1645..492098dd2 100644 --- a/parser/src/keys/mod.rs +++ b/parser/src/keys/mod.rs @@ -305,13 +305,9 @@ pub fn str_to_oscode(s: &str) -> Option { "mfwd" | "mouseforward" | "🖰5" => OsCode::BTN_EXTRA, // NOTE: these are linux and interception-only due to missing implementation for LLHOOK - #[cfg(any(target_os = "linux", target_os = "unknown", feature = "interception_driver"))] "mwu" | "mousewheelup" => OsCode::MouseWheelUp, - #[cfg(any(target_os = "linux", target_os = "unknown", feature = "interception_driver"))] "mwd" | "mousewheeldown" => OsCode::MouseWheelDown, - #[cfg(any(target_os = "linux", target_os = "unknown", feature = "interception_driver"))] "mwl" | "mousewheelleft" => OsCode::MouseWheelLeft, - #[cfg(any(target_os = "linux", target_os = "unknown", feature = "interception_driver"))] "mwr" | "mousewheelright" => OsCode::MouseWheelRight, "hmpg" | "homepage" => OsCode::KEY_HOMEPAGE, diff --git a/parser/src/keys/windows.rs b/parser/src/keys/windows.rs index b7b3bec1a..652267254 100644 --- a/parser/src/keys/windows.rs +++ b/parser/src/keys/windows.rs @@ -425,11 +425,11 @@ impl OsCode { 269 => Some(OsCode::KEY_269), 270 => Some(OsCode::KEY_270), 271 => Some(OsCode::KEY_271), - 272 => Some(OsCode::BTN_LEFT), - 273 => Some(OsCode::BTN_RIGHT), - 274 => Some(OsCode::BTN_MIDDLE), - 275 => Some(OsCode::BTN_SIDE), - 276 => Some(OsCode::BTN_EXTRA), + 1 => Some(OsCode::BTN_LEFT), + 2 => Some(OsCode::BTN_RIGHT), + 4 => Some(OsCode::BTN_MIDDLE), + 5 => Some(OsCode::BTN_SIDE), + 6 => Some(OsCode::BTN_EXTRA), 277 => Some(OsCode::BTN_FORWARD), 278 => Some(OsCode::BTN_BACK), 279 => Some(OsCode::BTN_TASK), @@ -898,6 +898,10 @@ impl OsCode { 742 => Some(OsCode::BTN_TRIGGER_HAPPY39), 743 => Some(OsCode::BTN_TRIGGER_HAPPY40), 744 => Some(OsCode::BTN_MAX), + 745 => Some(OsCode::MouseWheelUp), + 746 => Some(OsCode::MouseWheelDown), + 747 => Some(OsCode::MouseWheelLeft), + 748 => Some(OsCode::MouseWheelRight), 767 => Some(OsCode::KEY_MAX), _ => None, } diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index 02ed69f60..98f52e7db 100755 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -48,6 +48,9 @@ mod macos; #[cfg(target_os = "macos")] use macos::*; +mod output_logic; +use output_logic::*; + #[cfg(target_os = "unknown")] mod unknown; #[cfg(target_os = "unknown")] @@ -185,51 +188,6 @@ pub struct Kanata { tcp_server_address: Option, } -// Functions to send keys except those that fall in the ignorable range. -// -// POTENTIAL PROBLEM - G-keys: -// Some keys are ignored because they are *probably* unused, -// or otherwise are probably in an unergonomic, far away key position, -// so if you're using kanata, you can now stop using those keys and -// do something better! -// -// I should probably let people turn this off if they really want to, -// but I don't like how that would require extra code. -// I'll defer to YAGNI and add docs, and let people report problems if -// they want a fix 🐝. -// -// The keys ignored are intentionally the upper numbers of KEY_MACROX. -// The Linux input-event-codes.h file mentions G1-G18 and S1-S30 -// as keys that might use these codes. -// -// Logitech still makes devices with G-keys -// but the S-keys are apparently from the -// "Microsoft SideWinder X6 Keyboard" -// which appears to no longer be in production. -// -// Thus based on my reading, 18 is the highest macro key -// that can be assumed to be used by devices still in production. -const KEY_IGNORE_MIN: u16 = 0x2a4; // KEY_MACRO21 -const KEY_IGNORE_MAX: u16 = 0x2ad; // KEY_MACRO30 -fn write_key(kb: &mut KbdOut, osc: OsCode, val: KeyValue) -> Result<(), std::io::Error> { - match u16::from(osc) { - KEY_IGNORE_MIN..=KEY_IGNORE_MAX => Ok(()), - _ => kb.write_key(osc, val), - } -} -fn press_key(kb: &mut KbdOut, osc: OsCode) -> Result<(), std::io::Error> { - match u16::from(osc) { - KEY_IGNORE_MIN..=KEY_IGNORE_MAX => Ok(()), - _ => kb.press_key(osc), - } -} -fn release_key(kb: &mut KbdOut, osc: OsCode) -> Result<(), std::io::Error> { - match u16::from(osc) { - KEY_IGNORE_MIN..=KEY_IGNORE_MAX => Ok(()), - _ => kb.release_key(osc), - } -} - #[derive(PartialEq, Clone, Copy)] pub enum Axis { Vertical, diff --git a/src/kanata/output_logic.rs b/src/kanata/output_logic.rs new file mode 100644 index 000000000..c22b62f24 --- /dev/null +++ b/src/kanata/output_logic.rs @@ -0,0 +1,93 @@ +use super::*; + +// Functions to send keys except those that fall in the ignorable range. +// And also have been repurposed to have additional logic to send mouse events, out of convenience. +// +// POTENTIAL PROBLEM - G-keys: +// Some keys are ignored because they are *probably* unused, +// or otherwise are probably in an unergonomic, far away key position, +// so if you're using kanata, you can now stop using those keys and +// do something better! +// +// I should probably let people turn this off if they really want to, +// but I don't like how that would require extra code. +// I'll defer to YAGNI and add docs, and let people report problems if +// they want a fix 🐝. +// +// The keys ignored are intentionally the upper numbers of KEY_MACROX. +// The Linux input-event-codes.h file mentions G1-G18 and S1-S30 +// as keys that might use these codes. +// +// Logitech still makes devices with G-keys +// but the S-keys are apparently from the +// "Microsoft SideWinder X6 Keyboard" +// which appears to no longer be in production. +// +// Thus based on my reading, 18 is the highest macro key +// that can be assumed to be used by devices still in production. +pub(super) const KEY_IGNORE_MIN: u16 = 0x2a4; // KEY_MACRO21 +pub(super) const KEY_IGNORE_MAX: u16 = 0x2ad; // KEY_MACRO30 +pub(super) fn write_key(kb: &mut KbdOut, osc: OsCode, val: KeyValue) -> Result<(), std::io::Error> { + match u16::from(osc) { + KEY_IGNORE_MIN..=KEY_IGNORE_MAX => Ok(()), + _ => kb.write_key(osc, val), + } +} +pub(super) fn press_key(kb: &mut KbdOut, osc: OsCode) -> Result<(), std::io::Error> { + use OsCode::*; + match u16::from(osc) { + KEY_IGNORE_MIN..=KEY_IGNORE_MAX => Ok(()), + _ => match osc { + BTN_LEFT | BTN_RIGHT | BTN_MIDDLE | BTN_SIDE | BTN_EXTRA => { + let btn = osc_to_btn(osc); + kb.click_btn(btn) + } + MouseWheelUp | MouseWheelDown | MouseWheelLeft | MouseWheelRight => { + let direction = osc_to_wheel_direction(osc); + kb.scroll(direction, HI_RES_SCROLL_UNITS_IN_LO_RES) + } + _ => kb.press_key(osc), + }, + } +} +pub(super) fn release_key(kb: &mut KbdOut, osc: OsCode) -> Result<(), std::io::Error> { + use OsCode::*; + match u16::from(osc) { + KEY_IGNORE_MIN..=KEY_IGNORE_MAX => Ok(()), + _ => match osc { + BTN_LEFT | BTN_RIGHT | BTN_MIDDLE | BTN_SIDE | BTN_EXTRA => { + let btn = osc_to_btn(osc); + kb.release_btn(btn) + } + MouseWheelUp | MouseWheelDown | MouseWheelLeft | MouseWheelRight => { + // no-op: these are handled as scroll events in the press but scroll has no notion + // of release. + Ok(()) + } + _ => kb.release_key(osc), + }, + } +} +fn osc_to_btn(osc: OsCode) -> Btn { + use Btn::*; + use OsCode::*; + match osc { + BTN_LEFT => Left, + BTN_RIGHT => Right, + BTN_MIDDLE => Mid, + BTN_EXTRA => Forward, + BTN_SIDE => Backward, + _ => unreachable!("called osc_to_btn with bad value {osc}"), + } +} +fn osc_to_wheel_direction(osc: OsCode) -> MWheelDirection { + use MWheelDirection::*; + use OsCode::*; + match osc { + MouseWheelUp => Up, + MouseWheelDown => Down, + MouseWheelLeft => Left, + MouseWheelRight => Right, + _ => unreachable!("called osc_to_wheel_direction with bad value {osc}"), + } +} diff --git a/src/oskbd/simulated.rs b/src/oskbd/simulated.rs index 42d9f7820..4913056f1 100644 --- a/src/oskbd/simulated.rs +++ b/src/oskbd/simulated.rs @@ -408,6 +408,7 @@ impl KbdOut { Ok(()) } pub fn scroll(&mut self, direction: MWheelDirection, distance: u16) -> Result<(), io::Error> { + self.log.scroll(direction, distance); self.outputs .push(format!("scroll:{direction:?},{distance:?}")); Ok(()) diff --git a/src/tests/sim_tests/block_keys_tests.rs b/src/tests/sim_tests/block_keys_tests.rs new file mode 100644 index 000000000..24f41bac4 --- /dev/null +++ b/src/tests/sim_tests/block_keys_tests.rs @@ -0,0 +1,35 @@ +use super::*; + +#[test] +fn block_does_not_block_buttons() { + let result = simulate( + "(defcfg process-unmapped-keys yes + block-unmapped-keys yes) + (defsrc) + (deflayer base)", + "d:mlft d:mrgt d:mmid d:mbck d:mfwd t:10 d:f1 + u:mlft u:mrgt u:mmid u:mbck u:mfwd t:10 u:f1", + ); + assert_eq!( + "out🖰:↓Left\nt:1ms\nout🖰:↓Right\nt:1ms\nout🖰:↓Mid\nt:1ms\nout🖰:↓Backward\n\ + t:1ms\nout🖰:↓Forward\nt:7ms\nout🖰:↑Left\nt:1ms\nout🖰:↑Right\nt:1ms\nout🖰:↑Mid\n\ + t:1ms\nout🖰:↑Backward\nt:1ms\nout🖰:↑Forward", + result + ); +} + +#[test] +fn block_does_not_block_wheel() { + let result = simulate( + "(defcfg process-unmapped-keys yes + block-unmapped-keys yes) + (defsrc) + (deflayer base)", + "d:mwu d:mwd d:mwl d:mwr t:10 d:f1 + u:mwu u:mwd u:mwl u:mwr t:10 u:f1", + ); + assert_eq!( + "scroll:Up,120\nt:1ms\nscroll:Down,120\nt:1ms\nscroll:Left,120\nt:1ms\nscroll:Right,120", + result + ); +} diff --git a/src/tests/sim_tests/mod.rs b/src/tests/sim_tests/mod.rs index 5c8242d4a..dd5f3ad4a 100644 --- a/src/tests/sim_tests/mod.rs +++ b/src/tests/sim_tests/mod.rs @@ -10,6 +10,7 @@ use kanata_state_machine::{ str_to_oscode, Kanata, }; +mod block_keys_tests; mod chord_sim_tests; mod layer_sim_tests; mod seq_sim_tests;