diff --git a/src/backends/meta-backend-private.h b/src/backends/meta-backend-private.h index 1930a1a33..546c612e2 100644 --- a/src/backends/meta-backend-private.h +++ b/src/backends/meta-backend-private.h @@ -117,6 +117,7 @@ void meta_backend_prepare_shutdown (MetaBackend *backend); META_EXPORT_TEST ClutterBackend * meta_backend_get_clutter_backend (MetaBackend *backend); +META_EXPORT_TEST ClutterSeat * meta_backend_get_default_seat (MetaBackend *bakcend); MetaIdleMonitor * meta_backend_get_idle_monitor (MetaBackend *backend, diff --git a/src/compositor/meta-window-actor-private.h b/src/compositor/meta-window-actor-private.h index adae06547..035eebb08 100644 --- a/src/compositor/meta-window-actor-private.h +++ b/src/compositor/meta-window-actor-private.h @@ -85,6 +85,8 @@ void meta_window_actor_assign_surface_actor (MetaWindowActor *self, META_EXPORT_TEST MetaWindowActor *meta_window_actor_from_window (MetaWindow *window); + +META_EXPORT_TEST MetaWindowActor *meta_window_actor_from_actor (ClutterActor *actor); void meta_window_actor_set_geometry_scale (MetaWindowActor *window_actor, diff --git a/src/tests/wayland-test-clients/meson.build b/src/tests/wayland-test-clients/meson.build index 51fe52061..2b3c83389 100644 --- a/src/tests/wayland-test-clients/meson.build +++ b/src/tests/wayland-test-clients/meson.build @@ -48,6 +48,7 @@ common_sources = [ wayland_test_clients = [ 'subsurface-remap-toplevel', 'subsurface-reparenting', + 'subsurface-parent-unmapped', 'invalid-subsurfaces', 'invalid-xdg-shell-actions', 'xdg-apply-limits', diff --git a/src/tests/wayland-test-clients/subsurface-parent-unmapped.c b/src/tests/wayland-test-clients/subsurface-parent-unmapped.c new file mode 100644 index 000000000..0618e65c0 --- /dev/null +++ b/src/tests/wayland-test-clients/subsurface-parent-unmapped.c @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2019 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 +#include + +#include "wayland-test-client-utils.h" + +#include "test-driver-client-protocol.h" +#include "xdg-shell-client-protocol.h" + +static struct wl_display *display; +static struct wl_registry *registry; +static struct wl_compositor *compositor; +static struct wl_subcompositor *subcompositor; +static struct xdg_wm_base *xdg_wm_base; +static struct wl_shm *shm; +static struct wl_seat *seat; +static struct wl_pointer *pointer; +static struct test_driver *test_driver; + +static struct wl_surface *toplevel_surface; +static struct xdg_surface *toplevel_xdg_surface; +static struct xdg_toplevel *xdg_toplevel; + +static struct wl_surface *popup_surface; +static struct xdg_surface *popup_xdg_surface; +static struct xdg_popup *xdg_popup; + +static struct wl_surface *subsurface_surface; +static struct wl_subsurface *subsurface; + +static void +handle_buffer_release (void *data, + struct wl_buffer *buffer) +{ + wl_buffer_destroy (buffer); +} + +static const struct wl_buffer_listener buffer_listener = { + handle_buffer_release +}; + +static gboolean +create_shm_buffer (int width, + int height, + struct wl_buffer **out_buffer, + void **out_data, + int *out_size) +{ + struct wl_shm_pool *pool; + static struct wl_buffer *buffer; + int fd, size, stride; + int bytes_per_pixel; + void *data; + + bytes_per_pixel = 4; + stride = width * bytes_per_pixel; + size = stride * height; + + fd = create_anonymous_file (size); + if (fd < 0) + { + fprintf (stderr, "Creating a buffer file for %d B failed: %m\n", + size); + return FALSE; + } + + data = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) + { + fprintf (stderr, "mmap failed: %m\n"); + close (fd); + return FALSE; + } + + pool = wl_shm_create_pool (shm, fd, size); + buffer = wl_shm_pool_create_buffer (pool, 0, + width, height, + stride, + WL_SHM_FORMAT_ARGB8888); + wl_buffer_add_listener (buffer, &buffer_listener, buffer); + wl_shm_pool_destroy (pool); + close (fd); + + *out_buffer = buffer; + *out_data = data; + *out_size = size; + + return TRUE; +} + +static void +fill (void *buffer_data, + int width, + int height, + uint32_t color) +{ + uint32_t *pixels = buffer_data; + int x, y; + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + pixels[y * width + x] = color; + } +} + +static void +draw (struct wl_surface *surface, + int width, + int height, + uint32_t color) +{ + struct wl_buffer *buffer; + void *buffer_data; + int size; + + if (!create_shm_buffer (width, height, + &buffer, &buffer_data, &size)) + g_error ("Failed to create shm buffer"); + + fill (buffer_data, width, height, color); + + wl_surface_attach (surface, buffer, 0, 0); +} + +static void +draw_main (void) +{ + draw (toplevel_surface, 200, 200, 0xff00ffff); +} + +static void +draw_popup (void) +{ + draw (popup_surface, 100, 100, 0xff005500); +} + +static void +draw_subsurface (void) +{ + draw (subsurface_surface, 100, 50, 0xff001f00); +} + +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 const struct xdg_toplevel_listener xdg_toplevel_listener = { + handle_xdg_toplevel_configure, + handle_xdg_toplevel_close, +}; + +static void +handle_toplevel_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) +{ + xdg_surface_ack_configure (xdg_surface, serial); + draw_main (); + wl_surface_commit (toplevel_surface); + wl_display_flush (display); +} + +static const struct xdg_surface_listener toplevel_xdg_surface_listener = { + handle_toplevel_xdg_surface_configure, +}; + +static void +handle_xdg_wm_base_ping (void *data, + struct xdg_wm_base *xdg_wm_base, + uint32_t serial) +{ + xdg_wm_base_pong (xdg_wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + handle_xdg_wm_base_ping, +}; + +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) +{ +} + +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 +handle_popup_frame_callback (void *data, + struct wl_callback *callback, + uint32_t time) +{ + wl_callback_destroy (callback); + test_driver_sync_point (test_driver, 0, popup_surface); +} + +static const struct wl_callback_listener frame_listener = { + handle_popup_frame_callback, +}; + +static void +handle_popup_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) +{ + struct wl_callback *frame_callback; + + draw_popup (); + + draw_subsurface (); + wl_surface_commit (subsurface_surface); + + xdg_surface_ack_configure (xdg_surface, serial); + frame_callback = wl_surface_frame (popup_surface); + wl_callback_add_listener (frame_callback, &frame_listener, NULL); + wl_surface_commit (popup_surface); + wl_display_flush (display); +} + +static const struct xdg_surface_listener popup_xdg_surface_listener = { + handle_popup_xdg_surface_configure, +}; + +static void +pointer_handle_button (void *data, + struct wl_pointer *pointer, + uint32_t serial, + uint32_t time, + uint32_t button, + uint32_t state) +{ + struct xdg_positioner *positioner; + static int click_count = 0; + + if (button != BTN_LEFT || state != 1) + return; + + /* Create a grabbing popup surface */ + popup_xdg_surface = xdg_wm_base_get_xdg_surface (xdg_wm_base, + popup_surface); + xdg_surface_add_listener (popup_xdg_surface, + &popup_xdg_surface_listener, NULL); + positioner = xdg_wm_base_create_positioner (xdg_wm_base); + xdg_positioner_set_size (positioner, 100, 100); + xdg_positioner_set_anchor_rect (positioner, 0, 0, 1, 1); + xdg_popup = xdg_surface_get_popup (popup_xdg_surface, toplevel_xdg_surface, + positioner); + xdg_positioner_destroy (positioner); + xdg_popup_grab (xdg_popup, seat, serial); + wl_surface_commit (popup_surface); + + if (click_count == 1) + { + /* This ensure that the second time the popup is opened, the commit + * is handled accurately. This passing verifies we don't reproduce + * https://gitlab.gnome.org/GNOME/mutter/-/issues/1828. + */ + wl_display_roundtrip (display); + exit (EXIT_SUCCESS); + } + + click_count++; +} + +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) + { + pointer = wl_seat_get_pointer (wl_seat); + wl_pointer_add_listener (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 +test_driver_handle_sync_event (void *data, + struct test_driver *test_driver, + uint32_t serial) +{ + g_assert (serial == 0); + + /* Sync event 0 is sent when the popup window actor is destryed; + * prepare for opening a popup for the same wl_surface. + */ + wl_surface_attach (popup_surface, NULL, 0, 0); + wl_surface_commit (popup_surface); + g_clear_pointer (&xdg_popup, xdg_popup_destroy); + g_clear_pointer (&popup_xdg_surface, xdg_surface_destroy); + + /* This will trigger a click again, opening the popup a second time. */ + test_driver_sync_point (test_driver, 1, toplevel_surface); +} + +static const struct test_driver_listener test_driver_listener = { + test_driver_handle_sync_event, +}; + +static void +handle_registry_global (void *data, + struct wl_registry *registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + if (strcmp (interface, "wl_compositor") == 0) + { + compositor = wl_registry_bind (registry, id, &wl_compositor_interface, 1); + } + else if (strcmp (interface, "wl_subcompositor") == 0) + { + subcompositor = wl_registry_bind (registry, + id, &wl_subcompositor_interface, 1); + } + else if (strcmp (interface, "xdg_wm_base") == 0) + { + xdg_wm_base = wl_registry_bind (registry, id, + &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener (xdg_wm_base, &xdg_wm_base_listener, NULL); + } + else if (strcmp (interface, "wl_shm") == 0) + { + shm = wl_registry_bind (registry, + id, &wl_shm_interface, 1); + } + else if (strcmp (interface, "wl_seat") == 0) + { + seat = wl_registry_bind (registry, id, &wl_seat_interface, 1); + wl_seat_add_listener (seat, &seat_listener, NULL); + } + else if (strcmp (interface, "test_driver") == 0) + { + test_driver = wl_registry_bind (registry, id, &test_driver_interface, 1); + test_driver_add_listener (test_driver, &test_driver_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 +}; + +int +main (int argc, + char **argv) +{ + display = wl_display_connect (NULL); + registry = wl_display_get_registry (display); + wl_registry_add_listener (registry, ®istry_listener, NULL); + wl_display_roundtrip (display); + + if (!shm) + { + fprintf (stderr, "No wl_shm global\n"); + return EXIT_FAILURE; + } + + if (!xdg_wm_base) + { + fprintf (stderr, "No xdg_wm_base global\n"); + return EXIT_FAILURE; + } + + wl_display_roundtrip (display); + + g_assert_nonnull (test_driver); + + /* + * This test case does the following: + * + * 1) Open a toplevel + * 2) Open a popup in response to a pointer click + * 3) Place a subsurface on that popup + * 4) After painting, get the popup dismissed by the compositor + * 5) Once the popup window actor is destroyed, trigger a new pointer click + * 6) Open the popup again using the same wl_surface, thus with the same + * subsurface association set up. + */ + + toplevel_surface = wl_compositor_create_surface (compositor); + toplevel_xdg_surface = xdg_wm_base_get_xdg_surface (xdg_wm_base, + toplevel_surface); + xdg_surface_add_listener (toplevel_xdg_surface, + &toplevel_xdg_surface_listener, NULL); + xdg_toplevel = xdg_surface_get_toplevel (toplevel_xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + xdg_toplevel_set_title (xdg_toplevel, "subsurface-parent-unmapped"); + wl_surface_commit (toplevel_surface); + + popup_surface = wl_compositor_create_surface (compositor); + subsurface_surface = wl_compositor_create_surface (compositor); + subsurface = wl_subcompositor_get_subsurface (subcompositor, + subsurface_surface, + popup_surface); + wl_subsurface_set_position (subsurface, 0, 0); + wl_subsurface_set_desync (subsurface); + + while (TRUE) + { + if (wl_display_dispatch (display) == -1) + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/src/tests/wayland-unit-tests.c b/src/tests/wayland-unit-tests.c index 3a73b9e54..e4341a54f 100644 --- a/src/tests/wayland-unit-tests.c +++ b/src/tests/wayland-unit-tests.c @@ -20,6 +20,7 @@ #include #include "backends/meta-virtual-monitor.h" +#include "compositor/meta-window-actor-private.h" #include "core/display-private.h" #include "core/window-private.h" #include "meta-test/meta-context-test.h" @@ -37,6 +38,7 @@ typedef struct _WaylandTestClient static MetaContext *test_context; static MetaWaylandTestDriver *test_driver; static MetaVirtualMonitor *virtual_monitor; +static ClutterVirtualInputDevice *virtual_pointer; static char * get_test_client_path (const char *test_client_name) @@ -185,6 +187,187 @@ subsurface_invalid_xdg_shell_actions (void) g_test_assert_expected_messages (); } +static void +on_after_paint (ClutterStage *stage, + ClutterStageView *view, + gboolean *was_painted) +{ + *was_painted = TRUE; +} + +static void +wait_for_paint (ClutterActor *stage) +{ + gboolean was_painted = FALSE; + gulong was_painted_id; + + was_painted_id = g_signal_connect (CLUTTER_STAGE (stage), + "after-paint", + G_CALLBACK (on_after_paint), + &was_painted); + + while (!was_painted) + g_main_context_iteration (NULL, TRUE); + + g_signal_handler_disconnect (stage, was_painted_id); +} + +static gboolean +on_effects_completed_idle (gpointer user_data) +{ + MetaWindowActor *actor = user_data; + MetaBackend *backend = meta_context_get_backend (test_context); + ClutterActor *stage = meta_backend_get_stage (backend); + MetaWindow *window = meta_window_actor_get_meta_window (actor); + MetaRectangle buffer_rect; + + /* Move the window to a known position and perform a mouse click, allowing a + * popup to be mapped. */ + + meta_window_move_frame (window, FALSE, 0, 0); + + clutter_actor_queue_redraw (stage); + clutter_stage_schedule_update (CLUTTER_STAGE (stage)); + + wait_for_paint (stage); + + meta_window_get_buffer_rect (window, &buffer_rect); + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, + CLUTTER_CURRENT_TIME, + buffer_rect.x + 10, + buffer_rect.y + 10); + + clutter_virtual_input_device_notify_button (virtual_pointer, + CLUTTER_CURRENT_TIME, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_PRESSED); + clutter_virtual_input_device_notify_button (virtual_pointer, + CLUTTER_CURRENT_TIME, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_RELEASED); + + return G_SOURCE_REMOVE; +} + +static void +on_effects_completed (MetaWindowActor *actor) +{ + g_idle_add (on_effects_completed_idle, actor); +} + +static void +on_window_added (MetaStack *stack, + MetaWindow *window) +{ + MetaWindowActor *actor = meta_window_actor_from_window (window); + + g_assert_nonnull (actor); + + if (g_strcmp0 (meta_window_get_title (window), + "subsurface-parent-unmapped") != 0) + return; + + g_signal_connect (actor, "effects-completed", + G_CALLBACK (on_effects_completed), + NULL); +} + +static void +on_window_actor_destroyed (MetaWindowActor *actor, + MetaWaylandTestDriver *test_driver) +{ + meta_wayland_test_driver_emit_sync_event (test_driver, 0); +} + +static void +on_unmap_sync_point (MetaWaylandTestDriver *test_driver, + unsigned int sequence, + struct wl_resource *surface_resource, + struct wl_client *wl_client) +{ + if (sequence == 0) + { + /* Dismiss popup by clicking outside. */ + + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, + CLUTTER_CURRENT_TIME, + 390, 390); + + clutter_virtual_input_device_notify_button (virtual_pointer, + CLUTTER_CURRENT_TIME, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_PRESSED); + clutter_virtual_input_device_notify_button (virtual_pointer, + CLUTTER_CURRENT_TIME, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_RELEASED); + + MetaWaylandSurface *surface = wl_resource_get_user_data (surface_resource); + ClutterActor *actor = CLUTTER_ACTOR (meta_wayland_surface_get_actor (surface)); + MetaWindowActor *window_actor = meta_window_actor_from_actor (actor); + g_signal_connect (window_actor, "destroy", + G_CALLBACK (on_window_actor_destroyed), + test_driver); + } + else if (sequence == 1) + { + MetaWaylandSurface *surface = wl_resource_get_user_data (surface_resource); + ClutterActor *actor = CLUTTER_ACTOR (meta_wayland_surface_get_actor (surface)); + MetaWindowActor *window_actor = meta_window_actor_from_actor (actor); + MetaWindow *window = meta_window_actor_get_meta_window (window_actor); + MetaRectangle buffer_rect; + + /* Click inside the window to allow mapping a popup. */ + + meta_window_get_buffer_rect (window, &buffer_rect); + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, + CLUTTER_CURRENT_TIME, + buffer_rect.x + 10, + buffer_rect.y + 10); + + clutter_virtual_input_device_notify_button (virtual_pointer, + CLUTTER_CURRENT_TIME, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_PRESSED); + clutter_virtual_input_device_notify_button (virtual_pointer, + CLUTTER_CURRENT_TIME, + CLUTTER_BUTTON_PRIMARY, + CLUTTER_BUTTON_STATE_RELEASED); + } +} + +static void +subsurface_parent_unmapped (void) +{ + MetaBackend *backend = meta_context_get_backend (test_context); + MetaDisplay *display = meta_context_get_display (test_context); + WaylandTestClient *wayland_test_client; + ClutterSeat *seat; + gulong window_added_id; + gulong sync_point_id; + + seat = meta_backend_get_default_seat (backend); + virtual_pointer = clutter_seat_create_virtual_device (seat, + CLUTTER_POINTER_DEVICE); + + wayland_test_client = wayland_test_client_new ("subsurface-parent-unmapped"); + + window_added_id = + g_signal_connect (display->stack, "window-added", + G_CALLBACK (on_window_added), + virtual_pointer); + sync_point_id = + g_signal_connect (test_driver, "sync-point", + G_CALLBACK (on_unmap_sync_point), + NULL); + + wayland_test_client_finish (wayland_test_client); + + g_clear_object (&virtual_pointer); + g_signal_handler_disconnect (test_driver, sync_point_id); + g_signal_handler_disconnect (display->stack, window_added_id); +} + typedef enum _ApplyLimitState { APPLY_LIMIT_STATE_INIT, @@ -306,6 +489,8 @@ init_wayland_tests (void) subsurface_invalid_subsurfaces); g_test_add_func ("/wayland/subsurface/invalid-xdg-shell-actions", subsurface_invalid_xdg_shell_actions); + g_test_add_func ("/wayland/subsurface/parent-unmapped", + subsurface_parent_unmapped); g_test_add_func ("/wayland/toplevel/apply-limits", toplevel_apply_limits); g_test_add_func ("/wayland/toplevel/activation", diff --git a/src/wayland/meta-wayland-surface.h b/src/wayland/meta-wayland-surface.h index f0153b23b..cfe5976a5 100644 --- a/src/wayland/meta-wayland-surface.h +++ b/src/wayland/meta-wayland-surface.h @@ -348,6 +348,7 @@ gboolean meta_wayland_surface_is_shortcuts_inhibited (MetaWaylandSurf CoglTexture * meta_wayland_surface_get_texture (MetaWaylandSurface *surface); +META_EXPORT_TEST MetaSurfaceActor * meta_wayland_surface_get_actor (MetaWaylandSurface *surface); void meta_wayland_surface_notify_geometry_changed (MetaWaylandSurface *surface);