gri3-wm/src/tree.c

738 lines
25 KiB
C
Raw Normal View History

/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* tree.c: Everything that primarily modifies the layout tree data structure.
*
*/
#include "all.h"
struct Con *croot;
struct Con *focused;
struct all_cons_head all_cons = TAILQ_HEAD_INITIALIZER(all_cons);
/*
* Create the pseudo-output __i3. Output-independent workspaces such as
* __i3_scratch will live there.
*
*/
static Con *_create___i3(void) {
Con *__i3 = con_new(croot, NULL);
FREE(__i3->name);
__i3->name = sstrdup("__i3");
__i3->type = CT_OUTPUT;
__i3->layout = L_OUTPUT;
con_fix_percent(croot);
x_set_name(__i3, "[i3 con] pseudo-output __i3");
/* For retaining the correct position/size of a scratchpad window, the
* dimensions of the real outputs should be multiples of the __i3
* pseudo-output. Ensuring that is the job of scratchpad_fix_resolution()
* which gets called after this function and after detecting all the
* outputs (or whenever an output changes). */
__i3->rect.width = 1280;
__i3->rect.height = 1024;
/* Add a content container. */
DLOG("adding main content container\n");
Con *content = con_new(NULL, NULL);
content->type = CT_CON;
FREE(content->name);
content->name = sstrdup("content");
Introduce splith/splitv layouts, remove orientation With this commit, the "default" layout is replaced by the splith and splitv layouts. splith is equivalent to default with orientation horizontal and splitv is equivalent to default with orientation vertical. The "split h" and "split v" commands continue to work as before, they split the current container and you will end up in a split container with layout splith (after "split h") or splitv (after "split v"). To change a splith container into a splitv container, use either "layout splitv" or "layout toggle split". The latter command is used in the default config as mod+l (previously "layout default"). In case you have "layout default" in your config file, it is recommended to just replace it by "layout toggle split", which will work as "layout default" did before when pressing it once, but toggle between horizontal/vertical when pressing it repeatedly. The rationale behind this commit is that it’s cleaner to have all parameters that influence how windows are rendered in the layout itself rather than having a special parameter in combination with only one layout. This enables us to change existing split containers in all cases without breaking existing features (see ticket #464). Also, users should feel more confident about whether they are actually splitting or just changing an existing split container now. As a nice side-effect, this commit brings back the "layout toggle" feature we once had in i3 version 3 (see the userguide). AFAIK, it is safe to use in-place restart to upgrade into versions after this commit (switching to an older version will break your layout, though). Fixes #464
2012-08-04 03:04:00 +02:00
content->layout = L_SPLITH;
x_set_name(content, "[i3 con] content __i3");
con_attach(content, __i3, false);
/* Attach the __i3_scratch workspace. */
Con *ws = con_new(NULL, NULL);
ws->type = CT_WORKSPACE;
ws->num = -1;
ws->name = sstrdup("__i3_scratch");
Introduce splith/splitv layouts, remove orientation With this commit, the "default" layout is replaced by the splith and splitv layouts. splith is equivalent to default with orientation horizontal and splitv is equivalent to default with orientation vertical. The "split h" and "split v" commands continue to work as before, they split the current container and you will end up in a split container with layout splith (after "split h") or splitv (after "split v"). To change a splith container into a splitv container, use either "layout splitv" or "layout toggle split". The latter command is used in the default config as mod+l (previously "layout default"). In case you have "layout default" in your config file, it is recommended to just replace it by "layout toggle split", which will work as "layout default" did before when pressing it once, but toggle between horizontal/vertical when pressing it repeatedly. The rationale behind this commit is that it’s cleaner to have all parameters that influence how windows are rendered in the layout itself rather than having a special parameter in combination with only one layout. This enables us to change existing split containers in all cases without breaking existing features (see ticket #464). Also, users should feel more confident about whether they are actually splitting or just changing an existing split container now. As a nice side-effect, this commit brings back the "layout toggle" feature we once had in i3 version 3 (see the userguide). AFAIK, it is safe to use in-place restart to upgrade into versions after this commit (switching to an older version will break your layout, though). Fixes #464
2012-08-04 03:04:00 +02:00
ws->layout = L_SPLITH;
con_attach(ws, content, false);
x_set_name(ws, "[i3 con] workspace __i3_scratch");
ws->fullscreen_mode = CF_OUTPUT;
return __i3;
}
/*
* Loads tree from 'path' (used for in-place restarts).
*
*/
2011-06-10 18:27:20 +02:00
bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) {
bool result = false;
char *globbed = resolve_tilde(path);
char *buf = NULL;
if (!path_exists(globbed)) {
LOG("%s does not exist, not restoring tree\n", globbed);
goto out;
}
ssize_t len;
if ((len = slurp(globbed, &buf)) < 0) {
/* slurp already logged an error. */
goto out;
}
/* TODO: refactor the following */
croot = con_new(NULL, NULL);
croot->rect = (Rect){
2011-06-10 18:27:20 +02:00
geometry->x,
geometry->y,
geometry->width,
geometry->height};
focused = croot;
tree_append_json(focused, buf, len, NULL);
2010-11-27 01:27:38 +01:00
DLOG("appended tree, using new root\n");
croot = TAILQ_FIRST(&(croot->nodes_head));
if (!croot) {
/* tree_append_json failed. Continuing here would segfault. */
goto out;
}
DLOG("new root = %p\n", croot);
Con *out = TAILQ_FIRST(&(croot->nodes_head));
DLOG("out = %p\n", out);
Con *ws = TAILQ_FIRST(&(out->nodes_head));
DLOG("ws = %p\n", ws);
/* For in-place restarting into v4.2, we need to make sure the new
* pseudo-output __i3 is present. */
if (strcmp(out->name, "__i3") != 0) {
DLOG("Adding pseudo-output __i3 during inplace restart\n");
Con *__i3 = _create___i3();
/* Ensure that it is the first output, other places in the code make
* that assumption. */
TAILQ_REMOVE(&(croot->nodes_head), __i3, nodes);
TAILQ_INSERT_HEAD(&(croot->nodes_head), __i3, nodes);
}
restore_open_placeholder_windows(croot);
result = true;
out:
free(globbed);
free(buf);
return result;
}
/*
2011-01-05 00:16:10 +01:00
* Initializes the tree by creating the root node. The CT_OUTPUT Cons below the
* root node are created in randr.c for each Output.
*
*/
2011-06-10 18:27:20 +02:00
void tree_init(xcb_get_geometry_reply_t *geometry) {
croot = con_new(NULL, NULL);
FREE(croot->name);
croot->name = "root";
croot->type = CT_ROOT;
Introduce splith/splitv layouts, remove orientation With this commit, the "default" layout is replaced by the splith and splitv layouts. splith is equivalent to default with orientation horizontal and splitv is equivalent to default with orientation vertical. The "split h" and "split v" commands continue to work as before, they split the current container and you will end up in a split container with layout splith (after "split h") or splitv (after "split v"). To change a splith container into a splitv container, use either "layout splitv" or "layout toggle split". The latter command is used in the default config as mod+l (previously "layout default"). In case you have "layout default" in your config file, it is recommended to just replace it by "layout toggle split", which will work as "layout default" did before when pressing it once, but toggle between horizontal/vertical when pressing it repeatedly. The rationale behind this commit is that it’s cleaner to have all parameters that influence how windows are rendered in the layout itself rather than having a special parameter in combination with only one layout. This enables us to change existing split containers in all cases without breaking existing features (see ticket #464). Also, users should feel more confident about whether they are actually splitting or just changing an existing split container now. As a nice side-effect, this commit brings back the "layout toggle" feature we once had in i3 version 3 (see the userguide). AFAIK, it is safe to use in-place restart to upgrade into versions after this commit (switching to an older version will break your layout, though). Fixes #464
2012-08-04 03:04:00 +02:00
croot->layout = L_SPLITH;
croot->rect = (Rect){
2011-06-10 18:27:20 +02:00
geometry->x,
geometry->y,
geometry->width,
geometry->height};
_create___i3();
}
/*
* Opens an empty container in the current container
*
*/
Con *tree_open_con(Con *con, i3Window *window) {
if (con == NULL) {
/* every focusable Con has a parent (outputs have parent root) */
con = focused->parent;
/* If the parent is an output, we are on a workspace. In this case,
* the new container needs to be opened as a leaf of the workspace. */
if (con->parent->type == CT_OUTPUT && con->type != CT_DOCKAREA) {
con = focused;
}
/* If the currently focused container is a floating container, we
* attach the new container to the currently focused spot in its
* workspace. */
if (con->type == CT_FLOATING_CON) {
con = con_descend_tiling_focused(con->parent);
if (con->type != CT_WORKSPACE)
con = con->parent;
}
DLOG("con = %p\n", con);
}
assert(con != NULL);
/* 3. create the container and attach it to its parent */
Con *new = con_new(con, window);
Introduce splith/splitv layouts, remove orientation With this commit, the "default" layout is replaced by the splith and splitv layouts. splith is equivalent to default with orientation horizontal and splitv is equivalent to default with orientation vertical. The "split h" and "split v" commands continue to work as before, they split the current container and you will end up in a split container with layout splith (after "split h") or splitv (after "split v"). To change a splith container into a splitv container, use either "layout splitv" or "layout toggle split". The latter command is used in the default config as mod+l (previously "layout default"). In case you have "layout default" in your config file, it is recommended to just replace it by "layout toggle split", which will work as "layout default" did before when pressing it once, but toggle between horizontal/vertical when pressing it repeatedly. The rationale behind this commit is that it’s cleaner to have all parameters that influence how windows are rendered in the layout itself rather than having a special parameter in combination with only one layout. This enables us to change existing split containers in all cases without breaking existing features (see ticket #464). Also, users should feel more confident about whether they are actually splitting or just changing an existing split container now. As a nice side-effect, this commit brings back the "layout toggle" feature we once had in i3 version 3 (see the userguide). AFAIK, it is safe to use in-place restart to upgrade into versions after this commit (switching to an older version will break your layout, though). Fixes #464
2012-08-04 03:04:00 +02:00
new->layout = L_SPLITH;
/* 4: re-calculate child->percent for each child */
con_fix_percent(con);
return new;
}
/*
* Closes the given container including all children.
* Returns true if the container was killed or false if just WM_DELETE was sent
* and the window is expected to kill itself.
*
* The dont_kill_parent flag is specified when the function calls itself
* recursively while deleting a containers children.
*
*/
Simplify tree_close_internal This commit makes multiple changes in tree_close_internal. I didn't split them because they are not completely independent. - Remove force_set_focus parameter This parameter was always set to `false` throughout the code base except for one case where it was set to `(con == focused)`, when killing a floating con's parent (the one with type CT_FLOATING_CON). But this case is not needed anymore since the special handling of CT_FLOATING_CONs in con_next_focused was removed in #2941. - Assume that con_next_focused does not returned a container of type CT_DOCKAREA. This is reasonable since con_next_focused uses the focus_head stack and has special handling of CT_DOCKAREA containers. - Remove is_mapped This variable was only used in the if block towards the end of tree_close_internal. Ignoring the, now removed, dockarea code and the use of force_set_focus this block performed only one useful action: focus the `next` container when `con == focused`. `con == focused` was a necessary and sufficient condition for the con_activate call: if `con != focused` we could reach the inner if blocks because of the other conditions but would never focus another container. If `con == focused` then all other conditions would be irrelevant. - Remove special handling of floating containers Since the `next` focused container is calculated through the parent for floating containers, I moved this code to con_next_focused. Also, because of the removal of force_set_focus, it appears that we can call con_on_remove_child for floating containers as well.
2018-07-30 00:56:51 +02:00
bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent) {
Con *parent = con->parent;
/* remove the urgency hint of the workspace (if set) */
if (con->urgent) {
con_set_urgency(con, false);
con_update_parents_urgency(con);
workspace_update_urgent_flag(con_get_workspace(con));
}
2010-06-02 17:04:26 +02:00
DLOG("closing %p, kill_window = %d\n", con, kill_window);
Con *child, *nextchild;
bool abort_kill = false;
/* We cannot use TAILQ_FOREACH because the children get deleted
* in their parents nodes_head */
for (child = TAILQ_FIRST(&(con->nodes_head)); child;) {
nextchild = TAILQ_NEXT(child, nodes);
DLOG("killing child=%p\n", child);
Simplify tree_close_internal This commit makes multiple changes in tree_close_internal. I didn't split them because they are not completely independent. - Remove force_set_focus parameter This parameter was always set to `false` throughout the code base except for one case where it was set to `(con == focused)`, when killing a floating con's parent (the one with type CT_FLOATING_CON). But this case is not needed anymore since the special handling of CT_FLOATING_CONs in con_next_focused was removed in #2941. - Assume that con_next_focused does not returned a container of type CT_DOCKAREA. This is reasonable since con_next_focused uses the focus_head stack and has special handling of CT_DOCKAREA containers. - Remove is_mapped This variable was only used in the if block towards the end of tree_close_internal. Ignoring the, now removed, dockarea code and the use of force_set_focus this block performed only one useful action: focus the `next` container when `con == focused`. `con == focused` was a necessary and sufficient condition for the con_activate call: if `con != focused` we could reach the inner if blocks because of the other conditions but would never focus another container. If `con == focused` then all other conditions would be irrelevant. - Remove special handling of floating containers Since the `next` focused container is calculated through the parent for floating containers, I moved this code to con_next_focused. Also, because of the removal of force_set_focus, it appears that we can call con_on_remove_child for floating containers as well.
2018-07-30 00:56:51 +02:00
if (!tree_close_internal(child, kill_window, true)) {
abort_kill = true;
Simplify tree_close_internal This commit makes multiple changes in tree_close_internal. I didn't split them because they are not completely independent. - Remove force_set_focus parameter This parameter was always set to `false` throughout the code base except for one case where it was set to `(con == focused)`, when killing a floating con's parent (the one with type CT_FLOATING_CON). But this case is not needed anymore since the special handling of CT_FLOATING_CONs in con_next_focused was removed in #2941. - Assume that con_next_focused does not returned a container of type CT_DOCKAREA. This is reasonable since con_next_focused uses the focus_head stack and has special handling of CT_DOCKAREA containers. - Remove is_mapped This variable was only used in the if block towards the end of tree_close_internal. Ignoring the, now removed, dockarea code and the use of force_set_focus this block performed only one useful action: focus the `next` container when `con == focused`. `con == focused` was a necessary and sufficient condition for the con_activate call: if `con != focused` we could reach the inner if blocks because of the other conditions but would never focus another container. If `con == focused` then all other conditions would be irrelevant. - Remove special handling of floating containers Since the `next` focused container is calculated through the parent for floating containers, I moved this code to con_next_focused. Also, because of the removal of force_set_focus, it appears that we can call con_on_remove_child for floating containers as well.
2018-07-30 00:56:51 +02:00
}
child = nextchild;
}
if (abort_kill) {
DLOG("One of the children could not be killed immediately (WM_DELETE sent), aborting.\n");
return false;
}
if (con->window != NULL) {
if (kill_window != DONT_KILL_WINDOW) {
x_window_kill(con->window->id, kill_window);
return false;
} else {
xcb_void_cookie_t cookie;
/* Ignore any further events by clearing the event mask,
* unmap the window,
* then reparent it to the root window. */
xcb_change_window_attributes(conn, con->window->id,
XCB_CW_EVENT_MASK, (uint32_t[]){XCB_NONE});
xcb_unmap_window(conn, con->window->id);
cookie = xcb_reparent_window(conn, con->window->id, root, con->rect.x, con->rect.y);
/* Ignore X11 errors for the ReparentWindow request.
* X11 Errors are returned when the window was already destroyed */
add_ignore_event(cookie.sequence, 0);
/* We are no longer handling this window, thus set WM_STATE to
* WM_STATE_WITHDRAWN (see ICCCM 4.1.3.1) */
long data[] = {XCB_ICCCM_WM_STATE_WITHDRAWN, XCB_NONE};
cookie = xcb_change_property(conn, XCB_PROP_MODE_REPLACE,
con->window->id, A_WM_STATE, A_WM_STATE, 32, 2, data);
/* Remove the window from the save set. All windows in the save set
* will be mapped when i3 closes its connection (e.g. when
* restarting). This is not what we want, since some apps keep
* unmapped windows around and dont expect them to suddenly be
* mapped. See https://bugs.i3wm.org/1617 */
xcb_change_save_set(conn, XCB_SET_MODE_DELETE, con->window->id);
/* Stop receiving ShapeNotify events. */
if (shape_supported) {
xcb_shape_select_input(conn, con->window->id, false);
}
/* Ignore X11 errors for the ReparentWindow request.
* X11 Errors are returned when the window was already destroyed */
add_ignore_event(cookie.sequence, 0);
}
ipc_send_window_event("close", con);
window_free(con->window);
con->window = NULL;
}
Con *ws = con_get_workspace(con);
/* Figure out which container to focus next before detaching 'con'. */
Simplify tree_close_internal This commit makes multiple changes in tree_close_internal. I didn't split them because they are not completely independent. - Remove force_set_focus parameter This parameter was always set to `false` throughout the code base except for one case where it was set to `(con == focused)`, when killing a floating con's parent (the one with type CT_FLOATING_CON). But this case is not needed anymore since the special handling of CT_FLOATING_CONs in con_next_focused was removed in #2941. - Assume that con_next_focused does not returned a container of type CT_DOCKAREA. This is reasonable since con_next_focused uses the focus_head stack and has special handling of CT_DOCKAREA containers. - Remove is_mapped This variable was only used in the if block towards the end of tree_close_internal. Ignoring the, now removed, dockarea code and the use of force_set_focus this block performed only one useful action: focus the `next` container when `con == focused`. `con == focused` was a necessary and sufficient condition for the con_activate call: if `con != focused` we could reach the inner if blocks because of the other conditions but would never focus another container. If `con == focused` then all other conditions would be irrelevant. - Remove special handling of floating containers Since the `next` focused container is calculated through the parent for floating containers, I moved this code to con_next_focused. Also, because of the removal of force_set_focus, it appears that we can call con_on_remove_child for floating containers as well.
2018-07-30 00:56:51 +02:00
Con *next = (con == focused) ? con_next_focused(con) : NULL;
DLOG("next = %p, focused = %p\n", next, focused);
/* Detach the container so that it will not be rendered anymore. */
con_detach(con);
/* disable urgency timer, if needed */
if (con->urgency_timer != NULL) {
DLOG("Removing urgency timer of con %p\n", con);
workspace_update_urgent_flag(ws);
ev_timer_stop(main_loop, con->urgency_timer);
FREE(con->urgency_timer);
}
if (con->type != CT_FLOATING_CON) {
/* If the container is *not* floating, we might need to re-distribute
* percentage values for the resized containers. */
con_fix_percent(parent);
}
/* Render the tree so that the surrounding containers take up the space
* which 'con' does no longer occupy. If we dont render here, there will
* be a gap in our containers and that could trigger an EnterNotify for an
* underlying container, see ticket #660.
*
* Rendering has to be avoided when dont_kill_parent is set (when
* tree_close_internal calls itself recursively) because the tree is in a
* non-renderable state during that time. */
if (!dont_kill_parent)
tree_render();
/* kill the X11 part of this container */
x_con_kill(con);
if (ws == con) {
DLOG("Closing workspace container %s, updating EWMH atoms\n", ws->name);
ewmh_update_desktop_properties();
}
con_free(con);
Simplify tree_close_internal This commit makes multiple changes in tree_close_internal. I didn't split them because they are not completely independent. - Remove force_set_focus parameter This parameter was always set to `false` throughout the code base except for one case where it was set to `(con == focused)`, when killing a floating con's parent (the one with type CT_FLOATING_CON). But this case is not needed anymore since the special handling of CT_FLOATING_CONs in con_next_focused was removed in #2941. - Assume that con_next_focused does not returned a container of type CT_DOCKAREA. This is reasonable since con_next_focused uses the focus_head stack and has special handling of CT_DOCKAREA containers. - Remove is_mapped This variable was only used in the if block towards the end of tree_close_internal. Ignoring the, now removed, dockarea code and the use of force_set_focus this block performed only one useful action: focus the `next` container when `con == focused`. `con == focused` was a necessary and sufficient condition for the con_activate call: if `con != focused` we could reach the inner if blocks because of the other conditions but would never focus another container. If `con == focused` then all other conditions would be irrelevant. - Remove special handling of floating containers Since the `next` focused container is calculated through the parent for floating containers, I moved this code to con_next_focused. Also, because of the removal of force_set_focus, it appears that we can call con_on_remove_child for floating containers as well.
2018-07-30 00:56:51 +02:00
if (next) {
con_activate(next);
} else {
Simplify tree_close_internal This commit makes multiple changes in tree_close_internal. I didn't split them because they are not completely independent. - Remove force_set_focus parameter This parameter was always set to `false` throughout the code base except for one case where it was set to `(con == focused)`, when killing a floating con's parent (the one with type CT_FLOATING_CON). But this case is not needed anymore since the special handling of CT_FLOATING_CONs in con_next_focused was removed in #2941. - Assume that con_next_focused does not returned a container of type CT_DOCKAREA. This is reasonable since con_next_focused uses the focus_head stack and has special handling of CT_DOCKAREA containers. - Remove is_mapped This variable was only used in the if block towards the end of tree_close_internal. Ignoring the, now removed, dockarea code and the use of force_set_focus this block performed only one useful action: focus the `next` container when `con == focused`. `con == focused` was a necessary and sufficient condition for the con_activate call: if `con != focused` we could reach the inner if blocks because of the other conditions but would never focus another container. If `con == focused` then all other conditions would be irrelevant. - Remove special handling of floating containers Since the `next` focused container is calculated through the parent for floating containers, I moved this code to con_next_focused. Also, because of the removal of force_set_focus, it appears that we can call con_on_remove_child for floating containers as well.
2018-07-30 00:56:51 +02:00
DLOG("not changing focus, the container was not focused before\n");
}
/* check if the parent container is empty now and close it */
if (!dont_kill_parent)
CALL(parent, on_remove_child);
return true;
}
/*
* Splits (horizontally or vertically) the given container by creating a new
* container which contains the old one and the future ones.
*
*/
void tree_split(Con *con, orientation_t orientation) {
if (con_is_floating(con)) {
DLOG("Floating containers can't be split.\n");
return;
}
if (con->type == CT_WORKSPACE) {
if (con_num_children(con) < 2) {
if (con_num_children(con) == 0) {
DLOG("Changing workspace_layout to L_DEFAULT\n");
con->workspace_layout = L_DEFAULT;
}
DLOG("Changing orientation of workspace\n");
con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
return;
} else {
/* if there is more than one container on the workspace
* move them into a new container and handle this instead */
con = workspace_encapsulate(con);
}
}
Con *parent = con->parent;
2012-01-22 18:24:55 +01:00
/* Force re-rendering to make the indicator border visible. */
con_force_split_parents_redraw(con);
2012-01-22 18:24:55 +01:00
/* if we are in a container whose parent contains only one
* child (its split functionality is unused so far), we just change the
* orientation (more intuitive than splitting again) */
if (con_num_children(parent) == 1 &&
Introduce splith/splitv layouts, remove orientation With this commit, the "default" layout is replaced by the splith and splitv layouts. splith is equivalent to default with orientation horizontal and splitv is equivalent to default with orientation vertical. The "split h" and "split v" commands continue to work as before, they split the current container and you will end up in a split container with layout splith (after "split h") or splitv (after "split v"). To change a splith container into a splitv container, use either "layout splitv" or "layout toggle split". The latter command is used in the default config as mod+l (previously "layout default"). In case you have "layout default" in your config file, it is recommended to just replace it by "layout toggle split", which will work as "layout default" did before when pressing it once, but toggle between horizontal/vertical when pressing it repeatedly. The rationale behind this commit is that it’s cleaner to have all parameters that influence how windows are rendered in the layout itself rather than having a special parameter in combination with only one layout. This enables us to change existing split containers in all cases without breaking existing features (see ticket #464). Also, users should feel more confident about whether they are actually splitting or just changing an existing split container now. As a nice side-effect, this commit brings back the "layout toggle" feature we once had in i3 version 3 (see the userguide). AFAIK, it is safe to use in-place restart to upgrade into versions after this commit (switching to an older version will break your layout, though). Fixes #464
2012-08-04 03:04:00 +02:00
(parent->layout == L_SPLITH ||
parent->layout == L_SPLITV)) {
parent->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
DLOG("Just changing orientation of existing container\n");
return;
2010-06-02 23:32:05 +02:00
}
DLOG("Splitting in orientation %d\n", orientation);
/* 2: replace it with a new Con */
Con *new = con_new(NULL, NULL);
TAILQ_REPLACE(&(parent->nodes_head), con, new, nodes);
TAILQ_REPLACE(&(parent->focus_head), con, new, focused);
new->parent = parent;
Introduce splith/splitv layouts, remove orientation With this commit, the "default" layout is replaced by the splith and splitv layouts. splith is equivalent to default with orientation horizontal and splitv is equivalent to default with orientation vertical. The "split h" and "split v" commands continue to work as before, they split the current container and you will end up in a split container with layout splith (after "split h") or splitv (after "split v"). To change a splith container into a splitv container, use either "layout splitv" or "layout toggle split". The latter command is used in the default config as mod+l (previously "layout default"). In case you have "layout default" in your config file, it is recommended to just replace it by "layout toggle split", which will work as "layout default" did before when pressing it once, but toggle between horizontal/vertical when pressing it repeatedly. The rationale behind this commit is that it’s cleaner to have all parameters that influence how windows are rendered in the layout itself rather than having a special parameter in combination with only one layout. This enables us to change existing split containers in all cases without breaking existing features (see ticket #464). Also, users should feel more confident about whether they are actually splitting or just changing an existing split container now. As a nice side-effect, this commit brings back the "layout toggle" feature we once had in i3 version 3 (see the userguide). AFAIK, it is safe to use in-place restart to upgrade into versions after this commit (switching to an older version will break your layout, though). Fixes #464
2012-08-04 03:04:00 +02:00
new->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
/* 3: swap 'percent' (resize factor) */
new->percent = con->percent;
con->percent = 0.0;
/* 4: add it as a child to the new Con */
con_attach(con, new, false);
}
/*
* Moves focus one level up. Returns true if focus changed.
*
*/
bool level_up(void) {
/* Skip over floating containers and go directly to the grandparent
* (which should always be a workspace) */
if (focused->parent->type == CT_FLOATING_CON) {
con_activate(focused->parent->parent);
return true;
}
/* We can focus up to the workspace, but not any higher in the tree */
if ((focused->parent->type != CT_CON &&
focused->parent->type != CT_WORKSPACE) ||
focused->type == CT_WORKSPACE) {
ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n");
return false;
}
con_activate(focused->parent);
return true;
}
/*
* Moves focus one level down. Returns true if focus changed.
*
*/
bool level_down(void) {
/* Go down the focus stack of the current node */
Con *next = TAILQ_FIRST(&(focused->focus_head));
if (next == TAILQ_END(&(focused->focus_head))) {
DLOG("cannot go down\n");
return false;
} else if (next->type == CT_FLOATING_CON) {
/* Floating cons shouldn't be directly focused; try immediately
* going to the grandchild of the focused con. */
Con *child = TAILQ_FIRST(&(next->focus_head));
if (child == TAILQ_END(&(next->focus_head))) {
DLOG("cannot go down\n");
return false;
} else
next = TAILQ_FIRST(&(next->focus_head));
}
con_activate(next);
return true;
}
static void mark_unmapped(Con *con) {
Con *current;
con->mapped = false;
TAILQ_FOREACH (current, &(con->nodes_head), nodes) {
mark_unmapped(current);
}
if (con->type == CT_WORKSPACE) {
2016-04-04 09:33:59 +02:00
/* We need to call mark_unmapped on floating nodes as well since we can
* make containers floating. */
TAILQ_FOREACH (current, &(con->floating_head), floating_windows) {
mark_unmapped(current);
}
}
}
/*
* Renders the tree, that is rendering all outputs using render_con() and
* pushing the changes to X11 using x_push_changes().
*
*/
void tree_render(void) {
if (croot == NULL)
return;
DLOG("-- BEGIN RENDERING --\n");
/* Reset map state for all nodes in tree */
/* TODO: a nicer method to walk all nodes would be good, maybe? */
mark_unmapped(croot);
croot->mapped = true;
render_con(croot);
2011-06-10 18:27:20 +02:00
x_push_changes(croot);
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;
}
/*
* 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 Con *get_tree_next(Con *con, direction_t direction) {
const bool previous = position_from_direction(direction) == BEFORE;
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
* 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;
}
Con *const parent = con->parent;
if (con->type == CT_FLOATING_CON) {
if (orientation != HORIZ) {
/* up/down does not change floating containers */
return NULL;
}
/* left/right focuses the previous/next floating container */
Con *next = previous ? TAILQ_PREV(con, floating_head, floating_windows)
: TAILQ_NEXT(con, floating_windows);
/* If there is no next/previous container, wrap */
if (!next) {
next = previous ? TAILQ_LAST(&(parent->floating_head), floating_head)
: TAILQ_FIRST(&(parent->floating_head));
}
/* Our parent does not list us in floating heads? */
assert(next);
return next;
}
if (con_num_children(parent) > 1 && con_orientation(parent) == orientation) {
Con *const next = previous ? TAILQ_PREV(con, nodes_head, nodes)
: 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. */
/* Use descend_focused first to give higher priority to floating or
* tiling fullscreen containers. */
Con *focus = con_descend_focused(next);
if (focus->fullscreen_mode == CF_NONE) {
Con *focus_tiling = con_descend_tiling_focused(next);
/* If descend_tiling returned a workspace then focus is either a
* floating container or the same workspace. */
if (focus_tiling != next) {
focus = focus_tiling;
}
}
workspace_show(next);
con_activate(focus);
x_set_warp_to(&(focus->rect));
return;
} else if (next->type == CT_FLOATING_CON) {
/* Raise the floating window on top of other windows preserving relative
* stack order */
Con *parent = next->parent;
while (TAILQ_LAST(&(parent->floating_head), floating_head) != next) {
Con *last = TAILQ_LAST(&(parent->floating_head), floating_head);
TAILQ_REMOVE(&(parent->floating_head), last, floating_windows);
TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows);
}
}
workspace_show(con_get_workspace(next));
con_activate(con_descend_focused(next));
}
/*
* Get the previous / next sibling
*
*/
Con *get_tree_next_sibling(Con *con, position_t direction) {
Con *to_focus = (direction == BEFORE ? TAILQ_PREV(con, nodes_head, nodes)
: TAILQ_NEXT(con, nodes));
if (to_focus && con_fullscreen_permits_focusing(to_focus)) {
return to_focus;
}
return NULL;
}
/*
* tree_flatten() removes pairs of redundant split containers, e.g.:
* [workspace, horizontal]
* [v-split] [child3]
* [h-split]
* [child1] [child2]
* In this example, the v-split and h-split container are redundant.
* Such a situation can be created by moving containers in a direction which is
* not the orientation of their parent container. i3 needs to create a new
* split container then and if you move containers this way multiple times,
* redundant chains of split-containers can be the result.
*
*/
void tree_flatten(Con *con) {
Con *current, *child, *parent = con->parent;
DLOG("Checking if I can flatten con = %p / %s\n", con, con->name);
/* We only consider normal containers without windows */
Introduce splith/splitv layouts, remove orientation With this commit, the "default" layout is replaced by the splith and splitv layouts. splith is equivalent to default with orientation horizontal and splitv is equivalent to default with orientation vertical. The "split h" and "split v" commands continue to work as before, they split the current container and you will end up in a split container with layout splith (after "split h") or splitv (after "split v"). To change a splith container into a splitv container, use either "layout splitv" or "layout toggle split". The latter command is used in the default config as mod+l (previously "layout default"). In case you have "layout default" in your config file, it is recommended to just replace it by "layout toggle split", which will work as "layout default" did before when pressing it once, but toggle between horizontal/vertical when pressing it repeatedly. The rationale behind this commit is that it’s cleaner to have all parameters that influence how windows are rendered in the layout itself rather than having a special parameter in combination with only one layout. This enables us to change existing split containers in all cases without breaking existing features (see ticket #464). Also, users should feel more confident about whether they are actually splitting or just changing an existing split container now. As a nice side-effect, this commit brings back the "layout toggle" feature we once had in i3 version 3 (see the userguide). AFAIK, it is safe to use in-place restart to upgrade into versions after this commit (switching to an older version will break your layout, though). Fixes #464
2012-08-04 03:04:00 +02:00
if (con->type != CT_CON ||
parent->layout == L_OUTPUT || /* con == "content" */
con->window != NULL)
goto recurse;
/* Ensure it got only one child */
child = TAILQ_FIRST(&(con->nodes_head));
if (child == NULL || TAILQ_NEXT(child, nodes) != NULL)
goto recurse;
Introduce splith/splitv layouts, remove orientation With this commit, the "default" layout is replaced by the splith and splitv layouts. splith is equivalent to default with orientation horizontal and splitv is equivalent to default with orientation vertical. The "split h" and "split v" commands continue to work as before, they split the current container and you will end up in a split container with layout splith (after "split h") or splitv (after "split v"). To change a splith container into a splitv container, use either "layout splitv" or "layout toggle split". The latter command is used in the default config as mod+l (previously "layout default"). In case you have "layout default" in your config file, it is recommended to just replace it by "layout toggle split", which will work as "layout default" did before when pressing it once, but toggle between horizontal/vertical when pressing it repeatedly. The rationale behind this commit is that it’s cleaner to have all parameters that influence how windows are rendered in the layout itself rather than having a special parameter in combination with only one layout. This enables us to change existing split containers in all cases without breaking existing features (see ticket #464). Also, users should feel more confident about whether they are actually splitting or just changing an existing split container now. As a nice side-effect, this commit brings back the "layout toggle" feature we once had in i3 version 3 (see the userguide). AFAIK, it is safe to use in-place restart to upgrade into versions after this commit (switching to an older version will break your layout, though). Fixes #464
2012-08-04 03:04:00 +02:00
DLOG("child = %p, con = %p, parent = %p\n", child, con, parent);
/* The child must have a different orientation than the con but the same as
* the cons parent to be redundant */
if (!con_is_split(con) ||
!con_is_split(child) ||
(con->layout != L_SPLITH && con->layout != L_SPLITV) ||
(child->layout != L_SPLITH && child->layout != L_SPLITV) ||
Introduce splith/splitv layouts, remove orientation With this commit, the "default" layout is replaced by the splith and splitv layouts. splith is equivalent to default with orientation horizontal and splitv is equivalent to default with orientation vertical. The "split h" and "split v" commands continue to work as before, they split the current container and you will end up in a split container with layout splith (after "split h") or splitv (after "split v"). To change a splith container into a splitv container, use either "layout splitv" or "layout toggle split". The latter command is used in the default config as mod+l (previously "layout default"). In case you have "layout default" in your config file, it is recommended to just replace it by "layout toggle split", which will work as "layout default" did before when pressing it once, but toggle between horizontal/vertical when pressing it repeatedly. The rationale behind this commit is that it’s cleaner to have all parameters that influence how windows are rendered in the layout itself rather than having a special parameter in combination with only one layout. This enables us to change existing split containers in all cases without breaking existing features (see ticket #464). Also, users should feel more confident about whether they are actually splitting or just changing an existing split container now. As a nice side-effect, this commit brings back the "layout toggle" feature we once had in i3 version 3 (see the userguide). AFAIK, it is safe to use in-place restart to upgrade into versions after this commit (switching to an older version will break your layout, though). Fixes #464
2012-08-04 03:04:00 +02:00
con_orientation(con) == con_orientation(child) ||
con_orientation(child) != con_orientation(parent))
goto recurse;
DLOG("Alright, I have to flatten this situation now. Stay calm.\n");
/* 1: save focus */
Con *focus_next = TAILQ_FIRST(&(child->focus_head));
DLOG("detaching...\n");
/* 2: re-attach the children to the parent before con */
while (!TAILQ_EMPTY(&(child->nodes_head))) {
current = TAILQ_FIRST(&(child->nodes_head));
DLOG("detaching current=%p / %s\n", current, current->name);
con_detach(current);
DLOG("re-attaching\n");
/* We dont use con_attach() here because for a CT_CON, the special
* case handling of con_attach() does not trigger. So all it would do
* is calling TAILQ_INSERT_AFTER, but with the wrong container. So we
* directly use the TAILQ macros. */
current->parent = parent;
TAILQ_INSERT_BEFORE(con, current, nodes);
DLOG("attaching to focus list\n");
TAILQ_INSERT_TAIL(&(parent->focus_head), current, focused);
current->percent = con->percent;
}
DLOG("re-attached all\n");
/* 3: restore focus, if con was focused */
if (focus_next != NULL &&
TAILQ_FIRST(&(parent->focus_head)) == con) {
DLOG("restoring focus to focus_next=%p\n", focus_next);
TAILQ_REMOVE(&(parent->focus_head), focus_next, focused);
TAILQ_INSERT_HEAD(&(parent->focus_head), focus_next, focused);
DLOG("restored focus.\n");
}
/* 4: close the redundant cons */
DLOG("closing redundant cons\n");
Simplify tree_close_internal This commit makes multiple changes in tree_close_internal. I didn't split them because they are not completely independent. - Remove force_set_focus parameter This parameter was always set to `false` throughout the code base except for one case where it was set to `(con == focused)`, when killing a floating con's parent (the one with type CT_FLOATING_CON). But this case is not needed anymore since the special handling of CT_FLOATING_CONs in con_next_focused was removed in #2941. - Assume that con_next_focused does not returned a container of type CT_DOCKAREA. This is reasonable since con_next_focused uses the focus_head stack and has special handling of CT_DOCKAREA containers. - Remove is_mapped This variable was only used in the if block towards the end of tree_close_internal. Ignoring the, now removed, dockarea code and the use of force_set_focus this block performed only one useful action: focus the `next` container when `con == focused`. `con == focused` was a necessary and sufficient condition for the con_activate call: if `con != focused` we could reach the inner if blocks because of the other conditions but would never focus another container. If `con == focused` then all other conditions would be irrelevant. - Remove special handling of floating containers Since the `next` focused container is calculated through the parent for floating containers, I moved this code to con_next_focused. Also, because of the removal of force_set_focus, it appears that we can call con_on_remove_child for floating containers as well.
2018-07-30 00:56:51 +02:00
tree_close_internal(con, DONT_KILL_WINDOW, true);
/* Well, we got to abort the recursion here because we destroyed the
* container. However, if tree_flatten() is called sufficiently often,
* there cant be the situation of having two pairs of redundant containers
* at once. Therefore, we can safely abort the recursion on this level
* after flattening. */
return;
recurse:
/* We cannot use normal foreach here because tree_flatten might close the
* current container. */
current = TAILQ_FIRST(&(con->nodes_head));
while (current != NULL) {
Con *next = TAILQ_NEXT(current, nodes);
tree_flatten(current);
current = next;
}
current = TAILQ_FIRST(&(con->floating_head));
while (current != NULL) {
Con *next = TAILQ_NEXT(current, floating_windows);
tree_flatten(current);
current = next;
}
}