/* GDK - The GIMP Drawing Kit
 * Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald
 * Copyright (C) 2001 Archaeopteryx Software Inc.
 * Copyright (C) 1998-2002 Tor Lillqvist
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
 */

#include "config.h"
#include <string.h>

#include <io.h>
#include <fcntl.h>

/* The mingw.org compiler does not export GUIDS in it's import library. To work
 * around that, define INITGUID to have the GUIDS declared. */
#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
#define INITGUID
#endif

#include "gdkdropprivate.h"

#include "gdkdrag.h"
#include "gdkprivate-win32.h"
#include "gdkwin32.h"
#include "gdkwin32dnd.h"
#include "gdkdisplayprivate.h"
#include "gdkwin32dnd-private.h"
#include "gdkdisplay-win32.h"
#include "gdkdeviceprivate.h"
#include "gdkhdataoutputstream-win32.h"

#include <ole2.h>

#include <shlobj.h>
#include <shlguid.h>
#include <objidl.h>
#include <glib/gi18n-lib.h>

#include <gdk/gdk.h>
#include <glib/gstdio.h>

#define GDK_TYPE_WIN32_DROP              (gdk_win32_drop_get_type ())
#define GDK_WIN32_DROP(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_WIN32_DROP, GdkWin32Drop))
#define GDK_WIN32_DROP_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_WIN32_DROP, GdkWin32DropClass))
#define GDK_IS_WIN32_DROP(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WIN32_DROP))
#define GDK_IS_WIN32_DROP_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_WIN32_DROP))
#define GDK_WIN32_DROP_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_WIN32_DROP, GdkWin32DropClass))

typedef struct _GdkWin32Drop GdkWin32Drop;
typedef struct _GdkWin32DropClass GdkWin32DropClass;

struct _GdkWin32Drop
{
  GdkDrop          drop;

  /* The drag protocol being in use */
  GdkDragProtocol  protocol;

  /* The actions supported at GTK level. Set in gdk_win32_drop_status(). */
  GdkDragAction    actions;

  guint            scale;          /* Temporarily caches the HiDPI scale */
  int              last_x;         /* Coordinates from last event, in GDK space */
  int              last_y;
  DWORD            last_key_state; /* Key state from last event */

  /* Just like GdkDrop->formats, but an array, and with format IDs
   * stored inside.
   */
  GArray          *droptarget_w32format_contentformat_map;

  /* The list from WM_DROPFILES is store here temporarily,
   * until the next gdk_win32_drop_read_async ()
   */
  char            *dropfiles_list;

  guint drop_finished : 1; /* FALSE until gdk_drop_finish() is called */
  guint drop_failed : 1;   /* Whether the drop was unsuccessful */
};

struct _GdkWin32DropClass
{
  GdkDropClass parent_class;
};

GType    gdk_win32_drop_get_type (void);

G_DEFINE_TYPE (GdkWin32Drop, gdk_win32_drop, GDK_TYPE_DROP)

/* This structure is presented to COM as an object that
 * implements IDropTarget interface. Every surface that
 * can be a droptarget has one of these.
 */
struct _drop_target_context
{
  IDropTarget                     idt;
  int                             ref_count;

  /* The drop object we create when a drag enters our surface.
   * The drop object is destroyed when the drag leaves.
   */
  GdkDrop                        *drop;
  /* We get this at the object at creation time and keep
   * it indefinitely. Drops (see above) come and go, but
   * this surface remains the same.
   * This is not a reference, as drop_target_context must not
   * outlive the surface it's attached to.
   * drop_target_context is not folded into GdkWin32Surface
   * only because it's easier to present it to COM as a separate
   * object when it's allocated separately.
   */
  GdkSurface                     *surface;
  /* This is given to us by the OS, we store it here
   * until the drag leaves our surface HWND. It is referenced
   * (using COM reference counting).
   */
  IDataObject                    *data_object;
};

static void
gdk_win32_drop_init (GdkWin32Drop *drop)
{
  GDK_NOTE (DND, g_print ("gdk_win32_drop_init %p\n", drop));
}

static void
gdk_win32_drop_finalize (GObject *object)
{
  GdkDrop *drop;
  GdkWin32Drop *drop_win32;

  GDK_NOTE (DND, g_print ("gdk_win32_drop_finalize %p\n", object));

  drop = GDK_DROP (object);

  drop_win32 = GDK_WIN32_DROP (drop);

  g_array_unref (drop_win32->droptarget_w32format_contentformat_map);

  G_OBJECT_CLASS (gdk_win32_drop_parent_class)->finalize (object);
}

static GdkDrop *
gdk_drop_new (GdkDisplay        *display,
              GdkDevice         *device,
              GdkDrag           *drag,
              GdkContentFormats *formats,
              GdkSurface        *surface,
              GdkDragProtocol    protocol)
{
  GdkWin32Drop *drop_win32;
  GdkWin32Display *display_win32 = GDK_WIN32_DISPLAY (display);

  drop_win32 = g_object_new (GDK_TYPE_WIN32_DROP,
                             "device", device,
                             "drag", drag,
                             "formats", formats,
                             "surface", surface,
                             NULL);

  drop_win32->scale = gdk_win32_display_get_monitor_scale_factor (display_win32, NULL, NULL);
  drop_win32->protocol = protocol;

  return GDK_DROP (drop_win32);
}


#define PRINT_GUID(guid) \
  g_print ("%.08lx-%.04x-%.04x-%.02x%.02x-%.02x%.02x%.02x%.02x%.02x%.02x", \
           ((gulong *)  guid)[0], \
           ((gushort *) guid)[2], \
           ((gushort *) guid)[3], \
           ((guchar *)  guid)[8], \
           ((guchar *)  guid)[9], \
           ((guchar *)  guid)[10], \
           ((guchar *)  guid)[11], \
           ((guchar *)  guid)[12], \
           ((guchar *)  guid)[13], \
           ((guchar *)  guid)[14], \
           ((guchar *)  guid)[15]);

static ULONG STDMETHODCALLTYPE
idroptarget_addref (LPDROPTARGET This)
{
  drop_target_context *ctx = (drop_target_context *) This;

  int ref_count = ++ctx->ref_count;

  GDK_NOTE (DND, g_print ("idroptarget_addref %p %d\n", This, ref_count));

  return ref_count;
}

static HRESULT STDMETHODCALLTYPE
idroptarget_queryinterface (LPDROPTARGET This,
                            REFIID       riid,
                            LPVOID      *ppvObject)
{
  GDK_NOTE (DND, {
      g_print ("idroptarget_queryinterface %p ", This);
      PRINT_GUID (riid);
    });

  *ppvObject = NULL;

  if (IsEqualGUID (riid, &IID_IUnknown))
    {
      GDK_NOTE (DND, g_print ("...IUnknown S_OK\n"));
      idroptarget_addref (This);
      *ppvObject = This;
      return S_OK;
    }
  else if (IsEqualGUID (riid, &IID_IDropTarget))
    {
      GDK_NOTE (DND, g_print ("...IDropTarget S_OK\n"));
      idroptarget_addref (This);
      *ppvObject = This;
      return S_OK;
    }
  else
    {
      GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n"));
      return E_NOINTERFACE;
    }
}

static ULONG STDMETHODCALLTYPE
idroptarget_release (LPDROPTARGET This)
{
  drop_target_context *ctx = (drop_target_context *) This;

  int ref_count = --ctx->ref_count;

  GDK_NOTE (DND, g_print ("idroptarget_release %p %d\n", This, ref_count));

  if (ref_count == 0)
    {
      g_clear_object (&ctx->drop);
      g_free (This);
    }

  return ref_count;
}

static GdkContentFormats *
query_object_formats (GdkDisplay   *display,
                      LPDATAOBJECT  pDataObj,
                      GArray       *w32format_contentformat_map)
{
  IEnumFORMATETC *pfmt = NULL;
  FORMATETC fmt;
  HRESULT hr;
  GdkContentFormatsBuilder *builder;
  GdkContentFormats *result_formats;

  hr = IDataObject_EnumFormatEtc (pDataObj, DATADIR_GET, &pfmt);
  if (FAILED (hr))
    return gdk_content_formats_new (NULL, 0);

  builder = gdk_content_formats_builder_new ();
  hr = IEnumFORMATETC_Next (pfmt, 1, &fmt, NULL);

  while (SUCCEEDED (hr) && hr != S_FALSE)
    {
      gboolean is_predef;
      char *registered_name = _gdk_win32_get_clipboard_format_name (fmt.cfFormat, &is_predef);

      if (registered_name && is_predef)
        GDK_NOTE (DND, g_print ("supported built-in source format 0x%x %s\n", fmt.cfFormat, registered_name));
      else if (registered_name)
        GDK_NOTE (DND, g_print ("supported source format 0x%x %s\n", fmt.cfFormat, registered_name));
      else
        GDK_NOTE (DND, g_print ("supported unnamed? source format 0x%x\n", fmt.cfFormat));

      g_free (registered_name);
      gdk_win32_clipdrop_add_win32_format_to_pairs (gdk_win32_display_get_clipdrop (display), fmt.cfFormat, w32format_contentformat_map, builder);
      hr = IEnumFORMATETC_Next (pfmt, 1, &fmt, NULL);
    }

  gdk_win32_com_clear (&pfmt);

  result_formats = gdk_content_formats_builder_free_to_formats (builder);

  return result_formats;
}

static void
set_data_object (LPDATAOBJECT *location, LPDATAOBJECT data_object)
{
  gdk_win32_com_clear (location);

  *location = data_object;

  if (*location != NULL)
    IDataObject_AddRef (*location);
}

/* Figures out an action that the user forces onto us by
 * pressing some modifier keys.
 */
static GdkDragAction
get_user_action (DWORD grfKeyState)
{
  /* Windows explorer does this:
   * 'C'ontrol for 'C'opy
   * a'L't (or Contro'L' + Shift) for 'L'ink
   * Shift for Move
   * Control + Alt or Shift + Alt or Control + Alt + Shift for Default action (see below).
   *
   * Default action is 'Copy' when dragging between drives, 'Move' otherwise.
   * For GTK 'between drives' turns into 'between applications'.
   */
  if (((grfKeyState & (MK_CONTROL | MK_ALT)) == (MK_CONTROL | MK_ALT)) ||
      ((grfKeyState & (MK_ALT | MK_SHIFT)) == (MK_ALT | MK_SHIFT)) ||
      ((grfKeyState & (MK_CONTROL | MK_ALT | MK_SHIFT)) == (MK_CONTROL | MK_ALT | MK_SHIFT)))
    {
      return 0;
    }
  else if ((grfKeyState & (MK_CONTROL | MK_SHIFT)) == (MK_CONTROL | MK_SHIFT))
    return GDK_ACTION_LINK;
  else if (grfKeyState & MK_CONTROL)
    return GDK_ACTION_COPY;
  else if (grfKeyState & MK_ALT)
    return GDK_ACTION_MOVE;

  return 0;
}

static DWORD
drop_effect_for_actions (GdkDragAction actions)
{
  DWORD effects = 0;
  int effect_count = 0;

  if (actions & GDK_ACTION_MOVE)
    {
      effects |= DROPEFFECT_MOVE;
      effect_count++;
    }
  if (actions & GDK_ACTION_LINK)
    {
      effects |= DROPEFFECT_LINK;
      effect_count++;
    }
  if (actions & GDK_ACTION_COPY)
    {
      effects |= DROPEFFECT_COPY;
      effect_count++;
    }

  if (effect_count == 0)
    effects = DROPEFFECT_NONE;
  else if (effect_count > 1)
    /* Actually it should be DROPEFECT_ASK, but Windows doesn't support that */
    effects = DROPEFFECT_COPY;

  return effects;
}

static GdkDragAction
actions_for_drop_effects (DWORD effects)
{
  GdkDragAction actions = 0;

  if (effects & DROPEFFECT_MOVE)
    actions |= GDK_ACTION_MOVE;
  if (effects & DROPEFFECT_LINK)
    actions |= GDK_ACTION_LINK;
  if (effects & DROPEFFECT_COPY)
    actions |= GDK_ACTION_COPY;

  return actions;
}

static GdkDragAction
filter_actions (GdkDragAction actions,
                GdkDragAction filter)
{
  return actions & filter;
}

static GdkDragAction
set_source_actions_helper (GdkDrop       *drop,
                           GdkDragAction  actions,
                           DWORD          grfKeyState)
{
  GdkDragAction user_action;
  GdkWin32Drop *drop_win32;

  user_action = get_user_action (grfKeyState);

  drop_win32 = GDK_WIN32_DROP (drop);
  drop_win32->actions = actions;

  if (user_action != 0)
    gdk_drop_set_actions (drop, user_action);
  else
    gdk_drop_set_actions (drop, actions);

  return actions;
}

/* Utility function to translate screen coordinates to surface-relative
 * coordinates. This routine only works with pixel values that aren't
 * scaled by any GDK DPI scale factor.
 */
static void
unscaled_screen_to_client (GdkSurface* surface,
                           double screen_x,
                           double screen_y,
                           double *client_x,
                           double *client_y)
{
  POINT client_origin;

  client_origin.x = 0;
  client_origin.y = 0;
  ClientToScreen (GDK_SURFACE_HWND (surface), &client_origin);
  *client_x = screen_x - client_origin.x;
  *client_y = screen_y - client_origin.y;
}

/* The pdwEffect here initially points
 * to a DWORD that contains the value of dwOKEffects argument in DoDragDrop,
 * i.e. the drag action that the drag source deems acceptable.
 * On return it should point to the effect value that denotes the
 * action that is going to happen on drop, and that is what DoDragDrop will
 * put into the DWORD that pdwEffect was pointing to.
 */
static HRESULT STDMETHODCALLTYPE
idroptarget_dragenter (LPDROPTARGET This,
                       LPDATAOBJECT pDataObj,
                       DWORD        grfKeyState,
                       POINTL       pt,
                       LPDWORD      pdwEffect_and_dwOKEffects)
{
  drop_target_context *ctx = (drop_target_context *) This;
  GdkDrop *drop;
  GdkWin32Drop *drop_win32;
  GdkDisplay *display;
  int pt_x;
  int pt_y;
  double x = 0.0;
  double y = 0.0;
  GdkDrag *drag;
  GdkDragAction source_actions;
  GdkDragAction dest_actions;
  GdkContentFormats *formats;
  GArray *droptarget_w32format_contentformat_map;

  GDK_NOTE (DND, g_print ("idroptarget_dragenter %p @ %ld : %ld"
                          " for dest surface 0x%p"
                          ". dwOKEffects = %lu\n",
                          This, pt.x, pt.y,
                          ctx->surface,
                          *pdwEffect_and_dwOKEffects));

  g_clear_object (&ctx->drop);

  /* Try to find the GdkDrag object for this DnD operation,
   * if it originated in our own application.
   */
  drag = NULL;

  if (ctx->surface)
    drag = gdk_win32_find_drag_for_dest_surface (ctx->surface);

  display = gdk_surface_get_display (ctx->surface);

  droptarget_w32format_contentformat_map = g_array_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair));
  formats = query_object_formats (display, pDataObj, droptarget_w32format_contentformat_map);
  drop = gdk_drop_new (display,
                       gdk_seat_get_pointer (gdk_display_get_default_seat (display)),
                       drag,
                       formats,
                       ctx->surface,
                       GDK_DRAG_PROTO_OLE2);
  drop_win32 = GDK_WIN32_DROP (drop);
  drop_win32->droptarget_w32format_contentformat_map = droptarget_w32format_contentformat_map;
  gdk_content_formats_unref (formats);

  ctx->drop = drop;

  source_actions = set_source_actions_helper (drop,
                                              actions_for_drop_effects (*pdwEffect_and_dwOKEffects),
                                              grfKeyState);

  set_data_object (&ctx->data_object, pDataObj);
  pt_x = pt.x / drop_win32->scale;
  pt_y = pt.y / drop_win32->scale;

  unscaled_screen_to_client (ctx->surface, pt.x, pt.y, &x, &y);
  x /= drop_win32->scale;
  y /= drop_win32->scale;

  gdk_drop_emit_enter_event (drop, TRUE, x, y, GDK_CURRENT_TIME);
  drop_win32->last_key_state = grfKeyState;
  drop_win32->last_x = pt_x;
  drop_win32->last_y = pt_y;
  dest_actions = filter_actions (gdk_drop_get_actions (drop), source_actions);
  *pdwEffect_and_dwOKEffects = drop_effect_for_actions (dest_actions);

  GDK_NOTE (DND, g_print ("idroptarget_dragenter returns S_OK with actions %s"
                          " and drop effect %lu\n",
                          _gdk_win32_drag_action_to_string (dest_actions),
                          *pdwEffect_and_dwOKEffects));

  return S_OK;
}

/* NOTE: This method is called continuously, even if nothing is
 * happening, as long as the drag operation is in progress and
 * the cursor is above our surface.
 * It is OK to return a "safe" dropeffect value (DROPEFFECT_NONE,
 * to indicate that the drop is not possible here), when we
 * do not yet have any real information about acceptability of
 * the drag, because we will have another opportunity to return
 * the "right" value (once we know what it is, after GTK processes
 * the events we emit) very soon.
 */
static HRESULT STDMETHODCALLTYPE
idroptarget_dragover (LPDROPTARGET This,
                      DWORD        grfKeyState,
                      POINTL       pt,
                      LPDWORD      pdwEffect_and_dwOKEffects)
{
  drop_target_context *ctx = (drop_target_context *) This;
  GdkWin32Drop *drop_win32 = GDK_WIN32_DROP (ctx->drop);
  int pt_x = pt.x / drop_win32->scale;
  int pt_y = pt.y / drop_win32->scale;
  GdkDragAction source_actions;
  GdkDragAction dest_actions;

  source_actions = actions_for_drop_effects (*pdwEffect_and_dwOKEffects);

  GDK_NOTE (DND, g_print ("idroptarget_dragover %p @ %d : %d"
                          " (raw %ld : %ld)"
                          ", dwOKEffects = %lu"
                          ", suggests %d action\n",
                          This, pt_x, pt_y,
                          pt.x, pt.y,
                          *pdwEffect_and_dwOKEffects,
                          source_actions));

  if (pt_x != drop_win32->last_x ||
      pt_y != drop_win32->last_y ||
      grfKeyState != drop_win32->last_key_state ||
      source_actions != drop_win32->actions)
    {
      double x = 0.0;
      double y = 0.0;

      unscaled_screen_to_client (ctx->surface, pt.x, pt.y, &x, &y);
      x /= drop_win32->scale;
      y /= drop_win32->scale;

      set_source_actions_helper (ctx->drop, source_actions, grfKeyState);

      gdk_drop_emit_motion_event (ctx->drop, TRUE, x, y, GDK_CURRENT_TIME);
      drop_win32->last_key_state = grfKeyState;
      drop_win32->last_x = pt_x;
      drop_win32->last_y = pt_y;
    }

  dest_actions = filter_actions (gdk_drop_get_actions (ctx->drop), source_actions);
  *pdwEffect_and_dwOKEffects = drop_effect_for_actions (dest_actions);

  GDK_NOTE (DND, g_print ("idroptarget_dragover returns S_OK with actions %s"
                          " and effect %lu\n",
                          _gdk_win32_drag_action_to_string (dest_actions),
                          *pdwEffect_and_dwOKEffects));

  return S_OK;
}

static HRESULT STDMETHODCALLTYPE
idroptarget_dragleave (LPDROPTARGET This)
{
  drop_target_context *ctx = (drop_target_context *) This;

  GDK_NOTE (DND, g_print ("idroptarget_dragleave %p S_OK\n", This));

  gdk_drop_emit_leave_event (GDK_DROP (ctx->drop), TRUE, GDK_CURRENT_TIME);

  g_clear_object (&ctx->drop);
  set_data_object (&ctx->data_object, NULL);

  return S_OK;
}

static HRESULT STDMETHODCALLTYPE
idroptarget_drop (LPDROPTARGET This,
                  LPDATAOBJECT pDataObj,
                  DWORD        grfKeyState,
                  POINTL       pt,
                  LPDWORD      pdwEffect_and_dwOKEffects)
{
  drop_target_context *ctx = (drop_target_context *) This;
  GdkWin32Drop *drop_win32 = GDK_WIN32_DROP (ctx->drop);
  double x = 0.0;
  double y = 0.0;
  GdkDragAction dest_action;

  GDK_NOTE (DND, g_print ("idroptarget_drop %p ", This));

  if (pDataObj == NULL)
    {
      GDK_NOTE (DND, g_print ("E_POINTER\n"));
      gdk_drop_emit_leave_event (ctx->drop, TRUE, GDK_CURRENT_TIME);
      g_clear_object (&ctx->drop);
      set_data_object (&ctx->data_object, NULL);

      return E_POINTER;
    }

  set_source_actions_helper (ctx->drop,
                             actions_for_drop_effects (*pdwEffect_and_dwOKEffects),
                             grfKeyState);

  drop_win32->drop_finished = FALSE;

  unscaled_screen_to_client (ctx->surface, pt.x, pt.y, &x, &y);
  x /= drop_win32->scale;
  y /= drop_win32->scale;

  gdk_drop_emit_motion_event (ctx->drop, TRUE, x, y, GDK_CURRENT_TIME);

  gdk_drop_emit_drop_event (ctx->drop, TRUE, x, y, GDK_CURRENT_TIME);

  gdk_drop_emit_leave_event (ctx->drop, TRUE, GDK_CURRENT_TIME);

  while (!drop_win32->drop_finished)
    g_main_context_iteration (NULL, FALSE);

  /* Notify OLE of the DnD result
   * Special case:
   * drop_win32->actions is guaranteed to contain 1 action after gdk_drop_finish ()
   */
  dest_action = drop_win32->actions;
  *pdwEffect_and_dwOKEffects = drop_effect_for_actions (dest_action);

  g_clear_object (&ctx->drop);
  set_data_object (&ctx->data_object, NULL);

  GDK_NOTE (DND, g_print ("drop S_OK with effect %lx\n", *pdwEffect_and_dwOKEffects));

  return S_OK;
}

static IDropTargetVtbl idt_vtbl = {
  idroptarget_queryinterface,
  idroptarget_addref,
  idroptarget_release,
  idroptarget_dragenter,
  idroptarget_dragover,
  idroptarget_dragleave,
  idroptarget_drop
};

static drop_target_context *
target_context_new (GdkSurface *surface)
{
  drop_target_context *result;

  result = g_new0 (drop_target_context, 1);
  result->idt.lpVtbl = &idt_vtbl;
  result->ref_count = 0;

  result->surface = surface;

  idroptarget_addref (&result->idt);

  GDK_NOTE (DND, g_print ("target_context_new: %p (surface %p)\n", result, result->surface));

  return result;
}

#if 0

/* From MS Knowledge Base article Q130698 */

static gboolean
resolve_link (HWND     hWnd,
              wchar_t *link,
              char   **lpszPath)
{
  WIN32_FILE_ATTRIBUTE_DATA wfad;
  HRESULT hr;
  IShellLinkW *pslW = NULL;
  IPersistFile *ppf = NULL;

  /* Check if the file is empty first because IShellLink::Resolve for
   * some reason succeeds with an empty file and returns an empty
   * "link target". (#524151)
   */
    if (!GetFileAttributesExW (link, GetFileExInfoStandard, &wfad) ||
        (wfad.nFileSizeHigh == 0 && wfad.nFileSizeLow == 0))
      return FALSE;

  /* Assume failure to start with: */
  *lpszPath = 0;

  /* Call CoCreateInstance to obtain the IShellLink interface
   * pointer. This call fails if CoInitialize is not called, so it is
   * assumed that CoInitialize has been called.
   */

  hr = CoCreateInstance (&CLSID_ShellLink,
                         NULL,
                         CLSCTX_INPROC_SERVER,
                         &IID_IShellLinkW,
                         (LPVOID *)&pslW);

  if (SUCCEEDED (hr))
   {
     /* The IShellLink interface supports the IPersistFile
      * interface. Get an interface pointer to it.
      */
     hr = pslW->lpVtbl->QueryInterface (pslW,
                                        &IID_IPersistFile,
                                        (LPVOID *) &ppf);
   }

  if (SUCCEEDED (hr))
    {
      /* Load the file. */
      hr = ppf->lpVtbl->Load (ppf, link, STGM_READ);
    }

  if (SUCCEEDED (hr))
    {
      /* Resolve the link by calling the Resolve()
       * interface function.
       */
      hr = pslW->lpVtbl->Resolve (pslW, hWnd, SLR_ANY_MATCH | SLR_NO_UI);
    }

  if (SUCCEEDED (hr))
    {
      wchar_t wtarget[MAX_PATH];

      hr = pslW->lpVtbl->GetPath (pslW, wtarget, MAX_PATH, NULL, 0);
      if (SUCCEEDED (hr))
        *lpszPath = g_utf16_to_utf8 (wtarget, -1, NULL, NULL, NULL);
    }

  if (ppf)
    ppf->lpVtbl->Release (ppf);

  if (pslW)
    pslW->lpVtbl->Release (pslW);

  return SUCCEEDED (hr);
}

/* Check for filenames like C:\Users\tml\AppData\Local\Temp\d5qtkvvs.bmp */
static gboolean
filename_looks_tempish (const char *filename)
{
  char *dirname;
  char *p;
  const char *q;
  gboolean retval = FALSE;

  dirname = g_path_get_dirname (filename);

  p = dirname;
  q = g_get_tmp_dir ();

  while (*p && *q &&
         ((G_IS_DIR_SEPARATOR (*p) && G_IS_DIR_SEPARATOR (*q)) ||
          g_ascii_tolower (*p) == g_ascii_tolower (*q)))
    p++, q++;

  if (!*p && !*q)
    retval = TRUE;

  g_free (dirname);

  return retval;
}

static gboolean
close_it (gpointer data)
{
  close (GPOINTER_TO_INT (data));

  return FALSE;
}

#endif

static void
gdk_win32_drop_status (GdkDrop       *drop,
                       GdkDragAction  actions,
                       GdkDragAction  preferred)
{
  GdkWin32Drop *drop_win32 = GDK_WIN32_DROP (drop);

  g_return_if_fail (drop != NULL);

  GDK_NOTE (DND, g_print ("gdk_win32_drop_status: %s\n"
                          " context=%p:{source_actions=%s, preferred=%s}\n",
                          _gdk_win32_drag_action_to_string (actions),
                          drop,
                          _gdk_win32_drag_action_to_string (gdk_drop_get_actions (drop)),
                          _gdk_win32_drag_action_to_string (preferred)));

  if (preferred != 0)
    actions = preferred;

  gdk_drop_set_actions (drop, drop_win32->actions & actions);
}

static void
gdk_win32_drop_finish (GdkDrop       *drop,
                       GdkDragAction  action)
{
  GdkWin32Drop *drop_win32 = GDK_WIN32_DROP (drop);

  g_return_if_fail (drop != NULL);

  GDK_NOTE (DND, g_print ("gdk_win32_drop_finish with action %s\n",
                          _gdk_win32_drag_action_to_string (action)));

  drop_win32->actions = action;
  drop_win32->drop_finished = TRUE;
}

#if 0

static GdkFilterReturn
gdk_destroy_filter (GdkXEvent *xev,
                    GdkEvent  *event,
                    gpointer   data)
{
  MSG *msg = (MSG *) xev;

  if (msg->message == WM_DESTROY)
    {
      IDropTarget *idtp = (IDropTarget *) data;

      GDK_NOTE (DND, g_print ("gdk_destroy_filter: WM_DESTROY: %p\n", msg->hwnd));
#if 0
      idtp->lpVtbl->Release (idtp);
#endif
      RevokeDragDrop (msg->hwnd);
      CoLockObjectExternal ((IUnknown*) idtp, FALSE, TRUE);
    }
  return GDK_FILTER_CONTINUE;
}

#endif

void
_gdk_win32_surface_register_dnd (GdkSurface *surface)
{
  drop_target_context *ctx;
  HRESULT hr;
  GdkWin32Surface *impl;

  g_return_if_fail (surface != NULL);

  if (g_object_get_data (G_OBJECT (surface), "gdk-dnd-registered") != NULL)
    return;
  else
    g_object_set_data (G_OBJECT (surface), "gdk-dnd-registered", GINT_TO_POINTER (TRUE));

  GDK_NOTE (DND, g_print ("gdk_win32_surface_register_dnd: %p\n", GDK_SURFACE_HWND (surface)));

  impl = GDK_WIN32_SURFACE (surface);

  /* Return if surface is already setup for DND. */
  if (impl->drop_target != NULL)
    return;

  ctx = target_context_new (surface);

  hr = CoLockObjectExternal ((IUnknown *) &ctx->idt, TRUE, FALSE);
  if (!SUCCEEDED (hr))
    OTHER_API_FAILED ("CoLockObjectExternal");
  else
    {
      hr = RegisterDragDrop (GDK_SURFACE_HWND (surface), &ctx->idt);
      if (hr == DRAGDROP_E_ALREADYREGISTERED)
        {
          g_print ("DRAGDROP_E_ALREADYREGISTERED\n");
          CoLockObjectExternal ((IUnknown *) &ctx->idt, FALSE, FALSE);
        }
      else if (!SUCCEEDED (hr))
        OTHER_API_FAILED ("RegisterDragDrop");
      else
        {
          impl->drop_target = ctx;
        }
    }
}

void
_gdk_win32_surface_unregister_dnd (GdkSurface *surface)
{
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);

  if (impl->drop_target)
    idroptarget_release (&impl->drop_target->idt);
}

static gpointer
grab_data_from_hdata (GTask  *task,
                      HANDLE  hdata,
                      gsize  *data_len)
{
  gpointer ptr;
  SIZE_T length;
  guchar *data;

  ptr = GlobalLock (hdata);
  if (ptr == NULL)
    {
      DWORD error_code = GetLastError ();
      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                               _("Cannot get DnD data. GlobalLock(0x%p) failed: 0x%lx."), hdata, error_code);
      return NULL;
    }

  length = GlobalSize (hdata);
  if (length == 0 && GetLastError () != NO_ERROR)
    {
      DWORD error_code = GetLastError ();
      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                               _("Cannot get DnD data. GlobalSize(0x%p) failed: 0x%lx."), hdata, error_code);
      GlobalUnlock (hdata);
      return NULL;
    }

  data = g_try_malloc (length);

  if (data == NULL)
    {
      char *length_str = g_strdup_printf ("%" G_GSIZE_FORMAT, length);
      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                               _("Cannot get DnD data. Failed to allocate %s bytes to store the data."), length_str);
      g_free (length_str);
      GlobalUnlock (hdata);
      return NULL;
    }

  memcpy (data, ptr, length);
  *data_len = length;

  GlobalUnlock (hdata);

  return data;
}

static void
gdk_win32_drop_read_async (GdkDrop             *drop,
                           GdkContentFormats   *formats,
                           int                  io_priority,
                           GCancellable        *cancellable,
                           GAsyncReadyCallback  callback,
                           gpointer             user_data)
{
  GdkWin32Drop              *drop_win32 = GDK_WIN32_DROP (drop);
  GTask                     *task;
  drop_target_context       *tctx;
  const char * const        *mime_types;
  gsize                      i, j, n_mime_types;
  GdkWin32ContentFormatPair *pair;
  FORMATETC                  fmt;
  HRESULT                    hr;
  STGMEDIUM                  storage;
  guchar                    *data;
  gsize                      data_len;
  GInputStream              *stream;

  task = g_task_new (drop, cancellable, callback, user_data);
  g_task_set_priority (task, io_priority);
  g_task_set_source_tag (task, gdk_win32_drop_read_async);

  mime_types = gdk_content_formats_get_mime_types (formats, &n_mime_types);

  if (drop_win32->protocol == GDK_DRAG_PROTO_WIN32_DROPFILES)
    {
      for (i = 0; i < n_mime_types; i++)
        if (g_strcmp0 (mime_types[i], "text/uri-list") == 0)
          break;
      if (i >= n_mime_types)
        {
          g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                                   _("No compatible transfer format found"));
          g_object_unref (task);
          g_clear_pointer (&drop_win32->dropfiles_list, g_free);

          return;
        }

      stream = g_memory_input_stream_new_from_data (drop_win32->dropfiles_list, strlen (drop_win32->dropfiles_list), g_free);
      drop_win32->dropfiles_list = NULL;
      g_object_set_data (G_OBJECT (stream), "gdk-dnd-stream-contenttype", (gpointer) "text/uri-list");
      g_task_return_pointer (task, stream, g_object_unref);
      g_object_unref (task);

      return;
    }

  tctx = GDK_WIN32_SURFACE (gdk_drop_get_surface (drop))->drop_target;

  if (tctx == NULL)
    {
      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                               _("GDK surface 0x%p is not registered as a drop target"), gdk_drop_get_surface (drop));
      g_object_unref (task);
      return;
    }

  if (tctx->data_object == NULL)
    {
      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                               _("Target context record 0x%p has no data object"), tctx);
      g_object_unref (task);
      return;
    }

  for (pair = NULL, i = 0; i < n_mime_types; i++)
    {
      for (j = 0; j < drop_win32->droptarget_w32format_contentformat_map->len; j++)
        {
          pair = &g_array_index (drop_win32->droptarget_w32format_contentformat_map, GdkWin32ContentFormatPair, j);
          if (pair->contentformat == mime_types[i])
            break;

          pair = NULL;
        }
    }

  if (pair == NULL)
    {
      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                               _("No compatible transfer format found"));
      g_object_unref (task);
      return;
    }

  fmt.cfFormat = pair->w32format;
  if (_gdk_win32_format_uses_hdata (pair->w32format))
    fmt.tymed = TYMED_HGLOBAL;
  else
    g_assert_not_reached ();

  fmt.ptd = NULL;
  fmt.dwAspect = DVASPECT_CONTENT;
  fmt.lindex = -1;

  hr = IDataObject_GetData (tctx->data_object, &fmt, &storage);

  if (!SUCCEEDED (hr) || hr != S_OK)
    {
      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                               _("IDataObject_GetData (0x%x) failed, returning 0x%lx"), fmt.cfFormat, hr);
      g_object_unref (task);
      return;
    }

  if (!pair->transmute)
    {
      if (_gdk_win32_format_uses_hdata (pair->w32format))
        {
          data = grab_data_from_hdata (task, storage.hGlobal, &data_len);

          if (data == NULL)
            {
              ReleaseStgMedium (&storage);
              g_object_unref (task);

              return;
            }
        }
      else
        {
          g_assert_not_reached ();
        }
    }
  else
    {
      GdkDisplay *display = gdk_drop_get_display (drop);
      gdk_win32_clipdrop_transmute_windows_data (gdk_win32_display_get_clipdrop (display),
                                                 pair->w32format, pair->contentformat,
                                                 storage.hGlobal,
                                                 &data,
                                                 &data_len);
    }

  ReleaseStgMedium (&storage);

  if (data == NULL)
    {
      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                               _("Failed to transmute DnD data W32 format 0x%x to %p (%s)"), pair->w32format, pair->contentformat, pair->contentformat);
      g_object_unref (task);
      return;
    }

  stream = g_memory_input_stream_new_from_data (data, data_len, g_free);
  g_object_set_data (G_OBJECT (stream), "gdk-dnd-stream-contenttype", (gpointer) pair->contentformat);
  g_task_return_pointer (task, stream, g_object_unref);
  g_object_unref (task);
}

static GInputStream *
gdk_win32_drop_read_finish (GdkDrop       *drop,
                            GAsyncResult  *result,
                            const char   **out_mime_type,
                            GError       **error)
{
  GTask *task;
  GInputStream *stream;

  g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (drop)), NULL);
  task = G_TASK (result);
  g_return_val_if_fail (g_task_get_source_tag (task) == gdk_win32_drop_read_async, NULL);

  stream = g_task_propagate_pointer (task, error);

  if (stream && out_mime_type)
    *out_mime_type = g_object_get_data (G_OBJECT (stream), "gdk-dnd-stream-contenttype");

  return stream;
}

static void
gdk_win32_drop_class_init (GdkWin32DropClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GdkDropClass *drop_class = GDK_DROP_CLASS (klass);

  object_class->finalize = gdk_win32_drop_finalize;

  drop_class->status = gdk_win32_drop_status;
  drop_class->finish = gdk_win32_drop_finish;
  drop_class->read_async = gdk_win32_drop_read_async;
  drop_class->read_finish = gdk_win32_drop_read_finish;
}
