X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fcon.c;h=ba14e06c66028083c46da87a16c5f7ed0dc75562;hb=1dbdd4fece86c5f85bde54e40f58fa1a91810933;hp=eb7333552fd63ae11a68580401365a69f4324296;hpb=506b7f40048185bd3e7a87b9ec27e58c6724046c;p=i3%2Fi3 diff --git a/src/con.c b/src/con.c index eb733355..ba14e06c 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,19 +28,38 @@ 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 - * X11 IDs using x_con_init(). + * This function only initializes the data structures. * */ -Con *con_new(Con *parent, i3Window *window) { +Con *con_new_skeleton(Con *parent, i3Window *window) { Con *new = scalloc(sizeof(Con)); new->on_remove_child = con_on_remove_child; TAILQ_INSERT_TAIL(&all_cons, new, all_cons); + new->aspect_ratio = 0.0; new->type = CT_CON; new->window = window; new->border_style = config.default_border; + new->current_border_width = -1; + if (window) + new->depth = window->depth; + else + new->depth = XCB_COPY_FROM_PARENT; static int cnt = 0; DLOG("opening window %d\n", cnt); @@ -49,10 +70,6 @@ Con *con_new(Con *parent, i3Window *window) { cnt++; if ((cnt % (sizeof(colors) / sizeof(char*))) == 0) cnt = 0; - 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)); @@ -65,6 +82,15 @@ Con *con_new(Con *parent, i3Window *window) { return new; } +/* A wrapper for con_new_skeleton, to retain the old con_new behaviour + * + */ +Con *con_new(Con *parent, i3Window *window) { + Con *new = con_new_skeleton(parent, window); + x_con_init(new, new->depth); + return new; +} + /* * Attaches the given container to the given parent. This happens when moving * a container or when inserting a new container at a specific place in the @@ -133,7 +159,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); @@ -160,6 +186,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); } /* @@ -167,6 +194,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); @@ -193,8 +221,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)); } } @@ -207,6 +241,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). @@ -217,7 +277,7 @@ bool con_accepts_window(Con *con) { if (con->type == CT_WORKSPACE) return false; - if (con->split) { + if (con_is_split(con)) { DLOG("container %p does not accept windows, it is a split container.\n", con); return false; } @@ -335,6 +395,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. * @@ -501,8 +569,9 @@ void con_fix_percent(Con *con) { } /* - * Toggles fullscreen mode for the given container. Fullscreen mode will not be - * entered when there already is a fullscreen container on this workspace. + * Toggles fullscreen mode for the given container. If there already is a + * fullscreen container on this workspace, fullscreen will be disabled and then + * enabled for the container the user wants to have in fullscreen mode. * */ void con_toggle_fullscreen(Con *con, int fullscreen_mode) { @@ -574,11 +643,6 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) { * */ 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"); - return; - } - /* Prevent moving if this would violate the fullscreen focus restrictions. */ if (!con_fullscreen_permits_focusing(workspace)) { LOG("Cannot move out of a fullscreen container"); @@ -596,6 +660,25 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool 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); @@ -653,6 +736,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; @@ -664,30 +755,66 @@ 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)); - /* 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)) { - 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(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) + /* 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 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. */ + 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); @@ -882,6 +1009,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) { @@ -895,7 +1023,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 */ @@ -914,7 +1047,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 */ @@ -935,52 +1073,44 @@ Con *con_descend_direction(Con *con, direction_t direction) { */ Rect con_border_style_rect(Con *con) { 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; - switch (border_style) { - case BS_NORMAL: - result = (Rect){2, 0, -(2 * 2), -2}; - if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) { - result.x -= 2; - result.width += 2; - } - if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) { - result.width += 2; - } - /* With normal borders we never hide the upper border */ - if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) { - result.height += 2; - } - return result; - - case BS_1PIXEL: - result = (Rect){1, 1, -2, -2}; - if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) { - result.x -= 1; - result.width += 1; - } - if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) { - result.width += 1; - } - if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE) { - result.y -= 1; - result.height += 1; - } - if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) { - result.height += 1; - } - return result; + 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)}; + } - case BS_NONE: - return (Rect){0, 0, 0, 0}; + /* Floating windows are never adjacent to any other window, so + don’t hide their border(s). This prevents bug #998. */ + if (con_is_floating(con)) + return result; - default: - assert(false); + 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; + } /* @@ -1035,10 +1165,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; } @@ -1057,9 +1188,10 @@ 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); + (con->border_style == BS_NORMAL ? render_deco_height() : 0); con->rect.x -= bsr.x; con->rect.y -= bsr.y; @@ -1079,7 +1211,16 @@ void con_set_border_style(Con *con, int border_style) { * new split container before). * */ -void con_set_layout(Con *con, int layout) { +void con_set_layout(Con *con, layout_t 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(). */ @@ -1090,40 +1231,45 @@ void con_set_layout(Con *con, int 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) { - 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. */ - con_set_layout(new, layout); - new->last_split_layout = con->last_split_layout; - new->split = true; - - 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); - } - - /* 4: attach the new split container to the workspace */ - DLOG("Attaching new split to ws\n"); - con_attach(new, con, false); + 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 { + 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; } @@ -1144,6 +1290,7 @@ void con_set_layout(Con *con, int layout) { } else { con->layout = layout; } + con_force_split_parents_redraw(con); } /* @@ -1154,30 +1301,38 @@ void con_set_layout(Con *con, int layout) { * */ 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 (con->layout != L_SPLITH && con->layout != L_SPLITV) - con_set_layout(con, con->last_split_layout); + if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) + con_set_layout(con, parent->last_split_layout); else { - if (con->layout == L_SPLITH) + if (parent->layout == L_SPLITH) con_set_layout(con, L_SPLITV); else con_set_layout(con, L_SPLITH); } } else { - if (con->layout == L_STACKED) + if (parent->layout == L_STACKED) con_set_layout(con, L_TABBED); - else if (con->layout == 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, con->last_split_layout); - } else if (con->layout == L_SPLITH || con->layout == L_SPLITV) { + 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 (con->layout == L_SPLITH) + if (parent->layout == L_SPLITH) con_set_layout(con, L_SPLITV); else con_set_layout(con, L_STACKED); } else { @@ -1200,8 +1355,9 @@ static void con_on_remove_child(Con *con) { * not be closed when the last child was removed */ if (con->type == CT_OUTPUT || con->type == CT_ROOT || - con->type == CT_DOCKAREA) { - DLOG("not handling, type = %d\n", con->type); + con->type == CT_DOCKAREA || + (con->parent != NULL && con->parent->type == CT_OUTPUT)) { + DLOG("not handling, type = %d, name = %s\n", con->type, con->name); return; } @@ -1215,6 +1371,10 @@ static void con_on_remove_child(Con *con) { return; } + con_force_split_parents_redraw(con); + con->urgent = con_has_urgent_child(con); + con_update_parents_urgency(con); + /* TODO: check if this container would swallow any other client and * don’t close it automatically. */ int children = con_num_children(con); @@ -1261,7 +1421,7 @@ 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->split) { + if (con_is_split(con)) { uint32_t width = 0, height = 0; Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { @@ -1279,7 +1439,7 @@ Rect con_minimum_size(Con *con) { } ELOG("Unhandled case, type = %d, layout = %d, split = %d\n", - con->type, con->layout, con->split); + con->type, con->layout, con_is_split(con)); assert(false); } @@ -1345,3 +1505,147 @@ bool con_fullscreen_permits_focusing(Con *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; + } +} + +/* + * Set urgency flag to the container, all the parent containers and the workspace. + * + */ +void con_set_urgency(Con *con, bool urgent) { + if (focused == con) { + DLOG("Ignoring urgency flag for current client\n"); + con->window->urgent.tv_sec = 0; + con->window->urgent.tv_usec = 0; + return; + } + + if (con->urgency_timer == NULL) { + con->urgent = urgent; + } else + DLOG("Discarding urgency WM_HINT because timer is running\n"); + + //CLIENT_LOG(con); + if (con->window) { + if (con->urgent) { + gettimeofday(&con->window->urgent, NULL); + } else { + con->window->urgent.tv_sec = 0; + con->window->urgent.tv_usec = 0; + } + } + + con_update_parents_urgency(con); + + if (con->urgent == urgent) + LOG("Urgency flag changed to %d\n", con->urgent); + + Con *ws; + /* Set the urgency flag on the workspace, if a workspace could be found + * (for dock clients, that is not the case). */ + if ((ws = con_get_workspace(con)) != NULL) + workspace_update_urgent_flag(ws); +} + +/* + * 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["); + else { + ELOG("BUG: Code not updated to account for new layout type\n"); + assert(false); + } + + /* 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; +}