/* 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 . */ /* * 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 #include #include /* 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 "gdkdropprivate.h" #include "gdkdrag.h" #include "gdkinternals.h" #include "gdkprivate-win32.h" #include "gdkwin32.h" #include "gdkwin32dnd.h" #include "gdkdisplayprivate.h" #include "gdkwin32dnd-private.h" #include "gdkdisplay-win32.h" #include "gdkdeviceprivate.h" #include "gdkhdataoutputstream-win32.h" #include #include #include #include #include "gdkintl.h" #include #include #define GDK_TYPE_WIN32_DROP (gdk_win32_drop_get_type ()) #define GDK_WIN32_DROP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_WIN32_DROP, GdkWin32Drop)) #define GDK_WIN32_DROP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_WIN32_DROP, GdkWin32DropClass)) #define GDK_IS_WIN32_DROP(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WIN32_DROP)) #define GDK_IS_WIN32_DROP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_WIN32_DROP)) #define GDK_WIN32_DROP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_WIN32_DROP, GdkWin32DropClass)) typedef struct _GdkWin32Drop GdkWin32Drop; typedef struct _GdkWin32DropClass GdkWin32DropClass; struct _GdkWin32Drop { GdkDrop drop; /* The drag protocol being in use */ GdkDragProtocol protocol; /* The actions supported at GTK level. Set in gdk_win32_drop_status(). */ GdkDragAction actions; guint scale; /* Temporarily caches the HiDPI scale */ int last_x; /* Coordinates from last event, in GDK space */ int last_y; DWORD last_key_state; /* Key state from last event */ /* Just like GdkDrop->formats, but an array, and with format IDs * stored inside. */ GArray *droptarget_w32format_contentformat_map; /* The list from WM_DROPFILES is store here temporarily, * until the next gdk_win32_drop_read_async () */ char *dropfiles_list; guint drop_finished : 1; /* FALSE until gdk_drop_finish() is called */ guint drop_failed : 1; /* Whether the drop was unsuccessful */ }; struct _GdkWin32DropClass { GdkDropClass parent_class; }; GType gdk_win32_drop_get_type (void); G_DEFINE_TYPE (GdkWin32Drop, gdk_win32_drop, GDK_TYPE_DROP) /* This structure is presented to COM as an object that * implements IDropTarget interface. Every surface that * can be a droptarget has one of these. */ struct _drop_target_context { IDropTarget idt; int ref_count; /* The drop object we create when a drag enters our surface. * The drop object is destroyed when the drag leaves. */ GdkDrop *drop; /* We get this at the object at creation time and keep * it indefinitely. Drops (see above) come and go, but * this surface remains the same. * This is not a reference, as drop_target_context must not * outlive the surface it's attached to. * drop_target_context is not folded into GdkWin32Surface * only because it's easier to present it to COM as a separate * object when it's allocated separately. */ GdkSurface *surface; /* This is given to us by the OS, we store it here * until the drag leaves our window. It is referenced * (using COM reference counting). */ IDataObject *data_object; }; /* TRUE to use OLE2 protocol, FALSE to use local protocol */ static gboolean use_ole2_dnd = TRUE; static void gdk_win32_drop_init (GdkWin32Drop *drop) { drop->droptarget_w32format_contentformat_map = g_array_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair)); GDK_NOTE (DND, g_print ("gdk_win32_drop_init %p\n", drop)); } static void gdk_win32_drop_finalize (GObject *object) { GdkDrop *drop; GdkWin32Drop *drop_win32; GDK_NOTE (DND, g_print ("gdk_win32_drop_finalize %p\n", object)); drop = GDK_DROP (object); drop_win32 = GDK_WIN32_DROP (drop); g_array_unref (drop_win32->droptarget_w32format_contentformat_map); G_OBJECT_CLASS (gdk_win32_drop_parent_class)->finalize (object); } static GdkDrop * gdk_drop_new (GdkDisplay *display, GdkDevice *device, GdkDrag *drag, GdkContentFormats *formats, GdkSurface *surface, GdkDragProtocol protocol) { GdkWin32Drop *drop_win32; GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display); drop_win32 = g_object_new (GDK_TYPE_WIN32_DROP, "device", device, "drag", drag, "formats", formats, "surface", surface, NULL); if (win32_display->has_fixed_scale) drop_win32->scale = win32_display->surface_scale; else drop_win32->scale = _gdk_win32_display_get_monitor_scale_factor (win32_display, NULL, NULL, NULL); drop_win32->protocol = protocol; return GDK_DROP (drop_win32); } /* Gets the GdkDrop that corresponds to a particular GdkSurface. * Will be NULL for surfaces that are not registered as drop targets, * or for surfaces that are currently not under the drag cursor. * This function is only used for local DnD, where we do have * a real GdkSurface that corresponds to the HWND under cursor. */ GdkDrop * _gdk_win32_get_drop_for_dest_surface (GdkSurface *dest) { GdkWin32Surface *impl; if (dest == NULL) return NULL; impl = GDK_WIN32_SURFACE (dest); if (impl->drop_target != NULL) return impl->drop_target->drop; return impl->drop; } #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 ULONG STDMETHODCALLTYPE idroptarget_addref (LPDROPTARGET This) { drop_target_context *ctx = (drop_target_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) { drop_target_context *ctx = (drop_target_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_clear_object (&ctx->drop); g_free (This); } return ref_count; } static GdkContentFormats * query_object_formats (LPDATAOBJECT pDataObj, GArray *w32format_contentformat_map) { IEnumFORMATETC *pfmt = NULL; FORMATETC fmt; HRESULT hr; GdkContentFormatsBuilder *builder; GdkContentFormats *result_formats; builder = gdk_content_formats_builder_new (); 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; char *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, w32format_contentformat_map, builder); hr = IEnumFORMATETC_Next (pfmt, 1, &fmt, NULL); } if (pfmt) IEnumFORMATETC_Release (pfmt); result_formats = gdk_content_formats_builder_free_to_formats (builder); 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); } /* Figures out an action that the user forces onto us by * pressing some modifier keys. */ static GdkDragAction get_user_action (DWORD grfKeyState) { /* Windows explorer does this: * 'C'ontrol for 'C'opy * a'L't (or Contro'L' + Shift) for 'L'ink * Shift for Move * Control + Alt or Shift + Alt or Control + Alt + Shift for Default action (see below). * * Default action is 'Copy' when dragging between drives, 'Move' otherwise. * For GTK 'between drives' turns into 'between applications'. */ if (((grfKeyState & (MK_CONTROL | MK_ALT)) == (MK_CONTROL | MK_ALT)) || ((grfKeyState & (MK_ALT | MK_SHIFT)) == (MK_ALT | MK_SHIFT)) || ((grfKeyState & (MK_CONTROL | MK_ALT | MK_SHIFT)) == (MK_CONTROL | MK_ALT | MK_SHIFT))) { return 0; } else if ((grfKeyState & (MK_CONTROL | MK_SHIFT)) == (MK_CONTROL | MK_SHIFT)) return GDK_ACTION_LINK; else if (grfKeyState & MK_CONTROL) return GDK_ACTION_COPY; else if (grfKeyState & MK_ALT) return GDK_ACTION_MOVE; return 0; } static DWORD drop_effect_for_actions (GdkDragAction actions) { DWORD effects = 0; int effect_count = 0; if (actions & GDK_ACTION_MOVE) { effects |= DROPEFFECT_MOVE; effect_count++; } if (actions & GDK_ACTION_LINK) { effects |= DROPEFFECT_LINK; effect_count++; } if (actions & GDK_ACTION_COPY) { effects |= DROPEFFECT_COPY; effect_count++; } if (effect_count == 0) effects = DROPEFFECT_NONE; else if (effect_count > 1) /* Actually it should be DROPEFECT_ASK, but Windows doesn't support that */ effects = DROPEFFECT_COPY; return effects; } static GdkDragAction actions_for_drop_effects (DWORD effects) { GdkDragAction actions = 0; if (effects & DROPEFFECT_MOVE) actions |= GDK_ACTION_MOVE; if (effects & DROPEFFECT_LINK) actions |= GDK_ACTION_LINK; if (effects & DROPEFFECT_COPY) actions |= GDK_ACTION_COPY; return actions; } static GdkDragAction filter_actions (GdkDragAction actions, GdkDragAction filter) { return actions & filter; } static GdkDragAction set_source_actions_helper (GdkDrop *drop, GdkDragAction actions, DWORD grfKeyState) { GdkDragAction user_action; user_action = get_user_action (grfKeyState); if (user_action != 0) gdk_drop_set_actions (drop, user_action); else gdk_drop_set_actions (drop, actions); return actions; } void _gdk_win32_local_drop_target_dragenter (GdkDrag *drag, GdkSurface *dest_surface, int x_root, int y_root, DWORD grfKeyState, guint32 time_, GdkDragAction *actions) { GdkDrop *drop; GdkWin32Drop *drop_win32; GdkDisplay *display; GdkDragAction source_actions; GdkWin32Surface *impl = GDK_WIN32_SURFACE (dest_surface); GDK_NOTE (DND, g_print ("_gdk_win32_local_drop_target_dragenter %p @ %d : %d" " for dest window 0x%p" ". actions = %s\n", drag, x_root, y_root, dest_surface, _gdk_win32_drag_action_to_string (*actions))); display = gdk_surface_get_display (dest_surface); drop = gdk_drop_new (display, gdk_seat_get_pointer (gdk_display_get_default_seat (display)), drag, gdk_content_formats_ref (gdk_drag_get_formats (drag)), dest_surface, GDK_DRAG_PROTO_LOCAL); drop_win32 = GDK_WIN32_DROP (drop); impl->drop = drop; source_actions = set_source_actions_helper (drop, *actions, grfKeyState); gdk_drop_emit_enter_event (drop, TRUE, x_root, y_root, time_); drop_win32->last_key_state = grfKeyState; drop_win32->last_x = x_root; drop_win32->last_y = y_root; *actions = filter_actions (drop_win32->actions, source_actions); GDK_NOTE (DND, g_print ("_gdk_win32_local_drop_target_dragenter returns with actions %s\n", _gdk_win32_drag_action_to_string (*actions))); } /* The pdwEffect here initially points * to a DWORD that contains the value of dwOKEffects argument in DoDragDrop, * i.e. the drag action that the drag source deems acceptable. * On return it should point to the effect value that denotes the * action that is going to happen on drop, and that is what DoDragDrop will * put into the DWORD that pdwEffect was pointing to. */ static HRESULT STDMETHODCALLTYPE idroptarget_dragenter (LPDROPTARGET This, LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect_and_dwOKEffects) { drop_target_context *ctx = (drop_target_context *) This; GdkDrop *drop; GdkWin32Drop *drop_win32; GdkDisplay *display; int pt_x; int pt_y; GdkDrag *drag; GdkDragAction source_actions; GdkDragAction dest_actions; GDK_NOTE (DND, g_print ("idroptarget_dragenter %p @ %ld : %ld" " for dest window 0x%p" ". dwOKEffects = %lu\n", This, pt.x, pt.y, ctx->surface, *pdwEffect_and_dwOKEffects)); g_clear_object (&ctx->drop); /* Try to find the GdkDrag object for this DnD operation, * if it originated in our own application. */ drag = NULL; if (ctx->surface) drag = _gdk_win32_find_drag_for_dest_window (GDK_SURFACE_HWND (ctx->surface)); display = gdk_surface_get_display (ctx->surface); drop = gdk_drop_new (display, gdk_seat_get_pointer (gdk_display_get_default_seat (display)), drag, query_object_formats (pDataObj, NULL), ctx->surface, GDK_DRAG_PROTO_OLE2); drop_win32 = GDK_WIN32_DROP (drop); g_array_set_size (drop_win32->droptarget_w32format_contentformat_map, 0); gdk_content_formats_unref (query_object_formats (pDataObj, drop_win32->droptarget_w32format_contentformat_map)); ctx->drop = drop; source_actions = set_source_actions_helper (drop, actions_for_drop_effects (*pdwEffect_and_dwOKEffects), grfKeyState); set_data_object (&ctx->data_object, pDataObj); pt_x = pt.x / drop_win32->scale + _gdk_offset_x; pt_y = pt.y / drop_win32->scale + _gdk_offset_y; gdk_drop_emit_enter_event (drop, TRUE, pt_x, pt_y, GDK_CURRENT_TIME); drop_win32->last_key_state = grfKeyState; drop_win32->last_x = pt_x; drop_win32->last_y = pt_y; dest_actions = filter_actions (drop_win32->actions, source_actions); *pdwEffect_and_dwOKEffects = drop_effect_for_actions (dest_actions); GDK_NOTE (DND, g_print ("idroptarget_dragenter returns S_OK with actions %s" " and drop effect %lu\n", _gdk_win32_drag_action_to_string (dest_actions), *pdwEffect_and_dwOKEffects)); return S_OK; } gboolean _gdk_win32_local_drop_target_will_emit_motion (GdkDrop *drop, int x_root, int y_root, DWORD grfKeyState) { GdkWin32Drop *drop_win32 = GDK_WIN32_DROP (drop); if (x_root != drop_win32->last_x || y_root != drop_win32->last_y || grfKeyState != drop_win32->last_key_state) return TRUE; return FALSE; } void _gdk_win32_local_drop_target_dragover (GdkDrop *drop, GdkDrag *drag, int x_root, int y_root, DWORD grfKeyState, guint32 time_, GdkDragAction *actions) { GdkWin32Drop *drop_win32 = GDK_WIN32_DROP (drop); GdkDragAction source_actions; source_actions = set_source_actions_helper (drop, *actions, grfKeyState); GDK_NOTE (DND, g_print ("_gdk_win32_local_drop_target_dragover %p @ %d : %d" ", actions = %s\n", drop, x_root, y_root, _gdk_win32_drag_action_to_string (*actions))); if (_gdk_win32_local_drop_target_will_emit_motion (drop, x_root, y_root, grfKeyState)) { gdk_drop_emit_motion_event (drop, TRUE, x_root, y_root, time_); drop_win32->last_key_state = grfKeyState; drop_win32->last_x = x_root; drop_win32->last_y = y_root; } *actions = filter_actions (drop_win32->actions, source_actions); GDK_NOTE (DND, g_print ("_gdk_win32_local_drop_target_dragover returns with actions %s\n", _gdk_win32_drag_action_to_string (*actions))); } /* 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_and_dwOKEffects) { drop_target_context *ctx = (drop_target_context *) This; GdkWin32Drop *drop_win32 = GDK_WIN32_DROP (ctx->drop); int pt_x = pt.x / drop_win32->scale + _gdk_offset_x; int pt_y = pt.y / drop_win32->scale + _gdk_offset_y; GdkDragAction source_actions; GdkDragAction dest_actions; source_actions = set_source_actions_helper (ctx->drop, actions_for_drop_effects (*pdwEffect_and_dwOKEffects), grfKeyState); GDK_NOTE (DND, g_print ("idroptarget_dragover %p @ %d : %d" " (raw %ld : %ld)" ", dwOKEffects = %lu" ", suggests %d action\n", This, pt_x, pt_y, pt.x, pt.y, *pdwEffect_and_dwOKEffects, source_actions)); if (pt_x != drop_win32->last_x || pt_y != drop_win32->last_y || grfKeyState != drop_win32->last_key_state) { gdk_drop_emit_motion_event (ctx->drop, TRUE, pt_x, pt_y, GDK_CURRENT_TIME); drop_win32->last_key_state = grfKeyState; drop_win32->last_x = pt_x; drop_win32->last_y = pt_y; } dest_actions = filter_actions (drop_win32->actions, source_actions); *pdwEffect_and_dwOKEffects = drop_effect_for_actions (dest_actions); GDK_NOTE (DND, g_print ("idroptarget_dragover returns S_OK with actions %s" " and effect %lu\n", _gdk_win32_drag_action_to_string (dest_actions), *pdwEffect_and_dwOKEffects)); return S_OK; } void _gdk_win32_local_drop_target_dragleave (GdkDrop *drop, guint32 time_) { GdkWin32Surface *impl = GDK_WIN32_SURFACE (gdk_drop_get_surface (drop)); GDK_NOTE (DND, g_print ("_gdk_win32_local_drop_target_dragleave %p\n", drop)); gdk_drop_emit_leave_event (drop, TRUE, time_); g_clear_object (&impl->drop); } static HRESULT STDMETHODCALLTYPE idroptarget_dragleave (LPDROPTARGET This) { drop_target_context *ctx = (drop_target_context *) This; GDK_NOTE (DND, g_print ("idroptarget_dragleave %p S_OK\n", This)); gdk_drop_emit_leave_event (GDK_DROP (ctx->drop), TRUE, GDK_CURRENT_TIME); g_clear_object (&ctx->drop); set_data_object (&ctx->data_object, NULL); return S_OK; } void _gdk_win32_local_drop_target_drop (GdkDrop *drop, GdkDrag *drag, guint32 time_, GdkDragAction *actions) { GdkWin32Drop *drop_win32 = GDK_WIN32_DROP (drop); GDK_NOTE (DND, g_print ("_gdk_win32_local_drop_target_drop %p ", drop)); set_source_actions_helper (drop, *actions, drop_win32->last_key_state); drop_win32->drop_finished = FALSE; gdk_drop_emit_drop_event (drop, TRUE, drop_win32->last_x, drop_win32->last_y, time_); while (!drop_win32->drop_finished) g_main_context_iteration (NULL, FALSE); /* Notify local source of the DnD result * Special case: * drop_win32->actions is guaranteed to contain 1 action after gdk_drop_finish () */ *actions = drop_win32->actions; GDK_NOTE (DND, g_print ("drop with action %s\n", _gdk_win32_drag_action_to_string (*actions))); } static HRESULT STDMETHODCALLTYPE idroptarget_drop (LPDROPTARGET This, LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect_and_dwOKEffects) { drop_target_context *ctx = (drop_target_context *) This; GdkWin32Drop *drop_win32 = GDK_WIN32_DROP (ctx->drop); int pt_x = pt.x / drop_win32->scale + _gdk_offset_x; int pt_y = pt.y / drop_win32->scale + _gdk_offset_y; GdkDragAction dest_action; GDK_NOTE (DND, g_print ("idroptarget_drop %p ", This)); if (pDataObj == NULL) { GDK_NOTE (DND, g_print ("E_POINTER\n")); gdk_drop_emit_leave_event (ctx->drop, TRUE, GDK_CURRENT_TIME); g_clear_object (&ctx->drop); set_data_object (&ctx->data_object, NULL); return E_POINTER; } set_source_actions_helper (ctx->drop, actions_for_drop_effects (*pdwEffect_and_dwOKEffects), grfKeyState); drop_win32->drop_finished = FALSE; gdk_drop_emit_drop_event (ctx->drop, TRUE, pt_x, pt_y, GDK_CURRENT_TIME); while (!drop_win32->drop_finished) g_main_context_iteration (NULL, FALSE); /* Notify OLE of the DnD result * Special case: * drop_win32->actions is guaranteed to contain 1 action after gdk_drop_finish () */ dest_action = drop_win32->actions; *pdwEffect_and_dwOKEffects = drop_effect_for_actions (dest_action); g_clear_object (&ctx->drop); set_data_object (&ctx->data_object, NULL); GDK_NOTE (DND, g_print ("drop S_OK with effect %lx\n", *pdwEffect_and_dwOKEffects)); return S_OK; } static IDropTargetVtbl idt_vtbl = { idroptarget_queryinterface, idroptarget_addref, idroptarget_release, idroptarget_dragenter, idroptarget_dragover, idroptarget_dragleave, idroptarget_drop }; static drop_target_context * target_context_new (GdkSurface *window) { drop_target_context *result; result = g_new0 (drop_target_context, 1); result->idt.lpVtbl = &idt_vtbl; result->ref_count = 0; result->surface = window; idroptarget_addref (&result->idt); GDK_NOTE (DND, g_print ("target_context_new: %p (window %p)\n", result, result->surface)); return result; } /* From MS Knowledge Base article Q130698 */ static gboolean resolve_link (HWND hWnd, wchar_t *link, char **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 GdkWin32MessageFilterReturn gdk_dropfiles_filter (GdkWin32Display *display, MSG *msg, int *ret_valp, gpointer data) { GdkSurface *window; GdkDrop *drop; GdkWin32Drop *drop_win32; GString *result; HANDLE hdrop; POINT pt; int nfiles, i; char *fileName, *linkedFile; if (msg->message != WM_DROPFILES) return GDK_WIN32_MESSAGE_FILTER_CONTINUE; GDK_NOTE (DND, g_print ("WM_DROPFILES: %p\n", msg->hwnd)); window = gdk_win32_handle_table_lookup (msg->hwnd); drop = gdk_drop_new (GDK_DISPLAY (display), gdk_seat_get_pointer (gdk_display_get_default_seat (GDK_DISPLAY (display))), NULL, /* WM_DROPFILES drops are always file names */ gdk_content_formats_new ((const char *[2]) { "text/uri-list", NULL }, 1), window, GDK_DRAG_PROTO_WIN32_DROPFILES); drop_win32 = GDK_WIN32_DROP (drop); gdk_drop_set_actions (drop, GDK_ACTION_COPY); hdrop = (HANDLE) msg->wParam; DragQueryPoint (hdrop, &pt); ClientToScreen (msg->hwnd, &pt); nfiles = DragQueryFile (hdrop, 0xFFFFFFFF, NULL, 0); result = g_string_new (NULL); for (i = 0; i < nfiles; i++) { char *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"); } g_clear_pointer (&drop_win32->dropfiles_list, g_free); drop_win32->dropfiles_list = result->str; g_string_free (result, FALSE); if (drop_win32->dropfiles_list == NULL) drop_win32->dropfiles_list = g_strdup (""); gdk_drop_emit_drop_event (drop, FALSE, pt.x / drop_win32->scale + _gdk_offset_x, pt.y / drop_win32->scale + _gdk_offset_y, _gdk_win32_get_next_tick (msg->time)); DragFinish (hdrop); *ret_valp = 0; return GDK_WIN32_MESSAGE_FILTER_REMOVE; } static void gdk_win32_drop_status (GdkDrop *drop, GdkDragAction actions, GdkDragAction preferred) { GdkWin32Drop *drop_win32 = GDK_WIN32_DROP (drop); GdkDrag *drag; g_return_if_fail (drop != NULL); GDK_NOTE (DND, g_print ("gdk_win32_drop_status: %s\n" " context=%p:{source_actions=%s, preferred=%s}\n", _gdk_win32_drag_action_to_string (actions), drop, _gdk_win32_drag_action_to_string (gdk_drop_get_actions (drop)), _gdk_win32_drag_action_to_string (preferred))); drop_win32->actions = actions; if (drop_win32->protocol == GDK_DRAG_PROTO_OLE2) return; drag = gdk_drop_get_drag (drop); if (drag != NULL) _gdk_win32_local_drag_give_feedback (drag, actions); } static void gdk_win32_drop_finish (GdkDrop *drop, GdkDragAction action) { GdkWin32Drop *drop_win32 = GDK_WIN32_DROP (drop); g_return_if_fail (drop != NULL); GDK_NOTE (DND, g_print ("gdk_win32_drop_finish with action %s\n", _gdk_win32_drag_action_to_string (action))); drop_win32->actions = action; drop_win32->drop_finished = TRUE; if (drop_win32->protocol == GDK_DRAG_PROTO_OLE2) return; } #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) { drop_target_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_win32_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_WIN32_DISPLAY (gdk_display_get_default ()), gdk_dropfiles_filter, NULL); DragAcceptFiles (GDK_SURFACE_HWND (window), TRUE); } else { GdkWin32Surface *impl = GDK_WIN32_SURFACE (window); /* Return if window is already setup for DND. */ if (impl->drop_target != 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 { impl->drop_target = ctx; } } } } void _gdk_win32_surface_unregister_dnd (GdkSurface *window) { GdkWin32Surface *impl = GDK_WIN32_SURFACE (window); if (impl->drop_target) idroptarget_release (&impl->drop_target->idt); } 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) { char *length_str = g_strdup_printf ("%" G_GSIZE_FORMAT, length); g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot get DnD data. Failed to allocate %s bytes to store the data."), length_str); g_free (length_str); GlobalUnlock (hdata); return NULL; } memcpy (data, ptr, length); *data_len = length; GlobalUnlock (hdata); return data; } static void gdk_win32_drop_read_async (GdkDrop *drop, GdkContentFormats *formats, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GdkWin32Drop *drop_win32 = GDK_WIN32_DROP (drop); GTask *task; drop_target_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 (drop, cancellable, callback, user_data); g_task_set_priority (task, io_priority); g_task_set_source_tag (task, gdk_win32_drop_read_async); mime_types = gdk_content_formats_get_mime_types (formats, &n_mime_types); if (drop_win32->protocol == GDK_DRAG_PROTO_WIN32_DROPFILES) { for (i = 0; i < n_mime_types; i++) if (g_strcmp0 (mime_types[i], "text/uri-list") == 0) break; if (i >= n_mime_types) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("No compatible transfer format found")); g_clear_pointer (&drop_win32->dropfiles_list, g_free); return; } stream = g_memory_input_stream_new_from_data (drop_win32->dropfiles_list, strlen (drop_win32->dropfiles_list), g_free); drop_win32->dropfiles_list = NULL; g_object_set_data (G_OBJECT (stream), "gdk-dnd-stream-contenttype", (gpointer) "text/uri-list"); g_task_return_pointer (task, stream, g_object_unref); return; } tctx = GDK_WIN32_SURFACE (gdk_drop_get_surface (drop))->drop_target; if (tctx == NULL) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, _("GDK surface 0x%p is not registered as a drop target"), gdk_drop_get_surface (drop)); 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; } for (pair = NULL, i = 0; i < n_mime_types; i++) { for (j = 0; j < drop_win32->droptarget_w32format_contentformat_map->len; j++) { pair = &g_array_index (drop_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_read_finish (GdkDrop *drop, GAsyncResult *result, const char **out_mime_type, GError **error) { GTask *task; GInputStream *stream; g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (drop)), NULL); task = G_TASK (result); g_return_val_if_fail (g_task_get_source_tag (task) == gdk_win32_drop_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_class_init (GdkWin32DropClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GdkDropClass *drop_class = GDK_DROP_CLASS (klass); object_class->finalize = gdk_win32_drop_finalize; drop_class->status = gdk_win32_drop_status; drop_class->finish = gdk_win32_drop_finish; drop_class->read_async = gdk_win32_drop_read_async; drop_class->read_finish = gdk_win32_drop_read_finish; } void _gdk_drop_init (void) { if (g_strcmp0 (getenv ("GDK_WIN32_OLE2_DND"), "0") == 0) use_ole2_dnd = FALSE; }