]> Pileus Git - ~andy/gtk/blob - gtk/gtktrayicon-x11.c
Include "config.h" instead of <config.h> Command used: find -name
[~andy/gtk] / gtk / gtktrayicon-x11.c
1 /* gtktrayicon.c
2  * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * This is an implementation of the freedesktop.org "system tray" spec,
22  * http://www.freedesktop.org/wiki/Standards/systemtray-spec
23  */
24
25 #include "config.h"
26 #include <string.h>
27
28 #include "gtkintl.h"
29 #include "gtkprivate.h"
30 #include "gtktrayicon.h"
31
32 #include "gtkalias.h"
33
34 #include "x11/gdkx.h"
35 #include <X11/Xatom.h>
36
37 #define SYSTEM_TRAY_REQUEST_DOCK    0
38 #define SYSTEM_TRAY_BEGIN_MESSAGE   1
39 #define SYSTEM_TRAY_CANCEL_MESSAGE  2
40
41 #define SYSTEM_TRAY_ORIENTATION_HORZ 0
42 #define SYSTEM_TRAY_ORIENTATION_VERT 1
43
44 enum {
45   PROP_0,
46   PROP_ORIENTATION
47 };
48
49 struct _GtkTrayIconPrivate
50 {
51   guint stamp;
52   
53   Atom selection_atom;
54   Atom manager_atom;
55   Atom system_tray_opcode_atom;
56   Atom orientation_atom;
57   Window manager_window;
58
59   GtkOrientation orientation;
60 };
61          
62 static void gtk_tray_icon_get_property  (GObject     *object,
63                                          guint        prop_id,
64                                          GValue      *value,
65                                          GParamSpec  *pspec);
66
67 static void     gtk_tray_icon_realize   (GtkWidget   *widget);
68 static void     gtk_tray_icon_unrealize (GtkWidget   *widget);
69 static gboolean gtk_tray_icon_delete    (GtkWidget   *widget,
70                                          GdkEventAny *event);
71 static gboolean gtk_tray_icon_expose    (GtkWidget      *widget, 
72                                          GdkEventExpose *event);
73
74 static void gtk_tray_icon_update_manager_window    (GtkTrayIcon *icon);
75 static void gtk_tray_icon_manager_window_destroyed (GtkTrayIcon *icon);
76
77 G_DEFINE_TYPE (GtkTrayIcon, gtk_tray_icon, GTK_TYPE_PLUG)
78
79 static void
80 gtk_tray_icon_class_init (GtkTrayIconClass *class)
81 {
82   GObjectClass *gobject_class = (GObjectClass *)class;
83   GtkWidgetClass *widget_class = (GtkWidgetClass *)class;
84
85   gobject_class->get_property = gtk_tray_icon_get_property;
86
87   widget_class->realize   = gtk_tray_icon_realize;
88   widget_class->unrealize = gtk_tray_icon_unrealize;
89   widget_class->delete_event = gtk_tray_icon_delete;
90   widget_class->expose_event = gtk_tray_icon_expose;
91
92   g_object_class_install_property (gobject_class,
93                                    PROP_ORIENTATION,
94                                    g_param_spec_enum ("orientation",
95                                                       P_("Orientation"),
96                                                       P_("The orientation of the tray"),
97                                                       GTK_TYPE_ORIENTATION,
98                                                       GTK_ORIENTATION_HORIZONTAL,
99                                                       GTK_PARAM_READABLE));
100
101   g_type_class_add_private (class, sizeof (GtkTrayIconPrivate));
102 }
103
104 static void
105 gtk_tray_icon_init (GtkTrayIcon *icon)
106 {
107   icon->priv = G_TYPE_INSTANCE_GET_PRIVATE (icon, GTK_TYPE_TRAY_ICON,
108                                             GtkTrayIconPrivate);
109   
110   icon->priv->stamp = 1;
111   icon->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
112
113   gtk_widget_set_app_paintable (GTK_WIDGET (icon), TRUE);
114   gtk_widget_set_double_buffered (GTK_WIDGET (icon), FALSE);
115   gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
116 }
117
118 static void
119 gtk_tray_icon_get_property (GObject    *object,
120                             guint       prop_id,
121                             GValue     *value,
122                             GParamSpec *pspec)
123 {
124   GtkTrayIcon *icon = GTK_TRAY_ICON (object);
125
126   switch (prop_id)
127     {
128     case PROP_ORIENTATION:
129       g_value_set_enum (value, icon->priv->orientation);
130       break;
131     default:
132       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
133       break;
134     }
135 }
136
137 static gboolean
138 gtk_tray_icon_expose (GtkWidget      *widget, 
139                       GdkEventExpose *event)
140 {
141   GtkWidget *focus_child;
142   gint border_width, x, y, width, height;
143   gboolean retval = FALSE;
144
145   gdk_window_clear_area (widget->window, event->area.x, event->area.y,
146                          event->area.width, event->area.height);
147
148   if (GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->expose_event)
149     retval = GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->expose_event (widget, event);
150
151   focus_child = GTK_CONTAINER (widget)->focus_child;
152   if (focus_child && GTK_WIDGET_HAS_FOCUS (focus_child))
153     {
154       border_width = GTK_CONTAINER (widget)->border_width;
155
156       x = widget->allocation.x + border_width;
157       y = widget->allocation.y + border_width;
158
159       width  = widget->allocation.width  - 2 * border_width;
160       height = widget->allocation.height - 2 * border_width;
161
162       gtk_paint_focus (widget->style, widget->window,
163                        GTK_WIDGET_STATE (widget),
164                        &event->area, widget, "tray_icon",
165                        x, y, width, height);
166     }
167
168   return retval;
169 }
170
171 static void
172 gtk_tray_icon_get_orientation_property (GtkTrayIcon *icon)
173 {
174   Display *xdisplay;
175   Atom type;
176   int format;
177   union {
178         gulong *prop;
179         guchar *prop_ch;
180   } prop = { NULL };
181   gulong nitems;
182   gulong bytes_after;
183   int error, result;
184
185   g_assert (icon->priv->manager_window != None);
186   
187   xdisplay = 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->priv->manager_window,
193                                icon->priv->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     {
205       GtkOrientation orientation;
206
207       orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
208                                         GTK_ORIENTATION_HORIZONTAL :
209                                         GTK_ORIENTATION_VERTICAL;
210
211       if (icon->priv->orientation != orientation)
212         {
213           icon->priv->orientation = orientation;
214
215           g_object_notify (G_OBJECT (icon), "orientation");
216         }
217     }
218
219   if (prop.prop)
220     XFree (prop.prop);
221 }
222
223 static GdkFilterReturn
224 gtk_tray_icon_manager_filter (GdkXEvent *xevent, 
225                               GdkEvent  *event, 
226                               gpointer   user_data)
227 {
228   GtkTrayIcon *icon = user_data;
229   XEvent *xev = (XEvent *)xevent;
230
231   if (xev->xany.type == ClientMessage &&
232       xev->xclient.message_type == icon->priv->manager_atom &&
233       xev->xclient.data.l[1] == icon->priv->selection_atom)
234     {
235       GTK_NOTE (PLUGSOCKET,
236                 g_print ("GtkStatusIcon %p: tray manager appeared\n", icon));
237
238       gtk_tray_icon_update_manager_window (icon);
239     }
240   else if (xev->xany.window == icon->priv->manager_window)
241     {
242       if (xev->xany.type == PropertyNotify &&
243           xev->xproperty.atom == icon->priv->orientation_atom)
244         {
245           GTK_NOTE (PLUGSOCKET,
246                     g_print ("GtkStatusIcon %p: got PropertyNotify on manager window for orientation atom\n", icon));
247
248           gtk_tray_icon_get_orientation_property (icon);
249         }
250       else if (xev->xany.type == DestroyNotify)
251         {
252           GTK_NOTE (PLUGSOCKET,
253                     g_print ("GtkStatusIcon %p: got DestroyNotify for manager window\n", icon));
254
255           gtk_tray_icon_manager_window_destroyed (icon);
256         }
257       else
258         GTK_NOTE (PLUGSOCKET,
259                   g_print ("GtkStatusIcon %p: got other message on manager window\n", icon));
260     }
261   
262   return GDK_FILTER_CONTINUE;
263 }
264
265 static void
266 gtk_tray_icon_unrealize (GtkWidget *widget)
267 {
268   GtkTrayIcon *icon = GTK_TRAY_ICON (widget);
269   GdkWindow *root_window;
270
271   GTK_NOTE (PLUGSOCKET,
272             g_print ("GtkStatusIcon %p: unrealizing\n", icon));
273
274   if (icon->priv->manager_window != None)
275     {
276       GdkWindow *gdkwin;
277
278       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
279                                               icon->priv->manager_window);
280       
281       gdk_window_remove_filter (gdkwin, gtk_tray_icon_manager_filter, icon);
282
283       icon->priv->manager_window = None;
284     }
285
286   root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
287
288   gdk_window_remove_filter (root_window, gtk_tray_icon_manager_filter, icon);
289
290   GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->unrealize (widget);
291 }
292
293 static void
294 gtk_tray_icon_send_manager_message (GtkTrayIcon *icon,
295                                     long         message,
296                                     Window       window,
297                                     long         data1,
298                                     long         data2,
299                                     long         data3)
300 {
301   XClientMessageEvent ev;
302   Display *display;
303   
304   memset (&ev, 0, sizeof (ev));
305   ev.type = ClientMessage;
306   ev.window = window;
307   ev.message_type = icon->priv->system_tray_opcode_atom;
308   ev.format = 32;
309   ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
310   ev.data.l[1] = message;
311   ev.data.l[2] = data1;
312   ev.data.l[3] = data2;
313   ev.data.l[4] = data3;
314
315   display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
316   
317   gdk_error_trap_push ();
318   XSendEvent (display,
319               icon->priv->manager_window, False, NoEventMask, (XEvent *)&ev);
320   XSync (display, False);
321   gdk_error_trap_pop ();
322 }
323
324 static void
325 gtk_tray_icon_send_dock_request (GtkTrayIcon *icon)
326 {
327   GTK_NOTE (PLUGSOCKET,
328             g_print ("GtkStatusIcon %p: sending dock request to manager window %lx\n",
329                      icon, (gulong) icon->priv->manager_window));
330
331   gtk_tray_icon_send_manager_message (icon,
332                                       SYSTEM_TRAY_REQUEST_DOCK,
333                                       icon->priv->manager_window,
334                                       gtk_plug_get_id (GTK_PLUG (icon)),
335                                       0, 0);
336 }
337
338 static void
339 gtk_tray_icon_update_manager_window (GtkTrayIcon *icon)
340 {
341   Display *xdisplay;
342
343   g_return_if_fail (GTK_WIDGET_REALIZED (icon));
344
345   GTK_NOTE (PLUGSOCKET,
346             g_print ("GtkStatusIcon %p: updating tray icon manager window, current manager window: %lx\n",
347                      icon, (gulong) icon->priv->manager_window));
348
349   if (icon->priv->manager_window != None)
350     return;
351
352   GTK_NOTE (PLUGSOCKET,
353             g_print ("GtkStatusIcon %p: trying to find manager window\n", icon));
354
355   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
356   
357   XGrabServer (xdisplay);
358   
359   icon->priv->manager_window = XGetSelectionOwner (xdisplay,
360                                                    icon->priv->selection_atom);
361
362   if (icon->priv->manager_window != None)
363     XSelectInput (xdisplay,
364                   icon->priv->manager_window, StructureNotifyMask|PropertyChangeMask);
365
366   XUngrabServer (xdisplay);
367   XFlush (xdisplay);
368   
369   if (icon->priv->manager_window != None)
370     {
371       GdkWindow *gdkwin;
372
373       GTK_NOTE (PLUGSOCKET,
374                 g_print ("GtkStatusIcon %p: is being managed by window %lx\n",
375                                 icon, (gulong) icon->priv->manager_window));
376
377       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
378                                               icon->priv->manager_window);
379       
380       gdk_window_add_filter (gdkwin, gtk_tray_icon_manager_filter, icon);
381
382       gtk_tray_icon_send_dock_request (icon);
383
384       gtk_tray_icon_get_orientation_property (icon);
385     }
386   else
387     GTK_NOTE (PLUGSOCKET,
388               g_print ("GtkStatusIcon %p: no tray manager found\n", icon));
389 }
390
391 static void
392 gtk_tray_icon_manager_window_destroyed (GtkTrayIcon *icon)
393 {
394   GtkWidget *widget = GTK_WIDGET (icon);
395
396   g_return_if_fail (GTK_WIDGET_REALIZED (icon));
397   g_return_if_fail (icon->priv->manager_window != None);
398
399   GTK_NOTE (PLUGSOCKET,
400             g_print ("GtkStatusIcon %p: tray manager window destroyed\n", icon));
401
402   gtk_widget_hide (widget);
403   gtk_widget_unrealize (widget);
404
405   gtk_widget_show (widget);
406 }
407
408 static gboolean 
409 gtk_tray_icon_delete (GtkWidget   *widget,
410                       GdkEventAny *event)
411 {
412   GtkTrayIcon *icon = GTK_TRAY_ICON (widget);
413
414   GTK_NOTE (PLUGSOCKET,
415             g_print ("GtkStatusIcon %p: delete notify, tray manager window %lx\n",
416                      icon, (gulong) icon->priv->manager_window));
417
418   gtk_widget_hide (widget);
419   gtk_widget_unrealize (widget);
420
421   gtk_widget_show (widget);
422
423   return TRUE;
424 }
425
426 static void
427 gtk_tray_icon_realize (GtkWidget *widget)
428 {
429   GtkTrayIcon *icon = GTK_TRAY_ICON (widget);
430   GdkScreen *screen;
431   GdkDisplay *display;
432   Display *xdisplay;
433   char buffer[256];
434   GdkWindow *root_window;
435
436   GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->realize (widget);
437
438   GTK_NOTE (PLUGSOCKET,
439             g_print ("GtkStatusIcon %p: realized, window: %lx, socket window: %lx\n",
440                      widget,
441                      (gulong) GDK_WINDOW_XWINDOW (widget->window),
442                      GTK_PLUG (icon)->socket_window ?
443                              (gulong) GDK_WINDOW_XWINDOW (GTK_PLUG (icon)->socket_window) : 0UL));
444
445   screen = gtk_widget_get_screen (widget);
446   display = gdk_screen_get_display (screen);
447   xdisplay = gdk_x11_display_get_xdisplay (display);
448
449   /* Now see if there's a manager window around */
450   g_snprintf (buffer, sizeof (buffer),
451               "_NET_SYSTEM_TRAY_S%d",
452               gdk_screen_get_number (screen));
453
454   icon->priv->selection_atom = XInternAtom (xdisplay, buffer, False);
455   
456   icon->priv->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
457   
458   icon->priv->system_tray_opcode_atom = XInternAtom (xdisplay,
459                                                      "_NET_SYSTEM_TRAY_OPCODE",
460                                                      False);
461
462   icon->priv->orientation_atom = XInternAtom (xdisplay,
463                                               "_NET_SYSTEM_TRAY_ORIENTATION",
464                                               False);
465
466   gtk_tray_icon_update_manager_window (icon);
467
468   root_window = gdk_screen_get_root_window (screen);
469   
470   /* Add a root window filter so that we get changes on MANAGER */
471   gdk_window_add_filter (root_window,
472                          gtk_tray_icon_manager_filter, icon);
473 }
474
475 guint
476 _gtk_tray_icon_send_message (GtkTrayIcon *icon,
477                              gint         timeout,
478                              const gchar *message,
479                              gint         len)
480 {
481   guint stamp;
482   
483   g_return_val_if_fail (GTK_IS_TRAY_ICON (icon), 0);
484   g_return_val_if_fail (timeout >= 0, 0);
485   g_return_val_if_fail (message != NULL, 0);
486                      
487   if (icon->priv->manager_window == None)
488     return 0;
489
490   if (len < 0)
491     len = strlen (message);
492
493   stamp = icon->priv->stamp++;
494   
495   /* Get ready to send the message */
496   gtk_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
497                                       (Window)gtk_plug_get_id (GTK_PLUG (icon)),
498                                       timeout, len, stamp);
499
500   /* Now to send the actual message */
501   gdk_error_trap_push ();
502   while (len > 0)
503     {
504       XClientMessageEvent ev;
505       Display *xdisplay;
506
507       xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
508       
509       memset (&ev, 0, sizeof (ev));
510       ev.type = ClientMessage;
511       ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
512       ev.format = 8;
513       ev.message_type = XInternAtom (xdisplay,
514                                      "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
515       if (len > 20)
516         {
517           memcpy (&ev.data, message, 20);
518           len -= 20;
519           message += 20;
520         }
521       else
522         {
523           memcpy (&ev.data, message, len);
524           len = 0;
525         }
526
527       XSendEvent (xdisplay,
528                   icon->priv->manager_window, False, 
529                   StructureNotifyMask, (XEvent *)&ev);
530       XSync (xdisplay, False);
531     }
532
533   gdk_error_trap_pop ();
534
535   return stamp;
536 }
537
538 void
539 _gtk_tray_icon_cancel_message (GtkTrayIcon *icon,
540                                guint        id)
541 {
542   g_return_if_fail (GTK_IS_TRAY_ICON (icon));
543   g_return_if_fail (id > 0);
544   
545   gtk_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
546                                       (Window)gtk_plug_get_id (GTK_PLUG (icon)),
547                                       id, 0, 0);
548 }
549
550 GtkTrayIcon *
551 _gtk_tray_icon_new_for_screen (GdkScreen  *screen, 
552                                const gchar *name)
553 {
554   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
555
556   return g_object_new (GTK_TYPE_TRAY_ICON, 
557                        "screen", screen, 
558                        "title", name, 
559                        NULL);
560 }
561
562 GtkTrayIcon*
563 _gtk_tray_icon_new (const gchar *name)
564 {
565   return g_object_new (GTK_TYPE_TRAY_ICON, 
566                        "title", name, 
567                        NULL);
568 }
569
570 GtkOrientation
571 _gtk_tray_icon_get_orientation (GtkTrayIcon *icon)
572 {
573   g_return_val_if_fail (GTK_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
574
575   return icon->priv->orientation;
576 }
577
578
579 #define __GTK_TRAY_ICON_X11_C__
580 #include "gtkaliasdef.c"
581