[rhythmbox] shell: convert the shell into a GtkApplication



commit fa9a352b67347c9aa4f516052f3f1ef5a0b53dc6
Author: Jonathan Matthew <jonathan d14n org>
Date:   Thu Sep 29 09:30:34 2011 +1000

    shell: convert the shell into a GtkApplication
    
    This mostly replaces the shell dbus interface.  We're now using
    org.gnome.Rhythmbox3 as the dbus name since old dbus clients will not
    be able to talk to rhythmbox 3+ from here on.

 .gitignore                                         |    2 +-
 bindings/vala/rb.vapi                              |    1 -
 configure.ac                                       |   14 +-
 data/Makefile.am                                   |    4 +-
 ....service.in => org.gnome.Rhythmbox3.service.in} |    2 +-
 shell/Makefile.am                                  |    7 -
 shell/main.c                                       |  339 +---
 shell/rb-shell.c                                   | 2706 ++++++++++----------
 shell/rb-shell.h                                   |   29 +-
 shell/rb-shell.xml                                 |   93 -
 10 files changed, 1415 insertions(+), 1782 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 959f343..f623a7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,7 +36,7 @@ bindings/gi/RB-3.0.gir
 bindings/gi/RB-3.0.typelib
 
 #
-org.gnome.Rhythmbox.service
+org.gnome.Rhythmbox3.service
 *.desktop
 rhythmbox.desktop.in
 rhythmbox-device.desktop.in
diff --git a/bindings/vala/rb.vapi b/bindings/vala/rb.vapi
index 411ac8b..84c0970 100644
--- a/bindings/vala/rb.vapi
+++ b/bindings/vala/rb.vapi
@@ -19,7 +19,6 @@ namespace RB {
 
 		public void add_widget (Gtk.Widget widget, RB.ShellUILocation location);
 		public void remove_widget (Gtk.Widget widget, RB.ShellUILocation location);
-		public GLib.Object get_ui_manager ();
 
 		[NoAccessorMethod]
 		public RhythmDB.DB db { owned get; }
diff --git a/configure.ac b/configure.ac
index 1da1e2e..87273dd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -45,7 +45,6 @@ AC_CHECK_SIZEOF(long)
 
 GTK_REQS=2.91.4
 
-DBUS_MIN_REQS=0.35
 GST_0_10_REQS=0.10.32
 GDK_PIXBUF_REQS=2.18.0
 GLIB_REQS=2.26.0
@@ -144,14 +143,14 @@ AC_ARG_WITH(hal,
 	      AC_HELP_STRING([--without-hal],
 			     [Disable HAL support]))
 if test "x$with_hal" != "xno"; then
-  PKG_CHECK_MODULES(HAL, hal >= 0.5 hal < 0.6, enable_hal=yes, enable_hal=no)
+  PKG_CHECK_MODULES(HAL, hal >= 0.5 hal < 0.6 dbus-glib-1, enable_hal=yes, enable_hal=no)
   if test "x$enable_hal" != "xyes" -a "x$with_hal" = "xyes"; then
       AC_MSG_ERROR([HAL support explicitly requested but HAL couldn't be found])
   fi
 
   if test "x$enable_hal" = "xyes"; then
-  	AC_DEFINE(HAVE_HAL, 1, [Define if you have HAL support])
-	AC_SUBST(HAL_CFLAGS)
+    AC_DEFINE(HAVE_HAL, 1, [Define if you have HAL support])
+    AC_SUBST(HAL_CFLAGS)
     AC_SUBST(HAL_LIBS)
   fi	
 fi
@@ -471,13 +470,6 @@ AC_SUBST(mkdir_p) if test x"$mkdir_p" = "x"; then
 fi
 AC_SUBST(MKINSTALLDIRS)
 
-dnl DBUS
-PKG_CHECK_MODULES(DBUS, dbus-glib-1 >= $DBUS_MIN_REQS)
-
-DBUS_CFLAGS="$DBUS_CFLAGS -DDBUS_API_SUBJECT_TO_CHANGE"
-DBUS_GLIB_BIN="`$PKG_CONFIG --variable=exec_prefix dbus-glib-1`/bin"
-AC_SUBST(DBUS_GLIB_BIN)
-
 AM_GCONF_SOURCE_2
 
 dnl LIRC
diff --git a/data/Makefile.am b/data/Makefile.am
index ff52c31..d29ea7c 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -30,7 +30,7 @@ all-local: gschemas.compiled
 
 # Dbus service file
 servicedir = $(datadir)/dbus-1/services
-service_in_files = org.gnome.Rhythmbox.service.in
+service_in_files = org.gnome.Rhythmbox3.service.in
 service_DATA = $(service_in_files:.service.in=.service)
 
 # Rule to make the service file with bindir expanded
@@ -62,7 +62,7 @@ EXTRA_DIST = $(service_in_files)    \
 CLEANFILES = 					\
 	rhythmbox.desktop			\
 	rhythmbox-device.desktop		\
-	org.gnome.Rhythmbox.service		\
+	org.gnome.Rhythmbox3.service		\
 	playlists.xml				\
 	rhythmbox.desktop.in			\
 	rhythmbox-device.desktop.in		\
diff --git a/data/org.gnome.Rhythmbox.service.in b/data/org.gnome.Rhythmbox3.service.in
similarity index 60%
rename from data/org.gnome.Rhythmbox.service.in
rename to data/org.gnome.Rhythmbox3.service.in
index d68cba0..30c70d8 100644
--- a/data/org.gnome.Rhythmbox.service.in
+++ b/data/org.gnome.Rhythmbox3.service.in
@@ -1,3 +1,3 @@
 [D-BUS Service]
-Name=org.gnome.Rhythmbox
+Name=org.gnome.Rhythmbox3
 Exec= bindir@/rhythmbox
diff --git a/shell/Makefile.am b/shell/Makefile.am
index a14c718..7064391 100644
--- a/shell/Makefile.am
+++ b/shell/Makefile.am
@@ -123,13 +123,6 @@ librhythmbox_core_la_LDFLAGS = 				\
 	-export-dynamic -no-undefined
 librhythmbox_core_la_LIBTOOLFLAGS = --tag=disable-static
 
-rb-shell-glue.h: rb-shell.xml Makefile
-	$(LIBTOOL) --mode=execute $(DBUS_GLIB_BIN)/dbus-binding-tool --prefix=rb_shell --mode=glib-server --output=$@ $<
-rb-shell-binding.h: rb-shell.xml Makefile
-	$(LIBTOOL) --mode=execute $(DBUS_GLIB_BIN)/dbus-binding-tool --prefix=rb_shell --mode=glib-client --output=$@ $<
-
-BUILT_SOURCES += rb-shell-glue.h rb-shell-binding.h
-EXTRA_DIST += rb-shell.xml
 
 rhythmbox_LDADD = 					\
 	librhythmbox-core.la				\
diff --git a/shell/main.c b/shell/main.c
index 56fd488..6c6db54 100644
--- a/shell/main.c
+++ b/shell/main.c
@@ -29,96 +29,25 @@
 
 #include <config.h>
 
-#ifdef ENABLE_PYTHON
-/* pyconfig.h usually defines _XOPEN_SOURCE */
-#undef _XOPEN_SOURCE
-#define NO_IMPORT_PYGOBJECT
-#include <pygobject.h>
-#include "rb-python-module.h"
-
-/* make sure it's defined somehow */
-#ifndef _XOPEN_SOURCE
-#define _XOPEN_SOURCE
-#endif
-#endif
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <time.h>
-#include <string.h>
-#include <libintl.h>
-#include <locale.h>
-
 #include <glib/gi18n.h>
-#include <gdk/gdkx.h> /* For _get_user_time... */
 #include <gtk/gtk.h>
 
-#include <gst/gst.h>
-
-#ifdef WITH_RHYTHMDB_GDA
-#include <libgda/libgda.h>
-#endif
-
 #include <girepository.h>
 
-#include "rb-refstring.h"
 #include "rb-shell.h"
-#include "rb-shell-player.h"
-#include "rb-debug.h"
-#include "rb-dialog.h"
-#include "rb-file-helpers.h"
-#include "rb-stock-icons.h"
 #include "rb-util.h"
 #include "eggdesktopfile.h"
 #include "eggsmclient.h"
-
-#include <dbus/dbus-glib.h>
-#include "rb-shell-glue.h"
-#include "rb-playlist-manager.h"
-
-
-static gboolean debug           = FALSE;
-static char *debug_match        = NULL;
-static gboolean quit            = FALSE;
-static gboolean no_registration = FALSE;
-static gboolean no_update	= FALSE;
-static gboolean dry_run		= FALSE;
-static gboolean disable_plugins  = FALSE;
-static char *rhythmdb_file	= NULL;
-static char *playlists_file	= NULL;
-static char **remaining_args    = NULL;
-
-static gboolean load_uri_args (const char **args, GFunc handler, gpointer user_data);
-static void dbus_load_uri (const char *filename, DBusGProxy *proxy);
-static void database_load_complete (RBShell *shell, gpointer data);
-static void local_load_uri (const char *filename, RBShell *shell);
-
-static void main_shell_weak_ref_cb (gpointer data, GObject *objptr);
+#include "rb-debug.h"
 
 int
 main (int argc, char **argv)
 {
-	DBusGConnection *session_bus;
-	GError *error = NULL;
-	RBShell *rb_shell;
-	gboolean activated;
+	RBShell *shell;
 	gboolean autostarted;
 	char *desktop_file_path;
-
-	GOptionContext *context;
-	static const GOptionEntry options []  = {
-		{ "debug",           'd', 0, G_OPTION_ARG_NONE,         &debug,           N_("Enable debug output"), NULL },
-		{ "debug-match",     'D', 0, G_OPTION_ARG_STRING,       &debug_match,     N_("Enable debug output matching a specified string"), NULL },
-		{ "no-update",	       0, 0, G_OPTION_ARG_NONE,         &no_update,       N_("Do not update the library with file changes"), NULL },
-		{ "no-registration", 'n', 0, G_OPTION_ARG_NONE,         &no_registration, N_("Do not register the shell"), NULL },
-		{ "dry-run",	       0, 0, G_OPTION_ARG_NONE,         &dry_run,         N_("Don't save any data permanently (implies --no-registration)"), NULL },
-		{ "disable-plugins",   0, 0, G_OPTION_ARG_NONE,         &disable_plugins, N_("Disable loading of plugins"), NULL },
-		{ "rhythmdb-file",     0, 0, G_OPTION_ARG_STRING,       &rhythmdb_file,   N_("Path for database file to use"), NULL },
-		{ "playlists-file",    0, 0, G_OPTION_ARG_STRING,       &playlists_file,   N_("Path for playlists file to use"), NULL },
-		{ "quit",	     'q', 0, G_OPTION_ARG_NONE,         &quit,            N_("Quit Rhythmbox"), NULL },
-		{ G_OPTION_REMAINING,  0, 0, G_OPTION_ARG_STRING_ARRAY, &remaining_args,  NULL, N_("[URI...]") },
-		{ NULL }
-	};
+	int new_argc;
+	char **new_argv;
 
 	/* disable multidevice so clutter-gtk events work.
 	 * this needs to be done before gtk_open, so the visualizer
@@ -126,8 +55,8 @@ main (int argc, char **argv)
 	 */
 	gdk_disable_multidevice ();
 	g_thread_init (NULL);
-
-	rb_profile_start ("starting rhythmbox");
+	g_type_init ();
+	g_random_set_seed (time (0));
 
 	autostarted = (g_getenv ("DESKTOP_AUTOSTART_ID") != NULL);
 
@@ -141,49 +70,16 @@ main (int argc, char **argv)
 	egg_set_desktop_file (desktop_file_path);
 	g_free (desktop_file_path);
 
-	context = g_option_context_new (NULL);
-	g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
-
-	rb_profile_start ("initializing gstreamer");
-	g_option_context_add_group (context, gst_init_get_option_group ());
-	rb_profile_end ("initializing gstreamer");
-
-	g_option_context_add_group (context, egg_sm_client_get_option_group ());
-	g_option_context_add_group (context, gtk_get_option_group (TRUE));
-
 	setlocale (LC_ALL, NULL);
 
-	rb_profile_start ("parsing command line options");
-	if (g_option_context_parse (context, &argc, &argv, &error) == FALSE) {
-		g_print (_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
-			 error->message, argv[0]);
-		g_error_free (error);
-		g_option_context_free (context);
-		exit (1);
-	}
-	g_option_context_free (context);
-	rb_profile_end ("parsing command line options");
-
-	g_random_set_seed (time (0));
-
 #ifdef ENABLE_NLS
 	/* initialize i18n */
 	bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
 	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
 
-	/* ask for utf-8 message text from GStreamer too,
-	 * since it doesn't do that itself.
-	 */
-	bind_textdomain_codeset ("gstreamer-0.10", "UTF-8");
 	textdomain (GETTEXT_PACKAGE);
 #endif
 
-	if (!debug && debug_match)
-		rb_debug_init_match (debug_match);
-	else
-		rb_debug_init (debug);
-	rb_debug ("initializing Rhythmbox %s", VERSION);
-
 #if defined(USE_UNINSTALLED_DIRS)
 	g_irepository_prepend_search_path (SHARE_UNINSTALLED_BUILDDIR "/../bindings/gi");
 #endif
@@ -192,228 +88,15 @@ main (int argc, char **argv)
 	rb_threads_init ();
 	gdk_threads_enter ();
 
-	activated = FALSE;
-
-	rb_debug ("going to create DBus object");
-
-	dbus_g_thread_init ();
+	new_argc = argc;
+	new_argv = argv;
+	shell = rb_shell_new (autostarted, &argc, &argv);
 
-	session_bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
-	if (session_bus == NULL) {
-		g_warning ("couldn't connect to session bus: %s", (error) ? error->message : "(null)");
-		g_clear_error (&error);
-	} else if (!no_registration) {
-		guint request_name_reply;
-		int flags;
-#ifndef DBUS_NAME_FLAG_DO_NOT_QUEUE
-		flags = DBUS_NAME_FLAG_PROHIBIT_REPLACEMENT;
-#else
-		flags = DBUS_NAME_FLAG_DO_NOT_QUEUE;
-#endif
-
-		DBusGProxy *bus_proxy;
-
-		bus_proxy = dbus_g_proxy_new_for_name (session_bus,
-						       "org.freedesktop.DBus",
-						       "/org/freedesktop/DBus",
-						       "org.freedesktop.DBus");
-
-		if (!dbus_g_proxy_call (bus_proxy,
-					"RequestName",
-					&error,
-					G_TYPE_STRING,
-					"org.gnome.Rhythmbox",
-					G_TYPE_UINT,
-					flags,
-					G_TYPE_INVALID,
-					G_TYPE_UINT,
-					&request_name_reply,
-					G_TYPE_INVALID)) {
-			g_warning ("Failed to invoke RequestName: %s",
-				   error->message);
-		}
-		g_object_unref (bus_proxy);
+	g_application_run (G_APPLICATION (shell), new_argc, new_argv);
 
-		if (request_name_reply == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
-		    || request_name_reply == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER)
-			activated = FALSE;
-		else if (request_name_reply == DBUS_REQUEST_NAME_REPLY_EXISTS
-			 || request_name_reply == DBUS_REQUEST_NAME_REPLY_IN_QUEUE)
-			activated = TRUE;
-		else {
-			g_warning ("Got unhandled reply %u from RequestName",
-				   request_name_reply);
-			activated = FALSE;
-		}
-	}
-
-	if (!activated) {
-		if (quit) {
-			rb_debug ("was asked to quit, but no instance was running");
-			gdk_notify_startup_complete ();
-			exit (0);
-		}
-#ifdef WITH_RHYTHMDB_GDA
-		gda_init (PACKAGE, VERSION, argc, argv);
-#endif
-
-		rb_refstring_system_init ();
-
-#ifdef USE_UNINSTALLED_DIRS
-		rb_file_helpers_init (TRUE);
-#else
-		rb_file_helpers_init (FALSE);
-#endif
-		rb_debug ("Going to create a new shell");
-
-		rb_stock_icons_init ();
-
-		g_setenv ("PULSE_PROP_media.role", "music", TRUE);
-
-		rb_shell = rb_shell_new (no_registration, no_update, dry_run, autostarted, disable_plugins, rhythmdb_file, playlists_file);
-		g_object_weak_ref (G_OBJECT (rb_shell), main_shell_weak_ref_cb, NULL);
-		if (!no_registration && session_bus != NULL) {
-			dbus_g_object_type_install_info (RB_TYPE_SHELL, &dbus_glib_rb_shell_object_info);
-			dbus_g_connection_register_g_object (session_bus, "/org/gnome/Rhythmbox/Shell", G_OBJECT (rb_shell));
-
-			g_signal_connect (G_OBJECT (rb_shell),
-					  "database-load-complete",
-					  G_CALLBACK (database_load_complete),
-					  NULL);
-		}
-	} else if (!no_registration && session_bus != NULL) {
-		DBusGProxy *shell_proxy;
-		guint32 current_time;
-		current_time = gdk_x11_display_get_user_time (gdk_display_get_default ());
-		shell_proxy = dbus_g_proxy_new_for_name_owner (session_bus,
-							       "org.gnome.Rhythmbox",
-							       "/org/gnome/Rhythmbox/Shell",
-							       "org.gnome.Rhythmbox.Shell",
-							       &error);
-		if (!shell_proxy) {
-			g_warning ("Couldn't create proxy for Rhythmbox shell: %s",
-				   error->message);
-		} else {
-			if (quit) {
-				dbus_g_proxy_call_no_reply (shell_proxy, "quit",
-							    G_TYPE_INVALID);
-			} else {
-				load_uri_args ((const char **) remaining_args, (GFunc) dbus_load_uri, shell_proxy);
-				dbus_g_proxy_call_no_reply (shell_proxy, "present",
-							    G_TYPE_UINT, current_time,
-							    G_TYPE_INVALID);
-			}
-			g_object_unref (G_OBJECT (shell_proxy));
-		}
-	}
-
-	if (activated) {
-		gdk_notify_startup_complete ();
-	} else {
-
-		rb_profile_start ("mainloop");
-#ifdef ENABLE_PYTHON
-		if (rb_python_init_successful ()) {
-			pyg_begin_allow_threads;
-			gtk_main ();
-			pyg_end_allow_threads;
-		} else {
-			gtk_main ();
-		}
-#else
-		gtk_main ();
-#endif
-		rb_profile_end ("mainloop");
-
-		rb_debug ("out of toplevel loop");
-
-		rb_file_helpers_shutdown ();
-		rb_stock_icons_shutdown ();
-		rb_refstring_system_shutdown ();
-	}
-
-	gst_deinit ();
-
-	rb_debug ("THE END");
-	rb_profile_end ("starting rhythmbox");
+	g_object_unref (shell);
 
 	gdk_threads_leave ();
 
 	exit (0);
 }
-
-static gboolean
-load_uri_args (const char **args, GFunc handler, gpointer user_data)
-{
-	gboolean handled;
-	guint i;
-
-	handled = FALSE;
-	for (i = 0; args && args[i]; i++) {
-		GFile *file;
-		char *uri;
-
-		rb_debug ("examining argument %s", args[i]);
-
-		file = g_file_new_for_commandline_arg (args[i]);
-		uri = g_file_get_uri (file);
-
-		/*
-		 * rb_uri_exists won't work if the location isn't mounted.
-		 * however, things that are interesting to mount are generally
-		 * non-local, so we'll process them anyway.
-		 */
-		if (rb_uri_is_local (uri) == FALSE || rb_uri_exists (uri)) {
-			handler (uri, user_data);
-		}
-		g_free (uri);
-		g_object_unref (file);
-
-		handled = TRUE;
-	}
-	return handled;
-}
-
-static void
-dbus_load_uri (const char *filename, DBusGProxy *proxy)
-{
-	GError *error = NULL;
-	rb_debug ("Sending loadURI for %s", filename);
-	if (!dbus_g_proxy_call (proxy, "loadURI", &error,
-				G_TYPE_STRING, filename,
-				G_TYPE_BOOLEAN, TRUE,
-				G_TYPE_INVALID,
-				G_TYPE_INVALID)) {
-		g_printerr ("Failed to load %s: %s",
-			    filename, error->message);
-		g_error_free (error);
-	}
-}
-
-static void
-main_shell_weak_ref_cb (gpointer data, GObject *objptr)
-{
-	rb_debug ("caught shell finalization");
-	gtk_main_quit ();
-}
-
-static void
-database_load_complete (RBShell *shell, gpointer data)
-{
-	load_uri_args ((const char **) remaining_args, (GFunc) local_load_uri, shell);
-}
-
-static void
-local_load_uri (const char *filename, RBShell *shell)
-{
-	GError *error = NULL;
-	rb_debug ("Using load_uri for %s", filename);
-	if (!rb_shell_load_uri (shell, filename, TRUE, &error)) {
-		if (error != NULL) {
-			g_printerr ("Failed to load %s: %s",
-				    filename, error->message);
-			g_error_free (error);
-		}
-	}
-}
-
diff --git a/shell/rb-shell.c b/shell/rb-shell.c
index 037dd0d..924bc9f 100644
--- a/shell/rb-shell.c
+++ b/shell/rb-shell.c
@@ -52,6 +52,8 @@
 #include <libpeas/peas.h>
 #include <libpeas-gtk/peas-gtk.h>
 
+#include <gst/gst.h>
+
 #ifdef HAVE_MMKEYS
 #include <X11/XF86keysym.h>
 #endif /* HAVE_MMKEYS */
@@ -113,6 +115,9 @@ static void rb_shell_get_property (GObject *object,
 				   guint prop_id,
 				   GValue *value,
 				   GParamSpec *pspec);
+static void rb_shell_activate (GApplication *app);
+static void rb_shell_open (GApplication *app, GFile **files, int n_files, const char *hint);
+static gboolean rb_shell_local_command_line (GApplication *app, gchar ***args, int *exit_status);
 static gboolean rb_shell_get_visibility (RBShell *shell);
 static gboolean rb_shell_window_state_cb (GtkWidget *widget,
 					  GdkEventWindowState *event,
@@ -244,16 +249,14 @@ enum
 	VISIBILITY_CHANGED,
 	VISIBILITY_CHANGING,
 	CREATE_SONG_INFO,
-	REMOVABLE_MEDIA_SCAN_FINISHED,
 	NOTIFY_PLAYING_ENTRY,
 	NOTIFY_CUSTOM,
-	DATABASE_LOAD_COMPLETE,
 	LAST_SIGNAL
 };
 
 static guint rb_shell_signals[LAST_SIGNAL] = { 0 };
 
-G_DEFINE_TYPE (RBShell, rb_shell, G_TYPE_OBJECT)
+G_DEFINE_TYPE (RBShell, rb_shell, GTK_TYPE_APPLICATION)
 
 struct _RBShellPrivate
 {
@@ -404,1256 +407,1550 @@ static GtkToggleActionEntry rb_shell_toggle_entries [] =
 static guint rb_shell_n_toggle_entries = G_N_ELEMENTS (rb_shell_toggle_entries);
 
 static void
-rb_shell_class_init (RBShellClass *klass)
+rb_shell_activate (GApplication *app)
 {
-        GObjectClass *object_class = (GObjectClass *) klass;
+	rb_shell_present (RB_SHELL (app), gtk_get_current_event_time (), NULL);
+}
 
-	object_class->set_property = rb_shell_set_property;
-	object_class->get_property = rb_shell_get_property;
-        object_class->finalize = rb_shell_finalize;
-	object_class->constructed = rb_shell_constructed;
+static void
+rb_shell_open (GApplication *app, GFile **files, int n_files, const char *hint)
+{
+	int i;
 
-	klass->visibility_changing = rb_shell_visibility_changing;
+	for (i = 0; i < n_files; i++) {
+		char *uri;
+
+		uri = g_file_get_uri (files[i]);
+
+		/*
+		 * rb_uri_exists won't work if the location isn't mounted.
+		 * however, things that are interesting to mount are generally
+		 * non-local, so we'll process them anyway.
+		 */
+		if (rb_uri_is_local (uri) == FALSE || rb_uri_exists (uri)) {
+			rb_shell_load_uri (RB_SHELL (app), uri, TRUE, NULL);
+		}
+		g_free (uri);
+	}
+}
+
+static void
+load_state_changed_cb (GActionGroup *action_group, const char *action_name, GVariant *state, GPtrArray *files)
+{
+	gboolean loaded;
+	gboolean scanned;
+
+	if (g_strcmp0 (action_name, "LoadURI") != 0) {
+		return;
+	}
+
+	g_variant_get (state, "(bb)", &loaded, &scanned);
+	if (loaded) {
+		rb_debug ("opening files now");
+		g_signal_handlers_disconnect_by_func (action_group, load_state_changed_cb, files);
+
+		g_application_open (G_APPLICATION (action_group), (GFile **)files->pdata, files->len, "");
+		g_ptr_array_free (files, TRUE);
+	}
+}
+
+
+
+static GMountOperation *
+rb_shell_create_mount_op_cb (RhythmDB *db, RBShell *shell)
+{
+	/* we don't want the operation to be modal, so we don't associate it with the window. */
+	GMountOperation *op = gtk_mount_operation_new (NULL);
+	gtk_mount_operation_set_screen (GTK_MOUNT_OPERATION (op),
+					gtk_window_get_screen (GTK_WINDOW (shell->priv->window)));
+	return op;
+}
+
+static void
+construct_db (RBShell *shell)
+{
+	char *pathname;
+
+	/* Initialize the database */
+	rb_debug ("creating database object");
+	rb_profile_start ("creating database object");
+
+	if (shell->priv->rhythmdb_file) {
+		pathname = g_strdup (shell->priv->rhythmdb_file);
+	} else {
+		pathname = rb_find_user_data_file ("rhythmdb.xml");
+	}
 
-	/**
-	 * RBShell:no-registration:
-	 *
-	 * If %TRUE, disable single-instance features.
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_NO_REGISTRATION,
-					 g_param_spec_boolean ("no-registration",
-							       "no-registration",
-							       "Whether or not to register",
-							       FALSE,
-							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-	/**
-	 * RBShell:no-update:
-	 *
-	 * If %TRUE, don't update the database.
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_NO_UPDATE,
-					 g_param_spec_boolean ("no-update",
-							       "no-update",
-							       "Whether or not to update the library",
-							       FALSE,
-							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-	/**
-	 * RBShell:dry-run:
-	 *
-	 * If TRUE, don't write back file metadata changes.
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_DRY_RUN,
-					 g_param_spec_boolean ("dry-run",
-							       "dry-run",
-							       "Whether or not this is a dry run",
-							       FALSE,
-							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-	/**
-	 * RBShell:rhythmdb-file:
-	 *
-	 * The path to the rhythmdb file
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_RHYTHMDB_FILE,
-					 g_param_spec_string ("rhythmdb-file",
-							      "rhythmdb-file",
-							      "The RhythmDB file to use",
 #ifdef WITH_RHYTHMDB_TREE
-							      "rhythmdb.xml",
+	shell->priv->db = rhythmdb_tree_new (pathname);
 #elif defined(WITH_RHYTHMDB_GDA)
-							      "rhythmdb.sqlite", /* FIXME: correct extension? */
+	shell->priv->db = rhythmdb_gda_new (pathname);
 #endif
-							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_free (pathname);
 
-	/**
-	 * RBShell:playlists-file:
-	 *
-	 * The path to the playlist file
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_PLAYLISTS_FILE,
-					 g_param_spec_string ("playlists-file",
-							      "playlists-file",
-							      "The playlists file to use",
-							      "playlists.xml",
-							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	if (shell->priv->dry_run)
+		g_object_set (shell->priv->db, "dry-run", TRUE, NULL);
+	if (shell->priv->no_update)
+		g_object_set (shell->priv->db, "no-update", TRUE, NULL);
 
+	g_signal_connect_object (G_OBJECT (shell->priv->db), "load-complete",
+				 G_CALLBACK (rb_shell_load_complete_cb), shell,
+				 0);
+	g_signal_connect_object (G_OBJECT (shell->priv->db), "create-mount-op",
+				 G_CALLBACK (rb_shell_create_mount_op_cb), shell,
+				 0);
 
-	/**
-	 * RBShell:selected-page:
-	 *
-	 * The currently selected display page
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_SELECTED_PAGE,
-					 g_param_spec_object ("selected-page",
-							      "selected-page",
-							      "Display page which is currently selected",
-							      RB_TYPE_DISPLAY_PAGE,
-							      G_PARAM_READABLE));
-	/**
-	 * RBShell:db:
-	 *
-	 * The #RhythmDB instance
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_DB,
-					 g_param_spec_object ("db",
-							      "RhythmDB",
-							      "RhythmDB object",
-							      RHYTHMDB_TYPE,
-							      G_PARAM_READABLE));
-	/**
-	 * RBShell:ui-manager:
-	 *
-	 * The #GtkUIManager instance
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_UI_MANAGER,
-					 g_param_spec_object ("ui-manager",
-							      "GtkUIManager",
-							      "GtkUIManager object",
-							      GTK_TYPE_UI_MANAGER,
-							      G_PARAM_READABLE));
-	/**
-	 * RBShell:clipboard:
-	 *
-	 * The #RBShellClipboard instance
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_CLIPBOARD,
-					 g_param_spec_object ("clipboard",
-							      "RBShellClipboard",
-							      "RBShellClipboard object",
-							      RB_TYPE_SHELL_CLIPBOARD,
-							      G_PARAM_READABLE));
-	/**
-	 * RBShell:playlist-manager:
-	 *
-	 * The #RBPlaylistManager instance
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_PLAYLIST_MANAGER,
-					 g_param_spec_object ("playlist-manager",
-							      "RBPlaylistManager",
-							      "RBPlaylistManager object",
-							      RB_TYPE_PLAYLIST_MANAGER,
-							      G_PARAM_READABLE));
-	/**
-	 * RBShell:shell-player:
-	 *
-	 * The #RBShellPlayer instance
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_SHELL_PLAYER,
-					 g_param_spec_object ("shell-player",
-							      "RBShellPlayer",
-							      "RBShellPlayer object",
-							      RB_TYPE_SHELL_PLAYER,
-							      G_PARAM_READABLE));
-	/**
-	 * RBShell:removable-media-manager:
-	 *
-	 * The #RBRemovableMediaManager instance
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_REMOVABLE_MEDIA_MANAGER,
-					 g_param_spec_object ("removable-media-manager",
-							      "RBRemovableMediaManager",
-							      "RBRemovableMediaManager object",
-							      RB_TYPE_REMOVABLE_MEDIA_MANAGER,
-							      G_PARAM_READABLE));
-	/**
-	 * RBShell:window:
-	 *
-	 * The main Rhythmbox window.
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_WINDOW,
-					 g_param_spec_object ("window",
-							      "GtkWindow",
-							      "GtkWindow object",
-							      GTK_TYPE_WINDOW,
-							      G_PARAM_READABLE));
-	/**
-	 * RBShell:prefs:
-	 *
-	 * The #RBShellPreferences instance
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_PREFS,
-					 g_param_spec_object ("prefs",
-							      "RBShellPreferences",
-							      "RBShellPreferences object",
-							      RB_TYPE_SHELL_PREFERENCES,
-							      G_PARAM_READABLE));
-	/**
-	 * RBShell:queue-source:
-	 *
-	 * The play queue source
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_QUEUE_SOURCE,
-					 g_param_spec_object ("queue-source",
-							      "queue-source",
-							      "Queue source",
-							      RB_TYPE_PLAY_QUEUE_SOURCE,
-							      G_PARAM_READABLE));
-	/**
-	 * RBShell:library-source:
-	 *
-	 * The library source
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_LIBRARY_SOURCE,
-					 g_param_spec_object ("library-source",
-							      "library-source",
-							      "Library source",
-							      RB_TYPE_LIBRARY_SOURCE,
-							      G_PARAM_READABLE));
-	/**
-	 * RBShell:display-page-model:
-	 *
-	 * The model underlying the display page tree
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_DISPLAY_PAGE_MODEL,
-					 g_param_spec_object ("display-page-model",
-							      "display-page-model",
-							      "RBDisplayPageModel",
-							      RB_TYPE_DISPLAY_PAGE_MODEL,
-							      G_PARAM_READABLE));
-
-	/**
-	 * RBShell:display-page-tree:
-	 *
-	 * The #RBDisplayPageTree instance
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_DISPLAY_PAGE_TREE,
-					 g_param_spec_object ("display-page-tree",
-							      "display-page-tree",
-							      "RBDisplayPageTree",
-							      RB_TYPE_DISPLAY_PAGE_TREE,
-							      G_PARAM_READABLE));
-
-	/**
-	 * RBShell:visibility:
-	 *
-	 * Whether the main window is currently visible.
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_VISIBILITY,
-					 g_param_spec_boolean ("visibility",
-							       "visibility",
-							       "Current window visibility",
-							       TRUE,
-							       G_PARAM_READWRITE));
-	/**
-	 * RBShell:source-header:
-	 *
-	 * The #RBSourceHeader instance
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_SOURCE_HEADER,
-					 g_param_spec_object ("source-header",
-							      "source header widget",
-							      "RBSourceHeader",
-							      RB_TYPE_SOURCE_HEADER,
-							      G_PARAM_READABLE));
+	rb_profile_end ("creating database object");
+}
 
-	/**
-	 * RBShell:track-transfer-queue:
-	 *
-	 * The #RBTrackTransferQueue instance
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_TRACK_TRANSFER_QUEUE,
-					 g_param_spec_object ("track-transfer-queue",
-							      "RBTrackTransferQueue",
-							      "RBTrackTransferQueue object",
-							      RB_TYPE_TRACK_TRANSFER_QUEUE,
-							      G_PARAM_READABLE));
-	/**
-	 * RBShell:autostarted:
-	 *
-	 * Whether Rhythmbox was automatically started by the session manager
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_AUTOSTARTED,
-					 g_param_spec_boolean ("autostarted",
-							       "autostarted",
-							       "TRUE if autostarted",
-							       FALSE,
-							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+static void
+construct_widgets (RBShell *shell)
+{
+	GtkWindow *win;
 
-	/**
-	 * RBShell:disable-plugins:
-	 *
-	 * If %TRUE, disable plugins
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_DISABLE_PLUGINS,
-					 g_param_spec_boolean ("disable-plugins",
-							       "disable-plugins",
-							       "Whether or not to disable plugins",
-							       FALSE,
-							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	rb_profile_start ("constructing widgets");
 
-	/**
-	 * RBShell::visibility-changed:
-	 * @shell: the #RBShell
-	 * @visibile: new visibility
-	 *
-	 * Emitted after the visibility of the main Rhythmbox window has
-	 * changed.
-	 */
-	rb_shell_signals[VISIBILITY_CHANGED] =
-		g_signal_new ("visibility_changed",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_LAST,
-			      G_STRUCT_OFFSET (RBShellClass, visibility_changed),
-			      NULL, NULL,
-			      g_cclosure_marshal_VOID__BOOLEAN,
-			      G_TYPE_NONE,
-			      1,
-			      G_TYPE_BOOLEAN);
-	/**
-	 * RBShell::visibility-changing:
-	 * @shell: the #RBShell
-	 * @initial: if %TRUE, this is the initial visibility for the window
-	 * @visible: new shell visibility
-	 *
-	 * Emitted before the visibility of the main window changes.  The return
-	 * value overrides the visibility setting.  If multiple signal handlers
-	 * are attached, the last one wins.
-	 */
-	rb_shell_signals[VISIBILITY_CHANGING] =
-		g_signal_new ("visibility_changing",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_LAST,
-			      G_STRUCT_OFFSET (RBShellClass, visibility_changing),
-			      NULL, NULL,
-			      rb_marshal_BOOLEAN__BOOLEAN_BOOLEAN,
-			      G_TYPE_BOOLEAN,
-			      2,
-			      G_TYPE_BOOLEAN,
-			      G_TYPE_BOOLEAN);
+	/* initialize UI */
+	win = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));
+	gtk_window_set_title (win, _("Rhythmbox"));
 
-	/**
-	 * RBShell::create-song-info:
-	 * @shell: the #RBShell
-	 * @song_info: the new #RBSongInfo window
-	 * @multi: if %TRUE, the song info window is for multiple entries
-	 *
-	 * Emitted when creating a new #RBSongInfo window.  Signal handlers can
-	 * add pages to the song info window notebook to display additional
-	 * information.
-	 */
-	rb_shell_signals[CREATE_SONG_INFO] =
-		g_signal_new ("create_song_info",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_LAST,
-			      G_STRUCT_OFFSET (RBShellClass, create_song_info),
-			      NULL, NULL,
-			      rb_marshal_VOID__OBJECT_BOOLEAN,
-			      G_TYPE_NONE,
-			      2,
-			      RB_TYPE_SONG_INFO, G_TYPE_BOOLEAN);
-	/**
-	 * RBShell::removable-media-scan-finished:
-	 * @shell: the #RBShell
-	 *
-	 * Emitted when the initial scan for removable media devices is
-	 * complete.  This is intended to allow plugins to request a
-	 * device scan only if the scan on startup has already been done,
-	 * but it isn't very useful for that.
-	 * See #RBRemovableMediaManager:scanned for a better approach to
-	 * this problem.
-	 */
-	rb_shell_signals[REMOVABLE_MEDIA_SCAN_FINISHED] =
-		g_signal_new ("removable_media_scan_finished",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_LAST,
-			      G_STRUCT_OFFSET (RBShellClass, removable_media_scan_finished),
-			      NULL, NULL,
-			      g_cclosure_marshal_VOID__VOID,
-			      G_TYPE_NONE,
-			      0);
-	/**
-	 * RBShell::notify-playing-entry:
-	 * @shell: the #RBShell
-	 *
-	 * Emitted when a notification should be displayed showing the current
-	 * playing entry.
-	 */
-	rb_shell_signals[NOTIFY_PLAYING_ENTRY] =
-		g_signal_new ("notify-playing-entry",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_LAST,
-			      0,
-			      NULL, NULL,
-			      g_cclosure_marshal_VOID__BOOLEAN,
-			      G_TYPE_NONE,
-			      1,
-			      G_TYPE_BOOLEAN);
-	/**
-	 * RBShell::notify-custom:
-	 * @shell: the #RBShell
-	 * @timeout: length of time (in seconds) to display the notification
-	 * @primary: main notification text
-	 * @secondary: secondary notification text
-	 * @image_uri: URI for an image to include in the notification (optional)
-	 * @requested: if %TRUE, the notification was triggered by an explicit user action
-	 *
-	 * Emitted when a custom notification should be displayed to the user.
-	 */
-	rb_shell_signals[NOTIFY_CUSTOM] =
-		g_signal_new ("notify-custom",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_LAST,
-			      0,
-			      NULL, NULL,
-			      rb_marshal_VOID__UINT_STRING_STRING_STRING_BOOLEAN,
-			      G_TYPE_NONE,
-			      5,
-			      G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
-	/**
-	 * RBShell::database-load-complete:
-	 * @shell: the #RBShell
-	 *
-	 * Emitted when the database has been loaded.  This is intended to allow
-	 * DBus clients that start a new instance of the application to wait until
-	 * a reasonable amount of state has been loaded before making further requests.
-	 */
-	rb_shell_signals[DATABASE_LOAD_COMPLETE] =
-		g_signal_new ("database-load-complete",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_LAST,
-			      G_STRUCT_OFFSET (RBShellClass, database_load_complete),
-			      NULL, NULL,
-			      g_cclosure_marshal_VOID__VOID,
-			      G_TYPE_NONE,
-			      0);
+	shell->priv->window = GTK_WIDGET (win);
+	shell->priv->iconified = FALSE;
+	g_signal_connect_object (G_OBJECT (win), "window-state-event",
+				 G_CALLBACK (rb_shell_window_state_cb),
+				 shell, 0);
 
-	g_type_class_add_private (klass, sizeof (RBShellPrivate));
-}
+	g_signal_connect_object (G_OBJECT (win), "configure-event",
+				 G_CALLBACK (rb_shell_window_configure_cb),
+				 shell, 0);
 
-static void
-rb_shell_init (RBShell *shell)
-{
-	shell->priv = G_TYPE_INSTANCE_GET_PRIVATE (shell, RB_TYPE_SHELL, RBShellPrivate);
+	/* connect after, so that things can affect behaviour */
+	g_signal_connect_object (G_OBJECT (win), "delete_event",
+				 G_CALLBACK (rb_shell_window_delete_cb),
+				 shell, G_CONNECT_AFTER);
 
-	rb_user_data_dir ();
+	gtk_widget_add_events (GTK_WIDGET (win), GDK_KEY_PRESS_MASK);
+	g_signal_connect_object (G_OBJECT(win), "key_press_event",
+				 G_CALLBACK (rb_shell_key_press_event_cb), shell, 0);
 
-        rb_shell_session_init (shell);
-}
+	rb_debug ("shell: initializing shell services");
 
-static void
-rb_shell_set_property (GObject *object,
-		       guint prop_id,
-		       const GValue *value,
-		       GParamSpec *pspec)
-{
-	RBShell *shell = RB_SHELL (object);
+	shell->priv->podcast_manager = rb_podcast_manager_new (shell->priv->db);
+	shell->priv->track_transfer_queue = rb_track_transfer_queue_new (shell);
+	shell->priv->ui_manager = gtk_ui_manager_new ();
+	shell->priv->source_ui_merge_id = gtk_ui_manager_new_merge_id (shell->priv->ui_manager);
 
-	switch (prop_id)
-	{
-	case PROP_NO_REGISTRATION:
-		shell->priv->no_registration = g_value_get_boolean (value);
-		break;
-	case PROP_NO_UPDATE:
-		shell->priv->no_update = g_value_get_boolean (value);
-		break;
-	case PROP_DRY_RUN:
-		shell->priv->dry_run = g_value_get_boolean (value);
-		if (shell->priv->dry_run)
-			shell->priv->no_registration = TRUE;
-		break;
-	case PROP_RHYTHMDB_FILE:
-		g_free (shell->priv->rhythmdb_file);
-		shell->priv->rhythmdb_file = g_value_dup_string (value);
-		break;
-	case PROP_PLAYLISTS_FILE:
-		g_free (shell->priv->playlists_file);
-		shell->priv->playlists_file = g_value_dup_string (value);
-		break;
-	case PROP_VISIBILITY:
-		rb_shell_set_visibility (shell, FALSE, g_value_get_boolean (value));
-		break;
-	case PROP_AUTOSTARTED:
-		shell->priv->autostarted = g_value_get_boolean (value);
-		break;
-	case PROP_DISABLE_PLUGINS:
-		shell->priv->disable_plugins = g_value_get_boolean (value);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-static void
-rb_shell_get_property (GObject *object,
-		       guint prop_id,
-		       GValue *value,
-		       GParamSpec *pspec)
-{
-	RBShell *shell = RB_SHELL (object);
-
-	switch (prop_id)
-	{
-	case PROP_NO_REGISTRATION:
-		g_value_set_boolean (value, shell->priv->no_registration);
-		break;
-	case PROP_NO_UPDATE:
-		g_value_set_boolean (value, shell->priv->no_update);
-		break;
-	case PROP_DRY_RUN:
-		g_value_set_boolean (value, shell->priv->dry_run);
-		break;
-	case PROP_RHYTHMDB_FILE:
-		g_value_set_string (value, shell->priv->rhythmdb_file);
-		break;
-	case PROP_PLAYLISTS_FILE:
-		g_value_set_string (value, shell->priv->playlists_file);
-		break;
-	case PROP_DB:
-		g_value_set_object (value, shell->priv->db);
-		break;
-	case PROP_UI_MANAGER:
-		g_value_set_object (value, shell->priv->ui_manager);
-		break;
-	case PROP_CLIPBOARD:
-		g_value_set_object (value, shell->priv->clipboard_shell);
-		break;
-	case PROP_PLAYLIST_MANAGER:
-		g_value_set_object (value, shell->priv->playlist_manager);
-		break;
-	case PROP_SHELL_PLAYER:
-		g_value_set_object (value, shell->priv->player_shell);
-		break;
-	case PROP_REMOVABLE_MEDIA_MANAGER:
-		g_value_set_object (value, shell->priv->removable_media_manager);
-		break;
-	case PROP_SELECTED_PAGE:
-		g_value_set_object (value, shell->priv->selected_page);
-		break;
-	case PROP_WINDOW:
-		g_value_set_object (value, shell->priv->window);
-		break;
-	case PROP_PREFS:
-		/* create the preferences window the first time we need it */
-		if (shell->priv->prefs == NULL) {
-			GtkWidget *content;
+	shell->priv->player_shell = rb_shell_player_new (shell->priv->db,
+							 shell->priv->ui_manager,
+							 shell->priv->actiongroup);
+	g_signal_connect_object (G_OBJECT (shell->priv->player_shell),
+				 "playing-source-changed",
+				 G_CALLBACK (rb_shell_playing_source_changed_cb),
+				 shell, 0);
+	g_signal_connect_object (G_OBJECT (shell->priv->player_shell),
+				 "notify::playing-from-queue",
+				 G_CALLBACK (rb_shell_playing_from_queue_cb),
+				 shell, 0);
+	g_signal_connect_object (G_OBJECT (shell->priv->player_shell),
+				 "window_title_changed",
+				 G_CALLBACK (rb_shell_player_window_title_changed_cb),
+				 shell, 0);
+	shell->priv->clipboard_shell = rb_shell_clipboard_new (shell->priv->actiongroup,
+							       shell->priv->ui_manager,
+							       shell->priv->db);
+	shell->priv->source_header = rb_source_header_new (shell->priv->ui_manager,
+							   shell->priv->actiongroup);
+	gtk_widget_show_all (GTK_WIDGET (shell->priv->source_header));
 
-			shell->priv->prefs = rb_shell_preferences_new (shell->priv->sources);
+	shell->priv->display_page_tree = rb_display_page_tree_new (shell);
+	gtk_widget_show_all (GTK_WIDGET (shell->priv->display_page_tree));
+	g_signal_connect_object (shell->priv->display_page_tree, "drop-received",
+				 G_CALLBACK (display_page_tree_drag_received_cb), shell, 0);
+	g_object_get (shell->priv->display_page_tree, "model", &shell->priv->display_page_model, NULL);
+	rb_display_page_group_add_core_groups (G_OBJECT (shell), shell->priv->display_page_model);
 
-			gtk_window_set_transient_for (GTK_WINDOW (shell->priv->prefs),
-						      GTK_WINDOW (shell->priv->window));
-			content = gtk_dialog_get_content_area (GTK_DIALOG (shell->priv->prefs));
-			gtk_widget_show_all (content);
-		}
-		g_value_set_object (value, shell->priv->prefs);
-		break;
-	case PROP_QUEUE_SOURCE:
-		g_value_set_object (value, shell->priv->queue_source);
-		break;
-	case PROP_LIBRARY_SOURCE:
-		g_value_set_object (value, shell->priv->library_source);
-		break;
-	case PROP_DISPLAY_PAGE_MODEL:
-		g_value_set_object (value, shell->priv->display_page_model);
-		break;
-	case PROP_DISPLAY_PAGE_TREE:
-		g_value_set_object (value, shell->priv->display_page_tree);
-		break;
-	case PROP_VISIBILITY:
-		g_value_set_boolean (value, rb_shell_get_visibility (shell));
-		break;
-	case PROP_SOURCE_HEADER:
-		g_value_set_object (value, shell->priv->source_header);
-		break;
-	case PROP_TRACK_TRANSFER_QUEUE:
-		g_value_set_object (value, shell->priv->track_transfer_queue);
-		break;
-	case PROP_AUTOSTARTED:
-		g_value_set_boolean (value, shell->priv->autostarted);
-		break;
-	case PROP_DISABLE_PLUGINS:
-		g_value_set_boolean (value, shell->priv->disable_plugins);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
+	shell->priv->statusbar = rb_statusbar_new (shell->priv->db,
+						   shell->priv->ui_manager,
+						   shell->priv->track_transfer_queue);
+	g_object_set (shell->priv->player_shell, "statusbar", shell->priv->statusbar, NULL);
+	gtk_widget_show (GTK_WIDGET (shell->priv->statusbar));
 
-static gboolean
-rb_shell_sync_state (RBShell *shell)
-{
-	if (shell->priv->dry_run) {
-		rb_debug ("in dry-run mode, not syncing state");
-		return FALSE;
-	}
+	g_signal_connect_object (shell->priv->display_page_tree, "selected",
+				 G_CALLBACK (display_page_selected_cb), shell, 0);
 
-	if (!shell->priv->load_complete) {
-		rb_debug ("load incomplete, not syncing state");
-		return FALSE;
-	}
+	shell->priv->notebook = gtk_notebook_new ();
+	gtk_widget_show (shell->priv->notebook);
+	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (shell->priv->notebook), FALSE);
+	gtk_notebook_set_show_border (GTK_NOTEBOOK (shell->priv->notebook), FALSE);
+	g_signal_connect_object (shell->priv->display_page_tree,
+				 "size-allocate",
+				 G_CALLBACK (paned_size_allocate_cb),
+				 shell, 0);
 
-	rb_debug ("saving playlists");
-	rb_playlist_manager_save_playlists (shell->priv->playlist_manager,
-					    TRUE);
+	shell->priv->queue_source = RB_PLAYLIST_SOURCE (rb_play_queue_source_new (shell));
+	g_object_set (shell->priv->player_shell, "queue-source", shell->priv->queue_source, NULL);
+	g_object_set (shell->priv->clipboard_shell, "queue-source", shell->priv->queue_source, NULL);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->queue_source), RB_DISPLAY_PAGE_GROUP_LIBRARY);
+	g_object_get (shell->priv->queue_source, "sidebar", &shell->priv->queue_sidebar, NULL);
+	gtk_widget_show_all (shell->priv->queue_sidebar);
+	gtk_widget_set_no_show_all (shell->priv->queue_sidebar, TRUE);
 
-	rb_debug ("saving db");
-	rhythmdb_save (shell->priv->db);
-	return FALSE;
-}
+	/* places for plugins to put UI */
+	shell->priv->top_container = GTK_BOX (gtk_vbox_new (FALSE, 0));
+	shell->priv->bottom_container = GTK_BOX (gtk_vbox_new (FALSE, 0));
+	shell->priv->sidebar_container = GTK_BOX (gtk_vbox_new (FALSE, 0));
+	shell->priv->right_sidebar_container = GTK_BOX (gtk_vbox_new (FALSE, 0));
 
-static gboolean
-idle_save_rhythmdb (RBShell *shell)
-{
-	rhythmdb_save (shell->priv->db);
+	/* set up sidebars */
+	shell->priv->paned = gtk_hpaned_new ();
+	shell->priv->right_paned = gtk_hpaned_new ();
+	gtk_widget_show_all (shell->priv->right_paned);
+	g_signal_connect_object (G_OBJECT (shell->priv->right_paned),
+				 "size-allocate",
+				 G_CALLBACK (paned_size_allocate_cb),
+				 shell, 0);
+	gtk_widget_set_no_show_all (shell->priv->right_paned, TRUE);
+	{
+		GtkWidget *vbox2 = gtk_vbox_new (FALSE, 0);
 
-	shell->priv->save_db_id = 0;
+		shell->priv->queue_paned = gtk_vpaned_new ();
+		gtk_paned_pack1 (GTK_PANED (shell->priv->queue_paned),
+				 GTK_WIDGET (shell->priv->display_page_tree),
+				 FALSE, TRUE);
+		gtk_paned_pack2 (GTK_PANED (shell->priv->queue_paned),
+				 shell->priv->queue_sidebar,
+				 TRUE, TRUE);
+		gtk_container_child_set (GTK_CONTAINER (shell->priv->queue_paned),
+					 GTK_WIDGET (shell->priv->display_page_tree),
+					 "resize", FALSE,
+					 NULL);
 
-	return FALSE;
-}
+		gtk_box_pack_start (GTK_BOX (vbox2),
+				    GTK_WIDGET (shell->priv->source_header),
+				    FALSE, FALSE, 3);
+		gtk_box_pack_start (GTK_BOX (vbox2),
+				    shell->priv->notebook,
+				    TRUE, TRUE, 0);
+		gtk_box_pack_start (GTK_BOX (vbox2),
+				    GTK_WIDGET (shell->priv->bottom_container),
+				    FALSE, FALSE, 0);
 
-static gboolean
-idle_save_playlist_manager (RBShell *shell)
-{
-	GDK_THREADS_ENTER ();
-	rb_playlist_manager_save_playlists (shell->priv->playlist_manager,
-					    FALSE);
-	GDK_THREADS_LEAVE ();
+		gtk_paned_pack1 (GTK_PANED (shell->priv->right_paned),
+				 vbox2, TRUE, TRUE);
+		gtk_paned_pack2 (GTK_PANED (shell->priv->right_paned),
+				 GTK_WIDGET (shell->priv->right_sidebar_container),
+				 FALSE, FALSE);
+		gtk_widget_hide (GTK_WIDGET(shell->priv->right_sidebar_container));
 
-	return TRUE;
-}
+		gtk_box_pack_start (shell->priv->sidebar_container,
+				    shell->priv->queue_paned,
+				    TRUE, TRUE, 0);
+		gtk_paned_pack1 (GTK_PANED (shell->priv->paned),
+				 GTK_WIDGET (shell->priv->sidebar_container),
+				 FALSE, TRUE);
+		gtk_paned_pack2 (GTK_PANED (shell->priv->paned),
+				 shell->priv->right_paned,
+				 TRUE, TRUE);
+		gtk_widget_show (vbox2);
+	}
 
-static void
-rb_shell_shutdown (RBShell *shell)
-{
-	GdkDisplay *display;
+	g_signal_connect_object (G_OBJECT (shell->priv->queue_paned),
+				 "size-allocate",
+				 G_CALLBACK (paned_size_allocate_cb),
+				 shell, 0);
+	gtk_widget_show (shell->priv->paned);
 
-	if (shell->priv->shutting_down)
-		return;
-	shell->priv->shutting_down = TRUE;
+	shell->priv->main_vbox = gtk_vbox_new (FALSE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (shell->priv->main_vbox), 0);
+	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->player_shell), FALSE, TRUE, 6);
+	gtk_widget_show (GTK_WIDGET (shell->priv->player_shell));
 
-	/* Hide the main window and tray icon as soon as possible */
-	display = gtk_widget_get_display (shell->priv->window);
-	gtk_widget_hide (shell->priv->window);
-	gdk_display_sync (display);
+	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->top_container), FALSE, TRUE, 0);
+	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), shell->priv->paned, TRUE, TRUE, 0);
+	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->statusbar), FALSE, TRUE, 0);
+	gtk_widget_show_all (shell->priv->main_vbox);
 
-	if (shell->priv->plugin_engine != NULL) {
-		g_object_unref (shell->priv->plugin_engine);
-		shell->priv->plugin_engine = NULL;
-	}
-	if (shell->priv->activatable != NULL) {
-		g_object_unref (shell->priv->activatable);
-		shell->priv->activatable = NULL;
-	}
-	if (shell->priv->plugin_settings != NULL) {
-		g_object_unref (shell->priv->plugin_settings);
-		shell->priv->plugin_settings = NULL;
-	}
+	gtk_container_add (GTK_CONTAINER (win), shell->priv->main_vbox);
+
+	rb_profile_end ("constructing widgets");
 }
 
 static void
-rb_shell_finalize (GObject *object)
+construct_sources (RBShell *shell)
 {
-        RBShell *shell = RB_SHELL (object);
+	RBDisplayPage *page_group;
+	char *pathname;
 
-	rb_debug ("Finalizing shell");
+	rb_profile_start ("constructing sources");
 
-	rb_shell_player_stop (shell->priv->player_shell);
+	page_group = RB_DISPLAY_PAGE_GROUP_LIBRARY;
+	shell->priv->library_source = RB_LIBRARY_SOURCE (rb_library_source_new (shell));
+	shell->priv->podcast_source = RB_PODCAST_SOURCE (rb_podcast_main_source_new (shell, shell->priv->podcast_manager));
+	shell->priv->missing_files_source = rb_missing_files_source_new (shell, shell->priv->library_source);
 
-	if (shell->priv->settings != NULL) {
-		rb_settings_delayed_sync (shell->priv->settings, NULL, NULL, NULL);
-		g_object_unref (shell->priv->settings);
-	}
+	shell->priv->import_errors_source = rb_import_errors_source_new (shell,
+									 RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR,
+									 RHYTHMDB_ENTRY_TYPE_SONG,
+									 RHYTHMDB_ENTRY_TYPE_IGNORE);
 
-	g_free (shell->priv->cached_title);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source), page_group);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->podcast_source), page_group);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->missing_files_source), page_group);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->import_errors_source), page_group);
 
-	if (shell->priv->save_playlist_id > 0) {
-		g_source_remove (shell->priv->save_playlist_id);
-		shell->priv->save_playlist_id = 0;
-	}
+	rb_podcast_main_source_add_subsources (RB_PODCAST_MAIN_SOURCE (shell->priv->podcast_source));
 
-	if (shell->priv->save_db_id > 0) {
-		g_source_remove (shell->priv->save_db_id);
-		shell->priv->save_db_id = 0;
+	/* Find the playlist name if none supplied */
+	if (shell->priv->playlists_file) {
+		pathname = g_strdup (shell->priv->playlists_file);
+	} else {
+		pathname = rb_find_user_data_file ("playlists.xml");
 	}
 
-	if (shell->priv->queue_sidebar != NULL) {
-		g_object_unref (shell->priv->queue_sidebar);
-	}
+	/* Initialize playlist manager */
+	rb_debug ("shell: creating playlist manager");
+	shell->priv->playlist_manager = rb_playlist_manager_new (shell,
+								 shell->priv->display_page_model,
+								 shell->priv->display_page_tree,
+								 pathname);
+
+	g_object_set (shell->priv->clipboard_shell,
+		      "playlist-manager", shell->priv->playlist_manager,
+		      NULL);
+
+	g_signal_connect_object (G_OBJECT (shell->priv->playlist_manager), "playlist_added",
+				 G_CALLBACK (rb_shell_playlist_added_cb), shell, 0);
+	g_signal_connect_object (G_OBJECT (shell->priv->playlist_manager), "playlist_created",
+				 G_CALLBACK (rb_shell_playlist_created_cb), shell, 0);
 
-	rb_debug ("shutting down playlist manager");
-	rb_playlist_manager_shutdown (shell->priv->playlist_manager);
+	/* Initialize removable media manager */
+	rb_debug ("shell: creating removable media manager");
+	shell->priv->removable_media_manager = rb_removable_media_manager_new (shell);
 
-	rb_debug ("unreffing playlist manager");
-	g_object_unref (shell->priv->playlist_manager);
+	g_signal_connect_object (G_OBJECT (shell->priv->removable_media_manager), "medium_added",
+				 G_CALLBACK (rb_shell_medium_added_cb), shell, 0);
 
-	rb_debug ("unreffing removable media manager");
-	g_object_unref (shell->priv->removable_media_manager);
-	g_object_unref (shell->priv->track_transfer_queue);
 
-	rb_debug ("unreffing podcast manager");
-	g_object_unref (shell->priv->podcast_manager);
+	g_free (pathname);
 
-	rb_debug ("unreffing clipboard shell");
-	g_object_unref (shell->priv->clipboard_shell);
+	rb_profile_end ("constructing sources");
+}
 
-	rb_debug ("destroying prefs");
-	if (shell->priv->prefs != NULL)
-		gtk_widget_destroy (shell->priv->prefs);
+static void
+construct_load_ui (RBShell *shell)
+{
+	GtkWidget *menubar;
+	GtkWidget *toolbar;
+	GtkToolItem *tool_item;
+	GError *error = NULL;
 
-	g_free (shell->priv->rhythmdb_file);
+	rb_debug ("shell: loading ui");
+	rb_profile_start ("loading ui");
 
-	g_free (shell->priv->playlists_file);
+	gtk_ui_manager_insert_action_group (shell->priv->ui_manager,
+					    shell->priv->actiongroup, 0);
+	gtk_ui_manager_add_ui_from_file (shell->priv->ui_manager,
+					 rb_file ("rhythmbox-ui.xml"), &error);
 
-	rb_debug ("destroying window");
-	gtk_widget_destroy (shell->priv->window);
+	gtk_ui_manager_ensure_update (shell->priv->ui_manager);
+	gtk_window_add_accel_group (GTK_WINDOW (shell->priv->window),
+				    gtk_ui_manager_get_accel_group (shell->priv->ui_manager));
+	menubar = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/MenuBar");
 
-	g_list_free (shell->priv->sources);
-	shell->priv->sources = NULL;
+	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), menubar, FALSE, FALSE, 0);
+	gtk_box_reorder_child (GTK_BOX (shell->priv->main_vbox), menubar, 0);
+
+	toolbar = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/ToolBar");
+	gtk_style_context_add_class (gtk_widget_get_style_context (toolbar),
+				     GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
+	g_settings_bind (shell->priv->settings, "toolbar-visible",
+			 toolbar, "visible",
+			 G_SETTINGS_BIND_DEFAULT);
+	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), toolbar, FALSE, FALSE, 0);
+	gtk_box_reorder_child (GTK_BOX (shell->priv->main_vbox), toolbar, 1);
 
-	g_hash_table_destroy (shell->priv->sources_hash);
+	shell->priv->volume_button = gtk_volume_button_new ();
+	g_signal_connect (shell->priv->volume_button, "value-changed",
+			  G_CALLBACK (rb_shell_volume_widget_changed_cb),
+			  shell);
+	g_signal_connect (shell->priv->player_shell, "notify::volume",
+			  G_CALLBACK (rb_shell_player_volume_changed_cb),
+			  shell);
+	rb_shell_player_volume_changed_cb (shell->priv->player_shell, NULL, shell);
 
-	rb_debug ("shutting down DB");
-	rhythmdb_shutdown (shell->priv->db);
+	tool_item = gtk_tool_item_new ();
+	gtk_tool_item_set_expand (tool_item, TRUE);
+	gtk_widget_show (GTK_WIDGET (tool_item));
+	gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1);
 
-	rb_debug ("unreffing DB");
-	g_object_unref (shell->priv->db);
+	tool_item = gtk_tool_item_new ();
+	gtk_container_add (GTK_CONTAINER (tool_item), shell->priv->volume_button);
+	gtk_widget_show_all (GTK_WIDGET (tool_item));
+	gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1);
 
-        G_OBJECT_CLASS (rb_shell_parent_class)->finalize (object);
+	gtk_widget_set_tooltip_text (shell->priv->volume_button,
+				     _("Change the music volume"));
 
-	rb_debug ("shell shutdown complete");
+	if (error != NULL) {
+		g_warning ("Couldn't merge %s: %s",
+			   rb_file ("rhythmbox-ui.xml"), error->message);
+		g_clear_error (&error);
+	}
+
+	rb_profile_end ("loading ui");
 }
 
-/**
- * rb_shell_new:
- * @no_registration: if %TRUE, single-instance features are disabled
- * @no_update: if %TRUE, don't update the database file
- * @dry_run: if %TRUE, don't write back file metadata changes
- * @autostarted: %TRUE if autostarted by the session manager
- * @disable_plugins: %TRUE if the plugins should be disabled
- * @rhythmdb: path to the database file
- * @playlists: path to the playlist file
- *
- * Creates the Rhythmbox shell.  This is effectively a singleton, so it doesn't
- * make sense to call this from anywhere other than main.c.
- *
- * Return value: the #RBShell instance
- */
-RBShell *
-rb_shell_new (gboolean no_registration,
-	      gboolean no_update,
-	      gboolean dry_run,
-	      gboolean autostarted,
-	      gboolean disable_plugins,
-	      char *rhythmdb,
-	      char *playlists)
+static void
+extension_added_cb (PeasExtensionSet *set, PeasPluginInfo *info, PeasExtension *extension, RBShell *shell)
 {
-	return g_object_new (RB_TYPE_SHELL,
-			  "no-registration", no_registration,
-			  "no-update", no_update,
-			  "dry-run", dry_run, "rhythmdb-file", rhythmdb,
-			  "playlists-file", playlists,
-			  "autostarted", autostarted,
-			  "disable-plugins", disable_plugins,
-			  NULL);
+	rb_debug ("activating extension %s", peas_plugin_info_get_name (info));
+	peas_extension_call (extension, "activate");
 }
 
-static GMountOperation *
-rb_shell_create_mount_op_cb (RhythmDB *db, RBShell *shell)
+static void
+extension_removed_cb (PeasExtensionSet *set, PeasPluginInfo *info, PeasExtension *extension, RBShell *shell)
 {
-	/* we don't want the operation to be modal, so we don't associate it with the window. */
-	GMountOperation *op = gtk_mount_operation_new (NULL);
-	gtk_mount_operation_set_screen (GTK_MOUNT_OPERATION (op),
-					gtk_window_get_screen (GTK_WINDOW (shell->priv->window)));
-	return op;
+	rb_debug ("deactivating extension %s", peas_plugin_info_get_name (info));
+	peas_extension_call (extension, "deactivate");
 }
 
 static void
-construct_db (RBShell *shell)
+construct_plugins (RBShell *shell)
 {
-	char *pathname;
-
-	/* Initialize the database */
-	rb_debug ("creating database object");
-	rb_profile_start ("creating database object");
+	char *typelib_dir;
+	char *plugindir;
+	char *plugindatadir;
+	const GList *plugins;
+	const GList *l;
+	GError *error = NULL;
 
-	if (shell->priv->rhythmdb_file) {
-		pathname = g_strdup (shell->priv->rhythmdb_file);
-	} else {
-		pathname = rb_find_user_data_file ("rhythmdb.xml");
+	if (shell->priv->disable_plugins) {
+		return;
 	}
 
-#ifdef WITH_RHYTHMDB_TREE
-	shell->priv->db = rhythmdb_tree_new (pathname);
-#elif defined(WITH_RHYTHMDB_GDA)
-	shell->priv->db = rhythmdb_gda_new (pathname);
-#endif
-	g_free (pathname);
+	rb_profile_start ("loading plugins");
+	shell->priv->plugin_settings = g_settings_new ("org.gnome.rhythmbox.plugins");
 
-	if (shell->priv->dry_run)
-		g_object_set (shell->priv->db, "dry-run", TRUE, NULL);
-	if (shell->priv->no_update)
-		g_object_set (shell->priv->db, "no-update", TRUE, NULL);
+	shell->priv->plugin_engine = peas_engine_new ();
+	/* need an #ifdef for this? */
+	peas_engine_enable_loader (shell->priv->plugin_engine, "python");
 
-	g_signal_connect_object (G_OBJECT (shell->priv->db), "load-complete",
-				 G_CALLBACK (rb_shell_load_complete_cb), shell,
-				 0);
-	g_signal_connect_object (G_OBJECT (shell->priv->db), "create-mount-op",
-				 G_CALLBACK (rb_shell_create_mount_op_cb), shell,
-				 0);
+	typelib_dir = g_build_filename (LIBDIR,
+					"girepository-1.0",
+					NULL);
+	if (g_irepository_require_private (g_irepository_get_default (),
+					   typelib_dir, "MPID", "3.0", 0, &error) == FALSE) {
+		g_clear_error (&error);
+		if (g_irepository_require (g_irepository_get_default (), "MPID", "3.0", 0, &error) == FALSE) {
+			g_warning ("Could not load MPID typelib: %s", error->message);
+			g_clear_error (&error);
+		}
+	}
 
-	rb_profile_end ("creating database object");
-}
+	if (g_irepository_require_private (g_irepository_get_default (),
+					   typelib_dir, "RB", "3.0", 0, &error) == FALSE) {
+		g_clear_error (&error);
+		if (g_irepository_require (g_irepository_get_default (), "RB", "3.0", 0, &error) == FALSE) {
+			g_warning ("Could not load RB typelib: %s", error->message);
+			g_clear_error (&error);
+		}
+	}
+	g_free (typelib_dir);
 
-static void
-construct_widgets (RBShell *shell)
-{
-	GtkWindow *win;
+	if (g_irepository_require (g_irepository_get_default (), "Peas", "1.0", 0, &error) == FALSE) {
+		g_warning ("Could not load Peas typelib: %s", error->message);
+		g_clear_error (&error);
+	}
 
-	rb_profile_start ("constructing widgets");
+	if (g_irepository_require (g_irepository_get_default (), "PeasGtk", "1.0", 0, &error) == FALSE) {
+		g_warning ("Could not load PeasGtk typelib: %s", error->message);
+		g_clear_error (&error);
+	}
 
-	/* initialize UI */
-	win = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));
-	gtk_window_set_title (win, _("Rhythmbox"));
+	plugindir = g_build_filename (rb_user_data_dir (), "plugins", NULL);
+	rb_debug ("plugin search path: %s", plugindir);
+	peas_engine_add_search_path (shell->priv->plugin_engine,
+				     plugindir,
+				     plugindir);
+	g_free (plugindir);
 
-	shell->priv->window = GTK_WIDGET (win);
-	shell->priv->iconified = FALSE;
-	g_signal_connect_object (G_OBJECT (win), "window-state-event",
-				 G_CALLBACK (rb_shell_window_state_cb),
-				 shell, 0);
+	plugindir = g_build_filename (LIBDIR, "rhythmbox", "plugins", NULL);
+	plugindatadir = g_build_filename (DATADIR, "rhythmbox", "plugins", NULL);
+	rb_debug ("plugin search path: %s / %s", plugindir, plugindatadir);
+	peas_engine_add_search_path (shell->priv->plugin_engine,
+				     plugindir,
+				     plugindatadir);
+	g_free (plugindir);
+	g_free (plugindatadir);
 
-	g_signal_connect_object (G_OBJECT (win), "configure-event",
-				 G_CALLBACK (rb_shell_window_configure_cb),
-				 shell, 0);
+#ifdef USE_UNINSTALLED_DIRS
+	plugindir = g_build_filename (SHARE_UNINSTALLED_BUILDDIR, "..", UNINSTALLED_PLUGINS_LOCATION, NULL);
+	rb_debug ("plugin search path: %s", plugindir);
+	peas_engine_add_search_path (shell->priv->plugin_engine,
+				     plugindir,
+				     plugindir);
+	g_free (plugindir);
+#endif
 
-	/* connect after, so that things can affect behaviour */
-	g_signal_connect_object (G_OBJECT (win), "delete_event",
-				 G_CALLBACK (rb_shell_window_delete_cb),
-				 shell, G_CONNECT_AFTER);
+	shell->priv->activatable = peas_extension_set_new (shell->priv->plugin_engine,
+							   PEAS_TYPE_ACTIVATABLE,
+							   "object", shell,
+							   NULL);
+	g_signal_connect (shell->priv->activatable, "extension-added", G_CALLBACK (extension_added_cb), shell);
+	g_signal_connect (shell->priv->activatable, "extension-removed", G_CALLBACK (extension_removed_cb), shell);
 
-	gtk_widget_add_events (GTK_WIDGET (win), GDK_KEY_PRESS_MASK);
-	g_signal_connect_object (G_OBJECT(win), "key_press_event",
-				 G_CALLBACK (rb_shell_key_press_event_cb), shell, 0);
+	g_settings_bind (shell->priv->plugin_settings,
+			 "active-plugins",
+			 shell->priv->plugin_engine,
+			 "loaded-plugins",
+			 G_SETTINGS_BIND_DEFAULT);
 
-	rb_debug ("shell: initializing shell services");
+	/* load builtin plugins */
+	plugins = peas_engine_get_plugin_list (shell->priv->plugin_engine);
+	for (l = plugins; l != NULL; l = l->next) {
+		PeasPluginInfo *info = PEAS_PLUGIN_INFO (l->data);
+		if (peas_plugin_info_is_builtin (info) &&
+		    g_strcmp0 (peas_plugin_info_get_module_name (info), "rb") != 0) {
+			peas_engine_load_plugin (shell->priv->plugin_engine, info);
+		}
+	}
 
-	shell->priv->podcast_manager = rb_podcast_manager_new (shell->priv->db);
-	shell->priv->track_transfer_queue = rb_track_transfer_queue_new (shell);
-	shell->priv->ui_manager = gtk_ui_manager_new ();
-	shell->priv->source_ui_merge_id = gtk_ui_manager_new_merge_id (shell->priv->ui_manager);
+	rb_profile_end ("loading plugins");
+}
 
-	shell->priv->player_shell = rb_shell_player_new (shell->priv->db,
-							 shell->priv->ui_manager,
-							 shell->priv->actiongroup);
-	g_signal_connect_object (G_OBJECT (shell->priv->player_shell),
-				 "playing-source-changed",
-				 G_CALLBACK (rb_shell_playing_source_changed_cb),
-				 shell, 0);
-	g_signal_connect_object (G_OBJECT (shell->priv->player_shell),
-				 "notify::playing-from-queue",
-				 G_CALLBACK (rb_shell_playing_from_queue_cb),
-				 shell, 0);
-	g_signal_connect_object (G_OBJECT (shell->priv->player_shell),
-				 "window_title_changed",
-				 G_CALLBACK (rb_shell_player_window_title_changed_cb),
-				 shell, 0);
-	shell->priv->clipboard_shell = rb_shell_clipboard_new (shell->priv->actiongroup,
-							       shell->priv->ui_manager,
-							       shell->priv->db);
-	shell->priv->source_header = rb_source_header_new (shell->priv->ui_manager,
-							   shell->priv->actiongroup);
-	gtk_widget_show_all (GTK_WIDGET (shell->priv->source_header));
+static void
+emit_action_state_update (RBShell *shell, const char *action)
+{
+	GDBusConnection *bus;
+	GVariant *state;
+
+
+	bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+
+	state = g_action_group_get_action_state (G_ACTION_GROUP (shell), action);
+	g_signal_emit_by_name (shell, "action-state-changed::LoadURI", "LoadURI", state);
+	if (bus != NULL) {
+		g_dbus_connection_emit_signal (bus,
+					       NULL,
+					       "/org/gnome/Rhythmbox3",
+					       "org.gtk.Actions",
+					       "StateChanged",
+					       g_variant_new ("(sv)", action, state),
+					       NULL);
+		g_object_unref (bus);
+	}
+	g_variant_unref (state);
+}
+
+static gboolean
+_scan_idle (RBShell *shell)
+{
+	gboolean loaded, scanned;
+
+	GDK_THREADS_ENTER ();
+	rb_removable_media_manager_scan (shell->priv->removable_media_manager);
+	GDK_THREADS_LEAVE ();
+
+	if (shell->priv->no_registration == FALSE) {
+		g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (shell), "LoadURI"), "(bb)", &loaded, &scanned);
+		g_action_group_change_action_state (G_ACTION_GROUP (shell), "LoadURI", g_variant_new ("(bb)", loaded, TRUE));
+		emit_action_state_update (shell, "LoadURI");
+	}
+
+	return FALSE;
+}
+
+static void
+rb_shell_startup (GApplication *app)
+{
+	RBShell *shell = RB_SHELL (app);
+	GtkAction *gtkaction;
+
+	rb_debug ("Constructing shell");
+	rb_profile_start ("constructing shell");
+
+	g_signal_connect_object (shell->priv->settings, "changed", G_CALLBACK (settings_changed_cb), shell, 0);
+
+	gtkaction = gtk_action_group_get_action (shell->priv->actiongroup, "ViewSidePane");
+	g_settings_bind (shell->priv->settings, "display-page-tree-visible",
+			 gtkaction, "active",
+			 G_SETTINGS_BIND_DEFAULT);
+	g_settings_bind (shell->priv->settings, "display-page-tree-visible",
+			 shell->priv->sidebar_container, "visible",
+			 G_SETTINGS_BIND_DEFAULT);
+
+	gtkaction = gtk_action_group_get_action (shell->priv->actiongroup, "ViewToolbar");
+	g_settings_bind (shell->priv->settings, "toolbar-visible",
+			 gtkaction, "active",
+			 G_SETTINGS_BIND_DEFAULT);
+
+	rb_debug ("shell: syncing with settings");
+	rb_shell_sync_pane_visibility (shell);
+
+	g_signal_connect_object (G_OBJECT (shell->priv->db), "save-error",
+				 G_CALLBACK (rb_shell_db_save_error_cb), shell, 0);
+
+	construct_sources (shell);
+
+	construct_load_ui (shell);
+
+	construct_plugins (shell);
+
+	rb_shell_sync_window_state (shell, FALSE);
+	rb_shell_sync_smalldisplay (shell);
+	rb_shell_sync_party_mode (shell);
+	rb_shell_sync_toolbar_state (shell);
+
+	rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
+
+	/* by now we've added the built in sources and any sources from plugins,
+	 * so we can consider the fixed page groups loaded
+	 */
+	rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_LIBRARY));
+	rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_STORES));
+
+	rb_missing_plugins_init (GTK_WINDOW (shell->priv->window));
+
+	g_idle_add ((GSourceFunc)_scan_idle, shell);
+
+	/* GO GO GO! */
+	rb_debug ("loading database");
+	rhythmdb_load (shell->priv->db);
+
+	rb_debug ("shell: syncing window state");
+	rb_shell_sync_paned (shell);
+
+	/* set initial visibility */
+	rb_shell_set_visibility (shell, TRUE, TRUE);
+
+	gdk_notify_startup_complete ();
+
+	/* focus play if small, the entry view if not */
+	if (g_settings_get_boolean (shell->priv->settings, "small-display")) {
+		GtkWidget *play_button;
+
+		play_button = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/ToolBar/Play");
+		gtk_widget_grab_focus (play_button);
+	} else {
+		RBEntryView *view;
+
+		view = rb_source_get_entry_view (RB_SOURCE (shell->priv->library_source));
+		if (view != NULL) {
+			gtk_widget_grab_focus (GTK_WIDGET (view));
+		}
+	}
+
+	rb_profile_end ("constructing shell");
+
+	/* window-based usage counting doesn't work for us, just hold the app until
+	 * we're asked to quit.
+	 */
+	g_application_hold (app);
+}
+
+static gboolean
+rb_shell_local_command_line (GApplication *app, gchar ***args, int *exit_status)
+{
+	RBShell *shell;
+	GError *error = NULL;
+	gboolean scanned;
+	gboolean loaded;
+	GPtrArray *files;
+	int n_files;
+	int i;
+
+	n_files = g_strv_length (*args) - 1;
+
+	shell = RB_SHELL (app);
+	if (shell->priv->no_registration) {
+		if (n_files > 0) {
+			g_warning ("Unable to open files on the commandline with --no-registration");
+		}
+		rb_shell_startup (app);
+		return TRUE;
+	}
+
+	if (!g_application_register (app, NULL, &error)) {
+		g_critical ("%s", error->message);
+		g_error_free (error);
+		*exit_status = 1;
+		return TRUE;
+	}
+
+	if (n_files <= 0) {
+		g_application_activate (app);
+		*exit_status = 0;
+		return TRUE;
+	}
+
+	files = g_ptr_array_new_with_free_func (g_object_unref);
+	for (i = 0; i < n_files; i++) {
+		g_ptr_array_add (files, g_file_new_for_commandline_arg ((*args)[i + 1]));
+	}
+
+	g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (app), "LoadURI"), "(bb)", &loaded, &scanned);
+	if (loaded) {
+		rb_debug ("opening files immediately");
+		g_application_open (app, (GFile **)files->pdata, files->len, "");
+		g_ptr_array_free (files, TRUE);
+	} else {
+		rb_debug ("opening files once db is loaded");
+		g_signal_connect (app, "action-state-changed::LoadURI", G_CALLBACK (load_state_changed_cb), files);
+	}
+
+	return TRUE;
+}
+static void
+rb_shell_class_init (RBShellClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	GApplicationClass *app_class = G_APPLICATION_CLASS (klass);
+
+	object_class->set_property = rb_shell_set_property;
+	object_class->get_property = rb_shell_get_property;
+        object_class->finalize = rb_shell_finalize;
+	object_class->constructed = rb_shell_constructed;
+
+	app_class->activate = rb_shell_activate;
+	app_class->open = rb_shell_open;
+	app_class->local_command_line = rb_shell_local_command_line;
+	app_class->startup = rb_shell_startup;
+
+	klass->visibility_changing = rb_shell_visibility_changing;
+
+	/**
+	 * RBShell:no-registration:
+	 *
+	 * If %TRUE, disable single-instance features.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_NO_REGISTRATION,
+					 g_param_spec_boolean ("no-registration",
+							       "no-registration",
+							       "Whether or not to register",
+							       FALSE,
+							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBShell:no-update:
+	 *
+	 * If %TRUE, don't update the database.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_NO_UPDATE,
+					 g_param_spec_boolean ("no-update",
+							       "no-update",
+							       "Whether or not to update the library",
+							       FALSE,
+							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBShell:dry-run:
+	 *
+	 * If TRUE, don't write back file metadata changes.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_DRY_RUN,
+					 g_param_spec_boolean ("dry-run",
+							       "dry-run",
+							       "Whether or not this is a dry run",
+							       FALSE,
+							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBShell:rhythmdb-file:
+	 *
+	 * The path to the rhythmdb file
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_RHYTHMDB_FILE,
+					 g_param_spec_string ("rhythmdb-file",
+							      "rhythmdb-file",
+							      "The RhythmDB file to use",
+							      "rhythmdb.xml",
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	/**
+	 * RBShell:playlists-file:
+	 *
+	 * The path to the playlist file
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_PLAYLISTS_FILE,
+					 g_param_spec_string ("playlists-file",
+							      "playlists-file",
+							      "The playlists file to use",
+							      "playlists.xml",
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBShell:selected-page:
+	 *
+	 * The currently selected display page
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_SELECTED_PAGE,
+					 g_param_spec_object ("selected-page",
+							      "selected-page",
+							      "Display page which is currently selected",
+							      RB_TYPE_DISPLAY_PAGE,
+							      G_PARAM_READABLE));
+	/**
+	 * RBShell:db:
+	 *
+	 * The #RhythmDB instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_DB,
+					 g_param_spec_object ("db",
+							      "RhythmDB",
+							      "RhythmDB object",
+							      RHYTHMDB_TYPE,
+							      G_PARAM_READABLE));
+	/**
+	 * RBShell:ui-manager:
+	 *
+	 * The #GtkUIManager instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_UI_MANAGER,
+					 g_param_spec_object ("ui-manager",
+							      "GtkUIManager",
+							      "GtkUIManager object",
+							      GTK_TYPE_UI_MANAGER,
+							      G_PARAM_READABLE));
+	/**
+	 * RBShell:clipboard:
+	 *
+	 * The #RBShellClipboard instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_CLIPBOARD,
+					 g_param_spec_object ("clipboard",
+							      "RBShellClipboard",
+							      "RBShellClipboard object",
+							      RB_TYPE_SHELL_CLIPBOARD,
+							      G_PARAM_READABLE));
+	/**
+	 * RBShell:playlist-manager:
+	 *
+	 * The #RBPlaylistManager instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_PLAYLIST_MANAGER,
+					 g_param_spec_object ("playlist-manager",
+							      "RBPlaylistManager",
+							      "RBPlaylistManager object",
+							      RB_TYPE_PLAYLIST_MANAGER,
+							      G_PARAM_READABLE));
+	/**
+	 * RBShell:shell-player:
+	 *
+	 * The #RBShellPlayer instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_SHELL_PLAYER,
+					 g_param_spec_object ("shell-player",
+							      "RBShellPlayer",
+							      "RBShellPlayer object",
+							      RB_TYPE_SHELL_PLAYER,
+							      G_PARAM_READABLE));
+	/**
+	 * RBShell:removable-media-manager:
+	 *
+	 * The #RBRemovableMediaManager instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_REMOVABLE_MEDIA_MANAGER,
+					 g_param_spec_object ("removable-media-manager",
+							      "RBRemovableMediaManager",
+							      "RBRemovableMediaManager object",
+							      RB_TYPE_REMOVABLE_MEDIA_MANAGER,
+							      G_PARAM_READABLE));
+	/**
+	 * RBShell:window:
+	 *
+	 * The main Rhythmbox window.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_WINDOW,
+					 g_param_spec_object ("window",
+							      "GtkWindow",
+							      "GtkWindow object",
+							      GTK_TYPE_WINDOW,
+							      G_PARAM_READABLE));
+	/**
+	 * RBShell:prefs:
+	 *
+	 * The #RBShellPreferences instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_PREFS,
+					 g_param_spec_object ("prefs",
+							      "RBShellPreferences",
+							      "RBShellPreferences object",
+							      RB_TYPE_SHELL_PREFERENCES,
+							      G_PARAM_READABLE));
+	/**
+	 * RBShell:queue-source:
+	 *
+	 * The play queue source
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_QUEUE_SOURCE,
+					 g_param_spec_object ("queue-source",
+							      "queue-source",
+							      "Queue source",
+							      RB_TYPE_PLAY_QUEUE_SOURCE,
+							      G_PARAM_READABLE));
+	/**
+	 * RBShell:library-source:
+	 *
+	 * The library source
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_LIBRARY_SOURCE,
+					 g_param_spec_object ("library-source",
+							      "library-source",
+							      "Library source",
+							      RB_TYPE_LIBRARY_SOURCE,
+							      G_PARAM_READABLE));
+	/**
+	 * RBShell:display-page-model:
+	 *
+	 * The model underlying the display page tree
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_DISPLAY_PAGE_MODEL,
+					 g_param_spec_object ("display-page-model",
+							      "display-page-model",
+							      "RBDisplayPageModel",
+							      RB_TYPE_DISPLAY_PAGE_MODEL,
+							      G_PARAM_READABLE));
+
+	/**
+	 * RBShell:display-page-tree:
+	 *
+	 * The #RBDisplayPageTree instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_DISPLAY_PAGE_TREE,
+					 g_param_spec_object ("display-page-tree",
+							      "display-page-tree",
+							      "RBDisplayPageTree",
+							      RB_TYPE_DISPLAY_PAGE_TREE,
+							      G_PARAM_READABLE));
+
+	/**
+	 * RBShell:visibility:
+	 *
+	 * Whether the main window is currently visible.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_VISIBILITY,
+					 g_param_spec_boolean ("visibility",
+							       "visibility",
+							       "Current window visibility",
+							       TRUE,
+							       G_PARAM_READWRITE));
+	/**
+	 * RBShell:source-header:
+	 *
+	 * The #RBSourceHeader instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_SOURCE_HEADER,
+					 g_param_spec_object ("source-header",
+							      "source header widget",
+							      "RBSourceHeader",
+							      RB_TYPE_SOURCE_HEADER,
+							      G_PARAM_READABLE));
+
+	/**
+	 * RBShell:track-transfer-queue:
+	 *
+	 * The #RBTrackTransferQueue instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_TRACK_TRANSFER_QUEUE,
+					 g_param_spec_object ("track-transfer-queue",
+							      "RBTrackTransferQueue",
+							      "RBTrackTransferQueue object",
+							      RB_TYPE_TRACK_TRANSFER_QUEUE,
+							      G_PARAM_READABLE));
+	/**
+	 * RBShell:autostarted:
+	 *
+	 * Whether Rhythmbox was automatically started by the session manager
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_AUTOSTARTED,
+					 g_param_spec_boolean ("autostarted",
+							       "autostarted",
+							       "TRUE if autostarted",
+							       FALSE,
+							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBShell:disable-plugins:
+	 *
+	 * If %TRUE, disable plugins
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_DISABLE_PLUGINS,
+					 g_param_spec_boolean ("disable-plugins",
+							       "disable-plugins",
+							       "Whether or not to disable plugins",
+							       FALSE,
+							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	/**
+	 * RBShell::visibility-changed:
+	 * @shell: the #RBShell
+	 * @visibile: new visibility
+	 *
+	 * Emitted after the visibility of the main Rhythmbox window has
+	 * changed.
+	 */
+	rb_shell_signals[VISIBILITY_CHANGED] =
+		g_signal_new ("visibility_changed",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBShellClass, visibility_changed),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__BOOLEAN,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_BOOLEAN);
+	/**
+	 * RBShell::visibility-changing:
+	 * @shell: the #RBShell
+	 * @initial: if %TRUE, this is the initial visibility for the window
+	 * @visible: new shell visibility
+	 *
+	 * Emitted before the visibility of the main window changes.  The return
+	 * value overrides the visibility setting.  If multiple signal handlers
+	 * are attached, the last one wins.
+	 */
+	rb_shell_signals[VISIBILITY_CHANGING] =
+		g_signal_new ("visibility_changing",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBShellClass, visibility_changing),
+			      NULL, NULL,
+			      rb_marshal_BOOLEAN__BOOLEAN_BOOLEAN,
+			      G_TYPE_BOOLEAN,
+			      2,
+			      G_TYPE_BOOLEAN,
+			      G_TYPE_BOOLEAN);
+
+	/**
+	 * RBShell::create-song-info:
+	 * @shell: the #RBShell
+	 * @song_info: the new #RBSongInfo window
+	 * @multi: if %TRUE, the song info window is for multiple entries
+	 *
+	 * Emitted when creating a new #RBSongInfo window.  Signal handlers can
+	 * add pages to the song info window notebook to display additional
+	 * information.
+	 */
+	rb_shell_signals[CREATE_SONG_INFO] =
+		g_signal_new ("create_song_info",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBShellClass, create_song_info),
+			      NULL, NULL,
+			      rb_marshal_VOID__OBJECT_BOOLEAN,
+			      G_TYPE_NONE,
+			      2,
+			      RB_TYPE_SONG_INFO, G_TYPE_BOOLEAN);
+	/**
+	 * RBShell::notify-playing-entry:
+	 * @shell: the #RBShell
+	 *
+	 * Emitted when a notification should be displayed showing the current
+	 * playing entry.
+	 */
+	rb_shell_signals[NOTIFY_PLAYING_ENTRY] =
+		g_signal_new ("notify-playing-entry",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__BOOLEAN,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_BOOLEAN);
+	/**
+	 * RBShell::notify-custom:
+	 * @shell: the #RBShell
+	 * @timeout: length of time (in seconds) to display the notification
+	 * @primary: main notification text
+	 * @secondary: secondary notification text
+	 * @image_uri: URI for an image to include in the notification (optional)
+	 * @requested: if %TRUE, the notification was triggered by an explicit user action
+	 *
+	 * Emitted when a custom notification should be displayed to the user.
+	 */
+	rb_shell_signals[NOTIFY_CUSTOM] =
+		g_signal_new ("notify-custom",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      rb_marshal_VOID__UINT_STRING_STRING_STRING_BOOLEAN,
+			      G_TYPE_NONE,
+			      5,
+			      G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
+	g_type_class_add_private (klass, sizeof (RBShellPrivate));
+}
 
-	shell->priv->display_page_tree = rb_display_page_tree_new (shell);
-	gtk_widget_show_all (GTK_WIDGET (shell->priv->display_page_tree));
-	g_signal_connect_object (shell->priv->display_page_tree, "drop-received",
-				 G_CALLBACK (display_page_tree_drag_received_cb), shell, 0);
-	g_object_get (shell->priv->display_page_tree, "model", &shell->priv->display_page_model, NULL);
-	rb_display_page_group_add_core_groups (G_OBJECT (shell), shell->priv->display_page_model);
+static void
+rb_shell_init (RBShell *shell)
+{
+	shell->priv = G_TYPE_INSTANCE_GET_PRIVATE (shell, RB_TYPE_SHELL, RBShellPrivate);
 
-	shell->priv->statusbar = rb_statusbar_new (shell->priv->db,
-						   shell->priv->ui_manager,
-						   shell->priv->track_transfer_queue);
-	g_object_set (shell->priv->player_shell, "statusbar", shell->priv->statusbar, NULL);
-	gtk_widget_show (GTK_WIDGET (shell->priv->statusbar));
+	rb_user_data_dir ();
+	rb_refstring_system_init ();
 
-	g_signal_connect_object (shell->priv->display_page_tree, "selected",
-				 G_CALLBACK (display_page_selected_cb), shell, 0);
+#ifdef USE_UNINSTALLED_DIRS
+	rb_file_helpers_init (TRUE);
+#else
+	rb_file_helpers_init (FALSE);
+#endif
+	rb_stock_icons_init ();
 
-	shell->priv->notebook = gtk_notebook_new ();
-	gtk_widget_show (shell->priv->notebook);
-	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (shell->priv->notebook), FALSE);
-	gtk_notebook_set_show_border (GTK_NOTEBOOK (shell->priv->notebook), FALSE);
-	g_signal_connect_object (shell->priv->display_page_tree,
-				 "size-allocate",
-				 G_CALLBACK (paned_size_allocate_cb),
-				 shell, 0);
+        rb_shell_session_init (shell);
 
-	shell->priv->queue_source = RB_PLAYLIST_SOURCE (rb_play_queue_source_new (shell));
-	g_object_set (shell->priv->player_shell, "queue-source", shell->priv->queue_source, NULL);
-	g_object_set (shell->priv->clipboard_shell, "queue-source", shell->priv->queue_source, NULL);
-	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->queue_source), RB_DISPLAY_PAGE_GROUP_LIBRARY);
-	g_object_get (shell->priv->queue_source, "sidebar", &shell->priv->queue_sidebar, NULL);
-	gtk_widget_show_all (shell->priv->queue_sidebar);
-	gtk_widget_set_no_show_all (shell->priv->queue_sidebar, TRUE);
+	g_setenv ("PULSE_PROP_media.role", "music", TRUE);
+}
 
-	/* places for plugins to put UI */
-	shell->priv->top_container = GTK_BOX (gtk_vbox_new (FALSE, 0));
-	shell->priv->bottom_container = GTK_BOX (gtk_vbox_new (FALSE, 0));
-	shell->priv->sidebar_container = GTK_BOX (gtk_vbox_new (FALSE, 0));
-	shell->priv->right_sidebar_container = GTK_BOX (gtk_vbox_new (FALSE, 0));
+static void
+rb_shell_set_property (GObject *object,
+		       guint prop_id,
+		       const GValue *value,
+		       GParamSpec *pspec)
+{
+	RBShell *shell = RB_SHELL (object);
 
-	/* set up sidebars */
-	shell->priv->paned = gtk_hpaned_new ();
-	shell->priv->right_paned = gtk_hpaned_new ();
-	gtk_widget_show_all (shell->priv->right_paned);
-	g_signal_connect_object (G_OBJECT (shell->priv->right_paned),
-				 "size-allocate",
-				 G_CALLBACK (paned_size_allocate_cb),
-				 shell, 0);
-	gtk_widget_set_no_show_all (shell->priv->right_paned, TRUE);
+	switch (prop_id)
 	{
-		GtkWidget *vbox2 = gtk_vbox_new (FALSE, 0);
+	case PROP_NO_REGISTRATION:
+		shell->priv->no_registration = g_value_get_boolean (value);
+		break;
+	case PROP_NO_UPDATE:
+		shell->priv->no_update = g_value_get_boolean (value);
+		break;
+	case PROP_DRY_RUN:
+		shell->priv->dry_run = g_value_get_boolean (value);
+		if (shell->priv->dry_run)
+			shell->priv->no_registration = TRUE;
+		break;
+	case PROP_RHYTHMDB_FILE:
+		g_free (shell->priv->rhythmdb_file);
+		shell->priv->rhythmdb_file = g_value_dup_string (value);
+		break;
+	case PROP_PLAYLISTS_FILE:
+		g_free (shell->priv->playlists_file);
+		shell->priv->playlists_file = g_value_dup_string (value);
+		break;
+	case PROP_VISIBILITY:
+		rb_shell_set_visibility (shell, FALSE, g_value_get_boolean (value));
+		break;
+	case PROP_AUTOSTARTED:
+		shell->priv->autostarted = g_value_get_boolean (value);
+		break;
+	case PROP_DISABLE_PLUGINS:
+		shell->priv->disable_plugins = g_value_get_boolean (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
 
-		shell->priv->queue_paned = gtk_vpaned_new ();
-		gtk_paned_pack1 (GTK_PANED (shell->priv->queue_paned),
-				 GTK_WIDGET (shell->priv->display_page_tree),
-				 FALSE, TRUE);
-		gtk_paned_pack2 (GTK_PANED (shell->priv->queue_paned),
-				 shell->priv->queue_sidebar,
-				 TRUE, TRUE);
-		gtk_container_child_set (GTK_CONTAINER (shell->priv->queue_paned),
-					 GTK_WIDGET (shell->priv->display_page_tree),
-					 "resize", FALSE,
-					 NULL);
+static void
+rb_shell_get_property (GObject *object,
+		       guint prop_id,
+		       GValue *value,
+		       GParamSpec *pspec)
+{
+	RBShell *shell = RB_SHELL (object);
 
-		gtk_box_pack_start (GTK_BOX (vbox2),
-				    GTK_WIDGET (shell->priv->source_header),
-				    FALSE, FALSE, 3);
-		gtk_box_pack_start (GTK_BOX (vbox2),
-				    shell->priv->notebook,
-				    TRUE, TRUE, 0);
-		gtk_box_pack_start (GTK_BOX (vbox2),
-				    GTK_WIDGET (shell->priv->bottom_container),
-				    FALSE, FALSE, 0);
+	switch (prop_id)
+	{
+	case PROP_NO_REGISTRATION:
+		g_value_set_boolean (value, shell->priv->no_registration);
+		break;
+	case PROP_NO_UPDATE:
+		g_value_set_boolean (value, shell->priv->no_update);
+		break;
+	case PROP_DRY_RUN:
+		g_value_set_boolean (value, shell->priv->dry_run);
+		break;
+	case PROP_RHYTHMDB_FILE:
+		g_value_set_string (value, shell->priv->rhythmdb_file);
+		break;
+	case PROP_PLAYLISTS_FILE:
+		g_value_set_string (value, shell->priv->playlists_file);
+		break;
+	case PROP_DB:
+		g_value_set_object (value, shell->priv->db);
+		break;
+	case PROP_UI_MANAGER:
+		g_value_set_object (value, shell->priv->ui_manager);
+		break;
+	case PROP_CLIPBOARD:
+		g_value_set_object (value, shell->priv->clipboard_shell);
+		break;
+	case PROP_PLAYLIST_MANAGER:
+		g_value_set_object (value, shell->priv->playlist_manager);
+		break;
+	case PROP_SHELL_PLAYER:
+		g_value_set_object (value, shell->priv->player_shell);
+		break;
+	case PROP_REMOVABLE_MEDIA_MANAGER:
+		g_value_set_object (value, shell->priv->removable_media_manager);
+		break;
+	case PROP_SELECTED_PAGE:
+		g_value_set_object (value, shell->priv->selected_page);
+		break;
+	case PROP_WINDOW:
+		g_value_set_object (value, shell->priv->window);
+		break;
+	case PROP_PREFS:
+		/* create the preferences window the first time we need it */
+		if (shell->priv->prefs == NULL) {
+			GtkWidget *content;
 
-		gtk_paned_pack1 (GTK_PANED (shell->priv->right_paned),
-				 vbox2, TRUE, TRUE);
-		gtk_paned_pack2 (GTK_PANED (shell->priv->right_paned),
-				 GTK_WIDGET (shell->priv->right_sidebar_container),
-				 FALSE, FALSE);
-		gtk_widget_hide (GTK_WIDGET(shell->priv->right_sidebar_container));
+			shell->priv->prefs = rb_shell_preferences_new (shell->priv->sources);
 
-		gtk_box_pack_start (shell->priv->sidebar_container,
-				    shell->priv->queue_paned,
-				    TRUE, TRUE, 0);
-		gtk_paned_pack1 (GTK_PANED (shell->priv->paned),
-				 GTK_WIDGET (shell->priv->sidebar_container),
-				 FALSE, TRUE);
-		gtk_paned_pack2 (GTK_PANED (shell->priv->paned),
-				 shell->priv->right_paned,
-				 TRUE, TRUE);
-		gtk_widget_show (vbox2);
+			gtk_window_set_transient_for (GTK_WINDOW (shell->priv->prefs),
+						      GTK_WINDOW (shell->priv->window));
+			content = gtk_dialog_get_content_area (GTK_DIALOG (shell->priv->prefs));
+			gtk_widget_show_all (content);
+		}
+		g_value_set_object (value, shell->priv->prefs);
+		break;
+	case PROP_QUEUE_SOURCE:
+		g_value_set_object (value, shell->priv->queue_source);
+		break;
+	case PROP_LIBRARY_SOURCE:
+		g_value_set_object (value, shell->priv->library_source);
+		break;
+	case PROP_DISPLAY_PAGE_MODEL:
+		g_value_set_object (value, shell->priv->display_page_model);
+		break;
+	case PROP_DISPLAY_PAGE_TREE:
+		g_value_set_object (value, shell->priv->display_page_tree);
+		break;
+	case PROP_VISIBILITY:
+		g_value_set_boolean (value, rb_shell_get_visibility (shell));
+		break;
+	case PROP_SOURCE_HEADER:
+		g_value_set_object (value, shell->priv->source_header);
+		break;
+	case PROP_TRACK_TRANSFER_QUEUE:
+		g_value_set_object (value, shell->priv->track_transfer_queue);
+		break;
+	case PROP_AUTOSTARTED:
+		g_value_set_boolean (value, shell->priv->autostarted);
+		break;
+	case PROP_DISABLE_PLUGINS:
+		g_value_set_boolean (value, shell->priv->disable_plugins);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
 	}
-
-	g_signal_connect_object (G_OBJECT (shell->priv->queue_paned),
-				 "size-allocate",
-				 G_CALLBACK (paned_size_allocate_cb),
-				 shell, 0);
-	gtk_widget_show (shell->priv->paned);
-
-	shell->priv->main_vbox = gtk_vbox_new (FALSE, 0);
-	gtk_container_set_border_width (GTK_CONTAINER (shell->priv->main_vbox), 0);
-	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->player_shell), FALSE, TRUE, 6);
-	gtk_widget_show (GTK_WIDGET (shell->priv->player_shell));
-
-	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->top_container), FALSE, TRUE, 0);
-	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), shell->priv->paned, TRUE, TRUE, 0);
-	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->statusbar), FALSE, TRUE, 0);
-	gtk_widget_show_all (shell->priv->main_vbox);
-
-	gtk_container_add (GTK_CONTAINER (win), shell->priv->main_vbox);
-
-	rb_profile_end ("constructing widgets");
 }
 
-static void
-construct_sources (RBShell *shell)
+static gboolean
+rb_shell_sync_state (RBShell *shell)
 {
-	RBDisplayPage *page_group;
-	char *pathname;
-
-	rb_profile_start ("constructing sources");
-
-	page_group = RB_DISPLAY_PAGE_GROUP_LIBRARY;
-	shell->priv->library_source = RB_LIBRARY_SOURCE (rb_library_source_new (shell));
-	shell->priv->podcast_source = RB_PODCAST_SOURCE (rb_podcast_main_source_new (shell, shell->priv->podcast_manager));
-	shell->priv->missing_files_source = rb_missing_files_source_new (shell, shell->priv->library_source);
+	if (shell->priv->dry_run) {
+		rb_debug ("in dry-run mode, not syncing state");
+		return FALSE;
+	}
 
-	shell->priv->import_errors_source = rb_import_errors_source_new (shell,
-									 RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR,
-									 RHYTHMDB_ENTRY_TYPE_SONG,
-									 RHYTHMDB_ENTRY_TYPE_IGNORE);
+	if (!shell->priv->load_complete) {
+		rb_debug ("load incomplete, not syncing state");
+		return FALSE;
+	}
 
-	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source), page_group);
-	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->podcast_source), page_group);
-	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->missing_files_source), page_group);
-	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->import_errors_source), page_group);
+	rb_debug ("saving playlists");
+	rb_playlist_manager_save_playlists (shell->priv->playlist_manager,
+					    TRUE);
 
-	rb_podcast_main_source_add_subsources (RB_PODCAST_MAIN_SOURCE (shell->priv->podcast_source));
+	rb_debug ("saving db");
+	rhythmdb_save (shell->priv->db);
+	return FALSE;
+}
 
-	/* Find the playlist name if none supplied */
-	if (shell->priv->playlists_file) {
-		pathname = g_strdup (shell->priv->playlists_file);
-	} else {
-		pathname = rb_find_user_data_file ("playlists.xml");
-	}
+static gboolean
+idle_save_rhythmdb (RBShell *shell)
+{
+	rhythmdb_save (shell->priv->db);
 
-	/* Initialize playlist manager */
-	rb_debug ("shell: creating playlist manager");
-	shell->priv->playlist_manager = rb_playlist_manager_new (shell,
-								 shell->priv->display_page_model,
-								 shell->priv->display_page_tree,
-								 pathname);
+	shell->priv->save_db_id = 0;
 
-	g_object_set (shell->priv->clipboard_shell,
-		      "playlist-manager", shell->priv->playlist_manager,
-		      NULL);
+	return FALSE;
+}
 
-	g_signal_connect_object (G_OBJECT (shell->priv->playlist_manager), "playlist_added",
-				 G_CALLBACK (rb_shell_playlist_added_cb), shell, 0);
-	g_signal_connect_object (G_OBJECT (shell->priv->playlist_manager), "playlist_created",
-				 G_CALLBACK (rb_shell_playlist_created_cb), shell, 0);
+static gboolean
+idle_save_playlist_manager (RBShell *shell)
+{
+	GDK_THREADS_ENTER ();
+	rb_playlist_manager_save_playlists (shell->priv->playlist_manager,
+					    FALSE);
+	GDK_THREADS_LEAVE ();
 
-	/* Initialize removable media manager */
-	rb_debug ("shell: creating removable media manager");
-	shell->priv->removable_media_manager = rb_removable_media_manager_new (shell);
+	return TRUE;
+}
 
-	g_signal_connect_object (G_OBJECT (shell->priv->removable_media_manager), "medium_added",
-				 G_CALLBACK (rb_shell_medium_added_cb), shell, 0);
+static void
+rb_shell_shutdown (RBShell *shell)
+{
+	GdkDisplay *display;
 
+	if (shell->priv->shutting_down)
+		return;
+	shell->priv->shutting_down = TRUE;
 
-	g_free (pathname);
+	/* Hide the main window and tray icon as soon as possible */
+	display = gtk_widget_get_display (shell->priv->window);
+	gtk_widget_hide (shell->priv->window);
+	gdk_display_sync (display);
 
-	rb_profile_end ("constructing sources");
+	if (shell->priv->plugin_engine != NULL) {
+		g_object_unref (shell->priv->plugin_engine);
+		shell->priv->plugin_engine = NULL;
+	}
+	if (shell->priv->activatable != NULL) {
+		g_object_unref (shell->priv->activatable);
+		shell->priv->activatable = NULL;
+	}
+	if (shell->priv->plugin_settings != NULL) {
+		g_object_unref (shell->priv->plugin_settings);
+		shell->priv->plugin_settings = NULL;
+	}
 }
 
 static void
-construct_load_ui (RBShell *shell)
+rb_shell_finalize (GObject *object)
 {
-	GtkWidget *menubar;
-	GtkWidget *toolbar;
-	GtkToolItem *tool_item;
-	GError *error = NULL;
-
-	rb_debug ("shell: loading ui");
-	rb_profile_start ("loading ui");
+        RBShell *shell = RB_SHELL (object);
 
-	gtk_ui_manager_insert_action_group (shell->priv->ui_manager,
-					    shell->priv->actiongroup, 0);
-	gtk_ui_manager_add_ui_from_file (shell->priv->ui_manager,
-					 rb_file ("rhythmbox-ui.xml"), &error);
+	rb_debug ("Finalizing shell");
 
-	gtk_ui_manager_ensure_update (shell->priv->ui_manager);
-	gtk_window_add_accel_group (GTK_WINDOW (shell->priv->window),
-				    gtk_ui_manager_get_accel_group (shell->priv->ui_manager));
-	menubar = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/MenuBar");
+	rb_shell_player_stop (shell->priv->player_shell);
 
-	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), menubar, FALSE, FALSE, 0);
-	gtk_box_reorder_child (GTK_BOX (shell->priv->main_vbox), menubar, 0);
+	if (shell->priv->settings != NULL) {
+		rb_settings_delayed_sync (shell->priv->settings, NULL, NULL, NULL);
+		g_object_unref (shell->priv->settings);
+	}
 
-	toolbar = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/ToolBar");
-	gtk_style_context_add_class (gtk_widget_get_style_context (toolbar),
-				     GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
-	g_settings_bind (shell->priv->settings, "toolbar-visible",
-			 toolbar, "visible",
-			 G_SETTINGS_BIND_DEFAULT);
-	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), toolbar, FALSE, FALSE, 0);
-	gtk_box_reorder_child (GTK_BOX (shell->priv->main_vbox), toolbar, 1);
+	g_free (shell->priv->cached_title);
 
-	shell->priv->volume_button = gtk_volume_button_new ();
-	g_signal_connect (shell->priv->volume_button, "value-changed",
-			  G_CALLBACK (rb_shell_volume_widget_changed_cb),
-			  shell);
-	g_signal_connect (shell->priv->player_shell, "notify::volume",
-			  G_CALLBACK (rb_shell_player_volume_changed_cb),
-			  shell);
-	rb_shell_player_volume_changed_cb (shell->priv->player_shell, NULL, shell);
+	if (shell->priv->save_playlist_id > 0) {
+		g_source_remove (shell->priv->save_playlist_id);
+		shell->priv->save_playlist_id = 0;
+	}
 
-	tool_item = gtk_tool_item_new ();
-	gtk_tool_item_set_expand (tool_item, TRUE);
-	gtk_widget_show (GTK_WIDGET (tool_item));
-	gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1);
+	if (shell->priv->save_db_id > 0) {
+		g_source_remove (shell->priv->save_db_id);
+		shell->priv->save_db_id = 0;
+	}
 
-	tool_item = gtk_tool_item_new ();
-	gtk_container_add (GTK_CONTAINER (tool_item), shell->priv->volume_button);
-	gtk_widget_show_all (GTK_WIDGET (tool_item));
-	gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1);
+	if (shell->priv->queue_sidebar != NULL) {
+		g_object_unref (shell->priv->queue_sidebar);
+	}
 
-	gtk_widget_set_tooltip_text (shell->priv->volume_button,
-				     _("Change the music volume"));
+	if (shell->priv->playlist_manager != NULL) {
+		rb_debug ("shutting down playlist manager");
+		rb_playlist_manager_shutdown (shell->priv->playlist_manager);
 
-	if (error != NULL) {
-		g_warning ("Couldn't merge %s: %s",
-			   rb_file ("rhythmbox-ui.xml"), error->message);
-		g_clear_error (&error);
+		rb_debug ("unreffing playlist manager");
+		g_object_unref (shell->priv->playlist_manager);
 	}
 
-	rb_profile_end ("loading ui");
-}
-
-static void
-extension_added_cb (PeasExtensionSet *set, PeasPluginInfo *info, PeasExtension *extension, RBShell *shell)
-{
-	rb_debug ("activating extension %s", peas_plugin_info_get_name (info));
-	peas_extension_call (extension, "activate");
-}
+	if (shell->priv->removable_media_manager != NULL) {
+		rb_debug ("unreffing removable media manager");
+		g_object_unref (shell->priv->removable_media_manager);
+		g_object_unref (shell->priv->track_transfer_queue);
+	}
 
-static void
-extension_removed_cb (PeasExtensionSet *set, PeasPluginInfo *info, PeasExtension *extension, RBShell *shell)
-{
-	rb_debug ("deactivating extension %s", peas_plugin_info_get_name (info));
-	peas_extension_call (extension, "deactivate");
-}
+	if (shell->priv->podcast_manager != NULL) {
+		rb_debug ("unreffing podcast manager");
+		g_object_unref (shell->priv->podcast_manager);
+	}
 
-static void
-construct_plugins (RBShell *shell)
-{
-	char *typelib_dir;
-	char *plugindir;
-	char *plugindatadir;
-	const GList *plugins;
-	const GList *l;
-	GError *error = NULL;
+	if (shell->priv->clipboard_shell != NULL) {
+		rb_debug ("unreffing clipboard shell");
+		g_object_unref (shell->priv->clipboard_shell);
+	}
 
-	if (shell->priv->disable_plugins) {
-		return;
+	if (shell->priv->prefs != NULL) {
+		rb_debug ("destroying prefs");
+		gtk_widget_destroy (shell->priv->prefs);
 	}
 
-	rb_profile_start ("loading plugins");
-	shell->priv->plugin_settings = g_settings_new ("org.gnome.rhythmbox.plugins");
+	g_free (shell->priv->rhythmdb_file);
 
-	shell->priv->plugin_engine = peas_engine_new ();
-	/* need an #ifdef for this? */
-	peas_engine_enable_loader (shell->priv->plugin_engine, "python");
+	g_free (shell->priv->playlists_file);
 
-	typelib_dir = g_build_filename (LIBDIR,
-					"girepository-1.0",
-					NULL);
-	if (g_irepository_require_private (g_irepository_get_default (),
-					   typelib_dir, "MPID", "3.0", 0, &error) == FALSE) {
-		g_clear_error (&error);
-		if (g_irepository_require (g_irepository_get_default (), "MPID", "3.0", 0, &error) == FALSE) {
-			g_warning ("Could not load MPID typelib: %s", error->message);
-			g_clear_error (&error);
-		}
-	}
+	rb_debug ("destroying window");
+	gtk_widget_destroy (shell->priv->window);
 
-	if (g_irepository_require_private (g_irepository_get_default (),
-					   typelib_dir, "RB", "3.0", 0, &error) == FALSE) {
-		g_clear_error (&error);
-		if (g_irepository_require (g_irepository_get_default (), "RB", "3.0", 0, &error) == FALSE) {
-			g_warning ("Could not load RB typelib: %s", error->message);
-			g_clear_error (&error);
-		}
-	}
-	g_free (typelib_dir);
+	g_list_free (shell->priv->sources);
+	shell->priv->sources = NULL;
 
-	if (g_irepository_require (g_irepository_get_default (), "Peas", "1.0", 0, &error) == FALSE) {
-		g_warning ("Could not load Peas typelib: %s", error->message);
-		g_clear_error (&error);
+	if (shell->priv->sources_hash != NULL) {
+		g_hash_table_destroy (shell->priv->sources_hash);
 	}
 
-	if (g_irepository_require (g_irepository_get_default (), "PeasGtk", "1.0", 0, &error) == FALSE) {
-		g_warning ("Could not load PeasGtk typelib: %s", error->message);
-		g_clear_error (&error);
+	if (shell->priv->db != NULL) {
+		rb_debug ("shutting down DB");
+		rhythmdb_shutdown (shell->priv->db);
+
+		rb_debug ("unreffing DB");
+		g_object_unref (shell->priv->db);
 	}
 
-	plugindir = g_build_filename (rb_user_data_dir (), "plugins", NULL);
-	rb_debug ("plugin search path: %s", plugindir);
-	peas_engine_add_search_path (shell->priv->plugin_engine,
-				     plugindir,
-				     plugindir);
-	g_free (plugindir);
+	rb_file_helpers_shutdown ();
+	rb_stock_icons_shutdown ();
+	rb_refstring_system_shutdown ();
 
-	plugindir = g_build_filename (LIBDIR, "rhythmbox", "plugins", NULL);
-	plugindatadir = g_build_filename (DATADIR, "rhythmbox", "plugins", NULL);
-	rb_debug ("plugin search path: %s / %s", plugindir, plugindatadir);
-	peas_engine_add_search_path (shell->priv->plugin_engine,
-				     plugindir,
-				     plugindatadir);
-	g_free (plugindir);
-	g_free (plugindatadir);
+        G_OBJECT_CLASS (rb_shell_parent_class)->finalize (object);
 
-#ifdef USE_UNINSTALLED_DIRS
-	plugindir = g_build_filename (SHARE_UNINSTALLED_BUILDDIR, "..", UNINSTALLED_PLUGINS_LOCATION, NULL);
-	rb_debug ("plugin search path: %s", plugindir);
-	peas_engine_add_search_path (shell->priv->plugin_engine,
-				     plugindir,
-				     plugindir);
-	g_free (plugindir);
-#endif
+	rb_debug ("shell shutdown complete");
+}
 
-	shell->priv->activatable = peas_extension_set_new (shell->priv->plugin_engine,
-							   PEAS_TYPE_ACTIVATABLE,
-							   "object", shell,
-							   NULL);
-	g_signal_connect (shell->priv->activatable, "extension-added", G_CALLBACK (extension_added_cb), shell);
-	g_signal_connect (shell->priv->activatable, "extension-removed", G_CALLBACK (extension_removed_cb), shell);
+/**
+ * rb_shell_new:
+ * @autostarted: %TRUE if autostarted by the session manager
+ * @argc: a pointer to the number of command line arguments
+ * @argv: a pointer to the array of command line arguments
+ *
+ * Creates the Rhythmbox shell.  This is effectively a singleton, so it doesn't
+ * make sense to call this from anywhere other than main.c.
+ *
+ * Return value: the #RBShell instance
+ */
+RBShell *
+rb_shell_new (gboolean autostarted, int *argc, char ***argv)
+{
+	GOptionContext *context;
+	gboolean debug = FALSE;
+	char *debug_match = NULL;
+	gboolean no_update = FALSE;
+	gboolean no_registration = FALSE;
+	gboolean dry_run = FALSE;
+	gboolean disable_plugins = FALSE;
+	char *rhythmdb_file = NULL;
+	char *playlists_file = NULL;
+	GError *error = NULL;
 
-	g_settings_bind (shell->priv->plugin_settings,
-			 "active-plugins",
-			 shell->priv->plugin_engine,
-			 "loaded-plugins",
-			 G_SETTINGS_BIND_DEFAULT);
+	const GOptionEntry options []  = {
+		{ "debug",           'd', 0, G_OPTION_ARG_NONE,         &debug,           N_("Enable debug output"), NULL },
+		{ "debug-match",     'D', 0, G_OPTION_ARG_STRING,       &debug_match,     N_("Enable debug output matching a specified string"), NULL },
+		{ "no-update",	       0, 0, G_OPTION_ARG_NONE,         &no_update,       N_("Do not update the library with file changes"), NULL },
+		{ "no-registration", 'n', 0, G_OPTION_ARG_NONE,         &no_registration, N_("Do not register the shell"), NULL },
+		{ "dry-run",	       0, 0, G_OPTION_ARG_NONE,         &dry_run,         N_("Don't save any data permanently (implies --no-registration)"), NULL },
+		{ "disable-plugins",   0, 0, G_OPTION_ARG_NONE,		&disable_plugins, N_("Disable loading of plugins"), NULL },
+		{ "rhythmdb-file",     0, 0, G_OPTION_ARG_STRING,       &rhythmdb_file,   N_("Path for database file to use"), NULL },
+		{ "playlists-file",    0, 0, G_OPTION_ARG_STRING,       &playlists_file,   N_("Path for playlists file to use"), NULL },
+		{ NULL }
+	};
 
-	/* load builtin plugins */
-	plugins = peas_engine_get_plugin_list (shell->priv->plugin_engine);
-	for (l = plugins; l != NULL; l = l->next) {
-		PeasPluginInfo *info = PEAS_PLUGIN_INFO (l->data);
-		if (peas_plugin_info_is_builtin (info) &&
-		    g_strcmp0 (peas_plugin_info_get_module_name (info), "rb") != 0) {
-			peas_engine_load_plugin (shell->priv->plugin_engine, info);
-		}
+	context = g_option_context_new (NULL);
+	g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
+	g_option_context_add_group (context, gst_init_get_option_group ());
+	g_option_context_add_group (context, egg_sm_client_get_option_group ());
+	g_option_context_add_group (context, gtk_get_option_group (TRUE));
+
+	if (g_option_context_parse (context, argc, argv, &error) == FALSE) {
+		g_print (_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
+			 error->message, (*argv)[0]);
+		g_error_free (error);
+		g_option_context_free (context);
+		exit (1);
 	}
+	g_option_context_free (context);
 
-	rb_profile_end ("loading plugins");
+	if (!debug && debug_match)
+		rb_debug_init_match (debug_match);
+	else
+		rb_debug_init (debug);
+
+	return g_object_new (RB_TYPE_SHELL,
+			     "application-id", "org.gnome.Rhythmbox3",
+			     "flags", G_APPLICATION_HANDLES_OPEN,
+			     "autostarted", autostarted,
+			     "no-registration", no_registration,
+			     "no-update", no_update,
+			     "dry-run", dry_run,
+			     "rhythmdb-file", rhythmdb_file,
+			     "playlists-file", playlists_file,
+			     "disable-plugins", disable_plugins,
+			     NULL);
 }
 
-static gboolean
-_scan_idle (RBShell *shell)
+static void
+load_uri_action_cb (GSimpleAction *action, GVariant *parameters, RBShell *shell)
 {
-	GDK_THREADS_ENTER ();
-	rb_removable_media_manager_scan (shell->priv->removable_media_manager);
-	GDK_THREADS_LEAVE ();
-	g_signal_emit (shell, rb_shell_signals[REMOVABLE_MEDIA_SCAN_FINISHED], 0);
-	return FALSE;
+	const char *uri;
+	gboolean play;
+
+	g_variant_get (parameters, "(&sb)", &uri, &play);
+
+	rb_shell_load_uri (shell, uri, play, NULL);
+}
+
+static void
+activate_source_action_cb (GSimpleAction *action, GVariant *parameters, RBShell *shell)
+{
+	const char *source;
+	guint play;
+
+	g_variant_get (parameters, "(&su)", &source, &play);
+	rb_shell_activate_source_by_uri (shell, source, play, NULL);
+}
+
+static void
+quit_action_cb (GSimpleAction *action, GVariant *parameters, RBShell *shell)
+{
+	rb_shell_quit (shell, NULL);
 }
 
 static void
 rb_shell_constructed (GObject *object)
 {
 	RBShell *shell;
-	GtkAction *action;
+	GSimpleActionGroup *actions;
+	GSimpleAction *action;
+
+	gtk_init (NULL, NULL);
 
 	RB_CHAIN_GOBJECT_METHOD (rb_shell_parent_class, constructed, object);
 
 	shell = RB_SHELL (object);
 
-	rb_debug ("Constructing shell");
-	rb_profile_start ("constructing shell");
+	/* create application actions */
+	actions = g_simple_action_group_new ();
+	action = g_simple_action_new_stateful ("LoadURI", G_VARIANT_TYPE ("(sb)"), g_variant_new ("(bb)", FALSE, FALSE));
+	g_signal_connect_object (action, "activate", G_CALLBACK (load_uri_action_cb), shell, 0);
+	g_simple_action_group_insert (actions, G_ACTION (action));
+	g_object_unref (action);
+
+	action = g_simple_action_new ("ActivateSource", G_VARIANT_TYPE ("(su)"));
+	g_signal_connect_object (action, "activate", G_CALLBACK (activate_source_action_cb), shell, 0);
+	g_simple_action_group_insert (actions, G_ACTION (action));
+	g_object_unref (action);
+
+	action = g_simple_action_new ("Quit", NULL);
+	g_signal_connect_object (action, "activate", G_CALLBACK (quit_action_cb), shell, 0);
+	g_simple_action_group_insert (actions, G_ACTION (action));
+	g_object_unref (action);
+
+	g_application_set_action_group (G_APPLICATION (shell), G_ACTION_GROUP (actions));
+
+	/* construct enough of the rest of it to display the window if required */
 
 	shell->priv->settings = g_settings_new ("org.gnome.rhythmbox");
 
@@ -1670,81 +1967,7 @@ rb_shell_constructed (GObject *object)
 
 	construct_db (shell);
 
-	/* initialize shell services */
 	construct_widgets (shell);
-
-	g_signal_connect_object (shell->priv->settings, "changed", G_CALLBACK (settings_changed_cb), shell, 0);
-
-	action = gtk_action_group_get_action (shell->priv->actiongroup, "ViewSidePane");
-	g_settings_bind (shell->priv->settings, "display-page-tree-visible",
-			 action, "active",
-			 G_SETTINGS_BIND_DEFAULT);
-	g_settings_bind (shell->priv->settings, "display-page-tree-visible",
-			 shell->priv->sidebar_container, "visible",
-			 G_SETTINGS_BIND_DEFAULT);
-
-	action = gtk_action_group_get_action (shell->priv->actiongroup, "ViewToolbar");
-	g_settings_bind (shell->priv->settings, "toolbar-visible",
-			 action, "active",
-			 G_SETTINGS_BIND_DEFAULT);
-
-	rb_debug ("shell: syncing with settings");
-	rb_shell_sync_pane_visibility (shell);
-
-	g_signal_connect_object (G_OBJECT (shell->priv->db), "save-error",
-				 G_CALLBACK (rb_shell_db_save_error_cb), shell, 0);
-
-	construct_sources (shell);
-
-	construct_load_ui (shell);
-
-	construct_plugins (shell);
-
-	rb_shell_sync_window_state (shell, FALSE);
-	rb_shell_sync_smalldisplay (shell);
-	rb_shell_sync_party_mode (shell);
-	rb_shell_sync_toolbar_state (shell);
-
-	rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
-
-	/* by now we've added the built in sources and any sources from plugins,
-	 * so we can consider the fixed page groups loaded
-	 */
-	rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_LIBRARY));
-	rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_STORES));
-
-	rb_missing_plugins_init (GTK_WINDOW (shell->priv->window));
-
-	g_idle_add ((GSourceFunc)_scan_idle, shell);
-
-	/* GO GO GO! */
-	rb_debug ("loading database");
-	rhythmdb_load (shell->priv->db);
-
-	rb_debug ("shell: syncing window state");
-	rb_shell_sync_paned (shell);
-
-	/* set initial visibility */
-	rb_shell_set_visibility (shell, TRUE, TRUE);
-
-	gdk_notify_startup_complete ();
-
-	/* focus play if small, the entry view if not */
-	if (g_settings_get_boolean (shell->priv->settings, "small-display")) {
-		GtkWidget *play_button;
-
-		play_button = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/ToolBar/Play");
-		gtk_widget_grab_focus (play_button);
-	} else {
-		RBEntryView *view;
-
-		view = rb_source_get_entry_view (RB_SOURCE (shell->priv->library_source));
-		if (view != NULL) {
-			gtk_widget_grab_focus (GTK_WIDGET (view));
-		}
-	}
-
-	rb_profile_end ("constructing shell");
 }
 
 static gboolean
@@ -2727,7 +2950,8 @@ rb_shell_quit (RBShell *shell,
 
 	rb_shell_shutdown (shell);
 	rb_shell_sync_state (shell);
-	g_object_unref (G_OBJECT (shell));
+
+	g_application_release (G_APPLICATION (shell));
 
 	g_timeout_add_seconds (10, quit_timeout, NULL);
 	return TRUE;
@@ -2736,6 +2960,7 @@ rb_shell_quit (RBShell *shell,
 static gboolean
 idle_handle_load_complete (RBShell *shell)
 {
+	gboolean loaded, scanned;
 	GDK_THREADS_ENTER ();
 
 	rb_debug ("load complete");
@@ -2745,7 +2970,11 @@ idle_handle_load_complete (RBShell *shell)
 	shell->priv->load_complete = TRUE;
 	shell->priv->save_playlist_id = g_timeout_add_seconds (10, (GSourceFunc) idle_save_playlist_manager, shell);
 
-	g_signal_emit (shell, rb_shell_signals[DATABASE_LOAD_COMPLETE], 0);
+	if (shell->priv->no_registration == FALSE) {
+		g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (shell), "LoadURI"), "(bb)", &loaded, &scanned);
+		g_action_group_change_action_state (G_ACTION_GROUP (shell), "LoadURI", g_variant_new ("(bb)", TRUE, scanned));
+		emit_action_state_update (shell, "LoadURI");
+	}
 
 	rhythmdb_start_action_thread (shell->priv->db);
 
@@ -3471,153 +3700,6 @@ rb_shell_get_party_mode (RBShell *shell)
 }
 
 /**
- * rb_shell_get_player:
- * @shell: the #RBShell
- *
- * Returns the #RBShellPlayer object
- *
- * Return value: (transfer none): the #RBShellPlayer object
- */
-GObject *
-rb_shell_get_player (RBShell *shell)
-{
-	return G_OBJECT (shell->priv->player_shell);
-}
-
-/**
- * rb_shell_get_player_path:
- * @shell: the #RBShell
- *
- * Returns the DBus object path for the #RBShellPlayer
- *
- * Return value: the DBus object path for the #RBShellPlayer
- */
-const char *
-rb_shell_get_player_path (RBShell *shell)
-{
-	return "/org/gnome/Rhythmbox/Player";
-}
-
-/**
- * rb_shell_get_playlist_manager:
- * @shell: the #RBShell
- *
- * Returns the #RBPlaylistManager object
- *
- * Return value: (transfer none): the #RBPlaylistManager object
- */
-GObject *
-rb_shell_get_playlist_manager (RBShell *shell)
-{
-	return G_OBJECT (shell->priv->playlist_manager);
-}
-
-/**
- * rb_shell_get_playlist_manager_path:
- * @shell: the #RBShell
- *
- * Returns the DBus path for the #RBPlaylistManager object
- *
- * Return value: the DBus object path for the #RBPlaylistManager
- */
-const char *
-rb_shell_get_playlist_manager_path (RBShell *shell)
-{
-	return "/org/gnome/Rhythmbox/PlaylistManager";
-}
-
-/**
- * rb_shell_get_ui_manager:
- * @shell: the #RBShell
- *
- * Returns the main #GtkUIManager object
- *
- * Return value: (transfer none): the main #GtkUIManager object
- */
-GObject *
-rb_shell_get_ui_manager (RBShell *shell)
-{
-	return G_OBJECT (shell->priv->ui_manager);
-}
-
-/**
- * rb_shell_add_to_queue:
- * @shell: the #RBShell
- * @uri: the URI to add to the play queue
- * @error: not used
- *
- * Adds the specified URI to the play queue.  This only works if URI is already
- * in the database.
- *
- * Return value: not used
- */
-gboolean
-rb_shell_add_to_queue (RBShell *shell,
-		       const gchar *uri,
-		       GError **error)
-{
-	RhythmDBEntry *entry;
-
-	entry = rhythmdb_entry_lookup_by_location (shell->priv->db, uri);
-	if (entry == NULL) {
-		RBSource *source;
-		source = rb_shell_guess_source_for_uri (shell, uri);
-		if (source != NULL) {
-			rb_source_add_uri (source, uri, NULL, NULL, NULL, NULL, NULL);
-		} else {
-			g_set_error (error,
-				     RB_SHELL_ERROR,
-				     RB_SHELL_ERROR_NO_SOURCE_FOR_URI,
-				     _("No registered source can handle URI %s"),
-				     uri);
-			return FALSE;
-		}
-	}
-	rb_static_playlist_source_add_location (RB_STATIC_PLAYLIST_SOURCE (shell->priv->queue_source),
-						uri, -1);
-	return TRUE;
-}
-
-/**
- * rb_shell_remove_from_queue:
- * @shell: the #RBShell
- * @uri: the URI to remove from the play queue
- * @error: not used
- *
- * Removes the specified URI from the play queue.  If the URI is not
- * in the play queue, nothing happens.
- *
- * Return value: not used.
- */
-gboolean
-rb_shell_remove_from_queue (RBShell *shell,
-			    const gchar *uri,
-			    GError **error)
-{
-	if (rb_playlist_source_location_in_map (RB_PLAYLIST_SOURCE (shell->priv->queue_source), uri))
-		rb_static_playlist_source_remove_location (RB_STATIC_PLAYLIST_SOURCE (shell->priv->queue_source),
-							   uri);
-	return TRUE;
-}
-
-/**
- * rb_shell_clear_queue:
- * @shell: the #RBShell
- * @error: not used
- *
- * Removes all entries from the play queue.
- *
- * Return value: not used
- */
-gboolean
-rb_shell_clear_queue (RBShell *shell,
-		      GError **error)
-{
-	rb_play_queue_source_clear_queue (RB_PLAY_QUEUE_SOURCE (shell->priv->queue_source));
-	return TRUE;
-}
-
-/**
  * rb_shell_present:
  * @shell: the #RBShell
  * @timestamp: GTK timestamp to use (for focus-stealing prevention)
diff --git a/shell/rb-shell.h b/shell/rb-shell.h
index a9ef0b0..e5c92f9 100644
--- a/shell/rb-shell.h
+++ b/shell/rb-shell.h
@@ -84,14 +84,14 @@ typedef struct _RBShellPrivate RBShellPrivate;
 
 struct _RBShell
 {
-        GObject parent;
+        GtkApplication parent;
 
 	RBShellPrivate *priv;
 };
 
 struct _RBShellClass
 {
-        GObjectClass parent_class;
+        GtkApplicationClass parent_class;
 
 	/* signals */
 	gboolean (*visibility_changing)	(RBShell *shell, gboolean initial, gboolean visible);
@@ -103,13 +103,7 @@ struct _RBShellClass
 
 GType		rb_shell_get_type	(void);
 
-RBShell *	rb_shell_new		(gboolean no_registration,
-					 gboolean no_update,
-					 gboolean dry_run,
-					 gboolean autostarted,
-					 gboolean disable_plugins,
-					 char *rhythmdb,
-					 char *playlists);
+RBShell *	rb_shell_new		(gboolean autostarted, int *argc, char ***argv);
 
 gboolean        rb_shell_present        (RBShell *shell, guint32 timestamp, GError **error);
 
@@ -123,12 +117,6 @@ gboolean        rb_shell_add_uri        (RBShell *shell,
 
 gboolean        rb_shell_load_uri       (RBShell *shell, const char *uri, gboolean play, GError **error);
 
-GObject *       rb_shell_get_player     (RBShell *shell);
-const char *    rb_shell_get_player_path(RBShell *shell);
-GObject *	rb_shell_get_playlist_manager (RBShell *shell);
-const char *	rb_shell_get_playlist_manager_path (RBShell *shell);
-GObject *	rb_shell_get_ui_manager (RBShell *shell);
-
 void            rb_shell_toggle_visibility (RBShell *shell);
 
 gboolean        rb_shell_get_song_properties (RBShell *shell,
@@ -142,17 +130,6 @@ gboolean        rb_shell_set_song_property (RBShell *shell,
 					    const GValue *value,
 					    GError **error);
 
-gboolean	rb_shell_add_to_queue (RBShell *shell,
-				       const gchar *uri,
-				       GError **error);
-
-gboolean	rb_shell_remove_from_queue (RBShell *shell,
-					    const gchar *uri,
-					    GError **error);
-
-gboolean	rb_shell_clear_queue (RBShell *shell,
-				      GError **error);
-
 gboolean	rb_shell_quit (RBShell *shell,
 			       GError **error);
 



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