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