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 <sys/sysctl.h> 24 #include <sys/wait.h> 25 26 #include <err.h> 27 #include <errno.h> 28 #include <mixer.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <unistd.h> 33 34 enum { 35 C_VOL = 0, 36 C_MUT, 37 C_SRC, 38 }; 39 40 static void usage(void) __dead2; 41 static void initctls(struct mixer *); 42 static void printall(struct mixer *, int); 43 static void printminfo(struct mixer *, int); 44 static void printdev(struct mixer *, int); 45 static void printrecsrc(struct mixer *, int); /* XXX: change name */ 46 static int set_dunit(struct mixer *, int, char *); 47 /* Control handlers */ 48 static int mod_volume(struct mix_dev *, void *); 49 static int mod_mute(struct mix_dev *, void *); 50 static int mod_recsrc(struct mix_dev *, void *); 51 static int print_volume(struct mix_dev *, void *); 52 static int print_mute(struct mix_dev *, void *); 53 static int print_recsrc(struct mix_dev *, void *); 54 55 int 56 main(int argc, char *argv[]) 57 { 58 struct mixer *m; 59 mix_ctl_t *cp; 60 char *name = NULL, buf[NAME_MAX], *vctl = NULL; 61 char *p, *q, *devstr, *ctlstr, *valstr = NULL; 62 int dunit, i, n, pall = 1, shorthand; 63 int aflag = 0, dflag = 0, oflag = 0, sflag = 0; 64 int ch; 65 66 while ((ch = getopt(argc, argv, "ad:f:hosV:")) != -1) { 67 switch (ch) { 68 case 'a': 69 aflag = 1; 70 break; 71 case 'd': 72 if (strncmp(optarg, "pcm", 3) == 0) 73 optarg += 3; 74 errno = 0; 75 dunit = strtol(optarg, NULL, 10); 76 if (errno == EINVAL || errno == ERANGE) 77 err(1, "strtol(%s)", optarg); 78 dflag = 1; 79 break; 80 case 'f': 81 name = optarg; 82 break; 83 case 'o': 84 oflag = 1; 85 break; 86 case 's': 87 sflag = 1; 88 break; 89 case 'V': 90 vctl = optarg; 91 break; 92 case 'h': /* FALLTHROUGH */ 93 case '?': 94 default: 95 usage(); 96 } 97 } 98 argc -= optind; 99 argv += optind; 100 101 /* Print all mixers and exit. */ 102 if (aflag) { 103 if ((n = mixer_get_nmixers()) < 0) 104 errx(1, "no mixers present in the system"); 105 for (i = 0; i < n; i++) { 106 (void)mixer_get_path(buf, sizeof(buf), i); 107 if ((m = mixer_open(buf)) == NULL) 108 continue; 109 initctls(m); 110 if (sflag) 111 printrecsrc(m, oflag); 112 else { 113 printall(m, oflag); 114 if (oflag) 115 printf("\n"); 116 } 117 (void)mixer_close(m); 118 } 119 return (0); 120 } 121 122 if ((m = mixer_open(name)) == NULL) 123 errx(1, "%s: no such mixer", name); 124 125 initctls(m); 126 127 if (dflag) { 128 if (set_dunit(m, dunit, vctl) < 0) 129 goto parse; 130 else { 131 /* 132 * Open current mixer since we changed the default 133 * unit, otherwise we'll print and apply changes to the 134 * old one. 135 */ 136 (void)mixer_close(m); 137 if ((m = mixer_open(NULL)) == NULL) 138 errx(1, "cannot open default mixer"); 139 initctls(m); 140 } 141 } 142 if (sflag) { 143 printrecsrc(m, oflag); 144 (void)mixer_close(m); 145 return (0); 146 } 147 148 parse: 149 while (argc > 0) { 150 if ((p = strdup(*argv)) == NULL) 151 err(1, "strdup(%s)", *argv); 152 153 /* Check if we're using the shorthand syntax for volume setting. */ 154 shorthand = 0; 155 for (q = p; *q != '\0'; q++) { 156 if (*q == '=') { 157 q++; 158 shorthand = ((*q >= '0' && *q <= '9') || 159 *q == '+' || *q == '-' || *q == '.'); 160 break; 161 } else if (*q == '.') 162 break; 163 } 164 165 /* Split the string into device, control and value. */ 166 devstr = strsep(&p, ".="); 167 if ((m->dev = mixer_get_dev_byname(m, devstr)) == NULL) { 168 warnx("%s: no such device", devstr); 169 goto next; 170 } 171 /* Input: `dev`. */ 172 if (p == NULL) { 173 printdev(m, 1); 174 pall = 0; 175 goto next; 176 } else if (shorthand) { 177 /* 178 * Input: `dev=N` -> shorthand for `dev.volume=N`. 179 * 180 * We don't care what the rest of the string contains as 181 * long as we're sure the very beginning is right, 182 * mod_volume() will take care of parsing it properly. 183 */ 184 cp = mixer_get_ctl(m->dev, C_VOL); 185 cp->mod(cp->parent_dev, p); 186 goto next; 187 } 188 ctlstr = strsep(&p, "="); 189 if ((cp = mixer_get_ctl_byname(m->dev, ctlstr)) == NULL) { 190 warnx("%s.%s: no such control", devstr, ctlstr); 191 goto next; 192 } 193 /* Input: `dev.control`. */ 194 if (p == NULL) { 195 (void)cp->print(cp->parent_dev, cp->name); 196 pall = 0; 197 goto next; 198 } 199 valstr = p; 200 /* Input: `dev.control=val`. */ 201 cp->mod(cp->parent_dev, valstr); 202 next: 203 free(p); 204 argc--; 205 argv++; 206 } 207 208 if (pall) 209 printall(m, oflag); 210 (void)mixer_close(m); 211 212 return (0); 213 } 214 215 static void __dead2 216 usage(void) 217 { 218 fprintf(stderr, "usage: %1$s [-f device] [-d pcmN | N " 219 "[-V voss_device:mode]] [-os] [dev[.control[=value]]] ...\n" 220 " %1$s [-os] -a\n" 221 " %1$s -h\n", getprogname()); 222 exit(1); 223 } 224 225 static void 226 initctls(struct mixer *m) 227 { 228 struct mix_dev *dp; 229 int rc = 0; 230 231 TAILQ_FOREACH(dp, &m->devs, devs) { 232 rc += mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume); 233 rc += mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute); 234 rc += mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc); 235 } 236 if (rc) { 237 (void)mixer_close(m); 238 errx(1, "cannot make mixer controls"); 239 } 240 } 241 242 static void 243 printall(struct mixer *m, int oflag) 244 { 245 struct mix_dev *dp; 246 247 printminfo(m, oflag); 248 TAILQ_FOREACH(dp, &m->devs, devs) { 249 m->dev = dp; 250 printdev(m, oflag); 251 } 252 } 253 254 static void 255 printminfo(struct mixer *m, int oflag) 256 { 257 int playrec = MIX_MODE_PLAY | MIX_MODE_REC; 258 259 if (oflag) 260 return; 261 printf("%s:", m->mi.name); 262 if (*m->ci.longname != '\0') 263 printf(" <%s>", m->ci.longname); 264 if (*m->ci.hw_info != '\0') 265 printf(" %s", m->ci.hw_info); 266 267 if (m->mode != 0) 268 printf(" ("); 269 if (m->mode & MIX_MODE_PLAY) 270 printf("play"); 271 if ((m->mode & playrec) == playrec) 272 printf("/"); 273 if (m->mode & MIX_MODE_REC) 274 printf("rec"); 275 if (m->mode != 0) 276 printf(")"); 277 278 if (m->f_default) 279 printf(" (default)"); 280 printf("\n"); 281 } 282 283 static void 284 printdev(struct mixer *m, int oflag) 285 { 286 struct mix_dev *d = m->dev; 287 mix_ctl_t *cp; 288 289 if (!oflag) { 290 printf(" %-10s= %.2f:%.2f ", 291 d->name, d->vol.left, d->vol.right); 292 if (!MIX_ISREC(m, d->devno)) 293 printf(" pbk"); 294 if (MIX_ISREC(m, d->devno)) 295 printf(" rec"); 296 if (MIX_ISRECSRC(m, d->devno)) 297 printf(" src"); 298 if (MIX_ISMUTE(m, d->devno)) 299 printf(" mute"); 300 printf("\n"); 301 } else { 302 TAILQ_FOREACH(cp, &d->ctls, ctls) { 303 (void)cp->print(cp->parent_dev, cp->name); 304 } 305 } 306 } 307 308 static void 309 printrecsrc(struct mixer *m, int oflag) 310 { 311 struct mix_dev *dp; 312 int n = 0; 313 314 if (!m->recmask) 315 return; 316 if (!oflag) 317 printf("%s: ", m->mi.name); 318 TAILQ_FOREACH(dp, &m->devs, devs) { 319 if (MIX_ISRECSRC(m, dp->devno)) { 320 if (n++ && !oflag) 321 printf(", "); 322 printf("%s", dp->name); 323 if (oflag) 324 printf(".%s=+%s", 325 mixer_get_ctl(dp, C_SRC)->name, n ? " " : ""); 326 } 327 } 328 printf("\n"); 329 } 330 331 static int 332 set_dunit(struct mixer *m, int dunit, char *vctl) 333 { 334 const char *opt; 335 char *dev, *mode; 336 char buf[32]; 337 size_t size; 338 int n, rc; 339 340 /* 341 * Issue warning in case of hw.snd.basename_clone being unset. Omit the 342 * check and warning if the -V flag is used, since the user is most 343 * likely to be aware of this, and the warning might be confusing. 344 */ 345 if (vctl == NULL) { 346 size = sizeof(int); 347 if (sysctlbyname("hw.snd.basename_clone", &n, &size, 348 NULL, 0) < 0) { 349 warn("hw.snd.basename_clone failed"); 350 return (-1); 351 } 352 if (n == 0) { 353 warnx("warning: hw.snd.basename_clone not set. " 354 "/dev/dsp is managed externally and does not " 355 "change with the default unit change here."); 356 } 357 } 358 359 if ((n = mixer_get_dunit()) < 0) { 360 warn("cannot get default unit"); 361 return (-1); 362 } 363 if (mixer_set_dunit(m, dunit) < 0) { 364 warn("cannot set default unit to %d", dunit); 365 return (-1); 366 } 367 printf("default_unit: %d -> %d\n", n, dunit); 368 369 /* Hot-swap in case virtual_oss exists and is running. */ 370 if (vctl != NULL) { 371 dev = strsep(&vctl, ":"); 372 mode = vctl; 373 if (dev == NULL || mode == NULL) { 374 warnx("voss_device:mode tuple incomplete"); 375 return (-1); 376 } 377 if (strcmp(mode, "all") == 0) 378 opt = "-f"; 379 else if (strcmp(mode, "play") == 0) 380 opt = "-P"; 381 else if (strcmp(mode, "rec") == 0) 382 opt = "-R"; 383 else { 384 warnx("please use one of the following modes: " 385 "all, play, rec"); 386 return (-1); 387 } 388 snprintf(buf, sizeof(buf), "/dev/dsp%d", dunit); 389 switch (fork()) { 390 case -1: 391 warn("fork"); 392 break; 393 case 0: 394 rc = execl("/usr/local/sbin/virtual_oss_cmd", 395 "virtual_oss_cmd", dev, opt, buf, NULL); 396 if (rc < 0) 397 warn("virtual_oss_cmd"); 398 _exit(0); 399 default: 400 if (wait(NULL) < 0) 401 warn("wait"); 402 break; 403 } 404 } 405 406 return (0); 407 } 408 409 static int 410 mod_volume(struct mix_dev *d, void *p) 411 { 412 struct mixer *m; 413 mix_ctl_t *cp; 414 mix_volume_t v; 415 const char *val; 416 char *endp, lstr[8], rstr[8]; 417 float lprev, rprev, lrel, rrel; 418 int n; 419 420 m = d->parent_mixer; 421 cp = mixer_get_ctl(m->dev, C_VOL); 422 val = p; 423 n = sscanf(val, "%7[^:]:%7s", lstr, rstr); 424 if (n == EOF) { 425 warnx("invalid volume value: %s", val); 426 return (-1); 427 } 428 lrel = rrel = 0; 429 if (n > 0) { 430 if (*lstr == '+' || *lstr == '-') 431 lrel = 1; 432 v.left = strtof(lstr, &endp); 433 if (*endp != '\0' && (*endp != '%' || *(endp + 1) != '\0')) { 434 warnx("invalid volume value: %s", lstr); 435 return (-1); 436 } 437 438 if (*endp == '%') 439 v.left /= 100.0f; 440 } 441 if (n > 1) { 442 if (*rstr == '+' || *rstr == '-') 443 rrel = 1; 444 v.right = strtof(rstr, &endp); 445 if (*endp != '\0' && (*endp != '%' || *(endp + 1) != '\0')) { 446 warnx("invalid volume value: %s", rstr); 447 return (-1); 448 } 449 450 if (*endp == '%') 451 v.right /= 100.0f; 452 } 453 switch (n) { 454 case 1: 455 v.right = v.left; /* FALLTHROUGH */ 456 rrel = lrel; 457 case 2: 458 if (lrel) 459 v.left += m->dev->vol.left; 460 if (rrel) 461 v.right += m->dev->vol.right; 462 463 if (v.left < MIX_VOLMIN) 464 v.left = MIX_VOLMIN; 465 else if (v.left > MIX_VOLMAX) 466 v.left = MIX_VOLMAX; 467 if (v.right < MIX_VOLMIN) 468 v.right = MIX_VOLMIN; 469 else if (v.right > MIX_VOLMAX) 470 v.right = MIX_VOLMAX; 471 472 lprev = m->dev->vol.left; 473 rprev = m->dev->vol.right; 474 if (mixer_set_vol(m, v) < 0) 475 warn("%s.%s=%.2f:%.2f", 476 m->dev->name, cp->name, v.left, v.right); 477 else 478 printf("%s.%s: %.2f:%.2f -> %.2f:%.2f\n", 479 m->dev->name, cp->name, lprev, rprev, v.left, v.right); 480 } 481 482 return (0); 483 } 484 485 static int 486 mod_mute(struct mix_dev *d, void *p) 487 { 488 struct mixer *m; 489 mix_ctl_t *cp; 490 const char *val; 491 int n, opt = -1; 492 493 m = d->parent_mixer; 494 cp = mixer_get_ctl(m->dev, C_MUT); 495 val = p; 496 if (strncmp(val, "off", strlen(val)) == 0 || 497 strncmp(val, "0", strlen(val)) == 0) 498 opt = MIX_UNMUTE; 499 else if (strncmp(val, "on", strlen(val)) == 0 || 500 strncmp(val, "1", strlen(val)) == 0) 501 opt = MIX_MUTE; 502 else if (strncmp(val, "toggle", strlen(val)) == 0 || 503 strncmp(val, "^", strlen(val)) == 0) 504 opt = MIX_TOGGLEMUTE; 505 else { 506 warnx("%s: no such modifier", val); 507 return (-1); 508 } 509 n = MIX_ISMUTE(m, m->dev->devno); 510 if (mixer_set_mute(m, opt) < 0) 511 warn("%s.%s=%s", m->dev->name, cp->name, val); 512 else 513 printf("%s.%s: %s -> %s\n", 514 m->dev->name, cp->name, 515 n ? "on" : "off", 516 MIX_ISMUTE(m, m->dev->devno) ? "on" : "off"); 517 518 return (0); 519 } 520 521 static int 522 mod_recsrc(struct mix_dev *d, void *p) 523 { 524 struct mixer *m; 525 mix_ctl_t *cp; 526 const char *val; 527 int n, opt = -1; 528 529 m = d->parent_mixer; 530 cp = mixer_get_ctl(m->dev, C_SRC); 531 val = p; 532 if (strncmp(val, "add", strlen(val)) == 0 || 533 strncmp(val, "+", strlen(val)) == 0) 534 opt = MIX_ADDRECSRC; 535 else if (strncmp(val, "remove", strlen(val)) == 0 || 536 strncmp(val, "-", strlen(val)) == 0) 537 opt = MIX_REMOVERECSRC; 538 else if (strncmp(val, "set", strlen(val)) == 0 || 539 strncmp(val, "=", strlen(val)) == 0) 540 opt = MIX_SETRECSRC; 541 else if (strncmp(val, "toggle", strlen(val)) == 0 || 542 strncmp(val, "^", strlen(val)) == 0) 543 opt = MIX_TOGGLERECSRC; 544 else { 545 warnx("%s: no such modifier", val); 546 return (-1); 547 } 548 n = MIX_ISRECSRC(m, m->dev->devno); 549 if (mixer_mod_recsrc(m, opt) < 0) 550 warn("%s.%s=%s", m->dev->name, cp->name, val); 551 else 552 printf("%s.%s: %s -> %s\n", 553 m->dev->name, cp->name, 554 n ? "add" : "remove", 555 MIX_ISRECSRC(m, m->dev->devno) ? "add" : "remove"); 556 557 return (0); 558 } 559 560 static int 561 print_volume(struct mix_dev *d, void *p) 562 { 563 struct mixer *m = d->parent_mixer; 564 const char *ctl_name = p; 565 566 printf("%s.%s=%.2f:%.2f\n", 567 m->dev->name, ctl_name, m->dev->vol.left, m->dev->vol.right); 568 569 return (0); 570 } 571 572 static int 573 print_mute(struct mix_dev *d, void *p) 574 { 575 struct mixer *m = d->parent_mixer; 576 const char *ctl_name = p; 577 578 printf("%s.%s=%s\n", m->dev->name, ctl_name, 579 MIX_ISMUTE(m, m->dev->devno) ? "on" : "off"); 580 581 return (0); 582 } 583 584 static int 585 print_recsrc(struct mix_dev *d, void *p) 586 { 587 struct mixer *m = d->parent_mixer; 588 const char *ctl_name = p; 589 590 if (!MIX_ISRECSRC(m, m->dev->devno)) 591 return (-1); 592 printf("%s.%s=add\n", m->dev->name, ctl_name); 593 594 return (0); 595 } 596