/*- * Copyright (c) 2021 Christos Margiolis * * 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 #include #include #include #include #include #include #include #include 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/local/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); }