gtktrayicon-x11.c 28.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/* gtktrayicon.c
 * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

20 21 22 23 24
/*
 * This is an implementation of the freedesktop.org "system tray" spec,
 * http://www.freedesktop.org/wiki/Standards/systemtray-spec
 */

25
#include "config.h"
26
#include <math.h>
27 28 29 30 31 32 33 34
#include <string.h>

#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtktrayicon.h"

#include "x11/gdkx.h"
#include <X11/Xatom.h>
35
#include <cairo-xlib.h>
36 37 38 39 40 41 42 43 44 45

#define SYSTEM_TRAY_REQUEST_DOCK    0
#define SYSTEM_TRAY_BEGIN_MESSAGE   1
#define SYSTEM_TRAY_CANCEL_MESSAGE  2

#define SYSTEM_TRAY_ORIENTATION_HORZ 0
#define SYSTEM_TRAY_ORIENTATION_VERT 1

enum {
  PROP_0,
46 47 48 49
  PROP_ORIENTATION,
  PROP_FG_COLOR,
  PROP_ERROR_COLOR,
  PROP_WARNING_COLOR,
50 51
  PROP_SUCCESS_COLOR,
  PROP_PADDING
52 53 54 55 56 57 58 59 60 61
};

struct _GtkTrayIconPrivate
{
  guint stamp;
  
  Atom selection_atom;
  Atom manager_atom;
  Atom system_tray_opcode_atom;
  Atom orientation_atom;
62
  Atom visual_atom;
63
  Atom colors_atom;
64
  Atom padding_atom;
65
  Window manager_window;
66 67
  GdkVisual *manager_visual;
  gboolean manager_visual_rgba;
68 69

  GtkOrientation orientation;
70 71 72 73
  GdkColor fg_color;
  GdkColor error_color;
  GdkColor warning_color;
  GdkColor success_color;
74
  gint padding;
75
};
76 77 78 79

static void gtk_tray_icon_constructed   (GObject     *object);
static void gtk_tray_icon_dispose       (GObject     *object);

80 81 82 83 84 85
static void gtk_tray_icon_get_property  (GObject     *object,
				 	 guint        prop_id,
					 GValue      *value,
					 GParamSpec  *pspec);

static void     gtk_tray_icon_realize   (GtkWidget   *widget);
86 87
static void     gtk_tray_icon_style_set (GtkWidget   *widget,
					 GtkStyle    *previous_style);
88 89
static gboolean gtk_tray_icon_delete    (GtkWidget   *widget,
					 GdkEventAny *event);
90 91
static gboolean gtk_tray_icon_draw      (GtkWidget   *widget,
                                         cairo_t     *cr);
92

93
static void gtk_tray_icon_clear_manager_window     (GtkTrayIcon *icon);
94
static void gtk_tray_icon_update_manager_window    (GtkTrayIcon *icon);
95 96
static void gtk_tray_icon_manager_window_destroyed (GtkTrayIcon *icon);

97 98 99 100 101
static GdkFilterReturn gtk_tray_icon_manager_filter (GdkXEvent *xevent,
						     GdkEvent  *event,
						     gpointer   user_data);


Matthias Clasen's avatar
Matthias Clasen committed
102
G_DEFINE_TYPE (GtkTrayIcon, gtk_tray_icon, GTK_TYPE_PLUG)
103 104 105 106 107 108 109 110

static void
gtk_tray_icon_class_init (GtkTrayIconClass *class)
{
  GObjectClass *gobject_class = (GObjectClass *)class;
  GtkWidgetClass *widget_class = (GtkWidgetClass *)class;

  gobject_class->get_property = gtk_tray_icon_get_property;
111 112
  gobject_class->constructed = gtk_tray_icon_constructed;
  gobject_class->dispose = gtk_tray_icon_dispose;
113

114 115
  widget_class->realize = gtk_tray_icon_realize;
  widget_class->style_set = gtk_tray_icon_style_set;
116
  widget_class->delete_event = gtk_tray_icon_delete;
117
  widget_class->draw = gtk_tray_icon_draw;
118 119 120 121 122 123 124 125 126 127

  g_object_class_install_property (gobject_class,
				   PROP_ORIENTATION,
				   g_param_spec_enum ("orientation",
						      P_("Orientation"),
						      P_("The orientation of the tray"),
						      GTK_TYPE_ORIENTATION,
						      GTK_ORIENTATION_HORIZONTAL,
						      GTK_PARAM_READABLE));

128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
  g_object_class_install_property (gobject_class,
                                   PROP_FG_COLOR,
                                   g_param_spec_boxed ("fg-color",
                                                       P_("Foreground color"),
                                                       P_("Foreground color for symbolic icons"),
                                                       GDK_TYPE_COLOR,
                                                       GTK_PARAM_READABLE));

  g_object_class_install_property (gobject_class,
                                   PROP_ERROR_COLOR,
                                   g_param_spec_boxed ("error-color",
                                                       P_("Error color"),
                                                       P_("Error color for symbolic icons"),
                                                       GDK_TYPE_COLOR,
                                                       GTK_PARAM_READABLE));

  g_object_class_install_property (gobject_class,
                                   PROP_WARNING_COLOR,
                                   g_param_spec_boxed ("warning-color",
                                                       P_("Warning color"),
                                                       P_("Warning color for symbolic icons"),
                                                       GDK_TYPE_COLOR,
                                                       GTK_PARAM_READABLE));

  g_object_class_install_property (gobject_class,
                                   PROP_SUCCESS_COLOR,
                                   g_param_spec_boxed ("success-color",
                                                       P_("Success color"),
                                                       P_("Success color for symbolic icons"),
                                                       GDK_TYPE_COLOR,
                                                       GTK_PARAM_READABLE));

160 161 162 163 164 165 166 167 168 169
  g_object_class_install_property (gobject_class,
				   PROP_PADDING,
				   g_param_spec_int ("padding",
						     P_("Padding"),
						     P_("Padding that should be put around icons in the tray"),
						     0,
                                                     G_MAXINT,
                                                     0,
						     GTK_PARAM_READABLE));

170 171 172 173 174 175 176 177
  g_type_class_add_private (class, sizeof (GtkTrayIconPrivate));
}

static void
gtk_tray_icon_init (GtkTrayIcon *icon)
{
  icon->priv = G_TYPE_INSTANCE_GET_PRIVATE (icon, GTK_TYPE_TRAY_ICON,
					    GtkTrayIconPrivate);
178

179 180
  icon->priv->stamp = 1;
  icon->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
181 182 183 184 185 186 187 188 189 190 191 192
  icon->priv->fg_color.red        = 0x0000;
  icon->priv->fg_color.green      = 0x0000;
  icon->priv->fg_color.blue       = 0x0000;
  icon->priv->error_color.red     = 0xcc00;
  icon->priv->error_color.green   = 0x0000;
  icon->priv->error_color.blue    = 0x0000;
  icon->priv->warning_color.red   = 0xf500;
  icon->priv->warning_color.green = 0x7900;
  icon->priv->warning_color.blue  = 0x3e00;
  icon->priv->success_color.red   = 0x4e00;
  icon->priv->success_color.green = 0x9a00;
  icon->priv->success_color.blue  = 0x0600;
193
  icon->priv->padding = 0;
194 195

  gtk_widget_set_app_paintable (GTK_WIDGET (icon), TRUE);
196 197 198
  gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
}

199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
static void
gtk_tray_icon_constructed (GObject *object)
{
  /* Do setup that depends on the screen; screen has been set at this point */

  GtkTrayIcon *icon = GTK_TRAY_ICON (object);
  GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (object));
  GdkWindow *root_window = gdk_screen_get_root_window (screen);
  GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (object));
  Display *xdisplay = gdk_x11_display_get_xdisplay (display);
  char buffer[256];
  
  g_snprintf (buffer, sizeof (buffer),
	      "_NET_SYSTEM_TRAY_S%d",
	      gdk_screen_get_number (screen));

  icon->priv->selection_atom = XInternAtom (xdisplay, buffer, False);
  
  icon->priv->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
  
  icon->priv->system_tray_opcode_atom = XInternAtom (xdisplay,
						     "_NET_SYSTEM_TRAY_OPCODE",
						     False);

  icon->priv->orientation_atom = XInternAtom (xdisplay,
					      "_NET_SYSTEM_TRAY_ORIENTATION",
					      False);

  icon->priv->visual_atom = XInternAtom (xdisplay,
					 "_NET_SYSTEM_TRAY_VISUAL",
					 False);

231 232 233 234
  icon->priv->colors_atom = XInternAtom (xdisplay,
                                         "_NET_SYSTEM_TRAY_COLORS",
                                         False);

235 236 237 238
  icon->priv->padding_atom = XInternAtom (xdisplay,
					 "_NET_SYSTEM_TRAY_PADDING",
					 False);

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
  /* Add a root window filter so that we get changes on MANAGER */
  gdk_window_add_filter (root_window,
			 gtk_tray_icon_manager_filter, icon);

  gtk_tray_icon_update_manager_window (icon);
}

static void
gtk_tray_icon_clear_manager_window (GtkTrayIcon *icon)
{
  GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (icon));

  if (icon->priv->manager_window != None)
    {
      GdkWindow *gdkwin;

      gdkwin = gdk_window_lookup_for_display (display,
                                              icon->priv->manager_window);

      gdk_window_remove_filter (gdkwin, gtk_tray_icon_manager_filter, icon);

      icon->priv->manager_window = None;
      icon->priv->manager_visual = NULL;
    }
}

static void
gtk_tray_icon_dispose (GObject *object)
{
  GtkTrayIcon *icon = GTK_TRAY_ICON (object);
  GtkWidget *widget = GTK_WIDGET (object);
  GdkWindow *root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));

  gtk_tray_icon_clear_manager_window (icon);

  gdk_window_remove_filter (root_window, gtk_tray_icon_manager_filter, icon);
}

277 278 279 280 281 282 283 284 285 286 287 288 289
static void
gtk_tray_icon_get_property (GObject    *object,
			    guint       prop_id,
			    GValue     *value,
			    GParamSpec *pspec)
{
  GtkTrayIcon *icon = GTK_TRAY_ICON (object);

  switch (prop_id)
    {
    case PROP_ORIENTATION:
      g_value_set_enum (value, icon->priv->orientation);
      break;
290 291 292 293 294 295 296 297 298 299 300 301
    case PROP_FG_COLOR:
      g_value_set_boxed (value, &icon->priv->fg_color);
      break;
    case PROP_ERROR_COLOR:
      g_value_set_boxed (value, &icon->priv->error_color);
      break;
    case PROP_WARNING_COLOR:
      g_value_set_boxed (value, &icon->priv->warning_color);
      break;
    case PROP_SUCCESS_COLOR:
      g_value_set_boxed (value, &icon->priv->success_color);
      break;
302 303 304
    case PROP_PADDING:
      g_value_set_int (value, icon->priv->padding);
      break;
305 306 307 308 309 310
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

311
static gboolean
312 313
gtk_tray_icon_draw (GtkWidget *widget, 
		    cairo_t   *cr)
314
{
315
  GtkTrayIcon *icon = GTK_TRAY_ICON (widget);
316
  GtkWidget *focus_child;
317
  GdkWindow *window;
318
  gint border_width;
319
  gboolean retval = FALSE;
320
  cairo_surface_t *target;
321

322
  window = gtk_widget_get_window (widget);
323
  target = cairo_get_group_target (cr);
324

325 326 327
  if (icon->priv->manager_visual_rgba ||
      cairo_surface_get_type (target) != CAIRO_SURFACE_TYPE_XLIB ||
      cairo_xlib_surface_get_drawable (target) != GDK_WINDOW_XID (window))
328 329 330 331
    {
      /* Clear to transparent */
      cairo_set_source_rgba (cr, 0, 0, 0, 0);
      cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
332
      cairo_paint (cr);
333 334 335
    }
  else
    {
336 337 338
      double x1, y1, x2, y2;

      cairo_clip_extents (cr, &x1, &x2, &y1, &y2);
Benjamin Otte's avatar
Benjamin Otte committed
339 340 341
      /* Clear to parent-relative pixmap
       * We need to use direct X access here because GDK doesn't know about
       * the parent realtive pixmap. */
342 343
      cairo_surface_flush (target);

Benjamin Otte's avatar
Benjamin Otte committed
344 345
      XClearArea (GDK_WINDOW_XDISPLAY (window),
                  GDK_WINDOW_XID (window),
346 347
                  floor (x1), floor (y1),
                  ceil (x2) - floor (x1), ceil (y2) - floor (y1),
Benjamin Otte's avatar
Benjamin Otte committed
348
                  False);
349 350 351 352
      cairo_surface_mark_dirty_rectangle (target, 
                                          floor (x1), floor (y1),
                                          ceil (x2) - floor (x1),
                                          ceil (y2) - floor (y1));
353
    }
354

355 356
  if (GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->draw)
    retval = GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->draw (widget, cr);
357

358
  focus_child = gtk_container_get_focus_child (GTK_CONTAINER (widget));
359
  if (focus_child && gtk_widget_has_focus (focus_child))
360
    {
361
      border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
362

363
      gtk_paint_focus (gtk_widget_get_style (widget),
364
                       cr,
365
                       gtk_widget_get_state (widget),
366 367 368 369
                       widget, "tray_icon",
                       border_width, border_width,
                       gtk_widget_get_allocated_width (widget) - 2 * border_width,
                       gtk_widget_get_allocated_height (widget) - 2 * border_width);
370
    }
371

372
  return retval;
373 374
}

375 376 377
static void
gtk_tray_icon_get_orientation_property (GtkTrayIcon *icon)
{
378 379 380 381
  GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
  GdkDisplay *display = gdk_screen_get_display (screen);
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);

382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
  Atom type;
  int format;
  union {
	gulong *prop;
	guchar *prop_ch;
  } prop = { NULL };
  gulong nitems;
  gulong bytes_after;
  int error, result;

  g_assert (icon->priv->manager_window != None);
  
  gdk_error_trap_push ();
  type = None;
  result = XGetWindowProperty (xdisplay,
			       icon->priv->manager_window,
			       icon->priv->orientation_atom,
			       0, G_MAXLONG, FALSE,
			       XA_CARDINAL,
			       &type, &format, &nitems,
			       &bytes_after, &(prop.prop_ch));
  error = gdk_error_trap_pop ();

  if (error || result != Success)
    return;

408
  if (type == XA_CARDINAL && nitems == 1 && format == 32)
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
    {
      GtkOrientation orientation;

      orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
					GTK_ORIENTATION_HORIZONTAL :
					GTK_ORIENTATION_VERTICAL;

      if (icon->priv->orientation != orientation)
	{
	  icon->priv->orientation = orientation;

	  g_object_notify (G_OBJECT (icon), "orientation");
	}
    }

424 425 426 427
  if (type != None)
    XFree (prop.prop);
}

Matthias Clasen's avatar
Matthias Clasen committed
428
static void
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
gtk_tray_icon_get_visual_property (GtkTrayIcon *icon)
{
  GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
  GdkDisplay *display = gdk_screen_get_display (screen);
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);

  Atom type;
  int format;
  union {
	gulong *prop;
	guchar *prop_ch;
  } prop = { NULL };
  gulong nitems;
  gulong bytes_after;
  int error, result;
  GdkVisual *visual;

  g_assert (icon->priv->manager_window != None);

  gdk_error_trap_push ();
  type = None;
  result = XGetWindowProperty (xdisplay,
			       icon->priv->manager_window,
			       icon->priv->visual_atom,
			       0, G_MAXLONG, FALSE,
			       XA_VISUALID,
			       &type, &format, &nitems,
			       &bytes_after, &(prop.prop_ch));
  error = gdk_error_trap_pop ();

  visual = NULL;

  if (!error && result == Success &&
      type == XA_VISUALID && nitems == 1 && format == 32)
    {
      VisualID visual_id = prop.prop[0];
      visual = gdk_x11_screen_lookup_visual (screen, visual_id);
    }

  icon->priv->manager_visual = visual;
  icon->priv->manager_visual_rgba = visual != NULL &&
    (visual->red_prec + visual->blue_prec + visual->green_prec < visual->depth);

  /* For the background-relative hack we use when we aren't using a real RGBA
   * visual, we can't be double-buffered */
  gtk_widget_set_double_buffered (GTK_WIDGET (icon), icon->priv->manager_visual_rgba);

  if (type != None)
477 478 479
    XFree (prop.prop);
}

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
static void
gtk_tray_icon_get_colors_property (GtkTrayIcon *icon)
{
  GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
  GdkDisplay *display = gdk_screen_get_display (screen);
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);

  Atom type;
  int format;
  union {
        gulong *prop;
        guchar *prop_ch;
  } prop = { NULL };
  gulong nitems;
  gulong bytes_after;
  int error, result;

  g_assert (icon->priv->manager_window != None);

  gdk_error_trap_push ();
  type = None;
  result = XGetWindowProperty (xdisplay,
			       icon->priv->manager_window,
			       icon->priv->colors_atom,
			       0, G_MAXLONG, FALSE,
			       XA_CARDINAL,
			       &type, &format, &nitems,
			       &bytes_after, &(prop.prop_ch));
  error = gdk_error_trap_pop ();

  if (error || result != Success)
    return;

  if (type == XA_CARDINAL && nitems == 12 && format == 32)
    {
      GdkColor color;

      g_object_freeze_notify (G_OBJECT (icon));

      color.red = prop.prop[0];
      color.green = prop.prop[1];
      color.blue = prop.prop[2];

      if (!gdk_color_equal (&icon->priv->fg_color, &color))
        {
          icon->priv->fg_color = color;

          g_object_notify (G_OBJECT (icon), "fg-color");
        }

      color.red = prop.prop[3];
      color.green = prop.prop[4];
      color.blue = prop.prop[5];

      if (!gdk_color_equal (&icon->priv->error_color, &color))
        {
          icon->priv->error_color = color;

          g_object_notify (G_OBJECT (icon), "error-color");
        }

      g_object_thaw_notify (G_OBJECT (icon));

      color.red = prop.prop[6];
      color.green = prop.prop[7];
      color.blue = prop.prop[8];

      if (!gdk_color_equal (&icon->priv->warning_color, &color))
        {
          icon->priv->warning_color = color;

          g_object_notify (G_OBJECT (icon), "warning-color");
        }

      g_object_thaw_notify (G_OBJECT (icon));

      color.red = prop.prop[9];
      color.green = prop.prop[10];
      color.blue = prop.prop[11];

      if (!gdk_color_equal (&icon->priv->success_color, &color))
        {
          icon->priv->success_color = color;

          g_object_notify (G_OBJECT (icon), "success-color");
        }

      g_object_thaw_notify (G_OBJECT (icon));
    }

  if (type != None)
    XFree (prop.prop);
}

574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
static void
gtk_tray_icon_get_padding_property (GtkTrayIcon *icon)
{
  GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
  GdkDisplay *display = gdk_screen_get_display (screen);
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);

  Atom type;
  int format;
  union {
	gulong *prop;
	guchar *prop_ch;
  } prop = { NULL };
  gulong nitems;
  gulong bytes_after;
  int error, result;

  g_assert (icon->priv->manager_window != None);

  gdk_error_trap_push ();
  type = None;
  result = XGetWindowProperty (xdisplay,
			       icon->priv->manager_window,
			       icon->priv->padding_atom,
			       0, G_MAXLONG, FALSE,
			       XA_CARDINAL,
			       &type, &format, &nitems,
			       &bytes_after, &(prop.prop_ch));
  error = gdk_error_trap_pop ();

  if (!error && result == Success &&
      type == XA_CARDINAL && nitems == 1 && format == 32)
    {
      gint padding;

      padding = prop.prop[0];

      if (icon->priv->padding != padding)
	{
	  icon->priv->padding = padding;

	  g_object_notify (G_OBJECT (icon), "padding");
	}
    }

  if (type != None)
    XFree (prop.prop);
}

623
static GdkFilterReturn
624 625
gtk_tray_icon_manager_filter (GdkXEvent *xevent,
			      GdkEvent  *event,
626 627 628 629 630 631 632 633 634
			      gpointer   user_data)
{
  GtkTrayIcon *icon = user_data;
  XEvent *xev = (XEvent *)xevent;

  if (xev->xany.type == ClientMessage &&
      xev->xclient.message_type == icon->priv->manager_atom &&
      xev->xclient.data.l[1] == icon->priv->selection_atom)
    {
635 636 637 638
      GTK_NOTE (PLUGSOCKET,
		g_print ("GtkStatusIcon %p: tray manager appeared\n", icon));

      gtk_tray_icon_update_manager_window (icon);
639 640 641 642 643 644
    }
  else if (xev->xany.window == icon->priv->manager_window)
    {
      if (xev->xany.type == PropertyNotify &&
	  xev->xproperty.atom == icon->priv->orientation_atom)
	{
645 646 647
          GTK_NOTE (PLUGSOCKET,
		    g_print ("GtkStatusIcon %p: got PropertyNotify on manager window for orientation atom\n", icon));

648 649
	  gtk_tray_icon_get_orientation_property (icon);
	}
650 651 652 653 654 655 656 657
      else if (xev->xany.type == PropertyNotify &&
               xev->xproperty.atom == icon->priv->colors_atom)
        {
          GTK_NOTE (PLUGSOCKET,
                    g_print ("GtkStatusIcon %p: got PropertyNotify on manager window for colors atom\n", icon));

          gtk_tray_icon_get_colors_property (icon);
        }
658 659 660 661 662
      else if (xev->xany.type == PropertyNotify &&
               xev->xproperty.atom == icon->priv->padding_atom)
        {
          gtk_tray_icon_get_padding_property (icon);
        }
663
      else if (xev->xany.type == DestroyNotify)
664
	{
665 666 667
          GTK_NOTE (PLUGSOCKET,
		    g_print ("GtkStatusIcon %p: got DestroyNotify for manager window\n", icon));

668 669
	  gtk_tray_icon_manager_window_destroyed (icon);
	}
670 671 672
      else
        GTK_NOTE (PLUGSOCKET,
		  g_print ("GtkStatusIcon %p: got other message on manager window\n", icon));
673 674 675 676 677 678 679 680 681 682 683 684 685
    }
  
  return GDK_FILTER_CONTINUE;
}

static void
gtk_tray_icon_send_manager_message (GtkTrayIcon *icon,
				    long         message,
				    Window       window,
				    long         data1,
				    long         data2,
				    long         data3)
{
686
  GtkWidget *widget;
687 688
  XClientMessageEvent ev;
  Display *display;
689 690 691

  widget = GTK_WIDGET (icon);

692
  memset (&ev, 0, sizeof (ev));
693 694 695 696
  ev.type = ClientMessage;
  ev.window = window;
  ev.message_type = icon->priv->system_tray_opcode_atom;
  ev.format = 32;
697
  ev.data.l[0] = gdk_x11_get_server_time (gtk_widget_get_window (widget));
698 699 700 701 702
  ev.data.l[1] = message;
  ev.data.l[2] = data1;
  ev.data.l[3] = data2;
  ev.data.l[4] = data3;

703 704
  display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (widget));

705 706 707
  gdk_error_trap_push ();
  XSendEvent (display,
	      icon->priv->manager_window, False, NoEventMask, (XEvent *)&ev);
708
  gdk_error_trap_pop_ignored ();
709 710 711 712 713
}

static void
gtk_tray_icon_send_dock_request (GtkTrayIcon *icon)
{
714
  GTK_NOTE (PLUGSOCKET,
715 716
	    g_print ("GtkStatusIcon %p: sending dock request to manager window %lx\n",
	    	     icon, (gulong) icon->priv->manager_window));
717

718 719 720 721 722 723 724 725
  gtk_tray_icon_send_manager_message (icon,
				      SYSTEM_TRAY_REQUEST_DOCK,
				      icon->priv->manager_window,
				      gtk_plug_get_id (GTK_PLUG (icon)),
				      0, 0);
}

static void
726
gtk_tray_icon_update_manager_window (GtkTrayIcon *icon)
727
{
728 729 730 731
  GtkWidget *widget = GTK_WIDGET (icon);
  GdkScreen *screen = gtk_widget_get_screen (widget);
  GdkDisplay *display = gdk_screen_get_display (screen);
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
732 733

  GTK_NOTE (PLUGSOCKET,
734 735
	    g_print ("GtkStatusIcon %p: updating tray icon manager window, current manager window: %lx\n",
		     icon, (gulong) icon->priv->manager_window));
736

737 738 739
  if (icon->priv->manager_window != None)
    return;

740 741 742
  GTK_NOTE (PLUGSOCKET,
	    g_print ("GtkStatusIcon %p: trying to find manager window\n", icon));

743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758
  XGrabServer (xdisplay);
  
  icon->priv->manager_window = XGetSelectionOwner (xdisplay,
						   icon->priv->selection_atom);

  if (icon->priv->manager_window != None)
    XSelectInput (xdisplay,
		  icon->priv->manager_window, StructureNotifyMask|PropertyChangeMask);

  XUngrabServer (xdisplay);
  XFlush (xdisplay);
  
  if (icon->priv->manager_window != None)
    {
      GdkWindow *gdkwin;

759
      GTK_NOTE (PLUGSOCKET,
760 761
		g_print ("GtkStatusIcon %p: is being managed by window %lx\n",
				icon, (gulong) icon->priv->manager_window));
762

763
      gdkwin = gdk_window_lookup_for_display (display,
764 765 766 767 768
					      icon->priv->manager_window);
      
      gdk_window_add_filter (gdkwin, gtk_tray_icon_manager_filter, icon);

      gtk_tray_icon_get_orientation_property (icon);
769
      gtk_tray_icon_get_visual_property (icon);
770
      gtk_tray_icon_get_colors_property (icon);
771
      gtk_tray_icon_get_padding_property (icon);
772

773
      if (gtk_widget_get_realized (GTK_WIDGET (icon)))
774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791
	{
	  if ((icon->priv->manager_visual == NULL &&
	       gtk_widget_get_visual (widget) == gdk_screen_get_system_visual (screen)) ||
	      (icon->priv->manager_visual == gtk_widget_get_visual (widget)))
	    {
	      /* Already have the right visual, can just dock
	       */
	      gtk_tray_icon_send_dock_request (icon);
	    }
	  else
	    {
	      /* Need to re-realize the widget to get the right visual
	       */
	      gtk_widget_hide (widget);
	      gtk_widget_unrealize (widget);
	      gtk_widget_show (widget);
	    }
	}
792
    }
793 794 795
  else
    GTK_NOTE (PLUGSOCKET,
	      g_print ("GtkStatusIcon %p: no tray manager found\n", icon));
796 797 798 799 800 801 802
}

static void
gtk_tray_icon_manager_window_destroyed (GtkTrayIcon *icon)
{
  g_return_if_fail (icon->priv->manager_window != None);

803 804
  GTK_NOTE (PLUGSOCKET,
	    g_print ("GtkStatusIcon %p: tray manager window destroyed\n", icon));
805

806
  gtk_tray_icon_clear_manager_window (icon);
807 808
}

809
static gboolean
810 811 812
gtk_tray_icon_delete (GtkWidget   *widget,
		      GdkEventAny *event)
{
813
#ifdef G_ENABLE_DEBUG
814
  GtkTrayIcon *icon = GTK_TRAY_ICON (widget);
815
#endif
816

817
  GTK_NOTE (PLUGSOCKET,
818
	    g_print ("GtkStatusIcon %p: delete notify, tray manager window %lx\n",
819
		     icon, (gulong) icon->priv->manager_window));
820

821 822 823 824 825
  /* A bug in X server versions up to x.org 1.5.0 means that:
   * XFixesChangeSaveSet(...., SaveSetRoot, SaveSetUnmap) doesn't work properly
   * and we'll left mapped in a separate toplevel window if the tray is destroyed.
   * For simplicity just get rid of our X window and start over.
   */
826 827 828
  gtk_widget_hide (widget);
  gtk_widget_unrealize (widget);
  gtk_widget_show (widget);
829

830
  /* Handled it, don't destroy the tray icon */
831 832 833
  return TRUE;
}

834
static void
835
gtk_tray_icon_set_visual (GtkTrayIcon *icon)
836 837 838 839 840 841 842 843 844 845 846
{
  GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (icon));
  GdkVisual *visual = icon->priv->manager_visual;

  /* To avoid uncertainty about colormaps, _NET_SYSTEM_TRAY_VISUAL is supposed
   * to be either the screen default visual or a TrueColor visual; ignore it
   * if it is something else
   */
  if (visual && visual->type != GDK_VISUAL_TRUE_COLOR)
    visual = NULL;

847 848
  if (visual == NULL)
    visual = gdk_screen_get_system_visual (screen);
849

850
  gtk_window_set_visual (GTK_WINDOW (icon), visual);
851 852
}

853 854 855 856
static void
gtk_tray_icon_realize (GtkWidget *widget)
{
  GtkTrayIcon *icon = GTK_TRAY_ICON (widget);
857
  GdkWindow *window;
858

859 860
  /* Set our visual before realizing */
  gtk_tray_icon_set_visual (icon);
861

862
  GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->realize (widget);
863
  window = gtk_widget_get_window (widget);
864 865 866 867
  if (icon->priv->manager_visual_rgba)
    {
      /* Set a transparent background */
      GdkColor transparent = { 0, 0, 0, 0 }; /* Only pixel=0 matters */
868
      gdk_window_set_background (window, &transparent);
869 870 871 872
    }
  else
    {
      /* Set a parent-relative background pixmap */
873
      gdk_window_set_background_pattern (window, NULL);
874
    }
875 876

  GTK_NOTE (PLUGSOCKET,
877
	    g_print ("GtkStatusIcon %p: realized, window: %lx, socket window: %lx\n",
878
		     widget,
879
		     (gulong) GDK_WINDOW_XWINDOW (window),
880 881
		     gtk_plug_get_socket_window (GTK_PLUG (icon)) ?
		     (gulong) GDK_WINDOW_XWINDOW (gtk_plug_get_socket_window (GTK_PLUG (icon))) : 0UL));
882

883 884 885
  if (icon->priv->manager_window != None)
    gtk_tray_icon_send_dock_request (icon);
}
886

887 888 889 890 891 892 893 894
static void
gtk_tray_icon_style_set (GtkWidget   *widget,
			 GtkStyle    *previous_style)
{
  /* The default handler resets the background according to the style. We either
   * use a transparent background or a parent-relative background and ignore the
   * style background. So, just don't chain up.
   */
895 896 897 898 899 900 901 902 903
}

guint
_gtk_tray_icon_send_message (GtkTrayIcon *icon,
			     gint         timeout,
			     const gchar *message,
			     gint         len)
{
  guint stamp;
904 905
  Display *xdisplay;
 
906 907 908
  g_return_val_if_fail (GTK_IS_TRAY_ICON (icon), 0);
  g_return_val_if_fail (timeout >= 0, 0);
  g_return_val_if_fail (message != NULL, 0);
909

910 911 912 913 914 915 916 917 918 919
  if (icon->priv->manager_window == None)
    return 0;

  if (len < 0)
    len = strlen (message);

  stamp = icon->priv->stamp++;
  
  /* Get ready to send the message */
  gtk_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
920
				      (Window)gtk_plug_get_id (GTK_PLUG (icon)),
921 922 923
				      timeout, len, stamp);

  /* Now to send the actual message */
924
  xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
925 926 927 928 929
  gdk_error_trap_push ();
  while (len > 0)
    {
      XClientMessageEvent ev;

930
      memset (&ev, 0, sizeof (ev));
931
      ev.type = ClientMessage;
932
      ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
      ev.format = 8;
      ev.message_type = XInternAtom (xdisplay,
				     "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
      if (len > 20)
	{
	  memcpy (&ev.data, message, 20);
	  len -= 20;
	  message += 20;
	}
      else
	{
	  memcpy (&ev.data, message, len);
	  len = 0;
	}

      XSendEvent (xdisplay,
949
		  icon->priv->manager_window, False,
950 951
		  StructureNotifyMask, (XEvent *)&ev);
    }
952
  gdk_error_trap_pop_ignored ();
953 954 955 956 957 958 959 960 961 962 963 964

  return stamp;
}

void
_gtk_tray_icon_cancel_message (GtkTrayIcon *icon,
			       guint        id)
{
  g_return_if_fail (GTK_IS_TRAY_ICON (icon));
  g_return_if_fail (id > 0);
  
  gtk_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
965
				      (Window)gtk_plug_get_id (GTK_PLUG (icon)),
966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996
				      id, 0, 0);
}

GtkTrayIcon *
_gtk_tray_icon_new_for_screen (GdkScreen  *screen, 
			       const gchar *name)
{
  g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);

  return g_object_new (GTK_TYPE_TRAY_ICON, 
		       "screen", screen, 
		       "title", name, 
		       NULL);
}

GtkTrayIcon*
_gtk_tray_icon_new (const gchar *name)
{
  return g_object_new (GTK_TYPE_TRAY_ICON, 
		       "title", name, 
		       NULL);
}

GtkOrientation
_gtk_tray_icon_get_orientation (GtkTrayIcon *icon)
{
  g_return_val_if_fail (GTK_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);

  return icon->priv->orientation;
}

997 998 999 1000 1001 1002 1003
gint
_gtk_tray_icon_get_padding (GtkTrayIcon *icon)
{
  g_return_val_if_fail (GTK_IS_TRAY_ICON (icon), 0);

  return icon->priv->padding;
}