slstatus/components/volume.c
2025-03-12 08:38:50 -06:00

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