Barrierを使ってMacの気持ちのままWindowsで気持ちよくなる

# キーボード共有機能に沸く2021年6月
WWDCから結構経っちゃいましたが、WWDC2021の最高潮のポイントは__Universal Control__だったのではないかと思います。

https://youtu.be/0TD96VTf0Xs?t=4951

おおーと思ったものの、多くの人はこうも思ったと思うのです。
_「iPadよりも隣に置いてあるWindowsやLinuxマシンとのキーボード共有がしたいんだけれど」_
奥さん、できますよそれ

# Barrier
本題です。KVMソフトBarrierを使ってMacをホストにしつつ、WindowsマシンでMagic KeyboardやMagic Mouseをそのまま使って気持ちよくなろうというのがこの記事です。
Windows-Mac間で使えるKVMソフトとしてはSynergyやSharemouseなどが有名だとは思いますが、これらは有料ライセンス販売のソフトになります。僕もちょっと前から存在は知っていましたが、有料であることと、JIS対応がどうやらイマイチっぽいような話を小耳に挟んでいたのであんまり試す気にはなれずに放置していました。WWDCをきっかけに再びKVMへの気持ちが湧き上がってきて調べていたところ、__Barrier__に出会いました。
[Barrier](https://github.com/debauchee/barrier)はSynergyがオープンソースだった頃のソースコードからフォークされたプロジェクトで、UI部分も含めた状態で全て公開されています。
無料なら一旦使ってみるかという気持ちで導入してみました。

# 導入環境
本記事では以下のような環境への導入を行っています
サーバー: macOS 10.15(Mac mini 2018)
クライアント: Windows 10 20H2(AMD環境)
Barrierは現時点で最新のリリースビルドの[v2.3.3](https://github.com/debauchee/barrier/releases/tag/v2.3.3)をそれぞれの環境にインストールしています

# できること・問題点
ざっと試してみてクリティカルなものから軽微なものまでいくつかぶつかった問題があったのでまとめていきます。

## できる事

- Mac-Windows間のマウス・キーボードの共有
- サーバーから送信する修飾キー(`Ctrl`, `Meta`, `Super`, `Alt`)のカスタマイズ
- クリップボード共有、またそれぞれのクリップボード拡張アプリにヒストリーが蓄積される
- Windowsクライアント上でのAHKなどを用いたキー入力の上書き
- Macサーバー上でのKarabinerなどを用いて上書きしたキー入力の送信(一部ソフトでの結果は反映されず)
- Windowsクライアント上でのMagic Mouseの慣性スクロール
- Windowsクライアントへのボタン付きマウスのボタンの送信

思ったよりもしっかり動いてて感動しました。それぞれ有線LANに繋いでいる環境下ではありますが、遅延もほぼなく、日常動作では困らなさそうな精度を保っています。サーバー、クライアントそれぞれでキーバインドの変更などを行なっていることもあると思いますが、Karabiner・AHK・PowerToysは自分の環境では問題なく動作しています。ただ、Hammmerspoonやエレコムのマウスユーティリティはもう少し上のレイヤーで動作しているためかクライアントまでキーコードが伝播しませんでした。

#### サーバーから送信する修飾キー(`Ctrl`, `Meta`, `Super`)のカスタマイズ

これが個人的には結構大きかったところなのですが、Barrierではコード弄らなくてもアプリケーション側から送信するキーをカスタマイズできます。
どういう事かというと通常Magic KeyboardなどMac配列のキーボードをそのままWindowsで使うと`Command(⌘)キー`が`Windowsキー`に当たってしまって大変厳しい思いをするのですが、アプリケーションサイドで送信するキーをカスタマイズできるため、Windowsのレジストリなどを弄らなくても修飾キーの配列を変えることができます。ただし、修飾キーは全て__左側のキー__として認識されるため、左右で動作を変えるようにAHKを組むときなどは少し工夫が必要です。自分の環境では`Controlキー`を`左Control`、`Commandキー`を`右Control`として扱ってAHKを書いているので、`Command`から送信されるキーを右側にする修正を入れています。
 
## できない事

### 軽微な問題

- `かな・英数キー`が飛ばずに`スペース`が飛ぶ
- `ctrl+alt+del`がクライアントのWindows上で実行されない
- `capslock`の挙動が怪しい(日本語入力モードで`capslock`を入れると入力を受け付けなくなるなど)
- `Super`,`Control`などの装飾キーが全て左側のキーとして送信される
- Macサーバーにおいてクライアント側操作時でも、Barrierよりも上のレイヤーで動いているキーカスタマイズソフトを使っている場合はキー入力などがMac側に反映されてしまう

### クリティカルな問題

- JISキーボードの__`¥(|)キー`が飛ばない__
- Mac同士を繋いだときにキーボード共有が効かない
- keystrokeでクライアントに送るキーコードをオーバーライドするとサーバー側で該当するキーが効かなくなる

JIS配列のキーがいくつか飛ばないなどの問題は事前にある程度情報を見ていたため、予想通りなところはあったのですが、Mac同士で繋ぐとそもそもキーが一切飛ばないのはちょっとびっくりしました。ここはUniversal Controlに期待ですね・・・

#### `ctrl+alt+del`がクライアントのWindows上で実行されない

これに関しては困る人は困るかもしれませんが、僕の環境だとあんまり使うこともないのでそのままにしています。一応ソースコード上には__`fakeCtrlAltDel`__なる関数があり、何やら実装されてそうな雰囲気がありますが、自分の環境では現状こいつは効いてなさそうでした。[issue](https://github.com/debauchee/barrier/issues/949)にも上がっていたのですが、ちょっと不思議なワークアラウンドを残してクローズされてしまっていました。こちらのワークアラウンドは実際に自分の環境でも再現できたので、修飾キーの送信で何か問題が起きているのかもしれませんが、デバッグログを見る限りは`fakeCtrlAltDel`は通っていそうなので権限回りで弾かれてそうな感じもします。

#### `capslock`の挙動が怪しい

英数入力だと問題ありませんが、日本語入力時に効かなくなります。
これの解決は簡単ですね。__CapsLockを無効化__してしまえば解決です。使わないキーは潰してしまいましょう。

...__`OSXKeyState.cpp`__のキーモディファイア周りを触っていたときに正しく動作したりしていたので、この辺りの条件判定を弄くり回せば直りそうですが、そこまでの気持ちはなかったので放置しています。

#### JISキーボードの`¥キー`が飛ばない

これはまぁまぁ厳しいです。
まず、MacのJISキーボードの¥は実はWindowsのように`バックスラッシュ`のキーコードを上書きしているとかいう動作ではなく、__`international3`__という独自のキーコードが割り当てられています。現在最新のBarrier(v2.3.3)ではこの`international3`のキーコードから`Windows上での¥`にマッピングするコードがなく、そのままキーコードを送るとマッピングをすり抜けてキーコードが何も実行されないという状態になります。
これを解決するために 

1. `OSXKetState.cpp`内で__`0x005c`__に__`kVK_JIS_Yen(¥キーのキーコード)`__をマッピングする
2. サーバーのコンフィグに__`keystroke(Shift+Backspace) = keystroke(Bar)`__を追記する

といったワークアラウンドを紹介されているブログがあり、こちらの方法を試してはみたのですが、これでは完璧に解決には至りませんでした。
まず、なぜ上記のような手順になっているかというと、1のように`¥キー`に直接`Windowsにおける¥キー`のコード`0x005c`を割り当てているのですが、 この`0x005c`が厄介で、Barrierからこのキーコードを送信すると、`¥(|)`のキーではなく、`_(ろ)`から入力される`¥キー`、すなわち__`Win+_(アンダーバー)`__のキーコードが送信されます。なのでこのまま`Shift+¥`を入力しても`|(Bar)`が入力されないという状況になります。
これをなんとか`|(Bar)`が入力されるようにするため行っているのが2の設定です。Barrier(Synergy)のサーバーコンフィグにはキー入力を上書きする設定、__keystroke__があるのですが、これを使うとクライアント側では設定が上書きされてちゃんと入力できるようになるものの、サーバーサイドで`Shift+¥`を入力したときに何も反応しなくなってしまいます。この問題はissueも立っているのですが、本家Synergyでは長年放置されている状態でいつ対応されるかわからないので一旦この方法は忘れて違う方向性で修正していくことにしました。

ということで以下の

- `かな・英数キー`が飛ばずに`スペース`が飛ぶ
- `Super`,`Control`などの装飾キーが全て左側のキーとして送信される
- JISキーボードの`¥(|)`キーが飛ばない

3つの問題について修正するコードを用意しました。

# 修正コード

[修正ブランチ(Compare)](https://github.com/debauchee/barrier/compare/master...jdack41:keymapmod)

```diff:OSXKeyState.cpp
...略

    { kKeyBrightnessDown, s_brightnessDown },

    // JIS keyboards only
-   { kKeyEisuToggle, kVK_JIS_Eisu },
-   { kKeyKana, kVK_JIS_Kana }
+   { kKeyMuhenkan, kVK_JIS_Eisu },
+   { kKeyHenkan, kVK_JIS_Kana }
};

...略

    if ((changed & KeyModifierSuper) != 0) {
-       handleModifierKey(target, s_superVK, kKeySuper_L,
+       handleModifierKey(target, s_superVK, kKeySuper_R,
                            (newMask & KeyModifierSuper) != 0, newMask);
    }
    if ((changed & KeyModifierCapsLock) != 0) {
```

ここでは

1. `かな・英数キー`にWindowsの`変換・無変換キー`を当てる
1. `Commandキー`を左から右に変える

ということをやっています。1.に関してはどうもバグらしく、[修正PR](https://github.com/debauchee/barrier/pull/1214)が今立っていますが、こちらを試しても自分の環境ではあんまりうまくいかなかったので、`変換・無変換キー`にマップすることにしました。
2.は単純に`L`を`R`に置き換えただけです。ちなみにここからBarrier側の設定で`Super`を`Control`に当てるということをやっていますが、`右Control`として当てられていました。`Altキー`に設定しても同様に`右Alt`として割り当てられます。

```diff:key_state.h
static const KeyID		kKeyMissionControl	= 0xE0C0;
static const KeyID		kKeyLaunchpad		= 0xE0C1;


+ // for mac jis keyboard
+ static const KeyID		kKeyMacJISYen		= 0x00A5;
//@}

struct KeyNameMapEntry {
```

```diff:KeyState.cpp
    kKeyKP_6,            0x0036,
    kKeyKP_7,            0x0037,
    kKeyKP_8,            0x0038,
-   kKeyKP_9,            0x0039
+   kKeyKP_9,            0x0039,
+   kKeyMacJISYen,       0x005c // keyremap for Mac JIS keyboard
};

```

ここでは

1. `key_state.h`への`MacJISキーボードの¥キーコード`定義の追加
1. `KeyState.cpp`では追加した`MacJISキーボードの¥キーコード`への`Winでの¥キーコード`のマッピング

を行っています。
このKeyState.cppはサーバーから受け取ったキーコードの解決に使われていますが、該当コード内に適切なキーコード置き換えブロックがなかったため、とりあえず`キーパッド(テンキー)`の置き換え箇所の中にねじ込んでいます。この方法においてはMacからの入力を別のキーコードに差し替えたりせず、クライアント側でのリマップに委ねているため、`Shift+¥`はそのまま`|`になります
Mac->Win間において`0x00A5`に当たっているキーコードはなかったようなのでこのような実装にしてみましたが、他のOS環境下でぶつかるキーコードがあったら悪さをする可能性もあるのでご注意ください。

# あとがき

これらの修正コードをビルドしてそれぞれの環境にインストールすればMacJIS環境とWindowsの快適なキーボード・マウス共有環境が出来上がります。
Barrierを導入するまでは複数デバイス切り替え機能つきのBluetoothキーボードにてそれぞれのデバイス間のキーボードの切り替えを行っていましたが、操作したいデバイスにキーボードを切り替え忘れていて、事故りそうになるなどが頻発していました。Barrierではマウスの位置にキーボードが追従するため、自然と見ている画面への入力ができるようになります。またBarrierとともにAutoHotKeyなどを併用することで、Windows上でそこそこMacに近いキーバインドも再現できています。
Windowsアレルギーだという方もこの環境であれば少しは気持ちよくWindowsを使えるようになるのではないかと思います。