← More about keyboards

Overview

A frequently asked question about QMK is how to change what a key types when it is shifted. For instance, how to make a key with “inverted” shifting such that it types : when pressed normally and ; when pressed shifted. Or how to implement “programmer” layouts having keys that type symbols normally and type the digits when pressed shifted.

Some example custom shifts.

It’s surprisingly tricky to get a custom shift key implemented just right. I’ve seen a lot of proposed solutions, and tried a few things myself. Some subtle gotchas:

Implementation

Here is my solution. It correctly handles key repeating and rolled presses, and I’ve tested that it works predictably in combination with one-shot mods, mod-taps, and Space Cadet Shift. It does not work with Auto Shift.

Step 1: In your keymap.c, define a table of “custom_shift_key_t” structs. Each row defines one key. The keycode is the keycode as it appears in your layout and determines what is typed normally. The shifted_keycode is what you want the key to type when shifted. (See the QMK keycodes documentation for a reference of all QMK keycodes.)

E.g. the first row in my table has a . (KC_DOT) key that types ? (KC_QUES) when pressed shifted.

#include "features/custom_shift_keys.h"

const custom_shift_key_t custom_shift_keys[] = {
  {KC_DOT , KC_QUES}, // Shift . is ?
  {KC_COMM, KC_EXLM}, // Shift , is !
  {KC_MINS, KC_EQL }, // Shift - is =
  {KC_COLN, KC_SCLN}, // Shift : is ;
};
uint8_t NUM_CUSTOM_SHIFT_KEYS =
    sizeof(custom_shift_keys) / sizeof(custom_shift_key_t);

Step 2: Handle custom shift keys from your process_record_user function like so:

bool process_record_user(uint16_t keycode, keyrecord_t* record) {
  if (!process_custom_shift_keys(keycode, record)) { return false; }
  // Your macros ...

  return true;
}

Step 3: Add SRC += custom_shift_keys.c to your rules.mk file.

Step 4: In the directory containing your keymap.c, create a features subdirectory and copy custom_shift_keys.h and custom_shift_keys.c there. This is the meat of the implementation.

custom_shift_keys.h

// Copyright 2021 Google LLC.
// SPDX-License-Identifier: Apache-2.0

#pragma once
#include QMK_KEYBOARD_H

typedef struct {
  uint16_t keycode;
  uint16_t shifted_keycode;
} custom_shift_key_t;

extern const custom_shift_key_t custom_shift_keys[];
extern uint8_t NUM_CUSTOM_SHIFT_KEYS;

bool process_custom_shift_keys(uint16_t keycode, keyrecord_t *record);

custom_shift_keys.c

// Copyright 2021 Google LLC.
// SPDX-License-Identifier: Apache-2.0

#include "custom_shift_keys.h"

static uint16_t custom_shift_keycode(int active_key, bool shifted) {
  const custom_shift_key_t* p = &custom_shift_keys[active_key];
  return shifted ? p->shifted_keycode : p->keycode;
}

bool process_custom_shift_keys(uint16_t keycode, keyrecord_t *record) {
  const uint8_t kNone = 255;
  static uint8_t saved_mods = 0;
  static uint8_t active_key = kNone;
  static bool shifted = false;

  // If a custom key is active, then this event is either releasing it or
  // manipulating another key at the same time. Either way, we release the
  // currently active key.
  if (active_key != kNone) {
    unregister_code16(custom_shift_keycode(active_key, shifted));
    add_mods(saved_mods);  // Restore shift mods that were active on press.
    active_key = kNone;
    saved_mods = 0;
    shifted = false;
  }

  // Search for a custom key with keycode equal to `keycode`.
  for (int i = 0; i < NUM_CUSTOM_SHIFT_KEYS; ++i) {
    if (keycode == custom_shift_keys[i].keycode) {
      if (record->event.pressed) {
        if ((get_mods() | get_oneshot_mods()) & MOD_MASK_SHIFT) {
          // The key is being pressed with shift held. We save the shift
          // mods in `saved_mods`, then delete shift from the mod states.
          saved_mods = get_mods() & MOD_MASK_SHIFT;
          del_mods(MOD_MASK_SHIFT);
          del_oneshot_mods(MOD_MASK_SHIFT);
          shifted = true;
        } else {
          shifted = false;
        }

        active_key = i;  // Remember which custom key is active.
        register_code16(custom_shift_keycode(active_key, shifted));
      }

      return false;
    }
  }

  return true;
}

Explanation

The active_key variable is the index of the custom shift key that is currently pressed or otherwise kNone. Only one custom key can be pressed at a time. Attempting to hold multiple custom shift keys releases all but the last one.

On each press or release of any key, the following two rules are considered:

  1. If active_key is not kNone, we release the currently active custom shift key (unregister_code16). To avoid stuck modifiers, this is always the right thing to do: either the event is releasing the active custom shift key (so we should release it), or it is a rolled press manipulating another key while the active custom shift key is still held (so again, we should release it).

    After releasing the key, we also restore any shift modifiers from saved_mods that were active at the time the custom shift key was pressed.

  2. In the for loop, we check if the current key event is pressing a custom shift key. If so, it becomes the active custom shift key and active_key is set to its index. We check whether a shift mod is held, remember the shift mods in saved_mods, and delete shift from the mod state. We then press the key (register_code16).

So for instance with simple press and release of a custom shift key, rule 2 clears the shift mod state and registers the keycode on press, then rule 1 correspondingly unregistering and restoring the mod state on release.

Or in situations with rolled presses, rule 1 avoids trouble by releasing the active key and restoring the mod state as soon as another key is involved.

← More about keyboards