Die Lautstärketasten, die die ganze Zeit funktionierten: ein Sway-Debugging-Krimi
Meine Lautstärketasten „funktionierten nicht mehr“ — und die Tasten waren das Einzige, das nicht kaputt war.
Ich dachte, der Schwarzbildschirm-Beitrag wäre das Ende der Saga. Er war der Anfang eines Hobbys.
Vor ein paar Tagen schrieb ich auf, wie ich auf meinem Dell XPS 13 aus einem schwarzen Bildschirm einen Sway-Desktop machte, den ich wirklich liebe. Dies ist das nächste Kapitel, und es hat eine Moral, die ich immer wieder neu lernen muss: Wenn unter Linux ein Bedienelement „nichts tut“, ist das, was man drückt, selten das Kaputte.
Die Beschwerde
Symptom · kein Ton, keine LeisteMeine Lautstärketasten funktionierten nicht mehr. Beide Arten: die physische Wippe an der Gehäuseseite und die Fn-Tasten auf der Tastatur. Drücken und… nichts. Keine Tonänderung, keine Leiste auf dem Bildschirm. In beiden Fällen dieselbe tote Stille.
Die grausame Ironie — die ich erst später begriff — ist, dass es genau dieselbe Lautstärke-Bindung ist, mit deren Bändigung ich im letzten Beitrag geprahlt habe. Erinnerst du dich an diese Zeile, die ich „den Sicherheitsgurt, von dem ich nicht wusste, dass ich ihn brauche“ nannte?
bindsym XF86AudioRaiseVolume exec wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+
Dieser Sicherheitsgurt war noch angelegt. Nur war das Auto nicht mehr mit der Straße verbunden. Behalte das im Kopf.
Schritt 1: Erreichen die Tasten Sway überhaupt?
Eingrenzen · kam der Tastendruck an?Mein erster Instinkt war wie beim letzten Mal: „Ich habe eine Tastenbindung kaputtgemacht.“ Aber bevor man etwas auseinandernimmt, gibt es einen 30-Sekunden-Test, der das ganze Problem in zwei Hälften teilt: lass die Bindung sich selbst melden. Ich hängte an jede Lautstärketaste neben dem echten Befehl eine Logzeile:
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
Dann swaymsg reload, auf die Tasten hämmern und nachsehen:
cat /tmp/volkeys.log
13:56:45 raise 13:56:46 lower 13:56:53 mute
Das Log war voll. Jeder Druck — Wippe wie Tastatur — erreichte Sway und löste seine Bindung perfekt aus. (Nette Randnotiz: die Gehäusewippe erscheint Sway als ein Intel HID 5 button array, getrennt von der Tastatur. Beide funktionierten.)
Die Tasten waren also unschuldig. Was auch immer falsch war, lebte gänzlich stromabwärts des Tastendrucks. Wie die schwarze Arbeitsfläche beim letzten Mal war mein erster Verdächtiger der eine Teil, der sich tadellos verhielt.
Schritt 2: der Befehl schrie ins Leere
Diagnose · zwei Audio-ServerDie Bindung führte wpctl aus, das PipeWire steuert. Also stellte ich die naheliegende Frage, die ich übersprungen hatte: was treibt meine Lautsprecher gerade tatsächlich an?
pactl info | grep -i "server name"
# Server Name: pulseaudioDa war es. Schlichtes, eigenständiges PulseAudio besaß die Audio-Hardware — aber meine Tasten baten höflich PipeWire, die Lautstärke zu ändern. Beide waren installiert und liefen gleichzeitig:
pgrep -a pulseaudio # the real PulseAudio — holding the speakers hostage pgrep -a pipewire # also running pgrep -a wireplumber # also running
Jeder Tastendruck änderte ein PipeWire-Sink, das niemand hören konnte, während PulseAudio still die eigentliche Ausgabe hielt. Die Sicherheitsgurt-Bindung aus meinem letzten Beitrag war tadellos; sie war in ein Auto ohne Räder geschnallt. Deshalb tat dieselbe Zeile, die vor ein paar Tagen wunderbar lief, jetzt nichts — irgendwann zwischen den Beiträgen hatte sich das echte pulseaudio-Paket zurückgeschlichen und PipeWire die Hardware unter den Füßen weggezogen.
Das ist genau die Art von stillem X11/Wayland-Überbleibsel, vor dem ich mein früheres Ich beim letzten Mal gewarnt habe, nur im Audio-Kostüm.
Dieselbe Zeile, die vor ein paar Tagen wunderbar lief, tat jetzt nichts — ein Sicherheitsgurt, geschnallt in ein Auto ohne Räder.
Der Fix, Teil 1: ein Stack, nicht zwei
Fix · eigenständiges PulseAudio entfernenFedoras vorgesehenes Setup ist PipeWire von oben bis unten, wobei pipewire-pulseaudio die PulseAudio-API als Shim bereitstellt. Also vertrieb ich den echten Daemon:
sudo dnf swap --allowerasing pulseaudio pipewire-pulseaudio systemctl --user enable --now pipewire-pulse.socket pipewire-pulse.service
Eine Falle biss sofort zu: direkt nach dem Tausch scheiterte pactl mit Connection refused, weil pipewire-pulse zwar installiert war, aber noch nicht lief. Die Zeile systemctl --user enable --now ist es, die ihn weckt. Prüfe mit dem Servernamen, den du tatsächlich sehen willst:
pactl info | grep -i "server name"
# Server Name: PulseAudio (on PipeWire 1.4.11)Dieses (on PipeWire) ist der ganze Punkt: ein Stack, kein Tauziehen um die Hardware. Jetzt steuern wpctl und meine Tasten echten Ton.
Der Fix, Teil 2: eine Leiste zum Sehen
Aufbau · eine Bildschirmleiste mit wobDie andere Hälfte meiner Beschwerde war, dass ich nie eine Lautstärkeanzeige sah. Das ist kein Bug — Sway hat schlicht kein eingebautes OSD. (Meine waybar zeigt eine Lautstärke-Zahl, aber ich wollte die große gleitende Leiste, die beim Tastendruck aufblitzt.)
Das Standardwerkzeug dafür ist swayosd, aber es ist für Fedora 43 nicht paketiert. Was in den Repos ist, ist wob — eine herrlich winzige „Wayland Overlay Bar“, die aus Zahlen, die man hineinpipt, eine Leiste zeichnet.
sudo dnf install -y wob
wob liest ganze Zahlen (0–100) von stdin, eine pro Zeile. Die idiomatische Verdrahtung ist ein FIFO, das es tailt. In meiner Sway-Konfiguration:
exec rm -f $XDG_RUNTIME_DIR/wob.sock && mkfifo $XDG_RUNTIME_DIR/wob.sock && tail -f $XDG_RUNTIME_DIR/wob.sock | wob
Dann ändert ein kleiner Wrapper die Lautstärke und schreibt den neuen Pegel ins FIFO. Meiner liegt unter ~/.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'"
Und die Bindungen, endlich auf etwas Sinnvolles gerichtet — beachte, dass der -l-1.0-Sicherheitsgurt aus dem letzten Beitrag genau dort überlebt, wo er hingehört:
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
Drück jetzt eine Taste, und die Leiste gleitet auf dem Bildschirm hoch, während sich die Lautstärke wirklich bewegt. Beide Hälften der Beschwerde, weg.

Drei Fallstricke, die Blut zogen
Fallstricke · drei, die Blut zogenLetztes Mal waren es 3359 % Lautstärke und eine Vollbild-Video-Bridge. Diesmal:
- wob stirbt an einer einzigen schlechten Zeile. Gib ihm eine leere Zeichenkette — etwa wenn dein Lautstärke-Parsing still scheitert — und es loggt Invalid value received und beendet sich. Daher setzt das Skript den Pegel immer auf eine Zahl und klemmt ihn. Pipe nie eine leere Zeile an wob.
- Meine Locale wollte mich stummschalten. wpctl gibt 0.10 aus. Unter meiner deutschen Locale liest awk das Komma als Dezimaltrennzeichen und macht aus 0.10 eine glatte 0. Ein erzwungenes LC_ALL=C im Skript hält den Float ehrlich. (Glaub deiner Statusleiste; misstrau deiner Locale.)
- In ein FIFO ohne Leser zu schreiben hängt ewig. Läuft wob nicht, blockiert printf > fifo — und reißt deinen Tastendruck mit. Den Schreibvorgang in timeout 1 zu wickeln heißt, dass eine tote Leiste deine Lautstärketasten nicht einfrieren kann.
Und die Sway-spezifische Fußnote: exec läuft beim Login, nicht bei swaymsg reload. Die FIFO-und-wob-Zeile startet bei einem Reload also nicht neu; verschwindet die Leiste mitten in der Sitzung, ab- und wieder anmelden.
Glaub deiner Statusleiste; misstrau deiner Locale.
Nebenquest: sperr mich nicht aus, während ich am Strom hänge
Nebenquest · nur im Akkubetrieb sperrenWährend ich schon dabei war, noch ein Ärgernis: an meinem Schreibtisch, am Strom, sperrte sich der Bildschirm nach fünf Minuten immer wieder — ein Passwortprompt jedes Mal, wenn ich mich umdrehte, um etwas auf dem anderen Monitor zu lesen. Das ist swayidle, das tut, was ich ihm gesagt hatte:
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'Sperren nach fünf Minuten, Hintergrundbeleuchtung aus nach zehn. Das Backlight-Aus ist in Ordnung — ein Mauswackler holt das Panel zurück, kein Passwort. Es ist die Sperre, die ich beim Laden lockern wollte: das Backlight nach zehn Minuten weiterhin aus, aber am Netz erst nach einer vollen Stunde sperren. Im Akkubetrieb bleibt die Fünf-Minuten-Sperre — genau dann willst du, dass schnell gesperrt wird.
swayidle kann das Ladegerät nicht sehen, also muss die Strom-Prüfung im Befehl selbst wohnen. Ich verriegelte die frühe Sperre hinter einem winzigen Skript und fügte eine zweite, bedingungslose Sperre bei der Stundenmarke hinzu:
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 sperrt nur im Akkubetrieb; am Netz zieht es sich zurück und überlässt es dem 60-Minuten-Timeout. Es sind fünf Zeilen, die eine Datei lesen, die der Kernel aktuell hält — /sys/class/power_supply/AC/online ist 1, wenn das Ladegerät steckt:
#!/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
Zwei Hinweise: der Glob A{C,DP}* deckt Laptops ab, die den Adapter ADP1 statt AC nennen, und die Backlight-Zeile ist output * power off (Sway 1.11), nicht das ältere dpms off. Derselbe Login-nicht-Reload-Vorbehalt wie bei der Lautstärkeleiste — swayidle startet bei swaymsg reload nicht neu, also pkill swayidle und neu starten.
Was ich meinem früheren Ich sagen würde (Lautstärke-Edition)
- Ein totes Bedienelement ist meist ein Routing-Problem, kein Tasten-Problem. Eine Log-Bindung (echo >> datei) dauert dreißig Sekunden und sagt dir sofort, ob der Tastendruck ankam. Meiner kam immer an.
- Frag, wem die Hardware gehört, bevor du die Tastenbindung anfasst. pactl info hätte das in einer Zeile beendet, hätte ich es zuerst ausgeführt. Zwei Audio-Server, ein Satz Lautsprecher — nur einer gewinnt, und es war nicht der, mit dem meine Tasten sprachen.
- Eine Bindung, die gestern lief, kann von einem Paket sabotiert werden, nicht von einem Tippfehler. Die Zeile war identisch mit der von letzter Woche. Der Stack darunter nicht.
- Wähle Werkzeuge, die es in deinen Repos gibt. swayosd ist reizend und in Fedora 43 gänzlich abwesend; wob ist bescheiden und gleich da. Bescheiden-und-vorhanden schlägt elegant-und-eingebildet.
- swayidle kann die Welt nicht sehen; gib ihm ein Skript, das es kann. Alles Bedingte — Stromzustand, Tageszeit — lebt im Befehl, den swayidle ausführt, nicht in swayidle selbst. Der Kernel reicht dir den Ladezustand bereits in einer einzeiligen Datei; lass einen fünfzeiligen Schiedsrichter sie lesen und entscheiden.
Der Schwarzbildschirm-Beitrag lehrte mich, dass der ruhigst wirkende Teil des Systems oft der schuldige ist. Die Lautstärketasten bewiesen es gleich wieder — sie saßen da und funktionierten perfekt, während ich sie fast neu schrieb, genau wie jenes tadellose, treue foot-Terminal, das sich unter einem schwarzen Rechteck versteckte. Ich glaube langsam, das ist die ganze Persönlichkeit von Sway: nichts tut etwas für dich, und wenn also etwas schiefgeht, ist der Fix immer echt, immer nachvollziehbar und fast nie dort, wo du zuerst hinzeigst.
Sogar mein Aufräumen wehrte sich
Nachschrift · der Befehl, der sich selbst erschossEin letzter Lacher. Beim Aufräumen verirrter Test-Prozesse griff ich zum naheliegenden Einzeiler, um die alte Leiste zu killen:
pkill -f 'tail -f .*wob.sock'
…und meine Shell starb mitten im Befehl. Zweimal. Es dauerte peinlich lange, bis ich es sah: pkill -f matcht gegen die gesamte Kommandozeile jedes Prozesses — einschließlich der Shell, die mein pkill ausführte, deren Kommandozeile buchstäblich die Zeichenkette tail -f .*wob.sock enthielt. Ich hatte einen Befehl geschrieben, der sich selbst aufspürt und erschießt. Die Sway-Reise ist, wie sich zeigt, fraktal: sogar im Aufräumen wartet ein Schwarzer-Arbeitsflächen-Moment.