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:
commit
1ad7602a9b
@ -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
|
||||
|
14
Makefile
14
Makefile
@ -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
3
boot.py
Executable file
@ -0,0 +1,3 @@
|
||||
import supervisor
|
||||
|
||||
supervisor.set_next_stack_limit(4096 + 1024)
|
@ -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>|</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
158
docs/keys.md
Normal 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
112
docs/sequences.md
Normal 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,...]
|
||||
```
|
@ -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,...]
|
||||
```
|
@ -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
108
kmk/handlers/layers.py
Normal 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
152
kmk/handlers/sequences.py
Normal 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
97
kmk/handlers/stock.py
Normal 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)
|
17
kmk/hid.py
17
kmk/hid.py
@ -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:
|
||||
|
@ -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
|
||||
|
692
kmk/keycodes.py
692
kmk/keycodes.py
@ -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
737
kmk/keys.py
Normal 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,
|
||||
)
|
@ -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
|
@ -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)
|
@ -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)
|
@ -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
|
@ -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()
|
26
kmk/types.py
26
kmk/types.py
@ -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
|
||||
|
@ -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.")
|
||||
|
@ -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 ---------------------------------------------------------
|
||||
|
@ -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
|
||||
|
@ -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],
|
||||
|
@ -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],
|
||||
],
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user