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