1 /* na-tray-manager.c
2  * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
3  * Copyright (C) 2003-2006 Vincent Untz
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., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  * Used to be: eggtraymanager.c
21  */
22 
23 #include <config.h>
24 
25 #ifndef HAVE_X11
26 #error file should only be built when HAVE_X11 is enabled
27 #endif
28 
29 #include <string.h>
30 #include <libintl.h>
31 
32 #include "na-tray-manager.h"
33 
34 #include <gtk/gtk.h>
35 #include <glib/gi18n.h>
36 #include <gdk/gdkx.h>
37 #include <X11/Xatom.h>
38 
39 #include "na-marshal.h"
40 
41 /* Signals */
42 enum
43 {
44   TRAY_ICON_ADDED,
45   TRAY_ICON_REMOVED,
46   MESSAGE_SENT,
47   MESSAGE_CANCELLED,
48   LOST_SELECTION,
49   LAST_SIGNAL
50 };
51 
52 enum {
53   PROP_0,
54   PROP_ORIENTATION
55 };
56 
57 typedef struct
58 {
59   long id, len;
60   long remaining_len;
61 
62   long timeout;
63   char *str;
64 #ifdef GDK_WINDOWING_X11
65   Window window;
66 #endif
67 } PendingMessage;
68 
69 static guint manager_signals[LAST_SIGNAL] = { 0 };
70 
71 #define SYSTEM_TRAY_REQUEST_DOCK    0
72 #define SYSTEM_TRAY_BEGIN_MESSAGE   1
73 #define SYSTEM_TRAY_CANCEL_MESSAGE  2
74 
75 #define SYSTEM_TRAY_ORIENTATION_HORZ 0
76 #define SYSTEM_TRAY_ORIENTATION_VERT 1
77 
78 #ifdef GDK_WINDOWING_X11
79 static gboolean na_tray_manager_check_running_screen_x11 (GdkScreen *screen);
80 #endif
81 
82 static void na_tray_manager_finalize     (GObject      *object);
83 static void na_tray_manager_set_property (GObject      *object,
84 					  guint         prop_id,
85 					  const GValue *value,
86 					  GParamSpec   *pspec);
87 static void na_tray_manager_get_property (GObject      *object,
88 					  guint         prop_id,
89 					  GValue       *value,
90 					  GParamSpec   *pspec);
91 
92 static void na_tray_manager_unmanage (NaTrayManager *manager);
93 
G_DEFINE_TYPE(NaTrayManager,na_tray_manager,G_TYPE_OBJECT)94 G_DEFINE_TYPE (NaTrayManager, na_tray_manager, G_TYPE_OBJECT)
95 
96 static void
97 na_tray_manager_init (NaTrayManager *manager)
98 {
99   manager->invisible = NULL;
100   manager->socket_table = g_hash_table_new (NULL, NULL);
101 
102   manager->padding = 0;
103   manager->icon_size = 0;
104 
105   manager->fg.red = 0.0;
106   manager->fg.green = 0.0;
107   manager->fg.blue = 0.0;
108   manager->fg.alpha = 1.0;
109 
110   manager->error.red = 1.0;
111   manager->error.green = 0.0;
112   manager->error.blue = 0.0;
113   manager->error.alpha = 1.0;
114 
115   manager->warning.red = 1.0;
116   manager->warning.green = 1.0;
117   manager->warning.blue = 0.0;
118   manager->warning.alpha = 1.0;
119 
120   manager->success.red = 0.0;
121   manager->success.green = 1.0;
122   manager->success.blue = 0.0;
123   manager->success.alpha = 1.0;
124 }
125 
126 static void
na_tray_manager_class_init(NaTrayManagerClass * klass)127 na_tray_manager_class_init (NaTrayManagerClass *klass)
128 {
129   GObjectClass *gobject_class;
130 
131   gobject_class = (GObjectClass *)klass;
132 
133   gobject_class->finalize = na_tray_manager_finalize;
134   gobject_class->set_property = na_tray_manager_set_property;
135   gobject_class->get_property = na_tray_manager_get_property;
136 
137   g_object_class_install_property (gobject_class,
138 				   PROP_ORIENTATION,
139 				   g_param_spec_enum ("orientation",
140 						      "orientation",
141 						      "orientation",
142 						      GTK_TYPE_ORIENTATION,
143 						      GTK_ORIENTATION_HORIZONTAL,
144 						      G_PARAM_READWRITE |
145 						      G_PARAM_CONSTRUCT |
146 						      G_PARAM_STATIC_NAME |
147 						      G_PARAM_STATIC_NICK |
148 						      G_PARAM_STATIC_BLURB));
149 
150   manager_signals[TRAY_ICON_ADDED] =
151     g_signal_new ("tray_icon_added",
152 		  G_OBJECT_CLASS_TYPE (klass),
153 		  G_SIGNAL_RUN_LAST,
154 		  G_STRUCT_OFFSET (NaTrayManagerClass, tray_icon_added),
155 		  NULL, NULL,
156 		  g_cclosure_marshal_VOID__OBJECT,
157 		  G_TYPE_NONE, 1,
158 		  GTK_TYPE_SOCKET);
159 
160   manager_signals[TRAY_ICON_REMOVED] =
161     g_signal_new ("tray_icon_removed",
162 		  G_OBJECT_CLASS_TYPE (klass),
163 		  G_SIGNAL_RUN_LAST,
164 		  G_STRUCT_OFFSET (NaTrayManagerClass, tray_icon_removed),
165 		  NULL, NULL,
166 		  g_cclosure_marshal_VOID__OBJECT,
167 		  G_TYPE_NONE, 1,
168 		  GTK_TYPE_SOCKET);
169   manager_signals[MESSAGE_SENT] =
170     g_signal_new ("message_sent",
171 		  G_OBJECT_CLASS_TYPE (klass),
172 		  G_SIGNAL_RUN_LAST,
173 		  G_STRUCT_OFFSET (NaTrayManagerClass, message_sent),
174 		  NULL, NULL,
175 		  _na_marshal_VOID__OBJECT_STRING_LONG_LONG,
176 		  G_TYPE_NONE, 4,
177 		  GTK_TYPE_SOCKET,
178 		  G_TYPE_STRING,
179 		  G_TYPE_LONG,
180 		  G_TYPE_LONG);
181   manager_signals[MESSAGE_CANCELLED] =
182     g_signal_new ("message_cancelled",
183 		  G_OBJECT_CLASS_TYPE (klass),
184 		  G_SIGNAL_RUN_LAST,
185 		  G_STRUCT_OFFSET (NaTrayManagerClass, message_cancelled),
186 		  NULL, NULL,
187 		  _na_marshal_VOID__OBJECT_LONG,
188 		  G_TYPE_NONE, 2,
189 		  GTK_TYPE_SOCKET,
190 		  G_TYPE_LONG);
191   manager_signals[LOST_SELECTION] =
192     g_signal_new ("lost_selection",
193 		  G_OBJECT_CLASS_TYPE (klass),
194 		  G_SIGNAL_RUN_LAST,
195 		  G_STRUCT_OFFSET (NaTrayManagerClass, lost_selection),
196 		  NULL, NULL,
197 		  g_cclosure_marshal_VOID__VOID,
198 		  G_TYPE_NONE, 0);
199 }
200 
201 static void
na_tray_manager_finalize(GObject * object)202 na_tray_manager_finalize (GObject *object)
203 {
204   NaTrayManager *manager;
205 
206   manager = NA_TRAY_MANAGER (object);
207 
208   na_tray_manager_unmanage (manager);
209 
210   g_list_free (manager->messages);
211   g_hash_table_destroy (manager->socket_table);
212 
213   G_OBJECT_CLASS (na_tray_manager_parent_class)->finalize (object);
214 }
215 
216 static void
na_tray_manager_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)217 na_tray_manager_set_property (GObject      *object,
218 			      guint         prop_id,
219 			      const GValue *value,
220 			      GParamSpec   *pspec)
221 {
222   NaTrayManager *manager = NA_TRAY_MANAGER (object);
223 
224   switch (prop_id)
225     {
226     case PROP_ORIENTATION:
227       na_tray_manager_set_orientation (manager, g_value_get_enum (value));
228       break;
229     default:
230       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
231       break;
232     }
233 }
234 
235 static void
na_tray_manager_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)236 na_tray_manager_get_property (GObject    *object,
237 			      guint       prop_id,
238 			      GValue     *value,
239 			      GParamSpec *pspec)
240 {
241   NaTrayManager *manager = NA_TRAY_MANAGER (object);
242 
243   switch (prop_id)
244     {
245     case PROP_ORIENTATION:
246       g_value_set_enum (value, manager->orientation);
247       break;
248     default:
249       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
250       break;
251     }
252 }
253 
254 NaTrayManager *
na_tray_manager_new(void)255 na_tray_manager_new (void)
256 {
257   NaTrayManager *manager;
258 
259   manager = g_object_new (NA_TYPE_TRAY_MANAGER, NULL);
260 
261   return manager;
262 }
263 
264 #ifdef GDK_WINDOWING_X11
265 
266 static gboolean
na_tray_manager_plug_removed(GtkSocket * socket,NaTrayManager * manager)267 na_tray_manager_plug_removed (GtkSocket       *socket,
268 			      NaTrayManager   *manager)
269 {
270   NaTrayChild *child = NA_TRAY_CHILD (socket);
271 
272   g_hash_table_remove (manager->socket_table,
273                        GINT_TO_POINTER (child->icon_window));
274   g_signal_emit (manager, manager_signals[TRAY_ICON_REMOVED], 0, child);
275 
276   /* This destroys the socket. */
277   return FALSE;
278 }
279 
280 static void
na_tray_manager_handle_dock_request(NaTrayManager * manager,XClientMessageEvent * xevent)281 na_tray_manager_handle_dock_request (NaTrayManager       *manager,
282 				     XClientMessageEvent *xevent)
283 {
284   Window icon_window = xevent->data.l[2];
285   GtkWidget *child;
286 
287   if (g_hash_table_lookup (manager->socket_table,
288                            GINT_TO_POINTER (icon_window)))
289     {
290       /* We already got this notification earlier, ignore this one */
291       return;
292     }
293 
294   child = na_tray_child_new (manager->screen, icon_window);
295   if (child == NULL) /* already gone or other error */
296     return;
297 
298   g_signal_emit (manager, manager_signals[TRAY_ICON_ADDED], 0,
299 		 child);
300 
301   /* If the child wasn't attached, then destroy it */
302 
303   if (!GTK_IS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (child))))
304     {
305       gtk_widget_destroy (child);
306       return;
307     }
308 
309   g_signal_connect (child, "plug_removed",
310 		    G_CALLBACK (na_tray_manager_plug_removed), manager);
311 
312   gtk_socket_add_id (GTK_SOCKET (child), icon_window);
313 
314   if (!gtk_socket_get_plug_window (GTK_SOCKET (child)))
315     {
316       /* Embedding failed, we won't get a plug-removed signal */
317       /* This signal destroys the socket */
318       g_signal_emit (manager, manager_signals[TRAY_ICON_REMOVED], 0, child);
319       return;
320     }
321 
322   g_hash_table_insert (manager->socket_table,
323                        GINT_TO_POINTER (icon_window), child);
324   gtk_widget_show (child);
325 }
326 
327 static void
pending_message_free(PendingMessage * message)328 pending_message_free (PendingMessage *message)
329 {
330   g_free (message->str);
331   g_free (message);
332 }
333 
334 static void
na_tray_manager_handle_message_data(NaTrayManager * manager,XClientMessageEvent * xevent)335 na_tray_manager_handle_message_data (NaTrayManager *manager,
336                                      XClientMessageEvent *xevent)
337 {
338   GList               *p;
339   int                  len;
340 
341   /* Try to see if we can find the pending message in the list */
342   for (p = manager->messages; p; p = p->next)
343     {
344       PendingMessage *msg = p->data;
345 
346       if (xevent->window == msg->window)
347 	{
348 	  /* Append the message */
349 	  len = MIN (msg->remaining_len, 20);
350 
351 	  memcpy ((msg->str + msg->len - msg->remaining_len),
352 		  &xevent->data, len);
353 	  msg->remaining_len -= len;
354 
355 	  if (msg->remaining_len == 0)
356 	    {
357 	      GtkSocket *socket;
358 
359 	      socket = g_hash_table_lookup (manager->socket_table,
360                                             GINT_TO_POINTER (msg->window));
361 
362 	      if (socket)
363 		  g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0,
364 				 socket, msg->str, msg->id, msg->timeout);
365 
366 	      pending_message_free (msg);
367 	      manager->messages = g_list_remove_link (manager->messages, p);
368               g_list_free_1 (p);
369 	    }
370 
371           break;
372 	}
373     }
374 }
375 
376 static void
na_tray_manager_handle_begin_message(NaTrayManager * manager,XClientMessageEvent * xevent)377 na_tray_manager_handle_begin_message (NaTrayManager       *manager,
378 				      XClientMessageEvent *xevent)
379 {
380   GtkSocket      *socket;
381   GList          *p;
382   PendingMessage *msg;
383   long            timeout;
384   long            len;
385   long            id;
386 
387   socket = g_hash_table_lookup (manager->socket_table,
388                                 GINT_TO_POINTER (xevent->window));
389   /* we don't know about this tray icon, so ignore the message */
390   if (!socket)
391     return;
392 
393   timeout = xevent->data.l[2];
394   len     = xevent->data.l[3];
395   id      = xevent->data.l[4];
396 
397   /* Check if the same message is already in the queue and remove it if so */
398   for (p = manager->messages; p; p = p->next)
399     {
400       PendingMessage *pmsg = p->data;
401 
402       if (xevent->window == pmsg->window &&
403 	  id == pmsg->id)
404 	{
405 	  /* Hmm, we found it, now remove it */
406 	  pending_message_free (pmsg);
407 	  manager->messages = g_list_remove_link (manager->messages, p);
408           g_list_free_1 (p);
409 	  break;
410 	}
411     }
412 
413   if (len == 0)
414     {
415       g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0,
416                      socket, "", id, timeout);
417     }
418   else
419     {
420       /* Now add the new message to the queue */
421       msg = g_new0 (PendingMessage, 1);
422       msg->window = xevent->window;
423       msg->timeout = timeout;
424       msg->len = len;
425       msg->id = id;
426       msg->remaining_len = msg->len;
427       msg->str = g_malloc (msg->len + 1);
428       msg->str[msg->len] = '\0';
429       manager->messages = g_list_prepend (manager->messages, msg);
430     }
431 }
432 
433 static void
na_tray_manager_handle_cancel_message(NaTrayManager * manager,XClientMessageEvent * xevent)434 na_tray_manager_handle_cancel_message (NaTrayManager       *manager,
435 				       XClientMessageEvent *xevent)
436 {
437   GList     *p;
438   GtkSocket *socket;
439   long       id;
440 
441   id = xevent->data.l[2];
442 
443   /* Check if the message is in the queue and remove it if so */
444   for (p = manager->messages; p; p = p->next)
445     {
446       PendingMessage *msg = p->data;
447 
448       if (xevent->window == msg->window &&
449 	  id == msg->id)
450 	{
451 	  pending_message_free (msg);
452 	  manager->messages = g_list_remove_link (manager->messages, p);
453           g_list_free_1 (p);
454 	  break;
455 	}
456     }
457 
458   socket = g_hash_table_lookup (manager->socket_table,
459                                 GINT_TO_POINTER (xevent->window));
460 
461   if (socket)
462     {
463       g_signal_emit (manager, manager_signals[MESSAGE_CANCELLED], 0,
464 		     socket, xevent->data.l[2]);
465     }
466 }
467 
468 static GdkFilterReturn
na_tray_manager_window_filter(GdkXEvent * xev,GdkEvent * event,gpointer data)469 na_tray_manager_window_filter (GdkXEvent *xev,
470                                GdkEvent  *event,
471                                gpointer   data)
472 {
473   XEvent        *xevent = (GdkXEvent *)xev;
474   NaTrayManager *manager = data;
475 
476   if (xevent->type == ClientMessage)
477     {
478       /* We handle this client message here. See comment in
479        * na_tray_manager_handle_client_message_opcode() for details */
480       if (xevent->xclient.message_type == manager->opcode_atom &&
481           xevent->xclient.data.l[1]    == SYSTEM_TRAY_REQUEST_DOCK)
482 	{
483           na_tray_manager_handle_dock_request (manager,
484                                                (XClientMessageEvent *) xevent);
485           return GDK_FILTER_REMOVE;
486 	}
487       /* _NET_SYSTEM_TRAY_OPCODE: SYSTEM_TRAY_BEGIN_MESSAGE */
488       else if (xevent->xclient.message_type == manager->opcode_atom &&
489                xevent->xclient.data.l[1] == SYSTEM_TRAY_BEGIN_MESSAGE)
490         {
491           na_tray_manager_handle_begin_message (manager,
492                                                 (XClientMessageEvent *) event);
493           return GDK_FILTER_REMOVE;
494         }
495       /* _NET_SYSTEM_TRAY_OPCODE: SYSTEM_TRAY_CANCEL_MESSAGE */
496       else if (xevent->xclient.message_type == manager->opcode_atom &&
497                xevent->xclient.data.l[1] == SYSTEM_TRAY_CANCEL_MESSAGE)
498         {
499           na_tray_manager_handle_cancel_message (manager,
500                                                  (XClientMessageEvent *) event);
501           return GDK_FILTER_REMOVE;
502         }
503       /* _NET_SYSTEM_TRAY_MESSAGE_DATA */
504       else if (xevent->xclient.message_type == manager->message_data_atom)
505         {
506           na_tray_manager_handle_message_data (manager,
507                                                (XClientMessageEvent *) event);
508           return GDK_FILTER_REMOVE;
509         }
510     }
511   else if (xevent->type == SelectionClear)
512     {
513       g_signal_emit (manager, manager_signals[LOST_SELECTION], 0);
514       na_tray_manager_unmanage (manager);
515     }
516 
517   return GDK_FILTER_CONTINUE;
518 }
519 
520 #if 0
521 /* FIXME investigate why this doesn't work */
522 static gboolean
523 na_tray_manager_selection_clear_event (GtkWidget         *widget,
524                                        GdkEventSelection *event,
525                                        NaTrayManager     *manager)
526 {
527   g_signal_emit (manager, manager_signals[LOST_SELECTION], 0);
528   na_tray_manager_unmanage (manager);
529 
530   return FALSE;
531 }
532 #endif
533 #endif
534 
535 static void
na_tray_manager_unmanage(NaTrayManager * manager)536 na_tray_manager_unmanage (NaTrayManager *manager)
537 {
538 #ifdef GDK_WINDOWING_X11
539   GdkDisplay *display;
540   guint32     timestamp;
541   GtkWidget  *invisible;
542   GdkWindow  *window;
543 
544   if (manager->invisible == NULL)
545     return;
546 
547   invisible = manager->invisible;
548   window = gtk_widget_get_window (invisible);
549 
550   g_assert (GTK_IS_INVISIBLE (invisible));
551   g_assert (gtk_widget_get_realized (invisible));
552   g_assert (GDK_IS_WINDOW (window));
553 
554   display = gtk_widget_get_display (invisible);
555 
556   if (gdk_selection_owner_get_for_display (display, manager->selection_atom) ==
557       window)
558     {
559       timestamp = gdk_x11_get_server_time (window);
560       gdk_selection_owner_set_for_display (display,
561                                            NULL,
562                                            manager->selection_atom,
563                                            timestamp,
564                                            TRUE);
565     }
566 
567   gdk_window_remove_filter (window,
568                             na_tray_manager_window_filter, manager);
569 
570   manager->invisible = NULL; /* prior to destroy for reentrancy paranoia */
571   gtk_widget_destroy (invisible);
572   g_object_unref (G_OBJECT (invisible));
573 #endif
574 }
575 
576 static void
na_tray_manager_set_orientation_property(NaTrayManager * manager)577 na_tray_manager_set_orientation_property (NaTrayManager *manager)
578 {
579 #ifdef GDK_WINDOWING_X11
580   GdkWindow  *window;
581   GdkDisplay *display;
582   Atom        orientation_atom;
583   gulong      data[1];
584 
585   g_return_if_fail (manager->invisible != NULL);
586   window = gtk_widget_get_window (manager->invisible);
587   g_return_if_fail (window != NULL);
588 
589   display = gtk_widget_get_display (manager->invisible);
590   orientation_atom = gdk_x11_get_xatom_by_name_for_display (display,
591                                                             "_NET_SYSTEM_TRAY_ORIENTATION");
592 
593   data[0] = manager->orientation == GTK_ORIENTATION_HORIZONTAL ?
594 		SYSTEM_TRAY_ORIENTATION_HORZ :
595 		SYSTEM_TRAY_ORIENTATION_VERT;
596 
597   XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
598 		   GDK_WINDOW_XID (window),
599                    orientation_atom,
600 		   XA_CARDINAL, 32,
601 		   PropModeReplace,
602 		   (guchar *) &data, 1);
603 #endif
604 }
605 
606 static void
na_tray_manager_set_visual_property(NaTrayManager * manager)607 na_tray_manager_set_visual_property (NaTrayManager *manager)
608 {
609 #ifdef GDK_WINDOWING_X11
610   GdkWindow  *window;
611   GdkDisplay *display;
612   Visual     *xvisual;
613   Atom        visual_atom;
614   gulong      data[1];
615 
616   g_return_if_fail (manager->invisible != NULL);
617   window = gtk_widget_get_window (manager->invisible);
618   g_return_if_fail (window != NULL);
619 
620   /* The visual property is a hint to the tray icons as to what visual they
621    * should use for their windows. If the X server has RGBA colormaps, then
622    * we tell the tray icons to use a RGBA colormap and we'll composite the
623    * icon onto its parents with real transparency. Otherwise, we just tell
624    * the icon to use our colormap, and we'll do some hacks with parent
625    * relative backgrounds to simulate transparency.
626    */
627 
628   display = gtk_widget_get_display (manager->invisible);
629   visual_atom = gdk_x11_get_xatom_by_name_for_display (display,
630 						       "_NET_SYSTEM_TRAY_VISUAL");
631 
632   if (gdk_screen_get_rgba_visual (manager->screen) != NULL &&
633       gdk_display_supports_composite (display))
634     {
635       xvisual = GDK_VISUAL_XVISUAL (gdk_screen_get_rgba_visual (manager->screen));
636     }
637   else
638     {
639       /* We actually want the visual of the tray where the icons will
640        * be embedded. In almost all cases, this will be the same as the visual
641        * of the screen.
642        */
643       xvisual = GDK_VISUAL_XVISUAL (gdk_screen_get_system_visual (manager->screen));
644     }
645 
646   data[0] = XVisualIDFromVisual (xvisual);
647 
648   XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
649                    GDK_WINDOW_XID (window),
650                    visual_atom,
651                    XA_VISUALID, 32,
652                    PropModeReplace,
653                    (guchar *) &data, 1);
654 #endif
655 }
656 
657 static void
na_tray_manager_set_padding_property(NaTrayManager * manager)658 na_tray_manager_set_padding_property (NaTrayManager *manager)
659 {
660 #ifdef GDK_WINDOWING_X11
661   GdkWindow  *window;
662   GdkDisplay *display;
663   Atom        atom;
664   gulong      data[1];
665 
666   g_return_if_fail (manager->invisible != NULL);
667   window = gtk_widget_get_window (manager->invisible);
668   g_return_if_fail (window != NULL);
669 
670   display = gtk_widget_get_display (manager->invisible);
671   atom = gdk_x11_get_xatom_by_name_for_display (display,
672                                                 "_NET_SYSTEM_TRAY_PADDING");
673 
674   data[0] = manager->padding;
675 
676   XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
677                    GDK_WINDOW_XID (window),
678                    atom,
679                    XA_CARDINAL, 32,
680                    PropModeReplace,
681                    (guchar *) &data, 1);
682 #endif
683 }
684 
685 static void
na_tray_manager_set_icon_size_property(NaTrayManager * manager)686 na_tray_manager_set_icon_size_property (NaTrayManager *manager)
687 {
688 #ifdef GDK_WINDOWING_X11
689   GdkWindow  *window;
690   GdkDisplay *display;
691   Atom        atom;
692   gulong      data[1];
693 
694   g_return_if_fail (manager->invisible != NULL);
695   window = gtk_widget_get_window (manager->invisible);
696   g_return_if_fail (window != NULL);
697 
698   display = gtk_widget_get_display (manager->invisible);
699   atom = gdk_x11_get_xatom_by_name_for_display (display,
700                                                 "_NET_SYSTEM_TRAY_ICON_SIZE");
701 
702   data[0] = manager->icon_size;
703 
704   XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
705                    GDK_WINDOW_XID (window),
706                    atom,
707                    XA_CARDINAL, 32,
708                    PropModeReplace,
709                    (guchar *) &data, 1);
710 #endif
711 }
712 
713 static void
na_tray_manager_set_colors_property(NaTrayManager * manager)714 na_tray_manager_set_colors_property (NaTrayManager *manager)
715 {
716 #ifdef GDK_WINDOWING_X11
717   GdkWindow  *window;
718   GdkDisplay *display;
719   Atom        atom;
720   gulong      data[12];
721 
722   g_return_if_fail (manager->invisible != NULL);
723   window = gtk_widget_get_window (manager->invisible);
724   g_return_if_fail (window != NULL);
725 
726   display = gtk_widget_get_display (manager->invisible);
727   atom = gdk_x11_get_xatom_by_name_for_display (display,
728                                                 "_NET_SYSTEM_TRAY_COLORS");
729 
730   data[0] = manager->fg.red * 65535;
731   data[1] = manager->fg.green * 65535;
732   data[2] = manager->fg.blue * 65535;
733   data[3] = manager->error.red * 65535;
734   data[4] = manager->error.green * 65535;
735   data[5] = manager->error.blue * 65535;
736   data[6] = manager->warning.red * 65535;
737   data[7] = manager->warning.green * 65535;
738   data[8] = manager->warning.blue * 65535;
739   data[9] = manager->success.red * 65535;
740   data[10] = manager->success.green * 65535;
741   data[11] = manager->success.blue * 65535;
742 
743   XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
744                    GDK_WINDOW_XID (window),
745                    atom,
746                    XA_CARDINAL, 32,
747                    PropModeReplace,
748                    (guchar *) &data, 12);
749 #endif
750 }
751 
752 #ifdef GDK_WINDOWING_X11
753 
754 static gboolean
na_tray_manager_manage_screen_x11(NaTrayManager * manager,GdkScreen * screen)755 na_tray_manager_manage_screen_x11 (NaTrayManager *manager,
756 				   GdkScreen     *screen)
757 {
758   GdkDisplay *display;
759   Screen     *xscreen;
760   GtkWidget  *invisible;
761   GdkWindow  *window;
762   char       *selection_atom_name;
763   guint32     timestamp;
764 
765   g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), FALSE);
766   g_return_val_if_fail (manager->screen == NULL, FALSE);
767 
768   /* If there's already a manager running on the screen
769    * we can't create another one.
770    */
771 #if 0
772   if (na_tray_manager_check_running_screen_x11 (screen))
773     return FALSE;
774 #endif
775 
776   manager->screen = screen;
777 
778   display = gdk_screen_get_display (screen);
779   xscreen = GDK_SCREEN_XSCREEN (screen);
780 
781   invisible = gtk_invisible_new_for_screen (screen);
782   gtk_widget_realize (invisible);
783 
784   gtk_widget_add_events (invisible,
785                          GDK_PROPERTY_CHANGE_MASK | GDK_STRUCTURE_MASK);
786 
787   selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d",
788 					 gdk_x11_screen_get_screen_number (screen));
789   manager->selection_atom = gdk_atom_intern (selection_atom_name, FALSE);
790   g_free (selection_atom_name);
791 
792   manager->invisible = invisible;
793   g_object_ref (G_OBJECT (manager->invisible));
794 
795   na_tray_manager_set_orientation_property (manager);
796   na_tray_manager_set_visual_property (manager);
797   na_tray_manager_set_padding_property (manager);
798   na_tray_manager_set_icon_size_property (manager);
799   na_tray_manager_set_colors_property (manager);
800 
801   window = gtk_widget_get_window (invisible);
802 
803   timestamp = gdk_x11_get_server_time (window);
804 
805   /* Check if we could set the selection owner successfully */
806   if (gdk_selection_owner_set_for_display (display,
807                                            window,
808                                            manager->selection_atom,
809                                            timestamp,
810                                            TRUE))
811     {
812       XClientMessageEvent xev;
813       GdkAtom             opcode_atom;
814       GdkAtom             message_data_atom;
815 
816       xev.type = ClientMessage;
817       xev.window = RootWindowOfScreen (xscreen);
818       xev.message_type = gdk_x11_get_xatom_by_name_for_display (display,
819                                                                 "MANAGER");
820 
821       xev.format = 32;
822       xev.data.l[0] = timestamp;
823       xev.data.l[1] = gdk_x11_atom_to_xatom_for_display (display,
824                                                          manager->selection_atom);
825       xev.data.l[2] = GDK_WINDOW_XID (window);
826       xev.data.l[3] = 0;	/* manager specific data */
827       xev.data.l[4] = 0;	/* manager specific data */
828 
829       XSendEvent (GDK_DISPLAY_XDISPLAY (display),
830 		  RootWindowOfScreen (xscreen),
831 		  False, StructureNotifyMask, (XEvent *)&xev);
832 
833       opcode_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_OPCODE", FALSE);
834       manager->opcode_atom = gdk_x11_atom_to_xatom_for_display (display,
835                                                                 opcode_atom);
836 
837       message_data_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_MESSAGE_DATA",
838                                            FALSE);
839 
840       manager->message_data_atom = gdk_x11_atom_to_xatom_for_display (display,
841                                                                       message_data_atom);
842 
843       /* Add a window filter */
844 #if 0
845       /* This is for when we lose the selection of _NET_SYSTEM_TRAY_Sx */
846       g_signal_connect (invisible, "selection-clear-event",
847                         G_CALLBACK (na_tray_manager_selection_clear_event),
848                         manager);
849 #endif
850       /* This is for SYSTEM_TRAY_REQUEST_DOCK and SelectionClear */
851       gdk_window_add_filter (window,
852                              na_tray_manager_window_filter, manager);
853       return TRUE;
854     }
855   else
856     {
857       gtk_widget_destroy (invisible);
858       g_object_unref (invisible);
859       manager->invisible = NULL;
860 
861       manager->screen = NULL;
862 
863       return FALSE;
864     }
865 }
866 
867 #endif
868 
869 gboolean
na_tray_manager_manage_screen(NaTrayManager * manager,GdkScreen * screen)870 na_tray_manager_manage_screen (NaTrayManager *manager,
871 			       GdkScreen     *screen)
872 {
873   g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE);
874   g_return_val_if_fail (manager->screen == NULL, FALSE);
875 
876 #ifdef GDK_WINDOWING_X11
877   return na_tray_manager_manage_screen_x11 (manager, screen);
878 #else
879   return FALSE;
880 #endif
881 }
882 
883 #ifdef GDK_WINDOWING_X11
884 
885 static gboolean
na_tray_manager_check_running_screen_x11(GdkScreen * screen)886 na_tray_manager_check_running_screen_x11 (GdkScreen *screen)
887 {
888   GdkDisplay *display;
889   Atom        selection_atom;
890   char       *selection_atom_name;
891 
892   display = gdk_screen_get_display (screen);
893   selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d",
894                                          gdk_x11_screen_get_screen_number (screen));
895   selection_atom = gdk_x11_get_xatom_by_name_for_display (display,
896                                                           selection_atom_name);
897   g_free (selection_atom_name);
898 
899   if (XGetSelectionOwner (GDK_DISPLAY_XDISPLAY (display),
900                           selection_atom) != None)
901     return TRUE;
902   else
903     return FALSE;
904 }
905 
906 #endif
907 
908 gboolean
na_tray_manager_check_running(GdkScreen * screen)909 na_tray_manager_check_running (GdkScreen *screen)
910 {
911   g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE);
912 
913 #ifdef GDK_WINDOWING_X11
914   return na_tray_manager_check_running_screen_x11 (screen);
915 #else
916   return FALSE;
917 #endif
918 }
919 
920 void
na_tray_manager_set_orientation(NaTrayManager * manager,GtkOrientation orientation)921 na_tray_manager_set_orientation (NaTrayManager  *manager,
922 				 GtkOrientation  orientation)
923 {
924   g_return_if_fail (NA_IS_TRAY_MANAGER (manager));
925 
926   if (manager->orientation != orientation)
927     {
928       manager->orientation = orientation;
929 
930       na_tray_manager_set_orientation_property (manager);
931 
932       g_object_notify (G_OBJECT (manager), "orientation");
933     }
934 }
935 
936 void
na_tray_manager_set_padding(NaTrayManager * manager,gint padding)937 na_tray_manager_set_padding (NaTrayManager *manager,
938                              gint           padding)
939 {
940   g_return_if_fail (NA_IS_TRAY_MANAGER (manager));
941 
942   if (manager->padding != padding)
943     {
944       manager->padding = padding;
945 
946       na_tray_manager_set_padding_property (manager);
947     }
948 }
949 
950 void
na_tray_manager_set_icon_size(NaTrayManager * manager,gint icon_size)951 na_tray_manager_set_icon_size (NaTrayManager *manager,
952                                gint           icon_size)
953 {
954   g_return_if_fail (NA_IS_TRAY_MANAGER (manager));
955 
956   if (manager->icon_size != icon_size)
957     {
958       manager->icon_size = icon_size;
959 
960       na_tray_manager_set_icon_size_property (manager);
961     }
962 }
963 
964 void
na_tray_manager_set_colors(NaTrayManager * manager,GdkRGBA * fg,GdkRGBA * error,GdkRGBA * warning,GdkRGBA * success)965 na_tray_manager_set_colors (NaTrayManager *manager,
966                             GdkRGBA       *fg,
967                             GdkRGBA       *error,
968                             GdkRGBA       *warning,
969                             GdkRGBA       *success)
970 {
971   g_return_if_fail (NA_IS_TRAY_MANAGER (manager));
972 
973   if (!gdk_rgba_equal (&manager->fg, fg) ||
974       !gdk_rgba_equal (&manager->error, error) ||
975       !gdk_rgba_equal (&manager->warning, warning) ||
976       !gdk_rgba_equal (&manager->success, success))
977     {
978       manager->fg = *fg;
979       manager->error = *error;
980       manager->warning = *warning;
981       manager->success = *success;
982 
983       na_tray_manager_set_colors_property (manager);
984     }
985 }
986 
987 GtkOrientation
na_tray_manager_get_orientation(NaTrayManager * manager)988 na_tray_manager_get_orientation (NaTrayManager *manager)
989 {
990   g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), GTK_ORIENTATION_HORIZONTAL);
991 
992   return manager->orientation;
993 }
994