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