gnome-shell-3.6.3.1/src/tray/na-tray-child.c

No issues found

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