123 lines
4.2 KiB
Python
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)
|