Merge branch 'master' into master

This commit is contained in:
Kyle Brown 2018-11-15 17:03:04 -08:00 committed by GitHub
commit bdf626255d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 317 additions and 192 deletions

8
.gitmodules vendored
View File

@ -1,8 +0,0 @@
[submodule "upy-lib"]
path = vendor/upy-lib
url = https://github.com/kmkfw/micropython-lib.git
ignore = dirty
[submodule "micropython"]
path = vendor/micropython
url = https://github.com/kmkfw/micropython.git
ignore = dirty

View File

@ -1,3 +1,14 @@
Almost all of KMK is licensed under the GPLv3. There are a couple of
exceptions:
- `kmk/string.py` is copied directly from
[micropython-lib](https://github.com/micropython/micropython-lib) and is
under the MIT license, copyrighted by the micropython-lib contributors
- Hardware schematics are licensed under individual terms per schematic
Files/components not listed above or containing its own copyright header in the
file itself are licensed under GPLv3 as follows:
### GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007

View File

@ -43,7 +43,7 @@ fix-isort: devdeps
clean: clean-build-log
@echo "===> Cleaning build artifacts"
@rm -rf .submodules .circuitpy-deps .micropython-deps .devdeps build
@rm -rf .devdeps build
clean-build-log:
@echo "===> Clearing previous .build.log"
@ -57,38 +57,6 @@ powerwash: clean
test: lint
.submodules: .gitmodules submodules.toml
@echo "===> Pulling dependencies, this may take several minutes"
@echo "===> Pulling dependencies, this may take several minutes" >> .build.log
@git submodule sync 2>&1 >> .build.log
@git submodule update --init --recursive 2>&1 >> .build.log
@rsync -ah vendor/ build/
@touch .submodules
.micropython-deps: .submodules
@echo "===> Building micropython/mpy-cross"
@echo "===> Building micropython/mpy-cross" >> .build.log
@pipenv run $(MAKE) -C build/micropython/mpy-cross 2>&1 >> .build.log
@touch .micropython-deps
submodules: .submodules
micropython-deps: .micropython-deps
build/micropython/ports/unix/micropython: micropython-deps build/micropython/ports/unix/modules/.kmk_frozen
@echo "===> Building MicroPython for Unix"
@echo "===> Building MicroPython for Unix" >> .build.log
@pipenv run $(MAKE) -j4 -C build/micropython/ports/unix 2>&1 >> .build.log
micropython-build-unix: build/micropython/ports/unix/micropython
build/micropython/ports/unix/modules/.kmk_frozen: upy-freeze.txt submodules.toml
@echo "===> Preparing vendored dependencies for bundling into MicroPython for Unix"
@echo "===> Preparing vendored dependencies for bundling into MicroPython for Unix" >> .build.log
@rm -rf build/micropython/ports/unix/modules/*
@cat upy-freeze.txt | egrep -v '(^#|^\s*$|^\s*\t*#)' | grep MICROPY | cut -d'|' -f2- | \
xargs -I '{}' rsync -ah {} build/micropython/ports/unix/modules/
@touch $@
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
@ -101,8 +69,6 @@ ifdef MOUNTPOINT
$(MOUNTPOINT)/kmk/.copied: $(shell find kmk/ -name "*.py" | xargs -0)
@echo "===> Copying KMK source folder"
@rsync -rh kmk $(MOUNTPOINT)/
@cat upy-freeze.txt | egrep -v '(^#|^\s*$|^\s*\t*#)' | grep CIRCUITPY | cut -d'|' -f2- | \
xargs -I '{}' rsync -h {} $(MOUNTPOINT)/
@touch $(MOUNTPOINT)/kmk/.copied
@sync

View File

@ -76,11 +76,10 @@ out the Windows Subsystem for Linux if you're on Windows.
## License, Copyright, and Legal
This project, and all source code within (even if the file is missing headers),
is licensed
Most files in this project are licensed
[GPLv3](https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)) -
while the tl;dr is linked, the full license text is included in `LICENSE.md` at
the top of this source tree.
see `LICENSE.md` at the top of this source tree for exceptions and the full
license text.
When contributing for the first time, you'll need to sign a Contributor
Licensing Agreement which is based on the Free Software Foundation's CLA. The

View File

@ -1,6 +0,0 @@
if [ ! -f build/micropython/ports/unix/micropython ]; then
echo "Run make micropython-build-unix"
exit 1
fi
build/micropython/ports/unix/micropython $@

61
docs/tapdance.md Normal file
View File

@ -0,0 +1,61 @@
# Tap Dance
Tap dance is a way to allow a single physical key to work as multple logical
keys / actions without using layers. With basic tap dance, you can trigger these
"nested" keys or macros through a series of taps of the physical key within a
given timeout.
The resulting "logical" action works just like any other key - it can be pressed
and immediately released, or it can be held. For example, let's take a key
`KC.TD(KC.A, KC.B)`. If the tap dance key is tapped and released once quickly,
the letter "a" will be sent. If it is tapped and released twice quickly, the
letter "b" will be sent. If it is tapped once and held, the letter "a" will be
held down until the tap dance key is released. If it is tapped and released once
quickly, then tapped and held (both actions within the timeout window), the
letter "b" will be held down until the tap dance key is released.
To use this, you may want to define a `tap_time` value in your keyboard
configuration. This is an integer in milliseconds, and defaults to `300`.
You'll then want to create a sequence of keys using `KC.TD(KC.SOMETHING,
KC.SOMETHING_ELSE, MAYBE_THIS_IS_A_MACRO, WHATEVER_YO)`, and place it in your
keymap somewhere. The only limits on how many keys can go in the sequence are,
theoretically, the amount of RAM your MCU/board has, and how fast you can mash
the physical key. Here's your chance to use all that button-mash video game
experience you've built up over the years.
**NOTE**: Currently our basic tap dance implementation has some limitations that
are planned to be worked around "eventually", but for now are noteworthy:
- The behavior of momentary layer switching within a tap dance sequence is
currently "undefined" at best, and will probably crash your keyboard. For now,
we strongly recommend avoiding `KC.MO` (or any other layer switch keys that
use momentary switch behavior - `KC.LM`, `KC.LT`, and `KC.TT`)
- Super fancy stuff like sending a keypress only when the leader key is released
(perhaps based on how long the leader key was held) is **unsupported** - an
example usecase might be "tap for Home, hold for Shift"
Here's an example of all this in action:
```python
# user_keymaps/some_silly_example.py
from kmk.boards.klarank import Firmware
from kmk.keycodes import KC
from kmk.macros.simple import send_string
keyboard = Firmware()
keyboard.tap_time = 750
EXAMPLE_TD = KC.TD(
KC.A, # Tap once for "a"
KC.B, # Tap twice for "b"
# Tap three times to send a raw string via macro
send_string('macros in a tap dance? I think yes'),
# Tap four times to toggle layer index 1
KC.TG(1),
)
keyboard.keymap = [[ ...., EXAMPLE_TD, ....], ....]
```

View File

@ -64,6 +64,7 @@ class Firmware:
split_offsets = ()
split_flip = False
split_side = None
split_type = None
split_master_left = True
is_master = None
uart = None
@ -78,14 +79,14 @@ class Firmware:
def _send_key(self, key):
if not getattr(key, 'no_press', None):
self._state.force_keycode_down(key)
self._state.add_key(key)
self._send_hid()
if not getattr(key, 'no_release', None):
self._state.force_keycode_up(key)
self._state.remove_key(key)
self._send_hid()
def _handle_update(self, update):
def _handle_matrix_report(self, update=None):
'''
Bulk processing of update code for each cycle
:param update:
@ -97,24 +98,6 @@ class Firmware:
update[2],
)
if self._state.hid_pending:
self._send_hid()
if self.debug_enabled:
print('New State: {}'.format(self._state._to_dict()))
self._state.process_timeouts()
for key in self._state.pending_keys:
self._send_key(key)
self._state.pending_key_handled()
if self._state.macro_pending:
for key in self._state.macro_pending(self):
self._send_key(key)
self._state.resolve_macro()
def _send_to_master(self, update):
if self.split_master_left:
update[1] += self.split_offsets[update[0]]
@ -189,19 +172,45 @@ class Firmware:
print("Firin' lazers. Keyboard is booted.")
while True:
state_changed = False
if self.split_type is not None and self._master_half:
update = self._receive_from_slave()
if update is not None:
self._handle_update(update)
self._handle_matrix_report(update)
state_changed = True
update = self.matrix.scan_for_changes()
if update is not None:
if self._master_half():
self._handle_update(update)
self._handle_matrix_report(update)
state_changed = True
else:
# This keyboard is a slave, and needs to send data to master
self._send_to_master(update)
if self._state.hid_pending:
self._send_hid()
old_timeouts_len = len(self._state.timeouts)
self._state.process_timeouts()
new_timeouts_len = len(self._state.timeouts)
if old_timeouts_len != new_timeouts_len:
state_changed = True
if self._state.macros_pending:
# Blindly assume macros are going to change state, which is almost
# always a safe assumption
state_changed = True
for macro in self._state.macros_pending:
for key in macro(self):
self._send_key(key)
self._state.resolve_macro()
if self.debug_enabled and state_changed:
print('New State: {}'.format(self._state._to_dict()))
gc.collect()

View File

@ -1,5 +1,6 @@
from kmk.consts import LeaderMode
from kmk.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes, RawKeycodes
from kmk.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, Keycodes, RawKeycodes,
TapDanceKeycode)
from kmk.kmktime import sleep_ms, ticks_diff, ticks_ms
from kmk.util import intify_coordinate
@ -12,8 +13,7 @@ GESC_TRIGGERS = {
class InternalState:
keys_pressed = set()
coord_keys_pressed = {}
pending_keys = []
macro_pending = None
macros_pending = []
leader_pending = None
leader_last_len = 0
hid_pending = False
@ -28,6 +28,9 @@ class InternalState:
'leader': None,
}
timeouts = {}
tapping = False
tap_dance_counts = {}
tap_side_effects = {}
def __init__(self, config):
self.config = config
@ -45,6 +48,7 @@ class InternalState:
Keycodes.KMK.KC_LEAD.code: self._kc_lead,
Keycodes.KMK.KC_NO.code: self._kc_no,
Keycodes.KMK.KC_DEBUG.code: self._kc_debug_mode,
RawKeycodes.KC_TAP_DANCE: self._kc_tap_dance,
}
def __repr__(self):
@ -57,6 +61,9 @@ class InternalState:
'leader_mode_history': self.leader_mode_history,
'leader_mode': self.config.leader_mode,
'start_time': self.start_time,
'tapping': self.tapping,
'tap_dance_counts': self.tap_dance_counts,
'timeouts': self.timeouts,
}
return ret
@ -76,14 +83,26 @@ class InternalState:
return layer_key
def set_timeout(self, after_ticks, callback):
timeout_key = ticks_ms() + after_ticks
if after_ticks is False:
# We allow passing False as an implicit "run this on the next process timeouts cycle"
timeout_key = ticks_ms()
else:
timeout_key = ticks_ms() + after_ticks
self.timeouts[timeout_key] = callback
return self
def process_timeouts(self):
if not self.timeouts:
return self
current_time = ticks_ms()
for k, v in self.timeouts.items():
# cast this to a tuple to ensure that if a callback itself sets
# timeouts, we do not handle them on the current cycle
timeouts = tuple(self.timeouts.items())
for k, v in timeouts:
if k <= current_time:
v()
del self.timeouts[k]
@ -103,45 +122,52 @@ class InternalState:
print('No key accessible for col, row: {}, {}'.format(row, col))
return self
if is_pressed:
self.keys_pressed.add(kc_changed)
self.coord_keys_pressed[int_coord] = kc_changed
if self.tapping and not isinstance(kc_changed, TapDanceKeycode):
self._process_tap_dance(kc_changed, is_pressed)
else:
self.keys_pressed.discard(kc_changed)
self.keys_pressed.discard(self.coord_keys_pressed[int_coord])
self.coord_keys_pressed[int_coord] = None
if is_pressed:
self.coord_keys_pressed[int_coord] = kc_changed
self.add_key(kc_changed)
else:
self.remove_key(kc_changed)
self.keys_pressed.discard(self.coord_keys_pressed.get(int_coord, None))
self.coord_keys_pressed[int_coord] = None
if kc_changed.code >= FIRST_KMK_INTERNAL_KEYCODE:
self._process_internal_key_event(
kc_changed,
is_pressed,
)
if self.config.leader_mode % 2 == 1:
self._process_leader_mode()
return self
def remove_key(self, keycode):
self.keys_pressed.discard(keycode)
if keycode.code >= FIRST_KMK_INTERNAL_KEYCODE:
self._process_internal_key_event(keycode, False)
else:
self.hid_pending = True
if self.config.leader_mode % 2 == 1:
self._process_leader_mode()
return self
def force_keycode_up(self, keycode):
self.keys_pressed.discard(keycode)
self.hid_pending = True
return self
def force_keycode_down(self, keycode):
def add_key(self, keycode):
# TODO Make this itself a macro keycode with a keyup handler
# rather than handling this inline here. Gross.
if keycode.code == Keycodes.KMK.KC_MACRO_SLEEP_MS:
sleep_ms(keycode.ms)
else:
self.keys_pressed.add(keycode)
self.hid_pending = True
if keycode.code >= FIRST_KMK_INTERNAL_KEYCODE:
self._process_internal_key_event(keycode, True)
else:
self.hid_pending = True
return self
def pending_key_handled(self):
popped = self.pending_keys.pop()
if self.config.debug_enabled:
print('Popped pending key: {}'.format(popped))
def tap_key(self, keycode):
self.add_key(keycode)
# On the next cycle, we'll remove the key. This is way more clean than
# the `pending_keys` implementation that we used to rely on in
# firmware.py
self.set_timeout(False, lambda: self.remove_key(keycode))
return self
@ -153,7 +179,7 @@ class InternalState:
if self.config.debug_enabled:
print('Macro complete!')
self.macro_pending = None
self.macros_pending.pop()
return self
def _process_internal_key_event(self, changed_key, is_pressed):
@ -214,7 +240,7 @@ class InternalState:
ticks_diff(ticks_ms(), self.start_time['lt']) < self.config.tap_time
):
self.hid_pending = True
self.pending_keys.append(changed_key.kc)
self.tap_key(changed_key.kc)
self._layer_mo(changed_key, is_pressed)
self.start_time['lt'] = None
@ -274,10 +300,10 @@ class InternalState:
def _kc_macro(self, changed_key, is_pressed):
if is_pressed:
if changed_key.keyup:
self.macro_pending = changed_key.keyup
self.macros_pending.append(changed_key.keyup)
else:
if changed_key.keydown:
self.macro_pending = changed_key.keydown
self.macros_pending.append(changed_key.keydown)
return self
@ -318,6 +344,68 @@ class InternalState:
return self
def _kc_tap_dance(self, changed_key, is_pressed):
return self._process_tap_dance(changed_key, is_pressed)
def _process_tap_dance(self, changed_key, is_pressed):
if is_pressed:
if not isinstance(changed_key, TapDanceKeycode):
# If we get here, changed_key is not a TapDanceKeycode and thus
# the user kept typing elsewhere (presumably). End ALL of the
# currently outstanding tap dance runs.
for k, v in self.tap_dance_counts.items():
if v:
self._end_tap_dance(k)
return self
if (
changed_key not in self.tap_dance_counts or
not self.tap_dance_counts[changed_key]
):
self.tap_dance_counts[changed_key] = 1
self.set_timeout(self.config.tap_time, lambda: self._end_tap_dance(changed_key))
self.tapping = True
else:
self.tap_dance_counts[changed_key] += 1
if changed_key not in self.tap_side_effects:
self.tap_side_effects[changed_key] = None
else:
if (
self.tap_side_effects[changed_key] is not None or
self.tap_dance_counts[changed_key] == len(changed_key.codes)
):
self._end_tap_dance(changed_key)
return self
def _end_tap_dance(self, td_key):
v = self.tap_dance_counts[td_key] - 1
if v >= 0:
if td_key in self.keys_pressed:
key_to_press = td_key.codes[v]
self.add_key(key_to_press)
self.tap_side_effects[td_key] = key_to_press
self.hid_pending = True
else:
if self.tap_side_effects[td_key]:
self.remove_key(self.tap_side_effects[td_key])
self.tap_side_effects[td_key] = None
self.hid_pending = True
self._cleanup_tap_dance(td_key)
else:
self.tap_key(td_key.codes[v])
self._cleanup_tap_dance(td_key)
return self
def _cleanup_tap_dance(self, td_key):
self.tap_dance_counts[td_key] = 0
self.tapping = any(count > 0 for count in self.tap_dance_counts.values())
return self
def _begin_leader_mode(self):
if self.config.leader_mode % 2 == 0:
self.keys_pressed.discard(Keycodes.KMK.KC_LEAD)
@ -333,7 +421,7 @@ class InternalState:
lmh = tuple(self.leader_mode_history)
if lmh in self.config.leader_dictionary:
self.macro_pending = self.config.leader_dictionary[lmh].keydown
self.macros_pending.append(self.config.leader_dictionary[lmh].keydown)
return self._exit_leader_mode()

View File

@ -80,6 +80,7 @@ class RawKeycodes:
KC_MACRO = 1110
KC_MACRO_SLEEP_MS = 1111
KC_TAP_DANCE = 1113
# These shouldn't have all the fancy shenanigans Keycode allows
@ -184,6 +185,13 @@ class Macro:
return self.keyup() if self.keyup else None
class TapDanceKeycode:
code = RawKeycodes.KC_TAP_DANCE
def __init__(self, *codes):
self.codes = codes
class KeycodeCategory(type):
@classmethod
def to_dict(cls):
@ -364,7 +372,7 @@ class ShiftedKeycodes(KeycodeCategory):
KC_DOUBLE_QUOTE = KC_DQUO = KC_DQT = Modifiers.KC_LSHIFT(Common.KC_QUOTE)
KC_LEFT_ANGLE_BRACKET = KC_LABK = Modifiers.KC_LSHIFT(Common.KC_COMMA)
KC_RIGHT_ANGLE_BRACKET = KC_RABK = Modifiers.KC_LSHIFT(Common.KC_DOT)
KC_QUESTION = KC_QUES = Modifiers.KC_LSHIFT(Common.KC_DOT)
KC_QUESTION = KC_QUES = Modifiers.KC_LSHIFT(Common.KC_SLSH)
class FunctionKeys(KeycodeCategory):
@ -551,6 +559,13 @@ class KMK(KeycodeCategory):
def KC_MACRO_SLEEP_MS(ms):
return MacroSleepKeycode(RawKeycodes.KC_MACRO_SLEEP_MS, ms)
@staticmethod
def KC_TAP_DANCE(*args):
return TapDanceKeycode(*args)
KMK.KC_TD = KMK.KC_TAP_DANCE
class Layers(KeycodeCategory):
@staticmethod

View File

@ -1,6 +1,5 @@
import string
from kmk.keycodes import Keycodes, Macro, char_lookup, lookup_kc_with_cache
from kmk.string import ascii_letters, digits
def simple_key_sequence(seq):
@ -18,7 +17,7 @@ def send_string(message):
if char in char_lookup:
kc = char_lookup[char]
elif char in string.ascii_letters + string.digits:
elif char in ascii_letters + digits:
kc = lookup_kc_with_cache(char)
if char.isupper():

51
kmk/string.py Normal file
View File

@ -0,0 +1,51 @@
# This file copied from micropython-lib
# https://github.com/micropython/micropython-lib
#
# The MIT License (MIT)
#
# Copyright (c) 2013, 2014 micropython-lib contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# Some strings for ctype-style character classification
whitespace = ' \t\n\r\v\f'
ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ascii_letters = ascii_lowercase + ascii_uppercase
digits = '0123456789'
hexdigits = digits + 'abcdef' + 'ABCDEF'
octdigits = '01234567'
punctuation = """!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
printable = digits + ascii_letters + punctuation + whitespace
def translate(s, map):
import io
sb = io.StringIO()
for c in s:
v = ord(c)
if v in map:
v = map[v]
if isinstance(v, int):
sb.write(chr(v))
elif v is not None:
sb.write(v)
else:
sb.write(c)
return sb.getvalue()

View File

@ -1,3 +0,0 @@
from kmk_keyboard import main
main()

View File

@ -1,3 +0,0 @@
[submodules]
"vendor/micropython" = "65a49fa"
"vendor/upy-lib" = "451b1c0"

View File

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

View File

@ -1,3 +0,0 @@
class DigitalInOut:
def __init__(self, *args, **kwargs):
pass

View File

@ -1,38 +0,0 @@
class Anything:
'''
A stub class which will repr as a provided name
'''
def __init__(self, name):
self.name = name
def __call__(self, *args, **kwargs):
return self
def __repr__(self):
return 'Anything<{}>'.format(self.name)
def init(self, *args, **kwargs):
pass
@property
def value(self):
return False
class Passthrough:
def __getattr__(self, attr):
return Anything(attr)
class Pin:
board = Passthrough()
IN = 'IN'
OUT = 'OUT'
PULL_DOWN = 'PULL_DOWN'
PULL_UP = 'PULL_UP'
def __call__(self, *args, **kwargs):
return self.board
def __getattr__(self, attr):
return getattr(self.board, attr)

View File

@ -1,14 +0,0 @@
try:
from collections import namedtuple
except ImportError:
from ucollections import namedtuple
HIDMode = namedtuple('HIDMode', (
'subclass',
'protocol',
'max_packet_length',
'polling_interval',
'report_descriptor',
))
hid_keyboard = HIDMode(0, 0, 0, 0, bytearray(0))

View File

@ -1 +0,0 @@
devices = []

View File

@ -9,9 +9,7 @@ keyboard = Firmware()
keyboard.debug_enabled = True
keyboard.unicode_mode = UnicodeModes.LINUX
_______ = KC.TRNS
xxxxxxx = KC.NO
keyboard.tap_time = 750
emoticons = cuss({
# Emojis
@ -56,6 +54,16 @@ keyboard.leader_dictionary = {
glds('cel'): emoticons.CELEBRATORY_GLITTER,
}
_______ = KC.TRNS
xxxxxxx = KC.NO
HELLA_TD = KC.TD(
KC.A,
KC.B,
send_string('macros in a tap dance? I think yes'),
KC.TG(1),
)
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],
@ -79,10 +87,10 @@ keyboard.keymap = [
],
[
[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, KC.DBG, xxxxxxx, xxxxxxx, _______, _______, xxxxxxx, xxxxxxx, KC.MUTE, KC.VOLD, KC.VOLU, xxxxxxx],
[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, KC.DBG, HELLA_TD, xxxxxxx, _______, _______, xxxxxxx, xxxxxxx, KC.MUTE, KC.VOLD, KC.VOLU, xxxxxxx],
],
]

1
vendor/micropython vendored

@ -1 +0,0 @@
Subproject commit 65a49fa3c80e7432ccb35b9f34e5c4c0a62b933d

1
vendor/upy-lib vendored

@ -1 +0,0 @@
Subproject commit 451b1c07567de85062ef35b672b2101647285e9a