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: 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/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 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/boards/kdb424/handwire_planck_pyboard.py b/boards/kdb424/handwire_planck_pyboard.py index e7d600a..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.A, KC.A, 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( diff --git a/boards/klardotsh/threethree_matrix_pyboard.py b/boards/klardotsh/threethree_matrix_pyboard.py index 1e5b48d..a7af387 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.ESC, KC.H, KC.BACKSPACE], - [KC.TAB, 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.LCTRL, KC.SPACE, KC.LSHIFT], + ], + [ + [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/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..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 new file mode 100644 index 0000000..4263227 --- /dev/null +++ b/kmk/common/internal_keycodes.py @@ -0,0 +1,80 @@ +import logging + +from kmk.common.event_defs import KEY_DOWN_EVENT, KEY_UP_EVENT +from kmk.common.keycodes import Keycodes + + +def process_internal_key_event(state, action, changed_key, logger=None): + if logger is None: + logger = logging.getLogger(__name__) + + 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_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 + + +def tilde(state, action, changed_key, logger): + # TODO Actually process keycodes + return state + + +def df(state, action, changed_key, logger): + """Switches the default layer""" + if action['type'] == KEY_DOWN_EVENT: + state.active_layers[0] = changed_key.layer + + return state + + +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 != changed_key.layer + ] + elif action['type'] == KEY_DOWN_EVENT: + state.active_layers.append(changed_key.layer) + + return state + + +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(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(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): + """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..ce5962b 100644 --- a/kmk/common/internal_state.py +++ b/kmk/common/internal_state.py @@ -2,8 +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.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: @@ -15,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)) @@ -48,33 +57,62 @@ class InternalState: col_pins = [] 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 __enter__(self): + return self + + def __exit__(self, type, value, traceback): + pass + + 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 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): @@ -90,36 +128,57 @@ def kmk_reducer(state=None, action=None, logger=None): return state - if action['type'] == KEY_UP_EVENT: - return state.copy( - 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['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: - return state.copy( + 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 changed_key.code >= FIRST_KMK_INTERNAL_KEYCODE: + return process_internal_key_event(newstate, action, changed_key, logger=logger) + + 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'], @@ -129,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 63fddb5..09e226a 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -8,7 +8,10 @@ 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')) class KeycodeCategory(type): @@ -312,6 +315,77 @@ 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) + KC_NO = Keycode(1100, False) + KC_TRNS = Keycode(1101, False) + + class Layers(KeycodeCategory): + _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) + + 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 @@ -324,6 +398,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_GRAVE,), } 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 6af51ac..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 @@ -12,8 +11,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, + diode_orientation, hid=None, log_level=logging.NOTSET, ): logger = logging.getLogger(__name__) logger.setLevel(log_level) @@ -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 f775719..3677201 100644 --- a/kmk/micropython/matrix.py +++ b/kmk/micropython/matrix.py @@ -2,10 +2,11 @@ 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): - 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 +20,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 @@ -51,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 b257f56..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,20 +49,19 @@ class HIDHelper: self.clear_all() def _subscription(self, state, action): - if action['type'] == KEY_DOWN_EVENT: - 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'].is_modifier: - self.remove_modifier(action['keycode']) - self.send() - else: - self.remove_key(action['keycode']) - self.send() + if action['type'] == HID_REPORT_EVENT: + self.clear_all() + + for key in state.keys_pressed: + if key.code >= FIRST_KMK_INTERNAL_KEYCODE: + continue + + 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)) 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