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