1 /* $OpenBSD: mixerctl.c,v 1.34 2021/07/12 15:09:20 beck Exp $ */
2 /* $NetBSD: mixerctl.c,v 1.11 1998/04/27 16:55:23 augustss Exp $ */
3
4 /*
5 * Copyright (c) 1997 The NetBSD Foundation, Inc.
6 * All rights reserved.
7 *
8 * Author: Lennart Augustsson, with some code and ideas from Chuck Cranor.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 /*
33 * mixerctl(1) - a program to control audio mixing.
34 */
35
36 #include <sys/types.h>
37 #include <sys/ioctl.h>
38 #include <sys/audioio.h>
39
40 #include <err.h>
41 #include <errno.h>
42 #include <fcntl.h>
43 #include <limits.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48
49 struct field *findfield(char *);
50 void adjlevel(char **, u_char *, int);
51 void catstr(char *, char *, char *);
52 void prfield(struct field *, char *, int, mixer_ctrl_t *);
53 void rdfield(int, struct field *, char *, int, char *);
54 __dead void usage(void);
55
56 #define FIELD_NAME_MAX 64
57
58 struct field {
59 char name[FIELD_NAME_MAX];
60 mixer_ctrl_t *valp;
61 mixer_devinfo_t *infp;
62 } *fields, *rfields;
63
64 mixer_ctrl_t *values;
65 mixer_devinfo_t *infos;
66
67 void
catstr(char * p,char * q,char * out)68 catstr(char *p, char *q, char *out)
69 {
70 char tmp[FIELD_NAME_MAX];
71
72 snprintf(tmp, FIELD_NAME_MAX, "%s.%s", p, q);
73 strlcpy(out, tmp, FIELD_NAME_MAX);
74 }
75
76 struct field *
findfield(char * name)77 findfield(char *name)
78 {
79 int i;
80 for (i = 0; fields[i].name[0] != '\0'; i++)
81 if (strcmp(fields[i].name, name) == 0)
82 return &fields[i];
83 return (0);
84 }
85
86 #define e_member_name un.e.member[i].label.name
87 #define s_member_name un.s.member[i].label.name
88
89 void
prfield(struct field * p,char * sep,int prvalset,mixer_ctrl_t * m)90 prfield(struct field *p, char *sep, int prvalset, mixer_ctrl_t *m)
91 {
92 int i, n;
93
94 if (sep)
95 printf("%s%s", p->name, sep);
96 switch (m->type) {
97 case AUDIO_MIXER_ENUM:
98 for (i = 0; i < p->infp->un.e.num_mem; i++)
99 if (p->infp->un.e.member[i].ord == m->un.ord)
100 printf("%s",
101 p->infp->e_member_name);
102 if (prvalset) {
103 printf(" [ ");
104 for (i = 0; i < p->infp->un.e.num_mem; i++)
105 printf("%s ", p->infp->e_member_name);
106 printf("]");
107 }
108 break;
109 case AUDIO_MIXER_SET:
110 for (n = i = 0; i < p->infp->un.s.num_mem; i++)
111 if (m->un.mask & p->infp->un.s.member[i].mask)
112 printf("%s%s", n++ ? "," : "",
113 p->infp->s_member_name);
114 if (prvalset) {
115 printf(" { ");
116 for (i = 0; i < p->infp->un.s.num_mem; i++)
117 printf("%s ", p->infp->s_member_name);
118 printf("}");
119 }
120 break;
121 case AUDIO_MIXER_VALUE:
122 if (m->un.value.num_channels == 1)
123 printf("%d", m->un.value.level[0]);
124 else
125 printf("%d,%d", m->un.value.level[0],
126 m->un.value.level[1]);
127 if (prvalset)
128 printf(" %s", p->infp->un.v.units.name);
129 break;
130 default:
131 errx(1, "Invalid format.");
132 }
133 }
134
135 void
adjlevel(char ** p,u_char * olevel,int more)136 adjlevel(char **p, u_char *olevel, int more)
137 {
138 char *ep, *cp = *p;
139 long inc;
140 u_char level;
141
142 if (*cp != '+' && *cp != '-')
143 *olevel = 0; /* absolute setting */
144
145 errno = 0;
146 inc = strtol(cp, &ep, 10);
147 if (*cp == '\0' || (*ep != '\0' && *ep != ',') ||
148 (errno == ERANGE && (inc == LONG_MAX || inc == LONG_MIN)))
149 errx(1, "Bad number %s", cp);
150 if (*ep == ',' && !more)
151 errx(1, "Too many values");
152 *p = ep;
153
154 if (inc < AUDIO_MIN_GAIN - *olevel)
155 level = AUDIO_MIN_GAIN;
156 else if (inc > AUDIO_MAX_GAIN - *olevel)
157 level = AUDIO_MAX_GAIN;
158 else
159 level = *olevel + inc;
160 *olevel = level;
161 }
162
163 void
rdfield(int fd,struct field * p,char * q,int quiet,char * sep)164 rdfield(int fd, struct field *p, char *q, int quiet, char *sep)
165 {
166 mixer_ctrl_t *m, oldval;
167 int i, mask;
168 char *s;
169
170 oldval = *p->valp;
171 m = p->valp;
172
173 switch (m->type) {
174 case AUDIO_MIXER_ENUM:
175 if (strcmp(q, "toggle") == 0) {
176 for (i = 0; i < p->infp->un.e.num_mem; i++) {
177 if (m->un.ord == p->infp->un.e.member[i].ord)
178 break;
179 }
180 if (i < p->infp->un.e.num_mem)
181 i++;
182 else
183 i = 0;
184 m->un.ord = p->infp->un.e.member[i].ord;
185 break;
186 }
187 for (i = 0; i < p->infp->un.e.num_mem; i++)
188 if (strcmp(p->infp->e_member_name, q) == 0)
189 break;
190 if (i < p->infp->un.e.num_mem)
191 m->un.ord = p->infp->un.e.member[i].ord;
192 else
193 errx(1, "Bad enum value %s", q);
194 break;
195 case AUDIO_MIXER_SET:
196 mask = 0;
197 for (; q && *q; q = s) {
198 if ((s = strchr(q, ',')) != NULL)
199 *s++ = 0;
200 for (i = 0; i < p->infp->un.s.num_mem; i++)
201 if (strcmp(p->infp->s_member_name, q) == 0)
202 break;
203 if (i < p->infp->un.s.num_mem)
204 mask |= p->infp->un.s.member[i].mask;
205 else
206 errx(1, "Bad set value %s", q);
207 }
208 m->un.mask = mask;
209 break;
210 case AUDIO_MIXER_VALUE:
211 if (m->un.value.num_channels == 1) {
212 adjlevel(&q, &m->un.value.level[0], 0);
213 } else {
214 adjlevel(&q, &m->un.value.level[0], 1);
215 if (*q++ == ',')
216 adjlevel(&q, &m->un.value.level[1], 0);
217 else
218 m->un.value.level[1] = m->un.value.level[0];
219 }
220 break;
221 default:
222 errx(1, "Invalid format.");
223 }
224
225 if (ioctl(fd, AUDIO_MIXER_WRITE, p->valp) == -1) {
226 warn("AUDIO_MIXER_WRITE");
227 } else if (!quiet) {
228 if (ioctl(fd, AUDIO_MIXER_READ, p->valp) == -1) {
229 warn("AUDIO_MIXER_READ");
230 } else {
231 if (sep) {
232 prfield(p, ": ", 0, &oldval);
233 printf(" -> ");
234 }
235 prfield(p, NULL, 0, p->valp);
236 printf("\n");
237 }
238 }
239 }
240
241 int
main(int argc,char ** argv)242 main(int argc, char **argv)
243 {
244 int fd, i, j, ch, pos;
245 int aflag = 0, qflag = 0, vflag = 0, tflag = 0;
246 char *file;
247 char *sep = "=";
248 mixer_devinfo_t dinfo;
249 int ndev;
250
251 if ((file = getenv("MIXERDEVICE")) == 0 || *file == '\0')
252 file = "/dev/audioctl0";
253
254 while ((ch = getopt(argc, argv, "af:nqtvw")) != -1) {
255 switch (ch) {
256 case 'a':
257 aflag = 1;
258 break;
259 case 'w':
260 /* compat */
261 break;
262 case 'v':
263 vflag = 1;
264 break;
265 case 'n':
266 sep = 0;
267 break;
268 case 'f':
269 file = optarg;
270 break;
271 case 'q':
272 qflag = 1;
273 break;
274 case 't':
275 tflag = 1;
276 break;
277 default:
278 usage();
279 }
280 }
281 argc -= optind;
282 argv += optind;
283
284 if (argc == 0 && tflag == 0)
285 aflag = 1;
286
287 if (unveil(file, "w") == -1)
288 err(1, "unveil %s", file);
289
290 if (unveil(NULL, NULL) == -1)
291 err(1, "unveil");
292
293 if ((fd = open(file, O_WRONLY)) == -1)
294 err(1, "%s", file);
295
296 for (ndev = 0; ; ndev++) {
297 dinfo.index = ndev;
298 if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) == -1)
299 break;
300 }
301
302 if (!ndev)
303 errx(1, "no mixer devices configured");
304
305 if ((rfields = calloc(ndev, sizeof *rfields)) == NULL ||
306 (fields = calloc(ndev, sizeof *fields)) == NULL ||
307 (infos = calloc(ndev, sizeof *infos)) == NULL ||
308 (values = calloc(ndev, sizeof *values)) == NULL)
309 err(1, "calloc()");
310
311 for (i = 0; i < ndev; i++) {
312 infos[i].index = i;
313 if (ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]) == -1) {
314 ndev--;
315 i--;
316 continue;
317 }
318 }
319
320 for (i = 0; i < ndev; i++) {
321 strlcpy(rfields[i].name, infos[i].label.name, FIELD_NAME_MAX);
322 rfields[i].valp = &values[i];
323 rfields[i].infp = &infos[i];
324 }
325
326 for (i = 0; i < ndev; i++) {
327 values[i].dev = i;
328 values[i].type = infos[i].type;
329 if (infos[i].type != AUDIO_MIXER_CLASS) {
330 values[i].un.value.num_channels = 2;
331 if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) == -1) {
332 values[i].un.value.num_channels = 1;
333 if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) == -1)
334 err(1, "AUDIO_MIXER_READ");
335 }
336 }
337 }
338
339 for (j = i = 0; i < ndev; i++) {
340 if (infos[i].type != AUDIO_MIXER_CLASS &&
341 infos[i].prev == AUDIO_MIXER_LAST) {
342 fields[j++] = rfields[i];
343 for (pos = infos[i].next; pos != AUDIO_MIXER_LAST;
344 pos = infos[pos].next) {
345 fields[j] = rfields[pos];
346 catstr(rfields[i].name, infos[pos].label.name,
347 fields[j].name);
348 j++;
349 }
350 }
351 }
352
353 for (i = 0; i < j; i++) {
354 int cls = fields[i].infp->mixer_class;
355 if (cls >= 0 && cls < ndev)
356 catstr(infos[cls].label.name, fields[i].name,
357 fields[i].name);
358 }
359
360 if (!argc && aflag) {
361 for (i = 0; fields[i].name[0] != '\0'; i++) {
362 prfield(&fields[i], sep, vflag, fields[i].valp);
363 printf("\n");
364 }
365 } else if (argc > 0 && !aflag) {
366 struct field *p;
367
368 while (argc--) {
369 char *q;
370
371 ch = 0;
372 if ((q = strchr(*argv, '=')) != NULL) {
373 *q++ = '\0';
374 ch = 1;
375 }
376
377 if ((p = findfield(*argv)) == NULL) {
378 warnx("field %s does not exist", *argv);
379 } else if (ch || tflag) {
380 if (tflag && q == NULL)
381 q = "toggle";
382 rdfield(fd, p, q, qflag, sep);
383 } else {
384 prfield(p, sep, vflag, p->valp);
385 printf("\n");
386 }
387
388 argv++;
389 }
390 } else
391 usage();
392 exit(0);
393 }
394
395 __dead void
usage(void)396 usage(void)
397 {
398 extern char *__progname; /* from crt0.o */
399
400 fprintf(stderr,
401 "usage: %s [-anv] [-f file]\n"
402 " %s [-nv] [-f file] name ...\n"
403 " %s [-qt] [-f file] name ...\n"
404 " %s [-q] [-f file] name=value ...\n",
405 __progname, __progname, __progname, __progname);
406
407 exit(1);
408 }
409