diff --git a/src/tests/meson.build b/src/tests/meson.build index 1a196f0fa..eadef4173 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -114,8 +114,10 @@ stacking_tests = [ 'closed-transient-no-input-no-take-focus-parent', 'closed-transient-no-input-no-take-focus-parents', 'closed-transient-no-input-parent', - 'closed-transient-no-input-parents', 'closed-transient-no-input-parent-delayed-focus-default-cancelled', + 'closed-transient-no-input-parents', + 'closed-transient-no-input-parents-queued-default-focus-destroyed', + 'closed-transient-only-take-focus-parents', 'minimized', 'mixed-windows', 'set-parent', diff --git a/src/tests/stacking/closed-transient-no-input-parent.metatest b/src/tests/stacking/closed-transient-no-input-parent.metatest index e0f1dc1e2..d0f3228d5 100644 --- a/src/tests/stacking/closed-transient-no-input-parent.metatest +++ b/src/tests/stacking/closed-transient-no-input-parent.metatest @@ -25,6 +25,6 @@ dispatch assert_focused none assert_stacking 2/1 1/1 1/2 -sleep 250 +sleep 150 assert_focused 1/1 assert_stacking 2/1 1/1 1/2 diff --git a/src/tests/stacking/closed-transient-no-input-parents-queued-default-focus-destroyed.metatest b/src/tests/stacking/closed-transient-no-input-parents-queued-default-focus-destroyed.metatest new file mode 100644 index 000000000..49ecc510f --- /dev/null +++ b/src/tests/stacking/closed-transient-no-input-parents-queued-default-focus-destroyed.metatest @@ -0,0 +1,43 @@ +new_client 0 x11 +create 0/1 +show 0/1 + +new_client 1 x11 +create 1/1 +show 1/1 + +create 1/2 csd +set_parent 1/2 1 +accept_focus 1/2 false +show 1/2 + +create 1/3 csd +set_parent 1/3 2 +accept_focus 1/3 false +show 1/3 + +create 1/4 csd +set_parent 1/4 3 +accept_focus 1/4 false +show 1/4 + +create 1/5 csd +set_parent 1/5 3 +show 1/5 + +wait +assert_focused 1/5 +assert_stacking 0/1 1/1 1/2 1/3 1/4 1/5 + +destroy 1/5 +dispatch + +assert_focused none +assert_stacking 0/1 1/1 1/2 1/3 1/4 + +destroy 1/2 +dispatch + +sleep 450 +assert_focused 1/1 +assert_stacking 0/1 1/1 1/3 1/4 diff --git a/src/tests/stacking/closed-transient-no-input-parents.metatest b/src/tests/stacking/closed-transient-no-input-parents.metatest index e3ec2e84a..ee9984192 100644 --- a/src/tests/stacking/closed-transient-no-input-parents.metatest +++ b/src/tests/stacking/closed-transient-no-input-parents.metatest @@ -35,12 +35,12 @@ dispatch assert_focused none assert_stacking 0/1 1/1 1/2 1/3 1/4 -sleep 250 -assert_focused none +sleep 600 +assert_focused 1/1 assert_stacking 0/1 1/1 1/2 1/3 1/4 destroy 1/3 wait -assert_focused none +assert_focused 1/1 assert_stacking 0/1 1/1 1/2 1/4 diff --git a/src/tests/stacking/closed-transient-only-take-focus-parents.metatest b/src/tests/stacking/closed-transient-only-take-focus-parents.metatest new file mode 100644 index 000000000..8aa86700f --- /dev/null +++ b/src/tests/stacking/closed-transient-only-take-focus-parents.metatest @@ -0,0 +1,34 @@ +new_client 0 x11 +create 0/1 +show 0/1 + +new_client 1 x11 +create 1/1 +accept_focus 1/1 false +can_take_focus 1/1 true +accept_take_focus 1/1 true +show 1/1 + +create 1/2 csd +set_parent 1/2 1 +accept_focus 1/2 false +can_take_focus 1/2 true +accept_take_focus 1/2 true +show 1/2 + +create 1/3 +set_parent 1/3 2 +show 1/3 + +assert_focused 1/3 +assert_stacking 0/1 1/1 1/2 1/3 + +destroy 1/3 +wait + +assert_focused 1/2 +assert_stacking 0/1 1/1 1/2 + +sleep 150 +assert_focused 1/2 +assert_stacking 0/1 1/1 1/2 diff --git a/src/x11/window-x11.c b/src/x11/window-x11.c index 8450fdd77..477d39628 100644 --- a/src/x11/window-x11.c +++ b/src/x11/window-x11.c @@ -50,7 +50,7 @@ #include "x11/window-props.h" #include "x11/xprops.h" -#define TAKE_FOCUS_FALLBACK_DELAY_MS 250 +#define TAKE_FOCUS_FALLBACK_DELAY_MS 150 enum _MetaGtkEdgeConstraints { @@ -66,6 +66,11 @@ enum _MetaGtkEdgeConstraints G_DEFINE_TYPE_WITH_PRIVATE (MetaWindowX11, meta_window_x11, META_TYPE_WINDOW) +static void +meta_window_x11_maybe_focus_delayed (MetaWindow *window, + GQueue *other_focus_candidates, + guint32 timestamp); + static void meta_window_x11_init (MetaWindowX11 *window_x11) { @@ -781,22 +786,58 @@ request_take_focus (MetaWindow *window, typedef struct { MetaWindow *window; + GQueue *pending_focus_candidates; guint32 timestamp; guint timeout_id; gulong unmanaged_id; gulong focused_changed_id; } MetaWindowX11DelayedFocusData; +static void +disconnect_pending_focus_window_signals (MetaWindow *window, + GQueue *focus_candidates) +{ + g_signal_handlers_disconnect_by_func (window, g_queue_remove, + focus_candidates); +} + static void meta_window_x11_delayed_focus_data_free (MetaWindowX11DelayedFocusData *data) { g_signal_handler_disconnect (data->window, data->unmanaged_id); g_signal_handler_disconnect (data->window->display, data->focused_changed_id); + if (data->pending_focus_candidates) + { + g_queue_foreach (data->pending_focus_candidates, + (GFunc) disconnect_pending_focus_window_signals, + data->pending_focus_candidates); + g_queue_free (data->pending_focus_candidates); + } + g_clear_handle_id (&data->timeout_id, g_source_remove); g_free (data); } +static void +focus_candidates_maybe_take_and_focus_next (GQueue **focus_candidates_ptr, + guint32 timestamp) +{ + MetaWindow *focus_window; + GQueue *focus_candidates; + + g_assert (*focus_candidates_ptr); + + if (g_queue_is_empty (*focus_candidates_ptr)) + return; + + focus_candidates = g_steal_pointer (focus_candidates_ptr); + focus_window = g_queue_pop_head (focus_candidates); + + disconnect_pending_focus_window_signals (focus_window, focus_candidates); + meta_window_x11_maybe_focus_delayed (focus_window, focus_candidates, timestamp); +} + static gboolean focus_window_delayed_timeout (gpointer user_data) { @@ -804,6 +845,9 @@ focus_window_delayed_timeout (gpointer user_data) MetaWindow *window = data->window; guint32 timestamp = data->timestamp; + focus_candidates_maybe_take_and_focus_next (&data->pending_focus_candidates, + timestamp); + data->timeout_id = 0; meta_window_x11_delayed_focus_data_free (data); @@ -814,6 +858,7 @@ focus_window_delayed_timeout (gpointer user_data) static void meta_window_x11_maybe_focus_delayed (MetaWindow *window, + GQueue *other_focus_candidates, guint32 timestamp) { MetaWindowX11DelayedFocusData *data; @@ -821,6 +866,10 @@ meta_window_x11_maybe_focus_delayed (MetaWindow *window, data = g_new0 (MetaWindowX11DelayedFocusData, 1); data->window = window; data->timestamp = timestamp; + data->pending_focus_candidates = other_focus_candidates; + + meta_topic (META_DEBUG_FOCUS, + "Requesting delayed focus to %s\n", window->desc); data->unmanaged_id = g_signal_connect_swapped (window, "unmanaged", @@ -836,6 +885,50 @@ meta_window_x11_maybe_focus_delayed (MetaWindow *window, focus_window_delayed_timeout, data); } +static void +maybe_focus_default_window (MetaWorkspace *workspace, + MetaWindow *not_this_one, + guint32 timestamp) +{ + MetaStack *stack = workspace->display->stack; + g_autoptr (GList) focusable_windows = NULL; + g_autoptr (GQueue) focus_candidates = NULL; + GList *l; + + /* Go through all the focusable windows and try to focus them + * in order, waiting for a delay. The first one that replies to + * the request (in case of take focus windows) changing the display + * focused window, will stop the chained requests. + */ + focusable_windows = + meta_stack_get_default_focus_candidates (stack, workspace); + focus_candidates = g_queue_new (); + + for (l = g_list_last (focusable_windows); l; l = l->prev) + { + MetaWindow *focus_window = l->data; + + if (focus_window == not_this_one) + continue; + + g_queue_push_tail (focus_candidates, focus_window); + g_signal_connect_swapped (focus_window, "unmanaged", + G_CALLBACK (g_queue_remove), + focus_candidates); + + if (!META_IS_WINDOW_X11 (focus_window)) + break; + + if (focus_window->input) + break; + + if (focus_window->shaded && focus_window->frame) + break; + } + + focus_candidates_maybe_take_and_focus_next (&focus_candidates, timestamp); +} + static void meta_window_x11_focus (MetaWindow *window, guint32 timestamp) @@ -894,30 +987,9 @@ meta_window_x11_focus (MetaWindow *window, if (window->display->focus_window != NULL && window->display->focus_window->unmanaging) { - MetaWindow *focus_window = window; - MetaWorkspace *workspace = window->workspace; - MetaStack *stack = workspace->display->stack; - - while (TRUE) - { - focus_window = meta_stack_get_default_focus_window (stack, - workspace, - focus_window); - if (!focus_window) - break; - - if (meta_window_is_focusable (focus_window)) - break; - - if (focus_window->shaded && focus_window->frame) - break; - } - meta_display_unset_input_focus (window->display, timestamp); - - if (focus_window) - meta_window_x11_maybe_focus_delayed (focus_window, - timestamp); + maybe_focus_default_window (window->workspace, window, + timestamp); } }