From: Michael Stapelberg Date: Wed, 1 Sep 2010 16:11:01 +0000 (+0200) Subject: Implement sticky windows X-Git-Tag: tree-pr1~133 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=0925e8b7dc76e9a64872b5b25b210ae98ffe3755;p=i3%2Fi3 Implement sticky windows The implementation works like this: Containers can have a 'sticky-group' attribute. Imagine two different containers (on two different workspaces) which have the same sticky-group. Now you open a window in the first container. When you switch to the other workspace, the window will be re-assigned to the other container. An obvious problem which is not covered with the code at the moment is having two containers with the same sticky-group visible at the same time. --- diff --git a/include/data.h b/include/data.h index 79e0e18a..8341e5eb 100644 --- a/include/data.h +++ b/include/data.h @@ -273,6 +273,11 @@ struct Con { char *name; + /* a sticky-group is an identifier which bundles several containers to a + * group. The contents are shared between all of them, that is they are + * displayed on whichever of the containers is currently visible */ + char *sticky_group; + /* user-definable mark to jump to this container later */ char *mark; diff --git a/include/x.h b/include/x.h index 91af5014..1cd83fb8 100644 --- a/include/x.h +++ b/include/x.h @@ -12,6 +12,19 @@ */ void x_con_init(Con *con); +/** + * Moves a child window from Container src to Container dest. + * + */ +void x_move_win(Con *src, Con *dest); + +/** + * Reparents the child window of the given container (necessary for sticky + * containers). The reparenting happens in the next call of x_push_changes(). + * + */ +void x_reparent_child(Con *con, Con *old); + /** * Re-initializes the associated X window state for this container. You have * to call this when you assign a client to an empty container to ensure that diff --git a/src/load_layout.c b/src/load_layout.c index b88a49be..ae957bc6 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -71,6 +71,10 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { if (strcasecmp(last_key, "name") == 0) { json_node->name = scalloc((len+1) * sizeof(char)); memcpy(json_node->name, val, len); + } else if (strcasecmp(last_key, "sticky_group") == 0) { + json_node->sticky_group = scalloc((len+1) * sizeof(char)); + memcpy(json_node->sticky_group, val, len); + LOG("sticky_group of this container is %s\n", json_node->sticky_group); } } return 1; diff --git a/src/workspace.c b/src/workspace.c index 6a81f30e..81492240 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -92,6 +92,83 @@ bool workspace_is_visible(Workspace *ws) { } #endif +/* + * XXX: we need to clean up all this recursive walking code. + * + */ +Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) { + Con *current; + + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { + if (current != exclude && + current->sticky_group != NULL && + current->window != NULL && + strcmp(current->sticky_group, sticky_group) == 0) + return current; + + Con *recurse = _get_sticky(current, sticky_group, exclude); + if (recurse != NULL) + return recurse; + } + + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) { + if (current != exclude && + current->sticky_group != NULL && + current->window != NULL && + strcmp(current->sticky_group, sticky_group) == 0) + return current; + + Con *recurse = _get_sticky(current, sticky_group, exclude); + if (recurse != NULL) + return recurse; + } + + return NULL; +} + +/* + * Reassigns all child windows in sticky containers. Called when the user + * changes workspaces. + * + * XXX: what about sticky containers which contain containers? + * + */ +static void workspace_reassign_sticky(Con *con) { + Con *current; + /* 1: go through all containers */ + + /* handle all children and floating windows of this node */ + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { + if (current->sticky_group == NULL) { + workspace_reassign_sticky(current); + continue; + } + + LOG("Ah, this one is sticky: %s / %p\n", current->name, current); + /* 2: find a window which we can re-assign */ + Con *output = con_get_output(current); + Con *src = _get_sticky(output, current->sticky_group, current); + + if (src == NULL) { + LOG("No window found for this sticky group\n"); + workspace_reassign_sticky(current); + continue; + } + + x_move_win(src, current); + current->window = src->window; + current->mapped = true; + src->window = NULL; + src->mapped = false; + + x_reparent_child(current, src); + + LOG("re-assigned window from src %p to dest %p\n", src, current); + } + + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + workspace_reassign_sticky(current); +} /* * Switches to the given workspace @@ -110,6 +187,8 @@ void workspace_show(const char *num) { TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) current->fullscreen_mode = CF_NONE; + workspace_reassign_sticky(workspace); + LOG("switching to %p\n", workspace); Con *next = workspace; diff --git a/src/x.c b/src/x.c index 8fa52e02..a2e9c71e 100644 --- a/src/x.c +++ b/src/x.c @@ -18,6 +18,13 @@ static xcb_window_t focused_id = XCB_NONE; typedef struct con_state { xcb_window_t id; bool mapped; + + /* For reparenting, we have a flag (need_reparent) and the X ID of the old + * frame this window was in. The latter is necessary because we need to + * ignore UnmapNotify events (by changing the window event mask). */ + bool need_reparent; + xcb_window_t old_frame; + Rect rect; Rect window_rect; @@ -105,6 +112,47 @@ void x_reinit(Con *con) { memset(&(state->window_rect), 0, sizeof(Rect)); } +/* + * Reparents the child window of the given container (necessary for sticky + * containers). The reparenting happens in the next call of x_push_changes(). + * + */ +void x_reparent_child(Con *con, Con *old) { + struct con_state *state; + if ((state = state_for_frame(con->frame)) == NULL) { + ELOG("window state for con not found\n"); + return; + } + + state->need_reparent = true; + state->old_frame = old->frame; +} + +/* + * Moves a child window from Container src to Container dest. + * + */ +void x_move_win(Con *src, Con *dest) { + struct con_state *state_src, *state_dest; + + if ((state_src = state_for_frame(src->frame)) == NULL) { + ELOG("window state for src not found\n"); + return; + } + + if ((state_dest = state_for_frame(dest->frame)) == NULL) { + ELOG("window state for dest not found\n"); + return; + } + + Rect zero; + memset(&zero, 0, sizeof(Rect)); + if (memcmp(&(state_dest->window_rect), &(zero), sizeof(Rect)) == 0) { + memcpy(&(state_dest->window_rect), &(state_src->window_rect), sizeof(Rect)); + LOG("COPYING RECT\n"); + } +} + /* * Kills the window decoration associated with the given container. * @@ -233,6 +281,29 @@ static void x_push_node(Con *con) { LOG("Pushing changes for node %p / %s\n", con, con->name); state = state_for_frame(con->frame); + /* reparent the child window (when the window was moved due to a sticky + * container) */ + if (state->need_reparent && con->window != NULL) { + LOG("Reparenting child window\n"); + + /* Temporarily set the event masks to XCB_NONE so that we won’t get + * UnmapNotify events (otherwise the handler would close the container). + * These events are generated automatically when reparenting. */ + uint32_t values[] = { XCB_NONE }; + xcb_change_window_attributes(conn, state->old_frame, XCB_CW_EVENT_MASK, values); + xcb_change_window_attributes(conn, con->window->id, XCB_CW_EVENT_MASK, values); + + xcb_reparent_window(conn, con->window->id, con->frame, 0, 0); + + values[0] = FRAME_EVENT_MASK; + xcb_change_window_attributes(conn, state->old_frame, XCB_CW_EVENT_MASK, values); + values[0] = CHILD_EVENT_MASK; + xcb_change_window_attributes(conn, con->window->id, XCB_CW_EVENT_MASK, values); + + state->old_frame = XCB_NONE; + state->need_reparent = false; + } + /* map/unmap if map state changed, also ensure that the child window * is changed if we are mapped *and* in initial state (meaning the * container was empty before, but now got a child) */ @@ -269,7 +340,8 @@ static void x_push_node(Con *con) { } /* dito, but for child windows */ - if (memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) { + if (con->window != NULL && + memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) { LOG("setting window rect (%d, %d, %d, %d)\n", con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); xcb_set_window_rect(conn, con->window->id, con->window_rect);