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