[rhythmbox] add status icon plugin
- From: Jonathan Matthew <jmatthew src gnome org>
- To: svn-commits-list gnome org
- Subject: [rhythmbox] add status icon plugin
- Date: Sun, 17 May 2009 22:30:17 -0400 (EDT)
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]