Mostly workarounded MWM issue with panel resizing.
[lxde/lxpanel.git] / plugins / tray.c
CommitLineData
b021864b 1/*
2918994e 2 * System tray plugin to lxpanel
3 *
b021864b
AG
4 * Copyright (C) 2008-2009 Marty Jack <martyj19@comcast.net>
5 * 2009 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
6 * 2010-2013 Julien Lavergne <julien.lavergne@gmail.com>
7 * 2012-2013 Henry Gebhardt <hsggebhardt@gmail.com>
8 * 2014 Andriy Grytsenko <andrej@rep.kiev.ua>
9 *
10 * This file is a part of LXPanel project.
2918994e 11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 *
26 */
27
28/** Contains code adapted from na-tray-manager.c
29 * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
30 * Copyright (C) 2003-2006 Vincent Untz */
31
32#include <stdlib.h>
33#include <unistd.h>
34#include <string.h>
35
36#include <gdk-pixbuf/gdk-pixbuf.h>
37#include <glib/gi18n.h>
38
d8eb63e4 39#include "plugin.h"
2918994e 40#include "misc.h"
2918994e 41#include "icon-grid.h"
42
878cc1f8
AG
43#if GTK_CHECK_VERSION(3, 0, 0)
44#include <gtk/gtkx.h>
45#endif
46
2918994e 47/* Standards reference: http://standards.freedesktop.org/systemtray-spec/ */
48
49/* Protocol constants. */
50#define SYSTEM_TRAY_REQUEST_DOCK 0
51#define SYSTEM_TRAY_BEGIN_MESSAGE 1
52#define SYSTEM_TRAY_CANCEL_MESSAGE 2
53
54#define SYSTEM_TRAY_ORIENTATION_HORZ 0
55#define SYSTEM_TRAY_ORIENTATION_VERT 1
56
2918994e 57struct _balloon_message;
58struct _tray_client;
59struct _tray_plugin;
60
61/* Representative of a balloon message. */
62typedef struct _balloon_message {
63 struct _balloon_message * flink; /* Forward link */
64 Window window; /* X window ID */
65 long timeout; /* Time in milliseconds to display message; 0 if no timeout */
66 long length; /* Message string length */
67 long id; /* Client supplied unique message ID */
68 long remaining_length; /* Remaining length expected of incomplete message */
69 char * string; /* Message string */
70} BalloonMessage;
71
72/* Representative of a tray client. */
73typedef struct _tray_client {
74 struct _tray_client * client_flink; /* Forward link to next task in X window ID order */
75 struct _tray_plugin * tr; /* Back pointer to tray plugin */
76 Window window; /* X window ID */
77 GtkWidget * socket; /* Socket */
2918994e 78} TrayClient;
79
80/* Private context for system tray plugin. */
81typedef struct _tray_plugin {
d8eb63e4 82 GtkWidget * plugin; /* Back pointer to Plugin */
a7bd16a4 83 LXPanel * panel;
2918994e 84 TrayClient * client_list; /* List of tray clients */
85 BalloonMessage * incomplete_messages; /* List of balloon messages for which we are awaiting data */
86 BalloonMessage * messages; /* List of balloon messages actively being displayed or waiting to be displayed */
87 GtkWidget * balloon_message_popup; /* Popup showing balloon message */
88 guint balloon_message_timer; /* Timer controlling balloon message */
89 GtkWidget * invisible; /* Invisible window that holds manager selection */
964b8b7e 90 Window invisible_window; /* X window ID of invisible window */
2918994e 91 GdkAtom selection_atom; /* Atom for _NET_SYSTEM_TRAY_S%d */
92} TrayPlugin;
93
2918994e 94static void balloon_message_display(TrayPlugin * tr, BalloonMessage * msg);
2918994e 95static void balloon_incomplete_message_remove(TrayPlugin * tr, Window window, gboolean all_ids, long id);
96static void balloon_message_remove(TrayPlugin * tr, Window window, gboolean all_ids, long id);
2918994e 97static void tray_unmanage_selection(TrayPlugin * tr);
d8eb63e4 98static void tray_destructor(gpointer user_data);
2918994e 99
100/* Look up a client in the client list. */
101static TrayClient * client_lookup(TrayPlugin * tr, Window window)
102{
103 TrayClient * tc;
104 for (tc = tr->client_list; tc != NULL; tc = tc->client_flink)
105 {
106 if (tc->window == window)
107 return tc;
108 if (tc->window > window)
109 break;
110 }
111 return NULL;
112}
113
d6ca4fc5 114#if 0
1604938f
HG
115static void client_print(TrayPlugin * tr, char c, TrayClient * tc, XClientMessageEvent * xevent)
116{
1604938f
HG
117 char *name = get_utf8_property(tc->window, a_NET_WM_NAME);
118 int pid = get_net_wm_pid(tc->window);
119 XClientMessageEvent xcm = {0};
120 if (!xevent)
121 xevent = &xcm;
06e29ce1 122 g_debug("tray: %c%p, winid 0x%lx: %s (PID %d), plug %p, serial no %lu, send_event %c, format %d",
1604938f
HG
123 c, tc, tc->window, name, pid,
124 gtk_socket_get_plug_window(GTK_SOCKET(tc->socket)),
125 xevent->serial, xevent->send_event ? 'y' : 'n', xevent->format);
126 g_free(name);
1604938f 127}
d6ca4fc5 128#endif
1604938f 129
2918994e 130/* Delete a client. */
d8eb63e4 131static void client_delete(TrayPlugin * tr, TrayClient * tc, gboolean unlink, gboolean remove)
2918994e 132{
d6ca4fc5 133 //client_print(tr, '-', tc, NULL);
1604938f 134
964b8b7e 135 if (unlink)
2918994e 136 {
964b8b7e 137 if (tr->client_list == tc)
138 tr->client_list = tc->client_flink;
139 else
140 {
141 /* Locate the task and its predecessor in the list and then remove it. For safety, ensure it is found. */
142 TrayClient * tc_pred = NULL;
143 TrayClient * tc_cursor;
144 for (
145 tc_cursor = tr->client_list;
146 ((tc_cursor != NULL) && (tc_cursor != tc));
147 tc_pred = tc_cursor, tc_cursor = tc_cursor->client_flink) ;
148 if (tc_cursor == tc)
149 tc_pred->client_flink = tc->client_flink;
150 }
2918994e 151 }
152
964b8b7e 153 /* Clear out any balloon messages. */
154 balloon_incomplete_message_remove(tr, tc->window, TRUE, 0);
155 balloon_message_remove(tr, tc->window, TRUE, 0);
156
157 /* Remove the socket from the icon grid. */
d8eb63e4 158 if (remove)
5b397638 159 gtk_widget_destroy(tc->socket);
964b8b7e 160
2918994e 161 /* Deallocate the client structure. */
162 g_free(tc);
163}
164
165/*** Balloon message display ***/
166
167/* Free a balloon message structure. */
168static void balloon_message_free(BalloonMessage * message)
169{
170 g_free(message->string);
171 g_free(message);
172}
173
174/* General code to deactivate a message and optionally display the next.
175 * This is used in three scenarios: balloon clicked, timeout expired, destructor. */
176static void balloon_message_advance(TrayPlugin * tr, gboolean destroy_timer, gboolean display_next)
177{
178 /* Remove the message from the queue. */
179 BalloonMessage * msg = tr->messages;
180 tr->messages = msg->flink;
181
182 /* Cancel the timer, if set. This is not done when the timer has expired. */
183 if ((destroy_timer) && (tr->balloon_message_timer != 0))
184 g_source_remove(tr->balloon_message_timer);
185 tr->balloon_message_timer = 0;
186
187 /* Destroy the widget. */
188 if (tr->balloon_message_popup != NULL)
189 gtk_widget_destroy(tr->balloon_message_popup);
190 tr->balloon_message_popup = NULL;
191
192 /* Free the message. */
193 balloon_message_free(msg);
194
195 /* If there is another message waiting in the queue, display it. This is not done in the destructor. */
196 if ((display_next) && (tr->messages != NULL))
197 balloon_message_display(tr, tr->messages);
198}
199
200/* Handler for "button-press-event" from balloon message popup menu item. */
201static gboolean balloon_message_activate_event(GtkWidget * widget, GdkEventButton * event, TrayPlugin * tr)
202{
203 balloon_message_advance(tr, TRUE, TRUE);
204 return TRUE;
205}
206
207/* Timer expiration for balloon message. */
208static gboolean balloon_message_timeout(TrayPlugin * tr)
209{
251cfd3e
AG
210 if (!g_source_is_destroyed(g_main_current_source()))
211 balloon_message_advance(tr, FALSE, TRUE);
2918994e 212 return FALSE;
213}
214
215/* Create the graphic elements to display a balloon message. */
216static void balloon_message_display(TrayPlugin * tr, BalloonMessage * msg)
217{
218 /* Create a window and an item containing the text. */
219 tr->balloon_message_popup = gtk_window_new(GTK_WINDOW_POPUP);
220 GtkWidget * balloon_text = gtk_label_new(msg->string);
964b8b7e 221 gtk_label_set_line_wrap(GTK_LABEL(balloon_text), TRUE);
2918994e 222 gtk_misc_set_alignment(GTK_MISC(balloon_text), 0.5, 0.5);
2918994e 223 gtk_container_add(GTK_CONTAINER(tr->balloon_message_popup), balloon_text);
9fac586f 224 gtk_widget_show(balloon_text);
2918994e 225 gtk_container_set_border_width(GTK_CONTAINER(tr->balloon_message_popup), 4);
226
227 /* Connect signals. Clicking the popup dismisses it and displays the next message, if any. */
228 gtk_widget_add_events(tr->balloon_message_popup, GDK_BUTTON_PRESS_MASK);
8e05828e 229 g_signal_connect(tr->balloon_message_popup, "button-press-event", G_CALLBACK(balloon_message_activate_event), (gpointer) tr);
2918994e 230
2918994e 231 /* Compute the desired position in screen coordinates near the tray plugin. */
232 int x;
233 int y;
d6101d43 234 lxpanel_plugin_popup_set_position_helper(tr->panel, tr->plugin, tr->balloon_message_popup, &x, &y);
2918994e 235
236 /* Show the popup. */
237 gtk_window_move(GTK_WINDOW(tr->balloon_message_popup), x, y);
238 gtk_widget_show(tr->balloon_message_popup);
239
240 /* Set a timer, if the client specified one. Both are in units of milliseconds. */
241 if (msg->timeout != 0)
242 tr->balloon_message_timer = g_timeout_add(msg->timeout, (GSourceFunc) balloon_message_timeout, tr);
243}
244
245/* Add a balloon message to the tail of the message queue. If it is the only element, display it immediately. */
246static void balloon_message_queue(TrayPlugin * tr, BalloonMessage * msg)
247{
248 if (tr->messages == NULL)
249 {
250 tr->messages = msg;
251 balloon_message_display(tr, msg);
252 }
253 else
254 {
255 BalloonMessage * msg_pred;
256 for (msg_pred = tr->messages; ((msg_pred != NULL) && (msg_pred->flink != NULL)); msg_pred = msg_pred->flink) ;
257 if (msg_pred != NULL)
258 msg_pred->flink = msg;
259 }
260}
261
262/* Remove an incomplete message from the queue, selected by window and optionally also client's ID.
263 * Used in two scenarios: client issues CANCEL (ID significant), client plug removed (ID don't care). */
264static void balloon_incomplete_message_remove(TrayPlugin * tr, Window window, gboolean all_ids, long id)
265{
266 BalloonMessage * msg_pred = NULL;
267 BalloonMessage * msg = tr->incomplete_messages;
268 while (msg != NULL)
269 {
270 /* Establish successor in case of deletion. */
271 BalloonMessage * msg_succ = msg->flink;
272
273 if ((msg->window == window) && ((all_ids) || (msg->id == id)))
274 {
275 /* Found a message matching the criteria. Unlink and free it. */
276 if (msg_pred == NULL)
277 tr->incomplete_messages = msg->flink;
278 else
279 msg_pred->flink = msg->flink;
280 balloon_message_free(msg);
281 }
282 else
283 msg_pred = msg;
284
285 /* Advance to successor. */
286 msg = msg_succ;
287 }
288}
289
290/* Remove a message from the message queue, selected by window and optionally also client's ID.
291 * Used in two scenarios: client issues CANCEL (ID significant), client plug removed (ID don't care). */
292static void balloon_message_remove(TrayPlugin * tr, Window window, gboolean all_ids, long id)
293{
294 BalloonMessage * msg_pred = NULL;
295 BalloonMessage * msg_head = tr->messages;
296 BalloonMessage * msg = msg_head;
297 while (msg != NULL)
298 {
299 /* Establish successor in case of deletion. */
300 BalloonMessage * msg_succ = msg->flink;
301
302 if ((msg->window == window) && ((all_ids) || (msg->id == id)))
303 {
304 /* Found a message matching the criteria. */
305 if (msg_pred == NULL)
306 {
307 /* The message is at the queue head, so is being displayed. Stop the display. */
308 tr->messages = msg->flink;
309 if (tr->balloon_message_timer != 0)
310 {
311 g_source_remove(tr->balloon_message_timer);
312 tr->balloon_message_timer = 0;
313 }
314 if (tr->balloon_message_popup != NULL)
315 {
316 gtk_widget_destroy(tr->balloon_message_popup);
317 tr->balloon_message_popup = NULL;
318 }
319 }
320 else
321 msg_pred->flink = msg->flink;
322
323 /* Free the message. */
324 balloon_message_free(msg);
325 }
326 else
327 msg_pred = msg;
328
329 /* Advance to successor. */
330 msg = msg_succ;
331 }
332
333 /* If there is a new message head, display it now. */
334 if ((tr->messages != msg_head) && (tr->messages != NULL))
335 balloon_message_display(tr, tr->messages);
336}
337
338/*** Event interfaces ***/
339
2918994e 340/* Handle a balloon message SYSTEM_TRAY_BEGIN_MESSAGE event. */
341static void balloon_message_begin_event(TrayPlugin * tr, XClientMessageEvent * xevent)
342{
343 TrayClient * client = client_lookup(tr, xevent->window);
344 if (client != NULL)
345 {
346 /* Check if the message ID already exists. */
347 balloon_incomplete_message_remove(tr, xevent->window, FALSE, xevent->data.l[4]);
348
349 /* Allocate a BalloonMessage structure describing the message. */
350 BalloonMessage * msg = g_new0(BalloonMessage, 1);
351 msg->window = xevent->window;
352 msg->timeout = xevent->data.l[2];
353 msg->length = xevent->data.l[3];
354 msg->id = xevent->data.l[4];
355 msg->remaining_length = msg->length;
356 msg->string = g_new0(char, msg->length + 1);
357
358 /* Message length of 0 indicates that no follow-on messages will be sent. */
359 if (msg->length == 0)
360 balloon_message_queue(tr, msg);
361 else
362 {
363 /* Add the new message to the queue to await its message text. */
364 msg->flink = tr->incomplete_messages;
365 tr->incomplete_messages = msg;
366 }
367 }
368}
369
370/* Handle a balloon message SYSTEM_TRAY_CANCEL_MESSAGE event. */
371static void balloon_message_cancel_event(TrayPlugin * tr, XClientMessageEvent * xevent)
372{
373 /* Remove any incomplete messages on this window with the specified ID. */
374 balloon_incomplete_message_remove(tr, xevent->window, TRUE, 0);
375
376 /* Remove any displaying or waiting messages on this window with the specified ID. */
377 TrayClient * client = client_lookup(tr, xevent->window);
378 if (client != NULL)
379 balloon_message_remove(tr, xevent->window, FALSE, xevent->data.l[2]);
380}
381
964b8b7e 382/* Handle a balloon message _NET_SYSTEM_TRAY_MESSAGE_DATA event. */
383static void balloon_message_data_event(TrayPlugin * tr, XClientMessageEvent * xevent)
2918994e 384{
964b8b7e 385 /* Look up the pending message in the list. */
386 BalloonMessage * msg_pred = NULL;
387 BalloonMessage * msg;
388 for (msg = tr->incomplete_messages; msg != NULL; msg_pred = msg, msg = msg->flink)
2918994e 389 {
964b8b7e 390 if (xevent->window == msg->window)
391 {
392 /* Append the message segment to the message. */
393 int length = MIN(msg->remaining_length, 20);
394 memcpy((msg->string + msg->length - msg->remaining_length), &xevent->data, length);
395 msg->remaining_length -= length;
2918994e 396
964b8b7e 397 /* If the message has been completely collected, display it. */
398 if (msg->remaining_length == 0)
399 {
400 /* Unlink the message from the structure. */
401 if (msg_pred == NULL)
402 tr->incomplete_messages = msg->flink;
403 else
404 msg_pred->flink = msg->flink;
2918994e 405
964b8b7e 406 /* If the client window is valid, queue the message. Otherwise discard it. */
407 TrayClient * client = client_lookup(tr, msg->window);
408 if (client != NULL)
409 balloon_message_queue(tr, msg);
410 else
411 balloon_message_free(msg);
412 }
413 break;
414 }
415 }
2918994e 416}
417
418/* Handler for request dock message. */
419static void trayclient_request_dock(TrayPlugin * tr, XClientMessageEvent * xevent)
420{
421 /* Search for the window in the client list. Set up context to do an insert right away if needed. */
422 TrayClient * tc_pred = NULL;
423 TrayClient * tc_cursor;
424 for (tc_cursor = tr->client_list; tc_cursor != NULL; tc_pred = tc_cursor, tc_cursor = tc_cursor->client_flink)
425 {
fbe0300a 426 if (tc_cursor->window == (Window)xevent->data.l[2])
2918994e 427 return; /* We already got this notification earlier, ignore this one. */
fbe0300a 428 if (tc_cursor->window > (Window)xevent->data.l[2])
2918994e 429 break;
430 }
431
432 /* Allocate and initialize new client structure. */
433 TrayClient * tc = g_new0(TrayClient, 1);
434 tc->window = xevent->data.l[2];
435 tc->tr = tr;
436
437 /* Allocate a socket. This is the tray side of the Xembed connection. */
438 tc->socket = gtk_socket_new();
2918994e 439
9c9d8556 440 /* Add the socket to the icon grid. */
9fac586f
AG
441 gtk_container_add(GTK_CONTAINER(tr->plugin), tc->socket);
442 gtk_widget_show(tc->socket);
fe0de891 443
964b8b7e 444 /* Connect the socket to the plug. This can only be done after the socket is realized. */
2918994e 445 gtk_socket_add_id(GTK_SOCKET(tc->socket), tc->window);
24754248
JL
446
447 //fprintf(stderr, "Notice: checking plug %ud\n", tc->window );
448 /* Checks if the plug has been created inside of the socket. */
449 if (gtk_socket_get_plug_window ( GTK_SOCKET(tc->socket) ) == NULL) {
450 //fprintf(stderr, "Notice: removing plug %ud\n", tc->window );
5b397638
AG
451 gtk_widget_destroy(tc->socket);
452 g_free(tc);
24754248
JL
453 return;
454 }
5b397638
AG
455
456 /* Link the client structure into the client list. */
457 if (tc_pred == NULL)
458 {
459 tc->client_flink = tr->client_list;
460 tr->client_list = tc;
461 }
462 else
463 {
464 tc->client_flink = tc_pred->client_flink;
465 tc_pred->client_flink = tc;
466 }
2918994e 467}
468
469/* GDK event filter. */
964b8b7e 470static GdkFilterReturn tray_event_filter(XEvent * xev, GdkEvent * event, TrayPlugin * tr)
2918994e 471{
964b8b7e 472 if (xev->type == DestroyNotify)
473 {
474 /* Look for DestroyNotify events on tray icon windows and update state.
475 * We do it this way rather than with a "plug_removed" event because delivery
476 * of plug_removed events is observed to be unreliable if the client
477 * disconnects within less than 10 ms. */
478 XDestroyWindowEvent * xev_destroy = (XDestroyWindowEvent *) xev;
479 TrayClient * tc = client_lookup(tr, xev_destroy->window);
480 if (tc != NULL)
d8eb63e4 481 client_delete(tr, tc, TRUE, TRUE);
964b8b7e 482 }
2918994e 483
964b8b7e 484 else if (xev->type == ClientMessage)
2918994e 485 {
964b8b7e 486 if (xev->xclient.message_type == a_NET_SYSTEM_TRAY_OPCODE)
2918994e 487 {
964b8b7e 488 /* Client message of type _NET_SYSTEM_TRAY_OPCODE.
489 * Dispatch on the request. */
490 switch (xev->xclient.data.l[1])
491 {
492 case SYSTEM_TRAY_REQUEST_DOCK:
493 /* If a Request Dock event on the invisible window, which is holding the manager selection, execute it. */
494 if (xev->xclient.window == tr->invisible_window)
495 {
496 trayclient_request_dock(tr, (XClientMessageEvent *) xev);
497 return GDK_FILTER_REMOVE;
498 }
499 break;
500
501 case SYSTEM_TRAY_BEGIN_MESSAGE:
502 /* If a Begin Message event. look up the tray icon and execute it. */
503 balloon_message_begin_event(tr, (XClientMessageEvent *) xev);
504 return GDK_FILTER_REMOVE;
505
506 case SYSTEM_TRAY_CANCEL_MESSAGE:
507 /* If a Cancel Message event. look up the tray icon and execute it. */
508 balloon_message_cancel_event(tr, (XClientMessageEvent *) xev);
509 return GDK_FILTER_REMOVE;
510 }
511 }
512
513 else if (xev->xclient.message_type == a_NET_SYSTEM_TRAY_MESSAGE_DATA)
514 {
515 /* Client message of type _NET_SYSTEM_TRAY_MESSAGE_DATA.
516 * Look up the tray icon and execute it. */
517 balloon_message_data_event(tr, (XClientMessageEvent *) xev);
2918994e 518 return GDK_FILTER_REMOVE;
519 }
520 }
521
964b8b7e 522 else if ((xev->type == SelectionClear)
523 && (xev->xclient.window == tr->invisible_window))
2918994e 524 {
964b8b7e 525 /* Look for SelectionClear events on the invisible window, which is holding the manager selection.
526 * This should not happen. */
2918994e 527 tray_unmanage_selection(tr);
528 }
529
2918994e 530 return GDK_FILTER_CONTINUE;
531}
532
533/* Delete the selection on the invisible window. */
534static void tray_unmanage_selection(TrayPlugin * tr)
535{
536 if (tr->invisible != NULL)
537 {
538 GtkWidget * invisible = tr->invisible;
539 GdkDisplay * display = gtk_widget_get_display(invisible);
d5c46ffc 540 if (gdk_selection_owner_get_for_display(display, tr->selection_atom) == gtk_widget_get_window(invisible))
2918994e 541 {
d5c46ffc 542 guint32 timestamp = gdk_x11_get_server_time(gtk_widget_get_window(invisible));
2918994e 543 gdk_selection_owner_set_for_display(
544 display,
545 NULL,
546 tr->selection_atom,
547 timestamp,
548 TRUE);
549 }
550
9c9d8556 551 /* Destroy the invisible window. */
2918994e 552 tr->invisible = NULL;
964b8b7e 553 tr->invisible_window = None;
2918994e 554 gtk_widget_destroy(invisible);
555 g_object_unref(G_OBJECT(invisible));
556 }
557}
558
559/* Plugin constructor. */
a7bd16a4 560static GtkWidget *tray_constructor(LXPanel *panel, config_setting_t *settings)
2918994e 561{
d8eb63e4 562 GtkWidget *p;
2918994e 563
564 /* Get the screen and display. */
a7bd16a4 565 GdkScreen * screen = gtk_widget_get_screen(GTK_WIDGET(panel));
2918994e 566 Screen * xscreen = GDK_SCREEN_XSCREEN(screen);
567 GdkDisplay * display = gdk_screen_get_display(screen);
568
569 /* Create the selection atom. This has the screen number in it, so cannot be done ahead of time. */
570 char * selection_atom_name = g_strdup_printf("_NET_SYSTEM_TRAY_S%d", gdk_screen_get_number(screen));
571 Atom selection_atom = gdk_x11_get_xatom_by_name_for_display(display, selection_atom_name);
d8eb63e4 572 GdkAtom gdk_selection_atom = gdk_atom_intern(selection_atom_name, FALSE);
2918994e 573 g_free(selection_atom_name);
574
575 /* If the selection is already owned, there is another tray running. */
576 if (XGetSelectionOwner(GDK_DISPLAY_XDISPLAY(display), selection_atom) != None)
577 {
06e29ce1 578 g_warning("tray: another systray already running");
d8eb63e4 579 return NULL;
2918994e 580 }
581
582 /* Create an invisible window to hold the selection. */
583 GtkWidget * invisible = gtk_invisible_new_for_screen(screen);
584 gtk_widget_realize(invisible);
585 gtk_widget_add_events(invisible, GDK_PROPERTY_CHANGE_MASK | GDK_STRUCTURE_MASK);
586
587 /* Try to claim the _NET_SYSTEM_TRAY_Sn selection. */
d5c46ffc 588 guint32 timestamp = gdk_x11_get_server_time(gtk_widget_get_window(invisible));
2918994e 589 if (gdk_selection_owner_set_for_display(
590 display,
d5c46ffc 591 gtk_widget_get_window(invisible),
d8eb63e4 592 gdk_selection_atom,
2918994e 593 timestamp,
594 TRUE))
595 {
596 /* Send MANAGER client event (ICCCM). */
597 XClientMessageEvent xev;
598 xev.type = ClientMessage;
599 xev.window = RootWindowOfScreen(xscreen);
600 xev.message_type = a_MANAGER;
601 xev.format = 32;
602 xev.data.l[0] = timestamp;
603 xev.data.l[1] = selection_atom;
64d2d703 604 xev.data.l[2] = GDK_WINDOW_XID(gtk_widget_get_window(invisible));
2918994e 605 xev.data.l[3] = 0; /* manager specific data */
606 xev.data.l[4] = 0; /* manager specific data */
607 XSendEvent(GDK_DISPLAY_XDISPLAY(display), RootWindowOfScreen(xscreen), False, StructureNotifyMask, (XEvent *) &xev);
d8eb63e4 608
2918994e 609 /* Set the orientation property.
610 * We always set "horizontal" since even vertical panels are designed to use a lot of width. */
611 gulong data = SYSTEM_TRAY_ORIENTATION_HORZ;
612 XChangeProperty(
613 GDK_DISPLAY_XDISPLAY(display),
64d2d703 614 GDK_WINDOW_XID(gtk_widget_get_window(invisible)),
2918994e 615 a_NET_SYSTEM_TRAY_ORIENTATION,
616 XA_CARDINAL, 32,
617 PropModeReplace,
618 (guchar *) &data, 1);
2918994e 619 }
620 else
621 {
622 gtk_widget_destroy(invisible);
623 g_printerr("tray: System tray didn't get the system tray manager selection\n");
624 return 0;
625 }
d8eb63e4
AG
626
627 /* Allocate plugin context and set into Plugin private data pointer and static variable. */
628 TrayPlugin * tr = g_new0(TrayPlugin, 1);
629 tr->panel = panel;
630 tr->selection_atom = gdk_selection_atom;
631 /* Add GDK event filter. */
632 gdk_window_add_filter(NULL, (GdkFilterFunc) tray_event_filter, tr);
633 /* Reference the window since it is never added to a container. */
634 tr->invisible = g_object_ref_sink(G_OBJECT(invisible));
64d2d703 635 tr->invisible_window = GDK_WINDOW_XID(gtk_widget_get_window(invisible));
d8eb63e4 636
2918994e 637 /* Allocate top level widget and set into Plugin widget pointer. */
9fac586f
AG
638 tr->plugin = p = panel_icon_grid_new(panel_get_orientation(panel),
639 panel_get_icon_size(panel),
640 panel_get_icon_size(panel),
641 3, 0, panel_get_height(panel));
d8eb63e4 642 lxpanel_plugin_set_data(p, tr, tray_destructor);
d8eb63e4 643 gtk_widget_set_name(p, "tray");
9633d713 644 panel_icon_grid_set_aspect_width(PANEL_ICON_GRID(p), TRUE);
2918994e 645
d8eb63e4 646 return p;
2918994e 647}
648
649/* Plugin destructor. */
d8eb63e4 650static void tray_destructor(gpointer user_data)
2918994e 651{
d8eb63e4 652 TrayPlugin * tr = user_data;
2918994e 653
964b8b7e 654 /* Remove GDK event filter. */
655 gdk_window_remove_filter(NULL, (GdkFilterFunc) tray_event_filter, tr);
656
2918994e 657 /* Make sure we drop the manager selection. */
658 tray_unmanage_selection(tr);
659
660 /* Deallocate incomplete messages. */
661 while (tr->incomplete_messages != NULL)
662 {
663 BalloonMessage * msg_succ = tr->incomplete_messages->flink;
664 balloon_message_free(tr->incomplete_messages);
665 tr->incomplete_messages = msg_succ;
666 }
667
668 /* Terminate message display and deallocate messages. */
669 while (tr->messages != NULL)
670 balloon_message_advance(tr, TRUE, FALSE);
671
5b397638 672 /* Deallocate client list - widgets are already destroyed. */
2918994e 673 while (tr->client_list != NULL)
d8eb63e4 674 client_delete(tr, tr->client_list, TRUE, FALSE);
2918994e 675
2918994e 676 g_free(tr);
677}
678
679/* Callback when panel configuration changes. */
a7bd16a4 680static void tray_panel_configuration_changed(LXPanel *panel, GtkWidget *p)
2918994e 681{
682 /* Set orientation into the icon grid. */
9fac586f
AG
683 panel_icon_grid_set_geometry(PANEL_ICON_GRID(p), panel_get_orientation(panel),
684 panel_get_icon_size(panel),
685 panel_get_icon_size(panel),
686 3, 0, panel_get_height(panel));
2918994e 687}
688
689/* Plugin descriptor. */
d8eb63e4 690LXPanelPluginInit lxpanel_static_plugin_tray = {
3c3e9c9e 691 .name = N_("System Tray"),
3c3e9c9e 692 .description = N_("System tray"),
2918994e 693
694 /* Set a flag to identify the system tray. It is special in that only one per system can exist. */
3c3e9c9e 695 .one_per_system = TRUE,
2918994e 696
d8eb63e4
AG
697 .new_instance = tray_constructor,
698 .reconfigure = tray_panel_configuration_changed
2918994e 699};
1604938f
HG
700
701/* vim: set sw=4 sts=4 et : */