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