Use the sioctl_open(3) OpenBSD API to access vol Starting with OpenBSD 6.7 regular users cannot access raw audio devices anymore, for improved security. Instead use the sioctl_open(3) API to access and manipulate audio controls exposed by sndiod(8). On the first call a permanent connection is established with the running sndiod daemon, and call-back functions are registered which are triggered when audio controls are changed (e.g., a USB headset is attached) or when the volume is modified. On subsequent calls we poll for changes; if there are no volume changes this costs virtually nothing. Joint work with Alexandre Ratchov
Ingo Feinerer feinerer@logic.at
Sat, 09 May 2020 12:03:20 +0200
3 files changed,
161 insertions(+),
51 deletions(-)
M
components/volume.c
→
components/volume.c
@@ -8,69 +8,177 @@
#include "../util.h" #if defined(__OpenBSD__) - #include <sys/audioio.h> + #include <sys/queue.h> + #include <poll.h> + #include <sndio.h> + #include <stdlib.h> + + struct control { + LIST_ENTRY(control) next; + unsigned int addr; + #define CTRL_NONE 0 + #define CTRL_LEVEL 1 + #define CTRL_MUTE 2 + unsigned int type; + unsigned int maxval; + unsigned int val; + }; + + static LIST_HEAD(, control) controls = LIST_HEAD_INITIALIZER(controls); + static struct pollfd *pfds; + static struct sioctl_hdl *hdl; + static int initialized; + + /* + * Call-back to obtain the description of all audio controls. + */ + static void + ondesc(void *unused, struct sioctl_desc *desc, int val) + { + struct control *c, *ctmp; + unsigned int type = CTRL_NONE; + + if (desc == NULL) + return; + + /* Delete existing audio control with the same address. */ + LIST_FOREACH_SAFE(c, &controls, next, ctmp) { + if (desc->addr == c->addr) { + LIST_REMOVE(c, next); + free(c); + break; + } + } + + /* Only match output.level and output.mute audio controls. */ + if (desc->group[0] != 0 || + strcmp(desc->node0.name, "output") != 0) + return; + if (desc->type == SIOCTL_NUM && + strcmp(desc->func, "level") == 0) + type = CTRL_LEVEL; + else if (desc->type == SIOCTL_SW && + strcmp(desc->func, "mute") == 0) + type = CTRL_MUTE; + else + return; + + c = malloc(sizeof(struct control)); + if (c == NULL) { + warn("sndio: failed to allocate audio control\n"); + return; + } + + c->addr = desc->addr; + c->type = type; + c->maxval = desc->maxval; + c->val = val; + LIST_INSERT_HEAD(&controls, c, next); + } + + /* + * Call-back invoked whenever an audio control changes. + */ + static void + onval(void *unused, unsigned int addr, unsigned int val) + { + struct control *c; + + LIST_FOREACH(c, &controls, next) { + if (c->addr == addr) + break; + } + c->val = val; + } + + static void + cleanup(void) + { + struct control *c; + + if (hdl) { + sioctl_close(hdl); + hdl = NULL; + } + + free(pfds); + pfds = NULL; + + while (!LIST_EMPTY(&controls)) { + c = LIST_FIRST(&controls); + LIST_REMOVE(c, next); + free(c); + } + } + + static int + init(void) + { + hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ, 0); + if (hdl == NULL) { + warn("sndio: cannot open device"); + goto failed; + } + + if (!sioctl_ondesc(hdl, ondesc, NULL)) { + warn("sndio: cannot set control description call-back"); + goto failed; + } + + if (!sioctl_onval(hdl, onval, NULL)) { + warn("sndio: cannot set control values call-back"); + goto failed; + } + + pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd)); + if (pfds == NULL) { + warn("sndio: cannot allocate pollfd structures"); + goto failed; + } + + return 1; + failed: + cleanup(); + return 0; + } const char * - vol_perc(const char *card) + vol_perc(const char *unused) { - static int cls = -1; - mixer_devinfo_t mdi; - mixer_ctrl_t mc; - int afd = -1, m = -1, v = -1; + struct control *c; + int n, v, value; - if ((afd = open(card, O_RDONLY)) < 0) { - warn("open '%s':", card); + if (!initialized) + initialized = init(); + + if (hdl == NULL) return NULL; - } - for (mdi.index = 0; cls == -1; mdi.index++) { - if (ioctl(afd, AUDIO_MIXER_DEVINFO, &mdi) < 0) { - warn("ioctl 'AUDIO_MIXER_DEVINFO':"); - close(afd); - return NULL; - } - if (mdi.type == AUDIO_MIXER_CLASS && - !strncmp(mdi.label.name, - AudioCoutputs, - MAX_AUDIO_DEV_LEN)) - cls = mdi.index; - } - for (mdi.index = 0; v == -1 || m == -1; mdi.index++) { - if (ioctl(afd, AUDIO_MIXER_DEVINFO, &mdi) < 0) { - warn("ioctl 'AUDIO_MIXER_DEVINFO':"); - close(afd); - return NULL; - } - if (mdi.mixer_class == cls && - ((mdi.type == AUDIO_MIXER_VALUE && - !strncmp(mdi.label.name, - AudioNmaster, - MAX_AUDIO_DEV_LEN)) || - (mdi.type == AUDIO_MIXER_ENUM && - !strncmp(mdi.label.name, - AudioNmute, - MAX_AUDIO_DEV_LEN)))) { - mc.dev = mdi.index, mc.type = mdi.type; - if (ioctl(afd, AUDIO_MIXER_READ, &mc) < 0) { - warn("ioctl 'AUDIO_MIXER_READ':"); - close(afd); + n = sioctl_pollfd(hdl, pfds, POLLIN); + if (n > 0) { + n = poll(pfds, n, 0); + if (n > 0) { + if (sioctl_revents(hdl, pfds) & POLLHUP) { + warn("sndio: disconnected"); + cleanup(); return NULL; } - if (mc.type == AUDIO_MIXER_VALUE) - v = mc.un.value.num_channels == 1 ? - mc.un.value.level[AUDIO_MIXER_LEVEL_MONO] : - (mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] > - mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] ? - mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] : - mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT]); - else if (mc.type == AUDIO_MIXER_ENUM) - m = mc.un.ord; } } - close(afd); + value = 100; + LIST_FOREACH(c, &controls, next) { + if (c->type == CTRL_MUTE && c->val == 1) + value = 0; + else if (c->type == CTRL_LEVEL) { + v = (c->val * 100 + c->maxval / 2) / c->maxval; + /* For multiple channels return the minimum. */ + if (v < value) + value = v; + } + } - return bprintf("%d", m ? 0 : v * 100 / 255); + return bprintf("%d", value); } #else #include <sys/soundcard.h>
M
config.def.h
→
config.def.h
@@ -59,6 +59,7 @@ * uid UID of current user NULL
* uptime system uptime NULL * username username of current user NULL * vol_perc OSS/ALSA volume in percent mixer file (/dev/mixer) + * NULL on OpenBSD * wifi_perc WiFi signal in percent interface name (wlan0) * wifi_essid WiFi ESSID interface name (wlan0) */