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
This commit is contained in:
Ingo Feinerer 2020-05-09 12:03:20 +02:00 committed by Aaron Marcher
parent aaf279f6dd
commit 9ac721c23f
3 changed files with 161 additions and 51 deletions

View File

@ -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>

View File

@ -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)
*/

View File

@ -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