Home | History | Annotate | Line # | Download | only in mixerctl
mixerctl.c revision 1.28
      1 /*	$NetBSD: mixerctl.c,v 1.28 2021/12/17 13:42:06 christos 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.28 2021/12/17 13:42:06 christos 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 clip(int vol)
    143 {
    144 	if (vol <= AUDIO_MIN_GAIN)
    145 		return AUDIO_MIN_GAIN;
    146 	if (vol >= AUDIO_MAX_GAIN)
    147 		return AUDIO_MAX_GAIN;
    148 	return vol;
    149 }
    150 
    151 static int
    152 rdfield(struct field *p, char *q)
    153 {
    154 	mixer_ctrl_t *m;
    155 	int v, v0, v1, mask;
    156 	int i;
    157 	char *s;
    158 
    159 	m = p->valp;
    160 	switch(m->type) {
    161 	case AUDIO_MIXER_ENUM:
    162 		for (i = 0; i < p->infp->un.e.num_mem; i++)
    163 			if (strcmp(p->infp->un.e.member[i].label.name, q) == 0)
    164 				break;
    165 		if (i < p->infp->un.e.num_mem)
    166 			m->un.ord = p->infp->un.e.member[i].ord;
    167 		else {
    168 			warnx("Bad enum value %s", q);
    169 			return 0;
    170 		}
    171 		break;
    172 	case AUDIO_MIXER_SET:
    173 		mask = 0;
    174 		for (v = 0; q && *q; q = s) {
    175 			s = strchr(q, ',');
    176 			if (s)
    177 				*s++ = 0;
    178 			for (i = 0; i < p->infp->un.s.num_mem; i++)
    179 				if (strcmp(p->infp->un.s.member[i].label.name,
    180 				    q) == 0)
    181 					break;
    182 			if (i < p->infp->un.s.num_mem) {
    183 				mask |= p->infp->un.s.member[i].mask;
    184 			} else {
    185 				warnx("Bad set value %s", q);
    186 				return 0;
    187 			}
    188 		}
    189 		m->un.mask = mask;
    190 		break;
    191 	case AUDIO_MIXER_VALUE:
    192 		if (m->un.value.num_channels == 1) {
    193 			if (sscanf(q, "%d", &v) == 1) {
    194 				m->un.value.level[0] = clip(v);
    195 			} else {
    196 				warnx("Bad number %s", q);
    197 				return 0;
    198 			}
    199 		} else {
    200 			if (sscanf(q, "%d,%d", &v0, &v1) == 2) {
    201 				m->un.value.level[0] = clip(v0);
    202 				m->un.value.level[1] = clip(v1);
    203 			} else if (sscanf(q, "%d", &v) == 1) {
    204 				m->un.value.level[0] =
    205 				    m->un.value.level[1] = clip(v);
    206 			} else {
    207 				warnx("Bad numbers %s", q);
    208 				return 0;
    209 			}
    210 		}
    211 		break;
    212 	default:
    213 		errx(1, "Invalid format.");
    214 	}
    215 	p->changed = 1;
    216 	return 1;
    217 }
    218 
    219 static int
    220 incfield(struct field *p, int inc)
    221 {
    222 	mixer_ctrl_t *m;
    223 	int i, v;
    224 
    225 	m = p->valp;
    226 	switch(m->type) {
    227 	case AUDIO_MIXER_ENUM:
    228 		m->un.ord += inc;
    229 		if (m->un.ord < 0)
    230 			m->un.ord = p->infp->un.e.num_mem - 1;
    231 		if (m->un.ord >= p->infp->un.e.num_mem)
    232 			m->un.ord = 0;
    233 		break;
    234 	case AUDIO_MIXER_SET:
    235 		m->un.mask += inc;
    236 		if (m->un.mask < 0)
    237 			m->un.mask = (1 << p->infp->un.s.num_mem) - 1;
    238 		if (m->un.mask >= (1 << p->infp->un.s.num_mem))
    239 			m->un.mask = 0;
    240 		warnx("Can't ++/-- %s", p->name);
    241 		return 0;
    242 	case AUDIO_MIXER_VALUE:
    243 		if (p->infp->un.v.delta)
    244 			inc *= p->infp->un.v.delta;
    245 		for (i = 0; i < m->un.value.num_channels; i++) {
    246 			v = m->un.value.level[i];
    247 			v += inc;
    248 			m->un.value.level[i] = clip(v);
    249 		}
    250 		break;
    251 	default:
    252 		errx(1, "Invalid format.");
    253 	}
    254 	p->changed = 1;
    255 	return 1;
    256 }
    257 
    258 static void
    259 wrarg(int fd, char *arg, const char *sep)
    260 {
    261 	char *q;
    262 	struct field *p;
    263 	mixer_ctrl_t val;
    264 	int incdec, r;
    265 
    266 	q = strchr(arg, '=');
    267 	if (q == NULL) {
    268 		int l = strlen(arg);
    269 		incdec = 0;
    270 		if (l > 2 && arg[l-2] == '+' && arg[l-1] == '+')
    271 			incdec = 1;
    272 		else if (l > 2 && arg[l-2] == '-' && arg[l-1] == '-')
    273 			incdec = -1;
    274 		else {
    275 			warnx("No `=' in %s", arg);
    276 			return;
    277 		}
    278 		arg[l-2] = 0;
    279 	} else if (q > arg && (*(q-1) == '+' || *(q-1) == '-')) {
    280 		if (sscanf(q+1, "%d", &incdec) != 1) {
    281 			warnx("Bad number %s", q+1);
    282 			return;
    283 		}
    284 		if (*(q-1) == '-')
    285 			incdec *= -1;
    286 		*(q-1) = 0;
    287 		q = NULL;
    288 	} else
    289 		*q++ = 0;
    290 
    291 	p = findfield(arg);
    292 	if (p == NULL) {
    293 		warnx("field %s does not exist", arg);
    294 		return;
    295 	}
    296 
    297 	val = *p->valp;
    298 	if (q != NULL)
    299 		r = rdfield(p, q);
    300 	else
    301 		r = incfield(p, incdec);
    302 	if (r) {
    303 		if (ioctl(fd, AUDIO_MIXER_WRITE, p->valp) < 0)
    304 			warn("AUDIO_MIXER_WRITE");
    305 		else if (sep) {
    306 			*p->valp = val;
    307 			prfield(p, ": ", 0);
    308 			ioctl(fd, AUDIO_MIXER_READ, p->valp);
    309 			printf(" -> ");
    310 			prfield(p, 0, 0);
    311 			printf("\n");
    312 		}
    313 	}
    314 }
    315 
    316 static void
    317 prarg(int fd, char *arg, const char *sep)
    318 {
    319 	struct field *p;
    320 
    321 	p = findfield(arg);
    322 	if (p == NULL)
    323 		warnx("field %s does not exist", arg);
    324 	else
    325 		prfield(p, sep, vflag), fprintf(out, "\n");
    326 }
    327 
    328 static inline void __dead
    329 usage(void)
    330 {
    331 	fprintf(out, "%s [-d file] [-v] [-n] name ...\n", prog);
    332 	fprintf(out, "%s [-d file] [-v] [-n] -w name=value ...\n",prog);
    333 	fprintf(out, "%s [-d file] [-v] [-n] -a\n", prog);
    334 	exit(0);
    335 }
    336 
    337 int
    338 main(int argc, char **argv)
    339 {
    340 	int fd, i, j, ch, pos;
    341 	int aflag = 0, wflag = 0;
    342 	const char *file;
    343 	const char *sep = "=";
    344 	mixer_devinfo_t dinfo;
    345 	int ndev;
    346 
    347 	file = getenv("MIXERDEVICE");
    348 	if (file == NULL)
    349 		file = mixer_path;
    350 
    351 	prog = *argv;
    352 
    353 	while ((ch = getopt(argc, argv, "ad:f:nvw")) != -1) {
    354 		switch(ch) {
    355 		case 'a':
    356 			aflag++;
    357 			break;
    358 		case 'w':
    359 			wflag++;
    360 			break;
    361 		case 'v':
    362 			vflag++;
    363 			break;
    364 		case 'n':
    365 			sep = 0;
    366 			break;
    367 		case 'f': /* compatibility */
    368 		case 'd':
    369 			file = optarg;
    370 			break;
    371 		case '?':
    372 		default:
    373 			usage();
    374 		}
    375 	}
    376 	argc -= optind;
    377 	argv += optind;
    378 
    379 	if (aflag ? (argc != 0 || wflag) : argc == 0)
    380 		usage();
    381 
    382 	fd = open(file, O_RDWR);
    383 	/* Try with mixer0 but only if using the default device. */
    384 	if (fd < 0 && file == mixer_path) {
    385 		file = _PATH_MIXER0;
    386 		fd = open(file, O_RDWR);
    387 	}
    388 
    389 	if (fd < 0)
    390 		err(1, "%s", file);
    391 
    392 	for (ndev = 0; ; ndev++) {
    393 		dinfo.index = ndev;
    394 		if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) < 0)
    395 			break;
    396 	}
    397 	rfields = calloc(ndev, sizeof *rfields);
    398 	fields = calloc(ndev, sizeof *fields);
    399 	infos = calloc(ndev, sizeof *infos);
    400 	values = calloc(ndev, sizeof *values);
    401 
    402 	for (i = 0; i < ndev; i++) {
    403 		infos[i].index = i;
    404 		ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]);
    405 	}
    406 
    407 	for (i = 0; i < ndev; i++) {
    408 		rfields[i].name = infos[i].label.name;
    409 		rfields[i].valp = &values[i];
    410 		rfields[i].infp = &infos[i];
    411 	}
    412 
    413 	for (i = 0; i < ndev; i++) {
    414 		values[i].dev = i;
    415 		values[i].type = infos[i].type;
    416 		if (infos[i].type != AUDIO_MIXER_CLASS) {
    417 			values[i].un.value.num_channels = 2;
    418 			if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0) {
    419 				values[i].un.value.num_channels = 1;
    420 				if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0)
    421 					err(1, "AUDIO_MIXER_READ");
    422 			}
    423 		}
    424 	}
    425 
    426 	for (j = i = 0; i < ndev; i++) {
    427 		if (infos[i].type != AUDIO_MIXER_CLASS &&
    428 		    infos[i].type != -1) {
    429 			fields[j++] = rfields[i];
    430 			for (pos = infos[i].next; pos != AUDIO_MIXER_LAST;
    431 			    pos = infos[pos].next) {
    432 				fields[j] = rfields[pos];
    433 				fields[j].name = catstr(rfields[i].name,
    434 				    infos[pos].label.name);
    435 				infos[pos].type = -1;
    436 				j++;
    437 			}
    438 		}
    439 	}
    440 
    441 	for (i = 0; i < j; i++) {
    442 		int cls = fields[i].infp->mixer_class;
    443 		if (cls >= 0 && cls < ndev)
    444 			fields[i].name = catstr(infos[cls].label.name,
    445 			    fields[i].name);
    446 	}
    447 
    448 	if (argc == 0 && aflag && !wflag) {
    449 		for (i = 0; i < j; i++) {
    450 			prfield(&fields[i], sep, vflag);
    451 			fprintf(out, "\n");
    452 		}
    453 	} else if (argc > 0 && !aflag) {
    454 		while (argc--) {
    455 			if (wflag)
    456 				wrarg(fd, *argv, sep);
    457 			else
    458 				prarg(fd, *argv, sep);
    459 			argv++;
    460 		}
    461 	} else
    462 		usage();
    463 	exit(0);
    464 }
    465