diff --git a/include/atoms.xmacro b/include/atoms.xmacro index 80e3bbf0..f856559c 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -3,6 +3,7 @@ xmacro(_NET_SUPPORTING_WM_CHECK) xmacro(_NET_WM_NAME) xmacro(_NET_WM_VISIBLE_NAME) xmacro(_NET_WM_MOVERESIZE) +xmacro(_NET_WM_STATE_STICKY) xmacro(_NET_WM_STATE_FULLSCREEN) xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) xmacro(_NET_WM_STATE_MODAL) diff --git a/include/con.h b/include/con.h index 4813b776..c0a3a46b 100644 --- a/include/con.h +++ b/include/con.h @@ -55,6 +55,12 @@ bool con_is_split(Con *con); */ bool con_is_hidden(Con *con); +/** + * Returns whether the container or any of its children is sticky. + * + */ +bool con_is_sticky(Con *con); + /** * Returns true if this node has regular or floating children. * diff --git a/include/data.h b/include/data.h index d75622ec..58e4a00d 100644 --- a/include/data.h +++ b/include/data.h @@ -603,6 +603,12 @@ struct Con { TAILQ_HEAD(swallow_head, Match) swallow_head; fullscreen_mode_t fullscreen_mode; + + /* Whether this window should stick to the glass. This corresponds to + * the _NET_WM_STATE_STICKY atom and will only be respected if the + * window is floating. */ + bool sticky; + /* layout is the layout of this container: one of split[v|h], stacked or * tabbed. Special containers in the tree (above workspaces) have special * layouts like dockarea or output. diff --git a/src/con.c b/src/con.c index 9a5d36c1..24d563a5 100644 --- a/src/con.c +++ b/src/con.c @@ -284,6 +284,23 @@ bool con_is_hidden(Con *con) { return false; } +/* + * Returns whether the container or any of its children is sticky. + * + */ +bool con_is_sticky(Con *con) { + if (con->sticky) + return true; + + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (con_is_sticky(child)) + return true; + } + + return false; +} + /* * Returns true if this node accepts a window (if the node swallows windows, * it might already have swallowed enough and cannot hold any more). diff --git a/src/ewmh.c b/src/ewmh.c index d60bbb50..36c6a160 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -250,7 +250,7 @@ void ewmh_setup_hints(void) { xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); /* only send the first 31 atoms (last one is _NET_CLOSE_WINDOW) increment that number when adding supported atoms */ - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, /* number of atoms */ 31, supported_atoms); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, /* number of atoms */ 32, supported_atoms); /* We need to map this window to be able to set the input focus to it if no other window is available to be focused. */ xcb_map_window(conn, ewmh_window); diff --git a/src/handlers.c b/src/handlers.c index f3c2350e..4b01cb5e 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -695,7 +695,8 @@ static void handle_client_message(xcb_client_message_event_t *event) { if (event->type == A__NET_WM_STATE) { if (event->format != 32 || (event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN && - event->data.data32[1] != A__NET_WM_STATE_DEMANDS_ATTENTION)) { + event->data.data32[1] != A__NET_WM_STATE_DEMANDS_ATTENTION && + event->data.data32[1] != A__NET_WM_STATE_STICKY)) { DLOG("Unknown atom in clientmessage of type %d\n", event->data.data32[1]); return; } @@ -725,6 +726,16 @@ static void handle_client_message(xcb_client_message_event_t *event) { con_set_urgency(con, false); else if (event->data.data32[0] == _NET_WM_STATE_TOGGLE) con_set_urgency(con, !con->urgent); + } else if (event->data.data32[1] == A__NET_WM_STATE_STICKY) { + DLOG("Received a client message to modify _NET_WM_STATE_STICKY.\n"); + if (event->data.data32[0] == _NET_WM_STATE_ADD) + con->sticky = true; + else if (event->data.data32[0] == _NET_WM_STATE_REMOVE) + con->sticky = false; + else if (event->data.data32[0] == _NET_WM_STATE_TOGGLE) + con->sticky = !con->sticky; + + DLOG("New sticky status for con = %p is %i.\n", con, con->sticky); } tree_render(); diff --git a/src/ipc.c b/src/ipc.c index ad7ef1cb..1a8d28ed 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -436,6 +436,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("fullscreen_mode"); y(integer, con->fullscreen_mode); + ystr("sticky"); + y(bool, con->sticky); + ystr("floating"); switch (con->floating) { case FLOATING_AUTO_OFF: diff --git a/src/load_layout.c b/src/load_layout.c index 5a139bf2..e0dc4fa0 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -435,6 +435,9 @@ static int json_bool(void *ctx, int val) { to_focus = json_node; } + if (strcasecmp(last_key, "sticky") == 0) + json_node->sticky = val; + if (parsing_swallows) { if (strcasecmp(last_key, "restart_mode") == 0) current_swallow->restart_mode = val; diff --git a/src/manage.c b/src/manage.c index 08e11b57..e3769670 100644 --- a/src/manage.c +++ b/src/manage.c @@ -384,6 +384,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki want_floating = true; } + if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_STICKY)) + nc->sticky = true; + FREE(state_reply); FREE(type_reply); diff --git a/src/workspace.c b/src/workspace.c index 70022151..2b2c3cbf 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -450,6 +450,45 @@ static void _workspace_show(Con *workspace) { /* Update the EWMH hints */ ewmh_update_current_desktop(); + + /* Floating containers which are sticky need to be moved to the new workspace, + * but only on the same output. */ + if (current != NULL && old_output == new_output) { + Con *prev = focused; + Con *child; + + /* We can't simply iterate over the floating containers since moving a + * sticky container to the target workspace will modify that list. + * Instead, we first count the number of sticky containers, then memorize + * all of them and finally loop over that list to move them to the new + * workspace. */ + int num_sticky = 0; + TAILQ_FOREACH(child, &(current->floating_head), floating_windows) { + if (con_is_sticky(child)) + num_sticky++; + } + + Con *sticky_cons[num_sticky]; + int ctr = 0; + TAILQ_FOREACH(child, &(current->floating_head), floating_windows) { + if (con_is_sticky(child)) + sticky_cons[ctr++] = child; + } + + for (int i = 0; i < num_sticky; i++) { + con_move_to_workspace(sticky_cons[i], workspace, true, false); + + /* We want sticky containers to be at the end of the focus head. */ + TAILQ_REMOVE(&(workspace->focus_head), sticky_cons[i], focused); + TAILQ_INSERT_TAIL(&(workspace->focus_head), sticky_cons[i], focused); + } + + /* Focus the correct container since moving the sticky containers + * changed the focus. However, if no container was focused before, + * we can leave the focus at the sticky container. */ + if (prev != croot) + con_focus(prev); + } } /* diff --git a/testcases/t/116-nestedcons.t b/testcases/t/116-nestedcons.t index d9ff1c39..5e3110ad 100644 --- a/testcases/t/116-nestedcons.t +++ b/testcases/t/116-nestedcons.t @@ -47,6 +47,7 @@ my $ignore = \""; my $expected = { fullscreen_mode => 0, + sticky => $ignore, nodes => $ignore, window => undef, name => 'root',