From 9a94c9c1f7ac9b3d2d4b1959a9d52625ec959630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 27 Dec 2018 11:36:38 +0100 Subject: [PATCH] module/mpd: monitors MPD --- CMakeLists.txt | 5 + config.c | 21 ++++ modules/mpd/mpd.c | 301 ++++++++++++++++++++++++++++++++++++++++++++++ modules/mpd/mpd.h | 9 ++ 4 files changed, 336 insertions(+) create mode 100644 modules/mpd/mpd.c create mode 100644 modules/mpd/mpd.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 55fb894..ce05345 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ pkg_check_modules(YAML REQUIRED yaml-0.1) # Core (conf 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 add_executable(f00bar bar.c bar.h @@ -50,6 +51,7 @@ add_executable(f00bar modules/i3/dynlist-exposable.c modules/i3/dynlist-exposable.h modules/i3/i3.c modules/i3/i3.h modules/label/label.c modules/label/label.h + modules/mpd/mpd.c modules/mpd/mpd.h modules/xkb/xkb.c modules/xkb/xkb.h modules/xwindow/xwindow.c modules/xwindow/xwindow.h ) @@ -63,6 +65,7 @@ target_compile_options(f00bar PRIVATE ${XCB_XKB_CFLAGS_OTHER} ${JSON_CFLAGS_OTHER} ${UDEV_CFLAGS_OTHER} + ${MPD_CFLAGS_OTHER} ) target_include_directories(f00bar PRIVATE @@ -72,6 +75,7 @@ target_include_directories(f00bar PRIVATE ${XCB_XKB_INCLUDE_DIRS} ${JSON_INCLUDE_DIRS} ${UDEV_INCLUDE_DIRS} + ${MPD_INCLUDE_DIRS} ) target_link_libraries(f00bar @@ -82,4 +86,5 @@ target_link_libraries(f00bar ${XCB_XKB_LIBRARIES} ${JSON_LIBRARIES} ${UDEV_LIBRARIES} + ${MPD_LIBRARIES} ) diff --git a/config.c b/config.c index 54320da..0617bcf 100644 --- a/config.c +++ b/config.c @@ -24,6 +24,7 @@ #include "modules/clock/clock.h" #include "modules/i3/i3.h" #include "modules/label/label.h" +#include "modules/mpd/mpd.h" #include "modules/xkb/xkb.h" #include "modules/xwindow/xwindow.h" @@ -431,6 +432,24 @@ module_backlight_from_config(const struct yml_node *node, yml_value_as_string(name), particle_from_config(c, parent_font)); } +static struct module * +module_mpd_from_config(const struct yml_node *node, + const struct font *parent_font) +{ + const struct yml_node *host = yml_get_value(node, "host"); + const struct yml_node *port = yml_get_value(node, "port"); + const struct yml_node *c = yml_get_value(node, "content"); + + assert(yml_is_scalar(host)); + assert(port == NULL || yml_is_scalar(port)); + assert(yml_is_dict(c)); + + return module_mpd( + yml_value_as_string(host), + port != NULL ? yml_value_as_int(port) : 0, + particle_from_config(c, parent_font)); +} + struct bar * conf_to_bar(const struct yml_node *bar) { @@ -539,6 +558,8 @@ conf_to_bar(const struct yml_node *bar) mods[idx] = module_xkb_from_config(it.node, font); else if (strcmp(mod_name, "backlight") == 0) mods[idx] = module_backlight_from_config(it.node, font); + else if (strcmp(mod_name, "mpd") == 0) + mods[idx] = module_mpd_from_config(it.node, font); else assert(false); } diff --git a/modules/mpd/mpd.c b/modules/mpd/mpd.c new file mode 100644 index 0000000..c7ebb70 --- /dev/null +++ b/modules/mpd/mpd.c @@ -0,0 +1,301 @@ +#include "mpd.h" + +#include +#include +#include +#include +#include + +#include + +#define LOG_MODULE "mpd" +#define LOG_ENABLE_DBG 1 +#include "../../log.h" +#include "../../bar.h" + +enum state { + STATE_OFFLINE = 1000, + STATE_STOP = MPD_STATE_STOP, + STATE_PAUSE = MPD_STATE_PAUSE, + STATE_PLAY = MPD_STATE_PLAY, +}; + +struct private { + char *host; + uint16_t port; + struct particle *label; + + struct mpd_connection *conn; + enum state state; + char *album; + char *artist; + char *title; + + unsigned elapsed; + unsigned duration; +}; + +static void +destroy(struct module *mod) +{ + struct private *m = mod->private; + free(m->host); + free(m->album); + free(m->artist); + free(m->title); + assert(m->conn == NULL); + + m->label->destroy(m->label); + + free(m); + module_default_destroy(mod); +} + +static struct exposable * +content(struct module *mod) +{ + struct private *m = mod->private; + + mtx_lock(&mod->lock); + + const char *state_str = NULL; + switch (m->state) { + case STATE_OFFLINE: state_str = "offline"; break; + case STATE_STOP: state_str = "stopped"; break; + case STATE_PAUSE: state_str = "paused"; break; + case STATE_PLAY: state_str = "playing"; break; + } + + char pos[16], end[16]; + + if (m->elapsed >= 60 * 60) + snprintf(pos, sizeof(pos), "%02u:%02u:%02u", + m->elapsed / (60 * 60), + m->elapsed % (60 * 60) / 60, + m->elapsed % 60); + else + snprintf(pos, sizeof(pos), "%02u:%02u", + m->elapsed / 60, m->elapsed % 60); + + if (m->duration >= 60 * 60) + snprintf(end, sizeof(end), "%02u:%02u:%02u", + m->duration / (60 * 60), + m->duration % (60 * 60) / 60, + m->duration % 60); + else + snprintf(end, sizeof(end), "%02u:%02u", + m->duration / 60, m->duration % 60); + + struct tag_set tags = { + .tags = (struct tag *[]){ + tag_new_string("state", state_str), + tag_new_string("album", m->album), + tag_new_string("artist", m->artist), + tag_new_string("title", m->title), + tag_new_string("pos", pos), + tag_new_string("end", end), + tag_new_int("duration", m->duration), + tag_new_int_range("elapsed", m->elapsed, 0, m->duration), + }, + .count = 8, + }; + + mtx_unlock(&mod->lock); + + struct exposable *exposable = m->label->instantiate(m->label, &tags); + + tag_set_destroy(&tags); + return exposable; +} + +static struct mpd_connection * +connect_to_mpd(const struct module *mod) +{ + const struct private *m = mod->private; + + struct mpd_connection *conn = mpd_connection_new(m->host, m->port, 0); + if (conn == NULL) { + LOG_ERR("failed to create MPD connection"); + return NULL; + } + + enum mpd_error merr = mpd_connection_get_error(conn); + if (merr != MPD_ERROR_SUCCESS) { + LOG_WARN("failed to connect to MPD: %s", + mpd_connection_get_error_message(conn)); + return NULL; + } + + const unsigned *version = mpd_connection_get_server_version(conn); + LOG_INFO("connected to MPD %u.%u.%u", version[0], version[1], version[2]); + + return conn; +} + +static bool +update_status(struct module *mod) +{ + struct private *m = mod->private; + + struct mpd_status *status = mpd_run_status(m->conn); + if (status == NULL) { + LOG_ERR("failed to get status: %s", + mpd_connection_get_error_message(m->conn)); + return false; + } + + mtx_lock(&mod->lock); + m->state = mpd_status_get_state(status); + m->duration = mpd_status_get_total_time(status); + m->elapsed = mpd_status_get_elapsed_time(status); + mtx_unlock(&mod->lock); + + mpd_status_free(status); + + struct mpd_song *song = mpd_run_current_song(m->conn); + if (song == NULL && mpd_connection_get_error(m->conn) != MPD_ERROR_SUCCESS) { + LOG_ERR("failed to get current song: %s", + mpd_connection_get_error_message(m->conn)); + return false; + } + + if (song == NULL) { + mtx_lock(&mod->lock); + free(m->album); m->album = NULL; + free(m->artist); m->artist = NULL; + free(m->title); m->title = NULL; + mtx_unlock(&mod->lock); + } else { + const char *album = mpd_song_get_tag(song, MPD_TAG_ALBUM, 0); + const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0); + const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0); + + mtx_lock(&mod->lock); + m->album = strdup(album); + m->artist = strdup(artist); + m->title = strdup(title); + mtx_unlock(&mod->lock); + + mpd_song_free(song); + } + + return true; +} + +static int +run(struct module_run_context *ctx) +{ + module_signal_ready(ctx); + + struct module *mod = ctx->module; + const struct bar *bar = mod->bar; + struct private *m = mod->private; + + bool aborted = false; + + while (!aborted) { + + if (m->conn != NULL) { + mpd_connection_free(m->conn); + m->conn = NULL; + } + + /* Reset state */ + mtx_lock(&mod->lock); + free(m->album); m->album = NULL; + free(m->artist); m->artist = NULL; + free(m->title); m->title = NULL; + m->state = STATE_OFFLINE; + m->elapsed = m->duration = 0; + mtx_unlock(&mod->lock); + + /* Keep trying to connect, until we succeed */ + while (!aborted) { + m->conn = connect_to_mpd(mod); + if (m->conn != NULL) + break; + + struct pollfd fds[] = {{.fd = ctx->abort_fd, .events = POLLIN}}; + int res = poll(fds, 1, 1 * 1000); + + if (res == 1) { + assert(fds[0].revents & POLLIN); + aborted = true; + } + } + + if (aborted) + break; + + /* Initial state (after establishing a connection) */ + assert(m->conn != NULL); + if (!update_status(mod)) + continue; + + bar->refresh(bar); + + /* Monitor for events from MPD */ + while (true) { + struct pollfd fds[] = { + {.fd = ctx->abort_fd, .events = POLLIN}, + {.fd = mpd_connection_get_fd(m->conn), .events = POLLIN}, + }; + + if (!mpd_send_idle(m->conn)) { + LOG_ERR("failed to send IDLE command: %s", + mpd_connection_get_error_message(m->conn)); + break; + } + + poll(fds, 2, -1); + + if (fds[0].revents & POLLIN) { + aborted = true; + break; + } + + if (fds[1].revents & POLLHUP) { + LOG_WARN("disconnected from MPD daemon"); + break; + } + + enum mpd_idle idle = mpd_recv_idle(m->conn, true); + LOG_DBG("IDLE mask: %d", idle); + + if (!update_status(mod)) + break; + + bar->refresh(bar); + } + } + + if (m->conn != NULL) { + mpd_connection_free(m->conn); + m->conn = NULL; + } + + return 0; +} + +struct module * +module_mpd(const char *host, uint16_t port, struct particle *label) +{ + struct private *priv = malloc(sizeof(*priv)); + priv->host = strdup(host); + priv->port = port; + priv->label = label; + priv->conn = NULL; + priv->state = STATE_OFFLINE; + priv->album = NULL; + priv->artist = NULL; + priv->title = NULL; + priv->elapsed = 0; + priv->duration = 0; + + struct module *mod = module_common_new(); + mod->private = priv; + mod->run = &run; + mod->destroy = &destroy; + mod->content = &content; + return mod; +} diff --git a/modules/mpd/mpd.h b/modules/mpd/mpd.h new file mode 100644 index 0000000..e1c5183 --- /dev/null +++ b/modules/mpd/mpd.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include "../../module.h" +#include "../../particle.h" + +struct module *module_mpd( + const char *host, uint16_t port, struct particle *label);