X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fcon.c;h=b520c110bb2530833a985c4f9aeba77b6b7df7f7;hb=369c9ed50f2b9fc2dc904db07907cd5f95f0a5ad;hp=cebe0a7e7a473a49054cc14c1db468d5b3920399;hpb=35b05ccd2f740d8e6e21ef8b87a2d837a87003e2;p=i3%2Fi3 diff --git a/src/con.c b/src/con.c index cebe0a7e..b520c110 100644 --- a/src/con.c +++ b/src/con.c @@ -1,5 +1,3 @@ -#undef I3__FILE__ -#define I3__FILE__ "con.c" /* * vim:ts=4:sw=4:expandtab * @@ -12,6 +10,7 @@ * */ #include "all.h" + #include "yajl_utils.h" static void con_on_remove_child(Con *con); @@ -23,9 +22,11 @@ static void con_on_remove_child(Con *con); void con_force_split_parents_redraw(Con *con) { Con *parent = con; - while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { - if (!con_is_leaf(parent)) + while (parent != NULL && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { + if (!con_is_leaf(parent)) { FREE(parent->deco_render_params); + } + parent = parent->parent; } } @@ -47,7 +48,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) { new->depth = window->depth; new->window->aspect_ratio = 0.0; } else { - new->depth = XCB_COPY_FROM_PARENT; + new->depth = root_depth; } DLOG("opening window\n"); @@ -68,7 +69,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) { */ Con *con_new(Con *parent, i3Window *window) { Con *new = con_new_skeleton(parent, window); - x_con_init(new, new->depth); + x_con_init(new); return new; } @@ -146,7 +147,7 @@ static void _con_attach(Con *con, Con *parent, Con *previous, bool ignore_focus) /* Insert the container after the tiling container, if found. * When adding to a CT_OUTPUT, just append one after another. */ - if (current && parent->type != CT_OUTPUT) { + if (current != NULL && parent->type != CT_OUTPUT) { DLOG("Inserting con = %p after con %p\n", con, current); TAILQ_INSERT_AFTER(nodes_head, current, con, nodes); } else @@ -220,6 +221,36 @@ void con_focus(Con *con) { } } +/* + * Closes the given container. + * + */ +void con_close(Con *con, kill_window_t kill_window) { + assert(con != NULL); + DLOG("Closing con = %p.\n", con); + + /* We never close output or root containers. */ + if (con->type == CT_OUTPUT || con->type == CT_ROOT) { + DLOG("con = %p is of type %d, not closing anything.\n", con, con->type); + return; + } + + if (con->type == CT_WORKSPACE) { + DLOG("con = %p is a workspace, closing all children instead.\n", con); + Con *child, *nextchild; + for (child = TAILQ_FIRST(&(con->focus_head)); child;) { + nextchild = TAILQ_NEXT(child, focused); + DLOG("killing child = %p.\n", child); + tree_close_internal(child, kill_window, false, false); + child = nextchild; + } + + return; + } + + tree_close_internal(con, kill_window, false, false); +} + /* * Returns true when this node is a leaf node (has no children) * @@ -381,7 +412,8 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation) { struct bfs_entry { Con *con; - TAILQ_ENTRY(bfs_entry) entries; + TAILQ_ENTRY(bfs_entry) + entries; }; /* @@ -393,7 +425,9 @@ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) { /* TODO: is breadth-first-search really appropriate? (check as soon as * fullscreen levels and fullscreen for containers is implemented) */ - TAILQ_HEAD(bfs_head, bfs_entry) bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head); + TAILQ_HEAD(bfs_head, bfs_entry) + bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head); + struct bfs_entry *entry = smalloc(sizeof(struct bfs_entry)); entry->con = con; TAILQ_INSERT_TAIL(&bfs_head, entry, entries); @@ -448,6 +482,20 @@ bool con_is_floating(Con *con) { return (con->floating >= FLOATING_AUTO_ON); } +/* + * Returns true if the container is a docked container. + * + */ +bool con_is_docked(Con *con) { + if (con->parent == NULL) + return false; + + if (con->parent->type == CT_DOCKAREA) + return true; + + return con_is_docked(con->parent); +} + /* * Checks if the given container is either floating or inside some floating * container. It returns the FLOATING_CON container. @@ -479,6 +527,23 @@ bool con_inside_focused(Con *con) { return con_inside_focused(con->parent); } +/* + * Checks if the container has the given parent as an actual parent. + * + */ +bool con_has_parent(Con *con, Con *parent) { + Con *current = con->parent; + if (current == NULL) { + return false; + } + + if (current == parent) { + return true; + } + + return con_has_parent(current, parent); +} + /* * Returns the container with the given client window ID or NULL if no such * container exists. @@ -492,6 +557,22 @@ Con *con_by_window_id(xcb_window_t window) { return NULL; } +/* + * Returns the container with the given container ID or NULL if no such + * container exists. + * + */ +Con *con_by_con_id(long target) { + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + if (con == (Con *)target) { + return con; + } + } + + return NULL; +} + /* * Returns the container with the given frame ID or NULL if no such container * exists. @@ -500,7 +581,7 @@ Con *con_by_window_id(xcb_window_t window) { Con *con_by_frame_id(xcb_window_t frame) { Con *con; TAILQ_FOREACH(con, &all_cons, all_cons) - if (con->frame == frame) + if (con->frame.id == frame) return con; return NULL; } @@ -564,7 +645,8 @@ void con_mark(Con *con, const char *mark, mark_mode_t mode) { DLOG("Removing all existing marks on con = %p.\n", con); mark_t *current; - TAILQ_FOREACH(current, &(con->marks_head), marks) { + while (!TAILQ_EMPTY(&(con->marks_head))) { + current = TAILQ_FIRST(&(con->marks_head)); con_unmark(con, current->name); } } @@ -572,6 +654,7 @@ void con_mark(Con *con, const char *mark, mark_mode_t mode) { mark_t *new = scalloc(1, sizeof(mark_t)); new->name = sstrdup(mark); TAILQ_INSERT_TAIL(&(con->marks_head), new, marks); + ipc_send_window_event("mark", con); con->mark_changed = true; } @@ -600,6 +683,8 @@ void con_unmark(Con *con, const char *name) { FREE(mark->name); TAILQ_REMOVE(&(current->marks_head), mark, marks); FREE(mark); + + ipc_send_window_event("mark", current); } current->mark_changed = true; @@ -623,6 +708,8 @@ void con_unmark(Con *con, const char *name) { FREE(mark->name); TAILQ_REMOVE(&(current->marks_head), mark, marks); FREE(mark); + + ipc_send_window_event("mark", current); break; } } @@ -682,6 +769,49 @@ int con_num_children(Con *con) { return children; } +/** + * Returns the number of visible non-floating children of this container. + * For example, if the container contains a hsplit which has two children, + * this will return 2 instead of 1. + */ +int con_num_visible_children(Con *con) { + if (con == NULL) + return 0; + + int children = 0; + Con *current = NULL; + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { + /* Visible leaf nodes are a child. */ + if (!con_is_hidden(current) && con_is_leaf(current)) + children++; + /* All other containers need to be recursed. */ + else + children += con_num_visible_children(current); + } + + return children; +} + +/* + * Count the number of windows (i.e., leaf containers). + * + */ +int con_num_windows(Con *con) { + if (con == NULL) + return 0; + + if (con_has_managed_window(con)) + return 1; + + int num = 0; + Con *current = NULL; + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { + num += con_num_windows(current); + } + + return num; +} + /* * Updates the percent attribute of the children of the given container. This * function needs to be called when a window is added or removed from a @@ -708,10 +838,11 @@ void con_fix_percent(Con *con) { if (children_with_percent != children) { TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (child->percent <= 0.0) { - if (children_with_percent == 0) + if (children_with_percent == 0) { total += (child->percent = 1.0); - else + } else { total += (child->percent = total / children_with_percent); + } } } } @@ -719,11 +850,13 @@ void con_fix_percent(Con *con) { // if we got a zero, just distribute the space equally, otherwise // distribute according to the proportions we got if (total == 0.0) { - TAILQ_FOREACH(child, &(con->nodes_head), nodes) - child->percent = 1.0 / children; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + child->percent = 1.0 / children; + } } else if (total != 1.0) { - TAILQ_FOREACH(child, &(con->nodes_head), nodes) - child->percent /= total; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + child->percent /= total; + } } } @@ -849,7 +982,7 @@ void con_disable_fullscreen(Con *con) { con_set_fullscreen_mode(con, CF_NONE); } -static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fix_coordinates, bool dont_warp, bool ignore_focus) { +static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fix_coordinates, bool dont_warp, bool ignore_focus, bool fix_percentage) { Con *orig_target = target; /* Prevent moving if this would violate the fullscreen focus restrictions. */ @@ -958,9 +1091,11 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi _con_attach(con, target, behind_focused ? NULL : orig_target, !behind_focused); /* 5: fix the percentages */ - con_fix_percent(parent); - con->percent = 0.0; - con_fix_percent(target); + if (fix_percentage) { + con_fix_percent(parent); + con->percent = 0.0; + con_fix_percent(target); + } /* 6: focus the con on the target workspace, but only within that * workspace, that is, don’t move focus away if the target workspace is @@ -986,8 +1121,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* Descend focus stack in case focus_next is a workspace which can * occur if we move to the same workspace. Also show current workspace * to ensure it is focused. */ - if (!ignore_focus) + if (!ignore_focus) { workspace_show(current_ws); + if (dont_warp) { + DLOG("x_set_warp_to(NULL) because dont_warp is set\n"); + x_set_warp_to(NULL); + } + } /* Set focus only if con was on current workspace before moving. * Otherwise we would give focus to some window on different workspace. */ @@ -1026,15 +1166,19 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi startup_sequence_delete(sequence); } - CALL(parent, on_remove_child); - /* 9. If the container was marked urgent, move the urgency hint. */ if (urgent) { workspace_update_urgent_flag(source_ws); con_set_urgency(con, true); } + /* Ensure the container will be redrawn. */ + FREE(con->deco_render_params); + + CALL(parent, on_remove_child); + ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); return true; } @@ -1070,12 +1214,12 @@ bool con_move_to_mark(Con *con, const char *mark) { target = TAILQ_FIRST(&(target->focus_head)); } - if (con == target || con == target->parent) { + if (con == target || con_has_parent(target, con)) { DLOG("cannot move the container to or inside itself, aborting.\n"); return false; } - return _con_move_to_con(con, target, false, true, false, false); + return _con_move_to_con(con, target, false, true, false, false, true); } /* @@ -1108,7 +1252,20 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool } Con *target = con_descend_focused(workspace); - _con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus); + _con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus, true); +} + +/* + * Moves the given container to the currently focused container on the + * visible workspace on the given output. + * + */ +void con_move_to_output(Con *con, Output *output) { + Con *ws = NULL; + GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); + assert(ws != NULL); + DLOG("Moving con %p to output %s\n", con, output->name); + con_move_to_workspace(con, ws, false, false, false); } /* @@ -1148,7 +1305,7 @@ orientation_t con_orientation(Con *con) { /* * Returns the container which will be focused next when the given container - * is not available anymore. Called in tree_close and con_move_to_workspace + * is not available anymore. Called in tree_close_internal and con_move_to_workspace * to properly restore focus. * */ @@ -1202,15 +1359,16 @@ Con *con_next_focused(Con *con) { } else { /* try to focus the next container on the same level as this one or fall * back to its parent */ - if (!(next = TAILQ_NEXT(con, focused))) + if (!(next = TAILQ_NEXT(con, focused))) { next = con->parent; + } } /* now go down the focus stack as far as * possible, excluding the current container */ - while (!TAILQ_EMPTY(&(next->focus_head)) && - TAILQ_FIRST(&(next->focus_head)) != con) + while (!TAILQ_EMPTY(&(next->focus_head)) && TAILQ_FIRST(&(next->focus_head)) != con) { next = TAILQ_FIRST(&(next->focus_head)); + } return next; } @@ -1365,6 +1523,12 @@ Con *con_descend_direction(Con *con, direction_t direction) { * */ Rect con_border_style_rect(Con *con) { + if (config.hide_edge_borders == HEBM_SMART && con_num_visible_children(con_get_workspace(con)) <= 1) { + if (!con_is_floating(con)) { + return (Rect){0, 0, 0, 0}; + } + } + adjacent_t borders_to_hide = ADJ_NONE; int border_width = con->current_border_width; DLOG("The border width for con is set to: %d\n", con->current_border_width); @@ -1524,12 +1688,14 @@ void con_set_layout(Con *con, layout_t layout) { * whole workspace into stacked/tabbed mode. To do this and still allow * intuitive operations (like level-up and then opening a new window), we * need to create a new split container. */ - if (con->type == CT_WORKSPACE && - (layout == L_STACKED || layout == L_TABBED)) { + if (con->type == CT_WORKSPACE) { if (con_num_children(con) == 0) { - DLOG("Setting workspace_layout to %d\n", layout); - con->workspace_layout = layout; - } else { + layout_t ws_layout = (layout == L_STACKED || layout == L_TABBED) ? layout : L_DEFAULT; + DLOG("Setting workspace_layout to %d\n", ws_layout); + con->workspace_layout = ws_layout; + DLOG("Setting layout to %d\n", layout); + con->layout = layout; + } else if (layout == L_STACKED || layout == L_TABBED) { DLOG("Creating new split container\n"); /* 1: create a new split container */ Con *new = con_new(NULL, NULL); @@ -1604,28 +1770,64 @@ void con_toggle_layout(Con *con, const char *toggle_mode) { parent = con->parent; DLOG("con_toggle_layout(%p, %s), parent = %p\n", con, toggle_mode, parent); - if (strcmp(toggle_mode, "split") == 0) { - /* Toggle between splits. When the current layout is not a split - * layout, we just switch back to last_split_layout. Otherwise, we - * change to the opposite split layout. */ - if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) - con_set_layout(con, parent->last_split_layout); - else { - if (parent->layout == L_SPLITH) - con_set_layout(con, L_SPLITV); - else - con_set_layout(con, L_SPLITH); + const char delim[] = " "; + + if (strcasecmp(toggle_mode, "split") == 0 || strstr(toggle_mode, delim)) { + /* L_DEFAULT is used as a placeholder value to distinguish if + * the first layout has already been saved. (it can never be L_DEFAULT) */ + layout_t new_layout = L_DEFAULT; + bool current_layout_found = false; + char *tm_dup = sstrdup(toggle_mode); + char *cur_tok = strtok(tm_dup, delim); + + for (layout_t layout; cur_tok != NULL; cur_tok = strtok(NULL, delim)) { + if (strcasecmp(cur_tok, "split") == 0) { + /* Toggle between splits. When the current layout is not a split + * layout, we just switch back to last_split_layout. Otherwise, we + * change to the opposite split layout. */ + if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) { + layout = parent->last_split_layout; + } else { + layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH; + } + } else { + bool success = layout_from_name(cur_tok, &layout); + if (!success || layout == L_DEFAULT) { + ELOG("The token '%s' was not recognized and has been skipped.\n", cur_tok); + continue; + } + } + + /* If none of the specified layouts match the current, + * fall back to the first layout in the list */ + if (new_layout == L_DEFAULT) { + new_layout = layout; + } + + /* We found the active layout in the last iteration, so + * now let's activate the current layout (next in list) */ + if (current_layout_found) { + new_layout = layout; + free(tm_dup); + break; + } + + if (parent->layout == layout) { + current_layout_found = true; + } } - } else { + + con_set_layout(con, new_layout); + } else if (strcasecmp(toggle_mode, "all") == 0 || strcasecmp(toggle_mode, "default") == 0) { if (parent->layout == L_STACKED) con_set_layout(con, L_TABBED); else if (parent->layout == L_TABBED) { - if (strcmp(toggle_mode, "all") == 0) + if (strcasecmp(toggle_mode, "all") == 0) con_set_layout(con, L_SPLITH); else con_set_layout(con, parent->last_split_layout); } else if (parent->layout == L_SPLITH || parent->layout == L_SPLITV) { - if (strcmp(toggle_mode, "all") == 0) { + if (strcasecmp(toggle_mode, "all") == 0) { /* When toggling through all modes, we toggle between * splith/splitv, whereas normally we just directly jump to * stacked. */ @@ -1664,7 +1866,7 @@ static void con_on_remove_child(Con *con) { if (TAILQ_EMPTY(&(con->focus_head)) && !workspace_is_visible(con)) { LOG("Closing old workspace (%p / %s), it is empty\n", con, con->name); yajl_gen gen = ipc_marshal_workspace_event("empty", con, NULL); - tree_close(con, DONT_KILL_WINDOW, false, false); + tree_close_internal(con, DONT_KILL_WINDOW, false, false); const unsigned char *payload; ylength length; @@ -1685,7 +1887,7 @@ static void con_on_remove_child(Con *con) { int children = con_num_children(con); if (children == 0) { DLOG("Container empty, closing\n"); - tree_close(con, DONT_KILL_WINDOW, false, false); + tree_close_internal(con, DONT_KILL_WINDOW, false, false); return; } } @@ -1840,6 +2042,13 @@ bool con_has_urgent_child(Con *con) { void con_update_parents_urgency(Con *con) { Con *parent = con->parent; + /* Urgency hints should not be set on any container higher up in the + * hierarchy than the workspace level. Unfortunately, since the content + * container has type == CT_CON, that’s not easy to verify in the loop + * below, so we need another condition to catch that case: */ + if (con->type == CT_WORKSPACE) + return; + bool new_urgency_value = con->urgent; while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { if (new_urgency_value) { @@ -1947,6 +2156,7 @@ char *con_get_tree_representation(Con *con) { (TAILQ_FIRST(&(con->nodes_head)) == child ? "" : " "), child_txt); free(buf); buf = tmp_buf; + free(child_txt); } /* 3) close the brackets */ @@ -1956,3 +2166,213 @@ char *con_get_tree_representation(Con *con) { return complete_buf; } + +/* + * Returns the container's title considering the current title format. + * + */ +i3String *con_parse_title_format(Con *con) { + assert(con->title_format != NULL); + + i3Window *win = con->window; + + /* We need to ensure that we only escape the window title if pango + * is used by the current font. */ + const bool pango_markup = font_is_pango(); + + char *title; + char *class; + char *instance; + if (win == NULL) { + title = pango_escape_markup(con_get_tree_representation(con)); + class = sstrdup("i3-frame"); + instance = sstrdup("i3-frame"); + } else { + title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name))); + class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class)); + instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance)); + } + + placeholder_t placeholders[] = { + {.name = "%title", .value = title}, + {.name = "%class", .value = class}, + {.name = "%instance", .value = instance}}; + const size_t num = sizeof(placeholders) / sizeof(placeholder_t); + + char *formatted_str = format_placeholders(con->title_format, &placeholders[0], num); + i3String *formatted = i3string_from_utf8(formatted_str); + i3string_set_markup(formatted, pango_markup); + FREE(formatted_str); + + for (size_t i = 0; i < num; i++) { + FREE(placeholders[i].value); + } + + return formatted; +} + +/* + * Swaps the two containers. + * + */ +bool con_swap(Con *first, Con *second) { + assert(first != NULL); + assert(second != NULL); + DLOG("Swapping containers %p / %p\n", first, second); + + if (first->type != CT_CON) { + ELOG("Only regular containers can be swapped, but found con = %p with type = %d.\n", first, first->type); + return false; + } + + if (second->type != CT_CON) { + ELOG("Only regular containers can be swapped, but found con = %p with type = %d.\n", second, second->type); + return false; + } + + if (con_is_floating(first) || con_is_floating(second)) { + ELOG("Floating windows cannot be swapped.\n"); + return false; + } + + if (first == second) { + DLOG("Swapping container %p with itself, nothing to do.\n", first); + return false; + } + + if (con_has_parent(first, second) || con_has_parent(second, first)) { + ELOG("Cannot swap containers %p and %p because they are in a parent-child relationship.\n", first, second); + return false; + } + + Con *old_focus = focused; + + Con *first_ws = con_get_workspace(first); + Con *second_ws = con_get_workspace(second); + Con *current_ws = con_get_workspace(old_focus); + const bool focused_within_first = (first == old_focus || con_has_parent(old_focus, first)); + const bool focused_within_second = (second == old_focus || con_has_parent(old_focus, second)); + + if (!con_fullscreen_permits_focusing(first_ws)) { + DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", first_ws->name); + return false; + } + + if (!con_fullscreen_permits_focusing(second_ws)) { + DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", second_ws->name); + return false; + } + + double first_percent = first->percent; + double second_percent = second->percent; + + /* De- and reattaching the containers will insert them at the tail of the + * focus_heads. We will need to fix this. But we need to make sure first + * and second don't get in each other's way if they share the same parent, + * so we select the closest previous focus_head that isn't involved. */ + Con *first_prev_focus_head = first; + while (first_prev_focus_head == first || first_prev_focus_head == second) { + first_prev_focus_head = TAILQ_PREV(first_prev_focus_head, focus_head, focused); + } + + Con *second_prev_focus_head = second; + while (second_prev_focus_head == second || second_prev_focus_head == first) { + second_prev_focus_head = TAILQ_PREV(second_prev_focus_head, focus_head, focused); + } + + /* We use a fake container to mark the spot of where the second container needs to go. */ + Con *fake = con_new(NULL, NULL); + fake->layout = L_SPLITH; + _con_attach(fake, first->parent, first, true); + + bool result = true; + /* Swap the containers. We set the ignore_focus flag here because after the + * container is attached, the focus order is not yet correct and would + * result in wrong windows being focused. */ + + /* Move first to second. */ + result &= _con_move_to_con(first, second, false, false, false, true, false); + + /* If we moved the container holding the focused window to another + * workspace we need to ensure the visible workspace has the focused + * container. + * We don't need to check this for the second container because we've only + * moved the first one at this point.*/ + if (first_ws != second_ws && focused_within_first) { + con_focus(con_descend_focused(current_ws)); + } + + /* Move second to where first has been originally. */ + result &= _con_move_to_con(second, fake, false, false, false, true, false); + + /* If swapping the containers didn't work we don't need to mess with the focus. */ + if (!result) { + goto swap_end; + } + + /* Swapping will have inserted the containers at the tail of their parents' + * focus head. We fix this now by putting them in the position of the focus + * head the container they swapped with was in. */ + TAILQ_REMOVE(&(first->parent->focus_head), first, focused); + TAILQ_REMOVE(&(second->parent->focus_head), second, focused); + + if (second_prev_focus_head == NULL) { + TAILQ_INSERT_HEAD(&(first->parent->focus_head), first, focused); + } else { + TAILQ_INSERT_AFTER(&(first->parent->focus_head), second_prev_focus_head, first, focused); + } + + if (first_prev_focus_head == NULL) { + TAILQ_INSERT_HEAD(&(second->parent->focus_head), second, focused); + } else { + TAILQ_INSERT_AFTER(&(second->parent->focus_head), first_prev_focus_head, second, focused); + } + + /* If the focus was within any of the swapped containers, do the following: + * - If swapping took place within a workspace, ensure the previously + * focused container stays focused. + * - Otherwise, focus the container that has been swapped in. + * + * To understand why fixing the focus_head previously wasn't enough, + * consider the scenario + * H[ V[ A X ] V[ Y B ] ] + * with B being focused, but X being the focus_head within its parent. If + * we swap A and B now, fixing the focus_head would focus X, but since B + * was the focused container before it should stay focused. + */ + if (focused_within_first) { + if (first_ws == second_ws) { + con_focus(old_focus); + } else { + con_focus(con_descend_focused(second)); + } + } else if (focused_within_second) { + if (first_ws == second_ws) { + con_focus(old_focus); + } else { + con_focus(con_descend_focused(first)); + } + } + + /* We need to copy each other's percentages to ensure that the geometry + * doesn't change during the swap. This needs to happen _before_ we close + * the fake container as closing the tree will recalculate percentages. */ + first->percent = second_percent; + second->percent = first_percent; + fake->percent = 0.0; + +swap_end: + /* We don't actually need this since percentages-wise we haven't changed + * anything, but we'll better be safe than sorry and just make sure as we'd + * otherwise crash i3. */ + con_fix_percent(first->parent); + con_fix_percent(second->parent); + + /* We can get rid of the fake container again now. */ + con_close(fake, DONT_KILL_WINDOW); + + con_force_split_parents_redraw(first); + con_force_split_parents_redraw(second); + + return result; +}