// -*- 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) */ #include "config.h" #include "panel-service.h" #include "panel-service-private.h" #include #include #include #include #include #include #include #include #include #include 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 500 #define NUX_VERTICAL_SCROLL_DELTA 120 #define NUX_HORIZONTAL_SCROLL_DELTA (NUX_VERTICAL_SCROLL_DELTA ^ 2) #define COMPIZ_OPTION_SCHEMA "org.compiz.unityshell" #define COMPIZ_OPTION_PATH "/org/compiz/profiles/unity/plugins/" #define MENU_TOGGLE_KEYBINDING_KEY "panel-first-menu" #define SHOW_DASH_KEY "show-launcher" #define SHOW_HUD_KEY "show-hud" static PanelService *static_service = NULL; static gboolean lockscreen_mode = FALSE; struct _PanelServicePrivate { GSList *indicators; GSList *dropdown_entries; GSList *removed_entries; GHashTable *id2entry_hash; GHashTable *panel2entries_hash; IndicatorObject *appmenu_indicator; guint timeouts[N_TIMEOUT_SLOTS]; guint remove_idle; IndicatorObjectEntry *last_entry; IndicatorObjectEntry *last_dropdown_entry; const gchar *last_panel; GtkMenu *last_menu; gint32 last_x; gint32 last_y; gint last_left; gint last_top; gint last_right; gint last_bottom; guint32 last_menu_button; GSettings *gsettings; KeyBinding menu_toggle; KeyBinding show_dash; KeyBinding show_hud; 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 = G_MAXUINT, SYNC_NEUTRAL = 0, }; static guint32 _service_signals[LAST_SIGNAL] = { 0 }; static const gchar * indicator_order[][2] = { {APPMENU_INDICATOR_NAME, NULL}, /* indicator-appmenu" */ {"libapplication.so", NULL}, /* indicator-application" */ {"floating-indicators", NULL}, /* position-less NG indicators */ {"libprintersmenu.so", NULL}, /* indicator-printers */ {"libapplication.so", "gsd-keyboard-xkb"}, /* keyboard layout selector */ {"libmessaging.so", NULL}, /* indicator-messages */ {"libpower.so", NULL}, /* indicator-power */ {"libbluetooth.so", NULL}, /* indicator-bluetooth */ {"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 *, IndicatorObject *, const gchar *); static void load_indicators (PanelService *); static void load_indicators_from_indicator_files (PanelService *); static void sort_indicators (PanelService *); static void notify_object (IndicatorObject *object); static void update_keybinding (GSettings *, const gchar *, gpointer); static void emit_upstart_event (const gchar *); static gchar * get_indicator_entry_id_by_entry (IndicatorObjectEntry *entry); static IndicatorObjectEntry * get_indicator_entry_by_id (PanelService *self, const gchar *entry_id); static GdkFilterReturn event_filter (GdkXEvent *, GdkEvent *, PanelService *); /* * GObject stuff */ static void panel_service_class_dispose (GObject *self) { PanelServicePrivate *priv = PANEL_SERVICE (self)->priv; gint i; g_idle_remove_by_data (self); gdk_window_remove_filter (NULL, (GdkFilterFunc)event_filter, self); if (!lockscreen_mode) emit_upstart_event ("indicator-services-end"); if (GTK_IS_WIDGET (priv->last_menu) && gtk_widget_get_realized (GTK_WIDGET (priv->last_menu))) { gtk_menu_popdown (GTK_MENU (priv->last_menu)); g_signal_handlers_disconnect_by_data (priv->last_menu, self); priv->last_menu = NULL; } for (i = 0; i < N_TIMEOUT_SLOTS; i++) { if (priv->timeouts[i] > 0 && priv->timeouts[i] != SYNC_WAITING) { g_source_remove (priv->timeouts[i]); priv->timeouts[i] = 0; } } if (G_IS_OBJECT (priv->gsettings)) { g_signal_handlers_disconnect_matched (priv->gsettings, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, update_keybinding, NULL); g_object_unref (priv->gsettings); priv->gsettings = NULL; } if (priv->dropdown_entries) { g_slist_free_full (priv->dropdown_entries, g_free); priv->dropdown_entries = NULL; } G_OBJECT_CLASS (panel_service_parent_class)->dispose (self); } static void panel_service_class_finalize (GObject *object) { PanelServicePrivate *priv = PANEL_SERVICE (object)->priv; g_hash_table_destroy (priv->id2entry_hash); g_hash_table_destroy (priv->panel2entries_hash); static_service = NULL; G_OBJECT_CLASS (panel_service_parent_class)->finalize (object); } 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, NULL, G_TYPE_NONE, 6, G_TYPE_STRING, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, G_TYPE_NONE, 0); g_type_class_add_private (obj_class, sizeof (PanelServicePrivate)); } static gboolean rect_contains_point (GdkRectangle* rect, gint x, gint y) { if (!rect) return FALSE; return (x >= rect->x && x <= (rect->x + rect->width) && y >= rect->y && y <= (rect->y + rect->height)); } 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 (rect_contains_point (geo, x, y)) { return entry; } } } return NULL; } static IndicatorObjectEntry * get_entry_at_panel (PanelService *self, const gchar *panel, gint x, gint y) { GHashTable *entry2geometry_hash; GHashTableIter entries_iter; gpointer key, value; if (!panel) return NULL; entry2geometry_hash = g_hash_table_lookup (self->priv->panel2entries_hash, panel); if (!entry2geometry_hash) return NULL; g_hash_table_iter_init (&entries_iter, entry2geometry_hash); while (g_hash_table_iter_next (&entries_iter, &key, &value)) { IndicatorObjectEntry *entry = key; GdkRectangle *geo = value; if (rect_contains_point (geo, x, y)) { return entry; } } return NULL; } static const gchar* get_panel_for_parent_at (PanelService *self, guint parent, 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)) { const gchar *panel_id = key; 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; /* The entry might be invalid at this point (as it could have been * removed, but still not synced), so it's better to double check */ if (g_slist_find (self->priv->removed_entries, entry)) continue; if (!parent || entry->parent_window == parent) { if (rect_contains_point (geo, x, y)) { return panel_id; } } } } 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 (g_slist_find (static_service->priv->dropdown_entries, entry)) { return g_strdup (entry->name_hint); } 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); } } if (!entry && g_str_has_suffix (entry_id, "-dropdown")) { /* Unity might register some "fake" dropdown entries that it might use to * to present long menu bars (right now only for appmenu indicator) */ entry = g_new0 (IndicatorObjectEntry, 1); entry->parent_object = self->priv->appmenu_indicator; entry->name_hint = g_strdup (entry_id); self->priv->dropdown_entries = g_slist_append (self->priv->dropdown_entries, entry); g_hash_table_insert (self->priv->id2entry_hash, (gpointer)entry->name_hint, entry); } return entry; } static void ensure_entry_menu_is_closed (PanelService *self, const gchar *panel_id, IndicatorObjectEntry *entry) { PanelServicePrivate *priv = self->priv; /* If the entry has been removed let's make sure that its menu is closed */ if (GTK_IS_MENU (priv->last_menu) && priv->last_menu == entry->menu) { if (!priv->last_panel || !panel_id || g_strcmp0 (priv->last_panel, panel_id) == 0) gtk_menu_popdown (entry->menu); } } static void reinject_key_event_to_root_window (XIDeviceEvent *ev) { XKeyEvent kev; kev.display = ev->display; kev.window = ev->root; kev.root = ev->root; kev.subwindow = None; kev.time = ev->time; kev.x = ev->event_x; kev.y = ev->event_x; kev.x_root = ev->root_x; kev.y_root = ev->root_y; kev.same_screen = True; kev.keycode = ev->detail; kev.state = ev->mods.base; kev.type = ev->evtype; XSendEvent (kev.display, kev.root, True, KeyPressMask, (XEvent*) &kev); XFlush (kev.display); } static gboolean event_matches_keybinding (guint32 modifiers, KeySym key, KeyBinding *kb) { if (modifiers == kb->modifiers && key != NoSymbol) { if (key == kb->key || key == kb->fallback) { return TRUE; } } return FALSE; } static gboolean is_special_keysym (KeySym keysym) { /* Multimedia keys, see X11/XF86keysym.h */ if (keysym >= 0x1008FF00 && keysym <= 0x1008FFFF) return TRUE; return FALSE; } static gboolean is_control_keysym (KeySym keysym) { if (!is_special_keysym (keysym)) return FALSE; /* Display backlight controls */ if (keysym >= 0x1008FF01 && keysym <= 0x1008FF0F) return TRUE; switch (keysym) { case XF86XK_Battery: case XF86XK_Bluetooth: case XF86XK_WLAN: case XF86XK_UWB: return !lockscreen_mode; case XF86XK_Suspend: case XF86XK_Hibernate: case XF86XK_Sleep: case XF86XK_PowerOff: case XF86XK_ScreenSaver: return lockscreen_mode; } const gchar *keystr = XKeysymToString (keysym); if (g_str_has_prefix (keystr, "XF86Audio") || g_str_has_prefix (keystr, "XF86Touchpad")) { return TRUE; } return FALSE; } static gboolean is_allowed_keysym (KeySym keysym) { if (keysym == XK_Print) return TRUE; if (is_special_keysym (keysym)) return TRUE; return FALSE; } 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) return ret; XIDeviceEvent *event = cookie->data; if (!event) return ret; switch (event->evtype) { case XI_KeyPress: { KeySym keysym = XkbKeycodeToKeysym (event->display, event->detail, 0, 0); if (lockscreen_mode) { if (is_control_keysym (keysym)) { reinject_key_event_to_root_window (event); ret = GDK_FILTER_REMOVE; } break; } if (event_matches_keybinding (event->mods.base, keysym, &priv->menu_toggle) || event_matches_keybinding (event->mods.base, keysym, &priv->show_dash) || event_matches_keybinding (event->mods.base, keysym, &priv->show_hud)) { if (GTK_IS_MENU (priv->last_menu)) gtk_menu_popdown (GTK_MENU (priv->last_menu)); ret = GDK_FILTER_REMOVE; } else if (event->mods.base != GDK_CONTROL_MASK) { if (!IsModifierKey (keysym) && (event->mods.base != 0 || is_allowed_keysym (keysym))) { if (GTK_IS_MENU (priv->last_menu) && !is_control_keysym (keysym)) gtk_menu_popdown (GTK_MENU (priv->last_menu)); reinject_key_event_to_root_window (event); ret = GDK_FILTER_REMOVE; } } break; } case XI_ButtonPress: { priv->pressed_entry = get_entry_at_panel (self, priv->last_panel, event->root_x, event->root_y); priv->use_event = (priv->pressed_entry == NULL); if (priv->pressed_entry) ret = GDK_FILTER_REMOVE; break; } case XI_ButtonRelease: { IndicatorObjectEntry *entry = NULL; gboolean event_is_a_click = FALSE; entry = get_entry_at_panel (self, priv->last_panel, 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 */ if (get_entry_parent_indicator (entry) == priv->appmenu_indicator) { 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 <= 7)) { /* 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 <= 7) { gint32 delta = (event->detail >= 6) ? NUX_HORIZONTAL_SCROLL_DELTA : NUX_VERTICAL_SCROLL_DELTA; delta = (event->detail % 2 == 0) ? delta : delta * -1; panel_service_scroll_entry (self, entry_id, delta); } else if (event->detail == 2 && entry == priv->pressed_entry) { panel_service_secondary_activate_entry (self, entry_id); } ret = GDK_FILTER_REMOVE; g_free (entry_id); } break; } } return ret; } static gboolean initial_resync (PanelService *self) { g_signal_emit (self, _service_signals[RE_SYNC], 0, ""); return G_SOURCE_REMOVE; } static gboolean ready_signal (PanelService *self) { if (!lockscreen_mode) emit_upstart_event ("indicator-services-start"); return G_SOURCE_REMOVE; } static void update_keybinding (GSettings *settings, const gchar *key, gpointer data) { KeyBinding *kb = data; gchar *binding = g_settings_get_string (settings, key); parse_string_keybinding (binding, kb); g_free (binding); } void parse_string_keybinding (const char *str, KeyBinding *kb) { kb->key = NoSymbol; kb->fallback = NoSymbol; kb->modifiers = 0; gchar *binding = g_strdup (str); gchar *keystart = (binding) ? strrchr (binding, '>') : NULL; if (!keystart) keystart = binding; while (keystart && *keystart != '\0' && !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); kb->key = XStringToKeysym (keystr); g_free (keystr); } else { /* Parsing the case where we only have meta-keys */ keyend = (binding) ? strrchr (binding, '>') : NULL; keyend = keyend ? keyend - 1 : NULL; keystart = keyend; while (keystart && keystart > binding && g_ascii_isalnum (*keystart)) keystart--; if (keystart != keyend) { gchar *keystr = g_strndup (keystart+1, keyend-keystart); gchar *left = g_strconcat (keystr, "_L", NULL); kb->key = XStringToKeysym (left); gchar *right = g_strconcat (keystr, "_R", NULL); kb->fallback = XStringToKeysym (right); g_free (left); g_free (right); g_free (keystr); keystr = g_strndup (binding, keystart-binding); g_free (binding); binding = keystr; } } if (kb->key != NoSymbol) { if (g_strrstr (binding, "")) { kb->modifiers |= ShiftMask; } if (g_strrstr (binding, "") || g_strrstr (binding, "")) { kb->modifiers |= ControlMask; } if (g_strrstr (binding, "") || g_strrstr (binding, "")) { kb->modifiers |= AltMask; } if (g_strrstr (binding, "")) { kb->modifiers |= SuperMask; } } g_free (binding); } static void emit_upstart_event (const gchar *event) { const gchar *upstartsession = g_getenv ("UPSTART_SESSION"); if (!upstartsession) return; GError *error = NULL; GDBusConnection* conn = g_dbus_connection_new_for_address_sync (upstartsession, G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, NULL, NULL, &error); if (error) { g_warning ("Unable to connect to Upstart session: %s", error->message); g_error_free (error); return; } GVariant *result = g_dbus_connection_call_sync (conn, DBUS_SERVICE_UPSTART, DBUS_PATH_UPSTART, DBUS_INTERFACE_UPSTART, "EmitEvent", g_variant_new ("(sasb)", event, NULL, 0), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &error); if (error) { g_warning ("Unable to emit Upstart event: %s", error->message); g_error_free (error); } else { g_variant_unref (result); } g_object_unref (conn); } static void panel_service_init (PanelService *self) { PanelServicePrivate *priv; priv = self->priv = GET_PRIVATE (self); 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); priv->gsettings = g_settings_new_with_path (COMPIZ_OPTION_SCHEMA, COMPIZ_OPTION_PATH); g_signal_connect (priv->gsettings, "changed::"MENU_TOGGLE_KEYBINDING_KEY, G_CALLBACK (update_keybinding), &priv->menu_toggle); g_signal_connect (priv->gsettings, "changed::"SHOW_DASH_KEY, G_CALLBACK (update_keybinding), &priv->show_dash); g_signal_connect (priv->gsettings, "changed::"SHOW_HUD_KEY, G_CALLBACK (update_keybinding), &priv->show_hud); update_keybinding (priv->gsettings, MENU_TOGGLE_KEYBINDING_KEY, &priv->menu_toggle); update_keybinding (priv->gsettings, SHOW_DASH_KEY, &priv->show_dash); update_keybinding (priv->gsettings, SHOW_HUD_KEY, &priv->show_hud); } static gboolean panel_service_check_cleared (PanelService *self) { if (self->priv->indicators == NULL) { g_signal_emit (self, _service_signals[INDICATORS_CLEARED], 0); return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } 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; gpointer timeout; gint position; g_signal_handlers_disconnect_by_data (indicator, self); position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (indicator), "position")); if (self->priv->timeouts[position] > 0) { g_source_remove (self->priv->timeouts[position]); self->priv->timeouts[position] = SYNC_NEUTRAL; } 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-timeout", GUINT_TO_POINTER (0)); } 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; if (entry->label) { g_signal_handlers_disconnect_by_data (entry->label, indicator); } if (entry->image) { g_signal_handlers_disconnect_by_data (entry->image, indicator); } 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)); } if (self->priv->appmenu_indicator == indicator) self->priv->appmenu_indicator = NULL; 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; } static PanelService * get_or_init_static_service (gboolean* is_new) { if (is_new) *is_new = FALSE; if (!PANEL_IS_SERVICE (static_service)) { static_service = g_object_new (PANEL_TYPE_SERVICE, NULL); if (is_new) *is_new = TRUE; } return static_service; } static void initial_load_default_or_custom_indicators (PanelService *self, GList *indicators) { GList *l; suppress_signals = TRUE; if (!indicators) { if (!lockscreen_mode) { load_indicators (self); } load_indicators_from_indicator_files (self); sort_indicators (self); } else { for (l = indicators; l; l = l->next) { IndicatorObject *object = l->data; if (INDICATOR_IS_OBJECT (object)) panel_service_add_indicator (self, object); } } suppress_signals = FALSE; g_idle_add ((GSourceFunc)initial_resync, self); g_idle_add ((GSourceFunc)ready_signal, self); } PanelService * panel_service_get_default () { gboolean is_new; PanelService *self = get_or_init_static_service (&is_new); if (is_new) { initial_load_default_or_custom_indicators (self, NULL); } return self; } PanelService * panel_service_get_default_with_indicators (GList *indicators) { gboolean is_new; PanelService *self = get_or_init_static_service (&is_new); if (is_new && indicators) { initial_load_default_or_custom_indicators (self, indicators); } return self; } void panel_service_set_lockscreen_mode (gboolean enable) { lockscreen_mode = enable; } 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_add_indicator (PanelService *self, IndicatorObject *indicator) { g_return_if_fail (PANEL_IS_SERVICE (self)); g_return_if_fail (INDICATOR_IS_OBJECT (indicator)); g_object_ref (indicator); load_indicator (self, indicator, NULL); } static void panel_service_prepare_indicator_removal (PanelService *self, IndicatorObject *indicator, gboolean notify) { g_object_set_data (G_OBJECT (indicator), "remove", GINT_TO_POINTER (TRUE)); gpointer timeout = g_object_get_data (G_OBJECT (indicator), "remove-timeout"); if (timeout) { g_source_remove (GPOINTER_TO_UINT (timeout)); } if (notify) { 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_remove_indicator (PanelService *self, IndicatorObject *indicator) { g_return_if_fail (PANEL_IS_SERVICE (self)); g_return_if_fail (INDICATOR_IS_OBJECT (indicator)); panel_service_prepare_indicator_removal (self, indicator, TRUE); } 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_prepare_indicator_removal (self, ind, FALSE); } g_signal_emit (self, _service_signals[RE_SYNC], 0, ""); 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 G_SOURCE_REMOVE; } 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); self->priv->removed_entries = g_slist_remove (self->priv->removed_entries, entry); 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 gboolean on_removed_idle (PanelService *self) { GHashTableIter iter; GHashTable *entry2geometry_hash; IndicatorObjectEntry *entry; gpointer value; GSList *l; for (l = self->priv->removed_entries; l; l = l->next) { entry = l->data; ensure_entry_menu_is_closed (self, NULL, entry); g_hash_table_iter_init (&iter, self->priv->panel2entries_hash); while (g_hash_table_iter_next (&iter, NULL, &value)) { if ((entry2geometry_hash = value)) { g_hash_table_remove (entry2geometry_hash, entry); if (g_hash_table_size (entry2geometry_hash) == 0) { g_hash_table_iter_remove (&iter); } } } } g_slist_free (self->priv->removed_entries); self->priv->removed_entries = NULL; return G_SOURCE_REMOVE; } 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); gchar *entry_id = get_indicator_entry_id_by_entry (entry); g_hash_table_remove (self->priv->id2entry_hash, entry_id); g_free (entry_id); if (entry->label) { g_signal_handlers_disconnect_by_data (entry->label, object); } if (entry->image) { g_signal_handlers_disconnect_by_data (entry->image, object); } self->priv->removed_entries = g_slist_append (self->priv->removed_entries, entry); if (self->priv->remove_idle) g_source_remove (self->priv->remove_idle); self->priv->remove_idle = g_idle_add ((GSourceFunc) on_removed_idle, self); 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) { if (GTK_IS_MENU (self->priv->last_menu)) gtk_menu_popdown (GTK_MENU (self->priv->last_menu)); 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) { g_warning ("%s called with a NULL entry", G_STRFUNC); 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", "unity-all-menus", 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); if (!priv->appmenu_indicator && g_strcmp0 (name, APPMENU_INDICATOR_NAME) == 0) priv->appmenu_indicator = 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 void load_indicators_from_indicator_files (PanelService *self) { GDir *dir; const gchar *name; GError *error = NULL; dir = g_dir_open (INDICATOR_SERVICE_DIR, 0, &error); if (!dir) { g_warning ("unable to open indicator service file directory: %s", error->message); g_error_free (error); return; } while ((name = g_dir_read_name (dir))) { gchar *filename; IndicatorNg *indicator; filename = g_build_filename (INDICATOR_SERVICE_DIR, name, NULL); indicator = indicator_ng_new_for_profile (filename, !lockscreen_mode ? "desktop" : "desktop_lockscreen", &error); if (indicator) { load_indicator (self, INDICATOR_OBJECT (indicator), name); } else { g_warning ("unable to load '%s': %s", name, error->message); g_clear_error (&error); } g_free (filename); } 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 void sort_indicators (PanelService *self) { GSList *i; int k = 0; int floating_indicators = 0; int max_priority = G_N_ELEMENTS(indicator_order) * MAX_INDICATOR_ENTRIES; for (i = self->priv->indicators; i; i = i->next) { IndicatorObject *io = i->data; gint pos; pos = indicator_object_get_position (io); /* Continue using the state ordering as long as there are still * plugins statically defined in this file. Give them a much * higher position though, so that they appear to the right of the * indicators that return a proper position */ if (pos < 0) { gint prio = name2priority (g_object_get_data (G_OBJECT (io), "id"), NULL); if (prio < 0) { prio = name2priority ("floating-indicators", NULL) + floating_indicators; floating_indicators++; } pos = max_priority - prio; } /* unity's concept of priorities is inverse to ours right now */ g_object_set_data (G_OBJECT (i->data), "priority", GINT_TO_POINTER (max_priority - pos)); 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, guint32 *storage_type) { if (!GTK_IS_IMAGE (image)) return NULL; *storage_type = gtk_image_get_storage_type (image); gchar *ret = NULL; switch (*storage_type) { case GTK_IMAGE_PIXBUF: { gchar *file; g_object_get (image, "file", &file, NULL); if (file) { if (file[0] != '\0') { *storage_type = GTK_IMAGE_ICON_NAME; ret = file; break; } g_free (file); } 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); } break; } case GTK_IMAGE_STOCK: { g_object_get (G_OBJECT (image), "stock", &ret, NULL); break; } case GTK_IMAGE_ICON_NAME: { g_object_get (G_OBJECT (image), "icon-name", &ret, NULL); break; } case GTK_IMAGE_GICON: { GIcon *icon = NULL; gtk_image_get_gicon (image, &icon, NULL); if (G_IS_ICON (icon)) ret = g_icon_to_string (icon); break; } case GTK_IMAGE_EMPTY: { break; } default: { g_warning ("Unable to support GtkImageType: %u", *storage_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); guint32 image_type = 0; gchar *image_data = gtk_image_to_data (entry->image, &image_type); g_variant_builder_add (b, ENTRY_SIGNATURE, indicator_id, id, entry->name_hint ? entry->name_hint : "", entry->parent_window, 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 ? image_type : 0, image_data ? image_data : "", 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, ENTRY_SIGNATURE, indicator_id, "", "", 0, "", FALSE, FALSE, 0, "", FALSE, FALSE, -1); } static void indicator_object_full_to_variant (PanelService *self, IndicatorObject *object, const gchar *indicator_id, GVariantBuilder *b) { GList *entries, *e; GHashTable *index_hash = NULL; gint parent_prio = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "priority")); entries = indicator_object_get_entries (object); guint index = 0; if (entries) { if (object == self->priv->appmenu_indicator) index_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); 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 < 0) { if (index_hash) { index = GPOINTER_TO_UINT (g_hash_table_lookup (index_hash, GUINT_TO_POINTER (entry->parent_window))); } prio = parent_prio + index; index++; if (index_hash) { g_hash_table_insert (index_hash, GUINT_TO_POINTER (entry->parent_window), GUINT_TO_POINTER (index)); } } indicator_entry_to_variant (entry, id, indicator_id, b, prio); g_free (id); } if (index_hash) g_hash_table_destroy (index_hash); 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 indicator_object_to_variant (PanelService *self, IndicatorObject *object, const gchar *indicator_id, GVariantBuilder *b) { if (!GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "remove"))) { indicator_object_full_to_variant (self, object, indicator_id, b); } else { indicator_entry_null_to_variant (indicator_id, b); panel_service_actually_remove_indicator (self, object); } } static int get_monitor_at (gint x, gint y) { gint i; gdouble scale; GdkScreen *screen = gdk_screen_get_default (); gint monitors = gdk_screen_get_n_monitors (screen); for (i = 0; i < monitors; ++i) { GdkRectangle rect = { 0 }; gdk_screen_get_monitor_geometry (screen, i, &rect); scale = gdk_screen_get_monitor_scale_factor (screen, i); if (scale != 1.0) { rect.x *= scale; rect.y *= scale; rect.width *= scale; rect.height *= scale; } if (rect_contains_point (&rect, x, y)) { return i; } } return gdk_screen_get_monitor_at_point (screen, x, y); } static int get_monitor_scale_at (gint x, gint y) { gint monitor = get_monitor_at (x, y); return gdk_screen_get_monitor_scale_factor (gdk_screen_get_default (), monitor); } 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; GdkScreen *screen = gdk_screen_get_default (); gint monitor = get_monitor_at (priv->last_x, priv->last_y); gtk_menu_set_monitor (menu, monitor); gint scale = gdk_screen_get_monitor_scale_factor (screen, monitor); *x = priv->last_x / scale; *y = priv->last_y / scale; *push = TRUE; } static void on_active_menu_hidden (GtkMenu *menu, PanelService *self) { PanelServicePrivate *priv = self->priv; g_signal_handlers_disconnect_by_data (priv->last_menu, self); priv->last_x = 0; priv->last_y = 0; priv->last_menu_button = 0; priv->last_panel = NULL; priv->last_menu = NULL; 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; IndicatorObject *indicator; GSList *i; gint position; g_variant_builder_init (&b, G_VARIANT_TYPE ("("ENTRY_ARRAY_SIGNATURE")")); g_variant_builder_open (&b, G_VARIANT_TYPE (ENTRY_ARRAY_SIGNATURE)); for (i = self->priv->indicators; i;) { /* An indicator could be removed during this cycle, so we should be safe */ indicator = i->data; i = i->next; const gchar *indicator_id = g_object_get_data (G_OBJECT (indicator), "id"); position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (indicator), "position")); indicator_object_to_variant (self, indicator, indicator_id, &b); /* Set the sync back to neutral */ self->priv->timeouts[position] = SYNC_NEUTRAL; } 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 ("("ENTRY_ARRAY_SIGNATURE")")); g_variant_builder_open (&b, G_VARIANT_TYPE (ENTRY_ARRAY_SIGNATURE)); 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 = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (i->data), "position")); indicator_object_to_variant (self, i->data, indicator_id, &b); /* Set the sync back to neutral */ self->priv->timeouts[position] = SYNC_NEUTRAL; 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; GHashTable *entry2geometry_hash; 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) { entry2geometry_hash = g_hash_table_lookup (priv->panel2entries_hash, panel_id); if (width < 0 || height < 0 || !valid_entry) { if (valid_entry) ensure_entry_menu_is_closed (self, panel_id, entry); if (entry2geometry_hash) { g_hash_table_remove (entry2geometry_hash, entry); if (g_hash_table_size (entry2geometry_hash) == 0) { g_hash_table_remove (priv->panel2entries_hash, panel_id); } } } 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 && g_strcmp0 (priv->last_panel, panel_id) == 0) { 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; gint scale = get_monitor_scale_at (x, y); gtk_window_get_position (top_win, &old_x, &old_y); gtk_window_move (top_win, old_x - (geo->x - x) / scale, old_y - (geo->y - y) / scale); } } 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) { g_return_val_if_fail (PANEL_IS_SERVICE (self), FALSE); g_return_val_if_fail (entry != NULL, 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; } } if (g_slist_find (self->priv->dropdown_entries, entry)) return TRUE; return FALSE; } static gboolean panel_service_entry_is_visible_on_panel (PanelService *self, IndicatorObjectEntry *entry, const gchar *panel_id) { GHashTable *entry2geometry_hash; 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; if (panel_id) { entry2geometry_hash = g_hash_table_lookup (self->priv->panel2entries_hash, panel_id); if (entry2geometry_hash && g_hash_table_lookup (entry2geometry_hash, entry)) { found_geo = TRUE; } } else { g_hash_table_iter_init (&panel_iter, self->priv->panel2entries_hash); while (g_hash_table_iter_next (&panel_iter, &key, &value) && !found_geo) { entry2geometry_hash = value; if (g_hash_table_lookup (entry2geometry_hash, entry)) { found_geo = TRUE; } } } if (!found_geo) return FALSE; return panel_service_entry_is_visible (self, entry); } 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, 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, *sl; GList *ll; for (l = indicators; l; l = l->next) { IndicatorObject *object = l->data; gint parent_priority = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (object), "priority")); entries = indicator_object_get_entries (object); const gchar *indicator_id = g_object_get_data (G_OBJECT (object), "id"); if (entries) { int index = 0; if (!priv->last_dropdown_entry) { for (sl = priv->dropdown_entries; sl; sl = sl->next) { IndicatorObjectEntry *dropdown = sl->data; if (dropdown->parent_object == object) entries = g_list_append (entries, dropdown); } } for (ll = entries; ll; ll = ll->next) { gint prio = -1; new_entry = ll->data; if (!priv->last_dropdown_entry || new_entry != entry) { if (!panel_service_entry_is_visible_on_panel (self, new_entry, priv->last_panel)) continue; } if (new_entry->name_hint) { prio = name2priority (indicator_id, new_entry->name_hint); } if (prio < 0) { 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; IndicatorObjectEntry *entry; 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_flags (item) & GTK_STATE_FLAG_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 */ g_list_free (children); return; } } g_list_free (children); entry = (priv->last_dropdown_entry) ? priv->last_dropdown_entry : priv->last_entry; } else { entry = priv->last_entry; } /* Find the next/prev indicator */ activate_next_prev_menu (self, entry, direction); } static void menuitem_activated (GtkWidget *menuitem, IndicatorObjectEntry *entry) { IndicatorObject *object = get_entry_parent_indicator (entry); indicator_object_entry_activate (object, entry, CurrentTime); } static void panel_service_show_entry_common (PanelService *self, IndicatorObject *object, IndicatorObjectEntry *entry, GtkMenu *fake_menu, guint32 xid, gint32 x, gint32 y, guint32 button) { 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_handlers_disconnect_by_data (priv->last_menu, self); priv->last_panel = NULL; priv->last_entry = NULL; priv->last_menu = NULL; priv->last_menu_button = 0; } if (entry != NULL) { if (GTK_IS_MENU (entry->menu) || fake_menu) { if (xid > 0) { indicator_object_entry_activate_window (object, entry, xid, CurrentTime); } else { indicator_object_entry_activate (object, entry, CurrentTime); } priv->last_menu = (fake_menu) ? fake_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 ()); GtkWidget *menu_item = gtk_menu_item_new_with_label (_("Activate")); gtk_menu_shell_append (GTK_MENU_SHELL (priv->last_menu), menu_item); gtk_widget_show (menu_item); g_signal_connect (priv->last_menu, "deactivate", G_CALLBACK (gtk_widget_destroy), NULL); g_signal_connect (priv->last_menu, "destroy", G_CALLBACK (gtk_widget_destroyed), &priv->last_menu); g_signal_connect (menu_item, "activate", G_CALLBACK (menuitem_activated), entry); } priv->last_entry = entry; priv->last_x = x; priv->last_y = y; priv->last_menu_button = button; priv->last_panel = get_panel_for_parent_at (self, xid, x, y); g_signal_connect (priv->last_menu, "hide", G_CALLBACK (on_active_menu_hidden), self); g_signal_connect_after (priv->last_menu, "move-current", G_CALLBACK (on_active_menu_move_current), self); gtk_menu_shell_set_take_focus (GTK_MENU_SHELL (priv->last_menu), TRUE); gtk_menu_popup (priv->last_menu, NULL, NULL, positon_menu, self, button, CurrentTime); gboolean visible = gtk_widget_is_visible (GTK_WIDGET (priv->last_menu)); if (!visible) { /* If the menu is not visible at this point, it's very likely that's * due to a keyboard grab, so let's try with a menu with no key-grab */ gtk_menu_shell_set_take_focus (GTK_MENU_SHELL (priv->last_menu), FALSE); gtk_menu_popup (priv->last_menu, NULL, NULL, positon_menu, self, button, CurrentTime); visible = gtk_widget_is_visible (GTK_WIDGET (priv->last_menu)); } if (visible) { gtk_menu_reposition (priv->last_menu); GdkWindow *gdkwin = gtk_widget_get_window (GTK_WIDGET (priv->last_menu)); 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, priv->last_panel, 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 { on_active_menu_hidden (priv->last_menu, self); } } /* 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) { 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, NULL, xid, x, y, button); } static void on_drop_down_menu_hidden (GtkWidget *menu) { GList *l, *children; children = gtk_container_get_children (GTK_CONTAINER (menu)); for (l = children; l; l = l->next) { if (!GTK_IS_MENU_ITEM (l->data)) continue; GtkMenuItem *menu_item = GTK_MENU_ITEM (l->data); GtkWidget *entry_menu = gtk_menu_item_get_submenu (menu_item); GtkWidget *old_attached = g_object_get_data (G_OBJECT (entry_menu), "ups-attached-widget"); gtk_menu_item_set_submenu (menu_item, NULL); if (old_attached) gtk_menu_attach_to_widget (GTK_MENU (entry_menu), old_attached, NULL); } g_list_free (children); gtk_widget_destroy (menu); } void panel_service_show_entries (PanelService *self, gchar **entries, const gchar *selected, guint32 xid, gint32 x, gint32 y) { gint i; IndicatorObject *object; IndicatorObjectEntry *entry, *selected_entry, *first_entry, *last_entry; GtkWidget *menu; g_return_if_fail (PANEL_IS_SERVICE (self)); g_return_if_fail (entries && entries[0]); first_entry = get_indicator_entry_by_id (self, entries[0]); if (first_entry == self->priv->last_entry) return; if (!first_entry) { g_warning ("Impossible to show entries for an invalid entry or object"); return; } last_entry = NULL; menu = gtk_menu_new (); selected_entry = get_indicator_entry_by_id (self, selected); object = get_entry_parent_indicator (first_entry); for (i = 0; entries[i]; ++i) { entry = get_indicator_entry_by_id (self, entries[i]); if (entry != first_entry && !last_entry) continue; GtkWidget *menu_item; const char *label = NULL; if (GTK_IS_LABEL (entry->label)) label = gtk_label_get_label (entry->label); if (GTK_IS_IMAGE (entry->image)) { G_GNUC_BEGIN_IGNORE_DEPRECATIONS if (label) { menu_item = gtk_image_menu_item_new_with_mnemonic (label); } else { menu_item = gtk_image_menu_item_new (); } gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), GTK_WIDGET (entry->image)); gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menu_item), TRUE); G_GNUC_END_IGNORE_DEPRECATIONS } else if (label) { menu_item = gtk_menu_item_new_with_mnemonic (label); } else { continue; } GtkWidget *old_attached = gtk_menu_get_attach_widget (entry->menu); if (old_attached) { g_object_set_data (G_OBJECT (entry->menu), "ups-attached-widget", old_attached); gtk_menu_detach (entry->menu); } gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), GTK_WIDGET (entry->menu)); gtk_widget_show (menu_item); last_entry = entry; if (entry == selected_entry) gtk_menu_shell_select_item (GTK_MENU_SHELL (menu), menu_item); } if (!last_entry) { g_warning ("No valid entries to show"); gtk_widget_destroy (menu); return; } g_signal_connect_after (menu, "hide", G_CALLBACK (on_drop_down_menu_hidden), NULL); g_signal_connect (menu, "destroy", G_CALLBACK (gtk_widget_destroyed), &self->priv->last_dropdown_entry); panel_service_show_entry_common (self, object, first_entry, GTK_MENU (menu), xid, x, y, 1); self->priv->last_dropdown_entry = last_entry; } void panel_service_show_app_menu (PanelService *self, guint32 xid, gint32 x, gint32 y) { IndicatorObject *object; IndicatorObjectEntry *entry; GList *entries; g_return_if_fail (PANEL_IS_SERVICE (self)); g_return_if_fail (INDICATOR_IS_OBJECT (self->priv->appmenu_indicator)); object = self->priv->appmenu_indicator; entries = indicator_object_get_entries (object); if (entries) { entry = entries->data; g_list_free (entries); panel_service_show_entry_common (self, object, entry, NULL, xid, x, y, 1); } } void panel_service_secondary_activate_entry (PanelService *self, const gchar *entry_id) { IndicatorObject *object; IndicatorObjectEntry *entry; g_return_if_fail (PANEL_IS_SERVICE (self)); 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, CurrentTime); } void panel_service_scroll_entry (PanelService *self, const gchar *entry_id, gint32 delta) { IndicatorObject *object; IndicatorObjectEntry *entry; g_return_if_fail (PANEL_IS_SERVICE (self)); entry = get_indicator_entry_by_id (self, entry_id); g_return_if_fail (entry); GdkScrollDirection direction = G_MAXINT; switch (delta) { case NUX_VERTICAL_SCROLL_DELTA: direction = INDICATOR_OBJECT_SCROLL_UP; break; case -NUX_VERTICAL_SCROLL_DELTA: direction = INDICATOR_OBJECT_SCROLL_DOWN; break; case NUX_HORIZONTAL_SCROLL_DELTA: direction = INDICATOR_OBJECT_SCROLL_LEFT; break; case -NUX_HORIZONTAL_SCROLL_DELTA: direction = INDICATOR_OBJECT_SCROLL_RIGHT; break; } if (direction != G_MAXINT) { object = get_entry_parent_indicator (entry); g_signal_emit_by_name(object, INDICATOR_OBJECT_SIGNAL_ENTRY_SCROLLED, entry, 1, direction); } } void panel_service_close_active_entry (PanelService *self) { g_return_if_fail (PANEL_IS_SERVICE (self)); if (GTK_IS_MENU (self->priv->last_menu)) { gtk_menu_popdown (GTK_MENU (self->priv->last_menu)); } }