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