Skip to content

Instantly share code, notes, and snippets.

@neogeographica
Last active March 9, 2023 16:12
Show Gist options
  • Save neogeographica/a46b693a1a80acca0170d5bdf69245ea to your computer and use it in GitHub Desktop.
Save neogeographica/a46b693a1a80acca0170d5bdf69245ea to your computer and use it in GitHub Desktop.
switch audio output (PulseAudio)
#!/bin/bash
# For PulseAudio.
# FOR THIS TO WORK, I needed to edit /etc/pulse/default.pa so that this line:
# load-module module-stream-restore
# is instead like this:
# load-module module-stream-restore restore_device=false
#
# I believe this will also undermine the ability to set some app to use a
# non-default sink, and have it stay that way across app restarts, but my
# amount of care about that usecase is zero.
# The grepping/sed-ing patterns for various bits of command output may need
# to be tweaked to work with other systems or audio devices.
# We're going to be dealing with an list of sink indexes stored in a bash
# array. To avoid confusion (maybe) the sink indexes will be called "IDs" in
# the code/comments below, and the array index where an ID is stored will be
# called its index.
# Create an array of sink IDs.
sink_ids=()
mapfile -t sink_ids < <(pacmd list-sinks | grep -oP '^([[:space:]]|\*)*index:[[:space:]]\K[[:digit:]]+')
# Get the ID of the default sink (marked with asterisk).
default_sink_id=$(pacmd list-sinks | grep -oP '^[[:space:]]*\*[[:space:]]*index:[[:space:]]\K[[:digit:]]+')
# Get the index of the default sink.
for index in "${!sink_ids[@]}"; do
if [[ "${sink_ids[$index]}" == "$default_sink_id" ]]; then
default_sink_index=$index
fi
done
# Get the last index in the array.
final_sink_index=$((${#sink_ids[@]} - 1))
# Create a list of the sink descriptions.
sink_descriptions=()
mapfile -t sink_descriptions < <(pacmd list-sinks | sed -n -e 's/.*\(alsa.name\|bluez.alias\)[[:space:]]=[[:space:]]"\(.*\)"/\2/p')
# OK we want to cycle to a new sink ID that should become the default sink. We
# don't want to just "pick the next one" because some sinks are not hooked up
# to anything in reality.
# Ideally I'd support a list of sinks-to-skip here, but in practice right now
# there's only one that I need to skip.
blacklisted_sink_desc="HDMI 0"
# Start our search for next-sink-to-use at the current default sink.
next_sink_index=$default_sink_index
# Begin the search.
looping=true
while [[ $looping == true ]] ; do
# Choose the next sink in the array. Wrap around to the beginning if needed.
if [[ $next_sink_index -ne $final_sink_index ]] ; then
next_sink_index=$((next_sink_index + 1))
else
next_sink_index=0
fi
next_sink_id=${sink_ids[$next_sink_index]}
# Terminate the search if we've come back to our original starting point.
if [[ "$next_sink_index" == "$default_sink_index" ]] ; then
looping=false
fi
# Terminate the search if we found a non-blacklisted sink.
if [[ "${sink_descriptions[$next_sink_index]}" != "$blacklisted_sink_desc" ]] ; then
looping=false
fi
done
# Change the default sink.
pacmd set-default-sink $next_sink_id
# Move all current inputs to the new default sink.
for input_id in $(pacmd list-sink-inputs | grep -oP '^[[:space:]]*index:[[:space:]]\K[[:digit:]]+');
do
pacmd move-sink-input $input_id $next_sink_id
done
# And notify!
notify-send -i audio-volume-high "Sound output switched to ${sink_descriptions[$next_sink_index]}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment