module/mpd: monitors MPD

This commit is contained in:
Daniel Eklöf 2018-12-27 11:36:38 +01:00
parent 3aa6b21056
commit 9a94c9c1f7
4 changed files with 336 additions and 0 deletions

View file

@ -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(XCB_XKB REQUIRED xcb-xkb) # Module/xkb
pkg_check_modules(JSON REQUIRED json-c) # Module/i3 pkg_check_modules(JSON REQUIRED json-c) # Module/i3
pkg_check_modules(UDEV REQUIRED libudev) # Module/battery pkg_check_modules(UDEV REQUIRED libudev) # Module/battery
pkg_check_modules(MPD REQUIRED libmpdclient) # Module/mpd
add_executable(f00bar add_executable(f00bar
bar.c bar.h bar.c bar.h
@ -50,6 +51,7 @@ add_executable(f00bar
modules/i3/dynlist-exposable.c modules/i3/dynlist-exposable.h modules/i3/dynlist-exposable.c modules/i3/dynlist-exposable.h
modules/i3/i3.c modules/i3/i3.h modules/i3/i3.c modules/i3/i3.h
modules/label/label.c modules/label/label.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/xkb/xkb.c modules/xkb/xkb.h
modules/xwindow/xwindow.c modules/xwindow/xwindow.h modules/xwindow/xwindow.c modules/xwindow/xwindow.h
) )
@ -63,6 +65,7 @@ target_compile_options(f00bar PRIVATE
${XCB_XKB_CFLAGS_OTHER} ${XCB_XKB_CFLAGS_OTHER}
${JSON_CFLAGS_OTHER} ${JSON_CFLAGS_OTHER}
${UDEV_CFLAGS_OTHER} ${UDEV_CFLAGS_OTHER}
${MPD_CFLAGS_OTHER}
) )
target_include_directories(f00bar PRIVATE target_include_directories(f00bar PRIVATE
@ -72,6 +75,7 @@ target_include_directories(f00bar PRIVATE
${XCB_XKB_INCLUDE_DIRS} ${XCB_XKB_INCLUDE_DIRS}
${JSON_INCLUDE_DIRS} ${JSON_INCLUDE_DIRS}
${UDEV_INCLUDE_DIRS} ${UDEV_INCLUDE_DIRS}
${MPD_INCLUDE_DIRS}
) )
target_link_libraries(f00bar target_link_libraries(f00bar
@ -82,4 +86,5 @@ target_link_libraries(f00bar
${XCB_XKB_LIBRARIES} ${XCB_XKB_LIBRARIES}
${JSON_LIBRARIES} ${JSON_LIBRARIES}
${UDEV_LIBRARIES} ${UDEV_LIBRARIES}
${MPD_LIBRARIES}
) )

View file

@ -24,6 +24,7 @@
#include "modules/clock/clock.h" #include "modules/clock/clock.h"
#include "modules/i3/i3.h" #include "modules/i3/i3.h"
#include "modules/label/label.h" #include "modules/label/label.h"
#include "modules/mpd/mpd.h"
#include "modules/xkb/xkb.h" #include "modules/xkb/xkb.h"
#include "modules/xwindow/xwindow.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)); 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 * struct bar *
conf_to_bar(const struct yml_node *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); mods[idx] = module_xkb_from_config(it.node, font);
else if (strcmp(mod_name, "backlight") == 0) else if (strcmp(mod_name, "backlight") == 0)
mods[idx] = module_backlight_from_config(it.node, font); 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 else
assert(false); assert(false);
} }

301
modules/mpd/mpd.c Normal file
View file

@ -0,0 +1,301 @@
#include "mpd.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <poll.h>
#include <mpd/client.h>
#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;
}

9
modules/mpd/mpd.h Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#include <stdint.h>
#include "../../module.h"
#include "../../particle.h"
struct module *module_mpd(
const char *host, uint16_t port, struct particle *label);