VirtualBox上のUbuntuでxkeysnailを使用したキーマッピングをする

# 概要

WindowsのCLI環境がちょっと嫌になってきたので、ホストOSである Windows で仮想化ソフトに VirtualBox を使用し、ゲストOSの Ubuntu を立ち上げ、そこに開発環境にしようと試みました。

その際、Ubuntu でのキーマッピングがあまりに Windows のそれと違っていたのでだいぶやりにくく感じ、Windowsのキーマッピングになんとか寄せようと試みたのでその記録を書き連ねていきます。

# 動作環境

動作環境は以下のようです。

* ホストOS： Windows10 Home 
* 仮想化ソフト： VirtualBox 6.0.10
* ゲストOS： Ubuntu 19.04

また、使用しているキーボードは108キーボードです。

# Windowsでのキーカスタマイズはどんな感じだったのか？

Windows上では、[enthumble](https://qiita.com/mhara/items/d2d072592a4db9243c99)というキーボードカスタマイズツールを使っています。

enthumbleについては、私の意識している範囲だと以下の機能を提供してくれていました。

* 無変換を押すとIMEをOFFにする
* 変換を押すとIMEをONにする
* 無変換か変換を押しながら他のキーを押した場合の動作をカスタマイズできる
  * 例）変換 + h = ← 

無変換、変換を押して他のキーを押した場合の挙動のカスタマイズは以下のようにしていました。(※自由にカスタマイズするためには980円の有料版をダウンロードする必要があります。)
詳しい見方は[このページ](https://qiita.com/mhara/items/44064da2d77ddbd258b7)が参考になります。

```
[HowToUse]
Check=http://jp.enthumble.com/
Name=enthumble version 4.0
[KEY]
Space={Enter}
Muhenkan={Esc}
Henkan={Esc}
Hiragana={}
F1=#{1}
F2=#{2}
F3=#{3}
F4=#{4}
F5=#{5}
F6=#{6}
F7=#{7}
F8=#{8}
F9=#{9}
F10={Volume_Mute}
F11={Volume_Down}{Volume_Down}
F12={Volume_Up}{Volume_Up}
F18={}
F19={}
1={F1}
2={F2}
3={F3}
4={F4}
5={F5}
6={F6}
7={F7}
8={F8}
9={F9}
0={F10}
-={F11}
Hat={F12}
\={}
Q=^{PgUp}
W=^{PgDn}
E=^{Home}
R=^{End}
T=^{t}
Y={Backspace}
U={PgUp}
I={Insert}
O={delete}
P={up}
@={}
LB={}
A=^{a}
S=^{s}
D={PgDn}
F=^{f}
G=^{g}
H={left}
J={down}
K={up}
L={right}
Semicolon={}
:={}
RB={}
Z=^{z}
X=^{x}
C=^{c}
V=^{v}
B=^{b}
N={alt down}{left}{alt up}
M={alt down}{up}{alt up}
,={delete}
.={alt down}{right}{alt up}
/={}
Backslash={}
Up={vkF2sc070B}{vkF3sc029}{ASC 33194}
Down={vkF2sc070B}{vkF3sc029}{ASC 33195}
Left={vkF2sc070B}{vkF3sc029}{ASC 33193}
Right={vkF2sc070B}{vkF3sc029}{ASC 33192}
LButton=^{PgUp}
MButton={MButton}
RButton=^{PgDn}
WheelUp={}
WheeDown={}
PageUp=^{PgUp}
PageDown=^{PgDn}
Home={}
End={}
Delete={}
backspace={}
Enter={}
Escape={}
Hankaku={}
Tab={}
Capslock={}
Lwin={}
Rwin={}
Insert={}
```


# Ubuntu上で設定したこと

Ubuntu上での日本語入力環境としては、fcitx-mozcを使用しています。
無変換を押したらIMEがOFFになり、変換を押したらIMEがONになる、という挙動はfcitxの設定で再現するよう試みました。
また、無変換、変換を押しながら他のキーを押した場合の挙動のカスタマイズは、xkeysnailというツールを使用しました。

## fcitxの入力メソッド切り替え設定

`fcitx-configtool`で表示される画面で、以下のように「キーボード-日本語-日本語(OADG 109A)」と「Mozc」入力メソッドを設定します。
(108キーボードはなかったので109で代用しましたが、現状特に問題は起きていません)

![範囲を選択_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441085/95cdd31e-f37f-d3ad-c2a4-af9fb2148b0e.png)


次に、「全体の設定」タブをクリックし、「Show Advanced Options」にチェックを入れます。
「入力メソッドをオンに」に「Henkanmode」を指定し、「入力メソッドをオフに」に「Muhenkan」を指定します。

![範囲を選択_002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441085/01cb4972-f201-7cfa-cfa0-c62f94139170.png)


これで無変換を押せば半角英数入力になり、変換を押せば日本語入力になるはずです。

## xkeysnail

キーのマッピングのカスタマイズには[xmodmap](https://wiki.archlinux.jp/index.php/Xmodmap)や[remap](https://github.com/k0kubun/xremap)などがありましたが、一番後発であるという理由で[xkeysnail](https://github.com/mooz/xkeysnail)を使用しました。

[このページ](https://qiita.com/mooz@github/items/c5f25f27847333dd0b37)をみて、以下2点あたりが特に利点に感じました。

* アプリケーションごとにキーバインド設定ができる
  * 例)chromeとVSCodeで別のキーバインド設定ができる
* 他のツールに比べ、ほとんどの場所でキーバインド設定が効く

### xkeysnailのインストール方法

以下コマンドの実行でインストールできます。

```
sudo apt install python3-pip
sudo pip3 install xkeysnail
```

### xkeysnailのconfig.py 

config.py というファイルにカスタマイズ内容を記述していきます。
記述方法は[ここ](https://qiita.com/mooz@github/items/c5f25f27847333dd0b37#define_keymapcondition-mappings-name)に記載されています。

カスタマイズする際に頭を悩ませた点なのですが、xkeysnailの仕様として、修飾キーには`Control`、`Alt`、`Shift`、`Windows`キーのいずれかしか選択できません。
理想としては無変換、変換キーを修飾キーに使用したかったのですが、やむを得ず無変換、変換を普段使用していない右側のAltに割り当てるようにしました。

config.py は以下のように設定しました。

```
import re
from xkeysnail.transform import *

define_multipurpose_modmap({
    Key.MUHENKAN: [Key.MUHENKAN, Key.RIGHT_ALT],
    Key.HENKAN: [Key.HENKAN, Key.RIGHT_ALT]
})

define_keymap(None, {
    # vim like上下左右
    K("RAlt-h"): K("LEFT"),
    K("RAlt-j"): K("DOWN"),
    K("RAlt-k"): K("UP"),
    K("RAlt-l"): K("RIGHT"),
    # ページup&down
    K("RAlt-q"): K("C-PAGE_UP"),
    K("RAlt-w"): K("C-PAGE_DOWN"),
    # backspace&delete
    K("RAlt-y"): K("BACKSPACE"),
    K("RAlt-o"): K("DELETE"),
    # ページ戻る、次へ
    K("RAlt-n"): K("M-LEFT"),
    K("RAlt-DOT"): K("M-RIGHT"),
    # その他よく使う系
    K("RAlt-a"): K("C-a"),
    K("RAlt-c"): K("C-INSERT"),
    K("RAlt-s"): K("C-s"),
    K("RAlt-v"): K("C-v"),
    K("RAlt-z"): K("C-z"),
    K("RAlt-SPACE"): K("ENTER"),
    K("RAlt-HENKAN"): K("ESC"),
    K("RAlt-MUHENKAN"): K("ESC"),
}, "All")
```

#### define_multipurpose_modmap メソッド

以下の`define_multipurpose_modmap(multipurpose_remappings)`メソッド部分で、無変換、変換を押したときの挙動を再定義しています。

```
define_multipurpose_modmap({
    Key.MUHENKAN: [Key.MUHENKAN, Key.RIGHT_ALT],
    Key.HENKAN: [Key.HENKAN, Key.RIGHT_ALT]
})
```

ここでは、以下のように再定義しています。

* 無変換・変換キーが press される時と release される時 → それぞれ無変換・変換として動作
* 無変換・変換キーが held down されている時 → それぞれ右側のAltとして動作

つまり、単独で押した時は無変換・変換として機能させ、押しっぱなしにした時は修飾キーの1つである右Altとして機能させるといった感じです。

#### define_keymap メソッド

以下の`define_keymap(condition, mappings, name="Anonymous keymap")`メソッド部分で、修飾キーを押しながら他のキーを押した場合の挙動を再定義しています。

```
define_keymap(None, {
    # vim like上下左右
    K("RAlt-h"): K("LEFT"),
    ~~~~~
}, "All")
```

第一引数では、このメソッド内で定めたキーマッピングをどのアプリケーションで適応するかを限定できます。
今回は`None`として、すべてのアプリケーションで効くようにしました。

第二引数で、修飾キーを押したときのキーマッピングを定義しています。
`K("RAlt-h"): K("LEFT")`では、`右Alt`を押しながら`h`を押したときには、`←`キーを押したものと再定義しています。

第三引数はキーマップの名前を書くようです。たぶんあまり重要じゃないです。

### xkeysnailのサービス化(未対応！)

Ubuntu を立ち上げたときに xkeysnail が効くように、[この記事](https://qiita.com/samurai20000@github/items/2e1d779e806a7e8543d6)を参考にサービス化を試みました。
が、Ubuntu起動時にエラーが発生しているので、起動時毎回`systemctl --user start xkeysnail`を打っている現状です。。

具体的に取った手順としては、

1 . `~/.config/systemd/user/xkeysnail.service` を作成
内容は以下のようです。

```
[Unit]
Description=xkeysnail

[Service]
KillMode=process
ExecStart=/usr/local/bin/xkeysnail /home/vagrant/.ghq/github.com/nannany/xkeysnail_myconfig/config.py
ExecStartPre=/usr/bin/xhost +SI:localuser:root
Type=simple
Restart=always

# Update DISPLAY to be the same as `echo $DISPLAY` on your graphical terminal.
Environment=DISPLAY=:0

[Install]
WantedBy=default.target
```

2 . `systemctl --user enable xkeysnail`を実行

3 . グループを作成し、自分のユーザーをそのグループに追加

```
sudo groupadd uinput
sudo usermod -aG input,uinput vagrant   
```

4 . `/etc/udev/rules.d/`配下にrulesファイルを追加

```input.rules
KERNEL=="event*", NAME="input/%k", MODE="660", GROUP="input"
```

```uinput.rules
KERNEL=="uinput", GROUP="uinput"
```

1~4をやって再起動し、`systemctl --user status xkeysnail`すると私の環境では失敗を確認できました。
`journalctl -o verbose _PID=~~`で原因を確認すると以下のように出ており、エンコードに失敗していることがわかりましたが、ここで力尽きました。。

```
Sat 2019-08-17 23:16:27.839596 PDT [s=018b5ea1065b4eca938caedebbf94c60;i=7954;b=
    PRIORITY=6
    SYSLOG_FACILITY=3
    _UID=1000
    _GID=1000
    _CAP_EFFECTIVE=0
    _SELINUX_CONTEXT=unconfined
    _AUDIT_LOGINUID=1000
    _SYSTEMD_OWNER_UID=1000
    _SYSTEMD_UNIT=user@1000.service
    _SYSTEMD_SLICE=user-1000.slice
    _SYSTEMD_USER_SLICE=-.slice
    _MACHINE_ID=9f431bdbd7044796a650f3421beb0f08
    _HOSTNAME=ubuntu1904.localdomain
    _TRANSPORT=stdout
    _SYSTEMD_CGROUP=/user.slice/user-1000.slice/user@1000.service/xkeysnail.serv
    _SYSTEMD_USER_UNIT=xkeysnail.service
    SYSLOG_IDENTIFIER=xkeysnail
    _COMM=xkeysnail
    _EXE=/usr/bin/python3.7
    _CMDLINE=/usr/bin/python3 /usr/local/bin/xkeysnail /home/vagrant/.ghq/github
    MESSAGE=UnicodeEncodeError: 'latin-1' codec can't encode characters in posit
    _AUDIT_SESSION=4
    _SYSTEMD_INVOCATION_ID=dd0c986db07548f391e8a2ef1b1699b8
    _BOOT_ID=9f5087462fd443608af496685c0aebb6
    _STREAM_ID=4fc206f8a940436e9c6a9edcca2323ea
    _PID=1551
```

# まとめ

xkeysnailを使ってみてから数日立ちましたが、今の所すべてのアプリケーションでキーマッピングがちゃんと効いてくれていて不便な点はないです。

サービス化できてないのに加えて、まだ使用していないxkeysnailの機能として、

* アプリケーションごとにキーマッピングの定義を変える
* 複数ストロークのキーバインドを定義する

等々あるので、もっと快適な環境を目指したいなぁと思う次第です。