diff --git a/lib/Makefile.am b/lib/Makefile.am
index 6cd69c2..336b2f4 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -35,7 +35,11 @@ librb_la_SOURCES =					\
 	rb-string-value-map.h				\
 	rb-async-queue-watch.c				\
 	rb-async-queue-watch.h				\
-	rb-soup-compat.h
+	rb-soup-compat.h				\
+	rb-dbus-tree-iter.c				\
+	rb-dbus-tree-iter.h				\
+	rb-dbus-tree-model.c				\
+	rb-dbus-tree-model.h
 
 if WITH_INTERNAL_GSEQUENCE
 librb_la_SOURCES += gsequence.c gsequence.h
@@ -49,13 +53,15 @@ INCLUDES =						\
 	-I$(top_srcdir) 				\
 	-I$(top_srcdir)/widgets				\
 	$(SOUP_CFLAGS)					\
+	$(DBUS_CFLAGS)					\
 	$(RHYTHMBOX_CFLAGS)
 
-librb_la_LIBADD = $(SOUP_LIBS)
+librb_la_LIBADD = 					\
+	$(DBUS_LIBS)					\
+	$(SOUP_LIBS)
 
 BUILT_SOURCES=
 
-CLEAN_FILES = $(BUILT_SOURCES)
 
 rb-marshal.c: rb-marshal.list
 	@GLIB_GENMARSHAL@ --prefix=rb_marshal $(srcdir)/rb-marshal.list --header --body > rb-marshal.c
@@ -73,16 +79,26 @@ rhythmbox-unset-star-inline.h: $(top_srcdir)/data/icons/hicolor/16x16/status/rhy
 rhythmbox-no-star-inline.h: $(top_srcdir)/data/icons/hicolor/16x16/status/rhythmbox-no-star.png
 	@GDK_PIXBUF_CSOURCE@ --raw --name=rhythmbox_no_star_inline $< > $@
 
+# dbus interface(s)
+rb-dbus-tree-iter-glue.h: rb-dbus-tree-iter.xml Makefile
+	$(LIBTOOL) --mode=execute $(DBUS_GLIB_BIN)/dbus-binding-tool --prefix=rb_dbus_tree_iter --mode=glib-server --output=$@ $<
+
 
 BUILT_SOURCES += 				\
 	rb-marshal.c 				\
 	rb-marshal.h 				\
 	rhythmbox-set-star-inline.h		\
 	rhythmbox-unset-star-inline.h		\
-	rhythmbox-no-star-inline.h
+	rhythmbox-no-star-inline.h		\
+	rb-dbus-tree-iter-glue.h
 
 librb_la_SOURCES += $(BUILT_SOURCES)
 
 librb_la_LDFLAGS = -export-dynamic
 
-EXTRA_DIST = rb-marshal.list
+EXTRA_DIST = 					\
+	rb-marshal.list				\
+	rb-dbus-tree-iter.xml
+
+CLEAN_FILES = $(BUILT_SOURCES)
+
diff --git a/lib/rb-dbus-tree-iter.c b/lib/rb-dbus-tree-iter.c
new file mode 100644
index 0000000..6c07248
--- /dev/null
+++ b/lib/rb-dbus-tree-iter.c
@@ -0,0 +1,688 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2008  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 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grants 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.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "rb-dbus-tree-iter.h"
+#include "rb-debug.h"
+
+/* share one proxy for the bus between all iterator instances */
+static DBusGProxy *bus_proxy = NULL;
+
+/**
+ * SECTION:rb-dbus-tree-iter
+ * @short_description: DBus-exported iterator for GtkTreeModels
+ *
+ * Provides access to the contents of a GtkTreeModel over DBus.  To expose its
+ * contents, the model (or an object that owns it) must provide a DBus method
+ * that creates an iterator object and returns its path.
+ *
+ * DBus clients own references on the iterator objects they use.  Short-lived
+ * clients don't need to worry about dropping their references, as they will be
+ * cleaned up automatically when the client drops off the bus.  Otherwise, the
+ * client must call the Unref method on the iterator when it no longer needs it.
+ * Obviously the client should not call iterator methods after dropping its reference,
+ * but the iterator does not enforce that.
+ *
+ * The iterator object is initialized with a GtkTreeIter.  Iterable objects typically
+ * match the methods provided by GtkTreeModel for creating iterators, with GetIterFirst
+ * and GetIterNth methods.  In some cases, methods that search the model and return
+ * an iterator pointing to the result row could be useful.
+ *
+ * The iterator provides three methods for accessing the data in the model, and (like
+ * GtkTreeModel) none for modifying it.  The GetValue method returns the value from a
+ * single column of a single row.  The Get method returns the values from a set of
+ * columns in a single row.  The GetBatch method (when implemented) will return values
+ * from a set of columns in a number of consecutive rows, and then advance the iterator
+ * to point to the first row after that.
+ */
+
+#define OBJECT_PATH_FORMAT	"/org/gnome/Rhythmbox/TreeIter/%p"
+
+enum
+{
+	PROP_0,
+	PROP_CONNECTION,
+	PROP_TREE_MODEL,
+	PROP_DBUS_TREE_MODEL,
+	PROP_COLUMN_ENUM_TYPE,
+	PROP_TREE_ITER
+};
+
+static void	rb_dbus_tree_iter_class_init (RBDBusTreeIterClass *klass);
+static void	rb_dbus_tree_iter_init (RBDBusTreeIter *iter);
+
+struct _RBDBusTreeIterPrivate
+{
+	DBusGConnection *connection;
+	char *object_path;
+
+	GtkTreeModel *tree_model;
+	RBDBusTreeModel *dbus_tree_model;
+	GEnumClass *column_enum;
+
+	GtkTreeIter tree_iter;
+	GSList *references;
+};
+
+G_DEFINE_TYPE (RBDBusTreeIter, rb_dbus_tree_iter, G_TYPE_OBJECT)
+
+/* reference counting: */
+
+static void
+_impl_ref (RBDBusTreeIter *iter, const char *sender, gboolean ref_gobject)
+{
+	iter->priv->references = g_slist_prepend (iter->priv->references, g_strdup (sender));
+	if (ref_gobject)
+		g_object_ref (iter);
+}
+
+static gboolean
+_impl_unref (RBDBusTreeIter *iter, const char *sender)
+{
+	GSList *item;
+	
+	item = g_slist_find_custom (iter->priv->references, sender, (GCompareFunc)strcmp);
+	if (item != NULL) {
+		g_free (item->data);
+		iter->priv->references = g_slist_delete_link (iter->priv->references, item);
+		g_object_unref (iter);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+name_owner_changed_cb (DBusGProxy *proxy,
+		       const char *name,
+		       const char *prev_owner,
+		       const char *new_owner,
+		       RBDBusTreeIter *iter)
+{
+	/* ignore it unless it's an ownership loss */
+	if (strcmp (new_owner, "") != 0) {
+		return;
+	}
+
+	/* take a temporary ref so we can still use the object name after dropping the client reference */
+	g_object_ref (iter);
+	if (_impl_unref (iter, name)) {
+		rb_debug ("cleaned up reference to iter %s from owner %s",
+			  iter->priv->object_path,
+			  name);
+	}
+	g_object_unref (iter);
+}
+
+
+/**
+ * rb_dbus_tree_iter_new:
+ * @connection: 	DBus connection to register the iterator on
+ * @model:		The #GtkTreeModel to iterate
+ * @dbus_model:		An optional #RBDBusTreeModel to filter the model contents
+ * @column_enum:	An optional #GEnumClass giving names for the model columns
+ * @iter:		The initial #GtkTreeIter state for the iterator
+ * @invocation:		DBus method invocation context creating the iterator
+ *
+ * Creates a new tree model iterator and registers it on the DBus connection.
+ * If an invocation context is supplied, the sender from the context will hold
+ * the initial reference on the iterator.  Otherwise, no one will, and it'll
+ * live forever.
+ *
+ * The DBus tree model filter is required if the raw model contents are not
+ * safe to send over DBus, or if they're just not very interesting as-is.
+ * Either a separate object can be provided that implements the 
+ * #RBDBusTreeModel interface, or the #GtkTreeModel implementation can also
+ * implement #RBDBusTreeModel.
+ *
+ * If no DBus tree model filter is required, a GEnumClass can be provided to give
+ * names for the columns in the model.  If neither is provided, clients will not
+ * get names for the columns.
+ *
+ * Return value: the new iterator object
+ */
+RBDBusTreeIter *
+rb_dbus_tree_iter_new (DBusGConnection *connection,
+		       GtkTreeModel *model,
+		       RBDBusTreeModel *dbus_model,
+		       GType column_enum_type,
+		       GtkTreeIter *iter,
+		       DBusGMethodInvocation *invocation)
+{
+	RBDBusTreeIter *obj;
+	char *sender;
+
+	obj = RB_DBUS_TREE_ITER (g_object_new (RB_TYPE_DBUS_TREE_ITER,
+					       "connection", connection,
+					       "tree-model", model,
+					       "dbus-tree-model", dbus_model,
+					       "column-enum-type", column_enum_type,
+					       "tree-iter", iter,
+					       NULL));
+
+	if (invocation != NULL) {
+		sender = dbus_g_method_get_sender (invocation);
+		_impl_ref (obj, sender, FALSE);
+		g_free (sender);
+	} else {
+		rb_debug ("creating a dbus tree iter without an owner.  this will leak.");
+	}
+
+	return obj;
+}
+
+/**
+ * rb_dbus_tree_iter_get_path:
+ * @iter:		Iterator object
+ *
+ * Return value: the DBus object path of the iterator
+ */
+const char *
+rb_dbus_tree_iter_get_path (RBDBusTreeIter *iter)
+{
+	return iter->priv->object_path;
+}
+
+/* DBus interface methods: */
+
+/* whitelist for types we can safely return over DBus */
+gboolean
+rb_dbus_type_safe (GType type)
+{
+	switch (type) {
+	case G_TYPE_CHAR:
+	case G_TYPE_UCHAR:
+	case G_TYPE_BOOLEAN:
+	case G_TYPE_INT:
+	case G_TYPE_UINT:
+	case G_TYPE_LONG:
+	case G_TYPE_ULONG:
+	case G_TYPE_INT64:
+	case G_TYPE_UINT64:
+	case G_TYPE_ENUM:		/* really? */
+	case G_TYPE_FLOAT:
+	case G_TYPE_DOUBLE:
+	case G_TYPE_STRING:
+		return TRUE;
+
+	case G_TYPE_NONE:
+	case G_TYPE_INTERFACE:
+	case G_TYPE_POINTER:
+	case G_TYPE_BOXED:
+	case G_TYPE_PARAM:
+	case G_TYPE_OBJECT:		/* risky, so require a RBDBusTreeModel to explicitly return them */
+	default:
+		return FALSE;
+	}
+}
+
+static gboolean
+rb_dbus_tree_iter_get_columns (RBDBusTreeIter *iter, char ***names, GPtrArray **types, GError **error)
+{
+	int n;
+	int i;
+
+	if (iter->priv->dbus_tree_model) {
+		RBDBusTreeModelIface *iface = RB_DBUS_TREE_MODEL_GET_IFACE (iter->priv->dbus_tree_model);
+		return (iface->get_columns) (iter->priv->dbus_tree_model, names, types, error);
+	}
+
+	n = gtk_tree_model_get_n_columns (iter->priv->tree_model);
+
+	*types = g_ptr_array_new ();
+	*names = g_new0 (char *, n+1);
+
+	for (i = 0; i < n; i++) {
+		GType raw_type;
+		GValue *v;
+
+		v = g_new0 (GValue, 1);
+		raw_type = gtk_tree_model_get_column_type (iter->priv->tree_model, i);
+		if (rb_dbus_type_safe (raw_type)) {
+			g_value_init (v, raw_type);
+		} else {
+			g_value_init (v, G_TYPE_INT);
+		}
+
+		g_ptr_array_add (*types, v);
+
+		/* I'm not sure we can do anything intelligent here.. */
+		if (iter->priv->column_enum != NULL) {
+			GEnumValue *ev;
+			ev = g_enum_get_value (iter->priv->column_enum, i);
+			if (ev != NULL) {
+				(*names)[i] = g_strdup (ev->value_nick);
+			}
+		}
+
+		/* use an empty string if we still don't have a name */
+		if ((*names)[i] == NULL) {
+			(*names)[i] = g_strdup ("");
+		}
+	}
+	return TRUE;
+}
+
+static gboolean
+rb_dbus_tree_iter_get_value (RBDBusTreeIter *iter, int column, GValue *value, GError **error)
+{
+	if (iter->priv->dbus_tree_model) {
+		RBDBusTreeModelIface *iface = RB_DBUS_TREE_MODEL_GET_IFACE (iter->priv->dbus_tree_model);
+		return (iface->get_value) (iter->priv->dbus_tree_model, &iter->priv->tree_iter, column, value, error);
+	}
+
+	gtk_tree_model_get_value (iter->priv->tree_model,
+				  &iter->priv->tree_iter,
+				  column,
+				  value);
+
+	/* if the result type isn't safe to return over DBus, replace it */
+	if (rb_dbus_type_safe (G_VALUE_TYPE (value)) == FALSE) {
+		g_value_unset (value);
+		g_value_init (value, G_TYPE_INT);
+	}
+
+	return TRUE;
+}
+
+static void
+cleanup_value_array (GPtrArray *values)
+{
+	g_ptr_array_foreach (values, (GFunc)g_value_unset, NULL);
+	g_ptr_array_foreach (values, (GFunc)g_free, NULL);
+	g_ptr_array_free (values, TRUE);
+}
+
+static gboolean
+rb_dbus_tree_iter_get (RBDBusTreeIter *iter, GArray *columns, GPtrArray **values, GError **error)
+{
+	int i;
+	*values = g_ptr_array_new ();
+	for (i = 0; i < columns->len; i++) {
+		GValue *value;
+		
+		value = g_new0 (GValue, 1);
+		if (rb_dbus_tree_iter_get_value (iter, g_array_index (columns, int, i), value, error) == FALSE) {
+			g_free (value);
+			cleanup_value_array (*values);
+			return FALSE;
+		}
+
+		g_ptr_array_add (*values, value);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+rb_dbus_tree_iter_get_batch (RBDBusTreeIter *iter, int rows, GArray *columns, GPtrArray **values, gboolean *more, GError **error)
+{
+	int r;
+	gboolean ret = TRUE;
+
+	*values = g_ptr_array_new ();
+	*more = TRUE;
+	for (r = 0; r < rows; r++) {
+		GPtrArray *row_values;
+		if (rb_dbus_tree_iter_get (iter, columns, &row_values, error) == FALSE) {
+			cleanup_value_array (row_values);
+			ret = FALSE;
+			break;
+		}
+		g_ptr_array_add (*values, row_values);
+
+		if (gtk_tree_model_iter_next (iter->priv->tree_model, &iter->priv->tree_iter) == FALSE) {
+			*more = FALSE;
+			break;
+		}
+	}
+
+	if (ret == FALSE) {
+		g_ptr_array_foreach (*values, (GFunc)cleanup_value_array, NULL);
+		g_ptr_array_free (*values, TRUE);
+		*values = NULL;
+	}
+	return ret;
+}
+
+static gboolean
+rb_dbus_tree_iter_next (RBDBusTreeIter *iter, GError **error)
+{
+	return gtk_tree_model_iter_next (iter->priv->tree_model,
+					 &iter->priv->tree_iter);
+}
+
+static gboolean
+rb_dbus_tree_iter_has_child (RBDBusTreeIter *iter, GError **error)
+{
+	return gtk_tree_model_iter_has_child (iter->priv->tree_model,
+					      &iter->priv->tree_iter);
+}
+
+static int
+rb_dbus_tree_iter_n_children (RBDBusTreeIter *iter, GError **error)
+{
+	return gtk_tree_model_iter_n_children (iter->priv->tree_model,
+					       &iter->priv->tree_iter);
+}
+
+static void
+rb_dbus_tree_iter_children (RBDBusTreeIter *iter, DBusGMethodInvocation *invocation)
+{
+	GtkTreeIter child_iter;
+	RBDBusTreeIter *child_iter_obj;
+	GType enum_type = G_TYPE_NONE;
+
+	if (iter->priv->column_enum != NULL) {
+		enum_type = G_ENUM_CLASS_TYPE (iter->priv->column_enum);
+	}
+
+	gtk_tree_model_iter_children (iter->priv->tree_model,
+				      &child_iter,
+				      &iter->priv->tree_iter);
+	child_iter_obj = rb_dbus_tree_iter_new (iter->priv->connection,
+						iter->priv->tree_model,
+						iter->priv->dbus_tree_model,
+						enum_type,
+						&child_iter,
+						invocation);
+	dbus_g_method_return (invocation, child_iter_obj->priv->object_path);
+}
+
+static void
+rb_dbus_tree_iter_nth_child (RBDBusTreeIter *iter, int n, DBusGMethodInvocation *invocation)
+{
+	GtkTreeIter child_iter;
+	RBDBusTreeIter *child_iter_obj;
+	GType enum_type = G_TYPE_NONE;
+
+	if (iter->priv->column_enum != NULL) {
+		enum_type = G_ENUM_CLASS_TYPE (iter->priv->column_enum);
+	}
+
+	gtk_tree_model_iter_nth_child (iter->priv->tree_model,
+				       &child_iter,
+				       &iter->priv->tree_iter,
+				       n);
+	child_iter_obj = rb_dbus_tree_iter_new (iter->priv->connection,
+						iter->priv->tree_model,
+						iter->priv->dbus_tree_model,
+						enum_type,
+						&child_iter,
+						invocation);
+	dbus_g_method_return (invocation, child_iter_obj->priv->object_path);
+}
+
+static void
+rb_dbus_tree_iter_parent (RBDBusTreeIter *iter, DBusGMethodInvocation *invocation)
+{
+	GtkTreeIter parent_iter;
+	RBDBusTreeIter *parent_iter_obj;
+	GType enum_type = G_TYPE_NONE;
+
+	if (iter->priv->column_enum != NULL) {
+		enum_type = G_ENUM_CLASS_TYPE (iter->priv->column_enum);
+	}
+
+	gtk_tree_model_iter_parent (iter->priv->tree_model,
+				    &parent_iter,
+				    &iter->priv->tree_iter);
+	parent_iter_obj = rb_dbus_tree_iter_new (iter->priv->connection,
+						 iter->priv->tree_model,
+						 iter->priv->dbus_tree_model,
+						 enum_type,
+						 &parent_iter,
+						 invocation);
+	dbus_g_method_return (invocation, parent_iter_obj->priv->object_path);
+}
+
+static void
+rb_dbus_tree_iter_ref (RBDBusTreeIter *iter, DBusGMethodInvocation *context)
+{
+	char *sender;
+
+	sender = dbus_g_method_get_sender (context);
+	_impl_ref (iter, sender, TRUE);
+	g_free (sender);
+
+	dbus_g_method_return (context);
+}
+
+static void
+rb_dbus_tree_iter_unref (RBDBusTreeIter *iter, DBusGMethodInvocation *context)
+{
+	char *sender;
+
+	sender = dbus_g_method_get_sender (context);
+	if (_impl_unref (iter, sender) == FALSE) {
+		g_warning ("Unable to find a reference for sender %s on dbus tree iter %s",
+			   sender, iter->priv->object_path);
+	}
+
+	g_free (sender);
+	dbus_g_method_return (context);
+}
+
+#include "rb-dbus-tree-iter-glue.h"
+
+/* GObjectification */
+
+static void
+set_dbus_connection (RBDBusTreeIter *iter, DBusGConnection *connection)
+{
+	static gsize init_bus_proxy_once = 0;
+
+	iter->priv->connection = connection;
+	dbus_g_connection_ref (iter->priv->connection);
+
+	/* create one bus proxy to share between all iterators */
+	if (g_once_init_enter (&init_bus_proxy_once)) {
+		bus_proxy = dbus_g_proxy_new_for_name (connection,
+						       DBUS_SERVICE_DBUS,
+						       DBUS_PATH_DBUS,
+						       DBUS_INTERFACE_DBUS);
+		dbus_g_proxy_add_signal (bus_proxy,
+					 "NameOwnerChanged",
+					 G_TYPE_STRING,
+					 G_TYPE_STRING,
+					 G_TYPE_STRING,
+					 G_TYPE_INVALID);
+		g_once_init_leave (&init_bus_proxy_once, 1);
+	}
+
+	/* each iterator listens for name owner changes to clean up references whose owners vanish */
+	dbus_g_proxy_connect_signal (bus_proxy,
+				     "NameOwnerChanged",
+				     G_CALLBACK (name_owner_changed_cb),
+				     iter,
+				     NULL);
+
+	dbus_g_connection_register_g_object (iter->priv->connection,
+					     iter->priv->object_path,
+					     G_OBJECT (iter));
+}
+
+static void
+impl_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec)
+{
+	RBDBusTreeIter *iter = RB_DBUS_TREE_ITER (object);
+	switch (param_id) {
+	case PROP_CONNECTION:
+		g_value_set_pointer (value, iter->priv->connection);
+		break;
+	case PROP_TREE_MODEL:
+		g_value_set_object (value, iter->priv->tree_model);
+		break;
+	case PROP_DBUS_TREE_MODEL:
+		g_value_set_object (value, iter->priv->dbus_tree_model);
+		break;
+	case PROP_COLUMN_ENUM_TYPE:
+		if (iter->priv->column_enum) {
+			g_value_set_gtype (value, G_ENUM_CLASS_TYPE (iter->priv->column_enum));
+		} else {
+			g_value_set_gtype (value, G_TYPE_NONE);
+		}
+		break;
+	case PROP_TREE_ITER:
+		g_value_set_pointer (value, &iter->priv->tree_iter);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec)
+{
+	RBDBusTreeIter *iter = RB_DBUS_TREE_ITER (object);
+	switch (param_id) {
+	case PROP_CONNECTION:
+		set_dbus_connection (iter, g_value_get_pointer (value));
+		break;
+	case PROP_TREE_MODEL:
+		iter->priv->tree_model = g_value_dup_object (value);
+		break;
+	case PROP_DBUS_TREE_MODEL:
+		iter->priv->dbus_tree_model = g_value_dup_object (value);
+		break;
+	case PROP_COLUMN_ENUM_TYPE:
+		{
+			GType enum_type = g_value_get_gtype (value);
+			if (enum_type != G_TYPE_NONE) {
+				iter->priv->column_enum = G_ENUM_CLASS (g_type_class_ref (enum_type));
+			}
+		}
+		break;
+	case PROP_TREE_ITER:
+		{
+			GtkTreeIter *src = g_value_get_pointer (value);
+			iter->priv->tree_iter = *src;
+		}
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_dispose (GObject *object)
+{
+	RBDBusTreeIter *iter = RB_DBUS_TREE_ITER (object);
+
+	dbus_g_proxy_disconnect_signal (bus_proxy,
+					"NameOwnerChanged",
+					G_CALLBACK (name_owner_changed_cb),
+					iter);
+
+	if (iter->priv->connection != NULL) {
+		dbus_g_connection_unref (iter->priv->connection);
+		iter->priv->connection = NULL;
+	}
+	if (iter->priv->tree_model != NULL) {
+		g_object_unref (iter->priv->tree_model);
+		iter->priv->tree_model = NULL;
+	}
+	if (iter->priv->dbus_tree_model != NULL) {
+		g_object_unref (iter->priv->dbus_tree_model);
+		iter->priv->dbus_tree_model = NULL;
+	}
+	if (iter->priv->column_enum != NULL) {
+		g_type_class_unref (iter->priv->column_enum);
+		iter->priv->column_enum = NULL;
+	}
+	g_free (iter->priv->object_path);
+	iter->priv->object_path = NULL;
+	G_OBJECT_CLASS (rb_dbus_tree_iter_parent_class)->dispose (object);
+}
+
+static void
+rb_dbus_tree_iter_init (RBDBusTreeIter *iter)
+{
+	iter->priv = G_TYPE_INSTANCE_GET_PRIVATE (iter, RB_TYPE_DBUS_TREE_ITER, RBDBusTreeIterPrivate);
+
+	iter->priv->object_path = g_strdup_printf (OBJECT_PATH_FORMAT, iter);
+}
+
+static void
+rb_dbus_tree_iter_class_init (RBDBusTreeIterClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = impl_dispose;
+	object_class->set_property = impl_set_property;
+	object_class->get_property = impl_get_property;
+
+	g_object_class_install_property (object_class,
+					 PROP_CONNECTION,
+					 g_param_spec_pointer ("connection",
+						 	       "connection",
+							       "DBus connection",
+							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_TREE_MODEL,
+					 g_param_spec_object ("tree-model",
+						 	      "tree model",
+							      "GtkTreeModel implementation",
+							      G_TYPE_OBJECT,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_DBUS_TREE_MODEL,
+					 g_param_spec_object ("dbus-tree-model",
+						 	      "dbus tree model",
+							      "RBDBusTreeModel implementation",
+							      G_TYPE_OBJECT,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_COLUMN_ENUM_TYPE,
+					 g_param_spec_gtype ("column-enum-type",
+						 	     "column enum type",
+							     "GType for an enum containing column names",
+							     G_TYPE_NONE,		/* has to be G_TYPE_NONE to allow no type to be specified */
+							     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_TREE_ITER,
+					 g_param_spec_pointer ("tree-iter",
+						 	       "tree-iter",
+							       "Initial GtkTreeIter",
+							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_type_class_add_private (klass, sizeof (RBDBusTreeIterPrivate));
+
+	dbus_g_object_type_install_info (RB_TYPE_DBUS_TREE_ITER,
+					 &dbus_glib_rb_dbus_tree_iter_object_info);
+}
+
diff --git a/lib/rb-dbus-tree-iter.h b/lib/rb-dbus-tree-iter.h
new file mode 100644
index 0000000..1004204
--- /dev/null
+++ b/lib/rb-dbus-tree-iter.h
@@ -0,0 +1,77 @@
+/*
+ *  Copyright (C) 2008  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 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grants 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
+ */
+
+#ifndef RB_DBUS_TREE_ITER_H
+#define RB_DBUS_TREE_ITER_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "rb-dbus-tree-model.h"
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_DBUS_TREE_ITER			(rb_dbus_tree_iter_get_type ())
+#define RB_DBUS_TREE_ITER(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_DBUS_TREE_ITER, RBDBusTreeIter))
+#define RB_DBUS_TREE_ITER_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_DBUS_TREE_ITER, RBDBusTreeIterClass))
+#define RB_IS_DBUS_TREE_ITER(o)			(G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_DBUS_TREE_ITER))
+#define RB_IS_DBUS_TREE_ITER_CLASS(k)		(G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_DBUS_TREE_ITER))
+#define RB_DBUS_TREE_ITER_GET_CLASS(o)		(G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_DBUS_TREE_ITER, RBDBusTreeIterClass))
+
+typedef struct _RBDBusTreeIter RBDBusTreeIter;
+typedef struct _RBDBusTreeIterClass RBDBusTreeIterClass;
+typedef struct _RBDBusTreeIterPrivate RBDBusTreeIterPrivate;
+
+struct _RBDBusTreeIter
+{
+	GObject parent;
+	RBDBusTreeIterPrivate *priv;
+};
+
+struct _RBDBusTreeIterClass
+{
+	GObjectClass parent_class;
+};
+
+GType		rb_dbus_tree_iter_get_type 	(void);
+
+RBDBusTreeIter *rb_dbus_tree_iter_new 		(DBusGConnection *connection,
+						 GtkTreeModel *model,
+						 RBDBusTreeModel *dbus_model,
+						 GType column_enum_type,
+						 GtkTreeIter *iter,
+						 DBusGMethodInvocation *invocation);
+
+const char *	rb_dbus_tree_iter_get_path 	(RBDBusTreeIter *iter);
+
+/* would go in rb-dbus-util or similar, if such a thing existed */
+gboolean	rb_dbus_type_safe		(GType type);
+
+G_END_DECLS
+
+#endif /* RB_DBUS_TREE_ITER_H */
+
diff --git a/lib/rb-dbus-tree-iter.xml b/lib/rb-dbus-tree-iter.xml
new file mode 100644
index 0000000..a12f8e4
--- /dev/null
+++ b/lib/rb-dbus-tree-iter.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/">
+
+  <!--
+    interface for tree model iterators over dbus.
+
+    tree models that want to be iterated over dbus can provide
+    'IterFirst' and/or 'IterNthChild' methods that create an iterator
+    object and return its path.
+
+    iterator objects are inherently bound to the model they iterate, so
+    there is no need for a 'model' argument to any of the methods.
+
+    the 'GetBatch' method returns values from the next n rows and moves
+    the iterator past them.  this saves many round trips.  it's only intended
+    for use with list-like models, but as a slight concession, it will only
+    return data up to the next row with children.
+
+    when the client is done with the iterator, it calls Unref, which
+    will unref the iterator object on the server side, which should remove
+    it from the bus.
+
+    the Children and NthChild methods return new iterator objects, which
+    must themselves be unreffed when no longer needed.  these objects are
+    independent of the iterator they were created from.
+  -->
+      
+  <interface name="org.gnome.Rhythmbox.TreeIter">
+
+    <method name="GetColumns">
+      <arg type="as" direction="out" name="names"/>
+      <arg type="av" direction="out" name="types"/>
+    </method>
+
+    <method name="GetValue">
+      <arg type="i" name="column"/>
+      <arg type="v" direction="out" name="value"/>
+    </method>
+
+    <method name="Get">
+      <arg type="ai" name="columns"/>
+      <arg type="av" direction="out" name="values"/>
+    </method>
+
+    <method name="GetBatch">
+      <arg type="i" name="rows"/>
+      <arg type="ai" name="columns"/>
+      <arg type="aav" direction="out" name="values"/>
+      <arg type="b" direction="out" name="more"/>
+    </method>
+
+    <method name="Next">
+      <arg type="b" direction="out" name="result">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <method name="HasChild">
+      <arg type="b" direction="out" name="result">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+      
+    <method name="NChildren">
+      <arg type="i" direction="out" name="result">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <method name="Children">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="o" direction="out" name="iter">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <method name="NthChild">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="i" name="n"/>
+      <arg type="o" direction="out" name="iter">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <method name="Parent">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="o" direction="out" name="iter">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <!-- these are async only because they need to access the caller info -->
+    <method name="Ref">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+    </method>
+    <method name="Unref">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+    </method>
+    
+  </interface>
+</node>
+
diff --git a/lib/rb-dbus-tree-model.c b/lib/rb-dbus-tree-model.c
new file mode 100644
index 0000000..aec1ad8
--- /dev/null
+++ b/lib/rb-dbus-tree-model.c
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2008  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 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grants 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 "rb-dbus-tree-model.h"
+
+static void
+rb_dbus_tree_model_interface_init (RBDBusTreeModelIface *iface)
+{
+	/* nothing? */
+}
+
+GType
+rb_dbus_tree_model_get_type (void)
+{
+	static GType our_type = 0;
+
+	if (!our_type) {
+		static const GTypeInfo our_info = {
+			sizeof (RBDBusTreeModelIface),
+			NULL,	/* base_init */
+			NULL,	/* base_finalize */
+			(GClassInitFunc)rb_dbus_tree_model_interface_init,
+			NULL,	/* class_finalize */
+			NULL,	/* class_data */
+			0,
+			0,
+			NULL
+		};
+
+		our_type = g_type_register_static (G_TYPE_INTERFACE, "RBDBusTreeModel", &our_info, 0);
+	}
+
+	return our_type;
+}
+
diff --git a/lib/rb-dbus-tree-model.h b/lib/rb-dbus-tree-model.h
new file mode 100644
index 0000000..924a6e5
--- /dev/null
+++ b/lib/rb-dbus-tree-model.h
@@ -0,0 +1,66 @@
+/*
+ *  Copyright (C) 2008  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 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grants 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
+ */
+
+#ifndef RB_DBUS_TREE_MODEL_H
+#define RB_DBUS_TREE_MODEL_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_DBUS_TREE_MODEL			(rb_dbus_tree_model_get_type ())
+#define RB_DBUS_TREE_MODEL(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_DBUS_TREE_MODEL, RBDBusTreeModel))
+#define RB_IS_DBUS_TREE_MODEL(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_DBUS_TREE_MODEL))
+#define RB_DBUS_TREE_MODEL_GET_IFACE(o)		(G_TYPE_INSTANCE_GET_INTERFACE ((o), RB_TYPE_DBUS_TREE_MODEL, RBDBusTreeModelIface))
+
+typedef struct _RBDBusTreeModel RBDBusTreeModel;
+typedef struct _RBDBusTreeModelIface RBDBusTreeModelIface;
+
+struct _RBDBusTreeModelIface
+{
+	GTypeInterface g_iface;
+
+	gboolean	(*get_columns)		(RBDBusTreeModel *model,
+						 char ***names,
+						 GPtrArray **types,
+						 GError **error);
+	gboolean	(*get_value)		(RBDBusTreeModel *model,
+						 GtkTreeIter *iter,
+						 int column,
+						 GValue *value,
+						 GError **error);
+};
+
+
+GType		rb_dbus_tree_model_get_type 	(void);
+
+G_END_DECLS
+
+#endif /* RB_DBUS_TREE_MODEL_H */
+
+
diff --git a/lib/rb-marshal.list b/lib/rb-marshal.list
index 6285e7d..b2440ee 100644
--- a/lib/rb-marshal.list
+++ b/lib/rb-marshal.list
@@ -31,6 +31,7 @@ VOID:POINTER,UINT
 VOID:POINTER,ULONG
 VOID:STRING,DOUBLE
 VOID:STRING,INT
+VOID:STRING,STRING,BOXED,BOXED
 VOID:STRING,POINTER
 VOID:STRING,STRING
 VOID:STRING,STRING,OBJECT
diff --git a/lib/rb-util.c b/lib/rb-util.c
index 917e3e5..697e1ce 100644
--- a/lib/rb-util.c
+++ b/lib/rb-util.c
@@ -70,7 +70,7 @@ rb_copy_function (gpointer data)
 
 
 int
-rb_gvalue_compare (GValue *a, GValue *b)
+rb_gvalue_compare (const GValue *a, const GValue *b)
 {
 	int retval;
 	const char *stra, *strb;
diff --git a/lib/rb-util.h b/lib/rb-util.h
index 50845ad..5824f9c 100644
--- a/lib/rb-util.h
+++ b/lib/rb-util.h
@@ -43,7 +43,7 @@ gboolean rb_false_function (gpointer dummy);
 gpointer rb_null_function (gpointer dummy);
 gpointer rb_copy_function (gpointer data);
 
-int rb_gvalue_compare (GValue *a, GValue *b);
+int rb_gvalue_compare (const GValue *a, const GValue *b);
 
 int rb_compare_gtimeval (GTimeVal *a, GTimeVal *b);
 int rb_safe_strcmp (const char *a, const char *b);
diff --git a/rhythmdb/Makefile.am b/rhythmdb/Makefile.am
index 8a042d2..25d6a1f 100644
--- a/rhythmdb/Makefile.am
+++ b/rhythmdb/Makefile.am
@@ -15,6 +15,7 @@ INCLUDES = 						\
 	-I$(top_srcdir)/metadata			\
 	-I$(top_builddir)/lib                           \
 	$(RHYTHMBOX_CFLAGS)				\
+	$(DBUS_CFLAGS)					\
 	$(GIO_CFLAGS)					\
 	$(NO_STRICT_ALIASING_CFLAGS)
 
@@ -30,12 +31,23 @@ librhythmdb_la_SOURCES =				\
 	rhythmdb-property-model.c			\
 	rhythmdb-query-model.h				\
 	rhythmdb-query-model.c				\
+	rhythmdb-query-model-dbus.h			\
+	rhythmdb-query-model-dbus.c			\
 	rhythmdb-query-results.h			\
 	rhythmdb-query-results.c			\
 	rhythmdb-import-job.h				\
 	rhythmdb-import-job.c
 
-
 if USE_TREEDB
 librhythmdb_la_SOURCES += rhythmdb-tree.h rhythmdb-tree.c
 endif
+
+# dbus interface(s)
+rhythmdb-query-model-glue.h: rhythmdb-query-model.xml Makefile
+	$(LIBTOOL) --mode=execute $(DBUS_GLIB_BIN)/dbus-binding-tool --prefix=rhythmdb_query_model_dbus --mode=glib-server --output=$@ $<
+
+BUILT_SOURCES = rhythmdb-query-model-glue.h
+
+EXTRA_DIST = rhythmdb-query-model.xml
+
+CLEAN_FILES = $(BUILT_SOURCES)
diff --git a/rhythmdb/rhythmdb-query-model-dbus.c b/rhythmdb/rhythmdb-query-model-dbus.c
new file mode 100644
index 0000000..8a2de3b
--- /dev/null
+++ b/rhythmdb/rhythmdb-query-model-dbus.c
@@ -0,0 +1,704 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2008  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 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grants 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.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "rhythmdb-query-model-dbus.h"
+#include "rb-dbus-tree-iter.h"
+#include "rb-dbus-tree-model.h"
+#include "rb-marshal.h"
+#include "rb-debug.h"
+#include "rb-util.h"
+
+/* share one proxy for the bus between all model instances */
+static DBusGProxy *bus_proxy = NULL;
+
+/**
+ * SECTION:rhythmdb-query-model-dbus
+ * @short_description: DBus facade for #RhythmDBQueryModel
+ *
+ * Makes the contents of a #RhythmDBQueryModel accessible to DBus clients.
+ * Provides information about the model (number of entries, total size,
+ * total duration), and the ability to create #RBDBusTreeIter<!-- -->s
+ * to access individual rows.
+ *
+ * As the columns in a #RhythmDBQueryModel aren't very interesting or useful
+ * to a DBus clients, the properties of the entries are provided instead.
+ */
+
+#define OBJECT_PATH_FORMAT	"/org/gnome/Rhythmbox/QueryModel/%p"
+
+enum
+{
+	PROP_0,
+	PROP_CONNECTION,
+	PROP_OBJECT_PATH,
+	PROP_QUERY_MODEL
+};
+
+enum
+{
+	QUERY_COMPLETE,
+	ENTRY_PROP_CHANGED,
+	ENTRY_REMOVED,
+	ENTRY_ADDED,
+	ENTRIES_REORDERED,
+	LAST_SIGNAL
+};
+
+struct _RhythmDBQueryModelDBusPrivate
+{
+	DBusGConnection *connection;
+	RhythmDBQueryModel *query_model;
+	RhythmDB *db;
+	char *object_path;
+	GSList *references;
+	gboolean emit_signals;
+};
+
+static void rhythmdb_query_model_dbus_tree_model_init (RBDBusTreeModelIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (RhythmDBQueryModelDBus, rhythmdb_query_model_dbus, G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE(RB_TYPE_DBUS_TREE_MODEL,
+				 	       rhythmdb_query_model_dbus_tree_model_init))
+
+static guint rhythmdb_query_model_dbus_signals[LAST_SIGNAL] = { 0 };
+
+/* reference counting: */
+
+/* stupid trick here:
+ * the first character of the string in the reference list describes whether the reference
+ * owner is interested in signals from the model.  the reference owner name follows.
+ */
+
+static void
+_impl_ref (RhythmDBQueryModelDBus *model, const char *sender, gboolean enable_signals, gboolean ref_gobject)
+{
+	char *annotated_sender;
+	annotated_sender = g_strdup_printf ("%c%s", enable_signals ? 's' : ' ', sender);
+	model->priv->references = g_slist_prepend (model->priv->references, annotated_sender);
+
+	if (enable_signals)
+		model->priv->emit_signals = TRUE;
+
+	if (ref_gobject)
+		g_object_ref (model);
+}
+
+
+
+static gboolean
+_impl_unref (RhythmDBQueryModelDBus *model, const char *sender)
+{
+	GSList *t;
+	GSList *n;
+	gboolean found = FALSE;
+	
+	t = model->priv->references;
+	model->priv->emit_signals = FALSE;
+	while (t != NULL) {
+		char *owner = (char *)t->data;
+
+		/* skip the signal char to compare owner with sender */
+		if (strcmp (owner+1, sender) == 0) {
+			g_free (owner);
+			n = t->next;
+			model->priv->references = g_slist_delete_link (model->priv->references, t);
+			t = n;
+			found = TRUE;
+		} else {
+			/* figure out if we're still interested in signals */
+			model->priv->emit_signals |= (owner[0] != ' ');
+			t = t->next;
+		}
+	}
+
+	if (found)
+		g_object_unref (model);
+
+	return FALSE;
+}
+
+static void
+name_owner_changed_cb (DBusGProxy *proxy,
+		       const char *name,
+		       const char *prev_owner,
+		       const char *new_owner,
+		       RhythmDBQueryModelDBus *model)
+{
+	/* ignore it unless it's an ownership loss */
+	if (strcmp (new_owner, "") != 0) {
+		return;
+	}
+
+	/* take a temporary ref so we can still use the object name after dropping the client reference */
+	g_object_ref (model);
+	if (_impl_unref (model, name)) {
+		rb_debug ("cleaned up reference to model %s from owner %s",
+			  model->priv->object_path,
+			  name);
+	}
+	g_object_unref (model);
+}
+
+
+/**
+ * rhythmdb_query_model_dbus_new:
+ * @connection: 	DBus connection to register the model on
+ * @object_path:	Well-known DBus object path to use, or NULL to generate one
+ * @model:		The #RhythmDBQueryModel to expose
+ * @enable_signals:	If %TRUE, enable signal emission over DBus
+ * @invocation:		DBus method invocation context creating the model (if any) 
+ *
+ * Creates a new query model facade and registers it on the DBus connection.
+ * If an invocation context is supplied, the sender from the context will hold
+ * the initial reference on the model.  Otherwise, the in-process caller will.
+ *
+ * Return value: the new model facade object
+ */
+RhythmDBQueryModelDBus *
+rhythmdb_query_model_dbus_new (DBusGConnection *connection,
+			       const char *object_path,
+			       RhythmDBQueryModel *model,
+			       gboolean enable_signals,
+			       DBusGMethodInvocation *invocation)
+{
+	RhythmDBQueryModelDBus *obj;
+	char *sender;
+
+	obj = RHYTHMDB_QUERY_MODEL_DBUS (g_object_new (RHYTHMDB_TYPE_QUERY_MODEL_DBUS,
+						       "query-model", model,
+						       "object-path", object_path,	/* must be set *before* connection */
+						       "connection", connection,
+						       NULL)); 
+	if (invocation != NULL) {
+		sender = dbus_g_method_get_sender (invocation);
+		_impl_ref (obj, sender, enable_signals, FALSE);
+		g_free (sender);
+	}
+
+	return obj;
+}
+
+/**
+ * rhythmdb_query_model_dbus_get_path:
+ * @iter:		Model facade object
+ *
+ * Return value: the DBus object path of the model
+ */
+const char *
+rhythmdb_query_model_dbus_get_path (RhythmDBQueryModelDBus *model)
+{
+	return model->priv->object_path;
+}
+
+/* DBusTreeModel interface */
+
+static gboolean
+rhythmdb_query_model_dbus_get_columns (RBDBusTreeModel *iface, char ***names, GPtrArray **types, GError **error)
+{
+	RhythmDBQueryModelDBus *model = RHYTHMDB_QUERY_MODEL_DBUS (iface);
+	int i;
+
+	*types = g_ptr_array_new ();
+	*names = g_new0 (char *, RHYTHMDB_NUM_PROPERTIES+1);
+
+	for (i = 0; i < RHYTHMDB_NUM_PROPERTIES; i++) {
+		GValue *v;
+		GType raw_type;
+
+		v = g_new0 (GValue, 1);
+		raw_type = rhythmdb_get_property_type (model->priv->db, i);
+		if (rb_dbus_type_safe (raw_type)) {
+			g_value_init (v, raw_type);
+		} else {
+			g_value_init (v, G_TYPE_INT);
+		}
+
+		g_ptr_array_add (*types, v);
+
+		(*names)[i] = g_strdup ((char *)rhythmdb_nice_elt_name_from_propid (model->priv->db, i));
+	}
+
+	return TRUE;
+}
+
+static gboolean
+rhythmdb_query_model_dbus_get_value (RBDBusTreeModel *iface,
+				     GtkTreeIter *iter,
+				     int column,
+				     GValue *value,
+				     GError **error)
+{
+	RhythmDBQueryModelDBus *model = RHYTHMDB_QUERY_MODEL_DBUS (iface);
+	RhythmDBEntry *entry;
+	RhythmDBEntryType entry_type;
+	GType value_type;
+
+	entry = rhythmdb_query_model_iter_to_entry (model->priv->query_model, iter);
+	if (entry == NULL) {
+		/* XXX return error */
+		 return FALSE;
+	}
+
+	switch (column) {
+	case RHYTHMDB_PROP_TYPE:		/* return the entry type name */
+		entry_type = rhythmdb_entry_get_entry_type (entry);
+		g_value_init (value, G_TYPE_STRING);
+		g_value_set_string (value, entry_type->name);
+		g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
+		break;
+	default:
+		value_type = rhythmdb_get_property_type (model->priv->db, column);
+		if (rb_dbus_type_safe (value_type)) {
+			g_value_init (value, value_type);
+			rhythmdb_entry_get (model->priv->db, entry, column, value);
+		} else {
+			g_value_init (value, G_TYPE_INT);
+		}
+		break;
+	}
+	return TRUE;
+}
+
+static void
+rhythmdb_query_model_dbus_tree_model_init (RBDBusTreeModelIface *iface)
+{
+	iface->get_columns = rhythmdb_query_model_dbus_get_columns;
+	iface->get_value = rhythmdb_query_model_dbus_get_value;
+}
+
+/* DBus interface methods: */
+
+static gint64
+rhythmdb_query_model_dbus_get_size (RhythmDBQueryModelDBus *model, GError **error)
+{
+	return rhythmdb_query_model_get_size (model->priv->query_model);
+}
+
+static guint
+rhythmdb_query_model_dbus_get_duration (RhythmDBQueryModelDBus *model, GError **error)
+{
+	return rhythmdb_query_model_get_duration (model->priv->query_model);
+}
+
+static guint
+rhythmdb_query_model_dbus_get_num_entries (RhythmDBQueryModelDBus *model, GError **error)
+{
+	return gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model->priv->query_model), NULL);
+}
+
+static void
+rhythmdb_query_model_dbus_get_iter (RhythmDBQueryModelDBus *model, guint index, DBusGMethodInvocation *invocation)
+{
+	GtkTreeIter iter;
+	RBDBusTreeIter *dbus_iter;
+	
+	if (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (model->priv->query_model), &iter, NULL, index)) {
+		dbus_iter = rb_dbus_tree_iter_new (model->priv->connection,
+						   GTK_TREE_MODEL (model->priv->query_model),
+						   RB_DBUS_TREE_MODEL (model),
+						   G_TYPE_NONE,
+						   &iter,
+						   invocation);
+		dbus_g_method_return (invocation, rb_dbus_tree_iter_get_path (dbus_iter));
+	} else {
+		/* return error etc. */
+		dbus_g_method_return (invocation, "");
+	}
+}
+
+static void
+rhythmdb_query_model_dbus_get_iter_for_entry (RhythmDBQueryModelDBus *model, const char *entry_str, DBusGMethodInvocation *invocation)
+{
+	RhythmDBEntry *entry;
+	GtkTreeIter iter;
+	RBDBusTreeIter *dbus_iter;
+	RhythmDB *db;
+
+	/* find entry; currently pretending the entry string is a location,
+	 * but it should probably be something more interesting.
+	 */
+	g_object_get (model->priv->query_model, "db", &db, NULL);
+	entry = rhythmdb_entry_lookup_by_location (db, entry_str);
+	g_object_unref (db);
+
+	if (entry == NULL) {
+		/* XXX return error etc. */
+		dbus_g_method_return (invocation, "");
+		return;
+	}
+
+	if (rhythmdb_query_model_entry_to_iter (model->priv->query_model, entry, &iter) == FALSE) {
+		/* XXX return error etc. */
+		dbus_g_method_return (invocation, "");
+		return;
+	}
+
+	dbus_iter = rb_dbus_tree_iter_new (model->priv->connection,
+					   GTK_TREE_MODEL (model->priv->query_model),
+					   RB_DBUS_TREE_MODEL (model),
+					   G_TYPE_NONE,
+					   &iter,
+					   invocation);
+	dbus_g_method_return (invocation, rb_dbus_tree_iter_get_path (dbus_iter));
+}
+
+static void
+rhythmdb_query_model_dbus_ref (RhythmDBQueryModelDBus *model, gboolean enable_signals, DBusGMethodInvocation *context)
+{
+	char *sender;
+
+	sender = dbus_g_method_get_sender (context);
+	_impl_ref (model, sender, enable_signals, TRUE);
+	g_free (sender);
+
+	dbus_g_method_return (context);
+}
+
+static void
+rhythmdb_query_model_dbus_unref (RhythmDBQueryModelDBus *model, DBusGMethodInvocation *context)
+{
+	char *sender;
+
+	sender = dbus_g_method_get_sender (context);
+	if (_impl_unref (model, sender) == FALSE) {
+		g_warning ("Unable to find a reference for sender %s on dbus query model %s",
+			   sender, model->priv->object_path);
+	}
+
+	g_free (sender);
+	dbus_g_method_return (context);
+}
+
+#include "rhythmdb-query-model-glue.h"
+
+/* RhythmDBQueryModel signals */
+
+static void
+query_complete_cb (RhythmDBQueryModel *query_model, RhythmDBQueryModelDBus *model)
+{
+	if (!model->priv->emit_signals)
+		return;
+
+	g_signal_emit (model, rhythmdb_query_model_dbus_signals[QUERY_COMPLETE], 0);
+}
+
+static void
+entry_removed_cb (RhythmDBQueryModel *query_model, RhythmDBEntry *entry, RhythmDBQueryModelDBus *model)
+{
+	if (!model->priv->emit_signals)
+		return;
+
+	g_signal_emit (model, rhythmdb_query_model_dbus_signals[ENTRY_REMOVED], 0, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
+}
+
+static void
+row_inserted_cb (GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter, RhythmDBQueryModelDBus *model)
+{
+	RhythmDBEntry *entry;
+	RhythmDBQueryModel *query_model = RHYTHMDB_QUERY_MODEL (tree_model);
+
+	if (!model->priv->emit_signals)
+		return;
+
+	entry = rhythmdb_query_model_iter_to_entry (query_model, iter);
+	if (entry != NULL) {
+		g_signal_emit (model, rhythmdb_query_model_dbus_signals[ENTRY_ADDED], 0, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
+	}
+}
+
+static void
+entry_prop_changed_cb (RhythmDBQueryModel *query_model,
+		       RhythmDBEntry *entry,
+		       RhythmDBPropType prop,
+		       const GValue *old,
+		       const GValue *new_value,
+		       RhythmDBQueryModelDBus *model)
+{
+	if (!model->priv->emit_signals)
+		return;
+
+	/* don't emit null changes */
+	if (rb_gvalue_compare (old, new_value) == 0)
+		return;
+
+	/* check the value is safe to emit? */
+
+	g_signal_emit (model,
+		       rhythmdb_query_model_dbus_signals[ENTRY_PROP_CHANGED], 0,
+		       rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
+		       rhythmdb_nice_elt_name_from_propid (model->priv->db, prop),
+		       old,
+		       new_value);
+}
+
+static void
+rows_reordered_cb (GtkTreeModel *tree_model,
+		   GtkTreePath *path,
+		   GtkTreeIter *iter,
+		   gpointer um,
+		   RhythmDBQueryModelDBus *model)
+{
+	if (!model->priv->emit_signals)
+		return;
+
+	g_signal_emit (model, rhythmdb_query_model_dbus_signals[ENTRIES_REORDERED], 0);
+}
+
+/* GObjectification */
+
+static void
+set_dbus_connection (RhythmDBQueryModelDBus *model, DBusGConnection *connection)
+{
+	static gsize init_bus_proxy_once = 0;
+
+	model->priv->connection = connection;
+
+	/* create one bus proxy to share between all models. */
+	if (g_once_init_enter (&init_bus_proxy_once)) {
+		bus_proxy = dbus_g_proxy_new_for_name (connection,
+						       DBUS_SERVICE_DBUS,
+						       DBUS_PATH_DBUS,
+						       DBUS_INTERFACE_DBUS);
+		dbus_g_proxy_add_signal (bus_proxy,
+					 "NameOwnerChanged",
+					 G_TYPE_STRING,
+					 G_TYPE_STRING,
+					 G_TYPE_STRING,
+					 G_TYPE_INVALID);
+		g_once_init_leave (&init_bus_proxy_once, 1);
+	}
+
+	/* each model listens for name owner changes to clean up references whose owners vanish */
+	dbus_g_proxy_connect_signal (bus_proxy,
+				     "NameOwnerChanged",
+				     G_CALLBACK (name_owner_changed_cb),
+				     model,
+				     NULL);
+
+	dbus_g_connection_register_g_object (model->priv->connection,
+					     model->priv->object_path,
+					     G_OBJECT (model));
+}
+
+static void
+set_query_model (RhythmDBQueryModelDBus *model, RhythmDBQueryModel *query_model)
+{
+	model->priv->query_model = query_model;
+	g_object_get (model->priv->query_model, "db", &model->priv->db, NULL);
+
+	/* connect signal handlers, whether we're currently emitting signals or not */
+	g_signal_connect_object (model->priv->query_model,
+				 "complete",
+				 G_CALLBACK (query_complete_cb),
+				 model, 0);
+	g_signal_connect_object (model->priv->query_model,
+				 "entry-removed",
+				 G_CALLBACK (entry_removed_cb),
+				 model, 0);
+	g_signal_connect_object (model->priv->query_model,
+				 "row-inserted",
+				 G_CALLBACK (row_inserted_cb),
+				 model, 0);
+	g_signal_connect_object (model->priv->query_model,
+				 "entry-prop-changed",
+				 G_CALLBACK (entry_prop_changed_cb),
+				 model, 0);
+	g_signal_connect_object (model->priv->query_model,
+				 "rows-reordered",
+				 G_CALLBACK (rows_reordered_cb),
+				 model, 0);
+}
+
+static void
+impl_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec)
+{
+	RhythmDBQueryModelDBus *model = RHYTHMDB_QUERY_MODEL_DBUS (object);
+	switch (param_id) {
+	case PROP_CONNECTION:
+		g_value_set_pointer (value, model->priv->connection);
+		break;
+	case PROP_OBJECT_PATH:
+		g_value_set_string (value, model->priv->object_path);
+		break;
+	case PROP_QUERY_MODEL:
+		g_value_set_object (value, model->priv->query_model);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec)
+{
+	RhythmDBQueryModelDBus *model = RHYTHMDB_QUERY_MODEL_DBUS (object);
+	switch (param_id) {
+	case PROP_CONNECTION:
+		set_dbus_connection (model, g_value_get_pointer (value));
+		break;
+	case PROP_OBJECT_PATH:
+		if (g_value_get_string (value) != NULL) {
+			g_free (model->priv->object_path);
+			model->priv->object_path = g_value_dup_string (value);
+		}
+		break;
+	case PROP_QUERY_MODEL:
+		set_query_model (model, g_value_dup_object (value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_dispose (GObject *object)
+{
+	RhythmDBQueryModelDBus *model = RHYTHMDB_QUERY_MODEL_DBUS (object);
+
+	dbus_g_proxy_disconnect_signal (bus_proxy,
+					"NameOwnerChanged",
+					G_CALLBACK (name_owner_changed_cb),
+					model);
+
+	if (model->priv->connection != NULL) {
+		dbus_g_connection_unref (model->priv->connection);
+		model->priv->connection = NULL;
+	}
+	if (model->priv->query_model != NULL) {
+		g_object_unref (model->priv->query_model);
+		model->priv->query_model = NULL;
+	}
+	if (model->priv->db != NULL) {
+		g_object_unref (model->priv->db);
+		model->priv->db = NULL;
+	}
+	g_free (model->priv->object_path);
+	model->priv->object_path = NULL;
+	G_OBJECT_CLASS (rhythmdb_query_model_dbus_parent_class)->dispose (object);
+}
+
+static void
+rhythmdb_query_model_dbus_init (RhythmDBQueryModelDBus *model)
+{
+	model->priv = G_TYPE_INSTANCE_GET_PRIVATE (model, RHYTHMDB_TYPE_QUERY_MODEL_DBUS, RhythmDBQueryModelDBusPrivate);
+
+	model->priv->object_path = g_strdup_printf (OBJECT_PATH_FORMAT, model);
+}
+
+static void
+rhythmdb_query_model_dbus_class_init (RhythmDBQueryModelDBusClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = impl_dispose;
+	object_class->set_property = impl_set_property;
+	object_class->get_property = impl_get_property;
+
+	g_object_class_install_property (object_class,
+					 PROP_CONNECTION,
+					 g_param_spec_pointer ("connection",
+						 	       "connection",
+							       "DBus connection",
+							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_OBJECT_PATH,
+					 g_param_spec_string ("object-path",
+						 	      "object path",
+							      "DBus object path",
+							      NULL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_QUERY_MODEL,
+					 g_param_spec_object ("query-model",
+						 	      "query model",
+							      "RhythmDBQueryModel instance",
+							      RHYTHMDB_TYPE_QUERY_MODEL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	rhythmdb_query_model_dbus_signals[QUERY_COMPLETE] =
+		g_signal_new ("query-complete",
+			      RHYTHMDB_TYPE_QUERY_MODEL_DBUS,
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE, 0);
+	rhythmdb_query_model_dbus_signals[ENTRY_PROP_CHANGED] =
+		g_signal_new ("entry-prop-changed",
+			      RHYTHMDB_TYPE_QUERY_MODEL_DBUS,
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      rb_marshal_VOID__STRING_STRING_BOXED_BOXED,
+			      G_TYPE_NONE,
+			      4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_VALUE, G_TYPE_VALUE);
+	rhythmdb_query_model_dbus_signals[ENTRY_REMOVED] =
+		g_signal_new ("entry-removed",
+			      RHYTHMDB_TYPE_QUERY_MODEL_DBUS,
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__STRING,
+			      G_TYPE_NONE,
+			      1, G_TYPE_STRING);
+	rhythmdb_query_model_dbus_signals[ENTRY_ADDED] =
+		g_signal_new ("entry-added",
+			      RHYTHMDB_TYPE_QUERY_MODEL_DBUS,
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__STRING,
+			      G_TYPE_NONE,
+			      1, G_TYPE_STRING);
+	rhythmdb_query_model_dbus_signals[ENTRIES_REORDERED] =
+		g_signal_new ("entries-reordered",
+			      RHYTHMDB_TYPE_QUERY_MODEL_DBUS,
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE, 0);
+
+
+	g_type_class_add_private (klass, sizeof (RhythmDBQueryModelDBusPrivate));
+
+	dbus_g_object_type_install_info (RHYTHMDB_TYPE_QUERY_MODEL_DBUS,
+					 &dbus_glib_rhythmdb_query_model_dbus_object_info);
+}
+
+
diff --git a/rhythmdb/rhythmdb-query-model-dbus.h b/rhythmdb/rhythmdb-query-model-dbus.h
new file mode 100644
index 0000000..97b26fe
--- /dev/null
+++ b/rhythmdb/rhythmdb-query-model-dbus.h
@@ -0,0 +1,69 @@
+/*
+ *  Copyright (C) 2008  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 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grants 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
+ */
+
+#ifndef RHYTHMDB_QUERY_MODEL_DBUS_H
+#define RHYTHMDB_QUERY_MODEL_DBUS_H
+
+#include <glib.h>
+#include <dbus/dbus-glib.h>
+
+#include "rhythmdb-query-model.h"
+
+#define RHYTHMDB_TYPE_QUERY_MODEL_DBUS			(rhythmdb_query_model_dbus_get_type ())
+#define RHYTHMDB_QUERY_MODEL_DBUS(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), RHYTHMDB_TYPE_QUERY_MODEL_DBUS, RhythmDBQueryModelDBus))
+#define RHYTHMDB_QUERY_MODEL_DBUS_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), RHYTHMDB_TYPE_QUERY_MODEL_DBUS, RhythmDBQueryModelDBusClass))
+#define RHYTHMDB_IS_QUERY_MODEL_DBUS(o)			(G_TYPE_CHECK_INSTANCE_TYPE ((o), RHYTHMDB_TYPE_QUERY_MODEL_DBUS))
+#define RHYTHMDB_IS_QUERY_MODEL_DBUS_CLASS(k)		(G_TYPE_CHECK_CLASS_TYPE ((k), RHYTHMDB_TYPE_QUERY_MODEL_DBUS))
+#define RHYTHMDB_QUERY_MODEL_DBUS_GET_CLASS(o)		(G_TYPE_INSTANCE_GET_CLASS ((o), RHYTHMDB_TYPE_QUERY_MODEL_DBUS, RhythmDBQueryModelDBusClass))
+
+typedef struct _RhythmDBQueryModelDBus RhythmDBQueryModelDBus;
+typedef struct _RhythmDBQueryModelDBusClass RhythmDBQueryModelDBusClass;
+typedef struct _RhythmDBQueryModelDBusPrivate RhythmDBQueryModelDBusPrivate;
+
+
+struct _RhythmDBQueryModelDBus
+{
+	GObject parent;
+	RhythmDBQueryModelDBusPrivate *priv;
+};
+
+struct _RhythmDBQueryModelDBusClass
+{
+	GObjectClass parent_class;
+};
+
+GType				rhythmdb_query_model_dbus_get_type 	(void);
+
+RhythmDBQueryModelDBus *	rhythmdb_query_model_dbus_new		(DBusGConnection *connection,
+									 const char *object_path,
+									 RhythmDBQueryModel *model,
+									 gboolean enable_signals,
+									 DBusGMethodInvocation *invocation);
+
+const char *			rhythmdb_query_model_dbus_get_path	(RhythmDBQueryModelDBus *model);
+
+#endif /* RHYTHMDB_QUERY_MODEL_DBUS_H */
diff --git a/rhythmdb/rhythmdb-query-model.xml b/rhythmdb/rhythmdb-query-model.xml
new file mode 100644
index 0000000..16d233b
--- /dev/null
+++ b/rhythmdb/rhythmdb-query-model.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/">
+
+  <interface name="org.gnome.Rhythmbox.QueryModel">
+
+    <method name="GetSize">
+      <arg type="x" direction="out">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <method name="GetDuration">
+      <arg type="u" direction="out">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <method name="GetNumEntries">
+      <arg type="u" direction="out">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <method name="GetIter">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="u" name="index"/>
+      <arg type="o" direction="out" name="iter">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <method name="GetIterForEntry">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="s" name="location"/>
+      <arg type="o" direction="out" name="iter">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <!-- things to add..
+     - add entry
+     - remove entry
+     - move entry
+     - shuffle?
+     - set sort order .. somehow
+    -->
+
+    <!-- 
+      these are async only because they need to access the caller info.
+      the argument to Ref describes whether the reference owner is interested
+      in getting signals for changes to the model.  signals are only
+      emitted if one or more reference owners are interested.
+    -->
+    <method name="Ref">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="b" name="enable_signals"/>
+    </method>
+    <method name="Unref">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+    </method>
+
+    <signal name="QueryComplete"/>
+
+    <signal name="EntryPropChanged">
+      <arg type="s" name="entry"/>
+      <arg type="i" name="property"/>
+      <arg type="v" name="oldValue"/>
+      <arg type="v" name="newValue"/>
+    </signal>
+
+    <signal name="EntryRemoved">
+      <arg type="s" name="entry"/>
+    </signal>
+
+    <!-- only emitted once the query is complete? -->
+    <signal name="EntryAdded">
+      <arg type="s" name="entry"/>
+      <!-- position? -->
+    </signal>
+
+     <signal name="EntriesReordered"/>
+
+  </interface>
+</node>
diff --git a/sources/Makefile.am b/sources/Makefile.am
index 7e2fcae..23de506 100644
--- a/sources/Makefile.am
+++ b/sources/Makefile.am
@@ -63,6 +63,20 @@ INCLUDES =						\
 	$(RHYTHMBOX_CFLAGS)				\
 	$(NO_STRICT_ALIASING_CFLAGS)
 
+# dbus interfaces
+rb-sourcelist-glue.h: rb-sourcelist.xml Makefile
+	$(LIBTOOL) --mode=execute $(DBUS_GLIB_BIN)/dbus-binding-tool --prefix=rb_sourcelist_dbus --mode=glib-server --output=$@ $<
+rb-source-glue.h: rb-source.xml Makefile
+	$(LIBTOOL) --mode=execute $(DBUS_GLIB_BIN)/dbus-binding-tool --prefix=rb_source_dbus --mode=glib-server --output=$@ $<
+
+BUILT_SOURCES = 					\
+	rb-source-glue.h				\
+	rb-sourcelist-glue.h
+
+EXTRA_DIST =						\
+	rb-source.xml					\
+	rb-sourcelist.xml
+
 libsources_la_LDFLAGS = -export-dynamic
 libsourcesimpl_la_LDFLAGS = -export-dynamic
 
diff --git a/sources/rb-auto-playlist-source.c b/sources/rb-auto-playlist-source.c
index e3a1a42..f556ae1 100644
--- a/sources/rb-auto-playlist-source.c
+++ b/sources/rb-auto-playlist-source.c
@@ -213,7 +213,7 @@ rb_auto_playlist_source_dispose (GObject *object)
 		priv->action_group = NULL;
 	}
 
-	if (priv->cached_all_query) {
+	if (priv->cached_all_query != NULL) {
 		g_object_unref (G_OBJECT (priv->cached_all_query));
 		priv->cached_all_query = NULL;
 	}
@@ -294,6 +294,7 @@ rb_auto_playlist_source_constructor (GType type, guint n_construct_properties,
 	gtk_container_add (GTK_CONTAINER (source), priv->paned);
 	g_object_unref (songs);
 
+
 	gtk_widget_show_all (GTK_WIDGET (source));
 
 	return G_OBJECT (source);
@@ -816,7 +817,7 @@ rb_auto_playlist_source_set_query (RBAutoPlaylistSource *source,
 	}
 
 	if (priv->cached_all_query) {
-		g_object_unref (G_OBJECT (priv->cached_all_query));
+		g_object_unref (priv->cached_all_query);
 	}
 
 	if (priv->limit_value) {
@@ -841,6 +842,7 @@ rb_auto_playlist_source_set_query (RBAutoPlaylistSource *source,
 					     RHYTHMDB_QUERY_RESULTS (priv->cached_all_query),
 					     priv->query);
 
+	g_object_notify (G_OBJECT (source), "base-query-model");
 	priv->query_resetting = FALSE;
 }
 
diff --git a/sources/rb-browser-source.c b/sources/rb-browser-source.c
index 043ee44..78a4c7b 100644
--- a/sources/rb-browser-source.c
+++ b/sources/rb-browser-source.c
@@ -516,6 +516,8 @@ rb_browser_source_constructor (GType type,
 				      RHYTHMDB_QUERY_PROP_EQUALS, RHYTHMDB_PROP_TYPE, entry_type,
 				      RHYTHMDB_QUERY_END);
 	g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
+	
+	g_object_notify (G_OBJECT (source), "base-query-model");
 
 	return G_OBJECT (source);
 }
diff --git a/sources/rb-source.c b/sources/rb-source.c
index 30a4db6..b5caf4e 100644
--- a/sources/rb-source.c
+++ b/sources/rb-source.c
@@ -36,6 +36,7 @@
 
 #include <glib/gi18n.h>
 #include <gtk/gtkuimanager.h>
+#include <dbus/dbus-glib.h>
 
 #include "rb-cut-and-paste-code.h"
 #include "rb-debug.h"
@@ -46,8 +47,11 @@
 #include "rb-static-playlist-source.h"
 #include "rb-source-group.h"
 #include "rb-plugin.h"
+#include "rhythmdb-query-model-dbus.h"
 #include "rb-play-order.h"
 
+#define RB_SOURCE_DBUS_OBJECT_PATH	"/org/gnome/Rhythmbox/Source/%p"
+
 static void rb_source_class_init (RBSourceClass *klass);
 static void rb_source_init (RBSource *source);
 static void rb_source_dispose (GObject *object);
@@ -87,6 +91,10 @@ static void rb_source_row_inserted_cb (GtkTreeModel *model,
 				       GtkTreePath *path,
 				       GtkTreeIter *iter,
 				       RBSource *source);
+static void base_query_model_notify_cb (RBSource *source, GParamSpec *pspec, RBSource *uh);
+
+const DBusGObjectInfo dbus_glib_rb_source_dbus_object_info;
+
 G_DEFINE_ABSTRACT_TYPE (RBSource, rb_source, GTK_TYPE_HBOX)
 #define RB_SOURCE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), RB_TYPE_SOURCE, RBSourcePrivate))
 
@@ -124,6 +132,10 @@ struct _RBSourcePrivate
 	RhythmDBEntryType entry_type;
 	RBSourceGroup *source_group;
 	RBPlugin *plugin;
+
+	char *source_object_path;
+	char *query_model_path;
+	RhythmDBQueryModelDBus *query_model_dbus;
 };
 
 enum
@@ -404,12 +416,29 @@ rb_source_class_init (RBSourceClass *klass)
 			      0);
 
 	g_type_class_add_private (object_class, sizeof (RBSourcePrivate));
+
+	dbus_g_object_type_install_info (RB_TYPE_SOURCE, &dbus_glib_rb_source_dbus_object_info);
 }
 
 static void
 rb_source_init (RBSource *source)
 {
-	RB_SOURCE_GET_PRIVATE (source)->visible = TRUE;
+	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
+	DBusGConnection *connection;
+
+	priv->visible = TRUE;
+
+	connection = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
+	if (connection != NULL) {
+		priv->source_object_path = g_strdup_printf (RB_SOURCE_DBUS_OBJECT_PATH, source);
+		dbus_g_connection_register_g_object (connection,
+						     priv->source_object_path,
+						     G_OBJECT (source));
+
+		g_signal_connect_object (source, "notify::base-query-model",
+					 G_CALLBACK (base_query_model_notify_cb),
+					 source, 0);
+	}
 }
 
 static void
@@ -464,6 +493,7 @@ rb_source_finalize (GObject *object)
 	}
 
 	g_free (priv->name);
+	g_free (priv->source_object_path);
 
 	G_OBJECT_CLASS (rb_source_parent_class)->finalize (object);
 }
@@ -577,7 +607,10 @@ rb_source_set_query_model_internal (RBSource *source,
 					 source, 0);
 	}
 
-	/* g_object_notify (G_OBJECT (source), "query-model"); */
+
+	g_object_notify (G_OBJECT (source), "base-query-model");
+
+	/* g_object_notify (G_OBJECT (source), "query-model"); */		/* why is this commented out? */
 	rb_source_notify_status_changed (source);
 }
 
@@ -1736,6 +1769,130 @@ _rb_source_check_entry_type (RBSource *source, RhythmDBEntry *entry)
 	return ret;
 }
 
+/* DBus interface */
+
+static void
+base_query_model_notify_cb (RBSource *source, GParamSpec *pspec, RBSource *uh)
+{
+	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
+	RhythmDBQueryModel *new_model;
+	DBusGConnection *connection;
+
+	g_object_get (source, "base-query-model", &new_model, NULL);
+
+	if (priv->query_model_dbus != NULL) {
+		RhythmDBQueryModel *old_model;
+
+		g_object_get (priv->query_model_dbus, "query-model", &old_model, NULL);
+		if (old_model != new_model) {
+			g_object_unref (priv->query_model_dbus);
+			priv->query_model_dbus = NULL;
+		}
+		g_object_unref (old_model);
+		g_object_unref (new_model);
+
+		/* comparing pointers after unref is OK.. */
+		if (old_model == new_model) {
+			return;
+		}
+	}
+
+	connection = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
+	if (connection != NULL) {
+		priv->query_model_dbus = rhythmdb_query_model_dbus_new (connection, NULL, new_model, FALSE, NULL);
+	}
+}
+
+static char *
+rb_source_dbus_get_name (RBSource *source, GError **error)
+{
+	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
+	return priv->name;
+}
+
+static gboolean
+rb_source_dbus_get_query_model (RBSource *source, GValue *qm, GError **error)
+{
+	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
+	char *object_path = NULL;
+
+	if (priv->query_model_dbus != NULL) {
+		g_object_get (priv->query_model_dbus, "object-path", &object_path, NULL);
+	}
+
+	if (object_path != NULL) {
+		g_value_init (qm, G_TYPE_STRING);
+		g_value_take_string (qm, object_path);
+		return TRUE;
+	} else {
+		g_set_error (error, RB_SHELL_ERROR, RB_SHELL_ERROR_NO_SOURCE_FOR_URI, "The query model for this source is not available");
+		return FALSE;
+	}
+}
+
+static const char *
+rb_source_dbus_get_entry_type (RBSource *source, GError **error)
+{
+	RhythmDBEntryType entry_type;
+	char *type_name = NULL;
+
+	g_object_get (source, "entry-type", &entry_type, NULL);
+	if (entry_type != NULL) {
+		type_name = entry_type->name;
+		g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
+	} else {
+		type_name = "";
+	}
+
+	return type_name;
+}
+
+static gboolean
+rb_source_dbus_paste (RBSource *source, const char **entries, GError **error)
+{
+	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
+	RhythmDB *db;
+	GList *list = NULL;
+	int i;
+
+	g_object_get (priv->shell, "db", &db, NULL);
+
+	for (i = 0; entries[i] != NULL; i++) {
+		RhythmDBEntry *entry;
+
+		entry = rhythmdb_entry_lookup_by_location (db, entries[i]);
+		if (entry != NULL) {
+			list = g_list_prepend (list, entry);
+		}
+	}
+	list = g_list_reverse (list);
+
+	rb_source_paste (source, list);
+	g_list_free (list);
+	g_object_unref (db);
+	return TRUE;
+}
+
+static guint
+rb_source_dbus_want_uri (RBSource *source, const char *uri, GError **error)
+{
+	return rb_source_want_uri (source, uri);
+}
+
+static gboolean
+rb_source_dbus_uri_is_source (RBSource *source, const char *uri, GError **error)
+{
+	return rb_source_uri_is_source (source, uri);
+}
+
+static gboolean
+rb_source_dbus_add_uri (RBSource *source, const char *uri, GError **error)
+{
+	return rb_source_add_uri (source, uri, NULL, NULL);
+}
+
+#include "rb-source-glue.h"
+
 /* This should really be standard. */
 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
 
diff --git a/sources/rb-source.xml b/sources/rb-source.xml
new file mode 100644
index 0000000..cfd6558
--- /dev/null
+++ b/sources/rb-source.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/">
+  <interface name="org.gnome.Rhythmbox.Source">
+
+    <method name="GetName">
+      <arg type="s" direction="out">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+        <annotation name="org.freedesktop.DBus.GLib.Const" value=""/>
+      </arg>
+    </method>
+
+    <method name="GetQueryModel">
+      <!--
+      <arg type="o" direction="out">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+        <annotation name="org.freedesktop.DBus.GLib.Const" value=""/>
+      </arg>
+      -->
+      <arg type="v" direction="out"/>
+    </method>
+
+    <method name="GetEntryType">
+      <arg type="s" direction="out">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+        <annotation name="org.freedesktop.DBus.GLib.Const" value=""/>
+      </arg>
+    </method>
+ 
+    <method name="Paste">		<!-- name is kind of crap.. -->
+      <arg type="as"/>
+      <arg type="b" direction="out">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <method name="WantUri">
+      <arg type="s"/>
+      <arg type="u" direction="out">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <method name="UriIsSource">
+      <arg type="s"/>
+      <arg type="b" direction="out">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+    <method name="AddUri">
+      <arg type="s"/>
+      <arg type="b" direction="out">
+        <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/>
+      </arg>
+    </method>
+
+  </interface>
+</node>
+
diff --git a/sources/rb-sourcelist.c b/sources/rb-sourcelist.c
index d55c353..8777792 100644
--- a/sources/rb-sourcelist.c
+++ b/sources/rb-sourcelist.c
@@ -38,6 +38,7 @@
 #include <gdk/gdk.h>
 #include <gdk/gdkkeysyms.h>
 #include <gtk/gtk.h>
+#include <dbus/dbus-glib.h>
 
 #include "rb-sourcelist.h"
 #include "rb-sourcelist-model.h"
@@ -50,6 +51,8 @@
 #include "rb-util.h"
 #include "rb-auto-playlist-source.h"
 #include "rb-static-playlist-source.h"
+#include "rb-dbus-tree-iter.h"
+
 
 /**
  * SECTION:rb-sourcelist
@@ -103,6 +106,8 @@ enum
 	LAST_SIGNAL
 };
 
+#define RB_SOURCELIST_DBUS_OBJECT_PATH	"/org/gnome/Rhythmbox/SourceList"
+
 static void rb_sourcelist_class_init (RBSourceListClass *klass);
 static void rb_sourcelist_init (RBSourceList *sourcelist);
 static GObject * rb_sourcelist_constructor (GType type,
@@ -127,10 +132,18 @@ static void rb_sourcelist_selection_changed_cb (GtkTreeSelection *selection,
 						RBSourceList *sourcelist);
 static void source_name_edited_cb (GtkCellRendererText *renderer, const char *pathstr,
 				   const char *text, RBSourceList *sourcelist);
+static gboolean rb_sourcelist_find_group_iter (RBSourceList  *sourcelist,
+					       RBSourceGroup *group,
+					       GtkTreeIter   *iter);
+static void rb_sourcelist_dbus_tree_model_init (RBDBusTreeModelIface *iface);
 
 static guint rb_sourcelist_signals[LAST_SIGNAL] = { 0 };
 
-G_DEFINE_TYPE (RBSourceList, rb_sourcelist, GTK_TYPE_SCROLLED_WINDOW)
+const DBusGObjectInfo dbus_glib_rb_sourcelist_dbus_object_info;
+
+G_DEFINE_TYPE_WITH_CODE (RBSourceList, rb_sourcelist, GTK_TYPE_SCROLLED_WINDOW,
+			 G_IMPLEMENT_INTERFACE(RB_TYPE_DBUS_TREE_MODEL,
+				 	       rb_sourcelist_dbus_tree_model_init))
 
 static void
 rb_sourcelist_class_init (RBSourceListClass *class)
@@ -674,6 +687,7 @@ static void
 rb_sourcelist_init (RBSourceList *sourcelist)
 {
 	GtkCellRenderer *renderer;
+	DBusGConnection *connection;
 
 	sourcelist->priv = RB_SOURCELIST_GET_PRIVATE (sourcelist);
 
@@ -814,8 +828,18 @@ rb_sourcelist_init (RBSourceList *sourcelist)
 			         G_CALLBACK (rb_sourcelist_selection_changed_cb),
 			         sourcelist,
 				 0);
+
+	connection = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
+	if (connection != NULL) {
+		dbus_g_object_type_install_info (RB_TYPE_SOURCELIST,
+						 &dbus_glib_rb_sourcelist_dbus_object_info);
+		dbus_g_connection_register_g_object (connection,
+						     RB_SOURCELIST_DBUS_OBJECT_PATH,
+						     G_OBJECT (sourcelist));
+	}
 }
 
+
 static void
 rb_sourcelist_finalize (GObject *object)
 {
@@ -1441,3 +1465,131 @@ source_name_edited_cb (GtkCellRendererText *renderer,
 
 	g_object_unref (source);
 }
+
+/* DBus tree model support */
+
+static gboolean
+rb_sourcelist_dbus_get_columns (RBDBusTreeModel *iface, char ***names, GPtrArray **types, GError **error)
+{
+	RBSourceList *sourcelist;
+	GEnumClass *enum_class;
+	int n;
+	int i;
+
+	sourcelist = RB_SOURCELIST (iface);
+	enum_class = G_ENUM_CLASS (rb_sourcelist_model_column_get_type ());
+
+	n = gtk_tree_model_get_n_columns (sourcelist->priv->real_model);
+	*names = g_new0 (char *, n + 1);
+	*types = g_ptr_array_new ();
+
+	for (i = 0; i < n; i++) {
+		GEnumValue *ev;
+		GValue *v;
+
+		/* get name from the column enum */
+		ev = g_enum_get_value (enum_class, i);
+		(*names)[i] = g_strdup (ev->value_nick);
+
+		/* specifically allow the source column to be exported */
+		v = g_new0 (GValue, 1);
+		if (i == RB_SOURCELIST_MODEL_COLUMN_SOURCE) {
+			g_value_init (v, G_TYPE_OBJECT);
+		} else {
+			GType raw_type = gtk_tree_model_get_column_type (sourcelist->priv->real_model, i);
+			if (rb_dbus_type_safe (raw_type)) {
+				g_value_init (v, raw_type);
+			} else {
+				g_value_init (v, G_TYPE_INT);
+			}
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+rb_sourcelist_dbus_get_value (RBDBusTreeModel *iface, GtkTreeIter *iter, int column, GValue *value, GError **error)
+{
+	RBSourceList *sourcelist = RB_SOURCELIST (iface);
+	gtk_tree_model_get_value (sourcelist->priv->real_model, iter, column, value);
+
+	if (column != RB_SOURCELIST_MODEL_COLUMN_SOURCE && (rb_dbus_type_safe (G_VALUE_TYPE (value)) == FALSE)) {
+		g_value_unset (value);
+		g_value_init (value, G_TYPE_INT);
+	} 
+
+	return TRUE;
+}
+
+
+static void
+rb_sourcelist_dbus_tree_model_init (RBDBusTreeModelIface *iface)
+{
+	iface->get_columns = rb_sourcelist_dbus_get_columns;
+	iface->get_value = rb_sourcelist_dbus_get_value;
+}
+
+/* DBus interface */
+
+static void
+rb_sourcelist_dbus_get_iter_first (RBSourceList *sourcelist, DBusGMethodInvocation *invocation)
+{
+	GtkTreeIter iter;
+	RBDBusTreeIter *dbus_iter;
+	DBusGConnection *connection;
+
+	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (sourcelist->priv->real_model), &iter) == FALSE) {
+		GError *error = g_error_new (DBUS_GERROR,
+					     DBUS_GERROR_REMOTE_EXCEPTION,
+					     " ");
+		dbus_g_method_return_error (invocation, error);
+		return;
+	}
+	
+	connection = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
+	dbus_iter = rb_dbus_tree_iter_new (connection,
+					   sourcelist->priv->real_model,
+					   NULL,
+					   rb_sourcelist_model_column_get_type (),
+					   &iter,
+					   invocation);
+	dbus_g_method_return (invocation, rb_dbus_tree_iter_get_path (dbus_iter));
+}
+
+static void
+rb_sourcelist_dbus_get_iter_group (RBSourceList *sourcelist, const char *group_name, DBusGMethodInvocation *invocation)
+{
+	RBSourceGroup *group;
+	GtkTreeIter iter;
+	RBDBusTreeIter *dbus_iter;
+	DBusGConnection *connection;
+
+	group = rb_source_group_get_by_name (group_name);
+	if (group == NULL) {
+		GError *error = g_error_new (DBUS_GERROR,
+					     DBUS_GERROR_REMOTE_EXCEPTION,
+					     " ");
+		dbus_g_method_return_error (invocation, error);
+		return;
+	}
+
+	if (rb_sourcelist_find_group_iter (sourcelist, group, &iter) == FALSE) {
+		GError *error = g_error_new (DBUS_GERROR,
+					     DBUS_GERROR_REMOTE_EXCEPTION,
+					     " ");
+		dbus_g_method_return_error (invocation, error);
+		return;
+	}
+
+	connection = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
+	dbus_iter = rb_dbus_tree_iter_new (connection,
+					   sourcelist->priv->real_model,
+					   NULL,
+					   rb_sourcelist_model_column_get_type (),
+					   &iter,
+					   invocation);
+	dbus_g_method_return (invocation, rb_dbus_tree_iter_get_path (dbus_iter));
+}
+
+#include "rb-sourcelist-glue.h"
diff --git a/sources/rb-sourcelist.xml b/sources/rb-sourcelist.xml
new file mode 100644
index 0000000..76a8642
--- /dev/null
+++ b/sources/rb-sourcelist.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/">
+  <interface name="org.gnome.Rhythmbox.SourceList">
+
+    <method name="GetIterFirst">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="o" direction="out" name="iter"/>
+    </method>
+
+    <method name="GetIterGroup">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="s" name="group"/>
+      <arg type="o" direction="out" name="iter"/>
+    </method>
+
+  </interface>
+</node>
+
diff --git a/sources/rb-static-playlist-source.c b/sources/rb-static-playlist-source.c
index 6fd0277..af92b21 100644
--- a/sources/rb-static-playlist-source.c
+++ b/sources/rb-static-playlist-source.c
@@ -326,6 +326,8 @@ rb_static_playlist_source_constructor (GType type,
 				 G_CALLBACK (rb_static_playlist_source_rows_reordered),
 				 source, 0);
 
+	g_object_notify (G_OBJECT (source), "base-query-model");
+
 	gtk_widget_show_all (GTK_WIDGET (source));
 
 	return G_OBJECT (source);
