Skip to content

Commit

Permalink
feat: macro-cancel-on-press (#1345)
Browse files Browse the repository at this point in the history
  • Loading branch information
jtroo authored Nov 14, 2024
1 parent d932574 commit f4c9a73
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 13 deletions.
6 changes: 5 additions & 1 deletion cfg_samples/kanata.kbd
Original file line number Diff line number Diff line change
Expand Up @@ -603,12 +603,16 @@ If you need help, please feel welcome to ask in the GitHub discussions.
tbm (macro A-(tab 200 tab 200 tab) 200 S-A-(tab 200 tab 200 tab))
hpy (macro S-i spc a m spc S-(h a p p y) spc m y S-f r S-i e S-n d @🙃)

rls (macro-release-cancel 1 500 bspc S-1 500 bspc S-2)
rls (macro-release-cancel Digit1 500 bspc S-1 500 bspc S-2)
cop (macro-cancel-on-press Digit1 500 bspc S-1 500 bspc S-2)
rlpr (macro-release-cancel-and-cancel-on-press Digit1 500 bspc S-1 500 bspc S-2)

;; repeat variants will repeat while held, once ALL macros have ended,
;; including the held macro.
mr1 (macro-repeat mltp)
mr2 (macro-repeat-release-cancel mltp)
mr3 (macro-repeat-cancel-on-press mltp)
mr4 (macro-repeat-release-cancel-and-cancel-on-press mltp)

;; Kanata also supports dynamic macros. Dynamic macros can be nested, but
;; cannot recurse.
Expand Down
58 changes: 50 additions & 8 deletions docs/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1421,11 +1421,17 @@ needs the delay of `5` to work correctly.
)
----

There is a variant of the `+macro+` action that will cancel all active macros
upon releasing the key: `+macro-release-cancel+` or `+macro↑⤫+`. It is parsed identically to
the non-cancelling version. An example use case for this action is holding down
a key to get different outputs, similar to tap-dance but one can see which keys
are being outputted.
[[macro-release-cancel]]
==== macro-release-cancel

The `macro-release-cancel` variant of the `+macro+` action
will cancel all active macros
upon releasing the key.
Shorter unicode variant: `+macro↑⤫+`.
This variant is parsed identically to the non-cancelling version.
An example use case for this action is holding down
a key to get different outputs,
similar to tap-dance but one can see which keys are being outputted.

E.g. in the example below, when holding the key, first `1` is typed, then
replaced by `!` after 500ms, and finally that is replaced by `@` after another
Expand All @@ -1443,7 +1449,43 @@ and the rest of the macro does not run.
)
----

There are further variants of the two `macro` actions which repeat while held.
[[macro-cancel-on-press]]
==== macro-cancel-on-press

The `macro-cancel-on-press` variant of the `macro action`
enables a cancellation trigger for all active macros including itself,
which is activated when a physical press of any other key happens.
The trigger is enabled while the macro is in progress.

[source]
----
(defalias
1 1
1!@ (macro-cancel-on-press @1 500 bspc S-1 500 bspc S-2)
)
----

[[macro-release-cancel-and-cancel-on-press]]
==== macro-release-cancel-and-cancel-on-press

The `macro-release-cancel-and-cancel-on-press` variant
combines the cancel behaviours
of both the release-cancel and cancel-on-press.

[source]
----
(defalias
1 1
1!@ (macro-release-cancel-and-cancel-on-press @1 500 bspc S-1 500 bspc S-2)
)
----


[[macro-repeat]]
==== macro-repeat

There are further `macro-repeat` variants of the three `macro` actions described previously.
These variants repeat while held.
The repeat will only occur once all macros have completed,
including the held macro key.
If multiple repeating macros are being held simulaneously,
Expand All @@ -1453,9 +1495,9 @@ only the most recently pressed macro will be repeated.
----
(defalias
mr1 (macro-repeat mltp)
mr2 (macro⟳ mltp)
mr2 (macro-repeat-release-cancel mltp)
mr2 (macro⟳↑⤫ mltp)
mr3 (macro-repeat-cancel-on-press mltp)
mr4 (macro-repeat-release-cancel-and-cancel-on-press mltp)
)
----

Expand Down
10 changes: 10 additions & 0 deletions parser/src/cfg/list_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ pub const MACRO_RELEASE_CANCEL: &str = "macro-release-cancel";
pub const MACRO_RELEASE_CANCEL_A: &str = "macro↑⤫";
pub const MACRO_REPEAT_RELEASE_CANCEL: &str = "macro-repeat-release-cancel";
pub const MACRO_REPEAT_RELEASE_CANCEL_A: &str = "macro⟳↑⤫";
pub const MACRO_CANCEL_ON_NEXT_PRESS: &str = "macro-cancel-on-press";
pub const MACRO_REPEAT_CANCEL_ON_NEXT_PRESS: &str = "macro-repeat-cancel-on-press";
pub const MACRO_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE: &str =
"macro-release-cancel-and-cancel-on-press";
pub const MACRO_REPEAT_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE: &str =
"macro-repeat-release-cancel-and-cancel-on-press";
pub const UNICODE: &str = "unicode";
pub const SYM: &str = "🔣";
pub const ONE_SHOT: &str = "one-shot";
Expand Down Expand Up @@ -221,6 +227,10 @@ pub fn is_list_action(ac: &str) -> bool {
ON_RELEASE,
ON_RELEASE_A,
ON_IDLE,
MACRO_CANCEL_ON_NEXT_PRESS,
MACRO_REPEAT_CANCEL_ON_NEXT_PRESS,
MACRO_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE,
MACRO_REPEAT_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE,
];
LIST_ACTIONS.contains(&ac)
}
62 changes: 62 additions & 0 deletions parser/src/cfg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1744,6 +1744,18 @@ fn parse_action_list(ac: &[SExpr], s: &ParserState) -> Result<&'static KanataAct
MACRO_REPEAT_RELEASE_CANCEL | MACRO_REPEAT_RELEASE_CANCEL_A => {
parse_macro_release_cancel(&ac[1..], s, RepeatMacro::Yes)
}
MACRO_CANCEL_ON_NEXT_PRESS => {
parse_macro_cancel_on_next_press(&ac[1..], s, RepeatMacro::No)
}
MACRO_REPEAT_CANCEL_ON_NEXT_PRESS => {
parse_macro_cancel_on_next_press(&ac[1..], s, RepeatMacro::Yes)
}
MACRO_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE => {
parse_macro_cancel_on_next_press_cancel_on_release(&ac[1..], s, RepeatMacro::No)
}
MACRO_REPEAT_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE => {
parse_macro_cancel_on_next_press_cancel_on_release(&ac[1..], s, RepeatMacro::Yes)
}
UNICODE | SYM => parse_unicode(&ac[1..], s),
ONE_SHOT | ONE_SHOT_PRESS | ONE_SHOT_PRESS_A => {
parse_one_shot(&ac[1..], s, OneShotEndConfig::EndOnFirstPress)
Expand Down Expand Up @@ -2130,6 +2142,56 @@ fn parse_macro_release_cancel(
])))))
}

fn parse_macro_cancel_on_next_press(
ac_params: &[SExpr],
s: &ParserState,
repeat: RepeatMacro,
) -> Result<&'static KanataAction> {
let macro_action = parse_macro(ac_params, s, repeat)?;
let macro_duration = match macro_action {
Action::RepeatableSequence { events } | Action::Sequence { events } => {
macro_sequence_event_total_duration(events)
}
_ => unreachable!("parse_macro should return sequence action"),
};
Ok(s.a.sref(Action::MultipleActions(s.a.sref(s.a.sref_vec(vec![
*macro_action,
Action::Custom(
s.a.sref(s.a.sref_slice(CustomAction::CancelMacroOnNextPress(macro_duration))),
),
])))))
}

fn parse_macro_cancel_on_next_press_cancel_on_release(
ac_params: &[SExpr],
s: &ParserState,
repeat: RepeatMacro,
) -> Result<&'static KanataAction> {
let macro_action = parse_macro(ac_params, s, repeat)?;
let macro_duration = match macro_action {
Action::RepeatableSequence { events } | Action::Sequence { events } => {
macro_sequence_event_total_duration(events)
}
_ => unreachable!("parse_macro should return sequence action"),
};
Ok(s.a.sref(Action::MultipleActions(s.a.sref(s.a.sref_vec(vec![
*macro_action,
Action::Custom(s.a.sref(s.a.sref_vec(vec![
&CustomAction::CancelMacroOnRelease,
s.a.sref(CustomAction::CancelMacroOnNextPress(macro_duration)),
]))),
])))))
}

fn macro_sequence_event_total_duration<T>(events: &[SequenceEvent<T>]) -> u32 {
events.iter().fold(0, |duration, event| {
duration.saturating_add(match event {
SequenceEvent::Delay { duration: d } => *d,
_ => 1,
})
})
}

#[derive(PartialEq)]
enum MacroNumberParseMode {
Delay,
Expand Down
1 change: 1 addition & 0 deletions parser/src/custom_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub enum CustomAction {
LiveReloadFile(String),
Repeat,
CancelMacroOnRelease,
CancelMacroOnNextPress(u32),
DynamicMacroRecord(u16),
DynamicMacroRecordStop(u16),
DynamicMacroPlay(u16),
Expand Down
32 changes: 28 additions & 4 deletions src/kanata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ pub struct Kanata {
/// Various GUI-related options.
pub gui_opts: CfgOptionsGui,
pub allow_hardware_repeat: bool,
/// When > 0, it means macros should be cancelled on the next press.
/// Upon cancelling this should be set to 0.
pub macro_on_press_cancel_duration: u32,
}

#[derive(PartialEq, Clone, Copy)]
Expand Down Expand Up @@ -422,6 +425,7 @@ impl Kanata {
#[cfg(all(target_os = "windows", feature = "gui"))]
gui_opts: cfg.options.gui_opts,
allow_hardware_repeat: cfg.options.allow_hardware_repeat,
macro_on_press_cancel_duration: 0,
})
}

Expand Down Expand Up @@ -551,6 +555,7 @@ impl Kanata {
#[cfg(all(target_os = "windows", feature = "gui"))]
gui_opts: cfg.options.gui_opts,
allow_hardware_repeat: cfg.options.allow_hardware_repeat,
macro_on_press_cancel_duration: 0,
})
}

Expand Down Expand Up @@ -641,6 +646,7 @@ impl Kanata {
let cur_layer = self.layout.bm().current_layer();
self.prev_layer = cur_layer;
self.print_layer(cur_layer);
self.macro_on_press_cancel_duration = 0;

#[cfg(not(target_os = "linux"))]
{
Expand Down Expand Up @@ -676,6 +682,15 @@ impl Kanata {
) {
self.dynamic_macros.insert(macro_id, recorded_macro);
}
if self.macro_on_press_cancel_duration > 0 {
log::debug!("cancelling all macros: other press");
self.macro_on_press_cancel_duration = 0;
let layout = self.layout.bm();
layout.active_sequences.clear();
layout.states.retain(|s| {
!matches!(s, State::FakeKey { .. } | State::RepeatingSequence { .. })
});
}
Event::Press(0, evc)
}
KeyValue::Release => {
Expand Down Expand Up @@ -787,6 +802,7 @@ impl Kanata {
self.handle_move_mouse()?;
self.tick_sequence_state()?;
self.tick_idle_timeout();
self.macro_on_press_cancel_duration = self.macro_on_press_cancel_duration.saturating_sub(1);
tick_record_state(&mut self.dynamic_macro_record_state);
zippy_tick(self.caps_word.is_some());
self.prev_keys.clear();
Expand Down Expand Up @@ -1517,6 +1533,9 @@ impl Kanata {
&self.dynamic_macros,
);
}
CustomAction::CancelMacroOnNextPress(duration) => {
self.macro_on_press_cancel_duration = *duration;
}
CustomAction::SendArbitraryCode(code) => {
self.kbd_out.write_code(*code as u32, KeyValue::Press)?;
}
Expand Down Expand Up @@ -1629,11 +1648,15 @@ impl Kanata {
pbtn
}
CustomAction::CancelMacroOnRelease => {
log::debug!("cancelling all macros");
log::debug!("cancelling all macros: releasable macro");
layout.active_sequences.clear();
layout
.states
.retain(|s| !matches!(s, State::FakeKey { .. }));
self.macro_on_press_cancel_duration = 0;
layout.states.retain(|s| {
!matches!(
s,
State::FakeKey { .. } | State::RepeatingSequence { .. }
)
});
pbtn
}
CustomAction::SendArbitraryCode(code) => {
Expand Down Expand Up @@ -2045,6 +2068,7 @@ impl Kanata {
&& self.scroll_state.is_none()
&& self.hscroll_state.is_none()
&& self.move_mouse_state_vertical.is_none()
&& self.macro_on_press_cancel_duration == 0
&& self.move_mouse_state_horizontal.is_none()
&& self.dynamic_macro_replay_state.is_none()
&& self.caps_word.is_none()
Expand Down
Loading

0 comments on commit f4c9a73

Please sign in to comment.