49
A pure wireplumber way to switch audio devices, no pactl
(lemmy.frozeninferno.xyz)
submitted
11 months ago* (last edited 1 day ago)
by
communist@lemmy.frozeninferno.xyz
to
c/linux@lemmy.ml
$sinkswitch = bash -c 'set -euo pipefail; declare -A m=(); declare -a n=(); ex="^(easyeffects_sink|filter-chain|loopback|null-sink)([-_.].*)?$"; exec 3< <(pw-cli ls Node 2>/dev/null); exec 4< <(pw-metadata -n default 0 default.audio.sink 2>/dev/null); cur=; while IFS= read -r l <&4; do case $l in *"default.audio.sink"*) cur=${l##*\"name\":\"}; cur=${cur%%\"*}; break;; esac; done; id= desc= name= cls=; add(){ [[ $cls == "Audio/Sink" && -n $name ]] || return 0; [[ $name =~ $ex || $name == "$cur" ]] && return 0; local l=$desc; [[ -n ${m[$l]+x} ]] && l+=" [$id]"; m[$l]=$name; n+=("$l"); }; while IFS= read -r l <&3; do case $l in *"id "[0-9]*", type PipeWire:Interface:Node"*) add; id=${l##*id }; id=${id%%,*}; desc= name= cls=;; *"node.description = "*) desc=${l##* = \"}; desc=${desc%\"};; *"node.name = "*) name=${l##* = \"}; name=${name%\"};; *"media.class = "*) cls=${l##* = \"}; cls=${cls%\"};; esac; done; add; ((${##n[@]})) || exit 0; if ((${##n[@]}==1)); then c=${n[0]}; else c=$(printf "%s\n" "${n[@]}" | walker -d --minheight 1 -p "Audio output: " | head -n1 || true); fi; [[ -n $c ]] || exit 0; name=${m[$c]:-}; [[ -n $name ]] || exit 1; pw-metadata -n default 0 default.configured.audio.sink "{\"name\":\"$name\"}" Spa:String:JSON >/dev/null & s=/run/current-system/sw/share/sounds/freedesktop/stereo/audio-volume-change.oga; [[ -r $s ]] && pw-play --volume=0.2 "$s" >/dev/null 2>&1 & notify-send -u low -i /run/current-system/sw/share/icons/Flat-Remix-Red-Dark/panel/audio-volume-high-symbolic.svg "${c% \[*\]}" -h string:x-canonical-private-synchronous:sink-state >/dev/null 2>&1 &'
or, escaped for waybar:
"on-click-middle": "bash -c 'set -euo pipefail; declare -A m=(); declare -a n=(); ex=\"^(easyeffects_sink|filter-chain|loopback|null-sink)([-_.].*)?$\"; exec 3< <(pw-cli ls Node 2>/dev/null); exec 4< <(pw-metadata -n default 0 default.audio.sink 2>/dev/null); cur=; while IFS= read -r l <&4; do case $l in *\"default.audio.sink\"*) cur=${l##*\\\"name\\\":\\\"}; cur=${cur%%\\\"*}; break;; esac; done; id= desc= name= cls=; add(){ [[ $cls == \"Audio/Sink\" && -n $name ]] || return 0; [[ $name =~ $ex || $name == \"$cur\" ]] && return 0; local l=$desc; [[ -n ${m[$l]+x} ]] && l+=\" [$id]\"; m[$l]=$name; n+=(\"$l\"); }; while IFS= read -r l <&3; do case $l in *\"id \"[0-9]*\", type PipeWire:Interface:Node\"*) add; id=${l#*id }; id=${id%%,*}; desc= name= cls=;; *\"node.description = \"*) desc=${l#* = \\\"}; desc=${desc%\\\"};; *\"node.name = \"*) name=${l#* = \\\"}; name=${name%\\\"};; *\"media.class = \"*) cls=${l#* = \\\"}; cls=${cls%\\\"};; esac; done; add; ((${#n[@]})) || exit 0; if ((${#n[@]}==1)); then c=${n[0]}; else c=$(printf \"%s\\n\" \"${n[@]}\" | walker -d --minheight 1 -p \"Audio output: \" | head -n1 || true); fi; [[ -n $c ]] || exit 0; name=${m[$c]:-}; [[ -n $name ]] || exit 1; pw-metadata -n default 0 default.configured.audio.sink \"{\\\"name\\\":\\\"$name\\\"}\" Spa:String:JSON >/dev/null & s=/run/current-system/sw/share/sounds/freedesktop/stereo/audio-volume-change.oga; [[ -r $s ]] && pw-play --volume=0.2 \"$s\" >/dev/null 2>&1 & notify-send -u low -i /run/current-system/sw/share/icons/Flat-Remix-Red-Dark/panel/audio-volume-high-symbolic.svg \"${c% \\[*\\]}\" -h string:x-canonical-private-synchronous:sink-state >/dev/null 2>&1 &'",
there's probably a lot to improve here, took forever to get this working, did not do a cleanup pass, jfc this took forever, replace walker with your menu of choice, thought people should have this.
wishlist: change the icon based on volume level
edit: updated to a script that's basically a million times faster
old version:
real 0m0.050s user 0m0.043s sys 0m0.037s
new version:
real 0m0.021s user 0m0.013s sys 0m0.012s
Could you lint that please. This is unreadable XD
I worked hard to make it this unreadable