Merge pull request #21 from KMKfw/topic-internal-keycodes

Internal Keycodes and partial layer support (MO, DF, TO, TG)
This commit is contained in:
Josh Klar 2018-09-23 03:03:26 -07:00 committed by GitHub
commit 5f9f3be966
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 463 additions and 162 deletions

View File

@ -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
View File

@ -106,6 +106,7 @@ venv.bak/
.ampy
.submodules
.circuitpy-deps
.idea/
.micropython-deps
# Pycharms cruft
.idea

View File

@ -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

View File

@ -16,6 +16,7 @@ ipdb = "*"
isort = "*"
"flake8-isort" = "*"
neovim = "*"
"flake8-per-file-ignores" = "*"
[requires]
python_version = "3.7"

70
Pipfile.lock generated
View File

@ -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",

View File

@ -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(

View File

@ -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(

View File

@ -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):

View File

@ -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

View 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"""

View File

@ -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

View File

@ -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,),
}

View File

@ -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],
))

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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