1 /*
2  * rb-mpris-plugin.c
3  *
4  *  Copyright (C) 2010  Jonathan Matthew  <jonathan@d14n.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * The Rhythmbox authors hereby grant permission for non-GPL compatible
12  * GStreamer plugins to be used and distributed together with GStreamer
13  * and Rhythmbox. This permission is above and beyond the permissions granted
14  * by the GPL license by which Rhythmbox is covered. If you modify this code
15  * you may extend this exception to your version of the code, but you are not
16  * obligated to do so. If you do not wish to do so, delete this exception
17  * statement from your version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
27  */
28 
29 #include <config.h>
30 
31 #include <string.h>
32 #include <glib/gi18n-lib.h>
33 #include <gmodule.h>
34 #include <gtk/gtk.h>
35 #include <glib.h>
36 #include <glib-object.h>
37 #include <gio/gio.h>
38 
39 #include <lib/rb-util.h>
40 #include <lib/rb-debug.h>
41 #include <plugins/rb-plugin-macros.h>
42 #include <shell/rb-shell.h>
43 #include <shell/rb-shell-player.h>
44 #include <backends/rb-player.h>
45 #include <sources/rb-playlist-source.h>
46 #include <metadata/rb-ext-db.h>
47 
48 #define RB_TYPE_MPRIS_PLUGIN		(rb_mpris_plugin_get_type ())
49 #define RB_MPRIS_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_MPRIS_PLUGIN, RBMprisPlugin))
50 #define RB_MPRIS_PLUGIN_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_MPRIS_PLUGIN, RBMprisPluginClass))
51 #define RB_IS_MPRIS_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_MPRIS_PLUGIN))
52 #define RB_IS_MPRIS_PLUGIN_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_MPRIS_PLUGIN))
53 #define RB_MPRIS_PLUGIN_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_MPRIS_PLUGIN, RBMprisPluginClass))
54 
55 #define ENTRY_OBJECT_PATH_PREFIX 	"/org/mpris/MediaPlayer2/Track/"
56 
57 #define MPRIS_PLAYLIST_ID_ITEM		"rb-mpris-playlist-id"
58 
59 #include "mpris-spec.h"
60 
61 typedef struct
62 {
63 	PeasExtensionBase parent;
64 
65 	GDBusConnection *connection;
66 	GDBusNodeInfo *node_info;
67 	guint name_own_id;
68 	guint root_id;
69 	guint player_id;
70 	guint playlists_id;
71 
72 	RBShellPlayer *player;
73 	RhythmDB *db;
74 	RBDisplayPageModel *page_model;
75 	RBExtDB *art_store;
76 
77 	int playlist_count;
78 
79 	GHashTable *player_property_changes;
80 	GHashTable *playlist_property_changes;
81 	gboolean emit_seeked;
82 	guint property_emit_id;
83 
84 	gint64 last_elapsed;
85 } RBMprisPlugin;
86 
87 typedef struct
88 {
89 	PeasExtensionBaseClass parent_class;
90 } RBMprisPluginClass;
91 
92 
93 G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
94 
95 RB_DEFINE_PLUGIN(RB_TYPE_MPRIS_PLUGIN, RBMprisPlugin, rb_mpris_plugin,)
96 
97 static void
rb_mpris_plugin_init(RBMprisPlugin * plugin)98 rb_mpris_plugin_init (RBMprisPlugin *plugin)
99 {
100 }
101 
102 /* property change stuff */
103 
104 static void
emit_property_changes(RBMprisPlugin * plugin,GHashTable * changes,const char * interface)105 emit_property_changes (RBMprisPlugin *plugin, GHashTable *changes, const char *interface)
106 {
107 	GError *error = NULL;
108 	GVariantBuilder *properties;
109 	GVariantBuilder *invalidated;
110 	GVariant *parameters;
111 	gpointer propname, propvalue;
112 	GHashTableIter iter;
113 
114 	/* build property changes */
115 	properties = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
116 	invalidated = g_variant_builder_new (G_VARIANT_TYPE ("as"));
117 	g_hash_table_iter_init (&iter, changes);
118 	while (g_hash_table_iter_next (&iter, &propname, &propvalue)) {
119 		if (propvalue != NULL) {
120 			g_variant_builder_add (properties,
121 					       "{sv}",
122 					       propname,
123 					       propvalue);
124 		} else {
125 			g_variant_builder_add (invalidated, "s", propname);
126 		}
127 
128 	}
129 
130 	parameters = g_variant_new ("(sa{sv}as)",
131 				    interface,
132 				    properties,
133 				    invalidated);
134 	g_variant_builder_unref (properties);
135 	g_variant_builder_unref (invalidated);
136 	g_dbus_connection_emit_signal (plugin->connection,
137 				       NULL,
138 				       MPRIS_OBJECT_NAME,
139 				       "org.freedesktop.DBus.Properties",
140 				       "PropertiesChanged",
141 				       parameters,
142 				       &error);
143 	if (error != NULL) {
144 		g_warning ("Unable to send MPRIS property changes for %s: %s",
145 			   interface, error->message);
146 		g_clear_error (&error);
147 	}
148 
149 }
150 
151 static gboolean
emit_properties_idle(RBMprisPlugin * plugin)152 emit_properties_idle (RBMprisPlugin *plugin)
153 {
154 	if (plugin->player_property_changes != NULL) {
155 		emit_property_changes (plugin, plugin->player_property_changes, MPRIS_PLAYER_INTERFACE);
156 		g_hash_table_destroy (plugin->player_property_changes);
157 		plugin->player_property_changes = NULL;
158 	}
159 
160 	if (plugin->playlist_property_changes != NULL) {
161 		emit_property_changes (plugin, plugin->playlist_property_changes, MPRIS_PLAYLISTS_INTERFACE);
162 		g_hash_table_destroy (plugin->playlist_property_changes);
163 		plugin->playlist_property_changes = NULL;
164 	}
165 
166 	if (plugin->emit_seeked) {
167 		GError *error = NULL;
168 		rb_debug ("emitting Seeked; new time %" G_GINT64_FORMAT, plugin->last_elapsed/1000);
169 		g_dbus_connection_emit_signal (plugin->connection,
170 					       NULL,
171 					       MPRIS_OBJECT_NAME,
172 					       MPRIS_PLAYER_INTERFACE,
173 					       "Seeked",
174 					       g_variant_new ("(x)", plugin->last_elapsed / 1000),
175 					       &error);
176 		if (error != NULL) {
177 			g_warning ("Unable to set MPRIS Seeked signal: %s", error->message);
178 			g_clear_error (&error);
179 		}
180 		plugin->emit_seeked = 0;
181 	}
182 	plugin->property_emit_id = 0;
183 	return FALSE;
184 }
185 
186 static void
add_player_property_change(RBMprisPlugin * plugin,const char * property,GVariant * value)187 add_player_property_change (RBMprisPlugin *plugin,
188 			    const char *property,
189 			    GVariant *value)
190 {
191 	if (plugin->player_property_changes == NULL) {
192 		plugin->player_property_changes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
193 	}
194 	g_hash_table_insert (plugin->player_property_changes, g_strdup (property), g_variant_ref_sink (value));
195 
196 	if (plugin->property_emit_id == 0) {
197 		plugin->property_emit_id = g_idle_add ((GSourceFunc)emit_properties_idle, plugin);
198 	}
199 }
200 
201 static void
add_playlist_property_change(RBMprisPlugin * plugin,const char * property,GVariant * value)202 add_playlist_property_change (RBMprisPlugin *plugin,
203 			      const char *property,
204 			      GVariant *value)
205 {
206 	if (plugin->playlist_property_changes == NULL) {
207 		plugin->playlist_property_changes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
208 	}
209 	g_hash_table_insert (plugin->playlist_property_changes, g_strdup (property), g_variant_ref_sink (value));
210 
211 	if (plugin->property_emit_id == 0) {
212 		plugin->property_emit_id = g_idle_add ((GSourceFunc)emit_properties_idle, plugin);
213 	}
214 }
215 
216 /* MPRIS root interface */
217 
218 static void
handle_root_method_call(GDBusConnection * connection,const char * sender,const char * object_path,const char * interface_name,const char * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,RBMprisPlugin * plugin)219 handle_root_method_call (GDBusConnection *connection,
220 			 const char *sender,
221 			 const char *object_path,
222 			 const char *interface_name,
223 			 const char *method_name,
224 			 GVariant *parameters,
225 			 GDBusMethodInvocation *invocation,
226 			 RBMprisPlugin *plugin)
227 {
228 	RBShell *shell;
229 
230 	if (g_strcmp0 (object_path, MPRIS_OBJECT_NAME) != 0 ||
231 	    g_strcmp0 (interface_name, MPRIS_ROOT_INTERFACE) != 0) {
232 		g_dbus_method_invocation_return_error (invocation,
233 						       G_DBUS_ERROR,
234 						       G_DBUS_ERROR_NOT_SUPPORTED,
235 						       "Method %s.%s not supported",
236 						       interface_name,
237 						       method_name);
238 		return;
239 	}
240 
241 	if (g_strcmp0 (method_name, "Raise") == 0) {
242 		g_object_get (plugin, "object", &shell, NULL);
243 		rb_shell_present (shell, GDK_CURRENT_TIME, NULL);
244 		g_object_unref (shell);
245 		g_dbus_method_invocation_return_value (invocation, NULL);
246 	} else if (g_strcmp0 (method_name, "Quit") == 0) {
247 		g_object_get (plugin, "object", &shell, NULL);
248 		rb_shell_quit (shell, NULL);
249 		g_object_unref (shell);
250 		g_dbus_method_invocation_return_value (invocation, NULL);
251 	} else {
252 		g_dbus_method_invocation_return_error (invocation,
253 						       G_DBUS_ERROR,
254 						       G_DBUS_ERROR_NOT_SUPPORTED,
255 						       "Method %s.%s not supported",
256 						       interface_name,
257 						       method_name);
258 	}
259 }
260 
261 static GVariant *
get_root_property(GDBusConnection * connection,const char * sender,const char * object_path,const char * interface_name,const char * property_name,GError ** error,RBMprisPlugin * plugin)262 get_root_property (GDBusConnection *connection,
263 		   const char *sender,
264 		   const char *object_path,
265 		   const char *interface_name,
266 		   const char *property_name,
267 		   GError **error,
268 		   RBMprisPlugin *plugin)
269 {
270 	if (g_strcmp0 (object_path, MPRIS_OBJECT_NAME) != 0 ||
271 	    g_strcmp0 (interface_name, MPRIS_ROOT_INTERFACE) != 0) {
272 		g_set_error (error,
273 			     G_DBUS_ERROR,
274 			     G_DBUS_ERROR_NOT_SUPPORTED,
275 			     "Property %s.%s not supported",
276 			     interface_name,
277 			     property_name);
278 		return NULL;
279 	}
280 
281 	if (g_strcmp0 (property_name, "CanQuit") == 0) {
282 		return g_variant_new_boolean (TRUE);
283 	} else if (g_strcmp0 (property_name, "CanRaise") == 0) {
284 		return g_variant_new_boolean (TRUE);
285 	} else if (g_strcmp0 (property_name, "HasTrackList") == 0) {
286 		return g_variant_new_boolean (FALSE);
287 	} else if (g_strcmp0 (property_name, "Identity") == 0) {
288 		return g_variant_new_string ("Rhythmbox");
289 	} else if (g_strcmp0 (property_name, "DesktopEntry") == 0) {
290 		GVariant *v = NULL;
291 		char *path;
292 
293 #ifdef USE_UNINSTALLED_DIRS
294 		path = g_build_filename (SHARE_UNINSTALLED_BUILDDIR, "rhythmbox.desktop", NULL);
295 #else
296 		path = g_build_filename (DATADIR, "applications", "rhythmbox.desktop", NULL);
297 #endif
298 		if (path != NULL) {
299 			char *basename;
300 			char *ext;
301 
302 			basename = g_filename_display_basename (path);
303 			ext = g_utf8_strrchr (basename, -1, '.');
304 			if (ext != NULL) {
305 				*ext = '\0';
306 			}
307 
308 			v = g_variant_new_string (basename);
309 			g_free (basename);
310 			g_free (path);
311 		} else {
312 			g_warning ("Unable to return desktop file path to MPRIS client: %s", (*error)->message);
313 		}
314 
315 		return v;
316 	} else if (g_strcmp0 (property_name, "SupportedUriSchemes") == 0) {
317 		/* not planning to support this seriously */
318 		const char *fake_supported_schemes[] = {
319 			"file", "http", "cdda", "smb", "sftp", NULL
320 		};
321 		return g_variant_new_strv (fake_supported_schemes, -1);
322 	} else if (g_strcmp0 (property_name, "SupportedMimeTypes") == 0) {
323 		/* nor this */
324 		const char *fake_supported_mimetypes[] = {
325 			"application/ogg", "audio/x-vorbis+ogg", "audio/x-flac", "audio/mpeg", NULL
326 		};
327 		return g_variant_new_strv (fake_supported_mimetypes, -1);
328 	}
329 
330 	g_set_error (error,
331 		     G_DBUS_ERROR,
332 		     G_DBUS_ERROR_NOT_SUPPORTED,
333 		     "Property %s.%s not supported",
334 		     interface_name,
335 		     property_name);
336 	return NULL;
337 }
338 
339 static const GDBusInterfaceVTable root_vtable =
340 {
341 	(GDBusInterfaceMethodCallFunc) handle_root_method_call,
342 	(GDBusInterfaceGetPropertyFunc) get_root_property,
343 	NULL
344 };
345 
346 /* MPRIS player interface */
347 
348 static void
handle_result(GDBusMethodInvocation * invocation,gboolean ret,GError * error)349 handle_result (GDBusMethodInvocation *invocation, gboolean ret, GError *error)
350 {
351 	if (ret) {
352 		g_dbus_method_invocation_return_value (invocation, NULL);
353 	} else {
354 		if (error != NULL) {
355 			rb_debug ("returning error: %s", error->message);
356 			g_dbus_method_invocation_return_gerror (invocation, error);
357 			g_error_free (error);
358 		} else {
359 			rb_debug ("returning unknown error");
360 			g_dbus_method_invocation_return_error_literal (invocation,
361 								       G_DBUS_ERROR,
362 								       G_DBUS_ERROR_FAILED,
363 								       "Unknown error");
364 		}
365 	}
366 }
367 
368 static GVariant *
variant_for_metadata(const char * value,gboolean as_list)369 variant_for_metadata (const char *value, gboolean as_list)
370 {
371 	if (as_list) {
372 		const char *strv[] = {
373 			value, NULL
374 		};
375 		return g_variant_new_strv (strv, -1);
376 	} else {
377 		return g_variant_new_string (value);
378 	}
379 }
380 
381 static void
add_string_property(GVariantBuilder * builder,RhythmDBEntry * entry,RhythmDBPropType prop,const char * name,gboolean as_list)382 add_string_property (GVariantBuilder *builder,
383 		     RhythmDBEntry *entry,
384 		     RhythmDBPropType prop,
385 		     const char *name,
386 		     gboolean as_list)
387 {
388 	const char *value = rhythmdb_entry_get_string (entry, prop);
389 	if (value != NULL && value[0] != '\0') {
390 		rb_debug ("adding %s = %s", name, value);
391 		g_variant_builder_add (builder, "{sv}", name, variant_for_metadata (value, as_list));
392 	}
393 }
394 
395 static void
add_string_property_2(GVariantBuilder * builder,RhythmDB * db,RhythmDBEntry * entry,RhythmDBPropType prop,const char * name,const char * extra_field_name,gboolean as_list)396 add_string_property_2 (GVariantBuilder *builder,
397 		       RhythmDB *db,
398 		       RhythmDBEntry *entry,
399 		       RhythmDBPropType prop,
400 		       const char *name,
401 		       const char *extra_field_name,
402 		       gboolean as_list)
403 {
404 	GValue *v;
405 	const char *value;
406 
407 	v = rhythmdb_entry_request_extra_metadata (db, entry, extra_field_name);
408 	if (v != NULL) {
409 		value = g_value_get_string (v);
410 	} else {
411 		value = rhythmdb_entry_get_string (entry, prop);
412 	}
413 
414 	if (value != NULL && value[0] != '\0') {
415 		rb_debug ("adding %s = %s", name, value);
416 		g_variant_builder_add (builder, "{sv}", name, variant_for_metadata (value, as_list));
417 	}
418 
419 	if (v != NULL) {
420 		g_value_unset (v);
421 		g_free (v);
422 	}
423 }
424 
425 static void
add_ulong_property(GVariantBuilder * builder,RhythmDBEntry * entry,RhythmDBPropType prop,const char * name,int scale,gboolean zero_is_valid)426 add_ulong_property (GVariantBuilder *builder,
427 		    RhythmDBEntry *entry,
428 		    RhythmDBPropType prop,
429 		    const char *name,
430 		    int scale,
431 		    gboolean zero_is_valid)
432 {
433 	gulong v;
434 	v = rhythmdb_entry_get_ulong (entry, prop);
435 	if (zero_is_valid || v != 0) {
436 		rb_debug ("adding %s = %lu", name, v);
437 		g_variant_builder_add (builder,
438 				       "{sv}",
439 				       name,
440 				       g_variant_new_int32 (v * scale));
441 	}
442 }
443 
444 static void
add_ulong_property_as_int64(GVariantBuilder * builder,RhythmDBEntry * entry,RhythmDBPropType prop,const char * name,gint64 scale)445 add_ulong_property_as_int64 (GVariantBuilder *builder,
446 			     RhythmDBEntry *entry,
447 			     RhythmDBPropType prop,
448 			     const char *name,
449 			     gint64 scale)
450 {
451 	gint64 v;
452 	v = rhythmdb_entry_get_ulong (entry, prop);
453 	rb_debug ("adding %s = %" G_GINT64_FORMAT, name, v * scale);
454 	g_variant_builder_add (builder,
455 			       "{sv}",
456 			       name,
457 			       g_variant_new_int64 (v * scale));
458 }
459 
460 static void
add_double_property(GVariantBuilder * builder,RhythmDBEntry * entry,RhythmDBPropType prop,const char * name,gdouble scale)461 add_double_property (GVariantBuilder *builder,
462 		     RhythmDBEntry *entry,
463 		     RhythmDBPropType prop,
464 		     const char *name,
465 		     gdouble scale)
466 {
467 	gdouble v;
468 	v = rhythmdb_entry_get_double (entry, prop);
469 	rb_debug ("adding %s = %f", name, v * scale);
470 	g_variant_builder_add (builder,
471 			       "{sv}",
472 			       name,
473 			       g_variant_new_double (v * scale));
474 }
475 
476 static void
add_double_property_as_int(GVariantBuilder * builder,RhythmDBEntry * entry,RhythmDBPropType prop,const char * name,gdouble scale,gboolean zero_is_valid)477 add_double_property_as_int (GVariantBuilder *builder,
478 			    RhythmDBEntry *entry,
479 			    RhythmDBPropType prop,
480 			    const char *name,
481 			    gdouble scale,
482 			    gboolean zero_is_valid)
483 {
484 	int v;
485 	v = (int)(rhythmdb_entry_get_double (entry, prop) * scale);
486 	if (zero_is_valid || v != 0) {
487 		rb_debug ("adding %s = %d", name, v);
488 		g_variant_builder_add (builder,
489 				       "{sv}",
490 				       name,
491 				       g_variant_new_int32 (v));
492 	}
493 }
494 
495 static void
add_year_date_property(GVariantBuilder * builder,RhythmDBEntry * entry,RhythmDBPropType prop,const char * name)496 add_year_date_property (GVariantBuilder *builder,
497 			RhythmDBEntry *entry,
498 			RhythmDBPropType prop,
499 			const char *name)
500 {
501 	gulong year = rhythmdb_entry_get_ulong (entry, prop);
502 
503 	if (year != 0) {
504 		char *iso8601;
505 		iso8601 = g_strdup_printf ("%4d-%02d-%02dT%02d:%02d:%02dZ",
506 					   (int)year, 1, 1, 0, 0, 0);
507 
508 		g_variant_builder_add (builder,
509 				       "{sv}",
510 				       name,
511 				       g_variant_new_string (iso8601));
512 		g_free (iso8601);
513 	}
514 }
515 
516 static void
add_time_t_date_property(GVariantBuilder * builder,RhythmDBEntry * entry,RhythmDBPropType prop,const char * name)517 add_time_t_date_property (GVariantBuilder *builder,
518 			  RhythmDBEntry *entry,
519 			  RhythmDBPropType prop,
520 			  const char *name)
521 {
522 	GTimeVal tv;
523 
524 	tv.tv_sec = rhythmdb_entry_get_ulong (entry, prop);
525 	tv.tv_usec = 0;
526 
527 	if (tv.tv_sec != 0) {
528 		char *iso8601 = g_time_val_to_iso8601 (&tv);
529 		g_variant_builder_add (builder,
530 				       "{sv}",
531 				       name,
532 				       g_variant_new_string (iso8601));
533 		g_free (iso8601);
534 	}
535 }
536 
537 static void
build_track_metadata(RBMprisPlugin * plugin,GVariantBuilder * builder,RhythmDBEntry * entry)538 build_track_metadata (RBMprisPlugin *plugin,
539 		      GVariantBuilder *builder,
540 		      RhythmDBEntry *entry)
541 {
542 	RBExtDBKey *key;
543 	GValue *md;
544 	char *trackid_str;
545 	char *art_filename = NULL;
546 
547 	trackid_str = g_strdup_printf(ENTRY_OBJECT_PATH_PREFIX "%lu",
548 				      rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID));
549 	g_variant_builder_add (builder,
550 			       "{sv}",
551 			       "mpris:trackid",
552 			       g_variant_new ("s", trackid_str));
553 	g_free (trackid_str);
554 
555 	add_string_property (builder, entry, RHYTHMDB_PROP_LOCATION, "xesam:url", FALSE);
556 	add_string_property_2 (builder, plugin->db, entry, RHYTHMDB_PROP_TITLE, "xesam:title", RHYTHMDB_PROP_STREAM_SONG_TITLE, FALSE);
557 	add_string_property_2 (builder, plugin->db, entry, RHYTHMDB_PROP_ARTIST, "xesam:artist", RHYTHMDB_PROP_STREAM_SONG_ARTIST, TRUE);
558 	add_string_property_2 (builder, plugin->db, entry, RHYTHMDB_PROP_ALBUM, "xesam:album", RHYTHMDB_PROP_STREAM_SONG_ALBUM, FALSE);
559 	add_string_property (builder, entry, RHYTHMDB_PROP_GENRE, "xesam:genre", TRUE);
560 	add_string_property (builder, entry, RHYTHMDB_PROP_COMMENT, "xesam:comment", TRUE);
561 	add_string_property (builder, entry, RHYTHMDB_PROP_ALBUM_ARTIST, "xesam:albumArtist", TRUE);
562 
563 	add_string_property (builder, entry, RHYTHMDB_PROP_MUSICBRAINZ_TRACKID, "xesam:musicBrainzTrackID", TRUE);
564 	add_string_property (builder, entry, RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID, "xesam:musicBrainzAlbumID", TRUE);
565 	add_string_property (builder, entry, RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID, "xesam:musicBrainzArtistID", TRUE);
566 	add_string_property (builder, entry, RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID, "xesam:musicBrainzAlbumArtistID", TRUE);
567 
568 	/* would be nice to have mpris: names for these. */
569 	add_string_property (builder, entry, RHYTHMDB_PROP_ARTIST_SORTNAME, "rhythmbox:artistSortname", FALSE);
570 	add_string_property (builder, entry, RHYTHMDB_PROP_ALBUM_SORTNAME, "rhythmbox:albumSortname", FALSE);
571 	add_string_property (builder, entry, RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME, "rhythmbox:albumArtistSortname", FALSE);
572 
573 	/* if we have a streaming song title, provide the stream name too */
574 	md = rhythmdb_entry_request_extra_metadata (plugin->db, entry, RHYTHMDB_PROP_STREAM_SONG_TITLE);
575 	if (md != NULL) {
576 		add_string_property (builder, entry, RHYTHMDB_PROP_TITLE, "rhythmbox:streamTitle", FALSE);
577 
578 		g_value_unset (md);
579 		g_free (md);
580 	}
581 
582 	add_ulong_property (builder, entry, RHYTHMDB_PROP_BITRATE, "xesam:audioBitrate", 1024, FALSE);	/* scale to bits per second */
583 
584 	add_year_date_property (builder, entry, RHYTHMDB_PROP_YEAR, "xesam:contentCreated");
585 	add_time_t_date_property (builder, entry, RHYTHMDB_PROP_LAST_PLAYED, "xesam:lastUsed");
586 
587 	add_ulong_property_as_int64 (builder, entry, RHYTHMDB_PROP_DURATION, "mpris:length", G_USEC_PER_SEC);
588 	add_ulong_property (builder, entry, RHYTHMDB_PROP_TRACK_NUMBER, "xesam:trackNumber", 1, TRUE);
589 	add_ulong_property (builder, entry, RHYTHMDB_PROP_DISC_NUMBER, "xesam:discNumber", 1, FALSE);
590 	add_ulong_property (builder, entry, RHYTHMDB_PROP_PLAY_COUNT, "xesam:useCount", 1, TRUE);
591 
592 	add_double_property (builder, entry, RHYTHMDB_PROP_RATING, "xesam:userRating", 0.2);	/* scale to 0..1 */
593 	add_double_property_as_int (builder, entry, RHYTHMDB_PROP_BPM, "xesam:audioBPM", 1.0, FALSE);
594 
595 	key = rhythmdb_entry_create_ext_db_key (entry, RHYTHMDB_PROP_ALBUM);
596 
597 	art_filename = rb_ext_db_lookup (plugin->art_store, key, NULL);
598 	if (art_filename != NULL) {
599 		char *uri;
600 		uri = g_filename_to_uri (art_filename, NULL, NULL);
601 		if (uri != NULL) {
602 			g_variant_builder_add (builder, "{sv}", "mpris:artUrl", g_variant_new ("s", uri));
603 			g_free (uri);
604 		}
605 		g_free (art_filename);
606 	}
607 	rb_ext_db_key_free (key);
608 
609 	/* maybe do lyrics? */
610 }
611 
612 static void
handle_player_method_call(GDBusConnection * connection,const char * sender,const char * object_path,const char * interface_name,const char * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,RBMprisPlugin * plugin)613 handle_player_method_call (GDBusConnection *connection,
614 			   const char *sender,
615 			   const char *object_path,
616 			   const char *interface_name,
617 			   const char *method_name,
618 			   GVariant *parameters,
619 			   GDBusMethodInvocation *invocation,
620 			   RBMprisPlugin *plugin)
621 
622 {
623 	GError *error = NULL;
624 	gboolean ret;
625 	if (g_strcmp0 (object_path, MPRIS_OBJECT_NAME) != 0 ||
626 	    g_strcmp0 (interface_name, MPRIS_PLAYER_INTERFACE) != 0) {
627 		g_dbus_method_invocation_return_error (invocation,
628 						       G_DBUS_ERROR,
629 						       G_DBUS_ERROR_NOT_SUPPORTED,
630 						       "Method %s.%s not supported",
631 						       interface_name,
632 						       method_name);
633 		return;
634 	}
635 
636 	if (g_strcmp0 (method_name, "Next") == 0) {
637 		ret = rb_shell_player_do_next (plugin->player, &error);
638 		handle_result (invocation, ret, error);
639 	} else if (g_strcmp0 (method_name, "Previous") == 0) {
640 		ret = rb_shell_player_do_previous (plugin->player, &error);
641 		handle_result (invocation, ret, error);
642 	} else if (g_strcmp0 (method_name, "Pause") == 0) {
643 		ret = rb_shell_player_pause (plugin->player, &error);
644 		handle_result (invocation, ret, error);
645 	} else if (g_strcmp0 (method_name, "PlayPause") == 0) {
646 		ret = rb_shell_player_playpause (plugin->player, &error);
647 		handle_result (invocation, ret, error);
648 	} else if (g_strcmp0 (method_name, "Stop") == 0) {
649 		rb_shell_player_stop (plugin->player);
650 		handle_result (invocation, TRUE, NULL);
651 	} else if (g_strcmp0 (method_name, "Play") == 0) {
652 		ret = rb_shell_player_play (plugin->player, &error);
653 		handle_result (invocation, ret, error);
654 	} else if (g_strcmp0 (method_name, "Seek") == 0) {
655 		gint64 offset;
656 		g_variant_get (parameters, "(x)", &offset);
657 		rb_shell_player_seek (plugin->player, offset / G_USEC_PER_SEC, NULL);
658 		g_dbus_method_invocation_return_value (invocation, NULL);
659 	} else if (g_strcmp0 (method_name, "SetPosition") == 0) {
660 		RhythmDBEntry *playing_entry;
661 		RhythmDBEntry *client_entry;
662 		gint64 position;
663 		const char *client_entry_path;
664 
665 		playing_entry = rb_shell_player_get_playing_entry (plugin->player);
666 		if (playing_entry == NULL) {
667 			/* not playing, so we can't seek */
668 			g_dbus_method_invocation_return_value (invocation, NULL);
669 			return;
670 		}
671 
672 		g_variant_get (parameters, "(&ox)", &client_entry_path, &position);
673 
674 		if (g_str_has_prefix (client_entry_path, ENTRY_OBJECT_PATH_PREFIX) == FALSE) {
675 			/* this can't possibly be the current playing track, so ignore it */
676 			g_dbus_method_invocation_return_value (invocation, NULL);
677 			rhythmdb_entry_unref (playing_entry);
678 			return;
679 		}
680 
681 		client_entry_path += strlen (ENTRY_OBJECT_PATH_PREFIX);
682 		client_entry = rhythmdb_entry_lookup_from_string (plugin->db, client_entry_path, TRUE);
683 		if (client_entry == NULL) {
684 			/* ignore it */
685 			g_dbus_method_invocation_return_value (invocation, NULL);
686 			rhythmdb_entry_unref (playing_entry);
687 			return;
688 		}
689 
690 		if (playing_entry != client_entry) {
691 			/* client got the wrong entry, ignore it */
692 			g_dbus_method_invocation_return_value (invocation, NULL);
693 			rhythmdb_entry_unref (playing_entry);
694 			return;
695 		}
696 		rhythmdb_entry_unref (playing_entry);
697 
698 		ret = rb_shell_player_set_playing_time (plugin->player, position / G_USEC_PER_SEC, &error);
699 		handle_result (invocation, ret, error);
700 	} else if (g_strcmp0 (method_name, "OpenUri") == 0) {
701 		const char *uri;
702 		RBShell *shell;
703 
704 		g_variant_get (parameters, "(&s)", &uri);
705 		g_object_get (plugin, "object", &shell, NULL);
706 		ret = rb_shell_load_uri (shell, uri, TRUE, &error);
707 		g_object_unref (shell);
708 		handle_result (invocation, ret, error);
709 	} else {
710 		g_dbus_method_invocation_return_error (invocation,
711 						       G_DBUS_ERROR,
712 						       G_DBUS_ERROR_NOT_SUPPORTED,
713 						       "Method %s.%s not supported",
714 						       interface_name,
715 						       method_name);
716 	}
717 }
718 
719 static GVariant *
get_playback_status(RBMprisPlugin * plugin)720 get_playback_status (RBMprisPlugin *plugin)
721 {
722 	RhythmDBEntry *entry;
723 
724 	entry = rb_shell_player_get_playing_entry (plugin->player);
725 	if (entry == NULL) {
726 		return g_variant_new_string ("Stopped");
727 	} else {
728 		GVariant *v;
729 		gboolean playing;
730 		if (rb_shell_player_get_playing (plugin->player, &playing, NULL)) {
731 			if (playing) {
732 				v = g_variant_new_string ("Playing");
733 			} else {
734 				v = g_variant_new_string ("Paused");
735 			}
736 		} else {
737 			v = NULL;
738 		}
739 		rhythmdb_entry_unref (entry);
740 		return v;
741 	}
742 }
743 
744 static GVariant *
get_loop_status(RBMprisPlugin * plugin)745 get_loop_status (RBMprisPlugin *plugin)
746 {
747 	gboolean loop = FALSE;
748 	rb_shell_player_get_playback_state (plugin->player, NULL, &loop);
749 	if (loop) {
750 		return g_variant_new_string ("Playlist");
751 	} else {
752 		return g_variant_new_string ("None");
753 	}
754 }
755 
756 static GVariant *
get_shuffle(RBMprisPlugin * plugin)757 get_shuffle (RBMprisPlugin *plugin)
758 {
759 	gboolean random = FALSE;
760 
761 	rb_shell_player_get_playback_state (plugin->player, &random, NULL);
762 	return g_variant_new_boolean (random);
763 }
764 
765 static GVariant *
get_volume(RBMprisPlugin * plugin)766 get_volume (RBMprisPlugin *plugin)
767 {
768 	gdouble vol;
769 	if (rb_shell_player_get_volume (plugin->player, &vol, NULL)) {
770 		return g_variant_new_double (vol);
771 	} else {
772 		return NULL;
773 	}
774 }
775 
776 static GVariant *
get_can_pause(RBMprisPlugin * plugin)777 get_can_pause (RBMprisPlugin *plugin)
778 {
779 	RBSource *source;
780 	source = rb_shell_player_get_playing_source (plugin->player);
781 	if (source != NULL) {
782 		return g_variant_new_boolean (rb_source_can_pause (source));
783 	} else {
784 		return g_variant_new_boolean (TRUE);
785 	}
786 }
787 
788 static GVariant *
get_can_seek(RBMprisPlugin * plugin)789 get_can_seek (RBMprisPlugin *plugin)
790 {
791 	RBPlayer *player;
792 	GVariant *v;
793 
794 	g_object_get (plugin->player, "player", &player, NULL);
795 	if (player != NULL) {
796 		v = g_variant_new_boolean (rb_player_seekable (player));
797 		g_object_unref (player);
798 	} else {
799 		v = g_variant_new_boolean (FALSE);
800 	}
801 	return v;
802 }
803 
804 static GVariant *
get_player_property(GDBusConnection * connection,const char * sender,const char * object_path,const char * interface_name,const char * property_name,GError ** error,RBMprisPlugin * plugin)805 get_player_property (GDBusConnection *connection,
806 		     const char *sender,
807 		     const char *object_path,
808 		     const char *interface_name,
809 		     const char *property_name,
810 		     GError **error,
811 		     RBMprisPlugin *plugin)
812 {
813 	gboolean ret;
814 
815 	if (g_strcmp0 (object_path, MPRIS_OBJECT_NAME) != 0 ||
816 	    g_strcmp0 (interface_name, MPRIS_PLAYER_INTERFACE) != 0) {
817 		g_set_error (error,
818 			     G_DBUS_ERROR,
819 			     G_DBUS_ERROR_NOT_SUPPORTED,
820 			     "Property %s.%s not supported",
821 			     interface_name,
822 			     property_name);
823 		return NULL;
824 	}
825 
826 	if (g_strcmp0 (property_name, "PlaybackStatus") == 0) {
827 		return get_playback_status (plugin);
828 	} else if (g_strcmp0 (property_name, "LoopStatus") == 0) {
829 		return get_loop_status (plugin);
830 	} else if (g_strcmp0 (property_name, "Rate") == 0) {
831 		return g_variant_new_double (1.0);
832 	} else if (g_strcmp0 (property_name, "Shuffle") == 0) {
833 		return get_shuffle (plugin);
834 	} else if (g_strcmp0 (property_name, "Metadata") == 0) {
835 		RhythmDBEntry *entry;
836 		GVariantBuilder *builder;
837 		GVariant *v;
838 
839 		builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
840 		entry = rb_shell_player_get_playing_entry (plugin->player);
841 		if (entry != NULL) {
842 			build_track_metadata (plugin, builder, entry);
843 			rhythmdb_entry_unref (entry);
844 		}
845 
846 		v = g_variant_builder_end (builder);
847 		g_variant_builder_unref (builder);
848 		return v;
849 	} else if (g_strcmp0 (property_name, "Volume") == 0) {
850 		return get_volume (plugin);
851 	} else if (g_strcmp0 (property_name, "Position") == 0) {
852 		guint t;
853 		ret = rb_shell_player_get_playing_time (plugin->player, &t, error);
854 		if (ret) {
855 			return g_variant_new_int64 ((gint64)t * G_USEC_PER_SEC);
856 		} else {
857 			return NULL;
858 		}
859 	} else if (g_strcmp0 (property_name, "MinimumRate") == 0) {
860 		return g_variant_new_double (1.0);
861 	} else if (g_strcmp0 (property_name, "MaximumRate") == 0) {
862 		return g_variant_new_double (1.0);
863 	} else if (g_strcmp0 (property_name, "CanGoNext") == 0) {
864 		gboolean has_next;
865 		g_object_get (plugin->player, "has-next", &has_next, NULL);
866 		return g_variant_new_boolean (has_next);
867 	} else if (g_strcmp0 (property_name, "CanGoPrevious") == 0) {
868 		gboolean has_prev;
869 		g_object_get (plugin->player, "has-prev", &has_prev, NULL);
870 		return g_variant_new_boolean (has_prev);
871 	} else if (g_strcmp0 (property_name, "CanPlay") == 0) {
872 		/* uh.. under what conditions can we not play?  nothing in the source? */
873 		return g_variant_new_boolean (TRUE);
874 	} else if (g_strcmp0 (property_name, "CanPause") == 0) {
875 		return get_can_pause (plugin);
876 	} else if (g_strcmp0 (property_name, "CanSeek") == 0) {
877 		return get_can_seek (plugin);
878 	} else if (g_strcmp0 (property_name, "CanControl") == 0) {
879 		return g_variant_new_boolean (TRUE);
880 	}
881 
882 	g_set_error (error,
883 		     G_DBUS_ERROR,
884 		     G_DBUS_ERROR_NOT_SUPPORTED,
885 		     "Property %s.%s not supported",
886 		     interface_name,
887 		     property_name);
888 	return NULL;
889 }
890 
891 static gboolean
set_player_property(GDBusConnection * connection,const char * sender,const char * object_path,const char * interface_name,const char * property_name,GVariant * value,GError ** error,RBMprisPlugin * plugin)892 set_player_property (GDBusConnection *connection,
893 		     const char *sender,
894 		     const char *object_path,
895 		     const char *interface_name,
896 		     const char *property_name,
897 		     GVariant *value,
898 		     GError **error,
899 		     RBMprisPlugin *plugin)
900 {
901 	if (g_strcmp0 (object_path, MPRIS_OBJECT_NAME) != 0 ||
902 	    g_strcmp0 (interface_name, MPRIS_PLAYER_INTERFACE) != 0) {
903 		g_set_error (error,
904 			     G_DBUS_ERROR,
905 			     G_DBUS_ERROR_NOT_SUPPORTED,
906 			     "%s:%s not supported",
907 			     object_path,
908 			     interface_name);
909 		return FALSE;
910 	}
911 
912 	if (g_strcmp0 (property_name, "LoopStatus") == 0) {
913 		gboolean shuffle;
914 		gboolean repeat;
915 		const char *status;
916 
917 		rb_shell_player_get_playback_state (plugin->player, &shuffle, &repeat);
918 
919 		status = g_variant_get_string (value, NULL);
920 		if (g_strcmp0 (status, "None") == 0) {
921 			repeat = FALSE;
922 		} else if (g_strcmp0 (status, "Playlist") == 0) {
923 			repeat = TRUE;
924 		} else {
925 			repeat = FALSE;
926 		}
927 		rb_shell_player_set_playback_state (plugin->player, shuffle, repeat);
928 		return TRUE;
929 	} else if (g_strcmp0 (property_name, "Rate") == 0) {
930 		/* not supported */
931 		g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "Can't modify playback rate");
932 		return FALSE;
933 	} else if (g_strcmp0 (property_name, "Shuffle") == 0) {
934 		gboolean shuffle;
935 		gboolean repeat;
936 
937 		rb_shell_player_get_playback_state (plugin->player, &shuffle, &repeat);
938 		shuffle = g_variant_get_boolean (value);
939 		rb_shell_player_set_playback_state (plugin->player, shuffle, repeat);
940 		return TRUE;
941 	} else if (g_strcmp0 (property_name, "Volume") == 0) {
942 		rb_shell_player_set_volume (plugin->player, g_variant_get_double (value), error);
943 		return TRUE;
944 	}
945 
946 	g_set_error (error,
947 		     G_DBUS_ERROR,
948 		     G_DBUS_ERROR_NOT_SUPPORTED,
949 		     "Property %s.%s not supported",
950 		     interface_name,
951 		     property_name);
952 	return FALSE;
953 }
954 
955 static const GDBusInterfaceVTable player_vtable =
956 {
957 	(GDBusInterfaceMethodCallFunc) handle_player_method_call,
958 	(GDBusInterfaceGetPropertyFunc) get_player_property,
959 	(GDBusInterfaceSetPropertyFunc) set_player_property,
960 };
961 
962 static GVariant *
get_maybe_playlist_value(RBMprisPlugin * plugin,RBSource * source)963 get_maybe_playlist_value (RBMprisPlugin *plugin, RBSource *source)
964 {
965 	GVariant *maybe_playlist = NULL;
966 
967 	if (source != NULL) {
968 		const char *id;
969 
970 		id = g_object_get_data (G_OBJECT (source), MPRIS_PLAYLIST_ID_ITEM);
971 		if (id != NULL) {
972 			char *name;
973 			g_object_get (source, "name", &name, NULL);
974 			maybe_playlist = g_variant_new ("(b(oss))", TRUE, id, name, "");
975 			g_free (name);
976 		}
977 	}
978 
979 	if (maybe_playlist == NULL) {
980 		maybe_playlist = g_variant_new ("(b(oss))", FALSE, "/", "", "");
981 	}
982 
983 	return maybe_playlist;
984 }
985 
986 typedef struct {
987 	RBMprisPlugin *plugin;
988 	const char *playlist_id;
989 } ActivateSourceData;
990 
991 static gboolean
activate_source_by_id(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,ActivateSourceData * data)992 activate_source_by_id (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, ActivateSourceData *data)
993 {
994 	RBDisplayPage *page;
995 	const char *id;
996 
997 	gtk_tree_model_get (model, iter,
998 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
999 			    -1);
1000 	id = g_object_get_data (G_OBJECT (page), MPRIS_PLAYLIST_ID_ITEM);
1001 	if (g_strcmp0 (data->playlist_id, id) == 0) {
1002 		RBShell *shell;
1003 		g_object_get (data->plugin, "object", &shell, NULL);
1004 		rb_shell_activate_source (shell, RB_SOURCE (page), RB_SHELL_ACTIVATION_ALWAYS_PLAY, NULL);
1005 		g_object_unref (shell);
1006 		return TRUE;
1007 	}
1008 	return FALSE;
1009 }
1010 
1011 static gboolean
get_playlist_list(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,GList ** playlists)1012 get_playlist_list (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, GList **playlists)
1013 {
1014 	RBDisplayPage *page;
1015 	const char *id;
1016 
1017 	gtk_tree_model_get (model, iter,
1018 			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
1019 			    -1);
1020 	id = g_object_get_data (G_OBJECT (page), MPRIS_PLAYLIST_ID_ITEM);
1021 	if (id != NULL) {
1022 		*playlists = g_list_prepend (*playlists, RB_SOURCE (page));
1023 	}
1024 
1025 	return FALSE;
1026 }
1027 
1028 
1029 static void
handle_playlists_method_call(GDBusConnection * connection,const char * sender,const char * object_path,const char * interface_name,const char * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,RBMprisPlugin * plugin)1030 handle_playlists_method_call (GDBusConnection *connection,
1031 			      const char *sender,
1032 			      const char *object_path,
1033 			      const char *interface_name,
1034 			      const char *method_name,
1035 			      GVariant *parameters,
1036 			      GDBusMethodInvocation *invocation,
1037 			      RBMprisPlugin *plugin)
1038 
1039 {
1040 	if (g_strcmp0 (object_path, MPRIS_OBJECT_NAME) != 0 ||
1041 	    g_strcmp0 (interface_name, MPRIS_PLAYLISTS_INTERFACE) != 0) {
1042 		g_dbus_method_invocation_return_error (invocation,
1043 						       G_DBUS_ERROR,
1044 						       G_DBUS_ERROR_NOT_SUPPORTED,
1045 						       "Method %s.%s not supported",
1046 						       interface_name,
1047 						       method_name);
1048 		return;
1049 	}
1050 
1051 	if (g_strcmp0 (method_name, "ActivatePlaylist") == 0) {
1052 		ActivateSourceData data;
1053 
1054 		data.plugin = plugin;
1055 		g_variant_get (parameters, "(&o)", &data.playlist_id);
1056 		gtk_tree_model_foreach (GTK_TREE_MODEL (plugin->page_model),
1057 					(GtkTreeModelForeachFunc) activate_source_by_id,
1058 					&data);
1059 		g_dbus_method_invocation_return_value (invocation, NULL);
1060 
1061 	} else if (g_strcmp0 (method_name, "GetPlaylists") == 0) {
1062 		guint index;
1063 		guint max_count;
1064 		const char *order;
1065 		gboolean reverse;
1066 		GVariantBuilder *builder;
1067 		GList *playlists = NULL;
1068 		GList *l;
1069 
1070 		g_variant_get (parameters, "(uu&sb)", &index, &max_count, &order, &reverse);
1071 		gtk_tree_model_foreach (GTK_TREE_MODEL (plugin->page_model),
1072 					(GtkTreeModelForeachFunc) get_playlist_list,
1073 					&playlists);
1074 
1075 		/* list is already in reverse order, reverse it again if we want normal order */
1076 		if (reverse == FALSE) {
1077 			playlists = g_list_reverse (playlists);
1078 		}
1079 
1080 		builder = g_variant_builder_new (G_VARIANT_TYPE ("a(oss)"));
1081 		for (l = playlists; l != NULL; l = l->next) {
1082 			RBSource *source;
1083 			const char *id;
1084 			char *name;
1085 
1086 			if (index > 0) {
1087 				index--;
1088 				continue;
1089 			}
1090 
1091 			source = l->data;
1092 			id = g_object_get_data (G_OBJECT (source), MPRIS_PLAYLIST_ID_ITEM);
1093 			g_object_get (source, "name", &name, NULL);
1094 			g_variant_builder_add (builder, "(oss)", id, name, "");
1095 			g_free (name);
1096 
1097 			if (max_count > 0) {
1098 				max_count--;
1099 				if (max_count == 0) {
1100 					break;
1101 				}
1102 			}
1103 		}
1104 
1105 		g_list_free (playlists);
1106 		g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(oss))", builder));
1107 		g_variant_builder_unref (builder);
1108 	} else {
1109 		g_dbus_method_invocation_return_error (invocation,
1110 						       G_DBUS_ERROR,
1111 						       G_DBUS_ERROR_NOT_SUPPORTED,
1112 						       "Method %s.%s not supported",
1113 						       interface_name,
1114 						       method_name);
1115 	}
1116 }
1117 
1118 static GVariant *
get_playlists_property(GDBusConnection * connection,const char * sender,const char * object_path,const char * interface_name,const char * property_name,GError ** error,RBMprisPlugin * plugin)1119 get_playlists_property (GDBusConnection *connection,
1120 			const char *sender,
1121 			const char *object_path,
1122 			const char *interface_name,
1123 			const char *property_name,
1124 			GError **error,
1125 			RBMprisPlugin *plugin)
1126 {
1127 	if (g_strcmp0 (object_path, MPRIS_OBJECT_NAME) != 0 ||
1128 	    g_strcmp0 (interface_name, MPRIS_PLAYLISTS_INTERFACE) != 0) {
1129 		g_set_error (error,
1130 			     G_DBUS_ERROR,
1131 			     G_DBUS_ERROR_NOT_SUPPORTED,
1132 			     "Property %s.%s not supported",
1133 			     interface_name,
1134 			     property_name);
1135 		return NULL;
1136 	}
1137 
1138 	if (g_strcmp0 (property_name, "PlaylistCount") == 0) {
1139 		return g_variant_new_uint32 (plugin->playlist_count);
1140 	} else if (g_strcmp0 (property_name, "Orderings") == 0) {
1141 		const char *orderings[] = {
1142 			"Alphabetical", NULL
1143 		};
1144 		return g_variant_new_strv (orderings, -1);
1145 	} else if (g_strcmp0 (property_name, "ActivePlaylist") == 0) {
1146 		RBSource *source;
1147 
1148 		source = rb_shell_player_get_playing_source (plugin->player);
1149 		return get_maybe_playlist_value (plugin, source);
1150 	}
1151 
1152 	g_set_error (error,
1153 		     G_DBUS_ERROR,
1154 		     G_DBUS_ERROR_NOT_SUPPORTED,
1155 		     "Property %s.%s not supported",
1156 		     interface_name,
1157 		     property_name);
1158 	return NULL;
1159 }
1160 
1161 static gboolean
set_playlists_property(GDBusConnection * connection,const char * sender,const char * object_path,const char * interface_name,const char * property_name,GVariant * value,GError ** error,RBMprisPlugin * plugin)1162 set_playlists_property (GDBusConnection *connection,
1163 			const char *sender,
1164 			const char *object_path,
1165 			const char *interface_name,
1166 			const char *property_name,
1167 			GVariant *value,
1168 			GError **error,
1169 			RBMprisPlugin *plugin)
1170 {
1171 	/* no writeable properties on this interface */
1172 	g_set_error (error,
1173 		     G_DBUS_ERROR,
1174 		     G_DBUS_ERROR_NOT_SUPPORTED,
1175 		     "Property %s.%s not supported",
1176 		     interface_name,
1177 		     property_name);
1178 	return FALSE;
1179 }
1180 
1181 static const GDBusInterfaceVTable playlists_vtable =
1182 {
1183 	(GDBusInterfaceMethodCallFunc) handle_playlists_method_call,
1184 	(GDBusInterfaceGetPropertyFunc) get_playlists_property,
1185 	(GDBusInterfaceSetPropertyFunc) set_playlists_property
1186 };
1187 
1188 static void
play_order_changed_cb(GObject * object,GParamSpec * pspec,RBMprisPlugin * plugin)1189 play_order_changed_cb (GObject *object, GParamSpec *pspec, RBMprisPlugin *plugin)
1190 {
1191 	rb_debug ("emitting LoopStatus and Shuffle change");
1192 	add_player_property_change (plugin, "LoopStatus", get_loop_status (plugin));
1193 	add_player_property_change (plugin, "Shuffle", get_shuffle (plugin));
1194 }
1195 
1196 static void
volume_changed_cb(GObject * object,GParamSpec * pspec,RBMprisPlugin * plugin)1197 volume_changed_cb (GObject *object, GParamSpec *pspec, RBMprisPlugin *plugin)
1198 {
1199 	rb_debug ("emitting Volume change");
1200 	add_player_property_change (plugin, "Volume", get_volume (plugin));
1201 }
1202 
1203 static void
playing_changed_cb(RBShellPlayer * player,gboolean playing,RBMprisPlugin * plugin)1204 playing_changed_cb (RBShellPlayer *player, gboolean playing, RBMprisPlugin *plugin)
1205 {
1206 	rb_debug ("emitting PlaybackStatus change");
1207 	add_player_property_change (plugin, "PlaybackStatus", get_playback_status (plugin));
1208 }
1209 
1210 static void
metadata_changed(RBMprisPlugin * plugin,RhythmDBEntry * entry)1211 metadata_changed (RBMprisPlugin *plugin, RhythmDBEntry *entry)
1212 {
1213 	GVariantBuilder *builder;
1214 
1215 	builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
1216 	if (entry != NULL) {
1217 		build_track_metadata (plugin, builder, entry);
1218 	}
1219 	add_player_property_change (plugin, "Metadata", g_variant_builder_end (builder));
1220 	g_variant_builder_unref (builder);
1221 }
1222 
1223 static void
playing_entry_changed_cb(RBShellPlayer * player,RhythmDBEntry * entry,RBMprisPlugin * plugin)1224 playing_entry_changed_cb (RBShellPlayer *player, RhythmDBEntry *entry, RBMprisPlugin *plugin)
1225 {
1226 	rb_debug ("emitting Metadata and CanSeek changed");
1227 	plugin->last_elapsed = 0;
1228 	metadata_changed (plugin, entry);
1229 	add_player_property_change (plugin, "CanSeek", get_can_seek (plugin));
1230 }
1231 
1232 static void
entry_extra_metadata_notify_cb(RhythmDB * db,RhythmDBEntry * entry,const char * field,GValue * metadata,RBMprisPlugin * plugin)1233 entry_extra_metadata_notify_cb (RhythmDB *db, RhythmDBEntry *entry, const char *field, GValue *metadata, RBMprisPlugin *plugin)
1234 {
1235 	RhythmDBEntry *playing_entry = rb_shell_player_get_playing_entry (plugin->player);
1236 	if (entry == playing_entry) {
1237 		rb_debug ("emitting Metadata change due to extra metadata field %s", field);
1238 		metadata_changed (plugin, entry);
1239 	}
1240 	if (playing_entry != NULL) {
1241 		rhythmdb_entry_unref (playing_entry);
1242 	}
1243 }
1244 
1245 static void
art_added_cb(RBExtDB * store,RBExtDBKey * key,const char * filename,GValue * data,RBMprisPlugin * plugin)1246 art_added_cb (RBExtDB *store, RBExtDBKey *key, const char *filename, GValue *data, RBMprisPlugin *plugin)
1247 {
1248 	RhythmDBEntry *playing_entry = rb_shell_player_get_playing_entry (plugin->player);
1249 	if (playing_entry != NULL && rhythmdb_entry_matches_ext_db_key (plugin->db, playing_entry, key)) {
1250 		rb_debug ("emitting Metadata change due to album art");
1251 		metadata_changed (plugin, playing_entry);
1252 	}
1253 	if (playing_entry != NULL) {
1254 		rhythmdb_entry_unref (playing_entry);
1255 	}
1256 }
1257 
1258 static void
entry_changed_cb(RhythmDB * db,RhythmDBEntry * entry,GPtrArray * changes,RBMprisPlugin * plugin)1259 entry_changed_cb (RhythmDB *db, RhythmDBEntry *entry, GPtrArray *changes, RBMprisPlugin *plugin)
1260 {
1261 	RhythmDBEntry *playing_entry = rb_shell_player_get_playing_entry (plugin->player);
1262 	if (playing_entry == NULL) {
1263 		return;
1264 	}
1265 	if (playing_entry == entry) {
1266 		int i;
1267 		gboolean emit = FALSE;
1268 
1269 		/* make sure there's an interesting property change in there */
1270 		for (i = 0; i < changes->len; i++) {
1271 			RhythmDBEntryChange *change = g_ptr_array_index (changes, i);
1272 			switch (change->prop) {
1273 				/* probably not complete */
1274 				case RHYTHMDB_PROP_MOUNTPOINT:
1275 				case RHYTHMDB_PROP_MTIME:
1276 				case RHYTHMDB_PROP_FIRST_SEEN:
1277 				case RHYTHMDB_PROP_LAST_SEEN:
1278 				case RHYTHMDB_PROP_LAST_PLAYED:
1279 				case RHYTHMDB_PROP_MEDIA_TYPE:
1280 				case RHYTHMDB_PROP_PLAYBACK_ERROR:
1281 					break;
1282 
1283 				default:
1284 					emit = TRUE;
1285 					break;
1286 			}
1287 		}
1288 
1289 		if (emit) {
1290 			rb_debug ("emitting Metadata change due to property changes");
1291 			metadata_changed (plugin, playing_entry);
1292 		}
1293 	}
1294 	rhythmdb_entry_unref (playing_entry);
1295 }
1296 
1297 static void
playing_source_changed_cb(RBShellPlayer * player,RBSource * source,RBMprisPlugin * plugin)1298 playing_source_changed_cb (RBShellPlayer *player,
1299 			   RBSource *source,
1300 			   RBMprisPlugin *plugin)
1301 {
1302 	rb_debug ("emitting CanPause change");
1303 	add_player_property_change (plugin, "CanPause", get_can_pause (plugin));
1304 
1305 	rb_debug ("emitting ActivePlaylist change");
1306 	add_playlist_property_change (plugin, "ActivePlaylist", get_maybe_playlist_value (plugin, source));
1307 }
1308 
1309 static void
player_has_next_changed_cb(GObject * object,GParamSpec * pspec,RBMprisPlugin * plugin)1310 player_has_next_changed_cb (GObject *object, GParamSpec *pspec, RBMprisPlugin *plugin)
1311 {
1312 	GVariant *v;
1313 	gboolean has_next;
1314 	rb_debug ("emitting CanGoNext change");
1315 	g_object_get (object, "has-next", &has_next, NULL);
1316 	v = g_variant_new_boolean (has_next);
1317 	add_player_property_change (plugin, "CanGoNext", v);
1318 }
1319 
1320 static void
player_has_prev_changed_cb(GObject * object,GParamSpec * pspec,RBMprisPlugin * plugin)1321 player_has_prev_changed_cb (GObject *object, GParamSpec *pspec, RBMprisPlugin *plugin)
1322 {
1323 	GVariant *v;
1324 	gboolean has_prev;
1325 
1326 	rb_debug ("emitting CanGoPrevious change");
1327 	g_object_get (object, "has-prev", &has_prev, NULL);
1328 	v = g_variant_new_boolean (has_prev);
1329 	add_player_property_change (plugin, "CanGoPrevious", v);
1330 }
1331 
1332 static void
elapsed_nano_changed_cb(RBShellPlayer * player,gint64 elapsed,RBMprisPlugin * plugin)1333 elapsed_nano_changed_cb (RBShellPlayer *player, gint64 elapsed, RBMprisPlugin *plugin)
1334 {
1335 	/* interpret any change in the elapsed time other than an
1336 	 * increase of less than one second as a seek.  this includes
1337 	 * the seek back that we do after pausing (with crossfading),
1338 	 * which we intentionally report as a seek to help clients get
1339 	 * their time displays right.
1340 	 */
1341 	if (elapsed >= plugin->last_elapsed &&
1342 	    (elapsed - plugin->last_elapsed < (G_USEC_PER_SEC * 1000))) {
1343 		plugin->last_elapsed = elapsed;
1344 		return;
1345 	}
1346 
1347 	if (plugin->property_emit_id == 0) {
1348 		plugin->property_emit_id = g_idle_add ((GSourceFunc)emit_properties_idle, plugin);
1349 	}
1350 	plugin->emit_seeked = TRUE;
1351 	plugin->last_elapsed = elapsed;
1352 }
1353 
1354 static void
source_deleted_cb(RBDisplayPage * page,RBMprisPlugin * plugin)1355 source_deleted_cb (RBDisplayPage *page, RBMprisPlugin *plugin)
1356 {
1357 	plugin->playlist_count--;
1358 	rb_debug ("playlist deleted");
1359 	add_playlist_property_change (plugin, "PlaylistCount", g_variant_new_uint32 (plugin->playlist_count));
1360 }
1361 
1362 static void
display_page_inserted_cb(RBDisplayPageModel * model,RBDisplayPage * page,GtkTreeIter * iter,RBMprisPlugin * plugin)1363 display_page_inserted_cb (RBDisplayPageModel *model, RBDisplayPage *page, GtkTreeIter *iter, RBMprisPlugin *plugin)
1364 {
1365 	if (RB_IS_PLAYLIST_SOURCE (page)) {
1366 		gboolean is_local;
1367 
1368 		g_object_get (page, "is-local", &is_local, NULL);
1369 		if (is_local) {
1370 			char *id;
1371 
1372 			id = g_strdup_printf ("/org/gnome/Rhythmbox3/Playlist/%p", page);
1373 			g_object_set_data_full (G_OBJECT (page), MPRIS_PLAYLIST_ID_ITEM, id, g_free);
1374 
1375 			plugin->playlist_count++;
1376 			rb_debug ("new playlist %s", id);
1377 			add_playlist_property_change (plugin, "PlaylistCount", g_variant_new_uint32 (plugin->playlist_count));
1378 
1379 			g_signal_connect_object (page, "deleted", G_CALLBACK (source_deleted_cb), plugin, 0);
1380 		}
1381 	}
1382 }
1383 
1384 static gboolean
display_page_foreach_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,RBMprisPlugin * plugin)1385 display_page_foreach_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, RBMprisPlugin *plugin)
1386 {
1387 	RBDisplayPage *page;
1388 
1389 	gtk_tree_model_get (model, iter, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page, -1);
1390 	display_page_inserted_cb (RB_DISPLAY_PAGE_MODEL (model), page, iter, plugin);
1391 
1392 	return FALSE;
1393 }
1394 
1395 static void
name_acquired_cb(GDBusConnection * connection,const char * name,RBMprisPlugin * plugin)1396 name_acquired_cb (GDBusConnection *connection, const char *name, RBMprisPlugin *plugin)
1397 {
1398 	rb_debug ("successfully acquired dbus name %s", name);
1399 }
1400 
1401 static void
name_lost_cb(GDBusConnection * connection,const char * name,RBMprisPlugin * plugin)1402 name_lost_cb (GDBusConnection *connection, const char *name, RBMprisPlugin *plugin)
1403 {
1404 	rb_debug ("lost dbus name %s", name);
1405 }
1406 
1407 static void
impl_activate(PeasActivatable * bplugin)1408 impl_activate (PeasActivatable *bplugin)
1409 {
1410 	RBMprisPlugin *plugin;
1411 	GDBusInterfaceInfo *ifaceinfo;
1412 	GError *error = NULL;
1413 	RBShell *shell;
1414 
1415 	rb_debug ("activating MPRIS plugin");
1416 
1417 	plugin = RB_MPRIS_PLUGIN (bplugin);
1418 	g_object_get (plugin, "object", &shell, NULL);
1419 	g_object_get (shell,
1420 		      "shell-player", &plugin->player,
1421 		      "db", &plugin->db,
1422 		      "display-page-model", &plugin->page_model,
1423 		      NULL);
1424 
1425 	plugin->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
1426 	if (error != NULL) {
1427 		g_warning ("Unable to connect to D-Bus session bus: %s", error->message);
1428 		g_object_unref (shell);
1429 		return;
1430 	}
1431 
1432 	/* parse introspection data */
1433 	plugin->node_info = g_dbus_node_info_new_for_xml (mpris_introspection_xml, &error);
1434 	if (error != NULL) {
1435 		g_warning ("Unable to read MPRIS interface specificiation: %s", error->message);
1436 		g_object_unref (shell);
1437 		return;
1438 	}
1439 
1440 	/* register root interface */
1441 	ifaceinfo = g_dbus_node_info_lookup_interface (plugin->node_info, MPRIS_ROOT_INTERFACE);
1442 	plugin->root_id = g_dbus_connection_register_object (plugin->connection,
1443 							     MPRIS_OBJECT_NAME,
1444 							     ifaceinfo,
1445 							     &root_vtable,
1446 							     plugin,
1447 							     NULL,
1448 							     &error);
1449 	if (error != NULL) {
1450 		g_warning ("unable to register MPRIS root interface: %s", error->message);
1451 		g_error_free (error);
1452 	}
1453 
1454 	/* register player interface */
1455 	ifaceinfo = g_dbus_node_info_lookup_interface (plugin->node_info, MPRIS_PLAYER_INTERFACE);
1456 	plugin->player_id = g_dbus_connection_register_object (plugin->connection,
1457 							       MPRIS_OBJECT_NAME,
1458 							       ifaceinfo,
1459 							       &player_vtable,
1460 							       plugin,
1461 							       NULL,
1462 							       &error);
1463 	if (error != NULL) {
1464 		g_warning ("Unable to register MPRIS player interface: %s", error->message);
1465 		g_error_free (error);
1466 	}
1467 
1468 	/* register playlists interface */
1469 	ifaceinfo = g_dbus_node_info_lookup_interface (plugin->node_info, MPRIS_PLAYLISTS_INTERFACE);
1470 	plugin->playlists_id = g_dbus_connection_register_object (plugin->connection,
1471 								  MPRIS_OBJECT_NAME,
1472 								  ifaceinfo,
1473 								  &playlists_vtable,
1474 								  plugin,
1475 								  NULL,
1476 								  &error);
1477 	if (error != NULL) {
1478 		g_warning ("Unable to register MPRIS playlists interface: %s", error->message);
1479 		g_error_free (error);
1480 	}
1481 
1482 	/* connect signal handlers for stuff */
1483 	g_signal_connect_object (plugin->player,
1484 				 "notify::play-order",
1485 				 G_CALLBACK (play_order_changed_cb),
1486 				 plugin, 0);
1487 	g_signal_connect_object (plugin->player,
1488 				 "notify::volume",
1489 				 G_CALLBACK (volume_changed_cb),
1490 				 plugin, 0);
1491 	g_signal_connect_object (plugin->player,
1492 				 "playing-changed",
1493 				 G_CALLBACK (playing_changed_cb),
1494 				 plugin, 0);
1495 	g_signal_connect_object (plugin->player,
1496 				 "playing-song-changed",
1497 				 G_CALLBACK (playing_entry_changed_cb),
1498 				 plugin, 0);
1499 	g_signal_connect_object (plugin->db,
1500 				 "entry-extra-metadata-notify",
1501 				 G_CALLBACK (entry_extra_metadata_notify_cb),
1502 				 plugin, 0);
1503 	g_signal_connect_object (plugin->db,
1504 				 "entry-changed",
1505 				 G_CALLBACK (entry_changed_cb),
1506 				 plugin, 0);
1507 	g_signal_connect_object (plugin->player,
1508 				 "playing-source-changed",
1509 				 G_CALLBACK (playing_source_changed_cb),
1510 				 plugin, 0);
1511 	g_signal_connect_object (plugin->player,
1512 				 "elapsed-nano-changed",
1513 				 G_CALLBACK (elapsed_nano_changed_cb),
1514 				 plugin, 0);
1515 	g_signal_connect_object (plugin->player,
1516 				 "notify::has-next",
1517 				 G_CALLBACK (player_has_next_changed_cb),
1518 				 plugin, 0);
1519 	g_signal_connect_object (plugin->player,
1520 				 "notify::has-prev",
1521 				 G_CALLBACK (player_has_prev_changed_cb),
1522 				 plugin, 0);
1523 	g_signal_connect_object (plugin->page_model,
1524 				 "page-inserted",
1525 				 G_CALLBACK (display_page_inserted_cb),
1526 				 plugin, 0);
1527 	gtk_tree_model_foreach (GTK_TREE_MODEL (plugin->page_model),
1528 				(GtkTreeModelForeachFunc) display_page_foreach_cb,
1529 				plugin);
1530 
1531 	plugin->art_store = rb_ext_db_new ("album-art");
1532 	g_signal_connect_object (plugin->art_store,
1533 				 "added",
1534 				 G_CALLBACK (art_added_cb),
1535 				 plugin, 0);
1536 
1537 	plugin->name_own_id = g_bus_own_name (G_BUS_TYPE_SESSION,
1538 					      MPRIS_BUS_NAME_PREFIX ".rhythmbox",
1539 					      G_BUS_NAME_OWNER_FLAGS_NONE,
1540 					      NULL,
1541 					      (GBusNameAcquiredCallback) name_acquired_cb,
1542 					      (GBusNameLostCallback) name_lost_cb,
1543 					      g_object_ref (plugin),
1544 					      g_object_unref);
1545 	g_object_unref (shell);
1546 }
1547 
1548 static void
impl_deactivate(PeasActivatable * bplugin)1549 impl_deactivate	(PeasActivatable *bplugin)
1550 {
1551 	RBMprisPlugin *plugin;
1552 
1553 	plugin = RB_MPRIS_PLUGIN (bplugin);
1554 
1555 	if (plugin->root_id != 0) {
1556 		g_dbus_connection_unregister_object (plugin->connection, plugin->root_id);
1557 		plugin->root_id = 0;
1558 	}
1559 	if (plugin->player_id != 0) {
1560 		g_dbus_connection_unregister_object (plugin->connection, plugin->player_id);
1561 		plugin->player_id = 0;
1562 	}
1563 	if (plugin->playlists_id != 0) {
1564 		g_dbus_connection_unregister_object (plugin->connection, plugin->playlists_id);
1565 		plugin->playlists_id = 0;
1566 	}
1567 
1568 	if (plugin->property_emit_id != 0) {
1569 		g_source_remove (plugin->property_emit_id);
1570 		plugin->property_emit_id = 0;
1571 	}
1572 	if (plugin->player_property_changes != NULL) {
1573 		g_hash_table_destroy (plugin->player_property_changes);
1574 		plugin->player_property_changes = NULL;
1575 	}
1576 	if (plugin->playlist_property_changes != NULL) {
1577 		g_hash_table_destroy (plugin->playlist_property_changes);
1578 		plugin->playlist_property_changes = NULL;
1579 	}
1580 
1581 	if (plugin->player != NULL) {
1582 		g_signal_handlers_disconnect_by_func (plugin->player,
1583 						      G_CALLBACK (play_order_changed_cb),
1584 						      plugin);
1585 		g_signal_handlers_disconnect_by_func (plugin->player,
1586 						      G_CALLBACK (volume_changed_cb),
1587 						      plugin);
1588 		g_signal_handlers_disconnect_by_func (plugin->player,
1589 						      G_CALLBACK (playing_changed_cb),
1590 						      plugin);
1591 		g_signal_handlers_disconnect_by_func (plugin->player,
1592 						      G_CALLBACK (playing_entry_changed_cb),
1593 						      plugin);
1594 		g_signal_handlers_disconnect_by_func (plugin->player,
1595 						      G_CALLBACK (playing_source_changed_cb),
1596 						      plugin);
1597 		g_signal_handlers_disconnect_by_func (plugin->player,
1598 						      G_CALLBACK (elapsed_nano_changed_cb),
1599 						      plugin);
1600 		g_object_unref (plugin->player);
1601 		plugin->player = NULL;
1602 	}
1603 	if (plugin->db != NULL) {
1604 		g_signal_handlers_disconnect_by_func (plugin->db,
1605 						      G_CALLBACK (entry_extra_metadata_notify_cb),
1606 						      plugin);
1607 		g_signal_handlers_disconnect_by_func (plugin->db,
1608 						      G_CALLBACK (entry_changed_cb),
1609 						      plugin);
1610 		g_object_unref (plugin->db);
1611 		plugin->db = NULL;
1612 	}
1613 	if (plugin->page_model != NULL) {
1614 		g_signal_handlers_disconnect_by_func (plugin->page_model,
1615 						      G_CALLBACK (display_page_inserted_cb),
1616 						      plugin);
1617 		g_object_unref (plugin->page_model);
1618 		plugin->page_model = NULL;
1619 	}
1620 
1621 	if (plugin->name_own_id > 0) {
1622 		g_bus_unown_name (plugin->name_own_id);
1623 		plugin->name_own_id = 0;
1624 	}
1625 
1626 	if (plugin->node_info != NULL) {
1627 		g_dbus_node_info_unref (plugin->node_info);
1628 		plugin->node_info = NULL;
1629 	}
1630 
1631 	if (plugin->connection != NULL) {
1632 		g_object_unref (plugin->connection);
1633 		plugin->connection = NULL;
1634 	}
1635 
1636 	if (plugin->art_store != NULL) {
1637 		g_signal_handlers_disconnect_by_func (plugin->art_store,
1638 						      G_CALLBACK (art_added_cb),
1639 						      plugin);
1640 		g_object_unref (plugin->art_store);
1641 		plugin->art_store = NULL;
1642 	}
1643 }
1644 
1645 G_MODULE_EXPORT void
peas_register_types(PeasObjectModule * module)1646 peas_register_types (PeasObjectModule *module)
1647 {
1648 	rb_mpris_plugin_register_type (G_TYPE_MODULE (module));
1649 	peas_object_module_register_extension_type (module,
1650 						    PEAS_TYPE_ACTIVATABLE,
1651 						    RB_TYPE_MPRIS_PLUGIN);
1652 }
1653