OMEGA REFACTOR! Perf grind basically complete.
Resolves #70, Resolves #67 Still needs some regression testing in general, and a definite regression is that rotary encoders are no longer (for the immediate time being) supported. Moves to a much simpler internal state tracking system, and FAR lighter matrix scan. Removes MicroPython support entirely.
This commit is contained in:
parent
0c72554773
commit
16c82b1c0c
73
Makefile
73
Makefile
@ -15,7 +15,7 @@ AMPY_DELAY ?= 1.5
|
||||
ARDUINO ?= /usr/share/arduino
|
||||
PIPENV ?= $(shell which pipenv)
|
||||
|
||||
all: copy-kmk copy-keymap copy-main.py
|
||||
all: copy-kmk copy-keymap
|
||||
|
||||
.docker_base: Dockerfile_base
|
||||
@echo "===> Building Docker base image kmkfw/base:${DOCKER_BASE_TAG}"
|
||||
@ -99,57 +99,6 @@ build/micropython/ports/unix/modules/.kmk_frozen: upy-freeze.txt submodules.toml
|
||||
xargs -I '{}' rsync -ah {} build/micropython/ports/unix/modules/
|
||||
@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:
|
||||
@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
|
||||
@ -173,30 +122,18 @@ copy-kmk:
|
||||
echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1
|
||||
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
|
||||
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
|
||||
else
|
||||
$(MOUNTPOINT)/kmk_keyboard.py: $(USER_KEYMAP)
|
||||
@echo "===> Copying your keymap to kmk_keyboard.py"
|
||||
$(MOUNTPOINT)/main.py: $(USER_KEYMAP)
|
||||
@echo "===> Copying your keymap to main.py"
|
||||
@rsync -rh $(USER_KEYMAP) $@
|
||||
@sync
|
||||
endif # USER_KEYMAP
|
||||
|
||||
copy-keymap: $(MOUNTPOINT)/kmk_keyboard.py
|
||||
copy-keymap: $(MOUNTPOINT)/main.py
|
||||
else
|
||||
copy-keymap:
|
||||
echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1
|
||||
|
@ -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
18
kmk/boards/klarank.py
Normal 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),
|
||||
}
|
@ -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],
|
||||
)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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())
|
@ -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
|
143
kmk/firmware.py
143
kmk/firmware.py
@ -1,70 +1,111 @@
|
||||
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
|
||||
from kmk.internal_state import Store, kmk_reducer
|
||||
from kmk.leader_mode import LeaderHelper
|
||||
# First, stuff that has no dependencies, or only C/MPY deps
|
||||
import collections
|
||||
import kmk.consts
|
||||
import kmk.kmktime
|
||||
import kmk.types
|
||||
|
||||
# Now stuff that depends on the above (and so on)
|
||||
import kmk.keycodes
|
||||
import kmk.matrix
|
||||
|
||||
import kmk.hid
|
||||
import kmk.internal_state
|
||||
|
||||
# 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:
|
||||
def __init__(
|
||||
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
|
||||
debug_enabled = False
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(log_level)
|
||||
keymap = None
|
||||
|
||||
import kmk_keyboard
|
||||
self.encoders = getattr(kmk_keyboard, 'encoders', [])
|
||||
row_pins = None
|
||||
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)
|
||||
self.store.subscribe(
|
||||
lambda state, action: self._subscription(state, action),
|
||||
hid_helper = USB_HID
|
||||
|
||||
def __init__(self):
|
||||
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),
|
||||
)
|
||||
|
||||
if hid:
|
||||
self.hid = hid(store=self.store, log_level=log_level)
|
||||
else:
|
||||
logger.warning(
|
||||
"Must provide a HIDHelper (arg: hid), disabling HID\n"
|
||||
"Board will run in debug mode",
|
||||
)
|
||||
self._hid_helper_inst = self.hid_helper()
|
||||
|
||||
self.leader_helper = LeaderHelper(store=self.store, log_level=log_level)
|
||||
self._state = InternalState(self)
|
||||
|
||||
self.store.dispatch(init_firmware(
|
||||
keymap=keymap,
|
||||
row_pins=row_pins,
|
||||
col_pins=col_pins,
|
||||
diode_orientation=diode_orientation,
|
||||
))
|
||||
|
||||
def _subscription(self, state, action):
|
||||
if not self.hydrated:
|
||||
self.matrix = self.matrix_scanner(
|
||||
state.col_pins,
|
||||
state.row_pins,
|
||||
state.diode_orientation,
|
||||
)
|
||||
self.hydrated = True
|
||||
def _send_hid(self):
|
||||
self._hid_helper_inst.create_report(self._state.keys_pressed).send()
|
||||
self._state.resolve_hid()
|
||||
|
||||
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'
|
||||
|
||||
if self.debug_enabled:
|
||||
print("Firin' lazers. Keyboard is booted.")
|
||||
|
||||
while True:
|
||||
update = self.matrix.scan_for_pressed()
|
||||
|
||||
if update:
|
||||
self.store.dispatch(update)
|
||||
if update is not None:
|
||||
self._state.matrix_changed(
|
||||
update[0],
|
||||
update[1],
|
||||
update[2],
|
||||
)
|
||||
|
||||
for encoder in self.encoders:
|
||||
eupdate = encoder.scan()
|
||||
if self._state.hid_pending:
|
||||
self._send_hid()
|
||||
|
||||
if eupdate:
|
||||
for event in eupdate:
|
||||
self.store.dispatch(event)
|
||||
if self._state.macro_pending:
|
||||
for key in self._state.macro_pending(self):
|
||||
if not getattr(key, 'no_press', None):
|
||||
self._state.force_keycode_down(key)
|
||||
self._send_hid()
|
||||
|
||||
if not getattr(key, 'no_release', None):
|
||||
self._state.force_keycode_up(key)
|
||||
self._send_hid()
|
||||
|
||||
self._state.resolve_macro()
|
||||
|
||||
gc.collect()
|
||||
|
194
kmk/hid.py
Normal file
194
kmk/hid.py
Normal 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],
|
||||
)
|
@ -1,13 +1,5 @@
|
||||
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.kmktime import sleep_ms, ticks_diff, ticks_ms
|
||||
|
||||
GESC_TRIGGERS = {
|
||||
Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT,
|
||||
@ -15,57 +7,6 @@ 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:
|
||||
keys_pressed = set()
|
||||
pending_keys = set()
|
||||
@ -73,13 +14,9 @@ class InternalState:
|
||||
leader_pending = None
|
||||
leader_last_len = 0
|
||||
hid_pending = False
|
||||
keymap = []
|
||||
row_pins = []
|
||||
col_pins = []
|
||||
matrix = []
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
leader_mode_history = []
|
||||
active_layers = [0]
|
||||
reversed_active_layers = list(reversed(active_layers))
|
||||
start_time = {
|
||||
'lt': None,
|
||||
'tg': None,
|
||||
@ -87,23 +24,31 @@ class InternalState:
|
||||
'lm': None,
|
||||
'leader': None,
|
||||
}
|
||||
_oldstates = []
|
||||
|
||||
def __init__(self, preserve_intermediate_states=False):
|
||||
import kmk_keyboard
|
||||
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 __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
self.leader_mode = config.leader_mode
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
pass
|
||||
self.internal_key_handlers = {
|
||||
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 = {
|
||||
'keys_pressed': self.keys_pressed,
|
||||
'active_layers': self.active_layers,
|
||||
@ -113,307 +58,280 @@ class InternalState:
|
||||
'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
|
||||
|
||||
def __repr__(self):
|
||||
return 'InternalState({})'.format(self.to_dict())
|
||||
def _find_key_in_map(self, row, col):
|
||||
# Later-added layers have priority. Sift through the layers
|
||||
# in reverse order until we find a valid keycode object
|
||||
for layer in self.reversed_active_layers:
|
||||
layer_key = self.config.keymap[layer][row][col]
|
||||
|
||||
|
||||
def find_key_in_map(state, row, col):
|
||||
# Later-added layers have priority. Sift through the layers
|
||||
# in reverse order until we find a valid keycode object
|
||||
for layer in reversed(state.active_layers):
|
||||
layer_key = state.keymap[layer][row][col]
|
||||
|
||||
if not layer_key or layer_key == Keycodes.KMK.KC_TRNS:
|
||||
continue
|
||||
|
||||
if layer_key == Keycodes.KMK.KC_NO:
|
||||
break
|
||||
|
||||
return layer_key
|
||||
|
||||
|
||||
def kmk_reducer(state=None, action=None, logger=None):
|
||||
if state is None:
|
||||
state = InternalState()
|
||||
|
||||
if logger is not None:
|
||||
logger.debug('Reducer received state of None, creating new')
|
||||
|
||||
if action is None:
|
||||
if logger is not None:
|
||||
logger.debug('No action received, returning state unmodified')
|
||||
|
||||
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:
|
||||
if not layer_key or layer_key == Keycodes.KMK.KC_TRNS:
|
||||
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,
|
||||
)
|
||||
if layer_key == Keycodes.KMK.KC_NO:
|
||||
return layer_key
|
||||
|
||||
state.matrix = action.matrix
|
||||
state.keys_pressed |= pressed
|
||||
state.keys_pressed -= released
|
||||
if state.leader_mode % 2 == 1:
|
||||
state.hid_pending = False
|
||||
return layer_key
|
||||
|
||||
def matrix_changed(self, row, col, is_pressed):
|
||||
if self.config.debug_enabled:
|
||||
print('Matrix changed (col, row, pressed?): {}, {}, {}'.format(
|
||||
row, col, is_pressed,
|
||||
))
|
||||
|
||||
kc_changed = self._find_key_in_map(row, col)
|
||||
|
||||
if kc_changed is None:
|
||||
print('No key accessible for col, row: {}, {}'.format(row, col))
|
||||
return self
|
||||
|
||||
if kc_changed.code >= FIRST_KMK_INTERNAL_KEYCODE:
|
||||
self._process_internal_key_event(
|
||||
kc_changed,
|
||||
is_pressed,
|
||||
)
|
||||
else:
|
||||
state.hid_pending = True
|
||||
if is_pressed:
|
||||
self.keys_pressed.add(kc_changed)
|
||||
else:
|
||||
self.keys_pressed.discard(kc_changed)
|
||||
|
||||
return state
|
||||
self.hid_pending = True
|
||||
|
||||
if action.type == KEYCODE_UP_EVENT:
|
||||
state.keys_pressed.discard(action.keycode)
|
||||
return state
|
||||
if self.leader_mode % 2 == 1:
|
||||
self._process_leader_mode()
|
||||
|
||||
if action.type == KEYCODE_DOWN_EVENT:
|
||||
state.keys_pressed.add(action.keycode)
|
||||
return state
|
||||
return self
|
||||
|
||||
if action.type == INIT_FIRMWARE_EVENT:
|
||||
state.keymap = action.keymap
|
||||
state.row_pins = action.row_pins
|
||||
state.col_pins = action.col_pins
|
||||
state.diode_orientation = action.diode_orientation
|
||||
return state
|
||||
def force_keycode_up(self, keycode):
|
||||
self.keys_pressed.discard(keycode)
|
||||
self.hid_pending = True
|
||||
return self
|
||||
|
||||
# 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
|
||||
def force_keycode_down(self, keycode):
|
||||
if keycode.code == Keycodes.KMK.KC_MACRO_SLEEP_MS:
|
||||
sleep_ms(keycode.ms)
|
||||
else:
|
||||
self.keys_pressed.add(keycode)
|
||||
self.hid_pending = True
|
||||
return self
|
||||
|
||||
if action.type == MACRO_COMPLETE_EVENT:
|
||||
state.macro_pending = None
|
||||
return state
|
||||
def pending_key_handled(self):
|
||||
popped = self.pending_keys.pop()
|
||||
|
||||
if action.type == PENDING_KEYCODE_POP_EVENT:
|
||||
state.pending_keys.pop()
|
||||
return state
|
||||
if self.config.debug_enabled:
|
||||
print('Popped pending key: {}'.format(popped))
|
||||
|
||||
# On unhandled events, log and do not mutate state
|
||||
logger.warning('Unhandled event! Returning state unmodified.')
|
||||
return state
|
||||
return self
|
||||
|
||||
def resolve_hid(self):
|
||||
self.hid_pending = False
|
||||
return self
|
||||
|
||||
def process_internal_key_event(state, action_type, changed_key, logger=None):
|
||||
if logger is None:
|
||||
logger = logging.getLogger(__name__)
|
||||
def resolve_macro(self):
|
||||
if self.config.debug_enabled:
|
||||
print('Macro complete!')
|
||||
|
||||
# Since the key objects can be chained into new objects
|
||||
# with, for example, no_press set, always check against
|
||||
# the underlying code rather than comparing Keycode
|
||||
# objects
|
||||
self.macro_pending = None
|
||||
return self
|
||||
|
||||
if changed_key.code == RawKeycodes.KC_DF:
|
||||
return df(state, action_type, changed_key, logger=logger)
|
||||
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 _process_internal_key_event(self, changed_key, is_pressed):
|
||||
# Since the key objects can be chained into new objects
|
||||
# with, for example, no_press set, always check against
|
||||
# the underlying code rather than comparing Keycode
|
||||
# objects
|
||||
|
||||
return self.internal_key_handlers[changed_key.code](
|
||||
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
|
||||
def _layer_df(self, changed_key, is_pressed):
|
||||
"""Switches the default layer"""
|
||||
if is_pressed:
|
||||
self.active_layers[0] = changed_key.layer
|
||||
self.reversed_active_layers = list(reversed(self.active_layers))
|
||||
|
||||
# else return KC_ESC
|
||||
state.keys_pressed.add(Keycodes.Common.KC_ESCAPE)
|
||||
return state
|
||||
return self
|
||||
|
||||
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"""
|
||||
if action_type == KEY_DOWN_EVENT:
|
||||
state.active_layers[0] = changed_key.layer
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def mo(state, action_type, 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(state, action_type, changed_key, logger):
|
||||
"""As MO(layer) but with mod active"""
|
||||
if action_type == KEY_DOWN_EVENT:
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
state.start_time['lm'] = kmktime.ticks_ms()
|
||||
state.keys_pressed.add(changed_key.kc)
|
||||
state = mo(state, action_type, changed_key, logger)
|
||||
elif action_type == KEY_UP_EVENT:
|
||||
state.keys_pressed.discard(changed_key.kc)
|
||||
state.start_time['lm'] = None
|
||||
state = mo(state, action_type, changed_key)
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def lt(state, action_type, changed_key, logger):
|
||||
"""Momentarily activates layer if held, sends kc if tapped"""
|
||||
if action_type == KEY_DOWN_EVENT:
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
state.start_time['lt'] = kmktime.ticks_ms()
|
||||
state = mo(state, action_type, changed_key, logger)
|
||||
elif action_type == KEY_UP_EVENT:
|
||||
# On keyup, check timer, and press key if needed.
|
||||
if state.start_time['lt'] and (
|
||||
kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['lt']) < state.tap_time
|
||||
):
|
||||
state.pending_keys.add(changed_key.kc)
|
||||
|
||||
state.start_time['lt'] = None
|
||||
state = mo(state, action_type, changed_key, logger)
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def tg(state, action_type, 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
|
||||
def _layer_mo(self, changed_key, is_pressed):
|
||||
"""Momentarily activates layer, switches off when you let go"""
|
||||
if is_pressed:
|
||||
self.active_layers.append(changed_key.layer)
|
||||
else:
|
||||
self.active_layers = [
|
||||
layer for layer in self.active_layers
|
||||
if layer != changed_key.layer
|
||||
]
|
||||
else:
|
||||
state.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):
|
||||
"""Activates layer and deactivates all other layers"""
|
||||
if action_type == KEY_DOWN_EVENT:
|
||||
state.active_layers = [changed_key.layer]
|
||||
def _layer_lm(self, changed_key, is_pressed):
|
||||
"""As MO(layer) but with mod active"""
|
||||
self.hid_pending = True
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def tt(state, action_type, changed_key, logger):
|
||||
"""Momentarily activates layer if held, toggles it if tapped repeatedly"""
|
||||
# TODO Make this work with tap dance to function more correctly, but technically works.
|
||||
if action_type == KEY_DOWN_EVENT:
|
||||
if state.start_time['tt'] is None:
|
||||
if is_pressed:
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
state.start_time['tt'] = kmktime.ticks_ms()
|
||||
state = mo(state, action_type, changed_key, logger)
|
||||
elif kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['tt']) < state.tap_time:
|
||||
state.start_time['tt'] = None
|
||||
state = tg(state, action_type, changed_key, logger)
|
||||
elif action_type == KEY_UP_EVENT and (
|
||||
state.start_time['tt'] is None or
|
||||
kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['tt']) >= state.tap_time
|
||||
):
|
||||
# On first press, works like MO. On second press, does nothing unless let up within
|
||||
# time window, then acts like TG.
|
||||
state.start_time['tt'] = None
|
||||
state = mo(state, action_type, changed_key, logger)
|
||||
self.start_time['lm'] = ticks_ms()
|
||||
self.keys_pressed.add(changed_key.kc)
|
||||
return self.mo(changed_key, is_pressed)
|
||||
|
||||
return state
|
||||
self.keys_pressed.discard(changed_key.kc)
|
||||
self.start_time['lm'] = None
|
||||
return self.mo(changed_key, is_pressed)
|
||||
|
||||
def _layer_lt(self, changed_key, is_pressed):
|
||||
"""Momentarily activates layer if held, sends kc if tapped"""
|
||||
if is_pressed:
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
self.start_time['lt'] = ticks_ms()
|
||||
return self.mo(changed_key, is_pressed)
|
||||
|
||||
def unicode_mode(state, action_type, changed_key, logger):
|
||||
if action_type == KEY_DOWN_EVENT:
|
||||
state.unicode_mode = changed_key.mode
|
||||
# On keyup, check timer, and press key if needed.
|
||||
if self.start_time['lt'] and (
|
||||
ticks_diff(ticks_ms(), self.start_time['lt']) < self.tap_time
|
||||
):
|
||||
self.hid_pending = True
|
||||
self.pending_keys.add(changed_key.kc)
|
||||
|
||||
return state
|
||||
self.start_time['lt'] = None
|
||||
return self.mo(changed_key, is_pressed)
|
||||
|
||||
def _layer_tg(self, changed_key, is_pressed):
|
||||
"""Toggles the layer (enables it if not active, and vise versa)"""
|
||||
if is_pressed:
|
||||
if changed_key.layer in self.active_layers:
|
||||
self.active_layers = [
|
||||
layer for layer in self.active_layers
|
||||
if layer != changed_key.layer
|
||||
]
|
||||
else:
|
||||
self.active_layers.append(changed_key.layer)
|
||||
|
||||
def macro(state, action_type, changed_key, logger):
|
||||
if action_type == KEY_UP_EVENT:
|
||||
if changed_key.keyup:
|
||||
state.macro_pending = changed_key.keyup
|
||||
return state
|
||||
self.reversed_active_layers = list(reversed(self.active_layers))
|
||||
|
||||
elif action_type == KEY_DOWN_EVENT:
|
||||
if changed_key.keydown:
|
||||
state.macro_pending = changed_key.keydown
|
||||
return state
|
||||
return self
|
||||
|
||||
return state
|
||||
def _layer_to(self, changed_key, is_pressed):
|
||||
"""Activates layer and deactivates all other layers"""
|
||||
if is_pressed:
|
||||
self.active_layers = [changed_key.layer]
|
||||
self.reversed_active_layers = list(reversed(self.active_layers))
|
||||
|
||||
return self
|
||||
|
||||
def leader(state):
|
||||
if state.leader_mode % 2 == 0:
|
||||
state.keys_pressed.discard(Keycodes.KMK.KC_LEAD)
|
||||
# All leader modes are one number higher when activating
|
||||
state.leader_mode += 1
|
||||
def _layer_tt(self, changed_key, is_pressed):
|
||||
"""Momentarily activates layer if held, toggles it if tapped repeatedly"""
|
||||
# TODO Make this work with tap dance to function more correctly, but technically works.
|
||||
if is_pressed:
|
||||
if self.start_time['tt'] is None:
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
self.start_time['tt'] = ticks_ms()
|
||||
return self.mo(changed_key, is_pressed)
|
||||
elif ticks_diff(ticks_ms(), self.start_time['tt']) < self.tap_time:
|
||||
self.start_time['tt'] = None
|
||||
return self.tg(changed_key, is_pressed)
|
||||
elif (
|
||||
self.start_time['tt'] is None or
|
||||
ticks_diff(ticks_ms(), self.start_time['tt']) >= self.tap_time
|
||||
):
|
||||
# On first press, works like MO. On second press, does nothing unless let up within
|
||||
# time window, then acts like TG.
|
||||
self.start_time['tt'] = None
|
||||
return self.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
|
||||
|
||||
return self
|
||||
|
||||
def _kc_macro(self, changed_key, is_pressed):
|
||||
if is_pressed:
|
||||
if changed_key.keyup:
|
||||
self.macro_pending = changed_key.keyup
|
||||
else:
|
||||
if changed_key.keydown:
|
||||
self.macro_pending = changed_key.keydown
|
||||
|
||||
return self
|
||||
|
||||
def _kc_lead(self, changed_key, is_pressed):
|
||||
if is_pressed:
|
||||
self._begin_leader_mode()
|
||||
|
||||
return self
|
||||
|
||||
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
|
||||
self.leader_mode += 1
|
||||
|
||||
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
|
||||
|
@ -10,6 +10,45 @@ from kmk.types import AttrDict
|
||||
|
||||
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:
|
||||
'''
|
||||
@ -286,7 +325,7 @@ class Common(KeycodeCategory):
|
||||
|
||||
KC_ENTER = KC_ENT = Keycode(40)
|
||||
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_SPACE = KC_SPC = Keycode(44)
|
||||
KC_MINUS = KC_MINS = Keycode(45)
|
||||
|
@ -1,11 +1,7 @@
|
||||
import math
|
||||
|
||||
try:
|
||||
import utime as time
|
||||
USE_UTIME = True
|
||||
except ImportError:
|
||||
import time
|
||||
USE_UTIME = False
|
||||
import time
|
||||
USE_UTIME = False
|
||||
|
||||
|
||||
def sleep_ms(ms):
|
||||
|
@ -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
|
@ -1,40 +1,13 @@
|
||||
import string
|
||||
|
||||
from kmk.event_defs import (hid_report_event, keycode_down_event,
|
||||
keycode_up_event)
|
||||
from kmk.keycodes import Keycodes, Macro, RawKeycodes, char_lookup
|
||||
from kmk.keycodes import (Keycodes, Macro, RawKeycodes, char_lookup,
|
||||
lookup_kc_with_cache)
|
||||
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(state):
|
||||
for key in 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 seq
|
||||
|
||||
return Macro(keydown=_simple_key_sequence)
|
||||
|
||||
|
@ -1,38 +1,22 @@
|
||||
from kmk.consts import UnicodeModes
|
||||
from kmk.event_defs import (hid_report_event, keycode_down_event,
|
||||
keycode_up_event)
|
||||
from kmk.keycodes import Common, Macro, Modifiers
|
||||
from kmk.keycodes import Common, Macro, Modifiers, generate_codepoint_keysym_seq
|
||||
from kmk.macros.simple import lookup_kc_with_cache, simple_key_sequence
|
||||
from kmk.types import AttrDict
|
||||
from kmk.util import get_wide_ordinal
|
||||
|
||||
IBUS_KEY_COMBO = Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U))
|
||||
IBUS_KEY_DOWN = keycode_down_event(IBUS_KEY_COMBO)
|
||||
IBUS_KEY_UP = keycode_up_event(IBUS_KEY_COMBO)
|
||||
RALT_DOWN = keycode_down_event(Modifiers.KC_RALT)
|
||||
RALT_UP = keycode_up_event(Modifiers.KC_RALT)
|
||||
U_DOWN = keycode_down_event(Common.KC_U)
|
||||
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))
|
||||
RALT_KEY = Modifiers.KC_RALT
|
||||
U_KEY = Common.KC_U
|
||||
ENTER_KEY = Common.KC_ENTER
|
||||
RALT_DOWN_NO_RELEASE = Modifiers.KC_RALT(no_release=True)
|
||||
RALT_UP_NO_PRESS = Modifiers.KC_RALT(no_press=True)
|
||||
|
||||
|
||||
def generate_codepoint_keysym_seq(codepoint):
|
||||
# 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
|
||||
seq = [Common.KC_0 for _ in range(max(len(codepoint), 4))]
|
||||
def compile_unicode_string_sequences(string_table):
|
||||
for k, v in string_table.items():
|
||||
string_table[k] = unicode_string_sequence(v)
|
||||
|
||||
for idx, codepoint_fragment in enumerate(reversed(codepoint)):
|
||||
seq[-(idx + 1)] = lookup_kc_with_cache(codepoint_fragment)
|
||||
|
||||
return seq
|
||||
return AttrDict(string_table)
|
||||
|
||||
|
||||
def unicode_string_sequence(unistring):
|
||||
@ -71,23 +55,15 @@ def unicode_codepoint_sequence(codepoints):
|
||||
def _ralt_unicode_sequence(kc_macros, state):
|
||||
for kc_macro in kc_macros:
|
||||
yield RALT_DOWN_NO_RELEASE
|
||||
yield hid_report_event
|
||||
yield from kc_macro.keydown(state)
|
||||
yield RALT_UP_NO_PRESS
|
||||
yield hid_report_event
|
||||
|
||||
|
||||
def _ibus_unicode_sequence(kc_macros, state):
|
||||
for kc_macro in kc_macros:
|
||||
yield IBUS_KEY_DOWN
|
||||
yield hid_report_event
|
||||
yield IBUS_KEY_UP
|
||||
yield hid_report_event
|
||||
yield IBUS_KEY_COMBO
|
||||
yield from kc_macro.keydown(state)
|
||||
yield ENTER_DOWN
|
||||
yield hid_report_event
|
||||
yield ENTER_UP
|
||||
yield hid_report_event
|
||||
yield ENTER_KEY
|
||||
|
||||
|
||||
def _winc_unicode_sequence(kc_macros, state):
|
||||
@ -98,12 +74,6 @@ def _winc_unicode_sequence(kc_macros, state):
|
||||
https://github.com/SamHocevar/wincompose
|
||||
'''
|
||||
for kc_macro in kc_macros:
|
||||
yield RALT_DOWN
|
||||
yield hid_report_event
|
||||
yield RALT_UP
|
||||
yield hid_report_event
|
||||
yield U_DOWN
|
||||
yield hid_report_event
|
||||
yield U_UP
|
||||
yield hid_report_event
|
||||
yield RALT_KEY
|
||||
yield U_KEY
|
||||
yield from kc_macro.keydown(state)
|
||||
|
@ -1,11 +1,15 @@
|
||||
import digitalio
|
||||
|
||||
from kmk.consts import DiodeOrientation
|
||||
from kmk.event_defs import matrix_changed
|
||||
|
||||
|
||||
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
|
||||
# two tuples into a set and validating that the length did not drop
|
||||
#
|
||||
@ -20,14 +24,15 @@ class MatrixScanner:
|
||||
self.len_rows = len(rows)
|
||||
|
||||
self.diode_orientation = diode_orientation
|
||||
self.last_pressed_len = 0
|
||||
|
||||
if self.diode_orientation == DiodeOrientation.COLUMNS:
|
||||
self.outputs = self.cols
|
||||
self.inputs = self.rows
|
||||
self.translate_coords = True
|
||||
elif self.diode_orientation == DiodeOrientation.ROWS:
|
||||
self.outputs = self.rows
|
||||
self.inputs = self.cols
|
||||
self.translate_coords = False
|
||||
else:
|
||||
raise ValueError('Invalid DiodeOrientation: {}'.format(
|
||||
self.diode_orientation,
|
||||
@ -39,44 +44,64 @@ class MatrixScanner:
|
||||
for pin in self.inputs:
|
||||
pin.switch_to_input(pull=digitalio.Pull.DOWN)
|
||||
|
||||
import kmk_keyboard
|
||||
self.swap_indicies = {}
|
||||
if swap_indicies is not None:
|
||||
for k, v in swap_indicies.items():
|
||||
self.swap_indicies[self._intify_coordinate(*k)] = v
|
||||
self.swap_indicies[self._intify_coordinate(*v)] = k
|
||||
|
||||
self.swap_indicies = getattr(kmk_keyboard, 'swap_indicies', {})
|
||||
self.rollover_cols_every_rows = getattr(
|
||||
kmk_keyboard,
|
||||
'rollover_cols_every_rows',
|
||||
self.len_rows,
|
||||
)
|
||||
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
|
||||
|
||||
for k, v in self.swap_indicies.items():
|
||||
self.swap_indicies[v] = k
|
||||
self.len_state_arrays = self.len_cols * self.len_rows
|
||||
self.state = bytearray(self.len_state_arrays)
|
||||
self.report = bytearray(3)
|
||||
|
||||
def _intify_coordinate(self, row, col):
|
||||
return row << 8 | col
|
||||
|
||||
def scan_for_pressed(self):
|
||||
pressed = []
|
||||
ba_idx = 0
|
||||
any_changed = False
|
||||
|
||||
for oidx, opin in enumerate(self.outputs):
|
||||
opin.value(True)
|
||||
|
||||
for iidx, ipin in enumerate(self.inputs):
|
||||
if ipin.value():
|
||||
if self.diode_orientation == DiodeOrientation.ROWS:
|
||||
report_tuple = (oidx, iidx)
|
||||
else:
|
||||
old_val = self.state[ba_idx]
|
||||
new_val = ipin.value()
|
||||
|
||||
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
|
||||
)
|
||||
report_tuple = (new_iidx, new_oidx)
|
||||
|
||||
if report_tuple in self.swap_indicies:
|
||||
report_tuple = self.swap_indicies[report_tuple]
|
||||
self.report[0] = new_iidx
|
||||
self.report[1] = new_oidx
|
||||
else:
|
||||
self.report[0] = oidx
|
||||
self.report[1] = iidx
|
||||
|
||||
pressed.append(report_tuple)
|
||||
swap_src = self._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
|
||||
break
|
||||
|
||||
ba_idx += 1
|
||||
|
||||
opin.value(False)
|
||||
|
||||
if len(pressed) != self.last_pressed_len:
|
||||
self.last_pressed_len = len(pressed)
|
||||
return matrix_changed(pressed)
|
||||
if any_changed:
|
||||
break
|
||||
|
||||
return None # The default, but for explicitness
|
||||
if any_changed:
|
||||
return self.report
|
||||
|
6
kmk/mcus/circuitpython_samd51.py
Normal file
6
kmk/mcus/circuitpython_samd51.py
Normal 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
|
@ -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
|
@ -6,6 +6,8 @@ per-file-ignores =
|
||||
# Allow crazy line lengths, unused variables, and multiple spaces after commas in lists (for grid alignment)
|
||||
user_keymaps/**/*.py: F401,E501,E241
|
||||
tests/test_data/keymaps/**/*.py: F401,E501
|
||||
# Forgive me for my RAM hack sins
|
||||
kmk/firmware.py: I001,I003,I004,F401
|
||||
|
||||
[isort]
|
||||
known_third_party = analogio,bitbangio,bleio,board,busio,digitalio,framebuf,gamepad,gc,microcontroller,micropython,pulseio,pyb,pydux,uio,ubluepy,machine,pyb,uos
|
||||
|
@ -1,5 +1,4 @@
|
||||
# CircuitPython provides collections, don't overwrite it
|
||||
MICROPY|vendor/upy-lib/collections/collections
|
||||
|
||||
MICROPYCIRCUITPY|vendor/upy-lib/logging/logging.py
|
||||
MICROPYCIRCUITPY|vendor/upy-lib/string/string.py
|
||||
|
@ -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],
|
||||
],
|
||||
]
|
@ -1,24 +1,27 @@
|
||||
from kmk.consts import DiodeOrientation, UnicodeModes
|
||||
from kmk.entrypoints.handwire.circuitpython_samd51 import main
|
||||
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 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.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)
|
||||
rows = (P.D10, P.D11, P.D12, P.D13)
|
||||
keyboard = Firmware()
|
||||
|
||||
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 ---------------------------------------
|
||||
unicode_mode = UnicodeModes.LINUX
|
||||
tap_time = 200
|
||||
leader_timeout = 2000
|
||||
debug_enable = True
|
||||
keyboard.unicode_mode = UnicodeModes.LINUX
|
||||
keyboard.tap_time = 200
|
||||
keyboard.leader_timeout = 2000
|
||||
keyboard.debug_enabled = True
|
||||
|
||||
emoticons = AttrDict({
|
||||
emoticons = compile_unicode_string_sequences({
|
||||
# Emoticons, but fancier
|
||||
'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻',
|
||||
'CHEER': r'+。:.゚ヽ(´∀。)ノ゚.:。+゚゚+。:.゚ヽ(*´∀)ノ゚.:。+゚',
|
||||
@ -30,26 +33,23 @@ emoticons = AttrDict({
|
||||
'YAY': r'o(^▽^)o',
|
||||
})
|
||||
|
||||
for k, v in emoticons.items():
|
||||
emoticons[k] = unicode_string_sequence(v)
|
||||
|
||||
# ---------------------- Leader Key Macros --------------------------------------------
|
||||
|
||||
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,
|
||||
keyboard.leader_dictionary = {
|
||||
glds('flip'): emoticons.ANGRY_TABLE_FLIP,
|
||||
glds('cheer'): emoticons.CHEER,
|
||||
glds('wat'): emoticons.WAT,
|
||||
glds('ff'): emoticons.FF,
|
||||
glds('f'): emoticons.F,
|
||||
glds('meh'): emoticons.MEH,
|
||||
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.")
|
||||
|
||||
# ---------------------- Keymap ---------------------------------------------------------
|
||||
|
||||
keymap = [
|
||||
keyboard.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],
|
||||
@ -88,4 +88,4 @@ keymap = [
|
||||
]
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
keyboard.go()
|
@ -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()
|
@ -1,39 +0,0 @@
|
||||
from kmk.consts import DiodeOrientation, UnicodeModes
|
||||
from kmk.entrypoints.handwire.circuitpython_samd51 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
|
||||
|
||||
# physical, visible cols (SCK, MO, MI, RX, TX, D4)
|
||||
# physical, visible rows (10, 11, 12, 13) (9, 6, 5, SCL)
|
||||
cols = (P.SCK, P.MOSI, P.MISO, P.RX, P.TX, P.D4)
|
||||
rows = (P.D10, P.D11, P.D12, P.D13, P.D9, P.D6, P.D5, P.SCL)
|
||||
|
||||
swap_indicies = {
|
||||
(3, 3): (3, 9),
|
||||
(3, 4): (3, 10),
|
||||
(3, 5): (3, 11),
|
||||
}
|
||||
|
||||
rollover_cols_every_rows = 4
|
||||
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
|
||||
|
||||
# ------------------User level config variables ---------------------------------------
|
||||
unicode_mode = UnicodeModes.LINUX
|
||||
debug_enable = True
|
||||
|
||||
keymap = [
|
||||
[
|
||||
[KC.A, KC.E, KC.I, KC.M, KC.Q, KC.U, KC.N1, KC.N5, KC.N9, KC.HASH, KC.AMPR, KC.UNDS],
|
||||
[KC.B, KC.F, KC.J, KC.N, KC.R, KC.V, KC.N2, KC.N6, KC.N0, KC.DOLLAR, KC.ASTR, KC.LCBR],
|
||||
[KC.C, KC.G, KC.K, KC.O, KC.S, KC.W, KC.N3, KC.N7, KC.EXCLAIM, KC.PERCENT, KC.LPRN, KC.RCBR],
|
||||
[KC.D, KC.H, KC.L, KC.P, KC.T, KC.X, KC.N4, KC.N8, KC.AT, KC.CIRC, KC.RPRN, KC.PIPE],
|
||||
],
|
||||
]
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -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()
|
89
user_keymaps/klardotsh/klarank_featherm4.py
Normal file
89
user_keymaps/klardotsh/klarank_featherm4.py
Normal 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()
|
@ -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],
|
||||
],
|
||||
]
|
Loading…
Reference in New Issue
Block a user