Merge pull request #31 from KMKfw/topic-macros-part-one

Macros: Support basic sequences of keys (either raw or specified by a string) and cross-platform Unicode sequences (ex. (ノಠ痊ಠ)ノ彡┻━┻)
This commit is contained in:
Josh Klar 2018-10-01 00:33:44 -07:00 committed by GitHub
commit a089675aa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 754 additions and 382 deletions

View File

@ -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.')

View File

@ -113,3 +113,10 @@ class DiodeOrientation:
COLUMNS = 0
ROWS = 1
class UnicodeModes:
NOOP = 0
LINUX = IBUS = 1
MACOS = OSX = RALT = 2
WINC = 3

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,
}

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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,
)

View File

@ -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):

View File

@ -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

View File

@ -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]

View File

@ -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],
],
]