kanataで親指シフト

今回はクロスプラットフォームのキーボードリマッパーである[kanata](https://github.com/jtroo/kanata/tree/main)を用いて親指シフト(NICOLA配列)で打鍵できるようなコンフィギュレーションファイルを作ってみます．ただし，以下の処理が規格とは異なります．
親指シフト規格では「文字キー，親指キー，文字キー」と一定時間(同時打鍵と認識される時間)で打鍵した場合に各間隔の経過時間でどちらにシフトを適用するか判定しますが，今回の設定では決められた時間内に打鍵された場合１つ目の文字キーにシフトが適用されます．これは「する」を高速で打鍵した場合に「ずく」になる問題が起こる可能性があります．今回の設定ではキーに個別にタイムアウトを設定する対策しかできません．同様に一定時間(同時打鍵と認識される時間)内の「親指キー，文字キー，親指キー」に対しても，間隔による判定はせず１つ目の親指キーのシフトが適用されます．また，逐次打鍵にも対応していません．
設定ファル全体は[こちら](https://github.com/deepgreenAN/kanata_oyayubi_cfg)にあります．
今回の設定ファイルは自分が利用しているmozcに合わせて作っていますが，他のIMEでも同じように利用できると思います．

## defsrcの定義
kanataではまず物理キーボードに対応する`defsrc`を定義します．JISキーボードの`defsrc`は以下のようになります．kanataで利用できる記号とJISキーボードの記号が異なる場合は別名で表記しています．
```:oyayubi_single.kbd
(defsrc
  grv    1      2      3      4      5      6      7      8      9      0      -      eql    ¥      bspc
  tab    q      w      e      r      t      y      u      i      o      p      lbrc   rbrc   ent
  caps   a      s      d      f      g      h      j      k      l      ;      apo    bksl
  lsft   z      x      c      v      b      n      m      ,      .      /      ro     rsft
  lctl   lmet   lalt   mhnk   spc           hnk    kana   ralt   menu   rctl
)
```
こちらのソースに対してベースとなるレイヤ，各シフトなどの押下時に対応するレイヤを定義していきます．
## レイヤ定義
開始時のベースレイヤは以下です．半角モードの打鍵はこちらで行います．`@`で始まるキーアクションはエイリアスと呼ばれ，`defalias`内で定義します．`@on`は親指シフトレイヤの有効化(ベースレイヤの変更)とIMEのオンを行います．個人的な設定として利用頻度の低いcaps lockキーとかなキーはそれぞれバックスペースキーとエンターキーに割り当てています．その他の`_`はトランスパレントキーと呼ばれ，ベースレイヤで使う場合は`defsrc`で定義されたキーと同じ挙動を，一時的なレイヤで使う場合はベースレイヤのキーと同じ挙動をします．
```
(deflayer base
  @on    _      _      _      _      _      _      _      _      _      _      _      _      _      _
  _      _      _      _      _      _      _      _      _      _      _      _      _      _
  bspc   _      _      _      _      _      _      _      _      _      _      _      _
  _      _      _      _      _      _      _      _      _      _      _      _      _
  _      _      _      _      _             @on    ent    _      _      _
)
```
親指シフトモードのオン・オフを行う`@on`と`@off`は以下のように定義しています．ベースレイヤー切り替えと同時にIMEのオン・オフに設定されたキーを出力するようにします．`layer-switch`ではベースレイヤを恒久的に変更します．mozcのデフォルトやMS-IMEではIMEのオン・オフは半角/全角キー(グレイヴアクセントキー)となっています．
```
(defalias
  on (multi (layer-switch oyayubi) grv)
  off (multi (layer-switch base) grv)
)
```

親指シフトモードのベースレイヤは以下です．つまり全角モードの打鍵はこちらで行います．文字キーにはそれぞれ個別のエイリアスを与えています．この段階でレイヤにひらがなの出力を結びつけるやり方もありますが，親指シフトの同時打鍵という性格上，同時かどうかの判定を行う必要があります．今回の設定では左親指キーをスペースキー，右親指キーを変換キーとしており，親指キーの単独打鍵にも対応させます．また，shift，ctrl，altキーにもエイリアスを個別に設定しておきます．
```
(deflayer oyayubi
  @off   _      _      _      _      _      _      _      _      _      _      _      _      _      _
  _      @q     @w     @e     @r     @t     @y     @u     @i     @o     @p     @at    _      _
  bspc   @a     @s     @d     @f     @g     @h     @j     @k     @l     @;     _      _
  @lsft  @z     @x     @c     @v     @b     @n     @m     @,     @.     @/     _      @rsft
  @lctl  _      @lalt  _      @hdr          @mg    ent    @ralt  _      @rctl
)

```
各親指キー押下時のレイヤは以下です．これらのレイヤはそれぞれ対応する親指キーをホールドしている場合に一時的に有効になります．ここでも文字キーにそれぞれ個別のエイリアスを与えています．
```
(deflayer hidari-shift
  XX     _      _      _      _      _      _      _      _      _      _      _      _      _      _
  _      @h-q   @h-w   @h-e   @h-r   @h-t   @h-y   @h-u   @h-i   @h-o   @h-p   @h-at  _      _
  _      @h-a   @h-s   @h-d   @h-f   @h-g   @h-h   @h-j   @h-k   @h-l   @h-;   _      _
  _      @h-z   @h-x   @h-c   @h-v   @h-b   @h-n   @h-m   @h-,   @h-.   @h-/   _      _
  _      _      _      _      _             @mg    _      _      _      _
)

(deflayer migi-shift
  XX     _      _      _      _      _      _      _      _      _      _      _      _      _      _
  _      @m-q   @m-w   @m-e   @m-r   @m-t   @m-y   @m-u   @m-i   @m-o   @m-p   @m-at  _      _
  _      @m-a   @m-s   @m-d   @m-f   @m-g   @m-h   @m-j   @m-k   @m-l   @m-;   _      _
  _      @m-z   @m-x   @m-c   @m-v   @m-b   @m-n   @m-m   @m-,   @m-.   @m-/   _      _
  _      _      _      _      @hdr          _      _      _      _      _  
)
```

最後にshit, ctrl, altなどを押下したときにかなを出力せずに`defsrc`と同じ値を出力するため，`use-defsrc`を使ったレイヤも定義しておきます．`multi`は複数のキーアクションを同時に出力でき，`layer-while-held`はホールド時に指定したレイヤを有効化します．
```
(deflayer default
  _      _      _      _      _      _      _      _      _      _      _      _      _      _      _
  _      @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  _      _
  _      @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  _      _
  _      @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  @usrc  _      _
  _      _      _      _      @usrc         @usrc  _      _      _      _
)

(defalias
  usrc use-defsrc
  lsft (multi (layer-while-held default) lsft)
  rsft (multi (layer-while-held default) rsft)
  lctl (multi (layer-while-held default) lctl)
  rctl (multi (layer-while-held default) rctl)
  lalt (multi (layer-while-held default) lalt)
  ralt (multi (layer-while-held default) ralt)
)
```
## 各キーのレイヤとエイリアス
`oyayubi`ベースレイヤ(親指キーが押されていない段階のレイヤ)の各文字キーに対してエイリアスとその際に利用する個別のレイヤを定義していきます．ロジックとしては
1. まず，親指キーを押していない文字キーそのままのかな(`@j-ka`などで定義している)を出力する．
2. 同時に個別のレイヤを(`l-q`などで定義している)有効化しておく．そのレイヤでは左親指キー，右親指キーを割り当てる．
3. 左親指キー，右親指キーのそれぞれについて，文字キーの打鍵から一定時間(`$hdr-timeout`,`$mg-timeout`)内に押下された場合は左右それぞれに対応するかなをバックスペースを伴って(`@r-e`などで定義している)出力する．
4. 文字キーの打鍵から一定時間内に押されなかった場合は通常の左親指キー，右親指キーの挙動をとる．

のようにします．
```
;; テンプレートの定義
(deftemplate define-key-layer (layer-name hdr-action mg-action hdr-timeout mg-timeout)
  (deflayermap ($layer-name)
    spc (switch 
      ((key-timing 1 lt $hdr-timeout )) $hdr-action break
      () _ break
    );; 左親指シフト
    hnk (switch
      ((key-timing 1 lt $mg-timeout )) $mg-action break
      () _ break
    );; 右親指シフト
  )
)

(defvar
  default-key-timeout 50 ;; ms
)

;; テンプレートの利用
(template-expand define-key-layer l-q @r-la XX $default-key-timeout $default-key-timeout)
(template-expand define-key-layer l-w @r-e @r-ga $default-key-timeout $default-key-timeout)
(template-expand define-key-layer l-e @r-ri @r-da $default-key-timeout $default-key-timeout)
(template-expand define-key-layer l-r @r-lya @r-go $default-key-timeout $default-key-timeout)
...省略

(defalias
  q (multi @j-dot (layer-while-held l-q))
  w (multi @j-ka (layer-while-held l-w))
  e (multi @j-ta (layer-while-held l-e))
  r (multi @j-ko (layer-while-held l-r))
  ...省略
```

ここで`@j-ka`などや`@r-ga`などは以下のように定義しています．`macro`では連続的なキー出力を定義できます．今回はローマ字入力で定義していますが，もちろんかな入力での定義も可能です．クリップボードを経由して出力する方法もありますが，その場合はかなを貼り付けたのち既存のクリップボードの内容を復元する必要があるため多少時間がかかります．

```:roman.kbd
(defalias
  j-a a
  j-i i
  j-u u
  j-e e
  j-o o
  j-ka (macro k a)
  j-ki (macro k i)
  j-ku (macro k u)
  j-ke (macro k e)
  ...省略

(defalias
  r-a (macro bspc a)
  r-i (macro bspc i)
  r-u (macro bspc u)
  r-e (macro bspc e)
  r-o (macro bspc o)
  r-ka (macro bspc k a)
  r-ki (macro bspc k i)
  r-ku (macro bspc k u)
  r-ke (macro bspc k e)
  ...省略
```

次に`hidari-shift`レイヤ(左親指キー押下時のレイヤ)と`migi-shift`レイヤ(右親指キー押下時のレイヤ)の各文字キーに対してエイリアスを定義していきます．ロジックとしては
1. 各文字キーについて，親指キーの打鍵から連続に一定時間(`$shift-timeout`)内に押下された場合は対応するかな(`@j-la`など)を出力する
2. 親指キーの打鍵から連続に一定時間内に押されなかった場合は`oyayubi`ベースレイヤの各文字キーの挙動をとる．

のようにします．

```
;; テンプレートの定義
(deftemplate hidari-shifted-key (shifted-action shift-timeout)
  (switch
    ((and (key-timing 1 lt $shift-timeout) (input-history real spc 2))) $shifted-action break
    () _ break
  )
)

(deftemplate migi-shifted-key (shifted-action shift-timeout)
  (switch
    ((and (key-timing 1 lt $shift-timeout) (input-history real hnk 2))) $shifted-action break
    () _ break
  )
)

(defvar
  default-shift-timeout 50 ;; ms
)

;; 左親指シフト時の挙動
(defalias
  h-q (template-expand hidari-shifted-key @j-la $default-shift-timeout)
  h-w (template-expand hidari-shifted-key @j-e $default-shift-timeout)
  h-e (template-expand hidari-shifted-key @j-ri $default-shift-timeout)
  h-r (template-expand hidari-shifted-key @j-lya $default-shift-timeout)
  ...省略

;; 右親指シフト時の挙動
(defalias
  m-q XX
  m-w (template-expand migi-shifted-key @j-ga $default-shift-timeout)
  m-e (template-expand migi-shifted-key @j-da $default-shift-timeout)
  m-r (template-expand migi-shifted-key @j-go $default-shift-timeout)
  ...省略
```

最後に左親指キーと右親指キーの挙動を定義します．`tap-hold-press`では短いタップとホールドの挙動を変えることができ，ホールドしたまま指定した時間が経過するか，ホールドしたまま別のキーが押された場合にホールドアクション(第四引数)が実行されます．それ以外のタップなどはタップアクション(第三引数)が実行されます．今回の例ではホールドアクションとしては対応する各レイヤの有効化を行います(同時に行う`nop0`,`nop1`の出力は`key-timing`で時間を測るためです)．タップアクションは自由に機能を割り当てることができますが，個人的な設定として単独打鍵時の左親指キーには元の機能であるスペースキー，右親指キーには便利のためIMEオン・オフを割り当てています．

```
(defvar
  hidari-shift-tap-timeout 150 ;; ms
  migi-shift-tap-timeout 150 ;; ms
)

(defalias
  hdr (tap-hold-press $hidari-shift-tap-timeout $hidari-shift-tap-timeout spc (multi nop0 (layer-while-held hidari-shift)))
  mg (tap-hold-press $migi-shift-tap-timeout $migi-shift-tap-timeout @off (multi nop1 (layer-while-held migi-shift)))
)
```

以上のコンフィギュレーションファイルを用いてkanataを実行することで，親指シフトによる打鍵が可能になります(この設定では半角モードの時にkanataを実行してください)．この設定の使用感について，今まで利用していたエミュレーターと比べても今の所普通に使えています．ただし出力にバックスペースを伴うため，タイピングゲームなどでは利用できません．

以上はあくまでもキーボードリマッパーの設定ですので，親指シフト以外にも自分好みのキーボード配列やロジックを自由に使えることもkanataの良い点だと思います．未登録箇所や両親指シフトなどによく使う文末表現や助詞・接続詞などを割り当てるのも良いと思います．kanataではクリップボードを経由して漢字を含んだ任意の文字列を利用できますし，シェルのコマンドも実行できます．
kanataのインストール・デーモン化などに関しては[kanataのリポジトリ](https://github.com/jtroo/kanata/tree/main)をご確認ください．kanataの設定方法の詳細については[Configuration Guide](https://jtroo.github.io/config.html)をご確認ください．