--- /dev/null +++ src/sndio.c @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include +#include "i3status.h" + +struct control { + struct control *next; + char name[32]; + unsigned int addr; + unsigned int max; + unsigned int value; + unsigned int muted; + unsigned int muteaddr; +}; + +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; + } + } + + /* + * if we find an output.mute, associate it with its output.level + */ + if (d->type == SIOCTL_SW && + d->group[0] == 0 && + strcmp(d->node0.name, "output") == 0 && + strcmp(d->func, "mute") == 0) { + char name[32]; + snprintf(name, sizeof(name), "%s%d", d->node0.name, d->node0.unit); + for (pi = &controls; (i = *pi) != NULL; pi = &i->next) { + if (strcmp(name, i->name) == 0) { + i->muted = val; + i->muteaddr = d->addr; + break; + } + } + return; + } + + /* + * 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; + } + + snprintf(i->name, sizeof(i->name), "%s%d", d->node0.name, d->node0.unit); + i->addr = d->addr; + i->max = d->maxval; + i->value = val; + i->next = controls; + i->muted = 0; + i->muteaddr = -1; + 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) { + c->value = value; + return; + } + if (c->muteaddr == addr) { + c->muted = value; + return; + } + } +} + +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(int *vol, int *muted) +{ + struct control *c; + int n, v; + + 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 + */ + *vol = 100; + *muted = 0; + for (c = controls; c != NULL; c = c->next) { + v = (c->value * 100 + c->max / 2) / c->max; + if (v < *vol) { + *vol = v; + *muted = c->muted; + + } + } + + return 0; +} --- a/configure.ac +++ b/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. --- a/include/i3status.h +++ b/include/i3status.h @@ -425,6 +425,7 @@ void print_volume(volume_ctx_t *ctx); bool process_runs(const char *path); int volume_pulseaudio(uint32_t sink_idx, const char *sink_name); +int volume_sndio(int *vol, int *muted); bool description_pulseaudio(uint32_t sink_idx, const char *sink_name, char buffer[MAX_SINK_DESCRIPTION_LEN]); bool pulse_initialize(void);