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)snprintf(buf, sizeof(buf), "/dev/mixer%d", i); 101 if ((m = mixer_open(buf)) == NULL) 102 errx(1, "%s: no such mixer", buf); 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 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); 196 next: 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 209 static void __dead2 210 usage(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 218 static void 219 initctls(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 235 static void 236 printall(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 247 static void 248 printminfo(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 276 static void 277 printdev(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 301 static void 302 printrecsrc(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 324 static int 325 set_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 342 static int 343 mod_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 418 static int 419 mod_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 451 static int 452 mod_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 486 static int 487 print_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 498 static int 499 print_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 510 static int 511 print_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