From 867494d3df9f17a2cac1f102805ff348046abad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Mon, 17 Apr 2023 18:35:37 +0200 Subject: [PATCH] wayland/cursor-surface: Update cursor on dispose Otherwise we'll have a cursor sprite backed by a surface that no longer exist. This usually doesn't happen, but can happen in rare situations related to pointer capability changes Wayland client cursor changes and hotplugs. Fixes the following crash: #0 meta_wayland_buffer_get_resource() at ../src/wayland/meta-wayland-buffer.c:128 #1 realize_cursor_sprite_from_wl_buffer_for_gpu() at ../src/backends/native/meta-cursor-renderer-native.c:1649 #2 realize_cursor_sprite_for_gpu() at ../src/backends/native/meta-cursor-renderer-native.c:1869 #3 realize_cursor_sprite() at ../src/backends/native/meta-cursor-renderer-native.c:1887 #4 meta_cursor_renderer_native_update_cursor() at ../src/backends/native/meta-cursor-renderer-native.c:1100 #5 meta_cursor_renderer_update_cursor() at ../src/backends/meta-cursor-renderer.c:414 #6 meta_cursor_renderer_force_update() at ../src/backends/meta-cursor-renderer.c:449 #7 update_cursors() at ../src/backends/meta-backend.c:328 #8 meta_backend_monitors_changed() at ../src/backends/meta-backend.c:338 #9 meta_monitor_manager_notify_monitors_changed() at ../src/backends/meta-monitor-manager.c:3590 #10 meta_monitor_manager_rebuild() at ../src/backends/meta-monitor-manager.c:3678 #11 meta_monitor_manager_native_apply_monitors_config() at ../src/backends/native/meta-monitor-manager-native.c:343 #12 meta_monitor_manager_apply_monitors_config() at ../src/backends/meta-monitor-manager.c:706 #13 meta_monitor_manager_ensure_configured() at ../src/backends/meta-monitor-manager.c:779 #14 meta_monitor_manager_reconfigure() at ../src/backends/meta-monitor-manager.c:3738 #15 meta_monitor_manager_reload() at ../src/backends/meta-monitor-manager.c:3745 or the following on gnome-43: #0 meta_wayland_surface_get_buffer at ../src/wayland/meta-wayland-surface.c:441 #1 meta_cursor_sprite_wayland_get_buffer at ../src/wayland/meta-cursor-sprite-wayland.c:83 #2 realize_cursor_sprite_from_wl_buffer_for_gpu at ../src/backends/native/meta-cursor-renderer-native.c:1612 #3 realize_cursor_sprite_for_gpu at ../src/backends/native/meta-cursor-renderer-native.c:1836 #4 realize_cursor_sprite at ../src/backends/native/meta-cursor-renderer-native.c:1854 #5 meta_cursor_renderer_native_update_cursor at ../src/backends/native/meta-cursor-renderer-native.c:1087 #6 meta_cursor_renderer_update_cursor at ../src/backends/meta-cursor-renderer.c:413 #7 meta_cursor_renderer_force_update at ../src/backends/meta-cursor-renderer.c:448 #8 update_cursors at ../src/backends/meta-backend.c:344 #9 meta_backend_monitors_changed at ../src/backends/meta-backend.c:354 Related: https://bugzilla.redhat.com/show_bug.cgi?id=2185113 Part-of: --- meson.build | 1 + src/backends/meta-backend-private.h | 1 + src/backends/meta-cursor-renderer.h | 2 + src/tests/meson.build | 8 + .../kms-cursor-hotplug-off.xml | 30 ++ .../monitor-configs/kms-cursor-hotplug-on.xml | 40 +++ src/tests/native-kms-cursor-hotplug.c | 166 ++++++++++ .../kms-cursor-hotplug-helper.c | 286 ++++++++++++++++++ src/tests/wayland-test-clients/meson.build | 6 + src/wayland/meta-cursor-sprite-wayland.h | 1 + src/wayland/meta-wayland-cursor-surface.c | 6 + 11 files changed, 547 insertions(+) create mode 100644 src/tests/monitor-configs/kms-cursor-hotplug-off.xml create mode 100644 src/tests/monitor-configs/kms-cursor-hotplug-on.xml create mode 100644 src/tests/native-kms-cursor-hotplug.c create mode 100644 src/tests/wayland-test-clients/kms-cursor-hotplug-helper.c diff --git a/meson.build b/meson.build index 8947069b2..9793b4029 100644 --- a/meson.build +++ b/meson.build @@ -212,6 +212,7 @@ endif if have_wayland wayland_server_dep = dependency('wayland-server', version: wayland_server_req) wayland_client_dep = dependency('wayland-client', version: wayland_server_req) + wayland_cursor_dep = dependency('wayland-cursor') wayland_protocols_dep = dependency('wayland-protocols', version: wayland_protocols_req) wayland_egl_dep = dependency('wayland-egl') diff --git a/src/backends/meta-backend-private.h b/src/backends/meta-backend-private.h index e3e8ecb63..f133db0f2 100644 --- a/src/backends/meta-backend-private.h +++ b/src/backends/meta-backend-private.h @@ -135,6 +135,7 @@ META_EXPORT_TEST MetaCursorTracker * meta_backend_get_cursor_tracker (MetaBackend *backend); MetaCursorRenderer * meta_backend_get_cursor_renderer_for_device (MetaBackend *backend, ClutterInputDevice *device); +META_EXPORT_TEST MetaCursorRenderer * meta_backend_get_cursor_renderer (MetaBackend *backend); META_EXPORT_TEST MetaRenderer * meta_backend_get_renderer (MetaBackend *backend); diff --git a/src/backends/meta-cursor-renderer.h b/src/backends/meta-cursor-renderer.h index 550c59177..543988ad4 100644 --- a/src/backends/meta-cursor-renderer.h +++ b/src/backends/meta-cursor-renderer.h @@ -29,6 +29,7 @@ #include "backends/meta-backend-types.h" #include "backends/meta-cursor.h" +#include "core/util-private.h" #define META_TYPE_HW_CURSOR_INHIBITOR (meta_hw_cursor_inhibitor_get_type ()) G_DECLARE_INTERFACE (MetaHwCursorInhibitor, meta_hw_cursor_inhibitor, @@ -64,6 +65,7 @@ void meta_cursor_renderer_set_cursor (MetaCursorRenderer *renderer, void meta_cursor_renderer_update_position (MetaCursorRenderer *renderer); void meta_cursor_renderer_force_update (MetaCursorRenderer *renderer); +META_EXPORT_TEST MetaCursorSprite * meta_cursor_renderer_get_cursor (MetaCursorRenderer *renderer); graphene_rect_t meta_cursor_renderer_calculate_rect (MetaCursorRenderer *renderer, diff --git a/src/tests/meson.build b/src/tests/meson.build index 3768b83da..1b0a93ed7 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -409,6 +409,14 @@ if have_native_tests ], 'variants': kms_test_variants, }, + { + 'name': 'kms-cursor-hotplug', + 'suite': 'backends/native', + 'sources': [ + 'native-kms-cursor-hotplug.c', + wayland_test_utils, + ], + }, ] privileged_test_cases += kms_test_cases diff --git a/src/tests/monitor-configs/kms-cursor-hotplug-off.xml b/src/tests/monitor-configs/kms-cursor-hotplug-off.xml new file mode 100644 index 000000000..2eccd7887 --- /dev/null +++ b/src/tests/monitor-configs/kms-cursor-hotplug-off.xml @@ -0,0 +1,30 @@ + + + + 0 + 0 + yes + + + Meta-0 + MetaTestVendor + MetaVirtualMonitor + 0x1234 + + + 100 + 100 + 60 + + + + + + Virtual-1 + unknown + unknown + unknown + + + + diff --git a/src/tests/monitor-configs/kms-cursor-hotplug-on.xml b/src/tests/monitor-configs/kms-cursor-hotplug-on.xml new file mode 100644 index 000000000..67c0aacbd --- /dev/null +++ b/src/tests/monitor-configs/kms-cursor-hotplug-on.xml @@ -0,0 +1,40 @@ + + + + 0 + 0 + yes + + + Virtual-1 + unknown + unknown + unknown + + + 1024 + 768 + 60.003841 + + + + + 1024 + 0 + no + + + Meta-0 + MetaTestVendor + MetaVirtualMonitor + 0x1234 + + + 100 + 100 + 60 + + + + + diff --git a/src/tests/native-kms-cursor-hotplug.c b/src/tests/native-kms-cursor-hotplug.c new file mode 100644 index 000000000..1a9978c19 --- /dev/null +++ b/src/tests/native-kms-cursor-hotplug.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2022 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#include "config.h" + +#include "backends/meta-monitor-config-manager.h" +#include "backends/meta-virtual-monitor.h" +#include "core/window-private.h" +#include "meta-test/meta-context-test.h" +#include "meta/meta-backend.h" +#include "meta/meta-cursor-tracker.h" +#include "tests/meta-test-utils.h" +#include "tests/meta-wayland-test-driver.h" +#include "tests/meta-wayland-test-utils.h" +#include "tests/native-screen-cast.h" +#include "tests/native-virtual-monitor.h" +#include "wayland/meta-cursor-sprite-wayland.h" +#include "wayland/meta-wayland-private.h" +#include "wayland/meta-wayland-seat.h" + +static MetaContext *test_context; + +static void +meta_test_cursor_hotplug (void) +{ + MetaBackend *backend = meta_context_get_backend (test_context); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaCursorRenderer *cursor_renderer = meta_backend_get_cursor_renderer (backend); + MetaWaylandCompositor *wayland_compositor = + meta_context_get_wayland_compositor (test_context); + MetaWaylandSeat *wayland_seat = wayland_compositor->seat; + g_autoptr (MetaWaylandTestDriver) test_driver = NULL; + MetaCursorSprite *cursor_sprite; + g_autoptr (MetaVirtualMonitorInfo) monitor_info = NULL; + MetaVirtualMonitor *virtual_monitor; + ClutterSeat *seat; + g_autoptr (ClutterVirtualInputDevice) virtual_pointer = NULL; + MetaWaylandTestClient *test_client; + MetaWindow *window; + GError *error = NULL; + + test_driver = meta_wayland_test_driver_new (wayland_compositor); + + seat = meta_backend_get_default_seat (backend); + virtual_pointer = clutter_seat_create_virtual_device (seat, + CLUTTER_POINTER_DEVICE); + + meta_set_custom_monitor_config_full (backend, "kms-cursor-hotplug-off.xml", + META_MONITORS_CONFIG_FLAG_NONE); + + monitor_info = meta_virtual_monitor_info_new (100, 100, 60.0, + "MetaTestVendor", + "MetaVirtualMonitor", + "0x1234"); + virtual_monitor = meta_monitor_manager_create_virtual_monitor (monitor_manager, + monitor_info, + &error); + if (!virtual_monitor) + g_error ("Failed to create virtual monitor: %s", error->message); + meta_monitor_manager_reload (monitor_manager); + + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, + g_get_monotonic_time (), + 50, 50); + + test_client = meta_wayland_test_client_new (test_context, + "kms-cursor-hotplug-helper"); + if (!test_client) + g_error ("Failed to launch test client: %s", error->message); + + while (TRUE) + { + window = meta_find_window_from_title (test_context, + "kms-cursor-hotplug-helper"); + if (window && window->visible_to_compositor) + break; + g_main_context_iteration (NULL, TRUE); + } + + meta_window_move_frame (window, FALSE, 0, 0); + meta_wait_for_paint (test_context); + + cursor_renderer = meta_backend_get_cursor_renderer (backend); + + while (TRUE) + { + cursor_sprite = meta_cursor_renderer_get_cursor (cursor_renderer); + if (cursor_sprite) + break; + g_main_context_iteration (NULL, TRUE); + } + g_assert_true (META_IS_CURSOR_SPRITE_WAYLAND (cursor_sprite)); + + /* + * This tests a particular series of events: + * + * 1) Unplug the mouse + * 2) Client attaches a new cursor buffer + * 3) Client destroys cursor surface + * 4) Monitor hotplug + * + * This would cause a NULL pointer deference when getting the buffer from the + * cursor surface when trying to realize the hardware cursor buffer on the + * hotplugged monitor. + */ + + g_clear_object (&virtual_pointer); + while (!(wayland_seat->capabilities & WL_SEAT_CAPABILITY_POINTER)) + g_main_context_iteration (NULL, TRUE); + + meta_wayland_test_driver_emit_sync_event (test_driver, 0); + meta_wayland_test_driver_wait_for_sync_point (test_driver, 0); + + meta_set_custom_monitor_config_full (backend, "kms-cursor-hotplug-on.xml", + META_MONITORS_CONFIG_FLAG_NONE); + meta_monitor_manager_reload (monitor_manager); + meta_wait_for_paint (test_context); + + meta_wayland_test_driver_emit_sync_event (test_driver, 1); + meta_wayland_test_client_finish (test_client); +} + +static void +init_tests (void) +{ + g_test_add_func ("/wayland/cursor-hotplug", + meta_test_cursor_hotplug); +} + +int +main (int argc, + char **argv) +{ + g_autoptr (MetaContext) context = NULL; + + context = meta_create_test_context (META_CONTEXT_TEST_TYPE_VKMS, + META_CONTEXT_TEST_FLAG_NO_X11 | + META_CONTEXT_TEST_FLAG_TEST_CLIENT); + g_assert (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/kms-cursor-hotplug-helper.c b/src/tests/wayland-test-clients/kms-cursor-hotplug-helper.c new file mode 100644 index 000000000..ac9dd5822 --- /dev/null +++ b/src/tests/wayland-test-clients/kms-cursor-hotplug-helper.c @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2023 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 +#include + +#include "wayland-test-client-utils.h" + +static WaylandDisplay *display; + +static struct wl_registry *wl_registry; +static struct wl_seat *wl_seat; +static struct wl_pointer *wl_pointer; +static uint32_t enter_serial; + +static struct wl_surface *surface; +static struct xdg_surface *xdg_surface; +static struct xdg_toplevel *xdg_toplevel; +struct wl_surface *cursor_surface; +struct wl_cursor_theme *cursor_theme; +struct wl_cursor *cursor; +struct wl_cursor *cursor2; + +static gboolean running; + +static void +init_surface (void) +{ + xdg_toplevel_set_title (xdg_toplevel, "kms-cursor-hotplug-helper"); + wl_surface_commit (surface); +} + +static void +draw_main (int width, + int height) +{ + draw_surface (display, surface, width, height, 0xff00ff00); +} + +static void +handle_xdg_toplevel_configure (void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height, + struct wl_array *state) +{ +} + +static void +handle_xdg_toplevel_close (void *data, + struct xdg_toplevel *xdg_toplevel) +{ + g_assert_not_reached (); +} + +static void +handle_xdg_toplevel_configure_bounds (void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t bounds_width, + int32_t bounds_height) +{ +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + handle_xdg_toplevel_configure, + handle_xdg_toplevel_close, + handle_xdg_toplevel_configure_bounds, +}; + +static void +handle_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) +{ + draw_main (100, 100); + xdg_surface_ack_configure (xdg_surface, serial); + wl_surface_commit (surface); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + handle_xdg_surface_configure, +}; + + +static void +pointer_handle_enter (void *data, + struct wl_pointer *pointer, + uint32_t serial, + struct wl_surface *surface, + wl_fixed_t sx, + wl_fixed_t sy) +{ + struct wl_buffer *buffer; + struct wl_cursor_image *image; + + image = cursor->images[0]; + buffer = wl_cursor_image_get_buffer (image); + + enter_serial = serial; + wl_pointer_set_cursor (pointer, serial, + cursor_surface, + image->hotspot_x, + image->hotspot_y); + wl_surface_attach (cursor_surface, buffer, 0, 0); + wl_surface_damage (cursor_surface, 0, 0, + image->width, image->height); + wl_surface_commit (cursor_surface); +} + +static void +pointer_handle_leave (void *data, + struct wl_pointer *pointer, + uint32_t serial, + struct wl_surface *surface) +{ +} + +static void +pointer_handle_motion (void *data, + struct wl_pointer *pointer, + uint32_t time, + wl_fixed_t sx, + wl_fixed_t sy) +{ +} + +static void +pointer_handle_button (void *data, + struct wl_pointer *pointer, + uint32_t serial, + uint32_t time, + uint32_t button, + uint32_t state) +{ +} + +static void +pointer_handle_axis (void *data, + struct wl_pointer *pointer, + uint32_t time, + uint32_t axis, + wl_fixed_t value) +{ +} + +static const struct wl_pointer_listener pointer_listener = { + pointer_handle_enter, + pointer_handle_leave, + pointer_handle_motion, + pointer_handle_button, + pointer_handle_axis, +}; + +static void +seat_handle_capabilities (void *data, + struct wl_seat *wl_seat, + enum wl_seat_capability caps) +{ + if (caps & WL_SEAT_CAPABILITY_POINTER) + { + wl_pointer = wl_seat_get_pointer (wl_seat); + wl_pointer_add_listener (wl_pointer, &pointer_listener, NULL); + } +} + +static void +seat_handle_name (void *data, + struct wl_seat *seat, + const char *name) +{ +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, + seat_handle_name, +}; + +static void +handle_registry_global (void *data, + struct wl_registry *registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + if (strcmp (interface, "wl_seat") == 0) + { + wl_seat = wl_registry_bind (registry, id, + &wl_seat_interface, + 1); + wl_seat_add_listener (wl_seat, &seat_listener, NULL); + } +} + +static void +handle_registry_global_remove (void *data, + struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + handle_registry_global, + handle_registry_global_remove +}; + +static void +on_sync_event (WaylandDisplay *display, + uint32_t serial) +{ + if (serial == 1) + { + running = FALSE; + } + else if (serial == 0) + { + struct wl_buffer *buffer; + struct wl_cursor_image *image; + + image = cursor2->images[0]; + buffer = wl_cursor_image_get_buffer (image); + + wl_surface_attach (cursor_surface, buffer, 0, 0); + wl_surface_damage (cursor_surface, 0, 0, + image->width, image->height); + wl_surface_commit (cursor_surface); + + wl_surface_destroy (cursor_surface); + + test_driver_sync_point (display->test_driver, 0, NULL); + } +} + +int +main (int argc, + char **argv) +{ + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + wl_registry = wl_display_get_registry (display->display); + wl_registry_add_listener (wl_registry, ®istry_listener, display); + wl_display_roundtrip (display->display); + + g_signal_connect (display, "sync-event", G_CALLBACK (on_sync_event), NULL); + + surface = wl_compositor_create_surface (display->compositor); + xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, surface); + xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, NULL); + xdg_toplevel = xdg_surface_get_toplevel (xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + + cursor_surface = wl_compositor_create_surface (display->compositor); + cursor_theme = wl_cursor_theme_load (NULL, 24, display->shm); + cursor = wl_cursor_theme_get_cursor (cursor_theme, "left_ptr"); + cursor2 = wl_cursor_theme_get_cursor (cursor_theme, "right_ptr"); + g_assert_nonnull (cursor); + g_assert_nonnull (cursor2); + + init_surface (); + wl_surface_commit (surface); + + running = TRUE; + while (running) + { + if (wl_display_dispatch (display->display) == -1) + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + diff --git a/src/tests/wayland-test-clients/meson.build b/src/tests/wayland-test-clients/meson.build index eafda40e4..6c1b76a25 100644 --- a/src/tests/wayland-test-clients/meson.build +++ b/src/tests/wayland-test-clients/meson.build @@ -56,6 +56,12 @@ wayland_test_clients = [ { 'name': 'fullscreen', }, + { + 'name': 'kms-cursor-hotplug-helper', + 'extra_deps': [ + wayland_cursor_dep, + ], + }, { 'name': 'dma-buf-scanout', 'extra_deps': [ diff --git a/src/wayland/meta-cursor-sprite-wayland.h b/src/wayland/meta-cursor-sprite-wayland.h index 6beb0b69a..e0f0c0d4b 100644 --- a/src/wayland/meta-cursor-sprite-wayland.h +++ b/src/wayland/meta-cursor-sprite-wayland.h @@ -25,6 +25,7 @@ #include "wayland/meta-wayland-surface.h" #define META_TYPE_CURSOR_SPRITE_WAYLAND meta_cursor_sprite_wayland_get_type () +META_EXPORT_TEST G_DECLARE_FINAL_TYPE (MetaCursorSpriteWayland, meta_cursor_sprite_wayland, META, CURSOR_SPRITE_WAYLAND, MetaCursorSprite) diff --git a/src/wayland/meta-wayland-cursor-surface.c b/src/wayland/meta-wayland-cursor-surface.c index 812a0db70..c75fe0ee3 100644 --- a/src/wayland/meta-wayland-cursor-surface.c +++ b/src/wayland/meta-wayland-cursor-surface.c @@ -219,6 +219,10 @@ meta_wayland_cursor_surface_dispose (GObject *object) META_WAYLAND_CURSOR_SURFACE (object); MetaWaylandCursorSurfacePrivate *priv = meta_wayland_cursor_surface_get_instance_private (cursor_surface); + MetaWaylandSurface *surface = + meta_wayland_surface_role_get_surface (META_WAYLAND_SURFACE_ROLE (object)); + MetaWaylandSeat *seat = surface->compositor->seat; + MetaWaylandPointer *pointer = seat->pointer; MetaWaylandFrameCallback *cb, *next; wl_list_for_each_safe (cb, next, &priv->frame_callbacks, link) @@ -242,6 +246,8 @@ meta_wayland_cursor_surface_dispose (GObject *object) g_clear_object (&priv->buffer); } + meta_wayland_pointer_update_cursor_surface (pointer); + G_OBJECT_CLASS (meta_wayland_cursor_surface_parent_class)->dispose (object); }