aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/mixer/mixer.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/mixer/mixer.c')
-rw-r--r--usr.sbin/mixer/mixer.c597
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);
+}