Merge pull request #2953 from CyberShadow/focus_wrapping

Add "focus_wrapping" option
This commit is contained in:
Michael Stapelberg 2017-09-27 09:31:39 -07:00 committed by GitHub
commit 54d61b510d
10 changed files with 137 additions and 25 deletions

View File

@ -1055,26 +1055,39 @@ popup_during_fullscreen smart
=== Focus wrapping === Focus wrapping
When being in a tabbed or stacked container, the first container will be By default, when in a container with several windows or child containers, the
focused when you use +focus down+ on the last container -- the focus wraps. If opposite window will be focused when trying to move the focus over the edge of
however there is another stacked/tabbed container in that direction, focus will a container (and there are no other containers in that direction) -- the focus
be set on that container. This is the default behavior so you can navigate to wraps.
all your windows without having to use +focus parent+.
If desired, you can disable this behavior by setting the +focus_wrapping+
configuration directive to the value +no+.
When enabled, focus wrapping does not occur by default if there is another
window or container in the specified direction, and focus will instead be set
on that window or container. This is the default behavior so you can navigate
to all your windows without having to use +focus parent+.
If you want the focus to *always* wrap and you are aware of using +focus If you want the focus to *always* wrap and you are aware of using +focus
parent+ to switch to different containers, you can use the parent+ to switch to different containers, you can instead set +focus_wrapping+
+force_focus_wrapping+ configuration directive. After enabling it, the focus to the value +force+.
will always wrap.
*Syntax*: *Syntax*:
--------------------------- ---------------------------
force_focus_wrapping yes|no focus_wrapping yes|no|force
# Legacy syntax, equivalent to "focus_wrapping force"
force_focus_wrapping yes
--------------------------- ---------------------------
*Example*: *Examples*:
------------------------ -----------------
force_focus_wrapping yes # Disable focus wrapping
------------------------ focus_wrapping no
# Force focus wrapping
focus_wrapping force
-----------------
=== Forcing Xinerama === Forcing Xinerama

View File

@ -49,6 +49,7 @@ CFGFUN(workspace_layout, const char *layout);
CFGFUN(workspace_back_and_forth, const char *value); CFGFUN(workspace_back_and_forth, const char *value);
CFGFUN(focus_follows_mouse, const char *value); CFGFUN(focus_follows_mouse, const char *value);
CFGFUN(mouse_warping, const char *value); CFGFUN(mouse_warping, const char *value);
CFGFUN(focus_wrapping, const char *value);
CFGFUN(force_focus_wrapping, const char *value); CFGFUN(force_focus_wrapping, const char *value);
CFGFUN(force_xinerama, const char *value); CFGFUN(force_xinerama, const char *value);
CFGFUN(disable_randr15, const char *value); CFGFUN(disable_randr15, const char *value);

View File

@ -137,15 +137,24 @@ struct Config {
* comes with i3. Thus, you can turn it off entirely. */ * comes with i3. Thus, you can turn it off entirely. */
bool disable_workspace_bar; bool disable_workspace_bar;
/** Think of the following layout: Horizontal workspace with a tabbed /** When focus wrapping is enabled (the default), attempting to
* con on the left of the screen and a terminal on the right of the * move focus past the edge of the screen (in other words, in a
* screen. You are in the second container in the tabbed container and * direction in which there are no more containers to focus) will
* focus to the right. By default, i3 will set focus to the terminal on * cause the focus to wrap to the opposite edge of the current
* the right. If you are in the first container in the tabbed container * container. When it is disabled, nothing happens; the current
* however, focusing to the left will wrap. This option forces i3 to * focus is preserved.
* always wrap, which will result in you having to use "focus parent" *
* more often. */ * Additionally, focus wrapping may be forced. Think of the
bool force_focus_wrapping; * following layout: Horizontal workspace with a tabbed con on the
* left of the screen and a terminal on the right of the
* screen. You are in the second container in the tabbed container
* and focus to the right. By default, i3 will set focus to the
* terminal on the right. If you are in the first container in the
* tabbed container however, focusing to the left will
* wrap. Setting focus_wrapping to FOCUS_WRAPPING_FORCE forces i3
* to always wrap, which will result in you having to use "focus
* parent" more often. */
focus_wrapping_t focus_wrapping;
/** By default, use the RandR API for multi-monitor setups. /** By default, use the RandR API for multi-monitor setups.
* Unfortunately, the nVidia binary graphics driver doesn't support * Unfortunately, the nVidia binary graphics driver doesn't support

View File

@ -133,6 +133,15 @@ typedef enum {
POINTER_WARPING_NONE = 1 POINTER_WARPING_NONE = 1
} warping_t; } warping_t;
/**
* Focus wrapping modes.
*/
typedef enum {
FOCUS_WRAPPING_OFF = 0,
FOCUS_WRAPPING_ON = 1,
FOCUS_WRAPPING_FORCE = 2
} focus_wrapping_t;
/** /**
* Stores a rectangle, for example the size of a window, the child window etc. * Stores a rectangle, for example the size of a window, the child window etc.
* It needs to be packed so that the compiler will not add any padding bytes. * It needs to be packed so that the compiler will not add any padding bytes.

View File

@ -36,6 +36,7 @@ state INITIAL:
'no_focus' -> NO_FOCUS 'no_focus' -> NO_FOCUS
'focus_follows_mouse' -> FOCUS_FOLLOWS_MOUSE 'focus_follows_mouse' -> FOCUS_FOLLOWS_MOUSE
'mouse_warping' -> MOUSE_WARPING 'mouse_warping' -> MOUSE_WARPING
'focus_wrapping' -> FOCUS_WRAPPING
'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING 'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING
'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA 'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA
'disable_randr15', 'disable-randr15' -> DISABLE_RANDR15 'disable_randr15', 'disable-randr15' -> DISABLE_RANDR15
@ -209,6 +210,11 @@ state MOUSE_WARPING:
value = 'none', 'output' value = 'none', 'output'
-> call cfg_mouse_warping($value) -> call cfg_mouse_warping($value)
# focus_wrapping
state FOCUS_WRAPPING:
value = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive', 'force'
-> call cfg_focus_wrapping($value)
# force_focus_wrapping # force_focus_wrapping
state FORCE_FOCUS_WRAPPING: state FORCE_FOCUS_WRAPPING:
value = word value = word

View File

@ -227,6 +227,8 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
if (config.workspace_urgency_timer == 0) if (config.workspace_urgency_timer == 0)
config.workspace_urgency_timer = 0.5; config.workspace_urgency_timer = 0.5;
config.focus_wrapping = FOCUS_WRAPPING_ON;
parse_configuration(override_configpath, true); parse_configuration(override_configpath, true);
if (reload) { if (reload) {

View File

@ -264,8 +264,27 @@ CFGFUN(disable_randr15, const char *value) {
config.disable_randr15 = eval_boolstr(value); config.disable_randr15 = eval_boolstr(value);
} }
CFGFUN(focus_wrapping, const char *value) {
if (strcmp(value, "force") == 0) {
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
} else if (eval_boolstr(value)) {
config.focus_wrapping = FOCUS_WRAPPING_ON;
} else {
config.focus_wrapping = FOCUS_WRAPPING_OFF;
}
}
CFGFUN(force_focus_wrapping, const char *value) { CFGFUN(force_focus_wrapping, const char *value) {
config.force_focus_wrapping = eval_boolstr(value); /* Legacy syntax. */
if (eval_boolstr(value)) {
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
} else {
/* For "force_focus_wrapping off", don't enable or disable
* focus wrapping, just ensure it's not forced. */
if (config.focus_wrapping == FOCUS_WRAPPING_FORCE) {
config.focus_wrapping = FOCUS_WRAPPING_ON;
}
}
} }
CFGFUN(workspace_back_and_forth, const char *value) { CFGFUN(workspace_back_and_forth, const char *value) {

View File

@ -641,7 +641,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
next = TAILQ_PREV(current, nodes_head, nodes); next = TAILQ_PREV(current, nodes_head, nodes);
if (!next) { if (!next) {
if (!config.force_focus_wrapping) { if (config.focus_wrapping != FOCUS_WRAPPING_FORCE) {
/* If there is no next/previous container, we check if we can focus one /* If there is no next/previous container, we check if we can focus one
* when going higher (without wrapping, though). If so, we are done, if * when going higher (without wrapping, though). If so, we are done, if
* not, we wrap */ * not, we wrap */
@ -675,7 +675,8 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
* *
*/ */
void tree_next(char way, orientation_t orientation) { void tree_next(char way, orientation_t orientation) {
_tree_next(focused, way, orientation, true); _tree_next(focused, way, orientation,
config.focus_wrapping != FOCUS_WRAPPING_OFF);
} }
/* /*

View File

@ -470,6 +470,7 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
no_focus no_focus
focus_follows_mouse focus_follows_mouse
mouse_warping mouse_warping
focus_wrapping
force_focus_wrapping force_focus_wrapping
force_xinerama force_xinerama
force-xinerama force-xinerama

View File

@ -0,0 +1,51 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • http://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • http://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • http://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Tests that focus does not wrap when focus_wrapping is disabled in
# the configuration.
# Ticket: #2352
# Bug still in: 4.14-72-g6411130c
use i3test i3_config => <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
focus_wrapping no
EOT
sub test_orientation {
my ($orientation, $prev, $next) = @_;
my $tmp = fresh_workspace;
cmd "split $orientation";
my $win1 = open_window;
my $win2 = open_window;
is($x->input_focus, $win2->id, "Second window focused initially");
cmd "focus $prev";
is($x->input_focus, $win1->id, "First window focused");
cmd "focus $prev";
is($x->input_focus, $win1->id, "First window still focused");
cmd "focus $next";
is($x->input_focus, $win2->id, "Second window focused");
cmd "focus $next";
is($x->input_focus, $win2->id, "Second window still focused");
}
test_orientation('v', 'up', 'down');
test_orientation('h', 'left', 'right');
done_testing;