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