A simple yet powerful UI overlay made for Wayland WMs built with Quickshell
wayland qs linux ui ux
at main 132 lines 3.3 kB view raw
1#!/usr/bin/env bash 2set -euo pipefail 3 4wpctl_bin="${WPCTL_BIN:-wpctl}" 5 6fail() { echo "$*" >&2; exit 1; } 7 8inspect() { 9 "$wpctl_bin" inspect -a "$1" 2>/dev/null || true 10} 11 12field_quoted() { 13 local insp="$1" key="$2" 14 printf '%s\n' "$insp" | sed -n "s/.*${key}[[:space:]]*=[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" | head -n1 15} 16 17media_class() { 18 field_quoted "$1" "media\.class" 19} 20 21display_name() { 22 local insp="$1" n 23 n="$(field_quoted "$insp" "node\.description")" 24 [[ -z "$n" ]] && n="$(field_quoted "$insp" "node\.nick")" 25 [[ -z "$n" ]] && n="$(field_quoted "$insp" "node\.name")" 26 printf '%s' "${n:-}" 27} 28 29candidates_from_status() { 30 "$wpctl_bin" status --name | awk ' 31 function clean(s) { 32 gsub(/[│├└─]/, " ", s) 33 sub(/^[ \t]+/, "", s) 34 return s 35 } 36 37 BEGIN { in_audio=0; sec="" } 38 39 { line = clean($0) } 40 41 line == "Audio" { in_audio=1; next } 42 in_audio && line == "Video" { exit } 43 44 in_audio && line == "Sinks:" { sec="SINKS"; next } 45 in_audio && line == "Sources:" { sec="SOURCES"; next } 46 in_audio && line == "Filters:" { sec="FILTERS"; next } 47 48 in_audio && sec != "" && line ~ /^[A-Z][A-Za-z ]*:/ && line != "Sinks:" && line != "Sources:" && line != "Filters:" { 49 sec="" 50 next 51 } 52 53 (sec=="SINKS" || sec=="SOURCES") && match(line, /^(\*)?[[:space:]]*([0-9]+)\.[[:space:]]+(.+)$/, m) { 54 def = (m[1] == "*" ? 1 : 0) 55 id = m[2] 56 raw = m[3] 57 58 sub(/[[:space:]]+\[vol:[^]]+\][[:space:]]*$/, "", raw) 59 60 kind = (sec=="SINKS" ? "SINK" : "SOURCE") 61 printf "%s\t%d\t%s\t%s\n", kind, def, id, raw 62 next 63 } 64 65 sec=="FILTERS" && match(line, /^(\*)?[[:space:]]*([0-9]+)\.[[:space:]]+([^[]+)[[:space:]]+\[([^]]+)\][[:space:]]*$/, f) { 66 def = (f[1] == "*" ? 1 : 0) 67 id = f[2] 68 raw = f[3] 69 cls = f[4] 70 71 if (cls == "Audio/Sink") printf "SINK\t%d\t%s\t%s\n", def, id, raw 72 if (cls == "Audio/Source") printf "SOURCE\t%d\t%s\t%s\n", def, id, raw 73 next 74 } 75 ' 76} 77 78list() { 79 local line kind def id raw 80 local any=0 81 82 while IFS=$'\t' read -r kind def id raw; do 83 [[ -z "${id:-}" ]] && continue 84 85 local insp cls name 86 insp="$(inspect "$id")" 87 [[ -z "$insp" ]] && continue 88 89 cls="$(media_class "$insp")" 90 if [[ "$kind" == "SINK" ]]; then 91 [[ "$cls" == "Audio/Sink" ]] || continue 92 else 93 [[ "$cls" == "Audio/Source" ]] || continue 94 fi 95 96 name="$(display_name "$insp")" 97 [[ -z "$name" ]] && name="$raw" 98 [[ -z "$name" ]] && name="Node $id" 99 100 printf "%s\t%s\t%s\t%s\n" "$kind" "$def" "$id" "$name" 101 any=1 102 done < <(candidates_from_status) 103 104 if [[ "$any" -eq 0 ]]; then 105 echo "No audio nodes returned by audioctl" >&2 106 exit 0 107 fi 108} 109 110case "${1:-}" in 111 list) 112 list 113 ;; 114 115 set-default) 116 [[ $# -eq 3 ]] || fail "usage: $0 set-default sink|source <id>" 117 "$wpctl_bin" set-default "$3" 118 ;; 119 120 volume) 121 [[ $# -eq 2 ]] || fail "usage: $0 volume sink|source" 122 if [[ "$2" == "sink" ]]; then 123 "$wpctl_bin" get-volume @DEFAULT_AUDIO_SINK@ 2>/dev/null || true 124 else 125 "$wpctl_bin" get-volume @DEFAULT_AUDIO_SOURCE@ 2>/dev/null || true 126 fi 127 ;; 128 129 *) 130 fail "usage: $0 list | set-default sink|source <id> | volume sink|source" 131 ;; 132esac