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 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 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 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 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 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 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 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 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 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 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 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 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 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 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