// -*- Mode: C; indent-tabs-mode: nil; tab-width: 2 -*-
/*
* Copyright (C) 2010-2012 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Authored by: Neil Jagdish Patel
* Rodrigo Moya
* Marco Trevisan (TreviƱo)
*/
#if HAVE_CONFIG_H
#include
#endif
#include "panel-marshal.h"
#include "panel-service.h"
#include
#include
#include
#include
#include
#include
#include
#include "panel-marshal.h"
G_DEFINE_TYPE (PanelService, panel_service, G_TYPE_OBJECT);
#define GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), PANEL_TYPE_SERVICE, PanelServicePrivate))
#define NOTIFY_TIMEOUT 80
#define N_TIMEOUT_SLOTS 50
#define MAX_INDICATOR_ENTRIES 50
#define COMPIZ_OPTIONS_PATH "/apps/compiz-1/plugins/unityshell/screen0/options"
#define MENU_TOGGLE_KEYBINDING_PATH COMPIZ_OPTIONS_PATH"/panel_first_menu"
static PanelService *static_service = NULL;
struct _PanelServicePrivate
{
GSList *indicators;
GHashTable *id2entry_hash;
GHashTable *panel2entries_hash;
guint initial_sync_id;
gint32 timeouts[N_TIMEOUT_SLOTS];
IndicatorObjectEntry *last_entry;
GtkWidget *menubar;
GtkWidget *offscreen_window;
GtkMenu *last_menu;
guint32 last_menu_id;
guint32 last_menu_move_id;
gint32 last_x;
gint32 last_y;
gint last_left;
gint last_top;
gint last_right;
gint last_bottom;
guint32 last_menu_button;
KeyCode toggle_key;
guint32 toggle_modifiers;
guint32 key_monitor_id;
IndicatorObjectEntry *pressed_entry;
gboolean use_event;
};
/* Globals */
static gboolean suppress_signals = FALSE;
enum
{
ENTRY_ACTIVATED = 0,
RE_SYNC,
ENTRY_ACTIVATE_REQUEST,
ENTRY_SHOW_NOW_CHANGED,
GEOMETRIES_CHANGED,
INDICATORS_CLEARED,
LAST_SIGNAL
};
enum
{
SYNC_WAITING = -1,
SYNC_NEUTRAL = 0,
};
static guint32 _service_signals[LAST_SIGNAL] = { 0 };
static const gchar * indicator_order[][2] = {
{"libappmenu.so", NULL}, /* indicator-appmenu" */
{"libapplication.so", NULL}, /* indicator-application" */
{"libprintersmenu.so", NULL}, /* indicator-printers */
{"libapplication.so", "gsd-keyboard-xkb"}, /* keyboard layout selector */
{"libmessaging.so", NULL}, /* indicator-messages */
{"libpower.so", NULL}, /* indicator-power */
{"libapplication.so", "bluetooth-manager"}, /* bluetooth manager */
{"libnetwork.so", NULL}, /* indicator-network */
{"libnetworkmenu.so", NULL}, /* indicator-network */
{"libapplication.so", "nm-applet"}, /* network manager */
{"libsoundmenu.so", NULL}, /* indicator-sound */
{"libdatetime.so", NULL}, /* indicator-datetime */
{"libsession.so", NULL}, /* indicator-session */
{NULL, NULL}
};
/* Forwards */
static void load_indicator (PanelService *self,
IndicatorObject *object,
const gchar *_name);
static void load_indicators (PanelService *self);
static void sort_indicators (PanelService *self);
static void notify_object (IndicatorObject *object);
static GdkFilterReturn event_filter (GdkXEvent *ev,
GdkEvent *gev,
PanelService *self);
/*
* GObject stuff
*/
static void
panel_service_class_dispose (GObject *object)
{
PanelServicePrivate *priv = PANEL_SERVICE (object)->priv;
gint i;
g_hash_table_destroy (priv->id2entry_hash);
g_hash_table_destroy (priv->panel2entries_hash);
gdk_window_remove_filter (NULL, (GdkFilterFunc)event_filter, object);
if (GTK_IS_WIDGET (priv->menubar) &&
gtk_widget_get_realized (GTK_WIDGET (priv->menubar)))
{
g_object_unref (priv->menubar);
priv->menubar = NULL;
}
if (GTK_IS_WIDGET (priv->offscreen_window) &&
gtk_widget_get_realized (GTK_WIDGET (priv->offscreen_window)))
{
g_object_unref (priv->offscreen_window);
priv->offscreen_window = NULL;
}
if (GTK_IS_WIDGET (priv->last_menu) &&
gtk_widget_get_realized (GTK_WIDGET (priv->last_menu)))
{
g_object_unref (priv->last_menu);
priv->last_menu = NULL;
}
if (priv->initial_sync_id)
{
g_source_remove (priv->initial_sync_id);
priv->initial_sync_id = 0;
}
for (i = 0; i < N_TIMEOUT_SLOTS; i++)
{
if (priv->timeouts[i] > 0)
{
g_source_remove (priv->timeouts[i]);
priv->timeouts[i] = 0;
}
}
if (priv->key_monitor_id)
{
gconf_client_notify_remove (gconf_client_get_default(), priv->key_monitor_id);
priv->key_monitor_id = 0;
}
G_OBJECT_CLASS (panel_service_parent_class)->dispose (object);
}
static void
panel_service_class_finalize (GObject *object)
{
static_service = NULL;
}
static void
panel_service_class_init (PanelServiceClass *klass)
{
GObjectClass *obj_class = G_OBJECT_CLASS (klass);
obj_class->dispose = panel_service_class_dispose;
obj_class->finalize = panel_service_class_finalize;
/* Signals */
_service_signals[ENTRY_ACTIVATED] =
g_signal_new ("entry-activated",
G_OBJECT_CLASS_TYPE (obj_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
panel_marshal_VOID__STRING_INT_INT_UINT_UINT,
G_TYPE_NONE, 5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT,
G_TYPE_UINT, G_TYPE_UINT);
_service_signals[RE_SYNC] =
g_signal_new ("re-sync",
G_OBJECT_CLASS_TYPE (obj_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1, G_TYPE_STRING);
_service_signals[ENTRY_ACTIVATE_REQUEST] =
g_signal_new ("entry-activate-request",
G_OBJECT_CLASS_TYPE (obj_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1, G_TYPE_STRING);
_service_signals[GEOMETRIES_CHANGED] =
g_signal_new ("geometries-changed",
G_OBJECT_CLASS_TYPE (obj_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
panel_marshal_VOID__OBJECT_POINTER_INT_INT_INT_INT,
G_TYPE_NONE, 6,
G_TYPE_OBJECT, G_TYPE_POINTER,
G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
_service_signals[ENTRY_SHOW_NOW_CHANGED] =
g_signal_new ("entry-show-now-changed",
G_OBJECT_CLASS_TYPE (obj_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
panel_marshal_VOID__STRING_BOOLEAN,
G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN);
_service_signals[INDICATORS_CLEARED] =
g_signal_new ("indicators-cleared",
G_OBJECT_CLASS_TYPE (obj_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
g_type_class_add_private (obj_class, sizeof (PanelServicePrivate));
}
static IndicatorObjectEntry *
get_entry_at (PanelService *self, gint x, gint y)
{
GHashTableIter panel_iter, entries_iter;
gpointer key, value, k, v;
g_hash_table_iter_init (&panel_iter, self->priv->panel2entries_hash);
while (g_hash_table_iter_next (&panel_iter, &key, &value))
{
GHashTable *entry2geometry_hash = value;
g_hash_table_iter_init (&entries_iter, entry2geometry_hash);
while (g_hash_table_iter_next (&entries_iter, &k, &v))
{
IndicatorObjectEntry *entry = k;
GdkRectangle *geo = v;
if (x >= geo->x && x <= (geo->x + geo->width) &&
y >= geo->y && y <= (geo->y + geo->height))
{
return entry;
}
}
}
return NULL;
}
static IndicatorObject *
get_entry_parent_indicator (IndicatorObjectEntry *entry)
{
if (!entry || !INDICATOR_IS_OBJECT (entry->parent_object))
return NULL;
return INDICATOR_OBJECT (entry->parent_object);
}
static gchar *
get_indicator_entry_id_by_entry (IndicatorObjectEntry *entry)
{
gchar *entry_id = NULL;
if (entry)
entry_id = g_strdup_printf ("%p", entry);
return entry_id;
}
static IndicatorObjectEntry *
get_indicator_entry_by_id (PanelService *self, const gchar *entry_id)
{
IndicatorObjectEntry *entry;
entry = g_hash_table_lookup (self->priv->id2entry_hash, entry_id);
if (entry)
{
if (g_slist_find (self->priv->indicators, entry->parent_object))
{
if (!INDICATOR_IS_OBJECT (entry->parent_object))
entry = NULL;
}
else
{
entry = NULL;
}
if (!entry)
{
g_warning("The entry id '%s' you're trying to lookup is not a valid IndicatorObjectEntry!", entry_id);
}
}
return entry;
}
static GdkFilterReturn
event_filter (GdkXEvent *ev, GdkEvent *gev, PanelService *self)
{
PanelServicePrivate *priv = self->priv;
XEvent *e = (XEvent *)ev;
GdkFilterReturn ret = GDK_FILTER_CONTINUE;
if (!PANEL_IS_SERVICE (self))
{
g_warning ("%s: Invalid PanelService instance", G_STRLOC);
return ret;
}
if (!GTK_IS_WIDGET (self->priv->last_menu))
return ret;
/* Use XI2 to read the event data */
XGenericEventCookie *cookie = &e->xcookie;
if (cookie->type == GenericEvent)
{
XIDeviceEvent *event = cookie->data;
if (!event)
return ret;
if (event->evtype == XI_KeyPress)
{
if (event->mods.base == priv->toggle_modifiers && event->detail == priv->toggle_key)
{
if (GTK_IS_MENU (priv->last_menu))
gtk_menu_popdown (GTK_MENU (priv->last_menu));
}
}
if (event->evtype == XI_ButtonPress)
{
priv->pressed_entry = get_entry_at (self, event->root_x, event->root_y);
priv->use_event = (priv->pressed_entry == NULL);
if (priv->pressed_entry)
ret = GDK_FILTER_REMOVE;
}
if (event->evtype == XI_ButtonRelease)
{
IndicatorObjectEntry *entry;
gboolean event_is_a_click = FALSE;
entry = get_entry_at (self, event->root_x, event->root_y);
if (event->detail == 1 || event->detail == 3)
{
/* Consider only right and left clicks over the indicators entries */
event_is_a_click = TRUE;
}
else if (entry && event->detail == 2)
{
/* Middle clicks over an appmenu entry are considered just like
* all other clicks */
IndicatorObject *obj = get_entry_parent_indicator (entry);
if (g_strcmp0 (g_object_get_data (G_OBJECT (obj), "id"), "libappmenu.so") == 0)
{
event_is_a_click = TRUE;
}
}
if (event_is_a_click)
{
if (priv->use_event)
{
priv->use_event = FALSE;
}
else
{
if (entry)
{
if (entry != priv->pressed_entry)
{
ret = GDK_FILTER_REMOVE;
priv->use_event = TRUE;
}
else if (priv->last_entry && entry != priv->last_entry)
{
/* If we were navigating over indicators using the keyboard
* and now we click over the indicator under the mouse, we
* must force it to show back again, not make it close */
gchar *entry_id = get_indicator_entry_id_by_entry (entry);
g_signal_emit (self, _service_signals[ENTRY_ACTIVATE_REQUEST], 0, entry_id);
g_free (entry_id);
}
}
}
}
else if (entry && (event->detail == 2 || event->detail == 4 || event->detail == 5))
{
/* If we're scrolling or middle-clicking over an indicator
* (which is not an appmenu entry) then we need to send the
* event to the indicator itself, and avoid it to close */
gchar *entry_id = get_indicator_entry_id_by_entry (entry);
if (event->detail == 4 || event->detail == 5)
{
gint32 delta = (event->detail == 4) ? 120 : -120;
panel_service_scroll_entry (self, entry_id, delta);
}
else if (entry == priv->pressed_entry)
{
panel_service_secondary_activate_entry (self, entry_id, time(NULL));
}
ret = GDK_FILTER_REMOVE;
g_free (entry_id);
}
}
}
return ret;
}
static gboolean
initial_resync (PanelService *self)
{
if (PANEL_IS_SERVICE (self))
{
g_signal_emit (self, _service_signals[RE_SYNC], 0, "");
self->priv->initial_sync_id = 0;
}
return FALSE;
}
static void
panel_service_update_menu_keybinding (PanelService *self)
{
GConfClient *client = gconf_client_get_default ();
gchar *binding = gconf_client_get_string (client, MENU_TOGGLE_KEYBINDING_PATH, NULL);
KeyCode keycode = 0;
KeySym keysym = NoSymbol;
guint32 modifiers = 0;
gchar *keystart = (binding) ? strrchr (binding, '>') : NULL;
if (!keystart)
keystart = binding;
while (keystart && !g_ascii_isalnum (*keystart))
keystart++;
gchar *keyend = keystart;
while (keyend && g_ascii_isalnum (*keyend))
keyend++;
if (keystart != keyend)
{
gchar *keystr = g_strndup (keystart, keyend-keystart);
keysym = XStringToKeysym (keystr);
g_free (keystr);
}
if (keysym != NoSymbol)
{
Display *dpy = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
keycode = XKeysymToKeycode(dpy, keysym);
if (g_strrstr (binding, ""))
{
modifiers |= GDK_SHIFT_MASK;
}
if (g_strrstr (binding, ""))
{
modifiers |= GDK_CONTROL_MASK;
}
if (g_strrstr (binding, "") || g_strrstr (binding, ""))
{
modifiers |= GDK_MOD1_MASK;
}
if (g_strrstr (binding, ""))
{
modifiers |= GDK_SUPER_MASK;
}
}
self->priv->toggle_key = keycode;
self->priv->toggle_modifiers = modifiers;
g_free (binding);
}
void
on_keybinding_changed (GConfClient* client, guint id, GConfEntry* entry, gpointer data)
{
PanelService *self = data;
g_return_if_fail (PANEL_IS_SERVICE (data));
panel_service_update_menu_keybinding (self);
}
static void
panel_service_init (PanelService *self)
{
PanelServicePrivate *priv;
priv = self->priv = GET_PRIVATE (self);
priv->offscreen_window = gtk_offscreen_window_new ();
priv->menubar = gtk_menu_bar_new ();
gtk_container_add (GTK_CONTAINER (priv->offscreen_window), priv->menubar);
gdk_window_add_filter (NULL, (GdkFilterFunc)event_filter, self);
priv->id2entry_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
priv->panel2entries_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify) g_hash_table_destroy);
suppress_signals = TRUE;
load_indicators (self);
sort_indicators (self);
suppress_signals = FALSE;
panel_service_update_menu_keybinding (self);
GConfClient *client = gconf_client_get_default ();
gconf_client_add_dir (client, COMPIZ_OPTIONS_PATH, GCONF_CLIENT_PRELOAD_NONE, NULL);
priv->key_monitor_id = gconf_client_notify_add (client, MENU_TOGGLE_KEYBINDING_PATH,
on_keybinding_changed, self,
NULL, NULL);
priv->initial_sync_id = g_idle_add ((GSourceFunc)initial_resync, self);
}
static gboolean
panel_service_check_cleared (PanelService *self)
{
if (self->priv->indicators == NULL)
{
g_signal_emit (self, _service_signals[INDICATORS_CLEARED], 0);
return FALSE;
}
return TRUE;
}
static void
panel_service_actually_remove_indicator (PanelService *self, IndicatorObject *indicator)
{
g_return_if_fail (PANEL_IS_SERVICE (self));
g_return_if_fail (INDICATOR_IS_OBJECT (indicator));
GList *entries, *l;
entries = indicator_object_get_entries (indicator);
if (entries)
{
for (l = entries; l; l = l->next)
{
IndicatorObjectEntry *entry;
gchar *entry_id;
GHashTableIter iter;
gpointer key, value;
entry = l->data;
entry_id = get_indicator_entry_id_by_entry (entry);
g_hash_table_remove (self->priv->id2entry_hash, entry_id);
g_free (entry_id);
g_hash_table_iter_init (&iter, self->priv->panel2entries_hash);
while (g_hash_table_iter_next (&iter, &key, &value))
{
GHashTable *entry2geometry_hash = value;
if (g_hash_table_size (entry2geometry_hash) > 1)
g_hash_table_remove (entry2geometry_hash, entry);
else
g_hash_table_iter_remove (&iter);
}
}
g_list_free (entries);
}
self->priv->indicators = g_slist_remove (self->priv->indicators, indicator);
if (g_object_is_floating (G_OBJECT (indicator)))
{
g_object_ref_sink (G_OBJECT (indicator));
}
g_object_unref (G_OBJECT (indicator));
}
static gboolean
panel_service_indicator_remove_timeout (IndicatorObject *indicator)
{
PanelService *self = panel_service_get_default ();
panel_service_actually_remove_indicator (self, indicator);
return FALSE;
}
PanelService *
panel_service_get_default ()
{
if (static_service == NULL || !PANEL_IS_SERVICE (static_service))
static_service = g_object_new (PANEL_TYPE_SERVICE, NULL);
return static_service;
}
PanelService *
panel_service_get_default_with_indicators (GList *indicators)
{
PanelService *service = panel_service_get_default ();
GList *i;
for (i = indicators; i; i = i->next)
{
IndicatorObject *object = i->data;
if (INDICATOR_IS_OBJECT (object))
load_indicator (service, object, NULL);
}
return service;
}
guint
panel_service_get_n_indicators (PanelService *self)
{
g_return_val_if_fail (PANEL_IS_SERVICE (self), 0);
return g_slist_length (self->priv->indicators);
}
IndicatorObject *
panel_service_get_indicator_nth (PanelService *self, guint position)
{
g_return_val_if_fail (PANEL_IS_SERVICE (self), NULL);
return (IndicatorObject *) g_slist_nth_data (self->priv->indicators, position);
}
IndicatorObject *
panel_service_get_indicator (PanelService *self, const gchar *indicator_id)
{
g_return_val_if_fail (PANEL_IS_SERVICE (self), NULL);
GSList *l;
for (l = self->priv->indicators; l; l = l->next)
{
if (g_strcmp0 (indicator_id,
g_object_get_data (G_OBJECT (l->data), "id")) == 0)
{
return (IndicatorObject *) l->data;
}
}
return NULL;
}
void
panel_service_remove_indicator (PanelService *self, IndicatorObject *indicator)
{
g_return_if_fail (PANEL_IS_SERVICE (self));
g_return_if_fail (INDICATOR_IS_OBJECT (indicator));
gpointer timeout = g_object_get_data (G_OBJECT (indicator), "remove-timeout");
if (timeout)
g_source_remove (GPOINTER_TO_UINT (timeout));
g_object_set_data (G_OBJECT (indicator), "remove", GINT_TO_POINTER (TRUE));
notify_object (indicator);
guint id = g_timeout_add_seconds (1,
(GSourceFunc) panel_service_indicator_remove_timeout,
indicator);
g_object_set_data (G_OBJECT (indicator), "remove-timeout", GUINT_TO_POINTER (id));
}
void
panel_service_clear_indicators (PanelService *self)
{
g_return_if_fail (PANEL_IS_SERVICE (self));
GSList *l = self->priv->indicators;
while (l)
{
IndicatorObject *ind = l->data;
l = l->next;
panel_service_remove_indicator (self, ind);
}
g_idle_add ((GSourceFunc)panel_service_check_cleared, self);
}
/*
* Private Methods
*/
static gboolean
actually_notify_object (IndicatorObject *object)
{
PanelService *self;
PanelServicePrivate *priv;
gint position;
if (!PANEL_IS_SERVICE (static_service))
return FALSE;
if (!INDICATOR_IS_OBJECT (object))
return FALSE;
self = panel_service_get_default ();
priv = self->priv;
position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "position"));
priv->timeouts[position] = SYNC_WAITING;
if (!suppress_signals)
g_signal_emit (self, _service_signals[RE_SYNC],
0, g_object_get_data (G_OBJECT (object), "id"));
return FALSE;
}
static void
notify_object (IndicatorObject *object)
{
PanelService *self;
PanelServicePrivate *priv;
gint position;
if (suppress_signals)
return;
self = panel_service_get_default ();
priv = self->priv;
position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "position"));
if (priv->timeouts[position] == SYNC_WAITING)
{
/* No need to ping again as we're waiting for the client to sync anyway */
return;
}
else if (priv->timeouts[position] != SYNC_NEUTRAL)
{
/* We were going to signal that a sync was needed, but since there's been another change let's
* hold off a little longer so we're not flooding the client
*/
g_source_remove (priv->timeouts[position]);
}
priv->timeouts[position] = g_timeout_add (NOTIFY_TIMEOUT,
(GSourceFunc)actually_notify_object,
object);
}
static void
on_entry_property_changed (GObject *o,
GParamSpec *pspec,
IndicatorObject *object)
{
notify_object (object);
}
static void
on_entry_changed (GObject *o,
IndicatorObject *object)
{
notify_object (object);
}
static void
on_entry_added (IndicatorObject *object,
IndicatorObjectEntry *entry,
PanelService *self)
{
g_return_if_fail (PANEL_IS_SERVICE (self));
g_return_if_fail (entry != NULL);
gchar *entry_id = get_indicator_entry_id_by_entry (entry);
g_hash_table_insert (self->priv->id2entry_hash, entry_id, entry);
if (GTK_IS_LABEL (entry->label))
{
g_signal_connect (entry->label, "notify::label",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->label, "notify::sensitive",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->label, "show",
G_CALLBACK (on_entry_changed), object);
g_signal_connect (entry->label, "hide",
G_CALLBACK (on_entry_changed), object);
}
if (GTK_IS_IMAGE (entry->image))
{
g_signal_connect (entry->image, "notify::storage-type",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "notify::file",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "notify::gicon",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "notify::icon-name",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "notify::pixbuf",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "notify::stock",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "notify::sensitive",
G_CALLBACK (on_entry_property_changed), object);
g_signal_connect (entry->image, "show",
G_CALLBACK (on_entry_changed), object);
g_signal_connect (entry->image, "hide",
G_CALLBACK (on_entry_changed), object);
}
notify_object (object);
}
static void
on_entry_removed (IndicatorObject *object,
IndicatorObjectEntry *entry,
PanelService *self)
{
g_return_if_fail (PANEL_IS_SERVICE (self));
g_return_if_fail (entry != NULL);
/* Don't remove here the value from panel2entries_hash, this should be
* done during the geometries sync, to avoid false positive.
* FIXME this in libappmenu.so to avoid to send an "entry-removed" signal
* when switching the focus from a window to one of its dialog children */
gchar *entry_id = get_indicator_entry_id_by_entry (entry);
g_hash_table_remove (self->priv->id2entry_hash, entry_id);
g_free (entry_id);
notify_object (object);
}
static void
on_entry_moved (IndicatorObject *object,
IndicatorObjectEntry *entry,
PanelService *self)
{
notify_object (object);
}
static void
on_indicator_menu_show (IndicatorObject *object,
IndicatorObjectEntry *entry,
guint32 timestamp,
PanelService *self)
{
gchar *entry_id;
g_return_if_fail (PANEL_IS_SERVICE (self));
if (entry == NULL)
{
g_warning ("on_indicator_menu_show() called with a NULL entry");
return;
}
entry_id = get_indicator_entry_id_by_entry (entry);
g_signal_emit (self, _service_signals[ENTRY_ACTIVATE_REQUEST], 0, entry_id);
g_free (entry_id);
}
static void
on_indicator_menu_show_now_changed (IndicatorObject *object,
IndicatorObjectEntry *entry,
gboolean show_now_changed,
PanelService *self)
{
gchar *entry_id;
g_return_if_fail (PANEL_IS_SERVICE (self));
if (entry == NULL)
{
g_warning ("on_indicator_menu_show_now_changed() called with a NULL entry");
return;
}
entry_id = get_indicator_entry_id_by_entry (entry);
g_signal_emit (self, _service_signals[ENTRY_SHOW_NOW_CHANGED], 0, entry_id, show_now_changed);
g_free (entry_id);
}
static const gchar * indicator_environment[] = {
"unity",
"unity-3d",
"unity-panel-service",
NULL
};
static void
load_indicator (PanelService *self, IndicatorObject *object, const gchar *_name)
{
PanelServicePrivate *priv = self->priv;
gchar *name;
GList *entries, *entry;
indicator_object_set_environment(object, (GStrv)indicator_environment);
if (_name != NULL)
name = g_strdup (_name);
else
name = g_strdup_printf ("%p", object);
priv->indicators = g_slist_append (priv->indicators, object);
g_object_set_data_full (G_OBJECT (object), "id", g_strdup (name), g_free);
g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_ENTRY_ADDED,
G_CALLBACK (on_entry_added), self);
g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_ENTRY_REMOVED,
G_CALLBACK (on_entry_removed), self);
g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_ENTRY_MOVED,
G_CALLBACK (on_entry_moved), self);
g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_MENU_SHOW,
G_CALLBACK (on_indicator_menu_show), self);
g_signal_connect (object, INDICATOR_OBJECT_SIGNAL_SHOW_NOW_CHANGED,
G_CALLBACK (on_indicator_menu_show_now_changed), self);
entries = indicator_object_get_entries (object);
for (entry = entries; entry != NULL; entry = entry->next)
{
on_entry_added (object, entry->data, self);
}
g_list_free (entries);
g_free (name);
}
static void
load_indicators (PanelService *self)
{
GDir *dir;
const gchar *name;
if (!g_file_test (INDICATORDIR,
G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
{
g_warning ("%s does not exist, cannot read any indicators", INDICATORDIR);
gtk_main_quit ();
}
dir = g_dir_open (INDICATORDIR, 0, NULL);
while ((name = g_dir_read_name (dir)) != NULL)
{
IndicatorObject *object;
gchar *path;
if (!g_str_has_suffix (name, ".so"))
continue;
path = g_build_filename (INDICATORDIR, name, NULL);
g_debug ("Loading: %s", path);
object = indicator_object_new_from_file (path);
if (object == NULL)
{
g_warning ("Unable to load '%s'", path);
g_free (path);
continue;
}
load_indicator (self, object, name);
g_free (path);
}
g_dir_close (dir);
}
static gint
name2order (const gchar * name, const gchar * hint)
{
int i;
for (i = 0; indicator_order[i][0] != NULL; i++)
{
if (g_strcmp0(name, indicator_order[i][0]) == 0 &&
g_strcmp0(hint, indicator_order[i][1]) == 0)
{
return i;
}
}
return -1;
}
static gint
name2priority (const gchar * name, const gchar * hint)
{
gint order = name2order (name, hint);
if (order > -1)
return order * MAX_INDICATOR_ENTRIES;
return order;
}
static int
indicator_compare_func (IndicatorObject *o1, IndicatorObject *o2)
{
gchar *s1;
gchar *s2;
int i1;
int i2;
s1 = g_object_get_data (G_OBJECT (o1), "id");
s2 = g_object_get_data (G_OBJECT (o2), "id");
i1 = name2order (s1, NULL);
i2 = name2order (s2, NULL);
return i1 - i2;
}
static void
sort_indicators (PanelService *self)
{
GSList *i;
int k = 0;
int prio = 0;
self->priv->indicators = g_slist_sort (self->priv->indicators,
(GCompareFunc)indicator_compare_func);
for (i = self->priv->indicators; i; i = i->next)
{
prio = name2priority(g_object_get_data (G_OBJECT (i->data), "id"), NULL);
if (prio < 0) continue;
g_object_set_data (G_OBJECT (i->data), "priority", GINT_TO_POINTER (prio));
g_object_set_data (G_OBJECT (i->data), "position", GINT_TO_POINTER (k));
self->priv->timeouts[k] = SYNC_NEUTRAL;
k++;
}
}
static gchar *
gtk_image_to_data (GtkImage *image)
{
GtkImageType type = gtk_image_get_storage_type (image);
gchar *ret = NULL;
if (type == GTK_IMAGE_PIXBUF)
{
GdkPixbuf *pixbuf;
gchar *buffer = NULL;
gsize buffer_size = 0;
GError *error = NULL;
pixbuf = gtk_image_get_pixbuf (image);
if (gdk_pixbuf_save_to_buffer (pixbuf, &buffer, &buffer_size, "png", &error, NULL))
{
ret = g_base64_encode ((const guchar *)buffer, buffer_size);
g_free (buffer);
}
else
{
g_warning ("Unable to convert pixbuf to png data: '%s'", error ? error->message : "unknown");
if (error)
g_error_free (error);
ret = g_strdup ("");
}
}
else if (type == GTK_IMAGE_STOCK)
{
g_object_get (G_OBJECT (image), "stock", &ret, NULL);
}
else if (type == GTK_IMAGE_ICON_NAME)
{
g_object_get (G_OBJECT (image), "icon-name", &ret, NULL);
}
else if (type == GTK_IMAGE_GICON)
{
GIcon *icon = NULL;
gtk_image_get_gicon (image, &icon, NULL);
if (G_IS_ICON (icon))
{
ret = g_icon_to_string (icon);
}
}
else
{
ret = g_strdup ("");
g_warning ("Unable to support GtkImageType: %d", type);
}
return ret;
}
static void
indicator_entry_to_variant (IndicatorObjectEntry *entry,
const gchar *id,
const gchar *indicator_id,
GVariantBuilder *b,
gint prio)
{
gboolean is_label = GTK_IS_LABEL (entry->label);
gboolean is_image = GTK_IS_IMAGE (entry->image);
gchar *image_data = NULL;
g_variant_builder_add (b, "(ssssbbusbbi)",
indicator_id,
id,
entry->name_hint ? entry->name_hint : "",
is_label ? gtk_label_get_label (entry->label) : "",
is_label ? gtk_widget_get_sensitive (GTK_WIDGET (entry->label)) : FALSE,
is_label ? gtk_widget_get_visible (GTK_WIDGET (entry->label)) : FALSE,
is_image ? (guint32)gtk_image_get_storage_type (entry->image) : (guint32) 0,
is_image ? (image_data = gtk_image_to_data (entry->image)) : "",
is_image ? gtk_widget_get_sensitive (GTK_WIDGET (entry->image)) : FALSE,
is_image ? gtk_widget_get_visible (GTK_WIDGET (entry->image)) : FALSE,
prio);
g_free (image_data);
}
static void
indicator_entry_null_to_variant (const gchar *indicator_id,
GVariantBuilder *b)
{
g_variant_builder_add (b, "(ssssbbusbbi)",
indicator_id,
"",
"",
"",
FALSE,
FALSE,
(guint32) 0,
"",
FALSE,
FALSE,
-1);
}
static void
indicator_object_to_variant (IndicatorObject *object, const gchar *indicator_id, GVariantBuilder *b)
{
GList *entries, *e;
gint parent_prio = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "priority"));
entries = indicator_object_get_entries (object);
gint index = 0;
if (entries)
{
for (e = entries; e; e = e->next)
{
gint prio = -1;
IndicatorObjectEntry *entry = e->data;
gchar *id = get_indicator_entry_id_by_entry (entry);
if (entry->name_hint)
{
prio = name2priority(indicator_id, entry->name_hint);
}
if (prio == -1)
{
prio = parent_prio + index;
index++;
}
indicator_entry_to_variant (entry, id, indicator_id, b, prio);
g_free (id);
}
g_list_free (entries);
}
else
{
/* Add a null entry to indicate that there is an indicator here, it's just empty */
indicator_entry_null_to_variant (indicator_id, b);
}
}
static void
positon_menu (GtkMenu *menu,
gint *x,
gint *y,
gboolean *push,
gpointer user_data)
{
PanelService *self = PANEL_SERVICE (user_data);
PanelServicePrivate *priv = self->priv;
*x = priv->last_x;
*y = priv->last_y;
*push = TRUE;
}
static void
on_active_menu_hidden (GtkMenu *menu, PanelService *self)
{
PanelServicePrivate *priv = self->priv;
priv->last_x = 0;
priv->last_y = 0;
priv->last_menu_button = 0;
g_signal_handler_disconnect (priv->last_menu, priv->last_menu_id);
g_signal_handler_disconnect (priv->last_menu, priv->last_menu_move_id);
GtkWidget *top_win = gtk_widget_get_toplevel (GTK_WIDGET (priv->last_menu));
if (GTK_IS_WINDOW (top_win))
gtk_window_set_attached_to (GTK_WINDOW (top_win), NULL);
priv->last_menu = NULL;
priv->last_menu_id = 0;
priv->last_menu_move_id = 0;
priv->last_entry = NULL;
priv->last_left = 0;
priv->last_right = 0;
priv->last_top = 0;
priv->last_bottom = 0;
priv->use_event = FALSE;
priv->pressed_entry = NULL;
g_signal_emit (self, _service_signals[ENTRY_ACTIVATED], 0, "", 0, 0, 0, 0);
}
/*
* Public Methods
*/
GVariant *
panel_service_sync (PanelService *self)
{
GVariantBuilder b;
GSList *i;
g_variant_builder_init (&b, G_VARIANT_TYPE ("(a(ssssbbusbbi))"));
g_variant_builder_open (&b, G_VARIANT_TYPE ("a(ssssbbusbbi)"));
for (i = self->priv->indicators; i; i = i->next)
{
const gchar *indicator_id = g_object_get_data (G_OBJECT (i->data), "id");
gint position;
/* Set the sync back to neutral */
position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (i->data), "position"));
self->priv->timeouts[position] = SYNC_NEUTRAL;
indicator_object_to_variant (i->data, indicator_id, &b);
}
g_variant_builder_close (&b);
return g_variant_builder_end (&b);
}
GVariant *
panel_service_sync_one (PanelService *self, const gchar *indicator_id)
{
GVariantBuilder b;
GSList *i;
g_variant_builder_init (&b, G_VARIANT_TYPE ("(a(ssssbbusbbi))"));
g_variant_builder_open (&b, G_VARIANT_TYPE ("a(ssssbbusbbi)"));
for (i = self->priv->indicators; i; i = i->next)
{
if (g_strcmp0 (indicator_id,
g_object_get_data (G_OBJECT (i->data), "id")) == 0)
{
gint position;
/* Set the sync back to neutral */
position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (i->data), "position"));
self->priv->timeouts[position] = SYNC_NEUTRAL;
if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (i->data), "remove")) != TRUE)
{
indicator_object_to_variant (i->data, indicator_id, &b);
}
else
{
indicator_entry_null_to_variant (indicator_id, &b);
panel_service_actually_remove_indicator (self, i->data);
}
break;
}
}
g_variant_builder_close (&b);
return g_variant_builder_end (&b);
}
void
panel_service_sync_geometry (PanelService *self,
const gchar *panel_id,
const gchar *entry_id,
gint x,
gint y,
gint width,
gint height)
{
IndicatorObject *object;
IndicatorObjectEntry *entry;
gboolean valid_entry = TRUE;
PanelServicePrivate *priv = self->priv;
entry = get_indicator_entry_by_id (self, entry_id);
/* If the entry we read is not valid, maybe it has already been removed
* or unparented, so we need to make sure that the related key on the
* entry2geometry_hash is correctly removed and the value is free'd */
if (!entry)
{
IndicatorObjectEntry *invalid_entry;
if (sscanf (entry_id, "%p", &invalid_entry) == 1)
{
entry = invalid_entry;
valid_entry = FALSE;
}
}
if (entry)
{
GHashTable *entry2geometry_hash = g_hash_table_lookup (priv->panel2entries_hash, panel_id);
if (width < 0 || height < 0 || !valid_entry)
{
if (entry2geometry_hash)
{
if (g_hash_table_size (entry2geometry_hash) > 1)
{
g_hash_table_remove (entry2geometry_hash, entry);
}
else
{
g_hash_table_remove (priv->panel2entries_hash, panel_id);
}
}
/* If the entry has been removed let's make sure that its menu is closed */
if (valid_entry && GTK_IS_MENU (priv->last_menu) && priv->last_menu == entry->menu)
{
gtk_menu_popdown (entry->menu);
}
}
else
{
GdkRectangle *geo = NULL;
if (entry2geometry_hash == NULL)
{
entry2geometry_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, g_free);
g_hash_table_insert (priv->panel2entries_hash, g_strdup (panel_id),
entry2geometry_hash);
}
else
{
geo = g_hash_table_lookup (entry2geometry_hash, entry);
}
if (geo == NULL)
{
geo = g_new0 (GdkRectangle, 1);
g_hash_table_insert (entry2geometry_hash, entry, geo);
}
/* If the current entry geometry has changed, we need to move the menu
* accordingly to the change we recorded! */
if (GTK_IS_MENU (priv->last_menu) && priv->last_menu == entry->menu)
{
GtkWidget *top_widget = gtk_widget_get_toplevel (GTK_WIDGET (priv->last_menu));
if (GTK_IS_WINDOW (top_widget))
{
GtkWindow *top_win = GTK_WINDOW (top_widget);
gint old_x, old_y;
gtk_window_get_position (top_win, &old_x, &old_y);
gtk_window_move (top_win, old_x - (geo->x - x), old_y - (geo->y - y));
}
}
geo->x = x;
geo->y = y;
geo->width = width;
geo->height = height;
}
if (valid_entry)
{
object = get_entry_parent_indicator (entry);
g_signal_emit (self, _service_signals[GEOMETRIES_CHANGED], 0, object, entry, x, y, width, height);
}
}
}
static gboolean
panel_service_entry_is_visible (PanelService *self, IndicatorObjectEntry *entry)
{
GHashTableIter panel_iter;
gpointer key, value;
gboolean found_geo;
g_return_val_if_fail (PANEL_IS_SERVICE (self), FALSE);
g_return_val_if_fail (entry != NULL, FALSE);
found_geo = FALSE;
g_hash_table_iter_init (&panel_iter, self->priv->panel2entries_hash);
while (g_hash_table_iter_next (&panel_iter, &key, &value) && !found_geo)
{
GHashTable *entry2geometry_hash = value;
if (g_hash_table_lookup (entry2geometry_hash, entry))
{
found_geo = TRUE;
}
}
if (!found_geo)
return FALSE;
if (GTK_IS_LABEL (entry->label))
{
if (gtk_widget_get_visible (GTK_WIDGET (entry->label)) &&
gtk_widget_is_sensitive (GTK_WIDGET (entry->label)))
{
return TRUE;
}
}
if (GTK_IS_IMAGE (entry->image))
{
if (gtk_widget_get_visible (GTK_WIDGET (entry->image)) &&
gtk_widget_is_sensitive (GTK_WIDGET (entry->image)))
{
return TRUE;
}
}
return TRUE;
}
static int
indicator_entry_compare_func (gpointer* v1, gpointer* v2)
{
return (GPOINTER_TO_INT (v1[1]) > GPOINTER_TO_INT (v2[1])) ? 1 : -1;
}
static void
activate_next_prev_menu (PanelService *self,
IndicatorObject *object,
IndicatorObjectEntry *entry,
GtkMenuDirectionType direction)
{
IndicatorObjectEntry *new_entry;
PanelServicePrivate *priv = self->priv;
GSList *indicators = priv->indicators;
GList *ordered_entries = NULL;
GList *entries;
gchar *id;
GSList *l;
GList *ll;
for (l = indicators; l; l = l->next)
{
const gchar *indicator_id = g_object_get_data (G_OBJECT (l->data), "id");
gint parent_priority = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (l->data), "priority"));
entries = indicator_object_get_entries (l->data);
if (entries)
{
int index = 0;
for (ll = entries; ll; ll = ll->next)
{
gint prio = -1;
new_entry = ll->data;
if (!panel_service_entry_is_visible (self, new_entry))
continue;
if (new_entry->name_hint)
{
prio = name2priority(indicator_id, new_entry->name_hint);
}
if (prio == -1)
{
prio = parent_priority + index;
index++;
}
gpointer *values = g_new (gpointer, 2);
values[0] = new_entry;
values[1] = GINT_TO_POINTER (prio);
ordered_entries = g_list_insert_sorted (ordered_entries, values,
(GCompareFunc) indicator_entry_compare_func);
}
g_list_free (entries);
}
}
new_entry = NULL;
for (ll = ordered_entries; ll; ll = ll->next)
{
gpointer *values = ll->data;
if (entry == values[0])
{
if (direction == GTK_MENU_DIR_CHILD)
{
values = ll->next ? ll->next->data : ordered_entries->data;
}
else
{
values = ll->prev ? ll->prev->data : g_list_last(ordered_entries)->data;
}
new_entry = values[0];
break;
}
}
if (new_entry)
{
id = get_indicator_entry_id_by_entry (new_entry);
g_signal_emit (self, _service_signals[ENTRY_ACTIVATE_REQUEST], 0, id);
g_free (id);
}
g_list_free_full (ordered_entries, g_free);
}
static void
on_active_menu_move_current (GtkMenu *menu,
GtkMenuDirectionType direction,
PanelService *self)
{
PanelServicePrivate *priv;
IndicatorObject *object;
g_return_if_fail (PANEL_IS_SERVICE (self));
priv = self->priv;
/* Not interested in up or down */
if (direction == GTK_MENU_DIR_NEXT || direction == GTK_MENU_DIR_PREV)
return;
/* We don't want to distrupt going into submenus */
if (direction == GTK_MENU_DIR_CHILD)
{
GList *children, *c;
children = gtk_container_get_children (GTK_CONTAINER (menu));
for (c = children; c; c = c->next)
{
GtkWidget *item = (GtkWidget *)c->data;
if (GTK_IS_MENU_ITEM (item)
&& gtk_widget_get_state (item) == GTK_STATE_PRELIGHT
&& gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)))
{
/* Skip direction due to there being a submenu,
* and we don't want to inhibit going into that */
return;
}
}
g_list_free (children);
}
/* Find the next/prev indicator */
object = get_entry_parent_indicator(priv->last_entry);
if (object == NULL)
{
g_warning ("Unable to find IndicatorObject for entry");
return;
}
activate_next_prev_menu (self, object, priv->last_entry, direction);
}
static void
menu_deactivated (GtkWidget *menu)
{
g_signal_handlers_disconnect_by_func (menu, menu_deactivated, NULL);
gtk_widget_destroy (menu);
}
static void
panel_service_show_entry_common (PanelService *self,
IndicatorObject *object,
IndicatorObjectEntry *entry,
guint32 xid,
gint32 x,
gint32 y,
guint32 button,
guint32 timestamp)
{
PanelServicePrivate *priv;
GtkWidget *last_menu;
g_return_if_fail (PANEL_IS_SERVICE (self));
g_return_if_fail (INDICATOR_IS_OBJECT (object));
g_return_if_fail (entry);
priv = self->priv;
if (priv->last_entry == entry)
return;
last_menu = GTK_WIDGET (priv->last_menu);
if (GTK_IS_MENU (priv->last_menu))
{
priv->last_x = 0;
priv->last_y = 0;
g_signal_handler_disconnect (priv->last_menu, priv->last_menu_id);
g_signal_handler_disconnect (priv->last_menu, priv->last_menu_move_id);
priv->last_entry = NULL;
priv->last_menu = NULL;
priv->last_menu_id = 0;
priv->last_menu_move_id = 0;
priv->last_menu_button = 0;
}
if (entry != NULL)
{
if (xid > 0)
{
indicator_object_entry_activate_window (object, entry, xid, CurrentTime);
}
else
{
indicator_object_entry_activate (object, entry, CurrentTime);
}
if (GTK_IS_MENU (entry->menu))
{
priv->last_menu = entry->menu;
}
else
{
/* For some reason, this entry doesn't have a menu. To simplify the
rest of the code and to keep scrubbing fluidly, we'll create a
stub menu for the duration of this scrub. */
priv->last_menu = GTK_MENU (gtk_menu_new ());
g_signal_connect (priv->last_menu, "deactivate",
G_CALLBACK (menu_deactivated), NULL);
g_signal_connect (priv->last_menu, "destroy",
G_CALLBACK (gtk_widget_destroyed), &priv->last_menu);
}
GtkWidget *top_widget = gtk_widget_get_toplevel (GTK_WIDGET (priv->last_menu));
if (GTK_IS_WINDOW (top_widget))
{
GtkWindow *top_win = GTK_WINDOW (top_widget);
if (gtk_window_get_attached_to (top_win) != priv->menubar)
gtk_window_set_attached_to (top_win, priv->menubar);
}
priv->last_entry = entry;
priv->last_x = x;
priv->last_y = y;
priv->last_menu_button = button;
priv->last_menu_id = g_signal_connect (priv->last_menu, "hide",
G_CALLBACK (on_active_menu_hidden), self);
priv->last_menu_move_id = g_signal_connect_after (priv->last_menu, "move-current",
G_CALLBACK (on_active_menu_move_current), self);
gtk_menu_popup (priv->last_menu, NULL, NULL, positon_menu, self, 0, CurrentTime);
GdkWindow *gdkwin = gtk_widget_get_window (GTK_WIDGET (priv->last_menu));
if (gdkwin != NULL)
{
gint left=0, top=0, width=0, height=0;
gdk_window_get_geometry (gdkwin, NULL, NULL, &width, &height);
gdk_window_get_origin (gdkwin, &left, &top);
gchar *entry_id = get_indicator_entry_id_by_entry (entry);
g_signal_emit (self, _service_signals[ENTRY_ACTIVATED], 0, entry_id,
left, top, width, height);
g_free (entry_id);
priv->last_left = left;
priv->last_right = left + width -1;
priv->last_top = top;
priv->last_bottom = top + height -1;
}
else
{
priv->last_left = 0;
priv->last_right = 0;
priv->last_top = 0;
priv->last_bottom = 0;
}
}
/* We popdown the old one last so we don't accidently send key focus back to the
* active application (which will make it change colour (as state changes), which
* then looks like flickering to the user.
*/
if (GTK_IS_MENU (last_menu))
gtk_menu_popdown (GTK_MENU (last_menu));
}
void
panel_service_show_entry (PanelService *self,
const gchar *entry_id,
guint32 xid,
gint32 x,
gint32 y,
guint32 button,
guint32 timestamp)
{
IndicatorObject *object;
IndicatorObjectEntry *entry;
g_return_if_fail (PANEL_IS_SERVICE (self));
entry = get_indicator_entry_by_id (self, entry_id);
object = get_entry_parent_indicator (entry);
panel_service_show_entry_common (self, object, entry, xid, x, y, button, timestamp);
}
void
panel_service_show_app_menu (PanelService *self,
guint32 xid,
gint32 x,
gint32 y,
guint32 timestamp)
{
IndicatorObject *object;
IndicatorObjectEntry *entry;
GList *entries;
g_return_if_fail (PANEL_IS_SERVICE (self));
object = panel_service_get_indicator (self, "libappmenu.so");
g_return_if_fail (INDICATOR_IS_OBJECT (object));
entries = indicator_object_get_entries (object);
if (entries)
{
entry = entries->data;
g_list_free (entries);
panel_service_show_entry_common (self, object, entry, xid, x, y, 1, timestamp);
}
}
void
panel_service_secondary_activate_entry (PanelService *self,
const gchar *entry_id,
guint32 timestamp)
{
IndicatorObject *object;
IndicatorObjectEntry *entry;
entry = get_indicator_entry_by_id (self, entry_id);
g_return_if_fail (entry);
object = get_entry_parent_indicator (entry);
g_signal_emit_by_name(object, INDICATOR_OBJECT_SIGNAL_SECONDARY_ACTIVATE, entry,
timestamp);
}
void
panel_service_scroll_entry (PanelService *self,
const gchar *entry_id,
gint32 delta)
{
IndicatorObject *object;
IndicatorObjectEntry *entry;
entry = get_indicator_entry_by_id (self, entry_id);
g_return_if_fail (entry);
GdkScrollDirection direction = delta < 0 ? GDK_SCROLL_DOWN : GDK_SCROLL_UP;
object = get_entry_parent_indicator (entry);
g_signal_emit_by_name(object, INDICATOR_OBJECT_SIGNAL_ENTRY_SCROLLED, entry,
abs(delta/120), direction);
}