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