Home | History | Annotate | Line # | Download | only in mixerctl
mixerctl.c revision 1.27
      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 (at) 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