[rhythmbox] add status icon plugin



commit 569acd84fedff43c820c7e1ea4bc99fa756f3c6e
Author: Jonathan Matthew <jonathan d14n org>
Date:   Mon May 18 12:14:38 2009 +1000

    add status icon plugin
    
    This plugin uses GtkStatusIcon when built with gtk 2.16 or newer
    (fixing bug 349280); can be disabled as required (fixing bug 317982);
    makes some aspects of the status icon behaviour configurable (fixing
    bugs 158168, 324114, 331019).
    
    Notification popups are now a bit more configurable: they can be
    disabled, only shown when the main window is hidden, or always shown.
    
    The status icon is a configurable too:  it can be always hidden, only
    shown while a notification popup is visible, always shown, or it can
    own the main window, providing minimize/close-to-tray.  The new
    implementation of close-to-tray uses the same way of hiding the window
    as everything else, fixing bug 551002.
    
    The dontreallyclose plugin is no longer needed, so it has been removed.
---
 configure.ac                                       |   10 +-
 data/rhythmbox.schemas                             |   86 +-
 plugins/Makefile.am                                |    2 +-
 plugins/dontreallyclose/Makefile.am                |   14 -
 plugins/dontreallyclose/dontreallyclose.py         |   50 -
 .../dontreallyclose/dontreallyclose.rb-plugin.in   |    9 -
 plugins/status-icon/Makefile.am                    |   76 ++
 plugins/status-icon/eggtrayicon.c                  |  559 ++++++++
 plugins/status-icon/eggtrayicon.h                  |   80 ++
 plugins/status-icon/rb-status-icon-plugin.c        | 1383 ++++++++++++++++++++
 plugins/status-icon/rb-status-icon-plugin.h        |   78 ++
 plugins/status-icon/rb-tray-icon-gtk.c             |  359 +++++
 plugins/status-icon/rb-tray-icon-gtk.h             |   85 ++
 plugins/status-icon/rb-tray-icon.c                 |  487 +++++++
 plugins/status-icon/rb-tray-icon.h                 |   88 ++
 plugins/status-icon/status-icon-preferences.ui     |  199 +++
 plugins/status-icon/status-icon-ui.xml             |   26 +
 plugins/status-icon/status-icon.rb-plugin.in       |    8 +
 po/POTFILES.in                                     |    8 +-
 19 files changed, 3507 insertions(+), 100 deletions(-)

diff --git a/configure.ac b/configure.ac
index 6ba5007..fcf34b5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -70,6 +70,14 @@ PKG_CHECK_MODULES(RB_CLIENT, glib-2.0 >= $GLIB_REQS gio-2.0 >= $GLIB_REQS)
 # libgnomeui still required with gtk < 2.14
 PKG_CHECK_EXISTS(gtk+-2.0 >= 2.14, [LIBGNOME_REQS=], [LIBGNOME_REQS=libgnomeui-2.0])
 
+# use gtk status icon with gtk >= 2.16
+PKG_CHECK_EXISTS(gtk+-2.0 >= 2.16, [
+	use_gtk_status_icon=yes
+	AC_DEFINE(USE_GTK_STATUS_ICON, 1, [Use GtkStatusIcon])
+], [use_gtk_status_icon=no])
+AM_CONDITIONAL(USE_GTK_STATUS_ICON, test x"$use_gtk_status_icon" = xyes)
+
+
 PKG_CHECK_MODULES(RHYTHMBOX,				\
 		  gtk+-2.0 >= $GTK_REQS			\
 		  glib-2.0 >= $GLIB_REQS		\
@@ -819,7 +827,6 @@ plugins/sample-vala/Makefile
 plugins/pythonconsole/Makefile
 plugins/artdisplay/Makefile
 plugins/artdisplay/artdisplay/Makefile
-plugins/dontreallyclose/Makefile
 plugins/magnatune/Makefile
 plugins/magnatune/magnatune/Makefile
 plugins/jamendo/Makefile
@@ -827,6 +834,7 @@ plugins/jamendo/jamendo/Makefile
 plugins/generic-player/Makefile
 plugins/rb/Makefile
 plugins/power-manager/Makefile
+plugins/status-icon/Makefile
 plugins/visualizer/Makefile
 plugins/mmkeys/Makefile
 bindings/Makefile
diff --git a/data/rhythmbox.schemas b/data/rhythmbox.schemas
index 93d56df..dc240f7 100644
--- a/data/rhythmbox.schemas
+++ b/data/rhythmbox.schemas
@@ -124,17 +124,6 @@
       </schema>
 
       <schema>
-        <key>/schemas/apps/rhythmbox/state/window_visible</key>
-        <applyto>/apps/rhythmbox/state/window_visible</applyto>
-        <owner>rhythmbox</owner>
-        <type>bool</type>
-        <default>TRUE</default>
-        <locale name="C">
-	<short>Whether the window is visible</short>
-	<long>When set to false, causes Rhythmbox to start only as a tray icon with the window hidden.</long>
-        </locale>
-      </schema>
-      <schema>
         <key>/schemas/apps/rhythmbox/state/paned_position</key>
         <applyto>/apps/rhythmbox/state/paned_position</applyto>
         <owner>rhythmbox</owner>
@@ -833,17 +822,6 @@
         </locale>
       </schema>
       <schema>
-        <key>/schemas/apps/rhythmbox/ui/show_notifications</key>
-        <applyto>/apps/rhythmbox/ui/show_notifications</applyto>
-        <owner>rhythmbox</owner>
-        <type>bool</type>
-        <default>TRUE</default>
-        <locale name="C">
-	<short>Whether to show notifications from the tray icon</short>
-	<long>When enabled, Rhythmbox will display notification bubbles when the playing track changes, a podcast is downloaded, a CD finished burning, etc.</long>
-        </locale>
-      </schema>
-      <schema>
         <key>/schemas/apps/rhythmbox/plugins/no_user_plugins</key>
         <applyto>/apps/rhythmbox/plugins/no_user_plugins</applyto>
         <owner>rhythmbox</owner>
@@ -1478,5 +1456,69 @@
 	<long>True if the FM radio plugin is hidden.</long>
 	</locale>
       </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/status-icon/active</key>
+        <applyto>/apps/rhythmbox/plugins/status-icon/active</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>TRUE</default>
+        <locale name="C">
+        <short>True if the status icon plugin is enabled.</short>
+	<long>True if the status icon plugin is enabled.</long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/tray-icon/hidden</key>
+        <applyto>/apps/rhythmbox/plugins/tray-icon/hidden</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>FALSE</default>
+        <locale name="C">
+        <short>True if the status icon plugin is hidden.</short>
+	<long>True if the status icon plugin is hidden.</long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/status-icon/notification-mode</key>
+        <applyto>/apps/rhythmbox/plugins/status-icon/notification-mode</applyto>
+        <owner>rhythmbox</owner>
+        <type>int</type>
+        <default>0</default>
+        <locale name="C">
+        <short>Notification popup visibility mode.</short>
+	<long>
+          If 0, no notification popups are shown.
+          If 1, notification popups are shown when the main window is hidden.
+          If 2, notification popups are always shown.
+        </long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/status-icon/status-icon-mode</key>
+        <applyto>/apps/rhythmbox/plugins/status-icon/status-icon-mode</applyto>
+        <owner>rhythmbox</owner>
+        <type>int</type>
+        <default>0</default>
+        <locale name="C">
+        <short>Status icon visibility mode.</short>
+	<long>
+          If 0, the status icon is never shown.
+          If 1, the status icon is only shown while a notification popup is visible.
+          If 2, the status icon is always shown.
+          If 3, the status icon is always shown, and the main window minimizes and closes into the status icon.
+        </long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/status-icon/window-visible</key>
+        <applyto>/apps/rhythmbox/plugins/status-icon/window-visible</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>TRUE</default>
+        <locale name="C">
+	<short>Whether the window is visible</short>
+	<long>When set to false, causes Rhythmbox to start only as a tray icon with the window hidden.</long>
+        </locale>
+      </schema>
     </schemalist>
 </gconfschemafile>
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 0e7982f..8d68e92 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -8,6 +8,7 @@ SUBDIRS = 						\
 	mmkeys						\
 	power-manager					\
 	sample						\
+	status-icon					\
 	visualizer
 
 if WITH_LIRC
@@ -23,7 +24,6 @@ SUBDIRS += 						\
 	magnatune					\
 	jamendo						\
 	coherence					\
-	dontreallyclose					\
 	im-status					\
 	rb
 endif
diff --git a/plugins/dontreallyclose/Makefile.am b/plugins/dontreallyclose/Makefile.am
deleted file mode 100644
index 0474355..0000000
--- a/plugins/dontreallyclose/Makefile.am
+++ /dev/null
@@ -1,14 +0,0 @@
-# Don't Really Close Python Plugin
-
-plugin_in_files = dontreallyclose.rb-plugin.in
-%.rb-plugin: %.rb-plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
-
-plugin_DATA = $(plugin_in_files:.rb-plugin.in=.rb-plugin)
-
-plugindir = $(PLUGINDIR)/dontreallyclose
-plugin_PYTHON = dontreallyclose.py
-
-EXTRA_DIST = $(plugin_in_files) $(plugin_PYTHON)
-
-CLEANFILES = $(plugin_DATA)
-DISTCLEANFILES = $(plugin_DATA)
diff --git a/plugins/dontreallyclose/dontreallyclose.py b/plugins/dontreallyclose/dontreallyclose.py
deleted file mode 100644
index 77141bc..0000000
--- a/plugins/dontreallyclose/dontreallyclose.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
-#
-# Copyright (C) 2008 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.
-#
-# The Rhythmbox authors hereby grant permission for non-GPL compatible
-# GStreamer plugins to be used and distributed together with GStreamer
-# and Rhythmbox. This permission is above and beyond the permissions granted
-# by the GPL license by which Rhythmbox is covered. If you modify this code
-# you may extend this exception to your version of the code, but you are not
-# obligated to do so. If you do not wish to do so, delete this exception
-# statement from your version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
-
-
-# this plugin is more license than plugin.
-
-import rb
-import gconf
-
-class DontReallyClosePlugin(rb.Plugin):
-	def __init__(self):
-		rb.Plugin.__init__(self)
-		self.delete_event_id = 0
-
-	def delete_event_cb(self, widget, event):
-		widget.hide()
-		gconf.client_get_default().set_bool("/apps/rhythmbox/state/window_visible", 0)
-		return True
-
-	def activate(self, shell):
-		self.delete_event_id = shell.props.window.connect('delete-event', self.delete_event_cb)
-
-	def deactivate(self, shell):
-		if self.delete_event_id != 0:
-			shell.props.window.disconnect(self.delete_event_id)
-			self.delete_event_id = 0
-
diff --git a/plugins/dontreallyclose/dontreallyclose.rb-plugin.in b/plugins/dontreallyclose/dontreallyclose.rb-plugin.in
deleted file mode 100644
index 0f312dd..0000000
--- a/plugins/dontreallyclose/dontreallyclose.rb-plugin.in
+++ /dev/null
@@ -1,9 +0,0 @@
-[RB Plugin]
-Loader=python
-Module=dontreallyclose
-IAge=1
-_Name=Minimize to tray
-_Description=Minimize to the tray when closing the main window
-Authors=Jonathan Matthew
-Copyright=Copyright © 2008 Jonathan Matthew
-Website=http://www.rhythmbox.org/
diff --git a/plugins/status-icon/Makefile.am b/plugins/status-icon/Makefile.am
new file mode 100644
index 0000000..cc70896
--- /dev/null
+++ b/plugins/status-icon/Makefile.am
@@ -0,0 +1,76 @@
+NULL =
+
+plugindir = $(PLUGINDIR)/status-icon
+plugin_LTLIBRARIES = libstatus-icon.la
+
+if USE_GTK_STATUS_ICON
+ICON_IMPL_FILES = \
+	rb-tray-icon-gtk.h 				\
+	rb-tray-icon-gtk.c
+else
+ICON_IMPL_FILES = \
+	eggtrayicon.c \
+	eggtrayicon.h \
+	rb-tray-icon.c \
+	rb-tray-icon.h
+endif
+
+libstatus_icon_la_SOURCES = \
+	rb-status-icon-plugin.h				\
+	rb-status-icon-plugin.c				\
+	$(ICON_IMPL_FILES)				\
+	$(NULL)
+
+
+libstatus_icon_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+
+libstatus_icon_la_LIBADD = $(RHYTHMBOX_LIBS)
+
+INCLUDES = 						\
+        -DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
+	-DG_LOG_DOMAIN=\"Rhythmbox\"		 	\
+	-I$(top_srcdir) 				\
+	-I$(top_srcdir)/lib                        	\
+	-I$(top_srcdir)/rhythmdb                       	\
+	-I$(top_srcdir)/widgets                    	\
+	-I$(top_srcdir)/sources                    	\
+	-I$(top_srcdir)/plugins				\
+	-I$(top_srcdir)/shell				\
+	-DPIXMAP_DIR=\""$(datadir)/pixmaps"\"		\
+	-DSHARE_DIR=\"$(pkgdatadir)\"                   \
+	-DDATADIR=\""$(datadir)"\"			\
+	$(RHYTHMBOX_CFLAGS)				\
+	-D_XOPEN_SOURCE -D_BSD_SOURCE
+
+uixmldir = $(plugindir)
+uixml_DATA = 						\
+	status-icon-ui.xml				\
+	status-icon-preferences.ui			\
+	$(NULL)
+
+plugin_in_files = status-icon.rb-plugin.in
+
+%.rb-plugin: %.rb-plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+BUILT_SOURCES =							\
+	$(plugin_in_files:.rb-plugin.in=.rb-plugin) 	\
+	$(NULL)
+
+plugin_DATA = 			\
+	$(BUILT_SOURCES)	\
+	$(NULL)
+
+EXTRA_DIST = 			\
+	$(glade_DATA)		\
+	$(uixml_DATA)		\
+	$(plugin_in_files)	\
+	$(NULL)
+
+CLEANFILES = 			\
+	$(BUILT_SOURCES)	\
+	$(NULL)
+
+DISTCLEANFILES =		\
+	$(BUILT_SOURCES)	\
+	$(NULL)
+
diff --git a/plugins/status-icon/eggtrayicon.c b/plugins/status-icon/eggtrayicon.c
new file mode 100644
index 0000000..da30511
--- /dev/null
+++ b/plugins/status-icon/eggtrayicon.c
@@ -0,0 +1,559 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* eggtrayicon.c
+ * Copyright (C) 2002 Anders Carlsson <andersca gnu org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301  USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <libintl.h>
+
+#include "eggtrayicon.h"
+
+#include <gdkconfig.h>
+#if defined (GDK_WINDOWING_X11)
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+#elif defined (GDK_WINDOWING_WIN32)
+#include <gdk/gdkwin32.h>
+#endif
+
+#ifndef EGG_COMPILATION
+#ifndef _
+#define _(x) dgettext (GETTEXT_PACKAGE, x)
+#define N_(x) x
+#endif
+#else
+#define _(x) x
+#define N_(x) x
+#endif
+
+#define SYSTEM_TRAY_REQUEST_DOCK    0
+#define SYSTEM_TRAY_BEGIN_MESSAGE   1
+#define SYSTEM_TRAY_CANCEL_MESSAGE  2
+
+#define SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define SYSTEM_TRAY_ORIENTATION_VERT 1
+
+enum {
+  PROP_0,
+  PROP_ORIENTATION
+};
+
+static GtkPlugClass *parent_class = NULL;
+
+static void egg_tray_icon_init (EggTrayIcon *icon);
+static void egg_tray_icon_class_init (EggTrayIconClass *klass);
+
+static void egg_tray_icon_get_property (GObject    *object,
+					guint       prop_id,
+					GValue     *value,
+					GParamSpec *pspec);
+
+static void egg_tray_icon_realize   (GtkWidget *widget);
+static void egg_tray_icon_unrealize (GtkWidget *widget);
+
+static void egg_tray_icon_add (GtkContainer *container,
+			       GtkWidget    *widget);
+
+#ifdef GDK_WINDOWING_X11
+static void egg_tray_icon_update_manager_window    (EggTrayIcon *icon,
+						    gboolean     dock_if_realized);
+static void egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon);
+#endif
+
+GType
+egg_tray_icon_get_type (void)
+{
+  static GType our_type = 0;
+
+  if (our_type == 0)
+    {
+      static const GTypeInfo our_info =
+      {
+	sizeof (EggTrayIconClass),
+	(GBaseInitFunc) NULL,
+	(GBaseFinalizeFunc) NULL,
+	(GClassInitFunc) egg_tray_icon_class_init,
+	NULL, /* class_finalize */
+	NULL, /* class_data */
+	sizeof (EggTrayIcon),
+	0,    /* n_preallocs */
+	(GInstanceInitFunc) egg_tray_icon_init
+      };
+
+      our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0);
+    }
+
+  return our_type;
+}
+
+static void
+egg_tray_icon_init (EggTrayIcon *icon)
+{
+  icon->stamp = 1;
+  icon->orientation = GTK_ORIENTATION_HORIZONTAL;
+
+  gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
+}
+
+static void
+egg_tray_icon_class_init (EggTrayIconClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *)klass;
+  GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
+  GtkContainerClass *container_class = (GtkContainerClass *)klass;
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->get_property = egg_tray_icon_get_property;
+
+  widget_class->realize   = egg_tray_icon_realize;
+  widget_class->unrealize = egg_tray_icon_unrealize;
+
+  container_class->add = egg_tray_icon_add;
+
+  g_object_class_install_property (gobject_class,
+				   PROP_ORIENTATION,
+				   g_param_spec_enum ("orientation",
+						      _("Orientation"),
+						      _("The orientation of the tray."),
+						      GTK_TYPE_ORIENTATION,
+						      GTK_ORIENTATION_HORIZONTAL,
+						      G_PARAM_READABLE));
+
+#if defined (GDK_WINDOWING_X11)
+  /* Nothing */
+#elif defined (GDK_WINDOWING_WIN32)
+  g_warning ("Port eggtrayicon to Win32");
+#else
+  g_warning ("Port eggtrayicon to this GTK+ backend");
+#endif
+}
+
+static void
+egg_tray_icon_get_property (GObject    *object,
+			    guint       prop_id,
+			    GValue     *value,
+			    GParamSpec *pspec)
+{
+  EggTrayIcon *icon = EGG_TRAY_ICON (object);
+
+  switch (prop_id)
+    {
+    case PROP_ORIENTATION:
+      g_value_set_enum (value, icon->orientation);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+#ifdef GDK_WINDOWING_X11
+
+static void
+egg_tray_icon_get_orientation_property (EggTrayIcon *icon)
+{
+  Display *xdisplay;
+  Atom type;
+  int format;
+  union {
+	gulong *prop;
+	guchar *prop_ch;
+  } prop = { NULL };
+  gulong nitems;
+  gulong bytes_after;
+  int error, result;
+
+  g_assert (icon->manager_window != None);
+
+  xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+
+  gdk_error_trap_push ();
+  type = None;
+  result = XGetWindowProperty (xdisplay,
+			       icon->manager_window,
+			       icon->orientation_atom,
+			       0, G_MAXLONG, FALSE,
+			       XA_CARDINAL,
+			       &type, &format, &nitems,
+			       &bytes_after, &(prop.prop_ch));
+  error = gdk_error_trap_pop ();
+
+  if (error || result != Success)
+    return;
+
+  if (type == XA_CARDINAL)
+    {
+      GtkOrientation orientation;
+
+      orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
+					GTK_ORIENTATION_HORIZONTAL :
+					GTK_ORIENTATION_VERTICAL;
+
+      if (icon->orientation != orientation)
+	{
+	  icon->orientation = orientation;
+
+	  g_object_notify (G_OBJECT (icon), "orientation");
+	}
+    }
+
+  if (prop.prop)
+    XFree (prop.prop);
+}
+
+static GdkFilterReturn
+egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
+{
+  EggTrayIcon *icon = user_data;
+  XEvent *xev = (XEvent *)xevent;
+
+  if (xev->xany.type == ClientMessage &&
+      xev->xclient.message_type == icon->manager_atom &&
+      xev->xclient.data.l[1] == icon->selection_atom)
+    {
+      egg_tray_icon_update_manager_window (icon, TRUE);
+    }
+  else if (xev->xany.window == icon->manager_window)
+    {
+      if (xev->xany.type == PropertyNotify &&
+	  xev->xproperty.atom == icon->orientation_atom)
+	{
+	  egg_tray_icon_get_orientation_property (icon);
+	}
+      if (xev->xany.type == DestroyNotify)
+	{
+	  egg_tray_icon_manager_window_destroyed (icon);
+	}
+    }
+  return GDK_FILTER_CONTINUE;
+}
+
+#endif
+
+static void
+egg_tray_icon_unrealize (GtkWidget *widget)
+{
+#ifdef GDK_WINDOWING_X11
+  EggTrayIcon *icon = EGG_TRAY_ICON (widget);
+  GdkWindow *root_window;
+
+  if (icon->manager_window != None)
+    {
+      GdkWindow *gdkwin;
+
+      gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
+                                              icon->manager_window);
+
+      gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
+    }
+
+  root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
+
+  gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);
+
+  if (GTK_WIDGET_CLASS (parent_class)->unrealize)
+    (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
+#endif
+}
+
+#ifdef GDK_WINDOWING_X11
+
+static void
+egg_tray_icon_send_manager_message (EggTrayIcon *icon,
+				    long         message,
+				    Window       window,
+				    long         data1,
+				    long         data2,
+				    long         data3)
+{
+  XClientMessageEvent ev;
+  Display *display;
+
+  ev.type = ClientMessage;
+  ev.window = window;
+  ev.message_type = icon->system_tray_opcode_atom;
+  ev.format = 32;
+  ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
+  ev.data.l[1] = message;
+  ev.data.l[2] = data1;
+  ev.data.l[3] = data2;
+  ev.data.l[4] = data3;
+
+  display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+
+  gdk_error_trap_push ();
+  XSendEvent (display,
+	      icon->manager_window, False, NoEventMask, (XEvent *)&ev);
+  XSync (display, False);
+  gdk_error_trap_pop ();
+}
+
+static void
+egg_tray_icon_send_dock_request (EggTrayIcon *icon)
+{
+  egg_tray_icon_send_manager_message (icon,
+				      SYSTEM_TRAY_REQUEST_DOCK,
+				      icon->manager_window,
+				      gtk_plug_get_id (GTK_PLUG (icon)),
+				      0, 0);
+}
+
+static void
+egg_tray_icon_update_manager_window (EggTrayIcon *icon,
+				     gboolean     dock_if_realized)
+{
+  Display *xdisplay;
+
+  if (icon->manager_window != None)
+    return;
+
+  xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+
+  XGrabServer (xdisplay);
+
+  icon->manager_window = XGetSelectionOwner (xdisplay,
+					     icon->selection_atom);
+
+  if (icon->manager_window != None)
+    XSelectInput (xdisplay,
+		  icon->manager_window, StructureNotifyMask|PropertyChangeMask);
+
+  XUngrabServer (xdisplay);
+  XFlush (xdisplay);
+
+  if (icon->manager_window != None)
+    {
+      GdkWindow *gdkwin;
+
+      gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
+					      icon->manager_window);
+
+      gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
+
+      if (dock_if_realized && GTK_WIDGET_REALIZED (icon))
+	egg_tray_icon_send_dock_request (icon);
+
+      egg_tray_icon_get_orientation_property (icon);
+    }
+}
+
+static void
+egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon)
+{
+  GdkWindow *gdkwin;
+
+  g_return_if_fail (icon->manager_window != None);
+
+  gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
+					  icon->manager_window);
+
+  gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
+
+  icon->manager_window = None;
+
+  egg_tray_icon_update_manager_window (icon, TRUE);
+}
+
+#endif
+
+static gboolean
+transparent_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
+{
+  gdk_window_clear_area (widget->window, event->area.x, event->area.y,
+			 event->area.width, event->area.height);
+  return FALSE;
+}
+
+static void
+make_transparent_again (GtkWidget *widget, GtkStyle *previous_style,
+			gpointer user_data)
+{
+  gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
+}
+
+static void
+make_transparent (GtkWidget *widget, gpointer user_data)
+{
+  if (GTK_WIDGET_NO_WINDOW (widget) || GTK_WIDGET_APP_PAINTABLE (widget))
+    return;
+
+  gtk_widget_set_app_paintable (widget, TRUE);
+  gtk_widget_set_double_buffered (widget, FALSE);
+  gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
+  g_signal_connect (widget, "expose_event",
+		    G_CALLBACK (transparent_expose_event), NULL);
+  g_signal_connect_after (widget, "style_set",
+			  G_CALLBACK (make_transparent_again), NULL);
+}
+
+static void
+egg_tray_icon_realize (GtkWidget *widget)
+{
+#ifdef GDK_WINDOWING_X11
+  EggTrayIcon *icon = EGG_TRAY_ICON (widget);
+  GdkScreen *screen;
+  GdkDisplay *display;
+  Display *xdisplay;
+  char buffer[256];
+  GdkWindow *root_window;
+
+  if (GTK_WIDGET_CLASS (parent_class)->realize)
+    GTK_WIDGET_CLASS (parent_class)->realize (widget);
+
+  make_transparent (widget, NULL);
+
+  screen = gtk_widget_get_screen (widget);
+  display = gdk_screen_get_display (screen);
+  xdisplay = gdk_x11_display_get_xdisplay (display);
+
+  /* Now see if there's a manager window around */
+  g_snprintf (buffer, sizeof (buffer),
+	      "_NET_SYSTEM_TRAY_S%d",
+	      gdk_screen_get_number (screen));
+
+  icon->selection_atom = XInternAtom (xdisplay, buffer, False);
+
+  icon->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
+
+  icon->system_tray_opcode_atom = XInternAtom (xdisplay,
+						   "_NET_SYSTEM_TRAY_OPCODE",
+						   False);
+
+  icon->orientation_atom = XInternAtom (xdisplay,
+					"_NET_SYSTEM_TRAY_ORIENTATION",
+					False);
+
+  egg_tray_icon_update_manager_window (icon, FALSE);
+  egg_tray_icon_send_dock_request (icon);
+
+  root_window = gdk_screen_get_root_window (screen);
+
+  /* Add a root window filter so that we get changes on MANAGER */
+  gdk_window_add_filter (root_window,
+			 egg_tray_icon_manager_filter, icon);
+#endif
+}
+
+static void
+egg_tray_icon_add (GtkContainer *container, GtkWidget *widget)
+{
+  g_signal_connect (widget, "realize",
+		    G_CALLBACK (make_transparent), NULL);
+  GTK_CONTAINER_CLASS (parent_class)->add (container, widget);
+}
+
+EggTrayIcon *
+egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
+{
+  g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+
+  return g_object_new (EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
+}
+
+EggTrayIcon*
+egg_tray_icon_new (const gchar *name)
+{
+  return g_object_new (EGG_TYPE_TRAY_ICON, "title", name, NULL);
+}
+
+guint
+egg_tray_icon_send_message (EggTrayIcon *icon,
+			    gint         timeout,
+			    const gchar *message,
+			    gint         len)
+{
+  guint stamp;
+
+  g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
+  g_return_val_if_fail (timeout >= 0, 0);
+  g_return_val_if_fail (message != NULL, 0);
+
+#ifdef GDK_WINDOWING_X11
+  if (icon->manager_window == None)
+    return 0;
+#endif
+
+  if (len < 0)
+    len = strlen (message);
+
+  stamp = icon->stamp++;
+
+#ifdef GDK_WINDOWING_X11
+  /* Get ready to send the message */
+  egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
+				      icon->manager_window,
+				      timeout, len, stamp);
+
+  /* Now to send the actual message */
+  gdk_error_trap_push ();
+  while (len > 0)
+    {
+      XClientMessageEvent ev;
+      Display *xdisplay;
+
+      xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+
+      ev.type = ClientMessage;
+      ev.window = icon->manager_window;
+      ev.format = 8;
+      ev.message_type = XInternAtom (xdisplay,
+				     "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
+      if (len > 20)
+	{
+	  memcpy (&ev.data, message, 20);
+	  len -= 20;
+	  message += 20;
+	}
+      else
+	{
+	  memcpy (&ev.data, message, len);
+	  len = 0;
+	}
+
+      XSendEvent (xdisplay,
+		  icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
+      XSync (xdisplay, False);
+    }
+  gdk_error_trap_pop ();
+#endif
+
+  return stamp;
+}
+
+void
+egg_tray_icon_cancel_message (EggTrayIcon *icon,
+			      guint        id)
+{
+  g_return_if_fail (EGG_IS_TRAY_ICON (icon));
+  g_return_if_fail (id > 0);
+#ifdef GDK_WINDOWING_X11
+  egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
+				      (Window)gtk_plug_get_id (GTK_PLUG (icon)),
+				      id, 0, 0);
+#endif
+}
+
+GtkOrientation
+egg_tray_icon_get_orientation (EggTrayIcon *icon)
+{
+  g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
+
+  return icon->orientation;
+}
diff --git a/plugins/status-icon/eggtrayicon.h b/plugins/status-icon/eggtrayicon.h
new file mode 100644
index 0000000..d1ca4b3
--- /dev/null
+++ b/plugins/status-icon/eggtrayicon.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* eggtrayicon.h
+ * Copyright (C) 2002 Anders Carlsson <andersca gnu org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301  USA.
+ */
+
+#ifndef __EGG_TRAY_ICON_H__
+#define __EGG_TRAY_ICON_H__
+
+#include <gtk/gtk.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_TRAY_ICON		(egg_tray_icon_get_type ())
+#define EGG_TRAY_ICON(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TRAY_ICON, EggTrayIcon))
+#define EGG_TRAY_ICON_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_TRAY_ICON, EggTrayIconClass))
+#define EGG_IS_TRAY_ICON(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TRAY_ICON))
+#define EGG_IS_TRAY_ICON_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_TRAY_ICON))
+#define EGG_TRAY_ICON_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TRAY_ICON, EggTrayIconClass))
+
+typedef struct _EggTrayIcon	  EggTrayIcon;
+typedef struct _EggTrayIconClass  EggTrayIconClass;
+
+struct _EggTrayIcon
+{
+  GtkPlug parent_instance;
+
+  guint stamp;
+
+#ifdef GDK_WINDOWING_X11
+  Atom selection_atom;
+  Atom manager_atom;
+  Atom system_tray_opcode_atom;
+  Atom orientation_atom;
+  Window manager_window;
+#endif
+  GtkOrientation orientation;
+};
+
+struct _EggTrayIconClass
+{
+  GtkPlugClass parent_class;
+};
+
+GType        egg_tray_icon_get_type       (void);
+
+EggTrayIcon *egg_tray_icon_new_for_screen (GdkScreen   *screen,
+					   const gchar *name);
+
+EggTrayIcon *egg_tray_icon_new            (const gchar *name);
+
+guint        egg_tray_icon_send_message   (EggTrayIcon *icon,
+					   gint         timeout,
+					   const char  *message,
+					   gint         len);
+void         egg_tray_icon_cancel_message (EggTrayIcon *icon,
+					   guint        id);
+
+GtkOrientation egg_tray_icon_get_orientation (EggTrayIcon *icon);
+
+G_END_DECLS
+
+#endif /* __EGG_TRAY_ICON_H__ */
diff --git a/plugins/status-icon/rb-status-icon-plugin.c b/plugins/status-icon/rb-status-icon-plugin.c
new file mode 100644
index 0000000..e751515
--- /dev/null
+++ b/plugins/status-icon/rb-status-icon-plugin.c
@@ -0,0 +1,1383 @@
+/*
+ * rb-status-icon-plugin.c
+ *
+ *  Copyright (C) 2009  Jonathan Matthew  <jonathan d14n org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
+ * GStreamer plugins to be used and distributed together with GStreamer
+ * and Rhythmbox. This permission is above and beyond the permissions granted
+ * by the GPL license by which Rhythmbox is covered. If you modify this code
+ * you may extend this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#include <X11/Xatom.h>
+
+#ifdef HAVE_NOTIFY
+#include <libnotify/notify.h>
+#endif
+
+#include "rb-status-icon-plugin.h"
+#include "rb-util.h"
+#include "rb-plugin.h"
+#include "rb-debug.h"
+#include "rb-shell.h"
+#include "rb-shell-player.h"
+#include "rb-marshal.h"
+#include "rb-stock-icons.h"
+#include "eel-gconf-extensions.h"
+
+#if defined(USE_GTK_STATUS_ICON)
+#include "rb-tray-icon-gtk.h"
+#else
+#include "rb-tray-icon.h"
+#endif
+
+#define TRAY_ICON_DEFAULT_TOOLTIP _("Music Player")
+
+#define TOOLTIP_IMAGE_BORDER_WIDTH	1
+#define PLAYING_ENTRY_NOTIFY_TIME 	4
+
+#define CONF_PLUGIN_SETTINGS	"/apps/rhythmbox/plugins/status-icon"
+#define CONF_NOTIFICATION_MODE	CONF_PLUGIN_SETTINGS "/notification-mode"
+#define CONF_STATUS_ICON_MODE	CONF_PLUGIN_SETTINGS "/status-icon-mode"
+#define CONF_WINDOW_VISIBILITY	CONF_PLUGIN_SETTINGS "/window-visible"
+
+#define CONF_OLD_ICON_MODE	"/apps/rhythmbox/plugins/dontreallyclose/active"
+#define CONF_OLD_NOTIFICATIONS	"/apps/rhythmbox/ui/show_notifications"
+#define CONF_OLD_VISIBILITY	"/apps/rhythmbox/state/window_visible"
+
+static void toggle_window_cmd (GtkAction *action, RBStatusIconPlugin *plugin);
+static void show_window_cmd (GtkAction *action, RBStatusIconPlugin *plugin);
+static void show_notifications_cmd (GtkAction *action, RBStatusIconPlugin *plugin);
+static void update_status_icon_visibility (RBStatusIconPlugin *plugin, gboolean notifying);
+
+struct _RBStatusIconPluginPrivate
+{
+	GtkActionGroup *action_group;
+	guint ui_merge_id;
+
+	RBTrayIcon *tray_icon;
+
+	guint hide_main_window_id;
+	guint gconf_notify_id;
+
+	/* configuration */
+	enum {
+		ICON_NEVER = 0,
+		ICON_WITH_NOTIFY,
+		ICON_ALWAYS,
+		ICON_OWNS_WINDOW
+	} icon_mode;
+	enum {
+		NOTIFY_NEVER = 0,
+		NOTIFY_HIDDEN,
+		NOTIFY_ALWAYS
+	} notify_mode;
+
+	/* current playing data */
+	char *current_title;
+	char *current_album_and_artist;	/* from _album_ by _artist_ */
+
+	/* tooltip data */
+	char *tooltip_markup;
+	GdkPixbuf *tooltip_app_pixbuf;
+	GdkPixbuf *tooltip_pixbuf;
+	gboolean tooltips_suppressed;
+
+	/* notification data */
+	GdkPixbuf *notify_pixbuf;
+#ifdef HAVE_NOTIFY
+	NotifyNotification *notification;
+#endif
+
+	GtkWidget *config_dialog;
+
+	RBShellPlayer *shell_player;
+	RBShell *shell;
+	RhythmDB *db;
+};
+
+G_MODULE_EXPORT GType register_rb_plugin (GTypeModule *module);
+
+RB_PLUGIN_REGISTER(RBStatusIconPlugin, rb_status_icon_plugin)
+
+static GtkActionEntry rb_status_icon_plugin_actions [] =
+{
+	{ "MusicClose", GTK_STOCK_CLOSE, N_("_Close"), "<control>W",
+	  N_("Hide the music player window"),
+	  G_CALLBACK (toggle_window_cmd) }
+};
+
+static GtkToggleActionEntry rb_status_icon_plugin_toggle_entries [] =
+{
+	{ "TrayShowWindow", NULL, N_("_Show Music Player"), NULL,
+	  N_("Choose music to play"),
+	  G_CALLBACK (show_window_cmd) },
+	{ "TrayShowNotifications", NULL, N_("Show N_otifications"), NULL,
+	  N_("Show notifications of song changes and other events"),
+	  G_CALLBACK (show_notifications_cmd) },
+};
+
+static gchar *
+markup_escape (const char *text)
+{
+	return (text == NULL) ? NULL : g_markup_escape_text (text, -1);
+}
+
+static GdkPixbuf *
+create_tooltip_pixbuf (GdkPixbuf *pixbuf)
+{
+	GdkPixbuf *bordered;
+	int w;
+	int h;
+
+	/* add a black border */
+	w = gdk_pixbuf_get_width (pixbuf);
+	h = gdk_pixbuf_get_height (pixbuf);
+	bordered = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
+				   gdk_pixbuf_get_has_alpha (pixbuf),
+				   gdk_pixbuf_get_bits_per_sample (pixbuf),
+				   w + (TOOLTIP_IMAGE_BORDER_WIDTH*2),
+				   h + (TOOLTIP_IMAGE_BORDER_WIDTH*2));
+	gdk_pixbuf_fill (bordered, 0xff);		/* opaque black */
+	gdk_pixbuf_copy_area (pixbuf,
+			      0, 0, w, h,
+			      bordered,
+			      TOOLTIP_IMAGE_BORDER_WIDTH,
+			      TOOLTIP_IMAGE_BORDER_WIDTH);
+
+	return bordered;
+}
+
+/* UI actions */
+
+static void
+toggle_window_cmd (GtkAction *action, RBStatusIconPlugin *plugin)
+{
+	rb_shell_toggle_visibility (plugin->priv->shell);
+}
+
+static void
+show_window_cmd (GtkAction *action, RBStatusIconPlugin *plugin)
+{
+	g_object_set (plugin->priv->shell,
+		      "visibility", gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)),
+		      NULL);
+}
+
+static void
+show_notifications_cmd (GtkAction *action, RBStatusIconPlugin *plugin)
+{
+	gboolean active;
+	int new_mode;
+
+	/* we've only got on/off here, so map that to 'never' or 'only when hidden' */
+	active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+	new_mode = active ? NOTIFY_HIDDEN : NOTIFY_NEVER;
+
+	eel_gconf_set_integer (CONF_NOTIFICATION_MODE, new_mode);
+}
+
+void
+rb_status_icon_plugin_scroll_event (RBStatusIconPlugin *plugin,
+				  GdkEventScroll *event)
+{
+	gdouble adjust;
+
+	switch (event->direction) {
+	case GDK_SCROLL_UP:
+		adjust = 0.02;
+		break;
+	case GDK_SCROLL_DOWN:
+		adjust = -0.02;
+		break;
+	default:
+		return;
+	}
+
+	rb_shell_player_set_volume_relative (plugin->priv->shell_player, adjust, NULL);
+}
+
+void
+rb_status_icon_plugin_button_press_event (RBStatusIconPlugin *plugin,
+					GdkEventButton *event)
+{
+	GtkWidget *popup;
+	GtkUIManager *ui_manager;
+
+	/* filter out double, triple clicks */
+	if (event->type != GDK_BUTTON_PRESS)
+		return;
+
+	switch (event->button) {
+	case 1:
+		rb_shell_toggle_visibility (plugin->priv->shell);
+		break;
+	case 2:
+		rb_shell_player_playpause (plugin->priv->shell_player, FALSE, NULL);
+		break;
+	case 3:
+		g_object_get (plugin->priv->shell, "ui-manager", &ui_manager, NULL);
+		popup = gtk_ui_manager_get_widget (GTK_UI_MANAGER (ui_manager),
+						   "/RhythmboxTrayPopup");
+
+		rb_tray_icon_menu_popup (plugin->priv->tray_icon, popup, 3);
+		g_object_unref (ui_manager);
+		break;
+	}
+}
+
+static void
+sync_actions (RBStatusIconPlugin *plugin)
+{
+	GtkAction *action;
+	gboolean visible;
+
+	action = gtk_action_group_get_action (plugin->priv->action_group,
+					      "TrayShowWindow");
+	g_object_get (plugin->priv->shell, "visibility", &visible, NULL);
+	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible);
+
+	action = gtk_action_group_get_action (plugin->priv->action_group,
+					      "TrayShowNotifications");
+#ifdef HAVE_NOTIFY
+	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+				      plugin->priv->notify_mode != NOTIFY_NEVER);
+#else
+	gtk_action_set_visible (action, FALSE);
+#endif
+
+	/* only show the 'close window' menu item if the icon owns the window */
+	action = gtk_action_group_get_action (plugin->priv->action_group,
+					      "MusicClose");
+	gtk_action_set_visible (action, plugin->priv->icon_mode == ICON_OWNS_WINDOW);
+}
+
+static void
+visibility_changed_cb (RBShell *shell,
+		       gboolean visible,
+		       RBStatusIconPlugin *plugin)
+{
+	sync_actions (plugin);
+}
+
+/* notification popups */
+
+#ifdef HAVE_NOTIFY
+static void
+notification_closed_cb (NotifyNotification *notification,
+			RBStatusIconPlugin *plugin)
+{
+	rb_debug ("notification closed");
+	plugin->priv->tooltips_suppressed = FALSE;
+	rb_tray_icon_trigger_tooltip_query (plugin->priv->tray_icon);
+
+	update_status_icon_visibility (plugin, FALSE);
+}
+
+static void
+do_notify (RBStatusIconPlugin *plugin,
+	   guint timeout,
+	   const char *primary,
+	   const char *secondary,
+	   GdkPixbuf *pixbuf)
+{
+	const char *icon_name;
+	GError *error = NULL;
+
+	if (notify_is_initted () == FALSE) {
+		if (notify_init ("rhythmbox") == FALSE) {
+			g_warning ("libnotify initialization failed");
+			return;
+		}
+	}
+
+	update_status_icon_visibility (plugin, TRUE);
+
+	if (primary == NULL)
+		primary = "";
+
+	if (secondary == NULL)
+		secondary = "";
+
+	if (pixbuf == NULL)
+		icon_name = RB_APP_ICON;
+	else
+		icon_name = NULL;
+
+	if (plugin->priv->notification == NULL) {
+		plugin->priv->notification = notify_notification_new (primary, secondary, icon_name, NULL);
+
+		g_signal_connect_object (plugin->priv->notification,
+					 "closed",
+					 G_CALLBACK (notification_closed_cb),
+					 plugin, 0);
+	} else {
+		notify_notification_update (plugin->priv->notification, primary, secondary, icon_name);
+	}
+
+	switch (plugin->priv->icon_mode) {
+	case ICON_NEVER:
+		break;
+
+	case ICON_WITH_NOTIFY:
+	case ICON_ALWAYS:
+	case ICON_OWNS_WINDOW:
+		rb_tray_icon_attach_notification (plugin->priv->tray_icon,
+						  plugin->priv->notification);
+		break;
+
+	default:
+		g_assert_not_reached ();
+	}
+
+	notify_notification_set_timeout (plugin->priv->notification, timeout);
+
+	if (pixbuf != NULL) {
+		notify_notification_set_icon_from_pixbuf (plugin->priv->notification, pixbuf);
+	}
+
+	if (notify_notification_show (plugin->priv->notification, &error) == FALSE) {
+		g_warning ("Failed to send notification (%s): %s", primary, error->message);
+		g_error_free (error);
+		update_status_icon_visibility (plugin, FALSE);
+	} else {
+		/* hide the tooltip while the notification is visible */
+		plugin->priv->tooltips_suppressed = TRUE;
+		rb_tray_icon_trigger_tooltip_query (plugin->priv->tray_icon);
+	}
+}
+
+static gboolean
+should_notify (RBStatusIconPlugin *plugin)
+{
+	gboolean visible;
+
+	switch (plugin->priv->icon_mode) {
+	case ICON_NEVER:
+	case ICON_WITH_NOTIFY:
+		break;
+
+	case ICON_ALWAYS:
+	case ICON_OWNS_WINDOW:
+		if (rb_tray_icon_is_embedded (plugin->priv->tray_icon) == FALSE) {
+			rb_debug ("status icon is not embedded, not notifying");
+			return FALSE;
+		}
+		break;
+
+	default:
+		g_assert_not_reached ();
+	}
+
+	switch (plugin->priv->notify_mode) {
+	case NOTIFY_NEVER:
+		rb_debug ("notifications disabled, not notifying");
+		return FALSE;
+
+	case NOTIFY_HIDDEN:
+		g_object_get (plugin->priv->shell, "visibility", &visible, NULL);
+		if (visible) {
+			rb_debug ("shell is visible, not notifying");
+			return FALSE;
+		}
+		break;
+
+	case NOTIFY_ALWAYS:
+		break;
+
+	default:
+		g_assert_not_reached ();
+	}
+
+	return TRUE;
+}
+
+static void
+notify_playing_entry (RBStatusIconPlugin *plugin, gboolean requested)
+{
+	if (requested == FALSE && should_notify (plugin) == FALSE) {
+		return;
+	}
+
+	do_notify (plugin,
+		   PLAYING_ENTRY_NOTIFY_TIME * 1000,
+		   plugin->priv->current_title,
+		   plugin->priv->current_album_and_artist,
+		   plugin->priv->notify_pixbuf);
+}
+
+static void
+notify_custom (RBStatusIconPlugin *plugin, guint timeout, const char *primary, const char *secondary, GdkPixbuf *pixbuf, gboolean requested)
+{
+	if (requested == FALSE && should_notify (plugin) == FALSE) {
+		return;
+	}
+
+	do_notify (plugin, timeout, primary, secondary, pixbuf);
+}
+
+static void
+cleanup_notification (RBStatusIconPlugin *plugin)
+{
+	if (plugin->priv->notification != NULL) {
+		g_signal_handlers_disconnect_by_func (plugin->priv->notification,
+						      G_CALLBACK (notification_closed_cb),
+						      plugin);
+		notify_notification_close (plugin->priv->notification, NULL);
+		plugin->priv->notification = NULL;
+	}
+}
+
+#else
+
+/* lack of notification popups */
+
+static void
+notify_playing_entry (RBStatusIconPlugin *plugin, gboolean requested)
+{
+}
+
+static void
+notify_custom (RBStatusIconPlugin *plugin, const char *primary, const char *secondary, GdkPixbuf *pixbuf, gboolean requested)
+{
+}
+
+static void
+cleanup_notification (RBStatusIconPlugin *plugin)
+{
+}
+
+#endif
+
+static void
+shell_notify_playing_cb (RBShell *shell, gboolean requested, RBStatusIconPlugin *plugin)
+{
+	notify_playing_entry (plugin, requested);
+}
+
+static void
+shell_notify_custom_cb (RBShell *shell, guint timeout, const char *primary, const char *secondary, GdkPixbuf *pixbuf, gboolean requested, RBStatusIconPlugin *plugin)
+{
+	notify_custom (plugin, timeout, primary, secondary, pixbuf, requested);
+}
+
+/* tooltips */
+
+static void
+update_tooltip (RBStatusIconPlugin *plugin)
+{
+	gboolean playing;
+	char *elapsed_string;
+	GString *secondary;
+
+	rb_shell_player_get_playing (plugin->priv->shell_player, &playing, NULL);
+	elapsed_string = rb_shell_player_get_playing_time_string (plugin->priv->shell_player);
+
+	secondary = g_string_sized_new (100);
+	if (plugin->priv->current_album_and_artist != NULL) {
+		g_string_append (secondary, plugin->priv->current_album_and_artist);
+		if (secondary->len != 0)
+			g_string_append_c (secondary, '\n');
+	}
+	if (plugin->priv->current_title == NULL) {
+		g_string_append (secondary, _("Not playing"));
+	} else if (!playing) {
+		/* Translators: the %s is the elapsed and total time */
+		g_string_append_printf (secondary, _("Paused, %s"), elapsed_string);
+	} else {
+		g_string_append (secondary, elapsed_string);
+	}
+
+	plugin->priv->tooltip_markup = g_string_free (secondary, FALSE);
+	g_free (elapsed_string);
+
+	rb_tray_icon_trigger_tooltip_query (plugin->priv->tray_icon);
+}
+
+gboolean
+rb_status_icon_plugin_set_tooltip (GtkWidget        *widget,
+				 gint              x,
+				 gint              y,
+				 gboolean          keyboard_tooltip,
+				 GtkTooltip       *tooltip,
+				 RBStatusIconPlugin *plugin)
+{
+	char *esc_primary;
+	char *markup;
+
+	if (plugin->priv->tooltips_suppressed)
+		return FALSE;
+
+	if (plugin->priv->tooltip_pixbuf != NULL) {
+		gtk_tooltip_set_icon (tooltip, plugin->priv->tooltip_pixbuf);
+	} else {
+		gtk_tooltip_set_icon (tooltip, plugin->priv->tooltip_app_pixbuf);
+	}
+
+	if (plugin->priv->current_title != NULL) {
+		esc_primary = g_markup_escape_text (plugin->priv->current_title, -1);
+	} else {
+		esc_primary = g_markup_escape_text (TRAY_ICON_DEFAULT_TOOLTIP, -1);
+	}
+
+	if (plugin->priv->tooltip_markup != NULL) {
+		markup = g_strdup_printf ("<big><b>%s</b></big>\n\n%s",
+					  esc_primary,
+					  plugin->priv->tooltip_markup);
+	} else {
+		markup = g_strdup_printf ("<big><b>%s</b></big>", esc_primary);
+	}
+
+	gtk_tooltip_set_markup (tooltip, markup);
+
+	g_free (esc_primary);
+	g_free (markup);
+
+	return TRUE;
+}
+
+/* information on current track */
+
+static void
+update_current_playing_data (RBStatusIconPlugin *plugin, RhythmDBEntry *entry)
+{
+	GValue *value;
+	const char *stream_title = NULL;
+	char *artist = NULL;
+	char *album = NULL;
+	char *title = NULL;
+	GString *secondary;
+
+	g_free (plugin->priv->current_title);
+	g_free (plugin->priv->current_album_and_artist);
+	plugin->priv->current_title = NULL;
+	plugin->priv->current_album_and_artist = NULL;
+
+	if (entry == NULL)
+		return;
+
+	secondary = g_string_sized_new (100);
+
+	/* get artist, preferring streaming song details */
+	value = rhythmdb_entry_request_extra_metadata (plugin->priv->db,
+						       entry,
+						       RHYTHMDB_PROP_STREAM_SONG_ARTIST);
+	if (value != NULL) {
+		artist = markup_escape (g_value_get_string (value));
+		g_value_unset (value);
+		g_free (value);
+	} else {
+		artist = markup_escape (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
+	}
+
+	if (artist != NULL && artist[0] != '\0') {
+		/* Translators: by Artist */
+		g_string_append_printf (secondary, _("by <i>%s</i>"), artist);
+	}
+	g_free (artist);
+
+	/* get album, preferring streaming song details */
+	value = rhythmdb_entry_request_extra_metadata (plugin->priv->db,
+						       entry,
+						       RHYTHMDB_PROP_STREAM_SONG_ALBUM);
+	if (value != NULL) {
+		album = markup_escape (g_value_get_string (value));
+		g_value_unset (value);
+		g_free (value);
+	} else {
+		album = markup_escape (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
+	}
+
+	if (album != NULL && album[0] != '\0') {
+		if (secondary->len != 0)
+			g_string_append_c (secondary, ' ');
+
+		/* Translators: from Album */
+		g_string_append_printf (secondary, _("from <i>%s</i>"), album);
+	}
+	g_free (album);
+
+	/* get title and possibly stream name.
+	 * if we have a streaming song title, the entry's title
+	 * property is the stream name.
+	 */
+	value = rhythmdb_entry_request_extra_metadata (plugin->priv->db,
+						       entry,
+						       RHYTHMDB_PROP_STREAM_SONG_TITLE);
+	if (value != NULL) {
+		title = g_value_dup_string (value);
+		g_value_unset (value);
+		g_free (value);
+
+		stream_title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
+	} else {
+		title = g_strdup (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE));
+	}
+
+	if (stream_title != NULL && stream_title[0] != '\0') {
+		char *escaped;
+
+		escaped = markup_escape (stream_title);
+		if (secondary->len == 0)
+			g_string_append (secondary, escaped);
+		else
+			g_string_append_printf (secondary, " (%s)", escaped);
+		g_free (escaped);
+	}
+
+	if (title != NULL)
+		plugin->priv->current_title = title;
+	else
+		/* Translators: unknown track title */
+		plugin->priv->current_title = g_strdup (_("Unknown"));
+
+	plugin->priv->current_album_and_artist = g_string_free (secondary, FALSE);
+}
+
+static void
+forget_pixbufs (RBStatusIconPlugin *plugin)
+{
+	if (plugin->priv->tooltip_pixbuf != NULL) {
+		g_object_unref (plugin->priv->tooltip_pixbuf);
+		plugin->priv->tooltip_pixbuf = NULL;
+	}
+	if (plugin->priv->notify_pixbuf != NULL) {
+		g_object_unref (plugin->priv->notify_pixbuf);
+		plugin->priv->notify_pixbuf = NULL;
+	}
+}
+
+static void
+playing_entry_changed_cb (RBShellPlayer *player,
+			  RhythmDBEntry *entry,
+			  RBStatusIconPlugin *plugin)
+{
+	forget_pixbufs (plugin);
+
+	update_current_playing_data (plugin, entry);
+
+	if (entry != NULL) {
+		notify_playing_entry (plugin, FALSE);
+	}
+	update_tooltip (plugin);
+}
+
+static gboolean
+is_playing_entry (RBStatusIconPlugin *plugin, RhythmDBEntry *entry)
+{
+	RhythmDBEntry *playing;
+
+	playing = rb_shell_player_get_playing_entry (plugin->priv->shell_player);
+	if (playing == NULL) {
+		return FALSE;
+	}
+
+	rhythmdb_entry_unref (playing);
+	return (entry == playing);
+}
+
+static void
+db_art_metadata_cb (RhythmDB *db,
+		    RhythmDBEntry *entry,
+		    const char *field,
+		    GValue *metadata,
+		    RBStatusIconPlugin *plugin)
+{
+	guint time;
+
+	if (is_playing_entry (plugin, entry) == FALSE)
+		return;
+
+	forget_pixbufs (plugin);
+
+	if (G_VALUE_HOLDS (metadata, GDK_TYPE_PIXBUF)) {
+		GdkPixbuf *pixbuf;
+
+		pixbuf = GDK_PIXBUF (g_value_get_object (metadata));
+
+		/* create a smallish copy for the tooltip */
+		if (pixbuf != NULL) {
+			GdkPixbuf *scaled;
+
+			scaled = rb_scale_pixbuf_to_size (pixbuf, GTK_ICON_SIZE_DIALOG);
+			plugin->priv->tooltip_pixbuf = create_tooltip_pixbuf (scaled);
+			plugin->priv->notify_pixbuf = scaled;
+		}
+
+		/* probably keep the full size thing for notifications?  hmm. */
+	}
+
+	rb_tray_icon_trigger_tooltip_query (plugin->priv->tray_icon);
+
+	if (rb_shell_player_get_playing_time (plugin->priv->shell_player, &time, NULL)) {
+		if (time < PLAYING_ENTRY_NOTIFY_TIME) {
+			notify_playing_entry (plugin, FALSE);
+		}
+	}
+}
+
+static void
+db_stream_metadata_cb (RhythmDB *db,
+		       RhythmDBEntry *entry,
+		       const char *field,
+		       GValue *metadata,
+		       RBStatusIconPlugin *plugin)
+{
+	if (is_playing_entry (plugin, entry) == FALSE)
+		return;
+
+	update_current_playing_data (plugin, entry);
+}
+
+
+static void
+elapsed_changed_cb (RBShellPlayer *player,
+		    guint elapsed,
+		    RBStatusIconPlugin *plugin)
+{
+	update_tooltip (plugin);
+}
+
+/* status icon visibility */
+
+static void
+update_status_icon_visibility (RBStatusIconPlugin *plugin, gboolean notifying)
+{
+	gboolean visible;
+
+	switch (plugin->priv->icon_mode) {
+	case ICON_NEVER:
+		visible = FALSE;
+		break;
+
+	case ICON_WITH_NOTIFY:
+		visible = notifying;
+		break;
+
+	case ICON_ALWAYS:
+	case ICON_OWNS_WINDOW:
+		visible = TRUE;
+		break;
+
+	default:
+		g_assert_not_reached ();
+	}
+
+	rb_tray_icon_set_visible (plugin->priv->tray_icon, visible);
+}
+
+/* minimize/close to tray */
+
+/* Based on a function found in wnck */
+static void
+set_icon_geometry  (GdkWindow *window,
+		    int        x,
+		    int        y,
+		    int        width,
+		    int        height)
+{
+	gulong data[4];
+	Display *dpy = gdk_x11_drawable_get_xdisplay (window);
+
+	data[0] = x;
+	data[1] = y;
+	data[2] = width;
+	data[3] = height;
+
+	XChangeProperty (dpy,
+			 GDK_WINDOW_XID (window),
+			 gdk_x11_get_xatom_by_name_for_display (gdk_drawable_get_display (window),
+								"_NET_WM_ICON_GEOMETRY"),
+			 XA_CARDINAL, 32, PropModeReplace,
+			 (guchar *)&data, 4);
+}
+
+static gboolean
+hide_main_window (GtkWidget *window)
+{
+	GDK_THREADS_ENTER ();
+
+	gtk_widget_hide (window);
+	g_object_unref (window);
+
+	GDK_THREADS_LEAVE ();
+
+	return FALSE;
+}
+
+static void
+cancel_hide_main_window (RBStatusIconPlugin *plugin)
+{
+	/* FIXME - see below */
+	if (plugin->priv->hide_main_window_id > 0)
+		g_source_remove (plugin->priv->hide_main_window_id);
+	plugin->priv->hide_main_window_id = 0;
+}
+
+static void
+close_to_tray (RBStatusIconPlugin *plugin)
+{
+	int x, y, width, height;
+	GtkWindow *window;
+
+	cancel_hide_main_window (plugin);
+
+	g_object_get (plugin->priv->shell, "window", &window, NULL);
+
+	/* set the window's icon geometry to match the icon */
+	rb_tray_icon_get_geom (plugin->priv->tray_icon,
+			       &x, &y, &width, &height);
+	if (GTK_WIDGET_REALIZED (window))
+		set_icon_geometry (GTK_WIDGET (window)->window,
+				   x, y, width, height);
+
+	/* ask the tasklist not to show our window */
+	gtk_window_set_skip_taskbar_hint (window, TRUE);
+
+	/* FIXME - this is horribly evil racy workaround for a
+	 * current bug in the tasklist not noticing our hint
+	 * change
+	 */
+	plugin->priv->hide_main_window_id =
+		g_timeout_add (250, (GSourceFunc) hide_main_window, g_object_ref (window));
+}
+
+static gboolean
+visibility_changing_cb (RBShell *shell,
+			gboolean initial,
+			gboolean visible,
+			RBStatusIconPlugin *plugin)
+{
+
+	switch (plugin->priv->icon_mode) {
+	case ICON_NEVER:
+	case ICON_WITH_NOTIFY:
+	case ICON_ALWAYS:
+		return visible;
+
+	case ICON_OWNS_WINDOW:
+		/* complicated stuff below */
+		break;
+
+	default:
+		g_assert_not_reached ();
+	}
+
+	if (initial) {
+		/* restore visibility from gconf setting */
+		visible = eel_gconf_get_boolean (CONF_WINDOW_VISIBILITY);
+		rb_debug ("setting initial visibility %d from gconf", visible);
+		return visible;
+	}
+
+	cancel_hide_main_window (plugin);
+
+	if (visible) {
+		GtkWindow *window;
+
+		g_object_get (shell, "window", &window, NULL);
+		gtk_window_set_skip_taskbar_hint (window, FALSE);
+		g_object_unref (window);
+	} else {
+		/* don't allow the window to be hidden if the icon is not embedded */
+		if (rb_tray_icon_is_embedded (plugin->priv->tray_icon) == FALSE) {
+			rb_debug ("status icon is not embedded, disallowing visibility change");
+			visible = TRUE;
+		} else {
+			close_to_tray (plugin);
+		}
+	}
+
+	return visible;
+}
+
+static gboolean
+window_delete_event_cb (GtkWindow *window, GdkEvent *event, RBStatusIconPlugin *plugin)
+{
+
+	switch (plugin->priv->icon_mode) {
+	case ICON_NEVER:
+	case ICON_WITH_NOTIFY:
+	case ICON_ALWAYS:
+		return FALSE;
+
+	case ICON_OWNS_WINDOW:
+		rb_debug ("window deleted, but let's just hide it instead");
+		close_to_tray (plugin);
+		gtk_window_iconify (window);
+		return TRUE;
+
+	default:
+		g_assert_not_reached ();
+	}
+}
+
+static void
+store_window_visibility (RBStatusIconPlugin *plugin)
+{
+	/* if in icon-owns-window mode, store current visibility in gconf */
+	if (plugin->priv->icon_mode == ICON_OWNS_WINDOW) {
+		gboolean visible;
+
+		g_object_get (plugin->priv->shell, "visibility", &visible, NULL);
+		eel_gconf_set_boolean (CONF_WINDOW_VISIBILITY, visible);
+	}
+}
+
+#if !defined(USE_GTK_STATUS_ICON)
+
+/* EggTrayIcon helpers */
+
+#if 0
+static void
+tray_embedded_cb (GtkPlug *plug,
+		  gpointer data)
+{
+	/* FIXME - this doesn't work */
+	RBShell *shell = RB_SHELL (data);
+
+	rb_debug ("got embedded signal");
+
+	gdk_window_set_decorations (shell->priv->window->window,
+				    GDK_DECOR_ALL | GDK_DECOR_MINIMIZE | GDK_DECOR_MAXIMIZE);
+}
+#endif
+
+static gboolean
+tray_destroy_cb (GtkObject *object, RBStatusIconPlugin *plugin)
+{
+	if (plugin->priv->tray_icon) {
+		rb_debug ("caught destroy event for icon %p", object);
+		g_object_ref_sink (object);
+		plugin->priv->tray_icon = NULL;
+		rb_debug ("finished sinking tray");
+	}
+
+	rb_debug ("creating new icon");
+	plugin->priv->tray_icon = rb_tray_icon_new (plugin, plugin->priv->shell);
+	g_signal_connect_object (plugin->priv->tray_icon, "destroy", G_CALLBACK (tray_destroy_cb), plugin, 0);
+	/* g_signal_connect_object (plugin->priv->tray_icon, "embedded", G_CALLBACK (tray_embedded_cb), plugin, 0); */
+
+	rb_debug ("done creating new icon %p", plugin->priv->tray_icon);
+	return TRUE;
+}
+
+static void
+cleanup_status_icon (RBStatusIconPlugin *plugin)
+{
+	g_signal_handlers_disconnect_by_func (plugin->priv->tray_icon,
+					      G_CALLBACK (tray_destroy_cb),
+					      plugin);
+
+	gtk_widget_hide_all (GTK_WIDGET (plugin->priv->tray_icon));
+	gtk_widget_destroy (GTK_WIDGET (plugin->priv->tray_icon));
+}
+
+static void
+create_status_icon (RBStatusIconPlugin *plugin)
+{
+	tray_destroy_cb (NULL, plugin);
+}
+
+#else
+
+/* boring equivalents for GtkStatusIcon */
+
+static void
+create_status_icon (RBStatusIconPlugin *plugin)
+{
+	plugin->priv->tray_icon = rb_tray_icon_new (plugin, plugin->priv->shell_player);
+}
+
+static void
+cleanup_status_icon (RBStatusIconPlugin *plugin)
+{
+	g_object_unref (plugin->priv->tray_icon);
+}
+
+
+#endif
+
+/* preferences dialog and gconf stuff */
+
+static void
+notification_config_changed_cb (GtkComboBox *widget, RBStatusIconPlugin *plugin)
+{
+	eel_gconf_set_integer (CONF_NOTIFICATION_MODE, gtk_combo_box_get_active (widget));
+}
+
+static void
+status_icon_config_changed_cb (GtkComboBox *widget, RBStatusIconPlugin *plugin)
+{
+	eel_gconf_set_integer (CONF_STATUS_ICON_MODE, gtk_combo_box_get_active (widget));
+}
+
+static void
+config_response_cb (GtkWidget *dialog, gint response, RBStatusIconPlugin *plugin)
+{
+	gtk_widget_hide (dialog);
+}
+
+static gboolean
+should_upgrade (const char *from, const char *to)
+{
+	return (eel_gconf_is_default (to) && (eel_gconf_is_default (from) == FALSE));
+}
+
+static void
+maybe_upgrade_preferences (RBStatusIconPlugin *plugin)
+{
+	/* dontreallyclose plugin enabled -> icon owns window mode, otherwise, icon always visible */
+	if (should_upgrade (CONF_OLD_ICON_MODE, CONF_STATUS_ICON_MODE)) {
+		int new_mode = eel_gconf_get_boolean (CONF_OLD_ICON_MODE) ? ICON_OWNS_WINDOW : ICON_ALWAYS;
+		rb_debug ("using old gconf key " CONF_OLD_ICON_MODE " to set icon mode to %d", new_mode);
+		eel_gconf_set_integer (CONF_STATUS_ICON_MODE, new_mode);
+	}
+
+	/* old show_notifications key maps to hidden mode if true, never if false */
+	if (should_upgrade (CONF_OLD_NOTIFICATIONS, CONF_NOTIFICATION_MODE)) {
+		int new_mode = eel_gconf_get_boolean (CONF_OLD_NOTIFICATIONS) ? NOTIFY_HIDDEN : NOTIFY_NEVER;
+		rb_debug ("using old gconf key " CONF_OLD_NOTIFICATIONS  " to set notify mode to %d", new_mode);
+		eel_gconf_set_integer (CONF_NOTIFICATION_MODE, new_mode);
+	}
+
+	/* apply old window visibility key */
+	if (should_upgrade (CONF_OLD_VISIBILITY, CONF_WINDOW_VISIBILITY)) {
+		gboolean visible = eel_gconf_get_boolean (CONF_OLD_VISIBILITY);
+		rb_debug ("using old gconf key " CONF_OLD_VISIBILITY  " to set window visibility to %d", visible);
+		eel_gconf_set_boolean (CONF_WINDOW_VISIBILITY, visible);
+	}
+}
+
+static void
+config_notify_cb (GConfClient *client, guint connection_id, GConfEntry *entry, RBStatusIconPlugin *plugin)
+{
+	if (g_str_equal (gconf_entry_get_key (entry), CONF_STATUS_ICON_MODE)) {
+
+		plugin->priv->icon_mode = gconf_value_get_int (gconf_entry_get_value (entry));
+		rb_debug ("icon mode changed to %d", plugin->priv->icon_mode);
+
+		update_status_icon_visibility (plugin, FALSE);	/* maybe should remember if we're notifying.. */
+		sync_actions (plugin);
+
+	} else if (g_str_equal (gconf_entry_get_key (entry), CONF_NOTIFICATION_MODE)) {
+		plugin->priv->notify_mode = gconf_value_get_int (gconf_entry_get_value (entry));
+		rb_debug ("notify mode changed to %d", plugin->priv->notify_mode);
+	}
+}
+
+
+/* plugin infrastructure */
+
+static void
+boldify_label (GtkBuilder *builder, const char *name)
+{
+	GtkWidget *label;
+	char *bolded;
+
+	label = GTK_WIDGET (gtk_builder_get_object (builder, name));
+	if (label == NULL) {
+		g_warning ("widget '%s' not found", name);
+		return;
+	}
+
+	bolded = g_strdup_printf ("<b>%s</b>", gtk_label_get_label (GTK_LABEL (label)));
+	gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), bolded);
+	g_free (bolded);
+}
+
+static GtkWidget *
+impl_get_config_widget (RBPlugin *bplugin)
+{
+	RBStatusIconPlugin *plugin;
+	GtkBuilder *builder;
+	GtkComboBox *icon_combo;
+	GtkComboBox *notify_combo;
+	char *builderfile;
+	GError *error = NULL;
+
+	plugin = RB_STATUS_ICON_PLUGIN (bplugin);
+	if (plugin->priv->config_dialog != NULL) {
+		gtk_widget_show_all (plugin->priv->config_dialog);
+		return plugin->priv->config_dialog;
+	}
+
+	builderfile = rb_plugin_find_file (bplugin, "status-icon-preferences.ui");
+	if (builderfile == NULL) {
+		g_warning ("can't find status-icon-preferences.ui");
+		return NULL;
+	}
+
+	builder = gtk_builder_new ();
+	gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE);
+	if (gtk_builder_add_from_file (builder, builderfile, &error) == FALSE) {
+		g_warning ("error loading %s: %s", builderfile, error->message);
+		g_free (builderfile);
+		g_error_free (error);
+		return NULL;
+	}
+
+	g_free (builderfile);
+
+	boldify_label (builder, "headerlabel");
+
+	plugin->priv->config_dialog = GTK_WIDGET (gtk_builder_get_object (builder, "statusiconpreferences"));
+	gtk_widget_hide_on_delete (plugin->priv->config_dialog);
+
+	/* connect signals and stuff */
+	g_signal_connect_object (plugin->priv->config_dialog, "response", G_CALLBACK (config_response_cb), plugin, 0);
+
+	icon_combo = GTK_COMBO_BOX (gtk_builder_get_object (builder, "statusiconmode"));
+	notify_combo = GTK_COMBO_BOX (gtk_builder_get_object (builder, "notificationmode"));
+	g_signal_connect_object (notify_combo,
+				 "changed",
+				 G_CALLBACK (notification_config_changed_cb),
+				 plugin, 0);
+	g_signal_connect_object (icon_combo,
+				 "changed",
+				 G_CALLBACK (status_icon_config_changed_cb),
+				 plugin, 0);
+	gtk_combo_box_set_active (notify_combo, plugin->priv->notify_mode);
+	gtk_combo_box_set_active (icon_combo, plugin->priv->icon_mode);
+
+	g_object_unref (builder);
+	return plugin->priv->config_dialog;
+}
+
+static void
+impl_activate (RBPlugin *bplugin,
+	       RBShell *shell)
+{
+	RBStatusIconPlugin *plugin;
+	GtkUIManager *ui_manager;
+	RhythmDBEntry *entry;
+	GtkWindow *window;
+	char *uifile;
+
+	rb_debug ("activating status icon plugin");
+
+	plugin = RB_STATUS_ICON_PLUGIN (bplugin);
+	g_object_get (shell,
+		      "shell-player", &plugin->priv->shell_player,
+		      "db", &plugin->priv->db,
+		      "ui-manager", &ui_manager,
+		      "window", &window,
+		      NULL);
+	plugin->priv->shell = g_object_ref (shell);
+
+	/* create action group for the tray menu */
+	plugin->priv->action_group = gtk_action_group_new ("StatusIconActions");
+	gtk_action_group_set_translation_domain (plugin->priv->action_group, GETTEXT_PACKAGE);
+	gtk_action_group_add_actions (plugin->priv->action_group,
+				      rb_status_icon_plugin_actions,
+				      G_N_ELEMENTS (rb_status_icon_plugin_actions),
+				      plugin);
+	gtk_action_group_add_toggle_actions (plugin->priv->action_group,
+					     rb_status_icon_plugin_toggle_entries,
+					     G_N_ELEMENTS (rb_status_icon_plugin_toggle_entries),
+					     plugin);
+	sync_actions (plugin);
+
+	gtk_ui_manager_insert_action_group (ui_manager, plugin->priv->action_group, 0);
+
+	/* add icon menu UI */
+	uifile = rb_plugin_find_file (bplugin, "status-icon-ui.xml");
+	if (uifile != NULL) {
+		plugin->priv->ui_merge_id = gtk_ui_manager_add_ui_from_file (ui_manager, uifile, NULL);
+		g_free (uifile);
+	}
+
+	/* connect various things */
+	g_signal_connect_object (plugin->priv->shell, "visibility-changed", G_CALLBACK (visibility_changed_cb), plugin, 0);
+	g_signal_connect_object (plugin->priv->shell, "visibility-changing", G_CALLBACK (visibility_changing_cb), plugin, G_CONNECT_AFTER);
+	g_signal_connect_object (plugin->priv->shell, "notify-playing-entry", G_CALLBACK (shell_notify_playing_cb), plugin, 0);
+	g_signal_connect_object (plugin->priv->shell, "notify-custom", G_CALLBACK (shell_notify_custom_cb), plugin, 0);
+
+	g_signal_connect_object (plugin->priv->shell_player, "playing-song-changed", G_CALLBACK (playing_entry_changed_cb), plugin, 0);
+	g_signal_connect_object (plugin->priv->shell_player, "elapsed-changed", G_CALLBACK (elapsed_changed_cb), plugin, 0);
+
+	g_signal_connect_object (plugin->priv->db, "entry_extra_metadata_notify::" RHYTHMDB_PROP_COVER_ART,
+				 G_CALLBACK (db_art_metadata_cb), plugin, 0);
+	g_signal_connect_object (plugin->priv->db, "entry_extra_metadata_notify::" RHYTHMDB_PROP_STREAM_SONG_TITLE,
+				 G_CALLBACK (db_stream_metadata_cb), plugin, 0);
+	g_signal_connect_object (plugin->priv->db, "entry_extra_metadata_notify::" RHYTHMDB_PROP_STREAM_SONG_ARTIST,
+				 G_CALLBACK (db_stream_metadata_cb), plugin, 0);
+	g_signal_connect_object (plugin->priv->db, "entry_extra_metadata_notify::" RHYTHMDB_PROP_STREAM_SONG_ALBUM,
+				 G_CALLBACK (db_stream_metadata_cb), plugin, 0);
+
+	g_signal_connect_object (window, "delete-event", G_CALLBACK (window_delete_event_cb), plugin, 0);
+
+	/* read config */
+	eel_gconf_monitor_add (CONF_PLUGIN_SETTINGS);
+	plugin->priv->gconf_notify_id =
+		eel_gconf_notification_add (CONF_PLUGIN_SETTINGS,
+					    (GConfClientNotifyFunc) config_notify_cb,
+					    plugin);
+
+	maybe_upgrade_preferences (plugin);
+
+	plugin->priv->icon_mode = eel_gconf_get_integer (CONF_STATUS_ICON_MODE);
+	plugin->priv->notify_mode = eel_gconf_get_integer (CONF_NOTIFICATION_MODE);
+
+	/* create status icon */
+	create_status_icon (plugin);
+	update_status_icon_visibility (plugin, FALSE);
+
+	/* update everything in case we're already playing something */
+	entry = rb_shell_player_get_playing_entry (plugin->priv->shell_player);
+	if (entry != NULL) {
+		update_current_playing_data (plugin, entry);
+		rhythmdb_entry_unref (entry);
+	}
+	update_tooltip (plugin);
+
+	g_object_unref (ui_manager);
+	g_object_unref (window);
+}
+
+static void
+impl_deactivate	(RBPlugin *bplugin,
+		 RBShell *shell)
+{
+	RBStatusIconPlugin *plugin;
+	GtkUIManager *ui_manager;
+	GtkWindow *window;
+
+	plugin = RB_STATUS_ICON_PLUGIN (bplugin);
+	g_object_get (plugin->priv->shell, "ui-manager", &ui_manager, NULL);
+
+	store_window_visibility (plugin);
+
+	/* stop watching for config changes */
+	if (plugin->priv->gconf_notify_id != 0) {
+		eel_gconf_notification_remove (plugin->priv->gconf_notify_id);
+		eel_gconf_monitor_remove (CONF_PLUGIN_SETTINGS);
+		plugin->priv->gconf_notify_id = 0;
+	}
+
+	/* remove UI bits */
+	if (plugin->priv->ui_merge_id != 0) {
+		gtk_ui_manager_remove_ui (ui_manager, plugin->priv->ui_merge_id);
+		plugin->priv->ui_merge_id = 0;
+	}
+
+	if (plugin->priv->action_group != NULL) {
+		gtk_ui_manager_remove_action_group (ui_manager, plugin->priv->action_group);
+
+		g_object_unref (plugin->priv->action_group);
+		plugin->priv->action_group = NULL;
+	}
+
+	/* remove notification popups */
+	cleanup_notification (plugin);
+
+	/* remove icon */
+	if (plugin->priv->tray_icon != NULL) {
+		cleanup_status_icon (plugin);
+		plugin->priv->tray_icon = NULL;
+	}
+
+	/* disconnect signal handlers used to update the icon */
+	if (plugin->priv->shell_player != NULL) {
+		g_signal_handlers_disconnect_by_func (plugin->priv->shell_player, playing_entry_changed_cb, plugin);
+		g_signal_handlers_disconnect_by_func (plugin->priv->shell_player, elapsed_changed_cb, plugin);
+
+		g_object_unref (plugin->priv->shell_player);
+		plugin->priv->shell_player = NULL;
+	}
+
+	if (plugin->priv->db != NULL) {
+		g_signal_handlers_disconnect_by_func (plugin->priv->db, db_art_metadata_cb, plugin);
+		g_signal_handlers_disconnect_by_func (plugin->priv->db, db_stream_metadata_cb, plugin);
+
+		g_object_unref (plugin->priv->db);
+		plugin->priv->db = NULL;
+	}
+
+	if (plugin->priv->config_dialog != NULL) {
+		gtk_widget_destroy (plugin->priv->config_dialog);
+		plugin->priv->config_dialog = NULL;
+	}
+
+	g_object_unref (ui_manager);
+
+	g_object_get (plugin->priv->shell, "window", &window, NULL);
+	g_signal_handlers_disconnect_by_func (window, window_delete_event_cb, plugin);
+	g_object_unref (window);
+
+	g_signal_handlers_disconnect_by_func (plugin->priv->shell, visibility_changed_cb, plugin);
+	g_signal_handlers_disconnect_by_func (plugin->priv->shell, visibility_changing_cb, plugin);
+	g_signal_handlers_disconnect_by_func (plugin->priv->shell, shell_notify_playing_cb, plugin);
+	g_signal_handlers_disconnect_by_func (plugin->priv->shell, shell_notify_custom_cb, plugin);
+	g_object_unref (plugin->priv->shell);
+	plugin->priv->shell = NULL;
+
+	/* forget what's playing */
+	g_free (plugin->priv->current_title);
+	g_free (plugin->priv->current_album_and_artist);
+	g_free (plugin->priv->tooltip_markup);
+	plugin->priv->current_title = NULL;
+	plugin->priv->current_album_and_artist = NULL;
+	plugin->priv->tooltip_markup = NULL;
+
+	forget_pixbufs (plugin);
+}
+
+static void
+rb_status_icon_plugin_init (RBStatusIconPlugin *plugin)
+{
+	GtkIconTheme *theme;
+	gint size;
+
+	rb_debug ("RBStatusIconPlugin initialising");
+
+	plugin->priv = G_TYPE_INSTANCE_GET_PRIVATE (plugin,
+						    RB_TYPE_STATUS_ICON_PLUGIN,
+						    RBStatusIconPluginPrivate);
+
+	theme = gtk_icon_theme_get_default ();
+
+	gtk_icon_size_lookup (GTK_ICON_SIZE_DIALOG, &size, NULL);
+	plugin->priv->tooltip_app_pixbuf = gtk_icon_theme_load_icon (theme, RB_APP_ICON, size, 0, NULL);
+}
+
+
+static void
+rb_status_icon_plugin_class_init (RBStatusIconPluginClass *klass)
+{
+	RBPluginClass *plugin_class = RB_PLUGIN_CLASS (klass);
+
+	plugin_class->activate = impl_activate;
+	plugin_class->deactivate = impl_deactivate;
+
+	plugin_class->create_configure_dialog = impl_get_config_widget;
+
+	g_type_class_add_private (klass, sizeof (RBStatusIconPluginPrivate));
+}
+
diff --git a/plugins/status-icon/rb-status-icon-plugin.h b/plugins/status-icon/rb-status-icon-plugin.h
new file mode 100644
index 0000000..8472c15
--- /dev/null
+++ b/plugins/status-icon/rb-status-icon-plugin.h
@@ -0,0 +1,78 @@
+/*
+ *  Copyright (C) 2009 Jonathan Matthew  <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef __RB_STATUS_ICON_PLUGIN_H
+#define __RB_STATUS_ICON_PLUGIN_H
+
+#include "rb-plugin.h"
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_STATUS_ICON_PLUGIN         (rb_status_icon_plugin_get_type ())
+#define RB_STATUS_ICON_PLUGIN(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_STATUS_ICON_PLUGIN, RBStatusIconPlugin))
+#define RB_STATUS_ICON_PLUGIN_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_STATUS_ICON_PLUGIN, RBStatusIconPluginClass))
+#define RB_IS_STATUS_ICON_PLUGIN(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_STATUS_ICON_PLUGIN))
+#define RB_IS_STATUS_ICON_PLUGIN_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_STATUS_ICON_PLUGIN))
+#define RB_STATUS_ICON_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_STATUS_ICON_PLUGIN, RBStatusIconPluginClass))
+
+typedef struct _RBStatusIconPlugin RBStatusIconPlugin;
+typedef struct _RBStatusIconPluginClass RBStatusIconPluginClass;
+typedef struct _RBStatusIconPluginPrivate RBStatusIconPluginPrivate;
+
+struct _RBStatusIconPlugin
+{
+	RBPlugin parent;
+
+	RBStatusIconPluginPrivate *priv;
+};
+
+struct _RBStatusIconPluginClass
+{
+	RBPluginClass parent;
+};
+
+GType		rb_status_icon_plugin_get_type		(void);
+
+/* methods for icon implementations to call */
+
+void		rb_status_icon_plugin_scroll_event	(RBStatusIconPlugin *plugin,
+							 GdkEventScroll *event);
+
+void		rb_status_icon_plugin_button_press_event (RBStatusIconPlugin *plugin,
+							 GdkEventButton *event);
+
+gboolean	rb_status_icon_plugin_set_tooltip	(GtkWidget *widget,
+							 gint x,
+							 gint y,
+							 gboolean keyboard_tooltip,
+							 GtkTooltip *tooltip,
+							 RBStatusIconPlugin *plugin);
+
+G_END_DECLS
+
+#endif /* __RB_STATUS_ICON_PLUGIN_H */
+
diff --git a/plugins/status-icon/rb-tray-icon-gtk.c b/plugins/status-icon/rb-tray-icon-gtk.c
new file mode 100644
index 0000000..ab7922b
--- /dev/null
+++ b/plugins/status-icon/rb-tray-icon-gtk.c
@@ -0,0 +1,359 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2009 Jonathan Matthew  <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include <config.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "rb-tray-icon-gtk.h"
+#include "rb-stock-icons.h"
+#include "rb-debug.h"
+#include "rb-shell-player.h"
+#include "rb-util.h"
+
+
+
+/**
+ * SECTION:rb-tray-icon
+ * @short_description: Notification area icon
+ *
+ * The tray icon handles a few different forms of input:
+ * <itemizedlist>
+ *   <listitem>left clicking hides and shows the main window</listitem>
+ *   <listitem>right clicking brings up a popup menu</listitem>
+ *   <listitem>scroll events change the playback volume</listitem>
+ * </itemizedlist>
+ *
+ * The tooltip for the tray icon consists of an image, the primary text
+ * (displayed in bold large type), and the secondary text (which can
+ * contain markup).
+ */
+
+static void rb_tray_icon_class_init (RBTrayIconClass *klass);
+static void rb_tray_icon_init (RBTrayIcon *tray);
+static GObject *rb_tray_icon_constructor (GType type, guint n_construct_properties,
+					  GObjectConstructParam *construct_properties);
+static void rb_tray_icon_dispose (GObject *object);
+static void rb_tray_icon_set_property (GObject *object,
+					  guint prop_id,
+					  const GValue *value,
+					  GParamSpec *pspec);
+static void rb_tray_icon_get_property (GObject *object,
+					  guint prop_id,
+					  GValue *value,
+					  GParamSpec *pspec);
+static void rb_tray_icon_button_press_event_cb (GtkStatusIcon *status_icon,
+						GdkEventButton *event,
+						RBTrayIcon *tray);
+static void rb_tray_icon_scroll_event_cb (GtkStatusIcon *status_icon,
+					  GdkEventScroll *event,
+					  RBTrayIcon *tray);
+static void rb_tray_icon_playing_changed_cb (RBShellPlayer *player,
+					     gboolean playing,
+					     RBTrayIcon *tray);
+
+struct _RBTrayIconPrivate
+{
+	RBStatusIconPlugin *plugin;
+
+	GtkStatusIcon *icon;
+
+	RBShellPlayer *shell_player;
+};
+
+enum
+{
+	PROP_0,
+	PROP_PLUGIN,
+	PROP_SHELL_PLAYER
+};
+
+enum
+{
+	LAST_SIGNAL,
+};
+
+G_DEFINE_TYPE (RBTrayIcon, rb_tray_icon, G_TYPE_OBJECT)
+
+static void
+rb_tray_icon_class_init (RBTrayIconClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = rb_tray_icon_dispose;
+	object_class->constructor = rb_tray_icon_constructor;
+
+	object_class->set_property = rb_tray_icon_set_property;
+	object_class->get_property = rb_tray_icon_get_property;
+
+	/**
+	 * RBTrayIcon:plugin:
+	 *
+	 * #RBStatusIconPlugin instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_PLUGIN,
+					 g_param_spec_object ("plugin",
+							      "RBStatusIconPlugin",
+							      "RBStatusIconPlugin object",
+							      RB_TYPE_STATUS_ICON_PLUGIN,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBTrayIcon:shell-player:
+	 *
+	 * #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_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_type_class_add_private (klass, sizeof (RBTrayIconPrivate));
+}
+
+static void
+rb_tray_icon_init (RBTrayIcon *tray)
+{
+	rb_debug ("setting up tray icon");
+
+	tray->priv = G_TYPE_INSTANCE_GET_PRIVATE ((tray), RB_TYPE_TRAY_ICON, RBTrayIconPrivate);
+
+	tray->priv->icon = gtk_status_icon_new_from_icon_name (RB_STOCK_TRAY_ICON_NOT_PLAYING);
+	gtk_status_icon_set_visible (tray->priv->icon, FALSE);
+
+	g_signal_connect_object (tray->priv->icon, "button-press-event",
+				 G_CALLBACK (rb_tray_icon_button_press_event_cb),
+				 tray, 0);
+	g_signal_connect_object (tray->priv->icon,
+				 "scroll_event",
+				 G_CALLBACK (rb_tray_icon_scroll_event_cb),
+				 tray, 0);
+
+}
+
+static GObject *
+rb_tray_icon_constructor (GType type, guint n_construct_properties,
+			  GObjectConstructParam *construct_properties)
+{
+	RBTrayIcon *tray;
+	RBTrayIconClass *klass;
+
+	klass = RB_TRAY_ICON_CLASS (g_type_class_peek (RB_TYPE_TRAY_ICON));
+
+	tray = RB_TRAY_ICON (G_OBJECT_CLASS (rb_tray_icon_parent_class)->constructor
+				(type, n_construct_properties,
+				 construct_properties));
+
+	g_signal_connect_object (tray->priv->shell_player,
+				 "playing-changed",
+				 G_CALLBACK (rb_tray_icon_playing_changed_cb),
+				 tray, 0);
+
+	gtk_status_icon_set_has_tooltip (tray->priv->icon, TRUE);
+	g_signal_connect_object (tray->priv->icon, "query-tooltip",
+				 G_CALLBACK (rb_status_icon_plugin_set_tooltip),
+				 tray->priv->plugin, 0);
+
+	return G_OBJECT (tray);
+}
+
+static void
+rb_tray_icon_dispose (GObject *object)
+{
+	RBTrayIcon *tray;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (RB_IS_TRAY_ICON (object));
+
+	tray = RB_TRAY_ICON (object);
+
+	g_return_if_fail (tray->priv != NULL);
+
+	if (tray->priv->icon != NULL) {
+		g_object_unref (tray->priv->icon);
+		tray->priv->icon = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_tray_icon_parent_class)->dispose (object);
+}
+
+static void
+rb_tray_icon_playing_changed_cb (RBShellPlayer *player, gboolean playing, RBTrayIcon *tray)
+{
+	const char *icon_name;
+
+	icon_name = playing ? RB_STOCK_TRAY_ICON_PLAYING : RB_STOCK_TRAY_ICON_NOT_PLAYING;
+	gtk_status_icon_set_from_icon_name (tray->priv->icon, icon_name);
+}
+
+static void
+rb_tray_icon_set_property (GObject *object,
+			   guint prop_id,
+			   const GValue *value,
+			   GParamSpec *pspec)
+{
+	RBTrayIcon *tray = RB_TRAY_ICON (object);
+
+	switch (prop_id)
+	{
+	case PROP_SHELL_PLAYER:
+		tray->priv->shell_player = g_value_get_object (value);
+		break;
+	case PROP_PLUGIN:
+		tray->priv->plugin = g_value_get_object (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+rb_tray_icon_get_property (GObject *object,
+			      guint prop_id,
+			      GValue *value,
+			      GParamSpec *pspec)
+{
+	RBTrayIcon *tray = RB_TRAY_ICON (object);
+
+	switch (prop_id)
+	{
+	case PROP_SHELL_PLAYER:
+		g_value_set_object (value, tray->priv->shell_player);
+		break;
+	case PROP_PLUGIN:
+		g_value_set_object (value, tray->priv->plugin);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/**
+ * rb_tray_icon_new:
+ * @plugin: the #RBStatusIconPlugin
+ * @shell_player: the #RBShellPlayer
+ *
+ * Return value: the #RBTrayIcon
+ */
+RBTrayIcon *
+rb_tray_icon_new (RBStatusIconPlugin *plugin,
+		  RBShellPlayer *shell_player)
+{
+	return g_object_new (RB_TYPE_TRAY_ICON,
+			     "plugin", plugin,
+			     "shell-player", shell_player,
+			     NULL);
+}
+
+static void
+rb_tray_icon_button_press_event_cb (GtkStatusIcon *status_icon,
+				    GdkEventButton *event,
+				    RBTrayIcon *tray)
+{
+	rb_status_icon_plugin_button_press_event (tray->priv->plugin, event);
+}
+
+static void
+rb_tray_icon_scroll_event_cb (GtkStatusIcon *status_icon,
+			      GdkEventScroll *event,
+			      RBTrayIcon *tray)
+{
+	rb_status_icon_plugin_scroll_event (tray->priv->plugin, event);
+}
+
+void
+rb_tray_icon_menu_popup (RBTrayIcon *tray, GtkWidget *popup, gint button)
+{
+	gtk_menu_set_screen (GTK_MENU (popup), gtk_status_icon_get_screen (tray->priv->icon));
+	gtk_menu_popup (GTK_MENU (popup), NULL, NULL,
+			gtk_status_icon_position_menu, tray->priv->icon, button,
+			gtk_get_current_event_time ());
+}
+
+/**
+ * rb_tray_icon_get_geom:
+ * @icon: the #RBTrayIcon
+ * @x: returns the x position of the tray icon
+ * @y: returns the y position of the tray icon
+ * @width: returns the width of the tray icon
+ * @height: returns the height of the tray icon
+ *
+ * Retrieves the current position and size of the tray icon.
+ */
+void
+rb_tray_icon_get_geom (RBTrayIcon *tray, int *x, int *y, int *width, int *height)
+{
+	GdkRectangle area;
+
+	if (gtk_status_icon_get_geometry (tray->priv->icon, NULL, &area, NULL)) {
+		*x = area.x;
+		*y = area.y;
+		*width = area.width;
+		*height = area.height;
+	}
+}
+
+void
+rb_tray_icon_trigger_tooltip_query (RBTrayIcon *tray)
+{
+	GdkScreen *screen;
+	GdkDisplay *display;
+
+	screen = gtk_status_icon_get_screen (tray->priv->icon);
+	display = gdk_screen_get_display (screen);
+	gtk_tooltip_trigger_tooltip_query (display);
+}
+
+
+gboolean
+rb_tray_icon_is_embedded (RBTrayIcon *tray)
+{
+	return gtk_status_icon_is_embedded (tray->priv->icon);
+}
+
+void
+rb_tray_icon_attach_notification (RBTrayIcon *tray, NotifyNotification *notification)
+{
+	notify_notification_attach_to_status_icon (notification, tray->priv->icon);
+}
+
+void
+rb_tray_icon_set_visible (RBTrayIcon *tray, gboolean visible)
+{
+	gtk_status_icon_set_visible (tray->priv->icon, visible);
+}
diff --git a/plugins/status-icon/rb-tray-icon-gtk.h b/plugins/status-icon/rb-tray-icon-gtk.h
new file mode 100644
index 0000000..730f34d
--- /dev/null
+++ b/plugins/status-icon/rb-tray-icon-gtk.h
@@ -0,0 +1,85 @@
+/*
+ *  Copyright (C) 2003 Colin Walters <walters verbum org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include <gtk/gtk.h>
+#include "rb-status-icon-plugin.h"
+#include "rb-shell-player.h"
+
+#if defined(HAVE_NOTIFY)
+#include <libnotify/notify.h>
+#endif
+
+#ifndef __RB_TRAY_ICON_GTK_H
+#define __RB_TRAY_ICON_GTK_H
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_TRAY_ICON         (rb_tray_icon_get_type ())
+#define RB_TRAY_ICON(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_TRAY_ICON, RBTrayIcon))
+#define RB_TRAY_ICON_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_TRAY_ICON, RBTrayIconClass))
+#define RB_IS_TRAY_ICON(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_TRAY_ICON))
+#define RB_IS_TRAY_ICON_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_TRAY_ICON))
+#define RB_TRAY_ICON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_TRAY_ICON, RBTrayIconClass))
+
+typedef struct _RBTrayIcon RBTrayIcon;
+typedef struct _RBTrayIconClass RBTrayIconClass;
+
+typedef struct _RBTrayIconPrivate RBTrayIconPrivate;
+
+struct _RBTrayIcon
+{
+	GObject parent;
+
+	RBTrayIconPrivate *priv;
+};
+
+struct _RBTrayIconClass
+{
+	GObjectClass parent_class;
+};
+
+GType			rb_tray_icon_get_type	(void);
+
+RBTrayIcon *		rb_tray_icon_new	(RBStatusIconPlugin *plugin, RBShellPlayer *shell_player);
+
+void                    rb_tray_icon_get_geom   (RBTrayIcon *icon, int *x, int *y, int *width, int *height);
+
+void 			rb_tray_icon_trigger_tooltip_query (RBTrayIcon *icon);
+
+gboolean		rb_tray_icon_is_embedded (RBTrayIcon *icon);
+
+void			rb_tray_icon_menu_popup (RBTrayIcon *icon, GtkWidget *popup, gint button);
+
+#if defined(HAVE_NOTIFY)
+void			rb_tray_icon_attach_notification (RBTrayIcon *icon, NotifyNotification *notification);
+#endif
+
+void			rb_tray_icon_set_visible (RBTrayIcon *icon, gboolean visible);
+
+G_END_DECLS
+
+#endif /* __RB_TRAY_ICON_GTK_H */
diff --git a/plugins/status-icon/rb-tray-icon.c b/plugins/status-icon/rb-tray-icon.c
new file mode 100644
index 0000000..48367f1
--- /dev/null
+++ b/plugins/status-icon/rb-tray-icon.c
@@ -0,0 +1,487 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  arch-tag: Implementation of Rhythmbox tray icon object
+ *
+ *  Copyright (C) 2003,2004 Colin Walters <walters redhat com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include <config.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "rb-tray-icon.h"
+#include "rb-stock-icons.h"
+#include "rb-debug.h"
+#include "rb-shell.h"
+#include "rb-shell-player.h"
+#include "rb-util.h"
+
+
+
+/**
+ * SECTION:rb-tray-icon
+ * @short_description: Notification area icon
+ *
+ * The tray icon handles a few different forms of input:
+ * <itemizedlist>
+ *   <listitem>left clicking hides and shows the main window</listitem>
+ *   <listitem>right clicking brings up a popup menu</listitem>
+ *   <listitem>dropping files on the icon adds them to the library</listitem>
+ *   <listitem>scroll events change the playback volume</listitem>
+ * </itemizedlist>
+ *
+ * The tooltip for the tray icon consists of an image, the primary text
+ * (displayed in bold large type), and the secondary text (which can
+ * contain markup).
+ */
+
+static void rb_tray_icon_class_init (RBTrayIconClass *klass);
+static void rb_tray_icon_init (RBTrayIcon *icon);
+static GObject *rb_tray_icon_constructor (GType type, guint n_construct_properties,
+					  GObjectConstructParam *construct_properties);
+static void rb_tray_icon_dispose (GObject *object);
+static void rb_tray_icon_set_property (GObject *object,
+					  guint prop_id,
+					  const GValue *value,
+					  GParamSpec *pspec);
+static void rb_tray_icon_get_property (GObject *object,
+					  guint prop_id,
+					  GValue *value,
+					  GParamSpec *pspec);
+static void rb_tray_icon_button_press_event_cb (GtkWidget *ebox, GdkEventButton *event,
+						RBTrayIcon *icon);
+static void rb_tray_icon_scroll_event_cb (GtkWidget *ebox,
+					  GdkEventScroll *event,
+					  RBTrayIcon *icon);
+static void rb_tray_icon_drop_cb (GtkWidget *widget,
+				  GdkDragContext *context,
+				  gint x,
+				  gint y,
+				  GtkSelectionData *data,
+				  guint info,
+				  guint time,
+				  RBTrayIcon *icon);
+static void rb_tray_icon_playing_changed_cb (RBShellPlayer *player,
+					     gboolean playing,
+					     RBTrayIcon *tray);
+
+struct _RBTrayIconPrivate
+{
+	RBStatusIconPlugin *plugin;
+
+	GtkWidget *playing_image;
+	GtkWidget *not_playing_image;
+	GtkWidget *ebox;
+
+	RBShell *shell;
+	RBShellPlayer *shell_player;
+};
+
+#define RB_TRAY_ICON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_TRAY_ICON, RBTrayIconPrivate))
+
+enum
+{
+	PROP_0,
+	PROP_PLUGIN,
+	PROP_SHELL
+};
+
+enum
+{
+	LAST_SIGNAL,
+};
+
+static const GtkTargetEntry target_uri [] = {{ "text/uri-list", 0, 0 }};
+
+G_DEFINE_TYPE (RBTrayIcon, rb_tray_icon, EGG_TYPE_TRAY_ICON)
+
+static void
+rb_tray_icon_class_init (RBTrayIconClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = rb_tray_icon_dispose;
+	object_class->constructor = rb_tray_icon_constructor;
+
+	object_class->set_property = rb_tray_icon_set_property;
+	object_class->get_property = rb_tray_icon_get_property;
+
+	/**
+	 * RBTrayIcon:plugin:
+	 *
+	 * #RBStatusIconPlugin instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_PLUGIN,
+					 g_param_spec_object ("plugin",
+							      "RBStatusIconPlugin",
+							      "RBStatusIconPlugin object",
+							      RB_TYPE_STATUS_ICON_PLUGIN,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBTrayIcon:shell:
+	 *
+	 * #RBShell instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_SHELL,
+					 g_param_spec_object ("shell",
+							      "RBShell",
+							      "RBShell object",
+							      RB_TYPE_SHELL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_type_class_add_private (klass, sizeof (RBTrayIconPrivate));
+}
+
+static void
+rb_tray_icon_init (RBTrayIcon *icon)
+{
+	rb_debug ("setting up tray icon");
+
+	icon->priv = G_TYPE_INSTANCE_GET_PRIVATE ((icon), RB_TYPE_TRAY_ICON, RBTrayIconPrivate);
+
+	icon->priv->ebox = gtk_event_box_new ();
+	g_signal_connect_object (G_OBJECT (icon->priv->ebox),
+				 "button_press_event",
+				 G_CALLBACK (rb_tray_icon_button_press_event_cb),
+				 icon, 0);
+	g_signal_connect_object (G_OBJECT (icon->priv->ebox),
+				 "scroll_event",
+				 G_CALLBACK (rb_tray_icon_scroll_event_cb),
+				 icon, 0);
+
+	gtk_drag_dest_set (icon->priv->ebox, GTK_DEST_DEFAULT_ALL, target_uri, 1, GDK_ACTION_COPY);
+	g_signal_connect_object (G_OBJECT (icon->priv->ebox), "drag_data_received",
+				 G_CALLBACK (rb_tray_icon_drop_cb), icon, 0);
+
+	icon->priv->playing_image = gtk_image_new_from_icon_name (RB_STOCK_TRAY_ICON_PLAYING,
+								  GTK_ICON_SIZE_SMALL_TOOLBAR);
+	icon->priv->not_playing_image = gtk_image_new_from_icon_name (RB_STOCK_TRAY_ICON_NOT_PLAYING,
+								      GTK_ICON_SIZE_SMALL_TOOLBAR);
+	g_object_ref (icon->priv->playing_image);
+	g_object_ref (icon->priv->not_playing_image);
+
+	gtk_container_add (GTK_CONTAINER (icon->priv->ebox), icon->priv->not_playing_image);
+
+	gtk_container_add (GTK_CONTAINER (icon), icon->priv->ebox);
+	gtk_widget_show_all (GTK_WIDGET (icon->priv->ebox));
+}
+
+static GObject *
+rb_tray_icon_constructor (GType type, guint n_construct_properties,
+			  GObjectConstructParam *construct_properties)
+{
+	RBTrayIcon *tray;
+	RBTrayIconClass *klass;
+
+	klass = RB_TRAY_ICON_CLASS (g_type_class_peek (RB_TYPE_TRAY_ICON));
+
+	tray = RB_TRAY_ICON (G_OBJECT_CLASS (rb_tray_icon_parent_class)->constructor
+				(type, n_construct_properties,
+				 construct_properties));
+
+	g_object_get (tray->priv->shell,
+		      "shell-player", &tray->priv->shell_player,
+		      NULL);
+	g_signal_connect_object (tray->priv->shell_player,
+				 "playing-changed",
+				 G_CALLBACK (rb_tray_icon_playing_changed_cb),
+				 tray, 0);
+
+	g_object_set (tray, "has-tooltip", TRUE, NULL);
+	g_signal_connect_object (tray, "query-tooltip",
+				 G_CALLBACK (rb_status_icon_plugin_set_tooltip),
+				 tray->priv->plugin, 0);
+
+	return G_OBJECT (tray);
+}
+
+static void
+rb_tray_icon_dispose (GObject *object)
+{
+	RBTrayIcon *tray;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (RB_IS_TRAY_ICON (object));
+
+	tray = RB_TRAY_ICON (object);
+
+	g_return_if_fail (tray->priv != NULL);
+
+	if (tray->priv->shell_player != NULL) {
+		g_object_unref (tray->priv->shell_player);
+		tray->priv->shell_player = NULL;
+	}
+
+	if (tray->priv->playing_image != NULL) {
+		g_object_unref (tray->priv->playing_image);
+		tray->priv->playing_image = NULL;
+	}
+
+	if (tray->priv->not_playing_image != NULL) {
+		g_object_unref (tray->priv->not_playing_image);
+		tray->priv->not_playing_image = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_tray_icon_parent_class)->dispose (object);
+}
+
+static void
+rb_tray_icon_playing_changed_cb (RBShellPlayer *player, gboolean playing, RBTrayIcon *tray)
+{
+	GtkWidget *image;
+
+	if (playing)
+		image = tray->priv->playing_image;
+	else
+		image = tray->priv->not_playing_image;
+
+	gtk_container_remove (GTK_CONTAINER (tray->priv->ebox),
+			      gtk_bin_get_child (GTK_BIN (tray->priv->ebox)));
+	gtk_container_add (GTK_CONTAINER (tray->priv->ebox), image);
+	gtk_widget_show_all (GTK_WIDGET (tray->priv->ebox));
+}
+
+static void
+rb_tray_icon_set_property (GObject *object,
+			   guint prop_id,
+			   const GValue *value,
+			   GParamSpec *pspec)
+{
+	RBTrayIcon *tray = RB_TRAY_ICON (object);
+
+	switch (prop_id)
+	{
+	case PROP_SHELL:
+		tray->priv->shell = g_value_get_object (value);
+		break;
+	case PROP_PLUGIN:
+		tray->priv->plugin = g_value_get_object (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+rb_tray_icon_get_property (GObject *object,
+			      guint prop_id,
+			      GValue *value,
+			      GParamSpec *pspec)
+{
+	RBTrayIcon *tray = RB_TRAY_ICON (object);
+
+	switch (prop_id)
+	{
+	case PROP_SHELL:
+		g_value_set_object (value, tray->priv->shell);
+		break;
+	case PROP_PLUGIN:
+		g_value_set_object (value, tray->priv->plugin);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/**
+ * rb_tray_icon_new:
+ * @plugin: the #RBStatusIconPlugin
+ * @shell: the #RBShell
+ *
+ * Return value: the #RBTrayIcon
+ */
+RBTrayIcon *
+rb_tray_icon_new (RBStatusIconPlugin *plugin,
+		  RBShell *shell)
+{
+	return g_object_new (RB_TYPE_TRAY_ICON,
+			     "title", "Rhythmbox tray icon",
+			     "plugin", plugin,
+			     "shell", shell,
+			     NULL);
+}
+
+static void
+tray_popup_position_menu (GtkMenu *menu,
+			  int *x,
+			  int *y,
+			  gboolean *push_in,
+			  gpointer user_data)
+{
+        GtkWidget *widget;
+        GtkRequisition requisition;
+        gint menu_xpos;
+        gint menu_ypos;
+
+        widget = GTK_WIDGET (user_data);
+
+        gtk_widget_size_request (GTK_WIDGET (menu), &requisition);
+
+        gdk_window_get_origin (widget->window, &menu_xpos, &menu_ypos);
+
+        menu_xpos += widget->allocation.x;
+        menu_ypos += widget->allocation.y;
+
+	if (menu_ypos > gdk_screen_get_height (gtk_widget_get_screen (widget)) / 2)
+		menu_ypos -= requisition.height + widget->style->ythickness;
+	else
+		menu_ypos += widget->allocation.height + widget->style->ythickness;
+
+        *x = menu_xpos;
+        *y = menu_ypos;
+        *push_in = TRUE;
+}
+
+void
+rb_tray_icon_menu_popup (RBTrayIcon *tray, GtkWidget *popup, gint button)
+{
+	gtk_menu_set_screen (GTK_MENU (popup), gtk_widget_get_screen (GTK_WIDGET (tray)));
+	gtk_menu_popup (GTK_MENU (popup), NULL, NULL,
+			tray_popup_position_menu, tray->priv->ebox, button,
+			gtk_get_current_event_time ());
+}
+
+static void
+rb_tray_icon_button_press_event_cb (GtkWidget *ebox,
+				    GdkEventButton *event,
+				    RBTrayIcon *tray)
+{
+	rb_status_icon_plugin_button_press_event (tray->priv->plugin, event);
+}
+
+static void
+rb_tray_icon_scroll_event_cb (GtkWidget *ebox,
+			      GdkEventScroll *event,
+			      RBTrayIcon *tray)
+{
+	rb_status_icon_plugin_scroll_event (tray->priv->plugin, event);
+}
+
+static void
+rb_tray_icon_drop_cb (GtkWidget *widget,
+		      GdkDragContext *context,
+		      gint x,
+		      gint y,
+		      GtkSelectionData *data,
+		      guint info,
+		      guint time,
+		      RBTrayIcon *icon)
+{
+	GList *list, *i;
+	GtkTargetList *tlist;
+	gboolean ret;
+
+	tlist = gtk_target_list_new (target_uri, 1);
+	ret = (gtk_drag_dest_find_target (widget, context, tlist) != GDK_NONE);
+	gtk_target_list_unref (tlist);
+
+	if (ret == FALSE)
+		return;
+
+	list = rb_uri_list_parse ((char *) data->data);
+
+	if (list == NULL) {
+		gtk_drag_finish (context, FALSE, FALSE, time);
+		return;
+	}
+
+	for (i = list; i != NULL; i = i->next) {
+		char *uri = i->data;
+		if (uri != NULL)
+			rb_shell_load_uri (icon->priv->shell, uri, FALSE, NULL);
+
+		g_free (uri);
+	}
+
+	g_list_free (list);
+
+	gtk_drag_finish (context, TRUE, FALSE, time);
+}
+
+/**
+ * rb_tray_icon_get_geom:
+ * @icon: the #RBTrayIcon
+ * @x: returns the x position of the tray icon
+ * @y: returns the y position of the tray icon
+ * @width: returns the width of the tray icon
+ * @height: returns the height of the tray icon
+ *
+ * Retrieves the current position and size of the tray icon.
+ */
+void
+rb_tray_icon_get_geom (RBTrayIcon *icon, int *x, int *y, int *width, int *height)
+{
+	GtkWidget *widget;
+	GtkRequisition requisition;
+
+	widget = GTK_WIDGET (icon->priv->ebox);
+
+	gtk_widget_size_request (widget, &requisition);
+
+	gdk_window_get_origin (widget->window, x, y);
+
+	*width = widget->allocation.x;
+	*height = widget->allocation.y;
+}
+
+void
+rb_tray_icon_trigger_tooltip_query (RBTrayIcon *icon)
+{
+	gtk_widget_trigger_tooltip_query (GTK_WIDGET (icon));
+}
+
+
+gboolean
+rb_tray_icon_is_embedded (RBTrayIcon *icon)
+{
+	return (GTK_PLUG (icon)->socket_window != NULL);
+}
+
+void
+rb_tray_icon_attach_notification (RBTrayIcon *icon, NotifyNotification *notification)
+{
+	notify_notification_attach_to_widget (notification, GTK_WIDGET (icon));
+}
+
+
+void
+rb_tray_icon_set_visible (RBTrayIcon *icon, gboolean visible)
+{
+	if (visible)
+		gtk_widget_show_all (GTK_WIDGET (icon));
+	else
+		gtk_widget_hide_all (GTK_WIDGET (icon));
+}
+
diff --git a/plugins/status-icon/rb-tray-icon.h b/plugins/status-icon/rb-tray-icon.h
new file mode 100644
index 0000000..8a72f30
--- /dev/null
+++ b/plugins/status-icon/rb-tray-icon.h
@@ -0,0 +1,88 @@
+/*
+ *  arch-tag: Header for Rhythmbox tray icon object
+ *
+ *  Copyright (C) 2003 Colin Walters <walters verbum org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include <gtk/gtk.h>
+#include "rb-status-icon-plugin.h"
+#include "eggtrayicon.h"
+#include "rb-shell.h"
+
+#if defined(HAVE_NOTIFY)
+#include <libnotify/notify.h>
+#endif
+
+#ifndef __RB_TRAY_ICON_H
+#define __RB_TRAY_ICON_H
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_TRAY_ICON         (rb_tray_icon_get_type ())
+#define RB_TRAY_ICON(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_TRAY_ICON, RBTrayIcon))
+#define RB_TRAY_ICON_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_TRAY_ICON, RBTrayIconClass))
+#define RB_IS_TRAY_ICON(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_TRAY_ICON))
+#define RB_IS_TRAY_ICON_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_TRAY_ICON))
+#define RB_TRAY_ICON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_TRAY_ICON, RBTrayIconClass))
+
+typedef struct _RBTrayIcon RBTrayIcon;
+typedef struct _RBTrayIconClass RBTrayIconClass;
+
+typedef struct _RBTrayIconPrivate RBTrayIconPrivate;
+
+struct _RBTrayIcon
+{
+	EggTrayIcon parent;
+
+	RBTrayIconPrivate *priv;
+};
+
+struct _RBTrayIconClass
+{
+	EggTrayIconClass parent_class;
+};
+
+GType			rb_tray_icon_get_type	(void);
+
+RBTrayIcon *		rb_tray_icon_new	(RBStatusIconPlugin *plugin, RBShell *shell);
+
+void                    rb_tray_icon_get_geom   (RBTrayIcon *icon, int *x, int *y, int *width, int *height);
+
+void 			rb_tray_icon_trigger_tooltip_query (RBTrayIcon *icon);
+
+gboolean		rb_tray_icon_is_embedded (RBTrayIcon *icon);
+
+void			rb_tray_icon_menu_popup (RBTrayIcon *icon, GtkWidget *popup, gint button);
+
+#if defined(HAVE_NOTIFY)
+void			rb_tray_icon_attach_notification (RBTrayIcon *icon, NotifyNotification *notification);
+#endif
+
+void			rb_tray_icon_set_visible (RBTrayIcon *icon, gboolean visible);
+
+G_END_DECLS
+
+#endif /* __RB_TRAY_ICON_H */
diff --git a/plugins/status-icon/status-icon-preferences.ui b/plugins/status-icon/status-icon-preferences.ui
new file mode 100644
index 0000000..fc9ad0b
--- /dev/null
+++ b/plugins/status-icon/status-icon-preferences.ui
@@ -0,0 +1,199 @@
+<?xml version="1.0"?>
+<interface>
+  <!-- interface-requires gtk+ 2.12 -->
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkListStore" id="statusiconmodel">
+    <columns>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0" translatable="yes">Never visible</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Visible with notifications</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Always visible</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Owns the main window</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkListStore" id="notificationmodel">
+    <columns>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0" translatable="yes">Never shown</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Shown when the main window is hidden</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Always shown</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkDialog" id="statusiconpreferences">
+    <property name="border_width">5</property>
+    <property name="resizable">False</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">normal</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">18</property>
+        <child>
+          <object class="GtkVBox" id="vbox1">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkLabel" id="headerlabel">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">Status icon preferences</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkHBox" id="hbox2">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkLabel" id="label4">
+                    <property name="visible">True</property>
+                    <property name="xpad">8</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkTable" id="table1">
+                    <property name="visible">True</property>
+                    <property name="n_rows">2</property>
+                    <property name="n_columns">2</property>
+                    <property name="column_spacing">12</property>
+                    <property name="row_spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="label2">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">_Status Icon:</property>
+                        <property name="use_underline">True</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="statusiconmode">
+                        <property name="visible">True</property>
+                        <property name="model">statusiconmodel</property>
+                        <child>
+                          <object class="GtkCellRendererText" id="cellrenderertext1"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label3">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">_Notifications</property>
+                        <property name="use_underline">True</property>
+                      </object>
+                      <packing>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="notificationmode">
+                        <property name="visible">True</property>
+                        <property name="model">notificationmodel</property>
+                        <child>
+                          <object class="GtkCellRendererText" id="cellrenderertext2"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <object class="GtkButton" id="button1">
+                <property name="label" translatable="yes">gtk-close</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="0">button1</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/plugins/status-icon/status-icon-ui.xml b/plugins/status-icon/status-icon-ui.xml
new file mode 100644
index 0000000..a5cb6e1
--- /dev/null
+++ b/plugins/status-icon/status-icon-ui.xml
@@ -0,0 +1,26 @@
+<ui>
+
+  <!-- add 'close' item to the 'music' menu -->
+  <menubar name="MenuBar">
+    <menu name="MusicMenu" action="Music">
+      <placeholder name="PluginWindowPlaceholder">
+	<menuitem name="MusicClose" action="MusicClose"/>
+      </placeholder>
+    </menu>
+  </menubar>
+
+  <popup name="RhythmboxTrayPopup">
+      <menuitem name="PlayTray" action="ControlPlay"/>
+      <separator/>
+      <menuitem name="PreviousTray" action="ControlPrevious"/>
+      <menuitem name="NextTray" action="ControlNext"/>
+      <separator/>
+      <menuitem name="ShowWindowTray" action="TrayShowWindow"/>
+      <menuitem name="ShowNotifications" action="TrayShowNotifications"/>
+      <separator/>
+      <placeholder name="PluginPlaceholder"/> <!-- .. hmm. -->
+      <separator/>
+      <menuitem name="QuitTray" action="MusicQuit"/>
+  </popup>
+</ui>
+
diff --git a/plugins/status-icon/status-icon.rb-plugin.in b/plugins/status-icon/status-icon.rb-plugin.in
new file mode 100644
index 0000000..cf2e7c2
--- /dev/null
+++ b/plugins/status-icon/status-icon.rb-plugin.in
@@ -0,0 +1,8 @@
+[RB Plugin]
+Module=status-icon
+IAge=1
+_Name=Status Icon
+_Description=Status icon and notification popups
+Authors=Jonathan Matthew
+Copyright=Copyright © 2009 Jonathan Matthew
+Website=http://www.rhythmbox.org
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6f221e9..7e52294 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -64,7 +64,6 @@ plugins/daap/rb-daap-plugin.c
 plugins/daap/rb-daap-sharing.c
 plugins/daap/rb-daap-source.c
 plugins/daap/rb-daap-src.c
-[type: gettext/ini]plugins/dontreallyclose/dontreallyclose.rb-plugin.in
 [type: gettext/ini]plugins/fmradio/fmradio.rb-plugin.in
 plugins/fmradio/rb-fm-radio-source.c
 [type: gettext/ini]plugins/generic-player/generic-player.rb-plugin.in
@@ -127,6 +126,11 @@ plugins/sample-python/sample-python.py
 plugins/sample/rb-sample-plugin.c
 [type: gettext/ini]plugins/sample/sample.rb-plugin.in
 [type: gettext/ini]plugins/sample-vala/sample-vala.rb-plugin.in
+plugins/status-icon/eggtrayicon.c
+plugins/status-icon/rb-tray-icon.c
+plugins/status-icon/rb-status-icon-plugin.c
+[type: gettext/ini]plugins/status-icon/status-icon.rb-plugin.in
+[type: gettext/glade]plugins/status-icon/status-icon-preferences.ui
 plugins/visualizer/rb-visualizer-plugin.c
 plugins/visualizer/visualizer-controls.glade
 [type: gettext/ini]plugins/visualizer/visualizer.rb-plugin.in
@@ -149,7 +153,6 @@ shell/rb-shell-preferences.c
 shell/rb-shell.c
 shell/rb-source-header.c
 shell/rb-statusbar.c
-shell/rb-tray-icon.c
 sources/rb-auto-playlist-source.c
 sources/rb-browser-source.c
 sources/rb-import-errors-source.c
@@ -163,7 +166,6 @@ sources/rb-source-group.c
 sources/rb-sourcelist.c
 sources/rb-static-playlist-source.c
 sources/rb-streaming-source.c
-widgets/eggtrayicon.c
 widgets/rb-cell-renderer-pixbuf.c
 widgets/rb-dialog.c
 widgets/rb-entry-view.c



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