tests: Add basic ref-test based cursor sprite test case

It currently contains some basic tests using the built in cursor as well
as traditional buffer scale using Wayland client in different monitor
configurations.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3859>
This commit is contained in:
Jonas Ådahl 2024-11-14 15:45:49 +01:00 committed by Sebastian Wick
parent 129ead488b
commit e9475b2190
17 changed files with 565 additions and 0 deletions

View File

@ -38,6 +38,7 @@ MetaCursor meta_cursor_sprite_xcursor_get_cursor (MetaCursorSpriteXcursor *sprit
XcursorImage * meta_cursor_sprite_xcursor_get_current_image (MetaCursorSpriteXcursor *sprite_xcursor);
META_EXPORT_TEST
const char * meta_cursor_get_name (MetaCursor cursor);
const char * meta_cursor_get_legacy_name (MetaCursor cursor);

View File

@ -42,6 +42,9 @@ void meta_cursor_tracker_set_root_cursor (MetaCursorTracker *tracker,
void meta_cursor_tracker_invalidate_position (MetaCursorTracker *tracker);
META_EXPORT_TEST
gboolean meta_cursor_tracker_has_window_cursor (MetaCursorTracker *tracker);
void meta_cursor_tracker_track_position (MetaCursorTracker *tracker);
void meta_cursor_tracker_untrack_position (MetaCursorTracker *tracker);

View File

@ -346,6 +346,15 @@ set_window_cursor (MetaCursorTracker *tracker,
sync_cursor (tracker);
}
gboolean
meta_cursor_tracker_has_window_cursor (MetaCursorTracker *tracker)
{
MetaCursorTrackerPrivate *priv =
meta_cursor_tracker_get_instance_private (tracker);
return priv->has_window_cursor;
}
/**
* meta_cursor_tracker_get_sprite:
* @tracker: a #MetaCursorTracker

View File

@ -47,6 +47,7 @@ struct _MetaLogicalMonitor
typedef struct _MetaLogicalMonitorId MetaLogicalMonitorId;
#define META_TYPE_LOGICAL_MONITOR (meta_logical_monitor_get_type ())
META_EXPORT_TEST
G_DECLARE_FINAL_TYPE (MetaLogicalMonitor, meta_logical_monitor,
META, LOGICAL_MONITOR,
GObject)
@ -75,6 +76,7 @@ gboolean meta_logical_monitor_is_primary (MetaLogicalMonitor *logical_monitor);
void meta_logical_monitor_make_primary (MetaLogicalMonitor *logical_monitor);
META_EXPORT_TEST
float meta_logical_monitor_get_scale (MetaLogicalMonitor *logical_monitor);
MtkMonitorTransform meta_logical_monitor_get_transform (MetaLogicalMonitor *logical_monitor);

View File

@ -19,10 +19,13 @@
#pragma once
#include "core/util-private.h"
typedef struct _MetaFraction
{
int num;
int denom;
} MetaFraction;
META_EXPORT_TEST
MetaFraction meta_fraction_from_double (double src);

431
src/tests/cursor-tests.c Normal file
View File

@ -0,0 +1,431 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "backends/meta-cursor-sprite-xcursor.h"
#include "backends/meta-cursor-tracker-private.h"
#include "backends/meta-logical-monitor.h"
#include "clutter/clutter.h"
#include "compositor/meta-window-actor-private.h"
#include "core/meta-fraction.h"
#include "core/util-private.h"
#include "meta/meta-wayland-compositor.h"
#include "tests/meta-test/meta-context-test.h"
#include "tests/meta-monitor-test-utils.h"
#include "tests/meta-ref-test.h"
#include "tests/meta-wayland-test-driver.h"
#include "tests/meta-wayland-test-utils.h"
struct _MetaCrossOverlay
{
GObject parent;
};
static MetaContext *test_context;
static MetaWaylandTestDriver *test_driver;
static void clutter_content_iface_init (ClutterContentInterface *iface);
#define META_TYPE_CROSS_OVERLAY (meta_cross_overlay_get_type ())
G_DECLARE_FINAL_TYPE (MetaCrossOverlay, meta_cross_overlay,
META, CROSS_OVERLAY, GObject)
G_DEFINE_TYPE_WITH_CODE (MetaCrossOverlay, meta_cross_overlay,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
clutter_content_iface_init))
static void
meta_cross_overlay_paint_content (ClutterContent *content,
ClutterActor *actor,
ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
g_autoptr (ClutterPaintNode) cross_node = NULL;
ClutterActorBox allocation;
ClutterActorBox horizontal;
ClutterActorBox vertical;
CoglColor color;
clutter_actor_get_allocation_box (actor, &allocation);
cogl_color_init_from_4f (&color, 0.0f, 0.0f, 0.0f, 1.0f);
cross_node = clutter_color_node_new (&color);
clutter_paint_node_add_child (node, cross_node);
horizontal = (ClutterActorBox) {
.x1 = allocation.x1,
.y1 = (allocation.y2 - allocation.y1) / 2 - 0.5f,
.x2 = allocation.x2,
.y2 = (allocation.y2 - allocation.y1) / 2 + 0.5f,
};
vertical = (ClutterActorBox) {
.x1 = (allocation.x2 - allocation.x1) / 2 - 0.5f,
.y1 = allocation.y1,
.x2 = (allocation.x2 - allocation.x1) / 2 + 0.5f,
.y2 = allocation.y2,
};
clutter_paint_node_add_rectangle (cross_node, &horizontal);
clutter_paint_node_add_rectangle (cross_node, &vertical);
}
static void
clutter_content_iface_init (ClutterContentInterface *iface)
{
iface->paint_content = meta_cross_overlay_paint_content;
}
static void
meta_cross_overlay_class_init (MetaCrossOverlayClass *klass)
{
}
static void
meta_cross_overlay_init (MetaCrossOverlay *overlay)
{
}
static void
on_stage_size_changed (ClutterActor *stage,
GParamSpec *pspec,
ClutterActor *overlay_actor)
{
float width, height;
clutter_actor_get_size (stage, &width, &height);
clutter_actor_set_size (overlay_actor, width, height);
}
static ClutterActor *
create_overlay_actor (void)
{
MetaBackend *backend = meta_context_get_backend (test_context);
ClutterActor *stage = meta_backend_get_stage (backend);
ClutterActor *actor;
g_autoptr (ClutterContent) content = NULL;
content = g_object_new (META_TYPE_CROSS_OVERLAY, NULL);
actor = clutter_actor_new ();
clutter_actor_set_content (actor, content);
clutter_actor_set_name (actor, "cross-overlay");
clutter_actor_show (actor);
clutter_actor_add_child (stage, actor);
g_signal_connect_object (stage, "notify::size",
G_CALLBACK (on_stage_size_changed),
actor,
G_CONNECT_DEFAULT);
return actor;
}
static ClutterStageView *
setup_test_case (int width,
int height,
float scale,
MetaLogicalMonitorLayoutMode layout_mode,
ClutterVirtualInputDevice *virtual_pointer)
{
MetaBackend *backend = meta_context_get_backend (test_context);
MetaMonitorManager *monitor_manager =
meta_backend_get_monitor_manager (backend);
MetaMonitorManagerTest *monitor_manager_test =
META_MONITOR_MANAGER_TEST (monitor_manager);
MetaRenderer *renderer = meta_backend_get_renderer (backend);
GList *logical_monitors;
MetaLogicalMonitor *logical_monitor;
MetaFraction scale_fraction;
GList *views;
g_autofree char *output_serial = NULL;
MetaMonitorTestSetup *test_setup;
MonitorTestCaseSetup test_case_setup = {
.modes = {
{
.width = width,
.height = height,
.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,
.scale = scale,
},
},
.n_outputs = 1,
.crtcs = {
{
.current_mode = -1
},
},
.n_crtcs = 1
};
static int output_serial_counter = 0x12300000;
/* Always generate unique serials to never trigger policy trying to inherit
* the scale from previous configurations.
*/
output_serial = g_strdup_printf ("0x%x", output_serial_counter++);
test_case_setup.outputs[0].serial = output_serial;
meta_monitor_manager_test_set_layout_mode (monitor_manager_test,
layout_mode);
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);
logical_monitors = meta_monitor_manager_get_logical_monitors (monitor_manager);
g_assert_cmpuint (g_list_length (logical_monitors), ==, 1);
logical_monitor = META_LOGICAL_MONITOR (logical_monitors->data);
g_assert_cmpfloat_with_epsilon (meta_logical_monitor_get_scale (logical_monitor),
scale,
FLT_EPSILON);
scale_fraction = meta_fraction_from_double (scale);
meta_wayland_test_driver_set_property_int (test_driver,
"scale-num",
scale_fraction.num);
meta_wayland_test_driver_set_property_int (test_driver,
"scale-denom",
scale_fraction.denom);
switch (layout_mode)
{
case META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL:
clutter_virtual_input_device_notify_absolute_motion (virtual_pointer,
g_get_monotonic_time (),
width / scale / 2.0f,
height / scale / 2.0f);
break;
case META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL:
clutter_virtual_input_device_notify_absolute_motion (virtual_pointer,
g_get_monotonic_time (),
width / 2.0f,
height / 2.0f);
break;
}
meta_flush_input (test_context);
views = meta_renderer_get_views (renderer);
g_assert_cmpuint (g_list_length (views), ==, 1);
return CLUTTER_STAGE_VIEW (views->data);
}
static const char *
layout_mode_to_string (MetaLogicalMonitorLayoutMode layout_mode)
{
switch (layout_mode)
{
case META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL:
return "logical";
case META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL:
return "physical";
}
g_assert_not_reached ();
}
static void
wait_for_no_windows (void)
{
MetaDisplay *display = meta_context_get_display (test_context);
while (TRUE)
{
g_autoptr (GList) windows = NULL;
windows = meta_display_list_all_windows (display);
if (!windows)
return;
g_main_context_iteration (NULL, TRUE);
}
}
static void
meta_wait_for_window_cursor (void)
{
MetaBackend *backend = meta_context_get_backend (test_context);
MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend);
while (!meta_cursor_tracker_has_window_cursor (cursor_tracker))
g_main_context_iteration (NULL, TRUE);
}
static void
meta_test_native_cursor_scaling (void)
{
MetaBackend *backend = meta_context_get_backend (test_context);
MetaDisplay *display = meta_context_get_display (test_context);
ClutterSeat *seat = meta_backend_get_default_seat (backend);
g_autoptr (ClutterVirtualInputDevice) virtual_pointer = NULL;
ClutterActor *overlay_actor;
ClutterStageView *view;
MetaCursor cursor;
const char *cursor_name;
struct {
int width;
int height;
float scale;
MetaLogicalMonitorLayoutMode layout_mode;
} test_cases[] = {
{
.width = 1920, .height = 1080, .scale = 1.0,
.layout_mode = META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL,
},
{
.width = 1920, .height = 1080, .scale = 1.0,
.layout_mode = META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL,
},
{
.width = 1920, .height = 1080, .scale = 2.0,
.layout_mode = META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL,
},
{
.width = 1920, .height = 1080, .scale = 2.0,
.layout_mode = META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL,
},
};
int i;
cursor = META_CURSOR_MOVE_OR_RESIZE_WINDOW;
meta_display_set_cursor (display, cursor);
virtual_pointer = clutter_seat_create_virtual_device (seat,
CLUTTER_POINTER_DEVICE);
overlay_actor = create_overlay_actor ();
for (i = 0; i < G_N_ELEMENTS (test_cases); i++)
{
MetaWaylandTestClient *test_client;
g_autofree char *ref_test_name = NULL;
MetaWindow *window;
MetaWindowActor *window_actor;
g_debug ("Testing monitor resolution %dx%d with scale %f and "
"%s layout mode",
test_cases[i].width, test_cases[i].height, test_cases[i].scale,
layout_mode_to_string (test_cases[i].layout_mode));
wait_for_no_windows ();
ref_test_name = g_strdup_printf ("%s/%d", g_test_get_path (), i);
view = setup_test_case (test_cases[i].width, test_cases[i].height,
test_cases[i].scale,
test_cases[i].layout_mode,
virtual_pointer);
meta_ref_test_verify_view (view,
ref_test_name,
0,
meta_ref_test_determine_ref_test_flag ());
cursor_name = meta_cursor_get_name (cursor);
test_client =
meta_wayland_test_client_new_with_args (test_context,
"cursor-tests-client",
cursor_name,
NULL);
meta_wayland_test_driver_wait_for_sync_point (test_driver, 0);
window = meta_find_window_from_title (test_context,
"cursor-tests-surface");
g_assert_nonnull (window);
meta_wait_for_window_shown (window);
window_actor = meta_window_actor_from_window (window);
g_assert_nonnull (window_actor);
meta_wait_for_window_cursor ();
meta_ref_test_verify_view (view,
ref_test_name,
1,
meta_ref_test_determine_ref_test_flag ());
meta_wayland_test_driver_emit_sync_event (test_driver, 0);
g_object_add_weak_pointer (G_OBJECT (window_actor),
(gpointer *) &window_actor);
meta_wayland_test_client_finish (test_client);
while (window_actor)
g_main_context_iteration (NULL, TRUE);
}
clutter_actor_destroy (overlay_actor);
}
static void
init_tests (void)
{
g_test_add_func ("/backends/native/cursor/scaling",
meta_test_native_cursor_scaling);
}
static void
on_before_tests (void)
{
MetaWaylandCompositor *compositor =
meta_context_get_wayland_compositor (test_context);
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 ());
}
static void
on_after_tests (void)
{
g_clear_object (&test_driver);
}
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 |
META_CONTEXT_TEST_FLAG_TEST_CLIENT |
META_CONTEXT_TEST_FLAG_NO_ANIMATIONS));
g_assert_true (meta_context_configure (context, &argc, &argv, NULL));
meta_context_test_set_background_color (META_CONTEXT_TEST (context),
COGL_COLOR_INIT (255, 255, 255, 255));
test_context = context;
init_tests ();
g_signal_connect (context, "before-tests",
G_CALLBACK (on_before_tests), NULL);
g_signal_connect (context, "after-tests",
G_CALLBACK (on_after_tests), NULL);
return meta_context_test_run_tests (META_CONTEXT_TEST (context),
META_TEST_RUN_FLAG_NONE);
}

View File

@ -447,6 +447,17 @@ test_cases += [
'meta-thread-impl-test.h',
],
},
{
'name': 'cursor-tests',
'suite': 'backends/native',
'sources': [
'cursor-tests.c',
wayland_test_utils,
],
'depends': [
test_client_executables.get('cursor-tests-client'),
]
},
]
# KMS tests

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,99 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <wayland-cursor.h>
#include "wayland-test-client-utils.h"
static char *cursor_name;
static struct wl_surface *cursor_surface;
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;
int num, denom;
float scale;
int ceiled_scale;
theme_size = lookup_property_int (display, "cursor-theme-size");
num = lookup_property_int (display, "scale-num");
denom = lookup_property_int (display, "scale-denom");
scale = (float) num / (float) denom;
ceiled_scale = (int) ceilf (scale);
cursor_theme = wl_cursor_theme_load (NULL,
(int) (theme_size * ceilf (scale)),
display->shm);
cursor = wl_cursor_theme_get_cursor (cursor_theme, cursor_name);
if (!cursor_surface)
cursor_surface = wl_compositor_create_surface (display->compositor);
image = cursor->images[0];
buffer = wl_cursor_image_get_buffer (image);
g_assert_nonnull (buffer);
wl_pointer_set_cursor (pointer, serial,
cursor_surface,
image->hotspot_x / ceiled_scale,
image->hotspot_y / ceiled_scale);
wl_surface_attach (cursor_surface, buffer, 0, 0);
wl_surface_damage (cursor_surface, 0, 0,
image->width, image->height);
wl_surface_set_buffer_scale (cursor_surface,
ceiled_scale);
wl_surface_commit (cursor_surface);
wl_cursor_theme_destroy (cursor_theme);
test_driver_sync_point (display->test_driver, 0, NULL);
}
int
main (int argc,
char **argv)
{
g_autoptr (WaylandDisplay) display = NULL;
g_autoptr (WaylandSurface) surface = NULL;
cursor_name = argv[1];
display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER);
surface = wayland_surface_new (display,
"cursor-tests-surface",
100, 100, 0xffffffff);
g_signal_connect (surface, "pointer-enter",
G_CALLBACK (on_pointer_enter), NULL);
xdg_toplevel_set_fullscreen (surface->xdg_toplevel, NULL);
wl_surface_commit (surface->wl_surface);
wait_for_sync_event (display, 0);
return EXIT_SUCCESS;
}

View File

@ -102,6 +102,12 @@ wayland_test_clients = [
{
'name': 'ycbcr',
},
{
'name': 'cursor-tests-client',
'extra_deps': [
wayland_cursor_dep,
],
},
]
test_client_executables = {}