From 73990f011f41865588bc5a6ccb4d7c11ecd61b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Wed, 17 Apr 2024 16:38:25 +0200 Subject: [PATCH] window: Set the initial suspend state to 'hidden' Instead of initializing to 'suspended', which will send the `SUSPENDED` xdg_toplevel state, set it to hidden at first. If the window is placed on an inactive workspace, it'll eventually enter the 'suspended' state, but will have had some time in non-suspended state to get map, even if not visibly. This fixes inital suspended state when mapping a window maximized. Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/3229 Part-of: --- src/core/window.c | 31 +++- src/tests/wayland-test-clients/meson.build | 3 + .../xdg-toplevel-suspended.c | 175 ++++++++++++++++++ src/tests/wayland-unit-tests.c | 69 +++++++ 4 files changed, 269 insertions(+), 9 deletions(-) create mode 100644 src/tests/wayland-test-clients/xdg-toplevel-suspended.c diff --git a/src/core/window.c b/src/core/window.c index d54d73d40..1411d355c 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -168,6 +168,8 @@ static MetaWindow * meta_window_find_tile_match (MetaWindow *window, MetaTileMode mode); static void update_edge_constraints (MetaWindow *window); +static void set_hidden_suspended_state (MetaWindow *window); + static void initable_iface_init (GInitableIface *initable_iface); typedef struct _MetaWindowPrivate @@ -713,9 +715,6 @@ meta_window_class_init (MetaWindowClass *klass) static void meta_window_init (MetaWindow *window) { - MetaWindowPrivate *priv = meta_window_get_instance_private (window); - - priv->suspend_state = META_WINDOW_SUSPEND_STATE_SUSPENDED; window->stamp = next_window_stamp++; meta_prefs_add_listener (prefs_changed_callback, window); window->is_alive = TRUE; @@ -991,6 +990,7 @@ static void meta_window_constructed (GObject *object) { MetaWindow *window = META_WINDOW (object); + MetaWindowPrivate *priv = meta_window_get_instance_private (window); MetaDisplay *display = window->display; MetaContext *context = meta_display_get_context (display); MetaBackend *backend = meta_context_get_backend (context); @@ -1343,6 +1343,11 @@ meta_window_constructed (GObject *object) !window->initially_iconic) unminimize_window_and_all_transient_parents (window); + /* There is a slim chance we'll hit time out before a extremely slow client + * managed to become active, but unlikely enough. */ + priv->suspend_state = META_WINDOW_SUSPEND_STATE_HIDDEN; + set_hidden_suspended_state (window); + window->constructing = FALSE; } @@ -2164,6 +2169,19 @@ enter_suspend_state_cb (gpointer user_data) return G_SOURCE_REMOVE; } +static void +set_hidden_suspended_state (MetaWindow *window) +{ + MetaWindowPrivate *priv = meta_window_get_instance_private (window); + + priv->suspend_state = META_WINDOW_SUSPEND_STATE_HIDDEN; + g_return_if_fail (!priv->suspend_timoeut_id); + priv->suspend_timoeut_id = + g_timeout_add_seconds (SUSPEND_HIDDEN_TIMEOUT_S, + enter_suspend_state_cb, + window); +} + static void update_suspend_state (MetaWindow *window) { @@ -2181,13 +2199,8 @@ update_suspend_state (MetaWindow *window) } else if (priv->suspend_state == META_WINDOW_SUSPEND_STATE_ACTIVE) { - priv->suspend_state = META_WINDOW_SUSPEND_STATE_HIDDEN; + set_hidden_suspended_state (window); g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_SUSPEND_STATE]); - g_return_if_fail (!priv->suspend_timoeut_id); - priv->suspend_timoeut_id = - g_timeout_add_seconds (SUSPEND_HIDDEN_TIMEOUT_S, - enter_suspend_state_cb, - window); } } diff --git a/src/tests/wayland-test-clients/meson.build b/src/tests/wayland-test-clients/meson.build index 38ac799d0..1cdf1e62e 100644 --- a/src/tests/wayland-test-clients/meson.build +++ b/src/tests/wayland-test-clients/meson.build @@ -81,6 +81,9 @@ wayland_test_clients = [ { 'name': 'xdg-toplevel-bounds', }, + { + 'name': 'xdg-toplevel-suspended', + }, { 'name': 'ycbcr', }, diff --git a/src/tests/wayland-test-clients/xdg-toplevel-suspended.c b/src/tests/wayland-test-clients/xdg-toplevel-suspended.c new file mode 100644 index 000000000..4e58d2674 --- /dev/null +++ b/src/tests/wayland-test-clients/xdg-toplevel-suspended.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2024 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "config.h" + +#include +#include + +#include "wayland-test-client-utils.h" + +enum +{ + XDG_TOPLEVEL_SUSPENDED_COMMAND_NEXT_WORKSPACE = 0, + XDG_TOPLEVEL_SUSPENDED_COMMAND_PREV_WORKSPACE = 1, + XDG_TOPLEVEL_SUSPENDED_COMMAND_ACTIVATE_WINDOW = 2, +}; + +static void +wait_for_state (WaylandSurface *surface, + enum xdg_toplevel_state state) +{ + while (!wayland_surface_has_state (surface, state)) + wayland_display_dispatch (surface->display); +} + +static void +wait_for_no_state (WaylandSurface *surface, + enum xdg_toplevel_state state) +{ + while (wayland_surface_has_state (surface, state)) + wayland_display_dispatch (surface->display); +} + +static void +test_floating (WaylandDisplay *display) +{ + g_autoptr (WaylandSurface) surface = NULL; + + g_debug ("Testing suspended state when mapping floating"); + + surface = wayland_surface_new (display, __func__, 100, 100, 0xffffffff); + wl_surface_commit (surface->wl_surface); + + wait_for_window_shown (display, surface->wl_surface); + g_assert_false (wayland_surface_has_state (surface, + XDG_TOPLEVEL_STATE_SUSPENDED)); +} + +static void +test_maximized (WaylandDisplay *display) +{ + g_autoptr (WaylandSurface) surface = NULL; + + g_debug ("Testing suspended state when mapping maximized"); + + surface = wayland_surface_new (display, __func__, 100, 100, 0xffffffff); + xdg_toplevel_set_maximized (surface->xdg_toplevel); + wl_surface_commit (surface->wl_surface); + + wait_for_window_shown (display, surface->wl_surface); + g_assert_false (wayland_surface_has_state (surface, + XDG_TOPLEVEL_STATE_SUSPENDED)); +} + +static void +test_minimized (WaylandDisplay *display) +{ + g_autoptr (WaylandSurface) surface = NULL; + + g_debug ("Testing suspended state when mapping minimized"); + + surface = wayland_surface_new (display, __func__, 100, 100, 0xffffffff); + wl_surface_commit (surface->wl_surface); + + wait_for_window_shown (display, surface->wl_surface); + g_assert_false (wayland_surface_has_state (surface, + XDG_TOPLEVEL_STATE_SUSPENDED)); + + xdg_toplevel_set_minimized (surface->xdg_toplevel); + wait_for_state (surface, XDG_TOPLEVEL_STATE_SUSPENDED); +} + +static void +test_workspace_changes (WaylandDisplay *display) +{ + g_autoptr (WaylandSurface) surface = NULL; + + g_debug ("Testing suspended state when changing workspace"); + + surface = wayland_surface_new (display, __func__, 100, 100, 0xffffffff); + wl_surface_commit (surface->wl_surface); + + wait_for_window_shown (display, surface->wl_surface); + g_assert_false (wayland_surface_has_state (surface, + XDG_TOPLEVEL_STATE_SUSPENDED)); + + + test_driver_sync_point (display->test_driver, + XDG_TOPLEVEL_SUSPENDED_COMMAND_NEXT_WORKSPACE, + NULL); + + wait_for_state (surface, XDG_TOPLEVEL_STATE_SUSPENDED); + + test_driver_sync_point (display->test_driver, + XDG_TOPLEVEL_SUSPENDED_COMMAND_PREV_WORKSPACE, + NULL); + + wait_for_no_state (surface, XDG_TOPLEVEL_STATE_SUSPENDED); +} + +static void +test_obstructed (WaylandDisplay *display) +{ + g_autoptr (WaylandSurface) surface = NULL; + g_autoptr (WaylandSurface) cover_surface = NULL; + + g_debug ("Testing suspended state when obstructed"); + + surface = wayland_surface_new (display, __func__, + 100, 100, 0xffffffff); + wl_surface_commit (surface->wl_surface); + + wait_for_window_shown (display, surface->wl_surface); + g_assert_false (wayland_surface_has_state (surface, + XDG_TOPLEVEL_STATE_SUSPENDED)); + + cover_surface = wayland_surface_new (display, "obstruction", + 100, 100, 0xffffffff); + xdg_toplevel_set_maximized (cover_surface->xdg_toplevel); + wl_surface_commit (cover_surface->wl_surface); + + wait_for_window_shown (display, cover_surface->wl_surface); + test_driver_sync_point (display->test_driver, + XDG_TOPLEVEL_SUSPENDED_COMMAND_ACTIVATE_WINDOW, + cover_surface->wl_surface); + + wait_for_state (surface, XDG_TOPLEVEL_STATE_SUSPENDED); + + g_clear_object (&cover_surface); + + wait_for_no_state (surface, XDG_TOPLEVEL_STATE_SUSPENDED); +} + +int +main (int argc, + char **argv) +{ + g_autoptr (WaylandDisplay) display = NULL; + g_autoptr (WaylandSurface) surface = NULL; + + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER | + WAYLAND_DISPLAY_CAPABILITY_XDG_SHELL_V6); + + test_floating (display); + test_maximized (display); + test_minimized (display); + test_workspace_changes (display); + test_obstructed (display); + + return EXIT_SUCCESS; +} diff --git a/src/tests/wayland-unit-tests.c b/src/tests/wayland-unit-tests.c index 692b723df..681968da2 100644 --- a/src/tests/wayland-unit-tests.c +++ b/src/tests/wayland-unit-tests.c @@ -868,6 +868,73 @@ toplevel_show_states (void) meta_wayland_test_client_finish (wayland_test_client); } +enum +{ + XDG_TOPLEVEL_SUSPENDED_COMMAND_NEXT_WORKSPACE = 0, + XDG_TOPLEVEL_SUSPENDED_COMMAND_PREV_WORKSPACE = 1, + XDG_TOPLEVEL_SUSPENDED_COMMAND_ACTIVATE_WINDOW = 2, +}; + +static void +on_toplevel_suspended_sync_point (MetaWaylandTestDriver *test_driver, + unsigned int sequence, + struct wl_resource *surface_resource, + struct wl_client *wl_client) +{ + MetaDisplay *display = meta_context_get_display (test_context); + MetaWorkspaceManager *workspace_manager = + meta_display_get_workspace_manager (display); + MetaWorkspace *current_workspace; + int index; + MetaWorkspace *workspace; + MetaWaylandSurface *surface; + uint32_t now_ms; + + current_workspace = + meta_workspace_manager_get_active_workspace (workspace_manager); + index = meta_workspace_index (current_workspace); + switch (sequence) + { + case XDG_TOPLEVEL_SUSPENDED_COMMAND_NEXT_WORKSPACE: + workspace = + meta_workspace_manager_get_workspace_by_index (workspace_manager, + index + 1); + now_ms = meta_display_get_current_time_roundtrip (display); + meta_workspace_activate (workspace, now_ms); + break; + case XDG_TOPLEVEL_SUSPENDED_COMMAND_PREV_WORKSPACE: + workspace = + meta_workspace_manager_get_workspace_by_index (workspace_manager, + index - 1); + now_ms = meta_display_get_current_time_roundtrip (display); + meta_workspace_activate (workspace, now_ms); + break; + case XDG_TOPLEVEL_SUSPENDED_COMMAND_ACTIVATE_WINDOW: + surface = wl_resource_get_user_data (surface_resource); + now_ms = meta_display_get_current_time_roundtrip (display); + meta_window_activate (meta_wayland_surface_get_window (surface), now_ms); + break; + } +} + +static void +toplevel_suspended (void) +{ + MetaWaylandTestClient *wayland_test_client; + gulong sync_point_id; + + sync_point_id = + g_signal_connect (test_driver, "sync-point", + G_CALLBACK (on_toplevel_suspended_sync_point), + NULL); + + wayland_test_client = + meta_wayland_test_client_new (test_context, "xdg-toplevel-suspended"); + meta_wayland_test_client_finish (wayland_test_client); + + g_signal_handler_disconnect (test_driver, sync_point_id); +} + static void on_before_tests (void) { @@ -947,6 +1014,8 @@ init_tests (void) xdg_foreign_set_parent_of); g_test_add_func ("/wayland/toplevel/show-states", toplevel_show_states); + g_test_add_func ("/wayland/toplevel/suspended", + toplevel_suspended); } int