1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <errno.h> 29 #include <string.h> 30 #include <strings.h> 31 #include <locale.h> 32 #include <libintl.h> 33 #include <stdarg.h> 34 #include <stddef.h> 35 #include <sys/types.h> 36 #include <sys/stat.h> 37 #include <sys/mkdev.h> 38 #include <fcntl.h> 39 #include <unistd.h> 40 #include <ctype.h> 41 #include <sys/param.h> 42 #include <sys/soundcard.h> 43 44 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 45 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 46 #endif 47 48 #define _(s) gettext(s) 49 50 #define MAXLINE 1024 51 52 #define AUDIO_CTRL_STEREO_LEFT(v) ((uint8_t)((v) & 0xff)) 53 #define AUDIO_CTRL_STEREO_RIGHT(v) ((uint8_t)(((v) >> 8) & 0xff)) 54 #define AUDIO_CTRL_STEREO_VAL(l, r) (((l) & 0xff) | (((r) & 0xff) << 8)) 55 56 /* 57 * These are borrowed from sys/audio/audio_common.h, where the values 58 * are protected by _KERNEL. 59 */ 60 #define AUDIO_MN_TYPE_NBITS (4) 61 #define AUDIO_MN_TYPE_MASK ((1U << AUDIO_MN_TYPE_NBITS) - 1) 62 #define AUDIO_MINOR_MIXER (0) 63 64 65 /* 66 * Column display information 67 * All are related to the types enumerated in col_t and any change should be 68 * reflected in the corresponding indices and offsets for all the variables 69 * accordingly. Most tweaks to the display can be done by adjusting the 70 * values here. 71 */ 72 73 /* types of columns displayed */ 74 typedef enum { COL_DV = 0, COL_NM, COL_VAL, COL_SEL} col_t; 75 76 /* corresponding sizes of columns; does not include trailing null */ 77 #define COL_DV_SZ 16 78 #define COL_NM_SZ 24 79 #define COL_VAL_SZ 10 80 #define COL_SEL_SZ 20 81 #define COL_MAX_SZ 64 82 83 /* corresponding sizes of columns, indexed by col_t value */ 84 static int col_sz[] = { 85 COL_DV_SZ, COL_NM_SZ, COL_VAL_SZ, COL_SEL_SZ 86 }; 87 88 /* used by callers of the printing function */ 89 typedef struct col_prt { 90 char *col_dv; 91 char *col_nm; 92 char *col_val; 93 char *col_sel; 94 } col_prt_t; 95 96 /* columns displayed in order with vopt = 0 */ 97 static int col_dpy[] = {COL_NM, COL_VAL}; 98 static int col_dpy_len = sizeof (col_dpy) / sizeof (*col_dpy); 99 100 /* tells the printing function what members to use; follows col_dpy[] */ 101 static size_t col_dpy_prt[] = { 102 offsetof(col_prt_t, col_nm), 103 offsetof(col_prt_t, col_val), 104 }; 105 106 /* columns displayed in order with vopt = 1 */ 107 static int col_dpy_vopt[] = { COL_DV, COL_NM, COL_VAL, COL_SEL}; 108 static int col_dpy_vopt_len = sizeof (col_dpy_vopt) / sizeof (*col_dpy_vopt); 109 110 /* tells the printing function what members to use; follows col_dpy_vopt[] */ 111 static size_t col_dpy_prt_vopt[] = { 112 offsetof(col_prt_t, col_dv), 113 offsetof(col_prt_t, col_nm), 114 offsetof(col_prt_t, col_val), 115 offsetof(col_prt_t, col_sel) 116 }; 117 118 /* columns displayed in order with tofile = 1 */ 119 static int col_dpy_tofile[] = { COL_NM, COL_VAL}; 120 static int col_dpy_tofile_len = sizeof (col_dpy_tofile) / 121 sizeof (*col_dpy_tofile); 122 123 /* tells the printing function what members to use; follows col_dpy_tofile[] */ 124 static size_t col_dpy_prt_tofile[] = { 125 offsetof(col_prt_t, col_nm), 126 offsetof(col_prt_t, col_val) 127 }; 128 129 130 /* 131 * mixer and control accounting 132 */ 133 134 typedef struct cinfo { 135 oss_mixext ci; 136 oss_mixer_enuminfo *enump; 137 } cinfo_t; 138 139 typedef struct device { 140 oss_card_info card; 141 oss_mixerinfo mixer; 142 143 int cmax; 144 cinfo_t *controls; 145 146 int mfd; 147 dev_t devt; 148 149 struct device *nextp; 150 } device_t; 151 152 static device_t *devices = NULL; 153 154 /*PRINTFLIKE1*/ 155 static void 156 msg(char *fmt, ...) 157 { 158 va_list ap; 159 160 va_start(ap, fmt); 161 (void) vprintf(fmt, ap); 162 va_end(ap); 163 } 164 165 /*PRINTFLIKE1*/ 166 static void 167 warn(char *fmt, ...) 168 { 169 va_list ap; 170 171 va_start(ap, fmt); 172 (void) vfprintf(stderr, fmt, ap); 173 va_end(ap); 174 } 175 176 static void 177 free_device(device_t *d) 178 { 179 int i; 180 device_t **dpp; 181 182 dpp = &devices; 183 while ((*dpp) && ((*dpp) != d)) { 184 dpp = &((*dpp)->nextp); 185 } 186 if (*dpp) { 187 *dpp = d->nextp; 188 } 189 for (i = 0; i < d->cmax; i++) { 190 if (d->controls[i].enump != NULL) 191 free(d->controls[i].enump); 192 } 193 194 if (d->mfd >= 0) 195 (void) close(d->mfd); 196 197 free(d); 198 } 199 200 static void 201 free_devices(void) 202 { 203 device_t *d = devices; 204 205 while ((d = devices) != NULL) { 206 free_device(d); 207 } 208 209 devices = NULL; 210 } 211 212 213 /* 214 * adds to the end of global devices and returns a pointer to the new entry 215 */ 216 static device_t * 217 alloc_device(void) 218 { 219 device_t *p; 220 device_t *d = calloc(1, sizeof (*d)); 221 222 d->card.card = -1; 223 d->mixer.dev = -1; 224 d->mfd = -1; 225 226 if (devices == NULL) { 227 devices = d; 228 } else { 229 for (p = devices; p->nextp != NULL; p = p->nextp) {} 230 231 p->nextp = d; 232 } 233 return (d); 234 } 235 236 237 /* 238 * cinfop->enump needs to be present 239 * idx should be: >= 0 to < cinfop->ci.maxvalue 240 */ 241 static char * 242 get_enum_str(cinfo_t *cinfop, int idx) 243 { 244 int sz = sizeof (*cinfop->ci.enum_present) * 8; 245 246 if (cinfop->ci.enum_present[idx / sz] & (1 << (idx % sz))) 247 return (cinfop->enump->strings + cinfop->enump->strindex[idx]); 248 249 return (NULL); 250 } 251 252 253 /* 254 * caller fills in d->mixer.devnode; func fills in the rest 255 */ 256 static int 257 get_device_info(device_t *d) 258 { 259 int fd = -1; 260 int i; 261 cinfo_t *ci; 262 263 if ((fd = open(d->mixer.devnode, O_RDWR)) < 0) { 264 perror(_("Error opening device")); 265 return (errno); 266 } 267 d->mfd = fd; 268 269 d->cmax = -1; 270 if (ioctl(fd, SNDCTL_MIX_NREXT, &d->cmax) < 0) { 271 perror(_("Error getting control count")); 272 return (errno); 273 } 274 275 d->controls = calloc(d->cmax, sizeof (*d->controls)); 276 277 for (i = 0; i < d->cmax; i++) { 278 ci = &d->controls[i]; 279 280 ci->ci.dev = -1; 281 ci->ci.ctrl = i; 282 283 if (ioctl(fd, SNDCTL_MIX_EXTINFO, &ci->ci) < 0) { 284 perror(_("Error getting control info")); 285 return (errno); 286 } 287 288 if (ci->ci.type == MIXT_ENUM) { 289 ci->enump = calloc(1, sizeof (*ci->enump)); 290 ci->enump->dev = -1; 291 ci->enump->ctrl = ci->ci.ctrl; 292 293 if (ioctl(fd, SNDCTL_MIX_ENUMINFO, ci->enump) < 0) { 294 perror(_("Error getting enum info")); 295 return (errno); 296 } 297 } 298 } 299 300 return (0); 301 } 302 303 304 static int 305 load_devices(void) 306 { 307 int rv = -1; 308 int fd = -1; 309 int i; 310 oss_sysinfo si; 311 device_t *d; 312 313 if ((fd = open("/dev/mixer", O_RDWR)) < 0) { 314 rv = errno; 315 warn(_("Error opening mixer\n")); 316 goto OUT; 317 } 318 319 if (ioctl(fd, SNDCTL_SYSINFO, &si) < 0) { 320 rv = errno; 321 perror(_("Error getting system information")); 322 goto OUT; 323 } 324 325 for (i = 0; i < si.nummixers; i++) { 326 327 struct stat sbuf; 328 329 d = alloc_device(); 330 d->mixer.dev = i; 331 332 if (ioctl(fd, SNDCTL_MIXERINFO, &d->mixer) != 0) { 333 continue; 334 } 335 336 d->card.card = d->mixer.card_number; 337 338 if ((ioctl(fd, SNDCTL_CARDINFO, &d->card) != 0) || 339 (stat(d->mixer.devnode, &sbuf) != 0) || 340 ((sbuf.st_mode & S_IFCHR) == 0)) { 341 warn(_("Device present: %s\n"), d->mixer.devnode); 342 free_device(d); 343 continue; 344 } 345 d->devt = makedev(major(sbuf.st_rdev), 346 minor(sbuf.st_rdev) & ~(AUDIO_MN_TYPE_MASK)); 347 348 if ((rv = get_device_info(d)) != 0) { 349 free_device(d); 350 goto OUT; 351 } 352 } 353 354 rv = 0; 355 356 OUT: 357 if (fd >= 0) 358 (void) close(fd); 359 return (rv); 360 } 361 362 363 static int 364 ctype_valid(int type) 365 { 366 switch (type) { 367 case MIXT_ONOFF: 368 case MIXT_ENUM: 369 case MIXT_MONOSLIDER: 370 case MIXT_STEREOSLIDER: 371 return (1); 372 default: 373 return (0); 374 } 375 } 376 377 378 static void 379 print_control_line(FILE *sfp, col_prt_t *colp, int vopt) 380 { 381 int i; 382 size_t *col_prtp; 383 int *col_dpyp; 384 int col_cnt; 385 int col_type; 386 int width; 387 char *colstr; 388 char cbuf[COL_MAX_SZ + 1]; 389 char line[128]; 390 char *colsep = " "; 391 392 if (sfp != NULL) { 393 col_prtp = col_dpy_prt_tofile; 394 col_dpyp = col_dpy_tofile; 395 col_cnt = col_dpy_tofile_len; 396 } else if (vopt) { 397 col_prtp = col_dpy_prt_vopt; 398 col_dpyp = col_dpy_vopt; 399 col_cnt = col_dpy_vopt_len; 400 } else { 401 col_prtp = col_dpy_prt; 402 col_dpyp = col_dpy; 403 col_cnt = col_dpy_len; 404 } 405 406 line[0] = '\0'; 407 408 for (i = 0; i < col_cnt; i++) { 409 col_type = col_dpyp[i]; 410 width = col_sz[col_type]; 411 colstr = *(char **)(((size_t)colp) + col_prtp[i]); 412 413 (void) snprintf(cbuf, sizeof (cbuf), "%- *s", 414 width > 0 ? width : 1, 415 (colstr == NULL) ? "" : colstr); 416 417 (void) strlcat(line, cbuf, sizeof (line)); 418 if (i < col_cnt - 1) 419 (void) strlcat(line, colsep, sizeof (line)); 420 } 421 422 (void) fprintf(sfp ? sfp : stdout, "%s\n", line); 423 } 424 425 426 static void 427 print_header(FILE *sfp, int vopt) 428 { 429 col_prt_t col; 430 431 if (sfp) { 432 col.col_nm = _("#CONTROL"); 433 col.col_val = _("VALUE"); 434 } else { 435 col.col_dv = _("DEVICE"); 436 col.col_nm = _("CONTROL"); 437 col.col_val = _("VALUE"); 438 col.col_sel = _("POSSIBLE"); 439 } 440 print_control_line(sfp, &col, vopt); 441 } 442 443 444 static int 445 print_control(FILE *sfp, device_t *d, cinfo_t *cinfop, int vopt) 446 { 447 int mfd = d->mfd; 448 char *devnm = d->card.shortname; 449 oss_mixer_value cval; 450 char *str; 451 int i; 452 int idx = -1; 453 int rv = -1; 454 char valbuf[COL_VAL_SZ + 1]; 455 char selbuf[COL_SEL_SZ + 1]; 456 col_prt_t col; 457 458 cval.dev = -1; 459 cval.ctrl = cinfop->ci.ctrl; 460 461 if (ctype_valid(cinfop->ci.type)) { 462 if (ioctl(mfd, SNDCTL_MIX_READ, &cval) < 0) { 463 rv = errno; 464 perror(_("Error reading control\n")); 465 return (rv); 466 } 467 } else { 468 return (0); 469 } 470 471 /* 472 * convert the control value into a string 473 */ 474 switch (cinfop->ci.type) { 475 case MIXT_ONOFF: 476 (void) snprintf(valbuf, sizeof (valbuf), "%s", 477 cval.value ? _("on") : _("off")); 478 break; 479 480 case MIXT_MONOSLIDER: 481 (void) snprintf(valbuf, sizeof (valbuf), "%d", 482 cval.value & 0xff); 483 break; 484 485 case MIXT_STEREOSLIDER: 486 (void) snprintf(valbuf, sizeof (valbuf), "%d:%d", 487 (int)AUDIO_CTRL_STEREO_LEFT(cval.value), 488 (int)AUDIO_CTRL_STEREO_RIGHT(cval.value)); 489 break; 490 491 case MIXT_ENUM: 492 str = get_enum_str(cinfop, cval.value); 493 if (str == NULL) { 494 warn(_("Bad enum index %d for control '%s'\n"), 495 cval.value, cinfop->ci.extname); 496 return (EINVAL); 497 } 498 499 (void) snprintf(valbuf, sizeof (valbuf), "%s", str); 500 break; 501 502 default: 503 return (0); 504 } 505 506 /* 507 * possible control values (range/selection) 508 */ 509 switch (cinfop->ci.type) { 510 case MIXT_ONOFF: 511 (void) snprintf(selbuf, sizeof (selbuf), _("on,off")); 512 break; 513 514 case MIXT_MONOSLIDER: 515 (void) snprintf(selbuf, sizeof (selbuf), "%d-%d", 516 cinfop->ci.minvalue, cinfop->ci.maxvalue); 517 break; 518 case MIXT_STEREOSLIDER: 519 (void) snprintf(selbuf, sizeof (selbuf), "%d-%d:%d-%d", 520 cinfop->ci.minvalue, cinfop->ci.maxvalue, 521 cinfop->ci.minvalue, cinfop->ci.maxvalue); 522 break; 523 524 case MIXT_ENUM: 525 /* 526 * display the first choice on the same line, then display 527 * the rest on multiple lines 528 */ 529 selbuf[0] = 0; 530 for (i = 0; i < cinfop->ci.maxvalue; i++) { 531 str = get_enum_str(cinfop, i); 532 if (str == NULL) 533 continue; 534 535 if ((strlen(str) + 1 + strlen(selbuf)) >= 536 sizeof (selbuf)) { 537 break; 538 } 539 if (strlen(selbuf)) { 540 (void) strlcat(selbuf, ",", sizeof (selbuf)); 541 } 542 543 (void) strlcat(selbuf, str, sizeof (selbuf)); 544 } 545 idx = i; 546 break; 547 548 default: 549 (void) snprintf(selbuf, sizeof (selbuf), "-"); 550 } 551 552 col.col_dv = devnm; 553 col.col_nm = strlen(cinfop->ci.extname) ? 554 cinfop->ci.extname : cinfop->ci.id; 555 while (strchr(col.col_nm, '_') != NULL) { 556 col.col_nm = strchr(col.col_nm, '_') + 1; 557 } 558 col.col_val = valbuf; 559 col.col_sel = selbuf; 560 print_control_line(sfp, &col, vopt); 561 562 /* print leftover enum value selections */ 563 while ((sfp == NULL) && (idx >= 0) && (idx < cinfop->ci.maxvalue)) { 564 selbuf[0] = 0; 565 for (i = idx; i < cinfop->ci.maxvalue; i++) { 566 str = get_enum_str(cinfop, i); 567 if (str == NULL) 568 continue; 569 570 if ((strlen(str) + 1 + strlen(selbuf)) >= 571 sizeof (selbuf)) { 572 break; 573 } 574 if (strlen(selbuf)) { 575 (void) strlcat(selbuf, ",", sizeof (selbuf)); 576 } 577 578 (void) strlcat(selbuf, str, sizeof (selbuf)); 579 } 580 idx = i; 581 col.col_dv = NULL; 582 col.col_nm = NULL; 583 col.col_val = NULL; 584 col.col_sel = selbuf; 585 print_control_line(sfp, &col, vopt); 586 } 587 588 return (0); 589 } 590 591 592 static int 593 set_device_control(device_t *d, cinfo_t *cinfop, char *wstr, int vopt) 594 { 595 int mfd = d->mfd; 596 oss_mixer_value cval; 597 int wlen = strlen(wstr); 598 int lval, rval; 599 char *lstr, *rstr; 600 char *str; 601 int i; 602 int rv = -1; 603 604 cval.dev = -1; 605 cval.ctrl = cinfop->ci.ctrl; 606 cval.value = 0; 607 608 switch (cinfop->ci.type) { 609 case MIXT_ONOFF: 610 cval.value = (strncmp(_("on"), wstr, wlen) == 0) ? 1 : 0; 611 break; 612 613 case MIXT_MONOSLIDER: 614 cval.value = atoi(wstr); 615 break; 616 617 case MIXT_STEREOSLIDER: 618 lstr = wstr; 619 rstr = strchr(wstr, ':'); 620 if (rstr != NULL) { 621 *rstr = '\0'; 622 rstr++; 623 624 rval = atoi(rstr); 625 lval = atoi(lstr); 626 627 rstr--; 628 *rstr = ':'; 629 } else { 630 lval = atoi(lstr); 631 rval = lval; 632 } 633 634 cval.value = AUDIO_CTRL_STEREO_VAL(lval, rval); 635 break; 636 637 case MIXT_ENUM: 638 for (i = 0; i < cinfop->ci.maxvalue; i++) { 639 str = get_enum_str(cinfop, i); 640 if (str == NULL) 641 continue; 642 643 if (strncmp(wstr, str, wlen) == 0) { 644 cval.value = i; 645 break; 646 } 647 } 648 649 if (i >= cinfop->ci.maxvalue) { 650 warn(_("Invalid enumeration value\n")); 651 return (EINVAL); 652 } 653 break; 654 655 default: 656 warn(_("Unsupported control type: %d\n"), cinfop->ci.type); 657 return (EINVAL); 658 } 659 660 if (vopt) { 661 msg(_("%s: '%s' set to '%s'\n"), d->card.shortname, 662 cinfop->ci.extname, wstr); 663 } 664 665 if (ioctl(mfd, SNDCTL_MIX_WRITE, &cval) < 0) { 666 rv = errno; 667 perror(_("Error writing control")); 668 return (rv); 669 } 670 671 rv = 0; 672 return (rv); 673 } 674 675 676 static void 677 help(void) 678 { 679 #define HELP_STR _( \ 680 "audioctl list-devices\n" \ 681 " list all audio devices\n" \ 682 "\n" \ 683 "audioctl show-device [ -v ] [ -d <device> ]\n" \ 684 " display information about an audio device\n" \ 685 "\n" \ 686 "audioctl show-control [ -v ] [ -d <device> ] [ <control> ... ]\n" \ 687 " get the value of a specific control (all if not specified)\n" \ 688 "\n" \ 689 "audioctl set-control [ -v ] [ -d <device> ] <control> <value>\n" \ 690 " set the value of a specific control\n" \ 691 "\n" \ 692 "audioctl save-controls [ -d <device> ] [ -f ] <file>\n" \ 693 " save all control settings for the device to a file\n" \ 694 "\n" \ 695 "audioctl load-controls [ -d <device> ] <file>\n" \ 696 " restore previously saved control settings to device\n" \ 697 "\n" \ 698 "audioctl help\n" \ 699 " show this message.\n") 700 701 (void) fprintf(stderr, HELP_STR); 702 } 703 704 dev_t 705 device_devt(char *name) 706 { 707 struct stat sbuf; 708 709 if ((stat(name, &sbuf) != 0) || 710 ((sbuf.st_mode & S_IFCHR) == 0)) { 711 /* Not a device node! */ 712 return (0); 713 } 714 715 return (makedev(major(sbuf.st_rdev), 716 minor(sbuf.st_rdev) & ~(AUDIO_MN_TYPE_MASK))); 717 } 718 719 static device_t * 720 find_device(char *name) 721 { 722 dev_t devt; 723 device_t *d; 724 725 /* 726 * User may have specified: 727 * 728 * /dev/dsp[<num>] 729 * /dev/mixer[<num>] 730 * /dev/audio[<num>9] 731 * /dev/audioctl[<num>] 732 * /dev/sound/<num>{,ctl,dsp,mixer} 733 * /dev/sound/<driver>:<num>{,ctl,dsp,mixer} 734 * 735 * We can canonicalize these by looking at the dev_t though. 736 */ 737 738 if (name == NULL) 739 name = getenv("AUDIODEV"); 740 741 if ((name == NULL) || 742 (strcmp(name, "/dev/mixer") == 0)) { 743 /* /dev/mixer node doesn't point to real hw */ 744 name = "/dev/dsp"; 745 } 746 747 if (*name == '/') { 748 /* if we have a full path, convert to the devt */ 749 if ((devt = device_devt(name)) == 0) { 750 warn(_("No such audio device.\n")); 751 return (NULL); 752 } 753 name = NULL; 754 } 755 756 for (d = devices; d != NULL; d = d->nextp) { 757 oss_card_info *card = &d->card; 758 759 if ((name) && (strcmp(name, card->shortname) == 0)) { 760 return (d); 761 } 762 if (devt == d->devt) { 763 return (d); 764 } 765 } 766 767 warn(_("No such audio device.\n")); 768 return (NULL); 769 } 770 771 int 772 do_list_devices(int argc, char **argv) 773 { 774 int optc; 775 int verbose = 0; 776 device_t *d; 777 778 while ((optc = getopt(argc, argv, "v")) != EOF) { 779 switch (optc) { 780 case 'v': 781 verbose++; 782 break; 783 default: 784 help(); 785 return (-1); 786 } 787 } 788 argc -= optind; 789 argv += optind; 790 if (argc != 0) { 791 help(); 792 return (-1); 793 } 794 795 for (d = devices; d != NULL; d = d->nextp) { 796 797 if ((d->mixer.enabled == 0) && (!verbose)) 798 continue; 799 800 if (verbose) { 801 msg(_("%s (%s)\n"), d->card.shortname, 802 d->mixer.devnode); 803 } else { 804 msg(_("%s\n"), d->card.shortname); 805 } 806 } 807 808 return (0); 809 } 810 811 int 812 do_show_device(int argc, char **argv) 813 { 814 int optc; 815 char *devname = NULL; 816 device_t *d; 817 818 while ((optc = getopt(argc, argv, "d:v")) != EOF) { 819 switch (optc) { 820 case 'd': 821 devname = optarg; 822 break; 823 case 'v': 824 break; 825 default: 826 help(); 827 return (-1); 828 } 829 } 830 argc -= optind; 831 argv += optind; 832 if (argc != 0) { 833 help(); 834 return (-1); 835 } 836 837 if ((d = find_device(devname)) == NULL) { 838 return (ENODEV); 839 } 840 841 msg(_("Device: %s\n"), d->mixer.devnode); 842 msg(_(" Name = %s\n"), d->card.shortname); 843 msg(_(" Config = %s\n"), d->card.longname); 844 845 if (strlen(d->card.hw_info)) { 846 msg(_(" HW Info = %s"), d->card.hw_info); 847 } 848 849 return (0); 850 } 851 852 int 853 do_show_control(int argc, char **argv) 854 { 855 int optc; 856 int rval = 0; 857 int verbose = 0; 858 device_t *d; 859 char *devname = NULL; 860 int i; 861 int j; 862 int rv; 863 char *n; 864 cinfo_t *cinfop; 865 866 while ((optc = getopt(argc, argv, "d:v")) != EOF) { 867 switch (optc) { 868 case 'd': 869 devname = optarg; 870 break; 871 case 'v': 872 verbose++; 873 break; 874 default: 875 help(); 876 return (-1); 877 } 878 } 879 argc -= optind; 880 argv += optind; 881 882 if ((d = find_device(devname)) == NULL) { 883 return (ENODEV); 884 } 885 886 print_header(NULL, verbose); 887 if (argc == 0) { 888 /* do them all! */ 889 for (i = 0; i < d->cmax; i++) { 890 891 cinfop = &d->controls[i]; 892 rv = print_control(NULL, d, cinfop, verbose); 893 rval = rval ? rval : rv; 894 } 895 return (rval); 896 } 897 898 for (i = 0; i < argc; i++) { 899 for (j = 0; j < d->cmax; j++) { 900 cinfop = &d->controls[j]; 901 n = strrchr(cinfop->ci.extname, '_'); 902 n = n ? n + 1 : cinfop->ci.extname; 903 if (strcmp(argv[i], n) == 0) { 904 rv = print_control(NULL, d, cinfop, verbose); 905 rval = rval ? rval : rv; 906 break; 907 } 908 } 909 /* Didn't find requested control */ 910 if (j == d->cmax) { 911 warn(_("No such control: %s\n"), argv[i]); 912 rval = rval ? rval : ENODEV; 913 } 914 } 915 916 return (rval); 917 } 918 919 int 920 do_set_control(int argc, char **argv) 921 { 922 int optc; 923 int rval = 0; 924 int verbose = 0; 925 device_t *d; 926 char *devname = NULL; 927 char *cname; 928 char *value; 929 int i; 930 int found; 931 int rv; 932 char *n; 933 cinfo_t *cinfop; 934 935 while ((optc = getopt(argc, argv, "d:v")) != EOF) { 936 switch (optc) { 937 case 'd': 938 devname = optarg; 939 break; 940 case 'v': 941 verbose = 1; 942 break; 943 default: 944 help(); 945 return (-1); 946 } 947 } 948 argc -= optind; 949 argv += optind; 950 951 if (argc != 2) { 952 help(); 953 return (-1); 954 } 955 cname = argv[0]; 956 value = argv[1]; 957 958 if ((d = find_device(devname)) == NULL) { 959 return (ENODEV); 960 } 961 962 for (i = 0, found = 0; i < d->cmax; i++) { 963 cinfop = &d->controls[i]; 964 n = strrchr(cinfop->ci.extname, '_'); 965 n = n ? n + 1 : cinfop->ci.extname; 966 if (strcmp(cname, n) != 0) { 967 continue; 968 } 969 found = 1; 970 rv = set_device_control(d, cinfop, value, verbose); 971 rval = rval ? rval : rv; 972 } 973 if (!found) { 974 warn(_("No such control: %s\n"), cname); 975 } 976 977 return (rval); 978 } 979 980 int 981 do_save_controls(int argc, char **argv) 982 { 983 int optc; 984 int rval = 0; 985 device_t *d; 986 char *devname = NULL; 987 char *fname; 988 int i; 989 int rv; 990 cinfo_t *cinfop; 991 FILE *fp; 992 int fd; 993 int mode; 994 995 mode = O_WRONLY | O_CREAT | O_EXCL; 996 997 while ((optc = getopt(argc, argv, "d:f")) != EOF) { 998 switch (optc) { 999 case 'd': 1000 devname = optarg; 1001 break; 1002 case 'f': 1003 mode &= ~O_EXCL; 1004 mode |= O_TRUNC; 1005 break; 1006 default: 1007 help(); 1008 return (-1); 1009 } 1010 } 1011 argc -= optind; 1012 argv += optind; 1013 1014 if (argc != 1) { 1015 help(); 1016 return (-1); 1017 } 1018 fname = argv[0]; 1019 1020 if ((d = find_device(devname)) == NULL) { 1021 return (ENODEV); 1022 } 1023 1024 if ((fd = open(fname, mode, 0666)) < 0) { 1025 perror(_("Failed to create file")); 1026 return (errno); 1027 } 1028 1029 if ((fp = fdopen(fd, "w")) == NULL) { 1030 perror(_("Unable to open file\n")); 1031 (void) close(fd); 1032 (void) unlink(fname); 1033 return (errno); 1034 } 1035 1036 (void) fprintf(fp, "# Device: %s\n", d->mixer.devnode); 1037 (void) fprintf(fp, "# Name = %s\n", d->card.shortname); 1038 (void) fprintf(fp, "# Config = %s\n", d->card.longname); 1039 1040 if (strlen(d->card.hw_info)) { 1041 (void) fprintf(fp, "# HW Info = %s", d->card.hw_info); 1042 } 1043 (void) fprintf(fp, "#\n"); 1044 1045 print_header(fp, 0); 1046 1047 for (i = 0; i < d->cmax; i++) { 1048 cinfop = &d->controls[i]; 1049 rv = print_control(fp, d, cinfop, 0); 1050 rval = rval ? rval : rv; 1051 } 1052 1053 (void) fclose(fp); 1054 1055 return (rval); 1056 } 1057 1058 int 1059 do_load_controls(int argc, char **argv) 1060 { 1061 int optc; 1062 int rval = 0; 1063 device_t *d; 1064 char *devname = NULL; 1065 char *fname; 1066 char *cname; 1067 char *value; 1068 int i; 1069 int rv; 1070 cinfo_t *cinfop; 1071 FILE *fp; 1072 char linebuf[MAXLINE]; 1073 int lineno = 0; 1074 int found; 1075 1076 while ((optc = getopt(argc, argv, "d:")) != EOF) { 1077 switch (optc) { 1078 case 'd': 1079 devname = optarg; 1080 break; 1081 default: 1082 help(); 1083 return (-1); 1084 } 1085 } 1086 argc -= optind; 1087 argv += optind; 1088 1089 if (argc != 1) { 1090 help(); 1091 return (-1); 1092 } 1093 fname = argv[0]; 1094 1095 if ((d = find_device(devname)) == NULL) { 1096 return (ENODEV); 1097 } 1098 1099 if ((fp = fopen(fname, "r")) == NULL) { 1100 perror(_("Unable to open file")); 1101 return (errno); 1102 } 1103 1104 while (fgets(linebuf, sizeof (linebuf), fp) != NULL) { 1105 lineno++; 1106 if (linebuf[strlen(linebuf) - 1] != '\n') { 1107 warn(_("Warning: line too long at line %d\n"), lineno); 1108 /* read in the rest of the line and discard it */ 1109 while (fgets(linebuf, sizeof (linebuf), fp) != NULL && 1110 (linebuf[strlen(linebuf) - 1] != '\n')) { 1111 continue; 1112 } 1113 continue; 1114 } 1115 1116 /* we have a good line ... */ 1117 cname = strtok(linebuf, " \t\n"); 1118 /* skip comments and blank lines */ 1119 if ((cname == NULL) || (cname[0] == '#')) { 1120 continue; 1121 } 1122 value = strtok(NULL, " \t\n"); 1123 if ((value == NULL) || (*cname == 0)) { 1124 warn(_("Warning: missing value at line %d\n"), lineno); 1125 continue; 1126 } 1127 1128 for (i = 0, found = 0; i < d->cmax; i++) { 1129 /* save and restore requires an exact match */ 1130 cinfop = &d->controls[i]; 1131 if (strcmp(cinfop->ci.extname, cname) != 0) { 1132 continue; 1133 } 1134 found = 1; 1135 rv = set_device_control(d, cinfop, value, 0); 1136 rval = rval ? rval : rv; 1137 } 1138 if (!found) { 1139 warn(_("No such control: %s\n"), cname); 1140 } 1141 } 1142 (void) fclose(fp); 1143 1144 return (rval); 1145 } 1146 1147 int 1148 main(int argc, char **argv) 1149 { 1150 int rv = 0; 1151 int opt; 1152 1153 (void) setlocale(LC_ALL, ""); 1154 (void) textdomain(TEXT_DOMAIN); 1155 1156 while ((opt = getopt(argc, argv, "h")) != EOF) { 1157 switch (opt) { 1158 case 'h': 1159 help(); 1160 rv = 0; 1161 goto OUT; 1162 default: 1163 rv = EINVAL; 1164 break; 1165 } 1166 } 1167 1168 if (rv) { 1169 goto OUT; 1170 } 1171 1172 argc -= optind; 1173 argv += optind; 1174 1175 rv = load_devices(); 1176 if (rv != 0) { 1177 goto OUT; 1178 } 1179 1180 if (argc < 1) { 1181 help(); 1182 rv = EINVAL; 1183 } else if (strcmp(argv[0], "help") == 0) { 1184 help(); 1185 rv = 0; 1186 } else if (strcmp(argv[0], "list-devices") == 0) { 1187 rv = do_list_devices(argc, argv); 1188 } else if (strcmp(argv[0], "show-device") == 0) { 1189 rv = do_show_device(argc, argv); 1190 } else if (strcmp(argv[0], "show-control") == 0) { 1191 rv = do_show_control(argc, argv); 1192 } else if (strcmp(argv[0], "set-control") == 0) { 1193 rv = do_set_control(argc, argv); 1194 } else if (strcmp(argv[0], "load-controls") == 0) { 1195 rv = do_load_controls(argc, argv); 1196 } else if (strcmp(argv[0], "save-controls") == 0) { 1197 rv = do_save_controls(argc, argv); 1198 } else { 1199 help(); 1200 rv = EINVAL; 1201 } 1202 1203 OUT: 1204 free_devices(); 1205 return (rv); 1206 } 1207