summaryrefslogtreecommitdiffstats
path: root/.config/polybar/pulseaudio-control.sh
blob: 7ba2c3f0eee8999b3e6dbb90b2a75c8a924f10d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#!/usr/bin/env bash

##################################################################
# Polybar Pulseaudio Control                                     #
# https://github.com/marioortizmanero/polybar-pulseaudio-control #
##################################################################

# Script configuration (more info in the README)
OSD="no"  # On Screen Display message for KDE if enabled
INC=2  # Increment when lowering/rising the volume
MAX_VOL=100  # Maximum volume
AUTOSYNC="no"  # All programs have the same volume if enabled
VOLUME_ICONS=( " " " " " " )  # Volume icons array, from lower volume to higher
MUTED_ICON=" "  # Muted volume icon
MUTED_COLOR="%{F#A9AA83}"  # Color when the audio is muted
DEFAULT_SINK_ICON=""  # The default sink icon if a custom one isn't found
CUSTOM_SINK_ICONS=("" "")  # Custom sink icons in index of sink order
NOTIFICATIONS="no"  # Notifications when switching sinks if enabled
SINK_BLACKLIST=(  )  # Index blacklist for sinks when switching between them

# Environment constants
LANGUAGE=en_US  # Functions of this scripts depends on English outputs of pactl

# Global script variables
isMuted="no"
activeSink=""
endColor="%{F-}"

function getCurVol {
    curVol=$(pacmd list-sinks | grep -A 15 'index: '"$activeSink"'' | grep 'volume:' | grep -E -v 'base volume:' | awk -F : '{print $3}' | grep -o -P '.{0,3}%'| sed s/.$// | tr -d ' ')
}

function getCurSink {
    activeSink=$(pacmd list-sinks | awk '/* index:/{print $3}')
}

function volMuteStatus {
    isMuted=$(pacmd list-sinks | grep -A 15 "index: $activeSink" | awk '/muted/{ print $2}')
}

function getSinkInputs {
    inputArray=$(pacmd list-sink-inputs | grep -B 4 "sink: $1 " | awk '/index:/{print $2}')
}

function volUp {
    getCurVol
    maxLimit=$((MAX_VOL - INC))

    if [ "$curVol" -le "$MAX_VOL" ] && [ "$curVol" -ge "$maxLimit" ]; then
        pactl set-sink-volume "$activeSink" "$MAX_VOL%"
    elif [ "$curVol" -lt "$maxLimit" ]; then
        pactl set-sink-volume "$activeSink" "+$INC%"
    fi

    getCurVol

    if [ ${OSD} = "yes" ]; then
        qdbus org.kde.kded /modules/kosd showVolume "$curVol" 0
    fi

    if [ ${AUTOSYNC} = "yes" ]; then
        volSync
    fi
}

function volDown {
    pactl set-sink-volume "$activeSink" "-$INC%"
    getCurVol

    if [ ${OSD} = "yes" ]; then
        qdbus org.kde.kded /modules/kosd showVolume "$curVol" 0
    fi

    if [ ${AUTOSYNC} = "yes" ]; then
        volSync
    fi
}

function volSync {
    getSinkInputs "$activeSink"
    getCurVol

    for each in $inputArray; do
        pactl set-sink-input-volume "$each" "$curVol%"
    done
}

function volMute {
    case "$1" in
        mute)
            pactl set-sink-mute "$activeSink" 1
            curVol=0
            status=1
            ;;
        unmute)
            pactl set-sink-mute "$activeSink" 0
            getCurVol
            status=0
            ;;
    esac

    if [ ${OSD} = "yes" ]; then
        qdbus org.kde.kded /modules/kosd showVolume ${curVol} ${status}
    fi

}

function changeDevice {
    # Treat pulseaudio sink list to avoid calling pacmd list-sinks twice
    o_pulseaudio=$(pacmd list-sinks| grep -e 'index' -e 'device.description')

    # Get all sink indices in a list
    sinks=($(echo "$o_pulseaudio" | grep index | awk -F': ' '{print $2}'))

    # Get present default sink index
    activeSink=$(echo "$o_pulseaudio" | grep "\* index" | awk -F': ' '{print $2}')

    # Set new sink index, checks that it's not in the blacklist
    newSink=$activeSink

    # Remove blacklist devices from the sink list
    sinks=($(comm -23 <(echo "${sinks[@]}" | tr ' ' '\n' | sort) <(echo "${SINK_BLACKLIST[@]}" | tr ' ' '\n' | sort) | tr '\n' ' '))

    # If the resulting list is empty, do nothing
    if [ -z "${sinks}" ]; then exit; fi

    # If the current sink is greater or equal than last one, pick the first
    # sink in the list. Otherwise just pick the next sink avaliable.
    if [ "${activeSink}" -ge "${sinks[-1]}" ]; then
        newSink=${sinks[0]}
    else
        for sink in "${sinks[@]}"; do
            if [ "${activeSink}" -lt "${sink}" ]; then
                newSink=$sink
                break
            fi
        done
    fi

    # The new sink is set
    pacmd set-default-sink "${newSink}"

    # Move all audio threads to new sink
    inputs=$(pactl list sink-inputs short | cut -f 1)
    for i in $inputs; do
        pacmd move-sink-input "$i" "$newSink"
    done

    if [ $NOTIFICATIONS = "yes" ]; then
        sendNotification
    fi
}

function sendNotification {
    o_pulseaudio=$(pacmd list-sinks| grep -e 'index' -e 'device.description')
    deviceName=$(echo "$o_pulseaudio" | sed -n '/* index/{n;p;}' | grep -o '".*"' | sed 's/"//g')
    notify-send "Output cycle" "Changed output to ${deviceName}" --icon=audio-headphones-symbolic
}

function listen {
    firstrun=0

    pactl subscribe 2>/dev/null | {
        while true; do
            {
                # If this is the first time just continue
                # and print the current state
                # Otherwise wait for events
                # This is to prevent the module being empty until
                # an event occurs
                if [ $firstrun -eq 0 ]
                then
                    firstrun=1
                else
                    read -r event || break
                    if ! echo "$event" | grep -e "on card" -e "on sink" -e "on server"
                    then
                        # Avoid double events
                        continue
                    fi
                fi
            } &>/dev/null
            output
        done
    }
}

function output() {
    if [ -z "$(pgrep pulseaudio)" ]; then echo "Pulseaudio not running"; return 1; fi

    getCurSink
    getCurVol
    volMuteStatus

    # Fixed volume icons over max volume
    iconsLen="${#VOLUME_ICONS[@]}"
    if [ "$iconsLen" -ne 0 ]; then
        volSplit=$(( MAX_VOL / iconsLen ))
        for (( i=1; i<=iconsLen; i++ )); do
            if (( i*volSplit >= curVol )); then
                volIcon="${VOLUME_ICONS[$((i-1))]}"
                break
            fi
        done
    else
        volIcon=""
    fi

    # Uses custom sink icon if the array contains one
    sinksLen=${#CUSTOM_SINK_ICONS[@]}
    if [ "$activeSink" -le "$((sinksLen - 1))" ]; then
        sinkIcon=${CUSTOM_SINK_ICONS[$activeSink]}
    else
        sinkIcon=$DEFAULT_SINK_ICON
    fi

    # Showing the formatted message
    if [ "${isMuted}" = "yes" ]; then
        echo "${MUTED_COLOR}${MUTED_ICON}${curVol}% ${sinkIcon} ${activeSink}${endColor}"
    else
        echo "${volIcon}${curVol}% ${sinkIcon} ${activeSink}"
    fi
}


getCurSink
case "$1" in
    --up)
        volUp
        ;;
    --down)
        volDown
        ;;
    --togmute)
        volMuteStatus
        if [ "$isMuted" = "yes" ]
        then
            volMute unmute
        else
            volMute mute
        fi
        ;;
    --mute)
        volMute mute
        ;;
    --unmute)
        volMute unmute
        ;;
    --sync)
        volSync
        ;;
    --listen)
        # Listen for changes and immediately create new output for the bar
        # This is faster than having the script on an interval
        listen
        ;;
    --change)
        # Changes the audio device
        changeDevice
        ;;
    *)
        # By default print output for bar
        output
        ;;
esac