]> Pileus Git - ~andy/gtk/blob - gtk/gtktrayicon-x11.c
Merge branch 'gtk-3-0' into broadway
[~andy/gtk] / gtk / gtktrayicon-x11.c
1 /* gtktrayicon.c
2  * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * This is an implementation of the freedesktop.org "system tray" spec,
22  * http://www.freedesktop.org/wiki/Standards/systemtray-spec
23  */
24
25 #include "config.h"
26
27 #include <math.h>
28 #include <string.h>
29
30 #include "x11/gdkx.h"
31 #include <X11/Xatom.h>
32 #include <cairo-xlib.h>
33
34 #include "gtkintl.h"
35 #include "gtkprivate.h"
36 #include "gtktrayicon.h"
37 #include "gtktestutils.h"
38 #include "gtkdebug.h"
39 #include "gtktypebuiltins.h"
40
41 #define SYSTEM_TRAY_REQUEST_DOCK    0
42 #define SYSTEM_TRAY_BEGIN_MESSAGE   1
43 #define SYSTEM_TRAY_CANCEL_MESSAGE  2
44
45 #define SYSTEM_TRAY_ORIENTATION_HORZ 0
46 #define SYSTEM_TRAY_ORIENTATION_VERT 1
47
48 enum {
49   PROP_0,
50   PROP_ORIENTATION,
51   PROP_FG_COLOR,
52   PROP_ERROR_COLOR,
53   PROP_WARNING_COLOR,
54   PROP_SUCCESS_COLOR,
55   PROP_PADDING
56 };
57
58 struct _GtkTrayIconPrivate
59 {
60   guint stamp;
61   
62   Atom selection_atom;
63   Atom manager_atom;
64   Atom system_tray_opcode_atom;
65   Atom orientation_atom;
66   Atom visual_atom;
67   Atom colors_atom;
68   Atom padding_atom;
69   Window manager_window;
70   GdkVisual *manager_visual;
71   gboolean manager_visual_rgba;
72
73   GtkOrientation orientation;
74   GdkColor fg_color;
75   GdkColor error_color;
76   GdkColor warning_color;
77   GdkColor success_color;
78   gint padding;
79 };
80
81 static void gtk_tray_icon_constructed   (GObject     *object);
82 static void gtk_tray_icon_dispose       (GObject     *object);
83
84 static void gtk_tray_icon_get_property  (GObject     *object,
85                                          guint        prop_id,
86                                          GValue      *value,
87                                          GParamSpec  *pspec);
88
89 static void     gtk_tray_icon_realize   (GtkWidget   *widget);
90 static void     gtk_tray_icon_style_updated (GtkWidget   *widget);
91 static gboolean gtk_tray_icon_delete    (GtkWidget   *widget,
92                                          GdkEventAny *event);
93 static gboolean gtk_tray_icon_draw      (GtkWidget   *widget,
94                                          cairo_t     *cr);
95
96 static void gtk_tray_icon_clear_manager_window     (GtkTrayIcon *icon);
97 static void gtk_tray_icon_update_manager_window    (GtkTrayIcon *icon);
98 static void gtk_tray_icon_manager_window_destroyed (GtkTrayIcon *icon);
99
100 static GdkFilterReturn gtk_tray_icon_manager_filter (GdkXEvent *xevent,
101                                                      GdkEvent  *event,
102                                                      gpointer   user_data);
103
104
105 G_DEFINE_TYPE (GtkTrayIcon, gtk_tray_icon, GTK_TYPE_PLUG)
106
107 static void
108 gtk_tray_icon_class_init (GtkTrayIconClass *class)
109 {
110   GObjectClass *gobject_class = (GObjectClass *)class;
111   GtkWidgetClass *widget_class = (GtkWidgetClass *)class;
112
113   gobject_class->get_property = gtk_tray_icon_get_property;
114   gobject_class->constructed = gtk_tray_icon_constructed;
115   gobject_class->dispose = gtk_tray_icon_dispose;
116
117   widget_class->realize = gtk_tray_icon_realize;
118   widget_class->style_updated = gtk_tray_icon_style_updated;
119   widget_class->delete_event = gtk_tray_icon_delete;
120   widget_class->draw = gtk_tray_icon_draw;
121
122   g_object_class_install_property (gobject_class,
123                                    PROP_ORIENTATION,
124                                    g_param_spec_enum ("orientation",
125                                                       P_("Orientation"),
126                                                       P_("The orientation of the tray"),
127                                                       GTK_TYPE_ORIENTATION,
128                                                       GTK_ORIENTATION_HORIZONTAL,
129                                                       GTK_PARAM_READABLE));
130
131   g_object_class_install_property (gobject_class,
132                                    PROP_FG_COLOR,
133                                    g_param_spec_boxed ("fg-color",
134                                                        P_("Foreground color"),
135                                                        P_("Foreground color for symbolic icons"),
136                                                        GDK_TYPE_COLOR,
137                                                        GTK_PARAM_READABLE));
138
139   g_object_class_install_property (gobject_class,
140                                    PROP_ERROR_COLOR,
141                                    g_param_spec_boxed ("error-color",
142                                                        P_("Error color"),
143                                                        P_("Error color for symbolic icons"),
144                                                        GDK_TYPE_COLOR,
145                                                        GTK_PARAM_READABLE));
146
147   g_object_class_install_property (gobject_class,
148                                    PROP_WARNING_COLOR,
149                                    g_param_spec_boxed ("warning-color",
150                                                        P_("Warning color"),
151                                                        P_("Warning color for symbolic icons"),
152                                                        GDK_TYPE_COLOR,
153                                                        GTK_PARAM_READABLE));
154
155   g_object_class_install_property (gobject_class,
156                                    PROP_SUCCESS_COLOR,
157                                    g_param_spec_boxed ("success-color",
158                                                        P_("Success color"),
159                                                        P_("Success color for symbolic icons"),
160                                                        GDK_TYPE_COLOR,
161                                                        GTK_PARAM_READABLE));
162
163   g_object_class_install_property (gobject_class,
164                                    PROP_PADDING,
165                                    g_param_spec_int ("padding",
166                                                      P_("Padding"),
167                                                      P_("Padding that should be put around icons in the tray"),
168                                                      0,
169                                                      G_MAXINT,
170                                                      0,
171                                                      GTK_PARAM_READABLE));
172
173   g_type_class_add_private (class, sizeof (GtkTrayIconPrivate));
174 }
175
176 static void
177 gtk_tray_icon_init (GtkTrayIcon *icon)
178 {
179   icon->priv = G_TYPE_INSTANCE_GET_PRIVATE (icon, GTK_TYPE_TRAY_ICON,
180                                             GtkTrayIconPrivate);
181
182   icon->priv->stamp = 1;
183   icon->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
184   icon->priv->fg_color.red        = 0x0000;
185   icon->priv->fg_color.green      = 0x0000;
186   icon->priv->fg_color.blue       = 0x0000;
187   icon->priv->error_color.red     = 0xcc00;
188   icon->priv->error_color.green   = 0x0000;
189   icon->priv->error_color.blue    = 0x0000;
190   icon->priv->warning_color.red   = 0xf500;
191   icon->priv->warning_color.green = 0x7900;
192   icon->priv->warning_color.blue  = 0x3e00;
193   icon->priv->success_color.red   = 0x4e00;
194   icon->priv->success_color.green = 0x9a00;
195   icon->priv->success_color.blue  = 0x0600;
196   icon->priv->padding = 0;
197
198   gtk_widget_set_app_paintable (GTK_WIDGET (icon), TRUE);
199   gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
200 }
201
202 static void
203 gtk_tray_icon_constructed (GObject *object)
204 {
205   /* Do setup that depends on the screen; screen has been set at this point */
206
207   GtkTrayIcon *icon = GTK_TRAY_ICON (object);
208   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (object));
209   GdkWindow *root_window = gdk_screen_get_root_window (screen);
210   GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (object));
211   Display *xdisplay = gdk_x11_display_get_xdisplay (display);
212   char buffer[256];
213   
214   g_snprintf (buffer, sizeof (buffer),
215               "_NET_SYSTEM_TRAY_S%d",
216               gdk_screen_get_number (screen));
217
218   icon->priv->selection_atom = XInternAtom (xdisplay, buffer, False);
219   
220   icon->priv->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
221   
222   icon->priv->system_tray_opcode_atom = XInternAtom (xdisplay,
223                                                      "_NET_SYSTEM_TRAY_OPCODE",
224                                                      False);
225
226   icon->priv->orientation_atom = XInternAtom (xdisplay,
227                                               "_NET_SYSTEM_TRAY_ORIENTATION",
228                                               False);
229
230   icon->priv->visual_atom = XInternAtom (xdisplay,
231                                          "_NET_SYSTEM_TRAY_VISUAL",
232                                          False);
233
234   icon->priv->colors_atom = XInternAtom (xdisplay,
235                                          "_NET_SYSTEM_TRAY_COLORS",
236                                          False);
237
238   icon->priv->padding_atom = XInternAtom (xdisplay,
239                                          "_NET_SYSTEM_TRAY_PADDING",
240                                          False);
241
242   /* Add a root window filter so that we get changes on MANAGER */
243   gdk_window_add_filter (root_window,
244                          gtk_tray_icon_manager_filter, icon);
245
246   gtk_tray_icon_update_manager_window (icon);
247 }
248
249 static void
250 gtk_tray_icon_clear_manager_window (GtkTrayIcon *icon)
251 {
252   GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (icon));
253
254   if (icon->priv->manager_window != None)
255     {
256       GdkWindow *gdkwin;
257
258       gdkwin = gdk_x11_window_lookup_for_display (display,
259                                                   icon->priv->manager_window);
260
261       gdk_window_remove_filter (gdkwin, gtk_tray_icon_manager_filter, icon);
262
263       icon->priv->manager_window = None;
264       icon->priv->manager_visual = NULL;
265     }
266 }
267
268 static void
269 gtk_tray_icon_dispose (GObject *object)
270 {
271   GtkTrayIcon *icon = GTK_TRAY_ICON (object);
272   GtkWidget *widget = GTK_WIDGET (object);
273   GdkWindow *root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
274
275   gtk_tray_icon_clear_manager_window (icon);
276
277   gdk_window_remove_filter (root_window, gtk_tray_icon_manager_filter, icon);
278
279   G_OBJECT_CLASS (gtk_tray_icon_parent_class)->dispose (object);
280 }
281
282 static void
283 gtk_tray_icon_get_property (GObject    *object,
284                             guint       prop_id,
285                             GValue     *value,
286                             GParamSpec *pspec)
287 {
288   GtkTrayIcon *icon = GTK_TRAY_ICON (object);
289
290   switch (prop_id)
291     {
292     case PROP_ORIENTATION:
293       g_value_set_enum (value, icon->priv->orientation);
294       break;
295     case PROP_FG_COLOR:
296       g_value_set_boxed (value, &icon->priv->fg_color);
297       break;
298     case PROP_ERROR_COLOR:
299       g_value_set_boxed (value, &icon->priv->error_color);
300       break;
301     case PROP_WARNING_COLOR:
302       g_value_set_boxed (value, &icon->priv->warning_color);
303       break;
304     case PROP_SUCCESS_COLOR:
305       g_value_set_boxed (value, &icon->priv->success_color);
306       break;
307     case PROP_PADDING:
308       g_value_set_int (value, icon->priv->padding);
309       break;
310     default:
311       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
312       break;
313     }
314 }
315
316 static gboolean
317 gtk_tray_icon_draw (GtkWidget *widget, 
318                     cairo_t   *cr)
319 {
320   GtkTrayIcon *icon = GTK_TRAY_ICON (widget);
321   GtkWidget *focus_child;
322   GdkWindow *window;
323   gint border_width;
324   gboolean retval = FALSE;
325   cairo_surface_t *target;
326
327   window = gtk_widget_get_window (widget);
328   target = cairo_get_group_target (cr);
329
330   if (icon->priv->manager_visual_rgba ||
331       cairo_surface_get_type (target) != CAIRO_SURFACE_TYPE_XLIB ||
332       cairo_xlib_surface_get_drawable (target) != GDK_WINDOW_XID (window))
333     {
334       /* Clear to transparent */
335       cairo_set_source_rgba (cr, 0, 0, 0, 0);
336       cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
337       cairo_paint (cr);
338     }
339   else
340     {
341       GdkRectangle clip;
342
343       if (gdk_cairo_get_clip_rectangle (cr, &clip))
344         {
345           /* Clear to parent-relative pixmap
346            * We need to use direct X access here because GDK doesn't know about
347            * the parent realtive pixmap. */
348           cairo_surface_flush (target);
349
350           XClearArea (GDK_WINDOW_XDISPLAY (window),
351                       GDK_WINDOW_XID (window),
352                       clip.x, clip.y,
353                       clip.width, clip.height,
354                       False);
355           cairo_surface_mark_dirty_rectangle (target, 
356                                               clip.x, clip.y,
357                                               clip.width, clip.height);
358         }
359     }
360
361   if (GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->draw)
362     retval = GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->draw (widget, cr);
363
364   focus_child = gtk_container_get_focus_child (GTK_CONTAINER (widget));
365   if (focus_child && gtk_widget_has_focus (focus_child))
366     {
367       GtkStyleContext *context;
368       GtkStateFlags state;
369
370       border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
371       context = gtk_widget_get_style_context (widget);
372       state = gtk_widget_get_state_flags (widget);
373
374       gtk_style_context_save (context);
375       gtk_style_context_set_state (context, state);
376
377       gtk_render_focus (context, cr, border_width, border_width,
378                         gtk_widget_get_allocated_width (widget) - 2 * border_width,
379                         gtk_widget_get_allocated_height (widget) - 2 * border_width);
380
381       gtk_style_context_restore (context);
382     }
383
384   return retval;
385 }
386
387 static void
388 gtk_tray_icon_get_orientation_property (GtkTrayIcon *icon)
389 {
390   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
391   GdkDisplay *display = gdk_screen_get_display (screen);
392   Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
393
394   Atom type;
395   int format;
396   union {
397         gulong *prop;
398         guchar *prop_ch;
399   } prop = { NULL };
400   gulong nitems;
401   gulong bytes_after;
402   int error, result;
403
404   g_assert (icon->priv->manager_window != None);
405   
406   gdk_error_trap_push ();
407   type = None;
408   result = XGetWindowProperty (xdisplay,
409                                icon->priv->manager_window,
410                                icon->priv->orientation_atom,
411                                0, G_MAXLONG, FALSE,
412                                XA_CARDINAL,
413                                &type, &format, &nitems,
414                                &bytes_after, &(prop.prop_ch));
415   error = gdk_error_trap_pop ();
416
417   if (error || result != Success)
418     return;
419
420   if (type == XA_CARDINAL && nitems == 1 && format == 32)
421     {
422       GtkOrientation orientation;
423
424       orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
425                                         GTK_ORIENTATION_HORIZONTAL :
426                                         GTK_ORIENTATION_VERTICAL;
427
428       if (icon->priv->orientation != orientation)
429         {
430           icon->priv->orientation = orientation;
431
432           g_object_notify (G_OBJECT (icon), "orientation");
433         }
434     }
435
436   if (type != None)
437     XFree (prop.prop);
438 }
439
440 static void
441 gtk_tray_icon_get_visual_property (GtkTrayIcon *icon)
442 {
443   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
444   GdkDisplay *display = gdk_screen_get_display (screen);
445   Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
446
447   Atom type;
448   int format;
449   union {
450         gulong *prop;
451         guchar *prop_ch;
452   } prop = { NULL };
453   gulong nitems;
454   gulong bytes_after;
455   int error, result;
456   GdkVisual *visual;
457   gint red_prec;
458   gint green_prec;
459   gint blue_prec;
460
461   g_assert (icon->priv->manager_window != None);
462
463   gdk_error_trap_push ();
464   type = None;
465   result = XGetWindowProperty (xdisplay,
466                                icon->priv->manager_window,
467                                icon->priv->visual_atom,
468                                0, G_MAXLONG, FALSE,
469                                XA_VISUALID,
470                                &type, &format, &nitems,
471                                &bytes_after, &(prop.prop_ch));
472   error = gdk_error_trap_pop ();
473
474   visual = NULL;
475
476   if (!error && result == Success &&
477       type == XA_VISUALID && nitems == 1 && format == 32)
478     {
479       VisualID visual_id = prop.prop[0];
480       visual = gdk_x11_screen_lookup_visual (screen, visual_id);
481     }
482
483   gdk_visual_get_red_pixel_details (visual, NULL, NULL, &red_prec);
484   gdk_visual_get_green_pixel_details (visual, NULL, NULL, &green_prec);
485   gdk_visual_get_blue_pixel_details (visual, NULL, NULL, &blue_prec);
486
487   icon->priv->manager_visual = visual;
488   icon->priv->manager_visual_rgba = visual != NULL &&
489     (red_prec + blue_prec + green_prec < gdk_visual_get_depth (visual));
490
491   /* For the background-relative hack we use when we aren't using a real RGBA
492    * visual, we can't be double-buffered */
493   gtk_widget_set_double_buffered (GTK_WIDGET (icon), icon->priv->manager_visual_rgba);
494
495   if (type != None)
496     XFree (prop.prop);
497 }
498
499 static void
500 gtk_tray_icon_get_colors_property (GtkTrayIcon *icon)
501 {
502   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
503   GdkDisplay *display = gdk_screen_get_display (screen);
504   Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
505
506   Atom type;
507   int format;
508   union {
509         gulong *prop;
510         guchar *prop_ch;
511   } prop = { NULL };
512   gulong nitems;
513   gulong bytes_after;
514   int error, result;
515
516   g_assert (icon->priv->manager_window != None);
517
518   gdk_error_trap_push ();
519   type = None;
520   result = XGetWindowProperty (xdisplay,
521                                icon->priv->manager_window,
522                                icon->priv->colors_atom,
523                                0, G_MAXLONG, FALSE,
524                                XA_CARDINAL,
525                                &type, &format, &nitems,
526                                &bytes_after, &(prop.prop_ch));
527   error = gdk_error_trap_pop ();
528
529   if (error || result != Success)
530     return;
531
532   if (type == XA_CARDINAL && nitems == 12 && format == 32)
533     {
534       GdkColor color;
535
536       g_object_freeze_notify (G_OBJECT (icon));
537
538       color.red = prop.prop[0];
539       color.green = prop.prop[1];
540       color.blue = prop.prop[2];
541
542       if (!gdk_color_equal (&icon->priv->fg_color, &color))
543         {
544           icon->priv->fg_color = color;
545
546           g_object_notify (G_OBJECT (icon), "fg-color");
547         }
548
549       color.red = prop.prop[3];
550       color.green = prop.prop[4];
551       color.blue = prop.prop[5];
552
553       if (!gdk_color_equal (&icon->priv->error_color, &color))
554         {
555           icon->priv->error_color = color;
556
557           g_object_notify (G_OBJECT (icon), "error-color");
558         }
559
560       color.red = prop.prop[6];
561       color.green = prop.prop[7];
562       color.blue = prop.prop[8];
563
564       if (!gdk_color_equal (&icon->priv->warning_color, &color))
565         {
566           icon->priv->warning_color = color;
567
568           g_object_notify (G_OBJECT (icon), "warning-color");
569         }
570
571       color.red = prop.prop[9];
572       color.green = prop.prop[10];
573       color.blue = prop.prop[11];
574
575       if (!gdk_color_equal (&icon->priv->success_color, &color))
576         {
577           icon->priv->success_color = color;
578
579           g_object_notify (G_OBJECT (icon), "success-color");
580         }
581
582       g_object_thaw_notify (G_OBJECT (icon));
583     }
584
585   if (type != None)
586     XFree (prop.prop);
587 }
588
589 static void
590 gtk_tray_icon_get_padding_property (GtkTrayIcon *icon)
591 {
592   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
593   GdkDisplay *display = gdk_screen_get_display (screen);
594   Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
595
596   Atom type;
597   int format;
598   union {
599         gulong *prop;
600         guchar *prop_ch;
601   } prop = { NULL };
602   gulong nitems;
603   gulong bytes_after;
604   int error, result;
605
606   g_assert (icon->priv->manager_window != None);
607
608   gdk_error_trap_push ();
609   type = None;
610   result = XGetWindowProperty (xdisplay,
611                                icon->priv->manager_window,
612                                icon->priv->padding_atom,
613                                0, G_MAXLONG, FALSE,
614                                XA_CARDINAL,
615                                &type, &format, &nitems,
616                                &bytes_after, &(prop.prop_ch));
617   error = gdk_error_trap_pop ();
618
619   if (!error && result == Success &&
620       type == XA_CARDINAL && nitems == 1 && format == 32)
621     {
622       gint padding;
623
624       padding = prop.prop[0];
625
626       if (icon->priv->padding != padding)
627         {
628           icon->priv->padding = padding;
629
630           g_object_notify (G_OBJECT (icon), "padding");
631         }
632     }
633
634   if (type != None)
635     XFree (prop.prop);
636 }
637
638 static GdkFilterReturn
639 gtk_tray_icon_manager_filter (GdkXEvent *xevent,
640                               GdkEvent  *event,
641                               gpointer   user_data)
642 {
643   GtkTrayIcon *icon = user_data;
644   XEvent *xev = (XEvent *)xevent;
645
646   if (xev->xany.type == ClientMessage &&
647       xev->xclient.message_type == icon->priv->manager_atom &&
648       xev->xclient.data.l[1] == icon->priv->selection_atom)
649     {
650       GTK_NOTE (PLUGSOCKET,
651                 g_print ("GtkStatusIcon %p: tray manager appeared\n", icon));
652
653       gtk_tray_icon_update_manager_window (icon);
654     }
655   else if (xev->xany.window == icon->priv->manager_window)
656     {
657       if (xev->xany.type == PropertyNotify &&
658           xev->xproperty.atom == icon->priv->orientation_atom)
659         {
660           GTK_NOTE (PLUGSOCKET,
661                     g_print ("GtkStatusIcon %p: got PropertyNotify on manager window for orientation atom\n", icon));
662
663           gtk_tray_icon_get_orientation_property (icon);
664         }
665       else if (xev->xany.type == PropertyNotify &&
666                xev->xproperty.atom == icon->priv->colors_atom)
667         {
668           GTK_NOTE (PLUGSOCKET,
669                     g_print ("GtkStatusIcon %p: got PropertyNotify on manager window for colors atom\n", icon));
670
671           gtk_tray_icon_get_colors_property (icon);
672         }
673       else if (xev->xany.type == PropertyNotify &&
674                xev->xproperty.atom == icon->priv->padding_atom)
675         {
676           gtk_tray_icon_get_padding_property (icon);
677         }
678       else if (xev->xany.type == DestroyNotify)
679         {
680           GTK_NOTE (PLUGSOCKET,
681                     g_print ("GtkStatusIcon %p: got DestroyNotify for manager window\n", icon));
682
683           gtk_tray_icon_manager_window_destroyed (icon);
684         }
685       else
686         GTK_NOTE (PLUGSOCKET,
687                   g_print ("GtkStatusIcon %p: got other message on manager window\n", icon));
688     }
689   
690   return GDK_FILTER_CONTINUE;
691 }
692
693 static void
694 gtk_tray_icon_send_manager_message (GtkTrayIcon *icon,
695                                     long         message,
696                                     Window       window,
697                                     long         data1,
698                                     long         data2,
699                                     long         data3)
700 {
701   GtkWidget *widget;
702   XClientMessageEvent ev;
703   Display *display;
704
705   widget = GTK_WIDGET (icon);
706
707   memset (&ev, 0, sizeof (ev));
708   ev.type = ClientMessage;
709   ev.window = window;
710   ev.message_type = icon->priv->system_tray_opcode_atom;
711   ev.format = 32;
712   ev.data.l[0] = gdk_x11_get_server_time (gtk_widget_get_window (widget));
713   ev.data.l[1] = message;
714   ev.data.l[2] = data1;
715   ev.data.l[3] = data2;
716   ev.data.l[4] = data3;
717
718   display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (widget));
719
720   gdk_error_trap_push ();
721   XSendEvent (display,
722               icon->priv->manager_window, False, NoEventMask, (XEvent *)&ev);
723   gdk_error_trap_pop_ignored ();
724 }
725
726 static void
727 gtk_tray_icon_send_dock_request (GtkTrayIcon *icon)
728 {
729   GTK_NOTE (PLUGSOCKET,
730             g_print ("GtkStatusIcon %p: sending dock request to manager window %lx\n",
731                      icon, (gulong) icon->priv->manager_window));
732
733   gtk_tray_icon_send_manager_message (icon,
734                                       SYSTEM_TRAY_REQUEST_DOCK,
735                                       icon->priv->manager_window,
736                                       gtk_plug_get_id (GTK_PLUG (icon)),
737                                       0, 0);
738 }
739
740 static void
741 gtk_tray_icon_update_manager_window (GtkTrayIcon *icon)
742 {
743   GtkWidget *widget = GTK_WIDGET (icon);
744   GdkScreen *screen = gtk_widget_get_screen (widget);
745   GdkDisplay *display = gdk_screen_get_display (screen);
746   Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
747
748   GTK_NOTE (PLUGSOCKET,
749             g_print ("GtkStatusIcon %p: updating tray icon manager window, current manager window: %lx\n",
750                      icon, (gulong) icon->priv->manager_window));
751
752   if (icon->priv->manager_window != None)
753     return;
754
755   GTK_NOTE (PLUGSOCKET,
756             g_print ("GtkStatusIcon %p: trying to find manager window\n", icon));
757
758   XGrabServer (xdisplay);
759   
760   icon->priv->manager_window = XGetSelectionOwner (xdisplay,
761                                                    icon->priv->selection_atom);
762
763   if (icon->priv->manager_window != None)
764     XSelectInput (xdisplay,
765                   icon->priv->manager_window, StructureNotifyMask|PropertyChangeMask);
766
767   XUngrabServer (xdisplay);
768   XFlush (xdisplay);
769   
770   if (icon->priv->manager_window != None)
771     {
772       GdkWindow *gdkwin;
773
774       GTK_NOTE (PLUGSOCKET,
775         g_print ("GtkStatusIcon %p: is being managed by window %lx\n",
776                  icon, (gulong) icon->priv->manager_window));
777
778       gdkwin = gdk_x11_window_lookup_for_display (display,
779                                                   icon->priv->manager_window);
780
781       gdk_window_add_filter (gdkwin, gtk_tray_icon_manager_filter, icon);
782
783       gtk_tray_icon_get_orientation_property (icon);
784       gtk_tray_icon_get_visual_property (icon);
785       gtk_tray_icon_get_colors_property (icon);
786       gtk_tray_icon_get_padding_property (icon);
787
788       if (gtk_widget_get_realized (GTK_WIDGET (icon)))
789         {
790           if ((icon->priv->manager_visual == NULL &&
791                gtk_widget_get_visual (widget) == gdk_screen_get_system_visual (screen)) ||
792               (icon->priv->manager_visual == gtk_widget_get_visual (widget)))
793             {
794               /* Already have the right visual, can just dock
795                */
796               gtk_tray_icon_send_dock_request (icon);
797             }
798           else
799             {
800               /* Need to re-realize the widget to get the right visual
801                */
802               gtk_widget_hide (widget);
803               gtk_widget_unrealize (widget);
804               gtk_widget_show (widget);
805             }
806         }
807     }
808   else
809     GTK_NOTE (PLUGSOCKET,
810               g_print ("GtkStatusIcon %p: no tray manager found\n", icon));
811 }
812
813 static void
814 gtk_tray_icon_manager_window_destroyed (GtkTrayIcon *icon)
815 {
816   g_return_if_fail (icon->priv->manager_window != None);
817
818   GTK_NOTE (PLUGSOCKET,
819             g_print ("GtkStatusIcon %p: tray manager window destroyed\n", icon));
820
821   gtk_tray_icon_clear_manager_window (icon);
822 }
823
824 static gboolean
825 gtk_tray_icon_delete (GtkWidget   *widget,
826                       GdkEventAny *event)
827 {
828 #ifdef G_ENABLE_DEBUG
829   GtkTrayIcon *icon = GTK_TRAY_ICON (widget);
830 #endif
831
832   GTK_NOTE (PLUGSOCKET,
833             g_print ("GtkStatusIcon %p: delete notify, tray manager window %lx\n",
834                      icon, (gulong) icon->priv->manager_window));
835
836   /* A bug in X server versions up to x.org 1.5.0 means that:
837    * XFixesChangeSaveSet(...., SaveSetRoot, SaveSetUnmap) doesn't work properly
838    * and we'll left mapped in a separate toplevel window if the tray is destroyed.
839    * For simplicity just get rid of our X window and start over.
840    */
841   gtk_widget_hide (widget);
842   gtk_widget_unrealize (widget);
843   gtk_widget_show (widget);
844
845   /* Handled it, don't destroy the tray icon */
846   return TRUE;
847 }
848
849 static void
850 gtk_tray_icon_set_visual (GtkTrayIcon *icon)
851 {
852   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
853   GdkVisual *visual = icon->priv->manager_visual;
854
855   /* To avoid uncertainty about colormaps, _NET_SYSTEM_TRAY_VISUAL is supposed
856    * to be either the screen default visual or a TrueColor visual; ignore it
857    * if it is something else
858    */
859   if (visual && gdk_visual_get_visual_type (visual) != GDK_VISUAL_TRUE_COLOR)
860     visual = NULL;
861
862   if (visual == NULL)
863     visual = gdk_screen_get_system_visual (screen);
864
865   gtk_widget_set_visual (GTK_WIDGET (icon), visual);
866 }
867
868 static void
869 gtk_tray_icon_realize (GtkWidget *widget)
870 {
871   GtkTrayIcon *icon = GTK_TRAY_ICON (widget);
872   GdkWindow *window;
873
874   /* Set our visual before realizing */
875   gtk_tray_icon_set_visual (icon);
876
877   GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->realize (widget);
878   window = gtk_widget_get_window (widget);
879   if (icon->priv->manager_visual_rgba)
880     {
881       /* Set a transparent background */
882       GdkColor transparent = { 0, 0, 0, 0 }; /* Only pixel=0 matters */
883       gdk_window_set_background (window, &transparent);
884     }
885   else
886     {
887       /* Set a parent-relative background pixmap */
888       gdk_window_set_background_pattern (window, NULL);
889     }
890
891   GTK_NOTE (PLUGSOCKET,
892             g_print ("GtkStatusIcon %p: realized, window: %lx, socket window: %lx\n",
893                      widget,
894                      (gulong) GDK_WINDOW_XID (window),
895                      gtk_plug_get_socket_window (GTK_PLUG (icon)) ?
896                      (gulong) GDK_WINDOW_XID (gtk_plug_get_socket_window (GTK_PLUG (icon))) : 0UL));
897
898   if (icon->priv->manager_window != None)
899     gtk_tray_icon_send_dock_request (icon);
900 }
901
902 static void
903 gtk_tray_icon_style_updated (GtkWidget   *widget)
904 {
905   /* The default handler resets the background according to the style. We either
906    * use a transparent background or a parent-relative background and ignore the
907    * style background. So, just don't chain up.
908    */
909 }
910
911 guint
912 _gtk_tray_icon_send_message (GtkTrayIcon *icon,
913                              gint         timeout,
914                              const gchar *message,
915                              gint         len)
916 {
917   guint stamp;
918   Display *xdisplay;
919  
920   g_return_val_if_fail (GTK_IS_TRAY_ICON (icon), 0);
921   g_return_val_if_fail (timeout >= 0, 0);
922   g_return_val_if_fail (message != NULL, 0);
923
924   if (icon->priv->manager_window == None)
925     return 0;
926
927   if (len < 0)
928     len = strlen (message);
929
930   stamp = icon->priv->stamp++;
931   
932   /* Get ready to send the message */
933   gtk_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
934                                       (Window)gtk_plug_get_id (GTK_PLUG (icon)),
935                                       timeout, len, stamp);
936
937   /* Now to send the actual message */
938   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
939   gdk_error_trap_push ();
940   while (len > 0)
941     {
942       XClientMessageEvent ev;
943
944       memset (&ev, 0, sizeof (ev));
945       ev.type = ClientMessage;
946       ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
947       ev.format = 8;
948       ev.message_type = XInternAtom (xdisplay,
949                                      "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
950       if (len > 20)
951         {
952           memcpy (&ev.data, message, 20);
953           len -= 20;
954           message += 20;
955         }
956       else
957         {
958           memcpy (&ev.data, message, len);
959           len = 0;
960         }
961
962       XSendEvent (xdisplay,
963                   icon->priv->manager_window, False,
964                   StructureNotifyMask, (XEvent *)&ev);
965     }
966   gdk_error_trap_pop_ignored ();
967
968   return stamp;
969 }
970
971 void
972 _gtk_tray_icon_cancel_message (GtkTrayIcon *icon,
973                                guint        id)
974 {
975   g_return_if_fail (GTK_IS_TRAY_ICON (icon));
976   g_return_if_fail (id > 0);
977   
978   gtk_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
979                                       (Window)gtk_plug_get_id (GTK_PLUG (icon)),
980                                       id, 0, 0);
981 }
982
983 GtkTrayIcon *
984 _gtk_tray_icon_new_for_screen (GdkScreen  *screen, 
985                                const gchar *name)
986 {
987   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
988
989   return g_object_new (GTK_TYPE_TRAY_ICON, 
990                        "screen", screen, 
991                        "title", name, 
992                        NULL);
993 }
994
995 GtkTrayIcon*
996 _gtk_tray_icon_new (const gchar *name)
997 {
998   return g_object_new (GTK_TYPE_TRAY_ICON, 
999                        "title", name, 
1000                        NULL);
1001 }
1002
1003 GtkOrientation
1004 _gtk_tray_icon_get_orientation (GtkTrayIcon *icon)
1005 {
1006   g_return_val_if_fail (GTK_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
1007
1008   return icon->priv->orientation;
1009 }
1010
1011 gint
1012 _gtk_tray_icon_get_padding (GtkTrayIcon *icon)
1013 {
1014   g_return_val_if_fail (GTK_IS_TRAY_ICON (icon), 0);
1015
1016   return icon->priv->padding;
1017 }