From aecb8e198594ee032277b784712935eb10fc4708 Mon Sep 17 00:00:00 2001 From: Alexandre Ratchov Date: Wed, 26 Feb 2020 13:53:59 +0000 Subject: Add API to control audio device parameters exposed by sndiod. The API exposes controls of modern audio hardware and sndiod software volume knobs in a uniform way. Hardware knobs are exposed through sndiod. Multiple programs may use the controls at the same time without the need to continuously scan the controls. For now sndiod exposes only its own controls and the master output and input volumes of the underlying hardware (if any), i.e. those typically exposed by acpi volume keys. ok deraadt --- lib/libsndio/Makefile | 7 +- lib/libsndio/Symbols.map | 12 ++ lib/libsndio/amsg.h | 41 +++- lib/libsndio/shlib_version | 2 +- lib/libsndio/sioctl.c | 177 ++++++++++++++++ lib/libsndio/sioctl_aucat.c | 273 +++++++++++++++++++++++++ lib/libsndio/sioctl_open.3 | 252 +++++++++++++++++++++++ lib/libsndio/sioctl_priv.h | 62 ++++++ lib/libsndio/sioctl_sun.c | 483 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 1304 insertions(+), 5 deletions(-) create mode 100644 lib/libsndio/sioctl.c create mode 100644 lib/libsndio/sioctl_aucat.c create mode 100644 lib/libsndio/sioctl_open.3 create mode 100644 lib/libsndio/sioctl_priv.h create mode 100644 lib/libsndio/sioctl_sun.c (limited to 'lib/libsndio') diff --git a/lib/libsndio/Makefile b/lib/libsndio/Makefile index 05e69a363e7..c5134fa4b42 100644 --- a/lib/libsndio/Makefile +++ b/lib/libsndio/Makefile @@ -1,9 +1,10 @@ -# $OpenBSD: Makefile,v 1.13 2017/12/26 15:23:33 jca Exp $ +# $OpenBSD: Makefile,v 1.14 2020/02/26 13:53:58 ratchov 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 diff --git a/lib/libsndio/Symbols.map b/lib/libsndio/Symbols.map index 171d696643f..a83e63d6a97 100644 --- a/lib/libsndio/Symbols.map +++ b/lib/libsndio/Symbols.map @@ -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: *; }; diff --git a/lib/libsndio/amsg.h b/lib/libsndio/amsg.h index a22ea0e1078..abea9088879 100644 --- a/lib/libsndio/amsg.h +++ b/lib/libsndio/amsg.h @@ -1,4 +1,4 @@ -/* $OpenBSD: amsg.h,v 1.12 2019/07/12 06:30:55 ratchov Exp $ */ +/* $OpenBSD: amsg.h,v 1.13 2020/02/26 13:53:58 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * @@ -42,6 +42,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 @@ -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,9 +116,40 @@ 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]; +}; + /* * Initialize an amsg structure: fill all fields with 0xff, so the read * can test which fields were set. diff --git a/lib/libsndio/shlib_version b/lib/libsndio/shlib_version index 5b844bbf422..b39addfcc64 100644 --- a/lib/libsndio/shlib_version +++ b/lib/libsndio/shlib_version @@ -1,2 +1,2 @@ major=7 -minor=0 +minor=1 diff --git a/lib/libsndio/sioctl.c b/lib/libsndio/sioctl.c new file mode 100644 index 00000000000..73bc5b5a213 --- /dev/null +++ b/lib/libsndio/sioctl.c @@ -0,0 +1,177 @@ +/* $OpenBSD: sioctl.c,v 1.1 2020/02/26 13:53:58 ratchov Exp $ */ +/* + * Copyright (c) 2014-2020 Alexandre Ratchov + * + * 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 +#include +#include +#include +#include + +#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); +} diff --git a/lib/libsndio/sioctl_aucat.c b/lib/libsndio/sioctl_aucat.c new file mode 100644 index 00000000000..b6de2744f96 --- /dev/null +++ b/lib/libsndio/sioctl_aucat.c @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2014-2020 Alexandre Ratchov + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/lib/libsndio/sioctl_open.3 b/lib/libsndio/sioctl_open.3 new file mode 100644 index 00000000000..64933fd7591 --- /dev/null +++ b/lib/libsndio/sioctl_open.3 @@ -0,0 +1,252 @@ +.\" $OpenBSD: sioctl_open.3,v 1.1 2020/02/26 13:53:58 ratchov Exp $ +.\" +.\" Copyright (c) 2011 Alexandre Ratchov +.\" +.\" 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: February 26 2020 $ +.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 +.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 diff --git a/lib/libsndio/sioctl_priv.h b/lib/libsndio/sioctl_priv.h new file mode 100644 index 00000000000..bfe806c0757 --- /dev/null +++ b/lib/libsndio/sioctl_priv.h @@ -0,0 +1,62 @@ +/* $OpenBSD: sioctl_priv.h,v 1.1 2020/02/26 13:53:58 ratchov Exp $ */ +/* + * Copyright (c) 2014-2020 Alexandre Ratchov + * + * 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 + +#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) */ diff --git a/lib/libsndio/sioctl_sun.c b/lib/libsndio/sioctl_sun.c new file mode 100644 index 00000000000..c1d3ac33dc7 --- /dev/null +++ b/lib/libsndio/sioctl_sun.c @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2014-2020 Alexandre Ratchov + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} -- cgit v1.2.3