hythmbox-2.98/plugins/mpris/rb-mpris-plugin.c

No issues found

Incomplete coverage

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