#include "empd.h" #include #include #include #include #include #include #include #include #define STRING_SAFETY(X) X ?: "" #define S(X) #X typedef struct E_Slist E_Slist; struct E_Slist { E_Slist *next; unsigned int command; }; typedef struct EMPD { Ecore_Con_Server *svr; Ecore_Fd_Handler *fdh; Ecore_Poller *pinger; struct mpd_settings *settings; struct mpd_async *async; struct mpd_parser *parser; Eina_Array *pending; void *cur; E_Slist *cmds, *last; } EMPD; typedef enum { EMPD_COMMAND_NONE = 0, EMPD_COMMAND_PASSWORD, EMPD_COMMAND_STATUS, EMPD_COMMAND_CURRENT_SONG, EMPD_COMMAND_PLAY, EMPD_COMMAND_PAUSE, EMPD_COMMAND_TOGGLE_PAUSE, EMPD_COMMAND_STOP, EMPD_COMMAND_NEXT, EMPD_COMMAND_PREVIOUS, EMPD_COMMAND_SEEK, EMPD_COMMAND_PLAY_ID, EMPD_COMMAND_QUEUE_LIST, } EMPD_Command; static const char *cmd_txt[] = { #define CMD_TXT(X) \ [X] = S(X), CMD_TXT(EMPD_COMMAND_NONE) CMD_TXT(EMPD_COMMAND_PASSWORD) CMD_TXT(EMPD_COMMAND_STATUS) CMD_TXT(EMPD_COMMAND_CURRENT_SONG) CMD_TXT(EMPD_COMMAND_PLAY) CMD_TXT(EMPD_COMMAND_PAUSE) CMD_TXT(EMPD_COMMAND_TOGGLE_PAUSE) CMD_TXT(EMPD_COMMAND_STOP) CMD_TXT(EMPD_COMMAND_NEXT) CMD_TXT(EMPD_COMMAND_PREVIOUS) CMD_TXT(EMPD_COMMAND_SEEK) CMD_TXT(EMPD_COMMAND_PLAY_ID) CMD_TXT(EMPD_COMMAND_QUEUE_LIST) }; typedef enum { EMPD_SIGNAL_CONNECTED, EMPD_SIGNAL_DISCONNECTED, EMPD_SIGNAL_STATUS, EMPD_SIGNAL_CURRENT_SONG, EMPD_SIGNAL_QUEUE_LIST, } EMPD_Signals; static const Eldbus_Signal empd_signals[] = { [EMPD_SIGNAL_CONNECTED] = {"Connected", NULL, 0}, [EMPD_SIGNAL_DISCONNECTED] = {"Disconnected", NULL, 0}, [EMPD_SIGNAL_STATUS] = {"Status", ELDBUS_ARGS({"i", "volume"}, {"b", "repeat"}, {"b", "random"}, {"b", "single"}, {"b", "consume"}, {"u", "queue_version"}, {"u", "queue_length"}, {"d", "mixrampdb"}, {"u", "state"}, {"i", "song_pos"}, {"i", "songid"}, {"u", "song_length"}, {"u", "song_elapsed"}, {"u", "bitrate"}, {"u", "sample_rate"}, {"u", "sample_bits"}, {"u", "sample_channels"}, {"i", "next_song_pos"}, {"i", "next_songid"}), 0}, [EMPD_SIGNAL_CURRENT_SONG] = {"CurrentSong", ELDBUS_ARGS({"s", "uri"}, {"t", "last_modified"}, {"u", "duration"}, {"s", "artist"}, {"s", "title"}, {"s", "album"}, {"i", "track"}, {"s", "name"}, {"s", "date"}, {"s", "disc"}, {"i", "song_pos"}, {"i", "songid"}), 0}, [EMPD_SIGNAL_QUEUE_LIST] = {"QueueList", ELDBUS_ARGS({"a(stusssisssii)", "array_of_songs"}), 0}, {NULL, NULL, 0} }; static EMPD *empd = NULL; static Eina_List *handlers = NULL; static Eina_Mempool *slist_mempool = NULL; static Eldbus_Connection *dbus_conn = NULL; static Eldbus_Service_Interface *empd_iface = NULL; static inline void fdh_update(void) { int flags; flags = mpd_async_events(empd->async); flags &= ~MPD_ASYNC_EVENT_ERROR & ~MPD_ASYNC_EVENT_HUP; ecore_main_fd_handler_active_set(empd->fdh, flags); } static inline void cmd_append(EMPD_Command cmd) { E_Slist *es; es = eina_mempool_malloc(slist_mempool, sizeof(E_Slist)); es->command = cmd; es->next = NULL; if (empd->cmds) empd->last->next = es; else empd->cmds = es; //fprintf(stderr, "CMD ADD: %s\n", cmd_txt[es->command]); empd->last = es; //es = empd->cmds; //while (es) //{ //fprintf(stderr, "CMD ADD CYCLE: %s\n", cmd_txt[es->command]); //es = es->next; //} } static inline void cmd_remove(void) { E_Slist *es = empd->cmds; if (!es) return; //fprintf(stderr, "CMD REMOVE: %s\n", cmd_txt[es->command]); //if (es->next) //fprintf(stderr, "CMD NEXT: %s\n", cmd_txt[es->next->command]); //else //fprintf(stderr, "CMD END\n"); empd->cmds = es->next; eina_mempool_free(slist_mempool, es); if (!empd->cmds) empd->last = NULL; empd->cur = NULL; //es = empd->cmds; //while (es) //{ //fprintf(stderr, "CMD REMOVE CYCLE: %s\n", cmd_txt[es->command]); //if (es == es->next) abort(); //es = es->next; //} } static inline EMPD_Command cmd_get(void) { return empd->cmds ? empd->cmds->command : EMPD_COMMAND_NONE; } static inline Ecore_Con_Type conn_type_get(const char *host) { if (host[0] == '/') return ECORE_CON_LOCAL_SYSTEM; return ECORE_CON_REMOTE_TCP; } static Eina_Bool pinger_cb(void *d EINA_UNUSED) { mpd_async_send_command(empd->async, "status", NULL); cmd_append(EMPD_COMMAND_STATUS); fdh_update(); return EINA_TRUE; } static Eina_Bool fdh_func(void *d EINA_UNUSED, Ecore_Fd_Handler *fdh EINA_UNUSED) { char *txt; if (!mpd_async_io(empd->async, MPD_ASYNC_EVENT_READ | MPD_ASYNC_EVENT_WRITE)) { ecore_main_loop_quit(); return EINA_FALSE; } fdh_update(); txt = mpd_async_recv_line(empd->async); if (!txt) return EINA_TRUE; while (txt) { const char *name, *value; enum mpd_parser_result res = mpd_parser_feed(empd->parser, txt); switch (res) { case MPD_PARSER_MALFORMED: abort(); //FIXME /** * MPD has returned "ACK" with an error code. Call * mpd_parser_get_server_error() to get the error code. */ case MPD_PARSER_ERROR: if (cmd_get() == EMPD_COMMAND_PASSWORD) { /* login failure */ ecore_main_loop_quit(); return EINA_TRUE; } if (mpd_async_get_error(empd->async) == MPD_ERROR_CLOSED) { sleep(2); ecore_app_restart(); } break; /** * MPD has returned a name-value pair. Call * mpd_parser_get_name() and mpd_parser_get_value(). */ case MPD_PARSER_PAIR: /** * MPD has returned "OK" or "list_OK" (check with * mpd_parser_is_discrete()). */ name = mpd_parser_get_name(empd->parser); value = mpd_parser_get_value(empd->parser); //fprintf(stderr, "[%s]|%s: %s\n", cmd_txt[cmd_get()], name, value); break; case MPD_PARSER_SUCCESS: //fprintf(stderr, "[%s]|SUCCESS\n", cmd_txt[cmd_get()]); if (cmd_get() == EMPD_COMMAND_PASSWORD) { eldbus_service_signal_emit(empd_iface, EMPD_SIGNAL_CONNECTED); empd->pinger = ecore_poller_add(ECORE_POLLER_CORE, 8, pinger_cb, NULL); } break; } switch (cmd_get()) { case EMPD_COMMAND_STATUS: { struct mpd_status *st; if (!empd->cur) empd->cur = mpd_status_begin(); st = empd->cur; //fprintf(stderr, "[%s]|HIT\n", cmd_txt[cmd_get()]); if (res == MPD_PARSER_PAIR) { //fprintf(stderr, "[%s]|FEED\n", cmd_txt[cmd_get()]); mpd_status_feed(st, &(struct mpd_pair){name, value}); } else { const struct mpd_audio_format *af = mpd_status_get_audio_format(st); //fprintf(stderr, "[%s]|SENT\n", cmd_txt[cmd_get()]); eldbus_service_signal_emit(empd_iface, EMPD_SIGNAL_STATUS, mpd_status_get_volume(st), mpd_status_get_repeat(st), mpd_status_get_random(st), mpd_status_get_single(st), mpd_status_get_consume(st), mpd_status_get_queue_version(st), mpd_status_get_queue_length(st), mpd_status_get_mixrampdb(st), mpd_status_get_state(st), mpd_status_get_song_pos(st), mpd_status_get_song_id(st), mpd_status_get_total_time(st), mpd_status_get_elapsed_time(st), mpd_status_get_kbit_rate(st), af ? af->sample_rate : 0, af ? af->bits : 0, af ? af->channels : 0, mpd_status_get_next_song_pos(st), mpd_status_get_next_song_id(st)); mpd_status_free(st); } } break; case EMPD_COMMAND_CURRENT_SONG: case EMPD_COMMAND_QUEUE_LIST: { struct mpd_song *so; so = empd->cur; //fprintf(stderr, "[%s]|HIT: %d\n", cmd_txt[cmd_get()], res); if (res == MPD_PARSER_PAIR) { if (!empd->cur) { empd->cur = mpd_song_begin(&(struct mpd_pair){name, value}); break; } mpd_song_feed(so, &(struct mpd_pair){name, value}); //fprintf(stderr, "[%s]|FEED\n", cmd_txt[cmd_get()]); if (cmd_get() != EMPD_COMMAND_CURRENT_SONG) { if (!strcmp(name, "Id")) { /* at end of current song, push to array * for sending all at once later */ if (!empd->pending) empd->pending = eina_array_new(1); eina_array_push(empd->pending, so); empd->cur = NULL; } } } else if (cmd_get() == EMPD_COMMAND_CURRENT_SONG) { const char *track = mpd_song_get_tag(so, MPD_TAG_TRACK, 0); //fprintf(stderr, "CURRENT\n"); eldbus_service_signal_emit(empd_iface, EMPD_SIGNAL_CURRENT_SONG, STRING_SAFETY(mpd_song_get_uri(so)), mpd_song_get_last_modified(so), mpd_song_get_duration(so), STRING_SAFETY(mpd_song_get_tag(so, MPD_TAG_ARTIST, 0)), STRING_SAFETY(mpd_song_get_tag(so, MPD_TAG_TITLE, 0)), STRING_SAFETY(mpd_song_get_tag(so, MPD_TAG_ALBUM, 0)), track ? atoi(track) : 0, STRING_SAFETY(mpd_song_get_tag(so, MPD_TAG_NAME, 0)), STRING_SAFETY(mpd_song_get_tag(so, MPD_TAG_DATE, 0)), STRING_SAFETY(mpd_song_get_tag(so, MPD_TAG_DISC, 0)), mpd_song_get_pos(so), mpd_song_get_id(so)); mpd_song_free(so); } else if (cmd_get() == EMPD_COMMAND_QUEUE_LIST) { Eldbus_Message *msg; Eldbus_Message_Iter *iter, *array, *struc; Eina_Iterator *it; struct mpd_song *so; //fprintf(stderr, "QUEUE\n"); msg = eldbus_service_signal_new(empd_iface, EMPD_SIGNAL_QUEUE_LIST); iter = eldbus_message_iter_get(msg); array = eldbus_message_iter_container_new(iter, 'a', "(stusssisssii)"); it = eina_array_iterator_new(empd->pending); EINA_ITERATOR_FOREACH(it, so) { /* holy shit. */ const char *track = mpd_song_get_tag(so, MPD_TAG_TRACK, 0); eldbus_message_iter_arguments_append(array, "(stusssisssii)", &struc); eldbus_message_iter_arguments_append(struc, "stusssisssii", STRING_SAFETY(mpd_song_get_uri(so)), mpd_song_get_last_modified(so), mpd_song_get_duration(so), STRING_SAFETY(mpd_song_get_tag(so, MPD_TAG_ARTIST, 0)), STRING_SAFETY(mpd_song_get_tag(so, MPD_TAG_TITLE, 0)), STRING_SAFETY(mpd_song_get_tag(so, MPD_TAG_ALBUM, 0)), track ? atoi(track) : 0, STRING_SAFETY(mpd_song_get_tag(so, MPD_TAG_NAME, 0)), STRING_SAFETY(mpd_song_get_tag(so, MPD_TAG_DATE, 0)), STRING_SAFETY(mpd_song_get_tag(so, MPD_TAG_DISC, 0)), mpd_song_get_pos(so), mpd_song_get_id(so)); mpd_song_free(so); eldbus_message_iter_container_close(array, struc); } eina_iterator_free(it); eina_array_free(empd->pending); empd->pending = NULL; eldbus_message_iter_container_close(iter, array); //fprintf(stderr, "[%s]|SENT\n", cmd_txt[cmd_get()]); eldbus_service_signal_send(empd_iface, msg); } } break; default: break; } if (res == MPD_PARSER_SUCCESS) cmd_remove(); txt = mpd_async_recv_line(empd->async); //fprintf(stderr, "[%s]|NEXT LINE\n", cmd_txt[cmd_get()]); } //fprintf(stderr, "[%s]|FDH_DONE\n", cmd_txt[cmd_get()]); return EINA_TRUE; } static Eina_Bool con(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Con_Event_Server_Add *ev) { return ECORE_CALLBACK_DONE; } static Eina_Bool del(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Con_Event_Server_Del *ev) { if (!empd->async) ecore_main_loop_quit(); return ECORE_CALLBACK_DONE; } static Eina_Bool data(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Con_Event_Server_Data *ev) { int fd, flags; char *txt; fd = dup(ecore_con_server_fd_get(ev->server)); fcntl(fd, F_SETFL, O_NONBLOCK); flags = fcntl(fd, F_GETFD); flags |= FD_CLOEXEC; fcntl(fd, F_SETFD, flags); empd->fdh = ecore_main_fd_handler_add(fd, ECORE_FD_READ | ECORE_FD_ERROR, fdh_func, NULL, NULL, NULL); empd->async = mpd_async_new(fd); ecore_con_server_del(ev->server); empd->svr = NULL; /* this will always just be the first line * OK MPD 0.18.0\n */ txt = ev->data; txt[ev->size - 1] = 0; empd->parser = mpd_parser_new(); if (mpd_settings_get_password(empd->settings)) { mpd_async_send_command(empd->async, "password", mpd_settings_get_password(empd->settings), NULL); cmd_append(EMPD_COMMAND_PASSWORD); fdh_update(); } else { eldbus_service_signal_emit(empd_iface, EMPD_SIGNAL_CONNECTED); empd->pinger = ecore_poller_add(ECORE_POLLER_CORE, 8, pinger_cb, NULL); } return ECORE_CALLBACK_DONE; } static void _dbus_request_name_cb(void *data EINA_UNUSED, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED) { unsigned int flag; if (eldbus_message_error_get(msg, NULL, NULL)) fprintf(stderr, "Could not request bus name\n"); else if (!eldbus_message_arguments_get(msg, "u", &flag)) fprintf(stderr, "Could not get arguments on on_name_request\n"); else if (!(flag & ELDBUS_NAME_REQUEST_REPLY_PRIMARY_OWNER)) fprintf(stderr, "Name already in use\n"); } #define DBUS_BASIC_CB(name, CMD) \ static Eldbus_Message * \ _dbus_##name##_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message *msg) \ { \ mpd_async_send_command(empd->async, S(name), NULL); \ fdh_update(); \ cmd_append(EMPD_COMMAND_##CMD); \ return eldbus_message_method_return_new(msg); \ } DBUS_BASIC_CB(status, STATUS) DBUS_BASIC_CB(currentsong, CURRENT_SONG) DBUS_BASIC_CB(play, PLAY) static Eldbus_Message * _dbus_pause_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message *msg) { Eina_Bool mode; const char *sig; sig = eldbus_message_signature_get(msg); if (sig && (!strcmp(sig, "b")) && eldbus_message_arguments_get(msg, "b", &mode)) { mpd_async_send_command(empd->async, "pause", mode ? "1" : "0", NULL); cmd_append(EMPD_COMMAND_PAUSE); } else { mpd_async_send_command(empd->async, "pause", NULL); cmd_append(EMPD_COMMAND_TOGGLE_PAUSE); } fdh_update(); return eldbus_message_method_return_new(msg); } DBUS_BASIC_CB(stop, STOP) DBUS_BASIC_CB(next, NEXT) DBUS_BASIC_CB(previous, PREVIOUS) static Eldbus_Message * _dbus_seek_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message *msg) { unsigned int songid, seconds; if (eldbus_message_arguments_get(msg, "uu", &songid, &seconds)) { char id[256], pos[256]; snprintf(id, sizeof(id), "%u", songid); snprintf(pos, sizeof(pos), "%u", seconds); mpd_async_send_command(empd->async, "seekid", id, pos, NULL); cmd_append(EMPD_COMMAND_SEEK); fdh_update(); } return eldbus_message_method_return_new(msg); } static Eldbus_Message * _dbus_playid_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message *msg) { unsigned int songid; if (eldbus_message_arguments_get(msg, "u", &songid)) { char id[256]; snprintf(id, sizeof(id), "%u", songid); mpd_async_send_command(empd->async, "playid", id, NULL); cmd_append(EMPD_COMMAND_PLAY_ID); fdh_update(); } return eldbus_message_method_return_new(msg); } static Eldbus_Message * _dbus_queue_list_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message *msg) { int start, end; const char *sig; sig = eldbus_message_signature_get(msg); if (sig && (!strcmp(sig, "ii")) && eldbus_message_arguments_get(msg, "ii", &start, &end)) { char s[256]; if ((end != -1) && (end < start)) return eldbus_message_method_return_new(msg); if (end == -1) snprintf(s, sizeof(s), "%d:", start); else snprintf(s, sizeof(s), "%d:%d", start, end); mpd_async_send_command(empd->async, "playlistinfo", s, NULL); } else mpd_async_send_command(empd->async, "playlistinfo", NULL); cmd_append(EMPD_COMMAND_QUEUE_LIST); fdh_update(); return eldbus_message_method_return_new(msg); } static const Eldbus_Method empd_methods[] = { { "Status", NULL, NULL, _dbus_status_cb, 0}, { "CurrentSong", NULL, NULL, _dbus_currentsong_cb, 0}, { "Play", NULL, NULL, _dbus_play_cb, 0}, { "Pause", ELDBUS_ARGS({"b", "mode"}), NULL, _dbus_pause_cb, 0}, { "TogglePause", NULL, NULL, _dbus_pause_cb, 0}, { "Stop", NULL, NULL, _dbus_stop_cb, 0}, { "Next", NULL, NULL, _dbus_next_cb, 0}, { "Previous", NULL, NULL, _dbus_previous_cb, 0}, { "Seek", ELDBUS_ARGS({"u", "songid"}, {"u", "position_in_seconds"}), NULL, _dbus_seek_cb, 0}, { "PlayId", ELDBUS_ARGS({"u", "songid"}), NULL, _dbus_playid_cb, 0}, { "QueueList", NULL, NULL, _dbus_queue_list_cb, 0}, { "QueueListRange", ELDBUS_ARGS({"i", "start"}, {"i", "num"}), NULL, _dbus_queue_list_cb, 0}, {NULL, NULL, NULL, NULL, 0} }; static const Eldbus_Service_Interface_Desc base_desc = { EMPD_METHOD_BASE, empd_methods, empd_signals, NULL, NULL, NULL }; int main(int argc, char *argv[]) { const char *h; struct mpd_settings *settings; if (argc < 2) { int port = DEFAULT_PORT; char *p; h = getenv("MPD_HOST"); if (!h) return 1; p = strchr(h, ':'); if (p) { char host[1024] = {0}; port = strtol(p + 1, NULL, 10); memcpy(host, h, p - h); settings = mpd_settings_new(host, port, 3000, NULL, NULL); } else settings = mpd_settings_new(h, port, 3000, NULL, NULL); } else settings = mpd_settings_new(argv[1], atoi(argv[2]), 3000, NULL, NULL); if (!settings) return 1; empd = calloc(1, sizeof(EMPD)); empd->settings = settings; ecore_con_init(); eldbus_init(); ecore_app_args_set(argc, (const char**)argv); { const char *t; t = getenv("EINA_MEMPOOL"); if ((!t) || (!t[0])) t = "chained_mempool"; slist_mempool = eina_mempool_add(t, "E_Slist", NULL, sizeof(E_Slist), 64); if (!slist_mempool) slist_mempool = eina_mempool_add("pass_through", "E_Slist", NULL, sizeof(E_Slist), 64); } dbus_conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION); eldbus_name_request(dbus_conn, EMPD_METHOD_BASE, 0, _dbus_request_name_cb, NULL); empd_iface = eldbus_service_interface_register(dbus_conn, "/", &base_desc); E_LIST_HANDLER_APPEND(handlers, ECORE_CON_EVENT_SERVER_ADD, con, NULL); E_LIST_HANDLER_APPEND(handlers, ECORE_CON_EVENT_SERVER_DEL, del, NULL); E_LIST_HANDLER_APPEND(handlers, ECORE_CON_EVENT_SERVER_DATA, data, NULL); h = mpd_settings_get_host(settings); empd->svr = ecore_con_server_connect(conn_type_get(h), h, (int)mpd_settings_get_port(settings), empd); ecore_main_loop_begin(); return 0; }