From a685618480bf1de529db58bb188b7c32d8dddd12 Mon Sep 17 00:00:00 2001 From: xs5871 Date: Sat, 29 Jan 2022 21:11:57 +0000 Subject: [PATCH] implement hold-tap interrupt on other key tap (i.e. release) --- kmk/key_validators.py | 18 ++++++++++++++---- kmk/modules/holdtap.py | 42 ++++++++++++++++++++++++++++++++++-------- kmk/types.py | 25 +++++++++++++++---------- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/kmk/key_validators.py b/kmk/key_validators.py index e819275..e3faa28 100644 --- a/kmk/key_validators.py +++ b/kmk/key_validators.py @@ -11,7 +11,7 @@ def key_seq_sleep_validator(ms): return KeySeqSleepMeta(ms) -def layer_key_validator(layer, kc=None): +def layer_key_validator(layer, kc=None, tap_time=None): ''' Validates the syntax (but not semantics) of a layer key call. We won't have access to the keymap here, so we can't verify much of anything useful @@ -19,14 +19,24 @@ def layer_key_validator(layer, kc=None): existing is mostly that Python will catch extraneous args/kwargs and error out. ''' - return LayerKeyMeta(layer=layer, kc=kc) + return LayerKeyMeta( + layer=layer, kc=kc, prefer_hold=False, tap_interrupted=False, tap_time=tap_time + ) -def mod_tap_validator(kc, mods=None, prefer_hold=True, tap_time=None): +def mod_tap_validator( + kc, mods=None, prefer_hold=True, tap_interrupted=False, tap_time=None +): ''' Validates that mod tap keys are correctly used ''' - return ModTapKeyMeta(kc=kc, mods=mods, prefer_hold=prefer_hold, tap_time=tap_time) + return ModTapKeyMeta( + kc=kc, + mods=mods, + prefer_hold=prefer_hold, + tap_interrupted=tap_interrupted, + tap_time=tap_time, + ) def tap_dance_key_validator(*codes): diff --git a/kmk/modules/holdtap.py b/kmk/modules/holdtap.py index a1aa152..d2a7a6b 100644 --- a/kmk/modules/holdtap.py +++ b/kmk/modules/holdtap.py @@ -21,6 +21,7 @@ class HoldTap(Module): tap_time = 300 def __init__(self): + self.key_buffer = set() self.key_states = {} def during_bootup(self, keyboard): @@ -33,19 +34,36 @@ class HoldTap(Module): return def process_key(self, keyboard, key, is_pressed): - '''Before other key down decide to send tap kc down.''' + '''Handle holdtap being interrupted by another key press/release.''' current_key = key for key, state in self.key_states.items(): if key == current_key: continue - if is_pressed: - if state.activated == ActivationType.NOT_ACTIVATED: - # press tap because interrupted by other key - self.key_states[key].activated = ActivationType.INTERRUPTED - self.ht_activate_on_interrupt( - key, keyboard, *state.args, **state.kwargs - ) + if state.activated != ActivationType.NOT_ACTIVATED: + continue + + # holdtap is interrupted by another key event. + if (is_pressed and not key.meta.tap_interrupted) or ( + not is_pressed and key.meta.tap_interrupted and self.key_buffer + ): + + keyboard.cancel_timeout(state.timeout_key) + self.key_states[key].activated = ActivationType.INTERRUPTED + self.ht_activate_on_interrupt( + key, keyboard, *state.args, **state.kwargs + ) + keyboard._send_hid() + + self.send_key_buffer(keyboard) + + # if interrupt on release: store interrupting keys until one of them + # is released. + if key.meta.tap_interrupted: + if is_pressed: + self.key_buffer.add(current_key) + current_key = None + return current_key def before_hid_send(self, keyboard): @@ -91,6 +109,7 @@ class HoldTap(Module): False, lambda: self.ht_deactivate_tap(key, keyboard, *args, **kwargs), ) + self.send_key_buffer(keyboard) del self.key_states[key] return keyboard @@ -103,6 +122,13 @@ class HoldTap(Module): # press hold because timer expired after tap time self.key_states[key].activated = ActivationType.HOLD_TIMEOUT self.ht_activate_hold(key, keyboard, *args, **kwargs) + self.send_key_buffer(keyboard) + + def send_key_buffer(self, keyboard): + for key in self.key_buffer: + key.on_press(keyboard) + keyboard._send_hid() + self.key_buffer.clear() def ht_activate_hold(self, key, keyboard, *args, **kwargs): pass diff --git a/kmk/types.py b/kmk/types.py index 10d5802..d64fece 100644 --- a/kmk/types.py +++ b/kmk/types.py @@ -11,21 +11,26 @@ class AttrDict(dict): return self[key] -class LayerKeyMeta: - def __init__(self, layer, kc=None, tap_time=None): - self.layer = layer +class HoldTapKeyMeta: + def __init__(self, kc=None, prefer_hold=True, tap_interrupted=False, tap_time=None): self.kc = kc - self.tap_time = tap_time - - -class ModTapKeyMeta: - def __init__(self, kc=None, mods=None, prefer_hold=True, tap_time=None): self.prefer_hold = prefer_hold - self.kc = kc - self.mods = mods + self.tap_interrupted = tap_interrupted self.tap_time = tap_time +class LayerKeyMeta(HoldTapKeyMeta): + def __init__(self, layer, **kwargs): + super().__init__(**kwargs) + self.layer = layer + + +class ModTapKeyMeta(HoldTapKeyMeta): + def __init__(self, kc=None, mods=None, **kwargs): + super().__init__(kc=kc, **kwargs) + self.mods = mods + + class KeySequenceMeta: def __init__(self, seq): self.seq = seq