$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

  • INeedMana@piefed.zip
    link
    fedilink
    English
    arrow-up
    9
    ·
    1 year ago

    At this point, I would make it a bash script and put it in ~/bin. If one has to get back to it 5 years later, it will be painful to debug

    • CommunistOP
      link
      fedilink
      English
      arrow-up
      4
      ·
      1 year ago

      I don’t like doing that because i’m working on nixos which already makes it complicated but i also have a script that fully sets up my entire system and that would add some complexity to that, plus i just have a strange aversion to making it a bash script purely because it would be the only common function in my setup that’s on a bash script

      and it wouldn’t be that hard to convert it to one for future debugging anyway

      • INeedMana@piefed.zip
        link
        fedilink
        English
        arrow-up
        2
        ·
        1 year ago

        I can see how that could be a hassle on nixos and if you don’t have more scripts

        FWIW, I use chezmoi for stuff like this, I guess it might be easier to make it work with nixos by setting up chezmoi in HOME, instead of defining scripts directly

  • everett@lemmy.ml
    link
    fedilink
    arrow-up
    9
    ·
    1 year ago

    Hey, that’s the combination on my luggage!

    But seriously, thanks for sharing. Looks like there’s some good stuff in there to learn from.

    • CommunistOP
      link
      fedilink
      English
      arrow-up
      6
      arrow-down
      1
      ·
      1 year ago

      I worked hard to make it this unreadable

    • CommunistOP
      link
      fedilink
      English
      arrow-up
      4
      ·
      1 year ago

      That’s tofi, the dmenu alternative I’m using, I set its height and width, the oga is a media file, that plays the same ding as a volume change so you know what sink you’re playing from now.

      feel free to replace tofi with dmenu or whatever

  • thetaT [none/use name]@hexbear.net
    link
    fedilink
    arrow-up
    1
    ·
    1 year ago

    i’d recommend rewriting this in some other shell-friendly language that can do proper text and JSON munging, because this just looks like spaghetti.

    Perl comes to mind

    • CommunistOP
      link
      fedilink
      English
      arrow-up
      1
      ·
      edit-2
      1 year ago

      Tbh all I care about is low dependencies and performance, dunno if that’d help

  • Ŝan • 𐑖ƨɤ@piefed.zip
    link
    fedilink
    English
    arrow-up
    3
    arrow-down
    5
    ·
    1 year ago

    You need 3 backticks to get a code formatting block.

    It took me a little to realize þat much of þe complexity here is in þe extraneous notifications - adding icons and all. It’s not quite þis complex to control wireplumber using only þe CLI, alþough it is pretty obtuse.

    • CommunistOP
      link
      fedilink
      English
      arrow-up
      2
      ·
      1 year ago

      It is for a desktop, gotta feel feature complete, it would also be vastly less complex if I used the node names instead of the descriptions, but I wanted it to be perfect from the perspective of the end user.