From 1c6b25517a5ea4161ac56c3047c7b4fb8f0bc4a9 Mon Sep 17 00:00:00 2001 From: xs5871 Date: Thu, 3 Feb 2022 21:09:39 +0000 Subject: [PATCH] implement hold-tap interrupt for Layers --- kmk/key_validators.py | 10 +++++++-- kmk/kmk_keyboard.py | 23 +++++++++++++-------- kmk/modules/__init__.py | 6 +++--- kmk/modules/holdtap.py | 10 ++++----- kmk/modules/layers.py | 46 +++++++++++++++++++++++++++-------------- kmk/modules/tapdance.py | 2 +- 6 files changed, 61 insertions(+), 36 deletions(-) diff --git a/kmk/key_validators.py b/kmk/key_validators.py index e3faa28..8a4ba25 100644 --- a/kmk/key_validators.py +++ b/kmk/key_validators.py @@ -11,7 +11,9 @@ def key_seq_sleep_validator(ms): return KeySeqSleepMeta(ms) -def layer_key_validator(layer, kc=None, tap_time=None): +def layer_key_validator( + layer, kc=None, prefer_hold=False, tap_interrupted=False, 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 @@ -20,7 +22,11 @@ def layer_key_validator(layer, kc=None, tap_time=None): out. ''' return LayerKeyMeta( - layer=layer, kc=kc, prefer_hold=False, tap_interrupted=False, tap_time=tap_time + layer=layer, + kc=kc, + prefer_hold=prefer_hold, + tap_interrupted=tap_interrupted, + tap_time=tap_time, ) diff --git a/kmk/kmk_keyboard.py b/kmk/kmk_keyboard.py index 7082ad3..90f845b 100644 --- a/kmk/kmk_keyboard.py +++ b/kmk/kmk_keyboard.py @@ -158,24 +158,29 @@ class KMKKeyboard: if self.current_key is None: self.current_key = self._find_key_in_map(int_coord, row, col) - if is_pressed: - self._coordkeys_pressed[int_coord] = self.current_key - else: - self._coordkeys_pressed[int_coord] = None - - if self.current_key is None: - print('MatrixUndefinedCoordinate(col={} row={})'.format(col, row)) - return self + if self.current_key is None: + print('MatrixUndefinedCoordinate(col={} row={})'.format(col, row)) + return self for module in self.modules: try: - if module.process_key(self, self.current_key, is_pressed) is None: + self.current_key = module.process_key( + self, self.current_key, is_pressed, int_coord + ) + if self.current_key is None: break except Exception as err: if self.debug_enabled: print('Failed to run process_key function in module: ', err, module) + + if is_pressed: + self._coordkeys_pressed[int_coord] = self.current_key else: + del self._coordkeys_pressed[int_coord] + + if self.current_key: self.process_key(self.current_key, is_pressed, int_coord, (row, col)) + return self def process_key(self, key, is_pressed, coord_int=None, coord_raw=None): diff --git a/kmk/modules/__init__.py b/kmk/modules/__init__.py index 53280ca..b616f07 100644 --- a/kmk/modules/__init__.py +++ b/kmk/modules/__init__.py @@ -27,6 +27,9 @@ class Module: ''' raise NotImplementedError + def process_key(self, keyboard, key, is_pressed, int_coord): + return key + def before_hid_send(self, keyboard): raise NotImplementedError @@ -38,6 +41,3 @@ class Module: def on_powersave_disable(self, keyboard): raise NotImplementedError - - def process_key(self, keyboard, key, is_pressed): - return key diff --git a/kmk/modules/holdtap.py b/kmk/modules/holdtap.py index d2a7a6b..185aa74 100644 --- a/kmk/modules/holdtap.py +++ b/kmk/modules/holdtap.py @@ -21,7 +21,7 @@ class HoldTap(Module): tap_time = 300 def __init__(self): - self.key_buffer = set() + self.key_buffer = [] self.key_states = {} def during_bootup(self, keyboard): @@ -33,7 +33,7 @@ class HoldTap(Module): def after_matrix_scan(self, keyboard): return - def process_key(self, keyboard, key, is_pressed): + def process_key(self, keyboard, key, is_pressed, int_coord): '''Handle holdtap being interrupted by another key press/release.''' current_key = key for key, state in self.key_states.items(): @@ -53,7 +53,7 @@ class HoldTap(Module): key, keyboard, *state.args, **state.kwargs ) - keyboard._send_hid() + keyboard._send_hid() self.send_key_buffer(keyboard) @@ -61,7 +61,7 @@ class HoldTap(Module): # is released. if key.meta.tap_interrupted: if is_pressed: - self.key_buffer.add(current_key) + self.key_buffer.append((int_coord, current_key)) current_key = None return current_key @@ -125,7 +125,7 @@ class HoldTap(Module): self.send_key_buffer(keyboard) def send_key_buffer(self, keyboard): - for key in self.key_buffer: + for (int_coord, key) in self.key_buffer: key.on_press(keyboard) keyboard._send_hid() self.key_buffer.clear() diff --git a/kmk/modules/layers.py b/kmk/modules/layers.py index 800c745..de36414 100644 --- a/kmk/modules/layers.py +++ b/kmk/modules/layers.py @@ -3,7 +3,7 @@ from micropython import const from kmk.key_validators import layer_key_validator from kmk.keys import make_argumented_key -from kmk.modules.holdtap import HoldTap +from kmk.modules.holdtap import ActivationType, HoldTap class LayerType: @@ -57,12 +57,40 @@ class Layers(HoldTap): validator=layer_key_validator, names=('TO',), on_press=self._to_pressed ) make_argumented_key( - validator=layer_key_validator, + validator=curry(layer_key_validator, prefer_hold=True), names=('TT',), on_press=curry(self.ht_pressed, key_type=LayerType.TT), on_release=curry(self.ht_released, key_type=LayerType.TT), ) + def process_key(self, keyboard, key, is_pressed, int_coord): + current_key = super().process_key(keyboard, key, is_pressed, int_coord) + + for key, state in self.key_states.items(): + if key == current_key: + continue + + # on interrupt: key must be translated here, because it was asigned + # before the layer shift happend. + if state.activated == ActivationType.INTERRUPTED: + current_key = keyboard._find_key_in_map(int_coord, None, None) + + return current_key + + def send_key_buffer(self, keyboard): + for (int_coord, old_key) in self.key_buffer: + new_key = keyboard._find_key_in_map(int_coord, None, None) + + # adding keys late to _coordkeys_pressed isn't pretty, + # but necessary to mitigate race conditions when multiple + # keys are pressed during a tap-interrupted hold-tap. + keyboard._coordkeys_pressed[int_coord] = new_key + new_key.on_press(keyboard) + + keyboard._send_hid() + + self.key_buffer.clear() + def _df_pressed(self, key, keyboard, *args, **kwargs): ''' Switches the default layer @@ -153,17 +181,3 @@ class Layers(HoldTap): if key_type == LayerType.LT: keyboard.hid_pending = True keyboard.keys_pressed.discard(key.meta.kc) - - def ht_activate_on_interrupt(self, key, keyboard, *args, **kwargs): - key_type = kwargs['key_type'] - if key_type == LayerType.LT: - self.ht_activate_tap(key, keyboard, *args, **kwargs) - elif key_type == LayerType.TT: - self.ht_activate_hold(key, keyboard, *args, **kwargs) - - def ht_deactivate_on_interrupt(self, key, keyboard, *args, **kwargs): - key_type = kwargs['key_type'] - if key_type == LayerType.LT: - self.ht_deactivate_tap(key, keyboard, *args, **kwargs) - elif key_type == LayerType.TT: - self.ht_deactivate_hold(key, keyboard, *args, **kwargs) diff --git a/kmk/modules/tapdance.py b/kmk/modules/tapdance.py index 8b294e6..2bf5274 100644 --- a/kmk/modules/tapdance.py +++ b/kmk/modules/tapdance.py @@ -43,7 +43,7 @@ class TapDance(Module): def on_powersave_disable(self, keyboard): return - def process_key(self, keyboard, key, is_pressed): + def process_key(self, keyboard, key, is_pressed, int_coord): if self._tapping and is_pressed and not isinstance(key.meta, TapDanceKeyMeta): for k, v in self._tap_dance_counts.items(): if v: