| /* 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> |
| #include <math.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, IDropSource 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 |
| * * GdkWin32Drop: 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_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 "gdkdrag.h" |
| #include "gdkinternals.h" |
| #include "gdkprivate-win32.h" |
| #include "gdkwin32.h" |
| #include "gdkwin32dnd.h" |
| #include "gdkdisplayprivate.h" |
| #include "gdk/gdkdragprivate.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; |
| GdkDrag *drag; |
| |
| /* These are thread-local |
| * copies of the similar fields from GdkWin32Drag |
| */ |
| GdkWin32DragUtilityData 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. |
| * Will be INVALID_HANDLE_VALUE (not NULL!) when unset. |
| */ |
| HWND dest_window_handle; |
| } source_drag_context; |
| |
| typedef struct { |
| IDataObject ido; |
| int ref_count; |
| GdkDrag *drag; |
| 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; |
| GdkWin32DragUtilityData 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: |
| /* These have no data to clean up */ |
| 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; |
| } |
| |
| void |
| _gdk_win32_local_drag_drop_response (GdkDrag *drag, |
| GdkDragAction action) |
| { |
| GDK_NOTE (DND, g_print ("_gdk_win32_local_drag_drop_response: 0x%p\n", |
| drag)); |
| |
| g_signal_emit_by_name (drag, "dnd-finished"); |
| gdk_drag_drop_done (drag, action != 0); |
| } |
| |
| static gboolean |
| do_drag_drop_response (gpointer user_data) |
| { |
| GdkWin32DnDThreadDoDragDrop *ddd = (GdkWin32DnDThreadDoDragDrop *) user_data; |
| HRESULT hr = ddd->received_result; |
| GdkDrag *drag = GDK_DRAG (ddd->base.opaque_context); |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get (); |
| gpointer table_value = g_hash_table_lookup (clipdrop->active_source_drags, drag); |
| |
| 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)); |
| |
| drag_win32->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_finished: 0x%p\n", |
| drag)); |
| |
| g_signal_emit_by_name (drag, "dnd-finished"); |
| gdk_drag_drop_done (drag, !drag_win32->drop_failed); |
| } |
| else |
| { |
| if (!table_value) |
| g_critical ("Did not find drag 0x%p in the active drags table", drag); |
| else |
| g_critical ("Found drag 0x%p in the active drags table, but the record doesn't match (0x%p != 0x%p)", drag, ddd, table_value); |
| } |
| |
| /* 3rd parties could keep a reference to this object, |
| * but we won't keep the drag alive that long. |
| * Neutralize it (attempts to get its data will fail) |
| * by nullifying the drag pointer (it doesn't hold |
| * a reference, so no unreffing). |
| */ |
| g_clear_object (&ddd->src_object->drag); |
| |
| IDropSource_Release (&ddd->src_context->ids); |
| IDataObject_Release (&ddd->src_object->ido); |
| |
| g_hash_table_remove (clipdrop->active_source_drags, drag); |
| free_queue_item (&ddd->base); |
| |
| return G_SOURCE_REMOVE; |
| } |
| |
| static void |
| received_drag_context_data (GObject *drag, |
| GAsyncResult *result, |
| gpointer user_data) |
| { |
| GError *error = NULL; |
| GdkWin32DnDThreadGetData *getdata = (GdkWin32DnDThreadGetData *) user_data; |
| GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get (); |
| |
| if (!gdk_drag_write_finish (GDK_DRAG (drag), result, &error)) |
| { |
| HANDLE handle; |
| gboolean is_hdata; |
| |
| GDK_NOTE (DND, g_printerr ("%p: failed to write HData-backed stream: %s\n", drag, 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 (); |
| GdkDrag *drag = GDK_DRAG (getdata->base.opaque_context); |
| gpointer ddd = g_hash_table_lookup (clipdrop->active_source_drags, drag); |
| |
| 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 drag, |
| * 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_write_async (drag, |
| 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 gboolean use_ole2_dnd = TRUE; |
| |
| static gboolean drag_context_grab (GdkDrag *drag); |
| |
| G_DEFINE_TYPE (GdkWin32Drag, gdk_win32_drag, GDK_TYPE_DRAG) |
| |
| static void |
| move_drag_surface (GdkDrag *drag, |
| guint x_root, |
| guint y_root) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| g_assert (_win32_main_thread == NULL || |
| _win32_main_thread == g_thread_self ()); |
| |
| gdk_win32_surface_move (drag_win32->drag_surface, |
| x_root - drag_win32->hot_x, |
| y_root - drag_win32->hot_y); |
| gdk_win32_surface_raise (drag_win32->drag_surface); |
| } |
| |
| static void |
| gdk_win32_drag_init (GdkWin32Drag *drag) |
| { |
| g_assert (_win32_main_thread == NULL || |
| _win32_main_thread == g_thread_self ()); |
| |
| drag->handle_events = TRUE; |
| drag->dest_window = INVALID_HANDLE_VALUE; |
| |
| GDK_NOTE (DND, g_print ("gdk_win32_drag_init %p\n", drag)); |
| } |
| |
| static void |
| gdk_win32_drag_finalize (GObject *object) |
| { |
| GdkDrag *drag; |
| GdkWin32Drag *drag_win32; |
| GdkSurface *drag_surface; |
| |
| g_assert (_win32_main_thread == NULL || |
| _win32_main_thread == g_thread_self ()); |
| |
| GDK_NOTE (DND, g_print ("gdk_win32_drag_finalize %p\n", object)); |
| |
| g_return_if_fail (GDK_IS_WIN32_DRAG (object)); |
| |
| drag = GDK_DRAG (object); |
| drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| gdk_drag_set_cursor (drag, NULL); |
| |
| g_set_object (&drag_win32->grab_surface, NULL); |
| drag_surface = drag_win32->drag_surface; |
| |
| G_OBJECT_CLASS (gdk_win32_drag_parent_class)->finalize (object); |
| |
| if (drag_surface) |
| gdk_surface_destroy (drag_surface); |
| } |
| |
| /* Drag Contexts */ |
| |
| static GdkDrag * |
| gdk_drag_new (GdkDisplay *display, |
| GdkSurface *surface, |
| GdkContentProvider *content, |
| GdkDragAction actions, |
| GdkDevice *device, |
| GdkDragProtocol protocol) |
| { |
| GdkWin32Drag *drag_win32; |
| GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display); |
| GdkDrag *drag; |
| |
| drag_win32 = g_object_new (GDK_TYPE_WIN32_DRAG, |
| "device", device, |
| "content", content, |
| "surface", surface, |
| "actions", actions, |
| NULL); |
| |
| drag = GDK_DRAG (drag_win32); |
| |
| if (win32_display->has_fixed_scale) |
| drag_win32->scale = win32_display->surface_scale; |
| else |
| drag_win32->scale = _gdk_win32_display_get_monitor_scale_factor (win32_display, NULL, NULL, NULL); |
| |
| drag_win32->protocol = protocol; |
| |
| return drag; |
| } |
| |
| #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); |
| |
| /* Finds a GdkDrag object that corresponds to a DnD operation |
| * which is currently targetting the dest_window |
| * Does not give a reference. |
| */ |
| GdkDrag * |
| _gdk_win32_find_drag_for_dest_window (HWND dest_window) |
| { |
| GHashTableIter iter; |
| GdkWin32Drag *drag_win32; |
| 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 *) &drag_win32, (gpointer *) &ddd)) |
| if (ddd->src_context->dest_window_handle == dest_window) |
| return GDK_DRAG (drag_win32); |
| |
| 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; |
| |
| 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; |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (notify->opaque_context); |
| |
| drag_win32->dest_window = notify->target_window_handle; |
| |
| g_free (notify); |
| |
| return G_SOURCE_REMOVE; |
| } |
| |
| static gboolean |
| notify_dnd_leave (gpointer user_data) |
| { |
| GdkWin32DnDEnterLeaveNotify *notify = (GdkWin32DnDEnterLeaveNotify *) user_data; |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (notify->opaque_context); |
| |
| if (notify->target_window_handle != drag_win32->dest_window) |
| g_warning ("DnD leave says that the window handle is 0x%p, but drag has 0x%p", notify->target_window_handle, drag_win32->dest_window); |
| |
| drag_win32->dest_window = INVALID_HANDLE_VALUE; |
| |
| 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->drag; |
| 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 = INVALID_HANDLE_VALUE; |
| notify->opaque_context = ctx->drag; |
| 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) |
| { |
| GdkDrag *drag = GDK_DRAG (opaque_context); |
| |
| g_clear_object (&drag); |
| |
| 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->drag); |
| 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\n", This, fEscapePressed, grfKeyState, ctx->util_data.state)); |
| |
| if (!dnd_queue_is_empty ()) |
| process_dnd_queue (FALSE, 0, NULL); |
| |
| GDK_NOTE (DND, g_print ("idropsource_querycontinuedrag state %d\n", ctx->util_data.state)); |
| |
| 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 void |
| maybe_emit_action_changed (GdkWin32Drag *drag_win32, |
| GdkDragAction actions) |
| { |
| if (actions != drag_win32->current_action) |
| { |
| drag_win32->current_action = actions; |
| gdk_drag_set_selected_action (GDK_DRAG (drag_win32), actions); |
| } |
| } |
| |
| void |
| _gdk_win32_local_drag_give_feedback (GdkDrag *drag, |
| GdkDragAction actions) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| if (drag_win32->drag_status == GDK_DRAG_STATUS_MOTION_WAIT) |
| drag_win32->drag_status = GDK_DRAG_STATUS_DRAG; |
| |
| GDK_NOTE (DND, g_print ("_gdk_win32_local_drag_give_feedback: 0x%p\n", |
| drag)); |
| |
| maybe_emit_action_changed (drag_win32, actions); |
| } |
| |
| 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) |
| { |
| GdkDrag *drag = GDK_DRAG (feedback->base.opaque_context); |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| GDK_NOTE (DND, g_print ("gdk_dnd_handle_drag_status: 0x%p\n", |
| drag)); |
| |
| maybe_emit_action_changed (drag_win32, action_for_drop_effect (feedback->received_drop_effect)); |
| } |
| |
| 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; |
| 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.item_type = GDK_WIN32_DND_THREAD_QUEUE_ITEM_GIVE_FEEDBACK; |
| feedback->base.opaque_context = ctx->drag; |
| 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->drag == 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->drag; |
| 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 (GdkDrag *drag, |
| GdkContentFormats *formats) |
| { |
| GdkWin32Drag *drag_win32; |
| source_drag_context *result; |
| GdkSurface *surface; |
| |
| drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| g_object_get (drag, "surface", &surface, NULL); |
| |
| result = g_new0 (source_drag_context, 1); |
| result->drag = g_object_ref (drag); |
| result->ids.lpVtbl = &ids_vtbl; |
| result->idsn.lpVtbl = &idsn_vtbl; |
| result->ref_count = 1; |
| result->source_window_handle = GDK_SURFACE_HWND (surface); |
| result->scale = drag_win32->scale; |
| result->util_data.state = GDK_WIN32_DND_PENDING; /* Implicit */ |
| result->dest_window_handle = INVALID_HANDLE_VALUE; |
| |
| g_object_unref (surface); |
| |
| GDK_NOTE (DND, g_print ("source_context_new: %p (drag %p)\n", result, result->drag)); |
| |
| return result; |
| } |
| |
| static data_object * |
| data_object_new (GdkDrag *drag) |
| { |
| 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->drag = drag; |
| result->formats = g_array_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair)); |
| |
| mime_types = gdk_content_formats_get_mime_types (gdk_drag_get_formats (drag), &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 = FALSE; |
| |
| 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 (); |
| } |
| |
| static GdkSurface * |
| create_drag_surface (GdkDisplay *display) |
| { |
| GdkSurface *surface; |
| |
| surface = gdk_surface_new_temp (display, &(GdkRectangle) { 0, 0, 100, 100 }); |
| |
| return surface; |
| } |
| |
| GdkDrag * |
| _gdk_win32_surface_drag_begin (GdkSurface *surface, |
| GdkDevice *device, |
| GdkContentProvider *content, |
| GdkDragAction actions, |
| double dx, |
| double dy) |
| { |
| GdkDrag *drag; |
| GdkWin32Drag *drag_win32; |
| GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get (); |
| double px, py; |
| int x_root, y_root; |
| |
| g_return_val_if_fail (surface != NULL, NULL); |
| |
| drag = gdk_drag_new (gdk_surface_get_display (surface), |
| surface, |
| content, |
| actions, |
| device, |
| use_ole2_dnd ? GDK_DRAG_PROTO_OLE2 : GDK_DRAG_PROTO_LOCAL); |
| drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| GDK_NOTE (DND, g_print ("_gdk_win32_surface_drag_begin\n")); |
| |
| gdk_device_get_position (device, &px, &py); |
| x_root = round (px + dx); |
| y_root = round (py + dy); |
| |
| drag_win32->start_x = x_root; |
| drag_win32->start_y = y_root; |
| drag_win32->util_data.last_x = drag_win32->start_x; |
| drag_win32->util_data.last_y = drag_win32->start_y; |
| |
| g_set_object (&drag_win32->grab_surface, surface); |
| |
| drag_win32->drag_surface = create_drag_surface (gdk_surface_get_display (surface)); |
| |
| if (!drag_context_grab (drag)) |
| { |
| g_object_unref (drag); |
| return FALSE; |
| } |
| |
| if (drag_win32->protocol == GDK_DRAG_PROTO_OLE2) |
| { |
| GdkWin32DnDThreadDoDragDrop *ddd = g_new0 (GdkWin32DnDThreadDoDragDrop, 1); |
| source_drag_context *source_ctx; |
| data_object *data_obj; |
| |
| source_ctx = source_context_new (drag, gdk_drag_get_formats (drag)); |
| data_obj = data_object_new (drag); |
| |
| ddd->base.item_type = GDK_WIN32_DND_THREAD_QUEUE_ITEM_DO_DRAG_DROP; |
| ddd->base.opaque_context = drag_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 (drag), 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)); |
| |
| drag_win32->util_data.state = GDK_WIN32_DND_PENDING; |
| } |
| |
| move_drag_surface (drag, x_root, y_root); |
| |
| return drag; |
| } |
| |
| /* 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; |
| } |
| |
| /* Finds the HWND under cursor. Local DnD protcol |
| * uses this function, since local protocol is implemented |
| * entirely in GDK and cannot rely on the OS to notify |
| * drop targets about drags that move over them. |
| */ |
| static HWND |
| gdk_win32_drag_find_window (GdkDrag *drag, |
| GdkSurface *drag_surface, |
| gint x_root, |
| gint y_root) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| find_window_enum_arg a; |
| |
| g_assert (_win32_main_thread == NULL || |
| _win32_main_thread == g_thread_self ()); |
| |
| a.x = x_root * drag_win32->scale - _gdk_offset_x; |
| a.y = y_root * drag_win32->scale - _gdk_offset_y; |
| a.ignore = drag_surface ? GDK_SURFACE_HWND (drag_surface) : NULL; |
| a.result = INVALID_HANDLE_VALUE; |
| |
| GDK_NOTE (DND, |
| g_print ("gdk_win32_drag_find_window: %p %+d%+d\n", |
| (drag_surface ? GDK_SURFACE_HWND (drag_surface) : NULL), |
| a.x, a.y)); |
| |
| EnumWindows (find_window_enum_proc, (LPARAM) &a); |
| |
| GDK_NOTE (DND, |
| g_print ("gdk_win32_drag_find_window: %p %+d%+d: %p\n", |
| (drag_surface ? GDK_SURFACE_HWND (drag_surface) : NULL), |
| x_root, y_root, |
| a.result)); |
| |
| return a.result; |
| } |
| |
| static DWORD |
| manufacture_keystate_from_GMT (GdkModifierType state) |
| { |
| DWORD key_state = 0; |
| |
| if (state & GDK_ALT_MASK) |
| key_state |= MK_ALT; |
| if (state & GDK_CONTROL_MASK) |
| key_state |= MK_CONTROL; |
| if (state & GDK_SHIFT_MASK) |
| key_state |= MK_SHIFT; |
| if (state & GDK_BUTTON1_MASK) |
| key_state |= MK_LBUTTON; |
| if (state & GDK_BUTTON2_MASK) |
| key_state |= MK_MBUTTON; |
| if (state & GDK_BUTTON3_MASK) |
| key_state |= MK_RBUTTON; |
| |
| return key_state; |
| } |
| |
| /* This only works if dest_window our window and the DnD operation |
| * is currently local to the application. |
| */ |
| static GdkDrop * |
| _gdk_win32_get_drop_for_dest_window (HWND dest_window) |
| { |
| GdkSurface *drop_surface = gdk_win32_handle_table_lookup (dest_window); |
| GdkDrop *result = NULL; |
| |
| if (drop_surface) |
| result = _gdk_win32_get_drop_for_dest_surface (drop_surface); |
| |
| return result; |
| } |
| |
| static gboolean |
| gdk_win32_local_drag_motion (GdkDrag *drag, |
| HWND dest_window, |
| gint x_root, |
| gint y_root, |
| GdkDragAction possible_actions, |
| DWORD key_state, |
| guint32 time_) |
| { |
| GdkWin32Drag *drag_win32; |
| GdkDrop *drop; |
| GdkDragAction actions; |
| |
| g_assert (_win32_main_thread == NULL || |
| _win32_main_thread == g_thread_self ()); |
| |
| g_return_val_if_fail (drag != NULL, FALSE); |
| |
| drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| drop = _gdk_win32_get_drop_for_dest_window (drag_win32->dest_window); |
| |
| actions = gdk_drag_get_actions (drag); |
| |
| GDK_NOTE (DND, g_print ("gdk_win32_local_drag_motion: @ %+d:%+d possible=%s\n" |
| " dest=%p (current %p) drop=%p drag=%p:{actions=%s,action=%s}\n", |
| x_root, y_root, |
| _gdk_win32_drag_action_to_string (possible_actions), |
| dest_window, drag_win32->dest_window, drop, drag, |
| _gdk_win32_drag_action_to_string (actions), |
| _gdk_win32_drag_action_to_string (gdk_drag_get_selected_action (drag)))); |
| |
| if (drag_win32->dest_window != dest_window) |
| { |
| /* Send a leave to the last destination */ |
| if (drop) |
| _gdk_win32_local_drop_target_dragleave (drop, time_); |
| |
| drag_win32->dest_window = dest_window; |
| drag_win32->drag_status = GDK_DRAG_STATUS_DRAG; |
| |
| _gdk_win32_local_drop_target_dragenter (drag, |
| gdk_win32_handle_table_lookup (dest_window), |
| x_root, |
| y_root, |
| key_state, |
| time_, |
| &actions); |
| |
| drop = _gdk_win32_get_drop_for_dest_window (drag_win32->dest_window); |
| maybe_emit_action_changed (drag_win32, actions); |
| } |
| |
| /* Send a drag-motion event */ |
| |
| drag_win32->util_data.last_x = x_root; |
| drag_win32->util_data.last_y = y_root; |
| |
| if (drop != NULL && |
| drag_win32->drag_status == GDK_DRAG_STATUS_DRAG && |
| _gdk_win32_local_drop_target_will_emit_motion (drop, x_root, y_root, key_state)) |
| { |
| actions = gdk_drag_get_actions (drag); |
| drag_win32->drag_status = GDK_DRAG_STATUS_MOTION_WAIT; |
| |
| _gdk_win32_local_drop_target_dragover (drop, drag, x_root, y_root, key_state, time_, &actions); |
| |
| maybe_emit_action_changed (drag_win32, actions); |
| } |
| |
| GDK_NOTE (DND, g_print (" returning %s\n" |
| " drag=%p:{actions=%s,action=%s}\n", |
| (drop != NULL && drag_win32->drag_status == GDK_DRAG_STATUS_DRAG) ? "TRUE" : "FALSE", |
| drag, |
| _gdk_win32_drag_action_to_string (gdk_drag_get_actions (drag)), |
| _gdk_win32_drag_action_to_string (gdk_drag_get_selected_action (drag)))); |
| return (drop != NULL && drag_win32->drag_status == GDK_DRAG_STATUS_DRAG); |
| } |
| |
| static void |
| send_source_state_update (GdkWin32Clipdrop *clipdrop, |
| GdkWin32Drag *drag_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 = drag_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_drop (GdkDrag *drag, |
| guint32 time_) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get (); |
| |
| g_assert (_win32_main_thread == NULL || |
| _win32_main_thread == g_thread_self ()); |
| |
| g_return_if_fail (drag != NULL); |
| |
| GDK_NOTE (DND, g_print ("gdk_win32_drag_drop\n")); |
| |
| if (drag_win32->protocol == GDK_DRAG_PROTO_LOCAL) |
| { |
| GdkDrop *drop = _gdk_win32_get_drop_for_dest_window (drag_win32->dest_window); |
| |
| if (drop) |
| { |
| GdkDragAction actions; |
| |
| actions = gdk_drag_get_actions (drag); |
| _gdk_win32_local_drop_target_drop (drop, drag, time_, &actions); |
| maybe_emit_action_changed (drag_win32, actions); |
| _gdk_win32_local_drag_drop_response (drag, actions); |
| } |
| } |
| else if (drag_win32->protocol == GDK_DRAG_PROTO_OLE2) |
| { |
| gpointer ddd = g_hash_table_lookup (clipdrop->active_source_drags, drag); |
| |
| drag_win32->util_data.state = GDK_WIN32_DND_DROPPED; |
| |
| if (ddd) |
| send_source_state_update (clipdrop, drag_win32, ddd); |
| } |
| } |
| |
| static void |
| gdk_win32_drag_set_cursor (GdkDrag *drag, |
| GdkCursor *cursor) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| GDK_NOTE (DND, g_print ("gdk_win32_drag_set_cursor: 0x%p 0x%p\n", drag, cursor)); |
| |
| if (!g_set_object (&drag_win32->cursor, cursor)) |
| return; |
| |
| if (drag_win32->grab_seat) |
| { |
| G_GNUC_BEGIN_IGNORE_DEPRECATIONS; |
| gdk_device_grab (gdk_seat_get_pointer (drag_win32->grab_seat), |
| drag_win32->grab_surface, |
| 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 { |
| GdkWin32Drag *drag; |
| GdkFrameClock *frame_clock; |
| gint64 start_time; |
| }; |
| |
| static void |
| gdk_drag_anim_destroy (GdkDragAnim *anim) |
| { |
| g_object_unref (anim->drag); |
| g_slice_free (GdkDragAnim, anim); |
| } |
| |
| static gboolean |
| gdk_drag_anim_timeout (gpointer data) |
| { |
| GdkDragAnim *anim = data; |
| GdkWin32Drag *drag = anim->drag; |
| GdkFrameClock *frame_clock = anim->frame_clock; |
| gint64 current_time; |
| double f; |
| double t; |
| gint x, y; |
| |
| 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_win32_surface_show (drag->drag_surface, FALSE); |
| x = (drag->util_data.last_x + |
| (drag->start_x - drag->util_data.last_x) * t - |
| drag->hot_x); |
| y = (drag->util_data.last_y + |
| (drag->start_y - drag->util_data.last_y) * t - |
| drag->hot_y); |
| gdk_win32_surface_move (drag->drag_surface, x, y); |
| gdk_win32_surface_set_opacity (drag->drag_surface, 1.0 - f); |
| |
| return G_SOURCE_CONTINUE; |
| } |
| |
| static void |
| gdk_win32_drag_drop_done (GdkDrag *drag, |
| gboolean success) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| GdkDragAnim *anim; |
| /* |
| cairo_surface_t *win_surface; |
| cairo_surface_t *surface; |
| cairo_t *cr; |
| */ |
| guint id; |
| |
| GDK_NOTE (DND, g_print ("gdk_win32_drag_drop_done: 0x%p %s\n", |
| drag, |
| success ? "dropped successfully" : "dropped unsuccessfully")); |
| |
| /* FIXME: This is temporary, until the code is fixed to ensure that |
| * gdk_drag_finish () is called by GTK. |
| */ |
| if (drag_win32->protocol == GDK_DRAG_PROTO_OLE2) |
| { |
| GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get (); |
| gpointer ddd = g_hash_table_lookup (clipdrop->active_source_drags, drag); |
| |
| if (success) |
| drag_win32->util_data.state = GDK_WIN32_DND_DROPPED; |
| else |
| drag_win32->util_data.state = GDK_WIN32_DND_NONE; |
| |
| if (ddd) |
| send_source_state_update (clipdrop, drag_win32, ddd); |
| } |
| else if (drag_win32->protocol == GDK_DRAG_PROTO_LOCAL) |
| { |
| |
| } |
| |
| drag_win32->handle_events = FALSE; |
| |
| if (success) |
| { |
| gdk_surface_hide (drag_win32->drag_surface); |
| |
| return; |
| } |
| |
| /* |
| win_surface = _gdk_surface_ref_cairo_surface (drag_win32->drag_surface); |
| surface = gdk_surface_create_similar_surface (drag_win32->drag_surface, |
| cairo_surface_get_content (win_surface), |
| gdk_surface_get_width (drag_win32->drag_surface), |
| gdk_surface_get_height (drag_win32->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 (drag_win32->drag_surface, pattern); |
| |
| cairo_pattern_destroy (pattern); |
| cairo_surface_destroy (surface); |
| */ |
| |
| anim = g_slice_new0 (GdkDragAnim); |
| g_set_object (&anim->drag, drag_win32); |
| anim->frame_clock = gdk_surface_get_frame_clock (drag_win32->drag_surface); |
| anim->start_time = gdk_frame_clock_get_frame_time (anim->frame_clock); |
| |
| GDK_NOTE (DND, g_print ("gdk_win32_drag_drop_done: animate the drag window from %d : %d to %d : %d\n", |
| drag_win32->util_data.last_x, drag_win32->util_data.last_y, |
| drag_win32->start_x, drag_win32->start_y)); |
| |
| id = g_timeout_add_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 (GdkDrag *drag) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| GdkSeatCapabilities capabilities; |
| GdkSeat *seat; |
| GdkCursor *cursor; |
| |
| GDK_NOTE (DND, g_print ("drag_context_grab: 0x%p with grab surface 0x%p\n", |
| drag, |
| drag_win32->grab_surface)); |
| |
| if (!drag_win32->grab_surface) |
| return FALSE; |
| |
| seat = gdk_device_get_seat (gdk_drag_get_device (drag)); |
| |
| capabilities = GDK_SEAT_CAPABILITY_ALL; |
| |
| cursor = gdk_drag_get_cursor (drag, gdk_drag_get_selected_action (drag)); |
| g_set_object (&drag_win32->cursor, cursor); |
| |
| if (gdk_seat_grab (seat, drag_win32->grab_surface, |
| capabilities, FALSE, |
| drag_win32->cursor, NULL, NULL, NULL) != GDK_GRAB_SUCCESS) |
| return FALSE; |
| |
| g_set_object (&drag_win32->grab_seat, seat); |
| |
| /* TODO: Should be grabbing keys here, to support keynav. SetWindowsHookEx()? */ |
| |
| return TRUE; |
| } |
| |
| static void |
| drag_context_ungrab (GdkDrag *drag) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| GDK_NOTE (DND, g_print ("drag_context_ungrab: 0x%p 0x%p\n", |
| drag, |
| drag_win32->grab_seat)); |
| |
| if (!drag_win32->grab_seat) |
| return; |
| |
| gdk_seat_ungrab (drag_win32->grab_seat); |
| |
| g_clear_object (&drag_win32->grab_seat); |
| |
| /* TODO: Should be ungrabbing keys here */ |
| } |
| |
| static void |
| gdk_win32_drag_cancel (GdkDrag *drag, |
| GdkDragCancelReason reason) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| 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_win32_drag_cancel: 0x%p %s\n", |
| drag, |
| reason_str)); |
| |
| if (drag_win32->protocol == GDK_DRAG_PROTO_LOCAL) |
| { |
| GdkDrop *drop = _gdk_win32_get_drop_for_dest_window (drag_win32->dest_window); |
| if (drop) |
| _gdk_win32_local_drop_target_dragleave (drop, GDK_CURRENT_TIME); |
| drop = NULL; |
| } |
| |
| gdk_drag_set_cursor (drag, NULL); |
| drag_context_ungrab (drag); |
| gdk_drag_drop_done (drag, FALSE); |
| } |
| |
| static void |
| gdk_win32_drag_drop_performed (GdkDrag *drag, |
| guint32 time_) |
| { |
| GDK_NOTE (DND, g_print ("gdk_win32_drag_drop_performed: 0x%p %u\n", |
| drag, |
| time_)); |
| |
| gdk_win32_drag_drop (drag, time_); |
| gdk_drag_set_cursor (drag, NULL); |
| drag_context_ungrab (drag); |
| } |
| |
| #define BIG_STEP 20 |
| #define SMALL_STEP 1 |
| |
| static void |
| gdk_local_drag_update (GdkDrag *drag, |
| gdouble x_root, |
| gdouble y_root, |
| DWORD grfKeyState, |
| guint32 evtime) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| HWND dest_window; |
| |
| g_assert (_win32_main_thread == NULL || |
| _win32_main_thread == g_thread_self ()); |
| |
| dest_window = gdk_win32_drag_find_window (drag, |
| drag_win32->drag_surface, |
| x_root, y_root); |
| |
| gdk_win32_local_drag_motion (drag, dest_window, x_root, y_root, |
| gdk_drag_get_actions (drag), |
| grfKeyState, evtime); |
| } |
| |
| static gboolean |
| gdk_dnd_handle_motion_event (GdkDrag *drag, |
| GdkEvent *event) |
| { |
| GdkModifierType state; |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| DWORD key_state; |
| double x, y; |
| double x_root, y_root; |
| |
| GDK_NOTE (DND, g_print ("gdk_dnd_handle_motion_event: 0x%p\n", drag)); |
| |
| state = gdk_event_get_modifier_state (event); |
| gdk_event_get_position (event, &x, &y); |
| |
| x_root = x + _gdk_offset_x; |
| y_root = y + _gdk_offset_y; |
| |
| if (drag_win32->drag_surface) |
| move_drag_surface (drag, x_root, y_root); |
| |
| key_state = manufacture_keystate_from_GMT (state); |
| |
| if (drag_win32->protocol == GDK_DRAG_PROTO_LOCAL) |
| { |
| gdk_local_drag_update (drag, x_root, y_root, key_state, |
| gdk_event_get_time (event)); |
| } |
| else if (drag_win32->protocol == GDK_DRAG_PROTO_OLE2) |
| { |
| GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get (); |
| |
| GDK_NOTE (DND, g_print ("Post WM_MOUSEMOVE keystate=%lu\n", key_state)); |
| |
| drag_win32->util_data.last_x = x_root; |
| drag_win32->util_data.last_y = y_root; |
| |
| API_CALL (PostThreadMessage, (clipdrop->dnd_thread_id, |
| WM_MOUSEMOVE, |
| key_state, |
| MAKELPARAM (x * drag_win32->scale, |
| y * drag_win32->scale))); |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gdk_dnd_handle_key_event (GdkDrag *drag, |
| GdkEvent *event) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| GdkModifierType state; |
| GdkDevice *pointer; |
| gint dx, dy; |
| |
| GDK_NOTE (DND, g_print ("gdk_dnd_handle_key_event: 0x%p\n", drag)); |
| |
| state = gdk_event_get_modifier_state (event); |
| |
| dx = dy = 0; |
| pointer = gdk_device_get_associated_device (gdk_event_get_device (event)); |
| |
| if (gdk_event_get_event_type (event) == GDK_KEY_PRESS) |
| { |
| switch (gdk_key_event_get_keyval (event)) |
| { |
| case GDK_KEY_Escape: |
| gdk_drag_cancel (drag, 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_get_selected_action (drag) != 0) && |
| (drag_win32->dest_window != INVALID_HANDLE_VALUE)) |
| { |
| g_signal_emit_by_name (drag, "drop-performed"); |
| } |
| else |
| gdk_drag_cancel (drag, GDK_DRAG_CANCEL_NO_TARGET); |
| |
| return TRUE; |
| |
| case GDK_KEY_Up: |
| case GDK_KEY_KP_Up: |
| dy = (state & GDK_ALT_MASK) ? -BIG_STEP : -SMALL_STEP; |
| break; |
| |
| case GDK_KEY_Down: |
| case GDK_KEY_KP_Down: |
| dy = (state & GDK_ALT_MASK) ? BIG_STEP : SMALL_STEP; |
| break; |
| |
| case GDK_KEY_Left: |
| case GDK_KEY_KP_Left: |
| dx = (state & GDK_ALT_MASK) ? -BIG_STEP : -SMALL_STEP; |
| break; |
| |
| case GDK_KEY_Right: |
| case GDK_KEY_KP_Right: |
| dx = (state & GDK_ALT_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, &state); |
| |
| if (dx != 0 || dy != 0) |
| { |
| drag_win32->util_data.last_x += dx; |
| drag_win32->util_data.last_y += dy; |
| } |
| |
| if (drag_win32->drag_surface) |
| move_drag_surface (drag, drag_win32->util_data.last_x, drag_win32->util_data.last_y); |
| |
| if (drag_win32->protocol == GDK_DRAG_PROTO_LOCAL) |
| gdk_local_drag_update (drag, drag_win32->util_data.last_x, drag_win32->util_data.last_y, |
| manufacture_keystate_from_GMT (state), |
| gdk_event_get_time (event)); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gdk_dnd_handle_grab_broken_event (GdkDrag *drag, |
| GdkEvent *event) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| GDK_NOTE (DND, g_print ("gdk_dnd_handle_grab_broken_event: 0x%p\n", drag)); |
| |
| /* 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 grab window, for |
| * example, when changing the drag cursor. |
| */ |
| if (/* FIXME: event->implicit || */ |
| gdk_grab_broken_event_get_grab_surface (event) == drag_win32->drag_surface || |
| gdk_grab_broken_event_get_grab_surface (event) == drag_win32->grab_surface) |
| return FALSE; |
| |
| if (gdk_event_get_device (event) != gdk_drag_get_device (drag)) |
| return FALSE; |
| |
| gdk_drag_cancel (drag, GDK_DRAG_CANCEL_ERROR); |
| return TRUE; |
| } |
| |
| static gboolean |
| gdk_dnd_handle_button_event (GdkDrag *drag, |
| GdkEvent *event) |
| { |
| GDK_NOTE (DND, g_print ("gdk_dnd_handle_button_event: 0x%p\n", drag)); |
| |
| #if 0 |
| /* FIXME: Check the button matches */ |
| if (event->button != drag_win32->button) |
| return FALSE; |
| #endif |
| |
| if ((gdk_drag_get_selected_action (drag) != 0)) |
| { |
| g_signal_emit_by_name (drag, "drop-performed"); |
| } |
| else |
| gdk_drag_cancel (drag, GDK_DRAG_CANCEL_NO_TARGET); |
| |
| /* Make sure GTK gets mouse release button event */ |
| return FALSE; |
| } |
| |
| gboolean |
| gdk_win32_drag_handle_event (GdkDrag *drag, |
| GdkEvent *event) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| if (!drag_win32->grab_seat) |
| return FALSE; |
| if (!drag_win32->handle_events) |
| { |
| /* FIXME: remove this functionality once gtk no longer calls DnD after drag_done() */ |
| g_warning ("Got an event %d for drag context %p, even though it's done!", |
| event->event_type, drag); |
| return FALSE; |
| } |
| |
| switch (gdk_event_get_event_type (event)) |
| { |
| case GDK_MOTION_NOTIFY: |
| return gdk_dnd_handle_motion_event (drag, event); |
| case GDK_BUTTON_RELEASE: |
| return gdk_dnd_handle_button_event (drag, event); |
| case GDK_KEY_PRESS: |
| case GDK_KEY_RELEASE: |
| return gdk_dnd_handle_key_event (drag, event); |
| case GDK_GRAB_BROKEN: |
| return gdk_dnd_handle_grab_broken_event (drag, event); |
| default: |
| break; |
| } |
| |
| return FALSE; |
| } |
| |
| static GdkSurface * |
| gdk_win32_drag_get_drag_surface (GdkDrag *drag) |
| { |
| return GDK_WIN32_DRAG (drag)->drag_surface; |
| } |
| |
| static void |
| gdk_win32_drag_set_hotspot (GdkDrag *drag, |
| gint hot_x, |
| gint hot_y) |
| { |
| GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag); |
| |
| GDK_NOTE (DND, g_print ("gdk_drag_set_hotspot: 0x%p %d:%d\n", |
| drag, |
| hot_x, hot_y)); |
| |
| drag_win32->hot_x = hot_x; |
| drag_win32->hot_y = hot_y; |
| |
| if (drag_win32->grab_seat) |
| { |
| /* DnD is managed, update current position */ |
| move_drag_surface (drag, drag_win32->util_data.last_x, drag_win32->util_data.last_y); |
| } |
| } |
| |
| static void |
| gdk_win32_drag_class_init (GdkWin32DragClass *klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); |
| GdkDragClass *drag_class = GDK_DRAG_CLASS (klass); |
| |
| object_class->finalize = gdk_win32_drag_finalize; |
| |
| drag_class->get_drag_surface = gdk_win32_drag_get_drag_surface; |
| drag_class->set_hotspot = gdk_win32_drag_set_hotspot; |
| drag_class->drop_done = gdk_win32_drag_drop_done; |
| drag_class->set_cursor = gdk_win32_drag_set_cursor; |
| drag_class->cancel = gdk_win32_drag_cancel; |
| drag_class->drop_performed = gdk_win32_drag_drop_performed; |
| drag_class->handle_event = gdk_win32_drag_handle_event; |
| } |