Implement focus_wrapping workspace

I had a dilemma about the behaviour here:
1. Prohibit focus leaving the workspace in any case unless if
get_tree_next's initial argument is a workspace. This is what this
commit does (also i3-cycle).
2. Leave the workspace if no warp is possible (eg workspace with single
container or `focus right` with `V[a b c*]`).

Fixes #2180
This commit is contained in:
Orestis Floros 2018-09-15 11:36:48 +03:00 committed by Orestis Floros
parent bbc4c99c72
commit 24a58d2952
No known key found for this signature in database
GPG Key ID: A09DBD7D3222C1C3
6 changed files with 118 additions and 7 deletions

View File

@ -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+ parent+ to switch to different containers, you can instead set +focus_wrapping+
to the value +force+. 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*: *Syntax*:
--------------------------- ---------------------------
focus_wrapping yes|no|force focus_wrapping yes|no|force|workspace
# Legacy syntax, equivalent to "focus_wrapping force" # Legacy syntax, equivalent to "focus_wrapping force"
force_focus_wrapping yes force_focus_wrapping yes

View File

@ -141,7 +141,8 @@ typedef enum {
typedef enum { typedef enum {
FOCUS_WRAPPING_OFF = 0, FOCUS_WRAPPING_OFF = 0,
FOCUS_WRAPPING_ON = 1, FOCUS_WRAPPING_ON = 1,
FOCUS_WRAPPING_FORCE = 2 FOCUS_WRAPPING_FORCE = 2,
FOCUS_WRAPPING_WORKSPACE = 3
} focus_wrapping_t; } focus_wrapping_t;
/** /**

View File

@ -215,7 +215,7 @@ state MOUSE_WARPING:
# focus_wrapping # focus_wrapping
state 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) -> call cfg_focus_wrapping($value)
# force_focus_wrapping # force_focus_wrapping

View File

@ -268,6 +268,8 @@ CFGFUN(disable_randr15, const char *value) {
CFGFUN(focus_wrapping, const char *value) { CFGFUN(focus_wrapping, const char *value) {
if (strcmp(value, "force") == 0) { if (strcmp(value, "force") == 0) {
config.focus_wrapping = FOCUS_WRAPPING_FORCE; config.focus_wrapping = FOCUS_WRAPPING_FORCE;
} else if (strcmp(value, "workspace") == 0) {
config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE;
} else if (eval_boolstr(value)) { } else if (eval_boolstr(value)) {
config.focus_wrapping = FOCUS_WRAPPING_ON; config.focus_wrapping = FOCUS_WRAPPING_ON;
} else { } else {

View File

@ -497,6 +497,13 @@ static Con *get_tree_next(Con *con, direction_t direction) {
const orientation_t orientation = orientation_from_direction(direction); const orientation_t orientation = orientation_from_direction(direction);
Con *first_wrap = NULL; 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) { while (con->type != CT_WORKSPACE) {
if (con->fullscreen_mode == CF_OUTPUT) { if (con->fullscreen_mode == CF_OUTPUT) {
/* We've reached a fullscreen container. Directional focus should /* 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) { switch (config.focus_wrapping) {
case FOCUS_WRAPPING_OFF: case FOCUS_WRAPPING_OFF:
break; break;
case FOCUS_WRAPPING_WORKSPACE:
case FOCUS_WRAPPING_ON: case FOCUS_WRAPPING_ON:
if (!first_wrap && con_fullscreen_permits_focusing(wrap)) { if (!first_wrap && con_fullscreen_permits_focusing(wrap)) {
first_wrap = wrap; first_wrap = wrap;
@ -561,6 +569,11 @@ static Con *get_tree_next(Con *con, direction_t direction) {
} }
assert(con->type == CT_WORKSPACE); 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); Con *workspace = get_tree_next_workspace(con, direction);
return workspace ? workspace : first_wrap; return workspace ? workspace : first_wrap;
} }

View File

@ -15,10 +15,11 @@
# (unless you are already familiar with Perl) # (unless you are already familiar with Perl)
# #
# Tests focus_wrapping yes|no|force|workspace with cmp_tree # Tests focus_wrapping yes|no|force|workspace with cmp_tree
# Tickets: #2352 # Tickets: #2180 #2352
use i3test i3_autostart => 0; use i3test i3_autostart => 0;
my $pid = 0; my $pid = 0;
sub focus_wrapping { sub focus_wrapping {
my ($setting) = @_; my ($setting) = @_;
@ -222,7 +223,7 @@ cmp_tree(
cb => sub { cb => sub {
cmd 'focus left'; cmd 'focus left';
}); });
cmp_tree( # 'focus_wrapping force' exclusive test cmp_tree( # 'focus_wrapping force' exclusive test
msg => 'But leaves when selecting parent', msg => 'But leaves when selecting parent',
layout_before => 'S[a b] V[c d T[e* f g]]', layout_before => 'S[a b] V[c d T[e* f g]]',
layout_after => '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'; cmd 'focus right';
is(focused_ws, 'left-top', 'Correct workspace focused'); 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', msg => 'But leaves when selecting parent x2',
layout_before => 'S[a b] V[c d* T[e f g]]', layout_before => 'S[a b] V[c d* T[e f g]]',
layout_after => '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'); 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); exit_gracefully($pid);
done_testing; done_testing;