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
31enum {
32	C_VOL = 0,
33	C_MUT,
34	C_SRC,
35};
36
37static void usage(void) __dead2;
38static void initctls(struct mixer *);
39static void printall(struct mixer *, int);
40static void printminfo(struct mixer *, int);
41static void printdev(struct mixer *, int);
42static void printrecsrc(struct mixer *, int); /* XXX: change name */
43static int set_dunit(struct mixer *, int);
44/* Control handlers */
45static int mod_volume(struct mix_dev *, void *);
46static int mod_mute(struct mix_dev *, void *);
47static int mod_recsrc(struct mix_dev *, void *);
48static int print_volume(struct mix_dev *, void *);
49static int print_mute(struct mix_dev *, void *);
50static int print_recsrc(struct mix_dev *, void *);
51
52int
53main(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)mixer_get_path(buf, sizeof(buf), i);
101			if ((m = mixer_open(buf)) == NULL)
102				continue;
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
142parse:
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);
196next:
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
209static void __dead2
210usage(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
218static void
219initctls(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
235static void
236printall(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
247static void
248printminfo(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
276static void
277printdev(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
301static void
302printrecsrc(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
324static int
325set_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
342static int
343mod_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
418static int
419mod_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
451static int
452mod_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
486static int
487print_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
498static int
499print_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
510static int
511print_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