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