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