From 63735f5187ae6010641b2ae1fc6823db3e4b89b1 Mon Sep 17 00:00:00 2001 From: xs5871 Date: Sun, 9 Jun 2024 09:35:44 +0000 Subject: [PATCH 1/3] Refactor key type assignment --- kmk/extensions/media_keys.py | 29 ++++++++--------- kmk/keys.py | 40 ++--------------------- kmk/modules/mouse_keys.py | 63 +++++++++++------------------------- 3 files changed, 35 insertions(+), 97 deletions(-) diff --git a/kmk/extensions/media_keys.py b/kmk/extensions/media_keys.py index 309a00a20..1d56029f3 100644 --- a/kmk/extensions/media_keys.py +++ b/kmk/extensions/media_keys.py @@ -1,5 +1,5 @@ from kmk.extensions import Extension -from kmk.keys import make_consumer_key +from kmk.keys import ConsumerKey, make_key class MediaKeys(Extension): @@ -14,20 +14,19 @@ def __init__(self): # support PC media keys, so I don't know how much value we would get out of # adding the old Apple-specific consumer codes, but again, PRs welcome if the # lack of them impacts you. - make_consumer_key(code=226, names=('AUDIO_MUTE', 'MUTE')) # 0xE2 - make_consumer_key(code=233, names=('AUDIO_VOL_UP', 'VOLU')) # 0xE9 - make_consumer_key(code=234, names=('AUDIO_VOL_DOWN', 'VOLD')) # 0xEA - make_consumer_key(code=111, names=('BRIGHTNESS_UP', 'BRIU')) # 0x6F - make_consumer_key(code=112, names=('BRIGHTNESS_DOWN', 'BRID')) # 0x70 - make_consumer_key(code=181, names=('MEDIA_NEXT_TRACK', 'MNXT')) # 0xB5 - make_consumer_key(code=182, names=('MEDIA_PREV_TRACK', 'MPRV')) # 0xB6 - make_consumer_key(code=183, names=('MEDIA_STOP', 'MSTP')) # 0xB7 - make_consumer_key( - code=205, names=('MEDIA_PLAY_PAUSE', 'MPLY') - ) # 0xCD (this may not be right) - make_consumer_key(code=184, names=('MEDIA_EJECT', 'EJCT')) # 0xB8 - make_consumer_key(code=179, names=('MEDIA_FAST_FORWARD', 'MFFD')) # 0xB3 - make_consumer_key(code=180, names=('MEDIA_REWIND', 'MRWD')) # 0xB4 + + make_key(code=0xE2, names=('AUDIO_MUTE', 'MUTE'), key_type=ConsumerKey) + make_key(code=0xE9, names=('AUDIO_VOL_UP', 'VOLU'), key_type=ConsumerKey) + make_key(code=0xEA, names=('AUDIO_VOL_DOWN', 'VOLD'), key_type=ConsumerKey) + make_key(code=0x6F, names=('BRIGHTNESS_UP', 'BRIU'), key_type=ConsumerKey) + make_key(code=0x70, names=('BRIGHTNESS_DOWN', 'BRID'), key_type=ConsumerKey) + make_key(code=0xB5, names=('MEDIA_NEXT_TRACK', 'MNXT'), key_type=ConsumerKey) + make_key(code=0xB6, names=('MEDIA_PREV_TRACK', 'MPRV'), key_type=ConsumerKey) + make_key(code=0xB7, names=('MEDIA_STOP', 'MSTP'), key_type=ConsumerKey) + make_key(code=0xCD, names=('MEDIA_PLAY_PAUSE', 'MPLY'), key_type=ConsumerKey) + make_key(code=0xB8, names=('MEDIA_EJECT', 'EJCT'), key_type=ConsumerKey) + make_key(code=0xB3, names=('MEDIA_FAST_FORWARD', 'MFFD'), key_type=ConsumerKey) + make_key(code=0xB4, names=('MEDIA_REWIND', 'MRWD'), key_type=ConsumerKey) def on_runtime_enable(self, sandbox): return diff --git a/kmk/keys.py b/kmk/keys.py index a256a8bc0..97dc55b28 100644 --- a/kmk/keys.py +++ b/kmk/keys.py @@ -13,13 +13,6 @@ Key = object -class KeyType: - SIMPLE = const(0) - MODIFIER = const(1) - CONSUMER = const(2) - MOUSE = const(3) - - FIRST_KMK_INTERNAL_KEY = const(1000) NEXT_AVAILABLE_KEY = 1000 @@ -147,7 +140,7 @@ def maybe_make_mod_key(candidate: str) -> Optional[Key]: for code, names in mods: if candidate in names: - return make_key(code=code, names=names, type=KeyType.MODIFIER) + return make_key(code=code, names=names, key_type=ModifierKey) def maybe_make_more_ascii(candidate: str) -> Optional[Key]: @@ -530,7 +523,7 @@ class MouseKey(Key): def make_key( code: Optional[int] = None, names: Tuple[str, ...] = tuple(), # NOQA - type: KeyType = KeyType.SIMPLE, + key_type: Key = Key, **kwargs, ) -> Key: ''' @@ -551,17 +544,6 @@ def make_key( global NEXT_AVAILABLE_KEY - if type == KeyType.SIMPLE: - constructor = Key - elif type == KeyType.MODIFIER: - constructor = ModifierKey - elif type == KeyType.CONSUMER: - constructor = ConsumerKey - elif type == KeyType.MOUSE: - constructor = MouseKey - else: - raise ValueError('Unrecognized key type') - if code is None: code = NEXT_AVAILABLE_KEY NEXT_AVAILABLE_KEY += 1 @@ -571,7 +553,7 @@ def make_key( # code NEXT_AVAILABLE_KEY = max(NEXT_AVAILABLE_KEY, code + 1) - key = constructor(code=code, **kwargs) + key = key_type(code=code, **kwargs) for name in names: KC[name] = key @@ -579,22 +561,6 @@ def make_key( return key -def make_mod_key(code: int, names: Tuple[str, ...], *args, **kwargs) -> Key: - return make_key(code, names, *args, **kwargs, type=KeyType.MODIFIER) - - -def make_shifted_key(code: int, names: Tuple[str, ...]) -> Key: - return make_key(code, names, has_modifiers={KC.LSFT.code}) - - -def make_consumer_key(*args, **kwargs) -> Key: - return make_key(*args, **kwargs, type=KeyType.CONSUMER) - - -def make_mouse_key(*args, **kwargs) -> Key: - return make_key(*args, **kwargs, type=KeyType.MOUSE) - - # Argumented keys are implicitly internal, so auto-gen of code # is almost certainly the best plan here def make_argumented_key( diff --git a/kmk/modules/mouse_keys.py b/kmk/modules/mouse_keys.py index 19140837d..e89682dab 100644 --- a/kmk/modules/mouse_keys.py +++ b/kmk/modules/mouse_keys.py @@ -1,17 +1,17 @@ from micropython import const -from kmk.keys import AX, make_key, make_mouse_key +from kmk.keys import AX, MouseKey, make_key from kmk.modules import Module from kmk.scheduler import cancel_task, create_task -_MU = const(1) -_MD = const(2) -_ML = const(4) -_MR = const(8) -_WU = const(16) -_WD = const(32) -_WL = const(64) -_WR = const(128) +_MU = const(0x01) +_MD = const(0x02) +_ML = const(0x04) +_MR = const(0x08) +_WU = const(0x10) +_WD = const(0x20) +_WL = const(0x40) +_WR = const(0x80) class MouseKeys(Module): @@ -21,36 +21,18 @@ def __init__(self, max_speed=10, acc_interval=20, move_step=1): self.acc_interval = acc_interval self.move_step = move_step - make_mouse_key( - names=('MB_LMB',), - code=1, - ) - make_mouse_key( - names=('MB_MMB',), - code=4, - ) - make_mouse_key( - names=('MB_RMB',), - code=2, - ) - make_mouse_key( - names=('MB_BTN4',), - code=8, - ) - make_mouse_key( - names=('MB_BTN5',), - code=16, - ) + make_key(code=0x01, names=('MB_LMB',), key_type=MouseKey) + make_key(code=0x02, names=('MB_RMB',), key_type=MouseKey) + make_key(code=0x04, names=('MB_MMB',), key_type=MouseKey) + make_key(code=0x08, names=('MB_BTN4',), key_type=MouseKey) + make_key(code=0x10, names=('MB_BTN5',), key_type=MouseKey) make_key( names=('MW_UP',), on_press=self._mw_up_press, on_release=self._mw_up_release, ) make_key( - names=( - 'MW_DOWN', - 'MW_DN', - ), + names=('MW_DOWN', 'MW_DN'), on_press=self._mw_down_press, on_release=self._mw_down_release, ) @@ -70,26 +52,17 @@ def __init__(self, max_speed=10, acc_interval=20, move_step=1): on_release=self._ms_up_release, ) make_key( - names=( - 'MS_DOWN', - 'MS_DN', - ), + names=('MS_DOWN', 'MS_DN'), on_press=self._ms_down_press, on_release=self._ms_down_release, ) make_key( - names=( - 'MS_LEFT', - 'MS_LT', - ), + names=('MS_LEFT', 'MS_LT'), on_press=self._ms_left_press, on_release=self._ms_left_release, ) make_key( - names=( - 'MS_RIGHT', - 'MS_RT', - ), + names=('MS_RIGHT', 'MS_RT'), on_press=self._ms_right_press, on_release=self._ms_right_release, ) From 831ff68b27aac3c17dc86c721817cb178cb83eb0 Mon Sep 17 00:00:00 2001 From: xs5871 Date: Tue, 11 Jun 2024 15:18:05 +0000 Subject: [PATCH 2/3] Refactor modified keys --- docs/en/keys.md | 6 -- kmk/hid.py | 15 +---- kmk/keys.py | 88 ++++++++++++++++++------------ kmk/kmk_keyboard.py | 2 - kmk/modules/string_substitution.py | 11 +++- tests/test_autoshift.py | 2 +- tests/test_kmk_keys.py | 39 ++++++------- tests/test_string_substitution.py | 10 +--- 8 files changed, 84 insertions(+), 89 deletions(-) diff --git a/docs/en/keys.md b/docs/en/keys.md index 157a6e96d..83f807bf4 100644 --- a/docs/en/keys.md +++ b/docs/en/keys.md @@ -32,12 +32,6 @@ objects have a few core pieces of information: computer, which will translate that integer to something meaningful - for example, `code=4` becomes `a` on a US QWERTY/Dvorak keyboard). -- Their attached modifiers (to implement things like shifted keys or `KC.HYPR`, - which are single key presses sending along more than one key in a single HID - report. For almost all purposes outside of KMK core, - this field should be ignored - it can be safely populated through far more - sane means than futzing with it by hand. - - Handlers for "press" (sometimes known as "keydown") and "release" (sometimes known as "keyup") events. KMK provides handlers for standard keyboard functions and some special override keys (like `KC.GESC`, which is an enhanced diff --git a/kmk/hid.py b/kmk/hid.py index ff1d8005c..1430a28a1 100644 --- a/kmk/hid.py +++ b/kmk/hid.py @@ -127,9 +127,6 @@ def create_report(self, keys_pressed, axes): self.add_pd(key) else: self.add_key(key) - if key.has_modifiers: - for mod in key.has_modifiers: - self.add_modifier(mod) for axis in axes: self.move_axis(axis) @@ -175,11 +172,7 @@ def clear_non_modifiers(self): def add_modifier(self, modifier): if isinstance(modifier, ModifierKey): - if modifier.code == ModifierKey.FAKE_CODE: - for mod in modifier.has_modifiers: - self.report_mods[0] |= mod - else: - self.report_mods[0] |= modifier.code + self.report_mods[0] |= modifier.code else: self.report_mods[0] |= modifier @@ -187,11 +180,7 @@ def add_modifier(self, modifier): def remove_modifier(self, modifier): if isinstance(modifier, ModifierKey): - if modifier.code == ModifierKey.FAKE_CODE: - for mod in modifier.has_modifiers: - self.report_mods[0] ^= mod - else: - self.report_mods[0] ^= modifier.code + self.report_mods[0] ^= modifier.code else: self.report_mods[0] ^= modifier diff --git a/kmk/keys.py b/kmk/keys.py index 97dc55b28..dc79b110e 100644 --- a/kmk/keys.py +++ b/kmk/keys.py @@ -286,7 +286,9 @@ def maybe_make_shifted_key(candidate: str) -> Optional[Key]: for code, names in codes: if candidate in names: - return make_key(code=code, names=names, has_modifiers={KC.LSFT.code}) + return make_key( + code=code, names=names, key_type=ModifiedKey, modifier=KC.LSFT + ) def maybe_make_international_key(candidate: str) -> Optional[Key]: @@ -445,7 +447,6 @@ class Key: def __init__( self, code: int, - has_modifiers: Optional[list[Key, ...]] = None, on_press: Callable[ [object, Key, Keyboard, ...], None ] = handlers.default_pressed, @@ -455,8 +456,6 @@ def __init__( meta: object = object(), ): self.code = code - self.has_modifiers = has_modifiers - # cast to bool() in case we get a None value self._on_press = on_press self._on_release = on_release @@ -466,7 +465,7 @@ def __call__(self) -> Key: return self def __repr__(self): - return f'Key(code={self.code}, has_modifiers={self.has_modifiers})' + return f'Key(code={self.code})' def on_press(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None: self._on_press(self, keyboard, KC, coord_int) @@ -476,40 +475,57 @@ def on_release(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> Non class ModifierKey(Key): - FAKE_CODE = const(-1) + def __call__(self, key: Key) -> Key: + # don't duplicate when applying the same modifier twice + if ( + isinstance(key, ModifiedKey) + and key.modifier.code & self.code == key.modifier.code + ): + return key + elif isinstance(key, ModifierKey) and key.code & self.code == key.code: + return key - def __call__( - self, - modified_key: Optional[Key] = None, - ) -> Key: - if modified_key is None: - return super().__call__() - - modifiers = set() - code = modified_key.code - - if self.code != ModifierKey.FAKE_CODE: - modifiers.add(self.code) - if self.has_modifiers: - modifiers |= self.has_modifiers - if modified_key.has_modifiers: - modifiers |= modified_key.has_modifiers - - if isinstance(modified_key, ModifierKey): - if modified_key.code != ModifierKey.FAKE_CODE: - modifiers.add(modified_key.code) - code = ModifierKey.FAKE_CODE - - return type(modified_key)( - code=code, - has_modifiers=modifiers, - on_press=modified_key._on_press, - on_release=modified_key._on_release, - meta=modified_key.meta, - ) + return ModifiedKey(key, self) def __repr__(self): - return f'ModifierKey(code={self.code}, has_modifiers={self.has_modifiers})' + return f'ModifierKey(code={self.code})' + + +class ModifiedKey(Key): + meta = None + code = -1 + + def __init__(self, code: [Key, int], modifier: [ModifierKey]): + # generate from code by maybe_make_shifted_key + if isinstance(code, int): + key = Key(code=code) + else: + key = code + + # stack modified keys + if isinstance(key, ModifiedKey): + modifier = ModifierKey(key.modifier.code | modifier.code) + key = key.key + + self.key = key + self.modifier = modifier + + def on_press(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None: + self.modifier.on_press(keyboard, coord_int) + self.key.on_press(keyboard, coord_int) + + def on_release(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None: + self.key.on_release(keyboard, coord_int) + self.modifier.on_release(keyboard, coord_int) + + def __repr__(self): + return ( + 'ModifiedKey(key=' + + str(self.key) + + ', modifier=' + + str(self.modifier) + + ')' + ) class ConsumerKey(Key): diff --git a/kmk/kmk_keyboard.py b/kmk/kmk_keyboard.py index e43c8952b..901ebbb4f 100644 --- a/kmk/kmk_keyboard.py +++ b/kmk/kmk_keyboard.py @@ -246,11 +246,9 @@ def resume_process_key( self._resume_buffer.append(ksf) def remove_key(self, keycode: Key) -> None: - self.keys_pressed.discard(keycode) self.process_key(keycode, False) def add_key(self, keycode: Key) -> None: - self.keys_pressed.add(keycode) self.process_key(keycode, True) def tap_key(self, keycode: Key) -> None: diff --git a/kmk/modules/string_substitution.py b/kmk/modules/string_substitution.py index cd72f55e4..bbb7d3d2a 100644 --- a/kmk/modules/string_substitution.py +++ b/kmk/modules/string_substitution.py @@ -5,7 +5,7 @@ pass from micropython import const -from kmk.keys import KC, Key, ModifierKey +from kmk.keys import KC, Key, ModifiedKey, ModifierKey from kmk.modules import Module @@ -27,6 +27,11 @@ def __init__(self, key_code: Key, is_shifted: bool) -> None: def __eq__(self, other: any) -> bool: # type: ignore try: + if isinstance(self.key_code, ModifiedKey): + return ( + self.key_code.key.code == other.key_code.key.code + and self.is_shifted == other.is_shifted + ) return ( self.key_code.code == other.key_code.code and self.is_shifted == other.is_shifted @@ -45,7 +50,9 @@ def __init__(self, string: str) -> None: key_code = KC[char] if key_code == KC.NO: raise ValueError(f'Invalid character in dictionary: {char}') - shifted = char.isupper() or key_code.has_modifiers == {2} + shifted = char.isupper() or ( + isinstance(key_code, ModifiedKey) and key_code.modifier == KC.LSHIFT + ) self._characters.append(Character(key_code, shifted)) def next_character(self) -> None: diff --git a/tests/test_autoshift.py b/tests/test_autoshift.py index 68e5dc849..5f1aa6073 100644 --- a/tests/test_autoshift.py +++ b/tests/test_autoshift.py @@ -76,7 +76,7 @@ def test_hold_shifted_hold_alpha(self): self.kb.test( '', [(2, True), (0, True), t_after, (2, False), (0, False)], - [{KC.LSHIFT, KC.HASH}, {KC.LSHIFT, KC.HASH, KC.A}, {KC.A}, {}], + [{KC.LSHIFT, KC.N3}, {KC.LSHIFT, KC.N3, KC.A}, {KC.A}, {}], ) def test_hold_internal(self): diff --git a/tests/test_kmk_keys.py b/tests/test_kmk_keys.py index 599ea4e26..aad528049 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, ModifierKey, make_key +from kmk.keys import KC, Key, ModifiedKey, ModifierKey, make_key from tests.keyboard_test import KeyboardTest @@ -14,13 +14,13 @@ def test_basic_kmk_keyboard(self): KC.RALT(KC.HASH), KC.RALT(KC.LSFT(KC.N3)), KC.RALT(KC.LSFT), - # Note: this is correct, if unusual, syntax. It's a useful test because it failed silently on previous builds. - KC.RALT(KC.LSFT)(KC.N3), KC.RALT, KC.TRNS, ] ], + debug_enabled=False, ) + keyboard.test( 'Shifted key', [(0, True), (0, False)], @@ -32,6 +32,7 @@ def test_basic_kmk_keyboard(self): {}, ], ) + keyboard.test( 'AltGr+Shifted key', [(1, True), (1, False)], @@ -44,6 +45,7 @@ def test_basic_kmk_keyboard(self): {}, ], ) + keyboard.test( 'AltGr+Shift+key', [(2, True), (2, False)], @@ -56,6 +58,7 @@ def test_basic_kmk_keyboard(self): {}, ], ) + keyboard.test( 'Shift+AltGr', [(3, True), (3, False)], @@ -67,21 +70,10 @@ def test_basic_kmk_keyboard(self): {}, ], ) - keyboard.test( - 'AltGr+Shift+key, alternate chaining', - [(4, True), (4, False)], - [ - { - KC.N3, - KC.LSFT, - KC.RALT, - }, - {}, - ], - ) + keyboard.test( 'AltGr', - [(5, True), (5, False)], + [(4, True), (4, False)], [ { KC.RALT, @@ -92,13 +84,13 @@ def test_basic_kmk_keyboard(self): keyboard.test( 'Transparent', - [(6, True)], + [(5, True)], [{}], ) - self.assertEqual(keyboard.keyboard._coordkeys_pressed, {6: KC.TRNS}) + self.assertEqual(keyboard.keyboard._coordkeys_pressed, {5: KC.TRNS}) assert isinstance(KC.RGUI, ModifierKey) - assert isinstance(KC.RALT(KC.RGUI), ModifierKey) + assert isinstance(KC.RALT(KC.RGUI), ModifiedKey) assert isinstance(KC.Q, Key) assert not isinstance(KC.Q, ModifierKey) assert isinstance(KC.RALT(KC.Q), Key) @@ -143,7 +135,8 @@ def test_custom_key(self): 'EURO', '€', ), - has_modifiers={KC.LSFT.code, KC.ROPT.code}, + key_type=ModifiedKey, + modifier=KC.LSFT(KC.ROPT), ) assert created is KC.get('EURO') assert created is KC.get('€') @@ -186,7 +179,8 @@ def test_custom_key(self): 'EURO', '€', ), - has_modifiers={KC['LSFT'].code, KC['ROPT'].code}, + key_type=ModifiedKey, + modifier=KC.LSFT(KC.ROPT), ) assert created is KC['EURO'] assert created is KC['€'] @@ -234,7 +228,8 @@ def test_custom_key(self): 'EURO', '€', ), - has_modifiers={KC.get('LSFT').code, KC.get('ROPT').code}, + key_type=ModifiedKey, + modifier=KC.LSFT(KC.ROPT), ) assert created is KC.get('EURO') assert created is KC.get('€') diff --git a/tests/test_string_substitution.py b/tests/test_string_substitution.py index 53ea53309..73e85d026 100644 --- a/tests/test_string_substitution.py +++ b/tests/test_string_substitution.py @@ -47,21 +47,21 @@ def test_keyboard_events_are_correct(self): # that results in a corresponding match, as that key is never sent self.keyboard.test( 'multi-character key, single-character value', - [(0, True), (0, False), (0, True), (0, False), self.delay], + [(0, True), (0, False), (0, True), (0, False)], [{KC.A}, {}, {KC.BACKSPACE}, {}, {KC.B}, {}], ) # note: the pressed key is never sent here, as the event is # intercepted and the replacement is sent instead self.keyboard.test( 'multi-character value, single-character key', - [(1, True), (1, False), self.delay], + [(1, True), (1, False)], [{KC.A}, {}, {KC.A}, {}], ) # modifiers are force-released if there's a match, # so the keyup event for them isn't sent self.keyboard.test( 'shifted alphanumeric or symbol in key and/or value', - [(3, True), (2, True), (2, False), (3, False), self.delay], + [(3, True), (2, True), (2, False), (3, False)], [{KC.LSHIFT}, {KC.LSHIFT, KC.N2}, {}], ) self.keyboard.test( @@ -75,7 +75,6 @@ def test_keyboard_events_are_correct(self): (5, False), (5, True), (5, False), - self.delay, ], [ {KC.D}, @@ -136,7 +135,6 @@ def test_keyboard_events_are_correct(self): (0, False), (0, True), (0, False), - self.delay, ], [ {KC.C}, @@ -158,7 +156,6 @@ def test_keyboard_events_are_correct(self): (0, False), (5, True), (5, False), - self.delay, ], [ {KC.A}, @@ -258,7 +255,6 @@ def test_keyboard_events_are_correct(self): # send the unreachable match "cccc" after matching "ccc" (5, True), (5, False), - self.delay, ], [ {KC.C}, From 8e9b0f0c2c0cef24739ad6a7efbe20ee20dc8ffa Mon Sep 17 00:00:00 2001 From: xs5871 Date: Wed, 12 Jun 2024 10:16:26 +0000 Subject: [PATCH 3/3] 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 dc79b110e..8585baad2 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]: @@ -444,28 +441,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) @@ -474,7 +463,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 ( @@ -487,9 +500,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 @@ -498,7 +508,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 @@ -520,7 +530,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) @@ -528,11 +539,11 @@ def __repr__(self): ) -class ConsumerKey(Key): +class ConsumerKey(_DefaultKey): pass -class MouseKey(Key): +class MouseKey(_DefaultKey): pass @@ -547,7 +558,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 @@ -558,18 +569,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 @@ -585,31 +588,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