From: Ingo Bürk Date: Sun, 23 Aug 2015 11:36:12 +0000 (+0200) Subject: Handle _NET_WM_STATE_STICKY, but only for floating containers. If this atom is set... X-Git-Tag: 4.11~18^2~3 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=2c338b6ae25b5b9aeb51341b26adc82dfb49e913;p=i3%2Fi3 Handle _NET_WM_STATE_STICKY, but only for floating containers. If this atom is set, the floating window will always be automatically moved to the currently active workspace of the output that it is on. This is the equivalent of a sticky note stuck to the monitor. We will respect this atom upon managing a window as well as when we receive a request that changes the sticky state. fixes #1455 --- 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',