QMK Basics

QMK Basics: Tap and hold actions: Tap into your modifiers

New to QMK, or in for a refresher? In this series of tutorials on QMK Basics, you’ll be brought up to speed on a number of features that help you customize your keyboard just the way you want it.

When fitting in your keymap onto a smaller keyboard than you’re used to, finding enough keys can be difficult. Even if you’ve got plenty of keys at your disposal, making some often used keys more accessible can provide you with more comfort and efficiency.

The QMK framework offers some creative ways to use your modifier keys so that they’re not just modifiers, but also a key of your choosing.

When is the last time you tapped a modifier key like Control, Shift or Alt? Modifier keys are often used in conjunction with another key, so you’ll often hold the key instead of tapping it. With the various keycodes I’ll describe below, you can put those modifier keys to use.

Mod-tap

To quote the documentation:

The Mod-Tap key MT(mod, kc) acts like a modifier when held, and like a regular keycode when tapped. In other words, you can have a key that sends Escape when you tap it, but functions as a Control or Shift key when you hold it down.

Advanced Keycodes documentation, section Mod-Tap

There are many shortcuts available for the MT keycode that are configured with the right modifier, so you’ll only need to pick the keycode you want. Some of those keycodes are LCTL_T(kc), which is Left Control when held and sends kc when tapped.

Limitations of Mod-tap

Shifted keycodes won’t work. They are keycodes that send, for example, a # by themselves instead of having to press SHIFT + 3. Mod-tap ignores modifiers sent with the keycode, so MT(MOD_LSFT,KC_HASH) would send a 3 when pressed instead of a #.

The keycodes MT(mod, kc) and LT(layer, kc) only support basic keycodes.

QMK collaborator Drashna in response to the question “Programming mod tap keys for shifted keycodes“, 2018-06-05.

Here is some background to this issue: a pull request by dpapavas and this thread by Fin_Complete on Reddit.

There also isn’t a way to execute custom functions with Mod-Tap. The Mod-tap function directly registers the keycode with register_code, and doesn’t call process_record_user.

A workaround for mod-tap

MechMerlin shared a very interesting code snippet in which he emulates the mod-tap behavior, adding the ability to perform custom functions. He makes various videos on keyboards and adventures with them at his YouTube channel.

We can emulate a mod-tap action by manually keeping track of whether a key was tapped or held in the process_record_user function. In this example, let’s send the shifted key # manually, something that mod-tap can’t do for us.

  1. You can add #define TAPPING_TERM 200 to your config.h. Any tap that’s shorter than 200ms will be a tap, anything longer will be held. You can pick a duration of your choice, 200 is the default.
  2. Define a custom keycode by adding the custom_keycodes array. Our keycode will be named MY_HASH.
  3. Place the custom keycode somewhere in your keymap. In this example, we only have a single key, so deciding where to put it is easy.
  4. Now to edit the process_record_user function. First, add a variable to hold a timer value in. We make it static so the value is saved over time: normally, when a function like this is over, all the variables are emptied. This time, we want to remember at what time MY_HASH was pressed, so static remembers the value for us.
  5. Add the below switch statement. You may already have such a switch statement, if so, you can place a new case in the existing statement.
  6. When MY_HASH is pressed, we log the time to the my_hash_timer variable. We also register the keycode KC_LCTL, you can replace this with your held modifier of choice. This means that the modifier is in effect the moment you press the key.
  7. When MY_HASH is released, we unregister KC_LCTL, so the modifier won’t be active anymore. Then, we check the timer: if it’s smaller than TAPPING_TERM, it’s a tap action and we will send the character we want: #. Instead of using TAPPING_TERM, you can enter a duration in milliseconds, anything below it will be a tap, anything more will be held.
#include QMK_KEYBOARD_H

enum custom_keycodes {
  MY_HASH = SAFE_RANGE
};

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
  [0] = LAYOUT( /* Base */
    MY_HASH \
  ),
};

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
  static uint16_t my_hash_timer;

  switch (keycode) {
    case MY_HASH:
      if(record->event.pressed) {
        my_hash_timer = timer_read();
        register_code(KC_LCTL); // Change the key to be held here
      } else {
        unregister_code(KC_LCTL); // Change the key that was held here, too!
        if (timer_elapsed(my_hash_timer) < TAPPING_TERM) {
          SEND_STRING("#"); // Change the character(s) to be sent on tap here
        }
      }
      return false; // We handled this keypress
  }
  return true; // We didn't handle other keypresses
}

Space cadet

QMK has two features that are very much alike Mod-tap: Space Cadet Shift and Space Cadet Shift Enter. These features are much like Mod-tap, but have the ability to send specific shifted keycodes.

Space Cadet Shift sends ( when the left shift is tapped, and ) when the right shift is tapped. When the shift keys are held, they’re simply shift. The Space Cadet shift keys have the keycodes KC_LSPO and KC_RSPC for left and right shift respectively.

Space Cadet Shift Enter acts as a shift key when held and sends Enter when tapped. Its keycode is KC_SFTENT.

Both Space Cadet Shift and Space Cadet Shift Enter conflict with the Command feature, since they all use the same timer. If you decide to use the keycodes for Space Cadet, disable Command by adding COMMAND_ENABLE = no to your rules.mk.

Layer keys

Like MT(mod, kc), there exists a key to enter a layer when held, but which sends a keycode when tapped: LT(layer, kc). LT(layer, kc) activates layer momentarily when pressed, sends the given keycode when pressed.

It has the same limitations as MT(mod, kc): it only supports basic keycodes, so no shifted keys and no user functions.

Customizing the tapping term

Under Behaviors that can be configured, there’s an interesting entry: #define TAPPING_TERM 200. The tapping term defines for how long in milliseconds you need to hold a key before the tap becomes a hold.

You can add the above line to your config.h.

Examples

There are many ways people use Mod-tap and Space Cadet. Some of those ways aren’t as obvious when starting out, but you may end up liking them in your keymap. Here are some examples of how people use tap and hold actions.

Home-row modifiers

In the thread What are your favorite QMK hacks?, user Canatella explains how to use mod-tap for home-row modifier keys. Canatella didn’t post a keymap, but here’s a possible application:

A sample layout for the Atreus. When the home row keys are held, they output modifiers or change the layer. When tapped, they output the usual characters.

You can accomplish the above with the following keycodes, from left to right:

  • MOD_LGUI(KC_A) for the left Windows/Command/Meta key;
  • MOD_LALT(KC_S) for the left alt key;
  • MOD_LCTL(KC_D) for the left control key;
  • MOD_LSFT(KC_F) for the left shift key;
  • LT(LOWER, KC_G) for the lower layer key.

Check out the Mod-Tap documentation for more keycodes.

Doubling up on thumb-keys

Kauyon Kais has shared his custom keyboard build, which makes use of a number of featurs, including tap and hold modifiers. In his keymap, you can find the full source code.

Kauyon Kais FNH36 keymap, shared through Keyboard Layout Editor.

Here are some of the keycodes used to make the tap and hold keys work:

  • MT(MOD_LSFT, KC_BSPC) for backspace on tap and shift on hold;
  • LT(_FUN, KC_ENT) for enter on tap and switching to the _FUN layer on hold;
  • LT(_SYB, KC_SPC) for space on tap and switching to the _SYB layer on hold.

Reusing some often used keys like shift, enter and space is a very clever way to regain some key estate. It also places more emphasis on the thumbs. Thumbs are generally stronger than the outer fingers, which should provide more comfort.

Kauyon Kais’ custom build keyboard, the Fingers Not Hands 36.

Conclusion

You can put your modifier and layer keys to use by also assigning a tap action to them. This also works the other way around: You can make ordinary keys function as a modifier or layer key when held.

Just know that only basic keycodes are supported for tap and hold keys. That’s still a rather long list, so there are plenty of possibilities to explore!

Further reading

There are more ways than #define TAPPING_TERM 200 to configure the way QMK handles your tap and hold keys:

  • Permissive hold: Makes tap and hold keys work better for fast typists, or when you’ve set TAPPING_TERM to a high value.
  • Ignore Mod Tap Interrupt: Alters the behaviour of tap and hold keys in a similar but different way than Permissive hold.
  • Tapping Force Hold: Allows you to have the given keycode repeat when tapping and then holding the modifier key for a second time.
  • Retro Tapping: Sends the keycode instead of the modifier when you have held the modifier without pressing another key.
New to QMK, or in for a refresher? In this series of tutorials on QMK Basics, you’ll be brought up to speed on a number of features that help you customize your keyboard just the way you want it.