From 7d39b2e82f46777efa67224f078c1cec9e827654 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Thu, 19 Jan 2017 18:46:10 +0100 Subject: Add StatusNotifier support to the Notification Area applet The StatusNotifier part of the implementation is based off gnome-panel's status-notifier applet. --- .../notification_area/system-tray/na-tray-child.c | 715 +++++++++++++++++++++ 1 file changed, 715 insertions(+) create mode 100644 applets/notification_area/system-tray/na-tray-child.c (limited to 'applets/notification_area/system-tray/na-tray-child.c') diff --git a/applets/notification_area/system-tray/na-tray-child.c b/applets/notification_area/system-tray/na-tray-child.c new file mode 100644 index 00000000..2a87f1e1 --- /dev/null +++ b/applets/notification_area/system-tray/na-tray-child.c @@ -0,0 +1,715 @@ +/* na-tray-child.c + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2003-2006 Vincent Untz + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include "na-tray-child.h" + +#include +#include +#include +#include +#include + +#include "na-item.h" + +enum +{ + PROP_0, + PROP_ORIENTATION +}; + +static void na_item_init (NaItemInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (NaTrayChild, na_tray_child, GTK_TYPE_SOCKET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL) + G_IMPLEMENT_INTERFACE (NA_TYPE_ITEM, na_item_init)) + +static void +na_tray_child_finalize (GObject *object) +{ + NaTrayChild *child = NA_TRAY_CHILD (object); + + g_clear_pointer (&child->id, g_free); + + G_OBJECT_CLASS (na_tray_child_parent_class)->finalize (object); +} + +static void +na_tray_child_realize (GtkWidget *widget) +{ + NaTrayChild *child = NA_TRAY_CHILD (widget); + GdkVisual *visual = gtk_widget_get_visual (widget); + GdkWindow *window; + + GTK_WIDGET_CLASS (na_tray_child_parent_class)->realize (widget); + + window = gtk_widget_get_window (widget); + + if (child->has_alpha) + { + /* We have real transparency with an ARGB visual and the Composite + * extension. */ + + /* Set a transparent background */ + cairo_pattern_t *transparent = cairo_pattern_create_rgba (0, 0, 0, 0); + gdk_window_set_background_pattern (window, transparent); + gdk_window_set_composited (window, TRUE); + cairo_pattern_destroy (transparent); + + child->parent_relative_bg = FALSE; + } + else if (visual == gdk_window_get_visual(gdk_window_get_parent(window))) + { + /* Otherwise, if the visual matches the visual of the parent window, we + * can use a parent-relative background and fake transparency. */ + gdk_window_set_background_pattern (window, NULL); + + child->parent_relative_bg = TRUE; + } + else + { + /* Nothing to do; the icon will sit on top of an ugly gray box */ + child->parent_relative_bg = FALSE; + } + + gdk_window_set_composited (window, child->composited); + + gtk_widget_set_app_paintable (GTK_WIDGET (child), + child->parent_relative_bg || child->has_alpha); + + /* Double-buffering will interfere with the parent-relative-background fake + * transparency, since the double-buffer code doesn't know how to fill in the + * background of the double-buffer correctly. + */ + gtk_widget_set_double_buffered (GTK_WIDGET (child), + child->parent_relative_bg); +} + +static void +na_tray_child_style_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + /* The default handler resets the background according to the new style. + * We either use a transparent background or a parent-relative background + * and ignore the style background. So, just don't chain up. + */ +} + +static void +na_tray_child_get_preferred_width (GtkWidget *widget, + gint *minimal_width, + gint *natural_width) +{ + GTK_WIDGET_CLASS (na_tray_child_parent_class)->get_preferred_width (widget, + minimal_width, + natural_width); + + if (*minimal_width < 16) + *minimal_width = 16; + + if (*natural_width < 16) + *natural_width = 16; +} + +static void +na_tray_child_get_preferred_height (GtkWidget *widget, + gint *minimal_height, + gint *natural_height) +{ + GTK_WIDGET_CLASS (na_tray_child_parent_class)->get_preferred_height (widget, + minimal_height, + natural_height); + + if (*minimal_height < 16) + *minimal_height = 16; + + if (*natural_height < 16) + *natural_height = 16; +} + +static void +na_tray_child_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + NaTrayChild *child = NA_TRAY_CHILD (widget); + GtkAllocation widget_allocation; + gboolean moved, resized; + + gtk_widget_get_allocation (widget, &widget_allocation); + + moved = (allocation->x != widget_allocation.x || + allocation->y != widget_allocation.y); + resized = (allocation->width != widget_allocation.width || + allocation->height != widget_allocation.height); + + /* When we are allocating the widget while mapped we need special handling + * for both real and fake transparency. + * + * Real transparency: we need to invalidate and trigger a redraw of the old + * and new areas. (GDK really should handle this for us, but doesn't as of + * GTK+-2.14) + * + * Fake transparency: if the widget moved, we need to force the contents to + * be redrawn with the new offset for the parent-relative background. + */ + if ((moved || resized) && gtk_widget_get_mapped (widget)) + { + if (na_tray_child_has_alpha (child)) + gdk_window_invalidate_rect (gdk_window_get_parent (gtk_widget_get_window (widget)), + &widget_allocation, FALSE); + } + + GTK_WIDGET_CLASS (na_tray_child_parent_class)->size_allocate (widget, + allocation); + + if ((moved || resized) && gtk_widget_get_mapped (widget)) + { + if (na_tray_child_has_alpha (NA_TRAY_CHILD (widget))) + gdk_window_invalidate_rect (gdk_window_get_parent (gtk_widget_get_window (widget)), + &widget_allocation, FALSE); + else if (moved && child->parent_relative_bg) + na_tray_child_force_redraw (child); + } +} + +/* The plug window should completely occupy the area of the child, so we won't + * get an expose event. But in case we do (the plug unmaps itself, say), this + * expose handler draws with real or fake transparency. + */ +static gboolean +na_tray_child_draw (GtkWidget *widget, + cairo_t *cr) +{ + NaTrayChild *child = NA_TRAY_CHILD (widget); + + if (na_tray_child_has_alpha (child)) + { + /* Clear to transparent */ + cairo_set_source_rgba (cr, 0, 0, 0, 0); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr); + } + else if (child->parent_relative_bg) + { + /* Clear to parent-relative pixmap */ + GdkWindow *window; + cairo_surface_t *target; + GdkRectangle clip_rect; + + window = gtk_widget_get_window (widget); + target = cairo_get_group_target (cr); + + gdk_cairo_get_clip_rectangle (cr, &clip_rect); + + /* Clear to parent-relative pixmap + * We need to use direct X access here because GDK doesn't know about + * the parent relative pixmap. */ + cairo_surface_flush (target); + + XClearArea (GDK_WINDOW_XDISPLAY (window), + GDK_WINDOW_XID (window), + clip_rect.x, clip_rect.y, + clip_rect.width, clip_rect.height, + False); + cairo_surface_mark_dirty_rectangle (target, + clip_rect.x, clip_rect.y, + clip_rect.width, clip_rect.height); + } + + return FALSE; +} + +/* Children with alpha channels have been set to be composited by calling + * gdk_window_set_composited(). We need to paint these children ourselves. + * + * FIXME: is that still needed on GTK3? Seems like it could be done in draw(). + */ +static gboolean +na_tray_child_draw_on_parent (NaItem *item, + GtkWidget *parent, + cairo_t *parent_cr) +{ + if (na_tray_child_has_alpha (NA_TRAY_CHILD (item))) + { + GtkWidget *widget = GTK_WIDGET (item); + GtkAllocation parent_allocation = { 0 }; + GtkAllocation allocation; + + /* if the parent doesn't have a window, our allocation is not relative to + * the context coordinates but to the parent's allocation */ + if (! gtk_widget_get_has_window (parent)) + gtk_widget_get_allocation (parent, &parent_allocation); + + gtk_widget_get_allocation (widget, &allocation); + allocation.x -= parent_allocation.x; + allocation.y -= parent_allocation.y; + + cairo_save (parent_cr); + gdk_cairo_set_source_window (parent_cr, + gtk_widget_get_window (widget), + allocation.x, + allocation.y); + cairo_rectangle (parent_cr, allocation.x, allocation.y, allocation.width, allocation.height); + cairo_clip (parent_cr); + cairo_paint (parent_cr); + cairo_restore (parent_cr); + } + + return TRUE; +} + +static void +na_tray_child_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + case PROP_ORIENTATION: + /* whatever */ + g_value_set_enum (value, GTK_ORIENTATION_HORIZONTAL); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +na_tray_child_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + case PROP_ORIENTATION: + /* we so don't care */ + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static const gchar * +na_tray_child_get_id (NaItem *item) +{ + NaTrayChild *child = NA_TRAY_CHILD (item); + + if (! child->id) + { + char *res_name = NULL; + + na_tray_child_get_wm_class (child, &res_name, NULL); + child->id = g_strdup_printf ("%s-%lu", res_name, child->icon_window); + g_free (res_name); + } + + return child->id; +} + +static NaItemCategory +na_tray_child_get_category (NaItem *item) +{ + const struct + { + const gchar *wm_class; + NaItemCategory category; + } wmclass_categories[] = { +/* FIXME: get the same order as it used to be, without fake categories. + * from right to left: +const char *ordered_roles[] = { + "keyboard", + "volume", + "bluetooth", + "network", + "battery", + NULL +}; + +const char *wmclass_roles[] = { + "Bluetooth-applet", "bluetooth", + "Mate-volume-control-applet", "volume", + "Nm-applet", "network", + "Mate-power-manager", "battery", + "keyboard", "keyboard", + NULL, +}; +*/ + { "Bluetooth-applet", NA_ITEM_CATEGORY_FAKE_BLUETOOTH }, + { "Mate-volume-control-applet", NA_ITEM_CATEGORY_FAKE_VOLUME }, + { "Nm-applet", NA_ITEM_CATEGORY_FAKE_NETWORK }, + { "Mate-power-manager", NA_ITEM_CATEGORY_FAKE_BATTERY }, + { "keyboard", NA_ITEM_CATEGORY_FAKE_KEYBOARD } + }; + guint i; + NaItemCategory category = NA_ITEM_CATEGORY_APPLICATION_STATUS; + char *res_class = NULL; + + na_tray_child_get_wm_class (NA_TRAY_CHILD (item), NULL, &res_class); + + for (i = 0; i < G_N_ELEMENTS (wmclass_categories); i++) + { + if (g_strcmp0 (res_class, wmclass_categories[i].wm_class) == 0) + { + category = wmclass_categories[i].category; + break; + } + } + + return category; +} + +static void +na_item_init (NaItemInterface *iface) +{ + iface->get_id = na_tray_child_get_id; + iface->get_category = na_tray_child_get_category; + + iface->draw_on_parent = na_tray_child_draw_on_parent; +} + +static void +na_tray_child_init (NaTrayChild *child) +{ + child->id = NULL; +} + +static void +na_tray_child_class_init (NaTrayChildClass *klass) +{ + GObjectClass *gobject_class; + GtkWidgetClass *widget_class; + + gobject_class = (GObjectClass *)klass; + widget_class = (GtkWidgetClass *)klass; + + gobject_class->finalize = na_tray_child_finalize; + gobject_class->get_property = na_tray_child_get_property; + gobject_class->set_property = na_tray_child_set_property; + + widget_class->style_set = na_tray_child_style_set; + widget_class->realize = na_tray_child_realize; + widget_class->get_preferred_width = na_tray_child_get_preferred_width; + widget_class->get_preferred_height = na_tray_child_get_preferred_height; + widget_class->size_allocate = na_tray_child_size_allocate; + widget_class->draw = na_tray_child_draw; + + /* we don't really care actually */ + g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation"); +} + +GtkWidget * +na_tray_child_new (GdkScreen *screen, + Window icon_window) +{ + XWindowAttributes window_attributes; + Display *xdisplay; + NaTrayChild *child; + GdkVisual *visual; + gboolean visual_has_alpha; + int red_prec, green_prec, blue_prec, depth; + int result; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + g_return_val_if_fail (icon_window != None, NULL); + + xdisplay = GDK_SCREEN_XDISPLAY (screen); + + /* We need to determine the visual of the window we are embedding and create + * the socket in the same visual. + */ + + gdk_error_trap_push (); + result = XGetWindowAttributes (xdisplay, icon_window, + &window_attributes); + gdk_error_trap_pop_ignored (); + + if (!result) /* Window already gone */ + return NULL; + + visual = gdk_x11_screen_lookup_visual (screen, + window_attributes.visual->visualid); + if (!visual) /* Icon window is on another screen? */ + return NULL; + + child = g_object_new (NA_TYPE_TRAY_CHILD, NULL); + child->icon_window = icon_window; + + gtk_widget_set_visual (GTK_WIDGET (child), visual); + + /* We have alpha if the visual has something other than red, green, + * and blue */ + gdk_visual_get_red_pixel_details (visual, NULL, NULL, &red_prec); + gdk_visual_get_green_pixel_details (visual, NULL, NULL, &green_prec); + gdk_visual_get_blue_pixel_details (visual, NULL, NULL, &blue_prec); + depth = gdk_visual_get_depth (visual); + + visual_has_alpha = red_prec + blue_prec + green_prec < depth; + child->has_alpha = (visual_has_alpha && + gdk_display_supports_composite (gdk_screen_get_display (screen))); + + child->composited = child->has_alpha; + + return GTK_WIDGET (child); +} + +char * +na_tray_child_get_title (NaTrayChild *child) +{ + char *retval = NULL; + GdkDisplay *display; + Atom utf8_string, atom, type; + int result; + int format; + gulong nitems; + gulong bytes_after; + gchar *val; + + g_return_val_if_fail (NA_IS_TRAY_CHILD (child), NULL); + + display = gtk_widget_get_display (GTK_WIDGET (child)); + + utf8_string = gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"); + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_NAME"); + + gdk_error_trap_push (); + + result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), + child->icon_window, + atom, + 0, G_MAXLONG, + False, utf8_string, + &type, &format, &nitems, + &bytes_after, (guchar **)&val); + + if (gdk_error_trap_pop () || result != Success) + return NULL; + + if (type != utf8_string || + format != 8 || + nitems == 0) + { + if (val) + XFree (val); + return NULL; + } + + if (!g_utf8_validate (val, nitems, NULL)) + { + XFree (val); + return NULL; + } + + retval = g_strndup (val, nitems); + + XFree (val); + + return retval; +} + +/** + * na_tray_child_has_alpha; + * @child: a #NaTrayChild + * + * Checks if the child has an ARGB visual and real alpha transparence. + * (as opposed to faked alpha transparency with an parent-relative + * background) + * + * Return value: %TRUE if the child has an alpha transparency + */ +gboolean +na_tray_child_has_alpha (NaTrayChild *child) +{ + g_return_val_if_fail (NA_IS_TRAY_CHILD (child), FALSE); + + return child->has_alpha; +} + +/** + * na_tray_child_set_composited; + * @child: a #NaTrayChild + * @composited: %TRUE if the child's window should be redirected + * + * Sets whether the #GdkWindow of the child should be set redirected + * using gdk_window_set_composited(). By default this is based off of + * na_tray_child_has_alpha(), but it may be useful to override it in + * certain circumstances; for example, if the #NaTrayChild is added + * to a parent window and that parent window is composited against the + * background. + */ +void +na_tray_child_set_composited (NaTrayChild *child, + gboolean composited) +{ + g_return_if_fail (NA_IS_TRAY_CHILD (child)); + + if (child->composited == composited) + return; + + child->composited = composited; + if (gtk_widget_get_realized (GTK_WIDGET (child))) + gdk_window_set_composited (gtk_widget_get_window (GTK_WIDGET (child)), + composited); +} + +/* If we are faking transparency with a window-relative background, force a + * redraw of the icon. This should be called if the background changes or if + * the child is shifted with respect to the background. + */ +void +na_tray_child_force_redraw (NaTrayChild *child) +{ + GtkWidget *widget = GTK_WIDGET (child); + + if (gtk_widget_get_mapped (widget) && child->parent_relative_bg) + { +#if 1 + /* Sending an ExposeEvent might cause redraw problems if the + * icon is expecting the server to clear-to-background before + * the redraw. It should be ok for GtkStatusIcon or EggTrayIcon. + */ + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (widget)); + XEvent xev; + GdkWindow *plug_window; + GtkAllocation allocation; + + plug_window = gtk_socket_get_plug_window (GTK_SOCKET (child)); + gtk_widget_get_allocation (widget, &allocation); + + xev.xexpose.type = Expose; + xev.xexpose.window = GDK_WINDOW_XID (plug_window); + xev.xexpose.x = 0; + xev.xexpose.y = 0; + xev.xexpose.width = allocation.width; + xev.xexpose.height = allocation.height; + xev.xexpose.count = 0; + + gdk_error_trap_push (); + XSendEvent (GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (widget)), + xev.xexpose.window, + False, ExposureMask, + &xev); + /* We have to sync to reliably catch errors from the XSendEvent(), + * since that is asynchronous. + */ + XSync (xdisplay, False); + gdk_error_trap_pop_ignored (); +#else + /* Hiding and showing is the safe way to do it, but can result in more + * flickering. + */ + gdk_window_hide (widget->window); + gdk_window_show (widget->window); +#endif + } +} + +/* from libwnck/xutils.c, comes as LGPLv2+ */ +static char * +latin1_to_utf8 (const char *latin1) +{ + GString *str; + const char *p; + + str = g_string_new (NULL); + + p = latin1; + while (*p) + { + g_string_append_unichar (str, (gunichar) *p); + ++p; + } + + return g_string_free (str, FALSE); +} + +/* derived from libwnck/xutils.c, comes as LGPLv2+ */ +static void +_get_wmclass (Display *xdisplay, + Window xwindow, + char **res_class, + char **res_name) +{ + XClassHint ch; + + ch.res_name = NULL; + ch.res_class = NULL; + + gdk_error_trap_push (); + XGetClassHint (xdisplay, xwindow, &ch); + gdk_error_trap_pop_ignored (); + + if (res_class) + *res_class = NULL; + + if (res_name) + *res_name = NULL; + + if (ch.res_name) + { + if (res_name) + *res_name = latin1_to_utf8 (ch.res_name); + + XFree (ch.res_name); + } + + if (ch.res_class) + { + if (res_class) + *res_class = latin1_to_utf8 (ch.res_class); + + XFree (ch.res_class); + } +} + +/** + * na_tray_child_get_wm_class; + * @child: a #NaTrayChild + * @res_name: return location for a string containing the application name of + * @child, or %NULL + * @res_class: return location for a string containing the application class of + * @child, or %NULL + * + * Fetches the resource associated with @child. + */ +void +na_tray_child_get_wm_class (NaTrayChild *child, + char **res_name, + char **res_class) +{ + GdkDisplay *display; + + g_return_if_fail (NA_IS_TRAY_CHILD (child)); + + display = gtk_widget_get_display (GTK_WIDGET (child)); + + _get_wmclass (GDK_DISPLAY_XDISPLAY (display), + child->icon_window, + res_class, + res_name); +} -- cgit v1.2.1