From ef456e4d245c355103f366cf098ac32ea2f96062 Mon Sep 17 00:00:00 2001 From: hujianwei Date: Mon, 7 Nov 2022 06:05:08 +0000 Subject: [PATCH] Check in new files for seat tests * test_seat.c: * tests/seat_test.c: * tests/seat_test.png: New files. --- test_seat.c | 790 ++++++++++++++++++++++++++++++++++++++++++++ tests/seat_test.c | 480 +++++++++++++++++++++++++++ tests/seat_test.png | Bin 0 -> 10665 bytes 3 files changed, 1270 insertions(+) create mode 100644 test_seat.c create mode 100644 tests/seat_test.c create mode 100644 tests/seat_test.png diff --git a/test_seat.c b/test_seat.c new file mode 100644 index 0000000..ebcf1e0 --- /dev/null +++ b/test_seat.c @@ -0,0 +1,790 @@ +/* Wayland compositor running on top of an X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 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 3 of the License, or (at your +option) any later version. + +12to11 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 12to11. If not, see . */ + +#include "12to11-test.h" + +/* This file is included by seat.c at the very bottom, so it does not + have to include anything itself! */ + +typedef struct _TestSeatController TestSeatController; +typedef struct _TestXIModifierState TestXIModifierState; +typedef struct _TestXIValuatorState TestXIValuatorState; +typedef struct _TestXIButtonState TestXIButtonState; + +/* The current test seat counter. */ +static unsigned int test_seat_counter; + +/* The test serial counter. */ +static unsigned long request_serial_counter; + +struct _TestSeatController +{ + /* The associated seat. */ + Seat *seat; + + /* The associated controller resource. */ + struct wl_resource *resource; +}; + +struct _TestXIModifierState +{ + /* Modifier state. These fields mean the same as they do in + XIModifierState. */ + int base; + int latched; + int locked; + int effective; +}; + +struct _TestXIValuatorState +{ + /* The mask of set valuators. */ + unsigned char *mask; + + /* Sparse array of valuators. */ + double *values; + + /* The length of the mask. */ + size_t mask_len; + + /* The number of valuators set. */ + int num_valuators; +}; + +struct _TestXIButtonState +{ + /* Mask of set buttons. Always between 0 and 8. */ + unsigned char mask[XIMaskLen (8)]; +}; + + + +static void +DestroyXIModifierState (struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static void +SetValues (struct wl_client *client, struct wl_resource *resource, + int32_t base, int32_t latched, int32_t locked, int32_t effective) +{ + TestXIModifierState *state; + + state = wl_resource_get_user_data (resource); + state->base = base; + state->latched = latched; + state->locked = locked; + state->effective = effective; +} + +static const struct test_XIModifierState_interface XIModifierState_impl = + { + .destroy = DestroyXIModifierState, + .set_values = SetValues, + }; + +static void +HandleXIModifierStateDestroy (struct wl_resource *resource) +{ + TestXIModifierState *state; + + state = wl_resource_get_user_data (resource); + XLFree (state); +} + + + +static void +DestroyXIButtonState (struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static void +AddButton (struct wl_client *client, struct wl_resource *resource, + uint32_t button) +{ + TestXIButtonState *state; + + state = wl_resource_get_user_data (resource); + + if (button < 1 || button > 8) + /* The button is invalid. */ + wl_resource_post_error (resource, TEST_MANAGER_ERROR_INVALID_BUTTON, + "invalid button specified"); + else + SetMask (state->mask, button); +} + +static void +RemoveButton (struct wl_client *client, struct wl_resource *resource, + uint32_t button) +{ + TestXIButtonState *state; + + state = wl_resource_get_user_data (resource); + + if (button < 1 || button > 8) + wl_resource_post_error (resource, TEST_MANAGER_ERROR_INVALID_BUTTON, + "invalid button specified"); + else + ClearMask (state->mask, button); +} + +static const struct test_XIButtonState_interface XIButtonState_impl = + { + .destroy = DestroyXIButtonState, + .add_button = AddButton, + .remove_button = RemoveButton, + }; + +static void +HandleXIButtonStateDestroy (struct wl_resource *resource) +{ + TestXIButtonState *state; + + state = wl_resource_get_user_data (resource); + XLFree (state); +} + + + +static void +AddValuatorToTestXIValuatorState (struct wl_client *client, + struct wl_resource *resource, + uint32_t valuator, + wl_fixed_t value) +{ + TestXIValuatorState *state; + double *old_values, *new_values; + size_t i, j; + + if (valuator < 1 || valuator > 65535) + { + /* The valuator cannot be represented. */ + wl_resource_post_error (resource, TEST_MANAGER_ERROR_INVALID_VALUATOR, + "the specified valuator cannot be represented"); + return; + } + + state = wl_resource_get_user_data (resource); + + /* Check if the valuator is already present and post a + value_exists error if so. */ + if (XIMaskLen (valuator) <= state->mask_len + && MaskIsSet (state->mask, valuator)) + wl_resource_post_error (resource, TEST_MANAGER_ERROR_VALUE_EXISTS, + "the specified valuator is already set"); + else + { + /* If the mask needs to be expanded, do it now. */ + if (state->mask_len < XIMaskLen (valuator)) + { + state->mask = XLRealloc (state->mask, + state->mask_len); + + /* Clear the newly allocated part of the mask. */ + memset (state->mask + state->mask_len, + 0, XIMaskLen (valuator) - state->mask_len); + } + + SetMask (state->mask, valuator); + state->num_valuators++; + + /* Now, rewrite the sparse array of values. */ + old_values = state->values; + new_values = XLCalloc (state->num_valuators, + sizeof *state->values); + + for (i = 0, j = 0; i < MAX (state->mask_len, + XIMaskLen (valuator)) * 8; ++i) + { + if (i == valuator) + /* Insert the new value. */ + new_values[j++] = wl_fixed_to_double (value); + else if (XIMaskIsSet (state->mask, valuator)) + /* Use the old value. */ + new_values[j++] = *old_values++; + } + + /* Free the old values. */ + XLFree (state->values); + + /* Assign the new values and mask length to the state. */ + state->values = new_values; + state->mask_len = MAX (state->mask_len, + XIMaskLen (valuator)); + } +} + +static const struct test_XIValuatorState_interface XIValuatorState_impl = + { + .add_valuator = AddValuatorToTestXIValuatorState, + }; + +static void +HandleXIValuatorStateDestroy (struct wl_resource *resource) +{ + TestXIValuatorState *state; + + state = wl_resource_get_user_data (resource); + + /* Free the mask. */ + XLFree (state->mask); + + /* Free the values. */ + XLFree (state->values); + + /* Free the state itself. */ + XLFree (state); +} + + + +static void +DestroyTestSeatController (struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static void +BindSeat (struct wl_client *client, struct wl_resource *resource, + uint32_t version, uint32_t id) +{ + TestSeatController *controller; + + controller = wl_resource_get_user_data (resource); + + if (!version || version > 8) + wl_resource_post_error (resource, TEST_MANAGER_ERROR_BAD_SEAT_VERSION, + "the specified version of the wl_seat interface" + " is not supported"); + else + /* Bind the resource to the seat. */ + HandleBind1 (client, controller->seat, version, id); +} + +static void +GetXIModifierState (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + TestXIModifierState *state; + struct wl_resource *state_resource; + + state = XLSafeMalloc (sizeof *state); + + if (!state) + { + /* Allocating the state structure failed. */ + wl_resource_post_no_memory (resource); + return; + } + + /* Now, try to create the modifier state resource. */ + state_resource + = wl_resource_create (client, &test_XIModifierState_interface, + wl_resource_get_version (resource), id); + + if (!state_resource) + { + /* Allocating the resource failed. */ + XLFree (state); + wl_resource_post_no_memory (resource); + + return; + } + + /* Clear the state. */ + memset (state, 0, sizeof *state); + + /* Set the resource data. */ + wl_resource_set_implementation (state_resource, &XIModifierState_impl, + state, HandleXIModifierStateDestroy); +} + +static void +GetXIButtonState (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + TestXIButtonState *state; + struct wl_resource *state_resource; + + state = XLSafeMalloc (sizeof *state); + + if (!state) + { + /* Allocating the state structure failed. */ + wl_resource_post_no_memory (resource); + return; + } + + /* Now, try to create the button state resource. */ + state_resource + = wl_resource_create (client, &test_XIButtonState_interface, + wl_resource_get_version (resource), id); + + if (!state_resource) + { + /* Allocating the resource failed. */ + XLFree (state); + wl_resource_post_no_memory (resource); + + return; + } + + /* Clear the state. */ + memset (state, 0, sizeof *state); + + /* Set the resource data. */ + wl_resource_set_implementation (state_resource, &XIButtonState_impl, + state, HandleXIButtonStateDestroy); +} + +static void +GetXIValuatorState (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + TestXIValuatorState *state; + struct wl_resource *state_resource; + + state = XLSafeMalloc (sizeof *state); + + if (!state) + { + /* Allocating the state structure failed. */ + wl_resource_post_no_memory (resource); + return; + } + + /* Now, try to create the button state resource. */ + state_resource + = wl_resource_create (client, &test_XIValuatorState_interface, + wl_resource_get_version (resource), id); + + if (!state_resource) + { + /* Allocating the resource failed. */ + XLFree (state); + wl_resource_post_no_memory (resource); + + return; + } + + /* Clear the state. */ + memset (state, 0, sizeof *state); + + /* Set the resource data. */ + wl_resource_set_implementation (state_resource, &XIValuatorState_impl, + state, HandleXIValuatorStateDestroy); +} + + + +static void +TranslateTestButtons (struct wl_resource *resource, XIButtonState *buttons) +{ + TestXIButtonState *state; + + if (!resource) + { + /* Use default values if nothing was specified. */ + + buttons->mask_len = 0; + buttons->mask = NULL; + + return; + } + + /* The mask in buttons will be destroyed along with the resource! */ + state = wl_resource_get_user_data (resource); + buttons->mask_len = sizeof state->mask; + buttons->mask = state->mask; +} + +static void +TranslateTestValuators (struct wl_resource *resource, + XIValuatorState *valuators) +{ + TestXIValuatorState *state; + + if (!resource) + { + /* Use default values if nothing was specified. */ + valuators->mask_len = 0; + valuators->values = NULL; + valuators->mask = NULL; + + return; + } + + state = wl_resource_get_user_data (resource); + valuators->mask_len = state->mask_len; + valuators->mask = state->mask; + valuators->values = state->values; +} + +static void +TranslateTestModifiers (struct wl_resource *resource, + XIModifierState *modifiers) +{ + TestXIModifierState *state; + + if (!resource) + { + /* Use default values if nothing was specified. */ + modifiers->base = 0; + modifiers->latched = 0; + modifiers->locked = 0; + modifiers->effective = 0; + + return; + } + + state = wl_resource_get_user_data (resource); + modifiers->base = state->base; + modifiers->latched = state->latched; + modifiers->locked = state->locked; + modifiers->effective = state->effective; +} + +static void +DispatchTestEvent (TestSeatController *controller, Window window, + XIEvent *event) +{ + Surface *surface; + Subcompositor *subcompositor; + + /* Look up a test surface with the given window and dispatch the + event to it. */ + + surface = XLLookUpTestSurface (window, &subcompositor); + + if (!surface) + /* The client submitted an invalid event window! */ + return; + + if (event->evtype == XI_FocusIn) + DispatchFocusIn (surface, (XIFocusInEvent *) event); + else if (event->evtype == XI_FocusOut) + DispatchFocusOut (surface, (XIFocusOutEvent *) event); + else if (event->evtype == XI_Enter + || event->evtype == XI_Leave) + DispatchEntryExit (subcompositor, (XIEnterEvent *) event); + else if (event->evtype == XI_Motion) + DispatchMotion (subcompositor, (XIDeviceEvent *) event); + else if (event->evtype == XI_ButtonPress + || event->evtype == XI_ButtonRelease) + DispatchButton (subcompositor, (XIDeviceEvent *) event); + else if (event->evtype == XI_KeyPress + || event->evtype == XI_KeyRelease) + DispatchKey ((XIDeviceEvent *) event); + else if (event->evtype == XI_BarrierHit) + DispatchBarrierHit ((XIBarrierEvent *) event); + else if (event->evtype == XI_GesturePinchBegin + || event->evtype == XI_GesturePinchUpdate + || event->evtype == XI_GesturePinchEnd) + DispatchGesturePinch (subcompositor, (XIGesturePinchEvent *) event); + else if (event->evtype == XI_GestureSwipeBegin + || event->evtype == XI_GestureSwipeUpdate + || event->evtype == XI_GestureSwipeEnd) + DispatchGestureSwipe (subcompositor, (XIGestureSwipeEvent *) event); +} + +#define GenerateCrossingEvent(event_type, controller, test_event) \ + test_event.type = GenericEvent; \ + test_event.serial = request_serial_counter++; \ + test_event.send_event = True; \ + test_event.display = compositor.display; \ + test_event.extension = xi2_opcode; \ + test_event.evtype = event_type; \ + test_event.time = time; \ + test_event.deviceid = controller->seat->master_pointer; \ + test_event.sourceid = sourceid; \ + test_event.detail = detail; \ + test_event.root = root; \ + test_event.event = event; \ + test_event.child = child; \ + test_event.root_x = wl_fixed_to_double (root_x); \ + test_event.root_y = wl_fixed_to_double (root_y); \ + test_event.event_x = wl_fixed_to_double (event_x); \ + test_event.event_y = wl_fixed_to_double (event_y); \ + test_event.mode = mode; \ + test_event.focus = focus; \ + test_event.same_screen = same_screen; \ + TranslateTestButtons (buttons_resource, &test_event.buttons); \ + TranslateTestModifiers (mods_resource, &test_event.mods); \ + TranslateTestModifiers (group_resource, &test_event.group) + +static void +DispatchXIEnter (struct wl_client *client, struct wl_resource *resource, + uint32_t time, int32_t sourceid, int32_t detail, + uint32_t root, uint32_t event, uint32_t child, + wl_fixed_t root_x, wl_fixed_t root_y, + wl_fixed_t event_x, wl_fixed_t event_y, int32_t mode, + int32_t focus, int32_t same_screen, + struct wl_resource *buttons_resource, + struct wl_resource *mods_resource, + struct wl_resource *group_resource) +{ + TestSeatController *controller; + XIEnterEvent test_event; + + controller = wl_resource_get_user_data (resource); + GenerateCrossingEvent (XI_Enter, controller, test_event); + + /* Now dispatch the event. */ + DispatchTestEvent (controller, event, (XIEvent *) &test_event); +} + +static void +DispatchXILeave (struct wl_client *client, struct wl_resource *resource, + uint32_t time, int32_t sourceid, int32_t detail, + uint32_t root, uint32_t event, uint32_t child, + wl_fixed_t root_x, wl_fixed_t root_y, + wl_fixed_t event_x, wl_fixed_t event_y, int32_t mode, + int32_t focus, int32_t same_screen, + struct wl_resource *buttons_resource, + struct wl_resource *mods_resource, + struct wl_resource *group_resource) +{ + TestSeatController *controller; + XILeaveEvent test_event; + + controller = wl_resource_get_user_data (resource); + GenerateCrossingEvent (XI_Leave, controller, test_event); + + /* Now dispatch the event. */ + DispatchTestEvent (controller, event, (XIEvent *) &test_event); +} + +#define GenerateDeviceEvent(event_type, controller, test_event) \ + test_event.type = GenericEvent; \ + test_event.serial = request_serial_counter++; \ + test_event.send_event = True; \ + test_event.display = compositor.display; \ + test_event.extension = xi2_opcode; \ + test_event.evtype = event_type; \ + test_event.time = time; \ + test_event.deviceid = controller->seat->master_pointer; \ + test_event.sourceid = sourceid; \ + test_event.detail = detail; \ + test_event.root = root; \ + test_event.child = child; \ + test_event.event = event; \ + test_event.root_x = wl_fixed_to_double (root_x); \ + test_event.root_y = wl_fixed_to_double (root_y); \ + test_event.event_x = wl_fixed_to_double (event_x); \ + test_event.event_y = wl_fixed_to_double (event_y); \ + test_event.flags = flags; \ + TranslateTestButtons (buttons_resource, &test_event.buttons); \ + TranslateTestValuators (valuators_resource, &test_event.valuators); \ + TranslateTestModifiers (mods_resource, &test_event.mods); \ + TranslateTestModifiers (group_resource, &test_event.group) + +static void +DispatchXIMotion (struct wl_client *client, struct wl_resource *resource, + uint32_t time, int32_t sourceid, int32_t detail, + uint32_t root, uint32_t event, uint32_t child, + wl_fixed_t root_x, wl_fixed_t root_y, wl_fixed_t event_x, + wl_fixed_t event_y, int32_t flags, + struct wl_resource *buttons_resource, + struct wl_resource *valuators_resource, + struct wl_resource *mods_resource, + struct wl_resource *group_resource) +{ + TestSeatController *controller; + XIDeviceEvent test_event; + + controller = wl_resource_get_user_data (resource); + GenerateDeviceEvent (XI_Motion, controller, test_event); + + /* Now dispatch the event. */ + DispatchTestEvent (controller, event, (XIEvent *) &test_event); +} + +static void +DispatchXIButtonPress (struct wl_client *client, struct wl_resource *resource, + uint32_t time, int32_t sourceid, int32_t detail, + uint32_t root, uint32_t event, uint32_t child, + wl_fixed_t root_x, wl_fixed_t root_y, + wl_fixed_t event_x, wl_fixed_t event_y, int32_t flags, + struct wl_resource *buttons_resource, + struct wl_resource *valuators_resource, + struct wl_resource *mods_resource, + struct wl_resource *group_resource) +{ + TestSeatController *controller; + XIDeviceEvent test_event; + + controller = wl_resource_get_user_data (resource); + GenerateDeviceEvent (XI_ButtonPress, controller, test_event); + + /* Now dispatch the event. */ + DispatchTestEvent (controller, event, (XIEvent *) &test_event); +} + +static void +DispatchXIButtonRelease (struct wl_client *client, struct wl_resource *resource, + uint32_t time, int32_t sourceid, int32_t detail, + uint32_t root, uint32_t event, uint32_t child, + wl_fixed_t root_x, wl_fixed_t root_y, + wl_fixed_t event_x, wl_fixed_t event_y, int32_t flags, + struct wl_resource *buttons_resource, + struct wl_resource *valuators_resource, + struct wl_resource *mods_resource, + struct wl_resource *group_resource) +{ + TestSeatController *controller; + XIDeviceEvent test_event; + + controller = wl_resource_get_user_data (resource); + GenerateDeviceEvent (XI_ButtonRelease, controller, test_event); + + /* Now dispatch the event. */ + DispatchTestEvent (controller, event, (XIEvent *) &test_event); +} + +static const struct test_seat_controller_interface seat_controller_impl = + { + .destroy = DestroyTestSeatController, + .bind_seat = BindSeat, + .get_XIModifierState = GetXIModifierState, + .get_XIButtonState = GetXIButtonState, + .get_XIValuatorState = GetXIValuatorState, + .dispatch_XI_Enter = DispatchXIEnter, + .dispatch_XI_Leave = DispatchXILeave, + .dispatch_XI_Motion = DispatchXIMotion, + .dispatch_XI_ButtonPress = DispatchXIButtonPress, + .dispatch_XI_ButtonRelease = DispatchXIButtonRelease, + }; + +static void +HandleControllerResourceDestroy (struct wl_resource *resource) +{ + TestSeatController *controller; + Seat *seat; + + controller = wl_resource_get_user_data (resource); + seat = controller->seat; + + /* Make the seat inert and remove it from live_seats. */ + seat->flags |= IsInert; + + /* Set the focus surface to NULL, so surfaces don't mistakenly + treat themselves as still focused. */ + + SetFocusSurface (seat, NULL); + + /* Run destroy handlers. */ + + RunDestroyListeners (seat); + + /* Since the seat is now inert, remove it from the assoc + table and destroy the global. */ + + XLDeleteAssoc (seats, seat->master_keyboard); + XLDeleteAssoc (seats, seat->master_pointer); + + /* Also remove it from the list of live seats. */ + + live_seats = XLListRemove (live_seats, seat); + + /* Run and remove all resize completion callbacks. */ + + RunResizeDoneCallbacks (seat); + + /* And release the seat. */ + + ReleaseSeat (seat); + + /* Free the controller resource. */ + XLFree (controller); +} + + + +void +XLGetTestSeat (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + TestSeatController *controller; + Seat *seat; + char name_format[sizeof "test seat: " + 40]; + + controller = XLSafeMalloc (sizeof *controller); + + if (!controller) + { + wl_resource_post_no_memory (resource); + return; + } + + memset (controller, 0, sizeof *controller); + controller->resource + = wl_resource_create (client, &test_seat_controller_interface, + wl_resource_get_version (resource), id); + + if (!controller->resource) + { + wl_resource_post_no_memory (resource); + XLFree (controller); + return; + } + + seat = XLCalloc (1, sizeof *seat); + + /* Allocate a "device ID" for the seat. Device IDs are unsigned 16 + bit values, so any larger value is guaranteed to be okay for our + own use. */ + + if (!test_seat_counter) + test_seat_counter = 65555; + test_seat_counter++; + + /* Initialize some random bogus values. */ + seat->master_pointer = test_seat_counter; + seat->master_keyboard = test_seat_counter; + + /* Add a unique seat name. */ + sprintf (name_format, "test seat %u", test_seat_counter); + seat->name = XLStrdup (name_format); + + /* Refrain from creating a global for this seat. */ + seat->global = NULL; + + InitSeatCommon (seat); + + /* Associate the dummy device with the seat. */ + XLMakeAssoc (seats, test_seat_counter, seat); + seat->flags |= IsTestSeat; + + /* Add the seat to the live seat list. */ + live_seats = XLListPrepend (live_seats, seat); + + /* Retain the seat. */ + RetainSeat (seat); + controller->seat = seat; + + wl_resource_set_implementation (controller->resource, &seat_controller_impl, + controller, HandleControllerResourceDestroy); +} diff --git a/tests/seat_test.c b/tests/seat_test.c new file mode 100644 index 0000000..5a74081 --- /dev/null +++ b/tests/seat_test.c @@ -0,0 +1,480 @@ +/* Tests for the Wayland compositor running on the X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 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 3 of the License, or (at your +option) any later version. + +12to11 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 12to11. If not, see . */ + +#include "test_harness.h" + +#include + +enum test_expect_event_kind + { + POINTER_ENTER_EVENT, + POINTER_FRAME_EVENT, + POINTER_MOTION_EVENT, + }; + +struct test_expect_data +{ + /* The coordinates of the event. */ + double x, y; + + /* What kind of event is being waited for. */ + enum test_expect_event_kind kind; + + /* Whether or not the expected event arrived. */ + bool arrived; +}; + +enum test_kind + { + MAP_WINDOW_KIND, + TEST_ENTRY_KIND, + }; + +static const char *test_names[] = + { + "map_window", + "test_entry", + }; + +#define LAST_TEST TEST_ENTRY_KIND +#define TEST_SOURCE_DEVICE 4500000 + +/* The display. */ +static struct test_display *display; + +/* Test interfaces. */ +static struct test_interface test_interfaces[] = + { + /* No interfaces yet. */ + }; + +/* The test surface window. */ +static Window test_surface_window; + +/* The test surface and Wayland surface. */ +static struct test_surface *test_surface; +static struct wl_surface *wayland_surface; + +/* How many elements are in the current listener data. */ +static int num_listener_data; + +/* The current listener data. */ +struct test_expect_data *current_listener_data; + + + +/* Forward declarations. */ +static void submit_surface_damage (struct wl_surface *, int, int, int, int); +static void expect_surface_enter (double, double); +static void expect_surface_motion (double, double); + + + +/* Get a timestamp suitable for use in events dispatched to the test + seat. */ + +static uint32_t +test_get_time (void) +{ + struct timespec timespec; + + clock_gettime (CLOCK_MONOTONIC, ×pec); + + return (timespec.tv_sec * 1000 + + timespec.tv_nsec / 1000000); +} + +/* Get the root window. */ + +static Window +test_get_root (void) +{ + return DefaultRootWindow (display->x_display); +} + + + +static void +test_single_step (enum test_kind kind) +{ + struct wl_buffer *buffer; + + test_log ("running test step: %s", test_names[kind]); + + switch (kind) + { + case MAP_WINDOW_KIND: + buffer = load_png_image (display, "seat_test.png"); + + if (!buffer) + report_test_failure ("failed to load seat_test.png"); + + wl_surface_attach (wayland_surface, buffer, 0, 0); + submit_surface_damage (wayland_surface, 0, 0, 500, 500); + wl_surface_commit (wayland_surface); + wl_buffer_destroy (buffer); + break; + + case TEST_ENTRY_KIND: + /* Enter the 500x500 window. The window is at 0, 0 relative to + the root window. */ + test_seat_controller_dispatch_XI_Enter (display->seat->controller, + test_get_time (), + TEST_SOURCE_DEVICE, + XINotifyAncestor, + test_get_root (), + test_surface_window, + None, + wl_fixed_from_double (0.0), + wl_fixed_from_double (0.0), + wl_fixed_from_double (0.0), + wl_fixed_from_double (0.0), + XINotifyNormal, + False, True, NULL, NULL, + NULL); + + /* Expect an enter at 0.0 by 0.0. */ + expect_surface_enter (0.0, 0.0); + + /* Now move the mouse a little. */ + test_seat_controller_dispatch_XI_Motion (display->seat->controller, + test_get_time (), + TEST_SOURCE_DEVICE, + 0, + test_get_root (), + test_surface_window, + None, + wl_fixed_from_double (1.0), + wl_fixed_from_double (2.0), + wl_fixed_from_double (1.0), + wl_fixed_from_double (2.0), + 0, + NULL, NULL, NULL, NULL); + + /* Expect mouse motion at the specified coordinates. */ + expect_surface_motion (1.0, 2.0); + break; + } + + if (kind == LAST_TEST) + test_complete (); +} + + + +static void +expect_surface_enter (double x, double y) +{ + struct test_expect_data data[2]; + + test_log ("waiting for enter at %g, %g", x, y); + + memset (data, 0, sizeof data); + + data[0].x = x; + data[0].y = y; + data[0].kind = POINTER_ENTER_EVENT; + data[1].kind = POINTER_FRAME_EVENT; + + /* Set the current listener data and do a roundtrip. */ + current_listener_data = data; + num_listener_data = 2; + + wl_display_roundtrip (display->display); + current_listener_data = NULL; + num_listener_data = 0; + + /* See whether or not the event arrived. */ + if (!data[0].arrived || !data[1].arrived) + report_test_failure ("expected events did not arrive"); + else + test_log ("received enter followed by frame"); +} + +static void +expect_surface_motion (double x, double y) +{ + struct test_expect_data data[2]; + + test_log ("waiting for motion at %g, %g", x, y); + + memset (data, 0, sizeof data); + + data[0].x = x; + data[0].y = y; + data[0].kind = POINTER_MOTION_EVENT; + data[1].kind = POINTER_FRAME_EVENT; + + /* Set the current listener data and do a roundtrip. */ + current_listener_data = data; + num_listener_data = 2; + + wl_display_roundtrip (display->display); + current_listener_data = NULL; + num_listener_data = 0; + + /* See whether or not the event arrived. */ + if (!data[0].arrived || !data[1].arrived) + report_test_failure ("expected events did not arrive"); + else + test_log ("received motion followed by frame"); +} + + + +static void +handle_test_surface_mapped (void *data, struct test_surface *surface, + uint32_t xid, const char *display_string) +{ + /* Sleep for 1 second to ensure that the window is exposed and + redirected. */ + sleep (1); + + /* Start the test. */ + test_surface_window = xid; + + /* Run the test again. */ + test_single_step (TEST_ENTRY_KIND); +} + +static const struct test_surface_listener test_surface_listener = + { + handle_test_surface_mapped, + }; + + + +/* Obtain the next test data record. The events arriving are checked + to be in the order in which they arrive in + current_listener_data. */ + +static struct test_expect_data * +get_next_expect_data (void) +{ + struct test_expect_data *data; + int i; + + data = current_listener_data; + + for (i = 0; i < num_listener_data; ++i) + { + if (current_listener_data[i].arrived) + continue; + + return ¤t_listener_data[i]; + } + + return NULL; +} + +static void +handle_pointer_enter (void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + struct test_expect_data *test_data; + + test_data = get_next_expect_data (); + + if (!test_data) + { + test_log ("ignored enter event at %g %g", + wl_fixed_to_double (surface_x), + wl_fixed_to_double (surface_y)); + return; + } + + if (test_data->kind != POINTER_ENTER_EVENT) + return; + + test_log ("got enter event at %g, %g", + wl_fixed_to_double (surface_x), + wl_fixed_to_double (surface_y)); + + if (test_data->x == wl_fixed_to_double (surface_x) + && test_data->y == wl_fixed_to_double (surface_y)) + test_data->arrived = true; + else + report_test_failure ("missed enter event at %g %g", + test_data->x, test_data->y); +} + +static void +handle_pointer_leave (void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface) +{ + /* ... */ +} + +static void +handle_pointer_motion (void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t surface_x, + wl_fixed_t surface_y) +{ + struct test_expect_data *test_data; + + test_data = get_next_expect_data (); + + if (!test_data) + { + test_log ("ignored motion event at %g %g", + wl_fixed_to_double (surface_x), + wl_fixed_to_double (surface_y)); + return; + } + + if (test_data->kind != POINTER_MOTION_EVENT) + return; + + test_log ("got motion event at %g, %g", + wl_fixed_to_double (surface_x), + wl_fixed_to_double (surface_y)); + + if (test_data->x == wl_fixed_to_double (surface_x) + && test_data->y == wl_fixed_to_double (surface_y)) + test_data->arrived = true; + else + report_test_failure ("missed motion event at %g %g", + test_data->x, test_data->y); +} + +static void +handle_pointer_button (void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, + uint32_t state) +{ + /* TODO... */ +} + +static void +handle_pointer_axis (void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ + /* TODO... */ +} + +static void +handle_pointer_frame (void *data, struct wl_pointer *wl_pointer) +{ + struct test_expect_data *test_data; + + test_data = get_next_expect_data (); + + if (!test_data) + { + test_log ("ignored frame event"); + return; + } + + if (test_data->kind != POINTER_FRAME_EVENT) + return; + + test_log ("got frame event"); + test_data->arrived = true; +} + +static void +handle_pointer_axis_source (void *data, struct wl_pointer *wl_pointer, + uint32_t axis_source) +{ + /* TODO... */ +} + +static void +handle_pointer_axis_stop (void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis) +{ + /* TODO... */ +} + +static void +handle_pointer_axis_discrete (void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t discrete) +{ + /* TODO... */ +} + +static void +handle_pointer_axis_value120 (void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t value120) +{ + /* TODO... */ +} + +static const struct wl_pointer_listener pointer_listener = + { + handle_pointer_enter, + handle_pointer_leave, + handle_pointer_motion, + handle_pointer_button, + handle_pointer_axis, + handle_pointer_frame, + handle_pointer_axis_source, + handle_pointer_axis_stop, + handle_pointer_axis_discrete, + handle_pointer_axis_value120, + }; + + + +static void +submit_surface_damage (struct wl_surface *surface, int x, int y, int width, + int height) +{ + test_log ("damaging surface by %d, %d, %d, %d", x, y, width, + height); + + wl_surface_damage (surface, x, y, width, height); +} + +static void +run_test (void) +{ + if (!make_test_surface (display, &wayland_surface, + &test_surface)) + report_test_failure ("failed to create test surface"); + + test_surface_add_listener (test_surface, &test_surface_listener, + NULL); + test_single_step (MAP_WINDOW_KIND); + + /* Initialize the pointer listener. */ + wl_pointer_add_listener (display->seat->pointer, &pointer_listener, + NULL); + + while (true) + { + if (wl_display_dispatch (display->display) == -1) + die ("wl_display_dispatch"); + } +} + +int +main (void) +{ + test_init (); + display = open_test_display (test_interfaces, + ARRAYELTS (test_interfaces)); + + if (!display) + report_test_failure ("failed to open display"); + + test_init_seat (display); + run_test (); +} diff --git a/tests/seat_test.png b/tests/seat_test.png new file mode 100644 index 0000000000000000000000000000000000000000..ce46f9415064852b01cfdda6272b90dbae7fb037 GIT binary patch literal 10665 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 F{{dFA%#Q#7 literal 0 HcmV?d00001