2 * Interface to Music Player Daemon
4 * (c) 2014 Martin Mares <mj@ucw.cz>
10 #include <ucw/clists.h>
11 #include <ucw/mainloop.h>
12 #include <ucw/mempool.h>
13 #include <ucw/simple-lists.h>
14 #include <ucw/stkstring.h>
15 #include <ucw/string.h>
24 #include <sys/types.h>
25 #include <sys/socket.h>
26 #include <netinet/in.h>
27 #include <netinet/tcp.h>
31 static struct main_timer mpd_connect_timer;
32 static struct main_file mpd_connect_file;
33 static struct main_rec_io mpd_rio;
34 static int mpd_sk = -1;
36 enum mpd_state mpd_state;
37 #define MPD_STATE(s) do { mpd_state = s; DBG("MPD: " #s); } while (0)
39 struct mpd_cmd { // malloc'ed
41 char *cmd; // malloc'ed
42 void (*done)(struct mpd_cmd *c);
45 int idle; // 1=this is an idle command, 2=aborted idle command
46 clist output; // list of simp2_node's allocated from mpd_reply_pool
49 static clist mpd_cmd_queue;
50 static struct mpd_cmd *mpd_current_command; // ... at the head of the queue
51 static struct mempool *mpd_reply_pool;
53 static void mpd_send_cmd(void);
54 static void mpd_cmd(void (*done)(struct mpd_cmd *c), const char *cmd, ...);
56 static struct mpd_cmd *mpd_new_cmd(void)
58 struct mpd_cmd *c = xmalloc_zero(sizeof(*c));
59 clist_add_tail(&mpd_cmd_queue, &c->n);
60 clist_init(&c->output);
64 static void mpd_free_cmd(struct mpd_cmd *c)
70 static void mpd_error(const char *text, ...)
74 const char *x = stk_vprintf(text, args);
75 msg(L_ERROR, "MPD: %s", x);
78 DBG("MPD: Flushing commands");
79 mpd_current_command = NULL;
81 while (c = (struct mpd_cmd *) clist_remove_head(&mpd_cmd_queue))
84 DBG("MPD: Tearing down server connection");
86 file_del(&mpd_connect_file);
93 DBG("MPD: Scheduling reconnect");
94 timer_add_rel(&mpd_connect_timer, 5000);
95 MPD_STATE(MPD_OFFLINE);
99 static void mpd_cmd_done(struct mpd_cmd *c)
101 DBG("MPD: Command finished (err=%d)", c->err);
102 rec_io_set_timeout(&mpd_rio, 0);
108 mpd_current_command = NULL;
112 static const char *mpd_find_output(struct mpd_cmd *c, const char *key, const char *deflt)
114 CLIST_FOR_EACH(struct simp2_node *, n, c->output)
115 if (!strcmp(n->s1, key))
120 static char *mpd_player_state;
122 static void mpd_got_status(struct mpd_cmd *c)
124 const char *state = mpd_find_output(c, "state", "");
125 DBG("MPD: Player state <%s>", state);
126 SET_STRING(mpd_player_state, state);
130 const char *mpd_get_player_state(void)
132 if (mpd_state != MPD_ONLINE)
134 else if (mpd_player_state)
135 return mpd_player_state;
140 static void mpd_got_idle(struct mpd_cmd *c)
142 const char *chg = mpd_find_output(c, "changed", "");
143 if (!strcmp(chg, "player"))
144 mpd_cmd(mpd_got_status, "status");
147 static bool mpd_got_line(char *line)
149 if (mpd_state == MPD_WAIT_GREETING)
151 if (!str_has_prefix(line, "OK "))
152 mpd_error("Invalid welcome line");
153 MPD_STATE(MPD_ONLINE);
154 mpd_cmd(mpd_got_status, "status");
158 ASSERT(mpd_state == MPD_ONLINE);
160 struct mpd_cmd *c = mpd_current_command;
163 mpd_error("Reply for no command");
167 if (!strcmp(line, "OK"))
173 else if (str_has_prefix(line, "ACK "))
178 mpd_error("Reply syntax error: <%s>", line);
181 c->err = atoi(line+5);
186 char *sep = strchr(line, ':');
187 if (!sep || sep[1] != ' ')
189 mpd_error("Reply syntax error: <%s>", line);
194 simp2_node *sn = simp2_append(mpd_reply_pool, &c->output);
195 sn->s1 = mp_strdup(mpd_reply_pool, line);
196 sn->s2 = mp_strdup(mpd_reply_pool, sep);
202 static uint mpd_read_handler(struct main_rec_io *rio)
204 uint len = rec_io_parse_line(rio);
209 memcpy(line, rio->read_rec_start, len-1);
212 DBG("Received <%s>", line);
213 if (mpd_got_line(line))
219 static int mpd_notify_handler(struct main_rec_io *rio UNUSED, int status)
224 mpd_error("Connection closed by server");
229 mpd_error("Error on server connection (status=%d errno=%d)", status, errno);
236 static void mpd_send_cmd(void)
240 if (c = mpd_current_command)
244 DBG("MPD: Sending noidle");
245 rec_io_write(&mpd_rio, "noidle\n", 7);
251 c = (struct mpd_cmd *) clist_head(&mpd_cmd_queue);
256 strcpy(c->cmd, "idle\n");
258 c->done = mpd_got_idle;
260 mpd_current_command = c;
262 DBG("MPD: Sending command <%s>", c->cmd);
263 rec_io_write(&mpd_rio, c->cmd, strlen(c->cmd));
265 rec_io_set_timeout(&mpd_rio, 5000);
268 static void mpd_cmd(void (*done)(struct mpd_cmd *c), const char *cmd, ...)
270 struct mpd_cmd *c = mpd_new_cmd();
275 va_copy(args2, args);
276 int len = vsnprintf(NULL, 0, cmd, args);
277 c->cmd = xmalloc(len + 2);
278 vsnprintf(c->cmd, len + 1, cmd, args2);
287 static int mpd_connected(struct main_file *f)
291 // Called from the hook
292 DBG("MPD: Connection hook");
294 timer_del(&mpd_connect_timer);
296 socklen_t len = sizeof(err);
297 if (getsockopt(mpd_sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
301 mpd_error("Connection refused: %s", strerror(err));
306 MPD_STATE(MPD_WAIT_GREETING);
307 mpd_rio.read_handler = mpd_read_handler;
308 mpd_rio.notify_handler = mpd_notify_handler;
309 mpd_rio.read_rec_max = 16384;
310 rec_io_add(&mpd_rio, mpd_sk);
311 rec_io_start_read(&mpd_rio);
318 return mpd_cmd(NULL, "play");
323 return mpd_cmd(NULL, "stop");
326 void mpd_pause(int arg)
328 return mpd_cmd(NULL, "pause %d", arg);
331 static void mpd_connect(struct main_timer *t)
335 if (mpd_state == MPD_CONNECTING)
337 mpd_error("Attempt to connect timed out");
341 DBG("MPD: Trying to connect");
343 mpd_sk = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
345 die("Cannot create socket: %m");
347 fcntl(mpd_sk, F_SETFL, fcntl(mpd_sk, F_GETFL) | O_NONBLOCK);
349 bzero(&mpd_rio, sizeof(mpd_rio));
350 bzero(&mpd_connect_file, sizeof(mpd_connect_file));
351 mpd_connect_file.fd = mpd_sk;
352 mpd_connect_file.write_handler = mpd_connected;
353 MPD_STATE(MPD_CONNECTING);
355 struct sockaddr_in sin;
356 sin.sin_family = AF_INET;
357 sin.sin_port = htons(6600);
358 sin.sin_addr.s_addr = htonl(0x7f000001);
359 if (connect(mpd_sk, (struct sockaddr *) &sin, sizeof(sin)) < 0)
361 if (errno == EINPROGRESS)
363 file_add(&mpd_connect_file);
364 timer_add_rel(&mpd_connect_timer, 5000);
367 mpd_error("Unable to connect: %m");
375 clist_init(&mpd_cmd_queue);
376 mpd_reply_pool = mp_new(4096);
378 mpd_connect_timer.handler = mpd_connect;
379 timer_add_rel(&mpd_connect_timer, 100);
380 mpd_state = MPD_OFFLINE;