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;
> +}
> 

Reply via email to