Ubuntu + Apple フルキーボードにおいて、udev + xremap + xdotool で「テンキーのカンマ」を入力できるようにする

## やりたいこと
Ubuntu 22.04でAppleの日本語フルキーボードを使っている（[Ubuntu 24.04についてはこちら](https://qiita.com/jidaikobo-shibata/items/be318ca855cd0bab8652#ubuntu-2404%E3%81%AE%E5%A0%B4%E5%90%88)）。

![apple_fullkeyboard_jp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3721114/037fc7eb-78f3-cae9-bb9f-35acea363b07.png "Appleの日本語フルキーボード")

Ubuntuを普通にインストールした状態では、このキーボードで、テンキーの「カンマ」を打鍵しても、「ピリオド（ドット）」を入力してしまう。

テンキーのカンマを打鍵したら、テンキーのカンマとして入力できるようにしたい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3721114/33062e6b-18a8-ea27-d73a-e597f193a040.png" alt="Appleの日本語フルキーボードのテンキー部分。最下段左から2つめがカンマキー" width="300">

## まず別解

このページの方法よりも、「[Ubuntu + Apple フルキーボードにおいて、xmodmap で「テンキーのカンマ」を入力できるようにする](https://qiita.com/jidaikobo-shibata/items/ff01bc067490f6966004)」のほうが、打鍵と入力とのタイムラグが少なくて気持ちが良い。

ただ、このページに書いてある方法自体は、xremapやxdotoolの典型的なサンプルでもあるのでこのまま残します。

## 解法

### X11前提
Waylandでは、`xdotool`が動かないため、X11が前提です。

### udevを設定する
```terminal
sudo vi /etc/udev/hwdb.d/10-mykeyboard.hwdb
```
として、以下を入力して保存（Apple Magic Keyboardの場合は`p022`は`p026`）。
```:/etc/udev/hwdb.d/10-mykeyboard.hwdb
evdev:input:b0003v05**p022*
 KEYBOARD_KEY_70085=scrolllock
```
以下で反映する。
```terminal
sudo systemd-hwdb update && sudo udevadm trigger
```
### xdotoolを用意する
いったんインストールだけでいい。
```terminal
sudo apt install xdotool
```
### xremapを用意する
以下手順で準備。
```terminal
sudo apt install curl
```
```terminal
curl https://sh.rustup.rs -sSf | sh
```
```terminal
sudo apt install cargo
```
```terminal
cargo install xremap --features x11
```
下準備（xremapをsudoで起動しない設定）をする（`YOURUSERNAME`は置き換える）。
```terminal
sudo gpasswd -a YOURUSERNAME input
```
以下を実行。
```terminal
echo 'KERNEL=="uinput", GROUP="input", TAG+="uaccess"' | sudo tee /etc/udev/rules.d/input.rules
```
ここまでやったらrebootする（もしX11にしてなかったら、このrebootのときに、起動画面でX11に切り替える）。

`~/.xremap/`に`config.yml`を作って、
```terminal
cd && mkdir .xremap && cd ~/.xremap && vi config.yml
```
以下内容を入れる。
```yaml:~/.xremap/config.yml
keymap:
  - name: Global
    remap:
      scrolllock: {launch: ['xdotool', 'key', 'KP_Separator']}
```
config.ymlを保存したら、以下で動作確認する。
```terminal
~/.cargo/bin/xremap ~/.xremap/config.yml
```
エディタなどで、テンキーの「カンマ」打鍵でカンマが入ることを確認する。<kbd>NUMLOCK</kbd>キーを忘れないようにすること。
### 自動起動を設定する
常に有効にさせるために、サービスを作成する。ディレクトリがない場合はあらかじめ作る。
```terminal
cd ~/.config/ && mkdir -p systemd/user && sudo vi ~/.config/systemd/user/xremap.service
```
以下の内容にする（`YOURUSERNAME`は置き換える。フルパス必須なので、`~`は使えない）。
```ini:~/.config/systemd/user/xremap.service
[Unit]
Description=xremap

[Service]
KillMode=process
ExecStart=/home/YOURUSERNAME/.cargo/bin/xremap /home/YOURUSERNAME/.xremap/config.yml
ExecStop=/usr/bin/killall xremap
Restart=always

[Install]
WantedBy=default.target
```
デーモン登録をする。
```terminal
systemctl --user daemon-reload
```
xremapを開始する。
```terminal
systemctl --user start xremap
```
自動起動を設定する。
```terminal
systemctl --user enable xremap
```
再起動して、うまく行くか確認する。

## 参考リンク

- [xremap/xremap: Key remapper for X11 and Wayland](https://github.com/xremap/xremap): Linuxのキーボードリマッパーである`xremap`のページ。かなり丁寧に使い方が説明されているので、よく読む必要がある。場合によるとissueも役に立つ
- [xremapをsystemctlで自動起動するメモ](https://blog.bomberowl.org/posts/427/): ぼくは`WantedBy`でハマったのだけど、ここに書いてある情報のおかげで解決できました
- [Ubuntu でキーマップの変更](http://iranoan.my.coocan.jp/essay/pc/202104020.htm): Linuxのデバイス管理の仕組みである`udev`の使い方が丁寧に説明されている。上記「解法」ではデバイスの判定を大きく端折っているが、うまく行かない場合は、このページでデバイスの指定の仕方を理解する必要がある
- [xdotool コマンド全２６実例](https://blog.capilano-fw.com/?p=3477): インストールからコマンド事例まで読める

## Ubuntu 24.04の場合

結論的にはここに書いてあるとおりの同様の設定で、テンキーのカンマを入力できる。しかし、どうにも収まりの悪い解決になっている。

### サービスの自動起動の不具合

ぼくの環境では、Ubuntu 24.04では、すなおに自動起動しない。起動直後はテンキーのカンマがなんの反応もしない。`xev`でも無反応。

でも、あらためて`systemctl --user restart xremap`したら、テンキーのカンマが入力できるようになる。当然ながら`xev`も反応するようになる。

サービスがきちんと登録されていないのか、と`systemctl --user status xremap`で確認してみても問題はない。

しかし、xremapで設定をした「何か」を使うと、とたんにテンキーのカンマが使えるようになり、`xev`の反応も復活する。具体的には、以下の設定。

```yaml:~/.xremap/config.yml
  # Global
  - name: Global Modmap for Apple Aluminium
    device:
      not: 'AT Translated Set 2 keyboard'
    remap:
      capslock: Ctrl_L
      Super_L: Ctrl_L
      Super_R: Ctrl_L
      Ctrl_L: Super_L

keymap:
  # Global
  - name: Global
    remap:
      hanja: muhenkan
      hangeul: henkan
      scrolllock: {launch: ['xdotool', 'key', 'KP_Separator']}
```
<details><summary>この設定について</summary>
<ul>
	<li><kbd>command</kbd>キー（<kbd>super</kbd>キー）を、<kbd>control</kbd>キーにしている。</li>
	<li><kbd>super</kbd>キーを<kbd>control</kbd>キーとして使うために、gnome-tweaksで、「アクティビティ」は「右Super」に変更し、「設定」で<kbd>Super</kbd>キーの絡むキーボードショートカットはすべて無効化している。</li>
	<li><kbd>A</kbd>の横にある<kbd>control</kbd>キーは、<kbd>super</kbd>キーにして、いろいろな機能をあてている。</li>
</ul>

実際は、以下を入れている。

<div>

```yaml:~/.xremap/config.yml
# Terminalでxremapを有効にするのに必要なディレイ
# X270では必要だが、macbookでは不要だった
#keypress_delay_ms: 20

# モディファイアキー設定
modmap:
  # Windowsキーボード用設定（ここから）
  # Emacs以外
  # - SuperをCtrlに、Caps LockをSuperに
  - name: Modmap of Except for Emacs
    device:
      only: 'AT Translated Set 2 keyboard'
    application:
      not: Emacs
    remap:
      Super_L: Ctrl_L
      Super_R: Ctrl_L
      capslock: Super_L

  # Emacsのみ
  # - CtrlとSuperを入れ替え、Caps LockをCtrlに
  - name: Modmap of Emacs
    device:
      only: 'AT Translated Set 2 keyboard'
    application:
      only: Emacs
    remap:
      Ctrl_L: Super_L
      Super_L: Ctrl_L
      capslock: Ctrl_L
  # Windowsキーボード用設定（ここまで）

  # Apple Aluminium キーボード用設定（ここから）
  # Global
  # - CtrlとSuperを入れ替え、Caps LockをCtrlに
  - name: Global Modmap for Apple Aluminium
    device:
      not: 'AT Translated Set 2 keyboard'
    remap:
      capslock: Ctrl_L
      Super_L: Ctrl_L
      Super_R: Ctrl_L
      Ctrl_L: Super_L
  # Apple Aluminium キーボード用設定（ここまで）

keymap:
  # Terminal
  - name: replace stop and copy for Terminal
    application:
#      only: gnome-terminal-server # wayland
      only: Gnome-terminal # X11
    remap:
      Ctrl-c: Ctrl-Shift-c # copy
      Super-c: Ctrl-c # stop

  # Emacs以外
  - name: not for Emacs
    application:
      not: Emacs
    remap:
      Super-a: home # line start
      Super-e: end # line end
      Super-k: Ctrl-k # to katakana at Mozc
      Super-j: Ctrl-j # to hiragana at Mozc
      Ctrl-up: Ctrl-home # document top
      Ctrl-down: Ctrl-end # document bottom
      Super-up: Ctrl-up # for Excel and Google Spreadsheet
      Super-down: Ctrl-down # for Excel and Google Spreadsheet
      shift-ctrl-h: alt-home # browser home
      alt-ctrl-left: ctrl-pageup # browser previous tab
      alt-ctrl-right: ctrl-pagedown # browser next tab

  # Thunderbird, Terminal以外
  - name: not for Thunderbird and Terminal
    application:
      not: [thunderbird-esr, Gnome-terminal]
    remap:
      Shift-Ctrl-w: {launch: ['xdotool', 'getactivewindow', 'windowsize', '1280', '980', 'windowmove', '2200', '0']}

  # Terminal
  - name: for Terminal
    application:
      only: Gnome-terminal
    remap:
      Shift-Ctrl-w: {launch: ['xdotool', 'getactivewindow', 'windowsize', '1000', '800', 'windowmove', '1900', '0']}

  # Thunderbird
  - name: for thunderbird
    application:
      only: thunderbird-esr
    remap:
      Super-Ctrl-w: {launch: ['xdotool', 'getactivewindow', 'windowsize', '1000', '1100', 'windowmove', '400', '-20']}
#      Super-Ctrl-w: {launch: ['xdotool', 'getactivewindow', 'windowsize', '1000', '1100', 'windowmove', '0', '-20']}
      Shift-Ctrl-w: {launch: ['xdotool', 'getactivewindow', 'windowsize', '800', '700', 'windowmove', '1200', '200']}
      Super-r: [alt-v,v,v,v,v,v,v,v,v,v,v,f,c] # 実験中
#      Super-r: {launch: ["bash", "-c", "xdotool key alt+v"]}
#      Super-r: alt-v

  # Global
  - name: Global
    remap:
      ctrl-f1:
        # launch word register
        launch: ["/usr/lib/mozc/mozc_tool", "--mode=word_register_dialog"]
      ctrl-f2:
        # launch screen shot
        launch: ["gnome-screenshot", "-i"]
      # Global for Apple Aluminium
      # - 「hanja」「hangeul」は他のキーボードにないキーなので、影響がないものとしてGlobalに入れる
      hanja: muhenkan
      hangeul: henkan
      scrolllock: {launch: ['xdotool', 'key', 'KP_Separator']}
```
</div>
</details>

この設定の絡んだ操作——たとえばコピーアンドペーストやTerminalでの`^C`——をすると、テンキーのカンマが入力できるようになる。`xev`も反応するようになる。

Ubuntu 24.04のインストールを3回ほどやって、まっさらな状態で試しており、毎回再現するのだけど、じゃっかん気持ちが悪い状態。[xremapのissue](https://github.com/xremap/xremap/issues)にもこの手がかりがないっぽい。

再起動のたびに`systemctl --user restart xremap`しろとなると、やってられない、となるのだけど、「コピーアンドペーストしたら動くようになる」だったら、あんまり問題もないところが、悩ましい。

この件に関しては、いったんオープンエンドで。

## 解決までの試行錯誤について

:::note info
以下、だらだらと試行錯誤を記録しているだけです。
:::

Apple フルキーボード（Apple Magic Keyboard (日本語)も有線キーボードも）のテンキーのカンマキーは、Ubuntuを普通にインストールした状態だと、押下した際、「.（ピリオド/ドット）」を入力する。
```terminal
sudo dpkg-reconfigure keyboard-configuration
```
でキーボードを変更したらなおるかと思い、`Apple Aluminium (JIS)`を選んでみるものの、うまくいかない。

あらためて、このテンキーのカンマキーを
```terminal
sudo evtest
```
で確認すると、`KPCOMMA`という信号を送っていることを確認できる。
```terminal:Apple Aluminium (JIS)のテンキーのカンマ (デフォルト)についてのevtestの出力:
Event: time 1722137343.613318, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70085
Event: time 1722137343.613318, type 1 (EV_KEY), code 121 (KEY_KPCOMMA), value 0
```
しかし、`xremap`（`xev`）ではこのカンマを認識できない。このカンマキーもその隣のピリオドも両方`KP_Decimal`になってしまう。
```terminal:テンキーのカンマ (xev抜粋):
state 0x10, keycode 129 (keysym 0xffae, KP_Decimal), same_screen YES,
```
```terminal:テンキーのピリオド (xev抜粋):
state 0x10, keycode 91 (keysym 0xffae, KP_Decimal), same_screen YES,
```
もっともシンプルなのは、`udev`だけで解決できることで、その方法は、後述の「[「設定」「キーボード」「入力ソース」で「日本語」を捨てる方針の場合](https://qiita.com/jidaikobo-shibata/items/be318ca855cd0bab8652#%E8%A8%AD%E5%AE%9A%E3%82%AD%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89%E5%85%A5%E5%8A%9B%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%A7%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%82%92%E6%8D%A8%E3%81%A6%E3%82%8B%E6%96%B9%E9%87%9D%E3%81%AE%E5%A0%B4%E5%90%88)」に残しておくが、何でも自由にできる代わりに茨の道が待っている。

`udev`だけの解決方法として、次のような手もある。

```:/etc/udev/hwdb.d/10-mykeyboard.hwdb
evdev:input:b0003v05**p022*
 KEYBOARD_KEY_70085=comma
```
この場合だと、いわゆる「普通のカンマ」が入力されるようになる。これでいいという人は、それでもいいかもしれない。しかし、`Mozc`の「`設定`」の「`テンキーからの入力`」「`直接入力`」の恩恵は受けられない。普通のカンマなので、日本語入力モードの際、テンキーのピリオドは直接入力のピリオドを入力してくれるのに、テンキーのカンマは全角の読点「、」を入力してしまう。フルキーボードをつかっている利点が、かなり減じてしまう。

そもそもこの「テンキーのカンマ」は、相当探してみたが、Appleの日本語キーボードにしかない。テンキーだけのキーボードですら、このカンマキーを実装している製品を見つけることが困難だった。

そういう事情からか、[`udev`で使えるキーの語彙](https://hal.freedesktop.org/quirk/quirk-keymap-list.txt)のなかでも、`kpjpcomma`という奇怪な名前が与えられている（`hanguel`や、`henkan`というキーはあるものの、ざっと俯瞰してみても地域名がキーの名前に含まれているのは稀）。

では、`kpjpcomma`をなんとか送信できればいいかというと、そんなにかんたんではなかった。

`xremap`や、`udev`で、`kpjpcomma`を送ると、何も出力しない。どうやら。`kpjpcomma`には、「テンキーのカンマ」という役割が与えられていない。

「[XKBを勉強してリベンジを試みる](https://dodoit.info/cid0.html)」によると、

|InputEventCode|evdevKeycode|xmodmapKeycode|
|:-----------:|:------------:|:------------:|
|KEY_KPJPCOMMA 95|\<JPCM\> = 103; // KPJPComma|keycode 103 =|

となっており、たしかに
```terminal
xmodmap -pke | grep 103
```
は、
```
keycode 103 =
```
を返してくる。`~/.Xmodmap`で、
```
keycode 103 = KP_Comma
```
としたが、これは
```
bad keysym name 'KP_Comma' in keysym list
```
となってしまう。`KP_Comma`も`kpjpcomma`も、どうも`xmodmap`の語彙ではない様子。

試行錯誤の結果、`KP_Separator`が、テンキーのカンマを入力してくれることがわかった（ここまでが相当長かった）。しかし、では、`xremap`で`KP_Separator`を送ることができればいいのだが、あいにくと[`xremap`の語彙](https://github.com/emberian/evdev/blob/1d020f11b283b0648427a2844b6b980f1a268221/src/scancodes.rs#L26-L572)の中に`KP_Separator`がない。`xremap`の語彙の中でそれっぽいものは一通り試したのだけど、どうにも見つからない。

<details><summary>「それっぽいもの」の記録</summary>

<table>
<tr>
	<th>kpdot</th>
	<td>ピリオドが出る</td>
</tr>
<tr>
	<th>kpcomma</th>
	<td>ピリオドが出る</td>
</tr>
<tr>
	<th>kpjpcomma</th>
	<td>無反応</td>
</tr>
</table>

</details>

問題を整理するとこう。

1. `xremap`はテンキーのカンマとピリオドを区別できない。`udev`はこれを区別できる
1. テンキーのカンマの信号を送ってくれるのは、`kpcomma`でも`kpjpcomma`でもなく、`KP_Separator`だが、`udev`は直接、`KP_Separator`を送信できない（おそらく対応するものがない）
1. `udev`で、テンキーのカンマを`xremap`が理解できるキー（普段使わないキー。ぼくは`scrolllock`にした）にする
1. `xremap`で`KP_Separator`を送信できればいいが、`xremap`では`KP_Separator`を送信できない（おそらく対応するものがない）
1. `xremap`では、`xdotool`を起動できる。`xdotool`であれば、`KP_Separator`を送信できる

というわけで、その解法が、このドキュメントの先頭にあるものです。いかにも無理やりな解決ですので、もっとエレガントな方法があったら教えてください。

## 「設定」「キーボード」「入力ソース」で「日本語」を捨てる方針の場合

「`入力ソース`」から「`日本語`」を捨てると、「`日本語 (Mozc)`」の「`直接入力`」に相当するものが、英語キーボード的なものに変わる。たとえば<kbd>;</kbd>を押下すると「:」が入力され、<kbd>shift</kbd>+<kbd>;</kbd>で「;」が入力される。

この状態だと`udev`でいじったとおりに何でも言うことを聞いてくれるので、以下設定が有効になる。
```terminal
sudo vi /etc/udev/hwdb.d/10-mykeyboard.hwdb
```
```:/etc/udev/hwdb.d/10-mykeyboard.hwdb
evdev:input:b0003v05**p022*
 KEYBOARD_KEY_70085=kpjpcomma
```
とした後、以下で反映する。
```terminal
sudo systemd-hwdb update && sudo udevadm trigger
```
「`設定`」「`キーボード`」で「`日本語`」キーボードを削除（順番の入れ替えではなく削除）し、「`日本語 (Mozc)`」だけにしたのち、`reboot`したら、`kpjpcomma`が入力できた。

しかし言い換えると、`kpjpcomma`が入力できるようになる**だけ**。英語キーボードを知ってたらどこに何があるかわかるとは思いますが、Appleの日本語キーボードとしては、ほとんど使い物にならなくなります。