Skip to content

Commit

Permalink
feat(override): add defcfg option to eagerly release override output (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
jtroo committed Jun 18, 2024
1 parent 56676e5 commit edbe161
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 10 deletions.
8 changes: 8 additions & 0 deletions cfg_samples/kanata.kbd
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,14 @@ If you need help, please feel welcome to ask in the GitHub discussions.
;; changes will be logged.
;;
;; log-layer-changes no

;; This configuration will press and then immediately release the non-modifier key
;; as soon as the override activates, meaning you are unlikely as a human to ever
;; release modifiers first, which can result in unintended behaviour.
;;
;; The downside of this configuration is that the non-modifier key
;; does not remain held which is important to consider for your use cases.
override-release-on-activation yes
)

;; deflocalkeys-* enables you to define and use key names that match your locale
Expand Down
36 changes: 36 additions & 0 deletions docs/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2188,6 +2188,42 @@ The default (and minimum) value is `5` and the unit is milliseconds.
)
----

[[override-release-on-activation]]
=== override-release-on-activation
<<table-of-contents,Back to ToC>>

This configuration item changes activation behaviour from `defoverrides`.

Take this example override:

[source]
----
(defoverrides (lsft a) (lsft 9))
----

The default behaviour is that if `lsft` is released **before** releasing `a`,
kanata's behaviour would be to send `a`.

A future improvement could be to make the `9` continue to be the key held,
but that is not implemented today.

The workaround in case the above behaviour negatively impacts your workflow
is to enable this configuration.
This configuration will press and then immediately release the `9` output
as soon as the override activates, meaning you are unlikely as a human to ever
release `lsft` first.

The effect of this configuration is that the `9` key cannot remain held
when activated by the override which is important to consider for your use cases.

.Example:
[source]
----
(defcfg
override-release-on-activation yes
)
----

[[linux-only-linux-dev]]
=== Linux only: linux-dev
<<table-of-contents,Back to ToC>>
Expand Down
5 changes: 4 additions & 1 deletion keyberon/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,10 @@ impl<'a, T: 'a> State<'a, T> {
}
pub fn release_state(&self, s: ReleasableState) -> Option<Self> {
match (*self, s) {
(NormalKey { keycode: k1, .. }, ReleasableState::KeyCode(k2)) => {
(
NormalKey { keycode: k1, .. } | FakeKey { keycode: k1 },
ReleasableState::KeyCode(k2),
) => {
if k1 == k2 {
None
} else {
Expand Down
5 changes: 5 additions & 0 deletions parser/src/cfg/defcfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub struct CfgOptions {
pub delegate_to_first_layer: bool,
pub movemouse_inherit_accel_state: bool,
pub movemouse_smooth_diagonals: bool,
pub override_release_on_activation: bool,
pub dynamic_macro_max_presses: u16,
pub dynamic_macro_replay_delay_behaviour: ReplayDelayBehaviour,
pub concurrent_tap_hold: bool,
Expand Down Expand Up @@ -115,6 +116,7 @@ impl Default for CfgOptions {
delegate_to_first_layer: false,
movemouse_inherit_accel_state: false,
movemouse_smooth_diagonals: false,
override_release_on_activation: false,
dynamic_macro_max_presses: 128,
dynamic_macro_replay_delay_behaviour: ReplayDelayBehaviour::Recorded,
concurrent_tap_hold: false,
Expand Down Expand Up @@ -587,6 +589,9 @@ pub fn parse_defcfg(expr: &[SExpr]) -> Result<CfgOptions> {
"movemouse-inherit-accel-state" => {
cfg.movemouse_inherit_accel_state = parse_defcfg_val_bool(val, label)?
}
"override-release-on-activation" => {
cfg.override_release_on_activation = parse_defcfg_val_bool(val, label)?
}
"concurrent-tap-hold" => {
cfg.concurrent_tap_hold = parse_defcfg_val_bool(val, label)?
}
Expand Down
6 changes: 5 additions & 1 deletion parser/src/cfg/key_override.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ impl OverrideStates {
fn add_overrides(&self, oscs: &mut Vec<KeyCode>) {
oscs.extend(self.oscs_to_add.iter().copied().map(KeyCode::from));
}

pub fn removed_oscs(&self) -> impl Iterator<Item = OsCode> + '_ {
self.oscs_to_remove.iter().copied()
}
}

/// A collection of global key overrides.
Expand Down Expand Up @@ -84,12 +88,12 @@ impl Overrides {
if self.is_empty() {
return;
}
states.cleanup();
for kc in kcs.iter().copied() {
states.update(kc.into(), self);
}
kcs.retain(|kc| !states.is_key_overridden((*kc).into()));
states.add_overrides(kcs);
states.cleanup();
}

pub fn output_non_mods_for_input_non_mod(&self, in_osc: OsCode) -> Vec<OsCode> {
Expand Down
1 change: 1 addition & 0 deletions parser/src/cfg/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,7 @@ fn parse_all_defcfg() {
delegate-to-first-layer yes
movemouse-inherit-accel-state yes
movemouse-smooth-diagonals yes
override-release-on-activation yes
dynamic-macro-max-presses 1000
concurrent-tap-hold yes
rapid-event-delay 5
Expand Down
14 changes: 14 additions & 0 deletions parser/src/keys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ impl OsCode {
#[cfg(target_os = "macos")]
return OsCode::from_u16_macos(code);
}

pub fn is_modifier(self) -> bool {
matches!(
self,
OsCode::KEY_LEFTSHIFT
| OsCode::KEY_RIGHTSHIFT
| OsCode::KEY_LEFTMETA
| OsCode::KEY_RIGHTMETA
| OsCode::KEY_LEFTCTRL
| OsCode::KEY_RIGHTCTRL
| OsCode::KEY_LEFTALT
| OsCode::KEY_RIGHTALT
)
}
}

static CUSTOM_STRS_TO_OSCODES: Lazy<Mutex<HashMap<String, OsCode>>> = Lazy::new(|| {
Expand Down
16 changes: 16 additions & 0 deletions src/kanata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::sync::mpsc::{Receiver, SyncSender as Sender, TryRecvError};
#[cfg(feature = "passthru_ahk")]
use std::sync::mpsc::Sender as ASender;

use kanata_keyberon::action::ReleasableState;
use kanata_keyberon::key_code::*;
use kanata_keyberon::layout::{CustomEvent, Event, Layout, State};

Expand Down Expand Up @@ -186,6 +187,7 @@ pub struct Kanata {
/// gets stored in this buffer and if the next movemouse action is opposite axis
/// than the one stored in the buffer, both events are outputted at the same time.
movemouse_buffer: Option<(Axis, CalculatedMouseMove)>,
override_release_on_activation: bool,
/// Configured maximum for dynamic macro recording, to protect users from themselves if they
/// have accidentally left it on.
dynamic_macro_max_presses: u16,
Expand Down Expand Up @@ -355,6 +357,7 @@ impl Kanata {
.unwrap_or(cfg.options.log_layer_changes),
caps_word: None,
movemouse_smooth_diagonals: cfg.options.movemouse_smooth_diagonals,
override_release_on_activation: cfg.options.override_release_on_activation,
movemouse_inherit_accel_state: cfg.options.movemouse_inherit_accel_state,
dynamic_macro_max_presses: cfg.options.dynamic_macro_max_presses,
dynamic_macro_replay_behaviour: ReplayBehaviour {
Expand Down Expand Up @@ -454,6 +457,7 @@ impl Kanata {
.unwrap_or(cfg.options.log_layer_changes),
caps_word: None,
movemouse_smooth_diagonals: cfg.options.movemouse_smooth_diagonals,
override_release_on_activation: cfg.options.override_release_on_activation,
movemouse_inherit_accel_state: cfg.options.movemouse_inherit_accel_state,
dynamic_macro_max_presses: cfg.options.dynamic_macro_max_presses,
dynamic_macro_replay_behaviour: ReplayBehaviour {
Expand Down Expand Up @@ -511,6 +515,7 @@ impl Kanata {
self.log_layer_changes =
get_forced_log_layer_changes().unwrap_or(cfg.options.log_layer_changes);
self.movemouse_smooth_diagonals = cfg.options.movemouse_smooth_diagonals;
self.override_release_on_activation = cfg.options.override_release_on_activation;
self.movemouse_inherit_accel_state = cfg.options.movemouse_inherit_accel_state;
self.dynamic_macro_max_presses = cfg.options.dynamic_macro_max_presses;
self.dynamic_macro_replay_behaviour = ReplayBehaviour {
Expand Down Expand Up @@ -935,6 +940,17 @@ impl Kanata {

self.overrides
.override_keys(cur_keys, &mut self.override_states);
if self.override_release_on_activation {
for removed in self.override_states.removed_oscs() {
if !removed.is_modifier() {
layout.states.retain(|s| {
s.release_state(ReleasableState::KeyCode(removed.into()))
.is_some()
});
}
}
}

if let Some(caps_word) = &mut self.caps_word {
if caps_word.maybe_add_lsft(cur_keys) == CapsWordNextState::End {
self.caps_word = None;
Expand Down
9 changes: 1 addition & 8 deletions src/kanata/sequences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,14 +317,7 @@ pub(super) fn do_successful_sequence_termination(
// desired to fix this, a shorter list of keys would
// probably be the list of keys that **do** output
// characters than those that don't.
OsCode::KEY_LEFTSHIFT
| OsCode::KEY_RIGHTSHIFT
| OsCode::KEY_LEFTMETA
| OsCode::KEY_RIGHTMETA
| OsCode::KEY_LEFTCTRL
| OsCode::KEY_RIGHTCTRL
| OsCode::KEY_LEFTALT
| OsCode::KEY_RIGHTALT => continue,
osc if osc.is_modifier() => continue,
osc if matches!(u16::from(osc), KEY_IGNORE_MIN..=KEY_IGNORE_MAX) => continue,
_ => {
kbd_out.press_key(OsCode::KEY_BACKSPACE)?;
Expand Down
33 changes: 33 additions & 0 deletions src/tests/sim_tests/override_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,36 @@ fn override_with_unmod() {
result
);
}

#[test]
fn override_release_mod_change_key() {
let result = simulate(
"
(defsrc)
(deflayer base)
(defoverrides (lsft a) (lsft 9))
",
"d:lsft t:10 d:a t:10 u:lsft t:10 u:a t:10",
)
.to_ascii()
.no_time();
assert_eq!("dn:LShift dn:Kb9 up:LShift up:Kb9 dn:A up:A", result);
}

#[test]
fn override_eagerly_releases() {
let result = simulate(
"
(defcfg override-release-on-activation yes)
(defsrc)
(deflayer base)
(defoverrides (lsft a) (lsft 9))
",
"d:lsft t:10 d:a t:10 u:lsft t:10 u:a t:10",
)
.to_ascii();
assert_eq!(
"dn:LShift t:10ms dn:Kb9 t:1ms up:Kb9 t:9ms up:LShift",
result
);
}

0 comments on commit edbe161

Please sign in to comment.