mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-24 12:55:41 +02:00
module/mpd: monitors MPD
This commit is contained in:
parent
3aa6b21056
commit
9a94c9c1f7
4 changed files with 336 additions and 0 deletions
|
@ -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}
|
||||||
)
|
)
|
||||||
|
|
21
config.c
21
config.c
|
@ -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
301
modules/mpd/mpd.c
Normal 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
9
modules/mpd/mpd.h
Normal 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);
|
Loading…
Add table
Reference in a new issue