From e9475b2190d69583ddb5c24dcc874cc04f0f7247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Thu, 14 Nov 2024 15:45:49 +0100 Subject: [PATCH] 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: --- src/backends/meta-cursor-sprite-xcursor.h | 1 + src/backends/meta-cursor-tracker-private.h | 3 + src/backends/meta-cursor-tracker.c | 9 + src/backends/meta-logical-monitor.h | 2 + src/core/meta-fraction.h | 3 + src/tests/cursor-tests.c | 431 ++++++++++++++++++ src/tests/meson.build | 11 + ...backends_native_cursor_scaling_0_0.ref.png | Bin 0 -> 9461 bytes ...backends_native_cursor_scaling_0_1.ref.png | Bin 0 -> 9461 bytes ...backends_native_cursor_scaling_1_0.ref.png | Bin 0 -> 9461 bytes ...backends_native_cursor_scaling_1_1.ref.png | Bin 0 -> 9461 bytes ...backends_native_cursor_scaling_2_0.ref.png | Bin 0 -> 10494 bytes ...backends_native_cursor_scaling_2_1.ref.png | Bin 0 -> 10494 bytes ...backends_native_cursor_scaling_3_0.ref.png | Bin 0 -> 10511 bytes ...backends_native_cursor_scaling_3_1.ref.png | Bin 0 -> 10511 bytes .../cursor-tests-client.c | 99 ++++ src/tests/wayland-test-clients/meson.build | 6 + 17 files changed, 565 insertions(+) create mode 100644 src/tests/cursor-tests.c create mode 100644 src/tests/ref-tests/backends_native_cursor_scaling_0_0.ref.png create mode 100644 src/tests/ref-tests/backends_native_cursor_scaling_0_1.ref.png create mode 100644 src/tests/ref-tests/backends_native_cursor_scaling_1_0.ref.png create mode 100644 src/tests/ref-tests/backends_native_cursor_scaling_1_1.ref.png create mode 100644 src/tests/ref-tests/backends_native_cursor_scaling_2_0.ref.png create mode 100644 src/tests/ref-tests/backends_native_cursor_scaling_2_1.ref.png create mode 100644 src/tests/ref-tests/backends_native_cursor_scaling_3_0.ref.png create mode 100644 src/tests/ref-tests/backends_native_cursor_scaling_3_1.ref.png create mode 100644 src/tests/wayland-test-clients/cursor-tests-client.c 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 0000000000000000000000000000000000000000..bbf50dfda4731906d805a6a02a6491d41584bdc2 GIT binary patch literal 9461 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 MyLuk0IvR52pP@5%MF0Q* literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7c58f816f7e7213281f6b7f3ed89831e369f984c GIT binary patch literal 10494 zcmeHNiC0rs8oyWv5Txk9NQqjs&H#=y47+P6RzwcUVw-?00kj4L3?K+)2~eq4slYg% z1A`F)bA*P$REmR@rR;b^-Te zF5iBCSIqZ{47@COzF-$-?#?^eWb01f`}j#LLwB1$|GM`^{;=+!!x{P%OfL@X#hYvk zy1vB;x5=f>TW=sMq~3q;x<_Q>_DJ6A*UctEQWbG+aDi1Nuczk zS5!bxhJHLw_c6Xodjr5D_ep{?bP8Icg%$?6XXv7L6bFz5I)Lf` zsspGFpgMr+0ICE3w>og0t2rtd>8U_WAdx9ld)#uOcnz~3IMnlJ^3D&3+hejj2+q3z z7-`1f@fW3b0I+vfRCTk6)x6of^9LLq3C8W&+nwa9w90`9M=Hi1fGGC19q=Hc!G%I3 z600gAqM{!1W)qnFnwpx5ioE>%6c&+1s-(fAlR57Vu>HeOSITJ3Ik)P;l<9l4<#78mbiQZ*c}_g0r0!vO8MN(Od?ITZdsjBDwS?#lx5{ow;^VkLv?aMTI2!) z0MwzG@<8jRdKV{*fdM@I;O_Yf4$;bQzzy2NMnjts$y*;Z7sZ{*)CL*6-xq6v;?-8J z@Qya@z1;{tj!F7IK5c!>{P?lG zjg60)6O}`5WPbRas80=DEVVrD=0@d^=sgh4afuc@n6Wg1CpDYP9l~gaI}bh{ooHLo zc3Hb>Li8gN;Ub-MpeyFc+^0pePZ!32GFwroN-al|goU+Mq9oz7pA3VWFHHuk<-Qbh z<$yntI44tBc-{?nq`LYnD^$M0lzPjoS}R)8hk~`WwOhAtMGjWs@#jCqfQ(Sm26!#0 zYisGwJbBp0re`|d+`@u4yOPxx9b|Xl!0>duHZyL>5TZo>6NK(BfsRYuwu*}T_wSR! z!V+iSIygAgpViO#VS0Kxh?&6Y>f%OfBgj73ge>V6k1K48k7M%Z;tv*jhDkRrvu7MK z|M3vUG2;lbqA#cg6tW|gPfnCJ=UWHEX3~YJsVT@H?$h?H>>|U@4~IL$;jR^ILax7% zW|n5ID1&VwB<#Q1X~C=aluXgc=-%Df&LC!)SVeg=TCxiI}tk$Ip4RYca4rl$S2 zwu|gf440ExzQm+Sl}w=4As-pK@@}L#&cnk)HkuO6rD!T>tW(N4~axM@I+2+D|fYB~z}H4hGW>#F;B6;nZDUAov=; ztleR1I^04@Ve)Z_b0eL%EkxRs)%kJGUEC0pZkfeV8;uv^(xn5wg4Cr`Ogul<8-MhoJjM={=wA+f(f51qB72ot+B{3xrC1-n!9NNXT89;W63Ak1$5ajiT#; z_<^-0VG3)sg`&tYb0+3ZF1&lLLuoD|V6!IVQqexF!--zFTIoPuUY>Shc}2u!N{g^4 zWkwz-X$?gO4fzB`?cP1e2<(?wEU_sj zG$A2D&_WRv)_+uu{PV*iDL0Tin-IX@LN;|39jU~@yve5ln1(ZYBTP4n3lTO2}Ow_pDv3mN}C^R`&Fm<%DmEL{KOepWgcpJ z^mC)$xcXROv0R0V+5VUi=t*5SE+BS-Azgc2V7G$!IiT^ApKS0X&0?G)|bZVcG;NaflA**$*5|%&r1#87G6c)(zHOx(!VNO2~r4?S)+@Ki@m+Q zFj(765QCjq@J+0iExxi=!z4yLjQX3V;53jjTVlq4L6l#LlC$7LAYm!55XsTYw#YV@egHD6#lK6vkI-NoAK(Nh50{f`S zC6jEAxqJhgbyl%5i);+aAu-tswHg9MGeD_!Ix-gguE9I_kkU7-2t!ZHAD8!Rx;GOj{hfrkE!!h+HPO@seb2jA0z zRseL%kpBXpN`VR}QYp~Fffh_i19TsO?w=qH(0wDiZ-g}Xuh<-Hec1o>uHNZF1#}3w MyLuk0IvR52pP@5%MF0Q* literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..237553ad21cacc1355cfa6e808c6ff7b7d64e6a5 GIT binary patch literal 10511 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 VQ+r;P?`|wnhuu66mhKO|_HU-)A9?@) literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..237553ad21cacc1355cfa6e808c6ff7b7d64e6a5 GIT binary patch literal 10511 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 VQ+r;P?`|wnhuu66mhKO|_HU-)A9?@) literal 0 HcmV?d00001 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 = {}