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.consts import UnicodeMode from kmk.types import AttrDict FIRST_KMK_INTERNAL_KEYCODE = 1000 kc_lookup_cache = {} def lookup_kc_with_cache(char): found_code = kc_lookup_cache.get( char, getattr(Common, 'KC_{}'.format(char.upper())), ) kc_lookup_cache[char] = found_code kc_lookup_cache[char.upper()] = found_code kc_lookup_cache[char.lower()] = found_code return found_code def generate_codepoint_keysym_seq(codepoint, expected_length=4): # To make MacOS and Windows happy, always try to send # sequences that are of length 4 at a minimum # On Linux systems, we can happily send longer strings. # They will almost certainly break on MacOS and Windows, # but this is a documentation problem more than anything. # Not sure how to send emojis on Mac/Windows like that, # though, since (for example) the Canadian flag is assembled # from two five-character codepoints, 1f1e8 and 1f1e6 # # As a bonus, this function can be pretty useful for # leader dictionary keys as strings. seq = [Common.KC_0 for _ in range(max(len(codepoint), expected_length))] for idx, codepoint_fragment in enumerate(reversed(codepoint)): seq[-(idx + 1)] = lookup_kc_with_cache(codepoint_fragment) return seq def generate_leader_dictionary_seq(string): return tuple(generate_codepoint_keysym_seq(string, 1)) class RawKeycodes: ''' These are raw keycode numbers for keys we'll use in generated "keys". For example, we want to be able to check against these numbers in the internal_keycodes reducer fragments, but due to a limitation in MicroPython, we can't simply assign the `.code` attribute to a function (which is what most internal KMK keys (including layer stuff) are implemented as). Thus, we have to keep an external lookup table. ''' LCTRL = 0x01 LSHIFT = 0x02 LALT = 0x04 LGUI = 0x08 RCTRL = 0x10 RSHIFT = 0x20 RALT = 0x40 RGUI = 0x80 KC_DF = 1050 KC_MO = 1051 KC_LM = 1052 KC_LT = 1053 KC_TG = 1054 KC_TO = 1055 KC_TT = 1056 KC_UC_MODE = 1109 KC_MACRO = 1110 KC_MACRO_SLEEP_MS = 1111 KC_TAP_DANCE = 1113 # These shouldn't have all the fancy shenanigans Keycode allows # such as no_press, because they modify KMK internal state in # ways we need to tightly control. Thus, we can get away with # a lighter-weight namedtuple implementation here LayerKeycode = namedtuple('LayerKeycode', ('code', 'layer', 'kc')) MacroSleepKeycode = namedtuple('MacroSleepKeycode', ('code', 'ms')) class UnicodeModeKeycode(namedtuple('UnicodeModeKeycode', ('code', 'mode'))): @staticmethod def from_mode_const(mode): return UnicodeModeKeycode(RawKeycodes.KC_UC_MODE, mode) class Keycode: def __init__(self, code, has_modifiers=None, no_press=False, no_release=False): self.code = code self.has_modifiers = has_modifiers # cast to bool() in case we get a None value self.no_press = bool(no_press) self.no_release = bool(no_press) def __call__(self, no_press=None, no_release=None): if no_press is None and no_release is None: return self return Keycode( code=self.code, has_modifiers=self.has_modifiers, no_press=no_press, no_release=no_release, ) def __repr__(self): return 'Keycode(code={}, has_modifiers={})'.format(self.code, self.has_modifiers) class ModifierKeycode(Keycode): FAKE_CODE = -1 def __call__(self, modified_code=None, no_press=None, no_release=None): if modified_code is None and no_press is None and no_release is None: return self if modified_code is not None: if isinstance(modified_code, ModifierKeycode): new_keycode = ModifierKeycode( ModifierKeycode.FAKE_CODE, set() if self.has_modifiers is None else self.has_modifiers, no_press=no_press, no_release=no_release, ) if self.code != ModifierKeycode.FAKE_CODE: new_keycode.has_modifiers.add(self.code) if modified_code.code != ModifierKeycode.FAKE_CODE: new_keycode.has_modifiers.add(modified_code.code) else: new_keycode = Keycode( modified_code.code, {self.code}, no_press=no_press, no_release=no_release, ) if modified_code.has_modifiers: new_keycode.has_modifiers |= modified_code.has_modifiers else: new_keycode = Keycode( self.code, no_press=no_press, no_release=no_release, ) return new_keycode def __repr__(self): return 'ModifierKeycode(code={}, has_modifiers={})'.format(self.code, self.has_modifiers) class ConsumerKeycode(Keycode): pass class Macro: ''' A special "key" which triggers a macro. ''' code = RawKeycodes.KC_MACRO def __init__(self, keydown=None, keyup=None): self.keydown = keydown self.keyup = keyup def on_keydown(self): return self.keydown() if self.keydown else None def on_keyup(self): return self.keyup() if self.keyup else None class TapDanceKeycode: code = RawKeycodes.KC_TAP_DANCE def __init__(self, *codes): self.codes = codes 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) class Modifiers(KeycodeCategory): KC_LCTRL = KC_LCTL = ModifierKeycode(RawKeycodes.LCTRL) KC_LSHIFT = KC_LSFT = ModifierKeycode(RawKeycodes.LSHIFT) KC_LALT = ModifierKeycode(RawKeycodes.LALT) KC_LGUI = KC_LCMD = KC_LWIN = ModifierKeycode(RawKeycodes.LGUI) KC_RCTRL = KC_RCTL = ModifierKeycode(RawKeycodes.RCTRL) KC_RSHIFT = KC_RSFT = ModifierKeycode(RawKeycodes.RSHIFT) KC_RALT = ModifierKeycode(RawKeycodes.RALT) KC_RGUI = KC_RCMD = KC_RWIN = ModifierKeycode(RawKeycodes.RGUI) KC_MEH = KC_LSHIFT(KC_LALT(KC_LCTRL)) KC_HYPR = KC_HYPER = KC_MEH(KC_LGUI) 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_BSPC = 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 ShiftedKeycodes(KeycodeCategory): KC_TILDE = KC_TILD = Modifiers.KC_LSHIFT(Common.KC_GRAVE) KC_EXCLAIM = KC_EXLM = Modifiers.KC_LSHIFT(Common.KC_1) KC_AT = Modifiers.KC_LSHIFT(Common.KC_2) KC_HASH = Modifiers.KC_LSHIFT(Common.KC_3) KC_DOLLAR = KC_DLR = Modifiers.KC_LSHIFT(Common.KC_4) KC_PERCENT = KC_PERC = Modifiers.KC_LSHIFT(Common.KC_5) KC_CIRCUMFLEX = KC_CIRC = Modifiers.KC_LSHIFT(Common.KC_6) # The ^ Symbol KC_AMPERSAND = KC_AMPR = Modifiers.KC_LSHIFT(Common.KC_7) KC_ASTERISK = KC_ASTR = Modifiers.KC_LSHIFT(Common.KC_8) KC_LEFT_PAREN = KC_LPRN = Modifiers.KC_LSHIFT(Common.KC_9) KC_RIGHT_PAREN = KC_RPRN = Modifiers.KC_LSHIFT(Common.KC_0) KC_UNDERSCORE = KC_UNDS = Modifiers.KC_LSHIFT(Common.KC_MINUS) KC_PLUS = Modifiers.KC_LSHIFT(Common.KC_EQUAL) KC_LEFT_CURLY_BRACE = KC_LCBR = Modifiers.KC_LSHIFT(Common.KC_LBRACKET) KC_RIGHT_CURLY_BRACE = KC_RCBR = Modifiers.KC_LSHIFT(Common.KC_RBRACKET) KC_PIPE = Modifiers.KC_LSHIFT(Common.KC_BACKSLASH) KC_COLON = KC_COLN = Modifiers.KC_LSHIFT(Common.KC_SEMICOLON) KC_DOUBLE_QUOTE = KC_DQUO = KC_DQT = Modifiers.KC_LSHIFT(Common.KC_QUOTE) KC_LEFT_ANGLE_BRACKET = KC_LABK = Modifiers.KC_LSHIFT(Common.KC_COMMA) KC_RIGHT_ANGLE_BRACKET = KC_RABK = Modifiers.KC_LSHIFT(Common.KC_DOT) KC_QUESTION = KC_QUES = Modifiers.KC_LSHIFT(Common.KC_SLSH) 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) KC_DEBUG = KC_DBG = Keycode(1112) @staticmethod def KC_UC_MODE(mode): ''' Set any Unicode Mode at runtime (allows the same keymap's unicode sequences to work across all supported platforms) ''' return UnicodeModeKeycode.from_mode_const(mode) KC_UC_MODE_NOOP = KC_UC_DISABLE = UnicodeModeKeycode.from_mode_const(UnicodeMode.NOOP) KC_UC_MODE_LINUX = KC_UC_MODE_IBUS = UnicodeModeKeycode.from_mode_const(UnicodeMode.IBUS) KC_UC_MODE_MACOS = KC_UC_MODE_OSX = KC_UC_MODE_RALT = UnicodeModeKeycode.from_mode_const( UnicodeMode.RALT, ) KC_UC_MODE_WINC = UnicodeModeKeycode.from_mode_const(UnicodeMode.WINC) @staticmethod def KC_MACRO_SLEEP_MS(ms): return MacroSleepKeycode(RawKeycodes.KC_MACRO_SLEEP_MS, ms) @staticmethod def KC_TAP_DANCE(*args): return TapDanceKeycode(*args) KMK.KC_TD = KMK.KC_TAP_DANCE class Layers(KeycodeCategory): @staticmethod def KC_DF(layer): return LayerKeycode(RawKeycodes.KC_DF, layer, KC.NO) @staticmethod def KC_MO(layer): return LayerKeycode(RawKeycodes.KC_MO, layer, KC.NO) @staticmethod def KC_LM(layer, kc): return LayerKeycode(RawKeycodes.KC_LM, layer, kc) @staticmethod def KC_LT(layer, kc): return LayerKeycode(RawKeycodes.KC_LT, layer, kc) @staticmethod def KC_TG(layer): return LayerKeycode(RawKeycodes.KC_TG, layer, KC.NO) @staticmethod def KC_TO(layer): return LayerKeycode(RawKeycodes.KC_TO, layer, KC.NO) @staticmethod def KC_TT(layer): return LayerKeycode(RawKeycodes.KC_TT, layer, KC.NO) 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. ''' _groupings = [ 'Modifiers', 'Common', 'ShiftedKeycodes', 'FunctionKeys', 'NavAndLocks', 'Numpad', 'International', 'Misc', 'Media', 'KMK', 'Layers', ] Modifiers = Modifiers Common = Common ShiftedKeycodes = ShiftedKeycodes FunctionKeys = FunctionKeys NavAndLocks = NavAndLocks Numpad = Numpad International = International Misc = Misc Media = Media KMK = KMK Layers = Layers class LazyKC: def __init__(self): self.cache = {} def __getattr__(self, attr): if attr in self.cache: return self.cache[attr] for grouping in Keycodes._groupings: grouping_cls = getattr(Keycodes, grouping) try: found = getattr(grouping_cls, 'KC_{}'.format(attr)) self.cache[attr] = found return found except AttributeError: continue raise AttributeError(attr) KC = LazyKC() char_lookup = { "\n": Common.KC_ENTER, "\t": Common.KC_TAB, ' ': Common.KC_SPACE, '-': Common.KC_MINUS, '=': Common.KC_EQUAL, '[': Common.KC_LBRACKET, ']': Common.KC_RBRACKET, "\\": Common.KC_BACKSLASH, ';': Common.KC_SEMICOLON, "'": Common.KC_QUOTE, '`': Common.KC_GRAVE, ',': Common.KC_COMMA, '.': Common.KC_DOT, '~': ShiftedKeycodes.KC_TILDE, '!': ShiftedKeycodes.KC_EXCLAIM, '@': ShiftedKeycodes.KC_AT, '#': ShiftedKeycodes.KC_HASH, '$': ShiftedKeycodes.KC_DOLLAR, '%': ShiftedKeycodes.KC_PERCENT, '^': ShiftedKeycodes.KC_CIRCUMFLEX, '&': ShiftedKeycodes.KC_AMPERSAND, '*': ShiftedKeycodes.KC_ASTERISK, '(': ShiftedKeycodes.KC_LEFT_PAREN, ')': ShiftedKeycodes.KC_RIGHT_PAREN, '_': ShiftedKeycodes.KC_UNDERSCORE, '+': ShiftedKeycodes.KC_PLUS, '{': ShiftedKeycodes.KC_LEFT_CURLY_BRACE, '}': ShiftedKeycodes.KC_RIGHT_CURLY_BRACE, '|': ShiftedKeycodes.KC_PIPE, ':': ShiftedKeycodes.KC_COLON, '"': ShiftedKeycodes.KC_DOUBLE_QUOTE, '<': ShiftedKeycodes.KC_LEFT_ANGLE_BRACKET, '>': ShiftedKeycodes.KC_RIGHT_ANGLE_BRACKET, '?': ShiftedKeycodes.KC_QUESTION, }