Merge pull request #105 from KMKfw/topic-refactor-keycodes-and-macros

Congressional Bill 122918 Forgot To Sleep Edition: Refactor everything about how key definitions work
This commit is contained in:
Josh Klar 2019-02-21 09:55:42 -08:00 committed by GitHub
commit 1ad7602a9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1568 additions and 1418 deletions

View File

@ -1,9 +1,5 @@
Almost all of KMK is licensed under the GPLv3. There are a couple of
exceptions:
Almost all of KMK is licensed under the GPLv3. The only exceptions are:
- `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

View File

@ -15,7 +15,7 @@ AMPY_DELAY ?= 1.5
ARDUINO ?= /usr/share/arduino
PIPENV ?= $(shell which pipenv)
all: copy-kmk copy-keymap
all: copy-kmk copy-bootpy copy-keymap
.docker_base: Dockerfile_base
@echo "===> Building Docker base image kmkfw/base:${DOCKER_BASE_TAG}"
@ -78,6 +78,18 @@ copy-kmk:
echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1
endif
ifdef MOUNTPOINT
$(MOUNTPOINT)/kmk/boot.py: boot.py
@echo "===> Copying required boot.py"
@rsync -rh boot.py $(MOUNTPOINT)/
@sync
copy-bootpy: $(MOUNTPOINT)/kmk/boot.py
else
copy-bootpy:
echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1
endif
ifdef MOUNTPOINT
ifndef USER_KEYMAP
$(MOUNTPOINT)/main.py:

3
boot.py Executable file
View File

@ -0,0 +1,3 @@
import supervisor
supervisor.set_next_stack_limit(4096 + 1024)

View File

@ -1,6 +1,6 @@
# Keycodes Overview
# Keys Overview
## [Basic Keycodes]
## [Basic Keys]
|Key |Aliases |Description |
|-----------------------|--------------------|-----------------------------------------------|
@ -103,8 +103,6 @@
|`KC.KP_0` |`KC.P0` |Keypad `0` and Insert |
|`KC.KP_DOT` |`KC.PDOT` |Keypad `.` and Delete |
|`KC.NONUS_BSLASH` |`KC.NUBS` |Non-US `\` and <code>&#124;</code> |
|`KC.APPLICATION` |`KC.APP` |Application (Windows Menu Key) |
|`KC.POWER` | |System Power (macOS) |
|`KC.KP_EQUAL` |`KC.PEQL` |Keypad `=` |
|`KC.F13` | |F13 |
|`KC.F14` | |F14 |
@ -118,20 +116,6 @@
|`KC.F22` | |F22 |
|`KC.F23` | |F23 |
|`KC.F24` | |F24 |
|`KC.EXECUTE` |`KC.EXEC` |Execute |
|`KC.HELP` | |Help |
|`KC.MENU` | |Menu |
|`KC.SELECT` |`KC.SLCT` |Select |
|`KC.STOP` | |Stop |
|`KC.AGAIN` |`KC.AGIN` |Again |
|`KC.UNDO` | |Undo |
|`KC.CUT` | |Cut |
|`KC.COPY` | |Copy |
|`KC.PASTE` |`KC.PSTE` |Paste |
|`KC.FIND` | |Find |
|`KC._MUTE` | |Mute (macOS) |
|`KC._VOLUP` | |Volume Up (macOS) |
|`KC._VOLDOWN` | |Volume Down (macOS) |
|`KC.LOCKING_CAPS` |`KC.LCAP` |Locking Caps Lock |
|`KC.LOCKING_NUM` |`KC.LNUM` |Locking Num Lock |
|`KC.LOCKING_SCROLL` |`KC.LSCR` |Locking Scroll Lock |
@ -155,18 +139,6 @@
|`KC.LANG7` | |Language 7 |
|`KC.LANG8` | |Language 8 |
|`KC.LANG9` | |Language 9 |
|`KC.ALT_ERASE` |`KC.ERAS` |Alternate Erase |
|`KC.SYSREQ` | |SysReq/Attention |
|`KC.CANCEL` | |Cancel |
|`KC.CLEAR` |`KC.CLR` |Clear |
|`KC.PRIOR` | |Prior |
|`KC.RETURN` | |Return |
|`KC.SEPARATOR` | |Separator |
|`KC.OUT` | |Out |
|`KC.OPER` | |Oper |
|`KC.CLEAR_AGAIN` | |Clear/Again |
|`KC.CRSEL` | |CrSel/Props |
|`KC.EXSEL` | |ExSel |
|`KC.LCTRL` |`KC.LCTL` |Left Control |
|`KC.LSHIFT` |`KC.LSFT` |Left Shift |
|`KC.LALT` | |Left Alt |
@ -175,9 +147,6 @@
|`KC.RSHIFT` |`KC.RSFT` |Right Shift |
|`KC.RALT` | |Right Alt |
|`KC.RGUI` |`KC.RCMD`, `KC.RWIN`|Right GUI (Windows/Command/Meta key) |
|`KC.SYSTEM_POWER` |`KC.PWR` |System Power Down |
|`KC.SYSTEM_SLEEP` |`KC.SLEP` |System Sleep |
|`KC.SYSTEM_WAKE` |`KC.WAKE` |System Wake |
|`KC.AUDIO_MUTE` |`KC.MUTE` |Mute |
|`KC.AUDIO_VOL_UP` |`KC.VOLU` |Volume Up |
|`KC.AUDIO_VOL_DOWN` |`KC.VOLD` |Volume Down |
@ -185,18 +154,7 @@
|`KC.MEDIA_PREV_TRACK` |`KC.MPRV` |Previous Track (Windows) |
|`KC.MEDIA_STOP` |`KC.MSTP` |Stop Track (Windows) |
|`KC.MEDIA_PLAY_PAUSE` |`KC.MPLY` |Play/Pause Track |
|`KC.MEDIA_SELECT` |`KC.MSEL` |Launch Media Player (Windows) |
|`KC.MEDIA_EJECT` |`KC.EJCT` |Eject (macOS) |
|`KC.MAIL` | |Launch Mail (Windows) |
|`KC.CALCULATOR` |`KC.CALC` |Launch Calculator (Windows) |
|`KC.MY_COMPUTER` |`KC.MYCM` |Launch My Computer (Windows) |
|`KC.WWW_SEARCH` |`KC.WSCH` |Browser Search (Windows) |
|`KC.WWW_HOME` |`KC.WHOM` |Browser Home (Windows) |
|`KC.WWW_BACK` |`KC.WBAK` |Browser Back (Windows) |
|`KC.WWW_FORWARD` |`KC.WFWD` |Browser Forward (Windows) |
|`KC.WWW_STOP` |`KC.WSTP` |Browser Stop (Windows) |
|`KC.WWW_REFRESH` |`KC.WREF` |Browser Refresh (Windows) |
|`KC.WWW_FAVORITES` |`KC.WFAV` |Browser Favorites (Windows) |
|`KC.MEDIA_FAST_FORWARD`|`KC.MFFD` |Next Track (macOS) |
|`KC.MEDIA_REWIND` |`KC.MRWD` |Previous Track (macOS) |
@ -228,7 +186,7 @@
|`KC.QUESTION` |`KC.QUES` |`?` |
## [Internal Keycodes]
## [Internal Keys]
|Key |Description |
|-----------------------|---------------------------------------------------------------------|

158
docs/keys.md Normal file
View File

@ -0,0 +1,158 @@
# Keys
> NOTE: This is not a lookup table of key objects provided by KMK. That listing
> can be found in `keycodes.md`, though that file is not always kept up to date.
> It's probably worth a look at the raw source if you're stumped: `kmk/keys.py`.
This is a bunch of documentation about how physical keypresses translate to
events (and the lifecycle of said events) in KMK. It's somewhat technical, but
if you're looking to extend your keyboard's functionality with extra code,
you'll need at least some of this technical knowledge.
The first few steps in the process aren't all that interesting for most
workflows, which is why they're buried deep in KMK: we scan a bunch of GPIO
lanes (about as quickly as CircuitPython will let us) to see where, in a matrix
of keys, a key has been pressed. The technical details about this process [are
probably best left to
Wikipedia](https://en.wikipedia.org/wiki/Keyboard_matrix_circuit). Then, we scan
through the defined keymap, finding the first valid key at this index based on
the stack of currently active layers (this logic, if you want to read through
the code, is in `kmk/internal_state.py`, method `_find_key_in_map`).
The next few steps are the interesting part, but to understand them, we need to
understand a bit about what a `Key` object is (found in `kmk/keys.py`). `Key`
objects have a few core pieces of information:
* Their `code`, which can be any integer. Integers below
`FIRST_KMK_INTERNAL_KEY` are sent through to the HID stack (and thus the
computer, which will translate that integer to something meaningful - for
example, `code=4` becomes `a` on a US QWERTY/Dvorak keyboard).
* Their attached modifiers (to implement things like shifted keys or `KC.HYPR`,
which are single key presses sending along more than one key in a single HID
report. This is a distinct concept from Sequences, which are a KMK feature
documented in `sequences.md`). For almost all purposes outside of KMK core,
this field should be ignored - it can be safely populated through far more
sane means than futzing with it by hand.
* Some data on whether the key should actually be pressed or released - this is
mostly an implementation detail of how Sequences work, where, for example,
`KC.RALT` may need to be held down for the entirety of a sequence, rather than
being released immediately before moving to the next character. Usually end
users shouldn't need to mess with this, but the fields are called `no_press`
and `no_release` and are referenced in a few places in the codebase if you
need examples.
* Handlers for "press" (sometimes known as "keydown") and "release" (sometimes
known as "keyup") events. KMK provides handlers for standard keyboard
functions and some special override keys (like `KC.GESC`, which is an enhanced
form of existing ANSI keys) in `kmk/handlers/stock.py`, for layer switching in
`kmk/handlers.layers.py`, and for everything related to Sequences (see
`sequences.md` again) in `kmk/handlers/sequences.py`. We'll discuss these more
shortly.
* Optional callbacks to be run before and/or after the above handlers. More on
that soon.
* A generic `meta` field, which is most commonly used for "argumented" keys -
objects in the `KC` object which are actually functions that return `Key`
instances, which often need to access the arguments passed into the "outer"
function. Many of these examples are related to layer switching - for example,
`KC.MO` is implemented as an argumented key - when the user adds `KC.MO(1)` to
their keymap, the function call returns a `Key` object with `meta` set to an
object containing `layer` and `kc` properties, for example. There's other uses
for `meta`, and examples can be found in `kmk/types.py`
`Key` objects can also be chained together by calling them! To create a key
which holds Control and Shift simultaneously, we can simply do:
```python
CTRLSHFT = KC.LCTL(KC.LSFT)
keyboard.keymap = [ ... CTRLSHFT ... ]
```
When a key is pressed and we've pulled a `Key` object out of the keymap, the
following will happen:
- Pre-press callbacks will be run in the order they were assigned, with their
return values discarded (unless the user attached these, they will almost
never exist)
- The assigned press handler will be run (most commonly, this is provided by
KMK)
- Post-press callbacks will be run in the order they were assigned, with their
return values discarded (unless the user attached these, they will almost
never exist)
These same steps are run for when a key is released.
_So now... what's a handler, and what's a pre/post callback?!_
All of these serve rougly the same purpose: to _do something_ with the key's
data, or to fire off side effects. Most handlers are provided by KMK internally
and modify the `InternalState` in some way - adding the key to the HID queue,
changing layers, etc. The pre/post handlers are designed to allow functionality
to be bolted on at these points in the event flow without having to reimplement
(or import and manually call) the internal handlers.
All of these methods take the same arguments, and for this, I'll lift a
docstring straight out of the source:
> Receives the following:
>
> - self (this Key instance)
> - state (the current InternalState)
> - KC (the global KC lookup table, for convenience)
> - `coord_int` (an internal integer representation of the matrix coordinate
> for the pressed key - this is likely not useful to end users, but is
> provided for consistency with the internal handlers)
> - `coord_raw` (an X,Y tuple of the matrix coordinate - also likely not useful)
>
> The return value of the provided callback is discarded. Exceptions are _not_
> caught, and will likely crash KMK if not handled within your function.
>
> These handlers are run in attachment order: handlers provided by earlier
> calls of this method will be executed before those provided by later calls.
This means if you want to add things like underglow/LED support, or have a
button that triggers your GSM modem to call someone, or whatever else you can
hack up in CircuitPython, which also retaining layer-switching abilities or
whatever the stock handler is, you're covered. This also means you can add
completely new functionality to KMK by writing your own handler.
Here's an example of a lifecycle hook to print a giant Shrek ASCII art. It
doesn't care about any of the arguments passed into it, because it has no
intentions of modifying the internal state. It is purely a [side
effect](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) run every
time Left Alt is pressed:
```python
def shrek(*args, **kwargs):
print('⢀⡴⠑⡄⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣤⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀')
print('⠸⡇⠀⠿⡀⠀⠀⠀⣀⡴⢿⣿⣿⣿⣿⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠑⢄⣠⠾⠁⣀⣄⡈⠙⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⢀⡀⠁⠀⠀⠈⠙⠛⠂⠈⣿⣿⣿⣿⣿⠿⡿⢿⣆⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⢀⡾⣁⣀⠀⠴⠂⠙⣗⡀⠀⢻⣿⣿⠭⢤⣴⣦⣤⣹⠀⠀⠀⢀⢴⣶⣆')
print('⠀⠀⢀⣾⣿⣿⣿⣷⣮⣽⣾⣿⣥⣴⣿⣿⡿⢂⠔⢚⡿⢿⣿⣦⣴⣾⠁⠸⣼⡿')
print('⠀⢀⡞⠁⠙⠻⠿⠟⠉⠀⠛⢹⣿⣿⣿⣿⣿⣌⢤⣼⣿⣾⣿⡟⠉⠀⠀⠀⠀⠀')
print('⠀⣾⣷⣶⠇⠀⠀⣤⣄⣀⡀⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀')
print('⠀⠉⠈⠉⠀⠀⢦⡈⢻⣿⣿⣿⣶⣶⣶⣶⣤⣽⡹⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠀⠀⠀⠉⠲⣽⡻⢿⣿⣿⣿⣿⣿⣿⣷⣜⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣷⣶⣮⣭⣽⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠀⠀⣀⣀⣈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⠿⠿⠿⠿⠛⠉')
KC.LALT.before_press_handler(shrek)
```
You can also copy a key without any pre/post handlers attached with `.clone()`,
so for example, if I've already added Shrek to my `LALT` but want a Shrek-less
`LALT` key elsewhere in my keymap, I can just clone it, and the new key won't
have my handlers attached:
```python
SHREKLESS_ALT = KC.LALT.clone()
```

112
docs/sequences.md Normal file
View File

@ -0,0 +1,112 @@
# Sequences
Sequences are used for sending multiple keystrokes in a single action, and can
be used for things like unicode characters (even emojis! 🇨🇦), lorei epsum
generators, triggering side effects (think lighting, speakers,
microcontroller-optimized cryptocurrency miners, whatever).
## Sending strings
The most basic sequence is `send_string`. It can be used to send any standard
English alphabet character, and an assortment of other "standard" keyboard keys
(return, space, exclamation points, etc.)
```python
from kmk.handlers.sequences import send_string
WOW = send_string("Wow, KMK is awesome!")
keyboard.keymap = [...WOW,...]
```
## Unicode
Before trying to send Unicode sequences, make sure you set your `UnicodeMode`.
You can set an initial value in your keymap by setting `keyboard.unicode_mode`.
Keys are provided to change this mode at runtime - for example, `KC.UC_MODE_LINUX`.
### Unicode Modes:
On Linux, Unicode uses `Ctrl-Shift-U`, which is supported by `ibus` and GTK+3.
`ibus` users will need to add `IBUS_ENABLE_CTRL_SHIFT_U=1` to their environment
(`~/profile`, `~/.bashrc`, `~/.zshrc`, or through your desktop environment's
configurator).
On Windows, [WinCompose](https://github.com/samhocevar/wincompose) is required.
- Linux : `UnicodeMode.LINUX` or `UnicodeMode.IBUS`
- Mac: `UnicodeMode.MACOS` or `UnicodeMode.OSX` or `UnicodeMode.RALT`
- Windows: `UnicodeMode.WINC`
### Unicode Examples
To send a simple unicode symbol
```python
from kmk.handlers.sequences import unicode_string_sequence
FLIP = unicode_string_sequence('(ノಠ痊ಠ)ノ彡┻━┻')
keyboard.keymap = [...FLIP,...]
```
If you'd rather keep a lookup table of your sequences (perhaps to bind emojis to
keys), that's supported too, through an obnoxiously long-winded method:
```python
from kmk.handlers.sequences import compile_unicode_string_sequences as cuss
emoticons = cuss({
'BEER': r'🍺',
'HAND_WAVE': r'👋',
})
keymap = [...emoticons.BEER, emoticons.HAND_WAVE...]
```
> The observant will notice dot-notation is supported here despite feeding in a
> dictionary - the return of `compile_unicode_string_sequences` is a
> `kmk.types.AttrDict`, which you can think of as a read-only view over a
> dictionary adding attribute-based (dot-notation) access.
Remember from the Leader Mode documentation that leader sequences simply bind to
keys, so extrapolating this example out a bit, you can bind emojis to leader
sequences matching some name or mnemonic representing the sequence you're
looking to send. If you ever wanted to type `<Leader>fire` and see a fire emoji
on your screen, welcome home.
```python
from kmk.handlers.sequences import compile_unicode_string_sequences as cuss
emoticons = cuss({
# Emojis
'BEER': r'🍺',
'BEER_TOAST': r'🍻',
'FACE_THINKING': r'🤔',
'FIRE': r'🔥',
'FLAG_CA': r'🇨🇦',
'FLAG_US': r'🇺🇸',
})
keyboard.leader_dictionary = {
'beer': emoticons.BEER,
'beers': emoticons.BEER_TOAST,
'fire': emoticons.FIRE,
'uhh': emoticons.FACE_THINKING,
'fca': emoticons.FLAG_CA,
'fus': emoticons.FLAG_US,
}
```
Finally, if you need to send arbitrary unicode codepoints in raw form, that's
supported too, through `unicode_codepoint_sequence`.
```python
from kmk.handlers.sequences import unicode_codepoint_sequence
TABLE_FLIP = unicode_codepoint_sequence([
"28", "30ce", "ca0", "75ca","ca0", "29",
"30ce", "5f61", "253b", "2501", "253b",
])
keyboard.keymap = [...TABLE_FLIP,...]
```

View File

@ -1,70 +0,0 @@
# Macros And Unicode
Macros are used for sending multiple keystrokes in a single action. This is useful for
things like unicode input, sending strings of text, or other automation.
## Basic Macros
The most basic macro is send_string(). It can be used to send any standard ASCII keycode, including the return and tab key.
```python
from kmk.macros.simple import send_string
WOW = send_string("Wow, KMK is awesome!")
keymap = [...WOW,...]
```
# Unicode
Before using unicode mode, you will need to set your platform. This can be done either of these ways.
You can use both in cases where you want to use one operating system, but occasionally use another.
This allows you to change modes on the fly without having to change your keymap.
unicode_mode = UnicodeMode.LINUX
Or
keymap = [...KC.UC_MODE_LINUX,...]
### Unicode Modes:
On Linux IBUS is required, and on Windows, requires [WinCompose](https://github.com/samhocevar/wincompose)
- Linux : UnicodeMode.LINUX or UnicodeMode.IBUS
- Mac: UnicodeMode.MACOS or UnicodeMode.OSX or UnicodeMode.RALT
- Windows: UnicodeMode.WINC
A note for IBUS users on Linux. This mode is not enabled by default, and will need to be turned on for this to work.
This works on X11, though if you are on Wayland, or in some GTK apps, it MAY work, but is not supported.
export IBUS_ENABLE_CTRL_SHIFT_U=1
### Unicode Examples
To send a simple unicode symbol
```python
FLIP = unicode_string_sequence('(ノಠ痊ಠ)ノ彡┻━┻')
keymap = [...FLIP,...]
```
And for many single character unicode:
```python
from kmk.types import AttrDic
emoticons = AttrDict({
'BEER': r'🍺',
'HAND_WAVE': r'👋',
})
for k, v in emoticons.items():
emoticons[k] = unicode_string_sequence(v)
keymap = [...emoticons.BEER, emoticons.HAND_WAVE...]
```
If you need to send a unicode hex string, use unicode_codepoint_sequence()
```python
from kmk.macros.unicode import unicode_codepoint_sequence
TABLE_FLIP = unicode_codepoint_sequence([
"28", "30ce", "ca0", "75ca","ca0", "29",
"30ce", "5f61", "253b", "2501", "253b",
])
keymap = [...TABLE_FLIP,...]
```

View File

@ -20,9 +20,14 @@ import collections # isort:skip
import kmk.consts # isort:skip
import kmk.kmktime # isort:skip
import kmk.types # isort:skip
import kmk.util # isort:skip
# Now handlers that will be used in keys later
import kmk.handlers.layers
import kmk.handlers.stock
# Now stuff that depends on the above (and so on)
import kmk.keycodes # isort:skip
import kmk.keys # isort:skip
import kmk.matrix # isort:skip
import kmk.hid # isort:skip
@ -41,6 +46,7 @@ import supervisor
from kmk.consts import LeaderMode, UnicodeMode
from kmk.hid import USB_HID
from kmk.internal_state import InternalState
from kmk.keys import KC
from kmk.matrix import MatrixScanner
@ -170,6 +176,16 @@ class Firmware:
self._hid_helper_inst = self.hid_helper()
# Compile string leader sequences
for k, v in self.leader_dictionary.items():
if not isinstance(k, tuple):
new_key = tuple(KC[c] for c in k)
self.leader_dictionary[new_key] = v
for k, v in self.leader_dictionary.items():
if not isinstance(k, tuple):
del self.leader_dictionary[k]
if self.debug_enabled:
print("Firin' lazers. Keyboard is booted.")
@ -202,15 +218,8 @@ class Firmware:
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._state.hid_pending:
self._send_hid()
if self.debug_enabled and state_changed:
print('New State: {}'.format(self._state._to_dict()))

108
kmk/handlers/layers.py Normal file
View File

@ -0,0 +1,108 @@
from kmk.kmktime import ticks_diff, ticks_ms
def df_pressed(key, state, *args, **kwargs):
"""Switches the default layer"""
state.active_layers[0] = key.meta.layer
state.reversed_active_layers = list(reversed(state.active_layers))
return state
def mo_pressed(key, state, *args, **kwargs):
"""Momentarily activates layer, switches off when you let go"""
state.active_layers.append(key.meta.layer)
state.reversed_active_layers = list(reversed(state.active_layers))
return state
def mo_released(key, state, KC, *args, **kwargs):
state.active_layers = [
layer for layer in state.active_layers
if layer != key.meta.layer
]
state.reversed_active_layers = list(reversed(state.active_layers))
return state
def lm_pressed(key, state, *args, **kwargs):
"""As MO(layer) but with mod active"""
state.hid_pending = True
# Sets the timer start and acts like MO otherwise
state.start_time['lm'] = ticks_ms()
state.keys_pressed.add(key.meta.kc)
return mo_pressed(key, state, *args, **kwargs)
def lm_released(key, state, *args, **kwargs):
"""As MO(layer) but with mod active"""
state.hid_pending = True
state.keys_pressed.discard(key.meta.kc)
state.start_time['lm'] = None
return mo_released(key, state, *args, **kwargs)
def lt_pressed(key, state, *args, **kwargs):
# Sets the timer start and acts like MO otherwise
state.start_time['lt'] = ticks_ms()
return mo_pressed(key, state, *args, **kwargs)
def lt_released(key, state, *args, **kwargs):
# On keyup, check timer, and press key if needed.
if state.start_time['lt'] and (
ticks_diff(ticks_ms(), state.start_time['lt']) < state.config.tap_time
):
state.hid_pending = True
state.tap_key(key.meta.kc)
mo_released(key, state, *args, **kwargs)
state.start_time['lt'] = None
return state
def tg_pressed(key, state, *args, **kwargs):
"""Toggles the layer (enables it if not active, and vise versa)"""
if key.meta.layer in state.active_layers:
state.active_layers = [
layer for layer in state.active_layers
if layer != key.meta.layer
]
else:
state.active_layers.append(key.meta.layer)
state.reversed_active_layers = list(reversed(state.active_layers))
return state
def to_pressed(key, state, *args, **kwargs):
"""Activates layer and deactivates all other layers"""
state.active_layers = [key.meta.kc]
state.reversed_active_layers = list(reversed(state.active_layers))
return state
def tt_pressed(key, state, *args, **kwargs):
"""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 state.start_time['tt'] is None:
# Sets the timer start and acts like MO otherwise
state.start_time['tt'] = ticks_ms()
return mo_pressed(key, state, *args, **kwargs)
elif ticks_diff(ticks_ms(), state.start_time['tt']) < state.config.tap_time:
state.start_time['tt'] = None
return tg_pressed(key, state, *args, **kwargs)
def tt_released(key, state, *args, **kwargs):
if (
state.start_time['tt'] is None or
ticks_diff(ticks_ms(), state.start_time['tt']) >= state.config.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
return mo_released(key, state, *args, **kwargs)
return state

152
kmk/handlers/sequences.py Normal file
View File

@ -0,0 +1,152 @@
from kmk.consts import UnicodeMode
from kmk.handlers.stock import passthrough
from kmk.keys import KC, make_key
from kmk.types import AttrDict, KeySequenceMeta
from kmk.util import get_wide_ordinal
def sequence_press_handler(key, state, KC, *args, **kwargs):
old_keys_pressed = state.keys_pressed
state.keys_pressed = set()
for ikey in key.meta.seq:
if not getattr(ikey, 'no_press', None):
state.process_key(ikey, True)
state.config._send_hid()
if not getattr(ikey, 'no_release', None):
state.process_key(ikey, False)
state.config._send_hid()
state.keys_pressed = old_keys_pressed
return state
def simple_key_sequence(seq):
return make_key(
meta=KeySequenceMeta(seq),
on_press=sequence_press_handler,
on_release=passthrough,
)
def send_string(message):
seq = []
for char in message:
kc = KC[char]
if char.isupper():
kc = KC.LSHIFT(kc)
seq.append(kc)
return simple_key_sequence(seq)
IBUS_KEY_COMBO = simple_key_sequence((KC.LCTRL(KC.LSHIFT(KC.U)),))
RALT_KEY = simple_key_sequence((KC.RALT,))
U_KEY = simple_key_sequence((KC.U,))
ENTER_KEY = simple_key_sequence((KC.ENTER,))
RALT_DOWN_NO_RELEASE = simple_key_sequence((KC.RALT(no_release=True),))
RALT_UP_NO_PRESS = simple_key_sequence((KC.RALT(no_press=True),))
def compile_unicode_string_sequences(string_table):
for k, v in string_table.items():
string_table[k] = unicode_string_sequence(v)
return AttrDict(string_table)
def unicode_string_sequence(unistring):
'''
Allows sending things like (°° directly, without
manual conversion to Unicode codepoints.
'''
return unicode_codepoint_sequence([
hex(get_wide_ordinal(s))[2:]
for s in unistring
])
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 = [KC.N0 for _ in range(max(len(codepoint), expected_length))]
for idx, codepoint_fragment in enumerate(reversed(codepoint)):
seq[-(idx + 1)] = KC.get(codepoint_fragment)
return seq
def generate_leader_dictionary_seq(string):
return tuple(generate_codepoint_keysym_seq(string, 1))
def unicode_codepoint_sequence(codepoints):
kc_seqs = (
generate_codepoint_keysym_seq(codepoint)
for codepoint in codepoints
)
kc_macros = [
simple_key_sequence(kc_seq)
for kc_seq in kc_seqs
]
def _unicode_sequence(key, state, *args, **kwargs):
if state.config.unicode_mode == UnicodeMode.IBUS:
state.process_key(
simple_key_sequence(_ibus_unicode_sequence(kc_macros, state)),
True,
)
elif state.config.unicode_mode == UnicodeMode.RALT:
state.process_key(
simple_key_sequence(_ralt_unicode_sequence(kc_macros, state)),
True,
)
elif state.config.unicode_mode == UnicodeMode.WINC:
state.process_key(
simple_key_sequence(_winc_unicode_sequence(kc_macros, state)),
True,
)
return make_key(on_press=_unicode_sequence)
def _ralt_unicode_sequence(kc_macros, state):
for kc_macro in kc_macros:
yield RALT_DOWN_NO_RELEASE
yield kc_macro
yield RALT_UP_NO_PRESS
def _ibus_unicode_sequence(kc_macros, state):
for kc_macro in kc_macros:
yield IBUS_KEY_COMBO
yield kc_macro
yield ENTER_KEY
def _winc_unicode_sequence(kc_macros, state):
'''
Send unicode sequence using WinCompose:
http://wincompose.info/
https://github.com/SamHocevar/wincompose
'''
for kc_macro in kc_macros:
yield RALT_KEY
yield U_KEY
yield kc_macro

97
kmk/handlers/stock.py Normal file
View File

@ -0,0 +1,97 @@
from kmk.kmktime import sleep_ms
from kmk.util import reset_bootloader, reset_keyboard
def passthrough(key, state, *args, **kwargs):
return state
def default_pressed(key, state, KC, coord_int=None, coord_raw=None):
state.hid_pending = True
if coord_int is not None:
state.coord_keys_pressed[coord_int] = key
state.keys_pressed.add(key)
return state
def default_released(key, state, KC, coord_int=None, coord_raw=None):
state.hid_pending = True
state.keys_pressed.discard(key)
if coord_int is not None:
state.keys_pressed.discard(state.coord_keys_pressed.get(coord_int, None))
state.coord_keys_pressed[coord_int] = None
return state
def reset(*args, **kwargs):
reset_keyboard()
def bootloader(*args, **kwargs):
reset_bootloader()
def debug_pressed(key, state, KC, *args, **kwargs):
if state.config.debug_enabled:
print('Disabling debug mode, bye!')
else:
print('Enabling debug mode. Welcome to the jungle.')
state.config.debug_enabled = not state.config.debug_enabled
return state
def gesc_pressed(key, state, KC, *args, **kwargs):
GESC_TRIGGERS = {KC.LSHIFT, KC.RSHIFT, KC.LGUI, KC.RGUI}
if GESC_TRIGGERS.intersection(state.keys_pressed):
# First, release GUI if already pressed
state.keys_pressed.discard(KC.LGUI)
state.keys_pressed.discard(KC.RGUI)
state.config._send_hid()
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level
state.keys_pressed.add(KC.GRAVE)
state.hid_pending = True
return state
# else return KC_ESC
state.keys_pressed.add(KC.ESCAPE)
state.hid_pending = True
return state
def gesc_released(key, state, KC, *args, **kwargs):
state.keys_pressed.discard(KC.ESCAPE)
state.keys_pressed.discard(KC.GRAVE)
state.hid_pending = True
return state
def sleep_pressed(key, state, KC, *args, **kwargs):
sleep_ms(key.meta.ms)
return state
def uc_mode_pressed(key, state, *args, **kwargs):
state.config.unicode_mode = key.meta.mode
return state
def leader_pressed(key, state, *args, **kwargs):
return state._begin_leader_mode()
def td_pressed(key, state, *args, **kwargs):
return state._process_tap_dance(key, True)
def td_released(key, state, *args, **kwargs):
return state._process_tap_dance(key, False)

View File

@ -1,6 +1,5 @@
from kmk.consts import HID_REPORT_SIZES, HIDReportTypes, HIDUsage, HIDUsagePage
from kmk.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, ConsumerKeycode,
ModifierKeycode)
from kmk.keys import FIRST_KMK_INTERNAL_KEY, ConsumerKey, ModifierKey
class USB_HID:
@ -30,7 +29,7 @@ class USB_HID:
consumer_key = None
for key in keys_pressed:
if isinstance(key, ConsumerKeycode):
if isinstance(key, ConsumerKey):
consumer_key = key
break
@ -53,10 +52,10 @@ class USB_HID:
self.add_key(consumer_key)
else:
for key in keys_pressed:
if key.code >= FIRST_KMK_INTERNAL_KEYCODE:
if key.code >= FIRST_KMK_INTERNAL_KEY:
continue
if isinstance(key, ModifierKeycode):
if isinstance(key, ModifierKey):
self.add_modifier(key)
else:
self.add_key(key)
@ -93,8 +92,8 @@ class USB_HID:
return self
def add_modifier(self, modifier):
if isinstance(modifier, ModifierKeycode):
if modifier.code == ModifierKeycode.FAKE_CODE:
if isinstance(modifier, ModifierKey):
if modifier.code == ModifierKey.FAKE_CODE:
for mod in modifier.has_modifiers:
self.report_mods[0] |= mod
else:
@ -105,8 +104,8 @@ class USB_HID:
return self
def remove_modifier(self, modifier):
if isinstance(modifier, ModifierKeycode):
if modifier.code == ModifierKeycode.FAKE_CODE:
if isinstance(modifier, ModifierKey):
if modifier.code == ModifierKey.FAKE_CODE:
for mod in modifier.has_modifiers:
self.report_mods[0] ^= mod
else:

View File

@ -1,19 +1,13 @@
from kmk.consts import LeaderMode
from kmk.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, Keycodes, RawKeycodes,
TapDanceKeycode)
from kmk.kmktime import sleep_ms, ticks_diff, ticks_ms
from kmk.keys import KC
from kmk.kmktime import ticks_ms
from kmk.types import TapDanceKeyMeta
from kmk.util import intify_coordinate
GESC_TRIGGERS = {
Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT,
Keycodes.Modifiers.KC_LGUI, Keycodes.Modifiers.KC_RGUI,
}
class InternalState:
keys_pressed = set()
coord_keys_pressed = {}
macros_pending = []
leader_pending = None
leader_last_len = 0
hid_pending = False
@ -34,22 +28,6 @@ class InternalState:
def __init__(self, config):
self.config = config
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,
Keycodes.KMK.KC_DEBUG.code: self._kc_debug_mode,
RawKeycodes.KC_TAP_DANCE: self._kc_tap_dance,
}
def __repr__(self):
return 'InternalState({})'.format(self._to_dict())
@ -74,7 +52,7 @@ class InternalState:
for layer in self.reversed_active_layers:
layer_key = self.config.keymap[layer][row][col]
if not layer_key or layer_key == Keycodes.KMK.KC_TRNS:
if not layer_key or layer_key == KC.TRNS:
continue
if self.config.debug_enabled:
@ -122,16 +100,16 @@ class InternalState:
print('No key accessible for col, row: {}, {}'.format(row, col))
return self
if self.tapping and not isinstance(kc_changed, TapDanceKeycode):
self._process_tap_dance(kc_changed, is_pressed)
return self.process_key(kc_changed, is_pressed, int_coord, (row, col))
def process_key(self, key, is_pressed, coord_int=None, coord_raw=None):
if self.tapping and not isinstance(key.meta, TapDanceKeyMeta):
self._process_tap_dance(key, is_pressed)
else:
if is_pressed:
self.coord_keys_pressed[int_coord] = kc_changed
self.add_key(kc_changed)
key._on_press(self, coord_int, coord_raw)
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
key._on_release(self, coord_int, coord_raw)
if self.config.leader_mode % 2 == 1:
self._process_leader_mode()
@ -140,33 +118,15 @@ class InternalState:
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
return self
return self.process_key(keycode, False)
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)
if keycode.code >= FIRST_KMK_INTERNAL_KEYCODE:
self._process_internal_key_event(keycode, True)
else:
self.hid_pending = True
return self
self.keys_pressed.add(keycode)
return self.process_key(keycode, True)
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
# On the next cycle, we'll remove the key.
self.set_timeout(False, lambda: self.remove_key(keycode))
return self
@ -175,182 +135,10 @@ class InternalState:
self.hid_pending = False
return self
def resolve_macro(self):
if self.config.debug_enabled:
print('Macro complete!')
self.macros_pending.pop()
return self
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 _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))
return self
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
]
self.reversed_active_layers = list(reversed(self.active_layers))
return self
def _layer_lm(self, changed_key, is_pressed):
"""As MO(layer) but with mod active"""
self.hid_pending = True
if is_pressed:
# Sets the timer start and acts like MO otherwise
self.start_time['lm'] = ticks_ms()
self.keys_pressed.add(changed_key.kc)
else:
self.keys_pressed.discard(changed_key.kc)
self.start_time['lm'] = None
return self._layer_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()
self._layer_mo(changed_key, is_pressed)
else:
# On keyup, check timer, and press key if needed.
if self.start_time['lt'] and (
ticks_diff(ticks_ms(), self.start_time['lt']) < self.config.tap_time
):
self.hid_pending = True
self.tap_key(changed_key.kc)
self._layer_mo(changed_key, is_pressed)
self.start_time['lt'] = None
return self
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)
self.reversed_active_layers = list(reversed(self.active_layers))
return self
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 _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._layer_mo(changed_key, is_pressed)
elif ticks_diff(ticks_ms(), self.start_time['tt']) < self.config.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.config.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._layer_mo(changed_key, is_pressed)
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.macros_pending.append(changed_key.keyup)
else:
if changed_key.keydown:
self.macros_pending.append(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 _kc_debug_mode(self, changed_key, is_pressed):
if is_pressed:
if self.config.debug_enabled:
print('Disabling debug mode, bye!')
else:
print('Enabling debug mode. Welcome to the jungle.')
self.config.debug_enabled = not self.config.debug_enabled
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
if not isinstance(changed_key.meta, TapDanceKeyMeta):
# If we get here, changed_key is not a TapDanceKey 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():
@ -408,7 +196,7 @@ class InternalState:
def _begin_leader_mode(self):
if self.config.leader_mode % 2 == 0:
self.keys_pressed.discard(Keycodes.KMK.KC_LEAD)
self.keys_pressed.discard(KC.LEAD)
# All leader modes are one number higher when activating
self.config.leader_mode += 1
@ -419,11 +207,20 @@ class InternalState:
def _handle_leader_sequence(self):
lmh = tuple(self.leader_mode_history)
# Will get caught in infinite processing loops if we don't
# exit leader mode before processing the target key
self._exit_leader_mode()
if lmh in self.config.leader_dictionary:
self.macros_pending.append(self.config.leader_dictionary[lmh].keydown)
# Stack depth exceeded if try to use add_key here with a unicode sequence
self.process_key(self.config.leader_dictionary[lmh], True)
return self._exit_leader_mode()
self.set_timeout(
False,
lambda: self.remove_key(self.config.leader_dictionary[lmh]),
)
return self
def _process_leader_mode(self):
keys_pressed = self.keys_pressed
@ -438,15 +235,15 @@ class InternalState:
for key in keys_pressed:
if (
self.config.leader_mode == LeaderMode.ENTER_ACTIVE and
key == Keycodes.Common.KC_ENT
key == KC.ENT
):
self._handle_leader_sequence()
break
elif key == Keycodes.Common.KC_ESC or key == Keycodes.KMK.KC_GESC:
elif key == KC.ESC or key == KC.GESC:
# Clean self and turn leader mode off.
self._exit_leader_mode()
break
elif key == Keycodes.KMK.KC_LEAD:
elif key == KC.LEAD:
break
else:
# Add key if not needing to escape

View File

@ -1,692 +0,0 @@
try:
from collections import namedtuple
except ImportError:
# This is handled by micropython-lib/collections, but on local runs of
# MicroPython, it doesn't exist
from ucollections import namedtuple
from kmk.consts import UnicodeMode
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:
'''
These are raw keycode numbers for keys we'll use in generated "keys".
For example, we want to be able to check against these numbers in
the internal_keycodes reducer fragments, but due to a limitation in
MicroPython, we can't simply assign the `.code` attribute to
a function (which is what most internal KMK keys (including layer stuff)
are implemented as). Thus, we have to keep an external lookup table.
'''
LCTRL = 0x01
LSHIFT = 0x02
LALT = 0x04
LGUI = 0x08
RCTRL = 0x10
RSHIFT = 0x20
RALT = 0x40
RGUI = 0x80
KC_DF = 1050
KC_MO = 1051
KC_LM = 1052
KC_LT = 1053
KC_TG = 1054
KC_TO = 1055
KC_TT = 1056
KC_UC_MODE = 1109
KC_MACRO = 1110
KC_MACRO_SLEEP_MS = 1111
KC_TAP_DANCE = 1113
# These shouldn't have all the fancy shenanigans Keycode allows
# such as no_press, because they modify KMK internal state in
# ways we need to tightly control. Thus, we can get away with
# a lighter-weight namedtuple implementation here
LayerKeycode = namedtuple('LayerKeycode', ('code', 'layer', 'kc'))
MacroSleepKeycode = namedtuple('MacroSleepKeycode', ('code', 'ms'))
class UnicodeModeKeycode(namedtuple('UnicodeModeKeycode', ('code', 'mode'))):
@staticmethod
def from_mode_const(mode):
return UnicodeModeKeycode(RawKeycodes.KC_UC_MODE, mode)
class Keycode:
def __init__(self, code, has_modifiers=None, no_press=False, no_release=False):
self.code = code
self.has_modifiers = has_modifiers
# cast to bool() in case we get a None value
self.no_press = bool(no_press)
self.no_release = bool(no_press)
def __call__(self, no_press=None, no_release=None):
if no_press is None and no_release is None:
return self
return Keycode(
code=self.code,
has_modifiers=self.has_modifiers,
no_press=no_press,
no_release=no_release,
)
def __repr__(self):
return 'Keycode(code={}, has_modifiers={})'.format(self.code, self.has_modifiers)
class ModifierKeycode(Keycode):
FAKE_CODE = -1
def __call__(self, modified_code=None, no_press=None, no_release=None):
if modified_code is None and no_press is None and no_release is None:
return self
if modified_code is not None:
if isinstance(modified_code, ModifierKeycode):
new_keycode = ModifierKeycode(
ModifierKeycode.FAKE_CODE,
set() if self.has_modifiers is None else self.has_modifiers,
no_press=no_press,
no_release=no_release,
)
if self.code != ModifierKeycode.FAKE_CODE:
new_keycode.has_modifiers.add(self.code)
if modified_code.code != ModifierKeycode.FAKE_CODE:
new_keycode.has_modifiers.add(modified_code.code)
else:
new_keycode = Keycode(
modified_code.code,
{self.code},
no_press=no_press,
no_release=no_release,
)
if modified_code.has_modifiers:
new_keycode.has_modifiers |= modified_code.has_modifiers
else:
new_keycode = Keycode(
self.code,
no_press=no_press,
no_release=no_release,
)
return new_keycode
def __repr__(self):
return 'ModifierKeycode(code={}, has_modifiers={})'.format(self.code, self.has_modifiers)
class ConsumerKeycode(Keycode):
pass
class Macro:
'''
A special "key" which triggers a macro.
'''
code = RawKeycodes.KC_MACRO
def __init__(self, keydown=None, keyup=None):
self.keydown = keydown
self.keyup = keyup
def on_keydown(self):
return self.keydown() if self.keydown else None
def on_keyup(self):
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):
'''
MicroPython, for whatever reason (probably performance/memory) makes
__dict__ optional for ports. Unfortunately, at least the STM32
(Pyboard) port is one such port. This reimplements a subset of
__dict__, limited to just keys we're likely to care about (though this
could be opened up further later).
'''
hidden = ('to_dict', 'recursive_dict', 'contains')
return AttrDict({
key: getattr(cls, key)
for key in dir(cls)
if not key.startswith('_') and key not in hidden
})
@classmethod
def recursive_dict(cls):
'''
to_dict() executed recursively all the way down a tree
'''
ret = cls.to_dict()
for key, val in ret.items():
try:
nested_ret = val.recursive_dict()
except (AttributeError, NameError):
continue
ret[key] = nested_ret
return ret
@classmethod
def contains(cls, kc):
'''
Emulates the 'in' operator for keycode groupings, given MicroPython's
lack of support for metaclasses (meaning implementing 'in' for
uninstantiated classes, such as these, is largely not possible). Not
super useful in most cases, but does allow for sanity checks like
```python
assert Keycodes.Modifiers.contains(requested_key)
```
This is not bulletproof due to how HID codes are defined (there is
overlap). Keycodes.Common.KC_A, for example, is equal in value to
Keycodes.Modifiers.KC_LALT, but it can still prevent silly mistakes
like trying to use, say, Keycodes.Common.KC_Q as a modifier.
This is recursive across subgroups, enabling stuff like:
```python
assert Keycodes.contains(requested_key)
```
To ensure that a valid keycode has been requested to begin with. Again,
not bulletproof, but adds at least some cushion to stuff that would
otherwise cause AttributeErrors and crash the keyboard.
'''
subcategories = (
category for category in cls.to_dict().values()
# Disgusting, but since `cls.__bases__` isn't implemented in MicroPython,
# I resort to a less foolproof inheritance check that should still ignore
# strings and other stupid stuff (we don't want to iterate over __doc__,
# for example), but include nested classes.
#
# One huge lesson in this project is that uninstantiated classes are hard...
# and four times harder when the implementation of Python is half-baked.
if isinstance(category, type)
)
if any(
kc == _kc
for name, _kc in cls.to_dict().items()
if name.startswith('KC_')
):
return True
return any(sc.contains(kc) for sc in subcategories)
class Modifiers(KeycodeCategory):
KC_LCTRL = KC_LCTL = ModifierKeycode(RawKeycodes.LCTRL)
KC_LSHIFT = KC_LSFT = ModifierKeycode(RawKeycodes.LSHIFT)
KC_LALT = ModifierKeycode(RawKeycodes.LALT)
KC_LGUI = KC_LCMD = KC_LWIN = ModifierKeycode(RawKeycodes.LGUI)
KC_RCTRL = KC_RCTL = ModifierKeycode(RawKeycodes.RCTRL)
KC_RSHIFT = KC_RSFT = ModifierKeycode(RawKeycodes.RSHIFT)
KC_RALT = ModifierKeycode(RawKeycodes.RALT)
KC_RGUI = KC_RCMD = KC_RWIN = ModifierKeycode(RawKeycodes.RGUI)
KC_MEH = KC_LSHIFT(KC_LALT(KC_LCTRL))
KC_HYPR = KC_HYPER = KC_MEH(KC_LGUI)
class Common(KeycodeCategory):
KC_A = Keycode(4)
KC_B = Keycode(5)
KC_C = Keycode(6)
KC_D = Keycode(7)
KC_E = Keycode(8)
KC_F = Keycode(9)
KC_G = Keycode(10)
KC_H = Keycode(11)
KC_I = Keycode(12)
KC_J = Keycode(13)
KC_K = Keycode(14)
KC_L = Keycode(15)
KC_M = Keycode(16)
KC_N = Keycode(17)
KC_O = Keycode(18)
KC_P = Keycode(19)
KC_Q = Keycode(20)
KC_R = Keycode(21)
KC_S = Keycode(22)
KC_T = Keycode(23)
KC_U = Keycode(24)
KC_V = Keycode(25)
KC_W = Keycode(26)
KC_X = Keycode(27)
KC_Y = Keycode(28)
KC_Z = Keycode(29)
# Aliases to play nicely with AttrDict, since KC.1 isn't a valid
# attribute key in Python, but KC.N1 is
KC_1 = KC_N1 = Keycode(30)
KC_2 = KC_N2 = Keycode(31)
KC_3 = KC_N3 = Keycode(32)
KC_4 = KC_N4 = Keycode(33)
KC_5 = KC_N5 = Keycode(34)
KC_6 = KC_N6 = Keycode(35)
KC_7 = KC_N7 = Keycode(36)
KC_8 = KC_N8 = Keycode(37)
KC_9 = KC_N9 = Keycode(38)
KC_0 = KC_N0 = Keycode(39)
KC_ENTER = KC_ENT = Keycode(40)
KC_ESCAPE = KC_ESC = Keycode(41)
KC_BACKSPACE = KC_BSPC = KC_BKSP = Keycode(42)
KC_TAB = Keycode(43)
KC_SPACE = KC_SPC = Keycode(44)
KC_MINUS = KC_MINS = Keycode(45)
KC_EQUAL = KC_EQL = Keycode(46)
KC_LBRACKET = KC_LBRC = Keycode(47)
KC_RBRACKET = KC_RBRC = Keycode(48)
KC_BACKSLASH = KC_BSLASH = KC_BSLS = Keycode(49)
KC_NONUS_HASH = KC_NUHS = Keycode(50)
KC_NONUS_BSLASH = KC_NUBS = Keycode(100)
KC_SEMICOLON = KC_SCOLON = KC_SCLN = Keycode(51)
KC_QUOTE = KC_QUOT = Keycode(52)
KC_GRAVE = KC_GRV = KC_ZKHK = Keycode(53)
KC_COMMA = KC_COMM = Keycode(54)
KC_DOT = Keycode(55)
KC_SLASH = KC_SLSH = Keycode(56)
class ShiftedKeycodes(KeycodeCategory):
KC_TILDE = KC_TILD = Modifiers.KC_LSHIFT(Common.KC_GRAVE)
KC_EXCLAIM = KC_EXLM = Modifiers.KC_LSHIFT(Common.KC_1)
KC_AT = Modifiers.KC_LSHIFT(Common.KC_2)
KC_HASH = Modifiers.KC_LSHIFT(Common.KC_3)
KC_DOLLAR = KC_DLR = Modifiers.KC_LSHIFT(Common.KC_4)
KC_PERCENT = KC_PERC = Modifiers.KC_LSHIFT(Common.KC_5)
KC_CIRCUMFLEX = KC_CIRC = Modifiers.KC_LSHIFT(Common.KC_6) # The ^ Symbol
KC_AMPERSAND = KC_AMPR = Modifiers.KC_LSHIFT(Common.KC_7)
KC_ASTERISK = KC_ASTR = Modifiers.KC_LSHIFT(Common.KC_8)
KC_LEFT_PAREN = KC_LPRN = Modifiers.KC_LSHIFT(Common.KC_9)
KC_RIGHT_PAREN = KC_RPRN = Modifiers.KC_LSHIFT(Common.KC_0)
KC_UNDERSCORE = KC_UNDS = Modifiers.KC_LSHIFT(Common.KC_MINUS)
KC_PLUS = Modifiers.KC_LSHIFT(Common.KC_EQUAL)
KC_LEFT_CURLY_BRACE = KC_LCBR = Modifiers.KC_LSHIFT(Common.KC_LBRACKET)
KC_RIGHT_CURLY_BRACE = KC_RCBR = Modifiers.KC_LSHIFT(Common.KC_RBRACKET)
KC_PIPE = Modifiers.KC_LSHIFT(Common.KC_BACKSLASH)
KC_COLON = KC_COLN = Modifiers.KC_LSHIFT(Common.KC_SEMICOLON)
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_SLSH)
class FunctionKeys(KeycodeCategory):
KC_F1 = Keycode(58)
KC_F2 = Keycode(59)
KC_F3 = Keycode(60)
KC_F4 = Keycode(61)
KC_F5 = Keycode(62)
KC_F6 = Keycode(63)
KC_F7 = Keycode(64)
KC_F8 = Keycode(65)
KC_F9 = Keycode(66)
KC_F10 = Keycode(67)
KC_F11 = Keycode(68)
KC_F12 = Keycode(69)
KC_F13 = Keycode(104)
KC_F14 = Keycode(105)
KC_F15 = Keycode(106)
KC_F16 = Keycode(107)
KC_F17 = Keycode(108)
KC_F18 = Keycode(109)
KC_F19 = Keycode(110)
KC_F20 = Keycode(111)
KC_F21 = Keycode(112)
KC_F22 = Keycode(113)
KC_F23 = Keycode(114)
KC_F24 = Keycode(115)
class NavAndLocks(KeycodeCategory):
KC_CAPS_LOCK = KC_CLCK = KC_CAPS = Keycode(57)
KC_LOCKING_CAPS = KC_LCAP = Keycode(130)
KC_PSCREEN = KC_PSCR = Keycode(70)
KC_SCROLLLOCK = KC_SLCK = Keycode(71)
KC_LOCKING_SCROLL = KC_LSCRL = Keycode(132)
KC_PAUSE = KC_PAUS = KC_BRK = Keycode(72)
KC_INSERT = KC_INS = Keycode(73)
KC_HOME = Keycode(74)
KC_PGUP = Keycode(75)
KC_DELETE = KC_DEL = Keycode(76)
KC_END = Keycode(77)
KC_PGDOWN = KC_PGDN = Keycode(78)
KC_RIGHT = KC_RGHT = Keycode(79)
KC_LEFT = Keycode(80)
KC_DOWN = Keycode(81)
KC_UP = Keycode(82)
class Numpad(KeycodeCategory):
KC_NUMLOCK = KC_NLCK = Keycode(83)
KC_LOCKING_NUM = KC_LNUM = Keycode(131)
KC_KP_SLASH = KC_PSLS = Keycode(84)
KC_KP_ASTERIK = KC_PAST = Keycode(85)
KC_KP_MINUS = KC_PMNS = Keycode(86)
KC_KP_PLUS = KC_PPLS = Keycode(87)
KC_KP_ENTER = KC_PENT = Keycode(88)
KC_KP_1 = KC_P1 = Keycode(89)
KC_KP_2 = KC_P2 = Keycode(90)
KC_KP_3 = KC_P3 = Keycode(91)
KC_KP_4 = KC_P4 = Keycode(92)
KC_KP_5 = KC_P5 = Keycode(93)
KC_KP_6 = KC_P6 = Keycode(94)
KC_KP_7 = KC_P7 = Keycode(95)
KC_KP_8 = KC_P8 = Keycode(96)
KC_KP_9 = KC_P9 = Keycode(97)
KC_KP_0 = KC_P0 = Keycode(98)
KC_KP_DOT = KC_PDOT = Keycode(99)
KC_KP_EQUAL = KC_PEQL = Keycode(103)
KC_KP_COMMA = KC_PCMM = Keycode(133)
KC_KP_EQUAL_AS400 = Keycode(134)
class International(KeycodeCategory):
KC_INT1 = KC_RO = Keycode(135)
KC_INT2 = KC_KANA = Keycode(136)
KC_INT3 = KC_JYEN = Keycode(137)
KC_INT4 = KC_HENK = Keycode(138)
KC_INT5 = KC_MHEN = Keycode(139)
KC_INT6 = Keycode(140)
KC_INT7 = Keycode(141)
KC_INT8 = Keycode(142)
KC_INT9 = Keycode(143)
KC_LANG1 = KC_HAEN = Keycode(144)
KC_LANG2 = KC_HAEJ = Keycode(145)
KC_LANG3 = Keycode(146)
KC_LANG4 = Keycode(147)
KC_LANG5 = Keycode(148)
KC_LANG6 = Keycode(149)
KC_LANG7 = Keycode(150)
KC_LANG8 = Keycode(151)
KC_LANG9 = Keycode(152)
class Misc(KeycodeCategory):
KC_APPLICATION = KC_APP = ConsumerKeycode(101)
KC_POWER = ConsumerKeycode(102)
KC_EXECUTE = KC_EXEC = ConsumerKeycode(116)
KC_SYSTEM_POWER = KC_PWR = ConsumerKeycode(165)
KC_SYSTEM_SLEEP = KC_SLEP = ConsumerKeycode(166)
KC_SYSTEM_WAKE = KC_WAKE = ConsumerKeycode(167)
KC_HELP = ConsumerKeycode(117)
KC_MENU = ConsumerKeycode(118)
KC_SELECT = KC_SLCT = ConsumerKeycode(119)
KC_STOP = ConsumerKeycode(120)
KC_AGAIN = KC_AGIN = ConsumerKeycode(121)
KC_UNDO = ConsumerKeycode(122)
KC_CUT = ConsumerKeycode(123)
KC_COPY = ConsumerKeycode(124)
KC_PASTE = KC_PSTE = ConsumerKeycode(125)
KC_FIND = ConsumerKeycode(126)
KC_ALT_ERASE = KC_ERAS = ConsumerKeycode(153)
KC_SYSREQ = ConsumerKeycode(154)
KC_CANCEL = ConsumerKeycode(155)
KC_CLEAR = KC_CLR = ConsumerKeycode(156)
KC_PRIOR = ConsumerKeycode(157)
KC_RETURN = ConsumerKeycode(158)
KC_SEPERATOR = ConsumerKeycode(159)
KC_OUT = ConsumerKeycode(160)
KC_OPER = ConsumerKeycode(161)
KC_CLEAR_AGAIN = ConsumerKeycode(162)
KC_CRSEL = ConsumerKeycode(163)
KC_EXSEL = ConsumerKeycode(164)
KC_MAIL = ConsumerKeycode(177)
KC_CALCULATOR = KC_CALC = ConsumerKeycode(178)
KC_MY_COMPUTER = KC_MYCM = ConsumerKeycode(179)
KC_WWW_SEARCH = KC_WSCH = ConsumerKeycode(180)
KC_WWW_HOME = KC_WHOM = ConsumerKeycode(181)
KC_WWW_BACK = KC_WBAK = ConsumerKeycode(182)
KC_WWW_FORWARD = KC_WFWD = ConsumerKeycode(183)
KC_WWW_STOP = KC_WSTP = ConsumerKeycode(184)
KC_WWW_REFRESH = KC_WREF = ConsumerKeycode(185)
KC_WWW_FAVORITES = KC_WFAV = ConsumerKeycode(186)
class Media(KeycodeCategory):
# I believe QMK used these double-underscore codes for MacOS
# support or something. I have no idea, but modern MacOS supports
# PC volume keys so I really don't care that these codes are the
# same as below. If bugs arise, these codes may need to change.
KC__MUTE = ConsumerKeycode(226)
KC__VOLUP = ConsumerKeycode(233)
KC__VOLDOWN = ConsumerKeycode(234)
KC_AUDIO_MUTE = KC_MUTE = ConsumerKeycode(226) # 0xE2
KC_AUDIO_VOL_UP = KC_VOLU = ConsumerKeycode(233) # 0xE9
KC_AUDIO_VOL_DOWN = KC_VOLD = ConsumerKeycode(234) # 0xEA
KC_MEDIA_NEXT_TRACK = KC_MNXT = ConsumerKeycode(181) # 0xB5
KC_MEDIA_PREV_TRACK = KC_MPRV = ConsumerKeycode(182) # 0xB6
KC_MEDIA_STOP = KC_MSTP = ConsumerKeycode(183) # 0xB7
KC_MEDIA_PLAY_PAUSE = KC_MPLY = ConsumerKeycode(205) # 0xCD (this may not be right)
KC_MEDIA_EJECT = KC_EJCT = ConsumerKeycode(184) # 0xB8
KC_MEDIA_FAST_FORWARD = KC_MFFD = ConsumerKeycode(179) # 0xB3
KC_MEDIA_REWIND = KC_MRWD = ConsumerKeycode(180) # 0xB4
class KMK(KeycodeCategory):
KC_RESET = Keycode(1000)
KC_DEBUG = Keycode(1001)
KC_GESC = Keycode(1002)
KC_LSPO = Keycode(1003)
KC_RSPC = Keycode(1004)
KC_LEAD = Keycode(1005)
KC_LOCK = Keycode(1006)
KC_NO = Keycode(1107)
KC_TRANSPARENT = KC_TRNS = Keycode(1108)
KC_DEBUG = KC_DBG = Keycode(1112)
@staticmethod
def KC_UC_MODE(mode):
'''
Set any Unicode Mode at runtime (allows the same keymap's unicode
sequences to work across all supported platforms)
'''
return UnicodeModeKeycode.from_mode_const(mode)
KC_UC_MODE_NOOP = KC_UC_DISABLE = UnicodeModeKeycode.from_mode_const(UnicodeMode.NOOP)
KC_UC_MODE_LINUX = KC_UC_MODE_IBUS = UnicodeModeKeycode.from_mode_const(UnicodeMode.IBUS)
KC_UC_MODE_MACOS = KC_UC_MODE_OSX = KC_UC_MODE_RALT = UnicodeModeKeycode.from_mode_const(
UnicodeMode.RALT,
)
KC_UC_MODE_WINC = UnicodeModeKeycode.from_mode_const(UnicodeMode.WINC)
@staticmethod
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
def KC_DF(layer):
return LayerKeycode(RawKeycodes.KC_DF, layer, KC.NO)
@staticmethod
def KC_MO(layer):
return LayerKeycode(RawKeycodes.KC_MO, layer, KC.NO)
@staticmethod
def KC_LM(layer, kc):
return LayerKeycode(RawKeycodes.KC_LM, layer, kc)
@staticmethod
def KC_LT(layer, kc):
return LayerKeycode(RawKeycodes.KC_LT, layer, kc)
@staticmethod
def KC_TG(layer):
return LayerKeycode(RawKeycodes.KC_TG, layer, KC.NO)
@staticmethod
def KC_TO(layer):
return LayerKeycode(RawKeycodes.KC_TO, layer, KC.NO)
@staticmethod
def KC_TT(layer):
return LayerKeycode(RawKeycodes.KC_TT, layer, KC.NO)
class Keycodes(KeycodeCategory):
'''
A massive grouping of keycodes
Some of these are from http://www.freebsddiary.org/APC/usb_hid_usages.php,
one of the most useful pages on the interwebs for HID stuff, apparently.
'''
_groupings = [
'Modifiers',
'Common',
'ShiftedKeycodes',
'FunctionKeys',
'NavAndLocks',
'Numpad',
'International',
'Misc',
'Media',
'KMK',
'Layers',
]
Modifiers = Modifiers
Common = Common
ShiftedKeycodes = ShiftedKeycodes
FunctionKeys = FunctionKeys
NavAndLocks = NavAndLocks
Numpad = Numpad
International = International
Misc = Misc
Media = Media
KMK = KMK
Layers = Layers
class LazyKC:
def __init__(self):
self.cache = {}
def __getattr__(self, attr):
if attr in self.cache:
return self.cache[attr]
for grouping in Keycodes._groupings:
grouping_cls = getattr(Keycodes, grouping)
try:
found = getattr(grouping_cls, 'KC_{}'.format(attr))
self.cache[attr] = found
return found
except AttributeError:
continue
raise AttributeError(attr)
KC = LazyKC()
char_lookup = {
"\n": Common.KC_ENTER,
"\t": Common.KC_TAB,
' ': Common.KC_SPACE,
'-': Common.KC_MINUS,
'=': Common.KC_EQUAL,
'[': Common.KC_LBRACKET,
']': Common.KC_RBRACKET,
"\\": Common.KC_BACKSLASH,
';': Common.KC_SEMICOLON,
"'": Common.KC_QUOTE,
'`': Common.KC_GRAVE,
',': Common.KC_COMMA,
'.': Common.KC_DOT,
'~': ShiftedKeycodes.KC_TILDE,
'!': ShiftedKeycodes.KC_EXCLAIM,
'@': ShiftedKeycodes.KC_AT,
'#': ShiftedKeycodes.KC_HASH,
'$': ShiftedKeycodes.KC_DOLLAR,
'%': ShiftedKeycodes.KC_PERCENT,
'^': ShiftedKeycodes.KC_CIRCUMFLEX,
'&': ShiftedKeycodes.KC_AMPERSAND,
'*': ShiftedKeycodes.KC_ASTERISK,
'(': ShiftedKeycodes.KC_LEFT_PAREN,
')': ShiftedKeycodes.KC_RIGHT_PAREN,
'_': ShiftedKeycodes.KC_UNDERSCORE,
'+': ShiftedKeycodes.KC_PLUS,
'{': ShiftedKeycodes.KC_LEFT_CURLY_BRACE,
'}': ShiftedKeycodes.KC_RIGHT_CURLY_BRACE,
'|': ShiftedKeycodes.KC_PIPE,
':': ShiftedKeycodes.KC_COLON,
'"': ShiftedKeycodes.KC_DOUBLE_QUOTE,
'<': ShiftedKeycodes.KC_LEFT_ANGLE_BRACKET,
'>': ShiftedKeycodes.KC_RIGHT_ANGLE_BRACKET,
'?': ShiftedKeycodes.KC_QUESTION,
}

737
kmk/keys.py Normal file
View File

@ -0,0 +1,737 @@
import gc
import kmk.handlers.layers as layers
import kmk.handlers.stock as handlers
from kmk.consts import UnicodeMode
from kmk.types import (AttrDict, KeySeqSleepMeta, LayerKeyMeta,
TapDanceKeyMeta, UnicodeModeKeyMeta)
FIRST_KMK_INTERNAL_KEY = 1000
NEXT_AVAILABLE_KEY = 1000
KEY_SIMPLE = 0
KEY_MODIFIER = 1
KEY_CONSUMER = 2
# Global state, will be filled in througout this file, and
# anywhere the user creates custom keys
KC = AttrDict()
class Key:
def __init__(
self,
code,
has_modifiers=None,
no_press=False,
no_release=False,
on_press=handlers.default_pressed,
on_release=handlers.default_released,
meta=object(),
):
self.code = code
self.has_modifiers = has_modifiers
# cast to bool() in case we get a None value
self.no_press = bool(no_press)
self.no_release = bool(no_press)
self._pre_press_handlers = []
self._post_press_handlers = []
self._pre_release_handlers = []
self._post_release_handlers = []
self._handle_press = on_press
self._handle_release = on_release
self.meta = meta
def __call__(self, no_press=None, no_release=None):
if no_press is None and no_release is None:
return self
return Key(
code=self.code,
has_modifiers=self.has_modifiers,
no_press=no_press,
no_release=no_release,
)
def __repr__(self):
return 'Key(code={}, has_modifiers={})'.format(self.code, self.has_modifiers)
def _on_press(self, state, coord_int, coord_raw):
for fn in self._pre_press_handlers:
fn(self, state, KC, coord_int, coord_raw)
ret = self._handle_press(self, state, KC, coord_int, coord_raw)
for fn in self._post_press_handlers:
fn(self, state, KC, coord_int, coord_raw)
return ret
def _on_release(self, state, coord_int, coord_raw):
for fn in self._pre_release_handlers:
fn(self, state, KC, coord_int, coord_raw)
ret = self._handle_release(self, state, KC, coord_int, coord_raw)
for fn in self._post_release_handlers:
fn(self, state, KC, coord_int, coord_raw)
return ret
def clone(self):
'''
Return a shallow clone of the current key without any pre/post press/release
handlers attached. Almost exclusively useful for creating non-colliding keys
to use such handlers.
'''
return type(self)(
code=self.code,
has_modifiers=self.has_modifiers,
no_press=self.no_press,
no_release=self.no_release,
on_press=self._handle_press,
on_release=self._handle_release,
meta=self.meta,
)
def before_press_handler(self, fn):
'''
Attach a callback to be run prior to the on_press handler for this key.
Receives the following:
- self (this Key instance)
- state (the current InternalState)
- KC (the global KC lookup table, for convenience)
- coord_int (an internal integer representation of the matrix coordinate
for the pressed key - this is likely not useful to end users, but is
provided for consistency with the internal handlers)
- coord_raw (an X,Y tuple of the matrix coordinate - also likely not useful)
The return value of the provided callback is discarded. Exceptions are _not_
caught, and will likely crash KMK if not handled within your function.
These handlers are run in attachment order: handlers provided by earlier
calls of this method will be executed before those provided by later calls.
'''
self._pre_press_handlers.append(fn)
return self
def after_press_handler(self, fn):
'''
Attach a callback to be run after the on_release handler for this key.
Receives the following:
- self (this Key instance)
- state (the current InternalState)
- KC (the global KC lookup table, for convenience)
- coord_int (an internal integer representation of the matrix coordinate
for the pressed key - this is likely not useful to end users, but is
provided for consistency with the internal handlers)
- coord_raw (an X,Y tuple of the matrix coordinate - also likely not useful)
The return value of the provided callback is discarded. Exceptions are _not_
caught, and will likely crash KMK if not handled within your function.
These handlers are run in attachment order: handlers provided by earlier
calls of this method will be executed before those provided by later calls.
'''
self._post_press_handlers.append(fn)
return self
def before_release_handler(self, fn):
'''
Attach a callback to be run prior to the on_release handler for this
key. Receives the following:
- self (this Key instance)
- state (the current InternalState)
- KC (the global KC lookup table, for convenience)
- coord_int (an internal integer representation of the matrix coordinate
for the pressed key - this is likely not useful to end users, but is
provided for consistency with the internal handlers)
- coord_raw (an X,Y tuple of the matrix coordinate - also likely not useful)
The return value of the provided callback is discarded. Exceptions are _not_
caught, and will likely crash KMK if not handled within your function.
These handlers are run in attachment order: handlers provided by earlier
calls of this method will be executed before those provided by later calls.
'''
self._pre_release_handlers.append(fn)
return self
def after_release_handler(self, fn):
'''
Attach a callback to be run after the on_release handler for this key.
Receives the following:
- self (this Key instance)
- state (the current InternalState)
- KC (the global KC lookup table, for convenience)
- coord_int (an internal integer representation of the matrix coordinate
for the pressed key - this is likely not useful to end users, but is
provided for consistency with the internal handlers)
- coord_raw (an X,Y tuple of the matrix coordinate - also likely not useful)
The return value of the provided callback is discarded. Exceptions are _not_
caught, and will likely crash KMK if not handled within your function.
These handlers are run in attachment order: handlers provided by earlier
calls of this method will be executed before those provided by later calls.
'''
self._post_release_handlers.append(fn)
return self
class ModifierKey(Key):
# FIXME this is atrocious to read. Please, please, please, strike down upon
# this with great vengeance and furious anger.
FAKE_CODE = -1
def __call__(self, modified_code=None, no_press=None, no_release=None):
if modified_code is None and no_press is None and no_release is None:
return self
if modified_code is not None:
if isinstance(modified_code, ModifierKey):
new_keycode = ModifierKey(
ModifierKey.FAKE_CODE,
set() if self.has_modifiers is None else self.has_modifiers,
no_press=no_press,
no_release=no_release,
)
if self.code != ModifierKey.FAKE_CODE:
new_keycode.has_modifiers.add(self.code)
if modified_code.code != ModifierKey.FAKE_CODE:
new_keycode.has_modifiers.add(modified_code.code)
else:
new_keycode = Key(
modified_code.code,
{self.code},
no_press=no_press,
no_release=no_release,
)
if modified_code.has_modifiers:
new_keycode.has_modifiers |= modified_code.has_modifiers
else:
new_keycode = Key(
self.code,
no_press=no_press,
no_release=no_release,
)
return new_keycode
def __repr__(self):
return 'ModifierKey(code={}, has_modifiers={})'.format(self.code, self.has_modifiers)
class ConsumerKey(Key):
pass
def register_key_names(key, names=tuple()): # NOQA
'''
Names are globally unique. If a later key is created with
the same name as an existing entry in `KC`, it will overwrite
the existing entry.
If a name entry is only a single letter, its entry in the KC
object will not be case-sensitive (meaning `names=('A',)` is
sufficient to create a key accessible by both `KC.A` and `KC.a`).
'''
for name in names:
KC[name] = key
if len(name) == 1:
KC[name.upper()] = key
KC[name.lower()] = key
return key
def make_key(
code=None,
names=tuple(), # NOQA
type=KEY_SIMPLE,
**kwargs,
):
'''
Create a new key, aliased by `names` in the KC lookup table.
If a code is not specified, the key is assumed to be a custom
internal key to be handled in a state callback rather than
sent directly to the OS. These codes will autoincrement.
See register_key_names() for details on the assignment.
All **kwargs are passed to the Key constructor
'''
global NEXT_AVAILABLE_KEY
if type == KEY_SIMPLE:
constructor = Key
elif type == KEY_MODIFIER:
constructor = ModifierKey
elif type == KEY_CONSUMER:
constructor = ConsumerKey
else:
raise ValueError('Unrecognized key type')
if code is None:
code = NEXT_AVAILABLE_KEY
NEXT_AVAILABLE_KEY += 1
elif code >= FIRST_KMK_INTERNAL_KEY:
# Try to ensure future auto-generated internal keycodes won't
# be overridden by continuing to +1 the sequence from the provided
# code
NEXT_AVAILABLE_KEY = max(NEXT_AVAILABLE_KEY, code + 1)
key = constructor(code=code, **kwargs)
register_key_names(key, names)
return key
def make_mod_key(*args, **kwargs):
return make_key(*args, **kwargs, type=KEY_MODIFIER)
def make_shifted_key(target_name, names=tuple()): # NOQA
key = KC.LSFT(KC[target_name])
register_key_names(key, names)
return key
def make_consumer_key(*args, **kwargs):
return make_key(*args, **kwargs, type=KEY_CONSUMER)
# Argumented keys are implicitly internal, so auto-gen of code
# is almost certainly the best plan here
def make_argumented_key(
validator=lambda *validator_args, **validator_kwargs: object(),
names=tuple(), # NOQA
*constructor_args,
**constructor_kwargs,
):
global NEXT_AVAILABLE_KEY
def _argumented_key(*user_args, **user_kwargs):
global NEXT_AVAILABLE_KEY
meta = validator(*user_args, **user_kwargs)
if meta:
key = Key(
NEXT_AVAILABLE_KEY,
meta=meta,
*constructor_args,
**constructor_kwargs,
)
NEXT_AVAILABLE_KEY += 1
return key
else:
raise ValueError(
'Argumented key validator failed for unknown reasons. '
'This may not be the keymap\'s fault, as a more specific error '
'should have been raised.',
)
for name in names:
KC[name] = _argumented_key
return _argumented_key
gc.collect()
# Modifiers
make_mod_key(code=0x01, names=('LEFT_CONTROL', 'LCTRL', 'LCTL'))
make_mod_key(code=0x02, names=('LEFT_SHIFT', 'LSHIFT', 'LSFT'))
make_mod_key(code=0x04, names=('LEFT_ALT', 'LALT'))
make_mod_key(code=0x08, names=('LEFT_SUPER', 'LGUI', 'LCMD', 'LWIN'))
make_mod_key(code=0x10, names=('RIGHT_CONTROL', 'RCTRL', 'RCTL'))
make_mod_key(code=0x20, names=('RIGHT_SHIFT', 'RSHIFT', 'RSFT'))
make_mod_key(code=0x40, names=('RIGHT_ALT', 'RALT'))
make_mod_key(code=0x80, names=('RIGHT_SUPER', 'RGUI', 'RCMD', 'RWIN'))
# MEH = LCTL | LALT | LSFT
make_mod_key(code=0x07, names=('MEH',))
# HYPR = LCTL | LALT | LSFT | LGUI
make_mod_key(code=0x0F, names=('HYPER', 'HYPR'))
gc.collect()
# Basic ASCII letters
make_key(code=4, names=('A',))
make_key(code=5, names=('B',))
make_key(code=6, names=('C',))
make_key(code=7, names=('D',))
make_key(code=8, names=('E',))
make_key(code=9, names=('F',))
make_key(code=10, names=('G',))
make_key(code=11, names=('H',))
make_key(code=12, names=('I',))
make_key(code=13, names=('J',))
make_key(code=14, names=('K',))
make_key(code=15, names=('L',))
make_key(code=16, names=('M',))
make_key(code=17, names=('N',))
make_key(code=18, names=('O',))
make_key(code=19, names=('P',))
make_key(code=20, names=('Q',))
make_key(code=21, names=('R',))
make_key(code=22, names=('S',))
make_key(code=23, names=('T',))
make_key(code=24, names=('U',))
make_key(code=25, names=('V',))
make_key(code=26, names=('W',))
make_key(code=27, names=('X',))
make_key(code=28, names=('Y',))
make_key(code=29, names=('Z',))
gc.collect()
# Numbers
# Aliases to play nicely with AttrDict, since KC.1 isn't a valid
# attribute key in Python, but KC.N1 is
make_key(code=30, names=('1', 'N1'))
make_key(code=31, names=('2', 'N2'))
make_key(code=32, names=('3', 'N3'))
make_key(code=33, names=('4', 'N4'))
make_key(code=34, names=('5', 'N5'))
make_key(code=35, names=('6', 'N6'))
make_key(code=36, names=('7', 'N7'))
make_key(code=37, names=('8', 'N8'))
make_key(code=38, names=('9', 'N9'))
make_key(code=39, names=('0', 'N0'))
gc.collect()
# More ASCII standard keys
make_key(code=40, names=('ENTER', 'ENT', "\n"))
make_key(code=41, names=('ESCAPE', 'ESC'))
make_key(code=42, names=('BACKSPACE', 'BSPC', 'BKSP'))
make_key(code=43, names=('TAB', "\t"))
make_key(code=44, names=('SPACE', 'SPC', ' '))
make_key(code=45, names=('MINUS', 'MINS', '-'))
make_key(code=46, names=('EQUAL', 'EQL', '='))
make_key(code=47, names=('LBRACKET', 'LBRC', '['))
make_key(code=48, names=('RBRACKET', 'RBRC', ']'))
make_key(code=49, names=('BACKSLASH', 'BSLASH', 'BSLS', "\\"))
make_key(code=51, names=('SEMICOLON', 'SCOLON', 'SCLN', ';'))
make_key(code=52, names=('QUOTE', 'QUOT', "'"))
make_key(code=53, names=('GRAVE', 'GRV', 'ZKHK', '`'))
make_key(code=54, names=('COMMA', 'COMM', ','))
make_key(code=55, names=('DOT', '.'))
make_key(code=56, names=('SLASH', 'SLSH'))
gc.collect()
# Function Keys
make_key(code=58, names=('F1',))
make_key(code=59, names=('F2',))
make_key(code=60, names=('F3',))
make_key(code=61, names=('F4',))
make_key(code=62, names=('F5',))
make_key(code=63, names=('F6',))
make_key(code=64, names=('F7',))
make_key(code=65, names=('F8',))
make_key(code=66, names=('F9',))
make_key(code=67, names=('F10',))
make_key(code=68, names=('F11',))
make_key(code=69, names=('F12',))
make_key(code=104, names=('F13',))
make_key(code=105, names=('F14',))
make_key(code=106, names=('F15',))
make_key(code=107, names=('F16',))
make_key(code=108, names=('F17',))
make_key(code=109, names=('F18',))
make_key(code=110, names=('F19',))
make_key(code=111, names=('F20',))
make_key(code=112, names=('F21',))
make_key(code=113, names=('F22',))
make_key(code=114, names=('F23',))
make_key(code=115, names=('F24',))
gc.collect()
# Lock Keys, Navigation, etc.
make_key(code=57, names=('CAPS_LOCK', 'CAPSLOCK', 'CLCK', 'CAPS'))
# FIXME: Investigate whether this key actually works, and
# uncomment when/if it does.
# make_key(code=130, names=('LOCKING_CAPS', 'LCAP'))
make_key(code=70, names=('PRINT_SCREEN', 'PSCREEN', 'PSCR'))
make_key(code=71, names=('SCROLL_LOCK', 'SCROLLLOCK', 'SLCK'))
# FIXME: Investigate whether this key actually works, and
# uncomment when/if it does.
# make_key(code=132, names=('LOCKING_SCROLL', 'LSCRL'))
make_key(code=72, names=('PAUSE', 'PAUS', 'BRK'))
make_key(code=73, names=('INSERT', 'INS'))
make_key(code=74, names=('HOME',))
make_key(code=75, names=('PGUP',))
make_key(code=76, names=('DELETE', 'DEL'))
make_key(code=77, names=('END',))
make_key(code=78, names=('PGDOWN', 'PGDN'))
make_key(code=79, names=('RIGHT', 'RGHT'))
make_key(code=80, names=('LEFT',))
make_key(code=81, names=('DOWN',))
make_key(code=82, names=('UP',))
gc.collect()
# Numpad
make_key(code=83, names=('NUM_LOCK', 'NUMLOCK', 'NLCK'))
# FIXME: Investigate whether this key actually works, and
# uncomment when/if it does.
# make_key(code=131, names=('LOCKING_NUM', 'LNUM'))
make_key(code=84, names=('KP_SLASH', 'NUMPAD_SLASH', 'PSLS'))
make_key(code=85, names=('KP_ASTERISK', 'NUMPAD_ASTERISK', 'PAST'))
make_key(code=86, names=('KP_MINUS', 'NUMPAD_MINUS', 'PMNS'))
make_key(code=87, names=('KP_PLUS', 'NUMPAD_PLUS', 'PPLS'))
make_key(code=88, names=('KP_ENTER', 'NUMPAD_ENTER', 'PENT'))
make_key(code=89, names=('KP_1', 'P1', 'NUMPAD_1'))
make_key(code=90, names=('KP_2', 'P2', 'NUMPAD_2'))
make_key(code=91, names=('KP_3', 'P3', 'NUMPAD_3'))
make_key(code=92, names=('KP_4', 'P4', 'NUMPAD_4'))
make_key(code=93, names=('KP_5', 'P5', 'NUMPAD_5'))
make_key(code=94, names=('KP_6', 'P6', 'NUMPAD_6'))
make_key(code=95, names=('KP_7', 'P7', 'NUMPAD_7'))
make_key(code=96, names=('KP_8', 'P8', 'NUMPAD_8'))
make_key(code=97, names=('KP_9', 'P9', 'NUMPAD_9'))
make_key(code=98, names=('KP_0', 'P0', 'NUMPAD_0'))
make_key(code=99, names=('KP_DOT', 'PDOT', 'NUMPAD_DOT'))
make_key(code=103, names=('KP_EQUAL', 'PEQL', 'NUMPAD_EQUAL'))
make_key(code=133, names=('KP_COMMA', 'PCMM', 'NUMPAD_COMMA'))
make_key(code=134, names=('KP_EQUAL_AS400', 'NUMPAD_EQUAL_AS400'))
gc.collect()
# Making life better for folks on tiny keyboards especially: exposes
# the "shifted" keys as raw keys. Under the hood we're still
# sending Shift+(whatever key is normally pressed) to get these, so
# for example `KC_AT` will hold shift and press 2.
make_shifted_key('GRAVE', names=('TILDE', 'TILD', '~'))
make_shifted_key('1', names=('EXCLAIM', 'EXLM', '!'))
make_shifted_key('2', names=('AT', '@'))
make_shifted_key('3', names=('HASH', 'POUND', '#'))
make_shifted_key('4', names=('DOLLAR', 'DLR', '$'))
make_shifted_key('5', names=('PERCENT', 'PERC', '%'))
make_shifted_key('6', names=('CIRCUMFLEX', 'CIRC', '^'))
make_shifted_key('7', names=('AMPERSAND', 'AMPR', '&'))
make_shifted_key('8', names=('ASTERISK', 'ASTR', '*'))
make_shifted_key('9', names=('LEFT_PAREN', 'LPRN', '('))
make_shifted_key('0', names=('RIGHT_PAREN', 'RPRN', ')'))
make_shifted_key('MINUS', names=('UNDERSCORE', 'UNDS', '_'))
make_shifted_key('EQUAL', names=('PLUS', '+'))
make_shifted_key('LBRACKET', names=('LEFT_CURLY_BRACE', 'LCBR', '{'))
make_shifted_key('RBRACKET', names=('RIGHT_CURLY_BRACE', 'RCBR', '}'))
make_shifted_key('BACKSLASH', names=('PIPE', '|'))
make_shifted_key('SEMICOLON', names=('COLON', 'COLN', ':'))
make_shifted_key('QUOTE', names=('DOUBLE_QUOTE', 'DQUO', 'DQT', '"'))
make_shifted_key('COMMA', names=('LEFT_ANGLE_BRACKET', 'LABK', '<'))
make_shifted_key('DOT', names=('RIGHT_ANGLE_BRACKET', 'RABK', '>'))
make_shifted_key('SLSH', names=('QUESTION', 'QUES', '?'))
gc.collect()
# International
make_key(code=50, names=('NONUS_HASH', 'NUHS'))
make_key(code=100, names=('NONUS_BSLASH', 'NUBS'))
make_key(code=135, names=('INT1', 'RO'))
make_key(code=136, names=('INT2', 'KANA'))
make_key(code=137, names=('INT3', 'JYEN'))
make_key(code=138, names=('INT4', 'HENK'))
make_key(code=139, names=('INT5', 'MHEN'))
make_key(code=140, names=('INT6',))
make_key(code=141, names=('INT7',))
make_key(code=142, names=('INT8',))
make_key(code=143, names=('INT9',))
make_key(code=144, names=('LANG1', 'HAEN'))
make_key(code=145, names=('LANG2', 'HAEJ'))
make_key(code=146, names=('LANG3',))
make_key(code=147, names=('LANG4',))
make_key(code=148, names=('LANG5',))
make_key(code=149, names=('LANG6',))
make_key(code=150, names=('LANG7',))
make_key(code=151, names=('LANG8',))
make_key(code=152, names=('LANG9',))
gc.collect()
# Consumer ("media") keys. Most known keys aren't supported here. A much
# longer list used to exist in this file, but the codes were almost certainly
# incorrect, conflicting with each other, or otherwise "weird". We'll add them
# back in piecemeal as needed. PRs welcome.
#
# A super useful reference for these is http://www.freebsddiary.org/APC/usb_hid_usages.php
# Note that currently we only have the PC codes. Recent MacOS versions seem to
# support PC media keys, so I don't know how much value we would get out of
# adding the old Apple-specific consumer codes, but again, PRs welcome if the
# lack of them impacts you.
make_consumer_key(code=226, names=('AUDIO_MUTE', 'MUTE')) # 0xE2
make_consumer_key(code=233, names=('AUDIO_VOL_UP', 'VOLU')) # 0xE9
make_consumer_key(code=234, names=('AUDIO_VOL_DOWN', 'VOLD')) # 0xEA
make_consumer_key(code=181, names=('MEDIA_NEXT_TRACK', 'MNXT')) # 0xB5
make_consumer_key(code=182, names=('MEDIA_PREV_TRACK', 'MPRV')) # 0xB6
make_consumer_key(code=183, names=('MEDIA_STOP', 'MSTP')) # 0xB7
make_consumer_key(code=205, names=('MEDIA_PLAY_PAUSE', 'MPLY')) # 0xCD (this may not be right)
make_consumer_key(code=184, names=('MEDIA_EJECT', 'EJCT')) # 0xB8
make_consumer_key(code=179, names=('MEDIA_FAST_FORWARD', 'MFFD')) # 0xB3
make_consumer_key(code=180, names=('MEDIA_REWIND', 'MRWD')) # 0xB4
gc.collect()
# Internal, diagnostic, or auxiliary/enhanced keys
# NO and TRNS are functionally identical in how they (don't) mutate
# the state, but are tracked semantically separately, so create
# two keys with the exact same functionality
for names in (('NO',), ('TRANSPARENT', 'TRNS')):
make_key(
names=names,
on_press=handlers.passthrough,
on_release=handlers.passthrough,
)
make_key(names=('RESET',), on_press=handlers.reset)
make_key(names=('BOOTLOADER',), on_press=handlers.bootloader)
make_key(names=('DEBUG', 'DBG'), on_press=handlers.debug_pressed, on_release=handlers.passthrough)
make_key(names=('GESC',), on_press=handlers.gesc_pressed, on_release=handlers.gesc_released)
make_key(
names=('LEADER', 'LEAD'),
on_press=handlers.leader_pressed,
on_release=handlers.passthrough,
)
def layer_key_validator(layer, kc=None):
'''
Validates the syntax (but not semantics) of a layer key call. We won't
have access to the keymap here, so we can't verify much of anything useful
here (like whether the target layer actually exists). The spirit of this
existing is mostly that Python will catch extraneous args/kwargs and error
out.
'''
return LayerKeyMeta(layer=layer, kc=kc)
# Layers
make_argumented_key(
validator=layer_key_validator,
names=('MO',),
on_press=layers.mo_pressed,
on_release=layers.mo_released,
)
make_argumented_key(
validator=layer_key_validator,
names=('DF',),
on_press=layers.df_pressed,
)
make_argumented_key(
validator=layer_key_validator,
names=('LM',),
on_press=layers.lm_pressed,
on_release=layers.lm_released,
)
make_argumented_key(
validator=layer_key_validator,
names=('LT',),
on_press=layers.lt_pressed,
on_release=layers.lt_released,
)
make_argumented_key(
validator=layer_key_validator,
names=('TG',),
on_press=layers.tg_pressed,
)
make_argumented_key(
validator=layer_key_validator,
names=('TO',),
on_press=layers.to_pressed,
)
make_argumented_key(
validator=layer_key_validator,
names=('TT',),
on_press=layers.tt_pressed,
on_release=layers.tt_released,
)
gc.collect()
def key_seq_sleep_validator(ms):
return KeySeqSleepMeta(ms)
# A dummy key to trigger a sleep_ms call in a sequence of other keys in a
# simple sequence macro.
make_argumented_key(
validator=key_seq_sleep_validator,
names=('MACRO_SLEEP_MS', 'SLEEP_IN_SEQ'),
on_press=handlers.sleep_pressed,
)
# Switch unicode modes at runtime
make_key(
names=('UC_MODE_NOOP', 'UC_DISABLE'),
meta=UnicodeModeKeyMeta(UnicodeMode.NOOP),
on_press=handlers.uc_mode_pressed,
)
make_key(
names=('UC_MODE_LINUX', 'UC_MODE_IBUS'),
meta=UnicodeModeKeyMeta(UnicodeMode.IBUS),
on_press=handlers.uc_mode_pressed,
)
make_key(
names=('UC_MODE_MACOS', 'UC_MODE_OSX', 'US_MODE_RALT'),
meta=UnicodeModeKeyMeta(UnicodeMode.RALT),
on_press=handlers.uc_mode_pressed,
)
make_key(
names=('UC_MODE_WINC',),
meta=UnicodeModeKeyMeta(UnicodeMode.WINC),
on_press=handlers.uc_mode_pressed,
)
def unicode_mode_key_validator(mode):
return UnicodeModeKeyMeta(mode)
make_argumented_key(
validator=unicode_mode_key_validator,
names=('UC_MODE',),
on_press=handlers.uc_mode_pressed,
)
# Tap Dance
make_argumented_key(
validator=lambda *codes: TapDanceKeyMeta(codes),
names=('TAP_DANCE', 'TD'),
on_press=handlers.td_pressed,
on_release=handlers.td_released,
)

View File

@ -1,76 +0,0 @@
import math
from kmk.event_defs import (hid_report_event, keycode_down_event,
keycode_up_event)
from kmk.keycodes import Media
from kmk.rotary_encoder import RotaryEncoder
VAL_FALSE = False + 1
VAL_NONE = True + 2
VAL_TRUE = True + 1
VOL_UP_PRESS = keycode_down_event(Media.KC_AUDIO_VOL_UP)
VOL_UP_RELEASE = keycode_up_event(Media.KC_AUDIO_VOL_UP)
VOL_DOWN_PRESS = keycode_down_event(Media.KC_AUDIO_VOL_DOWN)
VOL_DOWN_RELEASE = keycode_up_event(Media.KC_AUDIO_VOL_DOWN)
class RotaryEncoderMacro:
def __init__(self, pos_pin, neg_pin, slop_history=1, slop_threshold=1):
self.encoder = RotaryEncoder(pos_pin, neg_pin)
self.max_history = slop_history
self.history = bytearray(slop_history)
self.history_idx = 0
self.history_threshold = math.floor(slop_threshold * slop_history)
def scan(self):
# Anti-slop logic
self.history[self.history_idx] = 0
reading = self.encoder.direction()
self.history[self.history_idx] = VAL_NONE if reading is None else reading + 1
self.history_idx += 1
if self.history_idx >= self.max_history:
self.history_idx = 0
nones = 0
trues = 0
falses = 0
for val in self.history:
if val == VAL_NONE:
nones += 1
elif val == VAL_TRUE:
trues += 1
elif val == VAL_FALSE:
falses += 1
if nones >= self.history_threshold:
return None
if trues >= self.history_threshold:
return self.on_increase()
if falses >= self.history_threshold:
return self.on_decrease()
def on_decrease(self):
pass
def on_increase(self):
pass
class VolumeRotaryEncoder(RotaryEncoderMacro):
def on_decrease(self):
yield VOL_DOWN_PRESS
yield hid_report_event
yield VOL_DOWN_RELEASE
yield hid_report_event
def on_increase(self):
yield VOL_UP_PRESS
yield hid_report_event
yield VOL_UP_RELEASE
yield hid_report_event

View File

@ -1,28 +0,0 @@
from kmk.keycodes import Keycodes, Macro, char_lookup, lookup_kc_with_cache
from kmk.string import ascii_letters, digits
def simple_key_sequence(seq):
def _simple_key_sequence(state):
return seq
return Macro(keydown=_simple_key_sequence)
def send_string(message):
seq = []
for char in message:
kc = None
if char in char_lookup:
kc = char_lookup[char]
elif char in ascii_letters + digits:
kc = lookup_kc_with_cache(char)
if char.isupper():
kc = Keycodes.Modifiers.KC_LSHIFT(kc)
seq.append(kc)
return simple_key_sequence(seq)

View File

@ -1,80 +0,0 @@
from kmk.consts import UnicodeMode
from kmk.keycodes import (Common, Macro, Modifiers,
generate_codepoint_keysym_seq)
from kmk.macros.simple import 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))
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 compile_unicode_string_sequences(string_table):
for k, v in string_table.items():
string_table[k] = unicode_string_sequence(v)
return AttrDict(string_table)
def unicode_string_sequence(unistring):
'''
Allows sending things like (°° directly, without
manual conversion to Unicode codepoints.
'''
return unicode_codepoint_sequence([
hex(get_wide_ordinal(s))[2:]
for s in unistring
])
def unicode_codepoint_sequence(codepoints):
kc_seqs = (
generate_codepoint_keysym_seq(codepoint)
for codepoint in codepoints
)
kc_macros = [
simple_key_sequence(kc_seq)
for kc_seq in kc_seqs
]
def _unicode_sequence(state):
if state.unicode_mode == UnicodeMode.IBUS:
yield from _ibus_unicode_sequence(kc_macros, state)
elif state.unicode_mode == UnicodeMode.RALT:
yield from _ralt_unicode_sequence(kc_macros, state)
elif state.unicode_mode == UnicodeMode.WINC:
yield from _winc_unicode_sequence(kc_macros, state)
return Macro(keydown=_unicode_sequence)
def _ralt_unicode_sequence(kc_macros, state):
for kc_macro in kc_macros:
yield RALT_DOWN_NO_RELEASE
yield from kc_macro.keydown(state)
yield RALT_UP_NO_PRESS
def _ibus_unicode_sequence(kc_macros, state):
for kc_macro in kc_macros:
yield IBUS_KEY_COMBO
yield from kc_macro.keydown(state)
yield ENTER_KEY
def _winc_unicode_sequence(kc_macros, state):
'''
Send unicode sequence using WinCompose:
http://wincompose.info/
https://github.com/SamHocevar/wincompose
'''
for kc_macro in kc_macros:
yield RALT_KEY
yield U_KEY
yield from kc_macro.keydown(state)

View File

@ -1,57 +0,0 @@
from kmk.pins import PULL_UP
class RotaryEncoder:
# Please don't ask. I don't know. All I know is bit_value
# works as expected. Here be dragons, etc. etc.
MIN_VALUE = False + 1 << 1 | True + 1
MAX_VALUE = True + 1 << 1 | True + 1
def __init__(self, pos_pin, neg_pin):
self.pos_pin = pos_pin
self.neg_pin = neg_pin
self.pos_pin.switch_to_input(pull=PULL_UP)
self.neg_pin.switch_to_input(pull=PULL_UP)
self.prev_bit_value = self.bit_value()
def value(self):
return (self.pos_pin.value(), self.neg_pin.value())
def bit_value(self):
'''
Returns 2, 3, 5, or 6 based on the state of the rotary encoder's two
bits. This is a total hack but it does what we need pretty efficiently.
Shrug.
'''
return self.pos_pin.value() + 1 << 1 | self.neg_pin.value() + 1
def direction(self):
'''
Compares the current rotary position against the last seen position.
Returns True if we're rotating "positively", False if we're rotating "negatively",
and None if no change could safely be detected for any reason (usually this
means the encoder itself did not change)
'''
new_value = self.bit_value()
rolling_under = self.prev_bit_value == self.MIN_VALUE and new_value == self.MAX_VALUE
rolling_over = self.prev_bit_value == self.MAX_VALUE and new_value == self.MIN_VALUE
increasing = new_value > self.prev_bit_value
decreasing = new_value < self.prev_bit_value
self.prev_bit_value = new_value
if rolling_over:
return True
elif rolling_under:
return False
if increasing:
return True
if decreasing:
return False
# Either no change, or not a type of change we can safely detect,
# so safely do nothing
return None

View File

@ -1,51 +0,0 @@
# 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

@ -24,3 +24,29 @@ class Anything:
class Passthrough:
def __getattr__(self, attr):
return Anything(attr)
class LayerKeyMeta:
def __init__(self, layer, kc=None):
self.layer = layer
self.kc = kc
class KeySequenceMeta:
def __init__(self, seq):
self.seq = seq
class KeySeqSleepMeta:
def __init__(self, ms):
self.ms = ms
class UnicodeModeKeyMeta:
def __init__(self, mode):
self.mode = mode
class TapDanceKeyMeta:
def __init__(self, codes):
self.codes = codes

View File

@ -1,8 +1,7 @@
from kmk.consts import DiodeOrientation, UnicodeMode
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
from kmk.handlers.sequences import (compile_unicode_string_sequences,
send_string)
from kmk.keys import KC
from kmk.mcus.circuitpython_samd51 import Firmware
from kmk.pins import Pin as P
from kmk.types import AttrDict
@ -35,13 +34,13 @@ emoticons = compile_unicode_string_sequences({
# ---------------------- Leader Key Macros --------------------------------------------
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,
'flip': emoticons.ANGRY_TABLE_FLIP,
'cheer': emoticons.CHEER,
'wat': emoticons.WAT,
'ff': emoticons.FF,
'f': emoticons.F,
'meh': emoticons.MEH,
'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.")

View File

@ -2,9 +2,8 @@ import board
import busio
from kmk.consts import DiodeOrientation, LeaderMode, UnicodeMode
from kmk.keycodes import KC
from kmk.keycodes import generate_leader_dictionary_seq as glds
from kmk.macros.unicode import compile_unicode_string_sequences
from kmk.handlers.sequences import compile_unicode_string_sequences
from kmk.keys import KC
from kmk.mcus.circuitpython_samd51 import Firmware
from kmk.pins import Pin as P
@ -42,13 +41,13 @@ emoticons = compile_unicode_string_sequences({
# ---------------------- Leader Key Macros --------------------------------------------
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,
'flip': emoticons.ANGRY_TABLE_FLIP,
'cheer': emoticons.CHEER,
'wat': emoticons.WAT,
'ff': emoticons.FF,
'f': emoticons.F,
'meh': emoticons.MEH,
'yay': emoticons.YAY,
}
# ---------------------- Keymap ---------------------------------------------------------

View File

@ -2,10 +2,9 @@ import board
import busio
from kmk.consts import DiodeOrientation, LeaderMode, UnicodeMode
from kmk.keycodes import KC
from kmk.keycodes import generate_leader_dictionary_seq as glds
from kmk.macros.simple import simple_key_sequence
from kmk.macros.unicode import compile_unicode_string_sequences
from kmk.handlers.sequences import (compile_unicode_string_sequences,
simple_key_sequence)
from kmk.keys import KC
from kmk.mcus.circuitpython_samd51 import Firmware
from kmk.pins import Pin as P
@ -43,14 +42,14 @@ emoticons = compile_unicode_string_sequences({
# ---------------------- Leader Key Macros --------------------------------------------
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,
glds('gw'): simple_key_sequence([KC.DF(1)]),
'flip': emoticons.ANGRY_TABLE_FLIP,
'cheer': emoticons.CHEER,
'wat': emoticons.WAT,
'ff': emoticons.FF,
'f': emoticons.F,
'meh': emoticons.MEH,
'yay': emoticons.YAY,
'gw': KC.DF(1),
}
_______ = KC.TRNS

View File

@ -1,9 +1,8 @@
from kmk.boards.kitsym4_iris import Firmware
from kmk.consts import LeaderMode, UnicodeMode
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
from kmk.handlers.sequences import compile_unicode_string_sequences as cuss
from kmk.handlers.sequences import send_string, simple_key_sequence
from kmk.keys import KC
keyboard = Firmware()
@ -33,6 +32,7 @@ emoticons = cuss({
'MAPLE_LEAF': r'🍁',
'POOP': r'💩',
'TADA': r'🎉',
'SHRUG_EMOJI': r'🤷',
# Emoticons, but fancier
'ANGRY_TABLE_FLIP': r'(ノಠ痊ಠ)ノ彡┻━┻',
@ -43,15 +43,22 @@ emoticons = cuss({
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_mode = LeaderMode.TIMEOUT
keyboard.leader_mode = LeaderMode.ENTER
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,
'hello': send_string('hello world from kmk macros'),
'wpm': WPM,
'atf': emoticons.ANGRY_TABLE_FLIP,
'tf': emoticons.TABLE_FLIP,
'fca': emoticons.FLAG_CA,
'fus': emoticons.FLAG_US,
'cel': emoticons.CELEBRATORY_GLITTER,
'shr': emoticons.SHRUGGIE,
'shre': emoticons.SHRUG_EMOJI,
'poop': emoticons.POOP,
'joy': emoticons.FACE_JOY,
'ls': KC.LGUI(KC.HOME), # Lock screen
'cw': KC.LGUI(KC.END), # Close window
'dbg': KC.DBG,
}
_______ = KC.TRNS
@ -77,7 +84,7 @@ keyboard.keymap = [
[xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.F7, KC.F8, KC.F9, xxxxxxx, xxxxxxx, KC.EQUAL],
[xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.INS, KC.F4, KC.F5, KC.F6, xxxxxxx, xxxxxxx, xxxxxxx],
[xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.F1, KC.F2, KC.F3, xxxxxxx, xxxxxxx, _______],
[xxxxxxx, xxxxxxx, xxxxxxx, KC.HOME, KC.END, _______, xxxxxxx, KC.PGUP, KC.PGDN, _______, xxxxxxx, xxxxxxx],
[xxxxxxx, xxxxxxx, KC.LEAD, KC.HOME, KC.END, _______, xxxxxxx, KC.PGUP, KC.PGDN, _______, xxxxxxx, xxxxxxx],
],
[
[KC.MUTE, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.LBRC, KC.RBRC, KC.DEL],

View File

@ -1,9 +1,8 @@
from kmk.boards.klarank import Firmware
from kmk.consts import LeaderMode, UnicodeMode
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
from kmk.handlers.sequences import compile_unicode_string_sequences as cuss
from kmk.handlers.sequences import send_string
from kmk.keys import KC, make_key
keyboard = Firmware()
@ -45,13 +44,17 @@ WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
keyboard.leader_mode = LeaderMode.TIMEOUT
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,
'hello': send_string('hello world from kmk macros'),
'wpm': WPM,
'atf': emoticons.ANGRY_TABLE_FLIP,
'tf': emoticons.TABLE_FLIP,
'fca': emoticons.FLAG_CA,
'fus': emoticons.FLAG_US,
'cel': emoticons.CELEBRATORY_GLITTER,
'shr': emoticons.SHRUGGIE,
'poop': emoticons.POOP,
'ls': KC.LGUI(KC.HOME),
'dbg': KC.DBG,
}
_______ = KC.TRNS
@ -64,6 +67,39 @@ HELLA_TD = KC.TD(
)
def shrek_is_life(*args, **kwargs):
'''
Proof macros are a thing and don't actually have to care about the state. At all.
'''
print('⢀⡴⠑⡄⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣤⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀')
print('⠸⡇⠀⠿⡀⠀⠀⠀⣀⡴⢿⣿⣿⣿⣿⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠑⢄⣠⠾⠁⣀⣄⡈⠙⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⢀⡀⠁⠀⠀⠈⠙⠛⠂⠈⣿⣿⣿⣿⣿⠿⡿⢿⣆⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⢀⡾⣁⣀⠀⠴⠂⠙⣗⡀⠀⢻⣿⣿⠭⢤⣴⣦⣤⣹⠀⠀⠀⢀⢴⣶⣆')
print('⠀⠀⢀⣾⣿⣿⣿⣷⣮⣽⣾⣿⣥⣴⣿⣿⡿⢂⠔⢚⡿⢿⣿⣦⣴⣾⠁⠸⣼⡿')
print('⠀⢀⡞⠁⠙⠻⠿⠟⠉⠀⠛⢹⣿⣿⣿⣿⣿⣌⢤⣼⣿⣾⣿⡟⠉⠀⠀⠀⠀⠀')
print('⠀⣾⣷⣶⠇⠀⠀⣤⣄⣀⡀⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀')
print('⠀⠉⠈⠉⠀⠀⢦⡈⢻⣿⣿⣿⣶⣶⣶⣶⣤⣽⡹⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠀⠀⠀⠉⠲⣽⡻⢿⣿⣿⣿⣿⣿⣿⣷⣜⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣷⣶⣮⣭⣽⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠀⠀⣀⣀⣈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀')
print('⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⠿⠿⠿⠿⠛⠉')
# Spew shrek when hitting a fully custom key
# This won't modify internal state at all
SHREK_IS_LOVE = make_key(on_press=shrek_is_life)
# Also spew shrek every time I try to use Alt. It's a dev board, after all.
KC.LALT.before_press_handler(shrek_is_life)
# But also give me a normal alt if I want it. Shrek isn't ALWAYS life.
BORING_ALT = KC.LALT.clone()
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],
@ -80,7 +116,7 @@ keyboard.keymap = [
],
[
[KC.GESC, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, KC.BSLS, KC.LBRC, KC.RBRC, KC.DEL],
[KC.GESC, SHREK_IS_LOVE, 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],
@ -89,7 +125,7 @@ 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.LGUI, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, xxxxxxx, BORING_ALT],
[KC.LCTL, KC.DBG, HELLA_TD, xxxxxxx, _______, _______, xxxxxxx, xxxxxxx, KC.MUTE, KC.VOLD, KC.VOLU, xxxxxxx],
],
]