Surface Laptop Go 2 + Arch Linux + HyprlandでFnキーオンの状態でF3(VOLUME_DOWN)、F4(VOLUME_UP)の挙動がおかしい

# 問題
この間、Surface Laptop Go 2 + Arch Linux + Hyprlandで初めてのLinuxデスクトップ環境を構築しました。
しかし、音量DOWN・UPボタンを押すとなぜか設定量の2倍音量が上下するようになってしまいました…………………………………… :cry: 

# 環境
- Surface Laptop Go 2
- Arch Linux
- linux-surfaceカーネル v6.14.2.arch1
- Hyprland v0.49.0
- libinput v1.28.1


# 問題の調査
:::note
このSurfaceは切り替え式のFnキーを持っていて、キーボード上段のF1〜F12の動作を、Fnキーがオンかオフかでファンクションキー/操作キー(音量調整など)と切り替える方式を取っています。
今回不具合を起こしたF3, F4のFnキーを交えた期待する動作は以下です。

| Fnキー | F3 | F4 |
|:-:|:-:|:-:|
| オン | F3が入力 | F4が入力 |
| オフ | 音量が設定量ダウン | 音量が設定量アップ |

:::

## 問題の把握
音量調整ボタン(F3,F4)を押したときは以下の挙動を示しました。

| Fnキー | F3 | F4 |
|:-:|:-:|:-:|
| オン | F3は反応、音量が設定量ダウン  | F4は反応・音量は設定量アップ |
| オフ | 音量が2倍量ダウン | 音量が2倍量アップ |


## アプリから見たキー入力を調べる
はじめに、 [wev](https://github.com/jwrdegoede/wev) を使ってアプリケーションから見てどのようなキーが入力されているのか調査しました。

以下のコマンドを実行します。
```zsh:zsh
# wevがインストールされていない場合はインストール
sudo pacman -S wev
# wevを実行
wev
```

するとチェッカー柄のウィンドウが立ち上がり、シェルにはそのウィンドウで受け取ったキーが次々表示されるようになります。

Fnキーをオン・オフにしてF3(音量ダウン), F4(音量アップ)を押すと、以下のように出力されました。

| Fnキー | F3 | F4 |
|:-:|:-:|:-:|
| オン |KEY_ VOLUME_DOWNとF3が同時に1回ずつ重複して出力される | KEY_VOLUME_UPとF3が同時に1回ずつ重複して出力される |
| オフ | KEY_VOLUME_DOWNが2回重複して出力される | KEY_VOLUME_UPが2回重複して出力される |

キーが重複して入力されているのが問題の原因だと分かりました。

## hyprlandから見たキー入力を調べる
hyprlandはlibinputというソフトウェアからキー入力を受け取っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1777479/968f2a05-f938-4716-8cef-6cb65f312272.png)
(https://wayland.freedesktop.org/libinput/doc/latest/what-is-libinput.html より引用)

`libinput debug-events` を使ってlibinputがhyprlandに送っているキー入力を調べましょう！

以下のコマンドを実行します。
```zsh:zsh
sudo libinput debug-events
```

これでF3、F4を押すとlibinputが入力デバイスから受け取ったキー入力が次々と表示されます。

出力は以下のようになりました。 (wevと同じように重複)

| Fnキー | F3 | F4 |
|:-:|:-:|:-:|
| オン | event10から受け取ったKEY_ VOLUME_DOWN、event1から受け取ったF3が重複して出力 | event10から受け取ったKEY_VOLUME_UP、event1から受け取ったF3が重複して出力 |
| オフ | event10から受け取ったKEY_VOLUME_DOWN、event4から受けとったKEY_VOLUME_DOWNが重複して出力 | event10から受け取ったKEY_VOLUME_UP、event4から受けとったKEY_VOLUME_UPが重複して出力  |

見ると、このSurfaceには複数の入力デバイスがあって、event1に割り当てられているデバイスが通常のキー入力を送信し、event4に割り当てられているデバイスがFnキーがオフの間だけF3、F4を押されたときにKEY_VOLUME_DOWN、KEY_VOLUME_UPを送信していることが分かります。

どうやら原因は、Fnキーのオンオフにかかわらずevent10に割り当てられているデバイスがKEY_VOLUME_DOWN/UPを送信していることにありそうです。

event10に割り当てられているデバイスを無効化すれば解決しそうなので、もう少し調査してみます。

## evtestによって入力デバイスからの入力を調べる
evtestというツールを使って、入力デバイスが正常に動作しているかを確認します。

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

```zsh::zsh
# インストールされていない場合はevtestをインストール
sudo pacman -S evtest
# evtestを実行
sudo evtest
```

するとデバイスを選ぶ画面に遷移します。自分は以下のように表示されました。

```zsh:zsh
/dev/input/event0:	Lid Switch
/dev/input/event1:	ELAN Touchpad and Keyboard
/dev/input/event10:	gpio-keys
/dev/input/event11:	gpio-keys
/dev/input/event12:	MELF0411:00 1FD2:9005
/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
/dev/input/event2:	ELAN Touchpad and Keyboard Mouse
/dev/input/event3:	ELAN Touchpad and Keyboard Touchpad
/dev/input/event4:	ELAN Touchpad and Keyboard Consumer Control
/dev/input/event5:	ELAN Touchpad and Keyboard Wireless Radio Control
/dev/input/event6:	ELAN Touchpad and Keyboard UNKNOWN
/dev/input/event7:	ELAN Touchpad and Keyboard System Control
/dev/input/event8:	Video Bus
/dev/input/event9:	PC Speaker
Select the device event number [0-18]:
```

ここで、先程登場したevent1、event10、event4をそれぞれ調査してみます。

結果は以下のようになりました。

### event1 (ELAN Touchpad and Keyboard)
通常の他のキーに対しても反応しました。

| Fnキー | F3 | F4 |
|:-:|:-:|:-:|
| オン | F3を出力 | F4を出力 |
| オフ | 反応なし | 反応なし  |

### event4 (ELAN Touchpad an Keyboard Consumer Control)
通常の他のキーに対しては反応しませんでした。

| Fnキー | F3 | F4 |
|:-:|:-:|:-:|
| オン | 反応なし | 反応なし |
| オフ | KEY_VOLUME_DOWNを出力 | KEY_VOLUME_UPを出力 |

### event10 (gpio-keys)
通常の他のキーに対しては反応しませんでした。

| Fnキー | F3 | F4 |
|:-:|:-:|:-:|
| オン | KEY_VOLUME_DOWNを出力 | KEY_VOLUME_DOWNを出力 |
| オフ | KEY_VOLUME_DOWNを出力 | KEY_VOLUME_UPを出力 |

やはり、event10に割り当てられているgpio-keysを無効化すれば解決しそうですね！

# 解決方法
udevルールによってgpio-keysのデバイスを無視するよう設定します。

## デバイス名を確認
はじめに、改めて入力デバイスの名前を確認しましょう。

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

```zsh:zsh
cat /proc/bus/input/devices
```

自分は以下のように出力されました。
```zsh:zsh
# 中略〜

I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/platform/MSHW0040:00/gpio-keys.1.auto/input/input18
U: Uniq=
H: Handlers=kbd event10 
B: PROP=0
B: EV=100003
B: KEY=c000000000000 0

# 〜中略
```
Nameの部分がデバイス名、Handlersの部分がevent番号です。ここではデバイス名はgpio-keysでした。

## udevルールファイルを作成
設定ファイルを作成します。ファイル名は `99-` で始めると他のルールよりあとに読み込まれて良いそうです。

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

```zsh:zsh
sudo vim /etc/udev/rules.d/99-surface-gpio-keys-ignore.rules
```

ファイルの中身を以下に設定しましょう。

```99-surface-gpio-keys-ignore.rules
ACTION=="add|change", KERNEL=="event*", ATTRS{name}=="gpio-keys", ENV{LIBINPUT_IGNORE_DEVICE}="1"
```

`ATTRS{name}` には先程確認したデバイス名を入れてください。

## udevルールを適用
以下のコマンドを実行して、設定したルールを適用します。

```zsh:zsh
sudo udevadm control --reload-rules && sudo udevadm trigger
```

最後に再起動しましょう。

# 直ったのを確認
無事に音量ボタンが動作するようになりました！！！！

libinputからgpio-keysのデバイスが認識できなくなっていることを以下のコマンドで確認しましょう。

```zsh:zsh
sudo libinput list-devices | grep Device
```

自分は以下のように出力されました。

```zsh:zsh
Device:                  Video Bus
Device:                  Lid Switch
Device:                  ELAN Touchpad and Keyboard
Device:                  ELAN Touchpad and Keyboard Touchpad
Device:                  ELAN Touchpad and Keyboard Consumer Control
Device:                  ELAN Touchpad and Keyboard Wireless Radio Control
Device:                  ELAN Touchpad and Keyboard Mouse
Device:                  MELF0411:00 1FD2:9005
```

gpio-keysが表示されなければ成功です。

おめでとうございます！！ :tada: :tada: