1 /* $NetBSD: mixerctl.c,v 1.27 2017/02/23 14:09:11 kre 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.27 2017/02/23 14:09:11 kre 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 49 FILE *out = stdout; 50 int vflag = 0; 51 52 char *prog; 53 54 struct field { 55 char *name; 56 mixer_ctrl_t *valp; 57 mixer_devinfo_t *infp; 58 char changed; 59 } *fields, *rfields; 60 61 mixer_ctrl_t *values; 62 mixer_devinfo_t *infos; 63 64 static const char mixer_path[] = _PATH_MIXER; 65 66 static char * 67 catstr(char *p, char *q) 68 { 69 char *r; 70 71 asprintf(&r, "%s.%s", p, q); 72 if (!r) 73 err(1, "malloc"); 74 return r; 75 } 76 77 static struct field * 78 findfield(char *name) 79 { 80 int i; 81 for (i = 0; fields[i].name; i++) 82 if (strcmp(fields[i].name, name) == 0) 83 return &fields[i]; 84 return 0; 85 } 86 87 static void 88 prfield(struct field *p, const char *sep, int prvalset) 89 { 90 mixer_ctrl_t *m; 91 int i, n; 92 93 if (sep) 94 fprintf(out, "%s%s", p->name, sep); 95 m = p->valp; 96 switch(m->type) { 97 case AUDIO_MIXER_ENUM: 98 for (i = 0; i < p->infp->un.e.num_mem; i++) 99 if (p->infp->un.e.member[i].ord == m->un.ord) 100 fprintf(out, "%s", 101 p->infp->un.e.member[i].label.name); 102 if (prvalset) { 103 fprintf(out, " [ "); 104 for (i = 0; i < p->infp->un.e.num_mem; i++) 105 fprintf(out, "%s ", 106 p->infp->un.e.member[i].label.name); 107 fprintf(out, "]"); 108 } 109 break; 110 case AUDIO_MIXER_SET: 111 for (n = i = 0; i < p->infp->un.s.num_mem; i++) 112 if (m->un.mask & p->infp->un.s.member[i].mask) 113 fprintf(out, "%s%s", n++ ? "," : "", 114 p->infp->un.s.member[i].label.name); 115 if (prvalset) { 116 fprintf(out, " { "); 117 for (i = 0; i < p->infp->un.s.num_mem; i++) 118 fprintf(out, "%s ", 119 p->infp->un.s.member[i].label.name); 120 fprintf(out, "}"); 121 } 122 break; 123 case AUDIO_MIXER_VALUE: 124 if (m->un.value.num_channels == 1) 125 fprintf(out, "%d", m->un.value.level[0]); 126 else 127 fprintf(out, "%d,%d", m->un.value.level[0], 128 m->un.value.level[1]); 129 if (prvalset) { 130 fprintf(out, " %s", p->infp->un.v.units.name); 131 if (p->infp->un.v.delta) 132 fprintf(out, " delta=%d", p->infp->un.v.delta); 133 } 134 break; 135 default: 136 printf("\n"); 137 errx(1, "Invalid format."); 138 } 139 } 140 141 static int 142 rdfield(struct field *p, char *q) 143 { 144 mixer_ctrl_t *m; 145 int v, v0, v1, mask; 146 int i; 147 char *s; 148 149 m = p->valp; 150 switch(m->type) { 151 case AUDIO_MIXER_ENUM: 152 for (i = 0; i < p->infp->un.e.num_mem; i++) 153 if (strcmp(p->infp->un.e.member[i].label.name, q) == 0) 154 break; 155 if (i < p->infp->un.e.num_mem) 156 m->un.ord = p->infp->un.e.member[i].ord; 157 else { 158 warnx("Bad enum value %s", q); 159 return 0; 160 } 161 break; 162 case AUDIO_MIXER_SET: 163 mask = 0; 164 for (v = 0; q && *q; q = s) { 165 s = strchr(q, ','); 166 if (s) 167 *s++ = 0; 168 for (i = 0; i < p->infp->un.s.num_mem; i++) 169 if (strcmp(p->infp->un.s.member[i].label.name, 170 q) == 0) 171 break; 172 if (i < p->infp->un.s.num_mem) { 173 mask |= p->infp->un.s.member[i].mask; 174 } else { 175 warnx("Bad set value %s", q); 176 return 0; 177 } 178 } 179 m->un.mask = mask; 180 break; 181 case AUDIO_MIXER_VALUE: 182 if (m->un.value.num_channels == 1) { 183 if (sscanf(q, "%d", &v) == 1) { 184 m->un.value.level[0] = v; 185 } else { 186 warnx("Bad number %s", q); 187 return 0; 188 } 189 } else { 190 if (sscanf(q, "%d,%d", &v0, &v1) == 2) { 191 m->un.value.level[0] = v0; 192 m->un.value.level[1] = v1; 193 } else if (sscanf(q, "%d", &v) == 1) { 194 m->un.value.level[0] = m->un.value.level[1] = v; 195 } else { 196 warnx("Bad numbers %s", q); 197 return 0; 198 } 199 } 200 break; 201 default: 202 errx(1, "Invalid format."); 203 } 204 p->changed = 1; 205 return 1; 206 } 207 208 static int 209 incfield(struct field *p, int inc) 210 { 211 mixer_ctrl_t *m; 212 int i, v; 213 214 m = p->valp; 215 switch(m->type) { 216 case AUDIO_MIXER_ENUM: 217 m->un.ord += inc; 218 if (m->un.ord < 0) 219 m->un.ord = p->infp->un.e.num_mem - 1; 220 if (m->un.ord >= p->infp->un.e.num_mem) 221 m->un.ord = 0; 222 break; 223 case AUDIO_MIXER_SET: 224 m->un.mask += inc; 225 if (m->un.mask < 0) 226 m->un.mask = (1 << p->infp->un.s.num_mem) - 1; 227 if (m->un.mask >= (1 << p->infp->un.s.num_mem)) 228 m->un.mask = 0; 229 warnx("Can't ++/-- %s", p->name); 230 return 0; 231 case AUDIO_MIXER_VALUE: 232 if (p->infp->un.v.delta) 233 inc *= p->infp->un.v.delta; 234 for (i = 0; i < m->un.value.num_channels; i++) { 235 v = m->un.value.level[i]; 236 v += inc; 237 if (v < AUDIO_MIN_GAIN) 238 v = AUDIO_MIN_GAIN; 239 if (v > AUDIO_MAX_GAIN) 240 v = AUDIO_MAX_GAIN; 241 m->un.value.level[i] = v; 242 } 243 break; 244 default: 245 errx(1, "Invalid format."); 246 } 247 p->changed = 1; 248 return 1; 249 } 250 251 static void 252 wrarg(int fd, char *arg, const char *sep) 253 { 254 char *q; 255 struct field *p; 256 mixer_ctrl_t val; 257 int incdec, r; 258 259 q = strchr(arg, '='); 260 if (q == NULL) { 261 int l = strlen(arg); 262 incdec = 0; 263 if (l > 2 && arg[l-2] == '+' && arg[l-1] == '+') 264 incdec = 1; 265 else if (l > 2 && arg[l-2] == '-' && arg[l-1] == '-') 266 incdec = -1; 267 else { 268 warnx("No `=' in %s", arg); 269 return; 270 } 271 arg[l-2] = 0; 272 } else if (q > arg && (*(q-1) == '+' || *(q-1) == '-')) { 273 if (sscanf(q+1, "%d", &incdec) != 1) { 274 warnx("Bad number %s", q+1); 275 return; 276 } 277 if (*(q-1) == '-') 278 incdec *= -1; 279 *(q-1) = 0; 280 q = NULL; 281 } else 282 *q++ = 0; 283 284 p = findfield(arg); 285 if (p == NULL) { 286 warnx("field %s does not exist", arg); 287 return; 288 } 289 290 val = *p->valp; 291 if (q != NULL) 292 r = rdfield(p, q); 293 else 294 r = incfield(p, incdec); 295 if (r) { 296 if (ioctl(fd, AUDIO_MIXER_WRITE, p->valp) < 0) 297 warn("AUDIO_MIXER_WRITE"); 298 else if (sep) { 299 *p->valp = val; 300 prfield(p, ": ", 0); 301 ioctl(fd, AUDIO_MIXER_READ, p->valp); 302 printf(" -> "); 303 prfield(p, 0, 0); 304 printf("\n"); 305 } 306 } 307 } 308 309 static void 310 prarg(int fd, char *arg, const char *sep) 311 { 312 struct field *p; 313 314 p = findfield(arg); 315 if (p == NULL) 316 warnx("field %s does not exist", arg); 317 else 318 prfield(p, sep, vflag), fprintf(out, "\n"); 319 } 320 321 static inline void __dead 322 usage(void) 323 { 324 fprintf(out, "%s [-d file] [-v] [-n] name ...\n", prog); 325 fprintf(out, "%s [-d file] [-v] [-n] -w name=value ...\n",prog); 326 fprintf(out, "%s [-d file] [-v] [-n] -a\n", prog); 327 exit(0); 328 } 329 330 int 331 main(int argc, char **argv) 332 { 333 int fd, i, j, ch, pos; 334 int aflag = 0, wflag = 0; 335 const char *file; 336 const char *sep = "="; 337 mixer_devinfo_t dinfo; 338 int ndev; 339 340 file = getenv("MIXERDEVICE"); 341 if (file == NULL) 342 file = mixer_path; 343 344 prog = *argv; 345 346 while ((ch = getopt(argc, argv, "ad:f:nvw")) != -1) { 347 switch(ch) { 348 case 'a': 349 aflag++; 350 break; 351 case 'w': 352 wflag++; 353 break; 354 case 'v': 355 vflag++; 356 break; 357 case 'n': 358 sep = 0; 359 break; 360 case 'f': /* compatibility */ 361 case 'd': 362 file = optarg; 363 break; 364 case '?': 365 default: 366 usage(); 367 } 368 } 369 argc -= optind; 370 argv += optind; 371 372 if (aflag ? (argc != 0 || wflag) : argc == 0) 373 usage(); 374 375 fd = open(file, O_RDWR); 376 /* Try with mixer0 but only if using the default device. */ 377 if (fd < 0 && file == mixer_path) { 378 file = _PATH_MIXER0; 379 fd = open(file, O_RDWR); 380 } 381 382 if (fd < 0) 383 err(1, "%s", file); 384 385 for (ndev = 0; ; ndev++) { 386 dinfo.index = ndev; 387 if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) < 0) 388 break; 389 } 390 rfields = calloc(ndev, sizeof *rfields); 391 fields = calloc(ndev, sizeof *fields); 392 infos = calloc(ndev, sizeof *infos); 393 values = calloc(ndev, sizeof *values); 394 395 for (i = 0; i < ndev; i++) { 396 infos[i].index = i; 397 ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]); 398 } 399 400 for (i = 0; i < ndev; i++) { 401 rfields[i].name = infos[i].label.name; 402 rfields[i].valp = &values[i]; 403 rfields[i].infp = &infos[i]; 404 } 405 406 for (i = 0; i < ndev; i++) { 407 values[i].dev = i; 408 values[i].type = infos[i].type; 409 if (infos[i].type != AUDIO_MIXER_CLASS) { 410 values[i].un.value.num_channels = 2; 411 if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0) { 412 values[i].un.value.num_channels = 1; 413 if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0) 414 err(1, "AUDIO_MIXER_READ"); 415 } 416 } 417 } 418 419 for (j = i = 0; i < ndev; i++) { 420 if (infos[i].type != AUDIO_MIXER_CLASS && 421 infos[i].type != -1) { 422 fields[j++] = rfields[i]; 423 for (pos = infos[i].next; pos != AUDIO_MIXER_LAST; 424 pos = infos[pos].next) { 425 fields[j] = rfields[pos]; 426 fields[j].name = catstr(rfields[i].name, 427 infos[pos].label.name); 428 infos[pos].type = -1; 429 j++; 430 } 431 } 432 } 433 434 for (i = 0; i < j; i++) { 435 int cls = fields[i].infp->mixer_class; 436 if (cls >= 0 && cls < ndev) 437 fields[i].name = catstr(infos[cls].label.name, 438 fields[i].name); 439 } 440 441 if (argc == 0 && aflag && !wflag) { 442 for (i = 0; i < j; i++) { 443 prfield(&fields[i], sep, vflag); 444 fprintf(out, "\n"); 445 } 446 } else if (argc > 0 && !aflag) { 447 while (argc--) { 448 if (wflag) 449 wrarg(fd, *argv, sep); 450 else 451 prarg(fd, *argv, sep); 452 argv++; 453 } 454 } else 455 usage(); 456 exit(0); 457 } 458