[rhythmbox] add implementation of the MPRIS D-Bus spec



commit f72249439478c5ba2a96656e0672a500dd283e13
Author: Jonathan Matthew <jonathan d14n org>
Date:   Thu Jun 10 12:54:19 2010 +1000

    add implementation of the MPRIS D-Bus spec
    
    This is a new hidden plugin, enabled by default, using GDBus.
    As a result, it is only built when glib 2.26 or newer is available.
    
    The tracklist object has not been implemented.  It doesn't quite
    fit into how rhythmbox works, so it probably won't be implemented
    unless we need it for something interesting.

 configure.ac                     |    8 +-
 data/rhythmbox.schemas           |   22 ++
 plugins/Makefile.am              |    4 +
 plugins/mpris/Makefile.am        |   38 +++
 plugins/mpris/mpris-spec.h       |   66 ++++
 plugins/mpris/mpris.rb-plugin.in |    8 +
 plugins/mpris/rb-mpris-plugin.c  |  675 ++++++++++++++++++++++++++++++++++++++
 7 files changed, 820 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index c3013cf..161926e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -59,6 +59,7 @@ GUDEV_REQS=143
 LIBMTP_REQS=0.3.0
 PYGOBJECT_REQUIRED=2.15.4
 WEBKIT_MIN_REQS=1.1.17
+GLIB_GDBUS_REQS=2.25.6
 
 AC_MSG_CHECKING([for GNU extension fwrite_unlocked])
 AC_LINK_IFELSE(
@@ -82,7 +83,7 @@ AM_CONDITIONAL(MKDTEMP_MISSING, test x$mkdtemp_missing = xtrue)
 
 PKG_PROG_PKG_CONFIG
 
-PKG_CHECK_MODULES(RB_CLIENT, glib-2.0 >= $GLIB_REQS gio-2.0 >= $GLIB_REQS)
+PKG_CHECK_MODULES(RB_CLIENT, glib-2.0 >= $GLIB_REQS gio-2.0 >= $GLIB_REQS gio-unix-2.0 >= $GLIB_REQS)
 
 dnl  note: gio-unix-2.0 is here for libmediaplayerid
 PKG_CHECK_MODULES(RHYTHMBOX,				\
@@ -103,6 +104,10 @@ else
    fi
 fi
 
+dnl check for GDBus
+PKG_CHECK_EXISTS(glib-2.0 >= $GLIB_GDBUS_REQS, [have_gdbus=yes], [have_gdbus=no])
+AM_CONDITIONAL(USE_GDBUS, test x"$have_gdbus" = xyes)
+
 dnl gudev
 AC_ARG_WITH(gudev,
 	    AC_HELP_STRING([--with-gudev],
@@ -888,6 +893,7 @@ plugins/context/context/Makefile
 plugins/sendto/Makefile
 plugins/replaygain/Makefile
 plugins/replaygain/replaygain/Makefile
+plugins/mpris/Makefile
 bindings/Makefile
 bindings/python/Makefile
 bindings/vala/Makefile
diff --git a/data/rhythmbox.schemas b/data/rhythmbox.schemas
index 043c829..b0e6a25 100644
--- a/data/rhythmbox.schemas
+++ b/data/rhythmbox.schemas
@@ -1636,5 +1636,27 @@
 	<long>True if the MTP device plugin is hidden.</long>
 	</locale>
       </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/mpris/active</key>
+        <applyto>/apps/rhythmbox/plugins/mpris/active</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>TRUE</default>
+        <locale name="C">
+        <short>True if the MPRIS plugin is enabled.</short>
+	<long>True if the MPRIS plugin is enabled.</long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/mpris/hidden</key>
+        <applyto>/apps/rhythmbox/plugins/mpris/hidden</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>TRUE</default>
+        <locale name="C">
+        <short>True if the MPRIS plugin is hidden.</short>
+	<long>True if the MPRIS plugin is hidden.</long>
+	</locale>
+      </schema>
     </schemalist>
 </gconfschemafile>
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 55755f5..650aaa7 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -58,3 +58,7 @@ if ENABLE_FM_RADIO
 SUBDIRS += fmradio
 endif
 
+if USE_GDBUS
+SUBDIRS += mpris
+endif
+
diff --git a/plugins/mpris/Makefile.am b/plugins/mpris/Makefile.am
new file mode 100644
index 0000000..c50d530
--- /dev/null
+++ b/plugins/mpris/Makefile.am
@@ -0,0 +1,38 @@
+NULL =
+
+plugindir = $(PLUGINDIR)/mpris
+plugin_LTLIBRARIES = libmpris.la
+
+libmpris_la_SOURCES = \
+	rb-mpris-plugin.c				\
+	$(NULL)
+
+libmpris_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+libmpris_la_LIBTOOLFLAGS = --tag=disable-static
+
+libmpris_la_LIBADD =					\
+	$(top_builddir)/shell/librhythmbox-core.la	\
+	$(NULL)
+
+INCLUDES = 						\
+        -DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
+	-DG_LOG_DOMAIN=\"Rhythmbox\"		 	\
+	-I$(top_srcdir) 				\
+	-I$(top_builddir)	   			\
+	-DPIXMAP_DIR=\""$(datadir)/pixmaps"\"		\
+	-DSHARE_DIR=\"$(pkgdatadir)\"                   \
+	-DDATADIR=\""$(datadir)"\"			\
+	$(RHYTHMBOX_CFLAGS)				\
+	-D_XOPEN_SOURCE -D_BSD_SOURCE			\
+	$(NULL)
+
+plugin_in_files = mpris.rb-plugin.in
+
+%.rb-plugin: %.rb-plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.rb-plugin.in=.rb-plugin)
+
+EXTRA_DIST = $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
diff --git a/plugins/mpris/mpris-spec.h b/plugins/mpris/mpris-spec.h
new file mode 100644
index 0000000..556d379
--- /dev/null
+++ b/plugins/mpris/mpris-spec.h
@@ -0,0 +1,66 @@
+const char *mpris_iface_name = "org.freedesktop.MediaPlayer";
+
+const char *mpris_root_spec =
+	"<node name='/Root_Node'>"
+	"  <interface name='org.freedesktop.MediaPlayer'>"
+	"    <method name='Identity'>"
+	"      <arg direction='out' type='s' name='Identity' />"
+	"    </method>"
+	"    <method name='Quit' />"
+	"    <method name='MprisVersion'>"
+	"      <arg direction='out' type='(qq)' name='MprisVersion' />"
+	"    </method>"
+	"  </interface>"
+	"</node>";
+
+/* obviously incomplete */
+const char *mpris_tracklist_spec =
+	"<node name='/TrackList_Node'>"
+	"  <interface name='org.freedesktop.MediaPlayer'>"
+	"  </interface>"
+	"</node>";
+
+const char *mpris_player_spec =
+	"<node name='/Player_Node'>"
+	"  <interface name='org.freedesktop.MediaPlayer'>"
+	"    <method name='Next' />"
+	"    <method name='Prev' />"
+	"    <method name='Pause' />"
+	"    <method name='Stop' />"
+	"    <method name='Play' />"
+	"    <method name='PlayPause' />"
+	"    <method name='Repeat' >"
+	"      <arg direction='in' type='b' name='State' />"
+	"    </method>"
+	"    <method name='GetStatus'>"
+	"      <arg direction='out' type='(iiii)' name='Status' />"
+	"    </method>"
+	"    <method name='GetMetadata'>"
+	"      <arg direction='out' type='a{sv}' name='Metadata' />"
+	"    </method>"
+	"    <method name='GetCaps'>"
+	"      <arg direction='out' type='i' name='Capabilities' />"
+	"    </method>"
+	"    <method name='VolumeSet'>"
+	"      <arg direction='in' type='i' name='Volume' />"
+	"    </method>"
+	"    <method name='VolumeGet'>"
+	"      <arg direction='out' type='i' name='Volume' />"
+	"    </method>"
+	"    <method name='PositionSet'>"
+	"      <arg direction='in' type='i' name='Position' />"
+	"    </method>"
+	"    <method name='PositionGet'>"
+	"      <arg direction='out' type='i' name='Position' />"
+	"    </method>"
+	"    <signal name='TrackChange'>"
+	"      <arg name='Metadata' type='a{sv}' />"
+	"    </signal>"
+	"    <signal name='StatusChange'>"
+	"      <arg name='Status' type='(iiii)'/>"
+	"    </signal>"
+	"    <signal name='CapsChange'>"
+	"      <arg name='Capabilities' type='i' />"
+	"    </signal>"
+	"  </interface>"
+	"</node>";
diff --git a/plugins/mpris/mpris.rb-plugin.in b/plugins/mpris/mpris.rb-plugin.in
new file mode 100644
index 0000000..760152c
--- /dev/null
+++ b/plugins/mpris/mpris.rb-plugin.in
@@ -0,0 +1,8 @@
+[RB Plugin]
+Module=mpris
+IAge=1
+_Name=MPRIS D-Bus interface
+_Description=Provides an implementation of the MPRIS D-Bus interface specification
+Authors=Jonathan Matthew
+Copyright=Copyright © 2010 Jonathan Matthew
+Website=http://www.rhythmbox.org/
diff --git a/plugins/mpris/rb-mpris-plugin.c b/plugins/mpris/rb-mpris-plugin.c
new file mode 100644
index 0000000..45adb70
--- /dev/null
+++ b/plugins/mpris/rb-mpris-plugin.c
@@ -0,0 +1,675 @@
+/*
+ * rb-mpris-plugin.c
+ *
+ *  Copyright (C) 2010  Jonathan Matthew  <jonathan d14n org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
+ * GStreamer plugins to be used and distributed together with GStreamer
+ * and Rhythmbox. This permission is above and beyond the permissions granted
+ * by the GPL license by which Rhythmbox is covered. If you modify this code
+ * you may extend this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include <lib/rb-util.h>
+#include <lib/rb-debug.h>
+#include <shell/rb-plugin.h>
+#include <shell/rb-shell.h>
+#include <shell/rb-shell-player.h>
+
+#define RB_TYPE_MPRIS_PLUGIN		(rb_mpris_plugin_get_type ())
+#define RB_MPRIS_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_MPRIS_PLUGIN, RBMprisPlugin))
+#define RB_MPRIS_PLUGIN_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_MPRIS_PLUGIN, RBMprisPluginClass))
+#define RB_IS_MPRIS_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_MPRIS_PLUGIN))
+#define RB_IS_MPRIS_PLUGIN_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_MPRIS_PLUGIN))
+#define RB_MPRIS_PLUGIN_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_MPRIS_PLUGIN, RBMprisPluginClass))
+
+#include "mpris-spec.h"
+
+typedef struct
+{
+	RBPlugin parent;
+
+	guint name_own_id;
+
+	GDBusConnection *connection;
+	guint root_id;
+	guint tracklist_id;
+	guint player_id;
+
+	RBShell *shell;
+	RBShellPlayer *player;
+	RhythmDB *db;
+
+} RBMprisPlugin;
+
+typedef struct
+{
+	RBPluginClass parent_class;
+} RBMprisPluginClass;
+
+
+G_MODULE_EXPORT GType register_rb_plugin (GTypeModule *module);
+GType	rb_mpris_plugin_get_type		(void) G_GNUC_CONST;
+
+RB_PLUGIN_REGISTER(RBMprisPlugin, rb_mpris_plugin)
+#define RB_MPRIS_PLUGIN_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), RB_TYPE_MPRIS_PLUGIN, RBMprisPluginPrivate))
+
+
+static void
+rb_mpris_plugin_init (RBMprisPlugin *plugin)
+{
+}
+
+/* MPRIS root object */
+
+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)
+{
+	if (g_strcmp0 (object_path, "/") != 0 ||
+	    g_strcmp0 (interface_name, mpris_iface_name) != 0) {
+		rb_debug ("?!");
+		return;
+	}
+
+	if (g_strcmp0 (method_name, "Identity") == 0) {
+		g_dbus_method_invocation_return_value (invocation,
+						       g_variant_new ("(s)", "Rhythmbox " VERSION));
+	} else if (g_strcmp0 (method_name, "Quit") == 0) {
+		rb_shell_quit (plugin->shell, NULL);
+		g_dbus_method_invocation_return_value (invocation, NULL);
+	} else if (g_strcmp0 (method_name, "MprisVersion") == 0) {
+		g_dbus_method_invocation_return_value (invocation, g_variant_new ("((qq))", 1, 0));	/* what is the version number, anyway? */
+	}
+}
+
+static const GDBusInterfaceVTable root_vtable =
+{
+	(GDBusInterfaceMethodCallFunc) handle_root_method_call,
+	NULL,
+	NULL
+};
+
+/* MPRIS tracklist object (not implemented) */
+
+static void
+handle_tracklist_call (GDBusConnection *connection,
+		       const char *sender,
+		       const char *object_path,
+		       const char *interface_name,
+		       const char *method_name,
+		       GVariant *parameters,
+		       GDBusMethodInvocation *invocation,
+		       RBMprisPlugin *plugin)
+{
+	/* do nothing */
+}
+
+static const GDBusInterfaceVTable tracklist_vtable =
+{
+	(GDBusInterfaceMethodCallFunc) handle_tracklist_call,
+	NULL,
+	NULL
+};
+
+
+/* MPRIS player object */
+
+static void
+handle_result (GDBusMethodInvocation *invocation, gboolean ret, GError *error)
+{
+	if (ret) {
+		g_dbus_method_invocation_return_value (invocation, NULL);
+	} else {
+		g_dbus_method_invocation_return_gerror (invocation, error);
+		g_error_free (error);
+	}
+}
+
+static void
+add_string_property (GVariantBuilder *builder,
+		     RhythmDBEntry *entry,
+		     RhythmDBPropType prop,
+		     const char *name)
+{
+	rb_debug ("adding %s = %s", name, rhythmdb_entry_get_string (entry, prop));
+	g_variant_builder_add (builder,
+			       "{sv}",
+			       name,
+			       g_variant_new ("s", rhythmdb_entry_get_string (entry, prop)));
+}
+
+static void
+add_string_property_2 (GVariantBuilder *builder,
+		       RhythmDB *db,
+		       RhythmDBEntry *entry,
+		       RhythmDBPropType prop,
+		       const char *name,
+		       const char *extra_field_name)
+{
+	GValue *v;
+	const char *value;
+
+	v = rhythmdb_entry_request_extra_metadata (db, entry, extra_field_name);
+	if (v != NULL) {
+		value = g_value_get_string (v);
+	} else {
+		value = rhythmdb_entry_get_string (entry, prop);
+	}
+
+	rb_debug ("adding %s = %s", name, value);
+	g_variant_builder_add (builder, "{sv}", name, g_variant_new ("s", value));
+
+	if (v != NULL) {
+		g_value_unset (v);
+		g_free (v);
+	}
+
+}
+
+static void
+add_int_property (GVariantBuilder *builder,
+		  RhythmDBEntry *entry,
+		  RhythmDBPropType prop,
+		  const char *name)
+{
+	int v;
+	v = rhythmdb_entry_get_ulong (entry, prop);
+	rb_debug ("adding %s = %u", name, v);
+	g_variant_builder_add (builder,
+			       "{sv}",
+			       name,
+			       g_variant_new ("i", v));
+}
+
+static void
+add_double_property (GVariantBuilder *builder,
+		     RhythmDBEntry *entry,
+		     RhythmDBPropType prop,
+		     const char *name)
+{
+	int v;
+	v = (int)rhythmdb_entry_get_double (entry, prop);
+	rb_debug ("adding %s = %i", name, v);
+	g_variant_builder_add (builder,
+			       "{sv}",
+			       name,
+			       g_variant_new ("i", v));
+}
+
+static void
+build_track_metadata (RBMprisPlugin *plugin,
+		      GVariantBuilder *builder,
+		      RhythmDBEntry *entry)
+{
+	GValue *md;
+
+	add_string_property (builder, entry, RHYTHMDB_PROP_LOCATION, "location");
+	add_string_property_2 (builder, plugin->db, entry, RHYTHMDB_PROP_TITLE, "title", RHYTHMDB_PROP_STREAM_SONG_TITLE);
+	add_string_property_2 (builder, plugin->db, entry, RHYTHMDB_PROP_ARTIST, "artist", RHYTHMDB_PROP_STREAM_SONG_ARTIST);
+	add_string_property_2 (builder, plugin->db, entry, RHYTHMDB_PROP_ALBUM, "album", RHYTHMDB_PROP_STREAM_SONG_ALBUM);
+	add_string_property (builder, entry, RHYTHMDB_PROP_GENRE, "genre");
+	add_string_property (builder, entry, RHYTHMDB_PROP_COMMENT, "comment");
+
+	add_string_property (builder, entry, RHYTHMDB_PROP_MUSICBRAINZ_TRACKID, "mb track id");
+	add_string_property (builder, entry, RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID, "mb album id");
+	add_string_property (builder, entry, RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID, "mb artist id");
+	add_string_property (builder, entry, RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID, "mb album artist id");
+
+	add_string_property (builder, entry, RHYTHMDB_PROP_ARTIST_SORTNAME, "mb artist sort name");
+	add_string_property (builder, entry, RHYTHMDB_PROP_ALBUM_SORTNAME, "mb album sort name");	/* extension */
+
+	add_int_property (builder, entry, RHYTHMDB_PROP_TRACK_NUMBER, "tracknumber");
+	add_int_property (builder, entry, RHYTHMDB_PROP_DISC_NUMBER, "discnumber");	/* extension */
+	add_int_property (builder, entry, RHYTHMDB_PROP_DURATION, "duration");
+	add_int_property (builder, entry, RHYTHMDB_PROP_BITRATE, "audio-bitrate");
+	add_int_property (builder, entry, RHYTHMDB_PROP_YEAR, "year");
+	/* missing: date */
+
+	add_double_property (builder, entry, RHYTHMDB_PROP_RATING, "rating");
+
+	md = rhythmdb_entry_request_extra_metadata (plugin->db, entry, RHYTHMDB_PROP_COVER_ART_URI);
+	if (md != NULL) {
+		const char *uri;
+		uri = g_value_get_string (md);
+		if (uri != NULL && uri[0] != '\0') {
+			g_variant_builder_add (builder, "{sv}", "arturl", g_variant_new ("s", uri));
+		}
+
+		g_value_unset (md);
+		g_free (md);
+	}
+}
+
+static GVariant *
+get_player_state (RBMprisPlugin *plugin, GError **error)
+{
+	gboolean playing;
+	gboolean random;
+	gboolean repeat;
+	gboolean loop;
+
+	playing = FALSE;
+	if (rb_shell_player_get_playing (plugin->player, &playing, error) == FALSE) {
+		return NULL;
+	}
+
+	random = FALSE;
+	loop = FALSE;
+	rb_shell_player_get_playback_state (plugin->player, &random, &loop);
+
+	/* repeat is not supported */
+	repeat = FALSE;
+
+	return g_variant_new ("((iiii))", playing, random, repeat, loop);
+}
+
+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)
+
+{
+	GError *error = NULL;
+	gboolean ret;
+	if (g_strcmp0 (object_path, "/Player") != 0 ||
+	    g_strcmp0 (interface_name, mpris_iface_name) != 0) {
+		rb_debug ("?!");
+		return;
+	}
+
+	if (g_strcmp0 (method_name, "Next") == 0) {
+		ret = rb_shell_player_do_next (plugin->player, &error);
+		handle_result (invocation, ret, error);
+	} else if (g_strcmp0 (method_name, "Prev") == 0) {
+		ret = rb_shell_player_do_previous (plugin->player, &error);
+		handle_result (invocation, ret, error);
+	} else if ((g_strcmp0 (method_name, "Pause") == 0)
+		  || (g_strcmp0 (method_name, "PlayPause") == 0)) {
+		ret = rb_shell_player_playpause (plugin->player, TRUE, &error);
+		handle_result (invocation, ret, error);
+	} else if (g_strcmp0 (method_name, "Stop") == 0) {
+		rb_shell_player_stop (plugin->player);
+		handle_result (invocation, TRUE, NULL);
+	} else if (g_strcmp0 (method_name, "Play") == 0) {
+		gboolean playing;
+		g_object_get (plugin->player, "playing", &playing, NULL);
+		if (playing) {
+			ret = rb_shell_player_set_playing_time (plugin->player, 0, &error);
+		} else {
+			ret = rb_shell_player_playpause (plugin->player, TRUE, &error);
+		}
+		handle_result (invocation, ret, error);
+	} else if (g_strcmp0 (method_name, "Repeat") == 0) {
+		/* not actually supported */
+	} else if (g_strcmp0 (method_name, "GetStatus") == 0) {
+		GVariant *state;
+
+		state = get_player_state (plugin, &error);
+		if (state == NULL) {
+			handle_result (invocation, FALSE, error);
+		} else {
+			g_dbus_method_invocation_return_value (invocation, state);
+		}
+
+	} else if (g_strcmp0 (method_name, "GetCaps") == 0) {
+		/* values here are:
+		 * CAN_GO_NEXT: 1
+		 * CAN_GO_PREV: 2
+		 * CAN_PAUSE: 4
+		 * CAN_PLAY: 8
+		 * CAN_SEEK: 16
+		 * CAN_PROVIDE_METADATA: 32
+		 * CAN_HAS_TRACKLIST: 64
+		 */
+		g_dbus_method_invocation_return_value (invocation,
+						       g_variant_new ("i", 63));
+	} else if (g_strcmp0 (method_name, "GetMetadata") == 0) {
+		RhythmDBEntry *entry;
+		GVariantBuilder *builder;
+
+		builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
+
+		entry = rb_shell_player_get_playing_entry (plugin->player);
+		if (entry != NULL) {
+			build_track_metadata (plugin, builder, entry);
+		} else {
+			g_variant_builder_add (builder,
+					       "{sv}",
+					       "location",
+					       g_variant_new ("s", ""));
+		}
+
+		g_dbus_method_invocation_return_value (invocation,
+						       g_variant_new ("(a{sv})", builder));
+	} else if (g_strcmp0 (method_name, "VolumeGet") == 0) {
+		gdouble v;
+		ret = rb_shell_player_get_volume (plugin->player, &v, &error);
+		if (ret == FALSE) {
+			handle_result (invocation, ret, error);
+		} else {
+			int iv;
+			iv = (int)(v * 100.0);
+			g_dbus_method_invocation_return_value (invocation,
+							       g_variant_new ("(i)", iv));
+		}
+	} else if (g_strcmp0 (method_name, "VolumeSet") == 0) {
+		int iv;
+		gdouble v;
+		g_variant_get (parameters, "(i)", &iv);
+		v = ((gdouble)iv / 100.0);
+		ret = rb_shell_player_set_volume (plugin->player, v, &error);
+		handle_result (invocation, ret, error);
+
+	} else if (g_strcmp0 (method_name, "PositionGet") == 0) {
+		guint t;
+		ret = rb_shell_player_get_playing_time (plugin->player, &t, &error);
+		if (ret == FALSE) {
+			handle_result (invocation, ret, error);
+		} else {
+			g_dbus_method_invocation_return_value (invocation,
+							       g_variant_new ("(i)", t * 1000));
+		}
+	} else if (g_strcmp0 (method_name, "PositionSet") == 0) {
+		guint t;
+		g_variant_get (parameters, "(i)", &t);
+		ret = rb_shell_player_set_playing_time (plugin->player, t, &error);
+		handle_result (invocation, ret, error);
+	}
+}
+
+static const GDBusInterfaceVTable player_vtable =
+{
+	(GDBusInterfaceMethodCallFunc) handle_player_method_call,
+	NULL,
+	NULL
+};
+
+/* MPRIS signals */
+
+static void
+emit_status_change (RBMprisPlugin *plugin)
+{
+	GError *error = NULL;
+	GVariant *state;
+	state = get_player_state (plugin, &error);
+	if (state == NULL) {
+		g_warning ("Unable to emit MPRIS StatusChange signal: %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+	g_dbus_connection_emit_signal (plugin->connection,
+				       NULL,
+				       "/Player",
+				       mpris_iface_name,
+				       "StatusChange",
+				       state,
+				       &error);
+	if (error != NULL) {
+		g_warning ("Unable to emit MPRIS StatusChange signal: %s", error->message);
+		g_error_free (error);
+	}
+}
+
+static void
+play_order_changed_cb (GObject *object, GParamSpec *pspec, RBMprisPlugin *plugin)
+{
+	emit_status_change (plugin);
+}
+
+static void
+playing_changed_cb (RBShellPlayer *player, gboolean playing, RBMprisPlugin *plugin)
+{
+	emit_status_change (plugin);
+}
+
+static void
+emit_track_change (RBMprisPlugin *plugin, RhythmDBEntry *entry)
+{
+	GError *error = NULL;
+	GVariantBuilder *builder;
+	if (entry == NULL) {
+		return;
+	}
+
+	builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
+	build_track_metadata (plugin, builder, entry);
+
+	g_dbus_connection_emit_signal (plugin->connection,
+				       NULL,
+				       "/Player",
+				       mpris_iface_name,
+				       "TrackChange",
+				       g_variant_new ("(a{sv})", builder),
+				       &error);
+	if (error != NULL) {
+		g_warning ("Unable to emit MPRIS StatusChange signal: %s", error->message);
+		g_error_free (error);
+	}
+}
+
+static void
+playing_entry_changed_cb (RBShellPlayer *player, RhythmDBEntry *entry, RBMprisPlugin *plugin)
+{
+	emit_track_change (plugin, entry);
+}
+
+static void
+entry_extra_metadata_notify_cb (RhythmDB *db, RhythmDBEntry *entry, const char *field, GValue *metadata, RBMprisPlugin *plugin)
+{
+	if (entry == rb_shell_player_get_playing_entry (plugin->player))
+		emit_track_change (plugin, entry);
+}
+
+static void
+name_acquired_cb (GDBusConnection *connection, const char *name, RBMprisPlugin *plugin)
+{
+	GError *error = NULL;
+	GDBusNodeInfo *nodeinfo;
+	const GDBusInterfaceInfo *ifaceinfo;
+
+	plugin->connection = g_object_ref (connection);
+
+	/* register root object */
+	nodeinfo = g_dbus_node_info_new_for_xml (mpris_root_spec, &error);
+	if (error != NULL) {
+		g_warning ("Unable to read MPRIS root object specificiation: %s", error->message);
+		g_assert_not_reached ();
+		return;
+	}
+	ifaceinfo = g_dbus_node_info_lookup_interface (nodeinfo, mpris_iface_name);
+
+	plugin->root_id = g_dbus_connection_register_object (plugin->connection,
+							     "/",
+							     ifaceinfo,
+							     &root_vtable,
+							     plugin,
+							     NULL,
+							     &error);
+	if (error != NULL) {
+		g_warning ("unable to register MPRIS root object: %s", error->message);
+		g_error_free (error);
+	}
+
+	/* register fake tracklist object */
+	nodeinfo = g_dbus_node_info_new_for_xml (mpris_tracklist_spec, &error);
+	if (error != NULL) {
+		g_warning ("Unable to read MPRIS tracklist object specificiation: %s", error->message);
+		g_assert_not_reached ();
+		return;
+	}
+	ifaceinfo = g_dbus_node_info_lookup_interface (nodeinfo, mpris_iface_name);
+
+	plugin->tracklist_id = g_dbus_connection_register_object (plugin->connection,
+								  "/TrackList",
+								  ifaceinfo,
+								  &tracklist_vtable,
+								  plugin,
+								  NULL,
+								  &error);
+	if (error != NULL) {
+		g_warning ("unable to register MPRIS tracklist object: %s", error->message);
+		g_error_free (error);
+	}
+
+	/* register player object */
+	nodeinfo = g_dbus_node_info_new_for_xml (mpris_player_spec, &error);
+	if (error != NULL) {
+		g_warning ("Unable to read MPRIS player object specificiation: %s", error->message);
+		g_assert_not_reached ();
+		return;
+	}
+	ifaceinfo = g_dbus_node_info_lookup_interface (nodeinfo, mpris_iface_name);
+	plugin->player_id = g_dbus_connection_register_object (plugin->connection,
+							       "/Player",
+							       ifaceinfo,
+							       &player_vtable,
+							       plugin,
+							       NULL,
+							       &error);
+	if (error != NULL) {
+		g_warning ("Unable to register MPRIS player object: %s", error->message);
+		g_error_free (error);
+	}
+
+	/* connect signal handlers for stuff */
+	g_signal_connect_object (plugin->player,
+				 "notify::play-order",
+				 G_CALLBACK (play_order_changed_cb),
+				 plugin, 0);
+	g_signal_connect_object (plugin->player,
+				 "playing-changed",
+				 G_CALLBACK (playing_changed_cb),
+				 plugin, 0);
+	g_signal_connect_object (plugin->player,
+				 "playing-song-changed",
+				 G_CALLBACK (playing_entry_changed_cb),
+				 plugin, 0);
+	g_signal_connect_object (plugin->db,
+				 "entry-extra-metadata-notify",
+				 G_CALLBACK (entry_extra_metadata_notify_cb),
+				 plugin, 0);
+}
+
+static void
+name_lost_cb (GDBusConnection *connection, const char *name, RBMprisPlugin *plugin)
+{
+	if (plugin->root_id != 0) {
+		g_dbus_connection_unregister_object (plugin->connection, plugin->root_id);
+		plugin->root_id = 0;
+	}
+	if (plugin->tracklist_id != 0) {
+		g_dbus_connection_unregister_object (plugin->connection, plugin->tracklist_id);
+		plugin->tracklist_id = 0;
+	}
+	if (plugin->player_id != 0) {
+		g_dbus_connection_unregister_object (plugin->connection, plugin->player_id);
+		plugin->player_id = 0;
+	}
+
+	/* probably remove signal handlers? */
+
+	if (plugin->connection != NULL) {
+		g_object_unref (plugin->connection);
+		plugin->connection = NULL;
+	}
+}
+
+static void
+impl_activate (RBPlugin *bplugin,
+	       RBShell *shell)
+{
+	RBMprisPlugin *plugin;
+
+	rb_debug ("activating MPRIS plugin");
+
+	plugin = RB_MPRIS_PLUGIN (bplugin);
+	g_object_get (shell,
+		      "shell-player", &plugin->player,
+		      "db", &plugin->db,
+		      NULL);
+	plugin->shell = g_object_ref (shell);
+
+	plugin->name_own_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+					      "org.mpris.rhythmbox",
+					      G_BUS_NAME_OWNER_FLAGS_NONE,
+					      NULL,
+					      (GBusNameAcquiredCallback) name_acquired_cb,
+					      (GBusNameLostCallback) name_lost_cb,
+					      g_object_ref (plugin),
+					      g_object_unref);
+}
+
+static void
+impl_deactivate	(RBPlugin *bplugin,
+		 RBShell *shell)
+{
+	RBMprisPlugin *plugin;
+
+	plugin = RB_MPRIS_PLUGIN (bplugin);
+	if (plugin->player != NULL) {
+		g_object_unref (plugin->player);
+		plugin->player = NULL;
+	}
+	if (plugin->shell != NULL) {
+		g_object_unref (plugin->shell);
+		plugin->shell = NULL;
+	}
+	if (plugin->db != NULL) {
+		g_object_unref (plugin->db);
+		plugin->db = NULL;
+	}
+
+	if (plugin->name_own_id > 0) {
+		g_bus_unown_name (plugin->name_own_id);
+		plugin->name_own_id = 0;
+	}
+}
+
+
+static void
+rb_mpris_plugin_class_init (RBMprisPluginClass *klass)
+{
+	RBPluginClass *plugin_class = RB_PLUGIN_CLASS (klass);
+
+	plugin_class->activate = impl_activate;
+	plugin_class->deactivate = impl_deactivate;
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]