kmk_firmware/kmk/modules/tapdance.py
2023-03-16 21:50:42 +00:00

123 lines
4.2 KiB
Python

from kmk.keys import KC, make_argumented_key
from kmk.modules.holdtap import ActivationType, HoldTap, HoldTapKeyMeta
class TapDanceKeyMeta:
def __init__(self, *keys, tap_time=None):
'''
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 = []
for key in keys:
if not isinstance(key.meta, HoldTapKeyMeta):
ht_key = KC.HT(
tap=key,
hold=key,
prefer_hold=True,
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):
super().__init__()
make_argumented_key(
validator=TapDanceKeyMeta,
names=('TD',),
on_press=self.td_pressed,
on_release=self.td_released,
)
self.td_counts = {}
def process_key(self, keyboard, key, is_pressed, int_coord):
if isinstance(key.meta, TapDanceKeyMeta):
if key in self.td_counts:
return key
for _key, state in self.key_states.copy().items():
if state.activated == ActivationType.RELEASED:
keyboard.cancel_timeout(state.timeout_key)
self.ht_activate_tap(_key, keyboard)
self.send_key_buffer(keyboard)
self.ht_deactivate_tap(_key, keyboard)
keyboard.resume_process_key(self, key, is_pressed, int_coord)
key = None
del self.key_states[_key]
del self.td_counts[state.tap_dance]
key = super().process_key(keyboard, key, is_pressed, int_coord)
return key
def td_pressed(self, key, keyboard, *args, **kwargs):
# active tap dance
if key in self.td_counts:
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
else:
del self.key_states[kc]
# new tap dance
else:
count = 0
current_key = key.meta.keys[count]
self.ht_pressed(current_key, keyboard, *args, **kwargs)
self.td_counts[key] = count
# 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):
try:
kc = key.meta.keys[self.td_counts[key]]
except KeyError:
return
state = self.key_states[kc]
if state.activated == ActivationType.HOLD_TIMEOUT:
# release hold
self.ht_deactivate_hold(kc, keyboard, *args, **kwargs)
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:
# keep counting
state.activated = ActivationType.RELEASED
def on_tap_time_expired(self, key, keyboard, *args, **kwargs):
# Note: the `key` argument is the current holdtap key in the sequence,
# not the tapdance key.
state = self.key_states[key]
if state.activated == ActivationType.RELEASED:
self.ht_activate_tap(key, keyboard, *args, **kwargs)
self.send_key_buffer(keyboard)
del self.td_counts[state.tap_dance]
super().on_tap_time_expired(key, keyboard, *args, **kwargs)