continue/finish holdtap-repeat

This commit is contained in:
xs5871 2022-10-02 16:41:33 +00:00 committed by Kyle Brown
parent 5efd2688d7
commit 565ec8353b
3 changed files with 60 additions and 31 deletions

View File

@ -31,7 +31,7 @@ keyboard.modules.append(modtap)
## Custom HoldTap Behavior ## Custom HoldTap Behavior
The full ModTap signature is as follows: The full ModTap signature is as follows:
```python ```python
KC.MT(KC.TAP, KC.HOLD, prefer_hold=True, tap_interrupted=False, tap_time=None, repeat=True) KC.MT(KC.TAP, KC.HOLD, prefer_hold=True, tap_interrupted=False, tap_time=None, repeat=False)
``` ```
* `prefer_hold`: decides which keycode the ModTap key resolves to when another * `prefer_hold`: decides which keycode the ModTap key resolves to when another
key is pressed before the timeout finishes. When `True` the hold keycode is key is pressed before the timeout finishes. When `True` the hold keycode is
@ -40,10 +40,11 @@ KC.MT(KC.TAP, KC.HOLD, prefer_hold=True, tap_interrupted=False, tap_time=None, r
key press/down, or after the first other key up/release. Set to `True` for key press/down, or after the first other key up/release. Set to `True` for
interrupt on release. interrupt on release.
* `tap_time`: length of the tap timeout in milliseconds. * `tap_time`: length of the tap timeout in milliseconds.
* `repeat`: decides how to interpret a second press after a tap within the * `repeat`: decides how to interpret repeated presses if they happen within
timeout. When `True` the second press sends the tap keycode, no matter `tap_time` after a release.
how long the key remains pressed the second time. This allows the operating When `True` the repeated press sends the previous keycode, no matter
system to trigger the autorepeat feature. Set it to `False` for handling how long the key remains pressed the second time.
the second press as if no tap happened just before. This allows to hold the tap keycode, or repeatedly tap the hold keycode.
When `False` repeated presses are independent.
Each of these parameters can be set for every ModTap key individually. Each of these parameters can be set for every ModTap key individually.

View File

@ -1,6 +1,6 @@
from micropython import const from micropython import const
from kmk.keys import make_argumented_key from kmk.keys import KC, make_argumented_key
from kmk.modules import Module from kmk.modules import Module
from kmk.utils import Debug from kmk.utils import Debug
@ -47,6 +47,7 @@ class HoldTap(Module):
def __init__(self): def __init__(self):
self.key_buffer = [] self.key_buffer = []
self.key_states = {} self.key_states = {}
if not KC.get('HT'):
make_argumented_key( make_argumented_key(
validator=HoldTapKeyMeta, validator=HoldTapKeyMeta,
names=('HT',), names=('HT',),
@ -124,11 +125,19 @@ class HoldTap(Module):
def ht_pressed(self, key, keyboard, *args, **kwargs): def ht_pressed(self, key, keyboard, *args, **kwargs):
'''Unless in repeat mode, do nothing yet, action resolves when key is released, timer expires or other key is pressed.''' '''Unless in repeat mode, do nothing yet, action resolves when key is released, timer expires or other key is pressed.'''
state = self.key_states.get(key) if key in self.key_states:
if state is not None and state.activated == ActivationType.RELEASED: state = self.key_states[key]
keyboard.cancel_timeout(self.key_states[key].timeout_key)
if state.activated == ActivationType.RELEASED:
state.activated = ActivationType.REPEAT state.activated = ActivationType.REPEAT
self.ht_activate_tap(key, keyboard, *args, **kwargs) self.ht_activate_tap(key, keyboard, *args, **kwargs)
else: elif state.activated == ActivationType.HOLD_TIMEOUT:
self.ht_activate_hold(key, keyboard, *args, **kwargs)
elif state.activated == ActivationType.INTERRUPTED:
self.ht_activate_on_interrupt(key, keyboard, *args, **kwargs)
return
if key.meta.tap_time is None: if key.meta.tap_time is None:
tap_time = self.tap_time tap_time = self.tap_time
else: else:
@ -157,15 +166,24 @@ class HoldTap(Module):
elif state.activated == ActivationType.PRESSED: elif state.activated == ActivationType.PRESSED:
# press and release tap because key released within tap time # press and release tap because key released within tap time
self.ht_activate_tap(key, keyboard, *args, **kwargs) self.ht_activate_tap(key, keyboard, *args, **kwargs)
keyboard._send_hid()
self.ht_deactivate_tap(key, keyboard, *args, **kwargs) self.ht_deactivate_tap(key, keyboard, *args, **kwargs)
state.activated = ActivationType.RELEASED state.activated = ActivationType.RELEASED
self.send_key_buffer(keyboard) self.send_key_buffer(keyboard)
# don't delete the key state right now in this case
if key.meta.repeat:
return keyboard
elif state.activated == ActivationType.REPEAT: elif state.activated == ActivationType.REPEAT:
state.activated = ActivationType.RELEASED
self.ht_deactivate_tap(key, keyboard, *args, **kwargs) self.ht_deactivate_tap(key, keyboard, *args, **kwargs)
del self.key_states[key]
# don't delete the key state right now in this case
tap_time = 0
if key.meta.repeat:
if key.meta.tap_time is None:
tap_time = self.tap_time
else:
tap_time = key.meta.tap_time
state.timeout_key = keyboard.set_timeout(
tap_time, lambda: self.key_states.pop(key)
)
return keyboard return keyboard
@ -189,6 +207,9 @@ class HoldTap(Module):
del self.key_states[key] del self.key_states[key]
def send_key_buffer(self, keyboard): def send_key_buffer(self, keyboard):
if not self.key_buffer:
return
for (int_coord, key) in self.key_buffer: for (int_coord, key) in self.key_buffer:
new_key = keyboard._find_key_in_map(int_coord) new_key = keyboard._find_key_in_map(int_coord)
keyboard.resume_process_key(self, new_key, True, int_coord) keyboard.resume_process_key(self, new_key, True, int_coord)

View File

@ -192,7 +192,14 @@ class TestHoldTap(unittest.TestCase):
keyboard.test( keyboard.test(
'chained 4', 'chained 4',
[(1, True), (3, True), (0, True), (3, False), (1, False), (0, False)], [(1, True), (3, True), (0, True), (3, False), (1, False), (0, False)],
[{KC.LCTL}, {KC.LCTL, KC.N3, KC.N0}, {KC.LCTL, KC.N0}, {KC.N0}, {}], [
{KC.LCTL},
{KC.LCTL, KC.N3},
{KC.LCTL, KC.N0, KC.N3},
{KC.LCTL, KC.N0},
{KC.N0},
{},
],
) )
keyboard.test( keyboard.test(