Thu Oct 19 20:31:19 EST 2006  jonathan@kaolin.wh9.net
  * VIS: fix ref counting on vis widgets
Thu Oct 19 08:36:22 EST 2006  jonathan@kaolin.wh9.net
  * VIS: install glade file too
Thu Oct 19 08:17:11 EST 2006  jonathan@kaolin.wh9.net
  * VIS: fix makefile, include UI, minor code fixes
Thu Oct 19 08:04:18 EST 2006  jonathan@kaolin.wh9.net
  * VIS: actually build the plugin
Wed Oct 18 23:30:27 EST 2006  jonathan@kaolin.wh9.net
  * VIS: fix makefile a bit
Mon Oct 16 09:00:49 EST 2006  jonathan@kaolin.wh9.net
  * VIS: add mostly-complete visualizer plugin
Mon Oct 16 08:58:56 EST 2006  jonathan@kaolin.wh9.net
  * VIS: emit playing-song == NULL when stopping playback
Mon Oct 16 08:57:07 EST 2006  jonathan@kaolin.wh9.net
  * VIS: let plugins get at the playbin object and attach sinks to it
Mon Oct 16 08:43:29 EST 2006  jonathan@kaolin.wh9.net
  * VIS: minor whitespace cleanup
Wed Aug 16 16:30:55 EST 2006  jonathan@kaolin.wh9.net
  * VIS: interface to add notebook pages
diff -rN -u -p old-v2/backends/gstreamer/rb-player-gst.c new-v2/backends/gstreamer/rb-player-gst.c
--- old-v2/backends/gstreamer/rb-player-gst.c	2006-10-19 20:32:06.000000000 +1000
+++ new-v2/backends/gstreamer/rb-player-gst.c	2006-10-19 20:32:07.000000000 +1000
@@ -47,6 +47,10 @@
 
 static void rb_player_init (RBPlayerIface *iface);
 static void rb_player_gst_finalize (GObject *object);
+static void rb_player_gst_get_property (GObject *object,
+					guint prop_id,
+					GValue *value,
+					GParamSpec *pspec);
 
 static gboolean rb_player_gst_open (RBPlayer *player,
 				    const char *uri,
@@ -72,6 +76,12 @@ G_DEFINE_TYPE_WITH_CODE(RBPlayerGst, rb_
 
 #define RB_PLAYER_GST_TICK_HZ 5
 
+enum
+{
+	PROP_0,
+	PROP_PLAYBIN
+};
+
 struct _RBPlayerGstPrivate
 {
 	char *uri;
@@ -128,6 +138,15 @@ rb_player_gst_class_init (RBPlayerGstCla
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
 	object_class->finalize = rb_player_gst_finalize;
+	object_class->get_property = rb_player_gst_get_property;
+
+	g_object_class_install_property (object_class,
+					 PROP_PLAYBIN,
+					 g_param_spec_object ("playbin",
+						 	      "playbin",
+							      "playbin element",
+							      GST_TYPE_ELEMENT,
+							      G_PARAM_READABLE));
 
 	g_type_class_add_private (klass, sizeof (RBPlayerGstPrivate));
 }
@@ -173,6 +192,24 @@ rb_player_gst_init (RBPlayerGst *mp)
 }
 
 static void
+rb_player_gst_get_property (GObject *object,
+			    guint prop_id,
+			    GValue *value,
+			    GParamSpec *pspec)
+{
+	RBPlayerGst *mp = RB_PLAYER_GST (object);
+
+	switch (prop_id) {
+	case PROP_PLAYBIN:
+		g_value_set_object (value, mp->priv->playbin);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
 rb_player_gst_finalize (GObject *object)
 {
 	RBPlayerGst *mp;
@@ -548,7 +585,8 @@ static gboolean
 rb_player_gst_construct (RBPlayerGst *mp, GError **error)
 {
 	char *element_name = NULL;
-	GstElement *sink, *fakesink;
+	GstElement *sink;
+	GstElement *fakesink;
 
 	/* playbin */
 	rb_debug ("constructing element \"playbin\"");
@@ -557,9 +595,6 @@ rb_player_gst_construct (RBPlayerGst *mp
 		goto missing_element;
 	}
 
-	fakesink = gst_element_factory_make ("fakesink", "fakesink");
-	g_object_set (G_OBJECT (mp->priv->playbin), "video-sink", fakesink, NULL);
-
 #ifdef HAVE_GSTREAMER_0_8
 	g_signal_connect_object (G_OBJECT (mp->priv->playbin),
 				 "found_tag",
@@ -580,21 +615,43 @@ rb_player_gst_construct (RBPlayerGst *mp
 
 	g_signal_connect_object (G_OBJECT (mp->priv->playbin), "eos",
 				 G_CALLBACK (eos_cb), mp, 0);
+#endif
 
+#ifdef HAVE_GSTREAMER_0_8
 	/* Output sink */
 	sink = gst_gconf_get_default_audio_sink ();
+	g_object_set (G_OBJECT (mp->priv->playbin), "audio-sink", sink, NULL);
 #endif
 #ifdef HAVE_GSTREAMER_0_10
 	gst_bus_add_watch (gst_element_get_bus (GST_ELEMENT (mp->priv->playbin)),
 			     (GstBusFunc) rb_player_gst_bus_cb, mp);
 
-	/* Output sink */
-	sink = gst_element_factory_make ("gconfaudiosink", "audiosink");
-#endif
+	/* let plugins add bits to playbin */
+	g_object_notify (G_OBJECT (mp), "playbin");
 
-	/* if we could create the gconf sink use that, otherwise let playbin decide */
-	if (sink != NULL)
+	/* Use gconfaudiosink for audio if there's no audio sink yet */
+	g_object_get (G_OBJECT (mp->priv->playbin), "audio-sink", &sink, NULL);
+	if (sink == NULL) {
+		sink = gst_element_factory_make ("gconfaudiosink", "audiosink");
+
+		if (sink == NULL) {
+			/* fall back to autoaudiosink */
+			sink = gst_element_factory_make ("autoaudiosink", "audiosink");
+		}
 		g_object_set (G_OBJECT (mp->priv->playbin), "audio-sink", sink, NULL);
+	} else {
+		g_object_unref (sink);
+	}
+#endif
+
+	/* Use fakesink for video if there's no video sink yet */
+	g_object_get (G_OBJECT (mp->priv->playbin), "video-sink", &sink, NULL);
+	if (sink == NULL) {
+		fakesink = gst_element_factory_make ("fakesink", "fakesink");
+		g_object_set (G_OBJECT (mp->priv->playbin), "video-sink", fakesink, NULL);
+	} else {
+		g_object_unref (sink);
+	}
 
 	if (mp->priv->cur_volume > 1.0)
 		mp->priv->cur_volume = 1.0;
diff -rN -u -p old-v2/configure.ac new-v2/configure.ac
--- old-v2/configure.ac	2006-10-19 20:32:06.000000000 +1000
+++ new-v2/configure.ac	2006-10-19 20:32:07.000000000 +1000
@@ -979,6 +979,7 @@ plugins/magnatune/magnatune/Makefile
 plugins/generic-player/Makefile
 plugins/rb/Makefile
 plugins/power-manager/Makefile
+plugins/visualizer/Makefile
 bindings/Makefile
 bindings/python/Makefile
 help/Makefile
diff -rN -u -p old-v2/plugins/Makefile.am new-v2/plugins/Makefile.am
--- old-v2/plugins/Makefile.am	2006-10-19 20:32:06.000000000 +1000
+++ new-v2/plugins/Makefile.am	2006-10-19 20:32:07.000000000 +1000
@@ -33,6 +33,10 @@ if USE_CD_BURNING
 SUBDIRS += cd-recorder
 endif
 
+if USE_GSTREAMER_0_10
+SUBDIRS += visualizer
+endif
+
 noinst_LTLIBRARIES = librbplugins.la
 
 librbplugins_la_SOURCES =				\
diff -rN -u -p old-v2/plugins/visualizer/Makefile.am new-v2/plugins/visualizer/Makefile.am
--- old-v2/plugins/visualizer/Makefile.am	1970-01-01 10:00:00.000000000 +1000
+++ new-v2/plugins/visualizer/Makefile.am	2006-10-19 20:32:07.000000000 +1000
@@ -0,0 +1,72 @@
+NULL =
+
+plugindir = $(PLUGINDIR)/visualizer
+plugin_LTLIBRARIES = libvisualizer.la
+
+libvisualizer_la_SOURCES = \
+	rb-visualizer-plugin.c				\
+	rb-vis-widget.c					\
+	rb-vis-widget.h					\
+	rb-fake-visualizer.c
+
+
+libvisualizer_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+
+libvisualizer_la_LIBADD = 				\
+	-lgstinterfaces-0.10				\
+	$(RHYTHMBOX_LIBS)
+
+INCLUDES = 						\
+        -DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
+	-DG_LOG_DOMAIN=\"Rhythmbox\"		 	\
+	-I$(top_srcdir) 				\
+	-I$(top_srcdir)/lib                        	\
+	-I$(top_srcdir)/metadata                       	\
+	-I$(top_srcdir)/backends                       	\
+	-I$(top_srcdir)/backends/gstreamer            	\
+	-I$(top_srcdir)/rhythmdb                       	\
+	-I$(top_srcdir)/widgets                    	\
+	-I$(top_srcdir)/sources                    	\
+	-I$(top_srcdir)/iradio                    	\
+	-I$(top_srcdir)/podcast                    	\
+	-I$(top_srcdir)/remote				\
+	-I$(top_builddir)/remote			\
+	-I$(top_srcdir)/plugins				\
+	-I$(top_srcdir)/shell				\
+	-DPIXMAP_DIR=\""$(datadir)/pixmaps"\"		\
+	-DSHARE_DIR=\"$(pkgdatadir)\"                   \
+	-DDATADIR=\""$(datadir)"\"			\
+	$(RHYTHMBOX_CFLAGS)				\
+	$(WARN_CFLAGS)					\
+	-D_XOPEN_SOURCE -D_BSD_SOURCE
+
+gladedir = $(plugindir)
+glade_DATA =
+
+plugin_in_files = visualizer.rb-plugin.desktop.in
+
+%.rb-plugin: %.rb-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+BUILT_SOURCES =							\
+	$(plugin_in_files:.rb-plugin.desktop.in=.rb-plugin) 	\
+	$(NULL)
+
+plugin_DATA = 			\
+	$(BUILT_SOURCES)	\
+	visualizer-ui.xml	\
+	visualizer-controls.glade \
+	$(NULL)
+
+EXTRA_DIST = 			\
+	$(glade_DATA)		\
+	$(plugin_in_files)	\
+	$(NULL)
+
+CLEANFILES = 			\
+	$(BUILT_SOURCES)	\
+	$(NULL)
+
+DISTCLEANFILES =		\
+	$(BUILT_SOURCES)	\
+	$(NULL)
+
diff -rN -u -p old-v2/plugins/visualizer/rb-fake-visualizer.c new-v2/plugins/visualizer/rb-fake-visualizer.c
--- old-v2/plugins/visualizer/rb-fake-visualizer.c	1970-01-01 10:00:00.000000000 +1000
+++ new-v2/plugins/visualizer/rb-fake-visualizer.c	2006-10-19 20:32:07.000000000 +1000
@@ -0,0 +1,578 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2004 Benjamin Otte <otte@gnome.org>
+ *  Copyright (C) 2006 Jonathan Matthew
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ */
+
+/* fake visualizer element for evil rhythmbox purposes */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <gst/base/gstadapter.h>
+#include <gst/video/video.h>
+#include <gst/audio/audio.h>
+
+#define RB_TYPE_FAKE_VIS (rb_fake_vis_get_type())
+#define RB_IS_FAKE_VIS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),RB_TYPE_FAKE_VIS))
+#define RB_FAKE_VIS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),RB_TYPE_FAKE_VIS,RBFakeVis))
+#define RB_IS_FAKE_VIS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),RB_TYPE_FAKE_VIS))
+#define RB_FAKE_VIS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),RB_TYPE_FAKE_VIS,RBFakeVisClass))
+#define RB_FAKE_VIS_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), RB_TYPE_FAKE_VIS, RBFakeVisClass))
+
+typedef struct _RBFakeVis RBFakeVis;
+typedef struct _RBFakeVisClass RBFakeVisClass;
+
+GST_DEBUG_CATEGORY_STATIC (rb_fake_vis_debug);
+#define GST_CAT_DEFAULT (rb_fake_vis_debug)
+
+struct _RBFakeVis
+{
+  GstElement element;
+
+  /* pads */
+  GstPad *sinkpad;
+  GstPad *srcpad;
+  GstClockTime next_ts;
+  GstSegment segment;
+
+  /* audio/video state */
+  gint channels;
+  gint rate;                    /* Input samplerate */
+  gint bps;
+
+  /* framerate numerator & denominator */
+  gint fps_n;
+  gint fps_d;
+  gint width;
+  gint height;
+  gint depth;
+  GstClockTime duration;
+
+  /* samples per frame based on caps */
+  guint spf;
+
+  /* state stuff */
+  /*GstAdapter *adapter;*/
+  guint avail;
+
+  /* QoS stuff *//* with LOCK */
+  gdouble proportion;
+  GstClockTime earliest_time;
+};
+
+struct _RBFakeVisClass
+{
+  GstElementClass parent_class;
+};
+
+GType rb_fake_vis_get_type (void);
+
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_VIDEO_CAPS_xRGB_HOST_ENDIAN)
+    );
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("audio/x-raw-int, "
+        "width = (int) 16, "
+        "depth = (int) 16, "
+        "endianness = (int) BYTE_ORDER, "
+        "signed = (boolean) TRUE, "
+        "channels = (int) { 1, 2 }, " "rate = (int) [ 1000, MAX ]")
+    );
+
+static GstStateChangeReturn rb_fake_vis_change_state (GstElement * element,
+    GstStateChange transition);
+static GstFlowReturn rb_fake_vis_chain (GstPad * pad, GstBuffer * buffer);
+static gboolean rb_fake_vis_sink_event (GstPad * pad, GstEvent * event);
+static gboolean rb_fake_vis_src_event (GstPad * pad, GstEvent * event);
+
+static gboolean rb_fake_vis_sink_setcaps (GstPad * pad, GstCaps * caps);
+static gboolean rb_fake_vis_src_setcaps (GstPad * pad, GstCaps * caps);
+static GstCaps *rb_fake_vis_getcaps (GstPad * pad);
+
+static GstElementDetails rb_fake_vis_details =
+	GST_ELEMENT_DETAILS ("RB fake visualizer",
+			     "Visualization",
+			     "pretend to generate visualization from audio input",
+			     "Benjamin Otte <otte@gnome.org>, Jonathan Matthew <jonathan@kaolin.wh9.net>");
+
+static void
+_do_init (GType fake_vis_type)
+{
+  GST_DEBUG_CATEGORY_INIT (rb_fake_vis_debug,
+			   "fakevis", GST_DEBUG_FG_WHITE,
+			   "Rhythmbox built-in fake visualizer");
+}
+
+GST_BOILERPLATE_FULL (RBFakeVis, rb_fake_vis, GstElement, GST_TYPE_ELEMENT, _do_init);
+
+static void
+rb_fake_vis_base_init (gpointer g_class)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+  gst_element_class_set_details (element_class, &rb_fake_vis_details);
+  gst_element_class_add_pad_template (element_class,
+				      gst_static_pad_template_get (&src_template));
+  gst_element_class_add_pad_template (element_class,
+				      gst_static_pad_template_get (&sink_template));
+}
+
+static void
+rb_fake_vis_class_init (RBFakeVisClass *klass)
+{
+  GstElementClass *element = GST_ELEMENT_CLASS (klass);
+  element->change_state = rb_fake_vis_change_state;
+}
+
+static void
+rb_fake_vis_init (RBFakeVis * visual, RBFakeVisClass *klass)
+{
+  /* create the sink and src pads */
+  visual->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
+  gst_pad_set_setcaps_function (visual->sinkpad, rb_fake_vis_sink_setcaps);
+  gst_pad_set_chain_function (visual->sinkpad, rb_fake_vis_chain);
+  gst_pad_set_event_function (visual->sinkpad, rb_fake_vis_sink_event);
+  gst_element_add_pad (GST_ELEMENT (visual), visual->sinkpad);
+
+  visual->srcpad = gst_pad_new_from_static_template (&src_template, "src");
+  gst_pad_set_setcaps_function (visual->srcpad, rb_fake_vis_src_setcaps);
+  gst_pad_set_getcaps_function (visual->srcpad, rb_fake_vis_getcaps);
+  gst_pad_set_event_function (visual->srcpad, rb_fake_vis_src_event);
+  gst_element_add_pad (GST_ELEMENT (visual), visual->srcpad);
+}
+
+static void
+rb_fake_vis_reset (RBFakeVis * visual)
+{
+  visual->next_ts = -1;
+  visual->avail = 0;
+  gst_segment_init (&visual->segment, GST_FORMAT_UNDEFINED);
+
+  GST_OBJECT_LOCK (visual);
+  visual->proportion = 1.0;
+  visual->earliest_time = -1;
+  GST_OBJECT_UNLOCK (visual);
+}
+
+static gboolean
+rb_fake_vis_src_setcaps (GstPad * pad, GstCaps * caps)
+{
+  RBFakeVis *visual = RB_FAKE_VIS (gst_pad_get_parent (pad));
+  GstStructure *structure;
+
+  structure = gst_caps_get_structure (caps, 0);
+
+  GST_DEBUG_OBJECT (visual, "src pad got caps %" GST_PTR_FORMAT, caps);
+
+  if (!gst_structure_get_int (structure, "width", &visual->width))
+    goto error;
+  if (!gst_structure_get_int (structure, "height", &visual->height))
+    goto error;
+  if (!gst_structure_get_int (structure, "bpp", &visual->depth))
+    goto error;
+  if (!gst_structure_get_fraction (structure, "framerate", &visual->fps_n,
+          &visual->fps_d))
+    goto error;
+
+  visual->spf =
+      gst_util_uint64_scale_int (visual->rate, visual->fps_d, visual->fps_n);
+  visual->duration =
+      gst_util_uint64_scale_int (GST_SECOND, visual->fps_d, visual->fps_n);
+
+  gst_object_unref (visual);
+  return TRUE;
+
+  /* ERRORS */
+error:
+  {
+    GST_DEBUG_OBJECT (visual, "error parsing caps");
+    gst_object_unref (visual);
+    return FALSE;
+  }
+}
+
+
+static gboolean
+rb_fake_vis_sink_setcaps (GstPad * pad, GstCaps * caps)
+{
+  RBFakeVis *visual = RB_FAKE_VIS (gst_pad_get_parent (pad));
+  GstStructure *structure;
+
+  structure = gst_caps_get_structure (caps, 0);
+
+  gst_structure_get_int (structure, "channels", &visual->channels);
+  gst_structure_get_int (structure, "rate", &visual->rate);
+
+  if (visual->fps_n != 0) {
+    visual->spf =
+        gst_util_uint64_scale_int (visual->rate, visual->fps_d, visual->fps_n);
+  }
+  visual->bps = visual->channels * sizeof (gint16);
+
+  gst_object_unref (visual);
+  return TRUE;
+}
+
+
+static GstCaps *
+rb_fake_vis_getcaps (GstPad * pad)
+{
+  GstCaps *ret;
+  RBFakeVis *visual = RB_FAKE_VIS (gst_pad_get_parent (pad));
+
+  ret = gst_caps_copy (gst_pad_get_pad_template_caps (visual->srcpad));
+
+  GST_DEBUG_OBJECT (visual, "returning caps %" GST_PTR_FORMAT, ret);
+  gst_object_unref (visual);
+  return ret;
+}
+
+
+static gboolean
+rb_fake_vis_src_negotiate (RBFakeVis * visual)
+{
+  GstCaps *othercaps, *target, *intersect;
+  GstStructure *structure;
+  const GstCaps *templ;
+
+  templ = gst_pad_get_pad_template_caps (visual->srcpad);
+
+  /* see what the peer can do */
+  othercaps = gst_pad_peer_get_caps (visual->srcpad);
+  if (othercaps) {
+    intersect = gst_caps_intersect (othercaps, templ);
+    gst_caps_unref (othercaps);
+
+    if (gst_caps_is_empty (intersect))
+      goto no_format;
+
+    target = gst_caps_copy_nth (intersect, 0);
+    gst_caps_unref (intersect);
+  } else {
+    target = gst_caps_ref ((GstCaps *) templ);
+  }
+
+  structure = gst_caps_get_structure (target, 0);
+  gst_structure_fixate_field_nearest_int (structure, "width", 1);
+  gst_structure_fixate_field_nearest_int (structure, "height", 1);
+  gst_structure_fixate_field_nearest_fraction (structure, "framerate", 1, 1);
+
+  gst_pad_set_caps (visual->srcpad, target);
+  gst_caps_unref (target);
+
+  return TRUE;
+
+  /* ERRORS */
+no_format:
+  {
+    GST_ELEMENT_ERROR (visual, STREAM, FORMAT, (NULL),
+        ("could not negotiate output format"));
+    gst_caps_unref (intersect);
+    return FALSE;
+  }
+}
+
+static gboolean
+rb_fake_vis_sink_event (GstPad * pad, GstEvent * event)
+{
+  RBFakeVis *visual;
+  gboolean res;
+
+  visual = RB_FAKE_VIS (gst_pad_get_parent (pad));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_FLUSH_START:
+      res = gst_pad_push_event (visual->srcpad, event);
+      break;
+    case GST_EVENT_FLUSH_STOP:
+      rb_fake_vis_reset (visual);
+      res = gst_pad_push_event (visual->srcpad, event);
+      break;
+    case GST_EVENT_NEWSEGMENT:
+    {
+      GstFormat format;
+      gdouble rate, arate;
+      gint64 start, stop, time;
+      gboolean update;
+
+      /* the newsegment values are used to clip the input samples
+       * and to convert the incoming timestamps to running time so
+       * we can do QoS */
+      gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
+          &start, &stop, &time);
+
+      /* now configure the values */
+      gst_segment_set_newsegment_full (&visual->segment, update,
+          rate, arate, format, start, stop, time);
+
+      /* and forward */
+      res = gst_pad_push_event (visual->srcpad, event);
+      break;
+    }
+    default:
+      res = gst_pad_push_event (visual->srcpad, event);
+      break;
+  }
+
+  gst_object_unref (visual);
+  return res;
+}
+
+static gboolean
+rb_fake_vis_src_event (GstPad * pad, GstEvent * event)
+{
+  RBFakeVis *visual;
+  gboolean res;
+
+  visual = RB_FAKE_VIS (gst_pad_get_parent (pad));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_QOS:
+    {
+      gdouble proportion;
+      GstClockTimeDiff diff;
+      GstClockTime timestamp;
+
+      gst_event_parse_qos (event, &proportion, &diff, &timestamp);
+
+      /* save stuff for the _chain function */
+      GST_OBJECT_LOCK (visual);
+      visual->proportion = proportion;
+      if (diff >= 0)
+        /* we're late, this is a good estimate for next displayable
+         * frame (see part-qos.txt) */
+        visual->earliest_time = timestamp + 2 * diff + visual->duration;
+      else
+        visual->earliest_time = timestamp + diff;
+
+      GST_OBJECT_UNLOCK (visual);
+
+      res = gst_pad_push_event (visual->sinkpad, event);
+      break;
+    }
+    default:
+      res = gst_pad_push_event (visual->sinkpad, event);
+      break;
+  }
+
+  gst_object_unref (visual);
+  return res;
+}
+
+/* allocate and output buffer, if no format was negotiated, this
+ * function will negotiate one. After calling this function, a
+ * reverse negotiation could have happened. */
+static GstFlowReturn
+get_buffer (RBFakeVis * visual, GstBuffer ** outbuf)
+{
+  GstFlowReturn ret;
+  guint outsize;
+
+  /* we don't know an output format yet, pick one */
+  if (GST_PAD_CAPS (visual->srcpad) == NULL) {
+    if (!rb_fake_vis_src_negotiate (visual))
+      return GST_FLOW_NOT_NEGOTIATED;
+  }
+
+  outsize = visual->height * visual->width * (visual->depth / 8);
+
+  GST_DEBUG_OBJECT (visual, "allocating output buffer with caps %"
+      GST_PTR_FORMAT, GST_PAD_CAPS (visual->srcpad));
+
+  /* now allocate a buffer with the last negotiated format.
+   * Downstream could renegotiate a new format, which will trigger
+   * our setcaps function on the source pad. */
+  ret =
+      gst_pad_alloc_buffer_and_set_caps (visual->srcpad,
+      GST_BUFFER_OFFSET_NONE, outsize,
+      GST_PAD_CAPS (visual->srcpad), outbuf);
+
+  /* no buffer allocated, we don't care why. */
+  if (ret != GST_FLOW_OK)
+    return ret;
+
+  /* this is bad and should not happen. When the alloc function
+   * returns _OK, core ensures we have a valid buffer. */
+  if (*outbuf == NULL)
+    return GST_FLOW_ERROR;
+
+  return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+rb_fake_vis_chain (GstPad * pad, GstBuffer * buffer)
+{
+  GstBuffer *outbuf = NULL;
+  RBFakeVis *visual = RB_FAKE_VIS (gst_pad_get_parent (pad));
+  GstFlowReturn ret = GST_FLOW_OK;
+
+  GST_DEBUG_OBJECT (visual, "chain function called");
+
+  /* If we don't have an output format yet, preallocate a buffer to try and
+   * set one */
+  if (GST_PAD_CAPS (visual->srcpad) == NULL) {
+    GST_DEBUG_OBJECT (visual, "calling buffer alloc to set caps");
+    ret = get_buffer (visual, &outbuf);
+    if (ret != GST_FLOW_OK) {
+      GST_DEBUG_OBJECT (visual, "couldn't allocate buffer: %s", gst_flow_get_name (ret));
+      gst_buffer_unref (buffer);
+      goto beach;
+    }
+  }
+
+  /* resync on DISCONT */
+  if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) {
+    visual->avail = 0;
+    visual->next_ts = -1;
+  }
+
+  /* Match timestamps from the incoming audio */
+  if (GST_BUFFER_TIMESTAMP (buffer) != GST_CLOCK_TIME_NONE)
+    visual->next_ts = GST_BUFFER_TIMESTAMP (buffer);
+
+  GST_DEBUG_OBJECT (visual,
+      "Input buffer has %d samples, time=%" G_GUINT64_FORMAT,
+      GST_BUFFER_SIZE (buffer) / visual->bps, GST_BUFFER_TIMESTAMP (buffer));
+
+  visual->avail += GST_BUFFER_SIZE (buffer);
+  gst_buffer_unref (buffer);
+
+  while (visual->avail > MAX (512, visual->spf) * visual->bps) {
+    gboolean need_skip;
+
+    GST_DEBUG_OBJECT (visual, "processing buffer (%u avail)", visual->avail);
+
+    if (visual->next_ts != -1) {
+      gint64 qostime;
+
+      /* QoS is done on running time */
+      qostime = gst_segment_to_running_time (&visual->segment, GST_FORMAT_TIME,
+          visual->next_ts);
+
+      GST_OBJECT_LOCK (visual);
+      /* check for QoS, don't compute buffers that are known to be late */
+      need_skip = visual->earliest_time != -1 &&
+          qostime <= visual->earliest_time;
+      GST_OBJECT_UNLOCK (visual);
+
+      if (need_skip) {
+        GST_WARNING_OBJECT (visual,
+            "QoS: skip ts: %" GST_TIME_FORMAT ", earliest: %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (qostime), GST_TIME_ARGS (visual->earliest_time));
+        goto skip;
+      }
+    }
+    /* alloc a buffer if we don't have one yet, this happens
+     * when we pushed a buffer in this while loop before */
+    if (outbuf == NULL) {
+      ret = get_buffer (visual, &outbuf);
+      if (ret != GST_FLOW_OK) {
+        goto beach;
+      }
+    }
+
+    GST_BUFFER_TIMESTAMP (outbuf) = visual->next_ts;
+    GST_BUFFER_DURATION (outbuf) = visual->duration;
+
+    ret = gst_pad_push (visual->srcpad, outbuf);
+    outbuf = NULL;
+
+    GST_DEBUG_OBJECT (visual, "finished frame, flushing %u samples from input",
+        visual->spf);
+  skip:
+    /* interpolate next timestamp */
+    if (visual->next_ts != -1)
+      visual->next_ts += visual->duration;
+
+    /* Flush out the number of samples per frame * channels * sizeof (gint16) */
+    if (visual->avail < visual->spf * visual->bps)
+      visual->avail = 0;
+    else
+      visual->avail -= visual->spf * visual->bps;
+
+    /* quit the loop if something was wrong */
+    if (ret != GST_FLOW_OK)
+      break;
+  }
+
+  if (outbuf != NULL)
+    gst_buffer_unref (outbuf);
+
+beach:
+  gst_object_unref (visual);
+
+  GST_DEBUG_OBJECT (visual, "leaving chain function");
+  return ret;
+}
+
+static GstStateChangeReturn
+rb_fake_vis_change_state (GstElement * element, GstStateChange transition)
+{
+  RBFakeVis *visual = RB_FAKE_VIS (element);
+  GstStateChangeReturn ret;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      break;
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      rb_fake_vis_reset (visual);
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  return gst_element_register (plugin, "rbfakevis", GST_RANK_NONE, RB_TYPE_FAKE_VIS);
+}
+
+GST_PLUGIN_DEFINE_STATIC (GST_VERSION_MAJOR, GST_VERSION_MINOR,
+			  "rbfakevis",
+			  "fake visualizer",
+			  plugin_init,
+			  VERSION,
+			  "GPL",
+			  PACKAGE,
+			  "");
+
diff -rN -u -p old-v2/plugins/visualizer/rb-visualizer-plugin.c new-v2/plugins/visualizer/rb-visualizer-plugin.c
--- old-v2/plugins/visualizer/rb-visualizer-plugin.c	1970-01-01 10:00:00.000000000 +1000
+++ new-v2/plugins/visualizer/rb-visualizer-plugin.c	2006-10-19 20:32:07.000000000 +1000
@@ -0,0 +1,991 @@
+/*
+ * rb-visualizer-plugin.c
+ *
+ * Copyright (C) 2006  Jonathan Matthew
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ */
+
+/*
+ * visualizer plugin.
+ * - assumes we're going to continue using playbin
+ *     (it would be nice if any replacement pipeline had equivalent
+ *      hooks for a visualization->video output branch..)
+ * - does nasty tricks using a fake visualizer to convince
+ *   playbin to set up the machinery for visualization so we can
+ *   enable it at any time without even pausing the pipeline
+ *     (it would be nice if any replacement pipeline had the
+ *      ability to splice in a visualization->video output
+ *      branch at any time)
+ * - does further nasty tricks to make sure the video sink always
+ *     has a valid window ID to scribble on, so it doesn't go
+ *     creating its own window
+ *
+ * things to do:
+ * - fullscreen (including a way to use a different screen, maybe
+ *    just a different workspace)
+ * - make the controls disappear unless:
+ *    we have focus, and mouse has moved recently
+ *    (like totem's fullscreen controls)
+ * - libvisual opengl actors may require some work here too
+ * - especially for fullscreen, include a text overlay element
+ *    displaying artist/title/etc. for a bit after song changes
+ *    probably not very interesting otherwise.
+ * - maybe mangle caps filter when the output window size changes?
+ *    do something with aspect ratios?
+ * - ask for confirmation when enabling visualization for a remote
+ *    x display
+ * - screensaver hack (theme?) mode
+ *    I think this would just be a dbus method to set the window ID
+ *    and enable visualization.  the screensaver hack/theme would
+ *    just call this and pass in the correct window ID.  right?
+ *
+ * things to fix:
+ * - seems to act a bit wonky if disabled and reenabled - probably left
+ *     over signal handlers?
+ * - needs a way to hide the source header.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <glade/glade.h>
+
+#include <gst/gst.h>
+#include <gst/interfaces/xoverlay.h>
+
+#include "rb-plugin.h"
+#include "rb-debug.h"
+#include "rb-shell.h"
+#include "rb-shell-player.h"
+#include "rb-player.h"
+#include "rb-player-gst.h"
+#include "rb-dialog.h"
+#include "rb-file-helpers.h"
+#include "rb-preferences.h"
+#include "eel-gconf-extensions.h"
+
+#include "rb-vis-widget.h"
+
+/* not going to create a new header just for this */
+extern GType rb_fake_vis_get_type (void);
+
+/* preferences */
+#define CONF_VIS_PREFIX  CONF_PREFIX "/plugins/visualizer"
+#define CONF_VIS_ELEMENT CONF_VIS_PREFIX "/element"
+#define CONF_VIS_QUALITY CONF_VIS_PREFIX "/quality"
+
+/* defaults */
+#define DEFAULT_VIS_ELEMENT	"goom"
+#define DEFAULT_VIS_QUALITY	1
+
+/* plugin */
+#define RB_TYPE_VISUALIZER_PLUGIN		(rb_visualizer_plugin_get_type ())
+#define RB_VISUALIZER_PLUGIN(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_VISUALIZER_PLUGIN, RBVisualizerPlugin))
+#define RB_VISUALIZER_PLUGIN_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_VISUALIZER_PLUGIN, RBVisualizerPluginClass))
+#define RB_IS_VISUALIZER_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_VISUALIZER_PLUGIN))
+#define RB_IS_VISUALIZER_PLUGIN_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_VISUALIZER_PLUGIN))
+#define RB_VISUALIZER_PLUGIN_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_VISUALIZER_PLUGIN, RBVisualizerPluginClass))
+
+typedef struct {
+	const char *name;
+	const char *displayname;
+} VisPluginInfo;
+
+typedef struct {
+	const char *name;
+	int width;
+	int height;
+	gint fps_n;
+	gint fps_d;
+} VisualizerQuality;
+
+typedef struct
+{
+	RBPlugin parent;
+	RBShell *shell;
+	RBPlayer *player;
+
+	/* visualizer and output window */
+	GtkWidget *vis_box;
+	GtkWidget *vis_widget;
+	GstElement *visualizer;
+	GstElement *video_sink;
+	GstElement *playbin;
+	GstXOverlay *xoverlay;
+	gint bus_sync_id;
+	GdkWindow *fake_window;
+
+	gboolean enable_deferred;
+	gboolean fullscreen;	/* not yet */
+	gboolean active;
+
+	/* ui */
+	gint merge_id;
+	GtkActionGroup *action_group;
+
+	/* control ui */
+	GtkWidget *control_widget;
+	GtkWidget *fullscreen_button;		/* not yet */
+	GtkWidget *leave_fullscreen_button;	/* not yet */
+	GtkWidget *element_combo;
+	GtkWidget *quality_combo;
+	GList *vis_plugin_list;
+
+} RBVisualizerPlugin;
+
+typedef struct
+{
+	RBPluginClass parent_class;
+} RBVisualizerPluginClass;
+
+
+
+G_MODULE_EXPORT GType register_rb_plugin (GTypeModule *module);
+GType	rb_visualizer_plugin_get_type		(void) G_GNUC_CONST;
+
+static void rb_visualizer_plugin_cmd_toggle (GtkAction *action,
+					     RBVisualizerPlugin *pi);
+
+static void create_controls (RBVisualizerPlugin *pi);
+
+static GtkToggleActionEntry rb_visualizer_plugin_toggle_actions [] =
+{
+	{ "ToggleVisualizer", NULL, N_("Visualization"), NULL,
+	  N_("Start or stop visualization"),
+	  G_CALLBACK (rb_visualizer_plugin_cmd_toggle) },
+};
+
+/* these match totem's settings */
+static const VisualizerQuality vis_quality[] = {
+	{ N_("Small"),		200,	150,	10,	1 },
+	{ N_("Normal"),		320,	240,	20,	1 },
+	{ N_("Large"),		640,	480,	25,	1 },
+	{ N_("Extra Large"),	800,	600,	30,	1 },
+};
+
+/* "quality" to use for fake visualization; should be small and
+ * low framerate, but not so slow it makes a noticeable difference
+ * to playback start time.  the video sink needs to preroll too,
+ * so if the framerate is 1/10, that will take 10 seconds.
+ */
+static const VisualizerQuality fake_vis_quality = { "", 60, 60, 1, 1 };
+
+RB_PLUGIN_REGISTER(RBVisualizerPlugin, rb_visualizer_plugin)
+
+static void
+rb_visualizer_plugin_init (RBVisualizerPlugin *plugin)
+{
+	rb_debug ("RBVisualizerPlugin initialising");
+}
+
+static void
+rb_visualizer_plugin_dispose (GObject *object)
+{
+	RBVisualizerPlugin *plugin = RB_VISUALIZER_PLUGIN (object);
+	rb_debug ("RBVisualizerPlugin disposing");
+
+	if (plugin->vis_widget) {
+		g_object_unref (plugin->vis_widget);
+		plugin->vis_widget = NULL;
+	}
+
+	if (plugin->vis_box) {
+		g_object_unref (plugin->vis_box);
+		plugin->vis_box = NULL;
+	}
+
+	if (plugin->player) {
+		g_object_unref (plugin->player);
+		plugin->player = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_visualizer_plugin_parent_class)->dispose (object);
+}
+
+static void
+rb_visualizer_plugin_finalize (GObject *object)
+{
+	rb_debug ("RBVisualizerPlugin finalising");
+
+	G_OBJECT_CLASS (rb_visualizer_plugin_parent_class)->finalize (object);
+}
+
+static void
+bus_sync_message_cb (GstBus *bus, GstMessage *msg, RBVisualizerPlugin *plugin)
+{
+	if (msg->structure == NULL)
+		return;
+
+	if (plugin->vis_widget == NULL) {
+		rb_debug ("ignoring bus message");
+		return;
+	}
+
+	/* update our idea of where the xoverlay is */
+	if (plugin->xoverlay == NULL || GST_OBJECT (plugin->xoverlay) != msg->src) {
+		if (plugin->xoverlay)
+			gst_object_unref (GST_OBJECT (plugin->xoverlay));
+		plugin->xoverlay = GST_X_OVERLAY (msg->src);
+		gst_object_ref (msg->src);
+	}
+
+	/* feed it the window xid */
+	if (gst_structure_has_name (msg->structure, "prepare-xwindow-id")) {
+
+		XID window;
+		g_object_get (plugin->vis_widget, "window-xid", &window, NULL);
+		if (window == 0) {
+			window = GDK_WINDOW_XWINDOW (plugin->fake_window);
+			rb_debug ("setting fake window id %lu", window);
+		} else {
+			rb_debug ("setting window id %lu in prepare-xwindow-id handler", window);
+		}
+		gst_x_overlay_set_xwindow_id (plugin->xoverlay, window);
+	}
+}
+
+static gboolean
+find_xoverlay (RBVisualizerPlugin *plugin)
+{
+	/* (re-)locate xoverlay */
+	if (plugin->xoverlay) {
+		g_object_unref (plugin->xoverlay);
+		plugin->xoverlay = NULL;
+	}
+	if (GST_IS_BIN (plugin->video_sink)) {
+		GstElement *overlay;
+
+		overlay = gst_bin_get_by_interface (GST_BIN (plugin->video_sink),
+						    GST_TYPE_X_OVERLAY);
+		plugin->xoverlay = GST_X_OVERLAY (overlay);
+		rb_debug ("found xoverlay in video bin");
+	} else if (GST_IS_X_OVERLAY (plugin->video_sink)) {
+		plugin->xoverlay = GST_X_OVERLAY (plugin->video_sink);
+		g_object_ref (G_OBJECT (plugin->xoverlay));
+		rb_debug ("found video_sink implementing xoverlay");
+	}
+
+	if (!plugin->xoverlay) {
+		g_warning ("Couldn't find an x overlay");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void
+fixate_vis_caps (RBVisualizerPlugin *pi, GstElement *vis_element, GstElement *capsfilter, int quality)
+{
+	GstPad *pad;
+	GstCaps *caps = NULL;
+	const VisualizerQuality *q;
+
+	if (quality < 0)
+		quality = eel_gconf_get_integer (CONF_VIS_QUALITY);
+
+	if (quality < 0 || quality > G_N_ELEMENTS (vis_quality))
+		quality = DEFAULT_VIS_QUALITY;
+
+	if (pi->active)
+		q = &vis_quality[quality];
+	else
+		q = &fake_vis_quality;
+
+	pad = gst_element_get_pad (vis_element, "src");
+	caps = gst_pad_get_allowed_caps (pad);
+	gst_object_unref (pad);
+
+	if (caps && !gst_caps_is_fixed (caps)) {
+		guint i;
+		char *dbg;
+
+		caps = gst_caps_make_writable (caps);
+		for (i = 0; i < gst_caps_get_size (caps); i++) {
+			GstStructure *s = gst_caps_get_structure (caps, i);
+
+			gst_structure_fixate_field_nearest_int (s, "width", q->width);
+			gst_structure_fixate_field_nearest_int (s, "height", q->height);
+			gst_structure_fixate_field_nearest_fraction (s, "framerate", q->fps_n, q->fps_d);
+		}
+
+		dbg = gst_caps_to_string (caps);
+		rb_debug ("setting fixed caps on capsfilter: %s", dbg);
+		g_free (dbg);
+
+		g_object_set (capsfilter, "caps", caps, NULL);
+	} else if (caps) {
+		char *dbg = gst_caps_to_string (caps);
+		rb_debug ("vis element caps already fixed: %s", dbg);
+		g_free (dbg);
+	} else {
+		rb_debug ("vis element has no caps");
+	}
+
+	if (GST_IS_CAPS (caps))
+		gst_caps_unref (caps);
+}
+
+static void
+update_visualizer (RBVisualizerPlugin *plugin, const char *vis_override, int quality)
+{
+	XID window;
+	GstElement *capsfilter;
+	GstPad *pad;
+	char *vis_element_name = NULL;
+	GstElement *vis_plugin;
+
+	if (plugin->playbin == NULL)
+		return;
+
+	if (plugin->visualizer)
+		g_object_unref (plugin->visualizer);
+
+	plugin->visualizer = gst_bin_new (NULL);
+
+	/* set up capsfilter */
+	capsfilter = gst_element_factory_make ("capsfilter", NULL);
+	gst_bin_add (GST_BIN (plugin->visualizer), capsfilter);
+
+	pad = gst_element_get_pad (capsfilter, "src");
+	gst_element_add_pad (plugin->visualizer, gst_ghost_pad_new ("src", pad));
+	gst_object_unref (pad);
+
+	/* set up visualizer */
+	if (plugin->active) {
+
+		if (vis_override) {
+			vis_element_name = g_strdup (vis_override);
+		} else {
+			vis_element_name = eel_gconf_get_string (CONF_VIS_ELEMENT);
+		}
+
+		if (vis_element_name == NULL) {
+			vis_element_name = g_strdup (DEFAULT_VIS_ELEMENT);
+		}
+		rb_debug ("creating new visualizer: %s", vis_element_name);
+
+		vis_plugin = gst_element_factory_make (vis_element_name, NULL);
+		gst_bin_add (GST_BIN (plugin->visualizer), vis_plugin);
+		g_free (vis_element_name);
+	} else {
+		vis_plugin = g_object_new (rb_fake_vis_get_type (), NULL);
+		gst_bin_add (GST_BIN (plugin->visualizer), vis_plugin);
+	}
+
+	pad = gst_element_get_pad (vis_plugin, "sink");
+	gst_element_add_pad (plugin->visualizer, gst_ghost_pad_new ("sink", pad));
+	gst_object_unref (pad);
+
+	gst_element_link (vis_plugin, capsfilter);
+	fixate_vis_caps (plugin, vis_plugin, capsfilter, quality);
+
+	g_object_ref (plugin->visualizer);
+
+	g_object_set (plugin->playbin, "vis-plugin", plugin->visualizer, NULL);
+
+	/* update x overlay, just in case */
+	g_object_get (plugin->vis_widget, "window-xid", &window, NULL);
+	rb_debug ("setting window id %d", (int)window);
+	gst_x_overlay_set_xwindow_id (plugin->xoverlay, window);
+}
+
+
+static void
+mutate_playbin (RBPlayer *player, GstElement *playbin, RBVisualizerPlugin *plugin)
+{
+	GstElement *current_vis_plugin;
+	GstElement *current_video_sink;
+	GstBus *bus;
+
+	if (playbin == plugin->playbin)
+		return;
+
+	rb_debug ("mutating playbin");
+
+	/* check no one has already set the playbin properties we're interested in */
+	g_object_get (G_OBJECT (playbin),
+		      "vis-plugin", &current_vis_plugin,
+		      "video-sink", &current_video_sink,
+		      NULL);
+
+	if ((current_vis_plugin != plugin->visualizer) || (current_video_sink != plugin->video_sink)) {
+		g_warning ("sink and/or vis plugin already set on playbin");
+		if (current_vis_plugin)
+			g_object_unref (current_vis_plugin);
+		if (current_video_sink)
+			g_object_unref (current_video_sink);
+		return;
+	}
+
+	/* detach from old playbin (this should never really happen) */
+	if (plugin->playbin) {
+		if (plugin->bus_sync_id) {
+			g_signal_handler_disconnect (plugin->playbin, plugin->bus_sync_id);
+			plugin->bus_sync_id = 0;
+		}
+
+		g_object_unref (plugin->playbin);
+	}
+
+	/* attach to new playbin */
+	plugin->playbin = g_object_ref (playbin);
+	bus = gst_element_get_bus (playbin);
+	gst_bus_set_sync_handler (bus,
+				  gst_bus_sync_signal_handler,
+				  plugin);
+	plugin->bus_sync_id = g_signal_connect (bus,
+						"sync-message::element",
+						G_CALLBACK (bus_sync_message_cb),
+						plugin);
+
+	plugin->video_sink = gst_element_factory_make ("gconfvideosink", "videosink");
+	gst_element_set_state (plugin->video_sink, GST_STATE_READY);
+	find_xoverlay (plugin);
+	g_object_set (G_OBJECT (playbin), "video-sink", plugin->video_sink, NULL);
+
+	update_visualizer (plugin, NULL, -1);
+}
+
+static void
+playbin_notify_cb (GObject *object, GParamSpec *arg, RBVisualizerPlugin *pi)
+{
+	GstElement *playbin;
+
+	g_object_get (object, "playbin", &playbin, NULL);
+	if (playbin) {
+		mutate_playbin (RB_PLAYER (object), playbin, pi);
+		g_object_unref (playbin);
+	}
+}
+
+/* stolen directly from totem */
+
+static gboolean
+totem_display_is_local (void)
+{
+	const char *name, *work;
+	int display, screen;
+	gboolean has_hostname;
+
+	name = gdk_display_get_name (gdk_display_get_default ());
+	if (name == NULL)
+		return TRUE;
+
+	work = strstr (name, ":");
+	if (work == NULL)
+		return TRUE;
+
+	has_hostname = (work - name) > 0;
+
+	/* Get to the character after the colon */
+	work++;
+	if (work == NULL)
+		return TRUE;
+
+	if (sscanf (work, "%d.%d", &display, &screen) != 2)
+		return TRUE;
+
+	if (has_hostname == FALSE)
+		return TRUE;
+
+	if (display < 10)
+		return TRUE;
+
+	return FALSE;
+}
+
+static void
+enable_visualization (RBVisualizerPlugin *pi)
+{
+	rb_debug ("enabling visualization");
+
+	pi->active = TRUE;
+	gtk_widget_show_all (pi->vis_box);
+
+	/* these don't do anything (yet): */
+	gtk_widget_hide (pi->fullscreen_button);
+	gtk_widget_hide (pi->leave_fullscreen_button);
+
+	/* XXX need to make this hide the source header too */
+	rb_shell_notebook_set_page (pi->shell, pi->vis_box);
+}
+
+static gboolean
+disable_visualization (RBVisualizerPlugin *pi)
+{
+	rb_debug ("disabling visualization");
+
+	rb_shell_notebook_set_page (pi->shell, NULL);
+	gtk_widget_hide_all (pi->vis_box);
+	pi->active = FALSE;
+	return FALSE;
+}
+
+static void
+rb_visualizer_plugin_cmd_toggle (GtkAction *action, RBVisualizerPlugin *pi)
+{
+	gboolean enabled;
+	rb_debug ("visualization toggled");
+
+	enabled = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+	if (enabled) {
+		/* if on a remote display, ask for confirmation first */
+		if (totem_display_is_local () == FALSE) {
+			/* XXX etc. */
+		}
+		/* if playing something, enable visualization now, otherwise,
+		 * wait until we start playing.
+		 */
+		if (rb_player_opened (pi->player)) {
+			enable_visualization (pi);
+			update_visualizer (pi, NULL, -1);
+		} else {
+			pi->enable_deferred = TRUE;
+		}
+	} else {
+		disable_visualization (pi);
+		update_visualizer (pi, NULL, -1);
+	}
+}
+
+static void
+rb_visualizer_plugin_source_selected_cb (GObject *shell,
+					 GParamSpec *arg,
+					 RBVisualizerPlugin *plugin)
+{
+	GtkAction *action;
+
+	/* disable visualization */
+	disable_visualization (plugin);
+	action = gtk_action_group_get_action (plugin->action_group, "ToggleVisualizer");
+	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), FALSE);
+	update_visualizer (plugin, NULL, -1);
+}
+
+static void
+rb_visualizer_plugin_realize_cb (GObject *wis_widget,
+				 GParamSpec *pspec,
+				 RBVisualizerPlugin *plugin)
+{
+	rb_debug ("output window realized or exposed or something");
+	/* if visualization is actually disabled, just hide the window again */
+	if (plugin->active == FALSE) {
+		g_idle_add ((GSourceFunc)disable_visualization, plugin);
+	}
+}
+
+static void
+rb_visualizer_plugin_song_change_cb (RBShellPlayer *player,
+				     RhythmDBEntry *entry,
+				     RBVisualizerPlugin *plugin)
+{
+	if (entry) {
+		if (plugin->enable_deferred) {
+			enable_visualization (plugin);
+			update_visualizer (plugin, NULL, -1);
+			plugin->enable_deferred = FALSE;
+		}
+	} else {
+		/* disable, and re-enable when we start playing something */
+		disable_visualization (plugin);
+		update_visualizer (plugin, NULL, -1);
+		plugin->enable_deferred = TRUE;
+	}
+}
+
+static void
+impl_activate (RBPlugin *plugin,
+	       RBShell *shell)
+{
+	RBShellPlayer *shell_player;
+	gboolean connected = FALSE;
+	RBVisualizerPlugin *pi = RB_VISUALIZER_PLUGIN (plugin);
+	GtkUIManager *uim = NULL;
+	GtkAction *action;
+	int quality;
+	char *ui_file;
+
+	rb_fake_vis_get_type ();
+
+	/* find the player backend and connect to its pipeline mutation signal */
+
+	g_object_get (G_OBJECT (shell), "shell-player", &shell_player, NULL);
+	if (!shell_player)
+		return;
+
+	g_object_get (G_OBJECT (shell_player), "player", &pi->player, NULL);
+	if (pi->player && g_object_class_find_property (G_OBJECT_GET_CLASS (pi->player), "playbin")) {
+		GstElement *playbin;
+
+		g_signal_connect_object (pi->player,
+					 "notify::playbin",
+					 (GCallback) playbin_notify_cb,
+					 plugin,
+					 0);
+
+		g_object_get (G_OBJECT (pi->player), "playbin", &playbin, NULL);
+		if (playbin) {
+			mutate_playbin (pi->player, playbin, pi);
+		}
+
+		connected = TRUE;
+	} else {
+		g_warning ("no player backend exists or wrong type?");
+	}
+
+	if (!connected) {
+		g_object_unref (shell_player);
+		return;
+	}
+
+	rb_debug ("connected to playbin mutation signal");
+
+	/* create action group */
+	pi->action_group = gtk_action_group_new ("VisualizerActions");
+	gtk_action_group_set_translation_domain (pi->action_group, GETTEXT_PACKAGE);
+	gtk_action_group_add_toggle_actions (pi->action_group,
+					     rb_visualizer_plugin_toggle_actions,
+					     G_N_ELEMENTS (rb_visualizer_plugin_toggle_actions),
+					     pi);
+
+	g_object_get (G_OBJECT (shell), "ui-manager", &uim, NULL);
+	gtk_ui_manager_insert_action_group (uim, pi->action_group, 0);
+
+	ui_file = rb_plugin_find_file (plugin, "visualizer-ui.xml");
+	pi->merge_id = gtk_ui_manager_add_ui_from_file (uim,
+							ui_file,
+							NULL);
+	g_free (ui_file);
+
+	g_object_unref (uim);
+
+	pi->shell = shell;
+
+
+	/* create vis widget */
+
+	pi->vis_box = gtk_vbox_new (FALSE, 0);
+	g_object_ref (pi->vis_box);
+
+	quality = eel_gconf_get_integer (CONF_VIS_QUALITY);
+	pi->vis_widget = GTK_WIDGET (g_object_new (RB_TYPE_VIS_WIDGET,
+						   "width", vis_quality[quality].width,
+						   "height", vis_quality[quality].height,
+						   NULL));
+	g_object_ref (pi->vis_widget);
+
+	/* hopefully not needed */
+	g_signal_connect_object (G_OBJECT (pi->vis_widget),
+				 "notify::window-xid",
+				 G_CALLBACK (rb_visualizer_plugin_realize_cb),
+				 pi, G_CONNECT_AFTER);
+
+	gtk_box_pack_start (GTK_BOX (pi->vis_box), pi->vis_widget, TRUE, TRUE, 6);
+
+	create_controls (pi);
+	gtk_box_pack_end (GTK_BOX (pi->vis_box), pi->control_widget, FALSE, FALSE, 6);
+
+	rb_shell_add_widget (pi->shell, pi->vis_box, RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK);
+
+	/* fake output window */
+	{
+		GdkWindowAttr attributes;
+		gint attributes_mask;
+		attributes.window_type = GDK_WINDOW_CHILD;
+		attributes.x = 0;
+		attributes.y = 0;
+		attributes.width = 60;
+		attributes.height = 60;
+		attributes.wclass = GDK_INPUT_OUTPUT;
+		attributes.event_mask = gtk_widget_get_events (pi->vis_widget);
+		attributes.event_mask |= GDK_EXPOSURE_MASK;
+		attributes_mask = GDK_WA_X | GDK_WA_Y;
+		pi->fake_window = gdk_window_new (NULL, &attributes, attributes_mask);
+	}
+
+	action = gtk_action_group_get_action (pi->action_group, "ToggleVisualizer");
+	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), FALSE);
+
+	/* pretend to enable visualization just so the window gets realized */
+	/*enable_visualization (pi);
+	pi->active = FALSE; */
+
+	g_signal_connect_object (G_OBJECT (pi->shell),
+				 "notify::selected-source",
+				 G_CALLBACK (rb_visualizer_plugin_source_selected_cb),
+				 pi, 0);
+	g_signal_connect_object (G_OBJECT (shell_player),
+				 "playing-song-changed",
+				 G_CALLBACK (rb_visualizer_plugin_song_change_cb),
+				 pi, 0);
+
+	g_object_unref (shell_player);
+}
+
+static void
+impl_deactivate	(RBPlugin *plugin,
+		 RBShell *shell)
+{
+	RBVisualizerPlugin *pi = RB_VISUALIZER_PLUGIN (plugin);
+	GtkUIManager *uim;
+
+	disable_visualization (pi);
+	update_visualizer (pi, NULL, -1);
+
+	/* remove ui */
+	g_object_get (G_OBJECT (shell), "ui-manager", &uim, NULL);
+
+	gtk_ui_manager_remove_ui (uim, pi->merge_id);
+	gtk_ui_manager_remove_action_group (uim, pi->action_group);
+
+	g_object_unref (uim);
+}
+
+/* visualization configuration stuff */
+
+static gboolean
+vis_plugin_filter (GstPluginFeature *feature, gpointer data)
+{
+	GstElementFactory *f;
+
+	/* skip our fake visualizer */
+	if (strcmp (gst_plugin_feature_get_name (feature), "rbfakevis") == 0)
+		return FALSE;
+
+	if  (!GST_IS_ELEMENT_FACTORY (feature))
+		return FALSE;
+	f = GST_ELEMENT_FACTORY (feature);
+
+	return (g_strrstr (gst_element_factory_get_klass (f), "Visualization") != NULL);
+}
+
+static GList *
+get_vis_plugin_list (RBVisualizerPlugin *pi)
+{
+	GList *features;
+	GList *plugin_info = NULL;
+	GList *t;
+
+	if (pi->vis_plugin_list)
+		return pi->vis_plugin_list;
+
+	rb_debug ("building vis plugin list");
+	features = gst_registry_feature_filter (gst_registry_get_default (),
+						vis_plugin_filter,
+						FALSE, NULL);
+	for (t = features; t != NULL; t = t->next) {
+		GstElementFactory *f;
+		VisPluginInfo *plugin;
+
+		f = GST_ELEMENT_FACTORY (t->data);
+
+		plugin = g_new0 (VisPluginInfo, 1);
+		plugin->displayname = gst_element_factory_get_longname (f);
+		plugin->name = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (f));
+		rb_debug ("adding visualizer element: %s (%s)", plugin->displayname, plugin->name);
+
+		plugin_info = g_list_prepend (plugin_info, plugin);
+	}
+	plugin_info = g_list_reverse (plugin_info);
+
+	pi->vis_plugin_list = plugin_info;
+	return plugin_info;
+}
+
+static void
+element_list_cell_data (GtkCellLayout *layout,
+			GtkCellRenderer *cell,
+			GtkTreeModel *model,
+			GtkTreeIter *iter,
+			gpointer whatever)
+{
+	VisPluginInfo *vpi;
+
+	gtk_tree_model_get (model, iter, 0, &vpi, -1);
+	g_object_set (G_OBJECT (cell), "text", vpi->displayname, NULL);
+}
+
+
+static void
+quality_list_cell_data (GtkCellLayout *layout,
+			GtkCellRenderer *cell,
+			GtkTreeModel *model,
+			GtkTreeIter *iter,
+			gpointer whatever)
+{
+	VisualizerQuality *quality;
+
+	gtk_tree_model_get (model, iter, 0, &quality, -1);
+	g_object_set (G_OBJECT (cell), "text", gettext (quality->name), NULL);
+}
+
+static void
+populate_combo_boxes (RBVisualizerPlugin *pi)
+{
+	int i;
+	int active;
+	GList *l;
+	GtkListStore *model;
+	GtkCellRenderer *renderer;
+	int quality;
+	char *element;
+
+	/* visualizer element selection */
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (pi->element_combo), renderer, TRUE);
+	gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (pi->element_combo),
+					    renderer,
+					    (GtkCellLayoutDataFunc) element_list_cell_data,
+					    NULL, NULL);
+
+	model = gtk_list_store_new (1, G_TYPE_POINTER);
+	l = get_vis_plugin_list (pi);
+	active = 0;
+	element = eel_gconf_get_string (CONF_VIS_ELEMENT);
+	if (element == NULL)
+		element = g_strdup (DEFAULT_VIS_ELEMENT);
+
+	for (; l != NULL; l = l->next) {
+		GtkTreeIter iter;
+
+		VisPluginInfo *vpi = (VisPluginInfo *)l->data;
+		gtk_list_store_append (model, &iter);
+		gtk_list_store_set (model, &iter, 0, vpi, -1);
+
+		if (strcmp (element, vpi->name) == 0) {
+			active = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL) - 1;
+		}
+	}
+	gtk_combo_box_set_model (GTK_COMBO_BOX (pi->element_combo), GTK_TREE_MODEL (model));
+	gtk_combo_box_set_active (GTK_COMBO_BOX (pi->element_combo), active);
+	g_free (element);
+
+	/* quality selection */
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (pi->quality_combo), renderer, TRUE);
+	gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (pi->quality_combo),
+					    renderer,
+					    (GtkCellLayoutDataFunc) quality_list_cell_data,
+					    NULL, NULL);
+
+	model = gtk_list_store_new (1, G_TYPE_POINTER);
+	for (i = 0; i < G_N_ELEMENTS (vis_quality); i++) {
+		GtkTreeIter iter;
+
+		gtk_list_store_append (model, &iter);
+		gtk_list_store_set (model, &iter, 0, &vis_quality[i], -1);
+	}
+	gtk_combo_box_set_model (GTK_COMBO_BOX (pi->quality_combo), GTK_TREE_MODEL (model));
+
+	quality = eel_gconf_get_integer (CONF_VIS_QUALITY);
+	if (quality < 0 || quality >= G_N_ELEMENTS (vis_quality))
+		quality = DEFAULT_VIS_QUALITY;
+	gtk_combo_box_set_active (GTK_COMBO_BOX (pi->quality_combo), quality);
+}
+
+static void
+element_combo_changed_cb (GtkComboBox *combo, RBVisualizerPlugin *pi)
+{
+	int index;
+	VisPluginInfo *vpi;
+	char *old_element;
+	GList *plugins = get_vis_plugin_list (pi);
+
+	index = gtk_combo_box_get_active (combo);
+	vpi = g_list_nth_data (plugins, index);
+	if (vpi == NULL) {
+		rb_debug ("unknown vis element selected?");
+		return;
+	}
+
+	old_element = eel_gconf_get_string (CONF_VIS_ELEMENT);
+	if (old_element == NULL || strcmp (old_element, vpi->name)) {
+		rb_debug ("vis element changed: %s", vpi->name);
+		eel_gconf_set_string (CONF_VIS_ELEMENT, vpi->name);
+
+		update_visualizer (pi, vpi->name, -1);
+	}
+	g_free (old_element);
+}
+
+static void
+quality_combo_changed_cb (GtkComboBox *combo, RBVisualizerPlugin *pi)
+{
+	int index;
+
+	index = gtk_combo_box_get_active (combo);
+	if (index < 0 || index > G_N_ELEMENTS (vis_quality)) {
+		rb_debug ("unknown vis quality selected?");
+		index = DEFAULT_VIS_QUALITY;
+	}
+
+	eel_gconf_set_integer (CONF_VIS_QUALITY, index);
+
+	update_visualizer (pi, NULL, index);
+}
+
+static void
+create_controls (RBVisualizerPlugin *plugin)
+{
+	GladeXML *xml;
+
+	/* load glade stuff */
+	xml = glade_xml_new (rb_plugin_find_file (RB_PLUGIN (plugin), "visualizer-controls.glade"),
+			     "visualizer_controls",
+			     NULL);
+	plugin->control_widget = glade_xml_get_widget (xml, "visualizer_controls");
+	plugin->element_combo = glade_xml_get_widget (xml, "element");
+	plugin->quality_combo = glade_xml_get_widget (xml, "quality");
+
+	populate_combo_boxes (plugin);
+
+	g_signal_connect_object (G_OBJECT (plugin->element_combo), "changed",
+				 G_CALLBACK (element_combo_changed_cb), plugin,
+				 0);
+	g_signal_connect_object (G_OBJECT (plugin->quality_combo), "changed",
+				 G_CALLBACK (quality_combo_changed_cb), plugin,
+				 0);
+
+	/* not yet: */
+	plugin->fullscreen_button = glade_xml_get_widget (xml, "fullscreen");
+	plugin->leave_fullscreen_button = glade_xml_get_widget (xml, "leave_fullscreen");
+
+	g_object_unref (xml);
+}
+
+
+static void
+rb_visualizer_plugin_class_init (RBVisualizerPluginClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBPluginClass *plugin_class = RB_PLUGIN_CLASS (klass);
+
+	object_class->dispose = rb_visualizer_plugin_dispose;
+	object_class->finalize = rb_visualizer_plugin_finalize;
+
+	plugin_class->activate = impl_activate;
+	plugin_class->deactivate = impl_deactivate;
+}
+
diff -rN -u -p old-v2/plugins/visualizer/rb-vis-widget.c new-v2/plugins/visualizer/rb-vis-widget.c
--- old-v2/plugins/visualizer/rb-vis-widget.c	1970-01-01 10:00:00.000000000 +1000
+++ new-v2/plugins/visualizer/rb-vis-widget.c	2006-10-19 20:32:07.000000000 +1000
@@ -0,0 +1,311 @@
+/*
+ * rb-vis-widget.c
+ *
+ * Copyright (C) 2006  Jonathan Matthew
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#include "rb-vis-widget.h"
+#include "rb-debug.h"
+
+enum
+{
+	PROP_0,
+	PROP_WINDOW_XID,
+	PROP_WIDTH,
+	PROP_HEIGHT
+};
+
+
+G_DEFINE_TYPE(RBVisWidget, rb_vis_widget, GTK_TYPE_WIDGET)
+
+static void
+rb_vis_widget_init (RBVisWidget *rbvw)
+{
+	GTK_WIDGET_SET_FLAGS (GTK_WIDGET (rbvw), GTK_CAN_FOCUS);
+	GTK_WIDGET_SET_FLAGS (GTK_WIDGET (rbvw), GTK_NO_WINDOW);
+	GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (rbvw), GTK_DOUBLE_BUFFERED);
+}
+
+static void
+rb_vis_widget_realize (GtkWidget *widget)
+{
+	GdkWindowAttr attributes;
+	gint attributes_mask;
+
+	rb_debug ("realizing container window");
+
+	attributes.window_type = GDK_WINDOW_CHILD;
+	attributes.x = widget->allocation.x;
+	attributes.y = widget->allocation.y;
+	attributes.width = widget->allocation.width;
+	attributes.height = widget->allocation.height;
+	attributes.wclass = GDK_INPUT_OUTPUT;
+	attributes.visual = gtk_widget_get_visual (widget);
+	attributes.colormap = gtk_widget_get_colormap (widget);
+	attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
+	attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+
+	widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
+					 &attributes, attributes_mask);
+	gdk_window_set_user_data (widget->window, widget);
+	gdk_window_show (widget->window);
+
+	widget->style = gtk_style_attach (widget->style, widget->window);
+	gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
+
+	GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+	rb_vis_widget_create_window (RB_VIS_WIDGET (widget));
+}
+
+static void
+rb_vis_widget_size_request (GtkWidget *widget,
+			    GtkRequisition *requisition)
+{
+	RBVisWidget *rbvw = RB_VIS_WIDGET (widget);
+	rb_debug ("handling size request");
+	requisition->width = rbvw->width;
+	requisition->height = rbvw->height;
+}
+
+static void
+rb_vis_widget_size_allocate (GtkWidget *widget,
+			     GtkAllocation *allocation)
+{
+	RBVisWidget *rbvw = RB_VIS_WIDGET (widget);
+	widget->allocation = *allocation;
+
+	if (!GTK_WIDGET_REALIZED (widget))
+		return;
+
+	rb_debug ("handling size allocate event");
+	gdk_window_move_resize (widget->window,
+				allocation->x, allocation->y,
+				allocation->width, allocation->height);
+
+	if (rbvw->video_window) {
+		gdk_window_move_resize (rbvw->video_window,
+					0, 0, allocation->width, allocation->height);
+	}
+
+	if (rbvw->width != allocation->width) {
+		rbvw->width = allocation->width;
+		g_object_notify (G_OBJECT (rbvw), "width");
+	}
+	if (rbvw->height != allocation->height) {
+		rbvw->height = allocation->height;
+		g_object_notify (G_OBJECT (rbvw), "height");
+	}
+}
+
+static gboolean
+rb_vis_widget_expose_event (GtkWidget *widget,
+			    GdkEventExpose *event)
+{
+	RBVisWidget *rbvw = RB_VIS_WIDGET (widget);
+
+	if (rbvw->video_window == NULL)
+		return TRUE;
+
+	if (rbvw->window_xid != GDK_WINDOW_XWINDOW (rbvw->video_window)) {
+		rbvw->window_xid = GDK_WINDOW_XWINDOW (rbvw->video_window);
+		rb_debug ("got new window ID %lu", rbvw->window_xid);
+		g_object_notify (G_OBJECT (rbvw), "window-xid");
+	}
+
+	if (event && event->count > 0) {
+		rb_debug ("ignoring expose event?");
+		return TRUE;
+	}
+
+	if (rbvw->draw_in_expose) {
+		rb_debug ("handling expose event");
+		gdk_draw_rectangle (rbvw->video_window, widget->style->black_gc, TRUE, 0, 0,
+				    widget->allocation.width, widget->allocation.height);
+	}
+
+	return TRUE;
+}
+
+static void
+rb_vis_widget_hide (GtkWidget *widget)
+{
+	RBVisWidget *rbvw = RB_VIS_WIDGET (widget);
+
+	if (rbvw->video_window) {
+		rb_debug ("hiding video output window");
+		gdk_window_hide (rbvw->video_window);
+	}
+	gdk_window_hide (GTK_WIDGET (rbvw)->window);
+
+	GTK_WIDGET_CLASS (rb_vis_widget_parent_class)->hide (widget);
+}
+
+static void
+rb_vis_widget_show (GtkWidget *widget)
+{
+	RBVisWidget *rbvw = RB_VIS_WIDGET (widget);
+
+	if (rbvw->video_window) {
+		rb_debug ("showing video output window");
+		gdk_window_show (rbvw->video_window);
+	}
+	if ((GTK_WIDGET (rbvw)->window)) {
+		rb_debug ("showing container window");
+		gdk_window_show (GTK_WIDGET (rbvw)->window);
+	} else {
+		rb_debug ("got show event before realized..");
+	}
+
+	GTK_WIDGET_CLASS (rb_vis_widget_parent_class)->show (widget);
+}
+
+void
+rb_vis_widget_create_window (RBVisWidget *rbvw)
+{
+	GtkWidget *widget = GTK_WIDGET (rbvw);
+	GdkWindowAttr attributes;
+	gint attributes_mask;
+
+	if (rbvw->video_window)
+		return;
+
+	rb_debug ("creating video output window");
+
+	/* video window */
+	attributes.window_type = GDK_WINDOW_CHILD;
+	attributes.x = 0;
+	attributes.y = 0;
+	attributes.width = widget->allocation.width;
+	attributes.height = widget->allocation.height;
+	attributes.wclass = GDK_INPUT_OUTPUT;
+	attributes.event_mask = gtk_widget_get_events (widget);
+	attributes.event_mask |= GDK_EXPOSURE_MASK;
+	attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+	rbvw->video_window = gdk_window_new (widget->window, &attributes, attributes_mask);
+	gdk_window_set_user_data (rbvw->video_window, widget);
+	gdk_window_show (rbvw->video_window);
+
+	rbvw->window_xid = GDK_WINDOW_XWINDOW (rbvw->video_window);
+	rb_debug ("got new window ID %lu", rbvw->window_xid);
+	g_object_notify (G_OBJECT (rbvw), "window-xid");
+}
+
+void
+rb_vis_widget_resize (RBVisWidget *rbvw, int width, int height)
+{
+	rbvw->width = width;
+	rbvw->height = height;
+
+	if (GTK_WIDGET_REALIZED (rbvw))
+		gtk_widget_set_size_request (GTK_WIDGET (rbvw), width, height);
+}
+
+static void
+rb_vis_widget_set_property (GObject *object,
+			    guint prop_id,
+			    const GValue *value,
+			    GParamSpec *pspec)
+{
+	RBVisWidget *rbvw = RB_VIS_WIDGET (object);
+	switch (prop_id) {
+	case PROP_WIDTH:
+		rbvw->width = g_value_get_uint (value);
+		break;
+	case PROP_HEIGHT:
+		rbvw->height = g_value_get_uint (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+rb_vis_widget_get_property (GObject *object,
+			    guint prop_id,
+			    GValue *value,
+			    GParamSpec *pspec)
+{
+	RBVisWidget *rbvw = RB_VIS_WIDGET (object);
+	switch (prop_id) {
+	case PROP_WIDTH:
+		g_value_set_uint (value, rbvw->width);
+		break;
+	case PROP_HEIGHT:
+		g_value_set_uint (value, rbvw->height);
+		break;
+	case PROP_WINDOW_XID:
+		g_value_set_ulong (value, rbvw->window_xid);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+rb_vis_widget_class_init (RBVisWidgetClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+	widget_class->size_request = rb_vis_widget_size_request;
+	widget_class->size_allocate = rb_vis_widget_size_allocate;
+	widget_class->realize = rb_vis_widget_realize;
+	widget_class->expose_event = rb_vis_widget_expose_event;
+	widget_class->hide = rb_vis_widget_hide;
+	widget_class->show = rb_vis_widget_show;
+
+	object_class->set_property = rb_vis_widget_set_property;
+	object_class->get_property = rb_vis_widget_get_property;
+
+	g_object_class_install_property (object_class,
+					 PROP_WINDOW_XID,
+					 g_param_spec_ulong ("window-xid",
+						 	     "window XID",
+							     "XID for the video window",
+							     0, G_MAXULONG, 0,
+							     G_PARAM_READABLE));
+	g_object_class_install_property (object_class,
+					 PROP_WIDTH,
+					 g_param_spec_uint ("width",
+						 	    "width",
+							    "width of the video window",
+							    0, G_MAXUINT, 0,
+							    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_HEIGHT,
+					 g_param_spec_uint ("height",
+						 	    "height",
+							    "height of the video window",
+							    0, G_MAXUINT, 0,
+							    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
diff -rN -u -p old-v2/plugins/visualizer/rb-vis-widget.h new-v2/plugins/visualizer/rb-vis-widget.h
--- old-v2/plugins/visualizer/rb-vis-widget.h	1970-01-01 10:00:00.000000000 +1000
+++ new-v2/plugins/visualizer/rb-vis-widget.h	2006-10-19 20:32:07.000000000 +1000
@@ -0,0 +1,55 @@
+/*
+ * rb-vis-widget.h
+ *
+ * Copyright (C) 2006  Jonathan Matthew
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ */
+
+#ifndef __RB_VIS_WIDGET_H__
+#define __RB_VIS_WIDGET_H__
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_VIS_WIDGET		(rb_vis_widget_get_type ())
+#define RB_VIS_WIDGET(obj)		(GTK_CHECK_CAST ((obj), RB_TYPE_VIS_WIDGET, RBVisWidget))
+#define RB_VIS_WIDGET_CLASS(klass)	(GTK_CHECK_CLASS_CAST ((klass), RB_TYPE_VIS_WIDGET, RBVisWidgetClass))
+#define RB_IS_VIS_WIDGET(obj)		(GTK_CHECK_TYPE ((obj), RB_TYPE_VIS_WIDGET))
+#define RB_IS_VIS_WIDGET_CLASS(klass)	(GTK_CHECK_CLASS_TYPE ((klass), RB_TYPE_VIS_WIDGET))
+#define RB_VIS_WIDGET_GET_CLASS(obj)	(GTK_CHECK_GET_CLASS ((obj), RB_TYPE_VIS_WIDGET, RBVisWidgetClass))
+
+typedef struct _RBVisWidget
+{
+	GtkWidget parent;
+	GdkWindow *video_window;
+	int width;
+	int height;
+	unsigned long window_xid;
+	gboolean draw_in_expose;
+} RBVisWidget;
+
+typedef struct _RBVisWidgetClass
+{
+	GtkWidgetClass parent_class;
+} RBVisWidgetClass;
+
+GType	rb_vis_widget_get_type (void);
+void	rb_vis_widget_create_window (RBVisWidget *rbvw);
+void	rb_vis_widget_resize (RBVisWidget *rbvw, int width, int height);
+
+G_END_DECLS
+
+#endif /* __RB_VIS_WIDGET_H__ */
+
diff -rN -u -p old-v2/plugins/visualizer/visualizer-controls.glade new-v2/plugins/visualizer/visualizer-controls.glade
--- old-v2/plugins/visualizer/visualizer-controls.glade	1970-01-01 10:00:00.000000000 +1000
+++ new-v2/plugins/visualizer/visualizer-controls.glade	2006-10-19 20:32:07.000000000 +1000
@@ -0,0 +1,264 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+<requires lib="gnome"/>
+
+<widget class="GtkWindow" id="window1">
+  <property name="visible">True</property>
+  <property name="title" translatable="yes">window1</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">False</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+  <property name="urgency_hint">False</property>
+
+  <child>
+    <widget class="GtkHBox" id="visualizer_controls">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">12</property>
+
+      <child>
+	<widget class="GtkLabel" id="label1">
+	  <property name="visible">True</property>
+	  <property name="label" translatable="yes">Visualization:</property>
+	  <property name="use_underline">False</property>
+	  <property name="use_markup">False</property>
+	  <property name="justify">GTK_JUSTIFY_LEFT</property>
+	  <property name="wrap">False</property>
+	  <property name="selectable">False</property>
+	  <property name="xalign">0.5</property>
+	  <property name="yalign">0.5</property>
+	  <property name="xpad">0</property>
+	  <property name="ypad">0</property>
+	  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+	  <property name="width_chars">-1</property>
+	  <property name="single_line_mode">False</property>
+	  <property name="angle">0</property>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">False</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkComboBox" id="element">
+	  <property name="visible">True</property>
+	  <property name="add_tearoffs">False</property>
+	  <property name="focus_on_click">True</property>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">True</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkLabel" id="label2">
+	  <property name="visible">True</property>
+	  <property name="label" translatable="yes">Quality:</property>
+	  <property name="use_underline">False</property>
+	  <property name="use_markup">False</property>
+	  <property name="justify">GTK_JUSTIFY_LEFT</property>
+	  <property name="wrap">False</property>
+	  <property name="selectable">False</property>
+	  <property name="xalign">0.5</property>
+	  <property name="yalign">0.5</property>
+	  <property name="xpad">0</property>
+	  <property name="ypad">0</property>
+	  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+	  <property name="width_chars">-1</property>
+	  <property name="single_line_mode">False</property>
+	  <property name="angle">0</property>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">False</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkComboBox" id="quality">
+	  <property name="visible">True</property>
+	  <property name="add_tearoffs">False</property>
+	  <property name="focus_on_click">True</property>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">True</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkButton" id="fullscreen">
+	  <property name="visible">True</property>
+	  <property name="can_focus">True</property>
+	  <property name="relief">GTK_RELIEF_NORMAL</property>
+	  <property name="focus_on_click">True</property>
+
+	  <child>
+	    <widget class="GtkAlignment" id="alignment1">
+	      <property name="visible">True</property>
+	      <property name="xalign">0.5</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xscale">0</property>
+	      <property name="yscale">0</property>
+	      <property name="top_padding">0</property>
+	      <property name="bottom_padding">0</property>
+	      <property name="left_padding">0</property>
+	      <property name="right_padding">0</property>
+
+	      <child>
+		<widget class="GtkHBox" id="hbox2">
+		  <property name="visible">True</property>
+		  <property name="homogeneous">False</property>
+		  <property name="spacing">2</property>
+
+		  <child>
+		    <widget class="GtkImage" id="image1">
+		      <property name="visible">True</property>
+		      <property name="stock">gtk-fullscreen</property>
+		      <property name="icon_size">4</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		    </widget>
+		    <packing>
+		      <property name="padding">0</property>
+		      <property name="expand">False</property>
+		      <property name="fill">False</property>
+		    </packing>
+		  </child>
+
+		  <child>
+		    <widget class="GtkLabel" id="label3">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">Fullscreen</property>
+		      <property name="use_underline">True</property>
+		      <property name="use_markup">False</property>
+		      <property name="justify">GTK_JUSTIFY_LEFT</property>
+		      <property name="wrap">False</property>
+		      <property name="selectable">False</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		      <property name="width_chars">-1</property>
+		      <property name="single_line_mode">False</property>
+		      <property name="angle">0</property>
+		    </widget>
+		    <packing>
+		      <property name="padding">0</property>
+		      <property name="expand">False</property>
+		      <property name="fill">False</property>
+		    </packing>
+		  </child>
+		</widget>
+	      </child>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">False</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkButton" id="leave_fullscreen">
+	  <property name="visible">True</property>
+	  <property name="can_focus">True</property>
+	  <property name="relief">GTK_RELIEF_NORMAL</property>
+	  <property name="focus_on_click">True</property>
+
+	  <child>
+	    <widget class="GtkAlignment" id="alignment2">
+	      <property name="visible">True</property>
+	      <property name="xalign">0.5</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xscale">0</property>
+	      <property name="yscale">0</property>
+	      <property name="top_padding">0</property>
+	      <property name="bottom_padding">0</property>
+	      <property name="left_padding">0</property>
+	      <property name="right_padding">0</property>
+
+	      <child>
+		<widget class="GtkHBox" id="hbox3">
+		  <property name="visible">True</property>
+		  <property name="homogeneous">False</property>
+		  <property name="spacing">2</property>
+
+		  <child>
+		    <widget class="GtkImage" id="image2">
+		      <property name="visible">True</property>
+		      <property name="stock">gtk-leave-fullscreen</property>
+		      <property name="icon_size">4</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		    </widget>
+		    <packing>
+		      <property name="padding">0</property>
+		      <property name="expand">False</property>
+		      <property name="fill">False</property>
+		    </packing>
+		  </child>
+
+		  <child>
+		    <widget class="GtkLabel" id="label4">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">Leave Fullscreen</property>
+		      <property name="use_underline">True</property>
+		      <property name="use_markup">False</property>
+		      <property name="justify">GTK_JUSTIFY_LEFT</property>
+		      <property name="wrap">False</property>
+		      <property name="selectable">False</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		      <property name="width_chars">-1</property>
+		      <property name="single_line_mode">False</property>
+		      <property name="angle">0</property>
+		    </widget>
+		    <packing>
+		      <property name="padding">0</property>
+		      <property name="expand">False</property>
+		      <property name="fill">False</property>
+		    </packing>
+		  </child>
+		</widget>
+	      </child>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+</glade-interface>
diff -rN -u -p old-v2/plugins/visualizer/visualizer.rb-plugin.desktop.in new-v2/plugins/visualizer/visualizer.rb-plugin.desktop.in
--- old-v2/plugins/visualizer/visualizer.rb-plugin.desktop.in	1970-01-01 10:00:00.000000000 +1000
+++ new-v2/plugins/visualizer/visualizer.rb-plugin.desktop.in	2006-10-19 20:32:07.000000000 +1000
@@ -0,0 +1,8 @@
+[RB Plugin]
+Module=visualizer
+IAge=1
+_Name=Visualization
+_Description=Displays visualizations
+Authors=Jonathan Matthew
+Copyright=Copyright © 2006 Jonathan Matthew
+Website=http://www.rhythmbox.org
diff -rN -u -p old-v2/plugins/visualizer/visualizer-ui.xml new-v2/plugins/visualizer/visualizer-ui.xml
--- old-v2/plugins/visualizer/visualizer-ui.xml	1970-01-01 10:00:00.000000000 +1000
+++ new-v2/plugins/visualizer/visualizer-ui.xml	2006-10-19 20:32:07.000000000 +1000
@@ -0,0 +1,13 @@
+<ui>
+  <popup name="VisualizerPopup">
+    <menuitem name="VisualizerPopupDisable" action="ToggleVisualizer"/>
+    <placeholder name="VisualizerSelectPlaceholder"/>
+  </popup>
+
+  <menubar name="MenuBar">
+    <menu name="ControlMenu" action="Control">
+      <menuitem name="ControlMenuVisualizerToggle" action="ToggleVisualizer"/>
+    </menu>
+  </menubar>
+</ui>
+
diff -rN -u -p old-v2/shell/rb-shell.c new-v2/shell/rb-shell.c
--- old-v2/shell/rb-shell.c	2006-10-19 20:32:06.000000000 +1000
+++ new-v2/shell/rb-shell.c	2006-10-19 20:32:06.000000000 +1000
@@ -1895,6 +1895,9 @@ rb_shell_playing_entry_changed_cb (RBShe
 {
 	char *notifytitle;
 
+	if (entry == NULL)
+		return;
+
 	/* Translators: by Artist from Album*/
 	notifytitle = g_strdup_printf (_("by %s from %s"),
 				       rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST),
@@ -3292,6 +3295,8 @@ rb_shell_get_box_for_ui_location (RBShel
 	case RB_SHELL_UI_LOCATION_MAIN_BOTTOM:
 		box = shell->priv->bottom_container;
 		break;
+	default:
+		break;
 	}
 
 	return box;
@@ -3302,21 +3307,57 @@ rb_shell_add_widget (RBShell *shell, Gtk
 {
 	GtkBox *box;
 
-	box = rb_shell_get_box_for_ui_location (shell, location);
-	g_return_if_fail (box != NULL);
+	switch (location) {
+	case RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK:
+		gtk_notebook_append_page (GTK_NOTEBOOK (shell->priv->notebook),
+					  widget,
+					  gtk_label_new (""));
+		break;
+	default:
+		box = rb_shell_get_box_for_ui_location (shell, location);
+		g_return_if_fail (box != NULL);
 
-	gtk_box_pack_start (box, widget, FALSE, TRUE, 0);
+		gtk_box_pack_start (box, widget, FALSE, TRUE, 0);
+		break;
+	}
 }
 
 void
 rb_shell_remove_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation location)
 {
 	GtkBox *box;
+	gint page_num;
+
+	switch (location) {
+	case RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK:
+		page_num = gtk_notebook_page_num (GTK_NOTEBOOK (shell->priv->notebook),
+						  widget);
+		g_return_if_fail (page_num != -1);
+		gtk_notebook_remove_page (GTK_NOTEBOOK (shell->priv->notebook),
+					  page_num);
+		break;
+	default:
+		box = rb_shell_get_box_for_ui_location (shell, location);
+		g_return_if_fail (box != NULL);
+
+		gtk_container_remove (GTK_CONTAINER (box), widget);
+		break;
+	}
+}
+
+void
+rb_shell_notebook_set_page (RBShell *shell, GtkWidget *widget)
+{
+	gint page = 0;
+
+	/* if no widget specified, use the selected source */
+	if (widget == NULL && shell->priv->selected_source)
+		widget = GTK_WIDGET (shell->priv->selected_source);
 
-	box = rb_shell_get_box_for_ui_location (shell, location);
-	g_return_if_fail (box != NULL);
+	if (widget)
+		page = gtk_notebook_page_num (GTK_NOTEBOOK (shell->priv->notebook), widget);
 
-	gtk_container_remove (GTK_CONTAINER (box), widget);
+	gtk_notebook_set_current_page (GTK_NOTEBOOK (shell->priv->notebook), page);
 }
 
 /* This should really be standard. */
@@ -3332,6 +3373,7 @@ rb_shell_ui_location_get_type (void)
 			ENUM_ENTRY (RB_SHELL_UI_LOCATION_SIDEBAR, "Sidebar"),
 			ENUM_ENTRY (RB_SHELL_UI_LOCATION_MAIN_TOP, "Main Top"),
 			ENUM_ENTRY (RB_SHELL_UI_LOCATION_MAIN_BOTTOM, "Main Bottom"),
+			ENUM_ENTRY (RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK, "Main Notebook"),
 			{ 0, 0, 0 }
 		};
 
diff -rN -u -p old-v2/shell/rb-shell.h new-v2/shell/rb-shell.h
--- old-v2/shell/rb-shell.h	2006-10-19 20:32:06.000000000 +1000
+++ new-v2/shell/rb-shell.h	2006-10-19 20:32:06.000000000 +1000
@@ -54,6 +54,7 @@ typedef enum
 	RB_SHELL_UI_LOCATION_SIDEBAR,
 	RB_SHELL_UI_LOCATION_MAIN_TOP,
 	RB_SHELL_UI_LOCATION_MAIN_BOTTOM,
+	RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK
 } RBShellUILocation;
 
 GType rb_shell_ui_location_get_type (void);
@@ -147,8 +148,9 @@ gboolean        rb_shell_get_party_mode 
 
 void rb_shell_append_source (RBShell *shell, RBSource *source, RBSource *parent);
 
-void rb_shell_add_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation location);
-void rb_shell_remove_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation location);
+void 		rb_shell_add_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation location);
+void 		rb_shell_remove_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation location);
+void		rb_shell_notebook_set_page (RBShell *shell, GtkWidget *widget);
 
 G_END_DECLS
 
diff -rN -u -p old-v2/shell/rb-shell-player.c new-v2/shell/rb-shell-player.c
--- old-v2/shell/rb-shell-player.c	2006-10-19 20:32:06.000000000 +1000
+++ new-v2/shell/rb-shell-player.c	2006-10-19 20:32:06.000000000 +1000
@@ -2589,6 +2589,12 @@ rb_shell_player_stop (RBShellPlayer *pla
 	}
 
 	rb_shell_player_sync_with_source (player);
+	g_signal_emit (G_OBJECT (player),
+		       rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
+		       NULL);
+	g_signal_emit (G_OBJECT (player),
+		       rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
+		       NULL);
 	g_object_notify (G_OBJECT (player), "playing");
 	rb_shell_player_sync_buttons (player);
 }

