/**
 * Looking Glass
 * Copyright © 2017-2026 The Looking Glass Authors
 * https://looking-glass.io
 *
 * 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 "Wayland/input_event.h"
#include "X11/input_event.h"
#include "test.h"

#include <linux/input-event-codes.h>

#include <stdint.h>
#include <stdio.h>
#include <string.h>

enum Backend
{
  BACKEND_WAYLAND,
  BACKEND_X11,
};

enum TraceType
{
  TRACE_POSITION,
  TRACE_RELATIVE,
  TRACE_BUTTON,
  TRACE_WHEEL,
  TRACE_KEY,
  TRACE_TEXT,
  TRACE_MODIFIERS,
  TRACE_LEDS,
  TRACE_ENTER,
  TRACE_FOCUS,
};

struct Trace
{
  enum TraceType type;
  union
  {
    struct
    {
      double x;
      double y;
      double rawX;
      double rawY;
    }
    motion;

    struct
    {
      unsigned int value;
      bool         pressed;
    }
    input;

    struct
    {
      bool ctrl;
      bool shift;
      bool alt;
      bool super;
    }
    modifiers;

    struct
    {
      bool num;
      bool caps;
      bool scroll;
    }
    leds;

    double wheel;
    bool   state;
    char   text[32];
  } data;
};

#define POSITION(_x, _y) \
  { \
    .type = TRACE_POSITION, \
    .data.motion = { .x = (_x), .y = (_y) }, \
  }

#define RELATIVE(_x, _y, _rawX, _rawY) \
  { \
    .type = TRACE_RELATIVE, \
    .data.motion = \
      { .x = (_x), .y = (_y), .rawX = (_rawX), .rawY = (_rawY) }, \
  }

#define BUTTON(_button, _pressed) \
  { \
    .type = TRACE_BUTTON, \
    .data.input = { .value = (_button), .pressed = (_pressed) }, \
  }

#define WHEEL(_motion) \
  { \
    .type = TRACE_WHEEL, \
    .data.wheel = (_motion), \
  }

#define KEY(_key, _pressed) \
  { \
    .type = TRACE_KEY, \
    .data.input = { .value = (_key), .pressed = (_pressed) }, \
  }

#define TEXT(_text) \
  { \
    .type = TRACE_TEXT, \
    .data.text = _text, \
  }

#define MODIFIERS(_ctrl, _shift, _alt, _super) \
  { \
    .type = TRACE_MODIFIERS, \
    .data.modifiers = \
      { .ctrl = (_ctrl), .shift = (_shift), .alt = (_alt), \
        .super = (_super) }, \
  }

#define LEDS(_num, _caps, _scroll) \
  { \
    .type = TRACE_LEDS, \
    .data.leds = { .num = (_num), .caps = (_caps), .scroll = (_scroll) }, \
  }

#define ENTER(_entered) \
  { \
    .type = TRACE_ENTER, \
    .data.state = (_entered), \
  }

#define FOCUS(_focused) \
  { \
    .type = TRACE_FOCUS, \
    .data.state = (_focused), \
  }

#define ARRAY_LENGTH(_array) (sizeof(_array) / sizeof((_array)[0]))
#define MAX_TRACE 128

struct TraceLog
{
  struct Trace values[MAX_TRACE];
  size_t       count;
};

struct Fixture
{
  enum Backend backend;
  struct TraceLog trace;

  union
  {
    WaylandInput wayland;
    X11Input     x11;
  } input;
};

static struct Trace * pushTrace(struct TraceLog * log, enum TraceType type)
{
  CHECK(log->count < ARRAY_LENGTH(log->values));
  struct Trace * trace = &log->values[log->count++];
  memset(trace, 0, sizeof(*trace));
  trace->type = type;
  return trace;
}

static void tracePosition(void * opaque, double x, double y)
{
  struct Trace * trace = pushTrace(opaque, TRACE_POSITION);
  trace->data.motion.x = x;
  trace->data.motion.y = y;
}

static void traceRelative(void * opaque, double x, double y,
    double rawX, double rawY)
{
  struct Trace * trace = pushTrace(opaque, TRACE_RELATIVE);
  trace->data.motion.x    = x;
  trace->data.motion.y    = y;
  trace->data.motion.rawX = rawX;
  trace->data.motion.rawY = rawY;
}

static void traceButton(void * opaque, unsigned int button, bool pressed)
{
  struct Trace * trace = pushTrace(opaque, TRACE_BUTTON);
  trace->data.input.value   = button;
  trace->data.input.pressed = pressed;
}

static void traceWheel(void * opaque, double motion)
{
  struct Trace * trace = pushTrace(opaque, TRACE_WHEEL);
  trace->data.wheel = motion;
}

static void traceKey(void * opaque, int scancode, bool pressed)
{
  struct Trace * trace = pushTrace(opaque, TRACE_KEY);
  trace->data.input.value   = scancode;
  trace->data.input.pressed = pressed;
}

static void traceText(void * opaque, const char * text)
{
  struct Trace * trace = pushTrace(opaque, TRACE_TEXT);
  snprintf(trace->data.text, sizeof(trace->data.text), "%s", text);
}

static void traceModifiers(void * opaque, bool ctrl, bool shift,
    bool alt, bool super)
{
  struct Trace * trace = pushTrace(opaque, TRACE_MODIFIERS);
  trace->data.modifiers.ctrl  = ctrl;
  trace->data.modifiers.shift = shift;
  trace->data.modifiers.alt   = alt;
  trace->data.modifiers.super = super;
}

static void traceLEDs(void * opaque, bool numLock, bool capsLock,
    bool scrollLock)
{
  struct Trace * trace = pushTrace(opaque, TRACE_LEDS);
  trace->data.leds.num    = numLock;
  trace->data.leds.caps   = capsLock;
  trace->data.leds.scroll = scrollLock;
}

static void traceEnter(void * opaque, bool entered)
{
  struct Trace * trace = pushTrace(opaque, TRACE_ENTER);
  trace->data.state = entered;
}

static void traceFocus(void * opaque, bool focused)
{
  struct Trace * trace = pushTrace(opaque, TRACE_FOCUS);
  trace->data.state = focused;
}

static const LG_DSInputSink sink =
{
  .position  = tracePosition,
  .relative  = traceRelative,
  .button    = traceButton,
  .wheel     = traceWheel,
  .key       = traceKey,
  .text      = traceText,
  .modifiers = traceModifiers,
  .leds      = traceLEDs,
  .enter     = traceEnter,
  .focus     = traceFocus,
};

static void initFixture(struct Fixture * fixture, enum Backend backend)
{
  memset(fixture, 0, sizeof(*fixture));
  fixture->backend = backend;

  if (backend == BACKEND_WAYLAND)
    wlInputInit(&fixture->input.wayland, &sink, &fixture->trace);
  else
    x11InputInit(&fixture->input.x11, &sink, &fixture->trace);
}

static void clearTrace(struct Fixture * fixture)
{
  fixture->trace.count = 0;
}

static bool sameDouble(double a, double b)
{
  const double delta = a > b ? a - b : b - a;
  return delta < 0.000000001;
}

static bool sameTrace(const struct Trace * actual,
    const struct Trace * expected)
{
  if (actual->type != expected->type)
    return false;

  switch (actual->type)
  {
    case TRACE_POSITION:
      return
        sameDouble(actual->data.motion.x, expected->data.motion.x) &&
        sameDouble(actual->data.motion.y, expected->data.motion.y);

    case TRACE_RELATIVE:
      return
        sameDouble(actual->data.motion.x, expected->data.motion.x) &&
        sameDouble(actual->data.motion.y, expected->data.motion.y) &&
        sameDouble(actual->data.motion.rawX, expected->data.motion.rawX) &&
        sameDouble(actual->data.motion.rawY, expected->data.motion.rawY);

    case TRACE_BUTTON:
    case TRACE_KEY:
      return
        actual->data.input.value == expected->data.input.value &&
        actual->data.input.pressed == expected->data.input.pressed;

    case TRACE_WHEEL:
      return sameDouble(actual->data.wheel, expected->data.wheel);

    case TRACE_TEXT:
      return strcmp(actual->data.text, expected->data.text) == 0;

    case TRACE_MODIFIERS:
      return
        actual->data.modifiers.ctrl == expected->data.modifiers.ctrl &&
        actual->data.modifiers.shift == expected->data.modifiers.shift &&
        actual->data.modifiers.alt == expected->data.modifiers.alt &&
        actual->data.modifiers.super == expected->data.modifiers.super;

    case TRACE_LEDS:
      return
        actual->data.leds.num == expected->data.leds.num &&
        actual->data.leds.caps == expected->data.leds.caps &&
        actual->data.leds.scroll == expected->data.leds.scroll;

    case TRACE_ENTER:
    case TRACE_FOCUS:
      return actual->data.state == expected->data.state;
  }

  return false;
}

static const char * traceName(enum TraceType type)
{
  static const char * names[] =
  {
    [TRACE_POSITION ] = "position",
    [TRACE_RELATIVE ] = "relative",
    [TRACE_BUTTON   ] = "button",
    [TRACE_WHEEL    ] = "wheel",
    [TRACE_KEY      ] = "key",
    [TRACE_TEXT     ] = "text",
    [TRACE_MODIFIERS] = "modifiers",
    [TRACE_LEDS     ] = "leds",
    [TRACE_ENTER    ] = "enter",
    [TRACE_FOCUS    ] = "focus",
  };
  return names[type];
}

static void printTrace(const struct Trace * trace)
{
  fprintf(stderr, "%s", traceName(trace->type));
  switch (trace->type)
  {
    case TRACE_POSITION:
      fprintf(stderr, " %.6f %.6f", trace->data.motion.x,
          trace->data.motion.y);
      break;

    case TRACE_RELATIVE:
      fprintf(stderr, " %.6f %.6f %.6f %.6f", trace->data.motion.x,
          trace->data.motion.y, trace->data.motion.rawX,
          trace->data.motion.rawY);
      break;

    case TRACE_BUTTON:
    case TRACE_KEY:
      fprintf(stderr, " %u %s", trace->data.input.value,
          trace->data.input.pressed ? "down" : "up");
      break;

    case TRACE_WHEEL:
      fprintf(stderr, " %.6f", trace->data.wheel);
      break;

    case TRACE_TEXT:
      fprintf(stderr, " %s", trace->data.text);
      break;

    case TRACE_MODIFIERS:
      fprintf(stderr, " %d %d %d %d", trace->data.modifiers.ctrl,
          trace->data.modifiers.shift, trace->data.modifiers.alt,
          trace->data.modifiers.super);
      break;

    case TRACE_LEDS:
      fprintf(stderr, " %d %d %d", trace->data.leds.num,
          trace->data.leds.caps, trace->data.leds.scroll);
      break;

    case TRACE_ENTER:
    case TRACE_FOCUS:
      fprintf(stderr, " %d", trace->data.state);
      break;
  }
  fputc('\n', stderr);
}

static void expectTrace(const struct Fixture * fixture,
    const struct Trace * expected, size_t expectedCount)
{
  bool match = fixture->trace.count == expectedCount;
  if (match)
    for (size_t i = 0; i < expectedCount; ++i)
      if (!sameTrace(&fixture->trace.values[i], &expected[i]))
      {
        match = false;
        break;
      }

  if (match)
    return;

  fprintf(stderr, "expected trace (%zu events):\n", expectedCount);
  for (size_t i = 0; i < expectedCount; ++i)
    printTrace(&expected[i]);

  fprintf(stderr, "actual trace (%zu events):\n", fixture->trace.count);
  for (size_t i = 0; i < fixture->trace.count; ++i)
    printTrace(&fixture->trace.values[i]);

  exit(EXIT_FAILURE);
}

static void pointerEnter(struct Fixture * fixture, bool mainSurface,
    double x, double y)
{
  if (fixture->backend == BACKEND_WAYLAND)
  {
    if (wlInputPointerEnter(&fixture->input.wayland, mainSurface))
      wlInputPointerMotion(&fixture->input.wayland, x, y);
  }
  else
    x11InputPointerEnter(&fixture->input.x11, mainSurface, true, x, y);
}

static void pointerLeave(struct Fixture * fixture, bool mainSurface)
{
  if (fixture->backend == BACKEND_WAYLAND)
    wlInputPointerLeave(&fixture->input.wayland, mainSurface);
  else
    x11InputPointerLeave(&fixture->input.x11, mainSurface, true, false);
}

static void pointerMotion(struct Fixture * fixture, double x, double y)
{
  if (fixture->backend == BACKEND_WAYLAND)
    wlInputPointerMotion(&fixture->input.wayland, x, y);
  else
    x11InputPointerMotion(&fixture->input.x11, x, y);
}

static unsigned int nativeButton(enum Backend backend,
    unsigned int button)
{
  if (backend == BACKEND_X11)
    return button > 5 ? button + 2 : button;

  static const unsigned int wayland[] =
  {
    [1 ] = BTN_LEFT,
    [2 ] = BTN_MIDDLE,
    [3 ] = BTN_RIGHT,
    [6 ] = BTN_SIDE,
    [7 ] = BTN_EXTRA,
    [8 ] = BTN_FORWARD,
    [9 ] = BTN_BACK,
    [10] = BTN_TASK,
  };
  CHECK(button < ARRAY_LENGTH(wayland));
  return wayland[button];
}

static void pointerButton(struct Fixture * fixture, unsigned int button,
    bool pressed)
{
  const unsigned int native = nativeButton(fixture->backend, button);
  if (fixture->backend == BACKEND_WAYLAND)
    wlInputPointerButton(&fixture->input.wayland, native, pressed);
  else
    x11InputPointerButton(&fixture->input.x11, native, pressed, false);
}

static void unmappedButton(struct Fixture * fixture)
{
  if (fixture->backend == BACKEND_WAYLAND)
    wlInputPointerButton(&fixture->input.wayland, BTN_STYLUS, true);
  else
    x11InputPointerButton(&fixture->input.x11, 6, true, false);
}

static void wheelStep(struct Fixture * fixture, bool down)
{
  if (fixture->backend == BACKEND_WAYLAND)
    wlInputPointerAxis(&fixture->input.wayland, true, down ? 15.0 : -15.0);
  else
    x11InputPointerButton(&fixture->input.x11, down ? 5 : 4, true,
        false);
}

static void relativeMotion(struct Fixture * fixture, bool active,
    double x, double y, double rawX, double rawY)
{
  if (fixture->backend == BACKEND_WAYLAND)
    wlInputRelativeMotion(&fixture->input.wayland, active,
        x, y, rawX, rawY);
  else
  {
    x11InputSetPointerGrabbed(&fixture->input.x11, active);
    x11InputRelativeMotion(&fixture->input.x11, x, y, rawX, rawY);
  }
}

static void keyboardEnter(struct Fixture * fixture, bool mainSurface,
    const uint32_t * keys, size_t count)
{
  if (fixture->backend == BACKEND_WAYLAND)
    wlInputKeyboardEnter(&fixture->input.wayland, mainSurface, keys, count);
  else if (mainSurface)
    x11InputFocus(&fixture->input.x11, true, keys, count);
}

static void keyboardLeave(struct Fixture * fixture, bool mainSurface)
{
  if (fixture->backend == BACKEND_WAYLAND)
    wlInputKeyboardLeave(&fixture->input.wayland, mainSurface);
  else if (mainSurface)
    x11InputFocus(&fixture->input.x11, false, NULL, 0);
}

static void keyboardKey(struct Fixture * fixture, unsigned int key,
    bool pressed, const char * text)
{
  if (fixture->backend == BACKEND_WAYLAND)
    wlInputKeyboardKey(&fixture->input.wayland, key, pressed, text);
  else
  {
    const bool accepted = x11InputKeyboardKey(
        &fixture->input.x11, key + 8, 8, pressed, false);
    if (accepted && pressed)
      x11InputKeyboardText(&fixture->input.x11, text);
  }
}

static void keyboardState(struct Fixture * fixture,
    bool ctrl, bool shift, bool alt, bool super,
    bool numLock, bool capsLock, bool scrollLock)
{
  if (fixture->backend == BACKEND_WAYLAND)
    wlInputKeyboardState(&fixture->input.wayland,
        ctrl, shift, alt, super, numLock, capsLock, scrollLock);
  else
    x11InputKeyboardState(&fixture->input.x11,
        ctrl, shift, alt, super, numLock, capsLock, scrollLock);
}

static void activateInput(struct Fixture * fixture)
{
  pointerEnter(fixture, true, 40.0, 30.0);
  keyboardEnter(fixture, true, NULL, 0);
  clearTrace(fixture);
}

static void activatePointer(struct Fixture * fixture)
{
  pointerEnter(fixture, true, 40.0, 30.0);
  clearTrace(fixture);
}

static void testPointerEvents(enum Backend backend)
{
  struct Fixture fixture;
  initFixture(&fixture, backend);

  pointerEnter(&fixture, false, 1.0, 2.0);
  pointerEnter(&fixture, true, 10.0, 20.0);
  pointerMotion(&fixture, 11.5, 21.25);
  pointerLeave(&fixture, false);
  pointerLeave(&fixture, true);

  static const struct Trace expected[] =
  {
    ENTER(true),
    POSITION(10.0, 20.0),
    POSITION(11.5, 21.25),
    ENTER(false),
  };
  expectTrace(&fixture, expected, ARRAY_LENGTH(expected));
}

static void testPointerButtons(enum Backend backend)
{
  struct Fixture fixture;
  initFixture(&fixture, backend);
  activatePointer(&fixture);

  static const unsigned int buttons[] = { 1, 2, 3, 6, 7, 8, 9, 10 };
  for (size_t i = 0; i < ARRAY_LENGTH(buttons); ++i)
  {
    pointerButton(&fixture, buttons[i], true);
    pointerButton(&fixture, buttons[i], false);
  }
  unmappedButton(&fixture);

  static const struct Trace expected[] =
  {
    BUTTON(1 , true), BUTTON(1 , false),
    BUTTON(2 , true), BUTTON(2 , false),
    BUTTON(3 , true), BUTTON(3 , false),
    BUTTON(6 , true), BUTTON(6 , false),
    BUTTON(7 , true), BUTTON(7 , false),
    BUTTON(8 , true), BUTTON(8 , false),
    BUTTON(9 , true), BUTTON(9 , false),
    BUTTON(10, true), BUTTON(10, false),
  };
  expectTrace(&fixture, expected, ARRAY_LENGTH(expected));
}

static void testPointerRelease(enum Backend backend)
{
  struct Fixture fixture;
  initFixture(&fixture, backend);
  activateInput(&fixture);

  pointerButton(&fixture, 1, true);

  /* Wayland's implicit pointer grab suppresses this native leave. */
  if (backend == BACKEND_X11)
    pointerLeave(&fixture, true);

  pointerButton(&fixture, 1, false);

  static const struct Trace expected[] =
  {
    BUTTON(1, true),
    BUTTON(1, false),
  };
  expectTrace(&fixture, expected, ARRAY_LENGTH(expected));
}

static void testWheelSteps(enum Backend backend)
{
  struct Fixture fixture;
  initFixture(&fixture, backend);
  activateInput(&fixture);

  wheelStep(&fixture, true);
  wheelStep(&fixture, false);

  static const struct Trace expected[] =
  {
    BUTTON(5, true),
    BUTTON(5, false),
    WHEEL(1.0),
    BUTTON(4, true),
    BUTTON(4, false),
    WHEEL(-1.0),
  };
  expectTrace(&fixture, expected, ARRAY_LENGTH(expected));
}

static void testWheelResidual(enum Backend backend)
{
  CHECK(backend == BACKEND_WAYLAND);
  struct Fixture fixture;
  initFixture(&fixture, backend);

  wlInputPointerAxis(&fixture.input.wayland, false, 100.0);
  wlInputPointerAxis(&fixture.input.wayland, true, 5.0);
  wlInputPointerAxis(&fixture.input.wayland, true, 5.0);
  wlInputPointerAxis(&fixture.input.wayland, true, -2.0);
  wlInputPointerAxis(&fixture.input.wayland, true, -1.0);

  static const struct Trace expected[] =
  {
    WHEEL(1.0 / 3.0),
    BUTTON(5, true),
    BUTTON(5, false),
    WHEEL(1.0 / 3.0),
    WHEEL(-2.0 / 15.0),
    BUTTON(4, true),
    BUTTON(4, false),
    WHEEL(-1.0 / 15.0),
  };
  expectTrace(&fixture, expected, ARRAY_LENGTH(expected));
}

static void testWheelMultiple(enum Backend backend)
{
  CHECK(backend == BACKEND_WAYLAND);
  struct Fixture fixture;
  initFixture(&fixture, backend);

  wlInputPointerAxis(&fixture.input.wayland, true, 45.0);

  static const struct Trace expected[] =
  {
    BUTTON(5, true), BUTTON(5, false),
    BUTTON(5, true), BUTTON(5, false),
    BUTTON(5, true), BUTTON(5, false),
    WHEEL(3.0),
  };
  expectTrace(&fixture, expected, ARRAY_LENGTH(expected));
}

static void testRelativeMotion(enum Backend backend)
{
  struct Fixture fixture;
  initFixture(&fixture, backend);
  activateInput(&fixture);

  relativeMotion(&fixture, false, 1.0, 2.0, 3.0, 4.0);
  relativeMotion(&fixture, true, 1.0, 2.0, 3.0, 4.0);
  relativeMotion(&fixture, true, 1.0, 2.0, 3.0, 4.0);

  static const struct Trace expected[] =
  {
    RELATIVE(1.0, 2.0, 3.0, 4.0),
    RELATIVE(1.0, 2.0, 3.0, 4.0),
  };
  expectTrace(&fixture, expected, ARRAY_LENGTH(expected));
}

static void testKeyboardFocus(enum Backend backend)
{
  struct Fixture fixture;
  initFixture(&fixture, backend);
  static const uint32_t held[] = { 30, 42 };

  keyboardEnter(&fixture, false, held, ARRAY_LENGTH(held));
  keyboardEnter(&fixture, true, held, ARRAY_LENGTH(held));
  keyboardLeave(&fixture, false);
  keyboardLeave(&fixture, true);

  static const struct Trace expected[] =
  {
    FOCUS(true),
    KEY(30, true),
    KEY(42, true),
    FOCUS(false),
  };
  expectTrace(&fixture, expected, ARRAY_LENGTH(expected));
}

static void testKeyboardKeys(enum Backend backend)
{
  struct Fixture fixture;
  initFixture(&fixture, backend);

  keyboardKey(&fixture, 30, true, "ignored");
  keyboardKey(&fixture, 30, false, "ignored");
  CHECK(fixture.trace.count == 0);

  keyboardEnter(&fixture, true, NULL, 0);
  clearTrace(&fixture);

  keyboardKey(&fixture, 30, true, "a");
  keyboardKey(&fixture, 30, false, "ignored");
  keyboardKey(&fixture, 31, true, "");
  keyboardKey(&fixture, 31, false, NULL);

  static const struct Trace expected[] =
  {
    KEY(30, true),
    TEXT("a"),
    KEY(30, false),
    KEY(31, true),
    KEY(31, false),
  };
  expectTrace(&fixture, expected, ARRAY_LENGTH(expected));

  keyboardLeave(&fixture, true);
  clearTrace(&fixture);
  keyboardKey(&fixture, 31, true, "s");
  keyboardKey(&fixture, 31, false, NULL);
  CHECK(fixture.trace.count == 0);
}

static void testX11PointerRouting(enum Backend backend)
{
  CHECK(backend == BACKEND_X11);

  struct Fixture fixture;
  initFixture(&fixture, backend);
  pointerEnter(&fixture, true, 40.0, 30.0);
  clearTrace(&fixture);

  x11InputPointerButton(&fixture.input.x11, 1, true, true);
  x11InputRelativeMotion(&fixture.input.x11, 1.0, 2.0, 3.0, 4.0);

  x11InputSetPointerGrabbed(&fixture.input.x11, true);
  x11InputPointerButton(&fixture.input.x11, 1, true, false);
  x11InputPointerButton(&fixture.input.x11, 1, true, true);
  x11InputRelativeMotion(&fixture.input.x11, 1.0, 2.0, 3.0, 4.0);

  x11InputSetPointerGrabbed(&fixture.input.x11, false);
  x11InputPointerButton(&fixture.input.x11, 1, false, true);
  x11InputRelativeMotion(&fixture.input.x11, 1.0, 2.0, 3.0, 4.0);
  x11InputPointerButton(&fixture.input.x11, 1, false, false);

  static const struct Trace expected[] =
  {
    BUTTON(1, true),
    RELATIVE(1.0, 2.0, 3.0, 4.0),
    BUTTON(1, false),
  };
  expectTrace(&fixture, expected, ARRAY_LENGTH(expected));
  CHECK(fixture.input.x11.buttons == 0);
}

static void testX11KeyboardRouting(enum Backend backend)
{
  CHECK(backend == BACKEND_X11);

  struct Fixture fixture;
  initFixture(&fixture, backend);
  keyboardEnter(&fixture, true, NULL, 0);
  clearTrace(&fixture);

  CHECK(!x11InputKeyboardKey(&fixture.input.x11,
      30 + 8, 8, true, true));
  CHECK(x11InputKeyboardKey(&fixture.input.x11,
      30 + 8, 8, true, false));
  x11InputKeyboardText(&fixture.input.x11, "a");

  x11InputSetKeyboardGrabbed(&fixture.input.x11, true);
  CHECK(!x11InputKeyboardKey(&fixture.input.x11,
      31 + 8, 8, true, false));
  CHECK(x11InputKeyboardKey(&fixture.input.x11,
      30 + 8, 8, false, true));
  CHECK(x11InputKeyboardKey(&fixture.input.x11,
      31 + 8, 8, true, true));

  x11InputSetKeyboardGrabbed(&fixture.input.x11, false);
  CHECK(!x11InputKeyboardKey(&fixture.input.x11,
      31 + 8, 8, false, true));
  CHECK(x11InputKeyboardKey(&fixture.input.x11,
      31 + 8, 8, false, false));

  static const struct Trace expected[] =
  {
    KEY(30, true),
    TEXT("a"),
    KEY(30, false),
    KEY(31, true),
    KEY(31, false),
  };
  expectTrace(&fixture, expected, ARRAY_LENGTH(expected));
}

static void setKeymapKey(char keymap[X11_INPUT_KEYMAP_SIZE],
    unsigned int keycode)
{
  CHECK(keycode < X11_INPUT_KEYMAP_SIZE * 8);
  keymap[keycode / 8] = (char)(
      (unsigned char)keymap[keycode / 8] | (1U << (keycode % 8)));
}

static void testX11HeldKeys(enum Backend backend)
{
  CHECK(backend == BACKEND_X11);

  char keymap[X11_INPUT_KEYMAP_SIZE] = { 0 };
  setKeymapKey(keymap, 7);
  setKeymapKey(keymap, 8);
  setKeymapKey(keymap, 30);
  setKeymapKey(keymap, 42);
  setKeymapKey(keymap, 43);

  uint32_t keys[4];
  size_t count = x11InputHeldKeys(keymap, 8, 42,
      keys, ARRAY_LENGTH(keys));
  CHECK(count == 3);
  CHECK(keys[0] == 0);
  CHECK(keys[1] == 22);
  CHECK(keys[2] == 34);

  count = x11InputHeldKeys(keymap, 8, 42, keys, 2);
  CHECK(count == 2);
  CHECK(keys[0] == 0);
  CHECK(keys[1] == 22);
}

static void testKeyboardState(enum Backend backend)
{
  struct Fixture fixture;
  initFixture(&fixture, backend);
  keyboardEnter(&fixture, true, NULL, 0);
  clearTrace(&fixture);

  keyboardState(&fixture, true, false, true, false,
      true, false, true);
  keyboardState(&fixture, false, true, false, true,
      false, true, false);

  static const struct Trace expected[] =
  {
    MODIFIERS(true, false, true, false),
    LEDS(true, false, true),
    MODIFIERS(false, true, false, true),
    LEDS(false, true, false),
  };
  expectTrace(&fixture, expected, ARRAY_LENGTH(expected));
}

struct Test
{
  const char * name;
  void (*run)(enum Backend backend);
};

static const struct Test tests[] =
{
  { "pointer-events"  , testPointerEvents      },
  { "pointer-buttons" , testPointerButtons     },
  { "pointer-release" , testPointerRelease     },
  { "wheel-steps"     , testWheelSteps         },
  { "wheel-residual"  , testWheelResidual      },
  { "wheel-multiple"  , testWheelMultiple      },
  { "relative-motion" , testRelativeMotion     },
  { "keyboard-focus"  , testKeyboardFocus      },
  { "keyboard-keys"   , testKeyboardKeys       },
  { "keyboard-state"  , testKeyboardState      },
  { "pointer-routing" , testX11PointerRouting  },
  { "keyboard-routing", testX11KeyboardRouting },
  { "held-keys"       , testX11HeldKeys        },
};

static bool parseBackend(const char * name, enum Backend * backend)
{
  if (strcmp(name, "wayland") == 0)
  {
    *backend = BACKEND_WAYLAND;
    return true;
  }

  if (strcmp(name, "x11") == 0)
  {
    *backend = BACKEND_X11;
    return true;
  }

  return false;
}

int main(int argc, char ** argv)
{
  if (argc != 3)
  {
    fprintf(stderr, "usage: %s <wayland|x11> <case>\n", argv[0]);
    return EXIT_FAILURE;
  }

  enum Backend backend;
  if (!parseBackend(argv[1], &backend))
  {
    fprintf(stderr, "unknown backend: %s\n", argv[1]);
    return EXIT_FAILURE;
  }

  for (size_t i = 0; i < ARRAY_LENGTH(tests); ++i)
    if (strcmp(argv[2], tests[i].name) == 0)
    {
      tests[i].run(backend);
      return 0;
    }

  fprintf(stderr, "unknown test: %s\n", argv[2]);
  return EXIT_FAILURE;
}
