Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implemented an alt-repeat key #1392

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cfg_samples/kanata.kbd
Original file line number Diff line number Diff line change
Expand Up @@ -931,12 +931,17 @@ 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` 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 @{ @} [ ] _ _ +
@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))
)


Expand Down
25 changes: 25 additions & 0 deletions docs/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down Expand Up @@ -1171,6 +1175,23 @@ 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
)
----

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: <<key-history-and-key-timing>>

[[release-a-key-or-layer]]
=== Release a key or layer

Expand Down Expand Up @@ -2785,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 <<repeat-key>> section.

The `key-timing` compares how long ago recent key typing events occurred.
It accepts, in order,

Expand Down
4 changes: 4 additions & 0 deletions parser/src/cfg/list_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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] = &[
Expand Down Expand Up @@ -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)
}
28 changes: 28 additions & 0 deletions parser/src/cfg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -3892,3 +3893,30 @@ 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.first().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)
}

3 changes: 3 additions & 0 deletions parser/src/custom_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ pub enum CustomAction {
LiveReloadNum(u16),
LiveReloadFile(String),
Repeat,
AltRepeat {
substitutions: Vec<(KeyCode, KeyCode)>,
},
CancelMacroOnRelease,
CancelMacroOnNextPress(u32),
DynamicMacroRecord(u16),
Expand Down
29 changes: 29 additions & 0 deletions src/kanata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down