Skip to content
This repository
Browse code

add rhythmbox music info subscriber.

  • Loading branch information...
commit 22d1c3c280993116f2363219514d5c472ef3a6b9 1 parent f010259
mattn authored
2  Makefile.am
... ... @@ -1,5 +1,5 @@
1 1 ACLOCAL_AMFLAGS = -I m4
2   -SUBDIRS = data display/balloon display/default display/nico2 subscribe/tweets
  2 +SUBDIRS = data display/balloon display/default display/nico2 subscribe/tweets subscribe/rhythmbox
3 3
4 4 bin_PROGRAMS = gol
5 5 gol_SOURCES = gol.c gol.h
2  configure.ac
@@ -19,6 +19,7 @@ PKG_CHECK_MODULES(LIBCURL, libcurl)
19 19 PKG_CHECK_MODULES(LIBXML2, libxml-2.0)
20 20 PKG_CHECK_MODULES(OPENSSL, openssl)
21 21 PKG_CHECK_MODULES(SQLITE3, sqlite3)
  22 +PKG_CHECK_MODULES(DBUSGLIB1, dbus-glib-1)
22 23
23 24 # Checks for header files.
24 25 AC_CHECK_HEADERS([arpa/inet.h memory.h netdb.h netinet/in.h stdlib.h string.h sys/socket.h unistd.h])
@@ -38,5 +39,6 @@ AC_CONFIG_FILES([Makefile
38 39 display/default/Makefile
39 40 display/nico2/Makefile
40 41 subscribe/tweets/Makefile
  42 + subscribe/rhythmbox/Makefile
41 43 data/gol.desktop])
42 44 AC_OUTPUT
8 subscribe/rhythmbox/Makefile.am
... ... @@ -0,0 +1,8 @@
  1 +subscribedir = $(pkglibdir)/subscribe
  2 +subscribe_LTLIBRARIES = librhythmbox.la
  3 +librhythmbox_la_SOURCES = rhythmbox.c
  4 +librhythmbox_la_LDFLAGS = -rpath $(subscribedir) -module
  5 +librhythmbox_la_CFLAGS = $(GLIB2_CFLAGS) $(LIBCURL_CFLAGS) $(LIBXML2_CFLAGS) $(DBUSGLIB1_CFLAGS)
  6 +librhythmbox_la_LIBADD = $(GLIB2_LIBS) $(LIBCURL_LIBS) $(LIBXML2_LIBS) $(DBUSGLIB1_LIBS)
  7 +
  8 +EXTRA_DIST =
384 subscribe/rhythmbox/rhythmbox.c
... ... @@ -0,0 +1,384 @@
  1 +/* Copyright 2011 by Yasuhiro Matsumoto
  2 + * modification, are permitted provided that the following conditions are met:
  3 + *
  4 + * 1. Redistributions of source code must retain the above copyright notice,
  5 + * this list of conditions and the following disclaimer.
  6 + * 2. Redistributions in binary form must reproduce the above copyright notice,
  7 + * this list of conditions and the following disclaimer in the documentation
  8 + * and/or other materials provided with the distribution.
  9 + *
  10 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  11 + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  12 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  13 + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  14 + * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  15 + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  16 + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  17 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  18 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  19 + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  20 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  21 + * OF THE POSSIBILITY OF SUCH DAMAGE.
  22 + */
  23 +#include <gmodule.h>
  24 +#include <dbus/dbus.h>
  25 +#include <dbus/dbus-glib.h>
  26 +#include <libxml/parser.h>
  27 +#include <libxml/xpath.h>
  28 +#include <libxml/xpathInternals.h>
  29 +#include <ctype.h>
  30 +#include <stdlib.h>
  31 +#include <memory.h>
  32 +#include <curl/curl.h>
  33 +#include "../../gol.h"
  34 +
  35 +#define REQUEST_TIMEOUT (5)
  36 +
  37 +SUBSCRIPTOR_CONTEXT* sc = NULL;
  38 +DBusGConnection *conn = NULL;
  39 +DBusGProxy* proxy = NULL;
  40 +
  41 +gchar* last_title = NULL;
  42 +gchar* last_artist = NULL;
  43 +gchar* last_album = NULL;
  44 +
  45 +#define XML_CONTENT(x) (x->children ? (char*) x->children->content : NULL)
  46 +
  47 +typedef struct {
  48 + char* data; // response data from server
  49 + size_t size; // response size of data
  50 +} MEMFILE;
  51 +
  52 +static MEMFILE*
  53 +memfopen() {
  54 + MEMFILE* mf = (MEMFILE*) malloc(sizeof(MEMFILE));
  55 + if (mf) {
  56 + mf->data = NULL;
  57 + mf->size = 0;
  58 + }
  59 + return mf;
  60 +}
  61 +
  62 +static void
  63 +memfclose(MEMFILE* mf) {
  64 + if (mf->data) free(mf->data);
  65 + free(mf);
  66 +}
  67 +
  68 +static size_t
  69 +memfwrite(char* ptr, size_t size, size_t nmemb, void* stream) {
  70 + MEMFILE* mf = (MEMFILE*) stream;
  71 + int block = size * nmemb;
  72 + if (!mf) return block; // through
  73 + if (!mf->data)
  74 + mf->data = (char*) malloc(block);
  75 + else
  76 + mf->data = (char*) realloc(mf->data, mf->size + block);
  77 + if (mf->data) {
  78 + memcpy(mf->data + mf->size, ptr, block);
  79 + mf->size += block;
  80 + }
  81 + return block;
  82 +}
  83 +
  84 +static char*
  85 +memfstrdup(MEMFILE* mf) {
  86 + char* buf;
  87 + if (mf->size == 0) return NULL;
  88 + buf = (char*) malloc(mf->size + 1);
  89 + memcpy(buf, mf->data, mf->size);
  90 + buf[mf->size] = 0;
  91 + return buf;
  92 +}
  93 +
  94 +static gboolean
  95 +delay_show(gpointer data) {
  96 + sc->show((NOTIFICATION_INFO*) data);
  97 + return FALSE;
  98 +}
  99 +
  100 +static char*
  101 + urlencode_alloc(const char* url) {
  102 + unsigned long int i, len = strlen(url);
  103 + char* temp = (char*) calloc(len * 3 + 1, sizeof(char));
  104 + char* ret = temp;
  105 + for (i = 0; i < len; i++) {
  106 + unsigned char c = (unsigned char) url[i];
  107 + if (strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.~-", c))
  108 + *temp++ = c;
  109 + else {
  110 + char buf[3] = {0};
  111 + snprintf(buf, sizeof(buf), "%02x", c);
  112 + *temp++ = '%';
  113 + *temp++ = toupper(buf[0]);
  114 + *temp++ = toupper(buf[1]);
  115 + }
  116 + }
  117 + *temp = 0;
  118 + return ret;
  119 +}
  120 +
  121 +static gchar*
  122 +get_album_art(const char* artist, const char* album) {
  123 + CURL* curl = NULL;
  124 + CURLcode res = CURLE_OK;
  125 + long http_status = 0;
  126 +
  127 + MEMFILE* mbody = NULL;
  128 + char* body = NULL;
  129 +
  130 + xmlDocPtr doc = NULL;
  131 + xmlNodeSetPtr nodes = NULL;
  132 + xmlXPathContextPtr ctx = NULL;
  133 + xmlXPathObjectPtr path = NULL;
  134 +
  135 + char* info = g_strdup_printf("%s %s", artist, album);
  136 + char* qinfo = urlencode_alloc(info);
  137 + g_free(info);
  138 + gchar* url = g_strdup_printf(
  139 + "http://api.search.yahoo.com/ImageSearchService/V1/imageSearch?"
  140 + "appid=%s&query=%s&type=all&results=10&start=1&format=any&adult_ok=True",
  141 + "YahooExample",
  142 + qinfo);
  143 +
  144 + mbody = memfopen();
  145 + curl = curl_easy_init();
  146 + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
  147 + curl_easy_setopt(curl, CURLOPT_URL, url);
  148 + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, REQUEST_TIMEOUT);
  149 + curl_easy_setopt(curl, CURLOPT_TIMEOUT, REQUEST_TIMEOUT);
  150 + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memfwrite);
  151 + curl_easy_setopt(curl, CURLOPT_WRITEDATA, mbody);
  152 + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
  153 + res = curl_easy_perform(curl);
  154 + if (res == CURLE_OK)
  155 + curl_easy_getinfo(curl, CURLINFO_HTTP_CODE, &http_status);
  156 + curl_easy_cleanup(curl);
  157 +
  158 + body = memfstrdup(mbody);
  159 + memfclose(mbody);
  160 +
  161 + if (res != CURLE_OK) {
  162 + goto leave;
  163 + }
  164 + if (http_status == 304) {
  165 + goto leave;
  166 + }
  167 + if (http_status != 200) {
  168 + goto leave;
  169 + }
  170 +
  171 + doc = body ? xmlParseDoc((xmlChar*) body) : NULL;
  172 + xmlNodePtr node = doc->children;
  173 + gchar* image_url = NULL;
  174 + if (strcmp(node->name, "ResultSet")) goto leave;
  175 + for (node = node->children; node; node = node->next) {
  176 + if (strcmp(node->name, "Result")) continue;
  177 + for (node = node->children; node; node = node->next) {
  178 + if (strcmp(node->name, "Thumbnail")) continue;
  179 + for (node = node->children; node; node = node->next) {
  180 + if (strcmp(node->name, "Url")) continue;
  181 + image_url = g_strdup(XML_CONTENT(node));
  182 + break;
  183 + }
  184 + if (image_url) break;
  185 + }
  186 + if (image_url) break;
  187 + }
  188 +
  189 +leave:
  190 + if (body) free(body);
  191 + if (path) xmlXPathFreeObject(path);
  192 + if (ctx) xmlXPathFreeContext(ctx);
  193 + if (doc) xmlFreeDoc(doc);
  194 +
  195 + return image_url;
  196 +}
  197 +
  198 +static gboolean
  199 +get_rhythmbox_info(gpointer data) {
  200 + DBusGProxy *player = NULL;
  201 + DBusGProxy *shell = NULL;
  202 + GError *error = NULL;
  203 +
  204 + if (!conn) {
  205 + conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
  206 + if (error) g_error_free(error);
  207 + if (!conn) return FALSE;
  208 + }
  209 +
  210 + if (!proxy) {
  211 + proxy = dbus_g_proxy_new_for_name(
  212 + conn,
  213 + "org.freedesktop.DBus",
  214 + "/org/freedesktop/DBus",
  215 + "org.freedesktop.DBus");
  216 + if (error) g_error_free(error);
  217 + if (!proxy) return FALSE;
  218 + }
  219 +
  220 + gboolean exists = FALSE;
  221 + if (dbus_g_proxy_call_with_timeout(
  222 + proxy,
  223 + "NameHasOwner",
  224 + 5000,
  225 + &error,
  226 + G_TYPE_STRING,
  227 + "org.gnome.Rhythmbox",
  228 + G_TYPE_INVALID,
  229 + G_TYPE_BOOLEAN,
  230 + &exists,
  231 + G_TYPE_INVALID)) {
  232 + if (error) g_error_free(error);
  233 + if (!exists) return TRUE;
  234 + }
  235 +
  236 + if (!shell) {
  237 + shell = dbus_g_proxy_new_for_name(
  238 + conn,
  239 + "org.gnome.Rhythmbox",
  240 + "/org/gnome/Rhythmbox/Shell",
  241 + "org.gnome.Rhythmbox.Shell");
  242 + }
  243 +
  244 + if (!player) {
  245 + player = dbus_g_proxy_new_for_name(
  246 + conn,
  247 + "org.gnome.Rhythmbox",
  248 + "/org/gnome/Rhythmbox/Player",
  249 + "org.gnome.Rhythmbox.Player");
  250 + }
  251 +
  252 + gboolean playing;
  253 + if (!dbus_g_proxy_call_with_timeout(
  254 + player,
  255 + "getPlaying",
  256 + 5000,
  257 + &error,
  258 + G_TYPE_INVALID,
  259 + G_TYPE_BOOLEAN,
  260 + &playing,
  261 + G_TYPE_INVALID)) {
  262 + if (error) g_error_free(error);
  263 + if (!playing) return TRUE;
  264 + }
  265 +
  266 + char *uri;
  267 + if (!dbus_g_proxy_call_with_timeout(
  268 + player
  269 + , "getPlayingUri",
  270 + 5000,
  271 + &error,
  272 + G_TYPE_INVALID,
  273 + G_TYPE_STRING,
  274 + &uri,
  275 + G_TYPE_INVALID)) {
  276 + if (error) g_error_free(error);
  277 + return TRUE;
  278 + }
  279 +
  280 + GHashTable *table;
  281 + if (!dbus_g_proxy_call_with_timeout(
  282 + shell,
  283 + "getSongProperties",
  284 + 5000,
  285 + &error,
  286 + G_TYPE_STRING,
  287 + uri,
  288 + G_TYPE_INVALID,
  289 + dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE),
  290 + &table,
  291 + G_TYPE_INVALID)) {
  292 + if (error) g_error_free(error);
  293 + return TRUE;
  294 + }
  295 + g_free(uri);
  296 +
  297 + GValue* value;
  298 + gchar* title;
  299 + gchar* artist;
  300 + gchar* album;
  301 +
  302 + value = (GValue*) g_hash_table_lookup(table, "rb:stream-song-title");
  303 + if (value != NULL && G_VALUE_HOLDS_STRING(value)) {
  304 + title = g_strdup(g_value_get_string(value));
  305 + } else {
  306 + value = (GValue*) g_hash_table_lookup(table, "title");
  307 + if (value != NULL && G_VALUE_HOLDS_STRING(value)) {
  308 + title = g_strdup(g_value_get_string(value));
  309 + }
  310 + }
  311 + value = (GValue*) g_hash_table_lookup(table, "artist");
  312 + if (value != NULL && G_VALUE_HOLDS_STRING(value)) {
  313 + artist = g_strdup(g_value_get_string(value));
  314 + }
  315 + value = (GValue*) g_hash_table_lookup(table, "album");
  316 + if (value != NULL && G_VALUE_HOLDS_STRING(value)) {
  317 + album = g_strdup(g_value_get_string(value));
  318 + }
  319 +
  320 + g_hash_table_destroy(table);
  321 +
  322 + if (title && artist && album &&
  323 + (!last_title || strcmp(last_title, title)) &&
  324 + (!last_artist || strcmp(last_artist, artist)) &&
  325 + (!last_album || strcmp(last_album, album))) {
  326 + NOTIFICATION_INFO* ni = g_new0(NOTIFICATION_INFO, 1);
  327 + ni->title = g_strdup(title);
  328 + ni->text = g_strdup_printf("%s\n%s", album, artist);
  329 + ni->icon = get_album_art("Ozzy", "Ozmosis");
  330 + g_timeout_add(10, delay_show, ni);
  331 +
  332 + if (last_title) g_free(last_title);
  333 + if (last_artist) g_free(last_artist);
  334 + if (last_album) g_free(last_album);
  335 + last_title = title;
  336 + last_artist = artist;
  337 + last_album = album;
  338 + } else {
  339 + if (title) g_free(title);
  340 + if (artist) g_free(artist);
  341 + if (album) g_free(album);
  342 + }
  343 +
  344 + return TRUE;
  345 +}
  346 +
  347 +G_MODULE_EXPORT gboolean
  348 +subscribe_init(SUBSCRIPTOR_CONTEXT* _sc) {
  349 + sc = _sc;
  350 + return TRUE;
  351 +}
  352 +
  353 +G_MODULE_EXPORT void
  354 +subscribe_term() {
  355 +}
  356 +
  357 +G_MODULE_EXPORT gboolean
  358 +subscribe_start() {
  359 + g_timeout_add(1000, get_rhythmbox_info, NULL);
  360 + return TRUE;
  361 +}
  362 +
  363 +G_MODULE_EXPORT void
  364 +subscribe_stop() {
  365 +}
  366 +
  367 +G_MODULE_EXPORT gchar*
  368 +subscribe_name() {
  369 + return "Rhythmbox";
  370 +}
  371 +
  372 +G_MODULE_EXPORT gchar*
  373 +subscribe_description() {
  374 + return "<span size=\"large\"><b>Rhythmbox</b></span>\n"
  375 + "<span>This is rhythmbox subscriber.</span>\n"
  376 + "<span>Show now playing music in rhythmbox.</span>\n";
  377 +}
  378 +
  379 +G_MODULE_EXPORT char**
  380 +subscribe_thumbnail() {
  381 + return NULL;
  382 +}
  383 +
  384 +// vim:set et sw=2 ts=2 ai:

0 comments on commit 22d1c3c

Please sign in to comment.
Something went wrong with that request. Please try again.