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