summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar waker <wakeroid@gmail.com>2009-08-25 22:58:10 +0200
committerGravatar waker <wakeroid@gmail.com>2009-08-25 22:58:10 +0200
commitd8a6c838e60a50db2ec35a00c45786a2df9d0f43 (patch)
treea6eb64394a2f09594fb641a1448a836c542eb28e
parente961015695b5c65c61a2b945989c38061374e0a7 (diff)
plugins prototyping WIP
-rw-r--r--Makefile.am4
-rw-r--r--README1
-rwxr-xr-xbootstrap.sh2
-rw-r--r--configure.in8
-rw-r--r--deadbeef.h208
-rw-r--r--main.c4
-rw-r--r--palsa.c2
-rw-r--r--plugins.c166
-rw-r--r--plugins.h26
-rw-r--r--plugins/lastfm/Makefile.am7
-rw-r--r--plugins/lastfm/lastfm.c191
11 files changed, 616 insertions, 3 deletions
diff --git a/Makefile.am b/Makefile.am
index df2b63b4..45ea49c8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,7 @@
## Process this file with automake to produce Makefile.in
+ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = gme/Game_Music_Emu-0.5.2 gme/Game_Music_Emu-0.5.2/gme sid/sidplay-libs-2.1.0 dumb pixmaps
+SUBDIRS = gme/Game_Music_Emu-0.5.2 gme/Game_Music_Emu-0.5.2/gme sid/sidplay-libs-2.1.0 dumb pixmaps plugins/lastfm
dumbpath=@top_srcdir@/dumb
sidpath=@top_srcdir@/sid/sidplay-libs-2.1.0
@@ -10,6 +11,7 @@ bin_PROGRAMS = deadbeef
deadbeef_SOURCES =\
main.c common.h\
+ plugins.c plugins.h\
callbacks.c interface.c support.c callbacks.h interface.h support.h\
playlist.c playlist.h gtkplaylist.c gtkplaylist.h\
streamer.c streamer.h\
diff --git a/README b/README
index 30a94190..a9696837 100644
--- a/README
+++ b/README
@@ -6,6 +6,7 @@ dependencies:
gtk+ 2.x (and gthread, and glib)
libsamplerate
libalsa
+ libcurl (for audioscrobbler plugin)
if you want to build from git - install automake, and run ./bootstrap.sh before compiling
also it's convenient to use jam to build instead of ./configure && make && make install
diff --git a/bootstrap.sh b/bootstrap.sh
index e3dbfcb1..bb423635 100755
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -1,5 +1,5 @@
#!/bin/sh
aclocal
+libtoolize
autoconf
automake -a -c
-
diff --git a/configure.in b/configure.in
index b3517cca..23c482b3 100644
--- a/configure.in
+++ b/configure.in
@@ -11,7 +11,10 @@ AC_PROG_CC
AC_PROG_CXX
AC_STDC_HEADERS
AC_PROG_INSTALL
-AC_PROG_RANLIB
+dnl AC_PROG_RANLIB
+AC_PROG_LIBTOOL
+LT_INIT
+AC_CONFIG_MACRO_DIR([m4])
AC_C_BIGENDIAN
AC_DEFINE([PREFIX], [], [Installation prefix])
if test "x$prefix" = "xNONE" ; then
@@ -24,6 +27,8 @@ CFLAGS="$CFLAGS -D_GNU_SOURCE -O2 -DHAVE_UNIX"
CPPFLAGS="$CFLAGS"
PKG_CHECK_MODULES(DEPS, gtk+-2.0 gthread-2.0 glib-2.0 vorbis vorbisfile mad samplerate alsa)
+PKG_CHECK_MODULES(LASTFM_DEPS, libcurl)
+
AC_CHECK_LIB([FLAC], [main],,AC_MSG_ERROR([flac not found]))
AC_SUBST(DEPS_CFLAGS)
AC_SUBST(DEPS_LIBS)
@@ -35,5 +40,6 @@ gme/Game_Music_Emu-0.5.2/Makefile
gme/Game_Music_Emu-0.5.2/gme/Makefile
sid/sidplay-libs-2.1.0/Makefile
dumb/Makefile
+plugins/lastfm/Makefile
])
diff --git a/deadbeef.h b/deadbeef.h
new file mode 100644
index 00000000..8681e10a
--- /dev/null
+++ b/deadbeef.h
@@ -0,0 +1,208 @@
+/*
+ deadbeef.h -- plugin API of the DeaDBeeF audio player
+ http://deadbeef.sourceforge.net
+
+ Copyright (C) 2009 Alexey Yakovenko
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Note: DeaDBeeF player itself uses different license
+*/
+
+
+#ifndef __DEADBEEF_H
+#define __DEADBEEF_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// every plugin must define following entry-point:
+// extern "C" DB_plugin_t* $MODULENAME_load (DB_functions_t *api);
+// where $MODULENAME is a name of module
+// e.g. if your plugin is called "myplugin.so", $MODULENAME is "myplugin"
+// plugin should check vmajor and vminor members of api,
+// and return NULL if they are not compatible
+// otherwise it should return pointer to DB_plugin_t structure
+// that is enough for both static and dynamic modules
+
+// playlist structures
+
+// playlist item
+// there are "public" fields, available to plugins
+typedef struct {
+ char *fname; // full pathname
+ struct codec_s *codec; // codec to use with this file
+ int tracknum; // used for stuff like sid, nsf, cue (will be ignored by most codecs)
+ float timestart; // start time of cue track, or -1
+ float timeend; // end time of cue track, or -1
+ float duration; // in seconds
+ int startoffset; // offset to seek to skip tags and info-headers
+ int endoffset; // offset from end of file where music data ends
+ int shufflerating; // sort order for shuffle mode
+ const char *filetype; // e.g. MP3 or OGG
+} DB_playItem_t;
+
+// plugin types
+enum {
+ DB_PLUGIN_DECODER = 1,
+ DB_PLUGIN_OUTPUT = 2,
+ DB_PLUGIN_DSP = 3,
+ DB_PLUGIN_MISC = 4
+};
+
+// event callback type
+typedef int (*db_callback_t)(int ev, uintptr_t data);
+#define DB_CALLBACK(x) ((db_callback_t)(x))
+
+// events
+enum {
+ DB_EV_FRAMEUPDATE = 0, // ticks around 20 times per second, but ticker may stop sometimes
+ DB_EV_SONGCHANGED = 1, // triggers when song was just changed
+ DB_EV_MAX
+};
+
+// typecasting macros
+#define DB_PLUGIN(x) ((DB_plugin_t *)(x))
+
+// forward decl for plugin struct
+struct DB_plugin_s;
+
+// player api definition
+typedef struct {
+ // event subscribing
+ void (*ev_subscribe) (struct DB_plugin_s *plugin, int ev, db_callback_t callback, uintptr_t data);
+ void (*ev_unsubscribe) (struct DB_plugin_s *plugin, int ev, db_callback_t callback, uintptr_t data);
+ // md5sum calc
+ void (*md5) (uint8_t sig[16], const char *in, int len);
+ void (*md5_to_str) (char *str, const uint8_t sig[16]);
+
+ // event sending and handling
+ int (*messagepump_push)(uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2);
+ int (*messagepump_pop)(uint32_t *id, uintptr_t *ctx, uint32_t *p1, uint32_t *p2);
+ // playlist access
+ DB_playItem_t *(*pl_item_alloc) (void);
+ void (*pl_item_free)(DB_playItem_t *it);
+ int (*pl_add_dir) (const char *dirname, int (*cb)(DB_playItem_t *it, void *data), void *user_data);
+ int (*pl_add_file) (const char *fname, int (*cb)(DB_playItem_t *it, void *data), void *user_data);
+ DB_playItem_t *(*pl_insert_dir) (DB_playItem_t *after, const char *dirname, int *pabort, int (*cb)(DB_playItem_t *it, void *data), void *user_data);
+ DB_playItem_t *(*pl_insert_file) (DB_playItem_t *after, const char *fname, int *pabort, int (*cb)(DB_playItem_t *it, void *data), void *user_data);
+} DB_functions_t;
+
+// base plugin interface
+typedef struct DB_plugin_s {
+ // type must be one of DB_PLUGIN_ types
+ int32_t type;
+ // may be deactivated on failures after load
+ int inactive;
+ // any of those can be left NULL
+ // though it's much better to fill them with something useful
+ const char *name;
+ const char *descr;
+ const char *author;
+ const char *email;
+ const char *website;
+ // start is called to start plugin; can be NULL
+ int (*start) (void);
+ // stop is called to deinit plugin; can be NULL
+ int (*stop) (void);
+ // exec_cmdline may be called at any moment when user sends commandline to player
+ // can be NULL if plugin doesn't support commandline processing
+ // cmdline is 0-separated list of strings, guaranteed to have 0 at the end
+ // cmdline_size is number of bytes pointed by cmdline
+ int (*exec_cmdline) (const char *cmdline, int cmdline_size);
+} DB_plugin_t;
+
+// decoder plugin
+typedef struct {
+ DB_plugin_t plugin;
+ // init is called to prepare song to be started
+ int (*init) (DB_playItem_t *it);
+
+ // free is called after decoding is finished
+ void (*free) (void);
+
+ // read is called by streamer to decode specified number of bytes
+ // must return number of bytes that were successfully decoded (sample aligned)
+ // output format is always single precision float
+ int (*read) (char *buffer, int size);
+
+ // perform seeking in samples (if possible)
+ // return -1 if failed, or 0 on success
+ // if -1 is returned, that will mean that streamer must skip that song
+ int (*seek) (int64_t samples);
+
+ // 'insert' is called to insert new item to playlist
+ // decoder is responsible to calculate duration, split it into subsongs, load cuesheet, etc
+ // after==NULL means "prepend before 1st item in playlist"
+ struct playItem_s * (*insert) (DB_playItem_t *after, const char *fname);
+
+ // NULL terminated array of all supported extensions
+ const char **exts;
+
+ // codec id used for playlist serialization
+ const char *id;
+} DB_decoder_t;
+
+// output plugin
+typedef struct {
+ DB_plugin_t plugin;
+ // init is called once at plugin activation
+ int (*init) (void (*callback)(char *stream, int len));
+ // free is called if output plugin was changed to another, or unload is about to happen
+ int (*free) (void);
+ // play, stop, pause, unpause are called by deadbeef in response to user
+ // events, or as part of streaming process
+ // state must be 0 for stopped, 1 for playing and 2 for paused
+ int (*play) (void);
+ int (*stop) (void);
+ int (*pause) (void);
+ int (*unpause) (void);
+ int (*state) (void);
+ // following functions must return output sampling rate, bits per sample and number
+ // of channels
+ int (*samplerate) (void);
+ int (*bitspersample) (void);
+ int (*channels) (void);
+ // must return 0 for little endian output, or 1 for big endian
+ int (*endianess) (void);
+} DB_output_t;
+
+// dsp plugin
+typedef struct {
+ DB_plugin_t plugin;
+ // process gets called before SRC
+ // stereo samples are stored in interleaved format
+ // stereo sample is counted as 1 sample
+ void (*process) (float *samples, int channels, int nsamples);
+} DB_dsp_t;
+
+// misc plugin
+// purpose is to provide extra services
+// e.g. scrobbling, converting, tagging, custom gui, etc.
+// misc plugins should be mostly event driven, so no special entry points in them
+typedef struct {
+ DB_plugin_t plugin;
+} DB_misc_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __DEADBEEF_H
diff --git a/main.c b/main.c
index 973408ae..a6060979 100644
--- a/main.c
+++ b/main.c
@@ -48,6 +48,7 @@
#include "conf.h"
#include "volume.h"
#include "session.h"
+#include "plugins.h"
#ifndef PREFIX
#error PREFIX must be defined
@@ -310,6 +311,7 @@ player_thread (uintptr_t ctx) {
messagepump_push (M_TERMINATE, 0, 0, 0);
}
}
+ plug_trigger_event (DB_EV_FRAMEUPDATE);
uint32_t msg;
uintptr_t ctx;
uint32_t p1;
@@ -345,6 +347,7 @@ player_thread (uintptr_t ctx) {
// update playlist view
gtkpl_songchanged (&main_playlist, p1, p2);
GDK_THREADS_LEAVE();
+ plug_trigger_event (DB_EV_SONGCHANGED);
break;
case M_PLAYSONG:
gtkpl_playsong (&main_playlist);
@@ -576,6 +579,7 @@ main (int argc, char *argv[]) {
conf_load ();
pl_load (defpl);
session_load (sessfile);
+ plug_load_all ();
messagepump_init ();
codec_init_locking ();
streamer_init ();
diff --git a/palsa.c b/palsa.c
index 7dc001e6..967b8af8 100644
--- a/palsa.c
+++ b/palsa.c
@@ -324,6 +324,8 @@ palsa_thread (uintptr_t context) {
snd_strerror (err));
}
#endif
+ snd_pcm_prepare (audio);
+ snd_pcm_start (audio);
mutex_unlock (mutex);
continue;
}
diff --git a/plugins.c b/plugins.c
new file mode 100644
index 00000000..43fb875f
--- /dev/null
+++ b/plugins.c
@@ -0,0 +1,166 @@
+#include <dirent.h>
+#include <dlfcn.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include "plugins.h"
+#include "md5/md5.h"
+
+// deadbeef api
+DB_functions_t deadbeef_api = {
+ .ev_subscribe = plug_ev_subscribe,
+ .ev_unsubscribe = plug_ev_unsubscribe,
+ .md5 = plug_md5,
+ .md5_to_str = plug_md5_to_str
+};
+
+void
+plug_md5 (uint8_t sig[16], const char *in, int len) {
+ md5_buffer (in, len, sig);
+}
+
+void
+plug_md5_to_str (char *str, const uint8_t sig[16]) {
+ md5_sig_to_string ((char *)sig, str, 33);
+}
+
+// event handlers
+typedef struct {
+ DB_plugin_t *plugin;
+ db_callback_t callback;
+ uintptr_t data;
+} evhandler_t;
+#define MAX_HANDLERS 100
+static evhandler_t handlers[DB_EV_MAX][MAX_HANDLERS];
+
+// plugin control structures
+typedef struct plugin_s {
+ void *handle;
+ DB_plugin_t *plugin;
+ struct plugin_s *next;
+} plugin_t;
+plugin_t *plugins;
+
+void
+plug_ev_subscribe (DB_plugin_t *plugin, int ev, db_callback_t callback, uintptr_t data) {
+ assert (ev < DB_EV_MAX && ev >= 0);
+ for (int i = 0; i < MAX_HANDLERS; i++) {
+ if (!handlers[ev][i].plugin) {
+ handlers[ev][i].plugin = plugin;
+ handlers[ev][i].callback = callback;
+ handlers[ev][i].data = data;
+ return;
+ }
+ }
+ fprintf (stderr, "failed to subscribe plugin %s to event %d (too many event handlers)\n", plugin->name, ev);
+}
+
+void
+plug_ev_unsubscribe (DB_plugin_t *plugin, int ev, db_callback_t callback, uintptr_t data) {
+ assert (ev < DB_EV_MAX && ev >= 0);
+ for (int i = 0; i < MAX_HANDLERS; i++) {
+ if (handlers[ev][i].plugin == plugin) {
+ handlers[ev][i].plugin = NULL;
+ handlers[ev][i].callback = NULL;
+ handlers[ev][i].data = 0;
+ return;
+ }
+ }
+}
+
+void
+plug_trigger_event (int ev) {
+ for (int i = 0; i < MAX_HANDLERS; i++) {
+ if (handlers[ev][i].plugin && !handlers[ev][i].plugin->inactive) {
+ handlers[ev][i].callback (ev, handlers[ev][i].data);
+ }
+ }
+}
+
+void
+plug_load_all (void) {
+ char dirname[1024];
+ snprintf (dirname, 1024, "%s/lib/deadbeef", PREFIX);
+ struct dirent **namelist = NULL;
+ int n = scandir (dirname, &namelist, NULL, alphasort);
+ if (n < 0)
+ {
+ if (namelist) {
+ free (namelist);
+ }
+ return; // not a dir or no read access
+ }
+ else
+ {
+ int i;
+ for (i = 0; i < n; i++)
+ {
+ // no hidden files
+ if (namelist[i]->d_name[0] != '.')
+ {
+ int l = strlen (namelist[i]->d_name);
+ if (l < 3) {
+ continue;
+ }
+ if (strcasecmp (&namelist[i]->d_name[l-3], ".so")) {
+ continue;
+ }
+ char fullname[1024];
+ strcpy (fullname, dirname);
+ strncat (fullname, "/", 1024);
+ strncat (fullname, namelist[i]->d_name, 1024);
+ printf ("loading plugin %s\n", namelist[i]->d_name);
+ void *handle = dlopen (fullname, RTLD_NOW);
+ if (!handle) {
+ fprintf (stderr, "dlopen error: %s\n", dlerror ());
+ continue;
+ }
+ namelist[i]->d_name[l-3] = 0;
+ printf ("module name is %s\n", namelist[i]->d_name);
+ strcat (namelist[i]->d_name, "_load");
+ DB_plugin_t *(*plug_load)(DB_functions_t *api) = dlsym (handle, namelist[i]->d_name);
+ if (!plug_load) {
+ fprintf (stderr, "dlsym error: %s\n", dlerror ());
+ dlclose (handle);
+ continue;
+ }
+ DB_plugin_t *plugin_api = plug_load (&deadbeef_api);
+ if (!plugin_api) {
+ namelist[i]->d_name[l-3] = 0;
+ fprintf (stderr, "plugin %s is incompatible with current version of deadbeef, please upgrade the plugin\n");
+ dlclose (handle);
+ continue;
+ }
+ plugin_t *plug = malloc (sizeof (plugin_t));
+ memset (plug, 0, sizeof (plugin_t));
+ plug->plugin = plugin_api;
+ plug->handle = handle;
+ plug->next = plugins;
+ if (plug->plugin->start) {
+ if (plug->plugin->start () < 0) {
+ plug->plugin->inactive = 1;
+ }
+ }
+ plugins = plug;
+ }
+ free (namelist[i]);
+ }
+ free (namelist);
+ }
+}
+
+void
+plug_unload_all (void) {
+ while (plugins) {
+ plugin_t *next = plugins->next;
+ if (plugins->plugin->stop) {
+ plugins->plugin->stop ();
+ }
+ dlclose (plugins->handle);
+ plugins = next;
+ }
+}
diff --git a/plugins.h b/plugins.h
new file mode 100644
index 00000000..4cc1a409
--- /dev/null
+++ b/plugins.h
@@ -0,0 +1,26 @@
+#ifndef __PLUGINS_H
+#define __PLUGINS_H
+#include "deadbeef.h"
+
+void
+plug_ev_subscribe (DB_plugin_t *plugin, int ev, db_callback_t callback, uintptr_t data);
+
+void
+plug_ev_unsubscribe (DB_plugin_t *plugin, int ev, db_callback_t callback, uintptr_t data);
+
+void
+plug_trigger_event (int ev);
+
+void
+plug_load_all (void);
+
+void
+plug_unload_all (void);
+
+void
+plug_md5 (uint8_t sig[16], const char *in, int len);
+
+void
+plug_md5_to_str (char *str, const uint8_t sig[16]);
+
+#endif // __PLUGINS_H
diff --git a/plugins/lastfm/Makefile.am b/plugins/lastfm/Makefile.am
new file mode 100644
index 00000000..7e26b863
--- /dev/null
+++ b/plugins/lastfm/Makefile.am
@@ -0,0 +1,7 @@
+lastfmdir = $(libdir)/$(PACKAGE)
+pkglib_LTLIBRARIES = lastfm.la
+lastfm_la_SOURCES = lastfm.c
+lastfm_la_LDFLAGS = -module
+
+lastfm_la_LIBADD = $(LDADD) $(LASTFM_DEPS_LIBS)
+AM_CFLAGS = $(LASTFM_DEPS_CFLAGS)
diff --git a/plugins/lastfm/lastfm.c b/plugins/lastfm/lastfm.c
new file mode 100644
index 00000000..4b6bc21e
--- /dev/null
+++ b/plugins/lastfm/lastfm.c
@@ -0,0 +1,191 @@
+/*
+ DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+ Copyright (C) 2009 Alexey Yakovenko
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <curl/curl.h>
+#include "../../deadbeef.h"
+
+static DB_misc_t plugin;
+static DB_functions_t *deadbeef;
+static char lastfm_login[50];
+static char lastfm_pass[50];
+
+#define SCROBBLER_URL "http://ws.audioscrobbler.com/2.0"
+#define LASTFM_API_KEY "6b33c8ae4d598a9aff8fe63e334e6e86"
+#define LASTFM_API_SECRET "a9f5e17e358377d96e96477d870b2b18"
+
+DB_plugin_t *
+lastfm_load (DB_functions_t *api) {
+ deadbeef = api;
+ return DB_PLUGIN (&plugin);
+}
+
+static char *lastfm_srv_res;
+static char lastfm_srv_size;
+static char lastfm_curl_err[CURL_ERROR_SIZE];
+
+static size_t
+lastfm_curl_res (void *ptr, size_t size, size_t nmemb, void *stream)
+{
+ int len = size * nmemb;
+ lastfm_srv_res = realloc (lastfm_srv_res, lastfm_srv_size + len + 1);
+ memcpy (lastfm_srv_res + lastfm_srv_size, ptr, len);
+ lastfm_srv_size += len;
+
+ char s[size*nmemb+1];
+ memcpy (s, ptr, size*nmemb);
+ s[size*nmemb] = 0;
+ printf ("%s\n", s);
+
+ return len;
+}
+
+int
+lastfm_auth (void) {
+ // auth
+ char msg[4096];
+ char sigstr[4096];
+ uint8_t sig[16];
+ snprintf (sigstr, sizeof (sigstr), "api_key%smethodauth.getToken%s", SCROBBLER_URL, LASTFM_API_KEY, LASTFM_API_SECRET);
+ deadbeef->md5 (sig, sigstr, strlen (sigstr));
+ deadbeef->md5_to_str (sigstr, sig);
+ snprintf (msg, sizeof (msg), "%s/?method=auth.getToken&api_key=%s&api_sig=%s", SCROBBLER_URL, LASTFM_API_KEY, sigstr);
+ printf ("sending request: %s\n", msg);
+ // init curl
+ CURL *curl;
+ curl = curl_easy_init ();
+ if (!curl) {
+ fprintf (stderr, "lastfm: failed to init curl\n");
+ return 0;
+ }
+ curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
+ curl_easy_setopt(curl, CURLOPT_URL, msg);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, lastfm_curl_res);
+ memset(lastfm_curl_err, 0, sizeof(lastfm_curl_err));
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, lastfm_curl_err);
+ curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ int status = curl_easy_perform(curl);
+ curl_easy_cleanup (curl);
+
+ if (!status) {
+ // parse output
+ if (strstr (lastfm_srv_res, "<lfm status=\"ok\">")) {
+ char *token = strstr (lastfm_srv_res, "<token>");
+ if (token) {
+ token += 7;
+ char *end = strstr (token, "</token>");
+ if (end) {
+ *end = 0;
+ printf ("token: %s\n", token);
+ }
+ else {
+ printf ("no </token>\n");
+ }
+ }
+ else {
+ printf ("no <token>\n");
+ }
+ }
+ else {
+ printf ("not ok\n");
+ }
+ }
+
+ if (lastfm_srv_res) {
+ free (lastfm_srv_res);
+ lastfm_srv_res = NULL;
+ }
+}
+
+static int
+lastfm_frameupdate (int ev, uintptr_t data) {
+// printf ("lastfm tick\n");
+ return 0;
+}
+
+static int
+lastfm_songchanged (int ev, uintptr_t data) {
+ printf ("song changed\n");
+ return 0;
+}
+
+static int
+lastfm_start (void) {
+ // subscribe to frameupdate event
+ deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_FRAMEUPDATE, lastfm_frameupdate, 0);
+ deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_SONGCHANGED, lastfm_songchanged, 0);
+ // load login/pass
+ char config[1024];
+ snprintf (config, 1024, "%s/.config/deadbeef/lastfm", getenv ("HOME"));
+ FILE *fp = fopen (config, "rt");
+ if (!fp) {
+ fprintf (stderr, "lastfm: failed open %s\n", config);
+ return -1;
+ }
+ if (!fgets (lastfm_login, 50, fp)) {
+ fprintf (stderr, "lastfm: failed to read login from %s\n", config);
+ fclose (fp);
+ return -1;
+ }
+ if (!fgets (lastfm_pass, 50, fp)) {
+ fprintf (stderr, "lastfm: failed to read pass from %s\n", config);
+ fclose (fp);
+ return -1;
+ }
+ fclose (fp);
+ // remove trailing garbage
+ int l;
+ char *p;
+ l = strlen (lastfm_login);
+ p = lastfm_login+l-1;
+ while (p >= lastfm_login && *p < 0x20) {
+ p--;
+ }
+ *p = 0;
+ l = strlen (lastfm_pass);
+ p = lastfm_pass+l-1;
+ while (p >= lastfm_login && *p < 0x20) {
+ p--;
+ }
+ *p = 0;
+ lastfm_auth ();
+
+ return 0;
+}
+
+static int
+lastfm_stop (void) {
+ deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_FRAMEUPDATE, lastfm_frameupdate, 0);
+ deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_SONGCHANGED, lastfm_songchanged, 0);
+ return 0;
+}
+
+// define plugin interface
+static DB_misc_t plugin = {
+ .plugin.type = DB_PLUGIN_MISC,
+ .plugin.name = "last.fm scrobbler",
+ .plugin.descr = "sends played songs information to your last.fm account",
+ .plugin.author = "Alexey Yakovenko",
+ .plugin.email = "waker@users.sourceforge.net",
+ .plugin.website = "http://deadbeef.sf.net",
+ .plugin.start = lastfm_start,
+ .plugin.stop = lastfm_stop
+};