add type hinting for kmk.keys

This commit is contained in:
xs5871 2022-09-23 17:43:19 +00:00 committed by Kyle Brown
parent 2f0134eed3
commit 0fbba96026

View File

@ -1,3 +1,8 @@
try:
from typing import Callable, Optional, Tuple
except ImportError:
pass
from micropython import const from micropython import const
import kmk.handlers.stock as handlers import kmk.handlers.stock as handlers
@ -6,13 +11,20 @@ from kmk.key_validators import key_seq_sleep_validator, unicode_mode_key_validat
from kmk.types import UnicodeModeKeyMeta from kmk.types import UnicodeModeKeyMeta
from kmk.utils import Debug from kmk.utils import Debug
# Type aliases / forward declaration; can't use the proper types because of circular imports.
Keyboard = object
Key = object
class KeyType:
SIMPLE = const(0)
MODIFIER = const(1)
CONSUMER = const(2)
FIRST_KMK_INTERNAL_KEY = const(1000) FIRST_KMK_INTERNAL_KEY = const(1000)
NEXT_AVAILABLE_KEY = 1000 NEXT_AVAILABLE_KEY = 1000
KEY_SIMPLE = const(0)
KEY_MODIFIER = const(1)
KEY_CONSUMER = const(2)
ALL_ALPHAS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ALL_ALPHAS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ALL_NUMBERS = '1234567890' ALL_NUMBERS = '1234567890'
# since KC.1 isn't valid Python, alias to KC.N1 # since KC.1 isn't valid Python, alias to KC.N1
@ -21,7 +33,12 @@ ALL_NUMBER_ALIASES = tuple(f'N{x}' for x in ALL_NUMBERS)
debug = Debug(__name__) debug = Debug(__name__)
def maybe_make_key(code, names, *args, **kwargs): def maybe_make_key(
code: Optional[int],
names: Tuple[str, ...],
*args,
**kwargs,
) -> Callable[[str], Key]:
def closure(candidate): def closure(candidate):
if candidate in names: if candidate in names:
return make_key(code=code, names=names, *args, **kwargs) return make_key(code=code, names=names, *args, **kwargs)
@ -31,10 +48,10 @@ def maybe_make_key(code, names, *args, **kwargs):
def maybe_make_argumented_key( def maybe_make_argumented_key(
validator=lambda *validator_args, **validator_kwargs: object(), validator=lambda *validator_args, **validator_kwargs: object(),
names=tuple(), # NOQA names: Tuple[str, ...] = tuple(), # NOQA
*constructor_args, *constructor_args,
**constructor_kwargs, **constructor_kwargs,
): ) -> Callable[[str], Key]:
def closure(candidate): def closure(candidate):
if candidate in names: if candidate in names:
return make_argumented_key( return make_argumented_key(
@ -44,7 +61,7 @@ def maybe_make_argumented_key(
return closure return closure
def maybe_make_no_key(candidate): def maybe_make_no_key(candidate: str) -> Optional[Key]:
# NO and TRNS are functionally identical in how they (don't) mutate # NO and TRNS are functionally identical in how they (don't) mutate
# the state, but are tracked semantically separately, so create # the state, but are tracked semantically separately, so create
# two keys with the exact same functionality # two keys with the exact same functionality
@ -62,7 +79,7 @@ def maybe_make_no_key(candidate):
) )
def maybe_make_alpha_key(candidate): def maybe_make_alpha_key(candidate: str) -> Optional[Key]:
if len(candidate) != 1: if len(candidate) != 1:
return return
@ -74,7 +91,7 @@ def maybe_make_alpha_key(candidate):
) )
def maybe_make_numeric_key(candidate): def maybe_make_numeric_key(candidate: str) -> Optional[Key]:
if candidate in ALL_NUMBERS or candidate in ALL_NUMBER_ALIASES: if candidate in ALL_NUMBERS or candidate in ALL_NUMBER_ALIASES:
try: try:
offset = ALL_NUMBERS.index(candidate) offset = ALL_NUMBERS.index(candidate)
@ -87,7 +104,7 @@ def maybe_make_numeric_key(candidate):
) )
def maybe_make_mod_key(candidate): def maybe_make_mod_key(candidate: str) -> Optional[Key]:
# MEH = LCTL | LALT | LSFT # MEH = LCTL | LALT | LSFT
# HYPR = LCTL | LALT | LSFT | LGUI # HYPR = LCTL | LALT | LSFT | LGUI
mods = ( mods = (
@ -105,10 +122,10 @@ def maybe_make_mod_key(candidate):
for code, names in mods: for code, names in mods:
if candidate in names: if candidate in names:
return make_key(code=code, names=names, type=KEY_MODIFIER) return make_key(code=code, names=names, type=KeyType.MODIFIER)
def maybe_make_more_ascii(candidate): def maybe_make_more_ascii(candidate: str) -> Optional[Key]:
codes = ( codes = (
(40, ('ENTER', 'ENT', '\n')), (40, ('ENTER', 'ENT', '\n')),
(41, ('ESCAPE', 'ESC')), (41, ('ESCAPE', 'ESC')),
@ -133,7 +150,7 @@ def maybe_make_more_ascii(candidate):
return make_key(code=code, names=names) return make_key(code=code, names=names)
def maybe_make_fn_key(candidate): def maybe_make_fn_key(candidate: str) -> Optional[Key]:
codes = ( codes = (
(58, ('F1',)), (58, ('F1',)),
(59, ('F2',)), (59, ('F2',)),
@ -166,7 +183,7 @@ def maybe_make_fn_key(candidate):
return make_key(code=code, names=names) return make_key(code=code, names=names)
def maybe_make_navlock_key(candidate): def maybe_make_navlock_key(candidate: str) -> Optional[Key]:
codes = ( codes = (
(57, ('CAPS_LOCK', 'CAPSLOCK', 'CLCK', 'CAPS')), (57, ('CAPS_LOCK', 'CAPSLOCK', 'CLCK', 'CAPS')),
# FIXME: Investigate whether this key actually works, and # FIXME: Investigate whether this key actually works, and
@ -195,7 +212,7 @@ def maybe_make_navlock_key(candidate):
return make_key(code=code, names=names) return make_key(code=code, names=names)
def maybe_make_numpad_key(candidate): def maybe_make_numpad_key(candidate: str) -> Optional[Key]:
codes = ( codes = (
(83, ('NUM_LOCK', 'NUMLOCK', 'NLCK')), (83, ('NUM_LOCK', 'NUMLOCK', 'NLCK')),
(84, ('KP_SLASH', 'NUMPAD_SLASH', 'PSLS')), (84, ('KP_SLASH', 'NUMPAD_SLASH', 'PSLS')),
@ -224,7 +241,7 @@ def maybe_make_numpad_key(candidate):
return make_key(code=code, names=names) return make_key(code=code, names=names)
def maybe_make_shifted_key(candidate): def maybe_make_shifted_key(candidate: str) -> Optional[Key]:
codes = ( codes = (
(30, ('EXCLAIM', 'EXLM', '!')), (30, ('EXCLAIM', 'EXLM', '!')),
(31, ('AT', '@')), (31, ('AT', '@')),
@ -254,7 +271,7 @@ def maybe_make_shifted_key(candidate):
return make_key(code=code, names=names, has_modifiers={KC.LSFT.code}) return make_key(code=code, names=names, has_modifiers={KC.LSFT.code})
def maybe_make_international_key(candidate): def maybe_make_international_key(candidate: str) -> Optional[Key]:
codes = ( codes = (
(50, ('NONUS_HASH', 'NUHS')), (50, ('NONUS_HASH', 'NUHS')),
(100, ('NONUS_BSLASH', 'NUBS')), (100, ('NONUS_BSLASH', 'NUBS')),
@ -284,7 +301,7 @@ def maybe_make_international_key(candidate):
return make_key(code=code, names=names) return make_key(code=code, names=names)
def maybe_make_unicode_key(candidate): def maybe_make_unicode_key(candidate: str) -> Optional[Key]:
keys = ( keys = (
( (
('UC_MODE_NOOP', 'UC_DISABLE'), ('UC_MODE_NOOP', 'UC_DISABLE'),
@ -320,7 +337,7 @@ def maybe_make_unicode_key(candidate):
) )
def maybe_make_firmware_key(candidate): def maybe_make_firmware_key(candidate: str) -> Optional[Key]:
keys = ( keys = (
((('BLE_REFRESH',), handlers.ble_refresh)), ((('BLE_REFRESH',), handlers.ble_refresh)),
((('BOOTLOADER',), handlers.bootloader)), ((('BOOTLOADER',), handlers.bootloader)),
@ -388,13 +405,13 @@ class KeyAttrDict:
def __iter__(self): def __iter__(self):
return self.__cache.__iter__() return self.__cache.__iter__()
def __setitem__(self, key, value): def __setitem__(self, key: str, value: Key):
self.__cache.__setitem__(key, value) self.__cache.__setitem__(key, value)
def __getattr__(self, key): def __getattr__(self, key: Key):
return self.__getitem__(key) return self.__getitem__(key)
def get(self, key, default=None): def get(self, key: Key, default: Optional[Key] = None):
try: try:
return self.__getitem__(key) return self.__getitem__(key)
except Exception: except Exception:
@ -403,7 +420,7 @@ class KeyAttrDict:
def clear(self): def clear(self):
self.__cache.clear() self.__cache.clear()
def __getitem__(self, key): def __getitem__(self, key: Key):
try: try:
return self.__cache[key] return self.__cache[key]
except KeyError: except KeyError:
@ -430,13 +447,17 @@ KC = KeyAttrDict()
class Key: class Key:
def __init__( def __init__(
self, self,
code, code: int,
has_modifiers=None, has_modifiers: Optional[list[Key, ...]] = None,
no_press=False, no_press: bool = False,
no_release=False, no_release: bool = False,
on_press=handlers.default_pressed, on_press: Callable[
on_release=handlers.default_released, [object, Key, Keyboard, ...], None
meta=object(), ] = handlers.default_pressed,
on_release: Callable[
[object, Key, Keyboard, ...], None
] = handlers.default_released,
meta: object = object(),
): ):
self.code = code self.code = code
self.has_modifiers = has_modifiers self.has_modifiers = has_modifiers
@ -448,7 +469,9 @@ class Key:
self._handle_release = on_release self._handle_release = on_release
self.meta = meta self.meta = 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
@ -465,35 +488,31 @@ class Key:
def __repr__(self): def __repr__(self):
return f'Key(code={self.code}, has_modifiers={self.has_modifiers})' return f'Key(code={self.code}, has_modifiers={self.has_modifiers})'
def on_press(self, state, coord_int=None): def on_press(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None:
if hasattr(self, '_pre_press_handlers'): if hasattr(self, '_pre_press_handlers'):
for fn in self._pre_press_handlers: for fn in self._pre_press_handlers:
if not fn(self, state, KC, coord_int): if not fn(self, keyboard, KC, coord_int):
return None return
ret = self._handle_press(self, state, KC, coord_int) self._handle_press(self, keyboard, KC, coord_int)
if hasattr(self, '_post_press_handlers'): if hasattr(self, '_post_press_handlers'):
for fn in self._post_press_handlers: for fn in self._post_press_handlers:
fn(self, state, KC, coord_int) fn(self, keyboard, KC, coord_int)
return ret def on_release(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None:
def on_release(self, state, coord_int=None):
if hasattr(self, '_pre_release_handlers'): if hasattr(self, '_pre_release_handlers'):
for fn in self._pre_release_handlers: for fn in self._pre_release_handlers:
if not fn(self, state, KC, coord_int): if not fn(self, keyboard, KC, coord_int):
return None return
ret = self._handle_release(self, state, KC, coord_int) self._handle_release(self, keyboard, KC, coord_int)
if hasattr(self, '_post_release_handlers'): if hasattr(self, '_post_release_handlers'):
for fn in self._post_release_handlers: for fn in self._post_release_handlers:
fn(self, state, KC, coord_int) fn(self, keyboard, KC, coord_int)
return ret def clone(self) -> Key:
def clone(self):
''' '''
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
@ -510,7 +529,7 @@ class Key:
meta=self.meta, meta=self.meta,
) )
def before_press_handler(self, fn): 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. Attach a callback to be run prior to the on_press handler for this key.
Receives the following: Receives the following:
@ -533,9 +552,8 @@ class Key:
if not hasattr(self, '_pre_press_handlers'): if not hasattr(self, '_pre_press_handlers'):
self._pre_press_handlers = [] self._pre_press_handlers = []
self._pre_press_handlers.append(fn) self._pre_press_handlers.append(fn)
return self
def after_press_handler(self, 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. Attach a callback to be run after the on_release handler for this key.
Receives the following: Receives the following:
@ -557,9 +575,8 @@ class Key:
if not hasattr(self, '_post_press_handlers'): if not hasattr(self, '_post_press_handlers'):
self._post_press_handlers = [] self._post_press_handlers = []
self._post_press_handlers.append(fn) self._post_press_handlers.append(fn)
return self
def before_release_handler(self, 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 Attach a callback to be run prior to the on_release handler for this
key. Receives the following: key. Receives the following:
@ -582,9 +599,8 @@ class Key:
if not hasattr(self, '_pre_release_handlers'): if not hasattr(self, '_pre_release_handlers'):
self._pre_release_handlers = [] self._pre_release_handlers = []
self._pre_release_handlers.append(fn) self._pre_release_handlers.append(fn)
return self
def after_release_handler(self, 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. Attach a callback to be run after the on_release handler for this key.
Receives the following: Receives the following:
@ -606,13 +622,17 @@ class Key:
if not hasattr(self, '_post_release_handlers'): if not hasattr(self, '_post_release_handlers'):
self._post_release_handlers = [] self._post_release_handlers = []
self._post_release_handlers.append(fn) self._post_release_handlers.append(fn)
return self
class ModifierKey(Key): class ModifierKey(Key):
FAKE_CODE = const(-1) FAKE_CODE = const(-1)
def __call__(self, modified_key=None, no_press=None, no_release=None): def __call__(
self,
modified_key: Optional[Key] = None,
no_press: Optional[bool] = None,
no_release: Optional[bool] = None,
) -> Key:
if modified_key is None: if modified_key is None:
return super().__call__(no_press=no_press, no_release=no_release) return super().__call__(no_press=no_press, no_release=no_release)
@ -649,7 +669,12 @@ class ConsumerKey(Key):
pass pass
def make_key(code=None, names=tuple(), type=KEY_SIMPLE, **kwargs): # NOQA def make_key(
code: Optional[int] = None,
names: Tuple[str, ...] = tuple(), # NOQA
type: KeyType = KeyType.SIMPLE,
**kwargs,
) -> Key:
''' '''
Create a new key, aliased by `names` in the KC lookup table. Create a new key, aliased by `names` in the KC lookup table.
@ -668,11 +693,11 @@ def make_key(code=None, names=tuple(), type=KEY_SIMPLE, **kwargs): # NOQA
global NEXT_AVAILABLE_KEY global NEXT_AVAILABLE_KEY
if type == KEY_SIMPLE: if type == KeyType.SIMPLE:
constructor = Key constructor = Key
elif type == KEY_MODIFIER: elif type == KeyType.MODIFIER:
constructor = ModifierKey constructor = ModifierKey
elif type == KEY_CONSUMER: elif type == KeyType.CONSUMER:
constructor = ConsumerKey constructor = ConsumerKey
else: else:
raise ValueError('Unrecognized key type') raise ValueError('Unrecognized key type')
@ -694,29 +719,29 @@ def make_key(code=None, names=tuple(), type=KEY_SIMPLE, **kwargs): # NOQA
return key return key
def make_mod_key(code, names, *args, **kwargs): def make_mod_key(code: int, names: Tuple[str, ...], *args, **kwargs) -> Key:
return make_key(code, names, *args, **kwargs, type=KEY_MODIFIER) return make_key(code, names, *args, **kwargs, type=KeyType.MODIFIER)
def make_shifted_key(code, names): def make_shifted_key(code: int, names: Tuple[str, ...]) -> Key:
return make_key(code, names, has_modifiers={KC.LSFT.code}) return make_key(code, names, has_modifiers={KC.LSFT.code})
def make_consumer_key(*args, **kwargs): def make_consumer_key(*args, **kwargs) -> Key:
return make_key(*args, **kwargs, type=KEY_CONSUMER) return make_key(*args, **kwargs, type=KeyType.CONSUMER)
# Argumented keys are implicitly internal, so auto-gen of code # Argumented keys are implicitly internal, so auto-gen of code
# is almost certainly the best plan here # is almost certainly the best plan here
def make_argumented_key( def make_argumented_key(
validator=lambda *validator_args, **validator_kwargs: object(), validator: object = lambda *validator_args, **validator_kwargs: object(),
names=tuple(), # NOQA names: Tuple[str, ...] = tuple(), # NOQA
*constructor_args, *constructor_args,
**constructor_kwargs, **constructor_kwargs,
): ) -> Key:
global NEXT_AVAILABLE_KEY global NEXT_AVAILABLE_KEY
def _argumented_key(*user_args, **user_kwargs): def _argumented_key(*user_args, **user_kwargs) -> Key:
global NEXT_AVAILABLE_KEY global NEXT_AVAILABLE_KEY
meta = validator(*user_args, **user_kwargs) meta = validator(*user_args, **user_kwargs)