X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fcon.c;h=ad5025a92057aa10aa16d4916065f42fcf884183;hb=ae605bdd394bdf83a8015ac626b222fd40e35b04;hp=537b4436c0f2f1e0b5af6c6ff0b08245cb830717;hpb=e09e077b763bb1ff1975998c87d017afa9c319c3;p=i3%2Fi3 diff --git a/src/con.c b/src/con.c index 537b4436..ad5025a9 100644 --- a/src/con.c +++ b/src/con.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "con.c" /* * vim:ts=4:sw=4:expandtab * @@ -26,6 +28,20 @@ char *colors[] = { static void con_on_remove_child(Con *con); +/* + * force parent split containers to be redrawn + * + */ +static 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)) + FREE(parent->deco_render_params); + parent = parent->parent; + } +} + /* * Create a new container (and attach it to the given parent, if not NULL). * This function initializes the data structures and creates the appropriate @@ -39,6 +55,7 @@ Con *con_new(Con *parent, i3Window *window) { new->type = CT_CON; new->window = window; new->border_style = config.default_border; + new->current_border_width = -1; static int cnt = 0; DLOG("opening window %d\n", cnt); @@ -49,8 +66,10 @@ Con *con_new(Con *parent, i3Window *window) { cnt++; if ((cnt % (sizeof(colors) / sizeof(char*))) == 0) cnt = 0; - - x_con_init(new); + if (window) + x_con_init(new, window->depth); + else + x_con_init(new, XCB_COPY_FROM_PARENT); TAILQ_INIT(&(new->floating_head)); TAILQ_INIT(&(new->nodes_head)); @@ -131,7 +150,7 @@ void con_attach(Con *con, Con *parent, bool ignore_focus) { */ if (con->window != NULL && parent->type == CT_WORKSPACE && - config.default_layout != L_DEFAULT) { + parent->workspace_layout != L_DEFAULT) { DLOG("Parent is a workspace. Applying default layout...\n"); Con *target = workspace_attach_to(parent); @@ -158,6 +177,7 @@ add_to_focus_head: * This way, we have the option to insert Cons without having * to focus them. */ TAILQ_INSERT_TAIL(focus_head, con, focused); + con_force_split_parents_redraw(con); } /* @@ -165,6 +185,7 @@ add_to_focus_head: * */ void con_detach(Con *con) { + con_force_split_parents_redraw(con); if (con->type == CT_FLOATING_CON) { TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows); TAILQ_REMOVE(&(con->parent->focus_head), con, focused); @@ -191,8 +212,14 @@ void con_focus(Con *con) { con_focus(con->parent); focused = con; - if (con->urgent) { + /* We can't blindly reset non-leaf containers since they might have + * other urgent children. Therefore we only reset leafs and propagate + * the changes upwards via con_update_parents_urgency() which does proper + * checks before resetting the urgency. + */ + if (con->urgent && con_is_leaf(con)) { con->urgent = false; + con_update_parents_urgency(con); workspace_update_urgent_flag(con_get_workspace(con)); } } @@ -205,6 +232,32 @@ bool con_is_leaf(Con *con) { return TAILQ_EMPTY(&(con->nodes_head)); } +/** + * Returns true if this node has regular or floating children. + * + */ +bool con_has_children(Con *con) { + return (!con_is_leaf(con) || !TAILQ_EMPTY(&(con->floating_head))); +} + +/* + * Returns true if a container should be considered split. + * + */ +bool con_is_split(Con *con) { + if (con_is_leaf(con)) + return false; + + switch (con->layout) { + case L_DOCKAREA: + case L_OUTPUT: + return false; + + default: + return true; + } +} + /* * Returns true if this node accepts a window (if the node swallows windows, * it might already have swallowed enough and cannot hold any more). @@ -215,8 +268,8 @@ bool con_accepts_window(Con *con) { if (con->type == CT_WORKSPACE) return false; - if (con->orientation != NO_ORIENTATION) { - DLOG("container %p does not accepts windows, orientation != NO_ORIENTATION\n", con); + if (con_is_split(con)) { + DLOG("container %p does not accept windows, it is a split container.\n", con); return false; } @@ -263,8 +316,11 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation) { while (con_orientation(parent) != orientation) { DLOG("Need to go one level further up\n"); parent = parent->parent; - /* Abort when we reach a floating con */ - if (parent && parent->type == CT_FLOATING_CON) + /* Abort when we reach a floating con, or an output con */ + if (parent && + (parent->type == CT_FLOATING_CON || + parent->type == CT_OUTPUT || + (parent->parent && parent->parent->type == CT_OUTPUT))) parent = NULL; if (parent == NULL) break; @@ -330,6 +386,14 @@ Con *con_get_fullscreen_con(Con *con, int fullscreen_mode) { return NULL; } +/** + * Returns true if the container is internal, such as __i3_scratch + * + */ +bool con_is_internal(Con *con) { + return (con->name[0] == '_' && con->name[1] == '_'); +} + /* * Returns true if the node is floating. * @@ -518,10 +582,12 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) { fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); } if (fullscreen != NULL) { - LOG("Not entering fullscreen mode, container (%p/%s) " - "already is in fullscreen mode\n", + /* Disable fullscreen for the currently fullscreened + * container and enable it for the one the user wants + * to have in fullscreen mode. */ + LOG("Disabling fullscreen for (%p/%s) upon user request\n", fullscreen, fullscreen->name); - goto update_netwm_state; + fullscreen->fullscreen_mode = CF_NONE; } /* 2: enable fullscreen */ @@ -531,7 +597,6 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) { con->fullscreen_mode = CF_NONE; } -update_netwm_state: DLOG("mode now: %d\n", con->fullscreen_mode); /* update _NET_WM_STATE if this container has a window */ @@ -568,8 +633,9 @@ update_netwm_state: * */ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp) { - if (con->type == CT_WORKSPACE) { - DLOG("Moving workspaces is not yet implemented.\n"); + /* Prevent moving if this would violate the fullscreen focus restrictions. */ + if (!con_fullscreen_permits_focusing(workspace)) { + LOG("Cannot move out of a fullscreen container"); return; } @@ -578,6 +644,35 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool con = con->parent; } + Con *source_ws = con_get_workspace(con); + if (workspace == source_ws) { + DLOG("Not moving, already there\n"); + return; + } + + if (con->type == CT_WORKSPACE) { + /* Re-parent all of the old workspace's floating windows. */ + Con *child; + while (!TAILQ_EMPTY(&(source_ws->floating_head))) { + child = TAILQ_FIRST(&(source_ws->floating_head)); + con_move_to_workspace(child, workspace, true, true); + } + + /* If there are no non-floating children, ignore the workspace. */ + if (con_is_leaf(con)) + return; + + con = workspace_encapsulate(con); + if (con == NULL) { + ELOG("Workspace failed to move its contents into a container!\n"); + return; + } + } + + /* Save the current workspace. So we can call workspace_show() by the end + * of this function. */ + Con *current_ws = con_get_workspace(focused); + Con *source_output = con_get_output(con), *dest_output = con_get_output(workspace); @@ -631,6 +726,14 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool } } + /* If moving a fullscreen container and the destination already has a + * fullscreen window on it, un-fullscreen the target's fullscreen con. */ + Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); + if (con->fullscreen_mode != CF_NONE && fullscreen != NULL) { + con_toggle_fullscreen(fullscreen, CF_OUTPUT); + fullscreen = NULL; + } + DLOG("Re-attaching container to %p / %s\n", next, next->name); /* 5: re-attach the con to the parent of this focused container */ Con *parent = con->parent; @@ -642,26 +745,72 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool con->percent = 0.0; con_fix_percent(next); - /* 7: focus the con on the target workspace (the X focus is only updated by - * calling tree_render(), so for the "real" focus this is a no-op). + /* 7: focus the con on the target workspace, but only within that + * workspace, that is, don’t move focus away if the target workspace is + * invisible. * We don’t focus the con for i3 pseudo workspaces like __i3_scratch and * we don’t focus when there is a fullscreen con on that workspace. */ - if ((workspace->name[0] != '_' || workspace->name[1] != '_') && - con_get_fullscreen_con(workspace, CF_OUTPUT) == NULL) + if (!con_is_internal(workspace) && !fullscreen) { + /* 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); + } + /* 8: when moving to a visible workspace on a different output, we keep the * con focused. Otherwise, we leave the focus on the current workspace as we * don’t want to focus invisible workspaces */ if (source_output != dest_output && - workspace_is_visible(workspace)) { + workspace_is_visible(workspace) && + !con_is_internal(workspace)) { DLOG("Moved to a different output, focusing target\n"); } else { /* 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. */ - workspace_show(con_get_workspace(focus_next)); - con_focus(con_descend_focused(focus_next)); + workspace_show(current_ws); + + /* Set focus only if con was on current workspace before moving. + * Otherwise we would give focus to some window on different workspace. */ + if (source_ws == current_ws) + con_focus(con_descend_focused(focus_next)); + } + + /* If anything within the container is associated with a startup sequence, + * delete it so child windows won't be created on the old workspace. */ + struct Startup_Sequence *sequence; + xcb_get_property_cookie_t cookie; + xcb_get_property_reply_t *startup_id_reply; + + if (!con_is_leaf(con)) { + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (!child->window) + continue; + + cookie = xcb_get_property(conn, false, child->window->id, + A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512); + startup_id_reply = xcb_get_property_reply(conn, cookie, NULL); + + sequence = startup_sequence_get(child->window, startup_id_reply, true); + if (sequence != NULL) + startup_sequence_delete(sequence); + } + } + + if (con->window) { + cookie = xcb_get_property(conn, false, con->window->id, + A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512); + startup_id_reply = xcb_get_property_reply(conn, cookie, NULL); + + sequence = startup_sequence_get(con->window, startup_id_reply, true); + if (sequence != NULL) + startup_sequence_delete(sequence); } CALL(parent, on_remove_child); @@ -674,14 +823,32 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool * */ int con_orientation(Con *con) { - /* stacking containers behave like they are in vertical orientation */ - if (con->layout == L_STACKED) - return VERT; - - if (con->layout == L_TABBED) - return HORIZ; - - return con->orientation; + switch (con->layout) { + case L_SPLITV: + /* stacking containers behave like they are in vertical orientation */ + case L_STACKED: + return VERT; + + case L_SPLITH: + /* tabbed containers behave like they are in vertical orientation */ + case L_TABBED: + return HORIZ; + + case L_DEFAULT: + DLOG("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"); + assert(false); + } } /* @@ -838,6 +1005,7 @@ Con *con_descend_tiling_focused(Con *con) { */ Con *con_descend_direction(Con *con, direction_t direction) { Con *most = NULL; + Con *current; int orientation = con_orientation(con); DLOG("con_descend_direction(%p, orientation %d, direction %d)\n", con, orientation, direction); if (direction == D_LEFT || direction == D_RIGHT) { @@ -851,7 +1019,12 @@ Con *con_descend_direction(Con *con, direction_t direction) { /* Wrong orientation. We use the last focused con. Within that con, * we recurse to chose the left/right con or at least the last * focused one. */ - most = TAILQ_FIRST(&(con->focus_head)); + TAILQ_FOREACH(current, &(con->focus_head), focused) { + if (current->type != CT_FLOATING_CON) { + most = current; + break; + } + } } else { /* If the con has no orientation set, it’s not a split container * but a container with a client window, so stop recursing */ @@ -870,7 +1043,12 @@ Con *con_descend_direction(Con *con, direction_t direction) { /* Wrong orientation. We use the last focused con. Within that con, * we recurse to chose the top/bottom con or at least the last * focused one. */ - most = TAILQ_FIRST(&(con->focus_head)); + TAILQ_FOREACH(current, &(con->focus_head), focused) { + if (current->type != CT_FLOATING_CON) { + most = current; + break; + } + } } else { /* If the con has no orientation set, it’s not a split container * but a container with a client window, so stop recursing */ @@ -890,19 +1068,57 @@ Con *con_descend_direction(Con *con, direction_t direction) { * */ Rect con_border_style_rect(Con *con) { - switch (con_border_style(con)) { - case BS_NORMAL: - return (Rect){2, 0, -(2 * 2), -2}; - - case BS_1PIXEL: - return (Rect){1, 1, -2, -2}; + 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); + Rect result; + if (con->current_border_width < 0) + border_width = config.default_border_width; + DLOG("Effective border width is set to: %d\n", border_width); + /* Shortcut to avoid calling con_adjacent_borders() on dock containers. */ + int border_style = con_border_style(con); + if (border_style == BS_NONE) + return (Rect){ 0, 0, 0, 0 }; + borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; + if (border_style == BS_NORMAL) { + result = (Rect){border_width, 0 , -(2 * border_width), -(border_width)}; + } else { + result = (Rect){border_width, border_width, -(2 * border_width), -(2 * border_width)}; + } + if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) { + result.x -= border_width; + result.width += border_width; + } + if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) { + result.width += border_width; + } + if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE && (border_style != BS_NORMAL)) { + result.y -= border_width; + result.height += border_width; + } + if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) { + result.height += border_width; + } + return result; - case BS_NONE: - return (Rect){0, 0, 0, 0}; +} - default: - assert(false); - } +/* + * Returns adjacent borders of the window. We need this if hide_edge_borders is + * enabled. + */ +adjacent_t con_adjacent_borders(Con *con) { + adjacent_t result = ADJ_NONE; + Con *workspace = con_get_workspace(con); + if (con->rect.x == workspace->rect.x) + result |= ADJ_LEFT_SCREEN_EDGE; + if (con->rect.x + con->rect.width == workspace->rect.x + workspace->rect.width) + result |= ADJ_RIGHT_SCREEN_EDGE; + if (con->rect.y == workspace->rect.y) + result |= ADJ_UPPER_SCREEN_EDGE; + if (con->rect.y + con->rect.height == workspace->rect.y + workspace->rect.height) + result |= ADJ_LOWER_SCREEN_EDGE; + return result; } /* @@ -939,10 +1155,11 @@ int con_border_style(Con *con) { * floating window. * */ -void con_set_border_style(Con *con, int border_style) { +void con_set_border_style(Con *con, int border_style, int border_width) { /* Handle the simple case: non-floating containerns */ if (!con_is_floating(con)) { con->border_style = border_style; + con->current_border_width = border_width; return; } @@ -961,6 +1178,7 @@ void con_set_border_style(Con *con, int border_style) { /* Change the border style, get new border/decoration values. */ con->border_style = border_style; + con->current_border_width = border_width; bsr = con_border_style_rect(con); int deco_height = (con->border_style == BS_NORMAL ? config.font.height + 5 : 0); @@ -984,54 +1202,134 @@ void con_set_border_style(Con *con, int border_style) { * */ void con_set_layout(Con *con, int layout) { + DLOG("con_set_layout(%p, %d), con->type = %d\n", + con, layout, con->type); + + /* Users can focus workspaces, but not any higher in the hierarchy. + * Focus on the workspace is a special case, since in every other case, the + * user means "change the layout of the parent split container". */ + if (con->type != CT_WORKSPACE) + con = con->parent; + + /* We fill in last_split_layout when switching to a different layout + * since there are many places in the code that don’t use + * con_set_layout(). */ + if (con->layout == L_SPLITH || con->layout == L_SPLITV) + con->last_split_layout = con->layout; + /* When the container type is CT_WORKSPACE, the user wants to change the * 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) { - DLOG("Creating new split container\n"); - /* 1: create a new split container */ - Con *new = con_new(NULL, NULL); - new->parent = con; - - /* 2: set the requested layout on the split con */ - new->layout = layout; - - /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs - * to be set. Otherwise, this con will not be interpreted as a split - * container. */ - if (config.default_orientation == NO_ORIENTATION) { - new->orientation = (con->rect.height > con->rect.width) ? VERT : HORIZ; + if (con->type == CT_WORKSPACE && + (layout == L_STACKED || layout == L_TABBED)) { + if (con_num_children(con) == 0) { + DLOG("Setting workspace_layout to %d\n", layout); + con->workspace_layout = layout; } else { - new->orientation = config.default_orientation; - } - - Con *old_focused = TAILQ_FIRST(&(con->focus_head)); - if (old_focused == TAILQ_END(&(con->focus_head))) - old_focused = NULL; - - /* 4: move the existing cons of this workspace below the new con */ - DLOG("Moving cons\n"); - Con *child; - while (!TAILQ_EMPTY(&(con->nodes_head))) { - child = TAILQ_FIRST(&(con->nodes_head)); - con_detach(child); - con_attach(child, new, true); - } - - /* 4: attach the new split container to the workspace */ - DLOG("Attaching new split to ws\n"); - con_attach(new, con, false); + DLOG("Creating new split container\n"); + /* 1: create a new split container */ + Con *new = con_new(NULL, NULL); + new->parent = con; + + /* 2: Set the requested layout on the split container and mark it as + * split. */ + new->layout = layout; + new->last_split_layout = con->last_split_layout; + + Con *old_focused = TAILQ_FIRST(&(con->focus_head)); + if (old_focused == TAILQ_END(&(con->focus_head))) + old_focused = NULL; + + /* 3: move the existing cons of this workspace below the new con */ + DLOG("Moving cons\n"); + Con *child; + while (!TAILQ_EMPTY(&(con->nodes_head))) { + child = TAILQ_FIRST(&(con->nodes_head)); + con_detach(child); + con_attach(child, new, true); + } - if (old_focused) - con_focus(old_focused); + /* 4: attach the new split container to the workspace */ + DLOG("Attaching new split to ws\n"); + con_attach(new, con, false); - tree_flatten(croot); + if (old_focused) + con_focus(old_focused); + tree_flatten(croot); + } + con_force_split_parents_redraw(con); return; } - con->layout = layout; + if (layout == L_DEFAULT) { + /* Special case: the layout formerly known as "default" (in combination + * with an orientation). Since we switched to splith/splitv layouts, + * using the "default" layout (which "only" should happen when using + * legacy configs) is using the last split layout (either splith or + * splitv) in order to still do the same thing. + * + * Starting from v4.6 though, we will nag users about using "layout + * default", and in v4.9 we will remove it entirely (with an + * appropriate i3-migrate-config mechanism). */ + con->layout = con->last_split_layout; + /* In case last_split_layout was not initialized… */ + if (con->layout == L_DEFAULT) + con->layout = L_SPLITH; + } else { + con->layout = layout; + } + con_force_split_parents_redraw(con); +} + +/* + * This function toggles the layout of a given container. toggle_mode can be + * either 'default' (toggle only between stacked/tabbed/last_split_layout), + * 'split' (toggle only between splitv/splith) or 'all' (toggle between all + * layouts). + * + */ +void con_toggle_layout(Con *con, const char *toggle_mode) { + Con *parent = con; + /* Users can focus workspaces, but not any higher in the hierarchy. + * Focus on the workspace is a special case, since in every other case, the + * user means "change the layout of the parent split container". */ + if (con->type != CT_WORKSPACE) + 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); + } + } else { + if (parent->layout == L_STACKED) + con_set_layout(con, L_TABBED); + else if (parent->layout == L_TABBED) { + if (strcmp(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) { + /* When toggling through all modes, we toggle between + * splith/splitv, whereas normally we just directly jump to + * stacked. */ + if (parent->layout == L_SPLITH) + con_set_layout(con, L_SPLITV); + else con_set_layout(con, L_STACKED); + } else { + con_set_layout(con, L_STACKED); + } + } + } } /* @@ -1062,6 +1360,8 @@ static void con_on_remove_child(Con *con) { return; } + con_force_split_parents_redraw(con); + /* TODO: check if this container would swallow any other client and * don’t close it automatically. */ int children = con_num_children(con); @@ -1108,12 +1408,12 @@ Rect con_minimum_size(Con *con) { /* For horizontal/vertical split containers we sum up the width (h-split) * or height (v-split) and use the maximum of the height (h-split) or width * (v-split) as minimum size. */ - if (con->orientation == HORIZ || con->orientation == VERT) { + if (con_is_split(con)) { uint32_t width = 0, height = 0; Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { Rect min = con_minimum_size(child); - if (con->orientation == HORIZ) { + if (con->layout == L_SPLITH) { width += min.width; height = max(height, min.height); } else { @@ -1125,7 +1425,171 @@ Rect con_minimum_size(Con *con) { return (Rect){ 0, 0, width, height }; } - ELOG("Unhandled case, type = %d, layout = %d, orientation = %d\n", - con->type, con->layout, con->orientation); + ELOG("Unhandled case, type = %d, layout = %d, split = %d\n", + con->type, con->layout, con_is_split(con)); assert(false); } + +/* + * Returns true if changing the focus to con would be allowed considering + * the fullscreen focus constraints. Specifically, if a fullscreen container or + * any of its descendants is focused, this function returns true if and only if + * focusing con would mean that focus would still be visible on screen, i.e., + * the newly focused container would not be obscured by a fullscreen container. + * + * In the simplest case, if a fullscreen container or any of its descendants is + * fullscreen, this functions returns true if con is the fullscreen container + * itself or any of its descendants, as this means focus wouldn't escape the + * boundaries of the fullscreen container. + * + * In case the fullscreen container is of type CF_OUTPUT, this function returns + * true if con is on a different workspace, as focus wouldn't be obscured by + * the fullscreen container that is constrained to a different workspace. + * + * Note that this same logic can be applied to moving containers. If a + * container can be focused under the fullscreen focus constraints, it can also + * become a parent or sibling to the currently focused container. + * + */ +bool con_fullscreen_permits_focusing(Con *con) { + /* No focus, no problem. */ + if (!focused) + return true; + + /* Find the first fullscreen ascendent. */ + Con *fs = focused; + while (fs && fs->fullscreen_mode == CF_NONE) + fs = fs->parent; + + /* fs must be non-NULL since the workspace con doesn’t have CF_NONE and + * there always has to be a workspace con in the hierarchy. */ + assert(fs != NULL); + /* The most common case is we hit the workspace level. In this + * situation, changing focus is also harmless. */ + assert(fs->fullscreen_mode != CF_NONE); + if (fs->type == CT_WORKSPACE) + return true; + + /* Allow it if the container itself is the fullscreen container. */ + if (con == fs) + return true; + + /* If fullscreen is per-output, the focus being in a different workspace is + * sufficient to guarantee that change won't leave fullscreen in bad shape. */ + if (fs->fullscreen_mode == CF_OUTPUT && + con_get_workspace(con) != con_get_workspace(fs)) { + return true; + } + + /* 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; +} + +/* + * + * Checks if the given container has an urgent child. + * + */ +bool con_has_urgent_child(Con *con) { + Con *child; + + if (con_is_leaf(con)) + return con->urgent; + + /* We are not interested in floating windows since they can only be + * attached to a workspace → nodes_head instead of focus_head */ + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (con_has_urgent_child(child)) + return true; + } + + return false; +} + +/* + * Make all parent containers urgent if con is urgent or clear the urgent flag + * of all parent containers if there are no more urgent children left. + * + */ +void con_update_parents_urgency(Con *con) { + Con *parent = con->parent; + + bool new_urgency_value = con->urgent; + while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { + if (new_urgency_value) { + parent->urgent = true; + } else { + /* We can only reset the urgency when the parent + * has no other urgent children */ + if (!con_has_urgent_child(parent)) + parent->urgent = false; + } + parent = parent->parent; + } +} + +/* + * Create a string representing the subtree under con. + * + */ +char *con_get_tree_representation(Con *con) { + /* this code works as follows: + * 1) create a string with the layout type (D/V/H/T/S) and an opening bracket + * 2) append the tree representation of the children to the string + * 3) add closing bracket + * + * The recursion ends when we hit a leaf, in which case we return the + * class_instance of the contained window. + */ + + /* end of recursion */ + if (con_is_leaf(con)) { + if (!con->window) + return sstrdup("nowin"); + + if (!con->window->class_instance) + return sstrdup("noinstance"); + + return sstrdup(con->window->class_instance); + } + + char *buf; + /* 1) add the Layout type to buf */ + if (con->layout == L_DEFAULT) + buf = sstrdup("D["); + else if (con->layout == L_SPLITV) + buf = sstrdup("V["); + else if (con->layout == L_SPLITH) + buf = sstrdup("H["); + else if (con->layout == L_TABBED) + buf = sstrdup("T["); + else if (con->layout == L_STACKED) + buf = sstrdup("S["); + + /* 2) append representation of children */ + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + char *child_txt = con_get_tree_representation(child); + + char *tmp_buf; + sasprintf(&tmp_buf, "%s%s%s", buf, + (TAILQ_FIRST(&(con->nodes_head)) == child ? "" : " "), child_txt); + free(buf); + buf = tmp_buf; + } + + /* 3) close the brackets */ + char *complete_buf; + sasprintf(&complete_buf, "%s]", buf); + free(buf); + + return complete_buf; +}