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