On Sun, Feb 09, 2020 at 12:14:47PM GMT, Alexandre Ratchov wrote:
> Here's a new sndioctl utility similar to mixerctl(1) but using the new
> sndio API. Example:
>
> $ sndioctl
> output.level=127
> app/aucat0.level=127
> app/firefox0.level=127
> app/firefox1.level=12
> app/midisyn0.level=127
> app/mpv0.level=127
> app/prog5.level=127
> app/prog6.level=127
> app/prog7.level=127
> hw/input.level=62
> hw/input.mute=0
> hw/output.level=63
> hw/output.mute=0
>
Hi Alexandre,
Just a quick question.
Is there a good reason to have the above using "slash" ('/') as the
first separator instead of the, more familiar, "dot" ('.') known
from sysctl(8)'s MIB (Management Information Base) style names or
even the "pseudo" MIB know from mixerctl(1)?
Regards,
Raf
> Configuration parameters that are not exposed by sndiod will be
> handled by audioctl(1), including the /etc/mixerctl.conf file at
> system startup.
>
> Originally the program was designed to handle modern many-channel
> devices by presenting many-channel knobs on a single line; this
> feature isn't used yet as the corresponding kernel bits are missing.
>
> Index: usr.bin/Makefile
> ===================================================================
> RCS file: /cvs/src/usr.bin/Makefile,v
> retrieving revision 1.161
> diff -u -p -u -p -r1.161 Makefile
> --- usr.bin/Makefile 9 Aug 2019 06:18:25 -0000 1.161
> +++ usr.bin/Makefile 9 Feb 2020 11:05:02 -0000
> @@ -22,7 +22,7 @@ SUBDIR= apply arch at aucat audioctl awk
> pr printenv printf quota radioctl rcs rdist rdistd \
> readlink renice rev rpcgen rpcinfo rs rsync rup rusers rwall \
> sdiff script sed sendbug shar showmount signify skey \
> - skeyaudit skeyinfo skeyinit sndiod snmp \
> + skeyaudit skeyinfo skeyinit sndioctl sndiod snmp \
> sort spell split ssh stat su systat \
> tail talk tcpbench tee telnet tftp tic time \
> tmux top touch tput tr true tset tsort tty usbhidaction usbhidctl \
> Index: usr.bin/sndioctl/Makefile
> ===================================================================
> RCS file: usr.bin/sndioctl/Makefile
> diff -N usr.bin/sndioctl/Makefile
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ usr.bin/sndioctl/Makefile 9 Feb 2020 11:05:02 -0000
> @@ -0,0 +1,5 @@
> +# $OpenBSD$
> +
> +PROG= sndioctl
> +LDADD+= -lsndio
> +.include <bsd.prog.mk>
> Index: usr.bin/sndioctl/sndioctl.1
> ===================================================================
> RCS file: usr.bin/sndioctl/sndioctl.1
> diff -N usr.bin/sndioctl/sndioctl.1
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ usr.bin/sndioctl/sndioctl.1 9 Feb 2020 11:05:02 -0000
> @@ -0,0 +1,148 @@
> +.\" $OpenBSD$
> +.\"
> +.\" Copyright (c) 2007 Alexandre Ratchov <[email protected]>
> +.\"
> +.\" 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: April 8 2011 $
> +.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
> +.Bk -words
> +.Fl d
> +.Ek
> +.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 debug tool.
> +.It Fl f Ar device
> +Use this
> +.Xr sndio 7
> +audio device.
> +.It Fl m
> +Monitor and display audio parameters changes.
> +.It Fl i
> +Display characteristics of requested parameters
> +instead of their values.
> +.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
> +numer.
> +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
> Index: usr.bin/sndioctl/sndioctl.c
> ===================================================================
> RCS file: usr.bin/sndioctl/sndioctl.c
> diff -N usr.bin/sndioctl/sndioctl.c
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ usr.bin/sndioctl/sndioctl.c 9 Feb 2020 11:05:02 -0000
> @@ -0,0 +1,930 @@
> +/* $OpenBSD$ */
> +/*
> + * Copyright (c) 2007-2011 Alexandre Ratchov <[email protected]>
> + *
> + * 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 <stdlib.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sndio.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_dec(char **, int *);
> +int parse_node(char **, char *, int *);
> +int parse_modeval(char **, int *, int *);
> +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 == '_');
> +}
> +
> +/*
> + * 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("%u", p->curval);
> + 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(":%u", e->curval);
> + 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 number
> + */
> +int
> +parse_dec(char **line, int *num)
> +{
> +#define MAXQ (SIOCTL_VALMAX / 10)
> +#define MAXR (SIOCTL_VALMAX % 10)
> + char *p = *line;
> + unsigned int dig, val;
> +
> + val = 0;
> + for (;;) {
> + dig = *p - '0';
> + if (dig >= 10)
> + break;
> + if (val > MAXQ || (val == MAXQ && dig > MAXR)) {
> + fprintf(stderr, "integer overflow\n");
> + return 0;
> + }
> + val = val * 10 + dig;
> + p++;
> + }
> + if (p == *line) {
> + fprintf(stderr, "number expected near '%s'\n", p);
> + return 0;
> + }
> + *num = val;
> + *line = p;
> + 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_dec(&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, int *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_dec(&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("* (%u)", i->curval);
> + break;
> + case SIOCTL_VEC:
> + case SIOCTL_LIST:
> + print_node(&i->desc.node1, 0);
> + printf(":* (%u)", i->curval);
> + }
> + printf("\n");
> + }
> +}
> +
> +/*
> + * parse and execute a command ``<parameter>[=<value>]''
> + */
> +int
> +cmd(char *line)
> +{
> + char *pos = line;
> + struct info *i, *e, *g;
> + char group[SIOCTL_NAMEMAX];
> + char func[SIOCTL_NAMEMAX];
> + char astr[SIOCTL_NAMEMAX], vstr[SIOCTL_NAMEMAX];
> + int aunit, vunit;
> + unsigned npar = 0, nent = 0;
> + int val, comma, mode;
> +
> + if (!parse_name(&pos, group))
> + return 0;
> + if (*pos == '/')
> + pos++;
> + else {
> + /* this was node string, go backwards and assume no group */
> + pos = line;
> + group[0] = '\0';
> + }
> + 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 group\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 = val;
> + 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 = SIOCTL_VALMAX;
> + 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 = val;
> + 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 > SIOCTL_VALMAX)
> + val = SIOCTL_VALMAX;
> + break;
> + case MODE_SUB:
> + val = i->curval - i->newval;
> + if (val < 0)
> + val = 0;
> + break;
> + case MODE_TOGGLE:
> + val = (i->curval >= SIOCTL_VALMAX / 2) ?
> + 0 : SIOCTL_VALMAX;
> + }
> + switch (i->desc.type) {
> + case SIOCTL_NUM:
> + case SIOCTL_SW:
> + sioctl_setval(hdl, i->ctladdr, val);
> + break;
> + case SIOCTL_VEC:
> + case SIOCTL_LIST:
> + 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 = SIOCTL_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;
> +}
>