~ubuntu-branches/ubuntu/wily/lxpanel/wily-proposed

« back to all changes in this revision

Viewing changes to plugins/tray.c

  • Committer: Package Import Robot
  • Author(s): Julien Lavergne
  • Date: 2015-01-31 15:30:45 UTC
  • mfrom: (1.1.19) (44.1.1 vivid-proposed)
  • Revision ID: package-import@ubuntu.com-20150131153045-iabx5uuxwf2p9sl3
Tags: 0.7.2-1ubuntu1
* Merge with Debian. Ubuntu remaining changes:
* debian/control:
 - Add libindicator-dev build-depends.
 - Add a recommend on xterm | pavucontrol | gnome-alsamixer to enable the
   mixer on the sound applet. (LP: #957749).
 - Add indicator plugin binary.
 - Add build-depends on libicu-dev for weather plugin.
* debian/local/source_lxpanel.py:
 - Add apport hook.
* debian/lxpanel.install:
 - Install all files except indicators.
* debian/lxpanel-indicator-applet-plugin.install:
 - Install indicator plugin.
* debian/rules:
 - Add --enable-indicator-support flag.
 - Add dh_install --fail-missing.
* debian/patches:
 - 04_disable_gtk3_indicators.patch: Hide incompatible indicators in the
   preference menu (LP: #1165245).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
636
    return p;
 
637
}
 
638
 
 
639
/* Plugin destructor. */
 
640
static void tray_destructor(gpointer user_data)
 
641
{
 
642
    TrayPlugin * tr = user_data;
 
643
 
 
644
    /* Remove GDK event filter. */
 
645
    gdk_window_remove_filter(NULL, (GdkFilterFunc) tray_event_filter, tr);
 
646
 
 
647
    /* Make sure we drop the manager selection. */
 
648
    tray_unmanage_selection(tr);
 
649
 
 
650
    /* Deallocate incomplete messages. */
 
651
    while (tr->incomplete_messages != NULL)
 
652
    {
 
653
        BalloonMessage * msg_succ = tr->incomplete_messages->flink;
 
654
        balloon_message_free(tr->incomplete_messages);
 
655
        tr->incomplete_messages = msg_succ;
 
656
    }
 
657
 
 
658
    /* Terminate message display and deallocate messages. */
 
659
    while (tr->messages != NULL)
 
660
        balloon_message_advance(tr, TRUE, FALSE);
 
661
 
 
662
    /* Deallocate client list - widgets are already destroyed. */
 
663
    while (tr->client_list != NULL)
 
664
        client_delete(tr, tr->client_list, TRUE, FALSE);
 
665
 
 
666
    g_free(tr);
 
667
}
 
668
 
 
669
/* Callback when panel configuration changes. */
 
670
static void tray_panel_configuration_changed(LXPanel *panel, GtkWidget *p)
 
671
{
 
672
    /* Set orientation into the icon grid. */
 
673
    panel_icon_grid_set_geometry(PANEL_ICON_GRID(p), panel_get_orientation(panel),
 
674
                                 panel_get_icon_size(panel),
 
675
                                 panel_get_icon_size(panel),
 
676
                                 3, 0, panel_get_height(panel));
 
677
}
 
678
 
 
679
/* Plugin descriptor. */
 
680
LXPanelPluginInit lxpanel_static_plugin_tray = {
 
681
    .name = N_("System Tray"),
 
682
    .description = N_("System tray"),
 
683
 
 
684
    /* Set a flag to identify the system tray.  It is special in that only one per system can exist. */
 
685
    .one_per_system = TRUE,
 
686
 
 
687
    .new_instance = tray_constructor,
 
688
    .reconfigure = tray_panel_configuration_changed
 
689
};
 
690
 
 
691
/* vim: set sw=4 sts=4 et : */