Merge branch 'alsa-handle-device-disconnect'

Closes #59
Closes #61
Closes #86
This commit is contained in:
Daniel Eklöf 2021-08-19 19:31:28 +02:00
commit 591cae4c6d
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
3 changed files with 126 additions and 12 deletions

View file

@ -22,11 +22,15 @@
(https://codeberg.org/dnkl/yambar/issues/84). (https://codeberg.org/dnkl/yambar/issues/84).
* river: support for the river-status protocol, version 2 (urgent * river: support for the river-status protocol, version 2 (urgent
views). views).
* `online` tag to the `alsa` module.
### Changed ### Changed
* bar: do not add `spacing` around empty (zero-width) modules. * bar: do not add `spacing` around empty (zero-width) modules.
* alsa: do not error out if we fail to connect to the ALSA device, or
if we get disconnected. Instead, keep retrying until we succeed
(https://codeberg.org/dnkl/yambar/issues/86).
### Deprecated ### Deprecated
@ -37,6 +41,8 @@
compiled without the Wayland backend”_. compiled without the Wayland backend”_.
* Regression: `{where}` tag not being expanded in progress-bar * Regression: `{where}` tag not being expanded in progress-bar
`on-click` handlers. `on-click` handlers.
* `alsa` module causing yambar to use 100% CPU if the ALSA device is
disconnected (https://codeberg.org/dnkl/yambar/issues/61).
### Security ### Security

View file

@ -8,6 +8,9 @@ alsa - Monitors an alsa soundcard for volume and mute/unmute changes
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Description* :[ *Description*
| online
: bool
: True when the ALSA device has successfully been opened
| volume | volume
: range : range
: Volume level, with min and max as start and end range values : Volume level, with min and max as start and end range values

View file

@ -1,6 +1,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
#include <sys/time.h>
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
@ -21,6 +22,7 @@ struct private {
tll(snd_mixer_selem_channel_id_t) channels; tll(snd_mixer_selem_channel_id_t) channels;
bool online;
long vol_min; long vol_min;
long vol_max; long vol_max;
long vol_cur; long vol_cur;
@ -60,11 +62,12 @@ content(struct module *mod)
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
struct tag_set tags = { struct tag_set tags = {
.tags = (struct tag *[]){ .tags = (struct tag *[]){
tag_new_bool(mod, "online", m->online),
tag_new_int_range(mod, "volume", m->vol_cur, m->vol_min, m->vol_max), tag_new_int_range(mod, "volume", m->vol_cur, m->vol_min, m->vol_max),
tag_new_int_range(mod, "percent", percent, 0, 100), tag_new_int_range(mod, "percent", percent, 0, 100),
tag_new_bool(mod, "muted", m->muted), tag_new_bool(mod, "muted", m->muted),
}, },
.count = 3, .count = 4,
}; };
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
@ -186,22 +189,30 @@ update_state(struct module *mod, snd_mixer_elem_t *elem)
m->vol_min = min; m->vol_min = min;
m->vol_max = max; m->vol_max = max;
m->vol_cur = cur[0]; m->vol_cur = cur[0];
m->online = true;
m->muted = !unmuted[0]; m->muted = !unmuted[0];
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar); mod->bar->refresh(mod->bar);
} }
static int enum run_state {
run(struct module *mod) RUN_ERROR,
RUN_FAILED_CONNECT,
RUN_DISCONNECTED,
RUN_DONE,
};
static enum run_state
run_while_online(struct module *mod)
{ {
struct private *m = mod->private; struct private *m = mod->private;
int ret = 1; enum run_state ret = RUN_ERROR;
snd_mixer_t *handle; snd_mixer_t *handle;
if (snd_mixer_open(&handle, 0) != 0) { if (snd_mixer_open(&handle, 0) != 0) {
LOG_ERR("failed to open handle"); LOG_ERR("failed to open handle");
return 1; return ret;
} }
if (snd_mixer_attach(handle, m->card) != 0 || if (snd_mixer_attach(handle, m->card) != 0 ||
@ -209,6 +220,7 @@ run(struct module *mod)
snd_mixer_load(handle) != 0) snd_mixer_load(handle) != 0)
{ {
LOG_ERR("failed to attach to card"); LOG_ERR("failed to attach to card");
ret = RUN_FAILED_CONNECT;
goto err; goto err;
} }
@ -260,29 +272,122 @@ run(struct module *mod)
fds[0] = (struct pollfd){.fd = mod->abort_fd, .events = POLLIN}; fds[0] = (struct pollfd){.fd = mod->abort_fd, .events = POLLIN};
snd_mixer_poll_descriptors(handle, &fds[1], fd_count); snd_mixer_poll_descriptors(handle, &fds[1], fd_count);
poll(fds, fd_count + 1, -1); int r = poll(fds, fd_count + 1, -1);
if (r < 0) {
if (errno == EINTR)
continue;
if (fds[0].revents & POLLIN) LOG_ERRNO("failed to poll");
break; break;
}
if (fds[1].revents & POLLHUP) { if (fds[0].revents & POLLIN) {
/* Don't know if this can happen */ ret = RUN_DONE;
LOG_ERR("disconnected from alsa");
break; break;
} }
for (size_t i = 0; i < fd_count; i++) {
if (fds[1 + i].revents & (POLLHUP | POLLERR | POLLNVAL)) {
LOG_ERR("disconnected from alsa");
mtx_lock(&mod->lock);
m->online = false;
mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar);
ret = RUN_DISCONNECTED;
goto err;
}
}
snd_mixer_handle_events(handle); snd_mixer_handle_events(handle);
update_state(mod, elem); update_state(mod, elem);
} }
ret = 0;
err: err:
snd_mixer_close(handle); snd_mixer_close(handle);
snd_config_update_free_global(); snd_config_update_free_global();
return ret; return ret;
} }
static int
run(struct module *mod)
{
static const int min_timeout_ms = 500;
static const int max_timeout_ms = 30000;
int timeout_ms = min_timeout_ms;
while (true) {
enum run_state state = run_while_online(mod);
switch (state) {
case RUN_DONE:
return 0;
case RUN_ERROR:
return 1;
case RUN_FAILED_CONNECT:
timeout_ms *= 2;
break;
case RUN_DISCONNECTED:
timeout_ms = min_timeout_ms;
break;
}
if (timeout_ms > max_timeout_ms)
timeout_ms = max_timeout_ms;
struct timeval now;
gettimeofday(&now, NULL);
struct timeval timeout = {
.tv_sec = timeout_ms / 1000,
.tv_usec = (timeout_ms % 1000) * 1000,
};
struct timeval deadline;
timeradd(&now, &timeout, &deadline);
LOG_DBG("timeout is now %dms", timeout_ms);
while (true) {
struct timeval n;
gettimeofday(&n, NULL);
struct timeval left;
timersub(&deadline, &n, &left);
int poll_timeout = timercmp(&left, &(struct timeval){0}, <)
? 0
: left.tv_sec * 1000 + left.tv_usec / 1000;
LOG_DBG(
"polling for alsa device to become available (timeout=%dms)",
poll_timeout);
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
int r = poll(fds, 1, poll_timeout);
if (r < 0) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
return 1;
}
if (fds[0].revents & POLLIN)
return 0;
assert(r == 0);
break;
}
}
}
static struct module * static struct module *
alsa_new(const char *card, const char *mixer, struct particle *label) alsa_new(const char *card, const char *mixer, struct particle *label)
{ {