Use the sioctl_open(3) OpenBSD API to access vol - slstatus - status monitor | |
git clone git://git.suckless.org/slstatus | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 9ac721c23fb640de2a6d1f84c84a79b2ccc26691 | |
parent aaf279f6ddfb48146fc1a579efd83a55722910b5 | |
Author: Ingo Feinerer <[email protected]> | |
Date: Sat, 9 May 2020 12:03:20 +0200 | |
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 | |
Diffstat: | |
M components/volume.c | 210 +++++++++++++++++++++++------… | |
M config.def.h | 1 + | |
M config.mk | 1 + | |
3 files changed, 161 insertions(+), 51 deletions(-) | |
--- | |
diff --git a/components/volume.c b/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_LEVE… | |
- (mc.un.value.level[AUDIO_MIXER_LEV… | |
- mc.un.value.level[AUDIO_MIXER_LEV… | |
- mc.un.value.level[AUDIO_MIXER_LEV… | |
- mc.un.value.level[AUDIO_MIXER_LEV… | |
- 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> | |
diff --git a/config.def.h b/config.def.h | |
@@ -59,6 +59,7 @@ static const char unknown_str[] = "n/a"; | |
* 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) | |
*/ | |
diff --git a/config.mk b/config.mk | |
@@ -14,6 +14,7 @@ X11LIB = /usr/X11R6/lib | |
CPPFLAGS = -I$(X11INC) -D_DEFAULT_SOURCE | |
CFLAGS = -std=c99 -pedantic -Wall -Wextra -Os | |
LDFLAGS = -L$(X11LIB) -s | |
+# OpenBSD: add -lsndio | |
LDLIBS = -lX11 | |
# compiler and linker |