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

#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <glib-unix.h>
#include <gio/gunixoutputstream.h>
#include <gio/gunixinputstream.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xfixes.h>

#include "core/meta-workspace-manager-private.h"
#include "core/workspace-private.h"
#include "meta/meta-x11-errors.h"
#include "wayland/meta-wayland-data-device.h"
#include "wayland/meta-xwayland-private.h"
#include "wayland/meta-xwayland-dnd-private.h"
#include "wayland/meta-xwayland.h"
#include "x11/meta-x11-display-private.h"

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

struct _MetaWaylandDataSourceXWayland
{
  MetaWaylandDataSource parent;
  MetaXWaylandDnd *dnd;
  gboolean has_utf8_string_atom;
};

struct _MetaXWaylandDnd
{
  MetaXWaylandManager *manager;

  Window owner;
  Time client_message_timestamp;
  MetaWaylandDataSource *source; /* owned by MetaWaylandDataDevice */
  MetaWaylandSurface *focus_surface;
  Window dnd_window[2]; /* Mutter-internal windows, act as peer on wayland drop sites */
  Window dnd_dest; /* X11 drag dest window */
  guint32 last_motion_time;
  int current_dnd_window;
};

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);

static MetaDisplay *
display_from_compositor (MetaWaylandCompositor *compositor)
{
  MetaContext *context = meta_wayland_compositor_get_context (compositor);

  return meta_context_get_display (context);
}

static MetaX11Display *
x11_display_from_dnd (MetaXWaylandDnd *dnd)
{
  MetaWaylandCompositor *compositor = dnd->manager->compositor;
  MetaContext *context = meta_wayland_compositor_get_context (compositor);
  MetaDisplay *display = meta_context_get_display (context);

  return meta_display_get_x11_display (display);
}

/* 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 Window
current_dnd_window (MetaXWaylandDnd *dnd)
{
  return dnd->dnd_window[dnd->current_dnd_window];
}

static Window
next_dnd_window (MetaXWaylandDnd *dnd)
{
  dnd->current_dnd_window =
    (dnd->current_dnd_window + 1) % G_N_ELEMENTS (dnd->dnd_window);

  return current_dnd_window (dnd);
}

static void
create_dnd_windows (MetaXWaylandDnd *dnd,
                    MetaX11Display  *x11_display)
{
  Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
  XSetWindowAttributes attributes;
  guint32 version = XDND_VERSION;
  int i;

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

  for (i = 0; i < G_N_ELEMENTS (dnd->dnd_window); i++)
    {
      dnd->dnd_window[i] =
        XCreateWindow (xdisplay,
                       meta_x11_display_get_xroot (x11_display),
                       -1, -1, 1, 1,
                       0, /* border width */
                       0, /* depth */
                       InputOnly, /* class */
                       CopyFromParent, /* visual */
                       CWEventMask | CWOverrideRedirect,
                       &attributes);

      XChangeProperty (xdisplay, dnd->dnd_window[i],
                       xdnd_atoms[ATOM_DND_AWARE],
                       XA_ATOM, 32, PropModeReplace,
                       (guchar*) &version, 1);
    }
}

static void
destroy_dnd_windows (MetaXWaylandDnd *dnd,
                     MetaX11Display  *x11_display)
{
  Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
  int i;

  for (i = 0; i < G_N_ELEMENTS (dnd->dnd_window); i++)
    {
      XDestroyWindow (xdisplay, dnd->dnd_window[i]);
      dnd->dnd_window[i] = None;
    }
}

static void
hide_dnd_window (MetaXWaylandDnd *dnd,
                 MetaX11Display  *x11_display,
                 int              index)
{
  Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);

  g_assert (index < G_N_ELEMENTS (dnd->dnd_window));

  XMoveResizeWindow (xdisplay, dnd->dnd_window[index], -1, -1, 1, 1);
  XUnmapWindow (xdisplay, dnd->dnd_window[index]);
}

static void
hide_all_dnd_windows (MetaXWaylandDnd *dnd,
                      MetaX11Display  *x11_display)
{
  int i;

  for (i = 0; i < G_N_ELEMENTS (dnd->dnd_window); i++)
    hide_dnd_window (dnd, x11_display, i);
}

static void
xdnd_send_enter (MetaXWaylandDnd *dnd,
                 Window           dest)
{
  MetaWaylandCompositor *compositor = dnd->manager->compositor;
  MetaX11Display *x11_display = x11_display_from_dnd (dnd);
  Display *xdisplay = x11_display->xdisplay;
  MetaWaylandDataSource *data_source;
  XEvent xev = { 0 };
  gchar **p;
  struct wl_array *source_mime_types;

  meta_x11_error_trap_push (x11_display);

  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] = x11_display->selection.xwindow;
  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++] = XInternAtom (xdisplay, *p, False);
        }
    }
  else
    {
      /* We have more than 3 mimetypes, we must set up
       * the mimetype list as a XdndTypeList property.
       */
      g_autofree Atom *atomlist = NULL;
      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++] = XInternAtom (xdisplay, *p, False);
        }

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

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

  if (meta_x11_error_trap_pop_with_return (x11_display) != Success)
    g_critical ("Error sending XdndEnter");
}

static void
xdnd_send_leave (MetaXWaylandDnd *dnd,
                 Window           dest)
{
  MetaX11Display *x11_display = x11_display_from_dnd (dnd);
  Display *xdisplay = x11_display->xdisplay;
  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] = x11_display->selection.xwindow;

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

static void
xdnd_send_position (MetaXWaylandDnd *dnd,
                    Window           dest,
                    uint32_t         time,
                    int              x,
                    int              y)
{
  MetaWaylandCompositor *compositor = dnd->manager->compositor;
  MetaWaylandDataSource *source = compositor->seat->data_device.dnd_data_source;
  MetaX11Display *x11_display = x11_display_from_dnd (dnd);
  Display *xdisplay = x11_display->xdisplay;
  uint32_t action = 0, user_action, actions;
  XEvent xev = { 0 };

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

  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] = x11_display->selection.xwindow;
  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);

  meta_x11_error_trap_push (x11_display);
  XSendEvent (xdisplay, dest, False, NoEventMask, &xev);

  if (meta_x11_error_trap_pop_with_return (x11_display) != Success)
    g_critical ("Error sending XdndPosition");
}

static void
xdnd_send_drop (MetaXWaylandDnd *dnd,
                Window           dest,
                uint32_t         time)
{
  MetaX11Display *x11_display = x11_display_from_dnd (dnd);
  Display *xdisplay = x11_display->xdisplay;
  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] = x11_display->selection.xwindow;
  xev.xclient.data.l[2] = time;

  meta_x11_error_trap_push (x11_display);
  XSendEvent (xdisplay, dest, False, NoEventMask, &xev);

  if (meta_x11_error_trap_pop_with_return (x11_display) != Success)
    g_critical ("Error sending XdndDrop");
}

static void
xdnd_send_finished (MetaXWaylandDnd *dnd,
                    Window           dest,
                    gboolean         accepted)
{
  MetaX11Display *x11_display = x11_display_from_dnd (dnd);
  Display *xdisplay = x11_display->xdisplay;
  MetaWaylandDataSource *source = dnd->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] = current_dnd_window (dnd);

  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);
    }

  meta_x11_error_trap_push (x11_display);
  XSendEvent (xdisplay, dest, False, NoEventMask, &xev);

  if (meta_x11_error_trap_pop_with_return (x11_display) != Success)
    g_critical ("Error sending XdndFinished");
}

static void
xdnd_send_status (MetaXWaylandDnd *dnd,
                  Window           dest,
                  uint32_t         action)
{
  MetaX11Display *x11_display = x11_display_from_dnd (dnd);
  Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
  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] = current_dnd_window (dnd);
  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 */

  meta_x11_error_trap_push (x11_display);
  XSendEvent (xdisplay, dest, False, NoEventMask, &xev);

  if (meta_x11_error_trap_pop_with_return (x11_display) != Success)
    g_critical ("Error sending Xdndstatus");
}

static void
meta_xwayland_end_dnd_grab (MetaWaylandDataDevice *data_device,
                            gboolean               success)
{
  MetaWaylandSeat *seat = meta_wayland_data_device_get_seat (data_device);
  MetaWaylandCompositor *compositor = meta_wayland_seat_get_compositor (seat);
  MetaXWaylandManager *manager = &compositor->xwayland_manager;
  MetaWaylandDragGrab *drag_grab = compositor->seat->data_device.current_grab;
  MetaXWaylandDnd *dnd = manager->dnd;
  MetaX11Display *x11_display = x11_display_from_dnd (dnd);

  if (drag_grab)
    {
      if (!success && dnd->source)
        meta_wayland_data_source_set_current_offer (dnd->source, NULL);

      meta_wayland_data_device_end_drag (data_device);
    }

  hide_all_dnd_windows (dnd, x11_display);
}

static void
transfer_cb (MetaSelection *selection,
             GAsyncResult  *res,
             GOutputStream *stream)
{
  GError *error = NULL;

  if (!meta_selection_transfer_finish (selection, res, &error))
    {
      g_warning ("Could not transfer DnD selection: %s", error->message);
      g_error_free (error);
    }

  g_output_stream_close (stream, NULL, NULL);
  g_object_unref (stream);
}

static void
meta_x11_source_send (MetaWaylandDataSource *source,
                      const gchar           *mime_type,
                      gint                   fd)
{
  MetaWaylandCompositor *compositor =
    meta_wayland_data_source_get_compositor (source);
  MetaContext *context = meta_wayland_compositor_get_context (compositor);
  MetaDisplay *display = meta_context_get_display (context);
  GOutputStream *stream;

  stream = g_unix_output_stream_new (fd, TRUE);
  meta_selection_transfer_async (meta_display_get_selection (display),
                                 META_SELECTION_DND,
                                 mime_type,
                                 -1,
                                 stream,
                                 NULL,
                                 (GAsyncReadyCallback) transfer_cb,
                                 stream);
}

static void
meta_x11_source_target (MetaWaylandDataSource *source,
                        const gchar           *mime_type)
{
  MetaWaylandDataSourceXWayland *source_xwayland =
    META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
  MetaXWaylandDnd *dnd = source_xwayland->dnd;
  uint32_t action = 0;

  if (mime_type)
    action = meta_wayland_data_source_get_current_action (source);

  xdnd_send_status (dnd, dnd->owner, action);
}

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

  xdnd_send_finished (dnd, dnd->owner, FALSE);
}

static void
meta_x11_source_action (MetaWaylandDataSource *source,
                        uint32_t               action)
{
  MetaWaylandDataSourceXWayland *source_xwayland =
    META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
  MetaXWaylandDnd *dnd = source_xwayland->dnd;

  if (!meta_wayland_data_source_has_target (source))
    action = 0;

  xdnd_send_status (dnd, dnd->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);
  MetaXWaylandDnd *dnd = source_xwayland->dnd;
  MetaX11Display *x11_display = x11_display_from_dnd (dnd);
  uint32_t action = meta_wayland_data_source_get_current_action (source);

  if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
    {
      Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);

      /* Request data deletion on the drag source */
      XConvertSelection (xdisplay,
                         xdnd_atoms[ATOM_DND_SELECTION],
                         XInternAtom (xdisplay, "DELETE", False),
                         XInternAtom (xdisplay, "_META_SELECTION", False),
                         x11_display->selection.xwindow,
                         META_CURRENT_TIME);
    }

  xdnd_send_finished (dnd, dnd->owner, 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 (MetaXWaylandDnd       *dnd,
                                       MetaWaylandCompositor *compositor)
{
  MetaWaylandDataSourceXWayland *source_xwayland;

  source_xwayland = g_object_new (META_TYPE_WAYLAND_DATA_SOURCE_XWAYLAND,
                                  "compositor", compositor,
                                  NULL);
  source_xwayland->dnd = dnd;

  return META_WAYLAND_DATA_SOURCE (source_xwayland);
}

static void
meta_x11_drag_dest_focus_in (MetaWaylandDataDevice *data_device,
                             MetaWaylandSurface    *surface,
                             MetaWaylandDataOffer  *offer)
{
  MetaWaylandSeat *seat = meta_wayland_data_device_get_seat (data_device);
  MetaWaylandCompositor *compositor = meta_wayland_seat_get_compositor (seat);
  MetaXWaylandDnd *dnd = compositor->xwayland_manager.dnd;

  dnd->dnd_dest = meta_wayland_surface_get_window (surface)->xwindow;
  xdnd_send_enter (dnd, dnd->dnd_dest);
}

static void
meta_x11_drag_dest_focus_out (MetaWaylandDataDevice *data_device,
                              MetaWaylandSurface    *surface)
{
  MetaWaylandSeat *seat = meta_wayland_data_device_get_seat (data_device);
  MetaWaylandCompositor *compositor = meta_wayland_seat_get_compositor (seat);
  MetaXWaylandDnd *dnd = compositor->xwayland_manager.dnd;

  xdnd_send_leave (dnd, dnd->dnd_dest);
  dnd->dnd_dest = None;
}

static void
meta_x11_drag_dest_motion (MetaWaylandDataDevice *data_device,
                           MetaWaylandSurface    *surface,
                           const ClutterEvent    *event)
{
  MetaWaylandSeat *seat = meta_wayland_data_device_get_seat (data_device);
  MetaWaylandCompositor *compositor = meta_wayland_seat_get_compositor (seat);
  MetaXWaylandDnd *dnd = compositor->xwayland_manager.dnd;
  guint32 time;
  gfloat x, y;

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

static void
meta_x11_drag_dest_drop (MetaWaylandDataDevice *data_device,
                         MetaWaylandSurface    *surface)
{
  MetaWaylandSeat *seat = meta_wayland_data_device_get_seat (data_device);
  MetaWaylandCompositor *compositor = meta_wayland_seat_get_compositor (seat);
  MetaXWaylandDnd *dnd = compositor->xwayland_manager.dnd;
  MetaDisplay *display = display_from_compositor (compositor);

  xdnd_send_drop (dnd, dnd->dnd_dest,
                  meta_display_get_current_time_roundtrip (display));
}

static void
meta_x11_drag_dest_update (MetaWaylandDataDevice *data_device,
                           MetaWaylandSurface    *surface)
{
  MetaWaylandSeat *seat = meta_wayland_data_device_get_seat (data_device);
  MetaWaylandCompositor *compositor = meta_wayland_seat_get_compositor (seat);
  MetaXWaylandDnd *dnd = compositor->xwayland_manager.dnd;
  graphene_point_t pos;

  clutter_seat_query_state (clutter_input_device_get_seat (seat->pointer->device),
                            seat->pointer->device, NULL, &pos, NULL);
  xdnd_send_position (dnd, 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);
  MetaXWaylandDnd *dnd = source_xwayland->dnd;
  MetaX11Display *x11_display = x11_display_from_dnd (dnd);
  Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
  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;

  meta_x11_error_trap_push (x11_display);

  utf8_string = XInternAtom (xdisplay, "UTF8_STRING", False);
  if (XGetWindowProperty (xdisplay, window, prop,
                          0, /* offset */
                          0x1fffffff, /* length */
                          False, /* delete */
                          AnyPropertyType,
                          &type_ret,
                          &format_ret,
                          &nitems_ret,
                          &bytes_after_ret,
                          (guchar **) &atoms) != Success)
    {
      meta_x11_error_trap_pop (x11_display);
      return FALSE;
    }

  if (meta_x11_error_trap_pop_with_return (x11_display) != Success)
    return FALSE;

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

  for (i = 0; i < nitems_ret; i++)
    {
      char *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 = XGetAtomName (xdisplay, atoms[i]);
      meta_wayland_data_source_add_mime_type (source, mime_type);
      XFree (mime_type);
    }

  XFree (atoms);

  return TRUE;
}

static MetaWaylandSurface *
pick_drop_surface (MetaWaylandCompositor *compositor,
                   const ClutterEvent    *event)
{
  MetaContext *context = meta_wayland_compositor_get_context (compositor);
  MetaDisplay *display = meta_context_get_display (context);
  MetaWorkspaceManager *workspace_manager = display->workspace_manager;
  MetaWorkspace *workspace = workspace_manager->active_workspace;
  MetaWindow *focus_window = NULL;
  graphene_point_t pos;

  clutter_event_get_coords (event, &pos.x, &pos.y);
  focus_window = meta_workspace_get_default_focus_window_at_point (workspace,
                                                                   NULL,
                                                                   pos.x, pos.y);
  return focus_window ? meta_window_get_wayland_surface (focus_window) : NULL;
}

static void
repick_drop_surface (MetaWaylandCompositor *compositor,
                     MetaWaylandDragGrab   *drag_grab,
                     const ClutterEvent    *event)
{
  MetaXWaylandDnd *dnd = compositor->xwayland_manager.dnd;
  MetaX11Display *x11_display = x11_display_from_dnd (dnd);
  Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
  MetaWaylandSurface *focus = NULL;
  MetaWindow *focus_window;

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

  dnd->focus_surface = focus;

  if (focus)
    focus_window = meta_wayland_surface_get_window (focus);
  else
    focus_window = NULL;

  if (focus_window &&
      focus_window->client_type == META_WINDOW_CLIENT_TYPE_WAYLAND)
    {
      Window dnd_window;

      hide_dnd_window (dnd, x11_display, dnd->current_dnd_window);
      dnd_window = next_dnd_window (dnd);

      XMapRaised (xdisplay, dnd_window);
      XMoveResizeWindow (xdisplay, dnd_window,
                         focus_window->rect.x,
                         focus_window->rect.y,
                         focus_window->rect.width,
                         focus_window->rect.height);
    }
  else
    {
      hide_all_dnd_windows (dnd, x11_display);
    }
}

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)
{
  MetaWaylandSeat *seat = meta_wayland_pointer_get_seat (grab->pointer);
  MetaWaylandCompositor *compositor = meta_wayland_seat_get_compositor (seat);
  MetaXWaylandDnd *dnd = compositor->xwayland_manager.dnd;

  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)
{
  MetaWaylandSeat *seat = meta_wayland_pointer_get_seat (grab->pointer);
  MetaWaylandCompositor *compositor = meta_wayland_seat_get_compositor (seat);
  MetaWaylandDataSource *data_source;

  meta_wayland_pointer_send_button (seat->pointer, event);
  data_source = compositor->seat->data_device.dnd_data_source;

  if (seat->pointer->button_count == 0 &&
      (!meta_wayland_drag_grab_get_focus ((MetaWaylandDragGrab *) grab) ||
       meta_wayland_data_source_get_current_action (data_source) ==
       WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE))
    meta_xwayland_end_dnd_grab (&seat->data_device, FALSE);
}

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

static gboolean
meta_xwayland_dnd_handle_client_message (MetaWaylandCompositor *compositor,
                                         XEvent                *xevent)
{
  XClientMessageEvent *event = (XClientMessageEvent *) xevent;
  MetaXWaylandDnd *dnd = compositor->xwayland_manager.dnd;
  MetaWaylandSeat *seat = compositor->seat;
  MetaX11Display *x11_display = x11_display_from_dnd (dnd);
  Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);

  /* Source side messages */
  if (event->window == x11_display->selection.xwindow)
    {
      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->source &&
           compositor->seat->data_device.current_grab &&
           (Window) event->data.l[0] == dnd->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 */
              char *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->source);
              if (source_mime_types->size == 0)
                {
                  for (i = 2; i <= 4; i++)
                    {
                      if (event->data.l[i] == None)
                        break;

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

          meta_wayland_data_source_set_actions (dnd->source,
                                                WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
                                                WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE |
                                                WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK);
          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;
          graphene_point_t pos;
          uint32_t action = 0;

          dnd->client_message_timestamp = event->data.l[3];

          motion = clutter_event_new (CLUTTER_MOTION);
          clutter_seat_query_state (clutter_input_device_get_seat (seat->pointer->device),
                                    seat->pointer->device, NULL, &pos, NULL);
          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_user_action (dnd->source, action);

          meta_wayland_surface_drag_dest_motion (drag_focus, motion);
          xdnd_send_status (dnd, (Window) event->data.l[0],
                            meta_wayland_data_source_get_current_action (dnd->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])
        {
          dnd->client_message_timestamp = event->data.l[2];
          meta_wayland_surface_drag_dest_drop (drag_focus);
          meta_xwayland_end_dnd_grab (&seat->data_device, TRUE);
          return TRUE;
        }
    }

  return FALSE;
}

static gboolean
meta_xwayland_dnd_handle_xfixes_selection_notify (MetaWaylandCompositor *compositor,
                                                  XEvent                *xevent)
{
  XFixesSelectionNotifyEvent *event = (XFixesSelectionNotifyEvent *) xevent;
  MetaXWaylandDnd *dnd = compositor->xwayland_manager.dnd;
  MetaWaylandDataDevice *data_device = &compositor->seat->data_device;
  MetaX11Display *x11_display = x11_display_from_dnd (dnd);
  MetaWaylandSurface *focus;

  if (event->selection != xdnd_atoms[ATOM_DND_SELECTION])
    return FALSE;

  dnd->owner = event->owner;
  focus = compositor->seat->pointer->focus_surface;

  if (event->owner != None && event->owner != x11_display->selection.xwindow &&
      focus && meta_xwayland_is_xwayland_surface (focus))
    {
      dnd->source = meta_wayland_data_source_xwayland_new (dnd, compositor);
      meta_wayland_data_device_set_dnd_source (&compositor->seat->data_device,
                                               dnd->source);

      meta_wayland_data_device_start_drag (data_device,
                                           wl_resource_get_client (focus->resource),
                                           &drag_xgrab_interface,
                                           focus, dnd->source,
                                           NULL);
    }
  else if (event->owner == None)
    {
      meta_xwayland_end_dnd_grab (data_device, FALSE);
      g_clear_object (&dnd->source);
    }

  return FALSE;
}

gboolean
meta_xwayland_dnd_handle_xevent (MetaXWaylandManager *manager,
                                 XEvent              *xevent)
{
  MetaWaylandCompositor *compositor = manager->compositor;

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

  switch (xevent->type)
    {
    case ClientMessage:
      return meta_xwayland_dnd_handle_client_message (compositor, xevent);
    default:
      {
        MetaDisplay *display = display_from_compositor (compositor);
        MetaX11Display *x11_display = meta_display_get_x11_display (display);

        if (xevent->type - x11_display->xfixes_event_base == XFixesSelectionNotify)
          return meta_xwayland_dnd_handle_xfixes_selection_notify (compositor, xevent);

        return FALSE;
      }
    }
}

void
meta_xwayland_init_dnd (MetaX11Display *x11_display)
{
  MetaDisplay *display = meta_x11_display_get_display (x11_display);
  Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
  MetaContext *context = meta_display_get_context (display);
  MetaWaylandCompositor *compositor =
    meta_context_get_wayland_compositor (context);
  MetaXWaylandManager *manager = &compositor->xwayland_manager;
  MetaXWaylandDnd *dnd = manager->dnd;
  guint32 i;

  g_assert (manager->dnd == NULL);

  manager->dnd = dnd = g_new0 (MetaXWaylandDnd, 1);

  for (i = 0; i < N_DND_ATOMS; i++)
    xdnd_atoms[i] = XInternAtom (xdisplay, atom_names[i], False);

  create_dnd_windows (dnd, x11_display);
  dnd->manager = manager;
  dnd->current_dnd_window = 0;
}

void
meta_xwayland_shutdown_dnd (MetaXWaylandManager *manager,
                            MetaX11Display      *x11_display)
{
  MetaXWaylandDnd *dnd = manager->dnd;

  g_assert (dnd != NULL);

  destroy_dnd_windows (dnd, x11_display);

  g_free (dnd);
  manager->dnd = NULL;
}
