1 /* $OpenBSD: mixerctl.c,v 1.30 2015/02/08 23:40:34 deraadt Exp $ */ 2 /* $NetBSD: mixerctl.c,v 1.11 1998/04/27 16:55:23 augustss Exp $ */ 3 4 /* 5 * Copyright (c) 1997 The NetBSD Foundation, Inc. 6 * All rights reserved. 7 * 8 * Author: Lennart Augustsson, with some code and ideas from 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 32 /* 33 * mixerctl(1) - a program to control audio mixing. 34 */ 35 36 #include <sys/types.h> 37 #include <sys/ioctl.h> 38 #include <sys/audioio.h> 39 40 #include <err.h> 41 #include <errno.h> 42 #include <fcntl.h> 43 #include <limits.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <string.h> 47 #include <unistd.h> 48 49 struct field *findfield(char *); 50 void adjlevel(char **, u_char *, int); 51 void catstr(char *, char *, char *); 52 void prfield(struct field *, char *, int, mixer_ctrl_t *); 53 void rdfield(int, struct field *, char *, int, char *); 54 __dead void usage(void); 55 56 #define FIELD_NAME_MAX 64 57 58 struct field { 59 char name[FIELD_NAME_MAX]; 60 mixer_ctrl_t *valp; 61 mixer_devinfo_t *infp; 62 } *fields, *rfields; 63 64 mixer_ctrl_t *values; 65 mixer_devinfo_t *infos; 66 67 void 68 catstr(char *p, char *q, char *out) 69 { 70 char tmp[FIELD_NAME_MAX]; 71 72 snprintf(tmp, FIELD_NAME_MAX, "%s.%s", p, q); 73 strlcpy(out, tmp, FIELD_NAME_MAX); 74 } 75 76 struct field * 77 findfield(char *name) 78 { 79 int i; 80 for (i = 0; fields[i].name[0] != '\0'; i++) 81 if (strcmp(fields[i].name, name) == 0) 82 return &fields[i]; 83 return (0); 84 } 85 86 #define e_member_name un.e.member[i].label.name 87 #define s_member_name un.s.member[i].label.name 88 89 void 90 prfield(struct field *p, char *sep, int prvalset, mixer_ctrl_t *m) 91 { 92 int i, n; 93 94 if (sep) 95 printf("%s%s", p->name, sep); 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 printf("%s", 101 p->infp->e_member_name); 102 if (prvalset) { 103 printf(" [ "); 104 for (i = 0; i < p->infp->un.e.num_mem; i++) 105 printf("%s ", p->infp->e_member_name); 106 printf("]"); 107 } 108 break; 109 case AUDIO_MIXER_SET: 110 for (n = i = 0; i < p->infp->un.s.num_mem; i++) 111 if (m->un.mask & p->infp->un.s.member[i].mask) 112 printf("%s%s", n++ ? "," : "", 113 p->infp->s_member_name); 114 if (prvalset) { 115 printf(" { "); 116 for (i = 0; i < p->infp->un.s.num_mem; i++) 117 printf("%s ", p->infp->s_member_name); 118 printf("}"); 119 } 120 break; 121 case AUDIO_MIXER_VALUE: 122 if (m->un.value.num_channels == 1) 123 printf("%d", m->un.value.level[0]); 124 else 125 printf("%d,%d", m->un.value.level[0], 126 m->un.value.level[1]); 127 if (prvalset) 128 printf(" %s", p->infp->un.v.units.name); 129 break; 130 default: 131 errx(1, "Invalid format."); 132 } 133 } 134 135 void 136 adjlevel(char **p, u_char *olevel, int more) 137 { 138 char *ep, *cp = *p; 139 long inc; 140 u_char level; 141 142 if (*cp != '+' && *cp != '-') 143 *olevel = 0; /* absolute setting */ 144 145 errno = 0; 146 inc = strtol(cp, &ep, 10); 147 if (*cp == '\0' || (*ep != '\0' && *ep != ',') || 148 (errno == ERANGE && (inc == LONG_MAX || inc == LONG_MIN))) 149 errx(1, "Bad number %s", cp); 150 if (*ep == ',' && !more) 151 errx(1, "Too many values"); 152 *p = ep; 153 154 if (inc < AUDIO_MIN_GAIN - *olevel) 155 level = AUDIO_MIN_GAIN; 156 else if (inc > AUDIO_MAX_GAIN - *olevel) 157 level = AUDIO_MAX_GAIN; 158 else 159 level = *olevel + inc; 160 *olevel = level; 161 } 162 163 void 164 rdfield(int fd, struct field *p, char *q, int quiet, char *sep) 165 { 166 mixer_ctrl_t *m, oldval; 167 int i, mask; 168 char *s; 169 170 oldval = *p->valp; 171 m = p->valp; 172 173 switch (m->type) { 174 case AUDIO_MIXER_ENUM: 175 if (strcmp(q, "toggle") == 0) { 176 for (i = 0; i < p->infp->un.e.num_mem; i++) { 177 if (m->un.ord == p->infp->un.e.member[i].ord) 178 break; 179 } 180 if (i < p->infp->un.e.num_mem) 181 i++; 182 else 183 i = 0; 184 m->un.ord = p->infp->un.e.member[i].ord; 185 break; 186 } 187 for (i = 0; i < p->infp->un.e.num_mem; i++) 188 if (strcmp(p->infp->e_member_name, q) == 0) 189 break; 190 if (i < p->infp->un.e.num_mem) 191 m->un.ord = p->infp->un.e.member[i].ord; 192 else 193 errx(1, "Bad enum value %s", q); 194 break; 195 case AUDIO_MIXER_SET: 196 mask = 0; 197 for (; q && *q; q = s) { 198 if ((s = strchr(q, ',')) != NULL) 199 *s++ = 0; 200 for (i = 0; i < p->infp->un.s.num_mem; i++) 201 if (strcmp(p->infp->s_member_name, q) == 0) 202 break; 203 if (i < p->infp->un.s.num_mem) 204 mask |= p->infp->un.s.member[i].mask; 205 else 206 errx(1, "Bad set value %s", q); 207 } 208 m->un.mask = mask; 209 break; 210 case AUDIO_MIXER_VALUE: 211 if (m->un.value.num_channels == 1) { 212 adjlevel(&q, &m->un.value.level[0], 0); 213 } else { 214 adjlevel(&q, &m->un.value.level[0], 1); 215 if (*q++ == ',') 216 adjlevel(&q, &m->un.value.level[1], 0); 217 else 218 m->un.value.level[1] = m->un.value.level[0]; 219 } 220 break; 221 default: 222 errx(1, "Invalid format."); 223 } 224 225 if (ioctl(fd, AUDIO_MIXER_WRITE, p->valp) < 0) { 226 warn("AUDIO_MIXER_WRITE"); 227 } else if (!quiet) { 228 if (ioctl(fd, AUDIO_MIXER_READ, p->valp) < 0) { 229 warn("AUDIO_MIXER_READ"); 230 } else { 231 if (sep) { 232 prfield(p, ": ", 0, &oldval); 233 printf(" -> "); 234 } 235 prfield(p, NULL, 0, p->valp); 236 printf("\n"); 237 } 238 } 239 } 240 241 int 242 main(int argc, char **argv) 243 { 244 int fd, i, j, ch, pos; 245 int aflag = 0, qflag = 0, vflag = 0, tflag = 0; 246 char *file; 247 char *sep = "="; 248 mixer_devinfo_t dinfo; 249 int ndev; 250 251 if ((file = getenv("MIXERDEVICE")) == 0 || *file == '\0') 252 file = "/dev/mixer"; 253 254 while ((ch = getopt(argc, argv, "af:nqtvw")) != -1) { 255 switch (ch) { 256 case 'a': 257 aflag = 1; 258 break; 259 case 'w': 260 /* compat */ 261 break; 262 case 'v': 263 vflag = 1; 264 break; 265 case 'n': 266 sep = 0; 267 break; 268 case 'f': 269 file = optarg; 270 break; 271 case 'q': 272 qflag = 1; 273 break; 274 case 't': 275 tflag = 1; 276 break; 277 default: 278 usage(); 279 } 280 } 281 argc -= optind; 282 argv += optind; 283 284 if (argc == 0 && tflag == 0) 285 aflag = 1; 286 287 if ((fd = open(file, O_RDWR)) == -1) 288 if ((fd = open(file, O_RDONLY)) == -1) 289 err(1, "%s", file); 290 291 for (ndev = 0; ; ndev++) { 292 dinfo.index = ndev; 293 if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) < 0) 294 break; 295 } 296 297 if (!ndev) 298 errx(1, "no mixer devices configured"); 299 300 if ((rfields = calloc(ndev, sizeof *rfields)) == NULL || 301 (fields = calloc(ndev, sizeof *fields)) == NULL || 302 (infos = calloc(ndev, sizeof *infos)) == NULL || 303 (values = calloc(ndev, sizeof *values)) == NULL) 304 err(1, "calloc()"); 305 306 for (i = 0; i < ndev; i++) { 307 infos[i].index = i; 308 if (ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]) < 0) { 309 ndev--; 310 i--; 311 continue; 312 } 313 } 314 315 for (i = 0; i < ndev; i++) { 316 strlcpy(rfields[i].name, infos[i].label.name, FIELD_NAME_MAX); 317 rfields[i].valp = &values[i]; 318 rfields[i].infp = &infos[i]; 319 } 320 321 for (i = 0; i < ndev; i++) { 322 values[i].dev = i; 323 values[i].type = infos[i].type; 324 if (infos[i].type != AUDIO_MIXER_CLASS) { 325 values[i].un.value.num_channels = 2; 326 if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0) { 327 values[i].un.value.num_channels = 1; 328 if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0) 329 err(1, "AUDIO_MIXER_READ"); 330 } 331 } 332 } 333 334 for (j = i = 0; i < ndev; i++) { 335 if (infos[i].type != AUDIO_MIXER_CLASS && 336 infos[i].prev == AUDIO_MIXER_LAST) { 337 fields[j++] = rfields[i]; 338 for (pos = infos[i].next; pos != AUDIO_MIXER_LAST; 339 pos = infos[pos].next) { 340 fields[j] = rfields[pos]; 341 catstr(rfields[i].name, infos[pos].label.name, 342 fields[j].name); 343 j++; 344 } 345 } 346 } 347 348 for (i = 0; i < j; i++) { 349 int cls = fields[i].infp->mixer_class; 350 if (cls >= 0 && cls < ndev) 351 catstr(infos[cls].label.name, fields[i].name, 352 fields[i].name); 353 } 354 355 if (!argc && aflag) { 356 for (i = 0; fields[i].name[0] != '\0'; i++) { 357 prfield(&fields[i], sep, vflag, fields[i].valp); 358 printf("\n"); 359 } 360 } else if (argc > 0 && !aflag) { 361 struct field *p; 362 363 while (argc--) { 364 char *q; 365 366 ch = 0; 367 if ((q = strchr(*argv, '=')) != NULL) { 368 *q++ = '\0'; 369 ch = 1; 370 } 371 372 if ((p = findfield(*argv)) == NULL) { 373 warnx("field %s does not exist", *argv); 374 } else if (ch || tflag) { 375 if (tflag && q == NULL) 376 q = "toggle"; 377 rdfield(fd, p, q, qflag, sep); 378 } else { 379 prfield(p, sep, vflag, p->valp); 380 printf("\n"); 381 } 382 383 argv++; 384 } 385 } else 386 usage(); 387 exit(0); 388 } 389 390 __dead void 391 usage(void) 392 { 393 extern char *__progname; /* from crt0.o */ 394 395 fprintf(stderr, 396 "usage: %s [-anv] [-f file]\n" 397 " %s [-nv] [-f file] name ...\n" 398 " %s [-qt] [-f file] name ...\n" 399 " %s [-q] [-f file] name=value ...\n", 400 __progname, __progname, __progname, __progname); 401 402 exit(1); 403 } 404