#!/usr/bin/env zsh 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 } 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 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]}"