xref: /netbsd-src/usr.bin/mixerctl/mixerctl.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /*	$NetBSD: mixerctl.c,v 1.24 2009/07/14 21:02:24 apb Exp $	*/
2 
3 /*
4  * Copyright (c) 1997 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Lennart Augustsson (augustss@NetBSD.org) and 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 #include <sys/cdefs.h>
32 
33 #ifndef lint
34 __RCSID("$NetBSD: mixerctl.c,v 1.24 2009/07/14 21:02:24 apb Exp $");
35 #endif
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <fcntl.h>
40 #include <err.h>
41 #include <unistd.h>
42 #include <string.h>
43 #include <sys/types.h>
44 #include <sys/ioctl.h>
45 #include <sys/audioio.h>
46 
47 #include <paths.h>
48 
49 FILE *out = stdout;
50 int vflag = 0;
51 
52 char *prog;
53 
54 struct field {
55 	char *name;
56 	mixer_ctrl_t *valp;
57 	mixer_devinfo_t *infp;
58 	char changed;
59 } *fields, *rfields;
60 
61 mixer_ctrl_t *values;
62 mixer_devinfo_t *infos;
63 
64 static char *
65 catstr(char *p, char *q)
66 {
67 	char *r;
68 
69 	asprintf(&r, "%s.%s", p, q);
70 	if (!r)
71 		err(1, "malloc");
72 	return r;
73 }
74 
75 static struct field *
76 findfield(char *name)
77 {
78 	int i;
79 	for(i = 0; fields[i].name; i++)
80 		if (strcmp(fields[i].name, name) == 0)
81 			return &fields[i];
82 	return 0;
83 }
84 
85 static void
86 prfield(struct field *p, const char *sep, int prvalset)
87 {
88 	mixer_ctrl_t *m;
89 	int i, n;
90 
91 	if (sep)
92 		fprintf(out, "%s%s", p->name, sep);
93 	m = p->valp;
94 	switch(m->type) {
95 	case AUDIO_MIXER_ENUM:
96 		for(i = 0; i < p->infp->un.e.num_mem; i++)
97 			if (p->infp->un.e.member[i].ord == m->un.ord)
98 				fprintf(out, "%s",
99 					p->infp->un.e.member[i].label.name);
100 		if (prvalset) {
101 			fprintf(out, "  [ ");
102 			for(i = 0; i < p->infp->un.e.num_mem; i++)
103 				fprintf(out, "%s ",
104 				    p->infp->un.e.member[i].label.name);
105 			fprintf(out, "]");
106 		}
107 		break;
108 	case AUDIO_MIXER_SET:
109 		for(n = i = 0; i < p->infp->un.s.num_mem; i++)
110 			if (m->un.mask & p->infp->un.s.member[i].mask)
111 				fprintf(out, "%s%s", n++ ? "," : "",
112 					p->infp->un.s.member[i].label.name);
113 		if (prvalset) {
114 			fprintf(out, "  { ");
115 			for(i = 0; i < p->infp->un.s.num_mem; i++)
116 				fprintf(out, "%s ",
117 				    p->infp->un.s.member[i].label.name);
118 			fprintf(out, "}");
119 		}
120 		break;
121 	case AUDIO_MIXER_VALUE:
122 		if (m->un.value.num_channels == 1)
123 			fprintf(out, "%d", m->un.value.level[0]);
124 		else
125 			fprintf(out, "%d,%d", m->un.value.level[0],
126 			       m->un.value.level[1]);
127 		if (prvalset) {
128 			fprintf(out, " %s", p->infp->un.v.units.name);
129 			if (p->infp->un.v.delta)
130 				fprintf(out, " delta=%d", p->infp->un.v.delta);
131 		}
132 		break;
133 	default:
134 		printf("\n");
135 		errx(1, "Invalid format.");
136 	}
137 }
138 
139 static int
140 rdfield(struct field *p, char *q)
141 {
142 	mixer_ctrl_t *m;
143 	int v, v0, v1, mask;
144 	int i;
145 	char *s;
146 
147 	m = p->valp;
148 	switch(m->type) {
149 	case AUDIO_MIXER_ENUM:
150 		for(i = 0; i < p->infp->un.e.num_mem; i++)
151 			if (strcmp(p->infp->un.e.member[i].label.name, q) == 0)
152 				break;
153 		if (i < p->infp->un.e.num_mem)
154 			m->un.ord = p->infp->un.e.member[i].ord;
155 		else {
156 			warnx("Bad enum value %s", q);
157 			return 0;
158 		}
159 		break;
160 	case AUDIO_MIXER_SET:
161 		mask = 0;
162 		for(v = 0; q && *q; q = s) {
163 			s = strchr(q, ',');
164 			if (s)
165 				*s++ = 0;
166 			for(i = 0; i < p->infp->un.s.num_mem; i++)
167 				if (strcmp(p->infp->un.s.member[i].label.name,
168 					   q) == 0)
169 					break;
170 			if (i < p->infp->un.s.num_mem) {
171 				mask |= p->infp->un.s.member[i].mask;
172 			} else {
173 				warnx("Bad set value %s", q);
174 				return 0;
175 			}
176 		}
177 		m->un.mask = mask;
178 		break;
179 	case AUDIO_MIXER_VALUE:
180 		if (m->un.value.num_channels == 1) {
181 			if (sscanf(q, "%d", &v) == 1) {
182 				m->un.value.level[0] = v;
183 			} else {
184 				warnx("Bad number %s", q);
185 				return 0;
186 			}
187 		} else {
188 			if (sscanf(q, "%d,%d", &v0, &v1) == 2) {
189 				m->un.value.level[0] = v0;
190 				m->un.value.level[1] = v1;
191 			} else if (sscanf(q, "%d", &v) == 1) {
192 				m->un.value.level[0] = m->un.value.level[1] = v;
193 			} else {
194 				warnx("Bad numbers %s", q);
195 				return 0;
196 			}
197 		}
198 		break;
199 	default:
200 		errx(1, "Invalid format.");
201 	}
202 	p->changed = 1;
203 	return 1;
204 }
205 
206 static int
207 incfield(struct field *p, int inc)
208 {
209 	mixer_ctrl_t *m;
210 	int i, v;
211 
212 	m = p->valp;
213 	switch(m->type) {
214 	case AUDIO_MIXER_ENUM:
215 		m->un.ord += inc;
216 		if (m->un.ord < 0)
217 			m->un.ord = p->infp->un.e.num_mem-1;
218 		if (m->un.ord >= p->infp->un.e.num_mem)
219 			m->un.ord = 0;
220 		break;
221 	case AUDIO_MIXER_SET:
222 		m->un.mask += inc;
223 		if (m->un.mask < 0)
224 			m->un.mask = (1 << p->infp->un.s.num_mem) - 1;
225 		if (m->un.mask >= (1 << p->infp->un.s.num_mem))
226 			m->un.mask = 0;
227 		warnx("Can't ++/-- %s", p->name);
228 		return 0;
229 	case AUDIO_MIXER_VALUE:
230 		if (p->infp->un.v.delta)
231 			inc *= p->infp->un.v.delta;
232 		for (i = 0; i < m->un.value.num_channels; i++) {
233 			v = m->un.value.level[i];
234 			v += inc;
235 			if (v < AUDIO_MIN_GAIN)
236 				v = AUDIO_MIN_GAIN;
237 			if (v > AUDIO_MAX_GAIN)
238 				v = AUDIO_MAX_GAIN;
239 			m->un.value.level[i] = v;
240 		}
241 		break;
242 	default:
243 		errx(1, "Invalid format.");
244 	}
245 	p->changed = 1;
246 	return 1;
247 }
248 
249 static void
250 wrarg(int fd, char *arg, const char *sep)
251 {
252 	char *q;
253 	struct field *p;
254 	mixer_ctrl_t val;
255 	int incdec, r;
256 
257 	q = strchr(arg, '=');
258 	if (q == NULL) {
259 		int l = strlen(arg);
260 		incdec = 0;
261 		if (l > 2 && arg[l-2] == '+' && arg[l-1] == '+')
262 			incdec = 1;
263 		else if (l > 2 && arg[l-2] == '-' && arg[l-1] == '-')
264 			incdec = -1;
265 		else {
266 			warnx("No `=' in %s", arg);
267 			return;
268 		}
269 		arg[l-2] = 0;
270 	} else if (q > arg && (*(q-1) == '+' || *(q-1) == '-')) {
271 		if (sscanf(q+1, "%d", &incdec) != 1) {
272 			warnx("Bad number %s", q+1);
273 			return;
274 		}
275 		if (*(q-1) == '-')
276 			incdec *= -1;
277 		*(q-1) = 0;
278 		q = NULL;
279 	} else
280 		*q++ = 0;
281 
282 	p = findfield(arg);
283 	if (p == NULL) {
284 		warnx("field %s does not exist", arg);
285 		return;
286 	}
287 
288 	val = *p->valp;
289 	if (q != NULL)
290 		r = rdfield(p, q);
291 	else
292 		r = incfield(p, incdec);
293 	if (r) {
294 		if (ioctl(fd, AUDIO_MIXER_WRITE, p->valp) < 0)
295 			warn("AUDIO_MIXER_WRITE");
296 		else if (sep) {
297 			*p->valp = val;
298 			prfield(p, ": ", 0);
299 			ioctl(fd, AUDIO_MIXER_READ, p->valp);
300 			printf(" -> ");
301 			prfield(p, 0, 0);
302 			printf("\n");
303 		}
304 	}
305 }
306 
307 static void
308 prarg(int fd, char *arg, const char *sep)
309 {
310 	struct field *p;
311 
312 	p = findfield(arg);
313 	if (p == NULL)
314 		warnx("field %s does not exist", arg);
315 	else
316 		prfield(p, sep, vflag), fprintf(out, "\n");
317 }
318 
319 int
320 main(int argc, char **argv)
321 {
322 	int fd, i, j, ch, pos;
323 	int aflag = 0, wflag = 0;
324 	const char *file;
325 	const char *sep = "=";
326 	mixer_devinfo_t dinfo;
327 	int ndev;
328 
329 	file = getenv("MIXERDEVICE");
330 	if (file == NULL)
331 		file = _PATH_MIXER;
332 
333 	prog = *argv;
334 
335 	while ((ch = getopt(argc, argv, "ad:f:nvw")) != -1) {
336 		switch(ch) {
337 		case 'a':
338 			aflag++;
339 			break;
340 		case 'w':
341 			wflag++;
342 			break;
343 		case 'v':
344 			vflag++;
345 			break;
346 		case 'n':
347 			sep = 0;
348 			break;
349 		case 'f': /* compatibility */
350 		case 'd':
351 			file = optarg;
352 			break;
353 		case '?':
354 		default:
355 		usage:
356 		fprintf(out, "%s [-d file] [-v] [-n] name ...\n", prog);
357 		fprintf(out, "%s [-d file] [-v] [-n] -w name=value ...\n",prog);
358 		fprintf(out, "%s [-d file] [-v] [-n] -a\n", prog);
359 		exit(0);
360 		}
361 	}
362 	argc -= optind;
363 	argv += optind;
364 
365 	fd = open(file, O_RDWR);
366         /* Try with mixer0. */
367         if (fd < 0 && strcmp(file, _PATH_MIXER) == 0) {
368         	file = _PATH_MIXER0;
369                 fd = open(file, O_RDWR);
370         }
371 
372 	if (fd < 0)
373 		err(1, "%s", file);
374 
375 	for(ndev = 0; ; ndev++) {
376 		dinfo.index = ndev;
377 		if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) < 0)
378 			break;
379 	}
380 	rfields = calloc(ndev, sizeof *rfields);
381 	fields = calloc(ndev, sizeof *fields);
382 	infos = calloc(ndev, sizeof *infos);
383 	values = calloc(ndev, sizeof *values);
384 
385 	for(i = 0; i < ndev; i++) {
386 		infos[i].index = i;
387 		ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]);
388 	}
389 
390 	for(i = 0; i < ndev; i++) {
391 		rfields[i].name = infos[i].label.name;
392 		rfields[i].valp = &values[i];
393 		rfields[i].infp = &infos[i];
394 	}
395 
396 	for(i = 0; i < ndev; i++) {
397 		values[i].dev = i;
398 		values[i].type = infos[i].type;
399 		if (infos[i].type != AUDIO_MIXER_CLASS) {
400 			values[i].un.value.num_channels = 2;
401 			if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0) {
402 				values[i].un.value.num_channels = 1;
403 				if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0)
404 					err(1, "AUDIO_MIXER_READ");
405 			}
406 		}
407 	}
408 
409 	for(j = i = 0; i < ndev; i++) {
410 		if (infos[i].type != AUDIO_MIXER_CLASS &&
411 		    infos[i].type != -1) {
412 			fields[j++] = rfields[i];
413 			for(pos = infos[i].next; pos != AUDIO_MIXER_LAST;
414 			    pos = infos[pos].next) {
415 				fields[j] = rfields[pos];
416 				fields[j].name = catstr(rfields[i].name,
417 							infos[pos].label.name);
418 				infos[pos].type = -1;
419 				j++;
420 			}
421 		}
422 	}
423 
424 	for(i = 0; i < j; i++) {
425 		int cls = fields[i].infp->mixer_class;
426 		if (cls >= 0 && cls < ndev)
427 			fields[i].name = catstr(infos[cls].label.name,
428 						fields[i].name);
429 	}
430 
431 	if (argc == 0 && aflag && !wflag) {
432 		for(i = 0; fields[i].name; i++) {
433 			prfield(&fields[i], sep, vflag);
434 			fprintf(out, "\n");
435 		}
436 	} else if (argc > 0 && !aflag) {
437 		while(argc--) {
438 			if (wflag)
439 				wrarg(fd, *argv, sep);
440 			else
441 				prarg(fd, *argv, sep);
442 			argv++;
443 		}
444 	} else
445 		goto usage;
446 	exit(0);
447 }
448