Audio control API, part 1: libsndio, sndiod bits

classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

Audio control API, part 1: libsndio, sndiod bits

Alexandre Ratchov-2
This diff adds an API to control knobs of audio interfaces. It aims to
address these points:

- allow the controls of modern audio hardware and sndiod software
  volume knobs in a uniform way. Hardware knobs are exposed by sndiod,
  which will be also important from security standpoint: by default
  programs using this API won't need to mess with /dev nodes.

- allow multiple programs to use the controls at the same time without
  the need to continuously scan the controls.

- the API is multi-channel and allows programs to "clump" controls and
  to represent many knobs as a single one. For instance the volume
  knob of a "modern" 10-channel azalia(4) could be represented as a
  single knob. This requires driver changes, not yet there.

- the API allows controls to be dynamically added or removed. This is
  useful to avoid restarting programs when devices are swapped,
  ex. when usb head-sets are used.

For now sndiod exposes only its own controls and the master output and
input volume knobs of the underlying hardware (if any). Other controls
don't seem necessary to expose to regular programs; we can keep things
simple and leave the other controls as configuration parameters set
only once, at system startup.

To ease discussions and review, the diff is split in 4 parts. This
diff is the first of 3 base diffs and 1 ports diff (see next
mails). To test the new code, apply all diffs, for instance:

cd /usr/src
patch -p0 <1.diff
patch -p0 <2.diff
patch -p0 <3.diff
cd /usr/src/include && doas make includes
cd /usr/src/lib/libsndio && make obj && make && doas make install
cd /usr/src/lib/libossaudio && make obj && make && doas make install
cd /usr/src/usr.bin/sndiod && make obj && make && doas make install
cd /usr/src/usr.bin/sndioctl && make obj && make && doas make install
doas rcctl restart sndiod
cd /usr/ports
patch -p0 <4.diff
cd /usr/ports/audio/gqmpeg && make && make install
cd /usr/ports/sysutils/tray-app && make && make install

I'm very interested in any regression. Feedback about the new features
is of course welcome as well.

Enjoy,

Index: include/sndio.h
===================================================================
RCS file: /cvs/src/include/sndio.h,v
retrieving revision 1.9
diff -u -p -u -p -r1.9 sndio.h
--- include/sndio.h 20 Dec 2015 11:29:29 -0000 1.9
+++ include/sndio.h 8 Feb 2020 14:49:37 -0000
@@ -24,12 +24,20 @@
  */
 #define SIO_DEVANY "default"
 #define MIO_PORTANY "default"
+#define SIOCTL_DEVANY "default"
+
+/*
+ * limits
+ */
+#define SIOCTL_NAMEMAX 12 /* max name length */
+#define SIOCTL_VALMAX 127 /* max control value */
 
 /*
  * private ``handle'' structure
  */
 struct sio_hdl;
 struct mio_hdl;
+struct sioctl_hdl;
 
 /*
  * parameters of a full-duplex stream
@@ -85,12 +93,39 @@ struct sio_cap {
 #define SIO_XSTRINGS { "ignore", "sync", "error" }
 
 /*
+ * controlled component of the device
+ */
+struct sioctl_node {
+ char name[SIOCTL_NAMEMAX]; /* ex. "spkr" */
+ int unit; /* optional number or -1 */
+};
+
+/*
+ * description of a control (index, value) pair
+ */
+struct sioctl_desc {
+ unsigned int addr; /* control address */
+#define SIOCTL_NONE 0 /* deleted */
+#define SIOCTL_NUM 2 /* integer in the 0..127 range */
+#define SIOCTL_SW 3 /* on/off switch (0 or 1) */
+#define SIOCTL_VEC 4 /* number, element of vector */
+#define SIOCTL_LIST 5 /* switch, element of a list */
+ unsigned int type; /* one of above */
+ char func[SIOCTL_NAMEMAX]; /* function name, ex. "level" */
+ char group[SIOCTL_NAMEMAX]; /* group this control belongs to */
+ struct sioctl_node node0; /* affected node */
+ struct sioctl_node node1; /* dito for SIOCTL_{VEC,LIST} */
+};
+
+/*
  * mode bitmap
  */
 #define SIO_PLAY 1
 #define SIO_REC 2
 #define MIO_OUT 4
 #define MIO_IN 8
+#define SIOCTL_READ 0x100
+#define SIOCTL_WRITE 0x200
 
 /*
  * default bytes per sample for the given bits per sample
@@ -144,10 +179,24 @@ int mio_pollfd(struct mio_hdl *, struct
 int mio_revents(struct mio_hdl *, struct pollfd *);
 int mio_eof(struct mio_hdl *);
 
+struct sioctl_hdl *sioctl_open(const char *, unsigned int, int);
+void sioctl_close(struct sioctl_hdl *);
+int sioctl_ondesc(struct sioctl_hdl *,
+    void (*)(void *, struct sioctl_desc *, int), void *);
+int sioctl_onval(struct sioctl_hdl *,
+    void (*)(void *, unsigned int, unsigned int), void *);
+int sioctl_setval(struct sioctl_hdl *, unsigned int, unsigned int);
+int sioctl_nfds(struct sioctl_hdl *);
+int sioctl_pollfd(struct sioctl_hdl *, struct pollfd *, int);
+int sioctl_revents(struct sioctl_hdl *, struct pollfd *);
+int sioctl_eof(struct sioctl_hdl *);
+
 int mio_rmidi_getfd(const char *, unsigned int, int);
 struct mio_hdl *mio_rmidi_fdopen(int, unsigned int, int);
 int sio_sun_getfd(const char *, unsigned int, int);
 struct sio_hdl *sio_sun_fdopen(int, unsigned int, int);
+int sioctl_sun_getfd(const char *, unsigned int, int);
+struct sioctl_hdl *sioctl_sun_fdopen(int, unsigned int, int);
 
 #ifdef __cplusplus
 }
Index: lib/libsndio/Makefile
===================================================================
RCS file: /cvs/src/lib/libsndio/Makefile,v
retrieving revision 1.13
diff -u -p -u -p -r1.13 Makefile
--- lib/libsndio/Makefile 26 Dec 2017 15:23:33 -0000 1.13
+++ lib/libsndio/Makefile 8 Feb 2020 14:49:37 -0000
@@ -1,9 +1,10 @@
 # $OpenBSD: Makefile,v 1.13 2017/12/26 15:23:33 jca Exp $
 
 LIB= sndio
-MAN= sio_open.3 mio_open.3 sndio.7
+MAN= sio_open.3 mio_open.3 sioctl_open.3 sndio.7
 SRCS= debug.c aucat.c sio_aucat.c sio_sun.c sio.c \
- mio_rmidi.c mio_aucat.c mio.c
+ mio_rmidi.c mio_aucat.c mio.c \
+ sioctl_aucat.c sioctl_sun.c sioctl.c
 CFLAGS+=-DDEBUG
 COPTS+= -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wundef
 
Index: lib/libsndio/Symbols.map
===================================================================
RCS file: /cvs/src/lib/libsndio/Symbols.map,v
retrieving revision 1.1
diff -u -p -u -p -r1.1 Symbols.map
--- lib/libsndio/Symbols.map 26 Dec 2017 19:12:22 -0000 1.1
+++ lib/libsndio/Symbols.map 8 Feb 2020 14:49:37 -0000
@@ -27,10 +27,22 @@
  mio_revents;
  mio_eof;
 
+ sioctl_open;
+ sioctl_close;
+ sioctl_ondesc;
+ sioctl_onval;
+ sioctl_setval;
+ sioctl_nfds;
+ sioctl_pollfd;
+ sioctl_revents;
+ sioctl_eof;
+
  mio_rmidi_getfd;
  mio_rmidi_fdopen;
  sio_sun_getfd;
  sio_sun_fdopen;
+ sioctl_sun_getfd;
+ sioctl_sun_fdopen;
  local:
  *;
 };
Index: lib/libsndio/amsg.h
===================================================================
RCS file: /cvs/src/lib/libsndio/amsg.h,v
retrieving revision 1.12
diff -u -p -u -p -r1.12 amsg.h
--- lib/libsndio/amsg.h 12 Jul 2019 06:30:55 -0000 1.12
+++ lib/libsndio/amsg.h 8 Feb 2020 14:49:37 -0000
@@ -43,6 +43,11 @@
 #define AUCAT_PORT 11025
 
 /*
+ * limits
+ */
+#define AMSG_CTL_NAMEMAX 16 /* max name length */
+
+/*
  * WARNING: since the protocol may be simultaneously used by static
  * binaries or by different versions of a shared library, we are not
  * allowed to change the packet binary representation in a backward
@@ -64,6 +69,9 @@ struct amsg {
 #define AMSG_HELLO 10 /* say hello, check versions and so ... */
 #define AMSG_BYE 11 /* ask server to drop connection */
 #define AMSG_AUTH 12 /* send authentication cookie */
+#define AMSG_CTLSUB 13 /* ondesc/onctl subscription */
+#define AMSG_CTLSET 14 /* set control value */
+#define AMSG_CTLSYNC 15 /* end of controls descriptions */
  uint32_t cmd;
  uint32_t __pad;
  union {
@@ -108,7 +116,37 @@ struct amsg {
 #define AMSG_COOKIELEN 16
  uint8_t cookie[AMSG_COOKIELEN];
  } auth;
+ struct amsg_ctlsub {
+ uint8_t desc, val;
+ } ctlsub;
+ struct amsg_ctlset {
+ uint16_t addr, val;
+ } ctlset;
  } u;
+};
+
+/*
+ * network representation of sioctl_node structure
+ */
+struct amsg_ctl_node {
+ char name[AMSG_CTL_NAMEMAX];
+ int16_t unit;
+ uint8_t __pad[2];
+};
+
+/*
+ * network representation of sioctl_desc structure
+ */
+struct amsg_ctl_desc {
+ struct amsg_ctl_node node0; /* affected channels */
+ struct amsg_ctl_node node1; /* dito for AMSG_CTL_{SEL,VEC,LIST} */
+ char func[AMSG_CTL_NAMEMAX]; /* parameter function name */
+ char group[AMSG_CTL_NAMEMAX]; /* group of the control */
+ uint8_t type; /* see sioctl_desc structure */
+ uint8_t __pad1[1];
+ uint16_t addr; /* control address */
+ uint16_t __pad2[1];
+ uint16_t curval;
 };
 
 /*
Index: lib/libsndio/shlib_version
===================================================================
RCS file: /cvs/src/lib/libsndio/shlib_version,v
retrieving revision 1.11
diff -u -p -u -p -r1.11 shlib_version
--- lib/libsndio/shlib_version 26 Dec 2017 15:23:33 -0000 1.11
+++ lib/libsndio/shlib_version 8 Feb 2020 14:49:37 -0000
@@ -1,2 +1,2 @@
 major=7
-minor=0
+minor=1
Index: lib/libsndio/sioctl.c
===================================================================
RCS file: lib/libsndio/sioctl.c
diff -N lib/libsndio/sioctl.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl.c 8 Feb 2020 14:49:37 -0000
@@ -0,0 +1,177 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <[hidden email]>
+ *
+ * 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 <string.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "sioctl_priv.h"
+
+struct sioctl_hdl *
+sioctl_open(const char *str, unsigned int mode, int nbio)
+{
+ static char devany[] = SIOCTL_DEVANY;
+ struct sioctl_hdl *hdl;
+
+#ifdef DEBUG
+ _sndio_debug_init();
+#endif
+ if (str == NULL) /* backward compat */
+ str = devany;
+ if (strcmp(str, devany) == 0 && !issetugid()) {
+ str = getenv("AUDIODEVICE");
+ if (str == NULL)
+ str = devany;
+ }
+ if (strcmp(str, devany) == 0) {
+ hdl = _sioctl_aucat_open("snd/0", mode, nbio);
+ if (hdl != NULL)
+ return hdl;
+ return _sioctl_sun_open("rsnd/0", mode, nbio);
+ }
+ if (_sndio_parsetype(str, "snd"))
+ return _sioctl_aucat_open(str, mode, nbio);
+ if (_sndio_parsetype(str, "rsnd"))
+ return _sioctl_sun_open(str, mode, nbio);
+ DPRINTF("sioctl_open: %s: unknown device type\n", str);
+ return NULL;
+}
+
+void
+_sioctl_create(struct sioctl_hdl *hdl, struct sioctl_ops *ops,
+    unsigned int mode, int nbio)
+{
+ hdl->ops = ops;
+ hdl->mode = mode;
+ hdl->nbio = nbio;
+ hdl->eof = 0;
+ hdl->ctl_cb = NULL;
+}
+
+int
+_sioctl_psleep(struct sioctl_hdl *hdl, int event)
+{
+ struct pollfd pfds[SIOCTL_MAXNFDS];
+ int revents, nfds;
+
+ for (;;) {
+ nfds = sioctl_pollfd(hdl, pfds, event);
+ if (nfds == 0)
+ return 0;
+ while (poll(pfds, nfds, -1) < 0) {
+ if (errno == EINTR)
+ continue;
+ DPERROR("sioctl_psleep: poll");
+ hdl->eof = 1;
+ return 0;
+ }
+ revents = sioctl_revents(hdl, pfds);
+ if (revents & POLLHUP) {
+ DPRINTF("sioctl_psleep: hang-up\n");
+ return 0;
+ }
+ if (event == 0 || (revents & event))
+ break;
+ }
+ return 1;
+}
+
+void
+sioctl_close(struct sioctl_hdl *hdl)
+{
+ hdl->ops->close(hdl);
+}
+
+int
+sioctl_nfds(struct sioctl_hdl *hdl)
+{
+ return hdl->ops->nfds(hdl);
+}
+
+int
+sioctl_pollfd(struct sioctl_hdl *hdl, struct pollfd *pfd, int events)
+{
+ if (hdl->eof)
+ return 0;
+ return hdl->ops->pollfd(hdl, pfd, events);
+}
+
+int
+sioctl_revents(struct sioctl_hdl *hdl, struct pollfd *pfd)
+{
+ if (hdl->eof)
+ return POLLHUP;
+ return hdl->ops->revents(hdl, pfd);
+}
+
+int
+sioctl_eof(struct sioctl_hdl *hdl)
+{
+ return hdl->eof;
+}
+
+int
+sioctl_ondesc(struct sioctl_hdl *hdl,
+    void (*cb)(void *, struct sioctl_desc *, int), void *arg)
+{
+ hdl->desc_cb = cb;
+ hdl->desc_arg = arg;
+ return hdl->ops->ondesc(hdl);
+}
+
+int
+sioctl_onval(struct sioctl_hdl *hdl,
+    void (*cb)(void *, unsigned int, unsigned int), void *arg)
+{
+ hdl->ctl_cb = cb;
+ hdl->ctl_arg = arg;
+ return hdl->ops->onctl(hdl);
+}
+
+void
+_sioctl_ondesc_cb(struct sioctl_hdl *hdl,
+    struct sioctl_desc *desc, unsigned int val)
+{
+ if (desc) {
+ DPRINTF("_sioctl_ondesc_cb: %u -> %s[%d].%s=%s[%d]:%d\n",
+    desc->addr,
+    desc->node0.name, desc->node0.unit,
+    desc->func,
+    desc->node1.name, desc->node1.unit,
+    val);
+ }
+ if (hdl->desc_cb)
+ hdl->desc_cb(hdl->desc_arg, desc, val);
+}
+
+void
+_sioctl_onval_cb(struct sioctl_hdl *hdl, unsigned int addr, unsigned int val)
+{
+ DPRINTF("_sioctl_onval_cb: %u -> %u\n", addr, val);
+ if (hdl->ctl_cb)
+ hdl->ctl_cb(hdl->ctl_arg, addr, val);
+}
+
+int
+sioctl_setval(struct sioctl_hdl *hdl, unsigned int addr, unsigned int val)
+{
+ if (!(hdl->mode & SIOCTL_WRITE))
+ return 0;
+ return hdl->ops->setctl(hdl, addr, val);
+}
Index: lib/libsndio/sioctl_aucat.c
===================================================================
RCS file: lib/libsndio/sioctl_aucat.c
diff -N lib/libsndio/sioctl_aucat.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl_aucat.c 8 Feb 2020 14:49:37 -0000
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2010-2011 Alexandre Ratchov <[hidden email]>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sndio.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include "debug.h"
+#include "aucat.h"
+#include "sioctl_priv.h"
+
+struct sioctl_aucat_hdl {
+ struct sioctl_hdl sioctl;
+ struct aucat aucat;
+ struct sioctl_desc desc;
+ struct amsg_ctl_desc buf[16];
+ size_t buf_wpos;
+ int dump_wait;
+};
+
+static void sioctl_aucat_close(struct sioctl_hdl *);
+static int sioctl_aucat_nfds(struct sioctl_hdl *);
+static int sioctl_aucat_pollfd(struct sioctl_hdl *, struct pollfd *, int);
+static int sioctl_aucat_revents(struct sioctl_hdl *, struct pollfd *);
+static int sioctl_aucat_setctl(struct sioctl_hdl *, unsigned int, unsigned int);
+static int sioctl_aucat_onval(struct sioctl_hdl *);
+static int sioctl_aucat_ondesc(struct sioctl_hdl *);
+
+/*
+ * operations every device should support
+ */
+struct sioctl_ops sioctl_aucat_ops = {
+ sioctl_aucat_close,
+ sioctl_aucat_nfds,
+ sioctl_aucat_pollfd,
+ sioctl_aucat_revents,
+ sioctl_aucat_setctl,
+ sioctl_aucat_onval,
+ sioctl_aucat_ondesc
+};
+
+static int
+sioctl_aucat_rdata(struct sioctl_aucat_hdl *hdl)
+{
+ struct sioctl_desc desc;
+ struct amsg_ctl_desc *c;
+ size_t rpos;
+ int n;
+
+ while (hdl->aucat.rstate == RSTATE_DATA) {
+
+ /* read entries */
+ while (hdl->buf_wpos < sizeof(hdl->buf) &&
+    hdl->aucat.rstate == RSTATE_DATA) {
+ n = _aucat_rdata(&hdl->aucat,
+    (unsigned char *)hdl->buf + hdl->buf_wpos,
+    sizeof(hdl->buf) - hdl->buf_wpos,
+    &hdl->sioctl.eof);
+ if (n == 0 || hdl->sioctl.eof)
+ return 0;
+ hdl->buf_wpos += n;
+ }
+
+ /* parse entries */
+ c = hdl->buf;
+ rpos = 0;
+ while (rpos < hdl->buf_wpos) {
+ strlcpy(desc.group, c->group, SIOCTL_NAMEMAX);
+ strlcpy(desc.node0.name, c->node0.name, SIOCTL_NAMEMAX);
+ desc.node0.unit = (int16_t)ntohs(c->node0.unit);
+ strlcpy(desc.node1.name, c->node1.name, SIOCTL_NAMEMAX);
+ desc.node1.unit = (int16_t)ntohs(c->node1.unit);
+ strlcpy(desc.func, c->func, SIOCTL_NAMEMAX);
+ desc.type = c->type;
+ desc.addr = ntohs(c->addr);
+ _sioctl_ondesc_cb(&hdl->sioctl,
+    &desc, ntohs(c->curval));
+ rpos += sizeof(struct amsg_ctl_desc);
+ c++;
+ }
+ hdl->buf_wpos = 0;
+ }
+ return 1;
+}
+
+/*
+ * execute the next message, return 0 if blocked
+ */
+static int
+sioctl_aucat_runmsg(struct sioctl_aucat_hdl *hdl)
+{
+ if (!_aucat_rmsg(&hdl->aucat, &hdl->sioctl.eof))
+ return 0;
+ switch (ntohl(hdl->aucat.rmsg.cmd)) {
+ case AMSG_DATA:
+ hdl->buf_wpos = 0;
+ if (!sioctl_aucat_rdata(hdl))
+ return 0;
+ break;
+ case AMSG_CTLSET:
+ DPRINTF("sioctl_aucat_runmsg: got CTLSET\n");
+ _sioctl_onval_cb(&hdl->sioctl,
+    ntohs(hdl->aucat.rmsg.u.ctlset.addr),
+    ntohs(hdl->aucat.rmsg.u.ctlset.val));
+ break;
+ case AMSG_CTLSYNC:
+ DPRINTF("sioctl_aucat_runmsg: got CTLSYNC\n");
+ hdl->dump_wait = 0;
+ _sioctl_ondesc_cb(&hdl->sioctl, NULL, 0);
+ break;
+ default:
+ DPRINTF("sio_aucat_runmsg: unhandled message %u\n",
+    hdl->aucat.rmsg.cmd);
+ hdl->sioctl.eof = 1;
+ return 0;
+ }
+ hdl->aucat.rstate = RSTATE_MSG;
+ hdl->aucat.rtodo = sizeof(struct amsg);
+ return 1;
+}
+
+struct sioctl_hdl *
+_sioctl_aucat_open(const char *str, unsigned int mode, int nbio)
+{
+ struct sioctl_aucat_hdl *hdl;
+
+ hdl = malloc(sizeof(struct sioctl_aucat_hdl));
+ if (hdl == NULL)
+ return NULL;
+ if (!_aucat_open(&hdl->aucat, str, mode))
+ goto bad;
+ _sioctl_create(&hdl->sioctl, &sioctl_aucat_ops, mode, nbio);
+ if (!_aucat_setfl(&hdl->aucat, 1, &hdl->sioctl.eof))
+ goto bad;
+ hdl->dump_wait = 0;
+ return (struct sioctl_hdl *)hdl;
+bad:
+ free(hdl);
+ return NULL;
+}
+
+static void
+sioctl_aucat_close(struct sioctl_hdl *addr)
+{
+ struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+ if (!hdl->sioctl.eof)
+ _aucat_setfl(&hdl->aucat, 0, &hdl->sioctl.eof);
+ _aucat_close(&hdl->aucat, hdl->sioctl.eof);
+ free(hdl);
+}
+
+static int
+sioctl_aucat_ondesc(struct sioctl_hdl *addr)
+{
+ struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+ while (hdl->aucat.wstate != WSTATE_IDLE) {
+ if (!_sioctl_psleep(&hdl->sioctl, POLLOUT))
+ return 0;
+ }
+ AMSG_INIT(&hdl->aucat.wmsg);
+ hdl->aucat.wmsg.cmd = htonl(AMSG_CTLSUB);
+ hdl->aucat.wmsg.u.ctlsub.desc = 1;
+ hdl->aucat.wmsg.u.ctlsub.val = 0;
+ hdl->aucat.wtodo = sizeof(struct amsg);
+ if (!_aucat_wmsg(&hdl->aucat, &hdl->sioctl.eof))
+ return 0;
+ hdl->dump_wait = 1;
+ while (hdl->dump_wait) {
+ DPRINTF("psleeping...\n");
+ if (!_sioctl_psleep(&hdl->sioctl, 0))
+ return 0;
+ DPRINTF("psleeping done\n");
+ }
+ DPRINTF("done\n");
+ return 1;
+}
+
+static int
+sioctl_aucat_onval(struct sioctl_hdl *addr)
+{
+ struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+ while (hdl->aucat.wstate != WSTATE_IDLE) {
+ if (!_sioctl_psleep(&hdl->sioctl, POLLOUT))
+ return 0;
+ }
+ AMSG_INIT(&hdl->aucat.wmsg);
+ hdl->aucat.wmsg.cmd = htonl(AMSG_CTLSUB);
+ hdl->aucat.wmsg.u.ctlsub.desc = 1;
+ hdl->aucat.wmsg.u.ctlsub.val = 1;
+ hdl->aucat.wtodo = sizeof(struct amsg);
+ if (!_aucat_wmsg(&hdl->aucat, &hdl->sioctl.eof))
+ return 0;
+ return 1;
+}
+
+static int
+sioctl_aucat_setctl(struct sioctl_hdl *addr, unsigned int a, unsigned int v)
+{
+ struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+ hdl->aucat.wstate = WSTATE_MSG;
+ hdl->aucat.wtodo = sizeof(struct amsg);
+ hdl->aucat.wmsg.cmd = htonl(AMSG_CTLSET);
+ hdl->aucat.wmsg.u.ctlset.addr = htons(a);
+ hdl->aucat.wmsg.u.ctlset.val = htons(v);
+ while (hdl->aucat.wstate != WSTATE_IDLE) {
+ if (_aucat_wmsg(&hdl->aucat, &hdl->sioctl.eof))
+ break;
+ if (hdl->sioctl.nbio || !_sioctl_psleep(&hdl->sioctl, POLLOUT))
+ return 0;
+ }
+ return 1;
+}
+
+static int
+sioctl_aucat_nfds(struct sioctl_hdl *addr)
+{
+ return 1;
+}
+
+static int
+sioctl_aucat_pollfd(struct sioctl_hdl *addr, struct pollfd *pfd, int events)
+{
+ struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+ return _aucat_pollfd(&hdl->aucat, pfd, events | POLLIN);
+}
+
+static int
+sioctl_aucat_revents(struct sioctl_hdl *addr, struct pollfd *pfd)
+{
+ struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+ int revents;
+
+ revents = _aucat_revents(&hdl->aucat, pfd);
+ if (revents & POLLIN) {
+ while (1) {
+ if (hdl->aucat.rstate == RSTATE_MSG) {
+ if (!sioctl_aucat_runmsg(hdl))
+ break;
+ }
+ if (hdl->aucat.rstate == RSTATE_DATA) {
+ if (!sioctl_aucat_rdata(hdl))
+ break;
+ }
+ }
+ revents &= ~POLLIN;
+ }
+ if (hdl->sioctl.eof)
+ return POLLHUP;
+ DPRINTFN(3, "sioctl_aucat_revents: revents = 0x%x\n", revents);
+ return revents;
+}
Index: lib/libsndio/sioctl_open.3
===================================================================
RCS file: lib/libsndio/sioctl_open.3
diff -N lib/libsndio/sioctl_open.3
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl_open.3 8 Feb 2020 14:49:37 -0000
@@ -0,0 +1,252 @@
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2011 Alexandre Ratchov <[hidden email]>
+.\"
+.\" 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: September 29 2012 $
+.Dt SIO_OPEN 3
+.Os
+.Sh NAME
+.Nm sioctl_open ,
+.Nm sioctl_close ,
+.Nm sioctl_ondesc ,
+.Nm sioctl_onval ,
+.Nm sioctl_setval ,
+.Nm sioctl_nfds ,
+.Nm sioctl_pollfd ,
+.Nm sioctl_eof
+.Nd interface to audio parameters
+.Sh SYNOPSIS
+.Fd #include <sndio.h>
+.Ft "struct sioctl_hdl *"
+.Fn "sioctl_open" "const char *name" "unsigned int mode" "int nbio_flag"
+.Ft "void"
+.Fn "sioctl_close" "struct sioctl_hdl *hdl"
+.Ft "int"
+.Fn "sioctl_ondesc" "struct sioctl_hdl *hdl" "void (*cb)(void *arg, struct sioctl_desc *desc, int val)" "void *arg"
+.Ft "void"
+.Fn "sioctl_onval" "struct sioctl_hdl *hdl" "void (*cb)(void *arg, unsigned int addr, unsigned int val)" "void *arg"
+.Ft "int"
+.Fn "sioctl_setval" "struct sioctl_hdl *hdl" "unsigned int addr" "unsigned int val"
+.Ft "int"
+.Fn "sioctl_nfds" "struct sioctl_hdl *hdl"
+.Ft "int"
+.Fn "sioctl_pollfd" "struct sioctl_hdl *hdl" "struct pollfd *pfd" "int events"
+.Ft "int"
+.Fn "sioctl_revents" "struct sioctl_hdl *hdl" "struct pollfd *pfd"
+.Ft "int"
+.Fn "sioctl_eof" "struct sioctl_hdl *hdl"
+.Sh DESCRIPTION
+Audio devices may expose a number of controls, like the playback volume control.
+Each control has an integer
+.Em address
+and an integer
+.Em value .
+Depending on the control type, its integer value represents either a
+continuous quantity or a boolean.
+Any control may be changed by submitting
+a new value to its address.
+When values change, asynchronous notifications are sent.
+.Pp
+Controls descriptions are available, allowing them to be grouped and
+represented in a human usable form.
+.Sh Opening and closing the control device
+First the application must call the
+.Fn sioctl_open
+function to obtain a handle
+that will be passed as the
+.Ar hdl
+argument to other functions.
+.Pp
+The
+.Ar name
+parameter gives the device string discussed in
+.Xr sndio 7 .
+In most cases it should be set to SIOCTL_DEVANY to allow
+the user to select it using the
+.Ev AUDIODEVICE
+environment variable.
+The
+.Ar mode
+parameter is a bitmap of the SIOCTL_READ and SIOCTL_WRITE constants
+indicating whether control values can be read and
+modified respectively.
+.Pp
+If the
+.Ar nbio_flag
+argument is 1, then the
+.Fn sioctl_setval
+function (see below) may fail instead of blocking and
+the
+.Fn sioctl_ondesc
+function doesn't block.
+.Pp
+The
+.Fn sioctl_close
+function closes the control device and frees any allocated resources
+associated with the handle.
+.Sh Controls descriptions
+The
+.Fn sioctl_ondesc
+function can be used to obtain the description of all available controls
+and their initial values.
+It registers a call-back that is immediately invoked for all
+controls.
+It's called once with a NULL argument to indicate that the full
+description was sent and that the caller has a consistent
+representation of the controls set.
+.Pp
+Then, whenever a control description changes, the call-back is
+invoked with the updated information followed by a call with a NULL
+argument.
+.Pp
+Controls are described by the
+.Va sioctl_ondesc
+stucture as follows:
+.Bd -literal
+struct sioctl_node {
+ char name[SIOCTL_NAMEMAX]; /* ex. "spkr" */
+ int unit; /* optional number or -1 */
+};
+
+struct sioctl_desc {
+ unsigned int addr; /* control address */
+#define SIOCTL_NONE 0 /* deleted */
+#define SIOCTL_NUM 2 /* integer in the 0..127 range */
+#define SIOCTL_SW 3 /* on/off switch (0 or 1) */
+#define SIOCTL_VEC 4 /* number, element of vector */
+#define SIOCTL_LIST 5 /* switch, element of a list */
+ unsigned int type; /* one of above */
+ char func[SIOCTL_NAMEMAX]; /* function name, ex. "level" */
+ char group[SIOCTL_NAMEMAX]; /* group this control belongs to */
+ struct sioctl_node node0; /* affected node */
+ struct sioctl_node node1; /* dito for SIOCTL_{VEC,LIST} */
+};
+.Ed
+.Pp
+The
+.Va addr
+attribute is the control address, usable with
+.Fn sioctl_setval
+to set its value.
+.Pp
+The
+.Va type
+attribute indicates what the structure describes.
+Possible types are:
+.Bl -tag -width "SIOCTL_LIST"
+.It SIOCTL_NONE
+A previously valid control was deleted.
+.It SIOCTL_NUM
+A continuous control in the 0..SIOCTL_VALMAX range.
+For instance the volume of the speaker.
+.It SIOCTL_SW
+A on/off switch control.
+For instance the switch to mute the speaker.
+.It SIOCTL_VEC
+Element of an array of continuous controls.
+For instance the knob to control the amount of signal flowing
+from the line input to the speaker.
+.It SIOCTL_LIST
+An element of an array of on/off switches.
+For instance the line-in position of the
+speaker source selector.
+.El
+.Pp
+The
+.Va func
+attribute is the name of the parameter being controlled.
+There may be no parameters of different types with the same name.
+.Pp
+The
+.Va node0
+and
+.Va node1
+attributes indicate the names of the controlled nodes, typically
+channels of audio streams.
+.Va node1
+is meaningful for
+.Va SIOCTL_VEC
+and
+.Va SIOCTL_LIST
+only.
+.Pp
+Names in the
+.Va node0
+and
+.Va node1
+attributes and
+.Va func
+are strings usable as unique identifiers within the the given
+.Va group .
+.Sh Changing and reading control values
+Controls are changed with the
+.Fn sioctl_setval
+function, by giving the index of the control and the new value.
+The
+.Fn sioctl_onval
+function can be used to register a call-back which will be invoked whenever
+a control changes.
+Continuous values are in the 0..127 range.
+.Sh "Interface to" Xr poll 2
+The
+.Fn sioctl_pollfd
+function fills the array
+.Ar pfd
+of
+.Va pollfd
+structures, used by
+.Xr poll 2 ,
+with
+.Ar events ;
+the latter is a bit-mask of
+.Va POLLIN
+and
+.Va POLLOUT
+constants.
+.Fn sioctl_pollfd
+returns the number of
+.Va pollfd
+structures filled.
+The
+.Fn sioctl_revents
+function returns the bit-mask set by
+.Xr poll 2
+in the
+.Va pfd
+array of
+.Va pollfd
+structures.
+If
+.Va POLLOUT
+is set,
+.Fn sioctl_setval
+can be called without blocking.
+POLLHUP may be set if an error occurs, even if
+it is not selected with
+.Fn sioctl_pollfd .
+POLLIN is not used yet.
+.Pp
+The
+.Fn sioctl_nfds
+function returns the number of
+.Va pollfd
+structures the caller must preallocate in order to be sure
+that
+.Fn sioctl_pollfd
+will never overrun.
+.Sh SEE ALSO
+.Xr sndioctl 1 ,
+.Xr poll 2 ,
+.Xr sndio 7
Index: lib/libsndio/sioctl_priv.h
===================================================================
RCS file: lib/libsndio/sioctl_priv.h
diff -N lib/libsndio/sioctl_priv.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl_priv.h 8 Feb 2020 14:49:38 -0000
@@ -0,0 +1,62 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <[hidden email]>
+ *
+ * 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.
+ */
+#ifndef SIOCTL_PRIV_H
+#define SIOCTL_PRIV_H
+
+#include <sndio.h>
+
+#define SIOCTL_MAXNFDS 4
+
+/*
+ * private ``handle'' structure
+ */
+struct sioctl_hdl {
+ struct sioctl_ops *ops;
+ void (*desc_cb)(void *, struct sioctl_desc *, int);
+ void *desc_arg;
+ void (*ctl_cb)(void *, unsigned int, unsigned int);
+ void *ctl_arg;
+ unsigned int mode; /* SIOCTL_READ | SIOCTL_WRITE */
+ int nbio; /* true if non-blocking io */
+ int eof; /* true if error occured */
+};
+
+/*
+ * operations every device should support
+ */
+struct sioctl_ops {
+ void (*close)(struct sioctl_hdl *);
+ int (*nfds)(struct sioctl_hdl *);
+ int (*pollfd)(struct sioctl_hdl *, struct pollfd *, int);
+ int (*revents)(struct sioctl_hdl *, struct pollfd *);
+ int (*setctl)(struct sioctl_hdl *, unsigned int, unsigned int);
+ int (*onctl)(struct sioctl_hdl *);
+ int (*ondesc)(struct sioctl_hdl *);
+};
+
+struct sioctl_hdl *_sioctl_aucat_open(const char *, unsigned int, int);
+struct sioctl_hdl *_sioctl_obsd_open(const char *, unsigned int, int);
+struct sioctl_hdl *_sioctl_fake_open(const char *, unsigned int, int);
+struct sioctl_hdl *_sioctl_sun_open(const char *, unsigned int, int);
+void _sioctl_create(struct sioctl_hdl *,
+    struct sioctl_ops *, unsigned int, int);
+void _sioctl_ondesc_cb(struct sioctl_hdl *,
+    struct sioctl_desc *, unsigned int);
+void _sioctl_onval_cb(struct sioctl_hdl *, unsigned int, unsigned int);
+int _sioctl_psleep(struct sioctl_hdl *, int);
+
+#endif /* !defined(SIOCTL_PRIV_H) */
Index: lib/libsndio/sioctl_sun.c
===================================================================
RCS file: lib/libsndio/sioctl_sun.c
diff -N lib/libsndio/sioctl_sun.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl_sun.c 8 Feb 2020 14:49:38 -0000
@@ -0,0 +1,486 @@
+/*
+ * Copyright (c) 2010-2011 Alexandre Ratchov <[hidden email]>
+ *
+ * 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.
+ */
+/*
+ * the way the sun mixer is designed doesn't let us representing
+ * it easily with the sioctl api. For now expose only few
+ * white-listed controls the same way as we do in kernel
+ * for the wskbd volume keys.
+ */
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/audioio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "sioctl_priv.h"
+
+#define DEVPATH_PREFIX "/dev/audioctl"
+#define DEVPATH_MAX (1 + \
+ sizeof(DEVPATH_PREFIX) - 1 + \
+ sizeof(int) * 3)
+
+#define SUN_TO_SIOCTL(v) (((v) * 127 + 127) / 255)
+#define SIOCTL_TO_SUN(v) (((v) * 255 + 63) / 127)
+
+struct volume
+{
+ int nch; /* channels in the level control */
+ int level_idx; /* index of the level control */
+ int level_val[8]; /* current value */
+ int mute_idx; /* index of the mute control */
+ int mute_val; /* per channel state of mute control */
+ int base_addr;
+ char *name;
+};
+
+struct sioctl_sun_hdl {
+ struct sioctl_hdl sioctl;
+ struct volume output, input;
+ int fd, events;
+};
+
+static void sioctl_sun_close(struct sioctl_hdl *);
+static int sioctl_sun_nfds(struct sioctl_hdl *);
+static int sioctl_sun_pollfd(struct sioctl_hdl *, struct pollfd *, int);
+static int sioctl_sun_revents(struct sioctl_hdl *, struct pollfd *);
+static int sioctl_sun_setctl(struct sioctl_hdl *, unsigned int, unsigned int);
+static int sioctl_sun_onval(struct sioctl_hdl *);
+static int sioctl_sun_ondesc(struct sioctl_hdl *);
+
+/*
+ * operations every device should support
+ */
+struct sioctl_ops sioctl_sun_ops = {
+ sioctl_sun_close,
+ sioctl_sun_nfds,
+ sioctl_sun_pollfd,
+ sioctl_sun_revents,
+ sioctl_sun_setctl,
+ sioctl_sun_onval,
+ sioctl_sun_ondesc
+};
+
+static int
+initmute(struct sioctl_sun_hdl *hdl, struct mixer_devinfo *info)
+{
+ struct mixer_devinfo mi;
+
+ mi.index = info->next;
+ for (mi.index = info->next; mi.index != -1; mi.index = mi.next) {
+ if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &mi) < 0)
+ break;
+ if (strcmp(mi.label.name, AudioNmute) == 0)
+ return mi.index;
+ }
+ return -1;
+}
+
+static int
+initvol(struct sioctl_sun_hdl *hdl, struct volume *vol, char *cn, char *dn)
+{
+ struct mixer_devinfo dev, cls;
+
+ for (dev.index = 0; ; dev.index++) {
+ if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &dev) < 0)
+ break;
+ if (dev.type != AUDIO_MIXER_VALUE)
+ continue;
+ cls.index = dev.mixer_class;
+ if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &cls) < 0)
+ break;
+ if (strcmp(cls.label.name, cn) == 0 &&
+    strcmp(dev.label.name, dn) == 0) {
+ vol->nch = dev.un.v.num_channels;
+ vol->level_idx = dev.index;
+ vol->mute_idx = initmute(hdl, &dev);
+ DPRINTF("using %s.%s, %d channels, %s\n", cn, dn,
+    vol->nch, vol->mute_idx >= 0 ? "mute" : "no mute");
+ return 1;
+ }
+ }
+ vol->level_idx = vol->mute_idx = -1;
+ return 0;
+}
+
+static void
+init(struct sioctl_sun_hdl *hdl)
+{
+ static struct {
+ char *cn, *dn;
+ } output_names[] = {
+ {AudioCoutputs, AudioNmaster},
+ {AudioCinputs,  AudioNdac},
+ {AudioCoutputs, AudioNdac},
+ {AudioCoutputs, AudioNoutput}
+ }, input_names[] = {
+ {AudioCrecord, AudioNrecord},
+ {AudioCrecord, AudioNvolume},
+ {AudioCinputs, AudioNrecord},
+ {AudioCinputs, AudioNvolume},
+ {AudioCinputs, AudioNinput}
+ };
+ int i;
+
+ for (i = 0; i < sizeof(output_names) / sizeof(output_names[0]); i++) {
+ if (initvol(hdl, &hdl->output,
+ output_names[i].cn, output_names[i].dn)) {
+ hdl->output.name = "output";
+ hdl->output.base_addr = 0;
+ break;
+ }
+ }
+ for (i = 0; i < sizeof(input_names) / sizeof(input_names[0]); i++) {
+ if (initvol(hdl, &hdl->input,
+ input_names[i].cn, input_names[i].dn)) {
+ hdl->input.name = "input";
+ hdl->input.base_addr = 64;
+ break;
+ }
+ }
+}
+
+static int
+setvol(struct sioctl_sun_hdl *hdl, struct volume *vol, int addr, int val)
+{
+ struct mixer_ctrl ctrl;
+ int i;
+
+ addr -= vol->base_addr;
+ if (vol->level_idx >= 0 && addr >= 0 && addr < vol->nch) {
+ if (vol->level_val[addr] == val) {
+ DPRINTF("level %d, no change\n", val);
+ return 1;
+ }
+ vol->level_val[addr] = val;
+ ctrl.dev = vol->level_idx;
+ ctrl.type = AUDIO_MIXER_VALUE;
+ ctrl.un.value.num_channels = vol->nch;
+ for (i = 0; i < vol->nch; i++) {
+ ctrl.un.value.level[i] =
+    SIOCTL_TO_SUN(vol->level_val[i]);
+ }
+ DPRINTF("vol %d setting to %d\n", addr, vol->level_val[addr]);
+ if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) {
+ DPRINTF("level write failed\n");
+ return 0;
+ }
+ _sioctl_onval_cb(&hdl->sioctl, vol->base_addr + addr, val);
+ return 1;
+ }
+
+ addr -= 32;
+ if (vol->mute_idx >= 0 && addr >= 0 && addr < vol->nch) {
+ val = val ? 1 : 0;
+ if (vol->mute_val == val) {
+ DPRINTF("mute %d, no change\n", val);
+ return 1;
+ }
+ vol->mute_val = val;
+ ctrl.dev = vol->mute_idx;
+ ctrl.type = AUDIO_MIXER_ENUM;
+ ctrl.un.ord = val;
+ DPRINTF("mute setting to %d\n", val);
+ if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) {
+ DPERROR("mute write\n");
+ return 0;
+ }
+ for (i = 0; i < vol->nch; i++) {
+ _sioctl_onval_cb(&hdl->sioctl,
+    vol->base_addr + 32 + i, val);
+ }
+ return 1;
+ }
+ return 1;
+}
+
+static int
+scanvol(struct sioctl_sun_hdl *hdl, struct volume *vol)
+{
+ struct sioctl_desc desc;
+ struct mixer_ctrl ctrl;
+ int i, val;
+
+ memset(&desc, 0, sizeof(struct sioctl_desc));
+ if (vol->level_idx >= 0) {
+ ctrl.dev = vol->level_idx;
+ ctrl.type = AUDIO_MIXER_VALUE;
+ ctrl.un.value.num_channels = vol->nch;
+ if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) {
+ DPRINTF("level read failed\n");
+ return 0;
+ }
+ desc.type = SIOCTL_NUM;
+ desc.node1.name[0] = 0;
+ desc.node1.unit = -1;
+ strlcpy(desc.func, "level", SIOCTL_NAMEMAX);
+ strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX);
+ for (i = 0; i < vol->nch; i++) {
+ desc.node0.unit = i;
+ desc.addr = vol->base_addr + i;
+ val = SUN_TO_SIOCTL(ctrl.un.value.level[i]);
+ vol->level_val[i] = val;
+ _sioctl_ondesc_cb(&hdl->sioctl, &desc, val);
+ }
+ }
+ if (vol->mute_idx >= 0) {
+ ctrl.dev = vol->mute_idx;
+ ctrl.type = AUDIO_MIXER_ENUM;
+ if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) {
+ DPRINTF("mute read failed\n");
+ return 0;
+ }
+ desc.type = SIOCTL_SW;
+ desc.node1.name[0] = 0;
+ desc.node1.unit = -1;
+ strlcpy(desc.func, "mute", SIOCTL_NAMEMAX);
+ strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX);
+ val = ctrl.un.ord ? 1 : 0;
+ vol->mute_val = val;
+ for (i = 0; i < vol->nch; i++) {
+ desc.node0.unit = i;
+ desc.addr = vol->base_addr + 32 + i;
+ _sioctl_ondesc_cb(&hdl->sioctl, &desc, val);
+ }
+ }
+ return 1;
+}
+
+static int
+updatevol(struct sioctl_sun_hdl *hdl, struct volume *vol, int idx)
+{
+ struct mixer_ctrl ctrl;
+ int val, i;
+
+ if (idx == vol->mute_idx)
+ ctrl.type = AUDIO_MIXER_ENUM;
+ else {
+ ctrl.type = AUDIO_MIXER_VALUE;
+ ctrl.un.value.num_channels = vol->nch;
+ }
+ ctrl.dev = idx;
+ if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) == -1) {
+ DPERROR("sioctl_sun_revents: ioctl\n");
+ hdl->sioctl.eof = 1;
+ return 0;
+ }
+ if (idx == vol->mute_idx) {
+ val = ctrl.un.ord ? 1 : 0;
+ if (vol->mute_val == val)
+ return 1;
+ vol->mute_val = val;
+ for (i = 0; i < vol->nch; i++) {
+ _sioctl_onval_cb(&hdl->sioctl,
+    vol->base_addr + 32 + i, val);
+ }
+ } else {
+ for (i = 0; i < vol->nch; i++) {
+ val = SUN_TO_SIOCTL(ctrl.un.value.level[i]);
+ if (vol->level_val[i] == val)
+ continue;
+ vol->level_val[i] = val;
+ _sioctl_onval_cb(&hdl->sioctl,
+    vol->base_addr + i, val);
+ }
+ }
+ return 1;
+}
+
+int
+sioctl_sun_getfd(const char *str, unsigned int mode, int nbio)
+{
+ const char *p;
+ char path[DEVPATH_MAX];
+ unsigned int devnum;
+ int fd, flags;
+
+#ifdef DEBUG
+ _sndio_debug_init();
+#endif
+ p = _sndio_parsetype(str, "rsnd");
+ if (p == NULL) {
+ DPRINTF("sioctl_sun_getfd: %s: \"rsnd\" expected\n", str);
+ return -1;
+ }
+ switch (*p) {
+ case '/':
+ p++;
+ break;
+ default:
+ DPRINTF("sioctl_sun_getfd: %s: '/' expected\n", str);
+ return -1;
+ }
+ if (strcmp(p, "default") == 0) {
+ devnum = 0;
+ } else {
+ p = _sndio_parsenum(p, &devnum, 255);
+ if (p == NULL || *p != '\0') {
+ DPRINTF("sioctl_sun_getfd: %s: number expected after '/'\n", str);
+ return -1;
+ }
+ }
+ snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum);
+ if (mode == (SIOCTL_READ | SIOCTL_WRITE))
+ flags = O_RDWR;
+ else
+ flags = (mode & SIOCTL_WRITE) ? O_WRONLY : O_RDONLY;
+ while ((fd = open(path, flags | O_NONBLOCK | O_CLOEXEC)) < 0) {
+ if (errno == EINTR)
+ continue;
+ DPERROR(path);
+ return -1;
+ }
+ return fd;
+}
+
+struct sioctl_hdl *
+sioctl_sun_fdopen(int fd, unsigned int mode, int nbio)
+{
+ struct sioctl_sun_hdl *hdl;
+
+#ifdef DEBUG
+ _sndio_debug_init();
+#endif
+ hdl = malloc(sizeof(struct sioctl_sun_hdl));
+ if (hdl == NULL)
+ return NULL;
+ _sioctl_create(&hdl->sioctl, &sioctl_sun_ops, mode, nbio);
+ hdl->fd = fd;
+ init(hdl);
+ return (struct sioctl_hdl *)hdl;
+}
+
+struct sioctl_hdl *
+_sioctl_sun_open(const char *str, unsigned int mode, int nbio)
+{
+ struct sioctl_hdl *hdl;
+ int fd;
+
+ fd = sioctl_sun_getfd(str, mode, nbio);
+ if (fd < 0)
+ return NULL;
+ hdl = sioctl_sun_fdopen(fd, mode, nbio);
+ if (hdl != NULL)
+ return hdl;
+ while (close(fd) < 0 && errno == EINTR)
+ ; /* retry */
+ return NULL;
+}
+
+static void
+sioctl_sun_close(struct sioctl_hdl *addr)
+{
+ struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
+
+ close(hdl->fd);
+ free(hdl);
+}
+
+static int
+sioctl_sun_ondesc(struct sioctl_hdl *addr)
+{
+ struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
+
+ if (!scanvol(hdl, &hdl->output) ||
+    !scanvol(hdl, &hdl->input)) {
+ hdl->sioctl.eof = 1;
+ return 0;
+ }
+ _sioctl_ondesc_cb(&hdl->sioctl, NULL, 0);
+ return 1;
+}
+
+static int
+sioctl_sun_onval(struct sioctl_hdl *addr)
+{
+ return 1;
+}
+
+static int
+sioctl_sun_setctl(struct sioctl_hdl *arg, unsigned int addr, unsigned int val)
+{
+ struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
+
+ if (!setvol(hdl, &hdl->output, addr, val) ||
+    !setvol(hdl, &hdl->input, addr, val)) {
+ hdl->sioctl.eof = 1;
+ return 0;
+ }
+ return 1;
+}
+
+static int
+sioctl_sun_nfds(struct sioctl_hdl *addr)
+{
+ return 1;
+}
+
+static int
+sioctl_sun_pollfd(struct sioctl_hdl *addr, struct pollfd *pfd, int events)
+{
+ struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
+
+ pfd->fd = hdl->fd;
+ pfd->events = POLLIN;
+ hdl->events = events;
+ return 1;
+}
+
+static int
+sioctl_sun_revents(struct sioctl_hdl *arg, struct pollfd *pfd)
+{
+ struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
+ struct volume *vol;
+ int idx, n;
+
+ if (pfd->revents & POLLIN) {
+ while (1) {
+ n = read(hdl->fd, &idx, sizeof(int));
+ if (n == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ break;
+ DPERROR("read");
+ hdl->sioctl.eof = 1;
+ return POLLHUP;
+ }
+ if (n < sizeof(int)) {
+ DPRINTF("sioctl_sun_revents: short read\n");
+ hdl->sioctl.eof = 1;
+ return POLLHUP;
+ }
+
+ if (idx == hdl->output.level_idx ||
+    idx == hdl->output.mute_idx) {
+ vol = &hdl->output;
+ } else if (idx == hdl->input.level_idx ||
+    idx == hdl->input.mute_idx) {
+ vol = &hdl->input;
+ } else
+ continue;
+
+ if (!updatevol(hdl, vol, idx))
+ return POLLHUP;
+ }
+ }
+ return hdl->events & POLLOUT;
+}
Index: usr.bin/sndiod/Makefile
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/Makefile,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 Makefile
--- usr.bin/sndiod/Makefile 7 Jan 2016 07:41:01 -0000 1.5
+++ usr.bin/sndiod/Makefile 8 Feb 2020 14:49:38 -0000
@@ -1,8 +1,8 @@
 # $OpenBSD: Makefile,v 1.5 2016/01/07 07:41:01 ratchov Exp $
 
 PROG= sndiod
-SRCS= abuf.c dev.c dsp.c fdpass.c file.c listen.c midi.c miofile.c \
- opt.c siofile.c sndiod.c sock.c utils.c
+SRCS= abuf.c dev.c dev_sioctl.c dsp.c fdpass.c file.c listen.c \
+ midi.c miofile.c opt.c siofile.c sndiod.c sock.c utils.c
 MAN= sndiod.8
 CFLAGS+=-DDEBUG -I${.CURDIR}/../../lib/libsndio
 COPTS+= -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wundef
Index: usr.bin/sndiod/defs.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/defs.h,v
retrieving revision 1.4
diff -u -p -u -p -r1.4 defs.h
--- usr.bin/sndiod/defs.h 28 Jul 2019 09:44:10 -0000 1.4
+++ usr.bin/sndiod/defs.h 8 Feb 2020 14:49:38 -0000
@@ -37,9 +37,12 @@
 #define MODE_MIDIOUT 0x04 /* allowed to read midi */
 #define MODE_MIDIIN 0x08 /* allowed to write midi */
 #define MODE_MON 0x10 /* allowed to monitor */
+#define MODE_CTLREAD 0x100 /* allowed to read controls */
+#define MODE_CTLWRITE 0x200 /* allowed to change controls */
 #define MODE_RECMASK (MODE_REC | MODE_MON)
 #define MODE_AUDIOMASK (MODE_PLAY | MODE_REC | MODE_MON)
 #define MODE_MIDIMASK (MODE_MIDIIN | MODE_MIDIOUT)
+#define MODE_CTLMASK (MODE_CTLREAD | MODE_CTLWRITE)
 
 /*
  * underrun/overrun policies, must be the same as SIO_ constants
Index: usr.bin/sndiod/dev.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.c,v
retrieving revision 1.63
diff -u -p -u -p -r1.63 dev.c
--- usr.bin/sndiod/dev.c 10 Jan 2020 19:01:55 -0000 1.63
+++ usr.bin/sndiod/dev.c 8 Feb 2020 14:49:38 -0000
@@ -75,6 +75,7 @@ void dev_mmcstart(struct dev *);
 void dev_mmcstop(struct dev *);
 void dev_mmcloc(struct dev *, unsigned int);
 
+void slot_ctlname(struct slot *, char *, size_t);
 void slot_log(struct slot *);
 void slot_del(struct slot *);
 void slot_setvol(struct slot *, unsigned int);
@@ -91,6 +92,9 @@ void slot_write(struct slot *);
 void slot_read(struct slot *);
 int slot_skip(struct slot *);
 
+void ctl_node_log(struct ctl_node *);
+void ctl_log(struct ctl *);
+
 struct midiops dev_midiops = {
  dev_midi_imsg,
  dev_midi_omsg,
@@ -129,15 +133,22 @@ dev_log(struct dev *d)
 }
 
 void
+slot_ctlname(struct slot *s, char *name, size_t size)
+{
+ snprintf(name, size, "%s%u", s->name, s->unit);
+}
+
+void
 slot_log(struct slot *s)
 {
+ char name[CTL_NAMEMAX];
 #ifdef DEBUG
  static char *pstates[] = {
  "ini", "sta", "rdy", "run", "stp", "mid"
  };
 #endif
- log_puts(s->name);
- log_putu(s->unit);
+ slot_ctlname(s, name, CTL_NAMEMAX);
+ log_puts(name);
 #ifdef DEBUG
  if (log_level >= 3) {
  log_puts(" vol=");
@@ -365,10 +376,8 @@ dev_midi_slotdesc(struct dev *d, struct
  x.dev = SYSEX_DEV_ANY;
  x.id0 = SYSEX_AUCAT;
  x.id1 = SYSEX_AUCAT_SLOTDESC;
- if (*s->name != '\0') {
- snprintf((char *)x.u.slotdesc.name, SYSEX_NAMELEN,
-    "%s%u", s->name, s->unit);
- }
+ if (*s->name != '\0')
+ slot_ctlname(s, (char *)x.u.slotdesc.name, SYSEX_NAMELEN);
  x.u.slotdesc.chan = s - d->slot;
  x.u.slotdesc.end = SYSEX_END;
  midi_send(d->midi, (unsigned char *)&x, SYSEX_SIZE(slotdesc));
@@ -419,6 +428,7 @@ dev_midi_omsg(void *arg, unsigned char *
  if (chan >= DEV_NSLOT)
  return;
  slot_setvol(d->slot + chan, msg[2]);
+ dev_onval(d, CTLADDR_SLOT_LEVEL(chan), msg[2]);
  return;
  }
  x = (struct sysex *)msg;
@@ -429,8 +439,11 @@ dev_midi_omsg(void *arg, unsigned char *
  switch (x->type) {
  case SYSEX_TYPE_RT:
  if (x->id0 == SYSEX_CONTROL && x->id1 == SYSEX_MASTER) {
- if (len == SYSEX_SIZE(master))
+ if (len == SYSEX_SIZE(master)) {
  dev_master(d, x->u.master.coarse);
+ dev_onval(d, CTLADDR_MASTER,
+    x->u.master.coarse);
+ }
  return;
  }
  if (x->id0 != SYSEX_MMC)
@@ -1001,10 +1014,17 @@ dev_new(char *path, struct aparams *par,
  d->slot[i].serial = d->serial++;
  strlcpy(d->slot[i].name, "prog", SLOT_NAMEMAX);
  }
+ for (i = 0; i < DEV_NCTLSLOT; i++) {
+ d->ctlslot[i].ops = NULL;
+ d->ctlslot[i].dev = d;
+ d->ctlslot[i].mask = 0;
+ d->ctlslot[i].mode = 0;
+ }
  d->slot_list = NULL;
  d->master = MIDI_MAXCTL;
  d->mtc.origin = 0;
  d->tstate = MMC_STOP;
+ d->ctl_list = NULL;
  d->next = dev_list;
  dev_list = d;
  return d;
@@ -1097,6 +1117,9 @@ dev_allocbufs(struct dev *d)
 int
 dev_open(struct dev *d)
 {
+ int i;
+ char name[CTL_NAMEMAX];
+
  d->mode = d->reqmode;
  d->round = d->reqround;
  d->bufsz = d->reqbufsz;
@@ -1117,6 +1140,17 @@ dev_open(struct dev *d)
  }
  if (!dev_allocbufs(d))
  return 0;
+
+ for (i = 0; i < DEV_NSLOT; i++) {
+ slot_ctlname(&d->slot[i], name, CTL_NAMEMAX);
+ dev_addctl(d, "app", CTL_NUM,
+    CTLADDR_SLOT_LEVEL(i),
+    name, -1, "level",
+    NULL, -1, d->slot[i].vol);
+ }
+ dev_addctl(d, "", CTL_NUM,
+    CTLADDR_MASTER, "output", -1, "level", NULL, -1, d->master);
+
  d->pstate = DEV_INIT;
  return 1;
 }
@@ -1129,6 +1163,7 @@ dev_exitall(struct dev *d)
 {
  int i;
  struct slot *s;
+ struct ctlslot *c;
 
  for (s = d->slot, i = DEV_NSLOT; i > 0; i--, s++) {
  if (s->ops)
@@ -1136,6 +1171,12 @@ dev_exitall(struct dev *d)
  s->ops = NULL;
  }
  d->slot_list = NULL;
+
+ for (c = d->ctlslot, i = DEV_NCTLSLOT; i > 0; i--, c++) {
+ if (c->ops)
+ c->ops->exit(c->arg);
+ c->ops = NULL;
+ }
 }
 
 /*
@@ -1169,10 +1210,18 @@ dev_freebufs(struct dev *d)
 void
 dev_close(struct dev *d)
 {
+ struct ctl *c;
+
  dev_exitall(d);
  d->pstate = DEV_CFG;
  dev_sio_close(d);
  dev_freebufs(d);
+
+ /* there are no clients, just free remaining local controls */
+ while ((c = d->ctl_list) != NULL) {
+ d->ctl_list = c->next;
+ xfree(c);
+ }
 }
 
 /*
@@ -1183,6 +1232,7 @@ int
 dev_reopen(struct dev *d)
 {
  struct slot *s;
+ struct ctl *c, **pc;
  long long pos;
  unsigned int pstate;
  int delta;
@@ -1236,6 +1286,25 @@ dev_reopen(struct dev *d)
  }
  }
 
+ /* remove controls of old device */
+ pc = &d->ctl_list;
+ while ((c = *pc) != NULL) {
+ if (c->addr >= CTLADDR_END) {
+ c->refs_mask &= ~CTL_DEVMASK;
+ if (c->refs_mask == 0) {
+ *pc = c->next;
+ xfree(c);
+ continue;
+ }
+ c->type = CTL_NONE;
+ c->desc_mask = ~0;
+ }
+ pc = &c->next;
+ }
+
+ /* add new device controls */
+ dev_sioctl_open(d);
+
  /* start the device if needed */
  if (pstate == DEV_RUN)
  dev_wakeup(d);
@@ -1760,6 +1829,7 @@ found:
  }
  if (!dev_ref(d))
  return NULL;
+ dev_label(d, s - d->slot);
  if ((mode & d->mode) != mode) {
  if (log_level >= 1) {
  slot_log(s);
@@ -2096,4 +2166,278 @@ void
 slot_read(struct slot *s)
 {
  slot_skip_update(s);
+}
+
+/*
+ * allocate at control slot
+ */
+struct ctlslot *
+ctlslot_new(struct dev *d, struct ctlops *ops, void *arg)
+{
+ struct ctlslot *s;
+ struct ctl *c;
+ int i;
+
+ i = 0;
+ for (;;) {
+ if (i == DEV_NCTLSLOT)
+ return NULL;
+ s = d->ctlslot + i;
+ if (s->ops == NULL)
+ break;
+ i++;
+ }
+ s->dev = d;
+ s->mask = 1 << i;
+ if (!dev_ref(d))
+ return NULL;
+ s->ops = ops;
+ s->arg = arg;
+ for (c = d->ctl_list; c != NULL; c = c->next)
+ c->refs_mask |= s->mask;
+ return s;
+}
+
+/*
+ * free control slot
+ */
+void
+ctlslot_del(struct ctlslot *s)
+{
+ struct ctl *c, **pc;
+
+ pc = &s->dev->ctl_list;
+ while ((c = *pc) != NULL) {
+ c->refs_mask &= ~s->mask;
+ if (c->refs_mask == 0) {
+ *pc = c->next;
+ xfree(c);
+ } else
+ pc = &c->next;
+ }
+ s->ops = NULL;
+ dev_unref(s->dev);
+}
+
+void
+ctl_node_log(struct ctl_node *c)
+{
+ log_puts(c->name);
+ if (c->unit >= 0)
+ log_putu(c->unit);
+}
+
+void
+ctl_log(struct ctl *c)
+{
+ if (c->group[0] != 0) {
+ log_puts(c->group);
+ log_puts("/");
+ }
+ ctl_node_log(&c->node0);
+ log_puts(".");
+ log_puts(c->func);
+ log_puts("=");
+ switch (c->type) {
+ case CTL_NUM:
+ case CTL_SW:
+ log_putu(c->curval);
+ break;
+ case CTL_VEC:
+ case CTL_LIST:
+ ctl_node_log(&c->node1);
+ log_puts(":");
+ log_putu(c->curval);
+ }
+ log_puts(" at ");
+ log_putu(c->addr);
+}
+
+/*
+ * add a ctl
+ */
+struct ctl *
+dev_addctl(struct dev *d, char *gstr, int type, int addr,
+    char *str0, int unit0, char *func, char *str1, int unit1, int val)
+{
+ struct ctl *c, **pc;
+ int i;
+
+ c = xmalloc(sizeof(struct ctl));
+ c->type = type;
+ strlcpy(c->func, func, CTL_NAMEMAX);
+ strlcpy(c->group, gstr, CTL_NAMEMAX);
+ strlcpy(c->node0.name, str0, CTL_NAMEMAX);
+ c->node0.unit = unit0;
+ if (c->type == CTL_VEC || c->type == CTL_LIST) {
+ strlcpy(c->node1.name, str1, CTL_NAMEMAX);
+ c->node1.unit = unit1;
+ } else
+ memset(&c->node1, 0, sizeof(struct ctl_node));
+ c->addr = addr;
+ c->val_mask = ~0;
+ c->desc_mask = ~0;
+ c->curval = val;
+ c->dirty = 0;
+ c->refs_mask = 0;
+ for (i = 0; i < DEV_NCTLSLOT; i++) {
+ c->refs_mask |= CTL_DEVMASK;
+ if (d->ctlslot[i].ops != NULL)
+ c->refs_mask |= 1 << i;
+ }
+ for (pc = &d->ctl_list; *pc != NULL; pc = &(*pc)->next)
+ ; /* nothing */
+ c->next = NULL;
+ *pc = c;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ dev_log(d);
+ log_puts(": adding ");
+ ctl_log(c);
+ log_puts("\n");
+ }
+#endif
+ return c;
+}
+
+void
+dev_rmctl(struct dev *d, int addr)
+{
+ struct ctl *c, **pc;
+
+ pc = &d->ctl_list;
+ for (;;) {
+ c = *pc;
+ if (c == NULL)
+ return;
+ if (c->type != CTL_NONE && c->addr == addr)
+ break;
+ pc = &c->next;
+ }
+ c->type = CTL_NONE;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ dev_log(d);
+ log_puts(": removing ");
+ ctl_log(c);
+ log_puts(", refs_mask = 0x");
+ log_putx(c->refs_mask);
+ log_puts("\n");
+ }
+#endif
+ c->refs_mask &= ~CTL_DEVMASK;
+ if (c->refs_mask != 0)
+ return;
+ *pc = c->next;
+ xfree(c);
+}
+
+int
+dev_setctl(struct dev *d, int addr, int val)
+{
+ struct ctl *c;
+ int num;
+
+ if (val < 0 || val > MIDI_MAXCTL) {
+ if (log_level >= 3) {
+ dev_log(d);
+ log_puts(": ");
+ log_putu(val);
+ log_puts(": ctl val out of bounds\n");
+ }
+ return 0;
+ }
+ c = d->ctl_list;
+ for (;;) {
+ if (c == NULL) {
+ if (log_level >= 3) {
+ dev_log(d);
+ log_puts(": ");
+ log_putu(addr);
+ log_puts(": no such ctl address\n");
+ }
+ return 0;
+ }
+ if (c->type != CTL_NONE && c->addr == addr)
+ break;
+ c = c->next;
+ }
+ if (c->curval == val) {
+ if (log_level >= 3) {
+ ctl_log(c);
+ log_puts(": already set\n");
+ }
+ return 1;
+ }
+ if (addr >= CTLADDR_END) {
+ if (log_level >= 3) {
+ ctl_log(c);
+ log_puts(": marked as dirty\n");
+ }
+ c->dirty = 1;
+ dev_ref(d);
+ } else {
+ if (addr == CTLADDR_MASTER) {
+ dev_master(d, val);
+ dev_midi_master(d);
+ } else {
+ num = addr - CTLADDR_SLOT_LEVEL(0);
+ slot_setvol(d->slot + num, val);
+ dev_midi_vol(d, d->slot + num);
+ }
+ }
+ c->curval = val;
+ c->val_mask = ~0U;
+ return 1;
+}
+
+int
+dev_onval(struct dev *d, int addr, int val)
+{
+ struct ctl *c;
+
+ c = d->ctl_list;
+ for (;;) {
+ if (c == NULL)
+ return 0;
+ if (c->type != CTL_NONE && c->addr == addr)
+ break;
+ c = c->next;
+ }
+ c->curval = val;
+ c->val_mask = ~0U;
+ return 1;
+}
+
+void
+dev_label(struct dev *d, int i)
+{
+ struct ctl *c;
+ char name[CTL_NAMEMAX];
+
+ c = d->ctl_list;
+ for (;;) {
+ if (c == NULL)
+ return;
+ if (c->addr == CTLADDR_SLOT_LEVEL(i))
+ break;
+ c = c->next;
+ }
+ slot_ctlname(&d->slot[i], name, CTL_NAMEMAX);
+ if (strcmp(c->node0.name, name) == 0)
+ return;
+ strlcpy(c->node0.name, name, CTL_NAMEMAX);
+ c->desc_mask = ~0;
+}
+
+int
+dev_nctl(struct dev *d)
+{
+ struct ctl *c;
+ int n;
+
+ n = 0;
+ for (c = d->ctl_list; c != NULL; c = c->next)
+ n++;
+ return n;
 }
Index: usr.bin/sndiod/dev.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.h,v
retrieving revision 1.22
diff -u -p -u -p -r1.22 dev.h
--- usr.bin/sndiod/dev.h 21 Sep 2019 04:42:46 -0000 1.22
+++ usr.bin/sndiod/dev.h 8 Feb 2020 14:49:38 -0000
@@ -20,6 +20,11 @@
 #include "abuf.h"
 #include "dsp.h"
 #include "siofile.h"
+#include "dev_sioctl.h"
+
+#define CTLADDR_SLOT_LEVEL(n) (n)
+#define CTLADDR_MASTER (DEV_NSLOT)
+#define CTLADDR_END (DEV_NSLOT + 1)
 
 /*
  * audio stream state structure
@@ -28,13 +33,18 @@
 struct slotops
 {
  void (*onmove)(void *); /* clock tick */
- void (*onvol)(void *);        /* tell client vol changed */
+ void (*onvol)(void *); /* tell client vol changed */
  void (*fill)(void *); /* request to fill a play block */
  void (*flush)(void *); /* request to flush a rec block */
  void (*eof)(void *); /* notify that play drained */
  void (*exit)(void *); /* delete client */
 };
 
+struct ctlops
+{
+ void (*exit)(void *); /* delete client */
+};
+
 struct slot {
  struct slotops *ops; /* client callbacks */
  struct slot *next; /* next on the play list */
@@ -105,6 +115,43 @@ struct opt {
 };
 
 /*
+ * subset of channels of a stream
+ */
+
+struct ctl {
+ struct ctl *next;
+#define CTL_NONE 0 /* deleted */
+#define CTL_NUM 2 /* number (aka integer value) */
+#define CTL_SW 3 /* on/off switch, only bit 7 counts */
+#define CTL_VEC 4 /* number, element of vector */
+#define CTL_LIST 5 /* switch, element of a list */
+ unsigned int type; /* one of above */
+ unsigned int addr; /* control address */
+#define CTL_NAMEMAX 16 /* max name lenght */
+ char func[CTL_NAMEMAX]; /* parameter function name */
+ char group[CTL_NAMEMAX]; /* group aka namespace */
+ struct ctl_node {
+ char name[CTL_NAMEMAX]; /* stream name */
+ int unit;
+ } node0, node1; /* affected channels */
+#define CTL_DEVMASK (1 << 31)
+#define CTL_SLOTMASK(i) (1 << (i))
+ unsigned int val_mask;
+ unsigned int desc_mask;
+ unsigned int refs_mask;
+ unsigned int curval;
+ int dirty;
+};
+
+struct ctlslot {
+ struct ctlops *ops;
+ void *arg;
+ struct dev *dev;
+ unsigned int mask;
+ unsigned int mode;
+};
+
+/*
  * audio device with plenty of slots
  */
 struct dev {
@@ -117,6 +164,7 @@ struct dev {
  * audio device (while opened)
  */
  struct dev_sio sio;
+ struct dev_sioctl sioctl;
  struct aparams par; /* encoding */
  int pchan, rchan; /* play & rec channels */
  adata_t *rbuf; /* rec buffer */
@@ -195,6 +243,14 @@ struct dev {
 #define MMC_RUN 3 /* started */
  unsigned int tstate; /* one of above */
  unsigned int master; /* master volume controller */
+
+ /*
+ * control
+ */
+
+ struct ctl *ctl_list;
+#define DEV_NCTLSLOT 8
+ struct ctlslot ctlslot[DEV_NCTLSLOT];
 };
 
 extern struct dev *dev_list;
@@ -241,5 +297,20 @@ void slot_start(struct slot *);
 void slot_stop(struct slot *);
 void slot_read(struct slot *);
 void slot_write(struct slot *);
+
+/*
+ * control related functions
+ */
+void ctl_log(struct ctl *);
+struct ctlslot *ctlslot_new(struct dev *, struct ctlops *, void *);
+void ctlslot_del(struct ctlslot *);
+int dev_setctl(struct dev *, int, int);
+int dev_onval(struct dev *, int, int);
+int dev_nctl(struct dev *);
+void dev_label(struct dev *, int);
+struct ctl *dev_addctl(struct dev *, char *, int, int,
+    char *, int, char *, char *, int, int);
+void dev_rmctl(struct dev *, int);
+int dev_makeunit(struct dev *, char *);
 
 #endif /* !defined(DEV_H) */
Index: usr.bin/sndiod/dev_sioctl.c
===================================================================
RCS file: usr.bin/sndiod/dev_sioctl.c
diff -N usr.bin/sndiod/dev_sioctl.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.bin/sndiod/dev_sioctl.c 8 Feb 2020 14:49:38 -0000
@@ -0,0 +1,204 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2014 Alexandre Ratchov <[hidden email]>
+ *
+ * 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 <sys/time.h>
+#include <sys/types.h>
+
+#include <poll.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "abuf.h"
+#include "defs.h"
+#include "dev.h"
+#include "dsp.h"
+#include "file.h"
+#include "dev_sioctl.h"
+#include "utils.h"
+
+void dev_sioctl_ondesc(void *, struct sioctl_desc *, int);
+void dev_sioctl_onval(void *, unsigned int, unsigned int);
+int dev_sioctl_pollfd(void *, struct pollfd *);
+int dev_sioctl_revents(void *, struct pollfd *);
+void dev_sioctl_in(void *);
+void dev_sioctl_out(void *);
+void dev_sioctl_hup(void *);
+
+struct fileops dev_sioctl_ops = {
+ "sioctl",
+ dev_sioctl_pollfd,
+ dev_sioctl_revents,
+ dev_sioctl_in,
+ dev_sioctl_out,
+ dev_sioctl_hup
+};
+
+void
+dev_sioctl_ondesc(void *arg, struct sioctl_desc *desc, int val)
+{
+#define GROUP_PREFIX "hw"
+ char group_buf[CTL_NAMEMAX], *group;
+ struct dev *d = arg;
+ size_t len;
+ int addr;
+
+ if (desc == NULL)
+ return;
+ addr = CTLADDR_END + desc->addr;
+ dev_rmctl(d, addr);
+
+ /*
+ * prefix group names we use (top-level and "app") with "hw."
+ * to ensure that all controls have unique names when multiple
+ * sndiod's are chained
+ */
+ if (desc->group[0] == 0)
+ group = GROUP_PREFIX;
+ else if (strcmp(desc->group, GROUP_PREFIX) == 0 ||
+    strcmp(desc->group, "app") == 0) {
+ group = group_buf;
+ len = snprintf(group_buf, CTL_NAMEMAX,
+    GROUP_PREFIX ".%s", desc->group);
+ if (len >= CTL_NAMEMAX)
+ return;
+ } else
+ group = desc->group;
+
+ dev_addctl(d, group, desc->type, addr,
+    desc->node0.name, desc->node0.unit, desc->func,
+    desc->node1.name, desc->node1.unit, val);
+}
+
+void
+dev_sioctl_onval(void *arg, unsigned int addr, unsigned int val)
+{
+ struct dev *d = arg;
+ struct ctl *c;
+
+ addr += CTLADDR_END;
+
+ dev_log(d);
+ log_puts(": onctl: addr = ");
+ log_putu(addr);
+ log_puts(", val = ");
+ log_putu(val);
+ log_puts("\n");
+
+ for (c = d->ctl_list; c != NULL; c = c->next) {
+ if (c->addr != addr)
+ continue;
+ ctl_log(c);
+ log_puts(": new value -> ");
+ log_putu(val);
+ log_puts("\n");
+ c->val_mask = ~0U;
+ c->curval = val;
+ }
+}
+
+/*
+ * open the control device.
+ */
+void
+dev_sioctl_open(struct dev *d)
+{
+ if (d->sioctl.hdl == NULL)
+ return;
+ sioctl_ondesc(d->sioctl.hdl, dev_sioctl_ondesc, d);
+ sioctl_onval(d->sioctl.hdl, dev_sioctl_onval, d);
+ d->sioctl.file = file_new(&dev_sioctl_ops, d, "mix",
+    sioctl_nfds(d->sioctl.hdl));
+}
+
+/*
+ * close the control device.
+ */
+void
+dev_sioctl_close(struct dev *d)
+{
+ if (d->sioctl.hdl == NULL)
+ return;
+ file_del(d->sioctl.file);
+}
+
+int
+dev_sioctl_pollfd(void *arg, struct pollfd *pfd)
+{
+ struct dev *d = arg;
+ struct ctl *c;
+ int events = 0;
+
+ for (c = d->ctl_list; c != NULL; c = c->next) {
+ if (c->dirty)
+ events |= POLLOUT;
+ }
+ return sioctl_pollfd(d->sioctl.hdl, pfd, events);
+}
+
+int
+dev_sioctl_revents(void *arg, struct pollfd *pfd)
+{
+ struct dev *d = arg;
+
+ return sioctl_revents(d->sioctl.hdl, pfd);
+}
+
+void
+dev_sioctl_in(void *arg)
+{
+}
+
+void
+dev_sioctl_out(void *arg)
+{
+ struct dev *d = arg;
+ struct ctl *c;
+ int cnt;
+
+ /*
+ * for each dirty ctl, call sioctl_setval() and dev_unref(). As
+ * dev_unref() may destroy the ctl_list, we must call it after
+ * we've finished iterating on it.
+ */
+ cnt = 0;
+ for (c = d->ctl_list; c != NULL; c = c->next) {
+ if (!c->dirty)
+ continue;
+ if (!sioctl_setval(d->sioctl.hdl,
+ c->addr - CTLADDR_END, c->curval)) {
+ ctl_log(c);
+ log_puts(": set failed\n");
+ break;
+ }
+ if (log_level >= 2) {
+ ctl_log(c);
+ log_puts(": changed\n");
+ }
+ c->dirty = 0;
+ cnt++;
+ }
+ while (cnt-- > 0)
+ dev_unref(d);
+}
+
+void
+dev_sioctl_hup(void *arg)
+{
+ struct dev *d = arg;
+
+ dev_sioctl_close(d);
+}
Index: usr.bin/sndiod/dev_sioctl.h
===================================================================
RCS file: usr.bin/sndiod/dev_sioctl.h
diff -N usr.bin/sndiod/dev_sioctl.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.bin/sndiod/dev_sioctl.h 8 Feb 2020 14:49:38 -0000
@@ -0,0 +1,32 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2014 Alexandre Ratchov <[hidden email]>
+ *
+ * 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.
+ */
+#ifndef DEV_SIOCTL_H
+#define DEV_SIOCTL_H
+
+#include "file.h"
+
+struct dev;
+
+struct dev_sioctl {
+ struct sioctl_hdl *hdl;
+ struct file *file;
+};
+
+void dev_sioctl_open(struct dev *);
+void dev_sioctl_close(struct dev *);
+
+#endif /* !defined(DEV_SIOCTL_H) */
Index: usr.bin/sndiod/fdpass.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/fdpass.c,v
retrieving revision 1.8
diff -u -p -u -p -r1.8 fdpass.c
--- usr.bin/sndiod/fdpass.c 23 Jan 2020 05:40:09 -0000 1.8
+++ usr.bin/sndiod/fdpass.c 8 Feb 2020 14:49:38 -0000
@@ -32,6 +32,7 @@
 struct fdpass_msg {
 #define FDPASS_OPEN_SND 0 /* open an audio device */
 #define FDPASS_OPEN_MIDI 1 /* open a midi port */
+#define FDPASS_OPEN_CTL 2 /* open an audio control device */
 #define FDPASS_RETURN 3 /* return after above commands */
  unsigned int cmd; /* one of above */
  unsigned int num; /* audio device or midi port number */
@@ -287,6 +288,22 @@ fdpass_mio_open(int num, int idx, unsign
  return mio_rmidi_fdopen(fd, mode, 1);
 }
 
+struct sioctl_hdl *
+fdpass_sioctl_open(int num, int idx, unsigned int mode)
+{
+ int fd;
+
+ if (fdpass_peer == NULL)
+ return NULL;
+ if (!fdpass_send(fdpass_peer, FDPASS_OPEN_CTL, num, idx, mode, -1))
+ return NULL;
+ if (!fdpass_waitret(fdpass_peer, &fd))
+ return NULL;
+ if (fd < 0)
+ return NULL;
+ return sioctl_sun_fdopen(fd, mode, 1);
+}
+
 void
 fdpass_in_worker(void *arg)
 {
@@ -345,6 +362,23 @@ fdpass_in_helper(void *arg)
  return;
  }
  fd = mio_rmidi_getfd(path, mode, 1);
+ break;
+ case FDPASS_OPEN_CTL:
+ d = dev_bynum(num);
+ if (d == NULL || !(mode & (SIOCTL_READ | SIOCTL_WRITE))) {
+ if (log_level >= 1) {
+ fdpass_log(f);
+ log_puts(": bad audio control device\n");
+ }
+ fdpass_close(f);
+ return;
+ }
+ path = namelist_byindex(&d->path_list, idx);
+ if (path == NULL) {
+ fdpass_close(f);
+ return;
+ }
+ fd = sioctl_sun_getfd(path, mode, 1);
  break;
  default:
  fdpass_close(f);
Index: usr.bin/sndiod/fdpass.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/fdpass.h,v
retrieving revision 1.2
diff -u -p -u -p -r1.2 fdpass.h
--- usr.bin/sndiod/fdpass.h 23 Jan 2020 05:40:09 -0000 1.2
+++ usr.bin/sndiod/fdpass.h 8 Feb 2020 14:49:38 -0000
@@ -27,5 +27,6 @@ extern struct fdpass *fdpass_peer;
 
 struct sio_hdl *fdpass_sio_open(int, int, unsigned int);
 struct mio_hdl *fdpass_mio_open(int, int, unsigned int);
+struct sioctl_hdl *fdpass_sioctl_open(int, int, unsigned int);
 
 #endif /* !defined(FDPASS_H) */
Index: usr.bin/sndiod/siofile.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/siofile.c,v
retrieving revision 1.17
diff -u -p -u -p -r1.17 siofile.c
--- usr.bin/sndiod/siofile.c 23 Jan 2020 05:40:09 -0000 1.17
+++ usr.bin/sndiod/siofile.c 8 Feb 2020 14:49:39 -0000
@@ -26,6 +26,7 @@
 #include "abuf.h"
 #include "defs.h"
 #include "dev.h"
+#include "dev_sioctl.h"
 #include "dsp.h"
 #include "fdpass.h"
 #include "file.h"
@@ -88,10 +89,11 @@ dev_sio_timeout(void *arg)
  * open the device using one of the provided paths
  */
 static struct sio_hdl *
-dev_sio_openlist(struct dev *d, unsigned int mode)
+dev_sio_openlist(struct dev *d, unsigned int mode, struct sioctl_hdl **rctlhdl)
 {
  struct name *n;
  struct sio_hdl *hdl;
+ struct sioctl_hdl *ctlhdl;
  int idx;
 
  idx = 0;
@@ -107,6 +109,15 @@ dev_sio_openlist(struct dev *d, unsigned
  log_puts(n->str);
  log_puts("\n");
  }
+ ctlhdl = fdpass_sioctl_open(d->num, idx,
+    SIOCTL_READ | SIOCTL_WRITE);
+ if (ctlhdl == NULL) {
+ if (log_level >= 1) {
+ dev_log(d);
+ log_puts(": no control device\n");
+ }
+ }
+ *rctlhdl = ctlhdl;
  return hdl;
  }
  n = n->next;
@@ -124,15 +135,16 @@ dev_sio_open(struct dev *d)
  struct sio_par par;
  unsigned int mode = d->mode & (MODE_PLAY | MODE_REC);
 
- d->sio.hdl = dev_sio_openlist(d, mode);
+ d->sio.hdl = dev_sio_openlist(d, mode, &d->sioctl.hdl);
  if (d->sio.hdl == NULL) {
  if (mode != (SIO_PLAY | SIO_REC))
  return 0;
- d->sio.hdl = dev_sio_openlist(d, SIO_PLAY);
+ d->sio.hdl = dev_sio_openlist(d, SIO_PLAY, &d->sioctl.hdl);
  if (d->sio.hdl != NULL)
  mode = SIO_PLAY;
  else {
- d->sio.hdl = dev_sio_openlist(d, SIO_REC);
+ d->sio.hdl = dev_sio_openlist(d,
+    SIO_REC, &d->sioctl.hdl);
  if (d->sio.hdl != NULL)
  mode = SIO_REC;
  else
@@ -245,9 +257,14 @@ dev_sio_open(struct dev *d)
  sio_onmove(d->sio.hdl, dev_sio_onmove, d);
  d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(d->sio.hdl));
  timo_set(&d->sio.watchdog, dev_sio_timeout, d);
+ dev_sioctl_open(d);
  return 1;
  bad_close:
  sio_close(d->sio.hdl);
+ if (d->sioctl.hdl) {
+ sioctl_close(d->sioctl.hdl);
+ d->sioctl.hdl = NULL;
+ }
  return 0;
 }
 
@@ -259,10 +276,11 @@ dev_sio_open(struct dev *d)
 int
 dev_sio_reopen(struct dev *d)
 {
+ struct sioctl_hdl *ctlhdl;
  struct sio_par par;
  struct sio_hdl *hdl;
 
- hdl = dev_sio_openlist(d, d->mode & (MODE_PLAY | MODE_REC));
+ hdl = dev_sio_openlist(d, d->mode & (MODE_PLAY | MODE_REC), &ctlhdl);
  if (hdl == NULL) {
  if (log_level >= 1) {
  dev_log(d);
@@ -303,6 +321,11 @@ dev_sio_reopen(struct dev *d)
  timo_del(&d->sio.watchdog);
  file_del(d->sio.file);
  sio_close(d->sio.hdl);
+ dev_sioctl_close(d);
+ if (d->sioctl.hdl) {
+ sioctl_close(d->sioctl.hdl);
+ d->sioctl.hdl = NULL;
+ }
 
  /* update parameters */
  d->par.bits = par.bits;
@@ -316,17 +339,21 @@ dev_sio_reopen(struct dev *d)
  d->rchan = par.rchan;
 
  d->sio.hdl = hdl;
+ d->sioctl.hdl = ctlhdl;
  d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(hdl));
  sio_onmove(hdl, dev_sio_onmove, d);
  return 1;
 bad_close:
  sio_close(hdl);
+ if (ctlhdl)
+ sioctl_close(ctlhdl);
  return 0;
 }
 
 void
 dev_sio_close(struct dev *d)
 {
+ dev_sioctl_close(d);
 #ifdef DEBUG
  if (log_level >= 3) {
  dev_log(d);
@@ -336,6 +363,10 @@ dev_sio_close(struct dev *d)
  timo_del(&d->sio.watchdog);
  file_del(d->sio.file);
  sio_close(d->sio.hdl);
+ if (d->sioctl.hdl) {
+ sioctl_close(d->sioctl.hdl);
+ d->sioctl.hdl = NULL;
+ }
 }
 
 void
Index: usr.bin/sndiod/sndiod.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.c,v
retrieving revision 1.37
diff -u -p -u -p -r1.37 sndiod.c
--- usr.bin/sndiod/sndiod.c 21 Sep 2019 04:52:07 -0000 1.37
+++ usr.bin/sndiod/sndiod.c 8 Feb 2020 14:49:39 -0000
@@ -413,8 +413,10 @@ start_helper(int background)
  err(1, "cannot drop privileges");
  }
  for (d = dev_list; d != NULL; d = d->next) {
- for (n = d->path_list; n != NULL; n = n->next)
+ for (n = d->path_list; n != NULL; n = n->next) {
  dounveil(n->str, "rsnd/", "/dev/audio");
+ dounveil(n->str, "rsnd/", "/dev/audioctl");
+ }
  }
  for (p = port_list; p != NULL; p = p->next) {
  for (n = p->path_list; n != NULL; n = n->next)
Index: usr.bin/sndiod/sock.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sock.c,v
retrieving revision 1.31
diff -u -p -u -p -r1.31 sock.c
--- usr.bin/sndiod/sock.c 12 Jul 2019 06:30:55 -0000 1.31
+++ usr.bin/sndiod/sock.c 8 Feb 2020 14:49:39 -0000
@@ -32,6 +32,8 @@
 #include "sock.h"
 #include "utils.h"
 
+#define SOCK_CTLDESC_SIZE 16 /* number of entries in s->ctldesc */
+
 void sock_log(struct sock *);
 void sock_close(struct sock *);
 void sock_slot_fill(void *);
@@ -88,6 +90,10 @@ struct midiops sock_midiops = {
  sock_exit
 };
 
+struct ctlops sock_ctlops = {
+ sock_exit
+};
+
 struct sock *sock_list = NULL;
 unsigned int sock_sesrefs = 0; /* connections to the session */
 uint8_t sock_sescookie[AMSG_COOKIELEN]; /* owner of the session */
@@ -103,7 +109,10 @@ sock_log(struct sock *f)
  slot_log(f->slot);
  else if (f->midi)
  midi_log(f->midi);
- else
+ else if (f->ctlslot) {
+ log_puts("ctlslot");
+ log_putu(f->ctlslot - f->ctlslot->dev->ctlslot);
+ } else
  log_puts("sock");
 #ifdef DEBUG
  if (log_level >= 3) {
@@ -150,6 +159,11 @@ sock_close(struct sock *f)
  port_unref(f->port);
  f->port = NULL;
  }
+ if (f->ctlslot) {
+ ctlslot_del(f->ctlslot);
+ f->ctlslot = NULL;
+ xfree(f->ctldesc);
+ }
  file_del(f->file);
  close(f->fd);
  file_slowaccept = 0;
@@ -277,6 +291,7 @@ sock_new(int fd)
  f->slot = NULL;
  f->port = NULL;
  f->midi = NULL;
+ f->ctlslot = NULL;
  f->tickpending = 0;
  f->fillpending = 0;
  f->stoppending = 0;
@@ -286,6 +301,8 @@ sock_new(int fd)
  f->rtodo = sizeof(struct amsg);
  f->wmax = f->rmax = 0;
  f->lastvol = -1;
+ f->ctlops = 0;
+ f->ctlsyncpending = 0;
  f->file = file_new(&sock_fileops, f, "sock", 1);
  f->fd = fd;
  if (f->file == NULL) {
@@ -547,6 +564,11 @@ sock_wdata(struct sock *f)
  data = abuf_rgetblk(&f->slot->sub.buf, &count);
  else if (f->midi)
  data = abuf_rgetblk(&f->midi->obuf, &count);
+ else {
+ data = (unsigned char *)f->ctldesc +
+    (f->wsize - f->wtodo);
+ count = f->wtodo;
+ }
  if (count > f->wtodo)
  count = f->wtodo;
  n = sock_fdwrite(f, data, count);
@@ -800,6 +822,9 @@ sock_hello(struct sock *f)
  case MODE_REC:
  case MODE_PLAY:
  case MODE_PLAY | MODE_REC:
+ case MODE_CTLREAD:
+ case MODE_CTLWRITE:
+ case MODE_CTLREAD | MODE_CTLWRITE:
  break;
  default:
 #ifdef DEBUG
@@ -837,6 +862,31 @@ sock_hello(struct sock *f)
  return 0;
  return 1;
  }
+ if (mode & MODE_CTLMASK) {
+ d = dev_bynum(p->devnum);
+ if (d == NULL) {
+ if (log_level >= 2) {
+ sock_log(f);
+ log_puts(": ");
+ log_putu(p->devnum);
+ log_puts(": no such device\n");
+ }
+ return 0;
+ }
+ f->ctlslot = ctlslot_new(d, &sock_ctlops, f);
+ if (f->ctlslot == NULL) {
+ if (log_level >= 2) {
+ sock_log(f);
+ log_puts(": couldn't get slot\n");
+ }
+ return 0;
+ }
+ f->ctldesc = xmalloc(SOCK_CTLDESC_SIZE *
+    sizeof(struct amsg_ctl_desc));
+ f->ctlops = 0;
+ f->ctlsyncpending = 0;
+ return 1;
+ }
  d = dev_bynum(p->devnum);
  if (d == NULL)
  return 0;
@@ -856,6 +906,7 @@ sock_hello(struct sock *f)
 int
 sock_execmsg(struct sock *f)
 {
+ struct ctl *c;
  struct slot *s = f->slot;
  struct amsg *m = &f->rmsg;
  unsigned char *data;
@@ -1153,6 +1204,81 @@ sock_execmsg(struct sock *f)
  f->lastvol = ctl; /* dont trigger feedback message */
  slot_setvol(s, ctl);
  dev_midi_vol(s->dev, s);
+ dev_onval(s->dev,
+    CTLADDR_SLOT_LEVEL(f->slot - s->dev->slot), ctl);
+ break;
+ case AMSG_CTLSUB:
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": CTLSUB message, desc = ");
+ log_putx(m->u.ctlsub.desc);
+ log_puts(", val = ");
+ log_putx(m->u.ctlsub.val);
+ log_puts("\n");
+ }
+#endif
+ if (f->pstate != SOCK_INIT || f->ctlslot == NULL) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": CTLSUB, wrong state\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ if (m->u.ctlsub.desc) {
+ if (!(f->ctlops & SOCK_CTLDESC)) {
+ ctl = f->ctlslot->mask;
+ c = f->ctlslot->dev->ctl_list;
+ while (c != NULL) {
+ c->desc_mask |= ctl;
+ c = c->next;
+ }
+ }
+ f->ctlops |= SOCK_CTLDESC;
+ f->ctlsyncpending = 1;
+ } else
+ f->ctlops &= ~SOCK_CTLDESC;
+ if (m->u.ctlsub.val) {
+ f->ctlops |= SOCK_CTLVAL;
+ } else
+ f->ctlops &= ~SOCK_CTLVAL;
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+ break;
+ case AMSG_CTLSET:
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": CTLSET message\n");
+ }
+#endif
+ if (f->pstate < SOCK_INIT || f->ctlslot == NULL) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": CTLSET, wrong state\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ if (!dev_setctl(f->ctlslot->dev,
+ ntohs(m->u.ctlset.addr),
+ ntohs(m->u.ctlset.val))) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": CTLSET, wrong addr/val\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ f->rtodo = sizeof(struct amsg);
+ f->rstate = SOCK_RMSG;
  break;
  case AMSG_AUTH:
 #ifdef DEBUG
@@ -1241,7 +1367,9 @@ sock_execmsg(struct sock *f)
 int
 sock_buildmsg(struct sock *f)
 {
- unsigned int size;
+ unsigned int size, mask;
+ struct amsg_ctl_desc *desc;
+ struct ctl *c, **pc;
 
  /*
  * If pos changed (or initial tick), build a MOVE message.
@@ -1378,6 +1506,105 @@ sock_buildmsg(struct sock *f)
  f->wmsg.cmd = htonl(AMSG_STOP);
  f->wtodo = sizeof(struct amsg);
  f->wstate = SOCK_WMSG;
+ return 1;
+ }
+
+ /*
+ * XXX: add a flag indicating if there are changes
+ * in controls not seen by this client, rather
+ * than walking through the full list of control
+ * searching for the {desc,val}_mask bits
+ */
+ if (f->ctlslot && (f->ctlops & SOCK_CTLDESC)) {
+ desc = f->ctldesc;
+ mask = f->ctlslot->mask;
+ size = 0;
+ pc = &f->ctlslot->dev->ctl_list;
+ while ((c = *pc) != NULL) {
+ if ((c->desc_mask & mask) == 0 ||
+    (c->refs_mask & mask) == 0) {
+ pc = &c->next;
+ continue;
+ }
+ if (size == SOCK_CTLDESC_SIZE *
+ sizeof(struct amsg_ctl_desc))
+ break;
+ c->desc_mask &= ~mask;
+ c->val_mask &= ~mask;
+ strlcpy(desc->group, c->group,
+    AMSG_CTL_NAMEMAX);
+ strlcpy(desc->node0.name, c->node0.name,
+    AMSG_CTL_NAMEMAX);
+ desc->node0.unit = ntohs(c->node0.unit);
+ strlcpy(desc->node1.name, c->node1.name,
+    AMSG_CTL_NAMEMAX);
+ desc->node1.unit = ntohs(c->node1.unit);
+ desc->type = c->type;
+ strlcpy(desc->func, c->func, AMSG_CTL_NAMEMAX);
+ desc->addr = htons(c->addr);
+ desc->curval = htons(c->curval);
+ size += sizeof(struct amsg_ctl_desc);
+ desc++;
+
+ /* if this is a deleted entry unref it */
+ if (c->type == CTL_NONE) {
+ c->refs_mask &= ~mask;
+ if (c->refs_mask == 0) {
+ *pc = c->next;
+ xfree(c);
+ continue;
+ }
+ }
+
+ pc = &c->next;
+ }
+ if (size > 0) {
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = htonl(AMSG_DATA);
+ f->wmsg.u.data.size = htonl(size);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": building control DATA message\n");
+ }
+#endif
+ return 1;
+ }
+ }
+ if (f->ctlslot && (f->ctlops & SOCK_CTLVAL)) {
+ mask = f->ctlslot->mask;
+ for (c = f->ctlslot->dev->ctl_list; c != NULL; c = c->next) {
+ if ((c->val_mask & mask) == 0)
+ continue;
+ c->val_mask &= ~mask;
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = htonl(AMSG_CTLSET);
+ f->wmsg.u.ctlset.addr = htons(c->addr);
+ f->wmsg.u.ctlset.val = htons(c->curval);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": building CTLSET message\n");
+ }
+#endif
+ return 1;
+ }
+ }
+ if (f->ctlslot && f->ctlsyncpending) {
+ f->ctlsyncpending = 0;
+ f->wmsg.cmd = htonl(AMSG_CTLSYNC);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": building CTLSYNC message\n");
+ }
+#endif
  return 1;
  }
 #ifdef DEBUG
Index: usr.bin/sndiod/sock.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sock.h,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 sock.h
--- usr.bin/sndiod/sock.h 26 Jun 2018 07:13:54 -0000 1.5
+++ usr.bin/sndiod/sock.h 8 Feb 2020 14:49:39 -0000
@@ -58,6 +58,12 @@ struct sock {
  struct slot *slot; /* audio device slot number */
  struct midi *midi; /* midi endpoint */
  struct port *port; /* midi port */
+ struct ctlslot *ctlslot;
+ struct amsg_ctl_desc *ctldesc; /* temporary buffer */
+#define SOCK_CTLDESC 1 /* dump desc and send changes */
+#define SOCK_CTLVAL 2 /* send value changes */
+ unsigned int ctlops; /* bitmap of above */
+ int ctlsyncpending; /* CTLSYNC waiting to be transmitted */
 };
 
 struct sock *sock_new(int fd);

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Florian Obser-2
I've been running the base diffs since you posted them. Firefox,
chrome and mpv still make noise :)

I'm puzzled by this:

$ cat /etc/mixerctl.conf                                                      
outputs.master=255,255
record.enable=off

$ mixerctl outputs.master                                                    
outputs.master=255,255

$ sndioctl                
output.level=127

I don't understand how they relate and why one goes to 255 and the
other to 127.
The error reporting is confusing, too:

$ sndioctl output.level=128                                                  
integer overflow

But no regressions to report :)

--
I'm not entirely sure you are real.

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Alexandre Ratchov-2
On Tue, Feb 11, 2020 at 07:01:28PM +0100, Florian Obser wrote:

> I've been running the base diffs since you posted them. Firefox,
> chrome and mpv still make noise :)
>
> I'm puzzled by this:
>
> $ cat /etc/mixerctl.conf                                                      
> outputs.master=255,255
> record.enable=off
>
> $ mixerctl outputs.master                                                    
> outputs.master=255,255
>
> $ sndioctl                
> output.level=127
>
> I don't understand how they relate and why one goes to 255 and the
> other to 127.
> The error reporting is confusing, too:
>
> $ sndioctl output.level=128                                                  
> integer overflow
>
> But no regressions to report :)
>

Thanks, the code is base on MIDI bits, which uses the 0..127 range;
sndiod, aucat and many codecs also use the 0..127 range. Anyway,
replaced the error message by:

$ sndioctl output.level=128
128: expected integer in the 0..127 range

[...]

I'm wondering if persents or floating points in the [0:1] range would
be less confusing and solve most "units" problems.

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Jan Stary
In reply to this post by Alexandre Ratchov-2
Hi,

On Feb 09 13:13:02, [hidden email] wrote:

> cd /usr/src
> patch -p0 <1.diff
> patch -p0 <2.diff
> patch -p0 <3.diff
> cd /usr/src/include && doas make includes
> cd /usr/src/lib/libsndio && make obj && make && doas make install
> cd /usr/src/lib/libossaudio && make obj && make && doas make install
> cd /usr/src/usr.bin/sndiod && make obj && make && doas make install
> cd /usr/src/usr.bin/sndioctl && make obj && make && doas make install
> doas rcctl restart sndiod
>
> I'm very interested in any regression.

After restarting sndiod (with empty flags),
starting sndioctl -m makes sndiod crash with

  Feb 12 21:02:31 box /bsd: sndiod[95433]: pledge "tty", syscall 54

That's ioctl(2). I don't see that in any of the three pledge(2) calls
revealed by a grep in (the patched) .../sndiod/, but apparently
the new code does call ioctl().


Strangely, this happens on

OpenBSD 6.6-current (GENERIC.MP) #0: Tue Feb  4 17:33:19 CET 2020
    [hidden email]:/usr/src/sys/arch/amd64/compile/GENERIC.MP

but not on

OpenBSD 6.6-current (GENERIC.MP) #0: Sun Feb  9 17:30:47 CET 2020
    [hidden email]:/usr/src/sys/arch/amd64/compile/GENERIC.MP

(Did I miss somethong pledge() related in that window?)

        Jan

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Jan Stary
In reply to this post by Alexandre Ratchov-2
Hi,

On Feb 09 13:13:02, [hidden email] wrote:

> cd /usr/src
> patch -p0 <1.diff
> patch -p0 <2.diff
> patch -p0 <3.diff
> cd /usr/src/include && doas make includes
> cd /usr/src/lib/libsndio && make obj && make && doas make install
> cd /usr/src/lib/libossaudio && make obj && make && doas make install
> cd /usr/src/usr.bin/sndiod && make obj && make && doas make install
> cd /usr/src/usr.bin/sndioctl && make obj && make && doas make install
> doas rcctl restart sndiod
> cd /usr/ports
> patch -p0 <4.diff
> cd /usr/ports/audio/gqmpeg && make && make install
> cd /usr/ports/sysutils/tray-app && make && make install
>
> Feedback about the new features
> is of course welcome as well.

sndioctl -m works fine with e.g. mplayer,
i.e. sndioctl notices the "added" app,
and reports the volume changes.

With ffplay and firefox:youtube, volume changes are not reported.
Is that expected? Do they tweak their "internal" volume knob
but not the volume they register with sndio? As opposed to mplayer?

At any rate, thanks for the new tool!

        Jan

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Alexandre Ratchov-2
In reply to this post by Jan Stary
On Wed, Feb 12, 2020 at 09:22:20PM +0100, Jan Stary wrote:

> Hi,
>
> On Feb 09 13:13:02, [hidden email] wrote:
> > cd /usr/src
> > patch -p0 <1.diff
> > patch -p0 <2.diff
> > patch -p0 <3.diff
> > cd /usr/src/include && doas make includes
> > cd /usr/src/lib/libsndio && make obj && make && doas make install
> > cd /usr/src/lib/libossaudio && make obj && make && doas make install
> > cd /usr/src/usr.bin/sndiod && make obj && make && doas make install
> > cd /usr/src/usr.bin/sndioctl && make obj && make && doas make install
> > doas rcctl restart sndiod
> >
> > I'm very interested in any regression.
>
> After restarting sndiod (with empty flags),
> starting sndioctl -m makes sndiod crash with
>
>   Feb 12 21:02:31 box /bsd: sndiod[95433]: pledge "tty", syscall 54
>
> That's ioctl(2). I don't see that in any of the three pledge(2) calls
> revealed by a grep in (the patched) .../sndiod/, but apparently
> the new code does call ioctl().

sndiod does unauthorized ioctls on this kernel, see below

>
> Strangely, this happens on
>
> OpenBSD 6.6-current (GENERIC.MP) #0: Tue Feb  4 17:33:19 CET 2020
>     [hidden email]:/usr/src/sys/arch/amd64/compile/GENERIC.MP
>
> but not on
>
> OpenBSD 6.6-current (GENERIC.MP) #0: Sun Feb  9 17:30:47 CET 2020
>     [hidden email]:/usr/src/sys/arch/amd64/compile/GENERIC.MP
>
> (Did I miss somethong pledge() related in that window?)
>

Yes, there was a kernel commit to allow programs with the "audio"
promise (like sndiod) to use the mixer ioctl()s.

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Alexandre Ratchov-2
In reply to this post by Jan Stary
On Wed, Feb 12, 2020 at 09:28:38PM +0100, Jan Stary wrote:

> Hi,
>
> On Feb 09 13:13:02, [hidden email] wrote:
> > cd /usr/src
> > patch -p0 <1.diff
> > patch -p0 <2.diff
> > patch -p0 <3.diff
> > cd /usr/src/include && doas make includes
> > cd /usr/src/lib/libsndio && make obj && make && doas make install
> > cd /usr/src/lib/libossaudio && make obj && make && doas make install
> > cd /usr/src/usr.bin/sndiod && make obj && make && doas make install
> > cd /usr/src/usr.bin/sndioctl && make obj && make && doas make install
> > doas rcctl restart sndiod
> > cd /usr/ports
> > patch -p0 <4.diff
> > cd /usr/ports/audio/gqmpeg && make && make install
> > cd /usr/ports/sysutils/tray-app && make && make install
> >
> > Feedback about the new features
> > is of course welcome as well.
>
> sndioctl -m works fine with e.g. mplayer,
> i.e. sndioctl notices the "added" app,
> and reports the volume changes.

great :)

> With ffplay and firefox:youtube, volume changes are not reported.
> Is that expected? Do they tweak their "internal" volume knob
> but not the volume they register with sndio? As opposed to mplayer?

Yes exactly. I haven't checked ffplay, but firefox uses an internal
volume control because its internal API has no "getter", this is
explained here:

https://marc.info/?l=openbsd-ports&m=152641946326955

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Jan Stary
In reply to this post by Alexandre Ratchov-2
On Feb 12 21:38:56, [hidden email] wrote:

> On Wed, Feb 12, 2020 at 09:22:20PM +0100, Jan Stary wrote:
> > Hi,
> >
> > On Feb 09 13:13:02, [hidden email] wrote:
> > > cd /usr/src
> > > patch -p0 <1.diff
> > > patch -p0 <2.diff
> > > patch -p0 <3.diff
> > > cd /usr/src/include && doas make includes
> > > cd /usr/src/lib/libsndio && make obj && make && doas make install
> > > cd /usr/src/lib/libossaudio && make obj && make && doas make install
> > > cd /usr/src/usr.bin/sndiod && make obj && make && doas make install
> > > cd /usr/src/usr.bin/sndioctl && make obj && make && doas make install
> > > doas rcctl restart sndiod
> > >
> > > I'm very interested in any regression.
> >
> > After restarting sndiod (with empty flags),
> > starting sndioctl -m makes sndiod crash with
> >
> >   Feb 12 21:02:31 box /bsd: sndiod[95433]: pledge "tty", syscall 54
> >
> > That's ioctl(2). I don't see that in any of the three pledge(2) calls
> > revealed by a grep in (the patched) .../sndiod/, but apparently
> > the new code does call ioctl().
>
> sndiod does unauthorized ioctls on this kernel, see below
>
> >
> > Strangely, this happens on
> >
> > OpenBSD 6.6-current (GENERIC.MP) #0: Tue Feb  4 17:33:19 CET 2020
> >     [hidden email]:/usr/src/sys/arch/amd64/compile/GENERIC.MP
> >
> > but not on
> >
> > OpenBSD 6.6-current (GENERIC.MP) #0: Sun Feb  9 17:30:47 CET 2020
> >     [hidden email]:/usr/src/sys/arch/amd64/compile/GENERIC.MP
> >
> > (Did I miss somethong pledge() related in that window?)
> >
>
> Yes, there was a kernel commit to allow programs with the "audio"
> promise (like sndiod) to use the mixer ioctl()s.

Indeed, the problem disappears with the current curent.
Sorry for the noise.

        Jan

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Raf Czlonka-2
In reply to this post by Alexandre Ratchov-2
On Tue, Feb 11, 2020 at 07:15:00PM GMT, Alexandre Ratchov wrote:

> On Tue, Feb 11, 2020 at 07:01:28PM +0100, Florian Obser wrote:
> > I've been running the base diffs since you posted them. Firefox,
> > chrome and mpv still make noise :)
> >
> > I'm puzzled by this:
> >
> > $ cat /etc/mixerctl.conf                                                      
> > outputs.master=255,255
> > record.enable=off
> >
> > $ mixerctl outputs.master                                                    
> > outputs.master=255,255
> >
> > $ sndioctl                
> > output.level=127
> >
> > I don't understand how they relate and why one goes to 255 and the
> > other to 127.
> > The error reporting is confusing, too:
> >
> > $ sndioctl output.level=128                                                  
> > integer overflow
> >
> > But no regressions to report :)
> >
>
> Thanks, the code is base on MIDI bits, which uses the 0..127 range;
> sndiod, aucat and many codecs also use the 0..127 range. Anyway,
> replaced the error message by:
>
> $ sndioctl output.level=128
> 128: expected integer in the 0..127 range
>
> [...]
>
> I'm wondering if persents or floating points in the [0:1] range would
> be less confusing and solve most "units" problems.
>

Hi Alexandre,

I have to say that I also find the two ranges mildly confusing,
i.e. 0-255 in one place, and 0-127 in another. In terms of units,
personally, I'm used to, and quite like, the granularity of 0-255.

Again, not my place so others will certainly be more help here.

One more point regarding the interface, though.

This is the way mixerctl(1) currently behaves:

        $ mixerctl outputs.master    
        outputs.master=255,255
        $ mixerctl outputs.master=100
        outputs.master: 255,255 -> 100,100
        $ mixerctl outputs.master=300
        outputs.master: 100,100 -> 255,255

Should sndioctl(1) behave the same way?

Cheers,

Raf

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API

Alexandre Ratchov-2
On Thu, Feb 13, 2020 at 04:52:12AM +0000, Raf Czlonka wrote:

>
> Hi Alexandre,
>
> I have to say that I also find the two ranges mildly confusing,
> i.e. 0-255 in one place, and 0-127 in another. In terms of units,
> personally, I'm used to, and quite like, the granularity of 0-255.
>
> Again, not my place so others will certainly be more help here.
>
> One more point regarding the interface, though.
>
> This is the way mixerctl(1) currently behaves:
>
> $ mixerctl outputs.master    
> outputs.master=255,255
> $ mixerctl outputs.master=100
> outputs.master: 255,255 -> 100,100
> $ mixerctl outputs.master=300
> outputs.master: 100,100 -> 255,255
>
> Should sndioctl(1) behave the same way?

Many thanks for the feedback.

After some thinking and experimenting, floats in the [0:1] seem the
simplest option. It avoids discussions about preferences and allows
arbitrary precision (if needed, later).

Furthermore, as all controls are in the [0:1] range, it makes sense to
request the user to provide numbers between 0 and 1. Providing numbers
outside this range indicates he is misunderstanding how the program
works.

Below are all 3 base diffs combined (libsndio, sndiod, sndioctl,
libossaudio), to ease testing.

Index: include/sndio.h
===================================================================
RCS file: /cvs/src/include/sndio.h,v
retrieving revision 1.9
diff -u -p -u -p -r1.9 sndio.h
--- include/sndio.h 20 Dec 2015 11:29:29 -0000 1.9
+++ include/sndio.h 24 Feb 2020 06:03:28 -0000
@@ -26,10 +26,16 @@
 #define MIO_PORTANY "default"
 
 /*
+ * limits
+ */
+#define SIOCTL_NAMEMAX 12 /* max name length */
+
+/*
  * private ``handle'' structure
  */
 struct sio_hdl;
 struct mio_hdl;
+struct sioctl_hdl;
 
 /*
  * parameters of a full-duplex stream
@@ -85,12 +91,41 @@ struct sio_cap {
 #define SIO_XSTRINGS { "ignore", "sync", "error" }
 
 /*
+ * controlled component of the device
+ */
+struct sioctl_node {
+ char name[SIOCTL_NAMEMAX]; /* ex. "spkr" */
+ int unit; /* optional number or -1 */
+};
+
+/*
+ * description of a control (index, value) pair
+ */
+struct sioctl_desc {
+ unsigned int addr; /* control address */
+#define SIOCTL_NONE 0 /* deleted */
+#define SIOCTL_NUM 2 /* integer in the 0..127 range */
+#define SIOCTL_SW 3 /* on/off switch (0 or 1) */
+#define SIOCTL_VEC 4 /* number, element of vector */
+#define SIOCTL_LIST 5 /* switch, element of a list */
+ unsigned int type; /* one of above */
+ char func[SIOCTL_NAMEMAX]; /* function name, ex. "level" */
+ char group[SIOCTL_NAMEMAX]; /* group this control belongs to */
+ struct sioctl_node node0; /* affected node */
+ struct sioctl_node node1; /* dito for SIOCTL_{VEC,LIST} */
+ unsigned int maxval; /* max value for SIOCTL_{NUM,VEC} */
+ int __pad[3];
+};
+
+/*
  * mode bitmap
  */
 #define SIO_PLAY 1
 #define SIO_REC 2
 #define MIO_OUT 4
 #define MIO_IN 8
+#define SIOCTL_READ 0x100
+#define SIOCTL_WRITE 0x200
 
 /*
  * default bytes per sample for the given bits per sample
@@ -144,10 +179,24 @@ int mio_pollfd(struct mio_hdl *, struct
 int mio_revents(struct mio_hdl *, struct pollfd *);
 int mio_eof(struct mio_hdl *);
 
+struct sioctl_hdl *sioctl_open(const char *, unsigned int, int);
+void sioctl_close(struct sioctl_hdl *);
+int sioctl_ondesc(struct sioctl_hdl *,
+    void (*)(void *, struct sioctl_desc *, int), void *);
+int sioctl_onval(struct sioctl_hdl *,
+    void (*)(void *, unsigned int, unsigned int), void *);
+int sioctl_setval(struct sioctl_hdl *, unsigned int, unsigned int);
+int sioctl_nfds(struct sioctl_hdl *);
+int sioctl_pollfd(struct sioctl_hdl *, struct pollfd *, int);
+int sioctl_revents(struct sioctl_hdl *, struct pollfd *);
+int sioctl_eof(struct sioctl_hdl *);
+
 int mio_rmidi_getfd(const char *, unsigned int, int);
 struct mio_hdl *mio_rmidi_fdopen(int, unsigned int, int);
 int sio_sun_getfd(const char *, unsigned int, int);
 struct sio_hdl *sio_sun_fdopen(int, unsigned int, int);
+int sioctl_sun_getfd(const char *, unsigned int, int);
+struct sioctl_hdl *sioctl_sun_fdopen(int, unsigned int, int);
 
 #ifdef __cplusplus
 }
Index: lib/libsndio/Makefile
===================================================================
RCS file: /cvs/src/lib/libsndio/Makefile,v
retrieving revision 1.13
diff -u -p -u -p -r1.13 Makefile
--- lib/libsndio/Makefile 26 Dec 2017 15:23:33 -0000 1.13
+++ lib/libsndio/Makefile 24 Feb 2020 06:03:28 -0000
@@ -1,9 +1,10 @@
 # $OpenBSD: Makefile,v 1.13 2017/12/26 15:23:33 jca Exp $
 
 LIB= sndio
-MAN= sio_open.3 mio_open.3 sndio.7
+MAN= sio_open.3 mio_open.3 sioctl_open.3 sndio.7
 SRCS= debug.c aucat.c sio_aucat.c sio_sun.c sio.c \
- mio_rmidi.c mio_aucat.c mio.c
+ mio_rmidi.c mio_aucat.c mio.c \
+ sioctl_aucat.c sioctl_sun.c sioctl.c
 CFLAGS+=-DDEBUG
 COPTS+= -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wundef
 
Index: lib/libsndio/Symbols.map
===================================================================
RCS file: /cvs/src/lib/libsndio/Symbols.map,v
retrieving revision 1.1
diff -u -p -u -p -r1.1 Symbols.map
--- lib/libsndio/Symbols.map 26 Dec 2017 19:12:22 -0000 1.1
+++ lib/libsndio/Symbols.map 24 Feb 2020 06:03:28 -0000
@@ -27,10 +27,22 @@
  mio_revents;
  mio_eof;
 
+ sioctl_open;
+ sioctl_close;
+ sioctl_ondesc;
+ sioctl_onval;
+ sioctl_setval;
+ sioctl_nfds;
+ sioctl_pollfd;
+ sioctl_revents;
+ sioctl_eof;
+
  mio_rmidi_getfd;
  mio_rmidi_fdopen;
  sio_sun_getfd;
  sio_sun_fdopen;
+ sioctl_sun_getfd;
+ sioctl_sun_fdopen;
  local:
  *;
 };
Index: lib/libsndio/amsg.h
===================================================================
RCS file: /cvs/src/lib/libsndio/amsg.h,v
retrieving revision 1.12
diff -u -p -u -p -r1.12 amsg.h
--- lib/libsndio/amsg.h 12 Jul 2019 06:30:55 -0000 1.12
+++ lib/libsndio/amsg.h 24 Feb 2020 06:03:28 -0000
@@ -43,6 +43,11 @@
 #define AUCAT_PORT 11025
 
 /*
+ * limits
+ */
+#define AMSG_CTL_NAMEMAX 16 /* max name length */
+
+/*
  * WARNING: since the protocol may be simultaneously used by static
  * binaries or by different versions of a shared library, we are not
  * allowed to change the packet binary representation in a backward
@@ -64,6 +69,9 @@ struct amsg {
 #define AMSG_HELLO 10 /* say hello, check versions and so ... */
 #define AMSG_BYE 11 /* ask server to drop connection */
 #define AMSG_AUTH 12 /* send authentication cookie */
+#define AMSG_CTLSUB 13 /* ondesc/onctl subscription */
+#define AMSG_CTLSET 14 /* set control value */
+#define AMSG_CTLSYNC 15 /* end of controls descriptions */
  uint32_t cmd;
  uint32_t __pad;
  union {
@@ -108,7 +116,38 @@ struct amsg {
 #define AMSG_COOKIELEN 16
  uint8_t cookie[AMSG_COOKIELEN];
  } auth;
+ struct amsg_ctlsub {
+ uint8_t desc, val;
+ } ctlsub;
+ struct amsg_ctlset {
+ uint16_t addr, val;
+ } ctlset;
  } u;
+};
+
+/*
+ * network representation of sioctl_node structure
+ */
+struct amsg_ctl_node {
+ char name[AMSG_CTL_NAMEMAX];
+ int16_t unit;
+ uint8_t __pad[2];
+};
+
+/*
+ * network representation of sioctl_desc structure
+ */
+struct amsg_ctl_desc {
+ struct amsg_ctl_node node0; /* affected channels */
+ struct amsg_ctl_node node1; /* dito for AMSG_CTL_{SEL,VEC,LIST} */
+ char func[AMSG_CTL_NAMEMAX]; /* parameter function name */
+ char group[AMSG_CTL_NAMEMAX]; /* group of the control */
+ uint8_t type; /* see sioctl_desc structure */
+ uint8_t __pad1[1];
+ uint16_t addr; /* control address */
+ uint16_t maxval;
+ uint16_t curval;
+ uint32_t __pad2[3];
 };
 
 /*
Index: lib/libsndio/shlib_version
===================================================================
RCS file: /cvs/src/lib/libsndio/shlib_version,v
retrieving revision 1.11
diff -u -p -u -p -r1.11 shlib_version
--- lib/libsndio/shlib_version 26 Dec 2017 15:23:33 -0000 1.11
+++ lib/libsndio/shlib_version 24 Feb 2020 06:03:28 -0000
@@ -1,2 +1,2 @@
 major=7
-minor=0
+minor=1
Index: lib/libsndio/sioctl.c
===================================================================
RCS file: lib/libsndio/sioctl.c
diff -N lib/libsndio/sioctl.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl.c 24 Feb 2020 06:03:30 -0000
@@ -0,0 +1,177 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[hidden email]>
+ *
+ * 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 <string.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "sioctl_priv.h"
+
+struct sioctl_hdl *
+sioctl_open(const char *str, unsigned int mode, int nbio)
+{
+ static char devany[] = SIO_DEVANY;
+ struct sioctl_hdl *hdl;
+
+#ifdef DEBUG
+ _sndio_debug_init();
+#endif
+ if (str == NULL) /* backward compat */
+ str = devany;
+ if (strcmp(str, devany) == 0 && !issetugid()) {
+ str = getenv("AUDIODEVICE");
+ if (str == NULL)
+ str = devany;
+ }
+ if (strcmp(str, devany) == 0) {
+ hdl = _sioctl_aucat_open("snd/0", mode, nbio);
+ if (hdl != NULL)
+ return hdl;
+ return _sioctl_sun_open("rsnd/0", mode, nbio);
+ }
+ if (_sndio_parsetype(str, "snd"))
+ return _sioctl_aucat_open(str, mode, nbio);
+ if (_sndio_parsetype(str, "rsnd"))
+ return _sioctl_sun_open(str, mode, nbio);
+ DPRINTF("sioctl_open: %s: unknown device type\n", str);
+ return NULL;
+}
+
+void
+_sioctl_create(struct sioctl_hdl *hdl, struct sioctl_ops *ops,
+    unsigned int mode, int nbio)
+{
+ hdl->ops = ops;
+ hdl->mode = mode;
+ hdl->nbio = nbio;
+ hdl->eof = 0;
+ hdl->ctl_cb = NULL;
+}
+
+int
+_sioctl_psleep(struct sioctl_hdl *hdl, int event)
+{
+ struct pollfd pfds[SIOCTL_MAXNFDS];
+ int revents, nfds;
+
+ for (;;) {
+ nfds = sioctl_pollfd(hdl, pfds, event);
+ if (nfds == 0)
+ return 0;
+ while (poll(pfds, nfds, -1) < 0) {
+ if (errno == EINTR)
+ continue;
+ DPERROR("sioctl_psleep: poll");
+ hdl->eof = 1;
+ return 0;
+ }
+ revents = sioctl_revents(hdl, pfds);
+ if (revents & POLLHUP) {
+ DPRINTF("sioctl_psleep: hang-up\n");
+ return 0;
+ }
+ if (event == 0 || (revents & event))
+ break;
+ }
+ return 1;
+}
+
+void
+sioctl_close(struct sioctl_hdl *hdl)
+{
+ hdl->ops->close(hdl);
+}
+
+int
+sioctl_nfds(struct sioctl_hdl *hdl)
+{
+ return hdl->ops->nfds(hdl);
+}
+
+int
+sioctl_pollfd(struct sioctl_hdl *hdl, struct pollfd *pfd, int events)
+{
+ if (hdl->eof)
+ return 0;
+ return hdl->ops->pollfd(hdl, pfd, events);
+}
+
+int
+sioctl_revents(struct sioctl_hdl *hdl, struct pollfd *pfd)
+{
+ if (hdl->eof)
+ return POLLHUP;
+ return hdl->ops->revents(hdl, pfd);
+}
+
+int
+sioctl_eof(struct sioctl_hdl *hdl)
+{
+ return hdl->eof;
+}
+
+int
+sioctl_ondesc(struct sioctl_hdl *hdl,
+    void (*cb)(void *, struct sioctl_desc *, int), void *arg)
+{
+ hdl->desc_cb = cb;
+ hdl->desc_arg = arg;
+ return hdl->ops->ondesc(hdl);
+}
+
+int
+sioctl_onval(struct sioctl_hdl *hdl,
+    void (*cb)(void *, unsigned int, unsigned int), void *arg)
+{
+ hdl->ctl_cb = cb;
+ hdl->ctl_arg = arg;
+ return hdl->ops->onctl(hdl);
+}
+
+void
+_sioctl_ondesc_cb(struct sioctl_hdl *hdl,
+    struct sioctl_desc *desc, unsigned int val)
+{
+ if (desc) {
+ DPRINTF("_sioctl_ondesc_cb: %u -> %s[%d].%s=%s[%d]:%d\n",
+    desc->addr,
+    desc->node0.name, desc->node0.unit,
+    desc->func,
+    desc->node1.name, desc->node1.unit,
+    val);
+ }
+ if (hdl->desc_cb)
+ hdl->desc_cb(hdl->desc_arg, desc, val);
+}
+
+void
+_sioctl_onval_cb(struct sioctl_hdl *hdl, unsigned int addr, unsigned int val)
+{
+ DPRINTF("_sioctl_onval_cb: %u -> %u\n", addr, val);
+ if (hdl->ctl_cb)
+ hdl->ctl_cb(hdl->ctl_arg, addr, val);
+}
+
+int
+sioctl_setval(struct sioctl_hdl *hdl, unsigned int addr, unsigned int val)
+{
+ if (!(hdl->mode & SIOCTL_WRITE))
+ return 0;
+ return hdl->ops->setctl(hdl, addr, val);
+}
Index: lib/libsndio/sioctl_aucat.c
===================================================================
RCS file: lib/libsndio/sioctl_aucat.c
diff -N lib/libsndio/sioctl_aucat.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl_aucat.c 24 Feb 2020 06:03:30 -0000
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[hidden email]>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sndio.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include "debug.h"
+#include "aucat.h"
+#include "sioctl_priv.h"
+
+struct sioctl_aucat_hdl {
+ struct sioctl_hdl sioctl;
+ struct aucat aucat;
+ struct sioctl_desc desc;
+ struct amsg_ctl_desc buf[16];
+ size_t buf_wpos;
+ int dump_wait;
+};
+
+static void sioctl_aucat_close(struct sioctl_hdl *);
+static int sioctl_aucat_nfds(struct sioctl_hdl *);
+static int sioctl_aucat_pollfd(struct sioctl_hdl *, struct pollfd *, int);
+static int sioctl_aucat_revents(struct sioctl_hdl *, struct pollfd *);
+static int sioctl_aucat_setctl(struct sioctl_hdl *, unsigned int, unsigned int);
+static int sioctl_aucat_onval(struct sioctl_hdl *);
+static int sioctl_aucat_ondesc(struct sioctl_hdl *);
+
+/*
+ * operations every device should support
+ */
+struct sioctl_ops sioctl_aucat_ops = {
+ sioctl_aucat_close,
+ sioctl_aucat_nfds,
+ sioctl_aucat_pollfd,
+ sioctl_aucat_revents,
+ sioctl_aucat_setctl,
+ sioctl_aucat_onval,
+ sioctl_aucat_ondesc
+};
+
+static int
+sioctl_aucat_rdata(struct sioctl_aucat_hdl *hdl)
+{
+ struct sioctl_desc desc;
+ struct amsg_ctl_desc *c;
+ size_t rpos;
+ int n;
+
+ while (hdl->aucat.rstate == RSTATE_DATA) {
+
+ /* read entries */
+ while (hdl->buf_wpos < sizeof(hdl->buf) &&
+    hdl->aucat.rstate == RSTATE_DATA) {
+ n = _aucat_rdata(&hdl->aucat,
+    (unsigned char *)hdl->buf + hdl->buf_wpos,
+    sizeof(hdl->buf) - hdl->buf_wpos,
+    &hdl->sioctl.eof);
+ if (n == 0 || hdl->sioctl.eof)
+ return 0;
+ hdl->buf_wpos += n;
+ }
+
+ /* parse entries */
+ c = hdl->buf;
+ rpos = 0;
+ while (rpos < hdl->buf_wpos) {
+ strlcpy(desc.group, c->group, SIOCTL_NAMEMAX);
+ strlcpy(desc.node0.name, c->node0.name, SIOCTL_NAMEMAX);
+ desc.node0.unit = (int16_t)ntohs(c->node0.unit);
+ strlcpy(desc.node1.name, c->node1.name, SIOCTL_NAMEMAX);
+ desc.node1.unit = (int16_t)ntohs(c->node1.unit);
+ strlcpy(desc.func, c->func, SIOCTL_NAMEMAX);
+ desc.type = c->type;
+ desc.addr = ntohs(c->addr);
+ desc.maxval = ntohs(c->maxval);
+ _sioctl_ondesc_cb(&hdl->sioctl,
+    &desc, ntohs(c->curval));
+ rpos += sizeof(struct amsg_ctl_desc);
+ c++;
+ }
+ hdl->buf_wpos = 0;
+ }
+ return 1;
+}
+
+/*
+ * execute the next message, return 0 if blocked
+ */
+static int
+sioctl_aucat_runmsg(struct sioctl_aucat_hdl *hdl)
+{
+ if (!_aucat_rmsg(&hdl->aucat, &hdl->sioctl.eof))
+ return 0;
+ switch (ntohl(hdl->aucat.rmsg.cmd)) {
+ case AMSG_DATA:
+ hdl->buf_wpos = 0;
+ if (!sioctl_aucat_rdata(hdl))
+ return 0;
+ break;
+ case AMSG_CTLSET:
+ DPRINTF("sioctl_aucat_runmsg: got CTLSET\n");
+ _sioctl_onval_cb(&hdl->sioctl,
+    ntohs(hdl->aucat.rmsg.u.ctlset.addr),
+    ntohs(hdl->aucat.rmsg.u.ctlset.val));
+ break;
+ case AMSG_CTLSYNC:
+ DPRINTF("sioctl_aucat_runmsg: got CTLSYNC\n");
+ hdl->dump_wait = 0;
+ _sioctl_ondesc_cb(&hdl->sioctl, NULL, 0);
+ break;
+ default:
+ DPRINTF("sio_aucat_runmsg: unhandled message %u\n",
+    hdl->aucat.rmsg.cmd);
+ hdl->sioctl.eof = 1;
+ return 0;
+ }
+ hdl->aucat.rstate = RSTATE_MSG;
+ hdl->aucat.rtodo = sizeof(struct amsg);
+ return 1;
+}
+
+struct sioctl_hdl *
+_sioctl_aucat_open(const char *str, unsigned int mode, int nbio)
+{
+ struct sioctl_aucat_hdl *hdl;
+
+ hdl = malloc(sizeof(struct sioctl_aucat_hdl));
+ if (hdl == NULL)
+ return NULL;
+ if (!_aucat_open(&hdl->aucat, str, mode))
+ goto bad;
+ _sioctl_create(&hdl->sioctl, &sioctl_aucat_ops, mode, nbio);
+ if (!_aucat_setfl(&hdl->aucat, 1, &hdl->sioctl.eof))
+ goto bad;
+ hdl->dump_wait = 0;
+ return (struct sioctl_hdl *)hdl;
+bad:
+ free(hdl);
+ return NULL;
+}
+
+static void
+sioctl_aucat_close(struct sioctl_hdl *addr)
+{
+ struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+ if (!hdl->sioctl.eof)
+ _aucat_setfl(&hdl->aucat, 0, &hdl->sioctl.eof);
+ _aucat_close(&hdl->aucat, hdl->sioctl.eof);
+ free(hdl);
+}
+
+static int
+sioctl_aucat_ondesc(struct sioctl_hdl *addr)
+{
+ struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+ while (hdl->aucat.wstate != WSTATE_IDLE) {
+ if (!_sioctl_psleep(&hdl->sioctl, POLLOUT))
+ return 0;
+ }
+ AMSG_INIT(&hdl->aucat.wmsg);
+ hdl->aucat.wmsg.cmd = htonl(AMSG_CTLSUB);
+ hdl->aucat.wmsg.u.ctlsub.desc = 1;
+ hdl->aucat.wmsg.u.ctlsub.val = 0;
+ hdl->aucat.wtodo = sizeof(struct amsg);
+ if (!_aucat_wmsg(&hdl->aucat, &hdl->sioctl.eof))
+ return 0;
+ hdl->dump_wait = 1;
+ while (hdl->dump_wait) {
+ DPRINTF("psleeping...\n");
+ if (!_sioctl_psleep(&hdl->sioctl, 0))
+ return 0;
+ DPRINTF("psleeping done\n");
+ }
+ DPRINTF("done\n");
+ return 1;
+}
+
+static int
+sioctl_aucat_onval(struct sioctl_hdl *addr)
+{
+ struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+ while (hdl->aucat.wstate != WSTATE_IDLE) {
+ if (!_sioctl_psleep(&hdl->sioctl, POLLOUT))
+ return 0;
+ }
+ AMSG_INIT(&hdl->aucat.wmsg);
+ hdl->aucat.wmsg.cmd = htonl(AMSG_CTLSUB);
+ hdl->aucat.wmsg.u.ctlsub.desc = 1;
+ hdl->aucat.wmsg.u.ctlsub.val = 1;
+ hdl->aucat.wtodo = sizeof(struct amsg);
+ if (!_aucat_wmsg(&hdl->aucat, &hdl->sioctl.eof))
+ return 0;
+ return 1;
+}
+
+static int
+sioctl_aucat_setctl(struct sioctl_hdl *addr, unsigned int a, unsigned int v)
+{
+ struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+ hdl->aucat.wstate = WSTATE_MSG;
+ hdl->aucat.wtodo = sizeof(struct amsg);
+ hdl->aucat.wmsg.cmd = htonl(AMSG_CTLSET);
+ hdl->aucat.wmsg.u.ctlset.addr = htons(a);
+ hdl->aucat.wmsg.u.ctlset.val = htons(v);
+ while (hdl->aucat.wstate != WSTATE_IDLE) {
+ if (_aucat_wmsg(&hdl->aucat, &hdl->sioctl.eof))
+ break;
+ if (hdl->sioctl.nbio || !_sioctl_psleep(&hdl->sioctl, POLLOUT))
+ return 0;
+ }
+ return 1;
+}
+
+static int
+sioctl_aucat_nfds(struct sioctl_hdl *addr)
+{
+ return 1;
+}
+
+static int
+sioctl_aucat_pollfd(struct sioctl_hdl *addr, struct pollfd *pfd, int events)
+{
+ struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+ return _aucat_pollfd(&hdl->aucat, pfd, events | POLLIN);
+}
+
+static int
+sioctl_aucat_revents(struct sioctl_hdl *addr, struct pollfd *pfd)
+{
+ struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+ int revents;
+
+ revents = _aucat_revents(&hdl->aucat, pfd);
+ if (revents & POLLIN) {
+ while (1) {
+ if (hdl->aucat.rstate == RSTATE_MSG) {
+ if (!sioctl_aucat_runmsg(hdl))
+ break;
+ }
+ if (hdl->aucat.rstate == RSTATE_DATA) {
+ if (!sioctl_aucat_rdata(hdl))
+ break;
+ }
+ }
+ revents &= ~POLLIN;
+ }
+ if (hdl->sioctl.eof)
+ return POLLHUP;
+ DPRINTFN(3, "sioctl_aucat_revents: revents = 0x%x\n", revents);
+ return revents;
+}
Index: lib/libsndio/sioctl_open.3
===================================================================
RCS file: lib/libsndio/sioctl_open.3
diff -N lib/libsndio/sioctl_open.3
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl_open.3 24 Feb 2020 06:03:30 -0000
@@ -0,0 +1,252 @@
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2011 Alexandre Ratchov <[hidden email]>
+.\"
+.\" 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: September 29 2012 $
+.Dt SIO_OPEN 3
+.Os
+.Sh NAME
+.Nm sioctl_open ,
+.Nm sioctl_close ,
+.Nm sioctl_ondesc ,
+.Nm sioctl_onval ,
+.Nm sioctl_setval ,
+.Nm sioctl_nfds ,
+.Nm sioctl_pollfd ,
+.Nm sioctl_eof
+.Nd interface to audio parameters
+.Sh SYNOPSIS
+.Fd #include <sndio.h>
+.Ft "struct sioctl_hdl *"
+.Fn "sioctl_open" "const char *name" "unsigned int mode" "int nbio_flag"
+.Ft "void"
+.Fn "sioctl_close" "struct sioctl_hdl *hdl"
+.Ft "int"
+.Fn "sioctl_ondesc" "struct sioctl_hdl *hdl" "void (*cb)(void *arg, struct sioctl_desc *desc, int val)" "void *arg"
+.Ft "void"
+.Fn "sioctl_onval" "struct sioctl_hdl *hdl" "void (*cb)(void *arg, unsigned int addr, unsigned int val)" "void *arg"
+.Ft "int"
+.Fn "sioctl_setval" "struct sioctl_hdl *hdl" "unsigned int addr" "unsigned int val"
+.Ft "int"
+.Fn "sioctl_nfds" "struct sioctl_hdl *hdl"
+.Ft "int"
+.Fn "sioctl_pollfd" "struct sioctl_hdl *hdl" "struct pollfd *pfd" "int events"
+.Ft "int"
+.Fn "sioctl_revents" "struct sioctl_hdl *hdl" "struct pollfd *pfd"
+.Ft "int"
+.Fn "sioctl_eof" "struct sioctl_hdl *hdl"
+.Sh DESCRIPTION
+Audio devices may expose a number of controls, like the playback volume control.
+Each control has an integer
+.Em address
+and an integer
+.Em value .
+Depending on the control type, its integer value represents either a
+continuous quantity or a boolean.
+Any control may be changed by submitting
+a new value to its address.
+When values change, asynchronous notifications are sent.
+.Pp
+Controls descriptions are available, allowing them to be grouped and
+represented in a human usable form.
+.Sh Opening and closing the control device
+First the application must call the
+.Fn sioctl_open
+function to obtain a handle
+that will be passed as the
+.Ar hdl
+argument to other functions.
+.Pp
+The
+.Ar name
+parameter gives the device string discussed in
+.Xr sndio 7 .
+In most cases it should be set to SIOCTL_DEVANY to allow
+the user to select it using the
+.Ev AUDIODEVICE
+environment variable.
+The
+.Ar mode
+parameter is a bitmap of the SIOCTL_READ and SIOCTL_WRITE constants
+indicating whether control values can be read and
+modified respectively.
+.Pp
+If the
+.Ar nbio_flag
+argument is 1, then the
+.Fn sioctl_setval
+function (see below) may fail instead of blocking and
+the
+.Fn sioctl_ondesc
+function doesn't block.
+.Pp
+The
+.Fn sioctl_close
+function closes the control device and frees any allocated resources
+associated with the handle.
+.Sh Controls descriptions
+The
+.Fn sioctl_ondesc
+function can be used to obtain the description of all available controls
+and their initial values.
+It registers a call-back that is immediately invoked for all
+controls.
+It's called once with a NULL argument to indicate that the full
+description was sent and that the caller has a consistent
+representation of the controls set.
+.Pp
+Then, whenever a control description changes, the call-back is
+invoked with the updated information followed by a call with a NULL
+argument.
+.Pp
+Controls are described by the
+.Va sioctl_ondesc
+stucture as follows:
+.Bd -literal
+struct sioctl_node {
+ char name[SIOCTL_NAMEMAX]; /* ex. "spkr" */
+ int unit; /* optional number or -1 */
+};
+
+struct sioctl_desc {
+ unsigned int addr; /* control address */
+#define SIOCTL_NONE 0 /* deleted */
+#define SIOCTL_NUM 2 /* integer in the 0..127 range */
+#define SIOCTL_SW 3 /* on/off switch (0 or 1) */
+#define SIOCTL_VEC 4 /* number, element of vector */
+#define SIOCTL_LIST 5 /* switch, element of a list */
+ unsigned int type; /* one of above */
+ char func[SIOCTL_NAMEMAX]; /* function name, ex. "level" */
+ char group[SIOCTL_NAMEMAX]; /* group this control belongs to */
+ struct sioctl_node node0; /* affected node */
+ struct sioctl_node node1; /* dito for SIOCTL_{VEC,LIST} */
+};
+.Ed
+.Pp
+The
+.Va addr
+attribute is the control address, usable with
+.Fn sioctl_setval
+to set its value.
+.Pp
+The
+.Va type
+attribute indicates what the structure describes.
+Possible types are:
+.Bl -tag -width "SIOCTL_LIST"
+.It SIOCTL_NONE
+A previously valid control was deleted.
+.It SIOCTL_NUM
+A continuous control in the 0..SIOCTL_VALMAX range.
+For instance the volume of the speaker.
+.It SIOCTL_SW
+A on/off switch control.
+For instance the switch to mute the speaker.
+.It SIOCTL_VEC
+Element of an array of continuous controls.
+For instance the knob to control the amount of signal flowing
+from the line input to the speaker.
+.It SIOCTL_LIST
+An element of an array of on/off switches.
+For instance the line-in position of the
+speaker source selector.
+.El
+.Pp
+The
+.Va func
+attribute is the name of the parameter being controlled.
+There may be no parameters of different types with the same name.
+.Pp
+The
+.Va node0
+and
+.Va node1
+attributes indicate the names of the controlled nodes, typically
+channels of audio streams.
+.Va node1
+is meaningful for
+.Va SIOCTL_VEC
+and
+.Va SIOCTL_LIST
+only.
+.Pp
+Names in the
+.Va node0
+and
+.Va node1
+attributes and
+.Va func
+are strings usable as unique identifiers within the the given
+.Va group .
+.Sh Changing and reading control values
+Controls are changed with the
+.Fn sioctl_setval
+function, by giving the index of the control and the new value.
+The
+.Fn sioctl_onval
+function can be used to register a call-back which will be invoked whenever
+a control changes.
+Continuous values are in the 0..127 range.
+.Sh "Interface to" Xr poll 2
+The
+.Fn sioctl_pollfd
+function fills the array
+.Ar pfd
+of
+.Va pollfd
+structures, used by
+.Xr poll 2 ,
+with
+.Ar events ;
+the latter is a bit-mask of
+.Va POLLIN
+and
+.Va POLLOUT
+constants.
+.Fn sioctl_pollfd
+returns the number of
+.Va pollfd
+structures filled.
+The
+.Fn sioctl_revents
+function returns the bit-mask set by
+.Xr poll 2
+in the
+.Va pfd
+array of
+.Va pollfd
+structures.
+If
+.Va POLLOUT
+is set,
+.Fn sioctl_setval
+can be called without blocking.
+POLLHUP may be set if an error occurs, even if
+it is not selected with
+.Fn sioctl_pollfd .
+POLLIN is not used yet.
+.Pp
+The
+.Fn sioctl_nfds
+function returns the number of
+.Va pollfd
+structures the caller must preallocate in order to be sure
+that
+.Fn sioctl_pollfd
+will never overrun.
+.Sh SEE ALSO
+.Xr sndioctl 1 ,
+.Xr poll 2 ,
+.Xr sndio 7
Index: lib/libsndio/sioctl_priv.h
===================================================================
RCS file: lib/libsndio/sioctl_priv.h
diff -N lib/libsndio/sioctl_priv.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl_priv.h 24 Feb 2020 06:03:30 -0000
@@ -0,0 +1,62 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[hidden email]>
+ *
+ * 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.
+ */
+#ifndef SIOCTL_PRIV_H
+#define SIOCTL_PRIV_H
+
+#include <sndio.h>
+
+#define SIOCTL_MAXNFDS 4
+
+/*
+ * private ``handle'' structure
+ */
+struct sioctl_hdl {
+ struct sioctl_ops *ops;
+ void (*desc_cb)(void *, struct sioctl_desc *, int);
+ void *desc_arg;
+ void (*ctl_cb)(void *, unsigned int, unsigned int);
+ void *ctl_arg;
+ unsigned int mode; /* SIOCTL_READ | SIOCTL_WRITE */
+ int nbio; /* true if non-blocking io */
+ int eof; /* true if error occured */
+};
+
+/*
+ * operations every device should support
+ */
+struct sioctl_ops {
+ void (*close)(struct sioctl_hdl *);
+ int (*nfds)(struct sioctl_hdl *);
+ int (*pollfd)(struct sioctl_hdl *, struct pollfd *, int);
+ int (*revents)(struct sioctl_hdl *, struct pollfd *);
+ int (*setctl)(struct sioctl_hdl *, unsigned int, unsigned int);
+ int (*onctl)(struct sioctl_hdl *);
+ int (*ondesc)(struct sioctl_hdl *);
+};
+
+struct sioctl_hdl *_sioctl_aucat_open(const char *, unsigned int, int);
+struct sioctl_hdl *_sioctl_obsd_open(const char *, unsigned int, int);
+struct sioctl_hdl *_sioctl_fake_open(const char *, unsigned int, int);
+struct sioctl_hdl *_sioctl_sun_open(const char *, unsigned int, int);
+void _sioctl_create(struct sioctl_hdl *,
+    struct sioctl_ops *, unsigned int, int);
+void _sioctl_ondesc_cb(struct sioctl_hdl *,
+    struct sioctl_desc *, unsigned int);
+void _sioctl_onval_cb(struct sioctl_hdl *, unsigned int, unsigned int);
+int _sioctl_psleep(struct sioctl_hdl *, int);
+
+#endif /* !defined(SIOCTL_PRIV_H) */
Index: lib/libsndio/sioctl_sun.c
===================================================================
RCS file: lib/libsndio/sioctl_sun.c
diff -N lib/libsndio/sioctl_sun.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl_sun.c 24 Feb 2020 06:03:30 -0000
@@ -0,0 +1,483 @@
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[hidden email]>
+ *
+ * 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.
+ */
+/*
+ * the way the sun mixer is designed doesn't let us representing
+ * it easily with the sioctl api. For now expose only few
+ * white-listed controls the same way as we do in kernel
+ * for the wskbd volume keys.
+ */
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/audioio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "sioctl_priv.h"
+
+#define DEVPATH_PREFIX "/dev/audioctl"
+#define DEVPATH_MAX (1 + \
+ sizeof(DEVPATH_PREFIX) - 1 + \
+ sizeof(int) * 3)
+
+struct volume
+{
+ int nch; /* channels in the level control */
+ int level_idx; /* index of the level control */
+ int level_val[8]; /* current value */
+ int mute_idx; /* index of the mute control */
+ int mute_val; /* per channel state of mute control */
+ int base_addr;
+ char *name;
+};
+
+struct sioctl_sun_hdl {
+ struct sioctl_hdl sioctl;
+ struct volume output, input;
+ int fd, events;
+};
+
+static void sioctl_sun_close(struct sioctl_hdl *);
+static int sioctl_sun_nfds(struct sioctl_hdl *);
+static int sioctl_sun_pollfd(struct sioctl_hdl *, struct pollfd *, int);
+static int sioctl_sun_revents(struct sioctl_hdl *, struct pollfd *);
+static int sioctl_sun_setctl(struct sioctl_hdl *, unsigned int, unsigned int);
+static int sioctl_sun_onval(struct sioctl_hdl *);
+static int sioctl_sun_ondesc(struct sioctl_hdl *);
+
+/*
+ * operations every device should support
+ */
+struct sioctl_ops sioctl_sun_ops = {
+ sioctl_sun_close,
+ sioctl_sun_nfds,
+ sioctl_sun_pollfd,
+ sioctl_sun_revents,
+ sioctl_sun_setctl,
+ sioctl_sun_onval,
+ sioctl_sun_ondesc
+};
+
+static int
+initmute(struct sioctl_sun_hdl *hdl, struct mixer_devinfo *info)
+{
+ struct mixer_devinfo mi;
+
+ mi.index = info->next;
+ for (mi.index = info->next; mi.index != -1; mi.index = mi.next) {
+ if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &mi) < 0)
+ break;
+ if (strcmp(mi.label.name, AudioNmute) == 0)
+ return mi.index;
+ }
+ return -1;
+}
+
+static int
+initvol(struct sioctl_sun_hdl *hdl, struct volume *vol, char *cn, char *dn)
+{
+ struct mixer_devinfo dev, cls;
+
+ for (dev.index = 0; ; dev.index++) {
+ if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &dev) < 0)
+ break;
+ if (dev.type != AUDIO_MIXER_VALUE)
+ continue;
+ cls.index = dev.mixer_class;
+ if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &cls) < 0)
+ break;
+ if (strcmp(cls.label.name, cn) == 0 &&
+    strcmp(dev.label.name, dn) == 0) {
+ vol->nch = dev.un.v.num_channels;
+ vol->level_idx = dev.index;
+ vol->mute_idx = initmute(hdl, &dev);
+ DPRINTF("using %s.%s, %d channels, %s\n", cn, dn,
+    vol->nch, vol->mute_idx >= 0 ? "mute" : "no mute");
+ return 1;
+ }
+ }
+ vol->level_idx = vol->mute_idx = -1;
+ return 0;
+}
+
+static void
+init(struct sioctl_sun_hdl *hdl)
+{
+ static struct {
+ char *cn, *dn;
+ } output_names[] = {
+ {AudioCoutputs, AudioNmaster},
+ {AudioCinputs,  AudioNdac},
+ {AudioCoutputs, AudioNdac},
+ {AudioCoutputs, AudioNoutput}
+ }, input_names[] = {
+ {AudioCrecord, AudioNrecord},
+ {AudioCrecord, AudioNvolume},
+ {AudioCinputs, AudioNrecord},
+ {AudioCinputs, AudioNvolume},
+ {AudioCinputs, AudioNinput}
+ };
+ int i;
+
+ for (i = 0; i < sizeof(output_names) / sizeof(output_names[0]); i++) {
+ if (initvol(hdl, &hdl->output,
+ output_names[i].cn, output_names[i].dn)) {
+ hdl->output.name = "output";
+ hdl->output.base_addr = 0;
+ break;
+ }
+ }
+ for (i = 0; i < sizeof(input_names) / sizeof(input_names[0]); i++) {
+ if (initvol(hdl, &hdl->input,
+ input_names[i].cn, input_names[i].dn)) {
+ hdl->input.name = "input";
+ hdl->input.base_addr = 64;
+ break;
+ }
+ }
+}
+
+static int
+setvol(struct sioctl_sun_hdl *hdl, struct volume *vol, int addr, int val)
+{
+ struct mixer_ctrl ctrl;
+ int i;
+
+ addr -= vol->base_addr;
+ if (vol->level_idx >= 0 && addr >= 0 && addr < vol->nch) {
+ if (vol->level_val[addr] == val) {
+ DPRINTF("level %d, no change\n", val);
+ return 1;
+ }
+ vol->level_val[addr] = val;
+ ctrl.dev = vol->level_idx;
+ ctrl.type = AUDIO_MIXER_VALUE;
+ ctrl.un.value.num_channels = vol->nch;
+ for (i = 0; i < vol->nch; i++)
+ ctrl.un.value.level[i] = vol->level_val[i];
+ DPRINTF("vol %d setting to %d\n", addr, vol->level_val[addr]);
+ if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) {
+ DPRINTF("level write failed\n");
+ return 0;
+ }
+ _sioctl_onval_cb(&hdl->sioctl, vol->base_addr + addr, val);
+ return 1;
+ }
+
+ addr -= 32;
+ if (vol->mute_idx >= 0 && addr >= 0 && addr < vol->nch) {
+ val = val ? 1 : 0;
+ if (vol->mute_val == val) {
+ DPRINTF("mute %d, no change\n", val);
+ return 1;
+ }
+ vol->mute_val = val;
+ ctrl.dev = vol->mute_idx;
+ ctrl.type = AUDIO_MIXER_ENUM;
+ ctrl.un.ord = val;
+ DPRINTF("mute setting to %d\n", val);
+ if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) {
+ DPERROR("mute write\n");
+ return 0;
+ }
+ for (i = 0; i < vol->nch; i++) {
+ _sioctl_onval_cb(&hdl->sioctl,
+    vol->base_addr + 32 + i, val);
+ }
+ return 1;
+ }
+ return 1;
+}
+
+static int
+scanvol(struct sioctl_sun_hdl *hdl, struct volume *vol)
+{
+ struct sioctl_desc desc;
+ struct mixer_ctrl ctrl;
+ int i, val;
+
+ memset(&desc, 0, sizeof(struct sioctl_desc));
+ if (vol->level_idx >= 0) {
+ ctrl.dev = vol->level_idx;
+ ctrl.type = AUDIO_MIXER_VALUE;
+ ctrl.un.value.num_channels = vol->nch;
+ if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) {
+ DPRINTF("level read failed\n");
+ return 0;
+ }
+ desc.type = SIOCTL_NUM;
+ desc.maxval = AUDIO_MAX_GAIN;
+ desc.node1.name[0] = 0;
+ desc.node1.unit = -1;
+ strlcpy(desc.func, "level", SIOCTL_NAMEMAX);
+ strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX);
+ for (i = 0; i < vol->nch; i++) {
+ desc.node0.unit = i;
+ desc.addr = vol->base_addr + i;
+ val = ctrl.un.value.level[i];
+ vol->level_val[i] = val;
+ _sioctl_ondesc_cb(&hdl->sioctl, &desc, val);
+ }
+ }
+ if (vol->mute_idx >= 0) {
+ ctrl.dev = vol->mute_idx;
+ ctrl.type = AUDIO_MIXER_ENUM;
+ if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) {
+ DPRINTF("mute read failed\n");
+ return 0;
+ }
+ desc.type = SIOCTL_SW;
+ desc.maxval = 1;
+ desc.node1.name[0] = 0;
+ desc.node1.unit = -1;
+ strlcpy(desc.func, "mute", SIOCTL_NAMEMAX);
+ strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX);
+ val = ctrl.un.ord ? 1 : 0;
+ vol->mute_val = val;
+ for (i = 0; i < vol->nch; i++) {
+ desc.node0.unit = i;
+ desc.addr = vol->base_addr + 32 + i;
+ _sioctl_ondesc_cb(&hdl->sioctl, &desc, val);
+ }
+ }
+ return 1;
+}
+
+static int
+updatevol(struct sioctl_sun_hdl *hdl, struct volume *vol, int idx)
+{
+ struct mixer_ctrl ctrl;
+ int val, i;
+
+ if (idx == vol->mute_idx)
+ ctrl.type = AUDIO_MIXER_ENUM;
+ else {
+ ctrl.type = AUDIO_MIXER_VALUE;
+ ctrl.un.value.num_channels = vol->nch;
+ }
+ ctrl.dev = idx;
+ if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) == -1) {
+ DPERROR("sioctl_sun_revents: ioctl\n");
+ hdl->sioctl.eof = 1;
+ return 0;
+ }
+ if (idx == vol->mute_idx) {
+ val = ctrl.un.ord ? 1 : 0;
+ if (vol->mute_val == val)
+ return 1;
+ vol->mute_val = val;
+ for (i = 0; i < vol->nch; i++) {
+ _sioctl_onval_cb(&hdl->sioctl,
+    vol->base_addr + 32 + i, val);
+ }
+ } else {
+ for (i = 0; i < vol->nch; i++) {
+ val = ctrl.un.value.level[i];
+ if (vol->level_val[i] == val)
+ continue;
+ vol->level_val[i] = val;
+ _sioctl_onval_cb(&hdl->sioctl,
+    vol->base_addr + i, val);
+ }
+ }
+ return 1;
+}
+
+int
+sioctl_sun_getfd(const char *str, unsigned int mode, int nbio)
+{
+ const char *p;
+ char path[DEVPATH_MAX];
+ unsigned int devnum;
+ int fd, flags;
+
+#ifdef DEBUG
+ _sndio_debug_init();
+#endif
+ p = _sndio_parsetype(str, "rsnd");
+ if (p == NULL) {
+ DPRINTF("sioctl_sun_getfd: %s: \"rsnd\" expected\n", str);
+ return -1;
+ }
+ switch (*p) {
+ case '/':
+ p++;
+ break;
+ default:
+ DPRINTF("sioctl_sun_getfd: %s: '/' expected\n", str);
+ return -1;
+ }
+ if (strcmp(p, "default") == 0) {
+ devnum = 0;
+ } else {
+ p = _sndio_parsenum(p, &devnum, 255);
+ if (p == NULL || *p != '\0') {
+ DPRINTF("sioctl_sun_getfd: %s: number expected after '/'\n", str);
+ return -1;
+ }
+ }
+ snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum);
+ if (mode == (SIOCTL_READ | SIOCTL_WRITE))
+ flags = O_RDWR;
+ else
+ flags = (mode & SIOCTL_WRITE) ? O_WRONLY : O_RDONLY;
+ while ((fd = open(path, flags | O_NONBLOCK | O_CLOEXEC)) < 0) {
+ if (errno == EINTR)
+ continue;
+ DPERROR(path);
+ return -1;
+ }
+ return fd;
+}
+
+struct sioctl_hdl *
+sioctl_sun_fdopen(int fd, unsigned int mode, int nbio)
+{
+ struct sioctl_sun_hdl *hdl;
+
+#ifdef DEBUG
+ _sndio_debug_init();
+#endif
+ hdl = malloc(sizeof(struct sioctl_sun_hdl));
+ if (hdl == NULL)
+ return NULL;
+ _sioctl_create(&hdl->sioctl, &sioctl_sun_ops, mode, nbio);
+ hdl->fd = fd;
+ init(hdl);
+ return (struct sioctl_hdl *)hdl;
+}
+
+struct sioctl_hdl *
+_sioctl_sun_open(const char *str, unsigned int mode, int nbio)
+{
+ struct sioctl_hdl *hdl;
+ int fd;
+
+ fd = sioctl_sun_getfd(str, mode, nbio);
+ if (fd < 0)
+ return NULL;
+ hdl = sioctl_sun_fdopen(fd, mode, nbio);
+ if (hdl != NULL)
+ return hdl;
+ while (close(fd) < 0 && errno == EINTR)
+ ; /* retry */
+ return NULL;
+}
+
+static void
+sioctl_sun_close(struct sioctl_hdl *addr)
+{
+ struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
+
+ close(hdl->fd);
+ free(hdl);
+}
+
+static int
+sioctl_sun_ondesc(struct sioctl_hdl *addr)
+{
+ struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
+
+ if (!scanvol(hdl, &hdl->output) ||
+    !scanvol(hdl, &hdl->input)) {
+ hdl->sioctl.eof = 1;
+ return 0;
+ }
+ _sioctl_ondesc_cb(&hdl->sioctl, NULL, 0);
+ return 1;
+}
+
+static int
+sioctl_sun_onval(struct sioctl_hdl *addr)
+{
+ return 1;
+}
+
+static int
+sioctl_sun_setctl(struct sioctl_hdl *arg, unsigned int addr, unsigned int val)
+{
+ struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
+
+ if (!setvol(hdl, &hdl->output, addr, val) ||
+    !setvol(hdl, &hdl->input, addr, val)) {
+ hdl->sioctl.eof = 1;
+ return 0;
+ }
+ return 1;
+}
+
+static int
+sioctl_sun_nfds(struct sioctl_hdl *addr)
+{
+ return 1;
+}
+
+static int
+sioctl_sun_pollfd(struct sioctl_hdl *addr, struct pollfd *pfd, int events)
+{
+ struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
+
+ pfd->fd = hdl->fd;
+ pfd->events = POLLIN;
+ hdl->events = events;
+ return 1;
+}
+
+static int
+sioctl_sun_revents(struct sioctl_hdl *arg, struct pollfd *pfd)
+{
+ struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
+ struct volume *vol;
+ int idx, n;
+
+ if (pfd->revents & POLLIN) {
+ while (1) {
+ n = read(hdl->fd, &idx, sizeof(int));
+ if (n == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ break;
+ DPERROR("read");
+ hdl->sioctl.eof = 1;
+ return POLLHUP;
+ }
+ if (n < sizeof(int)) {
+ DPRINTF("sioctl_sun_revents: short read\n");
+ hdl->sioctl.eof = 1;
+ return POLLHUP;
+ }
+
+ if (idx == hdl->output.level_idx ||
+    idx == hdl->output.mute_idx) {
+ vol = &hdl->output;
+ } else if (idx == hdl->input.level_idx ||
+    idx == hdl->input.mute_idx) {
+ vol = &hdl->input;
+ } else
+ continue;
+
+ if (!updatevol(hdl, vol, idx))
+ return POLLHUP;
+ }
+ }
+ return hdl->events & POLLOUT;
+}
Index: usr.bin/sndiod/Makefile
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/Makefile,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 Makefile
--- usr.bin/sndiod/Makefile 7 Jan 2016 07:41:01 -0000 1.5
+++ usr.bin/sndiod/Makefile 24 Feb 2020 06:03:30 -0000
@@ -1,8 +1,8 @@
 # $OpenBSD: Makefile,v 1.5 2016/01/07 07:41:01 ratchov Exp $
 
 PROG= sndiod
-SRCS= abuf.c dev.c dsp.c fdpass.c file.c listen.c midi.c miofile.c \
- opt.c siofile.c sndiod.c sock.c utils.c
+SRCS= abuf.c dev.c dev_sioctl.c dsp.c fdpass.c file.c listen.c \
+ midi.c miofile.c opt.c siofile.c sndiod.c sock.c utils.c
 MAN= sndiod.8
 CFLAGS+=-DDEBUG -I${.CURDIR}/../../lib/libsndio
 COPTS+= -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wundef
Index: usr.bin/sndiod/defs.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/defs.h,v
retrieving revision 1.4
diff -u -p -u -p -r1.4 defs.h
--- usr.bin/sndiod/defs.h 28 Jul 2019 09:44:10 -0000 1.4
+++ usr.bin/sndiod/defs.h 24 Feb 2020 06:03:30 -0000
@@ -37,9 +37,12 @@
 #define MODE_MIDIOUT 0x04 /* allowed to read midi */
 #define MODE_MIDIIN 0x08 /* allowed to write midi */
 #define MODE_MON 0x10 /* allowed to monitor */
+#define MODE_CTLREAD 0x100 /* allowed to read controls */
+#define MODE_CTLWRITE 0x200 /* allowed to change controls */
 #define MODE_RECMASK (MODE_REC | MODE_MON)
 #define MODE_AUDIOMASK (MODE_PLAY | MODE_REC | MODE_MON)
 #define MODE_MIDIMASK (MODE_MIDIIN | MODE_MIDIOUT)
+#define MODE_CTLMASK (MODE_CTLREAD | MODE_CTLWRITE)
 
 /*
  * underrun/overrun policies, must be the same as SIO_ constants
Index: usr.bin/sndiod/dev.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.c,v
retrieving revision 1.63
diff -u -p -u -p -r1.63 dev.c
--- usr.bin/sndiod/dev.c 10 Jan 2020 19:01:55 -0000 1.63
+++ usr.bin/sndiod/dev.c 24 Feb 2020 06:03:31 -0000
@@ -75,6 +75,7 @@ void dev_mmcstart(struct dev *);
 void dev_mmcstop(struct dev *);
 void dev_mmcloc(struct dev *, unsigned int);
 
+void slot_ctlname(struct slot *, char *, size_t);
 void slot_log(struct slot *);
 void slot_del(struct slot *);
 void slot_setvol(struct slot *, unsigned int);
@@ -91,6 +92,9 @@ void slot_write(struct slot *);
 void slot_read(struct slot *);
 int slot_skip(struct slot *);
 
+void ctl_node_log(struct ctl_node *);
+void ctl_log(struct ctl *);
+
 struct midiops dev_midiops = {
  dev_midi_imsg,
  dev_midi_omsg,
@@ -129,15 +133,22 @@ dev_log(struct dev *d)
 }
 
 void
+slot_ctlname(struct slot *s, char *name, size_t size)
+{
+ snprintf(name, size, "%s%u", s->name, s->unit);
+}
+
+void
 slot_log(struct slot *s)
 {
+ char name[CTL_NAMEMAX];
 #ifdef DEBUG
  static char *pstates[] = {
  "ini", "sta", "rdy", "run", "stp", "mid"
  };
 #endif
- log_puts(s->name);
- log_putu(s->unit);
+ slot_ctlname(s, name, CTL_NAMEMAX);
+ log_puts(name);
 #ifdef DEBUG
  if (log_level >= 3) {
  log_puts(" vol=");
@@ -365,10 +376,8 @@ dev_midi_slotdesc(struct dev *d, struct
  x.dev = SYSEX_DEV_ANY;
  x.id0 = SYSEX_AUCAT;
  x.id1 = SYSEX_AUCAT_SLOTDESC;
- if (*s->name != '\0') {
- snprintf((char *)x.u.slotdesc.name, SYSEX_NAMELEN,
-    "%s%u", s->name, s->unit);
- }
+ if (*s->name != '\0')
+ slot_ctlname(s, (char *)x.u.slotdesc.name, SYSEX_NAMELEN);
  x.u.slotdesc.chan = s - d->slot;
  x.u.slotdesc.end = SYSEX_END;
  midi_send(d->midi, (unsigned char *)&x, SYSEX_SIZE(slotdesc));
@@ -419,6 +428,7 @@ dev_midi_omsg(void *arg, unsigned char *
  if (chan >= DEV_NSLOT)
  return;
  slot_setvol(d->slot + chan, msg[2]);
+ dev_onval(d, CTLADDR_SLOT_LEVEL(chan), msg[2]);
  return;
  }
  x = (struct sysex *)msg;
@@ -429,8 +439,11 @@ dev_midi_omsg(void *arg, unsigned char *
  switch (x->type) {
  case SYSEX_TYPE_RT:
  if (x->id0 == SYSEX_CONTROL && x->id1 == SYSEX_MASTER) {
- if (len == SYSEX_SIZE(master))
+ if (len == SYSEX_SIZE(master)) {
  dev_master(d, x->u.master.coarse);
+ dev_onval(d, CTLADDR_MASTER,
+    x->u.master.coarse);
+ }
  return;
  }
  if (x->id0 != SYSEX_MMC)
@@ -1001,10 +1014,17 @@ dev_new(char *path, struct aparams *par,
  d->slot[i].serial = d->serial++;
  strlcpy(d->slot[i].name, "prog", SLOT_NAMEMAX);
  }
+ for (i = 0; i < DEV_NCTLSLOT; i++) {
+ d->ctlslot[i].ops = NULL;
+ d->ctlslot[i].dev = d;
+ d->ctlslot[i].mask = 0;
+ d->ctlslot[i].mode = 0;
+ }
  d->slot_list = NULL;
  d->master = MIDI_MAXCTL;
  d->mtc.origin = 0;
  d->tstate = MMC_STOP;
+ d->ctl_list = NULL;
  d->next = dev_list;
  dev_list = d;
  return d;
@@ -1097,6 +1117,9 @@ dev_allocbufs(struct dev *d)
 int
 dev_open(struct dev *d)
 {
+ int i;
+ char name[CTL_NAMEMAX];
+
  d->mode = d->reqmode;
  d->round = d->reqround;
  d->bufsz = d->reqbufsz;
@@ -1117,6 +1140,17 @@ dev_open(struct dev *d)
  }
  if (!dev_allocbufs(d))
  return 0;
+
+ for (i = 0; i < DEV_NSLOT; i++) {
+ slot_ctlname(&d->slot[i], name, CTL_NAMEMAX);
+ dev_addctl(d, "app", CTL_NUM,
+    CTLADDR_SLOT_LEVEL(i),
+    name, -1, "level",
+    NULL, -1, 127, d->slot[i].vol);
+ }
+ dev_addctl(d, "", CTL_NUM,
+    CTLADDR_MASTER, "output", -1, "level", NULL, -1, 127, d->master);
+
  d->pstate = DEV_INIT;
  return 1;
 }
@@ -1129,6 +1163,7 @@ dev_exitall(struct dev *d)
 {
  int i;
  struct slot *s;
+ struct ctlslot *c;
 
  for (s = d->slot, i = DEV_NSLOT; i > 0; i--, s++) {
  if (s->ops)
@@ -1136,6 +1171,12 @@ dev_exitall(struct dev *d)
  s->ops = NULL;
  }
  d->slot_list = NULL;
+
+ for (c = d->ctlslot, i = DEV_NCTLSLOT; i > 0; i--, c++) {
+ if (c->ops)
+ c->ops->exit(c->arg);
+ c->ops = NULL;
+ }
 }
 
 /*
@@ -1169,10 +1210,18 @@ dev_freebufs(struct dev *d)
 void
 dev_close(struct dev *d)
 {
+ struct ctl *c;
+
  dev_exitall(d);
  d->pstate = DEV_CFG;
  dev_sio_close(d);
  dev_freebufs(d);
+
+ /* there are no clients, just free remaining local controls */
+ while ((c = d->ctl_list) != NULL) {
+ d->ctl_list = c->next;
+ xfree(c);
+ }
 }
 
 /*
@@ -1183,6 +1232,7 @@ int
 dev_reopen(struct dev *d)
 {
  struct slot *s;
+ struct ctl *c, **pc;
  long long pos;
  unsigned int pstate;
  int delta;
@@ -1236,6 +1286,25 @@ dev_reopen(struct dev *d)
  }
  }
 
+ /* remove controls of old device */
+ pc = &d->ctl_list;
+ while ((c = *pc) != NULL) {
+ if (c->addr >= CTLADDR_END) {
+ c->refs_mask &= ~CTL_DEVMASK;
+ if (c->refs_mask == 0) {
+ *pc = c->next;
+ xfree(c);
+ continue;
+ }
+ c->type = CTL_NONE;
+ c->desc_mask = ~0;
+ }
+ pc = &c->next;
+ }
+
+ /* add new device controls */
+ dev_sioctl_open(d);
+
  /* start the device if needed */
  if (pstate == DEV_RUN)
  dev_wakeup(d);
@@ -1760,6 +1829,7 @@ found:
  }
  if (!dev_ref(d))
  return NULL;
+ dev_label(d, s - d->slot);
  if ((mode & d->mode) != mode) {
  if (log_level >= 1) {
  slot_log(s);
@@ -2096,4 +2166,279 @@ void
 slot_read(struct slot *s)
 {
  slot_skip_update(s);
+}
+
+/*
+ * allocate at control slot
+ */
+struct ctlslot *
+ctlslot_new(struct dev *d, struct ctlops *ops, void *arg)
+{
+ struct ctlslot *s;
+ struct ctl *c;
+ int i;
+
+ i = 0;
+ for (;;) {
+ if (i == DEV_NCTLSLOT)
+ return NULL;
+ s = d->ctlslot + i;
+ if (s->ops == NULL)
+ break;
+ i++;
+ }
+ s->dev = d;
+ s->mask = 1 << i;
+ if (!dev_ref(d))
+ return NULL;
+ s->ops = ops;
+ s->arg = arg;
+ for (c = d->ctl_list; c != NULL; c = c->next)
+ c->refs_mask |= s->mask;
+ return s;
+}
+
+/*
+ * free control slot
+ */
+void
+ctlslot_del(struct ctlslot *s)
+{
+ struct ctl *c, **pc;
+
+ pc = &s->dev->ctl_list;
+ while ((c = *pc) != NULL) {
+ c->refs_mask &= ~s->mask;
+ if (c->refs_mask == 0) {
+ *pc = c->next;
+ xfree(c);
+ } else
+ pc = &c->next;
+ }
+ s->ops = NULL;
+ dev_unref(s->dev);
+}
+
+void
+ctl_node_log(struct ctl_node *c)
+{
+ log_puts(c->name);
+ if (c->unit >= 0)
+ log_putu(c->unit);
+}
+
+void
+ctl_log(struct ctl *c)
+{
+ if (c->group[0] != 0) {
+ log_puts(c->group);
+ log_puts("/");
+ }
+ ctl_node_log(&c->node0);
+ log_puts(".");
+ log_puts(c->func);
+ log_puts("=");
+ switch (c->type) {
+ case CTL_NUM:
+ case CTL_SW:
+ log_putu(c->curval);
+ break;
+ case CTL_VEC:
+ case CTL_LIST:
+ ctl_node_log(&c->node1);
+ log_puts(":");
+ log_putu(c->curval);
+ }
+ log_puts(" at ");
+ log_putu(c->addr);
+}
+
+/*
+ * add a ctl
+ */
+struct ctl *
+dev_addctl(struct dev *d, char *gstr, int type, int addr,
+    char *str0, int unit0, char *func, char *str1, int unit1, int maxval, int val)
+{
+ struct ctl *c, **pc;
+ int i;
+
+ c = xmalloc(sizeof(struct ctl));
+ c->type = type;
+ strlcpy(c->func, func, CTL_NAMEMAX);
+ strlcpy(c->group, gstr, CTL_NAMEMAX);
+ strlcpy(c->node0.name, str0, CTL_NAMEMAX);
+ c->node0.unit = unit0;
+ if (c->type == CTL_VEC || c->type == CTL_LIST) {
+ strlcpy(c->node1.name, str1, CTL_NAMEMAX);
+ c->node1.unit = unit1;
+ } else
+ memset(&c->node1, 0, sizeof(struct ctl_node));
+ c->addr = addr;
+ c->maxval = maxval;
+ c->val_mask = ~0;
+ c->desc_mask = ~0;
+ c->curval = val;
+ c->dirty = 0;
+ c->refs_mask = 0;
+ for (i = 0; i < DEV_NCTLSLOT; i++) {
+ c->refs_mask |= CTL_DEVMASK;
+ if (d->ctlslot[i].ops != NULL)
+ c->refs_mask |= 1 << i;
+ }
+ for (pc = &d->ctl_list; *pc != NULL; pc = &(*pc)->next)
+ ; /* nothing */
+ c->next = NULL;
+ *pc = c;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ dev_log(d);
+ log_puts(": adding ");
+ ctl_log(c);
+ log_puts("\n");
+ }
+#endif
+ return c;
+}
+
+void
+dev_rmctl(struct dev *d, int addr)
+{
+ struct ctl *c, **pc;
+
+ pc = &d->ctl_list;
+ for (;;) {
+ c = *pc;
+ if (c == NULL)
+ return;
+ if (c->type != CTL_NONE && c->addr == addr)
+ break;
+ pc = &c->next;
+ }
+ c->type = CTL_NONE;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ dev_log(d);
+ log_puts(": removing ");
+ ctl_log(c);
+ log_puts(", refs_mask = 0x");
+ log_putx(c->refs_mask);
+ log_puts("\n");
+ }
+#endif
+ c->refs_mask &= ~CTL_DEVMASK;
+ if (c->refs_mask != 0)
+ return;
+ *pc = c->next;
+ xfree(c);
+}
+
+int
+dev_setctl(struct dev *d, int addr, int val)
+{
+ struct ctl *c;
+ int num;
+
+ c = d->ctl_list;
+ for (;;) {
+ if (c == NULL) {
+ if (log_level >= 3) {
+ dev_log(d);
+ log_puts(": ");
+ log_putu(addr);
+ log_puts(": no such ctl address\n");
+ }
+ return 0;
+ }
+ if (c->type != CTL_NONE && c->addr == addr)
+ break;
+ c = c->next;
+ }
+ if (c->curval == val) {
+ if (log_level >= 3) {
+ ctl_log(c);
+ log_puts(": already set\n");
+ }
+ return 1;
+ }
+ if (val < 0 || val > c->maxval) {
+ if (log_level >= 3) {
+ dev_log(d);
+ log_puts(": ");
+ log_putu(val);
+ log_puts(": ctl val out of bounds\n");
+ }
+ return 0;
+ }
+ if (addr >= CTLADDR_END) {
+ if (log_level >= 3) {
+ ctl_log(c);
+ log_puts(": marked as dirty\n");
+ }
+ c->dirty = 1;
+ dev_ref(d);
+ } else {
+ if (addr == CTLADDR_MASTER) {
+ dev_master(d, val);
+ dev_midi_master(d);
+ } else {
+ num = addr - CTLADDR_SLOT_LEVEL(0);
+ slot_setvol(d->slot + num, val);
+ dev_midi_vol(d, d->slot + num);
+ }
+ }
+ c->curval = val;
+ c->val_mask = ~0U;
+ return 1;
+}
+
+int
+dev_onval(struct dev *d, int addr, int val)
+{
+ struct ctl *c;
+
+ c = d->ctl_list;
+ for (;;) {
+ if (c == NULL)
+ return 0;
+ if (c->type != CTL_NONE && c->addr == addr)
+ break;
+ c = c->next;
+ }
+ c->curval = val;
+ c->val_mask = ~0U;
+ return 1;
+}
+
+void
+dev_label(struct dev *d, int i)
+{
+ struct ctl *c;
+ char name[CTL_NAMEMAX];
+
+ c = d->ctl_list;
+ for (;;) {
+ if (c == NULL)
+ return;
+ if (c->addr == CTLADDR_SLOT_LEVEL(i))
+ break;
+ c = c->next;
+ }
+ slot_ctlname(&d->slot[i], name, CTL_NAMEMAX);
+ if (strcmp(c->node0.name, name) == 0)
+ return;
+ strlcpy(c->node0.name, name, CTL_NAMEMAX);
+ c->desc_mask = ~0;
+}
+
+int
+dev_nctl(struct dev *d)
+{
+ struct ctl *c;
+ int n;
+
+ n = 0;
+ for (c = d->ctl_list; c != NULL; c = c->next)
+ n++;
+ return n;
 }
Index: usr.bin/sndiod/dev.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.h,v
retrieving revision 1.22
diff -u -p -u -p -r1.22 dev.h
--- usr.bin/sndiod/dev.h 21 Sep 2019 04:42:46 -0000 1.22
+++ usr.bin/sndiod/dev.h 24 Feb 2020 06:03:31 -0000
@@ -20,6 +20,11 @@
 #include "abuf.h"
 #include "dsp.h"
 #include "siofile.h"
+#include "dev_sioctl.h"
+
+#define CTLADDR_SLOT_LEVEL(n) (n)
+#define CTLADDR_MASTER (DEV_NSLOT)
+#define CTLADDR_END (DEV_NSLOT + 1)
 
 /*
  * audio stream state structure
@@ -28,13 +33,18 @@
 struct slotops
 {
  void (*onmove)(void *); /* clock tick */
- void (*onvol)(void *);        /* tell client vol changed */
+ void (*onvol)(void *); /* tell client vol changed */
  void (*fill)(void *); /* request to fill a play block */
  void (*flush)(void *); /* request to flush a rec block */
  void (*eof)(void *); /* notify that play drained */
  void (*exit)(void *); /* delete client */
 };
 
+struct ctlops
+{
+ void (*exit)(void *); /* delete client */
+};
+
 struct slot {
  struct slotops *ops; /* client callbacks */
  struct slot *next; /* next on the play list */
@@ -105,6 +115,44 @@ struct opt {
 };
 
 /*
+ * subset of channels of a stream
+ */
+
+struct ctl {
+ struct ctl *next;
+#define CTL_NONE 0 /* deleted */
+#define CTL_NUM 2 /* number (aka integer value) */
+#define CTL_SW 3 /* on/off switch, only bit 7 counts */
+#define CTL_VEC 4 /* number, element of vector */
+#define CTL_LIST 5 /* switch, element of a list */
+ unsigned int type; /* one of above */
+ unsigned int addr; /* control address */
+#define CTL_NAMEMAX 16 /* max name lenght */
+ char func[CTL_NAMEMAX]; /* parameter function name */
+ char group[CTL_NAMEMAX]; /* group aka namespace */
+ struct ctl_node {
+ char name[CTL_NAMEMAX]; /* stream name */
+ int unit;
+ } node0, node1; /* affected channels */
+#define CTL_DEVMASK (1 << 31)
+#define CTL_SLOTMASK(i) (1 << (i))
+ unsigned int val_mask;
+ unsigned int desc_mask;
+ unsigned int refs_mask;
+ unsigned int maxval;
+ unsigned int curval;
+ int dirty;
+};
+
+struct ctlslot {
+ struct ctlops *ops;
+ void *arg;
+ struct dev *dev;
+ unsigned int mask;
+ unsigned int mode;
+};
+
+/*
  * audio device with plenty of slots
  */
 struct dev {
@@ -117,6 +165,7 @@ struct dev {
  * audio device (while opened)
  */
  struct dev_sio sio;
+ struct dev_sioctl sioctl;
  struct aparams par; /* encoding */
  int pchan, rchan; /* play & rec channels */
  adata_t *rbuf; /* rec buffer */
@@ -195,6 +244,14 @@ struct dev {
 #define MMC_RUN 3 /* started */
  unsigned int tstate; /* one of above */
  unsigned int master; /* master volume controller */
+
+ /*
+ * control
+ */
+
+ struct ctl *ctl_list;
+#define DEV_NCTLSLOT 8
+ struct ctlslot ctlslot[DEV_NCTLSLOT];
 };
 
 extern struct dev *dev_list;
@@ -241,5 +298,20 @@ void slot_start(struct slot *);
 void slot_stop(struct slot *);
 void slot_read(struct slot *);
 void slot_write(struct slot *);
+
+/*
+ * control related functions
+ */
+void ctl_log(struct ctl *);
+struct ctlslot *ctlslot_new(struct dev *, struct ctlops *, void *);
+void ctlslot_del(struct ctlslot *);
+int dev_setctl(struct dev *, int, int);
+int dev_onval(struct dev *, int, int);
+int dev_nctl(struct dev *);
+void dev_label(struct dev *, int);
+struct ctl *dev_addctl(struct dev *, char *, int, int,
+    char *, int, char *, char *, int, int, int);
+void dev_rmctl(struct dev *, int);
+int dev_makeunit(struct dev *, char *);
 
 #endif /* !defined(DEV_H) */
Index: usr.bin/sndiod/dev_sioctl.c
===================================================================
RCS file: usr.bin/sndiod/dev_sioctl.c
diff -N usr.bin/sndiod/dev_sioctl.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.bin/sndiod/dev_sioctl.c 24 Feb 2020 06:03:31 -0000
@@ -0,0 +1,200 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[hidden email]>
+ *
+ * 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 <sys/time.h>
+#include <sys/types.h>
+
+#include <poll.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "abuf.h"
+#include "defs.h"
+#include "dev.h"
+#include "dsp.h"
+#include "file.h"
+#include "dev_sioctl.h"
+#include "utils.h"
+
+void dev_sioctl_ondesc(void *, struct sioctl_desc *, int);
+void dev_sioctl_onval(void *, unsigned int, unsigned int);
+int dev_sioctl_pollfd(void *, struct pollfd *);
+int dev_sioctl_revents(void *, struct pollfd *);
+void dev_sioctl_in(void *);
+void dev_sioctl_out(void *);
+void dev_sioctl_hup(void *);
+
+struct fileops dev_sioctl_ops = {
+ "sioctl",
+ dev_sioctl_pollfd,
+ dev_sioctl_revents,
+ dev_sioctl_in,
+ dev_sioctl_out,
+ dev_sioctl_hup
+};
+
+void
+dev_sioctl_ondesc(void *arg, struct sioctl_desc *desc, int val)
+{
+#define GROUP_PREFIX "hw"
+ char group_buf[CTL_NAMEMAX], *group;
+ struct dev *d = arg;
+ int addr;
+
+ if (desc == NULL)
+ return;
+ addr = CTLADDR_END + desc->addr;
+ dev_rmctl(d, addr);
+
+ /*
+ * prefix group names we use (top-level and "app") with "hw."
+ * to ensure that all controls have unique names when multiple
+ * sndiod's are chained
+ */
+ if (desc->group[0] == 0)
+ group = GROUP_PREFIX;
+ else {
+ group = group_buf;
+ if (snprintf(group_buf, CTL_NAMEMAX, GROUP_PREFIX "/%s",
+    desc->group) >= CTL_NAMEMAX)
+ return;
+ }
+
+ dev_addctl(d, group, desc->type, addr,
+    desc->node0.name, desc->node0.unit, desc->func,
+    desc->node1.name, desc->node1.unit, desc->maxval, val);
+}
+
+void
+dev_sioctl_onval(void *arg, unsigned int addr, unsigned int val)
+{
+ struct dev *d = arg;
+ struct ctl *c;
+
+ addr += CTLADDR_END;
+
+ dev_log(d);
+ log_puts(": onctl: addr = ");
+ log_putu(addr);
+ log_puts(", val = ");
+ log_putu(val);
+ log_puts("\n");
+
+ for (c = d->ctl_list; c != NULL; c = c->next) {
+ if (c->addr != addr)
+ continue;
+ ctl_log(c);
+ log_puts(": new value -> ");
+ log_putu(val);
+ log_puts("\n");
+ c->val_mask = ~0U;
+ c->curval = val;
+ }
+}
+
+/*
+ * open the control device.
+ */
+void
+dev_sioctl_open(struct dev *d)
+{
+ if (d->sioctl.hdl == NULL)
+ return;
+ sioctl_ondesc(d->sioctl.hdl, dev_sioctl_ondesc, d);
+ sioctl_onval(d->sioctl.hdl, dev_sioctl_onval, d);
+ d->sioctl.file = file_new(&dev_sioctl_ops, d, "mix",
+    sioctl_nfds(d->sioctl.hdl));
+}
+
+/*
+ * close the control device.
+ */
+void
+dev_sioctl_close(struct dev *d)
+{
+ if (d->sioctl.hdl == NULL)
+ return;
+ file_del(d->sioctl.file);
+}
+
+int
+dev_sioctl_pollfd(void *arg, struct pollfd *pfd)
+{
+ struct dev *d = arg;
+ struct ctl *c;
+ int events = 0;
+
+ for (c = d->ctl_list; c != NULL; c = c->next) {
+ if (c->dirty)
+ events |= POLLOUT;
+ }
+ return sioctl_pollfd(d->sioctl.hdl, pfd, events);
+}
+
+int
+dev_sioctl_revents(void *arg, struct pollfd *pfd)
+{
+ struct dev *d = arg;
+
+ return sioctl_revents(d->sioctl.hdl, pfd);
+}
+
+void
+dev_sioctl_in(void *arg)
+{
+}
+
+void
+dev_sioctl_out(void *arg)
+{
+ struct dev *d = arg;
+ struct ctl *c;
+ int cnt;
+
+ /*
+ * for each dirty ctl, call sioctl_setval() and dev_unref(). As
+ * dev_unref() may destroy the ctl_list, we must call it after
+ * we've finished iterating on it.
+ */
+ cnt = 0;
+ for (c = d->ctl_list; c != NULL; c = c->next) {
+ if (!c->dirty)
+ continue;
+ if (!sioctl_setval(d->sioctl.hdl,
+ c->addr - CTLADDR_END, c->curval)) {
+ ctl_log(c);
+ log_puts(": set failed\n");
+ break;
+ }
+ if (log_level >= 2) {
+ ctl_log(c);
+ log_puts(": changed\n");
+ }
+ c->dirty = 0;
+ cnt++;
+ }
+ while (cnt-- > 0)
+ dev_unref(d);
+}
+
+void
+dev_sioctl_hup(void *arg)
+{
+ struct dev *d = arg;
+
+ dev_sioctl_close(d);
+}
Index: usr.bin/sndiod/dev_sioctl.h
===================================================================
RCS file: usr.bin/sndiod/dev_sioctl.h
diff -N usr.bin/sndiod/dev_sioctl.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.bin/sndiod/dev_sioctl.h 24 Feb 2020 06:03:31 -0000
@@ -0,0 +1,32 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[hidden email]>
+ *
+ * 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.
+ */
+#ifndef DEV_SIOCTL_H
+#define DEV_SIOCTL_H
+
+#include "file.h"
+
+struct dev;
+
+struct dev_sioctl {
+ struct sioctl_hdl *hdl;
+ struct file *file;
+};
+
+void dev_sioctl_open(struct dev *);
+void dev_sioctl_close(struct dev *);
+
+#endif /* !defined(DEV_SIOCTL_H) */
Index: usr.bin/sndiod/fdpass.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/fdpass.c,v
retrieving revision 1.8
diff -u -p -u -p -r1.8 fdpass.c
--- usr.bin/sndiod/fdpass.c 23 Jan 2020 05:40:09 -0000 1.8
+++ usr.bin/sndiod/fdpass.c 24 Feb 2020 06:03:32 -0000
@@ -32,6 +32,7 @@
 struct fdpass_msg {
 #define FDPASS_OPEN_SND 0 /* open an audio device */
 #define FDPASS_OPEN_MIDI 1 /* open a midi port */
+#define FDPASS_OPEN_CTL 2 /* open an audio control device */
 #define FDPASS_RETURN 3 /* return after above commands */
  unsigned int cmd; /* one of above */
  unsigned int num; /* audio device or midi port number */
@@ -287,6 +288,22 @@ fdpass_mio_open(int num, int idx, unsign
  return mio_rmidi_fdopen(fd, mode, 1);
 }
 
+struct sioctl_hdl *
+fdpass_sioctl_open(int num, int idx, unsigned int mode)
+{
+ int fd;
+
+ if (fdpass_peer == NULL)
+ return NULL;
+ if (!fdpass_send(fdpass_peer, FDPASS_OPEN_CTL, num, idx, mode, -1))
+ return NULL;
+ if (!fdpass_waitret(fdpass_peer, &fd))
+ return NULL;
+ if (fd < 0)
+ return NULL;
+ return sioctl_sun_fdopen(fd, mode, 1);
+}
+
 void
 fdpass_in_worker(void *arg)
 {
@@ -345,6 +362,23 @@ fdpass_in_helper(void *arg)
  return;
  }
  fd = mio_rmidi_getfd(path, mode, 1);
+ break;
+ case FDPASS_OPEN_CTL:
+ d = dev_bynum(num);
+ if (d == NULL || !(mode & (SIOCTL_READ | SIOCTL_WRITE))) {
+ if (log_level >= 1) {
+ fdpass_log(f);
+ log_puts(": bad audio control device\n");
+ }
+ fdpass_close(f);
+ return;
+ }
+ path = namelist_byindex(&d->path_list, idx);
+ if (path == NULL) {
+ fdpass_close(f);
+ return;
+ }
+ fd = sioctl_sun_getfd(path, mode, 1);
  break;
  default:
  fdpass_close(f);
Index: usr.bin/sndiod/fdpass.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/fdpass.h,v
retrieving revision 1.2
diff -u -p -u -p -r1.2 fdpass.h
--- usr.bin/sndiod/fdpass.h 23 Jan 2020 05:40:09 -0000 1.2
+++ usr.bin/sndiod/fdpass.h 24 Feb 2020 06:03:32 -0000
@@ -27,5 +27,6 @@ extern struct fdpass *fdpass_peer;
 
 struct sio_hdl *fdpass_sio_open(int, int, unsigned int);
 struct mio_hdl *fdpass_mio_open(int, int, unsigned int);
+struct sioctl_hdl *fdpass_sioctl_open(int, int, unsigned int);
 
 #endif /* !defined(FDPASS_H) */
Index: usr.bin/sndiod/siofile.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/siofile.c,v
retrieving revision 1.17
diff -u -p -u -p -r1.17 siofile.c
--- usr.bin/sndiod/siofile.c 23 Jan 2020 05:40:09 -0000 1.17
+++ usr.bin/sndiod/siofile.c 24 Feb 2020 06:03:32 -0000
@@ -26,6 +26,7 @@
 #include "abuf.h"
 #include "defs.h"
 #include "dev.h"
+#include "dev_sioctl.h"
 #include "dsp.h"
 #include "fdpass.h"
 #include "file.h"
@@ -88,10 +89,11 @@ dev_sio_timeout(void *arg)
  * open the device using one of the provided paths
  */
 static struct sio_hdl *
-dev_sio_openlist(struct dev *d, unsigned int mode)
+dev_sio_openlist(struct dev *d, unsigned int mode, struct sioctl_hdl **rctlhdl)
 {
  struct name *n;
  struct sio_hdl *hdl;
+ struct sioctl_hdl *ctlhdl;
  int idx;
 
  idx = 0;
@@ -107,6 +109,15 @@ dev_sio_openlist(struct dev *d, unsigned
  log_puts(n->str);
  log_puts("\n");
  }
+ ctlhdl = fdpass_sioctl_open(d->num, idx,
+    SIOCTL_READ | SIOCTL_WRITE);
+ if (ctlhdl == NULL) {
+ if (log_level >= 1) {
+ dev_log(d);
+ log_puts(": no control device\n");
+ }
+ }
+ *rctlhdl = ctlhdl;
  return hdl;
  }
  n = n->next;
@@ -124,15 +135,16 @@ dev_sio_open(struct dev *d)
  struct sio_par par;
  unsigned int mode = d->mode & (MODE_PLAY | MODE_REC);
 
- d->sio.hdl = dev_sio_openlist(d, mode);
+ d->sio.hdl = dev_sio_openlist(d, mode, &d->sioctl.hdl);
  if (d->sio.hdl == NULL) {
  if (mode != (SIO_PLAY | SIO_REC))
  return 0;
- d->sio.hdl = dev_sio_openlist(d, SIO_PLAY);
+ d->sio.hdl = dev_sio_openlist(d, SIO_PLAY, &d->sioctl.hdl);
  if (d->sio.hdl != NULL)
  mode = SIO_PLAY;
  else {
- d->sio.hdl = dev_sio_openlist(d, SIO_REC);
+ d->sio.hdl = dev_sio_openlist(d,
+    SIO_REC, &d->sioctl.hdl);
  if (d->sio.hdl != NULL)
  mode = SIO_REC;
  else
@@ -245,9 +257,14 @@ dev_sio_open(struct dev *d)
  sio_onmove(d->sio.hdl, dev_sio_onmove, d);
  d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(d->sio.hdl));
  timo_set(&d->sio.watchdog, dev_sio_timeout, d);
+ dev_sioctl_open(d);
  return 1;
  bad_close:
  sio_close(d->sio.hdl);
+ if (d->sioctl.hdl) {
+ sioctl_close(d->sioctl.hdl);
+ d->sioctl.hdl = NULL;
+ }
  return 0;
 }
 
@@ -259,10 +276,11 @@ dev_sio_open(struct dev *d)
 int
 dev_sio_reopen(struct dev *d)
 {
+ struct sioctl_hdl *ctlhdl;
  struct sio_par par;
  struct sio_hdl *hdl;
 
- hdl = dev_sio_openlist(d, d->mode & (MODE_PLAY | MODE_REC));
+ hdl = dev_sio_openlist(d, d->mode & (MODE_PLAY | MODE_REC), &ctlhdl);
  if (hdl == NULL) {
  if (log_level >= 1) {
  dev_log(d);
@@ -303,6 +321,11 @@ dev_sio_reopen(struct dev *d)
  timo_del(&d->sio.watchdog);
  file_del(d->sio.file);
  sio_close(d->sio.hdl);
+ dev_sioctl_close(d);
+ if (d->sioctl.hdl) {
+ sioctl_close(d->sioctl.hdl);
+ d->sioctl.hdl = NULL;
+ }
 
  /* update parameters */
  d->par.bits = par.bits;
@@ -316,17 +339,21 @@ dev_sio_reopen(struct dev *d)
  d->rchan = par.rchan;
 
  d->sio.hdl = hdl;
+ d->sioctl.hdl = ctlhdl;
  d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(hdl));
  sio_onmove(hdl, dev_sio_onmove, d);
  return 1;
 bad_close:
  sio_close(hdl);
+ if (ctlhdl)
+ sioctl_close(ctlhdl);
  return 0;
 }
 
 void
 dev_sio_close(struct dev *d)
 {
+ dev_sioctl_close(d);
 #ifdef DEBUG
  if (log_level >= 3) {
  dev_log(d);
@@ -336,6 +363,10 @@ dev_sio_close(struct dev *d)
  timo_del(&d->sio.watchdog);
  file_del(d->sio.file);
  sio_close(d->sio.hdl);
+ if (d->sioctl.hdl) {
+ sioctl_close(d->sioctl.hdl);
+ d->sioctl.hdl = NULL;
+ }
 }
 
 void
Index: usr.bin/sndiod/sndiod.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.c,v
retrieving revision 1.37
diff -u -p -u -p -r1.37 sndiod.c
--- usr.bin/sndiod/sndiod.c 21 Sep 2019 04:52:07 -0000 1.37
+++ usr.bin/sndiod/sndiod.c 24 Feb 2020 06:03:32 -0000
@@ -413,8 +413,10 @@ start_helper(int background)
  err(1, "cannot drop privileges");
  }
  for (d = dev_list; d != NULL; d = d->next) {
- for (n = d->path_list; n != NULL; n = n->next)
+ for (n = d->path_list; n != NULL; n = n->next) {
  dounveil(n->str, "rsnd/", "/dev/audio");
+ dounveil(n->str, "rsnd/", "/dev/audioctl");
+ }
  }
  for (p = port_list; p != NULL; p = p->next) {
  for (n = p->path_list; n != NULL; n = n->next)
Index: usr.bin/sndiod/sock.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sock.c,v
retrieving revision 1.31
diff -u -p -u -p -r1.31 sock.c
--- usr.bin/sndiod/sock.c 12 Jul 2019 06:30:55 -0000 1.31
+++ usr.bin/sndiod/sock.c 24 Feb 2020 06:03:32 -0000
@@ -32,6 +32,8 @@
 #include "sock.h"
 #include "utils.h"
 
+#define SOCK_CTLDESC_SIZE 16 /* number of entries in s->ctldesc */
+
 void sock_log(struct sock *);
 void sock_close(struct sock *);
 void sock_slot_fill(void *);
@@ -88,6 +90,10 @@ struct midiops sock_midiops = {
  sock_exit
 };
 
+struct ctlops sock_ctlops = {
+ sock_exit
+};
+
 struct sock *sock_list = NULL;
 unsigned int sock_sesrefs = 0; /* connections to the session */
 uint8_t sock_sescookie[AMSG_COOKIELEN]; /* owner of the session */
@@ -103,7 +109,10 @@ sock_log(struct sock *f)
  slot_log(f->slot);
  else if (f->midi)
  midi_log(f->midi);
- else
+ else if (f->ctlslot) {
+ log_puts("ctlslot");
+ log_putu(f->ctlslot - f->ctlslot->dev->ctlslot);
+ } else
  log_puts("sock");
 #ifdef DEBUG
  if (log_level >= 3) {
@@ -150,6 +159,11 @@ sock_close(struct sock *f)
  port_unref(f->port);
  f->port = NULL;
  }
+ if (f->ctlslot) {
+ ctlslot_del(f->ctlslot);
+ f->ctlslot = NULL;
+ xfree(f->ctldesc);
+ }
  file_del(f->file);
  close(f->fd);
  file_slowaccept = 0;
@@ -277,6 +291,7 @@ sock_new(int fd)
  f->slot = NULL;
  f->port = NULL;
  f->midi = NULL;
+ f->ctlslot = NULL;
  f->tickpending = 0;
  f->fillpending = 0;
  f->stoppending = 0;
@@ -286,6 +301,8 @@ sock_new(int fd)
  f->rtodo = sizeof(struct amsg);
  f->wmax = f->rmax = 0;
  f->lastvol = -1;
+ f->ctlops = 0;
+ f->ctlsyncpending = 0;
  f->file = file_new(&sock_fileops, f, "sock", 1);
  f->fd = fd;
  if (f->file == NULL) {
@@ -547,6 +564,11 @@ sock_wdata(struct sock *f)
  data = abuf_rgetblk(&f->slot->sub.buf, &count);
  else if (f->midi)
  data = abuf_rgetblk(&f->midi->obuf, &count);
+ else {
+ data = (unsigned char *)f->ctldesc +
+    (f->wsize - f->wtodo);
+ count = f->wtodo;
+ }
  if (count > f->wtodo)
  count = f->wtodo;
  n = sock_fdwrite(f, data, count);
@@ -800,6 +822,9 @@ sock_hello(struct sock *f)
  case MODE_REC:
  case MODE_PLAY:
  case MODE_PLAY | MODE_REC:
+ case MODE_CTLREAD:
+ case MODE_CTLWRITE:
+ case MODE_CTLREAD | MODE_CTLWRITE:
  break;
  default:
 #ifdef DEBUG
@@ -837,6 +862,31 @@ sock_hello(struct sock *f)
  return 0;
  return 1;
  }
+ if (mode & MODE_CTLMASK) {
+ d = dev_bynum(p->devnum);
+ if (d == NULL) {
+ if (log_level >= 2) {
+ sock_log(f);
+ log_puts(": ");
+ log_putu(p->devnum);
+ log_puts(": no such device\n");
+ }
+ return 0;
+ }
+ f->ctlslot = ctlslot_new(d, &sock_ctlops, f);
+ if (f->ctlslot == NULL) {
+ if (log_level >= 2) {
+ sock_log(f);
+ log_puts(": couldn't get slot\n");
+ }
+ return 0;
+ }
+ f->ctldesc = xmalloc(SOCK_CTLDESC_SIZE *
+    sizeof(struct amsg_ctl_desc));
+ f->ctlops = 0;
+ f->ctlsyncpending = 0;
+ return 1;
+ }
  d = dev_bynum(p->devnum);
  if (d == NULL)
  return 0;
@@ -856,6 +906,7 @@ sock_hello(struct sock *f)
 int
 sock_execmsg(struct sock *f)
 {
+ struct ctl *c;
  struct slot *s = f->slot;
  struct amsg *m = &f->rmsg;
  unsigned char *data;
@@ -1153,6 +1204,81 @@ sock_execmsg(struct sock *f)
  f->lastvol = ctl; /* dont trigger feedback message */
  slot_setvol(s, ctl);
  dev_midi_vol(s->dev, s);
+ dev_onval(s->dev,
+    CTLADDR_SLOT_LEVEL(f->slot - s->dev->slot), ctl);
+ break;
+ case AMSG_CTLSUB:
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": CTLSUB message, desc = ");
+ log_putx(m->u.ctlsub.desc);
+ log_puts(", val = ");
+ log_putx(m->u.ctlsub.val);
+ log_puts("\n");
+ }
+#endif
+ if (f->pstate != SOCK_INIT || f->ctlslot == NULL) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": CTLSUB, wrong state\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ if (m->u.ctlsub.desc) {
+ if (!(f->ctlops & SOCK_CTLDESC)) {
+ ctl = f->ctlslot->mask;
+ c = f->ctlslot->dev->ctl_list;
+ while (c != NULL) {
+ c->desc_mask |= ctl;
+ c = c->next;
+ }
+ }
+ f->ctlops |= SOCK_CTLDESC;
+ f->ctlsyncpending = 1;
+ } else
+ f->ctlops &= ~SOCK_CTLDESC;
+ if (m->u.ctlsub.val) {
+ f->ctlops |= SOCK_CTLVAL;
+ } else
+ f->ctlops &= ~SOCK_CTLVAL;
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+ break;
+ case AMSG_CTLSET:
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": CTLSET message\n");
+ }
+#endif
+ if (f->pstate < SOCK_INIT || f->ctlslot == NULL) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": CTLSET, wrong state\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ if (!dev_setctl(f->ctlslot->dev,
+ ntohs(m->u.ctlset.addr),
+ ntohs(m->u.ctlset.val))) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": CTLSET, wrong addr/val\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ f->rtodo = sizeof(struct amsg);
+ f->rstate = SOCK_RMSG;
  break;
  case AMSG_AUTH:
 #ifdef DEBUG
@@ -1241,7 +1367,9 @@ sock_execmsg(struct sock *f)
 int
 sock_buildmsg(struct sock *f)
 {
- unsigned int size;
+ unsigned int size, mask;
+ struct amsg_ctl_desc *desc;
+ struct ctl *c, **pc;
 
  /*
  * If pos changed (or initial tick), build a MOVE message.
@@ -1378,6 +1506,106 @@ sock_buildmsg(struct sock *f)
  f->wmsg.cmd = htonl(AMSG_STOP);
  f->wtodo = sizeof(struct amsg);
  f->wstate = SOCK_WMSG;
+ return 1;
+ }
+
+ /*
+ * XXX: add a flag indicating if there are changes
+ * in controls not seen by this client, rather
+ * than walking through the full list of control
+ * searching for the {desc,val}_mask bits
+ */
+ if (f->ctlslot && (f->ctlops & SOCK_CTLDESC)) {
+ desc = f->ctldesc;
+ mask = f->ctlslot->mask;
+ size = 0;
+ pc = &f->ctlslot->dev->ctl_list;
+ while ((c = *pc) != NULL) {
+ if ((c->desc_mask & mask) == 0 ||
+    (c->refs_mask & mask) == 0) {
+ pc = &c->next;
+ continue;
+ }
+ if (size == SOCK_CTLDESC_SIZE *
+ sizeof(struct amsg_ctl_desc))
+ break;
+ c->desc_mask &= ~mask;
+ c->val_mask &= ~mask;
+ strlcpy(desc->group, c->group,
+    AMSG_CTL_NAMEMAX);
+ strlcpy(desc->node0.name, c->node0.name,
+    AMSG_CTL_NAMEMAX);
+ desc->node0.unit = ntohs(c->node0.unit);
+ strlcpy(desc->node1.name, c->node1.name,
+    AMSG_CTL_NAMEMAX);
+ desc->node1.unit = ntohs(c->node1.unit);
+ desc->type = c->type;
+ strlcpy(desc->func, c->func, AMSG_CTL_NAMEMAX);
+ desc->addr = htons(c->addr);
+ desc->maxval = htons(c->maxval);
+ desc->curval = htons(c->curval);
+ size += sizeof(struct amsg_ctl_desc);
+ desc++;
+
+ /* if this is a deleted entry unref it */
+ if (c->type == CTL_NONE) {
+ c->refs_mask &= ~mask;
+ if (c->refs_mask == 0) {
+ *pc = c->next;
+ xfree(c);
+ continue;
+ }
+ }
+
+ pc = &c->next;
+ }
+ if (size > 0) {
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = htonl(AMSG_DATA);
+ f->wmsg.u.data.size = htonl(size);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": building control DATA message\n");
+ }
+#endif
+ return 1;
+ }
+ }
+ if (f->ctlslot && (f->ctlops & SOCK_CTLVAL)) {
+ mask = f->ctlslot->mask;
+ for (c = f->ctlslot->dev->ctl_list; c != NULL; c = c->next) {
+ if ((c->val_mask & mask) == 0)
+ continue;
+ c->val_mask &= ~mask;
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = htonl(AMSG_CTLSET);
+ f->wmsg.u.ctlset.addr = htons(c->addr);
+ f->wmsg.u.ctlset.val = htons(c->curval);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": building CTLSET message\n");
+ }
+#endif
+ return 1;
+ }
+ }
+ if (f->ctlslot && f->ctlsyncpending) {
+ f->ctlsyncpending = 0;
+ f->wmsg.cmd = htonl(AMSG_CTLSYNC);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": building CTLSYNC message\n");
+ }
+#endif
  return 1;
  }
 #ifdef DEBUG
Index: usr.bin/sndiod/sock.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sock.h,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 sock.h
--- usr.bin/sndiod/sock.h 26 Jun 2018 07:13:54 -0000 1.5
+++ usr.bin/sndiod/sock.h 24 Feb 2020 06:03:32 -0000
@@ -58,6 +58,12 @@ struct sock {
  struct slot *slot; /* audio device slot number */
  struct midi *midi; /* midi endpoint */
  struct port *port; /* midi port */
+ struct ctlslot *ctlslot;
+ struct amsg_ctl_desc *ctldesc; /* temporary buffer */
+#define SOCK_CTLDESC 1 /* dump desc and send changes */
+#define SOCK_CTLVAL 2 /* send value changes */
+ unsigned int ctlops; /* bitmap of above */
+ int ctlsyncpending; /* CTLSYNC waiting to be transmitted */
 };
 
 struct sock *sock_new(int fd);
Index: usr.bin/Makefile
===================================================================
RCS file: /cvs/src/usr.bin/Makefile,v
retrieving revision 1.162
diff -u -p -u -p -r1.162 Makefile
--- usr.bin/Makefile 7 Feb 2020 10:19:50 -0000 1.162
+++ usr.bin/Makefile 25 Feb 2020 06:47:34 -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 25 Feb 2020 06:47:34 -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 25 Feb 2020 06:47:34 -0000
@@ -0,0 +1,146 @@
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2014-2020 Alexandre Ratchov <[hidden email]>
+.\"
+.\" 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
+.Fl d
+.Sh DESCRIPTION
+The
+.Nm
+utility can display or change parameters of
+.Xr sndio 7
+audio devices.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Dump the raw list of available parameters and exit.
+Useful as a debugging tool.
+.It Fl f Ar device
+Use this
+.Xr sndio 7
+audio device.
+.It Fl i
+Display characteristics of requested parameters
+instead of their values.
+.It Fl m
+Monitor and display audio parameter changes.
+.It Fl v
+Enable verbose mode, a.k.a. multi-channel mode.
+By default parameters affecting different channels
+of the same stream are disguised as a single mono
+parameter to hide details that are not essential.
+.El
+.Pp
+If no commands are specified all valid parameters are displayed on
+.Em stdout .
+Unless
+.Fl d ,
+.Fl m ,
+or
+.Fl i
+are used, displayed parameters are valid commands.
+The set of available controls depends on the control device.
+.Pp
+Commands use the following two formats to display and set
+parameters respectively:
+.Pp
+.Dl group/stream[channel].function
+.Dl group/stream[channel].function=value
+.Pp
+On the left-hand side are specified the optional parameter group,
+the affected stream name, and the optional channel number.
+Examples of left-hand side terms:
+.Pp
+.Dl output.level
+.Dl hw/spkr[6].mute
+.Pp
+There are 4 parameter types: switches, numbers, selectors, and vectors.
+.Pp
+Numbers are specified in decimal and follow the same semantics
+as MIDI controllers.
+Values are in the 0..127 range and 64 is the neutral state (if applicable).
+Two-state controls (switches) take either 0 or 1 as value,
+typically corresponding to the
+.Em off
+and
+.Em on
+states respectively.
+.Pp
+If a decimal is prefixed by the plus (minus) sign then
+the given value is added to (subtracted from) the
+current value of the control.
+If
+.Qq \&!
+is used instead of a number, then the switch is toggled.
+Examples:
+.Pp
+.Dl hw/spkr.level=85
+.Dl hw/spkr.level=+10
+.Dl hw/spkr.mute=0
+.Dl hw/spkr.mute=!
+.Pp
+Selector values are substreams; they are specified
+as the stream name followed by an optional channel
+number.
+If no channel number is specified, the same
+number as the stream specified on the left-hand side is used.
+For instance the following are equivalent:
+.Pp
+.Dl hw/record[1].source=mic
+.Dl hw/record[1].source=mic1
+.Pp
+Vectors are arrays of numbers.
+Values are specified as comma-separated components.
+Each component is a substream, followed by
+a colon, followed by a number.
+If the colon and the number are omitted, then 127 is
+assumed.
+If a component is missing, then 0 is assumed.
+Example:
+.Pp
+.Dl hw/monitor.mix=play:120,linein:85
+.Dl hw/record.source=mic,linein
+.Pp
+Numbers are specified as discussed above.
+Note that a vector of switches is equivalent to
+a list.
+.Sh EXAMPLES
+The following will set all
+.Ar level
+parameters that control the
+.Ar spkr
+stream to zero.
+.Pp
+.Dl $ sndioctl hw/spkr.level=0
+.Pp
+The following commands are equivalent:
+.Pp
+.Dl $ sndioctl hw/record[0].source=mic0 hw/record[1].source=mic1
+.Dl $ sndioctl hw/record.source=mic
+.Sh SEE ALSO
+.Xr sioctl_open 3
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 25 Feb 2020 06:47:35 -0000
@@ -0,0 +1,939 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[hidden email]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <errno.h>
+#include <poll.h>
+#include <sndio.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+struct info {
+ struct info *next;
+ struct sioctl_desc desc;
+ unsigned ctladdr;
+#define MODE_IGNORE 0 /* ignore this value */
+#define MODE_PRINT 1 /* print-only, don't change value */
+#define MODE_SET 2 /* set to newval value */
+#define MODE_ADD 3 /* increase current value by newval */
+#define MODE_SUB 4 /* decrease current value by newval */
+#define MODE_TOGGLE 5 /* toggle current value */
+ unsigned mode;
+ int curval, newval;
+};
+
+int cmpdesc(struct sioctl_desc *, struct sioctl_desc *);
+int isdiag(struct info *);
+struct info *vecent(struct info *, char *, int);
+struct info *nextfunc(struct info *);
+struct info *nextpar(struct info *);
+struct info *firstent(struct info *, char *);
+struct info *nextent(struct info *, int);
+int matchpar(struct info *, char *, int);
+int matchent(struct info *, char *, int);
+int ismono(struct info *);
+void print_node(struct sioctl_node *, int);
+void print_desc(struct info *, int);
+void print_val(struct info *, int);
+void print_par(struct info *, int, char *);
+int parse_name(char **, char *);
+int parse_unit(char **, unsigned int *);
+int parse_val(char **, float *);
+int parse_node(char **, char *, int *);
+int parse_modeval(char **, int *, float *);
+void dump(void);
+int cmd(char *);
+void commit(void);
+void list(void);
+void ondesc(void *, struct sioctl_desc *, int);
+void onctl(void *, unsigned, unsigned);
+
+struct sioctl_hdl *hdl;
+struct info *infolist;
+int i_flag = 0, v_flag = 0, m_flag = 0;
+
+static inline int
+isname_first(int c)
+{
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+static inline int
+isname_next(int c)
+{
+ return isname_first(c) || (c >= '0' && c <= '9') || (c == '_');
+}
+
+static int
+ftoi(float f)
+{
+ return f + 0.5;
+}
+
+/*
+ * compare two sioctl_desc structures, used to sort infolist
+ */
+int
+cmpdesc(struct sioctl_desc *d1, struct sioctl_desc *d2)
+{
+ int res;
+
+ res = strcmp(d1->group, d2->group);
+ if (res != 0)
+ return res;
+ res = strcmp(d1->node0.name, d2->node0.name);
+ if (res != 0)
+ return res;
+ res = d1->type - d2->type;
+ if (res != 0)
+ return res;
+ res = strcmp(d1->func, d2->func);
+ if (res != 0)
+ return res;
+ res = d1->node0.unit - d2->node0.unit;
+ if (d1->type == SIOCTL_VEC ||
+    d1->type == SIOCTL_LIST) {
+ if (res != 0)
+ return res;
+ res = strcmp(d1->node1.name, d2->node1.name);
+ if (res != 0)
+ return res;
+ res = d1->node1.unit - d2->node1.unit;
+ }
+ return res;
+}
+
+/*
+ * return true of the vector entry is diagonal
+ */
+int
+isdiag(struct info *e)
+{
+ if (e->desc.node0.unit < 0 || e->desc.node1.unit < 0)
+ return 1;
+ return e->desc.node1.unit == e->desc.node0.unit;
+}
+
+/*
+ * find the selector or vector entry with the given name and channels
+ */
+struct info *
+vecent(struct info *i, char *vstr, int vunit)
+{
+ while (i != NULL) {
+ if ((strcmp(i->desc.node1.name, vstr) == 0) &&
+    (vunit < 0 || i->desc.node1.unit == vunit))
+ break;
+ i = i->next;
+ }
+ return i;
+}
+
+/*
+ * skip all parameters with the same group, name, and func
+ */
+struct info *
+nextfunc(struct info *i)
+{
+ char *str, *group, *func;
+
+ group = i->desc.group;
+ func = i->desc.func;
+ str = i->desc.node0.name;
+ for (i = i->next; i != NULL; i = i->next) {
+ if (strcmp(i->desc.group, group) != 0 ||
+    strcmp(i->desc.node0.name, str) != 0 ||
+    strcmp(i->desc.func, func) != 0)
+ return i;
+ }
+ return NULL;
+}
+
+/*
+ * find the next parameter with the same group, name, func
+ */
+struct info *
+nextpar(struct info *i)
+{
+ char *str, *group, *func;
+ int unit;
+
+ group = i->desc.group;
+ func = i->desc.func;
+ str = i->desc.node0.name;
+ unit = i->desc.node0.unit;
+ for (i = i->next; i != NULL; i = i->next) {
+ if (strcmp(i->desc.group, group) != 0 ||
+    strcmp(i->desc.node0.name, str) != 0 ||
+    strcmp(i->desc.func, func) != 0)
+ break;
+ /* XXX: need to check for -1 ? */
+ if (i->desc.node0.unit != unit)
+ return i;
+ }
+ return NULL;
+}
+
+/*
+ * return the first vector entry with the given name
+ */
+struct info *
+firstent(struct info *g, char *vstr)
+{
+ char *astr, *group, *func;
+ struct info *i;
+
+ group = g->desc.group;
+ astr = g->desc.node0.name;
+ func = g->desc.func;
+ for (i = g; i != NULL; i = i->next) {
+ if (strcmp(i->desc.group, group) != 0 ||
+    strcmp(i->desc.node0.name, astr) != 0 ||
+    strcmp(i->desc.func, func) != 0)
+ break;
+ if (!isdiag(i))
+ continue;
+ if (strcmp(i->desc.node1.name, vstr) == 0)
+ return i;
+ }
+ return NULL;
+}
+
+/*
+ * find the next entry of the given vector, if the mono flag
+ * is set then the whole group is searched and off-diagonal entries are
+ * skipped
+ */
+struct info *
+nextent(struct info *i, int mono)
+{
+ char *str, *group, *func;
+ int unit;
+
+ group = i->desc.group;
+ func = i->desc.func;
+ str = i->desc.node0.name;
+ unit = i->desc.node0.unit;
+ for (i = i->next; i != NULL; i = i->next) {
+ if (strcmp(i->desc.group, group) != 0 ||
+    strcmp(i->desc.node0.name, str) != 0 ||
+    strcmp(i->desc.func, func) != 0)
+ return NULL;
+ if (mono)
+ return i;
+ if (i->desc.node0.unit == unit)
+ return i;
+ }
+ return NULL;
+}
+
+/*
+ * return true if parameter matches the given name and channel
+ */
+int
+matchpar(struct info *i, char *astr, int aunit)
+{
+ if (strcmp(i->desc.node0.name, astr) != 0)
+ return 0;
+ if (aunit < 0)
+ return 1;
+ else if (i->desc.node0.unit < 0) {
+ fprintf(stderr, "unit used for parameter with no unit\n");
+ exit(1);
+ }
+ return i->desc.node0.unit == aunit;
+}
+
+/*
+ * return true if selector or vector entry matches the given name and
+ * channel range
+ */
+int
+matchent(struct info *i, char *vstr, int vunit)
+{
+ if (strcmp(i->desc.node1.name, vstr) != 0)
+ return 0;
+ if (vunit < 0)
+ return 1;
+ else if (i->desc.node1.unit < 0) {
+ fprintf(stderr, "unit used for parameter with no unit\n");
+ exit(1);
+ }
+ return i->desc.node1.unit == vunit;
+}
+
+/*
+ * return true if the given group can be represented as a signle mono
+ * parameter
+ */
+int
+ismono(struct info *g)
+{
+ struct info *p1, *p2;
+ struct info *e1, *e2;
+
+ p1 = g;
+ switch (g->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
+ if (p2->curval != p1->curval)
+ return 0;
+ }
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
+ for (e2 = p2; e2 != NULL; e2 = nextent(e2, 0)) {
+ if (!isdiag(e2)) {
+ if (e2->curval != 0)
+ return 0;
+ } else {
+ e1 = vecent(p1,
+    e2->desc.node1.name,
+    p1->desc.node0.unit);
+ if (e1 == NULL)
+ continue;
+ if (e1->curval != e2->curval)
+ return 0;
+ }
+ }
+ }
+ break;
+ }
+ return 1;
+}
+
+/*
+ * print a sub-stream, eg. "spkr[4]"
+ */
+void
+print_node(struct sioctl_node *c, int mono)
+{
+ printf("%s", c->name);
+ if (!mono && c->unit >= 0)
+ printf("[%d]", c->unit);
+}
+
+/*
+ * print info about the parameter
+ */
+void
+print_desc(struct info *p, int mono)
+{
+ struct info *e;
+ int more;
+
+ switch (p->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ printf("*");
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ more = 0;
+ for (e = p; e != NULL; e = nextent(e, mono)) {
+ if (mono) {
+ if (!isdiag(e))
+ continue;
+ if (e != firstent(p, e->desc.node1.name))
+ continue;
+ }
+ if (more)
+ printf(",");
+ print_node(&e->desc.node1, mono);
+ printf(":*");
+ more = 1;
+ }
+ }
+}
+
+/*
+ * print parameter value
+ */
+void
+print_val(struct info *p, int mono)
+{
+ struct info *e;
+ int more;
+
+ switch (p->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ printf("%.2g", p->curval / (float)p->desc.maxval);
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ more = 0;
+ for (e = p; e != NULL; e = nextent(e, mono)) {
+ if (mono) {
+ if (!isdiag(e))
+ continue;
+ if (e != firstent(p, e->desc.node1.name))
+ continue;
+ }
+ if (more)
+ printf(",");
+ print_node(&e->desc.node1, mono);
+ printf(":%.2g", e->curval / (float)e->desc.maxval);
+ more = 1;
+ }
+ }
+}
+
+/*
+ * print ``<parameter>=<value>'' string (including '\n')
+ */
+void
+print_par(struct info *p, int mono, char *comment)
+{
+ if (p->desc.group[0] != 0) {
+ printf("%s", p->desc.group);
+ printf("/");
+ }
+ print_node(&p->desc.node0, mono);
+ printf(".%s=", p->desc.func);
+ if (i_flag)
+ print_desc(p, mono);
+ else
+ print_val(p, mono);
+ if (comment)
+ printf(" # %s", comment);
+ printf("\n");
+}
+
+/*
+ * parse a stream name or parameter name
+ */
+int
+parse_name(char **line, char *name)
+{
+ char *p = *line;
+ unsigned len = 0;
+
+ if (!isname_first(*p)) {
+ fprintf(stderr, "letter expected near '%s'\n", p);
+ return 0;
+ }
+ while (isname_next(*p)) {
+ if (len >= SIOCTL_NAMEMAX - 1) {
+ name[SIOCTL_NAMEMAX - 1] = '\0';
+ fprintf(stderr, "%s...: too long\n", name);
+ return 0;
+ }
+ name[len++] = *p;
+ p++;
+ }
+ name[len] = '\0';
+ *line = p;
+ return 1;
+}
+
+/*
+ * parse a decimal integer
+ */
+int
+parse_unit(char **line, unsigned int *num)
+{
+ char *p = *line;
+ unsigned int val;
+ int n;
+
+ if (sscanf(p, "%u%n", &val, &n) != 1) {
+ fprintf(stderr, "number expected near '%s'\n", p);
+ return 0;
+ }
+ if (val >= 255) {
+ fprintf(stderr, "%d: too large\n", val);
+ return 0;
+ }
+ *num = val;
+ *line = p + n;
+ return 1;
+}
+
+int
+parse_val(char **line, float *num)
+{
+ char *p = *line;
+ float val;
+ int n;
+
+ if (sscanf(p, "%g%n", &val, &n) != 1) {
+ fprintf(stderr, "number expected near '%s'\n", p);
+ return 0;
+ }
+ if (val < 0 || val > 1) {
+ fprintf(stderr, "%g: expected number between 0 and 1\n", val);
+ return 0;
+ }
+ *num = val;
+ *line = p + n;
+ return 1;
+}
+
+/*
+ * parse a sub-stream, eg. "spkr[7]"
+ */
+int
+parse_node(char **line, char *str, int *unit)
+{
+ char *p = *line;
+
+ if (!parse_name(&p, str))
+ return 0;
+ if (*p != '[') {
+ *unit = -1;
+ *line = p;
+ return 1;
+ }
+ p++;
+ if (!parse_unit(&p, unit))
+ return 0;
+ if (*p != ']') {
+ fprintf(stderr, "']' expected near '%s'\n", p);
+ return 0;
+ }
+ p++;
+ *line = p;
+ return 1;
+}
+
+/*
+ * parse a decimal prefixed by the optional mode
+ */
+int
+parse_modeval(char **line, int *rmode, float *rval)
+{
+ char *p = *line;
+ unsigned mode;
+
+ switch (*p) {
+ case '+':
+ mode = MODE_ADD;
+ p++;
+ break;
+ case '-':
+ mode = MODE_SUB;
+ p++;
+ break;
+ case '!':
+ mode = MODE_TOGGLE;
+ p++;
+ break;
+ default:
+ mode = MODE_SET;
+ }
+ if (mode != MODE_TOGGLE) {
+ if (!parse_val(&p, rval))
+ return 0;
+ }
+ *line = p;
+ *rmode = mode;
+ return 1;
+}
+
+/*
+ * dump the whole controls list, useful for debugging
+ */
+void
+dump(void)
+{
+ struct info *i;
+
+ for (i = infolist; i != NULL; i = i->next) {
+ printf("%03u:", i->ctladdr);
+ print_node(&i->desc.node0, 0);
+ printf(".%s", i->desc.func);
+ printf("=");
+ switch (i->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ printf("0..%d (%u)", i->desc.maxval, i->curval);
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ print_node(&i->desc.node1, 0);
+ printf(":0..%d (%u)", i->desc.maxval, i->curval);
+ }
+ printf("\n");
+ }
+}
+
+/*
+ * parse and execute a command ``<parameter>[=<value>]''
+ */
+int
+cmd(char *line)
+{
+ char *pos, *group;
+ struct info *i, *e, *g;
+ char func[SIOCTL_NAMEMAX];
+ char astr[SIOCTL_NAMEMAX], vstr[SIOCTL_NAMEMAX];
+ int aunit, vunit;
+ unsigned npar = 0, nent = 0;
+ int comma, mode;
+ float val;
+
+ pos = strrchr(line, '/');
+ if (pos != NULL) {
+ group = line;
+ pos[0] = 0;
+ pos++;
+ } else {
+ group = "";
+ pos = line;
+ }
+ if (!parse_node(&pos, astr, &aunit))
+ return 0;
+ if (*pos != '.') {
+ fprintf(stderr, "'.' expected near '%s'\n", pos);
+ return 0;
+ }
+ pos++;
+ if (!parse_name(&pos, func))
+ return 0;
+ for (g = infolist;; g = g->next) {
+ if (g == NULL) {
+ fprintf(stderr, "%s.%s: no such control\n", astr, func);
+ return 0;
+ }
+ if (strcmp(g->desc.group, group) == 0 &&
+    strcmp(g->desc.func, func) == 0 &&
+    strcmp(g->desc.node0.name, astr) == 0)
+ break;
+ }
+ g->mode = MODE_PRINT;
+ if (*pos != '=') {
+ if (*pos != '\0') {
+ fprintf(stderr, "junk at end of command\n");
+ return 0;
+ }
+ return 1;
+ }
+ pos++;
+ if (i_flag) {
+ printf("can't set values in info mode\n");
+ return 0;
+ }
+ npar = 0;
+ switch (g->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ if (!parse_modeval(&pos, &mode, &val))
+ return 0;
+ for (i = g; i != NULL; i = nextpar(i)) {
+ if (!matchpar(i, astr, aunit))
+ continue;
+ i->mode = mode;
+ i->newval = ftoi(val * i->desc.maxval);
+ npar++;
+ }
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ for (i = g; i != NULL; i = nextpar(i)) {
+ if (!matchpar(i, astr, aunit))
+ continue;
+ for (e = i; e != NULL; e = nextent(e, 0)) {
+ e->newval = 0;
+ e->mode = MODE_SET;
+ }
+ npar++;
+ }
+ comma = 0;
+ for (;;) {
+ if (*pos == '\0')
+ break;
+ if (comma) {
+ if (*pos != ',')
+ break;
+ pos++;
+ }
+ if (!parse_node(&pos, vstr, &vunit))
+ return 0;
+ if (*pos == ':') {
+ pos++;
+ if (!parse_modeval(&pos, &mode, &val))
+ return 0;
+ } else {
+ val = 1.;
+ mode = MODE_SET;
+ }
+ nent = 0;
+ for (i = g; i != NULL; i = nextpar(i)) {
+ if (!matchpar(i, astr, aunit))
+ continue;
+ for (e = i; e != NULL; e = nextent(e, 0)) {
+ if (matchent(e, vstr, vunit)) {
+ e->newval = ftoi(val * e->desc.maxval);
+ e->mode = mode;
+ nent++;
+ }
+ }
+ }
+ if (nent == 0) {
+ /* XXX: use print_node()-like routine */
+ fprintf(stderr, "%s[%d]: invalid value\n", vstr, vunit);
+ print_par(g, 0, NULL);
+ exit(1);
+ }
+ comma = 1;
+ }
+ }
+ if (npar == 0) {
+ fprintf(stderr, "%s: invalid parameter\n", line);
+ exit(1);
+ }
+ if (*pos != '\0') {
+ printf("%s: junk at end of command\n", pos);
+ exit(1);
+ }
+ return 1;
+}
+
+/*
+ * write the controls with the ``set'' flag on the device
+ */
+void
+commit(void)
+{
+ struct info *i;
+ int val;
+
+ for (i = infolist; i != NULL; i = i->next) {
+ val = 0xdeadbeef;
+ switch (i->mode) {
+ case MODE_IGNORE:
+ case MODE_PRINT:
+ continue;
+ case MODE_SET:
+ val = i->newval;
+ break;
+ case MODE_ADD:
+ val = i->curval + i->newval;
+ if (val > i->desc.maxval)
+ val = i->desc.maxval;
+ break;
+ case MODE_SUB:
+ val = i->curval - i->newval;
+ if (val < 0)
+ val = 0;
+ break;
+ case MODE_TOGGLE:
+ val = i->curval ? 0 : i->desc.maxval;
+ }
+ sioctl_setval(hdl, i->ctladdr, val);
+ i->curval = val;
+ }
+}
+
+/*
+ * print all parameters
+ */
+void
+list(void)
+{
+ struct info *p, *g;
+
+ for (g = infolist; g != NULL; g = nextfunc(g)) {
+ if (g->mode == MODE_IGNORE)
+ continue;
+ if (i_flag) {
+ if (v_flag) {
+ for (p = g; p != NULL; p = nextpar(p))
+ print_par(p, 0, NULL);
+ } else
+ print_par(g, 1, NULL);
+ } else {
+ if (v_flag || !ismono(g)) {
+ for (p = g; p != NULL; p = nextpar(p))
+ print_par(p, 0, NULL);
+ } else
+ print_par(g, 1, NULL);
+ }
+ }
+}
+
+/*
+ * register a new knob/button, called from the poll() loop.  this may be
+ * called when label string changes, in which case we update the
+ * existing label widged rather than inserting a new one.
+ */
+void
+ondesc(void *arg, struct sioctl_desc *d, int curval)
+{
+ struct info *i, **pi;
+ int cmp;
+
+ if (d == NULL)
+ return;
+
+ /*
+ * delete control
+ */
+ for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
+ if (d->addr == i->desc.addr) {
+ if (m_flag)
+ print_par(i, 0, "deleted");
+ *pi = i->next;
+ free(i);
+ break;
+ }
+ }
+
+ if (d->type == SIOCTL_NONE)
+ return;
+
+ /*
+ * find the right position to insert the new widget
+ */
+ for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
+ cmp = cmpdesc(d, &i->desc);
+ if (cmp == 0) {
+ fprintf(stderr, "fatal: duplicate control:\n");
+ print_par(i, 0, "duplicate");
+ exit(1);
+ }
+ if (cmp < 0)
+ break;
+ }
+ i = malloc(sizeof(struct info));
+ if (i == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+ i->desc = *d;
+ i->ctladdr = d->addr;
+ i->curval = i->newval = curval;
+ i->mode = MODE_IGNORE;
+ i->next = *pi;
+ *pi = i;
+ if (m_flag)
+ print_par(i, 0, "added");
+}
+
+/*
+ * update a knob/button state, called from the poll() loop
+ */
+void
+onctl(void *arg, unsigned addr, unsigned val)
+{
+ struct info *i;
+
+ for (i = infolist; i != NULL; i = i->next) {
+ if (i->ctladdr != addr)
+ continue;
+ i->curval = val;
+ if (m_flag)
+ print_par(i, 0, "changed");
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ char *devname = SIO_DEVANY;
+ int i, c, d_flag = 0;
+ struct info *g;
+ struct pollfd *pfds;
+ int nfds, revents;
+
+ while ((c = getopt(argc, argv, "df:imv")) != -1) {
+ switch (c) {
+ case 'd':
+ d_flag = 1;
+ break;
+ case 'f':
+ devname = optarg;
+ break;
+ case 'i':
+ i_flag = 1;
+ break;
+ case 'm':
+ m_flag = 1;
+ break;
+ case 'v':
+ v_flag++;
+ break;
+ default:
+ fprintf(stderr, "usage: sndioctl "
+    "[-dimnv] [-f device] [command ...]\n");
+ exit(1);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ hdl = sioctl_open(devname, SIOCTL_READ | SIOCTL_WRITE, 0);
+ if (hdl == NULL) {
+ fprintf(stderr, "%s: can't open control device\n", devname);
+ exit(1);
+ }
+ if (!sioctl_ondesc(hdl, ondesc, NULL)) {
+ fprintf(stderr, "%s: can't get device description\n", devname);
+ exit(1);
+ }
+ sioctl_onval(hdl, onctl, NULL);
+
+ if (d_flag) {
+ if (argc > 0) {
+ fprintf(stderr,
+    "commands are not allowed with -d option\n");
+ exit(1);
+ }
+ dump();
+ } else {
+ if (argc == 0) {
+ for (g = infolist; g != NULL; g = nextfunc(g))
+ g->mode = MODE_PRINT;
+ } else {
+ for (i = 0; i < argc; i++) {
+ if (!cmd(argv[i]))
+ return 1;
+ }
+ }
+ commit();
+ list();
+ }
+ if (m_flag) {
+ pfds = malloc(sizeof(struct pollfd) * sioctl_nfds(hdl));
+ if (pfds == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+ for (;;) {
+ nfds = sioctl_pollfd(hdl, pfds, POLLIN);
+ if (nfds == 0)
+ break;
+ while (poll(pfds, nfds, -1) < 0) {
+ if (errno != EINTR) {
+ perror("poll");
+ exit(1);
+ }
+ }
+ revents = sioctl_revents(hdl, pfds);
+ if (revents & POLLHUP) {
+ fprintf(stderr, "disconnected\n");
+ break;
+ }
+ }
+ free(pfds);
+ }
+ sioctl_close(hdl);
+ return 0;
+}
Index: lib/libossaudio/Makefile
===================================================================
RCS file: /cvs/src/lib/libossaudio/Makefile,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 Makefile
--- lib/libossaudio/Makefile 16 Jul 2014 20:02:17 -0000 1.5
+++ lib/libossaudio/Makefile 22 Feb 2020 16:07:05 -0000
@@ -4,9 +4,11 @@
 LIB= ossaudio
 MAN= ossaudio.3
 
-SRCS= ossaudio.c
+SRCS= ossaudio.c aucat.c debug.c sioctl.c sioctl_aucat.c sioctl_sun.c
 
 CPPFLAGS+= -I${.CURDIR}
+
+.PATH: ${.CURDIR}/../libsndio
 
 includes:
  @cd ${.CURDIR}; cmp -s soundcard.h ${DESTDIR}/usr/include/soundcard.h || \
Index: lib/libossaudio/ossaudio.c
===================================================================
RCS file: /cvs/src/lib/libossaudio/ossaudio.c,v
retrieving revision 1.20
diff -u -p -u -p -r1.20 ossaudio.c
--- lib/libossaudio/ossaudio.c 28 Jun 2019 13:32:42 -0000 1.20
+++ lib/libossaudio/ossaudio.c 22 Feb 2020 16:07:05 -0000
@@ -36,26 +36,38 @@
 #include <string.h>
 #include <sys/types.h>
 #include <sys/ioctl.h>
-#include <sys/audioio.h>
-#include <sys/stat.h>
 #include <errno.h>
-
+#include <poll.h>
+#include <sndio.h>
+#include <stdlib.h>
+#include <stdio.h>
 #include "soundcard.h"
-#undef ioctl
 
-#define GET_DEV(com) ((com) & 0xff)
+#ifdef DEBUG
+#define DPRINTF(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(...) do {} while (0)
+#endif
 
-#define TO_OSSVOL(x) (((x) * 100 + 127) / 255)
-#define FROM_OSSVOL(x) ((((x) > 100 ? 100 : (x)) * 255 + 50) / 100)
+#define GET_DEV(com) ((com) & 0xff)
+#define INTARG (*(int*)argp)
 
-static struct audiodevinfo *getdevinfo(int);
+struct control {
+ struct control *next;
+ int type; /* one of SOUND_MIXER_xxx */
+ int chan; /* 0 -> left, 1 -> right, -1 -> mono */
+ int addr; /* sioctl control id */
+ int value; /* current value */
+ int max;
+};
 
 static int mixer_ioctl(int, unsigned long, void *);
-static int opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, int opq);
-static int enum_to_ord(struct audiodevinfo *di, int enm);
-static int enum_to_mask(struct audiodevinfo *di, int enm);
 
-#define INTARG (*(int*)argp)
+static int initialized;
+static struct control *controls;
+static struct sioctl_hdl *hdl;
+static char *dev_name = SIO_DEVANY;
+static struct pollfd *pfds;
 
 int
 _oss_ioctl(int fd, unsigned long com, ...)
@@ -71,201 +83,163 @@ _oss_ioctl(int fd, unsigned long com, ..
  else if (IOCGROUP(com) == 'M')
  return mixer_ioctl(fd, com, argp);
  else
- return ioctl(fd, com, argp);
+ return (ioctl)(fd, com, argp);
 }
 
-/* If the mixer device should have more than MAX_MIXER_DEVS devices
- * some will not be available to Linux */
-#define MAX_MIXER_DEVS 64
-struct audiodevinfo {
- int done;
- dev_t dev;
- ino_t ino;
- int16_t devmap[SOUND_MIXER_NRDEVICES],
-        rdevmap[MAX_MIXER_DEVS];
- char names[MAX_MIXER_DEVS][MAX_AUDIO_DEV_LEN];
- int enum2opaque[MAX_MIXER_DEVS];
-        u_long devmask, recmask, stereomask;
- u_long caps, recsource;
-};
-
-static int
-opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, int opq)
+/*
+ * new control
+ */
+static void
+mixer_ondesc(void *unused, struct sioctl_desc *d, int val)
 {
- int i, o;
+ struct control *i, **pi;
+ int type;
 
- for (i = 0; i < MAX_MIXER_DEVS; i++) {
- o = di->enum2opaque[i];
- if (o == opq)
- break;
- if (o == -1 && label != NULL &&
-    !strncmp(di->names[i], label->name, sizeof di->names[i])) {
- di->enum2opaque[i] = opq;
+ if (d == NULL)
+ return;
+
+ /*
+ * delete existing control with the same address
+ */
+ for (pi = &controls; (i = *pi) != NULL; pi = &i->next) {
+ if (d->addr == i->addr) {
+ *pi = i->next;
+ free(i);
  break;
  }
  }
- if (i >= MAX_MIXER_DEVS)
- i = -1;
- /*printf("opq_to_enum %s %d -> %d\n", label->name, opq, i);*/
- return (i);
+
+ /*
+ * we support only numeric "level" controls, first 2 channels
+ */
+ if (d->type != SIOCTL_NUM || d->node0.unit >= 2 ||
+    strcmp(d->func, "level") != 0)
+ return;
+
+ /*
+ * We expose top-level input.level and output.level as OSS
+ * volume and microphone knobs. By default sndiod exposes
+ * the underlying hardware knobs as hw/input.level and
+ * hw/output.level that we map to OSS gain controls. This
+ * ensures useful knobs are exposed no matter if sndiod
+ * is running or not.
+ */
+ if (d->group[0] == 0) {
+ if (strcmp(d->node0.name, "output") == 0)
+ type = SOUND_MIXER_VOLUME;
+ else if (strcmp(d->node0.name, "input") == 0)
+ type = SOUND_MIXER_MIC;
+ else
+ return;
+ } else if (strcmp(d->group, "hw") == 0) {
+ if (strcmp(d->node0.name, "output") == 0)
+ type = SOUND_MIXER_OGAIN;
+ else if (strcmp(d->node0.name, "input") == 0)
+ type = SOUND_MIXER_IGAIN;
+ else
+ return;
+ } else
+ return;
+
+ i = malloc(sizeof(struct control));
+ if (i == NULL) {
+ DPRINTF("%s: cannot allocate control\n", __func__);
+ return;
+ }
+
+ i->addr = d->addr;
+ i->chan = d->node0.unit;
+ i->max = d->maxval;
+ i->value = val;
+ i->type = type;
+ i->next = controls;
+ controls = i;
+ DPRINTF("%s: %d: used as %d, chan = %d, value = %d\n", __func__,
+    i->addr, i->type, i->chan, i->value);
 }
 
-static int
-enum_to_ord(struct audiodevinfo *di, int enm)
+/*
+ * control value changed
+ */
+static void
+mixer_onval(void *unused, unsigned int addr, unsigned int value)
 {
- if (enm >= MAX_MIXER_DEVS)
- return (-1);
+ struct control *c;
+
+ for (c = controls; ; c = c->next) {
+ if (c == NULL) {
+ DPRINTF("%s: %d: change ignored\n", __func__, addr);
+ return;
+ }
+ if (c->addr == addr)
+ break;
+ }
 
- /*printf("enum_to_ord %d -> %d\n", enm, di->enum2opaque[enm]);*/
- return (di->enum2opaque[enm]);
+ DPRINTF("%s: %d: changed to %d\n", __func__, addr, value);
+ c->value = value;
 }
 
 static int
-enum_to_mask(struct audiodevinfo *di, int enm)
+mixer_init(void)
 {
- int m;
- if (enm >= MAX_MIXER_DEVS)
- return (0);
-
- m = di->enum2opaque[enm];
- if (m == -1)
- m = 0;
- /*printf("enum_to_mask %d -> %d\n", enm, di->enum2opaque[enm]);*/
- return (m);
-}
+ if (initialized)
+ return hdl != NULL;
 
-/*
- * Collect the audio device information to allow faster
- * emulation of the Linux mixer ioctls.  Cache the information
- * to eliminate the overhead of repeating all the ioctls needed
- * to collect the information.
- */
-static struct audiodevinfo *
-getdevinfo(int fd)
-{
- mixer_devinfo_t mi, cl;
- int i, j, e;
- static struct {
- char *name;
- int code;
- } *dp, devs[] = {
- { AudioNmicrophone, SOUND_MIXER_MIC },
- { AudioNline, SOUND_MIXER_LINE },
- { AudioNcd, SOUND_MIXER_CD },
- { AudioNdac, SOUND_MIXER_PCM },
- { AudioNaux, SOUND_MIXER_LINE1 },
- { AudioNrecord, SOUND_MIXER_IMIX },
- { AudioNmaster, SOUND_MIXER_VOLUME },
- { AudioNtreble, SOUND_MIXER_TREBLE },
- { AudioNbass, SOUND_MIXER_BASS },
- { AudioNspeaker, SOUND_MIXER_SPEAKER },
- { AudioNoutput, SOUND_MIXER_OGAIN },
- { AudioNinput, SOUND_MIXER_IGAIN },
- { AudioNfmsynth, SOUND_MIXER_SYNTH },
- { AudioNmidi, SOUND_MIXER_SYNTH },
- { 0, -1 }
- };
- static struct audiodevinfo devcache = { 0 };
- struct audiodevinfo *di = &devcache;
- struct stat sb;
+ initialized = 1;
 
- /* Figure out what device it is so we can check if the
- * cached data is valid.
- */
- if (fstat(fd, &sb) < 0)
+ hdl = sioctl_open(dev_name, SIOCTL_READ | SIOCTL_WRITE, 0);
+ if (hdl == NULL) {
+ DPRINTF("%s: cannot open audio control device\n", __func__);
  return 0;
- if (di->done && (di->dev == sb.st_dev && di->ino == sb.st_ino))
- return di;
+ }
 
- di->done = 1;
- di->dev = sb.st_dev;
- di->ino = sb.st_ino;
- di->devmask = 0;
- di->recmask = 0;
- di->stereomask = 0;
- di->recsource = ~0;
- di->caps = 0;
- for(i = 0; i < SOUND_MIXER_NRDEVICES; i++)
- di->devmap[i] = -1;
- for(i = 0; i < MAX_MIXER_DEVS; i++) {
- di->rdevmap[i] = -1;
- di->names[i][0] = '\0';
- di->enum2opaque[i] = -1;
+ pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd));
+ if (pfds == NULL) {
+ DPRINTF("%s: cannot allocate pfds\n", __func__);
+ goto bad_close;
  }
- for(i = 0; i < MAX_MIXER_DEVS; i++) {
- mi.index = i;
- if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) == -1)
- break;
- switch(mi.type) {
- case AUDIO_MIXER_VALUE:
- for(dp = devs; dp->name; dp++)
-     if (strcmp(dp->name, mi.label.name) == 0)
- break;
- if (dp->code >= 0) {
- di->devmap[dp->code] = i;
- di->rdevmap[i] = dp->code;
- di->devmask |= 1 << dp->code;
- if (mi.un.v.num_channels == 2)
- di->stereomask |= 1 << dp->code;
- strncpy(di->names[i], mi.label.name,
- sizeof di->names[i]);
- }
- break;
- }
+
+ if (!sioctl_ondesc(hdl, mixer_ondesc, NULL)) {
+ DPRINTF("%s: cannot get controls descriptions\n", __func__);
+ goto bad_free;
  }
- for(i = 0; i < MAX_MIXER_DEVS; i++) {
- mi.index = i;
- if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) == -1)
- break;
- if (strcmp(mi.label.name, AudioNsource) != 0)
- continue;
- cl.index = mi.mixer_class;
- if (ioctl(fd, AUDIO_MIXER_DEVINFO, &cl) == -1)
- break;
- if ((cl.type != AUDIO_MIXER_CLASS) ||
-    (strcmp(cl.label.name, AudioCrecord) != 0))
- continue;
- di->recsource = i;
- switch(mi.type) {
- case AUDIO_MIXER_ENUM:
- for(j = 0; j < mi.un.e.num_mem; j++) {
- e = opaque_to_enum(di,
-   &mi.un.e.member[j].label,
-   mi.un.e.member[j].ord);
- if (e >= 0)
- di->recmask |= 1 << di->rdevmap[e];
- }
- di->caps = SOUND_CAP_EXCL_INPUT;
- break;
- case AUDIO_MIXER_SET:
- for(j = 0; j < mi.un.s.num_mem; j++) {
- e = opaque_to_enum(di,
-   &mi.un.s.member[j].label,
-   mi.un.s.member[j].mask);
- if (e >= 0)
- di->recmask |= 1 << di->rdevmap[e];
- }
- break;
- }
+
+ if (!sioctl_onval(hdl, mixer_onval, NULL)) {
+ DPRINTF("%s: cannot get controls values\n", __func__);
+ goto bad_free;
  }
- return di;
+
+ return 1;
+
+bad_free:
+ free(pfds);
+bad_close:
+ sioctl_close(hdl);
+ return 0;
 }
 
-int
+static int
 mixer_ioctl(int fd, unsigned long com, void *argp)
 {
- struct audiodevinfo *di;
+ struct control *c;
  struct mixer_info *omi;
- struct audio_device adev;
- mixer_ctrl_t mc;
  int idat = 0;
- int i;
- int retval;
- int l, r, n, error, e;
+ int v, n;
 
- di = getdevinfo(fd);
- if (di == 0)
+ if (!mixer_init()) {
+ DPRINTF("%s: not initialized\n", __func__);
+ errno = EIO;
  return -1;
+ }
+
+ n = sioctl_pollfd(hdl, pfds, POLLIN);
+ if (n > 0) {
+ n = poll(pfds, n, 0);
+ if (n == -1)
+ return -1;
+ if (n > 0)
+ sioctl_revents(hdl, pfds);
+ }
 
  switch (com) {
  case OSS_GETVERSION:
@@ -273,122 +247,80 @@ mixer_ioctl(int fd, unsigned long com, v
  break;
  case SOUND_MIXER_INFO:
  case SOUND_OLD_MIXER_INFO:
- error = ioctl(fd, AUDIO_GETDEV, &adev);
- if (error == -1)
- return (error);
  omi = argp;
  if (com == SOUND_MIXER_INFO)
  omi->modify_counter = 1;
- strncpy(omi->id, adev.name, sizeof omi->id);
- strncpy(omi->name, adev.name, sizeof omi->name);
+ strlcpy(omi->id, dev_name, sizeof omi->id);
+ strlcpy(omi->name, dev_name, sizeof omi->name);
  return 0;
  case SOUND_MIXER_READ_RECSRC:
- if (di->recsource == -1)
- return EINVAL;
- mc.dev = di->recsource;
- if (di->caps & SOUND_CAP_EXCL_INPUT) {
- mc.type = AUDIO_MIXER_ENUM;
- retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
- if (retval == -1)
- return retval;
- e = opaque_to_enum(di, NULL, mc.un.ord);
- if (e >= 0)
- idat = 1 << di->rdevmap[e];
- } else {
- mc.type = AUDIO_MIXER_SET;
- retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
- if (retval == -1)
- return retval;
- e = opaque_to_enum(di, NULL, mc.un.mask);
- if (e >= 0)
- idat = 1 << di->rdevmap[e];
- }
+ case SOUND_MIXER_READ_RECMASK:
+ idat = 0;
+ for (c = controls; c != NULL; c = c->next)
+ idat |= 1 << c->type;
+ idat &= (1 << SOUND_MIXER_MIC) | (1 << SOUND_MIXER_IGAIN);
+ DPRINTF("%s: SOUND_MIXER_READ_RECSRC: %d\n", __func__, idat);
  break;
  case SOUND_MIXER_READ_DEVMASK:
- idat = di->devmask;
- break;
- case SOUND_MIXER_READ_RECMASK:
- idat = di->recmask;
+ idat = 0;
+ for (c = controls; c != NULL; c = c->next)
+ idat |= 1 << c->type;
+ DPRINTF("%s: SOUND_MIXER_READ_DEVMASK: %d\n", __func__, idat);
  break;
  case SOUND_MIXER_READ_STEREODEVS:
- idat = di->stereomask;
+ idat = 0;
+ for (c = controls; c != NULL; c = c->next) {
+ if (c->chan == 1)
+ idat |= 1 << c->type;
+ }
+ DPRINTF("%s: SOUND_MIXER_STEREODEVS: %d\n", __func__, idat);
  break;
  case SOUND_MIXER_READ_CAPS:
- idat = di->caps;
+ idat = 0;
+ DPRINTF("%s: SOUND_MIXER_READ_CAPS: %d\n", __func__, idat);
  break;
  case SOUND_MIXER_WRITE_RECSRC:
  case SOUND_MIXER_WRITE_R_RECSRC:
- if (di->recsource == -1)
- return EINVAL;
- mc.dev = di->recsource;
- idat = INTARG;
- if (di->caps & SOUND_CAP_EXCL_INPUT) {
- mc.type = AUDIO_MIXER_ENUM;
- for(i = 0; i < SOUND_MIXER_NRDEVICES; i++)
- if (idat & (1 << i))
- break;
- if (i >= SOUND_MIXER_NRDEVICES ||
-    di->devmap[i] == -1)
- return EINVAL;
- mc.un.ord = enum_to_ord(di, di->devmap[i]);
- } else {
- mc.type = AUDIO_MIXER_SET;
- mc.un.mask = 0;
- for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
- if (idat & (1 << i)) {
- if (di->devmap[i] == -1)
- return EINVAL;
- mc.un.mask |= enum_to_mask(di, di->devmap[i]);
- }
- }
- }
- return ioctl(fd, AUDIO_MIXER_WRITE, &mc);
+ DPRINTF("%s: SOUND_MIXER_WRITE_RECSRC\n", __func__);
+ errno = EINVAL;
+ return -1;
  default:
  if (MIXER_READ(SOUND_MIXER_FIRST) <= com &&
     com < MIXER_READ(SOUND_MIXER_NRDEVICES)) {
+ doread:
+ idat = 0;
  n = GET_DEV(com);
- if (di->devmap[n] == -1)
- return EINVAL;
- mc.dev = di->devmap[n];
- mc.type = AUDIO_MIXER_VALUE;
-    doread:
- mc.un.value.num_channels = di->stereomask & (1<<n) ? 2 : 1;
- retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
- if (retval == -1)
- return retval;
- if (mc.type != AUDIO_MIXER_VALUE)
- return EINVAL;
- if (mc.un.value.num_channels != 2) {
- l = r = mc.un.value.level[AUDIO_MIXER_LEVEL_MONO];
- } else {
- l = mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT];
- r = mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
+ for (c = controls; c != NULL; c = c->next) {
+ if (c->type != n)
+ continue;
+ v = (c->value * 100 + c->max / 2) / c->max;
+ if (c->chan == 1)
+ v <<= 8;
+ idat |= v;
  }
- idat = TO_OSSVOL(l) | (TO_OSSVOL(r) << 8);
+ DPRINTF("%s: MIXER_READ: %d: 0x%04x\n",
+    __func__, n, idat);
  break;
  } else if ((MIXER_WRITE_R(SOUND_MIXER_FIRST) <= com &&
    com < MIXER_WRITE_R(SOUND_MIXER_NRDEVICES)) ||
    (MIXER_WRITE(SOUND_MIXER_FIRST) <= com &&
    com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))) {
- n = GET_DEV(com);
- if (di->devmap[n] == -1)
- return EINVAL;
  idat = INTARG;
- l = FROM_OSSVOL( idat       & 0xff);
- r = FROM_OSSVOL((idat >> 8) & 0xff);
- mc.dev = di->devmap[n];
- mc.type = AUDIO_MIXER_VALUE;
- if (di->stereomask & (1<<n)) {
- mc.un.value.num_channels = 2;
- mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = l;
- mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = r;
- } else {
- mc.un.value.num_channels = 1;
- mc.un.value.level[AUDIO_MIXER_LEVEL_MONO] = (l+r)/2;
+ n = GET_DEV(com);
+ for (c = controls; c != NULL; c = c->next) {
+ if (c->type != n)
+ continue;
+ v = idat;
+ if (c->chan == 1)
+ v >>= 8;
+ v &= 0xff;
+ if (v > 100)
+ v = 100;
+ v = (v * c->max + 50) / 100;
+ sioctl_setval(hdl, c->addr, v);
+ DPRINTF("%s: MIXER_WRITE: %d: %d\n",
+    __func__, n, v);
  }
- retval = ioctl(fd, AUDIO_MIXER_WRITE, &mc);
- if (retval == -1)
- return retval;
  if (MIXER_WRITE(SOUND_MIXER_FIRST) <= com &&
    com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))
  return 0;
@@ -398,6 +330,7 @@ mixer_ioctl(int fd, unsigned long com, v
  return -1;
  }
  }
+
  INTARG = idat;
  return 0;
 }

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API

Alexandre Ratchov-2
On Tue, Feb 25, 2020 at 01:02:06PM +0100, Alexandre Ratchov wrote:

> On Thu, Feb 13, 2020 at 04:52:12AM +0000, Raf Czlonka wrote:
> >
> > Hi Alexandre,
> >
> > I have to say that I also find the two ranges mildly confusing,
> > i.e. 0-255 in one place, and 0-127 in another. In terms of units,
> > personally, I'm used to, and quite like, the granularity of 0-255.
> >
> > Again, not my place so others will certainly be more help here.
> >
> > One more point regarding the interface, though.
> >
> > This is the way mixerctl(1) currently behaves:
> >
> > $ mixerctl outputs.master    
> > outputs.master=255,255
> > $ mixerctl outputs.master=100
> > outputs.master: 255,255 -> 100,100
> > $ mixerctl outputs.master=300
> > outputs.master: 100,100 -> 255,255
> >
> > Should sndioctl(1) behave the same way?
>
> Many thanks for the feedback.
>
> After some thinking and experimenting, floats in the [0:1] seem the
> simplest option. It avoids discussions about preferences and allows
> arbitrary precision (if needed, later).
>
> Furthermore, as all controls are in the [0:1] range, it makes sense to
> request the user to provide numbers between 0 and 1. Providing numbers
> outside this range indicates he is misunderstanding how the program
> works.
>
> Below are all 3 base diffs combined (libsndio, sndiod, sndioctl,
> libossaudio), to ease testing.

And here's the ports part of the diff including audio/gqmpeg,
sysutils/tray-app and x11/i3status ports.

Index: sysutils/tray-app/Makefile
===================================================================
RCS file: /cvs/ports/sysutils/tray-app/Makefile,v
retrieving revision 1.8
diff -u -p -u -p -r1.8 Makefile
--- sysutils/tray-app/Makefile 12 Jul 2019 20:49:53 -0000 1.8
+++ sysutils/tray-app/Makefile 22 Feb 2020 16:06:19 -0000
@@ -5,7 +5,7 @@ ONLY_FOR_ARCHS= ${APM_ARCHS}
 COMMENT= small utilities for X11 system tray: eject, battery, mixer
 
 DISTNAME= tray-app-0.3.1
-REVISION= 0
+REVISION= 1
 
 CATEGORIES= sysutils x11
 
Index: sysutils/tray-app/patches/patch-sound_Makefile
===================================================================
RCS file: /cvs/ports/sysutils/tray-app/patches/patch-sound_Makefile,v
retrieving revision 1.1.1.1
diff -u -p -u -p -r1.1.1.1 patch-sound_Makefile
--- sysutils/tray-app/patches/patch-sound_Makefile 17 Sep 2013 11:21:50 -0000 1.1.1.1
+++ sysutils/tray-app/patches/patch-sound_Makefile 22 Feb 2020 16:06:19 -0000
@@ -1,6 +1,7 @@
 $OpenBSD: patch-sound_Makefile,v 1.1.1.1 2013/09/17 11:21:50 sthen Exp $
---- sound/Makefile.orig Mon Mar 12 08:46:04 2012
-+++ sound/Makefile Tue Sep 17 11:39:18 2013
+Index: sound/Makefile
+--- sound/Makefile.orig
++++ sound/Makefile
 @@ -8,13 +8,13 @@ MAN=
 
  gtk_CFLAGS!= pkg-config --cflags gtk+-2.0
@@ -8,7 +9,8 @@ $OpenBSD: patch-sound_Makefile,v 1.1.1.1
 -CFLAGS= -W -Wall -g -O0 -I../lib $(gtk_CFLAGS)
 +CFLAGS+= -W -Wall -I../lib $(gtk_CFLAGS)
  LDFLAGS= -L../lib $(gtk_LDFLAGS)
- LDADD= -ltrayapp
+-LDADD= -ltrayapp
++LDADD= -ltrayapp -lsndio
 
 -BINDIR=/usr/local/libexec/tray-app
 +BINDIR=${TRUEPREFIX}/libexec/tray-app
Index: sysutils/tray-app/patches/patch-sound_sound_c
===================================================================
RCS file: sysutils/tray-app/patches/patch-sound_sound_c
diff -N sysutils/tray-app/patches/patch-sound_sound_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ sysutils/tray-app/patches/patch-sound_sound_c 22 Feb 2020 16:06:19 -0000
@@ -0,0 +1,516 @@
+$OpenBSD$
+
+Index: sound/sound.c
+--- sound/sound.c.orig
++++ sound/sound.c
+@@ -4,12 +4,13 @@
+  */
+ #include <sys/types.h>
+ #include <sys/ioctl.h>
+-#include <sys/audioio.h>
++#include <sndio.h>
+
+ #include <gtk/gtk.h>
+
+ #include <err.h>
+ #include <fcntl.h>
++#include <poll.h>
+ #include <stdlib.h>
+ #include <string.h>
+
+@@ -19,16 +20,20 @@
+ #include "sound-1.xpm"
+ #include "sound-2.xpm"
+
++struct control {
++ struct control *next;
++ unsigned int addr;
++ unsigned int value;
++ unsigned int max;
++ int ismute;
++};
++
+ static void usage(const char *prog);
+-static int get_mixer_index(int fd, mixer_devinfo_t *devinfo);
+-static int get_volume(int fd, mixer_devinfo_t *devinfo, u_char *volume);
+-static int set_volume(int fd, mixer_devinfo_t *devinfo, u_char volume);
+-static int get_mute(int fd, mixer_devinfo_t *devinfo, int *mute);
+-static int set_mute(int fd, mixer_devinfo_t *devinfo, int mute);
++static void set_state(int ismute, int mute);
++static void get_state(int *rvolume, int *rmute);
+
+ static void prepare_tooltip(int mute, u_char volume, char *text, size_t sz);
+
+-static gboolean cb_timer(GtkWidget *widget);
+ static void cb_button_toggled(GtkWidget *widget, gpointer data);
+ static void cb_scale_value_changed(GtkScale *scale, GtkAdjustment *adj);
+ static gboolean cb_window_delete(GtkWidget *widget,GdkEvent *event,
+@@ -48,8 +53,8 @@ static void hide_gui(void);
+ static int init_tray(int doinvert);
+ static void set_tray_icon(u_char volume);
+
+-static int mixer_fd;
+-static mixer_devinfo_t outputs_master_dev[2]; /* volume, mute */
++static struct sioctl_hdl *hdl;
++static struct control    *controls;
+
+ static GtkWidget *gui_window = NULL;
+ static GtkObject *gui_adj = NULL;
+@@ -63,9 +68,6 @@ static GdkPixbuf *tray_pixbuf_0 = NULL;
+ static GdkPixbuf *tray_pixbuf_1 = NULL;
+ static GdkPixbuf *tray_pixbuf_2 = NULL;
+
+-static u_char current_volume = 0;
+-static int current_mute = 0;
+-
+ void
+ usage(const char *prog)
+ {
+@@ -76,104 +78,136 @@ usage(const char *prog)
+    prog);
+ }
+
+-static int
+-get_mixer_index(int fd, mixer_devinfo_t *devinfo)
++/*
++ * new control registered
++ */
++static void
++cb_control_desc(void *unused, struct sioctl_desc *d, int val)
+ {
+- int error;
+- int i, outputs_idx;
++ struct control *i, **pi;
++ int ismute;
+
+- i = 0;
+- outputs_idx = -1;
+- devinfo[0].index = 0;
+- for (;;) {
+- error = ioctl(fd, AUDIO_MIXER_DEVINFO, devinfo + i);
+- if (error == -1)
++ if (d == NULL)
++ return;
++
++ /*
++ * delete existing control with the same address
++ */
++ for (pi = &controls; (i = *pi) != NULL; pi = &i->next) {
++ if (d->addr == i->addr) {
++ *pi = i->next;
++ free(i);
+ break;
++ }
++ }
+
+- if (i == 0 && devinfo[0].type == AUDIO_MIXER_CLASS &&
+-    strcmp(devinfo[0].label.name, "outputs") == 0)
+- outputs_idx = devinfo[0].index;
+- else if (i == 0 && devinfo[0].type == AUDIO_MIXER_VALUE &&
+-    strcmp(devinfo[0].label.name, "master") == 0 &&
+-    outputs_idx != -1 &&
+-    devinfo[0].mixer_class == outputs_idx) {
+- devinfo[1].index = devinfo[0].index;
+- i++;
+- } else if (i == 1 && devinfo[1].prev == devinfo[0].index &&
+-    devinfo[1].type == AUDIO_MIXER_ENUM &&
+-    strcmp(devinfo[1].label.name, "mute") == 0 &&
+-    devinfo[1].mixer_class == outputs_idx)
+- return (0);
++ /*
++ * SIOCTL_NONE means control was deleted from the device
++ */
++ if (d->type == SIOCTL_NONE)
++ return;
+
+- devinfo[i].index++;
+- }
+- return (-1);
+-}
++ /*
++ * we're interested in top-level output.xxx controls only
++ */
++ if (d->group[0] != 0 || strcmp(d->node0.name, "output") != 0)
++ return;
+
+-static int
+-get_volume(int fd, mixer_devinfo_t *devinfo, u_char *volume)
+-{
+- mixer_ctrl_t mctl;
+- int error;
++ if (strcmp(d->func, "level") == 0)
++ ismute = 0;
++ else if (strcmp(d->func, "mute") == 0)
++ ismute = 1;
++ else
++ return;
+
+- memset(&mctl, 0, sizeof(mctl));
+- mctl.dev = devinfo->index;
+- mctl.type = AUDIO_MIXER_VALUE;
+- error = ioctl(fd, AUDIO_MIXER_READ, &mctl);
+- if (error == -1)
+- return (-1);
+- *volume = mctl.un.value.level[0];
+- return (0);
++ i = malloc(sizeof(struct control));
++ if (i == NULL)
++ err(1, "malloc");
++
++ i->addr = d->addr;
++ i->max = d->maxval;
++ i->value = val;
++ i->ismute = ismute;
++ i->next = controls;
++ controls = i;
+ }
+
+-static int
+-set_volume(int fd, mixer_devinfo_t *devinfo, u_char volume)
++/*
++ * control value changed
++ */
++static void
++cb_control_value(void *unused, unsigned int addr, unsigned int value)
+ {
+- mixer_ctrl_t mctl;
+- int i, error;
++ GtkWidget *widget = (gpointer)gui_window;
++ struct control *c;
++ int volume, mute;
+
+- memset(&mctl, 0, sizeof(mctl));
+- mctl.dev = devinfo->index;
+- mctl.type = devinfo->type;
+- mctl.un.value.num_channels = devinfo->un.v.num_channels;
+- for (i = 0; i < devinfo->un.v.num_channels; i++)
+- mctl.un.value.level[i] = volume;
+- error = ioctl(fd, AUDIO_MIXER_WRITE, &mctl);
+- if (error == -1)
+- return (-1);
+- return (0);
++ for (c = controls; ; c = c->next) {
++ if (c == NULL)
++ return;
++ if (c->addr == addr)
++ break;
++ }
++
++ c->value = value;
++
++ /*
++ * refresh gui state
++ */
++
++ get_state(&volume, &mute);
++
++ set_tray_icon(mute ? 0 : volume);
++
++ /* Move slider. Change "check" button state. */
++ if (widget->window != NULL) {
++ gtk_adjustment_set_value(GTK_ADJUSTMENT(gui_adj),
++    100 - volume);
++ gtk_toggle_button_set_active(
++    GTK_TOGGLE_BUTTON(gui_check), mute);
++ gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !mute);
++ }
+ }
+
+-static int
+-get_mute(int fd, mixer_devinfo_t *devinfo, int *mute)
++/*
++ * Return current volume and mute states:
++ *   - the returned volume is the maximum volume of all channels.
++ *   - the returned mute set 1 if all channels are muted
++ */
++static void
++get_state(int *rvolume, int *rmute)
+ {
+- mixer_ctrl_t mctl;
+- int error;
+-
+- memset(&mctl, 0, sizeof(mctl));
+- mctl.dev = devinfo->index;
+- mctl.type = AUDIO_MIXER_ENUM;
+- error = ioctl(fd, AUDIO_MIXER_READ, &mctl);
+- if (error == -1)
+- return (-1);
+- *mute = mctl.un.ord;
+- return (0);
++ struct control *c;
++ int mute = 0;
++ int v, volume = 100;
++
++ for (c = controls; c != NULL; c = c->next) {
++ if (c->ismute) {
++ if (c->value)
++ mute = 1;
++ } else {
++ v = (c->value * 100 + c->max / 2) / c->max;
++ if (v < volume)
++ volume = v;
++ }
++ }
++ *rvolume = volume;
++ *rmute = mute;
+ }
+
+-static int
+-set_mute(int fd, mixer_devinfo_t *devinfo, int mute)
++static void
++set_state(int ismute, int value)
+ {
+- mixer_ctrl_t mctl;
+- int error;
++ struct control *c;
++ int v;
+
+- memset(&mctl, 0, sizeof(mctl));
+- mctl.dev = devinfo->index;
+- mctl.type = devinfo->type;
+- mctl.un.ord = mute;
+- error = ioctl(fd, AUDIO_MIXER_WRITE, &mctl);
+- if (error == -1)
+- return (-1);
+- return (0);
++ for (c = controls; c != NULL; c = c->next) {
++ v = (value * c->max + 50) / 100;
++ if (c->ismute == ismute && c->value == v)
++ continue;
++ c->value = v;
++ sioctl_setval(hdl, c->addr, v);
++ }
+ }
+
+ static void
+@@ -182,63 +216,31 @@ prepare_tooltip(int mute, u_char volume, char *text, s
+ if (mute) {
+ strlcpy(text, "Audio is muted", sz);
+ } else {
+- snprintf(text, sz, "Audio volume: %u%%",
+-    100U * (u_int)volume / AUDIO_MAX_GAIN);
++ snprintf(text, sz, "Audio volume: %u%%", volume);
+ }
+ }
+
+-static gboolean
+-cb_timer(GtkWidget *widget)
+-{
+- int mute;
+- u_char volume;
+-
+- if (get_mute(mixer_fd, outputs_master_dev + 1, &mute) == -1)
+- return (TRUE);
+- else if (get_volume(mixer_fd, outputs_master_dev, &volume) == -1)
+- return (TRUE);
+- if (mute != current_mute || volume != current_volume) {
+- set_tray_icon(mute ? 0 : volume);
+-
+- /* Move slider. Change "check" button state. */
+- if (widget->window != NULL) {
+- gtk_adjustment_set_value(GTK_ADJUSTMENT(gui_adj),
+-    AUDIO_MAX_GAIN - volume);
+- gtk_toggle_button_set_active(
+-    GTK_TOGGLE_BUTTON(gui_check), mute);
+- gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !mute);
+- }
+-
+- current_volume = volume;
+- current_mute = mute;
+- }
+-
+- return (TRUE);
+-}
+-
+ static void
+ cb_button_toggled(GtkWidget *widget, gpointer data)
+ {
+- int mute;
++ int mute, volume;
+
+ mute = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+- if (set_mute(mixer_fd, outputs_master_dev + 1, mute) == 0) {
+- gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !mute);
+- set_tray_icon(mute ? 0 : current_volume);
+- current_mute = mute;
+- }
++ set_state(1, mute);
++ get_state(&volume, &mute);
++ gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !mute);
++ set_tray_icon(mute ? 0 : volume);
+ }
+
+ static void
+ cb_scale_value_changed(GtkScale *scale, GtkAdjustment *adj)
+ {
+- u_char volume;
++ int mute, volume;
+
+- volume = AUDIO_MAX_GAIN - (u_char)gtk_adjustment_get_value(adj);
+- if (set_volume(mixer_fd, outputs_master_dev, volume) == 0) {
+- set_tray_icon(current_mute ? 0 : volume);
+- current_volume = volume;
+- }
++ volume = 100 - (int)gtk_adjustment_get_value(adj);
++ set_state(0, volume);
++ get_state(&volume, &mute);
++ set_tray_icon(mute ? 0 : volume);
+ }
+
+ static gboolean
+@@ -277,8 +279,10 @@ cb_tray_query_tooltip(GtkStatusIcon *icon, gint x, gin
+     gboolean keyboard_mode, GtkTooltip *tooltip, gpointer data)
+ {
+ char text[30];
++ int volume, mute;
+
+- prepare_tooltip(current_mute, current_volume, text, sizeof(text));
++ get_state(&volume, &mute);
++ prepare_tooltip(mute, volume, text, sizeof(text));
+ gtk_tooltip_set_text(tooltip, text);
+ return (TRUE);
+ }
+@@ -304,8 +308,7 @@ init_gui(void)
+ gtk_window_set_deletable(GTK_WINDOW(gui_window), FALSE);
+ gtk_window_set_decorated(GTK_WINDOW(gui_window), FALSE);
+
+- gui_adj = gtk_adjustment_new(0.0, AUDIO_MIN_GAIN,
+-    AUDIO_MAX_GAIN, 1.0, 10.0, 0.0);
++ gui_adj = gtk_adjustment_new(0.0, 0, 100, 1.0, 10.0, 0.0);
+ if (gui_adj == NULL)
+ return (-1);
+
+@@ -355,12 +358,15 @@ show_gui(void)
+ GdkRectangle area;
+ GtkOrientation orientation;
+ int width, height, x, y;
++ int volume, mute;
+
++ get_state(&volume, &mute);
++
+ gtk_adjustment_set_value(GTK_ADJUSTMENT(gui_adj),
+-    AUDIO_MAX_GAIN - current_volume);
++    100 - volume);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui_check),
+-    current_mute);
+- gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !current_mute);
++    mute);
++ gtk_widget_set_sensitive(GTK_WIDGET(gui_scale), !mute);
+ gtk_widget_show_all(GTK_WIDGET(gui_window));
+
+ gtk_status_icon_get_geometry(tray_icon, NULL, &area, &orientation);
+@@ -395,8 +401,7 @@ static int
+ init_tray(int doinvert)
+ {
+ char tooltip[30];
+- u_char volume;
+- int mute;
++ int volume, mute;
+
+ tray_pixbuf = gdk_pixbuf_new_from_xpm_data(sound_xpm);
+ if (tray_pixbuf == NULL)
+@@ -421,13 +426,9 @@ init_tray(int doinvert)
+ tray_icon = gtk_status_icon_new();
+ if (tray_icon == NULL)
+ return (-1);
+- volume = 0;
+- mute = 0;
+- get_volume(mixer_fd, outputs_master_dev, &volume);
+- get_mute(mixer_fd, outputs_master_dev + 1, &mute);
++
++ get_state(&volume, &mute);
+ set_tray_icon(mute ? 0 : volume);
+- current_volume = volume;
+- current_mute = mute;
+
+ prepare_tooltip(mute, volume, tooltip, sizeof(tooltip));
+ gtk_status_icon_set_tooltip_text(tray_icon, tooltip);
+@@ -455,7 +456,43 @@ set_tray_icon(u_char volume)
+ gtk_status_icon_set_from_pixbuf(tray_icon, pb);
+ }
+
++/*
++ * Call poll(2), for both gtk and sndio descriptors.
++ */
+ int
++do_poll(GPollFD *gtk_pfds, guint gtk_nfds, gint timeout)
++{
++#define MAXFDS 64
++ struct pollfd pfds[MAXFDS], *sioctl_pfds;
++ unsigned int sioctl_nfds;
++ unsigned int i;
++ int revents;
++ int rc;
++
++ for (i = 0; i < gtk_nfds; i++) {
++ pfds[i].fd = gtk_pfds[i].fd;
++ pfds[i].events = gtk_pfds[i].events;
++ }
++ if (hdl != NULL) {
++ sioctl_pfds = pfds + gtk_nfds;
++ sioctl_nfds = sioctl_pollfd(hdl, sioctl_pfds, POLLIN);
++ } else
++ sioctl_nfds = 0;
++
++ rc = poll(pfds, gtk_nfds + sioctl_nfds, timeout);
++ if (rc > 0 && hdl != NULL) {
++ revents = sioctl_revents(hdl, sioctl_pfds);
++ if (revents & POLLHUP)
++ errx(1, "Device disconnected");
++ }
++
++ for (i = 0; i < gtk_nfds; i++)
++ gtk_pfds[i].revents = pfds[i].revents;
++
++ return rc;
++}
++
++int
+ main(int argc, char **argv)
+ {
+ char *progname;
+@@ -481,30 +518,32 @@ main(int argc, char **argv)
+ }
+ argc -= optind;
+ argv += optind;
+-
+- mixer_fd = open("/dev/mixer", O_RDWR);
+- if (mixer_fd == -1)
++
++ hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0);
++ if (hdl == NULL) {
+ errx(1, "Cannot open mixer device");
+-
+- error = get_mixer_index(mixer_fd, outputs_master_dev);
+- if (error == -1) {
+- close(mixer_fd);
++ }
++ if (!sioctl_ondesc(hdl, cb_control_desc, NULL)) {
++ sioctl_close(hdl);
+ errx(1, "Cannot get mixer information");
+ }
+-
+ error = init_tray(invert_flag);
+ if (error == -1) {
+- close(mixer_fd);
++ sioctl_close(hdl);
+ errx(1, "Cannot initialize notification area");
+ }
+ error = init_gui();
+ if (error == -1) {
+- close(mixer_fd);
++ sioctl_close(hdl);
+ errx(1, "Cannot initialize program window");
+ }
+- g_timeout_add(1000, (GSourceFunc)cb_timer, (gpointer)gui_window);
++
++ /* register call-back for external volume changes */
++ sioctl_onval(hdl, cb_control_value, NULL);
++
++ g_main_context_set_poll_func(g_main_context_default(), do_poll);
+ gtk_main();
+
+- close(mixer_fd);
++ sioctl_close(hdl);
+ return (0);
+ }
Index: audio/gqmpeg/Makefile
===================================================================
RCS file: /cvs/ports/audio/gqmpeg/Makefile,v
retrieving revision 1.64
diff -u -p -u -p -r1.64 Makefile
--- audio/gqmpeg/Makefile 12 Jul 2019 20:43:33 -0000 1.64
+++ audio/gqmpeg/Makefile 22 Feb 2020 16:06:19 -0000
@@ -3,7 +3,7 @@
 COMMENT= front-end to various audio players
 
 DISTNAME= gqmpeg-0.91.1
-REVISION= 14
+REVISION= 15
 CATEGORIES= audio
 
 HOMEPAGE= http://gqmpeg.sourceforge.net/
Index: audio/gqmpeg/patches/patch-src_Makefile_in
===================================================================
RCS file: audio/gqmpeg/patches/patch-src_Makefile_in
diff -N audio/gqmpeg/patches/patch-src_Makefile_in
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ audio/gqmpeg/patches/patch-src_Makefile_in 22 Feb 2020 16:06:19 -0000
@@ -0,0 +1,14 @@
+$OpenBSD$
+
+Index: src/Makefile.in
+--- src/Makefile.in.orig
++++ src/Makefile.in
+@@ -342,7 +342,7 @@ gqmpeg_SOURCES = \
+ $(module_mpg123) $(module_xmp) $(module_ogg123) $(module_radio)
+
+
+-gqmpeg_LDADD = $(GTK_LIBS) $(LIBPNG)
++gqmpeg_LDADD = $(GTK_LIBS) $(LIBPNG) -lsndio
+
+ EXTRA_DIST = \
+ $(extra_SLIK) \
Index: audio/gqmpeg/patches/patch-src_mixer_c
===================================================================
RCS file: /cvs/ports/audio/gqmpeg/patches/patch-src_mixer_c,v
retrieving revision 1.2
diff -u -p -u -p -r1.2 patch-src_mixer_c
--- audio/gqmpeg/patches/patch-src_mixer_c 14 Oct 2007 14:12:42 -0000 1.2
+++ audio/gqmpeg/patches/patch-src_mixer_c 22 Feb 2020 16:06:19 -0000
@@ -1,39 +1,288 @@
 $OpenBSD: patch-src_mixer_c,v 1.2 2007/10/14 14:12:42 jasper Exp $
---- src/mixer.c.orig Tue Sep 10 16:16:26 2002
-+++ src/mixer.c Sun Oct 14 15:47:27 2007
-@@ -285,7 +285,11 @@ void mixer_init(gint init_device_id)
-
-   mixer_device = getenv("MIXERDEVICE");
-   if (mixer_device == NULL)
-+#ifdef __OpenBSD__
-+    mixer_device = "/dev/mixer";
-+#else
-     mixer_device = "/dev/mixer0";
-+#endif
+Index: src/mixer.c
+--- src/mixer.c.orig
++++ src/mixer.c
+@@ -39,10 +39,16 @@
+ #include <sys/soundcard.h>
+ #endif
 
-   if ((fd = open(mixer_device, O_RDWR)) == -1) {
-     perror(mixer_device);
-@@ -362,7 +366,11 @@ static void mixer_set_vol(DeviceData *device, gint vol
-
-   mixer_device = getenv("MIXERDEVICE");
-   if (mixer_device == NULL)
-+#ifdef __OpenBSD__
-+    mixer_device = "/dev/mixer";
-+#else
-     mixer_device = "/dev/mixer0";
-+#endif
+-#if defined(__NetBSD__) || defined(__OpenBSD__)
++#if defined(__NetBSD__)
+ #include <sys/audioio.h>
+ #endif
 
-   if ((fd = open(mixer_device, O_RDWR)) == -1) {
-     perror(mixer_device);
-@@ -406,7 +414,11 @@ static gint mixer_get_vol(DeviceData *device)
-
-   mixer_device = getenv("MIXERDEVICE");
-   if (mixer_device == NULL)
-+#ifdef __OpenBSD__
-+    mixer_device = "/dev/mixer";
-+#else
-     mixer_device = "/dev/mixer0";
++#if defined(__OpenBSD__)
++#include <poll.h>
++#include <sndio.h>
++#include "display.h"
 +#endif
++
+ #if defined(sun) && defined(__svr4__)
+ #include <sys/audioio.h>
+ #endif
+@@ -267,11 +273,11 @@ static gint mixer_get_vol(DeviceData *device)
+
+ /*
+  *--------------------------------------------------------------------
+- * NetBSD and OpenBSD
++ * NetBSD
+  *--------------------------------------------------------------------
+  */
+
+-#elif defined(__NetBSD__) || defined(__OpenBSD__)
++#elif defined(__NetBSD__)
+
+ mixer_devinfo_t *infos;
+ mixer_ctrl_t *values;
+@@ -442,6 +448,242 @@ static gint mixer_get_vol(DeviceData *device)
+
+ /*
+  *--------------------------------------------------------------------
++ * OpenBSD
++ *--------------------------------------------------------------------
++ */
++
++#elif defined(__OpenBSD__)
++
++struct control {
++ struct control *next;
++ unsigned int addr;
++ unsigned int max, value;
++};
++
++static struct control *controls;
++static struct sioctl_hdl *hdl;
++static struct pollfd *pfds;
++static int initialized;
++
++/*
++ * new control registered
++ */
++static void ondesc(void *unused, struct sioctl_desc *d, int val)
++{
++ struct control *i, **pi;
++
++ if (d == NULL)
++ return;
++
++ /*
++ * delete existing control with the same address
++ */
++ for (pi = &controls; (i = *pi) != NULL; pi = &i->next) {
++ if (d->addr == i->addr) {
++ *pi = i->next;
++ free(i);
++ break;
++ }
++ }
++
++ /*
++ * SIOCTL_NONE means control was deleted from the device
++ */
++ if (d->type == SIOCTL_NONE)
++ return;
++
++ /*
++ * we're interested in top-level output.level controls only
++ */
++ if (d->group[0] != 0 ||
++    strcmp(d->node0.name, "output") != 0 ||
++    strcmp(d->func, "level") != 0)
++ return;
++
++ i = malloc(sizeof(struct control));
++ if (i == NULL) {
++ perror("malloc");
++ return;
++ }
++
++ i->addr = d->addr;
++ i->max = d->maxval;
++ i->value = val;
++ i->next = controls;
++ controls = i;
++
++ if (debug_mode)
++ fprintf(stderr, "found output.level at %d\n", i->addr);
++}
++
++/*
++ * control value changed
++ */
++static void onval(void *unused, unsigned int addr, unsigned int value)
++{
++ struct control *c;
++
++ if (debug_mode)
++ fprintf(stderr, "control %d changed to %d\n", addr, value);
++
++ for (c = controls; ; c = c->next) {
++ if (c == NULL)
++ return;
++ if (c->addr == addr)
++ break;
++ }
++
++ c->value = value;
++
++ if (debug_mode)
++ fprintf(stderr, "refreshing\n");
++ display_set_volume();
++}
++
++/*
++ * Call poll(2), for both gtk and sndio descriptors.
++ */
++int
++do_poll(GPollFD *gtk_pfds, guint gtk_nfds, gint timeout)
++{
++#define MAXFDS 64
++ struct pollfd pfds[MAXFDS], *sioctl_pfds;
++ unsigned int sioctl_nfds;
++ unsigned int i;
++ int revents;
++ int rc;
++
++ for (i = 0; i < gtk_nfds; i++) {
++ pfds[i].fd = gtk_pfds[i].fd;
++ pfds[i].events = gtk_pfds[i].events;
++ }
++ if (hdl != NULL) {
++ sioctl_pfds = pfds + gtk_nfds;
++ sioctl_nfds = sioctl_pollfd(hdl, sioctl_pfds, POLLIN);
++ } else
++ sioctl_nfds = 0;
++
++ rc = poll(pfds, gtk_nfds + sioctl_nfds, timeout);
++ if (rc > 0 && hdl != NULL) {
++ revents = sioctl_revents(hdl, sioctl_pfds);
++ if (revents & POLLHUP) {
++ fprintf(stderr, "Device disconnected\n");
++ sioctl_close(hdl);
++ hdl = NULL;
++ }
++ }
++
++ for (i = 0; i < gtk_nfds; i++)
++ gtk_pfds[i].revents = pfds[i].revents;
++
++ return rc;
++}
++
++void mixer_init(gint init_device_id)
++{
++ if (debug_mode)
++ fprintf(stderr, "mixer, initializing...\n");
++
++ if (initialized) {
++ fprintf(stderr, "mixer, already initialized\n");
++ return;
++ }
++
++ initialized = 1;
++
++ hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0);
++ if (hdl == NULL) {
++ fprintf(stderr, "Cannot open audio control device\n");
++ mixer_enabled = FALSE;
++ return;
++ }
++ if (!sioctl_ondesc(hdl, ondesc, NULL)) {
++ sioctl_close(hdl);
++ fprintf(stderr, "Cannot get mixer information\n");
++ mixer_enabled = FALSE;
++ return;
++ }
++
++ /* register call-back for external volume changes */
++ if (!sioctl_onval(hdl, onval, NULL)) {
++ sioctl_close(hdl);
++ fprintf(stderr, "Cannot get mixer values\n");
++ mixer_enabled = FALSE;
++ return;
++ }
++
++ pfds = malloc(sizeof(struct pollfd) * sioctl_nfds(hdl));
++ if (pfds == NULL) {
++ sioctl_close(hdl);
++ fprintf(stderr, "Cannot allocate pollfd structures\n");
++ mixer_enabled = FALSE;
++ return;
++ }
++
++ if (controls != NULL) {
++ DeviceData *device = g_new0(DeviceData, 1);
++ device->device_id = 0;
++ device->device_name = "output.level";
++ device->stereo = (controls->next != NULL);
++ device->recordable = 0;
++ device_list = g_list_append(device_list, device);
++ current_device = device_list->data;
++ current_vol = mixer_get_vol(current_device);
++ mixer_enabled = TRUE;
++ } else
++ mixer_enabled = FALSE;
++
++ if (debug_mode)
++ fprintf(stderr, "setting gtk poll function\n");
++ g_main_context_set_poll_func(g_main_context_default(), do_poll);
++
++}
++
++static void mixer_poll(void)
++{
++ int n, nfds;
++
++ nfds = sioctl_pollfd(hdl, pfds, 0);
++ if (nfds > 0) {
++ n = poll(pfds, nfds, 0);
++ if (n >= 0)
++ sioctl_revents(hdl, pfds);
++ }
++}
++
++static void mixer_set_vol(DeviceData *device, gint vol)
++{
++ struct control *c;
++
++ if (hdl == NULL)
++ return;
++
++ for (c = controls; c != NULL; c = c->next) {
++ sioctl_setval(hdl, c->addr, (vol * c->max + 50) / 100);
++ if (debug_mode)
++ fprintf(stderr, "setting %d to %d%%\n", c->addr, vol);
++ }
++}
++
++static gint mixer_get_vol(DeviceData *device)
++{
++ struct control *c;
++ int vol, minvol = 100;
++
++ for (c = controls; c != NULL; c = c->next) {
++ vol = (c->value * 100 + c->max / 2) / c->max;
++ if (vol < minvol)
++ minvol = vol;
++ }
++
++ if (debug_mode)
++ fprintf(stderr, "get volume: %d\n", minvol);
++
++ return minvol;
++}
++
++/*
++ *--------------------------------------------------------------------
+  * Sun (svr4)
+  *--------------------------------------------------------------------
+  */
+@@ -1266,7 +1508,7 @@ gint get_volume(void)
+  * but some platforms did not have it update the volume (mixer_get_vol),
+  * and I am not going to mess with it.
+  */
+-#if defined (linux) || defined (__FreeBSD__)
++#if defined (linux) || defined (__FreeBSD__) || defined (__OpenBSD__)
+ current_vol = mixer_get_vol(current_device);
+ #endif
 
-   if ((fd = open(mixer_device, O_RDWR)) == -1) {
-     perror(mixer_device);
Index: x11/i3status/Makefile
===================================================================
RCS file: /cvs/ports/x11/i3status/Makefile,v
retrieving revision 1.58
diff -u -p -u -p -r1.58 Makefile
--- x11/i3status/Makefile 27 Sep 2019 20:33:22 -0000 1.58
+++ x11/i3status/Makefile 22 Feb 2020 16:06:19 -0000
@@ -5,7 +5,7 @@ ONLY_FOR_ARCHS= ${APM_ARCHS}
 COMMENT= generate a statusbar for use with i3/xmobar/dzen2
 
 DISTNAME= i3status-2.13
-REVISION= 1
+REVISION= 2
 CATEGORIES= x11 sysutils
 
 HOMEPAGE= https://i3wm.org/i3status/
@@ -28,7 +28,9 @@ BUILD_DEPENDS= textproc/asciidoc>=8.6.8
 LIB_DEPENDS= devel/libconfuse \
  devel/libyajl
 
-CONFIGURE_STYLE = gnu
+AUTOCONF_VERSION = 2.69
+AUTOMAKE_VERSION = 1.16
+CONFIGURE_STYLE = autoreconf
 SEPARATE_BUILD = Yes
 
 FAKE_FLAGS += sysconfdir=${PREFIX}/share/examples/i3status/
Index: x11/i3status/patches/patch-Makefile_am
===================================================================
RCS file: x11/i3status/patches/patch-Makefile_am
diff -N x11/i3status/patches/patch-Makefile_am
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ x11/i3status/patches/patch-Makefile_am 22 Feb 2020 16:06:19 -0000
@@ -0,0 +1,37 @@
+$OpenBSD$
+
+Index: Makefile.am
+--- Makefile.am.orig
++++ Makefile.am
+@@ -1,5 +1,3 @@
+-@CODE_COVERAGE_RULES@
+-
+ echo-version:
+ @echo "@I3STATUS_VERSION@"
+
+@@ -30,6 +28,7 @@ i3status_CFLAGS = \
+ $(PULSE_CFLAGS) \
+ $(NLGENL_CFLAGS) \
+ $(ALSA_CFLAGS) \
++ $(SNDIO_CFLAGS) \
+ $(PTHREAD_CFLAGS)
+
+ i3status_CPPFLAGS = \
+@@ -42,6 +41,7 @@ i3status_LDADD = \
+ $(PULSE_LIBS) \
+ $(NLGENL_LIBS) \
+ $(ALSA_LIBS) \
++ $(SNDIO_LIBS) \
+ $(PTHREAD_LIBS)
+
+ i3status_SOURCES = \
+@@ -69,7 +69,8 @@ i3status_SOURCES = \
+ src/print_wireless_info.c \
+ src/print_file_contents.c \
+ src/process_runs.c \
+- src/pulse.c
++ src/pulse.c \
++ src/sndio.c
+
+ dist_sysconf_DATA = \
+ i3status.conf
Index: x11/i3status/patches/patch-Makefile_in
===================================================================
RCS file: x11/i3status/patches/patch-Makefile_in
diff -N x11/i3status/patches/patch-Makefile_in
--- x11/i3status/patches/patch-Makefile_in 6 Jul 2019 20:20:27 -0000 1.1
+++ /dev/null 1 Jan 1970 00:00:00 -0000
@@ -1,15 +0,0 @@
-$OpenBSD: patch-Makefile_in,v 1.1 2019/07/06 20:20:27 jasper Exp $
-
-The CODE_COVERAGE_RULES fragment contains an unmatched "if" clause.
-
-Index: Makefile.in
---- Makefile.in.orig
-+++ Makefile.in
-@@ -1851,7 +1851,6 @@ uninstall-man: uninstall-man1
-
- .PRECIOUS: Makefile
-
--@CODE_COVERAGE_RULES@
-
- echo-version:
- @echo "@I3STATUS_VERSION@"
Index: x11/i3status/patches/patch-configure_ac
===================================================================
RCS file: x11/i3status/patches/patch-configure_ac
diff -N x11/i3status/patches/patch-configure_ac
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ x11/i3status/patches/patch-configure_ac 22 Feb 2020 16:06:19 -0000
@@ -0,0 +1,19 @@
+$OpenBSD$
+
+Index: configure.ac
+--- configure.ac.orig
++++ configure.ac
+@@ -91,6 +91,13 @@ case $host_os in
+ ;;
+ esac
+
++# if sndio is available, define USE_SNDIO
++AC_CHECK_HEADER(sndio.h,
++ [AC_CHECK_LIB([sndio], [sio_open], [
++ AC_SUBST(SNDIO_LIBS, "-lsndio")
++ AC_DEFINE([USE_SNDIO], [], [Use sndio])
++ ], [])], [])
++
+ dnl TODO: check for libbsd for GNU/kFreeBSD
+
+ # Checks for programs.
Index: x11/i3status/patches/patch-include_i3status_h
===================================================================
RCS file: x11/i3status/patches/patch-include_i3status_h
diff -N x11/i3status/patches/patch-include_i3status_h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ x11/i3status/patches/patch-include_i3status_h 22 Feb 2020 16:06:19 -0000
@@ -0,0 +1,13 @@
+$OpenBSD$
+
+Index: include/i3status.h
+--- include/i3status.h.orig
++++ include/i3status.h
+@@ -232,6 +232,7 @@ int volume_pulseaudio(uint32_t sink_idx, const char *s
+ bool description_pulseaudio(uint32_t sink_idx, const char *sink_name, char buffer[MAX_SINK_DESCRIPTION_LEN]);
+ bool pulse_initialize(void);
+ void print_file_contents(yajl_gen json_gen, char *buffer, const char *title, const char *path, const char *format, const char *format_bad, const int max_chars);
++int volume_sndio(void);
+
+ /* socket file descriptor for general purposes */
+ extern int general_socket;
Index: x11/i3status/patches/patch-src_print_volume_c
===================================================================
RCS file: x11/i3status/patches/patch-src_print_volume_c
diff -N x11/i3status/patches/patch-src_print_volume_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ x11/i3status/patches/patch-src_print_volume_c 22 Feb 2020 16:06:19 -0000
@@ -0,0 +1,144 @@
+$OpenBSD$
+
+Index: src/print_volume.c
+--- src/print_volume.c.orig
++++ src/print_volume.c
+@@ -21,13 +21,6 @@
+ #include <sys/soundcard.h>
+ #endif
+
+-#ifdef __OpenBSD__
+-#include <fcntl.h>
+-#include <unistd.h>
+-#include <sys/audioio.h>
+-#include <sys/ioctl.h>
+-#endif
+-
+ #include "i3status.h"
+ #include "queue.h"
+
+@@ -145,9 +138,20 @@ void print_volume(yajl_gen json_gen, char *buffer, con
+         /* negative result or NULL description means error, fail PulseAudio attempt */
+     }
+ /* If some other device was specified or PulseAudio is not detected,
+- * proceed to ALSA / OSS */
++ * proceed to sndio / ALSA / OSS */
+ #endif
+
++#ifdef USE_SNDIO
++    int vol;
++    
++ vol = volume_sndio();
++ if (vol != -1) {
++        outwalk = apply_volume_format(fmt, outwalk, vol & 0x7f, "sndio");
++        goto out;
++    }
++/* If sndio is not detected, proceed to ALSA / OSS */
++#endif
++
+ #ifdef __linux__
+     const long MAX_LINEAR_DB_SCALE = 24;
+     int err;
+@@ -248,7 +252,8 @@ void print_volume(yajl_gen json_gen, char *buffer, con
+     snd_mixer_selem_id_free(sid);
+
+ #endif
+-#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
++
++#if defined(__FreeBSD__) || defined(__DragonFly__)
+     char *mixerpath;
+     char defaultmixer[] = "/dev/mixer";
+     int mixfd, vol, devmask = 0;
+@@ -261,84 +266,13 @@ void print_volume(yajl_gen json_gen, char *buffer, con
+         mixerpath = defaultmixer;
+
+     if ((mixfd = open(mixerpath, O_RDWR)) < 0) {
+-#if defined(__OpenBSD__)
+-        warn("audioio: Cannot open mixer");
+-#else
+         warn("OSS: Cannot open mixer");
+-#endif
+         goto out;
+     }
+
+     if (mixer_idx > 0)
+         free(mixerpath);
+
+-#if defined(__OpenBSD__)
+-    int oclass_idx = -1, master_idx = -1, master_mute_idx = -1;
+-    int master_next = AUDIO_MIXER_LAST;
+-    mixer_devinfo_t devinfo, devinfo2;
+-    mixer_ctrl_t vinfo;
+-
+-    devinfo.index = 0;
+-    while (ioctl(mixfd, AUDIO_MIXER_DEVINFO, &devinfo) >= 0) {
+-        if (devinfo.type != AUDIO_MIXER_CLASS) {
+-            devinfo.index++;
+-            continue;
+-        }
+-        if (strncmp(devinfo.label.name, AudioCoutputs, MAX_AUDIO_DEV_LEN) == 0)
+-            oclass_idx = devinfo.index;
+-
+-        devinfo.index++;
+-    }
+-
+-    devinfo2.index = 0;
+-    while (ioctl(mixfd, AUDIO_MIXER_DEVINFO, &devinfo2) >= 0) {
+-        if ((devinfo2.type == AUDIO_MIXER_VALUE) && (devinfo2.mixer_class == oclass_idx) && (strncmp(devinfo2.label.name, AudioNmaster, MAX_AUDIO_DEV_LEN) == 0)) {
+-            master_idx = devinfo2.index;
+-            master_next = devinfo2.next;
+-        }
+-
+-        if ((devinfo2.type == AUDIO_MIXER_ENUM) && (devinfo2.mixer_class == oclass_idx) && (strncmp(devinfo2.label.name, AudioNmute, MAX_AUDIO_DEV_LEN) == 0))
+-            if (master_next == devinfo2.index)
+-                master_mute_idx = devinfo2.index;
+-
+-        if (master_next != AUDIO_MIXER_LAST)
+-            master_next = devinfo2.next;
+-        devinfo2.index++;
+-    }
+-
+-    if (master_idx == -1)
+-        goto out;
+-
+-    devinfo.index = master_idx;
+-    if (ioctl(mixfd, AUDIO_MIXER_DEVINFO, &devinfo) == -1)
+-        goto out;
+-
+-    vinfo.dev = master_idx;
+-    vinfo.type = AUDIO_MIXER_VALUE;
+-    vinfo.un.value.num_channels = devinfo.un.v.num_channels;
+-    if (ioctl(mixfd, AUDIO_MIXER_READ, &vinfo) == -1)
+-        goto out;
+-
+-    if (AUDIO_MAX_GAIN != 100) {
+-        float avgf = ((float)vinfo.un.value.level[AUDIO_MIXER_LEVEL_MONO] / AUDIO_MAX_GAIN) * 100;
+-        vol = (int)avgf;
+-        vol = (avgf - vol < 0.5 ? vol : (vol + 1));
+-    } else {
+-        vol = (int)vinfo.un.value.level[AUDIO_MIXER_LEVEL_MONO];
+-    }
+-
+-    vinfo.dev = master_mute_idx;
+-    vinfo.type = AUDIO_MIXER_ENUM;
+-    if (ioctl(mixfd, AUDIO_MIXER_READ, &vinfo) == -1)
+-        goto out;
+-
+-    if (master_mute_idx != -1 && vinfo.un.ord) {
+-        START_COLOR("color_degraded");
+-        fmt = fmt_muted;
+-        pbval = 0;
+-    }
+-
+-#else
+     if (ioctl(mixfd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) {
+         warn("OSS: Cannot read mixer information");
+         goto out;
+@@ -353,7 +287,6 @@ void print_volume(yajl_gen json_gen, char *buffer, con
+         pbval = 0;
+     }
+
+-#endif
+     outwalk = apply_volume_format(fmt, outwalk, vol & 0x7f, devicename);
+     close(mixfd);
+ #endif
Index: x11/i3status/patches/patch-src_sndio_c
===================================================================
RCS file: x11/i3status/patches/patch-src_sndio_c
diff -N x11/i3status/patches/patch-src_sndio_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ x11/i3status/patches/patch-src_sndio_c 22 Feb 2020 16:06:20 -0000
@@ -0,0 +1,176 @@
+$OpenBSD$
+
+Add sndio volume backend.
+
+Index: src/sndio.c
+--- src/sndio.c.orig
++++ src/sndio.c
+@@ -0,0 +1,168 @@
++#include <poll.h>
++#include <sndio.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include "i3status.h"
++
++struct control {
++    struct control *next;
++    unsigned int addr;
++    unsigned int max;
++    unsigned int value;
++};
++
++static int initialized;
++static struct sioctl_hdl *hdl;
++static struct control *controls;
++static struct pollfd *pfds;
++
++/*
++ * new control registered or control changed
++ */
++static void ondesc(void *unused, struct sioctl_desc *d, int val)
++{
++    struct control *i, **pi;
++
++    if (d == NULL)
++        return;
++
++    /*
++     * delete existing control with the same address
++     */
++    for (pi = &controls; (i = *pi) != NULL; pi = &i->next) {
++        if (d->addr == i->addr) {
++            *pi = i->next;
++            free(i);
++            break;
++        }
++    }
++
++    /*
++     * we're interested in top-level output.level controls only
++     */
++    if (d->type != SIOCTL_NUM ||
++        d->group[0] != 0 ||
++        strcmp(d->node0.name, "output") != 0 ||
++        strcmp(d->func, "level") != 0)
++        return;
++
++    i = malloc(sizeof(struct control));
++    if (i == NULL) {
++        fprintf(stderr, "sndio: failed to allocate control\n");
++        return;
++    }
++
++    i->addr = d->addr;
++    i->max = d->maxval;
++    i->value = val;
++    i->next = controls;
++    controls = i;
++}
++
++/*
++ * control value changed
++ */
++static void onval(void *unused, unsigned int addr, unsigned int value)
++{
++    struct control *c;
++
++    for (c = controls; ; c = c->next) {
++        if (c == NULL)
++            return;
++        if (c->addr == addr)
++            break;
++    }
++
++    c->value = value;
++}
++
++static void cleanup(void)
++{
++    struct control *c;
++
++    if (hdl) {
++        sioctl_close(hdl);
++        hdl = NULL;
++    }
++    if (pfds) {
++        free(pfds);
++        pfds = NULL;
++    }
++    while ((c = controls) != NULL) {
++        controls = c->next;
++        free(c);
++    }
++}
++
++static int init(void)
++{
++    /* open device */
++    hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ, 0);
++    if (hdl == NULL) {
++        fprintf(stderr, "sndio: cannot open device\n");
++        goto failed;
++    }
++
++    /* register call-back for control description changes */
++    if (!sioctl_ondesc(hdl, ondesc, NULL)) {
++        fprintf(stderr, "sndio: cannot get description\n");
++        goto failed;
++    }
++
++    /* register call-back for volume changes */
++    if (!sioctl_onval(hdl, onval, NULL)) {
++        fprintf(stderr, "sndio: cannot get values\n");
++        goto failed;
++    }
++
++    /* allocate structures for poll() syscall */
++    pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd));
++    if (pfds == NULL) {
++        fprintf(stderr, "sndio: cannot allocate pollfd structures\n");
++        goto failed;
++    }
++    return 1;
++failed:
++    cleanup();
++    return 0;
++}
++
++int volume_sndio(void)
++{
++    struct control *c;
++    int n, v, value;
++
++    if (!initialized) {
++        initialized = 1;
++        init();
++    }
++    if (hdl == NULL)
++        return -1;
++
++    /* check if controls changed */
++    n = sioctl_pollfd(hdl, pfds, POLLIN);
++    if (n > 0) {
++        n = poll(pfds, n, 0);
++        if (n > 0) {
++            if (sioctl_revents(hdl, pfds) & POLLHUP) {
++                fprintf(stderr, "sndio: disconnected\n");
++                cleanup();
++                return -1;
++            }
++        }
++    }
++
++    /*
++     * get control value: as there may be multiple
++     * channels, return the minimum
++     */
++    value = 100;
++    for (c = controls; c != NULL; c = c->next) {
++        v = (c->value * 100 + c->max / 2) / c->max;
++        if (v < value)
++            value = v;
++    }
++
++    return value;
++}