diff options
Diffstat (limited to 'usr.sbin/mixer/mixer.c')
| -rw-r--r-- | usr.sbin/mixer/mixer.c | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/usr.sbin/mixer/mixer.c b/usr.sbin/mixer/mixer.c new file mode 100644 index 000000000000..15e0eae69952 --- /dev/null +++ b/usr.sbin/mixer/mixer.c @@ -0,0 +1,597 @@ +/*- + * Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <sys/sysctl.h> +#include <sys/wait.h> + +#include <err.h> +#include <errno.h> +#include <mixer.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +enum { + C_VOL = 0, + C_MUT, + C_SRC, +}; + +static void usage(void) __dead2; +static void initctls(struct mixer *); +static void printall(struct mixer *, int); +static void printminfo(struct mixer *, int); +static void printdev(struct mixer *, int); +static void printrecsrc(struct mixer *, int); /* XXX: change name */ +static int set_dunit(struct mixer *, int, char *); +/* Control handlers */ +static int mod_volume(struct mix_dev *, void *); +static int mod_mute(struct mix_dev *, void *); +static int mod_recsrc(struct mix_dev *, void *); +static int print_volume(struct mix_dev *, void *); +static int print_mute(struct mix_dev *, void *); +static int print_recsrc(struct mix_dev *, void *); + +int +main(int argc, char *argv[]) +{ + struct mixer *m; + mix_ctl_t *cp; + char *name = NULL, buf[NAME_MAX], *vctl = NULL; + char *p, *q, *devstr, *ctlstr, *valstr = NULL; + int dunit, i, n, pall = 1, shorthand; + int aflag = 0, dflag = 0, oflag = 0, sflag = 0; + int ch; + + while ((ch = getopt(argc, argv, "ad:f:hosV:")) != -1) { + switch (ch) { + case 'a': + aflag = 1; + break; + case 'd': + if (strncmp(optarg, "pcm", 3) == 0) + optarg += 3; + errno = 0; + dunit = strtol(optarg, NULL, 10); + if (errno == EINVAL || errno == ERANGE) + err(1, "strtol(%s)", optarg); + dflag = 1; + break; + case 'f': + name = optarg; + break; + case 'o': + oflag = 1; + break; + case 's': + sflag = 1; + break; + case 'V': + vctl = optarg; + break; + case 'h': /* FALLTHROUGH */ + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + /* Print all mixers and exit. */ + if (aflag) { + if ((n = mixer_get_nmixers()) < 0) + errx(1, "no mixers present in the system"); + for (i = 0; i < n; i++) { + (void)mixer_get_path(buf, sizeof(buf), i); + if ((m = mixer_open(buf)) == NULL) + continue; + initctls(m); + if (sflag) + printrecsrc(m, oflag); + else { + printall(m, oflag); + if (oflag) + printf("\n"); + } + (void)mixer_close(m); + } + return (0); + } + + if ((m = mixer_open(name)) == NULL) + errx(1, "%s: no such mixer", name); + + initctls(m); + + if (dflag) { + if (set_dunit(m, dunit, vctl) < 0) + goto parse; + else { + /* + * Open current mixer since we changed the default + * unit, otherwise we'll print and apply changes to the + * old one. + */ + (void)mixer_close(m); + if ((m = mixer_open(NULL)) == NULL) + errx(1, "cannot open default mixer"); + initctls(m); + } + } + if (sflag) { + printrecsrc(m, oflag); + (void)mixer_close(m); + return (0); + } + +parse: + while (argc > 0) { + char *orig; + + if ((orig = p = strdup(*argv)) == NULL) + err(1, "strdup(%s)", *argv); + + /* Check if we're using the shorthand syntax for volume setting. */ + shorthand = 0; + for (q = p; *q != '\0'; q++) { + if (*q == '=') { + q++; + shorthand = ((*q >= '0' && *q <= '9') || + *q == '+' || *q == '-' || *q == '.'); + break; + } else if (*q == '.') + break; + } + + /* Split the string into device, control and value. */ + devstr = strsep(&p, ".="); + if ((m->dev = mixer_get_dev_byname(m, devstr)) == NULL) { + warnx("%s: no such device", devstr); + goto next; + } + /* Input: `dev`. */ + if (p == NULL) { + printdev(m, 1); + pall = 0; + goto next; + } else if (shorthand) { + /* + * Input: `dev=N` -> shorthand for `dev.volume=N`. + * + * We don't care what the rest of the string contains as + * long as we're sure the very beginning is right, + * mod_volume() will take care of parsing it properly. + */ + cp = mixer_get_ctl(m->dev, C_VOL); + cp->mod(cp->parent_dev, p); + goto next; + } + ctlstr = strsep(&p, "="); + if ((cp = mixer_get_ctl_byname(m->dev, ctlstr)) == NULL) { + warnx("%s.%s: no such control", devstr, ctlstr); + goto next; + } + /* Input: `dev.control`. */ + if (p == NULL) { + (void)cp->print(cp->parent_dev, cp->name); + pall = 0; + goto next; + } + valstr = p; + /* Input: `dev.control=val`. */ + cp->mod(cp->parent_dev, valstr); +next: + free(orig); + argc--; + argv++; + } + + if (pall) + printall(m, oflag); + (void)mixer_close(m); + + return (0); +} + +static void __dead2 +usage(void) +{ + fprintf(stderr, "usage: %1$s [-f device] [-d pcmN | N " + "[-V voss_device:mode]] [-os] [dev[.control[=value]]] ...\n" + " %1$s [-os] -a\n" + " %1$s -h\n", getprogname()); + exit(1); +} + +static void +initctls(struct mixer *m) +{ + struct mix_dev *dp; + int rc = 0; + + TAILQ_FOREACH(dp, &m->devs, devs) { + rc += mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume); + rc += mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute); + rc += mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc); + } + if (rc) { + (void)mixer_close(m); + errx(1, "cannot make mixer controls"); + } +} + +static void +printall(struct mixer *m, int oflag) +{ + struct mix_dev *dp; + + printminfo(m, oflag); + TAILQ_FOREACH(dp, &m->devs, devs) { + m->dev = dp; + printdev(m, oflag); + } +} + +static void +printminfo(struct mixer *m, int oflag) +{ + int playrec = MIX_MODE_PLAY | MIX_MODE_REC; + + if (oflag) + return; + printf("%s:", m->mi.name); + if (*m->ci.longname != '\0') + printf(" <%s>", m->ci.longname); + if (*m->ci.hw_info != '\0') + printf(" %s", m->ci.hw_info); + + if (m->mode != 0) + printf(" ("); + if (m->mode & MIX_MODE_PLAY) + printf("play"); + if ((m->mode & playrec) == playrec) + printf("/"); + if (m->mode & MIX_MODE_REC) + printf("rec"); + if (m->mode != 0) + printf(")"); + + if (m->f_default) + printf(" (default)"); + printf("\n"); +} + +static void +printdev(struct mixer *m, int oflag) +{ + struct mix_dev *d = m->dev; + mix_ctl_t *cp; + + if (!oflag) { + printf(" %-10s= %.2f:%.2f ", + d->name, d->vol.left, d->vol.right); + if (!MIX_ISREC(m, d->devno)) + printf(" pbk"); + if (MIX_ISREC(m, d->devno)) + printf(" rec"); + if (MIX_ISRECSRC(m, d->devno)) + printf(" src"); + if (MIX_ISMUTE(m, d->devno)) + printf(" mute"); + printf("\n"); + } else { + TAILQ_FOREACH(cp, &d->ctls, ctls) { + (void)cp->print(cp->parent_dev, cp->name); + } + } +} + +static void +printrecsrc(struct mixer *m, int oflag) +{ + struct mix_dev *dp; + int n = 0; + + if (!m->recmask) + return; + if (!oflag) + printf("%s: ", m->mi.name); + TAILQ_FOREACH(dp, &m->devs, devs) { + if (MIX_ISRECSRC(m, dp->devno)) { + if (n++ && !oflag) + printf(", "); + printf("%s", dp->name); + if (oflag) + printf(".%s=+%s", + mixer_get_ctl(dp, C_SRC)->name, n ? " " : ""); + } + } + printf("\n"); +} + +static int +set_dunit(struct mixer *m, int dunit, char *vctl) +{ + const char *opt; + char *dev, *mode; + char buf[32]; + size_t size; + int n, rc; + + /* + * Issue warning in case of hw.snd.basename_clone being unset. Omit the + * check and warning if the -V flag is used, since the user is most + * likely to be aware of this, and the warning might be confusing. + */ + if (vctl == NULL) { + size = sizeof(int); + if (sysctlbyname("hw.snd.basename_clone", &n, &size, + NULL, 0) < 0) { + warn("hw.snd.basename_clone failed"); + return (-1); + } + if (n == 0) { + warnx("warning: hw.snd.basename_clone not set. " + "/dev/dsp is managed externally and does not " + "change with the default unit change here."); + } + } + + if ((n = mixer_get_dunit()) < 0) { + warn("cannot get default unit"); + return (-1); + } + if (mixer_set_dunit(m, dunit) < 0) { + warn("cannot set default unit to %d", dunit); + return (-1); + } + printf("default_unit: %d -> %d\n", n, dunit); + + /* Hot-swap in case virtual_oss exists and is running. */ + if (vctl != NULL) { + dev = strsep(&vctl, ":"); + mode = vctl; + if (dev == NULL || mode == NULL) { + warnx("voss_device:mode tuple incomplete"); + return (-1); + } + if (strcmp(mode, "all") == 0) + opt = "-f"; + else if (strcmp(mode, "play") == 0) + opt = "-P"; + else if (strcmp(mode, "rec") == 0) + opt = "-R"; + else { + warnx("please use one of the following modes: " + "all, play, rec"); + return (-1); + } + snprintf(buf, sizeof(buf), "/dev/dsp%d", dunit); + switch (fork()) { + case -1: + warn("fork"); + break; + case 0: + rc = execl("/usr/sbin/virtual_oss_cmd", + "virtual_oss_cmd", dev, opt, buf, NULL); + if (rc < 0) + warn("virtual_oss_cmd"); + _exit(0); + default: + if (wait(NULL) < 0) + warn("wait"); + break; + } + } + + return (0); +} + +static int +mod_volume(struct mix_dev *d, void *p) +{ + struct mixer *m; + mix_ctl_t *cp; + mix_volume_t v; + const char *val; + char *endp, lstr[8], rstr[8]; + float lprev, rprev, lrel, rrel; + int n; + + m = d->parent_mixer; + cp = mixer_get_ctl(m->dev, C_VOL); + val = p; + n = sscanf(val, "%7[^:]:%7s", lstr, rstr); + if (n == EOF) { + warnx("invalid volume value: %s", val); + return (-1); + } + lrel = rrel = 0; + if (n > 0) { + if (*lstr == '+' || *lstr == '-') + lrel = 1; + v.left = strtof(lstr, &endp); + if (*endp != '\0' && (*endp != '%' || *(endp + 1) != '\0')) { + warnx("invalid volume value: %s", lstr); + return (-1); + } + + if (*endp == '%') + v.left /= 100.0f; + } + if (n > 1) { + if (*rstr == '+' || *rstr == '-') + rrel = 1; + v.right = strtof(rstr, &endp); + if (*endp != '\0' && (*endp != '%' || *(endp + 1) != '\0')) { + warnx("invalid volume value: %s", rstr); + return (-1); + } + + if (*endp == '%') + v.right /= 100.0f; + } + switch (n) { + case 1: + v.right = v.left; /* FALLTHROUGH */ + rrel = lrel; + case 2: + if (lrel) + v.left += m->dev->vol.left; + if (rrel) + v.right += m->dev->vol.right; + + if (v.left < MIX_VOLMIN) + v.left = MIX_VOLMIN; + else if (v.left > MIX_VOLMAX) + v.left = MIX_VOLMAX; + if (v.right < MIX_VOLMIN) + v.right = MIX_VOLMIN; + else if (v.right > MIX_VOLMAX) + v.right = MIX_VOLMAX; + + lprev = m->dev->vol.left; + rprev = m->dev->vol.right; + if (mixer_set_vol(m, v) < 0) + warn("%s.%s=%.2f:%.2f", + m->dev->name, cp->name, v.left, v.right); + else + printf("%s.%s: %.2f:%.2f -> %.2f:%.2f\n", + m->dev->name, cp->name, lprev, rprev, v.left, v.right); + } + + return (0); +} + +static int +mod_mute(struct mix_dev *d, void *p) +{ + struct mixer *m; + mix_ctl_t *cp; + const char *val; + int n, opt = -1; + + m = d->parent_mixer; + cp = mixer_get_ctl(m->dev, C_MUT); + val = p; + if (strncmp(val, "off", strlen(val)) == 0 || + strncmp(val, "0", strlen(val)) == 0) + opt = MIX_UNMUTE; + else if (strncmp(val, "on", strlen(val)) == 0 || + strncmp(val, "1", strlen(val)) == 0) + opt = MIX_MUTE; + else if (strncmp(val, "toggle", strlen(val)) == 0 || + strncmp(val, "^", strlen(val)) == 0) + opt = MIX_TOGGLEMUTE; + else { + warnx("%s: no such modifier", val); + return (-1); + } + n = MIX_ISMUTE(m, m->dev->devno); + if (mixer_set_mute(m, opt) < 0) + warn("%s.%s=%s", m->dev->name, cp->name, val); + else + printf("%s.%s: %s -> %s\n", + m->dev->name, cp->name, + n ? "on" : "off", + MIX_ISMUTE(m, m->dev->devno) ? "on" : "off"); + + return (0); +} + +static int +mod_recsrc(struct mix_dev *d, void *p) +{ + struct mixer *m; + mix_ctl_t *cp; + const char *val; + int n, opt = -1; + + m = d->parent_mixer; + cp = mixer_get_ctl(m->dev, C_SRC); + val = p; + if (strncmp(val, "add", strlen(val)) == 0 || + strncmp(val, "+", strlen(val)) == 0) + opt = MIX_ADDRECSRC; + else if (strncmp(val, "remove", strlen(val)) == 0 || + strncmp(val, "-", strlen(val)) == 0) + opt = MIX_REMOVERECSRC; + else if (strncmp(val, "set", strlen(val)) == 0 || + strncmp(val, "=", strlen(val)) == 0) + opt = MIX_SETRECSRC; + else if (strncmp(val, "toggle", strlen(val)) == 0 || + strncmp(val, "^", strlen(val)) == 0) + opt = MIX_TOGGLERECSRC; + else { + warnx("%s: no such modifier", val); + return (-1); + } + n = MIX_ISRECSRC(m, m->dev->devno); + if (mixer_mod_recsrc(m, opt) < 0) + warn("%s.%s=%s", m->dev->name, cp->name, val); + else + printf("%s.%s: %s -> %s\n", + m->dev->name, cp->name, + n ? "add" : "remove", + MIX_ISRECSRC(m, m->dev->devno) ? "add" : "remove"); + + return (0); +} + +static int +print_volume(struct mix_dev *d, void *p) +{ + struct mixer *m = d->parent_mixer; + const char *ctl_name = p; + + printf("%s.%s=%.2f:%.2f\n", + m->dev->name, ctl_name, m->dev->vol.left, m->dev->vol.right); + + return (0); +} + +static int +print_mute(struct mix_dev *d, void *p) +{ + struct mixer *m = d->parent_mixer; + const char *ctl_name = p; + + printf("%s.%s=%s\n", m->dev->name, ctl_name, + MIX_ISMUTE(m, m->dev->devno) ? "on" : "off"); + + return (0); +} + +static int +print_recsrc(struct mix_dev *d, void *p) +{ + struct mixer *m = d->parent_mixer; + const char *ctl_name = p; + + if (!MIX_ISRECSRC(m, m->dev->devno)) + return (-1); + printf("%s.%s=add\n", m->dev->name, ctl_name); + + return (0); +} |
