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 <a...@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: 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 <a...@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 <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; > +} >