commit fc6d30ce359bab7a77363705f92b2c1702660a36
Author: Christos Margiolis <christos@margiolis.net>
Date: Sun, 13 Jun 2021 20:37:29 +0300
initial commit
Diffstat:
10 files changed, 731 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,3 @@
+*.o
+*.depend
+*.depend.*
diff --git a/Makefile b/Makefile
@@ -0,0 +1,3 @@
+SUBDIR= mixer_lib mixer_prog
+
+.include <bsd.subdir.mk>
diff --git a/README b/README
@@ -0,0 +1,12 @@
+mixer
+=====
+A new sound mixer library implementation for the FreeBSD, and a complete
+ rewrite of mixer(8) to use it and its new features. Some of its development
+is part of Google Summer of Code 2021.
+
+Usage
+-----
+ $ make
+ # make install clean
+
+Please report any bugs to <christos@freebsd.org>.
diff --git a/mixer_lib/Makefile b/mixer_lib/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+LIB= mixer
+SRCS= ${LIB}.c
+INCS= ${LIB}.h
+MAN= ${LIB}.3
+CFLAGS+= -pedantic -Wall -Wunused
+
+.include <bsd.lib.mk>
diff --git a/mixer_lib/mixer.3 b/mixer_lib/mixer.3
@@ -0,0 +1,39 @@
+.\"-
+.\" 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.
+.\"
+
+.Dd March 11, 2021
+.Dt mixer 3
+.Os
+.Sh NAME
+.Nm mixer
+.Nd a mixer library
+.Sh LIBRARY
+Mixer (libmixer, -lmixer)
+.Sh SYNOPSIS
+.In mixer.h
+.\" TODO
+.Sh DESCRIPTION
+.\" TODO
+.Sh SEE ALSO
+.Xr sound 4
+.Sh AUTHORS
+.An Christos Margiolis Aq Mt christos@margiolis.net
diff --git a/mixer_lib/mixer.c b/mixer_lib/mixer.c
@@ -0,0 +1,267 @@
+/*-
+ * 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/types.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mixer.h"
+
+#define BASEPATH "/dev/mixer"
+
+static int _mixer_close(struct mixer *m);
+
+static char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
+
+/*
+ * Open a mixer device in `/dev/mixerN`, where N is the number of the mixer
+ * file. Each device maps to an actual pcmN audio card, so `/dev/mixer0`
+ * is the mixer device for pcm0, and so on.
+ *
+ * @arg: `name`: path to a mixer device. If it's NULL or "/dev/mixer",
+ * we open the default mixer (i.e `hw.snd.default_unit`).
+ */
+struct mixer *
+mixer_open(const char *name)
+{
+ struct mixer *m = NULL;
+ struct mix_dev *dp;
+ int i, v;
+
+ if ((m = calloc(0, sizeof(struct mixer))) == NULL)
+ goto fail;
+
+ if (name != NULL) {
+ /* `name` does not start with "/dev/mixer". */
+ if (strncmp(name, BASEPATH, strlen(BASEPATH)) != 0) {
+ errno = EINVAL;
+ goto fail;
+ }
+ /* `name` is "/dev/mixer" so, we'll use the default unit. */
+ if (strncmp(name, BASEPATH, strlen(name)) == 0)
+ goto default_unit;
+ m->unit = strtol(name + strlen(BASEPATH), NULL, 10);
+ (void)strncpy(m->name, name, sizeof(m->name));
+ } else {
+default_unit:
+ if ((m->unit = mixer_get_default_unit()) < 0)
+ goto fail;
+ (void)snprintf(m->name, sizeof(m->name), "/dev/mixer%d", m->unit);
+ }
+
+ if ((m->fd = open(m->name, O_RDWR, 0)) < 0)
+ goto fail;
+
+ m->f_default = m->unit == mixer_get_default_unit();
+ /* The unit number _must_ be set before the ioctl. */
+ m->mi.dev = m->unit;
+ m->ci.card = m->unit;
+ if (ioctl(m->fd, SNDCTL_MIXERINFO, &m->mi) < 0)
+ goto fail;
+ if (ioctl(m->fd, SNDCTL_CARDINFO, &m->ci) < 0)
+ goto fail;
+ if (ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask) < 0)
+ goto fail;
+ if (ioctl(m->fd, SOUND_MIXER_READ_RECMASK, &m->recmask) < 0)
+ goto fail;
+ if (ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0)
+ goto fail;
+
+ TAILQ_INIT(&m->devs);
+ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
+ if (!M_ISDEV(m, i) && !M_ISREC(m, i) && !M_ISRECSRC(m, i))
+ continue;
+ if ((dp = malloc(sizeof(struct mix_dev))) == NULL)
+ goto fail;
+ if (ioctl(m->fd, MIXER_READ(i), &v) < 0)
+ goto fail;
+ dp->devno = i;
+ dp->lvol = v & 0x7f;
+ dp->rvol = (v >> 8) & 0x7f;
+ dp->lmute = 0;
+ dp->rmute = 0;
+ dp->f_pbk = !M_ISREC(m, i);
+ dp->f_rec = M_ISREC(m, i);
+ (void)strncpy(dp->name, names[i], sizeof(dp->name));
+ TAILQ_INSERT_TAIL(&m->devs, dp, devs);
+ }
+ /* The default device is always "vol". */
+ m->dev = TAILQ_FIRST(&m->devs);
+
+ return m;
+fail:
+ if (m != NULL)
+ (void)mixer_close(m);
+
+ return NULL;
+}
+
+int
+mixer_close(struct mixer *m)
+{
+ return _mixer_close(m);
+}
+
+/*
+ * Select a mixer device (e.g vol, pcm, mic) by name. The mixer structure
+ * keeps a list of all the devices the mixer has, but only one can be
+ * manipulated at a time -- this is what the `dev` field is for. Each
+ * time we want to manipulate a device, `dev` has to point to it first.
+ *
+ * @arg: `flags`: m->devmask / m->recmask / m->recsrc
+ */
+int
+mixer_seldevbyname(struct mixer *m, const char *name, int flags)
+{
+ struct mix_dev *dp;
+
+ TAILQ_FOREACH(dp, &m->devs, devs) {
+ if (M_ISSET(dp->devno, flags)
+ && !strncmp(dp->name, name, sizeof(dp->name))) {
+ m->dev = dp;
+ return 0;
+ }
+ }
+ errno = EINVAL;
+
+ return -1;
+}
+
+/*
+ * Change the mixer's left and right volume. The allowed volume values are
+ * between 0 and 100 and are stored as `lvol | rvol << 8`.
+ */
+int
+mixer_chvol(struct mixer *m, int l, int r)
+{
+ if (l < 0)
+ l = 0;
+ else if (l > 100)
+ l = 100;
+ if (r < 0)
+ r = 0;
+ else if (r > 100)
+ r = 100;
+ m->dev->lvol = l;
+ m->dev->rvol = r;
+ l |= r << 8;
+ if (ioctl(m->fd, MIXER_WRITE(m->dev->devno), &l) < 0)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * Modify the mixer's selected device flags. The `recsrc` flag tells
+ * us if a device is a recording source.
+ */
+int
+mixer_modrecsrc(struct mixer *m, int opt)
+{
+ switch (opt) {
+ case M_ADDRECDEV:
+ m->recsrc |= (1 << m->dev->devno);
+ break;
+ case M_REMOVERECDEV:
+ m->recsrc &= ~(1 << m->dev->devno);
+ break;
+ case M_SETRECDEV:
+ m->recsrc = (1 << m->dev->devno);
+ break;
+ case M_TOGGLERECDEV:
+ m->recsrc ^= (1 << m->dev->devno);
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ if (ioctl(m->fd, SOUND_MIXER_WRITE_RECSRC, &m->recsrc) < 0)
+ return -1;
+ if (ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * Get default audio card's number. This is used to open the default mixer
+ * and set the mixer structure's `f_default` flag.
+ */
+int
+mixer_get_default_unit(void)
+{
+ int unit;
+ size_t size;
+
+ size = sizeof(int);
+ if (sysctlbyname("hw.snd.default_unit", &unit, &size, NULL, 0) < 0)
+ return -1;
+
+ return unit;
+}
+
+/*
+ * Change the default audio card. This is normally _not_ a mixer feature, but
+ * it's useful to have, so the caller can avoid having to manually use
+ * the sysctl API.
+ *
+ * @arg: `unit`: the audio card number (e.g pcm0, pcm1, ...).
+ */
+int
+mixer_set_default_unit(struct mixer *m, int unit)
+{
+ size_t size;
+
+ size = sizeof(int);
+ if (sysctlbyname("hw.snd.default_unit", NULL, 0, &unit, size) < 0)
+ return -1;
+ m->f_default = m->unit == unit;
+
+ return 0;
+}
+
+/*
+ * Free resources and close the mixer.
+ */
+static int
+_mixer_close(struct mixer *m)
+{
+ struct mix_dev *dp;
+ int r;
+
+ r = close(m->fd);
+ while (!TAILQ_EMPTY(&m->devs)) {
+ dp = TAILQ_FIRST(&m->devs);
+ TAILQ_REMOVE(&m->devs, dp, devs);
+ free(dp);
+ }
+ free(m);
+
+ return r;
+}
diff --git a/mixer_lib/mixer.h b/mixer_lib/mixer.h
@@ -0,0 +1,80 @@
+/*-
+ * 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.
+ */
+
+#ifndef _MIXER_H_
+#define _MIXER_H_
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/queue.h>
+#include <sys/soundcard.h>
+
+#include <limits.h>
+
+#define M_ADDRECDEV 0x01
+#define M_REMOVERECDEV 0x02
+#define M_SETRECDEV 0x04
+#define M_TOGGLERECDEV 0x08
+
+#define M_ISSET(n, f) ((1 << (n)) & (f))
+#define M_ISDEV(m, n) M_ISSET(n, (m)->devmask)
+#define M_ISREC(m, n) M_ISSET(n, (m)->recmask)
+#define M_ISRECSRC(m, n) M_ISSET(n, (m)->recsrc)
+
+struct mix_dev {
+ char name[NAME_MAX];
+ int devno;
+ int lvol;
+ int rvol;
+ int lmute;
+ int rmute;
+ //int rate;
+ //int samples;
+ int f_pbk;
+ int f_rec;
+ TAILQ_ENTRY(mix_dev) devs;
+};
+
+struct mixer {
+ TAILQ_HEAD(head, mix_dev) devs;
+ struct mix_dev *dev;
+ oss_mixerinfo mi;
+ oss_card_info ci;
+ char name[NAME_MAX];
+ int fd;
+ int unit;
+ int devmask;
+ int recmask;
+ int recsrc;
+ int f_default;
+};
+
+struct mixer *mixer_open(const char *);
+int mixer_close(struct mixer *);
+int mixer_seldevbyname(struct mixer *, const char *, int);
+int mixer_chvol(struct mixer *, int, int);
+int mixer_modrecsrc(struct mixer *, int);
+int mixer_get_default_unit(void);
+int mixer_set_default_unit(struct mixer *, int);
+
+#endif /* _MIXER_H_ */
diff --git a/mixer_prog/Makefile b/mixer_prog/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= mixer_prog
+BINDIR= /usr/sbin
+SRCS= ${PROG}.c
+MAN= ${PROG}.8
+CFLAGS+= -pedantic -Wall -Wunused
+LDFLAGS+= -lmixer
+
+.include <bsd.prog.mk>
diff --git a/mixer_prog/mixer_prog.8 b/mixer_prog/mixer_prog.8
@@ -0,0 +1,36 @@
+.\"-
+.\" 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.
+.\"
+
+.Dd June 07, 2021
+.Dt mixer 8
+.Os
+.Sh NAME
+.Nm mixer
+.Nd manipulate soundcard mixer values
+.Sh SYNOPSIS
+.\" TODO:
+.Sh DESCRIPTION
+.\" TODO:
+.Sh SEE ALSO
+.Xr mixer 3
+.Sh AUTHORS
+.An Christos Margiolis Aq Mt christos@margiolis.net
diff --git a/mixer_prog/mixer_prog.c b/mixer_prog/mixer_prog.c
@@ -0,0 +1,272 @@
+/*-
+ * 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 <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <mixer.h>
+
+static void usage(struct mixer *) __dead2;
+static void printrecsrc(struct mixer *, int);
+
+static void __dead2
+usage(struct mixer *m)
+{
+ struct mix_dev *dp;
+ int n;
+
+ printf("usage: %1$s [-f device] [-s | -S] [dev [+|-][voll[:[+|-]volr]] ...\n"
+ " %1$s [-f device] [-s | -S] recsrc ...\n"
+ " %1$s [-f device] [-s | -S] {^|+|-|=}rec rdev ...\n",
+ getprogname());
+ if (m->devmask) {
+ printf(" devices: ");
+ n = 0;
+ TAILQ_FOREACH(dp, &m->devs, devs) {
+ if (M_ISDEV(m, dp->devno)) {
+ if (n)
+ printf(", ");
+ printf("%s", dp->name);
+ n++;
+ }
+ }
+ }
+ if (m->recmask) {
+ printf("\n rec devices: ");
+ n = 0;
+ TAILQ_FOREACH(dp, &m->devs, devs) {
+ if (M_ISREC(m, dp->devno)) {
+ if (n)
+ printf(", ");
+ printf("%s", dp->name);
+ n++;
+ }
+ }
+ }
+ printf("\n");
+ (void)mixer_close(m);
+ exit(1);
+}
+
+static void
+printrecsrc(struct mixer *m, int sflag)
+{
+ struct mix_dev *dp;
+ int n;
+
+ if (!m->recmask)
+ return;
+ if (!sflag)
+ printf("Recording source: ");
+ n = 0;
+ TAILQ_FOREACH(dp, &m->devs, devs) {
+ if (M_ISRECSRC(m, dp->devno)) {
+ if (sflag)
+ printf("%srec ", n ? " +" : "=");
+ else if (n)
+ printf(", ");
+ printf("%s", dp->name);
+ n++;
+ }
+ }
+ if (!sflag)
+ printf("\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct mixer *m;
+ struct mix_dev *dp;
+ char lstr[8], rstr[8], *name = NULL;
+ int dusage = 0, drecsrc = 0, sflag = 0, Sflag = 0;
+ int l, r, lprev, rprev, lrel, rrel;
+ char ch, n, t, k, opt = 0;
+
+ /* FIXME: problems with -rec */
+ while ((ch = getopt(argc, argv, "f:sS")) != -1) {
+ switch (ch) {
+ case 'f':
+ name = optarg;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'S':
+ Sflag = 1;
+ break;
+ case '?':
+ default:
+ dusage = 1;
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (sflag && Sflag)
+ dusage = 1;
+
+ if ((m = mixer_open(name)) == NULL)
+ err(1, "mixer_open: %s", name);
+
+ if (!argc && !dusage) {
+ n = 0;
+ TAILQ_FOREACH(dp, &m->devs, devs) {
+ if (sflag || Sflag) {
+ printf("%s%s%c%d:%d", n ? " " : "",
+ dp->name, Sflag ? ':' : ' ',
+ dp->lvol, dp->rvol);
+ n++;
+ } else
+ printf("Mixer %-8s is currently set to %3d:%d\n",
+ dp->name, dp->lvol, dp->rvol);
+ }
+ if (n && m->recmask)
+ printf(" ");
+ printrecsrc(m, sflag || Sflag);
+ (void)mixer_close(m);
+ return 0;
+ }
+
+ n = 0;
+ while (argc > 0 && !dusage) {
+ if (strcmp("recsrc", *argv) == 0) {
+ drecsrc = 1;
+ argc--;
+ argv++;
+ continue;
+ } else if (strcmp("rec", *argv + 1) == 0) {
+ if (**argv != '+' && **argv != '-'
+ && **argv != '=' && **argv != '^') {
+ warnx("unkown modifier: %c", **argv);
+ dusage = 1;
+ break;
+ }
+ if (argc <= 1) {
+ warnx("no recording device specified");
+ dusage = 1;
+ break;
+ }
+ if (mixer_seldevbyname(m, argv[1], m->recmask) < 0) {
+ warnx("unkown recording revice: %s", argv[1]);
+ dusage = 1;
+ break;
+ }
+
+ switch (**argv) {
+ case '+':
+ opt = M_ADDRECDEV;
+ break;
+ case '-':
+ opt = M_REMOVERECDEV;
+ break;
+ case '=':
+ opt = M_SETRECDEV;
+ break;
+ case '^':
+ opt = M_TOGGLERECDEV;
+ break;
+ }
+ if (mixer_modrecsrc(m, opt) < 0) {
+ warnx("cannot modify device");
+ break;
+ }
+ drecsrc = 1;
+ argc -= 2;
+ argv += 2;
+ continue;
+ }
+
+ if ((t = sscanf(*argv, "%d:%d", &l, &r)) > 0)
+ ; /* nothing */
+ else if (mixer_seldevbyname(m, *argv, m->devmask) < 0) {
+ warnx("unkown device: %s", *argv);
+ dusage = 1;
+ break;
+ }
+
+ lrel = rrel = 0;
+ if (argc > 1) {
+ k = sscanf(argv[1], "%7[^:]:%7s", lstr, rstr);
+ if (k == EOF) {
+ warnx("invalid value: %s", argv[1]);
+ dusage = 1;
+ break;
+ }
+ if (k > 0) {
+ if (*lstr == '+' || *lstr == '-')
+ lrel = rrel = 1;
+ l = strtol(lstr, NULL, 10);
+ }
+ if (k > 1) {
+ if (*rstr == '+' || *rstr == '-')
+ rrel = 1;
+ r = strtol(rstr, NULL, 10);
+ }
+ }
+
+ switch (argc > 1 ? k : t) {
+ case 0:
+ printf("Mixer %-8s is currently set to %3d:%d\n",
+ m->dev->name, m->dev->lvol, m->dev->rvol);
+ argc--;
+ argv++;
+ continue;
+ case 1:
+ r = l; /* FALLTHROUGH */
+ case 2:
+ /*
+ * Keep the previous volume so we don't have to clump
+ * it manually before we print it in case the user gives a
+ * value < 0 or > 100.
+ */
+ lprev = m->dev->lvol;
+ rprev = m->dev->rvol;
+ if (lrel)
+ l += m->dev->lvol;
+ if (rrel)
+ r += m->dev->rvol;
+ if (mixer_chvol(m, l, r) < 0) {
+ warnx("cannot change volume");
+ break;
+ }
+ if (!Sflag)
+ printf("Setting the mixer %s from %d:%d to %d:%d\n",
+ m->dev->name, lprev, rprev,
+ m->dev->lvol, m->dev->rvol);
+ argc -= 2;
+ argv += 2;
+ }
+ }
+
+ if (dusage)
+ usage(m);
+ if (drecsrc)
+ printrecsrc(m, sflag || Sflag);
+ (void)mixer_close(m);
+
+ return 0;
+}