From 39a64656583ba155290991be07234c261422d803 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 29 Dec 2018 04:44:52 -0800 Subject: [PATCH 01/15] Checkpoint alpha: Reflow macros and keycodes into a consistent structure. Most internal state functionality largely untouched (just moved) --- kmk/firmware.py | 10 - kmk/handlers/__init__.py | 0 kmk/handlers/layers.py | 108 ++++ kmk/handlers/sequences.py | 40 ++ kmk/handlers/stock.py | 89 +++ kmk/internal_state.py | 240 +------- kmk/keycodes.py | 1025 +++++++++++++++------------------- kmk/macros/rotary_encoder.py | 76 --- kmk/macros/simple.py | 28 - kmk/macros/unicode.py | 35 +- kmk/rotary_encoder.py | 57 -- kmk/types.py | 26 + 12 files changed, 767 insertions(+), 967 deletions(-) create mode 100644 kmk/handlers/__init__.py create mode 100644 kmk/handlers/layers.py create mode 100644 kmk/handlers/sequences.py create mode 100644 kmk/handlers/stock.py delete mode 100644 kmk/macros/rotary_encoder.py delete mode 100644 kmk/macros/simple.py delete mode 100644 kmk/rotary_encoder.py diff --git a/kmk/firmware.py b/kmk/firmware.py index c59f580..ac8f3b8 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -202,16 +202,6 @@ class Firmware: if old_timeouts_len != new_timeouts_len: state_changed = True - if self._state.macros_pending: - # Blindly assume macros are going to change state, which is almost - # always a safe assumption - state_changed = True - for macro in self._state.macros_pending: - for key in macro(self): - self._send_key(key) - - self._state.resolve_macro() - if self.debug_enabled and state_changed: print('New State: {}'.format(self._state._to_dict())) diff --git a/kmk/handlers/__init__.py b/kmk/handlers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kmk/handlers/layers.py b/kmk/handlers/layers.py new file mode 100644 index 0000000..0e448dd --- /dev/null +++ b/kmk/handlers/layers.py @@ -0,0 +1,108 @@ +from kmk.kmktime import ticks_diff, ticks_ms + + +def df_pressed(key, state, *args, **kwargs): + """Switches the default layer""" + state.active_layers[0] = key.meta.layer + state.reversed_active_layers = list(reversed(state.active_layers)) + return state + + +def mo_pressed(key, state, *args, **kwargs): + """Momentarily activates layer, switches off when you let go""" + state.active_layers.append(key.meta.layer) + state.reversed_active_layers = list(reversed(state.active_layers)) + return state + + +def mo_released(key, state, KC, *args, **kwargs): + state.active_layers = [ + layer for layer in state.active_layers + if layer != key.meta.layer + ] + state.reversed_active_layers = list(reversed(state.active_layers)) + return state + + +def lm_pressed(key, state, *args, **kwargs): + """As MO(layer) but with mod active""" + state.hid_pending = True + # Sets the timer start and acts like MO otherwise + state.start_time['lm'] = ticks_ms() + state.keys_pressed.add(key.meta.kc) + return mo_pressed(key, state, *args, **kwargs) + + +def lm_released(key, state, *args, **kwargs): + """As MO(layer) but with mod active""" + state.hid_pending = True + state.keys_pressed.discard(key.meta.kc) + state.start_time['lm'] = None + return mo_released(key, state, *args, **kwargs) + + +def lt_pressed(key, state, *args, **kwargs): + # Sets the timer start and acts like MO otherwise + state.start_time['lt'] = ticks_ms() + return mo_pressed(key, state, *args, **kwargs) + + +def lt_released(key, state, *args, **kwargs): + # On keyup, check timer, and press key if needed. + if state.start_time['lt'] and ( + ticks_diff(ticks_ms(), state.start_time['lt']) < state.config.tap_time + ): + state.hid_pending = True + state.tap_key(key.meta.kc) + + mo_released(key, state, *args, **kwargs) + state.start_time['lt'] = None + return state + + +def tg_pressed(key, state, *args, **kwargs): + """Toggles the layer (enables it if not active, and vise versa)""" + if key.meta.layer in state.active_layers: + state.active_layers = [ + layer for layer in state.active_layers + if layer != key.meta.layer + ] + else: + state.active_layers.append(key.meta.layer) + + state.reversed_active_layers = list(reversed(state.active_layers)) + + return state + + +def to_pressed(key, state, *args, **kwargs): + """Activates layer and deactivates all other layers""" + state.active_layers = [key.meta.kc] + state.reversed_active_layers = list(reversed(state.active_layers)) + + return state + + +def tt_pressed(key, state, *args, **kwargs): + """Momentarily activates layer if held, toggles it if tapped repeatedly""" + # TODO Make this work with tap dance to function more correctly, but technically works. + if state.start_time['tt'] is None: + # Sets the timer start and acts like MO otherwise + state.start_time['tt'] = ticks_ms() + return mo_pressed(key, state, *args, **kwargs) + elif ticks_diff(ticks_ms(), state.start_time['tt']) < state.config.tap_time: + state.start_time['tt'] = None + return tg_pressed(key, state, *args, **kwargs) + + +def tt_released(key, state, *args, **kwargs): + if ( + state.start_time['tt'] is None or + ticks_diff(ticks_ms(), state.start_time['tt']) >= state.config.tap_time + ): + # On first press, works like MO. On second press, does nothing unless let up within + # time window, then acts like TG. + state.start_time['tt'] = None + return mo_released(key, state, *args, **kwargs) + + return state diff --git a/kmk/handlers/sequences.py b/kmk/handlers/sequences.py new file mode 100644 index 0000000..c218a5f --- /dev/null +++ b/kmk/handlers/sequences.py @@ -0,0 +1,40 @@ +from kmk.keycodes import ALL_KEYS, KC, make_key +from kmk.types import KeySequenceMeta + + +def sequence_press_handler(key, state, KC, *args, **kwargs): + old_keys_pressed = state.keys_pressed + state.keys_pressed = set() + + for ikey in key.meta.seq: + if not getattr(ikey, 'no_press', None): + state.process_key(ikey, True) + state.config._send_hid() + if not getattr(ikey, 'no_release', None): + state.process_key(ikey, False) + state.config._send_hid() + + state.keys_pressed = old_keys_pressed + + return state + + +def simple_key_sequence(seq): + return make_key( + meta=KeySequenceMeta(seq), + on_press=sequence_press_handler, + ) + + +def send_string(message): + seq = [] + + for char in message: + kc = ALL_KEYS[char] + + if char.isupper(): + kc = KC.LSHIFT(kc) + + seq.append(kc) + + return simple_key_sequence(seq) diff --git a/kmk/handlers/stock.py b/kmk/handlers/stock.py new file mode 100644 index 0000000..e117dd5 --- /dev/null +++ b/kmk/handlers/stock.py @@ -0,0 +1,89 @@ +from kmk.kmktime import sleep_ms +from kmk.util import reset_bootloader, reset_keyboard + + +def passthrough(key, state, *args, **kwargs): + return state + + +def default_pressed(key, state, KC, coord_int=None, coord_raw=None): + if coord_int is not None: + state.coord_keys_pressed[coord_int] = key + + state.add_key(key) + + return state + + +def default_released(key, state, KC, coord_int=None, coord_raw=None): + state.remove_key(key) + + if coord_int is not None: + state.keys_pressed.discard(key.coord_keys_pressed.get(coord_int, None)) + state.coord_keys_pressed[coord_int] = None + + return state + + +def reset(*args, **kwargs): + reset_keyboard() + + +def bootloader(*args, **kwargs): + reset_bootloader() + + +def debug_pressed(key, state, KC, *args, **kwargs): + if state.config.debug_enabled: + print('Disabling debug mode, bye!') + else: + print('Enabling debug mode. Welcome to the jungle.') + + state.config.debug_enabled = not state.config.debug_enabled + + return state + + +def gesc_pressed(key, state, KC, *args, **kwargs): + GESC_TRIGGERS = {KC.LSHIFT, KC.RSHIFT, KC.LGUI, KC.RGUI} + + if GESC_TRIGGERS.intersection(state.keys_pressed): + # if Shift is held, KC_GRAVE will become KC_TILDE on OS level + state.keys_pressed.add(KC.GRAVE) + return state + + # else return KC_ESC + state.keys_pressed.add(KC.ESCAPE) + state.hid_pending = True + + return state + + +def gesc_released(key, state, KC, *args, **kwargs): + state.keys_pressed.discard(KC.ESCAPE) + state.keys_pressed.discard(KC.GRAVE) + state.hid_pending = True + return state + + +def sleep_pressed(key, state, KC, *args, **kwargs): + sleep_ms(key.meta.ms) + return state + + +def uc_mode_pressed(key, state, *args, **kwargs): + state.config.unicode_mode = key.meta.mode + + return state + + +def leader_pressed(key, state, *args, **kwargs): + return state._begin_leader_mode() + + +def tap_dance_pressed(key, state, *args, **kwargs): + return state._process_tap_dance(key, True) + + +def tap_dance_released(key, state, *args, **kwargs): + return state._process_tap_dance(key, False) diff --git a/kmk/internal_state.py b/kmk/internal_state.py index 5d5d429..1cf40d6 100644 --- a/kmk/internal_state.py +++ b/kmk/internal_state.py @@ -1,19 +1,13 @@ from kmk.consts import LeaderMode -from kmk.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, Keycodes, RawKeycodes, - TapDanceKeycode) -from kmk.kmktime import sleep_ms, ticks_diff, ticks_ms +from kmk.keycodes import KC +from kmk.kmktime import ticks_ms +from kmk.types import TapDanceKeyMeta from kmk.util import intify_coordinate -GESC_TRIGGERS = { - Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT, - Keycodes.Modifiers.KC_LGUI, Keycodes.Modifiers.KC_RGUI, -} - class InternalState: keys_pressed = set() coord_keys_pressed = {} - macros_pending = [] leader_pending = None leader_last_len = 0 hid_pending = False @@ -34,22 +28,6 @@ class InternalState: def __init__(self, config): self.config = config - self.internal_key_handlers = { - RawKeycodes.KC_DF: self._layer_df, - RawKeycodes.KC_MO: self._layer_mo, - RawKeycodes.KC_LM: self._layer_lm, - RawKeycodes.KC_LT: self._layer_lt, - RawKeycodes.KC_TG: self._layer_tg, - RawKeycodes.KC_TO: self._layer_to, - RawKeycodes.KC_TT: self._layer_tt, - Keycodes.KMK.KC_GESC.code: self._kc_gesc, - RawKeycodes.KC_UC_MODE: self._kc_uc_mode, - RawKeycodes.KC_MACRO: self._kc_macro, - Keycodes.KMK.KC_LEAD.code: self._kc_lead, - Keycodes.KMK.KC_NO.code: self._kc_no, - Keycodes.KMK.KC_DEBUG.code: self._kc_debug_mode, - RawKeycodes.KC_TAP_DANCE: self._kc_tap_dance, - } def __repr__(self): return 'InternalState({})'.format(self._to_dict()) @@ -74,7 +52,7 @@ class InternalState: for layer in self.reversed_active_layers: layer_key = self.config.keymap[layer][row][col] - if not layer_key or layer_key == Keycodes.KMK.KC_TRNS: + if not layer_key or layer_key == KC.TRNS: continue if self.config.debug_enabled: @@ -122,16 +100,16 @@ class InternalState: print('No key accessible for col, row: {}, {}'.format(row, col)) return self - if self.tapping and not isinstance(kc_changed, TapDanceKeycode): - self._process_tap_dance(kc_changed, is_pressed) + return self.process_key(kc_changed, is_pressed, int_coord, (row, col)) + + def process_key(self, key, is_pressed, coord_int=None, coord_raw=None): + if self.tapping and not isinstance(key.meta, TapDanceKeyMeta): + self._process_tap_dance(key, is_pressed) else: if is_pressed: - self.coord_keys_pressed[int_coord] = kc_changed - self.add_key(kc_changed) + key.on_press(self, coord_int, coord_raw) else: - self.remove_key(kc_changed) - self.keys_pressed.discard(self.coord_keys_pressed.get(int_coord, None)) - self.coord_keys_pressed[int_coord] = None + key.on_release(self, coord_int, coord_raw) if self.config.leader_mode % 2 == 1: self._process_leader_mode() @@ -140,27 +118,11 @@ class InternalState: def remove_key(self, keycode): self.keys_pressed.discard(keycode) - - if keycode.code >= FIRST_KMK_INTERNAL_KEYCODE: - self._process_internal_key_event(keycode, False) - else: - self.hid_pending = True - - return self + return self.process_key(keycode, False) def add_key(self, keycode): - # TODO Make this itself a macro keycode with a keyup handler - # rather than handling this inline here. Gross. - if keycode.code == Keycodes.KMK.KC_MACRO_SLEEP_MS: - sleep_ms(keycode.ms) - else: - self.keys_pressed.add(keycode) - - if keycode.code >= FIRST_KMK_INTERNAL_KEYCODE: - self._process_internal_key_event(keycode, True) - else: - self.hid_pending = True - return self + self.keys_pressed.add(keycode) + return self.process_key(keycode, True) def tap_key(self, keycode): self.add_key(keycode) @@ -175,13 +137,6 @@ class InternalState: self.hid_pending = False return self - def resolve_macro(self): - if self.config.debug_enabled: - print('Macro complete!') - - self.macros_pending.pop() - return self - def _process_internal_key_event(self, changed_key, is_pressed): # Since the key objects can be chained into new objects # with, for example, no_press set, always check against @@ -192,164 +147,9 @@ class InternalState: changed_key, is_pressed, ) - def _layer_df(self, changed_key, is_pressed): - """Switches the default layer""" - if is_pressed: - self.active_layers[0] = changed_key.layer - self.reversed_active_layers = list(reversed(self.active_layers)) - - return self - - def _layer_mo(self, changed_key, is_pressed): - """Momentarily activates layer, switches off when you let go""" - if is_pressed: - self.active_layers.append(changed_key.layer) - else: - self.active_layers = [ - layer for layer in self.active_layers - if layer != changed_key.layer - ] - - self.reversed_active_layers = list(reversed(self.active_layers)) - - return self - - def _layer_lm(self, changed_key, is_pressed): - """As MO(layer) but with mod active""" - self.hid_pending = True - - if is_pressed: - # Sets the timer start and acts like MO otherwise - self.start_time['lm'] = ticks_ms() - self.keys_pressed.add(changed_key.kc) - else: - self.keys_pressed.discard(changed_key.kc) - self.start_time['lm'] = None - - return self._layer_mo(changed_key, is_pressed) - - def _layer_lt(self, changed_key, is_pressed): - """Momentarily activates layer if held, sends kc if tapped""" - if is_pressed: - # Sets the timer start and acts like MO otherwise - self.start_time['lt'] = ticks_ms() - self._layer_mo(changed_key, is_pressed) - else: - # On keyup, check timer, and press key if needed. - if self.start_time['lt'] and ( - ticks_diff(ticks_ms(), self.start_time['lt']) < self.config.tap_time - ): - self.hid_pending = True - self.tap_key(changed_key.kc) - - self._layer_mo(changed_key, is_pressed) - self.start_time['lt'] = None - return self - - def _layer_tg(self, changed_key, is_pressed): - """Toggles the layer (enables it if not active, and vise versa)""" - if is_pressed: - if changed_key.layer in self.active_layers: - self.active_layers = [ - layer for layer in self.active_layers - if layer != changed_key.layer - ] - else: - self.active_layers.append(changed_key.layer) - - self.reversed_active_layers = list(reversed(self.active_layers)) - - return self - - def _layer_to(self, changed_key, is_pressed): - """Activates layer and deactivates all other layers""" - if is_pressed: - self.active_layers = [changed_key.layer] - self.reversed_active_layers = list(reversed(self.active_layers)) - - return self - - def _layer_tt(self, changed_key, is_pressed): - """Momentarily activates layer if held, toggles it if tapped repeatedly""" - # TODO Make this work with tap dance to function more correctly, but technically works. - if is_pressed: - if self.start_time['tt'] is None: - # Sets the timer start and acts like MO otherwise - self.start_time['tt'] = ticks_ms() - return self._layer_mo(changed_key, is_pressed) - elif ticks_diff(ticks_ms(), self.start_time['tt']) < self.config.tap_time: - self.start_time['tt'] = None - return self.tg(changed_key, is_pressed) - elif ( - self.start_time['tt'] is None or - ticks_diff(ticks_ms(), self.start_time['tt']) >= self.config.tap_time - ): - # On first press, works like MO. On second press, does nothing unless let up within - # time window, then acts like TG. - self.start_time['tt'] = None - return self._layer_mo(changed_key, is_pressed) - - return self - - def _kc_uc_mode(self, changed_key, is_pressed): - if is_pressed: - self.config.unicode_mode = changed_key.mode - - return self - - def _kc_macro(self, changed_key, is_pressed): - if is_pressed: - if changed_key.keyup: - self.macros_pending.append(changed_key.keyup) - else: - if changed_key.keydown: - self.macros_pending.append(changed_key.keydown) - - return self - - def _kc_lead(self, changed_key, is_pressed): - if is_pressed: - self._begin_leader_mode() - - return self - - def _kc_gesc(self, changed_key, is_pressed): - self.hid_pending = True - - if is_pressed: - if GESC_TRIGGERS.intersection(self.keys_pressed): - # if Shift is held, KC_GRAVE will become KC_TILDE on OS level - self.keys_pressed.add(Keycodes.Common.KC_GRAVE) - return self - - # else return KC_ESC - self.keys_pressed.add(Keycodes.Common.KC_ESCAPE) - return self - - self.keys_pressed.discard(Keycodes.Common.KC_ESCAPE) - self.keys_pressed.discard(Keycodes.Common.KC_GRAVE) - return self - - def _kc_no(self, changed_key, is_pressed): - return self - - def _kc_debug_mode(self, changed_key, is_pressed): - if is_pressed: - if self.config.debug_enabled: - print('Disabling debug mode, bye!') - else: - print('Enabling debug mode. Welcome to the jungle.') - - self.config.debug_enabled = not self.config.debug_enabled - - return self - - def _kc_tap_dance(self, changed_key, is_pressed): - return self._process_tap_dance(changed_key, is_pressed) - def _process_tap_dance(self, changed_key, is_pressed): if is_pressed: - if not isinstance(changed_key, TapDanceKeycode): + if not isinstance(changed_key.meta, TapDanceKeyMeta): # If we get here, changed_key is not a TapDanceKeycode and thus # the user kept typing elsewhere (presumably). End ALL of the # currently outstanding tap dance runs. @@ -408,7 +208,7 @@ class InternalState: def _begin_leader_mode(self): if self.config.leader_mode % 2 == 0: - self.keys_pressed.discard(Keycodes.KMK.KC_LEAD) + self.keys_pressed.discard(KC.LEAD) # All leader modes are one number higher when activating self.config.leader_mode += 1 @@ -421,7 +221,7 @@ class InternalState: lmh = tuple(self.leader_mode_history) if lmh in self.config.leader_dictionary: - self.macros_pending.append(self.config.leader_dictionary[lmh].keydown) + self.process_key(self.config.leader_dictionary[lmh], True) return self._exit_leader_mode() @@ -438,15 +238,15 @@ class InternalState: for key in keys_pressed: if ( self.config.leader_mode == LeaderMode.ENTER_ACTIVE and - key == Keycodes.Common.KC_ENT + key == KC.ENT ): self._handle_leader_sequence() break - elif key == Keycodes.Common.KC_ESC or key == Keycodes.KMK.KC_GESC: + elif key == KC.ESC or key == KC.GESC: # Clean self and turn leader mode off. self._exit_leader_mode() break - elif key == Keycodes.KMK.KC_LEAD: + elif key == KC.LEAD: break else: # Add key if not needing to escape diff --git a/kmk/keycodes.py b/kmk/keycodes.py index ef0d6ea..2d0d528 100644 --- a/kmk/keycodes.py +++ b/kmk/keycodes.py @@ -1,110 +1,55 @@ -try: - from collections import namedtuple -except ImportError: - # This is handled by micropython-lib/collections, but on local runs of - # MicroPython, it doesn't exist - from ucollections import namedtuple - +import kmk.handlers.layers as layers +import kmk.handlers.stock as handlers from kmk.consts import UnicodeMode -from kmk.types import AttrDict +from kmk.types import (AttrDict, KeySeqSleepMeta, LayerKeyMeta, + TapDanceKeyMeta, UnicodeModeKeyMeta) FIRST_KMK_INTERNAL_KEYCODE = 1000 +NEXT_AVAILABLE_KEYCODE = 1000 -kc_lookup_cache = {} +KEYCODE_SIMPLE = 0 +KEYCODE_MODIFIER = 1 +KEYCODE_CONSUMER = 2 - -def lookup_kc_with_cache(char): - found_code = kc_lookup_cache.get( - char, - getattr(Common, 'KC_{}'.format(char.upper())), - ) - - kc_lookup_cache[char] = found_code - kc_lookup_cache[char.upper()] = found_code - kc_lookup_cache[char.lower()] = found_code - - return found_code - - -def generate_codepoint_keysym_seq(codepoint, expected_length=4): - # 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 - # - # As a bonus, this function can be pretty useful for - # leader dictionary keys as strings. - seq = [Common.KC_0 for _ in range(max(len(codepoint), expected_length))] - - for idx, codepoint_fragment in enumerate(reversed(codepoint)): - seq[-(idx + 1)] = lookup_kc_with_cache(codepoint_fragment) - - return seq +# Global state, will be filled in througout this file, and +# anywhere the user creates custom keys +ALL_KEYS = {} +KC = AttrDict(ALL_KEYS) def generate_leader_dictionary_seq(string): + # FIXME move to kmk.macros.unicode or somewhere else more fitting + # left here for backwards compat with various keymaps that + # import this + # + # I have absolutely no idea why it was in this file to begin with, + # probably something related to import order at one point. + from kmk.macros.unicode import generate_codepoint_keysym_seq + return tuple(generate_codepoint_keysym_seq(string, 1)) -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 = 1110 - KC_MACRO_SLEEP_MS = 1111 - KC_TAP_DANCE = 1113 - - -# 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', 'kc')) -MacroSleepKeycode = namedtuple('MacroSleepKeycode', ('code', 'ms')) - - -class UnicodeModeKeycode(namedtuple('UnicodeModeKeycode', ('code', 'mode'))): - @staticmethod - def from_mode_const(mode): - return UnicodeModeKeycode(RawKeycodes.KC_UC_MODE, mode) - - class Keycode: - def __init__(self, code, has_modifiers=None, no_press=False, no_release=False): + def __init__( + self, + code, + has_modifiers=None, + no_press=False, + no_release=False, + on_press=handlers.default_pressed, + on_release=handlers.default_released, + meta=object(), + ): 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) + self._on_press = on_press + self._on_release = on_release + self.meta = meta + def __call__(self, no_press=None, no_release=None): if no_press is None and no_release is None: return self @@ -119,8 +64,17 @@ class Keycode: def __repr__(self): return 'Keycode(code={}, has_modifiers={})'.format(self.code, self.has_modifiers) + def on_press(self, state, coord_int, coord_raw): + return self._on_press(self, state, KC, coord_int, coord_raw) + + def on_release(self, state, coord_int, coord_raw): + return self._on_release(self, state, KC, coord_int, coord_raw) + class ModifierKeycode(Keycode): + # FIXME this is atrocious to read. Please, please, please, strike down upon + # this with great vengeance and furious anger. + FAKE_CODE = -1 def __call__(self, modified_code=None, no_press=None, no_release=None): @@ -168,525 +122,460 @@ class ConsumerKeycode(Keycode): pass -class Macro: +def register_key_names(key, names=tuple()): # NOQA ''' - A special "key" which triggers a macro. + Names are globally unique. If a later key is created with + the same name as an existing entry in `KC`, it will overwrite + the existing entry. + + If a name entry is only a single letter, its entry in the KC + object will not be case-sensitive (meaning `names=('A',)` is + sufficient to create a key accessible by both `KC.A` and `KC.a`). ''' - code = RawKeycodes.KC_MACRO - def __init__(self, keydown=None, keyup=None): - self.keydown = keydown - self.keyup = keyup + for name in names: + ALL_KEYS[name] = key - def on_keydown(self): - return self.keydown() if self.keydown else None + if len(name) == 1: + ALL_KEYS[name.upper()] = key + ALL_KEYS[name.lower()] = key - def on_keyup(self): - return self.keyup() if self.keyup else None + return key -class TapDanceKeycode: - code = RawKeycodes.KC_TAP_DANCE +def make_key( + code=None, + names=tuple(), # NOQA + type=KEYCODE_SIMPLE, + **kwargs, +): + ''' + Create a new key, aliased by `names` in the KC lookup table. - def __init__(self, *codes): - self.codes = codes + If a code is not specified, the key is assumed to be a custom + internal key to be handled in a state callback rather than + sent directly to the OS. These codes will autoincrement. + + See register_key_names() for details on the assignment. + + All **kwargs are passed to the Keycode constructor + ''' + + global NEXT_AVAILABLE_KEYCODE + + if type == KEYCODE_SIMPLE: + constructor = Keycode + elif type == KEYCODE_MODIFIER: + constructor = ModifierKeycode + elif type == KEYCODE_CONSUMER: + constructor = ConsumerKeycode + else: + raise ValueError('Unrecognized key type') + + if code is None: + code = NEXT_AVAILABLE_KEYCODE + NEXT_AVAILABLE_KEYCODE += 1 + elif code >= FIRST_KMK_INTERNAL_KEYCODE: + # Try to ensure future auto-generated internal keycodes won't + # be overridden by continuing to +1 the sequence from the provided + # code + NEXT_AVAILABLE_KEYCODE = max(NEXT_AVAILABLE_KEYCODE, code + 1) + + key = constructor(code=code, **kwargs) + + register_key_names(key, names) + + return key -class KeycodeCategory(type): - @classmethod - def to_dict(cls): - ''' - MicroPython, for whatever reason (probably performance/memory) makes - __dict__ optional for ports. Unfortunately, at least the STM32 - (Pyboard) port is one such port. This reimplements a subset of - __dict__, limited to just keys we're likely to care about (though this - could be opened up further later). - ''' - - hidden = ('to_dict', 'recursive_dict', 'contains') - return AttrDict({ - key: getattr(cls, key) - for key in dir(cls) - if not key.startswith('_') and key not in hidden - }) - - @classmethod - def recursive_dict(cls): - ''' - to_dict() executed recursively all the way down a tree - ''' - ret = cls.to_dict() - - for key, val in ret.items(): - try: - nested_ret = val.recursive_dict() - except (AttributeError, NameError): - continue - - ret[key] = nested_ret - - return ret - - @classmethod - def contains(cls, kc): - ''' - Emulates the 'in' operator for keycode groupings, given MicroPython's - lack of support for metaclasses (meaning implementing 'in' for - uninstantiated classes, such as these, is largely not possible). Not - super useful in most cases, but does allow for sanity checks like - - ```python - assert Keycodes.Modifiers.contains(requested_key) - ``` - - This is not bulletproof due to how HID codes are defined (there is - overlap). Keycodes.Common.KC_A, for example, is equal in value to - Keycodes.Modifiers.KC_LALT, but it can still prevent silly mistakes - like trying to use, say, Keycodes.Common.KC_Q as a modifier. - - This is recursive across subgroups, enabling stuff like: - - ```python - assert Keycodes.contains(requested_key) - ``` - - To ensure that a valid keycode has been requested to begin with. Again, - not bulletproof, but adds at least some cushion to stuff that would - otherwise cause AttributeErrors and crash the keyboard. - ''' - subcategories = ( - category for category in cls.to_dict().values() - # Disgusting, but since `cls.__bases__` isn't implemented in MicroPython, - # I resort to a less foolproof inheritance check that should still ignore - # strings and other stupid stuff (we don't want to iterate over __doc__, - # for example), but include nested classes. - # - # One huge lesson in this project is that uninstantiated classes are hard... - # and four times harder when the implementation of Python is half-baked. - if isinstance(category, type) - ) - - if any( - kc == _kc - for name, _kc in cls.to_dict().items() - if name.startswith('KC_') - ): - return True - - return any(sc.contains(kc) for sc in subcategories) +def make_mod_key(*args, **kwargs): + return make_key(*args, **kwargs, type=KEYCODE_MODIFIER) -class Modifiers(KeycodeCategory): - KC_LCTRL = KC_LCTL = ModifierKeycode(RawKeycodes.LCTRL) - KC_LSHIFT = KC_LSFT = ModifierKeycode(RawKeycodes.LSHIFT) - KC_LALT = ModifierKeycode(RawKeycodes.LALT) - KC_LGUI = KC_LCMD = KC_LWIN = ModifierKeycode(RawKeycodes.LGUI) - KC_RCTRL = KC_RCTL = ModifierKeycode(RawKeycodes.RCTRL) - KC_RSHIFT = KC_RSFT = ModifierKeycode(RawKeycodes.RSHIFT) - KC_RALT = ModifierKeycode(RawKeycodes.RALT) - KC_RGUI = KC_RCMD = KC_RWIN = ModifierKeycode(RawKeycodes.RGUI) +def make_shifted_key(target_name, names=tuple()): # NOQA + key = KC.LSFT(ALL_KEYS[target_name]) - KC_MEH = KC_LSHIFT(KC_LALT(KC_LCTRL)) - KC_HYPR = KC_HYPER = KC_MEH(KC_LGUI) + register_key_names(key, names) + + return key -class Common(KeycodeCategory): - KC_A = Keycode(4) - KC_B = Keycode(5) - KC_C = Keycode(6) - KC_D = Keycode(7) - KC_E = Keycode(8) - KC_F = Keycode(9) - KC_G = Keycode(10) - KC_H = Keycode(11) - KC_I = Keycode(12) - KC_J = Keycode(13) - KC_K = Keycode(14) - KC_L = Keycode(15) - KC_M = Keycode(16) - KC_N = Keycode(17) - KC_O = Keycode(18) - KC_P = Keycode(19) - KC_Q = Keycode(20) - KC_R = Keycode(21) - KC_S = Keycode(22) - KC_T = Keycode(23) - KC_U = Keycode(24) - KC_V = Keycode(25) - KC_W = Keycode(26) - KC_X = Keycode(27) - KC_Y = Keycode(28) - KC_Z = Keycode(29) - - # Aliases to play nicely with AttrDict, since KC.1 isn't a valid - # attribute key in Python, but KC.N1 is - KC_1 = KC_N1 = Keycode(30) - KC_2 = KC_N2 = Keycode(31) - KC_3 = KC_N3 = Keycode(32) - KC_4 = KC_N4 = Keycode(33) - KC_5 = KC_N5 = Keycode(34) - KC_6 = KC_N6 = Keycode(35) - KC_7 = KC_N7 = Keycode(36) - KC_8 = KC_N8 = Keycode(37) - KC_9 = KC_N9 = Keycode(38) - KC_0 = KC_N0 = Keycode(39) - - KC_ENTER = KC_ENT = Keycode(40) - KC_ESCAPE = KC_ESC = Keycode(41) - KC_BACKSPACE = KC_BSPC = KC_BKSP = Keycode(42) - KC_TAB = Keycode(43) - KC_SPACE = KC_SPC = Keycode(44) - KC_MINUS = KC_MINS = Keycode(45) - KC_EQUAL = KC_EQL = Keycode(46) - KC_LBRACKET = KC_LBRC = Keycode(47) - KC_RBRACKET = KC_RBRC = Keycode(48) - KC_BACKSLASH = KC_BSLASH = KC_BSLS = Keycode(49) - KC_NONUS_HASH = KC_NUHS = Keycode(50) - KC_NONUS_BSLASH = KC_NUBS = Keycode(100) - KC_SEMICOLON = KC_SCOLON = KC_SCLN = Keycode(51) - KC_QUOTE = KC_QUOT = Keycode(52) - KC_GRAVE = KC_GRV = KC_ZKHK = Keycode(53) - KC_COMMA = KC_COMM = Keycode(54) - KC_DOT = Keycode(55) - KC_SLASH = KC_SLSH = Keycode(56) +def make_consumer_key(*args, **kwargs): + return make_key(*args, **kwargs, type=KEYCODE_CONSUMER) -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 = Modifiers.KC_LSHIFT(Common.KC_COMMA) - KC_RIGHT_ANGLE_BRACKET = KC_RABK = Modifiers.KC_LSHIFT(Common.KC_DOT) - KC_QUESTION = KC_QUES = Modifiers.KC_LSHIFT(Common.KC_SLSH) +# Argumented keys are implicitly internal, so auto-gen of code +# is almost certainly the best plan here +def make_argumented_key( + validator=lambda *validator_args, **validator_kwargs: object(), + *constructor_args, + **constructor_kwargs, +): + def _argumented_key(*user_args, **user_kwargs): + meta = validator(*user_args, **user_kwargs) + + if meta: + return Keycode( + meta=meta, + *constructor_args, + **constructor_kwargs, + ) + else: + raise ValueError( + 'Argumented key validator failed for unknown reasons. ' + 'This may not be the keymap\'s fault, as a more specific error ' + 'should have been raised.', + ) -class FunctionKeys(KeycodeCategory): - KC_F1 = Keycode(58) - KC_F2 = Keycode(59) - KC_F3 = Keycode(60) - KC_F4 = Keycode(61) - KC_F5 = Keycode(62) - KC_F6 = Keycode(63) - KC_F7 = Keycode(64) - KC_F8 = Keycode(65) - KC_F9 = Keycode(66) - KC_F10 = Keycode(67) - KC_F11 = Keycode(68) - KC_F12 = Keycode(69) - KC_F13 = Keycode(104) - KC_F14 = Keycode(105) - KC_F15 = Keycode(106) - KC_F16 = Keycode(107) - KC_F17 = Keycode(108) - KC_F18 = Keycode(109) - KC_F19 = Keycode(110) - KC_F20 = Keycode(111) - KC_F21 = Keycode(112) - KC_F22 = Keycode(113) - KC_F23 = Keycode(114) - KC_F24 = Keycode(115) +# Modifiers +make_mod_key(code=0x01, names=('LEFT_CONTROL', 'LCTRL', 'LCTL')) +make_mod_key(code=0x02, names=('LEFT_SHIFT', 'LSHIFT', 'LSFT')) +make_mod_key(code=0x04, names=('LEFT_ALT', 'LALT')) +make_mod_key(code=0x08, names=('LEFT_SUPER', 'LGUI', 'LCMD', 'LWIN')) +make_mod_key(code=0x10, names=('RIGHT_CONTROL', 'RCTRL', 'RCTL')) +make_mod_key(code=0x20, names=('RIGHT_SHIFT', 'RSHIFT', 'RSFT')) +make_mod_key(code=0x40, names=('RIGHT_ALT', 'RALT')) +make_mod_key(code=0x80, names=('RIGHT_SUPER', 'RGUI', 'RCMD', 'RWIN')) +# MEH = LCTL | LALT | LSFT +make_mod_key(code=0x07, names=('MEH',)) +# HYPR = LCTL | LALT | LSFT | LGUI +make_mod_key(code=0x0F, names=('HYPER', 'HYPR')) +# Basic ASCII letters +make_key(code=4, names=('A',)) +make_key(code=5, names=('B',)) +make_key(code=6, names=('C',)) +make_key(code=7, names=('D',)) +make_key(code=8, names=('E',)) +make_key(code=9, names=('F',)) +make_key(code=10, names=('G',)) +make_key(code=11, names=('H',)) +make_key(code=12, names=('I',)) +make_key(code=13, names=('J',)) +make_key(code=14, names=('K',)) +make_key(code=15, names=('L',)) +make_key(code=16, names=('M',)) +make_key(code=17, names=('N',)) +make_key(code=18, names=('O',)) +make_key(code=19, names=('P',)) +make_key(code=20, names=('Q',)) +make_key(code=21, names=('R',)) +make_key(code=22, names=('S',)) +make_key(code=23, names=('T',)) +make_key(code=24, names=('U',)) +make_key(code=25, names=('V',)) +make_key(code=26, names=('W',)) +make_key(code=27, names=('X',)) +make_key(code=28, names=('Y',)) +make_key(code=29, names=('Z',)) -class NavAndLocks(KeycodeCategory): - KC_CAPS_LOCK = KC_CLCK = KC_CAPS = Keycode(57) - KC_LOCKING_CAPS = KC_LCAP = Keycode(130) - KC_PSCREEN = KC_PSCR = Keycode(70) - KC_SCROLLLOCK = KC_SLCK = Keycode(71) - KC_LOCKING_SCROLL = KC_LSCRL = Keycode(132) - KC_PAUSE = KC_PAUS = KC_BRK = Keycode(72) - KC_INSERT = KC_INS = Keycode(73) - KC_HOME = Keycode(74) - KC_PGUP = Keycode(75) - KC_DELETE = KC_DEL = Keycode(76) - KC_END = Keycode(77) - KC_PGDOWN = KC_PGDN = Keycode(78) - KC_RIGHT = KC_RGHT = Keycode(79) - KC_LEFT = Keycode(80) - KC_DOWN = Keycode(81) - KC_UP = Keycode(82) +# Numbers +# Aliases to play nicely with AttrDict, since KC.1 isn't a valid +# attribute key in Python, but KC.N1 is +make_key(code=30, names=('1', 'N1')) +make_key(code=31, names=('2', 'N2')) +make_key(code=32, names=('3', 'N3')) +make_key(code=33, names=('4', 'N4')) +make_key(code=34, names=('5', 'N5')) +make_key(code=35, names=('6', 'N6')) +make_key(code=36, names=('7', 'N7')) +make_key(code=37, names=('8', 'N8')) +make_key(code=38, names=('9', 'N9')) +make_key(code=39, names=('0', 'N0')) +# More ASCII standard keys +make_key(code=40, names=('ENTER', 'ENT', "\n")) +make_key(code=41, names=('ESCAPE', 'ESC')) +make_key(code=42, names=('BACKSPACE', 'BSPC', 'BKSP')) +make_key(code=43, names=('TAB', "\t")) +make_key(code=44, names=('SPACE', 'SPC', ' ')) +make_key(code=45, names=('MINUS', 'MINS', '-')) +make_key(code=46, names=('EQUAL', 'EQL', '=')) +make_key(code=47, names=('LBRACKET', 'LBRC', '[')) +make_key(code=48, names=('RBRACKET', 'RBRC', ']')) +make_key(code=49, names=('BACKSLASH', 'BSLASH', 'BSLS', "\\")) +make_key(code=51, names=('SEMICOLON', 'SCOLON', 'SCLN', ';')) +make_key(code=52, names=('QUOTE', 'QUOT', "'")) +make_key(code=53, names=('GRAVE', 'GRV', 'ZKHK', '`')) +make_key(code=54, names=('COMMA', 'COMM', ',')) +make_key(code=55, names=('DOT', '.')) +make_key(code=56, names=('SLASH', 'SLSH')) -class Numpad(KeycodeCategory): - KC_NUMLOCK = KC_NLCK = Keycode(83) - KC_LOCKING_NUM = KC_LNUM = Keycode(131) - KC_KP_SLASH = KC_PSLS = Keycode(84) - KC_KP_ASTERIK = KC_PAST = Keycode(85) - KC_KP_MINUS = KC_PMNS = Keycode(86) - KC_KP_PLUS = KC_PPLS = Keycode(87) - KC_KP_ENTER = KC_PENT = Keycode(88) - KC_KP_1 = KC_P1 = Keycode(89) - KC_KP_2 = KC_P2 = Keycode(90) - KC_KP_3 = KC_P3 = Keycode(91) - KC_KP_4 = KC_P4 = Keycode(92) - KC_KP_5 = KC_P5 = Keycode(93) - KC_KP_6 = KC_P6 = Keycode(94) - KC_KP_7 = KC_P7 = Keycode(95) - KC_KP_8 = KC_P8 = Keycode(96) - KC_KP_9 = KC_P9 = Keycode(97) - KC_KP_0 = KC_P0 = Keycode(98) - KC_KP_DOT = KC_PDOT = Keycode(99) - KC_KP_EQUAL = KC_PEQL = Keycode(103) - KC_KP_COMMA = KC_PCMM = Keycode(133) - KC_KP_EQUAL_AS400 = Keycode(134) +# Function Keys +make_key(code=58, names=('F1',)) +make_key(code=59, names=('F2',)) +make_key(code=60, names=('F3',)) +make_key(code=61, names=('F4',)) +make_key(code=62, names=('F5',)) +make_key(code=63, names=('F6',)) +make_key(code=64, names=('F7',)) +make_key(code=65, names=('F8',)) +make_key(code=66, names=('F9',)) +make_key(code=67, names=('F10',)) +make_key(code=68, names=('F10',)) +make_key(code=69, names=('F10',)) +make_key(code=104, names=('F13',)) +make_key(code=105, names=('F14',)) +make_key(code=106, names=('F15',)) +make_key(code=107, names=('F16',)) +make_key(code=108, names=('F17',)) +make_key(code=109, names=('F18',)) +make_key(code=110, names=('F19',)) +make_key(code=111, names=('F20',)) +make_key(code=112, names=('F21',)) +make_key(code=113, names=('F22',)) +make_key(code=114, names=('F23',)) +make_key(code=115, names=('F24',)) +# Lock Keys, Navigation, etc. +make_key(code=57, names=('CAPS_LOCK', 'CAPSLOCK', 'CLCK', 'CAPS')) +# FIXME: Investigate whether this key actually works, and +# uncomment when/if it does. +# make_key(code=130, names=('LOCKING_CAPS', 'LCAP')) +make_key(code=70, names=('PRINT_SCREEN', 'PSCREEN', 'PSCR')) +make_key(code=71, names=('SCROLL_LOCK', 'SCROLLLOCK', 'SLCK')) +# FIXME: Investigate whether this key actually works, and +# uncomment when/if it does. +# make_key(code=132, names=('LOCKING_SCROLL', 'LSCRL')) +make_key(code=72, names=('PAUSE', 'PAUS', 'BRK')) +make_key(code=73, names=('INSERT', 'INS')) +make_key(code=74, names=('HOME',)) +make_key(code=75, names=('PGUP',)) +make_key(code=76, names=('DELETE', 'DEL')) +make_key(code=77, names=('END',)) +make_key(code=78, names=('PGDOWN', 'PGDN')) +make_key(code=79, names=('RIGHT', 'RGHT')) +make_key(code=80, names=('LEFT',)) +make_key(code=81, names=('DOWN',)) +make_key(code=82, names=('UP',)) -class International(KeycodeCategory): - KC_INT1 = KC_RO = Keycode(135) - KC_INT2 = KC_KANA = Keycode(136) - KC_INT3 = KC_JYEN = Keycode(137) - KC_INT4 = KC_HENK = Keycode(138) - KC_INT5 = KC_MHEN = Keycode(139) - KC_INT6 = Keycode(140) - KC_INT7 = Keycode(141) - KC_INT8 = Keycode(142) - KC_INT9 = Keycode(143) - KC_LANG1 = KC_HAEN = Keycode(144) - KC_LANG2 = KC_HAEJ = Keycode(145) - KC_LANG3 = Keycode(146) - KC_LANG4 = Keycode(147) - KC_LANG5 = Keycode(148) - KC_LANG6 = Keycode(149) - KC_LANG7 = Keycode(150) - KC_LANG8 = Keycode(151) - KC_LANG9 = Keycode(152) +# Numpad +make_key(code=83, names=('NUM_LOCK', 'NUMLOCK', 'NLCK')) +# FIXME: Investigate whether this key actually works, and +# uncomment when/if it does. +# make_key(code=131, names=('LOCKING_NUM', 'LNUM')) +make_key(code=84, names=('KP_SLASH', 'NUMPAD_SLASH', 'PSLS')) +make_key(code=85, names=('KP_ASTERISK', 'NUMPAD_ASTERISK', 'PAST')) +make_key(code=86, names=('KP_MINUS', 'NUMPAD_MINUS', 'PMNS')) +make_key(code=87, names=('KP_PLUS', 'NUMPAD_PLUS', 'PPLS')) +make_key(code=88, names=('KP_ENTER', 'NUMPAD_ENTER', 'PENT')) +make_key(code=89, names=('KP_1', 'P1', 'NUMPAD_1')) +make_key(code=90, names=('KP_2', 'P2', 'NUMPAD_2')) +make_key(code=91, names=('KP_3', 'P3', 'NUMPAD_3')) +make_key(code=92, names=('KP_4', 'P4', 'NUMPAD_4')) +make_key(code=93, names=('KP_5', 'P5', 'NUMPAD_5')) +make_key(code=94, names=('KP_6', 'P6', 'NUMPAD_6')) +make_key(code=95, names=('KP_7', 'P7', 'NUMPAD_7')) +make_key(code=96, names=('KP_8', 'P8', 'NUMPAD_8')) +make_key(code=97, names=('KP_9', 'P9', 'NUMPAD_9')) +make_key(code=98, names=('KP_0', 'P0', 'NUMPAD_0')) +make_key(code=99, names=('KP_DOT', 'PDOT', 'NUMPAD_DOT')) +make_key(code=103, names=('KP_EQUAL', 'PEQL', 'NUMPAD_EQUAL')) +make_key(code=133, names=('KP_COMMA', 'PCMM', 'NUMPAD_COMMA')) +make_key(code=134, names=('KP_EQUAL_AS400', 'NUMPAD_EQUAL_AS400')) +# Making life better for folks on tiny keyboards especially: exposes +# the "shifted" keys as raw keys. Under the hood we're still +# sending Shift+(whatever key is normally pressed) to get these, so +# for example `KC_AT` will hold shift and press 2. +make_shifted_key('GRAVE', names=('TILDE', 'TILD', '~')) +make_shifted_key('1', names=('EXCLAIM', 'EXLM', '!')) +make_shifted_key('2', names=('AT', '@')) +make_shifted_key('3', names=('HASH', 'POUND', '#')) +make_shifted_key('4', names=('DOLLAR', 'DLR', '$')) +make_shifted_key('5', names=('PERCENT', 'PERC', '%')) +make_shifted_key('6', names=('CIRCUMFLEX', 'CIRC', '^')) +make_shifted_key('7', names=('AMPERSAND', 'AMPR', '&')) +make_shifted_key('8', names=('ASTERISK', 'ASTR', '*')) +make_shifted_key('9', names=('LEFT_PAREN', 'LPRN', '(')) +make_shifted_key('0', names=('RIGHT_PAREN', 'RPRN', ')')) +make_shifted_key('MINUS', names=('UNDERSCORE', 'UNDS', '_')) +make_shifted_key('EQUAL', names=('PLUS', '+')) +make_shifted_key('LBRACKET', names=('LEFT_CURLY_BRACE', 'LCBR', '{')) +make_shifted_key('RBRACKET', names=('RIGHT_CURLY_BRACE', 'RCBR', '}')) +make_shifted_key('BACKSLASH', names=('PIPE', '|')) +make_shifted_key('SEMICOLON', names=('COLON', 'COLN', ':')) +make_shifted_key('QUOTE', names=('DOUBLE_QUOTE', 'DQUO', 'DQT', '"')) +make_shifted_key('COMMA', names=('LEFT_ANGLE_BRACKET', 'LABK', '<')) +make_shifted_key('DOT', names=('RIGHT_ANGLE_BRACKET', 'RABK', '>')) +make_shifted_key('SLSH', names=('QUESTION', 'QUES', '?')) -class Misc(KeycodeCategory): - KC_APPLICATION = KC_APP = ConsumerKeycode(101) - KC_POWER = ConsumerKeycode(102) - KC_EXECUTE = KC_EXEC = ConsumerKeycode(116) - KC_SYSTEM_POWER = KC_PWR = ConsumerKeycode(165) - KC_SYSTEM_SLEEP = KC_SLEP = ConsumerKeycode(166) - KC_SYSTEM_WAKE = KC_WAKE = ConsumerKeycode(167) - KC_HELP = ConsumerKeycode(117) - KC_MENU = ConsumerKeycode(118) - KC_SELECT = KC_SLCT = ConsumerKeycode(119) - KC_STOP = ConsumerKeycode(120) - KC_AGAIN = KC_AGIN = ConsumerKeycode(121) - KC_UNDO = ConsumerKeycode(122) - KC_CUT = ConsumerKeycode(123) - KC_COPY = ConsumerKeycode(124) - KC_PASTE = KC_PSTE = ConsumerKeycode(125) - KC_FIND = ConsumerKeycode(126) - KC_ALT_ERASE = KC_ERAS = ConsumerKeycode(153) - KC_SYSREQ = ConsumerKeycode(154) - KC_CANCEL = ConsumerKeycode(155) - KC_CLEAR = KC_CLR = ConsumerKeycode(156) - KC_PRIOR = ConsumerKeycode(157) - KC_RETURN = ConsumerKeycode(158) - KC_SEPERATOR = ConsumerKeycode(159) - KC_OUT = ConsumerKeycode(160) - KC_OPER = ConsumerKeycode(161) - KC_CLEAR_AGAIN = ConsumerKeycode(162) - KC_CRSEL = ConsumerKeycode(163) - KC_EXSEL = ConsumerKeycode(164) - KC_MAIL = ConsumerKeycode(177) - KC_CALCULATOR = KC_CALC = ConsumerKeycode(178) - KC_MY_COMPUTER = KC_MYCM = ConsumerKeycode(179) - KC_WWW_SEARCH = KC_WSCH = ConsumerKeycode(180) - KC_WWW_HOME = KC_WHOM = ConsumerKeycode(181) - KC_WWW_BACK = KC_WBAK = ConsumerKeycode(182) - KC_WWW_FORWARD = KC_WFWD = ConsumerKeycode(183) - KC_WWW_STOP = KC_WSTP = ConsumerKeycode(184) - KC_WWW_REFRESH = KC_WREF = ConsumerKeycode(185) - KC_WWW_FAVORITES = KC_WFAV = ConsumerKeycode(186) +# International +make_key(code=50, names=('NONUS_HASH', 'NUHS')) +make_key(code=100, names=('NONUS_BSLASH', 'NUBS')) +make_key(code=135, names=('INT1', 'RO')) +make_key(code=136, names=('INT2', 'KANA')) +make_key(code=137, names=('INT3', 'JYEN')) +make_key(code=138, names=('INT4', 'HENK')) +make_key(code=139, names=('INT5', 'MHEN')) +make_key(code=140, names=('INT6',)) +make_key(code=141, names=('INT7',)) +make_key(code=142, names=('INT8',)) +make_key(code=143, names=('INT9',)) +make_key(code=144, names=('LANG1', 'HAEN')) +make_key(code=145, names=('LANG2', 'HAEJ')) +make_key(code=146, names=('LANG3',)) +make_key(code=147, names=('LANG4',)) +make_key(code=148, names=('LANG5',)) +make_key(code=149, names=('LANG6',)) +make_key(code=150, names=('LANG7',)) +make_key(code=151, names=('LANG8',)) +make_key(code=152, names=('LANG9',)) -class Media(KeycodeCategory): - # I believe QMK used these double-underscore codes for MacOS - # support or something. I have no idea, but modern MacOS supports - # PC volume keys so I really don't care that these codes are the - # same as below. If bugs arise, these codes may need to change. - KC__MUTE = ConsumerKeycode(226) - KC__VOLUP = ConsumerKeycode(233) - KC__VOLDOWN = ConsumerKeycode(234) +# Consumer ("media") keys. Most known keys aren't supported here. A much +# longer list used to exist in this file, but the codes were almost certainly +# incorrect, conflicting with each other, or otherwise "weird". We'll add them +# back in piecemeal as needed. PRs welcome. +# +# A super useful reference for these is http://www.freebsddiary.org/APC/usb_hid_usages.php +# Note that currently we only have the PC codes. Recent MacOS versions seem to +# support PC media keys, so I don't know how much value we would get out of +# adding the old Apple-specific consumer codes, but again, PRs welcome if the +# lack of them impacts you. +make_consumer_key(code=226, names=('AUDIO_MUTE', 'MUTE')) # 0xE2 +make_consumer_key(code=233, names=('AUDIO_VOL_UP', 'VOLU')) # 0xE9 +make_consumer_key(code=234, names=('AUDIO_VOL_DOWN', 'VOLD')) # 0xEA +make_consumer_key(code=181, names=('MEDIA_NEXT_TRACK', 'MNXT')) # 0xB5 +make_consumer_key(code=182, names=('MEDIA_PREV_TRACK', 'MPRV')) # 0xB6 +make_consumer_key(code=183, names=('MEDIA_STOP', 'MSTP')) # 0xB7 +make_consumer_key(code=205, names=('MEDIA_PLAY_PAUSE', 'MPLY')) # 0xCD (this may not be right) +make_consumer_key(code=184, names=('MEDIA_EJECT', 'EJCT')) # 0xB8 +make_consumer_key(code=179, names=('MEDIA_FAST_FORWARD', 'MFFD')) # 0xB3 +make_consumer_key(code=180, names=('MEDIA_REWIND', 'MRWD')) # 0xB4 - KC_AUDIO_MUTE = KC_MUTE = ConsumerKeycode(226) # 0xE2 - KC_AUDIO_VOL_UP = KC_VOLU = ConsumerKeycode(233) # 0xE9 - KC_AUDIO_VOL_DOWN = KC_VOLD = ConsumerKeycode(234) # 0xEA - KC_MEDIA_NEXT_TRACK = KC_MNXT = ConsumerKeycode(181) # 0xB5 - KC_MEDIA_PREV_TRACK = KC_MPRV = ConsumerKeycode(182) # 0xB6 - KC_MEDIA_STOP = KC_MSTP = ConsumerKeycode(183) # 0xB7 - KC_MEDIA_PLAY_PAUSE = KC_MPLY = ConsumerKeycode(205) # 0xCD (this may not be right) - KC_MEDIA_EJECT = KC_EJCT = ConsumerKeycode(184) # 0xB8 - KC_MEDIA_FAST_FORWARD = KC_MFFD = ConsumerKeycode(179) # 0xB3 - KC_MEDIA_REWIND = KC_MRWD = ConsumerKeycode(180) # 0xB4 +# Internal, diagnostic, or auxiliary/enhanced keys - -class KMK(KeycodeCategory): - KC_RESET = Keycode(1000) - KC_DEBUG = Keycode(1001) - KC_GESC = Keycode(1002) - KC_LSPO = Keycode(1003) - KC_RSPC = Keycode(1004) - KC_LEAD = Keycode(1005) - KC_LOCK = Keycode(1006) - KC_NO = Keycode(1107) - KC_TRANSPARENT = KC_TRNS = Keycode(1108) - KC_DEBUG = KC_DBG = Keycode(1112) - - @staticmethod - def KC_UC_MODE(mode): - ''' - Set any Unicode Mode at runtime (allows the same keymap's unicode - sequences to work across all supported platforms) - ''' - return UnicodeModeKeycode.from_mode_const(mode) - - KC_UC_MODE_NOOP = KC_UC_DISABLE = UnicodeModeKeycode.from_mode_const(UnicodeMode.NOOP) - KC_UC_MODE_LINUX = KC_UC_MODE_IBUS = UnicodeModeKeycode.from_mode_const(UnicodeMode.IBUS) - KC_UC_MODE_MACOS = KC_UC_MODE_OSX = KC_UC_MODE_RALT = UnicodeModeKeycode.from_mode_const( - UnicodeMode.RALT, +# NO and TRNS are functionally identical in how they (don't) mutate +# the state, but are tracked semantically separately, so create +# two keys with the exact same functionality +for names in (('NO',), ('TRANSPARENT', 'TRNS')): + make_key( + names=names, + on_press=handlers.passthrough, + on_release=handlers.passthrough, ) - KC_UC_MODE_WINC = UnicodeModeKeycode.from_mode_const(UnicodeMode.WINC) - @staticmethod - def KC_MACRO_SLEEP_MS(ms): - return MacroSleepKeycode(RawKeycodes.KC_MACRO_SLEEP_MS, ms) +make_key(names=('RESET',), on_press=handlers.reset) +make_key(names=('BOOTLOADER',), on_press=handlers.bootloader) +make_key(names=('DEBUG', 'DBG'), on_press=handlers.debug_pressed, on_release=handlers.passthrough) - @staticmethod - def KC_TAP_DANCE(*args): - return TapDanceKeycode(*args) +make_key(names=('GESC',), on_press=handlers.gesc_pressed, on_release=handlers.gesc_released) +make_key( + names=('LEADER', 'LEAD'), + on_press=handlers.leader_pressed, + on_release=handlers.leader_released, +) -KMK.KC_TD = KMK.KC_TAP_DANCE - - -class Layers(KeycodeCategory): - @staticmethod - def KC_DF(layer): - return LayerKeycode(RawKeycodes.KC_DF, layer, KC.NO) - - @staticmethod - def KC_MO(layer): - return LayerKeycode(RawKeycodes.KC_MO, layer, KC.NO) - - @staticmethod - def KC_LM(layer, kc): - return LayerKeycode(RawKeycodes.KC_LM, layer, kc) - - @staticmethod - def KC_LT(layer, kc): - return LayerKeycode(RawKeycodes.KC_LT, layer, kc) - - @staticmethod - def KC_TG(layer): - return LayerKeycode(RawKeycodes.KC_TG, layer, KC.NO) - - @staticmethod - def KC_TO(layer): - return LayerKeycode(RawKeycodes.KC_TO, layer, KC.NO) - - @staticmethod - def KC_TT(layer): - return LayerKeycode(RawKeycodes.KC_TT, layer, KC.NO) - - -class Keycodes(KeycodeCategory): +def layer_key_validator(layer, kc=None): ''' - 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. + Validates the syntax (but not semantics) of a layer key call. We won't + have access to the keymap here, so we can't verify much of anything useful + here (like whether the target layer actually exists). The spirit of this + existing is mostly that Python will catch extraneous args/kwargs and error + out. ''' - _groupings = [ - 'Modifiers', - 'Common', - 'ShiftedKeycodes', - 'FunctionKeys', - 'NavAndLocks', - 'Numpad', - 'International', - 'Misc', - 'Media', - 'KMK', - 'Layers', - ] - - Modifiers = Modifiers - Common = Common - ShiftedKeycodes = ShiftedKeycodes - FunctionKeys = FunctionKeys - NavAndLocks = NavAndLocks - Numpad = Numpad - International = International - Misc = Misc - Media = Media - KMK = KMK - Layers = Layers + return LayerKeyMeta(layer=layer, kc=kc) -class LazyKC: - def __init__(self): - self.cache = {} - - def __getattr__(self, attr): - if attr in self.cache: - return self.cache[attr] - - for grouping in Keycodes._groupings: - grouping_cls = getattr(Keycodes, grouping) - - try: - found = getattr(grouping_cls, 'KC_{}'.format(attr)) - self.cache[attr] = found - return found - except AttributeError: - continue - - raise AttributeError(attr) +# Layers +make_argumented_key( + validator=layer_key_validator, + names=('MO',), + on_press=layers.mo_pressed, + on_release=layers.mo_released, +) +make_argumented_key( + validator=layer_key_validator, + names=('DF',), + on_press=layers.df_pressed, + on_release=layers.df_released, +) +make_argumented_key( + validator=layer_key_validator, + names=('LM',), + on_press=layers.lm_pressed, + on_release=layers.lm_released, +) +make_argumented_key( + validator=layer_key_validator, + names=('LT',), + on_press=layers.lt_pressed, + on_release=layers.lt_released, +) +make_argumented_key( + validator=layer_key_validator, + names=('TG',), + on_press=layers.tg_pressed, + on_release=layers.tg_released, +) +make_argumented_key( + validator=layer_key_validator, + names=('TO',), + on_press=layers.to_pressed, + on_release=layers.to_released, +) +make_argumented_key( + validator=layer_key_validator, + names=('TT',), + on_press=layers.tt_pressed, + on_release=layers.tt_released, +) -KC = LazyKC() +def key_seq_sleep_validator(ms): + return KeySeqSleepMeta(ms) -char_lookup = { - "\n": Common.KC_ENTER, - "\t": Common.KC_TAB, - ' ': Common.KC_SPACE, - '-': Common.KC_MINUS, - '=': Common.KC_EQUAL, - '[': Common.KC_LBRACKET, - ']': Common.KC_RBRACKET, - "\\": Common.KC_BACKSLASH, - ';': Common.KC_SEMICOLON, - "'": Common.KC_QUOTE, - '`': Common.KC_GRAVE, - ',': Common.KC_COMMA, - '.': Common.KC_DOT, - '~': ShiftedKeycodes.KC_TILDE, - '!': ShiftedKeycodes.KC_EXCLAIM, - '@': ShiftedKeycodes.KC_AT, - '#': ShiftedKeycodes.KC_HASH, - '$': ShiftedKeycodes.KC_DOLLAR, - '%': ShiftedKeycodes.KC_PERCENT, - '^': ShiftedKeycodes.KC_CIRCUMFLEX, - '&': ShiftedKeycodes.KC_AMPERSAND, - '*': ShiftedKeycodes.KC_ASTERISK, - '(': ShiftedKeycodes.KC_LEFT_PAREN, - ')': ShiftedKeycodes.KC_RIGHT_PAREN, - '_': ShiftedKeycodes.KC_UNDERSCORE, - '+': ShiftedKeycodes.KC_PLUS, - '{': ShiftedKeycodes.KC_LEFT_CURLY_BRACE, - '}': ShiftedKeycodes.KC_RIGHT_CURLY_BRACE, - '|': ShiftedKeycodes.KC_PIPE, - ':': ShiftedKeycodes.KC_COLON, - '"': ShiftedKeycodes.KC_DOUBLE_QUOTE, - '<': ShiftedKeycodes.KC_LEFT_ANGLE_BRACKET, - '>': ShiftedKeycodes.KC_RIGHT_ANGLE_BRACKET, - '?': ShiftedKeycodes.KC_QUESTION, -} + +# A dummy key to trigger a sleep_ms call in a sequence of other keys in a +# simple sequence macro. +make_argumented_key( + validator=key_seq_sleep_validator, + names=('MACRO_SLEEP_MS', 'SLEEP_IN_SEQ'), + on_press=handlers.sleep_pressed, +) + + +# Switch unicode modes at runtime +make_key( + names=('UC_MODE_NOOP', 'UC_DISABLE'), + meta=UnicodeModeKeyMeta(UnicodeMode.NOOP), + on_press=handlers.uc_mode_pressed, +) +make_key( + names=('UC_MODE_LINUX', 'UC_MODE_IBUS'), + meta=UnicodeModeKeyMeta(UnicodeMode.IBUS), + on_press=handlers.uc_mode_pressed, +) +make_key( + names=('UC_MODE_MACOS', 'UC_MODE_OSX', 'US_MODE_RALT'), + meta=UnicodeModeKeyMeta(UnicodeMode.RALT), + on_press=handlers.uc_mode_pressed, +) +make_key( + names=('UC_MODE_WINC',), + meta=UnicodeModeKeyMeta(UnicodeMode.WINC), + on_press=handlers.uc_mode_pressed, +) + + +def unicode_mode_key_validator(mode): + return UnicodeModeKeyMeta(mode) + + +make_argumented_key( + validator=unicode_mode_key_validator, + names=('UC_MODE',), + on_press=handlers.uc_mode_pressed, +) + + +# Tap Dance +make_argumented_key( + validator=lambda *codes: TapDanceKeyMeta(codes), + names=('TAP_DANCE', 'TD'), + on_press=handlers.td_pressed, + on_release=handlers.td_released, +) diff --git a/kmk/macros/rotary_encoder.py b/kmk/macros/rotary_encoder.py deleted file mode 100644 index 68abc8e..0000000 --- a/kmk/macros/rotary_encoder.py +++ /dev/null @@ -1,76 +0,0 @@ -import math - -from kmk.event_defs import (hid_report_event, keycode_down_event, - keycode_up_event) -from kmk.keycodes import Media -from kmk.rotary_encoder import RotaryEncoder - -VAL_FALSE = False + 1 -VAL_NONE = True + 2 -VAL_TRUE = True + 1 -VOL_UP_PRESS = keycode_down_event(Media.KC_AUDIO_VOL_UP) -VOL_UP_RELEASE = keycode_up_event(Media.KC_AUDIO_VOL_UP) -VOL_DOWN_PRESS = keycode_down_event(Media.KC_AUDIO_VOL_DOWN) -VOL_DOWN_RELEASE = keycode_up_event(Media.KC_AUDIO_VOL_DOWN) - - -class RotaryEncoderMacro: - def __init__(self, pos_pin, neg_pin, slop_history=1, slop_threshold=1): - self.encoder = RotaryEncoder(pos_pin, neg_pin) - self.max_history = slop_history - self.history = bytearray(slop_history) - self.history_idx = 0 - self.history_threshold = math.floor(slop_threshold * slop_history) - - def scan(self): - # Anti-slop logic - self.history[self.history_idx] = 0 - - reading = self.encoder.direction() - self.history[self.history_idx] = VAL_NONE if reading is None else reading + 1 - - self.history_idx += 1 - - if self.history_idx >= self.max_history: - self.history_idx = 0 - - nones = 0 - trues = 0 - falses = 0 - - for val in self.history: - if val == VAL_NONE: - nones += 1 - elif val == VAL_TRUE: - trues += 1 - elif val == VAL_FALSE: - falses += 1 - - if nones >= self.history_threshold: - return None - - if trues >= self.history_threshold: - return self.on_increase() - - if falses >= self.history_threshold: - return self.on_decrease() - - def on_decrease(self): - pass - - def on_increase(self): - pass - - -class VolumeRotaryEncoder(RotaryEncoderMacro): - def on_decrease(self): - yield VOL_DOWN_PRESS - yield hid_report_event - yield VOL_DOWN_RELEASE - yield hid_report_event - - def on_increase(self): - yield VOL_UP_PRESS - yield hid_report_event - yield VOL_UP_RELEASE - yield hid_report_event diff --git a/kmk/macros/simple.py b/kmk/macros/simple.py deleted file mode 100644 index 2bbf2d0..0000000 --- a/kmk/macros/simple.py +++ /dev/null @@ -1,28 +0,0 @@ -from kmk.keycodes import Keycodes, Macro, char_lookup, lookup_kc_with_cache -from kmk.string import ascii_letters, digits - - -def simple_key_sequence(seq): - def _simple_key_sequence(state): - return seq - - return Macro(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 ascii_letters + digits: - kc = lookup_kc_with_cache(char) - - if char.isupper(): - kc = Keycodes.Modifiers.KC_LSHIFT(kc) - - seq.append(kc) - - return simple_key_sequence(seq) diff --git a/kmk/macros/unicode.py b/kmk/macros/unicode.py index 84507ce..f95f014 100644 --- a/kmk/macros/unicode.py +++ b/kmk/macros/unicode.py @@ -1,16 +1,15 @@ from kmk.consts import UnicodeMode -from kmk.keycodes import (Common, Macro, Modifiers, - generate_codepoint_keysym_seq) +from kmk.keycodes import ALL_KEYS, KC, Macro from kmk.macros.simple import simple_key_sequence from kmk.types import AttrDict from kmk.util import get_wide_ordinal -IBUS_KEY_COMBO = Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U)) -RALT_KEY = Modifiers.KC_RALT -U_KEY = Common.KC_U -ENTER_KEY = Common.KC_ENTER -RALT_DOWN_NO_RELEASE = Modifiers.KC_RALT(no_release=True) -RALT_UP_NO_PRESS = Modifiers.KC_RALT(no_press=True) +IBUS_KEY_COMBO = KC.LCTRL(KC.LSHIFT(KC.U)) +RALT_KEY = KC.RALT +U_KEY = KC.U +ENTER_KEY = KC.ENTER +RALT_DOWN_NO_RELEASE = KC.RALT(no_release=True) +RALT_UP_NO_PRESS = KC.RALT(no_press=True) def compile_unicode_string_sequences(string_table): @@ -31,6 +30,26 @@ def unicode_string_sequence(unistring): ]) +def generate_codepoint_keysym_seq(codepoint, expected_length=4): + # 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 + # + # As a bonus, this function can be pretty useful for + # leader dictionary keys as strings. + seq = [KC.N0 for _ in range(max(len(codepoint), expected_length))] + + for idx, codepoint_fragment in enumerate(reversed(codepoint)): + seq[-(idx + 1)] = ALL_KEYS.get(codepoint_fragment) + + return seq + + def unicode_codepoint_sequence(codepoints): kc_seqs = ( generate_codepoint_keysym_seq(codepoint) diff --git a/kmk/rotary_encoder.py b/kmk/rotary_encoder.py deleted file mode 100644 index 96fe278..0000000 --- a/kmk/rotary_encoder.py +++ /dev/null @@ -1,57 +0,0 @@ -from kmk.pins import PULL_UP - - -class RotaryEncoder: - # Please don't ask. I don't know. All I know is bit_value - # works as expected. Here be dragons, etc. etc. - MIN_VALUE = False + 1 << 1 | True + 1 - MAX_VALUE = True + 1 << 1 | True + 1 - - def __init__(self, pos_pin, neg_pin): - self.pos_pin = pos_pin - self.neg_pin = neg_pin - - self.pos_pin.switch_to_input(pull=PULL_UP) - self.neg_pin.switch_to_input(pull=PULL_UP) - - self.prev_bit_value = self.bit_value() - - def value(self): - return (self.pos_pin.value(), self.neg_pin.value()) - - def bit_value(self): - ''' - Returns 2, 3, 5, or 6 based on the state of the rotary encoder's two - bits. This is a total hack but it does what we need pretty efficiently. - Shrug. - ''' - return self.pos_pin.value() + 1 << 1 | self.neg_pin.value() + 1 - - def direction(self): - ''' - Compares the current rotary position against the last seen position. - - Returns True if we're rotating "positively", False if we're rotating "negatively", - and None if no change could safely be detected for any reason (usually this - means the encoder itself did not change) - ''' - new_value = self.bit_value() - rolling_under = self.prev_bit_value == self.MIN_VALUE and new_value == self.MAX_VALUE - rolling_over = self.prev_bit_value == self.MAX_VALUE and new_value == self.MIN_VALUE - increasing = new_value > self.prev_bit_value - decreasing = new_value < self.prev_bit_value - self.prev_bit_value = new_value - - if rolling_over: - return True - elif rolling_under: - return False - - if increasing: - return True - if decreasing: - return False - - # Either no change, or not a type of change we can safely detect, - # so safely do nothing - return None diff --git a/kmk/types.py b/kmk/types.py index aeacb85..123f806 100644 --- a/kmk/types.py +++ b/kmk/types.py @@ -24,3 +24,29 @@ class Anything: class Passthrough: def __getattr__(self, attr): return Anything(attr) + + +class LayerKeyMeta: + def __init__(self, layer, kc=None): + self.layer = layer + self.kc = kc + + +class KeySequenceMeta: + def __init__(self, seq): + self.seq = seq + + +class KeySeqSleepMeta: + def __init__(self, ms): + self.ms = ms + + +class UnicodeModeKeyMeta: + def __init__(self, mode): + self.mode = mode + + +class TapDanceKeyMeta: + def __init__(self, codes): + self.codes = codes From 57239e3163bcf0ae7644e373042a6896b71fdaa9 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 29 Dec 2018 06:03:31 -0800 Subject: [PATCH 02/15] Everything necessary to get this to boot finally. Planck types! --- Makefile | 14 +++- boot.py | 3 + kmk/firmware.py | 5 ++ kmk/handlers/sequences.py | 6 +- kmk/handlers/stock.py | 13 +-- kmk/keycodes.py | 68 ++++++++++++--- kmk/macros/unicode.py | 4 +- kmk/mcus/circuitpython_samd51.py | 2 + user_keymaps/klardotsh/klarank_featherm4.py | 91 +++++++++++---------- 9 files changed, 138 insertions(+), 68 deletions(-) create mode 100755 boot.py diff --git a/Makefile b/Makefile index 22a01e9..ddc34ed 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ AMPY_DELAY ?= 1.5 ARDUINO ?= /usr/share/arduino PIPENV ?= $(shell which pipenv) -all: copy-kmk copy-keymap +all: copy-kmk copy-bootpy copy-keymap .docker_base: Dockerfile_base @echo "===> Building Docker base image kmkfw/base:${DOCKER_BASE_TAG}" @@ -78,6 +78,18 @@ copy-kmk: echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1 endif +ifdef MOUNTPOINT +$(MOUNTPOINT)/kmk/boot.py: boot.py + @echo "===> Copying required boot.py" + @rsync -rh boot.py $(MOUNTPOINT)/ + @sync + +copy-bootpy: $(MOUNTPOINT)/kmk/boot.py +else +copy-bootpy: + echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1 +endif + ifdef MOUNTPOINT ifndef USER_KEYMAP $(MOUNTPOINT)/main.py: diff --git a/boot.py b/boot.py new file mode 100755 index 0000000..dd14a28 --- /dev/null +++ b/boot.py @@ -0,0 +1,3 @@ +import supervisor + +supervisor.set_next_stack_limit(4096 + 1024) diff --git a/kmk/firmware.py b/kmk/firmware.py index ac8f3b8..a98f23c 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -20,6 +20,11 @@ import collections # isort:skip import kmk.consts # isort:skip import kmk.kmktime # isort:skip import kmk.types # isort:skip +import kmk.util # isort:skip + +# Now handlers that will be used in keycodes later +import kmk.handlers.layers +import kmk.handlers.stock # Now stuff that depends on the above (and so on) import kmk.keycodes # isort:skip diff --git a/kmk/handlers/sequences.py b/kmk/handlers/sequences.py index c218a5f..d015196 100644 --- a/kmk/handlers/sequences.py +++ b/kmk/handlers/sequences.py @@ -1,4 +1,5 @@ -from kmk.keycodes import ALL_KEYS, KC, make_key +from kmk.keycodes import KC, make_key +from kmk.handlers.stock import passthrough from kmk.types import KeySequenceMeta @@ -23,6 +24,7 @@ def simple_key_sequence(seq): return make_key( meta=KeySequenceMeta(seq), on_press=sequence_press_handler, + on_release=passthrough, ) @@ -30,7 +32,7 @@ def send_string(message): seq = [] for char in message: - kc = ALL_KEYS[char] + kc = KC[char] if char.isupper(): kc = KC.LSHIFT(kc) diff --git a/kmk/handlers/stock.py b/kmk/handlers/stock.py index e117dd5..3ad69c4 100644 --- a/kmk/handlers/stock.py +++ b/kmk/handlers/stock.py @@ -7,19 +7,22 @@ def passthrough(key, state, *args, **kwargs): def default_pressed(key, state, KC, coord_int=None, coord_raw=None): + state.hid_pending = True + if coord_int is not None: state.coord_keys_pressed[coord_int] = key - state.add_key(key) + state.keys_pressed.add(key) return state def default_released(key, state, KC, coord_int=None, coord_raw=None): - state.remove_key(key) + state.hid_pending = True + state.keys_pressed.discard(key) if coord_int is not None: - state.keys_pressed.discard(key.coord_keys_pressed.get(coord_int, None)) + state.keys_pressed.discard(state.coord_keys_pressed.get(coord_int, None)) state.coord_keys_pressed[coord_int] = None return state @@ -81,9 +84,9 @@ def leader_pressed(key, state, *args, **kwargs): return state._begin_leader_mode() -def tap_dance_pressed(key, state, *args, **kwargs): +def td_pressed(key, state, *args, **kwargs): return state._process_tap_dance(key, True) -def tap_dance_released(key, state, *args, **kwargs): +def td_released(key, state, *args, **kwargs): return state._process_tap_dance(key, False) diff --git a/kmk/keycodes.py b/kmk/keycodes.py index 2d0d528..3866a55 100644 --- a/kmk/keycodes.py +++ b/kmk/keycodes.py @@ -1,3 +1,5 @@ +import gc + import kmk.handlers.layers as layers import kmk.handlers.stock as handlers from kmk.consts import UnicodeMode @@ -13,8 +15,7 @@ KEYCODE_CONSUMER = 2 # Global state, will be filled in througout this file, and # anywhere the user creates custom keys -ALL_KEYS = {} -KC = AttrDict(ALL_KEYS) +KC = AttrDict() def generate_leader_dictionary_seq(string): @@ -134,11 +135,11 @@ def register_key_names(key, names=tuple()): # NOQA ''' for name in names: - ALL_KEYS[name] = key + KC[name] = key if len(name) == 1: - ALL_KEYS[name.upper()] = key - ALL_KEYS[name.lower()] = key + KC[name.upper()] = key + KC[name.lower()] = key return key @@ -193,7 +194,7 @@ def make_mod_key(*args, **kwargs): def make_shifted_key(target_name, names=tuple()): # NOQA - key = KC.LSFT(ALL_KEYS[target_name]) + key = KC.LSFT(KC[target_name]) register_key_names(key, names) @@ -208,18 +209,29 @@ def make_consumer_key(*args, **kwargs): # is almost certainly the best plan here def make_argumented_key( validator=lambda *validator_args, **validator_kwargs: object(), + names=tuple(), # NOQA *constructor_args, **constructor_kwargs, ): + global NEXT_AVAILABLE_KEYCODE + def _argumented_key(*user_args, **user_kwargs): + global NEXT_AVAILABLE_KEYCODE + meta = validator(*user_args, **user_kwargs) if meta: - return Keycode( + key = Keycode( + NEXT_AVAILABLE_KEYCODE, meta=meta, *constructor_args, **constructor_kwargs, ) + + NEXT_AVAILABLE_KEYCODE += 1 + + return key + else: raise ValueError( 'Argumented key validator failed for unknown reasons. ' @@ -227,6 +239,13 @@ def make_argumented_key( 'should have been raised.', ) + for name in names: + KC[name] = _argumented_key + + return _argumented_key + + +gc.collect() # Modifiers make_mod_key(code=0x01, names=('LEFT_CONTROL', 'LCTRL', 'LCTL')) @@ -242,6 +261,8 @@ make_mod_key(code=0x07, names=('MEH',)) # HYPR = LCTL | LALT | LSFT | LGUI make_mod_key(code=0x0F, names=('HYPER', 'HYPR')) +gc.collect() + # Basic ASCII letters make_key(code=4, names=('A',)) make_key(code=5, names=('B',)) @@ -270,6 +291,8 @@ make_key(code=27, names=('X',)) make_key(code=28, names=('Y',)) make_key(code=29, names=('Z',)) +gc.collect() + # Numbers # Aliases to play nicely with AttrDict, since KC.1 isn't a valid # attribute key in Python, but KC.N1 is @@ -284,6 +307,8 @@ make_key(code=37, names=('8', 'N8')) make_key(code=38, names=('9', 'N9')) make_key(code=39, names=('0', 'N0')) +gc.collect() + # More ASCII standard keys make_key(code=40, names=('ENTER', 'ENT', "\n")) make_key(code=41, names=('ESCAPE', 'ESC')) @@ -302,6 +327,8 @@ make_key(code=54, names=('COMMA', 'COMM', ',')) make_key(code=55, names=('DOT', '.')) make_key(code=56, names=('SLASH', 'SLSH')) +gc.collect() + # Function Keys make_key(code=58, names=('F1',)) make_key(code=59, names=('F2',)) @@ -313,8 +340,8 @@ make_key(code=64, names=('F7',)) make_key(code=65, names=('F8',)) make_key(code=66, names=('F9',)) make_key(code=67, names=('F10',)) -make_key(code=68, names=('F10',)) -make_key(code=69, names=('F10',)) +make_key(code=68, names=('F11',)) +make_key(code=69, names=('F12',)) make_key(code=104, names=('F13',)) make_key(code=105, names=('F14',)) make_key(code=106, names=('F15',)) @@ -328,6 +355,8 @@ make_key(code=113, names=('F22',)) make_key(code=114, names=('F23',)) make_key(code=115, names=('F24',)) +gc.collect() + # Lock Keys, Navigation, etc. make_key(code=57, names=('CAPS_LOCK', 'CAPSLOCK', 'CLCK', 'CAPS')) # FIXME: Investigate whether this key actually works, and @@ -350,6 +379,8 @@ make_key(code=80, names=('LEFT',)) make_key(code=81, names=('DOWN',)) make_key(code=82, names=('UP',)) +gc.collect() + # Numpad make_key(code=83, names=('NUM_LOCK', 'NUMLOCK', 'NLCK')) # FIXME: Investigate whether this key actually works, and @@ -375,6 +406,8 @@ make_key(code=103, names=('KP_EQUAL', 'PEQL', 'NUMPAD_EQUAL')) make_key(code=133, names=('KP_COMMA', 'PCMM', 'NUMPAD_COMMA')) make_key(code=134, names=('KP_EQUAL_AS400', 'NUMPAD_EQUAL_AS400')) +gc.collect() + # Making life better for folks on tiny keyboards especially: exposes # the "shifted" keys as raw keys. Under the hood we're still # sending Shift+(whatever key is normally pressed) to get these, so @@ -401,6 +434,8 @@ make_shifted_key('COMMA', names=('LEFT_ANGLE_BRACKET', 'LABK', '<')) make_shifted_key('DOT', names=('RIGHT_ANGLE_BRACKET', 'RABK', '>')) make_shifted_key('SLSH', names=('QUESTION', 'QUES', '?')) +gc.collect() + # International make_key(code=50, names=('NONUS_HASH', 'NUHS')) make_key(code=100, names=('NONUS_BSLASH', 'NUBS')) @@ -424,6 +459,8 @@ make_key(code=150, names=('LANG7',)) make_key(code=151, names=('LANG8',)) make_key(code=152, names=('LANG9',)) +gc.collect() + # Consumer ("media") keys. Most known keys aren't supported here. A much # longer list used to exist in this file, but the codes were almost certainly # incorrect, conflicting with each other, or otherwise "weird". We'll add them @@ -445,6 +482,8 @@ make_consumer_key(code=184, names=('MEDIA_EJECT', 'EJCT')) # 0xB8 make_consumer_key(code=179, names=('MEDIA_FAST_FORWARD', 'MFFD')) # 0xB3 make_consumer_key(code=180, names=('MEDIA_REWIND', 'MRWD')) # 0xB4 +gc.collect() + # Internal, diagnostic, or auxiliary/enhanced keys # NO and TRNS are functionally identical in how they (don't) mutate @@ -465,7 +504,7 @@ make_key(names=('GESC',), on_press=handlers.gesc_pressed, on_release=handlers.ge make_key( names=('LEADER', 'LEAD'), on_press=handlers.leader_pressed, - on_release=handlers.leader_released, + on_release=handlers.passthrough, ) @@ -491,7 +530,7 @@ make_argumented_key( validator=layer_key_validator, names=('DF',), on_press=layers.df_pressed, - on_release=layers.df_released, + on_release=handlers.passthrough, ) make_argumented_key( validator=layer_key_validator, @@ -509,13 +548,13 @@ make_argumented_key( validator=layer_key_validator, names=('TG',), on_press=layers.tg_pressed, - on_release=layers.tg_released, + on_release=handlers.passthrough, ) make_argumented_key( validator=layer_key_validator, names=('TO',), on_press=layers.to_pressed, - on_release=layers.to_released, + on_release=handlers.passthrough, ) make_argumented_key( validator=layer_key_validator, @@ -525,6 +564,9 @@ make_argumented_key( ) +gc.collect() + + def key_seq_sleep_validator(ms): return KeySeqSleepMeta(ms) diff --git a/kmk/macros/unicode.py b/kmk/macros/unicode.py index f95f014..a670e32 100644 --- a/kmk/macros/unicode.py +++ b/kmk/macros/unicode.py @@ -1,5 +1,5 @@ from kmk.consts import UnicodeMode -from kmk.keycodes import ALL_KEYS, KC, Macro +from kmk.keycodes import KC, Macro from kmk.macros.simple import simple_key_sequence from kmk.types import AttrDict from kmk.util import get_wide_ordinal @@ -45,7 +45,7 @@ def generate_codepoint_keysym_seq(codepoint, expected_length=4): seq = [KC.N0 for _ in range(max(len(codepoint), expected_length))] for idx, codepoint_fragment in enumerate(reversed(codepoint)): - seq[-(idx + 1)] = ALL_KEYS.get(codepoint_fragment) + seq[-(idx + 1)] = KC.get(codepoint_fragment) return seq diff --git a/kmk/mcus/circuitpython_samd51.py b/kmk/mcus/circuitpython_samd51.py index 7957f26..1d6518f 100644 --- a/kmk/mcus/circuitpython_samd51.py +++ b/kmk/mcus/circuitpython_samd51.py @@ -1,6 +1,8 @@ from kmk.firmware import Firmware as _Firmware from kmk.hid import CircuitPythonUSB_HID +import kmk.keycodes + class Firmware(_Firmware): hid_helper = CircuitPythonUSB_HID diff --git a/user_keymaps/klardotsh/klarank_featherm4.py b/user_keymaps/klardotsh/klarank_featherm4.py index ae2f397..8aceb70 100644 --- a/user_keymaps/klardotsh/klarank_featherm4.py +++ b/user_keymaps/klardotsh/klarank_featherm4.py @@ -1,9 +1,10 @@ from kmk.boards.klarank import Firmware from kmk.consts import LeaderMode, UnicodeMode from kmk.keycodes import KC -from kmk.keycodes import generate_leader_dictionary_seq as glds -from kmk.macros.simple import send_string -from kmk.macros.unicode import compile_unicode_string_sequences as cuss + +# from kmk.keycodes import generate_leader_dictionary_seq as glds +# from kmk.macros.simple import send_string +# from kmk.macros.unicode import compile_unicode_string_sequences as cuss keyboard = Firmware() @@ -11,55 +12,55 @@ keyboard.debug_enabled = True keyboard.unicode_mode = UnicodeMode.LINUX keyboard.tap_time = 750 -emoticons = cuss({ - # Emojis - 'BEER': r'🍺', - 'BEER_TOAST': r'🍻', - 'FACE_CUTE_SMILE': r'😊', - 'FACE_HEART_EYES': r'😍', - 'FACE_JOY': r'😂', - 'FACE_SWEAT_SMILE': r'😅', - 'FACE_THINKING': r'🤔', - 'FIRE': r'🔥', - 'FLAG_CA': r'🇨🇦', - 'FLAG_US': r'🇺🇸', - 'HAND_CLAP': r'👏', - 'HAND_HORNS': r'🤘', - 'HAND_OK': r'👌', - 'HAND_THUMB_DOWN': r'👎', - 'HAND_THUMB_UP': r'👍', - 'HAND_WAVE': r'👋', - 'HEART': r'❤️', - 'MAPLE_LEAF': r'🍁', - 'POOP': r'💩', - 'TADA': r'🎉', +# emoticons = cuss({ +# # Emojis +# 'BEER': r'🍺', +# 'BEER_TOAST': r'🍻', +# 'FACE_CUTE_SMILE': r'😊', +# 'FACE_HEART_EYES': r'😍', +# 'FACE_JOY': r'😂', +# 'FACE_SWEAT_SMILE': r'😅', +# 'FACE_THINKING': r'🤔', +# 'FIRE': r'🔥', +# 'FLAG_CA': r'🇨🇦', +# 'FLAG_US': r'🇺🇸', +# 'HAND_CLAP': r'👏', +# 'HAND_HORNS': r'🤘', +# 'HAND_OK': r'👌', +# 'HAND_THUMB_DOWN': r'👎', +# 'HAND_THUMB_UP': r'👍', +# 'HAND_WAVE': r'👋', +# 'HEART': r'❤️', +# 'MAPLE_LEAF': r'🍁', +# 'POOP': r'💩', +# 'TADA': r'🎉', +# +# # Emoticons, but fancier +# 'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻', +# 'CELEBRATORY_GLITTER': r'+。:.゚ヽ(´∀。)ノ゚.:。+゚゚+。:.゚ヽ(*´∀)ノ゚.:。+゚', +# 'SHRUGGIE': r'¯\_(ツ)_/¯', +# 'TABLE_FLIP': r'(╯°□°)╯︵ ┻━┻', +# }) - # Emoticons, but fancier - 'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻', - 'CELEBRATORY_GLITTER': r'+。:.゚ヽ(´∀。)ノ゚.:。+゚゚+。:.゚ヽ(*´∀)ノ゚.:。+゚', - 'SHRUGGIE': r'¯\_(ツ)_/¯', - 'TABLE_FLIP': r'(╯°□°)╯︵ ┻━┻', -}) - -WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.") - -keyboard.leader_mode = LeaderMode.TIMEOUT -keyboard.leader_dictionary = { - glds('hello'): send_string('hello world from kmk macros'), - glds('wpm'): WPM, - glds('atf'): emoticons.ANGRY_TABLE_FLIP, - glds('tf'): emoticons.TABLE_FLIP, - glds('fca'): emoticons.FLAG_CA, - glds('fus'): emoticons.FLAG_US, - glds('cel'): emoticons.CELEBRATORY_GLITTER, -} +# WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.") +# +# keyboard.leader_mode = LeaderMode.TIMEOUT +# keyboard.leader_dictionary = { +# glds('hello'): send_string('hello world from kmk macros'), +# glds('wpm'): WPM, +# glds('atf'): emoticons.ANGRY_TABLE_FLIP, +# glds('tf'): emoticons.TABLE_FLIP, +# glds('fca'): emoticons.FLAG_CA, +# glds('fus'): emoticons.FLAG_US, +# glds('cel'): emoticons.CELEBRATORY_GLITTER, +# } _______ = KC.TRNS xxxxxxx = KC.NO HELLA_TD = KC.TD( KC.A, KC.B, - send_string('macros in a tap dance? I think yes'), + # send_string('macros in a tap dance? I think yes'), KC.TG(1), ) From 8a21b4135defdd93d3ae9df47b4c6ac3668af1b3 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 29 Dec 2018 06:58:08 -0800 Subject: [PATCH 03/15] Restore Unicode Macro support --- kmk/firmware.py | 9 ++ kmk/handlers/sequences.py | 2 - kmk/keycodes.py | 15 ---- kmk/macros/unicode.py | 51 +++++++----- user_keymaps/klardotsh/klarank_featherm4.py | 92 ++++++++++----------- 5 files changed, 87 insertions(+), 82 deletions(-) diff --git a/kmk/firmware.py b/kmk/firmware.py index a98f23c..2e2714b 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -46,6 +46,7 @@ import supervisor from kmk.consts import LeaderMode, UnicodeMode from kmk.hid import USB_HID from kmk.internal_state import InternalState +from kmk.keycodes import KC from kmk.matrix import MatrixScanner @@ -175,6 +176,14 @@ class Firmware: self._hid_helper_inst = self.hid_helper() + # Compile string leader sequences + for k, v in self.leader_dictionary.items(): + if not isinstance(k, tuple): + new_key = tuple(KC[c] for c in k) + self.leader_dictionary[new_key] = v + + del self.leader_dictionary[k] + if self.debug_enabled: print("Firin' lazers. Keyboard is booted.") diff --git a/kmk/handlers/sequences.py b/kmk/handlers/sequences.py index d015196..2eaa371 100644 --- a/kmk/handlers/sequences.py +++ b/kmk/handlers/sequences.py @@ -1,5 +1,4 @@ from kmk.keycodes import KC, make_key -from kmk.handlers.stock import passthrough from kmk.types import KeySequenceMeta @@ -24,7 +23,6 @@ def simple_key_sequence(seq): return make_key( meta=KeySequenceMeta(seq), on_press=sequence_press_handler, - on_release=passthrough, ) diff --git a/kmk/keycodes.py b/kmk/keycodes.py index 3866a55..cda002b 100644 --- a/kmk/keycodes.py +++ b/kmk/keycodes.py @@ -18,18 +18,6 @@ KEYCODE_CONSUMER = 2 KC = AttrDict() -def generate_leader_dictionary_seq(string): - # FIXME move to kmk.macros.unicode or somewhere else more fitting - # left here for backwards compat with various keymaps that - # import this - # - # I have absolutely no idea why it was in this file to begin with, - # probably something related to import order at one point. - from kmk.macros.unicode import generate_codepoint_keysym_seq - - return tuple(generate_codepoint_keysym_seq(string, 1)) - - class Keycode: def __init__( self, @@ -530,7 +518,6 @@ make_argumented_key( validator=layer_key_validator, names=('DF',), on_press=layers.df_pressed, - on_release=handlers.passthrough, ) make_argumented_key( validator=layer_key_validator, @@ -548,13 +535,11 @@ make_argumented_key( validator=layer_key_validator, names=('TG',), on_press=layers.tg_pressed, - on_release=handlers.passthrough, ) make_argumented_key( validator=layer_key_validator, names=('TO',), on_press=layers.to_pressed, - on_release=handlers.passthrough, ) make_argumented_key( validator=layer_key_validator, diff --git a/kmk/macros/unicode.py b/kmk/macros/unicode.py index a670e32..1eac257 100644 --- a/kmk/macros/unicode.py +++ b/kmk/macros/unicode.py @@ -1,15 +1,15 @@ from kmk.consts import UnicodeMode -from kmk.keycodes import KC, Macro -from kmk.macros.simple import simple_key_sequence +from kmk.handlers.sequences import simple_key_sequence +from kmk.keycodes import KC, make_key from kmk.types import AttrDict from kmk.util import get_wide_ordinal -IBUS_KEY_COMBO = KC.LCTRL(KC.LSHIFT(KC.U)) -RALT_KEY = KC.RALT -U_KEY = KC.U -ENTER_KEY = KC.ENTER -RALT_DOWN_NO_RELEASE = KC.RALT(no_release=True) -RALT_UP_NO_PRESS = KC.RALT(no_press=True) +IBUS_KEY_COMBO = simple_key_sequence((KC.LCTRL(KC.LSHIFT(KC.U)),)) +RALT_KEY = simple_key_sequence((KC.RALT,)) +U_KEY = simple_key_sequence((KC.U,)) +ENTER_KEY = simple_key_sequence((KC.ENTER,)) +RALT_DOWN_NO_RELEASE = simple_key_sequence((KC.RALT(no_release=True),)) +RALT_UP_NO_PRESS = simple_key_sequence((KC.RALT(no_press=True),)) def compile_unicode_string_sequences(string_table): @@ -50,6 +50,10 @@ def generate_codepoint_keysym_seq(codepoint, expected_length=4): return seq +def generate_leader_dictionary_seq(string): + return tuple(generate_codepoint_keysym_seq(string, 1)) + + def unicode_codepoint_sequence(codepoints): kc_seqs = ( generate_codepoint_keysym_seq(codepoint) @@ -61,28 +65,37 @@ def unicode_codepoint_sequence(codepoints): for kc_seq in kc_seqs ] - def _unicode_sequence(state): - if state.unicode_mode == UnicodeMode.IBUS: - yield from _ibus_unicode_sequence(kc_macros, state) - elif state.unicode_mode == UnicodeMode.RALT: - yield from _ralt_unicode_sequence(kc_macros, state) - elif state.unicode_mode == UnicodeMode.WINC: - yield from _winc_unicode_sequence(kc_macros, state) + def _unicode_sequence(key, state, *args, **kwargs): + if state.config.unicode_mode == UnicodeMode.IBUS: + state.process_key( + simple_key_sequence(_ibus_unicode_sequence(kc_macros, state)), + True, + ) + elif state.config.unicode_mode == UnicodeMode.RALT: + state.process_key( + simple_key_sequence(_ralt_unicode_sequence(kc_macros, state)), + True, + ) + elif state.config.unicode_mode == UnicodeMode.WINC: + state.process_key( + simple_key_sequence(_winc_unicode_sequence(kc_macros, state)), + True, + ) - return Macro(keydown=_unicode_sequence) + return make_key(on_press=_unicode_sequence) def _ralt_unicode_sequence(kc_macros, state): for kc_macro in kc_macros: yield RALT_DOWN_NO_RELEASE - yield from kc_macro.keydown(state) + yield kc_macro yield RALT_UP_NO_PRESS def _ibus_unicode_sequence(kc_macros, state): for kc_macro in kc_macros: yield IBUS_KEY_COMBO - yield from kc_macro.keydown(state) + yield kc_macro yield ENTER_KEY @@ -96,4 +109,4 @@ def _winc_unicode_sequence(kc_macros, state): for kc_macro in kc_macros: yield RALT_KEY yield U_KEY - yield from kc_macro.keydown(state) + yield kc_macro diff --git a/user_keymaps/klardotsh/klarank_featherm4.py b/user_keymaps/klardotsh/klarank_featherm4.py index 8aceb70..5393759 100644 --- a/user_keymaps/klardotsh/klarank_featherm4.py +++ b/user_keymaps/klardotsh/klarank_featherm4.py @@ -1,10 +1,8 @@ from kmk.boards.klarank import Firmware from kmk.consts import LeaderMode, UnicodeMode +from kmk.handlers.sequences import send_string from kmk.keycodes import KC - -# from kmk.keycodes import generate_leader_dictionary_seq as glds -# from kmk.macros.simple import send_string -# from kmk.macros.unicode import compile_unicode_string_sequences as cuss +from kmk.macros.unicode import compile_unicode_string_sequences as cuss keyboard = Firmware() @@ -12,55 +10,57 @@ keyboard.debug_enabled = True keyboard.unicode_mode = UnicodeMode.LINUX keyboard.tap_time = 750 -# emoticons = cuss({ -# # Emojis -# 'BEER': r'🍺', -# 'BEER_TOAST': r'🍻', -# 'FACE_CUTE_SMILE': r'😊', -# 'FACE_HEART_EYES': r'😍', -# 'FACE_JOY': r'😂', -# 'FACE_SWEAT_SMILE': r'😅', -# 'FACE_THINKING': r'🤔', -# 'FIRE': r'🔥', -# 'FLAG_CA': r'🇨🇦', -# 'FLAG_US': r'🇺🇸', -# 'HAND_CLAP': r'👏', -# 'HAND_HORNS': r'🤘', -# 'HAND_OK': r'👌', -# 'HAND_THUMB_DOWN': r'👎', -# 'HAND_THUMB_UP': r'👍', -# 'HAND_WAVE': r'👋', -# 'HEART': r'❤️', -# 'MAPLE_LEAF': r'🍁', -# 'POOP': r'💩', -# 'TADA': r'🎉', -# -# # Emoticons, but fancier -# 'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻', -# 'CELEBRATORY_GLITTER': r'+。:.゚ヽ(´∀。)ノ゚.:。+゚゚+。:.゚ヽ(*´∀)ノ゚.:。+゚', -# 'SHRUGGIE': r'¯\_(ツ)_/¯', -# 'TABLE_FLIP': r'(╯°□°)╯︵ ┻━┻', -# }) +emoticons = cuss({ + # Emojis + 'BEER': r'🍺', + 'BEER_TOAST': r'🍻', + 'FACE_CUTE_SMILE': r'😊', + 'FACE_HEART_EYES': r'😍', + 'FACE_JOY': r'😂', + 'FACE_SWEAT_SMILE': r'😅', + 'FACE_THINKING': r'🤔', + 'FIRE': r'🔥', + 'FLAG_CA': r'🇨🇦', + 'FLAG_US': r'🇺🇸', + 'HAND_CLAP': r'👏', + 'HAND_HORNS': r'🤘', + 'HAND_OK': r'👌', + 'HAND_THUMB_DOWN': r'👎', + 'HAND_THUMB_UP': r'👍', + 'HAND_WAVE': r'👋', + 'HEART': r'❤️', + 'MAPLE_LEAF': r'🍁', + 'POOP': r'💩', + 'TADA': r'🎉', -# WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.") -# -# keyboard.leader_mode = LeaderMode.TIMEOUT -# keyboard.leader_dictionary = { -# glds('hello'): send_string('hello world from kmk macros'), -# glds('wpm'): WPM, -# glds('atf'): emoticons.ANGRY_TABLE_FLIP, -# glds('tf'): emoticons.TABLE_FLIP, -# glds('fca'): emoticons.FLAG_CA, -# glds('fus'): emoticons.FLAG_US, -# glds('cel'): emoticons.CELEBRATORY_GLITTER, -# } + # Emoticons, but fancier + 'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻', + 'CELEBRATORY_GLITTER': r'+。:.゚ヽ(´∀。)ノ゚.:。+゚゚+。:.゚ヽ(*´∀)ノ゚.:。+゚', + 'SHRUGGIE': r'¯\_(ツ)_/¯', + 'TABLE_FLIP': r'(╯°□°)╯︵ ┻━┻', +}) + +WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.") + +keyboard.leader_mode = LeaderMode.TIMEOUT +keyboard.leader_dictionary = { + 'hello': send_string('hello world from kmk macros'), + 'wpm': WPM, + 'atf': emoticons.ANGRY_TABLE_FLIP, + 'tf': emoticons.TABLE_FLIP, + 'fca': emoticons.FLAG_CA, + 'fus': emoticons.FLAG_US, + 'cel': emoticons.CELEBRATORY_GLITTER, + 'shr': emoticons.SHRUGGIE, + 'poop': emoticons.POOP, +} _______ = KC.TRNS xxxxxxx = KC.NO HELLA_TD = KC.TD( KC.A, KC.B, - # send_string('macros in a tap dance? I think yes'), + send_string('macros in a tap dance? I think yes'), KC.TG(1), ) From e5c8f5587d96a3c5cc96f6d6cd4ac4e093488f67 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 29 Dec 2018 07:09:33 -0800 Subject: [PATCH 04/15] Merge unicode handlers with the others. Prove arbitrary Macros are now doable in userspace --- kmk/handlers/sequences.py | 112 +++++++++++++++++++- kmk/macros/__init__.py | 0 kmk/macros/unicode.py | 112 -------------------- user_keymaps/klardotsh/klarank_featherm4.py | 31 +++++- 4 files changed, 139 insertions(+), 116 deletions(-) delete mode 100644 kmk/macros/__init__.py delete mode 100644 kmk/macros/unicode.py diff --git a/kmk/handlers/sequences.py b/kmk/handlers/sequences.py index 2eaa371..933b7a2 100644 --- a/kmk/handlers/sequences.py +++ b/kmk/handlers/sequences.py @@ -1,5 +1,7 @@ +from kmk.consts import UnicodeMode from kmk.keycodes import KC, make_key -from kmk.types import KeySequenceMeta +from kmk.types import AttrDict, KeySequenceMeta +from kmk.util import get_wide_ordinal def sequence_press_handler(key, state, KC, *args, **kwargs): @@ -38,3 +40,111 @@ def send_string(message): seq.append(kc) return simple_key_sequence(seq) + + +IBUS_KEY_COMBO = simple_key_sequence((KC.LCTRL(KC.LSHIFT(KC.U)),)) +RALT_KEY = simple_key_sequence((KC.RALT,)) +U_KEY = simple_key_sequence((KC.U,)) +ENTER_KEY = simple_key_sequence((KC.ENTER,)) +RALT_DOWN_NO_RELEASE = simple_key_sequence((KC.RALT(no_release=True),)) +RALT_UP_NO_PRESS = simple_key_sequence((KC.RALT(no_press=True),)) + + +def compile_unicode_string_sequences(string_table): + for k, v in string_table.items(): + string_table[k] = unicode_string_sequence(v) + + return AttrDict(string_table) + + +def unicode_string_sequence(unistring): + ''' + Allows sending things like (╯°□°)╯︵ ┻━┻ directly, without + manual conversion to Unicode codepoints. + ''' + return unicode_codepoint_sequence([ + hex(get_wide_ordinal(s))[2:] + for s in unistring + ]) + + +def generate_codepoint_keysym_seq(codepoint, expected_length=4): + # 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 + # + # As a bonus, this function can be pretty useful for + # leader dictionary keys as strings. + seq = [KC.N0 for _ in range(max(len(codepoint), expected_length))] + + for idx, codepoint_fragment in enumerate(reversed(codepoint)): + seq[-(idx + 1)] = KC.get(codepoint_fragment) + + return seq + + +def generate_leader_dictionary_seq(string): + return tuple(generate_codepoint_keysym_seq(string, 1)) + + +def unicode_codepoint_sequence(codepoints): + kc_seqs = ( + generate_codepoint_keysym_seq(codepoint) + for codepoint in codepoints + ) + + kc_macros = [ + simple_key_sequence(kc_seq) + for kc_seq in kc_seqs + ] + + def _unicode_sequence(key, state, *args, **kwargs): + if state.config.unicode_mode == UnicodeMode.IBUS: + state.process_key( + simple_key_sequence(_ibus_unicode_sequence(kc_macros, state)), + True, + ) + elif state.config.unicode_mode == UnicodeMode.RALT: + state.process_key( + simple_key_sequence(_ralt_unicode_sequence(kc_macros, state)), + True, + ) + elif state.config.unicode_mode == UnicodeMode.WINC: + state.process_key( + simple_key_sequence(_winc_unicode_sequence(kc_macros, state)), + True, + ) + + return make_key(on_press=_unicode_sequence) + + +def _ralt_unicode_sequence(kc_macros, state): + for kc_macro in kc_macros: + yield RALT_DOWN_NO_RELEASE + yield kc_macro + yield RALT_UP_NO_PRESS + + +def _ibus_unicode_sequence(kc_macros, state): + for kc_macro in kc_macros: + yield IBUS_KEY_COMBO + yield kc_macro + yield ENTER_KEY + + +def _winc_unicode_sequence(kc_macros, state): + ''' + Send unicode sequence using WinCompose: + + http://wincompose.info/ + https://github.com/SamHocevar/wincompose + ''' + for kc_macro in kc_macros: + yield RALT_KEY + yield U_KEY + yield kc_macro diff --git a/kmk/macros/__init__.py b/kmk/macros/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/kmk/macros/unicode.py b/kmk/macros/unicode.py deleted file mode 100644 index 1eac257..0000000 --- a/kmk/macros/unicode.py +++ /dev/null @@ -1,112 +0,0 @@ -from kmk.consts import UnicodeMode -from kmk.handlers.sequences import simple_key_sequence -from kmk.keycodes import KC, make_key -from kmk.types import AttrDict -from kmk.util import get_wide_ordinal - -IBUS_KEY_COMBO = simple_key_sequence((KC.LCTRL(KC.LSHIFT(KC.U)),)) -RALT_KEY = simple_key_sequence((KC.RALT,)) -U_KEY = simple_key_sequence((KC.U,)) -ENTER_KEY = simple_key_sequence((KC.ENTER,)) -RALT_DOWN_NO_RELEASE = simple_key_sequence((KC.RALT(no_release=True),)) -RALT_UP_NO_PRESS = simple_key_sequence((KC.RALT(no_press=True),)) - - -def compile_unicode_string_sequences(string_table): - for k, v in string_table.items(): - string_table[k] = unicode_string_sequence(v) - - return AttrDict(string_table) - - -def unicode_string_sequence(unistring): - ''' - Allows sending things like (╯°□°)╯︵ ┻━┻ directly, without - manual conversion to Unicode codepoints. - ''' - return unicode_codepoint_sequence([ - hex(get_wide_ordinal(s))[2:] - for s in unistring - ]) - - -def generate_codepoint_keysym_seq(codepoint, expected_length=4): - # 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 - # - # As a bonus, this function can be pretty useful for - # leader dictionary keys as strings. - seq = [KC.N0 for _ in range(max(len(codepoint), expected_length))] - - for idx, codepoint_fragment in enumerate(reversed(codepoint)): - seq[-(idx + 1)] = KC.get(codepoint_fragment) - - return seq - - -def generate_leader_dictionary_seq(string): - return tuple(generate_codepoint_keysym_seq(string, 1)) - - -def unicode_codepoint_sequence(codepoints): - kc_seqs = ( - generate_codepoint_keysym_seq(codepoint) - for codepoint in codepoints - ) - - kc_macros = [ - simple_key_sequence(kc_seq) - for kc_seq in kc_seqs - ] - - def _unicode_sequence(key, state, *args, **kwargs): - if state.config.unicode_mode == UnicodeMode.IBUS: - state.process_key( - simple_key_sequence(_ibus_unicode_sequence(kc_macros, state)), - True, - ) - elif state.config.unicode_mode == UnicodeMode.RALT: - state.process_key( - simple_key_sequence(_ralt_unicode_sequence(kc_macros, state)), - True, - ) - elif state.config.unicode_mode == UnicodeMode.WINC: - state.process_key( - simple_key_sequence(_winc_unicode_sequence(kc_macros, state)), - True, - ) - - return make_key(on_press=_unicode_sequence) - - -def _ralt_unicode_sequence(kc_macros, state): - for kc_macro in kc_macros: - yield RALT_DOWN_NO_RELEASE - yield kc_macro - yield RALT_UP_NO_PRESS - - -def _ibus_unicode_sequence(kc_macros, state): - for kc_macro in kc_macros: - yield IBUS_KEY_COMBO - yield kc_macro - yield ENTER_KEY - - -def _winc_unicode_sequence(kc_macros, state): - ''' - Send unicode sequence using WinCompose: - - http://wincompose.info/ - https://github.com/SamHocevar/wincompose - ''' - for kc_macro in kc_macros: - yield RALT_KEY - yield U_KEY - yield kc_macro diff --git a/user_keymaps/klardotsh/klarank_featherm4.py b/user_keymaps/klardotsh/klarank_featherm4.py index 5393759..c9fbf2c 100644 --- a/user_keymaps/klardotsh/klarank_featherm4.py +++ b/user_keymaps/klardotsh/klarank_featherm4.py @@ -1,8 +1,8 @@ from kmk.boards.klarank import Firmware from kmk.consts import LeaderMode, UnicodeMode +from kmk.handlers.sequences import compile_unicode_string_sequences as cuss from kmk.handlers.sequences import send_string -from kmk.keycodes import KC -from kmk.macros.unicode import compile_unicode_string_sequences as cuss +from kmk.keycodes import KC, make_key keyboard = Firmware() @@ -65,6 +65,31 @@ HELLA_TD = KC.TD( ) +def shrek_is_life(*args, **kwargs): + ''' + Proof macros are a thing and don't actually have to care about the state. At all. + ''' + + print('⢀⡴⠑⡄⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣤⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀') + print('⠸⡇⠀⠿⡀⠀⠀⠀⣀⡴⢿⣿⣿⣿⣿⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠑⢄⣠⠾⠁⣀⣄⡈⠙⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⢀⡀⠁⠀⠀⠈⠙⠛⠂⠈⣿⣿⣿⣿⣿⠿⡿⢿⣆⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⢀⡾⣁⣀⠀⠴⠂⠙⣗⡀⠀⢻⣿⣿⠭⢤⣴⣦⣤⣹⠀⠀⠀⢀⢴⣶⣆') + print('⠀⠀⢀⣾⣿⣿⣿⣷⣮⣽⣾⣿⣥⣴⣿⣿⡿⢂⠔⢚⡿⢿⣿⣦⣴⣾⠁⠸⣼⡿') + print('⠀⢀⡞⠁⠙⠻⠿⠟⠉⠀⠛⢹⣿⣿⣿⣿⣿⣌⢤⣼⣿⣾⣿⡟⠉⠀⠀⠀⠀⠀') + print('⠀⣾⣷⣶⠇⠀⠀⣤⣄⣀⡀⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀') + print('⠀⠉⠈⠉⠀⠀⢦⡈⢻⣿⣿⣿⣶⣶⣶⣶⣤⣽⡹⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠀⠀⠀⠉⠲⣽⡻⢿⣿⣿⣿⣿⣿⣿⣷⣜⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣷⣶⣮⣭⣽⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠀⠀⣀⣀⣈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⠿⠿⠿⠿⠛⠉') + + +SHREK_IS_LOVE = make_key(on_press=shrek_is_life) + + keyboard.keymap = [ [ [KC.GESC, KC.QUOT, KC.COMM, KC.DOT, KC.P, KC.Y, KC.F, KC.G, KC.C, KC.R, KC.L, KC.BSPC], @@ -81,7 +106,7 @@ keyboard.keymap = [ ], [ - [KC.GESC, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.BSLS, KC.LBRC, KC.RBRC, KC.DEL], + [KC.GESC, SHREK_IS_LOVE, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.BSLS, KC.LBRC, KC.RBRC, KC.DEL], [KC.TAB, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.MINS], [KC.LGUI, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.LBRC, xxxxxxx, xxxxxxx, KC.INS], [KC.LCTL, xxxxxxx, _______, _______, xxxxxxx, _______, xxxxxxx, xxxxxxx, KC.HOME, KC.PGDN, KC.PGUP, KC.END], From faa61f7df102ff6a2ce5f3db487bc2503fa9d07c Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 29 Dec 2018 07:27:44 -0800 Subject: [PATCH 05/15] Remove debug leftovers causing failed lints --- kmk/mcus/circuitpython_samd51.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/kmk/mcus/circuitpython_samd51.py b/kmk/mcus/circuitpython_samd51.py index 1d6518f..7957f26 100644 --- a/kmk/mcus/circuitpython_samd51.py +++ b/kmk/mcus/circuitpython_samd51.py @@ -1,8 +1,6 @@ from kmk.firmware import Firmware as _Firmware from kmk.hid import CircuitPythonUSB_HID -import kmk.keycodes - class Firmware(_Firmware): hid_helper = CircuitPythonUSB_HID From 413e8b88068450a3f69818cd3fe20e1e6da73643 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 29 Dec 2018 15:29:11 -0800 Subject: [PATCH 06/15] Rename keycodes to keys, simpler and easier to deal with --- docs/{keycodes.md => keys.md} | 48 ++-------------------- kmk/firmware.py | 6 +-- kmk/handlers/sequences.py | 2 +- kmk/hid.py | 17 ++++---- kmk/internal_state.py | 6 +-- kmk/{keycodes.py => keys.py} | 76 +++++++++++++++++------------------ 6 files changed, 56 insertions(+), 99 deletions(-) rename docs/{keycodes.md => keys.md} (83%) rename kmk/{keycodes.py => keys.py} (92%) diff --git a/docs/keycodes.md b/docs/keys.md similarity index 83% rename from docs/keycodes.md rename to docs/keys.md index 618037e..0708371 100644 --- a/docs/keycodes.md +++ b/docs/keys.md @@ -1,6 +1,6 @@ -# Keycodes Overview +# Keys Overview -## [Basic Keycodes] +## [Basic Keys] |Key |Aliases |Description | |-----------------------|--------------------|-----------------------------------------------| @@ -103,8 +103,6 @@ |`KC.KP_0` |`KC.P0` |Keypad `0` and Insert | |`KC.KP_DOT` |`KC.PDOT` |Keypad `.` and Delete | |`KC.NONUS_BSLASH` |`KC.NUBS` |Non-US `\` and | | -|`KC.APPLICATION` |`KC.APP` |Application (Windows Menu Key) | -|`KC.POWER` | |System Power (macOS) | |`KC.KP_EQUAL` |`KC.PEQL` |Keypad `=` | |`KC.F13` | |F13 | |`KC.F14` | |F14 | @@ -118,20 +116,6 @@ |`KC.F22` | |F22 | |`KC.F23` | |F23 | |`KC.F24` | |F24 | -|`KC.EXECUTE` |`KC.EXEC` |Execute | -|`KC.HELP` | |Help | -|`KC.MENU` | |Menu | -|`KC.SELECT` |`KC.SLCT` |Select | -|`KC.STOP` | |Stop | -|`KC.AGAIN` |`KC.AGIN` |Again | -|`KC.UNDO` | |Undo | -|`KC.CUT` | |Cut | -|`KC.COPY` | |Copy | -|`KC.PASTE` |`KC.PSTE` |Paste | -|`KC.FIND` | |Find | -|`KC._MUTE` | |Mute (macOS) | -|`KC._VOLUP` | |Volume Up (macOS) | -|`KC._VOLDOWN` | |Volume Down (macOS) | |`KC.LOCKING_CAPS` |`KC.LCAP` |Locking Caps Lock | |`KC.LOCKING_NUM` |`KC.LNUM` |Locking Num Lock | |`KC.LOCKING_SCROLL` |`KC.LSCR` |Locking Scroll Lock | @@ -155,18 +139,6 @@ |`KC.LANG7` | |Language 7 | |`KC.LANG8` | |Language 8 | |`KC.LANG9` | |Language 9 | -|`KC.ALT_ERASE` |`KC.ERAS` |Alternate Erase | -|`KC.SYSREQ` | |SysReq/Attention | -|`KC.CANCEL` | |Cancel | -|`KC.CLEAR` |`KC.CLR` |Clear | -|`KC.PRIOR` | |Prior | -|`KC.RETURN` | |Return | -|`KC.SEPARATOR` | |Separator | -|`KC.OUT` | |Out | -|`KC.OPER` | |Oper | -|`KC.CLEAR_AGAIN` | |Clear/Again | -|`KC.CRSEL` | |CrSel/Props | -|`KC.EXSEL` | |ExSel | |`KC.LCTRL` |`KC.LCTL` |Left Control | |`KC.LSHIFT` |`KC.LSFT` |Left Shift | |`KC.LALT` | |Left Alt | @@ -175,9 +147,6 @@ |`KC.RSHIFT` |`KC.RSFT` |Right Shift | |`KC.RALT` | |Right Alt | |`KC.RGUI` |`KC.RCMD`, `KC.RWIN`|Right GUI (Windows/Command/Meta key) | -|`KC.SYSTEM_POWER` |`KC.PWR` |System Power Down | -|`KC.SYSTEM_SLEEP` |`KC.SLEP` |System Sleep | -|`KC.SYSTEM_WAKE` |`KC.WAKE` |System Wake | |`KC.AUDIO_MUTE` |`KC.MUTE` |Mute | |`KC.AUDIO_VOL_UP` |`KC.VOLU` |Volume Up | |`KC.AUDIO_VOL_DOWN` |`KC.VOLD` |Volume Down | @@ -185,18 +154,7 @@ |`KC.MEDIA_PREV_TRACK` |`KC.MPRV` |Previous Track (Windows) | |`KC.MEDIA_STOP` |`KC.MSTP` |Stop Track (Windows) | |`KC.MEDIA_PLAY_PAUSE` |`KC.MPLY` |Play/Pause Track | -|`KC.MEDIA_SELECT` |`KC.MSEL` |Launch Media Player (Windows) | |`KC.MEDIA_EJECT` |`KC.EJCT` |Eject (macOS) | -|`KC.MAIL` | |Launch Mail (Windows) | -|`KC.CALCULATOR` |`KC.CALC` |Launch Calculator (Windows) | -|`KC.MY_COMPUTER` |`KC.MYCM` |Launch My Computer (Windows) | -|`KC.WWW_SEARCH` |`KC.WSCH` |Browser Search (Windows) | -|`KC.WWW_HOME` |`KC.WHOM` |Browser Home (Windows) | -|`KC.WWW_BACK` |`KC.WBAK` |Browser Back (Windows) | -|`KC.WWW_FORWARD` |`KC.WFWD` |Browser Forward (Windows) | -|`KC.WWW_STOP` |`KC.WSTP` |Browser Stop (Windows) | -|`KC.WWW_REFRESH` |`KC.WREF` |Browser Refresh (Windows) | -|`KC.WWW_FAVORITES` |`KC.WFAV` |Browser Favorites (Windows) | |`KC.MEDIA_FAST_FORWARD`|`KC.MFFD` |Next Track (macOS) | |`KC.MEDIA_REWIND` |`KC.MRWD` |Previous Track (macOS) | @@ -228,7 +186,7 @@ |`KC.QUESTION` |`KC.QUES` |`?` | -## [Internal Keycodes] +## [Internal Keys] |Key |Description | |-----------------------|---------------------------------------------------------------------| diff --git a/kmk/firmware.py b/kmk/firmware.py index 2e2714b..e916de9 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -22,12 +22,12 @@ import kmk.kmktime # isort:skip import kmk.types # isort:skip import kmk.util # isort:skip -# Now handlers that will be used in keycodes later +# Now handlers that will be used in keys later import kmk.handlers.layers import kmk.handlers.stock # Now stuff that depends on the above (and so on) -import kmk.keycodes # isort:skip +import kmk.keys # isort:skip import kmk.matrix # isort:skip import kmk.hid # isort:skip @@ -46,7 +46,7 @@ import supervisor from kmk.consts import LeaderMode, UnicodeMode from kmk.hid import USB_HID from kmk.internal_state import InternalState -from kmk.keycodes import KC +from kmk.keys import KC from kmk.matrix import MatrixScanner diff --git a/kmk/handlers/sequences.py b/kmk/handlers/sequences.py index 933b7a2..bbb57ad 100644 --- a/kmk/handlers/sequences.py +++ b/kmk/handlers/sequences.py @@ -1,5 +1,5 @@ from kmk.consts import UnicodeMode -from kmk.keycodes import KC, make_key +from kmk.keys import KC, make_key from kmk.types import AttrDict, KeySequenceMeta from kmk.util import get_wide_ordinal diff --git a/kmk/hid.py b/kmk/hid.py index 4be6ec6..41c5b1b 100644 --- a/kmk/hid.py +++ b/kmk/hid.py @@ -1,6 +1,5 @@ from kmk.consts import HID_REPORT_SIZES, HIDReportTypes, HIDUsage, HIDUsagePage -from kmk.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, ConsumerKeycode, - ModifierKeycode) +from kmk.keys import FIRST_KMK_INTERNAL_KEY, ConsumerKey, ModifierKey class USB_HID: @@ -30,7 +29,7 @@ class USB_HID: consumer_key = None for key in keys_pressed: - if isinstance(key, ConsumerKeycode): + if isinstance(key, ConsumerKey): consumer_key = key break @@ -53,10 +52,10 @@ class USB_HID: self.add_key(consumer_key) else: for key in keys_pressed: - if key.code >= FIRST_KMK_INTERNAL_KEYCODE: + if key.code >= FIRST_KMK_INTERNAL_KEY: continue - if isinstance(key, ModifierKeycode): + if isinstance(key, ModifierKey): self.add_modifier(key) else: self.add_key(key) @@ -93,8 +92,8 @@ class USB_HID: return self def add_modifier(self, modifier): - if isinstance(modifier, ModifierKeycode): - if modifier.code == ModifierKeycode.FAKE_CODE: + if isinstance(modifier, ModifierKey): + if modifier.code == ModifierKey.FAKE_CODE: for mod in modifier.has_modifiers: self.report_mods[0] |= mod else: @@ -105,8 +104,8 @@ class USB_HID: return self def remove_modifier(self, modifier): - if isinstance(modifier, ModifierKeycode): - if modifier.code == ModifierKeycode.FAKE_CODE: + if isinstance(modifier, ModifierKey): + if modifier.code == ModifierKey.FAKE_CODE: for mod in modifier.has_modifiers: self.report_mods[0] ^= mod else: diff --git a/kmk/internal_state.py b/kmk/internal_state.py index 1cf40d6..f264b06 100644 --- a/kmk/internal_state.py +++ b/kmk/internal_state.py @@ -1,5 +1,5 @@ from kmk.consts import LeaderMode -from kmk.keycodes import KC +from kmk.keys import KC from kmk.kmktime import ticks_ms from kmk.types import TapDanceKeyMeta from kmk.util import intify_coordinate @@ -140,7 +140,7 @@ class InternalState: def _process_internal_key_event(self, changed_key, is_pressed): # 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 + # the underlying code rather than comparing Key # objects return self.internal_key_handlers[changed_key.code]( @@ -150,7 +150,7 @@ class InternalState: def _process_tap_dance(self, changed_key, is_pressed): if is_pressed: if not isinstance(changed_key.meta, TapDanceKeyMeta): - # If we get here, changed_key is not a TapDanceKeycode and thus + # If we get here, changed_key is not a TapDanceKey and thus # the user kept typing elsewhere (presumably). End ALL of the # currently outstanding tap dance runs. for k, v in self.tap_dance_counts.items(): diff --git a/kmk/keycodes.py b/kmk/keys.py similarity index 92% rename from kmk/keycodes.py rename to kmk/keys.py index cda002b..b226f38 100644 --- a/kmk/keycodes.py +++ b/kmk/keys.py @@ -6,19 +6,19 @@ from kmk.consts import UnicodeMode from kmk.types import (AttrDict, KeySeqSleepMeta, LayerKeyMeta, TapDanceKeyMeta, UnicodeModeKeyMeta) -FIRST_KMK_INTERNAL_KEYCODE = 1000 -NEXT_AVAILABLE_KEYCODE = 1000 +FIRST_KMK_INTERNAL_KEY = 1000 +NEXT_AVAILABLE_KEY = 1000 -KEYCODE_SIMPLE = 0 -KEYCODE_MODIFIER = 1 -KEYCODE_CONSUMER = 2 +KEY_SIMPLE = 0 +KEY_MODIFIER = 1 +KEY_CONSUMER = 2 # Global state, will be filled in througout this file, and # anywhere the user creates custom keys KC = AttrDict() -class Keycode: +class Key: def __init__( self, code, @@ -43,7 +43,7 @@ class Keycode: if no_press is None and no_release is None: return self - return Keycode( + return Key( code=self.code, has_modifiers=self.has_modifiers, no_press=no_press, @@ -51,7 +51,7 @@ class Keycode: ) def __repr__(self): - return 'Keycode(code={}, has_modifiers={})'.format(self.code, self.has_modifiers) + return 'Key(code={}, has_modifiers={})'.format(self.code, self.has_modifiers) def on_press(self, state, coord_int, coord_raw): return self._on_press(self, state, KC, coord_int, coord_raw) @@ -60,7 +60,7 @@ class Keycode: return self._on_release(self, state, KC, coord_int, coord_raw) -class ModifierKeycode(Keycode): +class ModifierKey(Key): # FIXME this is atrocious to read. Please, please, please, strike down upon # this with great vengeance and furious anger. @@ -71,21 +71,21 @@ class ModifierKeycode(Keycode): return self if modified_code is not None: - if isinstance(modified_code, ModifierKeycode): - new_keycode = ModifierKeycode( - ModifierKeycode.FAKE_CODE, + if isinstance(modified_code, ModifierKey): + new_keycode = ModifierKey( + ModifierKey.FAKE_CODE, set() if self.has_modifiers is None else self.has_modifiers, no_press=no_press, no_release=no_release, ) - if self.code != ModifierKeycode.FAKE_CODE: + if self.code != ModifierKey.FAKE_CODE: new_keycode.has_modifiers.add(self.code) - if modified_code.code != ModifierKeycode.FAKE_CODE: + if modified_code.code != ModifierKey.FAKE_CODE: new_keycode.has_modifiers.add(modified_code.code) else: - new_keycode = Keycode( + new_keycode = Key( modified_code.code, {self.code}, no_press=no_press, @@ -95,7 +95,7 @@ class ModifierKeycode(Keycode): if modified_code.has_modifiers: new_keycode.has_modifiers |= modified_code.has_modifiers else: - new_keycode = Keycode( + new_keycode = Key( self.code, no_press=no_press, no_release=no_release, @@ -104,10 +104,10 @@ class ModifierKeycode(Keycode): return new_keycode def __repr__(self): - return 'ModifierKeycode(code={}, has_modifiers={})'.format(self.code, self.has_modifiers) + return 'ModifierKey(code={}, has_modifiers={})'.format(self.code, self.has_modifiers) -class ConsumerKeycode(Keycode): +class ConsumerKey(Key): pass @@ -135,7 +135,7 @@ def register_key_names(key, names=tuple()): # NOQA def make_key( code=None, names=tuple(), # NOQA - type=KEYCODE_SIMPLE, + type=KEY_SIMPLE, **kwargs, ): ''' @@ -147,28 +147,28 @@ def make_key( See register_key_names() for details on the assignment. - All **kwargs are passed to the Keycode constructor + All **kwargs are passed to the Key constructor ''' - global NEXT_AVAILABLE_KEYCODE + global NEXT_AVAILABLE_KEY - if type == KEYCODE_SIMPLE: - constructor = Keycode - elif type == KEYCODE_MODIFIER: - constructor = ModifierKeycode - elif type == KEYCODE_CONSUMER: - constructor = ConsumerKeycode + if type == KEY_SIMPLE: + constructor = Key + elif type == KEY_MODIFIER: + constructor = ModifierKey + elif type == KEY_CONSUMER: + constructor = ConsumerKey else: raise ValueError('Unrecognized key type') if code is None: - code = NEXT_AVAILABLE_KEYCODE - NEXT_AVAILABLE_KEYCODE += 1 - elif code >= FIRST_KMK_INTERNAL_KEYCODE: + code = NEXT_AVAILABLE_KEY + NEXT_AVAILABLE_KEY += 1 + elif code >= FIRST_KMK_INTERNAL_KEY: # Try to ensure future auto-generated internal keycodes won't # be overridden by continuing to +1 the sequence from the provided # code - NEXT_AVAILABLE_KEYCODE = max(NEXT_AVAILABLE_KEYCODE, code + 1) + NEXT_AVAILABLE_KEY = max(NEXT_AVAILABLE_KEY, code + 1) key = constructor(code=code, **kwargs) @@ -178,7 +178,7 @@ def make_key( def make_mod_key(*args, **kwargs): - return make_key(*args, **kwargs, type=KEYCODE_MODIFIER) + return make_key(*args, **kwargs, type=KEY_MODIFIER) def make_shifted_key(target_name, names=tuple()): # NOQA @@ -190,7 +190,7 @@ def make_shifted_key(target_name, names=tuple()): # NOQA def make_consumer_key(*args, **kwargs): - return make_key(*args, **kwargs, type=KEYCODE_CONSUMER) + return make_key(*args, **kwargs, type=KEY_CONSUMER) # Argumented keys are implicitly internal, so auto-gen of code @@ -201,22 +201,22 @@ def make_argumented_key( *constructor_args, **constructor_kwargs, ): - global NEXT_AVAILABLE_KEYCODE + global NEXT_AVAILABLE_KEY def _argumented_key(*user_args, **user_kwargs): - global NEXT_AVAILABLE_KEYCODE + global NEXT_AVAILABLE_KEY meta = validator(*user_args, **user_kwargs) if meta: - key = Keycode( - NEXT_AVAILABLE_KEYCODE, + key = Key( + NEXT_AVAILABLE_KEY, meta=meta, *constructor_args, **constructor_kwargs, ) - NEXT_AVAILABLE_KEYCODE += 1 + NEXT_AVAILABLE_KEY += 1 return key From 9bed3db9be66e951f133b9dda320b9d97e8ae470 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 29 Dec 2018 15:36:46 -0800 Subject: [PATCH 07/15] Update all other keymaps currently in the system --- user_keymaps/kdb424/klanck.py | 21 ++++++++++---------- user_keymaps/kdb424/levinson_m4.py | 19 +++++++++--------- user_keymaps/kdb424/nyquist_converter.py | 23 +++++++++++----------- user_keymaps/klardotsh/kitsym4_iris.py | 25 ++++++++++++------------ 4 files changed, 43 insertions(+), 45 deletions(-) diff --git a/user_keymaps/kdb424/klanck.py b/user_keymaps/kdb424/klanck.py index c86d623..f5366d0 100644 --- a/user_keymaps/kdb424/klanck.py +++ b/user_keymaps/kdb424/klanck.py @@ -1,8 +1,7 @@ from kmk.consts import DiodeOrientation, UnicodeMode -from kmk.keycodes import KC -from kmk.keycodes import generate_leader_dictionary_seq as glds -from kmk.macros.simple import send_string -from kmk.macros.unicode import compile_unicode_string_sequences +from kmk.handlers.sequences import (compile_unicode_string_sequences, + send_string) +from kmk.keys import KC from kmk.mcus.circuitpython_samd51 import Firmware from kmk.pins import Pin as P from kmk.types import AttrDict @@ -35,13 +34,13 @@ emoticons = compile_unicode_string_sequences({ # ---------------------- Leader Key Macros -------------------------------------------- keyboard.leader_dictionary = { - glds('flip'): emoticons.ANGRY_TABLE_FLIP, - glds('cheer'): emoticons.CHEER, - glds('wat'): emoticons.WAT, - glds('ff'): emoticons.FF, - glds('f'): emoticons.F, - glds('meh'): emoticons.MEH, - glds('yay'): emoticons.YAY, + 'flip': emoticons.ANGRY_TABLE_FLIP, + 'cheer': emoticons.CHEER, + 'wat': emoticons.WAT, + 'ff': emoticons.FF, + 'f': emoticons.F, + 'meh': emoticons.MEH, + 'yay': emoticons.YAY, } WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.") diff --git a/user_keymaps/kdb424/levinson_m4.py b/user_keymaps/kdb424/levinson_m4.py index 2fb97a4..2c9d33e 100644 --- a/user_keymaps/kdb424/levinson_m4.py +++ b/user_keymaps/kdb424/levinson_m4.py @@ -2,9 +2,8 @@ import board import busio from kmk.consts import DiodeOrientation, LeaderMode, UnicodeMode -from kmk.keycodes import KC -from kmk.keycodes import generate_leader_dictionary_seq as glds -from kmk.macros.unicode import compile_unicode_string_sequences +from kmk.handlers.sequences import compile_unicode_string_sequences +from kmk.keys import KC from kmk.mcus.circuitpython_samd51 import Firmware from kmk.pins import Pin as P @@ -42,13 +41,13 @@ emoticons = compile_unicode_string_sequences({ # ---------------------- Leader Key Macros -------------------------------------------- keyboard.leader_dictionary = { - glds('flip'): emoticons.ANGRY_TABLE_FLIP, - glds('cheer'): emoticons.CHEER, - glds('wat'): emoticons.WAT, - glds('ff'): emoticons.FF, - glds('f'): emoticons.F, - glds('meh'): emoticons.MEH, - glds('yay'): emoticons.YAY, + 'flip': emoticons.ANGRY_TABLE_FLIP, + 'cheer': emoticons.CHEER, + 'wat': emoticons.WAT, + 'ff': emoticons.FF, + 'f': emoticons.F, + 'meh': emoticons.MEH, + 'yay': emoticons.YAY, } # ---------------------- Keymap --------------------------------------------------------- diff --git a/user_keymaps/kdb424/nyquist_converter.py b/user_keymaps/kdb424/nyquist_converter.py index fd604d7..d937436 100644 --- a/user_keymaps/kdb424/nyquist_converter.py +++ b/user_keymaps/kdb424/nyquist_converter.py @@ -2,10 +2,9 @@ import board import busio from kmk.consts import DiodeOrientation, LeaderMode, UnicodeMode -from kmk.keycodes import KC -from kmk.keycodes import generate_leader_dictionary_seq as glds -from kmk.macros.simple import simple_key_sequence -from kmk.macros.unicode import compile_unicode_string_sequences +from kmk.handlers.sequences import (compile_unicode_string_sequences, + simple_key_sequence) +from kmk.keys import KC from kmk.mcus.circuitpython_samd51 import Firmware from kmk.pins import Pin as P @@ -43,14 +42,14 @@ emoticons = compile_unicode_string_sequences({ # ---------------------- Leader Key Macros -------------------------------------------- keyboard.leader_dictionary = { - glds('flip'): emoticons.ANGRY_TABLE_FLIP, - glds('cheer'): emoticons.CHEER, - glds('wat'): emoticons.WAT, - glds('ff'): emoticons.FF, - glds('f'): emoticons.F, - glds('meh'): emoticons.MEH, - glds('yay'): emoticons.YAY, - glds('gw'): simple_key_sequence([KC.DF(1)]), + 'flip': emoticons.ANGRY_TABLE_FLIP, + 'cheer': emoticons.CHEER, + 'wat': emoticons.WAT, + 'ff': emoticons.FF, + 'f': emoticons.F, + 'meh': emoticons.MEH, + 'yay': emoticons.YAY, + 'gw': simple_key_sequence([KC.DF(1)]), } _______ = KC.TRNS diff --git a/user_keymaps/klardotsh/kitsym4_iris.py b/user_keymaps/klardotsh/kitsym4_iris.py index 72f91ff..5531b12 100644 --- a/user_keymaps/klardotsh/kitsym4_iris.py +++ b/user_keymaps/klardotsh/kitsym4_iris.py @@ -1,9 +1,8 @@ from kmk.boards.kitsym4_iris import Firmware from kmk.consts import LeaderMode, UnicodeMode -from kmk.keycodes import KC -from kmk.keycodes import generate_leader_dictionary_seq as glds -from kmk.macros.simple import send_string -from kmk.macros.unicode import compile_unicode_string_sequences as cuss +from kmk.handlers.sequences import compile_unicode_string_sequences as cuss +from kmk.handlers.sequences import send_string +from kmk.keys import KC keyboard = Firmware() @@ -43,15 +42,17 @@ emoticons = cuss({ WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.") -keyboard.leader_mode = LeaderMode.TIMEOUT +keyboard.leader_mode = LeaderMode.ENTER keyboard.leader_dictionary = { - glds('hello'): send_string('hello world from kmk macros'), - glds('wpm'): WPM, - glds('atf'): emoticons.ANGRY_TABLE_FLIP, - glds('tf'): emoticons.TABLE_FLIP, - glds('fca'): emoticons.FLAG_CA, - glds('fus'): emoticons.FLAG_US, - glds('cel'): emoticons.CELEBRATORY_GLITTER, + 'hello': send_string('hello world from kmk macros'), + 'wpm': WPM, + 'atf': emoticons.ANGRY_TABLE_FLIP, + 'tf': emoticons.TABLE_FLIP, + 'fca': emoticons.FLAG_CA, + 'fus': emoticons.FLAG_US, + 'cel': emoticons.CELEBRATORY_GLITTER, + 'shr': emoticons.SHRUGGIE, + 'poop': emoticons.POOP, } _______ = KC.TRNS From 375783742f2b8c5108edaee0def94940ad623be0 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 29 Dec 2018 15:42:56 -0800 Subject: [PATCH 08/15] Remove unused kmk.string (originally from micropython-lib) --- LICENSE.md | 6 +----- kmk/string.py | 51 --------------------------------------------------- 2 files changed, 1 insertion(+), 56 deletions(-) delete mode 100644 kmk/string.py diff --git a/LICENSE.md b/LICENSE.md index ad610ae..0849dd6 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,9 +1,5 @@ -Almost all of KMK is licensed under the GPLv3. There are a couple of -exceptions: +Almost all of KMK is licensed under the GPLv3. The only exceptions are: -- `kmk/string.py` is copied directly from - [micropython-lib](https://github.com/micropython/micropython-lib) and is - under the MIT license, copyrighted by the micropython-lib contributors - Hardware schematics are licensed under individual terms per schematic Files/components not listed above or containing its own copyright header in the diff --git a/kmk/string.py b/kmk/string.py deleted file mode 100644 index bab4454..0000000 --- a/kmk/string.py +++ /dev/null @@ -1,51 +0,0 @@ -# This file copied from micropython-lib -# https://github.com/micropython/micropython-lib -# -# The MIT License (MIT) -# -# Copyright (c) 2013, 2014 micropython-lib contributors -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -# Some strings for ctype-style character classification -whitespace = ' \t\n\r\v\f' -ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz' -ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' -ascii_letters = ascii_lowercase + ascii_uppercase -digits = '0123456789' -hexdigits = digits + 'abcdef' + 'ABCDEF' -octdigits = '01234567' -punctuation = """!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~""" -printable = digits + ascii_letters + punctuation + whitespace - - -def translate(s, map): - import io - sb = io.StringIO() - for c in s: - v = ord(c) - if v in map: - v = map[v] - if isinstance(v, int): - sb.write(chr(v)) - elif v is not None: - sb.write(v) - else: - sb.write(c) - return sb.getvalue() From ea63c888d666e2c8b17cbbc4d02fba6585d73229 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 29 Dec 2018 15:57:30 -0800 Subject: [PATCH 09/15] Oops, never fixed MY OWN LAYOUT. So many bugs.... --- user_keymaps/klardotsh/klarank_featherm4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_keymaps/klardotsh/klarank_featherm4.py b/user_keymaps/klardotsh/klarank_featherm4.py index c9fbf2c..4b2122c 100644 --- a/user_keymaps/klardotsh/klarank_featherm4.py +++ b/user_keymaps/klardotsh/klarank_featherm4.py @@ -2,7 +2,7 @@ from kmk.boards.klarank import Firmware from kmk.consts import LeaderMode, UnicodeMode from kmk.handlers.sequences import compile_unicode_string_sequences as cuss from kmk.handlers.sequences import send_string -from kmk.keycodes import KC, make_key +from kmk.keys import KC, make_key keyboard = Firmware() From 0878538f428cdfcc4f7ed362402e9e61049a7d66 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 29 Dec 2018 16:52:06 -0800 Subject: [PATCH 10/15] Resolve issues with leader mode, allow single-key targets (HID or internal) --- kmk/firmware.py | 7 ++++++- kmk/handlers/sequences.py | 2 ++ kmk/internal_state.py | 11 ++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/kmk/firmware.py b/kmk/firmware.py index e916de9..1bfcb54 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -182,7 +182,9 @@ class Firmware: new_key = tuple(KC[c] for c in k) self.leader_dictionary[new_key] = v - del self.leader_dictionary[k] + for k, v in self.leader_dictionary.items(): + if not isinstance(k, tuple): + del self.leader_dictionary[k] if self.debug_enabled: print("Firin' lazers. Keyboard is booted.") @@ -216,6 +218,9 @@ class Firmware: if old_timeouts_len != new_timeouts_len: state_changed = True + if self._state.hid_pending: + self._send_hid() + if self.debug_enabled and state_changed: print('New State: {}'.format(self._state._to_dict())) diff --git a/kmk/handlers/sequences.py b/kmk/handlers/sequences.py index bbb57ad..8f11cbf 100644 --- a/kmk/handlers/sequences.py +++ b/kmk/handlers/sequences.py @@ -1,4 +1,5 @@ from kmk.consts import UnicodeMode +from kmk.handlers.stock import passthrough from kmk.keys import KC, make_key from kmk.types import AttrDict, KeySequenceMeta from kmk.util import get_wide_ordinal @@ -25,6 +26,7 @@ def simple_key_sequence(seq): return make_key( meta=KeySequenceMeta(seq), on_press=sequence_press_handler, + on_release=passthrough, ) diff --git a/kmk/internal_state.py b/kmk/internal_state.py index f264b06..cc692eb 100644 --- a/kmk/internal_state.py +++ b/kmk/internal_state.py @@ -219,11 +219,20 @@ class InternalState: def _handle_leader_sequence(self): lmh = tuple(self.leader_mode_history) + # Will get caught in infinite processing loops if we don't + # exit leader mode before processing the target key + self._exit_leader_mode() if lmh in self.config.leader_dictionary: + # Stack depth exceeded if try to use add_key here with a unicode sequence self.process_key(self.config.leader_dictionary[lmh], True) - return self._exit_leader_mode() + self.set_timeout( + False, + lambda: self.remove_key(self.config.leader_dictionary[lmh]), + ) + + return self def _process_leader_mode(self): keys_pressed = self.keys_pressed From bcdc97a56bdd5fcd466fd6ad8e76f8598c8448c4 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 29 Dec 2018 16:52:44 -0800 Subject: [PATCH 11/15] More keymap updates --- user_keymaps/kdb424/nyquist_converter.py | 2 +- user_keymaps/klardotsh/kitsym4_iris.py | 10 ++++++++-- user_keymaps/klardotsh/klarank_featherm4.py | 2 ++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/user_keymaps/kdb424/nyquist_converter.py b/user_keymaps/kdb424/nyquist_converter.py index d937436..ab77ffb 100644 --- a/user_keymaps/kdb424/nyquist_converter.py +++ b/user_keymaps/kdb424/nyquist_converter.py @@ -49,7 +49,7 @@ keyboard.leader_dictionary = { 'f': emoticons.F, 'meh': emoticons.MEH, 'yay': emoticons.YAY, - 'gw': simple_key_sequence([KC.DF(1)]), + 'gw': KC.DF(1), } _______ = KC.TRNS diff --git a/user_keymaps/klardotsh/kitsym4_iris.py b/user_keymaps/klardotsh/kitsym4_iris.py index 5531b12..dfbb84e 100644 --- a/user_keymaps/klardotsh/kitsym4_iris.py +++ b/user_keymaps/klardotsh/kitsym4_iris.py @@ -1,7 +1,7 @@ from kmk.boards.kitsym4_iris import Firmware from kmk.consts import LeaderMode, UnicodeMode from kmk.handlers.sequences import compile_unicode_string_sequences as cuss -from kmk.handlers.sequences import send_string +from kmk.handlers.sequences import send_string, simple_key_sequence from kmk.keys import KC keyboard = Firmware() @@ -32,6 +32,7 @@ emoticons = cuss({ 'MAPLE_LEAF': r'🍁', 'POOP': r'💩', 'TADA': r'🎉', + 'SHRUG_EMOJI': r'🤷', # Emoticons, but fancier 'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻', @@ -52,7 +53,12 @@ keyboard.leader_dictionary = { 'fus': emoticons.FLAG_US, 'cel': emoticons.CELEBRATORY_GLITTER, 'shr': emoticons.SHRUGGIE, + 'shre': emoticons.SHRUG_EMOJI, 'poop': emoticons.POOP, + 'joy': emoticons.FACE_JOY, + 'ls': KC.LGUI(KC.HOME), # Lock screen + 'cw': KC.LGUI(KC.END), # Close window + 'dbg': KC.DBG, } _______ = KC.TRNS @@ -78,7 +84,7 @@ keyboard.keymap = [ [xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.F7, KC.F8, KC.F9, xxxxxxx, xxxxxxx, KC.EQUAL], [xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.INS, KC.F4, KC.F5, KC.F6, xxxxxxx, xxxxxxx, xxxxxxx], [xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.F1, KC.F2, KC.F3, xxxxxxx, xxxxxxx, _______], - [xxxxxxx, xxxxxxx, xxxxxxx, KC.HOME, KC.END, _______, xxxxxxx, KC.PGUP, KC.PGDN, _______, xxxxxxx, xxxxxxx], + [xxxxxxx, xxxxxxx, KC.LEAD, KC.HOME, KC.END, _______, xxxxxxx, KC.PGUP, KC.PGDN, _______, xxxxxxx, xxxxxxx], ], [ [KC.MUTE, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.LBRC, KC.RBRC, KC.DEL], diff --git a/user_keymaps/klardotsh/klarank_featherm4.py b/user_keymaps/klardotsh/klarank_featherm4.py index 4b2122c..249dfd7 100644 --- a/user_keymaps/klardotsh/klarank_featherm4.py +++ b/user_keymaps/klardotsh/klarank_featherm4.py @@ -53,6 +53,8 @@ keyboard.leader_dictionary = { 'cel': emoticons.CELEBRATORY_GLITTER, 'shr': emoticons.SHRUGGIE, 'poop': emoticons.POOP, + 'ls': KC.LGUI(KC.HOME), + 'dbg': KC.DBG, } _______ = KC.TRNS From 6ddfbadfbff1e27ec1d9821ba7541f2581faa52d Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 14 Jan 2019 11:25:21 -0800 Subject: [PATCH 12/15] Remove unused function and useless comment --- kmk/internal_state.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/kmk/internal_state.py b/kmk/internal_state.py index cc692eb..a3f130f 100644 --- a/kmk/internal_state.py +++ b/kmk/internal_state.py @@ -126,9 +126,7 @@ class InternalState: def tap_key(self, keycode): self.add_key(keycode) - # On the next cycle, we'll remove the key. This is way more clean than - # the `pending_keys` implementation that we used to rely on in - # firmware.py + # On the next cycle, we'll remove the key. self.set_timeout(False, lambda: self.remove_key(keycode)) return self @@ -137,16 +135,6 @@ class InternalState: self.hid_pending = False return self - def _process_internal_key_event(self, changed_key, is_pressed): - # 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 Key - # objects - - return self.internal_key_handlers[changed_key.code]( - changed_key, is_pressed, - ) - def _process_tap_dance(self, changed_key, is_pressed): if is_pressed: if not isinstance(changed_key.meta, TapDanceKeyMeta): From 4ab673ba80360adaeb5ddba1f4150285e11fb925 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Wed, 23 Jan 2019 01:51:31 -0800 Subject: [PATCH 13/15] Resolves #106: Unbreak KC.GESC --- kmk/handlers/stock.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kmk/handlers/stock.py b/kmk/handlers/stock.py index 3ad69c4..b404794 100644 --- a/kmk/handlers/stock.py +++ b/kmk/handlers/stock.py @@ -51,8 +51,13 @@ def gesc_pressed(key, state, KC, *args, **kwargs): GESC_TRIGGERS = {KC.LSHIFT, KC.RSHIFT, KC.LGUI, KC.RGUI} if GESC_TRIGGERS.intersection(state.keys_pressed): + # First, release GUI if already pressed + state.keys_pressed.discard(KC.LGUI) + state.keys_pressed.discard(KC.RGUI) + state.config._send_hid() # if Shift is held, KC_GRAVE will become KC_TILDE on OS level state.keys_pressed.add(KC.GRAVE) + state.hid_pending = True return state # else return KC_ESC From 848afb78019c1e3c7197facc1b95fb139ee035e6 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 18 Feb 2019 15:08:07 -0800 Subject: [PATCH 14/15] Allow pre/post on_press/on_release handlers to be attached to all keys (example provided) --- kmk/internal_state.py | 4 +- kmk/keys.py | 141 +++++++++++++++++++- user_keymaps/klardotsh/klarank_featherm4.py | 10 +- 3 files changed, 146 insertions(+), 9 deletions(-) diff --git a/kmk/internal_state.py b/kmk/internal_state.py index a3f130f..b27454b 100644 --- a/kmk/internal_state.py +++ b/kmk/internal_state.py @@ -107,9 +107,9 @@ class InternalState: self._process_tap_dance(key, is_pressed) else: if is_pressed: - key.on_press(self, coord_int, coord_raw) + key._on_press(self, coord_int, coord_raw) else: - key.on_release(self, coord_int, coord_raw) + key._on_release(self, coord_int, coord_raw) if self.config.leader_mode % 2 == 1: self._process_leader_mode() diff --git a/kmk/keys.py b/kmk/keys.py index b226f38..544c5be 100644 --- a/kmk/keys.py +++ b/kmk/keys.py @@ -35,8 +35,12 @@ class Key: self.no_press = bool(no_press) self.no_release = bool(no_press) - self._on_press = on_press - self._on_release = on_release + self._pre_press_handlers = [] + self._post_press_handlers = [] + self._pre_release_handlers = [] + self._post_release_handlers = [] + self._handle_press = on_press + self._handle_release = on_release self.meta = meta def __call__(self, no_press=None, no_release=None): @@ -53,11 +57,136 @@ class Key: def __repr__(self): return 'Key(code={}, has_modifiers={})'.format(self.code, self.has_modifiers) - def on_press(self, state, coord_int, coord_raw): - return self._on_press(self, state, KC, coord_int, coord_raw) + def _on_press(self, state, coord_int, coord_raw): + for fn in self._pre_press_handlers: + fn(self, state, KC, coord_int, coord_raw) - def on_release(self, state, coord_int, coord_raw): - return self._on_release(self, state, KC, coord_int, coord_raw) + ret = self._handle_press(self, state, KC, coord_int, coord_raw) + + for fn in self._post_press_handlers: + fn(self, state, KC, coord_int, coord_raw) + + return ret + + def _on_release(self, state, coord_int, coord_raw): + for fn in self._pre_release_handlers: + fn(self, state, KC, coord_int, coord_raw) + + ret = self._handle_release(self, state, KC, coord_int, coord_raw) + + for fn in self._post_release_handlers: + fn(self, state, KC, coord_int, coord_raw) + + return ret + + def clone(self): + ''' + Return a shallow clone of the current key without any pre/post press/release + handlers attached. Almost exclusively useful for creating non-colliding keys + to use such handlers. + ''' + + return type(self)( + code=self.code, + has_modifiers=self.has_modifiers, + no_press=self.no_press, + no_release=self.no_release, + on_press=self._handle_press, + on_release=self._handle_release, + meta=self.meta, + ) + + def before_press_handler(self, fn): + ''' + Attach a callback to be run prior to the on_press handler for this key. + Receives the following: + + - self (this Key instance) + - state (the current InternalState) + - KC (the global KC lookup table, for convenience) + - coord_int (an internal integer representation of the matrix coordinate + for the pressed key - this is likely not useful to end users, but is + provided for consistency with the internal handlers) + - coord_raw (an X,Y tuple of the matrix coordinate - also likely not useful) + + The return value of the provided callback is discarded. Exceptions are _not_ + caught, and will likely crash KMK if not handled within your function. + + These handlers are run in attachment order: handlers provided by earlier + calls of this method will be executed before those provided by later calls. + ''' + + self._pre_press_handlers.append(fn) + return self + + def after_press_handler(self, fn): + ''' + Attach a callback to be run after the on_release handler for this key. + Receives the following: + + - self (this Key instance) + - state (the current InternalState) + - KC (the global KC lookup table, for convenience) + - coord_int (an internal integer representation of the matrix coordinate + for the pressed key - this is likely not useful to end users, but is + provided for consistency with the internal handlers) + - coord_raw (an X,Y tuple of the matrix coordinate - also likely not useful) + + The return value of the provided callback is discarded. Exceptions are _not_ + caught, and will likely crash KMK if not handled within your function. + + These handlers are run in attachment order: handlers provided by earlier + calls of this method will be executed before those provided by later calls. + ''' + + self._post_press_handlers.append(fn) + return self + + def before_release_handler(self, fn): + ''' + Attach a callback to be run prior to the on_release handler for this + key. Receives the following: + + - self (this Key instance) + - state (the current InternalState) + - KC (the global KC lookup table, for convenience) + - coord_int (an internal integer representation of the matrix coordinate + for the pressed key - this is likely not useful to end users, but is + provided for consistency with the internal handlers) + - coord_raw (an X,Y tuple of the matrix coordinate - also likely not useful) + + The return value of the provided callback is discarded. Exceptions are _not_ + caught, and will likely crash KMK if not handled within your function. + + These handlers are run in attachment order: handlers provided by earlier + calls of this method will be executed before those provided by later calls. + ''' + + self._pre_release_handlers.append(fn) + return self + + def after_release_handler(self, fn): + ''' + Attach a callback to be run after the on_release handler for this key. + Receives the following: + + - self (this Key instance) + - state (the current InternalState) + - KC (the global KC lookup table, for convenience) + - coord_int (an internal integer representation of the matrix coordinate + for the pressed key - this is likely not useful to end users, but is + provided for consistency with the internal handlers) + - coord_raw (an X,Y tuple of the matrix coordinate - also likely not useful) + + The return value of the provided callback is discarded. Exceptions are _not_ + caught, and will likely crash KMK if not handled within your function. + + These handlers are run in attachment order: handlers provided by earlier + calls of this method will be executed before those provided by later calls. + ''' + + self._post_release_handlers.append(fn) + return self class ModifierKey(Key): diff --git a/user_keymaps/klardotsh/klarank_featherm4.py b/user_keymaps/klardotsh/klarank_featherm4.py index 249dfd7..5abb66b 100644 --- a/user_keymaps/klardotsh/klarank_featherm4.py +++ b/user_keymaps/klardotsh/klarank_featherm4.py @@ -89,8 +89,16 @@ def shrek_is_life(*args, **kwargs): print('⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⠿⠿⠿⠿⠛⠉') +# Spew shrek when hitting a fully custom key +# This won't modify internal state at all SHREK_IS_LOVE = make_key(on_press=shrek_is_life) +# Also spew shrek every time I try to use Alt. It's a dev board, after all. +KC.LALT.before_press_handler(shrek_is_life) + +# But also give me a normal alt if I want it. Shrek isn't ALWAYS life. +BORING_ALT = KC.LALT.clone() + keyboard.keymap = [ [ @@ -117,7 +125,7 @@ keyboard.keymap = [ [ [KC.GRV, KC.EXLM, KC.AT, KC.HASH, KC.DLR, KC.PERC, KC.CIRC, KC.AMPR, KC.ASTR, KC.LPRN, KC.RPRN, KC.SLSH], [KC.TAB, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.MINS], - [KC.LGUI, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx], + [KC.LGUI, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, BORING_ALT], [KC.LCTL, KC.DBG, HELLA_TD, xxxxxxx, _______, _______, xxxxxxx, xxxxxxx, KC.MUTE, KC.VOLD, KC.VOLU, xxxxxxx], ], ] From d4f4872b0006e516b5fa82f0b7e37ef7f1bdcc24 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Mon, 18 Feb 2019 16:28:50 -0800 Subject: [PATCH 15/15] So much documentation --- docs/keycodes.md | 251 +++++++++++++++++++++++++++++ docs/keys.md | 397 ++++++++++++++++++---------------------------- docs/sequences.md | 112 +++++++++++++ docs/unicode.md | 70 -------- 4 files changed, 515 insertions(+), 315 deletions(-) create mode 100644 docs/keycodes.md create mode 100644 docs/sequences.md delete mode 100644 docs/unicode.md diff --git a/docs/keycodes.md b/docs/keycodes.md new file mode 100644 index 0000000..0708371 --- /dev/null +++ b/docs/keycodes.md @@ -0,0 +1,251 @@ +# Keys Overview + +## [Basic Keys] + +|Key |Aliases |Description | +|-----------------------|--------------------|-----------------------------------------------| +|`KC.NO` | |Ignore this key (NOOP) | +|`KC.TRANSPARENT` |`KC.TRNS` |Use the next lowest non-transparent key | +|`KC.A` | |`a` and `A` | +|`KC.B` | |`b` and `B` | +|`KC.C` | |`c` and `C` | +|`KC.D` | |`d` and `D` | +|`KC.E` | |`e` and `E` | +|`KC.F` | |`f` and `F` | +|`KC.G` | |`g` and `G` | +|`KC.H` | |`h` and `H` | +|`KC.I` | |`i` and `I` | +|`KC.J` | |`j` and `J` | +|`KC.K` | |`k` and `K` | +|`KC.L` | |`l` and `L` | +|`KC.M` | |`m` and `M` | +|`KC.N` | |`n` and `N` | +|`KC.O` | |`o` and `O` | +|`KC.P` | |`p` and `P` | +|`KC.Q` | |`q` and `Q` | +|`KC.R` | |`r` and `R` | +|`KC.S` | |`s` and `S` | +|`KC.T` | |`t` and `T` | +|`KC.U` | |`u` and `U` | +|`KC.V` | |`v` and `V` | +|`KC.W` | |`w` and `W` | +|`KC.X` | |`x` and `X` | +|`KC.Y` | |`y` and `Y` | +|`KC.Z` | |`z` and `Z` | +|`KC.N1` | |`1` and `!` | +|`KC.N2` | |`2` and `@` | +|`KC.N3` | |`3` and `#` | +|`KC.N4` | |`4` and `$` | +|`KC.N5` | |`5` and `%` | +|`KC.N6` | |`6` and `^` | +|`KC.N7` | |`7` and `&` | +|`KC.N8` | |`8` and `*` | +|`KC.N9` | |`9` and `(` | +|`KC.N0` | |`0` and `)` | +|`KC.ENTER` |`KC.ENT` |Return (Enter) | +|`KC.ESCAPE` |`KC.ESC` |Escape | +|`KC.BSPACE` |`KC.BSPC` |Delete (Backspace) | +|`KC.TAB` | |Tab | +|`KC.SPACE` |`KC.SPC` |Spacebar | +|`KC.MINUS` |`KC.MINS` |`-` and `_` | +|`KC.EQUAL` |`KC.EQL` |`=` and `+` | +|`KC.LBRACKET` |`KC.LBRC` |`[` and `{` | +|`KC.RBRACKET` |`KC.RBRC` |`]` and `}` | +|`KC.BSLASH` |`KC.BSLS` |`\` and | | +|`KC.NONUS_HASH` |`KC.NUHS` |Non-US `#` and `~` | +|`KC.SCOLON` |`KC.SCLN` |`;` and `:` | +|`KC.QUOTE` |`KC.QUOT` |`'` and `"` | +|`KC.GRAVE` |`KC.GRV`, `KC.ZKHK` |` and `~`, JIS Zenkaku/Hankaku| +|`KC.COMMA` |`KC.COMM` |`,` and `<` | +|`KC.DOT` | |`.` and `>` | +|`KC.SLASH` |`KC.SLSH` |`/` and `?` | +|`KC.CAPSLOCK` |`KC.CLCK`, `KC.CAPS`|Caps Lock | +|`KC.F1` | |F1 | +|`KC.F2` | |F2 | +|`KC.F3` | |F3 | +|`KC.F4` | |F4 | +|`KC.F5` | |F5 | +|`KC.F6` | |F6 | +|`KC.F7` | |F7 | +|`KC.F8` | |F8 | +|`KC.F9` | |F9 | +|`KC.F10` | |F10 | +|`KC.F11` | |F11 | +|`KC.F12` | |F12 | +|`KC.PSCREEN` |`KC.PSCR` |Print Screen | +|`KC.SCROLLLOCK` |`KC.SLCK` |Scroll Lock | +|`KC.PAUSE` |`KC.PAUS`, `KC.BRK` |Pause | +|`KC.INSERT` |`KC.INS` |Insert | +|`KC.HOME` | |Home | +|`KC.PGUP` | |Page Up | +|`KC.DELETE` |`KC.DEL` |Forward Delete | +|`KC.END` | |End | +|`KC.PGDOWN` |`KC.PGDN` |Page Down | +|`KC.RIGHT` |`KC.RGHT` |Right Arrow | +|`KC.LEFT` | |Left Arrow | +|`KC.DOWN` | |Down Arrow | +|`KC.UP` | |Up Arrow | +|`KC.NUMLOCK` |`KC.NLCK` |Keypad Num Lock and Clear | +|`KC.KP_SLASH` |`KC.PSLS` |Keypad `/` | +|`KC.KP_ASTERISK` |`KC.PAST` |Keypad `*` | +|`KC.KP_MINUS` |`KC.PMNS` |Keypad `-` | +|`KC.KP_PLUS` |`KC.PPLS` |Keypad `+` | +|`KC.KP_ENTER` |`KC.PENT` |Keypad Enter | +|`KC.KP_1` |`KC.P1` |Keypad `1` and End | +|`KC.KP_2` |`KC.P2` |Keypad `2` and Down Arrow | +|`KC.KP_3` |`KC.P3` |Keypad `3` and Page Down | +|`KC.KP_4` |`KC.P4` |Keypad `4` and Left Arrow | +|`KC.KP_5` |`KC.P5` |Keypad `5` | +|`KC.KP_6` |`KC.P6` |Keypad `6` and Right Arrow | +|`KC.KP_7` |`KC.P7` |Keypad `7` and Home | +|`KC.KP_8` |`KC.P8` |Keypad `8` and Up Arrow | +|`KC.KP_9` |`KC.P9` |Keypad `9` and Page Up | +|`KC.KP_0` |`KC.P0` |Keypad `0` and Insert | +|`KC.KP_DOT` |`KC.PDOT` |Keypad `.` and Delete | +|`KC.NONUS_BSLASH` |`KC.NUBS` |Non-US `\` and | | +|`KC.KP_EQUAL` |`KC.PEQL` |Keypad `=` | +|`KC.F13` | |F13 | +|`KC.F14` | |F14 | +|`KC.F15` | |F15 | +|`KC.F16` | |F16 | +|`KC.F17` | |F17 | +|`KC.F18` | |F18 | +|`KC.F19` | |F19 | +|`KC.F20` | |F20 | +|`KC.F21` | |F21 | +|`KC.F22` | |F22 | +|`KC.F23` | |F23 | +|`KC.F24` | |F24 | +|`KC.LOCKING_CAPS` |`KC.LCAP` |Locking Caps Lock | +|`KC.LOCKING_NUM` |`KC.LNUM` |Locking Num Lock | +|`KC.LOCKING_SCROLL` |`KC.LSCR` |Locking Scroll Lock | +|`KC.KP_COMMA` |`KC.PCMM` |Keypad `,` | +|`KC.KP_EQUAL_AS400` | |Keypad `=` on AS/400 keyboards | +|`KC.INT1` |`KC.RO` |JIS `\` and | | +|`KC.INT2` |`KC.KANA` |JIS Katakana/Hiragana | +|`KC.INT3` |`KC.JYEN` |JIS `¥` | +|`KC.INT4` |`KC.HENK` |JIS Henkan | +|`KC.INT5` |`KC.MHEN` |JIS Muhenkan | +|`KC.INT6` | |JIS Numpad `,` | +|`KC.INT7` | |International 7 | +|`KC.INT8` | |International 8 | +|`KC.INT9` | |International 9 | +|`KC.LANG1` |`KC.HAEN` |Hangul/English | +|`KC.LANG2` |`KC.HANJ` |Hanja | +|`KC.LANG3` | |JIS Katakana | +|`KC.LANG4` | |JIS Hiragana | +|`KC.LANG5` | |JIS Zenkaku/Hankaku | +|`KC.LANG6` | |Language 6 | +|`KC.LANG7` | |Language 7 | +|`KC.LANG8` | |Language 8 | +|`KC.LANG9` | |Language 9 | +|`KC.LCTRL` |`KC.LCTL` |Left Control | +|`KC.LSHIFT` |`KC.LSFT` |Left Shift | +|`KC.LALT` | |Left Alt | +|`KC.LGUI` |`KC.LCMD`, `KC.LWIN`|Left GUI (Windows/Command/Meta key) | +|`KC.RCTRL` |`KC.RCTL` |Right Control | +|`KC.RSHIFT` |`KC.RSFT` |Right Shift | +|`KC.RALT` | |Right Alt | +|`KC.RGUI` |`KC.RCMD`, `KC.RWIN`|Right GUI (Windows/Command/Meta key) | +|`KC.AUDIO_MUTE` |`KC.MUTE` |Mute | +|`KC.AUDIO_VOL_UP` |`KC.VOLU` |Volume Up | +|`KC.AUDIO_VOL_DOWN` |`KC.VOLD` |Volume Down | +|`KC.MEDIA_NEXT_TRACK` |`KC.MNXT` |Next Track (Windows) | +|`KC.MEDIA_PREV_TRACK` |`KC.MPRV` |Previous Track (Windows) | +|`KC.MEDIA_STOP` |`KC.MSTP` |Stop Track (Windows) | +|`KC.MEDIA_PLAY_PAUSE` |`KC.MPLY` |Play/Pause Track | +|`KC.MEDIA_EJECT` |`KC.EJCT` |Eject (macOS) | +|`KC.MEDIA_FAST_FORWARD`|`KC.MFFD` |Next Track (macOS) | +|`KC.MEDIA_REWIND` |`KC.MRWD` |Previous Track (macOS) | + + +## [US ANSI Shifted Symbols] + +|Key |Aliases |Description | +|------------------------|-------------------|-------------------| +|`KC.TILDE` |`KC.TILD` |`~` | +|`KC.EXCLAIM` |`KC.EXLM` |`!` | +|`KC.AT` | |`@` | +|`KC.HASH` | |`#` | +|`KC.DOLLAR` |`KC.DLR` |`$` | +|`KC.PERCENT` |`KC.PERC` |`%` | +|`KC.CIRCUMFLEX` |`KC.CIRC` |`^` | +|`KC.AMPERSAND` |`KC.AMPR` |`&` | +|`KC.ASTERISK` |`KC.ASTR` |`*` | +|`KC.LEFT_PAREN` |`KC.LPRN` |`(` | +|`KC.RIGHT_PAREN` |`KC.RPRN` |`)` | +|`KC.UNDERSCORE` |`KC.UNDS` |`_` | +|`KC.PLUS` | |`+` | +|`KC.LEFT_CURLY_BRACE` |`KC.LCBR` |`{` | +|`KC.RIGHT_CURLY_BRACE` |`KC.RCBR` |`}` | +|`KC.PIPE` | ||| +|`KC.COLON` |`KC.COLN` |`:` | +|`KC.DOUBLE_QUOTE` |`KC.DQUO`, `KC.DQT`|`"` | +|`KC.LEFT_ANGLE_BRACKET` |`KC.LABK`, `KC.LT` |`<` | +|`KC.RIGHT_ANGLE_BRACKET`|`KC.RABK`, `KC.GT` |`>` | +|`KC.QUESTION` |`KC.QUES` |`?` | + + +## [Internal Keys] + +|Key |Description | +|-----------------------|---------------------------------------------------------------------| +|`KC.RESET` |Put the keyboard into DFU mode for flashing | +|`KC.DEBUG` |Toggle `debug_enabled`, which enables log spew to serial console | +|`KC.GESC` |Escape when tapped, ` when pressed with Shift or GUI| +|`KC.LEAD` |The [Leader key] | +|`KC.UC_MODE_NOOP` |Sets UnicodeMode to NOOP | +|`KC.UC_MODE_LINUX` |Sets UnicodeMode to Linux | +|`KC.UC_MODE_MACOS` |Sets UnicodeMode to MocOS | +|`KC.UC_MODE_WINC` |Sets UnicodeMode to WinCompose | +|`KC.MACRO_SLEEP_MS(ms)`|Sleeps in a macro. Check MACROS for more information. | + + +## [Layer Switching] + +|Key |Description | +|-----------------|------------------------------------------------------------------------| +|`KC.DF(layer)` |Switches the default layer | +|`KC.MO(layer)` |Momentarily activates layer, switches off when you let go | +|`KC.LM(layer, mod)` |As `MO(layer)` but with `mod` active | +|`KC.LT(layer, kc)` |Momentarily activates layer if held, sends kc if tapped | +|`KC.TG(layer)` |Toggles the layer (enables it if no active, and vise versa) | +|`KC.TO(layer)` |Activates layer and deactivates all other layers | +|`KC.TT(layer)` |Momentarily activates layer if held, toggles it if tapped repeatedly | + + +## [Modifiers] + +|Key |Description | +|-------------|----------------------------------------------------| +|`KC.HYPR` |Hold Left Control, Shift, Alt and GUI | +|`KC.MEH` |Hold Left Control, Shift and Alt | +|`KC.LCTL(kc)`|Hold Left Control and press `kc` | +|`KC.LSFT(kc)`|Hold Left Shift and press `kc` | +|`KC.LALT(kc)`|Hold Left Alt and press `kc` | +|`KC.LGUI(kc)`|Hold Left GUI and press `kc` | +|`KC.RCTL(kc)`|Hold Right Control and press `kc` | +|`KC.RSFT(kc)`|Hold Right Shift and press `kc` | +|`KC.RALT(kc)`|Hold Right Alt and press `kc` | +|`KC.RGUI(kc)`|Hold Right GUI and press `kc` | + + +## [Mod-Tap Keys] NOT IMPLEMENTED AT THIS TIME + +|Key |Aliases |Description | +|------------|---------------------------------------|-------------------------------------------------------| +|`LCTL_T(kc)`|`CTL_T(kc)` |Left Control when held, `kc` when tapped | +|`RCTL_T(kc)`| |Right Control when held, `kc` when tapped | +|`LSFT_T(kc)`|`SFT_T(kc)` |Left Shift when held, `kc` when tapped | +|`RSFT_T(kc)`| |Right Shift when held, `kc` when tapped | +|`LALT_T(kc)`|`ALT_T(kc)` |Left Alt when held, `kc` when tapped | +|`RALT_T(kc)`|`ALGR_T(kc)` |Right Alt when held, `kc` when tapped | +|`LGUI_T(kc)`|`LCMD_T(kc)`, `RWIN_T(kc)`, `GUI_T(kc)`|Left GUI when held, `kc` when tapped | +|`RGUI_T(kc)`|`RCMD_T(kc)`, `RWIN_T(kc)` |Right GUI when held, `kc` when tapped | +|`C_S_T(kc)` | |Left Control and Shift when held, `kc` when tapped | +|`MEH_T(kc)` | |Left Control, Shift and Alt when held, `kc` when tapped| +|`LCAG_T(kc)`| |Left Control, Alt and GUI when held, `kc` when tapped | +|`RCAG_T(kc)`| |Right Control, Alt and GUI when held, `kc` when tapped | +|`ALL_T(kc)` | |Left Control, Shift, Alt and GUI when held, `kc` when tapped - more info [here](http://brettterpstra.com/2012/12/08/a-useful-caps-lock-key/)| +|`SGUI_T(kc)`|`SCMD_T(kc)`, `SWIN_T(kc)` |Left Shift and GUI when held, `kc` when tapped | +|`LCA_T(kc)` | |Left Control and Alt when held, `kc` when tapped | diff --git a/docs/keys.md b/docs/keys.md index 0708371..a1f2839 100644 --- a/docs/keys.md +++ b/docs/keys.md @@ -1,251 +1,158 @@ -# Keys Overview +# Keys -## [Basic Keys] +> NOTE: This is not a lookup table of key objects provided by KMK. That listing +> can be found in `keycodes.md`, though that file is not always kept up to date. +> It's probably worth a look at the raw source if you're stumped: `kmk/keys.py`. -|Key |Aliases |Description | -|-----------------------|--------------------|-----------------------------------------------| -|`KC.NO` | |Ignore this key (NOOP) | -|`KC.TRANSPARENT` |`KC.TRNS` |Use the next lowest non-transparent key | -|`KC.A` | |`a` and `A` | -|`KC.B` | |`b` and `B` | -|`KC.C` | |`c` and `C` | -|`KC.D` | |`d` and `D` | -|`KC.E` | |`e` and `E` | -|`KC.F` | |`f` and `F` | -|`KC.G` | |`g` and `G` | -|`KC.H` | |`h` and `H` | -|`KC.I` | |`i` and `I` | -|`KC.J` | |`j` and `J` | -|`KC.K` | |`k` and `K` | -|`KC.L` | |`l` and `L` | -|`KC.M` | |`m` and `M` | -|`KC.N` | |`n` and `N` | -|`KC.O` | |`o` and `O` | -|`KC.P` | |`p` and `P` | -|`KC.Q` | |`q` and `Q` | -|`KC.R` | |`r` and `R` | -|`KC.S` | |`s` and `S` | -|`KC.T` | |`t` and `T` | -|`KC.U` | |`u` and `U` | -|`KC.V` | |`v` and `V` | -|`KC.W` | |`w` and `W` | -|`KC.X` | |`x` and `X` | -|`KC.Y` | |`y` and `Y` | -|`KC.Z` | |`z` and `Z` | -|`KC.N1` | |`1` and `!` | -|`KC.N2` | |`2` and `@` | -|`KC.N3` | |`3` and `#` | -|`KC.N4` | |`4` and `$` | -|`KC.N5` | |`5` and `%` | -|`KC.N6` | |`6` and `^` | -|`KC.N7` | |`7` and `&` | -|`KC.N8` | |`8` and `*` | -|`KC.N9` | |`9` and `(` | -|`KC.N0` | |`0` and `)` | -|`KC.ENTER` |`KC.ENT` |Return (Enter) | -|`KC.ESCAPE` |`KC.ESC` |Escape | -|`KC.BSPACE` |`KC.BSPC` |Delete (Backspace) | -|`KC.TAB` | |Tab | -|`KC.SPACE` |`KC.SPC` |Spacebar | -|`KC.MINUS` |`KC.MINS` |`-` and `_` | -|`KC.EQUAL` |`KC.EQL` |`=` and `+` | -|`KC.LBRACKET` |`KC.LBRC` |`[` and `{` | -|`KC.RBRACKET` |`KC.RBRC` |`]` and `}` | -|`KC.BSLASH` |`KC.BSLS` |`\` and | | -|`KC.NONUS_HASH` |`KC.NUHS` |Non-US `#` and `~` | -|`KC.SCOLON` |`KC.SCLN` |`;` and `:` | -|`KC.QUOTE` |`KC.QUOT` |`'` and `"` | -|`KC.GRAVE` |`KC.GRV`, `KC.ZKHK` |` and `~`, JIS Zenkaku/Hankaku| -|`KC.COMMA` |`KC.COMM` |`,` and `<` | -|`KC.DOT` | |`.` and `>` | -|`KC.SLASH` |`KC.SLSH` |`/` and `?` | -|`KC.CAPSLOCK` |`KC.CLCK`, `KC.CAPS`|Caps Lock | -|`KC.F1` | |F1 | -|`KC.F2` | |F2 | -|`KC.F3` | |F3 | -|`KC.F4` | |F4 | -|`KC.F5` | |F5 | -|`KC.F6` | |F6 | -|`KC.F7` | |F7 | -|`KC.F8` | |F8 | -|`KC.F9` | |F9 | -|`KC.F10` | |F10 | -|`KC.F11` | |F11 | -|`KC.F12` | |F12 | -|`KC.PSCREEN` |`KC.PSCR` |Print Screen | -|`KC.SCROLLLOCK` |`KC.SLCK` |Scroll Lock | -|`KC.PAUSE` |`KC.PAUS`, `KC.BRK` |Pause | -|`KC.INSERT` |`KC.INS` |Insert | -|`KC.HOME` | |Home | -|`KC.PGUP` | |Page Up | -|`KC.DELETE` |`KC.DEL` |Forward Delete | -|`KC.END` | |End | -|`KC.PGDOWN` |`KC.PGDN` |Page Down | -|`KC.RIGHT` |`KC.RGHT` |Right Arrow | -|`KC.LEFT` | |Left Arrow | -|`KC.DOWN` | |Down Arrow | -|`KC.UP` | |Up Arrow | -|`KC.NUMLOCK` |`KC.NLCK` |Keypad Num Lock and Clear | -|`KC.KP_SLASH` |`KC.PSLS` |Keypad `/` | -|`KC.KP_ASTERISK` |`KC.PAST` |Keypad `*` | -|`KC.KP_MINUS` |`KC.PMNS` |Keypad `-` | -|`KC.KP_PLUS` |`KC.PPLS` |Keypad `+` | -|`KC.KP_ENTER` |`KC.PENT` |Keypad Enter | -|`KC.KP_1` |`KC.P1` |Keypad `1` and End | -|`KC.KP_2` |`KC.P2` |Keypad `2` and Down Arrow | -|`KC.KP_3` |`KC.P3` |Keypad `3` and Page Down | -|`KC.KP_4` |`KC.P4` |Keypad `4` and Left Arrow | -|`KC.KP_5` |`KC.P5` |Keypad `5` | -|`KC.KP_6` |`KC.P6` |Keypad `6` and Right Arrow | -|`KC.KP_7` |`KC.P7` |Keypad `7` and Home | -|`KC.KP_8` |`KC.P8` |Keypad `8` and Up Arrow | -|`KC.KP_9` |`KC.P9` |Keypad `9` and Page Up | -|`KC.KP_0` |`KC.P0` |Keypad `0` and Insert | -|`KC.KP_DOT` |`KC.PDOT` |Keypad `.` and Delete | -|`KC.NONUS_BSLASH` |`KC.NUBS` |Non-US `\` and | | -|`KC.KP_EQUAL` |`KC.PEQL` |Keypad `=` | -|`KC.F13` | |F13 | -|`KC.F14` | |F14 | -|`KC.F15` | |F15 | -|`KC.F16` | |F16 | -|`KC.F17` | |F17 | -|`KC.F18` | |F18 | -|`KC.F19` | |F19 | -|`KC.F20` | |F20 | -|`KC.F21` | |F21 | -|`KC.F22` | |F22 | -|`KC.F23` | |F23 | -|`KC.F24` | |F24 | -|`KC.LOCKING_CAPS` |`KC.LCAP` |Locking Caps Lock | -|`KC.LOCKING_NUM` |`KC.LNUM` |Locking Num Lock | -|`KC.LOCKING_SCROLL` |`KC.LSCR` |Locking Scroll Lock | -|`KC.KP_COMMA` |`KC.PCMM` |Keypad `,` | -|`KC.KP_EQUAL_AS400` | |Keypad `=` on AS/400 keyboards | -|`KC.INT1` |`KC.RO` |JIS `\` and | | -|`KC.INT2` |`KC.KANA` |JIS Katakana/Hiragana | -|`KC.INT3` |`KC.JYEN` |JIS `¥` | -|`KC.INT4` |`KC.HENK` |JIS Henkan | -|`KC.INT5` |`KC.MHEN` |JIS Muhenkan | -|`KC.INT6` | |JIS Numpad `,` | -|`KC.INT7` | |International 7 | -|`KC.INT8` | |International 8 | -|`KC.INT9` | |International 9 | -|`KC.LANG1` |`KC.HAEN` |Hangul/English | -|`KC.LANG2` |`KC.HANJ` |Hanja | -|`KC.LANG3` | |JIS Katakana | -|`KC.LANG4` | |JIS Hiragana | -|`KC.LANG5` | |JIS Zenkaku/Hankaku | -|`KC.LANG6` | |Language 6 | -|`KC.LANG7` | |Language 7 | -|`KC.LANG8` | |Language 8 | -|`KC.LANG9` | |Language 9 | -|`KC.LCTRL` |`KC.LCTL` |Left Control | -|`KC.LSHIFT` |`KC.LSFT` |Left Shift | -|`KC.LALT` | |Left Alt | -|`KC.LGUI` |`KC.LCMD`, `KC.LWIN`|Left GUI (Windows/Command/Meta key) | -|`KC.RCTRL` |`KC.RCTL` |Right Control | -|`KC.RSHIFT` |`KC.RSFT` |Right Shift | -|`KC.RALT` | |Right Alt | -|`KC.RGUI` |`KC.RCMD`, `KC.RWIN`|Right GUI (Windows/Command/Meta key) | -|`KC.AUDIO_MUTE` |`KC.MUTE` |Mute | -|`KC.AUDIO_VOL_UP` |`KC.VOLU` |Volume Up | -|`KC.AUDIO_VOL_DOWN` |`KC.VOLD` |Volume Down | -|`KC.MEDIA_NEXT_TRACK` |`KC.MNXT` |Next Track (Windows) | -|`KC.MEDIA_PREV_TRACK` |`KC.MPRV` |Previous Track (Windows) | -|`KC.MEDIA_STOP` |`KC.MSTP` |Stop Track (Windows) | -|`KC.MEDIA_PLAY_PAUSE` |`KC.MPLY` |Play/Pause Track | -|`KC.MEDIA_EJECT` |`KC.EJCT` |Eject (macOS) | -|`KC.MEDIA_FAST_FORWARD`|`KC.MFFD` |Next Track (macOS) | -|`KC.MEDIA_REWIND` |`KC.MRWD` |Previous Track (macOS) | +This is a bunch of documentation about how physical keypresses translate to +events (and the lifecycle of said events) in KMK. It's somewhat technical, but +if you're looking to extend your keyboard's functionality with extra code, +you'll need at least some of this technical knowledge. + +The first few steps in the process aren't all that interesting for most +workflows, which is why they're buried deep in KMK: we scan a bunch of GPIO +lanes (about as quickly as CircuitPython will let us) to see where, in a matrix +of keys, a key has been pressed. The technical details about this process [are +probably best left to +Wikipedia](https://en.wikipedia.org/wiki/Keyboard_matrix_circuit). Then, we scan +through the defined keymap, finding the first valid key at this index based on +the stack of currently active layers (this logic, if you want to read through +the code, is in `kmk/internal_state.py`, method `_find_key_in_map`). + +The next few steps are the interesting part, but to understand them, we need to +understand a bit about what a `Key` object is (found in `kmk/keys.py`). `Key` +objects have a few core pieces of information: + +* Their `code`, which can be any integer. Integers below + `FIRST_KMK_INTERNAL_KEY` are sent through to the HID stack (and thus the + computer, which will translate that integer to something meaningful - for + example, `code=4` becomes `a` on a US QWERTY/Dvorak keyboard). + +* Their attached modifiers (to implement things like shifted keys or `KC.HYPR`, + which are single key presses sending along more than one key in a single HID + report. This is a distinct concept from Sequences, which are a KMK feature + documented in `sequences.md`). For almost all purposes outside of KMK core, + this field should be ignored - it can be safely populated through far more + sane means than futzing with it by hand. + +* Some data on whether the key should actually be pressed or released - this is + mostly an implementation detail of how Sequences work, where, for example, + `KC.RALT` may need to be held down for the entirety of a sequence, rather than + being released immediately before moving to the next character. Usually end + users shouldn't need to mess with this, but the fields are called `no_press` + and `no_release` and are referenced in a few places in the codebase if you + need examples. + +* Handlers for "press" (sometimes known as "keydown") and "release" (sometimes + known as "keyup") events. KMK provides handlers for standard keyboard + functions and some special override keys (like `KC.GESC`, which is an enhanced + form of existing ANSI keys) in `kmk/handlers/stock.py`, for layer switching in + `kmk/handlers.layers.py`, and for everything related to Sequences (see + `sequences.md` again) in `kmk/handlers/sequences.py`. We'll discuss these more + shortly. + +* Optional callbacks to be run before and/or after the above handlers. More on + that soon. + +* A generic `meta` field, which is most commonly used for "argumented" keys - + objects in the `KC` object which are actually functions that return `Key` + instances, which often need to access the arguments passed into the "outer" + function. Many of these examples are related to layer switching - for example, + `KC.MO` is implemented as an argumented key - when the user adds `KC.MO(1)` to + their keymap, the function call returns a `Key` object with `meta` set to an + object containing `layer` and `kc` properties, for example. There's other uses + for `meta`, and examples can be found in `kmk/types.py` + +`Key` objects can also be chained together by calling them! To create a key +which holds Control and Shift simultaneously, we can simply do: + +```python +CTRLSHFT = KC.LCTL(KC.LSFT) + +keyboard.keymap = [ ... CTRLSHFT ... ] +``` + +When a key is pressed and we've pulled a `Key` object out of the keymap, the +following will happen: + +- Pre-press callbacks will be run in the order they were assigned, with their + return values discarded (unless the user attached these, they will almost + never exist) +- The assigned press handler will be run (most commonly, this is provided by + KMK) +- Post-press callbacks will be run in the order they were assigned, with their + return values discarded (unless the user attached these, they will almost + never exist) + +These same steps are run for when a key is released. + +_So now... what's a handler, and what's a pre/post callback?!_ + +All of these serve rougly the same purpose: to _do something_ with the key's +data, or to fire off side effects. Most handlers are provided by KMK internally +and modify the `InternalState` in some way - adding the key to the HID queue, +changing layers, etc. The pre/post handlers are designed to allow functionality +to be bolted on at these points in the event flow without having to reimplement +(or import and manually call) the internal handlers. + +All of these methods take the same arguments, and for this, I'll lift a +docstring straight out of the source: + +> Receives the following: +> +> - self (this Key instance) +> - state (the current InternalState) +> - KC (the global KC lookup table, for convenience) +> - `coord_int` (an internal integer representation of the matrix coordinate +> for the pressed key - this is likely not useful to end users, but is +> provided for consistency with the internal handlers) +> - `coord_raw` (an X,Y tuple of the matrix coordinate - also likely not useful) +> +> The return value of the provided callback is discarded. Exceptions are _not_ +> caught, and will likely crash KMK if not handled within your function. +> +> These handlers are run in attachment order: handlers provided by earlier +> calls of this method will be executed before those provided by later calls. + +This means if you want to add things like underglow/LED support, or have a +button that triggers your GSM modem to call someone, or whatever else you can +hack up in CircuitPython, which also retaining layer-switching abilities or +whatever the stock handler is, you're covered. This also means you can add +completely new functionality to KMK by writing your own handler. + +Here's an example of a lifecycle hook to print a giant Shrek ASCII art. It +doesn't care about any of the arguments passed into it, because it has no +intentions of modifying the internal state. It is purely a [side +effect](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) run every +time Left Alt is pressed: + +```python +def shrek(*args, **kwargs): + print('⢀⡴⠑⡄⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣤⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀') + print('⠸⡇⠀⠿⡀⠀⠀⠀⣀⡴⢿⣿⣿⣿⣿⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠑⢄⣠⠾⠁⣀⣄⡈⠙⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⢀⡀⠁⠀⠀⠈⠙⠛⠂⠈⣿⣿⣿⣿⣿⠿⡿⢿⣆⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⢀⡾⣁⣀⠀⠴⠂⠙⣗⡀⠀⢻⣿⣿⠭⢤⣴⣦⣤⣹⠀⠀⠀⢀⢴⣶⣆') + print('⠀⠀⢀⣾⣿⣿⣿⣷⣮⣽⣾⣿⣥⣴⣿⣿⡿⢂⠔⢚⡿⢿⣿⣦⣴⣾⠁⠸⣼⡿') + print('⠀⢀⡞⠁⠙⠻⠿⠟⠉⠀⠛⢹⣿⣿⣿⣿⣿⣌⢤⣼⣿⣾⣿⡟⠉⠀⠀⠀⠀⠀') + print('⠀⣾⣷⣶⠇⠀⠀⣤⣄⣀⡀⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀') + print('⠀⠉⠈⠉⠀⠀⢦⡈⢻⣿⣿⣿⣶⣶⣶⣶⣤⣽⡹⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠀⠀⠀⠉⠲⣽⡻⢿⣿⣿⣿⣿⣿⣿⣷⣜⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣷⣶⣮⣭⣽⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠀⠀⣀⣀⣈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀') + print('⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⠿⠿⠿⠿⠛⠉') -## [US ANSI Shifted Symbols] +KC.LALT.before_press_handler(shrek) +``` -|Key |Aliases |Description | -|------------------------|-------------------|-------------------| -|`KC.TILDE` |`KC.TILD` |`~` | -|`KC.EXCLAIM` |`KC.EXLM` |`!` | -|`KC.AT` | |`@` | -|`KC.HASH` | |`#` | -|`KC.DOLLAR` |`KC.DLR` |`$` | -|`KC.PERCENT` |`KC.PERC` |`%` | -|`KC.CIRCUMFLEX` |`KC.CIRC` |`^` | -|`KC.AMPERSAND` |`KC.AMPR` |`&` | -|`KC.ASTERISK` |`KC.ASTR` |`*` | -|`KC.LEFT_PAREN` |`KC.LPRN` |`(` | -|`KC.RIGHT_PAREN` |`KC.RPRN` |`)` | -|`KC.UNDERSCORE` |`KC.UNDS` |`_` | -|`KC.PLUS` | |`+` | -|`KC.LEFT_CURLY_BRACE` |`KC.LCBR` |`{` | -|`KC.RIGHT_CURLY_BRACE` |`KC.RCBR` |`}` | -|`KC.PIPE` | ||| -|`KC.COLON` |`KC.COLN` |`:` | -|`KC.DOUBLE_QUOTE` |`KC.DQUO`, `KC.DQT`|`"` | -|`KC.LEFT_ANGLE_BRACKET` |`KC.LABK`, `KC.LT` |`<` | -|`KC.RIGHT_ANGLE_BRACKET`|`KC.RABK`, `KC.GT` |`>` | -|`KC.QUESTION` |`KC.QUES` |`?` | +You can also copy a key without any pre/post handlers attached with `.clone()`, +so for example, if I've already added Shrek to my `LALT` but want a Shrek-less +`LALT` key elsewhere in my keymap, I can just clone it, and the new key won't +have my handlers attached: - -## [Internal Keys] - -|Key |Description | -|-----------------------|---------------------------------------------------------------------| -|`KC.RESET` |Put the keyboard into DFU mode for flashing | -|`KC.DEBUG` |Toggle `debug_enabled`, which enables log spew to serial console | -|`KC.GESC` |Escape when tapped, ` when pressed with Shift or GUI| -|`KC.LEAD` |The [Leader key] | -|`KC.UC_MODE_NOOP` |Sets UnicodeMode to NOOP | -|`KC.UC_MODE_LINUX` |Sets UnicodeMode to Linux | -|`KC.UC_MODE_MACOS` |Sets UnicodeMode to MocOS | -|`KC.UC_MODE_WINC` |Sets UnicodeMode to WinCompose | -|`KC.MACRO_SLEEP_MS(ms)`|Sleeps in a macro. Check MACROS for more information. | - - -## [Layer Switching] - -|Key |Description | -|-----------------|------------------------------------------------------------------------| -|`KC.DF(layer)` |Switches the default layer | -|`KC.MO(layer)` |Momentarily activates layer, switches off when you let go | -|`KC.LM(layer, mod)` |As `MO(layer)` but with `mod` active | -|`KC.LT(layer, kc)` |Momentarily activates layer if held, sends kc if tapped | -|`KC.TG(layer)` |Toggles the layer (enables it if no active, and vise versa) | -|`KC.TO(layer)` |Activates layer and deactivates all other layers | -|`KC.TT(layer)` |Momentarily activates layer if held, toggles it if tapped repeatedly | - - -## [Modifiers] - -|Key |Description | -|-------------|----------------------------------------------------| -|`KC.HYPR` |Hold Left Control, Shift, Alt and GUI | -|`KC.MEH` |Hold Left Control, Shift and Alt | -|`KC.LCTL(kc)`|Hold Left Control and press `kc` | -|`KC.LSFT(kc)`|Hold Left Shift and press `kc` | -|`KC.LALT(kc)`|Hold Left Alt and press `kc` | -|`KC.LGUI(kc)`|Hold Left GUI and press `kc` | -|`KC.RCTL(kc)`|Hold Right Control and press `kc` | -|`KC.RSFT(kc)`|Hold Right Shift and press `kc` | -|`KC.RALT(kc)`|Hold Right Alt and press `kc` | -|`KC.RGUI(kc)`|Hold Right GUI and press `kc` | - - -## [Mod-Tap Keys] NOT IMPLEMENTED AT THIS TIME - -|Key |Aliases |Description | -|------------|---------------------------------------|-------------------------------------------------------| -|`LCTL_T(kc)`|`CTL_T(kc)` |Left Control when held, `kc` when tapped | -|`RCTL_T(kc)`| |Right Control when held, `kc` when tapped | -|`LSFT_T(kc)`|`SFT_T(kc)` |Left Shift when held, `kc` when tapped | -|`RSFT_T(kc)`| |Right Shift when held, `kc` when tapped | -|`LALT_T(kc)`|`ALT_T(kc)` |Left Alt when held, `kc` when tapped | -|`RALT_T(kc)`|`ALGR_T(kc)` |Right Alt when held, `kc` when tapped | -|`LGUI_T(kc)`|`LCMD_T(kc)`, `RWIN_T(kc)`, `GUI_T(kc)`|Left GUI when held, `kc` when tapped | -|`RGUI_T(kc)`|`RCMD_T(kc)`, `RWIN_T(kc)` |Right GUI when held, `kc` when tapped | -|`C_S_T(kc)` | |Left Control and Shift when held, `kc` when tapped | -|`MEH_T(kc)` | |Left Control, Shift and Alt when held, `kc` when tapped| -|`LCAG_T(kc)`| |Left Control, Alt and GUI when held, `kc` when tapped | -|`RCAG_T(kc)`| |Right Control, Alt and GUI when held, `kc` when tapped | -|`ALL_T(kc)` | |Left Control, Shift, Alt and GUI when held, `kc` when tapped - more info [here](http://brettterpstra.com/2012/12/08/a-useful-caps-lock-key/)| -|`SGUI_T(kc)`|`SCMD_T(kc)`, `SWIN_T(kc)` |Left Shift and GUI when held, `kc` when tapped | -|`LCA_T(kc)` | |Left Control and Alt when held, `kc` when tapped | +```python +SHREKLESS_ALT = KC.LALT.clone() +``` diff --git a/docs/sequences.md b/docs/sequences.md new file mode 100644 index 0000000..3a18523 --- /dev/null +++ b/docs/sequences.md @@ -0,0 +1,112 @@ +# Sequences + +Sequences are used for sending multiple keystrokes in a single action, and can +be used for things like unicode characters (even emojis! 🇨🇦), lorei epsum +generators, triggering side effects (think lighting, speakers, +microcontroller-optimized cryptocurrency miners, whatever). + +## Sending strings +The most basic sequence is `send_string`. It can be used to send any standard +English alphabet character, and an assortment of other "standard" keyboard keys +(return, space, exclamation points, etc.) + +```python +from kmk.handlers.sequences import send_string + +WOW = send_string("Wow, KMK is awesome!") + +keyboard.keymap = [...WOW,...] +``` + +## Unicode +Before trying to send Unicode sequences, make sure you set your `UnicodeMode`. +You can set an initial value in your keymap by setting `keyboard.unicode_mode`. + +Keys are provided to change this mode at runtime - for example, `KC.UC_MODE_LINUX`. + + +### Unicode Modes: +On Linux, Unicode uses `Ctrl-Shift-U`, which is supported by `ibus` and GTK+3. +`ibus` users will need to add `IBUS_ENABLE_CTRL_SHIFT_U=1` to their environment +(`~/profile`, `~/.bashrc`, `~/.zshrc`, or through your desktop environment's +configurator). + +On Windows, [WinCompose](https://github.com/samhocevar/wincompose) is required. + +- Linux : `UnicodeMode.LINUX` or `UnicodeMode.IBUS` +- Mac: `UnicodeMode.MACOS` or `UnicodeMode.OSX` or `UnicodeMode.RALT` +- Windows: `UnicodeMode.WINC` + + +### Unicode Examples + +To send a simple unicode symbol +```python +from kmk.handlers.sequences import unicode_string_sequence + +FLIP = unicode_string_sequence('(ノಠ痊ಠ)ノ彡┻━┻') + +keyboard.keymap = [...FLIP,...] +``` + +If you'd rather keep a lookup table of your sequences (perhaps to bind emojis to +keys), that's supported too, through an obnoxiously long-winded method: + +```python +from kmk.handlers.sequences import compile_unicode_string_sequences as cuss + +emoticons = cuss({ + 'BEER': r'🍺', + 'HAND_WAVE': r'👋', +}) + +keymap = [...emoticons.BEER, emoticons.HAND_WAVE...] +``` + +> The observant will notice dot-notation is supported here despite feeding in a +> dictionary - the return of `compile_unicode_string_sequences` is a +> `kmk.types.AttrDict`, which you can think of as a read-only view over a +> dictionary adding attribute-based (dot-notation) access. + +Remember from the Leader Mode documentation that leader sequences simply bind to +keys, so extrapolating this example out a bit, you can bind emojis to leader +sequences matching some name or mnemonic representing the sequence you're +looking to send. If you ever wanted to type `fire` and see a fire emoji +on your screen, welcome home. + +```python +from kmk.handlers.sequences import compile_unicode_string_sequences as cuss + +emoticons = cuss({ + # Emojis + 'BEER': r'🍺', + 'BEER_TOAST': r'🍻', + 'FACE_THINKING': r'🤔', + 'FIRE': r'🔥', + 'FLAG_CA': r'🇨🇦', + 'FLAG_US': r'🇺🇸', +}) + +keyboard.leader_dictionary = { + 'beer': emoticons.BEER, + 'beers': emoticons.BEER_TOAST, + 'fire': emoticons.FIRE, + 'uhh': emoticons.FACE_THINKING, + 'fca': emoticons.FLAG_CA, + 'fus': emoticons.FLAG_US, +} +``` + +Finally, if you need to send arbitrary unicode codepoints in raw form, that's +supported too, through `unicode_codepoint_sequence`. + +```python +from kmk.handlers.sequences import unicode_codepoint_sequence + +TABLE_FLIP = unicode_codepoint_sequence([ + "28", "30ce", "ca0", "75ca","ca0", "29", + "30ce", "5f61", "253b", "2501", "253b", +]) + +keyboard.keymap = [...TABLE_FLIP,...] +``` diff --git a/docs/unicode.md b/docs/unicode.md deleted file mode 100644 index c065358..0000000 --- a/docs/unicode.md +++ /dev/null @@ -1,70 +0,0 @@ -# Macros And Unicode -Macros are used for sending multiple keystrokes in a single action. This is useful for -things like unicode input, sending strings of text, or other automation. - -## Basic Macros -The most basic macro is send_string(). It can be used to send any standard ASCII keycode, including the return and tab key. -```python -from kmk.macros.simple import send_string - -WOW = send_string("Wow, KMK is awesome!") - -keymap = [...WOW,...] -``` -# Unicode -Before using unicode mode, you will need to set your platform. This can be done either of these ways. -You can use both in cases where you want to use one operating system, but occasionally use another. -This allows you to change modes on the fly without having to change your keymap. - - unicode_mode = UnicodeMode.LINUX - Or - keymap = [...KC.UC_MODE_LINUX,...] - - -### Unicode Modes: -On Linux IBUS is required, and on Windows, requires [WinCompose](https://github.com/samhocevar/wincompose) -- Linux : UnicodeMode.LINUX or UnicodeMode.IBUS -- Mac: UnicodeMode.MACOS or UnicodeMode.OSX or UnicodeMode.RALT -- Windows: UnicodeMode.WINC - -A note for IBUS users on Linux. This mode is not enabled by default, and will need to be turned on for this to work. -This works on X11, though if you are on Wayland, or in some GTK apps, it MAY work, but is not supported. - - export IBUS_ENABLE_CTRL_SHIFT_U=1 - -### Unicode Examples - -To send a simple unicode symbol -```python -FLIP = unicode_string_sequence('(ノಠ痊ಠ)ノ彡┻━┻') -keymap = [...FLIP,...] -``` - -And for many single character unicode: - -```python -from kmk.types import AttrDic - -emoticons = AttrDict({ - 'BEER': r'🍺', - 'HAND_WAVE': r'👋', -}) - -for k, v in emoticons.items(): -emoticons[k] = unicode_string_sequence(v) - -keymap = [...emoticons.BEER, emoticons.HAND_WAVE...] -``` - -If you need to send a unicode hex string, use unicode_codepoint_sequence() - -```python -from kmk.macros.unicode import unicode_codepoint_sequence - -TABLE_FLIP = unicode_codepoint_sequence([ - "28", "30ce", "ca0", "75ca","ca0", "29", - "30ce", "5f61", "253b", "2501", "253b", -]) - -keymap = [...TABLE_FLIP,...] -```