ずっと動いていた音量キー:Sway デバッグ・フーダニット

Sway · Dell XPS 13 · Fedora 43

ずっと動いていた音量キー:Sway デバッグ・フーダニット

音量キーが「効かなくなった」——そして壊れていなかった唯一のものが、そのキーだった。

XF86AudioRaiseVolume · 押す → 何も起きない · 音もバーもなし

黒い画面の記事で物語は終わったと思っていた。あれは趣味の始まりだった。

数日前、Dell XPS 13 で黒い画面を本当に気に入る Sway デスクトップに変えた話を書いた。これはその次の章で、何度も学び直す教訓がある。Linux では、操作子が「何もしない」とき、押しているものが壊れているものであることはまずない。


00記録 · 苦情

苦情

症状 · 音もバーもなし

音量キーが効かなくなった。両方とも。筐体側面の物理ロッカーも、キーボードの Fn キーも。押しても……何もない。音は変わらず、画面にバーも出ない。どちらも同じ、死んだような静けさ。

残酷な皮肉は——後になるまで気づかなかったが——これが前回の記事で手なずけたと自慢した、まさにその音量バインドだということ。「必要だと知らなかったシートベルト」と呼んだあの一行を覚えているだろうか?

~/.config/sway/config
bindsym XF86AudioRaiseVolume exec wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+

そのシートベルトはまだ締まっていた。ただ、車が道路につながっていなかっただけだ。これを覚えておいてほしい。

01記録 · 二分

ステップ1:そもそもキーは Sway に届いているのか?

二分 · キー入力は届いたか?

最初の直感は前回同様「キーバインドを壊した」だった。だが何かを分解する前に、問題全体を真っ二つに割る 30 秒のテストがある。バインド自身に名乗らせるのだ。実際のコマンドと並べて、各音量キーにログ行をつないだ:

~/.config/sway/config
bindsym XF86AudioRaiseVolume exec echo "$(date +%T) raise" >> /tmp/volkeys.log; wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+
bindsym XF86AudioLowerVolume exec echo "$(date +%T) lower" >> /tmp/volkeys.log; wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindsym XF86AudioMute        exec echo "$(date +%T) mute"  >> /tmp/volkeys.log; wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle

そして swaymsg reload、キーを連打して、見る:

shell
cat /tmp/volkeys.log
/tmp/volkeys.log
13:56:45 raise
13:56:46 lower
13:56:53 mute

ログは満杯だった。すべての押下——ロッカーもキーボードも——が Sway に届き、バインドを完璧に発火させていた。(余談:筐体のロッカーは Sway からはキーボードとは別の Intel HID 5 button array デバイスとして見える。どちらも動いた。)

つまりキーは無実。何が悪いにせよ、それはキー入力の完全に下流に住んでいた。前回の黒いワークスペースと同じく、最初の容疑者は、完璧に振る舞っていたその部分だった。

02記録 · 診断

ステップ2:コマンドは虚空に叫んでいた

診断 · 二つのオーディオサーバー

バインドは PipeWire を制御する wpctl を実行していた。そこで、飛ばしていた当たり前の問いを立てた。いま実際に私のスピーカーを駆動しているのは何だ?

shell
pactl info | grep -i "server name"
# Server Name: pulseaudio

そこにあった。素の、単体の PulseAudio がオーディオハードウェアを握っていた——なのに私のキーは丁寧にも PipeWire に音量変更を頼んでいた。両方がインストールされ、同時に動いていた:

shell
pgrep -a pulseaudio   # the real PulseAudio — holding the speakers hostage
pgrep -a pipewire     # also running
pgrep -a wireplumber  # also running

押すたびに誰にも聞こえない PipeWire のシンクが変わり、PulseAudio が静かに実際の出力を握っていた。前回のシートベルトのバインドは完璧だった。ただ、車輪のない車に締められていた。数日前に見事に動いた同じ一行が今は何もしない理由がこれだ——記事と記事のあいだのどこかで、本物の pulseaudio パッケージが忍び戻り、PipeWire の足元からハードウェアを奪っていた。

これはまさに、前回、過去の自分に警告した類いの静かな X11/Wayland 時代の置き土産——ただオーディオの衣装をまとっただけだ。

数日前に見事に動いた同じ一行が、今は何もしない——車輪のない車に締められたシートベルト。

03記録 · 修正 1/2

修正・前編:二つではなく一つのスタックに

修正 · 単体 PulseAudio を追い出す

Fedora が意図する構成は、上から下まで PipeWire で、pipewire-pulseaudio が PulseAudio API をシムとして提供する。そこで本物のデーモンを立ち退かせた:

shell
sudo dnf swap --allowerasing pulseaudio pipewire-pulseaudio
systemctl --user enable --now pipewire-pulse.socket pipewire-pulse.service

一つの落とし穴がすぐ噛みついた。入れ替え直後、pactl が Connection refused で失敗した。pipewire-pulse はインストール済みだが、まだ動いていなかったからだ。systemctl --user enable --now の行が、それを起こす。実際に見たいサーバー名で確認する:

shell
pactl info | grep -i "server name"
# Server Name: PulseAudio (on PipeWire 1.4.11)

その (on PipeWire) こそが要点だ。スタックは一つ、ハードウェアの綱引きはない。これで wpctl とキーが本物の音を操る。

04記録 · 修正 2/2

修正・後編:見えるバーを

構築 · wob で画面にバーを

苦情のもう半分は、音量インジケーターが一度も見えなかったこと。これはバグではない——Sway には OSD が組み込まれていないだけだ。(waybar は音量の数値を出すが、私が欲しかったのはキーを押すと光って滑る、あの大きなバーだ。)

定番の道具は swayosd だが、Fedora 43 にはパッケージされていない。リポジトリにあるのは wob——流し込んだ数値からバーを描く、愛らしいほど小さな「Wayland Overlay Bar」だ。

shell
sudo dnf install -y wob

wob は stdin から整数(0–100)を一行ずつ読む。定石の配線は、それが tail する FIFO だ。私の Sway 設定では:

~/.config/sway/config
exec rm -f $XDG_RUNTIME_DIR/wob.sock && mkfifo $XDG_RUNTIME_DIR/wob.sock && tail -f $XDG_RUNTIME_DIR/wob.sock | wob

そして小さなラッパーが音量を変え、新しいレベルを FIFO に書く。私のは ~/.config/sway/volume-wob.sh にある:

~/.config/sway/volume-wob.sh
#!/bin/sh
# Adjust the default sink via wpctl (PipeWire-native) and push the resulting
# level (0-100) to the wob FIFO so Sway shows an on-screen bar.
export LC_ALL=C

WOBSOCK="${XDG_RUNTIME_DIR}/wob.sock"
SINK="@DEFAULT_AUDIO_SINK@"

case "$1" in
	up)   wpctl set-mute "$SINK" 0; wpctl set-volume -l 1.0 "$SINK" 5%+ ;;
	down) wpctl set-mute "$SINK" 0; wpctl set-volume "$SINK" 5%- ;;
	mute) wpctl set-mute "$SINK" toggle ;;
esac

vol=$(wpctl get-volume "$SINK" 2>/dev/null)   # e.g. "Volume: 0.10" or "... [MUTED]"
case "$vol" in
	*MUTED*) level=0 ;;
	*)       level=$(printf '%s' "$vol" | awk '{printf "%d", $2 * 100}') ;;
esac

[ -z "$level" ] && level=0
[ "$level" -gt 100 ] && level=100

# timeout guards against a dead wob reader hanging the keypress (see gotcha #3)
[ -p "$WOBSOCK" ] && timeout 1 sh -c "printf '%s\n' '$level' > '$WOBSOCK'"

そしてバインドは、ついに役立つ先を指す——注目、前回の -l 1.0 のシートベルトが、あるべき場所でちゃんと生き残っている:

~/.config/sway/config
bindsym XF86AudioRaiseVolume exec ~/.config/sway/volume-wob.sh up
bindsym XF86AudioLowerVolume exec ~/.config/sway/volume-wob.sh down
bindsym XF86AudioMute        exec ~/.config/sway/volume-wob.sh mute
bindsym XF86AudioMicMute     exec wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle

今キーを押すと、画面でバーが滑り上がり、音量も実際に動く。苦情の両方の半分が、消えた。

キーを押すと画面で滑り上がる wob の音量バー
キーを押すと画面で滑り上がる wob の音量バー
05記録 · 落とし穴

血を流した三つの落とし穴

落とし穴 · 血を流した三つ

前回は音量 3359% と全画面のビデオブリッジだった。今回は:

  1. wob は一行の不正で死ぬ。 空文字列を一つ与えると——たとえば音量のパースが静かに失敗したとき—— Invalid value received とログを吐いて終了する。だからスクリプトはレベルを常に数値に既定し、クランプする。wob に空行を絶対に流すな。
  2. ロケールが私を黙らせようとした。 wpctl は 0.10 と出す。私のドイツ語ロケールでは awk がカンマを小数点と読み、0.10 を平板な 0 にしてしまう。スクリプトで LC_ALL=C を強制すると小数が正直になる。(ステータスバーを信じよ。ロケールは疑え。)
  3. 読み手のいない FIFO への書き込みは永遠に止まる。 wob が動いていなければ printf > fifo はブロックし——キー入力も道連れにする。書き込みを timeout 1 で包めば、死んだバーが音量キーを凍らせることはない。

そして Sway 固有の脚注:exec は swaymsg reload ではなくログイン時に走る。だから FIFO と wob の行はリロードでは再起動しない。セッション途中でバーが消えたら、ログアウトして入り直す。

ステータスバーを信じよ。ロケールは疑え。

06記録 · サイドクエスト

サイドクエスト:充電中はロックで締め出さないで

サイドクエスト · バッテリー時のみロック

ついでにもう一つの煩わしさ。机で充電しながらだと、画面が五分でロックしてくる——もう一台のモニターを読もうと振り向くたびにパスワード入力。これは swayidle が、私の指示どおりに働いているだけだ:

~/.config/sway/config
exec swayidle -w \
    timeout 300 'swaylock -f -c 000000' \
    timeout 600 'swaymsg "output * power off"' resume 'swaymsg "output * power on"' \
    before-sleep 'swaylock -f -c 000000'

五分でロック、十分でバックライトを消す。バックライト消灯はいい——マウスを少し動かせばパネルが戻り、パスワードは要らない。充電中に緩めたかったのはロックのほうだ。十分でバックライトはやはり消すが、AC ではまる一時間までロックしない。バッテリーでは五分ロックを残す——速くロックしてほしいのはまさにその時だ。

swayidle は充電器を見られないので、電源チェックはコマンドの中に住まわせるしかない。早いロックを小さなスクリプトの後ろに置き、一時間の地点に二つめの無条件ロックを足した:

~/.config/sway/config
exec swayidle -w \
    timeout 300 "$HOME/.config/sway/lock-idle.sh" \
    timeout 600 'swaymsg "output * power off"' resume 'swaymsg "output * power on"' \
    timeout 3600 'swaylock -f -c 000000' \
    before-sleep 'swaylock -f -c 000000'

lock-idle.sh はバッテリー時のみロックする。AC では身を引き、60 分のタイムアウトに任せる。カーネルが最新に保つ一つのファイルを読む五行だ——充電器が挿さっていれば /sys/class/power_supply/AC/online は 1:

~/.config/sway/lock-idle.sh
#!/bin/sh
# Called by swayidle at the short (5 min) timeout. On AC, skip locking and let
# the 60-min timeout lock instead (the backlight still powers off at 10 min).
# On battery, lock right away.
for ac in /sys/class/power_supply/A{C,DP}*/online; do
    [ -r "$ac" ] && [ "$(cat "$ac")" = "1" ] && exit 0   # on AC: don't lock yet
done
swaylock -f -c 000000

注記二つ。グロブ A{C,DP}* は、アダプターを AC ではなく ADP1 と名付けるノートをカバーする。そしてバックライトの行は、古い dpms off ではなく output * power off(Sway 1.11)だ。音量バーと同じ「ログイン時であってリロード時でない」注意も効く——swayidle は swaymsg reload では再起動しないので、pkill swayidle して起動し直す。

07フィールドノート

過去の自分に言いたいこと(音量編)

  1. 死んだ操作子は、たいていキーの問題ではなくルーティングの問題だ。 ログするバインド(echo >> ファイル)は三十秒で済み、キー入力が届いたかを即座に教える。私のは毎回届いていた。
  2. キーバインドに触れる前に、ハードウェアの持ち主を問え。 先に走らせていれば pactl info が一行でこれを終わらせていた。二つのオーディオサーバー、一組のスピーカー——勝つのは一つだけで、それは私のキーが話していた相手ではなかった。
  3. 昨日動いたバインドは、タイポではなくパッケージに妨害されうる。 一行は先週と同一だった。その下のスタックが違った。
  4. 自分のリポジトリに存在する道具を選べ。 swayosd は素敵だが Fedora 43 には皆無。wob は地味だがすぐそこにある。地味で在るは、優雅で空想に勝る。
  5. swayidle は世界を見られない。見られるスクリプトを渡せ。 条件しだいのもの——電源状態や時刻——は swayidle 自身ではなく、それが実行するコマンドに住む。カーネルは充電状態をすでに一行のファイルで渡してくれる。五行の審判に読ませて決めさせよ。

黒い画面の記事は、最も穏やかに見える部分こそしばしば犯人だと教えてくれた。音量キーはそれをまた証明した——私がほとんど書き直しかけている間、そこで完璧に動いていた。黒い四角の下に隠れていた、あの完璧で忠実な foot 端末とまったく同じだ。Sway を走らせることの人格は結局これなのだと思い始めている。何も自分のためにはしてくれない。だから何かがおかしくなったとき、直しは常に実在し、常に腑に落ち、そして最初に指さす場所にはまずない。

08追記

後片付けまで反撃してきた

追記 · 自分を撃ったコマンド

最後の笑い。散らかったテストプロセスを片付けようと、古いバーを殺す当たり前の一行に手を伸ばした:

shell
pkill -f 'tail -f .*wob.sock'

……すると、シェルがコマンドの途中で死んだ。二度も。気づくのに恥ずかしいほど時間がかかった。pkill -f はすべてのプロセスのコマンドライン全体に対して照合する——その pkill を走らせているまさにそのシェルも含めて。そのコマンドラインには文字どおり tail -f .*wob.sock という文字列が入っていた。自分を狩って撃つコマンドを書いていたのだ。Sway の旅は、どうやらフラクタルらしい。後片付けの中にすら、黒いワークスペースの瞬間が待っている。

コメント (0)

まだコメントがありません。最初のコメントを書いてみましょう!

コメントを書く