2005-09-28 [martin] 1.9.14cvs60
[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 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 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24
25 #include <time.h>
26 #include <glib.h>
27 #include <glib/gi18n.h>
28
29 #include <string.h>
30 #include "eggtrayicon.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_update_manager_window (EggTrayIcon *icon);
60
61 GType
62 egg_tray_icon_get_type (void)
63 {
64   static GType our_type = 0;
65
66   if (our_type == 0)
67     {
68       gchar *type_name = g_strdup_printf("EggTrayIcon%d", (int)time(NULL));
69       static const GTypeInfo our_info =
70       {
71         sizeof (EggTrayIconClass),
72         (GBaseInitFunc) NULL,
73         (GBaseFinalizeFunc) NULL,
74         (GClassInitFunc) egg_tray_icon_class_init,
75         NULL, /* class_finalize */
76         NULL, /* class_data */
77         sizeof (EggTrayIcon),
78         0,    /* n_preallocs */
79         (GInstanceInitFunc) egg_tray_icon_init
80       };
81
82       our_type = g_type_register_static (GTK_TYPE_PLUG, type_name, 
83                                          &our_info, 0);
84       g_free(type_name);
85     }
86
87   return our_type;
88 }
89
90 static void
91 egg_tray_icon_init (EggTrayIcon *icon)
92 {
93   icon->stamp = 1;
94   icon->orientation = GTK_ORIENTATION_HORIZONTAL;
95   
96   gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
97 }
98
99 static void
100 egg_tray_icon_class_init (EggTrayIconClass *klass)
101 {
102   GObjectClass *gobject_class = (GObjectClass *)klass;
103   GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
104
105   parent_class = g_type_class_peek_parent (klass);
106
107   gobject_class->get_property = egg_tray_icon_get_property;
108
109   widget_class->realize   = egg_tray_icon_realize;
110   widget_class->unrealize = egg_tray_icon_unrealize;
111
112   g_object_class_install_property (gobject_class,
113                                    PROP_ORIENTATION,
114                                    g_param_spec_enum ("orientation",
115                                                       _("Orientation"),
116                                                       _("The orientation of the tray."),
117                                                       GTK_TYPE_ORIENTATION,
118                                                       GTK_ORIENTATION_HORIZONTAL,
119                                                       G_PARAM_READABLE));
120 }
121
122 static void
123 egg_tray_icon_get_property (GObject    *object,
124                             guint       prop_id,
125                             GValue     *value,
126                             GParamSpec *pspec)
127 {
128   EggTrayIcon *icon = EGG_TRAY_ICON (object);
129
130   switch (prop_id)
131     {
132     case PROP_ORIENTATION:
133       g_value_set_enum (value, icon->orientation);
134       break;
135     default:
136       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
137       break;
138     }
139 }
140
141 static void
142 egg_tray_icon_get_orientation_property (EggTrayIcon *icon)
143 {
144   Display *xdisplay;
145   Atom type;
146   int format;
147   union {
148         gulong *prop;
149         guchar *prop_ch;
150   } prop = { NULL };
151   gulong nitems;
152   gulong bytes_after;
153   int error, result;
154
155   g_assert (icon->manager_window != None);
156  
157 #ifndef GDK_MULTIHEAD_SAFE
158   xdisplay = gdk_display;
159 #else
160   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
161 #endif
162
163   gdk_error_trap_push ();
164   type = None;
165   result = XGetWindowProperty (xdisplay,
166                                icon->manager_window,
167                                icon->orientation_atom,
168                                0, G_MAXLONG, FALSE,
169                                XA_CARDINAL,
170                                &type, &format, &nitems,
171                                &bytes_after, &(prop.prop_ch));
172   error = gdk_error_trap_pop ();
173
174   if (error || result != Success)
175     return;
176
177   if (type == XA_CARDINAL)
178     {
179       GtkOrientation orientation;
180
181       orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
182                                         GTK_ORIENTATION_HORIZONTAL :
183                                         GTK_ORIENTATION_VERTICAL;
184
185       if (icon->orientation != orientation)
186         {
187           icon->orientation = orientation;
188
189           g_object_notify (G_OBJECT (icon), "orientation");
190         }
191     }
192
193   if (prop.prop)
194     XFree (prop.prop);
195 }
196
197 static GdkFilterReturn
198 egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
199 {
200   EggTrayIcon *icon = user_data;
201   XEvent *xev = (XEvent *)xevent;
202
203   if (xev->xany.type == ClientMessage &&
204       xev->xclient.message_type == icon->manager_atom &&
205       xev->xclient.data.l[1] == icon->selection_atom)
206     {
207       egg_tray_icon_update_manager_window (icon);
208     }
209   else if (xev->xany.window == icon->manager_window)
210     {
211       if (xev->xany.type == PropertyNotify &&
212           xev->xproperty.atom == icon->orientation_atom)
213         {
214           egg_tray_icon_get_orientation_property (icon);
215         }
216       if (xev->xany.type == DestroyNotify)
217         {
218           egg_tray_icon_update_manager_window (icon);
219         }
220     }
221   
222   return GDK_FILTER_CONTINUE;
223 }
224
225 static void
226 egg_tray_icon_unrealize (GtkWidget *widget)
227 {
228   EggTrayIcon *icon = EGG_TRAY_ICON (widget);
229   GdkWindow *root_window;
230
231   if (icon->manager_window != None)
232     {
233       GdkWindow *gdkwin;
234
235 #ifndef GDK_MULTIHEAD_SAFE
236       gdkwin = gdk_window_lookup (icon->manager_window);
237 #else
238       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
239                                               icon->manager_window);
240 #endif
241
242       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
243     }
244
245   root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
246
247   gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);
248
249   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
250     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
251 }
252
253 static void
254 egg_tray_icon_send_manager_message (EggTrayIcon *icon,
255                                     long         message,
256                                     Window       window,
257                                     long         data1,
258                                     long         data2,
259                                     long         data3)
260 {
261   XClientMessageEvent ev;
262   Display *display;
263   
264   ev.type = ClientMessage;
265   ev.window = window;
266   ev.message_type = icon->system_tray_opcode_atom;
267   ev.format = 32;
268   ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
269   ev.data.l[1] = message;
270   ev.data.l[2] = data1;
271   ev.data.l[3] = data2;
272   ev.data.l[4] = data3;
273
274 #ifndef GDK_MULTIHEAD_SAFE
275   display = gdk_display;
276 #else
277   display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
278 #endif
279
280   gdk_error_trap_push ();
281   XSendEvent (display,
282               icon->manager_window, False, NoEventMask, (XEvent *)&ev);
283   XSync (display, False);
284   gdk_error_trap_pop ();
285 }
286
287 static void
288 egg_tray_icon_send_dock_request (EggTrayIcon *icon)
289 {
290   egg_tray_icon_send_manager_message (icon,
291                                       SYSTEM_TRAY_REQUEST_DOCK,
292                                       icon->manager_window,
293                                       gtk_plug_get_id (GTK_PLUG (icon)),
294                                       0, 0);
295 }
296
297 static void
298 egg_tray_icon_update_manager_window (EggTrayIcon *icon)
299 {
300   Display *xdisplay;
301   
302 #ifndef GDK_MULTIHEAD_SAFE
303   xdisplay = gdk_display;
304 #else
305   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
306 #endif
307
308   if (icon->manager_window != None)
309     {
310       GdkWindow *gdkwin;
311
312 #ifndef GDK_MULTIHEAD_SAFE
313       gdkwin = gdk_window_lookup (icon->manager_window);
314 #else
315       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
316                                               icon->manager_window);
317 #endif
318       
319       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
320     }
321   
322   XGrabServer (xdisplay);
323   
324   icon->manager_window = XGetSelectionOwner (xdisplay,
325                                              icon->selection_atom);
326
327   if (icon->manager_window != None)
328     XSelectInput (xdisplay,
329                   icon->manager_window, StructureNotifyMask|PropertyChangeMask);
330
331   XUngrabServer (xdisplay);
332   XFlush (xdisplay);
333   
334   if (icon->manager_window != None)
335     {
336       GdkWindow *gdkwin;
337
338 #ifndef GDK_MULTIHEAD_SAFE
339       gdkwin = gdk_window_lookup (icon->manager_window);
340 #else
341       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
342                                               icon->manager_window);
343 #endif
344  
345       gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
346
347       /* Send a request that we'd like to dock */
348       egg_tray_icon_send_dock_request (icon);
349
350       egg_tray_icon_get_orientation_property (icon);
351     }
352 }
353
354 static void
355 egg_tray_icon_realize (GtkWidget *widget)
356 {
357   EggTrayIcon *icon = EGG_TRAY_ICON (widget);
358   GdkScreen *screen;
359   GdkDisplay *display;
360   Display *xdisplay;
361   char buffer[256];
362   GdkWindow *root_window;
363
364   if (GTK_WIDGET_CLASS (parent_class)->realize)
365     GTK_WIDGET_CLASS (parent_class)->realize (widget);
366
367   screen = gtk_widget_get_screen (widget);
368   display = gdk_screen_get_display (screen);
369   xdisplay = gdk_x11_display_get_xdisplay (display);
370
371   /* Now see if there's a manager window around */
372   g_snprintf (buffer, sizeof (buffer),
373               "_NET_SYSTEM_TRAY_S%d",
374               gdk_screen_get_number (screen));
375
376   icon->selection_atom = XInternAtom (xdisplay, buffer, False);
377   
378   icon->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
379   
380   icon->system_tray_opcode_atom = XInternAtom (xdisplay,
381                                                    "_NET_SYSTEM_TRAY_OPCODE",
382                                                    False);
383
384   icon->orientation_atom = XInternAtom (xdisplay,
385                                         "_NET_SYSTEM_TRAY_ORIENTATION",
386                                         False);
387
388   egg_tray_icon_update_manager_window (icon);
389
390   root_window = gdk_screen_get_root_window (screen);
391   
392   /* Add a root window filter so that we get changes on MANAGER */
393   gdk_window_add_filter (root_window,
394                          egg_tray_icon_manager_filter, icon);
395 }
396
397 EggTrayIcon *
398 egg_tray_icon_new_for_xscreen (Screen *xscreen, const char *name)
399 {
400   GdkDisplay *display;
401   GdkScreen *screen;
402
403   display = gdk_x11_lookup_xdisplay (DisplayOfScreen (xscreen));
404   screen = gdk_display_get_screen (display, XScreenNumberOfScreen (xscreen));
405
406   return egg_tray_icon_new_for_screen (screen, name);
407 }
408
409 EggTrayIcon *
410 egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
411 {
412   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
413
414   return g_object_new (EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
415 }
416
417 EggTrayIcon*
418 egg_tray_icon_new (const gchar *name)
419 {
420   return g_object_new (EGG_TYPE_TRAY_ICON, "title", name, NULL);
421 }
422
423 guint
424 egg_tray_icon_send_message (EggTrayIcon *icon,
425                             gint         timeout,
426                             const gchar *message,
427                             gint         len)
428 {
429   guint stamp;
430   
431   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
432   g_return_val_if_fail (timeout >= 0, 0);
433   g_return_val_if_fail (message != NULL, 0);
434                      
435   if (icon->manager_window == None)
436     return 0;
437
438   if (len < 0)
439     len = strlen (message);
440
441   stamp = icon->stamp++;
442   
443   /* Get ready to send the message */
444   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
445                                       (Window)gtk_plug_get_id (GTK_PLUG (icon)),
446                                       timeout, len, stamp);
447
448   /* Now to send the actual message */
449   gdk_error_trap_push ();
450   while (len > 0)
451     {
452       XClientMessageEvent ev;
453       Display *xdisplay;
454
455 #ifndef GDK_MULTIHEAD_SAFE
456       xdisplay = gdk_display;
457 #else
458       xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
459 #endif
460       
461       ev.type = ClientMessage;
462       ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
463       ev.format = 8;
464       ev.message_type = XInternAtom (xdisplay,
465                                      "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
466       if (len > 20)
467         {
468           memcpy (&ev.data, message, 20);
469           len -= 20;
470           message += 20;
471         }
472       else
473         {
474           memcpy (&ev.data, message, len);
475           len = 0;
476         }
477
478       XSendEvent (xdisplay,
479                   icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
480       XSync (xdisplay, False);
481     }
482   gdk_error_trap_pop ();
483
484   return stamp;
485 }
486
487 void
488 egg_tray_icon_cancel_message (EggTrayIcon *icon,
489                               guint        id)
490 {
491   g_return_if_fail (EGG_IS_TRAY_ICON (icon));
492   g_return_if_fail (id > 0);
493   
494   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
495                                       (Window)gtk_plug_get_id (GTK_PLUG (icon)),
496                                       id, 0, 0);
497 }
498
499 GtkOrientation
500 egg_tray_icon_get_orientation (EggTrayIcon *icon)
501 {
502   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
503
504   return icon->orientation;
505 }