2022-09-23 19:43:19 +02:00
|
|
|
try:
|
|
|
|
from typing import Callable, Optional, Tuple
|
|
|
|
except ImportError:
|
|
|
|
pass
|
|
|
|
|
2020-10-21 21:19:42 +02:00
|
|
|
from micropython import const
|
|
|
|
|
2018-12-29 13:44:52 +01:00
|
|
|
import kmk.handlers.stock as handlers
|
2018-12-05 02:03:13 +01:00
|
|
|
from kmk.consts import UnicodeMode
|
2022-01-18 06:21:05 +01:00
|
|
|
from kmk.key_validators import key_seq_sleep_validator, unicode_mode_key_validator
|
2022-04-22 20:24:18 +02:00
|
|
|
from kmk.types import UnicodeModeKeyMeta
|
2022-07-20 15:35:56 +02:00
|
|
|
from kmk.utils import Debug
|
2021-07-07 23:44:44 +02:00
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
# 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)
|
2022-10-21 23:19:56 +02:00
|
|
|
MOUSE = const(3)
|
2022-09-23 19:43:19 +02:00
|
|
|
|
|
|
|
|
2020-10-21 21:19:42 +02:00
|
|
|
FIRST_KMK_INTERNAL_KEY = const(1000)
|
2018-12-30 00:29:11 +01:00
|
|
|
NEXT_AVAILABLE_KEY = 1000
|
2018-09-23 06:49:58 +02:00
|
|
|
|
2021-07-07 23:44:44 +02:00
|
|
|
ALL_ALPHAS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
|
|
ALL_NUMBERS = '1234567890'
|
|
|
|
# since KC.1 isn't valid Python, alias to KC.N1
|
|
|
|
ALL_NUMBER_ALIASES = tuple(f'N{x}' for x in ALL_NUMBERS)
|
|
|
|
|
2022-07-20 15:35:56 +02:00
|
|
|
debug = Debug(__name__)
|
|
|
|
|
2022-07-21 16:38:24 +02:00
|
|
|
|
2022-10-16 21:39:02 +02:00
|
|
|
class Axis:
|
|
|
|
def __init__(self, code: int) -> None:
|
|
|
|
self.code = code
|
|
|
|
self.delta = 0
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f'Axis(code={self.code}, delta={self.delta})'
|
|
|
|
|
2022-10-22 22:49:12 +02:00
|
|
|
def move(self, keyboard: Keyboard, delta: int):
|
|
|
|
self.delta += delta
|
2023-02-11 22:23:03 +01:00
|
|
|
if self.delta:
|
|
|
|
keyboard.axes.add(self)
|
|
|
|
keyboard.hid_pending = True
|
|
|
|
else:
|
|
|
|
keyboard.axes.discard(self)
|
|
|
|
|
|
|
|
|
|
|
|
class AX:
|
|
|
|
W = Axis(2)
|
|
|
|
X = Axis(0)
|
|
|
|
Y = Axis(1)
|
2022-10-22 22:49:12 +02:00
|
|
|
|
2022-10-16 21:39:02 +02:00
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_key(
|
|
|
|
code: Optional[int],
|
|
|
|
names: Tuple[str, ...],
|
|
|
|
*args,
|
|
|
|
**kwargs,
|
|
|
|
) -> Callable[[str], Key]:
|
2022-07-21 00:29:01 +02:00
|
|
|
def closure(candidate):
|
|
|
|
if candidate in names:
|
|
|
|
return make_key(code=code, names=names, *args, **kwargs)
|
2021-07-07 23:44:44 +02:00
|
|
|
|
2022-07-21 00:29:01 +02:00
|
|
|
return closure
|
2021-07-07 23:44:44 +02:00
|
|
|
|
|
|
|
|
2022-07-21 00:29:01 +02:00
|
|
|
def maybe_make_argumented_key(
|
|
|
|
validator=lambda *validator_args, **validator_kwargs: object(),
|
2022-09-23 19:43:19 +02:00
|
|
|
names: Tuple[str, ...] = tuple(), # NOQA
|
2022-07-21 00:29:01 +02:00
|
|
|
*constructor_args,
|
|
|
|
**constructor_kwargs,
|
2022-09-23 19:43:19 +02:00
|
|
|
) -> Callable[[str], Key]:
|
2022-07-21 00:29:01 +02:00
|
|
|
def closure(candidate):
|
|
|
|
if candidate in names:
|
|
|
|
return make_argumented_key(
|
|
|
|
validator, names, *constructor_args, **constructor_kwargs
|
|
|
|
)
|
2021-07-07 23:44:44 +02:00
|
|
|
|
2022-07-21 00:29:01 +02:00
|
|
|
return closure
|
2021-07-07 23:44:44 +02:00
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_no_key(candidate: str) -> Optional[Key]:
|
2022-07-21 17:12:55 +02:00
|
|
|
# NO and TRNS are functionally identical in how they (don't) mutate
|
|
|
|
# the state, but are tracked semantically separately, so create
|
|
|
|
# two keys with the exact same functionality
|
|
|
|
keys = (
|
|
|
|
('NO', 'XXXXXXX'),
|
|
|
|
('TRANSPARENT', 'TRNS'),
|
|
|
|
)
|
|
|
|
|
|
|
|
for names in keys:
|
|
|
|
if candidate in names:
|
|
|
|
return make_key(
|
|
|
|
names=names,
|
|
|
|
on_press=handlers.passthrough,
|
|
|
|
on_release=handlers.passthrough,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_alpha_key(candidate: str) -> Optional[Key]:
|
2022-07-21 00:29:01 +02:00
|
|
|
if len(candidate) != 1:
|
|
|
|
return
|
2021-07-07 23:44:44 +02:00
|
|
|
|
2022-07-21 00:29:01 +02:00
|
|
|
candidate_upper = candidate.upper()
|
|
|
|
if candidate_upper in ALL_ALPHAS:
|
|
|
|
return make_key(
|
|
|
|
code=4 + ALL_ALPHAS.index(candidate_upper),
|
|
|
|
names=(candidate_upper, candidate.lower()),
|
|
|
|
)
|
2021-07-07 23:44:44 +02:00
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_numeric_key(candidate: str) -> Optional[Key]:
|
2022-07-21 00:29:01 +02:00
|
|
|
if candidate in ALL_NUMBERS or candidate in ALL_NUMBER_ALIASES:
|
|
|
|
try:
|
|
|
|
offset = ALL_NUMBERS.index(candidate)
|
|
|
|
except ValueError:
|
|
|
|
offset = ALL_NUMBER_ALIASES.index(candidate)
|
2021-07-07 23:44:44 +02:00
|
|
|
|
2022-07-21 00:29:01 +02:00
|
|
|
return make_key(
|
|
|
|
code=30 + offset,
|
|
|
|
names=(ALL_NUMBERS[offset], ALL_NUMBER_ALIASES[offset]),
|
|
|
|
)
|
2021-07-07 23:44:44 +02:00
|
|
|
|
2022-07-21 00:29:01 +02:00
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_mod_key(candidate: str) -> Optional[Key]:
|
2022-07-21 00:29:01 +02:00
|
|
|
# MEH = LCTL | LALT | LSFT
|
|
|
|
# HYPR = LCTL | LALT | LSFT | LGUI
|
|
|
|
mods = (
|
|
|
|
(0x01, ('LEFT_CONTROL', 'LCTRL', 'LCTL')),
|
|
|
|
(0x02, ('LEFT_SHIFT', 'LSHIFT', 'LSFT')),
|
|
|
|
(0x04, ('LEFT_ALT', 'LALT', 'LOPT')),
|
|
|
|
(0x08, ('LEFT_SUPER', 'LGUI', 'LCMD', 'LWIN')),
|
|
|
|
(0x10, ('RIGHT_CONTROL', 'RCTRL', 'RCTL')),
|
|
|
|
(0x20, ('RIGHT_SHIFT', 'RSHIFT', 'RSFT')),
|
|
|
|
(0x40, ('RIGHT_ALT', 'RALT', 'ROPT')),
|
|
|
|
(0x80, ('RIGHT_SUPER', 'RGUI', 'RCMD', 'RWIN')),
|
|
|
|
(0x07, ('MEH',)),
|
|
|
|
(0x0F, ('HYPER', 'HYPR')),
|
|
|
|
)
|
|
|
|
|
|
|
|
for code, names in mods:
|
|
|
|
if candidate in names:
|
2022-09-23 19:43:19 +02:00
|
|
|
return make_key(code=code, names=names, type=KeyType.MODIFIER)
|
2022-07-21 00:29:01 +02:00
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_more_ascii(candidate: str) -> Optional[Key]:
|
2022-07-21 00:29:01 +02:00
|
|
|
codes = (
|
|
|
|
(40, ('ENTER', 'ENT', '\n')),
|
|
|
|
(41, ('ESCAPE', 'ESC')),
|
|
|
|
(42, ('BACKSPACE', 'BSPACE', 'BSPC', 'BKSP')),
|
|
|
|
(43, ('TAB', '\t')),
|
|
|
|
(44, ('SPACE', 'SPC', ' ')),
|
|
|
|
(45, ('MINUS', 'MINS', '-')),
|
|
|
|
(46, ('EQUAL', 'EQL', '=')),
|
|
|
|
(47, ('LBRACKET', 'LBRC', '[')),
|
|
|
|
(48, ('RBRACKET', 'RBRC', ']')),
|
|
|
|
(49, ('BACKSLASH', 'BSLASH', 'BSLS', '\\')),
|
|
|
|
(51, ('SEMICOLON', 'SCOLON', 'SCLN', ';')),
|
|
|
|
(52, ('QUOTE', 'QUOT', "'")),
|
|
|
|
(53, ('GRAVE', 'GRV', 'ZKHK', '`')),
|
|
|
|
(54, ('COMMA', 'COMM', ',')),
|
|
|
|
(55, ('DOT', '.')),
|
|
|
|
(56, ('SLASH', 'SLSH', '/')),
|
|
|
|
)
|
|
|
|
|
|
|
|
for code, names in codes:
|
|
|
|
if candidate in names:
|
|
|
|
return make_key(code=code, names=names)
|
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_fn_key(candidate: str) -> Optional[Key]:
|
2022-07-21 00:29:01 +02:00
|
|
|
codes = (
|
|
|
|
(58, ('F1',)),
|
|
|
|
(59, ('F2',)),
|
|
|
|
(60, ('F3',)),
|
|
|
|
(61, ('F4',)),
|
|
|
|
(62, ('F5',)),
|
|
|
|
(63, ('F6',)),
|
|
|
|
(64, ('F7',)),
|
|
|
|
(65, ('F8',)),
|
|
|
|
(66, ('F9',)),
|
|
|
|
(67, ('F10',)),
|
|
|
|
(68, ('F11',)),
|
|
|
|
(69, ('F12',)),
|
|
|
|
(104, ('F13',)),
|
|
|
|
(105, ('F14',)),
|
|
|
|
(106, ('F15',)),
|
|
|
|
(107, ('F16',)),
|
|
|
|
(108, ('F17',)),
|
|
|
|
(109, ('F18',)),
|
|
|
|
(110, ('F19',)),
|
|
|
|
(111, ('F20',)),
|
|
|
|
(112, ('F21',)),
|
|
|
|
(113, ('F22',)),
|
|
|
|
(114, ('F23',)),
|
|
|
|
(115, ('F24',)),
|
|
|
|
)
|
|
|
|
|
|
|
|
for code, names in codes:
|
|
|
|
if candidate in names:
|
|
|
|
return make_key(code=code, names=names)
|
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_navlock_key(candidate: str) -> Optional[Key]:
|
2022-07-21 00:29:01 +02:00
|
|
|
codes = (
|
|
|
|
(57, ('CAPS_LOCK', 'CAPSLOCK', 'CLCK', 'CAPS')),
|
|
|
|
# FIXME: Investigate whether this key actually works, and
|
|
|
|
# uncomment when/if it does.
|
|
|
|
# (130, ('LOCKING_CAPS', 'LCAP')),
|
|
|
|
(70, ('PRINT_SCREEN', 'PSCREEN', 'PSCR')),
|
|
|
|
(71, ('SCROLL_LOCK', 'SCROLLLOCK', 'SLCK')),
|
|
|
|
# FIXME: Investigate whether this key actually works, and
|
|
|
|
# uncomment when/if it does.
|
|
|
|
# (132, ('LOCKING_SCROLL', 'LSCRL')),
|
|
|
|
(72, ('PAUSE', 'PAUS', 'BRK')),
|
|
|
|
(73, ('INSERT', 'INS')),
|
|
|
|
(74, ('HOME',)),
|
|
|
|
(75, ('PGUP',)),
|
|
|
|
(76, ('DELETE', 'DEL')),
|
|
|
|
(77, ('END',)),
|
|
|
|
(78, ('PGDOWN', 'PGDN')),
|
|
|
|
(79, ('RIGHT', 'RGHT')),
|
|
|
|
(80, ('LEFT',)),
|
|
|
|
(81, ('DOWN',)),
|
|
|
|
(82, ('UP',)),
|
|
|
|
)
|
|
|
|
|
|
|
|
for code, names in codes:
|
|
|
|
if candidate in names:
|
|
|
|
return make_key(code=code, names=names)
|
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_numpad_key(candidate: str) -> Optional[Key]:
|
2022-07-21 00:29:01 +02:00
|
|
|
codes = (
|
|
|
|
(83, ('NUM_LOCK', 'NUMLOCK', 'NLCK')),
|
|
|
|
(84, ('KP_SLASH', 'NUMPAD_SLASH', 'PSLS')),
|
|
|
|
(85, ('KP_ASTERISK', 'NUMPAD_ASTERISK', 'PAST')),
|
|
|
|
(86, ('KP_MINUS', 'NUMPAD_MINUS', 'PMNS')),
|
|
|
|
(87, ('KP_PLUS', 'NUMPAD_PLUS', 'PPLS')),
|
|
|
|
(88, ('KP_ENTER', 'NUMPAD_ENTER', 'PENT')),
|
|
|
|
(89, ('KP_1', 'P1', 'NUMPAD_1')),
|
|
|
|
(90, ('KP_2', 'P2', 'NUMPAD_2')),
|
|
|
|
(91, ('KP_3', 'P3', 'NUMPAD_3')),
|
|
|
|
(92, ('KP_4', 'P4', 'NUMPAD_4')),
|
|
|
|
(93, ('KP_5', 'P5', 'NUMPAD_5')),
|
|
|
|
(94, ('KP_6', 'P6', 'NUMPAD_6')),
|
|
|
|
(95, ('KP_7', 'P7', 'NUMPAD_7')),
|
|
|
|
(96, ('KP_8', 'P8', 'NUMPAD_8')),
|
|
|
|
(97, ('KP_9', 'P9', 'NUMPAD_9')),
|
|
|
|
(98, ('KP_0', 'P0', 'NUMPAD_0')),
|
|
|
|
(99, ('KP_DOT', 'PDOT', 'NUMPAD_DOT')),
|
|
|
|
(103, ('KP_EQUAL', 'PEQL', 'NUMPAD_EQUAL')),
|
|
|
|
(133, ('KP_COMMA', 'PCMM', 'NUMPAD_COMMA')),
|
|
|
|
(134, ('KP_EQUAL_AS400', 'NUMPAD_EQUAL_AS400')),
|
|
|
|
)
|
|
|
|
|
|
|
|
for code, names in codes:
|
|
|
|
if candidate in names:
|
|
|
|
return make_key(code=code, names=names)
|
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_shifted_key(candidate: str) -> Optional[Key]:
|
2022-07-21 00:29:01 +02:00
|
|
|
codes = (
|
|
|
|
(30, ('EXCLAIM', 'EXLM', '!')),
|
|
|
|
(31, ('AT', '@')),
|
|
|
|
(32, ('HASH', 'POUND', '#')),
|
|
|
|
(33, ('DOLLAR', 'DLR', '$')),
|
|
|
|
(34, ('PERCENT', 'PERC', '%')),
|
|
|
|
(35, ('CIRCUMFLEX', 'CIRC', '^')),
|
|
|
|
(36, ('AMPERSAND', 'AMPR', '&')),
|
|
|
|
(37, ('ASTERISK', 'ASTR', '*')),
|
|
|
|
(38, ('LEFT_PAREN', 'LPRN', '(')),
|
|
|
|
(39, ('RIGHT_PAREN', 'RPRN', ')')),
|
|
|
|
(45, ('UNDERSCORE', 'UNDS', '_')),
|
|
|
|
(46, ('PLUS', '+')),
|
|
|
|
(47, ('LEFT_CURLY_BRACE', 'LCBR', '{')),
|
|
|
|
(48, ('RIGHT_CURLY_BRACE', 'RCBR', '}')),
|
|
|
|
(49, ('PIPE', '|')),
|
|
|
|
(51, ('COLON', 'COLN', ':')),
|
|
|
|
(52, ('DOUBLE_QUOTE', 'DQUO', 'DQT', '"')),
|
|
|
|
(53, ('TILDE', 'TILD', '~')),
|
|
|
|
(54, ('LEFT_ANGLE_BRACKET', 'LABK', '<')),
|
|
|
|
(55, ('RIGHT_ANGLE_BRACKET', 'RABK', '>')),
|
|
|
|
(56, ('QUESTION', 'QUES', '?')),
|
|
|
|
)
|
|
|
|
|
|
|
|
for code, names in codes:
|
|
|
|
if candidate in names:
|
2022-07-21 16:47:36 +02:00
|
|
|
return make_key(code=code, names=names, has_modifiers={KC.LSFT.code})
|
2022-07-21 00:29:01 +02:00
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_international_key(candidate: str) -> Optional[Key]:
|
2022-07-21 00:29:01 +02:00
|
|
|
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:
|
2022-07-21 16:47:36 +02:00
|
|
|
return make_key(code=code, names=names)
|
2022-07-21 00:29:01 +02:00
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_unicode_key(candidate: str) -> Optional[Key]:
|
2022-07-21 17:12:55 +02:00
|
|
|
keys = (
|
|
|
|
(
|
|
|
|
('UC_MODE_NOOP', 'UC_DISABLE'),
|
|
|
|
handlers.uc_mode_pressed,
|
|
|
|
UnicodeModeKeyMeta(UnicodeMode.NOOP),
|
|
|
|
),
|
|
|
|
(
|
|
|
|
('UC_MODE_LINUX', 'UC_MODE_IBUS'),
|
|
|
|
handlers.uc_mode_pressed,
|
|
|
|
UnicodeModeKeyMeta(UnicodeMode.IBUS),
|
|
|
|
),
|
|
|
|
(
|
|
|
|
('UC_MODE_MACOS', 'UC_MODE_OSX', 'US_MODE_RALT'),
|
|
|
|
handlers.uc_mode_pressed,
|
|
|
|
UnicodeModeKeyMeta(UnicodeMode.RALT),
|
|
|
|
),
|
|
|
|
(
|
|
|
|
('UC_MODE_WINC',),
|
|
|
|
handlers.uc_mode_pressed,
|
|
|
|
UnicodeModeKeyMeta(UnicodeMode.WINC),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
for names, handler, meta in keys:
|
|
|
|
if candidate in names:
|
|
|
|
return make_key(names=names, on_press=handler, meta=meta)
|
|
|
|
|
|
|
|
if candidate in ('UC_MODE',):
|
|
|
|
return make_argumented_key(
|
|
|
|
names=('UC_MODE',),
|
|
|
|
validator=unicode_mode_key_validator,
|
|
|
|
on_press=handlers.uc_mode_pressed,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def maybe_make_firmware_key(candidate: str) -> Optional[Key]:
|
2022-07-21 17:12:55 +02:00
|
|
|
keys = (
|
|
|
|
((('BLE_REFRESH',), handlers.ble_refresh)),
|
2023-02-14 21:35:00 +01:00
|
|
|
((('BLE_DISCONNECT',), handlers.ble_disconnect)),
|
2022-07-21 17:12:55 +02:00
|
|
|
((('BOOTLOADER',), handlers.bootloader)),
|
|
|
|
((('DEBUG', 'DBG'), handlers.debug_pressed)),
|
|
|
|
((('HID_SWITCH', 'HID'), handlers.hid_switch)),
|
|
|
|
((('RELOAD', 'RLD'), handlers.reload)),
|
|
|
|
((('RESET',), handlers.reset)),
|
2023-04-01 02:08:41 +02:00
|
|
|
((('ANY',), handlers.any_pressed)),
|
2022-07-21 17:12:55 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
for names, handler in keys:
|
|
|
|
if candidate in names:
|
|
|
|
return make_key(names=names, on_press=handler)
|
|
|
|
|
|
|
|
|
2022-07-21 00:29:01 +02:00
|
|
|
KEY_GENERATORS = (
|
2022-07-21 17:12:55 +02:00
|
|
|
maybe_make_no_key,
|
2022-07-21 00:29:01 +02:00
|
|
|
maybe_make_alpha_key,
|
|
|
|
maybe_make_numeric_key,
|
2022-07-21 17:12:55 +02:00
|
|
|
maybe_make_firmware_key,
|
2022-07-21 00:29:01 +02:00
|
|
|
maybe_make_key(
|
|
|
|
None,
|
|
|
|
('BKDL',),
|
|
|
|
on_press=handlers.bkdl_pressed,
|
|
|
|
on_release=handlers.bkdl_released,
|
|
|
|
),
|
|
|
|
maybe_make_key(
|
|
|
|
None,
|
|
|
|
('GESC', 'GRAVE_ESC'),
|
|
|
|
on_press=handlers.gesc_pressed,
|
|
|
|
on_release=handlers.gesc_released,
|
|
|
|
),
|
|
|
|
# A dummy key to trigger a sleep_ms call in a sequence of other keys in a
|
|
|
|
# simple sequence macro.
|
|
|
|
maybe_make_argumented_key(
|
|
|
|
key_seq_sleep_validator,
|
|
|
|
('MACRO_SLEEP_MS', 'SLEEP_IN_SEQ'),
|
|
|
|
on_press=handlers.sleep_pressed,
|
|
|
|
),
|
|
|
|
maybe_make_mod_key,
|
|
|
|
# More ASCII standard keys
|
|
|
|
maybe_make_more_ascii,
|
|
|
|
# Function Keys
|
|
|
|
maybe_make_fn_key,
|
|
|
|
# Lock Keys, Navigation, etc.
|
|
|
|
maybe_make_navlock_key,
|
|
|
|
# Numpad
|
|
|
|
# FIXME: Investigate whether this key actually works, and
|
|
|
|
# uncomment when/if it does.
|
|
|
|
# maybe_make_key(131, ('LOCKING_NUM', 'LNUM')),
|
|
|
|
maybe_make_numpad_key,
|
|
|
|
# Making life better for folks on tiny keyboards especially: exposes
|
|
|
|
# the 'shifted' keys as raw keys. Under the hood we're still
|
|
|
|
# sending Shift+(whatever key is normally pressed) to get these, so
|
|
|
|
# for example `KC_AT` will hold shift and press 2.
|
|
|
|
maybe_make_shifted_key,
|
|
|
|
# International
|
|
|
|
maybe_make_international_key,
|
2022-07-21 17:12:55 +02:00
|
|
|
maybe_make_unicode_key,
|
2022-07-21 00:29:01 +02:00
|
|
|
)
|
2021-07-07 23:44:44 +02:00
|
|
|
|
2021-05-19 04:15:20 +02:00
|
|
|
|
2022-04-22 20:16:30 +02:00
|
|
|
class KeyAttrDict:
|
2023-02-08 22:21:22 +01:00
|
|
|
# Instead of relying on the uncontrollable availability of a big chunk of
|
|
|
|
# contiguous memory for key caching, we can manually fragment the cache into
|
|
|
|
# reasonably small partitions. The partition size is chosen from the magic
|
|
|
|
# values of CPs hash allocation sizes.
|
|
|
|
# (https://github.com/adafruit/circuitpython/blob/main/py/map.c, 2023-02)
|
|
|
|
__partition_size = 37
|
|
|
|
__cache = [{}]
|
2022-04-22 20:16:30 +02:00
|
|
|
|
2022-07-03 17:31:39 +02:00
|
|
|
def __iter__(self):
|
2023-02-11 10:35:11 +01:00
|
|
|
for partition in self.__cache:
|
2023-02-22 10:42:53 +01:00
|
|
|
for name in partition:
|
2023-02-11 10:35:11 +01:00
|
|
|
yield name
|
2022-07-03 17:31:39 +02:00
|
|
|
|
2023-02-08 22:21:22 +01:00
|
|
|
def __setitem__(self, name: str, key: Key):
|
|
|
|
# Overwrite existing reference.
|
|
|
|
for partition in self.__cache:
|
|
|
|
if name in partition:
|
|
|
|
partition[name] = key
|
|
|
|
return key
|
2022-04-22 20:16:30 +02:00
|
|
|
|
2023-02-08 22:21:22 +01:00
|
|
|
# Insert new reference.
|
|
|
|
if len(self.__cache[-1]) >= self.__partition_size:
|
|
|
|
self.__cache.append({})
|
|
|
|
self.__cache[-1][name] = key
|
|
|
|
return key
|
2022-04-22 20:16:30 +02:00
|
|
|
|
2023-02-08 22:21:22 +01:00
|
|
|
def __getattr__(self, name: str):
|
|
|
|
return self.__getitem__(name)
|
|
|
|
|
|
|
|
def get(self, name: str, default: Optional[Key] = None):
|
2022-04-22 20:16:30 +02:00
|
|
|
try:
|
2023-02-08 22:21:22 +01:00
|
|
|
return self.__getitem__(name)
|
2022-04-22 20:16:30 +02:00
|
|
|
except Exception:
|
|
|
|
return default
|
2021-07-07 23:44:44 +02:00
|
|
|
|
2022-04-23 08:57:39 +02:00
|
|
|
def clear(self):
|
|
|
|
self.__cache.clear()
|
2023-02-08 22:21:22 +01:00
|
|
|
self.__cache.append({})
|
2022-04-23 08:57:39 +02:00
|
|
|
|
2023-02-08 22:21:22 +01:00
|
|
|
def __getitem__(self, name: str):
|
|
|
|
for partition in self.__cache:
|
|
|
|
if name in partition:
|
|
|
|
return partition[name]
|
2021-07-07 23:44:44 +02:00
|
|
|
|
2022-07-21 00:29:01 +02:00
|
|
|
for func in KEY_GENERATORS:
|
2023-02-08 22:21:22 +01:00
|
|
|
maybe_key = func(name)
|
2022-07-21 00:29:01 +02:00
|
|
|
if maybe_key:
|
|
|
|
break
|
2023-02-11 10:52:33 +01:00
|
|
|
|
|
|
|
if not maybe_key:
|
2023-03-06 21:31:16 +01:00
|
|
|
if debug.enabled:
|
|
|
|
debug(f'Invalid key: {name}')
|
|
|
|
return KC.NO
|
2021-07-07 23:44:44 +02:00
|
|
|
|
2022-07-20 15:35:56 +02:00
|
|
|
if debug.enabled:
|
2023-02-08 22:21:22 +01:00
|
|
|
debug(f'{name}: {maybe_key}')
|
2021-07-07 23:44:44 +02:00
|
|
|
|
2023-02-08 22:21:22 +01:00
|
|
|
return maybe_key
|
2021-05-19 04:15:20 +02:00
|
|
|
|
|
|
|
|
2022-04-18 11:39:19 +02:00
|
|
|
# Global state, will be filled in throughout this file, and
|
2018-12-29 13:44:52 +01:00
|
|
|
# anywhere the user creates custom keys
|
2021-05-19 04:15:20 +02:00
|
|
|
KC = KeyAttrDict()
|
2018-10-16 13:04:39 +02:00
|
|
|
|
|
|
|
|
2018-12-30 00:29:11 +01:00
|
|
|
class Key:
|
2018-12-29 13:44:52 +01:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-09-23 19:43:19 +02:00
|
|
|
code: int,
|
|
|
|
has_modifiers: Optional[list[Key, ...]] = None,
|
|
|
|
no_press: bool = False,
|
|
|
|
no_release: bool = False,
|
|
|
|
on_press: Callable[
|
|
|
|
[object, Key, Keyboard, ...], None
|
|
|
|
] = handlers.default_pressed,
|
|
|
|
on_release: Callable[
|
|
|
|
[object, Key, Keyboard, ...], None
|
|
|
|
] = handlers.default_released,
|
|
|
|
meta: object = object(),
|
2018-12-29 13:44:52 +01:00
|
|
|
):
|
2018-09-23 14:19:57 +02:00
|
|
|
self.code = code
|
|
|
|
self.has_modifiers = has_modifiers
|
Massive refactor largely to support Unicode on Mac
This does a bunch of crazy stuff:
- The ability to set a unicode mode (right now only Linux+ibus or
MacOS-RALT) in the keymap. This will be changeable at runtime soon, to
allow a single keyboard to be able to send table flips and whatever
other crazy stuff on any OS the board is plugged into (something that's
not currently doable on QMK, so yay us?)
- As part of the above, there is now just one user-facing macro for
unicode codepoint submission,
`kmk.common.macros.unicode.unicode_sequence`. Users should never use the
platform-specific macros, partly because they just outright won't work.
There's all sorts of fun stuff in these methods now, thank goodness
MicroPython supports the `yield from` construct.
- Keycode (these should really be renamed Keysym or something) objects
that are intended to not be pressed, or not be released. Right now these
properties are completely ignored if not part of a macro, and it's
probably sane to keep it that way. This was necessary to support MacOS's
"hold RALT while typing the codepoint characters" flow.
- Other refactor-y bits, like moving macro support to `kmk/common`
rather than sitting at the top level of the tree. One day `kmk/common`
may make sense to surface at top level `kmk/`, but that's a discussion
for another day.
2018-10-01 04:33:23 +02:00
|
|
|
# cast to bool() in case we get a None value
|
|
|
|
self.no_press = bool(no_press)
|
2021-09-26 10:31:04 +02:00
|
|
|
self.no_release = bool(no_release)
|
Massive refactor largely to support Unicode on Mac
This does a bunch of crazy stuff:
- The ability to set a unicode mode (right now only Linux+ibus or
MacOS-RALT) in the keymap. This will be changeable at runtime soon, to
allow a single keyboard to be able to send table flips and whatever
other crazy stuff on any OS the board is plugged into (something that's
not currently doable on QMK, so yay us?)
- As part of the above, there is now just one user-facing macro for
unicode codepoint submission,
`kmk.common.macros.unicode.unicode_sequence`. Users should never use the
platform-specific macros, partly because they just outright won't work.
There's all sorts of fun stuff in these methods now, thank goodness
MicroPython supports the `yield from` construct.
- Keycode (these should really be renamed Keysym or something) objects
that are intended to not be pressed, or not be released. Right now these
properties are completely ignored if not part of a macro, and it's
probably sane to keep it that way. This was necessary to support MacOS's
"hold RALT while typing the codepoint characters" flow.
- Other refactor-y bits, like moving macro support to `kmk/common`
rather than sitting at the top level of the tree. One day `kmk/common`
may make sense to surface at top level `kmk/`, but that's a discussion
for another day.
2018-10-01 04:33:23 +02:00
|
|
|
|
2019-02-19 00:08:07 +01:00
|
|
|
self._handle_press = on_press
|
|
|
|
self._handle_release = on_release
|
2018-12-29 13:44:52 +01:00
|
|
|
self.meta = meta
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def __call__(
|
|
|
|
self, no_press: Optional[bool] = None, no_release: Optional[bool] = None
|
|
|
|
) -> Key:
|
Massive refactor largely to support Unicode on Mac
This does a bunch of crazy stuff:
- The ability to set a unicode mode (right now only Linux+ibus or
MacOS-RALT) in the keymap. This will be changeable at runtime soon, to
allow a single keyboard to be able to send table flips and whatever
other crazy stuff on any OS the board is plugged into (something that's
not currently doable on QMK, so yay us?)
- As part of the above, there is now just one user-facing macro for
unicode codepoint submission,
`kmk.common.macros.unicode.unicode_sequence`. Users should never use the
platform-specific macros, partly because they just outright won't work.
There's all sorts of fun stuff in these methods now, thank goodness
MicroPython supports the `yield from` construct.
- Keycode (these should really be renamed Keysym or something) objects
that are intended to not be pressed, or not be released. Right now these
properties are completely ignored if not part of a macro, and it's
probably sane to keep it that way. This was necessary to support MacOS's
"hold RALT while typing the codepoint characters" flow.
- Other refactor-y bits, like moving macro support to `kmk/common`
rather than sitting at the top level of the tree. One day `kmk/common`
may make sense to surface at top level `kmk/`, but that's a discussion
for another day.
2018-10-01 04:33:23 +02:00
|
|
|
if no_press is None and no_release is None:
|
|
|
|
return self
|
|
|
|
|
2022-04-17 23:52:24 +02:00
|
|
|
return type(self)(
|
Massive refactor largely to support Unicode on Mac
This does a bunch of crazy stuff:
- The ability to set a unicode mode (right now only Linux+ibus or
MacOS-RALT) in the keymap. This will be changeable at runtime soon, to
allow a single keyboard to be able to send table flips and whatever
other crazy stuff on any OS the board is plugged into (something that's
not currently doable on QMK, so yay us?)
- As part of the above, there is now just one user-facing macro for
unicode codepoint submission,
`kmk.common.macros.unicode.unicode_sequence`. Users should never use the
platform-specific macros, partly because they just outright won't work.
There's all sorts of fun stuff in these methods now, thank goodness
MicroPython supports the `yield from` construct.
- Keycode (these should really be renamed Keysym or something) objects
that are intended to not be pressed, or not be released. Right now these
properties are completely ignored if not part of a macro, and it's
probably sane to keep it that way. This was necessary to support MacOS's
"hold RALT while typing the codepoint characters" flow.
- Other refactor-y bits, like moving macro support to `kmk/common`
rather than sitting at the top level of the tree. One day `kmk/common`
may make sense to surface at top level `kmk/`, but that's a discussion
for another day.
2018-10-01 04:33:23 +02:00
|
|
|
code=self.code,
|
|
|
|
has_modifiers=self.has_modifiers,
|
|
|
|
no_press=no_press,
|
|
|
|
no_release=no_release,
|
2022-04-17 23:52:24 +02:00
|
|
|
on_press=self._handle_press,
|
|
|
|
on_release=self._handle_release,
|
|
|
|
meta=self.meta,
|
Massive refactor largely to support Unicode on Mac
This does a bunch of crazy stuff:
- The ability to set a unicode mode (right now only Linux+ibus or
MacOS-RALT) in the keymap. This will be changeable at runtime soon, to
allow a single keyboard to be able to send table flips and whatever
other crazy stuff on any OS the board is plugged into (something that's
not currently doable on QMK, so yay us?)
- As part of the above, there is now just one user-facing macro for
unicode codepoint submission,
`kmk.common.macros.unicode.unicode_sequence`. Users should never use the
platform-specific macros, partly because they just outright won't work.
There's all sorts of fun stuff in these methods now, thank goodness
MicroPython supports the `yield from` construct.
- Keycode (these should really be renamed Keysym or something) objects
that are intended to not be pressed, or not be released. Right now these
properties are completely ignored if not part of a macro, and it's
probably sane to keep it that way. This was necessary to support MacOS's
"hold RALT while typing the codepoint characters" flow.
- Other refactor-y bits, like moving macro support to `kmk/common`
rather than sitting at the top level of the tree. One day `kmk/common`
may make sense to surface at top level `kmk/`, but that's a discussion
for another day.
2018-10-01 04:33:23 +02:00
|
|
|
)
|
|
|
|
|
2018-09-28 23:35:52 +02:00
|
|
|
def __repr__(self):
|
2022-06-11 23:54:01 +02:00
|
|
|
return f'Key(code={self.code}, has_modifiers={self.has_modifiers})'
|
2018-09-28 23:35:52 +02:00
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def on_press(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None:
|
2021-09-17 15:50:45 +02:00
|
|
|
if hasattr(self, '_pre_press_handlers'):
|
|
|
|
for fn in self._pre_press_handlers:
|
2022-09-23 19:43:19 +02:00
|
|
|
if not fn(self, keyboard, KC, coord_int):
|
|
|
|
return
|
2018-12-29 13:44:52 +01:00
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
self._handle_press(self, keyboard, KC, coord_int)
|
2019-02-19 00:08:07 +01:00
|
|
|
|
2021-09-17 15:50:45 +02:00
|
|
|
if hasattr(self, '_post_press_handlers'):
|
|
|
|
for fn in self._post_press_handlers:
|
2022-09-23 19:43:19 +02:00
|
|
|
fn(self, keyboard, KC, coord_int)
|
2019-02-19 00:08:07 +01:00
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def on_release(self, keyboard: Keyboard, coord_int: Optional[int] = None) -> None:
|
2021-09-17 15:50:45 +02:00
|
|
|
if hasattr(self, '_pre_release_handlers'):
|
|
|
|
for fn in self._pre_release_handlers:
|
2022-09-23 19:43:19 +02:00
|
|
|
if not fn(self, keyboard, KC, coord_int):
|
|
|
|
return
|
2019-02-19 00:08:07 +01:00
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
self._handle_release(self, keyboard, KC, coord_int)
|
2019-02-19 00:08:07 +01:00
|
|
|
|
2021-09-17 15:50:45 +02:00
|
|
|
if hasattr(self, '_post_release_handlers'):
|
|
|
|
for fn in self._post_release_handlers:
|
2022-09-23 19:43:19 +02:00
|
|
|
fn(self, keyboard, KC, coord_int)
|
2019-02-19 00:08:07 +01:00
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def clone(self) -> Key:
|
2019-02-19 00:08:07 +01:00
|
|
|
'''
|
|
|
|
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,
|
|
|
|
no_press=self.no_press,
|
|
|
|
no_release=self.no_release,
|
|
|
|
on_press=self._handle_press,
|
|
|
|
on_release=self._handle_release,
|
|
|
|
meta=self.meta,
|
|
|
|
)
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def before_press_handler(self, fn: Callable[[Key, Keyboard, ...], bool]) -> None:
|
2019-02-19 00:08:07 +01:00
|
|
|
'''
|
|
|
|
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)
|
|
|
|
|
2019-04-26 16:21:12 +02:00
|
|
|
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.
|
2019-02-19 00:08:07 +01:00
|
|
|
|
|
|
|
These handlers are run in attachment order: handlers provided by earlier
|
|
|
|
calls of this method will be executed before those provided by later calls.
|
|
|
|
'''
|
|
|
|
|
2021-09-17 15:50:45 +02:00
|
|
|
if not hasattr(self, '_pre_press_handlers'):
|
|
|
|
self._pre_press_handlers = []
|
2019-02-19 00:08:07 +01:00
|
|
|
self._pre_press_handlers.append(fn)
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def after_press_handler(self, fn: Callable[[Key, Keyboard, ...], bool]) -> None:
|
2019-02-19 00:08:07 +01:00
|
|
|
'''
|
|
|
|
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.
|
|
|
|
'''
|
|
|
|
|
2021-09-17 15:50:45 +02:00
|
|
|
if not hasattr(self, '_post_press_handlers'):
|
|
|
|
self._post_press_handlers = []
|
2019-02-19 00:08:07 +01:00
|
|
|
self._post_press_handlers.append(fn)
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def before_release_handler(self, fn: Callable[[Key, Keyboard, ...], bool]) -> None:
|
2019-02-19 00:08:07 +01:00
|
|
|
'''
|
|
|
|
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)
|
|
|
|
|
2019-04-26 16:21:12 +02:00
|
|
|
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.
|
2019-02-19 00:08:07 +01:00
|
|
|
|
|
|
|
These handlers are run in attachment order: handlers provided by earlier
|
|
|
|
calls of this method will be executed before those provided by later calls.
|
|
|
|
'''
|
|
|
|
|
2021-09-17 15:50:45 +02:00
|
|
|
if not hasattr(self, '_pre_release_handlers'):
|
|
|
|
self._pre_release_handlers = []
|
2019-02-19 00:08:07 +01:00
|
|
|
self._pre_release_handlers.append(fn)
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def after_release_handler(self, fn: Callable[[Key, Keyboard, ...], bool]) -> None:
|
2019-02-19 00:08:07 +01:00
|
|
|
'''
|
|
|
|
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.
|
|
|
|
'''
|
|
|
|
|
2021-09-17 15:50:45 +02:00
|
|
|
if not hasattr(self, '_post_release_handlers'):
|
|
|
|
self._post_release_handlers = []
|
2019-02-19 00:08:07 +01:00
|
|
|
self._post_release_handlers.append(fn)
|
2018-12-29 13:44:52 +01:00
|
|
|
|
Massive refactor largely to support Unicode on Mac
This does a bunch of crazy stuff:
- The ability to set a unicode mode (right now only Linux+ibus or
MacOS-RALT) in the keymap. This will be changeable at runtime soon, to
allow a single keyboard to be able to send table flips and whatever
other crazy stuff on any OS the board is plugged into (something that's
not currently doable on QMK, so yay us?)
- As part of the above, there is now just one user-facing macro for
unicode codepoint submission,
`kmk.common.macros.unicode.unicode_sequence`. Users should never use the
platform-specific macros, partly because they just outright won't work.
There's all sorts of fun stuff in these methods now, thank goodness
MicroPython supports the `yield from` construct.
- Keycode (these should really be renamed Keysym or something) objects
that are intended to not be pressed, or not be released. Right now these
properties are completely ignored if not part of a macro, and it's
probably sane to keep it that way. This was necessary to support MacOS's
"hold RALT while typing the codepoint characters" flow.
- Other refactor-y bits, like moving macro support to `kmk/common`
rather than sitting at the top level of the tree. One day `kmk/common`
may make sense to surface at top level `kmk/`, but that's a discussion
for another day.
2018-10-01 04:33:23 +02:00
|
|
|
|
2018-12-30 00:29:11 +01:00
|
|
|
class ModifierKey(Key):
|
2020-10-21 21:19:42 +02:00
|
|
|
FAKE_CODE = const(-1)
|
2018-10-12 03:56:46 +02:00
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def __call__(
|
|
|
|
self,
|
|
|
|
modified_key: Optional[Key] = None,
|
|
|
|
no_press: Optional[bool] = None,
|
|
|
|
no_release: Optional[bool] = None,
|
|
|
|
) -> Key:
|
2022-04-17 23:52:24 +02:00
|
|
|
if modified_key is None:
|
|
|
|
return super().__call__(no_press=no_press, no_release=no_release)
|
Massive refactor largely to support Unicode on Mac
This does a bunch of crazy stuff:
- The ability to set a unicode mode (right now only Linux+ibus or
MacOS-RALT) in the keymap. This will be changeable at runtime soon, to
allow a single keyboard to be able to send table flips and whatever
other crazy stuff on any OS the board is plugged into (something that's
not currently doable on QMK, so yay us?)
- As part of the above, there is now just one user-facing macro for
unicode codepoint submission,
`kmk.common.macros.unicode.unicode_sequence`. Users should never use the
platform-specific macros, partly because they just outright won't work.
There's all sorts of fun stuff in these methods now, thank goodness
MicroPython supports the `yield from` construct.
- Keycode (these should really be renamed Keysym or something) objects
that are intended to not be pressed, or not be released. Right now these
properties are completely ignored if not part of a macro, and it's
probably sane to keep it that way. This was necessary to support MacOS's
"hold RALT while typing the codepoint characters" flow.
- Other refactor-y bits, like moving macro support to `kmk/common`
rather than sitting at the top level of the tree. One day `kmk/common`
may make sense to surface at top level `kmk/`, but that's a discussion
for another day.
2018-10-01 04:33:23 +02:00
|
|
|
|
2022-04-17 23:52:24 +02:00
|
|
|
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
|
2018-10-01 02:58:36 +02:00
|
|
|
|
2022-04-17 23:52:24 +02:00
|
|
|
return type(modified_key)(
|
|
|
|
code=code,
|
|
|
|
has_modifiers=modifiers,
|
|
|
|
no_press=no_press,
|
|
|
|
no_release=no_release,
|
|
|
|
on_press=modified_key._handle_press,
|
|
|
|
on_release=modified_key._handle_release,
|
|
|
|
meta=modified_key.meta,
|
|
|
|
)
|
2018-10-01 02:58:36 +02:00
|
|
|
|
2018-10-12 03:56:46 +02:00
|
|
|
def __repr__(self):
|
2022-06-11 23:54:01 +02:00
|
|
|
return f'ModifierKey(code={self.code}, has_modifiers={self.has_modifiers})'
|
2018-10-12 03:56:46 +02:00
|
|
|
|
2018-09-23 14:19:57 +02:00
|
|
|
|
2018-12-30 00:29:11 +01:00
|
|
|
class ConsumerKey(Key):
|
2018-10-01 05:21:42 +02:00
|
|
|
pass
|
HID: Support Consumer (media) keys
What a short title for such a massive diff.
This (heavily squashed) commit adds support for Consumer keys such as
volume keys, media play/pause/stop, etc. by exposing four HID devices
over a single USB lane (as opposed to just exposing a keyboard). This
heavily refactors how HIDHelper works due to the new reporting
structure.
Many of the media keys were changed (mostly Keycodes.Media section), but
many (especially anything regarding Application keys) haven't been
touched yet - thus many keycodes may still be wrong. Probably worth
updating those soon, but I didn't get around to it yet. The definitive
list I refered to was
http://www.freebsddiary.org/APC/usb_hid_usages.php, which is basically
copy-pasta from the official USB HID spec at
https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
(warning: massive PDF, not light reading).
The only known regression this introduces is that instead of 6KRO as the
USB spec usually supports, we can now only have 5KRO (maybe even 4KRO),
for reasons I have yet to fully debug - this seems to be related to the
report having to include the device descriptor _and_ not supporting a
full 8 bytes as it used to. For now I'm willing to accept this, but it
definitely will be great to squash that bug.
This adds descriptor support for MOUSE and SYSCONTROL devices, as of yet
unimplemented.
2018-09-30 00:46:17 +02:00
|
|
|
|
|
|
|
|
2022-10-21 23:19:56 +02:00
|
|
|
class MouseKey(Key):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def make_key(
|
|
|
|
code: Optional[int] = None,
|
|
|
|
names: Tuple[str, ...] = tuple(), # NOQA
|
|
|
|
type: KeyType = KeyType.SIMPLE,
|
|
|
|
**kwargs,
|
|
|
|
) -> Key:
|
2018-12-29 13:44:52 +01:00
|
|
|
'''
|
|
|
|
Create a new key, aliased by `names` in the KC lookup table.
|
2018-10-19 12:04:54 +02:00
|
|
|
|
2018-12-29 13:44:52 +01:00
|
|
|
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.
|
2018-10-01 02:58:36 +02:00
|
|
|
|
2022-04-23 08:57:39 +02:00
|
|
|
Names are globally unique. If a later key is created with
|
|
|
|
the same name as an existing entry in `KC`, it will overwrite
|
|
|
|
the existing entry.
|
|
|
|
|
|
|
|
Names are case sensitive.
|
2018-10-01 02:58:36 +02:00
|
|
|
|
2018-12-30 00:29:11 +01:00
|
|
|
All **kwargs are passed to the Key constructor
|
2018-12-29 13:44:52 +01:00
|
|
|
'''
|
2018-10-01 02:58:36 +02:00
|
|
|
|
2018-12-30 00:29:11 +01:00
|
|
|
global NEXT_AVAILABLE_KEY
|
2018-10-01 02:58:36 +02:00
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
if type == KeyType.SIMPLE:
|
2018-12-30 00:29:11 +01:00
|
|
|
constructor = Key
|
2022-09-23 19:43:19 +02:00
|
|
|
elif type == KeyType.MODIFIER:
|
2018-12-30 00:29:11 +01:00
|
|
|
constructor = ModifierKey
|
2022-09-23 19:43:19 +02:00
|
|
|
elif type == KeyType.CONSUMER:
|
2018-12-30 00:29:11 +01:00
|
|
|
constructor = ConsumerKey
|
2022-10-21 23:19:56 +02:00
|
|
|
elif type == KeyType.MOUSE:
|
|
|
|
constructor = MouseKey
|
2018-12-29 13:44:52 +01:00
|
|
|
else:
|
|
|
|
raise ValueError('Unrecognized key type')
|
2018-10-01 02:58:36 +02:00
|
|
|
|
2018-12-29 13:44:52 +01:00
|
|
|
if code is None:
|
2018-12-30 00:29:11 +01:00
|
|
|
code = NEXT_AVAILABLE_KEY
|
|
|
|
NEXT_AVAILABLE_KEY += 1
|
|
|
|
elif code >= FIRST_KMK_INTERNAL_KEY:
|
2018-12-29 13:44:52 +01:00
|
|
|
# Try to ensure future auto-generated internal keycodes won't
|
|
|
|
# be overridden by continuing to +1 the sequence from the provided
|
|
|
|
# code
|
2018-12-30 00:29:11 +01:00
|
|
|
NEXT_AVAILABLE_KEY = max(NEXT_AVAILABLE_KEY, code + 1)
|
2018-10-01 02:58:36 +02:00
|
|
|
|
2018-12-29 13:44:52 +01:00
|
|
|
key = constructor(code=code, **kwargs)
|
2018-10-01 02:58:36 +02:00
|
|
|
|
2022-04-23 08:57:39 +02:00
|
|
|
for name in names:
|
|
|
|
KC[name] = key
|
2018-10-01 02:58:36 +02:00
|
|
|
|
2018-12-29 13:44:52 +01:00
|
|
|
return key
|
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def make_mod_key(code: int, names: Tuple[str, ...], *args, **kwargs) -> Key:
|
|
|
|
return make_key(code, names, *args, **kwargs, type=KeyType.MODIFIER)
|
2018-12-29 13:44:52 +01:00
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def make_shifted_key(code: int, names: Tuple[str, ...]) -> Key:
|
2022-03-31 13:29:30 +02:00
|
|
|
return make_key(code, names, has_modifiers={KC.LSFT.code})
|
2018-12-29 13:44:52 +01:00
|
|
|
|
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def make_consumer_key(*args, **kwargs) -> Key:
|
|
|
|
return make_key(*args, **kwargs, type=KeyType.CONSUMER)
|
2018-10-01 02:58:36 +02:00
|
|
|
|
HID: Support Consumer (media) keys
What a short title for such a massive diff.
This (heavily squashed) commit adds support for Consumer keys such as
volume keys, media play/pause/stop, etc. by exposing four HID devices
over a single USB lane (as opposed to just exposing a keyboard). This
heavily refactors how HIDHelper works due to the new reporting
structure.
Many of the media keys were changed (mostly Keycodes.Media section), but
many (especially anything regarding Application keys) haven't been
touched yet - thus many keycodes may still be wrong. Probably worth
updating those soon, but I didn't get around to it yet. The definitive
list I refered to was
http://www.freebsddiary.org/APC/usb_hid_usages.php, which is basically
copy-pasta from the official USB HID spec at
https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
(warning: massive PDF, not light reading).
The only known regression this introduces is that instead of 6KRO as the
USB spec usually supports, we can now only have 5KRO (maybe even 4KRO),
for reasons I have yet to fully debug - this seems to be related to the
report having to include the device descriptor _and_ not supporting a
full 8 bytes as it used to. For now I'm willing to accept this, but it
definitely will be great to squash that bug.
This adds descriptor support for MOUSE and SYSCONTROL devices, as of yet
unimplemented.
2018-09-30 00:46:17 +02:00
|
|
|
|
2022-10-21 23:19:56 +02:00
|
|
|
def make_mouse_key(*args, **kwargs) -> Key:
|
|
|
|
return make_key(*args, **kwargs, type=KeyType.MOUSE)
|
|
|
|
|
|
|
|
|
2018-12-29 13:44:52 +01:00
|
|
|
# Argumented keys are implicitly internal, so auto-gen of code
|
|
|
|
# is almost certainly the best plan here
|
|
|
|
def make_argumented_key(
|
2022-09-23 19:43:19 +02:00
|
|
|
validator: object = lambda *validator_args, **validator_kwargs: object(),
|
|
|
|
names: Tuple[str, ...] = tuple(), # NOQA
|
2018-12-29 13:44:52 +01:00
|
|
|
*constructor_args,
|
|
|
|
**constructor_kwargs,
|
2022-09-23 19:43:19 +02:00
|
|
|
) -> Key:
|
2018-12-30 00:29:11 +01:00
|
|
|
global NEXT_AVAILABLE_KEY
|
2018-12-29 15:03:31 +01:00
|
|
|
|
2022-09-23 19:43:19 +02:00
|
|
|
def _argumented_key(*user_args, **user_kwargs) -> Key:
|
2018-12-30 00:29:11 +01:00
|
|
|
global NEXT_AVAILABLE_KEY
|
2018-12-29 15:03:31 +01:00
|
|
|
|
2018-12-29 13:44:52 +01:00
|
|
|
meta = validator(*user_args, **user_kwargs)
|
|
|
|
|
|
|
|
if meta:
|
2018-12-30 00:29:11 +01:00
|
|
|
key = Key(
|
2019-07-25 07:57:11 +02:00
|
|
|
NEXT_AVAILABLE_KEY, meta=meta, *constructor_args, **constructor_kwargs
|
2018-12-29 13:44:52 +01:00
|
|
|
)
|
2018-12-29 15:03:31 +01:00
|
|
|
|
2018-12-30 00:29:11 +01:00
|
|
|
NEXT_AVAILABLE_KEY += 1
|
2018-12-29 15:03:31 +01:00
|
|
|
|
|
|
|
return key
|
|
|
|
|
2018-12-29 13:44:52 +01:00
|
|
|
else:
|
|
|
|
raise ValueError(
|
|
|
|
'Argumented key validator failed for unknown reasons. '
|
2019-07-25 07:56:10 +02:00
|
|
|
"This may not be the keymap's fault, as a more specific error "
|
2019-07-25 07:57:11 +02:00
|
|
|
'should have been raised.'
|
2018-12-29 13:44:52 +01:00
|
|
|
)
|
|
|
|
|
2018-12-29 15:03:31 +01:00
|
|
|
for name in names:
|
|
|
|
KC[name] = _argumented_key
|
|
|
|
|
|
|
|
return _argumented_key
|