diff --git a/src/backends/meta-cursor-sprite-xcursor.h b/src/backends/meta-cursor-sprite-xcursor.h
index a3f599b26..33f112ba2 100644
--- a/src/backends/meta-cursor-sprite-xcursor.h
+++ b/src/backends/meta-cursor-sprite-xcursor.h
@@ -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);
diff --git a/src/backends/meta-cursor-tracker-private.h b/src/backends/meta-cursor-tracker-private.h
index 995cbb5a4..48e86e72a 100644
--- a/src/backends/meta-cursor-tracker-private.h
+++ b/src/backends/meta-cursor-tracker-private.h
@@ -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);
diff --git a/src/backends/meta-cursor-tracker.c b/src/backends/meta-cursor-tracker.c
index 0680a1945..e6c30aa69 100644
--- a/src/backends/meta-cursor-tracker.c
+++ b/src/backends/meta-cursor-tracker.c
@@ -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
diff --git a/src/backends/meta-logical-monitor.h b/src/backends/meta-logical-monitor.h
index 51b4c070f..58b266681 100644
--- a/src/backends/meta-logical-monitor.h
+++ b/src/backends/meta-logical-monitor.h
@@ -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);
diff --git a/src/core/meta-fraction.h b/src/core/meta-fraction.h
index d24ae2ce0..6704282bf 100644
--- a/src/core/meta-fraction.h
+++ b/src/core/meta-fraction.h
@@ -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);
diff --git a/src/tests/cursor-tests.c b/src/tests/cursor-tests.c
new file mode 100644
index 000000000..3c0c6a462
--- /dev/null
+++ b/src/tests/cursor-tests.c
@@ -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 .
+ *
+ */
+
+#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);
+}
diff --git a/src/tests/meson.build b/src/tests/meson.build
index 487421d26..77fab2ffd 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -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
diff --git a/src/tests/ref-tests/backends_native_cursor_scaling_0_0.ref.png b/src/tests/ref-tests/backends_native_cursor_scaling_0_0.ref.png
new file mode 100644
index 000000000..bbf50dfda
Binary files /dev/null and b/src/tests/ref-tests/backends_native_cursor_scaling_0_0.ref.png differ
diff --git a/src/tests/ref-tests/backends_native_cursor_scaling_0_1.ref.png b/src/tests/ref-tests/backends_native_cursor_scaling_0_1.ref.png
new file mode 100644
index 000000000..bbf50dfda
Binary files /dev/null and b/src/tests/ref-tests/backends_native_cursor_scaling_0_1.ref.png differ
diff --git a/src/tests/ref-tests/backends_native_cursor_scaling_1_0.ref.png b/src/tests/ref-tests/backends_native_cursor_scaling_1_0.ref.png
new file mode 100644
index 000000000..bbf50dfda
Binary files /dev/null and b/src/tests/ref-tests/backends_native_cursor_scaling_1_0.ref.png differ
diff --git a/src/tests/ref-tests/backends_native_cursor_scaling_1_1.ref.png b/src/tests/ref-tests/backends_native_cursor_scaling_1_1.ref.png
new file mode 100644
index 000000000..bbf50dfda
Binary files /dev/null and b/src/tests/ref-tests/backends_native_cursor_scaling_1_1.ref.png differ
diff --git a/src/tests/ref-tests/backends_native_cursor_scaling_2_0.ref.png b/src/tests/ref-tests/backends_native_cursor_scaling_2_0.ref.png
new file mode 100644
index 000000000..7c58f816f
Binary files /dev/null and b/src/tests/ref-tests/backends_native_cursor_scaling_2_0.ref.png differ
diff --git a/src/tests/ref-tests/backends_native_cursor_scaling_2_1.ref.png b/src/tests/ref-tests/backends_native_cursor_scaling_2_1.ref.png
new file mode 100644
index 000000000..7c58f816f
Binary files /dev/null and b/src/tests/ref-tests/backends_native_cursor_scaling_2_1.ref.png differ
diff --git a/src/tests/ref-tests/backends_native_cursor_scaling_3_0.ref.png b/src/tests/ref-tests/backends_native_cursor_scaling_3_0.ref.png
new file mode 100644
index 000000000..237553ad2
Binary files /dev/null and b/src/tests/ref-tests/backends_native_cursor_scaling_3_0.ref.png differ
diff --git a/src/tests/ref-tests/backends_native_cursor_scaling_3_1.ref.png b/src/tests/ref-tests/backends_native_cursor_scaling_3_1.ref.png
new file mode 100644
index 000000000..237553ad2
Binary files /dev/null and b/src/tests/ref-tests/backends_native_cursor_scaling_3_1.ref.png differ
diff --git a/src/tests/wayland-test-clients/cursor-tests-client.c b/src/tests/wayland-test-clients/cursor-tests-client.c
new file mode 100644
index 000000000..3ccd4bc50
--- /dev/null
+++ b/src/tests/wayland-test-clients/cursor-tests-client.c
@@ -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 .
+ *
+ */
+
+#include "config.h"
+
+#include
+
+#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;
+}
diff --git a/src/tests/wayland-test-clients/meson.build b/src/tests/wayland-test-clients/meson.build
index bdea36df3..122912d39 100644
--- a/src/tests/wayland-test-clients/meson.build
+++ b/src/tests/wayland-test-clients/meson.build
@@ -102,6 +102,12 @@ wayland_test_clients = [
{
'name': 'ycbcr',
},
+ {
+ 'name': 'cursor-tests-client',
+ 'extra_deps': [
+ wayland_cursor_dep,
+ ],
+ },
]
test_client_executables = {}