From 8a9b87a036d1d801b7a9e03702a3300a61f9ab74 Mon Sep 17 00:00:00 2001 From: Alexander Rosenberg Date: Fri, 6 Feb 2026 17:43:19 -0800 Subject: [PATCH] Rewrite select-sound-output.sh --- old/pulse/select-sound-output-pulse.sh | 28 +++++++ system-menu/select-sound-output.sh | 100 ++++++++++++++++++------- 2 files changed, 103 insertions(+), 25 deletions(-) create mode 100755 old/pulse/select-sound-output-pulse.sh diff --git a/old/pulse/select-sound-output-pulse.sh b/old/pulse/select-sound-output-pulse.sh new file mode 100755 index 0000000..4585abe --- /dev/null +++ b/old/pulse/select-sound-output-pulse.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env zsh +local info="$(pactl list sinks)" +local default_name="$(pactl info | grep -Po '(?<=Default Sink: ).+')" +local ids=(${(@f)"$(echo "${info}" | grep -Po '(?<=Sink #).+')"}) +local names=(${(@f)"$(echo "${info}" | grep -Po '(?<=Name: ).+')"}) +local descs=(${(@f)"$(echo "${info}" | grep -Po '(?<=Description: ).+')"}) +local default_id="" +local query_string="" +for ((i = 1; i <= "${#ids}"; ++i)); do + if [[ "${names[${i}]}" == "${default_name}" ]]; then + default_id="${ids[${i}]}" + fi + query_string+="${ids[${i}]}: ${descs[${i}]} (${names[${i}]})\n" +done +[[ -v WAYLAND_DISPLAY ]] \ + && prompt_cmd=("fuzzel" "-dp" "${default_id}> ") \ + || prompt_cmd=("dmenu" "-p" "${default_id}:") +local choice +choice="$(echo "${query_string%"\n"}" | ${prompt_cmd})" +if (( "${?}" != 0 )); then + exit +fi +local selected_id="$(echo "${choice}" | grep -Eo '^[0-9]+')" +pactl set-default-sink "${selected_id}" +local selected_name="$(echo "${choice}" \ + | cut -c "$(("${#selected_id}" + 3))-" \ + | grep -Po '^.+(?= \()')" +notify-send 'Output Device' "${selected_name} (${selected_id})" diff --git a/system-menu/select-sound-output.sh b/system-menu/select-sound-output.sh index 4585abe..917948d 100755 --- a/system-menu/select-sound-output.sh +++ b/system-menu/select-sound-output.sh @@ -1,28 +1,78 @@ #!/usr/bin/env zsh -local info="$(pactl list sinks)" -local default_name="$(pactl info | grep -Po '(?<=Default Sink: ).+')" -local ids=(${(@f)"$(echo "${info}" | grep -Po '(?<=Sink #).+')"}) -local names=(${(@f)"$(echo "${info}" | grep -Po '(?<=Name: ).+')"}) -local descs=(${(@f)"$(echo "${info}" | grep -Po '(?<=Description: ).+')"}) -local default_id="" -local query_string="" -for ((i = 1; i <= "${#ids}"; ++i)); do - if [[ "${names[${i}]}" == "${default_name}" ]]; then - default_id="${ids[${i}]}" + +setopt pipefail typeset_silent + +get_default_sink() { + local -a first_line + first_line=${${(f)"$(wpctl inspect @DEFAULT_AUDIO_SINK@)"}[1]} + if (( ${?} )); then + echo "error: getting default sink with wpctl failed with error code ${?}" >&2 + exit 1 + elif [[ ${first_line} =~ '^id ([0-9]+), ' ]]; then + printf '%s' "${match}" + else + echo "error: getting default sink with wpctl failed" >&2 + exit 1 fi - query_string+="${ids[${i}]}: ${descs[${i}]} (${names[${i}]})\n" -done -[[ -v WAYLAND_DISPLAY ]] \ - && prompt_cmd=("fuzzel" "-dp" "${default_id}> ") \ - || prompt_cmd=("dmenu" "-p" "${default_id}:") -local choice -choice="$(echo "${query_string%"\n"}" | ${prompt_cmd})" -if (( "${?}" != 0 )); then - exit +} + +let current_sink="$(get_default_sink)" + +local JQ_SCRIPT=$(<<'EOF' +.[] | select(.type == "PipeWire:Interface:Node" + and .info.props.["media.class"] == "Audio/Sink") + | "\(.id)\u0000\(.info.props.["node.nick"]) - \(.info.props.["node.description"]) (\(.id))\u0000\(.info.props.["device.icon-name"])\u0000" +EOF +) + +local -a id_desc_pairs + +id_desc_pairs=(${(0)"$(pw-dump | jq -rj "${JQ_SCRIPT}")"}) +if (( ${?} )); then + echo "error: pw-dump or jq failed with error code ${?}" >&2 + exit 1 fi -local selected_id="$(echo "${choice}" | grep -Eo '^[0-9]+')" -pactl set-default-sink "${selected_id}" -local selected_name="$(echo "${choice}" \ - | cut -c "$(("${#selected_id}" + 3))-" \ - | grep -Po '^.+(?= \()')" -notify-send 'Output Device' "${selected_name} (${selected_id})" + +generate_icon_names() { + # I got this idea from the pavucontrol source + # - First try the icon name as is + # - Then concatenate "-symbolic" + # - Then remove the last dash (-) character and everything after it + # - Then again try that with "-symbolic" + local nonl="${1//%'\n'/}" + printf '%s,%s' "${nonl}" "${nonl}-symbolic" + if [[ ${1} = *-* ]]; then + printf ',%s,%s' "${nonl%-*}" "${nonl%-*}-symbolic" + fi +} + +local -a ids descs fuzzel_ents + +for id desc icon in ${id_desc_pairs}; do + ids+=("${id}") + descs+=("${desc}") + fuzzel_ents+=("${desc//$'\n'/}" "$(generate_icon_names "${icon}")") +done + +local fuzzel_index + +fuzzel_index="$(printf '%s\0icon\x1f%s\n' ${fuzzel_ents} \ + | fuzzel -p "Cur: ${current_sink}> " --dmenu --index)" +if (( ${?} )); then + echo "Canceled by user" >&2 + exit 0 +elif [[ ${fuzzel_res} = -1 ]]; then + echo "User selected non-existant entry" >&2 + exit 0 +fi + +let new_id=$(( ids[fuzzel_index + 1] )) + +wpctl set-default ${new_id} +if (( ${?} )); then + echo 'error: wpctl failed with exit code ${?}' >&2 + exit 1 +fi + +notify-send -t 5000 'Default audio sink changed' \ + "${descs[${fuzzel_index} + 1]}"