#!/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