1 /* $OpenBSD: mixerctl.c,v 1.33 2020/04/04 08:43:08 ratchov 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) == -1) { 226 warn("AUDIO_MIXER_WRITE"); 227 } else if (!quiet) { 228 if (ioctl(fd, AUDIO_MIXER_READ, p->valp) == -1) { 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/audioctl0"; 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 (unveil(file, "w") == -1) 288 err(1, "unveil"); 289 290 if (unveil(NULL, NULL) == -1) 291 err(1, "unveil"); 292 293 if ((fd = open(file, O_WRONLY)) == -1) 294 err(1, "%s", file); 295 296 for (ndev = 0; ; ndev++) { 297 dinfo.index = ndev; 298 if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) == -1) 299 break; 300 } 301 302 if (!ndev) 303 errx(1, "no mixer devices configured"); 304 305 if ((rfields = calloc(ndev, sizeof *rfields)) == NULL || 306 (fields = calloc(ndev, sizeof *fields)) == NULL || 307 (infos = calloc(ndev, sizeof *infos)) == NULL || 308 (values = calloc(ndev, sizeof *values)) == NULL) 309 err(1, "calloc()"); 310 311 for (i = 0; i < ndev; i++) { 312 infos[i].index = i; 313 if (ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]) == -1) { 314 ndev--; 315 i--; 316 continue; 317 } 318 } 319 320 for (i = 0; i < ndev; i++) { 321 strlcpy(rfields[i].name, infos[i].label.name, FIELD_NAME_MAX); 322 rfields[i].valp = &values[i]; 323 rfields[i].infp = &infos[i]; 324 } 325 326 for (i = 0; i < ndev; i++) { 327 values[i].dev = i; 328 values[i].type = infos[i].type; 329 if (infos[i].type != AUDIO_MIXER_CLASS) { 330 values[i].un.value.num_channels = 2; 331 if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) == -1) { 332 values[i].un.value.num_channels = 1; 333 if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) == -1) 334 err(1, "AUDIO_MIXER_READ"); 335 } 336 } 337 } 338 339 for (j = i = 0; i < ndev; i++) { 340 if (infos[i].type != AUDIO_MIXER_CLASS && 341 infos[i].prev == AUDIO_MIXER_LAST) { 342 fields[j++] = rfields[i]; 343 for (pos = infos[i].next; pos != AUDIO_MIXER_LAST; 344 pos = infos[pos].next) { 345 fields[j] = rfields[pos]; 346 catstr(rfields[i].name, infos[pos].label.name, 347 fields[j].name); 348 j++; 349 } 350 } 351 } 352 353 for (i = 0; i < j; i++) { 354 int cls = fields[i].infp->mixer_class; 355 if (cls >= 0 && cls < ndev) 356 catstr(infos[cls].label.name, fields[i].name, 357 fields[i].name); 358 } 359 360 if (!argc && aflag) { 361 for (i = 0; fields[i].name[0] != '\0'; i++) { 362 prfield(&fields[i], sep, vflag, fields[i].valp); 363 printf("\n"); 364 } 365 } else if (argc > 0 && !aflag) { 366 struct field *p; 367 368 while (argc--) { 369 char *q; 370 371 ch = 0; 372 if ((q = strchr(*argv, '=')) != NULL) { 373 *q++ = '\0'; 374 ch = 1; 375 } 376 377 if ((p = findfield(*argv)) == NULL) { 378 warnx("field %s does not exist", *argv); 379 } else if (ch || tflag) { 380 if (tflag && q == NULL) 381 q = "toggle"; 382 rdfield(fd, p, q, qflag, sep); 383 } else { 384 prfield(p, sep, vflag, p->valp); 385 printf("\n"); 386 } 387 388 argv++; 389 } 390 } else 391 usage(); 392 exit(0); 393 } 394 395 __dead void 396 usage(void) 397 { 398 extern char *__progname; /* from crt0.o */ 399 400 fprintf(stderr, 401 "usage: %s [-anv] [-f file]\n" 402 " %s [-nv] [-f file] name ...\n" 403 " %s [-qt] [-f file] name ...\n" 404 " %s [-q] [-f file] name=value ...\n", 405 __progname, __progname, __progname, __progname); 406 407 exit(1); 408 } 409