🤖

メカニカルキーボード【Keychron K8 Pro】の設定

2024/01/28に公開

初めての記事です。

仕事で使ってるPCがUS配列のWindowsでIME ON/OFFがalt + `などする必要があり、常々面倒だなっと思っていた。PowerToysのkeyboard managerでどうにかできると思うが、入れることができない環境なのであきらめていた。

そんな時にWindowsがLang1/2に対応したことを今更ながら知った。

これでUS配列でも無変換でIME OFF、変換でIME ONっぽいことができる。
US配列なので変換/無変換はないのでcapslockなど不要なキーを犠牲にするか、comboやTap Danceを使うことになる。
今回はTap Danceを使うことにする。

なのでKeychron K8 Proのキーマップを作成してみる。
ついでqmkのビルド環境の準備もまとめてみる。

前提

  • git、vagrantが使えること。
    dockerfileも用意されてるみたいなのでdockerでも同じことができるかも。
    vagrant上でビルドするのでwindowsでもwslでもlinuxでもmacでも問題ないと思う。
  • qmk_toolboxをインストールしていること。

ビルドとフラッシュの動作確認

defaultをビルドして正常に成功するか確認する。
ビルドできた場合、hostのqmk_firmware/.buildにビルド結果が出力される。

  1. keychronのソースコードを取得する。
    host
    git clone --recurse-submodules git@github.com:Keychron/qmk_firmware.git
    cd qmk_firmware
    git checkout bluetooth_playground
    
  2. vagrantの起動とssh接続
    host
    vagrant up
    vagrant ssh
    
  3. defaultのキーマップをビルド
    vagrant
    cd vagrant
    make keychron/k8_pro/ansi/white:default
    
  4. キーボードに書き込む
    1. qmk toolboxを起動する。
    2. キーボード側面のスイッチを「OFF」にする。
    3. Keychron K8 ProをUSBケーブルでPCに接続する。
    4. スペースキー下左側にある「reset」ボタンを押しながらキーボード側面のスイッチを「cable」にする。
    5. qmk toolboxの「Open」を押下してqmk_firmware/.buildの中にあるbinファイルを選択する。
    6. 「Flash」を押下して書き込む。
      「File downloaded successfully」と表示されれば書き込み成功。

キーマップの編集

ここまででqmkのビルドとフラッシュができるようになったのでキーマップをkeymap.cその他諸々を編集していく。

  1. defaultのキーマップをコピーしてカスタム用のフォルダを作成する。

    host
    cd qmk_firmware/keyboards/keychron/k8_pro/ansi/white/keymaps/
    cp default myKeyMap
    
  2. 作成したmyKeyMapに下記ファイルを格納する。

    rules.mk
    rules.mk
    TAP_DANCE_ENABLE = yes
    
    SRC += myTapDance.c
    
    myKeyMap.h
    myKeyMap.h
    #pragma once
    
    #include QMK_KEYBOARD_H
    #include "myTapDance.h"
    
    enum layers{
      MAC_BASE,
      MAC_FN,
      WIN_BASE,
      WIN_FN
    };
    
    keymap.c
    keymap.c
    #include "myKeyMap.h"
    
    const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [MAC_BASE] = LAYOUT_tkl_ansi(
         KC_ESC,   KC_BRID,  KC_BRIU,  KC_MCTL,  KC_LPAD,  BL_DOWN,  BL_UP,    KC_MPRV,  KC_MPLY,  KC_MNXT,  KC_MUTE,  KC_VOLD,  KC_VOLU,           KC_SNAP,   KC_SIRI,  BL_STEP,
         KC_GRV,   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_BSPC, KC_INS,    KC_HOME,  KC_PGUP,
         KC_TAB,   KC_Q,     KC_W,     KC_E,     KC_R,     KC_T,     KC_Y,     KC_U,     KC_I,     KC_O,     KC_P,     KC_LBRC,  KC_RBRC,  KC_BSLS, KC_DEL,    KC_END,   KC_PGDN,
         KC_CAPS,  KC_A,     KC_S,     KC_D,     KC_F,     KC_G,     KC_H,     KC_J,     KC_K,     KC_L,     KC_SCLN,  KC_QUOT,            KC_ENT,
         KC_LSFT,            KC_Z,     KC_X,     KC_C,     KC_V,     KC_B,     KC_N,     KC_M,     KC_COMM,  KC_DOT,   KC_SLSH,            KC_RSFT,            KC_UP,
         KC_LCTL,  KC_LOPTN, KC_LCMMD,                               KC_SPC,                                 KC_RCMMD, KC_ROPTN, MO(MAC_FN),KC_RCTL,  KC_LEFT,  KC_DOWN,  KC_RGHT),
    
    [MAC_FN] = LAYOUT_tkl_ansi(
         _______,  KC_F1,    KC_F2,    KC_F3,    KC_F4,    KC_F5,    KC_F6,    KC_F7,    KC_F8,    KC_F9,    KC_F10,   KC_F11,   KC_F12,             _______,  _______,  BL_TOGG,
         _______,  BT_HST1,  BT_HST2,  BT_HST3,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,
         BL_TOGG,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,
         _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,            _______,
         _______,            _______,  _______,  _______,  _______,  BAT_LVL,  NK_TOGG,  _______,  _______,  _______,  _______,            _______,            _______,
         _______,  _______,  _______,                                _______,                                _______,  _______,  _______,  _______,  _______,  _______,  _______),
    
    [WIN_BASE] = LAYOUT_tkl_ansi(
         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_PSCR,   KC_CTANA, BL_STEP,
         KC_GRV,   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_BSPC,  KC_INS,    KC_HOME,  KC_PGUP,
         KC_TAB,   KC_Q,     KC_W,     KC_E,     KC_R,     KC_T,     KC_Y,     KC_U,     KC_I,     KC_O,     KC_P,     KC_LBRC,  KC_RBRC,  KC_BSLS,  KC_DEL,    KC_END,   KC_PGDN,
         KC_CAPS,  KC_A,     KC_S,     KC_D,     KC_F,     KC_G,     KC_H,     KC_J,     KC_K,     KC_L,     KC_SCLN,  KC_QUOT,            KC_ENT,
         TD_LSHIFTLANG1,     KC_Z,     KC_X,     KC_C,     KC_V,     KC_B,     KC_N,     KC_M,     KC_COMM,  KC_DOT,   KC_SLSH,            TD_RSHIFTLANG2,     KC_UP,
         KC_LCTL,  KC_LGUI,  KC_LALT,                                TD_SPCL1,                               KC_RALT,  KC_RGUI, MO(WIN_FN),KC_RCTL,  KC_LEFT,  KC_DOWN,  KC_RGHT),
    
    [WIN_FN] = LAYOUT_tkl_ansi(
         _______,  KC_BRID,  KC_BRIU,  KC_TASK,  KC_FILE,  BL_DOWN,  BL_UP,    KC_MPRV,  KC_MPLY,  KC_MNXT,  KC_MUTE,  KC_VOLD,  KC_VOLU,            _______,  _______,  _______,
         _______,  BT_HST1,  BT_HST2,  BT_HST3,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,
         _______,  _______,  _______,  _______,  _______,  _______,  _______,  TD_HMPGUP,KC_UP,    TD_EDPGDN,_______,  _______,  _______,  _______,  _______,  _______,  _______,
         _______,  _______,  _______,  _______,  TD_LKKK,  TD_RKKK,  _______,  KC_LEFT,  KC_DOWN,  KC_RGHT,  _______,  _______,            _______,
         _______,            _______,  _______,  _______,  _______,  BAT_LVL,  _______,  _______,  _______,  _______,  _______,            _______,            _______,
         _______,  _______,  _______,                                _______,                                _______,  _______,  _______,  _______,  _______,  _______,  _______)
    
    };
    
    myTapDance.h
    myTapDance.h
    #pragma once
    
    #include "myKeyMap.h"
    
    enum {
      SINGLE_TAP = 1,
      SINGLE_HOLD,
      DOUBLE_TAP,
      TRIPLE_TAP,
      QUADRUPLE_TAP,
    };
    
    typedef struct {
      bool is_press_action;
      int state;
    } tap;
    
    enum {
      HMPGUP, // home, PgUp
      EDPGDN, // end, PgDn
    
      LSHIFTLANG1,  // L shift, LANG1
      RSHIFTLANG2,  // R shift, LANG2
    
      LKAKKO, // (,[,{,<
      RKAKKO, // ),],},>
    
      SPCL1,  // SPACE, L1
    };
    
    #define TD_HMPGUP TD(HMPGUP)
    #define TD_EDPGDN TD(EDPGDN)
    
    #define TD_LSHIFTLANG1 TD(LSHIFTLANG1)
    #define TD_RSHIFTLANG2 TD(RSHIFTLANG2)
    
    #define TD_LKKK TD(LKAKKO)
    #define TD_RKKK TD(RKAKKO)
    
    #define TD_SPCL1 TD(SPCL1)
    
    int cur_dance (tap_dance_state_t *);
    void x_finished_1 (tap_dance_state_t *, void *);
    void x_reset_1 (tap_dance_state_t *, void *);
    void x_finished_2 (tap_dance_state_t *, void *);
    void x_reset_2 (tap_dance_state_t *, void *);
    void x_finished_3 (tap_dance_state_t *, void *);
    void x_reset_3 (tap_dance_state_t *, void *);
    void x_finished_4 (tap_dance_state_t *, void *);
    void x_reset_4 (tap_dance_state_t *, void *);
    void x_finished_5 (tap_dance_state_t *, void *);
    void x_reset_5 (tap_dance_state_t *, void *);
    
    myTapDance.c
    myTapDance.c
    #include "myTapDance.h"
    
    tap_dance_action_t tap_dance_actions[] = {
        [HMPGUP] = ACTION_TAP_DANCE_DOUBLE(KC_HOME, KC_PGUP),
        [EDPGDN] = ACTION_TAP_DANCE_DOUBLE(KC_END, KC_PGDN),
    
        [LKAKKO] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, x_finished_1, x_reset_1),
        [RKAKKO] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, x_finished_2, x_reset_2),
    
        [LSHIFTLANG1] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, x_finished_3, x_reset_3),
        [RSHIFTLANG2] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, x_finished_4, x_reset_4),
    
        [SPCL1] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, x_finished_5, x_reset_5),
    };
    
    int cur_dance (tap_dance_state_t *state) {
      if (state->count == 1) {
        if (!state->pressed){
    	return SINGLE_TAP;
        }
        else{
    	return SINGLE_HOLD;
        }
      }
      else if (state->count == 2) {
        return DOUBLE_TAP;
      }
      else if (state->count == 3) {
        return TRIPLE_TAP;
      }
      else if (state->count == 4) {
        return QUADRUPLE_TAP;
      }
      else {
        return 6; //magic number.
      }
    };
    
    //instanalize an instance of 'tap' for the 'x' tap dance.
    static tap xtap_state = {
      .is_press_action = true,
      .state = 0
    };
    
    // SPCL1用
    static tap xtap_state_SPCL1 = {
      .is_press_action = true,
      .state = 0
    };
    
    // tap:(、ダブルタップ:{、トリプルタップ:[、トリプルタップで<
    void x_finished_1 (tap_dance_state_t *state, void *user_data) {
      xtap_state.state = cur_dance(state);
      switch (xtap_state.state) {
        case SINGLE_TAP:                    // タップで(
    	register_code16(KC_LPRN);
    	break;
        case DOUBLE_TAP:                    // ダブルタップで{
    	register_code16(KC_LCBR);
    	break;
        case TRIPLE_TAP:                    // トリプルタップで[
    	register_code16(KC_LBRC);
    	break;
        case QUADRUPLE_TAP:                 // トリプルタップで<
    	register_code16(KC_LABK);
    	break;
        default:
    	break;
      }
    };
    
    void x_reset_1 (tap_dance_state_t *state, void *user_data) {
      switch (xtap_state.state) {
        case SINGLE_TAP:                    // タップで(
    	unregister_code16(KC_LPRN);
    	break;
        case DOUBLE_TAP:                    // ダブルタップで{
    	unregister_code16(KC_LCBR);
    	break;
        case TRIPLE_TAP:                    // トリプルタップで[
    	unregister_code16(KC_LBRC);
    	break;
        case QUADRUPLE_TAP:                 // トリプルタップで<
    	unregister_code16(KC_LABK);
    	break;
        default:
    	break;
      }
      xtap_state.state = 0;
    };
    
    // tap:)、ダブルタップ:}、トリプルタップ:]、トリプルタップで>
    void x_finished_2 (tap_dance_state_t *state, void *user_data) {
      xtap_state.state = cur_dance(state);
      switch (xtap_state.state) {
        case SINGLE_TAP:                    // タップで)
    	register_code16(KC_RPRN);
    	break;
        case DOUBLE_TAP:                    // ダブルタップで}
    	register_code16(KC_RCBR);
    	break;
        case TRIPLE_TAP:                    // トリプルタップで]
    	register_code16(KC_RBRC);
    	break;
        case QUADRUPLE_TAP:                 // トリプルタップで>
    	register_code16(KC_RABK);
    	break;
        default:
    	break;
      }
    };
    
    void x_reset_2 (tap_dance_state_t *state, void *user_data) {
      switch (xtap_state.state) {
        case SINGLE_TAP:                    // タップで)
    	unregister_code16(KC_RPRN);
    	break;
        case DOUBLE_TAP:                    // ダブルタップで}
    	unregister_code16(KC_RCBR);
    	break;
        case TRIPLE_TAP:                    // トリプルタップで]
    	unregister_code16(KC_RBRC);
    	break;
        case QUADRUPLE_TAP:                 // トリプルタップで>
    	unregister_code16(KC_RABK);
    	break;
        default:
    	break;
      }
      xtap_state.state = 0;
    };
    
    // tap:L shuft、ダブルタップ:LANG1、ホールドでL shiift
    void x_finished_3 (tap_dance_state_t *state, void *user_data) {
      xtap_state.state = cur_dance(state);
      switch (xtap_state.state) {
        case SINGLE_TAP:                    // タップでL shiift
    	register_code16(KC_LSFT);
    	break;
        case DOUBLE_TAP:                    // ダブルタップでLANG1
    	register_code16(KC_LNG1);
    	break;
        case SINGLE_HOLD:                   // ホールドでL shift
    	register_code16(KC_LSFT);
    	break;
        default:
    	break;
      }
    };
    
    void x_reset_3 (tap_dance_state_t *state, void *user_data) {
      switch (xtap_state.state) {
        case SINGLE_TAP:                    // タップでL shift
    	unregister_code16(KC_LSFT);
    	break;
        case DOUBLE_TAP:                    // ダブルタップでLANG1
    	unregister_code16(KC_LNG1);
    	break;
        case SINGLE_HOLD:                   // ホールドでL shift
    	unregister_code16(KC_LSFT);
    	break;
        default:
    	break;
      }
      xtap_state.state = 0;
    };
    
    // tap:R shuft、ダブルタップ:LANG2、ホールドでR shiift
    void x_finished_4 (tap_dance_state_t *state, void *user_data) {
      xtap_state.state = cur_dance(state);
      switch (xtap_state.state) {
        case SINGLE_TAP:                    // タップでR shiift
    	register_code16(KC_RSFT);
    	break;
        case DOUBLE_TAP:                    // ダブルタップでLANG2
    	register_code16(KC_LNG2);
    	break;
        case SINGLE_HOLD:                   // ホールドでR shift
    	register_code16(KC_RSFT);
    	break;
        default:
    	break;
      }
    };
    
    void x_reset_4 (tap_dance_state_t *state, void *user_data) {
      switch (xtap_state.state) {
        case SINGLE_TAP:                    // タップでR shift
    	unregister_code16(KC_RSFT);
    	break;
        case DOUBLE_TAP:                    // ダブルタップでLANG2
    	unregister_code16(KC_LNG2);
    	break;
        case SINGLE_HOLD:                   // ホールドでR shift
    	unregister_code16(KC_RSFT);
    	break;
        default:
    	break;
      }
      xtap_state.state = 0;
    };
    
    // tap:SPACE、ホールドでL1
    void x_finished_5 (tap_dance_state_t *state, void *user_data) {
      xtap_state_SPCL1.state = cur_dance(state);
      switch (xtap_state_SPCL1.state) {
        case SINGLE_TAP:                    // タップでSPACE
    	register_code16(KC_SPC);
    	break;
        case SINGLE_HOLD:                   // ホールドでL1
    	layer_on(WIN_FN);
    	break;
        default:
    	break;
      }
    };
    
    void x_reset_5 (tap_dance_state_t *state, void *user_data) {
      switch (xtap_state_SPCL1.state) {
        case SINGLE_TAP:                    // タップでSPACE
    	unregister_code16(KC_SPC);
    	break;
        case SINGLE_HOLD:                   // ホールドでL1
    	layer_off(WIN_FN);
    	break;
        default:
    	break;
      }
      xtap_state_SPCL1.state = 0;
    };
    
  3. myKeyMapをビルドする。

    vagrant
    make keychron/k8_pro/ansi/white:myKeyMap
    

説明

mac用レイヤー2つは基本的に触っていない(使わないので放置)。
win_baseの動作は下記。

  • 基本的には普通のキーボード通り。
  • 左シフト2回連打でIME ON
  • 右シフト2回連打でIME OFF
  • スペース長押しでレイヤー移動

win_fn

  • 方向キー各種
  • かっこ各種

詳細は下記の通り。
win_baseレイヤー

操作 動作
左シフトを1回押す 普通のシフト
左シフトを2回押す Lang1(変換)
左シフトを長押し シフトの長押し
右シフトを1回押す 普通のシフト
右シフトを2回押す Lang2(無変換)
右シフトを長押し シフトの長押し
スペースを1回押す スペース
スペースを長押し win_fnレイヤーに移行

win_fnレイヤー(スペースを押しながら)

操作 動作
J
K
L
I
Uを1回押す home
Uを2回押す pageUp
Oを1回押す end
Oを2回押す pageDown
Fを1回押す (
Fを2回押す [
Fを3回押す {
Fを4回押す <
Gを1回押す )
Gを2回押す ]
Gを3回押す }
Gを4回押す >

Discussion