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 0000000..ce46f94
Binary files /dev/null and b/tests/seat_test.png differ