1 #!/usr/bin/dash
  2 
  3 #    #  ####  #
  4 #    # #    # #     
  5 #    # #    # #
  6 #    # #    # #
  7  #  #  #    # #
  8   ##    ####  ######
  9 
 10 # simple way to control volume from shell or key binding
 11 
 12 
 13 # Author: Violet
 14 # Last Change: 08 April 2023
 15 
 16 # Notes:
 17 # [1] if this script is called from a noninteractive context (ie, from a
 18 #     keybinding in your window manager), it will use notify-send if an X
 19 #     session is detected). Please run dunst or some other notification manager.
 20 #     Otherwise, this script will hang on notify-send. This script will always
 21 #     use notify-send from a noninteractive session if -n is passed but not if
 22 #     an X session isn't detected. However, it will call notify-send if -nn is
 23 #     passed.
 24 #
 25 # [2] -q simply sets verbosity to 0; a following -v will reset it to the default
 26 #     of 1, and further -v flags will increase the verbosity again. The initial
 27 #     verbosity is also initially based on whether the script is called
 28 #     interactively or not. If the script is called from an interactive
 29 #     environment, then the verbosity is 1 initially; otherwise, it's initially
 30 #     0. Similarly, notify is set to 1 if called noninteractively or 0
 31 #     otherwise. To force verbosity to be exactly one in both contexts, use -qv
 32 #     since -q sets verbosity=0 and -v increments verbosity by one. Similarly,
 33 #     you can do -Nn to reset notify=0 with -N and increse notify by one with
 34 #     -n.
 35 
 36 
 37 version=1.4;
 38 script="$(basename "$0")";
 39 
 40 debug(){
 41   if [ $verb -gt "$1" ];
 42   then
 43     >&2 printf "[debug%d] %s\n" "$1" "$2";
 44   fi;
 45 };
 46 
 47 getVol(){
 48   read vol on_off <<EOF
 49   $(amixer get $_card "$mixer" 2>/tmp/vol.error \
 50     | perl -n -e '/\[(\d+)%\].*\[(on|off)\]$/ && print("$1 $2\n") && exit')
 51 EOF
 52   if [ "$on_off" = 'off' ]
 53   then
 54     mutestr='(muted)';
 55   else
 56     mutestr='';
 57   fi;
 58 };
 59 
 60 usage(){
 61   if [ "${1:-1}" -eq 0 ];
 62   then
 63     figlet -k -f banner vol 2>/dev/null;
 64     printf "Easily see and change the volume with amixer\n\n";
 65   fi;
 66 cat <<__EOF
 67 Usage: $script;  $script -h;  $script -V;
 68        $script [-rmutqvnN] [-x mixer] [-c card] [vol[+-]];
 69        $script -d [-rmutqvnN] [-x mixer] [-c card] [+-];
 70 
 71 Options:
 72   -r  =>  round volume to nearest multiple of 5
 73   -d  =>  dynamically increase/decrease volume (coming soon)
 74   
 75   -m, -u, -t  =>  mute, unmute, toggle mute (resp) in order of precedence
 76   
 77   -q    =>  quiet
 78   -v    =>  increase verbosity (max: -vvv)
 79   -n    =>  increase notifiability for notify-send (max: -nn)
 80             note: this happens automatically when called noninteractively
 81   -N    =>  do not notify (even if noninteractive)
 82   
 83   -x mixer  =>  select mixer ('Master' by default)
 84   -c card   =>  select card (empty by default)
 85   
 86   -h  => display this help and exit
 87   -V  => display the version and exit
 88 
 89 Examples:
 90   - $script -r      => round volume to multiple of 5
 91   - $script 10+     => increase volume by 10
 92   - $script -rn 5-  => decrease volume by 5, round it, and notify-send volume
 93   - $script -d +    => dynamically increase volume (coming soon)
 94   - $script -u 40   => unmute and set volume to 40
 95 __EOF
 96 
 97   if [ "${1:-1}" -eq 0 ];
 98   then
 99     printf "\nDepends: amixer, perl, sed, notify-send, figlet (opt)\n"
100   fi;
101   exit "${1:-1}";
102 }
103 
104 notify(){
105   local vol mutestr icon_name
106   vol="$1"
107   mutestr="$2"
108   if [ -n "$mutestr" ] || [ "$vol" -eq 0 ];
109   then
110     icon_name="audio-volume-muted";
111   elif [ "$vol" -lt 33 ];
112   then
113     icon_name="audio-volume-low";
114   elif [ "$vol" -lt 67 ];
115   then
116     icon_name="audio-volume-medium";
117   else
118     icon_name="audio-volume-high";
119   fi;
120   notify-send "${mutestr:-vol} $vol" \
121     -i "$icon_name" \
122     -h "int:value:$vol" \
123     -h 'string:synchronous:volume';
124 }
125 
126 interactive=0;
127 shouldnotify=0;
128 if tty -s;
129 then
130   interactive=1;
131 else
132   if xset q >/dev/null 2>&1;
133   then
134     shouldnotify=1
135   fi;
136 fi;
137 
138 mixer='Master';
139 card='';
140 
141 round=0;
142 dynamic=0;
143 mute=0;
144 unmute=0;
145 togglemute=0;
146 verb=1;
147 
148 while getopts s:x:c:rmutqvnNhVd o;
149 do
150   case "$o" in
151     r) round=1             ;;
152     d) dynamic=1           ;;
153     m) mute=1              ;;
154     u) unmute=1            ;;
155     t) togglemute=1        ;;
156     q) verb=0              ;;
157     v) verb=$((verb+1))    ;;
158     n) shouldnotify=1      ;;
159     N) shouldnotify=0      ;;
160     x) mixer="$OPTARG"     ;;
161     c) card="$OPTARG"      ;;
162     V) echo "vol $version"
163        exit 0              ;;
164     h) usage 0             ;;
165     [?]) usage             ;;
166   esac;
167 done;
168 echo $*
169 shift $((OPTIND-1));
170 echo $*
171 exit 0;
172 
173 if [ $dynamic -eq 1 ];
174 then
175   echo "Dynamic volume control coming soon ;]";
176   exit 42;
177 fi;
178 
179 getVol;
180 debug 2 "initial volume: $vol$mutestr";
181 
182 volArg="$(echo -n "$*" | sed 's/^\([0-9]\+\)\([+-]\)\(.*\)/\1/')";
183 incSym="$(echo -n "$*" | sed 's/^\([0-9]\+\)\([+-]\)\(.*\)/\2/')";
184 restArg="$(echo -n "$*" | sed 's/^\([0-9]\+\)\([+-]\)\(.*\)/\3/')";
185 
186 if [ "_$restArg" != "_" ];
187 then
188   usage;
189 fi;
190 
191 if [ -z "$card" ]
192 then
193   _card="";
194 else
195   _card="-c $card";
196 fi;
197 
198 adjusted=0;
199 
200 # adjust volume
201 if [ -n "$incSym" ];
202 then
203   # increment volume
204   if [ $round -eq 1 ];
205   then
206     drift=$((2 - ( ( (vol + volArg) % 5 + 2) % 5) ));
207     volArg=$((volArg + drift));
208   fi;
209   amixerVolCmd="$volArg%$incSym"
210   debug 2 "incrementing volume by: $incSym$volArg";
211 elif [ -n "$volArg" ];
212 then
213   # set volume
214   if [ $round -eq 1 ];
215   then
216     drift=$((2 - ( ( (vol + volArg) % 5 + 2) % 5) ));
217     volArg=$((volArg + drift));
218   fi;
219   amixerVolCmd="$volArg%"
220   debug 2 "setting volume to: $amixerVolCmd";
221 elif [ $round -eq 1 ];
222 then
223   drift=$((2 - ( (vol % 5 + 2) % 5) ));
224   amixerVolCmd="$((vol + drift))%";
225 fi;
226 if [ -n "$amixerVolCmd" ];
227 then
228   debug 3 "\$ amixer set $_card $mixer $amixerVolCmd";
229   amixer set $_card "$mixer" "$amixerVolCmd" >/dev/null;
230   adjusted=1;
231 fi;
232 
233 # muting
234 if [ $mute -eq 1 ];
235 then
236   amixerMuteCmd=mute;
237   debug 2 "muting";
238 elif [ $unmute -eq 1 ];
239 then
240   amixerMuteCmd=unmute;
241   debug 2 "unmuting";
242 elif [ $togglemute -eq 1 ];
243 then
244   amixerMuteCmd=toggle;
245   debug 2 "toggleing mute";
246 fi;
247 if [ -n "$amixerMuteCmd" ];
248 then
249   debug 3 "\$ amixer set $_card $mixer $amixerMuteCmd";
250   amixer set $_card "$mixer" "$amixerMuteCmd" >/dev/null;
251   adjusted=1;
252 fi;
253 
254 if [ $adjusted -eq 1 ];
255 then
256   getVol;
257 fi;
258 
259 if [ $verb -gt 1 ] || [ $interactive = 1 ] && [ $verb -ne 0 ];
260 then
261   echo "$vol%$mutestr";
262 fi
263 
264 if [ $shouldnotify -eq 1 ];
265 then
266   notify "$vol" "$mutestr"
267 fi;
268 
269 # vim: ft=sh