Works towars typing keys.py
This commit is contained in:
parent
908da846fe
commit
36ea0eec4e
@ -1,3 +1,6 @@
|
|||||||
|
from kmk.keys import KeyAttrDict
|
||||||
|
from typing import Any, Optional
|
||||||
|
from kmk.kmk_keyboard import KMKKeyboard
|
||||||
from kmk.kmktime import sleep_ms
|
from kmk.kmktime import sleep_ms
|
||||||
|
|
||||||
|
|
||||||
@ -5,7 +8,15 @@ def passthrough(key, keyboard, *args, **kwargs):
|
|||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
|
|
||||||
def default_pressed(key, keyboard, KC, coord_int=None, coord_raw=None, *args, **kwargs):
|
def default_pressed(
|
||||||
|
key: str,
|
||||||
|
keyboard: KMKKeyboard,
|
||||||
|
KC: KeyAttrDict,
|
||||||
|
coord_int: Optional[int] = None,
|
||||||
|
coord_raw: Optional[str] = None,
|
||||||
|
*args: Any,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> KMKKeyboard:
|
||||||
keyboard.hid_pending = True
|
keyboard.hid_pending = True
|
||||||
|
|
||||||
if coord_int is not None:
|
if coord_int is not None:
|
||||||
@ -17,7 +28,13 @@ def default_pressed(key, keyboard, KC, coord_int=None, coord_raw=None, *args, **
|
|||||||
|
|
||||||
|
|
||||||
def default_released(
|
def default_released(
|
||||||
key, keyboard, KC, coord_int=None, coord_raw=None, *args, **kwargs # NOQA
|
key: str,
|
||||||
|
keyboard: KMKKeyboard,
|
||||||
|
KC: KeyAttrDict,
|
||||||
|
coord_int: Optional[int] = None,
|
||||||
|
coord_raw: Optional[str] = None,
|
||||||
|
*args: Any,
|
||||||
|
**kwargs: Any, # NOQA
|
||||||
):
|
):
|
||||||
keyboard.hid_pending = True
|
keyboard.hid_pending = True
|
||||||
keyboard.keys_pressed.discard(key)
|
keyboard.keys_pressed.discard(key)
|
||||||
|
153
kmk/keys.py
153
kmk/keys.py
@ -1,6 +1,11 @@
|
|||||||
import gc
|
import gc
|
||||||
|
from kmk.kmk_keyboard import KMKKeyboard
|
||||||
from micropython import const
|
from micropython import const
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Callable, List, Optional, Protocol, Set, Tuple, Union
|
||||||
|
|
||||||
import kmk.handlers.stock as handlers
|
import kmk.handlers.stock as handlers
|
||||||
from kmk.consts import UnicodeMode
|
from kmk.consts import UnicodeMode
|
||||||
from kmk.key_validators import (
|
from kmk.key_validators import (
|
||||||
@ -10,19 +15,29 @@ from kmk.key_validators import (
|
|||||||
)
|
)
|
||||||
from kmk.types import AttrDict, UnicodeModeKeyMeta
|
from kmk.types import AttrDict, UnicodeModeKeyMeta
|
||||||
|
|
||||||
DEBUG_OUTPUT = False
|
DEBUG_OUTPUT: bool = False
|
||||||
|
|
||||||
FIRST_KMK_INTERNAL_KEY = const(1000)
|
FIRST_KMK_INTERNAL_KEY: int = const(1000)
|
||||||
NEXT_AVAILABLE_KEY = 1000
|
NEXT_AVAILABLE_KEY: int = 1000
|
||||||
|
|
||||||
KEY_SIMPLE = const(0)
|
KEY_SIMPLE: int = const(0)
|
||||||
KEY_MODIFIER = const(1)
|
KEY_MODIFIER: int = const(1)
|
||||||
KEY_CONSUMER = const(2)
|
KEY_CONSUMER: int = const(2)
|
||||||
|
|
||||||
ALL_ALPHAS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
ALL_ALPHAS: str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||||
ALL_NUMBERS = '1234567890'
|
ALL_NUMBERS: str = '1234567890'
|
||||||
# since KC.1 isn't valid Python, alias to KC.N1
|
# since KC.1 isn't valid Python, alias to KC.N1
|
||||||
ALL_NUMBER_ALIASES = tuple(f'N{x}' for x in ALL_NUMBERS)
|
ALL_NUMBER_ALIASES: Tuple[str, ...] = tuple(f'N{x}' for x in ALL_NUMBERS)
|
||||||
|
|
||||||
|
|
||||||
|
KeyReturn = Union[Key, ModifierKey, ConsumerKey, None]
|
||||||
|
|
||||||
|
|
||||||
|
class LeftPipeCallback(Protocol):
|
||||||
|
def __call__(
|
||||||
|
self, candidate: str, code: Union[str, int], names: Tuple[str, ...]
|
||||||
|
) -> KeyReturn:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
class InfiniteLoopDetected(Exception):
|
class InfiniteLoopDetected(Exception):
|
||||||
@ -31,42 +46,53 @@ class InfiniteLoopDetected(Exception):
|
|||||||
|
|
||||||
# this is a bit of an FP style thing - combining a pipe operator a-la F# with
|
# this is a bit of an FP style thing - combining a pipe operator a-la F# with
|
||||||
# a bootleg Maybe monad to clean up these make_key sequences
|
# a bootleg Maybe monad to clean up these make_key sequences
|
||||||
def left_pipe_until_some(candidate, functor, *args_iter):
|
def left_pipe_until_some(
|
||||||
|
candidate: str,
|
||||||
|
functor: LeftPipeCallback,
|
||||||
|
*args_iter: Tuple[Union[str, int], Tuple[str, ...]],
|
||||||
|
) -> KeyReturn:
|
||||||
for args in args_iter:
|
for args in args_iter:
|
||||||
result = functor(candidate, *args)
|
result = functor(candidate, *args)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def first_truthy(candidate, *funcs):
|
def first_truthy(
|
||||||
|
candidate: str,
|
||||||
|
*funcs: Callable[[str], Union[Key, ModifierKey, ConsumerKey, None]],
|
||||||
|
) -> KeyReturn:
|
||||||
for func in funcs:
|
for func in funcs:
|
||||||
result = func(candidate)
|
result = func(candidate)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def maybe_make_mod_key(candidate, code, names):
|
def maybe_make_mod_key(candidate: str, code: int, names: Tuple[str, ...]) -> KeyReturn:
|
||||||
if candidate in names:
|
if candidate in names:
|
||||||
return make_mod_key(code=code, names=names)
|
return make_mod_key(code=code, names=names)
|
||||||
|
|
||||||
|
|
||||||
def maybe_make_key(candidate, code, names):
|
def maybe_make_key(candidate: str, code: int, names: Tuple[str, ...]) -> KeyReturn:
|
||||||
if candidate in names:
|
if candidate in names:
|
||||||
return make_key(code=code, names=names)
|
return make_key(code=code, names=names)
|
||||||
|
|
||||||
|
|
||||||
def maybe_make_shifted_key(candidate, target_name, names):
|
def maybe_make_shifted_key(
|
||||||
|
candidate: str, target_name: str, names: Tuple[str, ...]
|
||||||
|
) -> KeyReturn:
|
||||||
if candidate in names:
|
if candidate in names:
|
||||||
return make_shifted_key(target_name=target_name, names=names)
|
return make_shifted_key(target_name=target_name, names=names)
|
||||||
|
|
||||||
|
|
||||||
def maybe_make_consumer_key(candidate, code, names):
|
def maybe_make_consumer_key(
|
||||||
|
candidate: str, code: int, names: Tuple[str, ...]
|
||||||
|
) -> KeyReturn:
|
||||||
if candidate in names:
|
if candidate in names:
|
||||||
return make_consumer_key(code=code, names=names)
|
return make_consumer_key(code=code, names=names)
|
||||||
|
|
||||||
|
|
||||||
class KeyAttrDict(AttrDict):
|
class KeyAttrDict(AttrDict):
|
||||||
def __getattr__(self, key, depth=0):
|
def __getattr__(self, key: str, depth: int = 0) -> str:
|
||||||
if depth > 1:
|
if depth > 1:
|
||||||
raise InfiniteLoopDetected()
|
raise InfiniteLoopDetected()
|
||||||
|
|
||||||
@ -384,32 +410,54 @@ class KeyAttrDict(AttrDict):
|
|||||||
KC = KeyAttrDict()
|
KC = KeyAttrDict()
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultPressRelease(Protocol):
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
key: Key,
|
||||||
|
keyboard: KMKKeyboard,
|
||||||
|
KC: KeyAttrDict,
|
||||||
|
coord_int: Optional[int],
|
||||||
|
coord_raw: Optional[str],
|
||||||
|
*args: Any,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> KMKKeyboard:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
Handler = Callable[[Any, KMKKeyboard, KeyAttrDict, int, str], KMKKeyboard]
|
||||||
|
|
||||||
|
|
||||||
|
HandlerList = List[Handler]
|
||||||
|
|
||||||
|
|
||||||
class Key:
|
class Key:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
code,
|
code: int,
|
||||||
has_modifiers=None,
|
has_modifiers: Optional[Set[int]] = None,
|
||||||
no_press=False,
|
no_press: Optional[bool] = False,
|
||||||
no_release=False,
|
no_release: Optional[bool] = False,
|
||||||
on_press=handlers.default_pressed,
|
on_press: DefaultPressRelease = handlers.default_pressed,
|
||||||
on_release=handlers.default_released,
|
on_release: DefaultPressRelease = handlers.default_released,
|
||||||
meta=object(),
|
meta: object = object(),
|
||||||
):
|
) -> None:
|
||||||
self.code = code
|
self.code: int = code
|
||||||
self.has_modifiers = has_modifiers
|
self.has_modifiers: Optional[Set[int]] = has_modifiers
|
||||||
# cast to bool() in case we get a None value
|
# cast to bool() in case we get a None value
|
||||||
self.no_press = bool(no_press)
|
self.no_press: bool = bool(no_press)
|
||||||
self.no_release = bool(no_press)
|
self.no_release: bool = bool(no_press)
|
||||||
|
|
||||||
self._pre_press_handlers = []
|
self._pre_press_handlers: HandlerList = []
|
||||||
self._post_press_handlers = []
|
self._post_press_handlers: HandlerList = []
|
||||||
self._pre_release_handlers = []
|
self._pre_release_handlers: HandlerList = []
|
||||||
self._post_release_handlers = []
|
self._post_release_handlers: HandlerList = []
|
||||||
self._handle_press = on_press
|
self._handle_press: DefaultPressRelease = on_press
|
||||||
self._handle_release = on_release
|
self._handle_release: DefaultPressRelease = on_release
|
||||||
self.meta = meta
|
self.meta: object = meta
|
||||||
|
|
||||||
def __call__(self, no_press=None, no_release=None):
|
def __call__(
|
||||||
|
self, no_press: Optional[bool] = None, no_release: Optional[bool] = None
|
||||||
|
) -> Key:
|
||||||
if no_press is None and no_release is None:
|
if no_press is None and no_release is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@ -423,7 +471,9 @@ class Key:
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Key(code={}, has_modifiers={})'.format(self.code, self.has_modifiers)
|
return 'Key(code={}, has_modifiers={})'.format(self.code, self.has_modifiers)
|
||||||
|
|
||||||
def on_press(self, state, coord_int, coord_raw):
|
def on_press(
|
||||||
|
self, state: KMKKeyboard, coord_int: int, coord_raw: str
|
||||||
|
) -> Union[KMKKeyboard, None]:
|
||||||
for fn in self._pre_press_handlers:
|
for fn in self._pre_press_handlers:
|
||||||
if not fn(self, state, KC, coord_int, coord_raw):
|
if not fn(self, state, KC, coord_int, coord_raw):
|
||||||
return None
|
return None
|
||||||
@ -435,7 +485,9 @@ class Key:
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def on_release(self, state, coord_int, coord_raw):
|
def on_release(
|
||||||
|
self, state: KMKKeyboard, coord_int: int, coord_raw: str
|
||||||
|
) -> Union[KMKKeyboard, None]:
|
||||||
for fn in self._pre_release_handlers:
|
for fn in self._pre_release_handlers:
|
||||||
if not fn(self, state, KC, coord_int, coord_raw):
|
if not fn(self, state, KC, coord_int, coord_raw):
|
||||||
return None
|
return None
|
||||||
@ -447,7 +499,7 @@ class Key:
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def clone(self):
|
def clone(self) -> Key:
|
||||||
'''
|
'''
|
||||||
Return a shallow clone of the current key without any pre/post press/release
|
Return a shallow clone of the current key without any pre/post press/release
|
||||||
handlers attached. Almost exclusively useful for creating non-colliding keys
|
handlers attached. Almost exclusively useful for creating non-colliding keys
|
||||||
@ -464,7 +516,7 @@ class Key:
|
|||||||
meta=self.meta,
|
meta=self.meta,
|
||||||
)
|
)
|
||||||
|
|
||||||
def before_press_handler(self, fn):
|
def before_press_handler(self, fn: Handler) -> Key:
|
||||||
'''
|
'''
|
||||||
Attach a callback to be run prior to the on_press handler for this key.
|
Attach a callback to be run prior to the on_press handler for this key.
|
||||||
Receives the following:
|
Receives the following:
|
||||||
@ -488,7 +540,7 @@ class Key:
|
|||||||
self._pre_press_handlers.append(fn)
|
self._pre_press_handlers.append(fn)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def after_press_handler(self, fn):
|
def after_press_handler(self, fn: Handler) -> Key:
|
||||||
'''
|
'''
|
||||||
Attach a callback to be run after the on_release handler for this key.
|
Attach a callback to be run after the on_release handler for this key.
|
||||||
Receives the following:
|
Receives the following:
|
||||||
@ -511,7 +563,7 @@ class Key:
|
|||||||
self._post_press_handlers.append(fn)
|
self._post_press_handlers.append(fn)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def before_release_handler(self, fn):
|
def before_release_handler(self, fn: Handler) -> Key:
|
||||||
'''
|
'''
|
||||||
Attach a callback to be run prior to the on_release handler for this
|
Attach a callback to be run prior to the on_release handler for this
|
||||||
key. Receives the following:
|
key. Receives the following:
|
||||||
@ -535,7 +587,7 @@ class Key:
|
|||||||
self._pre_release_handlers.append(fn)
|
self._pre_release_handlers.append(fn)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def after_release_handler(self, fn):
|
def after_release_handler(self, fn: Handler) -> Key:
|
||||||
'''
|
'''
|
||||||
Attach a callback to be run after the on_release handler for this key.
|
Attach a callback to be run after the on_release handler for this key.
|
||||||
Receives the following:
|
Receives the following:
|
||||||
@ -563,9 +615,14 @@ class ModifierKey(Key):
|
|||||||
# FIXME this is atrocious to read. Please, please, please, strike down upon
|
# FIXME this is atrocious to read. Please, please, please, strike down upon
|
||||||
# this with great vengeance and furious anger.
|
# this with great vengeance and furious anger.
|
||||||
|
|
||||||
FAKE_CODE = const(-1)
|
FAKE_CODE: int = const(-1)
|
||||||
|
|
||||||
def __call__(self, modified_code=None, no_press=None, no_release=None):
|
def __call__(
|
||||||
|
self,
|
||||||
|
modified_code: Optional[Key] = None,
|
||||||
|
no_press: Optional[bool] = None,
|
||||||
|
no_release: Optional[bool] = None,
|
||||||
|
) -> Union[Key, ModifierKey]:
|
||||||
if modified_code is None and no_press is None and no_release is None:
|
if modified_code is None and no_press is None and no_release is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@ -598,7 +655,7 @@ class ModifierKey(Key):
|
|||||||
|
|
||||||
return new_keycode
|
return new_keycode
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return 'ModifierKey(code={}, has_modifiers={})'.format(
|
return 'ModifierKey(code={}, has_modifiers={})'.format(
|
||||||
self.code, self.has_modifiers
|
self.code, self.has_modifiers
|
||||||
)
|
)
|
||||||
@ -608,7 +665,7 @@ class ConsumerKey(Key):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def register_key_names(key, names=tuple()): # NOQA
|
def register_key_names(key: Key, names: Tuple[str, ...] = tuple()): # NOQA
|
||||||
'''
|
'''
|
||||||
Names are globally unique. If a later key is created with
|
Names are globally unique. If a later key is created with
|
||||||
the same name as an existing entry in `KC`, it will overwrite
|
the same name as an existing entry in `KC`, it will overwrite
|
||||||
@ -629,7 +686,7 @@ def register_key_names(key, names=tuple()): # NOQA
|
|||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
def make_key(code=None, names=tuple(), type=KEY_SIMPLE, **kwargs): # NOQA
|
def make_key(code: Optional[int] = None, names Tuple[str, ...] = tuple(), type: int = KEY_SIMPLE, **kwargs): # NOQA
|
||||||
'''
|
'''
|
||||||
Create a new key, aliased by `names` in the KC lookup table.
|
Create a new key, aliased by `names` in the KC lookup table.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user