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