macOSとWindowsで仲良くやっていくためのレイヤー設定

この投稿は :calendar: [ErgoDox Advent Calendar 2016](http://qiita.com/advent-calendar/2016/ergodox) の 12日目の記事です。

----

# 課題

- macOSの`Cmdキー`はWindowsだと`Winキー`になるよ
- 同じ感覚で使うために`Cmd`のところはWindowsでは`Ctrl`にしたいよ
- じゃあOS毎のレイヤーを作って設定しよう
- Linuxは残念ながら使ってないので書いてません…
- 単にレイヤー設定だけなら`DF`とか`TO`でいいんですが、今回はカスタムキーコードを使うやり方を書きます

# やり方

## 1. 各OS用の差分レイヤーを作成する
### macOS用
```
       ,-------------.       ,---------------.
       |ctl F2|      |       |ctl sp| ctl F3 |
,------|------|------|       |------+--------+------.
|      |      |      |       |      |        |      |
| Space| LAlt |------|       |------|        |      |
|  Cmd |      | LCtrl|       |      |        |      |
`--------------------'       `----------------------'
```

### Windows用
```
       ,-------------.       ,-------------.
       |Alt F4|      |       |      |      |
,------|------|------|       |------+------+------.
|      |      |      |       |      |      |      |
| Space| LAlt |------|       |------|      |      |
| LCtrl|      | Win  |       |      |      |      |
`--------------------'       `--------------------'
```

一例ですが、こんな感じにOSによって変更したい部分だけのレイヤーを用意することでMacのときには`Cmd`、Windowsのときには`Ctrl`として設定を変えるというのが今回の趣旨です。

## 2. カスタムキーコードを作る

レイヤー切り替え用のキーを設定します。

```keymap.c
enum custom_keycodes {
  //--layers--
  // layouts
  QWERTY = SAFE_RANGE,
  QWDR,
  GAME,

  // momentary layer
  NAV,
  CONF,
  GAME2,

  // os
  MAC,
  WIN,
};
```

最近[ErgoDoxのデフォルトキーマップ](https://github.com/jackhumbert/qmk_firmware/blob/cd413550693564cb17da362f27a540d577271d36/keyboards/ergodox/keymaps/default/keymap.c)にも追加されていたんですが、カスタムキーコードというものを設定して使います。enumの開始行で指定している`SAFE_RANGE`というのは[キーコード定義の最後で宣言されているもの](https://github.com/jackhumbert/qmk_firmware/blob/51ae6da99edd732ccdec6e102bd757e08950d23b/quantum/keymap.h#L186)なので、これ以降のキーコードを使うことでカスタム専用の入力ができます。

※ただ、このキーコードの注意点としては通常キーの`0x0000-0x00FF`の範囲外となるので`CTL_T(keycode)`といったアクション設定などはそのままでは利用できません。

## 3.カスタムキーコードをレイヤーに割り当てる

```keymap.c
  RESET,      MAC,        WIN,        _______,    _______,    _______,    _______,
  _______,    _______,    _______,    _______,    _______,    QWERTY,     _______,
  _______,    _______,    _______,    _______,    _______,    QWDR,       _______,
              _______,    _______,    _______,    _______,    GAME,       _______,
```

好きなところに配置しましょう。
自分の場合、設定系の入力は2段構えでレイヤー移動した先のコンフィグ用レイヤーに割り当ててます。

## 4.カスタムキー入力ハンドラ

カスタムキーコードの入力をどこで処理するかというと`process_record_user`というところです。

```keymap.c
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
  switch (keycode) {
    //--layers--

    //os
    case MAC:
      if (record->event.pressed) {
        persistant_default_layer_set(1UL<<L_MAC);
      }
      return false;
      break;
    case WIN:
      if (record->event.pressed) {
        persistant_default_layer_set(1UL<<L_WIN);
      }
      return false;
      break

    //layout
    case QWERTY:
      if (record->event.pressed) {
        layer_move(L_QWERTY);
      }
      return false;
      break;
    case QWDR:
      if (record->event.pressed) {
        layer_move(L_QWDR);
      }
      return false;
      break;
    case GAME:
      if (record->event.pressed) {
        layer_move(L_GAME);
      }
      return false;
      break;

    //momentary layer
    case GAME2:
      switch_layer(L_GAME2, record->event.pressed);
      return false;
      break;
    case NAV:
      switch_layer(L_NAV, record->event.pressed);
      return false;
      break;
    case CONF:
      switch_layer(L_CONF, record->event.pressed);
      return false;
      break;
  }
  return true;
}
```
[ソース全文](https://github.com/miyaoka/qmk_firmware/blob/e4089df48dfba19b3b45a6d8d731e11f554d186e/keyboards/ergodox/keymaps/miyaoka/keymap.c)

ここでやっているのは主に3つです。

- OS用のレイヤー設定をdefault_layerで指定
- 文字配列をlayer_moveで指定
- ホールド時の一時的なレイヤーをlayerのオンオフで設定

## 5.レイヤーについて

レイヤーについては[公式のドキュメント](https://github.com/jackhumbert/qmk_firmware/blob/master/doc/keymap.md)で解説されています。

```keymap.md
    Overlay feature layer
    ---------------------      bit|status
           ____________        ---+------
      31  /           /        31 |   0
      30 /___________// -----> 30 |   1
      29 /___________/  -----> 29 |   1
       :                        : |   :
       :   ____________         : |   :
       2  /           /         2 |   0
    ,->1 /___________/  ----->  1 |   1
    |  0                        0 |   0
    |                                 +
    `--- default_layer = 1            |
         layer_state   = 0x60000002 <-'
```

レイヤー設定は32bitの情報で管理されていて各レイヤーがオンオフの状態を持っているので、好きなレイヤーを有効にして上層から掛け合わせることができます。

- default_layer_state
- layer_state

レイヤー設定は上記2つのステートがあり、default_layerに指定されているレイヤーは常にオンになります。

今回はOS用レイヤーをdefault_layerに指定し、さらに文字配列のレイヤーをlayer_moveで排他的に選択することで、OSと文字配列レイヤーをそれぞれ選択して掛け合わせるようにしました。

レイヤー設定を変えるアクションについては[このへん](https://github.com/jackhumbert/qmk_firmware/blob/master/tmk_core/common/action_layer.c)に定義されています。

## 6.初期レイヤー設定

```keymap.c
void matrix_init_user(void) {
  persistant_default_layer_set(1UL<<L_MAC);
  layer_move(L_QWDR);
};
```

キーボード接続時には`matrix_init_user`が呼ばれるので、ここで任意の初期レイヤーを設定することが可能です。

## 7.まとめ

冒頭にも書いたんですが、レイヤー切り替えだけなら`DF`とか`TO`だけでも大丈夫です。ただ、カスタムキーコードを使ったやり方だと他の処理もいろいろできるのでまとめてみました。

# その他：:warning:レイヤー変更時の修飾キーのスタックを防ぐ

レイヤー設定の話とは違いますが、ついでに書いておきます。

レイヤー切り替えと修飾キーを使っていると、たまにホールド状態が戻らずに修飾キーの入力がスタックするケースがあります。

## スタックする例

- レイヤー0にShiftが割り当てられている
- 同じキーがレイヤー1ではAが割り当てられている
- Shiftをホールドする
- そのままレイヤー1に移動
- Shiftキーを離す
- レイヤー0に戻る

こうするとShiftキーが解除されず、押しっぱなしの状態になります。なぜかというと、レイヤー1で離したときにはShiftではなくAキーをリリースしたことになるので解除されないわけです。

## 解決法

これは`config.h`に

```
#define PREVENT_STUCK_MODIFIERS
```

というオプションをつけることで防ぐことができます。

このへんの話は[ちょっと前のreadme](https://github.com/jackhumbert/qmk_firmware/blob/f39e1b5dfe7552f01dbc6eab95c268f41a9d98e2/readme.md#prevent-stuck-modifiers)に書いてあったのですが、最近肥大化してきて[doc](https://github.com/jackhumbert/qmk_firmware/tree/master/doc)や[Wiki](https://github.com/jackhumbert/qmk_firmware/wiki)のほうにまとめ直されているようで、[こちら](https://github.com/jackhumbert/qmk_firmware/wiki#prevent-stuck-modifiers)に記載が移動してました。