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