Wayland でお好みのキーバインド

だいぶ前に以下のような記事を書きました。

bearmini.hatenablog.com

しかし時は下って Ubuntu 22.04 では Wayland がデフォルトのコンポジターになっており(実際には 21.04 からデフォルトになったみたいです)、そのため X.org では使えていた xmodmap が使えなくなってしまいました。Wayland を使うのをやめて X.org に戻すこともできたので、以前 Ubuntu 22.04 をインストールしたときは急いでいたということもあってその方法を取りました。

しかし今回再び Ubuntu 22.04 をインストールしてセットアップすることになったので、今度こそは Wayland で使える xmodmap と同等の方法を見つけ出そうと思っていろいろ調べました。

いろいろな情報が見つかりましたが、xmodmap そのものを単純になにか単一の別のもので置き換えることはできず、複数の方法を組み合わせる必要がありそうということはわかりました。

私が最終的に落ち着いたのは、

  • udev で単純なキーボードのリマップ(CapsLock を Ctrl に置き換えるなど)を行い、
  • xremapキーバインド(モディファイヤキー+何らかのキーの組み合わせで別のキーが押されたことにしたり何らかのコマンドを発行したりする)の設定をする

という方法です。

💡

xremap 単体でもキーボードのリマップはできるのですが、私が行いたかった以下のような変更を実施すると思ったような結果とならなかったためリマップは udev で行うこととしました。
私が行いたかった変更とは、

  • 左 Alt を何らかのモディファイヤキーに(例えば後述する virtual_modifiers を使って Fn キーをモディファイヤキーに)
  • 左 Meta(Windows キー)を左 Alt に
  • 左 Ctrl を左 Meta に
  • CapsLock を左 Ctrl に
というものです。
このような設定を行いたい場合、xremap の設定ファイルには以下のように記述するかと思います。
modmap:
  - remap
      LeftAlt: Fn
      LeftMeta: LeftAlt
      LeftCtrl: LeftMeta
      CapsLock: LeftCtrl
しかしこの設定だと CapsLock を押したときに Ctrl ではなく Meta(Windows キー)を押したことになってしまいました。
CapsLock -> LeftCtrl から LeftCtrl -> LeftMeta に連鎖してしまっているような感じの動きに見えました。
同じように LeftCtrl -> LeftMeta から LeftMeta -> LeftAlt に、LeftMeta -> LeftAlt も LeftAlt -> Fn に連鎖してしまっているような感じでした。

記述の順番等を変えてみたりもしましたが同様でした。

xremap の問題かもしれませんし私の記述の仕方の問題かもしれませんが、ちょっと解決策がわからなかったので今回はリマップは xremap 以外の方法で行うこととしました。

というわけで、以下では

  1. udev を使ったキーリマップの設定
  2. xremap を使ったキーバインディングの設定

を順に説明していきます。


1. udev を使ったキーリマップの設定

いろいろなページを見ましたが、キーのリマップをするのには udev を使っているものがほとんどでした。

その中でも、udev を使ってキーのリマップをすることに関する記述で一番正確だと感じたのは以下のページでした。

wiki.archlinux.org

このページに書かれている内容を要約すると以下の 4 つのステップになります。

  1. キーを押したときに発生する「スキャンコード」を調べる
  2. /etc/udev/hwdb.d/ にスキャンコード → キーコード(どのキーが押されたことにするか)の対応関係を書く
  3. sudo systemd-hwdb update を実行して hwdb を更新
  4. sudo udevadm trigger を実行して hwdb の変更を適用

以下、私が具体的に行った作業を例に、順に解説していきます。

1-1. キーを押したときに発生するスキャンコードを調べる

まず、どのキーが押されたらどのようなスキャンコードが発生するかを調べます。それには evtest というコマンドを使います。

evtest コマンドを、特に引数は指定せずにルートユーザー権限で実行します。

$ sudo evtest 
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0:  Sleep Button
/dev/input/event1:  Power Button
/dev/input/event2:  Lid Switch
/dev/input/event3:  AT Translated Set 2 keyboard
/dev/input/event4:  Topre REALFORCE 87 US
/dev/input/event5:  Topre REALFORCE 87 US Consumer Control
/dev/input/event6:  Topre REALFORCE 87 US Keyboard
/dev/input/event7:  Intel HID events
/dev/input/event8:  Intel HID 5 button array
/dev/input/event9:  Apple Inc. Magic Trackpad 2
/dev/input/event10: Wacom One by Wacom M Pen
/dev/input/event11: Apple Inc. Magic Trackpad 2
/dev/input/event12: Video Bus
/dev/input/event13: HDA Intel PCH Mic
/dev/input/event14: HDA Intel PCH Headphone
/dev/input/event15: HDA Intel PCH HDMI/DP,pcm=3
/dev/input/event16: HDA Intel PCH HDMI/DP,pcm=7
/dev/input/event17: HDA Intel PCH HDMI/DP,pcm=8
/dev/input/event18: HDA Intel PCH HDMI/DP,pcm=9
Select the device event number [0-18]: 

このような形でシステムに接続されている入力デバイスがずらずらと列挙されると思います。 そして最後に番号を選ぶように言われているので、自分のキーボードの名前が表示されている行の /dev/input/event* の番号の部分を入力します。

すると、以下のようにキーコードの一覧がずらーっと表示されると思います。

Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x853 product 0x145 version 0x111
Input device name: "Topre REALFORCE 87 US"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 1 (KEY_ESC)
    Event code 2 (KEY_1)
    Event code 3 (KEY_2)
    Event code 4 (KEY_3)
    Event code 5 (KEY_4)
    Event code 6 (KEY_5)
    Event code 7 (KEY_6)
    Event code 8 (KEY_7)
    Event code 9 (KEY_8)
    Event code 10 (KEY_9)
    Event code 11 (KEY_0)
    Event code 12 (KEY_MINUS)
    Event code 13 (KEY_EQUAL)
    Event code 14 (KEY_BACKSPACE)
    Event code 15 (KEY_TAB)
    Event code 16 (KEY_Q)
    Event code 17 (KEY_W)
    Event code 18 (KEY_E)
    Event code 19 (KEY_R)
    Event code 20 (KEY_T)
    Event code 21 (KEY_Y)
    Event code 22 (KEY_U)
    Event code 23 (KEY_I)
    Event code 24 (KEY_O)
    Event code 25 (KEY_P)
...

そして最後に以下のような表示で止まると思います。

...
Key repeat handling:
  Repeat type 20 (EV_REP)
    Repeat code 0 (REP_DELAY)
      Value    250
    Repeat code 1 (REP_PERIOD)
      Value     33
Properties:
Testing ... (interrupt to exit)
Event: time 1701488985.557366, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70028
Event: time 1701488985.557366, type 1 (EV_KEY), code 28 (KEY_ENTER), value 0
Event: time 1701488985.557366, -------------- SYN_REPORT ------------

ここで、置き換えたいキーをいろいろ押してみましょう。すると、以下のように表示が増えていくと思います。

Event: time 1701488990.899371, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70039
Event: time 1701488990.899371, type 1 (EV_KEY), code 58 (KEY_CAPSLOCK), value 1
Event: time 1701488990.899371, -------------- SYN_REPORT ------------
Event: time 1701488990.899543, type 17 (EV_LED), code 1 (LED_CAPSL), value 1
Event: time 1701488990.899543, -------------- SYN_REPORT ------------
Event: time 1701488991.057316, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70039
Event: time 1701488991.057316, type 1 (EV_KEY), code 58 (KEY_CAPSLOCK), value 0
Event: time 1701488991.057316, -------------- SYN_REPORT ------------
Event: time 1701488993.972222, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e0
Event: time 1701488993.972222, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 1
Event: time 1701488993.972222, -------------- SYN_REPORT ------------
Event: time 1701488994.114176, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e0
Event: time 1701488994.114176, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 0
Event: time 1701488994.114176, -------------- SYN_REPORT ------------
Event: time 1701488994.671199, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e3
Event: time 1701488994.671199, type 1 (EV_KEY), code 125 (KEY_LEFTMETA), value 1
Event: time 1701488994.671199, -------------- SYN_REPORT ------------
Event: time 1701488994.813167, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e3
Event: time 1701488994.813167, type 1 (EV_KEY), code 125 (KEY_LEFTMETA), value 0
Event: time 1701488994.813167, -------------- SYN_REPORT ------------
Event: time 1701488995.433148, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e2
Event: time 1701488995.433148, type 1 (EV_KEY), code 56 (KEY_LEFTALT), value 1
Event: time 1701488995.433148, -------------- SYN_REPORT ------------
Event: time 1701488995.559108, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e2
Event: time 1701488995.559108, type 1 (EV_KEY), code 56 (KEY_LEFTALT), value 0
Event: time 1701488995.559108, -------------- SYN_REPORT ------------

(MSC_SCAN) の右隣の value の 16 進数が今回取得したい「スキャンコード」です。たとえば CapsLock を押したときは

Event: time 1701488990.899371, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70039
Event: time 1701488990.899371, type 1 (EV_KEY), code 58 (KEY_CAPSLOCK), value 1

このような 2 行が表示されると思います。

1行目は物理的なキーを押したときに発生したスキャンコードで、私のキーボードでは CapsLock のスキャンコードは 70039 であることがわかります。 USB 接続のキーボードだと 70039 のような 5 桁の値になるようですが、PS/2 接続のキーボードだと 3e のような 2 桁の値になるようです。

2行目はそのスキャンコードがどのようなキーコードとして解釈されたかを表しています。KEY_CAPSLOCK と書かれているので CapsLock キーとして解釈されたことがわかります。

置き換えたいキーをすべて押して、それらのスキャンコードがわかったら、Ctrl-C を押して終了します。

1-2. /etc/udev/hwdb.d/ にスキャンコード → キーコード(どのキーが押されたことにするか)の対応関係を書く

私の場合は以下のような置き換えをしたいと考えています。

  • 左 Alt を何らかのモディファイヤキー(たとえば Fn)に
  • 左 Meta(Windows キー)を左 Alt に
  • 左 Ctrl を左 Meta に
  • CapsLock を左 Ctrl に

ですので、先ほど evtest コマンドを使って 左 Alt (KEY_LEFTALT)、左 Meta (KEY_LEFTMETA)、左 Ctrl (KEY_LEFTCTRL)、CapsLock (KEY_CAPSLOCK) を押してスキャンコードを取得しました。

これらのスキャンコードが希望するキーコードに解釈されるような変換ルールを /etc/udev/hwdb.d/ 以下のファイルに書いていきます。

拡張子が .hwdb であればファイル名は何でも良いようですが、ファイル名の順番にルールが適用されるようですので、慣習的には 10-mykeyboard.hwdb のように 2 桁の数字で始まる名前にするようです。

私の場合は以下のような内容でファイルを作成しました。

evdev:input:b0003v0853p0145e*
 KEYBOARD_KEY_700e2=fn
 KEYBOARD_KEY_700e3=leftalt
 KEYBOARD_KEY_700e0=leftmeta
 KEYBOARD_KEY_70039=leftctrl

1 行目は、この変換ルールを適用する対象となるデバイスを絞り込むためのものです。

USB 接続のキーボードの場合は

evdev:input:b<bus_id>v<vendor_id>p<product_id>e<version_id>-<modalias>

というフォーマットで指定します。

AT キーボード(おそらく PS/2 接続のキーボードのこと?)の場合は

evdev:atkbd:dmi:bvn*:bvr*:bd*:svn<vendor>:pn<product>:pvr*

というフォーマットになるようです。

もう一つ、以下のような指定の方法もあるようです。

evdev:name:<input device name>:dmi:bvn*:bvr*:bd*:svn<vendor>:pn*

どのような場合に使うのかは私にはわかりませんが、USB 接続でも PS/2 接続でもないキーボード(例えば仮想キーボード?)の場合に使用するのかもしれません。

私は USB 接続タイプの RealForce を使っていますが、その場合は一番めのフォーマットで USB のバス ID (0003 固定)、ベンダー ID、プロダクト IDを指定します。バージョン番号とかも指定することができるようにはなっていますが、さすがにそこまで細かい指定はが必要になるのは稀かと思うのでワイルドカードにすることもできます。

ベンダー ID とプロダクト ID は先ほど evtest コマンドを実行してキーボードの番号を選んだ直後くらいに表示されていますのでそれを用います。バス ID、ベンダー ID、プロダクト ID いずれも 16 進数ですが先頭の 0x は不要で数値部分だけ指定します。

このデバイスを指定する行に続けて、 半角スペース 1 つのインデント 1KEYBOARD_KEY_<scancode>=<keycode> という感じの行を書いてスキャンコード→キーコードの対応を書いていきます。

スキャンコードは先ほど evtest コマンドを使って収集した 16 進数の値です。 キーコードは Linux のヘッダー input-event-codes.h に定義されている KEY_<keycode> という感じの文字列の <keycode> 部分を 小文字 にしたものです。 (Linux のヘッダー input-event-codes.h が見当たらない場合は、後述する xremap の README からリンクされていますが、xremap のソース を参照しても良いと思います)

私の場合の例の以下の行

 KEYBOARD_KEY_700e2=fn

は、スキャンコード 700e2(物理的な左 Alt キー)が押されたときに KEY_FN というキーコードとして解釈する、という設定です。

同様に

 KEYBOARD_KEY_700e3=leftalt
 KEYBOARD_KEY_700e0=leftmeta
 KEYBOARD_KEY_70039=leftctrl

は、スキャンコード 700e3(物理的な左 Meta キー)を KEY_LEFTALT として、 700e0(物理的な左 Ctrl キー)を KEY_LEFTMETA として、 70039(物理的な CapsLock キー)を KEY_LEFTCTRL として解釈することを表しています。

1-3. sudo systemd-hwdb update を実行して hwdb を更新

変換ルールを書いたファイルができたら、それをコンパイルしてバイナリファイルにする必要があります。

これはコマンドを一つ実行するだけでできます。

$ sudo systemd-hwdb update

1-4. sudo udevadm trigger を実行して hwdb の変更を適用

hwdb のバイナリファイルができたら、それを適用します。

以下のコマンドを実行します。

$ sudo udevadm trigger

ここまで来ると、キーのリマップが完了した状態になっているはずです。

再度 evtest コマンドを実行してみて、今置き換えたキーを押してみましょう。

$ sudo evtest
...

Event: time 1701491035.067116, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70039
Event: time 1701491035.067116, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 1
Event: time 1701491035.067116, -------------- SYN_REPORT ------------
Event: time 1701491035.241117, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70039
Event: time 1701491035.241117, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 0
Event: time 1701491035.241117, -------------- SYN_REPORT ------------
Event: time 1701491038.436174, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e0
Event: time 1701491038.436174, type 1 (EV_KEY), code 125 (KEY_LEFTMETA), value 1
Event: time 1701491038.436174, -------------- SYN_REPORT ------------
Event: time 1701491038.610201, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e0
Event: time 1701491038.610201, type 1 (EV_KEY), code 125 (KEY_LEFTMETA), value 0
Event: time 1701491038.610201, -------------- SYN_REPORT ------------
Event: time 1701491038.898182, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e3
Event: time 1701491038.898182, type 1 (EV_KEY), code 56 (KEY_LEFTALT), value 1
Event: time 1701491038.898182, -------------- SYN_REPORT ------------
Event: time 1701491039.072185, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e3
Event: time 1701491039.072185, type 1 (EV_KEY), code 56 (KEY_LEFTALT), value 0
Event: time 1701491039.072185, -------------- SYN_REPORT ------------
Event: time 1701491039.754193, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e2
Event: time 1701491039.754193, type 1 (EV_KEY), code 464 (KEY_FN), value 1
Event: time 1701491039.754193, -------------- SYN_REPORT ------------
Event: time 1701491039.864188, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e2
Event: time 1701491039.864188, type 1 (EV_KEY), code 464 (KEY_FN), value 0
Event: time 1701491039.864188, -------------- SYN_REPORT ------------

スキャンコード 70039(物理的な CapsLock キー)が左 Ctrl キー(KEY_LEFTCTRL)として解釈されているのがわかると思います。 同様に 700e0(物理的な左 Ctrl キー)が左 Meta (KEY_LEFTMETA) に、700e3(物理的な左 Meta キー)が左 Alt (KEY_LEFTALT) に、700e2(物理的な左 Alt キー)が Fn キー(KEY_FN)として認識されています。

2. xremap を使ったキーバインディングの設定

続いて、キーバインディングを設定していきます。 キーバインディングとは、たとえば Fn+H / J / K / L で←↓↑→ の方向キーと認識されるようにするための設定です。

これを行う方法はいくつかありそうでしたが、 xremap を使うのが令和最新版のような感じがしました。

xremap の README を読めばインストール方法や設定方法はだいたいわかると思いますが、念のためこちらもまとめて行きます。

大まかには以下のような順番で作業を行っていきます。

  1. Rust をインストール(もしインストールされていなければ)
  2. cargo で xremap をインストール
  3. sudo なしで xremap を実行できるように設定
  4. xremap の設定ファイルを作成
  5. テスト実行
  6. ログイン時にバックグラウンドで自動実行されるようにする

2.1 Rust をインストール(もしインストールされていなければ)

xremap は cargo を使ってインストールする方法を取ります。cargo を利用するのに特に深い理由はありません。 そこそこ安定してそうなのでコンパイル済みのバイナリを使ってインストールしても良さそうではあります。

xremap の README にも書いてあるように、Rust とそのパッケージマネージャーである cargo をインストールするには rustup を使います。

今どきのプログラマであれば Rust くらいすでにインストール済みかもしれませんが、新しく構築するまっさらな環境で xremap を使いたいような場合は Rust のインストールが必要なので一応書いておいたという感じです。特に難しいことはないと思うのでここでは詳細には触れません。

2.2 cargo で xremap をインストール

これも特に難しいことはないかと思います。

xremap の README に書いてあるように、cargo install xremap を実行します。今回は Wayland で動かすので

cargo install xremap --features gnome   # GNOME Wayland

こちらの feature を選択します。

2.3. sudo なしで xremap を実行できるように設定

これも xremap の README に書いてありますが、以下の 2 行を実行します。

sudo gpasswd -a "$USER" input
echo 'KERNEL=="uinput", GROUP="input", TAG+="uaccess"' | sudo tee /etc/udev/rules.d/input.rules

これにより、自分のユーザーが input というグループに入り、input グループには uinput へのアクセスが許可されるようなルールが追加されます。

2.4. xremap の設定ファイルを作成

xremap のREADME を読めばどのような設定ファイルを作ればよいかがわかると思います。

私の場合は以下のような内容の設定ファイルを作りました。

virtual_modifiers:
  - fn
keymap:
  - remap:
      fn-a: home
      fn-e: end
      fn-d: delete
      fn-h: left
      fn-j: down
      fn-k: up
      fn-l: right

ファイルを保存する場所やファイル名は何でも良いようです。 私は xremap_conf.yaml というファイル名にして、dotfiles ばかりを集めて Git で管理しているディレクトリに入れました。

私の書いた設定の内容を解説しますと、まず virtual_modifiers: ですが、これは本来モディファイヤキー(Ctrl や Shift のような修飾キー)でないキーをモディファイヤキーのように扱えるようにする xremap の機能です。 何でも良かったのですが私は Fn キーを選びました。(ちなみに RealForce には物理的に Fn キーがありますが、これを押してもスキャンコードは発生しません。他のキーと組み合わせて押したときに一部のキーで発生するスキャンコード自体が変わるようです)

次の keymap: にはシンプルなキーバインドをいくつか設定しています。 keymap の子要素の名前が remap なのでちょっと分かりづらいですが、単純なリマップ(キーの置き換え)だけでなくキーバインドを定義することができます。

私の場合は Fn キーとの組み合わせで Fn + a で Home キー、Fn + e で End キー、Fn + d で Delete キー、Fn + h / j / k / l で ←↓↑→ となるような設定を書きました。 なお、この remap に指定する各要素はいろいろな書き方ができて、単純なキーバインドだけでなく、キーバインドからキーのシーケンスを生成するようなこともできますし、何らかのコマンドを実行したりもできますし、他にもいろいろなことができそうです。

私が使ったのは以下の一番シンプルな形式の設定です。

MOD1-KEY_XXX: MOD2-KEY_YYY

MOD1-MOD2- の部分は、SHIFT- CTRL- ALT- SUPER- のいずれか(およびそれらのバリアント)を指定できます。私の場合は Fn を virtual modifier として使っています。モディファイヤキーの指定は省略することもできます。

KEY_XXXKEY_YYY の部分はキーコードです。KEY_ を省略して XXXYYY だけ書くこともできます。

また、モディファイヤキー部分もキーコード部分もすべて大文字小文字どちらでも指定することができます。 つまり KEY_CAPSLOCKCAPSLOCKCapsLockcapslock もすべて同じ意味です。

私はすべて小文字で書きました。

2.5. テスト実行

設定ファイルができたら、思うように動作するか確認してみましょう。 以下のように xremap コマンドを実行します。

$ xremap ${conf_file}

${conf_file} は設定ファイルのパスに置き換えてください。

このコマンドを実行すると、設定ファイルにエラーがなければ xremap が入力デバイスの一覧を表示し、どのデバイスからの入力に対して適用するかを自動的に判別していることなどが表示されます。

この状態ですでにキーバインドが使えるようになっているので試してみましょう。

どうでしょう?うまく動いていましたか?

動作を確認したら Ctrl-C で終了します。

2.6. ログイン時にバックグラウンドで自動実行されるようにする

思ったとおりの設定ができたことが確認できたら xremap がログイン時に自動実行されるようにしましょう。 (以前、似たような話題で記事を書いたことがあります: Ubuntu 20.04 で GUI 起動時に任意のプログラムを実行したい - bearmini's blog

$HOME/.config/autostart/ ディレクトリに、xremap.desktop のような名前で以下のようなファイルを作成しましょう。

Name=run xremap at startup
Exec=/home/bearmini/.cargo/bin/xremap /home/bearmini/dotfiles/xremap_conf.yaml
Type=Application
X-GNOME-Autostart-enabled=true

Exec= の行の xremap のパスと設定ファイルのパスは適宜変更してください。

ファイルを作成したら一旦ログアウトして再度ログインし直してみてください。

ログイン直後からキーバインドが使えるようになっていたら成功です!


  1. hwdb(7) には "The match lines are followed by one or more key-value pair lines, which are recognized by a leading space character." と書かれています。