xref: /freebsd/usr.sbin/mixer/mixer.c (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
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 
23 #include <sys/sysctl.h>
24 #include <sys/wait.h>
25 
26 #include <err.h>
27 #include <errno.h>
28 #include <mixer.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 
34 enum {
35 	C_VOL = 0,
36 	C_MUT,
37 	C_SRC,
38 };
39 
40 static void usage(void) __dead2;
41 static void initctls(struct mixer *);
42 static void printall(struct mixer *, int);
43 static void printminfo(struct mixer *, int);
44 static void printdev(struct mixer *, int);
45 static void printrecsrc(struct mixer *, int); /* XXX: change name */
46 static int set_dunit(struct mixer *, int, char *);
47 /* Control handlers */
48 static int mod_volume(struct mix_dev *, void *);
49 static int mod_mute(struct mix_dev *, void *);
50 static int mod_recsrc(struct mix_dev *, void *);
51 static int print_volume(struct mix_dev *, void *);
52 static int print_mute(struct mix_dev *, void *);
53 static int print_recsrc(struct mix_dev *, void *);
54 
55 int
56 main(int argc, char *argv[])
57 {
58 	struct mixer *m;
59 	mix_ctl_t *cp;
60 	char *name = NULL, buf[NAME_MAX], *vctl = NULL;
61 	char *p, *q, *devstr, *ctlstr, *valstr = NULL;
62 	int dunit, i, n, pall = 1, shorthand;
63 	int aflag = 0, dflag = 0, oflag = 0, sflag = 0;
64 	int ch;
65 
66 	while ((ch = getopt(argc, argv, "ad:f:hosV:")) != -1) {
67 		switch (ch) {
68 		case 'a':
69 			aflag = 1;
70 			break;
71 		case 'd':
72 			if (strncmp(optarg, "pcm", 3) == 0)
73 				optarg += 3;
74 			errno = 0;
75 			dunit = strtol(optarg, NULL, 10);
76 			if (errno == EINVAL || errno == ERANGE)
77 				err(1, "strtol(%s)", optarg);
78 			dflag = 1;
79 			break;
80 		case 'f':
81 			name = optarg;
82 			break;
83 		case 'o':
84 			oflag = 1;
85 			break;
86 		case 's':
87 			sflag = 1;
88 			break;
89 		case 'V':
90 			vctl = optarg;
91 			break;
92 		case 'h': /* FALLTHROUGH */
93 		case '?':
94 		default:
95 			usage();
96 		}
97 	}
98 	argc -= optind;
99 	argv += optind;
100 
101 	/* Print all mixers and exit. */
102 	if (aflag) {
103 		if ((n = mixer_get_nmixers()) < 0)
104 			errx(1, "no mixers present in the system");
105 		for (i = 0; i < n; i++) {
106 			(void)mixer_get_path(buf, sizeof(buf), i);
107 			if ((m = mixer_open(buf)) == NULL)
108 				continue;
109 			initctls(m);
110 			if (sflag)
111 				printrecsrc(m, oflag);
112 			else {
113 				printall(m, oflag);
114 				if (oflag)
115 					printf("\n");
116 			}
117 			(void)mixer_close(m);
118 		}
119 		return (0);
120 	}
121 
122 	if ((m = mixer_open(name)) == NULL)
123 		errx(1, "%s: no such mixer", name);
124 
125 	initctls(m);
126 
127 	if (dflag) {
128 		if (set_dunit(m, dunit, vctl) < 0)
129 			goto parse;
130 		else {
131 			/*
132 			 * Open current mixer since we changed the default
133 			 * unit, otherwise we'll print and apply changes to the
134 			 * old one.
135 			 */
136 			(void)mixer_close(m);
137 			if ((m = mixer_open(NULL)) == NULL)
138 				errx(1, "cannot open default mixer");
139 			initctls(m);
140 		}
141 	}
142 	if (sflag) {
143 		printrecsrc(m, oflag);
144 		(void)mixer_close(m);
145 		return (0);
146 	}
147 
148 parse:
149 	while (argc > 0) {
150 		char *orig;
151 
152 		if ((orig = p = strdup(*argv)) == NULL)
153 			err(1, "strdup(%s)", *argv);
154 
155 		/* Check if we're using the shorthand syntax for volume setting. */
156 		shorthand = 0;
157 		for (q = p; *q != '\0'; q++) {
158 			if (*q == '=') {
159 				q++;
160 				shorthand = ((*q >= '0' && *q <= '9') ||
161 				    *q == '+' || *q == '-' || *q == '.');
162 				break;
163 			} else if (*q == '.')
164 				break;
165 		}
166 
167 		/* Split the string into device, control and value. */
168 		devstr = strsep(&p, ".=");
169 		if ((m->dev = mixer_get_dev_byname(m, devstr)) == NULL) {
170 			warnx("%s: no such device", devstr);
171 			goto next;
172 		}
173 		/* Input: `dev`. */
174 		if (p == NULL) {
175 			printdev(m, 1);
176 			pall = 0;
177 			goto next;
178 		} else if (shorthand) {
179 			/*
180 			 * Input: `dev=N` -> shorthand for `dev.volume=N`.
181 			 *
182 			 * We don't care what the rest of the string contains as
183 			 * long as we're sure the very beginning is right,
184 			 * mod_volume() will take care of parsing it properly.
185 			 */
186 			cp = mixer_get_ctl(m->dev, C_VOL);
187 			cp->mod(cp->parent_dev, p);
188 			goto next;
189 		}
190 		ctlstr = strsep(&p, "=");
191 		if ((cp = mixer_get_ctl_byname(m->dev, ctlstr)) == NULL) {
192 			warnx("%s.%s: no such control", devstr, ctlstr);
193 			goto next;
194 		}
195 		/* Input: `dev.control`. */
196 		if (p == NULL) {
197 			(void)cp->print(cp->parent_dev, cp->name);
198 			pall = 0;
199 			goto next;
200 		}
201 		valstr = p;
202 		/* Input: `dev.control=val`. */
203 		cp->mod(cp->parent_dev, valstr);
204 next:
205 		free(orig);
206 		argc--;
207 		argv++;
208 	}
209 
210 	if (pall)
211 		printall(m, oflag);
212 	(void)mixer_close(m);
213 
214 	return (0);
215 }
216 
217 static void __dead2
218 usage(void)
219 {
220 	fprintf(stderr, "usage: %1$s [-f device] [-d pcmN | N "
221 	    "[-V voss_device:mode]] [-os] [dev[.control[=value]]] ...\n"
222 	    "       %1$s [-os] -a\n"
223 	    "       %1$s -h\n", getprogname());
224 	exit(1);
225 }
226 
227 static void
228 initctls(struct mixer *m)
229 {
230 	struct mix_dev *dp;
231 	int rc = 0;
232 
233 	TAILQ_FOREACH(dp, &m->devs, devs) {
234 		rc += mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume);
235 		rc += mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute);
236 		rc += mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc);
237 	}
238 	if (rc) {
239 		(void)mixer_close(m);
240 		errx(1, "cannot make mixer controls");
241 	}
242 }
243 
244 static void
245 printall(struct mixer *m, int oflag)
246 {
247 	struct mix_dev *dp;
248 
249 	printminfo(m, oflag);
250 	TAILQ_FOREACH(dp, &m->devs, devs) {
251 		m->dev = dp;
252 		printdev(m, oflag);
253 	}
254 }
255 
256 static void
257 printminfo(struct mixer *m, int oflag)
258 {
259 	int playrec = MIX_MODE_PLAY | MIX_MODE_REC;
260 
261 	if (oflag)
262 		return;
263 	printf("%s:", m->mi.name);
264 	if (*m->ci.longname != '\0')
265 		printf(" <%s>", m->ci.longname);
266 	if (*m->ci.hw_info != '\0')
267 		printf(" %s", m->ci.hw_info);
268 
269 	if (m->mode != 0)
270 		printf(" (");
271 	if (m->mode & MIX_MODE_PLAY)
272 		printf("play");
273 	if ((m->mode & playrec) == playrec)
274 		printf("/");
275 	if (m->mode & MIX_MODE_REC)
276 		printf("rec");
277 	if (m->mode != 0)
278 		printf(")");
279 
280 	if (m->f_default)
281 		printf(" (default)");
282 	printf("\n");
283 }
284 
285 static void
286 printdev(struct mixer *m, int oflag)
287 {
288 	struct mix_dev *d = m->dev;
289 	mix_ctl_t *cp;
290 
291 	if (!oflag) {
292 		printf("    %-10s= %.2f:%.2f    ",
293 		    d->name, d->vol.left, d->vol.right);
294 		if (!MIX_ISREC(m, d->devno))
295 			printf(" pbk");
296 		if (MIX_ISREC(m, d->devno))
297 			printf(" rec");
298 		if (MIX_ISRECSRC(m, d->devno))
299 			printf(" src");
300 		if (MIX_ISMUTE(m, d->devno))
301 			printf(" mute");
302 		printf("\n");
303 	} else {
304 		TAILQ_FOREACH(cp, &d->ctls, ctls) {
305 			(void)cp->print(cp->parent_dev, cp->name);
306 		}
307 	}
308 }
309 
310 static void
311 printrecsrc(struct mixer *m, int oflag)
312 {
313 	struct mix_dev *dp;
314 	int n = 0;
315 
316 	if (!m->recmask)
317 		return;
318 	if (!oflag)
319 		printf("%s: ", m->mi.name);
320 	TAILQ_FOREACH(dp, &m->devs, devs) {
321 		if (MIX_ISRECSRC(m, dp->devno)) {
322 			if (n++ && !oflag)
323 				printf(", ");
324 			printf("%s", dp->name);
325 			if (oflag)
326 				printf(".%s=+%s",
327 				    mixer_get_ctl(dp, C_SRC)->name, n ? " " : "");
328 		}
329 	}
330 	printf("\n");
331 }
332 
333 static int
334 set_dunit(struct mixer *m, int dunit, char *vctl)
335 {
336 	const char *opt;
337 	char *dev, *mode;
338 	char buf[32];
339 	size_t size;
340 	int n, rc;
341 
342 	/*
343 	 * Issue warning in case of hw.snd.basename_clone being unset. Omit the
344 	 * check and warning if the -V flag is used, since the user is most
345 	 * likely to be aware of this, and the warning might be confusing.
346 	 */
347 	if (vctl == NULL) {
348 		size = sizeof(int);
349 		if (sysctlbyname("hw.snd.basename_clone", &n, &size,
350 		    NULL, 0) < 0) {
351 			warn("hw.snd.basename_clone failed");
352 			return (-1);
353 		}
354 		if (n == 0) {
355 			warnx("warning: hw.snd.basename_clone not set. "
356 			    "/dev/dsp is managed externally and does not "
357 			    "change with the default unit change here.");
358 		}
359 	}
360 
361 	if ((n = mixer_get_dunit()) < 0) {
362 		warn("cannot get default unit");
363 		return (-1);
364 	}
365 	if (mixer_set_dunit(m, dunit) < 0) {
366 		warn("cannot set default unit to %d", dunit);
367 		return (-1);
368 	}
369 	printf("default_unit: %d -> %d\n", n, dunit);
370 
371 	/* Hot-swap in case virtual_oss exists and is running. */
372 	if (vctl != NULL) {
373 		dev = strsep(&vctl, ":");
374 		mode = vctl;
375 		if (dev == NULL || mode == NULL) {
376 			warnx("voss_device:mode tuple incomplete");
377 			return (-1);
378 		}
379 		if (strcmp(mode, "all") == 0)
380 			opt = "-f";
381 		else if (strcmp(mode, "play") == 0)
382 			opt = "-P";
383 		else if (strcmp(mode, "rec") == 0)
384 			opt = "-R";
385 		else {
386 			warnx("please use one of the following modes: "
387 			    "all, play, rec");
388 			return (-1);
389 		}
390 		snprintf(buf, sizeof(buf), "/dev/dsp%d", dunit);
391 		switch (fork()) {
392 		case -1:
393 			warn("fork");
394 			break;
395 		case 0:
396 			rc = execl("/usr/local/sbin/virtual_oss_cmd",
397 			    "virtual_oss_cmd", dev, opt, buf, NULL);
398 			if (rc < 0)
399 				warn("virtual_oss_cmd");
400 			_exit(0);
401 		default:
402 			if (wait(NULL) < 0)
403 				warn("wait");
404 			break;
405 		}
406 	}
407 
408 	return (0);
409 }
410 
411 static int
412 mod_volume(struct mix_dev *d, void *p)
413 {
414 	struct mixer *m;
415 	mix_ctl_t *cp;
416 	mix_volume_t v;
417 	const char *val;
418 	char *endp, lstr[8], rstr[8];
419 	float lprev, rprev, lrel, rrel;
420 	int n;
421 
422 	m = d->parent_mixer;
423 	cp = mixer_get_ctl(m->dev, C_VOL);
424 	val = p;
425 	n = sscanf(val, "%7[^:]:%7s", lstr, rstr);
426 	if (n == EOF) {
427 		warnx("invalid volume value: %s", val);
428 		return (-1);
429 	}
430 	lrel = rrel = 0;
431 	if (n > 0) {
432 		if (*lstr == '+' || *lstr == '-')
433 			lrel = 1;
434 		v.left = strtof(lstr, &endp);
435 		if (*endp != '\0' && (*endp != '%' || *(endp + 1) != '\0')) {
436 			warnx("invalid volume value: %s", lstr);
437 			return (-1);
438 		}
439 
440 		if (*endp == '%')
441 			v.left /= 100.0f;
442 	}
443 	if (n > 1) {
444 		if (*rstr == '+' || *rstr == '-')
445 			rrel = 1;
446 		v.right = strtof(rstr, &endp);
447 		if (*endp != '\0' && (*endp != '%' || *(endp + 1) != '\0')) {
448 			warnx("invalid volume value: %s", rstr);
449 			return (-1);
450 		}
451 
452 		if (*endp == '%')
453 			v.right /= 100.0f;
454 	}
455 	switch (n) {
456 	case 1:
457 		v.right = v.left; /* FALLTHROUGH */
458 		rrel = lrel;
459 	case 2:
460 		if (lrel)
461 			v.left += m->dev->vol.left;
462 		if (rrel)
463 			v.right += m->dev->vol.right;
464 
465 		if (v.left < MIX_VOLMIN)
466 			v.left = MIX_VOLMIN;
467 		else if (v.left > MIX_VOLMAX)
468 			v.left = MIX_VOLMAX;
469 		if (v.right < MIX_VOLMIN)
470 			v.right = MIX_VOLMIN;
471 		else if (v.right > MIX_VOLMAX)
472 			v.right = MIX_VOLMAX;
473 
474 		lprev = m->dev->vol.left;
475 		rprev = m->dev->vol.right;
476 		if (mixer_set_vol(m, v) < 0)
477 			warn("%s.%s=%.2f:%.2f",
478 			    m->dev->name, cp->name, v.left, v.right);
479 		else
480 			printf("%s.%s: %.2f:%.2f -> %.2f:%.2f\n",
481 			   m->dev->name, cp->name, lprev, rprev, v.left, v.right);
482 	}
483 
484 	return (0);
485 }
486 
487 static int
488 mod_mute(struct mix_dev *d, void *p)
489 {
490 	struct mixer *m;
491 	mix_ctl_t *cp;
492 	const char *val;
493 	int n, opt = -1;
494 
495 	m = d->parent_mixer;
496 	cp = mixer_get_ctl(m->dev, C_MUT);
497 	val = p;
498 	if (strncmp(val, "off", strlen(val)) == 0 ||
499 	    strncmp(val, "0", strlen(val)) == 0)
500 		opt = MIX_UNMUTE;
501 	else if (strncmp(val, "on", strlen(val)) == 0 ||
502 	    strncmp(val, "1", strlen(val)) == 0)
503 		opt = MIX_MUTE;
504 	else if (strncmp(val, "toggle", strlen(val)) == 0 ||
505 	    strncmp(val, "^", strlen(val)) == 0)
506 		opt = MIX_TOGGLEMUTE;
507 	else {
508 		warnx("%s: no such modifier", val);
509 		return (-1);
510 	}
511 	n = MIX_ISMUTE(m, m->dev->devno);
512 	if (mixer_set_mute(m, opt) < 0)
513 		warn("%s.%s=%s", m->dev->name, cp->name, val);
514 	else
515 		printf("%s.%s: %s -> %s\n",
516 		    m->dev->name, cp->name,
517 		    n ? "on" : "off",
518 		    MIX_ISMUTE(m, m->dev->devno) ? "on" : "off");
519 
520 	return (0);
521 }
522 
523 static int
524 mod_recsrc(struct mix_dev *d, void *p)
525 {
526 	struct mixer *m;
527 	mix_ctl_t *cp;
528 	const char *val;
529 	int n, opt = -1;
530 
531 	m = d->parent_mixer;
532 	cp = mixer_get_ctl(m->dev, C_SRC);
533 	val = p;
534 	if (strncmp(val, "add", strlen(val)) == 0 ||
535 	    strncmp(val, "+", strlen(val)) == 0)
536 		opt = MIX_ADDRECSRC;
537 	else if (strncmp(val, "remove", strlen(val)) == 0 ||
538 	    strncmp(val, "-", strlen(val)) == 0)
539 		opt = MIX_REMOVERECSRC;
540 	else if (strncmp(val, "set", strlen(val)) == 0 ||
541 	    strncmp(val, "=", strlen(val)) == 0)
542 		opt = MIX_SETRECSRC;
543 	else if (strncmp(val, "toggle", strlen(val)) == 0 ||
544 	    strncmp(val, "^", strlen(val)) == 0)
545 		opt = MIX_TOGGLERECSRC;
546 	else {
547 		warnx("%s: no such modifier", val);
548 		return (-1);
549 	}
550 	n = MIX_ISRECSRC(m, m->dev->devno);
551 	if (mixer_mod_recsrc(m, opt) < 0)
552 		warn("%s.%s=%s", m->dev->name, cp->name, val);
553 	else
554 		printf("%s.%s: %s -> %s\n",
555 		    m->dev->name, cp->name,
556 		    n ? "add" : "remove",
557 		    MIX_ISRECSRC(m, m->dev->devno) ? "add" : "remove");
558 
559 	return (0);
560 }
561 
562 static int
563 print_volume(struct mix_dev *d, void *p)
564 {
565 	struct mixer *m = d->parent_mixer;
566 	const char *ctl_name = p;
567 
568 	printf("%s.%s=%.2f:%.2f\n",
569 	    m->dev->name, ctl_name, m->dev->vol.left, m->dev->vol.right);
570 
571 	return (0);
572 }
573 
574 static int
575 print_mute(struct mix_dev *d, void *p)
576 {
577 	struct mixer *m = d->parent_mixer;
578 	const char *ctl_name = p;
579 
580 	printf("%s.%s=%s\n", m->dev->name, ctl_name,
581 	    MIX_ISMUTE(m, m->dev->devno) ? "on" : "off");
582 
583 	return (0);
584 }
585 
586 static int
587 print_recsrc(struct mix_dev *d, void *p)
588 {
589 	struct mixer *m = d->parent_mixer;
590 	const char *ctl_name = p;
591 
592 	if (!MIX_ISRECSRC(m, m->dev->devno))
593 		return (-1);
594 	printf("%s.%s=add\n", m->dev->name, ctl_name);
595 
596 	return (0);
597 }
598