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