config

betoissues' dotfiles
git clone git://git.betoissues.com/config.git
Historial | Archivos | Refs | Submódulos | README

pulseaudio-control.bash (10648B)


      1 #!/usr/bin/env bash
      2 
      3 ##################################################################
      4 # Polybar Pulseaudio Control                                     #
      5 # https://github.com/marioortizmanero/polybar-pulseaudio-control #
      6 ##################################################################
      7 
      8 # Script configuration (more info in the README)
      9 OSD="yes"  # On Screen Display message for KDE if enabled
     10 INC=5  # Increment when lowering/rising the volume
     11 MAX_VOL=130  # Maximum volume
     12 AUTOSYNC="no"  # All programs have the same volume if enabled
     13 VOLUME_ICONS=( " " " " " " )  # Volume icons array, from lower volume to higher
     14 MUTED_ICON=" "  # Muted volume icon
     15 MUTED_COLOR="%{F#666666}"  # Color when the audio is muted
     16 NOTIFICATIONS="no"  # Notifications when switching sinks if enabled
     17 SINK_ICON=" "  # Icon always shown to the left of the default sink names
     18 
     19 # Blacklist of PulseAudio sink names when switching between them. To obtain
     20 # the names of your active sinks, use `pactl list sinks short`.
     21 SINK_BLACKLIST=(
     22     "alsa_output.pci-0000_2f_00.4.iec958-stereo"
     23 )
     24 
     25 # Maps PulseAudio sink names to human-readable names
     26 declare -A SINK_NICKNAMES
     27 SINK_NICKNAMES["alsa_output.usb-Yamaha_Corporation_Steinberg_UR22mkII-00.analog-stereo"]="UR22mkII"
     28 SINK_NICKNAMES["alsa_output.pci-0000_2d_00.1.hdmi-stereo-extra1"]="HDMI"
     29 
     30 
     31 # Environment & global constants for the script
     32 LANGUAGE=en_US  # Some calls depend on English outputs of pactl
     33 END_COLOR="%{F-}"
     34 
     35 
     36 # Saves the currently default sink into a variable named `curSink`. It will
     37 # return an error code when pulseaudio isn't running.
     38 function getCurSink() {
     39     if ! pulseaudio --check; then return 1; fi
     40     curSink=$(pacmd list-sinks | awk '/\* index:/{print $3}')
     41 }
     42 
     43 
     44 # Saves the sink passed by parameter's volume into a variable named `curVol`.
     45 function getCurVol() {
     46     curVol=$(pacmd list-sinks | grep -A 15 'index: '"$1"'' | grep 'volume:' | grep -E -v 'base volume:' | awk -F : '{print $3; exit}' | grep -o -P '.{0,3}%' | sed 's/.$//' | tr -d ' ')
     47 }
     48 
     49 
     50 # Saves the name of the sink passed by parameter into a variable named
     51 # `sinkName`.
     52 function getSinkName() {
     53     sinkName=$(pactl list sinks short | awk -v sink="$1" '{ if ($1 == sink) {print $2} }')
     54 }
     55 
     56 
     57 # Saves the name to be displayed for the sink passed by parameter into a
     58 # variable called `nickname`.
     59 # If a mapping for the sink name exists, that is used. Otherwise, the string
     60 # "Sink #<index>" is used.
     61 function getNickname() {
     62     getSinkName "$1"
     63     if [ -n "${SINK_NICKNAMES[$sinkName]}" ]; then
     64         nickname="${SINK_NICKNAMES[$sinkName]}"
     65     else
     66         nickname="Sink #$1"
     67     fi
     68 }
     69 
     70 
     71 # Saves the status of the sink passed by parameter into a variable named
     72 # `isMuted`.
     73 function getIsMuted() {
     74     isMuted=$(pacmd list-sinks | grep -A 15 "index: $1" | awk '/muted/ {print $2; exit}')
     75 }
     76 
     77 
     78 # Saves all the sink inputs of the sink passed by parameter into a string
     79 # named `sinkInputs`.
     80 function getSinkInputs() {
     81     sinkInputs=$(pacmd list-sink-inputs | grep -B 4 "sink: $1 " | awk '/index:/{print $2}')
     82 }
     83 
     84 
     85 function volUp() {
     86     # Obtaining the current volume from pacmd into $curVol.
     87     if ! getCurSink; then
     88         echo "PulseAudio not running"
     89         return 1
     90     fi
     91     getCurVol "$curSink"
     92     local maxLimit=$((MAX_VOL - INC))
     93 
     94     # Checking the volume upper bounds so that if MAX_VOL was 100% and the
     95     # increase percentage was 3%, a 99% volume would top at 100% instead
     96     # of 102%. If the volume is above the maximum limit, nothing is done.
     97     if [ "$curVol" -le "$MAX_VOL" ] && [ "$curVol" -ge "$maxLimit" ]; then
     98         pactl set-sink-volume "$curSink" "$MAX_VOL%"
     99     elif [ "$curVol" -lt "$maxLimit" ]; then
    100         pactl set-sink-volume "$curSink" "+$INC%"
    101     fi
    102 
    103     if [ $OSD = "yes" ]; then showOSD "$curSink"; fi
    104     if [ $AUTOSYNC = "yes" ]; then volSync; fi
    105 }
    106 
    107 
    108 function volDown() {
    109     # Pactl already handles the volume lower bounds so that negative values
    110     # are ignored.
    111     if ! getCurSink; then
    112         echo "PulseAudio not running"
    113         return 1
    114     fi
    115     pactl set-sink-volume "$curSink" "-$INC%"
    116 
    117     if [ $OSD = "yes" ]; then showOSD "$curSink"; fi
    118     if [ $AUTOSYNC = "yes" ]; then volSync; fi
    119 }
    120 
    121 
    122 function volSync() {
    123     if ! getCurSink; then
    124         echo "PulseAudio not running"
    125         return 1
    126     fi
    127     getSinkInputs "$curSink"
    128     getCurVol "$curSink"
    129 
    130     # Every output found in the active sink has their volume set to the
    131     # current one. This will only be called if $AUTOSYNC is `yes`.
    132     for each in $sinkInputs; do
    133         pactl set-sink-input-volume "$each" "$curVol%"
    134     done
    135 }
    136 
    137 
    138 function volMute() {
    139     # Switch to mute/unmute the volume with pactl.
    140     if ! getCurSink; then
    141         echo "PulseAudio not running"
    142         return 1
    143     fi
    144     if [ "$1" = "toggle" ]; then
    145         getIsMuted "$curSink"
    146         if [ "$isMuted" = "yes" ]; then
    147             pactl set-sink-mute "$curSink" "no"
    148         else
    149             pactl set-sink-mute "$curSink" "yes"
    150         fi
    151     elif [ "$1" = "mute" ]; then
    152         pactl set-sink-mute "$curSink" "yes"
    153     elif [ "$1" = "unmute" ]; then
    154         pactl set-sink-mute "$curSink" "no"
    155     fi
    156 
    157     if [ $OSD = "yes" ]; then showOSD "$curSink"; fi
    158 }
    159 
    160 
    161 function nextSink() {
    162     # The final sinks list, removing the blacklisted ones from the list of
    163     # currently available sinks.
    164     if ! getCurSink; then
    165         echo "PulseAudio not running"
    166         return 1
    167     fi
    168 
    169     # Obtaining a tuple of sink indexes after removing the blacklisted devices
    170     # with their name.
    171     sinks=()
    172     local i=0
    173     while read -r line; do
    174         index=$(echo "$line" | cut -f1)
    175         name=$(echo "$line" | cut -f2)
    176 
    177         # If it's in the blacklist, continue the main loop. Otherwise, add
    178         # it to the list.
    179         for sink in "${SINK_BLACKLIST[@]}"; do
    180             if [ "$sink" = "$name" ]; then
    181                 continue 2
    182             fi
    183         done
    184 
    185         sinks[$i]="$index"
    186         i=$((i + 1))
    187     done < <(pactl list short sinks)
    188 
    189     # If the resulting list is empty, nothing is done
    190     if [ ${#sinks[@]} -eq 0 ]; then return; fi
    191 
    192     # If the current sink is greater or equal than last one, pick the first
    193     # sink in the list. Otherwise just pick the next sink avaliable.
    194     local newSink
    195     if [ "$curSink" -ge "${sinks[-1]}" ]; then
    196         newSink=${sinks[0]}
    197     else
    198         for sink in "${sinks[@]}"; do
    199             if [ "$curSink" -lt "$sink" ]; then
    200                 newSink=$sink
    201                 break
    202             fi
    203         done
    204     fi
    205 
    206     # The new sink is set
    207     pacmd set-default-sink "$newSink"
    208 
    209     # Move all audio threads to new sink
    210     local inputs=$(pactl list short sink-inputs | cut -f 1)
    211     for i in $inputs; do
    212         pacmd move-sink-input "$i" "$newSink"
    213     done
    214 
    215     if [ $NOTIFICATIONS = "yes" ]; then
    216         getNickname "$newSink"
    217         
    218         if command -v dunstify &>/dev/null; then
    219             notify="dunstify --replace 201839192"
    220         else
    221             notify="notify-send"
    222         fi
    223         $notify "PulseAudio" "Changed output to $nickname" --icon=audio-headphones-symbolic &
    224     fi
    225 }
    226 
    227 
    228 # This function assumes that PulseAudio is already running. It only supports
    229 # KDE OSDs for now. It will show a system message with the status of the
    230 # sink passed by parameter, or the currently active one by default.
    231 function showOSD() {
    232     if [ -z "$1" ]; then
    233         curSink="$1"
    234     else
    235         getCurSink
    236     fi
    237     getCurVol "$curSink"
    238     getIsMuted "$curSink"
    239     qdbus org.kde.kded /modules/kosd showVolume "$curVol" "$isMuted"
    240 }
    241 
    242 
    243 function listen() {
    244     local firstRun=0
    245 
    246     # Listen for changes and immediately create new output for the bar.
    247     # This is faster than having the script on an interval.
    248     LANG=$LANGUAGE pactl subscribe 2>/dev/null | {
    249         while true; do
    250             {
    251                 # If this is the first time just continue and print the current
    252                 # state. Otherwise wait for events. This is to prevent the
    253                 # module being empty until an event occurs.
    254                 if [ $firstRun -eq 0 ]; then
    255                     firstRun=1
    256                 else
    257                     read -r event || break
    258                     # Avoid double events
    259                     if ! echo "$event" | grep -e "on card" -e "on sink" -e "on server"; then
    260                         continue
    261                     fi
    262                 fi
    263             } &>/dev/null
    264             output
    265         done
    266     }
    267 }
    268 
    269 
    270 function output() {
    271     if ! getCurSink; then
    272         echo "PulseAudio not running"
    273         return 1
    274     fi
    275     getCurVol "$curSink"
    276     getIsMuted "$curSink"
    277 
    278     # Fixed volume icons over max volume
    279     local iconsLen=${#VOLUME_ICONS[@]}
    280     if [ "$iconsLen" -ne 0 ]; then
    281         local volSplit=$((MAX_VOL / iconsLen))
    282         for i in $(seq 1 "$iconsLen"); do
    283             if [ $((i * volSplit)) -ge "$curVol" ]; then
    284                 volIcon="${VOLUME_ICONS[$((i-1))]}"
    285                 break
    286             fi
    287         done
    288     else
    289         volIcon=""
    290     fi
    291 
    292     getNickname "$curSink"
    293 
    294     # Showing the formatted message
    295     if [ "$isMuted" = "yes" ]; then
    296         echo "${MUTED_COLOR}${MUTED_ICON}${curVol}%   ${SINK_ICON}${nickname}${END_COLOR}"
    297     else
    298         echo "${volIcon}${curVol}%   ${SINK_ICON}${nickname}"
    299     fi
    300 }
    301 
    302 
    303 function usage() {
    304     echo "Usage: $0 ACTION"
    305     echo ""
    306     echo "Actions:"
    307     echo "    help              display this help and exit"
    308     echo "    output            print the PulseAudio status once"
    309     echo "    listen            listen for changes in PulseAudio to automatically"
    310     echo "                      update this script's output"
    311     echo "    up, down          increase or decrease the default sink's volume"
    312     echo "    mute, unmute      mute or unmute the default sink's audio"
    313     echo "    togmute           switch between muted and unmuted"
    314     echo "    next-sink         switch to the next available sink"
    315     echo "    sync              synchronize all the output streams volume to"
    316     echo "                      the be the same as the current sink's volume"
    317     echo ""
    318     echo "Author:"
    319     echo "    Mario O. M."
    320     echo "More info on GitHub:"
    321     echo "    https://github.com/marioortizmanero/polybar-pulseaudio-control"
    322 }
    323 
    324 
    325 case "$1" in
    326     up)
    327         volUp
    328         ;;
    329     down)
    330         volDown
    331         ;;
    332     togmute)
    333         volMute toggle
    334         ;;
    335     mute)
    336         volMute mute
    337         ;;
    338     unmute)
    339         volMute unmute
    340         ;;
    341     sync)
    342         volSync
    343         ;;
    344     listen)
    345         listen
    346         ;;
    347     next-sink)
    348         nextSink
    349         ;;
    350     output)
    351         output
    352         ;;
    353     *)
    354         usage
    355         ;;
    356 esac