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