make TapDance a module (#281)
* extract tapdance logic into a module * clean out old tapdance code * canonicalize key variable names * split _process_tap_dance into td_pressed and td_released * implement consistent argument order * update documentation * implement Module.process_key for key interception and modification * fix tapdance realesing instead of pressing * fix: default parameters in key handler * cleanup holdtap * add error handling to modules process_key * fix: key released too late Tapped keys didn't release on a "key released" event, but waited for a timeout. Resulted in, for example, modifiers applying to keys after the modifier was released. * fix lint/formatting * fix tap_time reference in modtap + minimal documentation * fix lint
This commit is contained in:
parent
10f8c74ad9
commit
a62d39a252
@ -4,7 +4,10 @@ added to the modules list.
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from kmk.modules.modtap import ModTap
|
from kmk.modules.modtap import ModTap
|
||||||
keyboard.modules.append(ModTap())
|
modtap = ModTap()
|
||||||
|
# optional: set a custom tap timeout in ms
|
||||||
|
# modtap.tap_time = 300
|
||||||
|
keyboard.modules.append(modtap)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Keycodes
|
## Keycodes
|
||||||
|
@ -13,3 +13,4 @@ put on your keyboard
|
|||||||
when tapped, and modifier when held.
|
when tapped, and modifier when held.
|
||||||
- [Power](power.md): Power saving features. This is mostly useful when on battery power.
|
- [Power](power.md): Power saving features. This is mostly useful when on battery power.
|
||||||
- [Split](split_keyboards.md): Keyboards split in two. Seems ergonomic!
|
- [Split](split_keyboards.md): Keyboards split in two. Seems ergonomic!
|
||||||
|
- [TapDance](tapdance.md): Different key actions depending on how often it is pressed.
|
||||||
|
@ -23,6 +23,8 @@ keymap somewhere. The only limits on how many keys can go in the sequence are,
|
|||||||
theoretically, the amount of RAM your MCU/board has, and how fast you can mash
|
theoretically, the amount of RAM your MCU/board has, and how fast you can mash
|
||||||
the physical key. Here's your chance to use all that button-mash video game
|
the physical key. Here's your chance to use all that button-mash video game
|
||||||
experience you've built up over the years.
|
experience you've built up over the years.
|
||||||
|
[//]: # (The button mashing part has been 'fixed' by a timeout refresh per)
|
||||||
|
[//]: # (button press. The comedic sentiment is worth keeping though.)
|
||||||
|
|
||||||
**NOTE**: Currently our basic tap dance implementation has some limitations that
|
**NOTE**: Currently our basic tap dance implementation has some limitations that
|
||||||
are planned to be worked around "eventually", but for now are noteworthy:
|
are planned to be worked around "eventually", but for now are noteworthy:
|
||||||
@ -31,16 +33,23 @@ are planned to be worked around "eventually", but for now are noteworthy:
|
|||||||
currently "undefined" at best, and will probably crash your keyboard. For now,
|
currently "undefined" at best, and will probably crash your keyboard. For now,
|
||||||
we strongly recommend avoiding `KC.MO` (or any other layer switch keys that
|
we strongly recommend avoiding `KC.MO` (or any other layer switch keys that
|
||||||
use momentary switch behavior - `KC.LM`, `KC.LT`, and `KC.TT`)
|
use momentary switch behavior - `KC.LM`, `KC.LT`, and `KC.TT`)
|
||||||
|
[//]: # (This also doesn't seem to be the case anymore; as long as the layer)
|
||||||
|
[//]: # (is transparent to the tap dance key.)
|
||||||
|
[//]: # (At least KC.MO is working as intended, other momentary actions haven't)
|
||||||
|
[//]: # (been tested.)
|
||||||
|
|
||||||
Here's an example of all this in action:
|
Here's an example of all this in action:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from kmk.keycodes import KC
|
from kmk.keycodes import KC
|
||||||
from kmk.macros.simple import send_string
|
from kmk.handlers.sequences import send_string
|
||||||
|
from kmk.modules.tapdance import TapDance
|
||||||
|
|
||||||
keyboard = KMKKeyboard()
|
keyboard = KMKKeyboard()
|
||||||
|
|
||||||
keyboard.tap_time = 750
|
tapdance = TapDance()
|
||||||
|
tapdance.tap_time = 750
|
||||||
|
keyboard.modules.append(tapdance)
|
||||||
|
|
||||||
EXAMPLE_TD = KC.TD(
|
EXAMPLE_TD = KC.TD(
|
||||||
KC.A, # Tap once for "a"
|
KC.A, # Tap once for "a"
|
||||||
|
@ -105,14 +105,6 @@ def uc_mode_pressed(key, keyboard, *args, **kwargs):
|
|||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
|
|
||||||
def td_pressed(key, keyboard, *args, **kwargs):
|
|
||||||
return keyboard._process_tap_dance(key, True)
|
|
||||||
|
|
||||||
|
|
||||||
def td_released(key, keyboard, *args, **kwargs):
|
|
||||||
return keyboard._process_tap_dance(key, False)
|
|
||||||
|
|
||||||
|
|
||||||
def hid_switch(key, keyboard, *args, **kwargs):
|
def hid_switch(key, keyboard, *args, **kwargs):
|
||||||
keyboard.hid_type, keyboard.secondary_hid_type = (
|
keyboard.hid_type, keyboard.secondary_hid_type = (
|
||||||
keyboard.secondary_hid_type,
|
keyboard.secondary_hid_type,
|
||||||
|
17
kmk/keys.py
17
kmk/keys.py
@ -3,11 +3,7 @@ from micropython import const
|
|||||||
|
|
||||||
import kmk.handlers.stock as handlers
|
import kmk.handlers.stock as handlers
|
||||||
from kmk.consts import UnicodeMode
|
from kmk.consts import UnicodeMode
|
||||||
from kmk.key_validators import (
|
from kmk.key_validators import key_seq_sleep_validator, unicode_mode_key_validator
|
||||||
key_seq_sleep_validator,
|
|
||||||
tap_dance_key_validator,
|
|
||||||
unicode_mode_key_validator,
|
|
||||||
)
|
|
||||||
from kmk.types import AttrDict, UnicodeModeKeyMeta
|
from kmk.types import AttrDict, UnicodeModeKeyMeta
|
||||||
|
|
||||||
DEBUG_OUTPUT = False
|
DEBUG_OUTPUT = False
|
||||||
@ -167,13 +163,6 @@ class KeyAttrDict(AttrDict):
|
|||||||
names=('UC_MODE',),
|
names=('UC_MODE',),
|
||||||
on_press=handlers.uc_mode_pressed,
|
on_press=handlers.uc_mode_pressed,
|
||||||
)
|
)
|
||||||
elif key in ('TAP_DANCE', 'TD'):
|
|
||||||
make_argumented_key(
|
|
||||||
validator=tap_dance_key_validator,
|
|
||||||
names=('TAP_DANCE', 'TD'),
|
|
||||||
on_press=handlers.td_pressed,
|
|
||||||
on_release=handlers.td_released,
|
|
||||||
)
|
|
||||||
elif key in ('HID_SWITCH', 'HID'):
|
elif key in ('HID_SWITCH', 'HID'):
|
||||||
make_key(names=('HID_SWITCH', 'HID'), on_press=handlers.hid_switch)
|
make_key(names=('HID_SWITCH', 'HID'), on_press=handlers.hid_switch)
|
||||||
else:
|
else:
|
||||||
@ -417,7 +406,7 @@ class Key:
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Key(code={}, has_modifiers={})'.format(self.code, self.has_modifiers)
|
return 'Key(code={}, has_modifiers={})'.format(self.code, self.has_modifiers)
|
||||||
|
|
||||||
def on_press(self, state, coord_int, coord_raw):
|
def on_press(self, state, coord_int=None, coord_raw=None):
|
||||||
if hasattr(self, '_pre_press_handlers'):
|
if hasattr(self, '_pre_press_handlers'):
|
||||||
for fn in self._pre_press_handlers:
|
for fn in self._pre_press_handlers:
|
||||||
if not fn(self, state, KC, coord_int, coord_raw):
|
if not fn(self, state, KC, coord_int, coord_raw):
|
||||||
@ -431,7 +420,7 @@ class Key:
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def on_release(self, state, coord_int, coord_raw):
|
def on_release(self, state, coord_int=None, coord_raw=None):
|
||||||
if hasattr(self, '_pre_release_handlers'):
|
if hasattr(self, '_pre_release_handlers'):
|
||||||
for fn in self._pre_release_handlers:
|
for fn in self._pre_release_handlers:
|
||||||
if not fn(self, state, KC, coord_int, coord_raw):
|
if not fn(self, state, KC, coord_int, coord_raw):
|
||||||
|
@ -4,7 +4,6 @@ from kmk.consts import KMK_RELEASE, UnicodeMode
|
|||||||
from kmk.hid import BLEHID, USBHID, AbstractHID, HIDModes
|
from kmk.hid import BLEHID, USBHID, AbstractHID, HIDModes
|
||||||
from kmk.keys import KC
|
from kmk.keys import KC
|
||||||
from kmk.matrix import MatrixScanner, intify_coordinate
|
from kmk.matrix import MatrixScanner, intify_coordinate
|
||||||
from kmk.types import TapDanceKeyMeta
|
|
||||||
|
|
||||||
|
|
||||||
class Sandbox:
|
class Sandbox:
|
||||||
@ -29,7 +28,6 @@ class KMKKeyboard:
|
|||||||
uart_buffer = []
|
uart_buffer = []
|
||||||
|
|
||||||
unicode_mode = UnicodeMode.NOOP
|
unicode_mode = UnicodeMode.NOOP
|
||||||
tap_time = 300
|
|
||||||
|
|
||||||
modules = []
|
modules = []
|
||||||
extensions = []
|
extensions = []
|
||||||
@ -61,9 +59,6 @@ class KMKKeyboard:
|
|||||||
active_layers = [0]
|
active_layers = [0]
|
||||||
|
|
||||||
_timeouts = {}
|
_timeouts = {}
|
||||||
_tapping = False
|
|
||||||
_tap_dance_counts = {}
|
|
||||||
_tap_side_effects = {}
|
|
||||||
|
|
||||||
# on some M4 setups (such as klardotsh/klarank_feather_m4, CircuitPython
|
# on some M4 setups (such as klardotsh/klarank_feather_m4, CircuitPython
|
||||||
# 6.0rc1) this runs out of RAM every cycle and takes down the board. no
|
# 6.0rc1) this runs out of RAM every cycle and takes down the board. no
|
||||||
@ -76,23 +71,18 @@ class KMKKeyboard:
|
|||||||
'diode_orientation={} '
|
'diode_orientation={} '
|
||||||
'matrix_scanner={} '
|
'matrix_scanner={} '
|
||||||
'unicode_mode={} '
|
'unicode_mode={} '
|
||||||
'tap_time={} '
|
|
||||||
'_hid_helper={} '
|
'_hid_helper={} '
|
||||||
'keys_pressed={} '
|
'keys_pressed={} '
|
||||||
'coordkeys_pressed={} '
|
'coordkeys_pressed={} '
|
||||||
'hid_pending={} '
|
'hid_pending={} '
|
||||||
'active_layers={} '
|
'active_layers={} '
|
||||||
'timeouts={} '
|
'timeouts={} '
|
||||||
'tapping={} '
|
|
||||||
'tap_dance_counts={} '
|
|
||||||
'tap_side_effects={}'
|
|
||||||
')'
|
')'
|
||||||
).format(
|
).format(
|
||||||
self.debug_enabled,
|
self.debug_enabled,
|
||||||
self.diode_orientation,
|
self.diode_orientation,
|
||||||
self.matrix_scanner,
|
self.matrix_scanner,
|
||||||
self.unicode_mode,
|
self.unicode_mode,
|
||||||
self.tap_time,
|
|
||||||
self._hid_helper,
|
self._hid_helper,
|
||||||
# internal state
|
# internal state
|
||||||
self.keys_pressed,
|
self.keys_pressed,
|
||||||
@ -100,9 +90,6 @@ class KMKKeyboard:
|
|||||||
self.hid_pending,
|
self.hid_pending,
|
||||||
self.active_layers,
|
self.active_layers,
|
||||||
self._timeouts,
|
self._timeouts,
|
||||||
self._tapping,
|
|
||||||
self._tap_dance_counts,
|
|
||||||
self._tap_side_effects,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _print_debug_cycle(self, init=False):
|
def _print_debug_cycle(self, init=False):
|
||||||
@ -171,16 +158,22 @@ class KMKKeyboard:
|
|||||||
print('MatrixUndefinedCoordinate(col={} row={})'.format(col, row))
|
print('MatrixUndefinedCoordinate(col={} row={})'.format(col, row))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
return self.process_key(self.current_key, is_pressed, int_coord, (row, col))
|
for module in self.modules:
|
||||||
|
try:
|
||||||
|
if module.process_key(self, self.current_key, is_pressed) is None:
|
||||||
|
break
|
||||||
|
except Exception as err:
|
||||||
|
if self.debug_enabled:
|
||||||
|
print('Failed to run process_key function in module: ', err, module)
|
||||||
|
else:
|
||||||
|
self.process_key(self.current_key, is_pressed, int_coord, (row, col))
|
||||||
|
return self
|
||||||
|
|
||||||
def process_key(self, key, is_pressed, coord_int=None, coord_raw=None):
|
def process_key(self, key, is_pressed, coord_int=None, coord_raw=None):
|
||||||
if self._tapping and isinstance(key.meta, TapDanceKeyMeta):
|
if is_pressed:
|
||||||
self._process_tap_dance(key, is_pressed)
|
key.on_press(self, coord_int, coord_raw)
|
||||||
else:
|
else:
|
||||||
if is_pressed:
|
key.on_release(self, coord_int, coord_raw)
|
||||||
key.on_press(self, coord_int, coord_raw)
|
|
||||||
else:
|
|
||||||
key.on_release(self, coord_int, coord_raw)
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@ -199,85 +192,6 @@ class KMKKeyboard:
|
|||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _process_tap_dance(self, changed_key, is_pressed):
|
|
||||||
if is_pressed:
|
|
||||||
if not isinstance(changed_key.meta, TapDanceKeyMeta):
|
|
||||||
# If we get here, changed_key is not a TapDanceKey and thus
|
|
||||||
# the user kept typing elsewhere (presumably). End ALL of the
|
|
||||||
# currently outstanding tap dance runs.
|
|
||||||
for k, v in self._tap_dance_counts.items():
|
|
||||||
if v:
|
|
||||||
self._end_tap_dance(k)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
if (
|
|
||||||
changed_key not in self._tap_dance_counts
|
|
||||||
or not self._tap_dance_counts[changed_key]
|
|
||||||
):
|
|
||||||
self._tap_dance_counts[changed_key] = 1
|
|
||||||
self._tapping = True
|
|
||||||
else:
|
|
||||||
self.cancel_timeout(self._tap_timeout)
|
|
||||||
self._tap_dance_counts[changed_key] += 1
|
|
||||||
|
|
||||||
if changed_key not in self._tap_side_effects:
|
|
||||||
self._tap_side_effects[changed_key] = None
|
|
||||||
|
|
||||||
self._tap_timeout = self.set_timeout(
|
|
||||||
self.tap_time, lambda: self._end_tap_dance(changed_key, hold=True)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if not isinstance(changed_key.meta, TapDanceKeyMeta):
|
|
||||||
return self
|
|
||||||
|
|
||||||
has_side_effects = self._tap_side_effects[changed_key] is not None
|
|
||||||
hit_max_defined_taps = self._tap_dance_counts[changed_key] == len(
|
|
||||||
changed_key.meta.codes
|
|
||||||
)
|
|
||||||
|
|
||||||
if has_side_effects or hit_max_defined_taps:
|
|
||||||
self._end_tap_dance(changed_key)
|
|
||||||
|
|
||||||
self.cancel_timeout(self._tap_timeout)
|
|
||||||
self._tap_timeout = self.set_timeout(
|
|
||||||
self.tap_time, lambda: self._end_tap_dance(changed_key)
|
|
||||||
)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _end_tap_dance(self, td_key, hold=False):
|
|
||||||
v = self._tap_dance_counts[td_key] - 1
|
|
||||||
|
|
||||||
if v < 0:
|
|
||||||
return self
|
|
||||||
|
|
||||||
if td_key in self.keys_pressed:
|
|
||||||
key_to_press = td_key.meta.codes[v]
|
|
||||||
self.add_key(key_to_press)
|
|
||||||
self._tap_side_effects[td_key] = key_to_press
|
|
||||||
elif self._tap_side_effects[td_key]:
|
|
||||||
self.remove_key(self._tap_side_effects[td_key])
|
|
||||||
self._tap_side_effects[td_key] = None
|
|
||||||
self._cleanup_tap_dance(td_key)
|
|
||||||
elif hold is False:
|
|
||||||
if td_key.meta.codes[v] in self.keys_pressed:
|
|
||||||
self.remove_key(td_key.meta.codes[v])
|
|
||||||
else:
|
|
||||||
self.tap_key(td_key.meta.codes[v])
|
|
||||||
self._cleanup_tap_dance(td_key)
|
|
||||||
else:
|
|
||||||
self.add_key(td_key.meta.codes[v])
|
|
||||||
|
|
||||||
self.hid_pending = True
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _cleanup_tap_dance(self, td_key):
|
|
||||||
self._tap_dance_counts[td_key] = 0
|
|
||||||
self._tapping = any(count > 0 for count in self._tap_dance_counts.values())
|
|
||||||
return self
|
|
||||||
|
|
||||||
def set_timeout(self, after_ticks, callback):
|
def set_timeout(self, after_ticks, callback):
|
||||||
if after_ticks is False:
|
if after_ticks is False:
|
||||||
# We allow passing False as an implicit "run this on the next process timeouts cycle"
|
# We allow passing False as an implicit "run this on the next process timeouts cycle"
|
||||||
|
@ -38,3 +38,6 @@ class Module:
|
|||||||
|
|
||||||
def on_powersave_disable(self, keyboard):
|
def on_powersave_disable(self, keyboard):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def process_key(self, keyboard, key, is_pressed):
|
||||||
|
return key
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from micropython import const
|
from micropython import const
|
||||||
|
|
||||||
from kmk.modules import Module
|
from kmk.modules import Module
|
||||||
|
from kmk.types import ModTapKeyMeta
|
||||||
|
|
||||||
|
|
||||||
class ActivationType:
|
class ActivationType:
|
||||||
@ -18,6 +19,8 @@ class HoldTapKeyState:
|
|||||||
|
|
||||||
|
|
||||||
class HoldTap(Module):
|
class HoldTap(Module):
|
||||||
|
tap_time = 300
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.key_states = {}
|
self.key_states = {}
|
||||||
|
|
||||||
@ -28,8 +31,12 @@ class HoldTap(Module):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def after_matrix_scan(self, keyboard):
|
def after_matrix_scan(self, keyboard):
|
||||||
|
return
|
||||||
|
|
||||||
|
def process_key(self, keyboard, key, is_pressed):
|
||||||
'''Before other key down decide to send tap kc down.'''
|
'''Before other key down decide to send tap kc down.'''
|
||||||
if self.matrix_detected_press(keyboard):
|
current_key = key
|
||||||
|
if is_pressed and not isinstance(key.meta, ModTapKeyMeta):
|
||||||
for key, state in self.key_states.items():
|
for key, state in self.key_states.items():
|
||||||
if state.activated == ActivationType.NOT_ACTIVATED:
|
if state.activated == ActivationType.NOT_ACTIVATED:
|
||||||
# press tap because interrupted by other key
|
# press tap because interrupted by other key
|
||||||
@ -39,7 +46,7 @@ class HoldTap(Module):
|
|||||||
)
|
)
|
||||||
if keyboard.hid_pending:
|
if keyboard.hid_pending:
|
||||||
keyboard._send_hid()
|
keyboard._send_hid()
|
||||||
return
|
return current_key
|
||||||
|
|
||||||
def before_hid_send(self, keyboard):
|
def before_hid_send(self, keyboard):
|
||||||
return
|
return
|
||||||
@ -53,16 +60,10 @@ class HoldTap(Module):
|
|||||||
def on_powersave_disable(self, keyboard):
|
def on_powersave_disable(self, keyboard):
|
||||||
return
|
return
|
||||||
|
|
||||||
def matrix_detected_press(self, keyboard):
|
|
||||||
return (keyboard.matrix_update is not None and keyboard.matrix_update[2]) or (
|
|
||||||
keyboard.secondary_matrix_update is not None
|
|
||||||
and keyboard.secondary_matrix_update[2]
|
|
||||||
)
|
|
||||||
|
|
||||||
def ht_pressed(self, key, keyboard, *args, **kwargs):
|
def ht_pressed(self, key, keyboard, *args, **kwargs):
|
||||||
'''Do nothing yet, action resolves when key is released, timer expires or other key is pressed.'''
|
'''Do nothing yet, action resolves when key is released, timer expires or other key is pressed.'''
|
||||||
timeout_key = keyboard.set_timeout(
|
timeout_key = keyboard.set_timeout(
|
||||||
keyboard.tap_time,
|
self.tap_time,
|
||||||
lambda: self.on_tap_time_expired(key, keyboard, *args, **kwargs),
|
lambda: self.on_tap_time_expired(key, keyboard, *args, **kwargs),
|
||||||
)
|
)
|
||||||
self.key_states[key] = HoldTapKeyState(timeout_key, *args, **kwargs)
|
self.key_states[key] = HoldTapKeyState(timeout_key, *args, **kwargs)
|
||||||
|
124
kmk/modules/tapdance.py
Normal file
124
kmk/modules/tapdance.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
from kmk.key_validators import tap_dance_key_validator
|
||||||
|
from kmk.keys import make_argumented_key
|
||||||
|
from kmk.modules import Module
|
||||||
|
from kmk.types import TapDanceKeyMeta
|
||||||
|
|
||||||
|
|
||||||
|
class TapDance(Module):
|
||||||
|
# User-configurable
|
||||||
|
tap_time = 300
|
||||||
|
|
||||||
|
# Internal State
|
||||||
|
_tapping = False
|
||||||
|
_tap_dance_counts = {}
|
||||||
|
_tap_timeout = None
|
||||||
|
_tap_side_effects = {}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
make_argumented_key(
|
||||||
|
validator=tap_dance_key_validator,
|
||||||
|
names=('TAP_DANCE', 'TD'),
|
||||||
|
on_press=self.td_pressed,
|
||||||
|
on_release=self.td_released,
|
||||||
|
)
|
||||||
|
|
||||||
|
def during_bootup(self, keyboard):
|
||||||
|
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):
|
||||||
|
if self._tapping and is_pressed and not isinstance(key.meta, TapDanceKeyMeta):
|
||||||
|
for k, v in self._tap_dance_counts.items():
|
||||||
|
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, None, None)
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return key
|
||||||
|
|
||||||
|
def td_pressed(self, key, keyboard, *args, **kwargs):
|
||||||
|
if key not in self._tap_dance_counts or not self._tap_dance_counts[key]:
|
||||||
|
self._tap_dance_counts[key] = 1
|
||||||
|
self._tapping = True
|
||||||
|
else:
|
||||||
|
keyboard.cancel_timeout(self._tap_timeout)
|
||||||
|
self._tap_dance_counts[key] += 1
|
||||||
|
|
||||||
|
if key not in self._tap_side_effects:
|
||||||
|
self._tap_side_effects[key] = None
|
||||||
|
|
||||||
|
self._tap_timeout = keyboard.set_timeout(
|
||||||
|
self.tap_time, lambda: self._end_tap_dance(key, keyboard, hold=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def td_released(self, key, keyboard, *args, **kwargs):
|
||||||
|
has_side_effects = self._tap_side_effects[key] is not None
|
||||||
|
hit_max_defined_taps = self._tap_dance_counts[key] == len(key.meta.codes)
|
||||||
|
|
||||||
|
keyboard.cancel_timeout(self._tap_timeout)
|
||||||
|
if has_side_effects or hit_max_defined_taps:
|
||||||
|
self._end_tap_dance(key, keyboard)
|
||||||
|
else:
|
||||||
|
self._tap_timeout = keyboard.set_timeout(
|
||||||
|
self.tap_time, lambda: self._end_tap_dance(key, keyboard)
|
||||||
|
)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _end_tap_dance(self, key, keyboard, hold=False):
|
||||||
|
v = self._tap_dance_counts[key] - 1
|
||||||
|
|
||||||
|
if v < 0:
|
||||||
|
return self
|
||||||
|
|
||||||
|
if key in keyboard.keys_pressed:
|
||||||
|
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
|
Loading…
x
Reference in New Issue
Block a user