1 /* $NetBSD: mixerctl.c,v 1.21 2003/10/23 22:17:58 cube Exp $ */ 2 3 /* 4 * Copyright (c) 1997 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Lennart Augustsson (augustss@NetBSD.org) and Chuck Cranor. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the NetBSD 21 * Foundation, Inc. and its contributors. 22 * 4. Neither the name of The NetBSD Foundation nor the names of its 23 * contributors may be used to endorse or promote products derived 24 * from this software without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 * POSSIBILITY OF SUCH DAMAGE. 37 */ 38 #include <sys/cdefs.h> 39 40 #ifndef lint 41 __RCSID("$NetBSD: mixerctl.c,v 1.21 2003/10/23 22:17:58 cube Exp $"); 42 #endif 43 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <fcntl.h> 47 #include <err.h> 48 #include <unistd.h> 49 #include <string.h> 50 #include <sys/types.h> 51 #include <sys/ioctl.h> 52 #include <sys/audioio.h> 53 54 #include <paths.h> 55 56 FILE *out = stdout; 57 int vflag = 0; 58 59 char *prog; 60 61 struct field { 62 char *name; 63 mixer_ctrl_t *valp; 64 mixer_devinfo_t *infp; 65 char changed; 66 } *fields, *rfields; 67 68 mixer_ctrl_t *values; 69 mixer_devinfo_t *infos; 70 71 static char * 72 catstr(char *p, char *q) 73 { 74 char *r; 75 76 asprintf(&r, "%s.%s", p, q); 77 if (!r) 78 err(1, "malloc"); 79 return r; 80 } 81 82 static struct field * 83 findfield(char *name) 84 { 85 int i; 86 for(i = 0; fields[i].name; i++) 87 if (strcmp(fields[i].name, name) == 0) 88 return &fields[i]; 89 return 0; 90 } 91 92 static void 93 prfield(struct field *p, char *sep, int prvalset) 94 { 95 mixer_ctrl_t *m; 96 int i, n; 97 98 if (sep) 99 fprintf(out, "%s%s", p->name, sep); 100 m = p->valp; 101 switch(m->type) { 102 case AUDIO_MIXER_ENUM: 103 for(i = 0; i < p->infp->un.e.num_mem; i++) 104 if (p->infp->un.e.member[i].ord == m->un.ord) 105 fprintf(out, "%s", 106 p->infp->un.e.member[i].label.name); 107 if (prvalset) { 108 fprintf(out, " [ "); 109 for(i = 0; i < p->infp->un.e.num_mem; i++) 110 fprintf(out, "%s ", 111 p->infp->un.e.member[i].label.name); 112 fprintf(out, "]"); 113 } 114 break; 115 case AUDIO_MIXER_SET: 116 for(n = i = 0; i < p->infp->un.s.num_mem; i++) 117 if (m->un.mask & p->infp->un.s.member[i].mask) 118 fprintf(out, "%s%s", n++ ? "," : "", 119 p->infp->un.s.member[i].label.name); 120 if (prvalset) { 121 fprintf(out, " { "); 122 for(i = 0; i < p->infp->un.s.num_mem; i++) 123 fprintf(out, "%s ", 124 p->infp->un.s.member[i].label.name); 125 fprintf(out, "}"); 126 } 127 break; 128 case AUDIO_MIXER_VALUE: 129 if (m->un.value.num_channels == 1) 130 fprintf(out, "%d", m->un.value.level[0]); 131 else 132 fprintf(out, "%d,%d", m->un.value.level[0], 133 m->un.value.level[1]); 134 if (prvalset) { 135 fprintf(out, " %s", p->infp->un.v.units.name); 136 if (p->infp->un.v.delta) 137 fprintf(out, " delta=%d", p->infp->un.v.delta); 138 } 139 break; 140 default: 141 printf("\n"); 142 errx(1, "Invalid format."); 143 } 144 } 145 146 static int 147 rdfield(struct field *p, char *q) 148 { 149 mixer_ctrl_t *m; 150 int v, v0, v1, mask; 151 int i; 152 char *s; 153 154 m = p->valp; 155 switch(m->type) { 156 case AUDIO_MIXER_ENUM: 157 for(i = 0; i < p->infp->un.e.num_mem; i++) 158 if (strcmp(p->infp->un.e.member[i].label.name, q) == 0) 159 break; 160 if (i < p->infp->un.e.num_mem) 161 m->un.ord = p->infp->un.e.member[i].ord; 162 else { 163 warnx("Bad enum value %s", q); 164 return 0; 165 } 166 break; 167 case AUDIO_MIXER_SET: 168 mask = 0; 169 for(v = 0; q && *q; q = s) { 170 s = strchr(q, ','); 171 if (s) 172 *s++ = 0; 173 for(i = 0; i < p->infp->un.s.num_mem; i++) 174 if (strcmp(p->infp->un.s.member[i].label.name, 175 q) == 0) 176 break; 177 if (i < p->infp->un.s.num_mem) { 178 mask |= p->infp->un.s.member[i].mask; 179 } else { 180 warnx("Bad set value %s", q); 181 return 0; 182 } 183 } 184 m->un.mask = mask; 185 break; 186 case AUDIO_MIXER_VALUE: 187 if (m->un.value.num_channels == 1) { 188 if (sscanf(q, "%d", &v) == 1) { 189 m->un.value.level[0] = v; 190 } else { 191 warnx("Bad number %s", q); 192 return 0; 193 } 194 } else { 195 if (sscanf(q, "%d,%d", &v0, &v1) == 2) { 196 m->un.value.level[0] = v0; 197 m->un.value.level[1] = v1; 198 } else if (sscanf(q, "%d", &v) == 1) { 199 m->un.value.level[0] = m->un.value.level[1] = v; 200 } else { 201 warnx("Bad numbers %s", q); 202 return 0; 203 } 204 } 205 break; 206 default: 207 errx(1, "Invalid format."); 208 } 209 p->changed = 1; 210 return 1; 211 } 212 213 static int 214 incfield(struct field *p, int inc) 215 { 216 mixer_ctrl_t *m; 217 int i, v; 218 219 m = p->valp; 220 switch(m->type) { 221 case AUDIO_MIXER_ENUM: 222 m->un.ord += inc; 223 if (m->un.ord < 0) 224 m->un.ord = p->infp->un.e.num_mem-1; 225 if (m->un.ord >= p->infp->un.e.num_mem) 226 m->un.ord = 0; 227 break; 228 case AUDIO_MIXER_SET: 229 m->un.mask += inc; 230 if (m->un.mask < 0) 231 m->un.mask = (1 << p->infp->un.s.num_mem) - 1; 232 if (m->un.mask >= (1 << p->infp->un.s.num_mem)) 233 m->un.mask = 0; 234 warnx("Can't ++/-- %s", p->name); 235 return 0; 236 case AUDIO_MIXER_VALUE: 237 if (p->infp->un.v.delta) 238 inc *= p->infp->un.v.delta; 239 for (i = 0; i < m->un.value.num_channels; i++) { 240 v = m->un.value.level[i]; 241 v += inc; 242 if (v < AUDIO_MIN_GAIN) 243 v = AUDIO_MIN_GAIN; 244 if (v > AUDIO_MAX_GAIN) 245 v = AUDIO_MAX_GAIN; 246 m->un.value.level[i] = v; 247 } 248 break; 249 default: 250 errx(1, "Invalid format."); 251 } 252 p->changed = 1; 253 return 1; 254 } 255 256 static void 257 wrarg(int fd, char *arg, char *sep) 258 { 259 char *q; 260 struct field *p; 261 mixer_ctrl_t val; 262 int incdec, r; 263 264 q = strchr(arg, '='); 265 if (q == NULL) { 266 int l = strlen(arg); 267 incdec = 0; 268 if (l > 2 && arg[l-2] == '+' && arg[l-1] == '+') 269 incdec = 1; 270 else if (l > 2 && arg[l-2] == '-' && arg[l-1] == '-') 271 incdec = -1; 272 else { 273 warnx("No `=' in %s", arg); 274 return; 275 } 276 arg[l-2] = 0; 277 } else if (q > arg && (*(q-1) == '+' || *(q-1) == '-')) { 278 if (sscanf(q+1, "%d", &incdec) != 1) { 279 warnx("Bad number %s", q+1); 280 return; 281 } 282 if (*(q-1) == '-') 283 incdec *= -1; 284 *(q-1) = 0; 285 q = NULL; 286 } else 287 *q++ = 0; 288 289 p = findfield(arg); 290 if (p == NULL) { 291 warnx("field %s does not exist", arg); 292 return; 293 } 294 295 val = *p->valp; 296 if (q != NULL) 297 r = rdfield(p, q); 298 else 299 r = incfield(p, incdec); 300 if (r) { 301 if (ioctl(fd, AUDIO_MIXER_WRITE, p->valp) < 0) 302 warn("AUDIO_MIXER_WRITE"); 303 else if (sep) { 304 *p->valp = val; 305 prfield(p, ": ", 0); 306 ioctl(fd, AUDIO_MIXER_READ, p->valp); 307 printf(" -> "); 308 prfield(p, 0, 0); 309 printf("\n"); 310 } 311 } 312 } 313 314 static void 315 prarg(int fd, char *arg, char *sep) 316 { 317 struct field *p; 318 319 p = findfield(arg); 320 if (p == NULL) 321 warnx("field %s does not exist", arg); 322 else 323 prfield(p, sep, vflag), fprintf(out, "\n"); 324 } 325 326 int 327 main(int argc, char **argv) 328 { 329 int fd, i, j, ch, pos; 330 int aflag = 0, wflag = 0; 331 char *file; 332 char *sep = "="; 333 mixer_devinfo_t dinfo; 334 int ndev; 335 336 file = getenv("MIXERDEVICE"); 337 if (file == NULL) 338 file = _PATH_MIXER; 339 340 prog = *argv; 341 342 while ((ch = getopt(argc, argv, "ad:f:nvw")) != -1) { 343 switch(ch) { 344 case 'a': 345 aflag++; 346 break; 347 case 'w': 348 wflag++; 349 break; 350 case 'v': 351 vflag++; 352 break; 353 case 'n': 354 sep = 0; 355 break; 356 case 'f': /* compatibility */ 357 case 'd': 358 file = optarg; 359 break; 360 case '?': 361 default: 362 usage: 363 fprintf(out, "%s [-d file] [-v] [-n] name ...\n", prog); 364 fprintf(out, "%s [-d file] [-v] [-n] -w name=value ...\n",prog); 365 fprintf(out, "%s [-d file] [-v] [-n] -a\n", prog); 366 exit(0); 367 } 368 } 369 argc -= optind; 370 argv += optind; 371 372 fd = open(file, O_RDWR); 373 /* Try with mixer0. */ 374 if (fd < 0 && file == _PATH_MIXER) { 375 file = _PATH_MIXER0; 376 fd = open(file, O_RDWR); 377 } 378 379 if (fd < 0) 380 err(1, "%s", file); 381 382 for(ndev = 0; ; ndev++) { 383 dinfo.index = ndev; 384 if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) < 0) 385 break; 386 } 387 rfields = calloc(ndev, sizeof *rfields); 388 fields = calloc(ndev, sizeof *fields); 389 infos = calloc(ndev, sizeof *infos); 390 values = calloc(ndev, sizeof *values); 391 392 for(i = 0; i < ndev; i++) { 393 infos[i].index = i; 394 ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]); 395 } 396 397 for(i = 0; i < ndev; i++) { 398 rfields[i].name = infos[i].label.name; 399 rfields[i].valp = &values[i]; 400 rfields[i].infp = &infos[i]; 401 } 402 403 for(i = 0; i < ndev; i++) { 404 values[i].dev = i; 405 values[i].type = infos[i].type; 406 if (infos[i].type != AUDIO_MIXER_CLASS) { 407 values[i].un.value.num_channels = 2; 408 if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0) { 409 values[i].un.value.num_channels = 1; 410 if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0) 411 err(1, "AUDIO_MIXER_READ"); 412 } 413 } 414 } 415 416 for(j = i = 0; i < ndev; i++) { 417 if (infos[i].type != AUDIO_MIXER_CLASS && 418 infos[i].type != -1) { 419 fields[j++] = rfields[i]; 420 for(pos = infos[i].next; pos != AUDIO_MIXER_LAST; 421 pos = infos[pos].next) { 422 fields[j] = rfields[pos]; 423 fields[j].name = catstr(rfields[i].name, 424 infos[pos].label.name); 425 infos[pos].type = -1; 426 j++; 427 } 428 } 429 } 430 431 for(i = 0; i < j; i++) { 432 int cls = fields[i].infp->mixer_class; 433 if (cls >= 0 && cls < ndev) 434 fields[i].name = catstr(infos[cls].label.name, 435 fields[i].name); 436 } 437 438 if (argc == 0 && aflag && !wflag) { 439 for(i = 0; fields[i].name; i++) { 440 prfield(&fields[i], sep, vflag); 441 fprintf(out, "\n"); 442 } 443 } else if (argc > 0 && !aflag) { 444 while(argc--) { 445 if (wflag) 446 wrarg(fd, *argv, sep); 447 else 448 prarg(fd, *argv, sep); 449 argv++; 450 } 451 } else 452 goto usage; 453 exit(0); 454 } 455