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 /*
22 
23 Permission to use this file under wxWindows licence given by
24 copyright holder:
25 --------
26 From andersca@gnu.org Tue Dec  9 13:01:56 2003
27 Return-path: <andersca@gnu.org>
28 Envelope-to: vasek@localhost
29 Delivery-date: Tue, 09 Dec 2003 13:04:35 +0100
30 Received: from localhost
31 	([127.0.0.1] helo=amavis ident=amavis)
32 	by armitage with esmtp (Exim 3.35 #1)
33 	id 1ATgbS-0001Gs-00
34 	for vasek@localhost; Tue, 09 Dec 2003 13:04:35 +0100
35 Received: from armitage ([127.0.0.1])
36 	by amavis (armitage [127.0.0.1]) (amavisd-new, port 10024) with ESMTP
37 	id 04227-09 for <vasek@localhost>;
38 	Tue, 9 Dec 2003 13:04:11 +0100 (CET)
39 Received: from localhost ([127.0.0.1] ident=fetchmail)
40 	by armitage with esmtp (Exim 3.35 #1)
41 	id 1ATgb5-0001GY-00
42 	for vasek@localhost; Tue, 09 Dec 2003 13:04:11 +0100
43 Delivered-To: alias-email-slavikvaclav@seznam.cz
44 Received: from pop3.seznam.cz [212.80.76.45]
45 	by localhost with POP3 (fetchmail-5.9.11)
46 	for vasek@localhost (single-drop); Tue, 09 Dec 2003 13:04:11 +0100 (CET)
47 Received: (qmail 9861 invoked from network); 9 Dec 2003 12:02:17 -0000
48 Received: from unknown (HELO maxipes.logix.cz) (81.0.234.97)
49   by buster.go.seznam.cz with SMTP; 9 Dec 2003 12:02:17 -0000
50 Received: by maxipes.logix.cz (Postfix, from userid 604)
51 	id 37E6D29A51; Tue,  9 Dec 2003 13:02:16 +0100 (CET)
52 X-Original-To: vaclav.slavik@matfyz.cz
53 Received: from mail.csbnet.se (glutus.csbnet.se [193.11.248.2])
54 	by maxipes.logix.cz (Postfix) with ESMTP id 90D6A29A51
55 	for <vaclav.slavik@matfyz.cz>; Tue,  9 Dec 2003 13:02:15 +0100 (CET)
56 Received: by mail.csbnet.se (Postfix, from userid 8)
57 	id 7AA7F10A6D7; Tue,  9 Dec 2003 13:02:14 +0100 (CET)
58 Received: from carbon.csbnet.se (carbon.csbnet.se [193.11.248.180])
59 	by mail.csbnet.se (Postfix) with ESMTP id A190F10A71D
60 	for <vaclav.slavik@matfyz.cz>; Tue,  9 Dec 2003 13:01:56 +0100 (CET)
61 Subject: Re: eggtrayicon.{c,h} licensing
62 From: Anders Carlsson <andersca@gnu.org>
63 To: Vaclav Slavik <vaclav.slavik@matfyz.cz>
64 In-Reply-To: <200312091142.54542.vaclav.slavik@matfyz.cz>
65 References: <200312091142.54542.vaclav.slavik@matfyz.cz>
66 Content-Type: text/plain
67 Message-Id: <1070971316.30989.0.camel@carbon.csbnet.se>
68 Mime-Version: 1.0
69 X-Mailer: Ximian Evolution 1.5
70 Date: Tue, 09 Dec 2003 13:01:56 +0100
71 Content-Transfer-Encoding: 7bit
72 X-Scanned-By: CLAM (openantivirus DB) antivirus scanner at mail.csbnet.se
73 X-Virus-Scanned: by amavisd-new-20030616-p5 (Debian) at armitage
74 X-Spam-Status: No, hits=-4.9 tagged_above=-999.0 required=6.3 tests=BAYES_00
75 X-Spam-Level:
76 Status: R
77 X-Status: N
78 X-KMail-EncryptionState:
79 X-KMail-SignatureState:
80 
81 On tis, 2003-12-09 at 11:42 +0100, Vaclav Slavik wrote:
82 > Hi,
83 >
84 > I'm working on the wxWindows cross-platform GUI toolkit
85 > (http://www.wxwindows.org) which uses GTK+ and it would save me a lot
86 > of time if I could use your eggtrayicon code to implement tray icons
87 > on X11. Unfortunately I can't use it right now because it is not part
88 > of any library we could depend on (as we do depend on GTK+) and would
89 > have to be included in our sources and it is under the LGPL license.
90 > The problem is that wxWindows' license is more permissive (see
91 > http://www.opensource.org/licenses/wxwindows.php for details) and so
92 > I can't take your code and put it under wxWindows licence. And I
93 > can't put code that can't be used under the terms of wxWindows
94 > License into wxWindows either. Do you think it would be possible to
95 > get permission to include eggtrayicon under wxWindows licence?
96 >
97 > Thanks,
98 > Vaclav
99 >
100 
101 Sure, that's fine by me.
102 
103 Anders
104 --------
105 */
106 
107 #include "wx/platform.h"
108 
109 #include <gdk/gdkx.h>
110 
111 #ifdef __WXGTK20__
112 #include <gtk/gtkversion.h>
113 #if GTK_CHECK_VERSION(2, 1, 0)
114 
115 #include <string.h>
116 #include "eggtrayicon.h"
117 
118 #define SYSTEM_TRAY_REQUEST_DOCK    0
119 #define SYSTEM_TRAY_BEGIN_MESSAGE   1
120 #define SYSTEM_TRAY_CANCEL_MESSAGE  2
121 
122 static GtkPlugClass *parent_class = NULL;
123 
124 static void egg_tray_icon_init (EggTrayIcon *icon);
125 static void egg_tray_icon_class_init (EggTrayIconClass *klass);
126 
127 static void egg_tray_icon_unrealize (GtkWidget *widget);
128 
129 static void egg_tray_icon_update_manager_window (EggTrayIcon *icon);
130 
131 GType
egg_tray_icon_get_type(void)132 egg_tray_icon_get_type (void)
133 {
134   static GType our_type = 0;
135 
136   if (our_type == 0)
137     {
138       const GTypeInfo our_info =
139       {
140 	sizeof (EggTrayIconClass),
141 	(GBaseInitFunc) NULL,
142 	(GBaseFinalizeFunc) NULL,
143 	(GClassInitFunc) egg_tray_icon_class_init,
144 	NULL, /* class_finalize */
145 	NULL, /* class_data */
146 	sizeof (EggTrayIcon),
147 	0,    /* n_preallocs */
148 	(GInstanceInitFunc) egg_tray_icon_init
149       };
150 
151       our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon",
152                                           &our_info, (GTypeFlags)0);
153     }
154 
155   return our_type;
156 }
157 
158 static void
egg_tray_icon_init(EggTrayIcon * icon)159 egg_tray_icon_init (EggTrayIcon *icon)
160 {
161   icon->stamp = 1;
162 
163   gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
164 }
165 
166 static void
egg_tray_icon_class_init(EggTrayIconClass * klass)167 egg_tray_icon_class_init (EggTrayIconClass *klass)
168 {
169   GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
170 
171   parent_class = g_type_class_peek_parent (klass);
172 
173   widget_class->unrealize = egg_tray_icon_unrealize;
174 }
175 
176 static GdkFilterReturn
egg_tray_icon_manager_filter(GdkXEvent * xevent,GdkEvent * event,gpointer user_data)177 egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
178 {
179   EggTrayIcon *icon = user_data;
180   XEvent *xev = (XEvent *)xevent;
181 
182   if (xev->xany.type == ClientMessage &&
183       xev->xclient.message_type == icon->manager_atom &&
184       xev->xclient.data.l[1] == icon->selection_atom)
185     {
186       egg_tray_icon_update_manager_window (icon);
187     }
188   else if (xev->xany.window == icon->manager_window)
189     {
190       if (xev->xany.type == DestroyNotify)
191 	{
192 	  egg_tray_icon_update_manager_window (icon);
193 	}
194     }
195 
196   return GDK_FILTER_CONTINUE;
197 }
198 
199 static void
egg_tray_icon_unrealize(GtkWidget * widget)200 egg_tray_icon_unrealize (GtkWidget *widget)
201 {
202   EggTrayIcon *icon = EGG_TRAY_ICON (widget);
203   GdkWindow *root_window;
204 
205   if (icon->manager_window != None)
206     {
207       GdkWindow *gdkwin;
208 
209       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
210                                               icon->manager_window);
211 
212       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
213     }
214 
215   root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
216 
217   gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);
218 
219   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
220     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
221 }
222 
223 static void
egg_tray_icon_send_manager_message(EggTrayIcon * icon,long message,Window window,long data1,long data2,long data3)224 egg_tray_icon_send_manager_message (EggTrayIcon *icon,
225 				    long         message,
226 				    Window       window,
227 				    long         data1,
228 				    long         data2,
229 				    long         data3)
230 {
231   XClientMessageEvent ev;
232   Display *display;
233 
234   ev.type = ClientMessage;
235   ev.window = window;
236   ev.message_type = icon->system_tray_opcode_atom;
237   ev.format = 32;
238   ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
239   ev.data.l[1] = message;
240   ev.data.l[2] = data1;
241   ev.data.l[3] = data2;
242   ev.data.l[4] = data3;
243 
244   display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
245 
246   gdk_error_trap_push ();
247   XSendEvent (display,
248 	      icon->manager_window, False, NoEventMask, (XEvent *)&ev);
249   XSync (display, False);
250   gdk_error_trap_pop ();
251 }
252 
253 static void
egg_tray_icon_send_dock_request(EggTrayIcon * icon)254 egg_tray_icon_send_dock_request (EggTrayIcon *icon)
255 {
256   egg_tray_icon_send_manager_message (icon,
257 				      SYSTEM_TRAY_REQUEST_DOCK,
258 				      icon->manager_window,
259 				      gtk_plug_get_id (GTK_PLUG (icon)),
260 				      0, 0);
261 }
262 
263 static void
egg_tray_icon_update_manager_window(EggTrayIcon * icon)264 egg_tray_icon_update_manager_window (EggTrayIcon *icon)
265 {
266   Display *xdisplay;
267 
268   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
269 
270   if (icon->manager_window != None)
271     {
272       GdkWindow *gdkwin;
273 
274       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
275 					      icon->manager_window);
276 
277       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
278     }
279 
280   XGrabServer (xdisplay);
281 
282   icon->manager_window = XGetSelectionOwner (xdisplay,
283 					     icon->selection_atom);
284 
285   if (icon->manager_window != None)
286     XSelectInput (xdisplay,
287 		  icon->manager_window, StructureNotifyMask);
288 
289   XUngrabServer (xdisplay);
290   XFlush (xdisplay);
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_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
300 
301       /* Send a request that we'd like to dock */
302       egg_tray_icon_send_dock_request (icon);
303     }
304 }
305 
306 EggTrayIcon *
egg_tray_icon_new_for_xscreen(Screen * xscreen,const char * name)307 egg_tray_icon_new_for_xscreen (Screen *xscreen, const char *name)
308 {
309   EggTrayIcon *icon;
310   char buffer[256];
311   GdkWindow *root_window;
312   GdkDisplay *display;
313   GdkScreen *screen;
314 
315   g_return_val_if_fail (xscreen != NULL, NULL);
316 
317   icon = g_object_new (EGG_TYPE_TRAY_ICON, NULL);
318   gtk_window_set_title (GTK_WINDOW (icon), name);
319 
320   display = gdk_x11_lookup_xdisplay (DisplayOfScreen (xscreen));
321   screen = gdk_display_get_screen (display, XScreenNumberOfScreen (xscreen));
322 
323   gtk_plug_construct_for_display (GTK_PLUG (icon),
324 				  display, 0);
325 
326   gtk_window_set_screen (GTK_WINDOW (icon), screen);
327 
328   gtk_widget_realize (GTK_WIDGET (icon));
329 
330   /* Now see if there's a manager window around */
331   g_snprintf (buffer, sizeof (buffer),
332 	      "_NET_SYSTEM_TRAY_S%d",
333 	      XScreenNumberOfScreen (xscreen));
334 
335   icon->selection_atom = XInternAtom (DisplayOfScreen (xscreen),
336 				      buffer, False);
337 
338   icon->manager_atom = XInternAtom (DisplayOfScreen (xscreen),
339 				    "MANAGER", False);
340 
341   icon->system_tray_opcode_atom = XInternAtom (DisplayOfScreen (xscreen),
342 					       "_NET_SYSTEM_TRAY_OPCODE", False);
343 
344   egg_tray_icon_update_manager_window (icon);
345 
346   root_window = gdk_screen_get_root_window (gtk_widget_get_screen (GTK_WIDGET (icon)));
347 
348   /* Add a root window filter so that we get changes on MANAGER */
349   gdk_window_add_filter (root_window,
350 			 egg_tray_icon_manager_filter, icon);
351 
352   return icon;
353 }
354 
355 EggTrayIcon *
egg_tray_icon_new_for_screen(GdkScreen * screen,const char * name)356 egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
357 {
358   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
359 
360   return egg_tray_icon_new_for_xscreen (GDK_SCREEN_XSCREEN (screen), name);
361 }
362 
363 EggTrayIcon*
egg_tray_icon_new(const gchar * name)364 egg_tray_icon_new (const gchar *name)
365 {
366   return egg_tray_icon_new_for_xscreen (DefaultScreenOfDisplay (gdk_display), name);
367 }
368 
369 guint
egg_tray_icon_send_message(EggTrayIcon * icon,gint timeout,const gchar * message,gint len)370 egg_tray_icon_send_message (EggTrayIcon *icon,
371 			    gint         timeout,
372 			    const gchar *message,
373 			    gint         len)
374 {
375   guint stamp;
376 
377   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
378   g_return_val_if_fail (timeout >= 0, 0);
379   g_return_val_if_fail (message != NULL, 0);
380 
381   if (icon->manager_window == None)
382     return 0;
383 
384   if (len < 0)
385     len = strlen (message);
386 
387   stamp = icon->stamp++;
388 
389   /* Get ready to send the message */
390   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
391 				      (Window)gtk_plug_get_id (GTK_PLUG (icon)),
392 				      timeout, len, stamp);
393 
394   /* Now to send the actual message */
395   gdk_error_trap_push ();
396   while (len > 0)
397     {
398       XClientMessageEvent ev;
399       Display *xdisplay;
400 
401       xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
402 
403       ev.type = ClientMessage;
404       ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
405       ev.format = 8;
406       ev.message_type = XInternAtom (xdisplay,
407 				     "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
408       if (len > 20)
409 	{
410 	  memcpy (&ev.data, message, 20);
411 	  len -= 20;
412 	  message += 20;
413 	}
414       else
415 	{
416 	  memcpy (&ev.data, message, len);
417 	  len = 0;
418 	}
419 
420       XSendEvent (xdisplay,
421 		  icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
422       XSync (xdisplay, False);
423     }
424   gdk_error_trap_pop ();
425 
426   return stamp;
427 }
428 
429 void
egg_tray_icon_cancel_message(EggTrayIcon * icon,guint id)430 egg_tray_icon_cancel_message (EggTrayIcon *icon,
431 			      guint        id)
432 {
433   g_return_if_fail (EGG_IS_TRAY_ICON (icon));
434   g_return_if_fail (id > 0);
435 
436   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
437 				      (Window)gtk_plug_get_id (GTK_PLUG (icon)),
438 				      id, 0, 0);
439 }
440 
441 #endif /* __WXGTK20__ */
442 #endif /* GTK_CHECK_VERSION(2, 1, 0) */
443