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 import uos
from kmk.common.keycodes import Keycodes from kmk.common.keycodes import Keycodes, RawKeycodes
if len(sys.argv) < 2: if len(sys.argv) < 2:
print('Must provide a keymap to test as first argument', file=sys.stderr) 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 ridx, row in enumerate(layer):
for cidx, key in enumerate(row): 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, \ 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 ' ('The physical key used for MO layer switching must be KC_TRNS on the '
'target layer or you will get stuck on that layer.') 'target layer or you will get stuck on that layer.')

View File

@ -113,3 +113,10 @@ class DiodeOrientation:
COLUMNS = 0 COLUMNS = 0
ROWS = 1 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) INIT_FIRMWARE_EVENT = const(3)
NEW_MATRIX_EVENT = const(4) NEW_MATRIX_EVENT = const(4)
HID_REPORT_EVENT = const(5) HID_REPORT_EVENT = const(5)
KEYCODE_UP_EVENT = const(6)
KEYCODE_DOWN_EVENT = const(7)
MACRO_COMPLETE_EVENT = const(8)
logger = logging.getLogger(__name__) 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 { return {
'type': INIT_FIRMWARE_EVENT, 'type': INIT_FIRMWARE_EVENT,
'keymap': keymap, 'keymap': keymap,
'row_pins': row_pins, 'row_pins': row_pins,
'col_pins': col_pins, 'col_pins': col_pins,
'diode_orientation': diode_orientation, '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): def new_matrix_event(matrix):
return { return {
'type': NEW_MATRIX_EVENT, '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 matrix_changed(new_matrix):
def _key_pressed(dispatch, get_state): def _key_pressed(dispatch, get_state):
state = get_state() state = get_state()
@ -94,4 +126,13 @@ def matrix_changed(new_matrix):
except ImportError: except ImportError:
logger.warning('Tried to reset to bootloader, but not supported on this chip?') 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 return _key_pressed

View File

@ -1,28 +1,35 @@
import logging import logging
from kmk.common.event_defs import KEY_DOWN_EVENT, KEY_UP_EVENT 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): def process_internal_key_event(state, action, changed_key, logger=None):
if logger is None: if logger is None:
logger = logging.getLogger(__name__) 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) 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) 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) 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) return to(state, action, changed_key, logger=logger)
elif changed_key == Keycodes.KMK.KC_GESC: elif changed_key.code == Keycodes.KMK.KC_GESC.code:
return grave_escape(action, state, logger=logger) 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: else:
return state return state
def grave_escape(action, state, logger): def grave_escape(state, action, logger):
if action['type'] == KEY_DOWN_EVENT: if action['type'] == KEY_DOWN_EVENT:
for key in state.keys_pressed: for key in state.keys_pressed:
if key in {Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT}: 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): def tt(layer):
"""Momentarily activates layer if held, toggles it if tapped repeatedly""" """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 logging
import sys 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, from kmk.common.event_defs import (HID_REPORT_EVENT, INIT_FIRMWARE_EVENT,
KEY_DOWN_EVENT, KEY_UP_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.internal_keycodes import process_internal_key_event
from kmk.common.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes from kmk.common.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes
from kmk.common.macros import KMKMacro
class ReduxStore: class ReduxStore:
@ -52,6 +54,8 @@ class ReduxStore:
class InternalState: class InternalState:
modifiers_pressed = frozenset() modifiers_pressed = frozenset()
keys_pressed = frozenset() keys_pressed = frozenset()
macro_pending = None
unicode_mode = UnicodeModes.NOOP
keymap = [] keymap = []
row_pins = [] row_pins = []
col_pins = [] col_pins = []
@ -74,6 +78,7 @@ class InternalState:
'keys_pressed': self.keys_pressed, 'keys_pressed': self.keys_pressed,
'modifiers_pressed': self.modifiers_pressed, 'modifiers_pressed': self.modifiers_pressed,
'active_layers': self.active_layers, 'active_layers': self.active_layers,
'unicode_mode': self.unicode_mode,
} }
if verbose: if verbose:
@ -133,6 +138,20 @@ def kmk_reducer(state=None, action=None, logger=None):
matrix=action['matrix'], 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: if action['type'] == KEY_UP_EVENT:
row = action['row'] row = action['row']
col = action['col'] col = action['col']
@ -144,6 +163,14 @@ def kmk_reducer(state=None, action=None, logger=None):
if not changed_key: if not changed_key:
return state return state
if isinstance(changed_key, KMKMacro):
if changed_key.keyup:
return state.update(
macro_pending=changed_key.keyup,
)
return state
newstate = state.update( newstate = state.update(
keys_pressed=frozenset( keys_pressed=frozenset(
key for key in state.keys_pressed if key != changed_key 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: if not changed_key:
return state return state
if isinstance(changed_key, KMKMacro):
if changed_key.keydown:
return state.update(
macro_pending=changed_key.keydown,
)
return state
newstate = state.update( newstate = state.update(
keys_pressed=( keys_pressed=(
state.keys_pressed | {changed_key} state.keys_pressed | {changed_key}
@ -183,6 +218,7 @@ def kmk_reducer(state=None, action=None, logger=None):
row_pins=action['row_pins'], row_pins=action['row_pins'],
col_pins=action['col_pins'], col_pins=action['col_pins'],
diode_orientation=action['diode_orientation'], diode_orientation=action['diode_orientation'],
unicode_mode=action['unicode_mode'],
matrix=[ matrix=[
[False for c in action['col_pins']] [False for c in action['col_pins']]
for r in action['row_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: if action['type'] == HID_REPORT_EVENT:
return state return state
if action['type'] == MACRO_COMPLETE_EVENT:
return state.update(macro_pending=None)
# On unhandled events, log and do not mutate state # On unhandled events, log and do not mutate state
logger.warning('Unhandled event! Returning state unmodified.') logger.warning('Unhandled event! Returning state unmodified.')
return state return state

View File

@ -5,28 +5,98 @@ except ImportError:
# MicroPython, it doesn't exist # MicroPython, it doesn't exist
from ucollections import namedtuple from ucollections import namedtuple
from kmk.common.consts import UnicodeModes
from kmk.common.types import AttrDict from kmk.common.types import AttrDict
from kmk.common.util import flatten_dict from kmk.common.util import flatten_dict
FIRST_KMK_INTERNAL_KEYCODE = 1000 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')) 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: 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.code = code
self.has_modifiers = has_modifiers 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: class ModifierKeycode(Keycode):
def __init__(self, code): def __call__(self, modified_code=None, no_press=None, no_release=None):
self.code = code 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: class ConsumerKeycode(Keycode):
def __init__(self, code): pass
self.code = code
class KeycodeCategory(type): class KeycodeCategory(type):
@ -113,32 +183,16 @@ class KeycodeCategory(type):
return any(sc.contains(kc) for sc in subcategories) return any(sc.contains(kc) for sc in subcategories)
CODE_LCTRL = CODE_LCTL = 0x01
CODE_LSHIFT = CODE_LSFT = 0x02
CODE_LALT = 0x04
CODE_LGUI = CODE_LCMD = CODE_LWIN = 0x08
CODE_RCTRL = CODE_RCTL = 0x10
CODE_RSHIFT = CODE_RSFT = 0x20
CODE_RALT = 0x40
CODE_RGUI = CODE_RCMD = CODE_RWIN = 0x80
class Keycodes(KeycodeCategory):
'''
A massive grouping of keycodes
Some of these are from http://www.freebsddiary.org/APC/usb_hid_usages.php,
one of the most useful pages on the interwebs for HID stuff, apparently.
'''
class Modifiers(KeycodeCategory): class Modifiers(KeycodeCategory):
KC_LCTRL = KC_LCTL = ModifierKeycode(CODE_LCTRL) KC_LCTRL = KC_LCTL = ModifierKeycode(RawKeycodes.LCTRL)
KC_LSHIFT = KC_LSFT = ModifierKeycode(CODE_LSHIFT) KC_LSHIFT = KC_LSFT = ModifierKeycode(RawKeycodes.LSHIFT)
KC_LALT = ModifierKeycode(CODE_LALT) KC_LALT = ModifierKeycode(RawKeycodes.LALT)
KC_LGUI = KC_LCMD = KC_LWIN = ModifierKeycode(CODE_LGUI) KC_LGUI = KC_LCMD = KC_LWIN = ModifierKeycode(RawKeycodes.LGUI)
KC_RCTRL = KC_RCTL = ModifierKeycode(CODE_RCTRL) KC_RCTRL = KC_RCTL = ModifierKeycode(RawKeycodes.RCTRL)
KC_RSHIFT = KC_RSFT = ModifierKeycode(CODE_RSHIFT) KC_RSHIFT = KC_RSFT = ModifierKeycode(RawKeycodes.RSHIFT)
KC_RALT = ModifierKeycode(CODE_RALT) KC_RALT = ModifierKeycode(RawKeycodes.RALT)
KC_RGUI = KC_RCMD = KC_RWIN = ModifierKeycode(CODE_RGUI) KC_RGUI = KC_RCMD = KC_RWIN = ModifierKeycode(RawKeycodes.RGUI)
class Common(KeycodeCategory): class Common(KeycodeCategory):
KC_A = Keycode(4) KC_A = Keycode(4)
@ -200,6 +254,31 @@ class Keycodes(KeycodeCategory):
KC_DOT = Keycode(55) KC_DOT = Keycode(55)
KC_SLASH = KC_SLSH = Keycode(56) 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): class FunctionKeys(KeycodeCategory):
KC_F1 = Keycode(58) KC_F1 = Keycode(58)
KC_F2 = Keycode(59) KC_F2 = Keycode(59)
@ -226,6 +305,7 @@ class Keycodes(KeycodeCategory):
KC_F23 = Keycode(114) KC_F23 = Keycode(114)
KC_F24 = Keycode(115) KC_F24 = Keycode(115)
class NavAndLocks(KeycodeCategory): class NavAndLocks(KeycodeCategory):
KC_CAPS_LOCK = KC_CLCK = KC_CAPS = Keycode(57) KC_CAPS_LOCK = KC_CLCK = KC_CAPS = Keycode(57)
KC_LOCKING_CAPS = KC_LCAP = Keycode(130) KC_LOCKING_CAPS = KC_LCAP = Keycode(130)
@ -244,6 +324,7 @@ class Keycodes(KeycodeCategory):
KC_DOWN = Keycode(81) KC_DOWN = Keycode(81)
KC_UP = Keycode(82) KC_UP = Keycode(82)
class Numpad(KeycodeCategory): class Numpad(KeycodeCategory):
KC_NUMLOCK = KC_NLCK = Keycode(83) KC_NUMLOCK = KC_NLCK = Keycode(83)
KC_LOCKING_NUM = KC_LNUM = Keycode(131) KC_LOCKING_NUM = KC_LNUM = Keycode(131)
@ -267,6 +348,7 @@ class Keycodes(KeycodeCategory):
KC_KP_COMMA = KC_PCMM = Keycode(133) KC_KP_COMMA = KC_PCMM = Keycode(133)
KC_KP_EQUAL_AS400 = Keycode(134) KC_KP_EQUAL_AS400 = Keycode(134)
class International(KeycodeCategory): class International(KeycodeCategory):
KC_INT1 = KC_RO = Keycode(135) KC_INT1 = KC_RO = Keycode(135)
KC_INT2 = KC_KANA = Keycode(136) KC_INT2 = KC_KANA = Keycode(136)
@ -287,6 +369,7 @@ class Keycodes(KeycodeCategory):
KC_LANG8 = Keycode(151) KC_LANG8 = Keycode(151)
KC_LANG9 = Keycode(152) KC_LANG9 = Keycode(152)
class Misc(KeycodeCategory): class Misc(KeycodeCategory):
KC_APPLICATION = KC_APP = ConsumerKeycode(101) KC_APPLICATION = KC_APP = ConsumerKeycode(101)
KC_POWER = ConsumerKeycode(102) KC_POWER = ConsumerKeycode(102)
@ -327,6 +410,7 @@ class Keycodes(KeycodeCategory):
KC_WWW_REFRESH = KC_WREF = ConsumerKeycode(185) KC_WWW_REFRESH = KC_WREF = ConsumerKeycode(185)
KC_WWW_FAVORITES = KC_WFAV = ConsumerKeycode(186) KC_WWW_FAVORITES = KC_WFAV = ConsumerKeycode(186)
class Media(KeycodeCategory): class Media(KeycodeCategory):
# I believe QMK used these double-underscore codes for MacOS # I believe QMK used these double-underscore codes for MacOS
# support or something. I have no idea, but modern MacOS supports # support or something. I have no idea, but modern MacOS supports
@ -347,6 +431,7 @@ class Keycodes(KeycodeCategory):
KC_MEDIA_FAST_FORWARD = KC_MFFD = ConsumerKeycode(179) # 0xB3 KC_MEDIA_FAST_FORWARD = KC_MFFD = ConsumerKeycode(179) # 0xB3
KC_MEDIA_REWIND = KC_MRWD = ConsumerKeycode(180) # 0xB4 KC_MEDIA_REWIND = KC_MRWD = ConsumerKeycode(180) # 0xB4
class KMK(KeycodeCategory): class KMK(KeycodeCategory):
KC_RESET = Keycode(1000) KC_RESET = Keycode(1000)
KC_DEBUG = Keycode(1001) KC_DEBUG = Keycode(1001)
@ -358,65 +443,75 @@ class Keycodes(KeycodeCategory):
KC_NO = Keycode(1107) KC_NO = Keycode(1107)
KC_TRANSPARENT = KC_TRNS = Keycode(1108) KC_TRANSPARENT = KC_TRNS = Keycode(1108)
class Layers(KeycodeCategory): @staticmethod
_KC_DF = 1050 def KC_UC_MODE(mode):
_KC_MO = 1051 '''
_KC_LM = 1052 Set any Unicode Mode at runtime (allows the same keymap's unicode
_KC_LT = 1053 sequences to work across all supported platforms)
_KC_TG = 1054 '''
_KC_TO = 1055 return UnicodeModeKeycode.from_mode_const(mode)
_KC_TT = 1056
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 @staticmethod
def KC_DF(layer): def KC_DF(layer):
return LayerKeycode(Keycodes.Layers._KC_DF, layer) return LayerKeycode(RawKeycodes.KC_DF, layer)
@staticmethod @staticmethod
def KC_MO(layer): def KC_MO(layer):
return LayerKeycode(Keycodes.Layers._KC_MO, layer) return LayerKeycode(RawKeycodes.KC_MO, layer)
@staticmethod @staticmethod
def KC_LM(layer): def KC_LM(layer):
return LayerKeycode(Keycodes.Layers._KC_LM, layer) return LayerKeycode(RawKeycodes.KC_LM, layer)
@staticmethod @staticmethod
def KC_LT(layer): def KC_LT(layer):
return LayerKeycode(Keycodes.Layers._KC_LT, layer) return LayerKeycode(RawKeycodes.KC_LT, layer)
@staticmethod @staticmethod
def KC_TG(layer): def KC_TG(layer):
return LayerKeycode(Keycodes.Layers._KC_TG, layer) return LayerKeycode(RawKeycodes.KC_TG, layer)
@staticmethod @staticmethod
def KC_TO(layer): def KC_TO(layer):
return LayerKeycode(Keycodes.Layers._KC_TO, layer) return LayerKeycode(RawKeycodes.KC_TO, layer)
@staticmethod @staticmethod
def KC_TT(layer): def KC_TT(layer):
return LayerKeycode(Keycodes.Layers._KC_TT, layer) return LayerKeycode(RawKeycodes.KC_TT, layer)
class ShiftedKeycodes(KeycodeCategory):
KC_TILDE = KC_TILD = Keycode(53, (CODE_LSHIFT,)) class Keycodes(KeycodeCategory):
KC_EXCLAIM = KC_EXLM = Keycode(30, (CODE_LSHIFT,)) '''
KC_AT = Keycode(31, (CODE_LSHIFT,)) A massive grouping of keycodes
KC_HASH = Keycode(32, (CODE_LSHIFT,))
KC_DOLLAR = KC_DLR = Keycode(33, (CODE_LSHIFT,)) Some of these are from http://www.freebsddiary.org/APC/usb_hid_usages.php,
KC_PERCENT = KC_PERC = Keycode(34, (CODE_LSHIFT,)) one of the most useful pages on the interwebs for HID stuff, apparently.
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,)) Modifiers = Modifiers
KC_LEFT_PAREN = KC_LPRN = Keycode(38, (CODE_LSHIFT,)) Common = Common
KC_RIGHT_PAREN = KC_RPRN = Keycode(39, (CODE_LSHIFT,)) ShiftedKeycodes = ShiftedKeycodes
KC_UNDERSCORE = KC_UNDS = Keycode(45, (CODE_LSHIFT,)) FunctionKeys = FunctionKeys
KC_PLUS = Keycode(46, (CODE_LSHIFT,)) NavAndLocks = NavAndLocks
KC_LEFT_CURLY_BRACE = KC_LCBR = Keycode(47, (CODE_LSHIFT,)) Numpad = Numpad
KC_RIGHT_CURLY_BRACE = KC_RCBR = Keycode(48, (CODE_LSHIFT,)) International = International
KC_PIPE = Keycode(49, (CODE_LSHIFT,)) Misc = Misc
KC_COLON = KC_COLN = Keycode(51, (CODE_LSHIFT,)) Media = Media
KC_DOUBLE_QUOTE = KC_DQUO = KC_DQT = Keycode(52, (CODE_LSHIFT,)) KMK = KMK
KC_LEFT_ANGLE_BRACKET = KC_LABK = KC_LT = Keycode(54, (CODE_LSHIFT,)) Layers = Layers
KC_RIGHT_ANGLE_BRACKET = KC_RABK = KC_GT = Keycode(55, (CODE_LSHIFT,))
KC_QUESTION = KC_QUES = Keycode(56, (CODE_LSHIFT,))
ALL_KEYS = KC = AttrDict({ ALL_KEYS = KC = AttrDict({
@ -425,11 +520,38 @@ ALL_KEYS = KC = AttrDict({
}) })
char_lookup = { char_lookup = {
"\n": (Keycodes.Common.KC_ENTER,), "\n": Common.KC_ENTER,
"\t": (Keycodes.Common.KC_TAB,), "\t": Common.KC_TAB,
' ': (Keycodes.Common.KC_SPACE,), ' ': Common.KC_SPACE,
'-': (Keycodes.Common.KC_MINUS,), '-': Common.KC_MINUS,
'=': (Keycodes.Common.KC_EQUAL,), '=': Common.KC_EQUAL,
'+': (Keycodes.Common.KC_EQUAL, Keycodes.Modifiers.KC_LSHIFT), '[': Common.KC_LBRACKET,
'~': (Keycodes.Common.KC_GRAVE,), ']': 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 import microcontroller
microcontroller.on_next_reset(microcontroller.RunMode.BOOTLOADER) microcontroller.on_next_reset(microcontroller.RunMode.BOOTLOADER)
microcontroller.reset() 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 import sys
from logging import DEBUG from logging import DEBUG
from kmk.common.consts import UnicodeModes
from kmk.firmware import Firmware from kmk.firmware import Firmware
from kmk.micropython.pyb_hid import HIDHelper from kmk.micropython.pyb_hid import HIDHelper
@ -8,12 +9,18 @@ from kmk.micropython.pyb_hid import HIDHelper
def main(): def main():
from kmk_keyboard_user import cols, diode_orientation, keymap, rows 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: try:
firmware = Firmware( firmware = Firmware(
keymap=keymap, keymap=keymap,
row_pins=rows, row_pins=rows,
col_pins=cols, col_pins=cols,
diode_orientation=diode_orientation, diode_orientation=diode_orientation,
unicode_mode=unicode_mode,
hid=HIDHelper, hid=HIDHelper,
log_level=DEBUG, log_level=DEBUG,
) )

View File

@ -12,7 +12,8 @@ except ImportError:
class Firmware: class Firmware:
def __init__( def __init__(
self, keymap, row_pins, col_pins, 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 = logging.getLogger(__name__)
logger.setLevel(log_level) logger.setLevel(log_level)
@ -36,6 +37,7 @@ class Firmware:
row_pins=row_pins, row_pins=row_pins,
col_pins=col_pins, col_pins=col_pins,
diode_orientation=diode_orientation, diode_orientation=diode_orientation,
unicode_mode=unicode_mode,
)) ))
def _subscription(self, state, action): def _subscription(self, state, action):

View File

@ -1,12 +1,11 @@
import logging import logging
import string
from pyb import USB_HID, delay, hid_keyboard from pyb import USB_HID, delay, hid_keyboard
from kmk.common.consts import HID_REPORT_STRUCTURE, HIDReportTypes from kmk.common.consts import HID_REPORT_STRUCTURE, HIDReportTypes
from kmk.common.event_defs import HID_REPORT_EVENT from kmk.common.event_defs import HID_REPORT_EVENT
from kmk.common.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, ConsumerKeycode, from kmk.common.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, ConsumerKeycode,
Keycodes, ModifierKeycode, char_lookup) ModifierKeycode)
def generate_pyb_hid_descriptor(): def generate_pyb_hid_descriptor():
@ -16,14 +15,6 @@ def generate_pyb_hid_descriptor():
class HIDHelper: 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): def __init__(self, store, log_level=logging.NOTSET):
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.logger.setLevel(log_level) self.logger.setLevel(log_level)
@ -73,7 +64,6 @@ class HIDHelper:
# device, or keys will get stuck (mostly when releasing # device, or keys will get stuck (mostly when releasing
# media/consumer keys) # media/consumer keys)
self.send() self.send()
delay(10)
self.report_device[0] = needed_reporting_device self.report_device[0] = needed_reporting_device
@ -99,46 +89,17 @@ class HIDHelper:
self.logger.debug('Sending HID report: {}'.format(self._evt)) self.logger.debug('Sending HID report: {}'.format(self._evt))
self._hid.send(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 # Without this delay, events get clobbered and you'll likely end up with
# a string like `heloooooooooooooooo` rather than `hello`. This number # a string like `heloooooooooooooooo` rather than `hello`. This number
# may be able to be shrunken down. It may also make sense to use # 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) # time.sleep_us or time.sleep_ms or time.sleep (platform dependent)
# on non-Pyboards. # 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) delay(10)
# Release all keys or we'll forever hold whatever the last keyadd was
self.clear_all()
self.send()
return self return self
def clear_all(self): def clear_all(self):

View File

@ -3,7 +3,8 @@ exclude = .git,__pycache__,vendor,.venv
max_line_length = 99 max_line_length = 99
ignore = X100, E262 ignore = X100, E262
per-file-ignores = 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 tests/test_data/keymaps/**/*.py: F401,E501
[isort] [isort]

View File

@ -1,7 +1,9 @@
import machine import machine
from kmk.common.consts import DiodeOrientation from kmk.common.consts import DiodeOrientation, UnicodeModes
from kmk.common.keycodes import KC 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 from kmk.entrypoints.handwire.pyboard import main
p = machine.Pin.board p = machine.Pin.board
@ -9,12 +11,46 @@ cols = (p.X10, p.X11, p.X12)
rows = (p.X1, p.X2, p.X3) rows = (p.X1, p.X2, p.X3)
diode_orientation = DiodeOrientation.COLUMNS 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 = [ keymap = [
[ [
[KC.MO(1), KC.GESC, KC.RESET], [KC.MO(1), KC.GESC, KC.RESET],
[KC.MO(2), KC.HASH, KC.ENTER], [KC.MO(2), KC.HASH, KC.ENTER],
[KC.LCTRL, KC.SPACE, KC.LSHIFT], [KC.MO(3), KC.SPACE, KC.LSHIFT],
], ],
[ [
[KC.TRNS, KC.B, KC.C], [KC.TRNS, KC.B, KC.C],
@ -22,8 +58,13 @@ keymap = [
[KC.F, KC.G, KC.H], [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.TRNS, KC.PIPE, MACRO_TEST_SIMPLE],
[KC.VOLD, KC.P, KC.Q], [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],
], ],
] ]