X-Git-Url: https://git.sur5r.net/?p=i3%2Fi3;a=blobdiff_plain;f=src%2Fcon.c;h=21d2f097af4d667c7ccd0b94afa7d25b209e1cb7;hp=9797afa658a2b9cdc2cdd8e38b2f7958ac7088d1;hb=HEAD;hpb=f120a9d929fd9ad64dd26813967e7b0a03b1390b diff --git a/src/con.c b/src/con.c index 9797afa6..21d2f097 100644 --- a/src/con.c +++ b/src/con.c @@ -245,6 +245,27 @@ void con_focus(Con *con) { } } +/* + * Raise container to the top if it is floating or inside some floating + * container. + * + */ +static void con_raise(Con *con) { + Con *floating = con_inside_floating(con); + if (floating) { + floating_raise_con(floating); + } +} + +/* + * Sets input focus to the given container and raises it to the top. + * + */ +void con_activate(Con *con) { + con_focus(con); + con_raise(con); +} + /* * Closes the given container. * @@ -265,14 +286,14 @@ void con_close(Con *con, kill_window_t kill_window) { 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); + tree_close_internal(child, kill_window, false); child = nextchild; } return; } - tree_close_internal(con, kill_window, false, false); + tree_close_internal(con, kill_window, false); } /* @@ -291,7 +312,7 @@ bool con_has_managed_window(Con *con) { return (con != NULL && con->window != NULL && con->window->id != XCB_WINDOW_NONE && con_get_workspace(con) != NULL); } -/** +/* * Returns true if this node has regular or floating children. * */ @@ -403,7 +424,7 @@ Con *con_get_workspace(Con *con) { } /* - * Searches parenst of the given 'con' until it reaches one with the specified + * Searches parents of the given 'con' until it reaches one with the specified * 'orientation'. Aborts when it comes across a floating_con. * */ @@ -488,7 +509,24 @@ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) { return NULL; } -/** +/* + * Returns the fullscreen node that covers the given workspace if it exists. + * This is either a CF_GLOBAL fullscreen container anywhere or a CF_OUTPUT + * fullscreen container in the workspace. + * + */ +Con *con_get_fullscreen_covering_ws(Con *ws) { + if (!ws) { + return NULL; + } + Con *fs = con_get_fullscreen_con(croot, CF_GLOBAL); + if (!fs) { + return con_get_fullscreen_con(ws, CF_OUTPUT); + } + return fs; +} + +/* * Returns true if the container is internal, such as __i3_scratch * */ @@ -597,6 +635,15 @@ Con *con_by_con_id(long target) { return NULL; } +/* + * Returns true if the given container (still) exists. + * This can be used, e.g., to make sure a container hasn't been closed in the meantime. + * + */ +bool con_exists(Con *con) { + return con_by_con_id((long)con) != NULL; +} + /* * Returns the container with the given frame ID or NULL if no such container * exists. @@ -779,6 +826,62 @@ Con *con_for_window(Con *con, i3Window *window, Match **store_match) { return NULL; } +static int num_focus_heads(Con *con) { + int focus_heads = 0; + + Con *current; + TAILQ_FOREACH(current, &(con->focus_head), focused) { + focus_heads++; + } + + return focus_heads; +} + +/* + * Iterate over the container's focus stack and return an array with the + * containers inside it, ordered from higher focus order to lowest. + * + */ +Con **get_focus_order(Con *con) { + const int focus_heads = num_focus_heads(con); + Con **focus_order = smalloc(focus_heads * sizeof(Con *)); + Con *current; + int idx = 0; + TAILQ_FOREACH(current, &(con->focus_head), focused) { + assert(idx < focus_heads); + focus_order[idx++] = current; + } + + return focus_order; +} + +/* + * Clear the container's focus stack and re-add it using the provided container + * array. The function doesn't check if the provided array contains the same + * containers with the previous focus stack but will not add floating containers + * in the new focus stack if container is not a workspace. + * + */ +void set_focus_order(Con *con, Con **focus_order) { + int focus_heads = 0; + while (!TAILQ_EMPTY(&(con->focus_head))) { + Con *current = TAILQ_FIRST(&(con->focus_head)); + + TAILQ_REMOVE(&(con->focus_head), current, focused); + focus_heads++; + } + + for (int idx = 0; idx < focus_heads; idx++) { + /* Useful when encapsulating a workspace. */ + if (con->type != CT_WORKSPACE && con_inside_floating(focus_order[idx])) { + focus_heads++; + continue; + } + + TAILQ_INSERT_TAIL(&(con->focus_head), focus_order[idx], focused); + } +} + /* * Returns the number of children of this container. * @@ -793,7 +896,7 @@ 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. @@ -833,6 +936,10 @@ int con_num_windows(Con *con) { num += con_num_windows(current); } + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) { + num += con_num_windows(current); + } + return num; } @@ -978,9 +1085,9 @@ void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode) { Con *old_focused = focused; if (fullscreen_mode == CF_GLOBAL && cur_ws != con_ws) workspace_show(con_ws); - con_focus(con); + con_activate(con); if (fullscreen_mode != CF_GLOBAL && cur_ws != con_ws) - con_focus(old_focused); + con_activate(old_focused); con_set_fullscreen_mode(con, fullscreen_mode); } @@ -1011,7 +1118,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* Prevent moving if this would violate the fullscreen focus restrictions. */ Con *target_ws = con_get_workspace(target); - if (!con_fullscreen_permits_focusing(target_ws)) { + if (!ignore_focus && !con_fullscreen_permits_focusing(target_ws)) { LOG("Cannot move out of a fullscreen container.\n"); return false; } @@ -1054,7 +1161,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* 1: save the container which is going to be focused after the current * container is moved away */ - Con *focus_next = con_next_focused(con); + Con *focus_next = NULL; + if (!ignore_focus && source_ws == current_ws) { + focus_next = con_descend_focused(source_ws); + if (focus_next == con || con_has_parent(focus_next, con)) { + focus_next = con_next_focused(con); + } + } /* 2: we go up one level, but only when target is a normal container */ if (target->type != CT_WORKSPACE) { @@ -1062,13 +1175,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi target = target->parent; } - /* 3: if the target container is floating, we get the workspace instead. - * Only tiling windows need to get inserted next to the current container. - * */ - Con *floatingcon = con_inside_floating(target); - if (floatingcon != NULL) { + /* 3: if the original target is the direct child of a floating container, we + * can't move con next to it - floating containers have only one child - so + * we get the workspace instead. */ + if (target->type == CT_FLOATING_CON) { DLOG("floatingcon, going up even further\n"); - target = floatingcon->parent; + orig_target = target; + target = target->parent; } if (con->type == CT_FLOATING_CON) { @@ -1084,20 +1197,6 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi floating_fix_coordinates(con, &(source_output->rect), &(dest_output->rect)); } else DLOG("Not fixing coordinates, fix_coordinates flag = %d\n", fix_coordinates); - - /* If moving to a visible workspace, call show so it can be considered - * focused. Must do before attaching because workspace_show checks to see - * if focused container is in its area. */ - if (!ignore_focus && workspace_is_visible(target_ws)) { - workspace_show(target_ws); - - /* Don’t warp if told so (when dragging floating windows with the - * mouse for example) */ - if (dont_warp) - x_set_warp_to(NULL); - else - x_set_warp_to(&(con->rect)); - } } /* If moving a fullscreen container and the destination already has a @@ -1131,20 +1230,21 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* We need to save the focused workspace on the output in case the * new workspace is hidden and it's necessary to immediately switch * back to the originally-focused workspace. */ - Con *old_focus = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head)); - con_focus(con_descend_focused(con)); - - /* Restore focus if the output's focused workspace has changed. */ - if (con_get_workspace(focused) != old_focus) - con_focus(old_focus); + Con *old_focus_ws = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head)); + Con *old_focus = focused; + con_activate(con_descend_focused(con)); + + if (old_focus_ws == current_ws && old_focus->type != CT_WORKSPACE) { + /* Restore focus to the currently focused container. */ + con_activate(old_focus); + } else if (con_get_workspace(focused) != old_focus_ws) { + /* Restore focus if the output's focused workspace has changed. */ + con_focus(con_descend_focused(old_focus_ws)); + } } /* 7: when moving to another workspace, we leave the focus on the current * workspace. (see also #809) */ - - /* 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) { workspace_show(current_ws); if (dont_warp) { @@ -1155,8 +1255,8 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* Set focus only if con was on current workspace before moving. * Otherwise we would give focus to some window on different workspace. */ - if (!ignore_focus && source_ws == current_ws) - con_focus(con_descend_focused(focus_next)); + if (focus_next) + con_activate(con_descend_focused(focus_next)); /* 8. If anything within the container is associated with a startup sequence, * delete it so child windows won't be created on the old workspace. */ @@ -1224,7 +1324,7 @@ bool con_move_to_mark(Con *con, const char *mark) { return true; } - if (con->type == CT_WORKSPACE) { + if (target->type == CT_WORKSPACE) { DLOG("target container is a workspace, simply moving the container there.\n"); con_move_to_workspace(con, target, true, false, false); return true; @@ -1284,12 +1384,33 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool * visible workspace on the given output. * */ -void con_move_to_output(Con *con, Output *output) { +void con_move_to_output(Con *con, Output *output, bool fix_coordinates) { 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_primary_name(output)); - con_move_to_workspace(con, ws, false, false, false); + con_move_to_workspace(con, ws, fix_coordinates, false, false); +} + +/* + * Moves the given container to the currently focused container on the + * visible workspace on the output specified by the given name. + * The current output for the container is used to resolve relative names + * such as left, right, up, down. + * + */ +bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates) { + Output *current_output = get_output_for_con(con); + assert(current_output != NULL); + + Output *output = get_output_from_string(current_output, name); + if (output == NULL) { + ELOG("Could not find output \"%s\"\n", name); + return false; + } + + con_move_to_output(con, output, fix_coordinates); + return true; } /* @@ -1311,20 +1432,16 @@ orientation_t con_orientation(Con *con) { return HORIZ; case L_DEFAULT: - DLOG("Someone called con_orientation() on a con with L_DEFAULT, this is a bug in the code.\n"); + ELOG("Someone called con_orientation() on a con with L_DEFAULT, this is a bug in the code.\n"); assert(false); - return HORIZ; case L_DOCKAREA: case L_OUTPUT: - DLOG("con_orientation() called on dockarea/output (%d) container %p\n", con->layout, con); - assert(false); - return HORIZ; - - default: - DLOG("con_orientation() ran into default\n"); + ELOG("con_orientation() called on dockarea/output (%d) container %p\n", con->layout, con); assert(false); } + /* should not be reached */ + assert(false); } /* @@ -1334,52 +1451,20 @@ orientation_t con_orientation(Con *con) { * */ Con *con_next_focused(Con *con) { - Con *next; - /* floating containers are attached to a workspace, so we focus either the - * next floating container (if any) or the workspace itself. */ - if (con->type == CT_FLOATING_CON) { - DLOG("selecting next for CT_FLOATING_CON\n"); - next = TAILQ_NEXT(con, floating_windows); - DLOG("next = %p\n", next); - if (!next) { - next = TAILQ_PREV(con, floating_head, floating_windows); - DLOG("using prev, next = %p\n", next); - } - if (!next) { - Con *ws = con_get_workspace(con); - next = ws; - DLOG("no more floating containers for next = %p, restoring workspace focus\n", next); - while (next != TAILQ_END(&(ws->focus_head)) && !TAILQ_EMPTY(&(next->focus_head))) { - next = TAILQ_FIRST(&(next->focus_head)); - if (next == con) { - DLOG("skipping container itself, we want the next client\n"); - next = TAILQ_NEXT(next, focused); - } - } - if (next == TAILQ_END(&(ws->focus_head))) { - DLOG("Focus list empty, returning ws\n"); - next = ws; - } - } else { - /* Instead of returning the next CT_FLOATING_CON, we descend it to - * get an actual window to focus. */ - next = con_descend_focused(next); - } - return next; - } - /* dock clients cannot be focused, so we focus the workspace instead */ if (con->parent->type == CT_DOCKAREA) { DLOG("selecting workspace for dock client\n"); return con_descend_focused(output_get_content(con->parent->parent)); } + if (con_is_floating(con)) { + con = con->parent; + } /* if 'con' is not the first entry in the focus stack, use the first one as * it’s currently focused already */ - Con *first = TAILQ_FIRST(&(con->parent->focus_head)); - if (first != con) { - DLOG("Using first entry %p\n", first); - next = first; + Con *next = TAILQ_FIRST(&(con->parent->focus_head)); + if (next != con) { + DLOG("Using first entry %p\n", next); } else { /* try to focus the next container on the same level as this one or fall * back to its parent */ @@ -1394,6 +1479,10 @@ Con *con_next_focused(Con *con) { next = TAILQ_FIRST(&(next->focus_head)); } + if (con->type == CT_FLOATING_CON && next != con->parent) { + next = con_descend_focused(next); + } + return next; } @@ -1627,8 +1716,7 @@ adjacent_t con_adjacent_borders(Con *con) { * */ int con_border_style(Con *con) { - Con *fs = con_get_fullscreen_con(con->parent, CF_OUTPUT); - if (fs == con) { + if (con->fullscreen_mode == CF_OUTPUT || con->fullscreen_mode == CF_GLOBAL) { DLOG("this one is fullscreen! overriding BS_NONE\n"); return BS_NONE; } @@ -1719,7 +1807,7 @@ void con_set_layout(Con *con, layout_t layout) { con->workspace_layout = ws_layout; DLOG("Setting layout to %d\n", layout); con->layout = layout; - } else if (layout == L_STACKED || layout == L_TABBED) { + } else if (layout == L_STACKED || layout == L_TABBED || layout == L_SPLITV || layout == L_SPLITH) { DLOG("Creating new split container\n"); /* 1: create a new split container */ Con *new = con_new(NULL, NULL); @@ -1730,17 +1818,9 @@ void con_set_layout(Con *con, layout_t layout) { new->layout = layout; new->last_split_layout = con->last_split_layout; - /* Save the container that was focused before we move containers - * around, but only if the container is visible (otherwise focus - * will be restored properly automatically when switching). */ - Con *old_focused = TAILQ_FIRST(&(con->focus_head)); - if (old_focused == TAILQ_END(&(con->focus_head))) - old_focused = NULL; - if (old_focused != NULL && - !workspace_is_visible(con_get_workspace(old_focused))) - old_focused = NULL; - /* 3: move the existing cons of this workspace below the new con */ + Con **focus_order = get_focus_order(con); + DLOG("Moving cons\n"); Con *child; while (!TAILQ_EMPTY(&(con->nodes_head))) { @@ -1749,13 +1829,13 @@ void con_set_layout(Con *con, layout_t layout) { con_attach(child, new, true); } + set_focus_order(new, focus_order); + free(focus_order); + /* 4: attach the new split container to the workspace */ DLOG("Attaching new split to ws\n"); con_attach(new, con, false); - if (old_focused) - con_focus(old_focused); - tree_flatten(croot); } con_force_split_parents_redraw(con); @@ -1811,6 +1891,10 @@ void con_toggle_layout(Con *con, const char *toggle_mode) { * change to the opposite split layout. */ if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) { layout = parent->last_split_layout; + /* In case last_split_layout was not initialized… */ + if (layout == L_DEFAULT) { + layout = L_SPLITH; + } } else { layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH; } @@ -1832,7 +1916,6 @@ void con_toggle_layout(Con *con, const char *toggle_mode) { * now let's activate the current layout (next in list) */ if (current_layout_found) { new_layout = layout; - free(tm_dup); break; } @@ -1840,8 +1923,11 @@ void con_toggle_layout(Con *con, const char *toggle_mode) { current_layout_found = true; } } + free(tm_dup); - con_set_layout(con, new_layout); + if (new_layout != L_DEFAULT) { + 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); @@ -1890,7 +1976,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_internal(con, DONT_KILL_WINDOW, false, false); + tree_close_internal(con, DONT_KILL_WINDOW, false); const unsigned char *payload; ylength length; @@ -1911,7 +1997,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_internal(con, DONT_KILL_WINDOW, false, false); + tree_close_internal(con, DONT_KILL_WINDOW, false); return; } } @@ -2027,14 +2113,7 @@ bool con_fullscreen_permits_focusing(Con *con) { /* Allow it only if the container to be focused is contained within the * current fullscreen container. */ - do { - if (con->parent == fs) - return true; - con = con->parent; - } while (con); - - /* Focusing con would hide it behind a fullscreen window, disallow it. */ - return false; + return con_has_parent(con, fs); } /* @@ -2276,15 +2355,14 @@ bool con_swap(Con *first, Con *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)); + fullscreen_mode_t first_fullscreen_mode = first->fullscreen_mode; + fullscreen_mode_t second_fullscreen_mode = second->fullscreen_mode; - if (!con_fullscreen_permits_focusing(first_ws)) { - DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", first_ws->name); - return false; + if (first_fullscreen_mode != CF_NONE) { + con_disable_fullscreen(first); } - - if (!con_fullscreen_permits_focusing(second_ws)) { - DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", second_ws->name); - return false; + if (second_fullscreen_mode != CF_NONE) { + con_disable_fullscreen(second); } double first_percent = first->percent; @@ -2316,6 +2394,10 @@ bool con_swap(Con *first, Con *second) { /* Move first to second. */ result &= _con_move_to_con(first, second, 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; + } /* If we moved the container holding the focused window to another * workspace we need to ensure the visible workspace has the focused @@ -2323,13 +2405,11 @@ bool con_swap(Con *first, Con *second) { * 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)); + con_activate(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; } @@ -2366,15 +2446,15 @@ bool con_swap(Con *first, Con *second) { */ if (focused_within_first) { if (first_ws == second_ws) { - con_focus(old_focus); + con_activate(old_focus); } else { - con_focus(con_descend_focused(second)); + con_activate(con_descend_focused(second)); } } else if (focused_within_second) { if (first_ws == second_ws) { - con_focus(old_focus); + con_activate(old_focus); } else { - con_focus(con_descend_focused(first)); + con_activate(con_descend_focused(first)); } } @@ -2385,7 +2465,17 @@ bool con_swap(Con *first, Con *second) { second->percent = first_percent; fake->percent = 0.0; + SWAP(first_fullscreen_mode, second_fullscreen_mode, fullscreen_mode_t); + swap_end: + /* The two windows exchange their original fullscreen status */ + if (first_fullscreen_mode != CF_NONE) { + con_enable_fullscreen(first, first_fullscreen_mode); + } + if (second_fullscreen_mode != CF_NONE) { + con_enable_fullscreen(second, second_fullscreen_mode); + } + /* 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. */