⌨️

16bitキーコードで LT(layer, kc) したかった

に公開

LT(layer, kc) とは

qmk_firmware のキー設定の記法で、ホールドしている間レイヤを有効化し、タップで指定したキーコードを送信します。

LT(layer, kc) - momentarily activates layer when held, and sends kc when tapped. Only supports layers 0-15.

https://docs.qmk.fm/feature_layers

例えば LT(1, KC_SPC) と記述したキーは、タップ(短押し)だとスペースを入力、ホールド(長押し)をしている間はレイヤ1を有効にします。

LT(layer, kc) の制約

マクロ定義を見るとわかりますが、kc には 8bit のキーコードしか使えないという制約があります。

qmk_firmware/quantum/quantum_keycodes.h
#define LT(layer, kc) (QK_LAYER_TAP | (((layer)&0xF) << 8) | ((kc)&0xFF)

例えばキーマップに LT(3, S(KC_BSLS)) と書いても LT(3, KC_BSLS) として処理されるので、タップしたときに | (Shift + \) ではなく \ が入力されてしまいます。この制約を回避したかったのでやってみました。

方法1: Tap Danceを使ってTap-Hold

https://docs.qmk.fm/features/tap_dance

rules.mk で TAP_DANCE_ENABLE = yes として Tap Dance を有効化し、Example 3: Send : on Tap, ; on Hold のサンプルコードを使って、ACTION_TAP_DANCE_TAP_HOLD(tap_kc16, hold_kc16) の形でアクションを定義すると tap_kc16{tap,register,unregister}_code16(tap_kc16) で送信してくれるのでタップしたときのキーコードの制約は回避できます。

一方で hold_kc16 の方も {regsiter,unregister}_code16(hold_kc16) で送信されるのですが、ホールド中にレイヤを有効にしようと hold_kc16MO(n) を指定しても何も起こりません。

keymap.c
enum {
    TD_PIPE_MO3,
};

tap_dance_action_t tap_dance_actions[] = {
    [TD_PIPE_MO3] = ACTION_TAP_DANCE_TAP_HOLD(S(KC_BSLS), MO(3)),
};

サンプルコードを一部変更して QK_MOMENTARY <= MO(n) <= QK_MOMENTARY_MAX の分岐を追加し、layer_on() , layer_off() することで、ホールド中にレイヤを有効にするという期待動作になります。これで解決。

keymap.c
void tap_dance_tap_hold_finished(tap_dance_state_t *state, void *user_data) {
    tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)user_data;

    if (state->pressed) {
        if (state->count == 1
#ifndef PERMISSIVE_HOLD
            && !state->interrupted
#endif
        ) {
            if (QK_MOMENTARY <= tap_hold->hold && tap_hold->hold <= QK_MOMENTARY_MAX) {
                layer_on(QK_MOMENTARY_GET_LAYER(tap_hold->hold));
            } else {
                register_code16(tap_hold->hold);
            }
            tap_hold->held = tap_hold->hold;
        } else {
            register_code16(tap_hold->tap);
            tap_hold->held = tap_hold->tap;
        }
    }
}

void tap_dance_tap_hold_reset(tap_dance_state_t *state, void *user_data) {
    tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)user_data;

    if (tap_hold->held) {
        if (QK_MOMENTARY <= tap_hold->held && tap_hold->held <= QK_MOMENTARY_MAX) {
            layer_off(QK_MOMENTARY_GET_LAYER(tap_hold->held));
        } else {
            unregister_code16(tap_hold->held);
        }
        tap_hold->held = 0;
    }
}

方法2: 未使用キーコードを流用しタップ動作を上書き

TAP_DANCE_ENABLE = yes とするとファームサイズがわりと増えてしまうので、別の方法はないかと調べていたところ、灯台下暗しで Mod-Tap のドキュメントにタップの動作を置き換えるサンプルコードがありました。

https://docs.qmk.fm/mod_tap#changing-tap-function

LT(layer, kc) の kc には8bitキーコードの範囲で使ってないものを書いておき、タップ動作を上書きすることで、実質 kc に16bitキーコードも指定できたということになります。例えば

keymap.c
enum custom_user_keycodes {
    // Reuse unused basic keycodes
    MY_PIPE = KC_NUBS,
};

と定義して keymaps 配列に LT(3, MY_PIPE) と書いた場合、それを process_record_user()

keymap.c
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    switch (keycode) {
        case LT(3, MY_PIPE):
            if (record->tap.count) {
                static uint16_t kc;
                if (record->event.pressed) {
                    kc = S(KC_BSLS);
                    register_code16(kc);
                    return false;
                } else if (kc) {
                    unregister_code16(kc);
                    kc = 0;
                    return false;
                }
            }
            break;
        default:
            break;
    }
    return true;
}

としてやると、当該キーをタップしたときに | (Shift + \) が入力されるようになります。

なお、Mod-Tapのドキュメント中のサンプルコードでは tap_code16(kc) を使っているのですが、それだと「タターン」とタップ+ホールドしてもキーリピートが効かなかったので register_code16(kc)unregister_code16(kc) を呼ぶようにしています。

https://zenn.dev/yoichi/articles/qmk-quick-tap-term

Discussion