diff --git a/docs/userguide b/docs/userguide index 5e148ff5..d1660c49 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1834,6 +1834,27 @@ bindsym $mod+c move absolute position center bindsym $mod+m move position mouse ------------------------------------------------------- +=== Sticky floating windows + +If you want a window to stick to the glass, i.e., have it stay on screen even +if you switch to another workspace, you can use the +sticky+ command. For +example, this can be useful for notepads, a media player or a video chat +window. + +Note that while any window can be made sticky through this command, it will +only take effect if the window is floating. + +*Syntax*: +---------------------------- +sticky enable|disable|toggle +---------------------------- + +*Examples*: +------------------------------------------------------ +# make a terminal sticky that was started as a notepad +for_window [instance=notepad] sticky enable +------------------------------------------------------ + === Changing (named) workspaces/moving to workspaces To change to a specific workspace, use the +workspace+ command, followed by the 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/commands.h b/include/commands.h index b243b9e0..80b0efb0 100644 --- a/include/commands.h +++ b/include/commands.h @@ -204,6 +204,12 @@ void cmd_focus(I3_CMD); */ void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode); +/** + * Implementation of 'sticky enable|disable|toggle'. + * + */ +void cmd_sticky(I3_CMD, char *action); + /** * Implementation of 'move [ [px]]'. * diff --git a/include/con.h b/include/con.h index 4813b776..cf55978d 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. * @@ -212,10 +218,14 @@ void con_disable_fullscreen(Con *con); * The dont_warp flag disables pointer warping and will be set when this * function is called while dragging a floating window. * + * If ignore_focus is set, the container will be moved without modifying focus + * at all. + * * TODO: is there a better place for this function? * */ -void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp); +void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, + bool dont_warp, bool ignore_focus); /** * Moves the given container to the given mark. 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/include/ewmh.h b/include/ewmh.h index d94b7f3a..7ed9b544 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -68,6 +68,12 @@ void ewmh_update_client_list(xcb_window_t *list, int num_windows); */ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows); +/** + * Set or remove _NET_WM_STATE_STICKY on the window. + * + */ +void ewmh_update_sticky(xcb_window_t window, bool sticky); + /** * Set up the EWMH hints on the root window. * diff --git a/include/output.h b/include/output.h index e0125c06..38e2689a 100644 --- a/include/output.h +++ b/include/output.h @@ -21,3 +21,10 @@ Con *output_get_content(Con *output); * */ Output *get_output_from_string(Output *current_output, const char *output_str); + +/** + * Iterates over all outputs and pushes sticky windows to the currently visible + * workspace on that output. + * + */ +void output_push_sticky_windows(void); diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index f5fb9884..5e2bfd8f 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -29,6 +29,7 @@ state INITIAL: 'kill' -> KILL 'open' -> call cmd_open() 'fullscreen' -> FULLSCREEN + 'sticky' -> STICKY 'split' -> SPLIT 'floating' -> FLOATING 'mark' -> MARK @@ -183,6 +184,11 @@ state FULLSCREEN_COMPAT: end -> call cmd_fullscreen("toggle", "output") +# sticky enable|disable|toggle +state STICKY: + action = 'enable', 'disable', 'toggle' + -> call cmd_sticky($action) + # split v|h|vertical|horizontal state SPLIT: direction = 'horizontal', 'vertical', 'v', 'h' diff --git a/src/commands.c b/src/commands.c index 443edf4a..81896047 100644 --- a/src/commands.c +++ b/src/commands.c @@ -461,7 +461,7 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false); + con_move_to_workspace(current->con, ws, true, false, false); } cmd_output->needs_tree_render = true; @@ -488,7 +488,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false); + con_move_to_workspace(current->con, ws, true, false, false); } cmd_output->needs_tree_render = true; @@ -532,7 +532,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false); + con_move_to_workspace(current->con, ws, true, false, false); } cmd_output->needs_tree_render = true; @@ -583,7 +583,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, workspace, true, false); + con_move_to_workspace(current->con, workspace, true, false, false); } cmd_output->needs_tree_render = true; @@ -1223,7 +1223,7 @@ void cmd_move_con_to_output(I3_CMD, char *name) { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false); + con_move_to_workspace(current->con, ws, true, false, false); } cmd_output->needs_tree_render = true; @@ -1569,6 +1569,42 @@ void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode) { ysuccess(true); } +/* + * Implementation of 'sticky enable|disable|toggle'. + * + */ +void cmd_sticky(I3_CMD, char *action) { + DLOG("%s sticky on window\n", action); + HANDLE_EMPTY_MATCH; + + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + if (current->con->window == NULL) { + ELOG("only containers holding a window can be made sticky, skipping con = %p\n", current->con); + continue; + } + DLOG("setting sticky for container = %p / %s\n", current->con, current->con->name); + + bool sticky = false; + if (strcmp(action, "enable") == 0) + sticky = true; + else if (strcmp(action, "disable") == 0) + sticky = false; + else if (strcmp(action, "toggle") == 0) + sticky = !current->con->sticky; + + current->con->sticky = sticky; + ewmh_update_sticky(current->con->window->id, sticky); + } + + /* A window we made sticky might not be on a visible workspace right now, so we need to make + * sure it gets pushed to the front now. */ + output_push_sticky_windows(); + + cmd_output->needs_tree_render = true; + ysuccess(true); +} + /* * Implementation of 'move [ [px]]'. * diff --git a/src/con.c b/src/con.c index 9a5d36c1..7a8ccd58 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). @@ -724,7 +741,7 @@ void con_disable_fullscreen(Con *con) { con_set_fullscreen_mode(con, CF_NONE); } -static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fix_coordinates, bool dont_warp) { +static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fix_coordinates, bool dont_warp, bool ignore_focus) { Con *orig_target = target; /* Prevent moving if this would violate the fullscreen focus restrictions. */ @@ -746,7 +763,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi Con *child; while (!TAILQ_EMPTY(&(source_ws->floating_head))) { child = TAILQ_FIRST(&(source_ws->floating_head)); - con_move_to_workspace(child, target_ws, true, true); + con_move_to_workspace(child, target_ws, true, true, false); } /* If there are no non-floating children, ignore the workspace. */ @@ -806,7 +823,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* If moving to a visible workspace, call show so it can be considered * focused. Must do before attaching because workspace_show checks to see * if focused container is in its area. */ - if (workspace_is_visible(target_ws)) { + if (!ignore_focus && workspace_is_visible(target_ws)) { workspace_show(target_ws); /* Don’t warp if told so (when dragging floating windows with the @@ -841,8 +858,9 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi * workspace, that is, don’t move focus away if the target workspace is * invisible. * We don’t focus the con for i3 pseudo workspaces like __i3_scratch and - * we don’t focus when there is a fullscreen con on that workspace. */ - if (!con_is_internal(target_ws) && !fullscreen) { + * we don’t focus when there is a fullscreen con on that workspace. We + * also don't do it if the caller requested to ignore focus. */ + if (!ignore_focus && !con_is_internal(target_ws) && !fullscreen) { /* We need to save the focused workspace on the output in case the * new workspace is hidden and it's necessary to immediately switch * back to the originally-focused workspace. */ @@ -860,11 +878,12 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* Descend focus stack in case focus_next is a workspace which can * occur if we move to the same workspace. Also show current workspace * to ensure it is focused. */ - workspace_show(current_ws); + if (!ignore_focus) + workspace_show(current_ws); /* Set focus only if con was on current workspace before moving. * Otherwise we would give focus to some window on different workspace. */ - if (source_ws == current_ws) + if (!ignore_focus && source_ws == current_ws) con_focus(con_descend_focused(focus_next)); /* 8. If anything within the container is associated with a startup sequence, @@ -925,7 +944,7 @@ bool con_move_to_mark(Con *con, const char *mark) { /* For floating target containers, we just send the window to the same workspace. */ if (con_is_floating(target)) { DLOG("target container is floating, moving container to target's workspace.\n"); - con_move_to_workspace(con, con_get_workspace(target), true, false); + con_move_to_workspace(con, con_get_workspace(target), true, false, false); return true; } @@ -942,7 +961,7 @@ bool con_move_to_mark(Con *con, const char *mark) { return false; } - return _con_move_to_con(con, target, false, true, false); + return _con_move_to_con(con, target, false, true, false, false); } /* @@ -959,10 +978,13 @@ bool con_move_to_mark(Con *con, const char *mark) { * The dont_warp flag disables pointer warping and will be set when this * function is called while dragging a floating window. * + * If ignore_focus is set, the container will be moved without modifying focus + * at all. + * * TODO: is there a better place for this function? * */ -void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp) { +void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp, bool ignore_focus) { assert(workspace->type == CT_WORKSPACE); Con *source_ws = con_get_workspace(con); @@ -972,7 +994,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool } Con *target = con_descend_focused(workspace); - _con_move_to_con(con, target, true, fix_coordinates, dont_warp); + _con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus); } /* diff --git a/src/ewmh.c b/src/ewmh.c index d60bbb50..eb6a6ea6 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -213,6 +213,20 @@ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows) { stack); } +/* + * Set or remove _NET_WM_STATE_STICKY on the window. + * + */ +void ewmh_update_sticky(xcb_window_t window, bool sticky) { + uint32_t values[1]; + unsigned int num = 0; + + if (sticky) + values[num++] = A__NET_WM_STATE_STICKY; + + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, A__NET_WM_STATE, XCB_ATOM_ATOM, 32, num, values); +} + /* * Set up the EWMH hints on the root window. * @@ -250,7 +264,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/floating.c b/src/floating.c index 0a510f67..a9da2c70 100644 --- a/src/floating.c +++ b/src/floating.c @@ -412,7 +412,7 @@ bool floating_maybe_reassign_ws(Con *con) { Con *content = output_get_content(output->con); Con *ws = TAILQ_FIRST(&(content->focus_head)); DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name); - con_move_to_workspace(con, ws, false, true); + con_move_to_workspace(con, ws, false, true, false); con_focus(con_descend_focused(con)); return true; } diff --git a/src/handlers.c b/src/handlers.c index f3c2350e..d0b66374 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,17 @@ 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); + output_push_sticky_windows(); } 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/output.c b/src/output.c index ec5d5f47..e29ab746 100644 --- a/src/output.c +++ b/src/output.c @@ -46,3 +46,38 @@ Output *get_output_from_string(Output *current_output, const char *output_str) { return output; } + +/* + * Iterates over all outputs and pushes sticky windows to the currently visible + * workspace on that output. + * + */ +void output_push_sticky_windows(void) { + Con *output; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *workspace, *visible_ws = NULL; + GREP_FIRST(visible_ws, output_get_content(output), workspace_is_visible(child)); + + /* We use this loop instead of TAILQ_FOREACH to avoid problems if the + * sticky window was the last window on that workspace as moving it in + * this case will close the workspace. */ + for (workspace = TAILQ_FIRST(&(output_get_content(output)->nodes_head)); + workspace != TAILQ_END(&(output_get_content(output)->nodes_head));) { + Con *current_ws = workspace; + workspace = TAILQ_NEXT(workspace, nodes); + + /* Since moving the windows actually removes them from the list of + * floating windows on this workspace, here too we need to use + * another loop than TAILQ_FOREACH. */ + Con *child; + for (child = TAILQ_FIRST(&(current_ws->floating_head)); + child != TAILQ_END(&(current_ws->floating_head));) { + Con *current = child; + child = TAILQ_NEXT(child, floating_windows); + + if (con_is_sticky(current)) + con_move_to_workspace(current, visible_ws, true, false, true); + } + } + } +} diff --git a/src/scratchpad.c b/src/scratchpad.c index 06a7cc73..6d83a558 100644 --- a/src/scratchpad.c +++ b/src/scratchpad.c @@ -60,7 +60,7 @@ void scratchpad_move(Con *con) { /* 2: Send the window to the __i3_scratch workspace, mainting its * coordinates and not warping the pointer. */ - con_move_to_workspace(con, __i3_scratch, true, true); + con_move_to_workspace(con, __i3_scratch, true, true, false); /* 3: If this is the first time this window is used as a scratchpad, we set * the scratchpad_state to SCRATCHPAD_FRESH. The window will then be @@ -142,7 +142,7 @@ void scratchpad_show(Con *con) { floating->scratchpad_state != SCRATCHPAD_NONE) { DLOG("Found a visible scratchpad window on another workspace,\n"); DLOG("moving it to this workspace: con = %p\n", walk_con); - con_move_to_workspace(walk_con, focused_ws, true, false); + con_move_to_workspace(walk_con, focused_ws, true, false, false); return; } } @@ -189,7 +189,7 @@ void scratchpad_show(Con *con) { } /* 1: Move the window from __i3_scratch to the current workspace. */ - con_move_to_workspace(con, active, true, false); + con_move_to_workspace(con, active, true, false, false); /* 2: Adjust the size if this window was not adjusted yet. */ if (con->scratchpad_state == SCRATCHPAD_FRESH) { diff --git a/src/workspace.c b/src/workspace.c index 70022151..04053e90 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -450,6 +450,9 @@ static void _workspace_show(Con *workspace) { /* Update the EWMH hints */ ewmh_update_current_desktop(); + + /* Push any sticky windows to the now visible workspace. */ + output_push_sticky_windows(); } /* 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', diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index fc7fa882..8aff1f6d 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -144,7 +144,7 @@ is(parser_calls("\nworkspace test"), ################################################################################ is(parser_calls('unknown_literal'), - "ERROR: Expected one of these tokens: , '[', 'move', 'exec', 'exit', 'restart', 'reload', 'shmlog', 'debuglog', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'unmark', 'resize', 'rename', 'nop', 'scratchpad', 'title_format', 'mode', 'bar'\n" . + "ERROR: Expected one of these tokens: , '[', 'move', 'exec', 'exit', 'restart', 'reload', 'shmlog', 'debuglog', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'sticky', 'split', 'floating', 'mark', 'unmark', 'resize', 'rename', 'nop', 'scratchpad', 'title_format', 'mode', 'bar'\n" . "ERROR: Your command: unknown_literal\n" . "ERROR: ^^^^^^^^^^^^^^^", 'error for unknown literal ok'); diff --git a/testcases/t/251-sticky.t b/testcases/t/251-sticky.t new file mode 100644 index 00000000..6729556d --- /dev/null +++ b/testcases/t/251-sticky.t @@ -0,0 +1,96 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests sticky windows. +# Ticket: #1455 +use i3test; + +my ($ws, $focused); + +############################################################################### +# 1: Given a sticky tiling container, when the workspace is switched, then +# nothing happens. +############################################################################### +fresh_workspace; +open_window(wm_class => 'findme'); +cmd 'sticky enable'; +$ws = fresh_workspace; + +is(@{get_ws($ws)->{nodes}}, 0, 'tiling sticky container did not move'); +is(@{get_ws($ws)->{floating_nodes}}, 0, 'tiling sticky container did not move'); +cmd '[class="findme"] kill'; + +############################################################################### +# 2: Given a sticky floating container, when the workspace is switched, then +# the container moves to the new workspace. +############################################################################### +$ws = fresh_workspace; +open_floating_window(wm_class => 'findme'); +$focused = get_focused($ws); +cmd 'sticky enable'; +$ws = fresh_workspace; + +is(@{get_ws($ws)->{floating_nodes}}, 1, 'floating sticky container moved to new workspace'); +is(get_focused($ws), $focused, 'sticky container has focus'); +cmd '[class="findme"] kill'; + +############################################################################### +# 3: Given two sticky floating containers, when the workspace is switched, +# then both containers move to the new workspace. +############################################################################### +fresh_workspace; +open_floating_window(wm_class => 'findme'); +cmd 'sticky enable'; +open_floating_window(wm_class => 'findme'); +cmd 'sticky enable'; +$ws = fresh_workspace; + +is(@{get_ws($ws)->{floating_nodes}}, 2, 'multiple sticky windows can be used at the same time'); +cmd '[class="findme"] kill'; + +############################################################################### +# 4: Given a sticky floating container and a tiling container on the target +# workspace, when the workspace is switched, then the tiling container is +# focused. +############################################################################### +$ws = fresh_workspace; +open_window; +$focused = get_focused($ws); +fresh_workspace; +open_floating_window(wm_class => 'findme'); +cmd 'sticky enable'; +cmd 'workspace ' . $ws; + +is(get_focused($ws), $focused, 'the tiling container has focus'); +cmd '[class="findme"] kill'; + +############################################################################### +# 5: Given a floating container on a non-visible workspace, when the window +# is made sticky, then the window immediately jumps to the currently +# visible workspace. +############################################################################### +fresh_workspace; +open_floating_window(wm_class => 'findme'); +cmd 'mark sticky'; +$ws = fresh_workspace; +cmd '[con_mark=sticky] sticky enable'; + +is(@{get_ws($ws)->{floating_nodes}}, 1, 'the sticky window jumps to the front'); +cmd '[class="findme"] kill'; + +############################################################################### + +done_testing;