i3 - improved tiling WM


i3bar: Repair tray client handling

Patch status: needinfo

Patch by Tony Crisci

Long description:

Show/hide tray clients asynchronously on MapNotify and UnmapNotify respectively.

Remove tray clients on DestroyNotify as per the EXembed protocol
specification (see:
http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html)

Add sanity checks and safety measures to guard against sending messages
to tray clients that could result in BadWindow errors by tray clients
(but that issue is not entirely resolved here).

Adds support for QSystemTrayIcon class, qt-base version 5.3 (qt-base
version 5.1 is still unsupported)

fixes #1110.

To apply this patch, use:
curl http://cr.i3wm.org/patch/295/raw.patch | git am

b/i3bar/src/xcb.c

27
@@ -53,6 +53,9 @@ xcb_window_t     xcb_root;
28
 static xcb_window_t selwin = XCB_NONE;
29
 static xcb_intern_atom_reply_t *tray_reply = NULL;
30
 
31
+/* set to true when the tray is initialized */
32
+static bool has_tray_initialized = false;
33
+
34
 /* This is needed for integration with libi3 */
35
 xcb_connection_t *conn;
36
 
37
@@ -448,6 +451,7 @@ static void configure_trayclients(void) {
38
         TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
39
             if (!trayclient->mapped)
40
                 continue;
41
+
42
             clients++;
43
 
44
             DLOG("Configuring tray window %08x to x=%d\n",
45
@@ -577,6 +581,26 @@ static void handle_client_message(xcb_client_message_event_t* event) {
46
                                  mask,
47
                                  values);
48
 
49
+            trayclient *tc = smalloc(sizeof(trayclient));
50
+            tc->win = client;
51
+            tc->mapped = map_it;
52
+            tc->xe_version = xe_version;
53
+            TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
54
+
55
+            /* Put the client inside the save set. Upon termination (whether
56
+             * killed or normal exit does not matter) of i3bar, these clients
57
+             * will be correctly reparented to their most closest living
58
+             * ancestor. Without this, tray icons might die when i3bar
59
+             * exits/crashes. */
60
+            xcb_change_save_set(xcb_connection, XCB_SET_MODE_INSERT, client);
61
+
62
+            if (map_it) {
63
+                DLOG("Mapping dock client\n");
64
+                xcb_map_window(xcb_connection, client);
65
+            } else {
66
+                DLOG("Not mapping dock client yet\n");
67
+            }
68
+
69
             /* send the XEMBED_EMBEDDED_NOTIFY message */
70
             void *event = scalloc(32);
71
             xcb_client_message_event_t *ev = event;
72
@@ -595,38 +619,42 @@ static void handle_client_message(xcb_client_message_event_t* event) {
73
                            (char*)ev);
74
             free(event);
75
 
76
-            /* Put the client inside the save set. Upon termination (whether
77
-             * killed or normal exit does not matter) of i3bar, these clients
78
-             * will be correctly reparented to their most closest living
79
-             * ancestor. Without this, tray icons might die when i3bar
80
-             * exits/crashes. */
81
-            xcb_change_save_set(xcb_connection, XCB_SET_MODE_INSERT, client);
82
+        }
83
+    }
84
+}
85
 
86
-            if (map_it) {
87
-                DLOG("Mapping dock client\n");
88
-                xcb_map_window(xcb_connection, client);
89
-            } else {
90
-                DLOG("Not mapping dock client yet\n");
91
-            }
92
-            trayclient *tc = smalloc(sizeof(trayclient));
93
-            tc->win = client;
94
-            tc->mapped = map_it;
95
-            tc->xe_version = xe_version;
96
-            TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
97
+/*
98
+ * Handles MapNotify events for tray clients by reconfiguring the clients and
99
+ * redrawing the bar.
100
+ */
101
 
102
-            /* Trigger an update to copy the statusline text to the appropriate
103
-             * position */
104
+static void handle_map_notify(xcb_map_notify_event_t* event) {
105
+    DLOG("MapNotify for window = %08x, event = %08x\n", event->window, event->event);
106
+
107
+    i3_output *walk;
108
+    SLIST_FOREACH(walk, outputs, slist) {
109
+        if (!walk->active)
110
+            continue;
111
+        DLOG("checking output %s\n", walk->name);
112
+        trayclient *trayclient;
113
+        TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
114
+            if (trayclient->win != event->window)
115
+                continue;
116
+
117
+            DLOG("Tray client mapped (window ID %08x) reconfiguring.\n", event->window);
118
+            trayclient->mapped = true;
119
             configure_trayclients();
120
             draw_bars(false);
121
+            return;
122
         }
123
     }
124
 }
125
 
126
 /*
127
- * Handles UnmapNotify events. These events happen when a tray window unmaps
128
- * itself. We then update our data structure
129
- *
130
+ * Handles UnmapNotify events for tray clients by reconfiguring the clients and
131
+ * redrawing the bar.
132
  */
133
+
134
 static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
135
     DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event);
136
 
137
@@ -640,6 +668,35 @@ static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
138
             if (trayclient->win != event->window)
139
                 continue;
140
 
141
+            DLOG("Tray client unmapped (window ID %08x) reconfiguring.\n", event->window);
142
+            trayclient->mapped = false;
143
+
144
+            /* Trigger an update, we now have more space for the statusline */
145
+            configure_trayclients();
146
+            draw_bars(false);
147
+            return;
148
+        }
149
+    }
150
+}
151
+
152
+/*
153
+ * Handles DestroyNotify events by removing the tray client. This signifies the
154
+ * end of the EXembed protocol.
155
+ *
156
+ */
157
+static void handle_destroy_notify(xcb_destroy_notify_event_t* event) {
158
+    DLOG("DestroyNotify for window = %08x, event = %08x\n", event->window, event->event);
159
+
160
+    i3_output *walk;
161
+    SLIST_FOREACH(walk, outputs, slist) {
162
+        if (!walk->active)
163
+            continue;
164
+        DLOG("checking output %s\n", walk->name);
165
+        trayclient *trayclient;
166
+        TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
167
+            if (trayclient->win != event->window)
168
+                continue;
169
+
170
             DLOG("Removing tray client with window ID %08x\n", event->window);
171
             TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
172
 
173
@@ -707,16 +764,12 @@ static void handle_property_notify(xcb_property_notify_event_t *event) {
174
         DLOG("map-state now %d\n", map_it);
175
         if (trayclient->mapped && !map_it) {
176
             /* need to unmap the window */
177
+            trayclient->mapped = false;
178
             xcb_unmap_window(xcb_connection, trayclient->win);
179
-            trayclient->mapped = map_it;
180
-            configure_trayclients();
181
-            draw_bars(false);
182
         } else if (!trayclient->mapped && map_it) {
183
             /* need to map the window */
184
+            trayclient->mapped = true;
185
             xcb_map_window(xcb_connection, trayclient->win);
186
-            trayclient->mapped = map_it;
187
-            configure_trayclients();
188
-            draw_bars(false);
189
         }
190
         free(xembedr);
191
     }
192
@@ -798,11 +851,16 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
193
                  * example system tray widgets talk to us directly via client messages. */
194
                 handle_client_message((xcb_client_message_event_t*) event);
195
                 break;
196
+            case XCB_MAP_NOTIFY:
197
+                handle_map_notify((xcb_map_notify_event_t*) event);
198
+                break;
199
             case XCB_UNMAP_NOTIFY:
200
-            case XCB_DESTROY_NOTIFY:
201
-                /* UnmapNotifies are received when a tray window unmaps itself */
202
                 handle_unmap_notify((xcb_unmap_notify_event_t*) event);
203
                 break;
204
+            case XCB_DESTROY_NOTIFY:
205
+                /* DestroyNotify signifies the end of the EXembed protocol */
206
+                handle_destroy_notify((xcb_destroy_notify_event_t*) event);
207
+                break;
208
             case XCB_PROPERTY_NOTIFY:
209
                 /* PropertyNotify */
210
                 handle_property_notify((xcb_property_notify_event_t*) event);
211
@@ -1078,6 +1136,9 @@ void init_xcb_late(char *fontname) {
212
  *
213
  */
214
 static void send_tray_clientmessage(void) {
215
+    if (!has_tray_initialized)
216
+        return;
217
+
218
     uint8_t buffer[32] = { 0 };
219
     xcb_client_message_event_t *ev = (xcb_client_message_event_t*)buffer;
220
 
221
@@ -1172,6 +1233,7 @@ void init_tray(void) {
222
         return;
223
     }
224
 
225
+    has_tray_initialized = true;
226
     send_tray_clientmessage();
227
 }
228
 
229
@@ -1273,7 +1335,7 @@ void get_atoms(void) {
230
  *
231
  */
232
 void kick_tray_clients(i3_output *output) {
233
-    if (TAILQ_EMPTY(output->trayclients))
234
+    if (!has_tray_initialized || TAILQ_EMPTY(output->trayclients))
235
         return;
236
 
237
     trayclient *trayclient;
238
@@ -1292,6 +1354,7 @@ void kick_tray_clients(i3_output *output) {
239
         TAILQ_REMOVE(output->trayclients, trayclient, tailq);
240
     }
241
 
242
+#if 0
243
     /* Fake a DestroyNotify so that Qt re-adds tray icons.
244
      * We cannot actually destroy the window because then Qt will not restore
245
      * its event mask on the new window. */
246
@@ -1305,6 +1368,7 @@ void kick_tray_clients(i3_output *output) {
247
     xcb_send_event(conn, false, selwin, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)event);
248
 
249
     send_tray_clientmessage();
250
+#endif
251
 }
252
 
253
 /*