Using the QMK Leader Key for Fancy Keyboard Functionality
QMK keyboards are keyboards where the functionality can be customized.
Customising keyboard functionality goes hand in hand with non-traditional keyboards, like ortholinear or split keyboards, which seek to improve upon the traditional keyboard’s pretty awful design. (I’ve designed a few such keyboards).
There are some QMK features where it’s fairly clear how to make effective use of the feature, such as layers, tap hold or caps word.
One feature that I had not found an effective use for was the “leader key” feature. This feature is named after Vim’s leader key.
Vim and Key Sequences
In vim, the leader key is a placeholder, and can be used for the start of a sequences of keys which maps to some command.
Generally, sequences of keys are fundamental to vim’s keybindings, especially for its
verb-motion
idiom.
e.g. the key sequence d$
commands the editor to “delete until the end of
line”, or yi"
commands the editor to yank (copy) the text inside the "
marks
the cursor is inside of.
I first came across heavy use of the space-as-leader idiom in spacemacs, which uses vim-keybindings, and space as its leader key.
My main text editing environment is Doom Emacs,
which also uses space-as-leader for its command map.
e.g. spc t b
to Toggles the font to a Big size. spc f s
Saves the File.
VSCode, to a lesser extent, also uses sequences in its keyboard shortcuts.
e.g. Ctrl+K Ctrl+C adds a line comment.
In QMK, the leader key is used to start listening for a sequence of keypresses, which can then be handled by custom functionality.
Typical Uses of the QMK Leader Key
What to use QMK’s leader key for?
QMK’s documentation for the leader
key gives examples of sequences which
map to SEND_STRING
. i.e. using it as a way to trigger macros.
I had previously tried that, but didn’t end up making much use of it.
I reckon the effort to recognise I could use the leader sequence (& recall how
to invoke it) was higher than just typing out my email or username or hostname
or whatever.
But also, I think there are other ways to avoid having to type the same thing
out frequently:
On the command line, using fzf with shell history is a great way to find
previously typed commands, which is close enough to “save typing the same thing
out” for me; fish shell’s
abbr seems a useful way of
“save typing the same thing out”. This is similar to typical shell aliases,
except it expands the abbreviation before executing it.
Another Idea for QMK Leader Key Sequences
Putting 2 and 2 together, I had another idea of what to use the QMK leader functionality for:
void leader_end_user(void) {
if (leader_sequence_two_keys(KC_Q, KC_B)) { // mnemonic: QMK Boot
();
reset_keyboard} else if (leader_sequence_two_keys(KC_C, KC_C)) { // mnemonic: Caps DWIM (capslock)
(KC_CAPS);
tap_code#ifdef CAPS_WORD_ENABLE
} else if (leader_sequence_two_keys(KC_C, KC_W)) { // mnemonic: Caps capsWord
();
caps_word_on#endif
#ifdef HAPTIC_ENABLE
} else if (leader_sequence_two_keys(KC_H, KC_H)) { // mnemonic: Haptic DWIM (toggle)
();
haptic_toggle} else if (leader_sequence_two_keys(KC_H, KC_E)) { // mnemonic: Haptic Enable
();
haptic_enable} else if (leader_sequence_two_keys(KC_H, KC_D)) { // mnemonic: Haptic Disable
();
haptic_disable#endif
} else if (leader_sequence_two_keys(KC_K, KC_K)) { // mnemonic: Keymap DWIM (set to default)
();
default_layer_set_dvorak_keymap} else if (leader_sequence_two_keys(KC_K, KC_D)) { // mnemonic: Keymap Dvorak
();
default_layer_set_dvorak_keymap} else if (leader_sequence_two_keys(KC_K, KC_Q)) { // mnemonic: Keymap Qwerty
();
default_layer_set_qwerty_keymap} else if (leader_sequence_two_keys(KC_K, KC_G)) { // mnemonic: Keymap Gaming
();
default_layer_set_gaming_keymap} else if (leader_sequence_two_keys(KC_K, KC_H)) { // mnemonic: Keymap Gaming (alt)
();
default_layer_set_gaming_alt_keymap} else if (leader_sequence_two_keys(KC_O, KC_W)) { // mnemonic: OS Windows
= _OS_WIN;
current_os } else if (leader_sequence_two_keys(KC_O, KC_L)) { // mnemonic: OS Linux
= _OS_LINUX;
current_os } else if (leader_sequence_two_keys(KC_O, KC_M)) { // mnemonic: OS MacOS
= _OS_MACOS;
current_os #ifdef RGB_MATRIX_ENABLE
} else if (leader_sequence_two_keys(KC_R, KC_R)) { // mnemonic: RGB DWIM (next effect)
();
rgb_matrix_step_noeeprom} else if (leader_sequence_two_keys(KC_R, KC_J)) { // mnemonic: RGB Jellybean
(RGB_MATRIX_JELLYBEAN_RAINDROPS);
rgb_matrix_mode_noeeprom} else if (leader_sequence_two_keys(KC_R, KC_T)) { // mnemonic: RGB Toggle
();
rgb_matrix_toggle_noeeprom#endif
} else if (leader_sequence_one_key(KC_L)) {
switch (current_os) {
case _OS_LINUX:
(CODE16_LINUX_DESKTOP_LOCK);
tap_code16break;
case _OS_MACOS:
(CODE16_MACOS_DESKTOP_LOCK);
tap_code16break;
case _OS_WIN:
(CODE16_WIN_DESKTOP_LOCK);
tap_code16break;
}
}
();
leader_end_keymap}
This code implements the leader_end_user
callback, which handles how QMK
leader key sequences behave for a keymap.
Example sequences:
LEAD q b
enters the bootloader, which I hope to recall with “QMK -> Bootloader”.LEAD r t
toggles whether RGB lighting is on/off, which I hope to recall with “RGB -> Toggle”.
“DWIM” means “Do What I Mean”.
In this case, I mean “what’s the most common functionality to expect (from
tapping the same key twice)”.
More concretely, the idea is that QMK leader key sequences seem like a natural interface for dynamic customisation of keyboard functionality.
I like the idea of allowing both the standard CapsLock and the fancier caps word within the same keymap, but without needing to take the effort to remember which key I put where.
I put my leader_end_user
implementation in my
userspace.
Downside: Discoverability
One obvious downside to this approach is that a keyboard’s firmware is not easily discoverable.
There’d be no easy way to discover what leader key sequences are implemented in a keymap.
Whereas, say, in VSCode, the keyboard shortcuts are shown as part of the command
palette.
Or in Emacs, which-key will
automatically show a map of keybindings (and the commands they’re bound to)
after pressing some key.
e.g. pressing spc f
presents a map of keybindings for next keys to press (such
as s
to save file, C
to copy the file, D
to delete the file).
Similar Functionality: QMK’s Command
The idea of using the keyboard to dynamically configure the keyboard configuration is similar to what the Command feature does. – Except these commands are invoked by holding down modifier keys, rather than by hitting a sequence of keys.
The idea of using leader-key sequences for dynamically customising various features is more general (& implemented by the keymap, rather than as part of QMK’s framework).
Incidentally, this Command feature has a “print help to the console” keybinding, which is one way of addressing lack of discoverability.
Recall Cost for Custom and Small Keyboard Keymaps
Maybe the idea of using leader key sequences seems complex; but I think it’s worth comparing the above idea to other techniques used in keymap implementations for custom (and small) keyboards:
Custom keyboards tend to have more functionality than just what the legends on
the keycaps denote.
This means you have to remember anything that’s not on the keycaps.
Anything that’s frequently used will be easy to recall.
What requires more consideration is trying to make recall easier for stuff
that’s used infrequently.
Small keyboards (like the ortholinear 4x12 keyboards, or split 36-key
keyboards) have keymaps which bring the full functionality of a typical keyboard
to within easy reach of the hands on home row.
In my experience, it’s fairly straightforward to recall the “standard keyboard”
keys (letters, numbers, symbols), since it’s easy to position the keys in a way
that’s either familiar, or coherent.
e.g. The popular Miryoku keymap demonstrates a few techniques for how this is commonly achieved:
As an example of good key placement, where the positions are easy to recall: The
Number/Symbol layers are different than a traditional keyboard, but it’s
(mostly) coherent: the numbers resemble a numpad, the [
key pairs with ]
.
_
is often used as a placeholder for “space”.
(I personally prefer to amend it by having /
complement \
, and by moving ~
to home row’s pinkie finger).
Examples of how functionality is brought to within reach of the hands:
Modifier keys Shift/Ctrl/Gui/Alt can be used with tap-hold using the keys on home row. (“Home Row Mods”).
Miryoku puts the
CapsLock
key on a layer.Miryoku has some “Additional Features”, such as switching between a Base and an Extra keymap. These keys are on a layer, with additional intention gated by requiring these keys to be tapped twice. (QMK’s tap dance feature).
Putting the key on a layer comes with positional cost. – You have to be able to recall where the key is. – And you have to recall which modifier keys to hold down to activate the key. (Modifier-based shortcuts are more common than sequence-based shortcuts. e.g. Ctrl+S for “save”, Ctrl+N for “new”. If you squint, holding down Ctrl & tapping S could be thought of as holding down a Fn key, and hitting the “Save” key).
For “fancy keycodes” (keys that don’t appear on a typical keyboard), it can be
difficult to come up with ways to make it easy to remember its position.
e.g. in the miryoku layout, it’s easier to recall where the arrow keys are
placed than where the “change RGB hue” key is. (The arrow keys are placed on
home row, or in a vi-style hjkl position).
Using QMK leader key sequences for “fancy keycodes” might be complex, but the effort to remember the key sequence doesn’t rely on finding a position for it on a layer, nor having to recall that position later.
Leader Key Sequence Cost: Multiple Key Presses
Another obvious cost to using QMK leader sequences like suggested is the sequence is not very quick to type out.
It involves pressing the LEAD
key, and then some sequence of keys.
That’s surely going to be slower than holding down some Fn keys and hitting a
single key.
This seems a reasonable trade-off to me: the cost of having to hit a sequence of keys comes with the benefit that it’s easier to recall how to invoke it.
I’m optimistic that using leader key sequences are suitable for making it easier to invoke behaviour that’s otherwise not frequently invoked enough to be easy to position on a keyboard.
Leader Key Sequence Cost: Firmware Size and Complexity
I’m guessing a large number leader key sequences wouldn’t fit neatly on a keyboard with a weak atmega32u4 MCU.
That’s probably another reason why getting an ARM-based custom keyboard. (Plugging again: I’ve designed a few such keyboards).
Other Possible Use Cases
Without having to worry about “where do I position these keys”, I reckon it’s easier to add additional functionality to a keymap by adding a leader key sequence which invokes it.
The point is less “it’s good to use leader key sequences for everything”, and more “it’s a low cost to add it to the keymap, and will hopefully be easy to recall if it’s needed”.
Here are some thoughts as to leader key sequences I think I might add to my keymap:
Toggle/Enable/Disable for various features.
- Rather than just having “RGB toggle” or “Haptics Enable”, it’d make sense to provide all of “enable/disable/toggle” as part of leader key sequences.
Dynamic Configuration: increase/decrease, default, min/max.
RGB effects can have different hue, saturation, brightness (value), and the effects can run at different speeds.
It might make sense to provide the ability to increase/decrease these through leader key sequences.Tap Hold configuration supports dynamic configuration of the tapping term.
AutoShift also has dynamic configuration of its tapping term.
-
“caps word” is a neat feature, since it makes it easier to type out a single word in all caps (e.g. variables with uppercase identifiers).
“case modes” generalises this feature, which would aim to make it easier to write out
snake_case
,kebab-case
or./path/case
values.
-
- Again, this is a neat feature, but I’m not quite sure where I’d position it on a layer in a keymap.
Send String and other shortcuts.
From searching for “qmk leader sequence”, most uses of the leader key I could find seem to be use of
SEND_STRING
, or as a convenient way to access modifier-heavy keyboard shortcuts.- Vaguely, the idea of using sequences of taps instead of simultaneous holds of modifier keys resembles one shot mods, or callum-style mods, which I’ve seen mentioned as a friendlier alternative to tap-hold home row mods.
“A New Kind of Modifier”
The docs page for the QMK leader key has the subtitle “a new kind of modifier”.
Most keyboard shortcuts operate with modifier keys that you have to hold down.
e.g. on a web browser, Ctrl+T opens a new tab, Ctrl+Shift+T re-opens a closed tab.
e.g. in Emacs and macOS, shortcuts like Ctrl+a or Ctrl+e navigate in text.
on laptop keyboards, you hold down the Fn key to change the behaviour of some other keys.
- or on small custom keyboards, you might access different keyboard layers by holding down one or more Fn keys.
Modifier keys allow re-using the same set of physical keys for more actions.
Key sequences are another way of mapping functionality onto a set of keys. (Although I’m not sure “modify key behaviour” is the best way to describe).
Implementation Notes: QMK Lead Timeout
I like defining #define LEADER_NO_TIMEOUT
so that when inputting LEAD r r
, I
don’t have to rush after invoking LEAD
.
Setting LEADER_PER_KEY_TIMING
also helps with long sequences. Maybe a
LEADER_TIMEOUT
longer than the default 350ms might help for leader key
sequences same-finger bigrams.
https://docs.qmk.fm/features/leader_key#disabling-initial-timeout
QMK Leader Key Sequence Idiosyncracies
The behaviour of key sequences I’m used to in Doom Emacs is that each sequence
must be unique and not the prefix of another sequence. (e.g. spc f s
saves
file, but spc f
does not map to a command).
QMK’s implementation is a bit more limited: the leader key sequence has a buffer
of up to 5 keycodes; and then once the leader key sequence has timed out, then
the leader_end_user
can be used to check which leader key sequence was used.
An advantage here is this allows for “one sequence can be a prefix of another”.
The disadvantage is it imposes a delay after inputting the sequence. (Because the leader key command behaviour is checked in the callback after the timeout, there’s no way for the leader to end before the timeout).
I suspect an alternative implementation could overcome those limitations (albeit, for different implementation costs).
Implementation Notes: QMK Combo
I have LEAD
as a combo (chord) on my keymap.
In order to still use LEAD
(and other combo keys) with different base layers
(e.g. qwerty instead of dvorak), I define:
#define COMBO_ONLY_FROM_LAYER 0
https://docs.qmk.fm/features/combo#layer-independent-combos