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