mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-25 13:25:39 +02:00
Merge branch 'alsa-handle-device-disconnect'
Closes #59 Closes #61 Closes #86
This commit is contained in:
commit
591cae4c6d
3 changed files with 126 additions and 12 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
129
modules/alsa.c
129
modules/alsa.c
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Reference in a new issue