mixer

FreeBSD OSS mixer library implementation and a complete rewrite of mixer(8)
git clone git://git.margiolis.net/mixer.git
Log | Files | Refs | README | LICENSE

mixer.c (11105B)


      1 /*-
      2  * Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
      3  *
      4  * Permission is hereby granted, free of charge, to any person obtaining a copy
      5  * of this software and associated documentation files (the "Software"), to deal
      6  * in the Software without restriction, including without limitation the rights
      7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      8  * copies of the Software, and to permit persons to whom the Software is
      9  * furnished to do so, subject to the following conditions:
     10  *
     11  * The above copyright notice and this permission notice shall be included in
     12  * all copies or substantial portions of the Software.
     13  *
     14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     20  * THE SOFTWARE.
     21  *
     22  * $FreeBSD$
     23  */
     24 
     25 #include <err.h>
     26 #include <errno.h>
     27 #include <mixer.h>
     28 #include <stdio.h>
     29 #include <stdlib.h>
     30 #include <string.h>
     31 #include <unistd.h>
     32 
     33 enum {
     34 	C_VOL = 0,
     35 	C_MUT,
     36 	C_SRC,
     37 };
     38 
     39 static void usage(void) __dead2;
     40 static void initctls(struct mixer *);
     41 static void printall(struct mixer *, int);
     42 static void printminfo(struct mixer *, int);
     43 static void printdev(struct mixer *, int);
     44 static void printrecsrc(struct mixer *, int); /* XXX: change name */
     45 /* Control handlers */
     46 static int mod_dunit(struct mix_dev *, void *);
     47 static int mod_volume(struct mix_dev *, void *);
     48 static int mod_mute(struct mix_dev *, void *);
     49 static int mod_recsrc(struct mix_dev *, void *);
     50 static int print_volume(struct mix_dev *, void *);
     51 static int print_mute(struct mix_dev *, void *);
     52 static int print_recsrc(struct mix_dev *, void *);
     53 
     54 static const mix_ctl_t ctl_dunit = {
     55 	.parent_dev	= NULL,
     56 	.id		= -1,
     57 	.name		= "default_unit",
     58 	.mod		= mod_dunit,
     59 	.print		= NULL
     60 };
     61 
     62 int
     63 main(int argc, char *argv[])
     64 {
     65 	struct mixer *m;
     66 	mix_ctl_t *cp;
     67 	char *name = NULL, buf[NAME_MAX];
     68 	char *p, *q, *devstr, *ctlstr, *valstr = NULL;
     69 	int dunit, i, n, pall = 1, shorthand;
     70 	int aflag = 0, dflag = 0, oflag = 0, sflag = 0;
     71 	int ch;
     72 
     73 	while ((ch = getopt(argc, argv, "ad:f:hos")) != -1) {
     74 		switch (ch) {
     75 		case 'a':
     76 			aflag = 1;
     77 			break;
     78 		case 'd':
     79 			dunit = strtol(optarg, NULL, 10);
     80 			if (errno == EINVAL || errno == ERANGE)
     81 				err(1, "strtol");
     82 			dflag = 1;
     83 			break;
     84 		case 'f':
     85 			name = optarg;
     86 			break;
     87 		case 'o':
     88 			oflag = 1;
     89 			break;
     90 		case 's':
     91 			sflag = 1;
     92 			break;
     93 		case 'h': /* FALLTHROUGH */
     94 		case '?':
     95 		default:
     96 			usage();
     97 		}
     98 	}
     99 	argc -= optind;
    100 	argv += optind;
    101 
    102 	/* Print all mixers and exit. */
    103 	if (aflag) {
    104 		if ((n = mixer_get_nmixers()) < 0)
    105 			err(1, "mixer_get_nmixers");
    106 		for (i = 0; i < n; i++) {
    107 			(void)snprintf(buf, sizeof(buf), "/dev/mixer%d", i);
    108 			if ((m = mixer_open(buf)) == NULL)
    109 				err(1, "mixer_open: %s", buf);
    110 			initctls(m);
    111 			if (sflag)
    112 				printrecsrc(m, oflag);
    113 			else {
    114 				printall(m, oflag);
    115 				if (oflag)
    116 					printf("\n");
    117 			}
    118 			(void)mixer_close(m);
    119 		}
    120 		return (0);
    121 	}
    122 
    123 	if ((m = mixer_open(name)) == NULL)
    124 		err(1, "mixer_open: %s", name);
    125 
    126 	initctls(m);
    127 
    128 	if (dflag && ctl_dunit.mod(m->dev, &dunit) < 0)
    129 		goto parse;
    130 	if (sflag) {
    131 		printrecsrc(m, oflag);
    132 		(void)mixer_close(m);
    133 		return (0);
    134 	}
    135 
    136 parse:
    137 	while (argc > 0) {
    138 		if ((p = strdup(*argv)) == NULL)
    139 			err(1, "strdup(%s)", *argv);
    140 
    141 		/* Check if we're using the shorthand syntax for volume setting. */
    142 		shorthand = 0;
    143 		for (q = p; *q != '\0'; q++) {
    144 			if (*q == '=') {
    145 				q++;
    146 				shorthand = ((*q >= '0' && *q <= '9') ||
    147 				    *q == '+' || *q == '-' || *q == '.');
    148 				break;
    149 			} else if (*q == '.')
    150 				break;
    151 		}
    152 
    153 		/* Split the string into device, control and value. */
    154 		devstr = strsep(&p, ".=");
    155 		if ((m->dev = mixer_get_dev_byname(m, devstr)) == NULL) {
    156 			warnx("%s: no such device", devstr);
    157 			goto next;
    158 		}
    159 		/* Input: `dev`. */
    160 		if (p == NULL) {
    161 			printdev(m, 1);
    162 			pall = 0;
    163 			goto next;
    164 		} else if (shorthand) {
    165 			/*
    166 			 * Input: `dev=N` -> shorthand for `dev.volume=N`.
    167 			 *
    168 			 * We don't care what the rest of the string contains as
    169 			 * long as we're sure the very beginning is right,
    170 			 * mod_volume() will take care of parsing it properly.
    171 			 */
    172 			cp = mixer_get_ctl(m->dev, C_VOL);
    173 			cp->mod(cp->parent_dev, p);
    174 			goto next;
    175 		}
    176 		ctlstr = strsep(&p, "=");
    177 		if ((cp = mixer_get_ctl_byname(m->dev, ctlstr)) == NULL) {
    178 			warnx("%s.%s: no such control", devstr, ctlstr);
    179 			goto next;
    180 		}
    181 		/* Input: `dev.control`. */
    182 		if (p == NULL) {
    183 			(void)cp->print(cp->parent_dev, cp->name);
    184 			pall = 0;
    185 			goto next;
    186 		}
    187 		valstr = p;
    188 		/* Input: `dev.control=val`. */
    189 		cp->mod(cp->parent_dev, valstr);
    190 next:
    191 		free(p);
    192 		argc--;
    193 		argv++;
    194 	}
    195 
    196 	if (pall)
    197 		printall(m, oflag);
    198 	(void)mixer_close(m);
    199 
    200 	return (0);
    201 }
    202 
    203 static void __dead2
    204 usage(void)
    205 {
    206 	fprintf(stderr, "usage: %1$s [-f device] [-d unit] [-os] [dev[.control[=value]]] ...\n"
    207 	    "       %1$s [-d unit] [-os] -a\n"
    208 	    "       %1$s -h\n", getprogname());
    209 	exit(1);
    210 }
    211 
    212 static void
    213 initctls(struct mixer *m)
    214 {
    215 	struct mix_dev *dp;
    216 	int rc = 0;
    217 
    218 	TAILQ_FOREACH(dp, &m->devs, devs) {
    219 		rc += mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume);
    220 		rc += mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute);
    221 		rc += mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc);
    222 	}
    223 	if (rc) {
    224 		(void)mixer_close(m);
    225 		err(1, "cannot make controls");
    226 	}
    227 }
    228 
    229 static void
    230 printall(struct mixer *m, int oflag)
    231 {
    232 	struct mix_dev *dp;
    233 
    234 	printminfo(m, oflag);
    235 	TAILQ_FOREACH(dp, &m->devs, devs) {
    236 		m->dev = dp;
    237 		printdev(m, oflag);
    238 	}
    239 }
    240 
    241 static void
    242 printminfo(struct mixer *m, int oflag)
    243 {
    244 	int playrec = MIX_MODE_PLAY | MIX_MODE_REC;
    245 
    246 	if (oflag)
    247 		return;
    248 	printf("%s:", m->mi.name);
    249 	if (*m->ci.longname != '\0')
    250 		printf(" <%s>", m->ci.longname);
    251 	if (*m->ci.hw_info != '\0')
    252 		printf(" %s", m->ci.hw_info);
    253 
    254 	if (m->mode != 0)
    255 		printf(" (");
    256 	if (m->mode & MIX_MODE_PLAY)
    257 		printf("play");
    258 	if ((m->mode & playrec) == playrec)
    259 		printf("/");
    260 	if (m->mode & MIX_MODE_REC)
    261 		printf("rec");
    262 	if (m->mode != 0)
    263 		printf(")");
    264 
    265 	if (m->f_default)
    266 		printf(" (default)");
    267 	printf("\n");
    268 }
    269 
    270 static void
    271 printdev(struct mixer *m, int oflag)
    272 {
    273 	struct mix_dev *d = m->dev;
    274 	mix_ctl_t *cp;
    275 
    276 	if (!oflag) {
    277 		printf("    %-10s= %.2f:%.2f    ",
    278 		    d->name, d->vol.left, d->vol.right);
    279 		if (!MIX_ISREC(m, d->devno))
    280 			printf(" pbk");
    281 		if (MIX_ISREC(m, d->devno))
    282 			printf(" rec");
    283 		if (MIX_ISRECSRC(m, d->devno))
    284 			printf(" src");
    285 		if (MIX_ISMUTE(m, d->devno))
    286 			printf(" mute");
    287 		printf("\n");
    288 	} else {
    289 		TAILQ_FOREACH(cp, &d->ctls, ctls) {
    290 			(void)cp->print(cp->parent_dev, cp->name);
    291 		}
    292 	}
    293 }
    294 
    295 static void
    296 printrecsrc(struct mixer *m, int oflag)
    297 {
    298 	struct mix_dev *dp;
    299 	int n = 0;
    300 
    301 	if (!m->recmask)
    302 		return;
    303 	if (!oflag)
    304 		printf("%s: ", m->mi.name);
    305 	TAILQ_FOREACH(dp, &m->devs, devs) {
    306 		if (MIX_ISRECSRC(m, dp->devno)) {
    307 			if (n++ && !oflag)
    308 				printf(", ");
    309 			printf("%s", dp->name);
    310 			if (oflag)
    311 				printf(".%s=+%s",
    312 				    mixer_get_ctl(dp, C_SRC)->name, n ? " " : "");
    313 		}
    314 	}
    315 	printf("\n");
    316 }
    317 
    318 static int
    319 mod_dunit(struct mix_dev *d, void *p)
    320 {
    321 	int dunit = *((int *)p);
    322 	int n;
    323 
    324 	if ((n = mixer_get_dunit()) < 0) {
    325 		warn("cannot get default unit");
    326 		return (-1);
    327 	}
    328 	if (mixer_set_dunit(d->parent_mixer, dunit) < 0) {
    329 		warn("cannot set default unit to: %d", dunit);
    330 		return (-1);
    331 	}
    332 	printf("%s: %d -> %d\n", ctl_dunit.name, n, dunit);
    333 
    334 	return (0);
    335 }
    336 
    337 static int
    338 mod_volume(struct mix_dev *d, void *p)
    339 {
    340 	struct mixer *m;
    341 	mix_ctl_t *cp;
    342 	mix_volume_t v;
    343 	const char *val;
    344 	char lstr[8], rstr[8];
    345 	float lprev, rprev, lrel, rrel;
    346 	int n;
    347 
    348 	m = d->parent_mixer;
    349 	cp = mixer_get_ctl(m->dev, C_VOL);
    350 	val = p;
    351 	n = sscanf(val, "%7[^:]:%7s", lstr, rstr);
    352 	if (n == EOF) {
    353 		warnx("invalid volume value: %s", val);
    354 		return (-1);
    355 	}
    356 	lrel = rrel = 0;
    357 	if (n > 0) {
    358 		if (*lstr == '+' || *lstr == '-')
    359 			lrel = rrel = 1;
    360 		v.left = strtof(lstr, NULL);
    361 
    362 		/* be backwards compatible */
    363 		if (strstr(lstr, ".") == NULL)
    364 			v.left /= 100.0f;
    365 	}
    366 	if (n > 1) {
    367 		if (*rstr == '+' || *rstr == '-')
    368 			rrel = 1;
    369 		v.right = strtof(rstr, NULL);
    370 
    371 		/* be backwards compatible */
    372 		if (strstr(rstr, ".") == NULL)
    373 			v.right /= 100.0f;
    374 	}
    375 	switch (n) {
    376 	case 1:
    377 		v.right = v.left; /* FALLTHROUGH */
    378 	case 2:
    379 		if (lrel)
    380 			v.left += m->dev->vol.left;
    381 		if (rrel)
    382 			v.right += m->dev->vol.right;
    383 
    384 		if (v.left < MIX_VOLMIN)
    385 			v.left = MIX_VOLMIN;
    386 		else if (v.left > MIX_VOLMAX)
    387 			v.left = MIX_VOLMAX;
    388 		if (v.right < MIX_VOLMIN)
    389 			v.right = MIX_VOLMIN;
    390 		else if (v.right > MIX_VOLMAX)
    391 			v.right = MIX_VOLMAX;
    392 
    393 		lprev = m->dev->vol.left;
    394 		rprev = m->dev->vol.right;
    395 		if (mixer_set_vol(m, v) < 0)
    396 			warn("%s.%s=%.2f:%.2f",
    397 			    m->dev->name, cp->name, v.left, v.right);
    398 		else
    399 			printf("%s.%s: %.2f:%.2f -> %.2f:%.2f\n",
    400 			   m->dev->name, cp->name, lprev, rprev, v.left, v.right);
    401 	}
    402 
    403 	return (0);
    404 }
    405 
    406 static int
    407 mod_mute(struct mix_dev *d, void *p)
    408 {
    409 	struct mixer *m;
    410 	mix_ctl_t *cp;
    411 	const char *val;
    412 	int n, opt = -1;
    413 
    414 	m = d->parent_mixer;
    415 	cp = mixer_get_ctl(m->dev, C_MUT);
    416 	val = p;
    417 	switch (*val) {
    418 	case '0':
    419 		opt = MIX_UNMUTE;
    420 		break;
    421 	case '1':
    422 		opt = MIX_MUTE;
    423 		break;
    424 	case '^':
    425 		opt = MIX_TOGGLEMUTE;
    426 		break;
    427 	default:
    428 		warnx("%c: no such modifier", *val);
    429 		return (-1);
    430 	}
    431 	n = MIX_ISMUTE(m, m->dev->devno);
    432 	if (mixer_set_mute(m, opt) < 0)
    433 		warn("%s.%s=%c", m->dev->name, cp->name, *val);
    434 	else
    435 		printf("%s.%s: %d -> %d\n",
    436 		    m->dev->name, cp->name, n, MIX_ISMUTE(m, m->dev->devno));
    437 
    438 	return (0);
    439 }
    440 
    441 static int
    442 mod_recsrc(struct mix_dev *d, void *p)
    443 {
    444 	struct mixer *m;
    445 	mix_ctl_t *cp;
    446 	const char *val;
    447 	int n, opt = -1;
    448 
    449 	m = d->parent_mixer;
    450 	cp = mixer_get_ctl(m->dev, C_SRC);
    451 	val = p;
    452 	switch (*val) {
    453 	case '+':
    454 		opt = MIX_ADDRECSRC;
    455 		break;
    456 	case '-':
    457 		opt = MIX_REMOVERECSRC;
    458 		break;
    459 	case '=':
    460 		opt = MIX_SETRECSRC;
    461 		break;
    462 	case '^':
    463 		opt = MIX_TOGGLERECSRC;
    464 		break;
    465 	default:
    466 		warnx("%c: no such modifier", *val);
    467 		return (-1);
    468 	}
    469 	n = MIX_ISRECSRC(m, m->dev->devno);
    470 	if (mixer_mod_recsrc(m, opt) < 0)
    471 		warn("%s.%s=%c", m->dev->name, cp->name, *val);
    472 	else
    473 		printf("%s.%s: %d -> %d\n",
    474 		    m->dev->name, cp->name, n, MIX_ISRECSRC(m, m->dev->devno));
    475 
    476 	return (0);
    477 }
    478 
    479 static int
    480 print_volume(struct mix_dev *d, void *p)
    481 {
    482 	struct mixer *m = d->parent_mixer;
    483 	const char *ctl_name = p;
    484 
    485 	printf("%s.%s=%.2f:%.2f\n",
    486 	    m->dev->name, ctl_name, m->dev->vol.left, m->dev->vol.right);
    487 
    488 	return (0);
    489 }
    490 
    491 static int
    492 print_mute(struct mix_dev *d, void *p)
    493 {
    494 	struct mixer *m = d->parent_mixer;
    495 	const char *ctl_name = p;
    496 
    497 	printf("%s.%s=%d\n", m->dev->name, ctl_name, MIX_ISMUTE(m, m->dev->devno));
    498 
    499 	return (0);
    500 }
    501 
    502 static int
    503 print_recsrc(struct mix_dev *d, void *p)
    504 {
    505 	struct mixer *m = d->parent_mixer;
    506 	const char *ctl_name = p;
    507 
    508 	if (!MIX_ISRECSRC(m, m->dev->devno))
    509 		return (-1);
    510 	printf("%s.%s=+\n", m->dev->name, ctl_name);
    511 
    512 	return (0);
    513 }