1 /* na-tray-child.c
2  * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
3  * Copyright (C) 2003-2006 Vincent Untz
4  * Copyright (C) 2008 Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <config.h>
21 #include <string.h>
22 
23 #include "na-tray-child.h"
24 
25 #include <gdk/gdk.h>
26 #include <gdk/gdkx.h>
27 #include <X11/Xatom.h>
28 
G_DEFINE_TYPE(NaTrayChild,na_tray_child,GTK_TYPE_SOCKET)29 G_DEFINE_TYPE (NaTrayChild, na_tray_child, GTK_TYPE_SOCKET)
30 
31 static void
32 na_tray_child_finalize (GObject *object)
33 {
34   G_OBJECT_CLASS (na_tray_child_parent_class)->finalize (object);
35 }
36 
37 static void
na_tray_child_realize(GtkWidget * widget)38 na_tray_child_realize (GtkWidget *widget)
39 {
40   NaTrayChild *child = NA_TRAY_CHILD (widget);
41   GdkVisual *visual = gtk_widget_get_visual (widget);
42   GdkWindow *window;
43 
44   GTK_WIDGET_CLASS (na_tray_child_parent_class)->realize (widget);
45 
46   window = gtk_widget_get_window (widget);
47 
48   if (child->has_alpha)
49     {
50       /* We have real transparency with an ARGB visual and the Composite
51        * extension. */
52 
53       /* Set a transparent background */
54       cairo_pattern_t *transparent = cairo_pattern_create_rgba (0, 0, 0, 0);
55 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
56       gdk_window_set_background_pattern (window, transparent);
57 G_GNUC_END_IGNORE_DEPRECATIONS
58       cairo_pattern_destroy (transparent);
59 
60       child->parent_relative_bg = FALSE;
61     }
62   else if (visual == gdk_window_get_visual (gdk_window_get_parent (window)))
63     {
64       /* Otherwise, if the visual matches the visual of the parent window, we
65        * can use a parent-relative background and fake transparency. */
66 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
67       gdk_window_set_background_pattern (window, NULL);
68 G_GNUC_END_IGNORE_DEPRECATIONS
69 
70       child->parent_relative_bg = TRUE;
71     }
72   else
73     {
74       /* Nothing to do; the icon will sit on top of an ugly gray box */
75       child->parent_relative_bg = FALSE;
76     }
77 
78   gtk_widget_set_app_paintable (GTK_WIDGET (child),
79                                 child->parent_relative_bg || child->has_alpha);
80 
81   /* Double-buffering will interfere with the parent-relative-background fake
82    * transparency, since the double-buffer code doesn't know how to fill in the
83    * background of the double-buffer correctly.
84    * The function is deprecated because it is only meaningful on X11 - the
85    * same is true for XEmbed of course, so just ignore the warning.
86    */
87 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
88   gtk_widget_set_double_buffered (GTK_WIDGET (child),
89                                   child->parent_relative_bg);
90 G_GNUC_END_IGNORE_DEPRECATIONS
91 }
92 
93 static void
na_tray_child_style_set(GtkWidget * widget,GtkStyle * previous_style)94 na_tray_child_style_set (GtkWidget *widget,
95                          GtkStyle  *previous_style)
96 {
97   /* The default handler resets the background according to the new style.
98    * We either use a transparent background or a parent-relative background
99    * and ignore the style background. So, just don't chain up.
100    */
101 }
102 
103 #if 0
104 /* This is adapted from code that was commented out in na-tray-manager.c; the
105  * code in na-tray-manager.c wouldn't have worked reliably, this will. So maybe
106  * it can be re-enabled. On other hand, things seem to be working fine without
107  * it.
108  *
109  * If reenabling, you need to hook it up in na_tray_child_class_init().
110  */
111 static void
112 na_tray_child_size_request (GtkWidget      *widget,
113                             GtkRequisition *request)
114 {
115   GTK_WIDGET_CLASS (na_tray_child_parent_class)->size_request (widget, request);
116 
117   /*
118    * Make sure the icons have a meaningful size ..
119    */
120   if ((request->width < 16) || (request->height < 16))
121     {
122       gint nw = MAX (24, request->width);
123       gint nh = MAX (24, request->height);
124       g_warning ("Tray icon has requested a size of (%ix%i), resizing to (%ix%i)",
125                  req.width, req.height, nw, nh);
126       request->width = nw;
127       request->height = nh;
128     }
129 }
130 #endif
131 
132 static void
na_tray_child_size_allocate(GtkWidget * widget,GtkAllocation * allocation)133 na_tray_child_size_allocate (GtkWidget      *widget,
134                              GtkAllocation  *allocation)
135 {
136   NaTrayChild *child = NA_TRAY_CHILD (widget);
137   GtkAllocation widget_allocation;
138   gboolean moved, resized;
139 
140   gtk_widget_get_allocation (widget, &widget_allocation);
141 
142   moved = (allocation->x != widget_allocation.x ||
143 	   allocation->y != widget_allocation.y);
144   resized = (allocation->width != widget_allocation.width ||
145 	     allocation->height != widget_allocation.height);
146 
147   /* When we are allocating the widget while mapped we need special handling
148    * for both real and fake transparency.
149    *
150    * Real transparency: we need to invalidate and trigger a redraw of the old
151    *   and new areas. (GDK really should handle this for us, but doesn't as of
152    *   GTK+-2.14)
153    *
154    * Fake transparency: if the widget moved, we need to force the contents to
155    *   be redrawn with the new offset for the parent-relative background.
156    */
157   if ((moved || resized) && gtk_widget_get_mapped (widget))
158     {
159       if (na_tray_child_has_alpha (child))
160         gdk_window_invalidate_rect (gdk_window_get_parent (gtk_widget_get_window (widget)),
161                                     &widget_allocation, FALSE);
162     }
163 
164   GTK_WIDGET_CLASS (na_tray_child_parent_class)->size_allocate (widget,
165                                                                 allocation);
166 
167   if ((moved || resized) && gtk_widget_get_mapped (widget))
168     {
169       if (na_tray_child_has_alpha (NA_TRAY_CHILD (widget)))
170         gdk_window_invalidate_rect (gdk_window_get_parent (gtk_widget_get_window (widget)),
171                                     &widget_allocation, FALSE);
172       else if (moved && child->parent_relative_bg)
173         na_tray_child_force_redraw (child);
174     }
175 }
176 
177 /* The plug window should completely occupy the area of the child, so we won't
178  * get a draw event. But in case we do (the plug unmaps itself, say), this
179  * draw handler draws with real or fake transparency.
180  */
181 static gboolean
na_tray_child_draw(GtkWidget * widget,cairo_t * cr)182 na_tray_child_draw (GtkWidget *widget,
183                     cairo_t   *cr)
184 {
185   NaTrayChild *child = NA_TRAY_CHILD (widget);
186 
187   if (na_tray_child_has_alpha (child))
188     {
189       /* Clear to transparent */
190       cairo_set_source_rgba (cr, 0, 0, 0, 0);
191       cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
192       cairo_paint (cr);
193     }
194   else if (child->parent_relative_bg)
195     {
196       GdkWindow *window;
197       cairo_surface_t *target;
198       GdkRectangle clip_rect;
199 
200       window = gtk_widget_get_window (widget);
201       target = cairo_get_group_target (cr);
202 
203       gdk_cairo_get_clip_rectangle (cr, &clip_rect);
204 
205       /* Clear to parent-relative pixmap
206        * We need to use direct X access here because GDK doesn't know about
207        * the parent relative pixmap. */
208       cairo_surface_flush (target);
209 
210       XClearArea (GDK_WINDOW_XDISPLAY (window),
211                   GDK_WINDOW_XID (window),
212                   clip_rect.x, clip_rect.y,
213                   clip_rect.width, clip_rect.height,
214                   False);
215       cairo_surface_mark_dirty_rectangle (target,
216                                           clip_rect.x, clip_rect.y,
217                                           clip_rect.width, clip_rect.height);
218     }
219 
220   return FALSE;
221 }
222 
223 static void
na_tray_child_init(NaTrayChild * child)224 na_tray_child_init (NaTrayChild *child)
225 {
226 }
227 
228 static void
na_tray_child_class_init(NaTrayChildClass * klass)229 na_tray_child_class_init (NaTrayChildClass *klass)
230 {
231   GObjectClass *gobject_class;
232   GtkWidgetClass *widget_class;
233 
234   gobject_class = (GObjectClass *)klass;
235   widget_class = (GtkWidgetClass *)klass;
236 
237   gobject_class->finalize = na_tray_child_finalize;
238   widget_class->style_set = na_tray_child_style_set;
239   widget_class->realize = na_tray_child_realize;
240   widget_class->size_allocate = na_tray_child_size_allocate;
241   widget_class->draw = na_tray_child_draw;
242 }
243 
244 GtkWidget *
na_tray_child_new(GdkScreen * screen,Window icon_window)245 na_tray_child_new (GdkScreen *screen,
246                    Window     icon_window)
247 {
248   XWindowAttributes window_attributes;
249   GdkDisplay *display;
250   Display *xdisplay;
251   NaTrayChild *child;
252   GdkVisual *visual;
253   gboolean visual_has_alpha;
254   int red_prec, green_prec, blue_prec, depth;
255   int result;
256 
257   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
258   g_return_val_if_fail (icon_window != None, NULL);
259 
260   xdisplay = GDK_SCREEN_XDISPLAY (screen);
261   display = gdk_x11_lookup_xdisplay (xdisplay);
262 
263   /* We need to determine the visual of the window we are embedding and create
264    * the socket in the same visual.
265    */
266 
267   gdk_x11_display_error_trap_push (display);
268   result = XGetWindowAttributes (xdisplay, icon_window,
269                                  &window_attributes);
270   gdk_x11_display_error_trap_pop_ignored (display);
271 
272   if (!result) /* Window already gone */
273     return NULL;
274 
275   visual = gdk_x11_screen_lookup_visual (screen,
276                                          window_attributes.visual->visualid);
277   if (!visual) /* Icon window is on another screen? */
278     return NULL;
279 
280   child = g_object_new (NA_TYPE_TRAY_CHILD, NULL);
281   child->icon_window = icon_window;
282 
283   gtk_widget_set_visual (GTK_WIDGET (child), visual);
284 
285   /* We have alpha if the visual has something other than red, green,
286    * and blue */
287   gdk_visual_get_red_pixel_details (visual, NULL, NULL, &red_prec);
288   gdk_visual_get_green_pixel_details (visual, NULL, NULL, &green_prec);
289   gdk_visual_get_blue_pixel_details (visual, NULL, NULL, &blue_prec);
290   depth = gdk_visual_get_depth (visual);
291 
292   visual_has_alpha = red_prec + blue_prec + green_prec < depth;
293   child->has_alpha = visual_has_alpha;
294 
295   return GTK_WIDGET (child);
296 }
297 
298 char *
na_tray_child_get_title(NaTrayChild * child)299 na_tray_child_get_title (NaTrayChild *child)
300 {
301   char *retval = NULL;
302   GdkDisplay *display;
303   Atom utf8_string, atom, type;
304   int result;
305   int format;
306   gulong nitems;
307   gulong bytes_after;
308   gchar *val;
309 
310   g_return_val_if_fail (NA_IS_TRAY_CHILD (child), NULL);
311 
312   display = gtk_widget_get_display (GTK_WIDGET (child));
313 
314   utf8_string = gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING");
315   atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_NAME");
316 
317   gdk_x11_display_error_trap_push (display);
318 
319   result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display),
320                                child->icon_window,
321                                atom,
322                                0, G_MAXLONG,
323                                False, utf8_string,
324                                &type, &format, &nitems,
325                                &bytes_after, (guchar **)&val);
326 
327   if (gdk_x11_display_error_trap_pop (display) || result != Success)
328     return NULL;
329 
330   if (type != utf8_string ||
331       format != 8 ||
332       nitems == 0)
333     {
334       if (val)
335         XFree (val);
336       return NULL;
337     }
338 
339   if (!g_utf8_validate (val, nitems, NULL))
340     {
341       XFree (val);
342       return NULL;
343     }
344 
345   retval = g_strndup (val, nitems);
346 
347   XFree (val);
348 
349   return retval;
350 }
351 
352 /**
353  * na_tray_child_has_alpha;
354  * @child: a #NaTrayChild
355  *
356  * Checks if the child has an ARGB visual and real alpha transparence.
357  * (as opposed to faked alpha transparency with an parent-relative
358  * background)
359  *
360  * Return value: %TRUE if the child has an alpha transparency
361  */
362 gboolean
na_tray_child_has_alpha(NaTrayChild * child)363 na_tray_child_has_alpha (NaTrayChild *child)
364 {
365   g_return_val_if_fail (NA_IS_TRAY_CHILD (child), FALSE);
366 
367   return child->has_alpha;
368 }
369 
370 /* If we are faking transparency with a window-relative background, force a
371  * redraw of the icon. This should be called if the background changes or if
372  * the child is shifted with respect to the background.
373  */
374 void
na_tray_child_force_redraw(NaTrayChild * child)375 na_tray_child_force_redraw (NaTrayChild *child)
376 {
377   GtkWidget *widget = GTK_WIDGET (child);
378 
379   if (gtk_widget_get_mapped (widget) && child->parent_relative_bg)
380     {
381 #if 1
382       /* Sending an ExposeEvent might cause redraw problems if the
383        * icon is expecting the server to clear-to-background before
384        * the redraw. It should be ok for GtkStatusIcon or EggTrayIcon.
385        */
386       GdkDisplay *display = gtk_widget_get_display (widget);
387       Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
388       XEvent xev;
389       GdkWindow *plug_window;
390       GtkAllocation allocation;
391 
392       plug_window = gtk_socket_get_plug_window (GTK_SOCKET (child));
393       gtk_widget_get_allocation (widget, &allocation);
394 
395       xev.xexpose.type = Expose;
396       xev.xexpose.window = GDK_WINDOW_XID (plug_window);
397       xev.xexpose.x = 0;
398       xev.xexpose.y = 0;
399       xev.xexpose.width = allocation.width;
400       xev.xexpose.height = allocation.height;
401       xev.xexpose.count = 0;
402 
403       gdk_x11_display_error_trap_push (display);
404       XSendEvent (xdisplay,
405                   xev.xexpose.window,
406                   False, ExposureMask,
407                   &xev);
408       gdk_x11_display_error_trap_pop_ignored (display);
409 #else
410       /* Hiding and showing is the safe way to do it, but can result in more
411        * flickering.
412        */
413       gdk_window_hide (widget->window);
414       gdk_window_show (widget->window);
415 #endif
416     }
417 }
418 
419 /* from libwnck/xutils.c, comes as LGPLv2+ */
420 static char *
latin1_to_utf8(const char * latin1)421 latin1_to_utf8 (const char *latin1)
422 {
423   GString *str;
424   const char *p;
425 
426   str = g_string_new (NULL);
427 
428   p = latin1;
429   while (*p)
430     {
431       g_string_append_unichar (str, (gunichar) *p);
432       ++p;
433     }
434 
435   return g_string_free (str, FALSE);
436 }
437 
438 /* derived from libwnck/xutils.c, comes as LGPLv2+ */
439 static void
_get_wmclass(Display * xdisplay,Window xwindow,char ** res_class,char ** res_name)440 _get_wmclass (Display *xdisplay,
441               Window   xwindow,
442               char   **res_class,
443               char   **res_name)
444 {
445   GdkDisplay *display;
446   XClassHint ch;
447 
448   ch.res_name = NULL;
449   ch.res_class = NULL;
450 
451   display = gdk_x11_lookup_xdisplay (xdisplay);
452   gdk_x11_display_error_trap_push (display);
453   XGetClassHint (xdisplay, xwindow, &ch);
454   gdk_x11_display_error_trap_pop_ignored (display);
455 
456   if (res_class)
457     *res_class = NULL;
458 
459   if (res_name)
460     *res_name = NULL;
461 
462   if (ch.res_name)
463     {
464       if (res_name)
465         *res_name = latin1_to_utf8 (ch.res_name);
466 
467       XFree (ch.res_name);
468     }
469 
470   if (ch.res_class)
471     {
472       if (res_class)
473         *res_class = latin1_to_utf8 (ch.res_class);
474 
475       XFree (ch.res_class);
476     }
477 }
478 
479 /**
480  * na_tray_child_get_wm_class;
481  * @child: a #NaTrayChild
482  * @res_name: return location for a string containing the application name of
483  * @child, or %NULL
484  * @res_class: return location for a string containing the application class of
485  * @child, or %NULL
486  *
487  * Fetches the resource associated with @child.
488  */
489 void
na_tray_child_get_wm_class(NaTrayChild * child,char ** res_name,char ** res_class)490 na_tray_child_get_wm_class (NaTrayChild  *child,
491                             char        **res_name,
492                             char        **res_class)
493 {
494   GdkDisplay *display;
495 
496   g_return_if_fail (NA_IS_TRAY_CHILD (child));
497 
498   display = gtk_widget_get_display (GTK_WIDGET (child));
499 
500   _get_wmclass (GDK_DISPLAY_XDISPLAY (display),
501                 child->icon_window,
502                 res_class,
503                 res_name);
504 }
505