1/*	$NetBSD: mixerctl.c,v 1.23 2009/04/12 14:17:25 lukem 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.23 2009/04/12 14:17:25 lukem 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
49FILE *out = stdout;
50int vflag = 0;
51
52char *prog;
53
54struct field {
55	char *name;
56	mixer_ctrl_t *valp;
57	mixer_devinfo_t *infp;
58	char changed;
59} *fields, *rfields;
60
61mixer_ctrl_t *values;
62mixer_devinfo_t *infos;
63
64static char *
65catstr(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
75static struct field *
76findfield(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
85static void
86prfield(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
139static int
140rdfield(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
206static int
207incfield(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
249static void
250wrarg(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
307static void
308prarg(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
319int
320main(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