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 |
/* |