From 083e4a701aa056b9c0664ad68878d17eebd7e3dc Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Wed, 7 Jul 2021 14:44:44 -0700 Subject: [PATCH] fix(keys): fix shifted keys bug, make lazy key defs more readable --- kmk/keys.py | 674 +++++++++++++++++++++------------------------------- 1 file changed, 271 insertions(+), 403 deletions(-) diff --git a/kmk/keys.py b/kmk/keys.py index e8b8076..1a0a4a3 100644 --- a/kmk/keys.py +++ b/kmk/keys.py @@ -10,6 +10,8 @@ from kmk.key_validators import ( ) from kmk.types import AttrDict, UnicodeModeKeyMeta +DEBUG_OUTPUT = False + FIRST_KMK_INTERNAL_KEY = const(1000) NEXT_AVAILABLE_KEY = 1000 @@ -17,407 +19,76 @@ KEY_SIMPLE = const(0) KEY_MODIFIER = const(1) KEY_CONSUMER = const(2) +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) + + +class InfiniteLoopDetected(Exception): + pass + + +# 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 +def left_pipe_until_some(candidate, functor, *args_iter): + for args in args_iter: + result = functor(candidate, *args) + if result is not None: + return result + + +def first_truthy(candidate, *funcs): + for func in funcs: + result = func(candidate) + if result is not None: + return result + + +def maybe_make_mod_key(candidate, code, names): + if candidate in names: + return make_mod_key(code=code, names=names) + + +def maybe_make_key(candidate, code, names): + if candidate in names: + return make_key(code=code, names=names) + + +def maybe_make_shifted_key(candidate, target_name, names): + if candidate in names: + return make_shifted_key(target_name=target_name, names=names) + + +def maybe_make_consumer_key(candidate, code, names): + if candidate in names: + return make_consumer_key(code=code, names=names) + class KeyAttrDict(AttrDict): - def __getattr__(self, key): + def __getattr__(self, key, depth=0): + if depth > 1: + raise InfiniteLoopDetected() + try: return super(KeyAttrDict, self).__getattr__(key) except Exception: pass - # Modifiers - if key in ('LEFT_CONTROL', 'LCTRL', 'LCTL'): - make_mod_key(code=0x01, names=('LEFT_CONTROL', 'LCTRL', 'LCTL')) - elif key in ('LEFT_SHIFT', 'LSHIFT', 'LSFT'): - make_mod_key(code=0x02, names=('LEFT_SHIFT', 'LSHIFT', 'LSFT')) - elif key in ('LEFT_ALT', 'LALT'): - make_mod_key(code=0x04, names=('LEFT_ALT', 'LALT')) - elif key in ('LEFT_SUPER', 'LGUI', 'LCMD', 'LWIN'): - make_mod_key(code=0x08, names=('LEFT_SUPER', 'LGUI', 'LCMD', 'LWIN')) - elif key in ('RIGHT_CONTROL', 'RCTRL', 'RCTL'): - make_mod_key(code=0x10, names=('RIGHT_CONTROL', 'RCTRL', 'RCTL')) - elif key in ('RIGHT_SHIFT', 'RSHIFT', 'RSFT'): - make_mod_key(code=0x20, names=('RIGHT_SHIFT', 'RSHIFT', 'RSFT')) - elif key in ('RIGHT_ALT', 'RALT'): - make_mod_key(code=0x40, names=('RIGHT_ALT', 'RALT')) - elif key in ('RIGHT_SUPER', 'RGUI', 'RCMD', 'RWIN'): - make_mod_key(code=0x80, names=('RIGHT_SUPER', 'RGUI', 'RCMD', 'RWIN')) - # MEH = LCTL | LALT | LSFT# MEH = LCTL | - elif key in ('MEH',): - make_mod_key(code=0x07, names=('MEH',)) - # HYPR = LCTL | LALT | LSFT | LGUI - elif key in ('HYPER', 'HYPR'): - make_mod_key(code=0x0F, names=('HYPER', 'HYPR')) - # Basic ASCII letters - elif key in ('A',): - make_key(code=4, names=('A',)) - elif key in ('B',): - make_key(code=5, names=('B',)) - elif key in ('C',): - make_key(code=6, names=('C',)) - elif key in ('D',): - make_key(code=7, names=('D',)) - elif key in ('E',): - make_key(code=8, names=('E',)) - elif key in ('F',): - make_key(code=9, names=('F',)) - elif key in ('G',): - make_key(code=10, names=('G',)) - elif key in ('H',): - make_key(code=11, names=('H',)) - elif key in ('I',): - make_key(code=12, names=('I',)) - elif key in ('J',): - make_key(code=13, names=('J',)) - elif key in ('K',): - make_key(code=14, names=('K',)) - elif key in ('L',): - make_key(code=15, names=('L',)) - elif key in ('M',): - make_key(code=16, names=('M',)) - elif key in ('N',): - make_key(code=17, names=('N',)) - elif key in ('O',): - make_key(code=18, names=('O',)) - elif key in ('P',): - make_key(code=19, names=('P',)) - elif key in ('Q',): - make_key(code=20, names=('Q',)) - elif key in ('R',): - make_key(code=21, names=('R',)) - elif key in ('S',): - make_key(code=22, names=('S',)) - elif key in ('T',): - make_key(code=23, names=('T',)) - elif key in ('U',): - make_key(code=24, names=('U',)) - elif key in ('V',): - make_key(code=25, names=('V',)) - elif key in ('W',): - make_key(code=26, names=('W',)) - elif key in ('X',): - make_key(code=27, names=('X',)) - elif key in ('Y',): - make_key(code=28, names=('Y',)) - elif key in ('Z',): - make_key(code=29, names=('Z',)) + # Basic ASCII letters/numbers don't need anything fancy, so check those + # in the laziest way + if key in ALL_ALPHAS: + make_key(code=4 + ALL_ALPHAS.index(key), names=(key,)) + elif key in ALL_NUMBERS or key in ALL_NUMBER_ALIASES: + try: + offset = ALL_NUMBERS.index(key) + except ValueError: + offset = ALL_NUMBER_ALIASES.index(key) - # Numbers - # Aliases to play nicely with AttrDict, since KC.1 isn't a valid - # attribute key in Python, but KC.N1 is - elif key in ('1', 'N1'): - make_key(code=30, names=('1', 'N1', 'EXCLAIM', 'EXLM', '!')) - elif key in ('2', 'N2'): - make_key(code=31, names=('2', 'N2', 'AT', '@')) - elif key in ('3', 'N3', 'HASH', 'POUND', '#'): - make_key(code=32, names=('3', 'N3')) - elif key in ('4', 'N4'): - make_key(code=33, names=('4', 'N4', 'DOLLAR', 'DLR', '$')) - elif key in ('5', 'N5'): - make_key(code=34, names=('5', 'N5', 'PERCENT', 'PERC', '%')) - elif key in ('6', 'N6'): - make_key(code=35, names=('6', 'N6', 'CIRCUMFLEX', 'CIRC', '^')) - elif key in ('7', 'N7'): - make_key(code=36, names=('7', 'N7', 'AMPERSAND', 'AMPR', '&')) - elif key in ('8', 'N8'): - make_key(code=37, names=('8', 'N8', 'ASTERISK', 'ASTR', '*')) - elif key in ('9', 'N9'): - make_key(code=38, names=('9', 'N9', 'LEFT_PAREN', 'LPRN', '[')) - elif key in ('0', 'N0', 'RIGHT_PAREN', 'LPRN', ']'): - make_key(code=39, names=('0', 'N0')) + names = (ALL_NUMBERS[offset], ALL_NUMBER_ALIASES[offset]) + make_key(code=30 + offset, names=names) - # More ASCII standard keys - elif key in ('ENTER', 'ENT', '\n'): - make_key(code=40, names=('ENTER', 'ENT', '\n')) - elif key in ('ESCAPE', 'ESC'): - make_key(code=41, names=('ESCAPE', 'ESC')) - elif key in ('BACKSPACE', 'BSPC', 'BKSP'): - make_key(code=42, names=('BACKSPACE', 'BSPC', 'BKSP')) - elif key in ('TAB', '\t'): - make_key(code=43, names=('TAB', '\t')) - elif key in ('SPACE', 'SPC', ' '): - make_key(code=44, names=('SPACE', 'SPC', ' ')) - elif key in ('MINUS', 'MINS', '-', 'UNDERSCORE', 'UNDS', '_'): - make_key(code=45, names=('MINUS', 'MINS', '-')) - elif key in ('EQUAL', 'EQL', '=', 'PLUS', '+'): - make_key(code=46, names=('EQUAL', 'EQL', '=')) - elif key in ('LBRACKET', 'LBRC', '[', 'LEFT_CURLY_BRACE', 'LCRB', '{'): - make_key(code=47, names=('LBRACKET', 'LBRC', '[')) - elif key in ('RBRACKET', 'RBRC', ']', 'RIGHT_CURLY_BRACE', 'RCRB', '}'): - make_key(code=48, names=('RBRACKET', 'RBRC', ']')) - elif key in ('BACKSLASH', 'BSLASH', 'BSLS', '\\', 'PIPE', '|'): - make_key(code=49, names=('BACKSLASH', 'BSLASH', 'BSLS', '\\')) - elif key in ('SEMICOLON', 'SCOLON', 'SCLN', ';', 'COLON', 'COLN', ':'): - make_key(code=51, names=('SEMICOLON', 'SCOLON', 'SCLN', ';')) - elif key in ('QUOTE', 'QUOT', "'", 'DOUBLE_QUOTE', 'DQUO', 'DQT', '"'): - make_key(code=52, names=('QUOTE', 'QUOT', "'")) - elif key in ('GRAVE', 'GRV', 'ZKHK', '`', 'TILDE', 'TILD', '~'): - make_key(code=53, names=('GRAVE', 'GRV', 'ZKHK', '`')) - elif key in ('COMMA', 'COMM', ',', 'LEFT_ANGLE_BRACKET', 'LABK', '<'): - make_key(code=54, names=('COMMA', 'COMM', ',')) - elif key in ('DOT', '.', 'RIGHT_ANGLE_BRACKET', 'RABK', '>'): - make_key(code=55, names=('DOT', '.')) - elif key in ('SLASH', 'SLSH', 'QUESTION', 'QUES', '?'): - make_key(code=56, names=('SLASH', 'SLSH')) - - # Function Keys - elif key in ('F1',): - make_key(code=58, names=('F1',)) - elif key in ('F2',): - make_key(code=59, names=('F2',)) - elif key in ('F3',): - make_key(code=60, names=('F3',)) - elif key in ('F4',): - make_key(code=61, names=('F4',)) - elif key in ('F5',): - make_key(code=62, names=('F5',)) - elif key in ('F6',): - make_key(code=63, names=('F6',)) - elif key in ('F7',): - make_key(code=64, names=('F7',)) - elif key in ('F8',): - make_key(code=65, names=('F8',)) - elif key in ('F9',): - make_key(code=66, names=('F9',)) - elif key in ('F10',): - make_key(code=67, names=('F10',)) - elif key in ('F11',): - make_key(code=68, names=('F11',)) - elif key in ('F12',): - make_key(code=69, names=('F12',)) - elif key in ('F13',): - make_key(code=104, names=('F13',)) - elif key in ('F14',): - make_key(code=105, names=('F14',)) - elif key in ('F15',): - make_key(code=106, names=('F15',)) - elif key in ('F16',): - make_key(code=107, names=('F16',)) - elif key in ('F17',): - make_key(code=108, names=('F17',)) - elif key in ('F18',): - make_key(code=109, names=('F18',)) - elif key in ('F19',): - make_key(code=110, names=('F19',)) - elif key in ('F20',): - make_key(code=111, names=('F20',)) - elif key in ('F21',): - make_key(code=112, names=('F21',)) - elif key in ('F22',): - make_key(code=113, names=('F22',)) - elif key in ('F23',): - make_key(code=114, names=('F23',)) - elif key in ('F24',): - make_key(code=115, names=('F24',)) - - # Lock Keys, Navigation, etc. - elif key in ('CAPS_LOCK', 'CAPSLOCK', 'CLCK', 'CAPS'): - make_key(code=57, names=('CAPS_LOCK', 'CAPSLOCK', 'CLCK', 'CAPS')) - # FIXME: Investigate whether this key actually works, and - # uncomment when/if it does. - # elif key in ('LOCKING_CAPS', 'LCAP'): - # # make_key(code=130, names=('LOCKING_CAPS', 'LCAP')) - elif key in ('PRINT_SCREEN', 'PSCREEN', 'PSCR'): - make_key(code=70, names=('PRINT_SCREEN', 'PSCREEN', 'PSCR')) - elif key in ('SCROLL_LOCK', 'SCROLLLOCK', 'SLCK'): - make_key(code=71, names=('SCROLL_LOCK', 'SCROLLLOCK', 'SLCK')) - # FIXME: Investigate whether this key actually works, and - # uncomment when/if it does. - # elif key in ('LOCKING_SCROLL', 'LSCRL'): - # make_key(code=132, names=('LOCKING_SCROLL', 'LSCRL')) - elif key in ('PAUSE', 'PAUS', 'BRK'): - make_key(code=72, names=('PAUSE', 'PAUS', 'BRK')) - elif key in ('INSERT', 'INS'): - make_key(code=73, names=('INSERT', 'INS')) - elif key in ('HOME',): - make_key(code=74, names=('HOME',)) - elif key in ('PGUP',): - make_key(code=75, names=('PGUP',)) - elif key in ('DELETE', 'DEL'): - make_key(code=76, names=('DELETE', 'DEL')) - elif key in ('END',): - make_key(code=77, names=('END',)) - elif key in ('PGDOWN', 'PGDN'): - make_key(code=78, names=('PGDOWN', 'PGDN')) - elif key in ('RIGHT', 'RGHT'): - make_key(code=79, names=('RIGHT', 'RGHT')) - elif key in ('LEFT',): - make_key(code=80, names=('LEFT',)) - elif key in ('DOWN',): - make_key(code=81, names=('DOWN',)) - elif key in ('UP',): - make_key(code=82, names=('UP',)) - - # Numpad - elif key in ('NUM_LOCK', 'NUMLOCK', 'NLCK'): - make_key(code=83, names=('NUM_LOCK', 'NUMLOCK', 'NLCK')) - # FIXME: Investigate whether this key actually works, and - # uncomment when/if it does. - # elif key in ('LOCKING_NUM', 'LNUM'): - # make_key(code=131, names=('LOCKING_NUM', 'LNUM')) - elif key in ('KP_SLASH', 'NUMPAD_SLASH', 'PSLS'): - make_key(code=84, names=('KP_SLASH', 'NUMPAD_SLASH', 'PSLS')) - elif key in ('KP_ASTERISK', 'NUMPAD_ASTERISK', 'PAST'): - make_key(code=85, names=('KP_ASTERISK', 'NUMPAD_ASTERISK', 'PAST')) - elif key in ('KP_MINUS', 'NUMPAD_MINUS', 'PMNS'): - make_key(code=86, names=('KP_MINUS', 'NUMPAD_MINUS', 'PMNS')) - elif key in ('KP_PLUS', 'NUMPAD_PLUS', 'PPLS'): - make_key(code=87, names=('KP_PLUS', 'NUMPAD_PLUS', 'PPLS')) - elif key in ('KP_ENTER', 'NUMPAD_ENTER', 'PENT'): - make_key(code=88, names=('KP_ENTER', 'NUMPAD_ENTER', 'PENT')) - elif key in ('KP_1', 'P1', 'NUMPAD_1'): - make_key(code=89, names=('KP_1', 'P1', 'NUMPAD_1')) - elif key in ('KP_2', 'P2', 'NUMPAD_2'): - make_key(code=90, names=('KP_2', 'P2', 'NUMPAD_2')) - elif key in ('KP_3', 'P3', 'NUMPAD_3'): - make_key(code=91, names=('KP_3', 'P3', 'NUMPAD_3')) - elif key in ('KP_4', 'P4', 'NUMPAD_4'): - make_key(code=92, names=('KP_4', 'P4', 'NUMPAD_4')) - elif key in ('KP_5', 'P5', 'NUMPAD_5'): - make_key(code=93, names=('KP_5', 'P5', 'NUMPAD_5')) - elif key in ('KP_6', 'P6', 'NUMPAD_6'): - make_key(code=94, names=('KP_6', 'P6', 'NUMPAD_6')) - elif key in ('KP_7', 'P7', 'NUMPAD_7'): - make_key(code=95, names=('KP_7', 'P7', 'NUMPAD_7')) - elif key in ('KP_8', 'P8', 'NUMPAD_8'): - make_key(code=96, names=('KP_8', 'P8', 'NUMPAD_8')) - elif key in ('KP_9', 'P9', 'NUMPAD_9'): - make_key(code=97, names=('KP_9', 'P9', 'NUMPAD_9')) - elif key in ('KP_0', 'P0', 'NUMPAD_0'): - make_key(code=98, names=('KP_0', 'P0', 'NUMPAD_0')) - elif key in ('KP_DOT', 'PDOT', 'NUMPAD_DOT'): - make_key(code=99, names=('KP_DOT', 'PDOT', 'NUMPAD_DOT')) - elif key in ('KP_EQUAL', 'PEQL', 'NUMPAD_EQUAL'): - make_key(code=103, names=('KP_EQUAL', 'PEQL', 'NUMPAD_EQUAL')) - elif key in ('KP_COMMA', 'PCMM', 'NUMPAD_COMMA'): - make_key(code=133, names=('KP_COMMA', 'PCMM', 'NUMPAD_COMMA')) - elif key in ('KP_EQUAL_AS400', 'NUMPAD_EQUAL_AS400'): - make_key(code=134, names=('KP_EQUAL_AS400', 'NUMPAD_EQUAL_AS400')) - - # 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. - elif key in ('TILDE', 'TILD', '~'): - make_shifted_key('GRAVE', names=('TILDE', 'TILD', '~')) - elif key in ('EXCLAIM', 'EXLM', '!'): - make_shifted_key('1', names=('EXCLAIM', 'EXLM', '!')) - elif key in ('AT', '@'): - make_shifted_key('2', names=('AT', '@')) - elif key in ('HASH', 'POUND', '#'): - make_shifted_key('3', names=('HASH', 'POUND', '#')) - elif key in ('DOLLAR', 'DLR', '$'): - make_shifted_key('4', names=('DOLLAR', 'DLR', '$')) - elif key in ('PERCENT', 'PERC', '%'): - make_shifted_key('5', names=('PERCENT', 'PERC', '%')) - elif key in ('CIRCUMFLEX', 'CIRC', '^'): - make_shifted_key('6', names=('CIRCUMFLEX', 'CIRC', '^')) - elif key in ('AMPERSAND', 'AMPR', '&'): - make_shifted_key('7', names=('AMPERSAND', 'AMPR', '&')) - elif key in ('ASTERISK', 'ASTR', '*'): - make_shifted_key('8', names=('ASTERISK', 'ASTR', '*')) - elif key in ('LEFT_PAREN', 'LPRN', '('): - make_shifted_key('9', names=('LEFT_PAREN', 'LPRN', '(')) - elif key in ('RIGHT_PAREN', 'RPRN', ')'): - make_shifted_key('0', names=('RIGHT_PAREN', 'RPRN', ')')) - elif key in ('UNDERSCORE', 'UNDS', '_'): - make_shifted_key('MINUS', names=('UNDERSCORE', 'UNDS', '_')) - elif key in ('PLUS', '+'): - make_shifted_key('EQUAL', names=('PLUS', '+')) - elif key in ('LEFT_CURLY_BRACE', 'LCBR', '{'): - make_shifted_key('LBRACKET', names=('LEFT_CURLY_BRACE', 'LCBR', '{')) - elif key in ('RIGHT_CURLY_BRACE', 'RCBR', '}'): - make_shifted_key('RBRACKET', names=('RIGHT_CURLY_BRACE', 'RCBR', '}')) - elif key in ('PIPE', '|'): - make_shifted_key('BACKSLASH', names=('PIPE', '|')) - elif key in ('COLON', 'COLN', ':'): - make_shifted_key('SEMICOLON', names=('COLON', 'COLN', ':')) - elif key in ('DOUBLE_QUOTE', 'DQUO', 'DQT', '"'): - make_shifted_key('QUOTE', names=('DOUBLE_QUOTE', 'DQUO', 'DQT', '"')) - elif key in ('LEFT_ANGLE_BRACKET', 'LABK', '<'): - make_shifted_key('COMMA', names=('LEFT_ANGLE_BRACKET', 'LABK', '<')) - elif key in ('RIGHT_ANGLE_BRACKET', 'RABK', '>'): - make_shifted_key('DOT', names=('RIGHT_ANGLE_BRACKET', 'RABK', '>')) - elif key in ('QUESTION', 'QUES', '?'): - make_shifted_key('SLSH', names=('QUESTION', 'QUES', '?')) - - # International - elif key in ('NONUS_HASH', 'NUHS'): - make_key(code=50, names=('NONUS_HASH', 'NUHS')) - elif key in ('NONUS_BSLASH', 'NUBS'): - make_key(code=100, names=('NONUS_BSLASH', 'NUBS')) - elif key in ('APP', 'APPLICATION', 'SEL', 'WINMENU'): - make_key(code=101, names=('APP', 'APPLICATION', 'SEL', 'WINMENU')) - - elif key in ('INT1', 'RO'): - make_key(code=135, names=('INT1', 'RO')) - elif key in ('INT2', 'KANA'): - make_key(code=136, names=('INT2', 'KANA')) - elif key in ('INT3', 'JYEN'): - make_key(code=137, names=('INT3', 'JYEN')) - elif key in ('INT4', 'HENK'): - make_key(code=138, names=('INT4', 'HENK')) - elif key in ('INT5', 'MHEN'): - make_key(code=139, names=('INT5', 'MHEN')) - elif key in ('INT6',): - make_key(code=140, names=('INT6',)) - elif key in ('INT7',): - make_key(code=141, names=('INT7',)) - elif key in ('INT8',): - make_key(code=142, names=('INT8',)) - elif key in ('INT9',): - make_key(code=143, names=('INT9',)) - elif key in ('LANG1', 'HAEN'): - make_key(code=144, names=('LANG1', 'HAEN')) - elif key in ('LANG2', 'HAEJ'): - make_key(code=145, names=('LANG2', 'HAEJ')) - elif key in ('LANG3',): - make_key(code=146, names=('LANG3',)) - elif key in ('LANG4',): - make_key(code=147, names=('LANG4',)) - elif key in ('LANG5',): - make_key(code=148, names=('LANG5',)) - elif key in ('LANG6',): - make_key(code=149, names=('LANG6',)) - elif key in ('LANG7',): - make_key(code=150, names=('LANG7',)) - elif key in ('LANG8',): - make_key(code=151, names=('LANG8',)) - elif key in ('LANG9',): - make_key(code=152, names=('LANG9',)) - - # 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 - # back in piecemeal as needed. PRs welcome. - # - # A super useful reference for these is http://www.freebsddiary.org/APC/usb_hid_usages.php - # Note that currently we only have the PC codes. Recent MacOS versions seem to - # 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. - elif key in ('AUDIO_MUTE', 'MUTE'): - make_consumer_key(code=226, names=('AUDIO_MUTE', 'MUTE')) # 0xE2 - elif key in ('AUDIO_VOL_UP', 'VOLU'): - make_consumer_key(code=233, names=('AUDIO_VOL_UP', 'VOLU')) # 0xE9 - elif key in ('AUDIO_VOL_DOWN', 'VOLD'): - make_consumer_key(code=234, names=('AUDIO_VOL_DOWN', 'VOLD')) # 0xEA - elif key in ('MEDIA_NEXT_TRACK', 'MNXT'): - make_consumer_key(code=181, names=('MEDIA_NEXT_TRACK', 'MNXT')) # 0xB5 - elif key in ('MEDIA_PREV_TRACK', 'MPRV'): - make_consumer_key(code=182, names=('MEDIA_PREV_TRACK', 'MPRV')) # 0xB6 - elif key in ('MEDIA_STOP', 'MSTP'): - make_consumer_key(code=183, names=('MEDIA_STOP', 'MSTP')) # 0xB7 - elif key in ('MEDIA_PLAY_PAUSE', 'MPLY'): - make_consumer_key( - code=205, names=('MEDIA_PLAY_PAUSE', 'MPLY') - ) # 0xCD (this may not be right) - elif key in ('MEDIA_EJECT', 'EJCT'): - make_consumer_key(code=184, names=('MEDIA_EJECT', 'EJCT')) # 0xB8 - elif key in ('MEDIA_FAST_FORWARD', 'MFFD'): - make_consumer_key(code=179, names=('MEDIA_FAST_FORWARD', 'MFFD')) # 0xB3 - elif key in ('MEDIA_REWIND', 'MRWD'): - make_consumer_key(code=180, names=('MEDIA_REWIND', 'MRWD')) # 0xB4 + # Now try all the other weird special cases to get them out of our way: # Internal, diagnostic, or auxiliary/enhanced keys @@ -447,12 +118,6 @@ class KeyAttrDict(AttrDict): on_press=handlers.debug_pressed, on_release=handlers.passthrough, ) - elif key in ('GESC',): - make_key( - names=('GESC',), - on_press=handlers.gesc_pressed, - on_release=handlers.gesc_released, - ) elif key in ('BKDL',): make_key( names=('BKDL',), @@ -514,8 +179,204 @@ class KeyAttrDict(AttrDict): elif key in ('HID_SWITCH', 'HID'): make_key(names=('HID_SWITCH', 'HID'), on_press=handlers.hid_switch) else: - raise ValueError('Invalid key') - return self.__getattr__(key) + maybe_key = first_truthy( + key, + # Modifiers + lambda key: left_pipe_until_some( + key, + maybe_make_mod_key, + (0x01, ('LEFT_CONTROL', 'LCTRL', 'LCTL')), + (0x02, ('LEFT_SHIFT', 'LSHIFT', 'LSFT')), + (0x04, ('LEFT_ALT', 'LALT')), + (0x08, ('LEFT_SUPER', 'LGUI', 'LCMD', 'LWIN')), + (0x10, ('RIGHT_CONTROL', 'RCTRL', 'RCTL')), + (0x20, ('RIGHT_SHIFT', 'RSHIFT', 'RSFT')), + (0x40, ('RIGHT_ALT', 'RALT')), + (0x80, ('RIGHT_SUPER', 'RGUI', 'RCMD', 'RWIN')), + # MEH = LCTL | LALT | LSFT# MEH = LCTL | + (0x07, ('MEH',)), + # HYPR = LCTL | LALT | LSFT | LGUI + (0x0F, ('HYPER', 'HYPR')), + ), + lambda key: left_pipe_until_some( + key, + maybe_make_key, + # More ASCII standard keys + (40, ('ENTER', 'ENT', '\n')), + (41, ('ESCAPE', 'ESC')), + (42, ('BACKSPACE', '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')), + # Function Keys + (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',)), + # Lock Keys, Navigation, etc. + (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',)), + # Numpad + (83, ('NUM_LOCK', 'NUMLOCK', 'NLCK')), + # FIXME: Investigate whether this key actually works, and + # uncomment when/if it does. + # (131, names=('LOCKING_NUM', 'LNUM')), + (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')), + ), + # 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. + lambda key: left_pipe_until_some( + key, + maybe_make_shifted_key, + ('GRAVE', ('TILDE', 'TILD', '~')), + ('1', ('EXCLAIM', 'EXLM', '!')), + ('2', ('AT', '@')), + ('3', ('HASH', 'POUND', '#')), + ('4', ('DOLLAR', 'DLR', '$')), + ('5', ('PERCENT', 'PERC', '%')), + ('6', ('CIRCUMFLEX', 'CIRC', '^')), + ('7', ('AMPERSAND', 'AMPR', '&')), + ('8', ('ASTERISK', 'ASTR', '*')), + ('9', ('LEFT_PAREN', 'LPRN', '(')), + ('0', ('RIGHT_PAREN', 'RPRN', ')')), + ('MINUS', ('UNDERSCORE', 'UNDS', '_')), + ('EQUAL', ('PLUS', '+')), + ('LBRACKET', ('LEFT_CURLY_BRACE', 'LCBR', '{')), + ('RBRACKET', ('RIGHT_CURLY_BRACE', 'RCBR', '}')), + ('BACKSLASH', ('PIPE', '|')), + ('SEMICOLON', ('COLON', 'COLN', ':')), + ('QUOTE', ('DOUBLE_QUOTE', 'DQUO', 'DQT', '"')), + ('COMMA', ('LEFT_ANGLE_BRACKET', 'LABK', '<')), + ('DOT', ('RIGHT_ANGLE_BRACKET', 'RABK', '>')), + ('SLSH', ('QUESTION', 'QUES', '?')), + ), + # International + lambda key: left_pipe_until_some( + key, + maybe_make_key, + (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',)), + ), + # 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 + # back in piecemeal as needed. PRs welcome. + # + # A super useful reference for these is http://www.freebsddiary.org/APC/usb_hid_usages.php + # Note that currently we only have the PC codes. Recent MacOS versions seem to + # 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. + lambda key: left_pipe_until_some( + key, + maybe_make_consumer_key, + (226, ('AUDIO_MUTE', 'MUTE')), # 0xE2 + (233, ('AUDIO_VOL_UP', 'VOLU')), # 0xE9 + (234, ('AUDIO_VOL_DOWN', 'VOLD')), # 0xEA + (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 + ), + ) + + if DEBUG_OUTPUT: + print(f'{key}: {maybe_key}') + + if not maybe_key: + raise ValueError('Invalid key') + + return self.__getattr__(key, depth=depth + 1) # Global state, will be filled in througout this file, and @@ -810,12 +671,19 @@ def make_key(code=None, names=tuple(), type=KEY_SIMPLE, **kwargs): # NOQA return key -def make_mod_key(*args, **kwargs): - return make_key(*args, **kwargs, type=KEY_MODIFIER) +def make_mod_key(code, names, *args, **kwargs): + return make_key(code, names, *args, **kwargs, type=KEY_MODIFIER) def make_shifted_key(target_name, names=tuple()): # NOQA - key = KC.LSFT(KC[target_name]) + # For... probably a few years, a bug existed here where keys were looked + # up by `KC[...]`, but that's incorrect: an AttrDit exposes a dictionary + # with attributes, but key-based dictionary access with brackets does + # *not* implicitly call __getattr__. We got away with this when key defs + # were not lazily created, but now that they are, shifted keys would + # sometimes break, complaining that they couldn't find their underlying + # key to shift. Fixed! + key = KC.LSFT(getattr(KC, target_name)) register_key_names(key, names)