Note: This is not a lookup table of key objects provided by KMK. That listing
can be found in keycodes.md
. It's probably worth a look at the raw source if
you're stumped: kmk/keys.py
.
Here's a very contrived example for a custom key with a limit to the number of times it can used (until the next keyboard reset). Custom keys are, as a rule of thumb, the way to go to implement custom functionality. If the objective is to type out a sequence of keys however, or an action has to be performed asynchronously while holding a key down, then macros are worth trading higher convenience for the hit in performance.
The base key class, of which all keys are derived, accepts custom handlers.
It's "single use", should be fine for most use cases, but is not recommended for
stateful keys.
Note: Both on_press
and on_release
methods are optional and a custom key is
allowed to have none of the two and do absolutely nothing.
from kmk.keys import Key
limit = 10
def limit_on_press(key, keyboard, *args):
global limit
if limit > 0:
keyboard.add_key(KC.A)
def limit_on_release(key, keyboard, *args):
global limit
if limit > 0:
keyboard.remove_key(KC.A)
limit -= 1
KC_A10 = Key(on_press=limit_on_press, on_release=limit_on_release)
Reusable or stateful keys are better implemented as a custom key derived from
the base class.
Giving the key a custom type (i.e. name) can make it easier to spot in
debug messages and opens up to possibility to react on key types in custom
modules; the downside is a potential slight increase in memory consumption.
All methods are technically optional, although it is recommended to implement
them anyway or the default implementations of Key
may look for handlers that
don't exist.
from kmk.keys import Key
class LimitKey(Key):
def __init__(self, key, limit):
self.key = KC.A
self.limit = limit
def on_press(self, keyboard, coord_int=None):
if self.limit > 0:
keyboard.add_key(self.key)
def on_release(self, keyboard, coord_int=None):
if self.limit > 0:
self.limit -= 1
keyboard.remove_key(self.key)
KC_A10 = LimitKey(KC.A, 10)
KC_B20 = LimitKey(KC.B, 20)
For completeness sake: this is how keys can be entered into the KC
dictionary.
There's no reason to do this as it will have a negative, if probably small
effect on memory usage with no actual benefit.
from kmk.keys import make_key
# with an instance of base key class with 1 alias
make_key(
names=('A10',),
constructor=Key,
on_press=limit_on_press,
on_release=limit_on_release,
)
# with a custom base key class with 3 aliases
make_key(
names=('B20', 'LIMIT_B_20', 'B_ONLY_20_TIMES'),
constructor=LimitKey,
key=KC.B,
count=20,
)
# makes those keys available as:
KC.A10
KC.B20
KC.LIMIT_B_20
KC.B_ONLY_20_TIMES
This is a bunch of documentation about how a physical keypress translates 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. 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/kmk_keyboard.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 or None. Integers sent through to the HID stack (and thus the computer, which will translate that integer to something meaningful - for example,code=4
becomesa
on a US QWERTY/Dvorak keyboard). -
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) inkmk/handlers/stock.py
. -
A generic
meta
field, which is most commonly used for "argumented" keys - objects in theKC
object which are actually functions that returnKey
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 addsKC.MO(1)
to their keymap, the function call returns aKey
object withmeta
set to an object containinglayer
andkc
properties, for example. There's other uses formeta
, and examples can be found inkmk/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:
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
Key
is first passed through the module processing pipeline.
Modules can do whatever with that Key
, but usually keys either pass right
through, or are intercepted and emitted again later (think of timing based
modules like Combos and Hold-Tap).
Finally the assigned press handler will be run (most commonly, this is provided
by KMK).
On release the Key
object lookup is, most of the time, cached and doesn't
require searching the keymap again.
Then it's the processing pipeline again, followed by the release handler.
Custom behavior can either be achieved with custom press and release handlers or with macros.
You can also refer to a key by index:
KC['A']
KC['NO']
KC['LALT']
Or the KC.get
function which has an optional default argument, which will
be returned if the key is not found (default=None
unless otherwise specified):
KC.get('A')
KC.get('NO', None)
KC.get('NOT DEFINED', KC.RALT)
Key names are case-sensitive. KC['NO']
is different from KC['no']
. It is recommended
that names are normally UPPER_CASE. The exception to this are alpha keys; KC['A']
and
KC['a']
will both return the same, unshifted, key.