xref: /openbsd/usr.bin/mixerctl/mixerctl.c (revision 404b540a)
1 /*	$OpenBSD: mixerctl.c,v 1.28 2008/06/26 05:42:21 ray 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);
53 void rdfield(int, struct field *, char *, int);
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)
91 {
92 	mixer_ctrl_t *m;
93 	int i, n;
94 
95 	if (sep)
96 		printf("%s%s", p->name, sep);
97 	m = p->valp;
98 	switch (m->type) {
99 	case AUDIO_MIXER_ENUM:
100 		for (i = 0; i < p->infp->un.e.num_mem; i++)
101 			if (p->infp->un.e.member[i].ord == m->un.ord)
102 				printf("%s",
103 					p->infp->e_member_name);
104 		if (prvalset) {
105 			printf("  [ ");
106 			for (i = 0; i < p->infp->un.e.num_mem; i++)
107 				printf("%s ", p->infp->e_member_name);
108 			printf("]");
109 		}
110 		break;
111 	case AUDIO_MIXER_SET:
112 		for (n = i = 0; i < p->infp->un.s.num_mem; i++)
113 			if (m->un.mask & p->infp->un.s.member[i].mask)
114 				printf("%s%s", n++ ? "," : "",
115 						p->infp->s_member_name);
116 		if (prvalset) {
117 			printf("  { ");
118 			for (i = 0; i < p->infp->un.s.num_mem; i++)
119 				printf("%s ", p->infp->s_member_name);
120 			printf("}");
121 		}
122 		break;
123 	case AUDIO_MIXER_VALUE:
124 		if (m->un.value.num_channels == 1)
125 			printf("%d", m->un.value.level[0]);
126 		else
127 			printf("%d,%d", m->un.value.level[0],
128 			    m->un.value.level[1]);
129 		if (prvalset)
130 			printf(" %s", p->infp->un.v.units.name);
131 		break;
132 	default:
133 		errx(1, "Invalid format.");
134 	}
135 }
136 
137 void
138 adjlevel(char **p, u_char *olevel, int more)
139 {
140 	char *ep, *cp = *p;
141 	long inc;
142 	u_char level;
143 
144 	if (*cp != '+' && *cp != '-')
145 		*olevel = 0;		/* absolute setting */
146 
147 	errno = 0;
148 	inc = strtol(cp, &ep, 10);
149 	if (*cp == '\0' || (*ep != '\0' && *ep != ',') ||
150 	    (errno == ERANGE && (inc == LONG_MAX || inc == LONG_MIN)))
151 		errx(1, "Bad number %s", cp);
152 	if (*ep == ',' && !more)
153 		errx(1, "Too many values");
154 	*p = ep;
155 
156 	if (inc < AUDIO_MIN_GAIN - *olevel)
157 		level = AUDIO_MIN_GAIN;
158 	else if (inc > AUDIO_MAX_GAIN - *olevel)
159 		level = AUDIO_MAX_GAIN;
160 	else
161 		level = *olevel + inc;
162 	*olevel = level;
163 }
164 
165 void
166 rdfield(int fd, struct field *p, char *q, int quiet)
167 {
168 	mixer_ctrl_t *m, oldval;
169 	int i, mask;
170 	char *s;
171 
172 	oldval = *p->valp;
173 	m = p->valp;
174 
175 	switch (m->type) {
176 	case AUDIO_MIXER_ENUM:
177 		if (strcmp(q, "toggle") == 0) {
178 			for (i = 0; i < p->infp->un.e.num_mem; i++) {
179 				if (m->un.ord == p->infp->un.e.member[i].ord)
180 					break;
181 			}
182 			if (i < p->infp->un.e.num_mem)
183 				i++;
184 			else
185 				i = 0;
186 			m->un.ord = p->infp->un.e.member[i].ord;
187 			break;
188 		}
189 		for (i = 0; i < p->infp->un.e.num_mem; i++)
190 			if (strcmp(p->infp->e_member_name, q) == 0)
191 				break;
192 		if (i < p->infp->un.e.num_mem)
193 			m->un.ord = p->infp->un.e.member[i].ord;
194 		else
195 			errx(1, "Bad enum value %s", q);
196 		break;
197 	case AUDIO_MIXER_SET:
198 		mask = 0;
199 		for (; q && *q; q = s) {
200 			if ((s = strchr(q, ',')) != NULL)
201 				*s++ = 0;
202 			for (i = 0; i < p->infp->un.s.num_mem; i++)
203 				if (strcmp(p->infp->s_member_name, q) == 0)
204 					break;
205 			if (i < p->infp->un.s.num_mem)
206 				mask |= p->infp->un.s.member[i].mask;
207 			else
208 				errx(1, "Bad set value %s", q);
209 		}
210 		m->un.mask = mask;
211 		break;
212 	case AUDIO_MIXER_VALUE:
213 		if (m->un.value.num_channels == 1) {
214 			adjlevel(&q, &m->un.value.level[0], 0);
215 		} else {
216 			adjlevel(&q, &m->un.value.level[0], 1);
217 			if (*q++ == ',')
218 				adjlevel(&q, &m->un.value.level[1], 0);
219 			else
220 				m->un.value.level[1] = m->un.value.level[0];
221 		}
222 		break;
223 	default:
224 		errx(1, "Invalid format.");
225 	}
226 
227 	if (ioctl(fd, AUDIO_MIXER_WRITE, p->valp) < 0) {
228 		warn("AUDIO_MIXER_WRITE");
229 	} else if (!quiet) {
230 		*p->valp = oldval;
231 		prfield(p, ": ", 0);
232 		if (ioctl(fd, AUDIO_MIXER_READ, p->valp) < 0) {
233 			warn("AUDIO_MIXER_READ");
234 		} else {
235 			printf(" -> ");
236 			prfield(p, NULL, 0);
237 			printf("\n");
238 		}
239 	}
240 }
241 
242 int
243 main(int argc, char **argv)
244 {
245 	int fd, i, j, ch, pos;
246 	int aflag = 0, qflag = 0, vflag = 0, tflag = 0;
247 	char *file;
248 	char *sep = "=";
249 	mixer_devinfo_t dinfo;
250 	int ndev;
251 
252 	if ((file = getenv("MIXERDEVICE")) == 0 || *file == '\0')
253 		file = "/dev/mixer";
254 
255 	while ((ch = getopt(argc, argv, "af:nqtvw")) != -1) {
256 		switch (ch) {
257 		case 'a':
258 			aflag++;
259 			break;
260 		case 'w':
261 			/* compat */
262 			break;
263 		case 'v':
264 			vflag++;
265 			break;
266 		case 'n':
267 			sep = 0;
268 			break;
269 		case 'f':
270 			file = optarg;
271 			break;
272 		case 'q':
273 			qflag = 1;
274 			break;
275 		case 't':
276 			tflag = 1;
277 			break;
278 		default:
279 			usage();
280 		}
281 	}
282 	argc -= optind;
283 	argv += optind;
284 
285 	if (argc == 0 && tflag == 0)
286 		aflag++;
287 
288 	if ((fd = open(file, O_RDWR)) == -1)
289 		if ((fd = open(file, O_RDONLY)) == -1)
290 			err(1, "%s", file);
291 
292 	for (ndev = 0; ; ndev++) {
293 		dinfo.index = ndev;
294 		if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) < 0)
295 			break;
296 	}
297 
298 	if (!ndev)
299 		errx(1, "no mixer devices configured");
300 
301 	if ((rfields = calloc(ndev, sizeof *rfields)) == NULL ||
302 	    (fields = calloc(ndev, sizeof *fields)) == NULL ||
303 	    (infos = calloc(ndev, sizeof *infos)) == NULL ||
304 	    (values = calloc(ndev, sizeof *values)) == NULL)
305 		err(1, "calloc()");
306 
307 	for (i = 0; i < ndev; i++) {
308 		infos[i].index = i;
309 		if (ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]) < 0) {
310 			ndev--;
311 			i--;
312 			continue;
313 		}
314 	}
315 
316 	for (i = 0; i < ndev; i++) {
317 		strlcpy(rfields[i].name, infos[i].label.name, FIELD_NAME_MAX);
318 		rfields[i].valp = &values[i];
319 		rfields[i].infp = &infos[i];
320 	}
321 
322 	for (i = 0; i < ndev; i++) {
323 		values[i].dev = i;
324 		values[i].type = infos[i].type;
325 		if (infos[i].type != AUDIO_MIXER_CLASS) {
326 			values[i].un.value.num_channels = 2;
327 			if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0) {
328 				values[i].un.value.num_channels = 1;
329 				if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0)
330 					err(1, "AUDIO_MIXER_READ");
331 			}
332 		}
333 	}
334 
335 	for (j = i = 0; i < ndev; i++) {
336 		if (infos[i].type != AUDIO_MIXER_CLASS &&
337 		    infos[i].prev == AUDIO_MIXER_LAST) {
338 			fields[j++] = rfields[i];
339 			for (pos = infos[i].next; pos != AUDIO_MIXER_LAST;
340 			    pos = infos[pos].next) {
341 				fields[j] = rfields[pos];
342 				catstr(rfields[i].name, infos[pos].label.name,
343 				    fields[j].name);
344 				j++;
345 			}
346 		}
347 	}
348 
349 	for (i = 0; i < j; i++) {
350 		int cls = fields[i].infp->mixer_class;
351 		if (cls >= 0 && cls < ndev)
352 			catstr(infos[cls].label.name, fields[i].name,
353 			    fields[i].name);
354 	}
355 
356 	if (!argc && aflag) {
357 		for (i = 0; fields[i].name[0] != '\0'; i++) {
358 			prfield(&fields[i], sep, vflag);
359 			printf("\n");
360 		}
361 	} else if (argc > 0 && !aflag) {
362 		struct field *p;
363 
364 		while (argc--) {
365 			char *q;
366 
367 			ch = 0;
368 			if ((q = strchr(*argv, '=')) != NULL) {
369 				*q++ = '\0';
370 				ch = 1;
371 			}
372 
373 			if ((p = findfield(*argv)) == NULL) {
374 				warnx("field %s does not exist", *argv);
375 			} else if (ch || tflag) {
376 				if (tflag && q == NULL)
377 					q = "toggle";
378 				rdfield(fd, p, q, qflag);
379 			} else {
380 				prfield(p, sep, vflag);
381 				printf("\n");
382 			}
383 
384 			argv++;
385 		}
386 	} else
387 		usage();
388 	exit(0);
389 }
390 
391 __dead void
392 usage(void)
393 {
394 	extern char *__progname;	/* from crt0.o */
395 
396 	fprintf(stderr,
397 	    "usage: %s [-anv] [-f file]\n"
398 	    "       %s [-nv] [-f file] name ...\n"
399 	    "       %s [-qt] [-f file] name ...\n"
400 	    "       %s [-q] [-f file] name=value ...\n",
401 	    __progname, __progname, __progname, __progname);
402 
403 	exit(1);
404 }
405