137 lines
3.8 KiB
Bash
Executable File
137 lines
3.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# Description: Switch PulseAudio/PipeWire audio output sinks
|
|
|
|
STATE_FILE="${XDG_CACHE_HOME:-$HOME/.cache}/audio-toggle-prev-sink"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $(basename "$0") [OPTION]
|
|
|
|
Toggle or switch PulseAudio/PipeWire audio output sinks.
|
|
|
|
Options:
|
|
--to <pattern> Switch to a sink whose name matches <pattern>
|
|
--prev, --previous Switch back to the previously active sink
|
|
--list List available sinks
|
|
--current Show the current default sink
|
|
--move-streams Also move existing audio streams to the new sink
|
|
--help, -h Show this help message
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
get_current_sink() {
|
|
pactl info | sed -En 's/Default Sink: (.*)/\1/p'
|
|
}
|
|
|
|
get_sink_description() {
|
|
local sink="$1"
|
|
pactl list sinks | awk -v s="$sink" '
|
|
/^[[:space:]]*Sink #[0-9]+/ { in_block=""; name="" }
|
|
$1 == "Name:" { name = $2 }
|
|
name == s && /Description:/ { $1=""; sub(/^[[:space:]]+/, ""); print; exit }
|
|
'
|
|
}
|
|
|
|
MOVE_STREAMS=0
|
|
MODE="toggle"
|
|
TARGET_SINK=""
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--help|-h) usage ;;
|
|
--list)
|
|
pactl list short sinks | grep -v easyeffects | while read -r line; do
|
|
name=$(echo "$line" | awk '{print $2}')
|
|
desc=$(get_sink_description "$name")
|
|
echo "$name ($desc)"
|
|
done
|
|
exit 0
|
|
;;
|
|
--current)
|
|
sink=$(get_current_sink)
|
|
desc=$(get_sink_description "$sink")
|
|
echo "$sink ($desc)"
|
|
exit 0
|
|
;;
|
|
--prev|--previous)
|
|
if [ ! -f "$STATE_FILE" ]; then
|
|
notify-send "Audio Toggle" "No previous sink saved"
|
|
exit 1
|
|
fi
|
|
prev=$(cat "$STATE_FILE")
|
|
if ! pactl list short sinks | awk '{print $2}' | grep -qxF "$prev"; then
|
|
notify-send "Audio Toggle" "Previous sink no longer available"
|
|
exit 1
|
|
fi
|
|
current=$(get_current_sink)
|
|
echo "$current" > "$STATE_FILE"
|
|
TARGET_SINK="$prev"
|
|
MODE="switch"
|
|
;;
|
|
--to)
|
|
shift
|
|
[ $# -eq 0 ] && echo "Error: --to requires a pattern" >&2 && exit 1
|
|
matched=$(pactl list short sinks | grep -v easyeffects | awk '{print $2}' | grep -i "$1" | head -1)
|
|
[ -z "$matched" ] && echo "Error: no sink matching '$1'" >&2 && exit 1
|
|
current=$(get_current_sink)
|
|
echo "$current" > "$STATE_FILE"
|
|
TARGET_SINK="$matched"
|
|
MODE="switch"
|
|
;;
|
|
--move-streams) MOVE_STREAMS=1 ;;
|
|
*)
|
|
echo "Unknown option: $1" >&2
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
SINKS=$(pactl list short sinks | grep -v easyeffects)
|
|
SINK_COUNT=$(echo "$SINKS" | wc -l)
|
|
|
|
if [ "$SINK_COUNT" -eq 0 ]; then
|
|
notify-send -u critical "Audio Toggle" "No audio sinks available"
|
|
exit 1
|
|
fi
|
|
|
|
CURRENT_SINK=$(get_current_sink)
|
|
|
|
if [ "$MODE" = "switch" ] && [ -n "$TARGET_SINK" ]; then
|
|
NEW_SINK="$TARGET_SINK"
|
|
else
|
|
SINK_INDEX=$(echo "$SINKS" | awk -v s="$CURRENT_SINK" '$2 == s { print NR; exit }')
|
|
SINK_INDEX=${SINK_INDEX:-0}
|
|
NEXT_INDEX=$((SINK_INDEX % SINK_COUNT + 1))
|
|
NEW_SINK=$(echo "$SINKS" | awk "NR == $NEXT_INDEX { print \$2; exit }")
|
|
echo "$CURRENT_SINK" > "$STATE_FILE"
|
|
fi
|
|
|
|
NEW_DESC=$(get_sink_description "$NEW_SINK")
|
|
notify-send "Audio Output" "Switched to $NEW_DESC" --icon=audio-speakers
|
|
|
|
MAX_RETRIES=6
|
|
RETRIES=0
|
|
|
|
while true; do
|
|
[ "$RETRIES" -ge "$MAX_RETRIES" ] && notify-send -u critical "Audio Toggle" "Failed after $MAX_RETRIES attempts" && break
|
|
|
|
pactl set-default-sink "$NEW_SINK"
|
|
|
|
[ "$(get_current_sink)" = "$NEW_SINK" ] && break
|
|
|
|
RETRIES=$((RETRIES + 1))
|
|
NEXT_INDEX=$(((SINK_INDEX + RETRIES) % SINK_COUNT + 1))
|
|
NEW_SINK=$(echo "$SINKS" | awk "NR == $NEXT_INDEX { print \$2; exit }")
|
|
NEW_DESC=$(get_sink_description "$NEW_SINK")
|
|
notify-send "Audio Toggle" "Sink unavailable, trying $NEW_DESC..."
|
|
done
|
|
|
|
if [ "$MOVE_STREAMS" = 1 ]; then
|
|
pactl list short sink-inputs | grep -Eo '^[0-9]+' | while read -r id; do
|
|
pactl move-sink-input "$id" "$NEW_SINK"
|
|
done
|
|
fi
|