LoginSignup
6
6

【QMK】理想のキーボードを実現するためのTips

Posted at

みなさん、キーボード作ってますか?

私はいろいろあって一年ほど前から自作キーボードに打ち込めなくなっていますが気持ち的にはまだ自キ勢です。
一年前の時点では自作キーボードはPro MicroやElite-CにQMKを焼くのが主流でしたがあれから世界は変わったのでしょうか?

今回は私が理想のキーボードを実現するためにどのようにQMKを使っているのか紹介したいと思います。

キー配列

最初にキー配列を紹介しておきます。
自キ沼の住人にとってはそんなに特殊なことはしてないかと思います。

沼の住人にとっても説明が欲しそうなところだけ説明しておくと、 F13 はIME切り替え(日本語入力と英語入力の切り替え)に使っていて、 Ctrl + [ はVimのノーマルモードに戻るコマンドですね
Dvorak

数字レイヤー

数字レイヤー

記号レイヤー

記号レイヤー

fnレイヤー

fnレイヤー

テンキーレイヤー

テンキーレイヤー

配列自体はそんなに特殊じゃないのでQMKの標準機能だけで十分実現できるかと思います。
ここからはQMKの標準機能で実現できることに加えて、私が最高に快適に入力するために調整したファームウェアのコードを説明していきます

レイヤー

Space + o@ を打てたり Enter + e3 を打てたりします。
スペースキーやエンターキーが「レイヤー切り替え」キーになっていて、これらのキーを押している間、普段は o のキーが一時的に @ になったりするわけです。

keymap.cでレイヤー名をenumで宣言しておいて、宣言したenumを使ってkeymapsにキー配列を記述する下記のパターンが一般的ですね。必ずこの記法じゃないといけないということはないと思いますが、まあこの慣習に従っておけばよいでしょう

keyboards/keyboard_name/keymaps/keymap_name/keymap.c
enum layer_number {
  _DVORAK = 0,
  _NUM,
  _SYMBOL,
  _NUM_KEYPAD,
  _FN
};

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
  [_DVORAK] = LAYOUT(
      KC_GRV , KC_1   , KC_2   , KC_3   , KC_4   , KC_5   ,                       KC_6   , KC_7   , KC_8   , KC_9   , KC_0   , KC_SLSH,
      KC_TAB , KC_QUOT, KC_COMM, KC_DOT , KC_P   , KC_Y   ,                       KC_F   , KC_G   , KC_C   , KC_R   , KC_L   , KC_EQL ,
      KC_LCTL, KC_A   , KC_O   , KC_E   , KC_U   , KC_I   ,                       KC_D   , KC_H   , KC_T   , KC_N   , KC_S   , KC_LBRC,
      KC_LSFT, SFT_Z  , KC_Q   , KC_J   , KC_K   , KC_X   ,                       KC_B   , KC_M   , KC_W   , KC_V   , KC_SCLN, KC_MINS,
                                 ALT_ESC, KC_UNDS, SYM_SPC, KC_BSPC,     ESC_SFT, NUM_ENT, FN_KANA, KC_RGUI
  ),
  [_NUM] = LAYOUT(
      _______, _______, _______, _______, _______, _______,                       _______, _______, _______, _______, _______, _______,
      _______, _______, _______, _______, _______, _______,                       _______, _______, _______, _______, _______, _______,
      KC_GRV , KC_1   , KC_2   , KC_3   , KC_4   , KC_5   ,                       KC_6   , KC_7   , KC_8   , KC_9   , KC_0   , KC_SLSH,
      _______, _______, _______, _______, _______, _______,                       _______, _______, _______, _______, _______, _______,
                                 _______, _______, _______, _______,     _______, _______, _______, _______
  ),
  [_FN] = LAYOUT(
      KC_F12 , KC_F1  , KC_F2  , KC_F3  , KC_F4  , KC_F5  ,                       KC_F6  , KC_F7  , KC_F8  , KC_F9  , KC_F10 , KC_F11 ,
      _______, _______, _______, _______, _______, _______,                       _______, _______, _______, _______, _______, _______,
      _______, _______, _______, KC_PSCR, _______, _______,                       KC_LEFT, KC_DOWN, KC_UP  , KC_RGHT, _______, _______,
      _______, _______, _______, _______, _______, _______,                       _______, MC_HOME, MC_END , _______, _______, _______,
                                 KC_RGUI, _______, _______, KC_DEL ,     _______, TG_NUM , _______, _______
  ),
      :
      :
};

ここで使う LAYOUT はキーボードの設計者が用意しているマクロ。誰かが作ったキーボードにファームウェアを焼く場合は気にせずこの通り書けばよいが、ハード側も自分が設計した場合はマクロも自分で用意する必要がある。

といっても

コードとして書きやすい形の羅列を実際の物理的な配線の並びに変換するだけ。

keyboards/keyboard_name/keyboard_name.h
#define LAYOUT( \
    L00, L01, L02, L03, L04, L05,                R00, R01, R02, R03, R04, R05, \
    L10, L11, L12, L13, L14, L15,                R10, R11, R12, R13, R14, R15, \
    L20, L21, L22, L23, L24, L25,                R20, R21, R22, R23, R24, R25, \
                   L32, L33, L34, L35,      R30, R31, R32, R33                 \
) \
{ \
  { L00, L01,   L02,   L03,   L04, L05 }, \
  { L10, L11,   L12,   L13,   L14, L15 }, \
  { L20, L21,   L22,   L23,   L24, L25 }, \
  { L32, KC_NO, KC_NO, L33,   L34, L35 }, \
  { R05, R04,   R03,   R02,   R01, R00 }, \
  { R15, R14,   R13,   R12,   R11, R10 }, \
  { R25, R24,   R23,   R22,   R21, R20 }, \
  { R32, R33,   KC_NO, KC_NO, R30, R31 }  \
}

たとえば私のキーボードは基板上の配線の都合でL00, L10, L20の行の線がL32に繋がっているのでこういう風にマクロ宣言する。

あとはconfig.hにキーボードの列数と行数、Pro Microのどのピンがどの列のキーに繋がっているのかをdefineしておく。

keyboards/keyboard_name/config.h
#define MATRIX_ROWS 10
#define MATRIX_COLS 6
#define MATRIX_ROW_PINS { F5, D4, C6, D7, D1 }
#define MATRIX_COL_PINS { D0, B1, B6, B5, B4, E6 }
#define UNUSED_PINS
#define DIODE_DIRECTION COL2ROW

電気信号を読み取ってどのキーが押されたのか検知してキーコードをPCに送信して〜〜みたいなとこはQMKがやってくれる。うっひょう。

レイヤー切り替えキー

リファレンス

レイヤーを切り替えるためのキーもほとんど通常のキーと同じ方法で宣言できる。
押している間別のレイヤーに切り替えたいのであれば MO(layer) 、押すたびにレイヤーのオンオフを切り替えるのであれば TG(layer) 、MOの機能に加えて短く単押しした場合に別のキーとして扱いたい場合は LT(layer, key) だ。
私の場合スペースキーを長押しすると記号レイヤーに切り替わるので LT(_SYMBOL, KC_SPC) という具合。

キー配列を記述するところに直接これを書くと長くて見にくくなってしまうのでdefineするのが通例。

keyboards/keyboard_name/keymaps/keymap_name/keymap.c
+ #define TG_NUM TG(_NUM_KEYPAD)
+ #define SYM_SPC LT(_SYMBOL, KC_SPC)
+ #define NUM_ENT LT(_NUM, KC_ENT)
+ #define FN_KANA LT(_FN, KC_F13)

  const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
     [_DVORAK] = LAYOUT(
        KC_GRV , KC_1   , KC_2   , KC_3   , KC_4   , KC_5   ,                       KC_6   , KC_7   , KC_8   , KC_9   , KC_0   , KC_SLSH,
        KC_TAB , KC_QUOT, KC_COMM, KC_DOT , KC_P   , KC_Y   ,                       KC_F   , KC_G   , KC_C   , KC_R   , KC_L   , KC_EQL ,
        KC_LCTL, KC_A   , KC_O   , KC_E   , KC_U   , KC_I   ,                       KC_D   , KC_H   , KC_T   , KC_N   , KC_S   , KC_LBRC,
        KC_LSFT, SFT_Z  , KC_Q   , KC_J   , KC_K   , KC_X   ,                       KC_B   , KC_M   , KC_W   , KC_V   , KC_SCLN, KC_MINS,
                                   ALT_ESC, KC_UNDS, SYM_SPC, KC_BSPC,     ESC_SFT, NUM_ENT, FN_KANA, KC_RGUI
     ),
     [_NUM] = LAYOUT(...),
     [_SYMBOL] = LAYOUT(...),
     [_NUM_KEYPAD] = LAYOUT(...),
     [_FN] = LAYOUT(...)
  };

モッドタップ

リファレンス

Shiftキーは使用頻度が極めて高いが、そのわりにホームポジションから遠いところにある。一方でShiftキーより少し近い左手小指下段にあるキーは「Z」だ。あまり押さない。
そのため私はZキーを押しながら他のキーを押したとき、ZキーをShiftキーとして動作させている。

こういうふうに、単押しの場合には通常のキーとして動作するが、他のキーとの同時押しした場合にモディファイヤキーとして動作するものをモッドタップと呼ぶ。

keyboards/keyboard_name/keymaps/keymap_name/keymap.c
+ #define SFT_Z SFT_T(KC_Z)
+ #define ALT_ESC ALT_T(KC_ESC)
  #define TG_NUM TG(_NUM_KEYPAD)
  #define SYM_SPC LT(_SYMBOL, KC_SPC)
  #define NUM_ENT LT(_NUM, KC_ENT)
  #define FN_KANA LT(_FN, KC_F13)

  const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
     [_DVORAK] = LAYOUT(
        KC_GRV , KC_1   , KC_2   , KC_3   , KC_4   , KC_5   ,                       KC_6   , KC_7   , KC_8   , KC_9   , KC_0   , KC_SLSH,
        KC_TAB , KC_QUOT, KC_COMM, KC_DOT , KC_P   , KC_Y   ,                       KC_F   , KC_G   , KC_C   , KC_R   , KC_L   , KC_EQL ,
        KC_LCTL, KC_A   , KC_O   , KC_E   , KC_U   , KC_I   ,                       KC_D   , KC_H   , KC_T   , KC_N   , KC_S   , KC_LBRC,
        KC_LSFT, SFT_Z  , KC_Q   , KC_J   , KC_K   , KC_X   ,                       KC_B   , KC_M   , KC_W   , KC_V   , KC_SCLN, KC_MINS,
                                   ALT_ESC, KC_UNDS, SYM_SPC, KC_BSPC,     ESC_SFT, NUM_ENT, FN_KANA, KC_RGUI
     ),
        :
        :
  };

TAPPING_TERM

リファレンス

モッドタップのキーを押し始めてからモディファイヤキーに切り替わるまでの時間。デフォルト200ミリ秒だが私は150ミリ秒にしている。

keyboards/keyboard_name/keymaps/keymap_name/config.h
#define TAPPING_TERM 150

PERMISSIVE_HOLD

リファレンス

たとえばZキーとTキーを同時に押すとZキーはShiftとして動作するので入力される文字は「T」であるべきだ。でも、QMKのデフォルトの設定だとこの入力をTAPPING_TERM以内にすべて終えると「zt」と入力されるのだ。キーボード初心者はモッドタップを扱う際しっかりモディファイヤキーに切り替わってから入力するだろうが、慣れると余裕で150ミリ秒以内に入力できてしまう。PERMISSIVE_HOLDを使うことでどんなに速く入力してもモッドタップとして動作させられる。

keyboards/keyboard_name/keymaps/keymap_name/config.h
  #define TAPPING_TERM 150
+ #define PERMISSIVE_HOLD

カスタムキーコード

私のキーボードには右手親指に"Ctrl+[ / Shiftキー"という一風変わったキーがある。
私はテキストを世界で一番速く編集できるVimというエディタを使っているのだが、そのVimにおいて最も多用するコマンドがCtrl+[だ。なのでCtrl+[をすぐ押せるここに置いている。

なぜEscではなくCtrl+[なのか?

IntelliJ IDEAをVim風に操作するためのプラグイン「IdeaVim」ではIDEAの補完候補が出た状態でEscを押しても補完候補を閉じるのにEscキーを一回消費してしまう。そのあとノーマルモードに戻るためにもう一度Escを押す必要があるのだ。これでは世界で一番速く編集できるエディタが遅くなってしまう。しかしCtrl+[であれば補完候補が出た状態からでも一気に補完候補を閉じてノーマルモードに戻るところまで一発でできる。だからCtrl+[なのだ

そしてこのキーを他のキーと同時に押すとShiftキーになる。「スペースキーと反対側の親指」でShiftを押せることは非常に重要だ。STOP THE COUNT!のような大文字の文章を入力しやすいからな。
プログラミングにおいては定数の命名規則でUPPER_SNAKE_CASEを使うことも多いだろう。(だから私のキーボードはスペースキーの隣に_キーがある)

さて、QMKでこれをどうやって実現するか?
Ctrl+[キーは RCTL(KC_LBRC) でできる。(リファレンス
では"Z / Shiftキー"のときと同じくモッドタップだな。 SFT_T(RCTL(KC_LBRC)) だ。と言いたいところだが、ここでついにQMKの限界に到達してしまう。モッドタップは単押しのキーコードにモディファイヤキーを使えない。単押し時に Ctrl + [ は発行できないのだ。(リファレンス

しかし絶望するのはまだ早い。
QMKでは既存のキーコードだけでうまく実現できないキーのためにカスタムキーコードを定義することができる。
キーが押されたときの動作を process_record_user 関数で記述できるので、カスタムキーコードが押されたときに Ctrl+[ を送信してみよう。

enum custom_keycodes {
   ESC_SFT = SAFE_RANGE
};

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    if (keycode == ESC_SFT && record->event.pressed) {
        register_code(KC_RCTL);
        tap_code(KC_LBRC);
        unregister_code(KC_RCTL);
    }
    return true;
}

おやおや。なんでもできそうな気がしてきますねえ。

それではこのキーを他のキーと同時押ししたときにShiftキーとして動作させましょう。

  enum custom_keycodes {
     ESC_SFT = SAFE_RANGE
  };

+ enum key_state {
+    RELEASED,
+    PRESSED,
+    HOLDEN
+ };
+
+ void tap_ctrl_brc(void) {
+    register_code(KC_RCTL);
+    tap_code(KC_LBRC);
+    unregister_code(KC_RCTL);
+ }
+
+ void register_shift(void) {
+    register_code(KC_RSFT);
+ }
+
+ void unregister_shift(void) {
+    unregister_code(KC_RSFT);
+ }
+
+ enum key_state esc_sft_state = RELEASED;

  bool process_record_user(uint16_t keycode, keyrecord_t *record) {
     if (keycode == ESC_SFT) {
        if (record->event.pressed) {
-          register_code(KC_RCTL);
-          tap_code(KC_LBRC);
-          unregister_code(KC_RCTL);
+          esc_sft_state = PRESSED;
+       } else {
+          switch (esc_sft_state) {
+             case PRESSED:
+                tap_tcrl_brc();
+                break;
+             case HOLDEN:
+                unregister_shift();
+                break;
+          }
+          esc_sft_state = RELEASED;
+       }
+    } else {
+       if (esc_sft_state == PRESSED) {
+          register_shift();
+          esc_sft_state = HOLDEN;
+       }
     }
     return true;
  }

ちなみにこのキーで使うCtrlやShiftは右Ctrlと右Shiftにしている。左Ctrlや左Shiftを押しながらこのキーを押しても問題が起こらないように。

SEND_STRING

記号レイヤーに "->キー" と "=>キー" がある。
非常に使用頻度が高い記号でありながら真面目に打とうとするとなかなか面倒なのでこのキーがあるのは当然ですね。

さて、一回のキー入力で2文字以上のキーコードを発行するときには SEND_STRING が便利だ。

  enum custom_keycodes {
     ESC_SFT = SAFE_RANGE,
+    S_ARW,
+    D_ARW
  };
     :
     :
  bool process_record_user(uint16_t keycode, keyrecord_t *record) {
     if (keycode == ESC_SFT) {
        if (record->event.pressed) {
           esc_sft_state = PRESSED;
        } else {
           switch (esc_sft_state) {
              case PRESSED:
                 tap_tcrl_brc();
                 break;
              case HOLDEN:
                 unregister_shift();
                 break;
           }
           esc_sft_state = RELEASED;
        }
     } else {
        if (esc_sft_state == PRESSED) {
           register_shift();
           esc_sft_state = HOLDEN;
        }
     }
+    if (keycode == S_ARW && record->event.pressed) {
+       SEND_STRING("->");
+    }
+    if (keycode == D_ARW && record->event.pressed) {
+       SEND_STRING("=>");
+    }
     return true;
  }

長押し

キーを押しっぱなしにするとリピート入力される。
キーボードとして当たり前のこの機能がQMKでは当たり前ではなくなる。たとえばスペースキーは長押しすると記号レイヤーに切り替える効果があるからだ。
そのためスペースキーを長押ししてもスペースは入力されない。クサヤ温泉に現れるリッター4Kを許せる私もこのストレスは許せない。

スペースキーを単体で長押ししているときはスペースキーとして動作させ、スペースキーを押したまま他のキーが押されたときにだけレイヤー切り替えとして動作させることはできるだろうか?
QMKの標準機能だけではできない。
なのでこれも自力で実装していく。

まずはTAPPING_FORCE_HOLDモードにしておく。
実は先述のスペースをリピート入力したい問題に対するQMK標準の解決策は、「一度スペースキーをタップしてすぐにもう一度スペースを押下してそのまま長押しする」というもの。この操作をするとスペースがリピート入力される。
しかしこの操作でスペースが入力されると、スペースを入力したあとすぐ記号レイヤーに切り替えたいときに非常に困る。この操作を無効化するのがTAPPING_FORCE_HOLDだ。(リファレンス

keyboards/keyboard_name/keymaps/keymap_name/config.h
  #define TAPPING_TERM 150
  #define PERMISSIVE_HOLD
+ #define TAPPING_FORCE_HOLD

続いてスペースキーを長押ししたときにスペースキーがリピート入力されるようにしよう。
長押しは timer_readtimer_elapsed で判定する。

  #define SYM_SPC LT(_SYMBOL, KC_SPC)

  enum custom_keycodes {
     ESC_SFT = SAFE_RANGE,
     S_ARW,
     D_ARW
  };
+
+ void register_space(void) {
+    register_code(KC_SPC);
+ }
+
+ void unregister_space(void) {
+    unregister_code(KC_SPC);
+ }
+
+ enum key_state sym_spc_state = RELEASED;
+ uint16_t sym_spc_pressed_timer;

  bool process_record_user(uint16_t keycode, keyrecord_t *record) {
     if (keycode == ESC_SFT) {
           :
           :
     }
     if (keycode == S_ARW && record->event.pressed) {
        SEND_STRING("->");
     }
     if (keycode == D_ARW && record->event.pressed) {
        SEND_STRING("=>");
     }
+    if (keycode == SYM_SPC) {
+       if (record->event.pressed) {
+          sym_spc_pressed_timer = timer_read();
+          sym_spc_state = PRESSED;
+       } else {
+          if (sym_spc_state == HOLDEN) {
+             unregister_space();
+          }
+          sym_spc_state = RELEASED;
+       }
+    }
     return true;
  }
+
+ void matrix_scan_user(void) {
+    if (sym_spc_state == PRESSED
+       && timer_elapsed(sym_spc_pressed_timer) > TAPPING_TERM)
+    {
+       register_space();
+       sym_spc_state = HOLDEN;
+    }
+ }

ここで非常に重要なポイントは "Ctrl+[ / Shiftキー" や "->キー" のときのようにカスタムキーコードを追加せずに #define SYM_SPC LT(_SYMBOL, KC_SPC) を残しているところだ。
LTじゃないとレイヤーを切り替えられないというわけじゃない。レイヤー切り替え関数は layer_on, layer_off がちゃんとある。しかしカスタムキーコードを使わずLTを使う。
その理由は、 同時押しをしたいわけじゃないのに同時押しをすることがあるから
例えば &mut self s の部分。スペースキーを上げてからsを押すなんて人はほとんどいないんじゃないだろうか。たぶんほとんどの人はスペースキーを押してすぐに続けてsを押してそのあとようやくスペースキーを上げる。
「他のキーと同時に押すとレイヤーを切り替える」実装だとこのときレイヤー切り替えが発生して  s と打ったつもりが ) が入力される。
LTを使えばこういうときに意図通り  s を入力でき、スペースキーを押したままsを押して上げる操作で ) となる。

図で表すとこうだ

            TAPPING_TERM
                 (150ms)
|                | 
|                |
| space          |
|----------------|-------|                   ')'
|                |    |---------------|
|                |      s
|                |
|                |
| space          |
|----------------|-------|                   ')'
|           |----|------------|
|             s  |
|                |
|                |
| space          |
|-----------|    |                           ' s'
|       |--------|----|
|         s      |
|                |
|                |
| space          |
|-----------|    |                           ')'
|   |---|        |
|     s          |
|                |
|                |
| space          |
|--------|       |                           ' s'
|    |-------|   |
|      s         |
|                |
|                |
| space          |
|-----------|    |                           ' '
|                |
|                |
| space          |
|----------------|-----------------|         '     '
|                |
|                |
| space          |
|----------------|-------------------------| '    ))'
|                |            |------------|
|                |              s

)を入力したいという明確な意思を持って押したときにしか)は出ない。

(macOS) Home, End

macOSでHomeキーやEndキーを入力するとカーソルがぶっ飛ぶ。それがmacOSにおけるHomeとEndの正しい挙動なのだろう。
私の期待する動作は行頭に移動、行末に移動だ。
なのでHomeとEnd(のつもりのキー)には ⌘ + ←, ⌘ + → を送信させる。

  enum custom_keycodes {
     ESC_SFT = SAFE_RANGE,
     S_ARW,
     D_ARW,
+    MC_HOME,
+    MC_END
  };

  bool process_record_user(uint16_t keycode, keyrecord_t *record) {
     if (keycode == ESC_SFT) {
           :
           :
     }
     if (keycode == S_ARW && record->event.pressed) {
        SEND_STRING("->");
     }
     if (keycode == D_ARW && record->event.pressed) {
        SEND_STRING("=>");
     }
+    if (keycode == MC_HOME && record->event.pressed) {
+       register_code(KC_LGUI);
+       tap_code(KC_LEFT);
+       unregister_code(KC_LGUI);
+    }
+    if (keycode == MC_END && record->event.pressed) {
+       register_code(KC_LGUI);
+       tap_code(KC_RGHT);
+       unregister_code(KC_LGUI);
+    }
     if (keycode == SYM_SPC) {
           :
           :
     }
     return true;
  }

合わせてターミナルの設定を変更しよう。
当然のことながら、Homeキーではなくなったのでこのキーを押してもCLIの各コマンドにはHomeキーとして伝わらない。私の愛用エディタ、Vimももちろんそうだ。

「Home」という制御文字はASCIIコードに存在しないので、Homeキーの入力はエスケープシーケンスで扱われる。
具体的にどのエスケープシーケンスになるかは環境によって変わるらしい(このあたりについては私がよくわかっていない)ので、まずはエスケープシーケンスを調べる。
ターミナルでCtrl+vを押したあと本物のHomeキーを押す。
私の環境では ^[OH が出た。

ターミナルの設定を変更して、"⌘+←"を入力したときにHomeキーのエスケープシーケンスを送るようにする。
iTerm2ならPreferences→Keys→Key Bindingsにショートカット"⌘+←"を追加してアクションSend Escape Sequence(Esc+OH)を設定。

これでだいたいのアプリケーションで期待通りの動きをしてくれるはずだ。

リファクタリング

何回同じコード書くねんという感じになっているので共通化する。

keyboards/keyboard_name/keymaps/keymap_name/custom_keys.c
enum key_state {
   RELEASED,
   PRESSED,
   HOLDEN
};

void nop(void) {}

typedef struct {
   enum custom_keycodes custom_keycode;
   enum key_state state;
   uint16_t pressed_timer;

   void (* on_pressed)(void);
   void (* on_tapped)(void);
   void (* on_interrupted)(void);
   void (* on_start_holding)(void);
   void (* on_release_holding)(void);
} key_t;

int all_key_count;
key_t *all_keys[];

void on_pressed(key_t *key) {
   enum key_state prev_state = key->state;
   key->state = PRESSED;
   key->pressed_timer = timer_read();
   if (prev_state != PRESSED) {
      key->on_pressed();
   }
}

void on_interrupted(key_t *key) {
   enum key_state prev_state = key->state;
   key->state = HOLDEN;
   if (prev_state != HOLDEN) {
      key->on_interrupted();
   }
}

void on_start_holding(key_t *key) {
   enum key_state prev_state = key->state;
   key->state = HOLDEN;
   if (prev_state != HOLDEN) {
      key->on_start_holding();
   }
}

void on_released(key_t *key) {
   enum key_state prev_state = key->state;
   key->state = RELEASED;
   if (prev_state == PRESSED) {
      key->on_tapped();
   }
   if (prev_state == HOLDEN) {
      key->on_release_holding();
   }
}

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
   for (int i = 0; i < all_key_count; i++) {
      key_t *key = all_keys[i];

      if (key->custom_keycode != keycode) {
         if (key->state == PRESSED && record->event.pressed) {
            on_interrupted(key);
         }
      } else {
         if (record->event.pressed) {
            on_pressed(key);
         } else {
            on_released(key);
         }
      }
   }

   return true;
}

void matrix_scan_user(void) {
   for (int i = 0; i < all_key_count; i++) {
      key_t *key = all_keys[i];

      if (key->state == PRESSED) {
         if (timer_elapsed(key->pressed_timer) > TAPPING_TERM) {
            on_start_holding(key);
         }
      }
   }
}
keyboards/keyboard_name/keymaps/keymap_name/keymap.c
#include "custom_keys.h"

enum layer_number {
  _DVORAK = 0,
  _NUM,
  _SYMBOL,
  _NUM_KEYPAD,
  _FN
};

#define SFT_Z SFT_T(KC_Z)
#define TG_NUM TG(_NUM_KEYPAD)
#define ALT_ESC ALT_T(KC_ESC)
#define SYM_SPC LT(_SYMBOL, KC_SPC)
#define NUM_ENT LT(_NUM, KC_ENT)
#define FN_KANA LT(_FN, KC_F13)

enum custom_keycodes {
   ESC_SFT = SAFE_RANGE,
   S_ARW,
   D_ARW,
   PLSASGN,
   MNSASGN,
   ASTASGN,
   SLSASGN,
   COMMENT,
   MC_HOME,
   MC_END
};

void tap_ctrl_brc(void) {
   register_code(KC_RCTL);
   tap_code(KC_LBRC);
   unregister_code(KC_RCTL);
}

void register_shift(void) {
   register_code(KC_RSFT);
}

void unregister_shift(void) {
   unregister_code(KC_RSFT);
}

key_t esc_sft = {
   ESC_SFT, RELEASED, 0,
   /* on_pressed         = */ nop,
   /* on_tapped          = */ tap_ctrl_brc,
   /* on_interrupted     = */ register_shift,
   /* on_start_holding   = */ register_shift,
   /* on_release_holding = */ unregister_shift
};

void register_space(void) {
   register_code(KC_SPC);
}

void unregister_space(void) {
   unregister_code(KC_SPC);
}

key_t sym_spc = {
   SYM_SPC, RELEASED, 0,
   /* on_pressed         = */ nop,
   /* on_tapped          = */ nop,
   /* on_interrupted     = */ nop,
   /* on_start_holding   = */ register_space,
   /* on_release_holding = */ unregister_space
};

void send_s_arw  (void) { SEND_STRING("->"); }
void send_d_arw  (void) { SEND_STRING("=>"); }
void send_plsasgn(void) { SEND_STRING("+="); }
void send_mnsasgn(void) { SEND_STRING("-="); }
void send_astasgn(void) { SEND_STRING("*="); }
void send_slsasgn(void) { SEND_STRING("/="); }
void send_comment(void) { SEND_STRING("/*"); }

void send_gui_left(void) {
   register_code(KC_LGUI);
   tap_code(KC_LEFT);
   unregister_code(KC_LGUI);
}

void send_gui_right(void) {
   register_code(KC_LGUI);
   tap_code(KC_RGHT);
   unregister_code(KC_LGUI);
}

key_t s_arw   = { S_ARW,   RELEASED, 0, send_s_arw,     nop, nop, nop, nop };
key_t d_arw   = { D_ARW,   RELEASED, 0, send_d_arw,     nop, nop, nop, nop };
key_t plsasgn = { PLSASGN, RELEASED, 0, send_plsasgn,   nop, nop, nop, nop };
key_t mnsasgn = { MNSASGN, RELEASED, 0, send_mnsasgn,   nop, nop, nop, nop };
key_t astasgn = { ASTASGN, RELEASED, 0, send_astasgn,   nop, nop, nop, nop };
key_t slsasgn = { SLSASGN, RELEASED, 0, send_slsasgn,   nop, nop, nop, nop };
key_t comment = { COMMENT, RELEASED, 0, send_comment,   nop, nop, nop, nop };
key_t mc_home = { MC_HOME, RELEASED, 0, send_gui_left,  nop, nop, nop, nop };
key_t mc_end  = { MC_END,  RELEASED, 0, send_gui_right, nop, nop, nop, nop };

int all_key_count = 11;
key_t *all_keys[] = {
   &esc_sft, &sym_spc, &s_arw, &d_arw, &plsasgn, &mnsasgn, &astasgn, &slsasgn,
   &comment, &mc_home, &mc_end
};

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
  [_DVORAK] = LAYOUT(
      KC_GRV , KC_1   , KC_2   , KC_3   , KC_4   , KC_5   ,                       KC_6   , KC_7   , KC_8   , KC_9   , KC_0   , KC_SLSH,
      KC_TAB , KC_QUOT, KC_COMM, KC_DOT , KC_P   , KC_Y   ,                       KC_F   , KC_G   , KC_C   , KC_R   , KC_L   , KC_EQL ,
      KC_LCTL, KC_A   , KC_O   , KC_E   , KC_U   , KC_I   ,                       KC_D   , KC_H   , KC_T   , KC_N   , KC_S   , KC_LBRC,
      KC_LSFT, SFT_Z  , KC_Q   , KC_J   , KC_K   , KC_X   ,                       KC_B   , KC_M   , KC_W   , KC_V   , KC_SCLN, KC_MINS,
                                 ALT_ESC, KC_UNDS, SYM_SPC, KC_BSPC,     ESC_SFT, NUM_ENT, FN_KANA, KC_RGUI
  ),

  [_NUM] = LAYOUT(
      _______, _______, _______, _______, _______, _______,                       _______, _______, _______, _______, _______, _______,
      _______, _______, _______, _______, _______, _______,                       _______, _______, _______, _______, _______, _______,
      KC_GRV , KC_1   , KC_2   , KC_3   , KC_4   , KC_5   ,                       KC_6   , KC_7   , KC_8   , KC_9   , KC_0   , KC_SLSH,
      _______, _______, _______, _______, _______, _______,                       _______, _______, _______, _______, _______, _______,
                                 _______, _______, _______, _______,     _______, _______, _______, _______
  ),

  [_SYMBOL] = LAYOUT(
      _______, _______, _______, _______, _______, _______,                       _______, _______, _______, _______, _______, _______,
      _______, SLSASGN, ASTASGN, MNSASGN, PLSASGN, COMMENT,                       KC_RBRC, S_ARW  , D_ARW  , _______, _______, KC_BSLS,
      KC_TILD, KC_EXLM, KC_AT  , KC_HASH, KC_DLR , KC_PERC,                       KC_CIRC, KC_AMPR, KC_ASTR, KC_LPRN, KC_RPRN, KC_QUES,
      _______, _______, _______, _______, _______, _______,                       _______, _______, _______, _______, _______, _______,
                                 _______, _______, _______, _______,     _______, _______, _______, _______
  ),

  [_NUM_KEYPAD] = LAYOUT(
      _______, _______, _______, _______, _______, _______,                       _______, _______, _______, _______, _______, _______,
      _______, _______, _______, _______, _______, _______,                       KC_NLCK, KC_P7  , KC_P8  , KC_P9  , _______, _______,
      _______, _______, _______, _______, _______, _______,                       _______, KC_P4  , KC_P5  , KC_P6  , _______, _______,
      _______, _______, _______, _______, _______, _______,                       KC_P0  , KC_P1  , KC_P2  , KC_P3  , _______, _______,
                                 _______, _______, _______, _______,     _______, _______, _______, _______
  ),

  [_FN] = LAYOUT(
      KC_F12 , KC_F1  , KC_F2  , KC_F3  , KC_F4  , KC_F5  ,                       KC_F6  , KC_F7  , KC_F8  , KC_F9  , KC_F10 , KC_F11 ,
      _______, _______, _______, _______, _______, _______,                       _______, _______, _______, _______, _______, _______,
      _______, _______, _______, KC_PSCR, _______, _______,                       KC_LEFT, KC_DOWN, KC_UP  , KC_RGHT, _______, _______,
      _______, _______, _______, _______, _______, _______,                       _______, MC_HOME, MC_END , _______, _______, _______,
                                 KC_RGUI, _______, _______, KC_DEL ,     _______, TG_NUM , _______, _______
  )
};

うーん…。
私のC言語力が足りなくてあんまりスマートに書けなかったです。

おわりに

キーボードを叩く仕事をしている以上、使用するキーボードに露ほども不満を残してはいけません。
50ミリ秒のTAPPING_TERMの調整、QMKが用意してないなら実装すればいいじゃないという精神、ときにはターミナルの設定をいじってでも思い通りに入力してやるのです。

みんなも参考にしてみてね

6
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6