1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* eggtrayicon.c
3  * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20 
21 #include <config.h>
22 
23 #include "uim/gettext.h"
24 #include <string.h>
25 
26 #include "eggtrayicon.h"
27 
28 #if !GTK_CHECK_VERSION(2, 90, 0)
29 #include <gdkconfig.h>
30 #endif
31 #include <gdk/gdkx.h>
32 #include <X11/Xatom.h>
33 #include <gtk/gtk.h>
34 
35 #ifndef EGG_COMPILATION
36 #ifndef _
37 #define _(x) dgettext (GETTEXT_PACKAGE, x)
38 #define N_(x) x
39 #endif
40 #else
41 #define _(x) x
42 #define N_(x) x
43 #endif
44 
45 #define SYSTEM_TRAY_REQUEST_DOCK    0
46 #define SYSTEM_TRAY_BEGIN_MESSAGE   1
47 #define SYSTEM_TRAY_CANCEL_MESSAGE  2
48 
49 #define SYSTEM_TRAY_ORIENTATION_HORZ 0
50 #define SYSTEM_TRAY_ORIENTATION_VERT 1
51 
52 enum {
53   PROP_0,
54   PROP_ORIENTATION
55 };
56 
57 static GtkPlugClass *parent_class = NULL;
58 
59 static void egg_tray_icon_init (EggTrayIcon *icon);
60 static void egg_tray_icon_class_init (EggTrayIconClass *klass);
61 
62 #if GLIB_CHECK_VERSION(2, 13, 1)
63 static void egg_tray_icon_constructed  (GObject    *object);
64 #endif
65 static void egg_tray_icon_dispose      (GObject    *object);
66 
67 static void egg_tray_icon_get_property (GObject    *object,
68 					guint       prop_id,
69 					GValue     *value,
70 					GParamSpec *pspec);
71 
72 static void    egg_tray_icon_realize   (GtkWidget   *widget);
73 static void    egg_tray_icon_style_set (GtkWidget   *widget,
74                                         GtkStyle    *previous_style);
75 static gboolean egg_tray_icon_delete   (GtkWidget   *widget,
76                                         GdkEventAny *event);
77 #if GTK_CHECK_VERSION(2, 90, 0)
78 static gboolean egg_tray_icon_draw   (GtkWidget      *widget,
79                                       cairo_t *cr);
80 #else
81 static gboolean egg_tray_icon_expose   (GtkWidget      *widget,
82                                         GdkEventExpose *event);
83 #endif
84 
85 static void egg_tray_icon_clear_manager_window     (EggTrayIcon *icon);
86 static void egg_tray_icon_update_manager_window    (EggTrayIcon *icon);
87 static void egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon);
88 static GdkFilterReturn egg_tray_icon_manager_filter (GdkXEvent *xevent,
89                                                      GdkEvent  *event,
90                                                      gpointer   user_data);
91 
92 GType
egg_tray_icon_get_type(void)93 egg_tray_icon_get_type (void)
94 {
95   static GType our_type = 0;
96 
97   if (our_type == 0)
98     {
99       static const GTypeInfo our_info =
100       {
101 	sizeof (EggTrayIconClass),
102 	(GBaseInitFunc) NULL,
103 	(GBaseFinalizeFunc) NULL,
104 	(GClassInitFunc) egg_tray_icon_class_init,
105 	NULL, /* class_finalize */
106 	NULL, /* class_data */
107 	sizeof (EggTrayIcon),
108 	0,    /* n_preallocs */
109 	(GInstanceInitFunc) egg_tray_icon_init
110       };
111 
112       our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0);
113     }
114 
115   return our_type;
116 }
117 
118 static void
egg_tray_icon_init(EggTrayIcon * icon)119 egg_tray_icon_init (EggTrayIcon *icon)
120 {
121   icon->stamp = 1;
122   icon->orientation = GTK_ORIENTATION_HORIZONTAL;
123 
124   gtk_widget_set_app_paintable (GTK_WIDGET (icon), TRUE);
125   gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
126 }
127 
128 static void
egg_tray_icon_class_init(EggTrayIconClass * klass)129 egg_tray_icon_class_init (EggTrayIconClass *klass)
130 {
131   GObjectClass *gobject_class = (GObjectClass *)klass;
132   GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
133 
134   parent_class = g_type_class_peek_parent (klass);
135 
136   gobject_class->get_property = egg_tray_icon_get_property;
137 #if GLIB_CHECK_VERSION(2, 13, 1)
138   gobject_class->constructed = egg_tray_icon_constructed;
139 #endif
140   gobject_class->dispose = egg_tray_icon_dispose;
141 
142   widget_class->realize = egg_tray_icon_realize;
143   widget_class->style_set = egg_tray_icon_style_set;
144   widget_class->delete_event = egg_tray_icon_delete;
145 #if GTK_CHECK_VERSION(2, 90, 0)
146   widget_class->draw = egg_tray_icon_draw;
147 #else
148   widget_class->expose_event = egg_tray_icon_expose;
149 #endif
150 
151   g_object_class_install_property (gobject_class,
152 				   PROP_ORIENTATION,
153 				   g_param_spec_enum ("orientation",
154 						      _("Orientation"),
155 						      _("The orientation of the tray."),
156 						      GTK_TYPE_ORIENTATION,
157 						      GTK_ORIENTATION_HORIZONTAL,
158 						      G_PARAM_READABLE));
159 
160 }
161 
162 static void
163 #if GLIB_CHECK_VERSION(2, 13, 1)
egg_tray_icon_constructed(GObject * object)164 egg_tray_icon_constructed (GObject *object)
165 #else
166 egg_tray_icon_realize_internal (GtkWidget *object)
167 #endif
168 {
169   /* Do setup that depends on the screen; screen has been set at this point */
170 
171   EggTrayIcon *icon = EGG_TRAY_ICON (object);
172   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (object));
173   GdkWindow *root_window = gdk_screen_get_root_window (screen);
174   GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (object));
175   Display *xdisplay = gdk_x11_display_get_xdisplay (display);
176   char buffer[256];
177 
178   g_snprintf (buffer, sizeof (buffer),
179               "_NET_SYSTEM_TRAY_S%d",
180               gdk_screen_get_number (screen));
181 
182   icon->selection_atom = XInternAtom (xdisplay, buffer, False);
183 
184   icon->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
185 
186   icon->system_tray_opcode_atom = XInternAtom (xdisplay,
187                                                "_NET_SYSTEM_TRAY_OPCODE",
188                                                False);
189 
190   icon->orientation_atom = XInternAtom (xdisplay,
191                                         "_NET_SYSTEM_TRAY_ORIENTATION",
192                                         False);
193 
194   icon->visual_atom = XInternAtom (xdisplay,
195                                    "_NET_SYSTEM_TRAY_VISUAL",
196                                    False);
197 
198   /* Add a root window filter so that we get changes on MANAGER */
199   gdk_window_add_filter (root_window,
200                          egg_tray_icon_manager_filter, icon);
201 
202   egg_tray_icon_update_manager_window (icon);
203 }
204 
205 static void
egg_tray_icon_clear_manager_window(EggTrayIcon * icon)206 egg_tray_icon_clear_manager_window (EggTrayIcon *icon)
207 {
208   GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (icon));
209 
210   if (icon->manager_window != None)
211     {
212       GdkWindow *gdkwin;
213 
214 #if GTK_CHECK_VERSION(2, 24, 0)
215       gdkwin = gdk_x11_window_lookup_for_display (display,
216                                               icon->manager_window);
217 #else
218       gdkwin = gdk_window_lookup_for_display (display,
219                                               icon->manager_window);
220 #endif
221 
222       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
223 
224       icon->manager_window = None;
225       icon->manager_visual = NULL;
226     }
227 }
228 
229 static void
egg_tray_icon_dispose(GObject * object)230 egg_tray_icon_dispose (GObject *object)
231 {
232   EggTrayIcon *icon = EGG_TRAY_ICON (object);
233   GtkWidget *widget = GTK_WIDGET (object);
234   GdkWindow *root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
235 
236   egg_tray_icon_clear_manager_window (icon);
237 
238   gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);
239 }
240 
241 static void
egg_tray_icon_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)242 egg_tray_icon_get_property (GObject    *object,
243 			    guint       prop_id,
244 			    GValue     *value,
245 			    GParamSpec *pspec)
246 {
247   EggTrayIcon *icon = EGG_TRAY_ICON (object);
248 
249   switch (prop_id)
250     {
251     case PROP_ORIENTATION:
252       g_value_set_enum (value, icon->orientation);
253       break;
254     default:
255       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
256       break;
257     }
258 }
259 
260 static gboolean
261 #if GTK_CHECK_VERSION(2, 90, 0)
egg_tray_icon_draw(GtkWidget * widget,cairo_t * cr)262 egg_tray_icon_draw (GtkWidget *widget,
263                     cairo_t *cr)
264 #else
265 egg_tray_icon_expose (GtkWidget *widget,
266                       GdkEventExpose *event)
267 #endif
268 {
269   EggTrayIcon *icon = EGG_TRAY_ICON (widget);
270   GtkWidget *focus_child;
271   gint border_width, x, y, width, height;
272   gboolean retval = FALSE;
273 
274 #if GTK_CHECK_VERSION(2, 8, 0)
275   if (icon->manager_visual_rgba)
276     {
277       /* Clear to transparent */
278 #if GTK_CHECK_VERSION(2, 90, 0)
279       GdkRectangle rect;
280       if (gdk_cairo_get_clip_rectangle(cr, &rect)) {
281         cairo_set_source_rgba (cr, 0, 0, 0, 0);
282         cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
283         gdk_cairo_rectangle (cr, &rect);
284         cairo_fill (cr);
285       }
286 #else
287       cairo_t *cr = gdk_cairo_create (gtk_widget_get_window(widget));
288       cairo_set_source_rgba (cr, 0, 0, 0, 0);
289       cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
290       gdk_cairo_region (cr, event->region);
291       cairo_fill (cr);
292       cairo_destroy (cr);
293 #endif
294     }
295   else
296 #endif
297     {
298       /* Clear to parent-relative pixmap */
299 #if !GTK_CHECK_VERSION(2, 90, 0)
300       gdk_window_clear_area (gtk_widget_get_window(widget),
301           event->area.x, event->area.y,
302                              event->area.width, event->area.height);
303 #endif
304     }
305 
306 #if GTK_CHECK_VERSION(2, 90, 0)
307   if (GTK_WIDGET_CLASS (parent_class)->draw)
308     retval = GTK_WIDGET_CLASS (parent_class)->draw (widget, cr);
309 #else
310   if (GTK_WIDGET_CLASS (parent_class)->expose_event)
311     retval = GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
312 #endif
313 
314   focus_child = gtk_container_get_focus_child(GTK_CONTAINER (widget));
315 #if GTK_CHECK_VERSION(2, 18, 0)
316   if (focus_child && gtk_widget_has_focus (focus_child))
317     {
318 #if GTK_CHECK_VERSION(2, 90, 0)
319       border_width = gtk_container_get_border_width(GTK_CONTAINER (widget));
320 
321       x = border_width;
322       y = border_width;
323 
324       width  = gtk_widget_get_allocated_width (widget);
325       height = gtk_widget_get_allocated_height (widget);
326 
327       gtk_render_focus (gtk_widget_get_style_context(widget),
328                         cr, x, y, width, height);
329 #else
330       GtkAllocation allocation;
331       border_width = gtk_container_get_border_width(GTK_CONTAINER (widget));
332 
333       gtk_widget_get_allocation(widget, &allocation);
334 
335       x = allocation.x + border_width;
336       y = allocation.y + border_width;
337 
338       width  = allocation.width  - 2 * border_width;
339       height = allocation.height - 2 * border_width;
340 
341       gtk_paint_focus (gtk_widget_get_style(widget),
342                        gtk_widget_get_window(widget),
343                        gtk_widget_get_state (widget),
344                        &event->area, widget, "tray_icon",
345                        x, y, width, height);
346 #endif
347     }
348 #else
349   if (focus_child && GTK_WIDGET_HAS_FOCUS (focus_child))
350     {
351       border_width = GTK_CONTAINER (widget)->border_width;
352 
353       x = widget->allocation.x + border_width;
354       y = widget->allocation.y + border_width;
355 
356       width  = widget->allocation.width  - 2 * border_width;
357       height = widget->allocation.height - 2 * border_width;
358 
359       gtk_paint_focus (widget->style, widget->window,
360                        GTK_WIDGET_STATE (widget),
361                        &event->area, widget, "tray_icon",
362                        x, y, width, height);
363     }
364 #endif
365   return retval;
366 }
367 
368 static void
egg_tray_icon_get_orientation_property(EggTrayIcon * icon)369 egg_tray_icon_get_orientation_property (EggTrayIcon *icon)
370 {
371   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
372   GdkDisplay *display = gdk_screen_get_display (screen);
373   Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
374 
375   Atom type;
376   int format;
377   union {
378 	gulong *prop;
379 	guchar *prop_ch;
380   } prop = { NULL };
381   gulong nitems;
382   gulong bytes_after;
383   int error, result;
384 
385   g_assert (icon->manager_window != None);
386 
387   gdk_error_trap_push ();
388   type = None;
389   result = XGetWindowProperty (xdisplay,
390 			       icon->manager_window,
391 			       icon->orientation_atom,
392 			       0, G_MAXLONG, FALSE,
393 			       XA_CARDINAL,
394 			       &type, &format, &nitems,
395 			       &bytes_after, &(prop.prop_ch));
396   error = gdk_error_trap_pop ();
397 
398   if (error || result != Success)
399     return;
400 
401   if (type == XA_CARDINAL)
402     {
403       GtkOrientation orientation;
404 
405       orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
406 					GTK_ORIENTATION_HORIZONTAL :
407 					GTK_ORIENTATION_VERTICAL;
408 
409       if (icon->orientation != orientation)
410 	{
411 	  icon->orientation = orientation;
412 
413 	  g_object_notify (G_OBJECT (icon), "orientation");
414 	}
415     }
416 
417   if (type != None)
418     XFree (prop.prop);
419 }
420 
421 static void
egg_tray_icon_get_visual_property(EggTrayIcon * icon)422 egg_tray_icon_get_visual_property (EggTrayIcon *icon)
423 {
424   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
425   GdkDisplay *display = gdk_screen_get_display (screen);
426   Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
427 
428   Atom type;
429   int format;
430   union {
431         gulong *prop;
432         guchar *prop_ch;
433   } prop = { NULL };
434   gulong nitems;
435   gulong bytes_after;
436   int error, result;
437   GdkVisual *visual;
438   gint red_prec;
439   gint blue_prec;
440   gint green_prec;
441   gint depth;
442 
443   g_assert (icon->manager_window != None);
444 
445   gdk_error_trap_push ();
446   type = None;
447   result = XGetWindowProperty (xdisplay,
448                                icon->manager_window,
449                                icon->visual_atom,
450                                0, G_MAXLONG, FALSE,
451                                XA_VISUALID,
452                                &type, &format, &nitems,
453                                &bytes_after, &(prop.prop_ch));
454   error = gdk_error_trap_pop ();
455 
456   visual = NULL;
457 
458   if (!error && result == Success &&
459       type == XA_VISUALID && nitems == 1 && format == 32)
460     {
461       VisualID visual_id = prop.prop[0];
462       visual = gdk_x11_screen_lookup_visual (screen, visual_id);
463     }
464 
465   icon->manager_visual = visual;
466 #if GTK_CHECK_VERSION(2, 22, 0)
467   gdk_visual_get_red_pixel_details(visual, NULL, NULL, &red_prec);
468   gdk_visual_get_blue_pixel_details(visual, NULL, NULL, &blue_prec);
469   gdk_visual_get_green_pixel_details(visual, NULL, NULL, &green_prec);
470   depth = gdk_visual_get_depth(visual);
471 #else
472   if (visual)
473     {
474       red_prec = visual->red_prec;
475       blue_prec = visual->blue_prec;
476       green_prec = visual->green_prec;
477       depth = visual->depth;
478     }
479 #endif
480   icon->manager_visual_rgba = visual != NULL &&
481     (red_prec + blue_prec + green_prec < depth);
482 
483   /* For the background-relative hack we use when we aren't using a real RGBA
484    * visual, we can't be double-buffered */
485   gtk_widget_set_double_buffered (GTK_WIDGET (icon), icon->manager_visual_rgba);
486 
487   if (type != None)
488     XFree (prop.prop);
489 }
490 
491 static GdkFilterReturn
egg_tray_icon_manager_filter(GdkXEvent * xevent,GdkEvent * event,gpointer user_data)492 egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
493 {
494   EggTrayIcon *icon = user_data;
495   XEvent *xev = (XEvent *)xevent;
496 
497   if (xev->xany.type == ClientMessage &&
498       xev->xclient.message_type == icon->manager_atom &&
499       xev->xclient.data.l[1] == (long)icon->selection_atom)
500     {
501       egg_tray_icon_update_manager_window (icon);
502     }
503   else if (xev->xany.window == icon->manager_window)
504     {
505       if (xev->xany.type == PropertyNotify &&
506 	  xev->xproperty.atom == icon->orientation_atom)
507 	{
508 	  egg_tray_icon_get_orientation_property (icon);
509 	}
510       else if (xev->xany.type == DestroyNotify)
511 	{
512 	  egg_tray_icon_manager_window_destroyed (icon);
513 	}
514     }
515 
516   return GDK_FILTER_CONTINUE;
517 }
518 
519 static void
egg_tray_icon_send_manager_message(EggTrayIcon * icon,long message,Window window,long data1,long data2,long data3)520 egg_tray_icon_send_manager_message (EggTrayIcon *icon,
521 				    long         message,
522 				    Window       window,
523 				    long         data1,
524 				    long         data2,
525 				    long         data3)
526 {
527   XClientMessageEvent ev;
528   Display *display;
529 
530   memset (&ev, 0, sizeof (ev));
531   ev.type = ClientMessage;
532   ev.window = window;
533   ev.message_type = icon->system_tray_opcode_atom;
534   ev.format = 32;
535   ev.data.l[0]
536       = gdk_x11_get_server_time (gtk_widget_get_window(GTK_WIDGET (icon)));
537   ev.data.l[1] = message;
538   ev.data.l[2] = data1;
539   ev.data.l[3] = data2;
540   ev.data.l[4] = data3;
541 
542   display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
543 
544   gdk_error_trap_push ();
545   XSendEvent (display,
546 	      icon->manager_window, False, NoEventMask, (XEvent *)&ev);
547   gdk_display_sync (gtk_widget_get_display (GTK_WIDGET (icon)));
548 #if GTK_CHECK_VERSION(2, 90, 0)
549   gdk_error_trap_pop_ignored ();
550 #else
551   gdk_error_trap_pop ();
552 #endif
553 }
554 
555 static void
egg_tray_icon_send_dock_request(EggTrayIcon * icon)556 egg_tray_icon_send_dock_request (EggTrayIcon *icon)
557 {
558   egg_tray_icon_send_manager_message (icon,
559 				      SYSTEM_TRAY_REQUEST_DOCK,
560 				      icon->manager_window,
561 				      gtk_plug_get_id (GTK_PLUG (icon)),
562 				      0, 0);
563 }
564 
565 static void
egg_tray_icon_update_manager_window(EggTrayIcon * icon)566 egg_tray_icon_update_manager_window (EggTrayIcon *icon)
567 {
568   GtkWidget *widget = GTK_WIDGET (icon);
569   GdkScreen *screen = gtk_widget_get_screen (widget);
570   GdkDisplay *display = gdk_screen_get_display (screen);
571   Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
572 
573   if (icon->manager_window != None)
574     return;
575 
576 
577   XGrabServer (xdisplay);
578 
579   icon->manager_window = XGetSelectionOwner (xdisplay,
580 					     icon->selection_atom);
581 
582   if (icon->manager_window != None)
583     XSelectInput (xdisplay,
584 		  icon->manager_window, StructureNotifyMask|PropertyChangeMask);
585 
586   XUngrabServer (xdisplay);
587   XFlush (xdisplay);
588 
589   if (icon->manager_window != None)
590     {
591       GdkWindow *gdkwin;
592 
593 #if GTK_CHECK_VERSION(2, 24, 0)
594       gdkwin = gdk_x11_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
595 					      icon->manager_window);
596 #else
597       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
598 					      icon->manager_window);
599 #endif
600 
601       gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
602 
603       egg_tray_icon_get_orientation_property (icon);
604       egg_tray_icon_get_visual_property (icon);
605 
606 #if GTK_CHECK_VERSION(2, 20, 0)
607       if (gtk_widget_get_realized (GTK_WIDGET (icon)))
608 #else
609       if (GTK_WIDGET_REALIZED (GTK_WIDGET (icon)))
610 #endif
611         {
612           if ((icon->manager_visual == NULL &&
613                gtk_widget_get_visual (widget) == gdk_screen_get_system_visual (screen)) ||
614               (icon->manager_visual == gtk_widget_get_visual (widget)))
615             {
616               /* Already have the right visual, can just dock
617                */
618               egg_tray_icon_send_dock_request (icon);
619             }
620           else
621             {
622               /* Need to re-realize the widget to get the right visual
623                */
624               gtk_widget_hide (widget);
625               gtk_widget_unrealize (widget);
626               gtk_widget_show (widget);
627             }
628         }
629     }
630 }
631 
632 static void
egg_tray_icon_manager_window_destroyed(EggTrayIcon * icon)633 egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon)
634 {
635   g_return_if_fail (icon->manager_window != None);
636 
637   egg_tray_icon_clear_manager_window (icon);
638 }
639 
640 static gboolean
egg_tray_icon_delete(GtkWidget * widget,GdkEventAny * event)641 egg_tray_icon_delete (GtkWidget   *widget,
642                       GdkEventAny *event)
643 {
644   /* A bug in X server versions up to x.org 1.5.0 means that:
645    * XFixesChangeSaveSet(...., SaveSetRoot, SaveSetUnmap) doesn't work properly
646    * and we'll left mapped in a separate toplevel window if the tray is destroyed.
647    * For simplicity just get rid of our X window and start over.
648    */
649   gtk_widget_hide (widget);
650   gtk_widget_unrealize (widget);
651   gtk_widget_show (widget);
652 
653   /* Handled it, don't destroy the tray icon */
654   return TRUE;
655 }
656 
657 #if GTK_CHECK_VERSION(2, 90, 0)
658 static void
egg_tray_icon_set_colormap(EggTrayIcon * icon)659 egg_tray_icon_set_colormap (EggTrayIcon *icon)
660 {
661   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
662   GdkVisual *visual = icon->manager_visual;
663 
664   /* To avoid uncertainty about colormaps, _NET_SYSTEM_TRAY_VISUAL is supposed
665    * to be either the screen default visual or a TrueColor visual; ignore it
666    * if it is something else
667    */
668   if (visual && gdk_visual_get_visual_type(visual) != GDK_VISUAL_TRUE_COLOR)
669     visual = NULL;
670 
671   if (visual == NULL || visual == gdk_screen_get_system_visual (screen))
672     visual = gdk_screen_get_system_visual (screen);
673   else if (visual == gdk_screen_get_rgba_visual (screen))
674     visual = gdk_screen_get_rgba_visual (screen);
675 
676   gtk_widget_set_visual (GTK_WIDGET (icon), visual);
677 }
678 #else
679 static void
egg_tray_icon_set_colormap(EggTrayIcon * icon)680 egg_tray_icon_set_colormap (EggTrayIcon *icon)
681 {
682   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
683   GdkColormap *colormap;
684   GdkVisual *visual = icon->manager_visual;
685   gboolean new_colormap = FALSE;
686 
687   /* To avoid uncertainty about colormaps, _NET_SYSTEM_TRAY_VISUAL is supposed
688    * to be either the screen default visual or a TrueColor visual; ignore it
689    * if it is something else
690    */
691   if (visual && visual->type != GDK_VISUAL_TRUE_COLOR)
692     visual = NULL;
693 
694   if (visual == NULL || visual == gdk_screen_get_system_visual (screen))
695     colormap = gdk_screen_get_system_colormap (screen);
696   else if (visual == gdk_screen_get_rgb_visual (screen))
697     colormap = gdk_screen_get_rgb_colormap (screen);
698 #if GTK_CHECK_VERSION(2, 8, 0)
699   else if (visual == gdk_screen_get_rgba_visual (screen))
700     colormap = gdk_screen_get_rgba_colormap (screen);
701 #endif
702   else
703     {
704       colormap = gdk_colormap_new (visual, FALSE);
705       new_colormap = TRUE;
706     }
707 
708   gtk_widget_set_colormap (GTK_WIDGET (icon), colormap);
709 
710   if (new_colormap)
711     g_object_unref (colormap);
712 }
713 #endif
714 
715 static void
egg_tray_icon_realize(GtkWidget * widget)716 egg_tray_icon_realize (GtkWidget *widget)
717 {
718   EggTrayIcon *icon = EGG_TRAY_ICON (widget);
719 
720 #if !GLIB_CHECK_VERSION(2, 13, 1)
721   egg_tray_icon_realize_internal (widget);
722 #endif
723 
724   egg_tray_icon_set_colormap (icon);
725 
726   GTK_WIDGET_CLASS (parent_class)->realize (widget);
727   if (icon->manager_visual_rgba)
728     {
729       /* Set a transparent background */
730 #if GTK_CHECK_VERSION(3, 4, 0)
731       GdkRGBA transparent = { 0, 0, 0, 0 };
732       gdk_window_set_background_rgba (gtk_widget_get_window(widget),
733                                       &transparent);
734 #else
735       GdkColor transparent = { 0, 0, 0, 0 }; /* Only pixel=0 matters */
736       gdk_window_set_background (gtk_widget_get_window(widget), &transparent);
737 #endif
738     }
739   else
740     {
741       /* Set a parent-relative background pixmap */
742 #if GTK_CHECK_VERSION(2, 90, 0)
743       gdk_window_set_background_pattern (gtk_widget_get_window(widget), NULL);
744 #else
745       gdk_window_set_back_pixmap (gtk_widget_get_window(widget), NULL, TRUE);
746 #endif
747     }
748 
749   if (icon->manager_window != None)
750     egg_tray_icon_send_dock_request (icon);
751 }
752 
753 static void
egg_tray_icon_style_set(GtkWidget * widget,GtkStyle * previous_style)754 egg_tray_icon_style_set (GtkWidget *widget, GtkStyle *previous_style)
755 {
756   /* The default handler resets the background according to the style. We either
757 
758    * use a transparent background or a parent-relative background and ignore the
759 
760    * style background. So, just don't chain up.
761    */
762 }
763 
764 guint
egg_tray_icon_send_message(EggTrayIcon * icon,gint timeout,const gchar * message,gint len)765 egg_tray_icon_send_message (EggTrayIcon *icon,
766 			    gint         timeout,
767 			    const gchar *message,
768 			    gint         len)
769 {
770   guint stamp;
771   Display *xdisplay;
772 
773   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
774   g_return_val_if_fail (timeout >= 0, 0);
775   g_return_val_if_fail (message != NULL, 0);
776 
777   if (icon->manager_window == None)
778     return 0;
779 
780   if (len < 0)
781     len = strlen (message);
782 
783   stamp = icon->stamp++;
784 
785   /* Get ready to send the message */
786   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
787 				      (Window)gtk_plug_get_id (GTK_PLUG (icon)),
788 				      timeout, len, stamp);
789 
790   /* Now to send the actual message */
791   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
792   gdk_error_trap_push ();
793   while (len > 0)
794     {
795       XClientMessageEvent ev;
796 
797       memset (&ev, 0, sizeof (ev));
798       ev.type = ClientMessage;
799       ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
800       ev.format = 8;
801       ev.message_type = XInternAtom (xdisplay,
802 				     "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
803       if (len > 20)
804 	{
805 	  memcpy (&ev.data, message, 20);
806 	  len -= 20;
807 	  message += 20;
808 	}
809       else
810 	{
811 	  memcpy (&ev.data, message, len);
812 	  len = 0;
813 	}
814 
815       XSendEvent (xdisplay,
816 		  icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
817     }
818   gdk_display_sync (gtk_widget_get_display (GTK_WIDGET (icon)));
819 #if GTK_CHECK_VERSION(2, 90, 0)
820   gdk_error_trap_pop_ignored ();
821 #else
822   gdk_error_trap_pop ();
823 #endif
824 
825   return stamp;
826 }
827 
828 void
egg_tray_icon_cancel_message(EggTrayIcon * icon,guint id)829 egg_tray_icon_cancel_message (EggTrayIcon *icon,
830 			      guint        id)
831 {
832   g_return_if_fail (EGG_IS_TRAY_ICON (icon));
833   g_return_if_fail (id > 0);
834   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
835 				      (Window)gtk_plug_get_id (GTK_PLUG (icon)),
836 				      id, 0, 0);
837 }
838 
839 EggTrayIcon *
egg_tray_icon_new_for_screen(GdkScreen * screen,const char * name)840 egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
841 {
842   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
843 
844   return g_object_new (EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
845 }
846 
847 EggTrayIcon*
egg_tray_icon_new(const gchar * name)848 egg_tray_icon_new (const gchar *name)
849 {
850   return g_object_new (EGG_TYPE_TRAY_ICON, "title", name, NULL);
851 }
852 
853 GtkOrientation
egg_tray_icon_get_orientation(EggTrayIcon * icon)854 egg_tray_icon_get_orientation (EggTrayIcon *icon)
855 {
856   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
857 
858   return icon->orientation;
859 }
860