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.') diff --git a/kmk/common/consts.py b/kmk/common/consts.py index 61d4d1c..ae681b5 100644 --- a/kmk/common/consts.py +++ b/kmk/common/consts.py @@ -113,3 +113,10 @@ class DiodeOrientation: COLUMNS = 0 ROWS = 1 + + +class UnicodeModes: + NOOP = 0 + LINUX = IBUS = 1 + MACOS = OSX = RALT = 2 + WINC = 3 diff --git a/kmk/common/event_defs.py b/kmk/common/event_defs.py index 69bfd8a..a8eb07f 100644 --- a/kmk/common/event_defs.py +++ b/kmk/common/event_defs.py @@ -9,17 +9,21 @@ 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__) -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, } @@ -39,6 +43,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 +78,12 @@ def hid_report_event(): } +def macro_complete_event(): + return { + 'type': MACRO_COMPLETE_EVENT, + } + + def matrix_changed(new_matrix): def _key_pressed(dispatch, get_state): state = get_state() @@ -94,4 +126,13 @@ def matrix_changed(new_matrix): except ImportError: logger.warning('Tried to reset to bootloader, but not supported on this chip?') + with get_state() as new_state: + if new_state.macro_pending: + macro = new_state.macro_pending + + for event in macro(new_state): + dispatch(event) + + dispatch(macro_complete_event()) + return _key_pressed 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/internal_state.py b/kmk/common/internal_state.py index ce5962b..b6bc9cb 100644 --- a/kmk/common/internal_state.py +++ b/kmk/common/internal_state.py @@ -1,12 +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, - 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.common.macros import KMKMacro class ReduxStore: @@ -52,6 +54,8 @@ class ReduxStore: class InternalState: modifiers_pressed = frozenset() keys_pressed = frozenset() + macro_pending = None + unicode_mode = UnicodeModes.NOOP keymap = [] row_pins = [] col_pins = [] @@ -74,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: @@ -133,6 +138,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 +163,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( + macro_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 +193,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( + macro_pending=changed_key.keydown, + ) + + return state + newstate = state.update( keys_pressed=( state.keys_pressed | {changed_key} @@ -183,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'] @@ -196,6 +232,9 @@ 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(macro_pending=None) + # On unhandled events, log and do not mutate state logger.warning('Unhandled event! Returning state unmodified.') return state diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index d0b0b36..0a83005 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -5,28 +5,98 @@ 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 + + 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'))): + @staticmethod + def from_mode_const(mode): + return UnicodeModeKeycode(RawKeycodes.KC_UC_MODE, mode) 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 + + 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 + + return new_keycode -class ConsumerKeycode: - def __init__(self, code): - self.code = code +class ConsumerKeycode(Keycode): + pass class KeycodeCategory(type): @@ -113,14 +183,314 @@ 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(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): + 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) + + @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, + ) + 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 + def KC_DF(layer): + return LayerKeycode(RawKeycodes.KC_DF, layer) + + @staticmethod + def KC_MO(layer): + return LayerKeycode(RawKeycodes.KC_MO, layer) + + @staticmethod + def KC_LM(layer): + return LayerKeycode(RawKeycodes.KC_LM, layer) + + @staticmethod + def KC_LT(layer): + return LayerKeycode(RawKeycodes.KC_LT, layer) + + @staticmethod + def KC_TG(layer): + return LayerKeycode(RawKeycodes.KC_TG, layer) + + @staticmethod + def KC_TO(layer): + return LayerKeycode(RawKeycodes.KC_TO, layer) + + @staticmethod + def KC_TT(layer): + return LayerKeycode(RawKeycodes.KC_TT, layer) class Keycodes(KeycodeCategory): @@ -130,293 +500,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({ @@ -425,11 +520,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/__init__.py b/kmk/common/macros/__init__.py new file mode 100644 index 0000000..a6cc1d9 --- /dev/null +++ b/kmk/common/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/common/macros/simple.py b/kmk/common/macros/simple.py new file mode 100644 index 0000000..128779b --- /dev/null +++ b/kmk/common/macros/simple.py @@ -0,0 +1,44 @@ +import string + +from kmk.common.event_defs import (hid_report_event, keycode_down_event, + keycode_up_event) +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() + + if not getattr(key, 'no_release', None): + yield keycode_up_event(key) + 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/common/macros/unicode.py b/kmk/common/macros/unicode.py new file mode 100644 index 0000000..ad2cd77 --- /dev/null +++ b/kmk/common/macros/unicode.py @@ -0,0 +1,70 @@ +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): + # 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): + 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) + elif state.unicode_mode == UnicodeModes.WINC: + yield from _winc_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) + + +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) 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/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/micropython/pyb_hid.py b/kmk/micropython/pyb_hid.py index a4a9526..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) @@ -73,7 +64,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,45 +89,16 @@ class HIDHelper: self.logger.debug('Sending HID report: {}'.format(self._evt)) self._hid.send(self._evt) - 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() - - # 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() + # 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 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 324b10c..cb48d77 100644 --- a/user_keymaps/klardotsh/threethree_matrix_pyboard.py +++ b/user_keymaps/klardotsh/threethree_matrix_pyboard.py @@ -1,7 +1,9 @@ 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 send_string, simple_key_sequence +from kmk.common.macros.unicode import unicode_sequence from kmk.entrypoints.handwire.pyboard import main p = machine.Pin.board @@ -9,21 +11,60 @@ cols = (p.X10, p.X11, p.X12) rows = (p.X1, p.X2, p.X3) diode_orientation = DiodeOrientation.COLUMNS +unicode_mode = UnicodeModes.LINUX + +MACRO_TEST_SIMPLE = simple_key_sequence([ + KC.LSHIFT(KC.H), + KC.E, + KC.L, + KC.L, + KC.O, + + KC.SPACE, + + KC.MACRO_SLEEP_MS(500), + + KC.LSHIFT(KC.K), + KC.LSHIFT(KC.M), + KC.LSHIFT(KC.K), + 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", + "ca0", + "75ca", + "ca0", + "29", + "30ce", + "5f61", + "253b", + "2501", + "253b", +]) 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, KC.Z], - [KC.TRNS, KC.PIPE, KC.MEDIA_PLAY_PAUSE], - [KC.VOLD, KC.P, KC.Q], + [KC.VOLU, KC.MUTE, ANGRY_TABLE_FLIP], + [KC.TRNS, KC.PIPE, MACRO_TEST_SIMPLE], + [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], ], ]