a3e3baa16f312f083c123b8e989b72c98d6082eb
[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   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
158
159   gdk_error_trap_push ();
160   type = None;
161   result = XGetWindowProperty (xdisplay,
162                                icon->manager_window,
163                                icon->orientation_atom,
164                                0, G_MAXLONG, FALSE,
165                                XA_CARDINAL,
166                                &type, &format, &nitems,
167                                &bytes_after, &(prop.prop_ch));
168   error = gdk_error_trap_pop ();
169
170   if (error || result != Success)
171     return;
172
173   if (type == XA_CARDINAL)
174     {
175       GtkOrientation orientation;
176
177       orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
178                                         GTK_ORIENTATION_HORIZONTAL :
179                                         GTK_ORIENTATION_VERTICAL;
180
181       if (icon->orientation != orientation)
182         {
183           icon->orientation = orientation;
184
185           g_object_notify (G_OBJECT (icon), "orientation");
186         }
187     }
188
189   if (prop.prop)
190     XFree (prop.prop);
191 }
192
193 static GdkFilterReturn
194 egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
195 {
196   EggTrayIcon *icon = user_data;
197   XEvent *xev = (XEvent *)xevent;
198
199   if (xev->xany.type == ClientMessage &&
200       xev->xclient.message_type == icon->manager_atom &&
201       xev->xclient.data.l[1] == icon->selection_atom)
202     {
203       egg_tray_icon_update_manager_window (icon);
204     }
205   else if (xev->xany.window == icon->manager_window)
206     {
207       if (xev->xany.type == PropertyNotify &&
208           xev->xproperty.atom == icon->orientation_atom)
209         {
210           egg_tray_icon_get_orientation_property (icon);
211         }
212       if (xev->xany.type == DestroyNotify)
213         {
214           egg_tray_icon_update_manager_window (icon);
215         }
216     }
217   
218   return GDK_FILTER_CONTINUE;
219 }
220
221 static void
222 egg_tray_icon_unrealize (GtkWidget *widget)
223 {
224   EggTrayIcon *icon = EGG_TRAY_ICON (widget);
225   GdkWindow *root_window;
226
227   if (icon->manager_window != None)
228     {
229       GdkWindow *gdkwin;
230
231       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
232                                               icon->manager_window);
233
234       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
235     }
236
237   root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
238
239   gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);
240
241   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
242     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
243 }
244
245 static void
246 egg_tray_icon_send_manager_message (EggTrayIcon *icon,
247                                     long         message,
248                                     Window       window,
249                                     long         data1,
250                                     long         data2,
251                                     long         data3)
252 {
253   XClientMessageEvent ev;
254   Display *display;
255   
256   ev.type = ClientMessage;
257   ev.window = window;
258   ev.message_type = icon->system_tray_opcode_atom;
259   ev.format = 32;
260   ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
261   ev.data.l[1] = message;
262   ev.data.l[2] = data1;
263   ev.data.l[3] = data2;
264   ev.data.l[4] = data3;
265
266   display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
267   
268   gdk_error_trap_push ();
269   XSendEvent (display,
270               icon->manager_window, False, NoEventMask, (XEvent *)&ev);
271   XSync (display, False);
272   gdk_error_trap_pop ();
273 }
274
275 static void
276 egg_tray_icon_send_dock_request (EggTrayIcon *icon)
277 {
278   egg_tray_icon_send_manager_message (icon,
279                                       SYSTEM_TRAY_REQUEST_DOCK,
280                                       icon->manager_window,
281                                       gtk_plug_get_id (GTK_PLUG (icon)),
282                                       0, 0);
283 }
284
285 static void
286 egg_tray_icon_update_manager_window (EggTrayIcon *icon)
287 {
288   Display *xdisplay;
289   
290   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
291   
292   if (icon->manager_window != None)
293     {
294       GdkWindow *gdkwin;
295
296       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
297                                               icon->manager_window);
298       
299       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
300     }
301   
302   XGrabServer (xdisplay);
303   
304   icon->manager_window = XGetSelectionOwner (xdisplay,
305                                              icon->selection_atom);
306
307   if (icon->manager_window != None)
308     XSelectInput (xdisplay,
309                   icon->manager_window, StructureNotifyMask|PropertyChangeMask);
310
311   XUngrabServer (xdisplay);
312   XFlush (xdisplay);
313   
314   if (icon->manager_window != None)
315     {
316       GdkWindow *gdkwin;
317
318       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
319                                               icon->manager_window);
320       
321       gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
322
323       /* Send a request that we'd like to dock */
324       egg_tray_icon_send_dock_request (icon);
325
326       egg_tray_icon_get_orientation_property (icon);
327     }
328 }
329
330 static void
331 egg_tray_icon_realize (GtkWidget *widget)
332 {
333   EggTrayIcon *icon = EGG_TRAY_ICON (widget);
334   GdkScreen *screen;
335   GdkDisplay *display;
336   Display *xdisplay;
337   char buffer[256];
338   GdkWindow *root_window;
339
340   if (GTK_WIDGET_CLASS (parent_class)->realize)
341     GTK_WIDGET_CLASS (parent_class)->realize (widget);
342
343   screen = gtk_widget_get_screen (widget);
344   display = gdk_screen_get_display (screen);
345   xdisplay = gdk_x11_display_get_xdisplay (display);
346
347   /* Now see if there's a manager window around */
348   g_snprintf (buffer, sizeof (buffer),
349               "_NET_SYSTEM_TRAY_S%d",
350               gdk_screen_get_number (screen));
351
352   icon->selection_atom = XInternAtom (xdisplay, buffer, False);
353   
354   icon->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
355   
356   icon->system_tray_opcode_atom = XInternAtom (xdisplay,
357                                                    "_NET_SYSTEM_TRAY_OPCODE",
358                                                    False);
359
360   icon->orientation_atom = XInternAtom (xdisplay,
361                                         "_NET_SYSTEM_TRAY_ORIENTATION",
362                                         False);
363
364   egg_tray_icon_update_manager_window (icon);
365
366   root_window = gdk_screen_get_root_window (screen);
367   
368   /* Add a root window filter so that we get changes on MANAGER */
369   gdk_window_add_filter (root_window,
370                          egg_tray_icon_manager_filter, icon);
371 }
372
373 EggTrayIcon *
374 egg_tray_icon_new_for_xscreen (Screen *xscreen, const char *name)
375 {
376   GdkDisplay *display;
377   GdkScreen *screen;
378
379   display = gdk_x11_lookup_xdisplay (DisplayOfScreen (xscreen));
380   screen = gdk_display_get_screen (display, XScreenNumberOfScreen (xscreen));
381
382   return egg_tray_icon_new_for_screen (screen, name);
383 }
384
385 EggTrayIcon *
386 egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
387 {
388   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
389
390   return g_object_new (EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
391 }
392
393 EggTrayIcon*
394 egg_tray_icon_new (const gchar *name)
395 {
396   return g_object_new (EGG_TYPE_TRAY_ICON, "title", name, NULL);
397 }
398
399 guint
400 egg_tray_icon_send_message (EggTrayIcon *icon,
401                             gint         timeout,
402                             const gchar *message,
403                             gint         len)
404 {
405   guint stamp;
406   
407   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
408   g_return_val_if_fail (timeout >= 0, 0);
409   g_return_val_if_fail (message != NULL, 0);
410                      
411   if (icon->manager_window == None)
412     return 0;
413
414   if (len < 0)
415     len = strlen (message);
416
417   stamp = icon->stamp++;
418   
419   /* Get ready to send the message */
420   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
421                                       (Window)gtk_plug_get_id (GTK_PLUG (icon)),
422                                       timeout, len, stamp);
423
424   /* Now to send the actual message */
425   gdk_error_trap_push ();
426   while (len > 0)
427     {
428       XClientMessageEvent ev;
429       Display *xdisplay;
430
431       xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
432       
433       ev.type = ClientMessage;
434       ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
435       ev.format = 8;
436       ev.message_type = XInternAtom (xdisplay,
437                                      "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
438       if (len > 20)
439         {
440           memcpy (&ev.data, message, 20);
441           len -= 20;
442           message += 20;
443         }
444       else
445         {
446           memcpy (&ev.data, message, len);
447           len = 0;
448         }
449
450       XSendEvent (xdisplay,
451                   icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
452       XSync (xdisplay, False);
453     }
454   gdk_error_trap_pop ();
455
456   return stamp;
457 }
458
459 void
460 egg_tray_icon_cancel_message (EggTrayIcon *icon,
461                               guint        id)
462 {
463   g_return_if_fail (EGG_IS_TRAY_ICON (icon));
464   g_return_if_fail (id > 0);
465   
466   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
467                                       (Window)gtk_plug_get_id (GTK_PLUG (icon)),
468                                       id, 0, 0);
469 }
470
471 GtkOrientation
472 egg_tray_icon_get_orientation (EggTrayIcon *icon)
473 {
474   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
475
476   return icon->orientation;
477 }