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.
This commit is contained in:
parent
4d12e18571
commit
0925e8b7dc
|
@ -273,6 +273,11 @@ struct Con {
|
||||||
|
|
||||||
char *name;
|
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 */
|
/* user-definable mark to jump to this container later */
|
||||||
char *mark;
|
char *mark;
|
||||||
|
|
||||||
|
|
13
include/x.h
13
include/x.h
|
@ -12,6 +12,19 @@
|
||||||
*/
|
*/
|
||||||
void x_con_init(Con *con);
|
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
|
* 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
|
* to call this when you assign a client to an empty container to ensure that
|
||||||
|
|
|
@ -71,6 +71,10 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
|
||||||
if (strcasecmp(last_key, "name") == 0) {
|
if (strcasecmp(last_key, "name") == 0) {
|
||||||
json_node->name = scalloc((len+1) * sizeof(char));
|
json_node->name = scalloc((len+1) * sizeof(char));
|
||||||
memcpy(json_node->name, val, len);
|
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;
|
return 1;
|
||||||
|
|
|
@ -92,6 +92,83 @@ bool workspace_is_visible(Workspace *ws) {
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
* Switches to the given workspace
|
||||||
|
@ -110,6 +187,8 @@ void workspace_show(const char *num) {
|
||||||
TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes)
|
TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes)
|
||||||
current->fullscreen_mode = CF_NONE;
|
current->fullscreen_mode = CF_NONE;
|
||||||
|
|
||||||
|
workspace_reassign_sticky(workspace);
|
||||||
|
|
||||||
LOG("switching to %p\n", workspace);
|
LOG("switching to %p\n", workspace);
|
||||||
Con *next = workspace;
|
Con *next = workspace;
|
||||||
|
|
||||||
|
|
74
src/x.c
74
src/x.c
|
@ -18,6 +18,13 @@ static xcb_window_t focused_id = XCB_NONE;
|
||||||
typedef struct con_state {
|
typedef struct con_state {
|
||||||
xcb_window_t id;
|
xcb_window_t id;
|
||||||
bool mapped;
|
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 rect;
|
||||||
Rect window_rect;
|
Rect window_rect;
|
||||||
|
|
||||||
|
@ -105,6 +112,47 @@ void x_reinit(Con *con) {
|
||||||
memset(&(state->window_rect), 0, sizeof(Rect));
|
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.
|
* 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);
|
LOG("Pushing changes for node %p / %s\n", con, con->name);
|
||||||
state = state_for_frame(con->frame);
|
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
|
/* map/unmap if map state changed, also ensure that the child window
|
||||||
* is changed if we are mapped *and* in initial state (meaning the
|
* is changed if we are mapped *and* in initial state (meaning the
|
||||||
* container was empty before, but now got a child) */
|
* 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 */
|
/* 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",
|
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);
|
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);
|
xcb_set_window_rect(conn, con->window->id, con->window_rect);
|
||||||
|
|
Loading…
Reference in New Issue