Merge pull request #21 from KMKfw/topic-internal-keycodes
Internal Keycodes and partial layer support (MO, DF, TO, TG)
This commit is contained in:
commit
5f9f3be966
@ -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:
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -106,6 +106,7 @@ venv.bak/
|
||||
.ampy
|
||||
.submodules
|
||||
.circuitpy-deps
|
||||
|
||||
.idea/
|
||||
.micropython-deps
|
||||
|
||||
# Pycharms cruft
|
||||
.idea
|
||||
|
3
Makefile
3
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
|
||||
|
1
Pipfile
1
Pipfile
@ -16,6 +16,7 @@ ipdb = "*"
|
||||
isort = "*"
|
||||
"flake8-isort" = "*"
|
||||
neovim = "*"
|
||||
"flake8-per-file-ignores" = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
|
70
Pipfile.lock
generated
70
Pipfile.lock
generated
@ -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",
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
80
kmk/common/internal_keycodes.py
Normal file
80
kmk/common/internal_keycodes.py
Normal file
@ -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"""
|
@ -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
|
||||
|
@ -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,),
|
||||
}
|
||||
|
@ -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],
|
||||
))
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user