From 50a7f7ea8a572ce3cab7045ac8cce14cf73ef8a1 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Mon, 10 Nov 2014 00:08:31 +0100 Subject: [PATCH] MPD controls --- Makefile | 3 +- mpd.c | 381 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ nocturn.c | 8 +- pulse.c | 6 +- ursaryd.c | 54 ++++++++ ursaryd.h | 31 ++++- 6 files changed, 468 insertions(+), 15 deletions(-) create mode 100644 mpd.c diff --git a/Makefile b/Makefile index 52e4dd2..5d8d44c 100644 --- a/Makefile +++ b/Makefile @@ -13,9 +13,10 @@ LDLIBS=$(LIBUCW_LIBS) $(LIBUSB_LIBS) $(LIBPULSE_LIBS) all: ursaryd -ursaryd: ursaryd.o nocturn.o pulse.o pulse-ucw.o +ursaryd: ursaryd.o mpd.o nocturn.o pulse.o pulse-ucw.o ursaryd.o: ursaryd.c ursaryd.h +mpd.o: mpd.c ursaryd.h nocturn.o: nocturn.c ursaryd.h pulse.o: pulse.c ursaryd.h pulse-ucw.o: pulse-ucw.c ursaryd.h diff --git a/mpd.c b/mpd.c new file mode 100644 index 0000000..f0da2a3 --- /dev/null +++ b/mpd.c @@ -0,0 +1,381 @@ +/* + * Interface to Music Player Daemon + * + * (c) 2014 Martin Mares + */ + +#define LOCAL_DEBUG + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ursaryd.h" + +static struct main_timer mpd_connect_timer; +static struct main_file mpd_connect_file; +static struct main_rec_io mpd_rio; +static int mpd_sk = -1; + +enum mpd_state mpd_state; +#define MPD_STATE(s) do { mpd_state = s; DBG("MPD: " #s); } while (0) + +struct mpd_cmd { // malloc'ed + cnode n; + char *cmd; // malloc'ed + void (*done)(struct mpd_cmd *c); + char *status; + int err; + int idle; // 1=this is an idle command, 2=aborted idle command + clist output; // list of simp2_node's allocated from mpd_reply_pool +}; + +static clist mpd_cmd_queue; +static struct mpd_cmd *mpd_current_command; // ... at the head of the queue +static struct mempool *mpd_reply_pool; + +static void mpd_send_cmd(void); +static void mpd_cmd(void (*done)(struct mpd_cmd *c), const char *cmd, ...); + +static struct mpd_cmd *mpd_new_cmd(void) +{ + struct mpd_cmd *c = xmalloc_zero(sizeof(*c)); + clist_add_tail(&mpd_cmd_queue, &c->n); + clist_init(&c->output); + return c; +} + +static void mpd_free_cmd(struct mpd_cmd *c) +{ + xfree(c->cmd); + xfree(c); +} + +static void mpd_error(const char *text, ...) +{ + va_list args; + va_start(args, text); + const char *x = stk_vprintf(text, args); + msg(L_ERROR, "MPD: %s", x); + va_end(args); + + DBG("MPD: Flushing commands"); + mpd_current_command = NULL; + struct mpd_cmd *c; + while (c = (struct mpd_cmd *) clist_remove_head(&mpd_cmd_queue)) + mpd_free_cmd(c); + + DBG("MPD: Tearing down server connection"); + rec_io_del(&mpd_rio); + file_del(&mpd_connect_file); + if (mpd_sk >= 0) + { + close(mpd_sk); + mpd_sk = -1; + } + + DBG("MPD: Scheduling reconnect"); + timer_add_rel(&mpd_connect_timer, 5000); + MPD_STATE(MPD_OFFLINE); + schedule_update(); +} + +static void mpd_cmd_done(struct mpd_cmd *c) +{ + DBG("MPD: Command finished (err=%d)", c->err); + rec_io_set_timeout(&mpd_rio, 0); + if (c->done) + c->done(c); + clist_remove(&c->n); + mpd_free_cmd(c); + + mpd_current_command = NULL; + mpd_send_cmd(); +} + +static const char *mpd_find_output(struct mpd_cmd *c, const char *key, const char *deflt) +{ + CLIST_FOR_EACH(struct simp2_node *, n, c->output) + if (!strcmp(n->s1, key)) + return n->s2; + return deflt; +} + +static char *mpd_player_state; + +static void mpd_got_status(struct mpd_cmd *c) +{ + const char *state = mpd_find_output(c, "state", ""); + DBG("MPD: Player state <%s>", state); + SET_STRING(mpd_player_state, state); + schedule_update(); +} + +const char *mpd_get_player_state(void) +{ + if (mpd_state != MPD_ONLINE) + return "down"; + else if (mpd_player_state) + return mpd_player_state; + else + return "unknown"; +} + +static void mpd_got_idle(struct mpd_cmd *c) +{ + const char *chg = mpd_find_output(c, "changed", ""); + if (!strcmp(chg, "player")) + mpd_cmd(mpd_got_status, "status"); +} + +static bool mpd_got_line(char *line) +{ + if (mpd_state == MPD_WAIT_GREETING) + { + if (!str_has_prefix(line, "OK ")) + mpd_error("Invalid welcome line"); + MPD_STATE(MPD_ONLINE); + mpd_cmd(mpd_got_status, "status"); + return 1; + } + + ASSERT(mpd_state == MPD_ONLINE); + + struct mpd_cmd *c = mpd_current_command; + if (!c) + { + mpd_error("Reply for no command"); + return 0; + } + + if (!strcmp(line, "OK")) + { + c->status = line; + c->err = 0; + mpd_cmd_done(c); + } + else if (str_has_prefix(line, "ACK ")) + { + c->status = line; + if (line[4] != '[') + { + mpd_error("Reply syntax error: <%s>", line); + return 0; + } + c->err = atoi(line+5); + mpd_cmd_done(c); + } + else + { + char *sep = strchr(line, ':'); + if (!sep || sep[1] != ' ') + { + mpd_error("Reply syntax error: <%s>", line); + return 0; + } + *sep++ = 0; + *sep++ = 0; + simp2_node *sn = simp2_append(mpd_reply_pool, &c->output); + sn->s1 = mp_strdup(mpd_reply_pool, line); + sn->s2 = mp_strdup(mpd_reply_pool, sep); + } + + return 1; +} + +static uint mpd_read_handler(struct main_rec_io *rio) +{ + uint len = rec_io_parse_line(rio); + if (!len) + return 0; + + char line[len]; + memcpy(line, rio->read_rec_start, len-1); + line[len-1] = 0; + + DBG("Received <%s>", line); + if (mpd_got_line(line)) + return len; + else + return ~0U; +} + +static int mpd_notify_handler(struct main_rec_io *rio UNUSED, int status) +{ + switch (status) + { + case RIO_EVENT_EOF: + mpd_error("Connection closed by server"); + return HOOK_IDLE; + default: + if (status < 0) + { + mpd_error("Error on server connection (status=%d errno=%d)", status, errno); + return HOOK_IDLE; + } + return HOOK_RETRY; + } +} + +static void mpd_send_cmd(void) +{ + struct mpd_cmd *c; + + if (c = mpd_current_command) + { + if (c->idle == 1) + { + DBG("MPD: Sending noidle"); + rec_io_write(&mpd_rio, "noidle\n", 7); + c->idle = 2; + } + return; + } + + c = (struct mpd_cmd *) clist_head(&mpd_cmd_queue); + if (!c) + { + c = mpd_new_cmd(); + c->cmd = malloc(6); + strcpy(c->cmd, "idle\n"); + c->idle = 1; + c->done = mpd_got_idle; + } + mpd_current_command = c; + + DBG("MPD: Sending command <%s>", c->cmd); + rec_io_write(&mpd_rio, c->cmd, strlen(c->cmd)); + if (!c->idle) + rec_io_set_timeout(&mpd_rio, 5000); +} + +static void mpd_cmd(void (*done)(struct mpd_cmd *c), const char *cmd, ...) +{ + struct mpd_cmd *c = mpd_new_cmd(); + c->done = done; + + va_list args, args2; + va_start(args, cmd); + va_copy(args2, args); + int len = vsnprintf(NULL, 0, cmd, args); + c->cmd = xmalloc(len + 2); + vsnprintf(c->cmd, len + 1, cmd, args2); + c->cmd[len] = '\n'; + c->cmd[len+1] = 0; + va_end(args); + va_end(args2); + + mpd_send_cmd(); +} + +static int mpd_connected(struct main_file *f) +{ + if (f) + { + // Called from the hook + DBG("MPD: Connection hook"); + file_del(f); + timer_del(&mpd_connect_timer); + int err; + socklen_t len = sizeof(err); + if (getsockopt(mpd_sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) + ASSERT(0); + if (err) + { + mpd_error("Connection refused: %s", strerror(err)); + return HOOK_IDLE; + } + } + + MPD_STATE(MPD_WAIT_GREETING); + mpd_rio.read_handler = mpd_read_handler; + mpd_rio.notify_handler = mpd_notify_handler; + mpd_rio.read_rec_max = 16384; + rec_io_add(&mpd_rio, mpd_sk); + rec_io_start_read(&mpd_rio); + + return HOOK_IDLE; +} + +void mpd_play(void) +{ + return mpd_cmd(NULL, "play"); +} + +void mpd_stop(void) +{ + return mpd_cmd(NULL, "stop"); +} + +void mpd_pause(int arg) +{ + return mpd_cmd(NULL, "pause %d", arg); +} + +static void mpd_connect(struct main_timer *t) +{ + timer_del(t); + + if (mpd_state == MPD_CONNECTING) + { + mpd_error("Attempt to connect timed out"); + return; + } + + DBG("MPD: Trying to connect"); + + mpd_sk = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (mpd_sk < 0) + die("Cannot create socket: %m"); + + fcntl(mpd_sk, F_SETFL, fcntl(mpd_sk, F_GETFL) | O_NONBLOCK); + + bzero(&mpd_rio, sizeof(mpd_rio)); + bzero(&mpd_connect_file, sizeof(mpd_connect_file)); + mpd_connect_file.fd = mpd_sk; + mpd_connect_file.write_handler = mpd_connected; + MPD_STATE(MPD_CONNECTING); + + struct sockaddr_in sin; + sin.sin_family = AF_INET; + sin.sin_port = htons(6600); + sin.sin_addr.s_addr = htonl(0x7f000001); + if (connect(mpd_sk, (struct sockaddr *) &sin, sizeof(sin)) < 0) + { + if (errno == EINPROGRESS) + { + file_add(&mpd_connect_file); + timer_add_rel(&mpd_connect_timer, 5000); + } + else + mpd_error("Unable to connect: %m"); + } + else + mpd_connected(NULL); +} + +void mpd_init(void) +{ + clist_init(&mpd_cmd_queue); + mpd_reply_pool = mp_new(4096); + + mpd_connect_timer.handler = mpd_connect; + timer_add_rel(&mpd_connect_timer, 100); + mpd_state = MPD_OFFLINE; +} diff --git a/nocturn.c b/nocturn.c index 510fb33..ba5edf2 100644 --- a/nocturn.c +++ b/nocturn.c @@ -229,9 +229,9 @@ static byte noct_button_light[16]; static byte noct_ring_mode[8]; // RING_MODE_xxx static byte noct_ring_val[9]; -static uns noct_dirty_button; -static uns noct_dirty_ring_mode; -static uns noct_dirty_ring_val; +static uint noct_dirty_button; +static uint noct_dirty_ring_mode; +static uint noct_dirty_ring_val; static struct libusb_transfer *noct_write_xfer; static bool noct_write_pending; @@ -251,7 +251,7 @@ static void noct_write_done(struct libusb_transfer *xfer) noct_sched_write(); } -static void noct_do_write(uns cmd, uns arg) +static void noct_do_write(uint cmd, uint arg) { DBG("USB: Submitting write %02x %02x", cmd, arg); ASSERT(!noct_write_pending); diff --git a/pulse.c b/pulse.c index 72b40d8..21005ba 100644 --- a/pulse.c +++ b/pulse.c @@ -25,8 +25,6 @@ clist pulse_client_list, pulse_sink_list, pulse_sink_input_list; static pa_context *pulse_ctx; static struct main_timer pulse_connect_timer; -#define SET_STRING(_field, _val) do { if (!_field || strcmp(_field, _val)) { xfree(_field); _field = xstrdup(_val); } } while (0) - /*** Tracking of currently running asynchronous operations ***/ struct pulse_op { @@ -339,8 +337,8 @@ static void pulse_event_cb(pa_context *ctx UNUSED, pa_subscription_event_type_t { DBG("Pulse: SUBSCRIBE EVENT type=%08x idx=%u", type, idx); - uns object = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; - uns action = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK; + uint object = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; + uint action = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK; switch (object) { case PA_SUBSCRIPTION_EVENT_CLIENT: diff --git a/ursaryd.c b/ursaryd.c index b2cd009..3b1d051 100644 --- a/ursaryd.c +++ b/ursaryd.c @@ -335,6 +335,55 @@ static void update_default_sink_from_button(int button, int on) } } +/*** MPD controls ***/ + +static void update_mpd(void) +{ + const char *state = mpd_get_player_state(); + if (!strcmp(state, "play")) + noct_set_button(12, 1); + else + noct_set_button(12, 0); +} + +static void mpd_button_timeout(struct main_timer *t) +{ + DBG("MPD stop"); + timer_del(t); + mpd_stop(); +} + +static void update_mpd_from_button(int button UNUSED, int on) +{ + static struct main_timer mpd_button_timer = { + .handler = mpd_button_timeout, + }; + + if (!on) + { + timer_del(&mpd_button_timer); + return; + } + + const char *state = mpd_get_player_state(); + if (!strcmp(state, "stop")) + { + DBG("MPD play"); + mpd_play(); + } + else if (!strcmp(state, "play")) + { + DBG("MPD pause"); + timer_add_rel(&mpd_button_timer, 1000); + mpd_pause(1); + } + else if (!strcmp(state, "pause")) + { + DBG("MPD resume"); + mpd_pause(0); + } +} + /*** Main update routines ***/ static struct main_timer update_timer; @@ -381,6 +430,7 @@ static void do_update(struct main_timer *t) update_ring_from_sink(1, "catarium"); update_clients(); update_default_sink(); + update_mpd(); } void schedule_update(void) @@ -433,6 +483,9 @@ void notify_button(int button, int on) case 9: update_default_sink_from_button(button, on); break; + case 12: + update_mpd_from_button(button, on); + break; default: update_client_from_button(button, on); } @@ -461,6 +514,7 @@ int main(int argc UNUSED, char **argv) noct_init(); pulse_init(); + mpd_init(); msg(L_DEBUG, "Entering main loop"); main_loop(); diff --git a/ursaryd.h b/ursaryd.h index 87f4854..ddfc18d 100644 --- a/ursaryd.h +++ b/ursaryd.h @@ -6,6 +6,8 @@ #include +#define SET_STRING(_field, _val) do { if (!_field || strcmp(_field, _val)) { xfree(_field); _field = xstrdup(_val); } } while (0) + /* ursary.c */ void schedule_update(void); @@ -59,9 +61,9 @@ struct pulse_sink { cnode n; int idx; char *name; - uns channels; - uns volume; - uns base_volume; + uint channels; + uint volume; + uint base_volume; int mute; }; @@ -71,9 +73,9 @@ struct pulse_sink_input { char *name; int client_idx; int sink_idx; - uns channels; - uns volume; - uns mute; + uint channels; + uint volume; + uint mute; int noct_client_idx; // Used by the high-level logic below }; @@ -96,3 +98,20 @@ void pulse_server_set_default_sink(const char *name); extern struct pa_mainloop_api pmain_api; void pmain_init(void); + +/* mpd.c */ + +enum mpd_state { + MPD_OFFLINE, + MPD_CONNECTING, + MPD_WAIT_GREETING, + MPD_ONLINE, +}; + +extern enum mpd_state mpd_state; + +void mpd_init(void); +const char *mpd_get_player_state(void); +void mpd_play(void); +void mpd_stop(void); +void mpd_pause(int arg); -- 2.39.5