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:
		| @@ -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.') | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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, | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								kmk/common/macros/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								kmk/common/macros/__init__.py
									
									
									
									
									
										Normal 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 | ||||||
							
								
								
									
										44
									
								
								kmk/common/macros/simple.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								kmk/common/macros/simple.py
									
									
									
									
									
										Normal 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) | ||||||
							
								
								
									
										70
									
								
								kmk/common/macros/unicode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								kmk/common/macros/unicode.py
									
									
									
									
									
										Normal 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) | ||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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, | ||||||
|         ) |         ) | ||||||
|   | |||||||
| @@ -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): | ||||||
|   | |||||||
| @@ -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): | ||||||
|   | |||||||
| @@ -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] | ||||||
|   | |||||||
| @@ -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], | ||||||
|     ], |     ], | ||||||
| ] | ] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user