src/eggtrayicon.c (14293B) - raw
1 /* eggtrayicon.c 2 * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org> 3 * 4 * Mail Notification 5 * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 3 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License along 18 * with this program; if not, write to the Free Software Foundation, Inc., 19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 */ 21 22 #include <string.h> 23 #include <glib/gi18n.h> 24 #include <libintl.h> 25 26 #include "eggtrayicon.h" 27 28 #include <gdkconfig.h> 29 #if defined (GDK_WINDOWING_X11) 30 #include <gdk/gdkx.h> 31 #include <X11/Xatom.h> 32 #elif defined (GDK_WINDOWING_WIN32) 33 #include <gdk/gdkwin32.h> 34 #endif 35 36 #ifndef EGG_COMPILATION 37 #ifndef _ 38 #define _(x) dgettext (GETTEXT_PACKAGE, x) 39 #define N_(x) x 40 #endif 41 #else 42 #define _(x) x 43 #define N_(x) x 44 #endif 45 46 #define SYSTEM_TRAY_REQUEST_DOCK 0 47 #define SYSTEM_TRAY_BEGIN_MESSAGE 1 48 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 49 50 #define SYSTEM_TRAY_ORIENTATION_HORZ 0 51 #define SYSTEM_TRAY_ORIENTATION_VERT 1 52 53 enum { 54 PROP_0, 55 PROP_ORIENTATION 56 }; 57 58 static GtkPlugClass *parent_class = NULL; 59 60 static void egg_tray_icon_init (EggTrayIcon *icon); 61 static void egg_tray_icon_class_init (EggTrayIconClass *klass); 62 63 static void egg_tray_icon_get_property (GObject *object, 64 guint prop_id, 65 GValue *value, 66 GParamSpec *pspec); 67 68 static void egg_tray_icon_add (GtkContainer *container, GtkWidget *widget); 69 70 static void egg_tray_icon_realize (GtkWidget *widget); 71 static void egg_tray_icon_unrealize (GtkWidget *widget); 72 73 #ifdef GDK_WINDOWING_X11 74 static void egg_tray_icon_update_manager_window (EggTrayIcon *icon, 75 gboolean dock_if_realized); 76 static void egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon); 77 #endif 78 79 GType 80 egg_tray_icon_get_type (void) 81 { 82 static GType our_type = 0; 83 84 if (our_type == 0) 85 { 86 static const GTypeInfo our_info = 87 { 88 sizeof (EggTrayIconClass), 89 (GBaseInitFunc) NULL, 90 (GBaseFinalizeFunc) NULL, 91 (GClassInitFunc) egg_tray_icon_class_init, 92 NULL, /* class_finalize */ 93 NULL, /* class_data */ 94 sizeof (EggTrayIcon), 95 0, /* n_preallocs */ 96 (GInstanceInitFunc) egg_tray_icon_init 97 }; 98 99 our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0); 100 } 101 102 return our_type; 103 } 104 105 static void 106 egg_tray_icon_init (EggTrayIcon *icon) 107 { 108 icon->stamp = 1; 109 icon->orientation = GTK_ORIENTATION_HORIZONTAL; 110 111 gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK); 112 } 113 114 static void 115 egg_tray_icon_class_init (EggTrayIconClass *klass) 116 { 117 GObjectClass *gobject_class = (GObjectClass *)klass; 118 GtkWidgetClass *widget_class = (GtkWidgetClass *)klass; 119 GtkContainerClass *container_class = (GtkContainerClass *)klass; 120 121 parent_class = g_type_class_peek_parent (klass); 122 123 gobject_class->get_property = egg_tray_icon_get_property; 124 125 widget_class->realize = egg_tray_icon_realize; 126 widget_class->unrealize = egg_tray_icon_unrealize; 127 128 container_class->add = egg_tray_icon_add; 129 130 g_object_class_install_property (gobject_class, 131 PROP_ORIENTATION, 132 g_param_spec_enum ("orientation", 133 _("Orientation"), 134 _("The orientation of the tray."), 135 GTK_TYPE_ORIENTATION, 136 GTK_ORIENTATION_HORIZONTAL, 137 G_PARAM_READABLE)); 138 139 #if defined (GDK_WINDOWING_X11) 140 /* Nothing */ 141 #elif defined (GDK_WINDOWING_WIN32) 142 g_warning ("Port eggtrayicon to Win32"); 143 #else 144 g_warning ("Port eggtrayicon to this GTK+ backend"); 145 #endif 146 } 147 148 static void 149 egg_tray_icon_get_property (GObject *object, 150 guint prop_id, 151 GValue *value, 152 GParamSpec *pspec) 153 { 154 EggTrayIcon *icon = EGG_TRAY_ICON (object); 155 156 switch (prop_id) 157 { 158 case PROP_ORIENTATION: 159 g_value_set_enum (value, icon->orientation); 160 break; 161 default: 162 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 163 break; 164 } 165 } 166 167 #ifdef GDK_WINDOWING_X11 168 169 static void 170 egg_tray_icon_get_orientation_property (EggTrayIcon *icon) 171 { 172 Display *xdisplay; 173 Atom type; 174 int format; 175 union { 176 gulong *prop; 177 guchar *prop_ch; 178 } prop = { NULL }; 179 gulong nitems; 180 gulong bytes_after; 181 int error, result; 182 183 g_assert (icon->manager_window != None); 184 185 xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); 186 187 gdk_error_trap_push (); 188 type = None; 189 result = XGetWindowProperty (xdisplay, 190 icon->manager_window, 191 icon->orientation_atom, 192 0, G_MAXLONG, FALSE, 193 XA_CARDINAL, 194 &type, &format, &nitems, 195 &bytes_after, &(prop.prop_ch)); 196 error = gdk_error_trap_pop (); 197 198 if (error || result != Success) 199 return; 200 201 if (type == XA_CARDINAL) 202 { 203 GtkOrientation orientation; 204 205 orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ? 206 GTK_ORIENTATION_HORIZONTAL : 207 GTK_ORIENTATION_VERTICAL; 208 209 if (icon->orientation != orientation) 210 { 211 icon->orientation = orientation; 212 213 g_object_notify (G_OBJECT (icon), "orientation"); 214 } 215 } 216 217 if (prop.prop) 218 XFree (prop.prop); 219 } 220 221 static GdkFilterReturn 222 egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data) 223 { 224 EggTrayIcon *icon = user_data; 225 XEvent *xev = (XEvent *)xevent; 226 227 if (xev->xany.type == ClientMessage && 228 xev->xclient.message_type == icon->manager_atom && 229 xev->xclient.data.l[1] == icon->selection_atom) 230 { 231 egg_tray_icon_update_manager_window (icon, TRUE); 232 } 233 else if (xev->xany.window == icon->manager_window) 234 { 235 if (xev->xany.type == PropertyNotify && 236 xev->xproperty.atom == icon->orientation_atom) 237 { 238 egg_tray_icon_get_orientation_property (icon); 239 } 240 if (xev->xany.type == DestroyNotify) 241 { 242 egg_tray_icon_manager_window_destroyed (icon); 243 } 244 } 245 return GDK_FILTER_CONTINUE; 246 } 247 248 #endif 249 250 static void 251 egg_tray_icon_unrealize (GtkWidget *widget) 252 { 253 #ifdef GDK_WINDOWING_X11 254 EggTrayIcon *icon = EGG_TRAY_ICON (widget); 255 GdkWindow *root_window; 256 257 if (icon->manager_window != None) 258 { 259 GdkWindow *gdkwin; 260 261 gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget), 262 icon->manager_window); 263 264 gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon); 265 } 266 267 root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget)); 268 269 gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon); 270 271 if (GTK_WIDGET_CLASS (parent_class)->unrealize) 272 (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); 273 #endif 274 } 275 276 #ifdef GDK_WINDOWING_X11 277 278 static void 279 egg_tray_icon_send_manager_message (EggTrayIcon *icon, 280 long message, 281 Window window, 282 long data1, 283 long data2, 284 long data3) 285 { 286 XClientMessageEvent ev; 287 Display *display; 288 289 ev.type = ClientMessage; 290 ev.window = window; 291 ev.message_type = icon->system_tray_opcode_atom; 292 ev.format = 32; 293 ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window); 294 ev.data.l[1] = message; 295 ev.data.l[2] = data1; 296 ev.data.l[3] = data2; 297 ev.data.l[4] = data3; 298 299 display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); 300 301 gdk_error_trap_push (); 302 XSendEvent (display, 303 icon->manager_window, False, NoEventMask, (XEvent *)&ev); 304 XSync (display, False); 305 gdk_error_trap_pop (); 306 } 307 308 static void 309 egg_tray_icon_send_dock_request (EggTrayIcon *icon) 310 { 311 egg_tray_icon_send_manager_message (icon, 312 SYSTEM_TRAY_REQUEST_DOCK, 313 icon->manager_window, 314 gtk_plug_get_id (GTK_PLUG (icon)), 315 0, 0); 316 } 317 318 static void 319 egg_tray_icon_update_manager_window (EggTrayIcon *icon, 320 gboolean dock_if_realized) 321 { 322 Display *xdisplay; 323 324 if (icon->manager_window != None) 325 return; 326 327 xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); 328 329 XGrabServer (xdisplay); 330 331 icon->manager_window = XGetSelectionOwner (xdisplay, 332 icon->selection_atom); 333 334 if (icon->manager_window != None) 335 XSelectInput (xdisplay, 336 icon->manager_window, StructureNotifyMask|PropertyChangeMask); 337 338 XUngrabServer (xdisplay); 339 XFlush (xdisplay); 340 341 if (icon->manager_window != None) 342 { 343 GdkWindow *gdkwin; 344 345 gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)), 346 icon->manager_window); 347 348 gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon); 349 350 if (dock_if_realized && GTK_WIDGET_REALIZED (icon)) 351 egg_tray_icon_send_dock_request (icon); 352 353 egg_tray_icon_get_orientation_property (icon); 354 } 355 } 356 357 static gboolean 358 transparent_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) 359 { 360 gdk_window_clear_area (widget->window, event->area.x, event->area.y, 361 event->area.width, event->area.height); 362 return FALSE; 363 } 364 365 static void 366 make_transparent_again (GtkWidget *widget, GtkStyle *previous_style, 367 gpointer user_data) 368 { 369 gdk_window_set_back_pixmap (widget->window, NULL, TRUE); 370 } 371 372 static void 373 make_transparent (GtkWidget *widget, gpointer user_data) 374 { 375 if (GTK_WIDGET_NO_WINDOW (widget) || GTK_WIDGET_APP_PAINTABLE (widget)) 376 return; 377 378 gtk_widget_set_app_paintable (widget, TRUE); 379 gtk_widget_set_double_buffered (widget, FALSE); 380 gdk_window_set_back_pixmap (widget->window, NULL, TRUE); 381 g_signal_connect (widget, "expose_event", 382 G_CALLBACK (transparent_expose_event), NULL); 383 g_signal_connect_after (widget, "style_set", 384 G_CALLBACK (make_transparent_again), NULL); 385 } 386 387 static void 388 egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon) 389 { 390 GdkWindow *gdkwin; 391 392 g_return_if_fail (icon->manager_window != None); 393 394 gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)), 395 icon->manager_window); 396 397 gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon); 398 399 icon->manager_window = None; 400 401 egg_tray_icon_update_manager_window (icon, TRUE); 402 } 403 404 #endif 405 406 static void 407 egg_tray_icon_realize (GtkWidget *widget) 408 { 409 #ifdef GDK_WINDOWING_X11 410 EggTrayIcon *icon = EGG_TRAY_ICON (widget); 411 GdkScreen *screen; 412 GdkDisplay *display; 413 Display *xdisplay; 414 char buffer[256]; 415 GdkWindow *root_window; 416 417 if (GTK_WIDGET_CLASS (parent_class)->realize) 418 GTK_WIDGET_CLASS (parent_class)->realize (widget); 419 420 make_transparent (widget, NULL); 421 422 screen = gtk_widget_get_screen (widget); 423 display = gdk_screen_get_display (screen); 424 xdisplay = gdk_x11_display_get_xdisplay (display); 425 426 /* Now see if there's a manager window around */ 427 g_snprintf (buffer, sizeof (buffer), 428 "_NET_SYSTEM_TRAY_S%d", 429 gdk_screen_get_number (screen)); 430 431 icon->selection_atom = XInternAtom (xdisplay, buffer, False); 432 433 icon->manager_atom = XInternAtom (xdisplay, "MANAGER", False); 434 435 icon->system_tray_opcode_atom = XInternAtom (xdisplay, 436 "_NET_SYSTEM_TRAY_OPCODE", 437 False); 438 439 icon->orientation_atom = XInternAtom (xdisplay, 440 "_NET_SYSTEM_TRAY_ORIENTATION", 441 False); 442 443 egg_tray_icon_update_manager_window (icon, FALSE); 444 egg_tray_icon_send_dock_request (icon); 445 446 root_window = gdk_screen_get_root_window (screen); 447 448 /* Add a root window filter so that we get changes on MANAGER */ 449 gdk_window_add_filter (root_window, 450 egg_tray_icon_manager_filter, icon); 451 #endif 452 } 453 454 static void 455 egg_tray_icon_add (GtkContainer *container, GtkWidget *widget) 456 { 457 g_signal_connect (widget, "realize", 458 G_CALLBACK (make_transparent), NULL); 459 GTK_CONTAINER_CLASS (parent_class)->add (container, widget); 460 } 461 462 EggTrayIcon * 463 egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name) 464 { 465 g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); 466 467 return g_object_new (EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL); 468 } 469 470 EggTrayIcon* 471 egg_tray_icon_new (const gchar *name) 472 { 473 return g_object_new (EGG_TYPE_TRAY_ICON, "title", name, NULL); 474 } 475 476 guint 477 egg_tray_icon_send_message (EggTrayIcon *icon, 478 gint timeout, 479 const gchar *message, 480 gint len) 481 { 482 guint stamp; 483 484 g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0); 485 g_return_val_if_fail (timeout >= 0, 0); 486 g_return_val_if_fail (message != NULL, 0); 487 488 #ifdef GDK_WINDOWING_X11 489 if (icon->manager_window == None) 490 return 0; 491 #endif 492 493 if (len < 0) 494 len = strlen (message); 495 496 stamp = icon->stamp++; 497 498 #ifdef GDK_WINDOWING_X11 499 /* Get ready to send the message */ 500 egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE, 501 (Window)gtk_plug_get_id (GTK_PLUG (icon)), 502 timeout, len, stamp); 503 504 /* Now to send the actual message */ 505 gdk_error_trap_push (); 506 while (len > 0) 507 { 508 XClientMessageEvent ev; 509 Display *xdisplay; 510 511 xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); 512 513 ev.type = ClientMessage; 514 ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon)); 515 ev.format = 8; 516 ev.message_type = XInternAtom (xdisplay, 517 "_NET_SYSTEM_TRAY_MESSAGE_DATA", False); 518 if (len > 20) 519 { 520 memcpy (&ev.data, message, 20); 521 len -= 20; 522 message += 20; 523 } 524 else 525 { 526 memcpy (&ev.data, message, len); 527 len = 0; 528 } 529 530 XSendEvent (xdisplay, 531 icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev); 532 XSync (xdisplay, False); 533 } 534 gdk_error_trap_pop (); 535 #endif 536 537 return stamp; 538 } 539 540 void 541 egg_tray_icon_cancel_message (EggTrayIcon *icon, 542 guint id) 543 { 544 g_return_if_fail (EGG_IS_TRAY_ICON (icon)); 545 g_return_if_fail (id > 0); 546 #ifdef GDK_WINDOWING_X11 547 egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE, 548 (Window)gtk_plug_get_id (GTK_PLUG (icon)), 549 id, 0, 0); 550 #endif 551 } 552 553 GtkOrientation 554 egg_tray_icon_get_orientation (EggTrayIcon *icon) 555 { 556 g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL); 557 558 return icon->orientation; 559 }