]> git.sur5r.net Git - i3/i3/commitdiff
Implement sticky windows
authorMichael Stapelberg <michael@stapelberg.de>
Wed, 1 Sep 2010 16:11:01 +0000 (18:11 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Wed, 1 Sep 2010 16:11:01 +0000 (18:11 +0200)
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.

include/data.h
include/x.h
src/load_layout.c
src/workspace.c
src/x.c

index 79e0e18a04107933d9436f280b6cf361436db349..8341e5eb0b00b1a767332f50d30acbf2ef0e007b 100644 (file)
@@ -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;
 
index 91af5014b6273fbd70328856fe9f9e871c5bcfa6..1cd83fb825aac9698df36364e30beffa3b1255e5 100644 (file)
  */
 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
index b88a49be1f9d5307f998e448a95638f32f59858b..ae957bc62fe937fa519fd08da50be8a290ad45ab 100644 (file)
@@ -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;
index 6a81f30e95f85495161514285ea91bdeac9ad7dc..81492240a57cf9ecb1cb120793c59f2686d34ed1 100644 (file)
@@ -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 8fa52e02b0c0bb39b2933130ce548102f14f9dfc..a2e9c71e1585773de944192514c2b45bd876814a 100644 (file)
--- 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);