/**
 * 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.h"

#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

#include <wayland-client.h>
#include <xkbcommon/xkbcommon.h>

#include "app.h"
#include "common/debug.h"

#define MTRACE(fmt, ...) \
  do \
  { \
    const uint64_t seq = app_mouseSeq(); \
    if (seq) \
      app_mouseTrace(__FILE__, __LINE__, __FUNCTION__, seq, \
          "wl." fmt, ##__VA_ARGS__); \
  } \
  while (0)

#define MLOG(seq, fmt, ...) \
  do \
  { \
    if (seq) \
      app_mouseTrace(__FILE__, __LINE__, __FUNCTION__, seq, \
          "wl." fmt, ##__VA_ARGS__); \
  } \
  while (0)

static uint32_t proxyId(void * proxy)
{
  return proxy ? wl_proxy_get_id(proxy) : 0;
}

static void inputPosition(void * opaque, double x, double y)
{
  (void)opaque;
  app_updateCursorPos(x, y);
}

static void inputRelative(void * opaque, double x, double y,
    double rawX, double rawY)
{
  (void)opaque;
  app_handleMouseRelative(x, y, rawX, rawY);
}

static void inputButton(void * opaque, unsigned int button, bool pressed)
{
  (void)opaque;
  if (pressed)
    app_handleButtonPress(button);
  else
    app_handleButtonRelease(button);
}

static void inputWheel(void * opaque, double motion)
{
  (void)opaque;
  app_handleWheelMotion(motion);
}

static void inputKey(void * opaque, int scancode, bool pressed)
{
  (void)opaque;
  if (pressed)
    app_handleKeyPress(scancode);
  else
    app_handleKeyRelease(scancode);
}

static void inputText(void * opaque, const char * text)
{
  (void)opaque;
  /* Test overlay state after the key callback, matching the native event
   * handler's ordering. */
  if (app_isOverlayMode())
    app_handleKeyboardTyped(text);
}

static void inputModifiers(void * opaque, bool ctrl, bool shift, bool alt,
    bool super)
{
  (void)opaque;
  app_handleKeyboardModifiers(ctrl, shift, alt, super);
}

static void inputLEDs(void * opaque, bool numLock, bool capsLock,
    bool scrollLock)
{
  (void)opaque;
  app_handleKeyboardLEDs(numLock, capsLock, scrollLock);
}

static void inputEnter(void * opaque, bool entered)
{
  (void)opaque;
  app_handleEnterEvent(entered);
}

static void inputFocus(void * opaque, bool focused)
{
  (void)opaque;
  if (!focused)
    waylandCBInvalidate();
  app_handleFocusEvent(focused);
}

static const LG_DSInputSink inputSink = {
  .position  = inputPosition,
  .relative  = inputRelative,
  .button    = inputButton,
  .wheel     = inputWheel,
  .key       = inputKey,
  .text      = inputText,
  .modifiers = inputModifiers,
  .leds      = inputLEDs,
  .enter     = inputEnter,
  .focus     = inputFocus,
};

// Mouse-handling listeners.

static void pointerMotionHandler(void * data, struct wl_pointer * pointer,
    uint32_t time, wl_fixed_t sxW, wl_fixed_t syW)
{
  wlMotionAbs(&wlWm.motion, wl_fixed_to_double(sxW),
      wl_fixed_to_double(syW));
  MTRACE("abs time=%u pos=%.3f,%.3f", time, wlWm.motion.x,
      wlWm.motion.y);
  wlInputPointerMotion(&wlWm.input, wlWm.motion.x, wlWm.motion.y);
}

static void pointerEnterHandler(void * data, struct wl_pointer * pointer,
    uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW,
    wl_fixed_t syW)
{
  MTRACE("enter main=%d surface=%p serial=%u pos=%.3f,%.3f",
      surface == wlWm.surface, (void *)surface, serial,
      wl_fixed_to_double(sxW), wl_fixed_to_double(syW));

  if (!wlInputPointerEnter(&wlWm.input, surface == wlWm.surface))
    return;

  wl_pointer_set_cursor(pointer, serial, wlWm.cursor, wlWm.cursorHotX, wlWm.cursorHotY);
  wlWm.pointerEnterSerial = serial;

  wlMotionAbs(&wlWm.motion, wl_fixed_to_double(sxW),
      wl_fixed_to_double(syW));
  wlInputPointerMotion(&wlWm.input, wlWm.motion.x, wlWm.motion.y);
}

static void pointerLeaveHandler(void * data, struct wl_pointer * pointer,
    uint32_t serial, struct wl_surface * surface)
{
  MTRACE("leave main=%d surface=%p serial=%u",
      surface == wlWm.surface, (void *)surface, serial);

  wlInputPointerLeave(&wlWm.input, surface == wlWm.surface);
}

static void pointerAxisHandler(void * data, struct wl_pointer * pointer,
  uint32_t serial, uint32_t axis, wl_fixed_t value)
{
  wlInputPointerAxis(&wlWm.input,
      axis == WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_to_double(value));
}

static void pointerButtonHandler(void *data, struct wl_pointer *pointer,
    uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW)
{
  wlInputPointerButton(&wlWm.input, button,
      stateW == WL_POINTER_BUTTON_STATE_PRESSED);
}

static const struct wl_pointer_listener pointerListener = {
  .enter = pointerEnterHandler,
  .leave = pointerLeaveHandler,
  .motion = pointerMotionHandler,
  .button = pointerButtonHandler,
  .axis = pointerAxisHandler,
};

static void lockedHandler(void * data,
    struct zwp_locked_pointer_v1 * pointer)
{
  bool valid;
  LG_LOCK(wlWm.surfaceLock);
  valid = pointer == wlWm.lockedPointer;
  if (valid)
    atomic_store_explicit(&wlWm.lockActive, true, memory_order_release);
  LG_UNLOCK(wlWm.surfaceLock);
  MTRACE("lock active=1 id=%u valid=%d", proxyId(pointer), valid);
}

static void unlockedHandler(void * data,
    struct zwp_locked_pointer_v1 * pointer)
{
  bool valid;
  LG_LOCK(wlWm.surfaceLock);
  valid = pointer == wlWm.lockedPointer;
  if (valid)
    atomic_store_explicit(&wlWm.lockActive, false, memory_order_release);
  LG_UNLOCK(wlWm.surfaceLock);
  MTRACE("lock active=0 id=%u valid=%d", proxyId(pointer), valid);
}

static const struct zwp_locked_pointer_v1_listener lockedListener = {
  .locked   = lockedHandler,
  .unlocked = unlockedHandler,
};

static struct zwp_locked_pointer_v1 * createLock(void)
{
  struct zwp_locked_pointer_v1 * pointer =
    zwp_pointer_constraints_v1_lock_pointer(
        wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL,
        ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
  zwp_locked_pointer_v1_add_listener(pointer, &lockedListener, NULL);
  return pointer;
}

static void relativePointerMotionHandler(void * data,
    struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo,
    wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW,
    wl_fixed_t dyUnaccelW)
{
  const bool active =
    atomic_load_explicit(&wlWm.lockActive, memory_order_acquire);
  const double dx = wl_fixed_to_double(dxW);
  const double dy = wl_fixed_to_double(dyW);
  const double rawX = wl_fixed_to_double(dxUnaccelW);
  const double rawY = wl_fixed_to_double(dyUnaccelW);
  if (active)
    MTRACE("rel time=%u:%u delta=%.3f,%.3f raw=%.3f,%.3f "
        "pos=%.3f,%.3f", timeHi, timeLo, dx, dy, rawX, rawY,
        wlWm.motion.x, wlWm.motion.y);

  wlInputRelativeMotion(&wlWm.input, active, dx, dy, rawX, rawY);
}

static const struct zwp_relative_pointer_v1_listener relativePointerListener = {
    .relative_motion = relativePointerMotionHandler,
};

// Keyboard-handling listeners.

static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard,
    uint32_t format, int fd, uint32_t size)
{
  if (!wlWm.xkb)
    goto done;

  if (wlWm.keymap)
  {
    xkb_keymap_unref(wlWm.keymap);
    wlWm.keymap = NULL;
  }

  if (wlWm.xkbState)
  {
    xkb_state_unref(wlWm.xkbState);
    wlWm.xkbState = NULL;
  }

  if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
  {
    DEBUG_WARN("Unsupported keymap format, keyboard input will not work: %d", format);
    goto done;
  }

  char * map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
  if (map == MAP_FAILED)
  {
    DEBUG_ERROR("Failed to mmap keymap: %s", strerror(errno));
    goto done;
  }

  wlWm.keymap = xkb_keymap_new_from_string(wlWm.xkb, map,
    XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);

  if (!wlWm.keymap)
    DEBUG_WARN("Failed to load keymap, keyboard input will not work");

  munmap(map, size);

  if (wlWm.keymap)
  {
    wlWm.xkbState = xkb_state_new(wlWm.keymap);
    if (!wlWm.xkbState)
      DEBUG_WARN("Failed to create xkb_state");
  }

done:
  close(fd);
}

bool waylandGetKeyLabel(int key, char * label, size_t size)
{
  if (!wlWm.xkbState)
    return false;

  key += 8; // xkb scancode is evdev scancode + 8
  xkb_keysym_t sym = xkb_state_key_get_one_sym(wlWm.xkbState, key);
  sym = xkb_keysym_to_upper(sym);

  const uint32_t codepoint = xkb_keysym_to_utf32(sym);
  return codepoint > 0x20 && codepoint != 0x7F &&
    xkb_keysym_to_utf8(sym, label, size) > 0;
}

static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard,
    uint32_t serial, struct wl_surface * surface, struct wl_array * keys)
{
  if (surface != wlWm.surface)
  {
    wlInputKeyboardEnter(&wlWm.input, false, NULL, 0);
    return;
  }

  wlWm.keyboardEnterSerial = serial;
  wlInputKeyboardEnter(&wlWm.input, true, keys->data,
      keys->size / sizeof(uint32_t));
}

static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard,
    uint32_t serial, struct wl_surface * surface)
{
  wlInputKeyboardLeave(&wlWm.input, surface == wlWm.surface);
}

static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard,
    uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
{
  if (!wlWm.input.focused)
    return;

  const bool pressed = state == WL_KEYBOARD_KEY_STATE_PRESSED;
  if (!wlWm.xkbState || !pressed)
  {
    wlInputKeyboardKey(&wlWm.input, key, pressed, NULL);
    return;
  }

  const xkb_keycode_t xkbKey = key + 8;
  const int size = xkb_state_key_get_utf8(wlWm.xkbState, xkbKey, NULL, 0);
  if (size <= 0)
  {
    wlInputKeyboardKey(&wlWm.input, key, true, NULL);
    return;
  }

  char buffer[size + 1];
  xkb_state_key_get_utf8(wlWm.xkbState, xkbKey, buffer, size + 1);
  wlInputKeyboardKey(&wlWm.input, key, true, buffer);
}

static void keyboardModifiersHandler(void * data,
    struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed,
    uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
{
  if (!wlWm.xkbState)
    return;

  xkb_state_update_mask(wlWm.xkbState, modsDepressed, modsLatched, modsLocked, 0, 0, group);
  const bool ctrl = xkb_state_mod_name_is_active(wlWm.xkbState,
      XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) > 0;
  const bool shift = xkb_state_mod_name_is_active(wlWm.xkbState,
      XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) > 0;
  const bool alt = xkb_state_mod_name_is_active(wlWm.xkbState,
      XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) > 0;
  const bool super = xkb_state_mod_name_is_active(wlWm.xkbState,
      XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) > 0;

  const bool numLock = xkb_state_led_name_is_active(wlWm.xkbState,
      XKB_LED_NAME_NUM) > 0;
  const bool capsLock = xkb_state_led_name_is_active(wlWm.xkbState,
      XKB_LED_NAME_CAPS) > 0;
  const bool scrollLock = xkb_state_led_name_is_active(wlWm.xkbState,
      XKB_LED_NAME_SCROLL) > 0;

  wlInputKeyboardState(&wlWm.input, ctrl, shift, alt, super,
      numLock, capsLock, scrollLock);
}

static const struct wl_keyboard_listener keyboardListener = {
  .keymap = keyboardKeymapHandler,
  .enter = keyboardEnterHandler,
  .leave = keyboardLeaveHandler,
  .key = keyboardKeyHandler,
  .modifiers = keyboardModifiersHandler,
};

static void ensureCapturePointer(bool requestCapture)
{
  uint32_t relativeId         = 0;
  uint32_t lockId             = 0;
  uint64_t lockSeq            = 0;
  bool     captureRequested    = false;
  bool     havePointer         = false;
  bool     haveRelativeManager = false;
  bool     haveConstraints     = false;
  INTERLOCKED_SECTION(wlWm.surfaceLock,
  {
    if (requestCapture)
      wlWm.captureRequested = true;

    captureRequested    = wlWm.captureRequested;
    havePointer         = !!wlWm.pointer;
    haveRelativeManager = !!wlWm.relativePointerManager;
    haveConstraints     = !!wlWm.pointerConstraints;

    if (captureRequested && havePointer && haveRelativeManager &&
        haveConstraints)
    {
      if (!wlWm.relativePointer)
      {
        wlWm.relativePointer =
          zwp_relative_pointer_manager_v1_get_relative_pointer(
            wlWm.relativePointerManager, wlWm.pointer);
        zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
            &relativePointerListener, NULL);
        relativeId = proxyId(wlWm.relativePointer);
      }

      if (!wlWm.lockedPointer)
      {
        atomic_store_explicit(&wlWm.lockActive, false,
            memory_order_release);
        wlWm.lockedPointer = createLock();
        lockId = proxyId(wlWm.lockedPointer);
        lockSeq = app_mouseSeq();
      }
    }
  });

  if (relativeId)
    MTRACE("rel req id=%u why=capture", relativeId);

  MLOG(lockSeq, "lock req id=%u why=capture", lockId);
  if (captureRequested &&
      (!havePointer || !haveRelativeManager || !haveConstraints))
    MTRACE("capture skip pointer=%d relMgr=%d constraints=%d",
        havePointer, haveRelativeManager, haveConstraints);
}

static void waylandCleanUpPointer(bool notify)
{
  uint32_t lockId = 0;
  uint64_t lockSeq = 0;
  INTERLOCKED_SECTION(wlWm.surfaceLock,
  {
    atomic_store_explicit(&wlWm.lockActive, false, memory_order_release);

    if (wlWm.lockedPointer)
    {
      lockId = proxyId(wlWm.lockedPointer);
      zwp_locked_pointer_v1_destroy(wlWm.lockedPointer);
      wlWm.lockedPointer = NULL;
      lockSeq = app_mouseSeq();
    }

    if (wlWm.relativePointer)
    {
      zwp_relative_pointer_v1_destroy(wlWm.relativePointer);
      wlWm.relativePointer = NULL;
    }

    wl_pointer_destroy(wlWm.pointer);
    wlWm.pointer = NULL;
  });

  MLOG(lockSeq, "lock destroy id=%u why=pointer", lockId);
  wlInputPointerDisconnect(&wlWm.input, notify);
}

// Seat-handling listeners.

static void handlePointerCapability(uint32_t capabilities)
{
  bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER;
  if (!hasPointer && wlWm.pointer)
    waylandCleanUpPointer(true);
  else if (hasPointer && !wlWm.pointer)
  {
    LG_LOCK(wlWm.surfaceLock);
    wlWm.pointer = wl_seat_get_pointer(wlWm.seat);
    wl_pointer_add_listener(wlWm.pointer, &pointerListener, NULL);
    LG_UNLOCK(wlWm.surfaceLock);
    waylandSetPointer(wlWm.cursorId);
    ensureCapturePointer(false);
  }
}

static void handleKeyboardCapability(uint32_t capabilities)
{
  bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD;
  if (!hasKeyboard && wlWm.keyboard)
  {
    wl_keyboard_destroy(wlWm.keyboard);
    wlWm.keyboard = NULL;
  }
  else if (hasKeyboard && !wlWm.keyboard)
  {
    wlWm.keyboard = wl_seat_get_keyboard(wlWm.seat);
    wl_keyboard_add_listener(wlWm.keyboard, &keyboardListener, NULL);
  }
}

static void seatCapabilitiesHandler(void * data, struct wl_seat * seat,
    uint32_t capabilities)
{
  wlWm.capabilities = capabilities;
  handlePointerCapability(capabilities);
  handleKeyboardCapability(capabilities);
}

static void seatNameHandler(void * data, struct wl_seat * seat,
    const char * name)
{
  // Do nothing.
}

static const struct wl_seat_listener seatListener = {
    .capabilities = seatCapabilitiesHandler,
    .name = seatNameHandler,
};

bool waylandInputInit(bool allowNoInput)
{
  wlInputInit(&wlWm.input, &inputSink, NULL);

  if (!wlWm.seat)
  {
    MTRACE("input seat=0 allowNone=%d", allowNoInput);
    if (allowNoInput)
    {
      DEBUG_WARN("Compositor missing wl_seat, input will be disabled");
      return true;
    }

    DEBUG_ERROR("Compositor missing wl_seat, will not proceed");
    return false;
  }

  if (!wlWm.relativePointerManager)
    DEBUG_WARN("zwp_relative_pointer_manager_v1 not exported by compositor, "
               "mouse will not be captured");

  if (!wlWm.pointerConstraints)
    DEBUG_WARN("zwp_pointer_constraints_v1 not exported by compositor, mouse "
               "will not be captured");

  if (!wlWm.keyboardInhibitManager)
    DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by "
               "compositor, keyboard will not be grabbed");

  MTRACE("input seat=1 relMgr=%d constraints=%d",
      !!wlWm.relativePointerManager,
      !!wlWm.pointerConstraints);

  wlWm.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
  if (!wlWm.xkb)
    DEBUG_WARN("Failed to initialize xkb, keyboard input will not work");

  wl_seat_add_listener(wlWm.seat, &seatListener, NULL);
  wl_display_roundtrip(wlWm.display);

  return true;
}

void waylandInputFree(void)
{
  if (!wlWm.seat)
    return;

  if (wlWm.pointer)
    waylandCleanUpPointer(false);

  // The only legal way the keyboard can be null is if it never existed.
  // When unplugged, the compositor must have an inert object.
  if (wlWm.keyboard)
    wl_keyboard_destroy(wlWm.keyboard);

  wl_seat_destroy(wlWm.seat);

  if (wlWm.xkbState)
    xkb_state_unref(wlWm.xkbState);

  if (wlWm.keymap)
    xkb_keymap_unref(wlWm.keymap);

  if (wlWm.xkb)
    xkb_context_unref(wlWm.xkb);
}

void waylandGrabPointer(void)
{
}

void waylandUngrabPointer(void)
{
}

void waylandCapturePointer(void)
{
  ensureCapturePointer(true);
}

void waylandUncapturePointer(void)
{
  uint32_t lockId     = 0;
  uint32_t relativeId = 0;
  uint64_t lockSeq    = 0;
  INTERLOCKED_SECTION(wlWm.surfaceLock,
  {
    wlWm.captureRequested = false;
    atomic_store_explicit(&wlWm.lockActive, false, memory_order_release);

    if (wlWm.lockedPointer)
    {
      lockId = proxyId(wlWm.lockedPointer);
      zwp_locked_pointer_v1_destroy(wlWm.lockedPointer);
      wlWm.lockedPointer = NULL;
      lockSeq = app_mouseSeq();
    }

    if (wlWm.relativePointer)
    {
      relativeId = proxyId(wlWm.relativePointer);
      zwp_relative_pointer_v1_destroy(wlWm.relativePointer);
      wlWm.relativePointer = NULL;
    }
  });

  MLOG(lockSeq, "lock destroy id=%u why=uncapture", lockId);
  if (relativeId)
    MTRACE("rel destroy id=%u why=uncapture", relativeId);
}

bool waylandIsPointerGrabbed(void)
{
  return false;
}

bool waylandIsPointerCaptured(void)
{
  return atomic_load_explicit(&wlWm.lockActive, memory_order_acquire);
}

void waylandGrabKeyboard(void)
{
  if (wlWm.seat &&
      wlWm.keyboardInhibitManager && !wlWm.keyboardInhibitor)
  {
    wlWm.keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(
        wlWm.keyboardInhibitManager, wlWm.surface, wlWm.seat);
  }
}

void waylandUngrabKeyboard(void)
{
  if (wlWm.keyboardInhibitor)
  {
    zwp_keyboard_shortcuts_inhibitor_v1_destroy(wlWm.keyboardInhibitor);
    wlWm.keyboardInhibitor = NULL;
  }
}

void waylandWarpPointer(int x, int y, bool exiting)
{
  const int reqX = x;
  const int reqY = y;
  if (!wlWm.input.pointerInSurface)
  {
    MTRACE("warp drop=surface target=%d,%d exit=%d", x, y, exiting);
    return;
  }

  if (wlWm.lockedPointer)
  {
    MTRACE("warp drop=lock target=%d,%d exit=%d", x, y, exiting);
    return;
  }

  if (!wlWm.pointerWarpper)
  {
    LG_LOCK(wlWm.surfaceLock);

    if (wlWm.lockedPointer)
    {
      LG_UNLOCK(wlWm.surfaceLock);
      MTRACE("warp drop=lock-race target=%d,%d exit=%d", x, y, exiting);
      return;
    }
  }

  int width, height;
  wlWm.desktop->getSize(&width, &height);

  if (x < 0) x = 0;
  else if (x >= width) x = width - 1;
  if (y < 0) y = 0;
  else if (y >= height) y = height - 1;

  uint32_t tempId = 0;
  uint64_t warpSeq;

  if (wlWm.pointerWarpper)
  {
    wp_pointer_warp_v1_warp_pointer(wlWm.pointerWarpper, wlWm.surface,
      wlWm.pointer, wl_fixed_from_int(x), wl_fixed_from_int(y),
      wlWm.pointerEnterSerial);
    warpSeq = app_mouseSeq();
  } else {
    struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
    wl_region_add(region, x, y, 1, 1);

    struct zwp_confined_pointer_v1 * confine = zwp_pointer_constraints_v1_confine_pointer(
      wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, region,
      ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
    tempId = proxyId(confine);
    wl_surface_commit(wlWm.surface);
    zwp_confined_pointer_v1_destroy(confine);

    wl_surface_commit(wlWm.surface);
    wl_region_destroy(region);

    warpSeq = app_mouseSeq();
    LG_UNLOCK(wlWm.surfaceLock);
  }

  MLOG(warpSeq, "warp req=%d,%d target=%d,%d exit=%d temp=%u",
      reqX, reqY, x, y, exiting, tempId);
}

void waylandRealignPointer(void)
{
  wlInputRealignPointer(&wlWm.input, wlWm.motion.x, wlWm.motion.y);
}

void waylandGuestPointerUpdated(double x, double y, double localX,
    double localY)
{
  (void)x;
  (void)y;
  (void)localX;
  (void)localY;
}
