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