From 31f58a47bdf198e1d9145ab87b613f23c4c154ea Mon Sep 17 00:00:00 2001 From: Nuclear Squid Date: Thu, 28 Nov 2024 15:06:09 +0100 Subject: [PATCH 1/4] feat: implemented an alt-repeat key. --- parser/src/cfg/list_actions.rs | 4 ++++ parser/src/cfg/mod.rs | 24 ++++++++++++++++++++++++ parser/src/custom_action.rs | 3 +++ src/kanata/mod.rs | 29 +++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/parser/src/cfg/list_actions.rs b/parser/src/cfg/list_actions.rs index 05b34ba12..973237fa1 100644 --- a/parser/src/cfg/list_actions.rs +++ b/parser/src/cfg/list_actions.rs @@ -118,6 +118,8 @@ pub const ON_RELEASE: &str = "on-release"; pub const ON_RELEASE_A: &str = "on↑"; pub const ON_IDLE: &str = "on-idle"; pub const HOLD_FOR_DURATION: &str = "hold-for-duration"; +pub const ALT_REPEAT: &str = "alt-repeat"; +pub const ALT_REPEAT_A: &str = "arpt"; pub fn is_list_action(ac: &str) -> bool { const LIST_ACTIONS: &[&str] = &[ @@ -235,6 +237,8 @@ pub fn is_list_action(ac: &str) -> bool { MACRO_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE, MACRO_REPEAT_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE, ONE_SHOT_PAUSE_PROCESSING, + ALT_REPEAT, + ALT_REPEAT_A, ]; LIST_ACTIONS.contains(&ac) } diff --git a/parser/src/cfg/mod.rs b/parser/src/cfg/mod.rs index e23283d43..537737287 100755 --- a/parser/src/cfg/mod.rs +++ b/parser/src/cfg/mod.rs @@ -1805,6 +1805,7 @@ fn parse_action_list(ac: &[SExpr], s: &ParserState) -> Result<&'static KanataAct ON_RELEASE | ON_RELEASE_A => parse_on_release(&ac[1..], s), ON_IDLE => parse_on_idle(&ac[1..], s), HOLD_FOR_DURATION => parse_hold_for_duration(&ac[1..], s), + ALT_REPEAT | ALT_REPEAT_A => parse_alt_repeat(&ac[1..], s), MWHEEL_UP | MWHEEL_UP_A => parse_mwheel(&ac[1..], MWheelDirection::Up, s), MWHEEL_DOWN | MWHEEL_DOWN_A => parse_mwheel(&ac[1..], MWheelDirection::Down, s), MWHEEL_LEFT | MWHEEL_LEFT_A => parse_mwheel(&ac[1..], MWheelDirection::Left, s), @@ -3892,3 +3893,26 @@ fn parse_unmod( _ => panic!("Unknown unmod type {unmod_type}"), } } + +fn parse_alt_repeat(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> { + let Some(list) = ac_params.get(0).and_then(|s| s.list(None)) else { + bail!("alt-repeat needs a list of previous-next keycodes") + }; + if list.len() % 2 != 0 { + bail!( + "alt-repeat needs a list of previous-next keycodes, found {} items", + ac_params.len() + ) + } + let mut base_key_set = HashSet::default(); + let key_list = parse_key_list(&ac_params[0], s, "alt-repeat parse key list")?; + for key in key_list.iter().step_by(2) { + if !base_key_set.insert(key) { + bail!("duplicated base keycode in alt-repeat: {}", key) + } + } + use itertools::Itertools; + let substitutions = key_list.iter().tuples().map(|(a, b)| (a.into(), b.into())).collect(); + custom(CustomAction::AltRepeat { substitutions }, &s.a) +} + diff --git a/parser/src/custom_action.rs b/parser/src/custom_action.rs index 600a36b41..02d8ccc51 100644 --- a/parser/src/custom_action.rs +++ b/parser/src/custom_action.rs @@ -64,6 +64,9 @@ pub enum CustomAction { LiveReloadNum(u16), LiveReloadFile(String), Repeat, + AltRepeat { + substitutions: Vec<(KeyCode, KeyCode)>, + }, CancelMacroOnRelease, CancelMacroOnNextPress(u32), DynamicMacroRecord(u16), diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index ff0a2bc59..9872c20f0 100755 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -1533,6 +1533,35 @@ impl Kanata { self.kbd_out.release_key(OsCode::KEY_LEFTSHIFT)?; } } + CustomAction::AltRepeat { substitutions } => { + let base_keycode = self.last_pressed_key; + let next_keycode = match substitutions.iter().find(|(x, _)| base_keycode == *x) { + Some(&(_, new_keycode)) => new_keycode, + None => base_keycode, + }; + let base_osc: OsCode = base_keycode.into(); + let next_osc: OsCode = next_keycode.into(); + log::debug!("alt-repeating a keypress {base_osc:?} -> {next_osc:?}"); + let mut do_caps_word = false; + if !cur_keys.contains(&KeyCode::LShift) { + if let Some(ref mut cw) = self.caps_word { + cur_keys.push(next_keycode); + let prev_len = cur_keys.len(); + cw.maybe_add_lsft(cur_keys); + if cur_keys.len() > prev_len { + do_caps_word = true; + press_key(&mut self.kbd_out, OsCode::KEY_LEFTSHIFT)?; + } + } + } + // Release key in case the most recently pressed key is still pressed. + release_key(&mut self.kbd_out, base_osc)?; + press_key(&mut self.kbd_out, next_osc)?; + release_key(&mut self.kbd_out, next_osc)?; + if do_caps_word { + self.kbd_out.release_key(OsCode::KEY_LEFTSHIFT)?; + } + }, CustomAction::DynamicMacroRecord(macro_id) => { if let Some((macro_id, prev_recorded_macro)) = begin_record_macro(*macro_id, &mut self.dynamic_macro_record_state) From 9ae67e620fe0db8031350a2ce330ff882520ba36 Mon Sep 17 00:00:00 2001 From: Nuclear Squid Date: Fri, 29 Nov 2024 13:41:18 +0100 Subject: [PATCH 2/4] Added some docs. --- cfg_samples/kanata.kbd | 6 +++++- docs/config.adoc | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/cfg_samples/kanata.kbd b/cfg_samples/kanata.kbd index a96a533b0..1fb4ecef5 100644 --- a/cfg_samples/kanata.kbd +++ b/cfg_samples/kanata.kbd @@ -931,12 +931,16 @@ If you need help, please feel welcome to ask in the GitHub discussions. ;; output a chord like `ctrl+c` if the previous key pressed was `C-c` - it ;; will only output `c`. There is a variant `rpt-any` which will repeat the ;; previous action and would work for that use case. +;; +;; The `arpt` action functions the same as `rpt` takes a list of substitutions +;; as argument. Here, the keys most keys are repeated upon pressing this key, +;; except `d` and `u`, who will send `e` and `n` respectively. (deflayer misc _ _ _ _ _ _ _ _ _ @é @è _ ì #|random custom key for testing|# _ _ _ @ab1 _ _ _ ins @{ @} [ ] _ _ + @cw _ _ _ C-u _ del bspc esc ret _ _ _ @cwc C-z C-x C-c C-v _ _ _ @td @os1 @os2 @os3 - rpt rpt-any _ _ _ _ _ + rpt rpt-any _ _ _ _ (arpt (d e u n)) ) diff --git a/docs/config.adoc b/docs/config.adoc index 203bd19e8..06b303a70 100644 --- a/docs/config.adoc +++ b/docs/config.adoc @@ -1139,6 +1139,10 @@ The output chord prefix strings are: | `rpt-any` | String action that outputs the most-recently outputted action. + +| `arpt` +| String action that outputs the single most-recently typed key unless a +aubstitution is specified. |=== **Description** @@ -1171,6 +1175,19 @@ and would output `ctrl+c` in the example case. ) ---- +the `arpt` (or `alt-repeat`) action functions the same way as `rpt` but takes a +list of keycode pairs as argument. If the last keycode correspond with the +first keycode in a pair, then the associated keycode will be sent instead. + +In the example below, `s` and `f` are repeated as usual, but pressing `arpt` +after `a` or `d` will send `q` and `e` respectively + +---- +(deflayer has-alt-repeat + (arpt (a q d e)) a s d f +) +---- + [[release-a-key-or-layer]] === Release a key or layer From 3e434a129d438e1649f79e12b34d82a285a05d34 Mon Sep 17 00:00:00 2001 From: Nuclear Squid Date: Sun, 1 Dec 2024 12:17:53 +0100 Subject: [PATCH 3/4] Fixed problems in PR. --- cfg_samples/kanata.kbd | 7 ++++--- docs/config.adoc | 8 ++++++++ parser/src/cfg/mod.rs | 8 ++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/cfg_samples/kanata.kbd b/cfg_samples/kanata.kbd index 1fb4ecef5..33ad80444 100644 --- a/cfg_samples/kanata.kbd +++ b/cfg_samples/kanata.kbd @@ -932,9 +932,10 @@ If you need help, please feel welcome to ask in the GitHub discussions. ;; will only output `c`. There is a variant `rpt-any` which will repeat the ;; previous action and would work for that use case. ;; -;; The `arpt` action functions the same as `rpt` takes a list of substitutions -;; as argument. Here, the keys most keys are repeated upon pressing this key, -;; except `d` and `u`, who will send `e` and `n` respectively. +;; The `arpt` action functions the same as `rpt` but takes a list of +;; substitutions as argument. Here, the keys most keys are repeated upon +;; pressing this key, ;; except `d` and `u`, who will send `e` and `n` +;; respectively. (deflayer misc _ _ _ _ _ _ _ _ _ @é @è _ ì #|random custom key for testing|# _ _ _ @ab1 _ _ _ ins @{ @} [ ] _ _ + diff --git a/docs/config.adoc b/docs/config.adoc index 06b303a70..eb7f44fbd 100644 --- a/docs/config.adoc +++ b/docs/config.adoc @@ -1188,6 +1188,10 @@ after `a` or `d` will send `q` and `e` respectively ) ---- +NOTE: Currently, `arpt` only allows for simple keycode to keycode +substitutions. If you need a more complex substitution, you could use a +`switch` using the `key-history` syntax: <> + [[release-a-key-or-layer]] === Release a key or layer @@ -2802,6 +2806,10 @@ and 8 is 8th most recent key pressed. ) ---- +NOTE: If the default action for this type of `switch` is a `repeat` key, then +you should consider using an `alt-repeat` key, as the syntax is a lot more +compact and simpler. For more information, check the <> section. + The `key-timing` compares how long ago recent key typing events occurred. It accepts, in order, diff --git a/parser/src/cfg/mod.rs b/parser/src/cfg/mod.rs index 537737287..af8a9c86a 100755 --- a/parser/src/cfg/mod.rs +++ b/parser/src/cfg/mod.rs @@ -3895,7 +3895,7 @@ fn parse_unmod( } fn parse_alt_repeat(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> { - let Some(list) = ac_params.get(0).and_then(|s| s.list(None)) else { + let Some(list) = ac_params.first().and_then(|s| s.list(None)) else { bail!("alt-repeat needs a list of previous-next keycodes") }; if list.len() % 2 != 0 { @@ -3912,7 +3912,11 @@ fn parse_alt_repeat(ac_params: &[SExpr], s: &ParserState) -> Result<&'static Kan } } use itertools::Itertools; - let substitutions = key_list.iter().tuples().map(|(a, b)| (a.into(), b.into())).collect(); + let substitutions = key_list + .iter() + .tuples() + .map(|(a, b)| (a.into(), b.into())) + .collect(); custom(CustomAction::AltRepeat { substitutions }, &s.a) } From 9cb9b753deefd02a0d92d627aa1c4cf763fad3c8 Mon Sep 17 00:00:00 2001 From: Nuclear Squid Date: Sun, 1 Dec 2024 12:24:14 +0100 Subject: [PATCH 4/4] Oops --- docs/config.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.adoc b/docs/config.adoc index eb7f44fbd..657e32c9d 100644 --- a/docs/config.adoc +++ b/docs/config.adoc @@ -2808,7 +2808,7 @@ and 8 is 8th most recent key pressed. NOTE: If the default action for this type of `switch` is a `repeat` key, then you should consider using an `alt-repeat` key, as the syntax is a lot more -compact and simpler. For more information, check the <> section. +compact and simpler. For more information, check the <> section. The `key-timing` compares how long ago recent key typing events occurred. It accepts, in order,