OS のキーボード配列が JIS キーボードの時の QMK Firmware の設定について

前置き

QMK Firmware は US 配列を前提にしたファームウェアなので、OS のキーボード配列を US キーボードにして US キーキャップを使う場合は、言語関係の問題は生じません。

しかし、OS のキーボード配列を JIS キーボードに設定すると、途端に言語関係の問題が生じます。対応方法は、US キーキャップを使う場合と JIS キーキャップを使う場合でそれぞれ違いますので、それぞれのパターンについて紹介したいと思います。

tl,dl

JIS キーキャップを使う場合
qmk_firmware/keymap_japanese.h at master · qmk/qmk_firmwarekeymap.c の先頭で #include"keymap_jp.h" して読み込んだ上で、KC_* の代わりに JP_* キーを使います。
US キーキャップを使う場合
この場合の方法は、次の2つです。

方法1

koktoh/jtu_custom_keycodes: Keycodes to use jis keycode in us keymap を使い、個別に対応が必要なキーを JU_* キーに置き換えます。
ただし、!( のように Shift キーを押しながら入力する記号類を直接入力する場合(例えば、Raise レイヤーで Q を押したら ! が入力されるといったものです。)、qmk_firmware/keymap_japanese.h at master · qmk/qmk_firmware を使って JP_EXLM 等の形で指定する必要があります。1

方法2

Version 0.14.0 で導入された Key Overrides 機能と JP_* キー を組み合わせてキーの挙動を変更するという方法です。

QMK Breaking Changes - 2021 August 28 Changelog

一見すると回りくどい方法ですが、JU_* キーはキーリピートが効かないのに対し、この方法で設定したキーはキーリピートが効きますので、普通のキーの挙動に可能な限り近づけることができます。

解説

追加ライブラリまたは設定が必要な理由

keymap.cKC_A と指定したキーをタイプした時、キーボードから PC に送信されるデータは A という文字に応じたキーコードです。そして、OS は受け取ったキーコードに応じた文字をアプリケーションに渡します。2

問題は、このキーコードと文字の対応関係が言語によって異なっており、例えば、[ に当たる KC_LBRC のキーコードを送信しても、OS のキーボード配列が JIS になっていると OSは = を返してしまいます。

このような取り違えが生じるキーがいくつかありますので、OS のキーボード配列を JIS にした状態でキーキャップの印字通りに入力したい場合、何らかの変換処理が必要となります。

上記で紹介した qmk_firmware/keymap_japanese.h at master · qmk/qmk_firmwarekoktoh/jtu_custom_keycodes: Keycodes to use jis keycode in us keymap を使う方法は、ライブラリに変換処理を任せる方法で、Key Overrides を使う方法は自力で変換処理を行うという方法です。

実装

ここからは具体的な実装を紹介していきます。なお、OS のキーボード配列が JIS 配列になっていることが前提です。

JIS キーキャップを使う場合

qmk_firmware/keymap_japanese.h at master · qmk/qmk_firmwarekeymap.c の先頭で #include"keymap_jp.h" して読み込んで、KC_* の代わりに JP_* キーを使います。

以下のキーマップは、OS のキーボード配列とキーキャップを JIS にする前提で設計された 自作キーボードキット『JISplit89』ビルドガイド - 自作キーボード温泉街の歩き方 のデフォルトキーマップですが、JIS キーキャップ独自のキー(下の図の赤枠のキー)のキーコードに JP_* キーコードを指定しています。

qmk_firmware/keyboards/salicylic_acid3/jisplit89 at master · qmk/qmk_firmware より
 1[_QWERTY] = LAYOUT(
 2   //,-----------------------------------------------------|   |--------------------------------------------------------------------------------.
 3        KC_ESC,   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_PSCR,
 4   //|--------+--------+--------+--------+--------+--------|   |--------+--------+--------+--------+--------+--------+--------+--------+--------|
 5 LT(_ADJUST,KC_ZKHK),KC_1,  KC_2,    KC_3,    KC_4,    KC_5,        KC_6,    KC_7,    KC_8,    KC_9,    KC_0, JP_MINS, JP_CIRC,  JP_YEN, KC_BSPC,  KC_DEL,
 6   //|--------+--------+--------+--------+--------+--------|   |--------+--------+--------+--------+--------+--------+--------+--------+--------|
 7        KC_TAB,    KC_Q,    KC_W,    KC_E,    KC_R,    KC_T,        KC_Y,    KC_U,    KC_I,    KC_O,    KC_P,   JP_AT, JP_LBRC,  KC_ENT, KC_HOME,
 8   //|--------+--------+--------+--------+--------+--------|   |--------+--------+--------+--------+--------+--------+--------+--------+--------|
 9       KC_CAPS,    KC_A,    KC_S,    KC_D,    KC_F,    KC_G,        KC_H,    KC_J,    KC_K,    KC_L, JP_SCLN, JP_COLN, JP_RBRC,           KC_END,
10   //|--------+--------+--------+--------+--------+--------|   |--------+--------+--------+--------+--------+--------+--------+--------+--------|
11       KC_LSFT,    KC_Z,    KC_X,    KC_C,    KC_V,    KC_B,        KC_N,    KC_M, KC_COMM,  KC_DOT, KC_SLSH, JP_BSLS, KC_PGDN,   KC_UP, KC_PGUP,
12   //|--------+--------+--------+--------+--------+--------|   |--------+--------+--------+--------+--------+--------+--------+--------+--------|
13      KC_LCTRL, KC_LGUI, KC_LALT, KC_MHEN,  KC_SPC,  KC_SPC,      KC_SPC,  KC_SPC, KC_HENK, KC_KANA,  KC_APP,          KC_LEFT, KC_DOWN,KC_RIGHT
14   //`-----------------------------------------------------|   |--------------------------------------------------------------------------------'
15),

このように、JIS キーキャップと US キーキャップで印字が異なるキーについて、JP_* キーコードを使用して違いを吸収しています。

US キーキャップを使う場合

JU_* を使う場合

koktoh/jtu_custom_keycodes: Keycodes to use jis keycode in us keymap を使い、;' のように Shift キーを使わなくてもキーキャップの印字通りに入力されないキーや、Shift キーを使った場合に印字通りに入力されない 26 について、JU_* キーを使って違いを吸収しています。

Keyboard Layout Editor で作成
1[_QWERTY] = LAYOUT(
2  KC_Q,  KC_W,  KC_E,  KC_R,  KC_T,  KC_Y,  KC_U,    KC_I,   KC_O,    KC_P,    JU_GRV,  \
3  KC_A,  KC_S,  KC_D,  KC_F,  KC_G,  KC_H,  KC_J,    KC_K,   KC_L,    JU_SCLN, JU_QUOT, \
4),
5[_LOWER] = LAYOUT( \
6  KC_1,  JU_2,  KC_3,  KC_4,  KC_5,  JU_6,  JU_7,    JU_8,   JU_9,    JU_0,    JU_BSLS, \
7  KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, JU_MINS, JU_EQL, JU_LBRC, JU_RBRC, _______,   \
8),

なお、JU_* キーを使う場合、ライブラリとして読み込む方法とコードをコピペして使う方法の2つがあります。jtu_custom_keycodes/default/README.md に手順が記載されていますので、そちらを確認して実装します。

Key Overrides を使う方法

Key Overrides の詳細は、QMK Firmware の公式ドキュメントの暫定日本語訳 で確認できますが、この機能を簡単に説明すると、修飾キーとキーの組み合わせを上書きして別のキーを送信できるようにするというものです。PC に送信するキーコード自体を変更できますので、US と JIS の違いを吸収するのにうってつけの機能となります。

例えば、OS のキーボード配列が JIS だと Shift + 2" と解釈されますが、Key Overrides を使うと Shift + 2JP_AT に変換して PC に送信できますので、Shift + 2 を押して @ を表示させるということができるようになります。このように修飾キーとキーの組み合わせを上書きすることで、US キーキャップの印字通りの入力を実現できるようになります。

ここから具体的なコードを示しますが、まず、Key Overrides を使うには rules.mkKEY_OVERRIDE_ENABLE = yes を追加する必要があります。

1KEY_OVERRIDE_ENABLE = yes

次に、keymap.c に Key Overrides の対象となるキーの組み合わせなどを定義した key_override_t 構造体を初期化します。

1// `shift + '` を上書きして `"` を送信する
2const key_override_t JP_QUOT_key_override =
3  ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, JP_QUOT, S(KC_2), ~0, MOD_MASK_CAG);
4
5// `shift + 2` を上書きして `@` を送信する
6const key_override_t KC_2_key_override = 
7  ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, KC_2, KC_LBRC, ~0, MOD_MASK_CAG);

初期化で使っている ko_make_with_layers_and_negmods 初期化子は、最もオプションが多い初期化子で、Key Overrides を使うレイヤーや押してはいけない修飾キーを指定できます。1つ目の設定に指定している引数の内容は次のとおりです。

MOD_MASK_SHIFT
Key Overrides を使うために押す必要がある修飾キーに Shift キーを指定しています。キーの指定は、MOD_BIT(KC_RSFT) のように MOD_BIT() を使うか、モッドマスクMOD_MASK_CTRL 等)を使って行います。
JP_QUOT
Key Overrides を使うために押す必要があるキーに ' を指定しています。なお、KC_QUOT ではなく JP_QUOT としているのは、キーマップで KC_QUOT の代わりに JP_QUOT を指定しているためです。
S(KC_2)
" を送信するためのキーコードを指定しています。OS のキーボード配列が JIS の場合に " を送信するには S(KC_2) と指定する必要がありますが、JP_DQUO と指定しても OK です。ここは好みで決めます。
~0
全てのレイヤーで Key Overrides を使うという指定です。レイヤー i でこのオーバーライドを使うには、i 番目のビット (1 << i) を設定します。
MOD_MASK_CAG
Key Overrides を使うときに押していてはいけない修飾キーに controlwindowsalt キーを指定しています。なお、押していてはいけない修飾キーを指定する場合、shiftcontrolwindowsalt の4種類の修飾キーを「押す必要がある」と「押していてはいけない」のどちらかに指定する必要があります。指定漏れや重複指定があると Key Overrides が無効化されます。

それから、key_overrides 配列の要素に key_override_t 構造体を指定します。key_overrides 配列は NULL で終了し、key_override_t 値へのポインタ (const key_override_t **) を含みます。

1const key_override_t **key_overrides = (const key_override_t *[]){
2    &JP_QUOT_key_override,
3    &KC_2_key_override,
4    NULL // Null terminate the array of overrides!
5};

あとは、これを必要なキーの数だけ繰り返していきます。私が行った設定をキーマップと合わせて以下に示します。

 1// 全レイヤーで Key Overrides を有効化
 2// Ctrl, Win, Altキーを押していたら Key Overrides を発動しない
 3const key_override_t JP_GRV_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, JP_GRV, S(KC_EQL), ~0, MOD_MASK_CAG);
 4const key_override_t KC_SCLN_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, KC_SCLN, KC_QUOT, ~0, MOD_MASK_CAG);
 5const key_override_t JP_QUOT_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, JP_QUOT, S(KC_2), ~0, MOD_MASK_CAG);
 6const key_override_t KC_2_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, KC_2, KC_LBRC, ~0, MOD_MASK_CAG);
 7const key_override_t KC_6_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, KC_6, KC_EQL, ~0, MOD_MASK_CAG);
 8const key_override_t KC_7_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, KC_7, S(KC_6), ~0, MOD_MASK_CAG);
 9const key_override_t KC_8_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, KC_8, S(KC_QUOT), ~0, MOD_MASK_CAG);
10const key_override_t KC_9_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, KC_9, S(KC_8), ~0, MOD_MASK_CAG);
11const key_override_t KC_0_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, KC_0, S(KC_9), ~0, MOD_MASK_CAG);
12const key_override_t JP_BSLS_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, JP_BSLS, S(KC_INT3), ~0, MOD_MASK_CAG);
13const key_override_t KC_MINS_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, KC_MINS, S(KC_INT1), ~0, MOD_MASK_CAG);
14const key_override_t JP_EQL_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, JP_EQL, S(KC_SCLN), ~0, MOD_MASK_CAG);
15const key_override_t JP_LBRC_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, JP_LBRC, S(KC_RBRC), ~0, MOD_MASK_CAG);
16const key_override_t JP_RBRC_key_override = ko_make_with_layers_and_negmods(MOD_MASK_SHIFT, JP_RBRC, S(KC_NUHS), ~0, MOD_MASK_CAG);
17
18const key_override_t **key_overrides = (const key_override_t *[]){
19    &JP_GRV_key_override,
20    &KC_SCLN_key_override,
21    &JP_QUOT_key_override,
22    &KC_2_key_override,
23    &KC_6_key_override,
24    &KC_7_key_override,
25    &KC_8_key_override,
26    &KC_9_key_override,
27    &KC_0_key_override,
28    &JP_BSLS_key_override,
29    &KC_MINS_key_override,
30    &JP_EQL_key_override,
31    &JP_LBRC_key_override,
32    &JP_RBRC_key_override,
33    NULL // Null terminate the array of overrides!
34};
35// key override setting end
36
37/* keymap */
38const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { \
39  [_QWERTY] = LAYOUT(
40      KC_TAB,   KC_Q,   KC_W,    KC_E,    KC_R,  KC_T,         KC_Y,    KC_U,  KC_I,    KC_O,    KC_P,    JP_GRV,  \
41      KC_LCTL,  KC_A,   KC_S,    KC_D,    KC_F,  KC_G,         KC_H,    KC_J,  KC_K,    KC_L,    KC_SCLN, JP_QUOT, \
42      KC_LSFT,  KC_Z,   KC_X,    KC_C,    KC_V,  KC_B,         KC_N,    KC_M,  KC_COMM, KC_DOT,  KC_SLSH, KC_ENT,  \
43      KC_LANG2, KC_ESC, KC_LALT, KC_LGUI, LOWER, KC_SPC,       KC_BSPC, RAISE, KC_RGUI, KC_RALT, KC_RCTL, KC_LANG1 \
44      ),
45
46  [_LOWER] = LAYOUT( \
47      JP_ZKHK, KC_1,    KC_2,    KC_3,    KC_4,    KC_5,       KC_6,   KC_7,    KC_8,    KC_9,    KC_0,    JP_BSLS, \
48      _______, KC_F1,   KC_F2,   KC_F3,   KC_F4,   KC_F5,      KC_F6,  KC_MINS, JP_EQL,  JP_LBRC, JP_RBRC, _______,   \
49      _______, KC_F7,   KC_F8,   KC_F9,   KC_F10,  KC_F11,     KC_F12, KC_APP,  JP_LPRN, JP_RPRN, _______, _______, \
50      _______, _______, _______, _______, _______, _______,    KC_DEL, _______, _______, _______, _______, _______ \
51      ),
52
53  [_RAISE] = LAYOUT( \
54      KC_AGRV, KC_NO,   KC_PGUP, KC_NO,   KC_NO,   KC_NO,      KC_NO,    KC_NO,   KC_INS,  KC_NO,   KC_PSCR, KC_NO,   \
55      _______, KC_HOME, KC_PGDN, KC_END,  KC_NO,   KC_NO,      KC_LEFT,  KC_DOWN, KC_UP,   KC_RGHT, KC_NO,   KC_NO,   \
56      _______, KC_NO,   KC_NO,   KC_NO,   KC_NO,   KC_NO,      KC_NO,    KC_NO,   _______, _______, _______, _______, \
57      _______, _______, _______, _______, _______, KC_ENT,     _______,  _______, _______, _______, _______, _______ \
58      ),
59
60    [_ADJUST] =  LAYOUT( \
61      RESET,   JP_EXLM, JP_AT,   JP_HASH, JP_DLR,  JP_PERC,    JP_CIRC, JP_AMPR,  JP_ASTR, JP_LPRN, JP_RPRN,  KC_NO, \
62      DEBUG,   KC_NO,   KC_NO,   KC_NO,   KC_NO,   KC_NO,      KC_NO,   KC_NO,    KC_NO,   KC_CAD,  KC_APSCR, KC_NO,\
63      _______, KC_NO,   KC_NO,   KC_NO,   KC_NO,   KC_NO,      KC_NO,   KC_NO,    _______, _______, _______,  _______, \
64      _______, _______, _______, _______, _______, _______,    _______, _______,  _______, _______, _______,  _______ \
65      )
66};

  1. S(JU_2) の形で指定したキーを押しても何も送信されませんので、JP_EXLM 等のキーコードを使う必要があります。 ↩︎

  2. PC に送信されるキーコードは qmk_firmware/keycode.h at master · qmk/qmk_firmware で定義されています。このキーコードは USB 規格 の HID Usage Table で定められているコードを基にして、現在の OS では使われないコード (0xA5-0xDF) にメディアキー等が割り当てられています。 ↩︎