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