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