xref: /netbsd/usr.bin/mixerctl/mixerctl.c (revision bf9ec67e)
1 /*	$NetBSD: mixerctl.c,v 1.17 2002/01/31 00:03:24 augustss 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 <stdio.h>
39 #include <stdlib.h>
40 #include <fcntl.h>
41 #include <err.h>
42 #include <unistd.h>
43 #include <string.h>
44 #include <sys/types.h>
45 #include <sys/ioctl.h>
46 #include <sys/audioio.h>
47 
48 #include <paths.h>
49 
50 FILE *out = stdout;
51 int vflag = 0;
52 
53 char *prog;
54 
55 struct field {
56 	char *name;
57 	mixer_ctrl_t *valp;
58 	mixer_devinfo_t *infp;
59 	char changed;
60 } *fields, *rfields;
61 
62 mixer_ctrl_t *values;
63 mixer_devinfo_t *infos;
64 
65 static char *
66 catstr(char *p, char *q)
67 {
68 	char *r = malloc(strlen(p) + strlen(q) + 2);
69 	strcpy(r, p);
70 	strcat(r, ".");
71 	strcat(r, q);
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, 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, 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
271 		*q++ = 0;
272 
273 	p = findfield(arg);
274 	if (p == NULL) {
275 		warnx("field %s does not exist", arg);
276 		return;
277 	}
278 
279 	val = *p->valp;
280 	if (q != NULL)
281 		r = rdfield(p, q);
282 	else
283 		r = incfield(p, incdec);
284 	if (r) {
285 		if (ioctl(fd, AUDIO_MIXER_WRITE, p->valp) < 0)
286 			warn("AUDIO_MIXER_WRITE");
287 		else if (sep) {
288 			*p->valp = val;
289 			prfield(p, ": ", 0);
290 			ioctl(fd, AUDIO_MIXER_READ, p->valp);
291 			printf(" -> ");
292 			prfield(p, 0, 0);
293 			printf("\n");
294 		}
295 	}
296 }
297 
298 static void
299 prarg(int fd, char *arg, char *sep)
300 {
301 	struct field *p;
302 
303 	p = findfield(arg);
304 	if (p == NULL)
305 		warnx("field %s does not exist", arg);
306 	else
307 		prfield(p, sep, vflag), fprintf(out, "\n");
308 }
309 
310 int
311 main(int argc, char **argv)
312 {
313 	int fd, i, j, ch, pos;
314 	int aflag = 0, wflag = 0;
315 	char *file;
316 	char *sep = "=";
317 	mixer_devinfo_t dinfo;
318 	int ndev;
319 
320 	file = getenv("MIXERDEVICE");
321 	if (file == NULL)
322 		file = _PATH_MIXER;
323 
324 	prog = *argv;
325 
326 	while ((ch = getopt(argc, argv, "ad:f:nvw")) != -1) {
327 		switch(ch) {
328 		case 'a':
329 			aflag++;
330 			break;
331 		case 'w':
332 			wflag++;
333 			break;
334 		case 'v':
335 			vflag++;
336 			break;
337 		case 'n':
338 			sep = 0;
339 			break;
340 		case 'f': /* compatibility */
341 		case 'd':
342 			file = optarg;
343 			break;
344 		case '?':
345 		default:
346 		usage:
347 		fprintf(out, "%s [-d file] [-v] [-n] name ...\n", prog);
348 		fprintf(out, "%s [-d file] [-v] [-n] -w name=value ...\n",prog);
349 		fprintf(out, "%s [-d file] [-v] [-n] -a\n", prog);
350 		exit(0);
351 		}
352 	}
353 	argc -= optind;
354 	argv += optind;
355 
356 	fd = open(file, O_RDWR);
357         /* Try with mixer0. */
358         if (fd < 0 && file == _PATH_MIXER) {
359         	file = _PATH_MIXER0;
360                 fd = open(file, O_RDWR);
361         }
362 
363 	if (fd < 0)
364 		err(1, "%s", file);
365 
366 	for(ndev = 0; ; ndev++) {
367 		dinfo.index = ndev;
368 		if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) < 0)
369 			break;
370 	}
371 	rfields = calloc(ndev, sizeof *rfields);
372 	fields = calloc(ndev, sizeof *fields);
373 	infos = calloc(ndev, sizeof *infos);
374 	values = calloc(ndev, sizeof *values);
375 
376 	for(i = 0; i < ndev; i++) {
377 		infos[i].index = i;
378 		ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]);
379 	}
380 
381 	for(i = 0; i < ndev; i++) {
382 		rfields[i].name = infos[i].label.name;
383 		rfields[i].valp = &values[i];
384 		rfields[i].infp = &infos[i];
385 	}
386 
387 	for(i = 0; i < ndev; i++) {
388 		values[i].dev = i;
389 		values[i].type = infos[i].type;
390 		if (infos[i].type != AUDIO_MIXER_CLASS) {
391 			values[i].un.value.num_channels = 2;
392 			if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0) {
393 				values[i].un.value.num_channels = 1;
394 				if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0)
395 					err(1, "AUDIO_MIXER_READ");
396 			}
397 		}
398 	}
399 
400 	for(j = i = 0; i < ndev; i++) {
401 		if (infos[i].type != AUDIO_MIXER_CLASS &&
402 		    infos[i].type != -1) {
403 			fields[j++] = rfields[i];
404 			for(pos = infos[i].next; pos != AUDIO_MIXER_LAST;
405 			    pos = infos[pos].next) {
406 				fields[j] = rfields[pos];
407 				fields[j].name = catstr(rfields[i].name,
408 							infos[pos].label.name);
409 				infos[pos].type = -1;
410 				j++;
411 			}
412 		}
413 	}
414 
415 	for(i = 0; i < j; i++) {
416 		int cls = fields[i].infp->mixer_class;
417 		if (cls >= 0 && cls < ndev)
418 			fields[i].name = catstr(infos[cls].label.name,
419 						fields[i].name);
420 	}
421 
422 	if (argc == 0 && aflag && !wflag) {
423 		for(i = 0; fields[i].name; i++) {
424 			prfield(&fields[i], sep, vflag);
425 			fprintf(out, "\n");
426 		}
427 	} else if (argc > 0 && !aflag) {
428 		while(argc--) {
429 			if (wflag)
430 				wrarg(fd, *argv, sep);
431 			else
432 				prarg(fd, *argv, sep);
433 			argv++;
434 		}
435 	} else
436 		goto usage;
437 	exit(0);
438 }
439