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