From 99573de21725f4825e63dd7fbbe393bf33c7b304 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 30 Sep 2018 17:58:36 -0700 Subject: [PATCH 01/10] Epic keycode cleanup; make modifiers callable and chainable --- kmk/common/keycodes.py | 604 ++++++++++++++++++++++------------------- 1 file changed, 318 insertions(+), 286 deletions(-) diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index d0b0b36..1168dc9 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -23,6 +23,14 @@ class ModifierKeycode: def __init__(self, code): self.code = code + def __call__(self, modified_code): + new_keycode = Keycode(modified_code.code, {self.code}) + + if modified_code.has_modifiers: + new_keycode.has_modifiers |= modified_code.has_modifiers + + return new_keycode + class ConsumerKeycode: def __init__(self, code): @@ -123,6 +131,305 @@ CODE_RALT = 0x40 CODE_RGUI = CODE_RCMD = CODE_RWIN = 0x80 +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 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 = KC_LT = Modifiers.KC_LSHIFT(Common.KC_COMMA) + KC_RIGHT_ANGLE_BRACKET = KC_RABK = KC_GT = Modifiers.KC_LSHIFT(Common.KC_DOT) + KC_QUESTION = KC_QUES = Modifiers.KC_LSHIFT(Common.KC_DOT) + + +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(Layers._KC_DF, layer) + + @staticmethod + def KC_MO(layer): + return LayerKeycode(Layers._KC_MO, layer) + + @staticmethod + def KC_LM(layer): + return LayerKeycode(Layers._KC_LM, layer) + + @staticmethod + def KC_LT(layer): + return LayerKeycode(Layers._KC_LT, layer) + + @staticmethod + def KC_TG(layer): + return LayerKeycode(Layers._KC_TG, layer) + + @staticmethod + def KC_TO(layer): + return LayerKeycode(Layers._KC_TO, layer) + + @staticmethod + def KC_TT(layer): + return LayerKeycode(Layers._KC_TT, layer) + + class Keycodes(KeycodeCategory): ''' A massive grouping of keycodes @@ -130,293 +437,18 @@ class Keycodes(KeycodeCategory): 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,)) + Modifiers = Modifiers + Common = Common + ShiftedKeycodes = ShiftedKeycodes + FunctionKeys = FunctionKeys + NavAndLocks = NavAndLocks + Numpad = Numpad + International = International + Misc = Misc + Media = Media + KMK = KMK + Layers = Layers ALL_KEYS = KC = AttrDict({ From bdd4f86472f7c89ed12fbfe92cd5d31d9e5ef4fd Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 30 Sep 2018 18:03:43 -0700 Subject: [PATCH 02/10] Support a simple macro of a sequence of keycodes (basis for SEND_STRING) --- kmk/common/event_defs.py | 40 +++++++++++++++++ kmk/common/internal_state.py | 43 ++++++++++++++++++- kmk/macros/__init__.py | 10 +++++ kmk/macros/simple.py | 14 ++++++ kmk/micropython/pyb_hid.py | 19 ++++---- .../klardotsh/threethree_matrix_pyboard.py | 18 +++++++- 6 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 kmk/macros/__init__.py create mode 100644 kmk/macros/simple.py diff --git a/kmk/common/event_defs.py b/kmk/common/event_defs.py index 69bfd8a..e87a628 100644 --- a/kmk/common/event_defs.py +++ b/kmk/common/event_defs.py @@ -9,6 +9,9 @@ KEY_DOWN_EVENT = const(2) INIT_FIRMWARE_EVENT = const(3) NEW_MATRIX_EVENT = const(4) HID_REPORT_EVENT = const(5) +KEYCODE_UP_EVENT = const(6) +KEYCODE_DOWN_EVENT = const(7) +MACRO_COMPLETE_EVENT = const(8) logger = logging.getLogger(__name__) @@ -39,6 +42,28 @@ def key_down_event(row, col): } +def keycode_up_event(keycode): + ''' + Press a key by Keycode object, bypassing the keymap. Used mostly for + macros. + ''' + return { + 'type': KEYCODE_UP_EVENT, + 'keycode': keycode, + } + + +def keycode_down_event(keycode): + ''' + Release a key by Keycode object, bypassing the keymap. Used mostly for + macros. + ''' + return { + 'type': KEYCODE_DOWN_EVENT, + 'keycode': keycode, + } + + def new_matrix_event(matrix): return { 'type': NEW_MATRIX_EVENT, @@ -52,6 +77,13 @@ def hid_report_event(): } +def macro_complete_event(macro): + return { + 'type': MACRO_COMPLETE_EVENT, + 'macro': macro, + } + + def matrix_changed(new_matrix): def _key_pressed(dispatch, get_state): state = get_state() @@ -94,4 +126,12 @@ def matrix_changed(new_matrix): except ImportError: logger.warning('Tried to reset to bootloader, but not supported on this chip?') + while get_state().macros_pending: + macro = get_state().macros_pending[0] + + for event in macro(): + dispatch(event) + + dispatch(macro_complete_event(macro)) + return _key_pressed diff --git a/kmk/common/internal_state.py b/kmk/common/internal_state.py index ce5962b..2d47807 100644 --- a/kmk/common/internal_state.py +++ b/kmk/common/internal_state.py @@ -4,9 +4,11 @@ import sys from kmk.common.consts import DiodeOrientation from kmk.common.event_defs import (HID_REPORT_EVENT, INIT_FIRMWARE_EVENT, KEY_DOWN_EVENT, KEY_UP_EVENT, - NEW_MATRIX_EVENT) + KEYCODE_DOWN_EVENT, KEYCODE_UP_EVENT, + MACRO_COMPLETE_EVENT, NEW_MATRIX_EVENT) from kmk.common.internal_keycodes import process_internal_key_event from kmk.common.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes +from kmk.macros import KMKMacro class ReduxStore: @@ -52,6 +54,7 @@ class ReduxStore: class InternalState: modifiers_pressed = frozenset() keys_pressed = frozenset() + macros_pending = [] keymap = [] row_pins = [] col_pins = [] @@ -133,6 +136,20 @@ def kmk_reducer(state=None, action=None, logger=None): matrix=action['matrix'], ) + if action['type'] == KEYCODE_UP_EVENT: + return state.update( + keys_pressed=frozenset( + key for key in state.keys_pressed if key != action['keycode'] + ), + ) + + if action['type'] == KEYCODE_DOWN_EVENT: + return state.update( + keys_pressed=( + state.keys_pressed | {action['keycode']} + ), + ) + if action['type'] == KEY_UP_EVENT: row = action['row'] col = action['col'] @@ -144,6 +161,14 @@ def kmk_reducer(state=None, action=None, logger=None): if not changed_key: return state + if isinstance(changed_key, KMKMacro): + if changed_key.keyup: + return state.update( + macros_pending=state.macros_pending + [changed_key.keyup], + ) + + return state + newstate = state.update( keys_pressed=frozenset( key for key in state.keys_pressed if key != changed_key @@ -166,6 +191,14 @@ def kmk_reducer(state=None, action=None, logger=None): if not changed_key: return state + if isinstance(changed_key, KMKMacro): + if changed_key.keydown: + return state.update( + macros_pending=state.macros_pending + [changed_key.keydown], + ) + + return state + newstate = state.update( keys_pressed=( state.keys_pressed | {changed_key} @@ -196,6 +229,14 @@ def kmk_reducer(state=None, action=None, logger=None): if action['type'] == HID_REPORT_EVENT: return state + if action['type'] == MACRO_COMPLETE_EVENT: + return state.update( + macros_pending=[ + m for m in state.macros_pending + if m != action['macro'] + ], + ) + # On unhandled events, log and do not mutate state logger.warning('Unhandled event! Returning state unmodified.') return state diff --git a/kmk/macros/__init__.py b/kmk/macros/__init__.py new file mode 100644 index 0000000..a6cc1d9 --- /dev/null +++ b/kmk/macros/__init__.py @@ -0,0 +1,10 @@ +class KMKMacro: + 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 diff --git a/kmk/macros/simple.py b/kmk/macros/simple.py new file mode 100644 index 0000000..62c3cff --- /dev/null +++ b/kmk/macros/simple.py @@ -0,0 +1,14 @@ +from kmk.common.event_defs import (hid_report_event, keycode_down_event, + keycode_up_event) +from kmk.macros import KMKMacro + + +def simple_key_sequence(seq): + def _simple_key_sequence(): + for key in seq: + yield keycode_down_event(key) + yield hid_report_event() + yield keycode_up_event(key) + yield hid_report_event() + + return KMKMacro(keydown=_simple_key_sequence) diff --git a/kmk/micropython/pyb_hid.py b/kmk/micropython/pyb_hid.py index a4a9526..1d6eb72 100644 --- a/kmk/micropython/pyb_hid.py +++ b/kmk/micropython/pyb_hid.py @@ -73,7 +73,6 @@ class HIDHelper: # device, or keys will get stuck (mostly when releasing # media/consumer keys) self.send() - delay(10) self.report_device[0] = needed_reporting_device @@ -99,6 +98,17 @@ class HIDHelper: self.logger.debug('Sending HID report: {}'.format(self._evt)) self._hid.send(self._evt) + # Without this delay, events get clobbered and you'll likely end up with + # a string like `heloooooooooooooooo` rather than `hello`. This number + # may be able to be shrunken down. It may also make sense to use + # time.sleep_us or time.sleep_ms or time.sleep (platform dependent) + # on non-Pyboards. + # + # It'd be real awesome if pyb.USB_HID.send/recv would support + # uselect.poll or uselect.select to more safely determine when + # it is safe to write to the host again... + delay(10) + return self def send_string(self, message): @@ -128,13 +138,6 @@ class HIDHelper: self.add_key(kc) self.send() - # Without this delay, events get clobbered and you'll likely end up with - # a string like `heloooooooooooooooo` rather than `hello`. This number - # may be able to be shrunken down. It may also make sense to use - # time.sleep_us or time.sleep_ms or time.sleep (platform dependent) - # on non-Pyboards. - delay(10) - # Release all keys or we'll forever hold whatever the last keyadd was self.clear_all() self.send() diff --git a/user_keymaps/klardotsh/threethree_matrix_pyboard.py b/user_keymaps/klardotsh/threethree_matrix_pyboard.py index 324b10c..dd62898 100644 --- a/user_keymaps/klardotsh/threethree_matrix_pyboard.py +++ b/user_keymaps/klardotsh/threethree_matrix_pyboard.py @@ -3,6 +3,7 @@ import machine from kmk.common.consts import DiodeOrientation from kmk.common.keycodes import KC from kmk.entrypoints.handwire.pyboard import main +from kmk.macros.simple import simple_key_sequence p = machine.Pin.board cols = (p.X10, p.X11, p.X12) @@ -10,6 +11,21 @@ rows = (p.X1, p.X2, p.X3) diode_orientation = DiodeOrientation.COLUMNS +MACRO_TEST_STRING = simple_key_sequence([ + KC.LSHIFT(KC.H), + KC.E, + KC.L, + KC.L, + KC.O, + + KC.SPACE, + + KC.LSHIFT(KC.K), + KC.LSHIFT(KC.M), + KC.LSHIFT(KC.K), + KC.EXCLAIM, +]) + keymap = [ [ [KC.MO(1), KC.GESC, KC.RESET], @@ -24,6 +40,6 @@ keymap = [ [ [KC.VOLU, KC.MUTE, KC.Z], [KC.TRNS, KC.PIPE, KC.MEDIA_PLAY_PAUSE], - [KC.VOLD, KC.P, KC.Q], + [KC.VOLD, KC.P, MACRO_TEST_STRING], ], ] From 2024eb959f4a494f6de2144bf931355c6b979a0f Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 30 Sep 2018 18:20:18 -0700 Subject: [PATCH 03/10] Support the real reason we're all here: unicode key sequences (Linux only). Basically takes the same output style as klardotsh/qmk_emote_macro_generator outputs --- kmk/macros/simple.py | 15 +++++++++++++++ .../klardotsh/threethree_matrix_pyboard.py | 18 ++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/kmk/macros/simple.py b/kmk/macros/simple.py index 62c3cff..324b556 100644 --- a/kmk/macros/simple.py +++ b/kmk/macros/simple.py @@ -1,5 +1,6 @@ from kmk.common.event_defs import (hid_report_event, keycode_down_event, keycode_up_event) +from kmk.common.keycodes import Common, Modifiers from kmk.macros import KMKMacro @@ -12,3 +13,17 @@ def simple_key_sequence(seq): yield hid_report_event() return KMKMacro(keydown=_simple_key_sequence) + + +def ibus_unicode_sequence(codepoints): + seq = [] + + for codepoint in codepoints: + seq.append(Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U))) + + for codepoint_fragment in codepoint: + seq.append(getattr(Common, 'KC_{}'.format(codepoint_fragment.upper()))) + + seq.append(Common.KC_ENTER) + + return simple_key_sequence(seq) diff --git a/user_keymaps/klardotsh/threethree_matrix_pyboard.py b/user_keymaps/klardotsh/threethree_matrix_pyboard.py index dd62898..9498a82 100644 --- a/user_keymaps/klardotsh/threethree_matrix_pyboard.py +++ b/user_keymaps/klardotsh/threethree_matrix_pyboard.py @@ -3,7 +3,7 @@ import machine from kmk.common.consts import DiodeOrientation from kmk.common.keycodes import KC from kmk.entrypoints.handwire.pyboard import main -from kmk.macros.simple import simple_key_sequence +from kmk.macros.simple import ibus_unicode_sequence, simple_key_sequence p = machine.Pin.board cols = (p.X10, p.X11, p.X12) @@ -26,6 +26,20 @@ MACRO_TEST_STRING = simple_key_sequence([ KC.EXCLAIM, ]) +ANGRY_TABLE_FLIP = ibus_unicode_sequence([ + "28", + "30ce", + "ca0", + "75ca", + "ca0", + "29", + "30ce", + "5f61", + "253b", + "2501", + "253b", +]) + keymap = [ [ [KC.MO(1), KC.GESC, KC.RESET], @@ -38,7 +52,7 @@ keymap = [ [KC.F, KC.G, KC.H], ], [ - [KC.VOLU, KC.MUTE, KC.Z], + [KC.VOLU, KC.MUTE, ANGRY_TABLE_FLIP], [KC.TRNS, KC.PIPE, KC.MEDIA_PLAY_PAUSE], [KC.VOLD, KC.P, MACRO_TEST_STRING], ], From ffa81bcf433b5ee18b11f91cfeea7a9948d8c9c9 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 30 Sep 2018 19:33:23 -0700 Subject: [PATCH 04/10] 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. --- kmk/common/consts.py | 6 +++ kmk/common/event_defs.py | 17 +++---- kmk/common/internal_state.py | 20 ++++----- kmk/common/keycodes.py | 31 ++++++++++--- kmk/{ => common}/macros/__init__.py | 0 kmk/common/macros/simple.py | 17 +++++++ kmk/common/macros/unicode.py | 45 +++++++++++++++++++ kmk/entrypoints/handwire/pyboard.py | 7 +++ kmk/firmware.py | 4 +- kmk/macros/simple.py | 29 ------------ .../klardotsh/threethree_matrix_pyboard.py | 8 ++-- 11 files changed, 126 insertions(+), 58 deletions(-) rename kmk/{ => common}/macros/__init__.py (100%) create mode 100644 kmk/common/macros/simple.py create mode 100644 kmk/common/macros/unicode.py delete mode 100644 kmk/macros/simple.py diff --git a/kmk/common/consts.py b/kmk/common/consts.py index 61d4d1c..29fddd9 100644 --- a/kmk/common/consts.py +++ b/kmk/common/consts.py @@ -113,3 +113,9 @@ class DiodeOrientation: COLUMNS = 0 ROWS = 1 + + +class UnicodeModes: + NOOP = 0 + LINUX = IBUS = 1 + MACOS = OSX = RALT = 2 diff --git a/kmk/common/event_defs.py b/kmk/common/event_defs.py index e87a628..a8eb07f 100644 --- a/kmk/common/event_defs.py +++ b/kmk/common/event_defs.py @@ -16,13 +16,14 @@ MACRO_COMPLETE_EVENT = const(8) logger = logging.getLogger(__name__) -def init_firmware(keymap, row_pins, col_pins, diode_orientation): +def init_firmware(keymap, row_pins, col_pins, diode_orientation, unicode_mode): return { 'type': INIT_FIRMWARE_EVENT, 'keymap': keymap, 'row_pins': row_pins, 'col_pins': col_pins, 'diode_orientation': diode_orientation, + 'unicode_mode': unicode_mode, } @@ -77,10 +78,9 @@ def hid_report_event(): } -def macro_complete_event(macro): +def macro_complete_event(): return { 'type': MACRO_COMPLETE_EVENT, - 'macro': macro, } @@ -126,12 +126,13 @@ def matrix_changed(new_matrix): except ImportError: logger.warning('Tried to reset to bootloader, but not supported on this chip?') - while get_state().macros_pending: - macro = get_state().macros_pending[0] + with get_state() as new_state: + if new_state.macro_pending: + macro = new_state.macro_pending - for event in macro(): - dispatch(event) + for event in macro(new_state): + dispatch(event) - dispatch(macro_complete_event(macro)) + dispatch(macro_complete_event()) return _key_pressed diff --git a/kmk/common/internal_state.py b/kmk/common/internal_state.py index 2d47807..b6bc9cb 100644 --- a/kmk/common/internal_state.py +++ b/kmk/common/internal_state.py @@ -1,14 +1,14 @@ import logging import sys -from kmk.common.consts import DiodeOrientation +from kmk.common.consts import DiodeOrientation, UnicodeModes from kmk.common.event_defs import (HID_REPORT_EVENT, INIT_FIRMWARE_EVENT, KEY_DOWN_EVENT, KEY_UP_EVENT, KEYCODE_DOWN_EVENT, KEYCODE_UP_EVENT, MACRO_COMPLETE_EVENT, NEW_MATRIX_EVENT) from kmk.common.internal_keycodes import process_internal_key_event from kmk.common.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes -from kmk.macros import KMKMacro +from kmk.common.macros import KMKMacro class ReduxStore: @@ -54,7 +54,8 @@ class ReduxStore: class InternalState: modifiers_pressed = frozenset() keys_pressed = frozenset() - macros_pending = [] + macro_pending = None + unicode_mode = UnicodeModes.NOOP keymap = [] row_pins = [] col_pins = [] @@ -77,6 +78,7 @@ class InternalState: 'keys_pressed': self.keys_pressed, 'modifiers_pressed': self.modifiers_pressed, 'active_layers': self.active_layers, + 'unicode_mode': self.unicode_mode, } if verbose: @@ -164,7 +166,7 @@ def kmk_reducer(state=None, action=None, logger=None): if isinstance(changed_key, KMKMacro): if changed_key.keyup: return state.update( - macros_pending=state.macros_pending + [changed_key.keyup], + macro_pending=changed_key.keyup, ) return state @@ -194,7 +196,7 @@ def kmk_reducer(state=None, action=None, logger=None): if isinstance(changed_key, KMKMacro): if changed_key.keydown: return state.update( - macros_pending=state.macros_pending + [changed_key.keydown], + macro_pending=changed_key.keydown, ) return state @@ -216,6 +218,7 @@ def kmk_reducer(state=None, action=None, logger=None): row_pins=action['row_pins'], col_pins=action['col_pins'], diode_orientation=action['diode_orientation'], + unicode_mode=action['unicode_mode'], matrix=[ [False for c in action['col_pins']] for r in action['row_pins'] @@ -230,12 +233,7 @@ def kmk_reducer(state=None, action=None, logger=None): return state if action['type'] == MACRO_COMPLETE_EVENT: - return state.update( - macros_pending=[ - m for m in state.macros_pending - if m != action['macro'] - ], - ) + return state.update(macro_pending=None) # On unhandled events, log and do not mutate state logger.warning('Unhandled event! Returning state unmodified.') diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index 1168dc9..1dfc9b9 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -14,17 +14,36 @@ LayerKeycode = namedtuple('LayerKeycode', ('code', 'layer')) class Keycode: - def __init__(self, code, has_modifiers=None): + 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, + ) -class ModifierKeycode: - def __init__(self, code): - self.code = code +class ModifierKeycode(Keycode): + 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 - def __call__(self, modified_code): - new_keycode = Keycode(modified_code.code, {self.code}) + 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 diff --git a/kmk/macros/__init__.py b/kmk/common/macros/__init__.py similarity index 100% rename from kmk/macros/__init__.py rename to kmk/common/macros/__init__.py diff --git a/kmk/common/macros/simple.py b/kmk/common/macros/simple.py new file mode 100644 index 0000000..b901338 --- /dev/null +++ b/kmk/common/macros/simple.py @@ -0,0 +1,17 @@ +from kmk.common.event_defs import (hid_report_event, keycode_down_event, + keycode_up_event) +from kmk.common.macros import KMKMacro + + +def simple_key_sequence(seq): + def _simple_key_sequence(state): + for key in seq: + if not getattr(key, 'no_press', None): + yield keycode_down_event(key) + yield hid_report_event() + + if not getattr(key, 'no_release', None): + yield keycode_up_event(key) + yield hid_report_event() + + return KMKMacro(keydown=_simple_key_sequence) diff --git a/kmk/common/macros/unicode.py b/kmk/common/macros/unicode.py new file mode 100644 index 0000000..2a6f090 --- /dev/null +++ b/kmk/common/macros/unicode.py @@ -0,0 +1,45 @@ +from kmk.common.consts import UnicodeModes +from kmk.common.event_defs import (hid_report_event, keycode_down_event, + keycode_up_event) +from kmk.common.keycodes import Common, Modifiers +from kmk.common.macros import KMKMacro +from kmk.common.macros.simple import simple_key_sequence + +IBUS_KEY_COMBO = Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U)) + + +def generate_codepoint_keysym_seq(codepoint): + return [ + getattr(Common, 'KC_{}'.format(codepoint_fragment.upper())) + for codepoint_fragment in codepoint + ] + + +def unicode_sequence(codepoints): + def _unicode_sequence(state): + if state.unicode_mode == UnicodeModes.IBUS: + yield from _ibus_unicode_sequence(codepoints, state) + elif state.unicode_mode == UnicodeModes.RALT: + yield from _ralt_unicode_sequence(codepoints, state) + + return KMKMacro(keydown=_unicode_sequence) + + +def _ralt_unicode_sequence(codepoints, state): + for codepoint in codepoints: + yield keycode_down_event(Modifiers.RALT(no_release=True)) + yield from simple_key_sequence(generate_codepoint_keysym_seq(codepoint)).keydown(state) + yield keycode_up_event(Modifiers.RALT(no_press=True)) + + +def _ibus_unicode_sequence(codepoints, state): + for codepoint in codepoints: + yield keycode_down_event(IBUS_KEY_COMBO) + yield hid_report_event() + yield keycode_up_event(IBUS_KEY_COMBO) + yield hid_report_event() + + seq = generate_codepoint_keysym_seq(codepoint) + seq.append(Common.KC_ENTER) + + yield from simple_key_sequence(seq).keydown(state) diff --git a/kmk/entrypoints/handwire/pyboard.py b/kmk/entrypoints/handwire/pyboard.py index 0a0f582..fb11c53 100644 --- a/kmk/entrypoints/handwire/pyboard.py +++ b/kmk/entrypoints/handwire/pyboard.py @@ -1,6 +1,7 @@ import sys from logging import DEBUG +from kmk.common.consts import UnicodeModes from kmk.firmware import Firmware from kmk.micropython.pyb_hid import HIDHelper @@ -8,12 +9,18 @@ from kmk.micropython.pyb_hid import HIDHelper def main(): from kmk_keyboard_user import cols, diode_orientation, keymap, rows + try: + from kmk_keyboard_user import unicode_mode + except Exception: + unicode_mode = UnicodeModes.NOOP + try: firmware = Firmware( keymap=keymap, row_pins=rows, col_pins=cols, diode_orientation=diode_orientation, + unicode_mode=unicode_mode, hid=HIDHelper, log_level=DEBUG, ) diff --git a/kmk/firmware.py b/kmk/firmware.py index 2156beb..bff6c08 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -12,7 +12,8 @@ except ImportError: class Firmware: def __init__( self, keymap, row_pins, col_pins, - diode_orientation, hid=None, log_level=logging.NOTSET, + diode_orientation, unicode_mode=None, + hid=None, log_level=logging.NOTSET, ): logger = logging.getLogger(__name__) logger.setLevel(log_level) @@ -36,6 +37,7 @@ class Firmware: row_pins=row_pins, col_pins=col_pins, diode_orientation=diode_orientation, + unicode_mode=unicode_mode, )) def _subscription(self, state, action): diff --git a/kmk/macros/simple.py b/kmk/macros/simple.py deleted file mode 100644 index 324b556..0000000 --- a/kmk/macros/simple.py +++ /dev/null @@ -1,29 +0,0 @@ -from kmk.common.event_defs import (hid_report_event, keycode_down_event, - keycode_up_event) -from kmk.common.keycodes import Common, Modifiers -from kmk.macros import KMKMacro - - -def simple_key_sequence(seq): - def _simple_key_sequence(): - for key in seq: - yield keycode_down_event(key) - yield hid_report_event() - yield keycode_up_event(key) - yield hid_report_event() - - return KMKMacro(keydown=_simple_key_sequence) - - -def ibus_unicode_sequence(codepoints): - seq = [] - - for codepoint in codepoints: - seq.append(Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U))) - - for codepoint_fragment in codepoint: - seq.append(getattr(Common, 'KC_{}'.format(codepoint_fragment.upper()))) - - seq.append(Common.KC_ENTER) - - return simple_key_sequence(seq) diff --git a/user_keymaps/klardotsh/threethree_matrix_pyboard.py b/user_keymaps/klardotsh/threethree_matrix_pyboard.py index 9498a82..c334b96 100644 --- a/user_keymaps/klardotsh/threethree_matrix_pyboard.py +++ b/user_keymaps/klardotsh/threethree_matrix_pyboard.py @@ -1,15 +1,17 @@ import machine -from kmk.common.consts import DiodeOrientation +from kmk.common.consts import DiodeOrientation, UnicodeModes from kmk.common.keycodes import KC +from kmk.common.macros.simple import simple_key_sequence +from kmk.common.macros.unicode import unicode_sequence from kmk.entrypoints.handwire.pyboard import main -from kmk.macros.simple import ibus_unicode_sequence, simple_key_sequence p = machine.Pin.board cols = (p.X10, p.X11, p.X12) rows = (p.X1, p.X2, p.X3) diode_orientation = DiodeOrientation.COLUMNS +unicode_mode = UnicodeModes.LINUX MACRO_TEST_STRING = simple_key_sequence([ KC.LSHIFT(KC.H), @@ -26,7 +28,7 @@ MACRO_TEST_STRING = simple_key_sequence([ KC.EXCLAIM, ]) -ANGRY_TABLE_FLIP = ibus_unicode_sequence([ +ANGRY_TABLE_FLIP = unicode_sequence([ "28", "30ce", "ca0", From 692d95018fa44a136d9b42f2ca637b3c58efcc91 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 30 Sep 2018 20:21:42 -0700 Subject: [PATCH 05/10] Enable switching Unicode modes at runtime; lots of Keycode cleanup again --- kmk/common/internal_keycodes.py | 30 +++-- kmk/common/keycodes.py | 108 ++++++++++++------ setup.cfg | 3 +- .../klardotsh/threethree_matrix_pyboard.py | 17 ++- 4 files changed, 107 insertions(+), 51 deletions(-) diff --git a/kmk/common/internal_keycodes.py b/kmk/common/internal_keycodes.py index ca8e55c..05d42ad 100644 --- a/kmk/common/internal_keycodes.py +++ b/kmk/common/internal_keycodes.py @@ -1,28 +1,35 @@ import logging from kmk.common.event_defs import KEY_DOWN_EVENT, KEY_UP_EVENT -from kmk.common.keycodes import Keycodes +from kmk.common.keycodes import Keycodes, RawKeycodes def process_internal_key_event(state, action, changed_key, logger=None): if logger is None: logger = logging.getLogger(__name__) - if changed_key.code == Keycodes.Layers._KC_DF: + # Since the key objects can be chained into new objects + # with, for example, no_press set, always check against + # the underlying code rather than comparing Keycode + # objects + + if changed_key.code == RawKeycodes.KC_DF: return df(state, action, changed_key, logger=logger) - elif changed_key.code == Keycodes.Layers._KC_MO: + elif changed_key.code == RawKeycodes.KC_MO: return mo(state, action, changed_key, logger=logger) - elif changed_key.code == Keycodes.Layers._KC_TG: + elif changed_key.code == RawKeycodes.KC_TG: return tg(state, action, changed_key, logger=logger) - elif changed_key.code == Keycodes.Layers._KC_TO: + elif changed_key.code == RawKeycodes.KC_TO: return to(state, action, changed_key, logger=logger) - elif changed_key == Keycodes.KMK.KC_GESC: - return grave_escape(action, state, logger=logger) + elif changed_key.code == Keycodes.KMK.KC_GESC.code: + return grave_escape(state, action, logger=logger) + elif changed_key.code == RawKeycodes.KC_UC_MODE: + return unicode_mode(state, action, changed_key, logger=logger) else: return state -def grave_escape(action, state, logger): +def grave_escape(state, action, logger): if action['type'] == KEY_DOWN_EVENT: for key in state.keys_pressed: if key in {Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT}: @@ -109,3 +116,10 @@ def to(state, action, changed_key, logger): def tt(layer): """Momentarily activates layer if held, toggles it if tapped repeatedly""" + + +def unicode_mode(state, action, changed_key, logger): + if action['type'] == KEY_DOWN_EVENT: + state.unicode_mode = changed_key.mode + + return state diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index 1dfc9b9..3ba5e1a 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -5,14 +5,55 @@ except ImportError: # MicroPython, it doesn't exist from ucollections import namedtuple +from kmk.common.consts import UnicodeModes from kmk.common.types import AttrDict from kmk.common.util import flatten_dict FIRST_KMK_INTERNAL_KEYCODE = 1000 + +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 + + +# 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')) +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 @@ -51,9 +92,8 @@ class ModifierKeycode(Keycode): return new_keycode -class ConsumerKeycode: - def __init__(self, code): - self.code = code +class ConsumerKeycode(Keycode): + pass class KeycodeCategory(type): @@ -140,25 +180,15 @@ class KeycodeCategory(type): 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 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) + 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) class Common(KeycodeCategory): @@ -410,43 +440,49 @@ class KMK(KeycodeCategory): KC_NO = Keycode(1107) KC_TRANSPARENT = KC_TRNS = Keycode(1108) + @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(UnicodeModes.NOOP) + KC_UC_MODE_LINUX = KC_UC_MODE_IBUS = UnicodeModeKeycode.from_mode_const(UnicodeModes.IBUS) + KC_UC_MODE_MACOS = KC_UC_MODE_OSX = KC_UC_MODE_RALT = UnicodeModeKeycode.from_mode_const( + UnicodeModes.RALT, + ) + 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(Layers._KC_DF, layer) + return LayerKeycode(RawKeycodes.KC_DF, layer) @staticmethod def KC_MO(layer): - return LayerKeycode(Layers._KC_MO, layer) + return LayerKeycode(RawKeycodes.KC_MO, layer) @staticmethod def KC_LM(layer): - return LayerKeycode(Layers._KC_LM, layer) + return LayerKeycode(RawKeycodes.KC_LM, layer) @staticmethod def KC_LT(layer): - return LayerKeycode(Layers._KC_LT, layer) + return LayerKeycode(RawKeycodes.KC_LT, layer) @staticmethod def KC_TG(layer): - return LayerKeycode(Layers._KC_TG, layer) + return LayerKeycode(RawKeycodes.KC_TG, layer) @staticmethod def KC_TO(layer): - return LayerKeycode(Layers._KC_TO, layer) + return LayerKeycode(RawKeycodes.KC_TO, layer) @staticmethod def KC_TT(layer): - return LayerKeycode(Layers._KC_TT, layer) + return LayerKeycode(RawKeycodes.KC_TT, layer) class Keycodes(KeycodeCategory): diff --git a/setup.cfg b/setup.cfg index d518cba..b262f02 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,8 @@ exclude = .git,__pycache__,vendor,.venv max_line_length = 99 ignore = X100, E262 per-file-ignores = - user_keymaps/**/*.py: F401,E501 +# Allow crazy line lengths, unused variables, and multiple spaces after commas in lists (for grid alignment) + user_keymaps/**/*.py: F401,E501,E241 tests/test_data/keymaps/**/*.py: F401,E501 [isort] diff --git a/user_keymaps/klardotsh/threethree_matrix_pyboard.py b/user_keymaps/klardotsh/threethree_matrix_pyboard.py index c334b96..5c27bdb 100644 --- a/user_keymaps/klardotsh/threethree_matrix_pyboard.py +++ b/user_keymaps/klardotsh/threethree_matrix_pyboard.py @@ -44,18 +44,23 @@ ANGRY_TABLE_FLIP = unicode_sequence([ keymap = [ [ - [KC.MO(1), KC.GESC, KC.RESET], - [KC.MO(2), KC.HASH, KC.ENTER], - [KC.LCTRL, KC.SPACE, KC.LSHIFT], + [KC.MO(1), KC.GESC, KC.RESET], + [KC.MO(2), KC.HASH, KC.ENTER], + [KC.MO(3), KC.SPACE, KC.LSHIFT], ], [ [KC.TRNS, KC.B, KC.C], - [KC.NO, KC.D, KC.E], - [KC.F, KC.G, KC.H], + [KC.NO, KC.D, KC.E], + [KC.F, KC.G, KC.H], ], [ [KC.VOLU, KC.MUTE, ANGRY_TABLE_FLIP], [KC.TRNS, KC.PIPE, KC.MEDIA_PLAY_PAUSE], - [KC.VOLD, KC.P, MACRO_TEST_STRING], + [KC.VOLD, KC.P, MACRO_TEST_STRING], + ], + [ + [KC.NO, KC.UC_MODE_NOOP, KC.C], + [KC.NO, KC.UC_MODE_LINUX, KC.E], + [KC.TRNS, KC.UC_MODE_MACOS, KC.H], ], ] From 137de4174379e65a0608af569daed9467580ae91 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 30 Sep 2018 20:24:32 -0700 Subject: [PATCH 06/10] Fix the sanity checker to use RawKeycodes where needed --- bin/keymap_sanity_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/keymap_sanity_check.py b/bin/keymap_sanity_check.py index 4ec5139..677ee03 100755 --- a/bin/keymap_sanity_check.py +++ b/bin/keymap_sanity_check.py @@ -4,7 +4,7 @@ import sys import uos -from kmk.common.keycodes import Keycodes +from kmk.common.keycodes import Keycodes, RawKeycodes if len(sys.argv) < 2: print('Must provide a keymap to test as first argument', file=sys.stderr) @@ -64,7 +64,7 @@ for lidx, layer in enumerate(user_keymap.keymap): for ridx, row in enumerate(layer): for cidx, key in enumerate(row): - if key.code == Keycodes.Layers._KC_MO: + if key.code == RawKeycodes.KC_MO: assert user_keymap.keymap[key.layer][ridx][cidx] == Keycodes.KMK.KC_TRNS, \ ('The physical key used for MO layer switching must be KC_TRNS on the ' 'target layer or you will get stuck on that layer.') From 0ccd27703da510ba61b810e5e4f59c1b93e0642d Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 30 Sep 2018 20:35:41 -0700 Subject: [PATCH 07/10] Support Unicode sequences on Windows through WinCompose ONLY --- kmk/common/consts.py | 1 + kmk/common/keycodes.py | 1 + kmk/common/macros/unicode.py | 15 +++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/kmk/common/consts.py b/kmk/common/consts.py index 29fddd9..ae681b5 100644 --- a/kmk/common/consts.py +++ b/kmk/common/consts.py @@ -119,3 +119,4 @@ class UnicodeModes: NOOP = 0 LINUX = IBUS = 1 MACOS = OSX = RALT = 2 + WINC = 3 diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index 3ba5e1a..498801b 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -453,6 +453,7 @@ class KMK(KeycodeCategory): KC_UC_MODE_MACOS = KC_UC_MODE_OSX = KC_UC_MODE_RALT = UnicodeModeKeycode.from_mode_const( UnicodeModes.RALT, ) + KC_UC_MODE_WINC = UnicodeModeKeycode.from_mode_const(UnicodeModes.WINC) class Layers(KeycodeCategory): diff --git a/kmk/common/macros/unicode.py b/kmk/common/macros/unicode.py index 2a6f090..11c465f 100644 --- a/kmk/common/macros/unicode.py +++ b/kmk/common/macros/unicode.py @@ -21,6 +21,8 @@ def unicode_sequence(codepoints): yield from _ibus_unicode_sequence(codepoints, state) elif state.unicode_mode == UnicodeModes.RALT: yield from _ralt_unicode_sequence(codepoints, state) + elif state.unicode_mode == UnicodeModes.WINC: + yield from _winc_unicode_sequence(codepoints, state) return KMKMacro(keydown=_unicode_sequence) @@ -43,3 +45,16 @@ def _ibus_unicode_sequence(codepoints, state): seq.append(Common.KC_ENTER) yield from simple_key_sequence(seq).keydown(state) + + +def _winc_unicode_sequence(codepoints, state): + ''' + Send unicode sequence using WinCompose: + + http://wincompose.info/ + https://github.com/SamHocevar/wincompose + ''' + for codepoint in codepoints: + yield keycode_down_event(Modifiers.RALT()) + yield keycode_down_event(Common.KC_U()) + yield from simple_key_sequence(generate_codepoint_keysym_seq(codepoint)).keydown(state) From 96b5c4ee0353effa7947dec1dfc2f1bf36940226 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 30 Sep 2018 20:49:40 -0700 Subject: [PATCH 08/10] Ensure we always send at least four characters when sending Unicode sequences to better support MacOS/Windows --- kmk/common/macros/unicode.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/kmk/common/macros/unicode.py b/kmk/common/macros/unicode.py index 11c465f..ad2cd77 100644 --- a/kmk/common/macros/unicode.py +++ b/kmk/common/macros/unicode.py @@ -9,10 +9,20 @@ IBUS_KEY_COMBO = Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U)) def generate_codepoint_keysym_seq(codepoint): - return [ - getattr(Common, 'KC_{}'.format(codepoint_fragment.upper())) - for codepoint_fragment in codepoint - ] + # 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 + seq = [Common.KC_0 for _ in range(max(len(codepoint), 4))] + + for idx, codepoint_fragment in enumerate(reversed(codepoint)): + seq[-(idx + 1)] = getattr(Common, 'KC_{}'.format(codepoint_fragment.upper())) + + return seq def unicode_sequence(codepoints): From 2c05efa805fc825d3d61d7b8b76d859cd74d382b Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 30 Sep 2018 21:14:30 -0700 Subject: [PATCH 09/10] Support a massively-enhanced SEND_STRING equivalent --- kmk/common/keycodes.py | 41 ++++++++++++++--- kmk/common/macros/simple.py | 22 ++++++++++ kmk/micropython/pyb_hid.py | 44 +------------------ .../klardotsh/threethree_matrix_pyboard.py | 8 ++-- 4 files changed, 62 insertions(+), 53 deletions(-) diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index 498801b..a1ecbc9 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -513,11 +513,38 @@ ALL_KEYS = KC = AttrDict({ }) 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,), + "\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, } diff --git a/kmk/common/macros/simple.py b/kmk/common/macros/simple.py index b901338..91e6680 100644 --- a/kmk/common/macros/simple.py +++ b/kmk/common/macros/simple.py @@ -1,5 +1,8 @@ +import string + from kmk.common.event_defs import (hid_report_event, keycode_down_event, keycode_up_event) +from kmk.common.keycodes import Keycodes, char_lookup from kmk.common.macros import KMKMacro @@ -15,3 +18,22 @@ def simple_key_sequence(seq): yield hid_report_event() return KMKMacro(keydown=_simple_key_sequence) + + +def send_string(message): + seq = [] + + for char in message: + kc = None + + if char in char_lookup: + kc = char_lookup[char] + elif char in string.ascii_letters + string.digits: + kc = getattr(Keycodes.Common, 'KC_{}'.format(char.upper())) + + if char.isupper(): + kc = Keycodes.Modifiers.KC_LSHIFT(kc) + + seq.append(kc) + + return simple_key_sequence(seq) diff --git a/kmk/micropython/pyb_hid.py b/kmk/micropython/pyb_hid.py index 1d6eb72..49e2977 100644 --- a/kmk/micropython/pyb_hid.py +++ b/kmk/micropython/pyb_hid.py @@ -1,12 +1,11 @@ import logging -import string from pyb import USB_HID, delay, hid_keyboard from kmk.common.consts import HID_REPORT_STRUCTURE, HIDReportTypes from kmk.common.event_defs import HID_REPORT_EVENT from kmk.common.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, ConsumerKeycode, - Keycodes, ModifierKeycode, char_lookup) + ModifierKeycode) def generate_pyb_hid_descriptor(): @@ -16,14 +15,6 @@ def generate_pyb_hid_descriptor(): class HIDHelper: - ''' - Most methods here return `self` upon completion, allowing chaining: - - ```python - myhid = HIDHelper() - myhid.send_string('testing').send_string(' ... and testing again') - ``` - ''' def __init__(self, store, log_level=logging.NOTSET): self.logger = logging.getLogger(__name__) self.logger.setLevel(log_level) @@ -111,39 +102,6 @@ class HIDHelper: return self - def send_string(self, message): - ''' - Clears the HID report, and sends along a string of arbitrary length. - All keys will be removed at the completion of the string. Modifiers - are not really supported here, though Shift will be added if - necessary to output the key. - ''' - - self.clear_all() - self.send() - - for char in message: - kc = None - modifier = None - - if char in char_lookup: - kc, modifier = char_lookup[char] - elif char in string.ascii_letters + string.digits: - kc = getattr(Keycodes.Common, 'KC_{}'.format(char.upper())) - modifier = Keycodes.Modifiers.KC_SHIFT if char.isupper() else None - - if modifier: - self.add_modifier(modifier) - - self.add_key(kc) - self.send() - - # Release all keys or we'll forever hold whatever the last keyadd was - self.clear_all() - self.send() - - return self - def clear_all(self): for idx, _ in enumerate(self.report_keys): self.report_keys[idx] = 0x00 diff --git a/user_keymaps/klardotsh/threethree_matrix_pyboard.py b/user_keymaps/klardotsh/threethree_matrix_pyboard.py index 5c27bdb..772a5c8 100644 --- a/user_keymaps/klardotsh/threethree_matrix_pyboard.py +++ b/user_keymaps/klardotsh/threethree_matrix_pyboard.py @@ -2,7 +2,7 @@ import machine from kmk.common.consts import DiodeOrientation, UnicodeModes from kmk.common.keycodes import KC -from kmk.common.macros.simple import simple_key_sequence +from kmk.common.macros.simple import send_string, simple_key_sequence from kmk.common.macros.unicode import unicode_sequence from kmk.entrypoints.handwire.pyboard import main @@ -13,7 +13,7 @@ rows = (p.X1, p.X2, p.X3) diode_orientation = DiodeOrientation.COLUMNS unicode_mode = UnicodeModes.LINUX -MACRO_TEST_STRING = simple_key_sequence([ +MACRO_TEST_SIMPLE = simple_key_sequence([ KC.LSHIFT(KC.H), KC.E, KC.L, @@ -28,6 +28,8 @@ MACRO_TEST_STRING = simple_key_sequence([ KC.EXCLAIM, ]) +MACRO_TEST_STRING = send_string("Hello! from, uhhhh, send_string | and some other WEIRD STUFF` \\ like this' \"\t[]") + ANGRY_TABLE_FLIP = unicode_sequence([ "28", "30ce", @@ -55,7 +57,7 @@ keymap = [ ], [ [KC.VOLU, KC.MUTE, ANGRY_TABLE_FLIP], - [KC.TRNS, KC.PIPE, KC.MEDIA_PLAY_PAUSE], + [KC.TRNS, KC.PIPE, MACRO_TEST_SIMPLE], [KC.VOLD, KC.P, MACRO_TEST_STRING], ], [ From fde9b7e274b93377b1d9721e1a5e797cf58f183f Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 30 Sep 2018 21:34:16 -0700 Subject: [PATCH 10/10] Add support for millisecond delays within a macro sequence --- kmk/common/keycodes.py | 7 +++++++ kmk/common/macros/simple.py | 7 ++++++- kmk/common/util.py | 13 +++++++++++++ user_keymaps/klardotsh/threethree_matrix_pyboard.py | 2 ++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index a1ecbc9..0a83005 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -40,12 +40,15 @@ class RawKeycodes: KC_UC_MODE = 1109 + KC_MACRO_SLEEP_MS = 1110 + # 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')) +MacroSleepKeycode = namedtuple('MacroSleepKeycode', ('code', 'ms')) class UnicodeModeKeycode(namedtuple('UnicodeModeKeycode', ('code', 'mode'))): @@ -455,6 +458,10 @@ class KMK(KeycodeCategory): ) KC_UC_MODE_WINC = UnicodeModeKeycode.from_mode_const(UnicodeModes.WINC) + @staticmethod + def KC_MACRO_SLEEP_MS(ms): + return MacroSleepKeycode(RawKeycodes.KC_MACRO_SLEEP_MS, ms) + class Layers(KeycodeCategory): @staticmethod diff --git a/kmk/common/macros/simple.py b/kmk/common/macros/simple.py index 91e6680..128779b 100644 --- a/kmk/common/macros/simple.py +++ b/kmk/common/macros/simple.py @@ -2,13 +2,18 @@ import string from kmk.common.event_defs import (hid_report_event, keycode_down_event, keycode_up_event) -from kmk.common.keycodes import Keycodes, char_lookup +from kmk.common.keycodes import Keycodes, RawKeycodes, char_lookup from kmk.common.macros import KMKMacro +from kmk.common.util import sleep_ms def simple_key_sequence(seq): def _simple_key_sequence(state): for key in seq: + if key.code == RawKeycodes.KC_MACRO_SLEEP_MS: + sleep_ms(key.ms) + continue + if not getattr(key, 'no_press', None): yield keycode_down_event(key) yield hid_report_event() diff --git a/kmk/common/util.py b/kmk/common/util.py index 2ff06d1..e90d85b 100644 --- a/kmk/common/util.py +++ b/kmk/common/util.py @@ -29,3 +29,16 @@ def reset_bootloader(): import microcontroller microcontroller.on_next_reset(microcontroller.RunMode.BOOTLOADER) microcontroller.reset() + + +def sleep_ms(ms): + ''' + Tries to sleep for a number of milliseconds in a cross-implementation + way. Will raise an ImportError if time is not available on the platform. + ''' + try: + import time + time.sleep_ms(ms) + except AttributeError: + import time + time.sleep(ms / 1000) diff --git a/user_keymaps/klardotsh/threethree_matrix_pyboard.py b/user_keymaps/klardotsh/threethree_matrix_pyboard.py index 772a5c8..cb48d77 100644 --- a/user_keymaps/klardotsh/threethree_matrix_pyboard.py +++ b/user_keymaps/klardotsh/threethree_matrix_pyboard.py @@ -22,6 +22,8 @@ MACRO_TEST_SIMPLE = simple_key_sequence([ KC.SPACE, + KC.MACRO_SLEEP_MS(500), + KC.LSHIFT(KC.K), KC.LSHIFT(KC.M), KC.LSHIFT(KC.K),