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 shift $((OPTIND-1));
169 
170 if [ $dynamic -eq 1 ];
171 then
172   echo "Dynamic volume control coming soon ;]";
173   exit 42;
174 fi;
175 
176 getVol;
177 debug 2 "initial volume: $vol$mutestr";
178 
179 volArg="$(echo -n "$*" | sed 's/^\([0-9]\+\)\([+-]\)\(.*\)/\1/')";
180 incSym="$(echo -n "$*" | sed 's/^\([0-9]\+\)\([+-]\)\(.*\)/\2/')";
181 restArg="$(echo -n "$*" | sed 's/^\([0-9]\+\)\([+-]\)\(.*\)/\3/')";
182 
183 if [ "_$restArg" != "_" ];
184 then
185   usage;
186 fi;
187 
188 if [ -z "$card" ]
189 then
190   _card="";
191 else
192   _card="-c $card";
193 fi;
194 
195 adjusted=0;
196 
197 # adjust volume
198 if [ -n "$incSym" ];
199 then
200   # increment volume
201   if [ $round -eq 1 ];
202   then
203     drift=$((2 - ( ( (vol + volArg) % 5 + 2) % 5) ));
204     volArg=$((volArg + drift));
205   fi;
206   amixerVolCmd="$volArg%$incSym"
207   debug 2 "incrementing volume by: $incSym$volArg";
208 elif [ -n "$volArg" ];
209 then
210   # set volume
211   if [ $round -eq 1 ];
212   then
213     drift=$((2 - ( ( (vol + volArg) % 5 + 2) % 5) ));
214     volArg=$((volArg + drift));
215   fi;
216   amixerVolCmd="$volArg%"
217   debug 2 "setting volume to: $amixerVolCmd";
218 elif [ $round -eq 1 ];
219 then
220   drift=$((2 - ( (vol % 5 + 2) % 5) ));
221   amixerVolCmd="$((vol + drift))%";
222 fi;
223 if [ -n "$amixerVolCmd" ];
224 then
225   debug 3 "\$ amixer set $_card $mixer $amixerVolCmd";
226   amixer set $_card "$mixer" "$amixerVolCmd" >/dev/null;
227   adjusted=1;
228 fi;
229 
230 # muting
231 if [ $mute -eq 1 ];
232 then
233   amixerMuteCmd=mute;
234   debug 2 "muting";
235 elif [ $unmute -eq 1 ];
236 then
237   amixerMuteCmd=unmute;
238   debug 2 "unmuting";
239 elif [ $togglemute -eq 1 ];
240 then
241   amixerMuteCmd=toggle;
242   debug 2 "toggleing mute";
243 fi;
244 if [ -n "$amixerMuteCmd" ];
245 then
246   debug 3 "\$ amixer set $_card $mixer $amixerMuteCmd";
247   amixer set $_card "$mixer" "$amixerMuteCmd" >/dev/null;
248   adjusted=1;
249 fi;
250 
251 if [ $adjusted -eq 1 ];
252 then
253   getVol;
254 fi;
255 
256 if [ $verb -gt 1 ] || [ $interactive = 1 ] && [ $verb -ne 0 ];
257 then
258   echo "$vol%$mutestr";
259 fi
260 
261 if [ $shouldnotify -eq 1 ];
262 then
263   notify "$vol" "$mutestr"
264 fi;
265 
266 # vim: ft=sh