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