From 9c1bd210ebc07f1df7ef0f22bd6b6aa9e7bd8fc0 Mon Sep 17 00:00:00 2001 From: xs5871 Date: Mon, 5 Dec 2022 19:54:57 +0000 Subject: [PATCH] Reduce key dictionary memory footprint Instead of indexing `Key` objects that have multiple names by each individual name, index by the set of names. This reduces the size of the default key dictionary by a factor of between 2 and 3, and as result also reduces reallocations/defragmentation. Instead of instantiating all module/extension keys by default, append them to the `maybe_key`-generator list. --- kmk/extensions/international.py | 53 ++++++++++-------- kmk/extensions/led.py | 71 +++++++++++++++--------- kmk/extensions/media_keys.py | 37 ++++++++----- kmk/extensions/peg_rgb_matrix.py | 37 ++++++++++--- kmk/extensions/rgb.py | 92 ++++++++++--------------------- kmk/extensions/statusled.py | 23 +++++++- kmk/handlers/sequences.py | 4 +- kmk/keys.py | 45 +++++++-------- kmk/modules/capsword.py | 9 ++- kmk/modules/cg_swap.py | 27 ++++++--- kmk/modules/combos.py | 13 +++-- kmk/modules/dynamic_sequences.py | 47 +++++++--------- kmk/modules/holdtap.py | 14 +++-- kmk/modules/layers.py | 62 ++++++++------------- kmk/modules/midi.py | 70 +++++++++++------------ kmk/modules/modtap.py | 14 +++-- kmk/modules/mouse_keys.py | 79 ++++++++------------------ kmk/modules/oneshot.py | 14 +++-- kmk/modules/pimoroni_trackball.py | 21 ++++--- kmk/modules/power.py | 28 +++++++--- kmk/modules/rapidfire.py | 14 +++-- kmk/modules/sticky_mod.py | 14 +++-- kmk/modules/tapdance.py | 14 +++-- tests/test_hold_tap.py | 3 + tests/test_kmk_keys.py | 66 ++++++++-------------- tests/test_layers.py | 1 + tests/test_tapdance.py | 1 + 27 files changed, 439 insertions(+), 434 deletions(-) diff --git a/kmk/extensions/international.py b/kmk/extensions/international.py index eab8087..3e3aefc 100644 --- a/kmk/extensions/international.py +++ b/kmk/extensions/international.py @@ -1,35 +1,42 @@ '''Adds international keys''' from kmk.extensions import Extension -from kmk.keys import make_key +from kmk.keys import KC, make_key class International(Extension): '''Adds international keys''' def __init__(self): - # International - make_key(code=50, names=('NONUS_HASH', 'NUHS')) - make_key(code=100, names=('NONUS_BSLASH', 'NUBS')) - make_key(code=101, names=('APP', 'APPLICATION', 'SEL', 'WINMENU')) + KC._generators.append(self.maybe_make_media_key) - make_key(code=135, names=('INT1', 'RO')) - make_key(code=136, names=('INT2', 'KANA')) - make_key(code=137, names=('INT3', 'JYEN')) - make_key(code=138, names=('INT4', 'HENK')) - make_key(code=139, names=('INT5', 'MHEN')) - make_key(code=140, names=('INT6',)) - make_key(code=141, names=('INT7',)) - make_key(code=142, names=('INT8',)) - make_key(code=143, names=('INT9',)) - make_key(code=144, names=('LANG1', 'HAEN')) - make_key(code=145, names=('LANG2', 'HAEJ')) - make_key(code=146, names=('LANG3',)) - make_key(code=147, names=('LANG4',)) - make_key(code=148, names=('LANG5',)) - make_key(code=149, names=('LANG6',)) - make_key(code=150, names=('LANG7',)) - make_key(code=151, names=('LANG8',)) - make_key(code=152, names=('LANG9',)) + @staticmethod + def maybe_make_media_key(candidate): + codes = ( + (50, ('NONUS_HASH', 'NUHS')), + (100, ('NONUS_BSLASH', 'NUBS')), + (101, ('APP', 'APPLICATION', 'SEL', 'WINMENU')), + (135, ('INT1', 'RO')), + (136, ('INT2', 'KANA')), + (137, ('INT3', 'JYEN')), + (138, ('INT4', 'HENK')), + (139, ('INT5', 'MHEN')), + (140, ('INT6',)), + (141, ('INT7',)), + (142, ('INT8',)), + (143, ('INT9',)), + (144, ('LANG1', 'HAEN')), + (145, ('LANG2', 'HAEJ')), + (146, ('LANG3',)), + (147, ('LANG4',)), + (148, ('LANG5',)), + (149, ('LANG6',)), + (150, ('LANG7',)), + (151, ('LANG8',)), + (152, ('LANG9',)), + ) + for code, names in codes: + if candidate in names: + return make_key(code=code, names=names) def on_runtime_enable(self, sandbox): return diff --git a/kmk/extensions/led.py b/kmk/extensions/led.py index 3f41706..ed4c460 100644 --- a/kmk/extensions/led.py +++ b/kmk/extensions/led.py @@ -2,7 +2,8 @@ import pwmio from math import e, exp, pi, sin from kmk.extensions import Extension, InvalidExtensionEnvironment -from kmk.keys import make_argumented_key, make_key +from kmk.handlers.stock import passthrough as handler_passthrough +from kmk.keys import KC, make_argumented_key, make_key from kmk.utils import clamp @@ -61,35 +62,51 @@ class LED(Extension): if user_animation is not None: self.user_animation = user_animation - make_argumented_key( - names=('LED_TOG',), - validator=self._led_key_validator, - on_press=self._key_led_tog, + KC._generators.append(self.maybe_make_led_key()) + + def maybe_make_led_key(self): + argumented_keys = ( + ( + ('LED_TOG',), + self._key_led_tog, + ), + ( + ('LED_INC',), + self._key_led_inc, + ), + ( + ('LED_DEC',), + self._key_led_dec, + ), + ( + ('LED_SET',), + self._key_led_set, + ), ) - make_argumented_key( - names=('LED_INC',), - validator=self._led_key_validator, - on_press=self._key_led_inc, - ) - make_argumented_key( - names=('LED_DEC',), - validator=self._led_key_validator, - on_press=self._key_led_dec, - ) - make_argumented_key( - names=('LED_SET',), - validator=self._led_set_key_validator, - on_press=self._key_led_set, - ) - make_key(names=('LED_ANI',), on_press=self._key_led_ani) - make_key(names=('LED_AND',), on_press=self._key_led_and) - make_key( - names=('LED_MODE_PLAIN', 'LED_M_P'), on_press=self._key_led_mode_static - ) - make_key( - names=('LED_MODE_BREATHE', 'LED_M_B'), on_press=self._key_led_mode_breathe + keys = ( + (('LED_ANI',), self._key_led_ani), + (('LED_AND',), self._key_led_and), + (('LED_MODE_PLAIN', 'LED_M_P'), self._key_led_mode_static), + (('LED_MODE_BREATHE', 'LED_M_B'), self._key_led_mode_breathe), ) + def closure(candidate): + for names, on_press in argumented_keys: + if candidate in names: + return make_argumented_key( + names=names, + validator=self._led_key_validator, + on_press=on_press, + on_release=handler_passthrough, + ) + for names, on_press in keys: + if candidate in names: + return make_key( + names=names, on_press=on_press, on_release=handler_passthrough + ) + + return closure + def __repr__(self): return f'LED({self._to_dict()})' diff --git a/kmk/extensions/media_keys.py b/kmk/extensions/media_keys.py index 309a00a..3562c76 100644 --- a/kmk/extensions/media_keys.py +++ b/kmk/extensions/media_keys.py @@ -1,9 +1,13 @@ from kmk.extensions import Extension -from kmk.keys import make_consumer_key +from kmk.keys import KC, make_consumer_key class MediaKeys(Extension): def __init__(self): + KC._generators.append(self.maybe_make_media_key) + + @staticmethod + def maybe_make_media_key(candidate): # Consumer ("media") keys. Most known keys aren't supported here. A much # longer list used to exist in this file, but the codes were almost certainly # incorrect, conflicting with each other, or otherwise 'weird'. We'll add them @@ -14,20 +18,23 @@ class MediaKeys(Extension): # 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 + codes = ( + (226, ('AUDIO_MUTE', 'MUTE')), # 0xE2 + (233, ('AUDIO_VOL_UP', 'VOLU')), # 0xE9 + (234, ('AUDIO_VOL_DOWN', 'VOLD')), # 0xEA + (111, ('BRIGHTNESS_UP', 'BRIU')), # 0x6F + (112, ('BRIGHTNESS_DOWN', 'BRID')), # 0x70 + (181, ('MEDIA_NEXT_TRACK', 'MNXT')), # 0xB5 + (182, ('MEDIA_PREV_TRACK', 'MPRV')), # 0xB6 + (183, ('MEDIA_STOP', 'MSTP')), # 0xB7 + (205, ('MEDIA_PLAY_PAUSE', 'MPLY')), # 0xCD (this may not be right) + (184, ('MEDIA_EJECT', 'EJCT')), # 0xB8 + (179, ('MEDIA_FAST_FORWARD', 'MFFD')), # 0xB3 + (180, ('MEDIA_REWIND', 'MRWD')), # 0xB4 + ) + for code, names in codes: + if candidate in names: + return make_consumer_key(code=code, names=names) def on_runtime_enable(self, sandbox): return diff --git a/kmk/extensions/peg_rgb_matrix.py b/kmk/extensions/peg_rgb_matrix.py index 77a2825..f648fec 100644 --- a/kmk/extensions/peg_rgb_matrix.py +++ b/kmk/extensions/peg_rgb_matrix.py @@ -4,7 +4,7 @@ from storage import getmount from kmk.extensions import Extension from kmk.handlers.stock import passthrough as handler_passthrough -from kmk.keys import make_key +from kmk.keys import KC, make_key class Color: @@ -69,16 +69,35 @@ class Rgb_matrix(Extension): 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 + KC._generators.append(self.maybe_make_peg_rgb_key()) + + def maybe_make_peg_rgb_key(self): + keys = ( + ( + ('RGB_TOG',), + self._rgb_tog, + ), + ( + ('RGB_BRI',), + self._rgb_bri, + ), + ( + ('RGB_BRD',), + self._rgb_brd, + ), ) + def closure(candidate): + for names, on_press in keys: + if candidate in names: + return make_key( + names=names, + on_press=on_press, + on_release=handler_passthrough, + ) + + return closure + def _rgb_tog(self, *args, **kwargs): if self.enable: self.off() diff --git a/kmk/extensions/rgb.py b/kmk/extensions/rgb.py index 005000d..87ce665 100644 --- a/kmk/extensions/rgb.py +++ b/kmk/extensions/rgb.py @@ -3,7 +3,7 @@ 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.keys import KC, make_key from kmk.kmktime import PeriodicTimer from kmk.utils import Debug, clamp @@ -157,69 +157,37 @@ class RGB(Extension): 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_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, + KC._generators.append(self.maybe_make_rgb_key()) + + def maybe_make_rgb_key(self): + keys = ( + (('RGB_TOG',), self._rgb_tog), + (('RGB_HUI',), self._rgb_hui), + (('RGB_HUD',), self._rgb_hud), + (('RGB_SAI',), self._rgb_sai), + (('RGB_SAD',), self._rgb_sad), + (('RGB_VAI',), self._rgb_vai), + (('RGB_VAD',), self._rgb_vad), + (('RGB_ANI',), self._rgb_ani), + (('RGB_AND',), self._rgb_and), + (('RGB_MODE_PLAIN', 'RGB_M_P'), self._rgb_mode_static), + (('RGB_MODE_BREATHE', 'RGB_M_B'), self._rgb_mode_breathe), + (('RGB_MODE_RAINBOW', 'RGB_M_R'), self._rgb_mode_rainbow), + (('RGB_MODE_BREATHE_RAINBOW', 'RGB_M_BR'), self._rgb_mode_breathe_rainbow), + (('RGB_MODE_SWIRL', 'RGB_M_S'), self._rgb_mode_swirl), + (('RGB_MODE_KNIGHT', 'RGB_M_K'), self._rgb_mode_knight), + (('RGB_RESET', 'RGB_RST'), self._rgb_reset), ) + def closure(candidate): + for names, on_press in keys: + if candidate in names: + return make_key( + names=names, on_press=on_press, on_release=handler_passthrough + ) + + return closure + def on_runtime_enable(self, sandbox): return diff --git a/kmk/extensions/statusled.py b/kmk/extensions/statusled.py index fa5e793..b52ffcd 100644 --- a/kmk/extensions/statusled.py +++ b/kmk/extensions/statusled.py @@ -4,7 +4,8 @@ import pwmio import time from kmk.extensions import Extension, InvalidExtensionEnvironment -from kmk.keys import make_key +from kmk.handlers.stock import passthrough as handler_passthrough +from kmk.keys import KC, make_key class statusLED(Extension): @@ -32,8 +33,24 @@ class statusLED(Extension): self.brightness_step = brightness_step self.brightness_limit = brightness_limit - make_key(names=('SLED_INC',), on_press=self._key_led_inc) - make_key(names=('SLED_DEC',), on_press=self._key_led_dec) + KC._generators.append(self.maybe_make_status_led_key()) + + def maybe_make_status_led_key(self): + keys = ( + (('SLED_INC',), self._key_led_inc), + (('SLED_DEC',), self._key_led_dec), + ) + + def closure(candidate): + for names, on_press in keys: + if candidate in names: + return make_key( + names=names, + on_press=on_press, + on_release=handler_passthrough, + ) + + return closure def _layer_indicator(self, layer_active, *args, **kwargs): ''' diff --git a/kmk/handlers/sequences.py b/kmk/handlers/sequences.py index 936c17e..dcb0cf5 100644 --- a/kmk/handlers/sequences.py +++ b/kmk/handlers/sequences.py @@ -35,7 +35,7 @@ def simple_key_sequence(seq): meta=KeySequenceMeta(seq), on_press=sequence_press_handler, on_release=passthrough, - ) + )[0] def send_string(message): @@ -124,7 +124,7 @@ def unicode_codepoint_sequence(codepoints): simple_key_sequence(_winc_unicode_sequence(kc_macros, keyboard)), True ) - return make_key(on_press=_unicode_sequence) + return make_key(on_press=_unicode_sequence)[0] def _ralt_unicode_sequence(kc_macros, keyboard): diff --git a/kmk/keys.py b/kmk/keys.py index e6a867a..6865905 100644 --- a/kmk/keys.py +++ b/kmk/keys.py @@ -34,8 +34,8 @@ debug = Debug(__name__) def maybe_make_key( - code: Optional[int], - names: Tuple[str, ...], + code: Optional[int] = None, + names: Tuple[str, ...] = tuple(), # NOQA *args, **kwargs, ) -> Callable[[str], Key]: @@ -401,6 +401,7 @@ KEY_GENERATORS = ( class KeyAttrDict: __cache = {} + _generators = list(KEY_GENERATORS) def __iter__(self): return self.__cache.__iter__() @@ -419,24 +420,30 @@ class KeyAttrDict: def clear(self): self.__cache.clear() + self._generators = list(KEY_GENERATORS) - def __getitem__(self, key: Key): - try: - return self.__cache[key] - except KeyError: - pass + def __getitem__(self, name: str): + for names, key in self.__cache.items(): + if name in names: + return key - for func in KEY_GENERATORS: - maybe_key = func(key) + for func in self._generators: + maybe_key = func(name) if maybe_key: break - else: - raise ValueError(f'Invalid key: {key}') + + if not maybe_key: + raise ValueError(f'Invalid key: {name}') + + key, names = maybe_key + + if names: + self.__cache[names] = key if debug.enabled: - debug(f'{key}: {maybe_key}') + debug(f'{name}: {key}') - return self.__cache[key] + return key # Global state, will be filled in throughout this file, and @@ -671,7 +678,7 @@ class ConsumerKey(Key): def make_key( code: Optional[int] = None, - names: Tuple[str, ...] = tuple(), # NOQA + names: Optional[Tuple[str, ...]] = tuple(), # NOQA type: KeyType = KeyType.SIMPLE, **kwargs, ) -> Key: @@ -713,10 +720,7 @@ def make_key( key = constructor(code=code, **kwargs) - for name in names: - KC[name] = key - - return key + return key, names def make_mod_key(code: int, names: Tuple[str, ...], *args, **kwargs) -> Key: @@ -762,7 +766,4 @@ def make_argumented_key( 'should have been raised.' ) - for name in names: - KC[name] = _argumented_key - - return _argumented_key + return _argumented_key, names diff --git a/kmk/modules/capsword.py b/kmk/modules/capsword.py index 8c94a08..f50126f 100755 --- a/kmk/modules/capsword.py +++ b/kmk/modules/capsword.py @@ -1,4 +1,5 @@ -from kmk.keys import FIRST_KMK_INTERNAL_KEY, KC, ModifierKey, make_key +from kmk.handlers.stock import passthrough as handler_passthrough +from kmk.keys import FIRST_KMK_INTERNAL_KEY, KC, ModifierKey, maybe_make_key from kmk.modules import Module @@ -16,12 +17,16 @@ class CapsWord(Module): self._timeout_key = False self._cw_active = False self.timeout = timeout - make_key( + KC._generators.append(self.maybe_make_capsword_key()) + + def maybe_make_capsword_key(self): + return maybe_make_key( names=( 'CAPSWORD', 'CW', ), on_press=self.cw_pressed, + on_release=handler_passthrough, ) def during_bootup(self, keyboard): diff --git a/kmk/modules/cg_swap.py b/kmk/modules/cg_swap.py index 9dbfbc7..2b5090e 100755 --- a/kmk/modules/cg_swap.py +++ b/kmk/modules/cg_swap.py @@ -1,3 +1,4 @@ +from kmk.handlers.stock import passthrough as handler_passthrough from kmk.keys import KC, ModifierKey, make_key from kmk.modules import Module @@ -12,16 +13,26 @@ class CgSwap(Module): KC.LGUI: KC.LCTL, KC.RGUI: KC.RCTL, } - make_key( - names=('CG_SWAP',), - ) - make_key( - names=('CG_NORM',), - ) - make_key( - names=('CG_TOGG',), + KC._generators.append(self.maybe_make_cgswap_key()) + + def maybe_make_cgswap_key(self): + keys = ( + (('CG_SWAP',),), + (('CG_NORM',),), + (('CG_TOGG',),), ) + def closure(candidate): + for names in keys: + if candidate in names: + return make_key( + names=names, + on_press=handler_passthrough, + on_release=handler_passthrough, + ) + + return closure + def during_bootup(self, keyboard): return diff --git a/kmk/modules/combos.py b/kmk/modules/combos.py index d44d031..83654b0 100644 --- a/kmk/modules/combos.py +++ b/kmk/modules/combos.py @@ -5,7 +5,7 @@ except ImportError: from micropython import const import kmk.handlers.stock as handlers -from kmk.keys import Key, make_key +from kmk.keys import KC, Key, maybe_make_key from kmk.kmk_keyboard import KMKKeyboard from kmk.modules import Module @@ -102,11 +102,12 @@ class Combos(Module): def __init__(self, combos=[]): self.combos = combos self._key_buffer = [] - - make_key( - names=('LEADER', 'LDR'), - on_press=handlers.passthrough, - on_release=handlers.passthrough, + KC._generators.append( + maybe_make_key( + names=('LEADER', 'LDR'), + on_press=handlers.passthrough, + on_release=handlers.passthrough, + ) ) def during_bootup(self, keyboard): diff --git a/kmk/modules/dynamic_sequences.py b/kmk/modules/dynamic_sequences.py index e633da5..10c7071 100755 --- a/kmk/modules/dynamic_sequences.py +++ b/kmk/modules/dynamic_sequences.py @@ -3,6 +3,7 @@ from supervisor import ticks_ms from collections import namedtuple +from kmk.handlers.stock import passthrough as handler_passthrough from kmk.keys import KC, make_argumented_key from kmk.kmktime import check_deadline, ticks_diff from kmk.modules import Module @@ -45,41 +46,33 @@ class DynamicSequences(Module): self.index = 0 self.start_time = 0 self.current_repetition = 0 - self.last_config_frame = set() self.timeout = timeout self.key_interval = key_interval self.use_recorded_speed = use_recorded_speed - # Create keycodes - make_argumented_key( - validator=DSMeta, names=('RECORD_SEQUENCE',), on_press=self._record_sequence + KC._generators.append(self.maybe_make_sequence_key()) + + def maybe_make_sequence_key(self): + keys = ( + (('RECORD_SEQUENCE',), DSMeta, self._record_sequence), + (('PLAY_SEQUENCE',), DSMeta, self._play_sequence), + (('SET_SEQUENCE', 'STOP_SEQUENCE'), DSMeta, self._stop_sequence), + (('SET_SEQUENCE_REPETITIONS',), DSMeta, self._set_sequence_repetitions), + (('SET_SEQUENCE_INTERVAL',), DSMeta, self._set_sequence_interval), ) - make_argumented_key( - validator=DSMeta, names=('PLAY_SEQUENCE',), on_press=self._play_sequence - ) + def closure(candidate): + for names, validator, on_press in keys: + if candidate in names: + return make_argumented_key( + names=names, + validator=validator, + on_press=on_press, + on_release=handler_passthrough, + ) - make_argumented_key( - validator=DSMeta, - names=( - 'SET_SEQUENCE', - 'STOP_SEQUENCE', - ), - on_press=self._stop_sequence, - ) - - make_argumented_key( - validator=DSMeta, - names=('SET_SEQUENCE_REPETITIONS',), - on_press=self._set_sequence_repetitions, - ) - - make_argumented_key( - validator=DSMeta, - names=('SET_SEQUENCE_INTERVAL',), - on_press=self._set_sequence_interval, - ) + return closure def _record_sequence(self, key, keyboard, *args, **kwargs): self._stop_sequence(key, keyboard) diff --git a/kmk/modules/holdtap.py b/kmk/modules/holdtap.py index 92e6120..8d557e2 100644 --- a/kmk/modules/holdtap.py +++ b/kmk/modules/holdtap.py @@ -1,6 +1,6 @@ from micropython import const -from kmk.keys import KC, make_argumented_key +from kmk.keys import KC, maybe_make_argumented_key from kmk.modules import Module from kmk.utils import Debug @@ -55,11 +55,13 @@ class HoldTap(Module): self.key_buffer = [] self.key_states = {} if not KC.get('HT'): - make_argumented_key( - validator=HoldTapKeyMeta, - names=('HT',), - on_press=self.ht_pressed, - on_release=self.ht_released, + KC._generators.append( + maybe_make_argumented_key( + validator=HoldTapKeyMeta, + names=('HT',), + on_press=self.ht_pressed, + on_release=self.ht_released, + ) ) def during_bootup(self, keyboard): diff --git a/kmk/modules/layers.py b/kmk/modules/layers.py index 459c35c..eb46898 100644 --- a/kmk/modules/layers.py +++ b/kmk/modules/layers.py @@ -1,4 +1,5 @@ '''One layer isn't enough. Adds keys to get to more of them''' +from kmk.handlers.stock import passthrough as handler_passthrough from kmk.keys import KC, make_argumented_key from kmk.modules.holdtap import HoldTap, HoldTapKeyMeta from kmk.utils import Debug @@ -39,46 +40,31 @@ class Layers(HoldTap): def __init__(self): # Layers super().__init__() - make_argumented_key( - validator=layer_key_validator, - names=('MO',), - on_press=self._mo_pressed, - on_release=self._mo_released, - ) - make_argumented_key( - validator=layer_key_validator, - names=('DF',), - on_press=self._df_pressed, - ) - make_argumented_key( - validator=layer_key_validator, - names=('LM',), - on_press=self._lm_pressed, - on_release=self._lm_released, - ) - make_argumented_key( - validator=layer_key_validator, - names=('TG',), - on_press=self._tg_pressed, - ) - make_argumented_key( - validator=layer_key_validator, - names=('TO',), - on_press=self._to_pressed, - ) - make_argumented_key( - validator=layer_key_validator_lt, - names=('LT',), - on_press=self.ht_pressed, - on_release=self.ht_released, - ) - make_argumented_key( - validator=layer_key_validator_tt, - names=('TT',), - on_press=self.ht_pressed, - on_release=self.ht_released, + KC._generators.append(self.maybe_make_layer_key()) + + def maybe_make_layer_key(self): + keys = ( + (('MO',), layer_key_validator, self._mo_pressed, self._mo_released), + (('DF',), layer_key_validator, self._df_pressed, handler_passthrough), + (('LM',), layer_key_validator, self._lm_pressed, self._lm_released), + (('TG',), layer_key_validator, self._tg_pressed, handler_passthrough), + (('TO',), layer_key_validator, self._to_pressed, handler_passthrough), + (('LT',), layer_key_validator_lt, self.ht_pressed, self.ht_released), + (('TT',), layer_key_validator_tt, self.ht_pressed, self.ht_released), ) + def closure(candidate): + for names, validator, on_press, on_release in keys: + if candidate in names: + return make_argumented_key( + names=names, + validator=validator, + on_press=on_press, + on_release=on_release, + ) + + return closure + def _df_pressed(self, key, keyboard, *args, **kwargs): ''' Switches the default layer diff --git a/kmk/modules/midi.py b/kmk/modules/midi.py index d32ce64..5721774 100644 --- a/kmk/modules/midi.py +++ b/kmk/modules/midi.py @@ -8,7 +8,8 @@ from adafruit_midi.program_change import ProgramChange from adafruit_midi.start import Start from adafruit_midi.stop import Stop -from kmk.keys import make_argumented_key +from kmk.handlers.stock import passthrough as handler_passthrough +from kmk.keys import KC, make_argumented_key from kmk.modules import Module @@ -21,42 +22,6 @@ class midiNoteValidator: class MidiKeys(Module): def __init__(self): - make_argumented_key( - names=('MIDI_CC',), - validator=ControlChange, - on_press=self.on_press, - ) - - make_argumented_key( - names=('MIDI_NOTE',), - validator=midiNoteValidator, - on_press=self.note_on, - on_release=self.note_off, - ) - - make_argumented_key( - names=('MIDI_PB',), - validator=PitchBend, - on_press=self.on_press, - ) - - make_argumented_key( - names=('MIDI_PC',), - validator=ProgramChange, - on_press=self.on_press, - ) - - make_argumented_key( - names=('MIDI_START',), - validator=Start, - on_press=self.on_press, - ) - - make_argumented_key( - names=('MIDI_STOP',), - validator=Stop, - on_press=self.on_press, - ) try: self.midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0) @@ -65,6 +30,37 @@ class MidiKeys(Module): # if debug_enabled: print('No midi device found.') + KC._generators.append(self.maybe_make_midi_key()) + + def maybe_make_midi_key(self): + keys = ( + (('MIDI_CC',), ControlChange), + (('MIDI_PB',), PitchBend), + (('MIDI_PC',), ProgramChange), + (('MIDI_START',), Start), + (('MIDI_STOP',), Stop), + ) + note = ('MIDI_NOTE',) + + def closure(candidate): + if candidate in note: + return make_argumented_key( + names=('MIDI_NOTE',), + validator=midiNoteValidator, + on_press=self.note_on, + on_release=self.note_off, + ) + for names, validator in keys: + if candidate in names: + return make_argumented_key( + names=names, + validator=validator, + on_press=self.on_press, + on_release=handler_passthrough, + ) + + return closure + def during_bootup(self, keyboard): return None diff --git a/kmk/modules/modtap.py b/kmk/modules/modtap.py index a37868c..74acb08 100644 --- a/kmk/modules/modtap.py +++ b/kmk/modules/modtap.py @@ -1,13 +1,15 @@ -from kmk.keys import make_argumented_key +from kmk.keys import KC, maybe_make_argumented_key from kmk.modules.holdtap import HoldTap, HoldTapKeyMeta class ModTap(HoldTap): def __init__(self): super().__init__() - make_argumented_key( - validator=HoldTapKeyMeta, - names=('MT',), - on_press=self.ht_pressed, - on_release=self.ht_released, + KC._generators.append( + maybe_make_argumented_key( + validator=HoldTapKeyMeta, + names=('MT',), + on_press=self.ht_pressed, + on_release=self.ht_released, + ) ) diff --git a/kmk/modules/mouse_keys.py b/kmk/modules/mouse_keys.py index 5a4e141..c5f3a83 100644 --- a/kmk/modules/mouse_keys.py +++ b/kmk/modules/mouse_keys.py @@ -1,7 +1,7 @@ from supervisor import ticks_ms from kmk.hid import HID_REPORT_SIZES, HIDReportTypes -from kmk.keys import make_key +from kmk.keys import KC, make_key from kmk.modules import Module @@ -34,65 +34,30 @@ class MouseKeys(Module): self.ac_interval = 100 # Delta ms to apply acceleration self._next_interval = 0 # Time for next tick interval self.move_step = 1 + KC._generators.append(self.maybe_make_mouse_key()) - make_key( - names=('MB_LMB',), - on_press=self._mb_lmb_press, - on_release=self._mb_lmb_release, - ) - make_key( - names=('MB_MMB',), - on_press=self._mb_mmb_press, - on_release=self._mb_mmb_release, - ) - make_key( - names=('MB_RMB',), - on_press=self._mb_rmb_press, - on_release=self._mb_rmb_release, - ) - make_key( - names=('MW_UP',), - on_press=self._mw_up_press, - on_release=self._mw_up_release, - ) - make_key( - names=( - 'MW_DOWN', - 'MW_DN', - ), - on_press=self._mw_down_press, - on_release=self._mw_down_release, - ) - make_key( - names=('MS_UP',), - on_press=self._ms_up_press, - on_release=self._ms_up_release, - ) - make_key( - names=( - 'MS_DOWN', - 'MS_DN', - ), - on_press=self._ms_down_press, - on_release=self._ms_down_release, - ) - make_key( - names=( - 'MS_LEFT', - 'MS_LT', - ), - on_press=self._ms_left_press, - on_release=self._ms_left_release, - ) - make_key( - names=( - 'MS_RIGHT', - 'MS_RT', - ), - on_press=self._ms_right_press, - on_release=self._ms_right_release, + def maybe_make_mouse_key(self): + keys = ( + (('MB_LMB',), self._mb_lmb_press, self._mb_lmb_release), + (('MB_MMB',), self._mb_mmb_press, self._mb_mmb_release), + (('MB_RMB',), self._mb_rmb_press, self._mb_rmb_release), + (('MW_UP',), self._mw_up_press, self._mw_up_release), + (('MW_DOWN', 'MW_DN'), self._mw_down_press, self._mw_down_release), + (('MS_UP',), self._ms_up_press, self._ms_up_release), + (('MS_DOWN', 'MS_DN'), self._ms_down_press, self._ms_down_release), + (('MS_LEFT', 'MS_LT'), self._ms_left_press, self._ms_left_release), + (('MS_RIGHT', 'MS_RT'), self._ms_right_press, self._ms_right_release), ) + def closure(candidate): + for names, on_press, on_release in keys: + if candidate in names: + return make_key( + names=names, on_press=on_press, on_release=on_release + ) + + return closure + def during_bootup(self, keyboard): return diff --git a/kmk/modules/oneshot.py b/kmk/modules/oneshot.py index 107fa1a..d86c374 100644 --- a/kmk/modules/oneshot.py +++ b/kmk/modules/oneshot.py @@ -1,4 +1,4 @@ -from kmk.keys import make_argumented_key +from kmk.keys import KC, maybe_make_argumented_key from kmk.modules.holdtap import ActivationType, HoldTap, HoldTapKeyMeta @@ -11,11 +11,13 @@ class OneShot(HoldTap): def __init__(self): super().__init__() - make_argumented_key( - validator=oneshot_validator, - names=('OS', 'ONESHOT'), - on_press=self.osk_pressed, - on_release=self.osk_released, + KC._generators.append( + maybe_make_argumented_key( + validator=oneshot_validator, + names=('OS', 'ONESHOT'), + on_press=self.osk_pressed, + on_release=self.osk_released, + ) ) def process_key(self, keyboard, current_key, is_pressed, int_coord): diff --git a/kmk/modules/pimoroni_trackball.py b/kmk/modules/pimoroni_trackball.py index c3144d0..859c2cf 100644 --- a/kmk/modules/pimoroni_trackball.py +++ b/kmk/modules/pimoroni_trackball.py @@ -7,7 +7,7 @@ from micropython import const import math import struct -from kmk.keys import make_argumented_key, make_key +from kmk.keys import KC, maybe_make_argumented_key, maybe_make_key from kmk.kmktime import PeriodicTimer from kmk.modules import Module from kmk.modules.mouse_keys import PointingDevice @@ -198,15 +198,18 @@ class Trackball(Module): f'Invalid chip ID: 0x{chip_id:04X}, expected 0x{CHIP_ID:04X}' ) - make_key( - names=('TB_MODE', 'TB_NEXT_HANDLER', 'TB_N'), - on_press=self._tb_handler_next_press, + KC._generators.append( + maybe_make_key( + names=('TB_MODE', 'TB_NEXT_HANDLER', 'TB_N'), + on_press=self._tb_handler_next_press, + ) ) - - make_argumented_key( - validator=layer_key_validator, - names=('TB_HANDLER', 'TB_H'), - on_press=self._tb_handler_press, + KC._generators.append( + maybe_make_argumented_key( + validator=layer_key_validator, + names=('TB_HANDLER', 'TB_H'), + on_press=self._tb_handler_press, + ) ) def during_bootup(self, keyboard): diff --git a/kmk/modules/power.py b/kmk/modules/power.py index aa72eb1..41cefa5 100644 --- a/kmk/modules/power.py +++ b/kmk/modules/power.py @@ -5,7 +5,7 @@ from supervisor import ticks_ms from time import sleep from kmk.handlers.stock import passthrough as handler_passthrough -from kmk.keys import make_key +from kmk.keys import KC, make_key from kmk.kmktime import check_deadline from kmk.modules import Module @@ -20,16 +20,26 @@ class Power(Module): self._i2c = 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 + KC._generators.append(self.maybe_make_power_key()) + + def maybe_make_power_key(self): + keys = ( + (('PS_TOG',), self._ps_tog), + (('PS_ON',), self._ps_enable), + (('PS_OFF',), self._ps_disable), ) + def closure(candidate): + for names, on_press in keys: + if candidate in names: + return make_key( + names=names, + on_press=on_press, + on_release=handler_passthrough, + ) + + return closure + def __repr__(self): return f'Power({self._to_dict()})' diff --git a/kmk/modules/rapidfire.py b/kmk/modules/rapidfire.py index 50720ea..105c63e 100644 --- a/kmk/modules/rapidfire.py +++ b/kmk/modules/rapidfire.py @@ -1,6 +1,6 @@ from random import randint -from kmk.keys import make_argumented_key +from kmk.keys import KC, maybe_make_argumented_key from kmk.modules import Module @@ -28,11 +28,13 @@ class RapidFire(Module): _waiting_keys = [] def __init__(self): - make_argumented_key( - validator=RapidFireMeta, - names=('RF',), - on_press=self._rf_pressed, - on_release=self._rf_released, + KC._generators.append( + maybe_make_argumented_key( + validator=RapidFireMeta, + names=('RF',), + on_press=self._rf_pressed, + on_release=self._rf_released, + ) ) def _get_repeat(self, key): diff --git a/kmk/modules/sticky_mod.py b/kmk/modules/sticky_mod.py index 73ad569..90e5738 100644 --- a/kmk/modules/sticky_mod.py +++ b/kmk/modules/sticky_mod.py @@ -1,4 +1,4 @@ -from kmk.keys import make_argumented_key +from kmk.keys import KC, maybe_make_argumented_key from kmk.modules import Module @@ -12,11 +12,13 @@ class StickyMod(Module): def __init__(self): self._active = False self._active_key = None - make_argumented_key( - names=('SM',), - validator=StickyModMeta, - on_press=self.sm_pressed, - on_release=self.sm_released, + KC._generators.append( + maybe_make_argumented_key( + names=('SM',), + validator=StickyModMeta, + on_press=self.sm_pressed, + on_release=self.sm_released, + ) ) def during_bootup(self, keyboard): diff --git a/kmk/modules/tapdance.py b/kmk/modules/tapdance.py index 0320fa9..0314ed7 100644 --- a/kmk/modules/tapdance.py +++ b/kmk/modules/tapdance.py @@ -1,4 +1,4 @@ -from kmk.keys import KC, make_argumented_key +from kmk.keys import KC, maybe_make_argumented_key from kmk.modules.holdtap import ActivationType, HoldTap, HoldTapKeyMeta @@ -30,11 +30,13 @@ class TapDanceKeyMeta: 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, + KC._generators.append( + maybe_make_argumented_key( + validator=TapDanceKeyMeta, + names=('TD',), + on_press=self.td_pressed, + on_release=self.td_released, + ) ) self.td_counts = {} diff --git a/tests/test_hold_tap.py b/tests/test_hold_tap.py index da2e9b3..232f32c 100644 --- a/tests/test_hold_tap.py +++ b/tests/test_hold_tap.py @@ -9,6 +9,9 @@ from tests.keyboard_test import KeyboardTest class TestHoldTap(unittest.TestCase): + def setUp(self): + KC.clear() + def test_holdtap(self): keyboard = KeyboardTest( [Layers(), ModTap(), OneShot()], diff --git a/tests/test_kmk_keys.py b/tests/test_kmk_keys.py index a06b14b..bf67089 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, ModifierKey, make_key, maybe_make_key from tests.keyboard_test import KeyboardTest @@ -131,20 +131,14 @@ class TestKeys_dot(unittest.TestCase): KC.invalid_key def test_custom_key(self): - created = make_key( - KC.N2.code, - names=( - 'EURO', - '€', - ), - has_modifiers={KC.LSFT.code, KC.ROPT.code}, + KC._generators.append( + maybe_make_key( + KC.N2.code, + names=('EURO', '€'), + has_modifiers={KC.LSFT.code, KC.ROPT.code}, + ) ) - assert created is KC.get('EURO') - assert created is KC.get('€') - - def test_match_exactly_case(self): - created = make_key(names=('ThIs_Is_A_StRaNgE_kEy',)) - assert created is KC.get('ThIs_Is_A_StRaNgE_kEy') + assert KC.get('€') is KC.get('EURO') class TestKeys_index(unittest.TestCase): @@ -176,20 +170,14 @@ class TestKeys_index(unittest.TestCase): KC['not_a_valid_key'] def test_custom_key(self): - created = make_key( - KC['N2'].code, - names=( - 'EURO', - '€', - ), - has_modifiers={KC['LSFT'].code, KC['ROPT'].code}, + KC._generators.append( + maybe_make_key( + KC.N2.code, + names=('EURO', '€'), + has_modifiers={KC.LSFT.code, KC.ROPT.code}, + ) ) - assert created is KC['EURO'] - assert created is KC['€'] - - def test_match_exactly_case(self): - created = make_key(names=('ThIs_Is_A_StRaNgE_kEy',)) - assert created is KC['ThIs_Is_A_StRaNgE_kEy'] + assert KC.get('€') is KC.get('EURO') class TestKeys_get(unittest.TestCase): @@ -224,20 +212,14 @@ class TestKeys_get(unittest.TestCase): assert KC.get('not_a_valid_key') is None def test_custom_key(self): - created = make_key( - KC.get('N2').code, - names=( - 'EURO', - '€', - ), - has_modifiers={KC.get('LSFT').code, KC.get('ROPT').code}, + KC._generators.append( + maybe_make_key( + KC.N2.code, + names=('EURO', '€'), + has_modifiers={KC.LSFT.code, KC.ROPT.code}, + ) ) - assert created is KC.get('EURO') - assert created is KC.get('€') - - def test_match_exactly_case(self): - created = make_key(names=('ThIs_Is_A_StRaNgE_kEy',)) - assert created is KC.get('ThIs_Is_A_StRaNgE_kEy') + assert KC.get('€') is KC.get('EURO') def test_underscore(self): assert KC.get('_') @@ -251,8 +233,8 @@ class TestKeys_instances(unittest.TestCase): KC.clear() def test_make_key_new_instance(self): - key1 = make_key(code=1) - key2 = make_key(code=1) + key1, _ = make_key(code=1) + key2, _ = make_key(code=1) assert key1 is not key2 assert key1.code == key2.code diff --git a/tests/test_layers.py b/tests/test_layers.py index 2a830d6..0040cfc 100644 --- a/tests/test_layers.py +++ b/tests/test_layers.py @@ -7,6 +7,7 @@ from tests.keyboard_test import KeyboardTest class TestLayers(unittest.TestCase): def setUp(self): + KC.clear() self.kb = KeyboardTest( [Layers()], [ diff --git a/tests/test_tapdance.py b/tests/test_tapdance.py index 0b35553..9129cf1 100644 --- a/tests/test_tapdance.py +++ b/tests/test_tapdance.py @@ -9,6 +9,7 @@ from tests.keyboard_test import KeyboardTest class TestTapDance(unittest.TestCase): def setUp(self): + KC.clear() self.keyboard = KeyboardTest( [Layers(), HoldTap(), TapDance()], [