diff --git a/src/tests/meson.build b/src/tests/meson.build index 9e449a2f5..fd91633e0 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -492,6 +492,17 @@ test_cases += [ cursor_screen_cast_client, ] }, + { + 'name': 'surface-scale-tests', + 'suite': 'backends/native', + 'sources': [ + 'surface-scale-tests.c', + wayland_test_utils, + ], + 'depends': [ + test_client_executables.get('cursor-tests-client'), + ] + }, ] # KMS tests diff --git a/src/tests/surface-scale-tests.c b/src/tests/surface-scale-tests.c new file mode 100644 index 000000000..546ca1900 --- /dev/null +++ b/src/tests/surface-scale-tests.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2025 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 "tests/meta-monitor-test-utils.h" +#include "tests/meta-test-utils.h" +#include "tests/meta-test/meta-context-test.h" +#include "tests/meta-wayland-test-driver.h" +#include "tests/meta-wayland-test-utils.h" + +static MetaContext *test_context; + +static MonitorTestCaseSetup test_case_base_setup = { + .modes = { + { + .width = 1920, + .height = 1080, + .refresh_rate = 60.0 + }, + }, + .n_modes = 1, + .outputs = { + { + .crtc = 0, + .modes = { 0 }, + .n_modes = 1, + .preferred_mode = 0, + .possible_crtcs = { 0 }, + .n_possible_crtcs = 1, + .width_mm = 150, + .height_mm = 85, + }, + }, + .n_outputs = 1, + .crtcs = { + { + .current_mode = -1 + }, + }, + .n_crtcs = 1 +}; + +static void +bump_output_serial (const char **serial) +{ + static int output_serial_counter = 0x1230000; + + g_clear_pointer ((gpointer *) serial, g_free); + *serial = g_strdup_printf ("0x%x", output_serial_counter++); +} + +static void +meta_test_wayland_surface_scales (void) +{ + MetaBackend *backend = meta_context_get_backend (test_context); + ClutterSeat *seat = meta_backend_get_default_seat (backend); + MetaWaylandCompositor *compositor = + meta_context_get_wayland_compositor (test_context); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaMonitorManagerTest *monitor_manager_test = + META_MONITOR_MANAGER_TEST (monitor_manager); + g_autoptr (MetaWaylandTestDriver) test_driver = NULL; + g_autoptr (ClutterVirtualInputDevice) virtual_pointer = NULL; + MetaWaylandTestClient *wayland_test_client; + MonitorTestCaseSetup test_case_setup = test_case_base_setup; + MetaMonitorTestSetup *test_setup; + float scale; + + virtual_pointer = clutter_seat_create_virtual_device (seat, + CLUTTER_POINTER_DEVICE); + + test_driver = meta_wayland_test_driver_new (compositor); + meta_wayland_test_driver_set_property_int (test_driver, + "cursor-theme-size", + meta_prefs_get_cursor_size ()); + + + g_debug ("Testing with scale 2.0, then launching client"); + scale = 2.0f; + test_case_setup.outputs[0].scale = scale; + bump_output_serial (&test_case_setup.outputs[0].serial); + test_setup = meta_create_monitor_test_setup (backend, + &test_case_setup, + MONITOR_TEST_FLAG_NO_STORED); + meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup); + + wayland_test_client = meta_wayland_test_client_new (test_context, + "surface-scale-client"); + meta_wait_for_window_cursor (test_context); + meta_wayland_test_driver_emit_sync_event (test_driver, + (uint32_t) (scale * 120.0f)); + meta_wayland_test_driver_wait_for_sync_point (test_driver, 0); + + g_debug ("Testing with scale 2.5 with existing client"); + scale = 2.5f; + test_case_setup.outputs[0].scale = scale; + bump_output_serial (&test_case_setup.outputs[0].serial); + test_setup = meta_create_monitor_test_setup (backend, + &test_case_setup, + MONITOR_TEST_FLAG_NO_STORED); + meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup); + meta_wayland_test_driver_emit_sync_event (test_driver, + (uint32_t) (scale * 120.0f)); + meta_wayland_test_driver_wait_for_sync_point (test_driver, 0); + + g_debug ("Terminating client"); + meta_wayland_test_driver_emit_sync_event (test_driver, 0); + + g_clear_pointer ((gpointer *) &test_case_setup.outputs[0].serial, g_free); + meta_wayland_test_client_finish (wayland_test_client); +} + +static void +init_tests (void) +{ + g_test_add_func ("/wayland/surface/surface-scales", + meta_test_wayland_surface_scales); +} + +int +main (int argc, + char **argv) +{ + g_autoptr (MetaContext) context = NULL; + + context = meta_create_test_context (META_CONTEXT_TEST_TYPE_TEST, + META_CONTEXT_TEST_FLAG_NO_X11); + g_assert_true (meta_context_configure (context, &argc, &argv, NULL)); + + test_context = context; + + init_tests (); + + return meta_context_test_run_tests (META_CONTEXT_TEST (context), + META_TEST_RUN_FLAG_NONE); +} diff --git a/src/tests/wayland-test-clients/meson.build b/src/tests/wayland-test-clients/meson.build index 0d76ac1bb..ec7370b7c 100644 --- a/src/tests/wayland-test-clients/meson.build +++ b/src/tests/wayland-test-clients/meson.build @@ -109,6 +109,12 @@ wayland_test_clients = [ libmutter_mtk_dep, ], }, + { + 'name': 'surface-scale-client', + 'extra_deps': [ + wayland_cursor_dep, + ], + }, ] test_client_executables = {} diff --git a/src/tests/wayland-test-clients/surface-scale-client.c b/src/tests/wayland-test-clients/surface-scale-client.c new file mode 100644 index 000000000..5fb2a9ae2 --- /dev/null +++ b/src/tests/wayland-test-clients/surface-scale-client.c @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2025 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 "wayland-test-client-utils.h" + +static float toplevel_scale; +static float cursor_scale; +static float subsurface_scale; + +static WaylandSurface *toplevel_surface; +static WaylandSurface *cursor_surface; +static WaylandSurface *subsurface; + +static void +check_scales (float scale) +{ + g_assert_cmpfloat_with_epsilon (toplevel_scale, scale, FLT_EPSILON); + g_assert_cmpint (toplevel_surface->preferred_buffer_scale, + ==, + (int32_t) ceilf (scale)); + + g_assert_cmpfloat_with_epsilon (cursor_scale, scale, FLT_EPSILON); + g_assert_cmpint (cursor_surface->preferred_buffer_scale, + ==, + (int32_t) ceilf (scale)); + + g_assert_cmpfloat_with_epsilon (subsurface_scale, scale, FLT_EPSILON); + g_assert_cmpint (subsurface->preferred_buffer_scale, + ==, + (int32_t) ceilf (scale)); +} + +static void +handle_preferred_scale (void *data, + struct wp_fractional_scale_v1 *fractional_scale, + uint32_t wire_scale) +{ + float *scale_ptr = data; + + *scale_ptr = wire_scale / 120.0f; +} + +static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { + .preferred_scale = handle_preferred_scale, +}; + +static void +watch_preferred_scales (WaylandDisplay *display, + struct wl_surface *wl_surface, + float *scale_ptr) +{ + struct wp_fractional_scale_v1 *fractional_scale; + + fractional_scale = + wp_fractional_scale_manager_v1_get_fractional_scale (display->fractional_scale_mgr, + wl_surface); + wp_fractional_scale_v1_add_listener (fractional_scale, + &fractional_scale_listener, + scale_ptr); +} + +static void +on_pointer_enter (WaylandSurface *surface, + struct wl_pointer *pointer, + uint32_t serial) +{ + WaylandDisplay *display = surface->display; + struct wl_cursor_theme *cursor_theme; + struct wl_cursor *cursor; + struct wl_cursor_image *image; + struct wl_buffer *buffer; + int theme_size; + + if (!cursor_surface) + { + cursor_surface = wayland_surface_new_unassigned (display); + + watch_preferred_scales (display, + cursor_surface->wl_surface, + &cursor_scale); + } + + theme_size = lookup_property_int (display, "cursor-theme-size"); + + cursor_theme = wl_cursor_theme_load (NULL, + theme_size, + display->shm); + cursor = wl_cursor_theme_get_cursor (cursor_theme, "left_ptr"); + image = cursor->images[0]; + buffer = wl_cursor_image_get_buffer (image); + g_assert_nonnull (buffer); + + wl_pointer_set_cursor (pointer, serial, + cursor_surface->wl_surface, + image->hotspot_x, image->hotspot_y); + wl_surface_attach (cursor_surface->wl_surface, buffer, 0, 0); + wl_surface_damage_buffer (cursor_surface->wl_surface, 0, 0, + image->width, image->height); + wl_surface_commit (cursor_surface->wl_surface); + + wl_cursor_theme_destroy (cursor_theme); +} + +static void +on_sync_event (WaylandDisplay *display, + uint32_t serial, + uint32_t *out_scale) +{ + *out_scale = serial; +} + +int +main (int argc, + char **argv) +{ + g_autoptr (WaylandDisplay) display = NULL; + uint32_t new_scale = UINT32_MAX; + uint32_t prev_scale; + struct wl_subsurface *wl_subsurface; + + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + g_signal_connect (display, "sync-event", + G_CALLBACK (on_sync_event), &new_scale); + toplevel_surface = wayland_surface_new (display, + "cursor-tests-surface", + 100, 100, 0xffffffff); + g_signal_connect (toplevel_surface, "pointer-enter", + G_CALLBACK (on_pointer_enter), NULL); + xdg_toplevel_set_fullscreen (toplevel_surface->xdg_toplevel, NULL); + watch_preferred_scales (display, + toplevel_surface->wl_surface, + &toplevel_scale); + + subsurface = wayland_surface_new_unassigned (display); + wl_subsurface = + wl_subcompositor_get_subsurface (display->subcompositor, + subsurface->wl_surface, + toplevel_surface->wl_surface); + draw_surface (display, subsurface->wl_surface, 10, 10, 0xff0000ff); + watch_preferred_scales (display, + subsurface->wl_surface, + &subsurface_scale); + wl_surface_commit (subsurface->wl_surface); + + wl_surface_commit (toplevel_surface->wl_surface); + + g_debug ("Waiting for scales to check"); + while (new_scale > 0) + { + prev_scale = new_scale; + wayland_display_dispatch (display); + wl_display_roundtrip (display->display); + if (prev_scale != new_scale && new_scale > 0) + { + float scale = new_scale / 120.0f; + + g_debug ("Checking scale %f", scale); + check_scales (scale); + test_driver_sync_point (display->test_driver, 0, NULL); + } + } + + g_clear_pointer (&wl_subsurface, wl_subsurface_destroy); + + g_clear_object (&toplevel_surface); + g_clear_object (&cursor_surface); + g_clear_object (&subsurface); + + return EXIT_SUCCESS; +}