1/*	$NetBSD: mixerctl.c,v 1.30 2021/12/26 15:36:49 rillig 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.30 2021/12/26 15:36:49 rillig 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
49static FILE *out = stdout;
50static int vflag = 0;
51
52static struct field {
53	char *name;
54	mixer_ctrl_t *valp;
55	mixer_devinfo_t *infp;
56	char changed;
57} *fields, *rfields;
58
59static mixer_ctrl_t *values;
60static mixer_devinfo_t *infos;
61
62static const char mixer_path[] = _PATH_MIXER;
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(EXIT_FAILURE, "malloc");
72	return r;
73}
74
75static struct field *
76findfield(const 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(EXIT_FAILURE, "Invalid format %d", m->type);
136	}
137}
138
139static int
140clip(int vol)
141{
142	if (vol <= AUDIO_MIN_GAIN)
143		return AUDIO_MIN_GAIN;
144	if (vol >= AUDIO_MAX_GAIN)
145		return AUDIO_MAX_GAIN;
146	return vol;
147}
148
149static int
150rdfield(struct field *p, char *q)
151{
152	mixer_ctrl_t *m;
153	int v, v0, v1, mask;
154	int i;
155	char *s;
156
157	m = p->valp;
158	switch(m->type) {
159	case AUDIO_MIXER_ENUM:
160		for (i = 0; i < p->infp->un.e.num_mem; i++)
161			if (strcmp(p->infp->un.e.member[i].label.name, q) == 0)
162				break;
163		if (i < p->infp->un.e.num_mem)
164			m->un.ord = p->infp->un.e.member[i].ord;
165		else {
166			warnx("Bad enum value %s", q);
167			return 0;
168		}
169		break;
170	case AUDIO_MIXER_SET:
171		mask = 0;
172		for (v = 0; q && *q; q = s) {
173			s = strchr(q, ',');
174			if (s)
175				*s++ = 0;
176			for (i = 0; i < p->infp->un.s.num_mem; i++)
177				if (strcmp(p->infp->un.s.member[i].label.name,
178				    q) == 0)
179					break;
180			if (i < p->infp->un.s.num_mem) {
181				mask |= p->infp->un.s.member[i].mask;
182			} else {
183				warnx("Bad set value %s", q);
184				return 0;
185			}
186		}
187		m->un.mask = mask;
188		break;
189	case AUDIO_MIXER_VALUE:
190		if (m->un.value.num_channels == 1) {
191			if (sscanf(q, "%d", &v) == 1) {
192				m->un.value.level[0] = clip(v);
193			} else {
194				warnx("Bad number %s", q);
195				return 0;
196			}
197		} else {
198			if (sscanf(q, "%d,%d", &v0, &v1) == 2) {
199				m->un.value.level[0] = clip(v0);
200				m->un.value.level[1] = clip(v1);
201			} else if (sscanf(q, "%d", &v) == 1) {
202				m->un.value.level[0] =
203				    m->un.value.level[1] = clip(v);
204			} else {
205				warnx("Bad numbers %s", q);
206				return 0;
207			}
208		}
209		break;
210	default:
211		errx(EXIT_FAILURE, "Invalid format %d", m->type);
212	}
213	p->changed = 1;
214	return 1;
215}
216
217static int
218incfield(struct field *p, int inc)
219{
220	mixer_ctrl_t *m;
221	int i, v;
222
223	m = p->valp;
224	switch(m->type) {
225	case AUDIO_MIXER_ENUM:
226		m->un.ord += inc;
227		if (m->un.ord < 0)
228			m->un.ord = p->infp->un.e.num_mem - 1;
229		if (m->un.ord >= p->infp->un.e.num_mem)
230			m->un.ord = 0;
231		break;
232	case AUDIO_MIXER_SET:
233		m->un.mask += inc;
234		if (m->un.mask < 0)
235			m->un.mask = (1 << p->infp->un.s.num_mem) - 1;
236		if (m->un.mask >= (1 << p->infp->un.s.num_mem))
237			m->un.mask = 0;
238		warnx("Can't ++/-- %s", p->name);
239		return 0;
240	case AUDIO_MIXER_VALUE:
241		if (p->infp->un.v.delta)
242			inc *= p->infp->un.v.delta;
243		for (i = 0; i < m->un.value.num_channels; i++) {
244			v = m->un.value.level[i];
245			v += inc;
246			m->un.value.level[i] = clip(v);
247		}
248		break;
249	default:
250		errx(EXIT_FAILURE, "Invalid format %d", m->type);
251	}
252	p->changed = 1;
253	return 1;
254}
255
256static void
257wrarg(int fd, char *arg, const 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) == -1)
302			warn("AUDIO_MIXER_WRITE");
303		else if (sep) {
304			*p->valp = val;
305			prfield(p, ": ", 0);
306			if (ioctl(fd, AUDIO_MIXER_READ, p->valp) == -1)
307				warn("AUDIO_MIXER_READ");
308			printf(" -> ");
309			prfield(p, 0, 0);
310			printf("\n");
311		}
312	}
313}
314
315static void
316prarg(int fd, char *arg, const char *sep)
317{
318	struct field *p;
319
320	p = findfield(arg);
321	if (p == NULL)
322		warnx("field %s does not exist", arg);
323	else
324		prfield(p, sep, vflag), fprintf(out, "\n");
325}
326
327static inline void __dead
328usage(void)
329{
330	const char *prog = getprogname();
331
332	fprintf(stderr, "Usage:\t%s [-d file] [-v] [-n] name ...\n", prog);
333	fprintf(stderr, "\t%s [-d file] [-v] [-n] -w name=value ...\n", prog);
334	fprintf(stderr, "\t%s [-d file] [-v] [-n] -a\n", prog);
335	exit(EXIT_FAILURE);
336}
337
338int
339main(int argc, char **argv)
340{
341	int fd, i, j, ch, pos;
342	int aflag = 0, wflag = 0;
343	const char *file;
344	const char *sep = "=";
345	mixer_devinfo_t dinfo;
346	int ndev;
347
348	file = getenv("MIXERDEVICE");
349	if (file == NULL)
350		file = mixer_path;
351
352
353	while ((ch = getopt(argc, argv, "ad:f:nvw")) != -1) {
354		switch(ch) {
355		case 'a':
356			aflag++;
357			break;
358		case 'w':
359			wflag++;
360			break;
361		case 'v':
362			vflag++;
363			break;
364		case 'n':
365			sep = 0;
366			break;
367		case 'f': /* compatibility */
368		case 'd':
369			file = optarg;
370			break;
371		case '?':
372		default:
373			usage();
374		}
375	}
376	argc -= optind;
377	argv += optind;
378
379	if (aflag ? (argc != 0 || wflag) : argc == 0)
380		usage();
381
382	fd = open(file, O_RDWR);
383	/* Try with mixer0 but only if using the default device. */
384	if (fd == -1 && file == mixer_path) {
385		file = _PATH_MIXER0;
386		fd = open(file, O_RDWR);
387	}
388
389	if (fd == -1)
390		err(EXIT_FAILURE, "Can't open `%s'", file);
391
392	for (ndev = 0; ; ndev++) {
393		dinfo.index = ndev;
394		if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) == -1)
395			break;
396	}
397	rfields = calloc(ndev, sizeof *rfields);
398	fields = calloc(ndev, sizeof *fields);
399	infos = calloc(ndev, sizeof *infos);
400	values = calloc(ndev, sizeof *values);
401
402	for (i = 0; i < ndev; i++) {
403		infos[i].index = i;
404		if (ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]) == -1)
405			warn("AUDIO_MIXER_DEVINFO for %d", i);
406	}
407
408	for (i = 0; i < ndev; i++) {
409		rfields[i].name = infos[i].label.name;
410		rfields[i].valp = &values[i];
411		rfields[i].infp = &infos[i];
412	}
413
414	for (i = 0; i < ndev; i++) {
415		values[i].dev = i;
416		values[i].type = infos[i].type;
417		if (infos[i].type != AUDIO_MIXER_CLASS) {
418			values[i].un.value.num_channels = 2;
419			if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) == -1) {
420				values[i].un.value.num_channels = 1;
421				if (ioctl(fd, AUDIO_MIXER_READ, &values[i])
422				    == -1)
423					err(EXIT_FAILURE, "AUDIO_MIXER_READ");
424			}
425		}
426	}
427
428	for (j = i = 0; i < ndev; i++) {
429		if (infos[i].type != AUDIO_MIXER_CLASS &&
430		    infos[i].type != -1) {
431			fields[j++] = rfields[i];
432			for (pos = infos[i].next; pos != AUDIO_MIXER_LAST;
433			    pos = infos[pos].next) {
434				fields[j] = rfields[pos];
435				fields[j].name = catstr(rfields[i].name,
436				    infos[pos].label.name);
437				infos[pos].type = -1;
438				j++;
439			}
440		}
441	}
442
443	for (i = 0; i < j; i++) {
444		int cls = fields[i].infp->mixer_class;
445		if (cls >= 0 && cls < ndev)
446			fields[i].name = catstr(infos[cls].label.name,
447			    fields[i].name);
448	}
449
450	if (argc == 0 && aflag && !wflag) {
451		for (i = 0; i < j; i++) {
452			prfield(&fields[i], sep, vflag);
453			fprintf(out, "\n");
454		}
455	} else if (argc > 0 && !aflag) {
456		while (argc--) {
457			if (wflag)
458				wrarg(fd, *argv, sep);
459			else
460				prarg(fd, *argv, sep);
461			argv++;
462		}
463	} else
464		usage();
465	return EXIT_SUCCESS;
466}
467