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 * $FreeBSD$ 23 */ 24 25 #include <err.h> 26 #include <errno.h> 27 #include <mixer.h> 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <unistd.h> 32 33 enum { 34 C_VOL = 0, 35 C_MUT, 36 C_SRC, 37 }; 38 39 static void usage(void) __dead2; 40 static void initctls(struct mixer *); 41 static void printall(struct mixer *, int); 42 static void printminfo(struct mixer *, int); 43 static void printdev(struct mixer *, int); 44 static void printrecsrc(struct mixer *, int); /* XXX: change name */ 45 /* Control handlers */ 46 static int mod_dunit(struct mix_dev *, void *); 47 static int mod_volume(struct mix_dev *, void *); 48 static int mod_mute(struct mix_dev *, void *); 49 static int mod_recsrc(struct mix_dev *, void *); 50 static int print_volume(struct mix_dev *, void *); 51 static int print_mute(struct mix_dev *, void *); 52 static int print_recsrc(struct mix_dev *, void *); 53 54 static const mix_ctl_t ctl_dunit = { 55 .parent_dev = NULL, 56 .id = -1, 57 .name = "default_unit", 58 .mod = mod_dunit, 59 .print = NULL 60 }; 61 62 int 63 main(int argc, char *argv[]) 64 { 65 struct mixer *m; 66 mix_ctl_t *cp; 67 char *name = NULL, buf[NAME_MAX]; 68 char *p, *q, *devstr, *ctlstr, *valstr = NULL; 69 int dunit, i, n, pall = 1, shorthand; 70 int aflag = 0, dflag = 0, oflag = 0, sflag = 0; 71 int ch; 72 73 while ((ch = getopt(argc, argv, "ad:f:hos")) != -1) { 74 switch (ch) { 75 case 'a': 76 aflag = 1; 77 break; 78 case 'd': 79 dunit = strtol(optarg, NULL, 10); 80 if (errno == EINVAL || errno == ERANGE) 81 err(1, "strtol"); 82 dflag = 1; 83 break; 84 case 'f': 85 name = optarg; 86 break; 87 case 'o': 88 oflag = 1; 89 break; 90 case 's': 91 sflag = 1; 92 break; 93 case 'h': /* FALLTHROUGH */ 94 case '?': 95 default: 96 usage(); 97 } 98 } 99 argc -= optind; 100 argv += optind; 101 102 /* Print all mixers and exit. */ 103 if (aflag) { 104 if ((n = mixer_get_nmixers()) < 0) 105 err(1, "mixer_get_nmixers"); 106 for (i = 0; i < n; i++) { 107 (void)snprintf(buf, sizeof(buf), "/dev/mixer%d", i); 108 if ((m = mixer_open(buf)) == NULL) 109 err(1, "mixer_open: %s", buf); 110 initctls(m); 111 if (sflag) 112 printrecsrc(m, oflag); 113 else { 114 printall(m, oflag); 115 if (oflag) 116 printf("\n"); 117 } 118 (void)mixer_close(m); 119 } 120 return (0); 121 } 122 123 if ((m = mixer_open(name)) == NULL) 124 err(1, "mixer_open: %s", name); 125 126 initctls(m); 127 128 if (dflag && ctl_dunit.mod(m->dev, &dunit) < 0) 129 goto parse; 130 if (sflag) { 131 printrecsrc(m, oflag); 132 (void)mixer_close(m); 133 return (0); 134 } 135 136 parse: 137 while (argc > 0) { 138 if ((p = strdup(*argv)) == NULL) 139 err(1, "strdup(%s)", *argv); 140 141 /* Check if we're using the shorthand syntax for volume setting. */ 142 shorthand = 0; 143 for (q = p; *q != '\0'; q++) { 144 if (*q == '=') { 145 q++; 146 shorthand = ((*q >= '0' && *q <= '9') || 147 *q == '+' || *q == '-' || *q == '.'); 148 break; 149 } else if (*q == '.') 150 break; 151 } 152 153 /* Split the string into device, control and value. */ 154 devstr = strsep(&p, ".="); 155 if ((m->dev = mixer_get_dev_byname(m, devstr)) == NULL) { 156 warnx("%s: no such device", devstr); 157 goto next; 158 } 159 /* Input: `dev`. */ 160 if (p == NULL) { 161 printdev(m, 1); 162 pall = 0; 163 goto next; 164 } else if (shorthand) { 165 /* 166 * Input: `dev=N` -> shorthand for `dev.volume=N`. 167 * 168 * We don't care what the rest of the string contains as 169 * long as we're sure the very beginning is right, 170 * mod_volume() will take care of parsing it properly. 171 */ 172 cp = mixer_get_ctl(m->dev, C_VOL); 173 cp->mod(cp->parent_dev, p); 174 goto next; 175 } 176 ctlstr = strsep(&p, "="); 177 if ((cp = mixer_get_ctl_byname(m->dev, ctlstr)) == NULL) { 178 warnx("%s.%s: no such control", devstr, ctlstr); 179 goto next; 180 } 181 /* Input: `dev.control`. */ 182 if (p == NULL) { 183 (void)cp->print(cp->parent_dev, cp->name); 184 pall = 0; 185 goto next; 186 } 187 valstr = p; 188 /* Input: `dev.control=val`. */ 189 cp->mod(cp->parent_dev, valstr); 190 next: 191 free(p); 192 argc--; 193 argv++; 194 } 195 196 if (pall) 197 printall(m, oflag); 198 (void)mixer_close(m); 199 200 return (0); 201 } 202 203 static void __dead2 204 usage(void) 205 { 206 fprintf(stderr, "usage: %1$s [-f device] [-d unit] [-os] [dev[.control[=value]]] ...\n" 207 " %1$s [-d unit] [-os] -a\n" 208 " %1$s -h\n", getprogname()); 209 exit(1); 210 } 211 212 static void 213 initctls(struct mixer *m) 214 { 215 struct mix_dev *dp; 216 int rc = 0; 217 218 TAILQ_FOREACH(dp, &m->devs, devs) { 219 rc += mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume); 220 rc += mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute); 221 rc += mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc); 222 } 223 if (rc) { 224 (void)mixer_close(m); 225 err(1, "cannot make controls"); 226 } 227 } 228 229 static void 230 printall(struct mixer *m, int oflag) 231 { 232 struct mix_dev *dp; 233 234 printminfo(m, oflag); 235 TAILQ_FOREACH(dp, &m->devs, devs) { 236 m->dev = dp; 237 printdev(m, oflag); 238 } 239 } 240 241 static void 242 printminfo(struct mixer *m, int oflag) 243 { 244 int playrec = MIX_MODE_PLAY | MIX_MODE_REC; 245 246 if (oflag) 247 return; 248 printf("%s:", m->mi.name); 249 if (*m->ci.longname != '\0') 250 printf(" <%s>", m->ci.longname); 251 if (*m->ci.hw_info != '\0') 252 printf(" %s", m->ci.hw_info); 253 254 if (m->mode != 0) 255 printf(" ("); 256 if (m->mode & MIX_MODE_PLAY) 257 printf("play"); 258 if ((m->mode & playrec) == playrec) 259 printf("/"); 260 if (m->mode & MIX_MODE_REC) 261 printf("rec"); 262 if (m->mode != 0) 263 printf(")"); 264 265 if (m->f_default) 266 printf(" (default)"); 267 printf("\n"); 268 } 269 270 static void 271 printdev(struct mixer *m, int oflag) 272 { 273 struct mix_dev *d = m->dev; 274 mix_ctl_t *cp; 275 276 if (!oflag) { 277 printf(" %-10s= %.2f:%.2f ", 278 d->name, d->vol.left, d->vol.right); 279 if (!MIX_ISREC(m, d->devno)) 280 printf(" pbk"); 281 if (MIX_ISREC(m, d->devno)) 282 printf(" rec"); 283 if (MIX_ISRECSRC(m, d->devno)) 284 printf(" src"); 285 if (MIX_ISMUTE(m, d->devno)) 286 printf(" mute"); 287 printf("\n"); 288 } else { 289 TAILQ_FOREACH(cp, &d->ctls, ctls) { 290 (void)cp->print(cp->parent_dev, cp->name); 291 } 292 } 293 } 294 295 static void 296 printrecsrc(struct mixer *m, int oflag) 297 { 298 struct mix_dev *dp; 299 int n = 0; 300 301 if (!m->recmask) 302 return; 303 if (!oflag) 304 printf("%s: ", m->mi.name); 305 TAILQ_FOREACH(dp, &m->devs, devs) { 306 if (MIX_ISRECSRC(m, dp->devno)) { 307 if (n++ && !oflag) 308 printf(", "); 309 printf("%s", dp->name); 310 if (oflag) 311 printf(".%s=+%s", 312 mixer_get_ctl(dp, C_SRC)->name, n ? " " : ""); 313 } 314 } 315 printf("\n"); 316 } 317 318 static int 319 mod_dunit(struct mix_dev *d, void *p) 320 { 321 int dunit = *((int *)p); 322 int n; 323 324 if ((n = mixer_get_dunit()) < 0) { 325 warn("cannot get default unit"); 326 return (-1); 327 } 328 if (mixer_set_dunit(d->parent_mixer, dunit) < 0) { 329 warn("cannot set default unit to: %d", dunit); 330 return (-1); 331 } 332 printf("%s: %d -> %d\n", ctl_dunit.name, n, dunit); 333 334 return (0); 335 } 336 337 static int 338 mod_volume(struct mix_dev *d, void *p) 339 { 340 struct mixer *m; 341 mix_ctl_t *cp; 342 mix_volume_t v; 343 const char *val; 344 char *endp, lstr[8], rstr[8]; 345 float lprev, rprev, lrel, rrel; 346 int n; 347 348 m = d->parent_mixer; 349 cp = mixer_get_ctl(m->dev, C_VOL); 350 val = p; 351 n = sscanf(val, "%7[^:]:%7s", lstr, rstr); 352 if (n == EOF) { 353 warnx("invalid volume value: %s", val); 354 return (-1); 355 } 356 lrel = rrel = 0; 357 if (n > 0) { 358 if (*lstr == '+' || *lstr == '-') 359 lrel = 1; 360 v.left = strtof(lstr, &endp); 361 if (*endp != '\0' && (*endp != '%' || *(endp + 1) != '\0')) { 362 warnx("invalid volume value: %s", lstr); 363 return (-1); 364 } 365 366 if (*endp == '%') 367 v.left /= 100.0f; 368 } 369 if (n > 1) { 370 if (*rstr == '+' || *rstr == '-') 371 rrel = 1; 372 v.right = strtof(rstr, &endp); 373 if (*endp != '\0' && (*endp != '%' || *(endp + 1) != '\0')) { 374 warnx("invalid volume value: %s", rstr); 375 return (-1); 376 } 377 378 if (*endp == '%') 379 v.right /= 100.0f; 380 } 381 switch (n) { 382 case 1: 383 v.right = v.left; /* FALLTHROUGH */ 384 rrel = lrel; 385 case 2: 386 if (lrel) 387 v.left += m->dev->vol.left; 388 if (rrel) 389 v.right += m->dev->vol.right; 390 391 if (v.left < MIX_VOLMIN) 392 v.left = MIX_VOLMIN; 393 else if (v.left > MIX_VOLMAX) 394 v.left = MIX_VOLMAX; 395 if (v.right < MIX_VOLMIN) 396 v.right = MIX_VOLMIN; 397 else if (v.right > MIX_VOLMAX) 398 v.right = MIX_VOLMAX; 399 400 lprev = m->dev->vol.left; 401 rprev = m->dev->vol.right; 402 if (mixer_set_vol(m, v) < 0) 403 warn("%s.%s=%.2f:%.2f", 404 m->dev->name, cp->name, v.left, v.right); 405 else 406 printf("%s.%s: %.2f:%.2f -> %.2f:%.2f\n", 407 m->dev->name, cp->name, lprev, rprev, v.left, v.right); 408 } 409 410 return (0); 411 } 412 413 static int 414 mod_mute(struct mix_dev *d, void *p) 415 { 416 struct mixer *m; 417 mix_ctl_t *cp; 418 const char *val; 419 int n, opt = -1; 420 421 m = d->parent_mixer; 422 cp = mixer_get_ctl(m->dev, C_MUT); 423 val = p; 424 switch (*val) { 425 case '0': 426 opt = MIX_UNMUTE; 427 break; 428 case '1': 429 opt = MIX_MUTE; 430 break; 431 case '^': 432 opt = MIX_TOGGLEMUTE; 433 break; 434 default: 435 warnx("%c: no such modifier", *val); 436 return (-1); 437 } 438 n = MIX_ISMUTE(m, m->dev->devno); 439 if (mixer_set_mute(m, opt) < 0) 440 warn("%s.%s=%c", m->dev->name, cp->name, *val); 441 else 442 printf("%s.%s: %d -> %d\n", 443 m->dev->name, cp->name, n, MIX_ISMUTE(m, m->dev->devno)); 444 445 return (0); 446 } 447 448 static int 449 mod_recsrc(struct mix_dev *d, void *p) 450 { 451 struct mixer *m; 452 mix_ctl_t *cp; 453 const char *val; 454 int n, opt = -1; 455 456 m = d->parent_mixer; 457 cp = mixer_get_ctl(m->dev, C_SRC); 458 val = p; 459 switch (*val) { 460 case '+': 461 opt = MIX_ADDRECSRC; 462 break; 463 case '-': 464 opt = MIX_REMOVERECSRC; 465 break; 466 case '=': 467 opt = MIX_SETRECSRC; 468 break; 469 case '^': 470 opt = MIX_TOGGLERECSRC; 471 break; 472 default: 473 warnx("%c: no such modifier", *val); 474 return (-1); 475 } 476 n = MIX_ISRECSRC(m, m->dev->devno); 477 if (mixer_mod_recsrc(m, opt) < 0) 478 warn("%s.%s=%c", m->dev->name, cp->name, *val); 479 else 480 printf("%s.%s: %d -> %d\n", 481 m->dev->name, cp->name, n, MIX_ISRECSRC(m, m->dev->devno)); 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=%d\n", m->dev->name, ctl_name, MIX_ISMUTE(m, m->dev->devno)); 505 506 return (0); 507 } 508 509 static int 510 print_recsrc(struct mix_dev *d, void *p) 511 { 512 struct mixer *m = d->parent_mixer; 513 const char *ctl_name = p; 514 515 if (!MIX_ISRECSRC(m, m->dev->devno)) 516 return (-1); 517 printf("%s.%s=+\n", m->dev->name, ctl_name); 518 519 return (0); 520 } 521