rofi/scripts/rofi-audio 4.0 K raw
1
#!/bin/sh
2
# Requires: rofi, wireplumber (wpctl)
3
# Audio menu: volume, mute, and which sink the sound comes out of. This is
4
# the rofi replacement for the gum-based audio-menu/audio-output pair, so it
5
# no longer needs a terminal window to run in.
6
set -u
7
8
STEP=${ROFI_AUDIO_STEP:-5}
9
NOTIFY_ID=7303
10
11
notify() {
12
    if command -v dunstify >/dev/null 2>&1; then
13
        dunstify -r "$NOTIFY_ID" "Audio" "$1"
14
    else
15
        notify-send "Audio" "$1"
16
    fi
17
}
18
19
# wpctl has no machine-readable listing, so carve the Sinks block out of
20
# `status`. Output: "<*|space>\t<id>\t<name>".
21
sinks() {
22
    wpctl status 2>/dev/null | awk '
23
        /^Audio/                                { audio = 1; next }
24
        /^[A-Z]/                                { audio = 0 }
25
        audio && /Sinks:/ && !/endpoints:/      { insec = 1; next }
26
        audio && /Devices:|Sources:|Streams:|endpoints:|Filters:/ { insec = 0; next }
27
        insec {
28
            line = $0
29
            prefix = line; sub(/[0-9].*$/, "", prefix)
30
            def = (prefix ~ /\*/) ? "*" : " "
31
            sub(/^[^0-9]*/, "", line)
32
            if (line !~ /^[0-9]+\./) next
33
            id = line;   sub(/\..*$/, "", id)
34
            name = line; sub(/^[0-9]+\.[[:space:]]*/, "", name)
35
            sub(/[[:space:]]*\[vol:.*$/, "", name)
36
            sub(/[[:space:]]+$/, "", name)
37
            print def "\t" id "\t" name
38
        }
39
    '
40
}
41
42
# @DEFAULT_AUDIO_SINK@ always resolves to whatever is current, so the volume
43
# actions never need to know which sink that is.
44
volume() { wpctl get-volume @DEFAULT_AUDIO_SINK@ 2>/dev/null; }
45
46
vol_percent() { volume | awk '{ printf "%d", $2 * 100 }'; }
47
48
# The icon tracks the level the same way the status bar blocks do, so a
49
# notification reads the same whether it came from here or from a media key.
50
vol_icon() {
51
    case "$(volume)" in
52
        *MUTED*) printf '󰝟' ;;
53
        *) v=$(vol_percent)
54
           if   [ "$v" -eq 0 ];  then printf '󰕿'
55
           elif [ "$v" -lt 34 ]; then printf '󰖀'
56
           elif [ "$v" -lt 67 ]; then printf '󰕾'
57
           else                       printf '󰕾'
58
           fi ;;
59
    esac
60
}
61
62
report() { notify "$(vol_icon)  $(vol_percent)%"; }
63
64
SINKS=$(sinks)
65
[ -n "$SINKS" ] || { notify "No sinks found — is pipewire running?"; exit 1; }
66
67
CURRENT=$(printf '%s\n' "$SINKS" | awk -F'\t' '$1 == "*" { print $3; exit }')
68
MIC=$(wpctl get-volume @DEFAULT_AUDIO_SOURCE@ 2>/dev/null | grep -q MUTED && echo "muted" || echo "live")
69
70
ACTIONS=$(printf '󰝝  volume up (+%s%%)\n󰝞  volume down (-%s%%)\n󰝟  toggle mute\n󰍬  toggle mic (currently %s)\n󰓃  automatic output\n' "$STEP" "$STEP" "$MIC")
71
72
# Sinks are listed by name with their wpctl id hidden behind two spaces, the
73
# same split the actions use, so one case statement handles both.
74
SINKLIST=$(printf '%s\n' "$SINKS" | awk -F'\t' '{
75
    icon = ($1 == "*") ? "󰄬" : "󰓃"
76
    printf "%s  %s\t%s\n", icon, $3, $2
77
}')
78
79
CHOICE=$(printf '%s\n%s\n' "$ACTIONS" "$(printf '%s\n' "$SINKLIST" | cut -f1)" |
80
    rofi -dmenu -i -no-custom -l 10 -p "audio" \
81
        -mesg "$(vol_icon)  $(vol_percent)% — ${CURRENT:-no default sink}") || exit 0
82
[ -n "$CHOICE" ] || exit 0
83
84
case "$CHOICE" in
85
    *"volume up"*)
86
        # The cap keeps a stray keystroke from pushing the sink into the
87
        # clipping range above 100%.
88
        wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ "$STEP%+"
89
        report ;;
90
    *"volume down"*)
91
        wpctl set-volume @DEFAULT_AUDIO_SINK@ "$STEP%-"
92
        report ;;
93
    *"toggle mute"*)
94
        wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
95
        report ;;
96
    *"toggle mic"*)
97
        wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
98
        wpctl get-volume @DEFAULT_AUDIO_SOURCE@ | grep -q MUTED &&
99
            notify "󰍭  microphone muted" || notify "󰍬  microphone live" ;;
100
    *"automatic output"*)
101
        wpctl clear-default
102
        notify "󰓃  output: automatic (wireplumber picks)" ;;
103
    *)
104
        ID=$(printf '%s\n' "$SINKLIST" | awk -F'\t' -v c="$CHOICE" '$1 == c { print $2; exit }')
105
        [ -n "$ID" ] || exit 0
106
        wpctl set-default "$ID"
107
        notify "󰓃  output: ${CHOICE##*  }" ;;
108
esac