fix inconsistencies with chained holdtap keys

This commit is contained in:
xs5871 2022-06-10 12:10:42 +00:00 committed by Kyle Brown
parent 50b8554757
commit 614961521d
2 changed files with 210 additions and 29 deletions

View File

@ -45,6 +45,9 @@ class HoldTap(Module):
def process_key(self, keyboard, key, is_pressed, int_coord): 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
send_buffer = False
append_buffer = False
for key, state in self.key_states.items(): for key, state in self.key_states.items():
if key == current_key: if key == current_key:
continue continue
@ -61,20 +64,28 @@ class HoldTap(Module):
self.ht_activate_on_interrupt( self.ht_activate_on_interrupt(
key, keyboard, *state.args, **state.kwargs key, keyboard, *state.args, **state.kwargs
) )
keyboard._send_hid() keyboard._send_hid()
send_buffer = True
self.send_key_buffer(keyboard)
if state.activated == ActivationType.INTERRUPTED: if state.activated == ActivationType.INTERRUPTED:
current_key = keyboard._find_key_in_map(int_coord) current_key = keyboard._find_key_in_map(int_coord)
# if interrupt on release: store interrupting keys until one of them # if interrupt on release: store interrupting keys until one of them
# is released. # is released.
if key.meta.tap_interrupted: if (
if is_pressed: key.meta.tap_interrupted
and is_pressed
and not isinstance(current_key.meta, HoldTapKeyMeta)
):
append_buffer = True
# apply changes with 'side-effects' on key_states or the loop behaviour
# outside the loop.
if append_buffer:
self.key_buffer.append((int_coord, current_key)) self.key_buffer.append((int_coord, current_key))
current_key = None current_key = None
elif send_buffer:
self.send_key_buffer(keyboard)
return current_key return current_key
@ -105,9 +116,12 @@ class HoldTap(Module):
def ht_released(self, key, keyboard, *args, **kwargs): def ht_released(self, key, keyboard, *args, **kwargs):
'''On keyup, release mod or tap key.''' '''On keyup, release mod or tap key.'''
if key in self.key_states: if key not in self.key_states:
return keyboard
state = self.key_states[key] state = self.key_states[key]
keyboard.cancel_timeout(state.timeout_key) keyboard.cancel_timeout(state.timeout_key)
if state.activated == ActivationType.HOLD_TIMEOUT: if state.activated == ActivationType.HOLD_TIMEOUT:
# release hold # release hold
self.ht_deactivate_hold(key, keyboard, *args, **kwargs) self.ht_deactivate_hold(key, keyboard, *args, **kwargs)
@ -121,8 +135,10 @@ class HoldTap(Module):
False, False,
lambda: self.ht_deactivate_tap(key, keyboard, *args, **kwargs), lambda: self.ht_deactivate_tap(key, keyboard, *args, **kwargs),
) )
state.activated = ActivationType.RELEASED
self.send_key_buffer(keyboard) self.send_key_buffer(keyboard)
del self.key_states[key] del self.key_states[key]
return keyboard return keyboard
def on_tap_time_expired(self, key, keyboard, *args, **kwargs): def on_tap_time_expired(self, key, keyboard, *args, **kwargs):
@ -145,8 +161,11 @@ 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):
for (int_coord, key) in self.key_buffer: key_buffer = self.key_buffer
key.on_press(keyboard) self.key_buffer = []
for (int_coord, key) in key_buffer:
new_key = keyboard._find_key_in_map(int_coord)
keyboard.pre_process_key(new_key, True, int_coord)
keyboard._send_hid() keyboard._send_hid()
self.key_buffer.clear() self.key_buffer.clear()

View File

@ -8,7 +8,7 @@ from tests.keyboard_test import KeyboardTest
class TestHoldTap(unittest.TestCase): class TestHoldTap(unittest.TestCase):
def test_basic_kmk_keyboard(self): def test_holdtap(self):
keyboard = KeyboardTest( keyboard = KeyboardTest(
[Layers(), ModTap(), OneShot()], [Layers(), ModTap(), OneShot()],
[ [
@ -100,6 +100,168 @@ class TestHoldTap(unittest.TestCase):
[{KC.N4}, {}], [{KC.N4}, {}],
) )
keyboard.test(
'LT after tap time nested -> hold behavior',
[
(0, True),
350,
(1, True),
350,
(3, True),
(3, False),
(1, False),
(0, False),
],
[{KC.LCTL}, {KC.LCTL, KC.N4}, {KC.LCTL}, {}],
)
def test_holdtap_chain(self):
keyboard = KeyboardTest(
[ModTap()],
[
[
KC.N0,
KC.MT(KC.N1, KC.LCTL, tap_time=50),
KC.MT(KC.N2, KC.LSFT, tap_interrupted=True, tap_time=50),
KC.MT(
KC.N3,
KC.LALT,
prefer_hold=False,
tap_interrupted=True,
tap_time=50,
),
],
],
debug_enabled=False,
)
# t_within = 40
t_after = 60
keyboard.test(
'chained 0',
[(1, True), (2, True), (0, True), (0, False), (2, False), (1, False)],
[
{KC.LCTL},
{KC.LCTL, KC.LSFT},
{KC.LCTL, KC.LSFT, KC.N0},
{KC.LCTL, KC.LSFT},
{KC.LCTL},
{},
],
)
keyboard.test(
'chained 1',
[(2, True), (1, True), (0, True), (0, False), (1, False), (2, False)],
[
{KC.LCTL},
{KC.LCTL, KC.LSFT},
{KC.LCTL, KC.LSFT, KC.N0},
{KC.LCTL, KC.LSFT},
{KC.LSFT},
{},
],
)
keyboard.test(
'chained 2',
[(1, True), (2, True), (0, True), (1, False), (2, False), (0, False)],
[
{KC.LCTL},
{KC.LCTL, KC.LSFT},
{KC.LCTL, KC.LSFT, KC.N0},
{KC.LSFT, KC.N0},
{KC.N0},
{},
],
)
keyboard.test(
'chained 3',
[(1, True), (3, True), (0, True), (0, False), (1, False), (3, False)],
[
{KC.LCTL},
{KC.LCTL, KC.N3},
{KC.LCTL, KC.N3, KC.N0},
{KC.LCTL, KC.N3},
{KC.N3},
{},
],
)
keyboard.test(
'chained 4',
[(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}, {}],
)
keyboard.test(
'chained 5',
[(3, True), (1, True), (0, True), (0, False), (1, False), (3, False)],
[
{KC.LCTL},
{KC.LCTL, KC.N3},
{KC.LCTL, KC.N3, KC.N0},
{KC.LCTL, KC.N3},
{KC.N3},
{},
],
)
keyboard.test(
'chained 6',
[
(3, True),
(1, True),
t_after,
(0, True),
(0, False),
(1, False),
(3, False),
],
[
{KC.LCTL, KC.LALT},
{KC.LCTL, KC.LALT, KC.N0},
{KC.LCTL, KC.LALT},
{KC.LALT},
{},
],
)
keyboard.test(
'chained 7',
[
(1, True),
(3, True),
t_after,
(0, True),
(0, False),
(1, False),
(3, False),
],
[
{KC.LCTL},
{KC.LCTL, KC.LALT},
{KC.LCTL, KC.LALT, KC.N0},
{KC.LCTL, KC.LALT},
{KC.LALT},
{},
],
)
keyboard.test(
'chained 8',
[(2, True), (3, True), (0, True), (0, False), (2, False), (3, False)],
[
{KC.LSFT},
{KC.LSFT, KC.N3},
{KC.LSFT, KC.N3, KC.N0},
{KC.LSFT, KC.N3},
{KC.N3},
{},
],
)
# TODO test TT # TODO test TT
def test_oneshot(self): def test_oneshot(self):