/*
 * Copyright © 2012 Intel Corporation
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the copyright holders not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  The copyright holders make
 * no representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/* The file is loosely based on xwayland/selection.c from Weston */

#include "config.h"

#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <glib-unix.h>
#include <gio/gunixoutputstream.h>
#include <gio/gunixinputstream.h>
#include <gdk/gdkx.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xfixes.h>
#include <meta/errors.h>
#include "meta-xwayland-private.h"
#include "meta-xwayland-selection-private.h"
#include "meta-wayland-data-device.h"

#define INCR_CHUNK_SIZE (128 * 1024)
#define XDND_VERSION 5

typedef struct {
  MetaXWaylandSelection *selection_data;
  GInputStream *stream;
  GCancellable *cancellable;
  MetaWindow *window;
  XSelectionRequestEvent request_event;
  guchar buffer[INCR_CHUNK_SIZE];
  gsize buffer_len;
  guint incr : 1;
} WaylandSelectionData;

typedef struct {
  MetaXWaylandSelection *selection_data;
  GOutputStream *stream;
  GCancellable *cancellable;
  gchar *mime_type;
  guint incr : 1;
} X11SelectionData;

typedef struct {
  Atom selection_atom;
  Window window;
  Window owner;
  Time timestamp;
  MetaWaylandDataSource *source; /* owned by MetaWaylandDataDevice */
  WaylandSelectionData *wayland_selection;
  X11SelectionData *x11_selection;

  struct wl_listener ownership_listener;
} MetaSelectionBridge;

typedef struct {
  MetaSelectionBridge selection;
  MetaWaylandSurface *focus_surface;
  Window dnd_window; /* Mutter-internal window, acts as peer on wayland drop sites */
  Window dnd_dest; /* X11 drag dest window */
  guint32 last_motion_time;
} MetaDndBridge;

struct _MetaWaylandDataSourceXWayland
{
  MetaWaylandDataSource parent;

  MetaSelectionBridge *selection;
  guint has_utf8_string_atom : 1;
};

struct _MetaXWaylandSelection {
  MetaSelectionBridge clipboard;
  MetaDndBridge dnd;
};

enum {
  ATOM_DND_SELECTION,
  ATOM_DND_AWARE,
  ATOM_DND_STATUS,
  ATOM_DND_POSITION,
  ATOM_DND_ENTER,
  ATOM_DND_LEAVE,
  ATOM_DND_DROP,
  ATOM_DND_FINISHED,
  ATOM_DND_PROXY,
  ATOM_DND_TYPE_LIST,
  ATOM_DND_ACTION_MOVE,
  ATOM_DND_ACTION_COPY,
  ATOM_DND_ACTION_ASK,
  ATOM_DND_ACTION_PRIVATE,
  N_DND_ATOMS
};

/* Matches order in enum above */
const gchar *atom_names[] = {
  "XdndSelection",
  "XdndAware",
  "XdndStatus",
  "XdndPosition",
  "XdndEnter",
  "XdndLeave",
  "XdndDrop",
  "XdndFinished",
  "XdndProxy",
  "XdndTypeList",
  "XdndActionMove",
  "XdndActionCopy",
  "XdndActionAsk",
  "XdndActionPrivate",
  NULL
};

Atom xdnd_atoms[N_DND_ATOMS];

G_DEFINE_TYPE (MetaWaylandDataSourceXWayland, meta_wayland_data_source_xwayland,
               META_TYPE_WAYLAND_DATA_SOURCE);

/* XDND helpers */
static Atom
action_to_atom (uint32_t action)
{
  if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
    return xdnd_atoms[ATOM_DND_ACTION_COPY];
  else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
    return xdnd_atoms[ATOM_DND_ACTION_MOVE];
  else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
    return xdnd_atoms[ATOM_DND_ACTION_ASK];
  else
    return None;
}

static enum wl_data_device_manager_dnd_action
atom_to_action (Atom atom)
{
  if (atom == xdnd_atoms[ATOM_DND_ACTION_COPY] ||
      atom == xdnd_atoms[ATOM_DND_ACTION_PRIVATE])
    return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
  else if (atom == xdnd_atoms[ATOM_DND_ACTION_MOVE])
    return WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
  else if (atom == xdnd_atoms[ATOM_DND_ACTION_ASK])
    return WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK;
  else
    return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
}

static void
xdnd_send_enter (MetaXWaylandSelection *selection_data,
                 Window                 dest)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
  MetaSelectionBridge *selection = &selection_data->dnd.selection;
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  MetaWaylandDataSource *data_source;
  XEvent xev = { 0 };
  gchar **p;
  struct wl_array *source_mime_types;

  data_source = compositor->seat->data_device.dnd_data_source;
  xev.xclient.type = ClientMessage;
  xev.xclient.message_type = xdnd_atoms[ATOM_DND_ENTER];
  xev.xclient.format = 32;
  xev.xclient.window = dest;

  xev.xclient.data.l[0] = selection->window;
  xev.xclient.data.l[1] = XDND_VERSION << 24; /* version */
  xev.xclient.data.l[2] = xev.xclient.data.l[3] = xev.xclient.data.l[4] = 0;

  source_mime_types = meta_wayland_data_source_get_mime_types (data_source);
  if (source_mime_types->size <= 3)
    {
      /* The mimetype atoms fit in this same message */
      gint i = 2;

      wl_array_for_each (p, source_mime_types)
        {
          xev.xclient.data.l[i++] = gdk_x11_get_xatom_by_name (*p);
        }
    }
  else
    {
      /* We have more than 3 mimetypes, we must set up
       * the mimetype list as a XdndTypeList property.
       */
      Atom *atomlist;
      gint i = 0;

      xev.xclient.data.l[1] |= 1;
      atomlist = g_new0 (Atom, source_mime_types->size);

      wl_array_for_each (p, source_mime_types)
        {
          atomlist[i++] = gdk_x11_get_xatom_by_name (*p);
        }

      XChangeProperty (xdisplay, selection->window,
                       xdnd_atoms[ATOM_DND_TYPE_LIST],
                       XA_ATOM, 32, PropModeReplace,
                       (guchar *) atomlist, i);
    }

  XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}

static void
xdnd_send_leave (MetaXWaylandSelection *selection_data,
                 Window                 dest)
{
  MetaSelectionBridge *selection = &selection_data->dnd.selection;
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  XEvent xev = { 0 };

  xev.xclient.type = ClientMessage;
  xev.xclient.message_type = xdnd_atoms[ATOM_DND_LEAVE];
  xev.xclient.format = 32;
  xev.xclient.window = dest;
  xev.xclient.data.l[0] = selection->window;

  XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}

static void
xdnd_send_position (MetaXWaylandSelection *selection_data,
                    Window                 dest,
                    uint32_t               time,
                    int                    x,
                    int                    y)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
  MetaSelectionBridge *selection = &selection_data->dnd.selection;
  MetaWaylandDataSource *source = compositor->seat->data_device.dnd_data_source;
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  uint32_t action = 0, user_action, actions;
  XEvent xev = { 0 };

  user_action = meta_wayland_data_source_get_user_action (source);
  actions = meta_wayland_data_source_get_actions (source);

  if (user_action & actions)
    action = user_action;
  if (!action)
    action = actions;

  xev.xclient.type = ClientMessage;
  xev.xclient.message_type = xdnd_atoms[ATOM_DND_POSITION];
  xev.xclient.format = 32;
  xev.xclient.window = dest;

  xev.xclient.data.l[0] = selection->window;
  xev.xclient.data.l[1] = 0;
  xev.xclient.data.l[2] = (x << 16) | y;
  xev.xclient.data.l[3] = time;
  xev.xclient.data.l[4] = action_to_atom (action);

  XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}

static void
xdnd_send_drop (MetaXWaylandSelection *selection_data,
                Window                 dest,
                uint32_t               time)
{
  MetaSelectionBridge *selection = &selection_data->dnd.selection;
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  XEvent xev = { 0 };

  xev.xclient.type = ClientMessage;
  xev.xclient.message_type = xdnd_atoms[ATOM_DND_DROP];
  xev.xclient.format = 32;
  xev.xclient.window = dest;

  xev.xclient.data.l[0] = selection->window;
  xev.xclient.data.l[2] = time;

  XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}

static void
xdnd_send_finished (MetaXWaylandSelection *selection_data,
                    Window                 dest,
                    gboolean               accepted)
{
  MetaDndBridge *selection = &selection_data->dnd;
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  MetaWaylandDataSource *source = selection_data->dnd.selection.source;
  uint32_t action = 0;
  XEvent xev = { 0 };

  xev.xclient.type = ClientMessage;
  xev.xclient.message_type = xdnd_atoms[ATOM_DND_FINISHED];
  xev.xclient.format = 32;
  xev.xclient.window = dest;

  xev.xclient.data.l[0] = selection->dnd_window;

  if (accepted)
    {
      action = meta_wayland_data_source_get_current_action (source);
      xev.xclient.data.l[1] = 1; /* Drop successful */
      xev.xclient.data.l[2] = action_to_atom (action);
    }

  XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}

static void
xdnd_send_status (MetaXWaylandSelection *selection_data,
                  Window                 dest,
                  uint32_t               action)
{
  MetaDndBridge *selection = &selection_data->dnd;
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  XEvent xev = { 0 };

  xev.xclient.type = ClientMessage;
  xev.xclient.message_type = xdnd_atoms[ATOM_DND_STATUS];
  xev.xclient.format = 32;
  xev.xclient.window = dest;

  xev.xclient.data.l[0] = selection->dnd_window;
  xev.xclient.data.l[1] = 1 << 1; /* Bit 2: dest wants XdndPosition messages */
  xev.xclient.data.l[4] = action_to_atom (action);

  if (xev.xclient.data.l[4])
    xev.xclient.data.l[1] |= 1 << 0; /* Bit 1: dest accepts the drop */

  XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}

static void
meta_xwayland_init_dnd (MetaXWaylandManager *manager)
{
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  MetaDndBridge *dnd = &manager->selection_data->dnd;
  XSetWindowAttributes attributes;
  guint32 i, version = XDND_VERSION;

  for (i = 0; i < N_DND_ATOMS; i++)
    xdnd_atoms[i] = gdk_x11_get_xatom_by_name (atom_names[i]);

  attributes.event_mask = PropertyChangeMask | SubstructureNotifyMask;
  attributes.override_redirect = True;

  dnd->dnd_window = XCreateWindow (xdisplay,
                                   gdk_x11_window_get_xid (gdk_get_default_root_window ()),
                                   -1, -1, 1, 1,
                                   0, /* border width */
                                   0, /* depth */
                                   InputOnly, /* class */
                                   CopyFromParent, /* visual */
                                   CWEventMask | CWOverrideRedirect,
                                   &attributes);
  XChangeProperty (xdisplay, dnd->dnd_window,
                   xdnd_atoms[ATOM_DND_AWARE],
                   XA_ATOM, 32, PropModeReplace,
                   (guchar*) &version, 1);
}

static void
meta_xwayland_shutdown_dnd (MetaXWaylandManager *manager)
{
  MetaDndBridge *dnd = &manager->selection_data->dnd;

  XDestroyWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
                  dnd->dnd_window);
  dnd->dnd_window = None;
}

/* X11/Wayland data bridges */

static MetaSelectionBridge *
atom_to_selection_bridge (MetaWaylandCompositor *compositor,
                          Atom                   selection_atom)
{
  MetaXWaylandSelection *selection_data = compositor->xwayland_manager.selection_data;

  if (selection_atom == selection_data->clipboard.selection_atom)
    return &selection_data->clipboard;
  else if (selection_atom == selection_data->dnd.selection.selection_atom)
    return &selection_data->dnd.selection;
  else
    return NULL;
}

static X11SelectionData *
x11_selection_data_new (MetaXWaylandSelection *selection_data,
                        int                    fd,
                        const char            *mime_type)
{
  X11SelectionData *data;

  data = g_slice_new0 (X11SelectionData);
  data->selection_data = selection_data;
  data->stream = g_unix_output_stream_new (fd, TRUE);
  data->cancellable = g_cancellable_new ();
  data->mime_type = g_strdup (mime_type);

  return data;
}

static void
x11_selection_data_free (X11SelectionData *data)
{
  g_cancellable_cancel (data->cancellable);
  g_object_unref (data->cancellable);
  g_object_unref (data->stream);
  g_free (data->mime_type);
  g_slice_free (X11SelectionData, data);
}

static void
x11_selection_data_send_finished (MetaSelectionBridge *selection,
                                  gboolean             success)
{
  uint32_t action = meta_wayland_data_source_get_current_action (selection->source);

  if (!selection->x11_selection)
    return;

  if (success && action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
    {
      Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());

      /* Request data deletion on the drag source */
      XConvertSelection (xdisplay,
                         selection->selection_atom,
                         gdk_x11_get_xatom_by_name ("DELETE"),
                         gdk_x11_get_xatom_by_name ("_META_SELECTION"),
                         selection->window,
                         CurrentTime);
    }

  xdnd_send_finished (selection->x11_selection->selection_data,
                      selection->owner, success);
}

static void
x11_selection_data_finish (MetaSelectionBridge *selection,
                           gboolean             success)
{
  if (!selection->x11_selection)
    return;

  if (selection == &selection->x11_selection->selection_data->dnd.selection)
    x11_selection_data_send_finished (selection, success);

  g_clear_pointer (&selection->x11_selection,
                   (GDestroyNotify) x11_selection_data_free);
}

static void
x11_selection_data_close (X11SelectionData *data)
{
  g_output_stream_close (data->stream, data->cancellable, NULL);
}

static void
x11_data_write_cb (GObject      *object,
                   GAsyncResult *res,
                   gpointer      user_data)
{
  MetaSelectionBridge *selection = user_data;
  X11SelectionData *data = selection->x11_selection;
  GError *error = NULL;
  gboolean success = TRUE;

  g_output_stream_write_finish (G_OUTPUT_STREAM (object), res, &error);

  if (error)
    {
      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        {
          g_error_free (error);
          return;
        }

      g_warning ("Error writing from X11 selection: %s\n", error->message);
      g_error_free (error);
      success = FALSE;
    }

  if (success && data->incr)
    {
      Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
      XDeleteProperty (xdisplay, selection->window,
                       gdk_x11_get_xatom_by_name ("_META_SELECTION"));
    }
  else
    {
      x11_selection_data_close (selection->x11_selection);
      x11_selection_data_finish (selection, success);
    }
}

static void
x11_selection_data_write (MetaSelectionBridge *selection,
                          guchar              *buffer,
                          gulong               len)
{
  X11SelectionData *data = selection->x11_selection;

  g_output_stream_write_async (data->stream, buffer, len,
                               G_PRIORITY_DEFAULT, data->cancellable,
                               x11_data_write_cb, selection);
}

static MetaWaylandDataSource *
data_device_get_active_source_for_atom (MetaWaylandDataDevice *data_device,
                                        Atom                   selection_atom)
{
  if (selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD"))
    return data_device->selection_data_source;
  else if (selection_atom == xdnd_atoms[ATOM_DND_SELECTION])
    return data_device->dnd_data_source;
  else
    return NULL;
}

static WaylandSelectionData *
wayland_selection_data_new (XSelectionRequestEvent *request_event,
                            MetaWaylandCompositor  *compositor)
{
  MetaWaylandDataDevice *data_device;
  MetaWaylandDataSource *wayland_source;
  MetaSelectionBridge *selection;
  WaylandSelectionData *data;
  const gchar *mime_type;
  GError *error = NULL;
  int p[2];

  selection = atom_to_selection_bridge (compositor, request_event->selection);

  if (!selection)
    return NULL;

  if (!g_unix_open_pipe (p, FD_CLOEXEC, &error))
    {
      g_critical ("Failed to open pipe: %s\n", error->message);
      g_error_free (error);
      return NULL;
    }

  data_device = &compositor->seat->data_device;
  mime_type = gdk_x11_get_xatom_name (request_event->target);

  if (!g_unix_set_fd_nonblocking (p[0], TRUE, &error) ||
      !g_unix_set_fd_nonblocking (p[1], TRUE, &error))
    {
      if (error)
        {
          g_critical ("Failed to make fds non-blocking: %s\n", error->message);
          g_error_free (error);
        }

      close (p[0]);
      close (p[1]);
      return NULL;
    }

  wayland_source = data_device_get_active_source_for_atom (data_device,
                                                           selection->selection_atom),
  meta_wayland_data_source_send (wayland_source, mime_type, p[1]);

  data = g_slice_new0 (WaylandSelectionData);
  data->request_event = *request_event;
  data->cancellable = g_cancellable_new ();
  data->stream = g_unix_input_stream_new (p[0], TRUE);

  data->window = meta_display_lookup_x_window (meta_get_display (),
                                               data->request_event.requestor);

  if (!data->window)
    {
      /* Not a managed window, set the PropertyChangeMask
       * for INCR deletion notifications.
       */
      XSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
                    data->request_event.requestor, PropertyChangeMask);
    }

  return data;
}

static void
reply_selection_request (XSelectionRequestEvent *request_event,
                         gboolean                accepted)
{
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  XSelectionEvent event;

  memset(&event, 0, sizeof (XSelectionEvent));
  event.type = SelectionNotify;
  event.time = request_event->time;
  event.requestor = request_event->requestor;
  event.selection = request_event->selection;
  event.target = request_event->target;
  event.property = accepted ? request_event->property : None;

  XSendEvent (xdisplay, request_event->requestor,
              False, NoEventMask, (XEvent *) &event);
}

static void
wayland_selection_data_free (WaylandSelectionData *data)
{
  if (!data->window)
    {
      MetaDisplay *display = meta_get_display ();

      meta_error_trap_push (display);
      XSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
                    data->request_event.requestor, NoEventMask);
      meta_error_trap_pop (display);
    }

  g_cancellable_cancel (data->cancellable);
  g_object_unref (data->cancellable);
  g_object_unref (data->stream);
  g_slice_free (WaylandSelectionData, data);
}

static void
wayland_selection_update_x11_property (WaylandSelectionData *data)
{
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());

  XChangeProperty (xdisplay,
                   data->request_event.requestor,
                   data->request_event.property,
                   data->request_event.target,
                   8, PropModeReplace,
                   data->buffer, data->buffer_len);
  data->buffer_len = 0;
}

static void
wayland_data_read_cb (GObject      *object,
                      GAsyncResult *res,
                      gpointer      user_data)
{
  MetaSelectionBridge *selection = user_data;
  WaylandSelectionData *data = selection->wayland_selection;
  GError *error = NULL;
  gsize bytes_read;

  bytes_read = g_input_stream_read_finish (G_INPUT_STREAM (object),
                                           res, &error);
  if (error)
    {
      g_warning ("Error transfering wayland clipboard to X11: %s\n",
                 error->message);
      g_error_free (error);

      if (data)
        {
          reply_selection_request (&data->request_event, FALSE);
          g_clear_pointer (&selection->wayland_selection,
                           (GDestroyNotify) wayland_selection_data_free);
        }

      return;
    }

  data->buffer_len = bytes_read;

  if (bytes_read == INCR_CHUNK_SIZE)
    {
      if (!data->incr)
        {
          Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
          guint32 incr_chunk_size = INCR_CHUNK_SIZE;

          /* Not yet in incr */
          data->incr = TRUE;
          XChangeProperty (xdisplay,
                           data->request_event.requestor,
                           data->request_event.property,
                           gdk_x11_get_xatom_by_name ("INCR"),
                           32, PropModeReplace,
                           (guchar *) &incr_chunk_size, 1);
          reply_selection_request (&data->request_event, TRUE);
        }
      else
        wayland_selection_update_x11_property (data);
    }
  else
    {
      if (!data->incr)
        {
          /* Non-incr transfer finished */
          wayland_selection_update_x11_property (data);
          reply_selection_request (&data->request_event, TRUE);
        }
      else if (data->incr)
        {
          /* Incr transfer complete, setting a new property */
          wayland_selection_update_x11_property (data);

          if (bytes_read > 0)
            return;
        }

      g_clear_pointer (&selection->wayland_selection,
                       (GDestroyNotify) wayland_selection_data_free);
    }
}

static void
wayland_selection_data_read (MetaSelectionBridge *selection)
{
  WaylandSelectionData *data = selection->wayland_selection;

  g_input_stream_read_async (data->stream, data->buffer,
                             INCR_CHUNK_SIZE, G_PRIORITY_DEFAULT,
                             data->cancellable,
                             wayland_data_read_cb, selection);
}

static void
meta_xwayland_selection_get_incr_chunk (MetaWaylandCompositor *compositor,
                                        MetaSelectionBridge   *selection)
{
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  gulong nitems_ret, bytes_after_ret;
  guchar *prop_ret;
  int format_ret;
  Atom type_ret;

  XGetWindowProperty (xdisplay,
                      selection->window,
                      gdk_x11_get_xatom_by_name ("_META_SELECTION"),
                      0, /* offset */
                      0x1fffffff, /* length */
                      False, /* delete */
                      AnyPropertyType,
                      &type_ret,
                      &format_ret,
                      &nitems_ret,
                      &bytes_after_ret,
                      &prop_ret);

  if (nitems_ret > 0)
    {
      x11_selection_data_write (selection, prop_ret, nitems_ret);
    }
  else
    {
      /* Transfer has completed */
      x11_selection_data_close (selection->x11_selection);
      x11_selection_data_finish (selection, TRUE);
    }

  XFree (prop_ret);
}

static void
meta_x11_source_send (MetaWaylandDataSource *source,
                      const gchar           *mime_type,
                      gint                   fd)
{
  MetaWaylandDataSourceXWayland *source_xwayland =
    META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  MetaSelectionBridge *selection = source_xwayland->selection;
  Atom type_atom;

  if (source_xwayland->has_utf8_string_atom &&
      strcmp (mime_type, "text/plain;charset=utf-8") == 0)
    type_atom = gdk_x11_get_xatom_by_name ("UTF8_STRING");
  else
    type_atom = gdk_x11_get_xatom_by_name (mime_type);

  /* Ensure we close previous transactions */
  x11_selection_data_finish (selection, FALSE);

  /* Takes ownership of fd */
  selection->x11_selection =
    x11_selection_data_new (compositor->xwayland_manager.selection_data,
                            fd, mime_type);

  XConvertSelection (xdisplay,
                     selection->selection_atom, type_atom,
                     gdk_x11_get_xatom_by_name ("_META_SELECTION"),
                     selection->window,
                     CurrentTime);
  XFlush (xdisplay);
}

static void
meta_x11_source_target (MetaWaylandDataSource *source,
                        const gchar           *mime_type)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
  MetaWaylandDataSourceXWayland *source_xwayland =
    META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
  MetaSelectionBridge *selection = source_xwayland->selection;
  uint32_t action = 0;

  if (selection->selection_atom == xdnd_atoms[ATOM_DND_SELECTION])
    {
      if (mime_type)
        action = meta_wayland_data_source_get_current_action (source);

      xdnd_send_status (compositor->xwayland_manager.selection_data,
                        selection->owner, action);
    }
}

static void
meta_x11_source_cancel (MetaWaylandDataSource *source)
{
  MetaWaylandDataSourceXWayland *source_xwayland =
    META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
  MetaSelectionBridge *selection = source_xwayland->selection;

  x11_selection_data_send_finished (selection, FALSE);
  g_clear_pointer (&selection->x11_selection,
                   (GDestroyNotify) x11_selection_data_free);
}

static void
meta_x11_source_action (MetaWaylandDataSource *source,
                        uint32_t               action)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
  MetaWaylandDataSourceXWayland *source_xwayland =
    META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
  MetaSelectionBridge *selection = source_xwayland->selection;

  if (selection->selection_atom == xdnd_atoms[ATOM_DND_SELECTION])
    {
      if (!meta_wayland_data_source_has_target (source))
        action = 0;

      xdnd_send_status (compositor->xwayland_manager.selection_data,
                        selection->owner, action);
    }
}

static void
meta_x11_source_drop_performed (MetaWaylandDataSource *source)
{
}

static void
meta_x11_source_drag_finished (MetaWaylandDataSource *source)
{
  MetaWaylandDataSourceXWayland *source_xwayland =
    META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
  MetaSelectionBridge *selection = source_xwayland->selection;

  if (selection->x11_selection)
    x11_selection_data_send_finished (selection, TRUE);
}

static void
meta_wayland_data_source_xwayland_init (MetaWaylandDataSourceXWayland *source_xwayland)
{
}

static void
meta_wayland_data_source_xwayland_class_init (MetaWaylandDataSourceXWaylandClass *klass)
{
  MetaWaylandDataSourceClass *data_source_class =
    META_WAYLAND_DATA_SOURCE_CLASS (klass);

  data_source_class->send = meta_x11_source_send;
  data_source_class->target = meta_x11_source_target;
  data_source_class->cancel = meta_x11_source_cancel;
  data_source_class->action = meta_x11_source_action;
  data_source_class->drop_performed = meta_x11_source_drop_performed;
  data_source_class->drag_finished = meta_x11_source_drag_finished;
}

static MetaWaylandDataSource *
meta_wayland_data_source_xwayland_new (MetaSelectionBridge *selection)
{
  MetaWaylandDataSourceXWayland *source_xwayland;

  source_xwayland = g_object_new (META_TYPE_WAYLAND_DATA_SOURCE_XWAYLAND, NULL);
  source_xwayland->selection = selection;

  return META_WAYLAND_DATA_SOURCE (source_xwayland);
}

static void
meta_x11_drag_dest_focus_in (MetaWaylandDataDevice *data_device,
                             MetaWaylandSurface    *surface,
                             MetaWaylandDataOffer  *offer)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();

  compositor->xwayland_manager.selection_data->dnd.dnd_dest = surface->window->xwindow;
  xdnd_send_enter (compositor->xwayland_manager.selection_data,
                   compositor->xwayland_manager.selection_data->dnd.dnd_dest);
}

static void
meta_x11_drag_dest_focus_out (MetaWaylandDataDevice *data_device,
                              MetaWaylandSurface    *surface)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();

  xdnd_send_leave (compositor->xwayland_manager.selection_data,
                   compositor->xwayland_manager.selection_data->dnd.dnd_dest);
  compositor->xwayland_manager.selection_data->dnd.dnd_dest = None;
}

static void
meta_x11_drag_dest_motion (MetaWaylandDataDevice *data_device,
                           MetaWaylandSurface    *surface,
                           const ClutterEvent    *event)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
  guint32 time;
  gfloat x, y;

  time = clutter_event_get_time (event);
  clutter_event_get_coords (event, &x, &y);
  xdnd_send_position (compositor->xwayland_manager.selection_data,
                      compositor->xwayland_manager.selection_data->dnd.dnd_dest,
                      time, x, y);
}

static void
meta_x11_drag_dest_drop (MetaWaylandDataDevice *data_device,
                         MetaWaylandSurface    *surface)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();

  xdnd_send_drop (compositor->xwayland_manager.selection_data,
                  compositor->xwayland_manager.selection_data->dnd.dnd_dest,
                  meta_display_get_current_time_roundtrip (meta_get_display ()));
}

static void
meta_x11_drag_dest_update (MetaWaylandDataDevice *data_device,
                           MetaWaylandSurface    *surface)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
  MetaWaylandSeat *seat = compositor->seat;
  ClutterPoint pos;

  clutter_input_device_get_coords (seat->pointer.device, NULL, &pos);
  xdnd_send_position (compositor->xwayland_manager.selection_data,
                      compositor->xwayland_manager.selection_data->dnd.dnd_dest,
                      clutter_get_current_event_time (),
                      pos.x, pos.y);
}

static const MetaWaylandDragDestFuncs meta_x11_drag_dest_funcs = {
  meta_x11_drag_dest_focus_in,
  meta_x11_drag_dest_focus_out,
  meta_x11_drag_dest_motion,
  meta_x11_drag_dest_drop,
  meta_x11_drag_dest_update
};

const MetaWaylandDragDestFuncs *
meta_xwayland_selection_get_drag_dest_funcs (void)
{
  return &meta_x11_drag_dest_funcs;
}

static gboolean
meta_xwayland_data_source_fetch_mimetype_list (MetaWaylandDataSource *source,
                                               Window                 window,
                                               Atom                   prop)
{
  MetaWaylandDataSourceXWayland *source_xwayland =
    META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  gulong nitems_ret, bytes_after_ret, i;
  Atom *atoms, type_ret, utf8_string;
  int format_ret;
  struct wl_array *source_mime_types;

  source_mime_types = meta_wayland_data_source_get_mime_types (source);
  if (source_mime_types->size != 0)
    return TRUE;

  utf8_string = gdk_x11_get_xatom_by_name ("UTF8_STRING");
  XGetWindowProperty (xdisplay, window, prop,
                      0, /* offset */
                      0x1fffffff, /* length */
                      True, /* delete */
                      AnyPropertyType,
                      &type_ret,
                      &format_ret,
                      &nitems_ret,
                      &bytes_after_ret,
                      (guchar **) &atoms);

  if (nitems_ret == 0 || type_ret != XA_ATOM)
    {
      XFree (atoms);
      return FALSE;
    }

  for (i = 0; i < nitems_ret; i++)
    {
      const gchar *mime_type;

      if (atoms[i] == utf8_string)
        {
          meta_wayland_data_source_add_mime_type (source,
                                                  "text/plain;charset=utf-8");
          source_xwayland->has_utf8_string_atom = TRUE;
        }

      mime_type = gdk_x11_get_xatom_name (atoms[i]);
      meta_wayland_data_source_add_mime_type (source, mime_type);
    }

  XFree (atoms);

  return TRUE;
}

static void
meta_xwayland_selection_get_x11_targets (MetaWaylandCompositor *compositor,
                                         MetaSelectionBridge   *selection)
{
  MetaWaylandDataSource *data_source;

  data_source = meta_wayland_data_source_xwayland_new (selection);

  if (meta_xwayland_data_source_fetch_mimetype_list (data_source,
                                                     selection->window,
                                                     gdk_x11_get_xatom_by_name ("_META_SELECTION")))
    {
      g_clear_object (&selection->source);
      selection->source = data_source;

      if (selection->selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD"))
        {
          meta_wayland_data_device_set_selection (&compositor->seat->data_device, data_source,
                                                  wl_display_next_serial (compositor->wayland_display));
        }
    }
  else
    g_object_unref (data_source);
}

static void
meta_xwayland_selection_get_x11_data (MetaWaylandCompositor *compositor,
                                      MetaSelectionBridge   *selection)
{
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  gulong nitems_ret, bytes_after_ret;
  guchar *prop_ret;
  int format_ret;
  Atom type_ret;

  if (!selection->x11_selection)
    return;

  XGetWindowProperty (xdisplay,
                      selection->window,
                      gdk_x11_get_xatom_by_name ("_META_SELECTION"),
                      0, /* offset */
                      0x1fffffff, /* length */
                      True, /* delete */
                      AnyPropertyType,
                      &type_ret,
                      &format_ret,
                      &nitems_ret,
                      &bytes_after_ret,
                      &prop_ret);

  selection->x11_selection->incr = (type_ret == gdk_x11_get_xatom_by_name ("INCR"));

  if (selection->x11_selection->incr)
    return;

  if (type_ret == gdk_x11_get_xatom_by_name (selection->x11_selection->mime_type))
    x11_selection_data_write (selection, prop_ret, nitems_ret);

  XFree (prop_ret);
}

static gboolean
meta_xwayland_selection_handle_selection_notify (MetaWaylandCompositor *compositor,
                                                 XEvent                *xevent)
{
  XSelectionEvent *event = (XSelectionEvent *) xevent;
  MetaSelectionBridge *selection;

  selection = atom_to_selection_bridge (compositor, event->selection);

  if (!selection)
    return FALSE;

  /* convert selection failed */
  if (event->property == None)
    {
      g_clear_pointer (&selection->x11_selection,
                       (GDestroyNotify) x11_selection_data_free);
      return FALSE;
    }

  if (event->target == gdk_x11_get_xatom_by_name ("TARGETS"))
    meta_xwayland_selection_get_x11_targets (compositor, selection);
  else
    meta_xwayland_selection_get_x11_data (compositor, selection);

  return TRUE;
}

static void
meta_xwayland_selection_send_targets (MetaWaylandCompositor       *compositor,
                                      const MetaWaylandDataSource *data_source,
                                      Window                       requestor,
                                      Atom                         property)
{
  Atom *targets;
  gchar **p;
  int i = 0;
  struct wl_array *source_mime_types;

  if (!data_source)
    return;

  source_mime_types = meta_wayland_data_source_get_mime_types (data_source);
  if (source_mime_types->size == 0)
    return;

  /* Make extra room for TIMESTAMP/TARGETS */
  targets = g_new (Atom, source_mime_types->size + 2);

  wl_array_for_each (p, source_mime_types)
    {
      targets[i++] = gdk_x11_get_xatom_by_name (*p);
    }

  targets[i++] = gdk_x11_get_xatom_by_name ("TIMESTAMP");
  targets[i++] = gdk_x11_get_xatom_by_name ("TARGETS");

  XChangeProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
                   requestor, property,
                   XA_ATOM, 32, PropModeReplace,
                   (guchar *) targets, i);

  g_free (targets);
}

static void
meta_xwayland_selection_send_timestamp (MetaWaylandCompositor *compositor,
                                        Window                 requestor,
                                        Atom                   property,
                                        Time                   timestamp)
{
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());

  XChangeProperty (xdisplay, requestor, property,
                   XA_INTEGER, 32,
                   PropModeReplace,
                   (guchar *) &timestamp, 1);
}

static void
meta_xwayland_selection_send_incr_chunk (MetaWaylandCompositor *compositor,
                                         MetaSelectionBridge   *selection)
{
  if (!selection->wayland_selection)
    return;

  if (selection->wayland_selection->buffer_len > 0)
    wayland_selection_update_x11_property (selection->wayland_selection);
  else
    wayland_selection_data_read (selection);
}

static gboolean
handle_incr_chunk (MetaWaylandCompositor *compositor,
                   MetaSelectionBridge   *selection,
                   XPropertyEvent        *event)
{
  if (selection->x11_selection &&
      selection->x11_selection->incr &&
      event->window == selection->owner &&
      event->state == PropertyNewValue &&
      event->atom == gdk_x11_get_xatom_by_name ("_META_SELECTION"))
    {
      /* X11 to Wayland */
      meta_xwayland_selection_get_incr_chunk (compositor, selection);
      return TRUE;
    }
  else if (selection->wayland_selection &&
           selection->wayland_selection->incr &&
           event->window == selection->window &&
           event->state == PropertyDelete &&
           event->atom == selection->wayland_selection->request_event.property)
    {
      /* Wayland to X11 */
      meta_xwayland_selection_send_incr_chunk (compositor, selection);
      return TRUE;
    }

  return FALSE;
}

static gboolean
meta_xwayland_selection_handle_property_notify (MetaWaylandCompositor *compositor,
                                                XEvent                *xevent)
{
  MetaXWaylandSelection *selection_data = compositor->xwayland_manager.selection_data;
  XPropertyEvent *event = (XPropertyEvent *) xevent;

  return handle_incr_chunk (compositor, &selection_data->clipboard, event);
}

static gboolean
meta_xwayland_selection_handle_selection_request (MetaWaylandCompositor *compositor,
                                                  XEvent                *xevent)
{
  XSelectionRequestEvent *event = (XSelectionRequestEvent *) xevent;
  MetaWaylandDataSource *data_source;
  MetaSelectionBridge *selection;

  selection = atom_to_selection_bridge (compositor, event->selection);

  if (!selection)
    return FALSE;

  /* We must fetch from the currently active source, not the Xwayland one */
  data_source = data_device_get_active_source_for_atom (&compositor->seat->data_device,
                                                        selection->selection_atom);
  if (!data_source)
    return FALSE;

  g_clear_pointer (&selection->wayland_selection,
                   (GDestroyNotify) wayland_selection_data_free);

  if (event->target == gdk_x11_get_xatom_by_name ("TARGETS"))
    {
      meta_xwayland_selection_send_targets (compositor,
                                            data_source,
                                            event->requestor,
                                            event->property);
      reply_selection_request (event, TRUE);
    }
  else if (event->target == gdk_x11_get_xatom_by_name ("TIMESTAMP"))
    {
      meta_xwayland_selection_send_timestamp (compositor,
                                              event->requestor, event->property,
                                              selection->timestamp);
      reply_selection_request (event, TRUE);
    }
  else if (data_source && event->target == gdk_x11_get_xatom_by_name ("DELETE"))
    {
      reply_selection_request (event, TRUE);
    }
  else
    {
      if (data_source &&
          meta_wayland_data_source_has_mime_type (data_source,
                                                  gdk_x11_get_xatom_name (event->target)))
        {
          selection->wayland_selection = wayland_selection_data_new (event,
                                                                     compositor);

          if (selection->wayland_selection)
            wayland_selection_data_read (selection);
        }

      if (!selection->wayland_selection)
        reply_selection_request (event, FALSE);
    }

  return TRUE;
}

static MetaWaylandSurface *
pick_drop_surface (MetaWaylandCompositor *compositor,
                   const ClutterEvent    *event)
{
  MetaDisplay *display = meta_get_display ();
  MetaWindow *focus_window = NULL;
  ClutterPoint pos;

  clutter_event_get_coords (event, &pos.x, &pos.y);
  focus_window = meta_stack_get_default_focus_window_at_point (display->screen->stack,
                                                               NULL, NULL,
                                                               pos.x, pos.y);
  return focus_window ? focus_window->surface : NULL;
}

static void
repick_drop_surface (MetaWaylandCompositor *compositor,
                     MetaWaylandDragGrab   *drag_grab,
                     const ClutterEvent    *event)
{
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  MetaDndBridge *dnd = &compositor->xwayland_manager.selection_data->dnd;
  MetaWaylandSurface *focus = NULL;

  focus = pick_drop_surface (compositor, event);
  dnd->focus_surface = focus;

  if (meta_wayland_drag_grab_get_focus (drag_grab) == focus)
    return;

  if (focus &&
      focus->window->client_type == META_WINDOW_CLIENT_TYPE_WAYLAND)
    {
      XMapWindow (xdisplay, dnd->dnd_window);
      XMoveResizeWindow (xdisplay, dnd->dnd_window,
                         focus->window->rect.x,
                         focus->window->rect.y,
                         focus->window->rect.width,
                         focus->window->rect.height);
    }
  else
    {
      XMoveResizeWindow (xdisplay, dnd->dnd_window, -1, -1, 1, 1);
      XUnmapWindow (xdisplay, dnd->dnd_window);
    }
}

static void
drag_xgrab_focus (MetaWaylandPointerGrab *grab,
                  MetaWaylandSurface     *surface)
{
  /* Do not update the focus here. First, the surface may perfectly
   * be the X11 source DnD icon window's, so we can only be fooled
   * here. Second, delaying focus handling to XdndEnter/Leave
   * makes us do the negotiation orderly on the X11 side.
   */
}

static void
drag_xgrab_motion (MetaWaylandPointerGrab *grab,
                   const ClutterEvent     *event)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
  MetaDndBridge *dnd = &compositor->xwayland_manager.selection_data->dnd;
  MetaWaylandSeat *seat = compositor->seat;

  repick_drop_surface (compositor,
                       (MetaWaylandDragGrab *) grab,
                       event);

  dnd->last_motion_time = clutter_event_get_time (event);
  meta_wayland_pointer_send_motion (&seat->pointer, event);
}

static void
drag_xgrab_button (MetaWaylandPointerGrab *grab,
                   const ClutterEvent     *event)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
  MetaWaylandSeat *seat = compositor->seat;

  meta_wayland_pointer_send_button (&seat->pointer, event);
}

static const MetaWaylandPointerGrabInterface drag_xgrab_interface = {
  drag_xgrab_focus,
  drag_xgrab_motion,
  drag_xgrab_button,
};

static gboolean
meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor,
                                               XEvent                *xevent)
{
  XClientMessageEvent *event = (XClientMessageEvent *) xevent;
  MetaDndBridge *dnd = &compositor->xwayland_manager.selection_data->dnd;
  MetaWaylandSeat *seat = compositor->seat;

  /* Source side messages */
  if (event->window == dnd->selection.window)
    {
      MetaWaylandDataSource *data_source;
      uint32_t action = 0;

      data_source = compositor->seat->data_device.dnd_data_source;

      if (!data_source)
        return FALSE;

      if (event->message_type == xdnd_atoms[ATOM_DND_STATUS])
        {
          /* The first bit in data.l[1] is set if the drag was accepted */
          meta_wayland_data_source_set_has_target (data_source,
                                                   (event->data.l[1] & 1) != 0);

          /* data.l[4] contains the action atom */
          if (event->data.l[4])
            action = atom_to_action ((Atom) event->data.l[4]);

          meta_wayland_data_source_set_current_action (data_source, action);
          return TRUE;
        }
      else if (event->message_type == xdnd_atoms[ATOM_DND_FINISHED])
        {
          /* Reject messages mid-grab */
          if (compositor->seat->data_device.current_grab)
            return FALSE;

          meta_wayland_data_source_notify_finish (data_source);
          return TRUE;
        }
    }
  /* Dest side messages */
  else if (dnd->selection.source &&
           compositor->seat->data_device.current_grab &&
           (Window) event->data.l[0] == dnd->selection.owner)
    {
      MetaWaylandDragGrab *drag_grab = compositor->seat->data_device.current_grab;
      MetaWaylandSurface *drag_focus = meta_wayland_drag_grab_get_focus (drag_grab);

      if (!drag_focus &&
          event->message_type != xdnd_atoms[ATOM_DND_ENTER])
        return FALSE;

      if (event->message_type == xdnd_atoms[ATOM_DND_ENTER])
        {
          /* Bit 1 in data.l[1] determines whether there's 3 or less mimetype
           * atoms (and are thus contained in this same message), or whether
           * there's more than 3 and we need to check the XdndTypeList property
           * for the full list.
           */
          if (!(event->data.l[1] & 1))
            {
              /* Mimetypes are contained in this message */
              const gchar *mimetype;
              gint i;
              struct wl_array *source_mime_types;

              /* We only need to fetch once */
              source_mime_types =
                meta_wayland_data_source_get_mime_types (dnd->selection.source);
              if (source_mime_types->size == 0)
                {
                  for (i = 2; i <= 4; i++)
                    {
                      if (event->data.l[i] == None)
                        break;

                      mimetype = gdk_x11_get_xatom_name (event->data.l[i]);
                      meta_wayland_data_source_add_mime_type (dnd->selection.source,
                                                              mimetype);
                    }
                }
            }
          else
            {
              /* Fetch mimetypes from type list */
              meta_xwayland_data_source_fetch_mimetype_list (dnd->selection.source,
                                                             event->data.l[0],
                                                             xdnd_atoms[ATOM_DND_TYPE_LIST]);
            }

          meta_wayland_drag_grab_set_focus (drag_grab, dnd->focus_surface);
          return TRUE;
        }
      else if (event->message_type == xdnd_atoms[ATOM_DND_POSITION])
        {
          ClutterEvent *motion;
          ClutterPoint pos;
          uint32_t action = 0;

          motion = clutter_event_new (CLUTTER_MOTION);
          clutter_input_device_get_coords (seat->pointer.device, NULL, &pos);
          clutter_event_set_coords (motion, pos.x, pos.y);
          clutter_event_set_device (motion, seat->pointer.device);
          clutter_event_set_source_device (motion, seat->pointer.device);
          clutter_event_set_time (motion, dnd->last_motion_time);

          action = atom_to_action ((Atom) event->data.l[4]);
          meta_wayland_data_source_set_actions (dnd->selection.source, action);

          meta_wayland_surface_drag_dest_motion (drag_focus, motion);
          xdnd_send_status (compositor->xwayland_manager.selection_data,
                            (Window) event->data.l[0],
                            meta_wayland_data_source_get_current_action (dnd->selection.source));

          clutter_event_free (motion);
          return TRUE;
        }
      else if (event->message_type == xdnd_atoms[ATOM_DND_LEAVE])
        {
          meta_wayland_drag_grab_set_focus (drag_grab, NULL);
          return TRUE;
        }
      else if (event->message_type == xdnd_atoms[ATOM_DND_DROP])
        {
          meta_wayland_surface_drag_dest_drop (drag_focus);
          return TRUE;
        }
    }

  return FALSE;
}

static gboolean
meta_xwayland_selection_handle_xfixes_selection_notify (MetaWaylandCompositor *compositor,
                                                        XEvent                *xevent)
{
  XFixesSelectionNotifyEvent *event = (XFixesSelectionNotifyEvent *) xevent;
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  MetaSelectionBridge *selection;

  selection = atom_to_selection_bridge (compositor, event->selection);

  if (!selection)
    return FALSE;

  if (selection->selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD"))
    {
      if (event->owner == None)
        {
          if (selection->source && selection->owner != selection->window)
            {
              /* An X client went away, clear the selection */
              g_clear_object (&selection->source);
            }

          selection->owner = None;
        }
      else
        {
          selection->owner = event->owner;

          if (selection->owner == selection->window)
            {
              /* This our own selection window */
              selection->timestamp = event->timestamp;
              return TRUE;
            }

          g_clear_pointer (&selection->x11_selection,
                           (GDestroyNotify) x11_selection_data_free);

          XConvertSelection (xdisplay,
                             event->selection,
                             gdk_x11_get_xatom_by_name ("TARGETS"),
                             gdk_x11_get_xatom_by_name ("_META_SELECTION"),
                             selection->window,
                             selection->timestamp);
          XFlush (xdisplay);
        }
    }
  else if (selection->selection_atom == xdnd_atoms[ATOM_DND_SELECTION])
    {
      MetaWaylandDataDevice *data_device = &compositor->seat->data_device;
      MetaXWaylandSelection *selection_data = compositor->xwayland_manager.selection_data;

      selection->owner = event->owner;

      if (event->owner != None && event->owner != selection->window)
        {
          MetaWaylandSurface *focus;

          focus = compositor->seat->pointer.focus_surface;
          selection->source = meta_wayland_data_source_xwayland_new (selection);
          meta_wayland_data_device_set_dnd_source (&compositor->seat->data_device,
                                                   selection->source);

          meta_wayland_data_device_start_drag (data_device,
                                               wl_resource_get_client (focus->resource),
                                               &drag_xgrab_interface,
                                               focus, selection->source,
                                               NULL);
        }
      else if (event->owner == None)
        {
          meta_wayland_data_device_end_drag (data_device);
          XUnmapWindow (xdisplay, selection_data->dnd.dnd_window);
        }
    }

  return TRUE;
}

gboolean
meta_xwayland_selection_handle_event (XEvent *xevent)
{
  MetaWaylandCompositor *compositor;

  compositor = meta_wayland_compositor_get_default ();

  if (!compositor->xwayland_manager.selection_data)
    return FALSE;

  switch (xevent->type)
    {
    case SelectionNotify:
      return meta_xwayland_selection_handle_selection_notify (compositor, xevent);
    case PropertyNotify:
      return meta_xwayland_selection_handle_property_notify (compositor, xevent);
    case SelectionRequest:
      return meta_xwayland_selection_handle_selection_request (compositor, xevent);
    case ClientMessage:
      return meta_xwayland_selection_handle_client_message (compositor, xevent);
    default:
      {
        MetaDisplay *display = meta_get_display ();

        if (xevent->type - display->xfixes_event_base == XFixesSelectionNotify)
          return meta_xwayland_selection_handle_xfixes_selection_notify (compositor, xevent);

        return FALSE;
      }
    }
}

static void
meta_selection_bridge_ownership_notify (struct wl_listener *listener,
                                        void               *data)
{
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  MetaSelectionBridge *selection =
    wl_container_of (listener, selection, ownership_listener);
  MetaWaylandDataSource *owner = data;

  if (!owner && selection->window == selection->owner)
    {
      XSetSelectionOwner (xdisplay, selection->selection_atom,
                          None, selection->timestamp);
    }
  else if (owner && selection->source != owner)
    {
      XSetSelectionOwner (xdisplay,
                          selection->selection_atom,
                          selection->window,
                          CurrentTime);
    }
}

static void
init_selection_bridge (MetaSelectionBridge *selection,
                       Atom                 selection_atom,
                       struct wl_signal    *signal)
{
  Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
  XSetWindowAttributes attributes;
  guint mask;

  attributes.event_mask = PropertyChangeMask;

  selection->ownership_listener.notify = meta_selection_bridge_ownership_notify;
  wl_signal_add (signal, &selection->ownership_listener);

  selection->selection_atom = selection_atom;
  selection->window =
    XCreateWindow (xdisplay,
                   gdk_x11_window_get_xid (gdk_get_default_root_window ()),
                   -1, -1, 1, 1, /* position */
                   0, /* border width */
                   0, /* depth */
                   InputOnly, /* class */
                   CopyFromParent, /* visual */
                   CWEventMask,
                   &attributes);

  mask = XFixesSetSelectionOwnerNotifyMask |
    XFixesSelectionWindowDestroyNotifyMask |
    XFixesSelectionClientCloseNotifyMask;

  XFixesSelectSelectionInput (xdisplay, selection->window,
                              selection_atom, mask);
}

static void
shutdown_selection_bridge (MetaSelectionBridge *selection)
{
  wl_list_remove (&selection->ownership_listener.link);

  XDestroyWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
                  selection->window);
  g_clear_pointer (&selection->wayland_selection,
                   (GDestroyNotify) wayland_selection_data_free);
  g_clear_pointer (&selection->x11_selection,
                   (GDestroyNotify) x11_selection_data_free);
}

void
meta_xwayland_init_selection (void)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
  MetaXWaylandManager *manager = &compositor->xwayland_manager;

  g_assert (manager->selection_data == NULL);

  manager->selection_data = g_slice_new0 (MetaXWaylandSelection);

  meta_xwayland_init_dnd (manager);
  init_selection_bridge (&manager->selection_data->clipboard,
                         gdk_x11_get_xatom_by_name ("CLIPBOARD"),
                         &compositor->seat->data_device.selection_ownership_signal);
  init_selection_bridge (&manager->selection_data->dnd.selection,
                         xdnd_atoms[ATOM_DND_SELECTION],
                         &compositor->seat->data_device.dnd_ownership_signal);
}

void
meta_xwayland_shutdown_selection (void)
{
  MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
  MetaXWaylandManager *manager = &compositor->xwayland_manager;
  MetaXWaylandSelection *selection = manager->selection_data;

  g_assert (selection != NULL);

  g_clear_object (&selection->clipboard.source);

  meta_xwayland_shutdown_dnd (manager);
  shutdown_selection_bridge (&selection->clipboard);
  shutdown_selection_bridge (&selection->dnd.selection);

  g_slice_free (MetaXWaylandSelection, selection);
  manager->selection_data = NULL;
}
