diff --git a/mutter.spec b/mutter.spec index 6f6c36a..d934746 100644 --- a/mutter.spec +++ b/mutter.spec @@ -56,6 +56,10 @@ Patch: 0005-window-Add-external-constraints-support.patch Patch: 0006-constraints-Apply-window-external-constraints.patch Patch: 0007-tests-Add-test-for-external-constraints.patch +# Handle unmaximize when headless +# https://redhat.atlassian.net/browse/RHEL-156728 +Patch: unmaximize-when-headless.patch + BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.41.0 BuildRequires: pkgconfig(sm) BuildRequires: pkgconfig(libadwaita-1) diff --git a/unmaximize-when-headless.patch b/unmaximize-when-headless.patch new file mode 100644 index 0000000..79c7374 --- /dev/null +++ b/unmaximize-when-headless.patch @@ -0,0 +1,298 @@ +From b14301e520c15fc2c5166bfbed06e14d03bbe878 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 17 Mar 2026 16:50:04 +0100 +Subject: [PATCH 1/4] tests/test-runner: Add 'remove_monitor' command + +This allows removing any added monitor, or the monitor added by default. +--- + src/tests/test-runner.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/src/tests/test-runner.c b/src/tests/test-runner.c +index e036b16bd6..6a8375144d 100644 +--- a/src/tests/test-runner.c ++++ b/src/tests/test-runner.c +@@ -2324,6 +2324,18 @@ test_case_do (TestCase *test, + + g_hash_table_insert (test->virtual_monitors, g_strdup (argv[1]), monitor); + } ++ else if (strcmp (argv[0], "remove_monitor") == 0) ++ { ++ MetaBackend *backend = meta_context_get_backend (test->context); ++ MetaMonitorManager *monitor_manager = ++ meta_backend_get_monitor_manager (backend); ++ ++ if (argc != 2) ++ BAD_COMMAND ("usage: %s ", argv[0]); ++ ++ g_hash_table_remove (test->virtual_monitors, g_strdup (argv[1])); ++ meta_monitor_manager_reload (monitor_manager); ++ } + else if (strcmp (argv[0], "set_monitor_order") == 0) + { + MetaBackend *backend = meta_context_get_backend (test->context); +-- +2.51.0 + + +From b4f6e1b3fac0440216c319ec4936df8cd4108f61 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 17 Mar 2026 16:50:44 +0100 +Subject: [PATCH 2/4] tests/test-runner: Handle sanity checking while headless + +When headless, monitors will not have a current monitor, as there is +none. Handle this. +--- + src/backends/meta-monitor-manager-private.h | 1 + + src/tests/test-runner.c | 21 +++++++++++++++++---- + 2 files changed, 18 insertions(+), 4 deletions(-) + +diff --git a/src/backends/meta-monitor-manager-private.h b/src/backends/meta-monitor-manager-private.h +index e219c95aaf..522730c904 100644 +--- a/src/backends/meta-monitor-manager-private.h ++++ b/src/backends/meta-monitor-manager-private.h +@@ -353,6 +353,7 @@ void meta_monitor_manager_update_logical_state_derived (MetaMonito + META_EXPORT_TEST + void meta_monitor_manager_lid_is_closed_changed (MetaMonitorManager *manager); + ++META_EXPORT_TEST + gboolean meta_monitor_manager_is_headless (MetaMonitorManager *manager); + + float meta_monitor_manager_calculate_monitor_mode_scale (MetaMonitorManager *manager, +diff --git a/src/tests/test-runner.c b/src/tests/test-runner.c +index 6a8375144d..551ac15357 100644 +--- a/src/tests/test-runner.c ++++ b/src/tests/test-runner.c +@@ -3183,11 +3183,24 @@ sanity_check_monitor (MetaWindow *window) + { + if (!meta_window_is_hidden (window)) + { +- MtkRectangle rect; ++ MetaContext *context = meta_display_get_context (window->display); ++ MetaBackend *backend = meta_context_get_backend (context); ++ MetaMonitorManager *monitor_manager = ++ meta_backend_get_monitor_manager (backend); + +- g_assert_nonnull (window->monitor); +- rect = meta_window_config_get_rect (window->config); +- g_assert_true (mtk_rectangle_overlap (&rect, &window->monitor->rect)); ++ if (meta_monitor_manager_is_headless (monitor_manager)) ++ { ++ g_assert_null (window->monitor); ++ } ++ else ++ { ++ MtkRectangle rect; ++ ++ g_assert_nonnull (window->monitor); ++ ++ rect = meta_window_config_get_rect (window->config); ++ g_assert_true (mtk_rectangle_overlap (&rect, &window->monitor->rect)); ++ } + } + } + +-- +2.51.0 + + +From dd0811b6a1576df8d1d8fd9c0fdb0381fec805e0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Fri, 17 Apr 2026 09:37:43 +0200 +Subject: [PATCH 3/4] window: Bail proper unmaximizing when headless + +We don't have any information to handle placing a window when +unmaximizing and headless, so just place it at 0,0. +--- + src/core/window.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/src/core/window.c b/src/core/window.c +index 8f44f60d22..2b2ad8b0ac 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -3443,6 +3443,7 @@ meta_window_set_unmaximize_flags (MetaWindow *window, + gboolean has_desired_rect = FALSE; + MtkRectangle target_rect; + MtkRectangle work_area; ++ gboolean has_work_area = FALSE; + MtkRectangle old_frame_rect, old_buffer_rect; + gboolean has_target_size; + MetaPlaceFlag place_flags = META_PLACE_FLAG_NONE; +@@ -3452,7 +3453,11 @@ meta_window_set_unmaximize_flags (MetaWindow *window, + + reset_pending_auto_maximize (window); + +- meta_window_get_work_area_current_monitor (window, &work_area); ++ if (window->monitor) ++ { ++ meta_window_get_work_area_current_monitor (window, &work_area); ++ has_work_area = TRUE; ++ } + meta_window_get_frame_rect (window, &old_frame_rect); + meta_window_get_buffer_rect (window, &old_buffer_rect); + +@@ -3508,6 +3513,7 @@ meta_window_set_unmaximize_flags (MetaWindow *window, + */ + if (unmaximize_horizontally && unmaximize_vertically && + has_desired_rect && ++ has_work_area && + desired_rect.width * desired_rect.height > + work_area.width * work_area.height * MAX_UNMAXIMIZED_WINDOW_AREA) + { +@@ -3572,7 +3578,7 @@ meta_window_set_unmaximize_flags (MetaWindow *window, + + meta_window_recalc_features (window); + set_net_wm_state (window); +- if (!window->monitor->in_fullscreen) ++ if (window->monitor && !window->monitor->in_fullscreen) + meta_display_queue_check_fullscreen (window->display); + } + +-- +2.51.0 + + +From 0c410fe7c31a9cbc1c9c2a5a1f115f509a97332f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 16 Apr 2026 23:38:28 +0200 +Subject: [PATCH 4/4] tests: Add test for unmaximizing while headless + +A test is added that verifies the behavior when unmaximizing while no +monitor is connected. The 'dispatch' test runner command had to be +adapted to handle not getting any frame clock dispatches, since there +are no frame CRTC associated frame clocks to dispatch from. +--- + src/tests/meson.build | 1 + + .../stacking/unmaximize-headless.metatest | 61 +++++++++++++++++++ + src/tests/test-runner.c | 32 ++++++---- + 3 files changed, 83 insertions(+), 11 deletions(-) + create mode 100644 src/tests/stacking/unmaximize-headless.metatest + +diff --git a/src/tests/meson.build b/src/tests/meson.build +index 8c3773033f..64ea5303b1 100644 +--- a/src/tests/meson.build ++++ b/src/tests/meson.build +@@ -1181,6 +1181,7 @@ stacking_tests = [ + 'partially-maximized', + 'move-to-monitor', + 'keyboard-resize', ++ 'unmaximize-headless', + ] + + foreach stacking_test: stacking_tests +diff --git a/src/tests/stacking/unmaximize-headless.metatest b/src/tests/stacking/unmaximize-headless.metatest +new file mode 100644 +index 0000000000..0a738c35d0 +--- /dev/null ++++ b/src/tests/stacking/unmaximize-headless.metatest +@@ -0,0 +1,61 @@ ++set_pref center-new-windows true ++ ++# Test unmaximizing when there is no monitor to place on ++ ++new_client w wayland ++create w/1 csd ++resize w/1 200 200 ++maximize w/1 ++show w/1 ++wait ++ ++remove_monitor default ++ ++unmaximize w/1 ++wait_reconfigure w/1 ++ ++add_monitor default 800 600 ++wait_reconfigure w/1 ++assert_size w/1 200 200 ++# Incorrect placement expected. ++#assert_position w/1 300 200 ++ ++remove_monitor default ++ ++maximize w/1 ++wait_reconfigure w/1 ++ ++add_monitor default 1024 768 ++wait_reconfigure w/1 ++assert_size w/1 1024 768 ++assert_position w/1 0 0 ++ ++# Same test with an X11 client ++ ++new_client x x11 ++create x/1 csd ++resize x/1 200 200 ++maximize x/1 ++show x/1 ++wait ++ ++remove_monitor default ++ ++unmaximize x/1 ++wait_reconfigure x/1 ++ ++add_monitor default 800 600 ++wait_reconfigure x/1 ++assert_size x/1 200 200 ++# Incorrect placement expected. ++#assert_position x/1 300 200 ++ ++remove_monitor default ++ ++maximize x/1 ++wait_reconfigure x/1 ++ ++add_monitor default 1024 768 ++wait_reconfigure x/1 ++assert_size x/1 1024 768 ++assert_position x/1 0 0 +diff --git a/src/tests/test-runner.c b/src/tests/test-runner.c +index 551ac15357..235575660f 100644 +--- a/src/tests/test-runner.c ++++ b/src/tests/test-runner.c +@@ -163,19 +163,29 @@ test_case_dispatch (TestCase *test, + MetaDisplay *display = meta_context_get_display (test->context); + MetaCompositor *compositor = meta_display_get_compositor (display); + MetaLaters *laters = meta_compositor_get_laters (compositor); ++ MetaMonitorManager *monitor_manager = ++ meta_backend_get_monitor_manager (backend); + +- /* Wait until we've done any outstanding queued up work. +- * Though we add this as BEFORE_REDRAW, the iteration that runs the +- * BEFORE_REDRAW idles will proceed on and do the redraw, so we're +- * waiting until after *all* frame processing. +- */ +- meta_laters_add (laters, META_LATER_BEFORE_REDRAW, +- test_case_loop_quit, +- test, +- NULL); ++ if (meta_monitor_manager_is_headless (monitor_manager)) ++ { ++ while (g_main_context_iteration (NULL, FALSE)) ++ ; ++ } ++ else ++ { ++ /* Wait until we've done any outstanding queued up work. ++ * Though we add this as BEFORE_REDRAW, the iteration that runs the ++ * BEFORE_REDRAW idles will proceed on and do the redraw, so we're ++ * waiting until after *all* frame processing. ++ */ ++ meta_laters_add (laters, META_LATER_BEFORE_REDRAW, ++ test_case_loop_quit, ++ test, ++ NULL); + +- clutter_stage_schedule_update (CLUTTER_STAGE (stage)); +- g_main_loop_run (test->loop); ++ clutter_stage_schedule_update (CLUTTER_STAGE (stage)); ++ g_main_loop_run (test->loop); ++ } + + return TRUE; + } +-- +2.51.0 +