From 4f9aa96de0f3f830d08136baebcc56015fb4a9ed Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 22 May 2026 09:39:26 +0200 Subject: [PATCH] Beef up audio recovery when something goes wrong --- bin/omarchy-restart-audio | 158 +++++++++++++++++++++++++++++ bin/omarchy-restart-pipewire | 10 -- default/omarchy/omarchy-menu.jsonc | 2 +- migrations/1768916735.sh | 2 +- 4 files changed, 160 insertions(+), 12 deletions(-) create mode 100755 bin/omarchy-restart-audio delete mode 100755 bin/omarchy-restart-pipewire diff --git a/bin/omarchy-restart-audio b/bin/omarchy-restart-audio new file mode 100755 index 00000000..a734c5d6 --- /dev/null +++ b/bin/omarchy-restart-audio @@ -0,0 +1,158 @@ +#!/bin/bash + +# omarchy:summary=Restart audio services and recover stuck USB audio devices. +# omarchy:examples=omarchy restart audio | omarchy-restart-audio + +services=(wireplumber.service pipewire.service pipewire-pulse.service) + +restart_audio_services() { + echo -e "Restarting audio services...\n" + + if timeout 25s systemctl --user restart "${services[@]}"; then + return 0 + fi + + echo -e "\nAudio services did not restart cleanly. Forcing stuck services down...\n" + systemctl --user cancel >/dev/null 2>&1 || true + systemctl --user kill --kill-whom=all --signal=KILL "${services[@]}" >/dev/null 2>&1 || true + systemctl --user reset-failed "${services[@]}" >/dev/null 2>&1 || true + + timeout 25s systemctl --user start pipewire.service pipewire-pulse.service wireplumber.service +} + +wpctl_healthy() { + timeout 5s wpctl status >/dev/null 2>&1 +} + +usb_device_for_card() { + local card="$1" + local path + + path=$(readlink -f "/sys/class/sound/card$card" 2>/dev/null) || return 1 + + while [[ $path != "/" ]]; do + if [[ -f $path/idVendor && -f $path/idProduct && -f $path/busnum && -f $path/devnum ]]; then + basename "$path" + return 0 + fi + + path=$(dirname "$path") + done + + return 1 +} + +append_unique() { + local value="$1" + shift + local existing + + [[ -z $value ]] && return + + for existing in "$@"; do + [[ $existing == "$value" ]] && return + done + + printf '%s\n' "$value" +} + +stuck_usb_audio_devices() { + local status + local state + local owner_pid + local owner_name + local card + local device + local devices=() + local unique + + for status in /proc/asound/card*/pcm*p/sub*/status; do + [[ -e $status ]] || continue + + state=$(awk '/^state:/ { print $2; exit }' "$status") + [[ $state == "SETUP" ]] || continue + + owner_pid=$(awk '/^owner_pid[[:space:]]*:/ { print $3; exit }' "$status") + [[ -n $owner_pid && -r /proc/$owner_pid/comm ]] || continue + + owner_name=$(cat "/proc/$owner_pid/comm" 2>/dev/null || true) + [[ $owner_name == "wireplumber" || $owner_name == "pipewire" ]] || continue + [[ $status =~ /card([0-9]+)/ ]] || continue + + card="${BASH_REMATCH[1]}" + device=$(usb_device_for_card "$card" || true) + unique=$(append_unique "$device" "${devices[@]}") + [[ -n $unique ]] && devices+=("$unique") + done + + printf '%s\n' "${devices[@]}" +} + +reset_usb_audio_device() { + local device="$1" + local sysfs="/sys/bus/usb/devices/$device" + local busnum + local devnum + local bus_device + local product + + [[ -d $sysfs ]] || return 1 + + if ! omarchy-cmd-present usbreset; then + echo "usbreset is not installed; replug or power-cycle $device to recover it." + return 1 + fi + + busnum=$(cat "$sysfs/busnum" 2>/dev/null) || return 1 + devnum=$(cat "$sysfs/devnum" 2>/dev/null) || return 1 + product=$(cat "$sysfs/product" 2>/dev/null || echo "$device") + bus_device=$(printf '%03d/%03d' "$busnum" "$devnum") + + echo -e "\nResetting stuck USB audio device: $product ($bus_device)..." + + if timeout 25s sudo usbreset "$bus_device"; then + return 0 + fi + + echo "Could not reset $product automatically. Replug or power-cycle it, then run omarchy-restart-audio again." + return 1 +} + +recover_stuck_usb_audio() { + local devices=() + local device + local reset_count=0 + + while IFS= read -r device; do + [[ -n $device ]] && devices+=("$device") + done < <(stuck_usb_audio_devices) + + (( ${#devices[@]} > 0 )) || return 1 + + for device in "${devices[@]}"; do + reset_usb_audio_device "$device" && reset_count=$((reset_count + 1)) + done + + (( reset_count > 0 )) +} + +restart_audio_services || true + +if ! wpctl_healthy; then + echo -e "\nPipeWire is still not responding. Checking for stuck USB audio devices..." + + if recover_stuck_usb_audio; then + echo -e "\nRestarting audio services after USB audio reset...\n" + restart_audio_services || true + else + echo "No stuck USB audio device was detected automatically." + fi +fi + +sleep 2 + +echo -e "\nAudio status:\n" +if ! timeout 5s wpctl status; then + echo "Audio services are still not responding. Try replugging or power-cycling the selected USB audio device." + exit 1 +fi diff --git a/bin/omarchy-restart-pipewire b/bin/omarchy-restart-pipewire deleted file mode 100755 index f69be512..00000000 --- a/bin/omarchy-restart-pipewire +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Restart the PipeWire audio service to fix audio issues or apply new configuration. - -echo -e "Restarting PipeWire audio services...\n" -systemctl --user restart wireplumber.service pipewire.service - -if systemctl --user --quiet is-active pipewire-pulse.service; then - systemctl --user restart pipewire-pulse.service -fi diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc index d24c7531..960f9c3e 100644 --- a/default/omarchy/omarchy-menu.jsonc +++ b/default/omarchy/omarchy-menu.jsonc @@ -301,7 +301,7 @@ "update.config.osd": {"icon":"","label":"OSD","keywords":"refresh","action":"omarchy-restart-shell"}, "update.config.tmux": {"icon":"","label":"Tmux","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-tmux"}, "update.config.shell": {"icon":"󰍜","label":"Shell","keywords":"refresh bar quickshell","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-shell"}, - "update.hardware.audio": {"icon":"","label":"Audio","keywords":"restart pipewire","action":"omarchy-launch-floating-terminal-with-presentation omarchy-restart-pipewire"}, + "update.hardware.audio": {"icon":"","label":"Audio","keywords":"restart audio pipewire speakers usb","action":"omarchy-launch-floating-terminal-with-presentation omarchy-restart-audio"}, "update.hardware.wifi": {"icon":"󱚾","label":"Wi-Fi","keywords":"restart wireless","action":"omarchy-launch-floating-terminal-with-presentation omarchy-restart-wifi"}, "update.hardware.bluetooth": {"icon":"󰂯","label":"Bluetooth","keywords":"restart","action":"omarchy-launch-floating-terminal-with-presentation omarchy-restart-bluetooth"}, "update.hardware.trackpad": {"icon":"󰟸","label":"Trackpad","keywords":"restart touchpad","action":"omarchy-launch-floating-terminal-with-presentation omarchy-restart-trackpad"}, diff --git a/migrations/1768916735.sh b/migrations/1768916735.sh index 3f788800..888076d9 100644 --- a/migrations/1768916735.sh +++ b/migrations/1768916735.sh @@ -4,5 +4,5 @@ source "$OMARCHY_PATH/install/config/hardware/asus/fix-mic.sh" source "$OMARCHY_PATH/install/config/hardware/asus/fix-audio-mixer.sh" if omarchy-hw-asus-rog; then - omarchy-restart-pipewire + omarchy-restart-audio fi