#!/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 pw-dump | jq -r --argjson def_sink "${match}" \ '.[] | select(.id == $def_sink) | .info.props | "\(if .["node.nick"] then "\(.["node.nick"]) - " else "" end)\(.["node.description"]) (\($def_sink))"' if (( ${?} )); then echo "error: pw-dump or jq failed with error code ${?}" >&2 exit 1 fi else echo "error: getting default sink with wpctl failed" >&2 exit 1 fi } local current_sink_desc="$(get_default_sink)" local JQ_SCRIPT=$(<<'EOF' . as $input | .[] | select(.type == "PipeWire:Interface:Node" and .info.props.["media.class"] == "Audio/Sink") | .id as $id | .info.props | if .["node.nick"] then "\(.["node.nick"]) - " else "" end as $nick_srt | .["node.description"] as $desc | if .["device.id"] then .["device.id"] as $did | $input.[] | select(.id == $did) | .info.props.["device.icon-name"] // "" else .["device.icon-name"] // "" end as $icon | debug($icon) | "\($id)\u0000\($nick_srt)\($desc) (\($id))\u0000\($icon)\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 # 1. First try the icon name as is # 2. Then concatenate "-symbolic" # 3. Then remove the last dash (-) character and everything after it # 4. Then again try that with "-symbolic" # 5. Repeat 3 and 4 while there is a dash (-) in the name local nonl="${1//%'\n'/}" printf '%s,%s' "${nonl}" "${nonl}-symbolic" while [[ ${nonl} = *-* ]]; do printf ',%s,%s' "${nonl%-*}" "${nonl%-*}-symbolic" nonl="${nonl%-*}" done } local -a ids descs fuzzel_ents for id desc icon in ${id_desc_pairs}; do ids+=("${id}") descs+=("${desc}") generate_icon_names "${icon}" echo fuzzel_ents+=("${desc//$'\n'/}" "$(generate_icon_names "${icon}")") done local fuzzel_index fuzzel_index="$(printf '%s\0icon\x1f%s\n' ${fuzzel_ents} \ | fuzzel --mesg "Current: ${current_sink_desc}" --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]}"