2004-10-18 [christoph] 0.9.12cvs127
[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 <string.h>
22 #include <gdk/gdkx.h>
23 #include "eggtrayicon.h"
24
25 #define SYSTEM_TRAY_REQUEST_DOCK    0
26 #define SYSTEM_TRAY_BEGIN_MESSAGE   1
27 #define SYSTEM_TRAY_CANCEL_MESSAGE  2
28          
29 static GtkPlugXEmbedClass *parent_class = NULL;
30
31 static void egg_tray_icon_init (EggTrayIcon *icon);
32 static void egg_tray_icon_class_init (EggTrayIconClass *klass);
33
34 static void egg_tray_icon_update_manager_window (EggTrayIcon *icon);
35
36 GtkType
37 egg_tray_icon_get_type (void)
38 {
39   static GtkType our_type = 0;
40
41   if (our_type == 0)
42     {
43       static const GtkTypeInfo our_info =
44       {
45         "EggTrayIcon",
46         sizeof (EggTrayIcon),
47         sizeof (EggTrayIconClass),
48         (GtkClassInitFunc) egg_tray_icon_class_init,
49         (GtkObjectInitFunc) egg_tray_icon_init,
50         NULL, NULL, NULL
51       };
52
53       our_type = gtk_type_unique (GTK_TYPE_PLUG_XEMBED, &our_info);
54     }
55
56   return our_type;
57 }
58
59 static void
60 egg_tray_icon_init (EggTrayIcon *icon)
61 {
62   icon->stamp = 1;
63   
64   gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
65 }
66
67 static void
68 egg_tray_icon_class_init (EggTrayIconClass *klass)
69 {
70         /* parent_class = g_type_class_peek_parent (klass); */
71         parent_class = gtk_type_class(gtk_plug_xembed_get_type());
72 }
73
74 static GdkFilterReturn
75 egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
76 {
77   EggTrayIcon *icon = user_data;
78   XEvent *xev = (XEvent *)xevent;
79
80   if (xev->xany.type == ClientMessage &&
81       xev->xclient.message_type == icon->manager_atom &&
82       xev->xclient.data.l[1] == icon->selection_atom)
83     {
84       egg_tray_icon_update_manager_window (icon);
85     }
86   else if (xev->xany.window == icon->manager_window)
87     {
88       if (xev->xany.type == DestroyNotify)
89         {
90           egg_tray_icon_update_manager_window (icon);
91         }
92     }
93   
94   return GDK_FILTER_CONTINUE;
95 }
96
97 static void
98 egg_tray_icon_send_manager_message (EggTrayIcon *icon,
99                                     long         message,
100                                     Window       window,
101                                     long         data1,
102                                     long         data2,
103                                     long         data3)
104 {
105   XClientMessageEvent ev;
106   Display *display;
107   
108 #ifdef DEBUG
109   g_print("Sending message %lx to %ld. data=(%ld, %ld, %ld)\n", message, window,
110           data1, data2, data3);
111 #endif
112
113   ev.type = ClientMessage;
114   ev.window = window;
115   ev.message_type = icon->system_tray_opcode_atom;
116   ev.format = 32;
117   ev.data.l[0] = GDK_CURRENT_TIME; /* gdk_x11_get_server_time (GTK_WIDGET (icon)->window); */
118   ev.data.l[1] = message;
119   ev.data.l[2] = data1;
120   ev.data.l[3] = data2;
121   ev.data.l[4] = data3;
122
123 #if HAVE_GTK_MULTIHEAD
124   display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
125 #else
126   display = gdk_display;
127 #endif
128   
129   gdk_error_trap_push ();
130   XSendEvent (display,
131               icon->manager_window, False, NoEventMask, (XEvent *)&ev);
132   XSync (display, False);
133   gdk_error_trap_pop ();
134 }
135
136 static void
137 egg_tray_icon_send_dock_request (EggTrayIcon *icon)
138 {
139   egg_tray_icon_send_manager_message (icon,
140                                       SYSTEM_TRAY_REQUEST_DOCK,
141                                       icon->manager_window,
142                                       gtk_plug_xembed_get_id (GTK_PLUG_XEMBED (icon)),
143                                       0, 0);
144 }
145
146 static void
147 egg_tray_icon_update_manager_window (EggTrayIcon *icon)
148 {
149   Display *xdisplay;
150   
151 #if HAVE_GTK_MULTIHEAD
152   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
153 #else
154   xdisplay = gdk_display;
155 #endif
156   
157   if (icon->manager_window != None)
158     {
159       GdkWindow *gdkwin;
160
161 #if HAVE_GTK_MULTIHEAD
162       gdkwin = gdk_window_lookup_for_display (display,
163                                               icon->manager_window);
164 #else
165       gdkwin = gdk_window_lookup (icon->manager_window);
166 #endif
167       
168       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
169     }
170   
171   XGrabServer (xdisplay);
172   
173   icon->manager_window = XGetSelectionOwner (xdisplay,
174                                              icon->selection_atom);
175
176   if (icon->manager_window != None)
177     XSelectInput (xdisplay,
178                   icon->manager_window, StructureNotifyMask);
179
180   XUngrabServer (xdisplay);
181   XFlush (xdisplay);
182   
183   if (icon->manager_window != None)
184     {
185       GdkWindow *gdkwin;
186
187 #if HAVE_GTK_MULTIHEAD
188       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
189                                               icon->manager_window);
190 #else
191       gdkwin = gdk_window_lookup (icon->manager_window);
192 #endif
193       
194       gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
195
196       /* Send a request that we'd like to dock */
197       egg_tray_icon_send_dock_request (icon);
198     }
199 }
200
201 EggTrayIcon *
202 egg_tray_icon_new_for_xscreen (Screen *xscreen, const char *name)
203 {
204   EggTrayIcon *icon;
205   char buffer[256];
206   GdkWindow *root_window;
207
208   g_return_val_if_fail (xscreen != NULL, NULL);
209   
210   icon = (EggTrayIcon*)gtk_object_new (EGG_TYPE_TRAY_ICON, NULL);
211   gtk_window_set_title (GTK_WINDOW (icon), name);
212
213 #if HAVE_GTK_MULTIHEAD
214   gtk_plug_construct_for_display (GTK_PLUG_XEMBED (icon),
215                                   gdk_screen_get_display (screen), 0);
216 #else
217   gtk_plug_xembed_construct (GTK_PLUG_XEMBED (icon), 0);
218 #endif
219   
220   gtk_widget_realize (GTK_WIDGET (icon));
221
222   /* Now see if there's a manager window around */
223   g_snprintf (buffer, sizeof (buffer),
224               "_NET_SYSTEM_TRAY_S%d",
225               XScreenNumberOfScreen (xscreen));
226   
227   icon->selection_atom = XInternAtom (DisplayOfScreen (xscreen),
228                                       buffer, False);
229   
230   icon->manager_atom = XInternAtom (DisplayOfScreen (xscreen),
231                                     "MANAGER", False);
232   
233   icon->system_tray_opcode_atom = XInternAtom (DisplayOfScreen (xscreen),
234                                                "_NET_SYSTEM_TRAY_OPCODE", False);
235
236   egg_tray_icon_update_manager_window (icon);
237
238 #if HAVE_GTK_MULTIHEAD
239   root_window = gdk_screen_get_root_window (screen);
240 #else
241   root_window = GDK_ROOT_PARENT(); /* gdk_window_lookup (gdk_x11_get_default_root_xwindow ()); */
242 #endif
243   
244   /* Add a root window filter so that we get changes on MANAGER */
245   gdk_window_add_filter (root_window,
246                          egg_tray_icon_manager_filter, icon);
247                       
248   return icon;
249 }
250
251 #if HAVE_GTK_MULTIHEAD
252 EggTrayIcon *
253 egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
254 {
255   EggTrayIcon *icon;
256   char buffer[256];
257
258   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
259
260   return egg_tray_icon_new_for_xscreen (GDK_SCREEN_XSCREEN (screen), name);
261 }
262 #endif
263
264 EggTrayIcon*
265 egg_tray_icon_new (const gchar *name)
266 {
267   return egg_tray_icon_new_for_xscreen (DefaultScreenOfDisplay (gdk_display), name);
268 }
269
270 guint
271 egg_tray_icon_send_message (EggTrayIcon *icon,
272                             gint         timeout,
273                             const gchar *message,
274                             gint         len)
275 {
276   guint stamp;
277   
278   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
279   g_return_val_if_fail (timeout >= 0, 0);
280   g_return_val_if_fail (message != NULL, 0);
281                      
282   if (icon->manager_window == None)
283     return 0;
284
285   if (len < 0)
286     len = strlen (message);
287
288   stamp = icon->stamp++;
289   
290   /* Get ready to send the message */
291   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
292                                       (Window)gtk_plug_xembed_get_id (GTK_PLUG_XEMBED (icon)),
293                                       timeout, len, stamp);
294
295   /* Now to send the actual message */
296   gdk_error_trap_push ();
297   while (len > 0)
298     {
299       XClientMessageEvent ev;
300       Display *xdisplay;
301
302 #if HAVE_GTK_MULTIHEAD
303       xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
304 #else
305       xdisplay = gdk_display;
306 #endif
307       
308       ev.type = ClientMessage;
309       ev.window = (Window)gtk_plug_xembed_get_id (GTK_PLUG_XEMBED (icon));
310       ev.format = 8;
311       ev.message_type = XInternAtom (xdisplay,
312                                      "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
313       if (len > 20)
314         {
315           memcpy (&ev.data, message, 20);
316           len -= 20;
317           message += 20;
318         }
319       else
320         {
321           memcpy (&ev.data, message, len);
322           len = 0;
323         }
324
325       XSendEvent (xdisplay,
326                   icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
327       XSync (xdisplay, False);
328     }
329   gdk_error_trap_pop ();
330
331   return stamp;
332 }
333
334 void
335 egg_tray_icon_cancel_message (EggTrayIcon *icon,
336                               guint        id)
337 {
338   g_return_if_fail (EGG_IS_TRAY_ICON (icon));
339   g_return_if_fail (id > 0);
340   
341   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
342                                       (Window)gtk_plug_xembed_get_id (GTK_PLUG_XEMBED (icon)),
343                                       id, 0, 0);
344 }