add README for trayicon plugin
[claws.git] / src / plugins / trayicon / 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 GtkPlugClass *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_unrealize (GtkWidget *widget);
35
36 static void egg_tray_icon_update_manager_window (EggTrayIcon *icon);
37
38 Window gtk_plug_get_id(GtkPlug *widget)
39 {
40     return (guint32) GDK_WINDOW_XWINDOW(GTK_WIDGET(widget)->window);
41 }
42
43 GtkType
44 egg_tray_icon_get_type (void)
45 {
46   static GtkType our_type = 0;
47
48   if (our_type == 0)
49     {
50       GtkTypeInfo our_info =
51       {
52         "EggTrayIcon",
53         sizeof (EggTrayIcon),
54         sizeof (EggTrayIconClass),
55         (GtkClassInitFunc) egg_tray_icon_class_init,
56         (GtkObjectInitFunc) egg_tray_icon_init,
57         NULL, /* class_finalize */
58         NULL, /* class_data */
59         0,    /* n_preallocs */
60       };
61
62       our_type = gtk_type_unique (gtk_plug_get_type(), &our_info);
63     }
64
65   return our_type;
66 }
67
68 static void
69 egg_tray_icon_init (EggTrayIcon *icon)
70 {
71   icon->stamp = 1;
72   
73   gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
74 }
75
76 static void
77 egg_tray_icon_class_init (EggTrayIconClass *klass)
78 {
79   GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
80
81   parent_class = gtk_type_class(gtk_plug_get_type());
82
83   widget_class->unrealize = egg_tray_icon_unrealize;
84 }
85
86 static GdkFilterReturn
87 egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
88 {
89   EggTrayIcon *icon = user_data;
90   XEvent *xev = (XEvent *)xevent;
91
92   if (xev->xany.type == ClientMessage &&
93       xev->xclient.message_type == icon->manager_atom &&
94       xev->xclient.data.l[1] == icon->selection_atom)
95     {
96       egg_tray_icon_update_manager_window (icon);
97     }
98   else if (xev->xany.window == icon->manager_window)
99     {
100       if (xev->xany.type == DestroyNotify)
101         {
102           egg_tray_icon_update_manager_window (icon);
103         }
104     }
105   
106   return GDK_FILTER_CONTINUE;
107 }
108
109 static void
110 egg_tray_icon_unrealize (GtkWidget *widget)
111 {
112   EggTrayIcon *icon = EGG_TRAY_ICON (widget);
113   GdkWindow *root_window;
114
115   if (icon->manager_window != None)
116     {
117       GdkWindow *gdkwin;
118
119 #if HAVE_GTK_MULTIHEAD
120       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
121                                               icon->manager_window);
122 #else
123       gdkwin = gdk_window_lookup (icon->manager_window);
124 #endif
125
126       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
127     }
128
129 #if HAVE_GTK_MULTIHEAD
130   root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
131 #else
132   root_window = gdk_window_lookup (GDK_ROOT_WINDOW());
133 #endif
134
135   gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);
136
137   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
138     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
139 }
140
141 static void
142 egg_tray_icon_send_manager_message (EggTrayIcon *icon,
143                                     long         message,
144                                     Window       window,
145                                     long         data1,
146                                     long         data2,
147                                     long         data3)
148 {
149   XClientMessageEvent ev;
150   Display *display;
151
152   ev.type = ClientMessage;
153   ev.window = window;
154   ev.message_type = icon->system_tray_opcode_atom;
155   ev.format = 32;
156   ev.data.l[0] = gdk_time_get(); /* gdk_x11_get_server_time (GTK_WIDGET (icon)->window); */
157   ev.data.l[1] = message;
158   ev.data.l[2] = data1;
159   ev.data.l[3] = data2;
160   ev.data.l[4] = data3;
161
162 #if HAVE_GTK_MULTIHEAD
163   display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
164 #else
165   display = gdk_display;
166 #endif
167   
168   gdk_error_trap_push ();
169   XSendEvent (display,
170               icon->manager_window, False, NoEventMask, (XEvent *)&ev);
171   XSync (display, False);
172   gdk_error_trap_pop ();
173 }
174
175 static void
176 egg_tray_icon_send_dock_request (EggTrayIcon *icon)
177 {
178   egg_tray_icon_send_manager_message (icon,
179                                       SYSTEM_TRAY_REQUEST_DOCK,
180                                       icon->manager_window,
181                                       gtk_plug_get_id (GTK_PLUG (icon)),
182                                       0, 0);
183 }
184
185 static void
186 egg_tray_icon_update_manager_window (EggTrayIcon *icon)
187 {
188   Display *xdisplay;
189   
190 #if HAVE_GTK_MULTIHEAD
191   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
192 #else
193   xdisplay = gdk_display;
194 #endif
195   
196   if (icon->manager_window != None)
197     {
198       GdkWindow *gdkwin;
199
200 #if HAVE_GTK_MULTIHEAD
201       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
202                                               icon->manager_window);
203 #else
204       gdkwin = gdk_window_lookup (icon->manager_window);
205 #endif
206       
207       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
208     }
209   
210   XGrabServer (xdisplay);
211   
212   icon->manager_window = XGetSelectionOwner (xdisplay,
213                                              icon->selection_atom);
214
215   if (icon->manager_window != None)
216     XSelectInput (xdisplay,
217                   icon->manager_window, StructureNotifyMask);
218
219   XUngrabServer (xdisplay);
220   XFlush (xdisplay);
221   
222   if (icon->manager_window != None)
223     {
224       GdkWindow *gdkwin;
225
226 #if HAVE_GTK_MULTIHEAD
227       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
228                                               icon->manager_window);
229 #else
230       gdkwin = gdk_window_lookup (icon->manager_window);
231 #endif
232       
233       gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
234
235       /* Send a request that we'd like to dock */
236       egg_tray_icon_send_dock_request (icon);
237     }
238 }
239
240 EggTrayIcon *
241 egg_tray_icon_new_for_xscreen (Screen *xscreen, const char *name)
242 {
243   EggTrayIcon *icon;
244   char buffer[256];
245   GdkWindow *root_window;
246
247   g_return_val_if_fail (xscreen != NULL, NULL);
248   
249   icon = gtk_type_new (EGG_TYPE_TRAY_ICON);
250   gtk_window_set_title (GTK_WINDOW (icon), name);
251
252 #if HAVE_GTK_MULTIHEAD
253   /* FIXME: this code does not compile, screen is undefined. Now try
254    * getting the GdkScreen from xscreen (:. Dunno how to solve this
255    * (there is prolly some easy way I cant think of right now)
256    */
257   gtk_plug_construct_for_display (GTK_PLUG (icon),
258                                   gdk_screen_get_display (screen), 0);
259 #else
260   gtk_plug_construct (GTK_PLUG (icon), 0);
261 #endif
262   
263   gtk_widget_set_events(GTK_WIDGET(icon), GDK_ALL_EVENTS_MASK);
264   gtk_widget_realize (GTK_WIDGET (icon));
265
266   /* Now see if there's a manager window around */
267   g_snprintf (buffer, sizeof (buffer),
268               "_NET_SYSTEM_TRAY_S%d",
269               XScreenNumberOfScreen (xscreen));
270   
271   icon->selection_atom = XInternAtom (DisplayOfScreen (xscreen),
272                                       buffer, False);
273   
274   icon->manager_atom = XInternAtom (DisplayOfScreen (xscreen),
275                                     "MANAGER", False);
276   
277   icon->system_tray_opcode_atom = XInternAtom (DisplayOfScreen (xscreen),
278                                                "_NET_SYSTEM_TRAY_OPCODE", False);
279
280   egg_tray_icon_update_manager_window (icon);
281
282 #if HAVE_GTK_MULTIHEAD
283   root_window = gdk_screen_get_root_window (gtk_widget_get_screen (screen));
284 #else
285   root_window = gdk_window_lookup (GDK_ROOT_WINDOW ());
286 #endif
287   
288   /* Add a root window filter so that we get changes on MANAGER */
289   gdk_window_add_filter (root_window,
290                          egg_tray_icon_manager_filter, icon);
291                       
292   return icon;
293 }
294
295 #if HAVE_GTK_MULTIHEAD
296 EggTrayIcon *
297 egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
298 {
299   EggTrayIcon *icon;
300   char buffer[256];
301
302   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
303
304   return egg_tray_icon_new_for_xscreen (GDK_SCREEN_XSCREEN (screen), name);
305 }
306 #endif
307
308 EggTrayIcon*
309 egg_tray_icon_new (const gchar *name)
310 {
311   return egg_tray_icon_new_for_xscreen (DefaultScreenOfDisplay (gdk_display), name);
312 }
313
314 guint
315 egg_tray_icon_send_message (EggTrayIcon *icon,
316                             gint         timeout,
317                             const gchar *message,
318                             gint         len)
319 {
320   guint stamp;
321   
322   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
323   g_return_val_if_fail (timeout >= 0, 0);
324   g_return_val_if_fail (message != NULL, 0);
325                      
326   if (icon->manager_window == None)
327     return 0;
328
329   if (len < 0)
330     len = strlen (message);
331
332   stamp = icon->stamp++;
333   
334   /* Get ready to send the message */
335   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
336                                       (Window)gtk_plug_get_id (GTK_PLUG (icon)),
337                                       timeout, len, stamp);
338
339   /* Now to send the actual message */
340   gdk_error_trap_push ();
341   while (len > 0)
342     {
343       XClientMessageEvent ev;
344       Display *xdisplay;
345
346 #if HAVE_GTK_MULTIHEAD
347       xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
348 #else
349       xdisplay = gdk_display;
350 #endif
351       
352       ev.type = ClientMessage;
353       ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
354       ev.format = 8;
355       ev.message_type = XInternAtom (xdisplay,
356                                      "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
357       if (len > 20)
358         {
359           memcpy (&ev.data, message, 20);
360           len -= 20;
361           message += 20;
362         }
363       else
364         {
365           memcpy (&ev.data, message, len);
366           len = 0;
367         }
368
369       XSendEvent (xdisplay,
370                   icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
371       XSync (xdisplay, False);
372     }
373   gdk_error_trap_pop ();
374
375   return stamp;
376 }
377
378 void
379 egg_tray_icon_cancel_message (EggTrayIcon *icon,
380                               guint        id)
381 {
382   g_return_if_fail (EGG_IS_TRAY_ICON (icon));
383   g_return_if_fail (id > 0);
384   
385   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
386                                       (Window)gtk_plug_get_id (GTK_PLUG (icon)),
387                                       id, 0, 0);
388 }