Maintain the _NET_CLIENT_LIST property
Patch status: merged
Patch by Tony Crisci
Long description:
Add and update the _NET_CLIENT_LIST property on the root window to better comply with ewmh standards. Information on this property can be found here: http://standards.freedesktop.org/wm-spec/latest/ar01s03.html > These arrays contain all X Windows managed by the Window Manager. > _NET_CLIENT_LIST has initial mapping order, starting with the oldest window. fixes #1099
To apply this patch, use:
curl http://cr.i3wm.org/patch/502/raw.patch | git am
b/include/atoms.xmacro
28 |
@@ -13,6 +13,7 @@ xmacro(_NET_WM_WINDOW_TYPE_TOOLBAR) |
29 |
xmacro(_NET_WM_WINDOW_TYPE_SPLASH) |
30 |
xmacro(_NET_WM_DESKTOP) |
31 |
xmacro(_NET_WM_STRUT_PARTIAL) |
32 |
+xmacro(_NET_CLIENT_LIST) |
33 |
xmacro(_NET_CLIENT_LIST_STACKING) |
34 |
xmacro(_NET_CURRENT_DESKTOP) |
35 |
xmacro(_NET_ACTIVE_WINDOW) |
b/include/ewmh.h
40 |
@@ -28,6 +28,11 @@ void ewmh_update_current_desktop(void); |
41 |
void ewmh_update_active_window(xcb_window_t window); |
42 |
|
43 |
/** |
44 |
+ * Updates the _NET_CLIENT_LIST hint. Used for window listers. |
45 |
+ */ |
46 |
+void ewmh_update_client_list(xcb_window_t *list, int num_windows); |
47 |
+ |
48 |
+/** |
49 |
* Updates the _NET_CLIENT_LIST_STACKING hint. Necessary to move tabs in |
50 |
* Chromium correctly. |
51 |
* |
b/src/ewmh.c
56 |
@@ -72,6 +72,22 @@ void ewmh_update_workarea(void) { |
57 |
} |
58 |
|
59 |
/* |
60 |
+ * Updates the _NET_CLIENT_LIST hint. |
61 |
+ * |
62 |
+ */ |
63 |
+void ewmh_update_client_list(xcb_window_t *list, int num_windows) { |
64 |
+ xcb_change_property( |
65 |
+ conn, |
66 |
+ XCB_PROP_MODE_REPLACE, |
67 |
+ root, |
68 |
+ A__NET_CLIENT_LIST, |
69 |
+ XCB_ATOM_WINDOW, |
70 |
+ 32, |
71 |
+ num_windows, |
72 |
+ list); |
73 |
+} |
74 |
+ |
75 |
+/* |
76 |
* Updates the _NET_CLIENT_LIST_STACKING hint. |
77 |
* |
78 |
*/ |
79 |
@@ -122,5 +138,5 @@ void ewmh_setup_hints(void) { |
80 |
/* I’m not entirely sure if we need to keep _NET_WM_NAME on root. */ |
81 |
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); |
82 |
|
83 |
- xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 18, supported_atoms); |
84 |
+ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 19, supported_atoms); |
85 |
} |
b/src/x.c
90 |
@@ -20,11 +20,6 @@ xcb_window_t focused_id = XCB_NONE; |
91 |
* tell whether the focused window actually changed. */ |
92 |
static xcb_window_t last_focused = XCB_NONE; |
93 |
|
94 |
-/* The bottom-to-top window stack of all windows which are managed by i3. |
95 |
- * Used for x_get_window_stack(). */ |
96 |
-static xcb_window_t *btt_stack; |
97 |
-static int btt_stack_num; |
98 |
- |
99 |
/* Stores coordinates to warp mouse pointer to if set */ |
100 |
static Rect *warp_to; |
101 |
|
102 |
@@ -60,6 +55,7 @@ typedef struct con_state { |
103 |
|
104 |
CIRCLEQ_ENTRY(con_state) state; |
105 |
CIRCLEQ_ENTRY(con_state) old_state; |
106 |
+ TAILQ_ENTRY(con_state) initial_mapping_order; |
107 |
} con_state; |
108 |
|
109 |
CIRCLEQ_HEAD(state_head, con_state) state_head = |
110 |
@@ -68,6 +64,9 @@ CIRCLEQ_HEAD(state_head, con_state) state_head = |
111 |
CIRCLEQ_HEAD(old_state_head, con_state) old_state_head = |
112 |
CIRCLEQ_HEAD_INITIALIZER(old_state_head); |
113 |
|
114 |
+TAILQ_HEAD(initial_mapping_head, con_state) initial_mapping_head = |
115 |
+ TAILQ_HEAD_INITIALIZER(initial_mapping_head); |
116 |
+ |
117 |
/* |
118 |
* Returns the container state for the given frame. This function always |
119 |
* returns a container state (otherwise, there is a bug in the code and the |
120 |
@@ -151,8 +150,10 @@ void x_con_init(Con *con, uint16_t depth) { |
121 |
state->id = con->frame; |
122 |
state->mapped = false; |
123 |
state->initial = true; |
124 |
+ DLOG("Adding window 0x%08x to lists\n", state->id); |
125 |
CIRCLEQ_INSERT_HEAD(&state_head, state, state); |
126 |
CIRCLEQ_INSERT_HEAD(&old_state_head, state, old_state); |
127 |
+ TAILQ_INSERT_TAIL(&initial_mapping_head, state, initial_mapping_order); |
128 |
DLOG("adding new state for window id 0x%08x\n", state->id); |
129 |
} |
130 |
|
131 |
@@ -233,6 +234,7 @@ void x_con_kill(Con *con) { |
132 |
state = state_for_frame(con->frame); |
133 |
CIRCLEQ_REMOVE(&state_head, state, state); |
134 |
CIRCLEQ_REMOVE(&old_state_head, state, old_state); |
135 |
+ TAILQ_REMOVE(&initial_mapping_head, state, initial_mapping_order); |
136 |
FREE(state->name); |
137 |
free(state); |
138 |
|
139 |
@@ -909,12 +911,17 @@ void x_push_changes(Con *con) { |
140 |
if (state->con && state->con->window) |
141 |
cnt++; |
142 |
|
143 |
- if (cnt != btt_stack_num) { |
144 |
- btt_stack = srealloc(btt_stack, sizeof(xcb_window_t) * cnt); |
145 |
- btt_stack_num = cnt; |
146 |
+ /* The bottom-to-top window stack of all windows which are managed by i3. |
147 |
+ * Used for x_get_window_stack(). */ |
148 |
+ static xcb_window_t *client_list_windows = NULL; |
149 |
+ static int client_list_count = 0; |
150 |
+ |
151 |
+ if (cnt != client_list_count) { |
152 |
+ client_list_windows = srealloc(client_list_windows, sizeof(xcb_window_t) * cnt); |
153 |
+ client_list_count = cnt; |
154 |
} |
155 |
|
156 |
- xcb_window_t *walk = btt_stack; |
157 |
+ xcb_window_t *walk = client_list_windows; |
158 |
|
159 |
/* X11 correctly represents the stack if we push it from bottom to top */ |
160 |
CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { |
161 |
@@ -940,9 +947,21 @@ void x_push_changes(Con *con) { |
162 |
} |
163 |
|
164 |
/* If we re-stacked something (or a new window appeared), we need to update |
165 |
- * the _NET_CLIENT_LIST_STACKING hint */ |
166 |
- if (stacking_changed) |
167 |
- ewmh_update_client_list_stacking(btt_stack, btt_stack_num); |
168 |
+ * the _NET_CLIENT_LIST and _NET_CLIENT_LIST_STACKING hints */ |
169 |
+ if (stacking_changed) { |
170 |
+ DLOG("Client list changed (%i clients)\n", cnt); |
171 |
+ ewmh_update_client_list_stacking(client_list_windows, client_list_count); |
172 |
+ |
173 |
+ walk = client_list_windows; |
174 |
+ |
175 |
+ /* reorder by initial mapping */ |
176 |
+ TAILQ_FOREACH(state, &initial_mapping_head, initial_mapping_order) { |
177 |
+ if (state->con && state->con->window) |
178 |
+ *walk++ = state->con->window->id; |
179 |
+ } |
180 |
+ |
181 |
+ ewmh_update_client_list(client_list_windows, client_list_count); |
182 |
+ } |
183 |
|
184 |
DLOG("PUSHING CHANGES\n"); |
185 |
x_push_node(con); |
b/testcases/t/223-net-client-list.t
191 |
@@ -0,0 +1,99 @@ |
192 |
+#!perl |
193 |
+# vim:ts=4:sw=4:expandtab |
194 |
+# |
195 |
+# Please read the following documents before working on tests: |
196 |
+# • http://build.i3wm.org/docs/testsuite.html |
197 |
+# (or docs/testsuite) |
198 |
+# |
199 |
+# • http://build.i3wm.org/docs/lib-i3test.html |
200 |
+# (alternatively: perldoc ./testcases/lib/i3test.pm) |
201 |
+# |
202 |
+# • http://build.i3wm.org/docs/ipc.html |
203 |
+# (or docs/ipc) |
204 |
+# |
205 |
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf |
206 |
+# (unless you are already familiar with Perl) |
207 |
+# |
208 |
+# Test that _NET_CLIENT_LIST is properly updated on the root window as windows |
209 |
+# are mapped and unmapped. |
210 |
+# |
211 |
+# Information on this property can be found here: |
212 |
+# http://standards.freedesktop.org/wm-spec/latest/ar01s03.html |
213 |
+# |
214 |
+# > These arrays contain all X Windows managed by the Window Manager. |
215 |
+# > _NET_CLIENT_LIST has initial mapping order, starting with the oldest window. |
216 |
+# |
217 |
+# Ticket: #1099 |
218 |
+# Bug still in: 4.7.2-8-ge6cce92 |
219 |
+use i3test; |
220 |
+ |
221 |
+sub get_client_list { |
222 |
+ my $cookie = $x->get_property( |
223 |
+ 0, |
224 |
+ $x->get_root_window(), |
225 |
+ $x->atom(name => '_NET_CLIENT_LIST')->id, |
226 |
+ $x->atom(name => 'WINDOW')->id, |
227 |
+ 0, |
228 |
+ 4096, |
229 |
+ ); |
230 |
+ my $reply = $x->get_property_reply($cookie->{sequence}); |
231 |
+ my $len = $reply->{length}; |
232 |
+ |
233 |
+ return () if $len == 0; |
234 |
+ return unpack("L$len", $reply->{value}); |
235 |
+} |
236 |
+ |
237 |
+# Mapping a window should give us one client in _NET_CLIENT_LIST |
238 |
+my $win1 = open_window; |
239 |
+ |
240 |
+my @clients = get_client_list; |
241 |
+ |
242 |
+is(@clients, 1, 'One client in _NET_CLIENT_LIST'); |
243 |
+is($clients[0], $win1->{id}, 'Correct client in position one'); |
244 |
+ |
245 |
+# Mapping another window should give us two clients in the list with the last |
246 |
+# client mapped in the last position |
247 |
+my $win2 = open_window; |
248 |
+ |
249 |
+@clients = get_client_list; |
250 |
+is(@clients, 2, 'Added mapped client to list (2)'); |
251 |
+is($clients[0], $win1->{id}, 'Correct client in position one'); |
252 |
+is($clients[1], $win2->{id}, 'Correct client in position two'); |
253 |
+ |
254 |
+# Mapping another window should give us three clients in the list in the order |
255 |
+# they were mapped |
256 |
+my $win3 = open_window; |
257 |
+ |
258 |
+@clients = get_client_list; |
259 |
+is(@clients, 3, 'Added mapped client to list (3)'); |
260 |
+is($clients[0], $win1->{id}, 'Correct client in position one'); |
261 |
+is($clients[1], $win2->{id}, 'Correct client in position two'); |
262 |
+is($clients[2], $win3->{id}, 'Correct client in position three'); |
263 |
+ |
264 |
+# Unmapping the second window should give us the two remaining clients in the |
265 |
+# order they were mapped |
266 |
+$win2->unmap; |
267 |
+wait_for_unmap($win2); |
268 |
+ |
269 |
+@clients = get_client_list; |
270 |
+is(@clients, 2, 'Removed unmapped client from list (2)'); |
271 |
+is($clients[0], $win1->{id}, 'Correct client in position one'); |
272 |
+is($clients[1], $win3->{id}, 'Correct client in position two'); |
273 |
+ |
274 |
+# Unmapping the first window should give us only the remaining mapped window in |
275 |
+# the list |
276 |
+$win1->unmap; |
277 |
+wait_for_unmap($win1); |
278 |
+ |
279 |
+@clients = get_client_list; |
280 |
+is(@clients, 1, 'Removed unmapped client from list (1)'); |
281 |
+is($clients[0], $win3->{id}, 'Correct client in position one'); |
282 |
+ |
283 |
+# Unmapping the last window should give us an empty list |
284 |
+$win3->unmap; |
285 |
+wait_for_unmap($win3); |
286 |
+ |
287 |
+@clients = get_client_list; |
288 |
+is(@clients, 0, 'Removed unmapped client from list (0)'); |
289 |
+ |
290 |
+done_testing; |