2010-03-24 [pawel] 3.7.5cvs38
[claws.git] / src / plugins / trayicon / libeggtrayicon / 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 3 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, see <http://www.gnu.org/licenses/>.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #endif
22
23 #include <time.h>
24 #include <glib.h>
25 #include <glib/gi18n.h>
26 #include <gtk/gtk.h>
27
28 #include <string.h>
29 #include "eggtrayicon.h"
30 #include "gtkutils.h"
31
32 #include <gdk/gdkx.h>
33 #include <X11/Xatom.h>
34 #define SYSTEM_TRAY_REQUEST_DOCK    0
35 #define SYSTEM_TRAY_BEGIN_MESSAGE   1
36 #define SYSTEM_TRAY_CANCEL_MESSAGE  2
37
38 #define SYSTEM_TRAY_ORIENTATION_HORZ 0
39 #define SYSTEM_TRAY_ORIENTATION_VERT 1
40
41 enum {
42   PROP_0,
43   PROP_ORIENTATION
44 };
45          
46 static GtkPlugClass *parent_class = NULL;
47
48 static void egg_tray_icon_init (EggTrayIcon *icon);
49 static void egg_tray_icon_class_init (EggTrayIconClass *klass);
50
51 static void egg_tray_icon_get_property (GObject    *object,
52                                         guint       prop_id,
53                                         GValue     *value,
54                                         GParamSpec *pspec);
55
56 static void egg_tray_icon_realize   (GtkWidget *widget);
57 static void egg_tray_icon_unrealize (GtkWidget *widget);
58
59 static void egg_tray_icon_add (GtkContainer *container,
60                                GtkWidget    *widget);
61
62 static void egg_tray_icon_update_manager_window    (EggTrayIcon *icon,
63                                                     gboolean     dock_if_realized);
64 static void egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon);
65
66 GType
67 egg_tray_icon_get_type (void)
68 {
69   static GType our_type = 0;
70
71   if (our_type == 0)
72     {
73       gchar *type_name = g_strdup_printf("EggTrayIcon%d", (int)time(NULL));
74       static const GTypeInfo our_info =
75       {
76         sizeof (EggTrayIconClass),
77         (GBaseInitFunc) NULL,
78         (GBaseFinalizeFunc) NULL,
79         (GClassInitFunc) egg_tray_icon_class_init,
80         NULL, /* class_finalize */
81         NULL, /* class_data */
82         sizeof (EggTrayIcon),
83         0,    /* n_preallocs */
84         (GInstanceInitFunc) egg_tray_icon_init
85       };
86
87       our_type = g_type_register_static (GTK_TYPE_PLUG, type_name, 
88                                          &our_info, 0);
89       g_free(type_name);
90     }
91
92   return our_type;
93 }
94
95 static void
96 egg_tray_icon_init (EggTrayIcon *icon)
97 {
98   icon->stamp = 1;
99   icon->orientation = GTK_ORIENTATION_HORIZONTAL;
100   
101   gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
102 }
103
104 static void
105 egg_tray_icon_class_init (EggTrayIconClass *klass)
106 {
107   GObjectClass *gobject_class = (GObjectClass *)klass;
108   GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
109   GtkContainerClass *container_class = (GtkContainerClass *)klass;
110
111   parent_class = g_type_class_peek_parent (klass);
112
113   gobject_class->get_property = egg_tray_icon_get_property;
114
115   widget_class->realize   = egg_tray_icon_realize;
116   widget_class->unrealize = egg_tray_icon_unrealize;
117   container_class->add = egg_tray_icon_add;
118
119   g_object_class_install_property (gobject_class,
120                                    PROP_ORIENTATION,
121                                    g_param_spec_enum ("orientation",
122                                                       _("Orientation"),
123                                                       _("The orientation of the tray."),
124                                                       GTK_TYPE_ORIENTATION,
125                                                       GTK_ORIENTATION_HORIZONTAL,
126                                                       G_PARAM_READABLE));
127 }
128
129 static void
130 egg_tray_icon_get_property (GObject    *object,
131                             guint       prop_id,
132                             GValue     *value,
133                             GParamSpec *pspec)
134 {
135   EggTrayIcon *icon = EGG_TRAY_ICON (object);
136
137   switch (prop_id)
138     {
139     case PROP_ORIENTATION:
140       g_value_set_enum (value, icon->orientation);
141       break;
142     default:
143       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
144       break;
145     }
146 }
147
148 static void
149 egg_tray_icon_get_orientation_property (EggTrayIcon *icon)
150 {
151   Display *xdisplay;
152   Atom type;
153   int format;
154   union {
155         gulong *prop;
156         guchar *prop_ch;
157   } prop = { NULL };
158   gulong nitems;
159   gulong bytes_after;
160   int error, result;
161
162   g_return_if_fail (icon->manager_window != None);
163  
164 #ifndef GDK_MULTIHEAD_SAFE
165   xdisplay = gdk_display;
166 #else
167   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
168 #endif
169
170   gdk_error_trap_push ();
171   type = None;
172   result = XGetWindowProperty (xdisplay,
173                                icon->manager_window,
174                                icon->orientation_atom,
175                                0, G_MAXLONG, FALSE,
176                                XA_CARDINAL,
177                                &type, &format, &nitems,
178                                &bytes_after, &(prop.prop_ch));
179   error = gdk_error_trap_pop ();
180
181   if (error || result != Success)
182     return;
183
184   if (type == XA_CARDINAL)
185     {
186       GtkOrientation orientation;
187
188       orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
189                                         GTK_ORIENTATION_HORIZONTAL :
190                                         GTK_ORIENTATION_VERTICAL;
191
192       if (icon->orientation != orientation)
193         {
194           icon->orientation = orientation;
195
196           g_object_notify (G_OBJECT (icon), "orientation");
197         }
198     }
199
200   if (prop.prop)
201     XFree (prop.prop);
202 }
203
204 static GdkFilterReturn
205 egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
206 {
207   EggTrayIcon *icon = user_data;
208   XEvent *xev = (XEvent *)xevent;
209
210   if (xev->xany.type == ClientMessage &&
211       xev->xclient.message_type == icon->manager_atom &&
212       xev->xclient.data.l[1] == icon->selection_atom)
213     {
214       egg_tray_icon_update_manager_window (icon, TRUE);
215     }
216   else if (xev->xany.window == icon->manager_window)
217     {
218       if (xev->xany.type == PropertyNotify &&
219           xev->xproperty.atom == icon->orientation_atom)
220         {
221           egg_tray_icon_get_orientation_property (icon);
222         }
223       if (xev->xany.type == DestroyNotify)
224         {
225           egg_tray_icon_manager_window_destroyed (icon);
226         }
227     }
228   
229   return GDK_FILTER_CONTINUE;
230 }
231
232 static void
233 egg_tray_icon_unrealize (GtkWidget *widget)
234 {
235   EggTrayIcon *icon = EGG_TRAY_ICON (widget);
236   GdkWindow *root_window;
237
238   if (icon->manager_window != None)
239     {
240       GdkWindow *gdkwin;
241
242 #ifndef GDK_MULTIHEAD_SAFE
243       gdkwin = gdk_window_lookup (icon->manager_window);
244 #else
245       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
246                                               icon->manager_window);
247 #endif
248
249       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
250     }
251
252   root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
253
254   gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);
255
256   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
257     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
258 }
259
260 static void
261 egg_tray_icon_send_manager_message (EggTrayIcon *icon,
262                                     long         message,
263                                     Window       window,
264                                     long         data1,
265                                     long         data2,
266                                     long         data3)
267 {
268   XClientMessageEvent ev;
269   Display *display;
270   
271   ev.type = ClientMessage;
272   ev.window = window;
273   ev.message_type = icon->system_tray_opcode_atom;
274   ev.format = 32;
275   ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
276   ev.data.l[1] = message;
277   ev.data.l[2] = data1;
278   ev.data.l[3] = data2;
279   ev.data.l[4] = data3;
280
281 #ifndef GDK_MULTIHEAD_SAFE
282   display = gdk_display;
283 #else
284   display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
285 #endif
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, gboolean dock_if_realized)
306 {
307   Display *xdisplay;
308   
309 #ifndef GDK_MULTIHEAD_SAFE
310   xdisplay = gdk_display;
311 #else
312   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
313 #endif
314
315   if (icon->manager_window != None)
316     {
317       GdkWindow *gdkwin;
318
319 #ifndef GDK_MULTIHEAD_SAFE
320       gdkwin = gdk_window_lookup (icon->manager_window);
321 #else
322       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
323                                               icon->manager_window);
324 #endif
325       
326       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
327     }
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 #ifndef GDK_MULTIHEAD_SAFE
346       gdkwin = gdk_window_lookup (icon->manager_window);
347 #else
348       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
349                                               icon->manager_window);
350 #endif
351  
352       gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
353
354       if (dock_if_realized && gtkut_widget_get_realized (GTK_WIDGET(icon)))
355         egg_tray_icon_send_dock_request (icon);
356
357       egg_tray_icon_get_orientation_property (icon);
358     }
359 }
360
361 static gboolean
362 transparent_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
363 {
364   gdk_window_clear_area (widget->window, event->area.x, event->area.y,
365                          event->area.width, event->area.height);
366   return FALSE;
367 }
368
369 static void
370 make_transparent_again (GtkWidget *widget, GtkStyle *previous_style,
371                         gpointer user_data)
372 {
373   gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
374 }
375
376 static void
377 make_transparent (GtkWidget *widget, gpointer user_data)
378 {
379   if (!gtkut_widget_get_has_window (widget) || gtkut_widget_get_app_paintable (widget))
380     return;
381
382   gtk_widget_set_app_paintable (widget, TRUE);
383   gtk_widget_set_double_buffered (widget, FALSE);
384   gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
385   g_signal_connect (widget, "expose_event",
386                     G_CALLBACK (transparent_expose_event), NULL);
387   g_signal_connect_after (widget, "style_set",
388                           G_CALLBACK (make_transparent_again), NULL);
389 }       
390
391 static void
392 egg_tray_icon_add (GtkContainer *container, GtkWidget *widget)
393 {
394    g_signal_connect (widget, "realize",
395                      G_CALLBACK (make_transparent), NULL);
396    GTK_CONTAINER_CLASS (parent_class)->add (container, widget);
397 }
398
399 static void
400 egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon)
401 {
402   GdkWindow *gdkwin;
403   
404   g_return_if_fail (icon->manager_window != None);
405
406   gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
407                                           icon->manager_window);
408
409   gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
410
411   icon->manager_window = None;
412
413   egg_tray_icon_update_manager_window (icon, TRUE);
414 }
415
416 static void
417 egg_tray_icon_realize (GtkWidget *widget)
418 {
419   EggTrayIcon *icon = EGG_TRAY_ICON (widget);
420   GdkScreen *screen;
421   GdkDisplay *display;
422   Display *xdisplay;
423   char buffer[256];
424   GdkWindow *root_window;
425
426   if (GTK_WIDGET_CLASS (parent_class)->realize)
427     GTK_WIDGET_CLASS (parent_class)->realize (widget);
428
429   make_transparent (widget, NULL);
430   screen = gtk_widget_get_screen (widget);
431   display = gdk_screen_get_display (screen);
432   xdisplay = gdk_x11_display_get_xdisplay (display);
433
434   /* Now see if there's a manager window around */
435   g_snprintf (buffer, sizeof (buffer),
436               "_NET_SYSTEM_TRAY_S%d",
437               gdk_screen_get_number (screen));
438
439   icon->selection_atom = XInternAtom (xdisplay, buffer, False);
440   
441   icon->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
442   
443   icon->system_tray_opcode_atom = XInternAtom (xdisplay,
444                                                    "_NET_SYSTEM_TRAY_OPCODE",
445                                                    False);
446
447   icon->orientation_atom = XInternAtom (xdisplay,
448                                         "_NET_SYSTEM_TRAY_ORIENTATION",
449                                         False);
450
451   egg_tray_icon_update_manager_window (icon, FALSE);
452   egg_tray_icon_send_dock_request (icon);
453
454   root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
455  
456   /* Add a root window filter so that we get changes on MANAGER */
457   gdk_window_add_filter (root_window,
458                          egg_tray_icon_manager_filter, icon);
459 }
460
461 EggTrayIcon *
462 egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
463 {
464   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
465
466   return g_object_new (EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
467 }
468
469 EggTrayIcon*
470 egg_tray_icon_new (const gchar *name)
471 {
472   return g_object_new (EGG_TYPE_TRAY_ICON, "title", name, NULL);
473 }
474
475 guint
476 egg_tray_icon_send_message (EggTrayIcon *icon,
477                             gint         timeout,
478                             const gchar *message,
479                             gint         len)
480 {
481   guint stamp;
482   
483   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
484   g_return_val_if_fail (timeout >= 0, 0);
485   g_return_val_if_fail (message != NULL, 0);
486                      
487   if (icon->manager_window == None)
488     return 0;
489
490   if (len < 0)
491     len = strlen (message);
492
493   stamp = icon->stamp++;
494   
495   /* Get ready to send the message */
496   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
497                                       (Window)gtk_plug_get_id (GTK_PLUG (icon)),
498                                       timeout, len, stamp);
499
500   /* Now to send the actual message */
501   gdk_error_trap_push ();
502   while (len > 0)
503     {
504       XClientMessageEvent ev;
505       Display *xdisplay;
506
507 #ifndef GDK_MULTIHEAD_SAFE
508       xdisplay = gdk_display;
509 #else
510       xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
511 #endif
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
536   return stamp;
537 }
538
539 void
540 egg_tray_icon_cancel_message (EggTrayIcon *icon,
541                               guint        id)
542 {
543   g_return_if_fail (EGG_IS_TRAY_ICON (icon));
544   g_return_if_fail (id > 0);
545   
546   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
547                                       (Window)gtk_plug_get_id (GTK_PLUG (icon)),
548                                       id, 0, 0);
549 }
550
551 GtkOrientation
552 egg_tray_icon_get_orientation (EggTrayIcon *icon)
553 {
554   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
555
556   return icon->orientation;
557 }