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
This commit is contained in:
parent
4d6f8b1329
commit
2c338b6ae2
@ -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)
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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.
|
||||
|
17
src/con.c
17
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).
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -47,6 +47,7 @@ my $ignore = \"";
|
||||
|
||||
my $expected = {
|
||||
fullscreen_mode => 0,
|
||||
sticky => $ignore,
|
||||
nodes => $ignore,
|
||||
window => undef,
|
||||
name => 'root',
|
||||
|
Loading…
Reference in New Issue
Block a user