QMKでのレイヤー機能の挙動について

情報追加

以下に記載したmodifierキーがロックされる事象は、QMKのreadmeのPrevent stuck modifiersに記載されていることをみやおかさんから教えていただき、その通りであることを確認しました。

概要

QMKでのレイヤー機能の動作が自分の想定したものとは異なっていたので、その調査結果を解説をしてみます。

動機

レイヤー機能を自前で実装するのが面倒くさかったので、自作の分割型キーボードにQMKを移植してみました。 レイヤーを使った自分の好みの少数キー配列が出来たので、ようやく本格的にレイヤーを使ってみる気になることができました。

しかし、しばらく使っていると一部のmodifierキーがロック(キーを離しているのにそれが認識されない)されてしまうことに気がついたため調べてみることにしました。

調査方法

調査対象のキーボード

自作の分割型にQMKの移植した際の誤りの可能性があると考えたので、QMKへの修正の影響があまり無いはずと考えている以前に作成したplanckモドキを対象としました。 hrhg.hatenablog.com

環境(OS)

Windows8.1/64bitを使用しました。

操作パターン

  1. 対象キーON -> レイヤーキーON -> レイヤーキーOFF -> 対象キーOFF
  2. 対象キーON -> レイヤーキーON -> 対象キーOFF -> レイヤーキーOFF
  3. レイヤーキーON -> 対象キーON -> 対象キーOFF -> レイヤーキーOFF
  4. レイヤーキーON -> 対象キーON -> レイヤーキーOFF -> 対象キーOFF

の4つとしました。対象キーおよびレイヤーキーの定義は後述します。

QMKのソース

2017/07/20時点でのソースをgitで取得して確認しました。

キーマップ

planck/keymaps/ab/keymap.c を元に、LOWERレイヤーの左下隅のキーコードをBLから、OSが認識できるKC_Aに変更しました。しかし変更した意味は無かったかもしれません。

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[_QWERTY] = { /* QWERTY */
    {KC_TAB,  KC_Q,    KC_W,    KC_E,    KC_R,    KC_T,    KC_Y,    KC_U,    KC_I,    KC_O,    KC_P,    KC_BSPC},
    {KC_LCTL, KC_A,    KC_S,    KC_D,    KC_F,    KC_G,    KC_H,    KC_J,    KC_K,    KC_L,    KC_SCLN, KC_ENT},
    {KC_LSFT, KC_Z,    KC_X,    KC_C,    KC_V,    KC_B,    KC_N,    KC_M,    KC_COMM, KC_DOT,  KC_SLSH, SFT_ENT},
    {KC_LCTL, KC_ESC,  KC_LGUI, KC_LALT, LOWER,   KC_SPC,  KC_SPC,  RAISE,   KC_LEFT, KC_DOWN, KC_UP,   KC_RGHT}
},
[_LOWER] = { /* LOWER */
    {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_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_LPRN, KC_RPRN, KC_LCBR, KC_RCBR, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS},
    {KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_LBRC, KC_RBRC, KC_QUOT, KC_DQT,  KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS},
/*  {BL,      ZM_NRM,  ZM_IN,   ZM_OUT,  KC_TRNS, KC_PGDN, KC_PGDN, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS} */
    {KC_A,    ZM_NRM,  ZM_IN,   ZM_OUT,  KC_TRNS, KC_PGDN, KC_PGDN, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS}
},
[_RAISE] = { /* RAISE */
    {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_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_LCBR, KC_LCBR, KC_BSLS, KC_TRNS},
    {KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_BSLS, KC_PIPE, KC_GRV,  KC_TILD, KC_LBRC, KC_LBRC, KC_TRNS, KC_TRNS},
    {RESET,   KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_PGUP, KC_PGUP, KC_TRNS, EM_UNDO, KC_VOLD, KC_VOLU, KC_MUTE}
},
[_CUSTOM] = { /* CUSTOM */
    {KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS},
    {KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS},
    {KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, MOB,     KC_TRNS, CUS1,    CUS2,    KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS},
    {KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS}
}
};

レイヤーキー

LOWERを使用しました。

対象キー

  1. modifierその1として、Aキーの左のキー:レイヤーの有無によらずキーコードが同じ
  2. modifierその2として、左下隅のキー:レイヤーの有無によってキーコードが異なる
  3. 文字キーとして、Qキー:レイヤーの有無によってキーコードが異なる

の3つとしました。

補助ツール

Keymillというツールを使い、OSがどのようにキーのON/OFFを認識しているかもあわせて観測してみました。

f:id:hrhg:20170722041247p:plain

調査結果

操作パターンと対象キーの組み合わせのうち、b2とd2の場合に対象キーがロックされてしまうようでした。 対象キーをON/OFFするとロックが解除されるようでした。

分析

操作パターンをkeymillで観測してみると、下図のようなタイミングになっているようでした。 f:id:hrhg:20170722041137p:plain

この図の中でb1の場合mod_onのキーコード(KC_LCTL)とlayer_offのキーコード(KC_TRNS:元のレイヤーと同じコード)は同一ですが、 b2の場合mod_onのキーコード(KC_LCTL)とlayer_offのキーコード(KC_A)が異なるため(朱字の部分)、mod_onのキーOFFが認識されないと思われます。d1,d2の場合も同様です。

また実質的に問題にはならないかもしれませんが、文字キーとの組み合わせでレイヤキーがONになるタイミングで文字キーがOFFと認識されるところも気になりました(b3, d3)。

modifierと文字キーの挙動が異なるのは tmk_core/common/action_leyer.c の layer_state_set() でロックを回避するために clear_mods() を呼んでいないからだと思われます。

いっぽう自分の想定した各パターンのタイミングは下図になります。 f:id:hrhg:20170722041206p:plain

異なる点は

  • 対象キーのOFF時には、ON時のレイヤーと同じキーコードを伝達する
  • modifierと文字キーとの挙動は同一にし、対象キーのON/OFFのタイミングでのみキーの変化を伝達する

になります。

対応

試にQMKの変更をやってみようとも思いましたが、

  • 上記のように方式を変えるため、ちょっと変える程度ではなさそう
  • 他にも影響がある可能性があるかもしれないので手間取りそう
  • print debugを行う方法がよくわからなかった

などという言い訳により手をつけていません。

自分の当初の目的としては分割型のキーボードのレイヤーの動作を意図するようにしたかったので、重い腰をあげて以前に作ったarduinoでの実装に自分の想定するタイミングのレイヤー機能を追加して、自分で納得する動きを確認しました。

現在のところQMKの変更は試みていませんが一応ここまで調べてみましたので、このような形で情報を公開してみることにしました。

所感

QMK(というかTMKでの処理でしょうか )は世界的に使われている実績があると思いますが、個人的には致命的な問題ととらえています。

しかし特に問題視されている情報が見当たらないので、何か自分が勘違いをしているのか、普通は問題が起こるような指運びをしないのか 不思議に思っているところです。

人さまのご意見をお手柔らかに伺えれば幸いです。

→ 冒頭に追記したように、全に私の確認不足のようでした。