From d8a6c838e60a50db2ec35a00c45786a2df9d0f43 Mon Sep 17 00:00:00 2001 From: waker Date: Tue, 25 Aug 2009 22:58:10 +0200 Subject: plugins prototyping WIP --- Makefile.am | 4 +- README | 1 + bootstrap.sh | 2 +- configure.in | 8 +- deadbeef.h | 208 +++++++++++++++++++++++++++++++++++++++++++++ main.c | 4 + palsa.c | 2 + plugins.c | 166 ++++++++++++++++++++++++++++++++++++ plugins.h | 26 ++++++ plugins/lastfm/Makefile.am | 7 ++ plugins/lastfm/lastfm.c | 191 +++++++++++++++++++++++++++++++++++++++++ 11 files changed, 616 insertions(+), 3 deletions(-) create mode 100644 deadbeef.h create mode 100644 plugins.c create mode 100644 plugins.h create mode 100644 plugins/lastfm/Makefile.am create mode 100644 plugins/lastfm/lastfm.c 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 + +#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 +#include +#include +#include +#include +#include +#ifdef HAVE_CONFIG_H +# include +#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 +#include +#include +#include +#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, "")) { + char *token = strstr (lastfm_srv_res, ""); + if (token) { + token += 7; + char *end = strstr (token, ""); + if (end) { + *end = 0; + printf ("token: %s\n", token); + } + else { + printf ("no \n"); + } + } + else { + printf ("no \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 +}; -- cgit v1.2.3