[gtk] GDK W32: Another massive clipboard and DnD update



commit 54a43071288a306229b4b2e1cc54cd6962a71549
Author: Руслан Ижбулатов <lrn1986 gmail com>
Date:   Sat Mar 24 16:27:11 2018 +0000

    GDK W32: Another massive clipboard and DnD update
    
    Rename GdkWin32Selection to GdkWin32Clipdrop, since GdkSelection
    is mostly gone, and the word "selection" does not reflect the
    functionality of this object too well.
    
    Clipboard is now handled by a separate thread, most of the code for
    it now lives in gdkclipdrop-win32.c, gdkclipboard-win32.c just uses
    clipdrop as a backend.
    
    The DnD source part is also put into a thread.
    The DnD target part does not spin the main loop, it just
    emits a GDK event and returns a default value if it doesn't get a reply
    by the time the event is processed.
    
    Both clipboard and DnD use a new GOutputStream subclass to get data
    from GTK and put it into a HGLOBAL.
    
    GdkWin32DragContext is split into GdkWin32DragContext and GdkWin32DropContext,
    anticipating a similar change that slated to happen to GdkDragContext.
    
    OLE2 DnD protocol is now used by default, set GDK_WIN32_OLE2_DND envvar to 0
    to make GDK use the old LOCAL and DROPFILES protocols.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=773299

 gdk/win32/gdkclipboard-win32.c         |  299 +++
 gdk/win32/gdkclipboard-win32.h         |   39 +
 gdk/win32/gdkclipdrop-win32.c          | 3289 ++++++++++++++++++++++++++++++++
 gdk/win32/gdkclipdrop-win32.h          |  278 +++
 gdk/win32/gdkdisplay-win32.c           |  192 +-
 gdk/win32/gdkdisplay-win32.h           |    1 -
 gdk/win32/gdkdrag-win32.c              | 2893 ++++++++++++++++++++++++++++
 gdk/win32/gdkdrop-win32.c              | 1262 ++++++++++++
 gdk/win32/gdkevents-win32.c            |  148 +-
 gdk/win32/gdkglobals-win32.c           |    6 +-
 gdk/win32/gdkhdataoutputstream-win32.c |  403 ++++
 gdk/win32/gdkhdataoutputstream-win32.h |   63 +
 gdk/win32/gdkmain-win32.c              |    2 +-
 gdk/win32/gdkprivate-win32.h           |   23 +-
 gdk/win32/gdkwin32dnd-private.h        |   73 +-
 gdk/win32/gdkwin32dnd.h                |   17 +
 gdk/win32/meson.build                  |    7 +-
 17 files changed, 8637 insertions(+), 358 deletions(-)
---
diff --git a/gdk/win32/gdkclipboard-win32.c b/gdk/win32/gdkclipboard-win32.c
new file mode 100644
index 0000000000..61f39124b0
--- /dev/null
+++ b/gdk/win32/gdkclipboard-win32.c
@@ -0,0 +1,299 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * 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/>.
+ */
+
+#include "config.h"
+
+#include "gdkclipboardprivate.h"
+#include "gdkclipboard-win32.h"
+
+#include "gdkintl.h"
+#include "gdkprivate-win32.h"
+#include "gdkhdataoutputstream-win32.h"
+#include "gdk/gdk-private.h"
+
+#include <string.h>
+
+typedef struct _GdkWin32ClipboardClass GdkWin32ClipboardClass;
+
+typedef struct _RetrievalInfo RetrievalInfo;
+
+struct _GdkWin32Clipboard
+{
+  GdkClipboard parent;
+
+  /* Taken from the OS, the OS increments it every time
+   * clipboard data changes.
+   * -1 means that clipboard data that we claim to
+   * have access to is actually just an empty set that
+   * we made up. Thus any real data from the OS will
+   * override anything we make up.
+   */
+  gint64      sequence_number;
+};
+
+struct _GdkWin32ClipboardClass
+{
+  GdkClipboardClass parent_class;
+};
+
+G_DEFINE_TYPE (GdkWin32Clipboard, gdk_win32_clipboard, GDK_TYPE_CLIPBOARD)
+
+static GdkContentFormats *
+gdk_win32_clipboard_request_contentformats (GdkWin32Clipboard *cb)
+{
+  BOOL success;
+  UINT *w32_formats = NULL;
+  UINT w32_formats_len = 0;
+  UINT w32_formats_allocated;
+  gsize i;
+  GArray *formatpairs;
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+  DWORD error_code;
+
+  SetLastError (0);
+  success = clipdrop->GetUpdatedClipboardFormats (NULL, 0, &w32_formats_allocated);
+  error_code = GetLastError ();
+
+  if (!success && error_code != ERROR_INSUFFICIENT_BUFFER)
+    {
+      g_warning ("Initial call to GetUpdatedClipboardFormats() failed with error %lu", error_code);
+      return NULL;
+    }
+
+  w32_formats = g_new0 (UINT, w32_formats_allocated);
+
+  SetLastError (0);
+  success = clipdrop->GetUpdatedClipboardFormats (w32_formats, w32_formats_allocated, &w32_formats_len);
+  error_code = GetLastError ();
+
+  if (!success)
+    {
+      g_warning ("Second call to GetUpdatedClipboardFormats() failed with error %lu", error_code);
+      g_free (w32_formats);
+      return NULL;
+    }
+
+  formatpairs = g_array_sized_new (FALSE,
+                                   FALSE,
+                                   sizeof (GdkWin32ContentFormatPair),
+                                   MIN (w32_formats_len, w32_formats_allocated));
+
+  for (i = 0; i < MIN (w32_formats_len, w32_formats_allocated); i++)
+    _gdk_win32_add_w32format_to_pairs (w32_formats[i], formatpairs, NULL);
+
+  g_free (w32_formats);
+
+  GDK_NOTE (DND, {
+      g_print ("... ");
+      for (i = 0; i < formatpairs->len; i++)
+        {
+          const char *mime_type = (const char *) g_array_index (formatpairs, GdkWin32ContentFormatPair, 
i).contentformat;
+
+          g_print ("%s", mime_type);
+          if (i < formatpairs->len - 1)
+            g_print (", ");
+        }
+      g_print ("\n");
+    });
+
+  if (formatpairs->len > 0)
+    {
+      GdkContentFormatsBuilder *builder = gdk_content_formats_builder_new ();
+
+      for (i = 0; i < formatpairs->len; i++)
+        gdk_content_formats_builder_add_mime_type (builder, g_array_index (formatpairs, 
GdkWin32ContentFormatPair, i).contentformat);
+
+      g_array_free (formatpairs, TRUE);
+
+      return gdk_content_formats_builder_free (builder);
+    }
+  else
+    {
+      g_array_free (formatpairs, TRUE);
+
+      return NULL;
+    }
+}
+
+void
+gdk_win32_clipboard_claim_remote (GdkWin32Clipboard *cb)
+{
+  GdkContentFormats *formats;
+
+  /* Claim empty first, in case the format request fails */
+  formats = gdk_content_formats_new (NULL, 0);
+  gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb), formats);
+  gdk_content_formats_unref (formats);
+  cb->sequence_number = -1;
+
+  formats = gdk_win32_clipboard_request_contentformats (cb);
+  gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb), formats);
+  gdk_content_formats_unref (formats);
+  cb->sequence_number = GetClipboardSequenceNumber ();
+}
+
+static void
+gdk_win32_clipboard_finalize (GObject *object)
+{
+  GdkWin32Clipboard *cb = GDK_WIN32_CLIPBOARD (object);
+
+  G_OBJECT_CLASS (gdk_win32_clipboard_parent_class)->finalize (object);
+}
+
+static gboolean
+gdk_win32_clipboard_claim (GdkClipboard       *clipboard,
+                           GdkContentFormats  *formats,
+                           gboolean            local,
+                           GdkContentProvider *content)
+{
+  if (local)
+    _gdk_win32_advertise_clipboard_contentformats (NULL, content ? formats : NULL);
+
+  return GDK_CLIPBOARD_CLASS (gdk_win32_clipboard_parent_class)->claim (clipboard, formats, local, content);
+}
+
+static void
+gdk_win32_clipboard_store_async (GdkClipboard        *clipboard,
+                                 int                  io_priority,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  GdkWin32Clipboard *cb = GDK_WIN32_CLIPBOARD (clipboard);
+  GdkContentProvider *content;
+  GdkContentFormats *formats;
+  GTask *store_task;
+
+  store_task = g_task_new (clipboard, cancellable, callback, user_data);
+  g_task_set_priority (store_task, io_priority);
+  g_task_set_source_tag (store_task, gdk_win32_clipboard_store_async);
+
+  content = gdk_clipboard_get_content (clipboard);
+
+  if (content == NULL)
+    {
+      GDK_NOTE (CLIPBOARD, g_printerr ("storing empty clipboard: SUCCESS!\n"));
+      g_task_return_boolean (store_task, TRUE);
+      g_clear_object (&store_task);
+      return;
+    }
+
+  formats = gdk_content_provider_ref_storable_formats (content);
+  formats = gdk_content_formats_union_serialize_mime_types (formats);
+
+  if (!_gdk_win32_store_clipboard_contentformats (cb, store_task, formats))
+    {
+      GDK_NOTE (CLIPBOARD, g_printerr ("clipdrop says there's nothing to store: SUCCESS!\n"));
+      g_task_return_boolean (store_task, TRUE);
+      g_clear_object (&store_task);
+
+      return;
+    }
+}
+
+static gboolean
+gdk_win32_clipboard_store_finish (GdkClipboard  *clipboard,
+                                  GAsyncResult  *result,
+                                  GError       **error)
+{
+  g_return_val_if_fail (g_task_is_valid (result, clipboard), FALSE);
+  g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_win32_clipboard_store_async, FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+gdk_win32_clipboard_read_async (GdkClipboard        *clipboard,
+                                GdkContentFormats   *contentformats,
+                                int                  io_priority,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+  GdkWin32Clipboard *cb = GDK_WIN32_CLIPBOARD (clipboard);
+  GSList *targets;
+  GTask *task;
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+
+  task = g_task_new (clipboard, cancellable, callback, user_data);
+  g_task_set_priority (task, io_priority);
+  g_task_set_source_tag (task, gdk_win32_clipboard_read_async);
+
+  _gdk_win32_retrieve_clipboard_contentformats (task, contentformats);
+
+  return;
+}
+
+static GInputStream *
+gdk_win32_clipboard_read_finish (GdkClipboard  *clipboard,
+                                 const char   **out_mime_type,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+  GTask *task;
+  GInputStream *stream;
+
+  g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (clipboard)), NULL);
+  task = G_TASK (result);
+  g_return_val_if_fail (g_task_get_source_tag (task) == gdk_win32_clipboard_read_async, NULL);
+
+  stream = g_task_propagate_pointer (task, error);
+
+  if (stream == NULL)
+    return stream;
+
+  if (out_mime_type)
+    *out_mime_type = g_object_get_data (G_OBJECT (stream), "gdk-clipboard-stream-contenttype");
+
+  return stream;
+}
+
+static void
+gdk_win32_clipboard_class_init (GdkWin32ClipboardClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  GdkClipboardClass *clipboard_class = GDK_CLIPBOARD_CLASS (class);
+
+  object_class->finalize = gdk_win32_clipboard_finalize;
+
+  clipboard_class->claim = gdk_win32_clipboard_claim;
+  clipboard_class->store_async = gdk_win32_clipboard_store_async;
+  clipboard_class->store_finish = gdk_win32_clipboard_store_finish;
+  clipboard_class->read_async = gdk_win32_clipboard_read_async;
+  clipboard_class->read_finish = gdk_win32_clipboard_read_finish;
+}
+
+static void
+gdk_win32_clipboard_init (GdkWin32Clipboard *cb)
+{
+  cb->sequence_number = -1;
+}
+
+GdkClipboard *
+gdk_win32_clipboard_new (GdkDisplay  *display)
+{
+  GdkWin32Clipboard *cb;
+
+  cb = g_object_new (GDK_TYPE_WIN32_CLIPBOARD,
+                     "display", display,
+                     NULL);
+
+  gdk_win32_clipboard_claim_remote (cb);
+
+  return GDK_CLIPBOARD (cb);
+}
+
diff --git a/gdk/win32/gdkclipboard-win32.h b/gdk/win32/gdkclipboard-win32.h
new file mode 100644
index 0000000000..1152ee17da
--- /dev/null
+++ b/gdk/win32/gdkclipboard-win32.h
@@ -0,0 +1,39 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * 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/>.
+ */
+
+#ifndef __GDK_CLIPBOARD_WIN32_H__
+#define __GDK_CLIPBOARD_WIN32_H__
+
+#include "gdk/gdkclipboard.h"
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_WIN32_CLIPBOARD    (gdk_win32_clipboard_get_type ())
+#define GDK_WIN32_CLIPBOARD(obj)    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_WIN32_CLIPBOARD, 
GdkWin32Clipboard))
+#define GDK_IS_WIN32_CLIPBOARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_WIN32_CLIPBOARD))
+
+typedef struct _GdkWin32Clipboard GdkWin32Clipboard;
+
+GType                   gdk_win32_clipboard_get_type            (void) G_GNUC_CONST;
+
+GdkClipboard *          gdk_win32_clipboard_new                 (GdkDisplay *display);
+
+void                    gdk_win32_clipboard_claim_remote        (GdkWin32Clipboard *cb);
+
+G_END_DECLS
+
+#endif /* __GDK_CLIPBOARD_WIN32_H__ */
diff --git a/gdk/win32/gdkclipdrop-win32.c b/gdk/win32/gdkclipdrop-win32.c
new file mode 100644
index 0000000000..6774d6cb3b
--- /dev/null
+++ b/gdk/win32/gdkclipdrop-win32.c
@@ -0,0 +1,3289 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * 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/.
+ */
+
+/*
+GTK+ has two clipboards - normal clipboard and primary clipboard
+Primary clipboard is only handled
+internally by GTK+ (it's not portable to Windows).
+
+("C:" means clipboard client (requestor), "S:" means clipboard server (provider))
+("transmute" here means "change the format of some data"; this term is used here
+ instead of "convert" to avoid clashing with the old g(t|d)k_selection_convert() APIs,
+ which are completely unrelated)
+
+For Clipboard:
+GTK+ calls one of the gdk_clipboard_set* () functions (either supplying
+its own content provider, or giving a GTyped data for which GDK will
+create a content provider automatically).
+That function associates the content provider with the clipboard and calls
+S: gdk_clipboard_claim(),
+to claim ownership. GDK first calls the backend implementation of
+that function, then the
+S: gdk_clipboard_real_claim()
+implementation.
+The "real" function does some mundane bookkeeping, whereas the backend
+implementation advertises the formats supported by the clipboard,
+if the call says that the claim is local. Non-local (remote) claims
+are there just to tell GDK that some other process owns the clipboard
+and claims to provide data in particular formats.
+No data is sent anywhere.
+
+The content provider has a callback, which will be invoked every time
+the data from this provider is needed.
+
+GTK+ might also call gdk_clipboard_store_async(), which instructs
+the backend to put the data into the OS clipboard manager (if
+supported and available) so that it remains available for other
+processes after the clipboard owner terminates.
+
+When something needs to be obtained from clipboard, GTK+ calls
+C: gdk_clipboard_read_async () -> gdk_clipboard_read_internal (),
+providing it with a string-array of mime/types, which is internally
+converted into a GdkContentFormats object.
+That function creates a task.
+
+Then, if the clipboard is local, it calls
+C: gdk_clipboard_read_local_async(),
+which matches given formats to the content provider formats and, if there's a match,
+creates a pipe, calls
+C: gdk_clipboard_write_async()
+on the write end, and sets the read end as a return value of the task, which will
+later be given to the caller-specified callback.
+
+If the clipboard isn't local, it calls
+C: read_async()
+of the backend clipboard stream class.
+The backend starts creating a stream (somehow) and sets up the callback to return that
+stream via the task, once the stream is created.
+Either way, the caller-specified callback is invoked, and it gets the read end
+of a stream by calling
+C: gdk_clipboard_read_finish(),
+then reads the data from the stream and unrefs the stream once it is done.
+IRL applications use wrappers, which create an extra task that gets the
+stream, reads from it asynchronously and turns the bytes that it reads into
+some kind of application-specific object type. GDK comes pre-equipped with
+functions that read arbitrary GTypes (as long as they are serializable),
+texts (strings) or textures (GdkPixbufs) this way.
+
+On Windows:
+Clipboard is opened by OpenClipboard(), emptied by EmptyClipboard() (which also
+makes the window the clipboard owner), data is put into it by SetClipboardData().
+Clipboard is closed with CloseClipboard().
+If SetClipboardData() is given a NULL data value, the owner will later
+receive WM_RENDERFORMAT message, in response to which it must call
+SetClipboardData() with the provided handle and the actual data this time.
+This way applications can avoid storing everything in the clipboard
+all the time, only putting the data there as it is requested by other applications.
+At some undefined points of time an application might get WM_RENDERALLFORMATS
+message, it should respond by opening the clipboard and rendering
+into it all the data that it offers, as if responding to multiple WM_RENDERFORMAT
+messages.
+
+On GDK-Win32:
+
+Any operations that require OpenClipboard()/CloseClipboard() combo (i.e.
+almost everything, except for WM_RENDERFORMAT handling) is offloaded into
+separate thread, which tries to to complete any operations in the queue.
+Each operation routine usually starts with a timeout check (all operations
+time out after 30 seconds), then a check for clipboard status (to abort
+any operations that became obsolete due to clipboard status being changed - 
+i.e. retrieving clipboard contents is aborted if clipboard contents change
+before the operation can be completed), then an attempt to OpenClipboard().
+Failure to OpenClipboard() leads to the queue processing stopping, and
+resuming from the beginning after another 1-second delay.
+A success in OpenClipboard() allows the operation to continue.
+The thread remembers the fact that it has clipboard open, and does
+not try to close & reopen it, unless that is strictly necessary.
+The clipboard is closed after each queue processing run.
+
+GTK+ calls one of the gdk_clipboard_set* () functions (either supplying
+its own content provider, or giving a GTyped data for which GDK will
+create a content provider automatically).
+That function associates the content provider with the clipboard and calls
+S: gdk_clipboard_claim(),
+to claim ownership. GDK first calls the backend implementation of
+that function,
+S: gdk_win32_clipboard_claim(),
+which maps the supported GDK contentformats to W32 data formats and
+caches this mapping, then the
+S: gdk_clipboard_real_claim()
+implementation.
+The "real" function does some mundane bookkeeping, whereas the backend
+implementation advertises the formats supported by the clipboard,
+if the call says that the claim is local. Non-local (remote) claims
+are there just to tell GDK that some other process owns the clipboard
+and claims to provide data in particular formats.
+For the local claims gdk_win32_clipboard_claim() queues a clipboard
+advertise operation (see above).
+That operation will call EmptyClipboard() to claim the ownership,
+then call SetClipboardData() with NULL value for each W32 data format
+supported, advertising the W32 data formats to other processes.
+No data is sent anywhere.
+
+The content provider has a callback, which will be invoked every time
+the data from this provider is needed.
+
+GTK+ might also call gdk_clipboard_store_async(), which instructs
+the W32 backend to put the data into the OS clipboard manager by
+sending WM_RENDERALLFORMATS to itself and then handling it normally.
+
+Every time W32 backend gets WM_DRAWCLIPBOARD or WM_CLIPBOARDUPDATE,
+it calls GetUpdatedClipboardFormats() and GetClipboardSequenceNumber()
+and caches the results of both. These calls do not require the clipboard
+to be opened.
+After that it would call
+C: gdk_win32_clipboard_claim_remote()
+to indicate that some other process owns the clipboard and supports
+the formats from the cached list. If the process is the owner,
+the remote claim is not performed (it's assumed that a local claim
+was already made when a clipboard content provider is set, so no need
+to do that either).
+Note: clipboard sequence number changes with each SetClipboardData() call.
+Specifically, a process that uses delayed rendering (like GDK does)
+must call SetClipboardData() with NULL value every time the data changes,
+even if its format remains the same.
+The cached remote formats are then mapped into GDK contentformats.
+This map is separate from the one that maps supported GDK contentformats
+to W32 formats for locally-claimed clipboards.
+
+When something needs to be obtained from clipboard, GTK+ calls
+C: gdk_clipboard_read_async () -> gdk_clipboard_read_internal (),
+providing it with a string-array of mime/types, which is internally
+converted into a GdkContentFormats object.
+That function creates a task.
+
+Then, if the clipboard is local, it calls
+C: gdk_clipboard_read_local_async(),
+which matches given formats to the content provider formats and, if there's a match,
+creates a pipe, calls
+C: gdk_clipboard_write_async()
+on the write end, and sets the read end as a return value of the task, which will
+later be given to the caller-specified callback.
+
+If the clipboard isn't local, it calls
+C: read_async()
+of the W32 backend clipboard stream class.
+It then queues a retrieve operation (see above).
+The retrieve operation goes over formats available on the clipboard,
+and picks the first one that matches the list supplied with the retrieve
+operation (that is, it gives priority to formats at the top of the clipboard
+format list, even if such formats are at the bottom of the list of formats
+supported by the application; this is due to the fact that formats at the
+top of the clipboard format list are usually "raw" or "native" and assumed
+to not to be transmuted by the clipboard owner from some other format,
+and thus it is better to use these, if the requesting application can handle
+them). It then calls GetClipboardData(), which either causes
+a WM_RENDERFORMAT to be sent to the server (for delayed rendering),
+or it just grabs the data from the OS.
+
+Server-side GDK catches WM_RENDERFORMAT, figures out a contentformat
+to request (it has an earlier advertisement cached in the thread, so
+there's no need to ask the main thread for anything), and
+creates a render request, then sends it to the main thread.
+After that it keeps polling the queue until the request comes back.
+The main thread render handler creates an output stream that
+writes the data into a global memory buffer, then calls
+S: gdk_clipboard_write_async()
+to write the data.
+The callback finishes that up with
+S: gdk_clipboard_write_finish(),
+which sends the render request back to the clipborad thread,
+along with the data. The clipboard thread then calls
+S: SetClipboardData()
+with the clipboard handle provided by the OS on behalf of the client.
+
+Once the data handle is available, the clipboard thread creates a stream
+that reads from a copy of that data (after transmutation, if necessary),
+and sends that stream back to the main thread. The data is kept in a client-side
+memory buffer (owned by the stream), the HGLOBAL given by the OS is not held
+around for this to happen.
+The stream is then returned through the task to the caller.
+
+Either way, the caller-specified callback is invoked, and it gets the read end
+of a stream by calling
+C: gdk_clipboard_read_finish(),
+then reads the data from the stream and unrefs the stream once it is done.
+The local buffer that backed the stream is freed with the stream.
+IRL applications use wrappers, which create an extra task that gets the
+stream, reads from it asynchronously and turns the bytes that it reads into
+some kind of application-specific object type. GDK comes pre-equipped with
+functions that read arbitrary GTypes (as long as they are serializable),
+texts (strings) or textures (GdkPixbufs) this way.
+
+If data must be stored on the clipboard, because the application is quitting,
+GTK+ will call
+S: gdk_clipboard_store_async()
+on all the clipboards it owns. This creates multiple write stream (one for each
+format being stored), each backed by a HGLOBAL memory object. Once all memory
+objects are written, the backend queues a store operation, passing along
+all these HGLOBAL objects. The clipboard thread processes that by sending
+WM_RENDERALLFORMATS to the window, then signals the task that it's done.
+
+When clipboard owner changes, the old owner receives WM_DESTROYCLIPBOARD message,
+the clipboard thread schedules a call to gdk_clipboard_claim_remote()
+in the main thread, with an empty list of formats,
+to indicate that the clipboard is now owned by a remote process.
+Later the OS will send WM_DRAWCLIPBOARD or WM_CLIPBOARDUPDATE to indicate
+the new clipboard contents (see above).
+
+DND:
+GDK-Win32:
+DnD server runs in a separate thread, and schedules calls to be
+made in the main thread in response to the DnD thread being invoked
+by the OS (using OLE2 mechanism).
+The DnD thread normally just idles, until the main thread tells it
+to call DoDragDrop(), at which point it enters the DoDragDrop() call
+(which means that its OLE2 DnD callbacks get invoked repeatedly by the OS
+ in response to user actions), and doesn't leave it until the DnD
+operation is finished.
+
+Otherwise it's similar to how the clipboard works. Only the DnD server
+(drag source) works in a thread. DnD client (drop target) works normally.
+*/
+
+#include "config.h"
+#include <string.h>
+#include <stdlib.h>
+
+/* For C-style COM wrapper macros */
+#define COBJMACROS
+
+/* for CIDA */
+#include <shlobj.h>
+
+#include "gdkproperty.h"
+#include "gdkdisplay.h"
+#include "gdkprivate-win32.h"
+#include "gdkclipboardprivate.h"
+#include "gdkclipboard-win32.h"
+#include "gdkclipdrop-win32.h"
+#include "gdkhdataoutputstream-win32.h"
+#include "gdk/gdkdndprivate.h"
+#include "gdkwin32dnd.h"
+#include "gdkwin32dnd-private.h"
+#include "gdkwin32.h"
+#include "gdkintl.h"
+
+#define HIDA_GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
+#define HIDA_GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
+
+#define CLIPBOARD_OPERATION_TIMEOUT (G_USEC_PER_SEC * 30)
+
+/* GetClipboardData() times out after 30 seconds.
+ * Try to reply (even if it's a no-action reply due to a timeout)
+ * before that happens.
+ */
+#define CLIPBOARD_RENDER_TIMEOUT (G_USEC_PER_SEC * 29)
+
+gboolean _gdk_win32_transmute_windows_data (UINT          from_w32format,
+                                            const gchar  *to_contentformat,
+                                            HANDLE        hdata,
+                                            guchar      **set_data,
+                                            gsize        *set_data_length);
+
+/* Just to avoid calling RegisterWindowMessage() every time */
+static UINT thread_wakeup_message;
+
+typedef enum _GdkWin32ClipboardThreadQueueItemType GdkWin32ClipboardThreadQueueItemType;
+
+enum _GdkWin32ClipboardThreadQueueItemType
+{
+  GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_ADVERTISE = 1,
+  GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_RETRIEVE = 2,
+  GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_STORE = 3,
+};
+
+typedef struct _GdkWin32ClipboardThreadQueueItem GdkWin32ClipboardThreadQueueItem;
+
+struct _GdkWin32ClipboardThreadQueueItem
+{
+  GdkWin32ClipboardThreadQueueItemType  item_type;
+  gint64                                start_time;
+  gint64                                end_time;
+  gpointer                              opaque_task;
+};
+
+typedef struct _GdkWin32ClipboardThreadAdvertise GdkWin32ClipboardThreadAdvertise;
+
+struct _GdkWin32ClipboardThreadAdvertise
+{
+  GdkWin32ClipboardThreadQueueItem  parent;
+  GArray                      *pairs; /* of GdkWin32ContentFormatPair */
+  gboolean                     unset;
+};
+
+typedef struct _GdkWin32ClipboardThreadRetrieve GdkWin32ClipboardThreadRetrieve;
+
+struct _GdkWin32ClipboardThreadRetrieve
+{
+  GdkWin32ClipboardThreadQueueItem  parent;
+  GArray                      *pairs; /* of GdkWin32ContentFormatPair */
+  gint64                       sequence_number;
+};
+
+typedef struct _GdkWin32ClipboardStorePrepElement GdkWin32ClipboardStorePrepElement;
+
+struct _GdkWin32ClipboardStorePrepElement
+{
+  UINT           w32format;
+  const gchar   *contentformat;
+  HANDLE         handle;
+  GOutputStream *stream;
+};
+
+typedef struct _GdkWin32ClipboardStorePrep GdkWin32ClipboardStorePrep;
+
+struct _GdkWin32ClipboardStorePrep
+{
+  GTask             *store_task;
+  GArray            *elements; /* of GdkWin32ClipboardStorePrepElement */
+};
+
+typedef struct _GdkWin32ClipboardThreadStore GdkWin32ClipboardThreadStore;
+
+struct _GdkWin32ClipboardThreadStore
+{
+  GdkWin32ClipboardThreadQueueItem  parent;
+  GArray                      *elements; /* of GdkWin32ClipboardStorePrepElement */
+};
+
+typedef struct _GdkWin32ClipboardThreadRender GdkWin32ClipboardThreadRender;
+
+struct _GdkWin32ClipboardThreadRender
+{
+  /* The handle that the main thread prepares for us.
+   * We just give it to SetClipboardData ().
+   * NULL means that the rendering failed.
+   */
+  HANDLE                                main_thread_data_handle;
+
+  /* The format that is being requested of us */
+  GdkWin32ContentFormatPair             pair;
+};
+
+typedef struct _GdkWin32ClipboardThread GdkWin32ClipboardThread;
+
+struct _GdkWin32ClipboardThread
+{
+  /* A hidden window that owns our clipboard
+   * and receives clipboard-related messages.
+   */
+  HWND         clipboard_window;
+
+  /* We receive instructions from the main thread in this queue */
+  GAsyncQueue *input_queue;
+
+  /* Last observer owner of the clipboard, as reported by the OS.
+   * This is compared to GetClipboardOwner() return value to see
+   * whether the owner changed.
+   */
+  HWND         stored_hwnd_owner;
+
+  /* The last time we saw an owner change event.
+   * Any requests made before this time are invalid and
+   * fail automatically.
+   */
+  gint64       owner_change_time;
+
+  /* The handle that was given to OpenClipboard().
+   * NULL is a valid handle,
+   * INVALID_HANDLE_VALUE means that the clipboard is closed.
+   */
+  HWND         clipboard_opened_for;
+
+  HWND         hwnd_next_viewer;
+
+  /* We can't peek the queue or "unpop" queue items,
+   * so the items that we can't act upon (yet) got
+   * to be stored *somewhere*.
+   */
+  GList       *dequeued_items;
+
+  /* Wakeup timer id (1 if timer is set, 0 otherwise) */
+  UINT         wakeup_timer;
+
+  /* The formats that the main thread claims to provide */
+  GArray      *cached_advertisement; /* of GdkWin32ContentFormatPair */
+
+  /* We receive rendered clipboard data in this queue.
+   * Contains GdkWin32ClipboardThreadRender structs.
+   */
+  GAsyncQueue *render_queue; 
+
+  /* Set to TRUE when we're calling EmptyClipboard () */
+  gboolean     ignore_destroy_clipboard;
+};
+
+/* The code is much more secure if we don't rely on the OS to keep
+ * this around for us.
+ */
+static GdkWin32ClipboardThread *clipboard_thread_data = NULL;
+
+typedef struct _GdkWin32ClipboardThreadResponse GdkWin32ClipboardThreadResponse;
+
+struct _GdkWin32ClipboardThreadResponse
+{
+  GdkWin32ClipboardThreadQueueItemType  item_type;
+  GError                               *error;
+  gpointer                              opaque_task;
+  GInputStream                         *input_stream;
+};
+
+gboolean
+_gdk_win32_format_uses_hdata (UINT w32format)
+{
+  switch (w32format)
+    {
+    case CF_DIB:
+    case CF_DIBV5:
+    case CF_DIF:
+    case CF_DSPBITMAP:
+    case CF_DSPENHMETAFILE:
+    case CF_DSPMETAFILEPICT:
+    case CF_DSPTEXT:
+    case CF_OEMTEXT:
+    case CF_RIFF:
+    case CF_SYLK:
+    case CF_TEXT:
+    case CF_TIFF:
+    case CF_UNICODETEXT:
+    case CF_WAVE:
+      return TRUE;
+    default:
+      if (w32format >= 0xC000)
+        return TRUE;
+      else
+        return FALSE;
+    }
+}
+
+
+/* This function is called in the main thread */
+static gboolean
+clipboard_window_created (gpointer user_data)
+{
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+
+  clipdrop->clipboard_window = (HWND) user_data;
+
+  return G_SOURCE_REMOVE;
+}
+
+/* This function is called in the main thread */
+static gboolean
+clipboard_owner_changed (gpointer user_data)
+{
+  GdkDisplay *display = gdk_display_get_default ();
+  GdkClipboard *clipboard = gdk_display_get_clipboard (display);
+  gdk_win32_clipboard_claim_remote (GDK_WIN32_CLIPBOARD (clipboard));
+
+  return G_SOURCE_REMOVE;
+}
+
+typedef struct _GdkWin32ClipboardRenderAndStream GdkWin32ClipboardRenderAndStream;
+
+struct _GdkWin32ClipboardRenderAndStream
+{
+  GdkWin32ClipboardThreadRender *render;
+  GdkWin32HDataOutputStream *stream;
+};
+
+static void
+clipboard_render_hdata_ready (GObject      *clipboard,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  GError *error = NULL;
+  GdkWin32ClipboardRenderAndStream render_and_stream = *(GdkWin32ClipboardRenderAndStream *) user_data;
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+
+  g_free (user_data);
+
+  if (!gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, &error))
+    {
+      HANDLE handle;
+      gboolean is_hdata;
+      GDK_NOTE(CLIPBOARD, g_printerr ("%p: failed to write HData-backed stream: %s\n", clipboard, 
error->message));
+      g_error_free (error);
+      g_output_stream_close (G_OUTPUT_STREAM (render_and_stream.stream), NULL, NULL);
+      handle = gdk_win32_hdata_output_stream_get_handle (render_and_stream.stream, &is_hdata);
+
+      if (is_hdata)
+        API_CALL (GlobalFree, (handle));
+      else
+        API_CALL (CloseHandle, (handle));
+
+      render_and_stream.render->main_thread_data_handle = NULL;
+    }
+  else
+    {
+      g_output_stream_close (G_OUTPUT_STREAM (render_and_stream.stream), NULL, NULL);
+      render_and_stream.render->main_thread_data_handle = gdk_win32_hdata_output_stream_get_handle 
(render_and_stream.stream, NULL);
+    }
+
+  g_async_queue_push (clipdrop->clipboard_render_queue, render_and_stream.render);
+  g_object_unref (render_and_stream.stream);
+}
+
+/* This function is called in the main thread */
+static gboolean
+clipboard_render (gpointer user_data)
+{
+  GdkWin32ClipboardThreadRender *render = (GdkWin32ClipboardThreadRender *) user_data;
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+  GdkDisplay *display = gdk_display_get_default ();
+  GdkClipboard *clipboard = gdk_display_get_clipboard (display);
+  GError *error = NULL;
+  GOutputStream *stream = gdk_win32_hdata_output_stream_new (&render->pair, &error);
+  GdkWin32ClipboardRenderAndStream *render_and_stream;
+
+  if (stream == NULL)
+    {
+      GDK_NOTE (SELECTION, g_printerr ("%p: failed create a HData-backed stream: %s\n", clipboard, 
error->message));
+      g_error_free (error);
+      render->main_thread_data_handle = NULL;
+      g_async_queue_push (clipdrop->clipboard_render_queue, render);
+
+      return G_SOURCE_REMOVE;
+    }
+
+  render_and_stream = g_new0 (GdkWin32ClipboardRenderAndStream, 1);
+  render_and_stream->render = render;
+  render_and_stream->stream = GDK_WIN32_HDATA_OUTPUT_STREAM (stream);
+
+  gdk_clipboard_write_async (GDK_CLIPBOARD (clipboard),
+                             render->pair.contentformat,
+                             stream,
+                             G_PRIORITY_DEFAULT,
+                             NULL,
+                             clipboard_render_hdata_ready,
+                             render_and_stream);
+
+  /* Keep our reference to the stream, don't unref it */
+
+  return G_SOURCE_REMOVE;
+}
+
+/* This function is called in the main thread */
+static gboolean
+clipboard_thread_response (gpointer user_data)
+{
+  GdkWin32ClipboardThreadResponse *response = (GdkWin32ClipboardThreadResponse *) user_data;
+  GTask *task = (GTask *) response->opaque_task;
+
+  if (task != NULL)
+    {
+      if (response->error)
+        g_task_return_error (task, response->error);
+      else if (response->input_stream)
+        g_task_return_pointer (task, response->input_stream, g_object_unref);
+      else
+        g_task_return_boolean (task, TRUE);
+
+      g_object_unref (task);
+    }
+
+  g_free (response);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+free_prep_element (GdkWin32ClipboardStorePrepElement *el)
+{
+  if (el->handle)
+    {
+      if (_gdk_win32_format_uses_hdata (el->w32format))
+        GlobalFree (el->handle);
+      else
+        CloseHandle (el->handle);
+    }
+
+  if (el->stream)
+    g_object_unref (el->stream);
+}
+
+static void
+free_queue_item (GdkWin32ClipboardThreadQueueItem *item)
+{
+  GdkWin32ClipboardThreadAdvertise *adv;
+  GdkWin32ClipboardThreadRetrieve *retr;
+  GdkWin32ClipboardThreadStore    *store;
+  gint i;
+
+  switch (item->item_type)
+    {
+    case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_ADVERTISE:
+      adv = (GdkWin32ClipboardThreadAdvertise *) item;
+      if (adv->pairs)
+        g_array_free (adv->pairs, TRUE);
+      break;
+    case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_RETRIEVE:
+      retr = (GdkWin32ClipboardThreadRetrieve *) item;
+      if (retr->pairs)
+        g_array_free (retr->pairs, TRUE);
+      break;
+    case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_STORE:
+      store = (GdkWin32ClipboardThreadStore *) item;
+      for (i = 0; i < store->elements->len; i++)
+        {
+          GdkWin32ClipboardStorePrepElement *el = &g_array_index (store->elements, 
GdkWin32ClipboardStorePrepElement, i);
+          free_prep_element (el);
+        }
+      g_array_free (store->elements, TRUE);
+      break;
+    }
+
+  g_free (item);
+}
+
+static void
+send_response (GdkWin32ClipboardThreadQueueItemType  request_type,
+               gpointer                              opaque_task,
+               GError                               *error)
+{
+  GdkWin32ClipboardThreadResponse *response = g_new0 (GdkWin32ClipboardThreadResponse, 1);
+  response->error = error;
+  response->opaque_task = opaque_task;
+  response->item_type = request_type;
+  g_idle_add_full (G_PRIORITY_DEFAULT, clipboard_thread_response, response, NULL);
+}
+
+static void
+send_input_stream (GdkWin32ClipboardThreadQueueItemType  request_type,
+                   gpointer                              opaque_task,
+                   GInputStream                         *stream)
+{
+  GdkWin32ClipboardThreadResponse *response = g_new0 (GdkWin32ClipboardThreadResponse, 1);
+  response->input_stream = stream;
+  response->opaque_task = opaque_task;
+  response->item_type = request_type;
+  g_idle_add_full (G_PRIORITY_DEFAULT, clipboard_thread_response, response, NULL);
+}
+
+static DWORD
+try_open_clipboard (HWND hwnd)
+{
+  if (clipboard_thread_data->clipboard_opened_for == hwnd)
+    return NO_ERROR;
+
+  if (clipboard_thread_data->clipboard_opened_for != INVALID_HANDLE_VALUE)
+    {
+      API_CALL (CloseClipboard, ());
+      clipboard_thread_data->clipboard_opened_for = INVALID_HANDLE_VALUE;
+    }
+
+  if (!OpenClipboard (hwnd))
+    return GetLastError ();
+
+  clipboard_thread_data->clipboard_opened_for = hwnd;
+
+  return NO_ERROR;
+}
+
+static gboolean
+process_advertise (GdkWin32ClipboardThreadAdvertise *adv)
+{
+  DWORD error_code;
+  gint i;
+
+  if (g_get_monotonic_time () > adv->parent.end_time)
+    {
+      GDK_NOTE (CLIPBOARD, g_printerr ("An advertise task timed out\n"));
+      send_response (adv->parent.item_type,
+                     adv->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot claim clipboard ownership. OpenClipboard() timed out.")));
+      return FALSE;
+    }
+
+  if (clipboard_thread_data->owner_change_time > adv->parent.start_time)
+    {
+      GDK_NOTE (CLIPBOARD, g_printerr ("An advertise task timed out due to ownership change\n"));
+      send_response (adv->parent.item_type,
+                     adv->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot claim clipboard ownership. Another process claimed it before 
us.")));
+      return FALSE;
+    }
+
+  error_code = try_open_clipboard (adv->unset ? NULL : clipboard_thread_data->clipboard_window);
+
+  if (error_code == ERROR_ACCESS_DENIED)
+    return TRUE;
+
+  if (G_UNLIKELY (error_code != NO_ERROR))
+    {
+      send_response (adv->parent.item_type,
+                     adv->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot claim clipboard ownership. OpenClipboard() failed: 0x%lx."), 
error_code));
+      return FALSE;
+    }
+
+  clipboard_thread_data->ignore_destroy_clipboard = TRUE;
+  if (!EmptyClipboard ())
+    {
+      clipboard_thread_data->ignore_destroy_clipboard = FALSE;
+      error_code = GetLastError ();
+      send_response (adv->parent.item_type,
+                     adv->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot claim clipboard ownership. EmptyClipboard() failed: 0x%lx."), 
error_code));
+      return FALSE;
+    }
+
+  clipboard_thread_data->ignore_destroy_clipboard = FALSE;
+
+  if (adv->unset)
+    return FALSE;
+
+  for (i = 0; i < adv->pairs->len; i++)
+    {
+      GdkWin32ContentFormatPair *pair = &g_array_index (adv->pairs, GdkWin32ContentFormatPair, i);
+
+      SetClipboardData (pair->w32format, NULL);
+    }
+
+  if (clipboard_thread_data->cached_advertisement)
+    g_array_free (clipboard_thread_data->cached_advertisement, TRUE);
+
+  clipboard_thread_data->cached_advertisement = adv->pairs;
+
+  /* To enure that we don't free it later on */
+  adv->pairs = NULL;
+
+  send_response (adv->parent.item_type,
+                 adv->parent.opaque_task,
+                 NULL);
+
+  return FALSE;
+}
+
+static gboolean
+process_store (GdkWin32ClipboardThreadStore *store)
+{
+  DWORD error_code;
+  gint i;
+
+  if (g_get_monotonic_time () > store->parent.end_time)
+    {
+      GDK_NOTE (CLIPBOARD, g_printerr ("A store task timed out\n"));
+      send_response (store->parent.item_type,
+                     store->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot set clipboard data. OpenClipboard() timed out.")));
+      return FALSE;
+    }
+
+  if (clipboard_thread_data->owner_change_time > store->parent.start_time)
+    {
+      GDK_NOTE (CLIPBOARD, g_printerr ("A store task timed out due to ownership change\n"));
+      send_response (store->parent.item_type,
+                     store->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot set clipboard data. Another process claimed clipboard 
ownership.")));
+      return FALSE;
+    }
+
+  error_code = try_open_clipboard (clipboard_thread_data->clipboard_window);
+
+  if (error_code == ERROR_ACCESS_DENIED)
+    return TRUE;
+
+  if (G_UNLIKELY (error_code != NO_ERROR))
+    {
+      send_response (store->parent.item_type,
+                     store->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot set clipboard data. OpenClipboard() failed: 0x%lx."), 
error_code));
+      return FALSE;
+    }
+
+  /* It's possible for another process to claim ownership
+   * between between us entering this function and us opening the clipboard.
+   * So check the ownership one last time.
+   * Unlike the advertisement routine above, here we don't want to
+   * claim clipboard ownership - we want to store stuff in the clipboard
+   * that we already own, otherwise we're just killing stuff that some other
+   * process put in there, which is not nice.
+   */
+  if (GetClipboardOwner () != clipboard_thread_data->clipboard_window)
+    {
+      send_response (store->parent.item_type,
+                     store->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot set clipboard data. Another process claimed clipboard 
ownership.")));
+      return FALSE;
+    }
+
+  for (i = 0; i < store->elements->len; i++)
+    {
+      GdkWin32ClipboardStorePrepElement *el = &g_array_index (store->elements, 
GdkWin32ClipboardStorePrepElement, i);
+      if (el->handle != NULL && el->w32format != 0)
+        if (SetClipboardData (el->w32format, el->handle))
+          el->handle = NULL; /* the OS now owns the handle, don't free it later on */
+    }
+
+  send_response (store->parent.item_type,
+                 store->parent.opaque_task,
+                 NULL);
+
+  return FALSE;
+}
+
+static gpointer
+grab_data_from_hdata (GdkWin32ClipboardThreadRetrieve *retr,
+                      HANDLE                           hdata,
+                      gsize                           *data_len)
+{
+  gpointer ptr;
+  SIZE_T length;
+  guchar *data;
+
+  ptr = GlobalLock (hdata);
+  if (ptr == NULL)
+    {
+      DWORD error_code = GetLastError ();
+      send_response (retr->parent.item_type,
+                     retr->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot get clipboard 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 ();
+      send_response (retr->parent.item_type,
+                     retr->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot get clipboard data. GlobalSize(0x%p) failed: 0x%lx."), hdata, 
error_code));
+      GlobalUnlock (hdata);
+      return NULL;
+    }
+
+  data = g_try_malloc (length);
+
+  if (data == NULL)
+    {
+      send_response (retr->parent.item_type,
+                     retr->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot get clipboard data. Failed to allocate %lu bytes to store the 
data."), length));
+      GlobalUnlock (hdata);
+      return NULL;
+    }
+
+  memcpy (data, ptr, length);
+  *data_len = length;
+
+  GlobalUnlock (hdata);
+
+  return data;
+}
+
+static gboolean
+process_retrieve (GdkWin32ClipboardThreadRetrieve *retr)
+{
+  DWORD error_code;
+  gint i;
+  UINT fmt, fmt_to_use;
+  HANDLE hdata;
+  GdkWin32ContentFormatPair *pair;
+  gpointer ptr;
+  guchar *data;
+  gsize   data_len;
+  GInputStream *stream;
+
+  if (g_get_monotonic_time () > retr->parent.end_time)
+    {
+      GDK_NOTE (CLIPBOARD, g_printerr ("A retrieve task timed out\n"));
+      send_response (retr->parent.item_type,
+                     retr->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot get clipboard data. OpenClipboard() timed out.")));
+      return FALSE;
+    }
+
+  if (clipboard_thread_data->owner_change_time > retr->parent.start_time)
+    {
+      GDK_NOTE (CLIPBOARD, g_printerr ("A retrieve task timed out due to ownership change\n"));
+      send_response (retr->parent.item_type,
+                     retr->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot get clipboard data. Clipboard ownership changed.")));
+      return FALSE;
+    }
+
+  if (GetClipboardSequenceNumber () > retr->sequence_number)
+    {
+      GDK_NOTE (CLIPBOARD, g_printerr ("A retrieve task timed out due to data change\n"));
+      send_response (retr->parent.item_type,
+                     retr->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot get clipboard data. Clipboard data changed before we could get 
it.")));
+      return FALSE;
+    }
+
+  if (clipboard_thread_data->clipboard_opened_for == INVALID_HANDLE_VALUE)
+    error_code = try_open_clipboard (clipboard_thread_data->clipboard_window);
+  else
+    error_code = try_open_clipboard (clipboard_thread_data->clipboard_opened_for);
+
+  if (error_code == ERROR_ACCESS_DENIED)
+    return TRUE;
+
+  if (G_UNLIKELY (error_code != NO_ERROR))
+    {
+      send_response (retr->parent.item_type,
+                     retr->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot get clipboard data. OpenClipboard() failed: 0x%lx."), 
error_code));
+      return FALSE;
+    }
+
+  for (fmt_to_use = 0, pair = NULL, fmt = 0;
+       fmt_to_use == 0 && 0 != (fmt = EnumClipboardFormats (fmt));
+      )
+    {
+      for (i = 0; i < retr->pairs->len; i++)
+        {
+          pair = &g_array_index (retr->pairs, GdkWin32ContentFormatPair, i);
+
+          if (pair->w32format != fmt)
+            continue;
+
+          fmt_to_use = fmt;
+          break;
+        }
+    }
+
+  if (!fmt_to_use)
+    {
+      send_response (retr->parent.item_type,
+                     retr->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot get clipboard data. No compatible transfer format found.")));
+      return FALSE;
+    }
+
+  if ((hdata = GetClipboardData (fmt_to_use)) == NULL)
+    {
+      error_code = GetLastError ();
+      send_response (retr->parent.item_type,
+                     retr->parent.opaque_task,
+                     g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                  _("Cannot get clipboard data. GetClipboardData() failed: 0x%lx."), 
error_code));
+      return FALSE;
+    }
+
+  if (!pair->transmute)
+    {
+      if (_gdk_win32_format_uses_hdata (pair->w32format))
+        {
+          data = grab_data_from_hdata (retr, hdata, &data_len);
+
+          if (data == NULL)
+            return FALSE;
+        }
+      else
+        {
+          data_len = sizeof (HANDLE);
+          data = g_malloc (data_len);
+          memcpy (data, &hdata, data_len);
+        }
+    }
+  else
+    {
+      _gdk_win32_transmute_windows_data (pair->w32format, pair->contentformat, hdata, &data, &data_len);
+
+      if (data == NULL)
+        return FALSE;
+    }
+
+  stream = g_memory_input_stream_new_from_data (data, data_len, g_free);
+  g_object_set_data (stream, "gdk-clipboard-stream-contenttype", pair->contentformat);
+
+  GDK_NOTE (CLIPBOARD, g_printerr ("%s: reading clipboard data from a %lu-byte buffer\n",
+                                   data_len));
+  send_input_stream (retr->parent.item_type,
+                     retr->parent.opaque_task,
+                     stream);
+
+  return FALSE;
+}
+
+static gboolean
+process_clipboard_queue ()
+{
+  GdkWin32ClipboardThreadQueueItem *placeholder;
+  GList *p;
+  gboolean try_again;
+  GList *p_next;
+
+  for (p = clipboard_thread_data->dequeued_items, p_next = NULL; p; p = p_next)
+    {
+      placeholder = (GdkWin32ClipboardThreadQueueItem *) p->data;
+      p_next = p->next;
+
+      switch (placeholder->item_type)
+        {
+        case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_ADVERTISE:
+          try_again = process_advertise ((GdkWin32ClipboardThreadAdvertise *) placeholder);
+          break;
+        case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_RETRIEVE:
+          try_again = process_retrieve ((GdkWin32ClipboardThreadRetrieve *) placeholder);
+          break;
+        case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_STORE:
+          try_again = process_store ((GdkWin32ClipboardThreadStore *) placeholder);
+          break;
+        }
+
+      if (try_again)
+        return FALSE;
+
+      clipboard_thread_data->dequeued_items = g_list_delete_link (clipboard_thread_data->dequeued_items, p);
+      free_queue_item (placeholder);
+    }
+
+  while ((placeholder = g_async_queue_try_pop (clipboard_thread_data->input_queue)) != NULL)
+    {
+      switch (placeholder->item_type)
+        {
+        case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_ADVERTISE:
+          try_again = process_advertise ((GdkWin32ClipboardThreadAdvertise *) placeholder);
+          break;
+        case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_RETRIEVE:
+          try_again = process_retrieve ((GdkWin32ClipboardThreadRetrieve *) placeholder);
+          break;
+        case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_STORE:
+          try_again = process_store ((GdkWin32ClipboardThreadStore *) placeholder);
+          break;
+        }
+
+      if (!try_again)
+        {
+          free_queue_item (placeholder);
+          continue;
+        }
+
+      clipboard_thread_data->dequeued_items = g_list_append (clipboard_thread_data->dequeued_items, 
placeholder);
+
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+discard_render (GdkWin32ClipboardThreadRender *render,
+                gboolean                       dont_touch_the_handle)
+{
+  GdkWin32ClipboardThreadRender render_copy = *render;
+
+  g_free (render);
+
+  if (dont_touch_the_handle || render_copy.main_thread_data_handle == NULL)
+    return;
+
+  if (_gdk_win32_format_uses_hdata (render_copy.pair.w32format))
+    API_CALL (GlobalFree, (render_copy.main_thread_data_handle));
+  else
+    API_CALL (CloseHandle, (render_copy.main_thread_data_handle));
+}
+
+static LRESULT
+inner_clipboard_window_procedure (HWND   hwnd,
+                                  UINT   message,
+                                  WPARAM wparam,
+                                  LPARAM lparam)
+{
+  if (message == thread_wakeup_message ||
+      message == WM_TIMER)
+    {
+      gboolean queue_is_empty = FALSE;
+
+      if (clipboard_thread_data == NULL)
+        {
+          g_warning ("Clipboard thread got an actionable message with no thread data");
+          return DefWindowProcW (hwnd, message, wparam, lparam);
+        }
+
+      queue_is_empty = process_clipboard_queue ();
+
+      if (queue_is_empty && clipboard_thread_data->wakeup_timer)
+        {
+          API_CALL (KillTimer, (clipboard_thread_data->clipboard_window, 
clipboard_thread_data->wakeup_timer));
+          clipboard_thread_data->wakeup_timer = 0;
+        }
+
+      /* Close the clipboard after each queue run, if it's open.
+       * It would be wrong to keep it open, even if we would
+       * need it again a second later.
+       * queue_is_empty == FALSE implies that the clipboard
+       * is closed already, but it's better to be sure.
+       */
+      if (clipboard_thread_data->clipboard_opened_for != INVALID_HANDLE_VALUE)
+        {
+          API_CALL (CloseClipboard, ());
+          clipboard_thread_data->clipboard_opened_for = INVALID_HANDLE_VALUE;
+        }
+
+      if (queue_is_empty ||
+          clipboard_thread_data->wakeup_timer != 0)
+        return 0;
+
+      if (SetTimer (clipboard_thread_data->clipboard_window, 1, 1000, NULL))
+        clipboard_thread_data->wakeup_timer = 1;
+      else
+        g_critical ("Failed to set a timer for the clipboard window 0x%p: %lu",
+                    clipboard_thread_data->clipboard_window,
+                    GetLastError ());
+    }
+
+  switch (message)
+    {
+    case WM_DESTROY: /* remove us from chain */
+      {
+        if (clipboard_thread_data == NULL)
+          {
+            g_warning ("Clipboard thread got an actionable message with no thread data");
+            return DefWindowProcW (hwnd, message, wparam, lparam);
+          }
+
+        ChangeClipboardChain (hwnd, clipboard_thread_data->hwnd_next_viewer);
+        PostQuitMessage (0);
+        return 0;
+      }
+    case WM_CHANGECBCHAIN:
+      {
+        HWND hwndRemove = (HWND) wparam; /* handle of window being removed */
+        HWND hwndNext   = (HWND) lparam; /* handle of next window in chain */
+
+        if (clipboard_thread_data == NULL)
+          {
+            g_warning ("Clipboard thread got an actionable message with no thread data");
+            return DefWindowProcW (hwnd, message, wparam, lparam);
+          }
+
+        if (hwndRemove == clipboard_thread_data->hwnd_next_viewer)
+          clipboard_thread_data->hwnd_next_viewer = hwndNext == hwnd ? NULL : hwndNext;
+        else if (clipboard_thread_data->hwnd_next_viewer != NULL)
+          return SendMessage (clipboard_thread_data->hwnd_next_viewer, message, wparam, lparam);
+
+        return 0;
+      }
+    case WM_DESTROYCLIPBOARD:
+      {
+        return 0;
+      }
+    case WM_CLIPBOARDUPDATE:
+    case WM_DRAWCLIPBOARD:
+      {
+        HWND hwnd_owner;
+        HWND hwnd_opener;
+/*
+        GdkEvent *event;
+*/
+        if (clipboard_thread_data == NULL)
+          {
+            g_warning ("Clipboard thread got an actionable message with no thread data");
+            return DefWindowProcW (hwnd, message, wparam, lparam);
+          }
+
+        hwnd_owner = GetClipboardOwner ();
+
+        if ((hwnd_owner == NULL) &&
+            (GetLastError () != ERROR_SUCCESS))
+            WIN32_API_FAILED ("GetClipboardOwner");
+
+        hwnd_opener = GetOpenClipboardWindow ();
+
+        GDK_NOTE (DND, g_print (" drawclipboard owner: %p; opener %p ", hwnd_owner, hwnd_opener));
+
+#ifdef G_ENABLE_DEBUG
+        if (_gdk_debug_flags & GDK_DEBUG_DND)
+          {
+            /* FIXME: grab and print clipboard formats without opening the clipboard
+            if (clipboard_thread_data->clipboard_opened_for != INVALID_HANDLE_VALUE ||
+                OpenClipboard (hwnd))
+              {
+                UINT nFormat = 0;
+
+                while ((nFormat = EnumClipboardFormats (nFormat)) != 0)
+                  g_print ("%s ", _gdk_win32_cf_to_string (nFormat));
+
+                if (clipboard_thread_data->clipboard_opened_for == INVALID_HANDLE_VALUE)
+                  CloseClipboard ();
+              }
+            else
+              {
+                WIN32_API_FAILED ("OpenClipboard");
+              }
+             */
+          }
+#endif
+
+        GDK_NOTE (DND, g_print (" \n"));
+
+        if (clipboard_thread_data->stored_hwnd_owner != hwnd_owner)
+          {
+            clipboard_thread_data->stored_hwnd_owner = hwnd_owner;
+            clipboard_thread_data->owner_change_time = g_get_monotonic_time ();
+
+            if (hwnd_owner != clipboard_thread_data->clipboard_window)
+              {
+                if (clipboard_thread_data->cached_advertisement)
+                  g_array_free (clipboard_thread_data->cached_advertisement, TRUE);
+
+                clipboard_thread_data->cached_advertisement = NULL;
+              }
+
+            API_CALL (PostMessage, (clipboard_thread_data->clipboard_window, thread_wakeup_message, 0, 0));
+
+            if (hwnd_owner != clipboard_thread_data->clipboard_window)
+              g_idle_add_full (G_PRIORITY_DEFAULT, clipboard_owner_changed, NULL, NULL);
+          }
+
+        if (clipboard_thread_data->hwnd_next_viewer != NULL)
+          return SendMessage (clipboard_thread_data->hwnd_next_viewer, message, wparam, lparam);
+
+        /* clear error to avoid confusing SetClipboardViewer() return */
+        SetLastError (0);
+
+        return 0;
+      }
+    case WM_RENDERALLFORMATS:
+      {
+        if (clipboard_thread_data == NULL)
+          {
+            g_warning ("Clipboard thread got an actionable message with no thread data");
+            return DefWindowProcW (hwnd, message, wparam, lparam);
+          }
+
+        if (clipboard_thread_data->cached_advertisement == NULL)
+          return DefWindowProcW (hwnd, message, wparam, lparam);
+
+        if (API_CALL (OpenClipboard, (hwnd)))
+          {
+            gint i;
+            GdkWin32ContentFormatPair *pair;
+
+            for (pair = NULL, i = 0;
+                 i < clipboard_thread_data->cached_advertisement->len;
+                 i++)
+              {
+                pair = &g_array_index (clipboard_thread_data->cached_advertisement, 
GdkWin32ContentFormatPair, i);
+
+                if (pair->w32format != 0)
+                  SendMessage (hwnd, WM_RENDERFORMAT, pair->w32format, 0);
+              }
+
+            API_CALL (CloseClipboard, ());
+          }
+
+        return 0;
+      }
+    case WM_RENDERFORMAT:
+      {
+        gint i;
+        GdkWin32ClipboardThreadRender *render;
+        GdkWin32ClipboardThreadRender *returned_render;
+        GdkWin32ContentFormatPair *pair;
+
+
+        GDK_NOTE (EVENTS, g_print (" %s", _gdk_win32_cf_to_string (wparam)));
+
+        if (clipboard_thread_data == NULL)
+          {
+            g_warning ("Clipboard thread got an actionable message with no thread data");
+            return DefWindowProcW (hwnd, message, wparam, lparam);
+          }
+
+        if (clipboard_thread_data->cached_advertisement == NULL)
+          return DefWindowProcW (hwnd, message, wparam, lparam);
+
+        for (pair = NULL, i = 0;
+             i < clipboard_thread_data->cached_advertisement->len;
+             i++)
+          {
+            pair = &g_array_index (clipboard_thread_data->cached_advertisement, GdkWin32ContentFormatPair, 
i);
+
+            if (pair->w32format == wparam)
+              break;
+
+            pair = NULL;
+          }
+
+        if (pair == NULL)
+          {
+            GDK_NOTE (EVENTS, g_print (" (contentformat not found)"));
+            return DefWindowProcW (hwnd, message, wparam, lparam);
+          }
+
+        /* Clear the queue */
+        while ((render = g_async_queue_try_pop (clipboard_thread_data->render_queue)) != NULL)
+          discard_render (render, FALSE);
+
+        render = g_new0 (GdkWin32ClipboardThreadRender, 1);
+        render->pair = *pair;
+        g_idle_add_full (G_PRIORITY_DEFAULT, clipboard_render, render, NULL);
+        returned_render = g_async_queue_timeout_pop (clipboard_thread_data->render_queue, 
CLIPBOARD_RENDER_TIMEOUT);
+
+        /* We should get back the same pointer, ignore everything else. */
+        while (returned_render != NULL && returned_render != render)
+        {
+          discard_render (returned_render, FALSE);
+          /* Technically, we should use timed pop here as well,
+           * as it's *possible* for a late render struct
+           * to come down the queue just after we cleared
+           * the queue, but before our idle function
+           * triggered the actual render to be pushed.
+           * If you get many "Clipboard rendering timed out" warnings,
+           * this is probably why.
+           */
+          returned_render = g_async_queue_try_pop (clipboard_thread_data->render_queue);
+        }
+
+        /* Just in case */
+        render = NULL;
+
+        if (returned_render == NULL)
+          {
+            g_warning ("Clipboard rendering timed out");
+          }
+        else if (returned_render->main_thread_data_handle)
+          {
+            BOOL set_data_succeeded;
+            /* The requestor is holding the clipboard, no
+             * OpenClipboard() is required/possible
+             */
+            GDK_NOTE (DND,
+                      g_print (" SetClipboardData (%s, %p)",
+                               _gdk_win32_cf_to_string (wparam),
+                               returned_render->main_thread_data_handle));
+
+            SetLastError (0);
+            set_data_succeeded = (SetClipboardData (wparam, returned_render->main_thread_data_handle) != 
NULL);
+
+            if (!set_data_succeeded)
+              WIN32_API_FAILED ("SetClipboardData");
+
+            discard_render (returned_render, set_data_succeeded);
+          }
+
+        return 0;
+      }
+    default:
+      /* Otherwise call DefWindowProcW(). */
+      GDK_NOTE (EVENTS, g_print (" DefWindowProcW"));
+      return DefWindowProcW (hwnd, message, wparam, lparam);
+    }
+}
+
+LRESULT CALLBACK
+_clipboard_window_procedure (HWND   hwnd,
+                             UINT   message,
+                             WPARAM wparam,
+                             LPARAM lparam)
+{
+  LRESULT retval;
+
+  GDK_NOTE (EVENTS, g_print ("clipboard thread %s %p",
+                            _gdk_win32_message_to_string (message), hwnd));
+  retval = inner_clipboard_window_procedure (hwnd, message, wparam, lparam);
+
+  GDK_NOTE (EVENTS, g_print (" => %" G_GINT64_FORMAT "\n", (gint64) retval));
+
+  return retval;
+}
+
+/*
+ * Creates a hidden window and adds it to the clipboard chain
+ */
+static gboolean
+register_clipboard_notification ()
+{
+  WNDCLASS wclass = { 0, };
+  ATOM klass;
+
+  wclass.lpszClassName = "GdkClipboardNotification";
+  wclass.lpfnWndProc = _clipboard_window_procedure;
+  wclass.hInstance = _gdk_dll_hinstance;
+  wclass.cbWndExtra = sizeof (GdkWin32ClipboardThread *);
+
+  klass = RegisterClass (&wclass);
+  if (!klass)
+    return FALSE;
+
+  clipboard_thread_data->clipboard_window = CreateWindow (MAKEINTRESOURCE (klass),
+                                                NULL, WS_POPUP,
+                                                0, 0, 0, 0, NULL, NULL,
+                                                _gdk_dll_hinstance, NULL);
+
+  if (clipboard_thread_data->clipboard_window == NULL)
+    goto failed;
+
+  SetLastError (0);
+  clipboard_thread_data->hwnd_next_viewer = SetClipboardViewer (clipboard_thread_data->clipboard_window);
+
+  if (clipboard_thread_data->hwnd_next_viewer == NULL && GetLastError() != NO_ERROR)
+    {
+      DestroyWindow (clipboard_thread_data->clipboard_window);
+      goto failed;
+    }
+
+  g_idle_add_full (G_PRIORITY_DEFAULT, clipboard_window_created, (gpointer) 
clipboard_thread_data->clipboard_window, NULL);
+
+  /* FIXME: http://msdn.microsoft.com/en-us/library/ms649033(v=VS.85).aspx */
+  /* This is only supported by Vista, and not yet by mingw64 */
+  /* if (AddClipboardFormatListener (hwnd) == FALSE) */
+  /*   goto failed; */
+
+  return TRUE;
+
+failed:
+  g_critical ("Failed to install clipboard viewer");
+  UnregisterClass (MAKEINTRESOURCE (klass), _gdk_dll_hinstance);
+  return FALSE;
+}
+
+static gpointer
+_gdk_win32_clipboard_thread_main (gpointer data)
+{
+  MSG msg;
+  GAsyncQueue *queue = (GAsyncQueue *) data;
+  GAsyncQueue *render_queue = (GAsyncQueue *) g_async_queue_pop (queue);
+
+  g_assert (clipboard_thread_data == NULL);
+
+  clipboard_thread_data = g_new0 (GdkWin32ClipboardThread, 1);
+  clipboard_thread_data->input_queue = queue;
+  clipboard_thread_data->render_queue = render_queue;
+  clipboard_thread_data->clipboard_opened_for = INVALID_HANDLE_VALUE;
+
+  if (!register_clipboard_notification ())
+    {
+      g_async_queue_unref (queue);
+      g_clear_pointer (&clipboard_thread_data, g_free);
+
+      return NULL;
+    }
+
+  while (GetMessage (&msg, NULL, 0, 0))
+    {
+      TranslateMessage (&msg); 
+      DispatchMessage (&msg); 
+    }
+
+  /* Just in case, as this should only happen when we shut down */
+  DestroyWindow (clipboard_thread_data->clipboard_window);
+  CloseHandle (clipboard_thread_data->clipboard_window);
+  g_async_queue_unref (queue);
+  g_clear_pointer (&clipboard_thread_data, g_free);
+
+  return NULL;
+}
+
+G_DEFINE_TYPE (GdkWin32Clipdrop, gdk_win32_clipdrop, G_TYPE_OBJECT)
+
+static void
+gdk_win32_clipdrop_class_init (GdkWin32ClipdropClass *klass)
+{
+}
+
+void
+_gdk_win32_clipdrop_init (void)
+{
+  _win32_main_thread = g_thread_self ();
+  _win32_clipdrop = GDK_WIN32_CLIPDROP (g_object_new (GDK_TYPE_WIN32_CLIPDROP, NULL));
+}
+
+static void
+gdk_win32_clipdrop_init (GdkWin32Clipdrop *win32_clipdrop)
+{
+  GArray             *atoms;
+  GArray             *cfs;
+  GSList             *pixbuf_formats;
+  GSList             *rover;
+  int                 i;
+  GArray             *comp;
+  GdkWin32ContentFormatPair fmt;
+  HMODULE                   user32;
+
+  thread_wakeup_message = RegisterWindowMessage ("GDK_WORKER_THREAD_WEAKEUP");
+
+  user32 = LoadLibrary ("user32.dll");
+  win32_clipdrop->GetUpdatedClipboardFormats = GetProcAddress (user32, "GetUpdatedClipboardFormats");
+  FreeLibrary (user32);
+
+  win32_clipdrop->dnd_target_state = GDK_WIN32_DND_NONE;
+
+  atoms = g_array_sized_new (FALSE, TRUE, sizeof (GdkAtom), GDK_WIN32_ATOM_INDEX_LAST);
+  g_array_set_size (atoms, GDK_WIN32_ATOM_INDEX_LAST);
+  cfs = g_array_sized_new (FALSE, TRUE, sizeof (UINT), GDK_WIN32_CF_INDEX_LAST);
+  g_array_set_size (cfs, GDK_WIN32_CF_INDEX_LAST);
+
+  win32_clipdrop->known_atoms = atoms;
+  win32_clipdrop->known_clipboard_formats = cfs;
+
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_GDK_SELECTION) = g_intern_static_string 
("GDK_SELECTION");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CLIPBOARD_MANAGER) = g_intern_static_string 
("CLIPBOARD_MANAGER");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_WM_TRANSIENT_FOR) = g_intern_static_string 
("WM_TRANSIENT_FOR");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TARGETS) = g_intern_static_string ("TARGETS");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_DELETE) = g_intern_static_string ("DELETE");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_SAVE_TARGETS) = g_intern_static_string ("SAVE_TARGETS");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8) = g_intern_static_string 
("text/plain;charset=utf-8");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_PLAIN) = g_intern_static_string ("text/plain");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) = g_intern_static_string 
("text/uri-list");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_HTML) = g_intern_static_string ("text/html");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG) = g_intern_static_string ("image/png");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) = g_intern_static_string ("image/jpeg");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP) = g_intern_static_string ("image/bmp");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_GIF) = g_intern_static_string ("image/gif");
+
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_LOCAL_DND_SELECTION) = g_intern_static_string 
("LocalDndSelection");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_DROPFILES_DND) = g_intern_static_string 
("DROPFILES_DND");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_OLE2_DND) = g_intern_static_string ("OLE2_DND");
+
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_PNG)= g_intern_static_string ("PNG");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_JFIF) = g_intern_static_string ("JFIF");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_GIF) = g_intern_static_string ("GIF");
+
+  /* These are a bit unusual. It's here to allow GTK+ applications
+   * to actually support CF_DIB and Shell ID List clipboard formats on their own,
+   * instead of allowing GDK to use them internally for interoperability.
+   */
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_DIB) = g_intern_static_string 
("application/x.windows.CF_DIB");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CFSTR_SHELLIDLIST) = g_intern_static_string 
("application/x.windows.Shell IDList Array");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_UNICODETEXT) = g_intern_static_string 
("application/x.windows.CF_UNICODETEXT");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_TEXT) = g_intern_static_string 
("application/x.windows.CF_TEXT");
+
+  /* MS Office 2007, at least, offers images in common file formats
+   * using clipboard format names like "PNG" and "JFIF". So we follow
+   * the lead and map the GDK contentformat "image/png" to the clipboard
+   * format name "PNG" etc.
+   */
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG) = RegisterClipboardFormatA ("PNG");
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_JFIF) = RegisterClipboardFormatA ("JFIF");
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_GIF) = RegisterClipboardFormatA ("GIF");
+
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_UNIFORMRESOURCELOCATORW) = RegisterClipboardFormatA 
("UniformResourceLocatorW");
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST) = RegisterClipboardFormatA 
(CFSTR_SHELLIDLIST);
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_HTML_FORMAT) = RegisterClipboardFormatA ("HTML Format");
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_TEXT_HTML) = RegisterClipboardFormatA ("text/html");
+
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_PNG) = RegisterClipboardFormatA ("image/png");
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_JPEG) = RegisterClipboardFormatA ("image/jpeg");
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_BMP) = RegisterClipboardFormatA ("image/bmp");
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_GIF) = RegisterClipboardFormatA ("image/gif");
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_TEXT_URI_LIST) = RegisterClipboardFormatA ("text/uri-list");
+  _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_TEXT_PLAIN_UTF8) = RegisterClipboardFormatA 
("text/plain;charset=utf-8");
+
+  win32_clipdrop->active_source_drags = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) g_object_unref, 
NULL);
+
+  pixbuf_formats = gdk_pixbuf_get_formats ();
+
+  win32_clipdrop->n_known_pixbuf_formats = 0;
+  for (rover = pixbuf_formats; rover != NULL; rover = rover->next)
+    {
+      gchar **mime_types =
+       gdk_pixbuf_format_get_mime_types ((GdkPixbufFormat *) rover->data);
+      gchar **mime_type;
+
+      for (mime_type = mime_types; *mime_type != NULL; mime_type++)
+       win32_clipdrop->n_known_pixbuf_formats++;
+    }
+
+  win32_clipdrop->known_pixbuf_formats = g_new (gchar *, win32_clipdrop->n_known_pixbuf_formats);
+
+  i = 0;
+  for (rover = pixbuf_formats; rover != NULL; rover = rover->next)
+    {
+      gchar **mime_types =
+       gdk_pixbuf_format_get_mime_types ((GdkPixbufFormat *) rover->data);
+      gchar **mime_type;
+
+      for (mime_type = mime_types; *mime_type != NULL; mime_type++)
+       win32_clipdrop->known_pixbuf_formats[i++] = g_intern_string (*mime_type);
+    }
+
+  g_slist_free (pixbuf_formats);
+
+  win32_clipdrop->compatibility_w32formats = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) 
g_array_unref);
+
+  /* GTK+ actually has more text formats, but it's unlikely that we'd
+   * get anything other than UTF8_STRING these days.
+   * GTKTEXTBUFFERCONTENTS format can potentially be converted to
+   * W32-compatible rich text format, but that's too complex to address right now.
+   */
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 3);
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8);
+
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_TEXT_PLAIN_UTF8);
+  fmt.transmute = FALSE;
+  g_array_append_val (comp, fmt);
+
+  fmt.w32format = CF_UNICODETEXT;
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  fmt.w32format = CF_TEXT;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_w32formats, fmt.contentformat, comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 3);
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
+
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_PNG);
+  fmt.transmute = FALSE;
+  g_array_append_val (comp, fmt);
+
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
+  g_array_append_val (comp, fmt);
+
+  fmt.w32format = CF_DIB;
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_w32formats, fmt.contentformat, comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 4);
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_JPEG);
+
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_JPEG);
+  fmt.transmute = FALSE;
+  g_array_append_val (comp, fmt);
+
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_JFIF);
+  g_array_append_val (comp, fmt);
+
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  fmt.w32format = CF_DIB;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_w32formats, fmt.contentformat, comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 4);
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_GIF);
+
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_GIF);
+  fmt.transmute = FALSE;
+  g_array_append_val (comp, fmt);
+
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_GIF);
+  g_array_append_val (comp, fmt);
+
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  fmt.w32format = CF_DIB;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_w32formats, fmt.contentformat, comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 2);
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
+
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_BMP);
+  fmt.transmute = FALSE;
+  g_array_append_val (comp, fmt);
+
+  fmt.w32format = CF_DIB;
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_w32formats, fmt.contentformat, comp);
+
+
+/* Not implemented, but definitely possible
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 2);
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST);
+
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_TEXT_URI_LIST);
+  fmt.transmute = FALSE;
+  g_array_append_val (comp, fmt);
+
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_w32formats, fmt.contentformat, comp);
+*/
+
+  win32_clipdrop->compatibility_contentformats = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) 
g_array_unref);
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 2);
+  fmt.w32format = CF_TEXT;
+  fmt.transmute = FALSE;
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_TEXT);
+  g_array_append_val (comp, fmt);
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_contentformats, GINT_TO_POINTER (CF_TEXT), comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 2);
+  fmt.w32format = CF_UNICODETEXT;
+  fmt.transmute = FALSE;
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_UNICODETEXT);
+  g_array_append_val (comp, fmt);
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_contentformats, GINT_TO_POINTER (CF_UNICODETEXT), 
comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 3);
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
+  fmt.transmute = FALSE;
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_PNG);
+  g_array_append_val (comp, fmt);
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
+  g_array_append_val (comp, fmt);
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_contentformats, GINT_TO_POINTER (_gdk_cf_array_index 
(cfs, GDK_WIN32_CF_INDEX_PNG)), comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 4);
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_JFIF);
+  fmt.transmute = FALSE;
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_JFIF);
+  g_array_append_val (comp, fmt);
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_JPEG);
+  g_array_append_val (comp, fmt);
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_contentformats, GINT_TO_POINTER (_gdk_cf_array_index 
(cfs, GDK_WIN32_CF_INDEX_JFIF)), comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 4);
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_GIF);
+  fmt.transmute = FALSE;
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_GIF);
+  g_array_append_val (comp, fmt);
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_GIF);
+  g_array_append_val (comp, fmt);
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_contentformats, GINT_TO_POINTER (_gdk_cf_array_index 
(cfs, GDK_WIN32_CF_INDEX_GIF)), comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 3);
+  fmt.w32format = CF_DIB;
+  fmt.transmute = FALSE;
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_DIB);
+  g_array_append_val (comp, fmt);
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_contentformats, GINT_TO_POINTER (CF_DIB), comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 3);
+  fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST);
+  fmt.transmute = FALSE;
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CFSTR_SHELLIDLIST);
+  g_array_append_val (comp, fmt);
+
+  fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_clipdrop->compatibility_contentformats, GINT_TO_POINTER (_gdk_cf_array_index 
(cfs, GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST)), comp);
+
+  win32_clipdrop->clipboard_open_thread_queue = g_async_queue_new ();
+  win32_clipdrop->clipboard_render_queue = g_async_queue_new ();
+  /* Out of sheer laziness, we just push the extra queue through the
+   * main queue, instead of allocating a struct with two queue
+   * pointers and then passing *that* to the thread.
+   */
+  g_async_queue_push (win32_clipdrop->clipboard_open_thread_queue, g_async_queue_ref 
(win32_clipdrop->clipboard_render_queue));
+  win32_clipdrop->clipboard_open_thread = g_thread_new ("GDK Win32 Clipboard Thread",
+                                                        _gdk_win32_clipboard_thread_main,
+                                                        g_async_queue_ref 
(win32_clipdrop->clipboard_open_thread_queue));
+
+  win32_clipdrop->dnd_queue = g_async_queue_new ();
+  win32_clipdrop->dnd_thread = g_thread_new ("GDK Win32 DnD Thread",
+                                             _gdk_win32_dnd_thread_main,
+                                             g_async_queue_ref (win32_clipdrop->dnd_queue));
+  win32_clipdrop->dnd_thread_id = GPOINTER_TO_UINT (g_async_queue_pop (win32_clipdrop->dnd_queue));
+}
+
+void
+_gdk_dropfiles_store (gchar *data)
+{
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+
+/* FIXME: REMOVE ALL THAT STUFF
+  if (data != NULL)
+    {
+      g_assert (clipdrop->dropfiles_prop == NULL);
+
+      clipdrop->dropfiles_prop = g_new (GdkSelProp, 1);
+      clipdrop->dropfiles_prop->data = (guchar *) data;
+      clipdrop->dropfiles_prop->length = strlen (data) + 1;
+      clipdrop->dropfiles_prop->bitness = 8;
+      clipdrop->dropfiles_prop->target = _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST);
+    }
+  else
+    {
+      if (clipdrop->dropfiles_prop != NULL)
+       {
+         g_free (clipdrop->dropfiles_prop->data);
+         g_free (clipdrop->dropfiles_prop);
+       }
+      clipdrop->dropfiles_prop = NULL;
+    }
+*/
+}
+
+#define CLIPBOARD_IDLE_ABORT_TIME 30
+
+static const gchar *
+predefined_name (UINT fmt)
+{
+#define CASE(fmt) case fmt: return #fmt
+  switch (fmt)
+    {
+    CASE (CF_TEXT);
+    CASE (CF_BITMAP);
+    CASE (CF_METAFILEPICT);
+    CASE (CF_SYLK);
+    CASE (CF_DIF);
+    CASE (CF_TIFF);
+    CASE (CF_OEMTEXT);
+    CASE (CF_DIB);
+    CASE (CF_PALETTE);
+    CASE (CF_PENDATA);
+    CASE (CF_RIFF);
+    CASE (CF_WAVE);
+    CASE (CF_UNICODETEXT);
+    CASE (CF_ENHMETAFILE);
+    CASE (CF_HDROP);
+    CASE (CF_LOCALE);
+    CASE (CF_DIBV5);
+    CASE (CF_MAX);
+
+    CASE (CF_OWNERDISPLAY);
+    CASE (CF_DSPTEXT);
+    CASE (CF_DSPBITMAP);
+    CASE (CF_DSPMETAFILEPICT);
+    CASE (CF_DSPENHMETAFILE);
+#undef CASE
+    default:
+      return NULL;
+    }
+}
+
+gchar *
+_gdk_win32_get_clipboard_format_name (UINT      fmt,
+                                      gboolean *is_predefined)
+{
+  gint registered_name_w_len = 1024;
+  wchar_t *registered_name_w = g_malloc (registered_name_w_len);
+  gchar *registered_name = NULL;
+  int gcfn_result;
+  const gchar *predef = predefined_name (fmt);
+
+  /* TODO: cache the result in a hash table */
+
+  do
+    {
+      gcfn_result = GetClipboardFormatNameW (fmt, registered_name_w, registered_name_w_len);
+
+      if (gcfn_result > 0 && gcfn_result < registered_name_w_len)
+        {
+          registered_name = g_utf16_to_utf8 (registered_name_w, -1, NULL, NULL, NULL);
+          g_clear_pointer (&registered_name_w, g_free);
+          if (!registered_name)
+            gcfn_result = 0;
+          else
+            *is_predefined = FALSE;
+          break;
+        }
+
+      /* If GetClipboardFormatNameW() used up all the space, it means that
+       * we probably need a bigger buffer, but cap this at 1 kilobyte.
+       */
+      if (gcfn_result == 0 || registered_name_w_len > 1024 * 1024)
+        {
+          gcfn_result = 0;
+          g_clear_pointer (&registered_name_w, g_free);
+          break;
+        }
+
+      registered_name_w_len *= 2;
+      registered_name_w = g_realloc (registered_name_w, registered_name_w_len);
+      gcfn_result = registered_name_w_len;
+    } while (gcfn_result == registered_name_w_len);
+
+  if (registered_name == NULL &&
+      predef != NULL)
+    {
+      registered_name = g_strdup (predef);
+      *is_predefined = TRUE;
+    }
+
+  return registered_name;
+}
+
+/* This turns an arbitrary string into a string that
+ * *looks* like a mime/type, such as:
+ * "application/x.windows.FOO_BAR" from "FOO_BAR".
+ */
+const gchar *
+_gdk_win32_get_clipboard_format_name_as_interned_mimetype (gchar *w32format_name)
+{
+  gchar *mime_type;
+  const gchar *result;
+
+  mime_type = g_strdup_printf ("application/x.windows.%s", w32format_name);
+  result = g_intern_string (mime_type);
+  g_free (mime_type);
+
+  return result;
+}
+
+static GArray *
+get_compatibility_w32formats_for_contentformat (const gchar *contentformat)
+{
+  GArray *result = NULL;
+  gint i;
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+
+  result = g_hash_table_lookup (clipdrop->compatibility_w32formats, contentformat);
+
+  if (result != NULL)
+    return result;
+
+  for (i = 0; i < clipdrop->n_known_pixbuf_formats; i++)
+    {
+      if (contentformat != clipdrop->known_pixbuf_formats[i])
+        continue;
+
+      /* Any format known to gdk-pixbuf can be presented as PNG or BMP */
+      result = g_hash_table_lookup (clipdrop->compatibility_w32formats,
+                                    _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG));
+      break;
+    }
+
+  return result;
+}
+
+static GArray *
+_gdk_win32_get_compatibility_contentformats_for_w32format (UINT w32format)
+{
+  GArray *result = NULL;
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+
+  result = g_hash_table_lookup (clipdrop->compatibility_contentformats, GINT_TO_POINTER (w32format));
+
+  if (result != NULL)
+    return result;
+
+  /* TODO: reverse gdk-pixbuf conversion? We have to somehow
+   * match gdk-pixbuf format names to the corresponding clipboard
+   * format names. The former are known only at runtime,
+   * the latter are presently unknown...
+   * Maybe try to get the data and then just feed it to gdk-pixbuf,
+   * see if it knows what it is?
+   */
+
+  return result;
+}
+
+/* Turn W32 format into a GDK content format and add it
+ * to the array of W32 format <-> GDK content format pairs
+ * and/or to a list of GDK content formats.
+ * Also add compatibility GDK content formats for that W32 format.
+ * The added content format string is always interned.
+ * Ensures that duplicates are not added.
+ */
+void
+_gdk_win32_add_w32format_to_pairs (UINT     w32format,
+                                   GArray  *array,
+                                   GList  **list)
+{
+  gboolean predef;
+  gchar *w32format_name = _gdk_win32_get_clipboard_format_name (w32format, &predef);
+  const gchar *interned_w32format_name;
+  GdkWin32ContentFormatPair pair;
+  GArray *comp_pairs;
+  gint i, j;
+
+  if (w32format_name != NULL)
+    {
+      interned_w32format_name = _gdk_win32_get_clipboard_format_name_as_interned_mimetype (w32format_name);
+      GDK_NOTE (DND, g_print ("Maybe add as-is format %s (%s) (0x%p)\n", w32format_name, 
interned_w32format_name, interned_w32format_name));
+      g_free (w32format_name);
+
+      if (array && interned_w32format_name != 0)
+        {
+          for (j = 0; j < array->len; j++)
+            if (g_array_index (array, GdkWin32ContentFormatPair, j).contentformat == interned_w32format_name)
+              break;
+          if (j == array->len)
+            {
+              pair.w32format = w32format;
+              pair.contentformat = interned_w32format_name;
+              pair.transmute = FALSE;
+              g_array_append_val (array, pair);
+            }
+        }
+
+      if (list && interned_w32format_name != 0 && g_list_find (*list, interned_w32format_name) == NULL)
+        *list = g_list_prepend (*list, interned_w32format_name);
+    }
+
+  comp_pairs = _gdk_win32_get_compatibility_contentformats_for_w32format (w32format);
+
+ if (array && comp_pairs != NULL)
+   for (i = 0; i < comp_pairs->len; i++)
+     {
+       pair = g_array_index (comp_pairs, GdkWin32ContentFormatPair, i);
+
+       for (j = 0; j < array->len; j++)
+         if (g_array_index (array, GdkWin32ContentFormatPair, j).contentformat == pair.contentformat &&
+             g_array_index (array, GdkWin32ContentFormatPair, j).w32format == pair.w32format)
+           break;
+
+       if (j == array->len)
+         g_array_append_val (array, pair);
+     }
+
+ if (list && comp_pairs != NULL)
+   for (i = 0; i < comp_pairs->len; i++)
+     {
+       pair = g_array_index (comp_pairs, GdkWin32ContentFormatPair, i);
+
+       if (g_list_find (*list, pair.contentformat) == NULL)
+         *list = g_list_prepend (*list, pair.contentformat);
+     }
+}
+
+static void
+transmute_cf_unicodetext_to_utf8_string (const guchar    *data,
+                                         gint             length,
+                                         guchar         **set_data,
+                                         gsize           *set_data_length,
+                                         GDestroyNotify  *set_data_destroy)
+{
+  wchar_t *ptr, *p, *q;
+  gchar *result;
+  glong wclen, u8_len;
+
+  /* Strip out \r */
+  ptr = (wchar_t *) data;
+  p = ptr;
+  q = ptr;
+  wclen = 0;
+
+  while (p < ptr + length / 2)
+    {
+      if (*p != L'\r')
+        {
+          *q++ = *p;
+          wclen++;
+        }
+      p++;
+    }
+
+  result = g_utf16_to_utf8 (ptr, wclen, NULL, &u8_len, NULL);
+
+  if (result)
+    {
+      *set_data = (guchar *) result;
+
+      if (set_data_length)
+        *set_data_length = u8_len + 1;
+      if (set_data_destroy)
+        *set_data_destroy = (GDestroyNotify) g_free;
+    }
+}
+
+static void
+transmute_utf8_string_to_cf_unicodetext (const guchar    *data,
+                                         gint             length,
+                                         guchar         **set_data,
+                                         gsize           *set_data_length,
+                                         GDestroyNotify  *set_data_destroy)
+{
+  glong wclen;
+  GError *err = NULL;
+  glong size;
+  gint i;
+  wchar_t *wcptr, *p;
+
+  wcptr = g_utf8_to_utf16 ((char *) data, length, NULL, &wclen, &err);
+  if (err != NULL)
+    {
+      g_warning ("Failed to convert utf8: %s", err->message);
+      g_clear_error (&err);
+      return;
+    }
+
+  wclen++; /* Terminating 0 */
+  size = wclen * 2;
+  for (i = 0; i < wclen; i++)
+    if (wcptr[i] == L'\n' && (i == 0 || wcptr[i - 1] != L'\r'))
+      size += 2;
+
+  *set_data = g_malloc0 (size);
+  if (set_data_length)
+    *set_data_length = size;
+  if (set_data_destroy)
+    *set_data_destroy = (GDestroyNotify) g_free;
+
+  p = (wchar_t *) *set_data;
+
+  for (i = 0; i < wclen; i++)
+    {
+      if (wcptr[i] == L'\n' && (i == 0 || wcptr[i - 1] != L'\r'))
+       *p++ = L'\r';
+      *p++ = wcptr[i];
+    }
+
+  g_free (wcptr);
+}
+
+static int
+wchar_to_str (const wchar_t  *wstr,
+              char         **retstr,
+              UINT            cp)
+{
+  char *str;
+  int   len;
+  int   lenc;
+
+  len = WideCharToMultiByte (cp, 0, wstr, -1, NULL, 0, NULL, NULL);
+
+  if (len <= 0)
+    return -1;
+
+  str = g_malloc (sizeof (char) * len);
+
+  lenc = WideCharToMultiByte (cp, 0, wstr, -1, str, len, NULL, NULL);
+
+  if (lenc != len)
+    {
+      g_free (str);
+
+      return -3;
+    }
+
+  *retstr = str;
+
+  return 0;
+}
+
+static void
+transmute_utf8_string_to_cf_text (const guchar    *data,
+                                  gint             length,
+                                  guchar         **set_data,
+                                  gsize           *set_data_length,
+                                  GDestroyNotify  *set_data_destroy)
+{
+  glong rlen;
+  GError *err = NULL;
+  glong size;
+  gint i;
+  char *strptr, *p;
+  wchar_t *wcptr;
+
+  wcptr = g_utf8_to_utf16 ((char *) data, length, NULL, NULL, &err);
+  if (err != NULL)
+    {
+      g_warning ("Failed to convert utf8: %s", err->message);
+      g_clear_error (&err);
+      return;
+    }
+
+  if (wchar_to_str (wcptr, &strptr, CP_ACP) != 0)
+    {
+      g_warning ("Failed to convert utf-16 %S to ACP", wcptr);
+      g_free (wcptr);
+      return;
+    }
+
+  rlen = strlen (strptr);
+  g_free (wcptr);
+
+  rlen++; /* Terminating 0 */
+  size = rlen * sizeof (char);
+  for (i = 0; i < rlen; i++)
+    if (strptr[i] == '\n' && (i == 0 || strptr[i - 1] != '\r'))
+      size += sizeof (char);
+
+  *set_data = g_malloc0 (size);
+  if (set_data_length)
+    *set_data_length = size;
+  if (set_data_destroy)
+    *set_data_destroy = (GDestroyNotify) g_free;
+
+  p = (char *) *set_data;
+
+  for (i = 0; i < rlen; i++)
+    {
+      if (strptr[i] == '\n' && (i == 0 || strptr[i - 1] != '\r'))
+       *p++ = '\r';
+      *p++ = strptr[i];
+    }
+
+  g_free (strptr);
+}
+
+static int
+str_to_wchar (const char  *str,
+              wchar_t    **wretstr,
+              UINT         cp)
+{
+  wchar_t *wstr;
+  int      len;
+  int      lenc;
+
+  len = MultiByteToWideChar (cp, 0, str, -1, NULL, 0);
+
+  if (len <= 0)
+    return -1;
+
+  wstr = g_malloc (sizeof (wchar_t) * len);
+
+  lenc = MultiByteToWideChar (cp, 0, str, -1, wstr, len);
+
+  if (lenc != len)
+    {
+      g_free (wstr);
+
+      return -3;
+    }
+
+  *wretstr = wstr;
+
+  return 0;
+}
+
+static void
+transmute_cf_text_to_utf8_string (const guchar    *data,
+                                  gint             length,
+                                  guchar         **set_data,
+                                  gsize           *set_data_length,
+                                  GDestroyNotify  *set_data_destroy)
+{
+  char *ptr, *p, *q;
+  gchar *result;
+  glong wclen, u8_len;
+  wchar_t *wstr;
+
+  /* Strip out \r */
+  ptr = (gchar *) data;
+  p = ptr;
+  q = ptr;
+  wclen = 0;
+
+  while (p < ptr + length / 2)
+    {
+      if (*p != '\r')
+        {
+          *q++ = *p;
+          wclen++;
+        }
+      p++;
+    }
+
+  if (str_to_wchar (ptr, &wstr, CP_ACP) < 0)
+    return;
+
+  result = g_utf16_to_utf8 (wstr, -1, NULL, &u8_len, NULL);
+
+  if (result)
+    {
+      *set_data = (guchar *) result;
+
+      if (set_data_length)
+        *set_data_length = u8_len + 1;
+      if (set_data_destroy)
+        *set_data_destroy = (GDestroyNotify) g_free;
+    }
+
+  g_free (wstr);
+}
+
+static void
+transmute_cf_dib_to_image_bmp (const guchar    *data,
+                               gint             length,
+                               guchar         **set_data,
+                               gsize           *set_data_length,
+                               GDestroyNotify  *set_data_destroy)
+{
+  /* Need to add a BMP file header so gdk-pixbuf can load
+   * it.
+   *
+   * If the data is from Mozilla Firefox or IE7, and
+   * starts with an "old fashioned" BITMAPINFOHEADER,
+   * i.e. with biSize==40, and biCompression == BI_RGB and
+   * biBitCount==32, we assume that the "extra" byte in
+   * each pixel in fact is alpha.
+   *
+   * The gdk-pixbuf bmp loader doesn't trust 32-bit BI_RGB
+   * bitmaps to in fact have alpha, so we have to convince
+   * it by changing the bitmap header to a version 5
+   * BI_BITFIELDS one with explicit alpha mask indicated.
+   *
+   * The RGB bytes that are in bitmaps on the clipboard
+   * originating from Firefox or IE7 seem to be
+   * premultiplied with alpha. The gdk-pixbuf bmp loader
+   * of course doesn't expect that, so we have to undo the
+   * premultiplication before feeding the bitmap to the
+   * bmp loader.
+   *
+   * Note that for some reason the bmp loader used to want
+   * the alpha bytes in its input to actually be
+   * 255-alpha, but here we assume that this has been
+   * fixed before this is committed.
+   */
+  BITMAPINFOHEADER *bi = (BITMAPINFOHEADER *) data;
+  BITMAPFILEHEADER *bf;
+  gpointer result;
+  gint data_length = length;
+  gint new_length;
+  gboolean make_dibv5 = FALSE;
+  BITMAPV5HEADER *bV5;
+  guchar *p;
+  guint i;
+
+  if (bi->biSize == sizeof (BITMAPINFOHEADER) &&
+      bi->biPlanes == 1 &&
+      bi->biBitCount == 32 &&
+      bi->biCompression == BI_RGB &&
+#if 0
+      /* Maybe check explicitly for Mozilla or IE7?
+       *
+       * If the clipboard format
+       * application/x-moz-nativeimage is present, that is
+       * a reliable indicator that the data is offered by
+       * Mozilla one would think. For IE7,
+       * UniformResourceLocatorW is presumably not that
+       * uniqie, so probably need to do some
+       * GetClipboardOwner(), GetWindowThreadProcessId(),
+       * OpenProcess(), GetModuleFileNameEx() dance to
+       * check?
+       */
+      (IsClipboardFormatAvailable
+       (RegisterClipboardFormatA ("application/x-moz-nativeimage")) ||
+       IsClipboardFormatAvailable
+       (RegisterClipboardFormatA ("UniformResourceLocatorW"))) &&
+#endif
+      TRUE)
+    {
+      /* We turn the BITMAPINFOHEADER into a
+       * BITMAPV5HEADER before feeding it to gdk-pixbuf.
+       */
+      new_length = (data_length +
+                   sizeof (BITMAPFILEHEADER) +
+                   (sizeof (BITMAPV5HEADER) - sizeof (BITMAPINFOHEADER)));
+      make_dibv5 = TRUE;
+    }
+  else
+    {
+      new_length = data_length + sizeof (BITMAPFILEHEADER);
+    }
+
+  result = g_try_malloc (new_length);
+
+  if (result == NULL)
+    return;
+
+  bf = (BITMAPFILEHEADER *) result;
+  bf->bfType = 0x4d42; /* "BM" */
+  bf->bfSize = new_length;
+  bf->bfReserved1 = 0;
+  bf->bfReserved2 = 0;
+
+  *set_data = result;
+
+  if (set_data_length)
+    *set_data_length = new_length;
+  if (set_data_destroy)
+    *set_data_destroy = g_free;
+
+  if (!make_dibv5)
+    {
+      bf->bfOffBits = (sizeof (BITMAPFILEHEADER) +
+                      bi->biSize +
+                      bi->biClrUsed * sizeof (RGBQUAD));
+
+      if (bi->biCompression == BI_BITFIELDS && bi->biBitCount >= 16)
+        {
+          /* Screenshots taken with PrintScreen or
+           * Alt + PrintScreen are found on the clipboard in
+           * this format. In this case the BITMAPINFOHEADER is
+           * followed by three DWORD specifying the masks of the
+           * red green and blue components, so adjust the offset
+           * accordingly. */
+          bf->bfOffBits += (3 * sizeof (DWORD));
+        }
+
+      memcpy ((char *) result + sizeof (BITMAPFILEHEADER),
+             bi,
+             data_length);
+
+      return;
+    }
+
+  bV5 = (BITMAPV5HEADER *) ((char *) result + sizeof (BITMAPFILEHEADER));
+
+  bV5->bV5Size = sizeof (BITMAPV5HEADER);
+  bV5->bV5Width = bi->biWidth;
+  bV5->bV5Height = bi->biHeight;
+  bV5->bV5Planes = 1;
+  bV5->bV5BitCount = 32;
+  bV5->bV5Compression = BI_BITFIELDS;
+  bV5->bV5SizeImage = 4 * bV5->bV5Width * ABS (bV5->bV5Height);
+  bV5->bV5XPelsPerMeter = bi->biXPelsPerMeter;
+  bV5->bV5YPelsPerMeter = bi->biYPelsPerMeter;
+  bV5->bV5ClrUsed = 0;
+  bV5->bV5ClrImportant = 0;
+  /* Now the added mask fields */
+  bV5->bV5RedMask   = 0x00ff0000;
+  bV5->bV5GreenMask = 0x0000ff00;
+  bV5->bV5BlueMask  = 0x000000ff;
+  bV5->bV5AlphaMask = 0xff000000;
+  ((char *) &bV5->bV5CSType)[3] = 's';
+  ((char *) &bV5->bV5CSType)[2] = 'R';
+  ((char *) &bV5->bV5CSType)[1] = 'G';
+  ((char *) &bV5->bV5CSType)[0] = 'B';
+  /* Ignore colorspace and profile fields */
+  bV5->bV5Intent = LCS_GM_GRAPHICS;
+  bV5->bV5Reserved = 0;
+
+  bf->bfOffBits = (sizeof (BITMAPFILEHEADER) +
+                  bV5->bV5Size);
+
+  p = ((guchar *) result) + sizeof (BITMAPFILEHEADER) + sizeof (BITMAPV5HEADER);
+  memcpy (p, ((char *) bi) + bi->biSize,
+          data_length - sizeof (BITMAPINFOHEADER));
+
+  for (i = 0; i < bV5->bV5SizeImage/4; i++)
+    {
+      if (p[3] != 0)
+        {
+          gdouble inverse_alpha = 255./p[3];
+
+          p[0] = p[0] * inverse_alpha + 0.5;
+          p[1] = p[1] * inverse_alpha + 0.5;
+          p[2] = p[2] * inverse_alpha + 0.5;
+        }
+
+      p += 4;
+    }
+}
+
+static void
+transmute_cf_shell_id_list_to_text_uri_list (const guchar    *data,
+                                             gint             length,
+                                             guchar         **set_data,
+                                             gsize           *set_data_length,
+                                             GDestroyNotify  *set_data_destroy)
+{
+  guint i;
+  CIDA *cida = (CIDA *) data;
+  guint number_of_ids = cida->cidl;
+  GString *result = g_string_new (NULL);
+  PCIDLIST_ABSOLUTE folder_id = HIDA_GetPIDLFolder (cida);
+  wchar_t path_w[MAX_PATH + 1];
+
+  for (i = 0; i < number_of_ids; i++)
+    {
+      PCUIDLIST_RELATIVE file_id = HIDA_GetPIDLItem (cida, i);
+      PIDLIST_ABSOLUTE file_id_full = ILCombine (folder_id, file_id);
+
+      if (SHGetPathFromIDListW (file_id_full, path_w))
+        {
+          gchar *filename = g_utf16_to_utf8 (path_w, -1, NULL, NULL, NULL);
+
+          if (filename)
+            {
+              gchar *uri = g_filename_to_uri (filename, NULL, NULL);
+
+              if (uri)
+                {
+                  g_string_append (result, uri);
+                  g_string_append (result, "\r\n");
+                  g_free (uri);
+                }
+
+              g_free (filename);
+            }
+        }
+
+      ILFree (file_id_full);
+    }
+
+  *set_data = (guchar *) result->str;
+  if (set_data_length)
+    *set_data_length = result->len;
+  if (set_data_destroy)
+    *set_data_destroy = g_free;
+
+  g_string_free (result, FALSE);
+}
+
+void
+transmute_image_bmp_to_cf_dib (const guchar    *data,
+                               gint             length,
+                               guchar         **set_data,
+                               gsize           *set_data_length,
+                               GDestroyNotify  *set_data_destroy)
+{
+  gint size;
+  guchar *ptr;
+
+  g_return_if_fail (length >= sizeof (BITMAPFILEHEADER));
+
+  /* No conversion is needed, just strip the BITMAPFILEHEADER */
+  size = length - sizeof (BITMAPFILEHEADER);
+  ptr = g_malloc (size);
+
+  memcpy (ptr, data + sizeof (BITMAPFILEHEADER), size);
+
+  *set_data = ptr;
+
+  if (set_data_length)
+    *set_data_length = size;
+  if (set_data_destroy)
+    *set_data_destroy = g_free;
+}
+
+gboolean
+_gdk_win32_transmute_windows_data (UINT          from_w32format,
+                                   const gchar  *to_contentformat,
+                                   HANDLE        hdata,
+                                   guchar      **set_data,
+                                   gsize        *set_data_length)
+{
+  const guchar *data;
+  gint          length;
+
+  /* FIXME: error reporting */
+
+  if ((data = GlobalLock (hdata)) == NULL)
+    {
+      return FALSE;
+    }
+
+  length = GlobalSize (hdata);
+
+  if ((to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG) &&
+       from_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_PNG)) ||
+      (to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) &&
+       from_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_JFIF)) ||
+      (to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_GIF) &&
+       from_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_GIF)))
+    {
+      /* No transmutation needed */
+      *set_data = g_memdup (data, length);
+      *set_data_length = length;
+    }
+  else if (to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8) &&
+           from_w32format == CF_UNICODETEXT)
+    {
+      transmute_cf_unicodetext_to_utf8_string (data, length, set_data, set_data_length, NULL);
+    }
+  else if (to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8) &&
+           from_w32format == CF_TEXT)
+    {
+      transmute_cf_text_to_utf8_string (data, length, set_data, set_data_length, NULL);
+    }
+  else if (to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_BMP) &&
+           (from_w32format == CF_DIB || from_w32format == CF_DIBV5))
+    {
+      transmute_cf_dib_to_image_bmp (data, length, set_data, set_data_length, NULL);
+    }
+  else if (to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) &&
+           from_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST))
+    {
+      transmute_cf_shell_id_list_to_text_uri_list (data, length, set_data, set_data_length, NULL);
+    }
+  else
+    {
+      g_warning ("Don't know how to transmute W32 format 0x%x to content format 0x%p (%s)",
+                 from_w32format, to_contentformat, to_contentformat);
+      return FALSE;
+    }
+
+  GlobalUnlock (hdata);
+
+  return TRUE;
+}
+
+static void
+transmute_selection_format (UINT          from_format,
+                            GdkAtom       to_target,
+                            const guchar *data,
+                            gint          length,
+                            guchar      **set_data,
+                            gint         *set_data_length)
+{
+  if ((to_target == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG) &&
+       from_format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_PNG)) ||
+      (to_target == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) &&
+       from_format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_JFIF)) ||
+      (to_target == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_GIF) &&
+       from_format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_GIF)))
+    {
+      /* No transmutation needed */
+      *set_data = g_memdup (data, length);
+      *set_data_length = length;
+    }
+  else if (to_target == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8) &&
+           from_format == CF_UNICODETEXT)
+    {
+      transmute_cf_unicodetext_to_utf8_string (data, length, set_data, set_data_length, NULL);
+    }
+  else if (to_target == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8) &&
+           from_format == CF_TEXT)
+    {
+      transmute_cf_text_to_utf8_string (data, length, set_data, set_data_length, NULL);
+    }
+  else if (to_target == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_BMP) &&
+           (from_format == CF_DIB || from_format == CF_DIBV5))
+    {
+      transmute_cf_dib_to_image_bmp (data, length, set_data, set_data_length, NULL);
+    }
+  else if (to_target == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) &&
+           from_format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST))
+    {
+      transmute_cf_shell_id_list_to_text_uri_list (data, length, set_data, set_data_length, NULL);
+    }
+  else
+    {
+      g_warning ("Don't know how to transmute format 0x%x to target 0x%p", from_format, to_target);
+    }
+}
+
+gboolean
+_gdk_win32_transmute_contentformat (const gchar   *from_contentformat,
+                                    UINT           to_w32format,
+                                    const guchar  *data,
+                                    gint           length,
+                                    guchar       **set_data,
+                                    gint          *set_data_length)
+{
+  if ((from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG) &&
+       to_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_PNG)) ||
+      (from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) &&
+       to_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_JFIF)) ||
+      (from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_GIF) &&
+       to_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_GIF)))
+    {
+      /* No conversion needed */
+      *set_data = g_memdup (data, length);
+      *set_data_length = length;
+    }
+  else if (from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8) &&
+           to_w32format == CF_UNICODETEXT)
+    {
+      transmute_utf8_string_to_cf_unicodetext (data, length, set_data, set_data_length, NULL);
+    }
+  else if (from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8) &&
+           to_w32format == CF_TEXT)
+    {
+      transmute_utf8_string_to_cf_text (data, length, set_data, set_data_length, NULL);
+    }
+  else if (from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_BMP) &&
+           to_w32format == CF_DIB)
+    {
+      transmute_image_bmp_to_cf_dib (data, length, set_data, set_data_length, NULL);
+    }
+  else if (from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_BMP) &&
+           to_w32format == CF_DIBV5)
+    {
+      transmute_image_bmp_to_cf_dib (data, length, set_data, set_data_length, NULL);
+    }
+/*
+  else if (from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) &&
+           to_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST))
+    {
+      transmute_text_uri_list_to_shell_id_list (data, length, set_data, set_data_length, NULL);
+    }
+*/
+  else
+    {
+      g_warning ("Don't know how to transmute from target 0x%p to format 0x%x", from_contentformat, 
to_w32format);
+
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static GdkAtom
+convert_clipboard_selection_to_targets_target (GdkSurface *requestor)
+{
+  gint fmt;
+  int i;
+  int format_count = CountClipboardFormats ();
+  GArray *targets = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), format_count);
+/* FIXME: REMOVE ALL THAT STUFF
+  for (fmt = 0; 0 != (fmt = EnumClipboardFormats (fmt)); )
+    _gdk_win32_add_format_to_targets (fmt, targets, NULL);
+*/
+  GDK_NOTE (DND, {
+      g_print ("... ");
+      for (i = 0; i < targets->len; i++)
+        {
+          const char *atom_name = (const char *)g_array_index (targets, GdkWin32ContentFormatPair, 
i).contentformat;
+
+          g_print ("%s", atom_name);
+          if (i < targets->len - 1)
+            g_print (", ");
+        }
+      g_print ("\n");
+    });
+
+  if (targets->len > 0)
+    {
+      gint len = targets->len;
+      GdkAtom *targets_only = g_new0 (GdkAtom, len);
+
+      for (i = 0; i < targets->len; i++)
+        targets_only[i] = g_array_index (targets, GdkWin32ContentFormatPair, i).contentformat;
+
+      g_array_free (targets, TRUE);
+/* FIXME: REMOVE ALL THAT STUFF
+      selection_property_store (requestor, GDK_SELECTION_TYPE_ATOM,
+                                32, (guchar *) targets_only,
+                                len * sizeof (GdkAtom));
+*/
+      return _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION);
+    }
+  else
+    {
+      g_array_free (targets, TRUE);
+      return NULL;
+    }
+}
+
+/* It's hard to say whether implementing this actually is of any use
+ * on the Win32 platform? gtk calls only
+ * gdk_text_property_to_utf8_list_for_display().
+ */
+gint
+gdk_text_property_to_text_list_for_display (GdkDisplay   *display,
+                                           GdkAtom       encoding,
+                                           gint          format,
+                                           const guchar *text,
+                                           gint          length,
+                                           gchar      ***list)
+{
+  gchar *result;
+  const gchar *charset;
+  gchar *source_charset;
+
+  GDK_NOTE (DND, {
+      const char *enc_name = (const char *)encoding;
+
+      g_print ("gdk_text_property_to_text_list_for_display: %s %d %.20s %d\n",
+              enc_name, format, text, length);
+    });
+
+  if (!list)
+    return 0;
+
+  if (encoding == g_intern_static_string ("STRING"))
+    source_charset = g_strdup ("ISO-8859-1");
+  else if (encoding == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8))
+    source_charset = g_strdup ("UTF-8");
+  else
+    source_charset = g_strdup ((const char *)encoding);
+
+  g_get_charset (&charset);
+
+  result = g_convert ((const gchar *) text, length, charset, source_charset,
+                     NULL, NULL, NULL);
+  g_free (source_charset);
+
+  if (!result)
+    return 0;
+
+  *list = g_new (gchar *, 1);
+  **list = result;
+
+  return 1;
+}
+
+void
+gdk_free_text_list (gchar **list)
+{
+  g_return_if_fail (list != NULL);
+
+  g_free (*list);
+  g_free (list);
+}
+
+static gint
+make_list (const gchar  *text,
+          gint          length,
+          gboolean      latin1,
+          gchar      ***list)
+{
+  GSList *strings = NULL;
+  gint n_strings = 0;
+  gint i;
+  const gchar *p = text;
+  const gchar *q;
+  GSList *tmp_list;
+  GError *error = NULL;
+
+  while (p < text + length)
+    {
+      gchar *str;
+
+      q = p;
+      while (*q && q < text + length)
+       q++;
+
+      if (latin1)
+       {
+         str = g_convert (p, q - p,
+                          "UTF-8", "ISO-8859-1",
+                          NULL, NULL, &error);
+
+         if (!str)
+           {
+             g_warning ("Error converting selection from STRING: %s",
+                        error->message);
+             g_error_free (error);
+           }
+       }
+      else
+       str = g_strndup (p, q - p);
+
+      if (str)
+       {
+         strings = g_slist_prepend (strings, str);
+         n_strings++;
+       }
+
+      p = q + 1;
+    }
+
+  if (list)
+    *list = g_new (gchar *, n_strings + 1);
+
+  (*list)[n_strings] = NULL;
+
+  i = n_strings;
+  tmp_list = strings;
+  while (tmp_list)
+    {
+      if (list)
+       (*list)[--i] = tmp_list->data;
+      else
+       g_free (tmp_list->data);
+
+      tmp_list = tmp_list->next;
+    }
+
+  g_slist_free (strings);
+
+  return n_strings;
+}
+
+gint
+_gdk_win32_display_text_property_to_utf8_list (GdkDisplay    *display,
+                                              GdkAtom        encoding,
+                                              gint           format,
+                                              const guchar  *text,
+                                              gint           length,
+                                              gchar       ***list)
+{
+  g_return_val_if_fail (text != NULL, 0);
+  g_return_val_if_fail (length >= 0, 0);
+
+  if (encoding == g_intern_static_string ("STRING"))
+    {
+      return make_list ((gchar *)text, length, TRUE, list);
+    }
+  else if (encoding == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8))
+    {
+      return make_list ((gchar *)text, length, FALSE, list);
+    }
+  else
+    {
+      const char *enc_name = (const char *)encoding;
+
+      g_warning ("gdk_text_property_to_utf8_list_for_display: encoding %s not handled\n", enc_name);
+
+      if (list)
+       *list = NULL;
+
+      return 0;
+    }
+}
+
+gchar *
+_gdk_win32_display_utf8_to_string_target (GdkDisplay *display,
+                                         const gchar *str)
+{
+  return g_strdup (str);
+}
+
+gint
+_gdk_win32_add_contentformat_to_pairs (const gchar *contentformat,
+                                       GArray      *array)
+{
+  gint added_count = 0;
+  wchar_t *contentformat_w;
+  GdkWin32ContentFormatPair fmt;
+  gint i;
+  GArray *comp_pairs;
+  gint starting_point;
+  const wchar_t *prefix = L"application/x.windows.";
+  size_t prefix_len = wcslen (prefix);
+  size_t offset = 0;
+
+  for (i = 0; i < array->len; i++)
+    if (g_array_index (array, GdkWin32ContentFormatPair, i).contentformat == contentformat)
+      break;
+
+  /* Don't put duplicates into the array */
+  if (i < array->len)
+    return added_count;
+
+  /* Only check the newly-added pairs for duplicates,
+   * all the ones that exist right now have different targets.
+   */
+  starting_point = array->len;
+
+  contentformat_w = g_utf8_to_utf16 (contentformat, -1, NULL, NULL, NULL);
+
+  if (contentformat_w == NULL)
+    return added_count;
+
+  if (wcsnicmp (contentformat_w, prefix, prefix_len) == 0)
+    offset = prefix_len;
+  else
+    offset = 0;
+
+  fmt.w32format = RegisterClipboardFormatW (&contentformat_w[offset]);
+  GDK_NOTE (DND, g_print ("Registered clipboard format %S as 0x%x\n", &contentformat_w[offset], 
fmt.w32format));
+  g_free (contentformat_w);
+  fmt.contentformat = contentformat;
+  fmt.transmute = FALSE;
+
+  /* Add the "as-is" pair */
+  g_array_append_val (array, fmt);
+  added_count += 1;
+
+  comp_pairs = get_compatibility_w32formats_for_contentformat (contentformat);
+  for (i = 0; comp_pairs != NULL && i < comp_pairs->len; i++)
+    {
+      gint j;
+
+      fmt = g_array_index (comp_pairs, GdkWin32ContentFormatPair, i);
+
+      /* Don't put duplicates into the array */
+      for (j = starting_point; j < array->len; j++)
+        if (g_array_index (array, GdkWin32ContentFormatPair, j).w32format == fmt.w32format)
+          break;
+
+      if (j < array->len)
+        continue;
+
+      /* Add a compatibility pair */
+      g_array_append_val (array, fmt);
+      added_count += 1;
+    }
+
+  return added_count;
+}
+
+void
+_gdk_win32_advertise_clipboard_contentformats (GTask             *task,
+                                               GdkContentFormats *contentformats)
+{
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+  GdkWin32ClipboardThreadAdvertise *adv = g_new0 (GdkWin32ClipboardThreadAdvertise, 1);
+  const char * const *mime_types;
+  gsize mime_types_len;
+  gsize i;
+
+  g_assert (clipdrop->clipboard_window != NULL);
+
+  adv->parent.item_type = GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_ADVERTISE;
+  adv->parent.start_time = g_get_monotonic_time ();
+  adv->parent.end_time = adv->parent.start_time + CLIPBOARD_OPERATION_TIMEOUT;
+  adv->parent.opaque_task = task;
+
+  if (contentformats == NULL)
+    {
+      adv->unset = TRUE;
+    }
+  else
+    {
+      adv->unset = FALSE;
+
+      adv->pairs = g_array_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair));
+      mime_types = gdk_content_formats_get_mime_types (contentformats, &mime_types_len);
+
+      for (i = 0; i < mime_types_len; i++)
+        _gdk_win32_add_contentformat_to_pairs (mime_types[i], adv->pairs);
+    }
+
+  g_async_queue_push (clipdrop->clipboard_open_thread_queue, adv);
+  API_CALL (PostMessage, (clipdrop->clipboard_window, thread_wakeup_message, 0, 0));
+
+  return;
+}
+
+void
+_gdk_win32_retrieve_clipboard_contentformats (GTask             *task,
+                                              GdkContentFormats *contentformats)
+{
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+  GdkWin32ClipboardThreadRetrieve *retr = g_new0 (GdkWin32ClipboardThreadRetrieve, 1);
+  const char * const *mime_types;
+  gsize mime_types_len;
+  gsize i;
+
+  g_assert (clipdrop->clipboard_window != NULL);
+
+  retr->parent.item_type = GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_RETRIEVE;
+  retr->parent.start_time = g_get_monotonic_time ();
+  retr->parent.end_time = retr->parent.start_time + CLIPBOARD_OPERATION_TIMEOUT;
+  retr->parent.opaque_task = task;
+  retr->pairs = g_array_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair));
+  retr->sequence_number = GetClipboardSequenceNumber ();
+
+  mime_types = gdk_content_formats_get_mime_types (contentformats, &mime_types_len);
+
+  for (i = 0; i < mime_types_len; i++)
+    _gdk_win32_add_contentformat_to_pairs (mime_types[i], retr->pairs);
+
+  g_async_queue_push (clipdrop->clipboard_open_thread_queue, retr);
+  API_CALL (PostMessage, (clipdrop->clipboard_window, thread_wakeup_message, 0, 0));
+
+  return;
+}
+
+typedef struct _GdkWin32ClipboardHDataPrepAndStream GdkWin32ClipboardHDataPrepAndStream;
+
+struct _GdkWin32ClipboardHDataPrepAndStream
+{
+  GdkWin32ClipboardStorePrep *prep;
+  GdkWin32HDataOutputStream  *stream;
+};
+
+static void
+clipboard_store_hdata_ready (GObject      *clipboard,
+                             GAsyncResult *result,
+                             gpointer      user_data)
+{
+  GError *error = NULL;
+  gint i;
+  gboolean no_other_streams;
+  GdkWin32ClipboardHDataPrepAndStream *prep_and_stream = (GdkWin32ClipboardHDataPrepAndStream *) user_data;
+  GdkWin32ClipboardStorePrep *prep = prep_and_stream->prep;
+  GdkWin32HDataOutputStream  *stream = prep_and_stream->stream;
+  GdkWin32ClipboardThreadStore *store;
+  GdkWin32Clipdrop *clipdrop;
+
+  g_clear_pointer (&prep_and_stream, g_free);
+
+  if (!gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, &error))
+    {
+      HANDLE handle;
+      gboolean is_hdata;
+
+      GDK_NOTE(CLIPBOARD, g_printerr ("Failed to write stream: %s\n", error->message));
+      g_error_free (error);
+      for (i = 0; i < prep->elements->len; i++)
+        free_prep_element (&g_array_index (prep->elements, GdkWin32ClipboardStorePrepElement, i));
+      g_free (prep);
+      g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, NULL);
+      handle = gdk_win32_hdata_output_stream_get_handle (stream, &is_hdata);
+
+      if (is_hdata)
+        API_CALL (GlobalFree, (handle));
+      else
+        API_CALL (CloseHandle, (handle));
+
+      g_object_unref (stream);
+
+      return;
+    }
+
+  for (i = 0, no_other_streams = TRUE; i < prep->elements->len; i++)
+    {
+      GdkWin32ClipboardStorePrepElement *el = &g_array_index (prep->elements, 
GdkWin32ClipboardStorePrepElement, i);
+
+      if (el->stream == stream)
+        {
+          g_output_stream_close (G_OUTPUT_STREAM (el->stream), NULL, NULL);
+          el->handle = gdk_win32_hdata_output_stream_get_handle (el->stream, NULL);
+          g_object_unref (el->stream);
+          el->stream = NULL;
+        }
+      else if (el->stream != NULL)
+        no_other_streams = FALSE;
+    }
+
+  if (!no_other_streams)
+    return;
+
+  clipdrop = _gdk_win32_clipdrop_get ();
+
+  store = g_new0 (GdkWin32ClipboardThreadStore, 1);
+
+  store->parent.start_time = g_get_monotonic_time ();
+  store->parent.end_time = store->parent.start_time + CLIPBOARD_OPERATION_TIMEOUT;
+  store->parent.item_type = GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_STORE;
+  store->parent.opaque_task = prep->store_task;
+  store->elements = prep->elements;
+
+  g_async_queue_push (clipdrop->clipboard_open_thread_queue, store);
+  API_CALL (PostMessage, (clipdrop->clipboard_window, thread_wakeup_message, 0, 0));
+
+  g_free (prep);
+}
+
+gboolean
+_gdk_win32_store_clipboard_contentformats (GdkClipboard      *cb,
+                                           GTask             *task,
+                                           GdkContentFormats *contentformats)
+{
+  GArray *pairs; /* of GdkWin32ContentFormatPair */
+  const char * const *mime_types;
+  gint n_mime_types;
+  gint i, offset;
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+  GArray *streams;
+  GdkWin32ClipboardStorePrep *prep;
+
+  g_assert (clipdrop->clipboard_window != NULL);
+
+  mime_types = gdk_content_formats_get_mime_types (contentformats, &n_mime_types);
+
+  pairs = g_array_sized_new (FALSE,
+                             FALSE,
+                             sizeof (GdkWin32ContentFormatPair),
+                             n_mime_types);
+
+  for (i = 0; i < n_mime_types; i++)
+    _gdk_win32_add_contentformat_to_pairs (mime_types[i], pairs);
+
+  if (pairs->len <= 0)
+    {
+      g_array_free (pairs, TRUE);
+
+      return FALSE;
+    }
+
+  prep = g_new0 (GdkWin32ClipboardStorePrep, 1);
+  prep->elements = g_array_sized_new (FALSE, TRUE, sizeof (GdkWin32ClipboardStorePrepElement), pairs->len);
+  prep->store_task = task;
+
+  for (i = 0; i < pairs->len; i++)
+    {
+      GdkWin32ClipboardStorePrepElement el;
+      GdkWin32ContentFormatPair *pair = &g_array_index (pairs, GdkWin32ContentFormatPair, i);
+
+      el.stream = gdk_win32_hdata_output_stream_new (pair, NULL);
+
+      if (!el.stream)
+        continue;
+
+      el.w32format = pair->w32format;
+      el.contentformat = pair->contentformat;
+      el.handle = NULL;
+      g_array_append_val (prep->elements, el);
+    }
+
+  for (i = 0; i < prep->elements->len; i++)
+    {
+      GdkWin32ClipboardStorePrepElement *el = &g_array_index (prep->elements, 
GdkWin32ClipboardStorePrepElement, i);
+      GdkWin32ClipboardHDataPrepAndStream *prep_and_stream = g_new0 (GdkWin32ClipboardHDataPrepAndStream, 1);
+      prep_and_stream->prep = prep;
+      prep_and_stream->stream = el->stream;
+
+      gdk_clipboard_write_async (GDK_CLIPBOARD (cb),
+                                 el->contentformat,
+                                 el->stream,
+                                 G_PRIORITY_DEFAULT,
+                                 NULL,
+                                 clipboard_store_hdata_ready,
+                                 prep_and_stream);
+    }
+
+  g_array_free (pairs, TRUE);
+
+  return TRUE;
+}
diff --git a/gdk/win32/gdkclipdrop-win32.h b/gdk/win32/gdkclipdrop-win32.h
new file mode 100644
index 0000000000..570f407dc4
--- /dev/null
+++ b/gdk/win32/gdkclipdrop-win32.h
@@ -0,0 +1,278 @@
+/* GDK - The GIMP Drawing Kit
+ *
+ * gdkclipdrop-win32.h: Private Win32-specific clipboard/DnD object
+ *
+ * Copyright © 2017 LRN
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GDK_CLIPDROP_WIN32_H__
+#define __GDK_CLIPDROP_WIN32_H__
+
+G_BEGIN_DECLS
+
+#define _gdk_win32_clipdrop_get() (_win32_clipdrop)
+#define _gdk_atom_array_index(a, i) (g_array_index (a, GdkAtom, i))
+#define _gdk_win32_clipdrop_atom(i) (_gdk_atom_array_index (_gdk_win32_clipdrop_get ()->known_atoms, i))
+#define _gdk_cf_array_index(a, i) (g_array_index (a, UINT, i))
+#define _gdk_win32_clipdrop_cf(i) (_gdk_cf_array_index (_gdk_win32_clipdrop_get ()->known_clipboard_formats, 
i))
+
+/* Maps GDK contentformats to w32formats or vice versa, depending on the
+ * semantics of the array that holds these.
+ * Also remembers whether the data needs to be transmuted.
+ */
+typedef struct {
+  gint w32format;
+  /* This is assumed to be an interned string, it will be
+   * compared by simply comparing the pointer.
+   */
+  const gchar *contentformat;
+  gboolean transmute;
+} GdkWin32ContentFormatPair;
+
+/* OLE-based DND state */
+typedef enum {
+  GDK_WIN32_DND_NONE,
+  GDK_WIN32_DND_PENDING,
+  GDK_WIN32_DND_DROPPED,
+  GDK_WIN32_DND_FAILED,
+  GDK_WIN32_DND_DRAGGING,
+} GdkWin32DndState;
+
+enum _GdkWin32AtomIndex
+{
+/* GdkAtoms: properties, targets and types */
+  GDK_WIN32_ATOM_INDEX_GDK_SELECTION = 0,
+  GDK_WIN32_ATOM_INDEX_CLIPBOARD_MANAGER,
+  GDK_WIN32_ATOM_INDEX_WM_TRANSIENT_FOR,
+  GDK_WIN32_ATOM_INDEX_TARGETS,
+  GDK_WIN32_ATOM_INDEX_DELETE,
+  GDK_WIN32_ATOM_INDEX_SAVE_TARGETS,
+  GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8,
+  GDK_WIN32_ATOM_INDEX_TEXT_PLAIN,
+  GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST,
+  GDK_WIN32_ATOM_INDEX_TEXT_HTML,
+  GDK_WIN32_ATOM_INDEX_IMAGE_PNG,
+  GDK_WIN32_ATOM_INDEX_IMAGE_JPEG,
+  GDK_WIN32_ATOM_INDEX_IMAGE_BMP,
+  GDK_WIN32_ATOM_INDEX_IMAGE_GIF,
+/* DND selections */
+  GDK_WIN32_ATOM_INDEX_LOCAL_DND_SELECTION,
+  GDK_WIN32_ATOM_INDEX_DROPFILES_DND,
+  GDK_WIN32_ATOM_INDEX_OLE2_DND,
+/* Clipboard formats */
+  GDK_WIN32_ATOM_INDEX_PNG,
+  GDK_WIN32_ATOM_INDEX_JFIF,
+  GDK_WIN32_ATOM_INDEX_GIF,
+  GDK_WIN32_ATOM_INDEX_CF_DIB,
+  GDK_WIN32_ATOM_INDEX_CFSTR_SHELLIDLIST,
+  GDK_WIN32_ATOM_INDEX_CF_TEXT,
+  GDK_WIN32_ATOM_INDEX_CF_UNICODETEXT,
+  GDK_WIN32_ATOM_INDEX_LAST
+};
+
+typedef enum _GdkWin32AtomIndex GdkWin32AtomIndex;
+
+enum _GdkWin32CFIndex
+{
+  GDK_WIN32_CF_INDEX_PNG = 0,
+  GDK_WIN32_CF_INDEX_JFIF,
+  GDK_WIN32_CF_INDEX_GIF,
+  GDK_WIN32_CF_INDEX_UNIFORMRESOURCELOCATORW,
+  GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST,
+  GDK_WIN32_CF_INDEX_HTML_FORMAT,
+  GDK_WIN32_CF_INDEX_TEXT_HTML,
+  GDK_WIN32_CF_INDEX_IMAGE_PNG,
+  GDK_WIN32_CF_INDEX_IMAGE_JPEG,
+  GDK_WIN32_CF_INDEX_IMAGE_BMP,
+  GDK_WIN32_CF_INDEX_IMAGE_GIF,
+  GDK_WIN32_CF_INDEX_TEXT_URI_LIST,
+  GDK_WIN32_CF_INDEX_TEXT_PLAIN_UTF8,
+  GDK_WIN32_CF_INDEX_LAST
+};
+
+typedef enum _GdkWin32CFIndex GdkWin32CFIndex;
+
+#define GDK_TYPE_WIN32_CLIPDROP         (gdk_win32_clipdrop_get_type ())
+#define GDK_WIN32_CLIPDROP(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDK_TYPE_WIN32_CLIPDROP, 
GdkWin32Clipdrop))
+#define GDK_WIN32_CLIPDROP_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c), GDK_TYPE_WIN32_CLIPDROP, 
GdkWin32ClipdropClass))
+#define GDK_IS_WIN32_CLIPDROP(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDK_TYPE_WIN32_CLIPDROP))
+#define GDK_IS_WIN32_CLIPDROP_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c), GDK_TYPE_WIN32_CLIPDROP))
+#define GDK_WIN32_CLIPDROP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDK_TYPE_WIN32_CLIPDROP, 
GdkWin32ClipdropClass))
+
+typedef struct _GdkWin32Clipdrop GdkWin32Clipdrop;
+typedef struct _GdkWin32ClipdropClass GdkWin32ClipdropClass;
+
+typedef BOOL (WINAPI * GetUpdatedClipboardFormatsFunc)(
+  _Out_ PUINT lpuiFormats,
+  _In_  UINT  cFormats,
+  _Out_ PUINT pcFormatsOut
+);
+
+/* This object is just a sink to hold all the clipboard- and dnd-related data
+ * that otherwise would be in global variables.
+ */
+struct _GdkWin32Clipdrop
+{
+  GObject *parent_instance;
+
+  /* interned strings for well-known image formats */
+  const gchar **known_pixbuf_formats;
+  int n_known_pixbuf_formats;
+
+  /* GArray of GdkAtoms for various known Selection and DnD strings.
+   * Size is guaranteed to be at least GDK_WIN32_ATOM_INDEX_LAST
+   */
+  GArray *known_atoms;
+
+  /* GArray of UINTs for various known clipboard formats.
+   * Size is guaranteed to be at least GDK_WIN32_CF_INDEX_LAST.
+   */
+  GArray *known_clipboard_formats;
+
+  GdkWin32DndState  dnd_target_state;
+
+  /* A target-keyed hash table of GArrays of GdkWin32ContentFormatPairs describing compatibility w32formats 
for a contentformat */
+  GHashTable       *compatibility_w32formats;
+  /* A format-keyed hash table of GArrays of GdkAtoms describing compatibility contentformats for a 
w32format */
+  GHashTable       *compatibility_contentformats;
+
+  /* By all rights we should be able to just use this function
+   * normally, as our target platform is Vista-or-later.
+   * This pointer is manually retrieved only to allow
+   * GTK to be compiled with old MinGW versions, which
+   * don't have GetUpdatedClipboardFormats in the import libs.
+   */
+  GetUpdatedClipboardFormatsFunc GetUpdatedClipboardFormats;
+
+  /* The thread that tries to open the clipboard and then
+   * do stuff with it. Since clipboard opening can
+   * fail, we split the code into a thread, and let
+   * it try to open the clipboard repeatedly until
+   * the operation times out.
+   */
+  GThread          *clipboard_open_thread;
+
+  /* Our primary means of communicating with the thread.
+   * The communication is one-way only - the thread replies
+   * by just queueing functions to be called in the main
+   * thread by using g_idle_add().
+   */
+  GAsyncQueue      *clipboard_open_thread_queue;
+
+  /* We reply to clipboard render requests via this thing.
+   * We can't use messages, since the clipboard thread will
+   * stop spinning the message loop while it waits for us
+   * to render the data.
+   */
+  GAsyncQueue      *clipboard_render_queue;
+
+  /* Window handle for the clipboard window tha we
+   * receive from the clipboard thread. We use that
+   * to wake up the clipboard window main loop by
+   * posting a message to it.
+   */
+  HWND              clipboard_window;
+
+  /* The thread that calls DoDragDrop (), which would
+   * normally block our main thread, as it runs its own
+   * Windows message loop.
+   */
+  GThread          *dnd_thread;
+  DWORD             dnd_thread_id;
+
+  /* We reply to the various dnd thread requests via this thing.
+   * We can't use messages, since the dnd thread will
+   * stop spinning the message loop while it waits for us
+   * to come up with a reply.
+   */
+  GAsyncQueue      *dnd_queue;
+
+  /* This counter is atomically incremented every time
+   * the main thread pushes something into the queue,
+   * and atomically decremented every time the DnD thread
+   * pops something out of it.
+   * It can be cheaply atomically checked to see if there's
+   * anything in the queue. If there is, then the queue
+   * processing (which requires expensice locks) can happen.
+   */
+  gint              dnd_queue_counter;
+
+  /* We don't actually support multiple simultaneous drags,
+   * for obvious reasons (though this might change with
+   * the advent of multitouch support?), but there may be
+   * circumstances where we have two drag contexts at
+   * the same time (one of them will grab the cursor
+   * and thus cancel the other drag operation, but
+   * there will be a point of time when both contexts
+   * are out there). Thus we keep them around in this hash table.
+   * Key is the context object (which is safe, because the main
+   * thread keeps a reference on each one of those), value
+   * is a pointer to a GdkWin32DnDThreadDoDragDrop struct,
+   * which we can only examine when we're sure that the
+   * dnd thread is not active.
+   */
+  GHashTable       *active_source_drags;
+};
+
+struct _GdkWin32ClipdropClass
+{
+  GObjectClass parent_class;
+};
+
+GType    gdk_win32_clipdrop_get_type                               (void) G_GNUC_CONST;
+
+void     _gdk_win32_clipdrop_init                                  (void);
+
+gboolean _gdk_win32_format_uses_hdata                              (UINT               w32format);
+
+gchar  * _gdk_win32_get_clipboard_format_name                      (UINT               fmt,
+                                                                    gboolean          *is_predefined);
+void     _gdk_win32_add_w32format_to_pairs                         (UINT               format,
+                                                                    GArray            *array,
+                                                                    GList            **list);
+gint     _gdk_win32_add_contentformat_to_pairs                     (GdkAtom            target,
+                                                                    GArray            *array);
+
+void     _gdk_win32_clipboard_default_output_done                  (GObject           *clipboard,
+                                                                    GAsyncResult      *result,
+                                                                    gpointer           user_data);
+gboolean _gdk_win32_transmute_contentformat                        (const gchar       *from_contentformat,
+                                                                    UINT               to_w32format,
+                                                                    const guchar      *data,
+                                                                    gint               length,
+                                                                    guchar           **set_data,
+                                                                    gint              *set_data_length);
+
+gboolean _gdk_win32_transmute_windows_data                         (UINT          from_w32format,
+                                                                    const gchar  *to_contentformat,
+                                                                    HANDLE        hdata,
+                                                                    guchar      **set_data,
+                                                                    gsize        *set_data_length);
+
+
+gboolean _gdk_win32_store_clipboard_contentformats                 (GdkClipboard      *cb,
+                                                                    GTask             *task,
+                                                                    GdkContentFormats *contentformats);
+
+void     _gdk_win32_retrieve_clipboard_contentformats              (GTask             *task,
+                                                                    GdkContentFormats *contentformats);
+
+void     _gdk_win32_advertise_clipboard_contentformats             (GTask             *task,
+                                                                    GdkContentFormats *contentformats);
+
+
+
+#endif /* __GDK_CLIPDROP_WIN32_H__ */
diff --git a/gdk/win32/gdkdisplay-win32.c b/gdk/win32/gdkdisplay-win32.c
index 218461ebab..b8d3bff7e8 100644
--- a/gdk/win32/gdkdisplay-win32.c
+++ b/gdk/win32/gdkdisplay-win32.c
@@ -23,6 +23,8 @@
 
 #include "gdk.h"
 #include "gdkprivate-win32.h"
+#include "gdkclipboardprivate.h"
+#include "gdkclipboard-win32.h"
 #include "gdkdisplay-win32.h"
 #include "gdkdevicemanager-win32.h"
 #include "gdkglcontext-win32.h"
@@ -412,7 +414,11 @@ _gdk_win32_display_open (const gchar *display_name)
                                       NULL);
   _gdk_device_manager->display = _gdk_display;
 
-  _gdk_dnd_init ();
+  _gdk_drag_init ();
+  _gdk_drop_init ();
+
+  _gdk_display->clipboard = gdk_win32_clipboard_new (_gdk_display);
+  _gdk_display->primary_clipboard = gdk_clipboard_new (_gdk_display);
 
   /* Precalculate display name */
   (void) gdk_display_get_name (_gdk_display);
@@ -502,183 +508,6 @@ gdk_win32_display_get_default_group (GdkDisplay *display)
   return NULL;
 }
 
-static HWND _hwnd_next_viewer = NULL;
-
-/*
- * maybe this should be integrated with the default message loop - or maybe not ;-)
- */
-static LRESULT CALLBACK
-inner_clipboard_window_procedure (HWND   hwnd,
-                                  UINT   message,
-                                  WPARAM wparam,
-                                  LPARAM lparam)
-{
-  switch (message)
-    {
-    case WM_DESTROY: /* remove us from chain */
-      {
-        ChangeClipboardChain (hwnd, _hwnd_next_viewer);
-        PostQuitMessage (0);
-        return 0;
-      }
-    case WM_CHANGECBCHAIN:
-      {
-        HWND hwndRemove = (HWND) wparam; /* handle of window being removed */
-        HWND hwndNext   = (HWND) lparam; /* handle of next window in chain */
-
-        if (hwndRemove == _hwnd_next_viewer)
-          _hwnd_next_viewer = hwndNext == hwnd ? NULL : hwndNext;
-        else if (_hwnd_next_viewer != NULL)
-          return SendMessage (_hwnd_next_viewer, message, wparam, lparam);
-
-        return 0;
-      }
-    case WM_CLIPBOARDUPDATE:
-    case WM_DRAWCLIPBOARD:
-      {
-        HWND hwnd_owner;
-        HWND hwnd_opener;
-/*
-        GdkEvent *event;
-*/
-        GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
-
-        hwnd_owner = GetClipboardOwner ();
-
-        if ((hwnd_owner == NULL) &&
-            (GetLastError () != ERROR_SUCCESS))
-            WIN32_API_FAILED ("GetClipboardOwner");
-
-        hwnd_opener = GetOpenClipboardWindow ();
-
-        GDK_NOTE (DND, g_print (" drawclipboard owner: %p; opener %p ", hwnd_owner, hwnd_opener));
-
-#ifdef G_ENABLE_DEBUG
-        if (_gdk_debug_flags & GDK_DEBUG_DND)
-          {
-            if (win32_sel->clipboard_opened_for != INVALID_HANDLE_VALUE ||
-                OpenClipboard (hwnd))
-              {
-                UINT nFormat = 0;
-
-                while ((nFormat = EnumClipboardFormats (nFormat)) != 0)
-                  g_print ("%s ", _gdk_win32_cf_to_string (nFormat));
-
-                if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE)
-                  CloseClipboard ();
-              }
-            else
-              {
-                WIN32_API_FAILED ("OpenClipboard");
-              }
-          }
-#endif
-
-        GDK_NOTE (DND, g_print (" \n"));
-
-        if (win32_sel->stored_hwnd_owner != hwnd_owner)
-          {
-            if (win32_sel->clipboard_opened_for != INVALID_HANDLE_VALUE)
-              {
-                CloseClipboard ();
-                GDK_NOTE (DND, g_print ("Closed clipboard @ %s:%d\n", __FILE__, __LINE__));
-              }
-
-            win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
-            win32_sel->stored_hwnd_owner = hwnd_owner;
-
-            _gdk_win32_clear_clipboard_queue ();
-          }
-/* GDK_OWNER_CHANGE does not exist anymore since 437d70f56919916e884a81d3bff0170322ab2906
-        event = gdk_event_new (GDK_OWNER_CHANGE);
-        event->owner_change.window = NULL;
-        event->owner_change.reason = GDK_OWNER_CHANGE_NEW_OWNER;
-        event->owner_change.selection = GDK_SELECTION_CLIPBOARD;
-        event->owner_change.time = _gdk_win32_get_next_tick (0);
-        event->owner_change.selection_time = GDK_CURRENT_TIME;
-        _gdk_win32_append_event (event);
-*/
-
-        if (_hwnd_next_viewer != NULL)
-          return SendMessage (_hwnd_next_viewer, message, wparam, lparam);
-
-        /* clear error to avoid confusing SetClipboardViewer() return */
-        SetLastError (0);
-        return 0;
-      }
-    default:
-      /* Otherwise call DefWindowProcW(). */
-      GDK_NOTE (EVENTS, g_print (" DefWindowProcW"));
-      return DefWindowProc (hwnd, message, wparam, lparam);
-    }
-}
-
-static LRESULT CALLBACK
-_clipboard_window_procedure (HWND   hwnd,
-                             UINT   message,
-                             WPARAM wparam,
-                             LPARAM lparam)
-{
-  LRESULT retval;
-
-  GDK_NOTE (EVENTS, g_print ("%s%*s%s %p",
-                            (debug_indent > 0 ? "\n" : ""),
-                            debug_indent, "",
-                            _gdk_win32_message_to_string (message), hwnd));
-  debug_indent += 2;
-  retval = inner_clipboard_window_procedure (hwnd, message, wparam, lparam);
-  debug_indent -= 2;
-
-  GDK_NOTE (EVENTS, g_print (" => %" G_GINT64_FORMAT "%s", (gint64) retval, (debug_indent == 0 ? "\n" : 
"")));
-
-  return retval;
-}
-
-/*
- * Creates a hidden window and adds it to the clipboard chain
- */
-static gboolean
-register_clipboard_notification (GdkDisplay *display)
-{
-  GdkWin32Display *display_win32 = GDK_WIN32_DISPLAY (display);
-  WNDCLASS wclass = { 0, };
-  ATOM klass;
-
-  wclass.lpszClassName = "GdkClipboardNotification";
-  wclass.lpfnWndProc = _clipboard_window_procedure;
-  wclass.hInstance = _gdk_app_hmodule;
-
-  klass = RegisterClass (&wclass);
-  if (!klass)
-    return FALSE;
-
-  display_win32->clipboard_hwnd = CreateWindow (MAKEINTRESOURCE (klass),
-                                                NULL, WS_POPUP,
-                                                0, 0, 0, 0, NULL, NULL,
-                                                _gdk_app_hmodule, NULL);
-
-  if (display_win32->clipboard_hwnd == NULL)
-    goto failed;
-
-  SetLastError (0);
-  _hwnd_next_viewer = SetClipboardViewer (display_win32->clipboard_hwnd);
-
-  if (_hwnd_next_viewer == NULL && GetLastError() != 0)
-    goto failed;
-
-  /* FIXME: http://msdn.microsoft.com/en-us/library/ms649033(v=VS.85).aspx */
-  /* This is only supported by Vista, and not yet by mingw64 */
-  /* if (AddClipboardFormatListener (hwnd) == FALSE) */
-  /*   goto failed; */
-
-  return TRUE;
-
-failed:
-  g_critical ("Failed to install clipboard viewer");
-  UnregisterClass (MAKEINTRESOURCE (klass), _gdk_app_hmodule);
-  return FALSE;
-}
-
 static gboolean
 gdk_win32_display_supports_shapes (GdkDisplay *display)
 {
@@ -731,13 +560,6 @@ gdk_win32_display_dispose (GObject *object)
       display_win32->hwnd = NULL;
     }
 
-  if (display_win32->clipboard_hwnd != NULL)
-    {
-      DestroyWindow (display_win32->clipboard_hwnd);
-      display_win32->clipboard_hwnd = NULL;
-      _hwnd_next_viewer = NULL;
-    }
-
   if (display_win32->have_at_least_win81)
     {
       if (display_win32->shcore_funcs.hshcore != NULL)
diff --git a/gdk/win32/gdkdisplay-win32.h b/gdk/win32/gdkdisplay-win32.h
index 3a46f8157b..3b73760036 100644
--- a/gdk/win32/gdkdisplay-win32.h
+++ b/gdk/win32/gdkdisplay-win32.h
@@ -75,7 +75,6 @@ struct _GdkWin32Display
   int cursor_theme_size;
 
   HWND hwnd;
-  HWND clipboard_hwnd;
 
   /* WGL/OpenGL Items */
   guint have_wgl : 1;
diff --git a/gdk/win32/gdkdrag-win32.c b/gdk/win32/gdkdrag-win32.c
new file mode 100644
index 0000000000..6706bafcff
--- /dev/null
+++ b/gdk/win32/gdkdrag-win32.c
@@ -0,0 +1,2893 @@
+/* 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>
+
+/*
+ * Support for OLE-2 drag and drop added at Archaeopteryx Software, 2001
+ * For more information, do not contact Stephan R.A. Deibel (sdeibel archaeopteryx com),
+ * because the code went through multiple modifications since then.
+ *
+ * Notes on the implementation:
+ *
+ * Source drag context, IDragSource and IDataObject for it are created
+ * (almost) simultaneously, whereas target drag context and IDropTarget
+ * are separated in time - IDropTarget is created when a window is made
+ * to accept drops, while target drag context is created when a dragging
+ * cursor enters the window and is destroyed when that cursor leaves
+ * the window.
+ *
+ * There's a mismatch between data types supported by W32 (W32 formats)
+ * and by GTK+ (GDK contentformats).
+ * To account for it the data is transmuted back and forth. There are two
+ * main points of transmutation:
+ * * GdkWin32HDATAOutputStream: transmutes GTK+ data to W32 data
+ * * GdkWin32DropContext: transmutes W32 data to GTK+ data
+ *
+ * There are also two points where data formats are considered:
+ * * When source drag context is created, it gets a list of GDK contentformats
+ *   that it supports, these are matched to the W32 formats they
+ *   correspond to (possibly with transmutation). New W32 formats for
+ *   GTK+-specific contentformats are also created here (see below).
+ * * When target drop context is created, it queries the IDataObject
+ *   for the list of W32 formats it supports and matches these to
+ *   corresponding GDK contentformats that it will be able to provide
+ *   (possibly with transmutation) later. Missing GDK contentformats for
+ *   W32-specific formats are also created here (see below).
+ *
+ * W32 formats are integers (CLIPFORMAT), while GTK+ contentformats
+ * are mime/type strings, and cannot be used interchangeably.
+ *
+ * To accommodate advanced GTK+ applications the code allows them to
+ * register drop targets that accept W32 data formats, and to register
+ * drag sources that provide W32 data formats. To do that they must
+ * register with the mime/type "application/x.windows.ZZZ", where
+ * ZZZ is the string name of the format in question
+ * (for example, "Shell IDList Array") or, for unnamed pre-defined
+ * formats, register with the stringified constant name of the format
+ * in question (for example, "CF_UNICODETEXT").
+ * If such contentformat is accepted/provided, GDK will not try to
+ * transmute it to/from something else. Otherwise GDK will do the following
+ * transmutation:
+ * * If GTK+ application provides image/png, image/gif or image/jpeg,
+ *   GDK will claim to also provide "PNG", "GIF" or "JFIF" respectively,
+ *   and will pass these along verbatim.
+ * * If GTK+ application provides any GdkPixbuf-compatible contentformat,
+ *   GDK will also offer "PNG" and CF_DIB W32 formats.
+ * * If GTK+ application provides text/plain;charset=utf8, GDK will also offer
+ *   CF_UNICODETEXT (UTF-16-encoded) and CF_TEXT (encoded with thread-
+ *   and locale-depenant codepage), and will do the conversion when such
+ *   data is requested.
+ * * If GTK+ application accepts image/png, image/gif or image/jpeg,
+ *   GDK will claim to also accept "PNG", "GIF" or "JFIF" respectively,
+ *   and will pass these along verbatim.
+ * * If GTK+ application accepts image/bmp, GDK will
+ *   claim to accept CF_DIB W32 format, and will convert
+ *   it, changing the header, when such data is provided.
+ * * If GTK+ application accepts text/plain;charset=utf8, GDK will
+ *   claim to accept CF_UNICODETEXT and CF_TEXT, and will do
+ *   the conversion when such data is provided.
+ * * If GTK+ application accepts text/uri-list, GDK will
+ *   claim to accept "Shell IDList Array", and will do the
+ *   conversion when such data is provided.
+ *
+ * Currently the conversion from text/uri-list to "Shell IDList Array" is not
+ * implemented, so it's not possible to drag & drop files from GTK+
+ * applications to non-GTK+ applications the same way one can drag files
+ * from Windows Explorer.
+ *
+ * To increase inter-GTK compatibility, GDK will register GTK+-specific
+ * formats by their mime/types, as-is (i.e "text/plain;charset=utf-8", for example).
+ * That way two GTK+ applications can exchange data in their native formats
+ * (both well-known ones, such as text/plain;charset=utf8, and special,
+ * known only to specific applications). This will work just
+ * fine as long as both applications agree on what kind of data is stored
+ * under such format exactly.
+ *
+ * Note that clipboard format space is limited, there can only be 16384
+ * of them for a particular user session. Therefore it is highly inadvisable
+ * to create and register such formats out of the whole cloth, dynamically.
+ * If more flexibility is needed, register one format that has some
+ * internal indicators of the kind of data it contains, then write the application
+ * in such a way that it requests the data and inspects its header before deciding
+ * whether to accept it or not. For details see GTK+ drag & drop documentation
+ * on the "drag-motion" and "drag-data-received" signals.
+ *
+ * How managed DnD works:
+ * GTK widget detects a drag gesture and calls
+ * S: gdk_drag_begin_from_point() -> backend:drag_begin()
+ * which creates the source drag context and the drag window,
+ * and grabs the pointing device. GDK layer adds the context
+ * to a list of currently-active contexts.
+ *
+ * From that point forward the context gets any events emitted
+ * by GDK, and can prevent these events from going anywhere else.
+ * They are all handled in
+ * S: gdk_drag_context_handle_source_event() -> backend:handle_event()
+ * (except for wayland backend - it doesn't have that function).
+ *
+ * That function catches the following events:
+ * GDK_MOTION_NOTIFY
+ * GDK_BUTTON_RELEASE
+ * GDK_KEY_PRESS
+ * GDK_KEY_RELEASE
+ * GDK_GRAB_BROKEN
+ *
+ * GDK_MOTION_NOTIFY is emitted by the backend in response to
+ * mouse movement.
+ * Drag context handles it by calling a bunch of functions to
+ * determine the state of the drag actions from the keys being
+ * pressed, finding the drag window (another backend function
+ * routed through GDK layer) and finally calls
+ * S: gdk_drag_motion -> backend:drag_motion()
+ * to notify the backend (i.e. itself) that the drag cursor
+ * moved.
+ * The response to that is to move the drag window and
+ * do various bookkeeping.
+ * W32: OLE2 protocol does nothing (other than moving the
+ * drag window) in response to this, as all the functions
+ * that GDK could perform here are already handled by the
+ * OS driving the DnD process via DoDragDrop() call.
+ * The LOCAL protocol, on the other hande, does a lot,
+ * similar to what X11 backend does with XDND - it sends
+ * GDK_DRAG_LEAVE and GDK_DRAG_ENTER, emits GDK_DRAG_MOTION.
+ *
+ * GDK_BUTTON_RELEASE checks the
+ * released button - if it's the button that was used to
+ * initiate the drag, the "drop-performed" signal is emitted,
+ * otherwise the drag is cancelled.
+ *
+ * GDK_KEY_PRESS and GDK_KEY_RELEASE handler does exactly the same thing as
+ * GDK_MOTION_NOTIFY handler, but only after it checks the pressed
+ * keys to emit "drop-performed" signal (on Space, Enter etc),
+ * cancel the drag (on Escape) or move the drag cursor (arrow keys).
+ *
+ * GDK_GRAB_BROKEN handler cancels the drag for most broken grabs
+ * (except for some special cases where the backend itself does
+ *  temporary grabs as part of DnD, such as changing the cursor).
+ *
+ * GDK_DRAG_ENTER, GDK_DRAG_LEAVE, GDK_DRAG_MOTION and GDK_DROP_START
+ * events are emitted when
+ * the OS notifies the process about these things happening.
+ * For X11 backend that is done in Xdnd event filters,
+ * for W32 backend this is done in IDropSource/IDropTarget
+ * object methods for the OLE2 protocol, whereas for the
+ * LOCAL protocol these events are emitted only by GDK itself
+ * (with the exception of WM_DROPFILES message, which causes
+ *  GDK to create a drop context and then immediately finish
+ *  the drag, providing the list of files it got from the message).
+ * 
+ */
+
+/* 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
+
+/* For C-style COM wrapper macros */
+#define COBJMACROS
+
+#include "gdkdnd.h"
+#include "gdkproperty.h"
+#include "gdkinternals.h"
+#include "gdkprivate-win32.h"
+#include "gdkwin32.h"
+#include "gdkwin32dnd.h"
+#include "gdkdisplayprivate.h"
+#include "gdk/gdkdndprivate.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 "gdkintl.h"
+
+#include <gdk/gdk.h>
+#include <glib/gstdio.h>
+
+/* Just to avoid calling RegisterWindowMessage() every time */
+static UINT thread_wakeup_message;
+
+typedef struct
+{
+  IDropSource                     ids;
+  IDropSourceNotify               idsn;
+  gint                            ref_count;
+  GdkDragContext                 *context;
+
+  /* These are thread-local
+   * copies of the similar fields from GdkWin32DragContext
+   */
+  GdkWin32DragContextUtilityData  util_data;
+
+  /* Cached here, so that we don't have to look in
+   * the context every time.
+   */
+  HWND                            source_window_handle;
+  guint                           scale;
+
+  /* We get this from the OS via IDropSourceNotify and pass it to the
+   * main thread.
+   */
+  HWND                            dest_window_handle;
+} source_drag_context;
+
+typedef struct {
+  IDataObject                     ido;
+  int                             ref_count;
+  GdkDragContext                 *context;
+  GArray                         *formats;
+} data_object;
+
+typedef struct {
+  IEnumFORMATETC                  ief;
+  int                             ref_count;
+  int                             ix;
+  GArray                         *formats;
+} enum_formats;
+
+typedef enum _GdkWin32DnDThreadQueueItemType GdkWin32DnDThreadQueueItemType;
+
+enum _GdkWin32DnDThreadQueueItemType
+{
+  GDK_WIN32_DND_THREAD_QUEUE_ITEM_GIVE_FEEDBACK = 1,
+  GDK_WIN32_DND_THREAD_QUEUE_ITEM_DRAG_INFO = 2,
+  GDK_WIN32_DND_THREAD_QUEUE_ITEM_DO_DRAG_DROP = 3,
+  GDK_WIN32_DND_THREAD_QUEUE_ITEM_GET_DATA = 4,
+  GDK_WIN32_DND_THREAD_QUEUE_ITEM_UPDATE_DRAG_STATE = 5,
+};
+
+typedef struct _GdkWin32DnDThreadQueueItem GdkWin32DnDThreadQueueItem;
+
+struct _GdkWin32DnDThreadQueueItem
+{
+  GdkWin32DnDThreadQueueItemType  item_type;
+
+  /* This is used by the DnD thread to communicate the identity
+   * of the drag context to the main thread. This is thread-safe
+   * because DnD thread holds a reference to the context.
+   */
+  gpointer                        opaque_context;
+};
+
+typedef struct _GdkWin32DnDThreadDoDragDrop GdkWin32DnDThreadDoDragDrop;
+
+/* This is used both to signal the DnD thread that it needs
+ * to call DoDragDrop(), *and* to signal the main thread
+ * that the DoDragDrop() call returned.
+ */
+struct _GdkWin32DnDThreadDoDragDrop
+{
+  GdkWin32DnDThreadQueueItem  base;
+
+  source_drag_context        *src_context;
+  data_object                *src_object;
+  DWORD                       allowed_drop_effects;
+
+  DWORD                       received_drop_effect;
+  HRESULT                     received_result;
+};
+
+typedef struct _GdkWin32DnDThreadGetData GdkWin32DnDThreadGetData;
+
+/* This is used both to signal the main thread that the DnD thread
+ * needs DnD data, and to give that data to the DnD thread.
+ */
+struct _GdkWin32DnDThreadGetData
+{
+  GdkWin32DnDThreadQueueItem  base;
+
+  GdkWin32ContentFormatPair   pair;
+  GdkWin32HDataOutputStream  *stream;
+
+  STGMEDIUM                   produced_data_medium;
+};
+
+typedef struct _GdkWin32DnDThreadGiveFeedback GdkWin32DnDThreadGiveFeedback;
+
+struct _GdkWin32DnDThreadGiveFeedback
+{
+  GdkWin32DnDThreadQueueItem base;
+
+  DWORD                      received_drop_effect;
+};
+
+typedef struct _GdkWin32DnDThreadDragInfo GdkWin32DnDThreadDragInfo;
+
+struct _GdkWin32DnDThreadDragInfo
+{
+  GdkWin32DnDThreadQueueItem base;
+
+  BOOL                       received_escape_pressed;
+  DWORD                      received_keyboard_mods;
+};
+
+typedef struct _GdkWin32DnDThreadUpdateDragState GdkWin32DnDThreadUpdateDragState;
+
+struct _GdkWin32DnDThreadUpdateDragState
+{
+  GdkWin32DnDThreadQueueItem base;
+
+  gpointer                       opaque_ddd;
+  GdkWin32DragContextUtilityData produced_util_data;
+};
+
+typedef struct _GdkWin32DnDThread GdkWin32DnDThread;
+
+struct _GdkWin32DnDThread
+{
+  /* We receive instructions from the main thread in this queue */
+  GAsyncQueue *input_queue;
+
+  /* We can't peek the queue or "unpop" queue items,
+   * so the items that we can't act upon (yet) got
+   * to be stored *somewhere*.
+   */
+  GList       *dequeued_items;
+
+  source_drag_context *src_context;
+  data_object         *src_object;
+};
+
+/* The code is much more secure if we don't rely on the OS to keep
+ * this around for us.
+ */
+static GdkWin32DnDThread *dnd_thread_data = NULL;
+
+static gboolean
+dnd_queue_is_empty ()
+{
+  return g_atomic_int_get (&_win32_clipdrop->dnd_queue_counter) == 0;
+}
+
+static void
+decrement_dnd_queue_counter ()
+{
+  g_atomic_int_dec_and_test (&_win32_clipdrop->dnd_queue_counter);
+}
+
+static void
+increment_dnd_queue_counter ()
+{
+  g_atomic_int_inc (&_win32_clipdrop->dnd_queue_counter);
+}
+
+static void
+free_queue_item (GdkWin32DnDThreadQueueItem *item)
+{
+  GdkWin32DnDThreadGetData *getdata;
+
+  switch (item->item_type)
+    {
+    case GDK_WIN32_DND_THREAD_QUEUE_ITEM_DO_DRAG_DROP:
+      /* Don't unref anything, it's all done in the main thread,
+       * when it receives a DoDragDrop reply.
+       */
+      break;
+    case GDK_WIN32_DND_THREAD_QUEUE_ITEM_UPDATE_DRAG_STATE:
+    case GDK_WIN32_DND_THREAD_QUEUE_ITEM_GIVE_FEEDBACK:
+    case GDK_WIN32_DND_THREAD_QUEUE_ITEM_DRAG_INFO:
+      break;
+    case GDK_WIN32_DND_THREAD_QUEUE_ITEM_GET_DATA:
+      getdata = (GdkWin32DnDThreadGetData *) item;
+
+      switch (getdata->produced_data_medium.tymed)
+        {
+        case TYMED_FILE:
+        case TYMED_ISTREAM:
+        case TYMED_ISTORAGE:
+        case TYMED_GDI:
+        case TYMED_MFPICT:
+        case TYMED_ENHMF:
+          g_critical ("Unsupported STGMEDIUM type");
+          break;
+        case TYMED_NULL:
+          break;
+        case TYMED_HGLOBAL:
+          GlobalFree (getdata->produced_data_medium.hGlobal);
+          break;
+        }
+    }
+
+  g_free (item);
+}
+
+static gboolean
+process_dnd_queue (gboolean                   timed,
+                   guint64                    end_time,
+                   GdkWin32DnDThreadGetData  *getdata_check)
+{
+  GdkWin32DnDThreadQueueItem *item;
+  GdkWin32DnDThreadUpdateDragState *updatestate;
+  GdkWin32DnDThreadDoDragDrop *ddd;
+
+  while (TRUE)
+    {
+      if (timed)
+        {
+          guint64 current_time = g_get_monotonic_time ();
+
+          if (current_time >= end_time)
+            break;
+
+          item = g_async_queue_timeout_pop (dnd_thread_data->input_queue, end_time - current_time);
+        }
+      else
+        {
+          item = g_async_queue_try_pop (dnd_thread_data->input_queue);
+        }
+
+      if (item == NULL)
+        break;
+
+      decrement_dnd_queue_counter ();
+
+      switch (item->item_type)
+        {
+        case GDK_WIN32_DND_THREAD_QUEUE_ITEM_DO_DRAG_DROP:
+          /* We don't support more than one DnD at a time */
+          free_queue_item (item);
+          break;
+        case GDK_WIN32_DND_THREAD_QUEUE_ITEM_UPDATE_DRAG_STATE:
+          updatestate = (GdkWin32DnDThreadUpdateDragState *) item;
+          ddd = (GdkWin32DnDThreadDoDragDrop *) updatestate->opaque_ddd;
+          ddd->src_context->util_data = updatestate->produced_util_data;
+          free_queue_item (item);
+          break;
+        case GDK_WIN32_DND_THREAD_QUEUE_ITEM_GET_DATA:
+          if (item == (GdkWin32DnDThreadQueueItem *) getdata_check)
+            return TRUE;
+
+          free_queue_item (item);
+          break;
+        case GDK_WIN32_DND_THREAD_QUEUE_ITEM_GIVE_FEEDBACK:
+        case GDK_WIN32_DND_THREAD_QUEUE_ITEM_DRAG_INFO:
+          g_assert_not_reached ();
+        }
+    }
+
+  return FALSE;
+}
+
+static void
+_gdk_display_put_event (GdkDisplay *display,
+                        GdkEvent   *event)
+{
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread == g_thread_self ());
+
+  gdk_event_set_display (event, display);
+  gdk_display_put_event (display, event);
+}
+
+static gboolean
+do_drag_drop_response (gpointer user_data)
+{
+  GdkWin32DnDThreadDoDragDrop *ddd = (GdkWin32DnDThreadDoDragDrop *) user_data;
+  HRESULT hr = ddd->received_result;
+  GdkDragContext *context = GDK_DRAG_CONTEXT (ddd->base.opaque_context);
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+  gpointer table_value = g_hash_table_lookup (clipdrop->active_source_drags, context);
+  GdkEvent *tmp_event;
+
+  /* This just verifies that we got the right context,
+   * we don't need the ddd struct itself.
+   */
+  if (ddd == table_value)
+    {
+      GDK_NOTE (DND, g_print ("DoDragDrop returned %s with effect %lu\n",
+                              (hr == DRAGDROP_S_DROP ? "DRAGDROP_S_DROP" :
+                               (hr == DRAGDROP_S_CANCEL ? "DRAGDROP_S_CANCEL" :
+                                (hr == E_UNEXPECTED ? "E_UNEXPECTED" :
+                                 g_strdup_printf ("%#.8lx", hr)))), ddd->received_drop_effect));
+
+      GDK_WIN32_DRAG_CONTEXT (context)->drop_failed = !(SUCCEEDED (hr) || hr == DRAGDROP_S_DROP);
+
+      /* We used to delete the selection here,
+       * now GTK does that automatically in response to 
+       * the "dnd-finished" signal,
+       * if the operation was successful and was a move.
+       */
+      GDK_NOTE (DND, g_print ("gdk_dnd_handle_drop_finihsed: 0x%p\n",
+                              context));
+
+      g_signal_emit_by_name (context, "dnd-finished");
+      gdk_drag_drop_done (context, !(GDK_WIN32_DRAG_CONTEXT (context))->drop_failed);
+    }
+  else
+    {
+      if (!table_value)
+        g_critical ("Did not find context 0x%p in the active contexts table", context);
+      else
+        g_critical ("Found context 0x%p in the active contexts table, but the record doesn't match (0x%p != 
0x%p)", context, ddd, table_value);
+    }
+
+  /* 3rd parties could keep a reference to this object,
+   * but we won't keep the context alive that long.
+   * Neutralize it (attempts to get its data will fail)
+   * by nullifying the context pointer (it doesn't hold
+   * a reference, so no unreffing).
+   */
+  ddd->src_object->context = NULL;
+
+  IDropSource_Release (&ddd->src_context->ids);
+  IDataObject_Release (&ddd->src_object->ido);
+
+  g_hash_table_remove (clipdrop->active_source_drags, context);
+  free_queue_item (&ddd->base);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+received_drag_context_data (GObject      *context,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  GError *error = NULL;
+  GdkWin32DnDThreadGetData *getdata = (GdkWin32DnDThreadGetData *) user_data;
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+
+  if (!gdk_drag_context_write_finish (GDK_DRAG_CONTEXT (context), result, &error))
+    {
+      HANDLE handle;
+      gboolean is_hdata;
+
+      GDK_NOTE (DND, g_printerr ("%p: failed to write HData-backed stream: %s\n", context, error->message));
+      g_error_free (error);
+      g_output_stream_close (G_OUTPUT_STREAM (getdata->stream), NULL, NULL);
+      handle = gdk_win32_hdata_output_stream_get_handle (getdata->stream, &is_hdata);
+
+      if (is_hdata)
+        API_CALL (GlobalFree, (handle));
+      else
+        API_CALL (CloseHandle, (handle));
+    }
+  else
+    {
+      g_output_stream_close (G_OUTPUT_STREAM (getdata->stream), NULL, NULL);
+      getdata->produced_data_medium.tymed = TYMED_HGLOBAL;
+      getdata->produced_data_medium.hGlobal = gdk_win32_hdata_output_stream_get_handle (getdata->stream, 
NULL);
+    }
+
+  g_clear_object (&getdata->stream);
+  increment_dnd_queue_counter ();
+  g_async_queue_push (clipdrop->dnd_queue, getdata);
+  API_CALL (PostThreadMessage, (clipdrop->dnd_thread_id, thread_wakeup_message, 0, 0));
+}
+
+static gboolean
+get_data_response (gpointer user_data)
+{
+  GdkWin32DnDThreadGetData *getdata = (GdkWin32DnDThreadGetData *) user_data;
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+  GdkDragContext *context = GDK_DRAG_CONTEXT (getdata->base.opaque_context);
+  gpointer ddd = g_hash_table_lookup (clipdrop->active_source_drags, context);
+
+  GDK_NOTE (DND, g_print ("idataobject_getdata will request target 0x%p (%s)",
+                          getdata->pair.contentformat, getdata->pair.contentformat));
+
+  /* This just verifies that we got the right context,
+   * we don't need the ddd struct itself.
+   */
+  if (ddd)
+    {
+      GError *error = NULL;
+      GOutputStream *stream = gdk_win32_hdata_output_stream_new (&getdata->pair, &error);
+
+      if (stream)
+        {
+          getdata->stream = GDK_WIN32_HDATA_OUTPUT_STREAM (stream);
+          gdk_drag_context_write_async (context,
+                                        getdata->pair.contentformat,
+                                        stream,
+                                        G_PRIORITY_DEFAULT,
+                                        NULL,
+                                        received_drag_context_data,
+                                        getdata);
+
+          return G_SOURCE_REMOVE;
+        }
+    }
+
+  increment_dnd_queue_counter ();
+  g_async_queue_push (clipdrop->dnd_queue, getdata);
+  API_CALL (PostThreadMessage, (clipdrop->dnd_thread_id, thread_wakeup_message, 0, 0));
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+do_drag_drop (GdkWin32DnDThreadDoDragDrop *ddd)
+{
+  HRESULT hr;
+
+  dnd_thread_data->src_object = ddd->src_object;
+  dnd_thread_data->src_context = ddd->src_context;
+
+  hr = DoDragDrop (&dnd_thread_data->src_object->ido,
+                   &dnd_thread_data->src_context->ids,
+                   ddd->allowed_drop_effects,
+                   &ddd->received_drop_effect);
+
+  ddd->received_result = hr;
+
+  g_idle_add_full (G_PRIORITY_DEFAULT, do_drag_drop_response, ddd, NULL);
+}
+
+gpointer
+_gdk_win32_dnd_thread_main (gpointer data)
+{
+  GAsyncQueue *queue = (GAsyncQueue *) data;
+  GdkWin32DnDThreadQueueItem *item;
+  MSG msg;
+  HRESULT hr;
+
+  g_assert (dnd_thread_data == NULL);
+
+  dnd_thread_data = g_new0 (GdkWin32DnDThread, 1);
+  dnd_thread_data->input_queue = queue;
+
+  CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
+
+  hr = OleInitialize (NULL);
+
+  if (!SUCCEEDED (hr))
+    g_error ("OleInitialize failed");
+
+  /* Create a message queue */
+  PeekMessage (&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
+
+  thread_wakeup_message = RegisterWindowMessage ("GDK_WORKER_THREAD_WEAKEUP");
+
+  /* Signal the main thread that we're ready.
+   * This is the only time the queue works in reverse.
+   */
+  g_async_queue_push (queue, GUINT_TO_POINTER (GetCurrentThreadId ()));
+
+  while (GetMessage (&msg, NULL, 0, 0))
+    {
+      if (!dnd_queue_is_empty ())
+        {
+          while ((item = g_async_queue_try_pop (queue)) != NULL)
+            {
+              decrement_dnd_queue_counter ();
+
+              if (item->item_type != GDK_WIN32_DND_THREAD_QUEUE_ITEM_DO_DRAG_DROP)
+                {
+                  free_queue_item (item);
+                  continue;
+                }
+
+              do_drag_drop ((GdkWin32DnDThreadDoDragDrop *) item);
+              API_CALL (PostThreadMessage, (GetCurrentThreadId (), thread_wakeup_message, 0, 0));
+              break;
+            }
+        }
+
+      /* Just to be safe, although this mostly does nothing */
+      TranslateMessage (&msg); 
+      DispatchMessage (&msg); 
+    }
+
+  g_async_queue_unref (queue);
+  g_clear_pointer (&dnd_thread_data, g_free);
+
+  OleUninitialize ();
+  CoUninitialize ();
+
+  return NULL;
+}
+
+/* For the LOCAL protocol */
+typedef enum {
+  GDK_DRAG_STATUS_DRAG,
+  GDK_DRAG_STATUS_MOTION_WAIT,
+  GDK_DRAG_STATUS_ACTION_WAIT,
+  GDK_DRAG_STATUS_DROP
+} GdkDragStatus;
+
+static GList *local_source_contexts;
+static GdkDragContext *current_dest_drag = NULL;
+
+static gboolean use_ole2_dnd = TRUE;
+
+static gboolean drag_context_grab (GdkDragContext *context);
+
+G_DEFINE_TYPE (GdkWin32DragContext, gdk_win32_drag_context, GDK_TYPE_DRAG_CONTEXT)
+
+static void
+move_drag_surface (GdkDragContext *context,
+                   guint           x_root,
+                   guint           y_root)
+{
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread == g_thread_self ());
+
+  gdk_surface_move (context_win32->drag_surface,
+                    x_root - context_win32->hot_x,
+                    y_root - context_win32->hot_y);
+  gdk_surface_raise (context_win32->drag_surface);
+}
+
+static void
+gdk_win32_drag_context_init (GdkWin32DragContext *context)
+{
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread == g_thread_self ());
+
+  if (!use_ole2_dnd)
+    {
+      local_source_contexts = g_list_prepend (local_source_contexts, context);
+    }
+  else
+    {
+    }
+
+  GDK_NOTE (DND, g_print ("gdk_drag_context_init %p\n", context));
+}
+
+static void
+gdk_win32_drag_context_finalize (GObject *object)
+{
+  GdkDragContext *context;
+  GdkWin32DragContext *context_win32;
+  GdkSurface *drag_surface;
+
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread == g_thread_self ());
+
+  GDK_NOTE (DND, g_print ("gdk_drag_context_finalize %p\n", object));
+
+  g_return_if_fail (GDK_IS_WIN32_DRAG_CONTEXT (object));
+
+  context = GDK_DRAG_CONTEXT (object);
+  context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+
+  if (!use_ole2_dnd)
+    {
+      local_source_contexts = g_list_remove (local_source_contexts, context);
+
+      if (context == current_dest_drag)
+        current_dest_drag = NULL;
+    }
+
+  g_set_object (&context_win32->ipc_window, NULL);
+  drag_surface = context_win32->drag_surface;
+
+  G_OBJECT_CLASS (gdk_win32_drag_context_parent_class)->finalize (object);
+
+  if (drag_surface)
+    gdk_surface_destroy (drag_surface);
+}
+
+/* Drag Contexts */
+
+static GdkDragContext *
+gdk_drag_context_new (GdkDisplay         *display,
+                      GdkContentProvider *content,
+                      GdkSurface         *source_surface,
+                      GdkDragAction       actions,
+                      GdkDevice          *device,
+                      GdkDragProtocol     protocol)
+{
+  GdkWin32DragContext *context_win32;
+  GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display);
+  GdkDragContext *context;
+
+  context_win32 = g_object_new (GDK_TYPE_WIN32_DRAG_CONTEXT,
+                                "display", display,
+                                "content", content,
+                                NULL);
+
+  context = GDK_DRAG_CONTEXT (context_win32);
+
+  gdk_drag_context_set_device (context, device ? device : gdk_seat_get_pointer (gdk_display_get_default_seat 
(display)));
+
+  if (win32_display->has_fixed_scale)
+    context_win32->scale = win32_display->surface_scale;
+  else
+    context_win32->scale = _gdk_win32_display_get_monitor_scale_factor (win32_display, NULL, NULL, NULL);
+
+  context->is_source = TRUE;
+  g_set_object (&context->source_surface, source_surface);
+  context->actions = actions;
+  context_win32->protocol = protocol;
+
+  return context;
+}
+
+GdkDragContext *
+_gdk_win32_drag_context_find (GdkSurface *source,
+                              GdkSurface *dest)
+{
+  GList *tmp_list = local_source_contexts;
+  GdkDragContext *context;
+
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread == g_thread_self ());
+
+  while (tmp_list)
+    {
+      context = (GdkDragContext *)tmp_list->data;
+
+      if (context->is_source &&
+          ((source == NULL) || (context->source_surface && (context->source_surface == source))) &&
+          ((dest == NULL) || (context->dest_surface && (context->dest_surface == dest))))
+        return context;
+
+      tmp_list = tmp_list->next;
+    }
+
+  return NULL;
+}
+
+#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 enum_formats *enum_formats_new (GArray *formats);
+
+GdkDragContext *
+_gdk_win32_find_source_context_for_dest_surface (GdkSurface *dest_surface)
+{
+  GHashTableIter               iter;
+  GdkWin32DragContext         *win32_context;
+  GdkWin32DnDThreadDoDragDrop *ddd;
+  GdkWin32Clipdrop            *clipdrop = _gdk_win32_clipdrop_get ();
+
+  g_hash_table_iter_init (&iter, clipdrop->active_source_drags);
+
+  while (g_hash_table_iter_next (&iter, (gpointer *) &win32_context, (gpointer *) &ddd))
+    if (ddd->src_context->dest_window_handle == GDK_SURFACE_HWND (dest_surface))
+      return GDK_DRAG_CONTEXT (win32_context);
+
+  return NULL;
+}
+
+static GdkDragAction
+action_for_drop_effect (DWORD effect)
+{
+  GdkDragAction action = 0;
+
+  if (effect & DROPEFFECT_MOVE)
+    action |= GDK_ACTION_MOVE;
+  if (effect & DROPEFFECT_LINK)
+    action |= GDK_ACTION_LINK;
+  if (effect & DROPEFFECT_COPY)
+    action |= GDK_ACTION_COPY;
+
+  if (action == 0)
+    action = GDK_ACTION_DEFAULT;
+
+  return action;
+}
+
+static ULONG STDMETHODCALLTYPE
+idropsource_addref (LPDROPSOURCE This)
+{
+  source_drag_context *ctx = (source_drag_context *) This;
+
+  int ref_count = ++ctx->ref_count;
+
+  GDK_NOTE (DND, g_print ("idropsource_addref %p %d\n", This, ref_count));
+
+  return ref_count;
+}
+
+typedef struct _GdkWin32DnDEnterLeaveNotify GdkWin32DnDEnterLeaveNotify;
+
+struct _GdkWin32DnDEnterLeaveNotify
+{
+  gpointer opaque_context;
+  HWND     target_window_handle;
+};
+
+static gboolean
+notify_dnd_enter (gpointer user_data)
+{
+  GdkWin32DnDEnterLeaveNotify *notify = (GdkWin32DnDEnterLeaveNotify *) user_data;
+  GdkDragContext *context = GDK_DRAG_CONTEXT (notify->opaque_context);
+  GdkSurface *dest_surface, *dw;
+
+  dw = gdk_win32_handle_table_lookup (notify->target_window_handle);
+
+  if (dw)
+    dest_surface = g_object_ref (dw);
+  else
+    dest_surface = gdk_win32_surface_foreign_new_for_display (context->display, 
notify->target_window_handle);
+
+  g_clear_object (&context->dest_surface);
+  context->dest_surface = dest_surface;
+
+  g_free (notify);
+
+  return G_SOURCE_REMOVE;
+}
+
+static gboolean
+notify_dnd_leave (gpointer user_data)
+{
+  GdkWin32DnDEnterLeaveNotify *notify = (GdkWin32DnDEnterLeaveNotify *) user_data;
+  GdkDragContext *context = GDK_DRAG_CONTEXT (notify->opaque_context);
+  GdkSurface *dest_surface, *dw;
+
+  dw = gdk_win32_handle_table_lookup (notify->target_window_handle);
+
+  if (dw)
+    {
+      dest_surface = gdk_surface_get_toplevel (dw);
+
+      if (dest_surface == context->dest_surface)
+        g_clear_object (&context->dest_surface);
+      else
+        g_warning ("Destination window for handle 0x%p is 0x%p, but context has 0x%p", 
notify->target_window_handle, dest_surface, context->dest_surface);
+    }
+  else
+    g_warning ("Failed to find destination window for handle 0x%p", notify->target_window_handle);
+
+  g_free (notify);
+
+  return G_SOURCE_REMOVE;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idropsourcenotify_dragentertarget (IDropSourceNotify *This,
+                                   HWND               hwndTarget)
+{
+  source_drag_context *ctx = (source_drag_context *) (((char *) This) - G_STRUCT_OFFSET 
(source_drag_context, idsn));
+  GdkWin32DnDEnterLeaveNotify *notify;
+
+  if (!dnd_queue_is_empty ())
+    process_dnd_queue (FALSE, 0, NULL);
+
+  GDK_NOTE (DND, g_print ("idropsourcenotify_dragentertarget %p (SDC %p) 0x%p\n", This, ctx, hwndTarget));
+
+  ctx->dest_window_handle = hwndTarget;
+
+  notify = g_new0 (GdkWin32DnDEnterLeaveNotify, 1);
+  notify->target_window_handle = hwndTarget;
+  notify->opaque_context = ctx->context;
+  g_idle_add_full (G_PRIORITY_DEFAULT, notify_dnd_enter, notify, NULL);
+
+  return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idropsourcenotify_dragleavetarget (IDropSourceNotify *This)
+{
+  source_drag_context *ctx = (source_drag_context *) (((char *) This) - G_STRUCT_OFFSET 
(source_drag_context, idsn));
+  GdkWin32DnDEnterLeaveNotify *notify;
+
+  if (!dnd_queue_is_empty ())
+    process_dnd_queue (FALSE, 0, NULL);
+
+  GDK_NOTE (DND, g_print ("idropsourcenotify_dragleavetarget %p (SDC %p) 0x%p\n", This, ctx, 
ctx->dest_window_handle));
+
+  notify = g_new0 (GdkWin32DnDEnterLeaveNotify, 1);
+  notify->target_window_handle = ctx->dest_window_handle;
+  ctx->dest_window_handle = NULL;
+  notify->opaque_context = ctx->context;
+  g_idle_add_full (G_PRIORITY_DEFAULT, notify_dnd_leave, notify, NULL);
+
+  return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idropsource_queryinterface (LPDROPSOURCE This,
+                            REFIID       riid,
+                            LPVOID      *ppvObject)
+{
+  GDK_NOTE (DND, {
+      g_print ("idropsource_queryinterface %p ", This);
+      PRINT_GUID (riid);
+    });
+
+  *ppvObject = NULL;
+
+  if (IsEqualGUID (riid, &IID_IUnknown))
+    {
+      GDK_NOTE (DND, g_print ("...IUnknown S_OK\n"));
+      idropsource_addref (This);
+      *ppvObject = This;
+      return S_OK;
+    }
+  else if (IsEqualGUID (riid, &IID_IDropSource))
+    {
+      GDK_NOTE (DND, g_print ("...IDropSource S_OK\n"));
+      idropsource_addref (This);
+      *ppvObject = This;
+      return S_OK;
+    }
+  else if (IsEqualGUID (riid, &IID_IDropSourceNotify))
+    {
+      GDK_NOTE (DND, g_print ("...IDropSourceNotify S_OK\n"));
+      idropsource_addref (This);
+      *ppvObject = &((source_drag_context *) This)->idsn;
+      return S_OK;
+    }
+  else
+    {
+      GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n"));
+      return E_NOINTERFACE;
+    }
+}
+
+static gboolean
+unref_context_in_main_thread (gpointer opaque_context)
+{
+  GdkDragContext *context = GDK_DRAG_CONTEXT (opaque_context);
+
+  g_clear_object (&context);
+
+  return G_SOURCE_REMOVE;
+}
+
+static ULONG STDMETHODCALLTYPE
+idropsource_release (LPDROPSOURCE This)
+{
+  source_drag_context *ctx = (source_drag_context *) This;
+
+  int ref_count = --ctx->ref_count;
+
+  GDK_NOTE (DND, g_print ("idropsource_release %p %d\n", This, ref_count));
+
+  if (ref_count == 0)
+  {
+    g_idle_add (unref_context_in_main_thread, ctx->context);
+    g_free (This);
+  }
+
+  return ref_count;
+}
+
+/* NOTE: This method is called continuously, even if nothing is
+ * happening, as long as the drag operation is in progress.
+ * It is OK to return a "safe" value (S_OK, to keep the drag
+ * operation going) even if something notable happens, because
+ * we will have another opportunity to return the "right" value
+ * (once we know what it is, after GTK processes the events we
+ *  send out) very soon.
+ * Note that keyboard-related state in this function is nonsense,
+ * as DoDragDrop doesn't get precise information about the keyboard,
+ * especially the fEscapePressed argument.
+ */
+static HRESULT STDMETHODCALLTYPE
+idropsource_querycontinuedrag (LPDROPSOURCE This,
+                               BOOL         fEscapePressed,
+                               DWORD        grfKeyState)
+{
+  source_drag_context *ctx = (source_drag_context *) This;
+
+  GDK_NOTE (DND, g_print ("idropsource_querycontinuedrag %p esc=%d keystate=0x%lx with state %d", This, 
fEscapePressed, grfKeyState, ctx->util_data.state));
+
+  if (!dnd_queue_is_empty ())
+    process_dnd_queue (FALSE, 0, NULL);
+
+  if (ctx->util_data.state == GDK_WIN32_DND_DROPPED)
+    {
+      GDK_NOTE (DND, g_print ("DRAGDROP_S_DROP\n"));
+      return DRAGDROP_S_DROP;
+    }
+  else if (ctx->util_data.state == GDK_WIN32_DND_NONE)
+    {
+      GDK_NOTE (DND, g_print ("DRAGDROP_S_CANCEL\n"));
+      return DRAGDROP_S_CANCEL;
+    }
+  else
+    {
+      GDK_NOTE (DND, g_print ("S_OK\n"));
+      return S_OK;
+    }
+}
+
+static gboolean
+give_feedback (gpointer user_data)
+{
+  GdkWin32DnDThreadGiveFeedback *feedback = (GdkWin32DnDThreadGiveFeedback *) user_data;
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+  gpointer ddd = g_hash_table_lookup (clipdrop->active_source_drags, feedback->base.opaque_context);
+
+  if (ddd)
+    {
+      GdkDragContext *context = GDK_DRAG_CONTEXT (feedback->base.opaque_context);
+      GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
+
+      GDK_NOTE (DND, g_print ("gdk_dnd_handle_drag_status: 0x%p\n",
+                              context));
+
+      context->action = action_for_drop_effect (feedback->received_drop_effect);
+
+      if (context->action != win32_context->current_action)
+        {
+          win32_context->current_action = context->action;
+          g_signal_emit_by_name (context, "action-changed", context->action);
+        }
+    }
+
+  free_queue_item (&feedback->base);
+
+  return G_SOURCE_REMOVE;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idropsource_givefeedback (LPDROPSOURCE This,
+                          DWORD        dwEffect)
+{
+  source_drag_context *ctx = (source_drag_context *) This;
+  POINT pt;
+  GdkWin32DnDThreadGiveFeedback *feedback;
+
+  GDK_NOTE (DND, g_print ("idropsource_givefeedback %p with drop effect %lu S_OK\n", This, dwEffect));
+
+  if (!dnd_queue_is_empty ())
+    process_dnd_queue (FALSE, 0, NULL);
+
+  feedback = g_new0 (GdkWin32DnDThreadGiveFeedback, 1);
+  feedback->base.opaque_context = ctx->context;
+  feedback->received_drop_effect = dwEffect;
+
+  g_idle_add_full (G_PRIORITY_DEFAULT, give_feedback, feedback, NULL);
+
+  GDK_NOTE (DND, g_print ("idropsource_givefeedback %p returns\n", This));
+
+  return S_OK;
+}
+
+static ULONG STDMETHODCALLTYPE
+idataobject_addref (LPDATAOBJECT This)
+{
+  data_object *dobj = (data_object *) This;
+  int ref_count = ++dobj->ref_count;
+
+  GDK_NOTE (DND, g_print ("idataobject_addref %p %d\n", This, ref_count));
+
+  return ref_count;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idataobject_queryinterface (LPDATAOBJECT This,
+                            REFIID       riid,
+                            LPVOID      *ppvObject)
+{
+  GDK_NOTE (DND, {
+      g_print ("idataobject_queryinterface %p ", This);
+      PRINT_GUID (riid);
+    });
+
+  *ppvObject = NULL;
+
+  if (IsEqualGUID (riid, &IID_IUnknown))
+    {
+      GDK_NOTE (DND, g_print ("...IUnknown S_OK\n"));
+      idataobject_addref (This);
+      *ppvObject = This;
+      return S_OK;
+    }
+  else if (IsEqualGUID (riid, &IID_IDataObject))
+    {
+      GDK_NOTE (DND, g_print ("...IDataObject S_OK\n"));
+      idataobject_addref (This);
+      *ppvObject = This;
+      return S_OK;
+    }
+  else
+    {
+      GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n"));
+      return E_NOINTERFACE;
+    }
+}
+
+static ULONG STDMETHODCALLTYPE
+idataobject_release (LPDATAOBJECT This)
+{
+  data_object *dobj = (data_object *) This;
+  int ref_count = --dobj->ref_count;
+
+  GDK_NOTE (DND, g_print ("idataobject_release %p %d\n", This, ref_count));
+
+  if (ref_count == 0)
+    {
+      g_array_free (dobj->formats, TRUE);
+      g_free (This);
+    }
+
+  return ref_count;
+}
+
+static HRESULT
+query (LPDATAOBJECT                This,
+       LPFORMATETC                 pFormatEtc,
+       GdkWin32ContentFormatPair **pair)
+{
+  data_object *ctx = (data_object *) This;
+  gint i;
+
+  if (pair)
+    *pair = NULL;
+
+  if (!pFormatEtc)
+    return DV_E_FORMATETC;
+
+  if (pFormatEtc->lindex != -1)
+    return DV_E_LINDEX;
+
+  if ((pFormatEtc->tymed & TYMED_HGLOBAL) == 0)
+    return DV_E_TYMED;
+
+  if ((pFormatEtc->dwAspect & DVASPECT_CONTENT) == 0)
+    return DV_E_DVASPECT;
+
+  for (i = 0; i < ctx->formats->len; i++)
+    {
+      GdkWin32ContentFormatPair *p = &g_array_index (ctx->formats, GdkWin32ContentFormatPair, i);
+      if (pFormatEtc->cfFormat == p->w32format)
+        {
+          if (pair)
+            *pair = p;
+
+          return S_OK;
+        }
+    }
+
+  return DV_E_FORMATETC;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idataobject_getdata (LPDATAOBJECT This,
+                     LPFORMATETC  pFormatEtc,
+                     LPSTGMEDIUM  pMedium)
+{
+  data_object *ctx = (data_object *) This;
+  HRESULT hr;
+  GdkWin32DnDThreadGetData *getdata;
+  GdkWin32ContentFormatPair *pair;
+
+  if (ctx->context == NULL)
+    return E_FAIL;
+
+  GDK_NOTE (DND, g_print ("idataobject_getdata %p %s ",
+                          This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat)));
+
+  /* Check whether we can provide requested format */
+  hr = query (This, pFormatEtc, &pair);
+
+  if (hr != S_OK)
+    {
+      GDK_NOTE (DND, g_print ("Unsupported format, returning 0x%lx\n", hr));
+      return hr;
+    }
+
+  if (!dnd_queue_is_empty ())
+    process_dnd_queue (FALSE, 0, NULL);
+
+  getdata = g_new0 (GdkWin32DnDThreadGetData, 1);
+  getdata->base.item_type = GDK_WIN32_DND_THREAD_QUEUE_ITEM_GET_DATA;
+  getdata->base.opaque_context = (gpointer) ctx->context;
+  getdata->pair = *pair;
+  g_idle_add_full (G_PRIORITY_DEFAULT, get_data_response, getdata, NULL);
+
+  if (!process_dnd_queue (TRUE, g_get_monotonic_time () + G_USEC_PER_SEC * 30, getdata))
+    return E_FAIL;
+
+  if (getdata->produced_data_medium.tymed == TYMED_NULL)
+    {
+      free_queue_item (&getdata->base);
+
+      return E_FAIL;
+    }
+
+  memcpy (pMedium, &getdata->produced_data_medium, sizeof (*pMedium));
+
+  /* To ensure that the data isn't freed */
+  getdata->produced_data_medium.tymed = TYMED_NULL;
+
+  free_queue_item (&getdata->base);
+
+  return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idataobject_getdatahere (LPDATAOBJECT This,
+                         LPFORMATETC  pFormatEtc,
+                         LPSTGMEDIUM  pMedium)
+{
+  GDK_NOTE (DND, g_print ("idataobject_getdatahere %p E_NOTIMPL\n", This));
+
+  return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idataobject_querygetdata (LPDATAOBJECT This,
+                          LPFORMATETC  pFormatEtc)
+{
+  HRESULT hr;
+
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread != g_thread_self ());
+
+  hr = query (This, pFormatEtc, NULL);
+
+  GDK_NOTE (DND,
+      g_print ("idataobject_querygetdata %p 0x%08x fmt, %p ptd, %lu aspect, %ld lindex, %0lx tymed - %s, 
return %#lx (%s)\n",
+               This, pFormatEtc->cfFormat, pFormatEtc->ptd, pFormatEtc->dwAspect, pFormatEtc->lindex, 
pFormatEtc->tymed, _gdk_win32_cf_to_string (pFormatEtc->cfFormat),
+               hr, (hr == S_OK) ? "S_OK" : (hr == DV_E_FORMATETC) ? "DV_E_FORMATETC" : (hr == DV_E_LINDEX) ? 
"DV_E_LINDEX" : (hr == DV_E_TYMED) ? "DV_E_TYMED" : (hr == DV_E_DVASPECT) ? "DV_E_DVASPECT" : "uknown 
meaning"));
+
+  return hr;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idataobject_getcanonicalformatetc (LPDATAOBJECT This,
+                                   LPFORMATETC  pFormatEtcIn,
+                                   LPFORMATETC  pFormatEtcOut)
+{
+  GDK_NOTE (DND, g_print ("idataobject_getcanonicalformatetc %p E_NOTIMPL\n", This));
+
+  return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idataobject_setdata (LPDATAOBJECT This,
+                     LPFORMATETC  pFormatEtc,
+                     LPSTGMEDIUM  pMedium,
+                     BOOL         fRelease)
+{
+  GDK_NOTE (DND, g_print ("idataobject_setdata %p %s E_NOTIMPL\n",
+                          This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat)));
+
+  return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idataobject_enumformatetc (LPDATAOBJECT     This,
+                           DWORD            dwDirection,
+                           LPENUMFORMATETC *ppEnumFormatEtc)
+{
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread != g_thread_self ());
+
+  if (dwDirection != DATADIR_GET)
+    {
+      GDK_NOTE (DND, g_print ("idataobject_enumformatetc %p E_NOTIMPL", This));
+
+      return E_NOTIMPL;
+    }
+
+  *ppEnumFormatEtc = &enum_formats_new (((data_object *) This)->formats)->ief;
+
+  GDK_NOTE (DND, g_print ("idataobject_enumformatetc %p -> %p S_OK", This, *ppEnumFormatEtc));
+
+  return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idataobject_dadvise (LPDATAOBJECT This,
+                     LPFORMATETC  pFormatetc,
+                     DWORD        advf,
+                     LPADVISESINK pAdvSink,
+                     DWORD       *pdwConnection)
+{
+  GDK_NOTE (DND, g_print ("idataobject_dadvise %p E_NOTIMPL\n", This));
+
+  return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idataobject_dunadvise (LPDATAOBJECT This,
+                       DWORD         dwConnection)
+{
+  GDK_NOTE (DND, g_print ("idataobject_dunadvise %p E_NOTIMPL\n", This));
+
+  return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idataobject_enumdadvise (LPDATAOBJECT    This,
+                         LPENUMSTATDATA *ppenumAdvise)
+{
+  GDK_NOTE (DND, g_print ("idataobject_enumdadvise %p OLE_E_ADVISENOTSUPPORTED\n", This));
+
+  return OLE_E_ADVISENOTSUPPORTED;
+}
+
+static ULONG STDMETHODCALLTYPE
+ienumformatetc_addref (LPENUMFORMATETC This)
+{
+  enum_formats *en = (enum_formats *) This;
+  int ref_count = ++en->ref_count;
+
+  GDK_NOTE (DND, g_print ("ienumformatetc_addref %p %d\n", This, ref_count));
+
+  return ref_count;
+}
+
+static HRESULT STDMETHODCALLTYPE
+ienumformatetc_queryinterface (LPENUMFORMATETC This,
+                               REFIID          riid,
+                               LPVOID         *ppvObject)
+{
+  GDK_NOTE (DND, {
+      g_print ("ienumformatetc_queryinterface %p", This);
+      PRINT_GUID (riid);
+    });
+
+  *ppvObject = NULL;
+
+  if (IsEqualGUID (riid, &IID_IUnknown))
+    {
+      GDK_NOTE (DND, g_print ("...IUnknown S_OK\n"));
+      ienumformatetc_addref (This);
+      *ppvObject = This;
+      return S_OK;
+    }
+  else if (IsEqualGUID (riid, &IID_IEnumFORMATETC))
+    {
+      GDK_NOTE (DND, g_print ("...IEnumFORMATETC S_OK\n"));
+      ienumformatetc_addref (This);
+      *ppvObject = This;
+      return S_OK;
+    }
+  else
+    {
+      GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n"));
+      return E_NOINTERFACE;
+    }
+}
+
+static ULONG STDMETHODCALLTYPE
+ienumformatetc_release (LPENUMFORMATETC This)
+{
+  enum_formats *en = (enum_formats *) This;
+  int ref_count = --en->ref_count;
+
+  GDK_NOTE (DND, g_print ("ienumformatetc_release %p %d\n", This, ref_count));
+
+  if (ref_count == 0)
+    {
+      g_array_unref (en->formats);
+      g_free (This);
+    }
+
+  return ref_count;
+}
+
+static HRESULT STDMETHODCALLTYPE
+ienumformatetc_next (LPENUMFORMATETC This,
+                     ULONG             celt,
+                     LPFORMATETC     elts,
+                     ULONG            *nelt)
+{
+  enum_formats *en = (enum_formats *) This;
+  ULONG i, n;
+  ULONG formats_to_get = celt;
+
+  GDK_NOTE (DND, g_print ("ienumformatetc_next %p %d %ld ", This, en->ix, celt));
+
+  n = 0;
+  for (i = 0; i < formats_to_get; i++)
+    {
+      UINT fmt;
+      if (en->ix >= en->formats->len)
+        break;
+      fmt = g_array_index (en->formats, GdkWin32ContentFormatPair, en->ix++).w32format;
+      /* skip internals */
+      if (fmt == 0 || fmt > 0xFFFF)
+        {
+          formats_to_get += 1;
+          continue;
+        }
+      elts[n].cfFormat = fmt;
+      elts[n].ptd = NULL;
+      elts[n].dwAspect = DVASPECT_CONTENT;
+      elts[n].lindex = -1;
+      elts[n].tymed = TYMED_HGLOBAL;
+
+      n++;
+    }
+
+  if (nelt != NULL)
+    *nelt = n;
+
+  GDK_NOTE (DND, g_print ("%s\n", (n == celt) ? "S_OK" : "S_FALSE"));
+
+  if (n == celt)
+    return S_OK;
+  else
+    return S_FALSE;
+}
+
+static HRESULT STDMETHODCALLTYPE
+ienumformatetc_skip (LPENUMFORMATETC This,
+                     ULONG             celt)
+{
+  enum_formats *en = (enum_formats *) This;
+
+  GDK_NOTE (DND, g_print ("ienumformatetc_skip %p %d %ld S_OK\n", This, en->ix, celt));
+
+  en->ix += celt;
+
+  return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+ienumformatetc_reset (LPENUMFORMATETC This)
+{
+  enum_formats *en = (enum_formats *) This;
+
+  GDK_NOTE (DND, g_print ("ienumformatetc_reset %p S_OK\n", This));
+
+  en->ix = 0;
+
+  return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+ienumformatetc_clone (LPENUMFORMATETC  This,
+                      LPENUMFORMATETC *ppEnumFormatEtc)
+{
+  enum_formats *en = (enum_formats *) This;
+  enum_formats *new;
+
+  GDK_NOTE (DND, g_print ("ienumformatetc_clone %p S_OK\n", This));
+
+  new = enum_formats_new (en->formats);
+
+  new->ix = en->ix;
+
+  *ppEnumFormatEtc = &new->ief;
+
+  return S_OK;
+}
+
+static IDropSourceVtbl ids_vtbl = {
+  idropsource_queryinterface,
+  idropsource_addref,
+  idropsource_release,
+  idropsource_querycontinuedrag,
+  idropsource_givefeedback
+};
+
+static IDropSourceNotifyVtbl idsn_vtbl = {
+  (HRESULT (STDMETHODCALLTYPE *) (IDropSourceNotify *, REFIID , LPVOID *)) idropsource_queryinterface,
+  (ULONG (STDMETHODCALLTYPE *) (IDropSourceNotify *)) idropsource_addref,
+  (ULONG (STDMETHODCALLTYPE *) (IDropSourceNotify *)) idropsource_release,
+  idropsourcenotify_dragentertarget,
+  idropsourcenotify_dragleavetarget
+};
+
+static IDataObjectVtbl ido_vtbl = {
+  idataobject_queryinterface,
+  idataobject_addref,
+  idataobject_release,
+  idataobject_getdata,
+  idataobject_getdatahere,
+  idataobject_querygetdata,
+  idataobject_getcanonicalformatetc,
+  idataobject_setdata,
+  idataobject_enumformatetc,
+  idataobject_dadvise,
+  idataobject_dunadvise,
+  idataobject_enumdadvise
+};
+
+static IEnumFORMATETCVtbl ief_vtbl = {
+  ienumformatetc_queryinterface,
+  ienumformatetc_addref,
+  ienumformatetc_release,
+  ienumformatetc_next,
+  ienumformatetc_skip,
+  ienumformatetc_reset,
+  ienumformatetc_clone
+};
+
+static source_drag_context *
+source_context_new (GdkDragContext    *context,
+                    GdkSurface        *window,
+                    GdkContentFormats *formats)
+{
+  GdkWin32DragContext *context_win32;
+  source_drag_context *result;
+
+  context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+
+  result = g_new0 (source_drag_context, 1);
+  result->context = g_object_ref (context);
+  result->ids.lpVtbl = &ids_vtbl;
+  result->idsn.lpVtbl = &idsn_vtbl;
+  result->ref_count = 1;
+  result->source_window_handle = GDK_SURFACE_HWND (context->source_surface);
+  result->scale = context_win32->scale;
+  result->util_data.state = GDK_WIN32_DND_PENDING; /* Implicit */
+
+  GDK_NOTE (DND, g_print ("source_context_new: %p (drag context %p)\n", result, result->context));
+
+  return result;
+}
+
+static data_object *
+data_object_new (GdkDragContext *context)
+{
+  data_object *result;
+  const char * const *mime_types;
+  gsize n_mime_types, i;
+
+  result = g_new0 (data_object, 1);
+
+  result->ido.lpVtbl = &ido_vtbl;
+  result->ref_count = 1;
+  result->context = context;
+  result->formats = g_array_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair));
+
+  mime_types = gdk_content_formats_get_mime_types (context->formats, &n_mime_types);
+
+  for (i = 0; i < n_mime_types; i++)
+    {
+      gint added_count = 0;
+      gint j;
+
+      GDK_NOTE (DND, g_print ("DataObject supports contentformat 0x%p (%s)\n", mime_types[i], 
mime_types[i]));
+
+      added_count = _gdk_win32_add_contentformat_to_pairs (mime_types[i], result->formats);
+
+      for (j = 0; j < added_count && result->formats->len - 1 - j >= 0; j++)
+        GDK_NOTE (DND, g_print ("DataObject will support w32format 0x%x\n", g_array_index (result->formats, 
GdkWin32ContentFormatPair, j).w32format));
+    }
+
+  GDK_NOTE (DND, g_print ("data_object_new: %p\n", result));
+
+  return result;
+}
+
+static enum_formats *
+enum_formats_new (GArray *formats)
+{
+  enum_formats *result;
+
+  result = g_new0 (enum_formats, 1);
+
+  result->ief.lpVtbl = &ief_vtbl;
+  result->ref_count = 1;
+  result->ix = 0;
+  result->formats = g_array_ref (formats);
+
+  return result;
+}
+
+void
+_gdk_drag_init (void)
+{
+  CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
+
+  if (g_strcmp0 (getenv ("GDK_WIN32_OLE2_DND"), "0") != 0)
+    use_ole2_dnd = TRUE;
+
+  if (use_ole2_dnd)
+    {
+      HRESULT hr;
+
+      hr = OleInitialize (NULL);
+
+      if (! SUCCEEDED (hr))
+        g_error ("OleInitialize failed");
+    }
+}
+
+void
+_gdk_win32_dnd_exit (void)
+{
+  if (use_ole2_dnd)
+    {
+      OleUninitialize ();
+    }
+
+  CoUninitialize ();
+}
+
+/* Source side */
+
+void
+_gdk_win32_drag_context_send_local_status_event (GdkDragContext *src_context,
+                                                 GdkDragAction   action)
+{
+  GdkWin32DragContext *src_context_win32 = GDK_WIN32_DRAG_CONTEXT (src_context);
+
+  if (src_context_win32->drag_status == GDK_DRAG_STATUS_MOTION_WAIT)
+    src_context_win32->drag_status = GDK_DRAG_STATUS_DRAG;
+
+  if (action == GDK_ACTION_DEFAULT)
+    action = 0;
+
+  src_context->action = action;
+
+  GDK_NOTE (DND, g_print ("gdk_dnd_handle_drag_status: 0x%p\n",
+                          src_context));
+
+  if (action != src_context_win32->current_action)
+    {
+      src_context_win32->current_action = action;
+      g_signal_emit_by_name (src_context, "action-changed", action);
+    }
+}
+
+static void
+local_send_leave (GdkDragContext *context,
+                  guint32         time)
+{
+  GdkEvent *tmp_event;
+
+  GDK_NOTE (DND, g_print ("local_send_leave: context=%p current_dest_drag=%p\n",
+                          context,
+                          current_dest_drag));
+
+  if ((current_dest_drag != NULL) &&
+      (GDK_WIN32_DRAG_CONTEXT (current_dest_drag)->protocol == GDK_DRAG_PROTO_LOCAL) &&
+      (current_dest_drag->source_surface == context->source_surface))
+    {
+      tmp_event = gdk_event_new (GDK_DRAG_LEAVE);
+
+      g_set_object (&tmp_event->any.surface, context->dest_surface);
+      /* Pass ownership of context to the event */
+      tmp_event->any.send_event = FALSE;
+      g_set_object (&tmp_event->dnd.context, current_dest_drag);
+      tmp_event->dnd.time = GDK_CURRENT_TIME; /* FIXME? */
+      gdk_event_set_device (tmp_event, gdk_drag_context_get_device (context));
+
+      current_dest_drag = NULL;
+
+      GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
+      _gdk_display_put_event (gdk_device_get_display (gdk_drag_context_get_device (context)), tmp_event);
+      gdk_event_free (tmp_event);
+    }
+}
+
+static void
+local_send_motion (GdkDragContext *context,
+                   gint            x_root,
+                   gint            y_root,
+                   GdkDragAction   action,
+                   guint32         time)
+{
+  GdkEvent *tmp_event;
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+
+  GDK_NOTE (DND, g_print ("local_send_motion: context=%p (%d,%d) current_dest_drag=%p\n",
+                          context, x_root, y_root,
+                          current_dest_drag));
+
+  if ((current_dest_drag != NULL) &&
+      (GDK_WIN32_DRAG_CONTEXT (current_dest_drag)->protocol == GDK_DRAG_PROTO_LOCAL) &&
+      (current_dest_drag->source_surface == context->source_surface))
+    {
+      GdkWin32DragContext *current_dest_drag_win32;
+
+      tmp_event = gdk_event_new (GDK_DRAG_MOTION);
+      g_set_object (&tmp_event->any.surface, current_dest_drag->dest_surface);
+      tmp_event->any.send_event = FALSE;
+      g_set_object (&tmp_event->dnd.context, current_dest_drag);
+      tmp_event->dnd.time = time;
+      gdk_event_set_device (tmp_event, gdk_drag_context_get_device (current_dest_drag));
+
+      current_dest_drag->suggested_action = action;
+
+      tmp_event->dnd.x_root = x_root;
+      tmp_event->dnd.y_root = y_root;
+
+      current_dest_drag_win32 = GDK_WIN32_DRAG_CONTEXT (current_dest_drag);
+      current_dest_drag_win32->util_data.last_x = x_root;
+      current_dest_drag_win32->util_data.last_y = y_root;
+
+      context_win32->drag_status = GDK_DRAG_STATUS_MOTION_WAIT;
+
+      GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
+      _gdk_display_put_event (gdk_device_get_display (gdk_drag_context_get_device (context)), tmp_event);
+      gdk_event_free (tmp_event);
+    }
+}
+
+static void
+local_send_drop (GdkDragContext *context,
+                 guint32         time)
+{
+  GdkEvent *tmp_event;
+
+  GDK_NOTE (DND, g_print ("local_send_drop: context=%p current_dest_drag=%p\n",
+                          context,
+                          current_dest_drag));
+
+  if ((current_dest_drag != NULL) &&
+      (GDK_WIN32_DRAG_CONTEXT (current_dest_drag)->protocol == GDK_DRAG_PROTO_LOCAL) &&
+      (current_dest_drag->source_surface == context->source_surface))
+    {
+      GdkWin32DragContext *context_win32;
+
+      /* Pass ownership of context to the event */
+      tmp_event = gdk_event_new (GDK_DROP_START);
+      g_set_object (&tmp_event->any.surface, current_dest_drag->dest_surface);
+      tmp_event->any.send_event = FALSE;
+      g_set_object (&tmp_event->dnd.context, current_dest_drag);
+      tmp_event->dnd.time = GDK_CURRENT_TIME;
+      gdk_event_set_device (tmp_event, gdk_drag_context_get_device (current_dest_drag));
+
+      context_win32 = GDK_WIN32_DRAG_CONTEXT (current_dest_drag);
+      tmp_event->dnd.x_root = context_win32->util_data.last_x;
+      tmp_event->dnd.y_root = context_win32->util_data.last_y;
+
+      current_dest_drag = NULL;
+
+      GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
+      _gdk_display_put_event (gdk_device_get_display (gdk_drag_context_get_device (context)), tmp_event);
+      gdk_event_free (tmp_event);
+    }
+
+}
+
+void
+_gdk_win32_drag_do_leave (GdkDragContext *context,
+                          guint32         time)
+{
+  if (context->dest_surface)
+    {
+      GDK_NOTE (DND, g_print ("gdk_drag_do_leave\n"));
+
+      if (!use_ole2_dnd)
+        {
+          if (GDK_WIN32_DRAG_CONTEXT (context)->protocol == GDK_DRAG_PROTO_LOCAL)
+            local_send_leave (context, time);
+        }
+
+      g_clear_object (&context->dest_surface);
+    }
+}
+
+static GdkSurface *
+create_drag_surface (GdkDisplay *display)
+{
+  GdkSurface *window;
+
+  window = gdk_surface_new_popup (display, &(GdkRectangle) { 0, 0, 100, 100 });
+
+  gdk_surface_set_type_hint (window, GDK_SURFACE_TYPE_HINT_DND);
+
+  return window;
+}
+
+GdkDragContext *
+_gdk_win32_surface_drag_begin (GdkSurface        *window,
+                              GdkDevice          *device,
+                              GdkContentProvider *content,
+                              GdkDragAction       actions,
+                              gint                dx,
+                              gint                dy)
+{
+  GdkDragContext *context;
+  GdkWin32DragContext *context_win32;
+  BYTE kbd_state[256];
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+  int x_root, y_root;
+
+  g_return_val_if_fail (window != NULL, NULL);
+
+  context = gdk_drag_context_new (gdk_surface_get_display (window),
+                                  content,
+                                  window,
+                                  actions,
+                                  device,
+                                  use_ole2_dnd ? GDK_DRAG_PROTO_OLE2 : GDK_DRAG_PROTO_LOCAL);
+  context->formats = gdk_content_formats_union_serialize_mime_types 
(gdk_content_provider_ref_storable_formats (content));
+
+  context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+
+  GDK_NOTE (DND, g_print ("gdk_drag_begin\n"));
+
+  gdk_device_get_position (device, &x_root, &y_root);
+  x_root += dx;
+  y_root += dy;
+
+  context_win32->start_x = x_root;
+  context_win32->start_y = y_root;
+  context_win32->util_data.last_x = context_win32->start_x;
+  context_win32->util_data.last_y = context_win32->start_y;
+
+  g_set_object (&context_win32->ipc_window, window);
+
+  context_win32->drag_surface = create_drag_surface (gdk_surface_get_display (window));
+
+  if (!drag_context_grab (context))
+    {
+      g_object_unref (context);
+      return FALSE;
+    }
+
+  if (use_ole2_dnd)
+    {
+      GdkWin32DnDThreadDoDragDrop *ddd = g_new0 (GdkWin32DnDThreadDoDragDrop, 1);
+      source_drag_context *source_ctx;
+      data_object         *data_obj;
+
+      source_ctx = source_context_new (context, window, context->formats);
+      data_obj = data_object_new (context);
+
+      ddd->base.item_type = GDK_WIN32_DND_THREAD_QUEUE_ITEM_DO_DRAG_DROP;
+      ddd->base.opaque_context = context_win32;
+      ddd->src_context = source_ctx;
+      ddd->src_object = data_obj;
+      ddd->allowed_drop_effects = 0;
+      if (actions & GDK_ACTION_COPY)
+        ddd->allowed_drop_effects |= DROPEFFECT_COPY;
+      if (actions & GDK_ACTION_MOVE)
+        ddd->allowed_drop_effects |= DROPEFFECT_MOVE;
+      if (actions & GDK_ACTION_LINK)
+        ddd->allowed_drop_effects |= DROPEFFECT_LINK;
+
+      g_hash_table_replace (clipdrop->active_source_drags, g_object_ref (context), ddd);
+      increment_dnd_queue_counter ();
+      g_async_queue_push (clipdrop->dnd_queue, ddd);
+      API_CALL (PostThreadMessage, (clipdrop->dnd_thread_id, thread_wakeup_message, 0, 0));
+
+      context_win32->util_data.state = GDK_WIN32_DND_PENDING;
+    }
+
+  move_drag_surface (context, x_root, y_root);
+
+  return context;
+}
+
+/* TODO: remove this?
+ * window finder is only used by our gdk_drag_update() to
+ * find the window at drag coordinates - which is
+ * something IDropSourceNotify already gives us.
+ * Unless, of course, we keep the LOCAL protocol around.
+ */
+typedef struct {
+  gint x;
+  gint y;
+  HWND ignore;
+  HWND result;
+} find_window_enum_arg;
+
+static BOOL CALLBACK
+find_window_enum_proc (HWND   hwnd,
+                       LPARAM lparam)
+{
+  RECT rect;
+  POINT tl, br;
+  find_window_enum_arg *a = (find_window_enum_arg *) lparam;
+
+  if (hwnd == a->ignore)
+    return TRUE;
+
+  if (!IsWindowVisible (hwnd))
+    return TRUE;
+
+  tl.x = tl.y = 0;
+  ClientToScreen (hwnd, &tl);
+  GetClientRect (hwnd, &rect);
+  br.x = rect.right;
+  br.y = rect.bottom;
+  ClientToScreen (hwnd, &br);
+
+  if (a->x >= tl.x && a->y >= tl.y && a->x < br.x && a->y < br.y)
+    {
+      a->result = hwnd;
+      return FALSE;
+    }
+  else
+    return TRUE;
+}
+
+static GdkSurface *
+gdk_win32_drag_context_find_surface (GdkDragContext  *context,
+                                     GdkSurface      *drag_surface,
+                                     gint             x_root,
+                                     gint             y_root,
+                                     GdkDragProtocol *protocol)
+{
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+  GdkSurface *dest_surface, *dw;
+  find_window_enum_arg a;
+
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread == g_thread_self ());
+
+  a.x = x_root * context_win32->scale - _gdk_offset_x;
+  a.y = y_root * context_win32->scale - _gdk_offset_y;
+  a.ignore = drag_surface ? GDK_SURFACE_HWND (drag_surface) : NULL;
+  a.result = NULL;
+
+  GDK_NOTE (DND,
+            g_print ("gdk_drag_find_surface_real: %p %+d%+d\n",
+                     (drag_surface ? GDK_SURFACE_HWND (drag_surface) : NULL),
+                     a.x, a.y));
+
+  EnumWindows (find_window_enum_proc, (LPARAM) &a);
+
+  if (a.result == NULL)
+    dest_surface = NULL;
+  else
+    {
+      dw = gdk_win32_handle_table_lookup (a.result);
+      if (dw)
+        {
+          dest_surface = gdk_surface_get_toplevel (dw);
+          g_object_ref (dest_surface);
+        }
+      else
+        dest_surface = gdk_win32_surface_foreign_new_for_display (context->display, a.result);
+
+      if (use_ole2_dnd)
+        *protocol = GDK_DRAG_PROTO_OLE2;
+      else if (context->source_surface)
+        *protocol = GDK_DRAG_PROTO_LOCAL;
+      else
+        *protocol = GDK_DRAG_PROTO_WIN32_DROPFILES;
+    }
+
+  GDK_NOTE (DND,
+            g_print ("gdk_drag_find_surface: %p %+d%+d: %p: %p %s\n",
+                     (drag_surface ? GDK_SURFACE_HWND (drag_surface) : NULL),
+                     x_root, y_root,
+                     a.result,
+                     (dest_surface ? GDK_SURFACE_HWND (dest_surface) : NULL),
+                     _gdk_win32_drag_protocol_to_string (*protocol)));
+
+  return dest_surface;
+}
+
+static gboolean
+gdk_win32_drag_context_drag_motion (GdkDragContext  *context,
+                                    GdkSurface      *dest_surface,
+                                    GdkDragProtocol  protocol,
+                                    gint             x_root,
+                                    gint             y_root,
+                                    GdkDragAction    suggested_action,
+                                    GdkDragAction    possible_actions,
+                                    guint32          time)
+{
+  GdkWin32DragContext *context_win32;
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread == g_thread_self ());
+
+  g_return_val_if_fail (context != NULL, FALSE);
+
+  context->actions = possible_actions;
+
+  GDK_NOTE (DND, g_print ("gdk_drag_motion: @ %+d:%+d %s suggested=%s, possible=%s\n"
+                          " context=%p:{actions=%s,suggested=%s,action=%s}\n",
+                          x_root, y_root,
+                          _gdk_win32_drag_protocol_to_string (protocol),
+                          _gdk_win32_drag_action_to_string (suggested_action),
+                          _gdk_win32_drag_action_to_string (possible_actions),
+                          context,
+                          _gdk_win32_drag_action_to_string (context->actions),
+                          _gdk_win32_drag_action_to_string (context->suggested_action),
+                          _gdk_win32_drag_action_to_string (context->action)));
+
+  context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+
+  if (context_win32->drag_surface)
+    move_drag_surface (context, x_root, y_root);
+
+  if (!use_ole2_dnd)
+    {
+      if (context->dest_surface == dest_surface)
+        {
+          GdkDragContext *dest_context;
+
+          dest_context = _gdk_win32_drop_context_find (context->source_surface,
+                                                       dest_surface);
+
+          if (dest_context)
+            dest_context->actions = context->actions;
+
+          context->suggested_action = suggested_action;
+        }
+      else
+        {
+          /* Send a leave to the last destination */
+          _gdk_win32_drag_do_leave (context, time);
+
+          context_win32->drag_status = GDK_DRAG_STATUS_DRAG;
+
+          /* Check if new destination accepts drags, and which protocol */
+          if (dest_surface)
+            {
+              g_set_object (&context->dest_surface, dest_surface);
+              context_win32->protocol = protocol;
+
+              switch (protocol)
+                {
+                case GDK_DRAG_PROTO_LOCAL:
+                  _gdk_win32_local_send_enter (context, time);
+                  break;
+
+                default:
+                  break;
+                }
+              context->suggested_action = suggested_action;
+            }
+          else
+            {
+              context->dest_surface = NULL;
+              context->action = 0;
+            }
+
+          GDK_NOTE (DND, g_print ("gdk_dnd_handle_drag_status: 0x%p\n",
+                                  context));
+
+          if (context->action != context_win32->current_action)
+            {
+              context_win32->current_action = context->action;
+              g_signal_emit_by_name (context, "action-changed", context->action);
+            }
+        }
+
+      /* Send a drag-motion event */
+
+      context_win32->util_data.last_x = x_root;
+      context_win32->util_data.last_y = y_root;
+
+      if (context->dest_surface)
+        {
+          if (context_win32->drag_status == GDK_DRAG_STATUS_DRAG)
+            {
+              switch (context_win32->protocol)
+                {
+                case GDK_DRAG_PROTO_LOCAL:
+                  local_send_motion (context, x_root, y_root, suggested_action, time);
+                  break;
+
+                case GDK_DRAG_PROTO_NONE:
+                  g_warning ("GDK_DRAG_PROTO_NONE is not valid in gdk_drag_motion()");
+                  break;
+
+                default:
+                  break;
+                }
+            }
+          else
+            {
+              GDK_NOTE (DND, g_print (" returning TRUE\n"
+                                      " context=%p:{actions=%s,suggested=%s,action=%s}\n",
+                                      context,
+                                      _gdk_win32_drag_action_to_string (context->actions),
+                                      _gdk_win32_drag_action_to_string (context->suggested_action),
+                                      _gdk_win32_drag_action_to_string (context->action)));
+              return TRUE;
+            }
+        }
+    }
+
+  GDK_NOTE (DND, g_print (" returning FALSE\n"
+                          " context=%p:{actions=%s,suggested=%s,action=%s}\n",
+                          context,
+                          _gdk_win32_drag_action_to_string (context->actions),
+                          _gdk_win32_drag_action_to_string (context->suggested_action),
+                          _gdk_win32_drag_action_to_string (context->action)));
+  return FALSE;
+}
+
+static void
+send_source_state_update (GdkWin32Clipdrop    *clipdrop,
+                          GdkWin32DragContext *context_win32,
+                          gpointer            *ddd)
+{
+  GdkWin32DnDThreadUpdateDragState *status = g_new0 (GdkWin32DnDThreadUpdateDragState, 1);
+  status->base.item_type = GDK_WIN32_DND_THREAD_QUEUE_ITEM_UPDATE_DRAG_STATE;
+  status->opaque_ddd = ddd;
+  status->produced_util_data = context_win32->util_data;
+  increment_dnd_queue_counter ();
+  g_async_queue_push (clipdrop->dnd_queue, status);
+  API_CALL (PostThreadMessage, (clipdrop->dnd_thread_id, thread_wakeup_message, 0, 0));
+}
+
+static void
+gdk_win32_drag_context_drag_drop (GdkDragContext *context,
+                                  guint32         time)
+{
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread == g_thread_self ());
+
+  g_return_if_fail (context != NULL);
+
+  GDK_NOTE (DND, g_print ("gdk_drag_drop\n"));
+
+  if (!use_ole2_dnd)
+    {
+      if (context->dest_surface &&
+          GDK_WIN32_DRAG_CONTEXT (context)->protocol == GDK_DRAG_PROTO_LOCAL)
+        local_send_drop (context, time);
+    }
+  else
+    {
+      gpointer ddd = g_hash_table_lookup (clipdrop->active_source_drags, context);
+
+      GDK_WIN32_DRAG_CONTEXT (context)->util_data.state = GDK_WIN32_DND_DROPPED;
+
+      if (ddd)
+        send_source_state_update (clipdrop, GDK_WIN32_DRAG_CONTEXT (context), ddd);
+    }
+}
+
+static void
+gdk_win32_drag_context_drag_abort (GdkDragContext *context,
+                                   guint32         time)
+{
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread == g_thread_self ());
+
+  g_return_if_fail (context != NULL);
+
+  GDK_NOTE (DND, g_print ("gdk_drag_abort\n"));
+
+  if (use_ole2_dnd)
+    {
+      gpointer ddd = g_hash_table_lookup (clipdrop->active_source_drags, context);
+
+      GDK_WIN32_DRAG_CONTEXT (context)->util_data.state = GDK_WIN32_DND_NONE;
+
+      if (ddd)
+        send_source_state_update (clipdrop, GDK_WIN32_DRAG_CONTEXT (context), ddd);
+    }
+}
+
+static gboolean
+gdk_win32_drag_context_drop_status (GdkDragContext *context)
+{
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+
+  return ! context_win32->drop_failed;
+}
+
+static void
+gdk_win32_drag_context_set_cursor (GdkDragContext *context,
+                                   GdkCursor      *cursor)
+{
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+
+  GDK_NOTE (DND, g_print ("gdk_drag_context_set_cursor: 0x%p 0x%p\n", context, cursor));
+
+  if (!g_set_object (&context_win32->cursor, cursor))
+    return;
+
+  if (context_win32->grab_seat)
+    {
+      G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+      gdk_device_grab (gdk_seat_get_pointer (context_win32->grab_seat),
+                       context_win32->ipc_window,
+                       GDK_OWNERSHIP_APPLICATION, FALSE,
+                       GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
+                       cursor, GDK_CURRENT_TIME);
+      G_GNUC_END_IGNORE_DEPRECATIONS;
+    }
+}
+
+static double
+ease_out_cubic (double t)
+{
+  double p = t - 1;
+  return p * p * p + 1;
+}
+
+#define ANIM_TIME 500000 /* half a second */
+
+typedef struct _GdkDragAnim GdkDragAnim;
+struct _GdkDragAnim {
+  GdkWin32DragContext *context;
+  GdkFrameClock *frame_clock;
+  gint64 start_time;
+};
+
+static void
+gdk_drag_anim_destroy (GdkDragAnim *anim)
+{
+  g_object_unref (anim->context);
+  g_slice_free (GdkDragAnim, anim);
+}
+
+static gboolean
+gdk_drag_anim_timeout (gpointer data)
+{
+  GdkDragAnim *anim = data;
+  GdkWin32DragContext *context = anim->context;
+  GdkFrameClock *frame_clock = anim->frame_clock;
+  gint64 current_time;
+  double f;
+  double t;
+
+  if (!frame_clock)
+    return G_SOURCE_REMOVE;
+
+  current_time = gdk_frame_clock_get_frame_time (frame_clock);
+
+  f = (current_time - anim->start_time) / (double) ANIM_TIME;
+
+  if (f >= 1.0)
+    return G_SOURCE_REMOVE;
+
+  t = ease_out_cubic (f);
+
+  gdk_surface_show (context->drag_surface);
+  gdk_surface_move (context->drag_surface,
+                    context->util_data.last_x + (context->start_x - context->util_data.last_x) * t - 
context->hot_x,
+                    context->util_data.last_y + (context->start_y - context->util_data.last_y) * t - 
context->hot_y);
+  gdk_surface_set_opacity (context->drag_surface, 1.0 - f);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+gdk_win32_drag_context_drop_done (GdkDragContext *context,
+                                  gboolean        success)
+{
+  GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
+  GdkDragAnim *anim;
+  cairo_surface_t *win_surface;
+  cairo_surface_t *surface;
+  cairo_t *cr;
+  guint id;
+
+  GDK_NOTE (DND, g_print ("gdk_drag_context_drop_done: 0x%p %s\n",
+                          context,
+                          success ? "dropped successfully" : "dropped unsuccessfully"));
+
+  /* FIXME: This is temporary, until the code is fixed to ensure that
+   * gdk_drop_finish () is called by GTK.
+   */
+  if (use_ole2_dnd)
+    {
+      GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+      gpointer ddd = g_hash_table_lookup (clipdrop->active_source_drags, context);
+
+      if (success)
+        win32_context->util_data.state = GDK_WIN32_DND_DROPPED;
+      else
+        win32_context->util_data.state = GDK_WIN32_DND_NONE;
+
+      if (ddd)
+        send_source_state_update (clipdrop, win32_context, ddd);
+    }
+
+  if (success)
+    {
+      gdk_surface_hide (win32_context->drag_surface);
+
+      return;
+    }
+
+  win_surface = _gdk_surface_ref_cairo_surface (win32_context->drag_surface);
+  surface = gdk_surface_create_similar_surface (win32_context->drag_surface,
+                                                cairo_surface_get_content (win_surface),
+                                                gdk_surface_get_width (win32_context->drag_surface),
+                                                gdk_surface_get_height (win32_context->drag_surface));
+  cr = cairo_create (surface);
+  cairo_set_source_surface (cr, win_surface, 0, 0);
+  cairo_paint (cr);
+  cairo_destroy (cr);
+  cairo_surface_destroy (win_surface);
+
+/*
+  pattern = cairo_pattern_create_for_surface (surface);
+
+  gdk_surface_set_background_pattern (win32_context->drag_surface, pattern);
+
+  cairo_pattern_destroy (pattern);
+*/
+  cairo_surface_destroy (surface);
+
+  anim = g_slice_new0 (GdkDragAnim);
+  g_set_object (&anim->context, win32_context);
+  anim->frame_clock = gdk_surface_get_frame_clock (win32_context->drag_surface);
+  anim->start_time = gdk_frame_clock_get_frame_time (anim->frame_clock);
+
+  GDK_NOTE (DND, g_print ("gdk_drag_context_drop_done: animate the drag window from %d : %d to %d : %d\n",
+                          win32_context->util_data.last_x, win32_context->util_data.last_y,
+                          win32_context->start_x, win32_context->start_y));
+
+  id = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT, 17,
+                                     gdk_drag_anim_timeout, anim,
+                                     (GDestroyNotify) gdk_drag_anim_destroy);
+  g_source_set_name_by_id (id, "[gtk+] gdk_drag_anim_timeout");
+}
+
+static gboolean
+drag_context_grab (GdkDragContext *context)
+{
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+  GdkSeatCapabilities capabilities;
+  GdkSeat *seat;
+  GdkCursor *cursor;
+
+  if (!context_win32->ipc_window)
+    return FALSE;
+
+  seat = gdk_device_get_seat (gdk_drag_context_get_device (context));
+
+  capabilities = GDK_SEAT_CAPABILITY_ALL;
+
+  cursor = gdk_drag_get_cursor (context, gdk_drag_context_get_selected_action (context));
+  g_set_object (&context_win32->cursor, cursor);
+
+  if (gdk_seat_grab (seat, context_win32->ipc_window,
+                     capabilities, FALSE,
+                     context_win32->cursor, NULL, NULL, NULL) != GDK_GRAB_SUCCESS)
+    return FALSE;
+
+  g_set_object (&context_win32->grab_seat, seat);
+
+  /* TODO: Should be grabbing keys here, to support keynav. SetWindowsHookEx()? */
+
+  return TRUE;
+}
+
+static void
+drag_context_ungrab (GdkDragContext *context)
+{
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+
+  if (!context_win32->grab_seat)
+    return;
+
+  gdk_seat_ungrab (context_win32->grab_seat);
+
+  g_clear_object (&context_win32->grab_seat);
+
+  /* TODO: Should be ungrabbing keys here */
+}
+
+static void
+gdk_win32_drag_context_cancel (GdkDragContext      *context,
+                               GdkDragCancelReason  reason)
+{
+  const gchar *reason_str = NULL;
+  switch (reason)
+    {
+    case GDK_DRAG_CANCEL_NO_TARGET:
+      reason_str = "no target";
+      break;
+    case GDK_DRAG_CANCEL_USER_CANCELLED:
+      reason_str = "user cancelled";
+      break;
+    case GDK_DRAG_CANCEL_ERROR:
+      reason_str = "error";
+      break;
+    default:
+      reason_str = "<unknown>";
+      break;
+    }
+
+  GDK_NOTE (DND, g_print ("gdk_drag_context_cancel: 0x%p %s\n",
+                          context,
+                          reason_str));
+  drag_context_ungrab (context);
+  gdk_drag_drop_done (context, FALSE);
+}
+
+static void
+gdk_win32_drag_context_drop_performed (GdkDragContext *context,
+                                       guint32         time_)
+{
+  GDK_NOTE (DND, g_print ("gdk_drag_context_drop_performed: 0x%p %u\n",
+                          context,
+                          time_));
+  gdk_drag_drop (context, time_);
+  drag_context_ungrab (context);
+}
+
+#define BIG_STEP 20
+#define SMALL_STEP 1
+
+static void
+gdk_drag_get_current_actions (GdkModifierType  state,
+                              gint             button,
+                              GdkDragAction    actions,
+                              GdkDragAction   *suggested_action,
+                              GdkDragAction   *possible_actions)
+{
+  *suggested_action = 0;
+  *possible_actions = 0;
+
+  if ((button == GDK_BUTTON_MIDDLE || button == GDK_BUTTON_SECONDARY) && (actions & GDK_ACTION_ASK))
+    {
+      *suggested_action = GDK_ACTION_ASK;
+      *possible_actions = actions;
+    }
+  else if (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
+    {
+      if ((state & GDK_SHIFT_MASK) && (state & GDK_CONTROL_MASK))
+        {
+          if (actions & GDK_ACTION_LINK)
+            {
+              *suggested_action = GDK_ACTION_LINK;
+              *possible_actions = GDK_ACTION_LINK;
+            }
+        }
+      else if (state & GDK_CONTROL_MASK)
+        {
+          if (actions & GDK_ACTION_COPY)
+            {
+              *suggested_action = GDK_ACTION_COPY;
+              *possible_actions = GDK_ACTION_COPY;
+            }
+        }
+      else
+        {
+          if (actions & GDK_ACTION_MOVE)
+            {
+              *suggested_action = GDK_ACTION_MOVE;
+              *possible_actions = GDK_ACTION_MOVE;
+            }
+        }
+    }
+  else
+    {
+      *possible_actions = actions;
+
+      if ((state & (GDK_MOD1_MASK)) && (actions & GDK_ACTION_ASK))
+        *suggested_action = GDK_ACTION_ASK;
+      else if (actions & GDK_ACTION_COPY)
+        *suggested_action =  GDK_ACTION_COPY;
+      else if (actions & GDK_ACTION_MOVE)
+        *suggested_action = GDK_ACTION_MOVE;
+      else if (actions & GDK_ACTION_LINK)
+        *suggested_action = GDK_ACTION_LINK;
+    }
+}
+
+static void
+gdk_drag_update (GdkDragContext  *context,
+                 gdouble          x_root,
+                 gdouble          y_root,
+                 GdkModifierType  mods,
+                 guint32          evtime)
+{
+  GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
+  GdkDragAction action, possible_actions;
+  GdkSurface *dest_surface;
+  GdkDragProtocol protocol;
+
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread == g_thread_self ());
+
+  gdk_drag_get_current_actions (mods, GDK_BUTTON_PRIMARY, win32_context->actions,
+                                &action, &possible_actions);
+
+  gdk_drag_find_surface (context,
+                         win32_context->drag_surface,
+                         x_root, y_root, &dest_surface, &protocol);
+
+  gdk_drag_motion (context, dest_surface, protocol, x_root, y_root,
+                   action, possible_actions, evtime);
+}
+
+static gboolean
+gdk_dnd_handle_motion_event (GdkDragContext       *context,
+                             const GdkEventMotion *event)
+{
+  GdkModifierType state;
+
+  if (!gdk_event_get_state ((GdkEvent *) event, &state))
+    return FALSE;
+
+  GDK_NOTE (DND, g_print ("gdk_dnd_handle_motion_event: 0x%p\n",
+                          context));
+
+  gdk_drag_update (context, event->x_root, event->y_root, state,
+                   gdk_event_get_time ((GdkEvent *) event));
+
+
+  if (use_ole2_dnd)
+    {
+      GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+      GdkWin32DragContext *context_win32;
+      DWORD key_state = 0;
+      BYTE kbd_state[256];
+
+      /* TODO: is this correct? We get the state at current moment,
+       * not at the moment when the event was emitted.
+       * I don't think that it ultimately serves any purpose,
+       * as our IDropSource does not react to the keyboard
+       * state changes (rather, it reacts to our status updates),
+       * but there's no way to tell what goes inside DoDragDrop(),
+       * so we should send at least *something* that looks right.
+       */
+      API_CALL (GetKeyboardState, (kbd_state));
+
+      if (kbd_state[VK_CONTROL] & 0x80)
+        key_state |= MK_CONTROL;
+      if (kbd_state[VK_SHIFT] & 0x80)
+        key_state |= MK_SHIFT;
+      if (kbd_state[VK_LBUTTON] & 0x80)
+        key_state |= MK_LBUTTON;
+      if (kbd_state[VK_MBUTTON] & 0x80)
+        key_state |= MK_MBUTTON;
+      if (kbd_state[VK_RBUTTON] & 0x80)
+        key_state |= MK_RBUTTON;
+
+      GDK_NOTE (DND, g_print ("Post WM_MOUSEMOVE keystate=%lu\n", key_state));
+
+      context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+
+      context_win32->util_data.last_x = event->x_root;
+      context_win32->util_data.last_y = event->y_root;
+
+      API_CALL (PostThreadMessage, (clipdrop->dnd_thread_id,
+                                    WM_MOUSEMOVE,
+                                    key_state,
+                                    MAKELPARAM ((event->x_root - _gdk_offset_x) * context_win32->scale,
+                                                (event->y_root - _gdk_offset_y) * context_win32->scale)));
+    }
+
+  return TRUE;
+}
+
+static gboolean
+gdk_dnd_handle_key_event (GdkDragContext    *context,
+                          const GdkEventKey *event)
+{
+  GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
+  GdkModifierType state;
+  GdkDevice *pointer;
+  gint dx, dy;
+
+  GDK_NOTE (DND, g_print ("gdk_dnd_handle_key_event: 0x%p\n",
+                          context));
+
+  dx = dy = 0;
+  state = event->state;
+  pointer = gdk_device_get_associated_device (gdk_event_get_device ((GdkEvent *) event));
+
+  if (event->any.type == GDK_KEY_PRESS)
+    {
+      switch (event->keyval)
+        {
+        case GDK_KEY_Escape:
+          gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_USER_CANCELLED);
+          return TRUE;
+
+        case GDK_KEY_space:
+        case GDK_KEY_Return:
+        case GDK_KEY_ISO_Enter:
+        case GDK_KEY_KP_Enter:
+        case GDK_KEY_KP_Space:
+          if ((gdk_drag_context_get_selected_action (context) != 0) &&
+              (gdk_drag_context_get_dest_surface (context) != NULL))
+            {
+              g_signal_emit_by_name (context, "drop-performed",
+                                     gdk_event_get_time ((GdkEvent *) event));
+            }
+          else
+            gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_NO_TARGET);
+
+          return TRUE;
+
+        case GDK_KEY_Up:
+        case GDK_KEY_KP_Up:
+          dy = (state & GDK_MOD1_MASK) ? -BIG_STEP : -SMALL_STEP;
+          break;
+
+        case GDK_KEY_Down:
+        case GDK_KEY_KP_Down:
+          dy = (state & GDK_MOD1_MASK) ? BIG_STEP : SMALL_STEP;
+          break;
+
+        case GDK_KEY_Left:
+        case GDK_KEY_KP_Left:
+          dx = (state & GDK_MOD1_MASK) ? -BIG_STEP : -SMALL_STEP;
+          break;
+
+        case GDK_KEY_Right:
+        case GDK_KEY_KP_Right:
+          dx = (state & GDK_MOD1_MASK) ? BIG_STEP : SMALL_STEP;
+          break;
+        }
+    }
+
+  /* The state is not yet updated in the event, so we need
+   * to query it here.
+   */
+  _gdk_device_query_state (pointer, NULL, NULL, NULL, NULL, NULL, NULL, &state);
+
+  if (dx != 0 || dy != 0)
+    {
+      win32_context->util_data.last_x += dx;
+      win32_context->util_data.last_y += dy;
+      gdk_device_warp (pointer, win32_context->util_data.last_x, win32_context->util_data.last_y);
+    }
+
+  gdk_drag_update (context, win32_context->util_data.last_x, win32_context->util_data.last_y, state,
+                   gdk_event_get_time ((GdkEvent *) event));
+
+  return TRUE;
+}
+
+static gboolean
+gdk_dnd_handle_grab_broken_event (GdkDragContext           *context,
+                                  const GdkEventGrabBroken *event)
+{
+  GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
+
+  GDK_NOTE (DND, g_print ("gdk_dnd_handle_grab_broken_event: 0x%p\n",
+                          context));
+
+  /* Don't cancel if we break the implicit grab from the initial button_press.
+   * Also, don't cancel if we re-grab on the widget or on our IPC window, for
+   * example, when changing the drag cursor.
+   */
+  if (event->implicit ||
+      event->grab_surface == win32_context->drag_surface ||
+      event->grab_surface == win32_context->ipc_window)
+    return FALSE;
+
+  if (gdk_event_get_device ((GdkEvent *) event) !=
+      gdk_drag_context_get_device (context))
+    return FALSE;
+
+  gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_ERROR);
+  return TRUE;
+}
+
+static gboolean
+gdk_dnd_handle_button_event (GdkDragContext       *context,
+                             const GdkEventButton *event)
+{
+  GDK_NOTE (DND, g_print ("gdk_dnd_handle_button_event: 0x%p\n",
+                          context));
+
+#if 0
+  /* FIXME: Check the button matches */
+  if (event->button != win32_context->button)
+    return FALSE;
+#endif
+
+  if ((gdk_drag_context_get_selected_action (context) != 0))
+    {
+      g_signal_emit_by_name (context, "drop-performed",
+                             gdk_event_get_time ((GdkEvent *) event));
+    }
+  else
+    gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_NO_TARGET);
+
+  return TRUE;
+}
+
+gboolean
+gdk_win32_drag_context_handle_event (GdkDragContext *context,
+                                     const GdkEvent *event)
+{
+  GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
+
+  if (!context->is_source)
+    return FALSE;
+  if (!win32_context->grab_seat)
+    return FALSE;
+
+  switch (event->any.type)
+    {
+    case GDK_MOTION_NOTIFY:
+      return gdk_dnd_handle_motion_event (context, &event->motion);
+    case GDK_BUTTON_RELEASE:
+      return gdk_dnd_handle_button_event (context, &event->button);
+    case GDK_KEY_PRESS:
+    case GDK_KEY_RELEASE:
+      return gdk_dnd_handle_key_event (context, &event->key);
+    case GDK_GRAB_BROKEN:
+      return gdk_dnd_handle_grab_broken_event (context, &event->grab_broken);
+    default:
+      break;
+    }
+
+  return FALSE;
+}
+
+void
+gdk_win32_drag_context_action_changed (GdkDragContext *context,
+                                       GdkDragAction   action)
+{
+  GdkCursor *cursor;
+
+  cursor = gdk_drag_get_cursor (context, action);
+  gdk_drag_context_set_cursor (context, cursor);
+}
+
+static GdkSurface *
+gdk_win32_drag_context_get_drag_surface (GdkDragContext *context)
+{
+  return GDK_WIN32_DRAG_CONTEXT (context)->drag_surface;
+}
+
+static void
+gdk_win32_drag_context_set_hotspot (GdkDragContext *context,
+                                    gint            hot_x,
+                                    gint            hot_y)
+{
+  GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
+
+  GDK_NOTE (DND, g_print ("gdk_drag_context_set_hotspot: 0x%p %d:%d\n",
+                          context,
+                          hot_x, hot_y));
+
+  win32_context->hot_x = hot_x;
+  win32_context->hot_y = hot_y;
+
+  if (win32_context->grab_seat)
+    {
+      /* DnD is managed, update current position */
+      move_drag_surface (context, win32_context->util_data.last_x, win32_context->util_data.last_y);
+    }
+}
+
+static void
+gdk_win32_drag_context_class_init (GdkWin32DragContextClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GdkDragContextClass *context_class = GDK_DRAG_CONTEXT_CLASS (klass);
+
+  object_class->finalize = gdk_win32_drag_context_finalize;
+
+  context_class->find_surface = gdk_win32_drag_context_find_surface;
+  context_class->drag_motion = gdk_win32_drag_context_drag_motion;
+  context_class->drag_abort = gdk_win32_drag_context_drag_abort;
+  context_class->drag_drop = gdk_win32_drag_context_drag_drop;
+  context_class->drop_status = gdk_win32_drag_context_drop_status;
+
+  context_class->get_drag_surface = gdk_win32_drag_context_get_drag_surface;
+  context_class->set_hotspot = gdk_win32_drag_context_set_hotspot;
+  context_class->drop_done = gdk_win32_drag_context_drop_done;
+  context_class->set_cursor = gdk_win32_drag_context_set_cursor;
+  context_class->cancel = gdk_win32_drag_context_cancel;
+  context_class->drop_performed = gdk_win32_drag_context_drop_performed;
+  context_class->handle_event = gdk_win32_drag_context_handle_event;
+  context_class->action_changed = gdk_win32_drag_context_action_changed;
+
+}
diff --git a/gdk/win32/gdkdrop-win32.c b/gdk/win32/gdkdrop-win32.c
new file mode 100644
index 0000000000..4fd1f7896c
--- /dev/null
+++ b/gdk/win32/gdkdrop-win32.c
@@ -0,0 +1,1262 @@
+/* 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
+
+/* For C-style COM wrapper macros */
+#define COBJMACROS
+
+#include "gdkdnd.h"
+#include "gdkproperty.h"
+#include "gdkinternals.h"
+#include "gdkprivate-win32.h"
+#include "gdkwin32.h"
+#include "gdkwin32dnd.h"
+#include "gdkdisplayprivate.h"
+#include "gdk/gdkdndprivate.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 "gdkintl.h"
+
+#include <gdk/gdk.h>
+#include <glib/gstdio.h>
+
+typedef struct
+{
+  IDropTarget                     idt;
+  gint                            ref_count;
+  GdkDragContext                 *context;
+  /* We get this at the object creation time and keep
+   * it indefinitely. Contexts come and go, but this window
+   * remains the same.
+   */
+  GdkSurface                     *dest_surface;
+  /* This is given to us by the OS, we store it here
+   * until the drag leaves our window.
+   */
+  IDataObject                    *data_object;
+} target_drag_context;
+
+static GList *dnd_target_contexts;
+static GdkDragContext *current_dest_drag = NULL;
+
+static gboolean use_ole2_dnd = TRUE;
+
+G_DEFINE_TYPE (GdkWin32DropContext, gdk_win32_drop_context, GDK_TYPE_DRAG_CONTEXT)
+
+static void
+gdk_win32_drop_context_init (GdkWin32DropContext *context)
+{
+  if (!use_ole2_dnd)
+    {
+      dnd_target_contexts = g_list_prepend (dnd_target_contexts, context);
+    }
+  else
+    {
+    }
+
+  GDK_NOTE (DND, g_print ("gdk_drop_context_init %p\n", context));
+}
+
+static void
+gdk_win32_drop_context_finalize (GObject *object)
+{
+  GdkDragContext *context;
+  GdkWin32DropContext *context_win32;
+
+  GDK_NOTE (DND, g_print ("gdk_drop_context_finalize %p\n", object));
+
+  g_return_if_fail (GDK_IS_WIN32_DROP_CONTEXT (object));
+
+  context = GDK_DRAG_CONTEXT (object);
+  context_win32 = GDK_WIN32_DROP_CONTEXT (context);
+
+  if (!use_ole2_dnd)
+    {
+      dnd_target_contexts = g_list_remove (dnd_target_contexts, context);
+
+      if (context == current_dest_drag)
+        current_dest_drag = NULL;
+    }
+
+  g_clear_object (&context_win32->local_source_context);
+
+  g_array_unref (context_win32->droptarget_w32format_contentformat_map);
+
+  G_OBJECT_CLASS (gdk_win32_drop_context_parent_class)->finalize (object);
+}
+
+/* Drag Contexts */
+
+static GdkDragContext *
+gdk_drop_context_new (GdkDisplay      *display,
+                      GdkSurface      *source_surface,
+                      GdkSurface      *dest_surface,
+                      GdkDragAction    actions,
+                      GdkDragProtocol  protocol)
+{
+  GdkWin32DropContext *context_win32;
+  GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display);
+  GdkDragContext *context;
+
+  context_win32 = g_object_new (GDK_TYPE_WIN32_DROP_CONTEXT,
+                                "display", display,
+                                NULL);
+
+  context = GDK_DRAG_CONTEXT (context_win32);
+
+  gdk_drag_context_set_device (context, gdk_seat_get_pointer (gdk_display_get_default_seat (display)));
+
+  if (win32_display->has_fixed_scale)
+    context_win32->scale = win32_display->surface_scale;
+  else
+    context_win32->scale = _gdk_win32_display_get_monitor_scale_factor (win32_display, NULL, NULL, NULL);
+
+  context_win32->droptarget_w32format_contentformat_map = g_array_new (FALSE, FALSE, sizeof 
(GdkWin32ContentFormatPair));
+
+  context->is_source = FALSE;
+  g_set_object (&context->source_surface, source_surface);
+  g_set_object (&context->dest_surface, dest_surface);
+  context->actions = actions;
+  context_win32->protocol = protocol;
+
+  return context;
+}
+
+GdkDragContext *
+_gdk_win32_drop_context_find (GdkSurface *source,
+                              GdkSurface *dest)
+{
+  GList *tmp_list = dnd_target_contexts;
+  GdkDragContext *context;
+
+  while (tmp_list)
+    {
+      context = (GdkDragContext *) tmp_list->data;
+
+      if (!context->is_source &&
+          ((source == NULL) || (context->source_surface && (context->source_surface == source))) &&
+          ((dest == NULL) || (context->dest_surface && (context->dest_surface == dest))))
+        return context;
+
+      tmp_list = tmp_list->next;
+    }
+
+  return NULL;
+}
+
+#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]);
+
+/* map windows -> target drag contexts. The table
+ * owns a ref to each context.
+ */
+static GHashTable* target_ctx_for_window = NULL;
+
+static target_drag_context *
+find_droptarget_for_target_context (GdkDragContext *context)
+{
+  return g_hash_table_lookup (target_ctx_for_window, GDK_SURFACE_HWND (context->dest_surface));
+}
+
+static ULONG STDMETHODCALLTYPE
+idroptarget_addref (LPDROPTARGET This)
+{
+  target_drag_context *ctx = (target_drag_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)
+{
+  target_drag_context *ctx = (target_drag_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_object_unref (ctx->context);
+      g_clear_object (&ctx->dest_surface);
+      g_free (This);
+    }
+
+  return ref_count;
+}
+
+static GdkDragAction
+get_suggested_action (GdkWin32DropContext *win32_context,
+                      DWORD                grfKeyState)
+{
+  /* This is the yucky Windows standard: Force link action if both
+   * Control and Alt are down, copy if Control is down alone, move if
+   * Alt is down alone, or use default of move within the app or copy
+   * when origin of the drag is in another app.
+   */
+  if (grfKeyState & MK_CONTROL && grfKeyState & MK_SHIFT)
+    return GDK_ACTION_LINK; /* Link action not supported */
+  else if (grfKeyState & MK_CONTROL)
+    return GDK_ACTION_COPY;
+  else if (grfKeyState & MK_ALT)
+    return GDK_ACTION_MOVE;
+  else if (win32_context->local_source_context &&
+           win32_context->local_source_context->util_data.state == GDK_WIN32_DND_DRAGGING)
+    return GDK_ACTION_MOVE;
+  else
+    return GDK_ACTION_COPY;
+  /* Any way to determine when to add in DROPEFFECT_SCROLL? */
+}
+
+static DWORD
+drop_effect_for_action (GdkDragAction action)
+{
+  DWORD effect = 0;
+
+  if (action & GDK_ACTION_MOVE)
+    effect |= DROPEFFECT_MOVE;
+  if (action & GDK_ACTION_LINK)
+    effect |= DROPEFFECT_LINK;
+  if (action & GDK_ACTION_COPY)
+    effect |= DROPEFFECT_COPY;
+
+  if (effect == 0)
+    effect = DROPEFFECT_NONE;
+
+  return effect;
+}
+
+static void
+dnd_event_emit (GdkEventType    type,
+                GdkDragContext *context,
+                gint            pt_x,
+                gint            pt_y,
+                GdkSurface     *dnd_surface)
+{
+  GdkEvent *e;
+
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread == g_thread_self ());
+
+  e = gdk_event_new (type);
+
+  e->any.send_event = FALSE;
+  g_set_object (&e->dnd.context, context);
+  g_set_object (&e->any.surface, dnd_surface);
+  e->dnd.time = GDK_CURRENT_TIME;
+  e->dnd.x_root = pt_x;
+  e->dnd.y_root = pt_y;
+
+  gdk_event_set_device (e, gdk_drag_context_get_device (context));
+
+  GDK_NOTE (EVENTS, _gdk_win32_print_event (e));
+  _gdk_event_emit (e);
+  gdk_event_free (e);
+}
+
+static GdkContentFormats *
+query_targets (LPDATAOBJECT  pDataObj,
+               GArray       *format_target_map)
+{
+  IEnumFORMATETC *pfmt = NULL;
+  FORMATETC fmt;
+  GList *result = NULL;
+  HRESULT hr;
+  GdkContentFormatsBuilder *builder;
+  GdkContentFormats *result_formats;
+  GList *p;
+
+  hr = IDataObject_EnumFormatEtc (pDataObj, DATADIR_GET, &pfmt);
+
+  if (SUCCEEDED (hr))
+    hr = IEnumFORMATETC_Next (pfmt, 1, &fmt, NULL);
+
+  while (SUCCEEDED (hr) && hr != S_FALSE)
+    {
+      gboolean is_predef;
+      gchar *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_add_w32format_to_pairs (fmt.cfFormat, format_target_map, &result);
+      hr = IEnumFORMATETC_Next (pfmt, 1, &fmt, NULL);
+    }
+
+  if (pfmt)
+    IEnumFORMATETC_Release (pfmt);
+
+  builder = gdk_content_formats_builder_new ();
+
+  for (p = g_list_reverse (result); p; p = p->next)
+    gdk_content_formats_builder_add_mime_type (builder, (const gchar *) p->data);
+
+  result_formats = gdk_content_formats_builder_free (builder);
+  g_list_free (result);
+
+  return result_formats;
+}
+
+static void
+set_data_object (LPDATAOBJECT *location, LPDATAOBJECT data_object)
+{
+  if (*location != NULL)
+    IDataObject_Release (*location);
+
+  *location = data_object;
+
+  if (*location != NULL)
+    IDataObject_AddRef (*location);
+}
+
+static HRESULT STDMETHODCALLTYPE
+idroptarget_dragenter (LPDROPTARGET This,
+                       LPDATAOBJECT pDataObj,
+                       DWORD        grfKeyState,
+                       POINTL       pt,
+                       LPDWORD      pdwEffect)
+{
+  target_drag_context *ctx = (target_drag_context *) This;
+  GdkDragContext *context;
+  GdkWin32DropContext *context_win32;
+  GdkDisplay *display;
+  gint pt_x;
+  gint pt_y;
+  GdkDragContext *source_context;
+
+  GDK_NOTE (DND, g_print ("idroptarget_dragenter %p @ %ld : %ld for dest window 0x%p S_OK\n", This, pt.x, 
pt.y, ctx->dest_surface));
+
+  g_clear_object (&ctx->context);
+
+  source_context = _gdk_win32_find_source_context_for_dest_surface (ctx->dest_surface);
+
+  display = gdk_surface_get_display (ctx->dest_surface);
+  context = gdk_drop_context_new (display,
+                                  /* OLE2 DnD does not allow us to get the source window,
+                                   * but we *can* find it if it's ours. This is needed to
+                                   * support DnD within the same widget, for example.
+                                   */
+                                  source_context ? source_context->source_surface : NULL,
+                                  ctx->dest_surface,
+                                  GDK_ACTION_DEFAULT | GDK_ACTION_COPY | GDK_ACTION_MOVE,
+                                  GDK_DRAG_PROTO_OLE2);
+  context_win32 = GDK_WIN32_DROP_CONTEXT (context);
+  g_array_set_size (context_win32->droptarget_w32format_contentformat_map, 0);
+  context->formats = query_targets (pDataObj, context_win32->droptarget_w32format_contentformat_map);
+  g_set_object (&context_win32->local_source_context, GDK_WIN32_DRAG_CONTEXT (source_context));
+
+  ctx->context = context;
+  context->action = GDK_ACTION_MOVE;
+  context->suggested_action = get_suggested_action (context_win32, grfKeyState);
+  set_data_object (&ctx->data_object, pDataObj);
+  pt_x = pt.x / context_win32->scale + _gdk_offset_x;
+  pt_y = pt.y / context_win32->scale + _gdk_offset_y;
+  dnd_event_emit (GDK_DRAG_ENTER, context, pt_x, pt_y, context->dest_surface);
+  dnd_event_emit (GDK_DRAG_MOTION, context, pt_x, pt_y, context->dest_surface);
+  context_win32->last_key_state = grfKeyState;
+  context_win32->last_x = pt_x;
+  context_win32->last_y = pt_y;
+  *pdwEffect = drop_effect_for_action (context->action);
+
+  GDK_NOTE (DND, g_print ("idroptarget_dragenter returns with action %d and drop effect %lu\n", 
context->action, *pdwEffect));
+
+  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 window.
+ * 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)
+{
+  target_drag_context *ctx = (target_drag_context *) This;
+  GdkWin32DropContext *context_win32 = GDK_WIN32_DROP_CONTEXT (ctx->context);
+  gint pt_x = pt.x / context_win32->scale + _gdk_offset_x;
+  gint pt_y = pt.y / context_win32->scale + _gdk_offset_y;
+
+  ctx->context->suggested_action = get_suggested_action (context_win32, grfKeyState);
+
+  GDK_NOTE (DND, g_print ("idroptarget_dragover %p @ %d : %d (raw %ld : %ld), suggests %d action S_OK\n", 
This, pt_x, pt_y, pt.x, pt.y, ctx->context->suggested_action));
+
+  if (pt_x != context_win32->last_x ||
+      pt_y != context_win32->last_y ||
+      grfKeyState != context_win32->last_key_state)
+    {
+      dnd_event_emit (GDK_DRAG_MOTION, ctx->context, pt_x, pt_y, ctx->context->dest_surface);
+      context_win32->last_key_state = grfKeyState;
+      context_win32->last_x = pt_x;
+      context_win32->last_y = pt_y;
+    }
+
+  *pdwEffect = drop_effect_for_action (ctx->context->action);
+
+  GDK_NOTE (DND, g_print ("idroptarget_dragover returns with action %d and effect %lu\n", 
ctx->context->action, *pdwEffect));
+
+  return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+idroptarget_dragleave (LPDROPTARGET This)
+{
+  target_drag_context *ctx = (target_drag_context *) This;
+
+  GDK_NOTE (DND, g_print ("idroptarget_dragleave %p S_OK\n", This));
+
+  dnd_event_emit (GDK_DRAG_LEAVE, ctx->context, 0, 0, ctx->context->dest_surface);
+
+  g_clear_object (&ctx->context);
+  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)
+{
+  target_drag_context *ctx = (target_drag_context *) This;
+  GdkWin32DropContext *context_win32 = GDK_WIN32_DROP_CONTEXT (ctx->context);
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+  gint pt_x = pt.x / context_win32->scale + _gdk_offset_x;
+  gint pt_y = pt.y / context_win32->scale + _gdk_offset_y;
+
+  GDK_NOTE (DND, g_print ("idroptarget_drop %p ", This));
+
+  if (pDataObj == NULL)
+    {
+      GDK_NOTE (DND, g_print ("E_POINTER\n"));
+      g_clear_object (&ctx->context);
+      set_data_object (&ctx->data_object, NULL);
+
+      return E_POINTER;
+    }
+
+  ctx->context->suggested_action = get_suggested_action (context_win32, grfKeyState);
+
+  dnd_event_emit (GDK_DROP_START, ctx->context, pt_x, pt_y, ctx->context->dest_surface);
+
+  /* Notify OLE of copy or move */
+  *pdwEffect = drop_effect_for_action (ctx->context->action);
+
+  g_clear_object (&ctx->context);
+  set_data_object (&ctx->data_object, NULL);
+
+  GDK_NOTE (DND, g_print ("drop S_OK with effect %lx\n", *pdwEffect));
+
+  return S_OK;
+}
+
+static IDropTargetVtbl idt_vtbl = {
+  idroptarget_queryinterface,
+  idroptarget_addref,
+  idroptarget_release,
+  idroptarget_dragenter,
+  idroptarget_dragover,
+  idroptarget_dragleave,
+  idroptarget_drop
+};
+
+static target_drag_context *
+target_context_new (GdkSurface *window)
+{
+  target_drag_context *result;
+
+  result = g_new0 (target_drag_context, 1);
+  result->idt.lpVtbl = &idt_vtbl;
+  result->ref_count = 0;
+
+  result->dest_surface = g_object_ref (window);
+
+  idroptarget_addref (&result->idt);
+
+  GDK_NOTE (DND, g_print ("target_context_new: %p (window %p)\n", result, result->dest_surface));
+
+  return result;
+}
+
+/* From MS Knowledge Base article Q130698 */
+
+static gboolean
+resolve_link (HWND     hWnd,
+              wchar_t *link,
+              gchar  **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);
+}
+
+#if 0
+
+/* 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 GdkFilterReturn
+gdk_dropfiles_filter (GdkXEvent *xev,
+                      GdkEvent  *event,
+                      gpointer   data)
+{
+  GdkDragContext *context;
+  GdkWin32DropContext *context_win32;
+  GString *result;
+  MSG *msg = (MSG *) xev;
+  HANDLE hdrop;
+  POINT pt;
+  gint nfiles, i;
+  gchar *fileName, *linkedFile;
+
+  if (msg->message == WM_DROPFILES)
+    {
+      GDK_NOTE (DND, g_print ("WM_DROPFILES: %p\n", msg->hwnd));
+
+      context = gdk_drop_context_new (gdk_surface_get_display (event->any.surface),
+                                      NULL,
+                                      event->any.surface,
+                                      GDK_ACTION_COPY,
+                                      GDK_DRAG_PROTO_WIN32_DROPFILES);
+      context_win32 = GDK_WIN32_DROP_CONTEXT (context);
+      /* WM_DROPFILES drops are always file names */
+      context->formats = gdk_content_formats_new ((const char *[2]) {
+                                                    "text/uri-list",
+                                                    NULL
+                                                  }, 1);
+
+
+      context->suggested_action = GDK_ACTION_COPY;
+      current_dest_drag = context;
+      event->any.type = GDK_DROP_START;
+      event->dnd.context = current_dest_drag;
+      gdk_event_set_device (event, gdk_drag_context_get_device (current_dest_drag));
+
+      hdrop = (HANDLE) msg->wParam;
+      DragQueryPoint (hdrop, &pt);
+      ClientToScreen (msg->hwnd, &pt);
+
+      event->dnd.x_root = pt.x / context_win32->scale + _gdk_offset_x;
+      event->dnd.y_root = pt.y / context_win32->scale + _gdk_offset_y;
+      event->dnd.time = _gdk_win32_get_next_tick (msg->time);
+
+      nfiles = DragQueryFile (hdrop, 0xFFFFFFFF, NULL, 0);
+
+      result = g_string_new (NULL);
+      for (i = 0; i < nfiles; i++)
+        {
+          gchar *uri;
+          wchar_t wfn[MAX_PATH];
+
+          DragQueryFileW (hdrop, i, wfn, MAX_PATH);
+          fileName = g_utf16_to_utf8 (wfn, -1, NULL, NULL, NULL);
+
+          /* Resolve shortcuts */
+          if (resolve_link (msg->hwnd, wfn, &linkedFile))
+            {
+              uri = g_filename_to_uri (linkedFile, NULL, NULL);
+              if (uri != NULL)
+                {
+                  g_string_append (result, uri);
+                  GDK_NOTE (DND, g_print ("... %s link to %s: %s\n",
+                                          fileName, linkedFile, uri));
+                  g_free (uri);
+                }
+              g_free (fileName);
+              fileName = linkedFile;
+            }
+          else
+            {
+              uri = g_filename_to_uri (fileName, NULL, NULL);
+              if (uri != NULL)
+                {
+                  g_string_append (result, uri);
+                  GDK_NOTE (DND, g_print ("... %s: %s\n", fileName, uri));
+                  g_free (uri);
+                }
+            }
+
+#if 0
+          /* Awful hack to recognize temp files corresponding to
+           * images dragged from Firefox... Open the file right here
+           * so that it is less likely that Firefox manages to delete
+           * it before the GTK+-using app (typically GIMP) has opened
+           * it.
+           *
+           * Not compiled in for now, because it means images dragged
+           * from Firefox would stay around in the temp folder which
+           * is not what Firefox intended. I don't feel comfortable
+           * with that, both from a geenral sanity point of view, and
+           * from a privacy point of view. It's better to wait for
+           * Firefox to fix the problem, for instance by deleting the
+           * temp file after a longer delay, or to wait until we
+           * implement the OLE2_DND...
+           */
+          if (filename_looks_tempish (fileName))
+            {
+              int fd = g_open (fileName, _O_RDONLY|_O_BINARY, 0);
+              if (fd == -1)
+                {
+                  GDK_NOTE (DND, g_print ("Could not open %s, maybe an image dragged from Firefox that it 
already deleted\n", fileName));
+                }
+              else
+                {
+                  GDK_NOTE (DND, g_print ("Opened %s as %d so that Firefox won't delete it\n", fileName, 
fd));
+                  g_timeout_add_seconds (1, close_it, GINT_TO_POINTER (fd));
+                }
+            }
+#endif
+
+          g_free (fileName);
+          g_string_append (result, "\015\012");
+        }
+
+      _gdk_dropfiles_store (result->str);
+      g_string_free (result, FALSE);
+
+      DragFinish (hdrop);
+      return GDK_FILTER_TRANSLATE;
+    }
+  else
+    return GDK_FILTER_CONTINUE;
+}
+
+/* Destination side */
+
+static void
+gdk_win32_drop_context_drag_status (GdkDragContext *context,
+                 GdkDragAction   action,
+                 guint32         time)
+{
+  GdkDragContext *src_context;
+
+  g_return_if_fail (context != NULL);
+
+  GDK_NOTE (DND, g_print ("gdk_drag_status: %s\n"
+                          " context=%p:{actions=%s,suggested=%s,action=%s}\n",
+                          _gdk_win32_drag_action_to_string (action),
+                          context,
+                          _gdk_win32_drag_action_to_string (context->actions),
+                          _gdk_win32_drag_action_to_string (context->suggested_action),
+                          _gdk_win32_drag_action_to_string (context->action)));
+
+  context->action = action;
+
+  if (!use_ole2_dnd)
+    {
+      src_context = _gdk_win32_drag_context_find (context->source_surface,
+                                                  context->dest_surface);
+
+      if (src_context)
+        {
+          _gdk_win32_drag_context_send_local_status_event (src_context, action);
+        }
+    }
+}
+
+static void
+gdk_win32_drop_context_drop_reply (GdkDragContext *context,
+                gboolean        ok,
+                guint32         time)
+{
+  g_return_if_fail (context != NULL);
+
+  GDK_NOTE (DND, g_print ("gdk_drop_reply\n"));
+
+  if (!use_ole2_dnd)
+    if (context->dest_surface)
+      {
+        if (GDK_WIN32_DRAG_CONTEXT (context)->protocol == GDK_DRAG_PROTO_WIN32_DROPFILES)
+          _gdk_dropfiles_store (NULL);
+      }
+}
+
+static void
+_gdk_display_put_event (GdkDisplay *display,
+                        GdkEvent   *event)
+{
+  g_assert (_win32_main_thread == NULL ||
+            _win32_main_thread == g_thread_self ());
+
+  gdk_event_set_display (event, display);
+  gdk_display_put_event (display, event);
+}
+
+static void
+gdk_win32_drop_context_drop_finish (GdkDragContext *context,
+                 gboolean        success,
+                 guint32         time)
+{
+  GdkDragContext *src_context;
+  GdkEvent *tmp_event;
+  GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
+
+  g_return_if_fail (context != NULL);
+
+  GDK_NOTE (DND, g_print ("gdk_drop_finish\n"));
+
+  if (!use_ole2_dnd)
+    {
+      src_context = _gdk_win32_drag_context_find (context->source_surface,
+                                                  context->dest_surface);
+      if (src_context)
+        {
+          GDK_NOTE (DND, g_print ("gdk_dnd_handle_drop_finihsed: 0x%p\n",
+                                  context));
+
+          g_signal_emit_by_name (context, "dnd-finished");
+          gdk_drag_drop_done (context, !GDK_WIN32_DROP_CONTEXT (context)->drop_failed);
+        }
+    }
+  else
+    {
+      _gdk_win32_drag_do_leave (context, time);
+
+      if (success)
+        clipdrop->dnd_target_state = GDK_WIN32_DND_DROPPED;
+      else
+        clipdrop->dnd_target_state = GDK_WIN32_DND_FAILED;
+    }
+}
+
+#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 *window)
+{
+  target_drag_context *ctx;
+  HRESULT hr;
+
+  g_return_if_fail (window != NULL);
+
+  if (g_object_get_data (G_OBJECT (window), "gdk-dnd-registered") != NULL)
+    return;
+  else
+    g_object_set_data (G_OBJECT (window), "gdk-dnd-registered", GINT_TO_POINTER (TRUE));
+
+  GDK_NOTE (DND, g_print ("gdk_surface_register_dnd: %p\n", GDK_SURFACE_HWND (window)));
+
+  if (!use_ole2_dnd)
+    {
+      /* We always claim to accept dropped files, but in fact we might not,
+       * of course. This function is called in such a way that it cannot know
+       * whether the window (widget) in question actually accepts files
+       * (in gtk, data of type text/uri-list) or not.
+       */
+      gdk_win32_display_add_filter (gdk_display_get_default (), gdk_dropfiles_filter, NULL);
+      DragAcceptFiles (GDK_SURFACE_HWND (window), TRUE);
+    }
+  else
+    {
+      /* Return if window is already setup for DND. */
+      if (g_hash_table_lookup (target_ctx_for_window, GDK_SURFACE_HWND (window)) != NULL)
+        return;
+
+      ctx = target_context_new (window);
+
+      hr = CoLockObjectExternal ((IUnknown *) &ctx->idt, TRUE, FALSE);
+      if (!SUCCEEDED (hr))
+        OTHER_API_FAILED ("CoLockObjectExternal");
+      else
+        {
+          hr = RegisterDragDrop (GDK_SURFACE_HWND (window), &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
+            {
+              g_object_ref (window);
+              g_hash_table_insert (target_ctx_for_window, GDK_SURFACE_HWND (window), ctx);
+            }
+        }
+    }
+}
+
+static gboolean
+gdk_win32_drop_context_drop_status (GdkDragContext *context)
+{
+  GdkWin32DropContext *context_win32 = GDK_WIN32_DROP_CONTEXT (context);
+
+  return ! context_win32->drop_failed;
+}
+
+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)
+    {
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+                               _("Cannot get DnD data. Failed to allocate %lu bytes to store the data."), 
length);
+      GlobalUnlock (hdata);
+      return NULL;
+    }
+
+  memcpy (data, ptr, length);
+  *data_len = length;
+
+  GlobalUnlock (hdata);
+
+  return data;
+}
+
+static void
+gdk_win32_drop_context_read_async (GdkDragContext      *context,
+                                   GdkContentFormats   *formats,
+                                   int                  io_priority,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+  GdkWin32DropContext       *context_win32 = GDK_WIN32_DROP_CONTEXT (context);
+  GTask                     *task;
+  target_drag_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 (context, cancellable, callback, user_data);
+  g_task_set_priority (task, io_priority);
+  g_task_set_source_tag (task, gdk_win32_drop_context_read_async);
+
+  tctx = find_droptarget_for_target_context (context);
+
+  if (tctx == NULL)
+    {
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+                               _("Failed to find target context record for context 0x%p"), context);
+      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);
+      return;
+    }
+
+  mime_types = gdk_content_formats_get_mime_types (formats, &n_mime_types);
+
+  for (pair = NULL, i = 0; i < n_mime_types; i++)
+    {
+      for (j = 0; j < context_win32->droptarget_w32format_contentformat_map->len; j++)
+        {
+          pair = &g_array_index (context_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"));
+      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);
+      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);
+
+              return;
+            }
+        }
+      else
+        {
+          g_assert_not_reached ();
+        }
+    }
+  else
+    {
+      _gdk_win32_transmute_windows_data (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);
+      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);
+}
+
+static GInputStream *
+gdk_win32_drop_context_read_finish (GdkDragContext  *context,
+                                    const char     **out_mime_type,
+                                    GAsyncResult    *result,
+                                    GError         **error)
+{
+  GTask *task;
+  GInputStream *stream;
+
+  g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (context)), NULL);
+  task = G_TASK (result);
+  g_return_val_if_fail (g_task_get_source_tag (task) == gdk_win32_drop_context_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_context_class_init (GdkWin32DropContextClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GdkDragContextClass *context_class = GDK_DRAG_CONTEXT_CLASS (klass);
+
+  object_class->finalize = gdk_win32_drop_context_finalize;
+
+  context_class->drag_status = gdk_win32_drop_context_drag_status;
+  context_class->drop_reply = gdk_win32_drop_context_drop_reply;
+  context_class->drop_finish = gdk_win32_drop_context_drop_finish;
+  context_class->drop_status = gdk_win32_drop_context_drop_status;
+  context_class->read_async = gdk_win32_drop_context_read_async;
+  context_class->read_finish = gdk_win32_drop_context_read_finish;
+}
+
+void
+_gdk_win32_local_send_enter (GdkDragContext *context,
+                             guint32         time)
+{
+  GdkEvent *tmp_event;
+  GdkDragContext *new_context;
+
+  GDK_NOTE (DND, g_print ("local_send_enter: context=%p current_dest_drag=%p\n",
+                          context,
+                          current_dest_drag));
+
+  if (current_dest_drag != NULL)
+    {
+      g_object_unref (G_OBJECT (current_dest_drag));
+      current_dest_drag = NULL;
+    }
+
+  new_context = gdk_drop_context_new (gdk_surface_get_display (context->source_surface),
+                                      context->source_surface,
+                                      context->dest_surface,
+                                      context->actions,
+                                      GDK_DRAG_PROTO_LOCAL);
+  new_context->formats = gdk_content_formats_ref (context->formats);
+
+  gdk_surface_set_events (new_context->source_surface,
+                          gdk_surface_get_events (new_context->source_surface) |
+                          GDK_PROPERTY_CHANGE_MASK);
+
+  tmp_event = gdk_event_new (GDK_DRAG_ENTER);
+  g_set_object (&tmp_event->any.surface, context->dest_surface);
+  tmp_event->any.send_event = FALSE;
+  g_set_object (&tmp_event->dnd.context, new_context);
+  tmp_event->dnd.time = GDK_CURRENT_TIME; /* FIXME? */
+  gdk_event_set_device (tmp_event, gdk_drag_context_get_device (context));
+
+  current_dest_drag = new_context;
+
+  GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
+  _gdk_display_put_event (gdk_device_get_display (gdk_drag_context_get_device (context)), tmp_event);
+  gdk_event_free (tmp_event);
+}
+
+void
+_gdk_drop_init (void)
+{
+  if (use_ole2_dnd)
+    {
+      target_ctx_for_window = g_hash_table_new (g_direct_hash, g_direct_equal);
+    }
+}
diff --git a/gdk/win32/gdkevents-win32.c b/gdk/win32/gdkevents-win32.c
index 7b3b75f6e5..6f8f7e7caa 100644
--- a/gdk/win32/gdkevents-win32.c
+++ b/gdk/win32/gdkevents-win32.c
@@ -57,7 +57,7 @@
 #include "gdkdevice-wintab.h"
 #include "gdkwin32dnd.h"
 #include "gdkdisplay-win32.h"
-#include "gdkselection-win32.h"
+//#include "gdkselection-win32.h"
 #include "gdkdndprivate.h"
 
 #include <windowsx.h>
@@ -780,18 +780,12 @@ _gdk_win32_print_event (const GdkEvent *event)
     CASE (GDK_CONFIGURE);
     CASE (GDK_MAP);
     CASE (GDK_UNMAP);
-    CASE (GDK_PROPERTY_NOTIFY);
-    CASE (GDK_SELECTION_CLEAR);
-    CASE (GDK_SELECTION_REQUEST);
-    CASE (GDK_SELECTION_NOTIFY);
     CASE (GDK_PROXIMITY_IN);
     CASE (GDK_PROXIMITY_OUT);
     CASE (GDK_DRAG_ENTER);
     CASE (GDK_DRAG_LEAVE);
     CASE (GDK_DRAG_MOTION);
-    CASE (GDK_DRAG_STATUS);
     CASE (GDK_DROP_START);
-    CASE (GDK_DROP_FINISHED);
     CASE (GDK_SCROLL);
     CASE (GDK_GRAB_BROKEN);
 #undef CASE
@@ -866,21 +860,10 @@ _gdk_win32_print_event (const GdkEvent *event)
               event->configure.x, event->configure.y,
               event->configure.width, event->configure.height);
       break;
-    case GDK_SELECTION_CLEAR:
-    case GDK_SELECTION_REQUEST:
-    case GDK_SELECTION_NOTIFY:
-      selection_name = (const char *)event->selection.selection;
-      target_name = (const char *)event->selection.target;
-      property_name = (const char *)event->selection.property;
-      g_print ("sel:%s tgt:%s prop:%s",
-              selection_name, target_name, property_name);
-      break;
     case GDK_DRAG_ENTER:
     case GDK_DRAG_LEAVE:
     case GDK_DRAG_MOTION:
-    case GDK_DRAG_STATUS:
     case GDK_DROP_START:
-    case GDK_DROP_FINISHED:
       if (event->dnd.context != NULL)
        g_print ("ctx:%p: %s %s src:%p dest:%p",
                 event->dnd.context,
@@ -942,11 +925,6 @@ fixup_event (GdkEvent *event)
        (event->any.type == GDK_LEAVE_NOTIFY)) &&
       (event->crossing.child_window != NULL))
     g_object_ref (event->crossing.child_window);
-  if (((event->any.type == GDK_SELECTION_CLEAR) ||
-       (event->any.type == GDK_SELECTION_NOTIFY) ||
-       (event->any.type == GDK_SELECTION_REQUEST)) &&
-      (event->selection.requestor != NULL))
-    g_object_ref (event->selection.requestor);
   event->any.send_event = InSendMessage ();
 }
 
@@ -2306,7 +2284,7 @@ gdk_event_translate (MSG  *msg,
 
   int i;
 
-  GdkWin32Selection *win32_sel = NULL;
+  GdkWin32Clipdrop *clipdrop = NULL;
 
   STGMEDIUM *property_change_data;
 
@@ -3700,118 +3678,6 @@ gdk_event_translate (MSG  *msg,
       _gdk_win32_surface_enable_transparency (window);
       break;
 
-    case WM_DESTROYCLIPBOARD:
-      win32_sel = _gdk_win32_selection_get ();
-
-      if (!win32_sel->ignore_destroy_clipboard)
-       {
-         event = gdk_event_new (GDK_SELECTION_CLEAR);
-         event->selection.window = window;
-         event->selection.selection = GDK_SELECTION_CLIPBOARD;
-         event->selection.time = _gdk_win32_get_next_tick (msg->time);
-          _gdk_win32_append_event (event);
-       }
-      else
-       {
-         return_val = TRUE;
-       }
-
-      break;
-
-    case WM_RENDERFORMAT:
-      GDK_NOTE (EVENTS, g_print (" %s", _gdk_win32_cf_to_string (msg->wParam)));
-
-      *ret_valp = 0;
-      return_val = TRUE;
-
-      win32_sel = _gdk_win32_selection_get ();
-
-      for (target = NULL, i = 0;
-           i < win32_sel->clipboard_selection_targets->len;
-           i++)
-        {
-          GdkSelTargetFormat target_format = g_array_index (win32_sel->clipboard_selection_targets, 
GdkSelTargetFormat, i);
-
-          if (target_format.format == msg->wParam)
-            {
-              target = target_format.target;
-              win32_sel->property_change_transmute = target_format.transmute;
-            }
-        }
-
-      if (target == NULL)
-        {
-          GDK_NOTE (EVENTS, g_print (" (target not found)"));
-          break;
-        }
-
-      /* We need to render to clipboard immediately, don't call
-       * _gdk_win32_append_event()
-       */
-      event = gdk_event_new (GDK_SELECTION_REQUEST);
-      event->selection.window = window;
-      event->selection.send_event = FALSE;
-      event->selection.selection = GDK_SELECTION_CLIPBOARD;
-      event->selection.target = target;
-      event->selection.property = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION);
-      event->selection.requestor = gdk_win32_handle_table_lookup (msg->hwnd);
-      event->selection.time = msg->time;
-      property_change_data = g_new0 (STGMEDIUM, 1);
-      win32_sel->property_change_data = property_change_data;
-      win32_sel->property_change_format = msg->wParam;
-      win32_sel->property_change_target_atom = target;
-
-      fixup_event (event);
-      GDK_NOTE (EVENTS, g_print (" (calling _gdk_event_emit)"));
-      GDK_NOTE (EVENTS, _gdk_win32_print_event (event));
-      _gdk_event_emit (event);
-      gdk_event_free (event);
-      win32_sel->property_change_format = 0;
-
-      /* Now the clipboard owner should have rendered */
-      if (!property_change_data->hGlobal)
-        {
-          GDK_NOTE (EVENTS, g_print (" (no _delayed_rendering_data?)"));
-        }
-      else
-        {
-          /* The requestor is holding the clipboard, no
-           * OpenClipboard() is required/possible
-           */
-          GDK_NOTE (DND,
-                    g_print (" SetClipboardData(%s,%p)",
-                             _gdk_win32_cf_to_string (msg->wParam),
-                             property_change_data->hGlobal));
-
-          API_CALL (SetClipboardData, (msg->wParam, property_change_data->hGlobal));
-        }
-
-        g_clear_pointer (&property_change_data, g_free);
-        *ret_valp = 0;
-        return_val = TRUE;
-      break;
-
-    case WM_RENDERALLFORMATS:
-      *ret_valp = 0;
-      return_val = TRUE;
-
-      win32_sel = _gdk_win32_selection_get ();
-
-      if (API_CALL (OpenClipboard, (msg->hwnd)))
-        {
-          for (target = NULL, i = 0;
-               i < win32_sel->clipboard_selection_targets->len;
-               i++)
-            {
-              GdkSelTargetFormat target_format = g_array_index (win32_sel->clipboard_selection_targets, 
GdkSelTargetFormat, i);
-              if (target_format.format != 0)
-                SendMessage (msg->hwnd, WM_RENDERFORMAT, target_format.format, 0);
-            }
-
-          API_CALL (CloseClipboard, ());
-        }
-      break;
-
     case WM_ACTIVATE:
       GDK_NOTE (EVENTS, g_print (" %s%s %p",
                                 (LOWORD (msg->wParam) == WA_ACTIVE ? "ACTIVE" :
@@ -3968,19 +3834,9 @@ gdk_event_dispatch (GSource     *source,
 
   if (event)
     {
-      GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
-
       _gdk_event_emit (event);
 
       gdk_event_free (event);
-
-      /* Do drag & drop if it is still pending */
-      if (sel_win32->dnd_source_state == GDK_WIN32_DND_PENDING)
-        {
-          sel_win32->dnd_source_state = GDK_WIN32_DND_DRAGGING;
-          _gdk_win32_dnd_do_dragdrop ();
-          sel_win32->dnd_source_state = GDK_WIN32_DND_NONE;
-        }
     }
 
   return TRUE;
diff --git a/gdk/win32/gdkglobals-win32.c b/gdk/win32/gdkglobals-win32.c
index 578d166c1e..523e7c3a3b 100644
--- a/gdk/win32/gdkglobals-win32.c
+++ b/gdk/win32/gdkglobals-win32.c
@@ -48,5 +48,7 @@ gint            _gdk_max_colors = 0;
 GdkWin32ModalOpKind      _modal_operation_in_progress = GDK_WIN32_MODAL_OP_NONE;
 HWND              _modal_move_resize_window = NULL;
 
-/* The singleton selection object pointer */
-GdkWin32Selection *_win32_selection = NULL;
+/* The singleton clipdrop object pointer */
+GdkWin32Clipdrop *_win32_clipdrop = NULL;
+
+GThread          *_win32_main_thread = NULL;
\ No newline at end of file
diff --git a/gdk/win32/gdkhdataoutputstream-win32.c b/gdk/win32/gdkhdataoutputstream-win32.c
new file mode 100644
index 0000000000..0a9d35b71b
--- /dev/null
+++ b/gdk/win32/gdkhdataoutputstream-win32.c
@@ -0,0 +1,403 @@
+/* GDK HData Output Stream - a stream backed by a global memory buffer
+ * 
+ * Copyright (C) 2018 Руслан Ижбулатов
+ *
+ * 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.1 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/>.
+ *
+ * Author: Руслан Ижбулатов <lrn1986 gmail com>
+ */
+
+#include "config.h"
+
+#include <Windows.h>
+
+#include "gdkprivate-win32.h"
+#include "gdkhdataoutputstream-win32.h"
+
+#include "gdkclipboard-win32.h"
+#include "gdkdisplay-win32.h"
+#include "gdkintl.h"
+#include "gdkwin32display.h"
+#include "gdkwin32surface.h"
+
+#include "gdkinternals.h"
+
+typedef struct _GdkWin32HDataOutputStreamPrivate  GdkWin32HDataOutputStreamPrivate;
+
+struct _GdkWin32HDataOutputStreamPrivate
+{
+  HANDLE                     handle;
+  guchar                    *data;
+  gsize                      data_allocated_size;
+  gsize                      data_length;
+  GdkWin32ContentFormatPair  pair;
+  guint                      handle_is_buffer : 1;
+  guint                      closed : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GdkWin32HDataOutputStream, gdk_win32_hdata_output_stream, G_TYPE_OUTPUT_STREAM);
+
+static gssize
+write_stream (GdkWin32HDataOutputStream         *stream,
+              GdkWin32HDataOutputStreamPrivate  *priv,
+              const void                        *buffer,
+              gsize                              count,
+              GError                           **error)
+{
+  gsize spillover = (priv->data_length + count) - priv->data_allocated_size;
+  gsize to_copy = count;
+
+  if (priv->closed)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           _("writing a closed stream"));
+      return -1;
+    }
+
+  if (spillover > 0 && !priv->handle_is_buffer)
+    {
+      guchar *new_data;
+      HANDLE new_handle = GlobalReAlloc (priv->handle, priv->data_allocated_size + spillover, 0);
+
+      if (new_handle != NULL)
+        {
+          new_data = g_try_realloc (priv->data, priv->data_allocated_size + spillover);
+
+          if (new_data != NULL)
+            {
+              priv->handle = new_handle;
+              priv->data = new_data;
+              priv->data_allocated_size += spillover;
+            }
+          else
+            {
+              g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                                   _("g_try_realloc () failed"));
+              return -1;
+            }
+        }
+      else
+        {
+          DWORD error_code = GetLastError ();
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "%s%lu", _("GlobalReAlloc() failed: "), error_code);
+          return -1;
+        }
+    }
+
+  if (priv->handle_is_buffer)
+    {
+      to_copy = MIN (count, priv->data_allocated_size - priv->data_length);
+
+      if (to_copy == 0)
+        {
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                               _("Ran out of buffer space (buffer size is fixed)"));
+          return -1;
+        }
+
+      memcpy (&((guchar *) priv->handle)[priv->data_length], buffer, to_copy);
+    }
+  else
+    memcpy (&priv->data[priv->data_length], buffer, to_copy);
+
+  priv->data_length += to_copy;
+
+  return to_copy;
+}
+
+static gssize
+gdk_win32_hdata_output_stream_write (GOutputStream  *output_stream,
+                                     const void     *buffer,
+                                     gsize           count,
+                                     GCancellable   *cancellable,
+                                     GError        **error)
+{
+  GdkWin32HDataOutputStream *stream = GDK_WIN32_HDATA_OUTPUT_STREAM (output_stream);
+  GdkWin32HDataOutputStreamPrivate *priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
+  gssize result = write_stream (stream, priv, buffer, count, error);
+
+  if (result != -1)
+    GDK_NOTE(SELECTION, g_printerr ("CLIPBOARD: wrote %zd bytes, %u total now\n",
+                                    result, priv->data_length));
+
+  return result;
+}
+
+static void
+gdk_win32_hdata_output_stream_write_async (GOutputStream        *output_stream,
+                                           const void           *buffer,
+                                           gsize                 count,
+                                           int                   io_priority,
+                                           GCancellable         *cancellable,
+                                           GAsyncReadyCallback   callback,
+                                           gpointer              user_data)
+{
+  GdkWin32HDataOutputStream *stream = GDK_WIN32_HDATA_OUTPUT_STREAM (output_stream);
+  GdkWin32HDataOutputStreamPrivate *priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
+  GTask *task;
+  gssize result;
+  GError *error = NULL;
+
+  task = g_task_new (stream, cancellable, callback, user_data);
+  g_task_set_source_tag (task, gdk_win32_hdata_output_stream_write_async);
+  g_task_set_priority (task, io_priority);
+
+  result = write_stream (stream, priv, buffer, count, &error);
+
+  if (result != -1)
+    {
+      GDK_NOTE (SELECTION, g_printerr ("CLIPBOARD async wrote %zd bytes, %u total now\n",
+                                       result, priv->data_length));
+      g_task_return_int (task, result);
+    }
+  else
+    g_task_return_error (task, error);
+
+  g_object_unref (task);
+
+  return;
+}
+
+static gssize
+gdk_win32_hdata_output_stream_write_finish (GOutputStream  *stream,
+                                            GAsyncResult   *result,
+                                            GError        **error)
+{
+  g_return_val_if_fail (g_task_is_valid (result, stream), -1);
+  g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == 
gdk_win32_hdata_output_stream_write_async, -1);
+
+  return g_task_propagate_int (G_TASK (result), error);
+}
+
+static gboolean
+gdk_win32_hdata_output_stream_close (GOutputStream  *output_stream,
+                                     GCancellable   *cancellable,
+                                     GError        **error)
+{
+  GdkWin32HDataOutputStream *stream = GDK_WIN32_HDATA_OUTPUT_STREAM (output_stream);
+  GdkWin32HDataOutputStreamPrivate *priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
+  guchar *hdata;
+
+  if (priv->closed)
+    return TRUE;
+
+  if (priv->pair.transmute)
+    {
+      guchar *transmuted_data = NULL;
+      gint    transmuted_data_length;
+
+      if (priv->handle_is_buffer)
+        {
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                               _("Can't transmute a single handle"));
+          return FALSE;
+        }
+
+      if (!_gdk_win32_transmute_contentformat (priv->pair.contentformat,
+                                               priv->pair.w32format,
+                                               priv->data,
+                                               priv->data_length,
+                                               &transmuted_data,
+                                               &transmuted_data_length))
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       _("Failed to transmute %zu bytes of data from %s to %u"),
+                       priv->data_length,
+                       priv->pair.contentformat,
+                       priv->pair.w32format);
+          return FALSE;
+        }
+      else
+        {
+          HANDLE new_handle;
+
+          new_handle = GlobalReAlloc (priv->handle, transmuted_data_length, 0);
+
+          if (new_handle == NULL)
+            {
+              DWORD error_code = GetLastError ();
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "%s%lu", _("GlobalReAlloc() failed: "), error_code);
+              return FALSE;
+            }
+
+          priv->handle = new_handle;
+          priv->data_length = transmuted_data_length;
+          g_clear_pointer (&priv->data, g_free);
+          priv->data = transmuted_data;
+        }
+    }
+
+  if (!priv->handle_is_buffer)
+    {
+      hdata = GlobalLock (priv->handle);
+
+      if (hdata == NULL)
+        {
+          DWORD error_code = GetLastError ();
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "%s%lu", _("GlobalLock() failed: "), error_code);
+          return FALSE;
+        }
+
+      memcpy (hdata, priv->data, priv->data_length);
+      GlobalUnlock (priv->handle);
+      g_clear_pointer (&priv->data, g_free);
+    }
+
+  priv->closed = 1;
+
+  return TRUE;
+}
+
+static void
+gdk_win32_hdata_output_stream_close_async (GOutputStream       *stream,
+                                           int                  io_priority,
+                                           GCancellable        *cancellable,
+                                           GAsyncReadyCallback  callback,
+                                           gpointer             user_data)
+{
+  GTask *task;
+  GError *error = NULL;
+
+  task = g_task_new (stream, cancellable, callback, user_data);
+  g_task_set_source_tag (task, gdk_win32_hdata_output_stream_close_async);
+  g_task_set_priority (task, io_priority);
+
+  if (gdk_win32_hdata_output_stream_close (stream, NULL, &error))
+    g_task_return_boolean (task, TRUE);
+  else
+    g_task_return_error (task, error);
+
+  g_object_unref (task);
+}
+
+static gboolean
+gdk_win32_hdata_output_stream_close_finish (GOutputStream  *stream,
+                                            GAsyncResult   *result,
+                                            GError        **error)
+{
+  g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
+  g_return_val_if_fail (g_async_result_is_tagged (result, gdk_win32_hdata_output_stream_close_async), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+gdk_win32_hdata_output_stream_finalize (GObject *object)
+{
+  GdkWin32HDataOutputStream *stream = GDK_WIN32_HDATA_OUTPUT_STREAM (object);
+  GdkWin32HDataOutputStreamPrivate *priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
+
+  g_clear_pointer (&priv->data, g_free);
+
+  /* We deliberately don't close the memory object,
+   * as it will be used elsewhere (it's a shame that
+   * MS global memory handles are not refcounted and
+   * not duplicateable).
+   * Except when the stream isn't closed, which means
+   * that the caller never bothered to get the handle.
+   */
+  if (!priv->closed && priv->handle)
+    {
+      if (_gdk_win32_format_uses_hdata (priv->pair.w32format))
+        GlobalFree (priv->handle);
+      else
+        CloseHandle (priv->handle);
+    }
+
+  G_OBJECT_CLASS (gdk_win32_hdata_output_stream_parent_class)->finalize (object);
+}
+
+static void
+gdk_win32_hdata_output_stream_class_init (GdkWin32HDataOutputStreamClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GOutputStreamClass *output_stream_class = G_OUTPUT_STREAM_CLASS (klass);
+
+  object_class->finalize = gdk_win32_hdata_output_stream_finalize;
+
+  output_stream_class->write_fn = gdk_win32_hdata_output_stream_write;
+  output_stream_class->close_fn = gdk_win32_hdata_output_stream_close;
+
+  output_stream_class->write_async = gdk_win32_hdata_output_stream_write_async;
+  output_stream_class->write_finish = gdk_win32_hdata_output_stream_write_finish;
+  output_stream_class->close_async = gdk_win32_hdata_output_stream_close_async;
+  output_stream_class->close_finish = gdk_win32_hdata_output_stream_close_finish;
+}
+
+static void
+gdk_win32_hdata_output_stream_init (GdkWin32HDataOutputStream *stream)
+{
+  GdkWin32HDataOutputStreamPrivate *priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
+}
+
+GOutputStream *
+gdk_win32_hdata_output_stream_new (GdkWin32ContentFormatPair  *pair,
+                                   GError                    **error)
+{
+  GdkWin32HDataOutputStream *stream;
+  GdkWin32HDataOutputStreamPrivate *priv;
+  HANDLE handle;
+  gboolean hmem;
+
+  hmem = _gdk_win32_format_uses_hdata (pair->w32format);
+
+  if (hmem)
+    {
+      handle = GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, 0);
+
+      if (handle == NULL)
+        {
+          DWORD error_code = GetLastError ();
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "%s%lu", _("GlobalAlloc() failed: "), error_code);
+
+          return NULL;
+        }
+    }
+
+  stream = g_object_new (GDK_TYPE_WIN32_HDATA_OUTPUT_STREAM, NULL);
+  priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
+  priv->pair = *pair;
+
+  if (hmem)
+    {
+      priv->handle = handle;
+    }
+  else
+    {
+      priv->data_allocated_size = sizeof (priv->handle);
+      priv->handle_is_buffer = 1;
+    }
+
+  return G_OUTPUT_STREAM (stream);
+}
+
+HANDLE
+gdk_win32_hdata_output_stream_get_handle (GdkWin32HDataOutputStream *stream,
+                                          gboolean                  *is_hdata)
+{
+  GdkWin32HDataOutputStreamPrivate *priv;
+  priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
+
+  if (!priv->closed)
+    return NULL;
+
+  if (is_hdata)
+    *is_hdata = _gdk_win32_format_uses_hdata (priv->pair.w32format);
+
+  return priv->handle;
+}
diff --git a/gdk/win32/gdkhdataoutputstream-win32.h b/gdk/win32/gdkhdataoutputstream-win32.h
new file mode 100644
index 0000000000..56fb786772
--- /dev/null
+++ b/gdk/win32/gdkhdataoutputstream-win32.h
@@ -0,0 +1,63 @@
+/* GIO - GLib Output, Output and Streaming Library
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * 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.1 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/>.
+ *
+ * Author: Benjamin Otte <otte gnome org>
+ *         Christian Kellner <gicmo gnome org>
+ */
+
+#ifndef __GDK_WIN32_HDATA_OUTPUT_STREAM_H__
+#define __GDK_WIN32_HDATA_OUTPUT_STREAM_H__
+
+#include <gio/gio.h>
+#include "gdktypes.h"
+#include "gdkclipdrop-win32.h"
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_WIN32_HDATA_OUTPUT_STREAM         (gdk_win32_hdata_output_stream_get_type ())
+#define GDK_WIN32_HDATA_OUTPUT_STREAM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
GDK_TYPE_WIN32_HDATA_OUTPUT_STREAM, GdkWin32HDataOutputStream))
+#define GDK_WIN32_HDATA_OUTPUT_STREAM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), 
GDK_TYPE_WIN32_HDATA_OUTPUT_STREAM, GdkWin32HDataOutputStreamClass))
+#define GDK_IS_WIN32_HDATA_OUTPUT_STREAM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
GDK_TYPE_WIN32_HDATA_OUTPUT_STREAM))
+#define GDK_IS_WIN32_HDATA_OUTPUT_STREAM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), 
GDK_TYPE_WIN32_HDATA_OUTPUT_STREAM))
+#define GDK_WIN32_HDATA_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
GDK_TYPE_WIN32_HDATA_OUTPUT_STREAM, GdkWin32HDataOutputStreamClass))
+
+typedef struct GdkWin32HDataOutputStream         GdkWin32HDataOutputStream;
+typedef struct GdkWin32HDataOutputStreamClass    GdkWin32HDataOutputStreamClass;
+
+typedef void (* GdkWin32HDataOutputHandler) (GOutputStream *stream, GdkWin32ContentFormatPair pair, gpointer 
user_data);
+
+struct GdkWin32HDataOutputStream
+{
+  GOutputStream parent_instance;
+};
+
+struct GdkWin32HDataOutputStreamClass
+{
+  GOutputStreamClass parent_class;
+};
+
+GType gdk_win32_hdata_output_stream_get_type            (void) G_GNUC_CONST;
+
+GOutputStream *gdk_win32_hdata_output_stream_new        (GdkWin32ContentFormatPair  *pair,
+                                                         GError                    **error);
+
+HANDLE         gdk_win32_hdata_output_stream_get_handle (GdkWin32HDataOutputStream  *stream,
+                                                         gboolean                   *is_hdata);
+
+G_END_DECLS
+
+#endif /* __GDK_WIN32_HDATA_OUTPUT_STREAM_H__ */
diff --git a/gdk/win32/gdkmain-win32.c b/gdk/win32/gdkmain-win32.c
index 49d30a3096..d76106f3cd 100644
--- a/gdk/win32/gdkmain-win32.c
+++ b/gdk/win32/gdkmain-win32.c
@@ -91,7 +91,7 @@ _gdk_win32_surfaceing_init (void)
   GDK_NOTE (EVENTS, g_print ("input_locale:%p, codepage:%d\n",
                             _gdk_input_locale, _gdk_input_codepage));
 
-  _gdk_win32_selection_init ();
+  _gdk_win32_clipdrop_init ();
 }
 
 void
diff --git a/gdk/win32/gdkprivate-win32.h b/gdk/win32/gdkprivate-win32.h
index 186a89a6ab..18684a9140 100644
--- a/gdk/win32/gdkprivate-win32.h
+++ b/gdk/win32/gdkprivate-win32.h
@@ -40,7 +40,8 @@
 #include <gdk/win32/gdkwin32screen.h>
 #include <gdk/win32/gdkwin32keys.h>
 #include <gdk/win32/gdkdevicemanager-win32.h>
-#include <gdk/win32/gdkselection-win32.h>
+#include <gdk/win32/gdkclipdrop-win32.h>
+//#include <gdk/win32/gdkselection-win32.h>
 
 #include "gdkinternals.h"
 
@@ -282,8 +283,11 @@ extern UINT              _gdk_input_codepage;
 
 extern guint             _gdk_keymap_serial;
 
-/* The singleton selection object pointer */
-GdkWin32Selection *_win32_selection;
+/* The singleton clipdrop object pointer */
+GdkWin32Clipdrop *_win32_clipdrop;
+
+/* Used to identify the main thread */
+GThread          *_win32_main_thread;
 
 void _gdk_win32_dnd_do_dragdrop (void);
 void _gdk_win32_ole2_dnd_property_change (GdkAtom       type,
@@ -412,11 +416,11 @@ void       _gdk_win32_display_create_surface_impl   (GdkDisplay    *display,
 /* stray GdkSurfaceImplWin32 members */
 void _gdk_win32_surface_register_dnd (GdkSurface *window);
 GdkDragContext *_gdk_win32_surface_drag_begin (GdkSurface         *window,
-                                               GdkDevice         *device,
-                                               GdkContentFormats *formats,
-                                               GdkDragAction      actions,
-                                               gint               x_root,
-                                               gint               y_root);
+                                               GdkDevice          *device,
+                                               GdkContentProvider *content,
+                                               GdkDragAction       actions,
+                                               gint                x_root,
+                                               gint                y_root);
 
 /* Stray GdkWin32Screen members */
 gboolean _gdk_win32_get_setting (const gchar *name, GValue *value);
@@ -458,7 +462,8 @@ BOOL WINAPI GtkShowWindow (GdkSurface *window,
 
 /* Initialization */
 void _gdk_win32_surfaceing_init (void);
-void _gdk_dnd_init    (void);
+void _gdk_drag_init    (void);
+void _gdk_drop_init    (void);
 void _gdk_events_init (GdkDisplay *display);
 
 #endif /* __GDK_PRIVATE_WIN32_H__ */
diff --git a/gdk/win32/gdkwin32dnd-private.h b/gdk/win32/gdkwin32dnd-private.h
index a6f414a9a0..4f8f872ed7 100644
--- a/gdk/win32/gdkwin32dnd-private.h
+++ b/gdk/win32/gdkwin32dnd-private.h
@@ -26,6 +26,16 @@
 
 G_BEGIN_DECLS
 
+typedef struct _GdkWin32DragContextUtilityData GdkWin32DragContextUtilityData;
+
+struct _GdkWin32DragContextUtilityData
+{
+  gint             last_x;         /* Coordinates from last event, in GDK space */
+  gint             last_y;
+  DWORD            last_key_state; /* Key state from last event */
+  GdkWin32DndState state;
+};
+
 struct _GdkWin32DragContext
 {
   GdkDragContext context;
@@ -36,30 +46,69 @@ struct _GdkWin32DragContext
   GdkDragAction actions;
   GdkDragAction current_action;
 
-  guint drag_status : 4;             /* Current status of drag */
-  guint drop_failed : 1;             /* Whether the drop was unsuccessful */
-  guint has_image_format : 1;
+  GdkWin32DragContextUtilityData util_data;
 
-  guint scale;              /* Temporarily caches the HiDPI scale */
-  gint hot_x;             /* Hotspot offset from the top-left of the drag-window, scaled (can be added to 
GDK space coordinates) */
+  guint scale;          /* Temporarily caches the HiDPI scale */
+  gint hot_x;           /* Hotspot offset from the top-left of the drag-window, scaled (can be added to GDK 
space coordinates) */
   gint hot_y;
-  gint last_x;            /* Coordinates from last event, in GDK space */
-  gint last_y;
-  gint start_x;           /* Coordinates of the drag start, in GDK space */
+  gint start_x;         /* Coordinates of the drag start, in GDK space */
   gint start_y;
-  DWORD last_key_state;     /* Key state from last event */
 
-  /* Just like context->targets, but an array, and with format IDs
+  guint drag_status : 4;             /* Current status of drag */
+  guint drop_failed : 1;             /* Whether the drop was unsuccessful */
+};
+
+struct _GdkWin32DragContextClass
+{
+  GdkDragContextClass parent_class;
+};
+
+struct _GdkWin32DropContext
+{
+  GdkDragContext context;
+  GdkDragAction actions;
+  GdkDragAction current_action;
+
+  guint scale;          /* Temporarily caches the HiDPI scale */
+  gint             last_x;         /* Coordinates from last event, in GDK space */
+  gint             last_y;
+  DWORD            last_key_state; /* Key state from last event */
+
+  /* Just like context->formats, but an array, and with format IDs
    * stored inside.
    */
-  GArray *droptarget_format_target_map;
+  GArray *droptarget_w32format_contentformat_map;
+
+  GdkWin32DragContext *local_source_context;
+
+  guint drag_status : 4;             /* Current status of drag */
+  guint drop_failed : 1;             /* Whether the drop was unsuccessful */
 };
 
-struct _GdkWin32DragContextClass
+struct _GdkWin32DropContextClass
 {
   GdkDragContextClass parent_class;
 };
 
+gpointer _gdk_win32_dnd_thread_main (gpointer data);
+
+GdkDragContext *_gdk_win32_find_source_context_for_dest_surface  (GdkSurface      *dest_surface);
+
+void            _gdk_win32_drag_context_send_local_status_event (GdkDragContext *src_context,
+                                                                 GdkDragAction   action);
+void            _gdk_win32_local_send_enter                     (GdkDragContext *context,
+                                                                 guint32         time);
+
+GdkDragContext *_gdk_win32_drag_context_find                    (GdkSurface      *source,
+                                                                 GdkSurface      *dest);
+GdkDragContext *_gdk_win32_drop_context_find                    (GdkSurface      *source,
+                                                                 GdkSurface      *dest);
+
+
+void            _gdk_win32_drag_do_leave                        (GdkDragContext *context,
+                                                                 guint32         time);
+
+
 G_END_DECLS
 
 #endif /* __GDK_WIN32_DND_PRIVATE_H__ */
diff --git a/gdk/win32/gdkwin32dnd.h b/gdk/win32/gdkwin32dnd.h
index 31a0ee85e4..a59aefa9b3 100644
--- a/gdk/win32/gdkwin32dnd.h
+++ b/gdk/win32/gdkwin32dnd.h
@@ -43,6 +43,23 @@ typedef struct _GdkWin32DragContextClass GdkWin32DragContextClass;
 GDK_AVAILABLE_IN_ALL
 GType    gdk_win32_drag_context_get_type (void);
 
+#define GDK_TYPE_WIN32_DROP_CONTEXT              (gdk_win32_drop_context_get_type ())
+#define GDK_WIN32_DROP_CONTEXT(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), 
GDK_TYPE_WIN32_DROP_CONTEXT, GdkWin32DropContext))
+#define GDK_WIN32_DROP_CONTEXT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), 
GDK_TYPE_WIN32_DROP_CONTEXT, GdkWin32DropContextClass))
+#define GDK_IS_WIN32_DROP_CONTEXT(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), 
GDK_TYPE_WIN32_DROP_CONTEXT))
+#define GDK_IS_WIN32_DROP_CONTEXT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GDK_TYPE_WIN32_DROP_CONTEXT))
+#define GDK_WIN32_DROP_CONTEXT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GDK_TYPE_WIN32_DROP_CONTEXT, GdkWin32DropContextClass))
+
+#ifdef GDK_COMPILATION
+typedef struct _GdkWin32DropContext GdkWin32DropContext;
+#else
+typedef GdkDragContext GdkWin32DropContext;
+#endif
+typedef struct _GdkWin32DropContextClass GdkWin32DropContextClass;
+
+GDK_AVAILABLE_IN_ALL
+GType    gdk_win32_drop_context_get_type (void);
+
 G_END_DECLS
 
 #endif /* __GDK_WIN32_DRAG_CONTEXT_H__ */
diff --git a/gdk/win32/meson.build b/gdk/win32/meson.build
index d609ca8d03..c1df54cda1 100644
--- a/gdk/win32/meson.build
+++ b/gdk/win32/meson.build
@@ -1,22 +1,25 @@
 gdk_win32_sources = files([
   'gdkcursor-win32.c',
+  'gdkclipboard-win32.c',
+  'gdkclipdrop-win32.c',
   'gdkdevicemanager-win32.c',
   'gdkdevice-virtual.c',
   'gdkdevice-win32.c',
   'gdkdevice-wintab.c',
   'gdkdisplay-win32.c',
   'gdkdisplaymanager-win32.c',
-  'gdkdnd-win32.c',
+  'gdkdrag-win32.c',
+  'gdkdrop-win32.c',
   'gdkevents-win32.c',
   'gdkgeometry-win32.c',
   'gdkglcontext-win32.c',
   'gdkglobals-win32.c',
+  'gdkhdataoutputstream-win32.c',
   'gdkkeys-win32.c',
   'gdkmain-win32.c',
   'gdkmonitor-win32.c',
   'gdkproperty-win32.c',
   'gdkscreen-win32.c',
-  'gdkselection-win32.c',
   'gdkvulkancontext-win32.c',
   'gdkwin32cursor.h',
   'gdkwin32display.h',



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]