Using the QMK Leader Key for Fancy Keyboard Functionality

Posted on June 16, 2024 by Richard Goulter
Tags: ,

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)
        tap_code(KC_CAPS);
#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
        current_os = _OS_WIN;
    } else if (leader_sequence_two_keys(KC_O, KC_L)) { // mnemonic: OS Linux
        current_os = _OS_LINUX;
    } else if (leader_sequence_two_keys(KC_O, KC_M)) { // mnemonic: OS MacOS
        current_os = _OS_MACOS;
#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_mode_noeeprom(RGB_MATRIX_JELLYBEAN_RAINDROPS);
    } 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:
                tap_code16(CODE16_LINUX_DESKTOP_LOCK);
                break;
            case _OS_MACOS:
                tap_code16(CODE16_MACOS_DESKTOP_LOCK);
                break;
            case _OS_WIN:
                tap_code16(CODE16_WIN_DESKTOP_LOCK);
                break;
        }
    }
    leader_end_keymap();
}

This code implements the leader_end_user callback, which handles how QMK leader key sequences behave for a keymap.

Example sequences:

“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:

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:

“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.

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


Newer post Older post