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