1 /*
2  * Copyright (c) 2002      Anders Carlsson <andersca@gnu.org>
3  * Copyright (c) 2003-2004 Benedikt Meurer <benny@xfce.org>
4  * Copyright (c) 2003-2004 Olivier Fourdan <fourdan@xfce.org>
5  * Copyright (c) 2003-2006 Vincent Untz
6  * Copyright (c) 2007-2010 Nick Schermer <nick@xfce.org>
7  *
8  * This library is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the Free
10  * Software Foundation; either version 2 of the License, or (at your option)
11  * any later version.
12  *
13  * This library is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  * more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #ifdef HAVE_STRING_H
28 #include <string.h>
29 #endif
30 
31 #include <X11/Xlib.h>
32 #include <X11/Xatom.h>
33 
34 #include <gdk/gdk.h>
35 #include <gdk/gdkx.h>
36 #include <gtk/gtk.h>
37 
38 #include <common/panel-private.h>
39 #include <common/panel-debug.h>
40 
41 #include <libxfce4panel/libxfce4panel.h>
42 #include <libxfce4util/libxfce4util.h>
43 
44 #include "systray-manager.h"
45 #include "systray-socket.h"
46 #include "systray-marshal.h"
47 
48 
49 
50 #define XFCE_SYSTRAY_MANAGER_REQUEST_DOCK   0
51 #define XFCE_SYSTRAY_MANAGER_BEGIN_MESSAGE  1
52 #define XFCE_SYSTRAY_MANAGER_CANCEL_MESSAGE 2
53 
54 #define XFCE_SYSTRAY_MANAGER_ORIENTATION_HORIZONTAL 0
55 #define XFCE_SYSTRAY_MANAGER_ORIENTATION_VERTICAL   1
56 
57 
58 
59 static void            systray_manager_finalize                           (GObject             *object);
60 static void            systray_manager_remove_socket                      (gpointer             key,
61                                                                            gpointer             value,
62                                                                            gpointer             user_data);
63 static GdkFilterReturn systray_manager_window_filter                      (GdkXEvent           *xev,
64                                                                            GdkEvent            *event,
65                                                                            gpointer             user_data);
66 static GdkFilterReturn systray_manager_handle_client_message_opcode       (GdkXEvent           *xevent,
67                                                                            GdkEvent            *event,
68                                                                            gpointer             user_data);
69 static GdkFilterReturn systray_manager_handle_client_message_message_data (GdkXEvent           *xevent,
70                                                                            GdkEvent            *event,
71                                                                            gpointer             user_data);
72 static void            systray_manager_handle_begin_message               (SystrayManager      *manager,
73                                                                            XClientMessageEvent *xevent);
74 static void            systray_manager_handle_cancel_message              (SystrayManager      *manager,
75                                                                            XClientMessageEvent *xevent);
76 static void            systray_manager_handle_dock_request                (SystrayManager      *manager,
77                                                                            XClientMessageEvent *xevent);
78 static gboolean        systray_manager_handle_undock_request              (GtkSocket           *socket,
79                                                                            gpointer             user_data);
80 static void            systray_manager_set_visual                         (SystrayManager      *manager);
81 static void            systray_manager_set_colors_property                (SystrayManager      *manager);
82 static void            systray_manager_message_free                       (SystrayMessage      *message);
83 static void            systray_manager_message_remove_from_list           (SystrayManager      *manager,
84                                                                            XClientMessageEvent *xevent);
85 
86 
87 
88 enum
89 {
90   ICON_ADDED,
91   ICON_REMOVED,
92   MESSAGE_SENT,
93   MESSAGE_CANCELLED,
94   LOST_SELECTION,
95   LAST_SIGNAL
96 };
97 
98 struct _SystrayManagerClass
99 {
100   GObjectClass __parent__;
101 };
102 
103 struct _SystrayManager
104 {
105   GObject __parent__;
106 
107   /* invisible window */
108   GtkWidget      *invisible;
109 
110   /* list of client sockets */
111   GHashTable     *sockets;
112 
113   /* symbolic colors */
114   GdkColor fg;
115   GdkColor error;
116   GdkColor warning;
117   GdkColor success;
118 
119   /* orientation of the tray */
120   GtkOrientation  orientation;
121 
122   /* list of pending messages */
123   GSList         *messages;
124 
125   /* _net_system_tray_opcode atom */
126   Atom            opcode_atom;
127 
128   /* _net_system_tray_message_data atom */
129   Atom            data_atom;
130 
131   /* _net_system_tray_s%d atom */
132   GdkAtom         selection_atom;
133 };
134 
135 struct _SystrayMessage
136 {
137   /* message string */
138   gchar          *string;
139 
140   /* message id */
141   glong           id;
142 
143   /* x11 window */
144   Window          window;
145 
146   /* numb3rs */
147   glong           length;
148   glong           remaining_length;
149   glong           timeout;
150 };
151 
152 
153 
154 static guint  systray_manager_signals[LAST_SIGNAL];
155 
156 
157 
XFCE_PANEL_DEFINE_TYPE(SystrayManager,systray_manager,G_TYPE_OBJECT)158 XFCE_PANEL_DEFINE_TYPE (SystrayManager, systray_manager, G_TYPE_OBJECT)
159 
160 
161 
162 static void
163 systray_manager_class_init (SystrayManagerClass *klass)
164 {
165   GObjectClass *gobject_class;
166 
167   gobject_class = G_OBJECT_CLASS (klass);
168   gobject_class->finalize = systray_manager_finalize;
169 
170   systray_manager_signals[ICON_ADDED] =
171       g_signal_new (g_intern_static_string ("icon-added"),
172                     G_OBJECT_CLASS_TYPE (klass),
173                     G_SIGNAL_RUN_LAST,
174                     0, NULL, NULL,
175                     g_cclosure_marshal_VOID__OBJECT,
176                     G_TYPE_NONE, 1,
177                     GTK_TYPE_SOCKET);
178 
179   systray_manager_signals[ICON_REMOVED] =
180       g_signal_new (g_intern_static_string ("icon-removed"),
181                     G_OBJECT_CLASS_TYPE (klass),
182                     G_SIGNAL_RUN_LAST,
183                     0, NULL, NULL,
184                     g_cclosure_marshal_VOID__OBJECT,
185                     G_TYPE_NONE, 1,
186                     GTK_TYPE_SOCKET);
187 
188   systray_manager_signals[MESSAGE_SENT] =
189       g_signal_new (g_intern_static_string ("message-sent"),
190                     G_OBJECT_CLASS_TYPE (klass),
191                     G_SIGNAL_RUN_LAST,
192                     0, NULL, NULL,
193                     _systray_marshal_VOID__OBJECT_STRING_LONG_LONG,
194                     G_TYPE_NONE, 4,
195                     GTK_TYPE_SOCKET,
196                     G_TYPE_STRING,
197                     G_TYPE_LONG,
198                     G_TYPE_LONG);
199 
200   systray_manager_signals[MESSAGE_CANCELLED] =
201       g_signal_new (g_intern_static_string ("message-cancelled"),
202                     G_OBJECT_CLASS_TYPE (klass),
203                     G_SIGNAL_RUN_LAST,
204                     0, NULL, NULL,
205                     _systray_marshal_VOID__OBJECT_LONG,
206                     G_TYPE_NONE, 2,
207                     GTK_TYPE_SOCKET,
208                     G_TYPE_LONG);
209 
210   systray_manager_signals[LOST_SELECTION] =
211       g_signal_new (g_intern_static_string ("lost-selection"),
212                     G_OBJECT_CLASS_TYPE (klass),
213                     G_SIGNAL_RUN_LAST,
214                     0, NULL, NULL,
215                     g_cclosure_marshal_VOID__VOID,
216                     G_TYPE_NONE, 0);
217 }
218 
219 
220 
221 static void
systray_manager_init(SystrayManager * manager)222 systray_manager_init (SystrayManager *manager)
223 {
224   manager->invisible = NULL;
225   manager->orientation = GTK_ORIENTATION_HORIZONTAL;
226   manager->messages = NULL;
227   manager->sockets = g_hash_table_new (NULL, NULL);
228 
229   /* initialize symbolic colors */
230   manager->fg.red = 0.0;
231   manager->fg.green = 0.0;
232   manager->fg.blue = 0.0;
233 
234   manager->error.red = 1.0;
235   manager->error.green = 0.0;
236   manager->error.blue = 0.0;
237 
238   manager->warning.red = 1.0;
239   manager->warning.green = 1.0;
240   manager->warning.blue = 0.0;
241 
242   manager->success.red = 0.0;
243   manager->success.green = 1.0;
244   manager->success.blue = 0.0;
245 }
246 
247 
248 
249 GQuark
systray_manager_error_quark(void)250 systray_manager_error_quark (void)
251 {
252   static GQuark q = 0;
253 
254   if (q == 0)
255     q = g_quark_from_static_string ("systray-manager-error-quark");
256 
257   return q;
258 }
259 
260 
261 
262 static void
systray_manager_finalize(GObject * object)263 systray_manager_finalize (GObject *object)
264 {
265   SystrayManager *manager = XFCE_SYSTRAY_MANAGER (object);
266 
267   panel_return_if_fail (manager->invisible == NULL);
268 
269   /* destroy the hash table */
270   g_hash_table_destroy (manager->sockets);
271 
272   if (manager->messages)
273     {
274       /* cleanup all pending messages */
275       g_slist_foreach (manager->messages,
276                        (GFunc) (void (*)(void)) systray_manager_message_free, NULL);
277 
278       /* free the list */
279       g_slist_free (manager->messages);
280     }
281 
282   G_OBJECT_CLASS (systray_manager_parent_class)->finalize (object);
283 }
284 
285 
286 
287 SystrayManager *
systray_manager_new(void)288 systray_manager_new (void)
289 {
290   return g_object_new (XFCE_TYPE_SYSTRAY_MANAGER, NULL);
291 }
292 
293 
294 
295 #if 0
296 gboolean
297 systray_manager_check_running (GdkScreen *screen)
298 {
299   gchar      *selection_name;
300   GdkDisplay *display;
301   Atom        selection_atom;
302 
303   panel_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE);
304 
305   /* get the display */
306   display = gdk_screen_get_display (screen);
307 
308   /* create the selection atom name */
309   selection_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d",
310                                     gdk_screen_get_number (screen));
311 
312   /* get the atom */
313   selection_atom = gdk_x11_get_xatom_by_name_for_display (display,
314                                                           selection_name);
315 
316   g_free (selection_name);
317 
318   /* return result */
319   return (XGetSelectionOwner (GDK_DISPLAY_XDISPLAY (display), selection_atom) != None);
320 }
321 #endif
322 
323 
324 
325 gboolean
systray_manager_register(SystrayManager * manager,GdkScreen * screen,GError ** error)326 systray_manager_register (SystrayManager  *manager,
327                           GdkScreen       *screen,
328                           GError         **error)
329 {
330   GdkDisplay          *display;
331   gchar               *selection_name;
332   gboolean             succeed;
333   gint                 screen_number;
334   GtkWidget           *invisible;
335   guint32              timestamp;
336   GdkAtom              opcode_atom;
337   GdkAtom              data_atom;
338   XClientMessageEvent  xevent;
339   Window               root_window;
340 
341   panel_return_val_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager), FALSE);
342   panel_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE);
343   panel_return_val_if_fail (error == NULL || *error == NULL, FALSE);
344 
345   /* create invisible window */
346   invisible = gtk_invisible_new_for_screen (screen);
347   gtk_widget_realize (invisible);
348 
349   /* let the invisible window monitor property and configuration changes */
350   gtk_widget_add_events (invisible, GDK_PROPERTY_CHANGE_MASK | GDK_STRUCTURE_MASK);
351 
352   /* get the screen number */
353 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
354   screen_number = gdk_screen_get_number (screen);
355 G_GNUC_END_IGNORE_DEPRECATIONS
356 
357   /* create the selection atom name */
358   selection_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d", screen_number);
359 
360   /* get the selection atom */
361   manager->selection_atom = gdk_atom_intern (selection_name, FALSE);
362 
363   g_free (selection_name);
364 
365   /* get the display */
366   display = gdk_screen_get_display (screen);
367 
368   /* set the invisible window and take a reference */
369   manager->invisible = GTK_WIDGET (g_object_ref (G_OBJECT (invisible)));
370 
371   /* set the visual property for transparent tray icons */
372   systray_manager_set_visual (manager);
373 
374   /* set the property for symbolic color support */
375   systray_manager_set_colors_property (manager);
376 
377   /* get the current x server time stamp */
378   timestamp = gdk_x11_get_server_time (gtk_widget_get_window (GTK_WIDGET (invisible)));
379 
380   /* try to become the selection owner of this display */
381   succeed = gdk_selection_owner_set_for_display (display,
382                                                  gtk_widget_get_window (GTK_WIDGET (invisible)),
383                                                  manager->selection_atom,
384                                                  timestamp, TRUE);
385 
386   if (G_LIKELY (succeed))
387     {
388       /* get the root window */
389       root_window = RootWindowOfScreen (GDK_SCREEN_XSCREEN (screen));
390 
391       /* send a message to x11 that we're going to handle this display */
392       xevent.type = ClientMessage;
393       xevent.window = root_window;
394       xevent.message_type = gdk_x11_get_xatom_by_name_for_display (display, "MANAGER");
395       xevent.format = 32;
396       xevent.data.l[0] = timestamp;
397       xevent.data.l[1] = gdk_x11_atom_to_xatom_for_display (display,
398                                                             manager->selection_atom);
399       xevent.data.l[2] = GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (invisible)));
400       xevent.data.l[3] = 0;
401       xevent.data.l[4] = 0;
402 
403       /* send the message */
404       XSendEvent (GDK_DISPLAY_XDISPLAY (display), root_window,
405                   False, StructureNotifyMask, (XEvent *)&xevent);
406 
407       /* system_tray_request_dock, system_tray_begin_message, system_tray_cancel_message and selectionclear */
408       gdk_window_add_filter (gtk_widget_get_window (GTK_WIDGET (invisible)),
409                              systray_manager_window_filter, manager);
410 
411       /* get the opcode atom (for both gdk and x11) */
412       opcode_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_OPCODE", FALSE);
413       manager->opcode_atom = gdk_x11_atom_to_xatom_for_display (display, opcode_atom);
414 
415       data_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_MESSAGE_DATA", FALSE);
416       manager->data_atom = gdk_x11_atom_to_xatom_for_display (display, data_atom);
417 
418       panel_debug (PANEL_DEBUG_SYSTRAY, "registered manager on screen %d", screen_number);
419     }
420   else
421     {
422       /* release the invisible */
423       g_object_unref (G_OBJECT (manager->invisible));
424       manager->invisible = NULL;
425 
426       /* desktroy the invisible window */
427       gtk_widget_destroy (invisible);
428 
429       /* set an error */
430       g_set_error (error, XFCE_SYSTRAY_MANAGER_ERROR,
431                    XFCE_SYSTRAY_MANAGER_ERROR_SELECTION_FAILED,
432                    _("Failed to acquire manager selection for screen %d"),
433                    screen_number);
434     }
435 
436   return succeed;
437 }
438 
439 
440 
441 static void
systray_manager_remove_socket(gpointer key,gpointer value,gpointer user_data)442 systray_manager_remove_socket (gpointer key,
443                                gpointer value,
444                                gpointer user_data)
445 {
446   SystrayManager *manager = XFCE_SYSTRAY_MANAGER (user_data);
447   GtkSocket      *socket = GTK_SOCKET (value);
448 
449   panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
450   panel_return_if_fail (GTK_IS_SOCKET (socket));
451 
452   /* properly undock from the tray */
453   g_signal_emit (manager, systray_manager_signals[ICON_REMOVED], 0, socket);
454 }
455 
456 
457 
458 void
systray_manager_unregister(SystrayManager * manager)459 systray_manager_unregister (SystrayManager *manager)
460 {
461   GdkDisplay *display;
462   GtkWidget  *invisible = manager->invisible;
463   GdkWindow  *owner;
464 
465   panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
466 
467   /* leave when there is no invisible window */
468   if (G_UNLIKELY (invisible == NULL))
469     return;
470 
471   panel_return_if_fail (GTK_IS_INVISIBLE (invisible));
472   panel_return_if_fail (gtk_widget_get_realized (invisible));
473   panel_return_if_fail (GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (invisible))));
474 
475   /* get the display of the invisible window */
476   display = gtk_widget_get_display (invisible);
477 
478   /* remove our handling of the selection if we're the owner */
479   owner = gdk_selection_owner_get_for_display (display, manager->selection_atom);
480   if (owner == gtk_widget_get_window (GTK_WIDGET (invisible)))
481     {
482       gdk_selection_owner_set_for_display (display, NULL,
483                                            manager->selection_atom,
484                                            gdk_x11_get_server_time (gtk_widget_get_window (GTK_WIDGET (invisible))),
485                                            TRUE);
486     }
487 
488   /* remove window filter */
489   gdk_window_remove_filter (gtk_widget_get_window (GTK_WIDGET (invisible)),
490       systray_manager_window_filter, manager);
491 
492   /* remove all sockets from the hash table */
493   g_hash_table_foreach (manager->sockets,
494       systray_manager_remove_socket, manager);
495 
496   /* destroy and unref the invisible window */
497   manager->invisible = NULL;
498   gtk_widget_destroy (invisible);
499   g_object_unref (G_OBJECT (invisible));
500 
501   panel_debug (PANEL_DEBUG_SYSTRAY, "unregistered manager");
502 }
503 
504 
505 
506 static GdkFilterReturn
systray_manager_window_filter(GdkXEvent * xev,GdkEvent * event,gpointer user_data)507 systray_manager_window_filter (GdkXEvent *xev,
508                                GdkEvent  *event,
509                                gpointer   user_data)
510 {
511   XEvent         *xevent = (XEvent *)xev;
512   SystrayManager *manager = XFCE_SYSTRAY_MANAGER (user_data);
513 
514   panel_return_val_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager), GDK_FILTER_CONTINUE);
515 
516   if (xevent->type == ClientMessage)
517     {
518       if (xevent->xclient.message_type == manager->opcode_atom)
519         return systray_manager_handle_client_message_opcode (xevent, event, user_data);
520 
521       if (xevent->xclient.message_type == manager->data_atom)
522         return systray_manager_handle_client_message_message_data (xevent, event, user_data);
523     }
524   else if (xevent->type == SelectionClear)
525     {
526       /* emit the signal */
527       g_signal_emit (manager, systray_manager_signals[LOST_SELECTION], 0);
528 
529       /* unregister the manager */
530       systray_manager_unregister (manager);
531     }
532 
533   return GDK_FILTER_CONTINUE;
534 }
535 
536 
537 
538 static GdkFilterReturn
systray_manager_handle_client_message_opcode(GdkXEvent * xevent,GdkEvent * event,gpointer user_data)539 systray_manager_handle_client_message_opcode (GdkXEvent *xevent,
540                                               GdkEvent  *event,
541                                               gpointer   user_data)
542 {
543   XClientMessageEvent *xev;
544   SystrayManager      *manager = XFCE_SYSTRAY_MANAGER (user_data);
545 
546   panel_return_val_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager), GDK_FILTER_REMOVE);
547 
548   /* cast to x11 event */
549   xev = (XClientMessageEvent *) xevent;
550 
551   switch (xev->data.l[1])
552     {
553     case XFCE_SYSTRAY_MANAGER_REQUEST_DOCK:
554         systray_manager_handle_dock_request (manager, xev);
555         return GDK_FILTER_REMOVE;
556 
557     case XFCE_SYSTRAY_MANAGER_BEGIN_MESSAGE:
558         systray_manager_handle_begin_message (manager, xev);
559         return GDK_FILTER_REMOVE;
560 
561     case XFCE_SYSTRAY_MANAGER_CANCEL_MESSAGE:
562         systray_manager_handle_cancel_message (manager, xev);
563         return GDK_FILTER_REMOVE;
564 
565     default:
566         break;
567     }
568 
569   return GDK_FILTER_CONTINUE;
570 }
571 
572 
573 
574 static GdkFilterReturn
systray_manager_handle_client_message_message_data(GdkXEvent * xevent,GdkEvent * event,gpointer user_data)575 systray_manager_handle_client_message_message_data (GdkXEvent *xevent,
576                                                     GdkEvent  *event,
577                                                     gpointer   user_data)
578 {
579   XClientMessageEvent *xev = xevent;
580   SystrayManager      *manager = XFCE_SYSTRAY_MANAGER (user_data);
581   GSList              *li;
582   SystrayMessage      *message;
583   glong                length;
584   GtkSocket           *socket;
585 
586   panel_return_val_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager), GDK_FILTER_REMOVE);
587 
588   /* try to find the pending message in the list */
589   for (li = manager->messages; li != NULL; li = li->next)
590     {
591       message = li->data;
592 
593       if (xev->window == message->window)
594         {
595           /* copy the data of this message */
596           length = MIN (message->remaining_length, 20);
597           memcpy ((message->string + message->length - message->remaining_length), &xev->data, length);
598           message->remaining_length -= length;
599 
600           /* check if we have the complete message */
601           if (message->remaining_length == 0)
602             {
603               /* try to get the socket from the known tray icons */
604               socket = g_hash_table_lookup (manager->sockets, GUINT_TO_POINTER (message->window));
605 
606               if (G_LIKELY (socket))
607                 {
608                   /* known socket, send the signal */
609                   g_signal_emit (manager, systray_manager_signals[MESSAGE_SENT], 0,
610                                  socket, message->string, message->id, message->timeout);
611                 }
612 
613               /* delete the message from the list */
614               manager->messages = g_slist_delete_link (manager->messages, li);
615 
616               /* free the message */
617               systray_manager_message_free (message);
618             }
619 
620           /* stop searching */
621           break;
622         }
623     }
624 
625   return GDK_FILTER_REMOVE;
626 }
627 
628 
629 
630 static void
systray_manager_handle_begin_message(SystrayManager * manager,XClientMessageEvent * xevent)631 systray_manager_handle_begin_message (SystrayManager      *manager,
632                                       XClientMessageEvent *xevent)
633 {
634   GtkSocket      *socket;
635   SystrayMessage *message;
636   glong           length, timeout, id;
637 
638   panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
639 
640   /* try to find the window in the list of known tray icons */
641   socket = g_hash_table_lookup (manager->sockets, GUINT_TO_POINTER (xevent->window));
642 
643   /* unkown tray icon: ignore the message */
644   if (G_UNLIKELY (socket == NULL))
645     return;
646 
647   /* remove the same message from the list */
648   systray_manager_message_remove_from_list (manager, xevent);
649 
650   /* get some message information */
651   timeout = xevent->data.l[2];
652   length = xevent->data.l[3];
653   id = xevent->data.l[4];
654 
655   if (length == 0)
656     {
657       /* directly emit empty messages */
658       g_signal_emit (manager, systray_manager_signals[MESSAGE_SENT], 0,
659                      socket, "", id, timeout);
660     }
661   else
662     {
663       /* create new structure */
664       message = g_slice_new0 (SystrayMessage);
665 
666       /* set message data */
667       message->window           = xevent->window;
668       message->timeout          = timeout;
669       message->length           = length;
670       message->id               = id;
671       message->remaining_length = length;
672       message->string           = g_malloc (length + 1);
673       message->string[length]   = '\0';
674 
675       /* add this message to the list of pending messages */
676       manager->messages = g_slist_prepend (manager->messages, message);
677     }
678 }
679 
680 
681 
682 static void
systray_manager_handle_cancel_message(SystrayManager * manager,XClientMessageEvent * xevent)683 systray_manager_handle_cancel_message (SystrayManager      *manager,
684                                        XClientMessageEvent *xevent)
685 {
686   GtkSocket       *socket;
687   Window           window = xevent->data.l[2];
688 
689   panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
690 
691   /* remove the same message from the list */
692   systray_manager_message_remove_from_list (manager, xevent);
693 
694   /* try to find the window in the list of known tray icons */
695   socket = g_hash_table_lookup (manager->sockets, GUINT_TO_POINTER (xevent->window));
696 
697   /* emit the cancelled signal */
698   if (G_LIKELY (socket != NULL))
699     g_signal_emit (manager, systray_manager_signals[MESSAGE_CANCELLED],
700                    0, socket, window);
701 }
702 
703 
704 
705 static void
systray_manager_handle_dock_request(SystrayManager * manager,XClientMessageEvent * xevent)706 systray_manager_handle_dock_request (SystrayManager      *manager,
707                                      XClientMessageEvent *xevent)
708 {
709   GtkWidget       *socket;
710   GdkScreen       *screen;
711   Window           window = xevent->data.l[2];
712 
713   panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
714   panel_return_if_fail (GTK_IS_INVISIBLE (manager->invisible));
715 
716   /* check if we already have this window */
717   if (g_hash_table_lookup (manager->sockets, GUINT_TO_POINTER (window)) != NULL)
718     return;
719 
720   /* create the socket */
721   screen = gtk_widget_get_screen (manager->invisible);
722   socket = systray_socket_new (screen, window);
723   if (G_UNLIKELY (socket == NULL))
724     return;
725 
726   /* add the icon to the tray */
727   g_signal_emit (manager, systray_manager_signals[ICON_ADDED], 0, socket);
728 
729   /* check if the widget has been attached. if the widget has no
730      toplevel window, we cannot set the socket id. */
731   if (G_LIKELY (GTK_IS_WINDOW (gtk_widget_get_toplevel (socket))))
732     {
733       /* signal to monitor if the client is removed from the socket */
734       g_signal_connect (G_OBJECT (socket), "plug-removed",
735           G_CALLBACK (systray_manager_handle_undock_request), manager);
736 
737       /* register the xembed client window id for this socket */
738       gtk_socket_add_id (GTK_SOCKET (socket), window);
739 
740       /* add the socket to the list of known sockets */
741       g_hash_table_insert (manager->sockets, GUINT_TO_POINTER (window), socket);
742     }
743   else
744     {
745       /* warning */
746       g_warning ("No parent window set, destroying socket");
747 
748       /* not attached successfully, destroy it */
749       gtk_widget_destroy (socket);
750     }
751 }
752 
753 
754 
755 static gboolean
systray_manager_handle_undock_request(GtkSocket * socket,gpointer user_data)756 systray_manager_handle_undock_request (GtkSocket *socket,
757                                        gpointer   user_data)
758 {
759   SystrayManager  *manager = XFCE_SYSTRAY_MANAGER (user_data);
760   Window          *window;
761 
762   panel_return_val_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager), FALSE);
763 
764   /* remove the socket from the list */
765   window = systray_socket_get_window (XFCE_SYSTRAY_SOCKET (socket));
766   g_hash_table_remove (manager->sockets, GUINT_TO_POINTER (*window));
767 
768   /* emit signal that the socket will be removed */
769   g_signal_emit (manager, systray_manager_signals[ICON_REMOVED], 0, socket);
770 
771   /* destroy the socket */
772   return FALSE;
773 }
774 
775 
776 
777 static void
systray_manager_set_visual(SystrayManager * manager)778 systray_manager_set_visual (SystrayManager *manager)
779 {
780   GdkDisplay  *display;
781   GdkVisual   *visual;
782   Visual      *xvisual;
783   Atom         visual_atom;
784   gulong       data[1];
785   GdkScreen   *screen;
786 
787   panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
788   panel_return_if_fail (GTK_IS_INVISIBLE (manager->invisible));
789   panel_return_if_fail (GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (manager->invisible))));
790 
791   /* get invisible display and screen */
792   display = gtk_widget_get_display (manager->invisible);
793   screen = gtk_invisible_get_screen (GTK_INVISIBLE (manager->invisible));
794 
795   /* get the xatom for the visual property */
796   visual_atom = gdk_x11_get_xatom_by_name_for_display (display,
797       "_NET_SYSTEM_TRAY_VISUAL");
798 
799   visual = gdk_screen_get_rgba_visual (screen);
800   panel_debug (PANEL_DEBUG_SYSTRAY, "rgba visual is %p", visual);
801   if (visual != NULL)
802     {
803       /* use the rgba visual */
804       xvisual = GDK_VISUAL_XVISUAL (visual);
805     }
806   else
807     {
808       /* use the default visual for the screen */
809       xvisual = GDK_VISUAL_XVISUAL (gdk_screen_get_system_visual (screen));
810     }
811 
812   data[0] = XVisualIDFromVisual (xvisual);
813   XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
814                    GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (manager->invisible))),
815                    visual_atom,
816                    XA_VISUALID, 32,
817                    PropModeReplace,
818                    (guchar *) &data, 1);
819 }
820 
821 
822 
823 void
systray_manager_set_colors(SystrayManager * manager,GdkColor * fg,GdkColor * error,GdkColor * warning,GdkColor * success)824 systray_manager_set_colors (SystrayManager *manager,
825                             GdkColor       *fg,
826                             GdkColor       *error,
827                             GdkColor       *warning,
828                             GdkColor       *success)
829 {
830   panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
831 
832   manager->fg = *fg;
833   manager->error = *error;
834   manager->warning = *warning;
835   manager->success = *success;
836 
837   systray_manager_set_colors_property (manager);
838 }
839 
840 
841 
842 static void
systray_manager_set_colors_property(SystrayManager * manager)843 systray_manager_set_colors_property (SystrayManager *manager)
844 {
845   GdkWindow  *window;
846   GdkDisplay *display;
847   Atom        atom;
848   gulong      data[12];
849 
850   g_return_if_fail (manager->invisible != NULL);
851   window = gtk_widget_get_window (manager->invisible);
852   g_return_if_fail (window != NULL);
853 
854   display = gtk_widget_get_display (manager->invisible);
855   atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_SYSTEM_TRAY_COLORS");
856 
857   data[0] = manager->fg.red;
858   data[1] = manager->fg.green;
859   data[2] = manager->fg.blue;
860   data[3] = manager->error.red;
861   data[4] = manager->error.green;
862   data[5] = manager->error.blue;
863   data[6] = manager->warning.red;
864   data[7] = manager->warning.green;
865   data[8] = manager->warning.blue;
866   data[9] = manager->success.red;
867   data[10] = manager->success.green;
868   data[11] = manager->success.blue;
869 
870   XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
871                    GDK_WINDOW_XID (window),
872                    atom,
873                    XA_CARDINAL, 32,
874                    PropModeReplace,
875                    (guchar *) &data, 12);
876 }
877 
878 
879 
880 void
systray_manager_set_orientation(SystrayManager * manager,GtkOrientation orientation)881 systray_manager_set_orientation (SystrayManager *manager,
882                                  GtkOrientation  orientation)
883 {
884   GdkDisplay *display;
885   Atom        orientation_atom;
886   gulong      data[1];
887 
888   panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
889   panel_return_if_fail (GTK_IS_INVISIBLE (manager->invisible));
890   panel_return_if_fail (GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (manager->invisible))));
891 
892   /* set the new orientation */
893   manager->orientation = orientation;
894 
895   /* get invisible display */
896   display = gtk_widget_get_display (manager->invisible);
897 
898   /* get the xatom for the orientation property */
899   orientation_atom = gdk_x11_get_xatom_by_name_for_display (display,
900       "_NET_SYSTEM_TRAY_ORIENTATION");
901 
902   /* set the data we're going to send to x */
903   data[0] = (manager->orientation == GTK_ORIENTATION_HORIZONTAL ?
904              XFCE_SYSTRAY_MANAGER_ORIENTATION_HORIZONTAL
905              : XFCE_SYSTRAY_MANAGER_ORIENTATION_VERTICAL);
906 
907   /* change the x property */
908   XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
909                    GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (manager->invisible))),
910                    orientation_atom,
911                    XA_CARDINAL, 32,
912                    PropModeReplace,
913                    (guchar *) &data, 1);
914 }
915 
916 
917 
918 /**
919  * tray messages
920  **/
921 static void
systray_manager_message_free(SystrayMessage * message)922 systray_manager_message_free (SystrayMessage *message)
923 {
924   g_free (message->string);
925   g_slice_free (SystrayMessage, message);
926 }
927 
928 
929 
930 static void
systray_manager_message_remove_from_list(SystrayManager * manager,XClientMessageEvent * xevent)931 systray_manager_message_remove_from_list (SystrayManager      *manager,
932                                           XClientMessageEvent *xevent)
933 {
934   GSList         *li;
935   SystrayMessage *message;
936 
937   panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
938 
939   /* seach for the same message in the list of pending messages */
940   for (li = manager->messages; li != NULL; li = li->next)
941     {
942       message = li->data;
943 
944       /* check if this is the same message */
945       if (xevent->window == message->window && xevent->data.l[4] == message->id)
946         {
947           /* delete the message from the list */
948           manager->messages = g_slist_delete_link (manager->messages, li);
949 
950           /* free the message */
951           systray_manager_message_free (message);
952 
953           break;
954         }
955     }
956 }
957