Back to index

indicator-appmenu  12.10.0
window-menu-dbusmenu.c
Go to the documentation of this file.
00001 /*
00002 An implementation of indicator object showing menus from applications.
00003 
00004 Copyright 2010 Canonical Ltd.
00005 
00006 Authors:
00007     Ted Gould <ted@canonical.com>
00008 
00009 This program is free software: you can redistribute it and/or modify it 
00010 under the terms of the GNU General Public License version 3, as published 
00011 by the Free Software Foundation.
00012 
00013 This program is distributed in the hope that it will be useful, but 
00014 WITHOUT ANY WARRANTY; without even the implied warranties of 
00015 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR 
00016 PURPOSE.  See the GNU General Public License for more details.
00017 
00018 You should have received a copy of the GNU General Public License along 
00019 with this program.  If not, see <http://www.gnu.org/licenses/>.
00020 */
00021 
00022 #ifdef HAVE_CONFIG_H
00023 #include "config.h"
00024 #endif
00025 
00026 #include <gtk/gtk.h>
00027 
00028 #include <libdbusmenu-gtk/menu.h>
00029 #include <glib.h>
00030 #include <gio/gio.h>
00031 
00032 #include "window-menu-dbusmenu.h"
00033 #include "indicator-appmenu-marshal.h"
00034 
00035 /* Private parts */
00036 
00037 typedef struct _WindowMenuDbusmenuPrivate WindowMenuDbusmenuPrivate;
00038 struct _WindowMenuDbusmenuPrivate {
00039        guint windowid;
00040        DbusmenuGtkClient * client;
00041        DbusmenuMenuitem * root;
00042        GCancellable * props_cancel;
00043        GDBusProxy * props;
00044        GArray * entries;
00045        gboolean error_state;
00046        guint   retry_timer;
00047 };
00048 
00049 typedef struct _WMEntry WMEntry;
00050 struct _WMEntry {
00051        IndicatorObjectEntry ioentry;
00052        gboolean disabled;
00053        gboolean hidden;
00054        DbusmenuMenuitem * mi;
00055        WindowMenuDbusmenu * wm;
00056 };
00057 
00058 #define WINDOW_MENU_DBUSMENU_GET_PRIVATE(o) \
00059 (G_TYPE_INSTANCE_GET_PRIVATE ((o), WINDOW_MENU_DBUSMENU_TYPE, WindowMenuDbusmenuPrivate))
00060 
00061 /* Prototypes */
00062 
00063 static void window_menu_dbusmenu_dispose    (GObject *object);
00064 static void root_changed            (DbusmenuClient * client, DbusmenuMenuitem * new_root, gpointer user_data);
00065 static void event_status            (DbusmenuClient * client, DbusmenuMenuitem * mi, gchar * event, GVariant * evdata, guint timestamp, GError * error, gpointer user_data);
00066 static void item_activate           (DbusmenuClient * client, DbusmenuMenuitem * item, guint timestamp, gpointer user_data);
00067 static void status_changed          (DbusmenuClient * client, GParamSpec * pspec, gpointer user_data);
00068 static void menu_entry_added        (DbusmenuMenuitem * root, DbusmenuMenuitem * newentry, guint position, gpointer user_data);
00069 static void menu_entry_removed      (DbusmenuMenuitem * root, DbusmenuMenuitem * oldentry, gpointer user_data);
00070 static void menu_entry_realized     (DbusmenuMenuitem * newentry, gpointer user_data);
00071 static void menu_entry_realized_child_added (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint position, gpointer user_data);
00072 static void menu_prop_changed       (DbusmenuMenuitem * item, const gchar * property, GVariant * value, gpointer user_data);
00073 static void menu_child_realized     (DbusmenuMenuitem * child, gpointer user_data);
00074 static void props_cb (GObject * object, GAsyncResult * res, gpointer user_data);
00075 static GList *          get_entries      (WindowMenu * wm);
00076 static guint            get_location     (WindowMenu * wm, IndicatorObjectEntry * entry);
00077 static guint            get_xid          (WindowMenu * wm);
00078 static gboolean         get_error_state  (WindowMenu * wm);
00079 static WindowMenuStatus get_status       (WindowMenu * wm);
00080 static void             entry_restore    (WindowMenu * wm, IndicatorObjectEntry * entry);
00081 static void             entry_activate   (WindowMenu * wm, IndicatorObjectEntry * entry, guint timestamp);
00082 
00083 G_DEFINE_TYPE (WindowMenuDbusmenu, window_menu_dbusmenu, WINDOW_MENU_TYPE);
00084 
00085 /* Build the one-time class */
00086 static void
00087 window_menu_dbusmenu_class_init (WindowMenuDbusmenuClass *klass)
00088 {
00089        GObjectClass *object_class = G_OBJECT_CLASS (klass);
00090 
00091        g_type_class_add_private (klass, sizeof (WindowMenuDbusmenuPrivate));
00092 
00093        object_class->dispose = window_menu_dbusmenu_dispose;
00094 
00095        WindowMenuClass * menu_class = WINDOW_MENU_CLASS(klass);
00096        menu_class->get_entries = get_entries;
00097        menu_class->get_location = get_location;
00098        menu_class->get_xid = get_xid;
00099        menu_class->get_error_state = get_error_state;
00100        menu_class->get_status = get_status;
00101        menu_class->entry_restore = entry_restore;
00102        menu_class->entry_activate = entry_activate;
00103 
00104        return;
00105 }
00106 
00107 /* Initialize the per-instance data */
00108 static void
00109 window_menu_dbusmenu_init (WindowMenuDbusmenu *self)
00110 {
00111        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(self);
00112 
00113        priv->client = NULL;
00114        priv->props_cancel = NULL;
00115        priv->props = NULL;
00116        priv->root = NULL;
00117        priv->error_state = FALSE;
00118 
00119        priv->entries = g_array_new(FALSE, FALSE, sizeof(WMEntry *));
00120 
00121        return;
00122 }
00123 
00124 static void
00125 entry_free(IndicatorObjectEntry * entry)
00126 {
00127        g_return_if_fail(entry != NULL);
00128        WMEntry * wmentry = (WMEntry *)entry;
00129 
00130        if (wmentry->mi != NULL) {
00131               g_signal_handlers_disconnect_by_func(wmentry->mi, G_CALLBACK(menu_prop_changed), &wmentry->ioentry);
00132               g_object_unref(G_OBJECT(wmentry->mi));
00133               wmentry->mi = NULL;
00134        }
00135 
00136        if (entry->label != NULL) {
00137               g_object_unref(entry->label);
00138               entry->label = NULL;
00139        }
00140        if (entry->accessible_desc != NULL) {
00141               entry->accessible_desc = NULL;
00142        }
00143        if (entry->image != NULL) {
00144               g_object_unref(entry->image);
00145               entry->image = NULL;
00146        }
00147        if (entry->menu != NULL) {
00148               g_signal_handlers_disconnect_by_func(entry->menu, G_CALLBACK(gtk_widget_destroyed), &entry->menu);
00149               g_object_unref(entry->menu);
00150               entry->menu = NULL;
00151        }
00152 
00153        g_free(entry);
00154 }
00155 
00156 static void
00157 free_entries(GObject *object, gboolean should_signal)
00158 {
00159        g_return_if_fail(IS_WINDOW_MENU_DBUSMENU(object));
00160 
00161        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(object);
00162 
00163        if (priv->entries != NULL) {
00164               while (priv->entries->len > 0) {
00165                      IndicatorObjectEntry * entry;
00166                      entry = g_array_index(priv->entries, IndicatorObjectEntry *, 0);
00167                      g_array_remove_index(priv->entries, 0);
00168                      if (should_signal) {               
00169                             g_signal_emit_by_name(object, WINDOW_MENU_SIGNAL_ENTRY_REMOVED, entry, TRUE);
00170                      }
00171                      entry_free(entry);
00172               }
00173        }
00174 }
00175 
00176 /* Destroy objects */
00177 static void
00178 window_menu_dbusmenu_dispose (GObject *object)
00179 {
00180        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(object);
00181 
00182        free_entries(object, FALSE);
00183 
00184        if (priv->entries != NULL) {
00185               g_array_free(priv->entries, TRUE);
00186               priv->entries = NULL;
00187        }
00188 
00189        if (priv->root != NULL) {
00190               root_changed(DBUSMENU_CLIENT(priv->client), NULL, object);
00191               g_warn_if_fail(priv->root == NULL);
00192        }
00193 
00194        if (priv->client != NULL) {
00195               g_signal_handlers_disconnect_by_func(G_OBJECT(priv->client), G_CALLBACK(root_changed),    object);
00196               g_signal_handlers_disconnect_by_func(G_OBJECT(priv->client), G_CALLBACK(event_status),    object);
00197               g_signal_handlers_disconnect_by_func(G_OBJECT(priv->client), G_CALLBACK(item_activate),   object);
00198               g_signal_handlers_disconnect_by_func(G_OBJECT(priv->client), G_CALLBACK(status_changed),  object);
00199 
00200               g_object_unref(G_OBJECT(priv->client));
00201               priv->client = NULL;
00202        }
00203        
00204        if (priv->props != NULL) {
00205               g_object_unref(G_OBJECT(priv->props));
00206               priv->props = NULL;
00207        }
00208 
00209        if (priv->props_cancel != NULL) {
00210               g_cancellable_cancel(priv->props_cancel);
00211               g_object_unref(priv->props_cancel);
00212               priv->props_cancel = NULL;
00213        }
00214 
00215        if (priv->retry_timer != 0) {
00216               g_source_remove(priv->retry_timer);
00217               priv->retry_timer = 0;
00218        }
00219 
00220        G_OBJECT_CLASS (window_menu_dbusmenu_parent_class)->dispose (object);
00221        return;
00222 }
00223 
00224 /* Retry the event sending to the server to see if we can get things
00225    working again. */
00226 static gboolean
00227 retry_event (gpointer user_data)
00228 {
00229        g_debug("Retrying event");
00230        g_return_val_if_fail(IS_WINDOW_MENU_DBUSMENU(user_data), FALSE);
00231        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(user_data);
00232 
00233        dbusmenu_menuitem_handle_event(dbusmenu_client_get_root(DBUSMENU_CLIENT(priv->client)),
00234                                       "x-appmenu-retry-ping",
00235                                       NULL,
00236                                       0);
00237 
00238        priv->retry_timer = 0;
00239 
00240        return FALSE;
00241 }
00242 
00243 /* Listen to whether our events are successfully sent */
00244 static void
00245 event_status (DbusmenuClient * client, DbusmenuMenuitem * mi, gchar * event, GVariant * evdata, guint timestamp, GError * error, gpointer user_data)
00246 {
00247        g_return_if_fail(IS_WINDOW_MENU_DBUSMENU(user_data));
00248        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(user_data);
00249 
00250        /* We don't care about status where there are no errors
00251           when we're in a happy state, just let them go. */
00252        if (error == NULL && priv->error_state == FALSE) {
00253               return;
00254        }
00255        int i;
00256 
00257        /* Oh, things are working now! */
00258        if (error == NULL) {
00259               g_debug("Error state repaired");
00260               priv->error_state = FALSE;
00261               g_signal_emit_by_name(G_OBJECT(user_data), WINDOW_MENU_SIGNAL_ERROR_STATE, priv->error_state, TRUE);
00262 
00263               for (i = 0; i < priv->entries->len; i++) {
00264                      IndicatorObjectEntry * entry = g_array_index(priv->entries, IndicatorObjectEntry *, i);
00265                      entry_restore(WINDOW_MENU(user_data), entry);
00266               }
00267 
00268               if (priv->retry_timer != 0) {
00269                      g_source_remove(priv->retry_timer);
00270                      priv->retry_timer = 0;
00271               }
00272 
00273               return;
00274        }
00275 
00276        /* Uhg, means that events are breaking, now we need to
00277           try and handle that case. */
00278        priv->error_state = TRUE;
00279        g_signal_emit_by_name(G_OBJECT(user_data), WINDOW_MENU_SIGNAL_ERROR_STATE, priv->error_state, TRUE);
00280 
00281        for (i = 0; i < priv->entries->len; i++) {
00282               IndicatorObjectEntry * entry = g_array_index(priv->entries, IndicatorObjectEntry *, i);
00283 
00284               if (entry->label != NULL) {
00285                      gtk_widget_set_sensitive(GTK_WIDGET(entry->label), FALSE);
00286               }
00287               if (entry->image != NULL) {
00288                      gtk_widget_set_sensitive(GTK_WIDGET(entry->image), FALSE);
00289               }
00290        }
00291 
00292        if (priv->retry_timer == 0) {
00293               g_debug("Setting up retry timer");
00294               priv->retry_timer = g_timeout_add_seconds(1, retry_event, user_data);
00295        }
00296 
00297        return;
00298 }
00299 
00300 static IndicatorObjectEntry *
00301 get_entry(WindowMenuDbusmenu *wm, DbusmenuMenuitem * item, guint *index)
00302 {
00303        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(wm);
00304 
00305        guint position = 0;
00306        for (position = 0; position < priv->entries->len; ++position) {
00307               WMEntry * entry = g_array_index(priv->entries, WMEntry *, position);
00308               if (entry->mi == item) {
00309                      if (index != NULL) {
00310                             *index = position;
00311                      }
00312                      return &entry->ioentry;
00313               }
00314        }
00315 
00316        /* Not found */
00317        return NULL;
00318 }
00319 
00320 /* Called when a menu item wants to be displayed.  We need to see if
00321    it's one of our root items and pass it up if so. */
00322 static void
00323 item_activate (DbusmenuClient * client, DbusmenuMenuitem * item, guint timestamp, gpointer user_data)
00324 {
00325        g_return_if_fail(IS_WINDOW_MENU_DBUSMENU(user_data));
00326        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(user_data);
00327 
00328        if (priv->root == NULL) {
00329               return;
00330        }
00331 
00332        IndicatorObjectEntry * entry = get_entry(WINDOW_MENU_DBUSMENU(user_data), item, NULL);
00333        if (entry == NULL) {
00334               /* Not found */
00335               return;
00336        }
00337 
00338        g_signal_emit_by_name(G_OBJECT(user_data), WINDOW_MENU_SIGNAL_SHOW_MENU, entry, timestamp, TRUE);
00339 
00340        return;
00341 }
00342 
00343 /* Called when the client changes its status.  Used to show panel if requested.
00344    (Say, by an Alt press.) */
00345 static void
00346 status_changed (DbusmenuClient * client, GParamSpec * pspec, gpointer user_data)
00347 {
00348        g_signal_emit_by_name(G_OBJECT(user_data), WINDOW_MENU_SIGNAL_STATUS_CHANGED, dbusmenu_client_get_status (client));
00349 }
00350 
00351 WindowMenuStatus dbusmenu_status_table[] = {
00352        [DBUSMENU_STATUS_NORMAL] = WINDOW_MENU_STATUS_NORMAL,
00353        [DBUSMENU_STATUS_NOTICE] = WINDOW_MENU_STATUS_ACTIVE
00354 };
00355 
00356 static WindowMenuStatus
00357 get_status (WindowMenu * wm)
00358 {
00359        g_return_val_if_fail(IS_WINDOW_MENU_DBUSMENU(wm), DBUSMENU_STATUS_NORMAL);
00360        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(wm);
00361 
00362        return dbusmenu_status_table[dbusmenu_client_get_status (DBUSMENU_CLIENT (priv->client))];
00363 }
00364 
00365 /* Build a new window menus object and attach to the signals to build
00366    up the representative menu. */
00367 WindowMenuDbusmenu *
00368 window_menu_dbusmenu_new (const guint windowid, const gchar * dbus_addr, const gchar * dbus_object)
00369 {
00370        g_debug("Creating new windows menu: %X, %s, %s", windowid, dbus_addr, dbus_object);
00371 
00372        g_return_val_if_fail(windowid != 0, NULL);
00373        g_return_val_if_fail(dbus_addr != NULL, NULL);
00374        g_return_val_if_fail(dbus_object != NULL, NULL);
00375 
00376        WindowMenuDbusmenu * newmenu = WINDOW_MENU_DBUSMENU(g_object_new(WINDOW_MENU_DBUSMENU_TYPE, NULL));
00377        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(newmenu);
00378 
00379        priv->windowid = windowid;
00380 
00381        /* Build the service proxy */
00382        priv->props_cancel = g_cancellable_new();
00383        g_object_ref(newmenu); /* Take a ref for the async callback */
00384        g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
00385                                 G_DBUS_PROXY_FLAGS_NONE,
00386                                 NULL,
00387                                 dbus_addr,
00388                                 dbus_object,
00389                                 "org.freedesktop.DBus.Properties",
00390                                 priv->props_cancel,
00391                                 props_cb,
00392                                 newmenu);
00393 
00394        priv->client = dbusmenu_gtkclient_new((gchar *)dbus_addr, (gchar *)dbus_object);
00395        GtkAccelGroup * agroup = gtk_accel_group_new();
00396        dbusmenu_gtkclient_set_accel_group(priv->client, agroup);
00397        g_object_unref(agroup);
00398 
00399        g_signal_connect(G_OBJECT(priv->client), DBUSMENU_GTKCLIENT_SIGNAL_ROOT_CHANGED, G_CALLBACK(root_changed),   newmenu);
00400        g_signal_connect(G_OBJECT(priv->client), DBUSMENU_CLIENT_SIGNAL_EVENT_RESULT, G_CALLBACK(event_status), newmenu);
00401        g_signal_connect(G_OBJECT(priv->client), DBUSMENU_CLIENT_SIGNAL_ITEM_ACTIVATE, G_CALLBACK(item_activate), newmenu);
00402        g_signal_connect(G_OBJECT(priv->client), "notify::" DBUSMENU_CLIENT_PROP_STATUS, G_CALLBACK(status_changed), newmenu);
00403 
00404        DbusmenuMenuitem * root = dbusmenu_client_get_root(DBUSMENU_CLIENT(priv->client));
00405        if (root != NULL) {
00406               root_changed(DBUSMENU_CLIENT(priv->client), root, newmenu);
00407        }
00408 
00409        return newmenu;
00410 }
00411 
00412 /* Callback from trying to create the proxy for the service, this
00413    could include starting the service. */
00414 static void
00415 props_cb (GObject * object, GAsyncResult * res, gpointer user_data)
00416 {
00417        GError * error = NULL;
00418        GDBusProxy * proxy = g_dbus_proxy_new_for_bus_finish(res, &error);
00419 
00420        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
00421               g_error_free (error);
00422               return; // Must exit before accessing freed memory
00423        }
00424 
00425        WindowMenuDbusmenu * self = WINDOW_MENU_DBUSMENU(user_data);
00426        g_return_if_fail(self != NULL);
00427 
00428        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(self);
00429 
00430        if (priv->props_cancel != NULL) {
00431               g_object_unref(priv->props_cancel);
00432               priv->props_cancel = NULL;
00433        }
00434 
00435        if (error != NULL) {
00436               g_warning("Could not grab DBus proxy for window %u: %s", priv->windowid, error->message);
00437               g_error_free(error);
00438               goto out;
00439        }
00440 
00441        /* Okay, we're good to grab the proxy at this point, we're
00442        sure that it's ours. */
00443        priv->props = proxy;
00444 
00445 out:
00446        g_object_unref(G_OBJECT(self));
00447 
00448        return;
00449 }
00450 
00451 /* Get the location of this entry */
00452 static guint
00453 get_location (WindowMenu * wm, IndicatorObjectEntry * entry)
00454 {
00455        g_return_val_if_fail(IS_WINDOW_MENU_DBUSMENU(wm), 0);
00456 
00457        if (entry == NULL) {
00458               return 0;
00459        }
00460 
00461        guint i;
00462        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(wm);
00463        for (i = 0; i < priv->entries->len; i++) {
00464               if (entry == g_array_index(priv->entries, IndicatorObjectEntry *, i)) {
00465                      break;
00466               }
00467        }
00468 
00469        if (i == priv->entries->len) {
00470               return 0;
00471        }
00472 
00473        return i;
00474 }
00475 
00476 /* Get the entries that we have */
00477 static GList *
00478 get_entries (WindowMenu * wm)
00479 {
00480        g_return_val_if_fail(IS_WINDOW_MENU_DBUSMENU(wm), NULL);
00481        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(wm);
00482 
00483        int i;
00484        GList * output = NULL;
00485        for (i = 0; i < priv->entries->len; i++) {
00486               output = g_list_prepend(output, g_array_index(priv->entries, IndicatorObjectEntry *, i));
00487        }
00488        if (output != NULL) {
00489               output = g_list_reverse(output);
00490        }
00491 
00492        return output;
00493 }
00494 
00495 /* Goes through the items in the root node and adds them
00496    to the flock */
00497 static void
00498 new_root_helper (DbusmenuMenuitem * item, gpointer user_data)
00499 {
00500        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(user_data);
00501        menu_entry_added(dbusmenu_client_get_root(DBUSMENU_CLIENT(priv->client)), item, priv->entries->len, user_data);
00502        return;
00503 }
00504 
00505 /* Remove the various signals that we attach to menuitems to
00506    ensure they don't pop up later. */
00507 static void
00508 remove_menuitem_signals (DbusmenuMenuitem * mi, gpointer user_data)
00509 {
00510        g_signal_handlers_disconnect_by_func(G_OBJECT(mi), G_CALLBACK(menu_entry_realized), user_data);
00511        g_signal_handlers_disconnect_by_func(G_OBJECT(mi), G_CALLBACK(menu_entry_realized_child_added), user_data);
00512        g_signal_handlers_disconnect_matched (mi, G_SIGNAL_MATCH_FUNC, 0, 0, 0, menu_child_realized, NULL);
00513        g_signal_handlers_disconnect_matched (mi, G_SIGNAL_MATCH_FUNC, 0, 0, 0, menu_prop_changed, NULL);
00514 
00515        return;
00516 }
00517 
00518 /* Respond to the root menu item on our client changing */
00519 static void
00520 root_changed (DbusmenuClient * client, DbusmenuMenuitem * new_root, gpointer user_data)
00521 {
00522        g_return_if_fail(IS_WINDOW_MENU_DBUSMENU(user_data));
00523        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(user_data);
00524 
00525        /* Remove the old entries */
00526        free_entries(G_OBJECT(user_data), TRUE);
00527 
00528        if (priv->root != NULL) {
00529               dbusmenu_menuitem_foreach(priv->root, remove_menuitem_signals, user_data);
00530 
00531               g_signal_handlers_disconnect_by_func(G_OBJECT(priv->root), G_CALLBACK(menu_entry_added), user_data);
00532               g_signal_handlers_disconnect_by_func(G_OBJECT(priv->root), G_CALLBACK(menu_entry_removed), user_data);
00533               g_object_unref(priv->root);
00534        }
00535 
00536        priv->root = new_root;
00537 
00538        /* See if we've got new entries */
00539        if (new_root == NULL) {
00540               return;
00541        }
00542 
00543        g_object_ref(priv->root);
00544 
00545        /* Set up signals */
00546        g_signal_connect(G_OBJECT(new_root), DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED,   G_CALLBACK(menu_entry_added),   user_data);
00547        g_signal_connect(G_OBJECT(new_root), DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED, G_CALLBACK(menu_entry_removed), user_data);
00548        /* TODO: Child Moved */
00549 
00550        /* Add the new entries */
00551        GList * children = dbusmenu_menuitem_get_children(new_root);
00552        while (children != NULL) {
00553               new_root_helper(DBUSMENU_MENUITEM(children->data), user_data);
00554               children = g_list_next(children);
00555        }
00556 
00557        return;
00558 }
00559 
00560 /* Respond to an entry getting added to the menu */
00561 static void
00562 menu_entry_added (DbusmenuMenuitem * root, DbusmenuMenuitem * newentry, guint position, gpointer user_data)
00563 {
00564        g_return_if_fail(IS_WINDOW_MENU_DBUSMENU(user_data));
00565        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(user_data);
00566 
00567        g_signal_connect(G_OBJECT(newentry), DBUSMENU_MENUITEM_SIGNAL_REALIZED, G_CALLBACK(menu_entry_realized), user_data);
00568 
00569        GtkMenuItem * mi = dbusmenu_gtkclient_menuitem_get(priv->client, newentry);
00570        if (mi != NULL) {
00571               menu_entry_realized(newentry, user_data);
00572        }
00573        return;
00574 }
00575 
00576 /* A small clean up function to ensure that the data
00577    gets free'd and the ref lost in all cases. */
00578 static void
00579 child_realized_data_cleanup (gpointer user_data, GClosure * closure)
00580 {
00581        gpointer * data = (gpointer *)user_data;
00582        g_object_unref(data[1]);
00583        g_free(user_data);
00584        return;
00585 }
00586 
00587 /* This is the callback for when we're done waiting for a new entry to have
00588    children */
00589 static void
00590 menu_entry_realized_child_added (DbusmenuMenuitem * parent,
00591                                  DbusmenuMenuitem * child,
00592                                  guint position, gpointer user_data)
00593 {
00594        /* Child added may be called more than once, so make sure we only
00595           handle it once by disconnecting. */
00596        g_signal_handlers_disconnect_by_func(G_OBJECT(parent), G_CALLBACK(menu_entry_realized_child_added), user_data);
00597 
00598        menu_entry_realized (parent, user_data);
00599 }
00600 
00601 /* React to the menuitem when we know that it's got all the data
00602    that we really need. */
00603 static void
00604 menu_entry_realized (DbusmenuMenuitem * newentry, gpointer user_data)
00605 {
00606        g_return_if_fail(IS_WINDOW_MENU_DBUSMENU(user_data));
00607        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(user_data);
00608 
00609        GtkMenu * menu = dbusmenu_gtkclient_menuitem_get_submenu(priv->client, newentry);
00610 
00611        /* Check to see if we have any children, if we don't let's see if
00612           we can scare some up for fun. */
00613        GList * children = dbusmenu_menuitem_get_children(newentry);
00614        if (children == NULL && g_strcmp0(DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU, dbusmenu_menuitem_property_get(newentry, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY)) == 0) {
00615               dbusmenu_menuitem_send_about_to_show(newentry, NULL, NULL);
00616        }
00617 
00618        if (menu == NULL) {
00619               if (children != NULL) {
00620                      gpointer * data = g_new(gpointer, 2);
00621                      data[0] = user_data;
00622                      data[1] = g_object_ref(newentry);
00623 
00624                      g_signal_connect_data(G_OBJECT(children->data), DBUSMENU_MENUITEM_SIGNAL_REALIZED, G_CALLBACK(menu_child_realized), data, child_realized_data_cleanup, 0);
00625               } else {
00626                      g_signal_connect(G_OBJECT(newentry), DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED, G_CALLBACK(menu_entry_realized_child_added), user_data);
00627               }
00628        } else {
00629               gpointer * data = g_new(gpointer, 2);
00630               data[0] = user_data;
00631               data[1] = newentry;
00632 
00633               menu_child_realized(NULL, data);
00634               g_free (data);
00635        }
00636        
00637        return;
00638 }
00639 
00640 /* Respond to properties changing on the menu item so that we can
00641    properly hide and show them. */
00642 static void
00643 menu_prop_changed (DbusmenuMenuitem * item, const gchar * property, GVariant * value, gpointer user_data)
00644 {
00645        IndicatorObjectEntry * entry = (IndicatorObjectEntry *)user_data;
00646        WMEntry * wmentry = (WMEntry *)user_data;
00647 
00648        if (!g_strcmp0(property, DBUSMENU_MENUITEM_PROP_VISIBLE)) {
00649               if (g_variant_get_boolean(value)) {
00650                      gtk_widget_show(GTK_WIDGET(entry->label));
00651                      wmentry->hidden = FALSE;
00652               } else {
00653                      gtk_widget_hide(GTK_WIDGET(entry->label));
00654                      wmentry->hidden = TRUE;
00655               }
00656        } else if (!g_strcmp0(property, DBUSMENU_MENUITEM_PROP_ENABLED)) {
00657               gtk_widget_set_sensitive(GTK_WIDGET(entry->label), g_variant_get_boolean(value));
00658               wmentry->disabled = !g_variant_get_boolean(value);
00659        } else if (!g_strcmp0(property, DBUSMENU_MENUITEM_PROP_LABEL)) {
00660               gtk_label_set_text_with_mnemonic(entry->label, g_variant_get_string(value, NULL));
00661               entry->accessible_desc = g_variant_get_string(value, NULL);
00662 
00663               if (wmentry->wm != NULL) {
00664                      g_signal_emit_by_name(G_OBJECT(wmentry->wm), WINDOW_MENU_SIGNAL_A11Y_UPDATE, entry, TRUE);
00665               }
00666        }
00667 
00668        return;
00669 }
00670 
00671 /* We can't go until we have some kids.  Really, it's important. */
00672 static void
00673 menu_child_realized (DbusmenuMenuitem * child, gpointer user_data)
00674 {
00675        /* Grab our values out to stack variables */
00676        DbusmenuMenuitem * newentry = DBUSMENU_MENUITEM(((gpointer *)user_data)[1]);
00677        WindowMenuDbusmenu * wm = WINDOW_MENU_DBUSMENU(((gpointer *)user_data)[0]);
00678 
00679        g_return_if_fail(newentry != NULL);
00680        g_return_if_fail(wm != NULL);
00681 
00682        /* Disconnection below will drop the ref for this signal
00683           handler, let's make sure that's not a problem */
00684        g_object_ref(G_OBJECT(newentry));
00685 
00686        /* Only care about the first */
00687        /* This will cause the cleanup function attached to the signal
00688           handler to be run. */
00689        if (child != NULL) {
00690               g_signal_handlers_disconnect_by_func(G_OBJECT(child), menu_child_realized, user_data);
00691        }
00692 
00693        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(wm);
00694        WMEntry * wmentry = g_new0(WMEntry, 1);
00695        wmentry->wm = wm;
00696        IndicatorObjectEntry * entry = &wmentry->ioentry;
00697 
00698        wmentry->mi = newentry;
00699        g_object_ref(G_OBJECT(wmentry->mi));
00700 
00701        entry->label = GTK_LABEL(gtk_label_new_with_mnemonic(dbusmenu_menuitem_property_get(newentry, DBUSMENU_MENUITEM_PROP_LABEL)));
00702 
00703        if (entry->label != NULL) {
00704               g_object_ref_sink(entry->label);
00705        }
00706 
00707        entry->accessible_desc = dbusmenu_menuitem_property_get(newentry, DBUSMENU_MENUITEM_PROP_LABEL);
00708 
00709        entry->menu = dbusmenu_gtkclient_menuitem_get_submenu(priv->client, newentry);
00710 
00711        if (entry->menu == NULL) {
00712               g_debug("Submenu for %s is NULL", dbusmenu_menuitem_property_get(newentry, DBUSMENU_MENUITEM_PROP_LABEL));
00713        } else {
00714               g_object_ref(entry->menu);
00715               gtk_menu_detach(entry->menu);
00716               g_signal_connect(entry->menu, "destroy", G_CALLBACK(gtk_widget_destroyed), &entry->menu);
00717        }
00718 
00719        g_signal_connect(G_OBJECT(newentry), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menu_prop_changed), entry);
00720 
00721        if (dbusmenu_menuitem_property_get_variant(newentry, DBUSMENU_MENUITEM_PROP_VISIBLE) != NULL
00722               && dbusmenu_menuitem_property_get_bool(newentry, DBUSMENU_MENUITEM_PROP_VISIBLE) == FALSE) {
00723               gtk_widget_hide(GTK_WIDGET(entry->label));
00724               wmentry->hidden = TRUE;
00725        } else {
00726               gtk_widget_show(GTK_WIDGET(entry->label));
00727               wmentry->hidden = FALSE;
00728        }
00729 
00730        if (dbusmenu_menuitem_property_get_variant (newentry, DBUSMENU_MENUITEM_PROP_ENABLED) != NULL) {
00731               gboolean sensitive = dbusmenu_menuitem_property_get_bool(newentry, DBUSMENU_MENUITEM_PROP_ENABLED);
00732               gtk_widget_set_sensitive(GTK_WIDGET(entry->label), sensitive);
00733               wmentry->disabled = !sensitive;
00734        }
00735 
00736        g_array_append_val(priv->entries, wmentry);
00737 
00738        g_signal_emit_by_name(G_OBJECT(wm), WINDOW_MENU_SIGNAL_ENTRY_ADDED, entry, TRUE);
00739 
00740        g_object_unref(newentry);
00741 
00742        guint pos = dbusmenu_menuitem_get_position (newentry,
00743                                                    dbusmenu_menuitem_get_parent (newentry));
00744        if (pos < priv->entries->len - 1) {
00745               /* Our entry added signals that we pass up don't have position
00746                  information, so we can only add to the end of the list of
00747                  entries.  So when we get an inserted-in-the-middle entry,
00748                  we fake a root change to remove all entries and add them
00749                  back up again */
00750               g_object_ref(priv->root);
00751               root_changed(DBUSMENU_CLIENT(priv->client), priv->root, wm);
00752               g_object_unref(priv->root);
00753        }
00754 
00755        return;
00756 }
00757 
00758 /* Respond to an entry getting removed from the menu */
00759 static void
00760 menu_entry_removed (DbusmenuMenuitem * root, DbusmenuMenuitem * oldentry, gpointer user_data)
00761 {
00762        g_return_if_fail(IS_WINDOW_MENU_DBUSMENU(user_data));
00763        g_return_if_fail(DBUSMENU_IS_MENUITEM(oldentry));
00764        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(user_data);
00765 
00766        if (priv->entries == NULL || priv->entries->len == 0) {
00767               return;
00768        }
00769 
00770        guint position;
00771        IndicatorObjectEntry * entry = get_entry(WINDOW_MENU_DBUSMENU(user_data), oldentry, &position);
00772 
00773        if (entry != NULL) {
00774               g_array_remove_index(priv->entries, position);
00775               g_signal_emit_by_name(G_OBJECT(user_data), WINDOW_MENU_SIGNAL_ENTRY_REMOVED, entry, TRUE);
00776               entry_free(entry);
00777        } else {
00778               /* We've been called before menu_child_realized fired,
00779                * so there isn't a WMEntry yet. Don't be going ahead
00780                * and creating one if this menu item continues to live! */
00781               g_signal_handlers_disconnect_by_func(G_OBJECT(oldentry), G_CALLBACK(menu_entry_realized), user_data);
00782               g_signal_handlers_disconnect_by_func(G_OBJECT(oldentry), G_CALLBACK(menu_entry_realized_child_added), user_data);
00783        }
00784 
00785        return;
00786 }
00787 
00788 /* Get the XID of this window */
00789 static guint
00790 get_xid (WindowMenu * wm)
00791 {
00792        g_return_val_if_fail(IS_WINDOW_MENU_DBUSMENU(wm), 0);
00793        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(wm);
00794        return priv->windowid;
00795 }
00796 
00797 /* Get the path for this object */
00798 gchar *
00799 window_menu_dbusmenu_get_path (WindowMenuDbusmenu * wm)
00800 {
00801        g_return_val_if_fail(IS_WINDOW_MENU_DBUSMENU(wm), NULL);
00802        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(wm);
00803        GValue obj = {0};
00804        g_value_init(&obj, G_TYPE_STRING);
00805        g_object_get_property(G_OBJECT(priv->client), DBUSMENU_CLIENT_PROP_DBUS_OBJECT, &obj);
00806        gchar * retval = g_value_dup_string(&obj);
00807        g_value_unset(&obj);
00808        return retval;
00809 }
00810 
00811 /* Get the address of this object */
00812 gchar *
00813 window_menu_dbusmenu_get_address (WindowMenuDbusmenu * wm)
00814 {
00815        g_return_val_if_fail(IS_WINDOW_MENU_DBUSMENU(wm), NULL);
00816        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(wm);
00817        GValue obj = {0};
00818        g_value_init(&obj, G_TYPE_STRING);
00819        g_object_get_property(G_OBJECT(priv->client), DBUSMENU_CLIENT_PROP_DBUS_NAME, &obj);
00820        gchar * retval = g_value_dup_string(&obj);
00821        g_value_unset(&obj);
00822        return retval;
00823 }
00824 
00825 /* Return whether we're in an error state or not */
00826 static gboolean
00827 get_error_state (WindowMenu * wm)
00828 {
00829        g_return_val_if_fail(IS_WINDOW_MENU_DBUSMENU(wm), TRUE);
00830        WindowMenuDbusmenuPrivate * priv = WINDOW_MENU_DBUSMENU_GET_PRIVATE(wm);
00831        return priv->error_state;
00832 }
00833 
00834 /* Regain whether we're supposed to be hidden or disabled, we
00835    want to keep that if that's the case, otherwise bring back
00836    to the base state */
00837 static void
00838 entry_restore (WindowMenu * wm, IndicatorObjectEntry * entry)
00839 {
00840        g_return_if_fail(IS_WINDOW_MENU_DBUSMENU(wm));
00841        WMEntry * wmentry = (WMEntry *)entry;
00842 
00843        if (entry->label != NULL) {
00844               gtk_widget_set_sensitive(GTK_WIDGET(entry->label), !wmentry->disabled);
00845               if (wmentry->hidden) {
00846                      gtk_widget_hide(GTK_WIDGET(entry->label));
00847               } else {
00848                      gtk_widget_show(GTK_WIDGET(entry->label));
00849               }
00850        }
00851 
00852        if (entry->image != NULL) {
00853               gtk_widget_set_sensitive(GTK_WIDGET(entry->image), !wmentry->disabled);
00854               if (wmentry->hidden) {
00855                      gtk_widget_hide(GTK_WIDGET(entry->image));
00856               } else {
00857                      gtk_widget_show(GTK_WIDGET(entry->image));
00858               }
00859        }
00860 
00861        return;
00862 }
00863 
00864 /* Signaled when the menu item is activated on the panel so we
00865    can pass it down the stack. */
00866 static void
00867 entry_activate (WindowMenu * wm, IndicatorObjectEntry * entry, guint timestamp)
00868 {
00869        g_return_if_fail(IS_WINDOW_MENU_DBUSMENU(wm));
00870        WMEntry * wme = (WMEntry *)entry;
00871        dbusmenu_menuitem_send_about_to_show(wme->mi, NULL, NULL);
00872        return;
00873 }