]> mj.ucw.cz Git - ursary.git/commitdiff
MPD controls
authorMartin Mares <mj@ucw.cz>
Sun, 9 Nov 2014 23:08:31 +0000 (00:08 +0100)
committerMartin Mares <mj@ucw.cz>
Sun, 9 Nov 2014 23:37:27 +0000 (00:37 +0100)
Makefile
mpd.c [new file with mode: 0644]
nocturn.c
pulse.c
ursaryd.c
ursaryd.h

index 52e4dd2f0d9fb16e4d7c0fd4cd497a5fb36446e2..5d8d44cbd59632080bfda92721ec82eb312197d7 100644 (file)
--- 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 (file)
index 0000000..f0da2a3
--- /dev/null
+++ b/mpd.c
@@ -0,0 +1,381 @@
+/*
+ *     Interface to Music Player Daemon
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#define LOCAL_DEBUG
+
+#include <ucw/lib.h>
+#include <ucw/clists.h>
+#include <ucw/mainloop.h>
+#include <ucw/mempool.h>
+#include <ucw/simple-lists.h>
+#include <ucw/stkstring.h>
+#include <ucw/string.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#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;
+}
index 510fb33590178118743bbef1252c382e7f7d4df0..ba5edf25f7e74693ad8952c9d5e304cf8bc01b29 100644 (file)
--- 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 72b40d82107838b87957c54a3f4b954d26a4cad7..21005ba79a50f02bfad9fe8da0b4da5b3521c089 100644 (file)
--- 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:
index b2cd009a3988223944cbbaebae744041d8c8f3b8..3b1d051313cd4b6eeffe5fd3004e9d2f36708311 100644 (file)
--- 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();
index 87f4854d844cf291f59333f1d008501a9f8233c5..ddfc18d2fc6de7c6f85ae9963b4a6e7c29bb5c83 100644 (file)
--- a/ursaryd.h
+++ b/ursaryd.h
@@ -6,6 +6,8 @@
 
 #include <pulse/pulseaudio.h>
 
+#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);