Tömő Viktor
Posted on June 27, 2024
Simple yet powerful all in one stytem volume watcher and changer script for linux. Let me show you my small script.
Introduction
I switched to the i3 tiling based window manager. Because it's a whole different environment and thinking, it was very different from what I was used to. The volume buttons were working on my keyboard, but I didn't get any visual feedback. Furthermore, the volume percentage could go down below zero and increase up to more than hundread percent. There were times when I was confused why the keys stopped working, but the actual hidden reason was that the volume's value was somehow -500 percent, so increasing it by 5 percent via my keys would have taken a little time.
To solve all this, I decided to write my own zsh script. If you are familiar with linux scripting you may ask: why didn't I use bash? It's simple, I did lots of bash scripting in school already so I decided to try out zsh (not like I discovered big differences).
The script is available at my GitHub .dotfiles repository named change-volume
. In this blog I will explain how to use it and how does the code work.
Using the script
There are two use cases for the script: watch, change the current volume.
Watch listens to volume changes and automatically shows notifications. The watcher actually watches meaning it also works if the volume isn't changed via this script.
Chaning the volume is just a wrapper that takes care of minimizing and maximizing the values so they don't go under or over a certain limit. You must decrease or increase by percentage and you also have the option to mute too. When the source is muted and a value change is requested the script first unmutes the source and the next volume change will actually do something with the source's value.
Starting a watch:
volume-changer "watch"
Increase or decrease the volume or mute it fully (which automatically toggles):
volume-changer "+5%"
volume-changer "+50%"
volume-changer "-5%"
volume-changer "-21%"
volume-changer "full"
My script also logs details via logger
so I can inspect it if it doesn't seem to work.
If you think don't want to bother with the code, you can just download it from GitHub. I made it so that in the top few lines you can easily configure few basic things. Don't forget to download the icons too if you need them.
The code
Sending notifications
The base of all of this is notifications. Because my i3 came with dunst and I liked the simple look of it I decided to use it as the notification daemon. I wanted to have 3 simple things: display current status of the volume via text, display an icon so it is somewhat prettier, display the volume level via a progress bar. Lucily all these are possible via dunst.
How do you actually send notifications? Just use dunstify. I also found a nice website that uses examples to show how it works.
Important note is that the progress bar feature is available since dunst version 1.6.0, so make sure you have a updated version. For me, the
apt install
on my Ubuntu downloaded a very outdated version of dunst which din't supported progress bars, so I decided to build it from source.
I created a perfect function for showing alerts:
lastalerttext=""
show_alert() {
if [[ $lastalerttext != $3 ]]; then
lastalerttext="$3"
dunstify --replace=1111 --timeout=1500 --icon="$1" --hints=int:value:"$2%" "change-volume" "$3"
fi
}
# usage: show_alert [iconpath] [progressbar percentage] [text]
Chaning the volume
I wanted to make it so you must provide two type of values for changing the percentage: +[NUMBER]%
or -[NUMBER]%
. For these format validations I made three functions (starts_with_pm
, ends_with_percent_and_numeric
, extract_number
) which I won't describe here, but you can view that on my GitHub (I also used them via if statements).
To actually change the values I used pactl
. It is very simple to use. You can even use @DEFAULT_SINK@
to not bother with getting the current source (sink) that you are making the changes to. Set volume with set-sink-volume
and mute with set-sink-mute
.
One last thing: just as I mentioned upper if the volume is muted then first I unmute.
I also crafted this into a function:
set_volume() {
if [[ $1 == "full" ]]; then
pactl set-sink-mute @DEFAULT_SINK@ toggle
else
muted=$(pactl get-sink-mute @DEFAULT_SINK@ | awk '{print $2}')
if [[ $muted == "yes" ]]; then
set_volume "full"
else
pactl set-sink-volume @DEFAULT_SINK@ "$1%"
fi
fi
}
# usage: set_volume ["full" or number]
To make this work via command line and to also minimize and maximize the values I used few if statements:
minvolume=0
maxvolume=150
# ...
volume="$1"
if [[ $volume == "full" ]]; then
set_volume $volume
exit
fi
# ...
changeval=$(extract_number "$volume")
currvolume=$(extract_number $(pactl get-sink-volume @DEFAULT_SINK@ | awk '{print $5}'))
finalvolume=$(( $currvolume + $changeval))
if (( $finalvolume < $minvolume )); then # if goes under min then use the min value
finalvolume=$minvolume
fi
if (( $finalvolume > $maxvolume )); then # if goes over max then use the max value
finalvolume=$maxvolume
fi
set_volume $finalvolume
Watching for change
Now comes the final part. I start by listening to events via pactl subscribe
and via a while loop I display notifications based on if it's muted or what's the current volume percentage.
Because now I will need icons I downloaded 4 types of them: low, mid, high, muted. I also added variables to my script which decides the border limits:
highafter=75
midafter=35
muteimg="$HOME/dotfile-assets/volume-mute.svg"
highimg="$HOME/dotfile-assets/volume-high.svg"
lowimg="$HOME/dotfile-assets/volume-low.svg"
midimg="$HOME/dotfile-assets/volume-mid.svg"
get_icon_from_value() {
if (( $1 < $midafter )); then
echo "$lowimg"
elif (( $1 < $highafter )); then
echo "$midimg"
else
echo "$highimg"
fi
}
Now I could easily display alerts. The while looks like this:
# ...
pactl subscribe | grep --line-buffered "sink" |
while read; do
muted=$(pactl get-sink-mute @DEFAULT_SINK@ | awk '{print $2}')
if [[ $muted == "yes" ]]; then
show_alert "$muteimg" "0" "Volume: Muted"
else
currvolume=$(get_curr_volume)
icontoshow=$(get_icon_from_value $currvolume)
show_alert "$icontoshow" "$currvolume" "Volume: $currvolume%"
fi
done
exit
# ...
Making it work
The script is ready. All that is left is making i3 start the watch on startup and adding keybinds that use the script.
I won't detail them here because i3 has great documentation about it. If you are curious you can view my i3 config to see where I created the keybindings and where I added the autostart.
If you are interested more of this content then I suggest you to read posts from my series about improving my setup and developer productivity.
Posted on June 27, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.