implement hold-tap interrupt for Layers

This commit is contained in:
xs5871 2022-02-03 21:09:39 +00:00 committed by Kyle Brown
parent a685618480
commit 1c6b25517a
6 changed files with 61 additions and 36 deletions

View File

@ -11,7 +11,9 @@ def key_seq_sleep_validator(ms):
return KeySeqSleepMeta(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 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 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. out.
''' '''
return LayerKeyMeta( 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,
) )

View File

@ -158,24 +158,29 @@ class KMKKeyboard:
if self.current_key is None: if self.current_key is None:
self.current_key = self._find_key_in_map(int_coord, row, col) self.current_key = self._find_key_in_map(int_coord, row, col)
if is_pressed: if self.current_key is None:
self._coordkeys_pressed[int_coord] = self.current_key print('MatrixUndefinedCoordinate(col={} row={})'.format(col, row))
else: return self
self._coordkeys_pressed[int_coord] = None
if self.current_key is None:
print('MatrixUndefinedCoordinate(col={} row={})'.format(col, row))
return self
for module in self.modules: for module in self.modules:
try: 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 break
except Exception as err: except Exception as err:
if self.debug_enabled: if self.debug_enabled:
print('Failed to run process_key function in module: ', err, module) print('Failed to run process_key function in module: ', err, module)
if is_pressed:
self._coordkeys_pressed[int_coord] = self.current_key
else: else:
del self._coordkeys_pressed[int_coord]
if self.current_key:
self.process_key(self.current_key, is_pressed, int_coord, (row, col)) self.process_key(self.current_key, is_pressed, int_coord, (row, col))
return self return self
def process_key(self, key, is_pressed, coord_int=None, coord_raw=None): def process_key(self, key, is_pressed, coord_int=None, coord_raw=None):

View File

@ -27,6 +27,9 @@ class Module:
''' '''
raise NotImplementedError raise NotImplementedError
def process_key(self, keyboard, key, is_pressed, int_coord):
return key
def before_hid_send(self, keyboard): def before_hid_send(self, keyboard):
raise NotImplementedError raise NotImplementedError
@ -38,6 +41,3 @@ class Module:
def on_powersave_disable(self, keyboard): def on_powersave_disable(self, keyboard):
raise NotImplementedError raise NotImplementedError
def process_key(self, keyboard, key, is_pressed):
return key

View File

@ -21,7 +21,7 @@ class HoldTap(Module):
tap_time = 300 tap_time = 300
def __init__(self): def __init__(self):
self.key_buffer = set() self.key_buffer = []
self.key_states = {} self.key_states = {}
def during_bootup(self, keyboard): def during_bootup(self, keyboard):
@ -33,7 +33,7 @@ class HoldTap(Module):
def after_matrix_scan(self, keyboard): def after_matrix_scan(self, keyboard):
return 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.''' '''Handle holdtap being interrupted by another key press/release.'''
current_key = key current_key = key
for key, state in self.key_states.items(): for key, state in self.key_states.items():
@ -53,7 +53,7 @@ class HoldTap(Module):
key, keyboard, *state.args, **state.kwargs key, keyboard, *state.args, **state.kwargs
) )
keyboard._send_hid() keyboard._send_hid()
self.send_key_buffer(keyboard) self.send_key_buffer(keyboard)
@ -61,7 +61,7 @@ class HoldTap(Module):
# is released. # is released.
if key.meta.tap_interrupted: if key.meta.tap_interrupted:
if is_pressed: if is_pressed:
self.key_buffer.add(current_key) self.key_buffer.append((int_coord, current_key))
current_key = None current_key = None
return current_key return current_key
@ -125,7 +125,7 @@ class HoldTap(Module):
self.send_key_buffer(keyboard) self.send_key_buffer(keyboard)
def send_key_buffer(self, 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) key.on_press(keyboard)
keyboard._send_hid() keyboard._send_hid()
self.key_buffer.clear() self.key_buffer.clear()

View File

@ -3,7 +3,7 @@ from micropython import const
from kmk.key_validators import layer_key_validator from kmk.key_validators import layer_key_validator
from kmk.keys import make_argumented_key from kmk.keys import make_argumented_key
from kmk.modules.holdtap import HoldTap from kmk.modules.holdtap import ActivationType, HoldTap
class LayerType: class LayerType:
@ -57,12 +57,40 @@ class Layers(HoldTap):
validator=layer_key_validator, names=('TO',), on_press=self._to_pressed validator=layer_key_validator, names=('TO',), on_press=self._to_pressed
) )
make_argumented_key( make_argumented_key(
validator=layer_key_validator, validator=curry(layer_key_validator, prefer_hold=True),
names=('TT',), names=('TT',),
on_press=curry(self.ht_pressed, key_type=LayerType.TT), on_press=curry(self.ht_pressed, key_type=LayerType.TT),
on_release=curry(self.ht_released, 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): def _df_pressed(self, key, keyboard, *args, **kwargs):
''' '''
Switches the default layer Switches the default layer
@ -153,17 +181,3 @@ class Layers(HoldTap):
if key_type == LayerType.LT: if key_type == LayerType.LT:
keyboard.hid_pending = True keyboard.hid_pending = True
keyboard.keys_pressed.discard(key.meta.kc) 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)

View File

@ -43,7 +43,7 @@ class TapDance(Module):
def on_powersave_disable(self, keyboard): def on_powersave_disable(self, keyboard):
return 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): if self._tapping and is_pressed and not isinstance(key.meta, TapDanceKeyMeta):
for k, v in self._tap_dance_counts.items(): for k, v in self._tap_dance_counts.items():
if v: if v: