QMK チートシート

# この記事について

OSSのキーボード用ファームウェアである [QMK](https://github.com/qmk/qmk_firmware) の各機能について、個人的によく使う機能を簡単にまとめた記事となります。
最新の情報は[公式ドキュメント](https://docs.qmk.fm/#/)を参照ください。

# 環境構築とファームウェアのビルド

最初に環境構築を行うためには ['Getting  started'](https://docs.qmk.fm/#/newbs_getting_started) を参考に、リポジトリのクローンと開発環境構築用スクリプトの実行を行います。

```shell
git clone --recurse-submodules https://github.com/qmk/qmk_firmware.git
cd qmk_firmware
util/qmk_install.sh
```

ファームウェアをコンパイルするには、QMKのルートディレクトリから`make <keyboard>:<default>`を実行します。
`keyboards/<keyboard>/keymaps/<keymap>`を実行します。
下記はHHKBのdefaultキーマップをビルドする例です。

```shell
make hhkb:default
```
ビルドが正常に完了すると、qmk_firmware フォルダ直下に、HEXファイルが作成されます。

# VSCode環境でインデント幅が4に固定される
VSCodeにてEditorConfig拡張が有効な環境にあると、 `.editorconfig` の下記設定によりインデント幅が4に固定されるようです。
https://github.com/qmk/qmk_firmware/commit/068571b9febb46edb64663bf6e84ae821a8219a3#diff-1e70daafb475c0ce3fef7d2728279182

このためインデント幅を4以外に設定するには`.editorconfig`を編集するか、EditorConfig拡張を無効化する必要があります。
`.editorconfig`を編集してCソースファイルのみインデントサイズを2にする場合は、下記の設定を追加します。

```:.editorconfig
[{*.h,*.c}]
indent_style = space
indent_size = 2
```

# ファームウェアの書き込み

 ['Flashing Firmware'](https://docs.qmk.fm/#/newbs_flashing)を参考に、QMK Toolbox をするのが一番簡単です。

ファームウェアの書き換えをおこなっても、マイコン内のEEPROMに書き込まれた設定情報(['default_layer_state'](https://docs.qmk.fm/#/keymap?id=keymap-layer-status)、'MAGIC_SWAP_ALT_GUI'など)が残っていると、新しいファームウェアで期待通りの動作とならない場合があります。
EEPROMの設定を初期化したい場合は、マイコンをブート状態にしてから QMK Toolbox のGUI下方の 'Reset EEPROM' を実行するか、[Bootマジックホットキー](https://docs.qmk.fm/#/feature_bootmagic)の 'Clear the EEPROM' 機能でEEPROMを初期化しましょう。

# 新しいキーマップを作成

キーマップをカスタマイズする場合は、まずはベースとするkeymapのフォルダを複製して、自分のユーザー名で保存しましょう。既存のkeymap.cを直接編集すると、`git pull`でQMKをバージョンアップする際にコンフリクトが発生して面倒なのでお勧めしません。
QMKの `./util/new_keymap.sh` を使うと、下記の様に`<keyboard>`のdefaultキーマップをベースに指定の`<user_name>`で複製することができます。

```:shell
./util/new_keymap.sh <keyboard> <user_name>
```

# キーマップとレイヤー

['Keymap Overview'](https://docs.qmk.fm/#/keymap)
QMKではキー配列を定義した行列を、レイヤーとして重ねたデータを`keymaps`の3次元配列として定義します。

QMKは様々な機能がありますが、最小構成としてはkeymap.cへ`keymaps`配列定義さえすればコンパイルすることができます。
下記に [HHKB JP キーマップ](https://github.com/qmk/qmk_firmware/tree/master/keyboards/hhkb/keymaps/jp) をベースに作成したキーマップファイルを示します。

```C:keymap.c
#include QMK_KEYBOARD_H

// keymap layer number
enum keymap_layer {
  KL_BASE,
  KL_FN,
};

// custom keycode
#define MO_FN   MO(KL_FN)

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
  [KL_BASE] = LAYOUT_JP(
     KC_ESC,    KC_1,    KC_2,    KC_3,    KC_4,    KC_5,    KC_6,    KC_7,    KC_8,    KC_9,    KC_0, KC_MINS,  KC_EQL, KC_JYEN, KC_BSPC,
     KC_TAB,    KC_Q,    KC_W,    KC_E,    KC_R,    KC_T,    KC_Y,    KC_U,    KC_I,    KC_O,    KC_P, KC_LBRC, KC_RBRC,
    KC_LCTL,    KC_A,    KC_S,    KC_D,    KC_F,    KC_G,    KC_H,    KC_J,    KC_K,    KC_L, KC_SCLN, KC_QUOT, KC_BSLS,  KC_ENT,
    KC_LSFT,    KC_Z,    KC_X,    KC_C,    KC_V,    KC_B,    KC_N,    KC_M, KC_COMM,  KC_DOT, KC_SLSH,   KC_RO,   KC_UP, KC_RSFT,
      MO_FN, KC_ZKHK, KC_LGUI, KC_LALT, KC_MHEN,      KC_SPC     , KC_HENK, KC_KANA, KC_RALT,   MO_FN, KC_LEFT, KC_DOWN, KC_RGHT
  ),

  [KL_FN] = LAYOUT_JP(
     KC_PWR,   KC_F1,   KC_F2,   KC_F3,   KC_F4,   KC_F5,   KC_F6,   KC_F7,   KC_F8,   KC_F9,  KC_F10,  KC_F11,  KC_F12,  KC_INS, KC_DEL,
    KC_CAPS, _______, _______, _______, _______, _______, _______, _______, KC_PSCR, KC_SLCK, KC_PAUS,   KC_UP, _______,
    _______, KC_VOLD, KC_VOLU, KC_MUTE, KC_EJCT, _______, KC_PAST, KC_PSLS, KC_HOME, KC_PGUP, KC_LEFT, KC_RGHT, _______, KC_PENT,
    _______, _______, _______, _______, _______, _______, KC_PPLS, KC_PMNS,  KC_END, KC_PGDN, KC_DOWN, _______, _______, _______,
    _______, _______, _______, _______, _______,     _______     , _______, _______, _______, _______, _______, _______, _______
  ),
};

```

各レイヤの有効状態は`default_layer_state` および `layer_state`変数の有効なビットで指定されます。

`default_layer_state`は`DF(layer)`キーコードによって切り替えられ、ベースとして常に有効なレイヤーとなります。

`layer_state`は`MO(layer)`などのキーコードによって切り替えられ、特定のキーを押している間、一時的に有効としたいレイヤーなどを設定します。
上記の例ではFNキー(`MO_FN`)が押されている間だけ`KL_FN`レイヤーが有効化されます。

レイヤーを切り替えるためのマクロは他にもいくつの種類が用意されています。詳細は公式マニュアルの['Switching and Toggling Layers'](https://docs.qmk.fm/#/feature_advanced_keycodes?id=switching-and-toggling-layers)を参照してください。

`KL_FN`に定義されたキーコードで`_______`は、`KC_TRANS`の別名で、このキーが押された場合は、レイヤーを透過して、より下位の有効なレイヤーのキーコードが選択されます。この例の場合は、`KL_FN`の次に有効なレイヤーは`KL_BASE`のみなので`_______`の押されたキーはすべて`KL_BASE`のキーが入力されます。

# 一つのキーに修飾キーとキーコード入力を割り当てる

['Mod-Tap'](https://docs.qmk.fm/#/feature_advanced_keycodes?id=mod-tap)機能を使用すると、一つのキーに通常のキーコード入力と、修飾キー機能の両方を割り当てることができます。
この手の機能で定番なカスタムとして、スペースバーにシフト機能も追加したSandS(Space and Shift)と呼ばれる機能を実装してみます。
これは単独でタップする場合はスペース入力、スペースバーを押しながらほかのキーを入力した場合はShift修飾キーとして動作させるキーとなります。
`MT`を使ってSandSキーを定義した例が下記になります。

```C
#define MT_SS       MT(MOD_LSFT, KC_SPACE)

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
  [KL_BASE] = LAYOUT_JP(
     ...
      MO_FN, KC_ZKHK, KC_LGUI, KC_LALT, KC_MHEN,      MT_SS     , KC_HENK, KC_KANA, KC_RALT,   MO_FN, KC_LEFT, KC_DOWN, KC_RGHT
  ),
};
```

Mod-Tapは通常にホールドした場合は、指定した修飾キーとして動作しますが、1回タッピングしてすぐに2回目のキー入力をホールドした場合は、修飾キーではなく、キーコード側をホールドした動作となり、キーコードのオートリピートが有効になります。
ただし、`TAPPING_FORCE_HOLD`が有効な場合は、上記のキーコードのホールドは行われません。

タッピングと判定されるまでのタイムアウト時間は`TAPPING_TERM`で設定された時間が適用されます。（デフォルトは200ms）

Mod-Tapが有効だとゲームなどでスペースキーを使用する際に不便なため、通常の動作と切り替えて使用したい場合は、SandSキー専用のレイヤー(`KL_SANDS`)を作って`default_layer_state`の切り替え(`DF(KL_SANDS)`)によって使用することもできます。

```C
#include QMK_KEYBOARD_H

// keymap layer number
enum keymap_layer {
  KL_BASE,
  KL_FN,
  KL_SANDS,
};

// custom keycode
#define MO_FN       MO(KL_FN)
#define DF_SAS      DF(KL_SANDS)
#define MT_SS       MT(MOD_LSFT, KC_SPACE)

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
  [KL_BASE] = LAYOUT_JP(
     ...
      MO_FN, KC_ZKHK, KC_LGUI, KC_LALT, KC_MHEN,      KC_SPC     , KC_HENK, KC_KANA, KC_RALT,   MO_FN, KC_LEFT, KC_DOWN, KC_RGHT
  ),

  [KL_FN] = LAYOUT_JP(
     ...
    _______, _______, _______, _______, _______,      DF_SAS     , _______, _______, _______, _______, _______, _______, _______
  ),

  [KL_SANDS] = LAYOUT_JP(
     ...
    _______, _______, _______, _______, _______,       MT_SS     , _______, _______, _______, _______, _______, _______, _______
  ),
};

```

# 一つのキーにレイヤー切り替えとキーコード入力を割り当てる

`LT(layer, kc)`を使用すると、タップした場合はキーコードを入力し、ホールドした場合はレイヤー切り替えを行うキーを設定できます。
例えば、カーソル移動キーを配置したレイヤー(`KL_CURSOR`)を作成し、「無変換」キーのホールドでレイヤー切り替え、タップで`KC_MHEN`の入力を行うといった動作を実現することができます。

```C
#include QMK_KEYBOARD_H

// keymap layer number
enum keymap_layer {
  KL_BASE,
  KL_CURSOR,
};

// custom keycode
#define LT_MHEN     LT(KL_CURSOR, KC_MHEN)

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
  [KL_BASE] = LAYOUT_JP(
      ...
      MO_FN, KC_ZKHK, KC_LGUI, KC_LALT, LT_MHEN, ...
  ),

  [KL_CURSOR] = LAYOUT_JP(
    ...
    _______, XXXXXXX, XXXXXXX,   KC_UP, XXXXXXX, ...
    _______, XXXXXXX, KC_LEFT, KC_DOWN, KC_RGHT, ...
    _______, _______, _______, _______, _______, ...
  ),
};

```

`LT_MHEN` と同じ位置にある移動先レイヤーのキーは、キーを解放した際にちゃんと`LT`の解放イベントを取得できるように、他のキーを設定せず`KC_TRANS`(`_______`)で透過させておきます。

Mod-Tap と同様に、`LT(layer, kc)`もダブルタップした後にホールドするとレイヤー移動ではなく、キーコードのオートリピートがを行う仕様となります。
しかしながら、通常`LT()`によるダブルタップのオートリピートは誤操作の原因となりやすいため無効化したいところです。無効化する方法としては下記の二つの方法があります。
一つ目は、`TAPPING_FORCE_HOLD`を有効化する方法。ただし、この方法では Mod-Tap のオートリピート機能も無効化されます。
二つ目は、`LT`を使わずに、['process_record_user'](https://docs.qmk.fm/#/feature_macros?id=the-new-way-send_string-amp-process_record_user)にてタッピング判定を行う方法です。下記にこの例を示します。

`process_record_user`関数を使用すると、特定キーコードの動作をユーザー定義で上書きすることができるため、これを使ってレイヤー切り替えと、タッピング判定を行います。[^1]

```C
#include QMK_KEYBOARD_H

// keymap layer number
enum keymap_layer {
  KL_BASE,
  KL_CURSOR,
};

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
  [KL_BASE] = LAYOUT_JP(
    ...
      MO_FN, KC_ZKHK, KC_LGUI, KC_LALT, KC_MHEN,      KC_SPC     , KC_HENK, KC_KANA, KC_RALT,   MO_FN, KC_LEFT, KC_DOWN, KC_RGHT
  ),

  [KL_CURSOR] = LAYOUT_JP(
    ...
  ),
};

// result of process_record_user
#define PROCESS_OVERRIDE_BEHAVIOR   (false)
#define PROCESS_USUAL_BEHAVIOR      (true)

bool process_record_user(uint16_t keycode, keyrecord_t *record) 
{
  static uint16_t mem_keycode;
  uint16_t prev_keycode = mem_keycode;
  bool is_tapped = ((!record->event.pressed) && (keycode == prev_keycode));
  mem_keycode = keycode;

  switch (keycode) {
    case KC_MHEN: {
      if (record->event.pressed) {
        layer_on(KL_CURSOR);
      }
      else {
        layer_off(KL_CURSOR);
        if (is_tapped) {
          tap_code(keycode);
        }
      }
      return PROCESS_OVERRIDE_BEHAVIOR;
    } break;

    default: {
    } break;
  }

  return PROCESS_USUAL_BEHAVIOR;
}

```

`process_record_user`はキーコードが押されたとき、離されたときにそれぞれ呼び出されます。返り値として`false`を返すと、キー動作を上書きしてデフォルトのキーコード送信をキャンセルします。返り値に`true`を返すと、QMKによる通常のキーコード送信を行います。
このため、それぞれの返り値を`PROCESS_OVERRIDE_BEHAVIOR`, `PROCESS_USUAL_BEHAVIOR`としてdefine定義しておきます。

引数の`keycode`, `record->event.pressed`によって関数実行時のキーコードと押下状態を取得できるので、`is_tapped`にキーが単独でタッピングされたかを判定した結果を格納しておきます。

switch文へ`keycode`が`KC_MHEN`だった場合の処理を記述します。
 - キーを押下した場合は`KL_CURSOR`レイヤーを有効化(`layer_on`)、キーを離した場合は`KL_CURSOR`レイヤーを無効化(`layer_off`)させます。
 - キーがタッピングで離された場合は、元の`keycode`を`tap_code`で送信します。

[^1]: okapies氏のブログ記事を参考にさせていただきました(http://okapies.hateblo.jp/entry/2019/02/02/133953)

#キーボードLEDの取得

['LED Control'](https://docs.qmk.fm/#/custom_quantum_functions?id=led-control) より5つのLEDを取得できるという仕様ですが実用上は下記三つを使用できるでしょう。

```C
// LED状態が変化時に呼ばれる
void led_set_user(uint8_t usb_led) 
{
  if (IS_HOST_LED_ON(USB_LED_NUM_LOCK)) {...}
  if (IS_HOST_LED_ON(USB_LED_CAPS_LOCK)) {...}
  if (IS_HOST_LED_ON(USB_LED_SCROLL_LOCK)) {...}
}
```

`process_record_user`にて空白文字入力を監視して、CapsLockを自動的に解除するということができます。

```C
bool process_record_user(uint16_t keycode, keyrecord_t *record)
{
  if (IS_HOST_LED_ON(USB_LED_CAPS_LOCK)){
    switch(keycode){
    case KC_ENTER:
    case KC_TAB:
    case KC_SPACE:
      //register_mods(MOD_MASK_SHIFT);  // JISキーボードの場合はShiftが必要
      tap_code(KC_CAPS);
      //unregister_mods(MOD_MASK_SHIFT);
      break;
    }
  }

  return true;
}

```

# コンボキー

['Combos'](https://docs.qmk.fm/#/feature_combo?id=combos) 機能を使うと、キーの同時押しに別のキーコード送信を割り当てることができます。
機能を有効化するには`rules.mk`へ`COMBO_ENABLE=yes`を追加します。

```Make:rules.mk
COMBO_ENABLE      = yes
```

登録するコンボキーの数を`COMBO_COUNT`で定義し、`key_combos`配列の要素数として定義します。
同時押し判定のタイムアウトは`TAPPING_TERM`がデフォルト値ですが、`COMBO_TERM`を定義すると、コンボキー専用のタイムアウト値として定義されます。

```C:config.h
#define COMBO_COUNT         9
#define COMBO_TERM          500
```

ファンクションキーF11～F19の入力を、F10＋F1～F9のコンビネーションとして割り当てた例を下記に記します。

```C:keymap.c
typedef const uint16_t comb_keys_t[];
static PROGMEM comb_keys_t
  comb_keys_F11 = {KC_F10, KC_F1, COMBO_END}, comb_keys_F12 = {KC_F10, KC_F2, COMBO_END},
  comb_keys_F13 = {KC_F10, KC_F3, COMBO_END}, comb_keys_F14 = {KC_F10, KC_F4, COMBO_END},
  comb_keys_F15 = {KC_F10, KC_F5, COMBO_END}, comb_keys_F16 = {KC_F10, KC_F6, COMBO_END},
  comb_keys_F17 = {KC_F10, KC_F7, COMBO_END}, comb_keys_F18 = {KC_F10, KC_F8, COMBO_END},
  comb_keys_F19 = {KC_F10, KC_F9, COMBO_END};
combo_t key_combos[COMBO_COUNT] = {
  COMBO( comb_keys_F11, KC_F11 ),  COMBO( comb_keys_F12, KC_F12 ),
  COMBO( comb_keys_F13, KC_F13 ),  COMBO( comb_keys_F14, KC_F14 ),
  COMBO( comb_keys_F15, KC_F15 ),  COMBO( comb_keys_F16, KC_F16 ),
  COMBO( comb_keys_F17, KC_F17 ),  COMBO( comb_keys_F18, KC_F18 ),
  COMBO( comb_keys_F19, KC_F19 ),
};
```