1 // -*- Mode: C; indent-tabs-mode: nil; tab-width: 2 -*-
   2 /*
   3  * Copyright (C) 2010 Canonical Ltd
   4  *
   5  * This program is free software: you can redistribute it and/or modify
   6  * it under the terms of the GNU General Public License version 3 as
   7  * published by the Free Software Foundation.
   8  *
   9  * This program is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  * GNU General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU General Public License
  15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  16  *
  17  * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
  18  *              Rodrigo Moya <rodrigo.moya@canonical.com>
  19  *              Marco Trevisan (Trevi��o) <mail@3v1n0.net>
  20  */
  21 
  22 #if HAVE_CONFIG_H
  23 #include <config.h>
  24 #endif
  25 
  26 #include "panel-marshal.h"
  27 #include "panel-service.h"
  28 
  29 #include <stdlib.h>
  30 #include <gtk/gtk.h>
  31 #include <gdk/gdkx.h>
  32 
  33 #include <X11/extensions/XInput2.h>
  34 #include <X11/XKBlib.h>
  35 
  36 #include "panel-marshal.h"
  37 
  38 G_DEFINE_TYPE (PanelService, panel_service, G_TYPE_OBJECT);
  39 
  40 #define GET_PRIVATE(o) \
  41   (G_TYPE_INSTANCE_GET_PRIVATE ((o), PANEL_TYPE_SERVICE, PanelServicePrivate))
  42 
  43 #define NOTIFY_TIMEOUT 80
  44 #define N_TIMEOUT_SLOTS 50
  45 #define MAX_INDICATOR_ENTRIES 50
  46 
  47 static PanelService *static_service = NULL;
  48 
  49 struct _PanelServicePrivate
  50 {
  51   GSList     *indicators;
  52   GHashTable *entry2indicator_hash;
  53   GHashTable *panel2entries_hash;
  54 
  55   guint  initial_sync_id;
  56   gint32 timeouts[N_TIMEOUT_SLOTS];
  57 
  58   IndicatorObjectEntry *last_entry;
  59   GtkWidget *menubar;
  60   GtkWidget *offscreen_window;
  61   GtkMenu *last_menu;
  62   guint32  last_menu_id;
  63   guint32  last_menu_move_id;
  64   gint32   last_x;
  65   gint32   last_y;
  66   gint     last_left;
  67   gint     last_top;
  68   gint     last_right;
  69   gint     last_bottom;
  70   guint32  last_menu_button;
  71 
  72   IndicatorObjectEntry *pressed_entry;
  73   gboolean use_event;
  74 };
  75 
  76 /* Globals */
  77 static gboolean suppress_signals = FALSE;
  78 
  79 enum
  80 {
  81   ENTRY_ACTIVATED = 0,
  82   RE_SYNC,
  83   ENTRY_ACTIVATE_REQUEST,
  84   ENTRY_SHOW_NOW_CHANGED,
  85   GEOMETRIES_CHANGED,
  86   INDICATORS_CLEARED,
  87 
  88   LAST_SIGNAL
  89 };
  90 
  91 enum
  92 {
  93   SYNC_WAITING = -1,
  94   SYNC_NEUTRAL = 0,
  95 };
  96 
  97 static guint32 _service_signals[LAST_SIGNAL] = { 0 };
  98 
  99 static const gchar * indicator_order[][2] = {
 100   {"libappmenu.so", NULL},                    /* indicator-appmenu" */
 101   {"libapplication.so", NULL},                /* indicator-application" */
 102   {"libapplication.so", "gsd-keyboard-xkb"},  /* keyboard layout selector */
 103   {"libmessaging.so", NULL},                  /* indicator-messages */
 104   {"libpower.so", NULL},                      /* indicator-power */
 105   {"libapplication.so", "bluetooth-manager"}, /* bluetooth manager */
 106   {"libnetwork.so", NULL},                    /* indicator-network */
 107   {"libnetworkmenu.so", NULL},                /* indicator-network */
 108   {"libapplication.so", "nm-applet"},         /* network manager */
 109   {"libsoundmenu.so", NULL},                  /* indicator-sound */
 110   {"libdatetime.so", NULL},                   /* indicator-datetime */
 111   {"libsession.so", NULL},                    /* indicator-session */
 112   {NULL, NULL}
 113 };
 114 
 115 /* Forwards */
 116 static void load_indicator  (PanelService    *self,
 117                              IndicatorObject *object,
 118                              const gchar     *_name);
 119 static void load_indicators (PanelService    *self);
 120 static void sort_indicators (PanelService    *self);
 121 
 122 static void notify_object (IndicatorObject *object);
 123 static IndicatorObjectEntry *get_entry_at (PanelService *self, gint x, gint y);
 124 
 125 static GdkFilterReturn event_filter (GdkXEvent    *ev,
 126                                      GdkEvent     *gev,
 127                                      PanelService *self);
 128 
 129 /*
 130  * GObject stuff
 131  */
 132 
 133 static void
 134 panel_service_class_dispose (GObject *object)
 135 {
 136   PanelServicePrivate *priv = PANEL_SERVICE (object)->priv;
 137   gint i;
 138 
 139   g_hash_table_destroy (priv->entry2indicator_hash);
 140   g_hash_table_destroy (priv->panel2entries_hash);
 141 
 142   gdk_window_remove_filter (NULL, (GdkFilterFunc)event_filter, object);
 143 
 144   if (G_IS_OBJECT (priv->menubar))
 145     {
 146       g_object_unref (priv->menubar);
 147       priv->menubar = NULL;
 148     }
 149 
 150   if (G_IS_OBJECT (priv->offscreen_window))
 151     {
 152       g_object_unref (priv->offscreen_window);
 153       priv->offscreen_window = NULL;
 154     }
 155 
 156   if (G_IS_OBJECT (priv->last_menu))
 157     {
 158       g_object_unref (priv->last_menu);
 159       priv->last_menu = NULL;
 160     }
 161 
 162   if (priv->initial_sync_id)
 163     {
 164       g_source_remove (priv->initial_sync_id);
 165       priv->initial_sync_id = 0;
 166     }
 167 
 168   for (i = 0; i < N_TIMEOUT_SLOTS; i++)
 169     {
 170       if (priv->timeouts[i] > 0)
 171         {
 172           g_source_remove (priv->timeouts[i]);
 173           priv->timeouts[i] = 0;
 174         }
 175     }
 176 
 177   G_OBJECT_CLASS (panel_service_parent_class)->dispose (object);
 178 }
 179 
 180 static void
 181 panel_service_class_init (PanelServiceClass *klass)
 182 {
 183   GObjectClass *obj_class = G_OBJECT_CLASS (klass);
 184 
 185   obj_class->dispose = panel_service_class_dispose;
 186 
 187   /* Signals */
 188   _service_signals[ENTRY_ACTIVATED] =
 189     g_signal_new ("entry-activated",
 190                   G_OBJECT_CLASS_TYPE (obj_class),
 191                   G_SIGNAL_RUN_LAST,
 192                   0,
 193                   NULL, NULL,
 194                   g_cclosure_marshal_VOID__STRING,
 195                   G_TYPE_NONE, 1, G_TYPE_STRING);
 196 
 197   _service_signals[RE_SYNC] =
 198     g_signal_new ("re-sync",
 199                   G_OBJECT_CLASS_TYPE (obj_class),
 200                   G_SIGNAL_RUN_LAST,
 201                   0,
 202                   NULL, NULL,
 203                   g_cclosure_marshal_VOID__STRING,
 204                   G_TYPE_NONE, 1, G_TYPE_STRING);
 205 
 206  _service_signals[ENTRY_ACTIVATE_REQUEST] =
 207     g_signal_new ("entry-activate-request",
 208                   G_OBJECT_CLASS_TYPE (obj_class),
 209                   G_SIGNAL_RUN_LAST,
 210                   0,
 211                   NULL, NULL,
 212                   g_cclosure_marshal_VOID__STRING,
 213                   G_TYPE_NONE, 1, G_TYPE_STRING);
 214  _service_signals[GEOMETRIES_CHANGED] =
 215     g_signal_new ("geometries-changed",
 216       G_OBJECT_CLASS_TYPE (obj_class),
 217       G_SIGNAL_RUN_LAST,
 218       0,
 219       NULL, NULL,
 220       panel_marshal_VOID__OBJECT_POINTER_INT_INT_INT_INT,
 221       G_TYPE_NONE, 6,
 222       G_TYPE_OBJECT, G_TYPE_POINTER,
 223       G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
 224 
 225  _service_signals[ENTRY_SHOW_NOW_CHANGED] =
 226     g_signal_new ("entry-show-now-changed",
 227                   G_OBJECT_CLASS_TYPE (obj_class),
 228                   G_SIGNAL_RUN_LAST,
 229                   0,
 230                   NULL, NULL,
 231                   panel_marshal_VOID__STRING_BOOLEAN,
 232                   G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN);
 233 
 234   _service_signals[INDICATORS_CLEARED] =
 235     g_signal_new ("indicators-cleared",
 236                   G_OBJECT_CLASS_TYPE (obj_class),
 237                   G_SIGNAL_RUN_LAST,
 238                   0,
 239                   NULL, NULL,
 240                   g_cclosure_marshal_VOID__VOID,
 241                   G_TYPE_NONE, 0);
 242 
 243 
 244   g_type_class_add_private (obj_class, sizeof (PanelServicePrivate));
 245 }
 246 
 247 static GdkFilterReturn
 248 event_filter (GdkXEvent *ev, GdkEvent *gev, PanelService *self)
 249 {
 250   PanelServicePrivate *priv = self->priv;
 251   XEvent *e = (XEvent *)ev;
 252   GdkFilterReturn ret = GDK_FILTER_CONTINUE;
 253 
 254   if (!PANEL_IS_SERVICE (self))
 255   {
 256     g_warning ("%s: Invalid PanelService instance", G_STRLOC);
 257     return ret;
 258   }
 259 
 260   if (!GTK_IS_WIDGET (self->priv->last_menu))
 261     return ret;
 262 
 263   /* Use XI2 to read the event data */
 264   XGenericEventCookie *cookie = &e->xcookie;
 265   if (cookie->type == GenericEvent)
 266     {
 267       XIDeviceEvent *event = cookie->data;
 268       if (!event)
 269         return ret;
 270 
 271       if (event->evtype == XI_KeyRelease)
 272         {
 273           if (XkbKeycodeToKeysym(event->display, event->detail, 0, 0) == GDK_KEY_F10)
 274           {
 275             if (GTK_IS_MENU (priv->last_menu))
 276               gtk_menu_popdown (GTK_MENU (priv->last_menu));
 277           }
 278         }
 279 
 280       if (event->evtype == XI_ButtonPress)
 281         {
 282           priv->pressed_entry = get_entry_at (self, event->root_x, event->root_y);
 283           priv->use_event = (priv->pressed_entry == NULL);
 284 
 285           if (priv->pressed_entry)
 286             ret = GDK_FILTER_REMOVE;
 287         }
 288 
 289       if (event->evtype == XI_ButtonRelease)
 290         {       
 291           IndicatorObjectEntry *entry;
 292           gboolean event_is_a_click = FALSE;
 293           entry = get_entry_at (self, event->root_x, event->root_y);
 294 
 295           if (XIMaskIsSet (event->buttons.mask, 1) || XIMaskIsSet (event->buttons.mask, 3))
 296             {
 297               /* Consider only right and left clicks over the indicators entries */
 298               event_is_a_click = TRUE;
 299             }
 300           else if (XIMaskIsSet (event->buttons.mask, 2) && entry)
 301             {
 302               /* Middle clicks over an appmenu entry are considered just like
 303                * all other clicks */
 304               IndicatorObject *obj = g_hash_table_lookup (priv->entry2indicator_hash, entry);
 305 
 306               if (g_strcmp0 (g_object_get_data (G_OBJECT (obj), "id"), "libappmenu.so") == 0)
 307                 {
 308                   event_is_a_click = TRUE;
 309                 }
 310             }
 311 
 312           if (event_is_a_click)
 313             {
 314               if (priv->use_event)
 315                 {
 316                   priv->use_event = FALSE;
 317                 }
 318               else
 319                 {
 320                   if (entry)
 321                     {
 322                       if (entry != priv->pressed_entry)
 323                         {
 324                           ret = GDK_FILTER_REMOVE;
 325                           priv->use_event = TRUE;
 326                         }
 327                       else if (priv->last_entry && entry != priv->last_entry)
 328                         {
 329                           /* If we were navigating over indicators using the keyboard
 330                            * and now we click over the indicator under the mouse, we
 331                            * must force it to show back again, not make it close */
 332                           gchar *entry_id = g_strdup_printf ("%p", entry);
 333                           g_signal_emit (self, _service_signals[ENTRY_ACTIVATE_REQUEST], 0, entry_id);
 334                           g_free (entry_id);
 335                         }
 336                     }
 337                 }
 338             }
 339           else if ((XIMaskIsSet (event->buttons.mask, 2) ||
 340                     XIMaskIsSet (event->buttons.mask, 4) ||
 341                     XIMaskIsSet (event->buttons.mask, 5)) && entry)
 342             {
 343               /* If we're scrolling or middle-clicking over an indicator
 344                * (which is not an appmenu entry) then we need to send the
 345                * event to the indicator itself, and avoid it to close */
 346               gchar *entry_id = g_strdup_printf ("%p", entry);
 347 
 348               if (XIMaskIsSet (event->buttons.mask, 4) || XIMaskIsSet (event->buttons.mask, 5))
 349                 {
 350                   gint32 delta = XIMaskIsSet (event->buttons.mask, 4) ? 120 : -120;
 351                   panel_service_scroll_entry (self, entry_id, delta);
 352                 }
 353               else if (entry == priv->pressed_entry)
 354                 {
 355                   panel_service_secondary_activate_entry (self, entry_id, time(NULL));
 356                 }
 357 
 358               ret = GDK_FILTER_REMOVE;
 359               g_free (entry_id);
 360             }
 361         }
 362     }
 363 
 364   return ret;
 365 }
 366 
 367 static IndicatorObjectEntry *
 368 get_entry_at (PanelService *self, gint x, gint y)
 369 {
 370   GHashTableIter panel_iter, entries_iter;
 371   gpointer key, value, k, v;
 372 
 373   g_hash_table_iter_init (&panel_iter, self->priv->panel2entries_hash);
 374   while (g_hash_table_iter_next (&panel_iter, &key, &value))
 375     {
 376       GHashTable *entry2geometry_hash = value;
 377       g_hash_table_iter_init (&entries_iter, entry2geometry_hash);
 378 
 379       while (g_hash_table_iter_next (&entries_iter, &k, &v))
 380         {
 381           IndicatorObjectEntry *entry = k;
 382           GdkRectangle *geo = v;
 383 
 384           if (x >= geo->x && x <= (geo->x + geo->width) &&
 385               y >= geo->y && y <= (geo->y + geo->height))
 386             {
 387               return entry;
 388             }
 389         }
 390     }
 391 
 392   return NULL;
 393 }
 394 
 395 static void
 396 panel_service_get_indicator_entry_by_id (PanelService *self, 
 397                                          const gchar *entry_id,
 398                                          IndicatorObjectEntry **entry,
 399                                          IndicatorObject **object)
 400 {
 401   IndicatorObject *indicator;
 402   IndicatorObjectEntry *probably_entry;
 403   PanelServicePrivate *priv = self->priv;
 404 
 405   /* FIXME: eeek, why do we even do this? */
 406   if (sscanf (entry_id, "%p", &probably_entry) == 1)
 407   {
 408     /* check that there really is such IndicatorObjectEntry */
 409     indicator = g_hash_table_lookup (priv->entry2indicator_hash,
 410                                      probably_entry);
 411     if (object) *object = indicator;
 412     if (entry) *entry = indicator != NULL ? probably_entry : NULL;
 413   }
 414   else
 415   {
 416     if (object) *object = NULL;
 417     if (entry) *entry = NULL;
 418   }
 419 }
 420 
 421 static gboolean
 422 initial_resync (PanelService *self)
 423 {
 424   if (PANEL_IS_SERVICE (self))
 425     {
 426       g_signal_emit (self, _service_signals[RE_SYNC], 0, "");
 427       self->priv->initial_sync_id = 0;
 428     }
 429   return FALSE;
 430 }
 431 
 432 static void
 433 panel_service_init (PanelService *self)
 434 {
 435   PanelServicePrivate *priv;
 436   priv = self->priv = GET_PRIVATE (self);
 437 
 438   priv->offscreen_window = gtk_offscreen_window_new ();
 439   priv->menubar = gtk_menu_bar_new ();
 440   gtk_container_add (GTK_CONTAINER (priv->offscreen_window), priv->menubar);
 441 
 442   gdk_window_add_filter (NULL, (GdkFilterFunc)event_filter, self);
 443 
 444   priv->entry2indicator_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
 445   priv->panel2entries_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
 446                                                     g_free,
 447                                                     (GDestroyNotify) g_hash_table_destroy);
 448 
 449   suppress_signals = TRUE;
 450   load_indicators (self);
 451   sort_indicators (self);
 452   suppress_signals = FALSE;
 453 
 454   priv->initial_sync_id = g_idle_add ((GSourceFunc)initial_resync, self);
 455 }
 456 
 457 static gboolean
 458 panel_service_check_cleared (PanelService *self)
 459 {
 460   if (self->priv->indicators == NULL)
 461     {
 462       g_signal_emit (self, _service_signals[INDICATORS_CLEARED], 0);
 463       return FALSE;
 464     }
 465 
 466   return TRUE;
 467 }
 468 
 469 static void
 470 panel_service_actually_remove_indicator (PanelService *self, IndicatorObject *indicator)
 471 {
 472   g_return_if_fail (PANEL_IS_SERVICE (self));
 473   g_return_if_fail (INDICATOR_IS_OBJECT (indicator));
 474 
 475   GList *entries, *l;
 476 
 477   entries = indicator_object_get_entries (indicator);
 478 
 479   if (entries)
 480     {
 481       for (l = entries; l; l = l->next)
 482         {
 483           g_hash_table_remove (self->priv->entry2indicator_hash, l->data);
 484 
 485           GHashTableIter iter;
 486           gpointer key, value;
 487           g_hash_table_iter_init (&iter, self->priv->panel2entries_hash);
 488           while (g_hash_table_iter_next (&iter, &key, &value))
 489             {
 490               GHashTable *entry2geometry_hash = value;
 491 
 492               if (g_hash_table_size (entry2geometry_hash) > 1)
 493                 g_hash_table_remove (entry2geometry_hash, l->data);
 494               else
 495                 g_hash_table_iter_remove (&iter);
 496             }
 497         }
 498 
 499       g_list_free (entries);
 500     }
 501 
 502   self->priv->indicators = g_slist_remove (self->priv->indicators, indicator);
 503 
 504   if (g_object_is_floating (G_OBJECT (indicator)))
 505     {
 506       g_object_ref_sink (G_OBJECT (indicator));
 507     }
 508 
 509   g_object_unref (G_OBJECT (indicator));
 510 }
 511 
 512 static gboolean
 513 panel_service_indicator_remove_timeout (IndicatorObject *indicator)
 514 {
 515   PanelService *self = panel_service_get_default ();
 516   panel_service_actually_remove_indicator (self, indicator);
 517 
 518   return FALSE;
 519 }
 520 
 521 PanelService *
 522 panel_service_get_default ()
 523 {
 524   if (static_service == NULL || !PANEL_IS_SERVICE (static_service))
 525     static_service = g_object_new (PANEL_TYPE_SERVICE, NULL);
 526 
 527   return static_service;
 528 }
 529 
 530 PanelService *
 531 panel_service_get_default_with_indicators (GList *indicators)
 532 {
 533   PanelService *service = panel_service_get_default ();
 534   GList        *i;
 535 
 536   for (i = indicators; i; i = i->next)
 537     {
 538       IndicatorObject *object = i->data;
 539       if (INDICATOR_IS_OBJECT (object))
 540           load_indicator (service, object, NULL);
 541     }
 542 
 543   return service;
 544 }
 545 guint
 546 panel_service_get_n_indicators (PanelService *self)
 547 {
 548   g_return_val_if_fail (PANEL_IS_SERVICE (self), 0);
 549 
 550   return g_slist_length (self->priv->indicators);
 551 }
 552 
 553 IndicatorObject *
 554 panel_service_get_indicator_nth (PanelService *self, guint position)
 555 {
 556   g_return_val_if_fail (PANEL_IS_SERVICE (self), NULL);
 557 
 558   return (IndicatorObject *) g_slist_nth_data (self->priv->indicators, position);
 559 }
 560 
 561 IndicatorObject *
 562 panel_service_get_indicator (PanelService *self, const gchar *indicator_id)
 563 {
 564   g_return_val_if_fail (PANEL_IS_SERVICE (self), NULL);
 565   GSList *l;
 566 
 567   for (l = self->priv->indicators; l; l = l->next)
 568     {
 569       if (g_strcmp0 (indicator_id,
 570                      g_object_get_data (G_OBJECT (l->data), "id")) == 0)
 571         {
 572           return (IndicatorObject *) l->data;
 573         }
 574     }
 575 
 576   return NULL;
 577 }
 578 
 579 void
 580 panel_service_remove_indicator (PanelService *self, IndicatorObject *indicator)
 581 {
 582   g_return_if_fail (PANEL_IS_SERVICE (self));
 583   g_return_if_fail (INDICATOR_IS_OBJECT (indicator));
 584 
 585   gpointer timeout = g_object_get_data (G_OBJECT (indicator), "remove-timeout");
 586 
 587   if (timeout)
 588       g_source_remove (GPOINTER_TO_UINT (timeout));
 589 
 590   g_object_set_data (G_OBJECT (indicator), "remove", GINT_TO_POINTER (TRUE));
 591   notify_object (indicator);
 592 
 593   guint id = g_timeout_add_seconds (1,
 594                                     (GSourceFunc) panel_service_indicator_remove_timeout,
 595                                     indicator);
 596   g_object_set_data (G_OBJECT (indicator), "remove-timeout", GUINT_TO_POINTER (id));
 597 }
 598 
 599 void
 600 panel_service_clear_indicators (PanelService *self)
 601 {
 602   g_return_if_fail (PANEL_IS_SERVICE (self));
 603   GSList *l = self->priv->indicators;
 604 
 605   while (l)
 606     {
 607       IndicatorObject *ind = l->data;
 608       l = l->next;
 609       panel_service_remove_indicator (self, ind);
 610     }
 611 
 612   g_idle_add ((GSourceFunc)panel_service_check_cleared, self);
 613 }
 614 
 615 /*
 616  * Private Methods
 617  */
 618 static gboolean
 619 actually_notify_object (IndicatorObject *object)
 620 {
 621   PanelService *self;
 622   PanelServicePrivate *priv;
 623   gint position;
 624 
 625   if (!PANEL_IS_SERVICE (static_service))
 626     return FALSE;
 627 
 628   if (!INDICATOR_IS_OBJECT (object))
 629     return FALSE;
 630 
 631   self = panel_service_get_default ();
 632   priv = self->priv;
 633 
 634   position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "position"));
 635   priv->timeouts[position] = SYNC_WAITING;
 636 
 637   if (!suppress_signals)
 638     g_signal_emit (self, _service_signals[RE_SYNC],
 639                    0, g_object_get_data (G_OBJECT (object), "id"));
 640 
 641   return FALSE;
 642 }
 643 
 644 static void
 645 notify_object (IndicatorObject *object)
 646 {
 647   PanelService        *self;
 648   PanelServicePrivate *priv;
 649   gint                 position;
 650 
 651   if (suppress_signals)
 652     return;
 653 
 654   self = panel_service_get_default ();
 655   priv = self->priv;
 656 
 657   position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "position"));
 658 
 659   if (priv->timeouts[position] == SYNC_WAITING)
 660     {
 661       /* No need to ping again as we're waiting for the client to sync anyway */
 662       return;
 663     }
 664   else if (priv->timeouts[position] != SYNC_NEUTRAL)
 665     {
 666       /* We were going to signal that a sync was needed, but since there's been another change let's
 667        * hold off a little longer so we're not flooding the client
 668        */
 669       g_source_remove (priv->timeouts[position]);
 670     }
 671 
 672   priv->timeouts[position] = g_timeout_add (NOTIFY_TIMEOUT,
 673                                             (GSourceFunc)actually_notify_object,
 674                                             object);
 675 }
 676 
 677 static void
 678 on_entry_property_changed (GObject        *o,
 679                            GParamSpec      *pspec,
 680                            IndicatorObject *object)
 681 {
 682   notify_object (object);
 683 }
 684 
 685 static void
 686 on_entry_changed (GObject *o,
 687                   IndicatorObject *object)
 688 {
 689   notify_object (object);
 690 }
 691 
 692 static void
 693 on_entry_added (IndicatorObject      *object,
 694                 IndicatorObjectEntry *entry,
 695                 PanelService         *self)
 696 {
 697   PanelServicePrivate *priv;
 698 
 699   g_return_if_fail (PANEL_IS_SERVICE (self));
 700   g_return_if_fail (entry != NULL);
 701   priv = self->priv;
 702 
 703   g_hash_table_insert (priv->entry2indicator_hash, entry, object);
 704 
 705   if (GTK_IS_LABEL (entry->label))
 706     {
 707       g_signal_connect (entry->label, "notify::label",
 708                         G_CALLBACK (on_entry_property_changed), object);
 709 
 710       g_signal_connect (entry->label, "notify::sensitive",
 711                         G_CALLBACK (on_entry_property_changed), object);
 712       g_signal_connect (entry->label, "show",
 713                         G_CALLBACK (on_entry_changed), object);
 714       g_signal_connect (entry->label, "hide",
 715                         G_CALLBACK (on_entry_changed), object);
 716 
 717     }
 718   if (GTK_IS_IMAGE (entry->image))
 719     {
 720       g_signal_connect (entry->image, "notify::storage-type",
 721                         G_CALLBACK (on_entry_property_changed), object);
 722       g_signal_connect (entry->image, "notify::file",
 723                         G_CALLBACK (on_entry_property_changed), object);
 724       g_signal_connect (entry->image, "notify::gicon",
 725                         G_CALLBACK (on_entry_property_changed), object);
 726       g_signal_connect (entry->image, "notify::icon-name",
 727                         G_CALLBACK (on_entry_property_changed), object);
 728       g_signal_connect (entry->image, "notify::pixbuf",
 729                         G_CALLBACK (on_entry_property_changed), object);
 730       g_signal_connect (entry->image, "notify::stock",
 731                         G_CALLBACK (on_entry_property_changed), object);
 732 
 733       g_signal_connect (entry->image, "notify::sensitive",
 734                         G_CALLBACK (on_entry_property_changed), object);
 735       g_signal_connect (entry->image, "show",
 736                         G_CALLBACK (on_entry_changed), object);
 737       g_signal_connect (entry->image, "hide",
 738                         G_CALLBACK (on_entry_changed), object);
 739 
 740     }
 741 
 742   notify_object (object);
 743 }
 744 
 745 static void
 746 on_entry_removed (IndicatorObject      *object,
 747                   IndicatorObjectEntry *entry,
 748                   PanelService         *self)
 749 {
 750   PanelServicePrivate *priv;
 751   g_return_if_fail (PANEL_IS_SERVICE (self));
 752   g_return_if_fail (entry != NULL);
 753 
 754   priv = self->priv;
 755 
 756   g_hash_table_remove (priv->entry2indicator_hash, entry);
 757   /* Don't remove here the value from priv->panel2entries_hash, this should
 758    * be done in during the sync, to avoid false positive.
 759    * FIXME this in libappmenu.so to avoid to send an "entry-removed" signal
 760    * when switching the focus from a window to one of its dialog children */
 761 
 762   notify_object (object);
 763 }
 764 
 765 static void
 766 on_entry_moved (IndicatorObject      *object,
 767                 IndicatorObjectEntry *entry,
 768                 PanelService         *self)
 769 {
 770   notify_object (object);
 771 }
 772 
 773 static void
 774 on_indicator_menu_show (IndicatorObject      *object,
 775                         IndicatorObjectEntry *entry,
 776                         guint32               timestamp,
 777                         PanelService         *self)
 778 {
 779   gchar *entry_id;
 780   
 781   g_return_if_fail (PANEL_IS_SERVICE (self));
 782   if (entry == NULL)
 783     {
 784       g_warning ("on_indicator_menu_show() called with a NULL entry");
 785       return;
 786     }
 787 
 788   entry_id = g_strdup_printf ("%p", entry);
 789 
 790   g_signal_emit (self, _service_signals[ENTRY_ACTIVATE_REQUEST], 0, entry_id);
 791 
 792   g_free (entry_id);
 793 }
 794 
 795 static void
 796 on_indicator_menu_show_now_changed (IndicatorObject      *object,
 797                                     IndicatorObjectEntry *entry,
 798                                     gboolean              show_now_changed,
 799                                     PanelService         *self)
 800 {
 801   gchar *entry_id;
 802   
 803   g_return_if_fail (PANEL_IS_SERVICE (self));
 804   if (entry == NULL)
 805     {
 806       g_warning ("on_indicator_menu_show_now_changed() called with a NULL entry");
 807       return;
 808     }
 809 
 810   entry_id = g_strdup_printf ("%p", entry);
 811 
 812   g_signal_emit (self, _service_signals[ENTRY_SHOW_NOW_CHANGED], 0, entry_id, show_now_changed);
 813 
 814   g_free (entry_id);
 815 }
 816 
 817 static const gchar * indicator_environment[] = {
 818   "unity",
 819   "unity-3d",
 820   "unity-panel-service",
 821   NULL
 822 };
 823 
 824 static void
 825 load_indicator (PanelService *self, IndicatorObject *object, const gchar *_name)
 826 {
 827   PanelServicePrivate *priv = self->priv;
 828   gchar *name;
 829   GList *entries, *entry;
 830 
CID 10670 - PW.CAST_TO_QUALIFIED_TYPE
type qualifier is meaningless on cast type
 831   indicator_object_set_environment(object, (const GStrv)indicator_environment);
 832 
 833   if (_name != NULL)
 834     name = g_strdup (_name);
 835   else
 836     name = g_strdup_printf ("%p", object);
 837 
 838   priv->indicators = g_slist_append (priv->indicators, object);
 839 
 840   g_object_set_data_full (G_OBJECT (object), "id", g_strdup (name), g_free);
 841 
 842   g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_ENTRY_ADDED,
 843                     G_CALLBACK (on_entry_added), self);
 844   g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_ENTRY_REMOVED,
 845                     G_CALLBACK (on_entry_removed), self);
 846   g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_ENTRY_MOVED,
 847                     G_CALLBACK (on_entry_moved), self);
 848   g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_MENU_SHOW,
 849                     G_CALLBACK (on_indicator_menu_show), self);
 850   g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_SHOW_NOW_CHANGED,
 851                     G_CALLBACK (on_indicator_menu_show_now_changed), self);
 852 
 853   entries = indicator_object_get_entries (object);
 854   for (entry = entries; entry != NULL; entry = entry->next)
 855     {
 856       on_entry_added (object, entry->data, self);
 857     }
 858   g_list_free (entries);
 859 
 860   g_free (name);
 861 }
 862 
 863 static void
 864 load_indicators (PanelService *self)
 865 {
 866   GDir        *dir;
 867   const gchar *name;
 868 
 869   if (!g_file_test (INDICATORDIR,
 870                     G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
 871     {
 872       g_warning ("%s does not exist, cannot read any indicators", INDICATORDIR);
 873       gtk_main_quit ();
 874     }
 875 
 876   dir = g_dir_open (INDICATORDIR, 0, NULL);
 877   while ((name = g_dir_read_name (dir)) != NULL)
 878     {
 879       IndicatorObject *object;
 880       gchar           *path;
 881 
 882       if (!g_str_has_suffix (name, ".so"))
 883         continue;
 884 
 885       path = g_build_filename (INDICATORDIR, name, NULL);
 886       g_debug ("Loading: %s", path);
 887 
 888       object = indicator_object_new_from_file (path);
 889       if (object == NULL)
 890         {
 891           g_warning ("Unable to load '%s'", path);
 892           g_free (path);
 893           continue;
 894         }
 895       load_indicator (self, object, name);
 896 
 897       g_free (path);
 898     }
 899 
 900   g_dir_close (dir);
 901 }
 902 
 903 static gint
 904 name2order (const gchar * name, const gchar * hint)
 905 {
 906   int i;
 907 
 908   for (i = 0; indicator_order[i][0] != NULL; i++)
 909     {
 910       if (g_strcmp0(name, indicator_order[i][0]) == 0 &&
 911           g_strcmp0(hint, indicator_order[i][1]) == 0)
 912         {
 913           return i;
 914         }
 915     }
 916   return -1;
 917 }
 918 
 919 static gint
 920 name2priority (const gchar * name, const gchar * hint)
 921 {
 922   gint order = name2order (name, hint);
 923   if (order > -1)
 924     return order * MAX_INDICATOR_ENTRIES;
 925 
 926   return order;
 927 }
 928 
 929 static int
 930 indicator_compare_func (IndicatorObject *o1, IndicatorObject *o2)
 931 {
 932   gchar *s1;
 933   gchar *s2;
 934   int    i1;
 935   int    i2;
 936 
 937   s1 = g_object_get_data (G_OBJECT (o1), "id");
 938   s2 = g_object_get_data (G_OBJECT (o2), "id");
 939 
 940   i1 = name2order (s1, NULL);
 941   i2 = name2order (s2, NULL);
 942 
 943   return i1 - i2;
 944 }
 945 
 946 static void
 947 sort_indicators (PanelService *self)
 948 {
 949   GSList *i;
 950   int     k = 0;
 951   int     prio = 0;
 952 
 953   self->priv->indicators = g_slist_sort (self->priv->indicators,
 954                                          (GCompareFunc)indicator_compare_func);
 955 
 956   for (i = self->priv->indicators; i; i = i->next)
 957     {
 958       prio = name2priority(g_object_get_data (G_OBJECT (i->data), "id"), NULL);
 959       if (prio < 0) continue;
 960       g_object_set_data (G_OBJECT (i->data), "priority", GINT_TO_POINTER (prio));
 961       g_object_set_data (G_OBJECT (i->data), "position", GINT_TO_POINTER (k));
 962       self->priv->timeouts[k] = SYNC_NEUTRAL;
 963       k++;
 964     }
 965 }
 966 
 967 static gchar *
 968 gtk_image_to_data (GtkImage *image)
 969 {
 970   GtkImageType type = gtk_image_get_storage_type (image);
 971   gchar *ret = NULL;
 972 
 973   if (type == GTK_IMAGE_PIXBUF)
 974     {
 975       GdkPixbuf  *pixbuf;
 976       gchar      *buffer = NULL;
 977       gsize       buffer_size = 0;
 978       GError     *error = NULL;
 979 
 980       pixbuf = gtk_image_get_pixbuf (image);
 981 
 982       if (gdk_pixbuf_save_to_buffer (pixbuf, &buffer, &buffer_size, "png", &error, NULL))
 983         {
 984           ret = g_base64_encode ((const guchar *)buffer, buffer_size);
 985           g_free (buffer);
 986         }
 987       else
 988         {
 989           g_warning ("Unable to convert pixbuf to png data: '%s'", error ? error->message : "unknown");
 990           if (error)
 991             g_error_free (error);
 992 
 993           ret = g_strdup ("");
 994         }
 995     }
 996   else if (type == GTK_IMAGE_STOCK)
 997     {
 998       g_object_get (G_OBJECT (image), "stock", &ret, NULL);
 999     }
1000   else if (type == GTK_IMAGE_ICON_NAME)
1001     {
1002       g_object_get (G_OBJECT (image), "icon-name", &ret, NULL);
1003     }
1004   else if (type == GTK_IMAGE_GICON)
1005     {
1006       GIcon *icon = NULL;
1007       gtk_image_get_gicon (image, &icon, NULL);
1008       if (G_IS_ICON (icon))
1009         {
1010           ret = g_icon_to_string (icon);
1011         }
1012     }
1013   else
1014     {
1015       ret = g_strdup ("");
1016       g_warning ("Unable to support GtkImageType: %d", type);
1017     }
1018 
1019   return ret;
1020 }
1021 
1022 static void
1023 indicator_entry_to_variant (IndicatorObjectEntry *entry,
1024                             const gchar          *id,
1025                             const gchar          *indicator_id,
1026                             GVariantBuilder      *b,
1027                             gint                  prio)
1028 {
1029   gboolean is_label = GTK_IS_LABEL (entry->label);
1030   gboolean is_image = GTK_IS_IMAGE (entry->image);
1031   gchar *image_data = NULL;
1032 
1033   g_variant_builder_add (b, "(ssssbbusbbi)",
1034                          indicator_id,
1035                          id,
1036                          entry->name_hint ? entry->name_hint : "",
1037                          is_label ? gtk_label_get_label (entry->label) : "",
1038                          is_label ? gtk_widget_get_sensitive (GTK_WIDGET (entry->label)) : FALSE,
1039                          is_label ? gtk_widget_get_visible (GTK_WIDGET (entry->label)) : FALSE,
1040                          is_image ? (guint32)gtk_image_get_storage_type (entry->image) : (guint32) 0,
1041                          is_image ? (image_data = gtk_image_to_data (entry->image)) : "",
1042                          is_image ? gtk_widget_get_sensitive (GTK_WIDGET (entry->image)) : FALSE,
1043                          is_image ? gtk_widget_get_visible (GTK_WIDGET (entry->image)) : FALSE,
1044                          prio);
1045 
1046   g_free (image_data);
1047 }
1048 
1049 static void
1050 indicator_entry_null_to_variant (const gchar     *indicator_id,
1051                                  GVariantBuilder *b)
1052 {
1053   g_variant_builder_add (b, "(ssssbbusbbi)",
1054                          indicator_id,
1055                          "",
1056                          "",
1057                          "",
1058                          FALSE,
1059                          FALSE,
1060                          (guint32) 0,
1061                          "",
1062                          FALSE,
1063                          FALSE,
1064                          -1);
1065 }
1066 
1067 static void
1068 indicator_object_to_variant (IndicatorObject *object, const gchar *indicator_id, GVariantBuilder *b)
1069 {
1070   GList *entries, *e;
1071   gint parent_prio = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "priority"));
1072   entries = indicator_object_get_entries (object);
1073   gint index = 0;
1074 
1075   if (entries)
1076     {
1077       for (e = entries; e; e = e->next)
1078         {
1079           gint prio = -1;
1080           IndicatorObjectEntry *entry = e->data;
1081           gchar *id = g_strdup_printf ("%p", entry);
1082 
1083           if (entry->name_hint)
1084             {
1085               prio = name2priority(indicator_id, entry->name_hint);
1086             }
1087 
1088           if (prio == -1)
1089             {
1090               prio = parent_prio + index;
1091               index++;
1092             }
1093 
1094           indicator_entry_to_variant (entry, id, indicator_id, b, prio);
1095           g_free (id);
1096         }
1097     }
1098   else
1099     {
1100       /* Add a null entry to indicate that there is an indicator here, it's just empty */
1101       indicator_entry_null_to_variant (indicator_id, b);
1102     }
1103   g_list_free (entries);
1104 }
1105 
1106 static void
1107 positon_menu (GtkMenu  *menu,
1108               gint     *x,
1109               gint     *y,
1110               gboolean *push,
1111               gpointer  user_data)
1112 {
1113   PanelService *self = PANEL_SERVICE (user_data);
1114   PanelServicePrivate *priv = self->priv;
1115 
1116   *x = priv->last_x;
1117   *y = priv->last_y;
1118   *push = TRUE;
1119 }
1120 
1121 static void
1122 on_active_menu_hidden (GtkMenu *menu, PanelService *self)
1123 {
1124   PanelServicePrivate *priv = self->priv;
1125 
1126   priv->last_x = 0;
1127   priv->last_y = 0;
1128   priv->last_menu_button = 0;
1129 
1130   g_signal_handler_disconnect (priv->last_menu, priv->last_menu_id);
1131   g_signal_handler_disconnect (priv->last_menu, priv->last_menu_move_id);
1132 
1133   GtkWidget *top_win = gtk_widget_get_toplevel (GTK_WIDGET (priv->last_menu));
1134   if (GTK_IS_WINDOW (top_win))
1135     gtk_window_set_attached_to (GTK_WINDOW (top_win), NULL);
1136 
1137   priv->last_menu = NULL;
1138   priv->last_menu_id = 0;
1139   priv->last_menu_move_id = 0;
1140   priv->last_entry = NULL;
1141   priv->last_left = 0;
1142   priv->last_right = 0;
1143   priv->last_top = 0;
1144   priv->last_bottom = 0;
1145 
1146   priv->use_event = FALSE;
1147   priv->pressed_entry = NULL;
1148 
1149   g_signal_emit (self, _service_signals[ENTRY_ACTIVATED], 0, "");
1150 }
1151 
1152 /*
1153  * Public Methods
1154  */
1155 GVariant *
1156 panel_service_sync (PanelService *self)
1157 {
1158   GVariantBuilder b;
1159   GSList *i;
1160 
1161   g_variant_builder_init (&b, G_VARIANT_TYPE ("(a(ssssbbusbbi))"));
1162   g_variant_builder_open (&b, G_VARIANT_TYPE ("a(ssssbbusbbi)"));
1163 
1164   for (i = self->priv->indicators; i; i = i->next)
1165     {
1166       const gchar *indicator_id = g_object_get_data (G_OBJECT (i->data), "id");
1167       gint position;
1168 
1169       /* Set the sync back to neutral */
1170       position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (i->data), "position"));
1171       self->priv->timeouts[position] = SYNC_NEUTRAL;
1172       indicator_object_to_variant (i->data, indicator_id, &b);
1173     }
1174 
1175   g_variant_builder_close (&b);
1176   return g_variant_builder_end (&b);
1177 }
1178 
1179 GVariant *
1180 panel_service_sync_one (PanelService *self, const gchar *indicator_id)
1181 {
1182   GVariantBuilder b;
1183   GSList *i;
1184 
1185   g_variant_builder_init (&b, G_VARIANT_TYPE ("(a(ssssbbusbbi))"));
1186   g_variant_builder_open (&b, G_VARIANT_TYPE ("a(ssssbbusbbi)"));
1187 
1188   for (i = self->priv->indicators; i; i = i->next)
1189     {
1190       if (g_strcmp0 (indicator_id,
1191                      g_object_get_data (G_OBJECT (i->data), "id")) == 0)
1192         {
1193           gint position;
1194 
1195           /* Set the sync back to neutral */
1196           position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (i->data), "position"));
1197           self->priv->timeouts[position] = SYNC_NEUTRAL;
1198 
1199           if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (i->data), "remove")) != TRUE)
1200             {
1201               indicator_object_to_variant (i->data, indicator_id, &b);
1202             }
1203           else
1204             {
1205               indicator_entry_null_to_variant (indicator_id, &b);
1206               panel_service_actually_remove_indicator (self, i->data);
1207             }
1208 
1209           break;
1210         }
1211     }
1212 
1213   g_variant_builder_close (&b);
1214   return g_variant_builder_end (&b);
1215 }
1216 
1217 void
1218 panel_service_sync_geometry (PanelService *self,
1219                              const gchar *panel_id,
1220                              const gchar *entry_id,
1221                              gint x,
1222                              gint y,
1223                              gint width,
1224                              gint height)
1225 {
1226   IndicatorObject      *object;
1227   IndicatorObjectEntry *entry;
1228   PanelServicePrivate  *priv = self->priv;
1229 
1230   panel_service_get_indicator_entry_by_id (self, entry_id, &entry, &object);
1231 
1232   if (entry)
1233     {
1234       GHashTable *entry2geometry_hash = g_hash_table_lookup (priv->panel2entries_hash, panel_id);
1235 
1236       if (width < 0 || height < 0)
1237         {
1238           if (entry2geometry_hash)
1239             {
1240               if (g_hash_table_size (entry2geometry_hash) > 1)
1241                 {
1242                   g_hash_table_remove (entry2geometry_hash, entry);
1243                 }
1244               else
1245                 {
1246                   g_hash_table_remove (priv->panel2entries_hash, panel_id);
1247                 }
1248             }
1249         }
1250       else
1251         {
1252           GdkRectangle *geo = NULL;
1253 
1254           if (entry2geometry_hash == NULL)
1255           {
1256             //FIXME - this leaks memory but i'm not 100% on the logic,
1257             // using g_free as the keys destructor function causes all
1258             // kinds of problems 
1259             entry2geometry_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal,
1260                                                         NULL, g_free);
1261             g_hash_table_insert (priv->panel2entries_hash, g_strdup (panel_id),
1262                                  entry2geometry_hash);
1263           }
1264           else
1265           {
1266             geo = g_hash_table_lookup (entry2geometry_hash, entry);
1267           }
1268 
1269           if (geo == NULL)
1270             {
1271               geo = g_new (GdkRectangle, 1);
1272               g_hash_table_insert (entry2geometry_hash, entry, geo);
1273             }
1274 
1275           geo->x = x;
1276           geo->y = y;
1277           geo->width = width;
1278           geo->height = height;
1279         }
1280 
1281       g_signal_emit (self, _service_signals[GEOMETRIES_CHANGED], 0, object, entry, x, y, width, height);
1282     }
1283 }
1284 
1285 static gboolean
1286 should_skip_menu (IndicatorObjectEntry *entry)
1287 {
1288   gboolean label_ok = FALSE;
1289   gboolean image_ok = FALSE;
1290 
1291   g_return_val_if_fail (entry != NULL, TRUE);
1292 
1293   if (GTK_IS_LABEL (entry->label))
1294     {
1295       label_ok = gtk_widget_get_visible (GTK_WIDGET (entry->label))
1296         && gtk_widget_is_sensitive (GTK_WIDGET (entry->label));
1297     }
1298 
1299   if (GTK_IS_IMAGE (entry->image))
1300     {
1301       image_ok = gtk_widget_get_visible (GTK_WIDGET (entry->image))
1302         && gtk_widget_is_sensitive (GTK_WIDGET (entry->image));
1303     }
1304 
1305   return !label_ok && !image_ok;
1306 }
1307 
1308 static int
1309 indicator_entry_compare_func (gpointer* v1, gpointer* v2)
1310 {
1311   return (GPOINTER_TO_INT (v1[1]) > GPOINTER_TO_INT (v2[1])) ? 1 : -1;
1312 }
1313 
1314 static void
1315 activate_next_prev_menu (PanelService         *self,
1316                          IndicatorObject      *object,
1317                          IndicatorObjectEntry *entry,
1318                          GtkMenuDirectionType  direction)
1319 {
1320   IndicatorObjectEntry *new_entry;
1321   PanelServicePrivate *priv = self->priv;
1322   GSList *indicators = priv->indicators;
1323   GList  *ordered_entries = NULL;
1324   GList  *entries;
1325   gchar  *id;
1326   GSList *l;
1327   GList *ll;
1328 
1329   for (l = indicators; l; l = l->next)
1330     {
1331       const gchar *indicator_id = g_object_get_data (G_OBJECT (l->data), "id");
1332       gint parent_priority = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (l->data), "priority"));
1333       entries = indicator_object_get_entries (l->data);
1334       if (entries)
1335         {
1336           int index = 0;
1337 
1338           for (ll = entries; ll; ll = ll->next)
1339             {
1340               gint prio = -1;
1341               new_entry = ll->data;
1342 
1343               if (should_skip_menu (new_entry))
1344                 continue;
1345 
1346               if (new_entry->name_hint)
1347                 {
1348                   prio = name2priority(indicator_id, new_entry->name_hint);
1349                 }
1350 
1351               if (prio == -1)
1352                 {
1353                   prio = parent_priority + index;
1354                   index++;
1355                 }
1356 
1357               gpointer *values = g_new (gpointer, 2);
1358               values[0] = new_entry;
1359               values[1] = GINT_TO_POINTER (prio);
1360               ordered_entries = g_list_insert_sorted (ordered_entries, values,
1361                                                       (GCompareFunc) indicator_entry_compare_func);
1362             }
1363 
1364           g_list_free (entries);
1365         }
1366     }
1367 
1368   new_entry = NULL;
1369   for (ll = ordered_entries; ll; ll = ll->next)
1370     {
1371       gpointer *values = ll->data;
1372       if (entry == values[0])
1373         {
1374           if (direction == GTK_MENU_DIR_CHILD)
1375             {
1376               values = ll->next ? ll->next->data : ordered_entries->data;
1377             }
1378           else
1379             {
1380               values = ll->prev ? ll->prev->data : g_list_last(ordered_entries)->data;
1381             }
1382 
1383           new_entry = values[0];
1384           break;
1385         }
1386     }
1387 
1388   if (new_entry)
1389     {
1390       id = g_strdup_printf ("%p", new_entry);
1391       g_signal_emit (self, _service_signals[ENTRY_ACTIVATE_REQUEST], 0, id);
1392       g_free (id);
1393     }
1394 
1395   g_list_free_full (ordered_entries, g_free);
1396 }
1397 
1398 static void
1399 on_active_menu_move_current (GtkMenu              *menu,
1400                              GtkMenuDirectionType  direction,
1401                              PanelService         *self)
1402 {
1403   PanelServicePrivate *priv;
1404   IndicatorObject     *object;
1405 
1406   g_return_if_fail (PANEL_IS_SERVICE (self));
1407   priv = self->priv;
1408 
1409   /* Not interested in up or down */
1410   if (direction == GTK_MENU_DIR_NEXT || direction == GTK_MENU_DIR_PREV)
1411     return;
1412 
1413   /* We don't want to distrupt going into submenus */
1414   if (direction == GTK_MENU_DIR_CHILD)
1415     {
1416       GList               *children, *c;
1417       children = gtk_container_get_children (GTK_CONTAINER (menu));
1418       for (c = children; c; c = c->next)
1419         {
1420           GtkWidget *item = (GtkWidget *)c->data;
1421 
1422           if (GTK_IS_MENU_ITEM (item)
1423               && gtk_widget_get_state (item) == GTK_STATE_PRELIGHT
1424               && gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)))
1425             {
1426               /* Skip direction due to there being a submenu,
1427                * and we don't want to inhibit going into that */
1428               return;
1429             }
1430         }
1431       g_list_free (children);
1432     }
1433 
1434   /* Find the next/prev indicator */
1435   object = g_hash_table_lookup (priv->entry2indicator_hash, priv->last_entry);
1436   if (object == NULL)
1437     {
1438       g_warning ("Unable to find IndicatorObject for entry");
1439       return;
1440     }
1441 
1442   activate_next_prev_menu (self, object, priv->last_entry, direction);
1443 }
1444 
1445 static void
1446 menu_deactivated (GtkWidget *menu)
1447 {
1448   g_signal_handlers_disconnect_by_func (menu, menu_deactivated, NULL);
1449   gtk_widget_destroy (menu);
1450 }
1451 
1452 void
1453 panel_service_show_entry (PanelService *self,
1454                           const gchar  *entry_id,
1455                           guint32       timestamp,
1456                           gint32        x,
1457                           gint32        y,
1458                           gint32        button)
1459 {
1460   IndicatorObject      *object;
1461   IndicatorObjectEntry *entry;
1462   GtkWidget            *last_menu;
1463   PanelServicePrivate  *priv = self->priv;
1464 
1465   panel_service_get_indicator_entry_by_id (self, entry_id, &entry, &object);
1466 
1467   g_return_if_fail (entry);
1468 
1469   if (priv->last_entry == entry)
1470     return;
1471 
1472   last_menu = GTK_WIDGET (priv->last_menu);
1473 
1474   if (GTK_IS_MENU (priv->last_menu))
1475     {
1476       priv->last_x = 0;
1477       priv->last_y = 0;
1478 
1479       g_signal_handler_disconnect (priv->last_menu, priv->last_menu_id);
1480       g_signal_handler_disconnect (priv->last_menu, priv->last_menu_move_id);
1481 
1482       priv->last_entry = NULL;
1483       priv->last_menu = NULL;
1484       priv->last_menu_id = 0;
1485       priv->last_menu_move_id = 0;
1486       priv->last_menu_button = 0;
1487     }
1488 
1489   if (entry != NULL)
1490     {
1491       g_signal_emit (self, _service_signals[ENTRY_ACTIVATED], 0, entry_id);
1492 
1493       if (GTK_IS_MENU (entry->menu))
1494         {
1495           priv->last_menu = entry->menu;
1496         }
1497       else
1498         {
1499           /* For some reason, this entry doesn't have a menu.  To simplify the
1500              rest of the code and to keep scrubbing fluidly, we'll create a
1501              stub menu for the duration of this scrub. */
1502           priv->last_menu = GTK_MENU (gtk_menu_new ());
1503           g_signal_connect (priv->last_menu, "deactivate",
1504                             G_CALLBACK (menu_deactivated), NULL);
1505           g_signal_connect (priv->last_menu, "destroy",
1506                             G_CALLBACK (gtk_widget_destroyed), &priv->last_menu);
1507         }
1508 
1509       GtkWidget *top_widget = gtk_widget_get_toplevel (GTK_WIDGET (priv->last_menu));
1510 
1511       if (GTK_IS_WINDOW (top_widget))
1512         {
1513           GtkWindow *top_win = GTK_WINDOW (top_widget);
1514 
1515           if (gtk_window_get_attached_to (top_win) != priv->menubar)
1516             gtk_window_set_attached_to (top_win, priv->menubar);
1517         }
1518 
1519       priv->last_entry = entry;
1520       priv->last_x = x;
1521       priv->last_y = y;
1522       priv->last_menu_button = button;
1523       priv->last_menu_id = g_signal_connect (priv->last_menu, "hide",
1524                                              G_CALLBACK (on_active_menu_hidden), self);
1525       priv->last_menu_move_id = g_signal_connect_after (priv->last_menu, "move-current",
1526                                                         G_CALLBACK (on_active_menu_move_current), self);
1527 
1528       indicator_object_entry_activate (object, entry, CurrentTime);
1529 
1530       gtk_menu_popup (priv->last_menu, NULL, NULL, positon_menu, self, 0, CurrentTime);
1531       GdkWindow *gdkwin = gtk_widget_get_window (GTK_WIDGET (priv->last_menu));
1532       if (gdkwin != NULL)
1533       {
1534         gint left=0, top=0, width=0, height=0;
1535 
1536         gdk_window_get_geometry (gdkwin, NULL, NULL, &width, &height);
1537         gdk_window_get_origin (gdkwin, &left, &top);
1538 
1539         priv->last_left = left;
1540         priv->last_right = left + width -1;
1541         priv->last_top = top;
1542         priv->last_bottom = top + height -1;
1543       }
1544       else
1545       {
1546         priv->last_left = 0;
1547         priv->last_right = 0;
1548         priv->last_top = 0;
1549         priv->last_bottom = 0;
1550       }
1551     }
1552 
1553   /* We popdown the old one last so we don't accidently send key focus back to the
1554    * active application (which will make it change colour (as state changes), which
1555    * then looks like flickering to the user.
1556    */
1557   if (GTK_IS_MENU (last_menu))
1558     gtk_menu_popdown (GTK_MENU (last_menu));
1559 }
1560 
1561 void
1562 panel_service_secondary_activate_entry (PanelService *self,
1563                                         const gchar  *entry_id,
1564                                         guint32       timestamp)
1565 {
1566   IndicatorObject      *object;
1567   IndicatorObjectEntry *entry;
1568 
1569   panel_service_get_indicator_entry_by_id (self, entry_id, &entry, &object);
1570   g_return_if_fail (entry);
1571 
1572   g_signal_emit_by_name(object, INDICATOR_OBJECT_SIGNAL_SECONDARY_ACTIVATE, entry,
1573                         timestamp);
1574 }
1575 
1576 void
1577 panel_service_scroll_entry (PanelService   *self,
1578                             const gchar    *entry_id,
1579                             gint32         delta)
1580 {
1581   IndicatorObject      *object;
1582   IndicatorObjectEntry *entry;
1583 
1584   panel_service_get_indicator_entry_by_id (self, entry_id, &entry, &object);
1585   g_return_if_fail (entry);
1586 
1587   GdkScrollDirection direction = delta < 0 ? GDK_SCROLL_DOWN : GDK_SCROLL_UP;
1588 
1589   g_signal_emit_by_name(object, INDICATOR_OBJECT_SIGNAL_ENTRY_SCROLLED, entry,
1590                         abs(delta/120), direction);
1591 }