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