#!/usr/bin/dash
# # #### #
# # # # #
# # # # #
# # # # #
# # # # #
## #### ######
# simple way to control volume from shell or key binding
# Author: Violet
# Last Change: 08 April 2023
# Notes:
# [1] if this script is called from a noninteractive context (ie, from a
# keybinding in your window manager), it will use notify-send if an X
# session is detected). Please run dunst or some other notification manager.
# Otherwise, this script will hang on notify-send. This script will always
# use notify-send from a noninteractive session if -n is passed but not if
# an X session isn't detected. However, it will call notify-send if -nn is
# passed.
#
# [2] -q simply sets verbosity to 0; a following -v will reset it to the default
# of 1, and further -v flags will increase the verbosity again. The initial
# verbosity is also initially based on whether the script is called
# interactively or not. If the script is called from an interactive
# environment, then the verbosity is 1 initially; otherwise, it's initially
# 0. Similarly, notify is set to 1 if called noninteractively or 0
# otherwise. To force verbosity to be exactly one in both contexts, use -qv
# since -q sets verbosity=0 and -v increments verbosity by one. Similarly,
# you can do -Nn to reset notify=0 with -N and increse notify by one with
# -n.
version=1.4;
script="$(basename "$0")";
debug(){
if [ $verb -gt "$1" ];
then
>&2 printf "[debug%d] %s\n" "$1" "$2";
fi;
};
getVol(){
read vol on_off <<EOF
$(amixer get $_card "$mixer" 2>/tmp/vol.error \
| perl -n -e '/\[(\d+)%\].*\[(on|off)\]$/ && print("$1 $2\n") && exit')
EOF
if [ "$on_off" = 'off' ]
then
mutestr='(muted)';
else
mutestr='';
fi;
};
usage(){
if [ "${1:-1}" -eq 0 ];
then
figlet -k -f banner vol 2>/dev/null;
printf "Easily see and change the volume with amixer\n\n";
fi;
cat <<__EOF
Usage: $script; $script -h; $script -V;
$script [-rmutqvnN] [-x mixer] [-c card] [vol[+-]];
$script -d [-rmutqvnN] [-x mixer] [-c card] [+-];
Options:
-r => round volume to nearest multiple of 5
-d => dynamically increase/decrease volume (coming soon)
-m, -u, -t => mute, unmute, toggle mute (resp) in order of precedence
-q => quiet
-v => increase verbosity (max: -vvv)
-n => increase notifiability for notify-send (max: -nn)
note: this happens automatically when called noninteractively
-N => do not notify (even if noninteractive)
-x mixer => select mixer ('Master' by default)
-c card => select card (empty by default)
-h => display this help and exit
-V => display the version and exit
Examples:
- $script -r => round volume to multiple of 5
- $script 10+ => increase volume by 10
- $script -rn 5- => decrease volume by 5, round it, and notify-send volume
- $script -d + => dynamically increase volume (coming soon)
- $script -u 40 => unmute and set volume to 40
__EOF
if [ "${1:-1}" -eq 0 ];
then
printf "\nDepends: amixer, perl, sed, notify-send, figlet (opt)\n"
fi;
exit "${1:-1}";
}
notify(){
local vol mutestr icon_name
vol="$1"
mutestr="$2"
if [ -n "$mutestr" ] || [ "$vol" -eq 0 ];
then
icon_name="audio-volume-muted";
elif [ "$vol" -lt 33 ];
then
icon_name="audio-volume-low";
elif [ "$vol" -lt 67 ];
then
icon_name="audio-volume-medium";
else
icon_name="audio-volume-high";
fi;
notify-send "${mutestr:-vol} $vol" \
-i "$icon_name" \
-h "int:value:$vol" \
-h 'string:synchronous:volume';
}
interactive=0;
shouldnotify=0;
if tty -s;
then
interactive=1;
else
if xset q >/dev/null 2>&1;
then
shouldnotify=1
fi;
fi;
mixer='Master';
card='';
round=0;
dynamic=0;
mute=0;
unmute=0;
togglemute=0;
verb=1;
while getopts s:x:c:rmutqvnNhVd o;
do
case "$o" in
r) round=1 ;;
d) dynamic=1 ;;
m) mute=1 ;;
u) unmute=1 ;;
t) togglemute=1 ;;
q) verb=0 ;;
v) verb=$((verb+1)) ;;
n) shouldnotify=1 ;;
N) shouldnotify=0 ;;
x) mixer="$OPTARG" ;;
c) card="$OPTARG" ;;
V) echo "vol $version"
exit 0 ;;
h) usage 0 ;;
[?]) usage ;;
esac;
done;
shift $((OPTIND-1));
if [ $dynamic -eq 1 ];
then
echo "Dynamic volume control coming soon ;]";
exit 42;
fi;
getVol;
debug 2 "initial volume: $vol$mutestr";
volArg="$(echo -n "$*" | sed 's/^\([0-9]\+\)\([+-]\)\(.*\)/\1/')";
incSym="$(echo -n "$*" | sed 's/^\([0-9]\+\)\([+-]\)\(.*\)/\2/')";
restArg="$(echo -n "$*" | sed 's/^\([0-9]\+\)\([+-]\)\(.*\)/\3/')";
if [ "_$restArg" != "_" ];
then
usage;
fi;
if [ -z "$card" ]
then
_card="";
else
_card="-c $card";
fi;
adjusted=0;
# adjust volume
if [ -n "$incSym" ];
then
# increment volume
if [ $round -eq 1 ];
then
drift=$((2 - ( ( (vol + volArg) % 5 + 2) % 5) ));
volArg=$((volArg + drift));
fi;
amixerVolCmd="$volArg%$incSym"
debug 2 "incrementing volume by: $incSym$volArg";
elif [ -n "$volArg" ];
then
# set volume
if [ $round -eq 1 ];
then
drift=$((2 - ( ( (vol + volArg) % 5 + 2) % 5) ));
volArg=$((volArg + drift));
fi;
amixerVolCmd="$volArg%"
debug 2 "setting volume to: $amixerVolCmd";
elif [ $round -eq 1 ];
then
drift=$((2 - ( (vol % 5 + 2) % 5) ));
amixerVolCmd="$((vol + drift))%";
fi;
if [ -n "$amixerVolCmd" ];
then
debug 3 "\$ amixer set $_card $mixer $amixerVolCmd";
amixer set $_card "$mixer" "$amixerVolCmd" >/dev/null;
adjusted=1;
fi;
# muting
if [ $mute -eq 1 ];
then
amixerMuteCmd=mute;
debug 2 "muting";
elif [ $unmute -eq 1 ];
then
amixerMuteCmd=unmute;
debug 2 "unmuting";
elif [ $togglemute -eq 1 ];
then
amixerMuteCmd=toggle;
debug 2 "toggleing mute";
fi;
if [ -n "$amixerMuteCmd" ];
then
debug 3 "\$ amixer set $_card $mixer $amixerMuteCmd";
amixer set $_card "$mixer" "$amixerMuteCmd" >/dev/null;
adjusted=1;
fi;
if [ $adjusted -eq 1 ];
then
getVol;
fi;
if [ $verb -gt 1 ] || [ $interactive = 1 ] && [ $verb -ne 0 ];
then
echo "$vol%$mutestr";
fi
if [ $shouldnotify -eq 1 ];
then
notify "$vol" "$mutestr"
fi;
# vim: ft=sh