Mostly workarounded MWM issue with panel resizing.
[lxde/lxpanel.git] / plugins / tray.c
1 /*
2 * System tray plugin to lxpanel
3 *
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.
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
39 #include "plugin.h"
40 #include "misc.h"
41 #include "icon-grid.h"
42
43 #if GTK_CHECK_VERSION(3, 0, 0)
44 #include <gtk/gtkx.h>
45 #endif
46
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
57 struct _balloon_message;
58 struct _tray_client;
59 struct _tray_plugin;
60
61 /* Representative of a balloon message. */
62 typedef 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. */
73 typedef 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 */
78 } TrayClient;
79
80 /* Private context for system tray plugin. */
81 typedef struct _tray_plugin {
82 GtkWidget * plugin; /* Back pointer to Plugin */
83 LXPanel * panel;
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 */
90 Window invisible_window; /* X window ID of invisible window */
91 GdkAtom selection_atom; /* Atom for _NET_SYSTEM_TRAY_S%d */
92 } TrayPlugin;
93
94 static void balloon_message_display(TrayPlugin * tr, BalloonMessage * msg);
95 static void balloon_incomplete_message_remove(TrayPlugin * tr, Window window, gboolean all_ids, long id);
96 static void balloon_message_remove(TrayPlugin * tr, Window window, gboolean all_ids, long id);
97 static void tray_unmanage_selection(TrayPlugin * tr);
98 static void tray_destructor(gpointer user_data);
99
100 /* Look up a client in the client list. */
101 static 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
114 #if 0
115 static void client_print(TrayPlugin * tr, char c, TrayClient * tc, XClientMessageEvent * xevent)
116 {
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;
122 g_debug("tray: %c%p, winid 0x%lx: %s (PID %d), plug %p, serial no %lu, send_event %c, format %d",
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);
127 }
128 #endif
129
130 /* Delete a client. */
131 static void client_delete(TrayPlugin * tr, TrayClient * tc, gboolean unlink, gboolean remove)
132 {
133 //client_print(tr, '-', tc, NULL);
134
135 if (unlink)
136 {
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 }
151 }
152
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. */
158 if (remove)
159 gtk_widget_destroy(tc->socket);
160
161 /* Deallocate the client structure. */
162 g_free(tc);
163 }
164
165 /*** Balloon message display ***/
166
167 /* Free a balloon message structure. */
168 static 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. */
176 static 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. */
201 static 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. */
208 static gboolean balloon_message_timeout(TrayPlugin * tr)
209 {
210 if (!g_source_is_destroyed(g_main_current_source()))
211 balloon_message_advance(tr, FALSE, TRUE);
212 return FALSE;
213 }
214
215 /* Create the graphic elements to display a balloon message. */
216 static 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);
221 gtk_label_set_line_wrap(GTK_LABEL(balloon_text), TRUE);
222 gtk_misc_set_alignment(GTK_MISC(balloon_text), 0.5, 0.5);
223 gtk_container_add(GTK_CONTAINER(tr->balloon_message_popup), balloon_text);
224 gtk_widget_show(balloon_text);
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);
229 g_signal_connect(tr->balloon_message_popup, "button-press-event", G_CALLBACK(balloon_message_activate_event), (gpointer) tr);
230
231 /* Compute the desired position in screen coordinates near the tray plugin. */
232 int x;
233 int y;
234 lxpanel_plugin_popup_set_position_helper(tr->panel, tr->plugin, tr->balloon_message_popup, &x, &y);
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. */
246 static 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). */
264 static 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). */
292 static 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
340 /* Handle a balloon message SYSTEM_TRAY_BEGIN_MESSAGE event. */
341 static 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. */
371 static 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
382 /* Handle a balloon message _NET_SYSTEM_TRAY_MESSAGE_DATA event. */
383 static void balloon_message_data_event(TrayPlugin * tr, XClientMessageEvent * xevent)
384 {
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)
389 {
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;
396
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;
405
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 }
416 }
417
418 /* Handler for request dock message. */
419 static 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 {
426 if (tc_cursor->window == (Window)xevent->data.l[2])
427 return; /* We already got this notification earlier, ignore this one. */
428 if (tc_cursor->window > (Window)xevent->data.l[2])
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();
439
440 /* Add the socket to the icon grid. */
441 gtk_container_add(GTK_CONTAINER(tr->plugin), tc->socket);
442 gtk_widget_show(tc->socket);
443
444 /* Connect the socket to the plug. This can only be done after the socket is realized. */
445 gtk_socket_add_id(GTK_SOCKET(tc->socket), tc->window);
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 );
451 gtk_widget_destroy(tc->socket);
452 g_free(tc);
453 return;
454 }
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 }
467 }
468
469 /* GDK event filter. */
470 static GdkFilterReturn tray_event_filter(XEvent * xev, GdkEvent * event, TrayPlugin * tr)
471 {
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)
481 client_delete(tr, tc, TRUE, TRUE);
482 }
483
484 else if (xev->type == ClientMessage)
485 {
486 if (xev->xclient.message_type == a_NET_SYSTEM_TRAY_OPCODE)
487 {
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);
518 return GDK_FILTER_REMOVE;
519 }
520 }
521
522 else if ((xev->type == SelectionClear)
523 && (xev->xclient.window == tr->invisible_window))
524 {
525 /* Look for SelectionClear events on the invisible window, which is holding the manager selection.
526 * This should not happen. */
527 tray_unmanage_selection(tr);
528 }
529
530 return GDK_FILTER_CONTINUE;
531 }
532
533 /* Delete the selection on the invisible window. */
534 static 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);
540 if (gdk_selection_owner_get_for_display(display, tr->selection_atom) == gtk_widget_get_window(invisible))
541 {
542 guint32 timestamp = gdk_x11_get_server_time(gtk_widget_get_window(invisible));
543 gdk_selection_owner_set_for_display(
544 display,
545 NULL,
546 tr->selection_atom,
547 timestamp,
548 TRUE);
549 }
550
551 /* Destroy the invisible window. */
552 tr->invisible = NULL;
553 tr->invisible_window = None;
554 gtk_widget_destroy(invisible);
555 g_object_unref(G_OBJECT(invisible));
556 }
557 }
558
559 /* Plugin constructor. */
560 static GtkWidget *tray_constructor(LXPanel *panel, config_setting_t *settings)
561 {
562 GtkWidget *p;
563
564 /* Get the screen and display. */
565 GdkScreen * screen = gtk_widget_get_screen(GTK_WIDGET(panel));
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);
572 GdkAtom gdk_selection_atom = gdk_atom_intern(selection_atom_name, FALSE);
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 {
578 g_warning("tray: another systray already running");
579 return NULL;
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. */
588 guint32 timestamp = gdk_x11_get_server_time(gtk_widget_get_window(invisible));
589 if (gdk_selection_owner_set_for_display(
590 display,
591 gtk_widget_get_window(invisible),
592 gdk_selection_atom,
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;
604 xev.data.l[2] = GDK_WINDOW_XID(gtk_widget_get_window(invisible));
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);
608
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),
614 GDK_WINDOW_XID(gtk_widget_get_window(invisible)),
615 a_NET_SYSTEM_TRAY_ORIENTATION,
616 XA_CARDINAL, 32,
617 PropModeReplace,
618 (guchar *) &data, 1);
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 }
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));
635 tr->invisible_window = GDK_WINDOW_XID(gtk_widget_get_window(invisible));
636
637 /* Allocate top level widget and set into Plugin widget pointer. */
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));
642 lxpanel_plugin_set_data(p, tr, tray_destructor);
643 gtk_widget_set_name(p, "tray");
644 panel_icon_grid_set_aspect_width(PANEL_ICON_GRID(p), TRUE);
645
646 return p;
647 }
648
649 /* Plugin destructor. */
650 static void tray_destructor(gpointer user_data)
651 {
652 TrayPlugin * tr = user_data;
653
654 /* Remove GDK event filter. */
655 gdk_window_remove_filter(NULL, (GdkFilterFunc) tray_event_filter, tr);
656
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
672 /* Deallocate client list - widgets are already destroyed. */
673 while (tr->client_list != NULL)
674 client_delete(tr, tr->client_list, TRUE, FALSE);
675
676 g_free(tr);
677 }
678
679 /* Callback when panel configuration changes. */
680 static void tray_panel_configuration_changed(LXPanel *panel, GtkWidget *p)
681 {
682 /* Set orientation into the icon grid. */
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));
687 }
688
689 /* Plugin descriptor. */
690 LXPanelPluginInit lxpanel_static_plugin_tray = {
691 .name = N_("System Tray"),
692 .description = N_("System tray"),
693
694 /* Set a flag to identify the system tray. It is special in that only one per system can exist. */
695 .one_per_system = TRUE,
696
697 .new_instance = tray_constructor,
698 .reconfigure = tray_panel_configuration_changed
699 };
700
701 /* vim: set sw=4 sts=4 et : */