diff --git a/docs/userguide b/docs/userguide index a8b0e00d..2b1dde0b 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1105,9 +1105,14 @@ If you want the focus to *always* wrap and you are aware of using +focus parent+ to switch to different containers, you can instead set +focus_wrapping+ to the value +force+. +To restrict focus inside the current workspace set +focus_wrapping+ to the +value +workspace+. You will need to use +focus parent+ until a workspace is +selected to switch to a different workspace using the focus commands (the ++workspace+ command will still work as expected). + *Syntax*: --------------------------- -focus_wrapping yes|no|force +focus_wrapping yes|no|force|workspace # Legacy syntax, equivalent to "focus_wrapping force" force_focus_wrapping yes diff --git a/include/data.h b/include/data.h index b8c31c52..c0e34b41 100644 --- a/include/data.h +++ b/include/data.h @@ -141,7 +141,8 @@ typedef enum { typedef enum { FOCUS_WRAPPING_OFF = 0, FOCUS_WRAPPING_ON = 1, - FOCUS_WRAPPING_FORCE = 2 + FOCUS_WRAPPING_FORCE = 2, + FOCUS_WRAPPING_WORKSPACE = 3 } focus_wrapping_t; /** diff --git a/parser-specs/config.spec b/parser-specs/config.spec index d5b7e063..93901fd9 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -215,7 +215,7 @@ state MOUSE_WARPING: # focus_wrapping state FOCUS_WRAPPING: - value = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive', 'force' + value = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive', 'force', 'workspace' -> call cfg_focus_wrapping($value) # force_focus_wrapping diff --git a/src/config_directives.c b/src/config_directives.c index f647fb4d..4712296c 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -268,6 +268,8 @@ CFGFUN(disable_randr15, const char *value) { CFGFUN(focus_wrapping, const char *value) { if (strcmp(value, "force") == 0) { config.focus_wrapping = FOCUS_WRAPPING_FORCE; + } else if (strcmp(value, "workspace") == 0) { + config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE; } else if (eval_boolstr(value)) { config.focus_wrapping = FOCUS_WRAPPING_ON; } else { diff --git a/src/tree.c b/src/tree.c index 039c3a5e..a81acce8 100644 --- a/src/tree.c +++ b/src/tree.c @@ -497,6 +497,13 @@ static Con *get_tree_next(Con *con, direction_t direction) { const orientation_t orientation = orientation_from_direction(direction); Con *first_wrap = NULL; + + if (con->type == CT_WORKSPACE) { + /* Special case for FOCUS_WRAPPING_WORKSPACE: allow the focus to leave + * the workspace only when a workspace is selected. */ + goto handle_workspace; + } + while (con->type != CT_WORKSPACE) { if (con->fullscreen_mode == CF_OUTPUT) { /* We've reached a fullscreen container. Directional focus should @@ -542,6 +549,7 @@ static Con *get_tree_next(Con *con, direction_t direction) { switch (config.focus_wrapping) { case FOCUS_WRAPPING_OFF: break; + case FOCUS_WRAPPING_WORKSPACE: case FOCUS_WRAPPING_ON: if (!first_wrap && con_fullscreen_permits_focusing(wrap)) { first_wrap = wrap; @@ -561,6 +569,11 @@ static Con *get_tree_next(Con *con, direction_t direction) { } assert(con->type == CT_WORKSPACE); + if (config.focus_wrapping == FOCUS_WRAPPING_WORKSPACE) { + return first_wrap; + } + +handle_workspace:; Con *workspace = get_tree_next_workspace(con, direction); return workspace ? workspace : first_wrap; } diff --git a/testcases/t/308-focus_wrapping.t b/testcases/t/308-focus_wrapping.t index 7053b5ae..9fa5858c 100644 --- a/testcases/t/308-focus_wrapping.t +++ b/testcases/t/308-focus_wrapping.t @@ -15,10 +15,11 @@ # (unless you are already familiar with Perl) # # Tests focus_wrapping yes|no|force|workspace with cmp_tree -# Tickets: #2352 +# Tickets: #2180 #2352 use i3test i3_autostart => 0; my $pid = 0; + sub focus_wrapping { my ($setting) = @_; @@ -222,7 +223,7 @@ cmp_tree( cb => sub { cmd 'focus left'; }); -cmp_tree( # 'focus_wrapping force' exclusive test +cmp_tree( # 'focus_wrapping force' exclusive test msg => 'But leaves when selecting parent', layout_before => 'S[a b] V[c d T[e* f g]]', layout_after => 'S[a b*] V[c d T[e f g]]', @@ -239,7 +240,7 @@ cmp_tree( cmd 'focus right'; is(focused_ws, 'left-top', 'Correct workspace focused'); }); -cmp_tree( # 'focus_wrapping force|workspace' exclusive test +cmp_tree( # 'focus_wrapping force|workspace' exclusive test msg => 'But leaves when selecting parent x2', layout_before => 'S[a b] V[c d* T[e f g]]', layout_after => 'S[a b] V[c d T[e f g]]', @@ -249,7 +250,96 @@ cmp_tree( # 'focus_wrapping force|workspace' exclusive test is(focused_ws, 'right-top', 'Correct workspace focused'); }); +############################################################################### +focus_wrapping('workspace'); +# See issue #2180 +############################################################################### + +cmp_tree( + msg => 'Normal focus up - should work for all options', + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a* b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus up'; + }); +cmp_tree( + msg => 'Normal focus right - should work for all options', + layout_before => 'S[a b] V[c d T[e* f g]]', + layout_after => 'S[a b] V[c d T[e f* g]]', + ws => 'left-top', + cb => sub { + cmd 'focus right'; + }); +cmp_tree( + msg => 'Focus does not leave workspace vertically', + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a* b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus down'; + is(focused_ws, 'left-top', 'Correct workspace focused'); + }); +cmp_tree( + msg => 'Focus wraps vertically', + layout_before => 'S[a* b] V[c d T[e f g]]', + layout_after => 'S[a b*] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus up'; + }); +cmp_tree( + msg => 'Focus wraps horizontally', + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a b] V[c d T[e f g*]]', + ws => 'left-top', + cb => sub { + cmd 'focus left'; + }); +cmp_tree( + msg => 'Directional focus in the orientation of the parent does not wrap', + layout_before => 'S[a b] V[c d T[e* f g]]', + layout_after => 'S[a b*] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus left'; + }); +cmp_tree( + msg => 'Focus does not leave workspace horizontally', + layout_before => 'S[a b] V[c d* T[e f g]]', + layout_after => 'S[a b*] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus right'; + is(focused_ws, 'left-top', 'Correct workspace focused'); + }); +cmp_tree( # 'focus_wrapping force|workspace' exclusive test + msg => 'But leaves when selecting parent x2', + layout_before => 'S[a b] V[c d* T[e f g]]', + layout_after => 'S[a b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus parent, focus parent, focus right'; + is(focused_ws, 'right-top', 'Correct workspace focused'); + }); + +cmp_tree( # 'focus_wrapping workspace' exclusive test + msg => 'x', + layout_before => 'S[a* b] V[c d T[e f g]]', + layout_after => 'S[a b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + subtest 'random tests' => sub { + my @directions = qw(left right top down); + for my $i (1 .. 50) { + my $direction = $directions[rand @directions]; + cmd "focus $direction"; + + return unless is(focused_ws, 'left-top', "'focus $direction' did not change workspace"); + } + }; + }); + exit_gracefully($pid); - done_testing;