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