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