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 }