/* simple-libmpd.c * * Copyright (c) 2006-2012 Landry Breuil * This code is licenced under a BSD-style licence. * (OpenBSD variant modeled after the ISC licence) * All rights reserved. * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* double inclusion ?*/ #ifdef HAVE_CONFIG_H #include #endif #include "simple-libmpd.h" /* for DBG(..) macros */ #include #include #include #include #include #include #include #include #include #include int mpd_wait_for_answer(MpdObj *); int mpd_send_single_cmd(MpdObj*, char*); void send_complex_cmd(MpdObj*, char*, void (*)(), void *); void parse_status_answer(MpdObj*, void*); void parse_one_song(MpdObj*, void*); void parse_playlistinfo_answer(MpdObj*, void*); void parse_outputs_answer(MpdObj*, void*); MpdObj* mpd_new(char* host, int port, char* pass) { MpdObj* mo = g_new0(MpdObj,1); DBG("host=%s, port=%d, pass=%s", host, port, pass); mo->host = g_strdup(host); mo->port = port; mo->pass = g_strdup(pass); mo->socket = 0; mo->status = 0; mo->repeat = 0; mo->random = 0; mo->curvol = 0; mo->song = 0; mo->songid = 0; mo->playlistlength = 0; mo->cursong = NULL; mo->error = 0; mo->buffer[0] = '\0'; mo->buflen = 0; return mo; } void mpd_free(MpdObj* mo) { DBG("!"); if (mo->socket) close(mo->socket); g_free(mo->host); g_free(mo->pass); g_free(mo); } void mpd_connect(MpdObj* mo) { struct hostent* remote_he; struct sockaddr* remote_sa; struct sockaddr_in remote_si; int err,nbread; struct timeval tv; fd_set fds; mo->buffer[0] = '\0'; mo->buflen = 0; /* ??? */ if (mo->socket) close(mo->socket); DBG("!"); if (!(remote_he = (struct hostent*) gethostbyname(mo->host))) { mo->error = MPD_ERROR_UNKHOST; DBG("ERROR @gethostbyname(), err=%s",strerror(errno)); return; } memset(&remote_si, 0, sizeof(struct sockaddr_in)); remote_si.sin_family = AF_INET; remote_si.sin_port = htons(mo->port); memcpy((char *)&remote_si.sin_addr.s_addr,( char *)remote_he->h_addr, remote_he->h_length); remote_sa = (struct sockaddr *)&remote_si; if ((mo->socket = socket(AF_INET,SOCK_STREAM,0)) < 0) { mo->error = MPD_ERROR_NOSOCK; DBG("ERROR @socket(), err=%s",strerror(errno)); return; } if (connect(mo->socket,remote_sa, sizeof(struct sockaddr_in)) < 0) { mo->error = MPD_ERROR_CONNPORT; DBG("ERROR @connect(), err=%s",strerror(errno)); return; } tv.tv_sec = 1; tv.tv_usec = 0; FD_ZERO(&fds); FD_SET(mo->socket,&fds); if((err = select(mo->socket+1,&fds,NULL,NULL,&tv)) == 1) { if ((nbread = recv(mo->socket, mo->buffer, MAXBUFLEN, 0)) < 0) { mo->error = MPD_ERROR_NORESPONSE; DBG("ERROR @recv(), err=%s",strerror(errno)); return; } if (nbread == 0) { mo->error = MPD_ERROR_CONNCLOSED; DBG("ERROR @recv(), connection closed by server"); return; } mo->buflen = nbread; mo->buffer[mo->buflen] = '\0'; } else if(err < 0) { mo->error = MPD_ERROR_CONNPORT; DBG("ERROR @select(), err=%s",strerror(errno)); return; } else { mo->error = MPD_ERROR_NORESPONSE; DBG("ERROR @select(), timeoute'ed -> err=%s",strerror(errno)); return; } if (strncmp(mo->buffer,MPD_WELCOME_MESSAGE, strlen(MPD_WELCOME_MESSAGE))) { mo->error = MPD_ERROR_NOTMPD; DBG("ERROR @strncmp() -> answer didn't come from mpd"); return; } DBG("Received %d bytes = welcome message :\"%s\"", mo->buflen, mo->buffer); *mo->buffer = '\0'; mo->buflen = 0; mo->error = 0; } void mpd_disconnect(MpdObj* mo) { DBG("!"); if (mo->socket) close(mo->socket); } int mpd_status_get_volume(MpdObj* mo) { DBG("! return %d",mo->curvol); return mo->curvol; } int mpd_wait_for_answer(MpdObj *mo) { struct timeval tv; int err,nbread; fd_set fds; err = nbread = 0; DBG("!"); tv.tv_sec = 1; tv.tv_usec = 0; FD_ZERO(&fds); FD_SET(mo->socket,&fds); if((err = select(mo->socket+1,&fds,NULL,NULL,&tv)) == 1) { if ((nbread = recv(mo->socket, mo->buffer, MAXBUFLEN, 0)) < 0) { mo->error = MPD_ERROR_NORESPONSE; DBG("ERROR @recv(), err=%s",strerror(errno)); return -1; } if (nbread == 0) { mo->error = MPD_ERROR_CONNCLOSED; DBG("ERROR @recv(), connection closed by server"); return -1; } mo->buflen = nbread; mo->buffer[mo->buflen] = '\0'; DBG("Read %d bytes, buff=\"%s\"", nbread, mo->buffer); } else if(err < 0) { mo->error = MPD_ERROR_CONNPORT; DBG("ERROR @select(), err=%s",strerror(errno)); return -1; } else { mo->error = MPD_ERROR_NORESPONSE; DBG("ERROR @select(), timeoute'ed -> err=%s",strerror(errno)); return -1; } if (!strncmp(mo->buffer,"ACK",3)) mo->error = MPD_NOTOK; else mo->error = MPD_OK; return nbread; } int mpd_send_single_cmd(MpdObj*mo, char* cmd) { int nbwri = 0; if (mo->socket) { DBG("Sending \"%s\"",cmd); if ((nbwri = send(mo->socket, cmd, strlen(cmd), 0)) <= 0) { mo->error = MPD_ERROR_SENDING; DBG("ERROR @send(), err=%s",strerror(errno)); } DBG("Sent %d bytes",nbwri); /* wait for OK */ mpd_wait_for_answer(mo); if(!mo->error) { if (strcmp(mo->buffer,"OK\n") != 0) { mo->error = MPD_FAILED; DBG("ERROR : did not received OK"); } } *mo->buffer = '\0'; mo->buflen = 0; } else { mo->error = MPD_ERROR_NOSOCK; DBG("ERROR : socket == NULL ?"); } return ((!mo->error) ? MPD_OK : MPD_FAILED); } void send_complex_cmd(MpdObj* mo, char* cmd, void (*parse_answer_fct)(), void *res) { int nbwri, nbread, tmp_bufsize; char *ptr; char *tmp_buffer, *tmp; /* write 'cmd' to socket */ if (mo->socket) { DBG("Sending \"%s\"",cmd); if ((nbwri = send(mo->socket, cmd, strlen(cmd), 0)) < 0) { mo->error = MPD_ERROR_SENDING; DBG("ERROR @send(), err=%s",strerror(errno)); return; } DBG("Sent %d bytes",nbwri); nbread = mpd_wait_for_answer(mo); /* special case for long answers with 'playlistinfo' - hack to loop until we have received the final OK\n*/ while (!mo->error && ( nbread == MAXBUFLEN || 0 != strcmp(mo->buffer + strlen(mo->buffer) - 3,"OK\n"))) { /* save the end of the buffer from last occurence of 'file:', and replace it with 'OK\n' */ ptr = g_strrstr(mo->buffer, "file:"); if (!ptr) { mo->error = MPD_ERROR_CONNCLOSED; DBG("ERROR parsing reply"); *mo->buffer = '\0'; mo->buflen = 0; return; } tmp_buffer = (char*) calloc (MAXBUFLEN*2, sizeof(char)); strcpy(tmp_buffer, ptr); tmp_bufsize = strlen(tmp_buffer); strcpy(ptr, "OK\n"); DBG("tmp_buffer contains \"%s\"", tmp_buffer); DBG("buffer now contains \"%s\"", mo->buffer); /* parse buffer */ (*parse_answer_fct)(mo, res); /* re-read stuff */ nbread = mpd_wait_for_answer(mo); /* append the saved end to the beginning of buffer */ /* ugly memory management. really. */ tmp = (char*) calloc (MAXBUFLEN*2, sizeof(char)); strcpy(tmp, mo->buffer); strcpy(mo->buffer, tmp_buffer); strcpy(mo->buffer + tmp_bufsize, tmp); mo->buffer[tmp_bufsize + nbread] = '\0'; DBG("added tmp_buffer at the beginning of buffer, now contains \"%s\"", mo->buffer); free(tmp_buffer); free(tmp); } /* finally parse the end of the answer => 'currentsong' and 'status' parsing should jump directly here*/ if (!mo->error) (*parse_answer_fct)(mo, res); *mo->buffer = '\0'; mo->buflen = 0; } else { mo->error = MPD_ERROR_NOSOCK; DBG("ERROR : socket == NULL ?"); } } void parse_status_answer(MpdObj *mo, void* unused_param) { gchar **lines, **tokens; int i; mo->songid = -1; lines = g_strsplit(mo->buffer, "\n", 0); for (i = 0 ; lines[i] && strncmp(lines[i], "OK", 2) ; i++) { tokens = g_strsplit(lines[i], ":", 2); /* remove leading whitespace */ tokens[1] = g_strchug(tokens[1]); DBG("key=\"%s\",value=\"%s\"", tokens[0], tokens[1]); if (0 == strcmp("volume",tokens[0])) mo->curvol = atoi(tokens[1]); else if (0 == strcmp("repeat",tokens[0])) mo->repeat = atoi(tokens[1]); else if (0 == strcmp("random",tokens[0])) mo->random = atoi(tokens[1]); else if (0 == strcmp("playlistlength",tokens[0])) mo->playlistlength = atoi(tokens[1]); else if (0 == strcmp("state", tokens[0])) { if (0 == strcmp("play", tokens[1])) mo->status = MPD_PLAYER_PLAY; else if (0 == strcmp("pause",tokens[1])) mo->status = MPD_PLAYER_PAUSE; else if (0 == strcmp("stop", tokens[1])) mo->status = MPD_PLAYER_STOP; } else if (0 == strcmp("song",tokens[0])) mo->song = atoi(tokens[1]); else if (0 == strcmp("songid",tokens[0])) mo->songid = atoi(tokens[1]); g_strfreev(tokens); } g_strfreev(lines); } int mpd_status_update(MpdObj* mo) { mo->status = 0; DBG("!"); send_complex_cmd(mo, "status\n", parse_status_answer, NULL); return ((!mo->error) ? MPD_OK : MPD_FAILED); } void parse_one_song(MpdObj *mo, void* param) { mpd_Song* ms = (mpd_Song*) param; gchar **lines, **tokens; int i; ms->file = ms->artist = ms->album = ms->title = ms->track = NULL; ms->id = ms->pos = -1; lines = g_strsplit(mo->buffer, "\n", 0); for (i = 0 ; lines[i] && strcmp(lines[i], "OK") ; i++) { tokens = g_strsplit(lines[i], ":", 2); /* remove leading whitespace */ tokens[1] = g_strchug(tokens[1]); DBG("key=\"%s\",value=\"%s\"", tokens[0], tokens[1]); if (!ms->file && 0 == strcmp("file", tokens[0])) ms->file = g_strdup(tokens[1]); else if (!ms->artist && 0 == strcmp("Artist",tokens[0])) ms->artist= g_strdup(tokens[1]); else if (!ms->album && 0 == strcmp("Album", tokens[0])) ms->album = g_strdup(tokens[1]); else if (!ms->title && 0 == strcmp("Title", tokens[0])) ms->title = g_strdup(tokens[1]); else if (!ms->track && 0 == strcmp("Track", tokens[0])) ms->track = g_strdup(tokens[1]); else if (ms->pos < 0 && 0 == strcmp("Pos", tokens[0])) ms->pos = atoi(tokens[1]); else if (ms->id < 0 && 0 == strcmp("Id", tokens[0])) ms->id = atoi(tokens[1]); g_strfreev(tokens); } if (ms->id < 0) mo->error = MPD_FAILED; g_strfreev(lines); } void parse_playlistinfo_answer(MpdObj *mo, void *param) { MpdData* md = (MpdData*) param; mpd_Song* ms; gchar **lines, **tokens; int i = 0; lines = g_strsplit(mo->buffer, "\n", 0); while(lines[i] && strcmp(lines[i],"OK")) { ms = &md->allsongs[md->nb]; ms->file = ms->artist = ms->album = ms->title = ms->track = NULL; ms->id = ms->pos = -1; DBG("Going to parse song #%d", md->nb); while(lines[i] && strcmp(lines[i],"OK") && ms->id < 0) { tokens = g_strsplit(lines[i], ":", 2); /* remove leading whitespace */ tokens[1] = g_strchug(tokens[1]); DBG("key=\"%s\",value=\"%s\"", tokens[0], tokens[1]); if (!ms->file && 0 == strcmp("file", tokens[0])) ms->file = g_strdup(tokens[1]); else if (!ms->artist && 0 == strcmp("Artist",tokens[0])) ms->artist= g_strdup(tokens[1]); else if (!ms->album && 0 == strcmp("Album", tokens[0])) ms->album = g_strdup(tokens[1]); else if (!ms->title && 0 == strcmp("Title", tokens[0])) ms->title = g_strdup(tokens[1]); else if (!ms->track && 0 == strcmp("Track", tokens[0])) ms->track = g_strdup(tokens[1]); else if (ms->pos < 0 && 0 == strcmp("Pos", tokens[0])) ms->pos = atoi(tokens[1]); else if (ms->id < 0 && 0 == strcmp("Id", tokens[0])) ms->id = atoi(tokens[1]); i++; g_strfreev(tokens); } md->nb++; } g_strfreev(lines); DBG("Got 'OK', md->nb = %d", md->nb); } void parse_outputs_answer(MpdObj *mo, void *param) { MpdData* md = (MpdData*) param; gchar **lines, **tokens; int i = 0; lines = g_strsplit(mo->buffer, "\n", 0); while(lines[i] && strcmp(lines[i],"OK")) { md->alloutputs[md->nb] = g_new(mpd_Output, 1); md->alloutputs[md->nb]->enabled = -1; DBG("Going to parse output #%d", md->nb); while(lines[i] && strcmp(lines[i],"OK") && md->alloutputs[md->nb]->enabled < 0) { tokens = g_strsplit(lines[i], ":", 2); /* remove leading whitespace */ tokens[1] = g_strchug(tokens[1]); DBG("key=\"%s\",value=\"%s\"", tokens[0], tokens[1]); if (0 == strcmp("outputid",tokens[0])) md->alloutputs[md->nb]->id = atoi(tokens[1]); else if (0 == strcmp("outputname",tokens[0])) md->alloutputs[md->nb]->name = g_strdup(tokens[1]); else if (0 == strcmp("outputenabled",tokens[0])) md->alloutputs[md->nb]->enabled = atoi(tokens[1]); i++; g_strfreev(tokens); } md->nb++; /* ignore extra lines with 'attribute:' */ while (lines[i] != NULL && strcmp(lines[i],"OK") && 0 == strncmp(lines[i], "attribute:", 10)) i++; if (strcmp(lines[i],"OK")) { /* make room for the next output ptr */ md->alloutputs = g_renew(mpd_Output*, md->alloutputs, md->nb + 1); } } g_strfreev(lines); } mpd_Song* mpd_playlist_get_current_song(MpdObj* mo) { DBG("!"); if (mo->cursong != NULL && mo->cursong->id != mo->songid) { if (mo->cursong->file) free(mo->cursong->file); if (mo->cursong->artist) free(mo->cursong->artist); if (mo->cursong->album) free(mo->cursong->album); if (mo->cursong->title) free(mo->cursong->title); if (mo->cursong->track) free(mo->cursong->track); free(mo->cursong); mo->cursong = NULL; } if (mo->cursong == NULL) { mo->cursong = g_new0(mpd_Song,1); DBG("updating currentsong"); send_complex_cmd(mo, "currentsong\n", parse_one_song, (void*) mo->cursong); } return ((!mo->error) ? mo->cursong : NULL); } int mpd_playlist_get_playlist_length(MpdObj* mo) { return mo->playlistlength; } int mpd_player_get_current_song_pos(MpdObj* mo) { DBG("!"); return ((mpd_status_update(mo) != MPD_OK) ? -1 : mo->song); } MpdData* mpd_playlist_get_changes(MpdObj* mo, int old_playlist_id) { MpdData* md = g_new0(MpdData,1); md->cur = md->nb = 0; md->type = MPD_DATA_TYPE_SONG; md->allsongs = g_new(mpd_Song, mo->playlistlength); DBG("!"); send_complex_cmd(mo, "playlistinfo\n", parse_playlistinfo_answer, (void*) md); md->song = &(md->allsongs[0]); return ((!mo->error) ? md : NULL); } MpdData* mpd_data_get_next(MpdData* md) { md->cur++; /* free(md) and return NULL if after last */ if (md->cur == md->nb) { for (md->cur--; md->cur; md->cur--) { switch (md->type) { case MPD_DATA_TYPE_OUTPUT_DEV: if (md->alloutputs[md->cur]->name) free(md->alloutputs[md->cur]->name); break; case MPD_DATA_TYPE_SONG: if (md->allsongs[md->cur].file) free(md->allsongs[md->cur].file); if (md->allsongs[md->cur].artist) free(md->allsongs[md->cur].artist); if (md->allsongs[md->cur].album) free(md->allsongs[md->cur].album); if (md->allsongs[md->cur].title) free(md->allsongs[md->cur].title); if (md->allsongs[md->cur].track) free(md->allsongs[md->cur].track); break; } } switch (md->type) { case MPD_DATA_TYPE_OUTPUT_DEV: g_free(md->alloutputs); break; case MPD_DATA_TYPE_SONG: g_free(md->allsongs); break; } g_free(md); DBG("Free()'d md"); return NULL; } switch (md->type) { case MPD_DATA_TYPE_OUTPUT_DEV: md->output_dev = md->alloutputs[md->cur]; break; case MPD_DATA_TYPE_SONG: md->song = (&md->allsongs[md->cur]); break; } return md; } MpdData* mpd_server_get_output_devices(MpdObj* mo) { MpdData* md = g_new0(MpdData,1); DBG("!"); md->cur = md->nb = 0; md->alloutputs = g_new(mpd_Output*,1); md->type = MPD_DATA_TYPE_OUTPUT_DEV; send_complex_cmd(mo, "outputs\n", parse_outputs_answer, (void*) md); md->output_dev = md->alloutputs[0]; return ((!mo->error) ? md : NULL); } int mpd_server_set_output_device (MpdObj* mo, int id, int state) { char outbuf[18]; /* write (enable|disable)output 'id' to socket */ DBG("!"); snprintf(outbuf, sizeof(outbuf), "%soutput %d\n",(state ? "enable" : "disable"), id); return mpd_send_single_cmd(mo,outbuf); } void mpd_status_set_volume(MpdObj* mo, int newvol) { char outbuf[15]; /* write setvol 'newvol' to socket */ DBG("!"); snprintf(outbuf, sizeof(outbuf), "setvol %d\n",newvol); mpd_send_single_cmd(mo,outbuf); } int mpd_player_get_state(MpdObj* mo) { DBG("! return %d",mo->status); return mo->status; } int mpd_player_get_random(MpdObj* mo) { DBG("! return %d",mo->random); return mo->random; } int mpd_player_set_random(MpdObj* mo, int random) { char outbuf[15]; DBG("!"); snprintf(outbuf, sizeof(outbuf), "random %d\n",random); return mpd_send_single_cmd(mo,outbuf); } int mpd_player_set_repeat(MpdObj* mo, int repeat) { char outbuf[15]; DBG("!"); snprintf(outbuf, sizeof(outbuf), "repeat %d\n",repeat); return mpd_send_single_cmd(mo,outbuf); } int mpd_player_get_repeat(MpdObj* mo) { DBG("! return %d",mo->repeat); return mo->repeat; } int mpd_player_prev(MpdObj* mo) { DBG("!"); return mpd_send_single_cmd(mo,"previous\n"); } int mpd_player_next(MpdObj* mo) { DBG("!"); return mpd_send_single_cmd(mo,"next\n"); } int mpd_player_stop(MpdObj* mo) { DBG("!"); return mpd_send_single_cmd(mo,"stop\n"); } int mpd_player_pause(MpdObj* mo) { DBG("!"); if (mo->status != MPD_PLAYER_PLAY) return mpd_send_single_cmd(mo,"pause 0\n"); else return mpd_send_single_cmd(mo,"pause 1\n"); } int mpd_player_play(MpdObj* mo) { DBG("!"); return mpd_send_single_cmd(mo,"play\n"); } int mpd_player_play_id(MpdObj* mo, int id) { char outbuf[15]; DBG("!"); snprintf(outbuf, sizeof(outbuf), "playid %d\n",id); return mpd_send_single_cmd(mo,outbuf); } int mpd_check_error(MpdObj* mo) { DBG("! return %d",mo->error); return mo->error; } void mpd_send_password(MpdObj* mo) { char outbuf[256]; int wrote; DBG("!"); /* write password 'password' to socket */ wrote = snprintf(outbuf, sizeof(outbuf), "password %s\n",mo->pass); if (wrote > 255) { /* the password is too long to fit though there doesn't seem to be a * nice way to report this error :-/ */ fprintf(stderr, "xfce4-mpc-plugin: password too long!\n"); mo->error = MPD_ERROR_SYSTEM; return; } mpd_send_single_cmd(mo,outbuf); } void mpd_set_hostname(MpdObj* mo, char* host) { DBG("! new hostname=%s",host); g_free(mo->host); mo->host = g_strdup(host); } void mpd_set_password(MpdObj* mo, char* pass) { DBG("! new password=%s",pass); g_free(mo->pass); mo->pass = g_strdup(pass); } void mpd_set_port(MpdObj* mo,int port) { DBG("! new port=%d",port); mo->port = port; }