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