From 68dc38e7d35bf415304bb73f4d776d802c705daf Mon Sep 17 00:00:00 2001 From: xs5871 Date: Wed, 12 Jun 2024 10:16:26 +0000 Subject: [PATCH] Add KeyboardKey class to distinguish from internal Keys --- docs/en/keys.md | 8 +- kmk/extensions/display/__init__.py | 13 +--- kmk/extensions/peg_rgb_matrix.py | 13 +--- kmk/extensions/rgb.py | 74 ++++-------------- kmk/handlers/stock.py | 18 +---- kmk/hid.py | 11 +-- kmk/keys.py | 119 +++++++++++++---------------- kmk/modules/autoshift.py | 5 +- kmk/modules/capsword.py | 7 +- kmk/modules/combos.py | 7 +- kmk/modules/power.py | 13 +--- tests/test_kmk_keys.py | 6 +- 12 files changed, 94 insertions(+), 200 deletions(-) diff --git a/docs/en/keys.md b/docs/en/keys.md index 83f807bf4..65b340201 100644 --- a/docs/en/keys.md +++ b/docs/en/keys.md @@ -27,10 +27,10 @@ The next few steps are the interesting part, but to understand them, we need to understand a bit about what a `Key` object is (found in [`kmk/keys.py`](/kmk/keys.py)). `Key` objects have a few core pieces of information: -- Their `code`, which can be any integer. Integers below - `FIRST_KMK_INTERNAL_KEY` are sent through to the HID stack (and thus the - computer, which will translate that integer to something meaningful - for - example, `code=4` becomes `a` on a US QWERTY/Dvorak keyboard). +- Their `code`, which can be any integer or None. Integers sent through to the + HID stack (and thus the computer, which will translate that integer to + something meaningful - for example, `code=4` becomes `a` on a US QWERTY/Dvorak + keyboard). - Handlers for "press" (sometimes known as "keydown") and "release" (sometimes known as "keyup") events. KMK provides handlers for standard keyboard diff --git a/kmk/extensions/display/__init__.py b/kmk/extensions/display/__init__.py index a1f19f190..518baa5be 100644 --- a/kmk/extensions/display/__init__.py +++ b/kmk/extensions/display/__init__.py @@ -5,7 +5,6 @@ from adafruit_display_text import label from kmk.extensions import Extension -from kmk.handlers.stock import passthrough as handler_passthrough from kmk.keys import make_key from kmk.kmktime import PeriodicTimer, ticks_diff from kmk.modules.split import Split, SplitSide @@ -147,16 +146,8 @@ def __init__( self.dim_period = PeriodicTimer(50) self.split_side = None - make_key( - names=('DIS_BRI',), - on_press=self.display_brightness_increase, - on_release=handler_passthrough, - ) - make_key( - names=('DIS_BRD',), - on_press=self.display_brightness_decrease, - on_release=handler_passthrough, - ) + make_key(names=('DIS_BRI',), on_press=self.display_brightness_increase) + make_key(names=('DIS_BRD',), on_press=self.display_brightness_decrease) def render(self, layer): splash = displayio.Group() diff --git a/kmk/extensions/peg_rgb_matrix.py b/kmk/extensions/peg_rgb_matrix.py index 77a2825ca..1e81d635e 100644 --- a/kmk/extensions/peg_rgb_matrix.py +++ b/kmk/extensions/peg_rgb_matrix.py @@ -3,7 +3,6 @@ from storage import getmount from kmk.extensions import Extension -from kmk.handlers.stock import passthrough as handler_passthrough from kmk.keys import make_key @@ -69,15 +68,9 @@ def __init__( else: self.ledDisplay = ledDisplay - make_key( - names=('RGB_TOG',), on_press=self._rgb_tog, on_release=handler_passthrough - ) - make_key( - names=('RGB_BRI',), on_press=self._rgb_bri, on_release=handler_passthrough - ) - make_key( - names=('RGB_BRD',), on_press=self._rgb_brd, on_release=handler_passthrough - ) + make_key(names=('RGB_TOG',), on_press=self._rgb_tog) + make_key(names=('RGB_BRI',), on_press=self._rgb_bri) + make_key(names=('RGB_BRD',), on_press=self._rgb_brd) def _rgb_tog(self, *args, **kwargs): if self.enable: diff --git a/kmk/extensions/rgb.py b/kmk/extensions/rgb.py index 93054ef8d..cb423d7ec 100644 --- a/kmk/extensions/rgb.py +++ b/kmk/extensions/rgb.py @@ -2,7 +2,6 @@ from math import e, exp, pi, sin from kmk.extensions import Extension -from kmk.handlers.stock import passthrough as handler_passthrough from kmk.keys import make_key from kmk.scheduler import create_task from kmk.utils import Debug, clamp @@ -135,68 +134,25 @@ def __init__( self._substep = 0 - make_key( - names=('RGB_TOG',), on_press=self._rgb_tog, on_release=handler_passthrough - ) - make_key( - names=('RGB_HUI',), on_press=self._rgb_hui, on_release=handler_passthrough - ) - make_key( - names=('RGB_HUD',), on_press=self._rgb_hud, on_release=handler_passthrough - ) - make_key( - names=('RGB_SAI',), on_press=self._rgb_sai, on_release=handler_passthrough - ) - make_key( - names=('RGB_SAD',), on_press=self._rgb_sad, on_release=handler_passthrough - ) - make_key( - names=('RGB_VAI',), on_press=self._rgb_vai, on_release=handler_passthrough - ) - make_key( - names=('RGB_VAD',), on_press=self._rgb_vad, on_release=handler_passthrough - ) - make_key( - names=('RGB_ANI',), on_press=self._rgb_ani, on_release=handler_passthrough - ) - make_key( - names=('RGB_AND',), on_press=self._rgb_and, on_release=handler_passthrough - ) - make_key( - names=('RGB_MODE_PLAIN', 'RGB_M_P'), - on_press=self._rgb_mode_static, - on_release=handler_passthrough, - ) - make_key( - names=('RGB_MODE_BREATHE', 'RGB_M_B'), - on_press=self._rgb_mode_breathe, - on_release=handler_passthrough, - ) - make_key( - names=('RGB_MODE_RAINBOW', 'RGB_M_R'), - on_press=self._rgb_mode_rainbow, - on_release=handler_passthrough, - ) + make_key(names=('RGB_TOG',), on_press=self._rgb_to) + make_key(names=('RGB_HUI',), on_press=self._rgb_hui) + make_key(names=('RGB_HUD',), on_press=self._rgb_hud) + make_key(names=('RGB_SAI',), on_press=self._rgb_sai) + make_key(names=('RGB_SAD',), on_press=self._rgb_sad) + make_key(names=('RGB_VAI',), on_press=self._rgb_vai) + make_key(names=('RGB_VAD',), on_press=self._rgb_vad) + make_key(names=('RGB_ANI',), on_press=self._rgb_ani) + make_key(names=('RGB_AND',), on_press=self._rgb_and) + make_key(names=('RGB_MODE_PLAIN', 'RGB_M_P'), on_press=self._rgb_mode_static) + make_key(names=('RGB_MODE_BREATHE', 'RGB_M_B'), on_press=self._rgb_mode_breathe) + make_key(names=('RGB_MODE_RAINBOW', 'RGB_M_R'), on_press=self._rgb_mode_rainbow) make_key( names=('RGB_MODE_BREATHE_RAINBOW', 'RGB_M_BR'), on_press=self._rgb_mode_breathe_rainbow, - on_release=handler_passthrough, - ) - make_key( - names=('RGB_MODE_SWIRL', 'RGB_M_S'), - on_press=self._rgb_mode_swirl, - on_release=handler_passthrough, - ) - make_key( - names=('RGB_MODE_KNIGHT', 'RGB_M_K'), - on_press=self._rgb_mode_knight, - on_release=handler_passthrough, - ) - make_key( - names=('RGB_RESET', 'RGB_RST'), - on_press=self._rgb_reset, - on_release=handler_passthrough, ) + make_key(names=('RGB_MODE_SWIRL', 'RGB_M_S'), on_press=self._rgb_mode_swirl) + make_key(names=('RGB_MODE_KNIGHT', 'RGB_M_K'), on_press=self._rgb_mode_knight) + make_key(names=('RGB_RESET', 'RGB_RST'), on_press=self._rgb_reset) def on_runtime_enable(self, sandbox): return diff --git a/kmk/handlers/stock.py b/kmk/handlers/stock.py index 89bdc33e3..6921908c1 100644 --- a/kmk/handlers/stock.py +++ b/kmk/handlers/stock.py @@ -5,21 +5,6 @@ def passthrough(key, keyboard, *args, **kwargs): return keyboard -def default_pressed(key, keyboard, KC, coord_int=None, *args, **kwargs): - keyboard.hid_pending = True - - keyboard.keys_pressed.add(key) - - return keyboard - - -def default_released(key, keyboard, KC, coord_int=None, *args, **kwargs): # NOQA - keyboard.hid_pending = True - keyboard.keys_pressed.discard(key) - - return keyboard - - def reset(*args, **kwargs): import microcontroller @@ -143,4 +128,5 @@ def any_pressed(key, keyboard, *args, **kwargs): from random import randint key.code = randint(4, 56) - default_pressed(key, keyboard, *args, **kwargs) + keyboard.keys_pressed.add(key) + keyboard.hid_pending = True diff --git a/kmk/hid.py b/kmk/hid.py index 1430a28a1..7d65409a2 100644 --- a/kmk/hid.py +++ b/kmk/hid.py @@ -4,7 +4,7 @@ from storage import getmount -from kmk.keys import FIRST_KMK_INTERNAL_KEY, ConsumerKey, ModifierKey, MouseKey +from kmk.keys import ConsumerKey, KeyboardKey, ModifierKey, MouseKey from kmk.utils import Debug, clamp try: @@ -116,17 +116,14 @@ def create_report(self, keys_pressed, axes): self.clear_all() for key in keys_pressed: - if key.code >= FIRST_KMK_INTERNAL_KEY: - continue - - if isinstance(key, ModifierKey): + if isinstance(key, KeyboardKey): + self.add_key(key) + elif isinstance(key, ModifierKey): self.add_modifier(key) elif isinstance(key, ConsumerKey): self.add_cc(key) elif isinstance(key, MouseKey): self.add_pd(key) - else: - self.add_key(key) for axis in axes: self.move_axis(axis) diff --git a/kmk/keys.py b/kmk/keys.py index 27c18a7b3..2fe304244 100644 --- a/kmk/keys.py +++ b/kmk/keys.py @@ -3,8 +3,6 @@ except ImportError: pass -from micropython import const - import kmk.handlers.stock as handlers from kmk.utils import Debug @@ -13,9 +11,6 @@ Key = object -FIRST_KMK_INTERNAL_KEY = const(1000) -NEXT_AVAILABLE_KEY = 1000 - ALL_ALPHAS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ALL_NUMBERS = '1234567890' # since KC.1 isn't valid Python, alias to KC.N1 @@ -106,6 +101,7 @@ def maybe_make_alpha_key(candidate: str) -> Optional[Key]: return make_key( code=4 + ALL_ALPHAS.index(candidate_upper), names=(candidate_upper, candidate.lower()), + key_type=KeyboardKey, ) @@ -119,6 +115,7 @@ def maybe_make_numeric_key(candidate: str) -> Optional[Key]: return make_key( code=30 + offset, names=(ALL_NUMBERS[offset], ALL_NUMBER_ALIASES[offset]), + key_type=KeyboardKey, ) @@ -165,7 +162,7 @@ def maybe_make_more_ascii(candidate: str) -> Optional[Key]: for code, names in codes: if candidate in names: - return make_key(code=code, names=names) + return make_key(code=code, names=names, key_type=KeyboardKey) def maybe_make_fn_key(candidate: str) -> Optional[Key]: @@ -198,7 +195,7 @@ def maybe_make_fn_key(candidate: str) -> Optional[Key]: for code, names in codes: if candidate in names: - return make_key(code=code, names=names) + return make_key(code=code, names=names, key_type=KeyboardKey) def maybe_make_navlock_key(candidate: str) -> Optional[Key]: @@ -227,7 +224,7 @@ def maybe_make_navlock_key(candidate: str) -> Optional[Key]: for code, names in codes: if candidate in names: - return make_key(code=code, names=names) + return make_key(code=code, names=names, key_type=KeyboardKey) def maybe_make_numpad_key(candidate: str) -> Optional[Key]: @@ -256,7 +253,7 @@ def maybe_make_numpad_key(candidate: str) -> Optional[Key]: for code, names in codes: if candidate in names: - return make_key(code=code, names=names) + return make_key(code=code, names=names, key_type=KeyboardKey) def maybe_make_shifted_key(candidate: str) -> Optional[Key]: @@ -318,7 +315,7 @@ def maybe_make_international_key(candidate: str) -> Optional[Key]: for code, names in codes: if candidate in names: - return make_key(code=code, names=names) + return make_key(code=code, names=names, key_type=KeyboardKey) def maybe_make_firmware_key(candidate: str) -> Optional[Key]: @@ -441,28 +438,20 @@ def __getitem__(self, name: str): class Key: + '''Generic Key class with assignable handlers.''' + def __init__( self, - code: int, - on_press: Callable[ - [object, Key, Keyboard, ...], None - ] = handlers.default_pressed, - on_release: Callable[ - [object, Key, Keyboard, ...], None - ] = handlers.default_released, + on_press: Callable[[object, Key, Keyboard, ...], None] = handlers.passthrough, + on_release: Callable[[object, Key, Keyboard, ...], None] = handlers.passthrough, meta: object = object(), ): - self.code = code - + self.meta = meta self._on_press = on_press self._on_release = on_release - self.meta = meta - - def __call__(self) -> Key: - return self def __repr__(self): - return f'Key(code={self.code})' + return self.__class__.__name__ def on_press(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None: self._on_press(self, keyboard, KC, coord_int) @@ -471,7 +460,31 @@ def on_release(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> Non self._on_release(self, keyboard, KC, coord_int) -class ModifierKey(Key): +class _DefaultKey(Key): + '''Meta class implementing handlers for Keys with HID codes.''' + + meta = None + + def __init__(self, code: Optional[int] = None): + self.code = code + + def __repr__(self): + return super().__repr__() + '(code=' + str(self.code) + ')' + + def on_press(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None: + keyboard.hid_pending = True + keyboard.keys_pressed.add(self) + + def on_release(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None: + keyboard.hid_pending = True + keyboard.keys_pressed.discard(self) + + +class KeyboardKey(_DefaultKey): + pass + + +class ModifierKey(_DefaultKey): def __call__(self, key: Key) -> Key: # don't duplicate when applying the same modifier twice if ( @@ -484,9 +497,6 @@ def __call__(self, key: Key) -> Key: return ModifiedKey(key, self) - def __repr__(self): - return f'ModifierKey(code={self.code})' - class ModifiedKey(Key): meta = None @@ -495,7 +505,7 @@ class ModifiedKey(Key): def __init__(self, code: [Key, int], modifier: [ModifierKey]): # generate from code by maybe_make_shifted_key if isinstance(code, int): - key = Key(code=code) + key = KeyboardKey(code=code) else: key = code @@ -517,7 +527,8 @@ def on_release(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> Non def __repr__(self): return ( - 'ModifiedKey(key=' + super().__repr__() + + '(key=' + str(self.key) + ', modifier=' + str(self.modifier) @@ -525,11 +536,11 @@ def __repr__(self): ) -class ConsumerKey(Key): +class ConsumerKey(_DefaultKey): pass -class MouseKey(Key): +class MouseKey(_DefaultKey): pass @@ -544,7 +555,7 @@ def make_key( If a code is not specified, the key is assumed to be a custom internal key to be handled in a state callback rather than - sent directly to the OS. These codes will autoincrement. + sent directly to the OS. Names are globally unique. If a later key is created with the same name as an existing entry in `KC`, it will overwrite @@ -555,18 +566,10 @@ def make_key( All **kwargs are passed to the Key constructor ''' - global NEXT_AVAILABLE_KEY - if code is None: - code = NEXT_AVAILABLE_KEY - NEXT_AVAILABLE_KEY += 1 - elif code >= FIRST_KMK_INTERNAL_KEY: - # Try to ensure future auto-generated internal keycodes won't - # be overridden by continuing to +1 the sequence from the provided - # code - NEXT_AVAILABLE_KEY = max(NEXT_AVAILABLE_KEY, code + 1) - - key = key_type(code=code, **kwargs) + key = key_type(**kwargs) + else: + key = key_type(code, **kwargs) for name in names: KC[name] = key @@ -582,31 +585,13 @@ def make_argumented_key( *constructor_args, **constructor_kwargs, ) -> Key: - global NEXT_AVAILABLE_KEY def _argumented_key(*user_args, **user_kwargs) -> Key: - global NEXT_AVAILABLE_KEY - - meta = validator(*user_args, **user_kwargs) - - if meta: - key = Key( - NEXT_AVAILABLE_KEY, - *constructor_args, - meta=meta, - **constructor_kwargs, - ) - - NEXT_AVAILABLE_KEY += 1 - - return key - - else: - raise ValueError( - 'Argumented key validator failed for unknown reasons. ' - "This may not be the keymap's fault, as a more specific error " - 'should have been raised.' - ) + return Key( + *constructor_args, + meta=validator(*user_args, **user_kwargs), + **constructor_kwargs, + ) for name in names: KC[name] = _argumented_key diff --git a/kmk/modules/autoshift.py b/kmk/modules/autoshift.py index 6a53e785c..fc48f0f83 100644 --- a/kmk/modules/autoshift.py +++ b/kmk/modules/autoshift.py @@ -1,4 +1,4 @@ -from kmk.keys import KC, Key +from kmk.keys import KC, KeyboardKey from kmk.modules import Module from kmk.scheduler import cancel_task, create_task from kmk.utils import Debug @@ -41,8 +41,7 @@ def process_key(self, keyboard, key, is_pressed, int_coord): if ( is_pressed and not self._key - and isinstance(key, Key) - and key.code + and isinstance(key, KeyboardKey) and KC.A.code <= key.code <= KC.Z.code ): create_task(self._task, after_ms=self.tap_time) diff --git a/kmk/modules/capsword.py b/kmk/modules/capsword.py index 8c94a083d..b264986e1 100644 --- a/kmk/modules/capsword.py +++ b/kmk/modules/capsword.py @@ -1,4 +1,4 @@ -from kmk.keys import FIRST_KMK_INTERNAL_KEY, KC, ModifierKey, make_key +from kmk.keys import KC, KeyboardKey, ModifierKey, make_key from kmk.modules import Module @@ -38,11 +38,10 @@ def process_key(self, keyboard, key, is_pressed, int_coord): continue_cw = True keyboard.process_key(KC.LSFT, is_pressed) elif ( - key.code in self._numbers + not isinstance(key, KeyboardKey) or isinstance(key, ModifierKey) + or key.code in self._numbers or key in self.keys_ignored - or key.code - >= FIRST_KMK_INTERNAL_KEY # user defined keys are also ignored ): continue_cw = True # requests and cancels existing timeouts diff --git a/kmk/modules/combos.py b/kmk/modules/combos.py index 2adf348bb..be70e37c5 100644 --- a/kmk/modules/combos.py +++ b/kmk/modules/combos.py @@ -4,7 +4,6 @@ pass from micropython import const -import kmk.handlers.stock as handlers from kmk.keys import Key, make_key from kmk.kmk_keyboard import KMKKeyboard from kmk.modules import Module @@ -109,11 +108,7 @@ def __init__(self, combos=[]): self.combos = combos self._key_buffer = [] - make_key( - names=('LEADER', 'LDR'), - on_press=handlers.passthrough, - on_release=handlers.passthrough, - ) + make_key(names=('LEADER', 'LDR')) def during_bootup(self, keyboard): self.reset(keyboard) diff --git a/kmk/modules/power.py b/kmk/modules/power.py index 13d82d982..07eca21c5 100644 --- a/kmk/modules/power.py +++ b/kmk/modules/power.py @@ -4,7 +4,6 @@ from time import sleep -from kmk.handlers.stock import passthrough as handler_passthrough from kmk.keys import make_key from kmk.kmktime import check_deadline from kmk.modules import Module @@ -21,15 +20,9 @@ def __init__(self, powersave_pin=None): self._i2c_deinit_count = 0 self._loopcounter = 0 - make_key( - names=('PS_TOG',), on_press=self._ps_tog, on_release=handler_passthrough - ) - make_key( - names=('PS_ON',), on_press=self._ps_enable, on_release=handler_passthrough - ) - make_key( - names=('PS_OFF',), on_press=self._ps_disable, on_release=handler_passthrough - ) + make_key(names=('PS_TOG',), on_press=self._ps_tog) + make_key(names=('PS_ON',), on_press=self._ps_enable) + make_key(names=('PS_OFF',), on_press=self._ps_disable) def __repr__(self): return f'Power({self._to_dict()})' diff --git a/tests/test_kmk_keys.py b/tests/test_kmk_keys.py index aad528049..b4d339445 100644 --- a/tests/test_kmk_keys.py +++ b/tests/test_kmk_keys.py @@ -1,6 +1,6 @@ import unittest -from kmk.keys import KC, Key, ModifiedKey, ModifierKey, make_key +from kmk.keys import KC, Key, KeyboardKey, ModifiedKey, ModifierKey, make_key from tests.keyboard_test import KeyboardTest @@ -250,8 +250,8 @@ def setUp(self): KC.clear() def test_make_key_new_instance(self): - key1 = make_key(code=1) - key2 = make_key(code=1) + key1 = make_key(code=1, key_type=KeyboardKey) + key2 = make_key(code=1, key_type=KeyboardKey) assert key1 is not key2 assert key1.code == key2.code