Implementation of 'floating hide|show|toggle_hide' commands
Patch status: needinfo
Patch by Fabián Ezequiel Gallina
Long description:
These allows changing the visibility of floating windows on all workspaces, but will not affect scratchpad windows. Visibility state is saved in the floating_hidden global variable, and it is included in the IPC's TREE reply as a root attribute.
To apply this patch, use:
curl http://cr.i3wm.org/patch/110/raw.patch | git am
b/include/commands.h
29 |
@@ -134,6 +134,12 @@ void cmd_move_con_to_output(I3_CMD, char *name); |
30 |
void cmd_floating(I3_CMD, char *floating_mode); |
31 |
|
32 |
/** |
33 |
+ * Implementation of floating show|hide|toggle_hide |
34 |
+ * |
35 |
+ */ |
36 |
+void cmd_floating_visibility(I3_CMD, char *visibility_mode); |
37 |
+ |
38 |
+/** |
39 |
* Implementation of 'move workspace to [output] <str>'. |
40 |
* |
41 |
*/ |
b/include/floating.h
46 |
@@ -26,6 +26,10 @@ typedef enum { BORDER_LEFT = (1 << 0), |
47 |
BORDER_TOP = (1 << 2), |
48 |
BORDER_BOTTOM = (1 << 3)} border_t; |
49 |
|
50 |
+typedef enum { FLOATING_TOGGLE_HIDE, |
51 |
+ FLOATING_SHOW, |
52 |
+ FLOATING_HIDE } floating_visibility_t; |
53 |
+ |
54 |
/** |
55 |
* Enables floating mode for the given container by detaching it from its |
56 |
* parent, creating a new container around it and storing this container in the |
57 |
@@ -125,14 +129,8 @@ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, |
58 |
void floating_move(xcb_connection_t *conn, Client *currently_focused, |
59 |
direction_t direction); |
60 |
|
61 |
-/** |
62 |
- * Hides all floating clients (or show them if they are currently hidden) on |
63 |
- * the specified workspace. |
64 |
- * |
65 |
- */ |
66 |
-void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace); |
67 |
- |
68 |
#endif |
69 |
+ |
70 |
/** |
71 |
* This function grabs your pointer and lets you drag stuff around (borders). |
72 |
* Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received |
73 |
@@ -161,4 +159,10 @@ void floating_reposition(Con *con, Rect newrect); |
74 |
*/ |
75 |
void floating_fix_coordinates(Con *con, Rect *old_rect, Rect *new_rect); |
76 |
|
77 |
+/** |
78 |
+ * Sets visibility of floating clients on all workspaces. |
79 |
+ * |
80 |
+ */ |
81 |
+void floating_visibility(floating_visibility_t value); |
82 |
+ |
83 |
#endif |
b/include/tree.h
88 |
@@ -16,6 +16,7 @@ extern Con *croot; |
89 |
extern Con *focused; |
90 |
TAILQ_HEAD(all_cons_head, Con); |
91 |
extern struct all_cons_head all_cons; |
92 |
+extern bool floating_hidden; |
93 |
|
94 |
/** |
95 |
* Initializes the tree by creating the root node, adding all RandR outputs |
b/parser-specs/commands.spec
100 |
@@ -106,7 +106,7 @@ state WORKSPACE: |
101 |
-> call cmd_workspace_back_and_forth() |
102 |
'number' |
103 |
-> WORKSPACE_NUMBER |
104 |
- workspace = string |
105 |
+ workspace = string |
106 |
-> call cmd_workspace_name($workspace) |
107 |
|
108 |
state WORKSPACE_NUMBER: |
109 |
@@ -153,8 +153,11 @@ state SPLIT: |
110 |
direction = 'horizontal', 'vertical', 'v', 'h' |
111 |
-> call cmd_split($direction) |
112 |
|
113 |
+# floating hide|show|toggle_hide |
114 |
# floating enable|disable|toggle |
115 |
state FLOATING: |
116 |
+ visibility = 'hide', 'show', 'toggle_hide' |
117 |
+ -> call cmd_floating_visibility($visibility) |
118 |
floating = 'enable', 'disable', 'toggle' |
119 |
-> call cmd_floating($floating) |
120 |
|
b/src/commands.c
125 |
@@ -1108,12 +1108,44 @@ void cmd_floating(I3_CMD, char *floating_mode) { |
126 |
} |
127 |
} |
128 |
|
129 |
+ if (floating_hidden && con_is_floating(focused)) { |
130 |
+ Con *con, *workspace = con_get_workspace(focused); |
131 |
+ /* When floating containers are hidden and the current->con |
132 |
+ * turned into a floating one, fix focus. */ |
133 |
+ TAILQ_FOREACH(con, &(workspace->focus_head), focused) { |
134 |
+ if (con->type != CT_FLOATING_CON) { |
135 |
+ con_focus(con_descend_focused(con)); |
136 |
+ DLOG("moved focus to %p\n", con); |
137 |
+ break; |
138 |
+ } |
139 |
+ } |
140 |
+ } |
141 |
+ |
142 |
cmd_output->needs_tree_render = true; |
143 |
// XXX: default reply for now, make this a better reply |
144 |
ysuccess(true); |
145 |
} |
146 |
|
147 |
/* |
148 |
+ * Implementation of floating show|hide|toggle_hide |
149 |
+ * |
150 |
+ */ |
151 |
+void cmd_floating_visibility(I3_CMD, char *visibility_mode) { |
152 |
+ |
153 |
+ if (strcmp(visibility_mode, "toggle_hide") == 0) { |
154 |
+ floating_visibility(FLOATING_TOGGLE_HIDE); |
155 |
+ } else { |
156 |
+ if (strcmp(visibility_mode, "show") == 0) { |
157 |
+ floating_visibility(FLOATING_SHOW); |
158 |
+ } else { |
159 |
+ floating_visibility(FLOATING_HIDE); |
160 |
+ } |
161 |
+ } |
162 |
+ |
163 |
+ ysuccess(true); |
164 |
+} |
165 |
+ |
166 |
+/* |
167 |
* Implementation of 'move workspace to [output] <str>'. |
168 |
* |
169 |
*/ |
b/src/floating.c
174 |
@@ -754,33 +754,84 @@ void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_ |
175 |
fake_absolute_configure_notify(conn, currently_focused); |
176 |
/* fake_absolute_configure_notify flushes */ |
177 |
} |
178 |
+#endif |
179 |
|
180 |
/* |
181 |
- * Hides all floating clients (or show them if they are currently hidden) on |
182 |
- * the specified workspace. |
183 |
+ * Sets visibility of floating clients on all workspaces. |
184 |
* |
185 |
*/ |
186 |
-void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) { |
187 |
- Client *client; |
188 |
- |
189 |
- workspace->floating_hidden = !workspace->floating_hidden; |
190 |
- DLOG("floating_hidden is now: %d\n", workspace->floating_hidden); |
191 |
- TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) { |
192 |
- if (workspace->floating_hidden) |
193 |
- client_unmap(conn, client); |
194 |
- else client_map(conn, client); |
195 |
- } |
196 |
+void floating_visibility(floating_visibility_t value) { |
197 |
+ Con *output, *con, *scratchpad_con = NULL, *workspace; |
198 |
+ bool old_floating_hidden = floating_hidden; |
199 |
+ |
200 |
+ if (value == FLOATING_TOGGLE_HIDE) { |
201 |
+ floating_hidden = !floating_hidden; |
202 |
+ DLOG("toggling floating_hidden: %d -> %d\n", |
203 |
+ !floating_hidden, floating_hidden); |
204 |
+ } else if (value == FLOATING_SHOW) { |
205 |
+ floating_hidden = false; |
206 |
+ DLOG("setting floating_hidden to false\n"); |
207 |
+ } else { |
208 |
+ floating_hidden = true; |
209 |
+ DLOG("setting floating_hidden to true\n"); |
210 |
+ } |
211 |
+ |
212 |
+ /* Flag didn't changed, do nothing */ |
213 |
+ if (floating_hidden == old_floating_hidden) |
214 |
+ return; |
215 |
|
216 |
- /* If we just unmapped all floating windows we should ensure that the focus |
217 |
- * is set correctly, that ist, to the first non-floating client in stack */ |
218 |
- if (workspace->floating_hidden) |
219 |
- SLIST_FOREACH(client, &(workspace->focus_stack), focus_clients) { |
220 |
- if (client_is_floating(client)) |
221 |
- continue; |
222 |
- set_focus(conn, client, true); |
223 |
- return; |
224 |
+ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { |
225 |
+ if (con_is_internal(output)) |
226 |
+ continue; |
227 |
+ Con *content = output_get_content(output); |
228 |
+ if (!content || TAILQ_EMPTY(&(content->focus_head))) { |
229 |
+ continue; |
230 |
+ } |
231 |
+ workspace = TAILQ_FIRST(&(content->focus_head)); |
232 |
+ DLOG("applying floating display changes on %p\n", workspace); |
233 |
+ TAILQ_FOREACH(con, &(workspace->floating_head), floating_windows) { |
234 |
+ /* Don't mess with scratchpad windows. */ |
235 |
+ if (con->scratchpad_state != SCRATCHPAD_NONE) { |
236 |
+ /* but in the case there's one showing, save its |
237 |
+ * reference so it can be raised over all re-displayed |
238 |
+ * floating windows. */ |
239 |
+ if (!floating_hidden) { |
240 |
+ scratchpad_con = con; |
241 |
} |
242 |
+ DLOG("ignore scratchpad window %p with state %d\n", |
243 |
+ con, con->scratchpad_state); |
244 |
+ continue; |
245 |
+ } |
246 |
+ con->mapped = !floating_hidden; |
247 |
+ DLOG("mapped for con (and children) %p is now %d\n", |
248 |
+ con, !floating_hidden); |
249 |
+ } |
250 |
+ /* Put the scratchpad window in front of all re-displayed |
251 |
+ * floating windows */ |
252 |
+ if (scratchpad_con != NULL) { |
253 |
+ x_raise_con(scratchpad_con); |
254 |
+ DLOG("re-raised scratchpad window %p\n", scratchpad_con); |
255 |
+ } |
256 |
+ } |
257 |
+ |
258 |
+ Con *current = TAILQ_FIRST(&workspace->focus_head); |
259 |
+ bool floating_focused = (current != NULL && |
260 |
+ current->type == CT_FLOATING_CON && |
261 |
+ current->scratchpad_state == SCRATCHPAD_NONE); |
262 |
+ |
263 |
+ /* If all floating windows are hidden and focus was in a floating |
264 |
+ * container (not a scratchpad window), set focus to the first |
265 |
+ * non-floating window available. */ |
266 |
+ if (floating_hidden && floating_focused) { |
267 |
+ workspace = con_get_workspace(focused); |
268 |
+ TAILQ_FOREACH(con, &(workspace->focus_head), focused) { |
269 |
+ if (con->type != CT_FLOATING_CON) { |
270 |
+ con_focus(con); |
271 |
+ DLOG("moved focus to %p\n", con); |
272 |
+ break; |
273 |
+ } |
274 |
+ } |
275 |
+ } |
276 |
|
277 |
- xcb_flush(conn); |
278 |
+ tree_render(); |
279 |
} |
280 |
-#endif |
b/src/ipc.c
285 |
@@ -165,6 +165,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { |
286 |
else ystr("vertical"); |
287 |
} |
288 |
|
289 |
+ if (con == croot) { |
290 |
+ ystr("floating_hidden"); |
291 |
+ y(bool, floating_hidden); |
292 |
+ } |
293 |
+ |
294 |
ystr("scratchpad_state"); |
295 |
switch (con->scratchpad_state) { |
296 |
case SCRATCHPAD_NONE: |
b/src/main.c
301 |
@@ -92,6 +92,10 @@ bool xkb_supported = true; |
302 |
* the config, for example. */ |
303 |
bool only_check_config = false; |
304 |
|
305 |
+/* Tells if floating windows hidden. This is here for global access |
306 |
+ * because at the time render.c and floating.c need it. */ |
307 |
+bool floating_hidden = false; |
308 |
+ |
309 |
/* |
310 |
* This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. |
311 |
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop |
b/src/render.c
316 |
@@ -268,6 +268,12 @@ void render_con(Con *con, bool render_fullscreen) { |
317 |
Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); |
318 |
Con *child; |
319 |
TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) { |
320 |
+ /* Don't show floating windows when floating_hidden is |
321 |
+ * set but also don't mess with scratchpad windows in |
322 |
+ * the process */ |
323 |
+ if (floating_hidden && |
324 |
+ child->scratchpad_state == SCRATCHPAD_NONE) |
325 |
+ continue; |
326 |
/* Don’t render floating windows when there is a fullscreen window |
327 |
* on that workspace. Necessary to make floating fullscreen work |
328 |
* correctly (ticket #564). */ |
b/testcases/t/116-nestedcons.t
333 |
@@ -60,6 +60,7 @@ my $expected = { |
334 |
percent => undef, |
335 |
layout => 'splith', |
336 |
floating => 'auto_off', |
337 |
+ floating_hidden => JSON::XS::false, |
338 |
last_split_layout => 'splith', |
339 |
scratchpad_state => 'none', |
340 |
focus => $ignore, |
b/testcases/t/206-floating_toggle_hide.t
346 |
@@ -0,0 +1,169 @@ |
347 |
+#!perl |
348 |
+# vim:ts=4:sw=4:expandtab |
349 |
+# |
350 |
+# Please read the following documents before working on tests: |
351 |
+# • http://build.i3wm.org/docs/testsuite.html |
352 |
+# (or docs/testsuite) |
353 |
+# |
354 |
+# • http://build.i3wm.org/docs/lib-i3test.html |
355 |
+# (alternatively: perldoc ./testcases/lib/i3test.pm) |
356 |
+# |
357 |
+# • http://build.i3wm.org/docs/ipc.html |
358 |
+# (or docs/ipc) |
359 |
+# |
360 |
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf |
361 |
+# (unless you are already familiar with Perl) |
362 |
+# |
363 |
+# Test floating_toggle_hide command implementation. |
364 |
+# Ticket: #807 |
365 |
+# Bug still in: 4.4-145-g8327f83 |
366 |
+use i3test; |
367 |
+ |
368 |
+my $i3 = i3(get_socket_path()); |
369 |
+ |
370 |
+sub get_floating_hidden () { |
371 |
+ my $sock = i3(get_socket_path())->get_tree; |
372 |
+ sync_with_i3; |
373 |
+ return $sock->recv->{floating_hidden}; |
374 |
+} |
375 |
+ |
376 |
+sub clear_scratchpad () { |
377 |
+ # Clear scratchpad |
378 |
+ while (scalar @{get_ws('__i3_scratch')->{floating_nodes}}) { |
379 |
+ cmd 'scratchpad show'; |
380 |
+ cmd 'kill'; |
381 |
+ } |
382 |
+ |
383 |
+} |
384 |
+ |
385 |
+################################################################### |
386 |
+# Floating visibility toggle works globally |
387 |
+################################################################### |
388 |
+ |
389 |
+my $ws1 = fresh_workspace; |
390 |
+my $ws2 = fresh_workspace; |
391 |
+ |
392 |
+my $ws1_con = get_ws($ws1); |
393 |
+ok(!$ws1_con->{floating_hidden}, 'ws1 floating cons are not hidden by default'); |
394 |
+ |
395 |
+cmd 'floating toggle_hide'; |
396 |
+$ws1_con = get_ws($ws1); |
397 |
+ok(get_floating_hidden(), 'ws1 floating cons are now hidden'); |
398 |
+ |
399 |
+my $ws2_con = get_ws($ws2); |
400 |
+ok(get_floating_hidden(), 'ws2 should have the same floating hidden status'); |
401 |
+ |
402 |
+# Show floating again |
403 |
+cmd 'floating toggle_hide'; |
404 |
+ |
405 |
+################################################################### |
406 |
+# Floating windows mapped property should match !floating_hidden |
407 |
+################################################################### |
408 |
+ |
409 |
+$ws1 = fresh_workspace; |
410 |
+$ws1_con = get_ws($ws1); |
411 |
+my $ws1_window = open_floating_window; |
412 |
+$ws2 = fresh_workspace; |
413 |
+my $ws2_window = open_floating_window; |
414 |
+ |
415 |
+cmd "workspace $ws1"; |
416 |
+ok($ws1_window->mapped, 'ws1 window is mapped'); |
417 |
+ |
418 |
+cmd 'floating hide'; |
419 |
+ok(!$ws1_window->mapped, 'ws1 window is not mapped'); |
420 |
+ |
421 |
+cmd "workspace $ws2"; |
422 |
+ok(!$ws2_window->mapped, 'ws2 window is not mapped'); |
423 |
+ |
424 |
+cmd 'floating show'; |
425 |
+ok($ws2_window->mapped, 'ws2 window is mapped'); |
426 |
+ |
427 |
+cmd "workspace $ws1"; |
428 |
+ok($ws1_window->mapped, 'ws1 window is mapped'); |
429 |
+ |
430 |
+################################################################### |
431 |
+# Focus must be relocated if it was on a hidden floating window |
432 |
+################################################################### |
433 |
+ |
434 |
+$ws1 = fresh_workspace; |
435 |
+$ws1_window = open_window; |
436 |
+cmd 'focus tiling'; |
437 |
+my $ws1_focused = get_focused($ws1); |
438 |
+my $ws1_floating_window = open_floating_window; |
439 |
+cmd 'focus floating'; |
440 |
+my $ws1_floating_focused = get_focused($ws1); |
441 |
+ |
442 |
+is(get_focused($ws1), $ws1_floating_focused, 'the floating window is now focused'); |
443 |
+ |
444 |
+cmd 'floating hide'; |
445 |
+is(get_focused($ws1), $ws1_focused, 'the focus changed to the tiling window'); |
446 |
+ |
447 |
+cmd 'floating show'; |
448 |
+ |
449 |
+################################################################### |
450 |
+# Don't mess with focus when its on a tiling window |
451 |
+################################################################### |
452 |
+ |
453 |
+$ws1 = fresh_workspace; |
454 |
+open_floating_window; |
455 |
+my $ws1_win = open_window; |
456 |
+cmd 'focus tiling'; |
457 |
+my $tiling_win_focused = get_focused($ws1); |
458 |
+ |
459 |
+cmd 'floating hide'; |
460 |
+is(get_focused($ws1), $tiling_win_focused, 'the focus remained on the tiling window'); |
461 |
+ |
462 |
+cmd 'floating show'; |
463 |
+ |
464 |
+################################################################### |
465 |
+# Floating enable must focus tiling if floating cons are hidden |
466 |
+################################################################### |
467 |
+ |
468 |
+$ws1 = fresh_workspace; |
469 |
+my $ws1_win1 = open_window; |
470 |
+my $ws1_win1_focused = get_focused($ws1); |
471 |
+my $ws1_win2 = open_window; |
472 |
+my $ws1_win2_focused = get_focused($ws1); |
473 |
+ |
474 |
+cmd 'floating hide'; |
475 |
+cmd 'floating enable'; |
476 |
+is(get_focused($ws1), $ws1_win1_focused, 'the focus changed to the tiling window'); |
477 |
+ |
478 |
+cmd 'floating show'; |
479 |
+ |
480 |
+################################################################### |
481 |
+# Don't mess with focus when its on a scratchpad window |
482 |
+################################################################### |
483 |
+ |
484 |
+$ws1 = fresh_workspace; |
485 |
+open_floating_window; |
486 |
+open_floating_window; |
487 |
+open_window; |
488 |
+open_window; |
489 |
+ |
490 |
+clear_scratchpad(); |
491 |
+ |
492 |
+my $scratch_win = open_window; |
493 |
+cmd 'focus tiling'; |
494 |
+cmd 'scratchpad move'; |
495 |
+cmd 'scratchpad show'; |
496 |
+my $scratch_win_focused = get_focused($ws1); |
497 |
+ |
498 |
+cmd 'floating hide'; |
499 |
+is(get_focused($ws1), $scratch_win_focused, 'the focus remained on the scratchpad window'); |
500 |
+ |
501 |
+################################################################### |
502 |
+# Don't mess with scratchpad window mapping |
503 |
+################################################################### |
504 |
+ |
505 |
+ok($scratch_win->mapped, 'scratchpad window remained mapped after hidding floating ones'); |
506 |
+ |
507 |
+################################################################### |
508 |
+# Scratchpad must remain on top of re-displayed floating cons |
509 |
+################################################################### |
510 |
+ |
511 |
+cmd 'floating show'; |
512 |
+(my $nodes) = get_ws_content($ws1); |
513 |
+is($scratch_win->id, $nodes->[-1]->{window}, 'scratchpad is on top'); |
514 |
+ |
515 |
+done_testing; |