[gtk+] W32: Massive W32 DnD fix



commit 6c29e81051bd622b22fbf057a6344dd66098cc0a
Author: Руслан Ижбулатов <lrn1986 gmail com>
Date:   Sat Aug 19 12:06:27 2017 +0000

    W32: Massive W32 DnD fix
    
    Massive changes to OLE2 DnD protocol, which was completely broken before:
    * Keep GdkDragContext and OLE2 objects separate (don't ref/unref them
      together, don't necessarily create them together).
    * Keep IDataObject formats in the object itself, not in a global variable.
    * Fix getdata() to look up the request target in its format list, not in the
      global hash table
    * Create target GdkDragContext on each drag_enter, destroy it on drag_leave,
      whereas IDropTarget is created when a window becomes a drag destination
      and is re-used indefinitely.
    * Query the source IDataObject for its supported types, cache them in the
      target (!) context. This is how GTK+ works, honestly.
    * Remember current_src_object when we initiate a drag, to be able
      to detect later on that the data object is ours and use a
      shortcut when querying targets
    * Make sure GDK_DRAG_MOTION is only sent when something changes
    * Support GTK drag cursors
    * Ensure that exotic GTK clipboard formats are registered
      (but try to avoid registering formats that can't be used between applications).
    * Don't enumerate internal formats
    * Ensure that DnD indicator window can't accept drags or receive any kind of input
      (use WS_EX_TRANSPARENT).
    * Remove unneeded indentation in _gdk_win32_dnd_do_dragdrop()
    * Fix indentation in gdk_win32_drag_context_drop_finish()
    * Remove obsolete comments in _gdk_win32_window_register_dnd()
    * Check for DnD in progress when processing WM_KILLFOCUS, don't emit a grab
      break event in such cases (this allows alt-tabbing while DnD is in progress,
      though there may be lingering issues with focus after dropping...)
    * Support Shell ID List -> text/uri-list conversion, now it's possible
      to drop files (dragged from Explorer) on GTK+ applications
    * Explicitly use RegisterClipboardFormatA() when we know that the string
      is not in unicode. Otherwise explicitly use RegisterClipboardFormatW()
      with a UTF8->UTF16 converted string
    * Fix _gdk_win32_display_get_selection_owner() to correctly bail
      when selection owner HWND is NULL (looking up GdkWindow for NULL
      HWND always succeeds and returns the root window - not the intended
      effect)
    * More logging
    * Send DROP_FINISHED event after DnD loop ends
    * Send STATUS event on feedback
    * Move GetKeyboardState() and related code into _gdk_win32_window_drag_begin(),
      so that it's closer to the point where last_pt and start_pt are set
    * Use & 0x80 to check for the key being pressed. Windows will set low-order bit
      to 1 for all mouse buttons to indicate that they are toggled, so simply
      checking for the value not being 0 is not enough anymore.
      This is probably a new thing in modern W32 that didn't exist before
      (OLE2 DnD code is old).
    * Fixed (hopefully) and simplified HiDPI parts of the code.
    
    Also adds managed DnD implementation for W32 GDK backend (for both
    OLE2 and LOCAL protocols). Mostly a copy of the X11 backend code, but
    there are some minor differences:
    * doesn't use drag_window field in GdkDragContext,
      uses the one in GdkWin32DragContext exclusively
    * subtracts hotspot offset from the window coordinates when showing
      the dragback animation
    * tries to consistently support scaling and caches the scale
      in the context
    * Some keynav code is removed (places where grabbing/ungrabbing should
      happen is marked with TODOs), and the rest is probably inert.
    
    Also significantly changes the way selection (and clipboard) is handled
    (as MSDN rightly notes, the handling for DnD and Clipboard
     formats is virtually the same, so it makes sense to handle
     both with the same code):
    * Don't spam GDK_OWNER_CHANGE, send them only when owner
      actually changes
    * Open clipboard when our process becomes the clipboard owner
      (we are doing it anyway, to empty the clipboard and *become* the owner),
      and then don't close it until a scheduled selection request event
      (with TARGETS target) is received. Process that event by announcing
      all of our supported formats (by that time add_targets() should have
      been called up the stack, thus the formats are known; just in case,
      add_targets() will also schedule a selection request, if one isn't
      scheduled already, so that late-coming formats can still be announced).
    * Allow clipboard opening for selection_convert() to be delayed if it
      fails initially.
    * The last two points above should fix all the bugs about GTK+ rising
      too much ruckus over OpenClipboard() failures, as owner change
      *is allowed* to fail (though not all callers currently handle
      that case), and selection_convert() is asynchronous to begin with.
      Still, this is somewhat risky, as there's a possibility that the
      code will work in unexpected ways and the clipboard will remain open.
      There's now logging to track the clipboard being opened and closed,
      and a number of failsafes that try to ensure that it isn't kept open
      for no reason.
    * Added copious notes on the way clipboard works on X11, Windows and GDK-W32,
      also removed old comments in DnD implementation, replaced some of them
      with the new ones
    * A lot of crufty module-global variables are stuffed into a singleton
      object, GdkWin32Selection. It's technically possible to make it a
      sub-object of the Display object (the way Wayland backend does),
      but since Display object on W32 is a singleton anyway... why bother?
    * Fixed the send_change_events() a bit (was slightly broken in one of the
      previous iterations)
    * Ensure that there's no confusion between selection conversion (an artifact
      term from X11) and selection transmutation (changing the data to be W32-compatible)
    * Put all the transmutation code and format-target-matching code into gdkselection-win32.c,
      now this code isn't spread across multiple files.
    * Consequently, moved some code away from gdkproperty-win32.c and gdkdnd-win32.c
    * Extensive format transmutation checks for OLE2 DnD and clipboard.
      We now keep track of which format mappings are for transmutations,
      and which aren't (for example, when formats are passed as-is, or when
      a registered name is just an alias)
    * Put transmutation code into separate functions
    
    * Ensure that drop target keeps a format->target map for supported formats,
      this is useful when selection_convert() is called, as it only receives a
      single target and no hints on the format from which the data should
      be transmuted into this target.
    * Add clear_targets() on W32, to de called by GTK
    * Use g_set_object() instead of g_ref_object() where it is allowed.
    * Fix indentation (and convert tabs to spaces), remove unused variables
    
    (This commit is cherry-picked from the gtk-3-22 branch)
    
    https://bugzilla.gnome.org/show_bug.cgi?id=786509

 gdk/win32/gdkdisplay-win32.c    |   46 +-
 gdk/win32/gdkdnd-win32.c        | 1745 +++++++++++++++++-------
 gdk/win32/gdkevents-win32.c     |  100 +-
 gdk/win32/gdkglobals-win32.c    |   34 +-
 gdk/win32/gdkmain-win32.c       |   35 +-
 gdk/win32/gdkprivate-win32.h    |   60 +-
 gdk/win32/gdkproperty-win32.c   |  146 +--
 gdk/win32/gdkselection-win32.c  | 2918 ++++++++++++++++++++++++++++++---------
 gdk/win32/gdkselection-win32.h  |  228 +++
 gdk/win32/gdkwin32dnd-private.h |   65 +
 gdk/win32/gdkwindow-win32.c     |    8 +
 gtk/gtkdnd.c                    |    7 +
 12 files changed, 3989 insertions(+), 1403 deletions(-)
---
diff --git a/gdk/win32/gdkdisplay-win32.c b/gdk/win32/gdkdisplay-win32.c
index 61d8535..fa32842 100644
--- a/gdk/win32/gdkdisplay-win32.c
+++ b/gdk/win32/gdkdisplay-win32.c
@@ -549,7 +549,7 @@ inner_clipboard_window_procedure (HWND   hwnd,
         HWND hwnd_owner;
         HWND hwnd_opener;
         GdkEvent *event;
-        GdkWindow *owner;
+        GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
 
         hwnd_owner = GetClipboardOwner ();
 
@@ -564,14 +564,16 @@ inner_clipboard_window_procedure (HWND   hwnd,
 #ifdef G_ENABLE_DEBUG
         if (_gdk_debug_flags & GDK_DEBUG_DND)
           {
-            if (OpenClipboard (hwnd))
+            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));
 
-                CloseClipboard ();
+                if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE)
+                  CloseClipboard ();
               }
             else
               {
@@ -582,6 +584,20 @@ inner_clipboard_window_procedure (HWND   hwnd,
 
         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 ();
+          }
+
         event = gdk_event_new (GDK_OWNER_CHANGE);
         event->owner_change.window = NULL;
         event->owner_change.reason = GDK_OWNER_CHANGE_NEW_OWNER;
@@ -710,16 +726,30 @@ gdk_win32_display_request_selection_notification (GdkDisplay *display,
 static gboolean
 gdk_win32_display_supports_clipboard_persistence (GdkDisplay *display)
 {
-  return FALSE;
+  return TRUE;
 }
 
 static void
 gdk_win32_display_store_clipboard (GdkDisplay    *display,
-                            GdkWindow     *clipboard_window,
-                            guint32        time_,
-                            const GdkAtom *targets,
-                            gint           n_targets)
+                                   GdkWindow     *clipboard_window,
+                                   guint32        time_,
+                                   const GdkAtom *targets,
+                                   gint           n_targets)
 {
+  GdkEvent tmp_event;
+  SendMessage (GDK_WINDOW_HWND (clipboard_window), WM_RENDERALLFORMATS, 0, 0);
+
+  memset (&tmp_event, 0, sizeof (tmp_event));
+  tmp_event.selection.type = GDK_SELECTION_NOTIFY;
+  tmp_event.selection.window = clipboard_window;
+  tmp_event.selection.send_event = FALSE;
+  tmp_event.selection.selection = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_CLIPBOARD_MANAGER);
+  tmp_event.selection.target = 0;
+  tmp_event.selection.property = NULL;
+  tmp_event.selection.requestor = 0;
+  tmp_event.selection.time = GDK_CURRENT_TIME;
+
+  gdk_event_put (&tmp_event);
 }
 
 static gboolean
diff --git a/gdk/win32/gdkdnd-win32.c b/gdk/win32/gdkdnd-win32.c
index e6fe374..daba91e 100644
--- a/gdk/win32/gdkdnd-win32.c
+++ b/gdk/win32/gdkdnd-win32.c
@@ -31,41 +31,94 @@
 #include <fcntl.h>
 
 /*
- * Comment from the old OLE2 DND code that is being merged in. Note
- * that this comment might not fully reflect reality as the code
- * obviously will have to be modified in this merge. Especially the
- * talk about supporting other than UTF-8 text is bogus, that will not
- * happen.
- *
  * Support for OLE-2 drag and drop added at Archaeopteryx Software, 2001
  * For more information, contact Stephan R.A. Deibel (sdeibel archaeopteryx com)
  *
- * Notes on implementation:
+ * 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 targets).
+ * To account for it the data is transmuted back and forth. There are two
+ * main points of transmutation:
+ * * GDK convert selection: transmute W32 data to GTK+ data
+ * * GDK window property change: transmute GTK+ data to W32 data
+ *
+ * There are also two points where data formats are considered:
+ * * When source drag context is created, it gets a list of GTK+ targets
+ *   that it supports, these are matched to the W32 formats they
+ *   correspond to (possibly with transmutation). New W32 formats for
+ *   GTK+-specific formats are also created here (see below).
+ * * When target drag context is created, it queries the IDataObject
+ *   for the list of W32 formats it supports and matches these to
+ *   corresponding GTK+ formats that it will be able to provide
+ *   (possibly with transmutation) later. Missing GDK targets for
+ *   W32-specific formats are also created here (see below).
+ *
+ * W32 formats and GTK+ targets are both integers (CLIPFORMAT and GdkAtom
+ * respectively), but cannot be used interchangeably.
  *
- * This is a first pass at OLE2 support. It only supports text and unicode text
- * data types, and file list dnd (which is handled seperately as it predates OLE2
- * both in this implementation and on Windows in general).
+ * 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 either with 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 target format 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 target,
+ *   GDK will also offer "PNG" and CF_DIB W32 formats.
+ * * If GTK+ application provides UTF8_STRING, 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 UTF8_STRING, 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.
  *
- * As such, the data type conversion from gdk selection formats to OLE2 CF_* data
- * type specifiers is partially hardwired. Fixing this is complicated by (a) the
- * fact that the widget’s declared selection types aren’t accessible in calls here
- * that need to declare the corresponding OLE2 data types, and (b) there isn’t a
- * 1-1 correspondence between gdk target types and OLE2 types. The former needs
- * some redesign in gtk dnd (something a gdk/gtk expert should do; I have tried
- * and failed!). As an example of the latter: gdk STRING, TEXT, COMPOUND_TEXT map
- * to CF_TEXT, CF_OEMTEXT, and CF_UNICODETEXT but as a group and with conversions
- * necessary for various combinations. Currently, the code here (and in
- * gdkdnd-win32.c) can handle gdk STRING and TEXT but not COMPOUND_TEXT, and OLE2
- * CF_TEXT and CF_UNICODETEXT but not CF_OEMTEXT. The necessary conversions are
- * supplied by the implementation here.
+ * 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.
  *
- * Note that in combination with another hack originated by Archaeopteryx
- * Software, the text conversions here may go to utf-8 unicode as the standard
- * within-gtk target or to single-byte ascii when the USE_ACP_TEXT compilation
- * flag is TRUE. This mode was added to support applications that aren’t using
- * utf-8 across the gtk/gdk API but instead use single-byte ascii according to
- * the current Windows code page. See gdkim-win32.c for more info on that.
+ * To accommodate GTK+ application compaibility the code allows
+ * GTK+ applications to register drop targets that accept GTK+-specific
+ * data formats, and to register drag sources that provide GTK+-specific
+ * data formats. This is done by simply registering target atom names
+ * as clipboard formats. This way two GTK+ applications can exchange
+ * data in their native formats (both well-known ones, such as UTF8_STRING,
+ * 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.
  */
 
 /* The mingw.org compiler does not export GUIDS in it's import library. To work
@@ -74,23 +127,36 @@
 #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 "gdk/gdkdndprivate.h"
 #include "gdkdisplayprivate.h"
+#include "gdk/gdkdndprivate.h"
+#include "gdkwin32dnd-private.h"
+#include "gdkdisplay-win32.h"
+#include "gdkselection-win32.h"
+#include "gdkdeviceprivate.h"
 
 #include <ole2.h>
 
 #include <shlobj.h>
 #include <shlguid.h>
+#include <objidl.h>
 
 #include <gdk/gdk.h>
 #include <glib/gstdio.h>
 
+/* from gdkselection-win32.c */
+extern GdkAtom *known_pixbuf_formats;
+extern int n_known_pixbuf_formats;
+
+
 typedef enum {
   GDK_DRAG_STATUS_DRAG,
   GDK_DRAG_STATUS_MOTION_WAIT,
@@ -98,25 +164,6 @@ typedef enum {
   GDK_DRAG_STATUS_DROP
 } GdkDragStatus;
 
-struct _GdkWin32DragContext
-{
-  GdkDragContext context;
-
-  guint drag_status : 4;             /* Current status of drag */
-  guint drop_failed : 1;             /* Whether the drop was unsuccessful */
-
-  POINT ole2_dnd_last_pt;            /* Coordinates from last event */
-  DWORD ole2_dnd_last_key_state;     /* Key state from last event */
-  gboolean ole2_dnd_being_finalized;
-  gint ole2_dnd_ref_count;
-  IUnknown *ole2_dnd_iface;
-};
-
-struct _GdkWin32DragContextClass
-{
-  GdkDragContextClass parent_class;
-};
-
 static GList *contexts;
 static GdkDragContext *current_dest_drag = NULL;
 static gboolean use_ole2_dnd = FALSE;
@@ -124,6 +171,19 @@ static gboolean use_ole2_dnd = FALSE;
 G_DEFINE_TYPE (GdkWin32DragContext, gdk_win32_drag_context, GDK_TYPE_DRAG_CONTEXT)
 
 static void
+move_drag_window (GdkDragContext *context,
+                  guint           x_root,
+                  guint           y_root)
+{
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+
+  gdk_window_move (context_win32->drag_window,
+                   x_root - context_win32->hot_x,
+                   y_root - context_win32->hot_y);
+  gdk_window_raise (context_win32->drag_window);
+}
+
+static void
 gdk_win32_drag_context_init (GdkWin32DragContext *context)
 {
   if (!use_ole2_dnd)
@@ -132,9 +192,6 @@ gdk_win32_drag_context_init (GdkWin32DragContext *context)
     }
   else
     {
-      context->ole2_dnd_being_finalized = FALSE;
-      context->ole2_dnd_ref_count = 1;
-      context->ole2_dnd_iface = NULL;
     }
 
   GDK_NOTE (DND, g_print ("gdk_drag_context_init %p\n", context));
@@ -145,13 +202,14 @@ gdk_win32_drag_context_finalize (GObject *object)
 {
   GdkDragContext *context;
   GdkWin32DragContext *context_win32;
+  GdkWindow *drag_window;
 
   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 (object);
+  context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
 
   if (!use_ole2_dnd)
     {
@@ -160,17 +218,15 @@ gdk_win32_drag_context_finalize (GObject *object)
       if (context == current_dest_drag)
        current_dest_drag = NULL;
     }
-  else
-    {
-      if (context_win32->ole2_dnd_iface)
-       {
-          context_win32->ole2_dnd_being_finalized = TRUE;
-          context_win32->ole2_dnd_iface->lpVtbl->Release (context_win32->ole2_dnd_iface);
-          context_win32->ole2_dnd_iface = NULL;
-       }
-    }
+
+  drag_window = context_win32->drag_window;
+
+  g_array_unref (context_win32->droptarget_format_target_map);
 
   G_OBJECT_CLASS (gdk_win32_drag_context_parent_class)->finalize (object);
+
+  if (drag_window)
+    gdk_window_destroy (drag_window);
 }
 
 /* Drag Contexts */
@@ -179,6 +235,7 @@ static GdkDragContext *
 gdk_drag_context_new (GdkDisplay *display)
 {
   GdkWin32DragContext *context_win32;
+  GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display);
   GdkDragContext *context;
 
   context_win32 = g_object_new (GDK_TYPE_WIN32_DRAG_CONTEXT, NULL);
@@ -187,6 +244,13 @@ gdk_drag_context_new (GdkDisplay *display)
 
   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->window_scale;
+  else
+    context_win32->scale = _gdk_win32_display_get_monitor_scale_factor (win32_display, NULL, NULL, NULL);
+
+  context_win32->droptarget_format_target_map = g_array_new (FALSE, FALSE, sizeof (GdkSelTargetFormat));
+
   return context;
 }
 
@@ -227,36 +291,40 @@ gdk_drag_context_find (gboolean   is_source,
           ((guchar *)  guid)[14], \
           ((guchar *)  guid)[15]);
 
-
-static FORMATETC *formats;
-static int nformats;
-
 typedef struct {
   IDropTarget idt;
   GdkDragContext *context;
+
+  gint ref_count;
+  GdkWindow *dest_window;
+
 } target_drag_context;
 
 typedef struct {
   IDropSource ids;
   GdkDragContext *context;
+  gint ref_count;
 } 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;
+  data_object *dataobj;
 } enum_formats;
 
 static source_drag_context *pending_src_context = NULL;
-static IDataObject *dnd_data = NULL;
+static source_drag_context *current_src_context = NULL;
+static data_object         *current_src_object = NULL;
 
-static enum_formats *enum_formats_new (void);
+static enum_formats *enum_formats_new (data_object *dataobj);
 
 /* map windows -> target drag contexts. The table
  * owns a ref to both objects.
@@ -267,12 +335,10 @@ static ULONG STDMETHODCALLTYPE
 idroptarget_addref (LPDROPTARGET This)
 {
   target_drag_context *ctx = (target_drag_context *) This;
-  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (ctx->context);
 
-  int ref_count = ++context_win32->ole2_dnd_ref_count;
+  int ref_count = ++ctx->ref_count;
 
   GDK_NOTE (DND, g_print ("idroptarget_addref %p %d\n", This, ref_count));
-  g_object_ref (G_OBJECT (ctx->context));
 
   return ref_count;
 }
@@ -314,50 +380,25 @@ static ULONG STDMETHODCALLTYPE
 idroptarget_release (LPDROPTARGET This)
 {
   target_drag_context *ctx = (target_drag_context *) This;
-  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (ctx->context);
 
-  int ref_count = --context_win32->ole2_dnd_ref_count;
+  int ref_count = --ctx->ref_count;
 
   GDK_NOTE (DND, g_print ("idroptarget_release %p %d\n", This, ref_count));
 
-  if (!context_win32->ole2_dnd_being_finalized)
-    g_object_unref (G_OBJECT (ctx->context));
-
   if (ref_count == 0)
-    g_free (This);
-
-  return ref_count;
-}
-
-#if 0
-
-static GdkAtom
-cf_to_atom (CLIPFORMAT cf)
-{
-  switch (cf)
     {
-    case CF_UNICODETEXT:
-      return _utf8_string;
-    case CF_HDROP:
-      return _text_uri_list;
-    case CF_DIB:
-      return _image_bmp;
+      g_object_unref (ctx->context);
+      g_clear_object (&ctx->dest_window);
+      g_free (This);
     }
 
-  if (cf == _cf_url)
-    return _text_uri_list;
-
-  if (cf == _cf_html_format || cf == _cf_text_html)
-    return _text_html;
-
-  return NULL;
+  return ref_count;
 }
 
-#endif
-
 static GdkDragAction
 get_suggested_action (DWORD grfKeyState)
 {
+  GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
   /* 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
@@ -369,10 +410,8 @@ get_suggested_action (DWORD grfKeyState)
     return GDK_ACTION_COPY;
   else if (grfKeyState & MK_ALT)
     return GDK_ACTION_MOVE;
-#if 0 /* Default is always copy for now */
-  else if (_dnd_source_state == GDK_WIN32_DND_DRAGGING)
+  else if (sel_win32->dnd_source_state == GDK_WIN32_DND_DRAGGING)
     return GDK_ACTION_MOVE;
-#endif
   else
     return GDK_ACTION_COPY;
   /* Any way to determine when to add in DROPEFFECT_SCROLL? */
@@ -406,10 +445,27 @@ drop_effect_for_action (GdkDragAction action)
     }
 }
 
+static GdkDragAction
+action_for_drop_effect (DWORD effect)
+{
+  switch (effect)
+    {
+    case DROPEFFECT_MOVE:
+      return GDK_ACTION_MOVE;
+    case DROPEFFECT_LINK:
+      return GDK_ACTION_LINK;
+    case DROPEFFECT_COPY:
+      return GDK_ACTION_COPY;
+    default:
+      return 0;
+    }
+}
+
 static void
 dnd_event_put (GdkEventType    type,
               GdkDragContext *context,
-              const POINTL    pt,
+              gint            pt_x,
+              gint            pt_y,
               gboolean        to_dest_window)
 {
   GdkEvent *e;
@@ -417,17 +473,14 @@ dnd_event_put (GdkEventType    type,
   e = gdk_event_new (type);
 
   if (to_dest_window)
-    e->dnd.window = context->dest_window;
+    g_set_object (&e->dnd.window, context->dest_window);
   else
-    e->dnd.window = context->source_window;
+    g_set_object (&e->dnd.window, context->source_window);
   e->dnd.send_event = FALSE;
-  e->dnd.context = g_object_ref (context);
+  g_set_object (&e->dnd.context, context);
   e->dnd.time = GDK_CURRENT_TIME;
-  e->dnd.x_root = pt.x + _gdk_offset_x;
-  e->dnd.y_root = pt.y + _gdk_offset_y;
-
-  if (e->dnd.window != NULL)
-    g_object_ref (e->dnd.window);
+  e->dnd.x_root = pt_x;
+  e->dnd.y_root = pt_y;
 
   gdk_event_set_device (e, gdk_drag_context_get_device (context));
   gdk_event_set_seat (e, gdk_device_get_seat (gdk_drag_context_get_device (context)));
@@ -437,6 +490,70 @@ dnd_event_put (GdkEventType    type,
   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;
+
+  if ((LPDATAOBJECT) current_src_object == pDataObj)
+    return gdk_content_formats_ref (current_src_object->context->formats);
+
+  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_format_to_targets (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,
@@ -445,17 +562,54 @@ idroptarget_dragenter (LPDROPTARGET This,
                       LPDWORD      pdwEffect)
 {
   target_drag_context *ctx = (target_drag_context *) This;
+  GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
+  GdkDragContext *context;
+  GdkWin32DragContext *context_win32;
+  gint pt_x;
+  gint pt_y;
+
+  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_window));
 
-  GDK_NOTE (DND, g_print ("idroptarget_dragenter %p S_OK\n", This));
+  g_clear_object (&ctx->context);
+
+  context = gdk_drag_context_new (gdk_window_get_display (ctx->dest_window));
+  context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+  ctx->context = context;
+  g_set_object (&context->dest_window, ctx->dest_window);
+
+  context->protocol = GDK_DRAG_PROTO_OLE2;
+  context->is_source = FALSE;
+  context->source_window = NULL;
+
+  /* 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.
+   */
+  if (current_src_context && current_src_context->context)
+    g_set_object (&context->source_window, current_src_context->context->source_window);
+
+  g_set_object (&sel_win32->target_drag_context, context);
+  context->actions = GDK_ACTION_DEFAULT | GDK_ACTION_COPY | GDK_ACTION_MOVE;
+  context->suggested_action = GDK_ACTION_MOVE;
+  context->action = GDK_ACTION_MOVE;
+
+  g_array_set_size (context_win32->droptarget_format_target_map, 0);
+  context->formats = query_targets (pDataObj, context_win32->droptarget_format_target_map);
 
   ctx->context->suggested_action = get_suggested_action (grfKeyState);
-  dnd_event_put (GDK_DRAG_ENTER, ctx->context, pt, TRUE);
+  set_data_object (&sel_win32->dnd_data_object_target, pDataObj);
+  pt_x = pt.x / context_win32->scale + _gdk_offset_x;
+  pt_y = pt.y / context_win32->scale + _gdk_offset_y;
+  dnd_event_put (GDK_DRAG_ENTER, ctx->context, pt_x, pt_y, TRUE);
+  dnd_event_put (GDK_DRAG_MOTION, ctx->context, pt_x, pt_y, TRUE);
+  context_win32->last_key_state = grfKeyState;
+  context_win32->last_x = pt_x;
+  context_win32->last_y = pt_y;
   process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
   *pdwEffect = drop_effect_for_action (ctx->context->action);
 
-  /* Assume that target can accept the data: In fact it may fail but
-   * we are not really set up to query the target!
-   */
+  GDK_NOTE (DND, g_print ("idroptarget_dragenter returns with action %d and drop effect %lu\n", 
ctx->context->action, *pdwEffect));
+
   return S_OK;
 }
 
@@ -466,14 +620,30 @@ idroptarget_dragover (LPDROPTARGET This,
                      LPDWORD      pdwEffect)
 {
   target_drag_context *ctx = (target_drag_context *) This;
-
-  GDK_NOTE (DND, g_print ("idroptarget_dragover %p S_OK\n", This));
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_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 (grfKeyState);
-  dnd_event_put (GDK_DRAG_MOTION, ctx->context, pt, TRUE);
+
+  GDK_NOTE (DND, g_print ("idroptarget_dragover %p @ %ld : %ld, suggests %d action S_OK\n", This, 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_put (GDK_DRAG_MOTION, ctx->context, pt_x, pt_y, TRUE);
+      context_win32->last_key_state = grfKeyState;
+      context_win32->last_x = pt_x;
+      context_win32->last_y = pt_y;
+    }
+
   process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
+
   *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;
 }
 
@@ -481,13 +651,17 @@ static HRESULT STDMETHODCALLTYPE
 idroptarget_dragleave (LPDROPTARGET This)
 {
   target_drag_context *ctx = (target_drag_context *) This;
-  POINTL pt = { 0, 0 };
+  GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
 
   GDK_NOTE (DND, g_print ("idroptarget_dragleave %p S_OK\n", This));
 
-  dnd_event_put (GDK_DRAG_LEAVE, ctx->context, pt, TRUE);
+  dnd_event_put (GDK_DRAG_LEAVE, ctx->context, 0, 0, TRUE);
   process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
 
+  g_clear_object (&sel_win32->target_drag_context);
+  g_clear_object (&ctx->context);
+  set_data_object (&sel_win32->dnd_data_object_target, NULL);
+
   return S_OK;
 }
 
@@ -499,30 +673,36 @@ idroptarget_drop (LPDROPTARGET This,
                  LPDWORD      pdwEffect)
 {
   target_drag_context *ctx = (target_drag_context *) This;
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_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;
+  GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
 
   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);
       return E_POINTER;
     }
 
-  dnd_data = pDataObj;
-
   ctx->context->suggested_action = get_suggested_action (grfKeyState);
-  dnd_event_put (GDK_DROP_START, ctx->context, pt, TRUE);
-  process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
 
-  dnd_data = NULL;
+  dnd_event_put (GDK_DROP_START, ctx->context, pt_x, pt_y, TRUE);
+  process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
 
   /* Notify OLE of copy or move */
-  if (_dnd_target_state != GDK_WIN32_DND_DROPPED)
+  if (sel_win32->dnd_target_state != GDK_WIN32_DND_DROPPED)
     *pdwEffect = DROPEFFECT_NONE;
   else
     *pdwEffect = drop_effect_for_action (ctx->context->action);
 
-  GDK_NOTE (DND, g_print ("S_OK\n"));
+  g_clear_object (&sel_win32->target_drag_context);
+  g_clear_object (&ctx->context);
+  set_data_object (&sel_win32->dnd_data_object_target, NULL);
+
+  GDK_NOTE (DND, g_print ("drop S_OK with effect %lx\n", *pdwEffect));
 
   return S_OK;
 }
@@ -531,12 +711,10 @@ static ULONG STDMETHODCALLTYPE
 idropsource_addref (LPDROPSOURCE This)
 {
   source_drag_context *ctx = (source_drag_context *) This;
-  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (ctx->context);
 
-  int ref_count = ++context_win32->ole2_dnd_ref_count;
+  int ref_count = ++ctx->ref_count;
 
   GDK_NOTE (DND, g_print ("idropsource_addref %p %d\n", This, ref_count));
-  g_object_ref (G_OBJECT (ctx->context));
 
   return ref_count;
 }
@@ -578,17 +756,18 @@ static ULONG STDMETHODCALLTYPE
 idropsource_release (LPDROPSOURCE This)
 {
   source_drag_context *ctx = (source_drag_context *) This;
-  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (ctx->context);
 
-  int ref_count = --context_win32->ole2_dnd_ref_count;
+  int ref_count = --ctx->ref_count;
 
   GDK_NOTE (DND, g_print ("idropsource_release %p %d\n", This, ref_count));
 
-  if (!context_win32->ole2_dnd_being_finalized)
-    g_object_unref (G_OBJECT (ctx->context));
-
   if (ref_count == 0)
+  {
+    g_clear_object (&ctx->context);
+    if (current_src_context == ctx)
+      current_src_context = NULL;
     g_free (This);
+  }
 
   return ref_count;
 }
@@ -604,67 +783,76 @@ send_change_events (GdkDragContext *context,
 {
   GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
   POINT pt;
+  POINT pt_client;
   gboolean changed = FALSE;
   HWND hwnd = GDK_WINDOW_HWND (context->source_window);
   LPARAM lparam;
   WPARAM wparam;
+  gint pt_x;
+  gint pt_y;
 
   if (!API_CALL (GetCursorPos, (&pt)))
     return FALSE;
 
-  if (!API_CALL (ScreenToClient, (hwnd, &pt)))
+  pt_client = pt;
+
+  if (!API_CALL (ScreenToClient, (hwnd, &pt_client)))
     return FALSE;
 
-  if (pt.x != context_win32->ole2_dnd_last_pt.x || pt.y != context_win32->ole2_dnd_last_pt.y ||
-      key_state != context_win32->ole2_dnd_last_key_state)
+  pt_x = pt.x / context_win32->scale + _gdk_offset_x;
+  pt_y = pt.y / context_win32->scale + _gdk_offset_y;
+
+  if (pt_x != context_win32->last_x || pt_y != context_win32->last_y ||
+      key_state != context_win32->last_key_state)
     {
-      lparam = MAKELPARAM (pt.x, pt.y);
+      lparam = MAKELPARAM (pt_client.x, pt_client.y);
       wparam = key_state;
-      if (pt.x != context_win32->ole2_dnd_last_pt.x || pt.y != context_win32->ole2_dnd_last_pt.y)
+      if (pt_x != context_win32->last_x || pt_y != context_win32->last_y)
        {
          GDK_NOTE (DND, g_print ("Sending WM_MOUSEMOVE (%ld,%ld)\n", pt.x, pt.y));
          SendMessage (hwnd, WM_MOUSEMOVE, wparam, lparam);
        }
 
-      if ((key_state & MK_LBUTTON) != (context_win32->ole2_dnd_last_key_state & MK_LBUTTON))
+      if ((key_state & MK_LBUTTON) != (context_win32->last_key_state & MK_LBUTTON))
        {
          if (key_state & MK_LBUTTON)
            SendMessage (hwnd, WM_LBUTTONDOWN, wparam, lparam);
          else
            SendMessage (hwnd, WM_LBUTTONUP, wparam, lparam);
        }
-      if ((key_state & MK_MBUTTON) != (context_win32->ole2_dnd_last_key_state & MK_MBUTTON))
+      if ((key_state & MK_MBUTTON) != (context_win32->last_key_state & MK_MBUTTON))
        {
          if (key_state & MK_MBUTTON)
            SendMessage (hwnd, WM_MBUTTONDOWN, wparam, lparam);
          else
            SendMessage (hwnd, WM_MBUTTONUP, wparam, lparam);
        }
-      if ((key_state & MK_RBUTTON) != (context_win32->ole2_dnd_last_key_state & MK_RBUTTON))
+      if ((key_state & MK_RBUTTON) != (context_win32->last_key_state & MK_RBUTTON))
        {
          if (key_state & MK_RBUTTON)
            SendMessage (hwnd, WM_RBUTTONDOWN, wparam, lparam);
          else
            SendMessage (hwnd, WM_RBUTTONUP, wparam, lparam);
        }
-      if ((key_state & MK_CONTROL) != (context_win32->ole2_dnd_last_key_state & MK_CONTROL))
+      if ((key_state & MK_CONTROL) != (context_win32->last_key_state & MK_CONTROL))
        {
          if (key_state & MK_CONTROL)
            SendMessage (hwnd, WM_KEYDOWN, VK_CONTROL, 0);
          else
            SendMessage (hwnd, WM_KEYUP, VK_CONTROL, 0);
        }
-      if ((key_state & MK_SHIFT) != (context_win32->ole2_dnd_last_key_state & MK_SHIFT))
+      if ((key_state & MK_SHIFT) != (context_win32->last_key_state & MK_SHIFT))
        {
-         if (key_state & MK_CONTROL)
+         if (key_state & MK_SHIFT)
            SendMessage (hwnd, WM_KEYDOWN, VK_SHIFT, 0);
          else
            SendMessage (hwnd, WM_KEYUP, VK_SHIFT, 0);
        }
 
       changed = TRUE;
-      context_win32->ole2_dnd_last_key_state = key_state;
-      context_win32->ole2_dnd_last_pt = pt;
+      context_win32->last_key_state = key_state;
+      context_win32->last_x = pt_x;
+      context_win32->last_y = pt_y;
     }
 
   if (esc_pressed)
@@ -683,18 +871,19 @@ idropsource_querycontinuedrag (LPDROPSOURCE This,
                               DWORD        grfKeyState)
 {
   source_drag_context *ctx = (source_drag_context *) This;
+  GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
 
-  GDK_NOTE (DND, g_print ("idropsource_querycontinuedrag %p ", This));
+  GDK_NOTE (DND, g_print ("idropsource_querycontinuedrag %p esc=%d keystate=0x%lx with state %d", This, 
fEscapePressed, grfKeyState, sel_win32->dnd_source_state));
 
   if (send_change_events (ctx->context, grfKeyState, fEscapePressed))
     process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
 
-  if (_dnd_source_state == GDK_WIN32_DND_DROPPED)
+  if (sel_win32->dnd_source_state == GDK_WIN32_DND_DROPPED)
     {
       GDK_NOTE (DND, g_print ("DRAGDROP_S_DROP\n"));
       return DRAGDROP_S_DROP;
     }
-  else if (_dnd_source_state == GDK_WIN32_DND_NONE)
+  else if (sel_win32->dnd_source_state == GDK_WIN32_DND_NONE)
     {
       GDK_NOTE (DND, g_print ("DRAGDROP_S_CANCEL\n"));
       return DRAGDROP_S_CANCEL;
@@ -711,26 +900,48 @@ idropsource_givefeedback (LPDROPSOURCE This,
                          DWORD        dwEffect)
 {
   source_drag_context *ctx = (source_drag_context *) This;
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (ctx->context);
   GdkDragAction suggested_action;
+  GdkEvent *e;
+  POINT pt;
 
-  GDK_NOTE (DND, g_print ("idropsource_givefeedback %p DRAGDROP_S_USEDEFAULTCURSORS\n", This));
+  GDK_NOTE (DND, g_print ("idropsource_givefeedback %p with drop effect %lu S_OK\n", This, dwEffect));
+
+  if (!API_CALL (GetCursorPos, (&pt)))
+    return S_OK;
+
+  suggested_action = action_for_drop_effect (dwEffect);
 
-  if (dwEffect == DROPEFFECT_MOVE)
-    suggested_action = GDK_ACTION_MOVE;
-  else
-    suggested_action = GDK_ACTION_COPY;
   ctx->context->action = suggested_action;
 
   if (dwEffect == DROPEFFECT_NONE)
-    {
-      if (ctx->context->dest_window != NULL)
-       {
-         g_object_unref (ctx->context->dest_window);
-         ctx->context->dest_window = NULL;
-       }
-    }
+    g_clear_object (&ctx->context->dest_window);
+  else if (ctx->context->dest_window == NULL)
+    ctx->context->dest_window = NULL; /* FIXME: Root window was here originally. Find a substitute? */
+
+  context_win32->last_x = pt.x / context_win32->scale + _gdk_offset_x;
+  context_win32->last_y = pt.y / context_win32->scale + _gdk_offset_y;
+
+  e = gdk_event_new (GDK_DRAG_STATUS);
 
-  return DRAGDROP_S_USEDEFAULTCURSORS;
+  g_set_object (&e->dnd.window, ctx->context->source_window);
+  e->dnd.send_event = FALSE;
+  g_set_object (&e->dnd.context, ctx->context);
+  e->dnd.time = GDK_CURRENT_TIME;
+  e->dnd.x_root = context_win32->last_x;
+  e->dnd.y_root = context_win32->last_y;
+
+  gdk_event_set_device (e, gdk_drag_context_get_device (ctx->context));
+  gdk_event_set_seat (e, gdk_device_get_seat (gdk_drag_context_get_device (ctx->context)));
+
+  GDK_NOTE (EVENTS, _gdk_win32_print_event (e));
+  gdk_event_put (e);
+  gdk_event_free (e);
+  process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
+
+  GDK_NOTE (DND, g_print ("idropsource_givefeedback %p returns\n", This));
+
+  return S_OK;
 }
 
 static ULONG STDMETHODCALLTYPE
@@ -786,7 +997,10 @@ idataobject_release (LPDATAOBJECT This)
   GDK_NOTE (DND, g_print ("idataobject_release %p %d\n", This, ref_count));
 
   if (ref_count == 0)
-    g_free (This);
+    {
+      g_array_free (dobj->formats, TRUE);
+      g_free (This);
+    }
 
   return ref_count;
 }
@@ -795,7 +1009,8 @@ static HRESULT
 query (LPDATAOBJECT This,
        LPFORMATETC  pFormatEtc)
 {
-  int i;
+  data_object *ctx = (data_object *) This;
+  gint i;
 
   if (!pFormatEtc)
     return DV_E_FORMATETC;
@@ -809,24 +1024,24 @@ query (LPDATAOBJECT This,
   if ((pFormatEtc->dwAspect & DVASPECT_CONTENT) == 0)
     return DV_E_DVASPECT;
 
-  for (i = 0; i < nformats; i++)
-    if (pFormatEtc->cfFormat == formats[i].cfFormat)
+  for (i = 0; i < ctx->formats->len; i++)
+    if (pFormatEtc->cfFormat == g_array_index (ctx->formats, GdkSelTargetFormat, i).format)
       return S_OK;
 
   return DV_E_FORMATETC;
 }
 
-static FORMATETC *active_pFormatEtc = NULL;
-static STGMEDIUM *active_pMedium = NULL;
-
 static HRESULT STDMETHODCALLTYPE
 idataobject_getdata (LPDATAOBJECT This,
                     LPFORMATETC  pFormatEtc,
                     LPSTGMEDIUM  pMedium)
 {
   data_object *ctx = (data_object *) This;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
   HRESULT hr;
   GdkEvent e;
+  gint i;
+  GdkAtom target;
 
   GDK_NOTE (DND, g_print ("idataobject_getdata %p %s ",
                          This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat)));
@@ -834,38 +1049,73 @@ idataobject_getdata (LPDATAOBJECT This,
   /* Check whether we can provide requested format */
   hr = query (This, pFormatEtc);
   if (hr != S_OK)
-    return hr;
+    {
+      GDK_NOTE (DND, g_print ("Unsupported format, returning 0x%lx\n", hr));
+      return hr;
+    }
 
-  /* Append a GDK_SELECTION_GET event and then hope the app sets the
+  /* Append a GDK_SELECTION_REQUEST event and then hope the app sets the
    * property associated with the _gdk_ole2_dnd atom
    */
+  win32_sel->property_change_format = pFormatEtc->cfFormat;
+  win32_sel->property_change_data = pMedium;
+
+  for (i = 0, target = NULL; i < ctx->formats->len; i++)
+    {
+      GdkSelTargetFormat *frec = &g_array_index (ctx->formats, GdkSelTargetFormat, i);
+
+      if (frec->format == pFormatEtc->cfFormat)
+        {
+          target = frec->target;
+          win32_sel->property_change_transmute = frec->transmute;
+        }
+    }
 
-  active_pFormatEtc = pFormatEtc;
-  active_pMedium = pMedium;
+  if (target == NULL)
+    {
+      GDK_NOTE (EVENTS, g_print ("(target not found)"));
+      return E_UNEXPECTED;
+    }
+
+  GDK_NOTE (DND, {
+      gchar *target_name = gdk_atom_name (target);
+      g_print ("idataobject_getdata will request target 0x%p (%s) ",
+               target, target_name);
+      g_free (target_name);
+    });
 
+
+  memset (&e, 0, sizeof (GdkEvent));
   e.type = GDK_SELECTION_REQUEST;
-  e.selection.window = ctx->context->source_window;
+  g_set_object (&e.selection.window, ctx->context->source_window);
   e.selection.send_event = FALSE; /* ??? */
-  /* FIXME: Should really both selection and property be _gdk_ole2_dnd? */
-  e.selection.selection = _gdk_ole2_dnd;
-  /* FIXME: Target? */
-  e.selection.target = _utf8_string;
-  e.selection.property = _gdk_ole2_dnd;
+  /* Both selection and property are OLE2_DND, because change_property()
+   * will only get the property and not the selection. Theoretically we
+   * could use two different atoms (SELECTION_OLE2_DND and PROPERTY_OLE2_DND),
+   * but there's little reason to do so.
+   */
+  e.selection.selection = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND);
+  e.selection.target = target;
+  /* Requestor here is fake, just to allow the event to be processed */
+  g_set_object (&e.selection.requestor, ctx->context->source_window);
+  e.selection.property = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND);
   e.selection.time = GDK_CURRENT_TIME;
 
-  g_object_ref (e.selection.window);
-
   GDK_NOTE (EVENTS, _gdk_win32_print_event (&e));
+
   gdk_event_put (&e);
+
   process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
 
-  active_pFormatEtc = NULL;
-  active_pMedium = NULL;
+  win32_sel->property_change_format = 0;
+  win32_sel->property_change_data = 0;
 
   if (pMedium->hGlobal == NULL) {
+    GDK_NOTE (DND, g_print (" E_UNEXPECTED\n"));
     return E_UNEXPECTED;
   }
 
+  GDK_NOTE (DND, g_print (" S_OK\n"));
   return S_OK;
 }
 
@@ -888,9 +1138,9 @@ idataobject_querygetdata (LPDATAOBJECT This,
 
   hr = query (This, pFormatEtc);
 
-#define CASE(x) case x: g_print (#x)
+#define CASE(x) case x: g_print (#x "\n"); break
   GDK_NOTE (DND, {
-      g_print ("idataobject_querygetdata %p %s \n",
+      g_print ("idataobject_querygetdata %p %s ",
               This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat));
       switch (hr)
        {
@@ -941,9 +1191,9 @@ idataobject_enumformatetc (LPDATAOBJECT     This,
       return E_NOTIMPL;
     }
 
-  *ppEnumFormatEtc = &enum_formats_new ()->ief;
+  *ppEnumFormatEtc = &enum_formats_new ((data_object *) This)->ief;
 
-  GDK_NOTE (DND, g_print ("%p S_OK\n", *ppEnumFormatEtc));
+  GDK_NOTE (DND, g_print (" %p S_OK\n", *ppEnumFormatEtc));
 
   return S_OK;
 }
@@ -1031,7 +1281,10 @@ ienumformatetc_release (LPENUMFORMATETC This)
   GDK_NOTE (DND, g_print ("ienumformatetc_release %p %d\n", This, ref_count));
 
   if (ref_count == 0)
-    g_free (This);
+    {
+      idataobject_release ((LPDATAOBJECT) en->dataobj);
+      g_free (This);
+    }
 
   return ref_count;
 }
@@ -1044,15 +1297,29 @@ ienumformatetc_next (LPENUMFORMATETC This,
 {
   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 < celt; i++)
+  for (i = 0; i < formats_to_get; i++)
     {
-      if (en->ix >= nformats)
+      UINT fmt;
+      if (en->ix >= en->dataobj->formats->len)
        break;
-      elts[i] = formats[en->ix++];
+      fmt = g_array_index (en->dataobj->formats, GdkSelTargetFormat, en->ix++).format;
+      /* 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++;
     }
 
@@ -1101,7 +1368,7 @@ ienumformatetc_clone (LPENUMFORMATETC  This,
 
   GDK_NOTE (DND, g_print ("ienumformatetc_clone %p S_OK\n", This));
 
-  new = enum_formats_new ();
+  new = enum_formats_new (en->dataobj);
 
   new->ix = en->ix;
 
@@ -1157,34 +1424,17 @@ static IEnumFORMATETCVtbl ief_vtbl = {
 static target_drag_context *
 target_context_new (GdkWindow *window)
 {
-  GdkDragContext *context;
-  GdkWin32DragContext *context_win32;
   target_drag_context *result;
 
-  context = gdk_drag_context_new (gdk_window_get_display (window));
-  context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
-
   result = g_new0 (target_drag_context, 1);
-  result->context = context;
   result->idt.lpVtbl = &idt_vtbl;
+  result->ref_count = 0;
 
-  result->context->protocol = GDK_DRAG_PROTO_OLE2;
-  result->context->is_source = FALSE;
-
-  result->context->source_window = NULL;
-
-  result->context->dest_window = window;
-  g_object_ref (window);
+  result->dest_window = g_object_ref (window);
 
-  /* FIXME: context->targets? */
-  result->context->actions = GDK_ACTION_DEFAULT | GDK_ACTION_COPY | GDK_ACTION_MOVE;
-  result->context->suggested_action = GDK_ACTION_MOVE;
-  result->context->action = GDK_ACTION_MOVE;
-
-  context_win32->ole2_dnd_iface = (IUnknown *) &result->idt;
   idroptarget_addref (&result->idt);
 
-  GDK_NOTE (DND, g_print ("target_context_new: %p\n", result));
+  GDK_NOTE (DND, g_print ("target_context_new: %p (window %p)\n", result, result->dest_window));
 
   return result;
 }
@@ -1203,20 +1453,22 @@ source_context_new (GdkWindow         *window,
   result = g_new0 (source_drag_context, 1);
   result->context = context;
   result->ids.lpVtbl = &ids_vtbl;
+  result->ref_count = 0;
 
   result->context->protocol = GDK_DRAG_PROTO_OLE2;
   result->context->is_source = TRUE;
 
-  result->context->source_window = window;
-  g_object_ref (window);
+  result->context->source_window = g_object_ref (window);
 
   result->context->dest_window = NULL;
   result->context->formats = gdk_content_formats_ref (formats);
 
-  context_win32->ole2_dnd_iface = (IUnknown *) &result->ids;
   idropsource_addref (&result->ids);
 
-  GDK_NOTE (DND, g_print ("source_context_new: %p\n", result));
+  GDK_NOTE (DND, g_print ("source_context_new: %p (drag context %p)\n", result, result->context));
+
+  if (current_src_context == NULL)
+    current_src_context = result;
 
   return result;
 }
@@ -1225,12 +1477,31 @@ static data_object *
 data_object_new (GdkDragContext *context)
 {
   data_object *result;
+  GList *p;
+  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 (GdkSelTargetFormat));
+
+  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 target 0x%p\n", mime_types[i]));
+
+      added_count = _gdk_win32_add_target_to_selformats (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 format 0x%x\n", g_array_index (result->formats, 
GdkSelTargetFormat, j).format));
+    }
 
   GDK_NOTE (DND, g_print ("data_object_new: %p\n", result));
 
@@ -1238,7 +1509,7 @@ data_object_new (GdkDragContext *context)
 }
 
 static enum_formats *
-enum_formats_new (void)
+enum_formats_new (data_object *dataobj)
 {
   enum_formats *result;
 
@@ -1247,49 +1518,12 @@ enum_formats_new (void)
   result->ief.lpVtbl = &ief_vtbl;
   result->ref_count = 1;
   result->ix = 0;
+  result->dataobj = dataobj;
+  idataobject_addref ((LPDATAOBJECT) dataobj);
 
   return result;
 }
 
-void
-_gdk_win32_ole2_dnd_property_change (GdkAtom       type,
-                                    gint          format,
-                                    const guchar *data,
-                                    gint          nelements)
-{
-  if (use_ole2_dnd)
-    {
-      HGLOBAL hdata = NULL;
-
-      if (active_pFormatEtc == NULL || active_pMedium == NULL)
-       return;
-
-      /* Set up the data buffer for wide character text request */
-      if (active_pFormatEtc->cfFormat == CF_UNICODETEXT)
-       {
-         gunichar2 *wdata;
-         glong wlen;
-
-         wdata = g_utf8_to_utf16 ((const char *) data, -1, NULL, &wlen, NULL);
-         hdata = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (wlen + 1) * 2);
-         if (hdata)
-           {
-             wchar_t *ptr = (wchar_t *) GlobalLock(hdata);
-             memcpy (ptr, wdata, (wlen + 1) * 2);
-             GlobalUnlock(hdata);
-           }
-         g_free (wdata);
-       }
-      else
-       g_warning ("Only text handled for now");
-
-      /* Pack up data */
-      active_pMedium->tymed = TYMED_HGLOBAL;
-      active_pMedium->hGlobal = hdata;
-      active_pMedium->pUnkForRelease = 0;
-    }
-}
-
 /* From MS Knowledge Base article Q130698 */
 
 static gboolean
@@ -1411,6 +1645,7 @@ gdk_dropfiles_filter (GdkXEvent *xev,
                      gpointer   data)
 {
   GdkDragContext *context;
+  GdkWin32DragContext *context_win32;
   GString *result;
   MSG *msg = (MSG *) xev;
   HANDLE hdrop;
@@ -1426,18 +1661,19 @@ gdk_dropfiles_filter (GdkXEvent *xev,
       GDK_NOTE (DND, g_print ("WM_DROPFILES: %p\n", msg->hwnd));
 
       context = gdk_drag_context_new (gdk_window_get_display (event->any.window));
+      context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
       context->protocol = GDK_DRAG_PROTO_WIN32_DROPFILES;
       context->is_source = FALSE;
 
       context->source_window = NULL;
 
-      context->dest_window = event->any.window;
-      g_object_ref (context->dest_window);
+      g_set_object (&context->dest_window, event->any.window);
 
       /* WM_DROPFILES drops are always file names */
-      g_ptr_array_add (formats, _text_uri_list);
-      context->formats = gdk_content_formats_new ((const char **) formats->pdata, formats->len);
-      g_ptr_array_unref (formats);
+      context->formats = gdk_content_formats_new ((const char *[2]) {
+                                                    "text/uri-list",
+                                                    NULL
+                                                  }, 1);
 
       context->actions = GDK_ACTION_COPY;
       context->suggested_action = GDK_ACTION_COPY;
@@ -1452,8 +1688,8 @@ gdk_dropfiles_filter (GdkXEvent *xev,
       DragQueryPoint (hdrop, &pt);
       ClientToScreen (msg->hwnd, &pt);
 
-      event->dnd.x_root = pt.x + _gdk_offset_x;
-      event->dnd.y_root = pt.y + _gdk_offset_y;
+      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);
@@ -1537,20 +1773,6 @@ gdk_dropfiles_filter (GdkXEvent *xev,
     return GDK_FILTER_CONTINUE;
 }
 
-static void
-add_format (GArray *fmts,
-           CLIPFORMAT cf)
-{
-  FORMATETC fmt;
-
-  fmt.cfFormat = cf;
-  fmt.ptd = NULL;
-  fmt.dwAspect = DVASPECT_CONTENT;
-  fmt.lindex = -1;
-  fmt.tymed = TYMED_HGLOBAL;
-
-  g_array_append_val (fmts, fmt);
-}
 
 
 void
@@ -1564,35 +1786,12 @@ _gdk_dnd_init (void)
   if (use_ole2_dnd)
     {
       HRESULT hr;
-      GArray *fmts;
 
       hr = OleInitialize (NULL);
 
       if (! SUCCEEDED (hr))
        g_error ("OleInitialize failed");
 
-      fmts = g_array_new (FALSE, FALSE, sizeof (FORMATETC));
-
-      /* The most important presumably */
-      add_format (fmts, CF_UNICODETEXT);
-
-      /* Used for GTK+ internal DND, I think was the intent? Anyway, code below assumes
-       * this is at index 1.
-       */
-      add_format (fmts, CF_GDIOBJFIRST);
-
-      add_format (fmts, CF_HDROP);
-
-      add_format (fmts, _cf_png);
-      add_format (fmts, CF_DIB);
-
-      add_format (fmts, _cf_url);
-      add_format (fmts, _cf_html_format);
-      add_format (fmts, _cf_text_html);
-
-      nformats = fmts->len;
-      formats = (FORMATETC*) g_array_free (fmts, FALSE);
-
       target_ctx_for_window = g_hash_table_new (g_direct_hash, g_direct_equal);
     }
 }
@@ -1626,10 +1825,10 @@ local_send_leave (GdkDragContext *context,
     {
       tmp_event = gdk_event_new (GDK_DRAG_LEAVE);
 
-      tmp_event->dnd.window = g_object_ref (context->dest_window);
+      g_set_object (&tmp_event->dnd.window, context->dest_window);
       /* Pass ownership of context to the event */
       tmp_event->dnd.send_event = FALSE;
-      tmp_event->dnd.context = g_object_ref (current_dest_drag);
+      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));
       gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (context)));
@@ -1663,11 +1862,8 @@ local_send_enter (GdkDragContext *context,
   new_context->protocol = GDK_DRAG_PROTO_LOCAL;
   new_context->is_source = FALSE;
 
-  new_context->source_window = context->source_window;
-  g_object_ref (new_context->source_window);
-
-  new_context->dest_window = context->dest_window;
-  g_object_ref (new_context->dest_window);
+  g_set_object (&new_context->source_window, context->source_window);
+  g_set_object (&new_context->dest_window, context->dest_window);
 
   new_context->formats = gdk_content_formats_ref (context->formats);
 
@@ -1677,9 +1873,9 @@ local_send_enter (GdkDragContext *context,
   new_context->actions = context->actions;
 
   tmp_event = gdk_event_new (GDK_DRAG_ENTER);
-  tmp_event->dnd.window = g_object_ref (context->dest_window);
+  g_set_object (&tmp_event->dnd.window, context->dest_window);
   tmp_event->dnd.send_event = FALSE;
-  tmp_event->dnd.context = g_object_ref (new_context);
+  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));
   gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (context)));
@@ -1712,9 +1908,9 @@ local_send_motion (GdkDragContext *context,
       GdkWin32DragContext *current_dest_drag_win32;
 
       tmp_event = gdk_event_new (GDK_DRAG_MOTION);
-      tmp_event->dnd.window = g_object_ref (current_dest_drag->dest_window);
+      g_set_object (&tmp_event->dnd.window, current_dest_drag->dest_window);
       tmp_event->dnd.send_event = FALSE;
-      tmp_event->dnd.context = g_object_ref (current_dest_drag);
+      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));
       gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (current_dest_drag)));
@@ -1725,8 +1921,8 @@ local_send_motion (GdkDragContext *context,
       tmp_event->dnd.y_root = y_root;
 
       current_dest_drag_win32 = GDK_WIN32_DRAG_CONTEXT (current_dest_drag);
-      current_dest_drag_win32->ole2_dnd_last_pt.x = x_root - _gdk_offset_x;
-      current_dest_drag_win32->ole2_dnd_last_pt.y = y_root - _gdk_offset_y;
+      current_dest_drag_win32->last_x = x_root;
+      current_dest_drag_win32->last_y = y_root;
 
       context_win32->drag_status = GDK_DRAG_STATUS_MOTION_WAIT;
 
@@ -1754,16 +1950,16 @@ local_send_drop (GdkDragContext *context,
 
       /* Pass ownership of context to the event */
       tmp_event = gdk_event_new (GDK_DROP_START);
-      tmp_event->dnd.window = g_object_ref (current_dest_drag->dest_window);
+      g_set_object (&tmp_event->dnd.window, current_dest_drag->dest_window);
       tmp_event->dnd.send_event = FALSE;
-      tmp_event->dnd.context = g_object_ref (current_dest_drag);
+      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));
       gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (current_dest_drag)));
 
       context_win32 = GDK_WIN32_DRAG_CONTEXT (current_dest_drag);
-      tmp_event->dnd.x_root = context_win32->ole2_dnd_last_pt.x + _gdk_offset_x;
-      tmp_event->dnd.y_root = context_win32->ole2_dnd_last_pt.y + _gdk_offset_y;
+      tmp_event->dnd.x_root = context_win32->last_x;
+      tmp_event->dnd.y_root = context_win32->last_y;
 
       current_dest_drag = NULL;
 
@@ -1788,11 +1984,22 @@ gdk_drag_do_leave (GdkDragContext *context,
            local_send_leave (context, time);
        }
 
-      g_object_unref (context->dest_window);
-      context->dest_window = NULL;
+      g_clear_object (&context->dest_window);
     }
 }
 
+static GdkWindow *
+create_drag_window (GdkDisplay *display)
+{
+  GdkWindow *window;
+
+  window = gdk_window_new_popup (display, &(GdkRectangle) { 0, 0, 100, 100 });
+
+  gdk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DND);
+  
+  return window;
+}
+
 GdkDragContext *
 _gdk_win32_window_drag_begin (GdkWindow         *window,
                               GdkDevice         *device,
@@ -1800,22 +2007,23 @@ _gdk_win32_window_drag_begin (GdkWindow         *window,
                               gint               x_root,
                               gint               y_root)
 {
+  GdkDragContext *new_context;
+  GdkWin32DragContext *context_win32;
+  BYTE kbd_state[256];
+  GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
+
   if (!use_ole2_dnd)
     {
-      GdkDragContext *new_context;
-
       g_return_val_if_fail (window != NULL, NULL);
 
       new_context = gdk_drag_context_new (gdk_window_get_display (window));
 
       new_context->is_source = TRUE;
-      new_context->source_window = window;
-      g_object_ref (window);
+      g_set_object (&new_context->source_window, window);
 
       new_context->formats = gdk_content_formats_ref (formats);
       new_context->actions = 0;
-
-      return new_context;
+      context_win32 = GDK_WIN32_DRAG_CONTEXT (new_context);
     }
   else
     {
@@ -1827,121 +2035,112 @@ _gdk_win32_window_drag_begin (GdkWindow         *window,
 
       ctx = source_context_new (window, formats);
 
-      _dnd_source_state = GDK_WIN32_DND_PENDING;
+      sel_win32->dnd_source_state = GDK_WIN32_DND_PENDING;
 
       pending_src_context = ctx;
-      g_object_ref (ctx->context);
+      new_context = g_object_ref (ctx->context);
 
-      return ctx->context;
+      context_win32 = GDK_WIN32_DRAG_CONTEXT (new_context);
     }
-}
-
-void
-_gdk_win32_dnd_do_dragdrop (void)
-{
-  if (use_ole2_dnd)
-    {
-      GdkDragContext* drag_ctx;
-      GdkWin32DragContext *drag_ctx_win32;
-      BYTE kbd_state[256];
-      data_object *dobj;
-      HRESULT hr;
-      DWORD dwEffect;
 
-#if 0
-      HGLOBAL global;
-      STGMEDIUM medium;
-#endif
+  context_win32->start_x = x_root;
+  context_win32->start_y = y_root;
+  context_win32->last_x = context_win32->start_x;
+  context_win32->last_y = context_win32->start_y;
 
-      if (pending_src_context == NULL)
-       return;
+  context_win32->last_key_state = 0;
+  API_CALL (GetKeyboardState, (kbd_state));
 
-      drag_ctx = pending_src_context->context;
-      drag_ctx_win32 = GDK_WIN32_DRAG_CONTEXT (drag_ctx);
+  if (kbd_state[VK_CONTROL] & 0x80)
+    context_win32->last_key_state |= MK_CONTROL;
+  if (kbd_state[VK_SHIFT] & 0x80)
+    context_win32->last_key_state |= MK_SHIFT;
+  if (kbd_state[VK_LBUTTON] & 0x80)
+    context_win32->last_key_state |= MK_LBUTTON;
+  if (kbd_state[VK_MBUTTON] & 0x80)
+    context_win32->last_key_state |= MK_MBUTTON;
+  if (kbd_state[VK_RBUTTON] & 0x80)
+    context_win32->last_key_state |= MK_RBUTTON;
 
-      dobj = data_object_new (drag_ctx);
+  context_win32->drag_window = create_drag_window (gdk_window_get_display (window));
 
-      API_CALL (GetCursorPos, (&drag_ctx_win32->ole2_dnd_last_pt));
-      API_CALL (ScreenToClient, (GDK_WINDOW_HWND (drag_ctx->source_window), 
&drag_ctx_win32->ole2_dnd_last_pt));
-      drag_ctx_win32->ole2_dnd_last_key_state = 0;
-      API_CALL (GetKeyboardState, (kbd_state));
+  return new_context;
+}
 
-      if (kbd_state[VK_CONTROL])
-        drag_ctx_win32->ole2_dnd_last_key_state |= MK_CONTROL;
-      if (kbd_state[VK_SHIFT])
-        drag_ctx_win32->ole2_dnd_last_key_state |= MK_SHIFT;
-      if (kbd_state[VK_LBUTTON])
-        drag_ctx_win32->ole2_dnd_last_key_state |= MK_LBUTTON;
-      if (kbd_state[VK_MBUTTON])
-        drag_ctx_win32->ole2_dnd_last_key_state |= MK_MBUTTON;
-      if (kbd_state[VK_RBUTTON])
-        drag_ctx_win32->ole2_dnd_last_key_state |= MK_RBUTTON;
+void
+_gdk_win32_dnd_do_dragdrop (void)
+{
+  GdkDragContext* drag_ctx;
+  data_object *dobj;
+  HRESULT hr;
+  DWORD dwEffect;
 
-#if 0
-      global = GlobalAlloc (GMEM_FIXED, sizeof (ctx));
+  if (!use_ole2_dnd)
+    return;
 
-      memcpy (&global, ctx, sizeof (ctx));
+  if (pending_src_context == NULL)
+    return;
 
-      medium.tymed = TYMED_HGLOBAL;
-      medium.hGlobal = global;
-      medium.pUnkForRelease = NULL;
+  drag_ctx = pending_src_context->context;
 
-      /* FIXME I wish I remember what I was thinking of here, i.e. what
-       * the formats[1] signifies, i.e. the CF_GDIOBJFIRST FORMATETC?
-       */
-      dobj->ido.lpVtbl->SetData (&dobj->ido, &formats[1], &medium, TRUE);
-#endif
+  dobj = data_object_new (drag_ctx);
+  current_src_object = dobj;
 
-      /* Start dragging with mainloop inside the OLE2 API. Exits only when done */
+  /* Start dragging with mainloop inside the OLE2 API. Exits only when done */
 
-      GDK_NOTE (DND, g_print ("Calling DoDragDrop\n"));
+  GDK_NOTE (DND, g_print ("Calling DoDragDrop\n"));
 
-      _gdk_win32_begin_modal_call (GDK_WIN32_MODAL_OP_DND);
-      hr = DoDragDrop (&dobj->ido, &pending_src_context->ids,
-                      DROPEFFECT_COPY | DROPEFFECT_MOVE,
-                      &dwEffect);
-      _gdk_win32_end_modal_call (GDK_WIN32_MODAL_OP_DND);
+  _gdk_win32_begin_modal_call (GDK_WIN32_MODAL_OP_DND);
+  hr = DoDragDrop (&dobj->ido, &pending_src_context->ids,
+              DROPEFFECT_COPY | DROPEFFECT_MOVE,
+              &dwEffect);
+  _gdk_win32_end_modal_call (GDK_WIN32_MODAL_OP_DND);
 
-      GDK_NOTE (DND, g_print ("DoDragDrop returned %s\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))))));
+  GDK_NOTE (DND, g_print ("DoDragDrop returned %s\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))))));
 
-      /* Delete dnd selection after successful move */
-      if (hr == DRAGDROP_S_DROP && dwEffect == DROPEFFECT_MOVE)
-       {
-         GdkEvent tmp_event;
-
-         tmp_event.type = GDK_SELECTION_REQUEST;
-         tmp_event.selection.window = drag_ctx->source_window;
-         tmp_event.selection.send_event = FALSE;
-         tmp_event.selection.selection = _gdk_ole2_dnd;
-         tmp_event.selection.target = _delete;
-         tmp_event.selection.property = _gdk_ole2_dnd; /* ??? */
-         tmp_event.selection.time = GDK_CURRENT_TIME; /* ??? */
-         g_object_ref (tmp_event.selection.window);
-
-         GDK_NOTE (EVENTS, _gdk_win32_print_event (&tmp_event));
-         gdk_event_put (&tmp_event);
-       }
+  /* Delete dnd selection after successful move */
+  if (hr == DRAGDROP_S_DROP && dwEffect == DROPEFFECT_MOVE)
+    {
+      GdkEvent tmp_event;
+
+      memset (&tmp_event, 0, sizeof (tmp_event));
+      tmp_event.type = GDK_SELECTION_REQUEST;
+      g_set_object (&tmp_event.selection.window, drag_ctx->source_window);
+      tmp_event.selection.send_event = FALSE;
+      tmp_event.selection.selection = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND);
+      tmp_event.selection.target = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_DELETE);
+      tmp_event.selection.property = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND);
+      g_set_object (&tmp_event.selection.requestor, drag_ctx->source_window);
+      tmp_event.selection.time = GDK_CURRENT_TIME; /* ??? */
+
+      GDK_NOTE (EVENTS, _gdk_win32_print_event (&tmp_event));
+      gdk_event_put (&tmp_event);
+    }
 
-#if 0
-      // Send a GDK_DROP_FINISHED to the source window
-      GetCursorPos (&pt);
-      ptl.x = pt.x;
-      ptl.y = pt.y;
-      if ( pending_src_context != NULL && pending_src_context->context != NULL
-          && pending_src_context->context->source_window != NULL )
-       push_dnd_event (GDK_DROP_FINISHED, pending_src_context->context, ptl, FALSE);
-#endif
+  {
+    GdkEvent *tmp_event;
+    tmp_event = gdk_event_new (GDK_DROP_FINISHED);
+    g_set_object (&tmp_event->dnd.window, drag_ctx->source_window);
+    tmp_event->dnd.send_event = FALSE;
+    g_set_object (&tmp_event->dnd.context, drag_ctx);
+    gdk_event_set_device (tmp_event, gdk_drag_context_get_device (drag_ctx));
+    gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (drag_ctx)));
+
+    GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
+    gdk_event_put (tmp_event);
+    gdk_event_free (tmp_event);
+  }
 
-      dobj->ido.lpVtbl->Release (&dobj->ido);
-      if (pending_src_context != NULL)
-       {
-         pending_src_context->ids.lpVtbl->Release (&pending_src_context->ids);
-         pending_src_context = NULL;
-       }
+  current_src_object = NULL;
+  dobj->ido.lpVtbl->Release (&dobj->ido);
+  if (pending_src_context != NULL)
+    {
+      pending_src_context->ids.lpVtbl->Release (&pending_src_context->ids);
+      pending_src_context = NULL;
     }
 }
 
@@ -2017,14 +2216,20 @@ gdk_win32_drag_context_find_window (GdkDragContext  *context,
                                    gint             y_root,
                                    GdkDragProtocol *protocol)
 {
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
   GdkWindow *dest_window, *dw;
   find_window_enum_arg a;
 
-  a.x = x_root - _gdk_offset_x;
-  a.y = y_root - _gdk_offset_y;
+  a.x = x_root * context_win32->scale - _gdk_offset_x;
+  a.y = y_root * context_win32->scale - _gdk_offset_y;
   a.ignore = drag_window ? GDK_WINDOW_HWND (drag_window) : NULL;
   a.result = NULL;
 
+  GDK_NOTE (DND,
+           g_print ("gdk_drag_find_window_real: %p %+d%+d\n",
+                    (drag_window ? GDK_WINDOW_HWND (drag_window) : NULL),
+                    a.x, a.y));
+
   EnumWindows (find_window_enum_proc, (LPARAM) &a);
 
   if (a.result == NULL)
@@ -2075,8 +2280,9 @@ gdk_win32_drag_context_drag_motion (GdkDragContext *context,
 
   context->actions = possible_actions;
 
-  GDK_NOTE (DND, g_print ("gdk_drag_motion: %s suggested=%s, possible=%s\n"
+  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),
@@ -2087,6 +2293,9 @@ gdk_win32_drag_context_drag_motion (GdkDragContext *context,
 
   context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
 
+  if (context_win32->drag_window)
+    move_drag_window (context, x_root, y_root);
+
   if (!use_ole2_dnd)
     {
       if (context->dest_window == dest_window)
@@ -2113,8 +2322,7 @@ gdk_win32_drag_context_drag_motion (GdkDragContext *context,
          /* Check if new destination accepts drags, and which protocol */
          if (dest_window)
            {
-             context->dest_window = dest_window;
-             g_object_ref (context->dest_window);
+             g_set_object (&context->dest_window, dest_window);
              context->protocol = protocol;
 
              switch (protocol)
@@ -2137,27 +2345,27 @@ gdk_win32_drag_context_drag_motion (GdkDragContext *context,
          /* Push a status event, to let the client know that
           * the drag changed
           */
-          tmp_event = gdk_event_new (GDK_DRAG_STATUS);
-         tmp_event->dnd.window = g_object_ref (context->source_window);
+         tmp_event = gdk_event_new (GDK_DRAG_STATUS);
+         g_set_object (&tmp_event->dnd.window, context->source_window);
          /* We use this to signal a synthetic status. Perhaps
           * we should use an extra field...
           */
          tmp_event->dnd.send_event = TRUE;
 
-         tmp_event->dnd.context = g_object_ref (context);
+         g_set_object (&tmp_event->dnd.context, context);
          tmp_event->dnd.time = time;
-          gdk_event_set_device (tmp_event, gdk_drag_context_get_device (context));
-          gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (context)));
+         gdk_event_set_device (tmp_event, gdk_drag_context_get_device (context));
+         gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (context)));
 
          GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
          gdk_event_put (tmp_event);
-          gdk_event_free (tmp_event);
+         gdk_event_free (tmp_event);
        }
 
       /* Send a drag-motion event */
 
-      context_win32->ole2_dnd_last_pt.x = x_root - _gdk_offset_x;
-      context_win32->ole2_dnd_last_pt.y = y_root - _gdk_offset_y;
+      context_win32->last_x = x_root;
+      context_win32->last_y = y_root;
 
       if (context->dest_window)
        {
@@ -2203,6 +2411,8 @@ static void
 gdk_win32_drag_context_drag_drop (GdkDragContext *context,
               guint32         time)
 {
+  GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
+
   g_return_if_fail (context != NULL);
 
   GDK_NOTE (DND, g_print ("gdk_drag_drop\n"));
@@ -2215,7 +2425,7 @@ gdk_win32_drag_context_drag_drop (GdkDragContext *context,
     }
   else
     {
-      _dnd_source_state = GDK_WIN32_DND_DROPPED;
+      sel_win32->dnd_source_state = GDK_WIN32_DND_DROPPED;
     }
 }
 
@@ -2223,12 +2433,14 @@ static void
 gdk_win32_drag_context_drag_abort (GdkDragContext *context,
                guint32         time)
 {
+  GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
+
   g_return_if_fail (context != NULL);
 
   GDK_NOTE (DND, g_print ("gdk_drag_abort\n"));
 
   if (use_ole2_dnd)
-    _dnd_source_state = GDK_WIN32_DND_NONE;
+    sel_win32->dnd_source_state = GDK_WIN32_DND_NONE;
 }
 
 /* Destination side */
@@ -2261,18 +2473,18 @@ gdk_win32_drag_context_drag_status (GdkDragContext *context,
 
       if (src_context)
        {
-          GdkWin32DragContext *src_context_win32 = GDK_WIN32_DRAG_CONTEXT (src_context);
+         GdkWin32DragContext *src_context_win32 = GDK_WIN32_DRAG_CONTEXT (src_context);
 
-          if (src_context_win32->drag_status == GDK_DRAG_STATUS_MOTION_WAIT)
+         if (src_context_win32->drag_status == GDK_DRAG_STATUS_MOTION_WAIT)
            src_context_win32->drag_status = GDK_DRAG_STATUS_DRAG;
 
          tmp_event = gdk_event_new (GDK_DRAG_STATUS);
-         tmp_event->dnd.window = g_object_ref (context->source_window);
+         g_set_object (&tmp_event->dnd.window, context->source_window);
          tmp_event->dnd.send_event = FALSE;
-         tmp_event->dnd.context = g_object_ref (src_context);
+         g_set_object (&tmp_event->dnd.context, src_context);
          tmp_event->dnd.time = GDK_CURRENT_TIME; /* FIXME? */
-          gdk_event_set_device (tmp_event, gdk_drag_context_get_device (src_context));
-          gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (src_context)));
+         gdk_event_set_device (tmp_event, gdk_drag_context_get_device (src_context));
+         gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (src_context)));
 
          if (action == GDK_ACTION_DEFAULT)
            action = 0;
@@ -2281,7 +2493,7 @@ gdk_win32_drag_context_drag_status (GdkDragContext *context,
 
          GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
          gdk_event_put (tmp_event);
-          gdk_event_free (tmp_event);
+         gdk_event_free (tmp_event);
        }
     }
 }
@@ -2310,6 +2522,7 @@ gdk_win32_drag_context_drop_finish (GdkDragContext *context,
 {
   GdkDragContext *src_context;
   GdkEvent *tmp_event;
+  GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
 
   g_return_if_fail (context != NULL);
 
@@ -2322,16 +2535,16 @@ gdk_win32_drag_context_drop_finish (GdkDragContext *context,
                                           context->dest_window);
       if (src_context)
        {
-    tmp_event = gdk_event_new (GDK_DROP_FINISHED);
-         tmp_event->dnd.window = g_object_ref (src_context->source_window);
+         tmp_event = gdk_event_new (GDK_DROP_FINISHED);
+         g_set_object (&tmp_event->dnd.window, src_context->source_window);
          tmp_event->dnd.send_event = FALSE;
-         tmp_event->dnd.context = g_object_ref (src_context);
-    gdk_event_set_device (tmp_event, gdk_drag_context_get_device (src_context));
-    gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (src_context)));
+         g_set_object (&tmp_event->dnd.context, src_context);
+         gdk_event_set_device (tmp_event, gdk_drag_context_get_device (src_context));
+         gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (src_context)));
 
          GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
          gdk_event_put (tmp_event);
-    gdk_event_free (tmp_event);
+         gdk_event_free (tmp_event);
        }
     }
   else
@@ -2339,9 +2552,9 @@ gdk_win32_drag_context_drop_finish (GdkDragContext *context,
       gdk_drag_do_leave (context, time);
 
       if (success)
-       _dnd_target_state = GDK_WIN32_DND_DROPPED;
+       sel_win32->dnd_target_state = GDK_WIN32_DND_DROPPED;
       else
-       _dnd_target_state = GDK_WIN32_DND_FAILED;
+       sel_win32->dnd_target_state = GDK_WIN32_DND_FAILED;
     }
 }
 
@@ -2401,11 +2614,6 @@ _gdk_win32_window_register_dnd (GdkWindow *window)
       if (g_hash_table_lookup (target_ctx_for_window, GDK_WINDOW_HWND (window)) != NULL)
        return;
 
-      /* Register for OLE2 d&d : similarly, claim to accept all supported
-       * data types because we cannot know from here what the window
-       * actually accepts.
-       */
-      /* FIXME: This of course won't work with user-extensible data types! */
       ctx = target_context_new (window);
 
       hr = CoLockObjectExternal ((IUnknown *) &ctx->idt, TRUE, FALSE);
@@ -2444,17 +2652,619 @@ gdk_win32_drag_context_get_selection (GdkDragContext *context)
   switch (context->protocol)
     {
     case GDK_DRAG_PROTO_LOCAL:
-      return _local_dnd;
+      return _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_LOCAL_DND_SELECTION);
     case GDK_DRAG_PROTO_WIN32_DROPFILES:
-      return _gdk_win32_dropfiles;
+      return _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_DROPFILES_DND);
     case GDK_DRAG_PROTO_OLE2:
-      return _gdk_ole2_dnd;
+      return _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND);
     default:
       return NULL;
     }
 }
 
 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_window_show (context->drag_window);
+  gdk_window_move (context->drag_window,
+                   context->last_x + (context->start_x - context->last_x) * t - context->hot_x,
+                   сontext->last_y + (context->start_y - context->last_y) * t - context->hot_y);
+  gdk_window_set_opacity (context->drag_window, 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)
+    {
+      GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
+
+      if (success)
+        sel_win32->dnd_source_state = GDK_WIN32_DND_DROPPED;
+      else
+        sel_win32->dnd_source_state = GDK_WIN32_DND_NONE;
+    }
+
+  if (success)
+    {
+      gdk_window_hide (win32_context->drag_window);
+      return;
+    }
+
+  win_surface = _gdk_window_ref_cairo_surface (win32_context->drag_window);
+  surface = gdk_window_create_similar_surface (win32_context->drag_window,
+                                               cairo_surface_get_content (win_surface),
+                                               gdk_window_get_width (win32_context->drag_window),
+                                               gdk_window_get_height (win32_context->drag_window));
+  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_window_set_background_pattern (win32_context->drag_window, 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_window_get_frame_clock (win32_context->drag_window);
+  anim->start_time = gdk_frame_clock_get_frame_time (anim->frame_clock);
+
+  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 gboolean
+gdk_win32_drag_context_manage_dnd (GdkDragContext *context,
+                                   GdkWindow      *ipc_window,
+                                   GdkDragAction   actions)
+{
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+
+  if (context_win32->ipc_window)
+    return FALSE;
+
+  if (use_ole2_dnd)
+    context->protocol = GDK_DRAG_PROTO_OLE2;
+  else
+    context->protocol = GDK_DRAG_PROTO_LOCAL;
+
+  g_set_object (&context_win32->ipc_window, ipc_window);
+
+  if (drag_context_grab (context))
+    {
+      context_win32->actions = actions;
+      move_drag_window (context, context_win32->start_x, context_win32->start_y);
+      return TRUE;
+    }
+  else
+    {
+      g_clear_object (&context_win32->ipc_window);
+      return FALSE;
+    }
+}
+
+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;
+  GdkWindow *dest_window;
+  GdkDragProtocol protocol;
+
+  gdk_drag_get_current_actions (mods, GDK_BUTTON_PRIMARY, win32_context->actions,
+                                &action, &possible_actions);
+
+  gdk_drag_find_window (context,
+                        win32_context->drag_window,
+                        x_root, y_root, &dest_window, &protocol);
+
+  gdk_drag_motion (context, dest_window, 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));
+  return TRUE;
+}
+
+static gboolean
+gdk_dnd_handle_key_event (GdkDragContext    *context,
+                          const GdkEventKey *event)
+{
+  GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
+  GdkModifierType state;
+  GdkWindow *root_window;
+  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->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_window (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->last_x += dx;
+      win32_context->last_y += dy;
+      gdk_device_warp (pointer, win32_context->last_x, win32_context->last_y);
+    }
+
+  gdk_drag_update (context, win32_context->last_x, win32_context->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_window == win32_context->drag_window ||
+      event->grab_window == 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) &&
+      (gdk_drag_context_get_dest_window (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;
+}
+
+gboolean
+gdk_dnd_handle_drag_status (GdkDragContext    *context,
+                            const GdkEventDND *event)
+{
+  GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
+  GdkDragAction action;
+
+  GDK_NOTE (DND, g_print ("gdk_dnd_handle_drag_status: 0x%p\n",
+                          context));
+
+  if (context != event->context)
+    return FALSE;
+
+  action = gdk_drag_context_get_selected_action (context);
+
+  if (action != context_win32->current_action)
+    {
+      context_win32->current_action = action;
+      g_signal_emit_by_name (context, "action-changed", action);
+    }
+
+  return TRUE;
+}
+
+static gboolean
+gdk_dnd_handle_drop_finished (GdkDragContext *context,
+                              const GdkEventDND *event)
+{
+  GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
+
+  GDK_NOTE (DND, g_print ("gdk_dnd_handle_drop_finihsed: 0x%p\n",
+                          context));
+
+  if (context != event->context)
+    return FALSE;
+
+  g_signal_emit_by_name (context, "dnd-finished");
+  gdk_drag_drop_done (context, !win32_context->drop_failed);
+  gdk_win32_selection_clear_targets (gdk_display_get_default (),
+                                     gdk_win32_drag_context_get_selection (context));
+
+  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 && event->type != GDK_DROP_FINISHED)
+    return FALSE;
+
+  switch (event->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);
+    case GDK_DRAG_STATUS:
+      return gdk_dnd_handle_drag_status (context, &event->dnd);
+    case GDK_DROP_FINISHED:
+      return gdk_dnd_handle_drop_finished (context, &event->dnd);
+    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 GdkWindow *
+gdk_win32_drag_context_get_drag_window (GdkDragContext *context)
+{
+  return GDK_WIN32_DRAG_CONTEXT (context)->drag_window;
+}
+
+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_window (context, win32_context->last_x, win32_context->last_y);
+    }
+}
+
+static void
 gdk_win32_drag_context_class_init (GdkWin32DragContextClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
@@ -2471,4 +3281,15 @@ gdk_win32_drag_context_class_init (GdkWin32DragContextClass *klass)
   context_class->drop_finish = gdk_win32_drag_context_drop_finish;
   context_class->drop_status = gdk_win32_drag_context_drop_status;
   context_class->get_selection = gdk_win32_drag_context_get_selection;
+
+  context_class->get_drag_window = gdk_win32_drag_context_get_drag_window;
+  context_class->set_hotspot = gdk_win32_drag_context_set_hotspot;
+  context_class->drop_done = gdk_win32_drag_context_drop_done;
+  context_class->manage_dnd = gdk_win32_drag_context_manage_dnd;
+  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/gdkevents-win32.c b/gdk/win32/gdkevents-win32.c
index eaafb4b..7b459db 100644
--- a/gdk/win32/gdkevents-win32.c
+++ b/gdk/win32/gdkevents-win32.c
@@ -56,6 +56,8 @@
 #include "gdkdeviceprivate.h"
 #include "gdkdevice-wintab.h"
 #include "gdkwin32dnd.h"
+#include "gdkdisplay-win32.h"
+#include "gdkselection-win32.h"
 #include "gdkdndprivate.h"
 
 #include <windowsx.h>
@@ -2324,6 +2326,10 @@ gdk_event_translate (MSG  *msg,
 
   int i;
 
+  GdkWin32Selection *win32_sel = NULL;
+
+  STGMEDIUM *property_change_data;
+
   display = gdk_display_get_default ();
   window = gdk_win32_handle_table_lookup (msg->hwnd);
 
@@ -3141,7 +3147,8 @@ gdk_event_translate (MSG  *msg,
 
     case WM_KILLFOCUS:
       if (keyboard_grab != NULL &&
-         !GDK_WINDOW_DESTROYED (keyboard_grab->window))
+         !GDK_WINDOW_DESTROYED (keyboard_grab->window) &&
+         (_modal_operation_in_progress & GDK_WIN32_MODAL_OP_DND) == 0)
        {
          generate_grab_broken_event (_gdk_device_manager, keyboard_grab->window, TRUE, NULL);
        }
@@ -3739,7 +3746,9 @@ gdk_event_translate (MSG  *msg,
       break;
 
     case WM_DESTROYCLIPBOARD:
-      if (!_ignore_destroy_clipboard)
+      win32_sel = _gdk_win32_selection_get ();
+
+      if (!win32_sel->ignore_destroy_clipboard)
        {
          event = gdk_event_new (GDK_SELECTION_CLEAR);
          event->selection.window = window;
@@ -3757,12 +3766,29 @@ gdk_event_translate (MSG  *msg,
     case WM_RENDERFORMAT:
       GDK_NOTE (EVENTS, g_print (" %s", _gdk_win32_cf_to_string (msg->wParam)));
 
-      if (!(target = g_hash_table_lookup (_format_atom_table, GINT_TO_POINTER (msg->wParam))))
-       {
-         GDK_NOTE (EVENTS, g_print (" (target not found)"));
-         return_val = TRUE;
-         break;
-       }
+      *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()
@@ -3772,45 +3798,61 @@ gdk_event_translate (MSG  *msg,
       event->selection.send_event = FALSE;
       event->selection.selection = GDK_SELECTION_CLIPBOARD;
       event->selection.target = target;
-      event->selection.property = _gdk_selection;
+      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;
 
       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 (!_delayed_rendering_data)
+      if (!property_change_data->hGlobal)
         {
           GDK_NOTE (EVENTS, g_print (" (no _delayed_rendering_data?)"));
         }
       else
         {
-          if (msg->wParam == CF_DIB)
-            {
-              _delayed_rendering_data =
-                _gdk_win32_selection_convert_to_dib (_delayed_rendering_data,
-                                                     target);
-              if (!_delayed_rendering_data)
-                {
-                  g_warning ("Cannot convert to DIB from delayed rendered image");
-                  break;
-                }
-            }
-
           /* 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),
-                             _delayed_rendering_data));
+                             property_change_data->hGlobal));
 
-          API_CALL (SetClipboardData, (msg->wParam, _delayed_rendering_data));
-          _delayed_rendering_data = NULL;
+          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;
 
@@ -3980,16 +4022,18 @@ 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 (_dnd_source_state == GDK_WIN32_DND_PENDING)
+      if (sel_win32->dnd_source_state == GDK_WIN32_DND_PENDING)
         {
-          _dnd_source_state = GDK_WIN32_DND_DRAGGING;
+          sel_win32->dnd_source_state = GDK_WIN32_DND_DRAGGING;
           _gdk_win32_dnd_do_dragdrop ();
-          _dnd_source_state = GDK_WIN32_DND_NONE;
+          sel_win32->dnd_source_state = GDK_WIN32_DND_NONE;
         }
     }
 
diff --git a/gdk/win32/gdkglobals-win32.c b/gdk/win32/gdkglobals-win32.c
index c41b1b4..578d166 100644
--- a/gdk/win32/gdkglobals-win32.c
+++ b/gdk/win32/gdkglobals-win32.c
@@ -42,41 +42,11 @@ HKL           _gdk_input_locale;
 gboolean         _gdk_input_locale_is_ime;
 UINT             _gdk_input_codepage;
 
-GdkAtom           _gdk_selection;
-GdkAtom                  _wm_transient_for;
-GdkAtom                  _targets;
-GdkAtom                  _delete;
-GdkAtom                  _save_targets;
-GdkAtom           _utf8_string;
-GdkAtom                  _text;
-GdkAtom                  _compound_text;
-GdkAtom                  _text_uri_list;
-GdkAtom                  _text_html;
-GdkAtom                  _image_png;
-GdkAtom                  _image_jpeg;
-GdkAtom                  _image_bmp;
-GdkAtom                  _image_gif;
-
-GdkAtom                  _local_dnd;
-GdkAtom                  _gdk_win32_dropfiles;
-GdkAtom                  _gdk_ole2_dnd;
-
-UINT             _cf_png;
-UINT             _cf_jfif;
-UINT             _cf_gif;
-UINT             _cf_url;
-UINT             _cf_html_format;
-UINT             _cf_text_html;
-
-GdkWin32DndState  _dnd_target_state = GDK_WIN32_DND_NONE;
-GdkWin32DndState  _dnd_source_state = GDK_WIN32_DND_NONE;
-
 gint             _gdk_input_ignore_wintab = FALSE;
 gint             _gdk_max_colors = 0;
 
 GdkWin32ModalOpKind      _modal_operation_in_progress = GDK_WIN32_MODAL_OP_NONE;
 HWND              _modal_move_resize_window = NULL;
-gboolean         _ignore_destroy_clipboard = FALSE;
 
-HGLOBAL           _delayed_rendering_data = NULL;
-GHashTable       *_format_atom_table = NULL;
+/* The singleton selection object pointer */
+GdkWin32Selection *_win32_selection = NULL;
diff --git a/gdk/win32/gdkmain-win32.c b/gdk/win32/gdkmain-win32.c
index 9d150e6..078b0dc 100644
--- a/gdk/win32/gdkmain-win32.c
+++ b/gdk/win32/gdkmain-win32.c
@@ -44,6 +44,9 @@
 #include <wintab.h>
 #include <imm.h>
 
+/* for CFSTR_SHELLIDLIST */
+#include <shlobj.h>
+
 static gboolean gdk_synchronize = FALSE;
 
 static gboolean dummy;
@@ -88,38 +91,6 @@ _gdk_win32_windowing_init (void)
   GDK_NOTE (EVENTS, g_print ("input_locale:%p, codepage:%d\n",
                             _gdk_input_locale, _gdk_input_codepage));
 
-  _gdk_selection = gdk_atom_intern_static_string ("GDK_SELECTION");
-  _wm_transient_for = gdk_atom_intern_static_string ("WM_TRANSIENT_FOR");
-  _targets = gdk_atom_intern_static_string ("TARGETS");
-  _delete = gdk_atom_intern_static_string ("DELETE");
-  _save_targets = gdk_atom_intern_static_string ("SAVE_TARGETS");
-  _utf8_string = gdk_atom_intern_static_string ("UTF8_STRING");
-  _text = gdk_atom_intern_static_string ("TEXT");
-  _compound_text = gdk_atom_intern_static_string ("COMPOUND_TEXT");
-  _text_uri_list = gdk_atom_intern_static_string ("text/uri-list");
-  _text_html = gdk_atom_intern_static_string ("text/html");
-  _image_png = gdk_atom_intern_static_string ("image/png");
-  _image_jpeg = gdk_atom_intern_static_string ("image/jpeg");
-  _image_bmp = gdk_atom_intern_static_string ("image/bmp");
-  _image_gif = gdk_atom_intern_static_string ("image/gif");
-
-  _local_dnd = gdk_atom_intern_static_string ("LocalDndSelection");
-  _gdk_win32_dropfiles = gdk_atom_intern_static_string ("DROPFILES_DND");
-  _gdk_ole2_dnd = gdk_atom_intern_static_string ("OLE2_DND");
-
-  /* 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 target name "image/png" to the clipboard
-   * format name "PNG" etc.
-   */
-  _cf_png = RegisterClipboardFormat ("PNG");
-  _cf_jfif = RegisterClipboardFormat ("JFIF");
-  _cf_gif = RegisterClipboardFormat ("GIF");
-
-  _cf_url = RegisterClipboardFormat ("UniformResourceLocatorW");
-  _cf_html_format = RegisterClipboardFormat ("HTML Format");
-  _cf_text_html = RegisterClipboardFormat ("text/html");
-
   _gdk_win32_selection_init ();
 }
 
diff --git a/gdk/win32/gdkprivate-win32.h b/gdk/win32/gdkprivate-win32.h
index 1b8ff86..414e972 100644
--- a/gdk/win32/gdkprivate-win32.h
+++ b/gdk/win32/gdkprivate-win32.h
@@ -40,6 +40,7 @@
 #include <gdk/win32/gdkwin32screen.h>
 #include <gdk/win32/gdkwin32keys.h>
 #include <gdk/win32/gdkdevicemanager-win32.h>
+#include <gdk/win32/gdkselection-win32.h>
 
 #include "gdkinternals.h"
 
@@ -284,46 +285,8 @@ extern UINT                 _gdk_input_codepage;
 
 extern guint            _gdk_keymap_serial;
 
-/* GdkAtoms: properties, targets and types */
-extern GdkAtom          _gdk_selection;
-extern GdkAtom          _wm_transient_for;
-extern GdkAtom          _targets;
-extern GdkAtom          _delete;
-extern GdkAtom          _save_targets;
-extern GdkAtom           _utf8_string;
-extern GdkAtom          _text;
-extern GdkAtom          _compound_text;
-extern GdkAtom          _text_uri_list;
-extern GdkAtom          _text_html;
-extern GdkAtom          _image_png;
-extern GdkAtom          _image_jpeg;
-extern GdkAtom          _image_bmp;
-extern GdkAtom          _image_gif;
-
-/* DND selections */
-extern GdkAtom           _local_dnd;
-extern GdkAtom          _gdk_win32_dropfiles;
-extern GdkAtom          _gdk_ole2_dnd;
-
-/* Clipboard formats */
-extern UINT             _cf_png;
-extern UINT             _cf_jfif;
-extern UINT             _cf_gif;
-extern UINT             _cf_url;
-extern UINT             _cf_html_format;
-extern UINT             _cf_text_html;
-
-/* 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;
-
-extern GdkWin32DndState  _dnd_target_state;
-extern GdkWin32DndState  _dnd_source_state;
+/* The singleton selection object pointer */
+GdkWin32Selection *_win32_selection;
 
 void _gdk_win32_dnd_do_dragdrop (void);
 void _gdk_win32_ole2_dnd_property_change (GdkAtom       type,
@@ -354,20 +317,6 @@ void  _gdk_win32_end_modal_call (GdkWin32ModalOpKind kind);
 extern gboolean                 _gdk_input_ignore_wintab;
 extern gint             _gdk_max_colors;
 
-/* TRUE when we are emptying the clipboard ourselves */
-extern gboolean                _ignore_destroy_clipboard;
-
-/* Mapping from registered clipboard format id (native) to
- * corresponding GdkAtom
- */
-extern GHashTable      *_format_atom_table;
-
-/* Hold the result of a delayed rendering */
-extern HGLOBAL         _delayed_rendering_data;
-
-HGLOBAL _gdk_win32_selection_convert_to_dib (HGLOBAL  hdata,
-                                            GdkAtom  target);
-
 /* Convert a pixbuf to an HICON (or HCURSOR).  Supports alpha under
  * Windows XP, thresholds alpha otherwise.
  */
@@ -513,6 +462,9 @@ void _gdk_win32_window_change_property (GdkWindow    *window,
                                        gint          nelements);
 void _gdk_win32_window_delete_property (GdkWindow *window, GdkAtom    property);
 
+void gdk_win32_selection_clear_targets (GdkDisplay *display,
+                                        GdkAtom     selection);
+
 /* Stray GdkWin32Screen members */
 gboolean _gdk_win32_get_setting (const gchar *name, GValue *value);
 void _gdk_win32_screen_on_displaychange_event (GdkWin32Screen *screen);
diff --git a/gdk/win32/gdkproperty-win32.c b/gdk/win32/gdkproperty-win32.c
index 72059f5..16a8db5 100644
--- a/gdk/win32/gdkproperty-win32.c
+++ b/gdk/win32/gdkproperty-win32.c
@@ -137,20 +137,15 @@ _gdk_win32_window_get_property (GdkWindow   *window,
 }
 
 void
-_gdk_win32_window_change_property (GdkWindow    *window,
-                    GdkAtom       property,
-                    GdkAtom       type,
-                    gint          format,
-                    GdkPropMode   mode,
-                    const guchar *data,
-                    gint          nelements)
+_gdk_win32_window_change_property (GdkWindow         *window,
+                                   GdkAtom            property,
+                                   GdkAtom            type,
+                                   gint               format,
+                                   GdkPropMode        mode,
+                                   const guchar      *data,
+                                   gint               nelements)
 {
-  HGLOBAL hdata;
-  gint i, size;
-  guchar *ucptr;
-  wchar_t *wcptr, *p;
-  glong wclen;
-  GError *err = NULL;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
 
   g_return_if_fail (window != NULL);
   g_return_if_fail (GDK_IS_WINDOW (window));
@@ -161,6 +156,7 @@ _gdk_win32_window_change_property (GdkWindow    *window,
   GDK_NOTE (DND, {
       gchar *prop_name = gdk_atom_name (property);
       gchar *type_name = gdk_atom_name (type);
+      gchar *datastring = _gdk_win32_data_to_string (data, MIN (10, format*nelements/8));
 
       g_print ("gdk_property_change: %p %s %s %s %d*%d bits: %s\n",
               GDK_WINDOW_HWND (window),
@@ -171,103 +167,35 @@ _gdk_win32_window_change_property (GdkWindow    *window,
                 (mode == GDK_PROP_MODE_APPEND ? "APPEND" :
                  "???"))),
               format, nelements,
-              _gdk_win32_data_to_string (data, MIN (10, format*nelements/8)));
+              datastring);
+      g_free (datastring);
       g_free (prop_name);
       g_free (type_name);
     });
 
+#ifndef G_DISABLE_CHECKS
   /* We should never come here for these types */
-  g_return_if_fail (type != GDK_TARGET_STRING);
-  g_return_if_fail (type != _text);
-  g_return_if_fail (type != _compound_text);
-  g_return_if_fail (type != _save_targets);
-
-  if (property == _gdk_selection &&
-      format == 8 &&
-      mode == GDK_PROP_MODE_REPLACE)
+  if (G_UNLIKELY (type == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_COMPOUND_TEXT) ||
+                  type == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_SAVE_TARGETS)))
     {
-      if (type == _image_bmp && nelements < sizeof (BITMAPFILEHEADER))
-        {
-           g_warning ("Clipboard contains invalid bitmap data");
-           return;
-        }
-
-      if (type == _utf8_string)
-       {
-         wcptr = g_utf8_to_utf16 ((char *) data, nelements, NULL, &wclen, &err);
-          if (err != NULL)
-            {
-              g_warning ("Failed to convert utf8: %s", err->message);
-              g_clear_error (&err);
-              return;
-            }
-
-         if (!OpenClipboard (GDK_WINDOW_HWND (window)))
-           {
-             WIN32_API_FAILED ("OpenClipboard");
-             g_free (wcptr);
-             return;
-           }
-
-         wclen++;              /* Terminating 0 */
-         size = wclen * 2;
-         for (i = 0; i < wclen; i++)
-           if (wcptr[i] == '\n' && (i == 0 || wcptr[i - 1] != '\r'))
-             size += 2;
-
-         if (!(hdata = GlobalAlloc (GMEM_MOVEABLE, size)))
-           {
-             WIN32_API_FAILED ("GlobalAlloc");
-             if (!CloseClipboard ())
-               WIN32_API_FAILED ("CloseClipboard");
-             g_free (wcptr);
-             return;
-           }
-
-         ucptr = GlobalLock (hdata);
-
-         p = (wchar_t *) ucptr;
-         for (i = 0; i < wclen; i++)
-           {
-             if (wcptr[i] == '\n' && (i == 0 || wcptr[i - 1] != '\r'))
-               *p++ = '\r';
-             *p++ = wcptr[i];
-           }
-         g_free (wcptr);
-
-         GlobalUnlock (hdata);
-         GDK_NOTE (DND, g_print ("... SetClipboardData(CF_UNICODETEXT,%p)\n",
-                                 hdata));
-         if (!SetClipboardData (CF_UNICODETEXT, hdata))
-           WIN32_API_FAILED ("SetClipboardData");
-
-         if (!CloseClipboard ())
-           WIN32_API_FAILED ("CloseClipboard");
-       }
-      else
-        {
-         /* We use delayed rendering for everything else than
-          * text. We can't assign hdata to the clipboard here as type
-          * may be "image/png", "image/jpg", etc. In this case
-          * there's a further conversion afterwards.
-          */
-         GDK_NOTE (DND, g_print ("... delayed rendering\n"));
-         _delayed_rendering_data = NULL;
-         if (!(hdata = GlobalAlloc (GMEM_MOVEABLE, nelements > 0 ? nelements : 1)))
-           {
-             WIN32_API_FAILED ("GlobalAlloc");
-             return;
-           }
-         ucptr = GlobalLock (hdata);
-         memcpy (ucptr, data, nelements);
-         GlobalUnlock (hdata);
-         _delayed_rendering_data = hdata;
-       }
+      g_return_if_fail_warning (G_LOG_DOMAIN,
+                                G_STRFUNC,
+                                "change_property called with a bad type");
+      return;
     }
-  else if (property == _gdk_ole2_dnd)
+#endif
+
+  if (property == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION) ||
+      property == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND))
     {
-      /* Will happen only if gdkdnd-win32.c has OLE2 dnd support compiled in */
-      _gdk_win32_ole2_dnd_property_change (type, format, data, nelements);
+      _gdk_win32_selection_property_change (win32_sel,
+                                            window,
+                                            property,
+                                            type,
+                                            format,
+                                            mode,
+                                            data,
+                                            nelements);
     }
   else
     g_warning ("gdk_property_change: General case not implemented");
@@ -291,10 +219,18 @@ _gdk_win32_window_delete_property (GdkWindow *window,
       g_free (prop_name);
     });
 
-  if (property == _gdk_selection)
+  if (property == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION) ||
+      property == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND))
     _gdk_selection_property_delete (window);
-  else if (property == _wm_transient_for)
-    gdk_window_set_transient_for (window, NULL);
+/*
+  else if (property == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_WM_TRANSIENT_FOR))
+    {
+      GdkScreen *screen;
+
+      screen = gdk_window_get_screen (window);
+      gdk_window_set_transient_for (window, NULL);
+    }
+*/
   else
     {
       prop_name = gdk_atom_name (property);
diff --git a/gdk/win32/gdkselection-win32.c b/gdk/win32/gdkselection-win32.c
index 336a88f..a164bb9 100644
--- a/gdk/win32/gdkselection-win32.c
+++ b/gdk/win32/gdkselection-win32.c
@@ -23,59 +23,409 @@
  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
  */
 
+/*
+GTK+ selection works like this:
+There are three selections that matter - GDK_SELECTION_CLIPBOARD,
+GDK_SELECTION_PRIMARY and DND. Primary selection is only handled
+internally by GTK+ (it's not portable to Windows). DND is actually
+represented by two selections - LOCAL and OLE2, one for each DnD protocol,
+but they work the same way.
+
+"Target" is a GdkAtom describing a clipboard format.
+
+For Clipboard:
+GTK+ calls gtk_clipboard_set_contents(), which first ensures the
+clipboard is owned by the clipboard widget (which also indirectly
+causes an SelectionRequest xevent to be sent to it), then clears the old
+supported targets from the clipboard, then adds all the
+targets it's given to the clipboard. No data is sent anywhere.
+
+gtk_clipboard_set_contents() is also given a callback to invoke when
+the actual data is needed. This callback is implemented by the widget
+from which the data can be put into clipboard.
+
+GTK+ might also call gtk_clipboard_set_can_store(), which sets the
+targets for which the data can be put into system clipboard, so that
+it remains usable even if the application is no longer around. Usually
+all data formats are storable, except for the shortcut formats, which
+refer to actual widgets directly, and are thus only working while
+the application is alive.
+
+("C:" means clipboard client (requestor), "S:" means clipboard server (provider))
+
+When something needs to be obtained from clipboard, GTK+ calls
+C: gtk_selection_convert().
+That function has a shortcut where it directly gets the selection contents by calling
+S: gtk_selection_invoke_handler(),
+asking the widget to provide data, and then calling
+C: gtk_selection_retrieval_report()
+to report the data back to the caller.
+
+If that shortcut isn't possible (selection is owned by another process),
+gtk_selection_convert() calls
+C:gdk_selection_convert() (_gdk_x11_display_convert_selection())
+
+On X11 gdk_selection_convert() just calls
+C:XConvertSelection(),
+which sends SelectionRequest xevent to the window that owns the selection.
+The client gives its clipboard window as the requestor for that event,
+and gives the property as GDK_SELECTION.
+
+Server-side GTK+ catches SelectionRequest in a
+S:_gtk_selection_request()
+event handler, which calls
+S:gtk_selection_invoke_handler()
+to get the data, and then calls
+S:gdk_property_change() (_gdk_x11_window_change_property())
+to submit the data, by setting the property given by the message sender
+(GDK_SELECTION) on the requestor window (our client clipboard window).
+
+On X11 data submission takes from of
+S:XChangeProperty()
+call, which causes SelectionNotify (and PropertyNotify for INCR)
+xevent to be sent, which client-side GTK+ catches and handles in
+C:_gtk_selection_notify()
+(and
+C:_gtk_selection_property_notify(),
+for INCR)
+event handler, which calls
+C:gtk_selection_retrieval_report()
+to report back to the caller. The caller gets the property
+data from the window, and returns it up the stack.
+
+On X11 the "TARGETS" target might be given in a SelectionRequest xmessage to request
+all supported targets for a selection.
+
+If data must be stored on the clipboard, because the application is quitting,
+GTK+ will call
+S:gdk_clipboard_store() -> gdk_display_store_clipboard() (gdk_x11_display_store_clipboard())
+on all the clipboards it owns.
+X11 gdk_display_store_clipboard() puts a list of storeable targets into GDK_SELECTION
+property of the clipboard window, then calls
+S:XConvertSelection()
+on the clipboard manager window (retrieved from the CLIPBOARD_MANAGER atom),
+and the clipboard manager responds by requesting all these formats and storing the data,
+then responds with SelectionNotify xevent to allow the application to quit.
+
+When clipboard owner changes, the old owner receives SelectionClear xevent,
+GTK+ handles it by clearing the clipboard object on its own level, GDK
+is not involved.
+
+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:
+GTK+ calls gtk_clipboard_set_contents(), which first ensures the
+clipboard is owned by the clipboard widget (calls OpenClipboard(),
+then EmptyClipboard() to become the owner, then
+sends a TARGETS GDK_SELECTION_REQUEST to itself, without closing the clipboard),
+then clears the old supported targets from the clipboard, then adds all the
+targets it's given to the clipboard. No data is sent anywhere.
+
+gtk_clipboard_set_contents() is also given a callback to invoke when
+the actual data is needed. This callback is implemented by the widget
+from which the data can be put into clipboard.
+
+GTK+ might also call gtk_clipboard_set_can_store(), which sets the
+targets for which the data can be put into system clipboard, so that
+it remains usable even if the application is no longer around. Usually
+all data formats are storable, except for the shortcut formats, which
+refer to actual widgets directly, and are thus only working while
+the application is alive.
+
+("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 g(t|d)k_selection_convert(), which
+ is completely unrelated)
+
+When something needs to be obtained from clipboard, GTK+ calls
+C: gtk_selection_convert().
+That function has a shortcut where it directly gets the selection contents by calling
+S: gtk_selection_invoke_handler(),
+asking the widget to provide data, and then calling
+C: gtk_selection_retrieval_report()
+to report the data back to the caller.
+
+If that shortcut isn't possible (selection is owned by another process),
+gtk_selection_convert() calls
+C:gdk_selection_convert() (_gdk_win32_display_convert_selection())
+
+On GDK-Win32 gdk_selection_convert() just calls
+C:OpenClipboard()
+to open clipboard (if that fails, it shedules a timeout to regularly
+try to open clipboard for the next 30 seconds, and do the actions
+outlined below once the clipboard is opened, or notify about
+conversion failure after 30 seconds),
+C:EnumClipboardFormats() (2000+)
+to get the list of supported formats, figures out the format it should
+use to request the data (first it looks for supported formats with names
+that match the target name, then looks through compatibility
+formats for the target and checks whether these are supported).
+Note that it has no list of supported targets at hand,
+just the single requested target, and thus it might have
+to do some transmutation between formats; the caller up the stack
+either only supports just one format that it asks for,
+or supports multiple formats and asks for them in sequence (from
+the most preferred to the least preferred), until one call succeeds,
+or supports multiple formats and asks for the TARGETS format first,
+and then figures out what to ask for - GDK can't know that.
+Either way, GDK has to call
+C:GetClipboardData()
+to get the data (this causes WM_RENDERFORMAT to be sent to the owner,
+if the owner uses delayed rendering for the requested format, otherwise
+it just picks the data right from the OS)
+
+Server-side GDK catches WM_RENDERFORMAT, figures out a target
+to request (this one is easier, as it has the list of supported
+targets saved up), and posts a GDK_SELECTION_REQUEST event, then runs the main loop,
+while GTK+ catches the event in a
+S:_gtk_selection_request()
+event handler, which calls
+S:gtk_selection_invoke_handler()
+to get the data, and then calls
+S:gdk_property_change() (_gdk_win32_window_change_property())
+to submit the data, by first transmuting it to the format actually requested
+by the sender of WM_RENDERFORMAT, and then by returning thedata back up the stack,
+to the WM_RENDERFORMAT handler, which then calls
+S:SetClipboardData()
+with the handle provided by the sender.
+
+Meanwhile, the client code, still in
+C:_gdk_win32_display_convert_selection(),
+gets the data in response to GetClipboardData(),
+transmutes it (if needed) to the target format, sets the requested
+window property to that data (unlike change_property!),
+calls
+C:CloseClipboard() (if there are no more clipboard opeartions
+scheduled)
+and posts a GDK_SELECTION_NOTIFY event, which GTK+ catches in
+C:_gtk_selection_notify()
+event handler, which calls
+C:gtk_selection_retrieval_report()
+to report back to the caller. The caller gets the property
+data from the window, and returns it up the stack.
+
+On GDK-Win32 the "TARGETS" target might be given in a GDK_SELECTION_REQUEST to request
+all supported targets for a selection.
+Note that this server side -
+client side should call gdk_selection_convert() -> gdk_selection_convert() with "TARGETS" target
+to get the list of targets offered by the clipboard holder. It never causes GDK_SELECTION_REQUEST
+to be generated, just queries the system clipboard.
+On server side GDK_SELECTION_REQUEST is only generated internally:
+in response to WM_RENDERFORMAT (it renders a target),
+in response to idataobject_getdata() (it renders a target),
+after DnD ends (with a DELETE target, this is caught by GTK to make it delete the selection),
+and in response to owner change, with TARGETS target, which makes it register its formats by calling
+S:SetClipboardData(..., NULL)
+
+If data must be stored on the clipboard, because the application is quitting,
+GTK+ will call
+S:gdk_clipboard_store() -> gdk_display_store_clipboard() (gdk_win32_display_store_clipboard())
+on all the clipboards it owns.
+GDK-Win32 gdk_display_store_clipboard() sends WM_RENDERALLFORMATS to the window,
+then posts a GDK_SELECTION_NOTIFY event allow the application to quit.
+
+When clipboard owner changes, the old owner receives WM_DESTROYCLIPBOARD message,
+GDK handles it by posting a GDK_SELECTION_CLEAR event, which
+GTK+ handles by clearing the clipboard object on its own level.
+
+Any operations that require OpenClipboard()/CloseClipboard() combo (i.e.
+everything, except for WM_RENDERFORMAT handling) must be put into a queue,
+and then a once-per-second-for-up-to-30-seconds timeout must be added.
+The timeout function must call OpenClipboard(),
+and then proceed to perform the queued actions on the clipboard, once it opened,
+or return and try again a second later, as long as there are still items in the queue,
+and remove the queue items that are older than 30 seconds.
+Once the queue is empty, the clipboard is closed.
+
+DND:
+GDK-Win32:
+S:idataobject_getdata()
+sends a GDK_SELECTION_REQUEST event, which results in a call to
+S:_gdk_win32_window_change_property()
+which passes clipboard data back via the selection singleton.
+GDK-Win32 uses delayed rendering for all formats, even text.
+
+GTK+ will call
+C:gtk_selection_convert() -> gdk_selection_convert() (_gdk_win32_display_convert_selection())
+to get the data associated with the drag, when GTK+ apps want to inspect the data,
+but with a OLE2_DND selection instead of CLIPBOARD selection.
+
+_gdk_win32_display_convert_selection() queries the droptarget global variable,
+which should already contain a matched list of supported formats and targets,
+picks a format there, then queries it from the IDataObject that the droptarget kept around.
+Then optionally transmutes the data, and sets the property. Then posts GDK_SELECTION_NOTIFY.
+
+GTK+ catches that event and processes it, causeing "selection-received" signal to
+be emitted on the selection widget, and its handler is
+C:gtk_drag_selection_received(),
+which emits the "drag-data-received" signal for the app.
+*/
+
 #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 "gdkselection.h"
 #include "gdkdisplay.h"
 #include "gdkprivate-win32.h"
+#include "gdkselection-win32.h"
+#include "gdk/gdkdndprivate.h"
+#include "gdkwin32dnd-private.h"
 #include "gdkwin32.h"
 
-/* We emulate the GDK_SELECTION window properties of windows (as used
- * in the X11 backend) by using a hash table from window handles to
- * GdkSelProp structs.
- */
+typedef struct _GdkWin32ClipboardQueueInfo GdkWin32ClipboardQueueInfo;
 
-typedef struct {
-  guchar *data;
-  gsize length;
-  gint format;
-  GdkAtom type;
-} GdkSelProp;
+typedef enum _GdkWin32ClipboardQueueAction GdkWin32ClipboardQueueAction;
 
-static GHashTable *sel_prop_table = NULL;
+enum _GdkWin32ClipboardQueueAction
+{
+  GDK_WIN32_CLIPBOARD_QUEUE_ACTION_CONVERT = 0,
+  GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS
+};
 
-static GdkSelProp *dropfiles_prop = NULL;
+struct _GdkWin32ClipboardQueueInfo
+{
+  GdkDisplay                   *display;
+  GdkWindow                    *requestor;
+  GdkAtom                       selection;
+  GdkAtom                       target;
+  guint32                       time;
+
+  /* Number of seconds since we started our
+   * attempts to open clipboard.
+   */
+  guint32                       idle_time;
 
-/* We store the owner of each selection in this table. Obviously, this only
- * is valid intra-app, and in fact it is necessary for the intra-app DND to work.
- */
-static GHashTable *sel_owner_table = NULL;
+  /* What to do once clipboard is opened */
+  GdkWin32ClipboardQueueAction  action;
+};
+
+/* List of GdkWin32ClipboardQueueInfo slices */
+static GList *clipboard_queue = NULL;
 
-/* GdkAtoms for well-known image formats */
-static GdkAtom *known_pixbuf_formats;
-static int n_known_pixbuf_formats;
+#define HIDA_GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
+#define HIDA_GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
 
-/* GdkAtoms for well-known text formats */
-static GdkAtom text_plain;
-static GdkAtom text_plain_charset_utf_8;
-static GdkAtom text_plain_charset_CP1252;
+G_DEFINE_TYPE (GdkWin32Selection, gdk_win32_selection, G_TYPE_OBJECT)
+
+static void
+gdk_win32_selection_class_init (GdkWin32SelectionClass *klass)
+{
+}
 
 void
 _gdk_win32_selection_init (void)
 {
-  GSList *pixbuf_formats;
-  GSList *rover;
+  _win32_selection = GDK_WIN32_SELECTION (g_object_new (GDK_TYPE_WIN32_SELECTION, NULL));
+}
+
+static void
+gdk_win32_selection_init (GdkWin32Selection *win32_selection)
+{
+  GArray             *atoms;
+  GArray             *cfs;
+  GSList             *pixbuf_formats;
+  GSList             *rover;
+  int                 i;
+  GArray             *comp;
+  GdkSelTargetFormat  fmt;
+
+  win32_selection->ignore_destroy_clipboard = FALSE;
+  win32_selection->clipboard_opened_for = INVALID_HANDLE_VALUE;
+
+  win32_selection->dnd_target_state = GDK_WIN32_DND_NONE;
+  win32_selection->dnd_source_state = GDK_WIN32_DND_NONE;
+  win32_selection->dnd_data_object_target = NULL;
+  win32_selection->property_change_format = 0;
+  win32_selection->property_change_data = NULL;
+
+  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_selection->known_atoms = atoms;
+  win32_selection->known_clipboard_formats = cfs;
+
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_GDK_SELECTION) = gdk_atom_intern_static_string 
("GDK_SELECTION");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CLIPBOARD_MANAGER) = gdk_atom_intern_static_string 
("CLIPBOARD_MANAGER");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_WM_TRANSIENT_FOR) = gdk_atom_intern_static_string 
("WM_TRANSIENT_FOR");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TARGETS) = gdk_atom_intern_static_string ("TARGETS");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_DELETE) = gdk_atom_intern_static_string ("DELETE");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_SAVE_TARGETS) = gdk_atom_intern_static_string 
("SAVE_TARGETS");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_UTF8_STRING) = gdk_atom_intern_static_string 
("UTF8_STRING");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT) = gdk_atom_intern_static_string ("TEXT");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_COMPOUND_TEXT) = gdk_atom_intern_static_string 
("COMPOUND_TEXT");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) = gdk_atom_intern_static_string 
("text/uri-list");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_HTML) = gdk_atom_intern_static_string 
("text/html");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG) = gdk_atom_intern_static_string 
("image/png");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) = gdk_atom_intern_static_string 
("image/jpeg");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP) = gdk_atom_intern_static_string 
("image/bmp");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_GIF) = gdk_atom_intern_static_string 
("image/gif");
+
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_LOCAL_DND_SELECTION) = gdk_atom_intern_static_string 
("LocalDndSelection");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_DROPFILES_DND) = gdk_atom_intern_static_string 
("DROPFILES_DND");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_OLE2_DND) = gdk_atom_intern_static_string ("OLE2_DND");
+
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_PNG)= gdk_atom_intern_static_string ("PNG");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_JFIF) = gdk_atom_intern_static_string ("JFIF");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_GIF) = gdk_atom_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) = gdk_atom_intern_static_string ("CF_DIB");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CFSTR_SHELLIDLIST) = gdk_atom_intern_static_string 
(CFSTR_SHELLIDLIST);
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_UNICODETEXT) = gdk_atom_intern_static_string 
("CF_UNICODETEXT");
+  _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_TEXT) = gdk_atom_intern_static_string ("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 target name "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_UTF8_STRING) = RegisterClipboardFormatA ("UTF8_STRING");
 
-  sel_prop_table = g_hash_table_new (NULL, NULL);
-  sel_owner_table = g_hash_table_new (NULL, NULL);
-  _format_atom_table = g_hash_table_new (NULL, NULL);
+  win32_selection->sel_prop_table = g_hash_table_new (NULL, NULL);
+  win32_selection->sel_owner_table = g_hash_table_new (NULL, NULL);
 
   pixbuf_formats = gdk_pixbuf_get_formats ();
 
-  n_known_pixbuf_formats = 0;
+  win32_selection->n_known_pixbuf_formats = 0;
   for (rover = pixbuf_formats; rover != NULL; rover = rover->next)
     {
       gchar **mime_types =
@@ -84,12 +434,12 @@ _gdk_win32_selection_init (void)
       gchar **mime_type;
 
       for (mime_type = mime_types; *mime_type != NULL; mime_type++)
-       n_known_pixbuf_formats++;
+       win32_selection->n_known_pixbuf_formats++;
     }
 
-  known_pixbuf_formats = g_new (GdkAtom, n_known_pixbuf_formats);
+  win32_selection->known_pixbuf_formats = g_new (GdkAtom, win32_selection->n_known_pixbuf_formats);
 
-  n_known_pixbuf_formats = 0;
+  i = 0;
   for (rover = pixbuf_formats; rover != NULL; rover = rover->next)
     {
       gchar **mime_types =
@@ -98,22 +448,236 @@ _gdk_win32_selection_init (void)
       gchar **mime_type;
 
       for (mime_type = mime_types; *mime_type != NULL; mime_type++)
-       known_pixbuf_formats[n_known_pixbuf_formats++] = gdk_atom_intern (*mime_type, FALSE);
+       win32_selection->known_pixbuf_formats[i++] = gdk_atom_intern (*mime_type, FALSE);
     }
 
   g_slist_free (pixbuf_formats);
 
-  text_plain = gdk_atom_intern ("text/plain", FALSE);
-  text_plain_charset_utf_8= gdk_atom_intern ("text/plain;charset=utf-8", FALSE);
-  text_plain_charset_CP1252 = gdk_atom_intern ("text/plain;charset=CP1252", FALSE);
+  win32_selection->dnd_selection_targets = g_array_new (FALSE, FALSE, sizeof (GdkSelTargetFormat));
+  win32_selection->clipboard_selection_targets = g_array_new (FALSE, FALSE, sizeof (GdkSelTargetFormat));
+  win32_selection->compatibility_formats = 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 (GdkSelTargetFormat), 3);
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_UTF8_STRING);
+
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_UTF8_STRING);
+  fmt.transmute = FALSE;
+  g_array_append_val (comp, fmt);
+
+  fmt.format = CF_UNICODETEXT;
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  fmt.format = CF_TEXT;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_selection->compatibility_formats, fmt.target, comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 3);
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
+
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_PNG);
+  fmt.transmute = FALSE;
+  g_array_append_val (comp, fmt);
+
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
+  g_array_append_val (comp, fmt);
+
+  fmt.format = CF_DIB;
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_selection->compatibility_formats, fmt.target, comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 4);
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_JPEG);
+
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_JPEG);
+  fmt.transmute = FALSE;
+  g_array_append_val (comp, fmt);
+
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_JFIF);
+  g_array_append_val (comp, fmt);
+
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  fmt.format = CF_DIB;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_selection->compatibility_formats, fmt.target, comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 4);
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_GIF);
+
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_GIF);
+  fmt.transmute = FALSE;
+  g_array_append_val (comp, fmt);
+
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_GIF);
+  g_array_append_val (comp, fmt);
+
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  fmt.format = CF_DIB;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_selection->compatibility_formats, fmt.target, comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 2);
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
 
-  g_hash_table_replace (_format_atom_table,
-                       GINT_TO_POINTER (_cf_png),
-                       _image_png);
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_BMP);
+  fmt.transmute = FALSE;
+  g_array_append_val (comp, fmt);
 
-  g_hash_table_replace (_format_atom_table,
-                       GINT_TO_POINTER (CF_DIB),
-                       _image_bmp);
+  fmt.format = CF_DIB;
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_selection->compatibility_formats, fmt.target, comp);
+
+
+/* Not implemented, but definitely possible
+  comp = g_array_sized_new (FALSE, FALSE, 2);
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST);
+
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_TEXT_URI_LIST);
+  fmt.transmute = FALSE;
+  g_array_append_val (comp, fmt);
+
+  fmt.format = _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_selection->compatibility_formats, fmt.target, comp);
+*/
+
+  win32_selection->compatibility_targets = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) 
g_array_unref);
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 2);
+  fmt.format = CF_TEXT;
+  fmt.transmute = FALSE;
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_TEXT);
+  g_array_append_val (comp, fmt);
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_UTF8_STRING);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_selection->compatibility_targets, GINT_TO_POINTER (CF_TEXT), comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 2);
+  fmt.format = CF_UNICODETEXT;
+  fmt.transmute = FALSE;
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_UNICODETEXT);
+  g_array_append_val (comp, fmt);
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_UTF8_STRING);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_selection->compatibility_targets, GINT_TO_POINTER (CF_UNICODETEXT), comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 3);
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
+  fmt.transmute = FALSE;
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_PNG);
+  g_array_append_val (comp, fmt);
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
+  g_array_append_val (comp, fmt);
+
+  fmt.target = _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_selection->compatibility_targets, GINT_TO_POINTER (_gdk_cf_array_index (cfs, 
GDK_WIN32_CF_INDEX_PNG)), comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 4);
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_JFIF);
+  fmt.transmute = FALSE;
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_JFIF);
+  g_array_append_val (comp, fmt);
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_JPEG);
+  g_array_append_val (comp, fmt);
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_selection->compatibility_targets, GINT_TO_POINTER (_gdk_cf_array_index (cfs, 
GDK_WIN32_CF_INDEX_JFIF)), comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 4);
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_GIF);
+  fmt.transmute = FALSE;
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_GIF);
+  g_array_append_val (comp, fmt);
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_GIF);
+  g_array_append_val (comp, fmt);
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
+  fmt.transmute = TRUE;
+  g_array_append_val (comp, fmt);
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
+  g_array_append_val (comp, fmt);
+
+  g_hash_table_replace (win32_selection->compatibility_targets, GINT_TO_POINTER (_gdk_cf_array_index (cfs, 
GDK_WIN32_CF_INDEX_GIF)), comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 3);
+  fmt.format = CF_DIB;
+  fmt.transmute = FALSE;
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_DIB);
+  g_array_append_val (comp, fmt);
+
+  fmt.target = _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_selection->compatibility_targets, GINT_TO_POINTER (CF_DIB), comp);
+
+
+  comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 3);
+  fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST);
+  fmt.transmute = FALSE;
+
+  fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CFSTR_SHELLIDLIST);
+  g_array_append_val (comp, fmt);
+
+  fmt.target = _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_selection->compatibility_targets, GINT_TO_POINTER (_gdk_cf_array_index (cfs, 
GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST)), comp);
 }
 
 /* The specifications for COMPOUND_TEXT and STRING specify that C0 and
@@ -190,65 +754,1263 @@ selection_property_store (GdkWindow *owner,
                          gint       length)
 {
   GdkSelProp *prop;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
 
   g_return_if_fail (type != GDK_TARGET_STRING);
 
-  prop = g_hash_table_lookup (sel_prop_table, GDK_WINDOW_HWND (owner));
+  prop = g_hash_table_lookup (win32_sel->sel_prop_table, GDK_WINDOW_HWND (owner));
 
   if (prop != NULL)
     {
       g_free (prop->data);
       g_free (prop);
-      g_hash_table_remove (sel_prop_table, GDK_WINDOW_HWND (owner));
+      g_hash_table_remove (win32_sel->sel_prop_table, GDK_WINDOW_HWND (owner));
     }
 
   prop = g_new (GdkSelProp, 1);
 
   prop->data = data;
   prop->length = length;
-  prop->format = format;
-  prop->type = type;
+  prop->bitness = format;
+  prop->target = type;
 
-  g_hash_table_insert (sel_prop_table, GDK_WINDOW_HWND (owner), prop);
+  g_hash_table_insert (win32_sel->sel_prop_table, GDK_WINDOW_HWND (owner), prop);
 }
 
 void
 _gdk_dropfiles_store (gchar *data)
 {
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
+
   if (data != NULL)
     {
-      g_assert (dropfiles_prop == NULL);
+      g_assert (win32_sel->dropfiles_prop == NULL);
 
-      dropfiles_prop = g_new (GdkSelProp, 1);
-      dropfiles_prop->data = (guchar *) data;
-      dropfiles_prop->length = strlen (data) + 1;
-      dropfiles_prop->format = 8;
-      dropfiles_prop->type = _text_uri_list;
+      win32_sel->dropfiles_prop = g_new (GdkSelProp, 1);
+      win32_sel->dropfiles_prop->data = (guchar *) data;
+      win32_sel->dropfiles_prop->length = strlen (data) + 1;
+      win32_sel->dropfiles_prop->bitness = 8;
+      win32_sel->dropfiles_prop->target = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST);
     }
   else
     {
-      if (dropfiles_prop != NULL)
+      if (win32_sel->dropfiles_prop != NULL)
        {
-         g_free (dropfiles_prop->data);
-         g_free (dropfiles_prop);
+         g_free (win32_sel->dropfiles_prop->data);
+         g_free (win32_sel->dropfiles_prop);
        }
-      dropfiles_prop = NULL;
+      win32_sel->dropfiles_prop = NULL;
     }
 }
 
-static gchar *
-get_mapped_gdk_atom_name (GdkAtom gdk_target)
+static void
+generate_selection_notify (GdkWindow *requestor,
+                          GdkAtom    selection,
+                          GdkAtom    target,
+                          GdkAtom    property,
+                          guint32    time)
+{
+  GdkEvent tmp_event;
+
+  memset (&tmp_event, 0, sizeof (tmp_event));
+  tmp_event.selection.type = GDK_SELECTION_NOTIFY;
+  tmp_event.selection.window = requestor;
+  tmp_event.selection.send_event = FALSE;
+  tmp_event.selection.selection = selection;
+  tmp_event.selection.target = target;
+  tmp_event.selection.property = property;
+  tmp_event.selection.requestor = 0;
+  tmp_event.selection.time = time;
+
+  gdk_event_put (&tmp_event);
+}
+
+void
+_gdk_win32_clear_clipboard_queue ()
+{
+  GList *tmp_list, *next;
+  GdkWin32ClipboardQueueInfo *info;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
+
+  GDK_NOTE (DND, g_print ("Clear clipboard queue\n"));
+
+  for (tmp_list = clipboard_queue; tmp_list; tmp_list = next)
+    {
+      info = (GdkWin32ClipboardQueueInfo *) tmp_list->data;
+      next = g_list_next (tmp_list);
+      clipboard_queue = g_list_remove_link (clipboard_queue, tmp_list);
+      g_list_free (tmp_list);
+      switch (info->action)
+        {
+        case GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS:
+          break;
+        case GDK_WIN32_CLIPBOARD_QUEUE_ACTION_CONVERT:
+          generate_selection_notify (info->requestor, info->selection, info->target, NULL, info->time);
+          break;
+        }
+      g_clear_object (&info->requestor);
+      g_slice_free (GdkWin32ClipboardQueueInfo, info);
+    }
+
+  win32_sel->targets_request_pending = FALSE;
+}
+
+/* Send ourselves a selection request message with
+ * the TARGETS target, we will do multiple SetClipboarData(...,NULL)
+ * calls in response to announce the formats we support.
+ */
+static void
+send_targets_request (guint time)
+{
+  GdkWindow *owner;
+  GdkEvent tmp_event;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
+
+  if (win32_sel->targets_request_pending)
+    return;
+
+  owner = _gdk_win32_display_get_selection_owner (gdk_display_get_default (),
+                                                  GDK_SELECTION_CLIPBOARD);
+
+  if (owner == NULL)
+    return;
+
+  if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE)
+    {
+      if (OpenClipboard (GDK_WINDOW_HWND (owner)))
+        {
+          win32_sel->clipboard_opened_for = GDK_WINDOW_HWND (owner);
+          GDK_NOTE (DND, g_print ("Opened clipboard for 0x%p @ %s:%d\n", win32_sel->clipboard_opened_for, 
__FILE__, __LINE__));
+        }
+    }
+
+  GDK_NOTE (DND, g_print ("... sending GDK_SELECTION_REQUEST to ourselves\n"));
+  memset (&tmp_event, 0, sizeof (tmp_event));
+  tmp_event.selection.type = GDK_SELECTION_REQUEST;
+  tmp_event.selection.window = owner;
+  tmp_event.selection.send_event = FALSE;
+  tmp_event.selection.selection = GDK_SELECTION_CLIPBOARD;
+  tmp_event.selection.target = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_TARGETS);
+  tmp_event.selection.property = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION);
+  tmp_event.selection.requestor = owner;
+  tmp_event.selection.time = time;
+
+  gdk_event_put (&tmp_event);
+  win32_sel->targets_request_pending = TRUE;
+}
+
+#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);
+
+  /* FIXME: 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;
+}
+
+static GArray *
+get_compatibility_formats_for_target (GdkAtom target)
+{
+  GArray *result = NULL;
+  gint i;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
+
+  result = g_hash_table_lookup (win32_sel->compatibility_formats, target);
+
+  if (result != NULL)
+    return result;
+
+  for (i = 0; i < win32_sel->n_known_pixbuf_formats; i++)
+    {
+      if (target != win32_sel->known_pixbuf_formats[i])
+        continue;
+
+      /* Any format known to gdk-pixbuf can be presented as PNG or BMP */
+      result = g_hash_table_lookup (win32_sel->compatibility_formats,
+                                    _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG));
+      break;
+    }
+
+  return result;
+}
+
+static GArray *
+_gdk_win32_selection_get_compatibility_targets_for_format (UINT format)
+{
+  GArray *result = NULL;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
+
+  result = g_hash_table_lookup (win32_sel->compatibility_targets, GINT_TO_POINTER (format));
+
+  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;
+}
+
+void
+_gdk_win32_add_format_to_targets (UINT     format,
+                                  GArray  *array,
+                                  GList  **list)
+{
+  gboolean predef;
+  gchar *format_name = _gdk_win32_get_clipboard_format_name (format, &predef);
+  GdkAtom target_atom;
+  GdkSelTargetFormat target_selformat;
+  GArray *target_selformats;
+  gint i,j;
+
+  if (format_name != NULL)
+    {
+      target_atom = gdk_atom_intern (format_name, FALSE);
+      GDK_NOTE (DND, g_print ("Maybe add as-is format %s (0x%p)\n", format_name, target_atom));
+      g_free (format_name);
+      if (array && target_atom != 0)
+        {
+          for (j = 0; j < array->len; j++)
+            if (g_array_index (array, GdkSelTargetFormat, j).target == target_atom)
+              break;
+          if (j == array->len)
+            {
+              target_selformat.format = format;
+              target_selformat.target = target_atom;
+              target_selformat.transmute = FALSE;
+              g_array_append_val (array, target_selformat);
+            }
+        }
+      if (list && target_atom != 0 && g_list_find (*list, target_atom) == NULL)
+        *list = g_list_prepend (*list, target_atom);
+    }
+
+ target_selformats = _gdk_win32_selection_get_compatibility_targets_for_format (format);
+
+ if (array && target_selformats != NULL)
+   for (i = 0; i < target_selformats->len; i++)
+     {
+       target_selformat = g_array_index (target_selformats, GdkSelTargetFormat, i);
+
+       for (j = 0; j < array->len; j++)
+         if (g_array_index (array, GdkSelTargetFormat, j).target == target_selformat.target &&
+             g_array_index (array, GdkSelTargetFormat, j).format == target_selformat.format)
+           break;
+
+       if (j == array->len)
+         g_array_append_val (array, target_selformat);
+     }
+
+ if (list && target_selformats != NULL)
+   for (i = 0; i < target_selformats->len; i++)
+     {
+       target_selformat = g_array_index (target_selformats, GdkSelTargetFormat, i);
+
+       if (g_list_find (*list, target_selformat.target) == NULL)
+         *list = g_list_prepend (*list, target_selformat.target);
+     }
+}
+
+static void
+transmute_cf_unicodetext_to_utf8_string (const guchar    *data,
+                                         gint             length,
+                                         guchar         **set_data,
+                                         gint            *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,
+                                         gint            *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,
+                                  gint            *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,
+                                  gint            *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,
+                               gint            *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,
+                                             gint            *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,
+                               gint            *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;
+}
+
+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_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG) &&
+       from_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_PNG)) ||
+      (to_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) &&
+       from_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_JFIF)) ||
+      (to_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GIF) &&
+       from_format == _gdk_win32_selection_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_selection_atom (GDK_WIN32_ATOM_INDEX_UTF8_STRING) &&
+           from_format == CF_UNICODETEXT)
+    {
+      transmute_cf_unicodetext_to_utf8_string (data, length, set_data, set_data_length, NULL);
+    }
+  else if (to_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_UTF8_STRING) &&
+           from_format == CF_TEXT)
+    {
+      transmute_cf_text_to_utf8_string (data, length, set_data, set_data_length, NULL);
+    }
+  else if (to_target == _gdk_win32_selection_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_selection_atom (GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) &&
+           from_format == _gdk_win32_selection_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);
+    }
+}
+
+void
+transmute_selection_target (GdkAtom       from_target,
+                            UINT          to_format,
+                            const guchar *data,
+                            gint          length,
+                            guchar      **set_data,
+                            gint         *set_data_length)
+{
+  if ((from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG) &&
+       to_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_PNG)) ||
+      (from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) &&
+       to_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_JFIF)) ||
+      (from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GIF) &&
+       to_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_GIF)))
+    {
+      /* No conversion needed */
+      *set_data = g_memdup (data, length);
+      *set_data_length = length;
+    }
+  else if (from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_UTF8_STRING) &&
+           to_format == CF_UNICODETEXT)
+    {
+      transmute_utf8_string_to_cf_unicodetext (data, length, set_data, set_data_length, NULL);
+    }
+  else if (from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_UTF8_STRING) &&
+           to_format == CF_TEXT)
+    {
+      transmute_utf8_string_to_cf_text (data, length, set_data, set_data_length, NULL);
+    }
+  else if (from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_BMP) &&
+           to_format == CF_DIB)
+    {
+      transmute_image_bmp_to_cf_dib (data, length, set_data, set_data_length, NULL);
+    }
+  else if (from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_BMP) &&
+           to_format == CF_DIBV5)
+    {
+      transmute_image_bmp_to_cf_dib (data, length, set_data, set_data_length, NULL);
+    }
+/*
+  else if (from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) &&
+           to_format == _gdk_win32_selection_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_target, to_format);
+    }
+}
+
+static GdkAtom
+convert_clipboard_selection_to_targets_target (GdkWindow *requestor)
+{
+  gint fmt;
+  int i;
+  int format_count = CountClipboardFormats ();
+  GArray *targets = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), format_count);
+
+  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++)
+        {
+          gchar *atom_name = gdk_atom_name (g_array_index (targets, GdkSelTargetFormat, i).target);
+
+          g_print ("%s", atom_name);
+          g_free (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, GdkSelTargetFormat, i).target;
+
+      g_array_free (targets, TRUE);
+      selection_property_store (requestor, GDK_SELECTION_TYPE_ATOM,
+                                32, (guchar *) targets_only,
+                                len * sizeof (GdkAtom));
+      return _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION);
+    }
+  else
+    {
+      g_array_free (targets, TRUE);
+      return NULL;
+    }
+}
+
+static GdkAtom
+convert_clipboard_selection_to_target (GdkWindow *requestor,
+                                       GdkAtom    target)
+{
+  UINT format;
+  HANDLE hdata;
+  guchar *ptr;
+  gint length;
+  gboolean transmute = FALSE;
+  GdkAtom result = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION);
+  gboolean found;
+  gchar *atom_name;
+
+  atom_name = gdk_atom_name (target);
+
+  for (format = 0, found = FALSE;
+       !found && 0 != (format = EnumClipboardFormats (format));
+      )
+    {
+      gboolean predef;
+      gchar *format_name = _gdk_win32_get_clipboard_format_name (format, &predef);
+
+      if (format_name == NULL)
+        continue;
+
+      found = g_strcmp0 (format_name, atom_name) == 0;
+      g_free (format_name);
+    }
+
+  g_free (atom_name);
+
+  if (format == 0)
+    {
+      gint i;
+      GArray *compat_formats = get_compatibility_formats_for_target (target);
+
+      for (i = 0; compat_formats != NULL && i < compat_formats->len; i++)
+        {
+          if (!IsClipboardFormatAvailable (g_array_index (compat_formats, GdkSelTargetFormat, i).format))
+            continue;
+
+          format = g_array_index (compat_formats, GdkSelTargetFormat, i).format;
+          transmute = g_array_index (compat_formats, GdkSelTargetFormat, i).transmute;
+          break;
+        }
+    }
+
+  if (format == 0)
+    return NULL;
+
+  if ((hdata = GetClipboardData (format)) == NULL)
+    return NULL;
+
+  if ((ptr = GlobalLock (hdata)) != NULL)
+    {
+      guchar *data = NULL;
+      gint data_len = 0;
+      length = GlobalSize (hdata);
+
+      GDK_NOTE (DND, g_print ("... format 0x%x: %d bytes\n", format, length));
+
+      if (transmute)
+        {
+          transmute_selection_format (format, target, ptr, length, &data, &data_len);
+        }
+      else
+        {
+          data = g_memdup (ptr, length);
+          data_len = length;
+        }
+
+      if (data)
+        selection_property_store (requestor, target,
+                                  8, data, data_len);
+      else
+        result = NULL;
+
+      GlobalUnlock (hdata);
+    }
+
+  return result;
+}
+
+static GdkAtom
+convert_selection_with_opened_clipboard (GdkDisplay *display,
+                                          GdkWindow  *requestor,
+                                          GdkAtom     target,
+                                          guint32     time)
 {
-  if (gdk_target == _image_png)
-    return g_strdup ("PNG");
+  GdkAtom property;
 
-  if (gdk_target == _image_jpeg)
-    return g_strdup ("JFIF");
+  if (target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_TARGETS))
+    property = convert_clipboard_selection_to_targets_target (requestor);
+  else
+    property = convert_clipboard_selection_to_target (requestor, target);
+
+  return property;
+}
 
-  if (gdk_target == _image_gif)
-    return g_strdup ("GIF");
+static void
+announce_delayrendered_targets_with_opened_clipboard (GdkWin32Selection *win32_sel)
+{
+  gint i;
+  /* Announce the formats we support, but don't actually put any data out there.
+   * Other processes will send us WM_RENDERFORMAT to get the data.
+   */
+  for (i = 0; i < win32_sel->clipboard_selection_targets->len; i++)
+    {
+      GdkSelTargetFormat *fmt = &g_array_index (win32_sel->clipboard_selection_targets, GdkSelTargetFormat, 
i);
 
-  return gdk_atom_name (gdk_target);
+      /* Some calls here may be duplicates, but we don't really care */
+      if (fmt->format != 0)
+        SetClipboardData (fmt->format, NULL);
+    }
+}
+
+static gboolean
+open_clipboard_timeout (gpointer data)
+{
+  GList *tmp_list, *next;
+  GdkWin32ClipboardQueueInfo *info;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
+
+  GDK_NOTE (DND, g_print ("Open clipboard timeout ticks\n"));
+
+  /* Clear out old and invalid entries */
+  for (tmp_list = clipboard_queue; tmp_list; tmp_list = next)
+    {
+      info = (GdkWin32ClipboardQueueInfo *) tmp_list->data;
+      next = g_list_next (tmp_list);
+
+      if (GDK_WINDOW_DESTROYED (info->requestor) ||
+          info->idle_time >= CLIPBOARD_IDLE_ABORT_TIME)
+        {
+          clipboard_queue = g_list_remove_link (clipboard_queue, tmp_list);
+          g_list_free (tmp_list);
+          switch (info->action)
+            {
+            case GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS:
+              break;
+            case GDK_WIN32_CLIPBOARD_QUEUE_ACTION_CONVERT:
+              generate_selection_notify (info->requestor, info->selection, info->target, NULL, info->time);
+              break;
+            }
+          g_clear_object (&info->requestor);
+          g_slice_free (GdkWin32ClipboardQueueInfo, info);
+        }
+    }
+
+  if (clipboard_queue == NULL)
+    {
+      GDK_NOTE (DND, g_print ("Stopping open clipboard timer\n"));
+
+      if (win32_sel->clipboard_opened_for != INVALID_HANDLE_VALUE)
+        {
+          API_CALL (CloseClipboard, ());
+          win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
+          GDK_NOTE (DND, g_print ("Closed clipboard @ %s:%d\n", __FILE__, __LINE__));
+        }
+
+      return FALSE;
+    }
+
+  for (tmp_list = clipboard_queue; tmp_list; tmp_list = next)
+    {
+      GdkAtom property;
+
+      info = (GdkWin32ClipboardQueueInfo *) tmp_list->data;
+      next = g_list_next (tmp_list);
+
+      /* CONVERT works with any opened clipboard,
+       * but TARGETS needs to open the clipboard with the hande of the
+       * owner window.
+       */
+      if (info->action == GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS &&
+          win32_sel->clipboard_opened_for == NULL)
+        {
+          GDK_NOTE (DND, g_print ("Need to re-open clipboard, closing\n"));
+          API_CALL (CloseClipboard, ());
+          win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
+        }
+
+      if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE)
+        {
+          if (!OpenClipboard (GDK_WINDOW_HWND (info->requestor)))
+            {
+              info->idle_time += 1;
+              continue;
+            }
+          win32_sel->clipboard_opened_for = GDK_WINDOW_HWND (info->requestor);
+          GDK_NOTE (DND, g_print ("Opened clipboard for 0x%p @ %s:%d\n", win32_sel->clipboard_opened_for, 
__FILE__, __LINE__));
+        }
+
+      clipboard_queue = g_list_remove_link (clipboard_queue, tmp_list);
+      g_list_free (tmp_list);
+
+      switch (info->action)
+        {
+        case GDK_WIN32_CLIPBOARD_QUEUE_ACTION_CONVERT:
+          property = convert_selection_with_opened_clipboard (info->display,
+                                                              info->requestor,
+                                                              info->target,
+                                                              info->time);
+          generate_selection_notify (info->requestor,
+                                     GDK_SELECTION_CLIPBOARD,
+                                     info->target,
+                                     property,
+                                     info->time);
+          break;
+        case GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS:
+          announce_delayrendered_targets_with_opened_clipboard (win32_sel);
+          break;
+        default:
+          g_assert_not_reached ();
+        }
+
+      g_clear_object (&info->requestor);
+      g_slice_free (GdkWin32ClipboardQueueInfo, info);
+    }
+
+  if (clipboard_queue != NULL)
+    return TRUE;
+
+  if (win32_sel->clipboard_opened_for != INVALID_HANDLE_VALUE)
+    {
+      API_CALL (CloseClipboard, ());
+      win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
+      GDK_NOTE (DND, g_print ("Closed clipboard @ %s:%d\n", __FILE__, __LINE__));
+    }
+
+  GDK_NOTE (DND, g_print ("Stopping open clipboard timer\n"));
+
+  return FALSE;
+}
+
+static void
+queue_open_clipboard (GdkWin32ClipboardQueueAction  action,
+                      GdkDisplay                   *display,
+                      GdkWindow                    *requestor,
+                      GdkAtom                       target,
+                      guint32                       time)
+{
+  guint id;
+  GList *tmp_list, *next;
+  GdkWin32ClipboardQueueInfo *info;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
+
+  for (tmp_list = clipboard_queue; tmp_list; tmp_list = next)
+    {
+      info = (GdkWin32ClipboardQueueInfo *) tmp_list->data;
+      next = g_list_next (tmp_list);
+
+      if (info->action == action &&
+          info->requestor == requestor)
+       return;
+    }
+
+  info = g_slice_new (GdkWin32ClipboardQueueInfo);
+
+  info->display = display;
+  g_set_object (&info->requestor, requestor);
+  info->selection = GDK_SELECTION_CLIPBOARD;
+  info->target = target;
+  info->idle_time = 0;
+  info->time = time;
+  info->action = action;
+
+  GDK_NOTE (DND, g_print ("Queueing open clipboard\n"));
+
+  if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE &&
+      clipboard_queue == NULL)
+    {
+      id = gdk_threads_add_timeout_seconds (1, (GSourceFunc) open_clipboard_timeout, NULL);
+      g_source_set_name_by_id (id, "[gdk-win32] open_clipboard_timeout");
+      GDK_NOTE (DND, g_print ("Started open clipboard timer\n"));
+    }
+
+  clipboard_queue = g_list_append (clipboard_queue, info);
 }
 
 gboolean
@@ -259,7 +2021,7 @@ _gdk_win32_display_set_selection_owner (GdkDisplay *display,
                                        gboolean    send_event)
 {
   HWND hwnd;
-  GdkEvent tmp_event;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
 
   g_return_val_if_fail (selection != NULL, FALSE);
 
@@ -275,9 +2037,10 @@ _gdk_win32_display_set_selection_owner (GdkDisplay *display,
   if (selection != GDK_SELECTION_CLIPBOARD)
     {
       if (owner != NULL)
-       g_hash_table_insert (sel_owner_table, selection, GDK_WINDOW_HWND (owner));
+        g_hash_table_insert (win32_sel->sel_owner_table, selection, GDK_WINDOW_HWND (owner));
       else
-       g_hash_table_remove (sel_owner_table, selection);
+        g_hash_table_remove (win32_sel->sel_owner_table, selection);
+
       return TRUE;
     }
 
@@ -292,41 +2055,55 @@ _gdk_win32_display_set_selection_owner (GdkDisplay *display,
   else
     hwnd = NULL;
 
-  if (!API_CALL (OpenClipboard, (hwnd)))
-    return FALSE;
+  if (win32_sel->clipboard_opened_for != hwnd &&
+      win32_sel->clipboard_opened_for != INVALID_HANDLE_VALUE)
+    {
+      API_CALL (CloseClipboard, ());
+      win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
+      GDK_NOTE (DND, g_print ("Closed clipboard @ %s:%d\n", __FILE__, __LINE__));
+    }
+
+  if (!OpenClipboard (hwnd))
+    {
+      if (GetLastError () != ERROR_ACCESS_DENIED)
+        WIN32_API_FAILED ("OpenClipboard");
+
+      return FALSE;
+    }
 
-  _ignore_destroy_clipboard = TRUE;
+  win32_sel->clipboard_opened_for = hwnd;
+  GDK_NOTE (DND, g_print ("Opened clipboard for 0x%p @ %s:%d\n", win32_sel->clipboard_opened_for, __FILE__, 
__LINE__));
+  win32_sel->ignore_destroy_clipboard = TRUE;
   GDK_NOTE (DND, g_print ("... EmptyClipboard()\n"));
   if (!API_CALL (EmptyClipboard, ()))
     {
-      _ignore_destroy_clipboard = FALSE;
+      win32_sel->ignore_destroy_clipboard = FALSE;
       API_CALL (CloseClipboard, ());
+      win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
+      GDK_NOTE (DND, g_print ("Closed clipboard @ %s:%d\n", __FILE__, __LINE__));
       return FALSE;
     }
-  _ignore_destroy_clipboard = FALSE;
+  win32_sel->ignore_destroy_clipboard = FALSE;
 
-  if (!API_CALL (CloseClipboard, ()))
-    return FALSE;
+  /* Any queued clipboard operations were just made pointless
+   * by EmptyClipboard().
+   */
+  _gdk_win32_clear_clipboard_queue ();
 
-  if (owner != NULL)
+  /* This is kind of risky, but we don't close the clipboard
+   * to ensure that it's still open when GDK_SELECTION_REQUEST
+   * is handled.
+   */
+  if (owner == NULL)
     {
-      /* Send ourselves a selection request message so that
-       * gdk_property_change will be called to store the clipboard
-       * data.
-       */
-      GDK_NOTE (DND, g_print ("... sending GDK_SELECTION_REQUEST to ourselves\n"));
-      tmp_event.selection.type = GDK_SELECTION_REQUEST;
-      tmp_event.selection.window = owner;
-      tmp_event.selection.send_event = FALSE;
-      tmp_event.selection.selection = selection;
-      tmp_event.selection.target = _utf8_string;
-      tmp_event.selection.property = _gdk_selection;
-      tmp_event.selection.requestor = gdk_win32_handle_table_lookup (hwnd);
-      tmp_event.selection.time = time;
-
-      gdk_event_put (&tmp_event);
+      if (!API_CALL (CloseClipboard, ()))
+        return FALSE;
+      GDK_NOTE (DND, g_print ("Closed clipboard @ %s:%d\n", __FILE__, __LINE__));
+      win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
     }
 
+  send_targets_request (time);
+
   return TRUE;
 }
 
@@ -335,21 +2112,21 @@ _gdk_win32_display_get_selection_owner (GdkDisplay *display,
                                         GdkAtom     selection)
 {
   GdkWindow *window;
+  HWND selection_owner;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
 
   g_return_val_if_fail (selection != NULL, NULL);
 
   if (selection == GDK_SELECTION_CLIPBOARD)
-    {
-      HWND owner = GetClipboardOwner ();
-
-      if (owner == NULL)
-       return NULL;
-
-      return gdk_win32_handle_table_lookup (owner);
-    }
+    selection_owner = GetClipboardOwner ();
+  else
+    selection_owner = g_hash_table_lookup (win32_sel->sel_owner_table, selection);
 
-  window = gdk_win32_window_lookup_for_display (display,
-                                                g_hash_table_lookup (sel_owner_table, selection));
+  if (selection_owner)
+    window = gdk_win32_window_lookup_for_display (display,
+                                                  selection_owner);
+  else
+    window = NULL;
 
   GDK_NOTE (DND, {
       gchar *sel_name = gdk_atom_name (selection);
@@ -363,25 +2140,102 @@ _gdk_win32_display_get_selection_owner (GdkDisplay *display,
   return window;
 }
 
-static void
-generate_selection_notify (GdkWindow *requestor,
-                          GdkAtom    selection,
-                          GdkAtom    target,
-                          GdkAtom    property,
-                          guint32    time)
+static GdkAtom
+convert_dnd_selection_to_target (GdkAtom    target,
+                                 GdkWindow *requestor)
 {
-  GdkEvent tmp_event;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
+  UINT format;
+  gint i, with_transmute;
+  guchar *ptr;
+  gint length;
+  gboolean transmute = FALSE;
+  GdkWin32DragContext *context_win32;
+  FORMATETC fmt;
+  STGMEDIUM storage;
+  HRESULT hr;
+  GdkAtom result = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND);
+
+  g_assert (win32_sel->target_drag_context != NULL);
+  g_assert (win32_sel->dnd_data_object_target != NULL);
+
+  context_win32 = GDK_WIN32_DRAG_CONTEXT (win32_sel->target_drag_context);
+
+  fmt.ptd = NULL;
+  fmt.dwAspect = DVASPECT_CONTENT;
+  fmt.lindex = -1;
+  fmt.tymed = TYMED_HGLOBAL;
+
+  for (format = 0, with_transmute = 0; format == 0 && with_transmute < 2; with_transmute++)
+    {
+      for (i = 0;
+           i < context_win32->droptarget_format_target_map->len;
+           i++)
+        {
+          GdkSelTargetFormat selformat = g_array_index (context_win32->droptarget_format_target_map, 
GdkSelTargetFormat, i);
 
-  tmp_event.selection.type = GDK_SELECTION_NOTIFY;
-  tmp_event.selection.window = requestor;
-  tmp_event.selection.send_event = FALSE;
-  tmp_event.selection.selection = selection;
-  tmp_event.selection.target = target;
-  tmp_event.selection.property = property;
-  tmp_event.selection.requestor = 0;
-  tmp_event.selection.time = time;
+          if (selformat.target != target ||
+              selformat.transmute != (with_transmute == 0 ? FALSE : TRUE))
+            continue;
 
-  gdk_event_put (&tmp_event);
+          fmt.cfFormat = selformat.format;
+
+          hr = IDataObject_QueryGetData (win32_sel->dnd_data_object_target, &fmt);
+
+          if (!SUCCEEDED (hr) || hr != S_OK)
+            continue;
+
+          format = selformat.format;
+          transmute = selformat.transmute;
+          break;
+        }
+    }
+
+  if (format == 0)
+    return NULL;
+
+  hr = IDataObject_GetData (win32_sel->dnd_data_object_target, &fmt, &storage);
+
+  if (!SUCCEEDED (hr) || hr != S_OK)
+    return NULL;
+
+  if ((ptr = GlobalLock (storage.hGlobal)) != NULL)
+    {
+      guchar *data = NULL;
+      gint    data_len = 0;
+
+      SetLastError (0);
+      length = GlobalSize (storage.hGlobal);
+
+      if (GetLastError () == NO_ERROR)
+        {
+          if (transmute)
+            {
+              transmute_selection_format (format, target, ptr, length, &data, &data_len);
+            }
+          else
+            {
+              data = g_memdup (ptr, length);
+              data_len = length;
+            }
+
+          if (data)
+            selection_property_store (requestor, target, 8,
+                                      data, data_len);
+          else
+            result = NULL;
+        }
+      else
+        result = NULL;
+
+      GlobalUnlock (storage.hGlobal);
+    }
+  else
+    result = NULL;
+
+  ReleaseStgMedium (&storage);
+
+  return result;
 }
 
 void
@@ -391,8 +2245,8 @@ _gdk_win32_display_convert_selection (GdkDisplay *display,
                                      GdkAtom    target,
                                      guint32    time)
 {
-  HGLOBAL hdata;
-  GdkAtom property = _gdk_selection;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
+  GdkAtom property = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION);
 
   g_return_if_fail (selection != NULL);
   g_return_if_fail (requestor != NULL);
@@ -411,404 +2265,156 @@ _gdk_win32_display_convert_selection (GdkDisplay *display,
       g_free (tgt_name);
     });
 
-  if (selection == GDK_SELECTION_CLIPBOARD && target == _targets)
+  if (selection == GDK_SELECTION_CLIPBOARD)
     {
-      gint ntargets, fmt;
-      GdkAtom *targets;
-      gboolean has_text = FALSE;
-      gboolean has_png = FALSE;
-      gboolean has_bmp = FALSE;
-
-      if (!API_CALL (OpenClipboard, (GDK_WINDOW_HWND (requestor))))
-       return;
-
-      targets = g_new (GdkAtom, CountClipboardFormats ());
-      ntargets = 0;
-
-      for (fmt = 0; 0 != (fmt = EnumClipboardFormats (fmt)); )
-       {
-         if (fmt == _cf_png)
-           {
-             targets[ntargets++] = _image_png;
-             has_png = TRUE;
-           }
-       }
-
-      for (fmt = 0; 0 != (fmt = EnumClipboardFormats (fmt)); )
-       {
-         gchar sFormat[80];
-
-         if (fmt == CF_UNICODETEXT || fmt == CF_TEXT)
-           {
-             /* Advertise text to GDK always as UTF8_STRING */
-             if (!has_text)
-               targets[ntargets++] = _utf8_string;
-             has_text = TRUE;
-           }
-         else if (fmt == _cf_png)
-           {
-             /* Already handled above */
-           }
-         else if (fmt == CF_DIB ||
-                  fmt == CF_DIBV5)
-           {
-             /* Don't bother telling that a bitmap is present if there is
-              * also PNG, which is much more reliable in transferring
-              * transparency.
-              */
-             if (!has_bmp && !has_png)
-               targets[ntargets++] = _image_bmp;
-             has_bmp = TRUE;
-           }
-         else if (fmt == _cf_jfif)
-           {
-             /* Ditto for JPEG */
-             if (!has_png)
-               targets[ntargets++] = _image_jpeg;
-           }
-         else if (fmt == _cf_gif)
-           {
-             /* Ditto for GIF.
-              */
-             if (!has_png)
-               targets[ntargets++] = _image_gif;
-           }
-         else if (GetClipboardFormatName (fmt, sFormat, 80) > 0)
-           {
-             if (strcmp (sFormat, "image/bmp") == 0 ||
-                 strcmp (sFormat, "image/x-bmp") == 0 ||
-                 strcmp (sFormat, "image/x-MS-bmp") == 0 ||
-                 strcmp (sFormat, "image/x-icon") == 0 ||
-                 strcmp (sFormat, "image/x-ico") == 0 ||
-                 strcmp (sFormat, "image/x-win-bitmap") == 0)
-               {
-                 /* Ignore these (from older GTK+ versions
-                  * presumably), as the same image in the CF_DIB
-                  * format will also be on the clipboard anyway.
-                  */
-               }
-             else
-               targets[ntargets++] = gdk_atom_intern (sFormat, FALSE);
+      if (win32_sel->clipboard_opened_for != INVALID_HANDLE_VALUE ||
+          OpenClipboard (GDK_WINDOW_HWND (requestor)))
+        {
+          if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE)
+            {
+              win32_sel->clipboard_opened_for = GDK_WINDOW_HWND (requestor);
+              GDK_NOTE (DND, g_print ("Opened clipboard for 0x%p @ %s:%d\n", 
win32_sel->clipboard_opened_for, __FILE__, __LINE__));
             }
-        }
-
-      API_CALL (CloseClipboard, ());
-
-      GDK_NOTE (DND, {
-         int i;
-
-         g_print ("... ");
-         for (i = 0; i < ntargets; i++)
-           {
-             gchar *atom_name = gdk_atom_name (targets[i]);
 
-             g_print ("%s", atom_name);
-             g_free (atom_name);
-             if (i < ntargets - 1)
-               g_print (", ");
-           }
-         g_print ("\n");
-       });
-
-      if (ntargets > 0)
-       selection_property_store (requestor, GDK_SELECTION_TYPE_ATOM,
-                                 32, (guchar *) targets,
-                                 ntargets * sizeof (GdkAtom));
+          queue_open_clipboard (GDK_WIN32_CLIPBOARD_QUEUE_ACTION_CONVERT, display, requestor, target, time);
+          open_clipboard_timeout (NULL);
+        }
       else
-       property = NULL;
+        {
+          queue_open_clipboard (GDK_WIN32_CLIPBOARD_QUEUE_ACTION_CONVERT, display, requestor, target, time);
+          /* Do not generate a selection notify message */
+          return;
+        }
     }
-  else if (selection == GDK_SELECTION_CLIPBOARD && target == _utf8_string)
+  else if (selection == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_DROPFILES_DND))
     {
-      /* Converting the CLIPBOARD selection means he wants the
-       * contents of the clipboard. Get the clipboard data, and store
-       * it for later.
+      /* This means he wants the names of the dropped files.
+       * gdk_dropfiles_filter already has stored the text/uri-list
+       * data temporarily in dropfiles_prop.
        */
-      if (!API_CALL (OpenClipboard, (GDK_WINDOW_HWND (requestor))))
-       return;
-
-      if ((hdata = GetClipboardData (CF_UNICODETEXT)) != NULL)
+      if (win32_sel->dropfiles_prop != NULL)
        {
-         wchar_t *ptr, *p, *q;
-         gchar *data;
-         glong length, wclen;
-
-         if ((ptr = GlobalLock (hdata)) != NULL)
-           {
-             length = GlobalSize (hdata);
-
-             GDK_NOTE (DND, g_print ("... CF_UNICODETEXT: %ld bytes\n",
-                                     length));
-
-             /* Strip out \r */
-             p = ptr;
-             q = ptr;
-             wclen = 0;
-             while (p < ptr + length / 2)
-               {
-                 if (*p != '\r')
-                   {
-                     *q++ = *p;
-                     wclen++;
-                   }
-                 p++;
-               }
-
-             data = g_utf16_to_utf8 (ptr, wclen, NULL, NULL, NULL);
-
-             if (data)
-               selection_property_store (requestor, _utf8_string, 8,
-                                         (guchar *) data, strlen (data) + 1);
-             GlobalUnlock (hdata);
-           }
+         selection_property_store
+           (requestor, win32_sel->dropfiles_prop->target, win32_sel->dropfiles_prop->bitness,
+            win32_sel->dropfiles_prop->data, win32_sel->dropfiles_prop->length);
+         g_clear_pointer (&win32_sel->dropfiles_prop, g_free);
        }
-      else
-       property = NULL;
-
-      API_CALL (CloseClipboard, ());
     }
-  else if (selection == GDK_SELECTION_CLIPBOARD && target == _image_bmp)
+  else if (selection == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND))
     {
-      if (!API_CALL (OpenClipboard, (GDK_WINDOW_HWND (requestor))))
-       return;
+      property = convert_dnd_selection_to_target (target, requestor);
+    }
+  else
+    property = NULL;
 
-      if ((hdata = GetClipboardData (CF_DIB)) != NULL)
-        {
-          BITMAPINFOHEADER *bi;
+  /* Generate a selection notify message so that we actually fetch the
+   * data (if property == GDK_SELECTION) or indicating failure (if
+   * property == NULL).
+   */
+  generate_selection_notify (requestor, selection, target, property, time);
+}
 
-          if ((bi = GlobalLock (hdata)) != NULL)
-            {
-             /* 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.
-              */
-              BITMAPFILEHEADER *bf;
-             gpointer data;
-             gint data_length = GlobalSize (hdata);
-             gint new_length;
-             gboolean make_dibv5 = FALSE;
-
-             GDK_NOTE (DND, g_print ("... CF_DIB: %d bytes\n", data_length));
-
-             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
-                  (RegisterClipboardFormat ("application/x-moz-nativeimage")) ||
-                  IsClipboardFormatAvailable
-                  (RegisterClipboardFormat ("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);
-               }
-
-              data = g_try_malloc (new_length);
-
-              if (data)
-                {
-                  bf = (BITMAPFILEHEADER *)data;
-                  bf->bfType = 0x4d42; /* "BM" */
-                  bf->bfSize = new_length;
-                  bf->bfReserved1 = 0;
-                  bf->bfReserved2 = 0;
-
-                 if (make_dibv5)
-                   {
-                     BITMAPV5HEADER *bV5 = (BITMAPV5HEADER *) ((char *) data + sizeof (BITMAPFILEHEADER));
-                     guchar *p;
-                     guint i;
-
-                     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 *) data) + 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;
-                       }
-                   }
-                 else
-                   {
-                     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 *) data + sizeof (BITMAPFILEHEADER),
-                             bi,
-                             data_length);
-                   }
-
-                 selection_property_store (requestor, _image_bmp, 8,
-                                           data, new_length);
-                }
-             GlobalUnlock (hdata);
-            }
-      }
+void
+_gdk_win32_selection_property_change (GdkWin32Selection *win32_sel,
+                                      GdkWindow         *window,
+                                      GdkAtom            property,
+                                      GdkAtom            type,
+                                      gint               format,
+                                      GdkPropMode        mode,
+                                      const guchar      *data,
+                                      gint               nelements)
+{
+  if (property == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION) &&
+      type == GDK_SELECTION_TYPE_ATOM) /* implies target == _gdk_win32_selection_atom 
(GDK_WIN32_ATOM_INDEX_TARGETS) */
+    {
+      if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE &&
+          OpenClipboard (GDK_WINDOW_HWND (window)))
+        {
+          win32_sel->clipboard_opened_for = GDK_WINDOW_HWND (window);
+          GDK_NOTE (DND, g_print ("Opened clipboard for 0x%p @ %s:%d\n", win32_sel->clipboard_opened_for, 
__FILE__, __LINE__));
+        }
 
-      API_CALL (CloseClipboard, ());
+      if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE)
+        {
+          queue_open_clipboard (GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS, NULL, window, type, 
GDK_CURRENT_TIME);
+          return;
+        }
+      else
+        {
+          queue_open_clipboard (GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS, NULL, window, type, 
GDK_CURRENT_TIME);
+          open_clipboard_timeout (NULL);
+        }
     }
-  else if (selection == GDK_SELECTION_CLIPBOARD)
+  else if (mode == GDK_PROP_MODE_REPLACE &&
+           (win32_sel->property_change_data == NULL ||
+            win32_sel->property_change_format == 0))
     {
-      gchar *mapped_target_name;
-      UINT fmt = 0;
-
-      if (!API_CALL (OpenClipboard, (GDK_WINDOW_HWND (requestor))))
-       return;
-
-      mapped_target_name = get_mapped_gdk_atom_name (target);
+      g_warning ("Setting selection property with 0x%p == NULL or 0x%x == 0", 
win32_sel->property_change_data, win32_sel->property_change_format);
+    }
+  else if (mode == GDK_PROP_MODE_REPLACE &&
+           win32_sel->property_change_data != NULL &&
+           win32_sel->property_change_format != 0)
+    {
+      guchar *set_data = NULL;
+      gint set_data_length = 0;
+      gint byte_length = format / 8 * nelements;
+
+      if (win32_sel->property_change_transmute)
+        transmute_selection_target (type,
+                                    win32_sel->property_change_format,
+                                    data,
+                                    byte_length,
+                                    &set_data,
+                                    &set_data_length);
+      else
+        {
+          set_data_length = byte_length;
+          set_data = g_memdup (data, set_data_length);
+        }
 
-      /* Check if it's available. We could simply call
-       * GetClipboardData (RegisterClipboardFormat (targetname)), but
-       * the global custom format ID space is limited,
-       * (0xC000~0xFFFF), and we better not waste an format ID if we
-       * are just a requestor.
-       */
-      for ( ; 0 != (fmt = EnumClipboardFormats (fmt)); )
+      if (set_data != NULL && set_data_length > 0)
         {
-          char sFormat[80];
+          HGLOBAL hdata;
 
-          if (GetClipboardFormatName (fmt, sFormat, 80) > 0 &&
-              strcmp (sFormat, mapped_target_name) == 0)
+          if ((hdata = GlobalAlloc (GMEM_MOVEABLE, set_data_length)) != 0)
             {
-              if ((hdata = GetClipboardData (fmt)) != NULL)
-               {
-                 /* Simply get it without conversion */
-                  guchar *ptr;
-                  gint length;
-
-                  if ((ptr = GlobalLock (hdata)) != NULL)
-                    {
-                      length = GlobalSize (hdata);
-
-                      GDK_NOTE (DND, g_print ("... %s: %d bytes\n", mapped_target_name, length));
-
-                      selection_property_store (requestor, target, 8,
-                                               g_memdup (ptr, length), length);
-                     GlobalUnlock (hdata);
-                      break;
-                    }
-                }
+              gchar *ucptr;
+              win32_sel->property_change_data->tymed = TYMED_HGLOBAL;
+              win32_sel->property_change_data->pUnkForRelease = NULL;
+              win32_sel->property_change_data->hGlobal = hdata;
+              ucptr = GlobalLock (hdata);
+              memcpy (ucptr, set_data, set_data_length);
+              GlobalUnlock (hdata);
             }
+          else
+            {
+             WIN32_API_FAILED ("GlobalAlloc");
+            }
+
+          g_free (set_data);
         }
-      g_free (mapped_target_name);
-      API_CALL (CloseClipboard, ());
     }
-  else if (selection == _gdk_win32_dropfiles)
+  else
     {
-      /* This means he wants the names of the dropped files.
-       * gdk_dropfiles_filter already has stored the text/uri-list
-       * data temporarily in dropfiles_prop.
-       */
-      if (dropfiles_prop != NULL)
-       {
-         selection_property_store
-           (requestor, dropfiles_prop->type, dropfiles_prop->format,
-            dropfiles_prop->data, dropfiles_prop->length);
-         g_free (dropfiles_prop);
-         dropfiles_prop = NULL;
-       }
+      gchar *prop_name = gdk_atom_name (property);
+      gchar *type_name = gdk_atom_name (type);
+      gchar *datastring = _gdk_win32_data_to_string (data, MIN (10, format*nelements/8));
+
+      g_warning ("Unsupported property change on window 0x%p, %s property %s, %d-bit, target 0x%x of %d 
bytes: %s",
+                 window,
+                 (mode == GDK_PROP_MODE_REPLACE ? "REPLACE" :
+                  (mode == GDK_PROP_MODE_PREPEND ? "PREPEND" :
+                   (mode == GDK_PROP_MODE_APPEND ? "APPEND" :
+                    "???"))),
+                 prop_name,
+                 format,
+                 type_name,
+                 nelements,
+                 datastring);
+      g_free (datastring);
+      g_free (prop_name);
+      g_free (type_name);
     }
-  else
-    property = NULL;
-
-  /* Generate a selection notify message so that we actually fetch the
-   * data (if property == _gdk_selection) or indicating failure (if
-   * property == NULL).
-   */
-  generate_selection_notify (requestor, selection, target, property, time);
 }
 
 gint
@@ -818,6 +2424,7 @@ _gdk_win32_display_get_selection_property (GdkDisplay *display,
                                           GdkAtom    *ret_type,
                                           gint       *ret_format)
 {
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
   GdkSelProp *prop;
 
   g_return_val_if_fail (requestor != NULL, 0);
@@ -829,7 +2436,7 @@ _gdk_win32_display_get_selection_property (GdkDisplay *display,
   GDK_NOTE (DND, g_print ("gdk_selection_property_get: %p",
                           GDK_WINDOW_HWND (requestor)));
 
-  prop = g_hash_table_lookup (sel_prop_table, GDK_WINDOW_HWND (requestor));
+  prop = g_hash_table_lookup (win32_sel->sel_prop_table, GDK_WINDOW_HWND (requestor));
 
   if (prop == NULL)
     {
@@ -845,17 +2452,17 @@ _gdk_win32_display_get_selection_property (GdkDisplay *display,
     memmove (*data, prop->data, prop->length);
 
   GDK_NOTE (DND, {
-      gchar *type_name = gdk_atom_name (prop->type);
+      gchar *type_name = gdk_atom_name (prop->target);
 
-      g_print (" %s format:%d length:%"G_GSIZE_FORMAT"\n", type_name, prop->format, prop->length);
+      g_print (" %s format:%d length:%"G_GSIZE_FORMAT"\n", type_name, prop->bitness, prop->length);
       g_free (type_name);
     });
 
   if (ret_type)
-    *ret_type = prop->type;
+    *ret_type = prop->target;
 
   if (ret_format)
-    *ret_format = prop->format;
+    *ret_format = prop->bitness;
 
   return prop->length;
 }
@@ -927,7 +2534,7 @@ gdk_text_property_to_text_list_for_display (GdkDisplay   *display,
 
   if (encoding == GDK_TARGET_STRING)
     source_charset = g_strdup ("ISO-8859-1");
-  else if (encoding == _utf8_string)
+  else if (encoding == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_UTF8_STRING))
     source_charset = g_strdup ("UTF-8");
   else
     source_charset = gdk_atom_name (encoding);
@@ -1040,7 +2647,7 @@ _gdk_win32_display_text_property_to_utf8_list (GdkDisplay    *display,
     {
       return make_list ((gchar *)text, length, TRUE, list);
     }
-  else if (encoding == _utf8_string)
+  else if (encoding == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_UTF8_STRING))
     {
       return make_list ((gchar *)text, length, FALSE, list);
     }
@@ -1058,80 +2665,118 @@ _gdk_win32_display_text_property_to_utf8_list (GdkDisplay    *display,
     }
 }
 
-gint
-gdk_string_to_compound_text_for_display (GdkDisplay  *display,
-                                        const gchar *str,
-                                        GdkAtom     *encoding,
-                                        gint        *format,
-                                        guchar     **ctext,
-                                        gint        *length)
+gchar *
+_gdk_win32_display_utf8_to_string_target (GdkDisplay *display,
+                                         const gchar *str)
 {
-  g_return_val_if_fail (str != NULL, 0);
-  g_return_val_if_fail (length >= 0, 0);
+  return _gdk_utf8_to_string_target_internal (str, strlen (str));
+}
 
-  GDK_NOTE (DND, g_print ("gdk_string_to_compound_text_for_display: %.20s\n", str));
+void
+gdk_win32_selection_clear_targets (GdkDisplay *display,
+                                   GdkAtom     selection)
+{
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
 
-  /* Always fail on Win32. No COMPOUND_TEXT support. */
+  if (selection == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND) ||
+      selection == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_LOCAL_DND_SELECTION))
+    {
+      g_array_set_size (win32_sel->dnd_selection_targets, 0);
+    }
+  else if (selection == GDK_SELECTION_CLIPBOARD)
+    {
+      g_array_set_size (win32_sel->clipboard_selection_targets, 0);
+    }
+  else if (selection == GDK_SELECTION_PRIMARY)
+    {
+      /* Do nothing */
+    }
+  else
+    {
+      gchar *sel_name = gdk_atom_name (selection);
 
-  if (encoding)
-    *encoding = NULL;
+      g_warning ("Unsupported generic selection %s (0x%p)", sel_name, selection);
+      g_free (sel_name);
+    }
+}
 
-  if (format)
-    *format = 0;
+gint
+_gdk_win32_add_target_to_selformats (GdkAtom  target,
+                                     GArray  *array)
+{
+  gint added_count = 0;
+  gchar *target_name;
+  wchar_t *target_name_w;
+  GdkSelTargetFormat fmt;
+  gint i;
+  GArray *compatibility_formats;
+  gint starting_point;
 
-  if (ctext)
-    *ctext = NULL;
+  for (i = 0; i < array->len; i++)
+    if (g_array_index (array, GdkSelTargetFormat, i).target == target)
+      break;
 
-  if (length)
-    *length = 0;
+  /* Don't put duplicates into the array */
+  if (i < array->len)
+    return added_count;
 
-  return -1;
-}
+  if (target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_TARGETS) ||
+      target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_COMPOUND_TEXT) || target == 
_gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_SAVE_TARGETS))
+    {
+      /* Add the "we don't really support transferring that to
+       * other processes" format, just to keep the taget around.
+       */
+      fmt.target = target;
+      fmt.format = 0;
+      fmt.transmute = FALSE;
+      g_array_append_val (array, fmt);
+      added_count += 1;
+      return added_count;
+    }
 
-gchar *
-_gdk_win32_display_utf8_to_string_target (GdkDisplay *display,
-                                         const gchar *str)
-{
-  return _gdk_utf8_to_string_target_internal (str, strlen (str));
-}
+  /* Only check the newly-added pairs for duplicates,
+   * all the ones that exist right now have different targets.
+   */
+  starting_point = array->len;
 
-gboolean
-gdk_utf8_to_compound_text_for_display (GdkDisplay  *display,
-                                       const gchar *str,
-                                       GdkAtom     *encoding,
-                                       gint        *format,
-                                       guchar     **ctext,
-                                       gint        *length)
-{
-  g_return_val_if_fail (str != NULL, FALSE);
+  target_name = gdk_atom_name (target);
+  target_name_w = g_utf8_to_utf16 (target_name, -1, NULL, NULL, NULL);
+  g_free (target_name);
 
-  GDK_NOTE (DND, g_print ("gdk_utf8_to_compound_text_for_display: %.20s\n", str));
+  if (target_name_w == NULL)
+    return added_count;
 
-  /* Always fail on Win32. No COMPOUND_TEXT support. */
+  fmt.format = RegisterClipboardFormatW (target_name_w);
+  GDK_NOTE (DND, g_print ("Registered clipboard format %S as 0x%x\n", target_name_w, fmt.format));
+  g_free (target_name_w);
+  fmt.target = target;
+  fmt.transmute = FALSE;
 
-  if (encoding)
-    *encoding = NULL;
+  /* Add the "as-is" format */
+  g_array_append_val (array, fmt);
+  added_count += 1;
 
-  if (format)
-    *format = 0;
+  compatibility_formats = get_compatibility_formats_for_target (target);
+  for (i = 0; compatibility_formats != NULL && i < compatibility_formats->len; i++)
+    {
+      gint j;
 
-  if (ctext)
-    *ctext = NULL;
+      fmt = g_array_index (compatibility_formats, GdkSelTargetFormat, i);
 
-  if (length)
-    *length = 0;
+      /* Don't put duplicates into the array */
+      for (j = starting_point; j < array->len; j++)
+        if (g_array_index (array, GdkSelTargetFormat, j).format == fmt.format)
+          break;
 
-  return FALSE;
-}
+      if (j < array->len)
+        continue;
 
-void
-gdk_free_compound_text (guchar *ctext)
-{
-  /* As we never generate anything claimed to be COMPOUND_TEXT, this
-   * should never be called. Or if it is called, ctext should be the
-   * NULL returned for conversions to COMPOUND_TEXT above.
-   */
-  g_return_if_fail (ctext == NULL);
+      /* Add a compatibility format */
+      g_array_append_val (array, fmt);
+      added_count += 1;
+    }
+
+  return added_count;
 }
 
 void
@@ -1143,10 +2788,19 @@ gdk_win32_display_clear_selection_targets (GdkDisplay *display,
 /* This function is called from gtk_selection_add_target() and
  * gtk_selection_add_targets() in gtkselection.c. It is this function
  * that takes care of setting those clipboard formats for which we use
- * delayed rendering. Formats copied directly to the clipboard are
- * handled in gdk_property_change() in gdkproperty-win32.c.
+ * delayed rendering (that is, all formats, as we use delayed rendering
+ * for everything). This function only registers the formats, but does
+ * not announce them as supported. That is handled as a special case
+ * in gdk_window_property_change().
+ *
+ * Implementation detail:
+ * This function will be called repeatedly, every time the PRIMARY selection changes.
+ * It will also be called immediately before the CLIPBOARD selection changes.
+ * We let GTK+ handle the PRIMARY selection internally and do nothing here
+ * (therefore it's not possible to middle-click-paste between processes,
+ * unless one process deliberately puts PRIMARY selection contents into
+ * CLIPBOARD selection, and the other process does paste on middle-click).
  */
-
 void
 gdk_win32_display_add_selection_targets (GdkDisplay *display,
                                          GdkWindow  *owner,
@@ -1154,8 +2808,7 @@ gdk_win32_display_add_selection_targets (GdkDisplay *display,
                                          GdkAtom    *targets,
                                          guint       ntargets)
 {
-  HWND hwnd = NULL;
-  gboolean has_image = FALSE;
+  GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
   gint i;
 
   GDK_NOTE (DND, {
@@ -1178,133 +2831,34 @@ gdk_win32_display_add_selection_targets (GdkDisplay *display,
       g_print ("\n");
     });
 
-  if (selection != GDK_SELECTION_CLIPBOARD)
-    return;
-
-  if (owner != NULL)
+  if (selection == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND) ||
+      selection == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_LOCAL_DND_SELECTION) ||
+      selection == GDK_SELECTION_CLIPBOARD)
     {
-      if (GDK_WINDOW_DESTROYED (owner))
-       return;
-      hwnd = GDK_WINDOW_HWND (owner);
-    }
-
-  if (!API_CALL (OpenClipboard, (hwnd)))
-    return;
-
-  /* We have a very simple strategy: If some kind of pixmap image
-   * format is being added, actually advertise just PNG and DIB. PNG
-   * is our preferred format because it can losslessly represent any
-   * image that gdk-pixbuf formats in general can, even with alpha,
-   * unambiguously. CF_DIB is also advertised because of the general
-   * support for it in Windows software, but note that alpha won't be
-   * handled.
-   */
-  for (i = 0; !has_image && i < ntargets; ++i)
-    {
-      UINT cf;
-      gchar *target_name;
-      int j;
-
-      for (j = 0; j < n_known_pixbuf_formats; j++)
-       if (targets[i] == known_pixbuf_formats[j])
-         {
-           if (!has_image)
-             {
-               GDK_NOTE (DND, g_print ("... SetClipboardData(PNG,NULL)\n"));
-               SetClipboardData (_cf_png, NULL);
-
-               GDK_NOTE (DND, g_print ("... SetClipboardData(CF_DIB,NULL)\n"));
-               SetClipboardData (CF_DIB, NULL);
-
-               has_image = TRUE;
-             }
-           break;
-         }
-
-      /* If it is one of the pixmap formats, already handled or not
-       * needed.
-       */
-      if (j < n_known_pixbuf_formats)
-       continue;
-
-      /* We don't bother registering and advertising clipboard formats
-       * that are X11 specific or no non-GTK+ apps will have ever
-       * heard of, and when there are equivalent clipboard formats
-       * that are commonly used.
-       */
-      if (targets[i] == _save_targets ||
-         targets[i] == _utf8_string ||
-         targets[i] == GDK_TARGET_STRING ||
-         targets[i] == _compound_text ||
-         targets[i] == _text ||
-         targets[i] == text_plain_charset_utf_8 ||
-         targets[i] == text_plain_charset_CP1252 ||
-         targets[i] == text_plain)
-       continue;
-
-      target_name = gdk_atom_name (targets[i]);
-
-      if (g_str_has_prefix (target_name, "text/plain;charset="))
-       {
-         g_free (target_name);
-         continue;
-       }
+      GArray *fmts = NULL;
+      gint added_count = 0;
 
-      cf = RegisterClipboardFormat (target_name);
-
-      g_hash_table_replace (_format_atom_table,
-                           GINT_TO_POINTER (cf),
-                           targets[i]);
+      if (selection == GDK_SELECTION_CLIPBOARD)
+        fmts = win32_sel->clipboard_selection_targets;
+      else
+        fmts = win32_sel->dnd_selection_targets;
 
-      GDK_NOTE (DND, g_print ("... SetClipboardData(%s,NULL)\n",
-                             _gdk_win32_cf_to_string (cf)));
-      SetClipboardData (cf, NULL);
+      for (i = 0; i < ntargets; i++)
+        added_count += _gdk_win32_add_target_to_selformats (targets[i], fmts);
 
-      g_free (target_name);
+      /* Re-announce our list of supported formats */
+      if (added_count > 0)
+        send_targets_request (GDK_CURRENT_TIME);
     }
-  API_CALL (CloseClipboard, ());
-}
-
-/* Convert from types such as "image/jpg" or "image/png" to DIB using
- * gdk-pixbuf so that image copied from GTK+ apps can be pasted in
- * native apps like mspaint.exe
- */
-HGLOBAL
-_gdk_win32_selection_convert_to_dib (HGLOBAL  hdata,
-                                    GdkAtom  target)
-{
-  GDK_NOTE (DND, {
-      gchar *target_name = gdk_atom_name (target);
-
-      g_print ("_gdk_win32_selection_convert_to_dib: %p %s\n",
-              hdata, target_name);
-      g_free (target_name);
-    });
-
-  if (target == _image_bmp)
+  else if (selection == GDK_SELECTION_PRIMARY)
     {
-      HGLOBAL hdatanew;
-      SIZE_T size;
-      guchar *ptr;
-
-      g_return_val_if_fail (GlobalSize (hdata) >= sizeof (BITMAPFILEHEADER), NULL);
-
-      /* No conversion is needed, just strip the BITMAPFILEHEADER */
-      size = GlobalSize (hdata) - sizeof (BITMAPFILEHEADER);
-      ptr = GlobalLock (hdata);
-
-      memmove (ptr, ptr + sizeof (BITMAPFILEHEADER), size);
-      GlobalUnlock (hdata);
-
-      if ((hdatanew = GlobalReAlloc (hdata, size, GMEM_MOVEABLE)) == NULL)
-       {
-         WIN32_API_FAILED ("GlobalReAlloc");
-         GlobalFree (hdata); /* The old hdata is not freed if error */
-       }
-      return hdatanew;
+      /* Do nothing */
     }
+  else
+    {
+      gchar *sel_name = gdk_atom_name (selection);
 
-  g_warning ("Should not happen: We provide some image format but not CF_DIB and CF_DIB is requested.");
-
-  return NULL;
+      g_warning ("Unsupported generic selection %s (0x%p)", sel_name, selection);
+      g_free (sel_name);
+    }
 }
diff --git a/gdk/win32/gdkselection-win32.h b/gdk/win32/gdkselection-win32.h
new file mode 100644
index 0000000..23dac73
--- /dev/null
+++ b/gdk/win32/gdkselection-win32.h
@@ -0,0 +1,228 @@
+/* GDK - The GIMP Drawing Kit
+ *
+ * gdkselection-win32.h: Private Win32 specific selection 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_SELECTION_WIN32_H__
+#define __GDK_SELECTION_WIN32_H__
+
+G_BEGIN_DECLS
+
+#define _gdk_win32_selection_get() (_win32_selection)
+#define _gdk_atom_array_index(a, i) (g_array_index (a, GdkAtom, i))
+#define _gdk_win32_selection_atom(i) (_gdk_atom_array_index (_gdk_win32_selection_get ()->known_atoms, i))
+#define _gdk_cf_array_index(a, i) (g_array_index (a, UINT, i))
+#define _gdk_win32_selection_cf(i) (_gdk_cf_array_index (_gdk_win32_selection_get 
()->known_clipboard_formats, i))
+
+/* Maps targets to formats 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 format;
+  GdkAtom target;
+  gboolean transmute;
+} GdkSelTargetFormat;
+
+/* We emulate the GDK_SELECTION window properties of windows (as used
+ * in the X11 backend) by using a hash table from window handles to
+ * GdkSelProp structs.
+ */
+typedef struct {
+  guchar *data;
+  gsize   length;
+  gint    bitness;
+  GdkAtom target;
+} GdkSelProp;
+
+/* 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_UTF8_STRING,
+  GDK_WIN32_ATOM_INDEX_TEXT,
+  GDK_WIN32_ATOM_INDEX_COMPOUND_TEXT,
+  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_UTF8_STRING,
+  GDK_WIN32_CF_INDEX_LAST
+};
+
+typedef enum _GdkWin32CFIndex GdkWin32CFIndex;
+
+#define GDK_TYPE_WIN32_SELECTION         (gdk_win32_selection_get_type ())
+#define GDK_WIN32_SELECTION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDK_TYPE_WIN32_SELECTION, 
GdkWin32Selection))
+#define GDK_WIN32_SELECTION_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c), GDK_TYPE_WIN32_SELECTION, 
GdkWin32SelectionClass))
+#define GDK_IS_WIN32_SELECTION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDK_TYPE_WIN32_SELECTION))
+#define GDK_IS_WIN32_SELECTION_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c), GDK_TYPE_WIN32_SELECTION))
+#define GDK_WIN32_SELECTION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDK_TYPE_WIN32_SELECTION, 
GdkWin32SelectionClass))
+
+typedef struct _GdkWin32Selection GdkWin32Selection;
+typedef struct _GdkWin32SelectionClass GdkWin32SelectionClass;
+
+/* This object is just a sink to hold all the selection- and dnd-related data
+ * that otherwise would be in global variables.
+ */
+struct _GdkWin32Selection
+{
+  GObject *parent_instance;
+  GHashTable *sel_prop_table;
+  GdkSelProp *dropfiles_prop;
+  /* We store the owner of each selection in this table. Obviously, this only
+   * is valid intra-app, and in fact it is necessary for the intra-app DND to work.
+   */
+  GHashTable *sel_owner_table;
+
+  /* GdkAtoms for well-known image formats */
+  GdkAtom *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;
+  GdkWin32DndState  dnd_source_state;
+
+  /* Holds a reference to the data object for the target drop site.
+   */
+  IDataObject      *dnd_data_object_target;
+
+  /* Carries DnD target context from idroptarget_*() to convert_selection() */
+  GdkDragContext   *target_drag_context;
+
+  /* Carries W32 format ID from idataobject_getdata() to property_change() */
+  UINT              property_change_format;
+  /* Carries the W32-wrapped data between idataobject_getdata() and property_change() */
+  LPSTGMEDIUM       property_change_data;
+  /* Carries the transmute field of the GdkSelTargetFormat from from idataobject_getdata() to 
property_change() */
+  gboolean          property_change_transmute;
+
+  /* TRUE when we are emptying the clipboard ourselves */
+  gboolean          ignore_destroy_clipboard;
+
+  /* Array of GdkSelTargetFormats describing the targets supported by the clipboard selection */
+  GArray           *clipboard_selection_targets;
+
+  /* Same for the DnD selection (applies for both LOCAL and OLE2 DnD) */
+  GArray           *dnd_selection_targets;
+
+  /* If TRUE, then we queued a GDK_SELECTION_REQUEST with TARGETS
+   * target. This field is checked to prevent queueing
+   * multiple selection requests.
+   */
+  gboolean          targets_request_pending;
+
+  /* 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;
+
+  /* A target-keyed hash table of GArrays of GdkSelTargetFormats describing compatibility formats for a 
target */
+  GHashTable       *compatibility_formats;
+  /* A format-keyed hash table of GArrays of GdkAtoms describing compatibility targets for a format */
+  GHashTable       *compatibility_targets;
+
+  /* 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;
+};
+
+struct _GdkWin32SelectionClass
+{
+  GObjectClass parent_class;
+};
+
+GType gdk_win32_selection_get_type (void) G_GNUC_CONST;
+
+void    _gdk_win32_clear_clipboard_queue                          ();
+gchar * _gdk_win32_get_clipboard_format_name                      (UINT               fmt,
+                                                                   gboolean          *is_predefined);
+void    _gdk_win32_add_format_to_targets                          (UINT               format,
+                                                                   GArray            *array,
+                                                                   GList            **list);
+gint    _gdk_win32_add_target_to_selformats                       (GdkAtom            target,
+                                                                   GArray            *array);
+void    _gdk_win32_selection_property_change                      (GdkWin32Selection *win32_sel,
+                                                                   GdkWindow         *window,
+                                                                   GdkAtom            property,
+                                                                   GdkAtom            type,
+                                                                   gint               format,
+                                                                   GdkPropMode        mode,
+                                                                   const guchar      *data,
+                                                                   gint               nelements);
+
+#endif /* __GDK_SELECTION_WIN32_H__ */
diff --git a/gdk/win32/gdkwin32dnd-private.h b/gdk/win32/gdkwin32dnd-private.h
new file mode 100644
index 0000000..769662e
--- /dev/null
+++ b/gdk/win32/gdkwin32dnd-private.h
@@ -0,0 +1,65 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 2010 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_WIN32_DND_PRIVATE_H__
+#define __GDK_WIN32_DND_PRIVATE_H__
+
+#if !defined (__GDKWIN32_H_INSIDE__) && !defined (GDK_COMPILATION)
+#error "Only <gdk/gdkwin32.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+struct _GdkWin32DragContext
+{
+  GdkDragContext context;
+  GdkWindow *ipc_window;
+  GdkWindow *drag_window;
+  GdkCursor *cursor;
+  GdkSeat *grab_seat;
+  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;
+
+  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_y;
+  DWORD last_key_state;     /* Key state from last event */
+
+  /* Just like context->targets, but an array, and with format IDs
+   * stored inside.
+   */
+  GArray *droptarget_format_target_map;
+};
+
+struct _GdkWin32DragContextClass
+{
+  GdkDragContextClass parent_class;
+};
+
+G_END_DECLS
+
+#endif /* __GDK_WIN32_DND_PRIVATE_H__ */
diff --git a/gdk/win32/gdkwindow-win32.c b/gdk/win32/gdkwindow-win32.c
index 4616d73..0b3eb25 100644
--- a/gdk/win32/gdkwindow-win32.c
+++ b/gdk/win32/gdkwindow-win32.c
@@ -789,6 +789,14 @@ _gdk_win32_display_create_window_impl (GdkDisplay    *display,
   if (impl->type_hint == GDK_WINDOW_TYPE_HINT_UTILITY)
     dwExStyle |= WS_EX_TOOLWINDOW;
 
+  /* WS_EX_TRANSPARENT means "try draw this window last, and ignore input".
+   * It's the last part we're after. We don't want DND indicator to accept
+   * input, because that will make it a potential drop target, and if it's
+   * under the mouse cursor, this will kill any DND.
+   */
+  if (impl->type_hint == GDK_WINDOW_TYPE_HINT_DND)
+    dwExStyle |= WS_EX_TRANSPARENT;
+
   klass = RegisterGdkClass (window->window_type, impl->type_hint);
 
   wtitle = g_utf8_to_utf16 (title, -1, NULL, NULL, NULL);
diff --git a/gtk/gtkdnd.c b/gtk/gtkdnd.c
index b48a64e..c3e6531 100644
--- a/gtk/gtkdnd.c
+++ b/gtk/gtkdnd.c
@@ -45,6 +45,10 @@
 #endif
 #endif
 
+#ifdef GDK_WINDOWING_WIN32
+#include <gdk/win32/gdkwin32.h>
+#endif
+
 #ifdef GDK_WINDOWING_WAYLAND
 #include <gdk/wayland/gdkwayland.h>
 #endif
@@ -1158,6 +1162,9 @@ gtk_drag_is_managed (GtkWidget *source_widget)
 #ifdef GDK_WINDOWING_WAYLAND
     GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (source_widget)) ||
 #endif
+#ifdef GDK_WINDOWING_WIN32
+    GDK_IS_WIN32_DISPLAY (gtk_widget_get_display (source_widget)) ||
+#endif
     FALSE;
 }
 



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