From d0f35100b31fdac59f596f6d0350dcfbebba47ae Mon Sep 17 00:00:00 2001 From: Kyle Brown Date: Fri, 21 Sep 2018 12:57:29 -0700 Subject: [PATCH 01/14] Start of internal keycodes --- .gitignore | 5 ++-- boards/kdb424/handwire_planck_pyboard.py | 2 +- kmk/common/internal_keycodes.py | 11 +++++++++ kmk/common/keycodes.py | 9 +++++++ kmk/micropython/pyb_hid.py | 30 +++++++++++++++--------- 5 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 kmk/common/internal_keycodes.py diff --git a/.gitignore b/.gitignore index 9b6f771..9609410 100644 --- a/.gitignore +++ b/.gitignore @@ -106,6 +106,7 @@ venv.bak/ .ampy .submodules .circuitpy-deps - -.idea/ .micropython-deps + +# Pycharms cruft +.idea diff --git a/boards/kdb424/handwire_planck_pyboard.py b/boards/kdb424/handwire_planck_pyboard.py index e7d600a..9c88ed9 100644 --- a/boards/kdb424/handwire_planck_pyboard.py +++ b/boards/kdb424/handwire_planck_pyboard.py @@ -21,7 +21,7 @@ def main(): KC.BACKSPACE], [KC.TAB, KC.A, KC.O, KC.E, KC.U, KC.I, KC.D, KC.H, KC.T, KC.N, KC.S, KC.ENTER], [KC.SHIFT, KC.SEMICOLON, KC.Q, KC.J, KC.K, KC.X, KC.B, KC.M, KC.W, KC.V, KC.Z, KC.SLASH], - [KC.CTRL, KC.GUI, KC.ALT, KC.A, KC.A, KC.SPACE, KC.SPACE, KC.A, KC.LEFT, KC.DOWN, + [KC.CTRL, KC.GUI, KC.ALT, KC.RESET, KC.A, KC.SPACE, KC.SPACE, KC.A, KC.LEFT, KC.DOWN, KC.UP, KC.RIGHT], ] diff --git a/kmk/common/internal_keycodes.py b/kmk/common/internal_keycodes.py new file mode 100644 index 0000000..c390454 --- /dev/null +++ b/kmk/common/internal_keycodes.py @@ -0,0 +1,11 @@ +def process(self, state, key): + self.logger.warning(key) + if key.code == 1000: + reset(self) + + +def reset(self): + self.logger.debug('Rebooting to bootloader') + import machine + machine.bootloader() + return self diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index 63fddb5..681eeec 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -312,6 +312,15 @@ class Keycodes(KeycodeCategory): KC_MEDIA_FAST_FORWARD = KC_MFFD = Keycode(187, False) KC_MEDIA_REWIND = KC_MRWD = Keycode(189, False) + class KMK(KeycodeCategory): + KC_RESET = Keycode(1000, False) + KC_DEBUG = Keycode(1001, False) + KC_GESC = Keycode(1002, False) + KC_LSPO = Keycode(1003, False) + KC_RSPC = Keycode(1004, False) + KC_LEAD = Keycode(1005, False) + KC_LOCK = Keycode(1006, False) + ALL_KEYS = KC = AttrDict({ k.replace('KC_', ''): v diff --git a/kmk/micropython/pyb_hid.py b/kmk/micropython/pyb_hid.py index b257f56..9fa0cb5 100644 --- a/kmk/micropython/pyb_hid.py +++ b/kmk/micropython/pyb_hid.py @@ -3,6 +3,7 @@ import string from pyb import USB_HID, delay +import kmk.common.internal_keycodes as internal_keycodes from kmk.common.event_defs import KEY_DOWN_EVENT, KEY_UP_EVENT from kmk.common.keycodes import Keycodes, char_lookup @@ -49,19 +50,26 @@ class HIDHelper: def _subscription(self, state, action): if action['type'] == KEY_DOWN_EVENT: - if action['keycode'].is_modifier: - self.add_modifier(action['keycode']) - self.send() + # If keycode is 1000 or over, these are internal keys + if action['keycode'].code < 1000: + if action['keycode'].is_modifier: + self.add_modifier(action['keycode']) + self.send() + else: + self.add_key(action['keycode']) + self.send() else: - self.add_key(action['keycode']) - self.send() + self.logger.warning('Should be processing') + internal_keycodes.process(self, state, action['keycode']) elif action['type'] == KEY_UP_EVENT: - if action['keycode'].is_modifier: - self.remove_modifier(action['keycode']) - self.send() - else: - self.remove_key(action['keycode']) - self.send() + # If keycode is 1000 or over, these are internal keys + if action['keycode'].code < 1000: + if action['keycode'].is_modifier: + self.remove_modifier(action['keycode']) + self.send() + else: + self.remove_key(action['keycode']) + self.send() def send(self): self.logger.debug('Sending HID report: {}'.format(self._evt)) From 7ae2d18e4523ec982123f95a6f3a75aa5f2e6cfe Mon Sep 17 00:00:00 2001 From: Kyle Brown Date: Fri, 21 Sep 2018 17:22:03 -0700 Subject: [PATCH 02/14] Very broken, but some work done probably --- boards/kdb424/handwire_planck_pyboard.py | 3 +- kmk/common/abstract/matrix_scanner.py | 2 +- kmk/common/event_defs.py | 9 ++++-- kmk/common/internal_keycodes.py | 37 ++++++++++++++++++++++-- kmk/common/internal_state.py | 3 ++ kmk/common/keycodes.py | 9 ++++++ kmk/firmware.py | 6 ++-- kmk/micropython/matrix.py | 3 +- kmk/micropython/pyb_hid.py | 4 +-- 9 files changed, 63 insertions(+), 13 deletions(-) diff --git a/boards/kdb424/handwire_planck_pyboard.py b/boards/kdb424/handwire_planck_pyboard.py index 9c88ed9..ae5a55a 100644 --- a/boards/kdb424/handwire_planck_pyboard.py +++ b/boards/kdb424/handwire_planck_pyboard.py @@ -21,7 +21,7 @@ def main(): KC.BACKSPACE], [KC.TAB, KC.A, KC.O, KC.E, KC.U, KC.I, KC.D, KC.H, KC.T, KC.N, KC.S, KC.ENTER], [KC.SHIFT, KC.SEMICOLON, KC.Q, KC.J, KC.K, KC.X, KC.B, KC.M, KC.W, KC.V, KC.Z, KC.SLASH], - [KC.CTRL, KC.GUI, KC.ALT, KC.RESET, KC.A, KC.SPACE, KC.SPACE, KC.A, KC.LEFT, KC.DOWN, + [KC.CTRL, KC.GUI, KC.ALT, KC.RESET, KC.DF, KC.SPACE, KC.SPACE, KC.A, KC.LEFT, KC.DOWN, KC.UP, KC.RIGHT], ] @@ -31,6 +31,7 @@ def main(): col_pins=cols, diode_orientation=diode_orientation, hid=HIDHelper, + active_layers=[0], log_level=DEBUG, ) diff --git a/kmk/common/abstract/matrix_scanner.py b/kmk/common/abstract/matrix_scanner.py index 4ef71dc..28561c9 100644 --- a/kmk/common/abstract/matrix_scanner.py +++ b/kmk/common/abstract/matrix_scanner.py @@ -2,7 +2,7 @@ from kmk.common.consts import DiodeOrientation class AbstractMatrixScanner(): - def __init__(self, cols, rows, diode_orientation=DiodeOrientation.COLUMNS): + def __init__(self, cols, rows, active_layers, diode_orientation=DiodeOrientation.COLUMNS): raise NotImplementedError('Abstract implementation') def _normalize_matrix(self, matrix): diff --git a/kmk/common/event_defs.py b/kmk/common/event_defs.py index 04a1f7a..71b496f 100644 --- a/kmk/common/event_defs.py +++ b/kmk/common/event_defs.py @@ -5,29 +5,32 @@ KEY_DOWN_EVENT = const(2) INIT_FIRMWARE_EVENT = const(3) -def init_firmware(keymap, row_pins, col_pins, diode_orientation): +def init_firmware(keymap, row_pins, col_pins, diode_orientation, active_layers): return { 'type': INIT_FIRMWARE_EVENT, 'keymap': keymap, 'row_pins': row_pins, 'col_pins': col_pins, 'diode_orientation': diode_orientation, + 'active_layers': active_layers, } -def key_up_event(keycode, row, col): +def key_up_event(keycode, row, col, active_layers): return { 'type': KEY_UP_EVENT, 'keycode': keycode, 'row': row, 'col': col, + 'active_layers': active_layers, } -def key_down_event(keycode, row, col): +def key_down_event(keycode, row, col, active_layers): return { 'type': KEY_DOWN_EVENT, 'keycode': keycode, 'row': row, 'col': col, + 'active_layers': active_layers, } diff --git a/kmk/common/internal_keycodes.py b/kmk/common/internal_keycodes.py index c390454..4982d3c 100644 --- a/kmk/common/internal_keycodes.py +++ b/kmk/common/internal_keycodes.py @@ -1,7 +1,9 @@ -def process(self, state, key): - self.logger.warning(key) - if key.code == 1000: +def process(self, state, action): + self.logger.warning(action['keycode']) + if action['keycode'].code == 1000: reset(self) + elif action['keycode'].code == 1050: + df(self, "Filler", action) def reset(self): @@ -9,3 +11,32 @@ def reset(self): import machine machine.bootloader() return self + + +def df(self, layer, action): + """Switches the default layer""" + self.logger.warning(action['active_layers']) + + +def mo(layer): + """Momentarily activates layer, switches off when you let go""" + + +def lm(layer, mod): + """As MO(layer) but with mod active""" + + +def lt(layer, kc): + """Momentarily activates layer if held, sends kc if tapped""" + + +def tg(layer): + """Toggles the layer (enables it if no active, and vise versa)""" + + +def to(layer): + """Activates layer and deactivates all other layers""" + + +def tt(layer): + """Momentarily activates layer if held, toggles it if tapped repeatedly""" diff --git a/kmk/common/internal_state.py b/kmk/common/internal_state.py index 8c54e63..4c75f0a 100644 --- a/kmk/common/internal_state.py +++ b/kmk/common/internal_state.py @@ -48,6 +48,7 @@ class InternalState: col_pins = [] matrix = [] diode_orientation = DiodeOrientation.COLUMNS + active_layers = [0] @property def __dict__(self): @@ -58,6 +59,7 @@ class InternalState: 'col_pins': self.col_pins, 'row_pins': self.row_pins, 'diode_orientation': self.diode_orientation, + 'active_layers': self.active_layers, } def __repr__(self): @@ -124,6 +126,7 @@ def kmk_reducer(state=None, action=None, logger=None): row_pins=action['row_pins'], col_pins=action['col_pins'], diode_orientation=action['diode_orientation'], + active_layers=action['active_layers'], matrix=[ [False for c in action['col_pins']] for r in action['row_pins'] diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index 681eeec..2d6ae12 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -321,6 +321,15 @@ class Keycodes(KeycodeCategory): KC_LEAD = Keycode(1005, False) KC_LOCK = Keycode(1006, False) + class Layers(KeycodeCategory): + KC_DF = Keycode(1050, False) + KC_MO = Keycode(1051, False) + KC_LM = Keycode(1052, False) + KC_LT = Keycode(1053, False) + KC_TG = Keycode(1054, False) + KC_TO = Keycode(1055, False) + KC_TT = Keycode(1056, False) + ALL_KEYS = KC = AttrDict({ k.replace('KC_', ''): v diff --git a/kmk/firmware.py b/kmk/firmware.py index 6af51ac..32ec2ba 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -12,8 +12,8 @@ except ImportError: class Firmware: def __init__( - self, keymap, row_pins, col_pins, diode_orientation, - hid=None, log_level=logging.NOTSET, + self, keymap, row_pins, col_pins, active_layers, + diode_orientation, hid=None, log_level=logging.NOTSET, ): logger = logging.getLogger(__name__) logger.setLevel(log_level) @@ -36,6 +36,7 @@ class Firmware: keymap=keymap, row_pins=row_pins, col_pins=col_pins, + active_layers=active_layers, diode_orientation=diode_orientation, )) @@ -50,6 +51,7 @@ class Firmware: self.matrix = MatrixScanner( state.col_pins, state.row_pins, + state.active_layers, state.diode_orientation, ) diff --git a/kmk/micropython/matrix.py b/kmk/micropython/matrix.py index f775719..584ea97 100644 --- a/kmk/micropython/matrix.py +++ b/kmk/micropython/matrix.py @@ -5,7 +5,7 @@ from kmk.common.consts import DiodeOrientation class MatrixScanner(AbstractMatrixScanner): - def __init__(self, cols, rows, diode_orientation=DiodeOrientation.COLUMNS): + def __init__(self, cols, rows, active_layers, diode_orientation=DiodeOrientation.COLUMNS): # A pin cannot be both a row and column, detect this by combining the # two tuples into a set and validating that the length did not drop # @@ -19,6 +19,7 @@ class MatrixScanner(AbstractMatrixScanner): self.cols = [machine.Pin(pin) for pin in cols] self.rows = [machine.Pin(pin) for pin in rows] self.diode_orientation = diode_orientation + self.active_layers = active_layers if self.diode_orientation == DiodeOrientation.COLUMNS: self.outputs = self.cols diff --git a/kmk/micropython/pyb_hid.py b/kmk/micropython/pyb_hid.py index 9fa0cb5..2f8dfc3 100644 --- a/kmk/micropython/pyb_hid.py +++ b/kmk/micropython/pyb_hid.py @@ -59,8 +59,8 @@ class HIDHelper: self.add_key(action['keycode']) self.send() else: - self.logger.warning('Should be processing') - internal_keycodes.process(self, state, action['keycode']) + self.logger.warning('Triggering KMK keycodes') + internal_keycodes.process(self, state, action) elif action['type'] == KEY_UP_EVENT: # If keycode is 1000 or over, these are internal keys if action['keycode'].code < 1000: From 8a55dcca04c59c6f51f547a88b512e4957f16fb9 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Fri, 21 Sep 2018 23:44:03 -0700 Subject: [PATCH 03/14] Helper makefile task to autofix some linter noise --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 79ad192..11bbce9 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,9 @@ devdeps: Pipfile.lock lint: devdeps @pipenv run flake8 +fix-isort: devdeps + @find kmk/ boards/ entrypoints/ -name "*.py" | xargs pipenv run isort + .submodules: .gitmodules @echo "===> Pulling dependencies, this may take several minutes" @git submodule update --init --recursive From 392917082a34f8d249e948423ded716a81b30717 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Fri, 21 Sep 2018 23:44:30 -0700 Subject: [PATCH 04/14] Unbreak the general idea of KC_DF and KC_MO, though still needs heavy refactors --- boards/klardotsh/threethree_matrix_pyboard.py | 4 +- kmk/common/event_defs.py | 9 ++-- kmk/common/internal_keycodes.py | 45 ++++++++++++++----- kmk/common/internal_state.py | 16 +++++-- kmk/firmware.py | 4 +- kmk/micropython/pyb_hid.py | 36 +++++++-------- 6 files changed, 69 insertions(+), 45 deletions(-) diff --git a/boards/klardotsh/threethree_matrix_pyboard.py b/boards/klardotsh/threethree_matrix_pyboard.py index 1e5b48d..e714932 100644 --- a/boards/klardotsh/threethree_matrix_pyboard.py +++ b/boards/klardotsh/threethree_matrix_pyboard.py @@ -17,8 +17,8 @@ def main(): diode_orientation = DiodeOrientation.COLUMNS keymap = [ - [KC.ESC, KC.H, KC.BACKSPACE], - [KC.TAB, KC.I, KC.ENTER], + [KC.DF, KC.H, KC.RESET], + [KC.MO, KC.I, KC.ENTER], [KC.CTRL, KC.SPACE, KC.SHIFT], ] diff --git a/kmk/common/event_defs.py b/kmk/common/event_defs.py index 71b496f..04a1f7a 100644 --- a/kmk/common/event_defs.py +++ b/kmk/common/event_defs.py @@ -5,32 +5,29 @@ KEY_DOWN_EVENT = const(2) INIT_FIRMWARE_EVENT = const(3) -def init_firmware(keymap, row_pins, col_pins, diode_orientation, active_layers): +def init_firmware(keymap, row_pins, col_pins, diode_orientation): return { 'type': INIT_FIRMWARE_EVENT, 'keymap': keymap, 'row_pins': row_pins, 'col_pins': col_pins, 'diode_orientation': diode_orientation, - 'active_layers': active_layers, } -def key_up_event(keycode, row, col, active_layers): +def key_up_event(keycode, row, col): return { 'type': KEY_UP_EVENT, 'keycode': keycode, 'row': row, 'col': col, - 'active_layers': active_layers, } -def key_down_event(keycode, row, col, active_layers): +def key_down_event(keycode, row, col): return { 'type': KEY_DOWN_EVENT, 'keycode': keycode, 'row': row, 'col': col, - 'active_layers': active_layers, } diff --git a/kmk/common/internal_keycodes.py b/kmk/common/internal_keycodes.py index 4982d3c..2b1377c 100644 --- a/kmk/common/internal_keycodes.py +++ b/kmk/common/internal_keycodes.py @@ -1,25 +1,46 @@ -def process(self, state, action): - self.logger.warning(action['keycode']) - if action['keycode'].code == 1000: - reset(self) - elif action['keycode'].code == 1050: - df(self, "Filler", action) +import logging + +from kmk.common.event_defs import KEY_DOWN_EVENT, KEY_UP_EVENT +from kmk.common.keycodes import Keycodes -def reset(self): - self.logger.debug('Rebooting to bootloader') +def process(state, action, logger=None): + if action['keycode'].code < 1000: + return state + + if logger is None: + logger = logging.getLogger(__name__) + + logger.warning(action['keycode']) + if action['keycode'] == Keycodes.KMK.KC_RESET: + return reset(logger) + elif action['keycode'] == Keycodes.Layers.KC_DF: + return df(state, "Filler", action, logger=logger) + elif action['keycode'] == Keycodes.Layers.KC_MO: + return mo(state, "Filler", action, logger=logger) + + +def reset(logger): + logger.debug('Rebooting to bootloader') import machine machine.bootloader() - return self -def df(self, layer, action): +def df(state, layer, action, logger): """Switches the default layer""" - self.logger.warning(action['active_layers']) + state.active_layers = [1] + + return state -def mo(layer): +def mo(state, layer, action, logger): """Momentarily activates layer, switches off when you let go""" + if action['type'] == KEY_UP_EVENT: + state.active_layers = [0] + elif action['type'] == KEY_DOWN_EVENT: + state.active_layers = [0, 1] + + return state def lm(layer, mod): diff --git a/kmk/common/internal_state.py b/kmk/common/internal_state.py index 4c75f0a..2078b9a 100644 --- a/kmk/common/internal_state.py +++ b/kmk/common/internal_state.py @@ -4,6 +4,7 @@ import sys from kmk.common.consts import DiodeOrientation from kmk.common.event_defs import (INIT_FIRMWARE_EVENT, KEY_DOWN_EVENT, KEY_UP_EVENT) +from kmk.common.internal_keycodes import process as process_internal class ReduxStore: @@ -93,7 +94,7 @@ def kmk_reducer(state=None, action=None, logger=None): return state if action['type'] == KEY_UP_EVENT: - return state.copy( + newstate = state.copy( keys_pressed=frozenset( key for key in state.keys_pressed if key != action['keycode'] ), @@ -106,8 +107,13 @@ def kmk_reducer(state=None, action=None, logger=None): ], ) + if action['keycode'].code >= 1000: + return process_internal(newstate, action, logger=logger) + + return newstate + if action['type'] == KEY_DOWN_EVENT: - return state.copy( + newstate = state.copy( keys_pressed=( state.keys_pressed | {action['keycode']} ), @@ -120,13 +126,17 @@ def kmk_reducer(state=None, action=None, logger=None): ], ) + if action['keycode'].code >= 1000: + return process_internal(newstate, action, logger=logger) + + return newstate + if action['type'] == INIT_FIRMWARE_EVENT: return state.copy( keymap=action['keymap'], row_pins=action['row_pins'], col_pins=action['col_pins'], diode_orientation=action['diode_orientation'], - active_layers=action['active_layers'], matrix=[ [False for c in action['col_pins']] for r in action['row_pins'] diff --git a/kmk/firmware.py b/kmk/firmware.py index 32ec2ba..87885e7 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -12,7 +12,7 @@ except ImportError: class Firmware: def __init__( - self, keymap, row_pins, col_pins, active_layers, + self, keymap, row_pins, col_pins, diode_orientation, hid=None, log_level=logging.NOTSET, ): logger = logging.getLogger(__name__) @@ -36,7 +36,6 @@ class Firmware: keymap=keymap, row_pins=row_pins, col_pins=col_pins, - active_layers=active_layers, diode_orientation=diode_orientation, )) @@ -51,7 +50,6 @@ class Firmware: self.matrix = MatrixScanner( state.col_pins, state.row_pins, - state.active_layers, state.diode_orientation, ) diff --git a/kmk/micropython/pyb_hid.py b/kmk/micropython/pyb_hid.py index 2f8dfc3..88a4e19 100644 --- a/kmk/micropython/pyb_hid.py +++ b/kmk/micropython/pyb_hid.py @@ -3,7 +3,6 @@ import string from pyb import USB_HID, delay -import kmk.common.internal_keycodes as internal_keycodes from kmk.common.event_defs import KEY_DOWN_EVENT, KEY_UP_EVENT from kmk.common.keycodes import Keycodes, char_lookup @@ -50,26 +49,25 @@ class HIDHelper: def _subscription(self, state, action): if action['type'] == KEY_DOWN_EVENT: - # If keycode is 1000 or over, these are internal keys - if action['keycode'].code < 1000: - if action['keycode'].is_modifier: - self.add_modifier(action['keycode']) - self.send() - else: - self.add_key(action['keycode']) - self.send() + if action['keycode'].code >= 1000: + return + + if action['keycode'].is_modifier: + self.add_modifier(action['keycode']) + self.send() else: - self.logger.warning('Triggering KMK keycodes') - internal_keycodes.process(self, state, action) + self.add_key(action['keycode']) + self.send() elif action['type'] == KEY_UP_EVENT: - # If keycode is 1000 or over, these are internal keys - if action['keycode'].code < 1000: - if action['keycode'].is_modifier: - self.remove_modifier(action['keycode']) - self.send() - else: - self.remove_key(action['keycode']) - self.send() + if action['keycode'].code >= 1000: + return + + if action['keycode'].is_modifier: + self.remove_modifier(action['keycode']) + self.send() + else: + self.remove_key(action['keycode']) + self.send() def send(self): self.logger.debug('Sending HID report: {}'.format(self._evt)) From 578773189086405aa752609e43b90bb602b2f6c1 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 22 Sep 2018 00:46:14 -0700 Subject: [PATCH 05/14] Add support for changing to N layers as needed --- boards/klardotsh/threethree_matrix_pyboard.py | 4 +- kmk/common/internal_keycodes.py | 27 ++++++----- kmk/common/internal_state.py | 46 +++++++++++-------- kmk/common/keycodes.py | 43 ++++++++++++++--- 4 files changed, 80 insertions(+), 40 deletions(-) diff --git a/boards/klardotsh/threethree_matrix_pyboard.py b/boards/klardotsh/threethree_matrix_pyboard.py index e714932..54817e5 100644 --- a/boards/klardotsh/threethree_matrix_pyboard.py +++ b/boards/klardotsh/threethree_matrix_pyboard.py @@ -17,8 +17,8 @@ def main(): diode_orientation = DiodeOrientation.COLUMNS keymap = [ - [KC.DF, KC.H, KC.RESET], - [KC.MO, KC.I, KC.ENTER], + [KC.DF(1), KC.H, KC.RESET], + [KC.MO(2), KC.I, KC.ENTER], [KC.CTRL, KC.SPACE, KC.SHIFT], ] diff --git a/kmk/common/internal_keycodes.py b/kmk/common/internal_keycodes.py index 2b1377c..65be714 100644 --- a/kmk/common/internal_keycodes.py +++ b/kmk/common/internal_keycodes.py @@ -13,32 +13,37 @@ def process(state, action, logger=None): logger.warning(action['keycode']) if action['keycode'] == Keycodes.KMK.KC_RESET: - return reset(logger) - elif action['keycode'] == Keycodes.Layers.KC_DF: - return df(state, "Filler", action, logger=logger) - elif action['keycode'] == Keycodes.Layers.KC_MO: - return mo(state, "Filler", action, logger=logger) + return reset(state, action, logger=logger) + elif action['keycode'].code == Keycodes.Layers._KC_DF: + return df(state, action, logger=logger) + elif action['keycode'].code == Keycodes.Layers._KC_MO: + return mo(state, action, logger=logger) + else: + return state -def reset(logger): +def reset(state, action, logger): logger.debug('Rebooting to bootloader') import machine machine.bootloader() -def df(state, layer, action, logger): +def df(state, action, logger): """Switches the default layer""" - state.active_layers = [1] + state.active_layers[0] = action['keycode'].layer return state -def mo(state, layer, action, logger): +def mo(state, action, logger): """Momentarily activates layer, switches off when you let go""" if action['type'] == KEY_UP_EVENT: - state.active_layers = [0] + state.active_layers = [ + layer for layer in state.active_layers + if layer != action['keycode'].layer + ] elif action['type'] == KEY_DOWN_EVENT: - state.active_layers = [0, 1] + state.active_layers.append(action['keycode'].layer) return state diff --git a/kmk/common/internal_state.py b/kmk/common/internal_state.py index 2078b9a..362d748 100644 --- a/kmk/common/internal_state.py +++ b/kmk/common/internal_state.py @@ -50,34 +50,40 @@ class InternalState: matrix = [] diode_orientation = DiodeOrientation.COLUMNS active_layers = [0] + _oldstates = [] - @property - def __dict__(self): - return { + def __init__(self, preserve_intermediate_states=False): + self.preserve_intermediate_states = preserve_intermediate_states + + def to_dict(self, verbose=False): + ret = { 'keys_pressed': self.keys_pressed, 'modifiers_pressed': self.modifiers_pressed, - 'keymap': self.keymap, - 'col_pins': self.col_pins, - 'row_pins': self.row_pins, - 'diode_orientation': self.diode_orientation, 'active_layers': self.active_layers, } + if verbose: + ret.update({ + 'keymap': self.keymap, + 'matrix': self.matrix, + 'col_pins': self.col_pins, + 'row_pins': self.row_pins, + 'diode_orientation': self.diode_orientation, + }) + + return ret + def __repr__(self): - return 'InternalState({})'.format(self.__dict__) + return 'InternalState({})'.format(self.to_dict()) - def copy(self, **kwargs): - new_state = InternalState() - - for k, v in self.__dict__.items(): - if hasattr(new_state, k): - setattr(new_state, k, v) + def update(self, **kwargs): + if self.preserve_intermediate_states: + self._oldstates.append(repr(self.to_dict(verbose=True))) for k, v in kwargs.items(): - if hasattr(new_state, k): - setattr(new_state, k, v) + setattr(self, k, v) - return new_state + return self def kmk_reducer(state=None, action=None, logger=None): @@ -94,7 +100,7 @@ def kmk_reducer(state=None, action=None, logger=None): return state if action['type'] == KEY_UP_EVENT: - newstate = state.copy( + newstate = state.update( keys_pressed=frozenset( key for key in state.keys_pressed if key != action['keycode'] ), @@ -113,7 +119,7 @@ def kmk_reducer(state=None, action=None, logger=None): return newstate if action['type'] == KEY_DOWN_EVENT: - newstate = state.copy( + newstate = state.update( keys_pressed=( state.keys_pressed | {action['keycode']} ), @@ -132,7 +138,7 @@ def kmk_reducer(state=None, action=None, logger=None): return newstate if action['type'] == INIT_FIRMWARE_EVENT: - return state.copy( + return state.update( keymap=action['keymap'], row_pins=action['row_pins'], col_pins=action['col_pins'], diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index 2d6ae12..d80b91c 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -9,6 +9,7 @@ from kmk.common.types import AttrDict from kmk.common.util import flatten_dict Keycode = namedtuple('Keycode', ('code', 'is_modifier')) +LayerKeycode = namedtuple('LayerKeycode', ('code', 'layer')) class KeycodeCategory(type): @@ -322,13 +323,41 @@ class Keycodes(KeycodeCategory): KC_LOCK = Keycode(1006, False) class Layers(KeycodeCategory): - KC_DF = Keycode(1050, False) - KC_MO = Keycode(1051, False) - KC_LM = Keycode(1052, False) - KC_LT = Keycode(1053, False) - KC_TG = Keycode(1054, False) - KC_TO = Keycode(1055, False) - KC_TT = Keycode(1056, False) + _KC_DF = 1050 + _KC_MO = 1051 + _KC_LM = 1052 + _KC_LT = 1053 + _KC_TG = 1054 + _KC_TO = 1055 + _KC_TT = 1056 + + @staticmethod + def KC_DF(layer): + return LayerKeycode(Keycodes.Layers._KC_DF, layer) + + @staticmethod + def KC_MO(layer): + return LayerKeycode(Keycodes.Layers._KC_MO, layer) + + @staticmethod + def KC_LM(layer): + return LayerKeycode(Keycodes.Layers._KC_LM, layer) + + @staticmethod + def KC_LT(layer): + return LayerKeycode(Keycodes.Layers._KC_LT, layer) + + @staticmethod + def KC_TG(layer): + return LayerKeycode(Keycodes.Layers._KC_TG, layer) + + @staticmethod + def KC_TO(layer): + return LayerKeycode(Keycodes.Layers._KC_TO, layer) + + @staticmethod + def KC_TT(layer): + return LayerKeycode(Keycodes.Layers._KC_TT, layer) ALL_KEYS = KC = AttrDict({ From fb053b7de46948547a9ded914768ef533382d219 Mon Sep 17 00:00:00 2001 From: Kyle Brown Date: Sat, 22 Sep 2018 12:36:28 -0700 Subject: [PATCH 06/14] Fix keycodes, and update keymap in prep for working layers --- boards/kdb424/handwire_planck_pyboard.py | 20 +++++++++++++------- kmk/common/keycodes.py | 4 ++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/boards/kdb424/handwire_planck_pyboard.py b/boards/kdb424/handwire_planck_pyboard.py index ae5a55a..79bfd10 100644 --- a/boards/kdb424/handwire_planck_pyboard.py +++ b/boards/kdb424/handwire_planck_pyboard.py @@ -1,3 +1,4 @@ +# flake8: noqa from logging import DEBUG import machine @@ -17,12 +18,18 @@ def main(): diode_orientation = DiodeOrientation.COLUMNS keymap = [ - [KC.ESC, KC.QUOTE, KC.COMMA, KC.PERIOD, KC.P, KC.Y, KC.F, KC.G, KC.C, KC.R, KC.L, - KC.BACKSPACE], - [KC.TAB, KC.A, KC.O, KC.E, KC.U, KC.I, KC.D, KC.H, KC.T, KC.N, KC.S, KC.ENTER], - [KC.SHIFT, KC.SEMICOLON, KC.Q, KC.J, KC.K, KC.X, KC.B, KC.M, KC.W, KC.V, KC.Z, KC.SLASH], - [KC.CTRL, KC.GUI, KC.ALT, KC.RESET, KC.DF, KC.SPACE, KC.SPACE, KC.A, KC.LEFT, KC.DOWN, - KC.UP, KC.RIGHT], + [ + [KC.ESC, KC.QUOTE, KC.COMMA, KC.DOT, KC.P, KC.Y, KC.F, KC.G, KC.C, KC.R, KC.L, KC.BKSP], + [KC.TAB, KC.A, KC.O, KC.E, KC.U, KC.I, KC.D, KC.H, KC.T, KC.N, KC.S, KC.ENT], + [KC.LSFT, KC.SCLN, KC.Q, KC.J, KC.K, KC.X, KC.B, KC.M, KC.W, KC.V, KC.Z, KC.SLSH], + [KC.CTRL, KC.GUI, KC.ALT, KC.RESET, KC.MO(1), KC.SPC, KC.SPC, KC.A, KC.LEFT, KC.DOWN, KC.UP, KC.RIGHT], + ], + [ + [KC.A, KC.QUOTE, KC.COMMA, KC.DOT, KC.P, KC.Y, KC.F, KC.G, KC.C, KC.R, KC.L, KC.BACKSPACE], + [KC.TAB, KC.A, KC.O, KC.E, KC.U, KC.I, KC.D, KC.H, KC.T, KC.N, KC.S, KC.ENT], + [KC.LSFT, KC.SCOLON, KC.Q, KC.J, KC.K, KC.X, KC.B, KC.M, KC.W, KC.V, KC.Z, KC.SLSH], + [KC.CTRL, KC.GUI, KC.ALT, KC.RESET, KC.MO(1), KC.SPC, KC.SPC, KC.A, KC.LEFT, KC.DOWN, KC.UP, KC.RIGHT], + ], ] firmware = Firmware( @@ -31,7 +38,6 @@ def main(): col_pins=cols, diode_orientation=diode_orientation, hid=HIDHelper, - active_layers=[0], log_level=DEBUG, ) diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index d80b91c..8908efb 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -371,6 +371,6 @@ char_lookup = { ' ': (Keycodes.Common.KC_SPACE,), '-': (Keycodes.Common.KC_MINUS,), '=': (Keycodes.Common.KC_EQUAL,), - '+': (Keycodes.Common.KC_EQUAL, Keycodes.Modifiers.KC_SHIFT), - '~': (Keycodes.Common.KC_TILDE,), + '+': (Keycodes.Common.KC_EQUAL, Keycodes.Modifiers.KC_LSHIFT), + '~': (Keycodes.Common.KC_NUHS,), } From 0ae3adcc84b3646b1b8e7595bdf5df8629063119 Mon Sep 17 00:00:00 2001 From: Kyle Brown Date: Sat, 22 Sep 2018 15:29:24 -0700 Subject: [PATCH 07/14] Added more work to shifted keycodes. --- kmk/common/internal_keycodes.py | 9 ++++++++- kmk/common/keycodes.py | 25 ++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/kmk/common/internal_keycodes.py b/kmk/common/internal_keycodes.py index 65be714..eb2c3dc 100644 --- a/kmk/common/internal_keycodes.py +++ b/kmk/common/internal_keycodes.py @@ -17,11 +17,18 @@ def process(state, action, logger=None): elif action['keycode'].code == Keycodes.Layers._KC_DF: return df(state, action, logger=logger) elif action['keycode'].code == Keycodes.Layers._KC_MO: - return mo(state, action, logger=logger) + return tilde(state, action, logger=logger) + elif action['keycode'].code == Keycodes.Layers.KC_TILDE: + pass else: return state +def tilde(state, action, logger): + # TODO Actually process keycodes + return state + + def reset(state, action, logger): logger.debug('Rebooting to bootloader') import machine diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index 8908efb..fe4d7d6 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -359,6 +359,29 @@ class Keycodes(KeycodeCategory): def KC_TT(layer): return LayerKeycode(Keycodes.Layers._KC_TT, layer) + class ShiftedKeycodes(KeycodeCategory): + KC_TILDE = KC_TILD = Keycode(1100, False) + KC_EXCLAIM = KC_EXLM = Keycode(1101, False) + KC_AT = Keycode(1102, False) + KC_HASH = Keycode(1103, False) + KC_DOLLAR = KC_DLR = Keycode(1104, False) + KC_PERCENT = KC_PERC = Keycode(1105, False) + KC_CIRCUMFLEX = KC_CIRC = Keycode(1106, False) # The ^ Symbol + KC_AMPERSAND = KC_AMPR = Keycode(1107, False) + KC_ASTERISK = KC_ASTR = Keycode(1108, False) + KC_LEFT_PAREN = KC_LPRN = Keycode(1109, False) + KC_RIGHT_PAREN = KC_RPRN = Keycode(1110, False) + KC_UNDERSCORE = KC_UNDS = Keycode(1111, False) + KC_PLUS = Keycode(1112, False) + KC_LEFT_CURLY_BRACE = KC_LCBR = Keycode(1113, False) + KC_RIGHT_CURLY_BRACE = KC_RCBR = Keycode(1114, False) + KC_PIPE = Keycode(1115, False) + KC_COLON = KC_COLN = Keycode(1116, False) + KC_DOUBLE_QUOTE = KC_DQUO = KC_DQT = Keycode(1117, False) + KC_LEFT_ANGLE_BRACKET = KC_LABK = KC_LT = Keycode(1118, False) + KC_RIGHT_ANGLE_BRACKET = KC_RABK = KC_GT = Keycode(1119, False) + KC_QUESTION = KC_QUES = Keycode(1120, False) + ALL_KEYS = KC = AttrDict({ k.replace('KC_', ''): v @@ -372,5 +395,5 @@ char_lookup = { '-': (Keycodes.Common.KC_MINUS,), '=': (Keycodes.Common.KC_EQUAL,), '+': (Keycodes.Common.KC_EQUAL, Keycodes.Modifiers.KC_LSHIFT), - '~': (Keycodes.Common.KC_NUHS,), + '~': (Keycodes.Common.KC_GRAVE,), } From 9bec905fce5e90b38396dd34e67d64e966de0eea Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 22 Sep 2018 21:49:58 -0700 Subject: [PATCH 08/14] Holy refactor, Batman: full layer support (MO/DF) Wow, what a trip this was. Layer support is now fully implemented. Other changes here mostly revolve around the event dispatching model: more floating state (hidden in clases wherever) has been purged, with the reducer (now mutable, comments inline) serving, as it should, as the sole source of truth. Thunk support has been added to our fake Redux clone, allowing Action Creators to handle sequences of events (which is arguably a cleaner way of handling matrix changes when not all matrix changes should result in a new HID report - in the case of internal keys). A whole class has been deprecated (Keymap) which only served as another arbitor of state: instead, the MatrixScanner has been made smarter and handles diffing internally, dispatching an Action when needed (and allowing the reducer to parse the keymap and figure out what key is pressed - this is the infinitely cleaner solution when layers come into play). --- boards/klardotsh/threethree_matrix_pyboard.py | 18 ++- kmk/common/event_defs.py | 72 +++++++++++- kmk/common/internal_keycodes.py | 36 ++---- kmk/common/internal_state.py | 111 +++++++++++++----- kmk/common/keycodes.py | 4 + kmk/common/keymap.py | 25 ---- kmk/firmware.py | 10 +- kmk/micropython/matrix.py | 15 +++ kmk/micropython/pyb_hid.py | 34 +++--- 9 files changed, 214 insertions(+), 111 deletions(-) delete mode 100644 kmk/common/keymap.py diff --git a/boards/klardotsh/threethree_matrix_pyboard.py b/boards/klardotsh/threethree_matrix_pyboard.py index 54817e5..c3b9c20 100644 --- a/boards/klardotsh/threethree_matrix_pyboard.py +++ b/boards/klardotsh/threethree_matrix_pyboard.py @@ -17,9 +17,21 @@ def main(): diode_orientation = DiodeOrientation.COLUMNS keymap = [ - [KC.DF(1), KC.H, KC.RESET], - [KC.MO(2), KC.I, KC.ENTER], - [KC.CTRL, KC.SPACE, KC.SHIFT], + [ + [KC.MO(1), KC.H, KC.RESET], + [KC.MO(2), KC.I, KC.ENTER], + [KC.CTRL, KC.SPACE, KC.SHIFT], + ], + [ + [KC.TRNS, KC.B, KC.C], + [KC.NO, KC.D, KC.E], + [KC.F, KC.G, KC.H], + ], + [ + [KC.X, KC.Y, KC.Z], + [KC.TRNS, KC.N, KC.O], + [KC.R, KC.P, KC.Q], + ], ] firmware = Firmware( diff --git a/kmk/common/event_defs.py b/kmk/common/event_defs.py index 04a1f7a..69bfd8a 100644 --- a/kmk/common/event_defs.py +++ b/kmk/common/event_defs.py @@ -1,8 +1,16 @@ +import logging + from micropython import const +from kmk.common.keycodes import Keycodes + KEY_UP_EVENT = const(1) KEY_DOWN_EVENT = const(2) INIT_FIRMWARE_EVENT = const(3) +NEW_MATRIX_EVENT = const(4) +HID_REPORT_EVENT = const(5) + +logger = logging.getLogger(__name__) def init_firmware(keymap, row_pins, col_pins, diode_orientation): @@ -15,19 +23,75 @@ def init_firmware(keymap, row_pins, col_pins, diode_orientation): } -def key_up_event(keycode, row, col): +def key_up_event(row, col): return { 'type': KEY_UP_EVENT, - 'keycode': keycode, 'row': row, 'col': col, } -def key_down_event(keycode, row, col): +def key_down_event(row, col): return { 'type': KEY_DOWN_EVENT, - 'keycode': keycode, 'row': row, 'col': col, } + + +def new_matrix_event(matrix): + return { + 'type': NEW_MATRIX_EVENT, + 'matrix': matrix, + } + + +def hid_report_event(): + return { + 'type': HID_REPORT_EVENT, + } + + +def matrix_changed(new_matrix): + def _key_pressed(dispatch, get_state): + state = get_state() + # Temporarily preserve a reference to the old event + # We do fake Redux around here because microcontrollers + # aren't exactly RAM or CPU powerhouses - the state does + # mutate in place. Unfortunately this makes reasoning + # about code a bit messier and really hurts one of the + # selling points of Redux. Former development versions + # of KMK created new InternalState copies every single + # time the state changed, but it was sometimes slow. + old_matrix = state.matrix + old_keys_pressed = state.keys_pressed + + dispatch(new_matrix_event(new_matrix)) + + with get_state() as new_state: + for ridx, row in enumerate(new_state.matrix): + for cidx, col in enumerate(row): + if col != old_matrix[ridx][cidx]: + if col: + dispatch(key_down_event( + row=ridx, + col=cidx, + )) + else: + dispatch(key_up_event( + row=ridx, + col=cidx, + )) + + with get_state() as new_state: + if old_keys_pressed != new_state.keys_pressed: + dispatch(hid_report_event()) + + if Keycodes.KMK.KC_RESET in new_state.keys_pressed: + try: + import machine + machine.bootloader() + except ImportError: + logger.warning('Tried to reset to bootloader, but not supported on this chip?') + + return _key_pressed diff --git a/kmk/common/internal_keycodes.py b/kmk/common/internal_keycodes.py index eb2c3dc..2371699 100644 --- a/kmk/common/internal_keycodes.py +++ b/kmk/common/internal_keycodes.py @@ -4,53 +4,41 @@ from kmk.common.event_defs import KEY_DOWN_EVENT, KEY_UP_EVENT from kmk.common.keycodes import Keycodes -def process(state, action, logger=None): - if action['keycode'].code < 1000: - return state - +def process_internal_key_event(state, action, changed_key, logger=None): if logger is None: logger = logging.getLogger(__name__) - logger.warning(action['keycode']) - if action['keycode'] == Keycodes.KMK.KC_RESET: - return reset(state, action, logger=logger) - elif action['keycode'].code == Keycodes.Layers._KC_DF: - return df(state, action, logger=logger) - elif action['keycode'].code == Keycodes.Layers._KC_MO: - return tilde(state, action, logger=logger) - elif action['keycode'].code == Keycodes.Layers.KC_TILDE: + if changed_key.code == Keycodes.Layers._KC_DF: + return df(state, action, changed_key, logger=logger) + elif changed_key.code == Keycodes.Layers._KC_MO: + return mo(state, action, changed_key, logger=logger) + elif changed_key.code == Keycodes.Layers.KC_TILDE: pass else: return state -def tilde(state, action, logger): +def tilde(state, action, changed_key, logger): # TODO Actually process keycodes return state -def reset(state, action, logger): - logger.debug('Rebooting to bootloader') - import machine - machine.bootloader() - - -def df(state, action, logger): +def df(state, action, changed_key, logger): """Switches the default layer""" - state.active_layers[0] = action['keycode'].layer + state.active_layers[0] = changed_key.layer return state -def mo(state, action, logger): +def mo(state, action, changed_key, logger): """Momentarily activates layer, switches off when you let go""" if action['type'] == KEY_UP_EVENT: state.active_layers = [ layer for layer in state.active_layers - if layer != action['keycode'].layer + if layer != changed_key.layer ] elif action['type'] == KEY_DOWN_EVENT: - state.active_layers.append(action['keycode'].layer) + state.active_layers.append(changed_key.layer) return state diff --git a/kmk/common/internal_state.py b/kmk/common/internal_state.py index 362d748..ce5962b 100644 --- a/kmk/common/internal_state.py +++ b/kmk/common/internal_state.py @@ -2,9 +2,11 @@ import logging import sys from kmk.common.consts import DiodeOrientation -from kmk.common.event_defs import (INIT_FIRMWARE_EVENT, KEY_DOWN_EVENT, - KEY_UP_EVENT) -from kmk.common.internal_keycodes import process as process_internal +from kmk.common.event_defs import (HID_REPORT_EVENT, INIT_FIRMWARE_EVENT, + KEY_DOWN_EVENT, KEY_UP_EVENT, + NEW_MATRIX_EVENT) +from kmk.common.internal_keycodes import process_internal_key_event +from kmk.common.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes class ReduxStore: @@ -16,9 +18,15 @@ class ReduxStore: self.callbacks = [] def dispatch(self, action): - self.logger.debug('Dispatching action: {}'.format(action)) - self.state = self.reducer(self.state, action) - self.logger.debug('Dispatching complete: {}'.format(action)) + if callable(action): + self.logger.debug('Received thunk') + action(self.dispatch, self.get_state) + self.logger.debug('Finished thunk') + return None + + self.logger.debug('Dispatching action: Type {} >> {}'.format(action['type'], action)) + self.state = self.reducer(self.state, action, logger=self.logger) + self.logger.debug('Dispatching complete: Type {}'.format(action['type'])) self.logger.debug('New state: {}'.format(self.state)) @@ -55,6 +63,12 @@ class InternalState: def __init__(self, preserve_intermediate_states=False): self.preserve_intermediate_states = preserve_intermediate_states + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + pass + def to_dict(self, verbose=False): ret = { 'keys_pressed': self.keys_pressed, @@ -86,6 +100,21 @@ class InternalState: return self +def find_key_in_map(state, row, col): + # Later-added layers have priority. Sift through the layers + # in reverse order until we find a valid keycode object + for layer in reversed(state.active_layers): + layer_key = state.keymap[layer][row][col] + + if not layer_key or layer_key == Keycodes.KMK.KC_TRNS: + continue + + if layer_key == Keycodes.KMK.KC_NO: + break + + return layer_key + + def kmk_reducer(state=None, action=None, logger=None): if state is None: state = InternalState() @@ -99,41 +128,52 @@ def kmk_reducer(state=None, action=None, logger=None): return state - if action['type'] == KEY_UP_EVENT: - newstate = state.update( - keys_pressed=frozenset( - key for key in state.keys_pressed if key != action['keycode'] - ), - matrix=[ - r if ridx != action['row'] else [ - c if cidx != action['col'] else False - for cidx, c in enumerate(r) - ] - for ridx, r in enumerate(state.matrix) - ], + if action['type'] == NEW_MATRIX_EVENT: + return state.update( + matrix=action['matrix'], ) - if action['keycode'].code >= 1000: - return process_internal(newstate, action, logger=logger) + if action['type'] == KEY_UP_EVENT: + row = action['row'] + col = action['col'] + + changed_key = find_key_in_map(state, row, col) + + logger.debug('Detected change to key: {}'.format(changed_key)) + + if not changed_key: + return state + + newstate = state.update( + keys_pressed=frozenset( + key for key in state.keys_pressed if key != changed_key + ), + ) + + if changed_key.code >= FIRST_KMK_INTERNAL_KEYCODE: + return process_internal_key_event(newstate, action, changed_key, logger=logger) return newstate if action['type'] == KEY_DOWN_EVENT: + row = action['row'] + col = action['col'] + + changed_key = find_key_in_map(state, row, col) + + logger.debug('Detected change to key: {}'.format(changed_key)) + + if not changed_key: + return state + newstate = state.update( keys_pressed=( - state.keys_pressed | {action['keycode']} + state.keys_pressed | {changed_key} ), - matrix=[ - r if ridx != action['row'] else [ - c if cidx != action['col'] else True - for cidx, c in enumerate(r) - ] - for ridx, r in enumerate(state.matrix) - ], ) - if action['keycode'].code >= 1000: - return process_internal(newstate, action, logger=logger) + if changed_key.code >= FIRST_KMK_INTERNAL_KEYCODE: + return process_internal_key_event(newstate, action, changed_key, logger=logger) return newstate @@ -148,3 +188,14 @@ def kmk_reducer(state=None, action=None, logger=None): for r in action['row_pins'] ], ) + + # HID events are non-mutating, used exclusively for listeners to know + # they should be doing things. This could/should arguably be folded back + # into KEY_UP_EVENT and KEY_DOWN_EVENT, but for now it's nice to separate + # this out for debugging's sake. + if action['type'] == HID_REPORT_EVENT: + return state + + # On unhandled events, log and do not mutate state + logger.warning('Unhandled event! Returning state unmodified.') + return state diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index fe4d7d6..09e226a 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -8,6 +8,8 @@ except ImportError: from kmk.common.types import AttrDict from kmk.common.util import flatten_dict +FIRST_KMK_INTERNAL_KEYCODE = 1000 + Keycode = namedtuple('Keycode', ('code', 'is_modifier')) LayerKeycode = namedtuple('LayerKeycode', ('code', 'layer')) @@ -321,6 +323,8 @@ class Keycodes(KeycodeCategory): KC_RSPC = Keycode(1004, False) KC_LEAD = Keycode(1005, False) KC_LOCK = Keycode(1006, False) + KC_NO = Keycode(1100, False) + KC_TRNS = Keycode(1101, False) class Layers(KeycodeCategory): _KC_DF = 1050 diff --git a/kmk/common/keymap.py b/kmk/common/keymap.py deleted file mode 100644 index c135552..0000000 --- a/kmk/common/keymap.py +++ /dev/null @@ -1,25 +0,0 @@ -from kmk.common.event_defs import key_down_event, key_up_event - - -class Keymap: - def __init__(self, map): - self.map = map - - def parse(self, matrix, store): - state = store.get_state() - - for ridx, row in enumerate(matrix): - for cidx, col in enumerate(row): - if col != state.matrix[ridx][cidx]: - if col: - store.dispatch(key_down_event( - row=ridx, - col=cidx, - keycode=self.map[ridx][cidx], - )) - else: - store.dispatch(key_up_event( - row=ridx, - col=cidx, - keycode=self.map[ridx][cidx], - )) diff --git a/kmk/firmware.py b/kmk/firmware.py index 87885e7..2156beb 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -2,7 +2,6 @@ import logging from kmk.common.event_defs import init_firmware from kmk.common.internal_state import ReduxStore, kmk_reducer -from kmk.common.keymap import Keymap try: from kmk.circuitpython.matrix import MatrixScanner @@ -40,9 +39,6 @@ class Firmware: )) def _subscription(self, state, action): - if self.cached_state is None or self.cached_state.keymap != state.keymap: - self.keymap = Keymap(state.keymap) - if self.cached_state is None or any( getattr(self.cached_state, k) != getattr(state, k) for k in state.__dict__.keys() @@ -55,4 +51,8 @@ class Firmware: def go(self): while True: - self.keymap.parse(self.matrix.raw_scan(), store=self.store) + state = self.store.get_state() + update = self.matrix.scan_for_changes(state.matrix) + + if update: + self.store.dispatch(update) diff --git a/kmk/micropython/matrix.py b/kmk/micropython/matrix.py index 584ea97..3677201 100644 --- a/kmk/micropython/matrix.py +++ b/kmk/micropython/matrix.py @@ -2,6 +2,7 @@ import machine from kmk.common.abstract.matrix_scanner import AbstractMatrixScanner from kmk.common.consts import DiodeOrientation +from kmk.common.event_defs import matrix_changed class MatrixScanner(AbstractMatrixScanner): @@ -52,3 +53,17 @@ class MatrixScanner(AbstractMatrixScanner): opin.value(0) return self._normalize_matrix(matrix) + + def scan_for_changes(self, old_matrix): + matrix = self.raw_scan() + + if any( + any( + col != old_matrix[ridx][cidx] + for cidx, col in enumerate(row) + ) + for ridx, row in enumerate(matrix) + ): + return matrix_changed(matrix) + + return None # The default, but for explicitness diff --git a/kmk/micropython/pyb_hid.py b/kmk/micropython/pyb_hid.py index 88a4e19..d2a60c8 100644 --- a/kmk/micropython/pyb_hid.py +++ b/kmk/micropython/pyb_hid.py @@ -3,8 +3,9 @@ import string from pyb import USB_HID, delay -from kmk.common.event_defs import KEY_DOWN_EVENT, KEY_UP_EVENT -from kmk.common.keycodes import Keycodes, char_lookup +from kmk.common.event_defs import HID_REPORT_EVENT +from kmk.common.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, Keycodes, + char_lookup) class HIDHelper: @@ -48,26 +49,19 @@ class HIDHelper: self.clear_all() def _subscription(self, state, action): - if action['type'] == KEY_DOWN_EVENT: - if action['keycode'].code >= 1000: - return + if action['type'] == HID_REPORT_EVENT: + self.clear_all() - if action['keycode'].is_modifier: - self.add_modifier(action['keycode']) - self.send() - else: - self.add_key(action['keycode']) - self.send() - elif action['type'] == KEY_UP_EVENT: - if action['keycode'].code >= 1000: - return + for key in state.keys_pressed: + if key.code >= FIRST_KMK_INTERNAL_KEYCODE: + continue - if action['keycode'].is_modifier: - self.remove_modifier(action['keycode']) - self.send() - else: - self.remove_key(action['keycode']) - self.send() + if key.is_modifier: + self.add_modifier(key) + else: + self.add_key(key) + + self.send() def send(self): self.logger.debug('Sending HID report: {}'.format(self._evt)) From 8c7c9958f9bb65752115ecf76f95c3416a186425 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 22 Sep 2018 21:57:27 -0700 Subject: [PATCH 09/14] Unbreak the reset key by removing dead code path (for now) --- kmk/common/internal_keycodes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/kmk/common/internal_keycodes.py b/kmk/common/internal_keycodes.py index 2371699..a399c20 100644 --- a/kmk/common/internal_keycodes.py +++ b/kmk/common/internal_keycodes.py @@ -12,8 +12,6 @@ def process_internal_key_event(state, action, changed_key, logger=None): return df(state, action, changed_key, logger=logger) elif changed_key.code == Keycodes.Layers._KC_MO: return mo(state, action, changed_key, logger=logger) - elif changed_key.code == Keycodes.Layers.KC_TILDE: - pass else: return state From 579c32f7035a82f58cfb1fd1d74b2673d8a5f366 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 22 Sep 2018 22:08:21 -0700 Subject: [PATCH 10/14] Disable line-length checks on user-defined keymaps --- Pipfile | 1 + Pipfile.lock | 70 ++++++++++++++++++++++++++++++++++------------------ setup.cfg | 3 +++ 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/Pipfile b/Pipfile index 45f1914..bec5f19 100644 --- a/Pipfile +++ b/Pipfile @@ -16,6 +16,7 @@ ipdb = "*" isort = "*" "flake8-isort" = "*" neovim = "*" +"flake8-per-file-ignores" = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index e92f6e8..7c05b8e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "44e8c37b94a71b7f47fc43f2c98bf17d546f4a5ef7ad1cad5076d4a47fc4515a" + "sha256": "96625b372d35c7f5ed0fd3289ba61afd0bcc034ddad31feb958a107e2751fb0a" }, "pipfile-spec": 6, "requires": { @@ -86,29 +86,37 @@ "index": "pypi", "version": "==2.5" }, + "flake8-per-file-ignores": { + "hashes": [ + "sha256:3c4b1d770fa509aaad997ca147bd3533b730c3f6c48290b69a4265072c465522", + "sha256:4ee4f24cbea5e18e1fefdfccb043e819caf483d16d08e39cb6df5d18b0407275" + ], + "index": "pypi", + "version": "==0.6" + }, "greenlet": { "hashes": [ - "sha256:0411b5bf0de5ec11060925fd811ad49073fa19f995bcf408839eb619b59bb9f7", - "sha256:131f4ed14f0fd28d2a9fa50f79a57d5ed1c8f742d3ccac3d773fee09ef6fe217", - "sha256:13510d32f8db72a0b3e1720dbf8cba5c4eecdf07abc4cb631982f51256c453d1", - "sha256:31dc4d77ef04ab0460d024786f51466dbbc274fda7c8aad0885a6df5ff8d642e", - "sha256:35021d9fecea53b21e4defec0ff3ad69a8e2b75aca1ceddd444a5ba71216547e", - "sha256:426a8ef9e3b97c27e841648241c2862442c13c91ec4a48c4a72b262ccf30add9", - "sha256:58217698193fb94f3e6ff57eed0ae20381a8d06c2bc10151f76c06bb449a3a19", - "sha256:5f45adbbb69281845981bb4e0a4efb8a405f10f3cd6c349cb4a5db3357c6bf93", - "sha256:5fdb524767288f7ad161d2182f7ed6cafc0a283363728dcd04b9485f6411547c", - "sha256:71fbee1f7ef3fb42efa3761a8faefc796e7e425f528de536cfb4c9de03bde885", - "sha256:80bd314157851d06f7db7ca527082dbb0ee97afefb529cdcd59f7a5950927ba0", - "sha256:b843c9ef6aed54a2649887f55959da0031595ccfaf7e7a0ba7aa681ffeaa0aa1", - "sha256:c6a05ef8125503d2d282ccf1448e3599b8a6bd805c3cdee79760fa3da0ea090e", - "sha256:deeda2769a52db840efe5bf7bdf7cefa0ae17b43a844a3259d39fb9465c8b008", - "sha256:e66f8b09eec1afdcab947d3a1d65b87b25fde39e9172ae1bec562488335633b4", - "sha256:e8db93045414980dbada8908d49dbbc0aa134277da3ff613b3e548cb275bdd37", - "sha256:f1cc268a15ade58d9a0c04569fe6613e19b8b0345b64453064e2c3c6d79051af", - "sha256:fe3001b6a4f3f3582a865b9e5081cc548b973ec20320f297f5e2d46860e9c703", - "sha256:fe85bf7adb26eb47ad53a1bae5d35a28df16b2b93b89042a3a28746617a4738d" + "sha256:000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0", + "sha256:0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28", + "sha256:23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8", + "sha256:37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304", + "sha256:51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0", + "sha256:8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214", + "sha256:81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043", + "sha256:853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6", + "sha256:8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625", + "sha256:9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc", + "sha256:9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638", + "sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163", + "sha256:a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4", + "sha256:a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490", + "sha256:ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248", + "sha256:bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939", + "sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87", + "sha256:d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720", + "sha256:d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656" ], - "version": "==0.4.14" + "version": "==0.4.15" }, "ipdb": { "hashes": [ @@ -189,6 +197,12 @@ ], "version": "==0.3.1" }, + "pathmatch": { + "hashes": [ + "sha256:b35db907d0532c66132e5bc8aaa20dbfae916441987c8f0abd53ac538376d9a7" + ], + "version": "==0.2.1" + }, "pexpect": { "hashes": [ "sha256:2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba", @@ -269,10 +283,10 @@ }, "testfixtures": { "hashes": [ - "sha256:7e4df89a8bf8b8905464160f08aff131a36f0b33654fe4f9e4387afe546eae25", - "sha256:bcadbad77526cc5fc38bfb2ab80da810d7bde56ffe4c7fdb8e2bba122ded9620" + "sha256:334497d26344e8c0c5d01b4d785a1c83464573151e6a5f7ab250eb7981d452ec", + "sha256:53c06c1feb0bf378d63c54d1d96858978422d5a34793b39f0dcb0e44f8ec26f4" ], - "version": "==6.2.0" + "version": "==6.3.0" }, "traitlets": { "hashes": [ @@ -281,6 +295,14 @@ ], "version": "==4.3.2" }, + "typing": { + "hashes": [ + "sha256:4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d", + "sha256:57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4", + "sha256:a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a" + ], + "version": "==3.6.6" + }, "wcwidth": { "hashes": [ "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", diff --git a/setup.cfg b/setup.cfg index 02f98c8..0c75357 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,9 @@ [flake8] exclude = .git,__pycache__,vendor,.venv max_line_length = 99 +ignore = X100 +per-file-ignores = + boards/**/*.py: E501 [isort] known_third_party = analogio,bitbangio,bleio,board,busio,digitalio,framebuf,gamepad,gc,microcontroller,micropython,pulseio,pyb,pydux,uio,ubluepy,machine,pyb From 634e6f14e66fef4332ea7a71bed5f5ffed0cce44 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sat, 22 Sep 2018 22:34:20 -0700 Subject: [PATCH 11/14] Support TO and TG layer switching --- kmk/common/internal_keycodes.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/kmk/common/internal_keycodes.py b/kmk/common/internal_keycodes.py index a399c20..4263227 100644 --- a/kmk/common/internal_keycodes.py +++ b/kmk/common/internal_keycodes.py @@ -12,6 +12,10 @@ def process_internal_key_event(state, action, changed_key, logger=None): return df(state, action, changed_key, logger=logger) elif changed_key.code == Keycodes.Layers._KC_MO: return mo(state, action, changed_key, logger=logger) + elif changed_key.code == Keycodes.Layers._KC_TG: + return tg(state, action, changed_key, logger=logger) + elif changed_key.code == Keycodes.Layers._KC_TO: + return to(state, action, changed_key, logger=logger) else: return state @@ -23,7 +27,8 @@ def tilde(state, action, changed_key, logger): def df(state, action, changed_key, logger): """Switches the default layer""" - state.active_layers[0] = changed_key.layer + if action['type'] == KEY_DOWN_EVENT: + state.active_layers[0] = changed_key.layer return state @@ -49,12 +54,26 @@ def lt(layer, kc): """Momentarily activates layer if held, sends kc if tapped""" -def tg(layer): - """Toggles the layer (enables it if no active, and vise versa)""" +def tg(state, action, changed_key, logger): + """Toggles the layer (enables it if not active, and vise versa)""" + if action['type'] == KEY_DOWN_EVENT: + if changed_key.layer in state.active_layers: + state.active_layers = [ + layer for layer in state.active_layers + if layer != changed_key.layer + ] + else: + state.active_layers.append(changed_key.layer) + + return state -def to(layer): +def to(state, action, changed_key, logger): """Activates layer and deactivates all other layers""" + if action['type'] == KEY_DOWN_EVENT: + state.active_layers = [changed_key.layer] + + return state def tt(layer): From 5cdd678073c06d0ee935cb019458ab8a2ba6d7c9 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 23 Sep 2018 00:22:15 -0700 Subject: [PATCH 12/14] We do not support the NRF52832 anyway, stop building it in Circle --- .circleci/config.yml | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b897dd6..546765c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,25 +41,6 @@ jobs: - run: make BOARD=boards/noop.py build-pyboard - build_feather_nrf52832: - docker: - - image: 'python:3.7' - - environment: - KMK_TEST: 1 - PIPENV_VENV_IN_PROJECT: 1 - - steps: - - checkout - - restore_cache: - keys: - - v1-kmk-venv-{{ checksum "Pipfile.lock" }} - - - run: pip install pipenv==2018.7.1 - - run: apt-get update && apt-get install -y gcc-arm-none-eabi gettext wget unzip - - - run: make BOARD=boards/noop.py build-feather-nrf52832 - build_teensy_31: docker: - image: 'python:3.7' @@ -97,14 +78,6 @@ workflows: only: /.*/ requires: - lint - - build_feather_nrf52832: - filters: - branches: - only: /.*/ - tags: - only: /.*/ - requires: - - lint - build_teensy_31: filters: branches: From 666f36d41ab5513c43cf55132ad6949162955b96 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 23 Sep 2018 01:04:10 -0700 Subject: [PATCH 13/14] Unbreak my own layout --- boards/klardotsh/threethree_matrix_pyboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boards/klardotsh/threethree_matrix_pyboard.py b/boards/klardotsh/threethree_matrix_pyboard.py index c3b9c20..1787700 100644 --- a/boards/klardotsh/threethree_matrix_pyboard.py +++ b/boards/klardotsh/threethree_matrix_pyboard.py @@ -20,7 +20,7 @@ def main(): [ [KC.MO(1), KC.H, KC.RESET], [KC.MO(2), KC.I, KC.ENTER], - [KC.CTRL, KC.SPACE, KC.SHIFT], + [KC.LCTRL, KC.SPACE, KC.SHIFT], ], [ [KC.TRNS, KC.B, KC.C], From 6977ae94b48969c12b5c1fdadc8114583c308566 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Sun, 23 Sep 2018 01:06:04 -0700 Subject: [PATCH 14/14] Finish unbreaking my own layout... --- boards/klardotsh/threethree_matrix_pyboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boards/klardotsh/threethree_matrix_pyboard.py b/boards/klardotsh/threethree_matrix_pyboard.py index 1787700..a7af387 100644 --- a/boards/klardotsh/threethree_matrix_pyboard.py +++ b/boards/klardotsh/threethree_matrix_pyboard.py @@ -20,7 +20,7 @@ def main(): [ [KC.MO(1), KC.H, KC.RESET], [KC.MO(2), KC.I, KC.ENTER], - [KC.LCTRL, KC.SPACE, KC.SHIFT], + [KC.LCTRL, KC.SPACE, KC.LSHIFT], ], [ [KC.TRNS, KC.B, KC.C],