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