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