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