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