kmk_firmware/kmk/common/keycodes.py
Josh Klar b7b1866ac9
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 16:08:08 -07:00

436 lines
15 KiB
Python

try:
from collections import namedtuple
except ImportError:
# This is handled by micropython-lib/collections, but on local runs of
# MicroPython, it doesn't exist
from ucollections import namedtuple
from kmk.common.types import AttrDict
from kmk.common.util import flatten_dict
FIRST_KMK_INTERNAL_KEYCODE = 1000
LayerKeycode = namedtuple('LayerKeycode', ('code', 'layer'))
class Keycode:
def __init__(self, code, has_modifiers=None):
self.code = code
self.has_modifiers = has_modifiers
class ModifierKeycode:
def __init__(self, code):
self.code = code
class ConsumerKeycode:
def __init__(self, code):
self.code = code
class KeycodeCategory(type):
@classmethod
def to_dict(cls):
'''
MicroPython, for whatever reason (probably performance/memory) makes
__dict__ optional for ports. Unfortunately, at least the STM32
(Pyboard) port is one such port. This reimplements a subset of
__dict__, limited to just keys we're likely to care about (though this
could be opened up further later).
'''
hidden = ('to_dict', 'recursive_dict', 'contains')
return AttrDict({
key: getattr(cls, key)
for key in dir(cls)
if not key.startswith('_') and key not in hidden
})
@classmethod
def recursive_dict(cls):
'''
to_dict() executed recursively all the way down a tree
'''
ret = cls.to_dict()
for key, val in ret.items():
try:
nested_ret = val.recursive_dict()
except (AttributeError, NameError):
continue
ret[key] = nested_ret
return ret
@classmethod
def contains(cls, kc):
'''
Emulates the 'in' operator for keycode groupings, given MicroPython's
lack of support for metaclasses (meaning implementing 'in' for
uninstantiated classes, such as these, is largely not possible). Not
super useful in most cases, but does allow for sanity checks like
```python
assert Keycodes.Modifiers.contains(requested_key)
```
This is not bulletproof due to how HID codes are defined (there is
overlap). Keycodes.Common.KC_A, for example, is equal in value to
Keycodes.Modifiers.KC_LALT, but it can still prevent silly mistakes
like trying to use, say, Keycodes.Common.KC_Q as a modifier.
This is recursive across subgroups, enabling stuff like:
```python
assert Keycodes.contains(requested_key)
```
To ensure that a valid keycode has been requested to begin with. Again,
not bulletproof, but adds at least some cushion to stuff that would
otherwise cause AttributeErrors and crash the keyboard.
'''
subcategories = (
category for category in cls.to_dict().values()
# Disgusting, but since `cls.__bases__` isn't implemented in MicroPython,
# I resort to a less foolproof inheritance check that should still ignore
# strings and other stupid stuff (we don't want to iterate over __doc__,
# for example), but include nested classes.
#
# One huge lesson in this project is that uninstantiated classes are hard...
# and four times harder when the implementation of Python is half-baked.
if isinstance(category, type)
)
if any(
kc == _kc
for name, _kc in cls.to_dict().items()
if name.startswith('KC_')
):
return True
return any(sc.contains(kc) for sc in subcategories)
CODE_LCTRL = CODE_LCTL = 0x01
CODE_LSHIFT = CODE_LSFT = 0x02
CODE_LALT = 0x04
CODE_LGUI = CODE_LCMD = CODE_LWIN = 0x08
CODE_RCTRL = CODE_RCTL = 0x10
CODE_RSHIFT = CODE_RSFT = 0x20
CODE_RALT = 0x40
CODE_RGUI = CODE_RCMD = CODE_RWIN = 0x80
class Keycodes(KeycodeCategory):
'''
A massive grouping of keycodes
Some of these are from http://www.freebsddiary.org/APC/usb_hid_usages.php,
one of the most useful pages on the interwebs for HID stuff, apparently.
'''
class Modifiers(KeycodeCategory):
KC_LCTRL = KC_LCTL = ModifierKeycode(CODE_LCTRL)
KC_LSHIFT = KC_LSFT = ModifierKeycode(CODE_LSHIFT)
KC_LALT = ModifierKeycode(CODE_LALT)
KC_LGUI = KC_LCMD = KC_LWIN = ModifierKeycode(CODE_LGUI)
KC_RCTRL = KC_RCTL = ModifierKeycode(CODE_RCTRL)
KC_RSHIFT = KC_RSFT = ModifierKeycode(CODE_RSHIFT)
KC_RALT = ModifierKeycode(CODE_RALT)
KC_RGUI = KC_RCMD = KC_RWIN = ModifierKeycode(CODE_RGUI)
class Common(KeycodeCategory):
KC_A = Keycode(4)
KC_B = Keycode(5)
KC_C = Keycode(6)
KC_D = Keycode(7)
KC_E = Keycode(8)
KC_F = Keycode(9)
KC_G = Keycode(10)
KC_H = Keycode(11)
KC_I = Keycode(12)
KC_J = Keycode(13)
KC_K = Keycode(14)
KC_L = Keycode(15)
KC_M = Keycode(16)
KC_N = Keycode(17)
KC_O = Keycode(18)
KC_P = Keycode(19)
KC_Q = Keycode(20)
KC_R = Keycode(21)
KC_S = Keycode(22)
KC_T = Keycode(23)
KC_U = Keycode(24)
KC_V = Keycode(25)
KC_W = Keycode(26)
KC_X = Keycode(27)
KC_Y = Keycode(28)
KC_Z = Keycode(29)
# Aliases to play nicely with AttrDict, since KC.1 isn't a valid
# attribute key in Python, but KC.N1 is
KC_1 = KC_N1 = Keycode(30)
KC_2 = KC_N2 = Keycode(31)
KC_3 = KC_N3 = Keycode(32)
KC_4 = KC_N4 = Keycode(33)
KC_5 = KC_N5 = Keycode(34)
KC_6 = KC_N6 = Keycode(35)
KC_7 = KC_N7 = Keycode(36)
KC_8 = KC_N8 = Keycode(37)
KC_9 = KC_N9 = Keycode(38)
KC_0 = KC_N0 = Keycode(39)
KC_ENTER = KC_ENT = Keycode(40)
KC_ESCAPE = KC_ESC = Keycode(41)
KC_BACKSPACE = KC_BKSP = Keycode(42)
KC_TAB = Keycode(43)
KC_SPACE = KC_SPC = Keycode(44)
KC_MINUS = KC_MINS = Keycode(45)
KC_EQUAL = KC_EQL = Keycode(46)
KC_LBRACKET = KC_LBRC = Keycode(47)
KC_RBRACKET = KC_RBRC = Keycode(48)
KC_BACKSLASH = KC_BSLASH = KC_BSLS = Keycode(49)
KC_NONUS_HASH = KC_NUHS = Keycode(50)
KC_NONUS_BSLASH = KC_NUBS = Keycode(100)
KC_SEMICOLON = KC_SCOLON = KC_SCLN = Keycode(51)
KC_QUOTE = KC_QUOT = Keycode(52)
KC_GRAVE = KC_GRV = KC_ZKHK = Keycode(53)
KC_COMMA = KC_COMM = Keycode(54)
KC_DOT = Keycode(55)
KC_SLASH = KC_SLSH = Keycode(56)
class FunctionKeys(KeycodeCategory):
KC_F1 = Keycode(58)
KC_F2 = Keycode(59)
KC_F3 = Keycode(60)
KC_F4 = Keycode(61)
KC_F5 = Keycode(62)
KC_F6 = Keycode(63)
KC_F7 = Keycode(64)
KC_F8 = Keycode(65)
KC_F9 = Keycode(66)
KC_F10 = Keycode(67)
KC_F11 = Keycode(68)
KC_F12 = Keycode(69)
KC_F13 = Keycode(104)
KC_F14 = Keycode(105)
KC_F15 = Keycode(106)
KC_F16 = Keycode(107)
KC_F17 = Keycode(108)
KC_F18 = Keycode(109)
KC_F19 = Keycode(110)
KC_F20 = Keycode(111)
KC_F21 = Keycode(112)
KC_F22 = Keycode(113)
KC_F23 = Keycode(114)
KC_F24 = Keycode(115)
class NavAndLocks(KeycodeCategory):
KC_CAPS_LOCK = KC_CLCK = KC_CAPS = Keycode(57)
KC_LOCKING_CAPS = KC_LCAP = Keycode(130)
KC_PSCREEN = KC_PSCR = Keycode(70)
KC_SCROLLLOCK = KC_SLCK = Keycode(71)
KC_LOCKING_SCROLL = KC_LSCRL = Keycode(132)
KC_PAUSE = KC_PAUS = KC_BRK = Keycode(72)
KC_INSERT = KC_INS = Keycode(73)
KC_HOME = Keycode(74)
KC_PGUP = Keycode(75)
KC_DELETE = KC_DEL = Keycode(76)
KC_END = Keycode(77)
KC_PGDOWN = KC_PGDN = Keycode(78)
KC_RIGHT = KC_RGHT = Keycode(79)
KC_LEFT = Keycode(80)
KC_DOWN = Keycode(81)
KC_UP = Keycode(82)
class Numpad(KeycodeCategory):
KC_NUMLOCK = KC_NLCK = Keycode(83)
KC_LOCKING_NUM = KC_LNUM = Keycode(131)
KC_KP_SLASH = KC_PSLS = Keycode(84)
KC_KP_ASTERIK = KC_PAST = Keycode(85)
KC_KP_MINUS = KC_PMNS = Keycode(86)
KC_KP_PLUS = KC_PPLS = Keycode(87)
KC_KP_ENTER = KC_PENT = Keycode(88)
KC_KP_1 = KC_P1 = Keycode(89)
KC_KP_2 = KC_P2 = Keycode(90)
KC_KP_3 = KC_P3 = Keycode(91)
KC_KP_4 = KC_P4 = Keycode(92)
KC_KP_5 = KC_P5 = Keycode(93)
KC_KP_6 = KC_P6 = Keycode(94)
KC_KP_7 = KC_P7 = Keycode(95)
KC_KP_8 = KC_P8 = Keycode(96)
KC_KP_9 = KC_P9 = Keycode(97)
KC_KP_0 = KC_P0 = Keycode(98)
KC_KP_DOT = KC_PDOT = Keycode(99)
KC_KP_EQUAL = KC_PEQL = Keycode(103)
KC_KP_COMMA = KC_PCMM = Keycode(133)
KC_KP_EQUAL_AS400 = Keycode(134)
class International(KeycodeCategory):
KC_INT1 = KC_RO = Keycode(135)
KC_INT2 = KC_KANA = Keycode(136)
KC_INT3 = KC_JYEN = Keycode(137)
KC_INT4 = KC_HENK = Keycode(138)
KC_INT5 = KC_MHEN = Keycode(139)
KC_INT6 = Keycode(140)
KC_INT7 = Keycode(141)
KC_INT8 = Keycode(142)
KC_INT9 = Keycode(143)
KC_LANG1 = KC_HAEN = Keycode(144)
KC_LANG2 = KC_HAEJ = Keycode(145)
KC_LANG3 = Keycode(146)
KC_LANG4 = Keycode(147)
KC_LANG5 = Keycode(148)
KC_LANG6 = Keycode(149)
KC_LANG7 = Keycode(150)
KC_LANG8 = Keycode(151)
KC_LANG9 = Keycode(152)
class Misc(KeycodeCategory):
KC_APPLICATION = KC_APP = ConsumerKeycode(101)
KC_POWER = ConsumerKeycode(102)
KC_EXECUTE = KC_EXEC = ConsumerKeycode(116)
KC_SYSTEM_POWER = KC_PWR = ConsumerKeycode(165)
KC_SYSTEM_SLEEP = KC_SLEP = ConsumerKeycode(166)
KC_SYSTEM_WAKE = KC_WAKE = ConsumerKeycode(167)
KC_HELP = ConsumerKeycode(117)
KC_MENU = ConsumerKeycode(118)
KC_SELECT = KC_SLCT = ConsumerKeycode(119)
KC_STOP = ConsumerKeycode(120)
KC_AGAIN = KC_AGIN = ConsumerKeycode(121)
KC_UNDO = ConsumerKeycode(122)
KC_CUT = ConsumerKeycode(123)
KC_COPY = ConsumerKeycode(124)
KC_PASTE = KC_PSTE = ConsumerKeycode(125)
KC_FIND = ConsumerKeycode(126)
KC_ALT_ERASE = KC_ERAS = ConsumerKeycode(153)
KC_SYSREQ = ConsumerKeycode(154)
KC_CANCEL = ConsumerKeycode(155)
KC_CLEAR = KC_CLR = ConsumerKeycode(156)
KC_PRIOR = ConsumerKeycode(157)
KC_RETURN = ConsumerKeycode(158)
KC_SEPERATOR = ConsumerKeycode(159)
KC_OUT = ConsumerKeycode(160)
KC_OPER = ConsumerKeycode(161)
KC_CLEAR_AGAIN = ConsumerKeycode(162)
KC_CRSEL = ConsumerKeycode(163)
KC_EXSEL = ConsumerKeycode(164)
KC_MAIL = ConsumerKeycode(177)
KC_CALCULATOR = KC_CALC = ConsumerKeycode(178)
KC_MY_COMPUTER = KC_MYCM = ConsumerKeycode(179)
KC_WWW_SEARCH = KC_WSCH = ConsumerKeycode(180)
KC_WWW_HOME = KC_WHOM = ConsumerKeycode(181)
KC_WWW_BACK = KC_WBAK = ConsumerKeycode(182)
KC_WWW_FORWARD = KC_WFWD = ConsumerKeycode(183)
KC_WWW_STOP = KC_WSTP = ConsumerKeycode(184)
KC_WWW_REFRESH = KC_WREF = ConsumerKeycode(185)
KC_WWW_FAVORITES = KC_WFAV = ConsumerKeycode(186)
class Media(KeycodeCategory):
# I believe QMK used these double-underscore codes for MacOS
# support or something. I have no idea, but modern MacOS supports
# PC volume keys so I really don't care that these codes are the
# same as below. If bugs arise, these codes may need to change.
KC__MUTE = ConsumerKeycode(226)
KC__VOLUP = ConsumerKeycode(233)
KC__VOLDOWN = ConsumerKeycode(234)
KC_AUDIO_MUTE = KC_MUTE = ConsumerKeycode(226) # 0xE2
KC_AUDIO_VOL_UP = KC_VOLU = ConsumerKeycode(233) # 0xE9
KC_AUDIO_VOL_DOWN = KC_VOLD = ConsumerKeycode(234) # 0xEA
KC_MEDIA_NEXT_TRACK = KC_MNXT = ConsumerKeycode(181) # 0xB5
KC_MEDIA_PREV_TRACK = KC_MPRV = ConsumerKeycode(182) # 0xB6
KC_MEDIA_STOP = KC_MSTP = ConsumerKeycode(183) # 0xB7
KC_MEDIA_PLAY_PAUSE = KC_MPLY = ConsumerKeycode(205) # 0xCD (this may not be right)
KC_MEDIA_EJECT = KC_EJCT = ConsumerKeycode(184) # 0xB8
KC_MEDIA_FAST_FORWARD = KC_MFFD = ConsumerKeycode(179) # 0xB3
KC_MEDIA_REWIND = KC_MRWD = ConsumerKeycode(180) # 0xB4
class KMK(KeycodeCategory):
KC_RESET = Keycode(1000)
KC_DEBUG = Keycode(1001)
KC_GESC = Keycode(1002)
KC_LSPO = Keycode(1003)
KC_RSPC = Keycode(1004)
KC_LEAD = Keycode(1005)
KC_LOCK = Keycode(1006)
KC_NO = Keycode(1107)
KC_TRANSPARENT = KC_TRNS = Keycode(1108)
class Layers(KeycodeCategory):
_KC_DF = 1050
_KC_MO = 1051
_KC_LM = 1052
_KC_LT = 1053
_KC_TG = 1054
_KC_TO = 1055
_KC_TT = 1056
@staticmethod
def KC_DF(layer):
return LayerKeycode(Keycodes.Layers._KC_DF, layer)
@staticmethod
def KC_MO(layer):
return LayerKeycode(Keycodes.Layers._KC_MO, layer)
@staticmethod
def KC_LM(layer):
return LayerKeycode(Keycodes.Layers._KC_LM, layer)
@staticmethod
def KC_LT(layer):
return LayerKeycode(Keycodes.Layers._KC_LT, layer)
@staticmethod
def KC_TG(layer):
return LayerKeycode(Keycodes.Layers._KC_TG, layer)
@staticmethod
def KC_TO(layer):
return LayerKeycode(Keycodes.Layers._KC_TO, layer)
@staticmethod
def KC_TT(layer):
return LayerKeycode(Keycodes.Layers._KC_TT, layer)
class ShiftedKeycodes(KeycodeCategory):
KC_TILDE = KC_TILD = Keycode(53, (CODE_LSHIFT,))
KC_EXCLAIM = KC_EXLM = Keycode(30, (CODE_LSHIFT,))
KC_AT = Keycode(31, (CODE_LSHIFT,))
KC_HASH = Keycode(32, (CODE_LSHIFT,))
KC_DOLLAR = KC_DLR = Keycode(33, (CODE_LSHIFT,))
KC_PERCENT = KC_PERC = Keycode(34, (CODE_LSHIFT,))
KC_CIRCUMFLEX = KC_CIRC = Keycode(35, (CODE_LSHIFT,)) # The ^ Symbol
KC_AMPERSAND = KC_AMPR = Keycode(36, (CODE_LSHIFT,))
KC_ASTERISK = KC_ASTR = Keycode(37, (CODE_LSHIFT,))
KC_LEFT_PAREN = KC_LPRN = Keycode(38, (CODE_LSHIFT,))
KC_RIGHT_PAREN = KC_RPRN = Keycode(39, (CODE_LSHIFT,))
KC_UNDERSCORE = KC_UNDS = Keycode(45, (CODE_LSHIFT,))
KC_PLUS = Keycode(46, (CODE_LSHIFT,))
KC_LEFT_CURLY_BRACE = KC_LCBR = Keycode(47, (CODE_LSHIFT,))
KC_RIGHT_CURLY_BRACE = KC_RCBR = Keycode(48, (CODE_LSHIFT,))
KC_PIPE = Keycode(49, (CODE_LSHIFT,))
KC_COLON = KC_COLN = Keycode(51, (CODE_LSHIFT,))
KC_DOUBLE_QUOTE = KC_DQUO = KC_DQT = Keycode(52, (CODE_LSHIFT,))
KC_LEFT_ANGLE_BRACKET = KC_LABK = KC_LT = Keycode(54, (CODE_LSHIFT,))
KC_RIGHT_ANGLE_BRACKET = KC_RABK = KC_GT = Keycode(55, (CODE_LSHIFT,))
KC_QUESTION = KC_QUES = Keycode(56, (CODE_LSHIFT,))
ALL_KEYS = KC = AttrDict({
k.replace('KC_', ''): v
for k, v in flatten_dict(Keycodes.recursive_dict()).items()
})
char_lookup = {
"\n": (Keycodes.Common.KC_ENTER,),
"\t": (Keycodes.Common.KC_TAB,),
' ': (Keycodes.Common.KC_SPACE,),
'-': (Keycodes.Common.KC_MINUS,),
'=': (Keycodes.Common.KC_EQUAL,),
'+': (Keycodes.Common.KC_EQUAL, Keycodes.Modifiers.KC_LSHIFT),
'~': (Keycodes.Common.KC_GRAVE,),
}