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 }