gtk2/gdk/win32/gdkdrop-win32.c
Chun-wei Fan 8acce5f294 gdk/win32: Drop local DnD protocol
It is basically not used by default and is pretty much broken at this point, so
it's about time to drop it.

Let's focus on fixing the OLE2 DnD protocol.
2021-08-06 17:16:27 +08:00

1238 lines
37 KiB
C

/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald
* Copyright (C) 2001 Archaeopteryx Software Inc.
* Copyright (C) 1998-2002 Tor Lillqvist
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include <string.h>
#include <io.h>
#include <fcntl.h>
/* The mingw.org compiler does not export GUIDS in it's import library. To work
* around that, define INITGUID to have the GUIDS declared. */
#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
#define INITGUID
#endif
/* For C-style COM wrapper macros */
#define COBJMACROS
#include "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 <ole2.h>
#include <shlobj.h>
#include <shlguid.h>
#include <objidl.h>
#include "gdkintl.h"
#include <gdk/gdk.h>
#include <glib/gstdio.h>
#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;
};
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 *display_win32 = GDK_WIN32_DISPLAY (display);
drop_win32 = g_object_new (GDK_TYPE_WIN32_DROP,
"device", device,
"drag", drag,
"formats", formats,
"surface", surface,
NULL);
if (display_win32->has_fixed_scale)
drop_win32->scale = display_win32->surface_scale;
else
drop_win32->scale = gdk_win32_display_get_monitor_scale_factor (display_win32, NULL, NULL);
drop_win32->protocol = protocol;
return GDK_DROP (drop_win32);
}
#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;
}
/* 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;
GdkContentFormats *formats;
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);
formats = query_object_formats (pDataObj, NULL);
drop = gdk_drop_new (display,
gdk_seat_get_pointer (gdk_display_get_default_seat (display)),
drag,
formats,
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 (formats);
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;
}
/* 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;
}
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;
}
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;
}
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 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;
GdkWin32Surface *impl;
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)));
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;
}