aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKen VanDine <ken.vandine@canonical.com>2010-01-14 14:30:48 -0500
committerKen VanDine <ken.vandine@canonical.com>2010-01-14 14:30:48 -0500
commit8cc2e354a6135b0074ecd47349a71bd4700ccf99 (patch)
treeb2361a1d09aca187f48d00604af719f1e92c5bb7
parent1a2eabc1588a888ea1721f8da0964dff0a44847d (diff)
parentbd7118f59345fbf9c7a22fdfb7a65e7f97dfa6c8 (diff)
downloadayatana-indicator-application-8cc2e354a6135b0074ecd47349a71bd4700ccf99.tar.gz
ayatana-indicator-application-8cc2e354a6135b0074ecd47349a71bd4700ccf99.tar.bz2
ayatana-indicator-application-8cc2e354a6135b0074ecd47349a71bd4700ccf99.zip
* Upstream Release 0.0.9
* Add fallback support for situations where the NotificationWatcher isn't available.
-rw-r--r--.bzrignore3
-rw-r--r--configure.ac6
-rw-r--r--debian/changelog8
-rw-r--r--docs/reference/libappindicator-sections.txt1
-rw-r--r--example/simple-client.c5
-rw-r--r--src/libappindicator/app-indicator.c293
-rw-r--r--src/libappindicator/app-indicator.h14
-rw-r--r--tests/Makefile.am37
-rw-r--r--tests/test-libappindicator-fallback-item.c130
-rw-r--r--tests/test-libappindicator-fallback-watcher.c97
10 files changed, 580 insertions, 14 deletions
diff --git a/.bzrignore b/.bzrignore
index 977611c..a218817 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -87,3 +87,6 @@ docs/reference/xml
docs/reference/tmpl/app-indicator.sgml
docs/reference/tmpl/app-indicator.sgml.bak
src/libappindicator/appindicator-0.1.pc
+tests/test-libappindicator-fallback-item
+tests/test-libappindicator-fallback-watcher
+tests/test-libappindicator-fallback
diff --git a/configure.ac b/configure.ac
index 713f40e..ebd6b20 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,11 +1,11 @@
-AC_INIT(indicator-application, 0.0.8, ted@canonical.com)
-AC_COPYRIGHT([Copyright 2009 Canonical])
+AC_INIT(indicator-application, 0.0.9, ted@canonical.com)
+AC_COPYRIGHT([Copyright 2009, 2010 Canonical])
AC_PREREQ(2.53)
AM_CONFIG_HEADER(config.h)
-AM_INIT_AUTOMAKE(indicator-application, 0.0.8)
+AM_INIT_AUTOMAKE(indicator-application, 0.0.9)
AM_MAINTAINER_MODE
diff --git a/debian/changelog b/debian/changelog
index 06198be..a9319b5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+indicator-application (0.0.9-0ubuntu1) lucid; urgency=low
+
+ * Upstream Release 0.0.9
+ * Add fallback support for situations where the NotificationWatcher
+ isn't available.
+
+ -- Ted Gould <ted@ubuntu.com> Thu, 14 Jan 2010 10:39:43 -0600
+
indicator-application (0.0.8-0ubuntu1) lucid; urgency=low
* Upstream Release 0.0.8
diff --git a/docs/reference/libappindicator-sections.txt b/docs/reference/libappindicator-sections.txt
index 68b120a..70df0b8 100644
--- a/docs/reference/libappindicator-sections.txt
+++ b/docs/reference/libappindicator-sections.txt
@@ -28,5 +28,6 @@ app_indicator_get_category
app_indicator_get_status
app_indicator_get_icon
app_indicator_get_attention_icon
+app_indicator_get_menu
</SECTION>
diff --git a/example/simple-client.c b/example/simple-client.c
index 8ff3827..6dcf5d1 100644
--- a/example/simple-client.c
+++ b/example/simple-client.c
@@ -73,26 +73,31 @@ main (int argc, char ** argv)
g_signal_connect (item, "activate",
G_CALLBACK (item_clicked_cb), "1");
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show(item);
item = gtk_radio_menu_item_new_with_label (NULL, "2");
g_signal_connect (item, "activate",
G_CALLBACK (item_clicked_cb), "2");
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show(item);
item = gtk_menu_item_new_with_label ("3");
g_signal_connect (item, "activate",
G_CALLBACK (item_clicked_cb), "3");
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show(item);
GtkWidget *toggle_item = gtk_menu_item_new_with_label ("Toggle 3");
g_signal_connect (toggle_item, "activate",
G_CALLBACK (toggle_sensitivity_cb), item);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), toggle_item);
+ gtk_widget_show(toggle_item);
item = gtk_image_menu_item_new_from_stock (GTK_STOCK_NEW, NULL);
g_signal_connect (item, "activate",
G_CALLBACK (image_clicked_cb), NULL);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show(item);
app_indicator_set_menu (ci, GTK_MENU (menu));
diff --git a/src/libappindicator/app-indicator.c b/src/libappindicator/app-indicator.c
index 84d9ebd..6c969c2 100644
--- a/src/libappindicator/app-indicator.c
+++ b/src/libappindicator/app-indicator.c
@@ -64,12 +64,16 @@ struct _AppIndicatorPrivate {
gchar *icon_name;
gchar *attention_icon_name;
gchar * icon_path;
- DbusmenuServer *menuservice;
- GtkWidget *menu;
+ DbusmenuServer *menuservice;
+ GtkWidget *menu;
+
+ GtkStatusIcon * status_icon;
+ gint fallback_timer;
/* Fun stuff */
DBusGProxy *watcher_proxy;
DBusGConnection *connection;
+ DBusGProxy * dbus_proxy;
};
/* Signals Stuff */
@@ -115,6 +119,9 @@ enum {
#define DEFAULT_ITEM_PATH "/org/ayatana/NotificationItem"
#define DEFAULT_MENU_PATH "/org/ayatana/NotificationItem/Menu"
+/* More constants */
+#define DEFAULT_FALLBACK_TIMER 100 /* in milliseconds */
+
/* Boiler plate */
static void app_indicator_class_init (AppIndicatorClass *klass);
static void app_indicator_init (AppIndicator *self);
@@ -126,6 +133,14 @@ static void app_indicator_get_property (GObject * object, guint prop_id, GValue
/* Other stuff */
static void check_connect (AppIndicator * self);
static void register_service_cb (DBusGProxy * proxy, GError * error, gpointer data);
+static void start_fallback_timer (AppIndicator * self, gboolean disable_timeout);
+static gboolean fallback_timer_expire (gpointer data);
+static GtkStatusIcon * fallback (AppIndicator * self);
+static void status_icon_status_wrapper (AppIndicator * self, const gchar * status, gpointer data);
+static void status_icon_changes (AppIndicator * self, gpointer data);
+static void status_icon_activate (GtkStatusIcon * icon, gpointer data);
+static void unfallback (AppIndicator * self, GtkStatusIcon * status_icon);
+static void watcher_proxy_destroyed (GObject * object, gpointer data);
/* GObject type */
G_DEFINE_TYPE (AppIndicator, app_indicator, G_TYPE_OBJECT);
@@ -145,6 +160,10 @@ app_indicator_class_init (AppIndicatorClass *klass)
object_class->set_property = app_indicator_set_property;
object_class->get_property = app_indicator_get_property;
+ /* Our own funcs */
+ klass->fallback = fallback;
+ klass->unfallback = unfallback;
+
/* Properties */
g_object_class_install_property (object_class,
PROP_ID,
@@ -298,6 +317,10 @@ app_indicator_init (AppIndicator *self)
priv->watcher_proxy = NULL;
priv->connection = NULL;
+ priv->dbus_proxy = NULL;
+
+ priv->status_icon = NULL;
+ priv->fallback_timer = 0;
/* Put the object on DBus */
GError * error = NULL;
@@ -322,20 +345,39 @@ app_indicator_init (AppIndicator *self)
static void
app_indicator_dispose (GObject *object)
{
- AppIndicator *self = APP_INDICATOR (object);
+ AppIndicator *self = APP_INDICATOR (object);
AppIndicatorPrivate *priv = self->priv;
if (priv->status != APP_INDICATOR_STATUS_PASSIVE) {
app_indicator_set_status(self, APP_INDICATOR_STATUS_PASSIVE);
}
+ if (priv->status_icon != NULL) {
+ AppIndicatorClass * class = APP_INDICATOR_GET_CLASS(object);
+ if (class->unfallback != NULL) {
+ class->unfallback(self, priv->status_icon);
+ }
+ priv->status_icon = NULL;
+ }
+
+ if (priv->fallback_timer != 0) {
+ g_source_remove(priv->fallback_timer);
+ priv->fallback_timer = 0;
+ }
+
if (priv->menu != NULL) {
g_object_unref(G_OBJECT(priv->menu));
priv->menu = NULL;
}
+ if (priv->dbus_proxy != NULL) {
+ g_object_unref(G_OBJECT(priv->dbus_proxy));
+ priv->dbus_proxy = NULL;
+ }
+
if (priv->watcher_proxy != NULL) {
dbus_g_connection_flush(priv->connection);
+ g_signal_handlers_disconnect_by_func(G_OBJECT(priv->watcher_proxy), watcher_proxy_destroyed, self);
g_object_unref(G_OBJECT(priv->watcher_proxy));
priv->watcher_proxy = NULL;
}
@@ -522,7 +564,7 @@ app_indicator_get_property (GObject * object, guint prop_id, GValue * value, GPa
static void
check_connect (AppIndicator *self)
{
- AppIndicatorPrivate *priv = self->priv;
+ AppIndicatorPrivate *priv = self->priv;
/* We're alreadying connecting or trying to connect. */
if (priv->watcher_proxy != NULL) return;
@@ -539,30 +581,62 @@ check_connect (AppIndicator *self)
NOTIFICATION_WATCHER_DBUS_IFACE,
&error);
if (error != NULL) {
- g_warning("Unable to create Ayatana Watcher proxy! %s", error->message);
- /* TODO: This is where we should start looking at fallbacks */
+ /* Unable to get proxy, but we're handling that now so
+ it's not a warning anymore. */
g_error_free(error);
+ start_fallback_timer(self, FALSE);
return;
}
+ g_signal_connect(G_OBJECT(priv->watcher_proxy), "destroy", G_CALLBACK(watcher_proxy_destroyed), self);
org_freedesktop_StatusNotifierWatcher_register_status_notifier_item_async(priv->watcher_proxy, DEFAULT_ITEM_PATH, register_service_cb, self);
return;
}
+/* A function that gets called when the watcher dies. Like
+ dies dies. Not our friend anymore. */
+static void
+watcher_proxy_destroyed (GObject * object, gpointer data)
+{
+ AppIndicator * self = APP_INDICATOR(data);
+ g_return_if_fail(self != NULL);
+
+ self->priv->watcher_proxy = NULL;
+ start_fallback_timer(self, FALSE);
+ return;
+}
+
+/* Responce from the DBus command to register a service
+ with a NotificationWatcher. */
static void
register_service_cb (DBusGProxy * proxy, GError * error, gpointer data)
{
- AppIndicatorPrivate * priv = APP_INDICATOR_GET_PRIVATE(data);
+ g_return_if_fail(IS_APP_INDICATOR(data));
+ AppIndicatorPrivate * priv = APP_INDICATOR(data)->priv;
if (error != NULL) {
+ /* They didn't respond, ewww. Not sure what they could
+ be doing */
g_warning("Unable to connect to the Notification Watcher: %s", error->message);
g_object_unref(G_OBJECT(priv->watcher_proxy));
priv->watcher_proxy = NULL;
+ start_fallback_timer(APP_INDICATOR(data), TRUE);
+ }
+
+ if (priv->status_icon) {
+ AppIndicatorClass * class = APP_INDICATOR_GET_CLASS(data);
+ if (class->unfallback != NULL) {
+ class->unfallback(APP_INDICATOR(data), priv->status_icon);
+ priv->status_icon = NULL;
+ }
}
+
return;
}
+/* A helper function to get the nick out of a given
+ category enum value. */
static const gchar *
category_from_enum (AppIndicatorCategory category)
{
@@ -572,6 +646,191 @@ category_from_enum (AppIndicatorCategory category)
return value->value_nick;
}
+/* Watching the dbus owner change events to see if someone
+ we care about pops up! */
+static void
+dbus_owner_change (DBusGProxy * proxy, const gchar * name, const gchar * prev, const gchar * new, gpointer data)
+{
+ if (new == NULL || new[0] == '\0') {
+ /* We only care about folks coming on the bus. Exit quickly otherwise. */
+ return;
+ }
+
+ if (g_strcmp0(name, NOTIFICATION_WATCHER_DBUS_ADDR)) {
+ /* We only care about this address, reject all others. */
+ return;
+ }
+
+ /* Woot, there's a new notification watcher in town. */
+
+ AppIndicatorPrivate * priv = APP_INDICATOR_GET_PRIVATE(data);
+
+ if (priv->fallback_timer != 0) {
+ /* Stop a timer */
+ g_source_remove(priv->fallback_timer);
+
+ /* Stop listening to bus events */
+ g_object_unref(G_OBJECT(priv->dbus_proxy));
+ priv->dbus_proxy = NULL;
+ }
+
+ /* Let's start from the very beginning */
+ check_connect(APP_INDICATOR(data));
+
+ return;
+}
+
+/* A function that will start the fallback timer if it's not
+ already started. It sets up the DBus watcher to see if
+ there is a change. Also, provides an override mode for cases
+ where it's unlikely that a timer will help anything. */
+static void
+start_fallback_timer (AppIndicator * self, gboolean disable_timeout)
+{
+ g_return_if_fail(IS_APP_INDICATOR(self));
+ AppIndicatorPrivate * priv = APP_INDICATOR(self)->priv;
+
+ if (priv->fallback_timer != 0) {
+ /* The timer is set, let's just be happy with the one
+ we've already got running */
+ return;
+ }
+
+ if (priv->dbus_proxy == NULL) {
+ priv->dbus_proxy = dbus_g_proxy_new_for_name(priv->connection,
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS);
+ dbus_g_proxy_add_signal(priv->dbus_proxy, "NameOwnerChanged",
+ G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_INVALID);
+ dbus_g_proxy_connect_signal(priv->dbus_proxy, "NameOwnerChanged",
+ G_CALLBACK(dbus_owner_change), self, NULL);
+ }
+
+ if (disable_timeout) {
+ fallback_timer_expire(self);
+ } else {
+ priv->fallback_timer = g_timeout_add(DEFAULT_FALLBACK_TIMER, fallback_timer_expire, self);
+ }
+
+ return;
+}
+
+/* A function that gets executed when we want to change the
+ state of the fallback. */
+static gboolean
+fallback_timer_expire (gpointer data)
+{
+ g_return_val_if_fail(IS_APP_INDICATOR(data), FALSE);
+
+ AppIndicatorPrivate * priv = APP_INDICATOR(data)->priv;
+ AppIndicatorClass * class = APP_INDICATOR_GET_CLASS(data);
+
+ if (priv->status_icon == NULL) {
+ if (class->fallback != NULL) {
+ priv->status_icon = class->fallback(APP_INDICATOR(data));
+ }
+ } else {
+ if (class->unfallback != NULL) {
+ class->unfallback(APP_INDICATOR(data), priv->status_icon);
+ priv->status_icon = NULL;
+ } else {
+ g_warning("No 'unfallback' function but the 'fallback' function returned a non-NULL result.");
+ }
+ }
+
+ priv->fallback_timer = 0;
+ return FALSE;
+}
+
+/* Creates a StatusIcon that can be used when the application
+ indicator area isn't available. */
+static GtkStatusIcon *
+fallback (AppIndicator * self)
+{
+ GtkStatusIcon * icon = gtk_status_icon_new();
+
+ gtk_status_icon_set_title(icon, app_indicator_get_id(self));
+
+ g_signal_connect(G_OBJECT(self), APP_INDICATOR_SIGNAL_NEW_STATUS,
+ G_CALLBACK(status_icon_changes), icon);
+ g_signal_connect(G_OBJECT(self), APP_INDICATOR_SIGNAL_NEW_ICON,
+ G_CALLBACK(status_icon_changes), icon);
+ g_signal_connect(G_OBJECT(self), APP_INDICATOR_SIGNAL_NEW_ATTENTION_ICON,
+ G_CALLBACK(status_icon_changes), icon);
+
+ status_icon_changes(self, icon);
+
+ g_signal_connect(G_OBJECT(icon), "activate", G_CALLBACK(status_icon_activate), self);
+
+ return icon;
+}
+
+/* A wrapper as the status update prototype is a little
+ bit different, but we want to handle it the same. */
+static void
+status_icon_status_wrapper (AppIndicator * self, const gchar * status, gpointer data)
+{
+ return status_icon_changes(self, data);
+}
+
+/* This tracks changes to either the status or the icons
+ that are associated with the app indicator */
+static void
+status_icon_changes (AppIndicator * self, gpointer data)
+{
+ GtkStatusIcon * icon = GTK_STATUS_ICON(data);
+
+ switch (app_indicator_get_status(self)) {
+ case APP_INDICATOR_STATUS_PASSIVE:
+ gtk_status_icon_set_visible(icon, FALSE);
+ gtk_status_icon_set_from_icon_name(icon, app_indicator_get_icon(self));
+ break;
+ case APP_INDICATOR_STATUS_ACTIVE:
+ gtk_status_icon_set_from_icon_name(icon, app_indicator_get_icon(self));
+ gtk_status_icon_set_visible(icon, TRUE);
+ break;
+ case APP_INDICATOR_STATUS_ATTENTION:
+ gtk_status_icon_set_from_icon_name(icon, app_indicator_get_attention_icon(self));
+ gtk_status_icon_set_visible(icon, TRUE);
+ break;
+ };
+
+ return;
+}
+
+/* Handles the activate action by the status icon by showing
+ the menu in a popup. */
+static void
+status_icon_activate (GtkStatusIcon * icon, gpointer data)
+{
+ GtkMenu * menu = app_indicator_get_menu(APP_INDICATOR(data));
+ if (menu == NULL)
+ return;
+
+ gtk_menu_popup(menu,
+ NULL, /* Parent Menu */
+ NULL, /* Parent item */
+ gtk_status_icon_position_menu,
+ icon,
+ 1, /* Button */
+ gtk_get_current_event_time());
+
+ return;
+}
+
+/* Removes the status icon as the application indicator area
+ is now up and running again. */
+static void
+unfallback (AppIndicator * self, GtkStatusIcon * status_icon)
+{
+ g_signal_handlers_disconnect_by_func(G_OBJECT(self), status_icon_status_wrapper, status_icon);
+ g_signal_handlers_disconnect_by_func(G_OBJECT(self), status_icon_changes, status_icon);
+ g_object_unref(G_OBJECT(status_icon));
+ return;
+}
+
/* ************************* */
/* Public Functions */
@@ -1050,3 +1309,23 @@ app_indicator_get_attention_icon (AppIndicator *self)
return self->priv->attention_icon_name;
}
+
+/**
+ app_indicator_get_menu:
+ @self: The #AppIndicator object to use
+
+ Gets the menu being used for this application indicator.
+
+ Return value: A menu object or #NULL if one hasn't been set.
+*/
+GtkMenu *
+app_indicator_get_menu (AppIndicator *self)
+{
+ AppIndicatorPrivate *priv;
+
+ g_return_val_if_fail (IS_APP_INDICATOR (self), NULL);
+
+ priv = self->priv;
+
+ return GTK_MENU(priv->menu);
+}
diff --git a/src/libappindicator/app-indicator.h b/src/libappindicator/app-indicator.h
index a680c33..03656ce 100644
--- a/src/libappindicator/app-indicator.h
+++ b/src/libappindicator/app-indicator.h
@@ -148,10 +148,12 @@ typedef struct _AppIndicatorPrivate AppIndicatorPrivate;
@new_attention_icon: Slot for #AppIndicator::new-attention-icon.
@new_status: Slot for #AppIndicator::new-status.
@connection_changed: Slot for #AppIndicator::connection-changed.
+ @fallback: Function that gets called to make a #GtkStatusIcon when
+ there is no Application Indicator area available.
+ @unfallback: The function that gets called if an Application
+ Indicator area appears after the fallback has been created.
@app_indicator_reserved_1: Reserved for future use.
@app_indicator_reserved_2: Reserved for future use.
- @app_indicator_reserved_3: Reserved for future use.
- @app_indicator_reserved_4: Reserved for future use.
The signals and external functions that make up the #AppIndicator
class object.
@@ -174,11 +176,14 @@ struct _AppIndicatorClass {
gboolean connected,
gpointer user_data);
+ /* Overridable Functions */
+ GtkStatusIcon * (*fallback) (AppIndicator * indicator);
+ void (*unfallback) (AppIndicator * indicator,
+ GtkStatusIcon * status_icon);
+
/* Reserved */
void (*app_indicator_reserved_1)(void);
void (*app_indicator_reserved_2)(void);
- void (*app_indicator_reserved_3)(void);
- void (*app_indicator_reserved_4)(void);
};
/**
@@ -223,6 +228,7 @@ AppIndicatorCategory app_indicator_get_category (AppIndicator *
AppIndicatorStatus app_indicator_get_status (AppIndicator *self);
const gchar * app_indicator_get_icon (AppIndicator *self);
const gchar * app_indicator_get_attention_icon (AppIndicator *self);
+GtkMenu * app_indicator_get_menu (AppIndicator *self);
G_END_DECLS
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 845b41c..c94ebdd 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -3,6 +3,8 @@ check_PROGRAMS = \
test-libappindicator \
test-libappindicator-dbus-client \
test-libappindicator-dbus-server \
+ test-libappindicator-fallback-watcher \
+ test-libappindicator-fallback-item \
test-simple-app
TESTS =
@@ -59,6 +61,41 @@ test_libappindicator_dbus_server_LDADD = \
$(top_builddir)/src/libappindicator.la
#########################################
+## test-libappindicator-fallback
+#########################################
+
+test_libappindicator_fallback_watcher_SOURCES = \
+ test-libappindicator-fallback-watcher.c
+
+test_libappindicator_fallback_watcher_CFLAGS = \
+ $(INDICATOR_CFLAGS) \
+ -Wall -Werror \
+ -I$(top_srcdir)/src
+
+test_libappindicator_fallback_watcher_LDADD = \
+ $(INDICATOR_LIBS) \
+ $(top_builddir)/src/libappindicator.la
+
+test_libappindicator_fallback_item_SOURCES = \
+ test-libappindicator-fallback-item.c
+
+test_libappindicator_fallback_item_CFLAGS = \
+ $(INDICATOR_CFLAGS) \
+ -Wall -Werror \
+ -I$(top_srcdir)/src
+
+test_libappindicator_fallback_item_LDADD = \
+ $(INDICATOR_LIBS) \
+ $(top_builddir)/src/libappindicator.la
+
+test-libappindicator-fallback: test-libappindicator-fallback-watcher test-libappindicator-fallback-item Makefile.am
+ @echo "#!/bin/sh" > $@
+ @echo $(DBUS_RUNNER) --task ./test-libappindicator-fallback-watcher --task-name Watcher --ignore-return --task ./test-libappindicator-fallback-item --task-name Item >> $@
+ @chmod +x $@
+
+TESTS += test-libappindicator-fallback
+
+#########################################
## Actual tests
#########################################
diff --git a/tests/test-libappindicator-fallback-item.c b/tests/test-libappindicator-fallback-item.c
new file mode 100644
index 0000000..291bc7c
--- /dev/null
+++ b/tests/test-libappindicator-fallback-item.c
@@ -0,0 +1,130 @@
+#include <glib.h>
+#include <glib-object.h>
+#include <libappindicator/app-indicator.h>
+
+#define TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE (test_libappindicator_fallback_item_get_type ())
+#define TEST_LIBAPPINDICATOR_FALLBACK_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE, TestLibappindicatorFallbackItem))
+#define TEST_LIBAPPINDICATOR_FALLBACK_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE, TestLibappindicatorFallbackItemClass))
+#define IS_TEST_LIBAPPINDICATOR_FALLBACK_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE))
+#define IS_TEST_LIBAPPINDICATOR_FALLBACK_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE))
+#define TEST_LIBAPPINDICATOR_FALLBACK_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE, TestLibappindicatorFallbackItemClass))
+
+typedef struct _TestLibappindicatorFallbackItem TestLibappindicatorFallbackItem;
+typedef struct _TestLibappindicatorFallbackItemClass TestLibappindicatorFallbackItemClass;
+
+struct _TestLibappindicatorFallbackItemClass {
+ AppIndicatorClass parent_class;
+
+};
+
+struct _TestLibappindicatorFallbackItem {
+ AppIndicator parent;
+
+};
+
+GType test_libappindicator_fallback_item_get_type (void);
+
+#define TEST_LIBAPPINDICATOR_FALLBACK_ITEM_GET_PRIVATE(o) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((o), TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE, TestLibappindicatorFallbackItemPrivate))
+
+static void test_libappindicator_fallback_item_class_init (TestLibappindicatorFallbackItemClass *klass);
+static void test_libappindicator_fallback_item_init (TestLibappindicatorFallbackItem *self);
+static GtkStatusIcon * fallback (AppIndicator * indicator);
+static void unfallback (AppIndicator * indicator, GtkStatusIcon * status_icon);
+
+G_DEFINE_TYPE (TestLibappindicatorFallbackItem, test_libappindicator_fallback_item, APP_INDICATOR_TYPE);
+
+static void
+test_libappindicator_fallback_item_class_init (TestLibappindicatorFallbackItemClass *klass)
+{
+ AppIndicatorClass * aiclass = APP_INDICATOR_CLASS(klass);
+
+ aiclass->fallback = fallback;
+ aiclass->unfallback = unfallback;
+}
+
+static void
+test_libappindicator_fallback_item_init (TestLibappindicatorFallbackItem *self)
+{
+}
+
+GMainLoop * mainloop = NULL;
+gboolean passed = FALSE;
+
+enum {
+ STATE_INIT,
+ STATE_FALLBACK,
+ STATE_UNFALLBACK,
+ STATE_REFALLBACK,
+ STATE_REUNFALLBACK
+};
+
+gint state = STATE_INIT;
+
+static GtkStatusIcon *
+fallback (AppIndicator * indicator)
+{
+ g_debug("Fallback");
+ if (state == STATE_INIT) {
+ state = STATE_FALLBACK;
+ } else if (state == STATE_UNFALLBACK) {
+ state = STATE_REFALLBACK;
+ } else {
+ g_debug("Error, fallback in state: %d", state);
+ passed = FALSE;
+ }
+ return (GtkStatusIcon *)5;
+}
+
+static void
+unfallback (AppIndicator * indicator, GtkStatusIcon * status_icon)
+{
+ g_debug("Unfallback");
+ if (state == STATE_FALLBACK) {
+ state = STATE_UNFALLBACK;
+ } else if (state == STATE_REFALLBACK) {
+ state = STATE_REUNFALLBACK;
+ passed = TRUE;
+ g_main_loop_quit(mainloop);
+ } else {
+ g_debug("Error, unfallback in state: %d", state);
+ passed = FALSE;
+ }
+ return;
+}
+
+gboolean
+kill_func (gpointer data)
+{
+ g_debug("Kill Function");
+ g_main_loop_quit(mainloop);
+ return FALSE;
+}
+
+int
+main (int argc, char ** argv)
+{
+ gtk_init(&argc, &argv);
+
+ TestLibappindicatorFallbackItem * item = g_object_new(TEST_LIBAPPINDICATOR_FALLBACK_ITEM_TYPE,
+ "id", "test-id",
+ "category", "other",
+ "icon-name", "bob",
+ NULL);
+
+ GtkWidget * menu = gtk_menu_new();
+ app_indicator_set_menu(APP_INDICATOR(item), GTK_MENU(menu));
+
+ g_timeout_add_seconds(1, kill_func, NULL);
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+ g_object_unref(G_OBJECT(item));
+
+ if (passed) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
diff --git a/tests/test-libappindicator-fallback-watcher.c b/tests/test-libappindicator-fallback-watcher.c
new file mode 100644
index 0000000..90c7db8
--- /dev/null
+++ b/tests/test-libappindicator-fallback-watcher.c
@@ -0,0 +1,97 @@
+/*
+This puts the NotificationWatcher on the bus, kinda. Enough to
+trick the Item into unfalling back.
+
+Copyright 2010 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <glib.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-bindings.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "../src/dbus-shared.h"
+
+static GMainLoop * mainloop = NULL;
+
+static DBusHandlerResult
+dbus_filter (DBusConnection * connection, DBusMessage * message, void * user_data)
+{
+ if (dbus_message_is_method_call(message, NOTIFICATION_WATCHER_DBUS_ADDR, "RegisterStatusNotifierItem")) {
+ DBusMessage * reply = dbus_message_new_method_return(message);
+ dbus_connection_send(connection, reply, NULL);
+ dbus_message_unref(reply);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+gboolean
+kill_func (gpointer userdata)
+{
+ g_main_loop_quit(mainloop);
+ return FALSE;
+}
+
+int
+main (int argv, char ** argc)
+{
+ g_type_init();
+
+ g_debug("Waiting to init.");
+
+ /* Wait 1/4 a second, which should trigger the fallback */
+ g_usleep(250000);
+
+ g_debug("Initing");
+
+ GError * error = NULL;
+ DBusGConnection * session_bus = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+ if (error != NULL) {
+ g_error("Unable to get session bus: %s", error->message);
+ return 1;
+ }
+
+ DBusGProxy * bus_proxy = dbus_g_proxy_new_for_name(session_bus, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
+ guint nameret = 0;
+
+ if (!org_freedesktop_DBus_request_name(bus_proxy, NOTIFICATION_WATCHER_DBUS_ADDR, 0, &nameret, &error)) {
+ g_error("Unable to call to request name");
+ return 1;
+ }
+
+ if (nameret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+ g_error("Unable to get name");
+ return 1;
+ }
+
+ dbus_connection_add_filter(dbus_g_connection_get_connection(session_bus), dbus_filter, NULL, NULL);
+
+ /* After we've got the name, let it unfallback, and then we'll drop again */
+ g_timeout_add(250, kill_func, NULL);
+
+ g_debug("Entering Mainloop");
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+ g_debug("Exiting");
+
+ return 0;
+}