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