xref: /openbsd/usr.bin/mixerctl/mixerctl.c (revision e5dd7070)
1 /*	$OpenBSD: mixerctl.c,v 1.33 2020/04/04 08:43:08 ratchov 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
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 *
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
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
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
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
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");
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
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