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