complex modificationsのjson fileを利用したマルチボタンのマウス + macOS + Karabinerの設定

**settings for multi button mouse + macOS + Karabiner with complex modifications .json file**




# 何がしたいか
腱鞘炎対策にマルチボタンのついてる縦型マウスを買ったが，macOSは一部のメーカを除いてマウスごとのドライバというものがなく，またデフォルトではmagic mouse（左右ボタン+中ボタン兼スクローラ）と同程度の動作しか利用できない，そのためKarabinerといった入出力の変換ソフトが必要になる<br>
今回はマルチボタンの有効化とその簡単な割り当てを紹介するが，基本的に左・右・中のボタン（1番・2番・3番）はあんまりイジらない，既存の動作に依存や干渉の関係があると面倒



## 注意
`Karabiner`は仕様の関係で割と動作が強力であり，ほぼハードの動作と直結下手をすると壊れて操作を受け付けなくなる可能性がある，なので自己責任で丁寧に作業をすること<br>
一応はアプリごとに動作を変えられたりもするが，あくまでウィンドウがどこにあるか程度でしか判別してくれないので，もしそれがソフト側をイジるだけで解決する問題なら，なるべくソフト側だけをイジった方が良い<br>
例えば
↓

https://soluna-eureka.hatenablog.com/entry/2020/07/25/151457




# インストール



## cliで取得

```zsh
brew install --cask karabiner-elements
```

https://formulae.brew.sh/cask/karabiner-elements

これに限る<br>
`--cask`なので`/Applications/Karabiner-Elements.app`と`/Applications/Karabiner-EventViewer.app`があればok，起動しよう



## 起動，システムへのアクセス許可
初回起動の時点で色々と権限を要求されるので，`システム環境設定`->`セキュリティとプライバシー`から適宜に承認する



## フォルダの確認
`~/.config/karabiner/assets/complex_modifications`が勝手に生成されていればOK，その配下に定義に則って作成された任意の`json`ファイルを置くことで，自由に機能を追加できるし管理できる<br>
実際なんだかんだでGUIには頼らずテキストで保存した方がいい，GUIだけでは複雑な挙動を検討できないし，ハードウェア設定をも考慮した本体プロファイルと分けないと混乱する



## プログラムの確認
表からは`Karabiner-Elements`と`Karabiner-EventViewer`を起動できるが，前者は設定用・後者はデバッグ用に過ぎない<br>
実際は初回起動において監視・制御のデーモンを裏で勝手に展開してくれる，次回起動からはログイン時に勝手にそれらが起動する，ログイン項目に登録する必要はない（ただし再読み込みは`Karabiner-Elements`からやる必要がある）

![daemon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540440/42fc3ee1-9b45-ee60-604e-790d6a8a9a6a.png)




# 設定



## デバイス登録
有線・無線・Bluetoothは関係なく，使いたいマウスを接続したら`Karabiner-Elements`->`Devices`->`Basic configuration`に移動，`hoge Optional Mouse (fuga)`が見つかるはずなので，左端チェックボックスから有効化する<br>
ついでに`Vendor ID`と`Product ID`をメモする，これらは後で条件設定の時に使いたい

![devices.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540440/71285c54-d723-4822-6ed9-5c460c776753.png)

`Advanced`は特に要らない



## 基本設定
混乱回避のため，`Simple Modifications`を空欄にし，`Function keys`をリセットする（`To Key`をそのまま割り振ると良い）




# 拡張ファイルの製作

以下に一例を示す<br>

+ 共通設定
  + 5番のみで`tab`
  + 4番のみで`shift`+`tab`
+ ブラウザのみ
  + 1番+2番で`command`+`r`
  + 5番+6番で`command`+`w`
  + 1番+5番で`command`+`click`
  + 1番+4番で`shift`+`click`
  + 2番+5番で`command`+`c`
  + 2番+4番で`command`+`v`

```fiveButtonedMouse.json
{
    "title": "MouseWithFiveButtons",
    "rules": [{
        "description": "Define how how a mouse with 5 buttons works",
        "manipulators": [{
                "description": "Rcmd+'w',for Browsers (safari, chrome, firefox, vivaldi, ...) and Editor(vscode, ...)",
                "conditions": [{
                        "type": "device_if",
                        "identifiers": [{
                            "vendor_id": 7119,
                            "product_id": 5,
                            "description": "a mouse with 5 buttons"
                        }]
                    },
                    {
                        "type": "frontmost_application_if",
                        "bundle_identifiers": [
                            "^com\\.apple\\.Safari$",
                            "^com\\.google\\.Chrome$",
                            "^org\\.mozilla\\.firefox$",
                            "^com\\.vivaldi\\.Vivaldi$",
                            "^com\\.microsoft\\.VSCode$"
                        ]
                    }
                ],
                "type": "basic",
                "from": {
                    "simultaneous": [{
                            "pointing_button": "button4"
                        },
                        {
                            "pointing_button": "button5"
                        }
                    ]
                },
                "to": [{
                    "key_code": "w",
                    "modifiers": [
                        "right_command"
                    ]
                }]
            },
            {
                "description": "Rcmd+'w',for Browsers (safari, chrome, firefox, vivaldi, ...) and Editor(vscode, ...)",
                "conditions": [{
                        "type": "device_if",
                        "identifiers": [{
                            "vendor_id": 7119,
                            "product_id": 5,
                            "description": "a mouse with 5 buttons"
                        }]
                    },
                    {
                        "type": "frontmost_application_if",
                        "bundle_identifiers": [
                            "^com\\.apple\\.Safari$",
                            "^com\\.google\\.Chrome$",
                            "^org\\.mozilla\\.firefox$",
                            "^com\\.vivaldi\\.Vivaldi$",
                            "^com\\.microsoft\\.VSCode$"
                        ]
                    }
                ],
                "type": "basic",
                "from": {
                    "simultaneous": [{
                            "pointing_button": "button1"
                        },
                        {
                            "pointing_button": "button2"
                        }
                    ]
                },
                "to": [{
                    "key_code": "r",
                    "modifiers": [
                        "right_command"
                    ]
                }]
            },
            {
                "description": "Lsft+Lclk, for Browsers (safari, chrome, firefox, vivaldi, ...) and Editor(vscode, ...)",
                "conditions": [{
                        "type": "device_if",
                        "identifiers": [{
                            "vendor_id": 7119,
                            "product_id": 5,
                            "description": "a mouse with 5 buttons"
                        }]
                    },
                    {
                        "type": "frontmost_application_if",
                        "bundle_identifiers": [
                            "^com\\.apple\\.Safari$",
                            "^com\\.google\\.Chrome$",
                            "^org\\.mozilla\\.firefox$",
                            "^com\\.vivaldi\\.Vivaldi$",
                            "^com\\.microsoft\\.VSCode$"
                        ]
                    }
                ],
                "type": "basic",
                "from": {
                    "simultaneous": [{
                            "pointing_button": "button1"
                        },
                        {
                            "pointing_button": "button4"
                        }
                    ]
                },
                "to": [{
                    "pointing_button": "button1",
                    "modifiers": [
                        "left_shift"
                    ]
                }]
            },
            {
                "description": "Lcmd+Lclk,for Browsers (safari, chrome, firefox, vivaldi, ...) and Editor(vscode, ...)",
                "conditions": [{
                        "type": "device_if",
                        "identifiers": [{
                            "vendor_id": 7119,
                            "product_id": 5,
                            "description": "a mouse with 5 buttons"
                        }]
                    },
                    {
                        "type": "frontmost_application_if",
                        "bundle_identifiers": [
                            "^com\\.apple\\.Safari$",
                            "^com\\.google\\.Chrome$",
                            "^org\\.mozilla\\.firefox$",
                            "^com\\.vivaldi\\.Vivaldi$",
                            "^com\\.microsoft\\.VSCode$"
                        ]
                    }
                ],
                "type": "basic",
                "from": {
                    "simultaneous": [{
                            "pointing_button": "button1"
                        },
                        {
                            "pointing_button": "button5"
                        }
                    ]
                },
                "to": [{
                    "pointing_button": "button1",
                    "modifiers": [
                        "left_command"
                    ]
                }]
            },
            {
                "description": "Rcmd+'v',for Browsers (safari, chrome, firefox, vivaldi, ...) and Editor(vscode, ...)",
                "conditions": [{
                        "type": "device_if",
                        "identifiers": [{
                            "vendor_id": 7119,
                            "product_id": 5,
                            "description": "a mouse with 5 buttons"
                        }]
                    },
                    {
                        "type": "frontmost_application_if",
                        "bundle_identifiers": [
                            "^com\\.apple\\.Safari$",
                            "^com\\.google\\.Chrome$",
                            "^org\\.mozilla\\.firefox$",
                            "^com\\.vivaldi\\.Vivaldi$",
                            "^com\\.microsoft\\.VSCode$"
                        ]
                    }
                ],
                "type": "basic",
                "from": {
                    "simultaneous": [{
                            "pointing_button": "button2"
                        },
                        {
                            "pointing_button": "button4"
                        }
                    ]
                },
                "to": [{
                    "key_code": "v",
                    "modifiers": [
                        "right_command"
                    ]
                }]
            },
            {
                "description": "Rcmd+'c',for Browsers (safari, chrome, firefox, vivaldi, ...) and Editor(vscode, ...)",
                "conditions": [{
                        "type": "device_if",
                        "identifiers": [{
                            "vendor_id": 7119,
                            "product_id": 5,
                            "description": "a mouse with 5 buttons"
                        }]
                    },
                    {
                        "type": "frontmost_application_if",
                        "bundle_identifiers": [
                            "^com\\.apple\\.Safari$",
                            "^com\\.google\\.Chrome$",
                            "^org\\.mozilla\\.firefox$",
                            "^com\\.vivaldi\\.Vivaldi$",
                            "^com\\.microsoft\\.VSCode$"
                        ]
                    }
                ],
                "type": "basic",
                "from": {
                    "simultaneous": [{
                            "pointing_button": "button2"
                        },
                        {
                            "pointing_button": "button5"
                        }
                    ]
                },
                "to": [{
                    "key_code": "c",
                    "modifiers": [
                        "right_command"
                    ]
                }]
            },
            {
                "description": "global settings for mouse button 4",
                "conditions": [{
                    "type": "device_if",
                    "identifiers": [{
                        "vendor_id": 7119,
                        "product_id": 5,
                        "description": "a mouse with 5 buttons"
                    }]
                }],
                "type": "basic",
                "from": {
                    "pointing_button": "button4"
                },
                "to": [{
                    "key_code": "tab",
                    "modifiers": [
                        "right_shift"
                    ]
                }]
            },
            {
                "description": "global settings for mouse button 5",
                "conditions": [{
                    "type": "device_if",
                    "identifiers": [{
                        "vendor_id": 7119,
                        "product_id": 5,
                        "description": "a mouse with 5 buttons"
                    }]
                }],
                "type": "basic",
                "from": {
                    "pointing_button": "button5"
                },
                "to": [{
                    "key_code": "tab"
                }]
            }
        ]
    }]
}
```

これを`~/.config/karabiner/assets/complex_modifications`に作成する

作成したら`Karabiner-Elements`->`Complex Modifications`->`Rules`->`Add rule`を見る，構文ミスさえなければ表示されるので右端の`Enable`ボタンでで有効化，ちなみにここには**ファイル名は表示されない**ので注意（`json`オブジェクトの`title`と`rules.description`のみ表示される）

![enable.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540440/5a065eac-7c22-c473-3cea-dc5111c31e7c.png)

うまく有効化できたら`Misc`->`Restart Karabiner-Elements`で再起動，これで完全に動作が反映されるはず



## 特徴

+ 同時押し`simultaneous`によって修飾キー以外による同時押しに対応
  + 単一押しと使用するボタンやキーが被る場合は，`simultaneous`させたい動作について`json`オブジェクト上に**先に記述**すること，後に記述すると単一押しの方が優先されて判定を受けてしまい，同時押しの方は問答無用で無視されてしまう
  + それぞれの動作の定義が`rules.manipulators`の配列に格納される形であるため，処理の優先度が前後関係で決定されている可能性がある，そのためなるべく少ない`json`ファイルに関連する設定をまとめた方がむしろ安心
+ アプリケーション判定`frontmost_application_if`によってブラウザ限定動作に対応
  + 実は`Karabiner-Elements`->`Complex Modifications`->`Parameters`から設定できる
     + マウスで使うにはあまりにもシビアなので，猶予時間は長めにとった方が良いかも
     + ![timeset.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540440/0f897612-9308-4a58-5b96-d8104c2723f6.png)
  + マルチディスプレイで別々のアプリをトップに表示すると*両方とも有効*と扱われるので注意，シングルディスプレイを想定した実装しかされていない（意図しない動作が思わず発生する可能性がある）



## カスタマイズ

+ `rules.manipulators[].conditions[].identifiers`には対応する`vendor_id`と`product_id`が必要，各自で確認
+ マウスのボタン配置も確認して使いやすい組み合わせを検討
+ `rules.manipulators[].conditions[].bundle_identifiers`には対応するアプリの`identifiers`が必要各自で確認
  + アプリを起動して`lsappinfo info -only bundleid hoge`すれば確認できる

https://qiita.com/miyasakura_/items/51e367b13d2fba545698




# 解説



## 何を読めば理解できるか
公式ドキュメント

https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/

ここに`json`オブジェクトの取り扱い方法が記載されている


## 判定時間の設定について
基本的には「キーを下げる」が`press`の定義であって，一般的には`to`にて処理が起爆される．後述の`halt`も使えるとは思うが動作は未確認，ドキュメントには例文どころか説明すら載ってないし使う価値もなさそう．<br>
また「キーを（指定した時間より長く）下げ続ける」と処理が起爆する`to_if_held_down`，「下げたキーを上げる」と処理が起爆する`to_after_key_up`があり，これら2つは両立して存在できる．その場合，`to_if_held_down`の中に`halt:true`を用いることで後続の`to_after_key_up`をキャンセルする，すなわちキーを下げ続ける時間で処理を変えることが可能になる．<br>
`to_if_alone`は「キーを（指定した時間より短く）単独で下げ続ける」と処理が起爆する，長く押し続けると判定が`to`に移行する．同様に`halt:true`で`to_after_key_up`をキャンセルして処理を変えることが可能になる．

もしくは`from.simultaneous`で修飾キー以外の複数入力を受け付けられるが，これは`to`系のメンバではなく`from`系のメンバであるために判定時間の概念が異なる．先に述べたように`rules.manipulators`の配列の順番が`from`系のメンバの実行の優先順序になると推定されており，同じキーを利用した同時押しの判定をやりたいのであれば先に記述すべきである．<br>

また包括されるキーの組み合わせを設定するのも全く推奨されない，例えば`hoge`+`huga`と`hoge`を待ち受ける2つの`from`オブジェクトがある場合，後者の設定を`to_if_held_down`でも設定しない限り`a`を下げて上げた瞬間に後者が発動する．<br>


総合的に考えると，今回の実装はえらく特殊なものである以上は，真似はしない方が良いだろう．<br>
…実際に使うとなれば`Karabiner-Elements`->`Complex Modifications`->`Parameters`の`to_if_alone_timeout_milliseconds`と`simultaneous_threshold_milliseconds`をイジるくらいで，これはデバイスの使用感に依存するだろう．<br>
あとは変数を`set_variable`で設定して`variable_if`や`variable_unless`で利用できるらしいが，使ったことがない…