QMK Firmwareのキーオーバーライド機能を試す


概要

自作キーボード界隈では有名な、OSSのキーボードファームウェアであるQuantum Mechanical Keyboard Firmware(QMKとよく呼ばれる)に、新しい機能であるキーオーバーライド(Key Overrides)がmasterブランチにマージされた。これが面白そうな機能だったので、早速ドキュメントを翻訳してキーマップへの実装を試してみた。

翻訳したドキュメントは2021/9/11現在、まだPRレビュー待ち状態であるため、ドキュメントを日本語で読みたい人はこのPRの変更内容を直接参照してほしい。

キーオーバーライド機能の使い道

キーオーバーライド機能の詳細は前述の公式ドキュメントを参照してほしいが、要は修飾キーと通常のキーの組み合わせを、別の修飾キーと通常キーの組み合わせや任意のカスタムコードに置き換えることができる機能である。修飾キーと通常キーの組み合わせとは書いたが、修飾キーのみ、通常キーのみももちろん可能である。

この機能の使用例は公式ドキュメントにもいくつか挙がっているが、私が推したい実用例として、個人的に疑似US配列と呼んでいる、物理的にUS配列のキーボードを、OSのキーボード設定は日本語キーボードのままUS配列として普通に使用できるようにしたキーマップの実現がある。この疑似US配列は、US配列のキーボードで日本語キーボード設定のPCにリモート接続する場合や、US配列のキーボード用のカスタムファームウェアで変換キーなどのUS配列には存在しないキーを配置したい場合に便利である。

リモート接続先が共用PCの場合、キーボード設定を勝手に変更することはできないので、諦めてそのまま使用することになる。US配列のキーボードでも、日本語配列を知っていれば普通に文字を入力することができるが、残念ながらキー数の問題で「¥」や「_」を入力することができない。また、Windowsの場合、キーボード設定を英語キーボードにすると、例えカスタムファームウェアで変換キーを配置してあったとしても、変換キーを認識してくれないという問題がある。変換キーを使いたければキーボード設定を日本語キーボードにするしかない(一応、キーボード設定をAXキーボードにしたり、独自のキーボード用DLLを作成するやり方もある)。

QMKでは、キーコードを配置することでキーマップを作成することができるが、US配列と日本語配列では、Shiftを押さずに単独でキーを押した時に入力される文字と、Shiftを押しながらキーを押したときに入力される文字の組み合わせが一部異なっているため、キーコードを配置するだけでは疑似US配列を実現することはできない。カスタムコードを作成すればこれまででもこの疑似US配列のキーマップを実現することはできたが、カスタムコードを書くにはキーボードの挙動やC言語、QMKの知識が要求されるため、かなりハードルが高かった。キーオーバーライド機能はこのハードルをかなり下げてくれるので、こういった複雑なキーマップでもカスタムコードと比べればかなり簡単に作ることができるようになった。

疑似US配列のキーマップの実装(キモの部分だけ)

概要

詳細は公式ドキュメントを参照してほしいが、キーオーバーライド機能を使用するには以下の3つを行えばよい。

  • rules.mk でキーオーバーライド機能を有効にする定義を書く
  • keymap.c で置換したい修飾キーと通常キーの組み合わせと、置換後の修飾キーと通常キーの組み合わせを定義する
  • keymap.c で、上記組み合わせの適用順を定義する

rules.mk でキーオーバーライド機能が有効(enable)になっていれば、キーオーバーライド機能はデフォルトで有効化(activate)されているので、一時的にキーオーバーライド機能を無効化/有効化したいといった要求がなければこれだけである。一時的にキーオーバーライド機能を無効化/有効化したい場合は、これ以外に専用のキーコードをキーマップに配置するかカスタムコードを書く必要がある。

実装例

実装例を以下に示す。実装に使用したキーボードは、namecard2x4という2行4列8キーの小さなものである。
詳細は後述するが、疑似US配列に必要な置換パターンは、

  • shift+通常キー → shift+通常キー
  • shift+通常キー → 通常キー
  • 通常キー → shift+通常キー
  • 通常キー → 通常キー

の4パターンであるため、全パターンを網羅するように4キー分(6文字分)だけ実装した。

rules.mk
KEY_OVERRIDE_ENABLE = yes
keymap.c
#include QMK_KEYBOARD_H
#include "keymap_jp.h"

enum keyboard_layers {
    _DEFAULT = 0
};

#define OVR_TGL KEY_OVERRIDE_TOGGLE

const key_override_t at_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_2, JP_AT);
const key_override_t rprn_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_0, JP_RPRN);
const key_override_t lcbr_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_LBRC, JP_LCBR);
const key_override_t dquo_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_QUOT, JP_DQUO);
const key_override_t lbrc_key_override = ko_make_with_layers_and_negmods(0, KC_LBRC, JP_LBRC, ~0, (uint8_t) MOD_MASK_SHIFT);
const key_override_t quot_key_override = ko_make_with_layers_and_negmods(0, KC_QUOT, JP_QUOT, ~0, (uint8_t) MOD_MASK_SHIFT);

const key_override_t **key_overrides = (const key_override_t *[]){
    &at_key_override,
    &rprn_key_override,
    &lcbr_key_override,
    &dquo_key_override,
    &lbrc_key_override,
    &quot_key_override,
    NULL
};

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [_DEFAULT] = LAYOUT(
        KC_2,    KC_0,    KC_LBRC, KC_QUOT,
        KC_LSFT, KC_LCTL, OVR_TGL, KC_NO
    ),
};

解説

ヘッダファイルのインクルード

まず始めに必要なヘッダファイルをインクルードする。

#include QMK_KEYBOARD_H
#include "keymap_jp.h"

QMK_KEYBOARD_H はQMKの動作に最低限必要なヘッダ類をまとめたもので、どんなキーボードでも必要となるものである。
keymap_jp.h は、日本語配列のキーボードを、OSのキーボード設定を日本語キーボードにして使用する際に便利な、QMKの基本的なキーコードを日本語配列に読み替えた場合のキーコードが定義されているものである。

レイヤー名の定義

列挙型で任意の名前と、名前に対する値を宣言しておく。これは後でキーマップのレイヤー配列のインデックス値として使用する。

enum keyboard_layers {
    _DEFAULT = 0
};

カスタムキーコードの定義

キーオーバーライド機能専用のキーコード名が長いので、短い名前のカスタムキーコードを宣言している。従ってなくても良い。

#define OVR_TGL KEY_OVERRIDE_TOGGLE

KEY_OVERRIDE_TOGGLE は、キーオーバーライド機能の有効化/無効化を切り替える、キーオーバーライド機能専用のキーコード。

置換定義

キーオーバーライド機能で使用する、置換したい修飾キーと通常キーの組み合わせと、置換後の修飾キーと通常キーの組み合わせを定義する。

const key_override_t at_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_2, JP_AT);
const key_override_t rprn_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_0, JP_RPRN);
const key_override_t lcbr_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_LBRC, JP_LCBR);
const key_override_t dquo_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_QUOT, JP_DQUO);
const key_override_t lbrc_key_override = ko_make_with_layers_and_negmods(0, KC_LBRC, JP_LBRC, ~0, (uint8_t) MOD_MASK_SHIFT);
const key_override_t quot_key_override = ko_make_with_layers_and_negmods(0, KC_QUOT, JP_QUOT, ~0, (uint8_t) MOD_MASK_SHIFT);
ko_make_basic関数

ko_make_basic(modifiers, key, replacement)key_override_t 構造体を簡単に作成するための関数で、引数で順に、置換したい修飾キーの組み合わせ、置換したい通常キー、置換後の修飾キーと通常キーの組み合わせを定義する。この定義は必要な置換パターンの数だけ行う。

MOD_MASK_SHIFT はShiftキー(左右いずれでも可)、KC_ で始まるキーコードはQMKの基本的なキーコード、JP_ で始まるキーコードは keymap_jp.h で定義された日本語配列用のキーコードである。最初の引数で指定する修飾キーは、押されていなくてはいけない修飾キーを指定するが、この修飾キーが押されていれば他の修飾キーが押されていたとしても置換は行われることに注意。例として、ko_make_basic(MOD_MASK_SHIFT, KC_2, JP_AT) は、Shift+2を押した時に、JP_AT つまり日本語配列における「@」に置換されるようにする定義である。

疑似US配列において、ko_make_basic関数で定義するキーは、Shiftを押しながら入力するキーのうち置換が必要なキー全てである。

ko_make_with_layers_and_negmods関数

ko_make_with_layers_and_negmods(modifiers, key, replacement, layers, negative_mods)ko_make_basic を拡張したもので、置換を行うレイヤーと、一緒に押されていた場合に置換を行わないようにする修飾キーを追加で指定できるようになっている。例として、ko_make_with_layers_and_negmods(0, KC_LBRC, JP_LBRC, ~0, (uint8_t) MOD_MASK_SHIFT) は、修飾キーなし(0)で KC_LBRC つまり「[」を単独で押された場合に、JP_LBRCつまり日本語配列における「[」に置換されるようにする定義である。layers~0 は全てのレイヤーで有効化する指定で、例えば3番目のレイヤーのみで有効化したい場合は 1 << 2 と指定する。Shiftキーが押されていた場合はこの定義を適用してはいけないため、negative_modsMOD_MASK_SHIFT を指定する。

疑似US配列において、ko_make_with_layers_and_negmods関数で定義するキーは、Shiftを押さず単独で入力するキーのうち置換が必要なキー全てである。

置換が必要なキー一覧

疑似US配列を実現するには、ある位置のキー(+Shiftキー)を押したときに日本語キーボード設定で入力される文字を、そのキーの位置で英語キーボード設定で入力される文字と同じ文字を入力できるキーとShiftキーの組み合わせに置き換える必要がある。以下が置換が必要な全てのキーの一覧である。
これらは、US配列と日本語配列とでキーの場所が異なるものや、Shiftを押した時に入力される文字が異なるものである。置換前入力文字はOSのキーボード設定が日本語キーボードの場合に入力される文字で、置換後Shiftが必要かどうかは、日本語キーボード設定でその文字を入力するためにShiftキーを押す必要があるかを示す。また、上述の実装例で定義したものには○をつけた。

US配列で押すキー 置換前
入力文字
置換後
入力文字
置換後
Shiftが必要
実装例で定義したもの
Shift+2 " @ No
Shift+6 & ^ No
Shift+7 ' & Yes
Shift+8 ( * Yes
Shift+9 ) ( Yes
Shift+0 ※Shift+0 ) Yes
Shift+- = _ Yes
= ^ = Yes
Shift+= ~ + Yes
[ @ [ No
Shift+[ ` { Yes
] [ ] No
Shift+] { } Yes
\ ] \ No
Shift+\ } | Yes
Shift+; + : No
' : ' Yes
Shift+' * " Yes
` ※半角/全角 ` Yes
Shift+` ※Shift+半角/全角 ~ Yes

※は文字が入力されないもの

置換定義の適用順の定義

定義した置換パターンを適用する順番を定義する。キーオーバーライド機能は、キーオーバーライド機能が有効(enable)の場合に何かキーが押されるとこの定義を上から順に探索し、最初に条件に該当した置換定義を有効化する。なお、必ず最後に NULL が必要で、NULL が出現すると探索を終了し、キーオーバーライド機能は無効化(deactivate)のままとなる。

const key_override_t **key_overrides = (const key_override_t *[]){
    &at_key_override,
    &rprn_key_override,
    &lcbr_key_override,
    &dquo_key_override,
    &lbrc_key_override,
    &quot_key_override,
    NULL
};

まとめ

このような感じでキーオーバーライド機能を使用することで簡単に複雑なキーマップを実装できるようになった。置換定義が増えてくるとどうしても分かりにくいコードになるため、このあたりを工夫して実装すればすっきりとしたコードになると思われる。