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:
parent
bbc4c99c72
commit
24a58d2952
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
13
src/tree.c
13
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);
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue