Back to index

unity  6.0.0
panel-service.c
Go to the documentation of this file.
00001 // -*- Mode: C; indent-tabs-mode: nil; tab-width: 2 -*-
00002 /*
00003  * Copyright (C) 2010-2012 Canonical Ltd
00004  *
00005  * This program is free software: you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License version 3 as
00007  * published by the Free Software Foundation.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00016  *
00017  * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
00018  *              Rodrigo Moya <rodrigo.moya@canonical.com>
00019  *              Marco Trevisan (TreviƱo) <mail@3v1n0.net>
00020  */
00021 
00022 #if HAVE_CONFIG_H
00023 #include <config.h>
00024 #endif
00025 
00026 #include "panel-marshal.h"
00027 #include "panel-service.h"
00028 
00029 #include <stdlib.h>
00030 #include <string.h>
00031 #include <gtk/gtk.h>
00032 #include <gdk/gdkx.h>
00033 #include <gconf/gconf-client.h>
00034 
00035 #include <X11/extensions/XInput2.h>
00036 #include <X11/XKBlib.h>
00037 
00038 #include "panel-marshal.h"
00039 
00040 G_DEFINE_TYPE (PanelService, panel_service, G_TYPE_OBJECT);
00041 
00042 #define GET_PRIVATE(o) \
00043   (G_TYPE_INSTANCE_GET_PRIVATE ((o), PANEL_TYPE_SERVICE, PanelServicePrivate))
00044 
00045 #define NOTIFY_TIMEOUT 80
00046 #define N_TIMEOUT_SLOTS 50
00047 #define MAX_INDICATOR_ENTRIES 50
00048 
00049 #define COMPIZ_OPTIONS_PATH "/apps/compiz-1/plugins/unityshell/screen0/options"
00050 #define MENU_TOGGLE_KEYBINDING_PATH COMPIZ_OPTIONS_PATH"/panel_first_menu"
00051 
00052 static PanelService *static_service = NULL;
00053 
00054 struct _PanelServicePrivate
00055 {
00056   GSList     *indicators;
00057   GHashTable *id2entry_hash;
00058   GHashTable *panel2entries_hash;
00059 
00060   guint  initial_sync_id;
00061   gint32 timeouts[N_TIMEOUT_SLOTS];
00062 
00063   IndicatorObjectEntry *last_entry;
00064   GtkWidget *menubar;
00065   GtkWidget *offscreen_window;
00066   GtkMenu *last_menu;
00067   guint32  last_menu_id;
00068   guint32  last_menu_move_id;
00069   gint32   last_x;
00070   gint32   last_y;
00071   gint     last_left;
00072   gint     last_top;
00073   gint     last_right;
00074   gint     last_bottom;
00075   guint32  last_menu_button;
00076 
00077   KeyCode toggle_key;
00078   guint32 toggle_modifiers;
00079   guint32 key_monitor_id;
00080 
00081   IndicatorObjectEntry *pressed_entry;
00082   gboolean use_event;
00083 };
00084 
00085 /* Globals */
00086 static gboolean suppress_signals = FALSE;
00087 
00088 enum
00089 {
00090   ENTRY_ACTIVATED = 0,
00091   RE_SYNC,
00092   ENTRY_ACTIVATE_REQUEST,
00093   ENTRY_SHOW_NOW_CHANGED,
00094   GEOMETRIES_CHANGED,
00095   INDICATORS_CLEARED,
00096 
00097   LAST_SIGNAL
00098 };
00099 
00100 enum
00101 {
00102   SYNC_WAITING = -1,
00103   SYNC_NEUTRAL = 0,
00104 };
00105 
00106 static guint32 _service_signals[LAST_SIGNAL] = { 0 };
00107 
00108 static const gchar * indicator_order[][2] = {
00109   {"libappmenu.so", NULL},                    /* indicator-appmenu" */
00110   {"libapplication.so", NULL},                /* indicator-application" */
00111   {"libprintersmenu.so", NULL},               /* indicator-printers */
00112   {"libapplication.so", "gsd-keyboard-xkb"},  /* keyboard layout selector */
00113   {"libmessaging.so", NULL},                  /* indicator-messages */
00114   {"libpower.so", NULL},                      /* indicator-power */
00115   {"libapplication.so", "bluetooth-manager"}, /* bluetooth manager */
00116   {"libnetwork.so", NULL},                    /* indicator-network */
00117   {"libnetworkmenu.so", NULL},                /* indicator-network */
00118   {"libapplication.so", "nm-applet"},         /* network manager */
00119   {"libsoundmenu.so", NULL},                  /* indicator-sound */
00120   {"libdatetime.so", NULL},                   /* indicator-datetime */
00121   {"libsession.so", NULL},                    /* indicator-session */
00122   {NULL, NULL}
00123 };
00124 
00125 /* Forwards */
00126 static void load_indicator  (PanelService    *self,
00127                              IndicatorObject *object,
00128                              const gchar     *_name);
00129 static void load_indicators (PanelService    *self);
00130 static void sort_indicators (PanelService    *self);
00131 
00132 static void notify_object (IndicatorObject *object);
00133 
00134 static GdkFilterReturn event_filter (GdkXEvent    *ev,
00135                                      GdkEvent     *gev,
00136                                      PanelService *self);
00137 
00138 /*
00139  * GObject stuff
00140  */
00141 
00142 static void
00143 panel_service_class_dispose (GObject *object)
00144 {
00145   PanelServicePrivate *priv = PANEL_SERVICE (object)->priv;
00146   gint i;
00147 
00148   g_hash_table_destroy (priv->id2entry_hash);
00149   g_hash_table_destroy (priv->panel2entries_hash);
00150 
00151   gdk_window_remove_filter (NULL, (GdkFilterFunc)event_filter, object);
00152 
00153   if (GTK_IS_WIDGET (priv->menubar) &&
00154       gtk_widget_get_realized (GTK_WIDGET (priv->menubar)))
00155     {
00156       g_object_unref (priv->menubar);
00157       priv->menubar = NULL;
00158     }
00159 
00160   if (GTK_IS_WIDGET (priv->offscreen_window) &&
00161       gtk_widget_get_realized (GTK_WIDGET (priv->offscreen_window)))
00162     {
00163       g_object_unref (priv->offscreen_window);
00164       priv->offscreen_window = NULL;
00165     }
00166 
00167   if (GTK_IS_WIDGET (priv->last_menu) &&
00168       gtk_widget_get_realized (GTK_WIDGET (priv->last_menu)))
00169     {
00170       g_object_unref (priv->last_menu);
00171       priv->last_menu = NULL;
00172     }
00173 
00174   if (priv->initial_sync_id)
00175     {
00176       g_source_remove (priv->initial_sync_id);
00177       priv->initial_sync_id = 0;
00178     }
00179 
00180   for (i = 0; i < N_TIMEOUT_SLOTS; i++)
00181     {
00182       if (priv->timeouts[i] > 0)
00183         {
00184           g_source_remove (priv->timeouts[i]);
00185           priv->timeouts[i] = 0;
00186         }
00187     }
00188 
00189   if (priv->key_monitor_id)
00190     {
00191       gconf_client_notify_remove (gconf_client_get_default(), priv->key_monitor_id);
00192       priv->key_monitor_id = 0;
00193     }
00194 
00195   G_OBJECT_CLASS (panel_service_parent_class)->dispose (object);
00196 }
00197 
00198 static void
00199 panel_service_class_finalize (GObject *object)
00200 {
00201   static_service = NULL;
00202 }
00203 
00204 static void
00205 panel_service_class_init (PanelServiceClass *klass)
00206 {
00207   GObjectClass *obj_class = G_OBJECT_CLASS (klass);
00208 
00209   obj_class->dispose = panel_service_class_dispose;
00210   obj_class->finalize = panel_service_class_finalize;
00211 
00212   /* Signals */
00213   _service_signals[ENTRY_ACTIVATED] =
00214     g_signal_new ("entry-activated",
00215                   G_OBJECT_CLASS_TYPE (obj_class),
00216                   G_SIGNAL_RUN_LAST,
00217                   0,
00218                   NULL, NULL,
00219                   panel_marshal_VOID__STRING_INT_INT_UINT_UINT,
00220                   G_TYPE_NONE, 5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT,
00221                   G_TYPE_UINT, G_TYPE_UINT);
00222 
00223   _service_signals[RE_SYNC] =
00224     g_signal_new ("re-sync",
00225                   G_OBJECT_CLASS_TYPE (obj_class),
00226                   G_SIGNAL_RUN_LAST,
00227                   0,
00228                   NULL, NULL,
00229                   g_cclosure_marshal_VOID__STRING,
00230                   G_TYPE_NONE, 1, G_TYPE_STRING);
00231 
00232  _service_signals[ENTRY_ACTIVATE_REQUEST] =
00233     g_signal_new ("entry-activate-request",
00234                   G_OBJECT_CLASS_TYPE (obj_class),
00235                   G_SIGNAL_RUN_LAST,
00236                   0,
00237                   NULL, NULL,
00238                   g_cclosure_marshal_VOID__STRING,
00239                   G_TYPE_NONE, 1, G_TYPE_STRING);
00240 
00241  _service_signals[GEOMETRIES_CHANGED] =
00242     g_signal_new ("geometries-changed",
00243       G_OBJECT_CLASS_TYPE (obj_class),
00244       G_SIGNAL_RUN_LAST,
00245       0,
00246       NULL, NULL,
00247       panel_marshal_VOID__OBJECT_POINTER_INT_INT_INT_INT,
00248       G_TYPE_NONE, 6,
00249       G_TYPE_OBJECT, G_TYPE_POINTER,
00250       G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
00251 
00252  _service_signals[ENTRY_SHOW_NOW_CHANGED] =
00253     g_signal_new ("entry-show-now-changed",
00254                   G_OBJECT_CLASS_TYPE (obj_class),
00255                   G_SIGNAL_RUN_LAST,
00256                   0,
00257                   NULL, NULL,
00258                   panel_marshal_VOID__STRING_BOOLEAN,
00259                   G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN);
00260 
00261   _service_signals[INDICATORS_CLEARED] =
00262     g_signal_new ("indicators-cleared",
00263                   G_OBJECT_CLASS_TYPE (obj_class),
00264                   G_SIGNAL_RUN_LAST,
00265                   0,
00266                   NULL, NULL,
00267                   g_cclosure_marshal_VOID__VOID,
00268                   G_TYPE_NONE, 0);
00269 
00270 
00271   g_type_class_add_private (obj_class, sizeof (PanelServicePrivate));
00272 }
00273 
00274 static IndicatorObjectEntry *
00275 get_entry_at (PanelService *self, gint x, gint y)
00276 {
00277   GHashTableIter panel_iter, entries_iter;
00278   gpointer key, value, k, v;
00279 
00280   g_hash_table_iter_init (&panel_iter, self->priv->panel2entries_hash);
00281   while (g_hash_table_iter_next (&panel_iter, &key, &value))
00282     {
00283       GHashTable *entry2geometry_hash = value;
00284       g_hash_table_iter_init (&entries_iter, entry2geometry_hash);
00285 
00286       while (g_hash_table_iter_next (&entries_iter, &k, &v))
00287         {
00288           IndicatorObjectEntry *entry = k;
00289           GdkRectangle *geo = v;
00290 
00291           if (x >= geo->x && x <= (geo->x + geo->width) &&
00292               y >= geo->y && y <= (geo->y + geo->height))
00293             {
00294               return entry;
00295             }
00296         }
00297     }
00298 
00299   return NULL;
00300 }
00301 
00302 static IndicatorObject *
00303 get_entry_parent_indicator (IndicatorObjectEntry *entry)
00304 {
00305   if (!entry || !INDICATOR_IS_OBJECT (entry->parent_object))
00306     return NULL;
00307 
00308   return INDICATOR_OBJECT (entry->parent_object);
00309 }
00310 
00311 static gchar *
00312 get_indicator_entry_id_by_entry (IndicatorObjectEntry *entry)
00313 {
00314   gchar *entry_id = NULL;
00315 
00316   if (entry)
00317     entry_id = g_strdup_printf ("%p", entry);
00318 
00319   return entry_id;
00320 }
00321 
00322 static IndicatorObjectEntry *
00323 get_indicator_entry_by_id (PanelService *self, const gchar *entry_id)
00324 {
00325   IndicatorObjectEntry *entry;
00326 
00327   entry = g_hash_table_lookup (self->priv->id2entry_hash, entry_id);
00328 
00329   if (entry)
00330     {
00331       if (g_slist_find (self->priv->indicators, entry->parent_object))
00332         {
00333           if (!INDICATOR_IS_OBJECT (entry->parent_object))
00334             entry = NULL;
00335         }
00336       else
00337         {
00338           entry = NULL;
00339         }
00340 
00341       if (!entry)
00342         {
00343           g_warning("The entry id '%s' you're trying to lookup is not a valid IndicatorObjectEntry!", entry_id);
00344         }
00345     }
00346 
00347   return entry;
00348 }
00349 
00350 static GdkFilterReturn
00351 event_filter (GdkXEvent *ev, GdkEvent *gev, PanelService *self)
00352 {
00353   PanelServicePrivate *priv = self->priv;
00354   XEvent *e = (XEvent *)ev;
00355   GdkFilterReturn ret = GDK_FILTER_CONTINUE;
00356 
00357   if (!PANEL_IS_SERVICE (self))
00358   {
00359     g_warning ("%s: Invalid PanelService instance", G_STRLOC);
00360     return ret;
00361   }
00362 
00363   if (!GTK_IS_WIDGET (self->priv->last_menu))
00364     return ret;
00365 
00366   /* Use XI2 to read the event data */
00367   XGenericEventCookie *cookie = &e->xcookie;
00368   if (cookie->type == GenericEvent)
00369     {
00370       XIDeviceEvent *event = cookie->data;
00371       if (!event)
00372         return ret;
00373 
00374       if (event->evtype == XI_KeyPress)
00375         {
00376           if (event->mods.base == priv->toggle_modifiers && event->detail == priv->toggle_key)
00377           {
00378             if (GTK_IS_MENU (priv->last_menu))
00379               gtk_menu_popdown (GTK_MENU (priv->last_menu));
00380           }
00381         }
00382 
00383       if (event->evtype == XI_ButtonPress)
00384         {
00385           priv->pressed_entry = get_entry_at (self, event->root_x, event->root_y);
00386           priv->use_event = (priv->pressed_entry == NULL);
00387 
00388           if (priv->pressed_entry)
00389             ret = GDK_FILTER_REMOVE;
00390         }
00391 
00392       if (event->evtype == XI_ButtonRelease)
00393         {
00394           IndicatorObjectEntry *entry;
00395           gboolean event_is_a_click = FALSE;
00396           entry = get_entry_at (self, event->root_x, event->root_y);
00397 
00398           if (event->detail == 1 || event->detail == 3)
00399             {
00400               /* Consider only right and left clicks over the indicators entries */
00401               event_is_a_click = TRUE;
00402             }
00403           else if (entry && event->detail == 2)
00404             {
00405               /* Middle clicks over an appmenu entry are considered just like
00406                * all other clicks */
00407               IndicatorObject *obj = get_entry_parent_indicator (entry);
00408 
00409               if (g_strcmp0 (g_object_get_data (G_OBJECT (obj), "id"), "libappmenu.so") == 0)
00410                 {
00411                   event_is_a_click = TRUE;
00412                 }
00413             }
00414 
00415           if (event_is_a_click)
00416             {
00417               if (priv->use_event)
00418                 {
00419                   priv->use_event = FALSE;
00420                 }
00421               else
00422                 {
00423                   if (entry)
00424                     {
00425                       if (entry != priv->pressed_entry)
00426                         {
00427                           ret = GDK_FILTER_REMOVE;
00428                           priv->use_event = TRUE;
00429                         }
00430                       else if (priv->last_entry && entry != priv->last_entry)
00431                         {
00432                           /* If we were navigating over indicators using the keyboard
00433                            * and now we click over the indicator under the mouse, we
00434                            * must force it to show back again, not make it close */
00435                           gchar *entry_id = get_indicator_entry_id_by_entry (entry);
00436                           g_signal_emit (self, _service_signals[ENTRY_ACTIVATE_REQUEST], 0, entry_id);
00437                           g_free (entry_id);
00438                         }
00439                     }
00440                 }
00441             }
00442           else if (entry && (event->detail == 2 || event->detail == 4 || event->detail == 5))
00443             {
00444               /* If we're scrolling or middle-clicking over an indicator
00445                * (which is not an appmenu entry) then we need to send the
00446                * event to the indicator itself, and avoid it to close */
00447               gchar *entry_id = get_indicator_entry_id_by_entry (entry);
00448 
00449               if (event->detail == 4 || event->detail == 5)
00450                 {
00451                   gint32 delta = (event->detail == 4) ? 120 : -120;
00452                   panel_service_scroll_entry (self, entry_id, delta);
00453                 }
00454               else if (entry == priv->pressed_entry)
00455                 {
00456                   panel_service_secondary_activate_entry (self, entry_id, time(NULL));
00457                 }
00458 
00459               ret = GDK_FILTER_REMOVE;
00460               g_free (entry_id);
00461             }
00462         }
00463     }
00464 
00465   return ret;
00466 }
00467 
00468 static gboolean
00469 initial_resync (PanelService *self)
00470 {
00471   if (PANEL_IS_SERVICE (self))
00472     {
00473       g_signal_emit (self, _service_signals[RE_SYNC], 0, "");
00474       self->priv->initial_sync_id = 0;
00475     }
00476   return FALSE;
00477 }
00478 
00479 static void
00480 panel_service_update_menu_keybinding (PanelService *self)
00481 {
00482   GConfClient *client = gconf_client_get_default ();
00483   gchar *binding = gconf_client_get_string (client, MENU_TOGGLE_KEYBINDING_PATH, NULL);
00484 
00485   KeyCode keycode = 0;
00486   KeySym keysym = NoSymbol;
00487   guint32 modifiers = 0;
00488 
00489   gchar *keystart = (binding) ? strrchr (binding, '>') : NULL;
00490 
00491   if (!keystart)
00492     keystart = binding;
00493 
00494   while (keystart && !g_ascii_isalnum (*keystart))
00495     keystart++;
00496 
00497   gchar *keyend = keystart;
00498 
00499   while (keyend && g_ascii_isalnum (*keyend))
00500     keyend++;
00501 
00502   if (keystart != keyend)
00503   {
00504     gchar *keystr = g_strndup (keystart, keyend-keystart);
00505     keysym = XStringToKeysym (keystr);
00506     g_free (keystr);
00507   }
00508 
00509   if (keysym != NoSymbol)
00510     {
00511       Display *dpy = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
00512       keycode = XKeysymToKeycode(dpy, keysym);
00513 
00514       if (g_strrstr (binding, "<Shift>"))
00515         {
00516           modifiers |= GDK_SHIFT_MASK;
00517         }
00518       if (g_strrstr (binding, "<Control>"))
00519         {
00520           modifiers |= GDK_CONTROL_MASK;
00521         }
00522       if (g_strrstr (binding, "<Alt>") || g_strrstr (binding, "<Mod1>"))
00523         {
00524           modifiers |= GDK_MOD1_MASK;
00525         }
00526       if (g_strrstr (binding, "<Super>"))
00527         {
00528           modifiers |= GDK_SUPER_MASK;
00529         }
00530     }
00531 
00532   self->priv->toggle_key = keycode;
00533   self->priv->toggle_modifiers = modifiers;
00534 
00535   g_free (binding);
00536 }
00537 
00538 void
00539 on_keybinding_changed (GConfClient* client, guint id, GConfEntry* entry, gpointer data)
00540 {
00541   PanelService *self = data;
00542   g_return_if_fail (PANEL_IS_SERVICE (data));
00543 
00544   panel_service_update_menu_keybinding (self);
00545 }
00546 
00547 static void
00548 panel_service_init (PanelService *self)
00549 {
00550   PanelServicePrivate *priv;
00551   priv = self->priv = GET_PRIVATE (self);
00552 
00553   priv->offscreen_window = gtk_offscreen_window_new ();
00554   priv->menubar = gtk_menu_bar_new ();
00555   gtk_container_add (GTK_CONTAINER (priv->offscreen_window), priv->menubar);
00556 
00557   gdk_window_add_filter (NULL, (GdkFilterFunc)event_filter, self);
00558 
00559   priv->id2entry_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
00560   priv->panel2entries_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
00561                                                     g_free,
00562                                                     (GDestroyNotify) g_hash_table_destroy);
00563 
00564   suppress_signals = TRUE;
00565   load_indicators (self);
00566   sort_indicators (self);
00567   suppress_signals = FALSE;
00568 
00569   panel_service_update_menu_keybinding (self);
00570 
00571   GConfClient *client = gconf_client_get_default ();
00572   gconf_client_add_dir (client, COMPIZ_OPTIONS_PATH, GCONF_CLIENT_PRELOAD_NONE, NULL);
00573   priv->key_monitor_id = gconf_client_notify_add (client, MENU_TOGGLE_KEYBINDING_PATH,
00574                                                   on_keybinding_changed, self,
00575                                                   NULL, NULL);
00576 
00577   priv->initial_sync_id = g_idle_add ((GSourceFunc)initial_resync, self);
00578 }
00579 
00580 static gboolean
00581 panel_service_check_cleared (PanelService *self)
00582 {
00583   if (self->priv->indicators == NULL)
00584     {
00585       g_signal_emit (self, _service_signals[INDICATORS_CLEARED], 0);
00586       return FALSE;
00587     }
00588 
00589   return TRUE;
00590 }
00591 
00592 static void
00593 panel_service_actually_remove_indicator (PanelService *self, IndicatorObject *indicator)
00594 {
00595   g_return_if_fail (PANEL_IS_SERVICE (self));
00596   g_return_if_fail (INDICATOR_IS_OBJECT (indicator));
00597 
00598   GList *entries, *l;
00599 
00600   entries = indicator_object_get_entries (indicator);
00601 
00602   if (entries)
00603     {
00604       for (l = entries; l; l = l->next)
00605         {
00606           IndicatorObjectEntry *entry;
00607           gchar *entry_id;
00608           GHashTableIter iter;
00609           gpointer key, value;
00610 
00611           entry = l->data;
00612           entry_id = get_indicator_entry_id_by_entry (entry);
00613           g_hash_table_remove (self->priv->id2entry_hash, entry_id);
00614           g_free (entry_id);
00615 
00616           g_hash_table_iter_init (&iter, self->priv->panel2entries_hash);
00617           while (g_hash_table_iter_next (&iter, &key, &value))
00618             {
00619               GHashTable *entry2geometry_hash = value;
00620 
00621               if (g_hash_table_size (entry2geometry_hash) > 1)
00622                 g_hash_table_remove (entry2geometry_hash, entry);
00623               else
00624                 g_hash_table_iter_remove (&iter);
00625             }
00626         }
00627 
00628       g_list_free (entries);
00629     }
00630 
00631   self->priv->indicators = g_slist_remove (self->priv->indicators, indicator);
00632 
00633   if (g_object_is_floating (G_OBJECT (indicator)))
00634     {
00635       g_object_ref_sink (G_OBJECT (indicator));
00636     }
00637 
00638   g_object_unref (G_OBJECT (indicator));
00639 }
00640 
00641 static gboolean
00642 panel_service_indicator_remove_timeout (IndicatorObject *indicator)
00643 {
00644   PanelService *self = panel_service_get_default ();
00645   panel_service_actually_remove_indicator (self, indicator);
00646 
00647   return FALSE;
00648 }
00649 
00650 PanelService *
00651 panel_service_get_default ()
00652 {
00653   if (static_service == NULL || !PANEL_IS_SERVICE (static_service))
00654     static_service = g_object_new (PANEL_TYPE_SERVICE, NULL);
00655 
00656   return static_service;
00657 }
00658 
00659 PanelService *
00660 panel_service_get_default_with_indicators (GList *indicators)
00661 {
00662   PanelService *service = panel_service_get_default ();
00663   GList        *i;
00664 
00665   for (i = indicators; i; i = i->next)
00666     {
00667       IndicatorObject *object = i->data;
00668       if (INDICATOR_IS_OBJECT (object))
00669           load_indicator (service, object, NULL);
00670     }
00671 
00672   return service;
00673 }
00674 guint
00675 panel_service_get_n_indicators (PanelService *self)
00676 {
00677   g_return_val_if_fail (PANEL_IS_SERVICE (self), 0);
00678 
00679   return g_slist_length (self->priv->indicators);
00680 }
00681 
00682 IndicatorObject *
00683 panel_service_get_indicator_nth (PanelService *self, guint position)
00684 {
00685   g_return_val_if_fail (PANEL_IS_SERVICE (self), NULL);
00686 
00687   return (IndicatorObject *) g_slist_nth_data (self->priv->indicators, position);
00688 }
00689 
00690 IndicatorObject *
00691 panel_service_get_indicator (PanelService *self, const gchar *indicator_id)
00692 {
00693   g_return_val_if_fail (PANEL_IS_SERVICE (self), NULL);
00694   GSList *l;
00695 
00696   for (l = self->priv->indicators; l; l = l->next)
00697     {
00698       if (g_strcmp0 (indicator_id,
00699                      g_object_get_data (G_OBJECT (l->data), "id")) == 0)
00700         {
00701           return (IndicatorObject *) l->data;
00702         }
00703     }
00704 
00705   return NULL;
00706 }
00707 
00708 void
00709 panel_service_remove_indicator (PanelService *self, IndicatorObject *indicator)
00710 {
00711   g_return_if_fail (PANEL_IS_SERVICE (self));
00712   g_return_if_fail (INDICATOR_IS_OBJECT (indicator));
00713 
00714   gpointer timeout = g_object_get_data (G_OBJECT (indicator), "remove-timeout");
00715 
00716   if (timeout)
00717     g_source_remove (GPOINTER_TO_UINT (timeout));
00718 
00719   g_object_set_data (G_OBJECT (indicator), "remove", GINT_TO_POINTER (TRUE));
00720   notify_object (indicator);
00721 
00722   guint id = g_timeout_add_seconds (1,
00723                                     (GSourceFunc) panel_service_indicator_remove_timeout,
00724                                     indicator);
00725   g_object_set_data (G_OBJECT (indicator), "remove-timeout", GUINT_TO_POINTER (id));
00726 }
00727 
00728 void
00729 panel_service_clear_indicators (PanelService *self)
00730 {
00731   g_return_if_fail (PANEL_IS_SERVICE (self));
00732   GSList *l = self->priv->indicators;
00733 
00734   while (l)
00735     {
00736       IndicatorObject *ind = l->data;
00737       l = l->next;
00738       panel_service_remove_indicator (self, ind);
00739     }
00740 
00741   g_idle_add ((GSourceFunc)panel_service_check_cleared, self);
00742 }
00743 
00744 /*
00745  * Private Methods
00746  */
00747 static gboolean
00748 actually_notify_object (IndicatorObject *object)
00749 {
00750   PanelService *self;
00751   PanelServicePrivate *priv;
00752   gint position;
00753 
00754   if (!PANEL_IS_SERVICE (static_service))
00755     return FALSE;
00756 
00757   if (!INDICATOR_IS_OBJECT (object))
00758     return FALSE;
00759 
00760   self = panel_service_get_default ();
00761   priv = self->priv;
00762 
00763   position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "position"));
00764   priv->timeouts[position] = SYNC_WAITING;
00765 
00766   if (!suppress_signals)
00767     g_signal_emit (self, _service_signals[RE_SYNC],
00768                    0, g_object_get_data (G_OBJECT (object), "id"));
00769 
00770   return FALSE;
00771 }
00772 
00773 static void
00774 notify_object (IndicatorObject *object)
00775 {
00776   PanelService        *self;
00777   PanelServicePrivate *priv;
00778   gint                 position;
00779 
00780   if (suppress_signals)
00781     return;
00782 
00783   self = panel_service_get_default ();
00784   priv = self->priv;
00785 
00786   position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "position"));
00787 
00788   if (priv->timeouts[position] == SYNC_WAITING)
00789     {
00790       /* No need to ping again as we're waiting for the client to sync anyway */
00791       return;
00792     }
00793   else if (priv->timeouts[position] != SYNC_NEUTRAL)
00794     {
00795       /* We were going to signal that a sync was needed, but since there's been another change let's
00796        * hold off a little longer so we're not flooding the client
00797        */
00798       g_source_remove (priv->timeouts[position]);
00799     }
00800 
00801   priv->timeouts[position] = g_timeout_add (NOTIFY_TIMEOUT,
00802                                             (GSourceFunc)actually_notify_object,
00803                                             object);
00804 }
00805 
00806 static void
00807 on_entry_property_changed (GObject        *o,
00808                            GParamSpec      *pspec,
00809                            IndicatorObject *object)
00810 {
00811   notify_object (object);
00812 }
00813 
00814 static void
00815 on_entry_changed (GObject *o,
00816                   IndicatorObject *object)
00817 {
00818   notify_object (object);
00819 }
00820 
00821 static void
00822 on_entry_added (IndicatorObject      *object,
00823                 IndicatorObjectEntry *entry,
00824                 PanelService         *self)
00825 {
00826   g_return_if_fail (PANEL_IS_SERVICE (self));
00827   g_return_if_fail (entry != NULL);
00828 
00829   gchar *entry_id = get_indicator_entry_id_by_entry (entry);
00830   g_hash_table_insert (self->priv->id2entry_hash, entry_id, entry);
00831 
00832   if (GTK_IS_LABEL (entry->label))
00833     {
00834       g_signal_connect (entry->label, "notify::label",
00835                         G_CALLBACK (on_entry_property_changed), object);
00836 
00837       g_signal_connect (entry->label, "notify::sensitive",
00838                         G_CALLBACK (on_entry_property_changed), object);
00839       g_signal_connect (entry->label, "show",
00840                         G_CALLBACK (on_entry_changed), object);
00841       g_signal_connect (entry->label, "hide",
00842                         G_CALLBACK (on_entry_changed), object);
00843 
00844     }
00845   if (GTK_IS_IMAGE (entry->image))
00846     {
00847       g_signal_connect (entry->image, "notify::storage-type",
00848                         G_CALLBACK (on_entry_property_changed), object);
00849       g_signal_connect (entry->image, "notify::file",
00850                         G_CALLBACK (on_entry_property_changed), object);
00851       g_signal_connect (entry->image, "notify::gicon",
00852                         G_CALLBACK (on_entry_property_changed), object);
00853       g_signal_connect (entry->image, "notify::icon-name",
00854                         G_CALLBACK (on_entry_property_changed), object);
00855       g_signal_connect (entry->image, "notify::pixbuf",
00856                         G_CALLBACK (on_entry_property_changed), object);
00857       g_signal_connect (entry->image, "notify::stock",
00858                         G_CALLBACK (on_entry_property_changed), object);
00859 
00860       g_signal_connect (entry->image, "notify::sensitive",
00861                         G_CALLBACK (on_entry_property_changed), object);
00862       g_signal_connect (entry->image, "show",
00863                         G_CALLBACK (on_entry_changed), object);
00864       g_signal_connect (entry->image, "hide",
00865                         G_CALLBACK (on_entry_changed), object);
00866 
00867     }
00868 
00869   notify_object (object);
00870 }
00871 
00872 static void
00873 on_entry_removed (IndicatorObject      *object,
00874                   IndicatorObjectEntry *entry,
00875                   PanelService         *self)
00876 {
00877   g_return_if_fail (PANEL_IS_SERVICE (self));
00878   g_return_if_fail (entry != NULL);
00879 
00880   /* Don't remove here the value from panel2entries_hash, this should be
00881    * done during the geometries sync, to avoid false positive.
00882    * FIXME this in libappmenu.so to avoid to send an "entry-removed" signal
00883    * when switching the focus from a window to one of its dialog children */
00884 
00885   gchar *entry_id = get_indicator_entry_id_by_entry (entry);
00886   g_hash_table_remove (self->priv->id2entry_hash, entry_id);
00887   g_free (entry_id);
00888 
00889   notify_object (object);
00890 }
00891 
00892 static void
00893 on_entry_moved (IndicatorObject      *object,
00894                 IndicatorObjectEntry *entry,
00895                 PanelService         *self)
00896 {
00897   notify_object (object);
00898 }
00899 
00900 static void
00901 on_indicator_menu_show (IndicatorObject      *object,
00902                         IndicatorObjectEntry *entry,
00903                         guint32               timestamp,
00904                         PanelService         *self)
00905 {
00906   gchar *entry_id;
00907   
00908   g_return_if_fail (PANEL_IS_SERVICE (self));
00909   if (entry == NULL)
00910     {
00911       g_warning ("on_indicator_menu_show() called with a NULL entry");
00912       return;
00913     }
00914 
00915   entry_id = get_indicator_entry_id_by_entry (entry);
00916   g_signal_emit (self, _service_signals[ENTRY_ACTIVATE_REQUEST], 0, entry_id);
00917   g_free (entry_id);
00918 }
00919 
00920 static void
00921 on_indicator_menu_show_now_changed (IndicatorObject      *object,
00922                                     IndicatorObjectEntry *entry,
00923                                     gboolean              show_now_changed,
00924                                     PanelService         *self)
00925 {
00926   gchar *entry_id;
00927   
00928   g_return_if_fail (PANEL_IS_SERVICE (self));
00929   if (entry == NULL)
00930     {
00931       g_warning ("on_indicator_menu_show_now_changed() called with a NULL entry");
00932       return;
00933     }
00934 
00935   entry_id = get_indicator_entry_id_by_entry (entry);
00936   g_signal_emit (self, _service_signals[ENTRY_SHOW_NOW_CHANGED], 0, entry_id, show_now_changed);
00937   g_free (entry_id);
00938 }
00939 
00940 static const gchar * indicator_environment[] = {
00941   "unity",
00942   "unity-3d",
00943   "unity-panel-service",
00944   NULL
00945 };
00946 
00947 static void
00948 load_indicator (PanelService *self, IndicatorObject *object, const gchar *_name)
00949 {
00950   PanelServicePrivate *priv = self->priv;
00951   gchar *name;
00952   GList *entries, *entry;
00953 
00954   indicator_object_set_environment(object, (GStrv)indicator_environment);
00955 
00956   if (_name != NULL)
00957     name = g_strdup (_name);
00958   else
00959     name = g_strdup_printf ("%p", object);
00960 
00961   priv->indicators = g_slist_append (priv->indicators, object);
00962 
00963   g_object_set_data_full (G_OBJECT (object), "id", g_strdup (name), g_free);
00964 
00965   g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_ENTRY_ADDED,
00966                     G_CALLBACK (on_entry_added), self);
00967   g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_ENTRY_REMOVED,
00968                     G_CALLBACK (on_entry_removed), self);
00969   g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_ENTRY_MOVED,
00970                     G_CALLBACK (on_entry_moved), self);
00971   g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_MENU_SHOW,
00972                     G_CALLBACK (on_indicator_menu_show), self);
00973   g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_SHOW_NOW_CHANGED,
00974                     G_CALLBACK (on_indicator_menu_show_now_changed), self);
00975 
00976   entries = indicator_object_get_entries (object);
00977   for (entry = entries; entry != NULL; entry = entry->next)
00978     {
00979       on_entry_added (object, entry->data, self);
00980     }
00981   g_list_free (entries);
00982 
00983   g_free (name);
00984 }
00985 
00986 static void
00987 load_indicators (PanelService *self)
00988 {
00989   GDir        *dir;
00990   const gchar *name;
00991 
00992   if (!g_file_test (INDICATORDIR,
00993                     G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
00994     {
00995       g_warning ("%s does not exist, cannot read any indicators", INDICATORDIR);
00996       gtk_main_quit ();
00997     }
00998 
00999   dir = g_dir_open (INDICATORDIR, 0, NULL);
01000   while ((name = g_dir_read_name (dir)) != NULL)
01001     {
01002       IndicatorObject *object;
01003       gchar           *path;
01004 
01005       if (!g_str_has_suffix (name, ".so"))
01006         continue;
01007 
01008       path = g_build_filename (INDICATORDIR, name, NULL);
01009       g_debug ("Loading: %s", path);
01010 
01011       object = indicator_object_new_from_file (path);
01012       if (object == NULL)
01013         {
01014           g_warning ("Unable to load '%s'", path);
01015           g_free (path);
01016           continue;
01017         }
01018       load_indicator (self, object, name);
01019 
01020       g_free (path);
01021     }
01022 
01023   g_dir_close (dir);
01024 }
01025 
01026 static gint
01027 name2order (const gchar * name, const gchar * hint)
01028 {
01029   int i;
01030 
01031   for (i = 0; indicator_order[i][0] != NULL; i++)
01032     {
01033       if (g_strcmp0(name, indicator_order[i][0]) == 0 &&
01034           g_strcmp0(hint, indicator_order[i][1]) == 0)
01035         {
01036           return i;
01037         }
01038     }
01039   return -1;
01040 }
01041 
01042 static gint
01043 name2priority (const gchar * name, const gchar * hint)
01044 {
01045   gint order = name2order (name, hint);
01046   if (order > -1)
01047     return order * MAX_INDICATOR_ENTRIES;
01048 
01049   return order;
01050 }
01051 
01052 static int
01053 indicator_compare_func (IndicatorObject *o1, IndicatorObject *o2)
01054 {
01055   gchar *s1;
01056   gchar *s2;
01057   int    i1;
01058   int    i2;
01059 
01060   s1 = g_object_get_data (G_OBJECT (o1), "id");
01061   s2 = g_object_get_data (G_OBJECT (o2), "id");
01062 
01063   i1 = name2order (s1, NULL);
01064   i2 = name2order (s2, NULL);
01065 
01066   return i1 - i2;
01067 }
01068 
01069 static void
01070 sort_indicators (PanelService *self)
01071 {
01072   GSList *i;
01073   int     k = 0;
01074   int     prio = 0;
01075 
01076   self->priv->indicators = g_slist_sort (self->priv->indicators,
01077                                          (GCompareFunc)indicator_compare_func);
01078 
01079   for (i = self->priv->indicators; i; i = i->next)
01080     {
01081       prio = name2priority(g_object_get_data (G_OBJECT (i->data), "id"), NULL);
01082       if (prio < 0) continue;
01083       g_object_set_data (G_OBJECT (i->data), "priority", GINT_TO_POINTER (prio));
01084       g_object_set_data (G_OBJECT (i->data), "position", GINT_TO_POINTER (k));
01085       self->priv->timeouts[k] = SYNC_NEUTRAL;
01086       k++;
01087     }
01088 }
01089 
01090 static gchar *
01091 gtk_image_to_data (GtkImage *image)
01092 {
01093   GtkImageType type = gtk_image_get_storage_type (image);
01094   gchar *ret = NULL;
01095 
01096   if (type == GTK_IMAGE_PIXBUF)
01097     {
01098       GdkPixbuf  *pixbuf;
01099       gchar      *buffer = NULL;
01100       gsize       buffer_size = 0;
01101       GError     *error = NULL;
01102 
01103       pixbuf = gtk_image_get_pixbuf (image);
01104 
01105       if (gdk_pixbuf_save_to_buffer (pixbuf, &buffer, &buffer_size, "png", &error, NULL))
01106         {
01107           ret = g_base64_encode ((const guchar *)buffer, buffer_size);
01108           g_free (buffer);
01109         }
01110       else
01111         {
01112           g_warning ("Unable to convert pixbuf to png data: '%s'", error ? error->message : "unknown");
01113           if (error)
01114             g_error_free (error);
01115 
01116           ret = g_strdup ("");
01117         }
01118     }
01119   else if (type == GTK_IMAGE_STOCK)
01120     {
01121       g_object_get (G_OBJECT (image), "stock", &ret, NULL);
01122     }
01123   else if (type == GTK_IMAGE_ICON_NAME)
01124     {
01125       g_object_get (G_OBJECT (image), "icon-name", &ret, NULL);
01126     }
01127   else if (type == GTK_IMAGE_GICON)
01128     {
01129       GIcon *icon = NULL;
01130       gtk_image_get_gicon (image, &icon, NULL);
01131       if (G_IS_ICON (icon))
01132         {
01133           ret = g_icon_to_string (icon);
01134         }
01135     }
01136   else
01137     {
01138       ret = g_strdup ("");
01139       g_warning ("Unable to support GtkImageType: %d", type);
01140     }
01141 
01142   return ret;
01143 }
01144 
01145 static void
01146 indicator_entry_to_variant (IndicatorObjectEntry *entry,
01147                             const gchar          *id,
01148                             const gchar          *indicator_id,
01149                             GVariantBuilder      *b,
01150                             gint                  prio)
01151 {
01152   gboolean is_label = GTK_IS_LABEL (entry->label);
01153   gboolean is_image = GTK_IS_IMAGE (entry->image);
01154   gchar *image_data = NULL;
01155 
01156   g_variant_builder_add (b, "(ssssbbusbbi)",
01157                          indicator_id,
01158                          id,
01159                          entry->name_hint ? entry->name_hint : "",
01160                          is_label ? gtk_label_get_label (entry->label) : "",
01161                          is_label ? gtk_widget_get_sensitive (GTK_WIDGET (entry->label)) : FALSE,
01162                          is_label ? gtk_widget_get_visible (GTK_WIDGET (entry->label)) : FALSE,
01163                          is_image ? (guint32)gtk_image_get_storage_type (entry->image) : (guint32) 0,
01164                          is_image ? (image_data = gtk_image_to_data (entry->image)) : "",
01165                          is_image ? gtk_widget_get_sensitive (GTK_WIDGET (entry->image)) : FALSE,
01166                          is_image ? gtk_widget_get_visible (GTK_WIDGET (entry->image)) : FALSE,
01167                          prio);
01168 
01169   g_free (image_data);
01170 }
01171 
01172 static void
01173 indicator_entry_null_to_variant (const gchar     *indicator_id,
01174                                  GVariantBuilder *b)
01175 {
01176   g_variant_builder_add (b, "(ssssbbusbbi)",
01177                          indicator_id,
01178                          "",
01179                          "",
01180                          "",
01181                          FALSE,
01182                          FALSE,
01183                          (guint32) 0,
01184                          "",
01185                          FALSE,
01186                          FALSE,
01187                          -1);
01188 }
01189 
01190 static void
01191 indicator_object_to_variant (IndicatorObject *object, const gchar *indicator_id, GVariantBuilder *b)
01192 {
01193   GList *entries, *e;
01194   gint parent_prio = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "priority"));
01195   entries = indicator_object_get_entries (object);
01196   gint index = 0;
01197 
01198   if (entries)
01199     {
01200       for (e = entries; e; e = e->next)
01201         {
01202           gint prio = -1;
01203           IndicatorObjectEntry *entry = e->data;
01204           gchar *id = get_indicator_entry_id_by_entry (entry);
01205 
01206           if (entry->name_hint)
01207             {
01208               prio = name2priority(indicator_id, entry->name_hint);
01209             }
01210 
01211           if (prio == -1)
01212             {
01213               prio = parent_prio + index;
01214               index++;
01215             }
01216 
01217           indicator_entry_to_variant (entry, id, indicator_id, b, prio);
01218           g_free (id);
01219         }
01220 
01221       g_list_free (entries);
01222     }
01223   else
01224     {
01225       /* Add a null entry to indicate that there is an indicator here, it's just empty */
01226       indicator_entry_null_to_variant (indicator_id, b);
01227     }
01228 }
01229 
01230 static void
01231 positon_menu (GtkMenu  *menu,
01232               gint     *x,
01233               gint     *y,
01234               gboolean *push,
01235               gpointer  user_data)
01236 {
01237   PanelService *self = PANEL_SERVICE (user_data);
01238   PanelServicePrivate *priv = self->priv;
01239 
01240   *x = priv->last_x;
01241   *y = priv->last_y;
01242   *push = TRUE;
01243 }
01244 
01245 static void
01246 on_active_menu_hidden (GtkMenu *menu, PanelService *self)
01247 {
01248   PanelServicePrivate *priv = self->priv;
01249 
01250   priv->last_x = 0;
01251   priv->last_y = 0;
01252   priv->last_menu_button = 0;
01253 
01254   g_signal_handler_disconnect (priv->last_menu, priv->last_menu_id);
01255   g_signal_handler_disconnect (priv->last_menu, priv->last_menu_move_id);
01256 
01257   GtkWidget *top_win = gtk_widget_get_toplevel (GTK_WIDGET (priv->last_menu));
01258   if (GTK_IS_WINDOW (top_win))
01259     gtk_window_set_attached_to (GTK_WINDOW (top_win), NULL);
01260 
01261   priv->last_menu = NULL;
01262   priv->last_menu_id = 0;
01263   priv->last_menu_move_id = 0;
01264   priv->last_entry = NULL;
01265   priv->last_left = 0;
01266   priv->last_right = 0;
01267   priv->last_top = 0;
01268   priv->last_bottom = 0;
01269 
01270   priv->use_event = FALSE;
01271   priv->pressed_entry = NULL;
01272 
01273   g_signal_emit (self, _service_signals[ENTRY_ACTIVATED], 0, "", 0, 0, 0, 0);
01274 }
01275 
01276 /*
01277  * Public Methods
01278  */
01279 GVariant *
01280 panel_service_sync (PanelService *self)
01281 {
01282   GVariantBuilder b;
01283   GSList *i;
01284 
01285   g_variant_builder_init (&b, G_VARIANT_TYPE ("(a(ssssbbusbbi))"));
01286   g_variant_builder_open (&b, G_VARIANT_TYPE ("a(ssssbbusbbi)"));
01287 
01288   for (i = self->priv->indicators; i; i = i->next)
01289     {
01290       const gchar *indicator_id = g_object_get_data (G_OBJECT (i->data), "id");
01291       gint position;
01292 
01293       /* Set the sync back to neutral */
01294       position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (i->data), "position"));
01295       self->priv->timeouts[position] = SYNC_NEUTRAL;
01296       indicator_object_to_variant (i->data, indicator_id, &b);
01297     }
01298 
01299   g_variant_builder_close (&b);
01300   return g_variant_builder_end (&b);
01301 }
01302 
01303 GVariant *
01304 panel_service_sync_one (PanelService *self, const gchar *indicator_id)
01305 {
01306   GVariantBuilder b;
01307   GSList *i;
01308 
01309   g_variant_builder_init (&b, G_VARIANT_TYPE ("(a(ssssbbusbbi))"));
01310   g_variant_builder_open (&b, G_VARIANT_TYPE ("a(ssssbbusbbi)"));
01311 
01312   for (i = self->priv->indicators; i; i = i->next)
01313     {
01314       if (g_strcmp0 (indicator_id,
01315                      g_object_get_data (G_OBJECT (i->data), "id")) == 0)
01316         {
01317           gint position;
01318 
01319           /* Set the sync back to neutral */
01320           position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (i->data), "position"));
01321           self->priv->timeouts[position] = SYNC_NEUTRAL;
01322 
01323           if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (i->data), "remove")) != TRUE)
01324             {
01325               indicator_object_to_variant (i->data, indicator_id, &b);
01326             }
01327           else
01328             {
01329               indicator_entry_null_to_variant (indicator_id, &b);
01330               panel_service_actually_remove_indicator (self, i->data);
01331             }
01332 
01333           break;
01334         }
01335     }
01336 
01337   g_variant_builder_close (&b);
01338   return g_variant_builder_end (&b);
01339 }
01340 
01341 void
01342 panel_service_sync_geometry (PanelService *self,
01343                              const gchar *panel_id,
01344                              const gchar *entry_id,
01345                              gint x,
01346                              gint y,
01347                              gint width,
01348                              gint height)
01349 {
01350   IndicatorObject      *object;
01351   IndicatorObjectEntry *entry;
01352   gboolean valid_entry = TRUE;
01353   PanelServicePrivate  *priv = self->priv;
01354 
01355   entry = get_indicator_entry_by_id (self, entry_id);
01356 
01357   /* If the entry we read is not valid, maybe it has already been removed
01358    * or unparented, so we need to make sure that the related key on the
01359    * entry2geometry_hash is correctly removed and the value is free'd */
01360   if (!entry)
01361     {
01362       IndicatorObjectEntry *invalid_entry;
01363       if (sscanf (entry_id, "%p", &invalid_entry) == 1)
01364         {
01365           entry = invalid_entry;
01366           valid_entry = FALSE;
01367         }
01368     }
01369 
01370   if (entry)
01371     {
01372       GHashTable *entry2geometry_hash = g_hash_table_lookup (priv->panel2entries_hash, panel_id);
01373 
01374       if (width < 0 || height < 0 || !valid_entry)
01375         {
01376           if (entry2geometry_hash)
01377             {
01378               if (g_hash_table_size (entry2geometry_hash) > 1)
01379                 {
01380                   g_hash_table_remove (entry2geometry_hash, entry);
01381                 }
01382               else
01383                 {
01384                   g_hash_table_remove (priv->panel2entries_hash, panel_id);
01385                 }
01386             }
01387 
01388           /* If the entry has been removed let's make sure that its menu is closed */
01389           if (valid_entry && GTK_IS_MENU (priv->last_menu) && priv->last_menu == entry->menu)
01390             {
01391               gtk_menu_popdown (entry->menu);
01392             }
01393         }
01394       else
01395         {
01396           GdkRectangle *geo = NULL;
01397 
01398           if (entry2geometry_hash == NULL)
01399             {
01400               entry2geometry_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal,
01401                                                            NULL, g_free);
01402               g_hash_table_insert (priv->panel2entries_hash, g_strdup (panel_id),
01403                                    entry2geometry_hash);
01404             }
01405           else
01406             {
01407               geo = g_hash_table_lookup (entry2geometry_hash, entry);
01408             }
01409 
01410           if (geo == NULL)
01411             {
01412               geo = g_new0 (GdkRectangle, 1);
01413               g_hash_table_insert (entry2geometry_hash, entry, geo);
01414             }
01415 
01416           /* If the current entry geometry has changed, we need to move the menu
01417            * accordingly to the change we recorded! */
01418           if (GTK_IS_MENU (priv->last_menu) && priv->last_menu == entry->menu)
01419             {
01420               GtkWidget *top_widget = gtk_widget_get_toplevel (GTK_WIDGET (priv->last_menu));
01421 
01422               if (GTK_IS_WINDOW (top_widget))
01423                 {
01424                   GtkWindow *top_win = GTK_WINDOW (top_widget);
01425                   gint old_x, old_y;
01426 
01427                   gtk_window_get_position (top_win, &old_x, &old_y);
01428                   gtk_window_move (top_win, old_x - (geo->x - x), old_y - (geo->y - y));
01429                 }
01430             }
01431 
01432           geo->x = x;
01433           geo->y = y;
01434           geo->width = width;
01435           geo->height = height;
01436         }
01437 
01438       if (valid_entry)
01439         {
01440           object = get_entry_parent_indicator (entry);
01441           g_signal_emit (self, _service_signals[GEOMETRIES_CHANGED], 0, object, entry, x, y, width, height);
01442         }
01443     }
01444 }
01445 
01446 static gboolean
01447 panel_service_entry_is_visible (PanelService *self, IndicatorObjectEntry *entry)
01448 {
01449   GHashTableIter panel_iter;
01450   gpointer key, value;
01451   gboolean found_geo;
01452 
01453   g_return_val_if_fail (PANEL_IS_SERVICE (self), FALSE);
01454   g_return_val_if_fail (entry != NULL, FALSE);
01455 
01456   found_geo = FALSE;
01457   g_hash_table_iter_init (&panel_iter, self->priv->panel2entries_hash);
01458 
01459   while (g_hash_table_iter_next (&panel_iter, &key, &value) && !found_geo)
01460     {
01461       GHashTable *entry2geometry_hash = value;
01462 
01463       if (g_hash_table_lookup (entry2geometry_hash, entry))
01464         {
01465           found_geo = TRUE;
01466         }
01467     }
01468 
01469   if (!found_geo)
01470     return FALSE;
01471 
01472   if (GTK_IS_LABEL (entry->label))
01473     {
01474       if (gtk_widget_get_visible (GTK_WIDGET (entry->label)) &&
01475           gtk_widget_is_sensitive (GTK_WIDGET (entry->label)))
01476         {
01477           return TRUE;
01478         }
01479     }
01480 
01481   if (GTK_IS_IMAGE (entry->image))
01482     {
01483       if (gtk_widget_get_visible (GTK_WIDGET (entry->image)) &&
01484           gtk_widget_is_sensitive (GTK_WIDGET (entry->image)))
01485         {
01486           return TRUE;
01487         }
01488     }
01489 
01490   return TRUE;
01491 }
01492 
01493 static int
01494 indicator_entry_compare_func (gpointer* v1, gpointer* v2)
01495 {
01496   return (GPOINTER_TO_INT (v1[1]) > GPOINTER_TO_INT (v2[1])) ? 1 : -1;
01497 }
01498 
01499 static void
01500 activate_next_prev_menu (PanelService         *self,
01501                          IndicatorObject      *object,
01502                          IndicatorObjectEntry *entry,
01503                          GtkMenuDirectionType  direction)
01504 {
01505   IndicatorObjectEntry *new_entry;
01506   PanelServicePrivate *priv = self->priv;
01507   GSList *indicators = priv->indicators;
01508   GList  *ordered_entries = NULL;
01509   GList  *entries;
01510   gchar  *id;
01511   GSList *l;
01512   GList *ll;
01513 
01514   for (l = indicators; l; l = l->next)
01515     {
01516       const gchar *indicator_id = g_object_get_data (G_OBJECT (l->data), "id");
01517       gint parent_priority = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (l->data), "priority"));
01518       entries = indicator_object_get_entries (l->data);
01519       if (entries)
01520         {
01521           int index = 0;
01522 
01523           for (ll = entries; ll; ll = ll->next)
01524             {
01525               gint prio = -1;
01526               new_entry = ll->data;
01527 
01528               if (!panel_service_entry_is_visible (self, new_entry))
01529                 continue;
01530 
01531               if (new_entry->name_hint)
01532                 {
01533                   prio = name2priority(indicator_id, new_entry->name_hint);
01534                 }
01535 
01536               if (prio == -1)
01537                 {
01538                   prio = parent_priority + index;
01539                   index++;
01540                 }
01541 
01542               gpointer *values = g_new (gpointer, 2);
01543               values[0] = new_entry;
01544               values[1] = GINT_TO_POINTER (prio);
01545               ordered_entries = g_list_insert_sorted (ordered_entries, values,
01546                                                       (GCompareFunc) indicator_entry_compare_func);
01547             }
01548 
01549           g_list_free (entries);
01550         }
01551     }
01552 
01553   new_entry = NULL;
01554   for (ll = ordered_entries; ll; ll = ll->next)
01555     {
01556       gpointer *values = ll->data;
01557       if (entry == values[0])
01558         {
01559           if (direction == GTK_MENU_DIR_CHILD)
01560             {
01561               values = ll->next ? ll->next->data : ordered_entries->data;
01562             }
01563           else
01564             {
01565               values = ll->prev ? ll->prev->data : g_list_last(ordered_entries)->data;
01566             }
01567 
01568           new_entry = values[0];
01569           break;
01570         }
01571     }
01572 
01573   if (new_entry)
01574     {
01575       id = get_indicator_entry_id_by_entry (new_entry);
01576       g_signal_emit (self, _service_signals[ENTRY_ACTIVATE_REQUEST], 0, id);
01577       g_free (id);
01578     }
01579 
01580   g_list_free_full (ordered_entries, g_free);
01581 }
01582 
01583 static void
01584 on_active_menu_move_current (GtkMenu              *menu,
01585                              GtkMenuDirectionType  direction,
01586                              PanelService         *self)
01587 {
01588   PanelServicePrivate *priv;
01589   IndicatorObject     *object;
01590 
01591   g_return_if_fail (PANEL_IS_SERVICE (self));
01592   priv = self->priv;
01593 
01594   /* Not interested in up or down */
01595   if (direction == GTK_MENU_DIR_NEXT || direction == GTK_MENU_DIR_PREV)
01596     return;
01597 
01598   /* We don't want to distrupt going into submenus */
01599   if (direction == GTK_MENU_DIR_CHILD)
01600     {
01601       GList               *children, *c;
01602       children = gtk_container_get_children (GTK_CONTAINER (menu));
01603       for (c = children; c; c = c->next)
01604         {
01605           GtkWidget *item = (GtkWidget *)c->data;
01606 
01607           if (GTK_IS_MENU_ITEM (item)
01608               && gtk_widget_get_state (item) == GTK_STATE_PRELIGHT
01609               && gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)))
01610             {
01611               /* Skip direction due to there being a submenu,
01612                * and we don't want to inhibit going into that */
01613               return;
01614             }
01615         }
01616       g_list_free (children);
01617     }
01618 
01619   /* Find the next/prev indicator */
01620   object = get_entry_parent_indicator(priv->last_entry);
01621   if (object == NULL)
01622     {
01623       g_warning ("Unable to find IndicatorObject for entry");
01624       return;
01625     }
01626 
01627   activate_next_prev_menu (self, object, priv->last_entry, direction);
01628 }
01629 
01630 static void
01631 menu_deactivated (GtkWidget *menu)
01632 {
01633   g_signal_handlers_disconnect_by_func (menu, menu_deactivated, NULL);
01634   gtk_widget_destroy (menu);
01635 }
01636 
01637 static void
01638 panel_service_show_entry_common (PanelService *self,
01639                                  IndicatorObject *object,
01640                                  IndicatorObjectEntry *entry,
01641                                  guint32       xid,
01642                                  gint32        x,
01643                                  gint32        y,
01644                                  guint32       button,
01645                                  guint32       timestamp)
01646 {
01647   PanelServicePrivate *priv;
01648   GtkWidget           *last_menu;
01649 
01650   g_return_if_fail (PANEL_IS_SERVICE (self));
01651   g_return_if_fail (INDICATOR_IS_OBJECT (object));
01652   g_return_if_fail (entry);
01653 
01654   priv = self->priv;
01655 
01656   if (priv->last_entry == entry)
01657     return;
01658 
01659   last_menu = GTK_WIDGET (priv->last_menu);
01660 
01661   if (GTK_IS_MENU (priv->last_menu))
01662     {
01663       priv->last_x = 0;
01664       priv->last_y = 0;
01665 
01666       g_signal_handler_disconnect (priv->last_menu, priv->last_menu_id);
01667       g_signal_handler_disconnect (priv->last_menu, priv->last_menu_move_id);
01668 
01669       priv->last_entry = NULL;
01670       priv->last_menu = NULL;
01671       priv->last_menu_id = 0;
01672       priv->last_menu_move_id = 0;
01673       priv->last_menu_button = 0;
01674     }
01675 
01676   if (entry != NULL)
01677     {
01678       if (xid > 0)
01679         {
01680           indicator_object_entry_activate_window (object, entry, xid, CurrentTime);
01681         }
01682       else
01683         {
01684           indicator_object_entry_activate (object, entry, CurrentTime);
01685         }
01686 
01687       if (GTK_IS_MENU (entry->menu))
01688         {
01689           priv->last_menu = entry->menu;
01690         }
01691       else
01692         {
01693           /* For some reason, this entry doesn't have a menu.  To simplify the
01694              rest of the code and to keep scrubbing fluidly, we'll create a
01695              stub menu for the duration of this scrub. */
01696           priv->last_menu = GTK_MENU (gtk_menu_new ());
01697           g_signal_connect (priv->last_menu, "deactivate",
01698                             G_CALLBACK (menu_deactivated), NULL);
01699           g_signal_connect (priv->last_menu, "destroy",
01700                             G_CALLBACK (gtk_widget_destroyed), &priv->last_menu);
01701         }
01702 
01703       GtkWidget *top_widget = gtk_widget_get_toplevel (GTK_WIDGET (priv->last_menu));
01704 
01705       if (GTK_IS_WINDOW (top_widget))
01706         {
01707           GtkWindow *top_win = GTK_WINDOW (top_widget);
01708 
01709           if (gtk_window_get_attached_to (top_win) != priv->menubar)
01710             gtk_window_set_attached_to (top_win, priv->menubar);
01711         }
01712 
01713       priv->last_entry = entry;
01714       priv->last_x = x;
01715       priv->last_y = y;
01716       priv->last_menu_button = button;
01717       priv->last_menu_id = g_signal_connect (priv->last_menu, "hide",
01718                                              G_CALLBACK (on_active_menu_hidden), self);
01719       priv->last_menu_move_id = g_signal_connect_after (priv->last_menu, "move-current",
01720                                                         G_CALLBACK (on_active_menu_move_current), self);
01721 
01722       gtk_menu_popup (priv->last_menu, NULL, NULL, positon_menu, self, 0, CurrentTime);
01723       gtk_menu_reposition (priv->last_menu);
01724 
01725       GdkWindow *gdkwin = gtk_widget_get_window (GTK_WIDGET (priv->last_menu));
01726       if (gdkwin != NULL)
01727         {
01728           gint left=0, top=0, width=0, height=0;
01729 
01730           gdk_window_get_geometry (gdkwin, NULL, NULL, &width, &height);
01731           gdk_window_get_origin (gdkwin, &left, &top);
01732 
01733           gchar *entry_id = get_indicator_entry_id_by_entry (entry);
01734           g_signal_emit (self, _service_signals[ENTRY_ACTIVATED], 0, entry_id,
01735                          left, top, width, height);
01736           g_free (entry_id);
01737 
01738           priv->last_left = left;
01739           priv->last_right = left + width -1;
01740           priv->last_top = top;
01741           priv->last_bottom = top + height -1;
01742         }
01743       else
01744         {
01745           priv->last_left = 0;
01746           priv->last_right = 0;
01747           priv->last_top = 0;
01748           priv->last_bottom = 0;
01749         }
01750     }
01751 
01752   /* We popdown the old one last so we don't accidently send key focus back to the
01753    * active application (which will make it change colour (as state changes), which
01754    * then looks like flickering to the user.
01755    */
01756   if (GTK_IS_MENU (last_menu))
01757     gtk_menu_popdown (GTK_MENU (last_menu));
01758 }
01759 
01760 void
01761 panel_service_show_entry (PanelService *self,
01762                           const gchar  *entry_id,
01763                           guint32       xid,
01764                           gint32        x,
01765                           gint32        y,
01766                           guint32       button,
01767                           guint32       timestamp)
01768 {
01769   IndicatorObject      *object;
01770   IndicatorObjectEntry *entry;
01771 
01772   g_return_if_fail (PANEL_IS_SERVICE (self));
01773 
01774   entry = get_indicator_entry_by_id (self, entry_id);
01775   object = get_entry_parent_indicator (entry);
01776 
01777   panel_service_show_entry_common (self, object, entry, xid, x, y, button, timestamp);
01778 }
01779 
01780 void
01781 panel_service_show_app_menu (PanelService *self,
01782                              guint32       xid,
01783                              gint32        x,
01784                              gint32        y,
01785                              guint32       timestamp)
01786 {
01787   IndicatorObject      *object;
01788   IndicatorObjectEntry *entry;
01789   GList                *entries;
01790 
01791   g_return_if_fail (PANEL_IS_SERVICE (self));
01792 
01793   object = panel_service_get_indicator (self, "libappmenu.so");
01794   g_return_if_fail (INDICATOR_IS_OBJECT (object));
01795 
01796   entries = indicator_object_get_entries (object);
01797 
01798   if (entries)
01799     {
01800       entry = entries->data;
01801       g_list_free (entries);
01802 
01803       panel_service_show_entry_common (self, object, entry, xid, x, y, 1, timestamp);
01804     }
01805 }
01806 
01807 void
01808 panel_service_secondary_activate_entry (PanelService *self,
01809                                         const gchar  *entry_id,
01810                                         guint32       timestamp)
01811 {
01812   IndicatorObject      *object;
01813   IndicatorObjectEntry *entry;
01814 
01815   entry = get_indicator_entry_by_id (self, entry_id);
01816   g_return_if_fail (entry);
01817 
01818   object = get_entry_parent_indicator (entry);
01819   g_signal_emit_by_name(object, INDICATOR_OBJECT_SIGNAL_SECONDARY_ACTIVATE, entry,
01820                         timestamp);
01821 }
01822 
01823 void
01824 panel_service_scroll_entry (PanelService   *self,
01825                             const gchar    *entry_id,
01826                             gint32         delta)
01827 {
01828   IndicatorObject      *object;
01829   IndicatorObjectEntry *entry;
01830 
01831   entry = get_indicator_entry_by_id (self, entry_id);
01832   g_return_if_fail (entry);
01833 
01834   GdkScrollDirection direction = delta < 0 ? GDK_SCROLL_DOWN : GDK_SCROLL_UP;
01835 
01836   object = get_entry_parent_indicator (entry);
01837   g_signal_emit_by_name(object, INDICATOR_OBJECT_SIGNAL_ENTRY_SCROLLED, entry,
01838                         abs(delta/120), direction);
01839 }