xref: /freebsd/usr.sbin/mixer/mixer.c (revision 5956d97f4b3204318ceb6aa9c77bd0bc6ea87a41)
1 /*-
2  * Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20  * THE SOFTWARE.
21  *
22  * $FreeBSD$
23  */
24 
25 #include <err.h>
26 #include <errno.h>
27 #include <mixer.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 
33 enum {
34 	C_VOL = 0,
35 	C_MUT,
36 	C_SRC,
37 };
38 
39 static void usage(void) __dead2;
40 static void initctls(struct mixer *);
41 static void printall(struct mixer *, int);
42 static void printminfo(struct mixer *, int);
43 static void printdev(struct mixer *, int);
44 static void printrecsrc(struct mixer *, int); /* XXX: change name */
45 /* Control handlers */
46 static int mod_dunit(struct mix_dev *, void *);
47 static int mod_volume(struct mix_dev *, void *);
48 static int mod_mute(struct mix_dev *, void *);
49 static int mod_recsrc(struct mix_dev *, void *);
50 static int print_volume(struct mix_dev *, void *);
51 static int print_mute(struct mix_dev *, void *);
52 static int print_recsrc(struct mix_dev *, void *);
53 
54 static const mix_ctl_t ctl_dunit = {
55 	.parent_dev	= NULL,
56 	.id		= -1,
57 	.name		= "default_unit",
58 	.mod		= mod_dunit,
59 	.print		= NULL
60 };
61 
62 int
63 main(int argc, char *argv[])
64 {
65 	struct mixer *m;
66 	mix_ctl_t *cp;
67 	char *name = NULL, buf[NAME_MAX];
68 	char *p, *q, *devstr, *ctlstr, *valstr = NULL;
69 	int dunit, i, n, pall = 1, shorthand;
70 	int aflag = 0, dflag = 0, oflag = 0, sflag = 0;
71 	int ch;
72 
73 	while ((ch = getopt(argc, argv, "ad:f:hos")) != -1) {
74 		switch (ch) {
75 		case 'a':
76 			aflag = 1;
77 			break;
78 		case 'd':
79 			dunit = strtol(optarg, NULL, 10);
80 			if (errno == EINVAL || errno == ERANGE)
81 				err(1, "strtol");
82 			dflag = 1;
83 			break;
84 		case 'f':
85 			name = optarg;
86 			break;
87 		case 'o':
88 			oflag = 1;
89 			break;
90 		case 's':
91 			sflag = 1;
92 			break;
93 		case 'h': /* FALLTHROUGH */
94 		case '?':
95 		default:
96 			usage();
97 		}
98 	}
99 	argc -= optind;
100 	argv += optind;
101 
102 	/* Print all mixers and exit. */
103 	if (aflag) {
104 		if ((n = mixer_get_nmixers()) < 0)
105 			err(1, "mixer_get_nmixers");
106 		for (i = 0; i < n; i++) {
107 			(void)snprintf(buf, sizeof(buf), "/dev/mixer%d", i);
108 			if ((m = mixer_open(buf)) == NULL)
109 				err(1, "mixer_open: %s", buf);
110 			initctls(m);
111 			if (sflag)
112 				printrecsrc(m, oflag);
113 			else {
114 				printall(m, oflag);
115 				if (oflag)
116 					printf("\n");
117 			}
118 			(void)mixer_close(m);
119 		}
120 		return (0);
121 	}
122 
123 	if ((m = mixer_open(name)) == NULL)
124 		err(1, "mixer_open: %s", name);
125 
126 	initctls(m);
127 
128 	if (dflag && ctl_dunit.mod(m->dev, &dunit) < 0)
129 		goto parse;
130 	if (sflag) {
131 		printrecsrc(m, oflag);
132 		(void)mixer_close(m);
133 		return (0);
134 	}
135 
136 parse:
137 	while (argc > 0) {
138 		if ((p = strdup(*argv)) == NULL)
139 			err(1, "strdup(%s)", *argv);
140 
141 		/* Check if we're using the shorthand syntax for volume setting. */
142 		shorthand = 0;
143 		for (q = p; *q != '\0'; q++) {
144 			if (*q == '=') {
145 				q++;
146 				shorthand = ((*q >= '0' && *q <= '9') ||
147 				    *q == '+' || *q == '-' || *q == '.');
148 				break;
149 			} else if (*q == '.')
150 				break;
151 		}
152 
153 		/* Split the string into device, control and value. */
154 		devstr = strsep(&p, ".=");
155 		if ((m->dev = mixer_get_dev_byname(m, devstr)) == NULL) {
156 			warnx("%s: no such device", devstr);
157 			goto next;
158 		}
159 		/* Input: `dev`. */
160 		if (p == NULL) {
161 			printdev(m, 1);
162 			pall = 0;
163 			goto next;
164 		} else if (shorthand) {
165 			/*
166 			 * Input: `dev=N` -> shorthand for `dev.volume=N`.
167 			 *
168 			 * We don't care what the rest of the string contains as
169 			 * long as we're sure the very beginning is right,
170 			 * mod_volume() will take care of parsing it properly.
171 			 */
172 			cp = mixer_get_ctl(m->dev, C_VOL);
173 			cp->mod(cp->parent_dev, p);
174 			goto next;
175 		}
176 		ctlstr = strsep(&p, "=");
177 		if ((cp = mixer_get_ctl_byname(m->dev, ctlstr)) == NULL) {
178 			warnx("%s.%s: no such control", devstr, ctlstr);
179 			goto next;
180 		}
181 		/* Input: `dev.control`. */
182 		if (p == NULL) {
183 			(void)cp->print(cp->parent_dev, cp->name);
184 			pall = 0;
185 			goto next;
186 		}
187 		valstr = p;
188 		/* Input: `dev.control=val`. */
189 		cp->mod(cp->parent_dev, valstr);
190 next:
191 		free(p);
192 		argc--;
193 		argv++;
194 	}
195 
196 	if (pall)
197 		printall(m, oflag);
198 	(void)mixer_close(m);
199 
200 	return (0);
201 }
202 
203 static void __dead2
204 usage(void)
205 {
206 	fprintf(stderr, "usage: %1$s [-f device] [-d unit] [-os] [dev[.control[=value]]] ...\n"
207 	    "       %1$s [-d unit] [-os] -a\n"
208 	    "       %1$s -h\n", getprogname());
209 	exit(1);
210 }
211 
212 static void
213 initctls(struct mixer *m)
214 {
215 	struct mix_dev *dp;
216 	int rc = 0;
217 
218 	TAILQ_FOREACH(dp, &m->devs, devs) {
219 		rc += mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume);
220 		rc += mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute);
221 		rc += mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc);
222 	}
223 	if (rc) {
224 		(void)mixer_close(m);
225 		err(1, "cannot make controls");
226 	}
227 }
228 
229 static void
230 printall(struct mixer *m, int oflag)
231 {
232 	struct mix_dev *dp;
233 
234 	printminfo(m, oflag);
235 	TAILQ_FOREACH(dp, &m->devs, devs) {
236 		m->dev = dp;
237 		printdev(m, oflag);
238 	}
239 }
240 
241 static void
242 printminfo(struct mixer *m, int oflag)
243 {
244 	int playrec = MIX_MODE_PLAY | MIX_MODE_REC;
245 
246 	if (oflag)
247 		return;
248 	printf("%s:", m->mi.name);
249 	if (*m->ci.longname != '\0')
250 		printf(" <%s>", m->ci.longname);
251 	if (*m->ci.hw_info != '\0')
252 		printf(" %s", m->ci.hw_info);
253 
254 	if (m->mode != 0)
255 		printf(" (");
256 	if (m->mode & MIX_MODE_PLAY)
257 		printf("play");
258 	if ((m->mode & playrec) == playrec)
259 		printf("/");
260 	if (m->mode & MIX_MODE_REC)
261 		printf("rec");
262 	if (m->mode != 0)
263 		printf(")");
264 
265 	if (m->f_default)
266 		printf(" (default)");
267 	printf("\n");
268 }
269 
270 static void
271 printdev(struct mixer *m, int oflag)
272 {
273 	struct mix_dev *d = m->dev;
274 	mix_ctl_t *cp;
275 
276 	if (!oflag) {
277 		printf("    %-10s= %.2f:%.2f    ",
278 		    d->name, d->vol.left, d->vol.right);
279 		if (!MIX_ISREC(m, d->devno))
280 			printf(" pbk");
281 		if (MIX_ISREC(m, d->devno))
282 			printf(" rec");
283 		if (MIX_ISRECSRC(m, d->devno))
284 			printf(" src");
285 		if (MIX_ISMUTE(m, d->devno))
286 			printf(" mute");
287 		printf("\n");
288 	} else {
289 		TAILQ_FOREACH(cp, &d->ctls, ctls) {
290 			(void)cp->print(cp->parent_dev, cp->name);
291 		}
292 	}
293 }
294 
295 static void
296 printrecsrc(struct mixer *m, int oflag)
297 {
298 	struct mix_dev *dp;
299 	int n = 0;
300 
301 	if (!m->recmask)
302 		return;
303 	if (!oflag)
304 		printf("%s: ", m->mi.name);
305 	TAILQ_FOREACH(dp, &m->devs, devs) {
306 		if (MIX_ISRECSRC(m, dp->devno)) {
307 			if (n++ && !oflag)
308 				printf(", ");
309 			printf("%s", dp->name);
310 			if (oflag)
311 				printf(".%s=+%s",
312 				    mixer_get_ctl(dp, C_SRC)->name, n ? " " : "");
313 		}
314 	}
315 	printf("\n");
316 }
317 
318 static int
319 mod_dunit(struct mix_dev *d, void *p)
320 {
321 	int dunit = *((int *)p);
322 	int n;
323 
324 	if ((n = mixer_get_dunit()) < 0) {
325 		warn("cannot get default unit");
326 		return (-1);
327 	}
328 	if (mixer_set_dunit(d->parent_mixer, dunit) < 0) {
329 		warn("cannot set default unit to: %d", dunit);
330 		return (-1);
331 	}
332 	printf("%s: %d -> %d\n", ctl_dunit.name, n, dunit);
333 
334 	return (0);
335 }
336 
337 static int
338 mod_volume(struct mix_dev *d, void *p)
339 {
340 	struct mixer *m;
341 	mix_ctl_t *cp;
342 	mix_volume_t v;
343 	const char *val;
344 	char *endp, lstr[8], rstr[8];
345 	float lprev, rprev, lrel, rrel;
346 	int n;
347 
348 	m = d->parent_mixer;
349 	cp = mixer_get_ctl(m->dev, C_VOL);
350 	val = p;
351 	n = sscanf(val, "%7[^:]:%7s", lstr, rstr);
352 	if (n == EOF) {
353 		warnx("invalid volume value: %s", val);
354 		return (-1);
355 	}
356 	lrel = rrel = 0;
357 	if (n > 0) {
358 		if (*lstr == '+' || *lstr == '-')
359 			lrel = 1;
360 		v.left = strtof(lstr, &endp);
361 		if (*endp != '\0' && (*endp != '%' || *(endp + 1) != '\0')) {
362 			warnx("invalid volume value: %s", lstr);
363 			return (-1);
364 		}
365 
366 		if (*endp == '%')
367 			v.left /= 100.0f;
368 	}
369 	if (n > 1) {
370 		if (*rstr == '+' || *rstr == '-')
371 			rrel = 1;
372 		v.right = strtof(rstr, &endp);
373 		if (*endp != '\0' && (*endp != '%' || *(endp + 1) != '\0')) {
374 			warnx("invalid volume value: %s", rstr);
375 			return (-1);
376 		}
377 
378 		if (*endp == '%')
379 			v.right /= 100.0f;
380 	}
381 	switch (n) {
382 	case 1:
383 		v.right = v.left; /* FALLTHROUGH */
384 		rrel = lrel;
385 	case 2:
386 		if (lrel)
387 			v.left += m->dev->vol.left;
388 		if (rrel)
389 			v.right += m->dev->vol.right;
390 
391 		if (v.left < MIX_VOLMIN)
392 			v.left = MIX_VOLMIN;
393 		else if (v.left > MIX_VOLMAX)
394 			v.left = MIX_VOLMAX;
395 		if (v.right < MIX_VOLMIN)
396 			v.right = MIX_VOLMIN;
397 		else if (v.right > MIX_VOLMAX)
398 			v.right = MIX_VOLMAX;
399 
400 		lprev = m->dev->vol.left;
401 		rprev = m->dev->vol.right;
402 		if (mixer_set_vol(m, v) < 0)
403 			warn("%s.%s=%.2f:%.2f",
404 			    m->dev->name, cp->name, v.left, v.right);
405 		else
406 			printf("%s.%s: %.2f:%.2f -> %.2f:%.2f\n",
407 			   m->dev->name, cp->name, lprev, rprev, v.left, v.right);
408 	}
409 
410 	return (0);
411 }
412 
413 static int
414 mod_mute(struct mix_dev *d, void *p)
415 {
416 	struct mixer *m;
417 	mix_ctl_t *cp;
418 	const char *val;
419 	int n, opt = -1;
420 
421 	m = d->parent_mixer;
422 	cp = mixer_get_ctl(m->dev, C_MUT);
423 	val = p;
424 	switch (*val) {
425 	case '0':
426 		opt = MIX_UNMUTE;
427 		break;
428 	case '1':
429 		opt = MIX_MUTE;
430 		break;
431 	case '^':
432 		opt = MIX_TOGGLEMUTE;
433 		break;
434 	default:
435 		warnx("%c: no such modifier", *val);
436 		return (-1);
437 	}
438 	n = MIX_ISMUTE(m, m->dev->devno);
439 	if (mixer_set_mute(m, opt) < 0)
440 		warn("%s.%s=%c", m->dev->name, cp->name, *val);
441 	else
442 		printf("%s.%s: %d -> %d\n",
443 		    m->dev->name, cp->name, n, MIX_ISMUTE(m, m->dev->devno));
444 
445 	return (0);
446 }
447 
448 static int
449 mod_recsrc(struct mix_dev *d, void *p)
450 {
451 	struct mixer *m;
452 	mix_ctl_t *cp;
453 	const char *val;
454 	int n, opt = -1;
455 
456 	m = d->parent_mixer;
457 	cp = mixer_get_ctl(m->dev, C_SRC);
458 	val = p;
459 	switch (*val) {
460 	case '+':
461 		opt = MIX_ADDRECSRC;
462 		break;
463 	case '-':
464 		opt = MIX_REMOVERECSRC;
465 		break;
466 	case '=':
467 		opt = MIX_SETRECSRC;
468 		break;
469 	case '^':
470 		opt = MIX_TOGGLERECSRC;
471 		break;
472 	default:
473 		warnx("%c: no such modifier", *val);
474 		return (-1);
475 	}
476 	n = MIX_ISRECSRC(m, m->dev->devno);
477 	if (mixer_mod_recsrc(m, opt) < 0)
478 		warn("%s.%s=%c", m->dev->name, cp->name, *val);
479 	else
480 		printf("%s.%s: %d -> %d\n",
481 		    m->dev->name, cp->name, n, MIX_ISRECSRC(m, m->dev->devno));
482 
483 	return (0);
484 }
485 
486 static int
487 print_volume(struct mix_dev *d, void *p)
488 {
489 	struct mixer *m = d->parent_mixer;
490 	const char *ctl_name = p;
491 
492 	printf("%s.%s=%.2f:%.2f\n",
493 	    m->dev->name, ctl_name, m->dev->vol.left, m->dev->vol.right);
494 
495 	return (0);
496 }
497 
498 static int
499 print_mute(struct mix_dev *d, void *p)
500 {
501 	struct mixer *m = d->parent_mixer;
502 	const char *ctl_name = p;
503 
504 	printf("%s.%s=%d\n", m->dev->name, ctl_name, MIX_ISMUTE(m, m->dev->devno));
505 
506 	return (0);
507 }
508 
509 static int
510 print_recsrc(struct mix_dev *d, void *p)
511 {
512 	struct mixer *m = d->parent_mixer;
513 	const char *ctl_name = p;
514 
515 	if (!MIX_ISRECSRC(m, m->dev->devno))
516 		return (-1);
517 	printf("%s.%s=+\n", m->dev->name, ctl_name);
518 
519 	return (0);
520 }
521