From 0d8704737ee1b4fbf354c91a573ec360fd12da4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 2 Jan 2019 18:07:16 +0100 Subject: [PATCH] module/alsa: monitors volume and muted state of selected card/mixer --- CMakeLists.txt | 5 ++ config.c | 21 +++++++ modules/alsa.c | 148 +++++++++++++++++++++++++++++++++++++++++++++++++ modules/alsa.h | 7 +++ 4 files changed, 181 insertions(+) create mode 100644 modules/alsa.c create mode 100644 modules/alsa.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a666411..ea40029 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ pkg_check_modules(XCB_XKB REQUIRED xcb-xkb) # Module/xkb pkg_check_modules(JSON REQUIRED json-c) # Module/i3 pkg_check_modules(UDEV REQUIRED libudev) # Module/battery pkg_check_modules(MPD REQUIRED libmpdclient) # Module/mpd +pkg_check_modules(ALSA REQUIRED alsa) # Module/alsa add_executable(f00bar bar.c bar.h @@ -48,6 +49,7 @@ add_executable(f00bar particles/ramp.c particles/ramp.h particles/string.c particles/string.h + modules/alsa.c modules/alsa.h modules/backlight.c modules/backlight.h modules/battery.c modules/battery.h modules/clock.c modules/clock.h @@ -70,6 +72,7 @@ target_compile_options(f00bar PRIVATE ${JSON_CFLAGS_OTHER} ${UDEV_CFLAGS_OTHER} ${MPD_CFLAGS_OTHER} + ${ALSA_CFLAGS_OTHER} ) target_include_directories(f00bar PRIVATE @@ -80,6 +83,7 @@ target_include_directories(f00bar PRIVATE ${JSON_INCLUDE_DIRS} ${UDEV_INCLUDE_DIRS} ${MPD_INCLUDE_DIRS} + ${ALSA_INCLUDE_DIRS} ) target_link_libraries(f00bar @@ -91,4 +95,5 @@ target_link_libraries(f00bar ${JSON_LIBRARIES} ${UDEV_LIBRARIES} ${MPD_LIBRARIES} + ${ALSA_LIBRARIES} ) diff --git a/config.c b/config.c index dc6e983..c46539d 100644 --- a/config.c +++ b/config.c @@ -21,6 +21,7 @@ #include "particles/string.h" #include "module.h" +#include "modules/alsa.h" #include "modules/backlight.h" #include "modules/battery.h" #include "modules/clock.h" @@ -568,6 +569,24 @@ module_removables_from_config(const struct yml_node *node, particle_from_config(content, parent_font), left, right); } +static struct module * +module_alsa_from_config(const struct yml_node *node, + const struct font *parent_font) +{ + const struct yml_node *card = yml_get_value(node, "card"); + const struct yml_node *mixer = yml_get_value(node, "mixer"); + const struct yml_node *content = yml_get_value(node, "content"); + + assert(yml_is_scalar(card)); + assert(yml_is_scalar(mixer)); + assert(content != NULL); + + return module_alsa( + yml_value_as_string(card), + yml_value_as_string(mixer), + particle_from_config(content, parent_font)); +} + struct bar * conf_to_bar(const struct yml_node *bar) { @@ -682,6 +701,8 @@ conf_to_bar(const struct yml_node *bar) mods[idx] = module_network_from_config(it.node, font); else if (strcmp(mod_name, "removables") == 0) mods[idx] = module_removables_from_config(it.node, font); + else if (strcmp(mod_name, "alsa") == 0) + mods[idx] = module_alsa_from_config(it.node, font); else assert(false); } diff --git a/modules/alsa.c b/modules/alsa.c new file mode 100644 index 0000000..0a28e7d --- /dev/null +++ b/modules/alsa.c @@ -0,0 +1,148 @@ +#include "alsa.h" + +#include +#include + +#include + +#define LOG_MODULE "alsa" +#define LOG_ENABLE_DBG 1 +#include "../log.h" +#include "../bar.h" + +struct private { + char *card; + char *mixer; + struct particle *label; + + long vol_min; + long vol_max; + long vol_cur; + bool muted; +}; + +static void +destroy(struct module *mod) +{ + struct private *m = mod->private; + m->label->destroy(m->label); + free(m->card); + free(m->mixer); + free(m); + module_default_destroy(mod); +} + +static struct exposable * +content(struct module *mod) +{ + struct private *m = mod->private; + + mtx_lock(&mod->lock); + struct tag_set tags = { + .tags = (struct tag *[]){ + tag_new_int_range(mod, "volume", m->vol_cur, m->vol_min, m->vol_max), + tag_new_bool(mod, "muted", m->muted), + }, + .count = 2, + }; + mtx_unlock(&mod->lock); + + struct exposable *exposable = m->label->instantiate(m->label, &tags); + + tag_set_destroy(&tags); + return exposable; +} + +static void +update_state(struct module *mod, snd_mixer_elem_t *elem) +{ + struct private *m = mod->private; + + long cur, min, max; + snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &cur); + snd_mixer_selem_get_playback_volume_range(elem, &min, &max); + + int unmuted; + snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &unmuted); + + LOG_DBG("muted=%d, cur=%ld, min=%ld, max=%ld", !unmuted, cur, min, max); + + mtx_lock(&mod->lock); + m->vol_min = min; + m->vol_max = max; + m->vol_cur = cur; + m->muted = !unmuted; + mtx_unlock(&mod->lock); + + mod->bar->refresh(mod->bar); +} + +static int +run(struct module_run_context *ctx) +{ + struct module *mod = ctx->module; + struct private *m = mod->private; + + module_signal_ready(ctx); + + snd_mixer_t *handle; + snd_mixer_open(&handle, 0); + snd_mixer_attach(handle, m->card); + snd_mixer_selem_register(handle, NULL, NULL); + snd_mixer_load(handle); + + snd_mixer_selem_id_t *sid; + snd_mixer_selem_id_alloca(&sid); + snd_mixer_selem_id_set_index(sid, 0); + snd_mixer_selem_id_set_name(sid, m->mixer); + snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); + + /* Initial state */ + update_state(mod, elem); + + while (true) { + int fd_count = snd_mixer_poll_descriptors_count(handle); + assert(fd_count >= 1); + + struct pollfd fds[1 + fd_count]; + + fds[0] = (struct pollfd){.fd = ctx->abort_fd, .events = POLLIN}; + snd_mixer_poll_descriptors(handle, &fds[1], fd_count); + + poll(fds, fd_count + 1, -1); + + if (fds[0].revents & POLLIN) + break; + + if (fds[1].revents & POLLHUP) { + /* Don't know if this can happen */ + LOG_ERR("disconnected from alsa"); + break; + } + + snd_mixer_handle_events(handle); + update_state(mod, elem); + } + + snd_mixer_close(handle); + snd_config_update_free_global(); + return 0; +} + +struct module * +module_alsa(const char *card, const char *mixer, struct particle *label) +{ + struct private *priv = malloc(sizeof(*priv)); + priv->label = label; + priv->card = strdup(card); + priv->mixer = strdup(mixer); + priv->vol_cur = priv->vol_min = priv->vol_max = 0; + priv->muted = true; + + struct module *mod = module_common_new(); + mod->private = priv; + mod->run = &run; + mod->destroy = &destroy; + mod->content = &content; + return mod; +} diff --git a/modules/alsa.h b/modules/alsa.h new file mode 100644 index 0000000..ec539e1 --- /dev/null +++ b/modules/alsa.h @@ -0,0 +1,7 @@ +#pragma once + +#include "../module.h" +#include "../particle.h" + +struct module *module_alsa( + const char *card, const char *mixer, struct particle *label);