From 1c4c3f06fa6ae8f0c4a445bc6f940e43081d40f6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ingo=20B=C3=BCrk?= Date: Wed, 26 Aug 2015 21:06:53 +0200 Subject: [PATCH] Make sure sticky windows pop to the front if they get sticky while not being on a visible workspace. This commit also reworks the way focusing sticky windows is prevented by not focusing them temporarily at all, but preventing the focus in the first place. --- include/con.h | 6 +++++- include/output.h | 7 +++++++ src/commands.c | 15 ++++++++++----- src/con.c | 27 ++++++++++++++++----------- src/floating.c | 2 +- src/handlers.c | 1 + src/output.c | 35 +++++++++++++++++++++++++++++++++++ src/scratchpad.c | 6 +++--- src/workspace.c | 40 ++-------------------------------------- testcases/t/251-sticky.t | 14 ++++++++++++++ 10 files changed, 94 insertions(+), 59 deletions(-) diff --git a/include/con.h b/include/con.h index c0a3a46b..cf55978d 100644 --- a/include/con.h +++ b/include/con.h @@ -218,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/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/src/commands.c b/src/commands.c index c8164da6..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; @@ -1597,6 +1597,11 @@ void cmd_sticky(I3_CMD, char *action) { 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); } diff --git a/src/con.c b/src/con.c index 24d563a5..7a8ccd58 100644 --- a/src/con.c +++ b/src/con.c @@ -741,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. */ @@ -763,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. */ @@ -823,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 @@ -858,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. */ @@ -877,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, @@ -942,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; } @@ -959,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); } /* @@ -976,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); @@ -989,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/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 4b01cb5e..d0b66374 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -736,6 +736,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { 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/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 2b2c3cbf..04053e90 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -451,44 +451,8 @@ 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); - } + /* Push any sticky windows to the now visible workspace. */ + output_push_sticky_windows(); } /* diff --git a/testcases/t/251-sticky.t b/testcases/t/251-sticky.t index ada8c9ac..6729556d 100644 --- a/testcases/t/251-sticky.t +++ b/testcases/t/251-sticky.t @@ -77,6 +77,20 @@ 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; -- 2.39.5