ずっと動いていた音量キー:Sway デバッグ・フーダニット
音量キーが「効かなくなった」——そして壊れていなかった唯一のものが、そのキーだった。
黒い画面の記事で物語は終わったと思っていた。あれは趣味の始まりだった。
数日前、Dell XPS 13 で黒い画面を本当に気に入る Sway デスクトップに変えた話を書いた。これはその次の章で、何度も学び直す教訓がある。Linux では、操作子が「何もしない」とき、押しているものが壊れているものであることはまずない。
苦情
症状 · 音もバーもなし音量キーが効かなくなった。両方とも。筐体側面の物理ロッカーも、キーボードの Fn キーも。押しても……何もない。音は変わらず、画面にバーも出ない。どちらも同じ、死んだような静けさ。
残酷な皮肉は——後になるまで気づかなかったが——これが前回の記事で手なずけたと自慢した、まさにその音量バインドだということ。「必要だと知らなかったシートベルト」と呼んだあの一行を覚えているだろうか?
bindsym XF86AudioRaiseVolume exec wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+
そのシートベルトはまだ締まっていた。ただ、車が道路につながっていなかっただけだ。これを覚えておいてほしい。
ステップ1:そもそもキーは Sway に届いているのか?
二分 · キー入力は届いたか?最初の直感は前回同様「キーバインドを壊した」だった。だが何かを分解する前に、問題全体を真っ二つに割る 30 秒のテストがある。バインド自身に名乗らせるのだ。実際のコマンドと並べて、各音量キーにログ行をつないだ:
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、キーを連打して、見る:
cat /tmp/volkeys.log
13:56:45 raise 13:56:46 lower 13:56:53 mute
ログは満杯だった。すべての押下——ロッカーもキーボードも——が Sway に届き、バインドを完璧に発火させていた。(余談:筐体のロッカーは Sway からはキーボードとは別の Intel HID 5 button array デバイスとして見える。どちらも動いた。)
つまりキーは無実。何が悪いにせよ、それはキー入力の完全に下流に住んでいた。前回の黒いワークスペースと同じく、最初の容疑者は、完璧に振る舞っていたその部分だった。
ステップ2:コマンドは虚空に叫んでいた
診断 · 二つのオーディオサーバーバインドは PipeWire を制御する wpctl を実行していた。そこで、飛ばしていた当たり前の問いを立てた。いま実際に私のスピーカーを駆動しているのは何だ?
pactl info | grep -i "server name"
# Server Name: pulseaudioそこにあった。素の、単体の PulseAudio がオーディオハードウェアを握っていた——なのに私のキーは丁寧にも PipeWire に音量変更を頼んでいた。両方がインストールされ、同時に動いていた:
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 時代の置き土産——ただオーディオの衣装をまとっただけだ。
数日前に見事に動いた同じ一行が、今は何もしない——車輪のない車に締められたシートベルト。
修正・前編:二つではなく一つのスタックに
修正 · 単体 PulseAudio を追い出すFedora が意図する構成は、上から下まで PipeWire で、pipewire-pulseaudio が PulseAudio API をシムとして提供する。そこで本物のデーモンを立ち退かせた:
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 の行が、それを起こす。実際に見たいサーバー名で確認する:
pactl info | grep -i "server name"
# Server Name: PulseAudio (on PipeWire 1.4.11)その (on PipeWire) こそが要点だ。スタックは一つ、ハードウェアの綱引きはない。これで wpctl とキーが本物の音を操る。
修正・後編:見えるバーを
構築 · wob で画面にバーを苦情のもう半分は、音量インジケーターが一度も見えなかったこと。これはバグではない——Sway には OSD が組み込まれていないだけだ。(waybar は音量の数値を出すが、私が欲しかったのはキーを押すと光って滑る、あの大きなバーだ。)
定番の道具は swayosd だが、Fedora 43 にはパッケージされていない。リポジトリにあるのは wob——流し込んだ数値からバーを描く、愛らしいほど小さな「Wayland Overlay Bar」だ。
sudo dnf install -y wob
wob は stdin から整数(0–100)を一行ずつ読む。定石の配線は、それが tail する FIFO だ。私の Sway 設定では:
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 にある:
#!/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 のシートベルトが、あるべき場所でちゃんと生き残っている:
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
今キーを押すと、画面でバーが滑り上がり、音量も実際に動く。苦情の両方の半分が、消えた。

血を流した三つの落とし穴
落とし穴 · 血を流した三つ前回は音量 3359% と全画面のビデオブリッジだった。今回は:
- wob は一行の不正で死ぬ。 空文字列を一つ与えると——たとえば音量のパースが静かに失敗したとき—— Invalid value received とログを吐いて終了する。だからスクリプトはレベルを常に数値に既定し、クランプする。wob に空行を絶対に流すな。
- ロケールが私を黙らせようとした。 wpctl は 0.10 と出す。私のドイツ語ロケールでは awk がカンマを小数点と読み、0.10 を平板な 0 にしてしまう。スクリプトで LC_ALL=C を強制すると小数が正直になる。(ステータスバーを信じよ。ロケールは疑え。)
- 読み手のいない FIFO への書き込みは永遠に止まる。 wob が動いていなければ printf > fifo はブロックし——キー入力も道連れにする。書き込みを timeout 1 で包めば、死んだバーが音量キーを凍らせることはない。
そして Sway 固有の脚注:exec は swaymsg reload ではなくログイン時に走る。だから FIFO と wob の行はリロードでは再起動しない。セッション途中でバーが消えたら、ログアウトして入り直す。
ステータスバーを信じよ。ロケールは疑え。
サイドクエスト:充電中はロックで締め出さないで
サイドクエスト · バッテリー時のみロックついでにもう一つの煩わしさ。机で充電しながらだと、画面が五分でロックしてくる——もう一台のモニターを読もうと振り向くたびにパスワード入力。これは swayidle が、私の指示どおりに働いているだけだ:
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 は充電器を見られないので、電源チェックはコマンドの中に住まわせるしかない。早いロックを小さなスクリプトの後ろに置き、一時間の地点に二つめの無条件ロックを足した:
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:
#!/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 して起動し直す。
過去の自分に言いたいこと(音量編)
- 死んだ操作子は、たいていキーの問題ではなくルーティングの問題だ。 ログするバインド(echo >> ファイル)は三十秒で済み、キー入力が届いたかを即座に教える。私のは毎回届いていた。
- キーバインドに触れる前に、ハードウェアの持ち主を問え。 先に走らせていれば pactl info が一行でこれを終わらせていた。二つのオーディオサーバー、一組のスピーカー——勝つのは一つだけで、それは私のキーが話していた相手ではなかった。
- 昨日動いたバインドは、タイポではなくパッケージに妨害されうる。 一行は先週と同一だった。その下のスタックが違った。
- 自分のリポジトリに存在する道具を選べ。 swayosd は素敵だが Fedora 43 には皆無。wob は地味だがすぐそこにある。地味で在るは、優雅で空想に勝る。
- swayidle は世界を見られない。見られるスクリプトを渡せ。 条件しだいのもの——電源状態や時刻——は swayidle 自身ではなく、それが実行するコマンドに住む。カーネルは充電状態をすでに一行のファイルで渡してくれる。五行の審判に読ませて決めさせよ。
黒い画面の記事は、最も穏やかに見える部分こそしばしば犯人だと教えてくれた。音量キーはそれをまた証明した——私がほとんど書き直しかけている間、そこで完璧に動いていた。黒い四角の下に隠れていた、あの完璧で忠実な foot 端末とまったく同じだ。Sway を走らせることの人格は結局これなのだと思い始めている。何も自分のためにはしてくれない。だから何かがおかしくなったとき、直しは常に実在し、常に腑に落ち、そして最初に指さす場所にはまずない。
後片付けまで反撃してきた
追記 · 自分を撃ったコマンド最後の笑い。散らかったテストプロセスを片付けようと、古いバーを殺す当たり前の一行に手を伸ばした:
pkill -f 'tail -f .*wob.sock'
……すると、シェルがコマンドの途中で死んだ。二度も。気づくのに恥ずかしいほど時間がかかった。pkill -f はすべてのプロセスのコマンドライン全体に対して照合する——その pkill を走らせているまさにそのシェルも含めて。そのコマンドラインには文字どおり tail -f .*wob.sock という文字列が入っていた。自分を狩って撃つコマンドを書いていたのだ。Sway の旅は、どうやらフラクタルらしい。後片付けの中にすら、黒いワークスペースの瞬間が待っている。