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 (11091B)


      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 static int set_dunit(struct mixer *, int);
     46 /* Control handlers */
     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 int
     55 main(int argc, char *argv[])
     56 {
     57 	struct mixer *m;
     58 	mix_ctl_t *cp;
     59 	char *name = NULL, buf[NAME_MAX];
     60 	char *p, *q, *devstr, *ctlstr, *valstr = NULL;
     61 	int dunit, i, n, pall = 1, shorthand;
     62 	int aflag = 0, dflag = 0, oflag = 0, sflag = 0;
     63 	int ch;
     64 
     65 	while ((ch = getopt(argc, argv, "ad:f:hos")) != -1) {
     66 		switch (ch) {
     67 		case 'a':
     68 			aflag = 1;
     69 			break;
     70 		case 'd':
     71 			dunit = strtol(optarg, NULL, 10);
     72 			if (errno == EINVAL || errno == ERANGE)
     73 				err(1, "strtol");
     74 			dflag = 1;
     75 			break;
     76 		case 'f':
     77 			name = optarg;
     78 			break;
     79 		case 'o':
     80 			oflag = 1;
     81 			break;
     82 		case 's':
     83 			sflag = 1;
     84 			break;
     85 		case 'h': /* FALLTHROUGH */
     86 		case '?':
     87 		default:
     88 			usage();
     89 		}
     90 	}
     91 	argc -= optind;
     92 	argv += optind;
     93 
     94 	/* Print all mixers and exit. */
     95 	if (aflag) {
     96 		if ((n = mixer_get_nmixers()) < 0)
     97 			err(1, "mixer_get_nmixers");
     98 		for (i = 0; i < n; i++) {
     99 			(void)snprintf(buf, sizeof(buf), "/dev/mixer%d", i);
    100 			if ((m = mixer_open(buf)) == NULL)
    101 				err(1, "mixer_open: %s", buf);
    102 			initctls(m);
    103 			if (sflag)
    104 				printrecsrc(m, oflag);
    105 			else {
    106 				printall(m, oflag);
    107 				if (oflag)
    108 					printf("\n");
    109 			}
    110 			(void)mixer_close(m);
    111 		}
    112 		return (0);
    113 	}
    114 
    115 	if ((m = mixer_open(name)) == NULL)
    116 		err(1, "mixer_open: %s", name);
    117 
    118 	initctls(m);
    119 
    120 	if (dflag && set_dunit(m, dunit) < 0)
    121 		goto parse;
    122 	if (sflag) {
    123 		printrecsrc(m, oflag);
    124 		(void)mixer_close(m);
    125 		return (0);
    126 	}
    127 
    128 parse:
    129 	while (argc > 0) {
    130 		if ((p = strdup(*argv)) == NULL)
    131 			err(1, "strdup(%s)", *argv);
    132 
    133 		/* Check if we're using the shorthand syntax for volume setting. */
    134 		shorthand = 0;
    135 		for (q = p; *q != '\0'; q++) {
    136 			if (*q == '=') {
    137 				q++;
    138 				shorthand = ((*q >= '0' && *q <= '9') ||
    139 				    *q == '+' || *q == '-' || *q == '.');
    140 				break;
    141 			} else if (*q == '.')
    142 				break;
    143 		}
    144 
    145 		/* Split the string into device, control and value. */
    146 		devstr = strsep(&p, ".=");
    147 		if ((m->dev = mixer_get_dev_byname(m, devstr)) == NULL) {
    148 			warnx("%s: no such device", devstr);
    149 			goto next;
    150 		}
    151 		/* Input: `dev`. */
    152 		if (p == NULL) {
    153 			printdev(m, 1);
    154 			pall = 0;
    155 			goto next;
    156 		} else if (shorthand) {
    157 			/*
    158 			 * Input: `dev=N` -> shorthand for `dev.volume=N`.
    159 			 *
    160 			 * We don't care what the rest of the string contains as
    161 			 * long as we're sure the very beginning is right,
    162 			 * mod_volume() will take care of parsing it properly.
    163 			 */
    164 			cp = mixer_get_ctl(m->dev, C_VOL);
    165 			cp->mod(cp->parent_dev, p);
    166 			goto next;
    167 		}
    168 		ctlstr = strsep(&p, "=");
    169 		if ((cp = mixer_get_ctl_byname(m->dev, ctlstr)) == NULL) {
    170 			warnx("%s.%s: no such control", devstr, ctlstr);
    171 			goto next;
    172 		}
    173 		/* Input: `dev.control`. */
    174 		if (p == NULL) {
    175 			(void)cp->print(cp->parent_dev, cp->name);
    176 			pall = 0;
    177 			goto next;
    178 		}
    179 		valstr = p;
    180 		/* Input: `dev.control=val`. */
    181 		cp->mod(cp->parent_dev, valstr);
    182 next:
    183 		free(p);
    184 		argc--;
    185 		argv++;
    186 	}
    187 
    188 	if (pall)
    189 		printall(m, oflag);
    190 	(void)mixer_close(m);
    191 
    192 	return (0);
    193 }
    194 
    195 static void __dead2
    196 usage(void)
    197 {
    198 	fprintf(stderr, "usage: %1$s [-f device] [-d unit] [-os] [dev[.control[=value]]] ...\n"
    199 	    "       %1$s [-d unit] [-os] -a\n"
    200 	    "       %1$s -h\n", getprogname());
    201 	exit(1);
    202 }
    203 
    204 static void
    205 initctls(struct mixer *m)
    206 {
    207 	struct mix_dev *dp;
    208 	int rc = 0;
    209 
    210 	TAILQ_FOREACH(dp, &m->devs, devs) {
    211 		rc += mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume);
    212 		rc += mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute);
    213 		rc += mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc);
    214 	}
    215 	if (rc) {
    216 		(void)mixer_close(m);
    217 		err(1, "cannot make controls");
    218 	}
    219 }
    220 
    221 static void
    222 printall(struct mixer *m, int oflag)
    223 {
    224 	struct mix_dev *dp;
    225 
    226 	printminfo(m, oflag);
    227 	TAILQ_FOREACH(dp, &m->devs, devs) {
    228 		m->dev = dp;
    229 		printdev(m, oflag);
    230 	}
    231 }
    232 
    233 static void
    234 printminfo(struct mixer *m, int oflag)
    235 {
    236 	int playrec = MIX_MODE_PLAY | MIX_MODE_REC;
    237 
    238 	if (oflag)
    239 		return;
    240 	printf("%s:", m->mi.name);
    241 	if (*m->ci.longname != '\0')
    242 		printf(" <%s>", m->ci.longname);
    243 	if (*m->ci.hw_info != '\0')
    244 		printf(" %s", m->ci.hw_info);
    245 
    246 	if (m->mode != 0)
    247 		printf(" (");
    248 	if (m->mode & MIX_MODE_PLAY)
    249 		printf("play");
    250 	if ((m->mode & playrec) == playrec)
    251 		printf("/");
    252 	if (m->mode & MIX_MODE_REC)
    253 		printf("rec");
    254 	if (m->mode != 0)
    255 		printf(")");
    256 
    257 	if (m->f_default)
    258 		printf(" (default)");
    259 	printf("\n");
    260 }
    261 
    262 static void
    263 printdev(struct mixer *m, int oflag)
    264 {
    265 	struct mix_dev *d = m->dev;
    266 	mix_ctl_t *cp;
    267 
    268 	if (!oflag) {
    269 		printf("    %-10s= %.2f:%.2f    ",
    270 		    d->name, d->vol.left, d->vol.right);
    271 		if (!MIX_ISREC(m, d->devno))
    272 			printf(" pbk");
    273 		if (MIX_ISREC(m, d->devno))
    274 			printf(" rec");
    275 		if (MIX_ISRECSRC(m, d->devno))
    276 			printf(" src");
    277 		if (MIX_ISMUTE(m, d->devno))
    278 			printf(" mute");
    279 		printf("\n");
    280 	} else {
    281 		TAILQ_FOREACH(cp, &d->ctls, ctls) {
    282 			(void)cp->print(cp->parent_dev, cp->name);
    283 		}
    284 	}
    285 }
    286 
    287 static void
    288 printrecsrc(struct mixer *m, int oflag)
    289 {
    290 	struct mix_dev *dp;
    291 	int n = 0;
    292 
    293 	if (!m->recmask)
    294 		return;
    295 	if (!oflag)
    296 		printf("%s: ", m->mi.name);
    297 	TAILQ_FOREACH(dp, &m->devs, devs) {
    298 		if (MIX_ISRECSRC(m, dp->devno)) {
    299 			if (n++ && !oflag)
    300 				printf(", ");
    301 			printf("%s", dp->name);
    302 			if (oflag)
    303 				printf(".%s=+%s",
    304 				    mixer_get_ctl(dp, C_SRC)->name, n ? " " : "");
    305 		}
    306 	}
    307 	printf("\n");
    308 }
    309 
    310 static int
    311 set_dunit(struct mixer *m, int dunit)
    312 {
    313 	int n;
    314 
    315 	if ((n = mixer_get_dunit()) < 0) {
    316 		warn("cannot get default unit");
    317 		return (-1);
    318 	}
    319 	if (mixer_set_dunit(m, dunit) < 0) {
    320 		warn("cannot set default unit to: %d", dunit);
    321 		return (-1);
    322 	}
    323 	printf("default_unit: %d -> %d\n", n, dunit);
    324 
    325 	return (0);
    326 }
    327 
    328 static int
    329 mod_volume(struct mix_dev *d, void *p)
    330 {
    331 	struct mixer *m;
    332 	mix_ctl_t *cp;
    333 	mix_volume_t v;
    334 	const char *val;
    335 	char *endp, lstr[8], rstr[8];
    336 	float lprev, rprev, lrel, rrel;
    337 	int n;
    338 
    339 	m = d->parent_mixer;
    340 	cp = mixer_get_ctl(m->dev, C_VOL);
    341 	val = p;
    342 	n = sscanf(val, "%7[^:]:%7s", lstr, rstr);
    343 	if (n == EOF) {
    344 		warnx("invalid volume value: %s", val);
    345 		return (-1);
    346 	}
    347 	lrel = rrel = 0;
    348 	if (n > 0) {
    349 		if (*lstr == '+' || *lstr == '-')
    350 			lrel = 1;
    351 		v.left = strtof(lstr, &endp);
    352 		if (*endp != '\0' && (*endp != '%' || *(endp + 1) != '\0')) {
    353 			warnx("invalid volume value: %s", lstr);
    354 			return (-1);
    355 		}
    356 
    357 		if (*endp == '%')
    358 			v.left /= 100.0f;
    359 	}
    360 	if (n > 1) {
    361 		if (*rstr == '+' || *rstr == '-')
    362 			rrel = 1;
    363 		v.right = strtof(rstr, &endp);
    364 		if (*endp != '\0' && (*endp != '%' || *(endp + 1) != '\0')) {
    365 			warnx("invalid volume value: %s", rstr);
    366 			return (-1);
    367 		}
    368 
    369 		if (*endp == '%')
    370 			v.right /= 100.0f;
    371 	}
    372 	switch (n) {
    373 	case 1:
    374 		v.right = v.left; /* FALLTHROUGH */
    375 		rrel = lrel;
    376 	case 2:
    377 		if (lrel)
    378 			v.left += m->dev->vol.left;
    379 		if (rrel)
    380 			v.right += m->dev->vol.right;
    381 
    382 		if (v.left < MIX_VOLMIN)
    383 			v.left = MIX_VOLMIN;
    384 		else if (v.left > MIX_VOLMAX)
    385 			v.left = MIX_VOLMAX;
    386 		if (v.right < MIX_VOLMIN)
    387 			v.right = MIX_VOLMIN;
    388 		else if (v.right > MIX_VOLMAX)
    389 			v.right = MIX_VOLMAX;
    390 
    391 		lprev = m->dev->vol.left;
    392 		rprev = m->dev->vol.right;
    393 		if (mixer_set_vol(m, v) < 0)
    394 			warn("%s.%s=%.2f:%.2f",
    395 			    m->dev->name, cp->name, v.left, v.right);
    396 		else
    397 			printf("%s.%s: %.2f:%.2f -> %.2f:%.2f\n",
    398 			   m->dev->name, cp->name, lprev, rprev, v.left, v.right);
    399 	}
    400 
    401 	return (0);
    402 }
    403 
    404 static int
    405 mod_mute(struct mix_dev *d, void *p)
    406 {
    407 	struct mixer *m;
    408 	mix_ctl_t *cp;
    409 	const char *val;
    410 	int n, opt = -1;
    411 
    412 	m = d->parent_mixer;
    413 	cp = mixer_get_ctl(m->dev, C_MUT);
    414 	val = p;
    415 	switch (*val) {
    416 	case '0':
    417 		opt = MIX_UNMUTE;
    418 		break;
    419 	case '1':
    420 		opt = MIX_MUTE;
    421 		break;
    422 	case '^':
    423 		opt = MIX_TOGGLEMUTE;
    424 		break;
    425 	default:
    426 		warnx("%c: no such modifier", *val);
    427 		return (-1);
    428 	}
    429 	n = MIX_ISMUTE(m, m->dev->devno);
    430 	if (mixer_set_mute(m, opt) < 0)
    431 		warn("%s.%s=%c", m->dev->name, cp->name, *val);
    432 	else
    433 		printf("%s.%s: %d -> %d\n",
    434 		    m->dev->name, cp->name, n, MIX_ISMUTE(m, m->dev->devno));
    435 
    436 	return (0);
    437 }
    438 
    439 static int
    440 mod_recsrc(struct mix_dev *d, void *p)
    441 {
    442 	struct mixer *m;
    443 	mix_ctl_t *cp;
    444 	const char *val;
    445 	int n, opt = -1;
    446 
    447 	m = d->parent_mixer;
    448 	cp = mixer_get_ctl(m->dev, C_SRC);
    449 	val = p;
    450 	switch (*val) {
    451 	case '+':
    452 		opt = MIX_ADDRECSRC;
    453 		break;
    454 	case '-':
    455 		opt = MIX_REMOVERECSRC;
    456 		break;
    457 	case '=':
    458 		opt = MIX_SETRECSRC;
    459 		break;
    460 	case '^':
    461 		opt = MIX_TOGGLERECSRC;
    462 		break;
    463 	default:
    464 		warnx("%c: no such modifier", *val);
    465 		return (-1);
    466 	}
    467 	n = MIX_ISRECSRC(m, m->dev->devno);
    468 	if (mixer_mod_recsrc(m, opt) < 0)
    469 		warn("%s.%s=%c", m->dev->name, cp->name, *val);
    470 	else
    471 		printf("%s.%s: %d -> %d\n",
    472 		    m->dev->name, cp->name, n, MIX_ISRECSRC(m, m->dev->devno));
    473 
    474 	return (0);
    475 }
    476 
    477 static int
    478 print_volume(struct mix_dev *d, void *p)
    479 {
    480 	struct mixer *m = d->parent_mixer;
    481 	const char *ctl_name = p;
    482 
    483 	printf("%s.%s=%.2f:%.2f\n",
    484 	    m->dev->name, ctl_name, m->dev->vol.left, m->dev->vol.right);
    485 
    486 	return (0);
    487 }
    488 
    489 static int
    490 print_mute(struct mix_dev *d, void *p)
    491 {
    492 	struct mixer *m = d->parent_mixer;
    493 	const char *ctl_name = p;
    494 
    495 	printf("%s.%s=%d\n", m->dev->name, ctl_name, MIX_ISMUTE(m, m->dev->devno));
    496 
    497 	return (0);
    498 }
    499 
    500 static int
    501 print_recsrc(struct mix_dev *d, void *p)
    502 {
    503 	struct mixer *m = d->parent_mixer;
    504 	const char *ctl_name = p;
    505 
    506 	if (!MIX_ISRECSRC(m, m->dev->devno))
    507 		return (-1);
    508 	printf("%s.%s=+\n", m->dev->name, ctl_name);
    509 
    510 	return (0);
    511 }