No issues found
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 |
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 }