298 lines
6.4 KiB
C
298 lines
6.4 KiB
C
/* See LICENSE file for copyright and license details. */
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
|
|
#include "../slstatus.h"
|
|
#include "../util.h"
|
|
|
|
const char *
|
|
vol_icon(const char *arg)
|
|
{
|
|
char *p;
|
|
const char *perc;
|
|
static const char *icons[] = { "", "", "" };
|
|
unsigned long ul_perc;
|
|
|
|
if (!(perc = vol_perc(arg)))
|
|
return NULL;
|
|
p = strrchr(perc, ' ');
|
|
ul_perc = strtoul(p ? p + 1 : perc, NULL, 10);
|
|
|
|
return bprintf("%s %d", p ? "" : icons[ul_perc / 34], ul_perc);
|
|
}
|
|
|
|
#if defined(__OpenBSD__) | defined(__FreeBSD__)
|
|
#include <poll.h>
|
|
#include <sndio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/queue.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 *unused)
|
|
{
|
|
struct control *c;
|
|
int n, v, value;
|
|
|
|
if (!initialized)
|
|
initialized = init();
|
|
|
|
if (hdl == NULL)
|
|
return NULL;
|
|
|
|
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();
|
|
initialized = 0;
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
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", value);
|
|
}
|
|
#elif defined(ALSA)
|
|
#include <alsa/asoundlib.h>
|
|
|
|
static const char *devname = "default";
|
|
const char *
|
|
vol_perc(const char *mixname)
|
|
{
|
|
snd_mixer_t *mixer = NULL;
|
|
snd_mixer_selem_id_t *mixid = NULL;
|
|
snd_mixer_elem_t *elem = NULL;
|
|
long min = 0, max = 0, volume = -1;
|
|
int err, sw1, sw2;
|
|
|
|
if ((err = snd_mixer_open(&mixer, 0))) {
|
|
warn("snd_mixer_open: %d", err);
|
|
return NULL;
|
|
}
|
|
if ((err = snd_mixer_attach(mixer, devname))) {
|
|
warn("snd_mixer_attach(mixer, \"%s\"): %d", devname, err);
|
|
goto cleanup;
|
|
}
|
|
if ((err = snd_mixer_selem_register(mixer, NULL, NULL))) {
|
|
warn("snd_mixer_selem_register(mixer, NULL, NULL): %d", err);
|
|
goto cleanup;
|
|
}
|
|
if ((err = snd_mixer_load(mixer))) {
|
|
warn("snd_mixer_load(mixer): %d", err);
|
|
goto cleanup;
|
|
}
|
|
|
|
snd_mixer_selem_id_alloca(&mixid);
|
|
snd_mixer_selem_id_set_name(mixid, mixname);
|
|
snd_mixer_selem_id_set_index(mixid, 0);
|
|
|
|
elem = snd_mixer_find_selem(mixer, mixid);
|
|
if (!elem) {
|
|
warn("snd_mixer_find_selem(mixer, \"%s\") == NULL", mixname);
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max))) {
|
|
warn("snd_mixer_selem_get_playback_volume_range(): %d", err);
|
|
goto cleanup;
|
|
}
|
|
if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &volume))) {
|
|
warn("snd_mixer_selem_get_playback_volume(): %d", err);
|
|
}
|
|
if ((err = snd_mixer_selem_get_playback_switch(elem, 0, &sw1))) {
|
|
warn("snd_mixer_selem_get_playback_switch(): %d", err);
|
|
}
|
|
if ((err = snd_mixer_selem_get_playback_switch(elem, 1, &sw2))) {
|
|
warn("snd_mixer_selem_get_playback_switch(): %d", err);
|
|
}
|
|
|
|
cleanup:
|
|
snd_mixer_free(mixer);
|
|
snd_mixer_detach(mixer, devname);
|
|
snd_mixer_close(mixer);
|
|
|
|
return volume == -1 ? NULL : bprintf("%s%.0f",
|
|
!(sw1 || sw2) ? "muted " : "", (volume-min)*100./(max-min));
|
|
}
|
|
#else
|
|
#include <sys/soundcard.h>
|
|
|
|
const char *
|
|
vol_perc(const char *card)
|
|
{
|
|
size_t i;
|
|
int v, afd, devmask;
|
|
char *vnames[] = SOUND_DEVICE_NAMES;
|
|
|
|
if ((afd = open(card, O_RDONLY | O_NONBLOCK)) < 0) {
|
|
warn("open '%s':", card);
|
|
return NULL;
|
|
}
|
|
|
|
if (ioctl(afd, (int)SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
|
|
warn("ioctl 'SOUND_MIXER_READ_DEVMASK':");
|
|
close(afd);
|
|
return NULL;
|
|
}
|
|
for (i = 0; i < LEN(vnames); i++) {
|
|
if (devmask & (1 << i) && !strcmp("vol", vnames[i])) {
|
|
if (ioctl(afd, MIXER_READ(i), &v) < 0) {
|
|
warn("ioctl 'MIXER_READ(%ld)':", i);
|
|
close(afd);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
close(afd);
|
|
|
|
return bprintf("%d", v & 0xff);
|
|
}
|
|
#endif
|