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