GDK W32: Another massive clipboard and DnD update
authorРуслан Ижбулатов <lrn1986@gmail.com>
Sat, 24 Mar 2018 16:27:11 +0000 (16:27 +0000)
committerРуслан Ижбулатов <lrn1986@gmail.com>
Thu, 29 Mar 2018 17:43:53 +0000 (17:43 +0000)
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

17 files changed:
gdk/win32/gdkclipboard-win32.c [new file with mode: 0644]
gdk/win32/gdkclipboard-win32.h [new file with mode: 0644]
gdk/win32/gdkclipdrop-win32.c [new file with mode: 0644]
gdk/win32/gdkclipdrop-win32.h [new file with mode: 0644]
gdk/win32/gdkdisplay-win32.c
gdk/win32/gdkdisplay-win32.h
gdk/win32/gdkdrag-win32.c [new file with mode: 0644]
gdk/win32/gdkdrop-win32.c [new file with mode: 0644]
gdk/win32/gdkevents-win32.c
gdk/win32/gdkglobals-win32.c
gdk/win32/gdkhdataoutputstream-win32.c [new file with mode: 0644]
gdk/win32/gdkhdataoutputstream-win32.h [new file with mode: 0644]
gdk/win32/gdkmain-win32.c
gdk/win32/gdkprivate-win32.h
gdk/win32/gdkwin32dnd-private.h
gdk/win32/gdkwin32dnd.h
gdk/win32/meson.build

diff --git a/gdk/win32/gdkclipboard-win32.c b/gdk/win32/gdkclipboard-win32.c
new file mode 100644 (file)
index 0000000..61f3912
--- /dev/null
@@ -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 (file)
index 0000000..1152ee1
--- /dev/null
@@ -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 (file)
index 0000000..6774d6c
--- /dev/null
@@ -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 (file)
index 0000000..570f407
--- /dev/null
@@ -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__ */
index 218461ebab0a908874bb3a74a71bf8bfa797b681..b8d3bff7e8a2dc079c7a6bfd44cc9b1332f9e65c 100644 (file)
@@ -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)
index 3a46f8157bec54fbef05cf8e93bcf6e0f814fb6e..3b7376003656dffec4a8e9ac6355fdf5800ead28 100644 (file)
@@ -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 (file)
index 0000000..6706baf
--- /dev/null
@@ -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 (file)
index 0000000..4fd1f78
--- /dev/null
@@ -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);
+    }
+}
index 7b3b75f6e5e514c2653db3f09a8c16f924f1cfa5..6f8f7e7caa3176cc5c80a21279e4006335fba55b 100644 (file)
@@ -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;
index 578d166c1ed73bdf01beea8e74f1d4bbe2f7a413..523e7c3a3b2936aac75bef4d804d34d62e2007f0 100644 (file)
@@ -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 (file)
index 0000000..0a9d35b
--- /dev/null
@@ -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 (file)
index 0000000..56fb786
--- /dev/null
@@ -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__ */
index 49d30a3096ba1b8b1a92d5b7dacc21005f45dded..d76106f3cd69608c108ee0f1279418cd4d5f9f20 100644 (file)
@@ -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
index 186a89a6abf847899fab4e7591da14d67feb10c5..18684a914049f81100e52f9b35e5a664e3e19b08 100644 (file)
@@ -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__ */
index a6f414a9a0fd2804575c0b2c3cc74df181a6af48..4f8f872ed79d775bce9fc0956d931a57e726b433 100644 (file)
 
 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__ */
index 31a0ee85e433869aa538e3a87b14040fdde23a42..a59aefa9b39d14047a765b341c3df196102a2cd4 100644 (file)
@@ -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__ */
index d609ca8d0337c4b815686dff2098e321aab08125..c1df54cda1cfbdc6b52e2a198eabe33c0bcfbe9a 100644 (file)
@@ -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',