From ffa81bcf433b5ee18b11f91cfeea7a9948d8c9c9 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 30 Sep 2018 19:33:23 -0700 Subject: [PATCH] Massive refactor largely to support Unicode on Mac This does a bunch of crazy stuff: - The ability to set a unicode mode (right now only Linux+ibus or MacOS-RALT) in the keymap. This will be changeable at runtime soon, to allow a single keyboard to be able to send table flips and whatever other crazy stuff on any OS the board is plugged into (something that's not currently doable on QMK, so yay us?) - As part of the above, there is now just one user-facing macro for unicode codepoint submission, `kmk.common.macros.unicode.unicode_sequence`. Users should never use the platform-specific macros, partly because they just outright won't work. There's all sorts of fun stuff in these methods now, thank goodness MicroPython supports the `yield from` construct. - Keycode (these should really be renamed Keysym or something) objects that are intended to not be pressed, or not be released. Right now these properties are completely ignored if not part of a macro, and it's probably sane to keep it that way. This was necessary to support MacOS's "hold RALT while typing the codepoint characters" flow. - Other refactor-y bits, like moving macro support to `kmk/common` rather than sitting at the top level of the tree. One day `kmk/common` may make sense to surface at top level `kmk/`, but that's a discussion for another day. --- kmk/common/consts.py | 6 +++ kmk/common/event_defs.py | 17 +++---- kmk/common/internal_state.py | 20 ++++----- kmk/common/keycodes.py | 31 ++++++++++--- kmk/{ => common}/macros/__init__.py | 0 kmk/common/macros/simple.py | 17 +++++++ kmk/common/macros/unicode.py | 45 +++++++++++++++++++ kmk/entrypoints/handwire/pyboard.py | 7 +++ kmk/firmware.py | 4 +- kmk/macros/simple.py | 29 ------------ .../klardotsh/threethree_matrix_pyboard.py | 8 ++-- 11 files changed, 126 insertions(+), 58 deletions(-) rename kmk/{ => common}/macros/__init__.py (100%) create mode 100644 kmk/common/macros/simple.py create mode 100644 kmk/common/macros/unicode.py delete mode 100644 kmk/macros/simple.py diff --git a/kmk/common/consts.py b/kmk/common/consts.py index 61d4d1c..29fddd9 100644 --- a/kmk/common/consts.py +++ b/kmk/common/consts.py @@ -113,3 +113,9 @@ class DiodeOrientation: COLUMNS = 0 ROWS = 1 + + +class UnicodeModes: + NOOP = 0 + LINUX = IBUS = 1 + MACOS = OSX = RALT = 2 diff --git a/kmk/common/event_defs.py b/kmk/common/event_defs.py index e87a628..a8eb07f 100644 --- a/kmk/common/event_defs.py +++ b/kmk/common/event_defs.py @@ -16,13 +16,14 @@ MACRO_COMPLETE_EVENT = const(8) logger = logging.getLogger(__name__) -def init_firmware(keymap, row_pins, col_pins, diode_orientation): +def init_firmware(keymap, row_pins, col_pins, diode_orientation, unicode_mode): return { 'type': INIT_FIRMWARE_EVENT, 'keymap': keymap, 'row_pins': row_pins, 'col_pins': col_pins, 'diode_orientation': diode_orientation, + 'unicode_mode': unicode_mode, } @@ -77,10 +78,9 @@ def hid_report_event(): } -def macro_complete_event(macro): +def macro_complete_event(): return { 'type': MACRO_COMPLETE_EVENT, - 'macro': macro, } @@ -126,12 +126,13 @@ def matrix_changed(new_matrix): except ImportError: logger.warning('Tried to reset to bootloader, but not supported on this chip?') - while get_state().macros_pending: - macro = get_state().macros_pending[0] + with get_state() as new_state: + if new_state.macro_pending: + macro = new_state.macro_pending - for event in macro(): - dispatch(event) + for event in macro(new_state): + dispatch(event) - dispatch(macro_complete_event(macro)) + dispatch(macro_complete_event()) return _key_pressed diff --git a/kmk/common/internal_state.py b/kmk/common/internal_state.py index 2d47807..b6bc9cb 100644 --- a/kmk/common/internal_state.py +++ b/kmk/common/internal_state.py @@ -1,14 +1,14 @@ import logging import sys -from kmk.common.consts import DiodeOrientation +from kmk.common.consts import DiodeOrientation, UnicodeModes from kmk.common.event_defs import (HID_REPORT_EVENT, INIT_FIRMWARE_EVENT, KEY_DOWN_EVENT, KEY_UP_EVENT, KEYCODE_DOWN_EVENT, KEYCODE_UP_EVENT, MACRO_COMPLETE_EVENT, NEW_MATRIX_EVENT) from kmk.common.internal_keycodes import process_internal_key_event from kmk.common.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes -from kmk.macros import KMKMacro +from kmk.common.macros import KMKMacro class ReduxStore: @@ -54,7 +54,8 @@ class ReduxStore: class InternalState: modifiers_pressed = frozenset() keys_pressed = frozenset() - macros_pending = [] + macro_pending = None + unicode_mode = UnicodeModes.NOOP keymap = [] row_pins = [] col_pins = [] @@ -77,6 +78,7 @@ class InternalState: 'keys_pressed': self.keys_pressed, 'modifiers_pressed': self.modifiers_pressed, 'active_layers': self.active_layers, + 'unicode_mode': self.unicode_mode, } if verbose: @@ -164,7 +166,7 @@ def kmk_reducer(state=None, action=None, logger=None): if isinstance(changed_key, KMKMacro): if changed_key.keyup: return state.update( - macros_pending=state.macros_pending + [changed_key.keyup], + macro_pending=changed_key.keyup, ) return state @@ -194,7 +196,7 @@ def kmk_reducer(state=None, action=None, logger=None): if isinstance(changed_key, KMKMacro): if changed_key.keydown: return state.update( - macros_pending=state.macros_pending + [changed_key.keydown], + macro_pending=changed_key.keydown, ) return state @@ -216,6 +218,7 @@ def kmk_reducer(state=None, action=None, logger=None): row_pins=action['row_pins'], col_pins=action['col_pins'], diode_orientation=action['diode_orientation'], + unicode_mode=action['unicode_mode'], matrix=[ [False for c in action['col_pins']] for r in action['row_pins'] @@ -230,12 +233,7 @@ def kmk_reducer(state=None, action=None, logger=None): return state if action['type'] == MACRO_COMPLETE_EVENT: - return state.update( - macros_pending=[ - m for m in state.macros_pending - if m != action['macro'] - ], - ) + return state.update(macro_pending=None) # On unhandled events, log and do not mutate state logger.warning('Unhandled event! Returning state unmodified.') diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index 1168dc9..1dfc9b9 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -14,17 +14,36 @@ LayerKeycode = namedtuple('LayerKeycode', ('code', 'layer')) class Keycode: - def __init__(self, code, has_modifiers=None): + def __init__(self, code, has_modifiers=None, no_press=False, no_release=False): self.code = code self.has_modifiers = has_modifiers + # cast to bool() in case we get a None value + self.no_press = bool(no_press) + self.no_release = bool(no_press) + + def __call__(self, no_press=None, no_release=None): + if no_press is None and no_release is None: + return self + + return Keycode( + code=self.code, + has_modifiers=self.has_modifiers, + no_press=no_press, + no_release=no_release, + ) -class ModifierKeycode: - def __init__(self, code): - self.code = code +class ModifierKeycode(Keycode): + def __call__(self, modified_code=None, no_press=None, no_release=None): + if modified_code is None and no_press is None and no_release is None: + return self - def __call__(self, modified_code): - new_keycode = Keycode(modified_code.code, {self.code}) + new_keycode = Keycode( + modified_code.code, + {self.code}, + no_press=no_press, + no_release=no_release, + ) if modified_code.has_modifiers: new_keycode.has_modifiers |= modified_code.has_modifiers diff --git a/kmk/macros/__init__.py b/kmk/common/macros/__init__.py similarity index 100% rename from kmk/macros/__init__.py rename to kmk/common/macros/__init__.py diff --git a/kmk/common/macros/simple.py b/kmk/common/macros/simple.py new file mode 100644 index 0000000..b901338 --- /dev/null +++ b/kmk/common/macros/simple.py @@ -0,0 +1,17 @@ +from kmk.common.event_defs import (hid_report_event, keycode_down_event, + keycode_up_event) +from kmk.common.macros import KMKMacro + + +def simple_key_sequence(seq): + def _simple_key_sequence(state): + for key in seq: + if not getattr(key, 'no_press', None): + yield keycode_down_event(key) + yield hid_report_event() + + if not getattr(key, 'no_release', None): + yield keycode_up_event(key) + yield hid_report_event() + + return KMKMacro(keydown=_simple_key_sequence) diff --git a/kmk/common/macros/unicode.py b/kmk/common/macros/unicode.py new file mode 100644 index 0000000..2a6f090 --- /dev/null +++ b/kmk/common/macros/unicode.py @@ -0,0 +1,45 @@ +from kmk.common.consts import UnicodeModes +from kmk.common.event_defs import (hid_report_event, keycode_down_event, + keycode_up_event) +from kmk.common.keycodes import Common, Modifiers +from kmk.common.macros import KMKMacro +from kmk.common.macros.simple import simple_key_sequence + +IBUS_KEY_COMBO = Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U)) + + +def generate_codepoint_keysym_seq(codepoint): + return [ + getattr(Common, 'KC_{}'.format(codepoint_fragment.upper())) + for codepoint_fragment in codepoint + ] + + +def unicode_sequence(codepoints): + def _unicode_sequence(state): + if state.unicode_mode == UnicodeModes.IBUS: + yield from _ibus_unicode_sequence(codepoints, state) + elif state.unicode_mode == UnicodeModes.RALT: + yield from _ralt_unicode_sequence(codepoints, state) + + return KMKMacro(keydown=_unicode_sequence) + + +def _ralt_unicode_sequence(codepoints, state): + for codepoint in codepoints: + yield keycode_down_event(Modifiers.RALT(no_release=True)) + yield from simple_key_sequence(generate_codepoint_keysym_seq(codepoint)).keydown(state) + yield keycode_up_event(Modifiers.RALT(no_press=True)) + + +def _ibus_unicode_sequence(codepoints, state): + for codepoint in codepoints: + yield keycode_down_event(IBUS_KEY_COMBO) + yield hid_report_event() + yield keycode_up_event(IBUS_KEY_COMBO) + yield hid_report_event() + + seq = generate_codepoint_keysym_seq(codepoint) + seq.append(Common.KC_ENTER) + + yield from simple_key_sequence(seq).keydown(state) diff --git a/kmk/entrypoints/handwire/pyboard.py b/kmk/entrypoints/handwire/pyboard.py index 0a0f582..fb11c53 100644 --- a/kmk/entrypoints/handwire/pyboard.py +++ b/kmk/entrypoints/handwire/pyboard.py @@ -1,6 +1,7 @@ import sys from logging import DEBUG +from kmk.common.consts import UnicodeModes from kmk.firmware import Firmware from kmk.micropython.pyb_hid import HIDHelper @@ -8,12 +9,18 @@ from kmk.micropython.pyb_hid import HIDHelper def main(): from kmk_keyboard_user import cols, diode_orientation, keymap, rows + try: + from kmk_keyboard_user import unicode_mode + except Exception: + unicode_mode = UnicodeModes.NOOP + try: firmware = Firmware( keymap=keymap, row_pins=rows, col_pins=cols, diode_orientation=diode_orientation, + unicode_mode=unicode_mode, hid=HIDHelper, log_level=DEBUG, ) diff --git a/kmk/firmware.py b/kmk/firmware.py index 2156beb..bff6c08 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -12,7 +12,8 @@ except ImportError: class Firmware: def __init__( self, keymap, row_pins, col_pins, - diode_orientation, hid=None, log_level=logging.NOTSET, + diode_orientation, unicode_mode=None, + hid=None, log_level=logging.NOTSET, ): logger = logging.getLogger(__name__) logger.setLevel(log_level) @@ -36,6 +37,7 @@ class Firmware: row_pins=row_pins, col_pins=col_pins, diode_orientation=diode_orientation, + unicode_mode=unicode_mode, )) def _subscription(self, state, action): diff --git a/kmk/macros/simple.py b/kmk/macros/simple.py deleted file mode 100644 index 324b556..0000000 --- a/kmk/macros/simple.py +++ /dev/null @@ -1,29 +0,0 @@ -from kmk.common.event_defs import (hid_report_event, keycode_down_event, - keycode_up_event) -from kmk.common.keycodes import Common, Modifiers -from kmk.macros import KMKMacro - - -def simple_key_sequence(seq): - def _simple_key_sequence(): - for key in seq: - yield keycode_down_event(key) - yield hid_report_event() - yield keycode_up_event(key) - yield hid_report_event() - - return KMKMacro(keydown=_simple_key_sequence) - - -def ibus_unicode_sequence(codepoints): - seq = [] - - for codepoint in codepoints: - seq.append(Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U))) - - for codepoint_fragment in codepoint: - seq.append(getattr(Common, 'KC_{}'.format(codepoint_fragment.upper()))) - - seq.append(Common.KC_ENTER) - - return simple_key_sequence(seq) diff --git a/user_keymaps/klardotsh/threethree_matrix_pyboard.py b/user_keymaps/klardotsh/threethree_matrix_pyboard.py index 9498a82..c334b96 100644 --- a/user_keymaps/klardotsh/threethree_matrix_pyboard.py +++ b/user_keymaps/klardotsh/threethree_matrix_pyboard.py @@ -1,15 +1,17 @@ import machine -from kmk.common.consts import DiodeOrientation +from kmk.common.consts import DiodeOrientation, UnicodeModes from kmk.common.keycodes import KC +from kmk.common.macros.simple import simple_key_sequence +from kmk.common.macros.unicode import unicode_sequence from kmk.entrypoints.handwire.pyboard import main -from kmk.macros.simple import ibus_unicode_sequence, simple_key_sequence p = machine.Pin.board cols = (p.X10, p.X11, p.X12) rows = (p.X1, p.X2, p.X3) diode_orientation = DiodeOrientation.COLUMNS +unicode_mode = UnicodeModes.LINUX MACRO_TEST_STRING = simple_key_sequence([ KC.LSHIFT(KC.H), @@ -26,7 +28,7 @@ MACRO_TEST_STRING = simple_key_sequence([ KC.EXCLAIM, ]) -ANGRY_TABLE_FLIP = ibus_unicode_sequence([ +ANGRY_TABLE_FLIP = unicode_sequence([ "28", "30ce", "ca0",