refactor tapdance on top of holdtap

This commit is contained in:
xs5871 2022-05-03 18:07:07 +00:00 committed by Kyle Brown
parent 7af0e11f75
commit 51f07d8558
3 changed files with 82 additions and 118 deletions

View File

@ -1,10 +1,4 @@
from kmk.types import ( from kmk.types import KeySeqSleepMeta, LayerKeyMeta, ModTapKeyMeta, UnicodeModeKeyMeta
KeySeqSleepMeta,
LayerKeyMeta,
ModTapKeyMeta,
TapDanceKeyMeta,
UnicodeModeKeyMeta,
)
def key_seq_sleep_validator(ms): def key_seq_sleep_validator(ms):
@ -37,9 +31,5 @@ def mod_tap_validator(
) )
def tap_dance_key_validator(*codes):
return TapDanceKeyMeta(codes)
def unicode_mode_key_validator(mode): def unicode_mode_key_validator(mode):
return UnicodeModeKeyMeta(mode) return UnicodeModeKeyMeta(mode)

View File

@ -1,124 +1,103 @@
from kmk.key_validators import tap_dance_key_validator from kmk.keys import KC, make_argumented_key
from kmk.keys import make_argumented_key from kmk.modules.holdtap import ActivationType, HoldTap
from kmk.modules import Module from kmk.types import HoldTapKeyMeta
from kmk.types import TapDanceKeyMeta
class TapDance(Module): class TapDanceKeyMeta:
# User-configurable def __init__(self, *keys, tap_time=None):
tap_time = 300 '''
Any key in the tapdance sequence that is not already a holdtap
key gets converted to a holdtap key with identical tap and hold
meta attributes.
'''
self.tap_time = tap_time
self.keys = []
# Internal State for key in keys:
_tapping = False if not isinstance(key.meta, HoldTapKeyMeta):
_tap_dance_counts = {} ht_key = KC.HT(
_tap_timeout = None tap=key,
_tap_side_effects = {} hold=key,
prefer_hold=False,
tap_interrupted=False,
tap_time=self.tap_time,
)
self.keys.append(ht_key)
else:
self.keys.append(key)
self.keys = tuple(self.keys)
class TapDance(HoldTap):
def __init__(self): def __init__(self):
super().__init__()
make_argumented_key( make_argumented_key(
validator=tap_dance_key_validator, validator=TapDanceKeyMeta,
names=('TAP_DANCE', 'TD'), names=('TD',),
on_press=self.td_pressed, on_press=self.td_pressed,
on_release=self.td_released, on_release=self.td_released,
) )
def during_bootup(self, keyboard): self.td_counts = {}
return
def before_matrix_scan(self, keyboard):
return
def after_matrix_scan(self, keyboard):
return
def before_hid_send(self, keyboard):
return
def after_hid_send(self, keyboard):
return
def on_powersave_enable(self, keyboard):
return
def on_powersave_disable(self, keyboard):
return
def process_key(self, keyboard, key, is_pressed, int_coord): def process_key(self, keyboard, key, is_pressed, int_coord):
if self._tapping and is_pressed and not isinstance(key.meta, TapDanceKeyMeta): if isinstance(key.meta, TapDanceKeyMeta):
for k, v in self._tap_dance_counts.items(): if key in self.td_counts:
if v:
self._end_tap_dance(k, keyboard, hold=True)
keyboard.hid_pending = True
keyboard._send_hid()
keyboard.set_timeout(
False, lambda: keyboard.process_key(key, is_pressed)
)
return None
return key return key
return super().process_key(keyboard, key, is_pressed, int_coord)
def td_pressed(self, key, keyboard, *args, **kwargs): def td_pressed(self, key, keyboard, *args, **kwargs):
if key not in self._tap_dance_counts or not self._tap_dance_counts[key]: # active tap dance
self._tap_dance_counts[key] = 1 if key in self.td_counts:
self._tapping = True count = self.td_counts[key]
kc = key.meta.keys[count]
keyboard.cancel_timeout(self.key_states[kc].timeout_key)
count += 1
# Tap dance reached the end of the list: send last tap in sequence
# and start from the beginning.
if count >= len(key.meta.keys):
self.key_states[kc].activated = ActivationType.RELEASED
self.on_tap_time_expired(kc, keyboard)
count = 0
# new tap dance
else: else:
keyboard.cancel_timeout(self._tap_timeout) count = 0
self._tap_dance_counts[key] += 1
if key not in self._tap_side_effects: current_key = key.meta.keys[count]
self._tap_side_effects[key] = None
self._tap_timeout = keyboard.set_timeout( self.ht_pressed(current_key, keyboard, *args, **kwargs)
self.tap_time, lambda: self._end_tap_dance(key, keyboard, hold=True) self.td_counts[key] = count
)
return self # Add the active tap dance to key_states; `on_tap_time_expired` needs
# the back-reference.
self.key_states[current_key].tap_dance = key
def td_released(self, key, keyboard, *args, **kwargs): def td_released(self, key, keyboard, *args, **kwargs):
has_side_effects = self._tap_side_effects[key] is not None kc = key.meta.keys[self.td_counts[key]]
hit_max_defined_taps = self._tap_dance_counts[key] == len(key.meta.codes) state = self.key_states[kc]
if state.activated == ActivationType.HOLD_TIMEOUT:
keyboard.cancel_timeout(self._tap_timeout) # release hold
if has_side_effects or hit_max_defined_taps: self.ht_deactivate_hold(kc, keyboard, *args, **kwargs)
self._end_tap_dance(key, keyboard) del self.key_states[kc]
del self.td_counts[key]
elif state.activated == ActivationType.INTERRUPTED:
# release tap
self.ht_deactivate_on_interrupt(kc, keyboard, *args, **kwargs)
del self.key_states[kc]
del self.td_counts[key]
else: else:
self._tap_timeout = keyboard.set_timeout( # keep counting
self.tap_time, lambda: self._end_tap_dance(key, keyboard) state.activated = ActivationType.RELEASED
)
return self def on_tap_time_expired(self, key, keyboard, *args, **kwargs):
# Note: the `key` argument is the current holdtap key in the sequence,
def _end_tap_dance(self, key, keyboard, hold=False): # not the tapdance key.
v = self._tap_dance_counts[key] - 1 state = self.key_states[key]
if state.activated == ActivationType.RELEASED:
if v < 0: self.ht_activate_tap(key, keyboard, *args, **kwargs)
return self keyboard._send_hid()
del self.td_counts[state.tap_dance]
if key in keyboard.keys_pressed: super().on_tap_time_expired(key, keyboard, *args, **kwargs)
key_to_press = key.meta.codes[v]
keyboard.add_key(key_to_press)
self._tap_side_effects[key] = key_to_press
elif self._tap_side_effects[key]:
keyboard.remove_key(self._tap_side_effects[key])
self._tap_side_effects[key] = None
self._cleanup_tap_dance(key)
elif hold is False:
if key.meta.codes[v] in keyboard.keys_pressed:
keyboard.remove_key(key.meta.codes[v])
else:
keyboard.tap_key(key.meta.codes[v])
self._cleanup_tap_dance(key)
else:
key_to_press = key.meta.codes[v]
keyboard.add_key(key_to_press)
self._tap_side_effects[key] = key_to_press
self._tapping = 0
keyboard.hid_pending = True
return self
def _cleanup_tap_dance(self, key):
self._tap_dance_counts[key] = 0
self._tapping = any(count > 0 for count in self._tap_dance_counts.values())
return self

View File

@ -50,8 +50,3 @@ class KeySeqSleepMeta:
class UnicodeModeKeyMeta: class UnicodeModeKeyMeta:
def __init__(self, mode): def __init__(self, mode):
self.mode = mode self.mode = mode
class TapDanceKeyMeta:
def __init__(self, codes):
self.codes = codes