Merge pull request #72 from KMKfw/topic-planck-klaranck

Congressional Bill #72: Add support for the Planck Rev 6 when wired up to an external MCU (breadboarded), and other changes
This commit is contained in:
Josh Klar 2018-10-19 00:11:29 -07:00 committed by GitHub
commit 64202e2ec1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1006 additions and 1628 deletions

View File

@ -16,22 +16,6 @@ jobs:
- run: make test - run: make test
build_pyboard:
docker:
- image: 'kmkfw/base'
environment:
KMK_TEST: 1
PIPENV_VENV_IN_PROJECT: 1
steps:
- checkout
- restore_cache:
keys:
- v2-kmk-venv-{{ checksum "Pipfile.lock" }}
- run: make SKIP_KEYMAP_VALIDATION=1 USER_KEYMAP=user_keymaps/noop.py build-pyboard
workflows: workflows:
version: 2 version: 2
build-deploy: build-deploy:
@ -42,11 +26,3 @@ workflows:
only: /.*/ only: /.*/
tags: tags:
only: /.*/ only: /.*/
- build_pyboard:
filters:
branches:
only: /.*/
tags:
only: /.*/
requires:
- test

View File

@ -15,7 +15,7 @@ AMPY_DELAY ?= 1.5
ARDUINO ?= /usr/share/arduino ARDUINO ?= /usr/share/arduino
PIPENV ?= $(shell which pipenv) PIPENV ?= $(shell which pipenv)
all: copy-kmk copy-keymap copy-main.py all: copy-kmk copy-keymap
.docker_base: Dockerfile_base .docker_base: Dockerfile_base
@echo "===> Building Docker base image kmkfw/base:${DOCKER_BASE_TAG}" @echo "===> Building Docker base image kmkfw/base:${DOCKER_BASE_TAG}"
@ -55,17 +55,7 @@ powerwash: clean
@echo "===> Removing pipenv-managed virtual environment" @echo "===> Removing pipenv-managed virtual environment"
@$(PIPENV) --rm || true @$(PIPENV) --rm || true
test: lint micropython-build-unix test: lint
@echo "===> Testing keymap_sanity_check.py script"
@echo " --> Known good layout should pass..."
@MICROPYPATH=tests/test_data:./ ./bin/micropython.sh bin/keymap_sanity_check.py keymaps/known_good.py
@echo " --> Layer with ghosted MO should fail..."
@MICROPYPATH=tests/test_data:./ ./bin/micropython.sh bin/keymap_sanity_check.py keymaps/ghosted_layer_mo.py 2>/dev/null && exit 1 || exit 0
@echo " --> Sharing a pin between rows/cols should fail..."
@MICROPYPATH=tests/test_data:./ ./bin/micropython.sh bin/keymap_sanity_check.py keymaps/duplicated_pins_between_row_col.py 2>/dev/null && exit 1 || exit 0
@echo " --> Sharing a pin between two rows should fail..."
@MICROPYPATH=tests/test_data:./ ./bin/micropython.sh bin/keymap_sanity_check.py keymaps/duplicate_row_pins.py 2>/dev/null && exit 1 || exit 0
@echo "===> The sanity checker is sane, unlike klardotsh"
.submodules: .gitmodules submodules.toml .submodules: .gitmodules submodules.toml
@echo "===> Pulling dependencies, this may take several minutes" @echo "===> Pulling dependencies, this may take several minutes"
@ -99,57 +89,6 @@ build/micropython/ports/unix/modules/.kmk_frozen: upy-freeze.txt submodules.toml
xargs -I '{}' rsync -ah {} build/micropython/ports/unix/modules/ xargs -I '{}' rsync -ah {} build/micropython/ports/unix/modules/
@touch $@ @touch $@
freeze-stm32-build-deps: build/micropython/ports/stm32/freeze/.kmk_frozen
build/micropython/ports/stm32/freeze/.kmk_frozen: upy-freeze.txt submodules.toml
@echo "===> Preparing vendored dependencies for bundling into MicroPython for STM32"
@echo "===> Preparing vendored dependencies for bundling into MicroPython for STM32" >> .build.log
@mkdir -p build/micropython/ports/stm32/freeze/
@rm -rf build/micropython/ports/stm32/freeze/*
@cat upy-freeze.txt | egrep -v '(^#|^\s*$|^\s*\t*#)' | grep MICROPY | cut -d'|' -f2- | \
xargs -I '{}' rsync -ah {} build/micropython/ports/stm32/freeze/
@touch $@
micropython-freeze-kmk-stm32: freeze-stm32-build-deps
@echo "===> Preparing KMK source for bundling into MicroPython for STM32"
@echo "===> Preparing KMK source for bundling into MicroPython for STM32" >> .build.log
@rm -rf build/micropython/ports/stm32/freeze/kmk*
@rsync -ah kmk build/micropython/ports/stm32/freeze/ --exclude kmk/circuitpython
micropython-build-pyboard:
@echo "===> Building MicroPython for STM32 - PYBV11"
@echo "===> Building MicroPython for STM32 - PYBV11" >> .build.log
@pipenv run $(MAKE) -j4 -C build/micropython/ports/stm32/ BOARD=PYBV11 FROZEN_MPY_DIR=freeze all 2>&1 >> .build.log
micropython-flash-pyboard: micropython-build-pyboard
@echo "===> Flashing MicroPython with KMK and your keymap"
@echo "===> Flashing MicroPython with KMK and your keymap" >> .build.log
@pipenv run $(MAKE) -j4 -C build/micropython/ports/stm32/ BOARD=PYBV11 FROZEN_MPY_DIR=freeze deploy 2>&1 >> .build.log
ifndef USER_KEYMAP
build-pyboard:
@echo "===> Must provide a USER_KEYMAP (usually from user_keymaps/...) to build!" && exit 1
flash-pyboard:
@echo "===> Must provide a USER_KEYMAP (usually from user_keymaps/...) to build!" && exit 1
else
ifndef SKIP_KEYMAP_VALIDATION
build-pyboard: clean-build-log lint micropython-deps micropython-freeze-kmk-stm32 micropython-build-unix
else
build-pyboard: clean-build-log lint micropython-deps micropython-freeze-kmk-stm32
endif
@echo "===> Preparing keyboard script for bundling into MicroPython for STM32"
ifndef SKIP_KEYMAP_VALIDATION
@MICROPYPATH=./ ./bin/micropython.sh bin/keymap_sanity_check.py ${USER_KEYMAP}
endif
@rsync -ah ${USER_KEYMAP} build/micropython/ports/stm32/freeze/main.py
@rsync -ah main.py build/micropython/ports/stm32/freeze/_main.py
@rsync -ah kmk/entrypoints/handwire/pyboard_boot.py build/micropython/ports/stm32/freeze/_boot.py
@$(MAKE) AMPY_PORT=/dev/ttyACM0 AMPY_BAUD=115200 micropython-build-pyboard
flash-pyboard: build-pyboard micropython-flash-pyboard
endif
reset-bootloader: reset-bootloader:
@echo "===> Rebooting your board to bootloader (safe to ignore file not found errors)" @echo "===> Rebooting your board to bootloader (safe to ignore file not found errors)"
@-timeout -k 5s 10s $(PIPENV) run ampy -p /dev/ttyACM0 -d ${AMPY_DELAY} -b ${AMPY_BAUD} run util/bootloader.py @-timeout -k 5s 10s $(PIPENV) run ampy -p /dev/ttyACM0 -d ${AMPY_DELAY} -b ${AMPY_BAUD} run util/bootloader.py
@ -173,30 +112,18 @@ copy-kmk:
echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1 echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1
endif endif
ifdef MOUNTPOINT
$(MOUNTPOINT)/main.py: main.py
@echo "===> Copying a basic main.py"
@rsync -rh main.py $@
@sync
copy-main.py: $(MOUNTPOINT)/main.py
else
copy-main.py:
echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1
endif
ifdef MOUNTPOINT ifdef MOUNTPOINT
ifndef USER_KEYMAP ifndef USER_KEYMAP
$(MOUNTPOINT)/kmk_keyboard.py: $(MOUNTPOINT)/main.py:
@echo "**** USER_KEYMAP must be defined (ex. USER_KEYMAP=user_keymaps/noop.py) ****" && exit 1 @echo "**** USER_KEYMAP must be defined (ex. USER_KEYMAP=user_keymaps/noop.py) ****" && exit 1
else else
$(MOUNTPOINT)/kmk_keyboard.py: $(USER_KEYMAP) $(MOUNTPOINT)/main.py: $(USER_KEYMAP)
@echo "===> Copying your keymap to kmk_keyboard.py" @echo "===> Copying your keymap to main.py"
@rsync -rh $(USER_KEYMAP) $@ @rsync -rh $(USER_KEYMAP) $@
@sync @sync
endif # USER_KEYMAP endif # USER_KEYMAP
copy-keymap: $(MOUNTPOINT)/kmk_keyboard.py copy-keymap: $(MOUNTPOINT)/main.py
else else
copy-keymap: copy-keymap:
echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1 echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1

View File

@ -1,68 +0,0 @@
#!/usr/bin/env micropython
import sys
import uos
from kmk.keycodes import Keycodes, RawKeycodes
if len(sys.argv) < 2:
print('Must provide a keymap to test as first argument', file=sys.stderr)
sys.exit(200)
user_keymap_file = sys.argv[1]
if user_keymap_file.endswith('.py'):
user_keymap_file = user_keymap_file[:-3]
# Before we can import the user's keymap, we need to wrangle sys.path to
# add our stub modules. Before we can do THAT, we have to figure out where
# we actually are, and that's not the most trivial thing in MicroPython!
#
# The hack here is to see if we can find ourselves in whatever uPy thinks
# the current directory is. If we can, we need to head up a level. Obviously,
# if the layout of the KMK repo ever changes, this script will need updated
# or all hell will break loose.
# First, hack around https://github.com/micropython/micropython/issues/2322,
# where frozen modules aren't available if MicroPython is running a script
# rather than via REPL
sys.path.insert(0, '')
if any(fname == 'keymap_sanity_check.py' for fname, _, _ in uos.ilistdir()):
sys.path.extend(('../', '../upy-unix-stubs/'))
else:
sys.path.extend(('./', './upy-unix-stubs'))
user_keymap = __import__(user_keymap_file)
if hasattr(user_keymap, 'cols') or hasattr(user_keymap, 'rows'):
assert hasattr(user_keymap, 'cols'), 'Handwired keyboards must have both rows and cols defined'
assert hasattr(user_keymap, 'rows'), 'Handwired keyboards must have both rows and cols defined'
# Ensure that no pins are duplicated in a handwire config
# This is the same check done in the MatrixScanners, relying
# on the __repr__ of the objects to be unique (because generally,
# Pin objects themselves are not hashable)
assert len(user_keymap.cols) == len({p for p in user_keymap.cols}), \
'Cannot use a single pin for multiple columns'
assert len(user_keymap.rows) == len({p for p in user_keymap.rows}), \
'Cannot use a single pin for multiple rows'
unique_pins = {repr(c) for c in user_keymap.cols} | {repr(r) for r in user_keymap.rows}
assert len(unique_pins) == len(user_keymap.cols) + len(user_keymap.rows), \
'Cannot use a pin as both a column and row'
assert hasattr(user_keymap, 'keymap'), 'Must define a keymap array'
assert len(user_keymap.keymap), 'Keymap must contain at least one layer'
for lidx, layer in enumerate(user_keymap.keymap):
assert len(layer), 'Layer {} must contain at least one row'.format(lidx)
assert all(len(row) for row in layer), 'Layer {} must not contain empty rows'.format(lidx)
for ridx, row in enumerate(layer):
for cidx, key in enumerate(row):
if key.code == RawKeycodes.KC_MO:
assert user_keymap.keymap[key.layer][ridx][cidx] == Keycodes.KMK.KC_TRNS, \
('The physical key used for MO layer switching must be KC_TRNS on the '
'target layer or you will get stuck on that layer.')

View File

@ -1,51 +1,177 @@
# Keymap # Keymap
The keymap is a very simple set of instruction for your keyboard.
An example keymap for a 4x3 macro pad Keymaps in KMK are simple Python class objects with various attributes assigned
(some by default, however all are overridable).
The basics of what you'll need to get started are:
- Import the `Firmware` object for your keyboard from `kmk.boards` (or, if
handwiring your keyboard, import `Firmware` from the appropriate MCU for your
board from `kmk.mcus`. See `hardware.md` for the list of supported boards and
map this to the correct Python module under either of those paths.
- Add a file to `user_keymaps/your_username` called whatever you'd like
- Assign a `Firmware` instance to a variable (ex. `keyboard = Firmware()` - note
the parentheses)
- Make sure this `Firmware` instance is actually run at the end of the file with
a block such as the following:
```python ```python
keymap = [ if __name__ == '__main__':
keyboard.go()
```
- Assign pins and your diode orientation (only necessary on handwire keyboards),
for example:
```python
col_pins = (P.SCK, P.MOSI, P.MISO, P.RX, P.TX, P.D4)
row_pins = (P.D10, P.D11, P.D12, P.D13, P.D9, P.D6, P.D5, P.SCL)
rollover_cols_every_rows = 4
diode_orientation = DiodeOrientation.COLUMNS
swap_indicies = {
(3, 3): (3, 9),
(3, 4): (3, 10),
(3, 5): (3, 11),
}
```
The pins should be based on whatever CircuitPython calls pins on your particular
board. You can find these in the REPL on your CircuitPython device:
```python
import board
print(dir(board))
```
> Note: `rollover_cols_every_rows` is only supported with
> `DiodeOrientation.COLUMNS`, not `DiodeOrientation.ROWS`. It is used for boards
> such as the Planck Rev6 which reuse column pins to simulate a 4x12 matrix in
> the form of an 8x6 matrix
> Note: `swap_indicies` is used to literally flip two keys' positions in the
> matrix. This is pretty rarely needed, but for example the Planck Rev6 in full
> 1u Grid mode swaps the bottom three right keys on each "half", thus the
> example above
You can further define a bunch of other stuff:
- `debug_enabled` which will spew a ton of debugging information to the serial
console. This is very rarely needed, but can provide very valuable information
if you need to open an issue.
- `unicode_mode` from `kmk.consts.UnicodeModes`, which defines the default
operating system implementation to use for unicode sequences (see examples
below, or `unicode.md`. This can be changed after boot with a key (see
`keycodes.md`)
- `tap_time` which defines how long `KC.TT` and `KC.LT` will wait before
considering a key "held" (see `keycodes.md`)
- `leader_dictionary`, which defines leader sequences (see `leader.md`), defined
as tuples of keycode objects (or you can use
`kmk.keycodes.generate_leader_dictionary_seq` with a string)
We also support unicode sequences (emojis, emoticons, umlauted letters,
whatever) if your operating system and system setup do! See `unicode.md` for
details.
Here's a giant example of all the above. This is my personal 4x12 matrix layout
running on a Planck Rev6 PCB, with a Feather M4 Express wired up to the outer
matrix pins (in somewhat of a "spider" setup), utilizing most of the above
features:
```python
from kmk.boards.klarank import Firmware
from kmk.consts import UnicodeModes
from kmk.keycodes import KC
from kmk.keycodes import generate_leader_dictionary_seq as glds
from kmk.macros.simple import send_string
from kmk.macros.unicode import compile_unicode_string_sequences as cuss
keyboard = Firmware()
keyboard.debug_enabled = True
keyboard.unicode_mode = UnicodeModes.LINUX
_______ = KC.TRNS
xxxxxxx = KC.NO
emoticons = cuss({
# Emojis
'BEER': r'🍺',
'BEER_TOAST': r'🍻',
'FACE_CUTE_SMILE': r'😊',
'FACE_HEART_EYES': r'😍',
'FACE_JOY': r'😂',
'FACE_SWEAT_SMILE': r'😅',
'FACE_THINKING': r'🤔',
'FIRE': r'🔥',
'FLAG_CA': r'🇨🇦',
'FLAG_US': r'🇺🇸',
'HAND_CLAP': r'👏',
'HAND_HORNS': r'🤘',
'HAND_OK': r'👌',
'HAND_THUMB_DOWN': r'👎',
'HAND_THUMB_UP': r'👍',
'HAND_WAVE': r'👋',
'HEART': r'❤️',
'MAPLE_LEAF': r'🍁',
'POOP': r'💩',
'TADA': r'🎉',
# Emoticons, but fancier
'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻',
'CELEBRATORY_GLITTER': r'+。:.゚ヽ(´∀。)ノ゚.:。+゚゚+。:.゚ヽ(*´∀)ノ゚.:。+゚',
'SHRUGGIE': r'¯\_(ツ)_/¯',
'TABLE_FLIP': r'(╯°□°)╯︵ ┻━┻',
})
WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.")
keyboard.leader_dictionary = {
glds('hello'): send_string('hello world from kmk macros'),
glds('wpm'): WPM,
glds('atf'): emoticons.ANGRY_TABLE_FLIP,
glds('tf'): emoticons.TABLE_FLIP,
glds('fca'): emoticons.FLAG_CA,
glds('fus'): emoticons.FLAG_US,
glds('cel'): emoticons.CELEBRATORY_GLITTER,
}
keyboard.keymap = [
[ [
[KC.GESC, KC.A, KC.B], [KC.GESC, KC.QUOT, KC.COMM, KC.DOT, KC.P, KC.Y, KC.F, KC.G, KC.C, KC.R, KC.L, KC.BSPC],
[KC.C, KC.D, KC.E], [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.F, KC.G, KC.H], [KC.LGUI, KC.SCLN, KC.Q, KC.J, KC.K, KC.X, KC.B, KC.M, KC.W, KC.V, KC.Z, KC.LALT],
[KC.MO(1), KC.I, KC.J], [KC.LCTL, KC.LEAD, KC.LSHIFT(KC.LGUI), KC.MO(2), KC.MO(3), KC.LSFT, KC.SPC, KC.MO(1), KC.LEFT, KC.DOWN, KC.UP, KC.RGHT],
], ],
[ [
[KC.TRNS, KC.K, KC.RESET], [KC.GESC, xxxxxxx, xxxxxxx, KC.F10, KC.F11, KC.F12, xxxxxxx, KC.PSLS, KC.N7, KC.N8, KC.N9, KC.BSPC],
[KC.L, KC.M, KC.N], [KC.TAB, xxxxxxx, xxxxxxx, KC.F7, KC.F8, KC.F9, xxxxxxx, KC.PAST, KC.N4, KC.N5, KC.N6, _______],
[KC.O, KC.P, KC.Q], [KC.LGUI, xxxxxxx, xxxxxxx, KC.F4, KC.F5, KC.F6, xxxxxxx, KC.PMNS, KC.N1, KC.N2, KC.N3, _______],
[KC.TRNS, KC.R, KC.S], [KC.LCTL, xxxxxxx, _______, KC.F1, KC.F2, KC.F3, KC.SPC, _______, KC.N0, KC.DOT, xxxxxxx, KC.EQL],
],
[
[KC.GESC, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.BSLS, KC.LBRC, KC.RBRC, KC.DEL],
[KC.TAB, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.MINS],
[KC.LGUI, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.LBRC, xxxxxxx, xxxxxxx, KC.INS],
[KC.LCTL, xxxxxxx, _______, _______, xxxxxxx, _______, xxxxxxx, xxxxxxx, KC.HOME, KC.PGDN, KC.PGUP, KC.END],
],
[
[KC.GRV, KC.EXLM, KC.AT, KC.HASH, KC.DLR, KC.PERC, KC.CIRC, KC.AMPR, KC.ASTR, KC.LPRN, KC.RPRN, KC.SLSH],
[KC.TAB, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.MINS],
[KC.LGUI, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx],
[KC.LCTL, xxxxxxx, xxxxxxx, xxxxxxx, _______, _______, xxxxxxx, xxxxxxx, KC.MUTE, KC.VOLD, KC.VOLU, xxxxxxx],
], ],
] ]
```
# Handwire if __name__ == '__main__':
When hand wiring custom keyboards, you will have to set the board up so KMK keyboard.go()
knows where things are. This is the basic SETUP.
```python
from kmk.consts import DiodeOrientation
from kmk.keycodes import KC
from kmk.pins import Pin as P
from kmk.entrypoints.circuitpython_samd51 import main
from kmk.firmware import Firmware
cols = (P.D11, P.D10, P.D9)
rows = (P.A2, P.A3, P.A4, P.A5)
diode_orientation = DiodeOrientation.COLUMNS
```
The main things that need set up are:
1. Your microcontroller will need to be set here.
```python
from kmk.entrypoints.circuitpython_samd51 import main
```
2. Set your row and column pins up
```python
cols = (P.D11, P.D10, P.D9)
rows = (P.A2, P.A3, P.A4, P.A5)
```
3. Set your diode orientation
```python
diode_orientation = DiodeOrientation.COLUMNS
``` ```

View File

@ -1,163 +0,0 @@
import logging
from kmk.consts import HIDReportTypes
from kmk.event_defs import HID_REPORT_EVENT
from kmk.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, ConsumerKeycode,
ModifierKeycode)
class AbstractHidHelper:
REPORT_BYTES = 8
def __init__(self, store, log_level=logging.NOTSET):
self.logger = logging.getLogger(__name__)
self.logger.setLevel(log_level)
self.store = store
self.store.subscribe(
lambda state, action: self._subscription(state, action),
)
self._evt = bytearray(self.REPORT_BYTES)
self.report_device = memoryview(self._evt)[0:1]
self.report_device[0] = HIDReportTypes.KEYBOARD
# Landmine alert for HIDReportTypes.KEYBOARD: byte index 1 of this view
# is "reserved" and evidently (mostly?) unused. However, other modes (or
# at least consumer, so far) will use this byte, which is the main reason
# this view exists. For KEYBOARD, use report_mods and report_non_mods
self.report_keys = memoryview(self._evt)[1:]
self.report_mods = memoryview(self._evt)[1:2]
self.report_non_mods = memoryview(self._evt)[3:]
self.post_init()
def post_init(self):
pass
def _subscription(self, state, action):
if action.type == HID_REPORT_EVENT:
self.clear_all()
consumer_key = None
for key in state.keys_pressed:
if isinstance(key, ConsumerKeycode):
consumer_key = key
break
reporting_device = self.report_device[0]
needed_reporting_device = HIDReportTypes.KEYBOARD
if consumer_key:
needed_reporting_device = HIDReportTypes.CONSUMER
if reporting_device != needed_reporting_device:
# If we are about to change reporting devices, release
# all keys and close our proverbial tab on the existing
# device, or keys will get stuck (mostly when releasing
# media/consumer keys)
self.send()
self.report_device[0] = needed_reporting_device
if consumer_key:
self.add_key(consumer_key)
else:
for key in state.keys_pressed:
if key.code >= FIRST_KMK_INTERNAL_KEYCODE:
continue
if isinstance(key, ModifierKeycode):
self.add_modifier(key)
else:
self.add_key(key)
if key.has_modifiers:
for mod in key.has_modifiers:
self.add_modifier(mod)
self.send()
def hid_send(self, evt):
raise NotImplementedError('hid_send(evt) must be implemented')
def send(self):
self.logger.debug('Sending HID report: {}'.format(self._evt))
self.hid_send(self._evt)
return self
def clear_all(self):
for idx, _ in enumerate(self.report_keys):
self.report_keys[idx] = 0x00
return self
def clear_non_modifiers(self):
for idx, _ in enumerate(self.report_non_mods):
self.report_non_mods[idx] = 0x00
return self
def add_modifier(self, modifier):
if isinstance(modifier, ModifierKeycode):
if modifier.code == ModifierKeycode.FAKE_CODE:
for mod in modifier.has_modifiers:
self.report_mods[0] |= mod
else:
self.report_mods[0] |= modifier.code
else:
self.report_mods[0] |= modifier
return self
def remove_modifier(self, modifier):
if isinstance(modifier, ModifierKeycode):
if modifier.code == ModifierKeycode.FAKE_CODE:
for mod in modifier.has_modifiers:
self.report_mods[0] ^= mod
else:
self.report_mods[0] ^= modifier.code
else:
self.report_mods[0] ^= modifier
return self
def add_key(self, key):
# Try to find the first empty slot in the key report, and fill it
placed = False
where_to_place = self.report_non_mods
if self.report_device[0] == HIDReportTypes.CONSUMER:
where_to_place = self.report_keys
for idx, _ in enumerate(where_to_place):
if where_to_place[idx] == 0x00:
where_to_place[idx] = key.code
placed = True
break
if not placed:
self.logger.warning('Out of space in HID report, could not add key')
return self
def remove_key(self, key):
removed = False
where_to_place = self.report_non_mods
if self.report_device[0] == HIDReportTypes.CONSUMER:
where_to_place = self.report_keys
for idx, _ in enumerate(where_to_place):
if where_to_place[idx] == key.code:
where_to_place[idx] = 0x00
removed = True
if not removed:
self.logger.warning('Tried to remove key that was not added')
return self

18
kmk/boards/klarank.py Normal file
View File

@ -0,0 +1,18 @@
from kmk.consts import DiodeOrientation
from kmk.mcus.circuitpython_samd51 import Firmware as _Firmware
from kmk.pins import Pin as P
class Firmware(_Firmware):
# physical, visible cols (SCK, MO, MI, RX, TX, D4)
# physical, visible rows (10, 11, 12, 13) (9, 6, 5, SCL)
col_pins = (P.SCK, P.MOSI, P.MISO, P.RX, P.TX, P.D4)
row_pins = (P.D10, P.D11, P.D12, P.D13, P.D9, P.D6, P.D5, P.SCL)
rollover_cols_every_rows = 4
diode_orientation = DiodeOrientation.COLUMNS
swap_indicies = {
(3, 3): (3, 9),
(3, 4): (3, 10),
(3, 5): (3, 11),
}

View File

@ -1,38 +0,0 @@
import usb_hid
from kmk.abstract.hid import AbstractHidHelper
from kmk.consts import HID_REPORT_SIZES, HIDReportTypes, HIDUsage, HIDUsagePage
class HIDHelper(AbstractHidHelper):
REPORT_BYTES = 9
def post_init(self):
self.devices = {}
for device in usb_hid.devices:
if device.usage_page == HIDUsagePage.CONSUMER and device.usage == HIDUsage.CONSUMER:
self.devices[HIDReportTypes.CONSUMER] = device
continue
if device.usage_page == HIDUsagePage.KEYBOARD and device.usage == HIDUsage.KEYBOARD:
self.devices[HIDReportTypes.KEYBOARD] = device
continue
if device.usage_page == HIDUsagePage.MOUSE and device.usage == HIDUsage.MOUSE:
self.devices[HIDReportTypes.MOUSE] = device
continue
if (
device.usage_page == HIDUsagePage.SYSCONTROL and
device.usage == HIDUsage.SYSCONTROL
):
self.devices[HIDReportTypes.SYSCONTROL] = device
continue
def hid_send(self, evt):
# int, can be looked up in HIDReportTypes
reporting_device_const = self.report_device[0]
return self.devices[reporting_device_const].send_report(
evt[1:HID_REPORT_SIZES[reporting_device_const] + 1],
)

View File

@ -1,17 +0,0 @@
import time
import board
import digitalio
def feather_red_led_flash(duration=10, rate=0.5):
'''
Flash the red LED for $duration seconds, alternating every $rate
'''
rled = digitalio.DigitalInOut(board.LED1)
rled.direction = digitalio.Direction.OUTPUT
for cycle in range(duration / rate):
rled.value = cycle % 2
time.sleep(rate)

View File

@ -1,36 +0,0 @@
def main():
import sys
from kmk.circuitpython.hid import HIDHelper
from kmk.firmware import Firmware
from kmk.matrix import MatrixScanner
import kmk_keyboard
cols = getattr(kmk_keyboard, 'cols')
diode_orientation = getattr(kmk_keyboard, 'diode_orientation')
keymap = getattr(kmk_keyboard, 'keymap')
rows = getattr(kmk_keyboard, 'rows')
debug_enable = getattr(kmk_keyboard, 'debug_enable', False)
if debug_enable:
from logging import DEBUG as log_level
else:
from logging import ERROR as log_level
try:
firmware = Firmware(
keymap=keymap,
row_pins=rows,
col_pins=cols,
diode_orientation=diode_orientation,
log_level=log_level,
matrix_scanner=MatrixScanner,
hid=HIDHelper,
)
firmware.go()
except Exception as e:
sys.print_exception(e)
sys.exit(1)

View File

@ -1,43 +0,0 @@
import sys
import gc
from kmk.firmware import Firmware
from kmk.matrix import MatrixScanner
from kmk.micropython.pyb_hid import HIDHelper
def main():
import kmk_keyboard
cols = getattr(kmk_keyboard, 'cols')
diode_orientation = getattr(kmk_keyboard, 'diode_orientation')
keymap = getattr(kmk_keyboard, 'keymap')
rows = getattr(kmk_keyboard, 'rows')
debug_enable = getattr(kmk_keyboard, 'debug_enable', False)
if debug_enable:
from logging import DEBUG as log_level
else:
from logging import ERROR as log_level
# This will run out of ram at this point unless you manually GC
gc.collect()
try:
firmware = Firmware(
keymap=keymap,
row_pins=rows,
col_pins=cols,
diode_orientation=diode_orientation,
hid=HIDHelper,
log_level=log_level,
matrix_scanner=MatrixScanner,
)
# This will run out of ram at this point unless you manually GC
gc.collect()
firmware.go()
except Exception as e:
sys.print_exception(e)
sys.exit(1)

View File

@ -1,6 +0,0 @@
import pyb
from kmk.micropython.pyb_hid import generate_pyb_hid_descriptor
# act as a serial device and a KMK device
pyb.usb_mode('VCP+HID', hid=generate_pyb_hid_descriptor())

View File

@ -1,137 +0,0 @@
import logging
from collections import namedtuple
from micropython import const
from kmk.keycodes import Keycodes
from kmk.util import reset_bootloader
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)
KEYCODE_UP_EVENT = const(6)
KEYCODE_DOWN_EVENT = const(7)
MACRO_COMPLETE_EVENT = const(8)
PENDING_KEYCODE_POP_EVENT = const(9)
logger = logging.getLogger(__name__)
InitFirmware = namedtuple('InitFirmware', (
'type',
'keymap',
'row_pins',
'col_pins',
'diode_orientation',
))
KeyUpDown = namedtuple('KeyUpDown', ('type', 'row', 'col'))
KeycodeUpDown = namedtuple('KeycodeUpDown', ('type', 'keycode'))
NewMatrix = namedtuple('NewMatrix', ('type', 'matrix'))
BareEvent = namedtuple('BareEvent', ('type',))
hid_report_event = BareEvent(
type=HID_REPORT_EVENT,
)
macro_complete_event = BareEvent(
type=MACRO_COMPLETE_EVENT,
)
pending_keycode_pop_event = BareEvent(
type=PENDING_KEYCODE_POP_EVENT,
)
def init_firmware(keymap, row_pins, col_pins, diode_orientation):
return InitFirmware(
type=INIT_FIRMWARE_EVENT,
keymap=keymap,
row_pins=row_pins,
col_pins=col_pins,
diode_orientation=diode_orientation,
)
def key_up_event(row, col):
return KeyUpDown(
type=KEY_UP_EVENT,
row=row,
col=col,
)
def key_down_event(row, col):
return KeyUpDown(
type=KEY_DOWN_EVENT,
row=row,
col=col,
)
def keycode_up_event(keycode):
'''
Press a key by Keycode object, bypassing the keymap. Used mostly for
macros.
'''
return KeycodeUpDown(
type=KEYCODE_UP_EVENT,
keycode=keycode,
)
def keycode_down_event(keycode):
'''
Release a key by Keycode object, bypassing the keymap. Used mostly for
macros.
'''
return KeycodeUpDown(
type=KEYCODE_DOWN_EVENT,
keycode=keycode,
)
def new_matrix_event(matrix):
return NewMatrix(
type=NEW_MATRIX_EVENT,
matrix=matrix,
)
def matrix_changed(new_pressed):
def _key_pressed(dispatch, get_state):
dispatch(new_matrix_event(new_pressed))
state = get_state()
if state.hid_pending:
dispatch(hid_report_event)
if Keycodes.KMK.KC_RESET in state.keys_pressed:
reset_bootloader()
if state.pending_keys:
for key in state.pending_keys:
if not key.no_press:
dispatch(keycode_down_event(key))
dispatch(hid_report_event)
if not key.no_release:
dispatch(keycode_up_event(key))
dispatch(hid_report_event)
dispatch(pending_keycode_pop_event)
if state.macro_pending:
macro = state.macro_pending
for event in macro(state):
dispatch(event)
dispatch(macro_complete_event)
return _key_pressed

View File

@ -1,70 +1,121 @@
import logging # Welcome to RAM and stack size hacks central, I'm your host, klardotsh!
# We really get stuck between a rock and a hard place on CircuitPython
# sometimes: our import structure is deeply nested enough that stuff
# breaks in some truly bizarre ways, including:
# - explicit RuntimeError exceptions, complaining that our
# stack depth is too deep
#
# - silent hard locks of the device (basically unrecoverable without
# UF2 flash if done in main.py, fixable with a reboot if done
# in REPL)
#
# However, there's a hackaround that works for us! Because sys.modules
# caches everything it sees (and future imports will use that cached
# copy of the module), let's take this opportunity _way_ up the import
# chain to import _every single thing_ KMK eventually uses in a normal
# workflow, in order from fewest to least nested dependencies.
from kmk.event_defs import init_firmware # First, stuff that has no dependencies, or only C/MPY deps
from kmk.internal_state import Store, kmk_reducer import collections # isort:skip
from kmk.leader_mode import LeaderHelper import kmk.consts # isort:skip
import kmk.kmktime # isort:skip
import kmk.types # isort:skip
# Now stuff that depends on the above (and so on)
import kmk.keycodes # isort:skip
import kmk.matrix # isort:skip
import kmk.hid # isort:skip
import kmk.internal_state # isort:skip
# GC runs automatically after CircuitPython imports. If we ever go back to
# supporting MicroPython, we'll need a GC here (and probably after each
# chunk of the above)
# Thanks for sticking around. Now let's do real work, starting below
import gc
from kmk.consts import LeaderMode, UnicodeModes
from kmk.hid import USB_HID
from kmk.internal_state import InternalState
from kmk.matrix import MatrixScanner
class Firmware: class Firmware:
def __init__( debug_enabled = False
self, keymap, row_pins, col_pins,
diode_orientation,
hid=None,
log_level=logging.NOTSET,
matrix_scanner=None,
):
assert matrix_scanner is not None
self.matrix_scanner = matrix_scanner
logger = logging.getLogger(__name__) keymap = None
logger.setLevel(log_level)
import kmk_keyboard row_pins = None
self.encoders = getattr(kmk_keyboard, 'encoders', []) col_pins = None
diode_orientation = None
self.hydrated = False unicode_mode = UnicodeModes.NOOP
tap_time = 300
leader_mode = LeaderMode.ENTER
leader_dictionary = {}
self.store = Store(kmk_reducer, log_level=log_level) hid_helper = USB_HID
self.store.subscribe(
lambda state, action: self._subscription(state, action),
)
if hid: def __init__(self):
self.hid = hid(store=self.store, log_level=log_level) self._state = InternalState(self)
else:
logger.warning(
"Must provide a HIDHelper (arg: hid), disabling HID\n"
"Board will run in debug mode",
)
self.leader_helper = LeaderHelper(store=self.store, log_level=log_level) def _send_hid(self):
self._hid_helper_inst.create_report(self._state.keys_pressed).send()
self._state.resolve_hid()
self.store.dispatch(init_firmware( def _send_key(self, key):
keymap=keymap, if not getattr(key, 'no_press', None):
row_pins=row_pins, self._state.force_keycode_down(key)
col_pins=col_pins, self._send_hid()
diode_orientation=diode_orientation,
))
def _subscription(self, state, action): if not getattr(key, 'no_release', None):
if not self.hydrated: self._state.force_keycode_up(key)
self.matrix = self.matrix_scanner( self._send_hid()
state.col_pins,
state.row_pins,
state.diode_orientation,
)
self.hydrated = True
def go(self): def go(self):
assert self.keymap, 'must define a keymap with at least one row'
assert self.row_pins, 'no GPIO pins defined for matrix rows'
assert self.col_pins, 'no GPIO pins defined for matrix columns'
assert self.diode_orientation is not None, 'diode orientation must be defined'
self.matrix = MatrixScanner(
cols=self.col_pins,
rows=self.row_pins,
diode_orientation=self.diode_orientation,
rollover_cols_every_rows=getattr(self, 'rollover_cols_every_rows', None),
swap_indicies=getattr(self, 'swap_indicies', None),
)
self._hid_helper_inst = self.hid_helper()
if self.debug_enabled:
print("Firin' lazers. Keyboard is booted.")
while True: while True:
update = self.matrix.scan_for_pressed() for update in self.matrix.scan_for_changes():
if update is not None:
self._state.matrix_changed(
update[0],
update[1],
update[2],
)
if update: if self._state.hid_pending:
self.store.dispatch(update) self._send_hid()
for encoder in self.encoders: for key in self._state.pending_keys:
eupdate = encoder.scan() self._send_key(key)
self._state.pending_key_handled()
if eupdate: if self._state.macro_pending:
for event in eupdate: for key in self._state.macro_pending(self):
self.store.dispatch(event) self._send_key(key)
self._state.resolve_macro()
if self.debug_enabled:
print('New State: {}'.format(self._state._to_dict()))
gc.collect()

194
kmk/hid.py Normal file
View File

@ -0,0 +1,194 @@
from kmk.consts import HID_REPORT_SIZES, HIDReportTypes, HIDUsage, HIDUsagePage
from kmk.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, ConsumerKeycode,
ModifierKeycode)
class USB_HID:
REPORT_BYTES = 8
def __init__(self):
self._evt = bytearray(self.REPORT_BYTES)
self.report_device = memoryview(self._evt)[0:1]
self.report_device[0] = HIDReportTypes.KEYBOARD
# Landmine alert for HIDReportTypes.KEYBOARD: byte index 1 of this view
# is "reserved" and evidently (mostly?) unused. However, other modes (or
# at least consumer, so far) will use this byte, which is the main reason
# this view exists. For KEYBOARD, use report_mods and report_non_mods
self.report_keys = memoryview(self._evt)[1:]
self.report_mods = memoryview(self._evt)[1:2]
self.report_non_mods = memoryview(self._evt)[3:]
self.post_init()
def post_init(self):
pass
def create_report(self, keys_pressed):
self.clear_all()
consumer_key = None
for key in keys_pressed:
if isinstance(key, ConsumerKeycode):
consumer_key = key
break
reporting_device = self.report_device[0]
needed_reporting_device = HIDReportTypes.KEYBOARD
if consumer_key:
needed_reporting_device = HIDReportTypes.CONSUMER
if reporting_device != needed_reporting_device:
# If we are about to change reporting devices, release
# all keys and close our proverbial tab on the existing
# device, or keys will get stuck (mostly when releasing
# media/consumer keys)
self.send()
self.report_device[0] = needed_reporting_device
if consumer_key:
self.add_key(consumer_key)
else:
for key in keys_pressed:
if key.code >= FIRST_KMK_INTERNAL_KEYCODE:
continue
if isinstance(key, ModifierKeycode):
self.add_modifier(key)
else:
self.add_key(key)
if key.has_modifiers:
for mod in key.has_modifiers:
self.add_modifier(mod)
return self
def hid_send(self, evt):
# Don't raise a NotImplementedError so this can serve as our "dummy" HID
# when MCU/board doesn't define one to use (which should almost always be
# the CircuitPython-targeting one, except when unit testing or doing
# something truly bizarre. This will likely change eventually when Bluetooth
# is added)
pass
def send(self):
self.hid_send(self._evt)
return self
def clear_all(self):
for idx, _ in enumerate(self.report_keys):
self.report_keys[idx] = 0x00
return self
def clear_non_modifiers(self):
for idx, _ in enumerate(self.report_non_mods):
self.report_non_mods[idx] = 0x00
return self
def add_modifier(self, modifier):
if isinstance(modifier, ModifierKeycode):
if modifier.code == ModifierKeycode.FAKE_CODE:
for mod in modifier.has_modifiers:
self.report_mods[0] |= mod
else:
self.report_mods[0] |= modifier.code
else:
self.report_mods[0] |= modifier
return self
def remove_modifier(self, modifier):
if isinstance(modifier, ModifierKeycode):
if modifier.code == ModifierKeycode.FAKE_CODE:
for mod in modifier.has_modifiers:
self.report_mods[0] ^= mod
else:
self.report_mods[0] ^= modifier.code
else:
self.report_mods[0] ^= modifier
return self
def add_key(self, key):
# Try to find the first empty slot in the key report, and fill it
placed = False
where_to_place = self.report_non_mods
if self.report_device[0] == HIDReportTypes.CONSUMER:
where_to_place = self.report_keys
for idx, _ in enumerate(where_to_place):
if where_to_place[idx] == 0x00:
where_to_place[idx] = key.code
placed = True
break
if not placed:
# TODO what do we do here?......
pass
return self
def remove_key(self, key):
where_to_place = self.report_non_mods
if self.report_device[0] == HIDReportTypes.CONSUMER:
where_to_place = self.report_keys
for idx, _ in enumerate(where_to_place):
if where_to_place[idx] == key.code:
where_to_place[idx] = 0x00
return self
try:
import usb_hid
PLATFORM_CIRCUITPYTHON = True
except ImportError:
PLATFORM_CIRCUITPYTHON = False
else:
class CircuitPythonUSB_HID(USB_HID):
REPORT_BYTES = 9
def post_init(self):
self.devices = {}
for device in usb_hid.devices:
us = device.usage
up = device.usage_page
if up == HIDUsagePage.CONSUMER and us == HIDUsage.CONSUMER:
self.devices[HIDReportTypes.CONSUMER] = device
continue
if up == HIDUsagePage.KEYBOARD and us == HIDUsage.KEYBOARD:
self.devices[HIDReportTypes.KEYBOARD] = device
continue
if up == HIDUsagePage.MOUSE and us == HIDUsage.MOUSE:
self.devices[HIDReportTypes.MOUSE] = device
continue
if (
up == HIDUsagePage.SYSCONTROL and
us == HIDUsage.SYSCONTROL
):
self.devices[HIDReportTypes.SYSCONTROL] = device
continue
def hid_send(self, evt):
# int, can be looked up in HIDReportTypes
reporting_device_const = self.report_device[0]
return self.devices[reporting_device_const].send_report(
evt[1:HID_REPORT_SIZES[reporting_device_const] + 1],
)

View File

@ -1,13 +1,6 @@
import logging
import sys
from kmk import kmktime
from kmk.consts import DiodeOrientation, LeaderMode, UnicodeModes
from kmk.event_defs import (HID_REPORT_EVENT, INIT_FIRMWARE_EVENT,
KEY_DOWN_EVENT, KEY_UP_EVENT, KEYCODE_DOWN_EVENT,
KEYCODE_UP_EVENT, MACRO_COMPLETE_EVENT,
NEW_MATRIX_EVENT, PENDING_KEYCODE_POP_EVENT)
from kmk.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes, RawKeycodes from kmk.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes, RawKeycodes
from kmk.kmktime import sleep_ms, ticks_diff, ticks_ms
from kmk.util import intify_coordinate
GESC_TRIGGERS = { GESC_TRIGGERS = {
Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT, Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT,
@ -15,71 +8,17 @@ GESC_TRIGGERS = {
} }
class Store:
'''
A data store very loosely inspired by Redux, but with most of the fancy
functional and immutable abilities unavailable because microcontrollers.
This serves as the event dispatcher at the heart of KMK. All changes to the
state of the keyboard should be triggered by events (see event_defs.py)
dispatched through this store, and listened to (for side-effects or other
handling) by subscription functions.
'''
def __init__(self, reducer, log_level=logging.NOTSET):
self.reducer = reducer
self.logger = logging.getLogger(__name__)
self.logger.setLevel(log_level)
self.state = self.reducer(logger=self.logger)
self.callbacks = []
def dispatch(self, action):
if self.state.preserve_intermediate_states:
self.state._oldstates.append(repr(self.state.to_dict(verbose=True)))
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))
for cb in self.callbacks:
if cb is not None:
try:
cb(self.state, action)
except Exception as e:
self.logger.error('Callback failed, moving on')
sys.print_exception(e)
def get_state(self):
return self.state
def subscribe(self, callback):
self.callbacks.append(callback)
return len(self.callbacks) - 1
def unsubscribe(self, idx):
self.callbacks[idx] = None
class InternalState: class InternalState:
keys_pressed = set() keys_pressed = set()
pending_keys = set() coord_keys_pressed = {}
pending_keys = []
macro_pending = None macro_pending = None
leader_pending = None leader_pending = None
leader_last_len = 0 leader_last_len = 0
hid_pending = False hid_pending = False
keymap = []
row_pins = []
col_pins = []
matrix = []
diode_orientation = DiodeOrientation.COLUMNS
leader_mode_history = [] leader_mode_history = []
active_layers = [0] active_layers = [0]
reversed_active_layers = list(reversed(active_layers))
start_time = { start_time = {
'lt': None, 'lt': None,
'tg': None, 'tg': None,
@ -87,333 +26,317 @@ class InternalState:
'lm': None, 'lm': None,
'leader': None, 'leader': None,
} }
_oldstates = []
def __init__(self, preserve_intermediate_states=False): def __init__(self, config):
import kmk_keyboard self.config = config
self.unicode_mode = getattr(kmk_keyboard, 'unicode_mode', UnicodeModes.NOOP)
self.tap_time = getattr(kmk_keyboard, 'tap_time', 300)
self.leader_mode = getattr(kmk_keyboard, 'leader_mode', LeaderMode.ENTER)
self.leader_dictionary = getattr(kmk_keyboard, 'leader_dictionary', {})
self.preserve_intermediate_states = preserve_intermediate_states
def __enter__(self): self.leader_mode = config.leader_mode
return self
def __exit__(self, type, value, traceback): self.internal_key_handlers = {
pass RawKeycodes.KC_DF: self._layer_df,
RawKeycodes.KC_MO: self._layer_mo,
RawKeycodes.KC_LM: self._layer_lm,
RawKeycodes.KC_LT: self._layer_lt,
RawKeycodes.KC_TG: self._layer_tg,
RawKeycodes.KC_TO: self._layer_to,
RawKeycodes.KC_TT: self._layer_tt,
Keycodes.KMK.KC_GESC.code: self._kc_gesc,
RawKeycodes.KC_UC_MODE: self._kc_uc_mode,
RawKeycodes.KC_MACRO: self._kc_macro,
Keycodes.KMK.KC_LEAD.code: self._kc_lead,
Keycodes.KMK.KC_NO.code: self._kc_no,
}
def to_dict(self, verbose=False): def __repr__(self):
return 'InternalState({})'.format(self._to_dict())
def _to_dict(self):
ret = { ret = {
'keys_pressed': self.keys_pressed, 'keys_pressed': self.keys_pressed,
'active_layers': self.active_layers, 'active_layers': self.active_layers,
'unicode_mode': self.unicode_mode,
'tap_time': self.tap_time,
'leader_mode_history': self.leader_mode_history, 'leader_mode_history': self.leader_mode_history,
'start_time': self.start_time, 'start_time': self.start_time,
} }
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 return ret
def __repr__(self): def _find_key_in_map(self, row, col):
return 'InternalState({})'.format(self.to_dict())
def find_key_in_map(state, row, col):
# Later-added layers have priority. Sift through the layers # Later-added layers have priority. Sift through the layers
# in reverse order until we find a valid keycode object # in reverse order until we find a valid keycode object
for layer in reversed(state.active_layers): for layer in self.reversed_active_layers:
layer_key = state.keymap[layer][row][col] layer_key = self.config.keymap[layer][row][col]
if not layer_key or layer_key == Keycodes.KMK.KC_TRNS: if not layer_key or layer_key == Keycodes.KMK.KC_TRNS:
continue continue
if layer_key == Keycodes.KMK.KC_NO: if self.config.debug_enabled:
break print('Resolved key: {}'.format(layer_key))
return layer_key return layer_key
def matrix_changed(self, row, col, is_pressed):
if self.config.debug_enabled:
print('Matrix changed (col, row, pressed?): {}, {}, {}'.format(
col, row, is_pressed,
))
def kmk_reducer(state=None, action=None, logger=None): int_coord = intify_coordinate(row, col)
if state is None: kc_changed = self._find_key_in_map(row, col)
state = InternalState()
if logger is not None: if kc_changed is None:
logger.debug('Reducer received state of None, creating new') print('No key accessible for col, row: {}, {}'.format(row, col))
return self
if action is None: if kc_changed.code >= FIRST_KMK_INTERNAL_KEYCODE:
if logger is not None: self._process_internal_key_event(
logger.debug('No action received, returning state unmodified') kc_changed,
is_pressed,
return state
if action.type == NEW_MATRIX_EVENT:
matrix_keys_pressed = {
find_key_in_map(state, row, col)
for row, col, in action.matrix
}
pressed = matrix_keys_pressed - state.keys_pressed
released = state.keys_pressed - matrix_keys_pressed
if not pressed and not released:
return state
for changed_key in released:
if not changed_key:
continue
elif changed_key.code >= FIRST_KMK_INTERNAL_KEYCODE:
state = process_internal_key_event(
state,
KEY_UP_EVENT,
changed_key,
logger=logger,
) )
for changed_key in pressed:
if not changed_key:
continue
elif changed_key.code >= FIRST_KMK_INTERNAL_KEYCODE:
state = process_internal_key_event(
state,
KEY_DOWN_EVENT,
changed_key,
logger=logger,
)
state.matrix = action.matrix
state.keys_pressed |= pressed
state.keys_pressed -= released
if state.leader_mode % 2 == 1:
state.hid_pending = False
else: else:
state.hid_pending = True if is_pressed:
self.keys_pressed.add(kc_changed)
self.coord_keys_pressed[int_coord] = kc_changed
else:
self.keys_pressed.discard(kc_changed)
self.keys_pressed.discard(self.coord_keys_pressed[int_coord])
self.coord_keys_pressed[int_coord] = None
return state self.hid_pending = True
if action.type == KEYCODE_UP_EVENT: if self.leader_mode % 2 == 1:
state.keys_pressed.discard(action.keycode) self._process_leader_mode()
return state
if action.type == KEYCODE_DOWN_EVENT: return self
state.keys_pressed.add(action.keycode)
return state
if action.type == INIT_FIRMWARE_EVENT: def force_keycode_up(self, keycode):
state.keymap = action.keymap self.keys_pressed.discard(keycode)
state.row_pins = action.row_pins self.hid_pending = True
state.col_pins = action.col_pins return self
state.diode_orientation = action.diode_orientation
return state
# HID events are non-mutating, used exclusively for listeners to know def force_keycode_down(self, keycode):
# they should be doing things. This could/should arguably be folded back if keycode.code == Keycodes.KMK.KC_MACRO_SLEEP_MS:
# into KEY_UP_EVENT and KEY_DOWN_EVENT, but for now it's nice to separate sleep_ms(keycode.ms)
# this out for debugging's sake. else:
if action.type == HID_REPORT_EVENT: self.keys_pressed.add(keycode)
return state self.hid_pending = True
return self
if action.type == MACRO_COMPLETE_EVENT: def pending_key_handled(self):
state.macro_pending = None popped = self.pending_keys.pop()
return state
if action.type == PENDING_KEYCODE_POP_EVENT: if self.config.debug_enabled:
state.pending_keys.pop() print('Popped pending key: {}'.format(popped))
return state
# On unhandled events, log and do not mutate state return self
logger.warning('Unhandled event! Returning state unmodified.')
return state
def resolve_hid(self):
self.hid_pending = False
return self
def process_internal_key_event(state, action_type, changed_key, logger=None): def resolve_macro(self):
if logger is None: if self.config.debug_enabled:
logger = logging.getLogger(__name__) print('Macro complete!')
self.macro_pending = None
return self
def _process_internal_key_event(self, changed_key, is_pressed):
# Since the key objects can be chained into new objects # Since the key objects can be chained into new objects
# with, for example, no_press set, always check against # with, for example, no_press set, always check against
# the underlying code rather than comparing Keycode # the underlying code rather than comparing Keycode
# objects # objects
if changed_key.code == RawKeycodes.KC_DF: return self.internal_key_handlers[changed_key.code](
return df(state, action_type, changed_key, logger=logger) changed_key, is_pressed,
elif changed_key.code == RawKeycodes.KC_MO: )
return mo(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_LM:
return lm(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_LT:
return lt(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_TG:
return tg(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_TO:
return to(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_TT:
return tt(state, action_type, changed_key, logger=logger)
elif changed_key.code == Keycodes.KMK.KC_GESC.code:
return grave_escape(state, action_type, logger=logger)
elif changed_key.code == RawKeycodes.KC_UC_MODE:
return unicode_mode(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_MACRO:
return macro(state, action_type, changed_key, logger=logger)
elif changed_key.code == Keycodes.KMK.KC_LEAD.code:
return leader(state)
else:
return state
def _layer_df(self, changed_key, is_pressed):
def grave_escape(state, action_type, logger):
if action_type == KEY_DOWN_EVENT:
if any(key in GESC_TRIGGERS for key in state.keys_pressed):
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level
state.keys_pressed.add(Keycodes.Common.KC_GRAVE)
return state
# else return KC_ESC
state.keys_pressed.add(Keycodes.Common.KC_ESCAPE)
return state
elif action_type == KEY_UP_EVENT:
state.keys_pressed.discard(Keycodes.Common.KC_ESCAPE)
state.keys_pressed.discard(Keycodes.Common.KC_GRAVE)
return state
return state
def df(state, action_type, changed_key, logger):
"""Switches the default layer""" """Switches the default layer"""
if action_type == KEY_DOWN_EVENT: if is_pressed:
state.active_layers[0] = changed_key.layer self.active_layers[0] = changed_key.layer
self.reversed_active_layers = list(reversed(self.active_layers))
return state return self
def _layer_mo(self, changed_key, is_pressed):
def mo(state, action_type, changed_key, logger):
"""Momentarily activates layer, switches off when you let go""" """Momentarily activates layer, switches off when you let go"""
if action_type == KEY_UP_EVENT: if is_pressed:
state.active_layers = [ self.active_layers.append(changed_key.layer)
layer for layer in state.active_layers else:
self.active_layers = [
layer for layer in self.active_layers
if layer != changed_key.layer if layer != changed_key.layer
] ]
elif action_type == KEY_DOWN_EVENT:
state.active_layers.append(changed_key.layer)
return state self.reversed_active_layers = list(reversed(self.active_layers))
return self
def lm(state, action_type, changed_key, logger): def _layer_lm(self, changed_key, is_pressed):
"""As MO(layer) but with mod active""" """As MO(layer) but with mod active"""
if action_type == KEY_DOWN_EVENT: self.hid_pending = True
if is_pressed:
# Sets the timer start and acts like MO otherwise # Sets the timer start and acts like MO otherwise
state.start_time['lm'] = kmktime.ticks_ms() self.start_time['lm'] = ticks_ms()
state.keys_pressed.add(changed_key.kc) self.keys_pressed.add(changed_key.kc)
state = mo(state, action_type, changed_key, logger) else:
elif action_type == KEY_UP_EVENT: self.keys_pressed.discard(changed_key.kc)
state.keys_pressed.discard(changed_key.kc) self.start_time['lm'] = None
state.start_time['lm'] = None
state = mo(state, action_type, changed_key)
return state return self._layer_mo(changed_key, is_pressed)
def _layer_lt(self, changed_key, is_pressed):
def lt(state, action_type, changed_key, logger):
"""Momentarily activates layer if held, sends kc if tapped""" """Momentarily activates layer if held, sends kc if tapped"""
if action_type == KEY_DOWN_EVENT: if is_pressed:
# Sets the timer start and acts like MO otherwise # Sets the timer start and acts like MO otherwise
state.start_time['lt'] = kmktime.ticks_ms() self.start_time['lt'] = ticks_ms()
state = mo(state, action_type, changed_key, logger) self._layer_mo(changed_key, is_pressed)
elif action_type == KEY_UP_EVENT: else:
# On keyup, check timer, and press key if needed. # On keyup, check timer, and press key if needed.
if state.start_time['lt'] and ( if self.start_time['lt'] and (
kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['lt']) < state.tap_time ticks_diff(ticks_ms(), self.start_time['lt']) < self.config.tap_time
): ):
state.pending_keys.add(changed_key.kc) self.hid_pending = True
self.pending_keys.append(changed_key.kc)
state.start_time['lt'] = None self._layer_mo(changed_key, is_pressed)
state = mo(state, action_type, changed_key, logger) self.start_time['lt'] = None
return self
return state def _layer_tg(self, changed_key, is_pressed):
def tg(state, action_type, changed_key, logger):
"""Toggles the layer (enables it if not active, and vise versa)""" """Toggles the layer (enables it if not active, and vise versa)"""
if action_type == KEY_DOWN_EVENT: if is_pressed:
if changed_key.layer in state.active_layers: if changed_key.layer in self.active_layers:
state.active_layers = [ self.active_layers = [
layer for layer in state.active_layers layer for layer in self.active_layers
if layer != changed_key.layer if layer != changed_key.layer
] ]
else: else:
state.active_layers.append(changed_key.layer) self.active_layers.append(changed_key.layer)
return state self.reversed_active_layers = list(reversed(self.active_layers))
return self
def to(state, action_type, changed_key, logger): def _layer_to(self, changed_key, is_pressed):
"""Activates layer and deactivates all other layers""" """Activates layer and deactivates all other layers"""
if action_type == KEY_DOWN_EVENT: if is_pressed:
state.active_layers = [changed_key.layer] self.active_layers = [changed_key.layer]
self.reversed_active_layers = list(reversed(self.active_layers))
return state return self
def _layer_tt(self, changed_key, is_pressed):
def tt(state, action_type, changed_key, logger):
"""Momentarily activates layer if held, toggles it if tapped repeatedly""" """Momentarily activates layer if held, toggles it if tapped repeatedly"""
# TODO Make this work with tap dance to function more correctly, but technically works. # TODO Make this work with tap dance to function more correctly, but technically works.
if action_type == KEY_DOWN_EVENT: if is_pressed:
if state.start_time['tt'] is None: if self.start_time['tt'] is None:
# Sets the timer start and acts like MO otherwise # Sets the timer start and acts like MO otherwise
state.start_time['tt'] = kmktime.ticks_ms() self.start_time['tt'] = ticks_ms()
state = mo(state, action_type, changed_key, logger) return self._layer_mo(changed_key, is_pressed)
elif kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['tt']) < state.tap_time: elif ticks_diff(ticks_ms(), self.start_time['tt']) < self.config.tap_time:
state.start_time['tt'] = None self.start_time['tt'] = None
state = tg(state, action_type, changed_key, logger) return self.tg(changed_key, is_pressed)
elif action_type == KEY_UP_EVENT and ( elif (
state.start_time['tt'] is None or self.start_time['tt'] is None or
kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['tt']) >= state.tap_time ticks_diff(ticks_ms(), self.start_time['tt']) >= self.config.tap_time
): ):
# On first press, works like MO. On second press, does nothing unless let up within # On first press, works like MO. On second press, does nothing unless let up within
# time window, then acts like TG. # time window, then acts like TG.
state.start_time['tt'] = None self.start_time['tt'] = None
state = mo(state, action_type, changed_key, logger) return self._layer_mo(changed_key, is_pressed)
return state return self
def _kc_uc_mode(self, changed_key, is_pressed):
if is_pressed:
self.config.unicode_mode = changed_key.mode
def unicode_mode(state, action_type, changed_key, logger): return self
if action_type == KEY_DOWN_EVENT:
state.unicode_mode = changed_key.mode
return state def _kc_macro(self, changed_key, is_pressed):
if is_pressed:
def macro(state, action_type, changed_key, logger):
if action_type == KEY_UP_EVENT:
if changed_key.keyup: if changed_key.keyup:
state.macro_pending = changed_key.keyup self.macro_pending = changed_key.keyup
return state else:
elif action_type == KEY_DOWN_EVENT:
if changed_key.keydown: if changed_key.keydown:
state.macro_pending = changed_key.keydown self.macro_pending = changed_key.keydown
return state
return state return self
def _kc_lead(self, changed_key, is_pressed):
if is_pressed:
self._begin_leader_mode()
def leader(state): return self
if state.leader_mode % 2 == 0:
state.keys_pressed.discard(Keycodes.KMK.KC_LEAD) def _kc_gesc(self, changed_key, is_pressed):
self.hid_pending = True
if is_pressed:
if GESC_TRIGGERS.intersection(self.keys_pressed):
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level
self.keys_pressed.add(Keycodes.Common.KC_GRAVE)
return self
# else return KC_ESC
self.keys_pressed.add(Keycodes.Common.KC_ESCAPE)
return self
self.keys_pressed.discard(Keycodes.Common.KC_ESCAPE)
self.keys_pressed.discard(Keycodes.Common.KC_GRAVE)
return self
def _kc_no(self, changed_key, is_pressed):
return self
def _begin_leader_mode(self):
if self.leader_mode % 2 == 0:
self.keys_pressed.discard(Keycodes.KMK.KC_LEAD)
# All leader modes are one number higher when activating # All leader modes are one number higher when activating
state.leader_mode += 1 self.leader_mode += 1
return state return self
def _process_leader_mode(self):
keys_pressed = self.keys_pressed
if self.leader_last_len and self.leader_mode_history:
history_set = set(self.leader_mode_history)
keys_pressed = keys_pressed - history_set
self.leader_last_len = len(self.keys_pressed)
for key in keys_pressed:
if key == Keycodes.Common.KC_ENT:
# Process the action and remove the extra KC.ENT that was added to get here
lmh = tuple(self.leader_mode_history)
if lmh in self.config.leader_dictionary:
self.macro_pending = self.config.leader_dictionary[lmh].keydown
self._exit_leader_mode()
break
elif key == Keycodes.Common.KC_ESC or key == Keycodes.KMK.KC_GESC:
# Clean self and turn leader mode off.
self._exit_leader_mode()
break
elif key == Keycodes.KMK.KC_LEAD:
break
else:
# Add key if not needing to escape
# This needs replaced later with a proper debounce
self.leader_mode_history.append(key)
self.hid_pending = False
return self
def _exit_leader_mode(self):
self.leader_mode_history.clear()
self.leader_mode -= 1
self.leader_last_len = 0
self.keys_pressed.clear()
return self

View File

@ -10,6 +10,45 @@ from kmk.types import AttrDict
FIRST_KMK_INTERNAL_KEYCODE = 1000 FIRST_KMK_INTERNAL_KEYCODE = 1000
kc_lookup_cache = {}
def lookup_kc_with_cache(char):
found_code = kc_lookup_cache.get(
char,
getattr(Common, 'KC_{}'.format(char.upper())),
)
kc_lookup_cache[char] = found_code
kc_lookup_cache[char.upper()] = found_code
kc_lookup_cache[char.lower()] = found_code
return found_code
def generate_codepoint_keysym_seq(codepoint, expected_length=4):
# To make MacOS and Windows happy, always try to send
# sequences that are of length 4 at a minimum
# On Linux systems, we can happily send longer strings.
# They will almost certainly break on MacOS and Windows,
# but this is a documentation problem more than anything.
# Not sure how to send emojis on Mac/Windows like that,
# though, since (for example) the Canadian flag is assembled
# from two five-character codepoints, 1f1e8 and 1f1e6
#
# As a bonus, this function can be pretty useful for
# leader dictionary keys as strings.
seq = [Common.KC_0 for _ in range(max(len(codepoint), expected_length))]
for idx, codepoint_fragment in enumerate(reversed(codepoint)):
seq[-(idx + 1)] = lookup_kc_with_cache(codepoint_fragment)
return seq
def generate_leader_dictionary_seq(string):
return tuple(generate_codepoint_keysym_seq(string, 1))
class RawKeycodes: class RawKeycodes:
''' '''
@ -286,7 +325,7 @@ class Common(KeycodeCategory):
KC_ENTER = KC_ENT = Keycode(40) KC_ENTER = KC_ENT = Keycode(40)
KC_ESCAPE = KC_ESC = Keycode(41) KC_ESCAPE = KC_ESC = Keycode(41)
KC_BACKSPACE = KC_BKSP = Keycode(42) KC_BACKSPACE = KC_BSPC = KC_BKSP = Keycode(42)
KC_TAB = Keycode(43) KC_TAB = Keycode(43)
KC_SPACE = KC_SPC = Keycode(44) KC_SPACE = KC_SPC = Keycode(44)
KC_MINUS = KC_MINS = Keycode(45) KC_MINUS = KC_MINS = Keycode(45)

View File

@ -1,11 +1,7 @@
import math import math
import time
try: USE_UTIME = False
import utime as time
USE_UTIME = True
except ImportError:
import time
USE_UTIME = False
def sleep_ms(ms): def sleep_ms(ms):

View File

@ -1,86 +0,0 @@
import logging
from kmk.keycodes import Keycodes
class LeaderHelper:
"""
Acts as a hid to absorb keypress, and perform macros when a timer
or enter key is pressed depending on the mode set.
"""
def __init__(self, store, log_level=logging.NOTSET):
self.logger = logging.getLogger(__name__)
self.logger.setLevel(log_level)
self.store = store
self.store.subscribe(
lambda state, action: self._subscription(state, action),
)
def _subscription(self, state, action):
"""
Subscribes to the state machine, and dispatches actions based
based on incoming keypresses, or when a timer runs out depending
on the mode.
:param state:
:param action:
:return state:
"""
if state.leader_mode % 2 == 1:
keys_pressed = state.keys_pressed
if state.leader_last_len and state.leader_mode_history:
history_set = set(state.leader_mode_history)
keys_pressed = keys_pressed - history_set
state.leader_last_len = len(state.keys_pressed)
for key in keys_pressed:
if key == Keycodes.Common.KC_ENT:
# Process the action and remove the extra KC.ENT that was added to get here
state = process(state)
return clean_exit(state)
elif key == Keycodes.Common.KC_ESC or key == Keycodes.KMK.KC_GESC:
# Clean state and turn leader mode off.
return clean_exit(state)
elif key == Keycodes.KMK.KC_LEAD:
return state
else:
# Add key if not needing to escape
# This needs replaced later with a proper debounce
state.leader_mode_history.append(key)
return state
return state
def clean_exit(state):
"""
Cleans up the state and hands the HID control back.
:param state:
:return state:
"""
state.leader_mode_history = []
state.leader_mode -= 1
state.leader_last_len = 0
state.keys_pressed.clear()
return state
def process(state):
"""
Checks if there are iny matching sequences of keys, and
performs the macro specified by the user.
:param state:
:param leader_dictionary:
:return state:
"""
lmh = tuple(state.leader_mode_history)
if lmh in state.leader_dictionary:
state.macro_pending = state.leader_dictionary[lmh].keydown
state.keys_pressed.clear()
return state

View File

@ -1,40 +1,11 @@
import string import string
from kmk.event_defs import (hid_report_event, keycode_down_event, from kmk.keycodes import Keycodes, Macro, char_lookup, lookup_kc_with_cache
keycode_up_event)
from kmk.keycodes import Keycodes, Macro, RawKeycodes, char_lookup
from kmk.kmktime import sleep_ms
kc_lookup_cache = {}
def lookup_kc_with_cache(char):
found_code = kc_lookup_cache.get(
char,
getattr(Keycodes.Common, 'KC_{}'.format(char.upper())),
)
kc_lookup_cache[char] = found_code
kc_lookup_cache[char.upper()] = found_code
kc_lookup_cache[char.lower()] = found_code
return found_code
def simple_key_sequence(seq): def simple_key_sequence(seq):
def _simple_key_sequence(state): def _simple_key_sequence(state):
for key in seq: return seq
if key.code == RawKeycodes.KC_MACRO_SLEEP_MS:
sleep_ms(key.ms)
continue
if not getattr(key, 'no_press', None):
yield keycode_down_event(key)
yield hid_report_event
if not getattr(key, 'no_release', None):
yield keycode_up_event(key)
yield hid_report_event
return Macro(keydown=_simple_key_sequence) return Macro(keydown=_simple_key_sequence)

View File

@ -1,38 +1,23 @@
from kmk.consts import UnicodeModes from kmk.consts import UnicodeModes
from kmk.event_defs import (hid_report_event, keycode_down_event, from kmk.keycodes import (Common, Macro, Modifiers,
keycode_up_event) generate_codepoint_keysym_seq)
from kmk.keycodes import Common, Macro, Modifiers from kmk.macros.simple import simple_key_sequence
from kmk.macros.simple import lookup_kc_with_cache, simple_key_sequence from kmk.types import AttrDict
from kmk.util import get_wide_ordinal from kmk.util import get_wide_ordinal
IBUS_KEY_COMBO = Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U)) IBUS_KEY_COMBO = Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U))
IBUS_KEY_DOWN = keycode_down_event(IBUS_KEY_COMBO) RALT_KEY = Modifiers.KC_RALT
IBUS_KEY_UP = keycode_up_event(IBUS_KEY_COMBO) U_KEY = Common.KC_U
RALT_DOWN = keycode_down_event(Modifiers.KC_RALT) ENTER_KEY = Common.KC_ENTER
RALT_UP = keycode_up_event(Modifiers.KC_RALT) RALT_DOWN_NO_RELEASE = Modifiers.KC_RALT(no_release=True)
U_DOWN = keycode_down_event(Common.KC_U) RALT_UP_NO_PRESS = Modifiers.KC_RALT(no_press=True)
U_UP = keycode_up_event(Common.KC_U)
ENTER_DOWN = keycode_down_event(Common.KC_ENTER)
ENTER_UP = keycode_up_event(Common.KC_ENTER)
RALT_DOWN_NO_RELEASE = keycode_down_event(Modifiers.KC_RALT(no_release=True))
RALT_UP_NO_PRESS = keycode_up_event(Modifiers.KC_RALT(no_press=True))
def generate_codepoint_keysym_seq(codepoint): def compile_unicode_string_sequences(string_table):
# To make MacOS and Windows happy, always try to send for k, v in string_table.items():
# sequences that are of length 4 at a minimum string_table[k] = unicode_string_sequence(v)
# On Linux systems, we can happily send longer strings.
# They will almost certainly break on MacOS and Windows,
# but this is a documentation problem more than anything.
# Not sure how to send emojis on Mac/Windows like that,
# though, since (for example) the Canadian flag is assembled
# from two five-character codepoints, 1f1e8 and 1f1e6
seq = [Common.KC_0 for _ in range(max(len(codepoint), 4))]
for idx, codepoint_fragment in enumerate(reversed(codepoint)): return AttrDict(string_table)
seq[-(idx + 1)] = lookup_kc_with_cache(codepoint_fragment)
return seq
def unicode_string_sequence(unistring): def unicode_string_sequence(unistring):
@ -71,23 +56,15 @@ def unicode_codepoint_sequence(codepoints):
def _ralt_unicode_sequence(kc_macros, state): def _ralt_unicode_sequence(kc_macros, state):
for kc_macro in kc_macros: for kc_macro in kc_macros:
yield RALT_DOWN_NO_RELEASE yield RALT_DOWN_NO_RELEASE
yield hid_report_event
yield from kc_macro.keydown(state) yield from kc_macro.keydown(state)
yield RALT_UP_NO_PRESS yield RALT_UP_NO_PRESS
yield hid_report_event
def _ibus_unicode_sequence(kc_macros, state): def _ibus_unicode_sequence(kc_macros, state):
for kc_macro in kc_macros: for kc_macro in kc_macros:
yield IBUS_KEY_DOWN yield IBUS_KEY_COMBO
yield hid_report_event
yield IBUS_KEY_UP
yield hid_report_event
yield from kc_macro.keydown(state) yield from kc_macro.keydown(state)
yield ENTER_DOWN yield ENTER_KEY
yield hid_report_event
yield ENTER_UP
yield hid_report_event
def _winc_unicode_sequence(kc_macros, state): def _winc_unicode_sequence(kc_macros, state):
@ -98,12 +75,6 @@ def _winc_unicode_sequence(kc_macros, state):
https://github.com/SamHocevar/wincompose https://github.com/SamHocevar/wincompose
''' '''
for kc_macro in kc_macros: for kc_macro in kc_macros:
yield RALT_DOWN yield RALT_KEY
yield hid_report_event yield U_KEY
yield RALT_UP
yield hid_report_event
yield U_DOWN
yield hid_report_event
yield U_UP
yield hid_report_event
yield from kc_macro.keydown(state) yield from kc_macro.keydown(state)

View File

@ -1,11 +1,16 @@
import digitalio import digitalio
from kmk.consts import DiodeOrientation from kmk.consts import DiodeOrientation
from kmk.event_defs import matrix_changed from kmk.util import intify_coordinate
class MatrixScanner: class MatrixScanner:
def __init__(self, cols, rows, diode_orientation=DiodeOrientation.COLUMNS): def __init__(
self, cols, rows,
diode_orientation=DiodeOrientation.COLUMNS,
rollover_cols_every_rows=None,
swap_indicies=None,
):
# A pin cannot be both a row and column, detect this by combining the # 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 # two tuples into a set and validating that the length did not drop
# #
@ -16,15 +21,19 @@ class MatrixScanner:
self.cols = cols self.cols = cols
self.rows = rows self.rows = rows
self.len_cols = len(cols)
self.len_rows = len(rows)
self.diode_orientation = diode_orientation self.diode_orientation = diode_orientation
self.last_pressed_len = 0
if self.diode_orientation == DiodeOrientation.COLUMNS: if self.diode_orientation == DiodeOrientation.COLUMNS:
self.outputs = self.cols self.outputs = self.cols
self.inputs = self.rows self.inputs = self.rows
self.translate_coords = True
elif self.diode_orientation == DiodeOrientation.ROWS: elif self.diode_orientation == DiodeOrientation.ROWS:
self.outputs = self.rows self.outputs = self.rows
self.inputs = self.cols self.inputs = self.cols
self.translate_coords = False
else: else:
raise ValueError('Invalid DiodeOrientation: {}'.format( raise ValueError('Invalid DiodeOrientation: {}'.format(
self.diode_orientation, self.diode_orientation,
@ -36,22 +45,65 @@ class MatrixScanner:
for pin in self.inputs: for pin in self.inputs:
pin.switch_to_input(pull=digitalio.Pull.DOWN) pin.switch_to_input(pull=digitalio.Pull.DOWN)
def scan_for_pressed(self): self.swap_indicies = {}
pressed = [] if swap_indicies is not None:
for k, v in swap_indicies.items():
self.swap_indicies[intify_coordinate(*k)] = v
self.swap_indicies[intify_coordinate(*v)] = k
self.rollover_cols_every_rows = rollover_cols_every_rows
if self.rollover_cols_every_rows is None:
self.rollover_cols_every_rows = self.len_rows
self.len_state_arrays = self.len_cols * self.len_rows
self.state = bytearray(self.len_state_arrays)
self.report = bytearray(3)
def scan_for_changes(self):
'''
Poll the matrix for changes and return either None (if nothing updated)
or a bytearray (reused in later runs so copy this if you need the raw
array itself for some crazy reason) consisting of (row, col, pressed)
which are (int, int, bool)
'''
ba_idx = 0
any_changed = False
for oidx, opin in enumerate(self.outputs): for oidx, opin in enumerate(self.outputs):
opin.value(True) opin.value(True)
for iidx, ipin in enumerate(self.inputs): for iidx, ipin in enumerate(self.inputs):
if ipin.value(): old_val = self.state[ba_idx]
pressed.append( new_val = ipin.value()
(oidx, iidx) if self.diode_orientation == DiodeOrientation.ROWS else (iidx, oidx) # noqa
if old_val != new_val:
if self.translate_coords:
new_oidx = oidx + self.len_cols * (iidx // self.rollover_cols_every_rows)
new_iidx = iidx - self.rollover_cols_every_rows * (
iidx // self.rollover_cols_every_rows
) )
self.report[0] = new_iidx
self.report[1] = new_oidx
else:
self.report[0] = oidx
self.report[1] = iidx
swap_src = intify_coordinate(self.report[0], self.report[1])
if swap_src in self.swap_indicies:
tgt_row, tgt_col = self.swap_indicies[swap_src]
self.report[0] = tgt_row
self.report[1] = tgt_col
self.report[2] = new_val
self.state[ba_idx] = new_val
any_changed = True
yield self.report
ba_idx += 1
opin.value(False) opin.value(False)
if len(pressed) != self.last_pressed_len: if not any_changed:
self.last_pressed_len = len(pressed) yield None
return matrix_changed(pressed)
return None # The default, but for explicitness

View File

@ -0,0 +1,6 @@
from kmk.firmware import Firmware as _Firmware
from kmk.hid import CircuitPythonUSB_HID
class Firmware(_Firmware):
hid_helper = CircuitPythonUSB_HID

View File

@ -1,39 +0,0 @@
from pyb import USB_HID, delay, hid_keyboard
from kmk.abstract.hid import AbstractHidHelper
from kmk.consts import HID_REPORT_STRUCTURE
def generate_pyb_hid_descriptor():
existing_keyboard = list(hid_keyboard)
existing_keyboard[-1] = HID_REPORT_STRUCTURE
return tuple(existing_keyboard)
class HIDHelper(AbstractHidHelper):
# For some bizarre reason this can no longer be 8, it'll just fail to send
# anything. This is almost certainly a bug in the report descriptor sent
# over in the boot process. For now the sacrifice is that we only support
# 5KRO until I figure this out, rather than the 6KRO HID defines.
REPORT_BYTES = 7
def post_init(self):
self._hid = USB_HID()
self.hid_send = self._hid.send
def send(self):
self.logger.debug('Sending HID report: {}'.format(self._evt))
self.hid_send(self._evt)
# Without this delay, events get clobbered and you'll likely end up with
# a string like `heloooooooooooooooo` rather than `hello`. This number
# may be able to be shrunken down. It may also make sense to use
# time.sleep_us or time.sleep_ms or time.sleep (platform dependent)
# on non-Pyboards.
#
# It'd be real awesome if pyb.USB_HID.send/recv would support
# uselect.poll or uselect.select to more safely determine when
# it is safe to write to the host again...
delay(1)
return self

View File

@ -1,3 +1,7 @@
def intify_coordinate(row, col):
return row << 8 | col
def get_wide_ordinal(char): def get_wide_ordinal(char):
if len(char) != 2: if len(char) != 2:
return ord(char) return ord(char)

View File

@ -6,6 +6,8 @@ per-file-ignores =
# Allow crazy line lengths, unused variables, and multiple spaces after commas in lists (for grid alignment) # Allow crazy line lengths, unused variables, and multiple spaces after commas in lists (for grid alignment)
user_keymaps/**/*.py: F401,E501,E241 user_keymaps/**/*.py: F401,E501,E241
tests/test_data/keymaps/**/*.py: F401,E501 tests/test_data/keymaps/**/*.py: F401,E501
# Forgive me for my RAM hack sins
kmk/firmware.py: I001,I003,I004,F401
[isort] [isort]
known_third_party = analogio,bitbangio,bleio,board,busio,digitalio,framebuf,gamepad,gc,microcontroller,micropython,pulseio,pyb,pydux,uio,ubluepy,machine,pyb,uos known_third_party = analogio,bitbangio,bleio,board,busio,digitalio,framebuf,gamepad,gc,microcontroller,micropython,pulseio,pyb,pydux,uio,ubluepy,machine,pyb,uos

View File

@ -1,5 +1,4 @@
# CircuitPython provides collections, don't overwrite it # CircuitPython provides collections, don't overwrite it
MICROPY|vendor/upy-lib/collections/collections MICROPY|vendor/upy-lib/collections/collections
MICROPYCIRCUITPY|vendor/upy-lib/logging/logging.py
MICROPYCIRCUITPY|vendor/upy-lib/string/string.py MICROPYCIRCUITPY|vendor/upy-lib/string/string.py

View File

@ -1,96 +0,0 @@
import gc
from kmk.consts import DiodeOrientation, UnicodeModes
from kmk.entrypoints.handwire.pyboard import main
from kmk.keycodes import KC
from kmk.macros.simple import send_string
from kmk.macros.unicode import unicode_string_sequence
from kmk.pins import Pin as P
from kmk.types import AttrDict
cols = (P.Y12, P.Y11, P.Y10, P.Y9, P.X8, P.X7, P.X6, P.X5, P.X4, P.X3, P.X2, P.X1)
rows = (P.Y1, P.Y2, P.Y3, P.Y4)
diode_orientation = DiodeOrientation.COLUMNS
# ------------------User level config variables ---------------------------------------
unicode_mode = UnicodeModes.LINUX
tap_time = 150
leader_timeout = 2000
debug_enable = False
# -------------------------------Macros -----------------------------------------------
gc.collect()
emoticons = AttrDict({
# Emoticons, but fancier
'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻',
'CHEER': r'+。:.゚ヽ(´∀。)ノ゚.:。+゚゚+。:.゚ヽ(*´∀)ノ゚.:。+゚',
'TABLE_FLIP': r'(╯°□°)╯︵ ┻━┻',
'WAT': r'⊙.☉',
'FF': r'凸(゚Д゚#)',
'F': r'( ̄^ ̄)凸',
'MEH': r'╮( ̄_ ̄)╭',
'YAY': r'o(^▽^)o',
})
for k, v in emoticons.items():
emoticons[k] = unicode_string_sequence(v)
# ---------------------- Leader Key Macros --------------------------------------------
gc.collect()
leader_dictionary = {
(KC.F, KC.L, KC.I, KC.P): emoticons.ANGRY_TABLE_FLIP,
(KC.C, KC.H, KC.E, KC.E, KC.R): emoticons.CHEER,
(KC.W, KC.A, KC.T): emoticons.WAT,
(KC.F, KC.F): emoticons.FF,
(KC.F,): emoticons.F,
(KC.M, KC.E, KC.H): emoticons.MEH,
(KC.Y, KC.A, KC.Y): emoticons.YAY,
}
gc.collect()
WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.")
# ---------------------- Keymap ---------------------------------------------------------
gc.collect()
keymap = [
[
# Default
[KC.GESC, 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.LCTRL, KC.LGUI, KC.LALT, KC.LEAD, KC.MO(2), KC.LT(3, KC.SPC), KC.LT(3, KC.SPC), KC.MO(4), KC.LEFT, KC.DOWN, KC.UP, KC.RIGHT],
],
[
# Gaming
[KC.TAB, KC.QUOT, KC.COMM, KC.DOT, KC.P, KC.Y, KC.F, KC.G, KC.C, KC.R, KC.L, KC.BKSP],
[KC.ESC, 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.LCTRL, KC.LGUI, KC.LALT, KC.F1, KC.F2, KC.SPC, KC.SPC, KC.MO(4), KC.LEFT, KC.DOWN, KC.UP, KC.RIGHT],
],
[
# Raise1
[KC.TILD, KC.EXLM, KC.AT, KC.HASH, KC.DLR, KC.PERC, KC.CIRC, KC.AMPR, KC.ASTR, KC.LPRN, KC.RPRN, KC.DEL],
[KC.TRNS, KC.NO, KC.NO, KC.NO, KC.NO, KC.NO, KC.NO, KC.NO, KC.NO, KC.LBRC, KC.RBRC, KC.BSLS],
[KC.TRNS, KC.NO, KC.NO, KC.NO, KC.NO, KC.NO, KC.NO, KC.NO, KC.INS, KC.PGDN, KC.PGUP, KC.MINS],
[KC.RESET, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.NO, KC.NO, KC.EQL, KC.HOME, KC.VOLD, KC.VOLU, KC.END],
],
[
# Raise2
[KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.N7, KC.N8, KC.N9, KC.BKSP],
[KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.N4, KC.N5, KC.N6, KC.NO],
[KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.N1, KC.N2, KC.N3, KC.NO],
[KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.N0, KC.N0, KC.PDOT, KC.ENT],
],
[
# Raise3
[WPM, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.F10, KC.F11, KC.F12, KC.LSHIFT(KC.INS)],
[KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.F7, KC.F8, KC.F9, KC.NO],
[KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.F4, KC.F5, KC.F6, KC.NO],
[KC.DF(0), KC.DF(1), KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.F1, KC.F2, KC.F3, KC.NO],
],
]

View File

@ -1,24 +1,26 @@
from kmk.consts import DiodeOrientation, UnicodeModes from kmk.consts import DiodeOrientation, UnicodeModes
from kmk.entrypoints.handwire.circuitpython_samd51 import main
from kmk.keycodes import KC from kmk.keycodes import KC
from kmk.keycodes import generate_leader_dictionary_seq as glds
from kmk.macros.simple import send_string from kmk.macros.simple import send_string
from kmk.macros.unicode import unicode_string_sequence from kmk.macros.unicode import compile_unicode_string_sequences
from kmk.mcus.circuitpython_samd51 import Firmware
from kmk.pins import Pin as P from kmk.pins import Pin as P
from kmk.types import AttrDict from kmk.types import AttrDict
cols = (P.A0, P.A1, P.A2, P.A3, P.A4, P.A5, P.SCK, P.MOSI, P.MISO, P.RX, P.TX, P.D4) keyboard = Firmware()
rows = (P.D10, P.D11, P.D12, P.D13)
diode_orientation = DiodeOrientation.COLUMNS keyboard.col_pins = (P.A0, P.A1, P.A2, P.A3, P.A4, P.A5, P.SCK, P.MOSI, P.MISO, P.RX, P.TX, P.D4)
keyboard.row_pins = (P.D10, P.D11, P.D12, P.D13)
keyboard.diode_orientation = DiodeOrientation.COLUMNS
# ------------------User level config variables --------------------------------------- # ------------------User level config variables ---------------------------------------
unicode_mode = UnicodeModes.LINUX keyboard.unicode_mode = UnicodeModes.LINUX
tap_time = 200 keyboard.tap_time = 900
leader_timeout = 2000 keyboard.leader_timeout = 2000
debug_enable = True keyboard.debug_enabled = True
emoticons = AttrDict({ emoticons = compile_unicode_string_sequences({
# Emoticons, but fancier # Emoticons, but fancier
'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻', 'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻',
'CHEER': r'+。:.゚ヽ(´∀。)ノ゚.:。+゚゚+。:.゚ヽ(*´∀)ノ゚.:。+゚', 'CHEER': r'+。:.゚ヽ(´∀。)ノ゚.:。+゚゚+。:.゚ヽ(*´∀)ノ゚.:。+゚',
@ -30,26 +32,23 @@ emoticons = AttrDict({
'YAY': r'o(^▽^)o', 'YAY': r'o(^▽^)o',
}) })
for k, v in emoticons.items():
emoticons[k] = unicode_string_sequence(v)
# ---------------------- Leader Key Macros -------------------------------------------- # ---------------------- Leader Key Macros --------------------------------------------
leader_dictionary = { keyboard.leader_dictionary = {
(KC.F, KC.L, KC.I, KC.P): emoticons.ANGRY_TABLE_FLIP, glds('flip'): emoticons.ANGRY_TABLE_FLIP,
(KC.C, KC.H, KC.E, KC.E, KC.R): emoticons.CHEER, glds('cheer'): emoticons.CHEER,
(KC.W, KC.A, KC.T): emoticons.WAT, glds('wat'): emoticons.WAT,
(KC.F, KC.F): emoticons.FF, glds('ff'): emoticons.FF,
(KC.F,): emoticons.F, glds('f'): emoticons.F,
(KC.M, KC.E, KC.H): emoticons.MEH, glds('meh'): emoticons.MEH,
(KC.Y, KC.A, KC.Y): emoticons.YAY, glds('yay'): emoticons.YAY,
} }
WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.") WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.")
# ---------------------- Keymap --------------------------------------------------------- # ---------------------- Keymap ---------------------------------------------------------
keymap = [ keyboard.keymap = [
[ [
# Default # Default
[KC.GESC, KC.QUOTE, KC.COMMA, KC.DOT, KC.P, KC.Y, KC.F, KC.G, KC.C, KC.R, KC.L, KC.BKSP], [KC.GESC, KC.QUOTE, KC.COMMA, KC.DOT, KC.P, KC.Y, KC.F, KC.G, KC.C, KC.R, KC.L, KC.BKSP],
@ -88,4 +87,4 @@ keymap = [
] ]
if __name__ == '__main__': if __name__ == '__main__':
main() keyboard.go()

View File

@ -1,76 +0,0 @@
from kmk.consts import DiodeOrientation, UnicodeModes
from kmk.entrypoints.handwire.circuitpython_samd51 import main
from kmk.firmware import Firmware
from kmk.keycodes import KC
from kmk.macros.simple import send_string, simple_key_sequence
from kmk.macros.unicode import unicode_codepoint_sequence
from kmk.pins import Pin as P
cols = (P.D11, P.D10, P.D9)
rows = (P.A2, P.A3, P.A4, P.A5)
diode_orientation = DiodeOrientation.COLUMNS
unicode_mode = UnicodeModes.LINUX
MACRO_TEST_SIMPLE = simple_key_sequence([
KC.LSHIFT(KC.H),
KC.E,
KC.L,
KC.L,
KC.O,
KC.SPACE,
KC.MACRO_SLEEP_MS(500),
KC.LSHIFT(KC.K),
KC.LSHIFT(KC.M),
KC.LSHIFT(KC.K),
KC.EXCLAIM,
])
MACRO_TEST_STRING = send_string("Hello! from, uhhhh, send_string | and some other WEIRD STUFF` \\ like this' \"\t[]")
ANGRY_TABLE_FLIP = unicode_codepoint_sequence([
"28",
"30ce",
"ca0",
"75ca",
"ca0",
"29",
"30ce",
"5f61",
"253b",
"2501",
"253b",
])
keymap = [
[
[KC.GESC, KC.A, KC.RESET],
[KC.MO(1), KC.B, KC.MUTE],
[KC.LT(2, KC.EXCLAIM), KC.HASH, KC.ENTER],
[KC.TT(3), KC.SPACE, ANGRY_TABLE_FLIP],
],
[
[KC.TRNS, KC.B, KC.C],
[KC.NO, KC.D, KC.E],
[KC.F, KC.G, KC.H],
[KC.I, KC.J, KC.K],
],
[
[KC.VOLU, KC.MUTE, ANGRY_TABLE_FLIP],
[KC.TRNS, KC.PIPE, MACRO_TEST_SIMPLE],
[KC.VOLD, KC.P, MACRO_TEST_STRING],
[KC.L, KC.M, KC.N],
],
[
[KC.NO, KC.UC_MODE_NOOP, KC.C],
[KC.NO, KC.UC_MODE_LINUX, KC.E],
[KC.TRNS, KC.UC_MODE_MACOS, KC.H],
[KC.O, KC.P, KC.Q],
],
]
if __name__ == '__main__':
main()

View File

@ -1,92 +0,0 @@
from kmk.consts import DiodeOrientation, UnicodeModes
from kmk.entrypoints.handwire.circuitpython_samd51 import main
from kmk.firmware import Firmware
from kmk.keycodes import KC
from kmk.macros.rotary_encoder import VolumeRotaryEncoder
from kmk.macros.simple import send_string, simple_key_sequence
from kmk.macros.unicode import unicode_string_sequence
from kmk.pins import Pin as P
from kmk.types import AttrDict
DEBUG_ENABLE = True
cols = (P.A4, P.A5, P.D7)
rows = (P.D12, P.D11, P.D10)
diode_orientation = DiodeOrientation.COLUMNS
unicode_mode = UnicodeModes.LINUX
encoders = [
VolumeRotaryEncoder(P.A3, P.A2, 6, 0.6),
]
emoticons = AttrDict({
# Emojis
'BEER': r'🍺',
'BEER_TOAST': r'🍻',
'FACE_CUTE_SMILE': r'😊',
'FACE_HEART_EYES': r'😍',
'FACE_JOY': r'😂',
'FACE_SWEAT_SMILE': r'😅',
'FACE_THINKING': r'🤔',
'FIRE': r'🔥',
'FLAG_CA': r'🇨🇦',
'FLAG_US': r'🇺🇸',
'HAND_CLAP': r'👏',
'HAND_HORNS': r'🤘',
'HAND_OK': r'👌',
'HAND_THUMB_DOWN': r'👎',
'HAND_THUMB_UP': r'👍',
'HAND_WAVE': r'👋',
'HEART': r'❤️',
'MAPLE_LEAF': r'🍁',
'POOP': r'💩',
'TADA': r'🎉',
# Emoticons, but fancier
'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻',
'CELEBRATORY_GLITTER': r'+。:.゚ヽ(´∀。)ノ゚.:。+゚゚+。:.゚ヽ(*´∀)ノ゚.:。+゚',
'SHRUGGIE': r'¯\_(ツ)_/¯',
'TABLE_FLIP': r'(╯°□°)╯︵ ┻━┻',
})
for k, v in emoticons.items():
emoticons[k] = unicode_string_sequence(v)
MACRO_HELLO_WORLD = simple_key_sequence([
KC.LSHIFT(KC.H),
KC.E,
KC.L,
KC.L,
KC.O,
KC.SPACE,
KC.MACRO_SLEEP_MS(500),
KC.LSHIFT(KC.K),
KC.LSHIFT(KC.M),
KC.LSHIFT(KC.K),
KC.EXCLAIM,
])
keymap = [
[
[KC.GESC, KC.HYPR, KC.RESET],
[KC.MO(1), KC.B, KC.MUTE],
[KC.LT(2, KC.EXCLAIM), KC.HASH, KC.ENTER],
],
[
[KC.MUTE, KC.B, KC.C],
[KC.TRNS, KC.D, KC.E],
[KC.F, KC.G, KC.H],
],
[
[emoticons.CELEBRATORY_GLITTER, emoticons.SHRUGGIE, emoticons.ANGRY_TABLE_FLIP],
[emoticons.BEER, emoticons.FLAG_CA, emoticons.FLAG_US],
[KC.TRNS, KC.P, MACRO_HELLO_WORLD],
],
]
if __name__ == '__main__':
main()

View File

@ -0,0 +1,89 @@
from kmk.boards.klarank import Firmware
from kmk.consts import UnicodeModes
from kmk.keycodes import KC
from kmk.keycodes import generate_leader_dictionary_seq as glds
from kmk.macros.simple import send_string
from kmk.macros.unicode import compile_unicode_string_sequences as cuss
keyboard = Firmware()
keyboard.debug_enabled = True
keyboard.unicode_mode = UnicodeModes.LINUX
_______ = KC.TRNS
xxxxxxx = KC.NO
emoticons = cuss({
# Emojis
'BEER': r'🍺',
'BEER_TOAST': r'🍻',
'FACE_CUTE_SMILE': r'😊',
'FACE_HEART_EYES': r'😍',
'FACE_JOY': r'😂',
'FACE_SWEAT_SMILE': r'😅',
'FACE_THINKING': r'🤔',
'FIRE': r'🔥',
'FLAG_CA': r'🇨🇦',
'FLAG_US': r'🇺🇸',
'HAND_CLAP': r'👏',
'HAND_HORNS': r'🤘',
'HAND_OK': r'👌',
'HAND_THUMB_DOWN': r'👎',
'HAND_THUMB_UP': r'👍',
'HAND_WAVE': r'👋',
'HEART': r'❤️',
'MAPLE_LEAF': r'🍁',
'POOP': r'💩',
'TADA': r'🎉',
# Emoticons, but fancier
'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻',
'CELEBRATORY_GLITTER': r'+。:.゚ヽ(´∀。)ノ゚.:。+゚゚+。:.゚ヽ(*´∀)ノ゚.:。+゚',
'SHRUGGIE': r'¯\_(ツ)_/¯',
'TABLE_FLIP': r'(╯°□°)╯︵ ┻━┻',
})
WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.")
keyboard.leader_dictionary = {
glds('hello'): send_string('hello world from kmk macros'),
glds('wpm'): WPM,
glds('atf'): emoticons.ANGRY_TABLE_FLIP,
glds('tf'): emoticons.TABLE_FLIP,
glds('fca'): emoticons.FLAG_CA,
glds('fus'): emoticons.FLAG_US,
glds('cel'): emoticons.CELEBRATORY_GLITTER,
}
keyboard.keymap = [
[
[KC.GESC, KC.QUOT, KC.COMM, KC.DOT, KC.P, KC.Y, KC.F, KC.G, KC.C, KC.R, KC.L, KC.BSPC],
[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.LGUI, KC.SCLN, KC.Q, KC.J, KC.K, KC.X, KC.B, KC.M, KC.W, KC.V, KC.Z, KC.LALT],
[KC.LCTL, KC.LEAD, KC.LSHIFT(KC.LGUI), KC.MO(2), KC.MO(3), KC.LSFT, KC.SPC, KC.MO(1), KC.LEFT, KC.DOWN, KC.UP, KC.RGHT],
],
[
[KC.GESC, xxxxxxx, xxxxxxx, KC.F10, KC.F11, KC.F12, xxxxxxx, KC.PSLS, KC.N7, KC.N8, KC.N9, KC.BSPC],
[KC.TAB, xxxxxxx, xxxxxxx, KC.F7, KC.F8, KC.F9, xxxxxxx, KC.PAST, KC.N4, KC.N5, KC.N6, _______],
[KC.LGUI, xxxxxxx, xxxxxxx, KC.F4, KC.F5, KC.F6, xxxxxxx, KC.PMNS, KC.N1, KC.N2, KC.N3, _______],
[KC.LCTL, xxxxxxx, _______, KC.F1, KC.F2, KC.F3, KC.SPC, _______, KC.N0, KC.DOT, xxxxxxx, KC.EQL],
],
[
[KC.GESC, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.BSLS, KC.LBRC, KC.RBRC, KC.DEL],
[KC.TAB, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.MINS],
[KC.LGUI, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.LBRC, xxxxxxx, xxxxxxx, KC.INS],
[KC.LCTL, xxxxxxx, _______, _______, xxxxxxx, _______, xxxxxxx, xxxxxxx, KC.HOME, KC.PGDN, KC.PGUP, KC.END],
],
[
[KC.GRV, KC.EXLM, KC.AT, KC.HASH, KC.DLR, KC.PERC, KC.CIRC, KC.AMPR, KC.ASTR, KC.LPRN, KC.RPRN, KC.SLSH],
[KC.TAB, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.MINS],
[KC.LGUI, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx],
[KC.LCTL, xxxxxxx, xxxxxxx, xxxxxxx, _______, _______, xxxxxxx, xxxxxxx, KC.MUTE, KC.VOLD, KC.VOLU, xxxxxxx],
],
]
if __name__ == '__main__':
keyboard.go()

View File

@ -1,68 +0,0 @@
from kmk.consts import DiodeOrientation, UnicodeModes
from kmk.entrypoints.handwire.pyboard import main
from kmk.keycodes import KC
from kmk.macros.simple import send_string, simple_key_sequence
from kmk.macros.unicode import unicode_codepoint_sequence
from kmk.pins import Pin as P
cols = (P.X10, P.X11, P.X12)
rows = (P.X1, P.X2, P.X3)
diode_orientation = DiodeOrientation.COLUMNS
unicode_mode = UnicodeModes.LINUX
MACRO_TEST_SIMPLE = simple_key_sequence([
KC.LSHIFT(KC.H),
KC.E,
KC.L,
KC.L,
KC.O,
KC.SPACE,
KC.MACRO_SLEEP_MS(500),
KC.LSHIFT(KC.K),
KC.LSHIFT(KC.M),
KC.LSHIFT(KC.K),
KC.EXCLAIM,
])
MACRO_TEST_STRING = send_string("Hello! from, uhhhh, send_string | and some other WEIRD STUFF` \\ like this' \"\t[]")
ANGRY_TABLE_FLIP = unicode_codepoint_sequence([
"28",
"30ce",
"ca0",
"75ca",
"ca0",
"29",
"30ce",
"5f61",
"253b",
"2501",
"253b",
])
keymap = [
[
[KC.MO(1), KC.GESC, KC.RESET],
[KC.LT(2, KC.EXCLAIM), KC.HASH, KC.ENTER],
[KC.TT(3), KC.SPACE, KC.LSHIFT],
],
[
[KC.TRNS, KC.B, KC.C],
[KC.NO, KC.D, KC.E],
[KC.F, KC.G, KC.H],
],
[
[KC.VOLU, KC.MUTE, ANGRY_TABLE_FLIP],
[KC.TRNS, KC.PIPE, MACRO_TEST_SIMPLE],
[KC.VOLD, KC.P, MACRO_TEST_STRING],
],
[
[KC.NO, KC.UC_MODE_NOOP, KC.C],
[KC.NO, KC.UC_MODE_LINUX, KC.E],
[KC.TRNS, KC.UC_MODE_MACOS, KC.H],
],
]