From 6df56e538651a752f5ca18411a8d94dd959a777d Mon Sep 17 00:00:00 2001 From: xs5871 Date: Sat, 8 Jun 2024 19:57:01 +0000 Subject: [PATCH] Remove key pre/post handlers in favor of macros --- docs/en/keys.md | 114 ++++++--------------------------------- kmk/keys.py | 139 +++--------------------------------------------- 2 files changed, 22 insertions(+), 231 deletions(-) diff --git a/docs/en/keys.md b/docs/en/keys.md index 84da6b698..157a6e96d 100644 --- a/docs/en/keys.md +++ b/docs/en/keys.md @@ -6,6 +6,8 @@ you're stumped: [`kmk/keys.py`](/kmk/keys.py). --- +## Key Objects + This is a bunch of documentation about how a physical keypress translates to events (and the lifecycle of said events) in KMK. It's somewhat technical, but if you're looking to extend your keyboard's functionality with extra code, @@ -41,9 +43,6 @@ objects have a few core pieces of information: functions and some special override keys (like `KC.GESC`, which is an enhanced form of existing ANSI keys) in [`kmk/handlers/stock.py`](/kmk/handlers/stock.py). -- Optional callbacks to be run before and/or after the above handlers. More on - that soon. - - A generic `meta` field, which is most commonly used for "argumented" keys - objects in the `KC` object which are actually functions that return `Key` instances, which often need to access the arguments passed into the "outer" @@ -63,101 +62,20 @@ keyboard.keymap = [ ... CTRLSHFT ... ] ``` When a key is pressed and we've pulled a `Key` object out of the keymap, the -following will happen: - -- Pre-press callbacks will be run in the order they were assigned, with their - return values discarded (unless the user attached these, they will almost - never exist) -- The assigned press handler will be run (most commonly, this is provided by - KMK) -- Post-press callbacks will be run in the order they were assigned, with their - return values discarded (unless the user attached these, they will almost - never exist) - -These same steps are run for when a key is released. - -_So now... what's a handler, and what's a pre/post callback?!_ - -All of these serve roughly the same purpose: to _do something_ with the key's -data, or to fire off side effects. Most handlers are provided by KMK internally -and modify the `InternalState` in some way - adding the key to the HID queue, -changing layers, etc. The pre/post handlers are designed to allow functionality -to be bolted on at these points in the event flow without having to reimplement -(or import and manually call) the internal handlers. - -All of these methods take the same arguments, and for this, I'll lift a -docstring straight out of the source: - -> Receives the following: -> -> - self (this Key instance) -> - state (the current InternalState) -> - KC (the global KC lookup table, for convenience) -> - `coord_int` (an internal integer representation of the matrix coordinate -> for the pressed key - this is likely not useful to end users, but is -> provided for consistency with the internal handlers) -> - `coord_raw` (an X,Y tuple of the matrix coordinate - also likely not useful) -> -> The return value of the provided callback is discarded. Exceptions are _not_ -> caught, and will likely crash KMK if not handled within your function. -> -> These handlers are run in attachment order: handlers provided by earlier -> calls of this method will be executed before those provided by later calls. - -This means if you want to add things like underglow/LED support, or have a -button that triggers your GSM modem to call someone, or whatever else you can -hack up in CircuitPython, which also retaining layer-switching abilities or -whatever the stock handler is, you're covered. This also means you can add -completely new functionality to KMK by writing your own handler. - -Here's an example of an after_press_handler to change the RGB lights with a layer change: - -```python -LOWER = KC.DF(LYR_LOWER) #Set layer to LOWER - -def low_lights(key, keyboard, *args): - print('Lower Layer') #serial feedback - keyboard.pixels.set_hsv_fill(0, 100, 255) #RGB extension call to set (H,S,V) values - -LOWER.after_press_handler(low_lights) #call the key with the after_press_handler -``` - -Here's an example of a lifecycle hook to print a giant Shrek ASCII art. It -doesn't care about any of the arguments passed into it, because it has no -intentions of modifying the internal state. It is purely a [side -effect]() run every -time Left Alt is pressed: - -```python -def shrek(*args, **kwargs): - print('⢀⡴⠑⡄⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣤⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀') - print('⠸⡇⠀⠿⡀⠀⠀⠀⣀⡴⢿⣿⣿⣿⣿⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀') - print('⠀⠀⠀⠀⠑⢄⣠⠾⠁⣀⣄⡈⠙⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀') - print('⠀⠀⠀⠀⢀⡀⠁⠀⠀⠈⠙⠛⠂⠈⣿⣿⣿⣿⣿⠿⡿⢿⣆⠀⠀⠀⠀⠀⠀⠀') - print('⠀⠀⠀⢀⡾⣁⣀⠀⠴⠂⠙⣗⡀⠀⢻⣿⣿⠭⢤⣴⣦⣤⣹⠀⠀⠀⢀⢴⣶⣆') - print('⠀⠀⢀⣾⣿⣿⣿⣷⣮⣽⣾⣿⣥⣴⣿⣿⡿⢂⠔⢚⡿⢿⣿⣦⣴⣾⠁⠸⣼⡿') - print('⠀⢀⡞⠁⠙⠻⠿⠟⠉⠀⠛⢹⣿⣿⣿⣿⣿⣌⢤⣼⣿⣾⣿⡟⠉⠀⠀⠀⠀⠀') - print('⠀⣾⣷⣶⠇⠀⠀⣤⣄⣀⡀⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀') - print('⠀⠉⠈⠉⠀⠀⢦⡈⢻⣿⣿⣿⣶⣶⣶⣶⣤⣽⡹⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀') - print('⠀⠀⠀⠀⠀⠀⠀⠉⠲⣽⡻⢿⣿⣿⣿⣿⣿⣿⣷⣜⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀') - print('⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣷⣶⣮⣭⣽⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀') - print('⠀⠀⠀⠀⠀⠀⣀⣀⣈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀') - print('⠀⠀⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀') - print('⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀') - print('⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⠿⠿⠿⠿⠛⠉') - - return False #Returning True will follow thru the normal handlers sending the ALT key to the OS -KC.LALT.before_press_handler(shrek) -``` - -You can also copy a key without any pre/post handlers attached with `.clone()`, -so for example, if I've already added Shrek to my `LALT` but want a Shrek-less -`LALT` key elsewhere in my keymap, I can just clone it, and the new key won't -have my handlers attached: - -```python -SHREKLESS_ALT = KC.LALT.clone() -``` +`Key` is first passed through the module processing pipeline. +Modules can do whatever with that `Key`, but usually keys either pass right +through, or are intercepted and emitted again later (think of timing based +modules like Combos and Hold-Tap). +Finally the assigned press handler will be run (most commonly, this is provided +by KMK). +On release the `Key` object lookup is, most of the time, cached and doesn't +require searching the keymap again. +Then it's the processing pipeline again, followed by the release handler. + +Custom behavior can either be achieved with custom press and release handlers or +with [macros](docs/en/macros.md). + +## The Key Code Dictionary You can also refer to a key by index: diff --git a/kmk/keys.py b/kmk/keys.py index 79684860a..a256a8bc0 100644 --- a/kmk/keys.py +++ b/kmk/keys.py @@ -465,8 +465,8 @@ def __init__( self.has_modifiers = has_modifiers # cast to bool() in case we get a None value - self._handle_press = on_press - self._handle_release = on_release + self._on_press = on_press + self._on_release = on_release self.meta = meta def __call__(self) -> Key: @@ -476,137 +476,10 @@ def __repr__(self): return f'Key(code={self.code}, has_modifiers={self.has_modifiers})' def on_press(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None: - if hasattr(self, '_pre_press_handlers'): - for fn in self._pre_press_handlers: - if not fn(self, keyboard, KC, coord_int): - return - - self._handle_press(self, keyboard, KC, coord_int) - - if hasattr(self, '_post_press_handlers'): - for fn in self._post_press_handlers: - fn(self, keyboard, KC, coord_int) + self._on_press(self, keyboard, KC, coord_int) def on_release(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None: - if hasattr(self, '_pre_release_handlers'): - for fn in self._pre_release_handlers: - if not fn(self, keyboard, KC, coord_int): - return - - self._handle_release(self, keyboard, KC, coord_int) - - if hasattr(self, '_post_release_handlers'): - for fn in self._post_release_handlers: - fn(self, keyboard, KC, coord_int) - - def clone(self) -> Key: - ''' - Return a shallow clone of the current key without any pre/post press/release - handlers attached. Almost exclusively useful for creating non-colliding keys - to use such handlers. - ''' - - return type(self)( - code=self.code, - has_modifiers=self.has_modifiers, - on_press=self._handle_press, - on_release=self._handle_release, - meta=self.meta, - ) - - def before_press_handler(self, fn: Callable[[Key, Keyboard, ...], bool]) -> None: - ''' - Attach a callback to be run prior to the on_press handler for this key. - Receives the following: - - - self (this Key instance) - - state (the current InternalState) - - KC (the global KC lookup table, for convenience) - - coord_int (an internal integer representation of the matrix coordinate - for the pressed key - this is likely not useful to end users, but is - provided for consistency with the internal handlers) - - If return value of the provided callback is evaluated to False, press - processing is cancelled. Exceptions are _not_ caught, and will likely - crash KMK if not handled within your function. - - These handlers are run in attachment order: handlers provided by earlier - calls of this method will be executed before those provided by later calls. - ''' - - if not hasattr(self, '_pre_press_handlers'): - self._pre_press_handlers = [] - self._pre_press_handlers.append(fn) - - def after_press_handler(self, fn: Callable[[Key, Keyboard, ...], bool]) -> None: - ''' - Attach a callback to be run after the on_release handler for this key. - Receives the following: - - - self (this Key instance) - - state (the current InternalState) - - KC (the global KC lookup table, for convenience) - - coord_int (an internal integer representation of the matrix coordinate - for the pressed key - this is likely not useful to end users, but is - provided for consistency with the internal handlers) - - The return value of the provided callback is discarded. Exceptions are _not_ - caught, and will likely crash KMK if not handled within your function. - - These handlers are run in attachment order: handlers provided by earlier - calls of this method will be executed before those provided by later calls. - ''' - - if not hasattr(self, '_post_press_handlers'): - self._post_press_handlers = [] - self._post_press_handlers.append(fn) - - def before_release_handler(self, fn: Callable[[Key, Keyboard, ...], bool]) -> None: - ''' - Attach a callback to be run prior to the on_release handler for this - key. Receives the following: - - - self (this Key instance) - - state (the current InternalState) - - KC (the global KC lookup table, for convenience) - - coord_int (an internal integer representation of the matrix coordinate - for the pressed key - this is likely not useful to end users, but is - provided for consistency with the internal handlers) - - If return value of the provided callback evaluates to False, the release - processing is cancelled. Exceptions are _not_ caught, and will likely crash - KMK if not handled within your function. - - These handlers are run in attachment order: handlers provided by earlier - calls of this method will be executed before those provided by later calls. - ''' - - if not hasattr(self, '_pre_release_handlers'): - self._pre_release_handlers = [] - self._pre_release_handlers.append(fn) - - def after_release_handler(self, fn: Callable[[Key, Keyboard, ...], bool]) -> None: - ''' - Attach a callback to be run after the on_release handler for this key. - Receives the following: - - - self (this Key instance) - - state (the current InternalState) - - KC (the global KC lookup table, for convenience) - - coord_int (an internal integer representation of the matrix coordinate - for the pressed key - this is likely not useful to end users, but is - provided for consistency with the internal handlers) - - The return value of the provided callback is discarded. Exceptions are _not_ - caught, and will likely crash KMK if not handled within your function. - - These handlers are run in attachment order: handlers provided by earlier - calls of this method will be executed before those provided by later calls. - ''' - - if not hasattr(self, '_post_release_handlers'): - self._post_release_handlers = [] - self._post_release_handlers.append(fn) + self._on_release(self, keyboard, KC, coord_int) class ModifierKey(Key): @@ -637,8 +510,8 @@ def __call__( return type(modified_key)( code=code, has_modifiers=modifiers, - on_press=modified_key._handle_press, - on_release=modified_key._handle_release, + on_press=modified_key._on_press, + on_release=modified_key._on_release, meta=modified_key.meta, )