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