summaryrefslogtreecommitdiffstats
path: root/usr.bin/sndioctl
diff options
context:
space:
mode:
authorratchov <ratchov@openbsd.org>2020-02-26 13:58:48 +0000
committerratchov <ratchov@openbsd.org>2020-02-26 13:58:48 +0000
commit82fd4e087b6cb326444f9b2b9068b53b147ea3d9 (patch)
treeead4192ea80e7e071f5f0a353a07070a744f679e /usr.bin/sndioctl
parentRelease the file descriptor table lock before calling closef() (diff)
downloadwireguard-openbsd-82fd4e087b6cb326444f9b2b9068b53b147ea3d9.tar.xz
wireguard-openbsd-82fd4e087b6cb326444f9b2b9068b53b147ea3d9.zip
Add sndioctl utility to control audio parameters exposed by sndiod.
ok deraadt
Diffstat (limited to 'usr.bin/sndioctl')
-rw-r--r--usr.bin/sndioctl/Makefile5
-rw-r--r--usr.bin/sndioctl/sndioctl.1146
-rw-r--r--usr.bin/sndioctl/sndioctl.c939
3 files changed, 1090 insertions, 0 deletions
diff --git a/usr.bin/sndioctl/Makefile b/usr.bin/sndioctl/Makefile
new file mode 100644
index 00000000000..85793ee9e60
--- /dev/null
+++ b/usr.bin/sndioctl/Makefile
@@ -0,0 +1,5 @@
+# $OpenBSD: Makefile,v 1.1 2020/02/26 13:58:48 ratchov Exp $
+
+PROG= sndioctl
+LDADD+= -lsndio
+.include <bsd.prog.mk>
diff --git a/usr.bin/sndioctl/sndioctl.1 b/usr.bin/sndioctl/sndioctl.1
new file mode 100644
index 00000000000..fa3675e4904
--- /dev/null
+++ b/usr.bin/sndioctl/sndioctl.1
@@ -0,0 +1,146 @@
+.\" $OpenBSD: sndioctl.1,v 1.1 2020/02/26 13:58:48 ratchov Exp $
+.\"
+.\" Copyright (c) 2014-2020 Alexandre Ratchov <alex@caoua.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: February 26 2020 $
+.Dt SNDIOCTL 1
+.Os
+.Sh NAME
+.Nm sndioctl
+.Nd control audio parameters
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl iv
+.Op Fl f Ar device
+.Op Ar command ...
+.Ek
+.Nm
+.Fl d
+.Sh DESCRIPTION
+The
+.Nm
+utility can display or change parameters of
+.Xr sndio 7
+audio devices.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Dump the raw list of available parameters and exit.
+Useful as a debugging tool.
+.It Fl f Ar device
+Use this
+.Xr sndio 7
+audio device.
+.It Fl i
+Display characteristics of requested parameters
+instead of their values.
+.It Fl m
+Monitor and display audio parameter changes.
+.It Fl v
+Enable verbose mode, a.k.a. multi-channel mode.
+By default parameters affecting different channels
+of the same stream are disguised as a single mono
+parameter to hide details that are not essential.
+.El
+.Pp
+If no commands are specified all valid parameters are displayed on
+.Em stdout .
+Unless
+.Fl d ,
+.Fl m ,
+or
+.Fl i
+are used, displayed parameters are valid commands.
+The set of available controls depends on the control device.
+.Pp
+Commands use the following two formats to display and set
+parameters respectively:
+.Pp
+.Dl group/stream[channel].function
+.Dl group/stream[channel].function=value
+.Pp
+On the left-hand side are specified the optional parameter group,
+the affected stream name, and the optional channel number.
+Examples of left-hand side terms:
+.Pp
+.Dl output.level
+.Dl hw/spkr[6].mute
+.Pp
+There are 4 parameter types: switches, numbers, selectors, and vectors.
+.Pp
+Numbers are specified in decimal and follow the same semantics
+as MIDI controllers.
+Values are in the 0..127 range and 64 is the neutral state (if applicable).
+Two-state controls (switches) take either 0 or 1 as value,
+typically corresponding to the
+.Em off
+and
+.Em on
+states respectively.
+.Pp
+If a decimal is prefixed by the plus (minus) sign then
+the given value is added to (subtracted from) the
+current value of the control.
+If
+.Qq \&!
+is used instead of a number, then the switch is toggled.
+Examples:
+.Pp
+.Dl hw/spkr.level=85
+.Dl hw/spkr.level=+10
+.Dl hw/spkr.mute=0
+.Dl hw/spkr.mute=!
+.Pp
+Selector values are substreams; they are specified
+as the stream name followed by an optional channel
+number.
+If no channel number is specified, the same
+number as the stream specified on the left-hand side is used.
+For instance the following are equivalent:
+.Pp
+.Dl hw/record[1].source=mic
+.Dl hw/record[1].source=mic1
+.Pp
+Vectors are arrays of numbers.
+Values are specified as comma-separated components.
+Each component is a substream, followed by
+a colon, followed by a number.
+If the colon and the number are omitted, then 127 is
+assumed.
+If a component is missing, then 0 is assumed.
+Example:
+.Pp
+.Dl hw/monitor.mix=play:120,linein:85
+.Dl hw/record.source=mic,linein
+.Pp
+Numbers are specified as discussed above.
+Note that a vector of switches is equivalent to
+a list.
+.Sh EXAMPLES
+The following will set all
+.Ar level
+parameters that control the
+.Ar spkr
+stream to zero.
+.Pp
+.Dl $ sndioctl hw/spkr.level=0
+.Pp
+The following commands are equivalent:
+.Pp
+.Dl $ sndioctl hw/record[0].source=mic0 hw/record[1].source=mic1
+.Dl $ sndioctl hw/record.source=mic
+.Sh SEE ALSO
+.Xr sioctl_open 3
diff --git a/usr.bin/sndioctl/sndioctl.c b/usr.bin/sndioctl/sndioctl.c
new file mode 100644
index 00000000000..24a79b23189
--- /dev/null
+++ b/usr.bin/sndioctl/sndioctl.c
@@ -0,0 +1,939 @@
+/* $OpenBSD: sndioctl.c,v 1.1 2020/02/26 13:58:48 ratchov Exp $ */
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <alex@caoua.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <errno.h>
+#include <poll.h>
+#include <sndio.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+struct info {
+ struct info *next;
+ struct sioctl_desc desc;
+ unsigned ctladdr;
+#define MODE_IGNORE 0 /* ignore this value */
+#define MODE_PRINT 1 /* print-only, don't change value */
+#define MODE_SET 2 /* set to newval value */
+#define MODE_ADD 3 /* increase current value by newval */
+#define MODE_SUB 4 /* decrease current value by newval */
+#define MODE_TOGGLE 5 /* toggle current value */
+ unsigned mode;
+ int curval, newval;
+};
+
+int cmpdesc(struct sioctl_desc *, struct sioctl_desc *);
+int isdiag(struct info *);
+struct info *vecent(struct info *, char *, int);
+struct info *nextfunc(struct info *);
+struct info *nextpar(struct info *);
+struct info *firstent(struct info *, char *);
+struct info *nextent(struct info *, int);
+int matchpar(struct info *, char *, int);
+int matchent(struct info *, char *, int);
+int ismono(struct info *);
+void print_node(struct sioctl_node *, int);
+void print_desc(struct info *, int);
+void print_val(struct info *, int);
+void print_par(struct info *, int, char *);
+int parse_name(char **, char *);
+int parse_unit(char **, unsigned int *);
+int parse_val(char **, float *);
+int parse_node(char **, char *, int *);
+int parse_modeval(char **, int *, float *);
+void dump(void);
+int cmd(char *);
+void commit(void);
+void list(void);
+void ondesc(void *, struct sioctl_desc *, int);
+void onctl(void *, unsigned, unsigned);
+
+struct sioctl_hdl *hdl;
+struct info *infolist;
+int i_flag = 0, v_flag = 0, m_flag = 0;
+
+static inline int
+isname_first(int c)
+{
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+static inline int
+isname_next(int c)
+{
+ return isname_first(c) || (c >= '0' && c <= '9') || (c == '_');
+}
+
+static int
+ftoi(float f)
+{
+ return f + 0.5;
+}
+
+/*
+ * compare two sioctl_desc structures, used to sort infolist
+ */
+int
+cmpdesc(struct sioctl_desc *d1, struct sioctl_desc *d2)
+{
+ int res;
+
+ res = strcmp(d1->group, d2->group);
+ if (res != 0)
+ return res;
+ res = strcmp(d1->node0.name, d2->node0.name);
+ if (res != 0)
+ return res;
+ res = d1->type - d2->type;
+ if (res != 0)
+ return res;
+ res = strcmp(d1->func, d2->func);
+ if (res != 0)
+ return res;
+ res = d1->node0.unit - d2->node0.unit;
+ if (d1->type == SIOCTL_VEC ||
+ d1->type == SIOCTL_LIST) {
+ if (res != 0)
+ return res;
+ res = strcmp(d1->node1.name, d2->node1.name);
+ if (res != 0)
+ return res;
+ res = d1->node1.unit - d2->node1.unit;
+ }
+ return res;
+}
+
+/*
+ * return true of the vector entry is diagonal
+ */
+int
+isdiag(struct info *e)
+{
+ if (e->desc.node0.unit < 0 || e->desc.node1.unit < 0)
+ return 1;
+ return e->desc.node1.unit == e->desc.node0.unit;
+}
+
+/*
+ * find the selector or vector entry with the given name and channels
+ */
+struct info *
+vecent(struct info *i, char *vstr, int vunit)
+{
+ while (i != NULL) {
+ if ((strcmp(i->desc.node1.name, vstr) == 0) &&
+ (vunit < 0 || i->desc.node1.unit == vunit))
+ break;
+ i = i->next;
+ }
+ return i;
+}
+
+/*
+ * skip all parameters with the same group, name, and func
+ */
+struct info *
+nextfunc(struct info *i)
+{
+ char *str, *group, *func;
+
+ group = i->desc.group;
+ func = i->desc.func;
+ str = i->desc.node0.name;
+ for (i = i->next; i != NULL; i = i->next) {
+ if (strcmp(i->desc.group, group) != 0 ||
+ strcmp(i->desc.node0.name, str) != 0 ||
+ strcmp(i->desc.func, func) != 0)
+ return i;
+ }
+ return NULL;
+}
+
+/*
+ * find the next parameter with the same group, name, func
+ */
+struct info *
+nextpar(struct info *i)
+{
+ char *str, *group, *func;
+ int unit;
+
+ group = i->desc.group;
+ func = i->desc.func;
+ str = i->desc.node0.name;
+ unit = i->desc.node0.unit;
+ for (i = i->next; i != NULL; i = i->next) {
+ if (strcmp(i->desc.group, group) != 0 ||
+ strcmp(i->desc.node0.name, str) != 0 ||
+ strcmp(i->desc.func, func) != 0)
+ break;
+ /* XXX: need to check for -1 ? */
+ if (i->desc.node0.unit != unit)
+ return i;
+ }
+ return NULL;
+}
+
+/*
+ * return the first vector entry with the given name
+ */
+struct info *
+firstent(struct info *g, char *vstr)
+{
+ char *astr, *group, *func;
+ struct info *i;
+
+ group = g->desc.group;
+ astr = g->desc.node0.name;
+ func = g->desc.func;
+ for (i = g; i != NULL; i = i->next) {
+ if (strcmp(i->desc.group, group) != 0 ||
+ strcmp(i->desc.node0.name, astr) != 0 ||
+ strcmp(i->desc.func, func) != 0)
+ break;
+ if (!isdiag(i))
+ continue;
+ if (strcmp(i->desc.node1.name, vstr) == 0)
+ return i;
+ }
+ return NULL;
+}
+
+/*
+ * find the next entry of the given vector, if the mono flag
+ * is set then the whole group is searched and off-diagonal entries are
+ * skipped
+ */
+struct info *
+nextent(struct info *i, int mono)
+{
+ char *str, *group, *func;
+ int unit;
+
+ group = i->desc.group;
+ func = i->desc.func;
+ str = i->desc.node0.name;
+ unit = i->desc.node0.unit;
+ for (i = i->next; i != NULL; i = i->next) {
+ if (strcmp(i->desc.group, group) != 0 ||
+ strcmp(i->desc.node0.name, str) != 0 ||
+ strcmp(i->desc.func, func) != 0)
+ return NULL;
+ if (mono)
+ return i;
+ if (i->desc.node0.unit == unit)
+ return i;
+ }
+ return NULL;
+}
+
+/*
+ * return true if parameter matches the given name and channel
+ */
+int
+matchpar(struct info *i, char *astr, int aunit)
+{
+ if (strcmp(i->desc.node0.name, astr) != 0)
+ return 0;
+ if (aunit < 0)
+ return 1;
+ else if (i->desc.node0.unit < 0) {
+ fprintf(stderr, "unit used for parameter with no unit\n");
+ exit(1);
+ }
+ return i->desc.node0.unit == aunit;
+}
+
+/*
+ * return true if selector or vector entry matches the given name and
+ * channel range
+ */
+int
+matchent(struct info *i, char *vstr, int vunit)
+{
+ if (strcmp(i->desc.node1.name, vstr) != 0)
+ return 0;
+ if (vunit < 0)
+ return 1;
+ else if (i->desc.node1.unit < 0) {
+ fprintf(stderr, "unit used for parameter with no unit\n");
+ exit(1);
+ }
+ return i->desc.node1.unit == vunit;
+}
+
+/*
+ * return true if the given group can be represented as a signle mono
+ * parameter
+ */
+int
+ismono(struct info *g)
+{
+ struct info *p1, *p2;
+ struct info *e1, *e2;
+
+ p1 = g;
+ switch (g->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
+ if (p2->curval != p1->curval)
+ return 0;
+ }
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
+ for (e2 = p2; e2 != NULL; e2 = nextent(e2, 0)) {
+ if (!isdiag(e2)) {
+ if (e2->curval != 0)
+ return 0;
+ } else {
+ e1 = vecent(p1,
+ e2->desc.node1.name,
+ p1->desc.node0.unit);
+ if (e1 == NULL)
+ continue;
+ if (e1->curval != e2->curval)
+ return 0;
+ }
+ }
+ }
+ break;
+ }
+ return 1;
+}
+
+/*
+ * print a sub-stream, eg. "spkr[4]"
+ */
+void
+print_node(struct sioctl_node *c, int mono)
+{
+ printf("%s", c->name);
+ if (!mono && c->unit >= 0)
+ printf("[%d]", c->unit);
+}
+
+/*
+ * print info about the parameter
+ */
+void
+print_desc(struct info *p, int mono)
+{
+ struct info *e;
+ int more;
+
+ switch (p->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ printf("*");
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ more = 0;
+ for (e = p; e != NULL; e = nextent(e, mono)) {
+ if (mono) {
+ if (!isdiag(e))
+ continue;
+ if (e != firstent(p, e->desc.node1.name))
+ continue;
+ }
+ if (more)
+ printf(",");
+ print_node(&e->desc.node1, mono);
+ printf(":*");
+ more = 1;
+ }
+ }
+}
+
+/*
+ * print parameter value
+ */
+void
+print_val(struct info *p, int mono)
+{
+ struct info *e;
+ int more;
+
+ switch (p->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ printf("%.2g", p->curval / (float)p->desc.maxval);
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ more = 0;
+ for (e = p; e != NULL; e = nextent(e, mono)) {
+ if (mono) {
+ if (!isdiag(e))
+ continue;
+ if (e != firstent(p, e->desc.node1.name))
+ continue;
+ }
+ if (more)
+ printf(",");
+ print_node(&e->desc.node1, mono);
+ printf(":%.2g", e->curval / (float)e->desc.maxval);
+ more = 1;
+ }
+ }
+}
+
+/*
+ * print ``<parameter>=<value>'' string (including '\n')
+ */
+void
+print_par(struct info *p, int mono, char *comment)
+{
+ if (p->desc.group[0] != 0) {
+ printf("%s", p->desc.group);
+ printf("/");
+ }
+ print_node(&p->desc.node0, mono);
+ printf(".%s=", p->desc.func);
+ if (i_flag)
+ print_desc(p, mono);
+ else
+ print_val(p, mono);
+ if (comment)
+ printf(" # %s", comment);
+ printf("\n");
+}
+
+/*
+ * parse a stream name or parameter name
+ */
+int
+parse_name(char **line, char *name)
+{
+ char *p = *line;
+ unsigned len = 0;
+
+ if (!isname_first(*p)) {
+ fprintf(stderr, "letter expected near '%s'\n", p);
+ return 0;
+ }
+ while (isname_next(*p)) {
+ if (len >= SIOCTL_NAMEMAX - 1) {
+ name[SIOCTL_NAMEMAX - 1] = '\0';
+ fprintf(stderr, "%s...: too long\n", name);
+ return 0;
+ }
+ name[len++] = *p;
+ p++;
+ }
+ name[len] = '\0';
+ *line = p;
+ return 1;
+}
+
+/*
+ * parse a decimal integer
+ */
+int
+parse_unit(char **line, unsigned int *num)
+{
+ char *p = *line;
+ unsigned int val;
+ int n;
+
+ if (sscanf(p, "%u%n", &val, &n) != 1) {
+ fprintf(stderr, "number expected near '%s'\n", p);
+ return 0;
+ }
+ if (val >= 255) {
+ fprintf(stderr, "%d: too large\n", val);
+ return 0;
+ }
+ *num = val;
+ *line = p + n;
+ return 1;
+}
+
+int
+parse_val(char **line, float *num)
+{
+ char *p = *line;
+ float val;
+ int n;
+
+ if (sscanf(p, "%g%n", &val, &n) != 1) {
+ fprintf(stderr, "number expected near '%s'\n", p);
+ return 0;
+ }
+ if (val < 0 || val > 1) {
+ fprintf(stderr, "%g: expected number between 0 and 1\n", val);
+ return 0;
+ }
+ *num = val;
+ *line = p + n;
+ return 1;
+}
+
+/*
+ * parse a sub-stream, eg. "spkr[7]"
+ */
+int
+parse_node(char **line, char *str, int *unit)
+{
+ char *p = *line;
+
+ if (!parse_name(&p, str))
+ return 0;
+ if (*p != '[') {
+ *unit = -1;
+ *line = p;
+ return 1;
+ }
+ p++;
+ if (!parse_unit(&p, unit))
+ return 0;
+ if (*p != ']') {
+ fprintf(stderr, "']' expected near '%s'\n", p);
+ return 0;
+ }
+ p++;
+ *line = p;
+ return 1;
+}
+
+/*
+ * parse a decimal prefixed by the optional mode
+ */
+int
+parse_modeval(char **line, int *rmode, float *rval)
+{
+ char *p = *line;
+ unsigned mode;
+
+ switch (*p) {
+ case '+':
+ mode = MODE_ADD;
+ p++;
+ break;
+ case '-':
+ mode = MODE_SUB;
+ p++;
+ break;
+ case '!':
+ mode = MODE_TOGGLE;
+ p++;
+ break;
+ default:
+ mode = MODE_SET;
+ }
+ if (mode != MODE_TOGGLE) {
+ if (!parse_val(&p, rval))
+ return 0;
+ }
+ *line = p;
+ *rmode = mode;
+ return 1;
+}
+
+/*
+ * dump the whole controls list, useful for debugging
+ */
+void
+dump(void)
+{
+ struct info *i;
+
+ for (i = infolist; i != NULL; i = i->next) {
+ printf("%03u:", i->ctladdr);
+ print_node(&i->desc.node0, 0);
+ printf(".%s", i->desc.func);
+ printf("=");
+ switch (i->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ printf("0..%d (%u)", i->desc.maxval, i->curval);
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ print_node(&i->desc.node1, 0);
+ printf(":0..%d (%u)", i->desc.maxval, i->curval);
+ }
+ printf("\n");
+ }
+}
+
+/*
+ * parse and execute a command ``<parameter>[=<value>]''
+ */
+int
+cmd(char *line)
+{
+ char *pos, *group;
+ struct info *i, *e, *g;
+ char func[SIOCTL_NAMEMAX];
+ char astr[SIOCTL_NAMEMAX], vstr[SIOCTL_NAMEMAX];
+ int aunit, vunit;
+ unsigned npar = 0, nent = 0;
+ int comma, mode;
+ float val;
+
+ pos = strrchr(line, '/');
+ if (pos != NULL) {
+ group = line;
+ pos[0] = 0;
+ pos++;
+ } else {
+ group = "";
+ pos = line;
+ }
+ if (!parse_node(&pos, astr, &aunit))
+ return 0;
+ if (*pos != '.') {
+ fprintf(stderr, "'.' expected near '%s'\n", pos);
+ return 0;
+ }
+ pos++;
+ if (!parse_name(&pos, func))
+ return 0;
+ for (g = infolist;; g = g->next) {
+ if (g == NULL) {
+ fprintf(stderr, "%s.%s: no such control\n", astr, func);
+ return 0;
+ }
+ if (strcmp(g->desc.group, group) == 0 &&
+ strcmp(g->desc.func, func) == 0 &&
+ strcmp(g->desc.node0.name, astr) == 0)
+ break;
+ }
+ g->mode = MODE_PRINT;
+ if (*pos != '=') {
+ if (*pos != '\0') {
+ fprintf(stderr, "junk at end of command\n");
+ return 0;
+ }
+ return 1;
+ }
+ pos++;
+ if (i_flag) {
+ printf("can't set values in info mode\n");
+ return 0;
+ }
+ npar = 0;
+ switch (g->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ if (!parse_modeval(&pos, &mode, &val))
+ return 0;
+ for (i = g; i != NULL; i = nextpar(i)) {
+ if (!matchpar(i, astr, aunit))
+ continue;
+ i->mode = mode;
+ i->newval = ftoi(val * i->desc.maxval);
+ npar++;
+ }
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ for (i = g; i != NULL; i = nextpar(i)) {
+ if (!matchpar(i, astr, aunit))
+ continue;
+ for (e = i; e != NULL; e = nextent(e, 0)) {
+ e->newval = 0;
+ e->mode = MODE_SET;
+ }
+ npar++;
+ }
+ comma = 0;
+ for (;;) {
+ if (*pos == '\0')
+ break;
+ if (comma) {
+ if (*pos != ',')
+ break;
+ pos++;
+ }
+ if (!parse_node(&pos, vstr, &vunit))
+ return 0;
+ if (*pos == ':') {
+ pos++;
+ if (!parse_modeval(&pos, &mode, &val))
+ return 0;
+ } else {
+ val = 1.;
+ mode = MODE_SET;
+ }
+ nent = 0;
+ for (i = g; i != NULL; i = nextpar(i)) {
+ if (!matchpar(i, astr, aunit))
+ continue;
+ for (e = i; e != NULL; e = nextent(e, 0)) {
+ if (matchent(e, vstr, vunit)) {
+ e->newval = ftoi(val * e->desc.maxval);
+ e->mode = mode;
+ nent++;
+ }
+ }
+ }
+ if (nent == 0) {
+ /* XXX: use print_node()-like routine */
+ fprintf(stderr, "%s[%d]: invalid value\n", vstr, vunit);
+ print_par(g, 0, NULL);
+ exit(1);
+ }
+ comma = 1;
+ }
+ }
+ if (npar == 0) {
+ fprintf(stderr, "%s: invalid parameter\n", line);
+ exit(1);
+ }
+ if (*pos != '\0') {
+ printf("%s: junk at end of command\n", pos);
+ exit(1);
+ }
+ return 1;
+}
+
+/*
+ * write the controls with the ``set'' flag on the device
+ */
+void
+commit(void)
+{
+ struct info *i;
+ int val;
+
+ for (i = infolist; i != NULL; i = i->next) {
+ val = 0xdeadbeef;
+ switch (i->mode) {
+ case MODE_IGNORE:
+ case MODE_PRINT:
+ continue;
+ case MODE_SET:
+ val = i->newval;
+ break;
+ case MODE_ADD:
+ val = i->curval + i->newval;
+ if (val > i->desc.maxval)
+ val = i->desc.maxval;
+ break;
+ case MODE_SUB:
+ val = i->curval - i->newval;
+ if (val < 0)
+ val = 0;
+ break;
+ case MODE_TOGGLE:
+ val = i->curval ? 0 : i->desc.maxval;
+ }
+ sioctl_setval(hdl, i->ctladdr, val);
+ i->curval = val;
+ }
+}
+
+/*
+ * print all parameters
+ */
+void
+list(void)
+{
+ struct info *p, *g;
+
+ for (g = infolist; g != NULL; g = nextfunc(g)) {
+ if (g->mode == MODE_IGNORE)
+ continue;
+ if (i_flag) {
+ if (v_flag) {
+ for (p = g; p != NULL; p = nextpar(p))
+ print_par(p, 0, NULL);
+ } else
+ print_par(g, 1, NULL);
+ } else {
+ if (v_flag || !ismono(g)) {
+ for (p = g; p != NULL; p = nextpar(p))
+ print_par(p, 0, NULL);
+ } else
+ print_par(g, 1, NULL);
+ }
+ }
+}
+
+/*
+ * register a new knob/button, called from the poll() loop. this may be
+ * called when label string changes, in which case we update the
+ * existing label widged rather than inserting a new one.
+ */
+void
+ondesc(void *arg, struct sioctl_desc *d, int curval)
+{
+ struct info *i, **pi;
+ int cmp;
+
+ if (d == NULL)
+ return;
+
+ /*
+ * delete control
+ */
+ for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
+ if (d->addr == i->desc.addr) {
+ if (m_flag)
+ print_par(i, 0, "deleted");
+ *pi = i->next;
+ free(i);
+ break;
+ }
+ }
+
+ if (d->type == SIOCTL_NONE)
+ return;
+
+ /*
+ * find the right position to insert the new widget
+ */
+ for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
+ cmp = cmpdesc(d, &i->desc);
+ if (cmp == 0) {
+ fprintf(stderr, "fatal: duplicate control:\n");
+ print_par(i, 0, "duplicate");
+ exit(1);
+ }
+ if (cmp < 0)
+ break;
+ }
+ i = malloc(sizeof(struct info));
+ if (i == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+ i->desc = *d;
+ i->ctladdr = d->addr;
+ i->curval = i->newval = curval;
+ i->mode = MODE_IGNORE;
+ i->next = *pi;
+ *pi = i;
+ if (m_flag)
+ print_par(i, 0, "added");
+}
+
+/*
+ * update a knob/button state, called from the poll() loop
+ */
+void
+onctl(void *arg, unsigned addr, unsigned val)
+{
+ struct info *i;
+
+ for (i = infolist; i != NULL; i = i->next) {
+ if (i->ctladdr != addr)
+ continue;
+ i->curval = val;
+ if (m_flag)
+ print_par(i, 0, "changed");
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ char *devname = SIO_DEVANY;
+ int i, c, d_flag = 0;
+ struct info *g;
+ struct pollfd *pfds;
+ int nfds, revents;
+
+ while ((c = getopt(argc, argv, "df:imv")) != -1) {
+ switch (c) {
+ case 'd':
+ d_flag = 1;
+ break;
+ case 'f':
+ devname = optarg;
+ break;
+ case 'i':
+ i_flag = 1;
+ break;
+ case 'm':
+ m_flag = 1;
+ break;
+ case 'v':
+ v_flag++;
+ break;
+ default:
+ fprintf(stderr, "usage: sndioctl "
+ "[-dimnv] [-f device] [command ...]\n");
+ exit(1);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ hdl = sioctl_open(devname, SIOCTL_READ | SIOCTL_WRITE, 0);
+ if (hdl == NULL) {
+ fprintf(stderr, "%s: can't open control device\n", devname);
+ exit(1);
+ }
+ if (!sioctl_ondesc(hdl, ondesc, NULL)) {
+ fprintf(stderr, "%s: can't get device description\n", devname);
+ exit(1);
+ }
+ sioctl_onval(hdl, onctl, NULL);
+
+ if (d_flag) {
+ if (argc > 0) {
+ fprintf(stderr,
+ "commands are not allowed with -d option\n");
+ exit(1);
+ }
+ dump();
+ } else {
+ if (argc == 0) {
+ for (g = infolist; g != NULL; g = nextfunc(g))
+ g->mode = MODE_PRINT;
+ } else {
+ for (i = 0; i < argc; i++) {
+ if (!cmd(argv[i]))
+ return 1;
+ }
+ }
+ commit();
+ list();
+ }
+ if (m_flag) {
+ pfds = malloc(sizeof(struct pollfd) * sioctl_nfds(hdl));
+ if (pfds == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+ for (;;) {
+ nfds = sioctl_pollfd(hdl, pfds, POLLIN);
+ if (nfds == 0)
+ break;
+ while (poll(pfds, nfds, -1) < 0) {
+ if (errno != EINTR) {
+ perror("poll");
+ exit(1);
+ }
+ }
+ revents = sioctl_revents(hdl, pfds);
+ if (revents & POLLHUP) {
+ fprintf(stderr, "disconnected\n");
+ break;
+ }
+ }
+ free(pfds);
+ }
+ sioctl_close(hdl);
+ return 0;
+}