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-2009 Nick Schermer <nick@xfce.org>
8
* This program 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)
13
* This program 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
18
* You should have received a copy of the GNU General Public License along with
19
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
20
* Place, Suite 330, Boston, MA 02111-1307 USA
32
#include <X11/Xatom.h>
38
#include <common/panel-private.h>
40
#include <libxfce4panel/libxfce4panel.h>
41
#include <libxfce4util/libxfce4util.h>
43
#include "systray-manager.h"
44
#include "systray-socket.h"
45
#include "systray-marshal.h"
49
#define XFCE_SYSTRAY_MANAGER_REQUEST_DOCK 0
50
#define XFCE_SYSTRAY_MANAGER_BEGIN_MESSAGE 1
51
#define XFCE_SYSTRAY_MANAGER_CANCEL_MESSAGE 2
53
#define XFCE_SYSTRAY_MANAGER_ORIENTATION_HORIZONTAL 0
54
#define XFCE_SYSTRAY_MANAGER_ORIENTATION_VERTICAL 1
58
static void systray_manager_finalize (GObject *object);
59
static void systray_manager_remove_socket (gpointer key,
62
static GdkFilterReturn systray_manager_window_filter (GdkXEvent *xev,
65
static GdkFilterReturn systray_manager_handle_client_message_opcode (GdkXEvent *xevent,
68
static GdkFilterReturn systray_manager_handle_client_message_message_data (GdkXEvent *xevent,
71
static void systray_manager_handle_begin_message (SystrayManager *manager,
72
XClientMessageEvent *xevent);
73
static void systray_manager_handle_cancel_message (SystrayManager *manager,
74
XClientMessageEvent *xevent);
75
static void systray_manager_handle_dock_request (SystrayManager *manager,
76
XClientMessageEvent *xevent);
77
static gboolean systray_manager_handle_undock_request (GtkSocket *socket,
79
static void systray_manager_set_visual (SystrayManager *manager);
80
static void systray_manager_message_free (SystrayMessage *message);
81
static void systray_manager_message_remove_from_list (SystrayManager *manager,
82
XClientMessageEvent *xevent);
96
struct _SystrayManagerClass
98
GObjectClass __parent__;
101
struct _SystrayManager
105
/* invisible window */
106
GtkWidget *invisible;
108
/* list of client sockets */
111
/* orientation of the tray */
112
GtkOrientation orientation;
114
/* list of pending messages */
117
/* _net_system_tray_opcode atom */
120
/* _net_system_tray_s%d atom */
121
GdkAtom selection_atom;
124
struct _SystrayMessage
137
glong remaining_length;
143
static guint systray_manager_signals[LAST_SIGNAL];
147
XFCE_PANEL_DEFINE_TYPE (SystrayManager, systray_manager, G_TYPE_OBJECT)
152
systray_manager_class_init (SystrayManagerClass *klass)
154
GObjectClass *gobject_class;
156
gobject_class = G_OBJECT_CLASS (klass);
157
gobject_class->finalize = systray_manager_finalize;
159
systray_manager_signals[ICON_ADDED] =
160
g_signal_new (g_intern_static_string ("icon-added"),
161
G_OBJECT_CLASS_TYPE (klass),
164
g_cclosure_marshal_VOID__OBJECT,
168
systray_manager_signals[ICON_REMOVED] =
169
g_signal_new (g_intern_static_string ("icon-removed"),
170
G_OBJECT_CLASS_TYPE (klass),
173
g_cclosure_marshal_VOID__OBJECT,
177
systray_manager_signals[MESSAGE_SENT] =
178
g_signal_new (g_intern_static_string ("message-sent"),
179
G_OBJECT_CLASS_TYPE (klass),
182
_systray_marshal_VOID__OBJECT_STRING_LONG_LONG,
189
systray_manager_signals[MESSAGE_CANCELLED] =
190
g_signal_new (g_intern_static_string ("message-cancelled"),
191
G_OBJECT_CLASS_TYPE (klass),
194
_systray_marshal_VOID__OBJECT_LONG,
199
systray_manager_signals[LOST_SELECTION] =
200
g_signal_new (g_intern_static_string ("lost-selection"),
201
G_OBJECT_CLASS_TYPE (klass),
204
g_cclosure_marshal_VOID__VOID,
211
systray_manager_init (SystrayManager *manager)
213
manager->invisible = NULL;
214
manager->orientation = GTK_ORIENTATION_HORIZONTAL;
215
manager->messages = NULL;
217
/* create new sockets table */
218
manager->sockets = g_hash_table_new (NULL, NULL);
224
systray_manager_error_quark (void)
229
q = g_quark_from_static_string ("systray-manager-error-quark");
237
systray_manager_finalize (GObject *object)
239
SystrayManager *manager = XFCE_SYSTRAY_MANAGER (object);
241
panel_return_if_fail (manager->invisible == NULL);
243
/* destroy the hash table */
244
g_hash_table_destroy (manager->sockets);
246
if (manager->messages)
248
/* cleanup all pending messages */
249
g_slist_foreach (manager->messages,
250
(GFunc) systray_manager_message_free, NULL);
253
g_slist_free (manager->messages);
256
G_OBJECT_CLASS (systray_manager_parent_class)->finalize (object);
262
systray_manager_new (void)
264
return g_object_new (XFCE_TYPE_SYSTRAY_MANAGER, NULL);
271
systray_manager_check_running (GdkScreen *screen)
273
gchar *selection_name;
277
panel_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE);
279
/* get the display */
280
display = gdk_screen_get_display (screen);
282
/* create the selection atom name */
283
selection_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d",
284
gdk_screen_get_number (screen));
287
selection_atom = gdk_x11_get_xatom_by_name_for_display (display,
290
g_free (selection_name);
293
return (XGetSelectionOwner (GDK_DISPLAY_XDISPLAY (display), selection_atom) != None);
300
systray_manager_register (SystrayManager *manager,
305
gchar *selection_name;
308
GtkWidget *invisible;
311
XClientMessageEvent xevent;
314
panel_return_val_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager), FALSE);
315
panel_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE);
316
panel_return_val_if_fail (error == NULL || *error == NULL, FALSE);
318
/* create invisible window */
319
invisible = gtk_invisible_new_for_screen (screen);
320
gtk_widget_realize (invisible);
322
/* let the invisible window monitor property and configuration changes */
323
gtk_widget_add_events (invisible, GDK_PROPERTY_CHANGE_MASK | GDK_STRUCTURE_MASK);
325
/* get the screen number */
326
screen_number = gdk_screen_get_number (screen);
328
/* create the selection atom name */
329
selection_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d", screen_number);
331
/* get the selection atom */
332
manager->selection_atom = gdk_atom_intern (selection_name, FALSE);
334
g_free (selection_name);
336
/* get the display */
337
display = gdk_screen_get_display (screen);
339
/* set the invisible window and take a reference */
340
manager->invisible = g_object_ref (G_OBJECT (invisible));
342
/* set the visial property for transparent tray icons */
343
if (gtk_check_version (2, 16, 0) == NULL)
344
systray_manager_set_visual (manager);
346
/* get the current x server time stamp */
347
timestamp = gdk_x11_get_server_time (invisible->window);
349
/* try to become the selection owner of this display */
350
succeed = gdk_selection_owner_set_for_display (display, invisible->window,
351
manager->selection_atom,
354
if (G_LIKELY (succeed))
356
/* get the root window */
357
root_window = RootWindowOfScreen (GDK_SCREEN_XSCREEN (screen));
359
/* send a message to x11 that we're going to handle this display */
360
xevent.type = ClientMessage;
361
xevent.window = root_window;
362
xevent.message_type = gdk_x11_get_xatom_by_name_for_display (display, "MANAGER");
364
xevent.data.l[0] = timestamp;
365
xevent.data.l[1] = gdk_x11_atom_to_xatom_for_display (display,
366
manager->selection_atom);
367
xevent.data.l[2] = GDK_WINDOW_XWINDOW (invisible->window);
368
xevent.data.l[3] = 0;
369
xevent.data.l[4] = 0;
371
/* send the message */
372
XSendEvent (GDK_DISPLAY_XDISPLAY (display), root_window,
373
False, StructureNotifyMask, (XEvent *)&xevent);
375
/* system_tray_request_dock and selectionclear */
376
gdk_window_add_filter (invisible->window, systray_manager_window_filter, manager);
378
/* get the opcode atom (for both gdk and x11) */
379
opcode_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_OPCODE", FALSE);
380
manager->opcode_atom = gdk_x11_atom_to_xatom_for_display (display, opcode_atom);
382
/* system_tray_begin_message and system_tray_cancel_message */
383
gdk_display_add_client_message_filter (display,
384
opcode_atom, systray_manager_handle_client_message_opcode, manager);
386
/* _net_system_tray_message_data */
387
gdk_display_add_client_message_filter (display,
388
gdk_atom_intern ("_NET_SYSTEM_TRAY_MESSAGE_DATA", FALSE),
389
systray_manager_handle_client_message_message_data, manager);
393
/* release the invisible */
394
g_object_unref (G_OBJECT (manager->invisible));
395
manager->invisible = NULL;
397
/* desktroy the invisible window */
398
gtk_widget_destroy (invisible);
401
g_set_error (error, XFCE_SYSTRAY_MANAGER_ERROR,
402
XFCE_SYSTRAY_MANAGER_ERROR_SELECTION_FAILED,
403
_("Failed to acquire manager selection for screen %d"),
413
systray_manager_remove_socket (gpointer key,
417
SystrayManager *manager = XFCE_SYSTRAY_MANAGER (user_data);
418
GtkSocket *socket = GTK_SOCKET (value);
420
panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
421
panel_return_if_fail (GTK_IS_SOCKET (socket));
423
/* properly undock from the tray */
424
g_signal_emit (manager, systray_manager_signals[ICON_REMOVED], 0, socket);
430
systray_manager_unregister (SystrayManager *manager)
433
GtkWidget *invisible = manager->invisible;
436
panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
438
/* leave when there is no invisible window */
439
if (G_UNLIKELY (invisible == NULL))
442
panel_return_if_fail (GTK_IS_INVISIBLE (invisible));
443
panel_return_if_fail (GTK_WIDGET_REALIZED (invisible));
444
panel_return_if_fail (GDK_IS_WINDOW (invisible->window));
446
/* get the display of the invisible window */
447
display = gtk_widget_get_display (invisible);
449
/* remove our handling of the selection if we're the owner */
450
owner = gdk_selection_owner_get_for_display (display, manager->selection_atom);
451
if (owner == invisible->window)
453
gdk_selection_owner_set_for_display (display, NULL,
454
manager->selection_atom,
455
gdk_x11_get_server_time (invisible->window),
459
/* remove window filter */
460
gdk_window_remove_filter (invisible->window,
461
systray_manager_window_filter, manager);
463
/* remove all sockets from the hash table */
464
g_hash_table_foreach (manager->sockets,
465
systray_manager_remove_socket, manager);
467
/* destroy and unref the invisible window */
468
manager->invisible = NULL;
469
gtk_widget_destroy (invisible);
470
g_object_unref (G_OBJECT (invisible));
475
static GdkFilterReturn
476
systray_manager_window_filter (GdkXEvent *xev,
480
XEvent *xevent = (XEvent *)xev;
481
SystrayManager *manager = XFCE_SYSTRAY_MANAGER (user_data);
483
panel_return_val_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager), GDK_FILTER_CONTINUE);
485
if (xevent->type == ClientMessage)
487
if (xevent->xclient.message_type == manager->opcode_atom
488
&& xevent->xclient.data.l[1] == XFCE_SYSTRAY_MANAGER_REQUEST_DOCK)
490
/* dock a tray icon */
491
systray_manager_handle_dock_request (manager, (XClientMessageEvent *) xevent);
493
return GDK_FILTER_REMOVE;
496
else if (xevent->type == SelectionClear)
498
/* emit the signal */
499
g_signal_emit (manager, systray_manager_signals[LOST_SELECTION], 0);
501
/* unregister the manager */
502
systray_manager_unregister (manager);
505
return GDK_FILTER_CONTINUE;
510
static GdkFilterReturn
511
systray_manager_handle_client_message_opcode (GdkXEvent *xevent,
515
XClientMessageEvent *xev;
516
SystrayManager *manager = XFCE_SYSTRAY_MANAGER (user_data);
518
panel_return_val_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager), GDK_FILTER_REMOVE);
520
/* cast to x11 event */
521
xev = (XClientMessageEvent *) xevent;
523
switch (xev->data.l[1])
525
case XFCE_SYSTRAY_MANAGER_REQUEST_DOCK:
526
/* handled in systray_manager_window_filter () */
529
case XFCE_SYSTRAY_MANAGER_BEGIN_MESSAGE:
530
systray_manager_handle_begin_message (manager, xev);
531
return GDK_FILTER_REMOVE;
533
case XFCE_SYSTRAY_MANAGER_CANCEL_MESSAGE:
534
systray_manager_handle_cancel_message (manager, xev);
535
return GDK_FILTER_REMOVE;
541
return GDK_FILTER_CONTINUE;
546
static GdkFilterReturn
547
systray_manager_handle_client_message_message_data (GdkXEvent *xevent,
551
XClientMessageEvent *xev = xevent;
552
SystrayManager *manager = XFCE_SYSTRAY_MANAGER (user_data);
554
SystrayMessage *message;
558
panel_return_val_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager), GDK_FILTER_REMOVE);
560
/* try to find the pending message in the list */
561
for (li = manager->messages; li != NULL; li = li->next)
565
if (xev->window == message->window)
567
/* copy the data of this message */
568
length = MIN (message->remaining_length, 20);
569
memcpy ((message->string + message->length - message->remaining_length), &xev->data, length);
570
message->remaining_length -= length;
572
/* check if we have the complete message */
573
if (message->remaining_length == 0)
575
/* try to get the socket from the known tray icons */
576
socket = g_hash_table_lookup (manager->sockets, GUINT_TO_POINTER (message->window));
578
if (G_LIKELY (socket))
580
/* known socket, send the signal */
581
g_signal_emit (manager, systray_manager_signals[MESSAGE_SENT], 0,
582
socket, message->string, message->id, message->timeout);
585
/* delete the message from the list */
586
manager->messages = g_slist_delete_link (manager->messages, li);
588
/* free the message */
589
systray_manager_message_free (message);
597
return GDK_FILTER_REMOVE;
603
systray_manager_handle_begin_message (SystrayManager *manager,
604
XClientMessageEvent *xevent)
607
SystrayMessage *message;
608
glong length, timeout, id;
610
panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
612
/* try to find the window in the list of known tray icons */
613
socket = g_hash_table_lookup (manager->sockets, GUINT_TO_POINTER (xevent->window));
615
/* unkown tray icon: ignore the message */
616
if (G_UNLIKELY (socket == NULL))
619
/* remove the same message from the list */
620
systray_manager_message_remove_from_list (manager, xevent);
622
/* get some message information */
623
timeout = xevent->data.l[2];
624
length = xevent->data.l[3];
625
id = xevent->data.l[4];
629
/* directly emit empty messages */
630
g_signal_emit (manager, systray_manager_signals[MESSAGE_SENT], 0,
631
socket, "", id, timeout);
635
/* create new structure */
636
message = g_slice_new0 (SystrayMessage);
638
/* set message data */
639
message->window = xevent->window;
640
message->timeout = timeout;
641
message->length = length;
643
message->remaining_length = length;
644
message->string = g_malloc (length + 1);
645
message->string[length] = '\0';
647
/* add this message to the list of pending messages */
648
manager->messages = g_slist_prepend (manager->messages, message);
655
systray_manager_handle_cancel_message (SystrayManager *manager,
656
XClientMessageEvent *xevent)
659
GdkNativeWindow window = xevent->data.l[2];
661
panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
663
/* remove the same message from the list */
664
systray_manager_message_remove_from_list (manager, xevent);
666
/* try to find the window in the list of known tray icons */
667
socket = g_hash_table_lookup (manager->sockets, GUINT_TO_POINTER (xevent->window));
669
/* emit the cancelled signal */
670
if (G_LIKELY (socket != NULL))
671
g_signal_emit (manager, systray_manager_signals[MESSAGE_CANCELLED],
678
systray_manager_handle_dock_request (SystrayManager *manager,
679
XClientMessageEvent *xevent)
683
GdkNativeWindow window = xevent->data.l[2];
685
panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
686
panel_return_if_fail (GTK_IS_INVISIBLE (manager->invisible));
688
/* check if we already have this window */
689
if (g_hash_table_lookup (manager->sockets, GUINT_TO_POINTER (window)) != NULL)
692
/* create the socket */
693
screen = gtk_widget_get_screen (manager->invisible);
694
socket = systray_socket_new (screen, window);
695
if (G_UNLIKELY (socket == NULL))
698
/* add the icon to the tray */
699
g_signal_emit (manager, systray_manager_signals[ICON_ADDED], 0, socket);
701
/* check if the widget has been attached. if the widget has no
702
toplevel window, we cannot set the socket id. */
703
if (G_LIKELY (GTK_IS_WINDOW (gtk_widget_get_toplevel (socket))))
705
/* signal to monitor if the client is removed from the socket */
706
g_signal_connect (G_OBJECT (socket), "plug-removed",
707
G_CALLBACK (systray_manager_handle_undock_request), manager);
709
/* register the xembed client window id for this socket */
710
gtk_socket_add_id (GTK_SOCKET (socket), window);
712
/* add the socket to the list of known sockets */
713
g_hash_table_insert (manager->sockets, GUINT_TO_POINTER (window), socket);
718
g_warning ("No parent window set, destroying socket");
720
/* not attached successfully, destroy it */
721
gtk_widget_destroy (socket);
728
systray_manager_handle_undock_request (GtkSocket *socket,
731
SystrayManager *manager = XFCE_SYSTRAY_MANAGER (user_data);
732
GdkNativeWindow *window;
734
panel_return_val_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager), FALSE);
736
/* emit signal that the socket will be removed */
737
g_signal_emit (manager, systray_manager_signals[ICON_REMOVED], 0, socket);
739
/* get the xwindow */
740
window = systray_socket_get_window (XFCE_SYSTRAY_SOCKET (socket));
742
/* remove the socket from the list */
743
g_hash_table_remove (manager->sockets, GUINT_TO_POINTER (*window));
745
/* destroy the socket */
752
systray_manager_set_visual (SystrayManager *manager)
758
GdkColormap *colormap;
761
panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
762
panel_return_if_fail (GTK_IS_INVISIBLE (manager->invisible));
763
panel_return_if_fail (GDK_IS_WINDOW (manager->invisible->window));
765
/* get invisible display and screen */
766
display = gtk_widget_get_display (manager->invisible);
767
screen = gtk_invisible_get_screen (GTK_INVISIBLE (manager->invisible));
769
/* get the xatom for the visual property */
770
visual_atom = gdk_x11_get_xatom_by_name_for_display (display,
771
"_NET_SYSTEM_TRAY_VISUAL");
773
if (gtk_widget_is_composited (manager->invisible)
774
&& gdk_screen_get_rgba_visual (screen) != NULL
775
&& gdk_display_supports_composite (display))
777
/* get the rgba visual */
778
xvisual = GDK_VISUAL_XVISUAL (gdk_screen_get_rgba_visual (screen));
782
/* use the default visual for the screen */
783
colormap = gdk_screen_get_default_colormap (screen);
784
xvisual = GDK_VISUAL_XVISUAL (gdk_colormap_get_visual (colormap));
787
data[0] = XVisualIDFromVisual (xvisual);
788
XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
789
GDK_WINDOW_XWINDOW (manager->invisible->window),
793
(guchar *) &data, 1);
799
systray_manager_set_orientation (SystrayManager *manager,
800
GtkOrientation orientation)
803
Atom orientation_atom;
806
panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
807
panel_return_if_fail (GTK_IS_INVISIBLE (manager->invisible));
808
panel_return_if_fail (GDK_IS_WINDOW (manager->invisible->window));
810
/* set the new orientation */
811
manager->orientation = orientation;
813
/* get invisible display */
814
display = gtk_widget_get_display (manager->invisible);
816
/* get the xatom for the orientation property */
817
orientation_atom = gdk_x11_get_xatom_by_name_for_display (display,
818
"_NET_SYSTEM_TRAY_ORIENTATION");
820
/* set the data we're going to send to x */
821
data[0] = (manager->orientation == GTK_ORIENTATION_HORIZONTAL ?
822
XFCE_SYSTRAY_MANAGER_ORIENTATION_HORIZONTAL
823
: XFCE_SYSTRAY_MANAGER_ORIENTATION_VERTICAL);
825
/* change the x property */
826
XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
827
GDK_WINDOW_XWINDOW (manager->invisible->window),
831
(guchar *) &data, 1);
840
systray_manager_message_free (SystrayMessage *message)
842
g_free (message->string);
843
g_slice_free (SystrayMessage, message);
849
systray_manager_message_remove_from_list (SystrayManager *manager,
850
XClientMessageEvent *xevent)
853
SystrayMessage *message;
855
panel_return_if_fail (XFCE_IS_SYSTRAY_MANAGER (manager));
857
/* seach for the same message in the list of pending messages */
858
for (li = manager->messages; li != NULL; li = li->next)
862
/* check if this is the same message */
863
if (xevent->window == message->window && xevent->data.l[4] == message->id)
865
/* delete the message from the list */
866
manager->messages = g_slist_delete_link (manager->messages, li);
868
/* free the message */
869
systray_manager_message_free (message);