Merge pull request #3407 from orestisfl/tree_next
tree_next refactor & enhancements
This commit is contained in:
commit
cb9620c60a
|
@ -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
|
||||||
|
@ -2053,6 +2058,12 @@ parent::
|
||||||
child::
|
child::
|
||||||
The opposite of +focus parent+, sets the focus to the last focused
|
The opposite of +focus parent+, sets the focus to the last focused
|
||||||
child container.
|
child container.
|
||||||
|
next|prev::
|
||||||
|
Automatically sets focus to the adjacent container. If +sibling+ is
|
||||||
|
specified, the command will focus the exact sibling container,
|
||||||
|
including non-leaf containers like split containers. Otherwise, it is
|
||||||
|
an automatic version of +focus left|right|up|down+ in the orientation
|
||||||
|
of the parent container.
|
||||||
floating::
|
floating::
|
||||||
Sets focus to the last focused floating container.
|
Sets focus to the last focused floating container.
|
||||||
tiling::
|
tiling::
|
||||||
|
@ -2068,6 +2079,7 @@ output::
|
||||||
<criteria> focus
|
<criteria> focus
|
||||||
focus left|right|down|up
|
focus left|right|down|up
|
||||||
focus parent|child|floating|tiling|mode_toggle
|
focus parent|child|floating|tiling|mode_toggle
|
||||||
|
focus next|prev [sibling]
|
||||||
focus output left|right|up|down|primary|<output>
|
focus output left|right|up|down|primary|<output>
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -182,6 +182,12 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command);
|
||||||
*/
|
*/
|
||||||
void cmd_focus_direction(I3_CMD, const char *direction);
|
void cmd_focus_direction(I3_CMD, const char *direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of 'focus next|prev sibling'
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void cmd_focus_sibling(I3_CMD, const char *direction);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of 'focus tiling|floating|mode_toggle'.
|
* Implementation of 'focus tiling|floating|mode_toggle'.
|
||||||
*
|
*
|
||||||
|
|
|
@ -59,6 +59,8 @@ typedef enum { D_LEFT,
|
||||||
typedef enum { NO_ORIENTATION = 0,
|
typedef enum { NO_ORIENTATION = 0,
|
||||||
HORIZ,
|
HORIZ,
|
||||||
VERT } orientation_t;
|
VERT } orientation_t;
|
||||||
|
typedef enum { BEFORE,
|
||||||
|
AFTER } position_t;
|
||||||
typedef enum { BS_NORMAL = 0,
|
typedef enum { BS_NORMAL = 0,
|
||||||
BS_NONE = 1,
|
BS_NONE = 1,
|
||||||
BS_PIXEL = 2 } border_style_t;
|
BS_PIXEL = 2 } border_style_t;
|
||||||
|
@ -139,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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -12,14 +12,10 @@
|
||||||
#include <config.h>
|
#include <config.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves the given container in the given direction (TOK_LEFT, TOK_RIGHT,
|
* Moves the given container in the given direction
|
||||||
* TOK_UP, TOK_DOWN from cmdparse.l)
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void tree_move(Con *con, int direction);
|
void tree_move(Con *con, direction_t direction);
|
||||||
|
|
||||||
typedef enum { BEFORE,
|
|
||||||
AFTER } position_t;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function detaches 'con' from its parent and inserts it either before or
|
* This function detaches 'con' from its parent and inserts it either before or
|
||||||
|
|
|
@ -59,11 +59,16 @@ bool level_down(void);
|
||||||
void tree_render(void);
|
void tree_render(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes focus in the given way (next/previous) and given orientation
|
* Changes focus in the given direction
|
||||||
* (horizontal/vertical).
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void tree_next(char way, orientation_t orientation);
|
void tree_next(Con *con, direction_t direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the previous / next sibling
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Con *get_tree_next_sibling(Con *con, position_t direction);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the given container including all children.
|
* Closes the given container including all children.
|
||||||
|
|
|
@ -181,3 +181,15 @@ ssize_t slurp(const char *path, char **buf);
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
orientation_t orientation_from_direction(direction_t direction);
|
orientation_t orientation_from_direction(direction_t direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a direction to its corresponding position.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
position_t position_from_direction(direction_t direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert orientation and position to the corresponding direction.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
direction_t direction_from_orientation_position(orientation_t orientation, position_t position);
|
||||||
|
|
|
@ -146,6 +146,8 @@ state WORKSPACE_NUMBER:
|
||||||
state FOCUS:
|
state FOCUS:
|
||||||
direction = 'left', 'right', 'up', 'down'
|
direction = 'left', 'right', 'up', 'down'
|
||||||
-> call cmd_focus_direction($direction)
|
-> call cmd_focus_direction($direction)
|
||||||
|
direction = 'prev', 'next'
|
||||||
|
-> FOCUS_AUTO
|
||||||
'output'
|
'output'
|
||||||
-> FOCUS_OUTPUT
|
-> FOCUS_OUTPUT
|
||||||
window_mode = 'tiling', 'floating', 'mode_toggle'
|
window_mode = 'tiling', 'floating', 'mode_toggle'
|
||||||
|
@ -155,6 +157,12 @@ state FOCUS:
|
||||||
end
|
end
|
||||||
-> call cmd_focus()
|
-> call cmd_focus()
|
||||||
|
|
||||||
|
state FOCUS_AUTO:
|
||||||
|
'sibling'
|
||||||
|
-> call cmd_focus_sibling($direction)
|
||||||
|
end
|
||||||
|
-> call cmd_focus_direction($direction)
|
||||||
|
|
||||||
state FOCUS_OUTPUT:
|
state FOCUS_OUTPUT:
|
||||||
output = string
|
output = string
|
||||||
-> call cmd_focus_output($output)
|
-> call cmd_focus_output($output)
|
||||||
|
|
|
@ -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
|
||||||
|
|
18
src/click.c
18
src/click.c
|
@ -212,21 +212,13 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
||||||
event->detail == XCB_BUTTON_SCROLL_LEFT ||
|
event->detail == XCB_BUTTON_SCROLL_LEFT ||
|
||||||
event->detail == XCB_BUTTON_SCROLL_RIGHT)) {
|
event->detail == XCB_BUTTON_SCROLL_RIGHT)) {
|
||||||
DLOG("Scrolling on a window decoration\n");
|
DLOG("Scrolling on a window decoration\n");
|
||||||
orientation_t orientation = con_orientation(con->parent);
|
|
||||||
/* Use the focused child of the tabbed / stacked container, not the
|
/* Use the focused child of the tabbed / stacked container, not the
|
||||||
* container the user scrolled on. */
|
* container the user scrolled on. */
|
||||||
Con *focused = con->parent;
|
Con *current = TAILQ_FIRST(&(con->parent->focus_head));
|
||||||
focused = TAILQ_FIRST(&(focused->focus_head));
|
const position_t direction =
|
||||||
con_activate(con_descend_focused(focused));
|
(event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) ? BEFORE : AFTER;
|
||||||
/* To prevent scrolling from going outside the container (see ticket
|
Con *next = get_tree_next_sibling(current, direction);
|
||||||
* #557), we first check if scrolling is possible at all. */
|
con_activate(con_descend_focused(next ? next : current));
|
||||||
bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
|
|
||||||
bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL);
|
|
||||||
if ((event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) && scroll_prev_possible) {
|
|
||||||
tree_next('p', orientation);
|
|
||||||
} else if ((event->detail == XCB_BUTTON_SCROLL_DOWN || event->detail == XCB_BUTTON_SCROLL_RIGHT) && scroll_next_possible) {
|
|
||||||
tree_next('n', orientation);
|
|
||||||
}
|
|
||||||
|
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
100
src/commands.c
100
src/commands.c
|
@ -1213,24 +1213,78 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) {
|
||||||
ysuccess(true);
|
ysuccess(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CMD_FOCUS_WARN_CHILDREN \
|
||||||
|
do { \
|
||||||
|
int count = 0; \
|
||||||
|
owindow *current; \
|
||||||
|
TAILQ_FOREACH(current, &owindows, owindows) { \
|
||||||
|
count++; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
if (count > 1) { \
|
||||||
|
LOG("WARNING: Your criteria for the focus command matches %d containers, " \
|
||||||
|
"while only exactly one container can be focused at a time.\n", \
|
||||||
|
count); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Implementation of 'focus left|right|up|down'.
|
* Implementation of 'focus left|right|up|down|next|prev'.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void cmd_focus_direction(I3_CMD, const char *direction) {
|
void cmd_focus_direction(I3_CMD, const char *direction_str) {
|
||||||
switch (parse_direction(direction)) {
|
HANDLE_EMPTY_MATCH;
|
||||||
case D_LEFT:
|
CMD_FOCUS_WARN_CHILDREN;
|
||||||
tree_next('p', HORIZ);
|
|
||||||
break;
|
direction_t direction;
|
||||||
case D_RIGHT:
|
position_t position;
|
||||||
tree_next('n', HORIZ);
|
bool auto_direction = true;
|
||||||
break;
|
if (strcmp(direction_str, "prev") == 0) {
|
||||||
case D_UP:
|
position = BEFORE;
|
||||||
tree_next('p', VERT);
|
} else if (strcmp(direction_str, "next") == 0) {
|
||||||
break;
|
position = AFTER;
|
||||||
case D_DOWN:
|
} else {
|
||||||
tree_next('n', VERT);
|
auto_direction = false;
|
||||||
break;
|
direction = parse_direction(direction_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
owindow *current;
|
||||||
|
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||||
|
Con *ws = con_get_workspace(current->con);
|
||||||
|
if (!ws || con_is_internal(ws)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (auto_direction) {
|
||||||
|
orientation_t o = con_orientation(current->con->parent);
|
||||||
|
direction = direction_from_orientation_position(o, position);
|
||||||
|
}
|
||||||
|
tree_next(current->con, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_output->needs_tree_render = true;
|
||||||
|
// XXX: default reply for now, make this a better reply
|
||||||
|
ysuccess(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implementation of 'focus next|prev sibling'
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void cmd_focus_sibling(I3_CMD, const char *direction_str) {
|
||||||
|
HANDLE_EMPTY_MATCH;
|
||||||
|
CMD_FOCUS_WARN_CHILDREN;
|
||||||
|
|
||||||
|
const position_t direction = (STARTS_WITH(direction_str, "prev")) ? BEFORE : AFTER;
|
||||||
|
owindow *current;
|
||||||
|
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||||
|
Con *ws = con_get_workspace(current->con);
|
||||||
|
if (!ws || con_is_internal(ws)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Con *next = get_tree_next_sibling(current->con, direction);
|
||||||
|
if (next) {
|
||||||
|
con_activate(next);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_output->needs_tree_render = true;
|
cmd_output->needs_tree_render = true;
|
||||||
|
@ -1315,12 +1369,15 @@ void cmd_focus(I3_CMD) {
|
||||||
ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
|
ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
|
||||||
|
|
||||||
yerror("You have to specify which window/container should be focused");
|
yerror("You have to specify which window/container should be focused");
|
||||||
|
return;
|
||||||
|
} else if (TAILQ_EMPTY(&owindows)) {
|
||||||
|
yerror("No window matches given criteria");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CMD_FOCUS_WARN_CHILDREN;
|
||||||
|
|
||||||
Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
|
Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
|
||||||
int count = 0;
|
|
||||||
owindow *current;
|
owindow *current;
|
||||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||||
Con *ws = con_get_workspace(current->con);
|
Con *ws = con_get_workspace(current->con);
|
||||||
|
@ -1332,7 +1389,6 @@ void cmd_focus(I3_CMD) {
|
||||||
/* In case this is a scratchpad window, call scratchpad_show(). */
|
/* In case this is a scratchpad window, call scratchpad_show(). */
|
||||||
if (ws == __i3_scratch) {
|
if (ws == __i3_scratch) {
|
||||||
scratchpad_show(current->con);
|
scratchpad_show(current->con);
|
||||||
count++;
|
|
||||||
/* While for the normal focus case we can change focus multiple
|
/* While for the normal focus case we can change focus multiple
|
||||||
* times and only a single window ends up focused, we could show
|
* times and only a single window ends up focused, we could show
|
||||||
* multiple scratchpad windows. So, rather break here. */
|
* multiple scratchpad windows. So, rather break here. */
|
||||||
|
@ -1341,16 +1397,10 @@ void cmd_focus(I3_CMD) {
|
||||||
|
|
||||||
LOG("focusing %p / %s\n", current->con, current->con->name);
|
LOG("focusing %p / %s\n", current->con, current->con->name);
|
||||||
con_activate_unblock(current->con);
|
con_activate_unblock(current->con);
|
||||||
count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count > 1)
|
|
||||||
LOG("WARNING: Your criteria for the focus command matches %d containers, "
|
|
||||||
"while only exactly one container can be focused at a time.\n",
|
|
||||||
count);
|
|
||||||
|
|
||||||
cmd_output->needs_tree_render = true;
|
cmd_output->needs_tree_render = true;
|
||||||
ysuccess(count > 0);
|
ysuccess(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -243,11 +243,10 @@ static void move_to_output_directed(Con *con, direction_t direction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Moves the given container in the given direction (D_LEFT, D_RIGHT,
|
* Moves the given container in the given direction
|
||||||
* D_UP, D_DOWN).
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void tree_move(Con *con, int direction) {
|
void tree_move(Con *con, direction_t direction) {
|
||||||
position_t position;
|
position_t position;
|
||||||
Con *target;
|
Con *target;
|
||||||
|
|
||||||
|
|
261
src/tree.c
261
src/tree.c
|
@ -462,170 +462,175 @@ void tree_render(void) {
|
||||||
DLOG("-- END RENDERING --\n");
|
DLOG("-- END RENDERING --\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Con *get_tree_next_workspace(Con *con, direction_t direction) {
|
||||||
|
if (con_get_fullscreen_con(con, CF_GLOBAL)) {
|
||||||
|
DLOG("Cannot change workspace while in global fullscreen mode.\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Output *current_output = get_output_containing(con->rect.x, con->rect.y);
|
||||||
|
if (!current_output) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
DLOG("Current output is %s\n", output_primary_name(current_output));
|
||||||
|
|
||||||
|
Output *next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
|
||||||
|
if (!next_output) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
DLOG("Next output is %s\n", output_primary_name(next_output));
|
||||||
|
|
||||||
|
/* Find visible workspace on next output */
|
||||||
|
Con *workspace = NULL;
|
||||||
|
GREP_FIRST(workspace, output_get_content(next_output->con), workspace_is_visible(child));
|
||||||
|
return workspace;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Recursive function to walk the tree until a con can be found to focus.
|
* Returns the next / previous container to focus in the given direction. Does
|
||||||
|
* not modify focus and ensures focus restrictions for fullscreen containers
|
||||||
|
* are respected.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) {
|
static Con *get_tree_next(Con *con, direction_t direction) {
|
||||||
/* When dealing with fullscreen containers, it's necessary to go up to the
|
const bool previous = position_from_direction(direction) == BEFORE;
|
||||||
* workspace level, because 'focus $dir' will start at the con's real
|
const orientation_t orientation = orientation_from_direction(direction);
|
||||||
* position in the tree, and it may not be possible to get to the edge
|
|
||||||
* normally due to fullscreen focusing restrictions. */
|
Con *first_wrap = NULL;
|
||||||
if (con->fullscreen_mode == CF_OUTPUT && con->type != CT_WORKSPACE)
|
|
||||||
con = con_get_workspace(con);
|
|
||||||
|
|
||||||
/* Stop recursing at workspaces after attempting to switch to next
|
|
||||||
* workspace if possible. */
|
|
||||||
if (con->type == CT_WORKSPACE) {
|
if (con->type == CT_WORKSPACE) {
|
||||||
if (con_get_fullscreen_con(con, CF_GLOBAL)) {
|
/* Special case for FOCUS_WRAPPING_WORKSPACE: allow the focus to leave
|
||||||
DLOG("Cannot change workspace while in global fullscreen mode.\n");
|
* the workspace only when a workspace is selected. */
|
||||||
return false;
|
goto handle_workspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (con->type != CT_WORKSPACE) {
|
||||||
|
if (con->fullscreen_mode == CF_OUTPUT) {
|
||||||
|
/* We've reached a fullscreen container. Directional focus should
|
||||||
|
* now operate on the workspace level. */
|
||||||
|
con = con_get_workspace(con);
|
||||||
|
break;
|
||||||
|
} else if (con->fullscreen_mode == CF_GLOBAL) {
|
||||||
|
/* Focus changes should happen only inside the children of a global
|
||||||
|
* fullscreen container. */
|
||||||
|
return first_wrap;
|
||||||
}
|
}
|
||||||
Output *current_output = get_output_containing(con->rect.x, con->rect.y);
|
|
||||||
Output *next_output;
|
|
||||||
|
|
||||||
if (!current_output)
|
Con *const parent = con->parent;
|
||||||
return false;
|
if (con->type == CT_FLOATING_CON) {
|
||||||
DLOG("Current output is %s\n", output_primary_name(current_output));
|
if (orientation != HORIZ) {
|
||||||
|
/* up/down does not change floating containers */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* Try to find next output */
|
/* left/right focuses the previous/next floating container */
|
||||||
direction_t direction;
|
Con *next = previous ? TAILQ_PREV(con, floating_head, floating_windows)
|
||||||
if (way == 'n' && orientation == HORIZ)
|
: TAILQ_NEXT(con, floating_windows);
|
||||||
direction = D_RIGHT;
|
/* If there is no next/previous container, wrap */
|
||||||
else if (way == 'p' && orientation == HORIZ)
|
if (!next) {
|
||||||
direction = D_LEFT;
|
next = previous ? TAILQ_LAST(&(parent->floating_head), floating_head)
|
||||||
else if (way == 'n' && orientation == VERT)
|
: TAILQ_FIRST(&(parent->floating_head));
|
||||||
direction = D_DOWN;
|
}
|
||||||
else if (way == 'p' && orientation == VERT)
|
/* Our parent does not list us in floating heads? */
|
||||||
direction = D_UP;
|
assert(next);
|
||||||
else
|
|
||||||
return false;
|
|
||||||
|
|
||||||
next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
|
return next;
|
||||||
if (!next_output)
|
}
|
||||||
return false;
|
|
||||||
DLOG("Next output is %s\n", output_primary_name(next_output));
|
|
||||||
|
|
||||||
/* Find visible workspace on next output */
|
if (con_num_children(parent) > 1 && con_orientation(parent) == orientation) {
|
||||||
Con *workspace = NULL;
|
Con *const next = previous ? TAILQ_PREV(con, nodes_head, nodes)
|
||||||
GREP_FIRST(workspace, output_get_content(next_output->con), workspace_is_visible(child));
|
: TAILQ_NEXT(con, nodes);
|
||||||
|
if (next && con_fullscreen_permits_focusing(next)) {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
Con *const wrap = previous ? TAILQ_LAST(&(parent->nodes_head), nodes_head)
|
||||||
|
: TAILQ_FIRST(&(parent->nodes_head));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FOCUS_WRAPPING_FORCE:
|
||||||
|
/* 'force' should always return to ensure focus doesn't
|
||||||
|
* leave the parent. */
|
||||||
|
if (next) {
|
||||||
|
return NULL; /* blocked by fullscreen */
|
||||||
|
}
|
||||||
|
return con_fullscreen_permits_focusing(wrap) ? wrap : NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
con = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Changes focus in the given direction
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void tree_next(Con *con, direction_t direction) {
|
||||||
|
Con *next = get_tree_next(con, direction);
|
||||||
|
if (!next) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (next->type == CT_WORKSPACE) {
|
||||||
/* Show next workspace and focus appropriate container if possible. */
|
/* Show next workspace and focus appropriate container if possible. */
|
||||||
if (!workspace)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* Use descend_focused first to give higher priority to floating or
|
/* Use descend_focused first to give higher priority to floating or
|
||||||
* tiling fullscreen containers. */
|
* tiling fullscreen containers. */
|
||||||
Con *focus = con_descend_focused(workspace);
|
Con *focus = con_descend_focused(next);
|
||||||
if (focus->fullscreen_mode == CF_NONE) {
|
if (focus->fullscreen_mode == CF_NONE) {
|
||||||
Con *focus_tiling = con_descend_tiling_focused(workspace);
|
Con *focus_tiling = con_descend_tiling_focused(next);
|
||||||
/* If descend_tiling returned a workspace then focus is either a
|
/* If descend_tiling returned a workspace then focus is either a
|
||||||
* floating container or the same workspace. */
|
* floating container or the same workspace. */
|
||||||
if (focus_tiling != workspace) {
|
if (focus_tiling != next) {
|
||||||
focus = focus_tiling;
|
focus = focus_tiling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
workspace_show(workspace);
|
workspace_show(next);
|
||||||
con_activate(focus);
|
con_activate(focus);
|
||||||
x_set_warp_to(&(focus->rect));
|
x_set_warp_to(&(focus->rect));
|
||||||
return true;
|
return;
|
||||||
}
|
} else if (next->type == CT_FLOATING_CON) {
|
||||||
|
/* Raise the floating window on top of other windows preserving relative
|
||||||
Con *parent = con->parent;
|
* stack order */
|
||||||
|
Con *parent = next->parent;
|
||||||
if (con->type == CT_FLOATING_CON) {
|
|
||||||
if (orientation != HORIZ)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* left/right focuses the previous/next floating container */
|
|
||||||
Con *next;
|
|
||||||
if (way == 'n')
|
|
||||||
next = TAILQ_NEXT(con, floating_windows);
|
|
||||||
else
|
|
||||||
next = TAILQ_PREV(con, floating_head, floating_windows);
|
|
||||||
|
|
||||||
/* If there is no next/previous container, wrap */
|
|
||||||
if (!next) {
|
|
||||||
if (way == 'n')
|
|
||||||
next = TAILQ_FIRST(&(parent->floating_head));
|
|
||||||
else
|
|
||||||
next = TAILQ_LAST(&(parent->floating_head), floating_head);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Still no next/previous container? bail out */
|
|
||||||
if (!next)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* Raise the floating window on top of other windows preserving
|
|
||||||
* relative stack order */
|
|
||||||
while (TAILQ_LAST(&(parent->floating_head), floating_head) != next) {
|
while (TAILQ_LAST(&(parent->floating_head), floating_head) != next) {
|
||||||
Con *last = TAILQ_LAST(&(parent->floating_head), floating_head);
|
Con *last = TAILQ_LAST(&(parent->floating_head), floating_head);
|
||||||
TAILQ_REMOVE(&(parent->floating_head), last, floating_windows);
|
TAILQ_REMOVE(&(parent->floating_head), last, floating_windows);
|
||||||
TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows);
|
TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows);
|
||||||
}
|
}
|
||||||
|
|
||||||
con_activate(con_descend_focused(next));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If the orientation does not match or there is no other con to focus, we
|
workspace_show(con_get_workspace(next));
|
||||||
* need to go higher in the hierarchy */
|
|
||||||
if (con_orientation(parent) != orientation ||
|
|
||||||
con_num_children(parent) == 1)
|
|
||||||
return _tree_next(parent, way, orientation, wrap);
|
|
||||||
|
|
||||||
Con *current = TAILQ_FIRST(&(parent->focus_head));
|
|
||||||
/* TODO: when can the following happen (except for floating windows, which
|
|
||||||
* are handled above)? */
|
|
||||||
if (TAILQ_EMPTY(&(parent->nodes_head))) {
|
|
||||||
DLOG("nothing to focus\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Con *next;
|
|
||||||
if (way == 'n')
|
|
||||||
next = TAILQ_NEXT(current, nodes);
|
|
||||||
else
|
|
||||||
next = TAILQ_PREV(current, nodes_head, nodes);
|
|
||||||
|
|
||||||
if (!next) {
|
|
||||||
if (config.focus_wrapping != FOCUS_WRAPPING_FORCE) {
|
|
||||||
/* 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
|
|
||||||
* not, we wrap */
|
|
||||||
if (_tree_next(parent, way, orientation, false))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (!wrap)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (way == 'n')
|
|
||||||
next = TAILQ_FIRST(&(parent->nodes_head));
|
|
||||||
else
|
|
||||||
next = TAILQ_LAST(&(parent->nodes_head), nodes_head);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Don't violate fullscreen focus restrictions. */
|
|
||||||
if (!con_fullscreen_permits_focusing(next))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* 3: focus choice comes in here. at the moment we will go down
|
|
||||||
* until we find a window */
|
|
||||||
/* TODO: check for window, atm we only go down as far as possible */
|
|
||||||
con_activate(con_descend_focused(next));
|
con_activate(con_descend_focused(next));
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Changes focus in the given way (next/previous) and given orientation
|
* Get the previous / next sibling
|
||||||
* (horizontal/vertical).
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void tree_next(char way, orientation_t orientation) {
|
Con *get_tree_next_sibling(Con *con, position_t direction) {
|
||||||
_tree_next(focused, way, orientation,
|
Con *to_focus = (direction == BEFORE ? TAILQ_PREV(con, nodes_head, nodes)
|
||||||
config.focus_wrapping != FOCUS_WRAPPING_OFF);
|
: TAILQ_NEXT(con, nodes));
|
||||||
|
if (to_focus && con_fullscreen_permits_focusing(to_focus)) {
|
||||||
|
return to_focus;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
20
src/util.c
20
src/util.c
|
@ -518,3 +518,23 @@ ssize_t slurp(const char *path, char **buf) {
|
||||||
orientation_t orientation_from_direction(direction_t direction) {
|
orientation_t orientation_from_direction(direction_t direction) {
|
||||||
return (direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT;
|
return (direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert a direction to its corresponding position.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
position_t position_from_direction(direction_t direction) {
|
||||||
|
return (direction == D_LEFT || direction == D_UP) ? BEFORE : AFTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert orientation and position to the corresponding direction.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
direction_t direction_from_orientation_position(orientation_t orientation, position_t position) {
|
||||||
|
if (orientation == HORIZ) {
|
||||||
|
return position == BEFORE ? D_LEFT : D_RIGHT;
|
||||||
|
} else {
|
||||||
|
return position == BEFORE ? D_UP : D_DOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -35,9 +35,13 @@ my $bottom = open_window;
|
||||||
# end sleeping for half a second to make sure i3 reacted
|
# end sleeping for half a second to make sure i3 reacted
|
||||||
#
|
#
|
||||||
sub focus_after {
|
sub focus_after {
|
||||||
my $msg = shift;
|
my ($msg, $win_id) = @_;
|
||||||
|
|
||||||
cmd $msg;
|
if (defined($win_id)) {
|
||||||
|
cmd "[id=$win_id] $msg";
|
||||||
|
} else {
|
||||||
|
cmd $msg;
|
||||||
|
}
|
||||||
return $x->input_focus;
|
return $x->input_focus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +54,14 @@ is($focus, $mid->id, "Middle window focused");
|
||||||
$focus = focus_after('focus up');
|
$focus = focus_after('focus up');
|
||||||
is($focus, $top->id, "Top window focused");
|
is($focus, $top->id, "Top window focused");
|
||||||
|
|
||||||
|
# Same using command criteria
|
||||||
|
$focus = focus_after('focus up', $bottom->id);
|
||||||
|
is($focus, $mid->id, "Middle window focused");
|
||||||
|
|
||||||
|
cmd 'focus down';
|
||||||
|
$focus = focus_after('focus up', $mid->id);
|
||||||
|
is($focus, $top->id, "Top window focused");
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
# Test focus wrapping
|
# Test focus wrapping
|
||||||
#####################################################################
|
#####################################################################
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!perl
|
||||||
|
# vim:ts=4:sw=4:expandtab
|
||||||
|
#
|
||||||
|
# Please read the following documents before working on tests:
|
||||||
|
# • https://build.i3wm.org/docs/testsuite.html
|
||||||
|
# (or docs/testsuite)
|
||||||
|
#
|
||||||
|
# • https://build.i3wm.org/docs/lib-i3test.html
|
||||||
|
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||||
|
#
|
||||||
|
# • https://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)
|
||||||
|
#
|
||||||
|
# Test focus next|prev
|
||||||
|
# Ticket: #2587
|
||||||
|
use i3test;
|
||||||
|
|
||||||
|
cmp_tree(
|
||||||
|
msg => "cmd 'prev' selects leaf 1/2",
|
||||||
|
layout_before => 'S[a b] V[c d* T[e f g]]',
|
||||||
|
layout_after => 'S[a b] V[c* d T[e f g]]',
|
||||||
|
cb => sub {
|
||||||
|
cmd 'focus prev';
|
||||||
|
});
|
||||||
|
|
||||||
|
cmp_tree(
|
||||||
|
msg => "cmd 'prev' selects leaf 2/2",
|
||||||
|
layout_before => 'S[a b] V[c* d T[e f g]]',
|
||||||
|
layout_after => 'S[a b*] V[c d T[e f g]]',
|
||||||
|
cb => sub {
|
||||||
|
# c* -> V -> b*
|
||||||
|
cmd 'focus parent, focus prev';
|
||||||
|
});
|
||||||
|
|
||||||
|
cmp_tree(
|
||||||
|
msg => "cmd 'prev sibling' selects leaf again",
|
||||||
|
layout_before => 'S[a b] V[c d* T[e f g]]',
|
||||||
|
layout_after => 'S[a b] V[c* d T[e f g]]',
|
||||||
|
cb => sub {
|
||||||
|
cmd 'focus prev sibling';
|
||||||
|
});
|
||||||
|
|
||||||
|
cmp_tree(
|
||||||
|
msg => "cmd 'next' selects leaf",
|
||||||
|
# Notice that g is the last to open before focus moves to d*
|
||||||
|
layout_before => 'S[a b] V[c d* T[e f g]]',
|
||||||
|
layout_after => 'S[a b] V[c d T[e f g*]]',
|
||||||
|
cb => sub {
|
||||||
|
cmd 'focus next';
|
||||||
|
});
|
||||||
|
|
||||||
|
cmp_tree(
|
||||||
|
msg => "cmd 'next sibling' selects parent 1/2",
|
||||||
|
layout_before => 'S[a b] V[c d* T[e f g]]',
|
||||||
|
layout_after => 'S[a b] V[c d T*[e f g]]',
|
||||||
|
cb => sub {
|
||||||
|
cmd 'focus next sibling';
|
||||||
|
});
|
||||||
|
|
||||||
|
cmp_tree(
|
||||||
|
msg => "cmd 'next sibling' selects parent 2/2",
|
||||||
|
layout_before => 'S[a b*] V[c d T[e f g]]',
|
||||||
|
layout_after => 'S[a b] V*[c d T[e f g]]',
|
||||||
|
cb => sub {
|
||||||
|
# b* -> S* -> V*
|
||||||
|
cmd 'focus parent, focus next sibling';
|
||||||
|
});
|
||||||
|
|
||||||
|
done_testing;
|
|
@ -0,0 +1,345 @@
|
||||||
|
#!perl
|
||||||
|
# vim:ts=4:sw=4:expandtab
|
||||||
|
#
|
||||||
|
# Please read the following documents before working on tests:
|
||||||
|
# • https://build.i3wm.org/docs/testsuite.html
|
||||||
|
# (or docs/testsuite)
|
||||||
|
#
|
||||||
|
# • https://build.i3wm.org/docs/lib-i3test.html
|
||||||
|
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||||
|
#
|
||||||
|
# • https://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 focus_wrapping yes|no|force|workspace with cmp_tree
|
||||||
|
# Tickets: #2180 #2352
|
||||||
|
use i3test i3_autostart => 0;
|
||||||
|
|
||||||
|
my $pid = 0;
|
||||||
|
|
||||||
|
sub focus_wrapping {
|
||||||
|
my ($setting) = @_;
|
||||||
|
|
||||||
|
print "--------------------------------------------------------------------------------\n";
|
||||||
|
print " focus_wrapping $setting\n";
|
||||||
|
print "--------------------------------------------------------------------------------\n";
|
||||||
|
exit_gracefully($pid) if $pid > 0;
|
||||||
|
|
||||||
|
my $config = <<"EOT";
|
||||||
|
# i3 config file (v4)
|
||||||
|
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||||
|
|
||||||
|
fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
|
||||||
|
workspace left-top output fake-0
|
||||||
|
workspace right-top output fake-1
|
||||||
|
workspace right-bottom output fake-2
|
||||||
|
workspace left-bottom output fake-3
|
||||||
|
|
||||||
|
focus_wrapping $setting
|
||||||
|
EOT
|
||||||
|
$pid = launch_with_config($config);
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
focus_wrapping('yes');
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
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 leaves 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-bottom', '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 leaves 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, 'right-top', 'Correct workspace focused');
|
||||||
|
});
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
focus_wrapping('no');
|
||||||
|
# See issue #2352
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
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 leaves 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-bottom', 'Correct workspace focused');
|
||||||
|
});
|
||||||
|
cmp_tree(
|
||||||
|
msg => 'Focus does not wrap 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 does not wrap 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 leaves 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, 'right-top', 'Correct workspace focused');
|
||||||
|
});
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
focus_wrapping('force');
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
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 (focus direction different than parent\'s orientation)',
|
||||||
|
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 wraps',
|
||||||
|
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( # '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]]',
|
||||||
|
ws => 'left-top',
|
||||||
|
cb => sub {
|
||||||
|
cmd 'focus parent, focus right';
|
||||||
|
});
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
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;
|
Loading…
Reference in New Issue