gtk/gdk/win32/gdkdnd-win32.c
Tor Lillqvist 6861b9247f Bug 524151 - Dragging of 0-byte files results in an empty filename on
2008-03-24  Tor Lillqvist  <tml@novell.com>

	Bug 524151 - Dragging of 0-byte files results in an empty filename
	on Windows XP and above

	* gdk/win32/gdkdnd-win32.c (resolve_link): Check for the file
	being empty first. For some reason ISHellLink and IPersistFile
	succeeds in interpreting empty files as shortcuts, claiming the
	target of the shortcut is an empty path.

	Change the function to take the wide character file name that the
	caller already has anyway, to avoid a superfluous conversion from
	UTF-8 to UTF-16.


svn path=/trunk/; revision=19931
2008-03-24 17:35:02 +00:00

1742 lines
40 KiB
C

/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald
* Copyright (C) 1998-2002 Tor Lillqvist
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* 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>
/* #define OLE2_DND */
#define INITGUID
#include "gdkdnd.h"
#include "gdkproperty.h"
#include "gdkinternals.h"
#include "gdkprivate-win32.h"
#ifdef OLE2_DND
#include <ole2.h>
#else
#include <objbase.h>
#endif
#include <shlobj.h>
#include <shlguid.h>
#include <gdk/gdk.h>
typedef struct _GdkDragContextPrivateWin32 GdkDragContextPrivateWin32;
typedef enum {
GDK_DRAG_STATUS_DRAG,
GDK_DRAG_STATUS_MOTION_WAIT,
GDK_DRAG_STATUS_ACTION_WAIT,
GDK_DRAG_STATUS_DROP
} GtkDragStatus;
typedef enum {
GDK_DRAG_SOURCE,
GDK_DRAG_TARGET
} GdkDragKind;
#ifdef OLE2_DND
#define PRINT_GUID(guid) \
g_print ("guid = %.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 FORMATETC *formats;
static int nformats;
#endif /* OLE2_DND */
/* Structure that holds information about a drag in progress.
* this is used on both source and destination sides.
*/
struct _GdkDragContextPrivateWin32 {
#ifdef OLE2_DND
gint ref_count;
#endif
guint16 last_x; /* Coordinates from last event */
guint16 last_y;
HWND dest_xid;
guint drag_status : 4; /* Current status of drag */
guint drop_failed : 1; /* Whether the drop was unsuccessful */
};
#define GDK_DRAG_CONTEXT_PRIVATE_DATA(context) ((GdkDragContextPrivateWin32 *) GDK_DRAG_CONTEXT (context)->windowing_data)
static GdkDragContext *current_dest_drag = NULL;
static void gdk_drag_context_init (GdkDragContext *dragcontext);
static void gdk_drag_context_class_init (GdkDragContextClass *klass);
static void gdk_drag_context_finalize (GObject *object);
static gpointer parent_class = NULL;
static GList *contexts;
GType
gdk_drag_context_get_type (void)
{
static GType object_type = 0;
if (!object_type)
{
static const GTypeInfo object_info =
{
sizeof (GdkDragContextClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) gdk_drag_context_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (GdkDragContext),
0, /* n_preallocs */
(GInstanceInitFunc) gdk_drag_context_init,
};
object_type = g_type_register_static (G_TYPE_OBJECT,
"GdkDragContext",
&object_info, 0);
}
return object_type;
}
static void
gdk_drag_context_init (GdkDragContext *dragcontext)
{
GdkDragContextPrivateWin32 *private = g_new0 (GdkDragContextPrivateWin32, 1);
dragcontext->windowing_data = private;
#ifdef OLE2_DND
private->ref_count = 1;
#endif
contexts = g_list_prepend (contexts, dragcontext);
}
static void
gdk_drag_context_class_init (GdkDragContextClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
object_class->finalize = gdk_drag_context_finalize;
}
static void
gdk_drag_context_finalize (GObject *object)
{
GdkDragContext *context = GDK_DRAG_CONTEXT (object);
GdkDragContextPrivateWin32 *private = GDK_DRAG_CONTEXT_PRIVATE_DATA (context);
GDK_NOTE (DND, g_print ("gdk_drag_context_finalize\n"));
g_list_free (context->targets);
if (context->source_window)
{
g_object_unref (context->source_window);
}
if (context->dest_window)
g_object_unref (context->dest_window);
contexts = g_list_remove (contexts, context);
if (context == current_dest_drag)
current_dest_drag = NULL;
g_free (private);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/* Drag Contexts */
GdkDragContext *
gdk_drag_context_new (void)
{
return g_object_new (gdk_drag_context_get_type (), NULL);
}
void
gdk_drag_context_ref (GdkDragContext *context)
{
g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
g_object_ref (context);
}
void
gdk_drag_context_unref (GdkDragContext *context)
{
g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
g_object_unref (context);
}
static GdkDragContext *
gdk_drag_context_find (gboolean is_source,
GdkWindow *source,
GdkWindow *dest)
{
GList *tmp_list = contexts;
GdkDragContext *context;
GdkDragContextPrivateWin32 *private;
while (tmp_list)
{
context = (GdkDragContext *)tmp_list->data;
private = GDK_DRAG_CONTEXT_PRIVATE_DATA (context);
if ((!context->is_source == !is_source) &&
((source == NULL) || (context->source_window && (context->source_window == source))) &&
((dest == NULL) || (context->dest_window && (context->dest_window == dest))))
return context;
tmp_list = tmp_list->next;
}
return NULL;
}
typedef struct {
#ifdef OLE2_DND
IDropTarget idt;
#endif
GdkDragContext *context;
} target_drag_context;
typedef struct {
#ifdef OLE2_DND
IDropSource ids;
#endif
GdkDragContext *context;
} source_drag_context;
#ifdef OLE2_DND
typedef struct {
IDataObject ido;
int ref_count;
} data_object;
typedef struct {
IEnumFORMATETC ief;
int ref_count;
int ix;
} enum_formats;
static enum_formats *enum_formats_new (void);
static ULONG STDMETHODCALLTYPE
idroptarget_addref (LPDROPTARGET This)
{
target_drag_context *ctx = (target_drag_context *) This;
GdkDragContextPrivateWin32 *private = GDK_DRAG_CONTEXT_PRIVATE_DATA (ctx->context);
int ref_count = ++private->ref_count;
gdk_drag_context_ref (ctx->context);
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\n", This));
*ppvObject = NULL;
PRINT_GUID (riid);
if (IsEqualGUID (riid, &IID_IUnknown))
{
g_print ("...IUnknown\n");
idroptarget_addref (This);
*ppvObject = This;
return S_OK;
}
else if (IsEqualGUID (riid, &IID_IDropTarget))
{
g_print ("...IDropTarget\n");
idroptarget_addref (This);
*ppvObject = This;
return S_OK;
}
else
{
g_print ("...Huh?\n");
return E_NOINTERFACE;
}
}
static ULONG STDMETHODCALLTYPE
idroptarget_release (LPDROPTARGET This)
{
target_drag_context *ctx = (target_drag_context *) This;
GdkDragContextPrivateWin32 *private = GDK_DRAG_CONTEXT_PRIVATE_DATA (ctx->context);
int ref_count = --private->ref_count;
gdk_drag_context_unref (ctx->context);
GDK_NOTE (DND, g_print ("idroptarget_release %p %d\n", This, ref_count));
if (ref_count == 0)
g_free (This);
return ref_count;
}
static HRESULT STDMETHODCALLTYPE
idroptarget_dragenter (LPDROPTARGET This,
LPDATAOBJECT pDataObj,
DWORD grfKeyState,
POINTL pt,
LPDWORD pdwEffect)
{
GDK_NOTE (DND, g_print ("idroptarget_dragenter %p\n", This));
return E_UNEXPECTED;
}
static HRESULT STDMETHODCALLTYPE
idroptarget_dragover (LPDROPTARGET This,
DWORD grfKeyState,
POINTL pt,
LPDWORD pdwEffect)
{
GDK_NOTE (DND, g_print ("idroptarget_dragover %p\n", This));
return E_UNEXPECTED;
}
static HRESULT STDMETHODCALLTYPE
idroptarget_dragleave (LPDROPTARGET This)
{
GDK_NOTE (DND, g_print ("idroptarget_dragleave %p\n", This));
return E_UNEXPECTED;
}
static HRESULT STDMETHODCALLTYPE
idroptarget_drop (LPDROPTARGET This,
LPDATAOBJECT pDataObj,
DWORD grfKeyState,
POINTL pt,
LPDWORD pdwEffect)
{
GDK_NOTE (DND, g_print ("idroptarget_drop %p\n", This));
return E_UNEXPECTED;
}
static ULONG STDMETHODCALLTYPE
idropsource_addref (LPDROPSOURCE This)
{
source_drag_context *ctx = (source_drag_context *) This;
GdkDragContextPrivateWin32 *private = GDK_DRAG_CONTEXT_PRIVATE_DATA (ctx->context);
gdk_drag_context_ref (ctx->context);
GDK_NOTE (DND, g_print ("idropsource_addref %p %d\n",
This, private->ref_count));
return private->ref_count;
}
static HRESULT STDMETHODCALLTYPE
idropsource_queryinterface (LPDROPSOURCE This,
REFIID riid,
LPVOID *ppvObject)
{
GDK_NOTE (DND, g_print ("idropsource_queryinterface %p\n", This));
*ppvObject = NULL;
PRINT_GUID (riid);
if (IsEqualGUID (riid, &IID_IUnknown))
{
g_print ("...IUnknown\n");
idropsource_addref (This);
*ppvObject = This;
return S_OK;
}
else if (IsEqualGUID (riid, &IID_IDropSource))
{
g_print ("...IDropSource\n");
idropsource_addref (This);
*ppvObject = This;
return S_OK;
}
else
{
g_print ("...Huh?\n");
return E_NOINTERFACE;
}
}
static ULONG STDMETHODCALLTYPE
idropsource_release (LPDROPSOURCE This)
{
source_drag_context *ctx = (source_drag_context *) This;
GdkDragContextPrivateWin32 *private = GDK_DRAG_CONTEXT_PRIVATE_DATA (ctx->context);
int ref_count = --private->ref_count;
gdk_drag_context_unref (ctx->context);
GDK_NOTE (DND, g_print ("idropsource_release %p %d\n", This, ref_count));
if (ref_count == 0)
g_free (This);
return ref_count;
}
static HRESULT STDMETHODCALLTYPE
idropsource_querycontinuedrag (LPDROPSOURCE This,
BOOL fEscapePressed,
DWORD grfKeyState)
{
GDK_NOTE (DND, g_print ("idropsource_querycontinuedrag %p\n", This));
return E_UNEXPECTED;
}
static HRESULT STDMETHODCALLTYPE
idropsource_givefeedback (LPDROPSOURCE This,
DWORD dwEffect)
{
GDK_NOTE (DND, g_print ("idropsource_givefeedback %p\n", This));
return E_UNEXPECTED;
}
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\n", This));
*ppvObject = NULL;
PRINT_GUID (riid);
if (IsEqualGUID (riid, &IID_IUnknown))
{
g_print ("...IUnknown\n");
idataobject_addref (This);
*ppvObject = This;
return S_OK;
}
else if (IsEqualGUID (riid, &IID_IDataObject))
{
g_print ("...IDataObject\n");
idataobject_addref (This);
*ppvObject = This;
return S_OK;
}
else
{
g_print ("...Huh?\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_free (This);
return ref_count;
}
static HRESULT STDMETHODCALLTYPE
idataobject_getdata (LPDATAOBJECT This,
LPFORMATETC pFormatEtc,
LPSTGMEDIUM pMedium)
{
GDK_NOTE (DND, g_print ("idataobject_getdata %p\n", This));
return E_UNEXPECTED;
}
static HRESULT STDMETHODCALLTYPE
idataobject_getdatahere (LPDATAOBJECT This,
LPFORMATETC pFormatEtc,
LPSTGMEDIUM pMedium)
{
GDK_NOTE (DND, g_print ("idataobject_getdatahere %p\n", This));
return E_UNEXPECTED;
}
static HRESULT STDMETHODCALLTYPE
idataobject_querygetdata (LPDATAOBJECT This,
LPFORMATETC pFormatEtc)
{
int i;
GDK_NOTE (DND, g_print ("idataobject_querygetdata %p %#x", This, pFormatEtc->cfFormat));
for (i = 0; i < nformats; i++)
if (pFormatEtc->cfFormat == formats[i].cfFormat)
{
GDK_NOTE (DND, g_print (" S_OK\n"));
return S_OK;
}
GDK_NOTE (DND, g_print (" DV_E_FORMATETC\n"));
return DV_E_FORMATETC;
}
static HRESULT STDMETHODCALLTYPE
idataobject_getcanonicalformatetc (LPDATAOBJECT This,
LPFORMATETC pFormatEtcIn,
LPFORMATETC pFormatEtcOut)
{
GDK_NOTE (DND, g_print ("idataobject_getcanonicalformatetc %p\n", This));
return E_FAIL;
}
static HRESULT STDMETHODCALLTYPE
idataobject_setdata (LPDATAOBJECT This,
LPFORMATETC pFormatEtc,
LPSTGMEDIUM pMedium,
BOOL fRelease)
{
GDK_NOTE (DND, g_print ("idataobject_setdata %p\n", This));
return E_UNEXPECTED;
}
static HRESULT STDMETHODCALLTYPE
idataobject_enumformatetc (LPDATAOBJECT This,
DWORD dwDirection,
LPENUMFORMATETC *ppEnumFormatEtc)
{
GDK_NOTE (DND, g_print ("idataobject_enumformatetc %p\n", This));
if (dwDirection != DATADIR_GET)
return E_NOTIMPL;
*ppEnumFormatEtc = &enum_formats_new ()->ief;
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\n", This));
return E_FAIL;
}
static HRESULT STDMETHODCALLTYPE
idataobject_dunadvise (LPDATAOBJECT This,
DWORD dwConnection)
{
GDK_NOTE (DND, g_print ("idataobject_dunadvise %p\n", This));
return E_FAIL;
}
static HRESULT STDMETHODCALLTYPE
idataobject_enumdadvise (LPDATAOBJECT This,
LPENUMSTATDATA *ppenumAdvise)
{
GDK_NOTE (DND, g_print ("idataobject_enumdadvise %p\n", This));
return E_FAIL;
}
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\n", This));
*ppvObject = NULL;
PRINT_GUID (riid);
if (IsEqualGUID (riid, &IID_IUnknown))
{
g_print ("...IUnknown\n");
ienumformatetc_addref (This);
*ppvObject = This;
return S_OK;
}
else if (IsEqualGUID (riid, &IID_IEnumFORMATETC))
{
g_print ("...IEnumFORMATETC\n");
ienumformatetc_addref (This);
*ppvObject = This;
return S_OK;
}
else
{
g_print ("...Huh?\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_free (This);
return ref_count;
}
static HRESULT STDMETHODCALLTYPE
ienumformatetc_next (LPENUMFORMATETC This,
ULONG celt,
LPFORMATETC elts,
ULONG *nelt)
{
enum_formats *en = (enum_formats *) This;
int i, n;
GDK_NOTE (DND, g_print ("ienumformatetc_next %p %d %ld\n", This, en->ix, celt));
n = 0;
for (i = 0; i < celt; i++)
{
if (en->ix >= nformats)
break;
elts[i] = formats[en->ix++];
n++;
}
if (nelt != NULL)
*nelt = n;
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\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\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\n", This));
new = enum_formats_new ();
new->ix = en->ix;
*ppEnumFormatEtc = &new->ief;
return S_OK;
}
static IDropTargetVtbl idt_vtbl = {
idroptarget_queryinterface,
idroptarget_addref,
idroptarget_release,
idroptarget_dragenter,
idroptarget_dragover,
idroptarget_dragleave,
idroptarget_drop
};
static IDropSourceVtbl ids_vtbl = {
idropsource_queryinterface,
idropsource_addref,
idropsource_release,
idropsource_querycontinuedrag,
idropsource_givefeedback
};
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 target_drag_context *
target_context_new (void)
{
target_drag_context *result;
result = g_new0 (target_drag_context, 1);
result->idt.lpVtbl = &idt_vtbl;
result->context = gdk_drag_context_new ();
result->context->is_source = FALSE;
GDK_NOTE (DND, g_print ("target_context_new: %p\n", result));
return result;
}
static source_drag_context *
source_context_new (void)
{
source_drag_context *result;
result = g_new0 (source_drag_context, 1);
result->ids.lpVtbl = &ids_vtbl;
result->context = gdk_drag_context_new ();
result->context->is_source = TRUE;
GDK_NOTE (DND, g_print ("source_context_new: %p\n", result));
return result;
}
static data_object *
data_object_new (void)
{
data_object *result;
result = g_new0 (data_object, 1);
result->ido.lpVtbl = &ido_vtbl;
result->ref_count = 1;
GDK_NOTE (DND, g_print ("data_object_new: %p\n", result));
return result;
}
static enum_formats *
enum_formats_new (void)
{
enum_formats *result;
result = g_new0 (enum_formats, 1);
result->ief.lpVtbl = &ief_vtbl;
result->ref_count = 1;
result->ix = 0;
GDK_NOTE (DND, g_print ("enum_formats_new: %p\n", result));
return result;
}
#endif
/* From MS Knowledge Base article Q130698 */
static gboolean
resolve_link (HWND hWnd,
wchar_t *link,
guchar **lpszPath)
{
WIN32_FILE_ATTRIBUTE_DATA wfad;
HRESULT hres;
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.
*/
hres = CoCreateInstance (&CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
&IID_IShellLinkW,
(LPVOID *)&pslW);
if (SUCCEEDED (hres))
{
/* The IShellLink interface supports the IPersistFile
* interface. Get an interface pointer to it.
*/
hres = pslW->lpVtbl->QueryInterface (pslW,
&IID_IPersistFile,
(LPVOID *) &ppf);
}
if (SUCCEEDED (hres))
{
/* Load the file. */
hres = ppf->lpVtbl->Load (ppf, link, STGM_READ);
}
if (SUCCEEDED (hres))
{
/* Resolve the link by calling the Resolve()
* interface function.
*/
hres = pslW->lpVtbl->Resolve (pslW, hWnd, SLR_ANY_MATCH | SLR_NO_UI);
}
if (SUCCEEDED (hres))
{
wchar_t wtarget[MAX_PATH];
hres = pslW->lpVtbl->GetPath (pslW, wtarget, MAX_PATH, NULL, 0);
if (SUCCEEDED (hres))
*lpszPath = g_utf16_to_utf8 (wtarget, -1, NULL, NULL, NULL);
}
if (ppf)
ppf->lpVtbl->Release (ppf);
if (pslW)
pslW->lpVtbl->Release (pslW);
return SUCCEEDED (hres);
}
static GdkFilterReturn
gdk_dropfiles_filter (GdkXEvent *xev,
GdkEvent *event,
gpointer data)
{
GdkDragContext *context;
GdkDragContextPrivateWin32 *private;
GString *result;
MSG *msg = (MSG *) xev;
HANDLE hdrop;
POINT pt;
gint nfiles, i;
guchar *fileName, *linkedFile;
if (msg->message == WM_DROPFILES)
{
GDK_NOTE (DND, g_print ("WM_DROPFILES: %p\n", msg->hwnd));
context = gdk_drag_context_new ();
private = GDK_DRAG_CONTEXT_PRIVATE_DATA (context);
context->protocol = GDK_DRAG_PROTO_WIN32_DROPFILES;
context->is_source = FALSE;
context->source_window = _gdk_root;
g_object_ref (context->source_window);
context->dest_window = event->any.window;
g_object_ref (context->dest_window);
/* WM_DROPFILES drops are always file names */
context->targets =
g_list_append (NULL, GUINT_TO_POINTER (_text_uri_list));
context->actions = GDK_ACTION_COPY;
context->suggested_action = GDK_ACTION_COPY;
current_dest_drag = context;
event->dnd.type = GDK_DROP_START;
event->dnd.context = current_dest_drag;
hdrop = (HANDLE) msg->wParam;
DragQueryPoint (hdrop, &pt);
ClientToScreen (msg->hwnd, &pt);
event->dnd.x_root = pt.x + _gdk_offset_x;
event->dnd.y_root = pt.y + _gdk_offset_y;
event->dnd.time = _gdk_win32_get_next_tick (msg->time);
nfiles = DragQueryFile (hdrop, 0xFFFFFFFF, NULL, 0);
result = g_string_new (NULL);
for (i = 0; i < nfiles; i++)
{
gchar *uri;
wchar_t wfn[MAX_PATH];
DragQueryFileW (hdrop, i, wfn, MAX_PATH);
fileName = g_utf16_to_utf8 (wfn, -1, NULL, NULL, NULL);
/* Resolve shortcuts */
if (resolve_link (msg->hwnd, wfn, &linkedFile))
{
uri = g_filename_to_uri (linkedFile, NULL, NULL);
g_free (linkedFile);
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);
}
}
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);
}
}
g_free (fileName);
g_string_append (result, "\015\012");
}
_gdk_dropfiles_store (result->str);
g_string_free (result, FALSE);
DragFinish (hdrop);
return GDK_FILTER_TRANSLATE;
}
else
return GDK_FILTER_CONTINUE;
}
/*************************************************************
************************** Public API ***********************
*************************************************************/
void
_gdk_dnd_init (void)
{
#ifdef OLE2_DND
HRESULT hres;
hres = OleInitialize (NULL);
if (! SUCCEEDED (hres))
g_error ("OleInitialize failed");
nformats = 2;
formats = g_new (FORMATETC, nformats);
formats[0].cfFormat = CF_TEXT;
formats[0].ptd = NULL;
formats[0].dwAspect = DVASPECT_CONTENT;
formats[0].lindex = -1;
formats[0].tymed = TYMED_HGLOBAL;
formats[1].cfFormat = CF_GDIOBJFIRST;
formats[1].ptd = NULL;
formats[1].dwAspect = DVASPECT_CONTENT;
formats[1].lindex = -1;
formats[1].tymed = TYMED_HGLOBAL;
#endif
}
void
_gdk_win32_dnd_exit (void)
{
#ifdef OLE2_DND
OleUninitialize ();
#endif
}
/* Source side */
static void
local_send_leave (GdkDragContext *context,
guint32 time)
{
GdkEvent tmp_event;
if ((current_dest_drag != NULL) &&
(current_dest_drag->protocol == GDK_DRAG_PROTO_LOCAL) &&
(current_dest_drag->source_window == context->source_window))
{
tmp_event.dnd.type = GDK_DRAG_LEAVE;
tmp_event.dnd.window = context->dest_window;
/* Pass ownership of context to the event */
tmp_event.dnd.send_event = FALSE;
tmp_event.dnd.context = current_dest_drag;
tmp_event.dnd.time = GDK_CURRENT_TIME; /* FIXME? */
current_dest_drag = NULL;
gdk_event_put (&tmp_event);
}
}
static void
local_send_enter (GdkDragContext *context,
guint32 time)
{
GdkEvent tmp_event;
GdkDragContextPrivateWin32 *private;
GdkDragContext *new_context;
private = GDK_DRAG_CONTEXT_PRIVATE_DATA (context);
if (current_dest_drag != NULL)
{
gdk_drag_context_unref (current_dest_drag);
current_dest_drag = NULL;
}
new_context = gdk_drag_context_new ();
new_context->protocol = GDK_DRAG_PROTO_LOCAL;
new_context->is_source = FALSE;
new_context->source_window = context->source_window;
g_object_ref (new_context->source_window);
new_context->dest_window = context->dest_window;
g_object_ref (new_context->dest_window);
new_context->targets = g_list_copy (context->targets);
gdk_window_set_events (new_context->source_window,
gdk_window_get_events (new_context->source_window) |
GDK_PROPERTY_CHANGE_MASK);
new_context->actions = context->actions;
tmp_event.dnd.type = GDK_DRAG_ENTER;
tmp_event.dnd.window = context->dest_window;
tmp_event.dnd.send_event = FALSE;
tmp_event.dnd.context = new_context;
tmp_event.dnd.time = GDK_CURRENT_TIME; /* FIXME? */
current_dest_drag = new_context;
gdk_event_put (&tmp_event);
}
static void
local_send_motion (GdkDragContext *context,
gint x_root,
gint y_root,
GdkDragAction action,
guint32 time)
{
GdkEvent tmp_event;
if ((current_dest_drag != NULL) &&
(current_dest_drag->protocol == GDK_DRAG_PROTO_LOCAL) &&
(current_dest_drag->source_window == context->source_window))
{
tmp_event.dnd.type = GDK_DRAG_MOTION;
tmp_event.dnd.window = current_dest_drag->dest_window;
tmp_event.dnd.send_event = FALSE;
tmp_event.dnd.context = current_dest_drag;
tmp_event.dnd.time = time;
current_dest_drag->suggested_action = action;
current_dest_drag->actions = current_dest_drag->suggested_action;
tmp_event.dnd.x_root = x_root;
tmp_event.dnd.y_root = y_root;
(GDK_DRAG_CONTEXT_PRIVATE_DATA (current_dest_drag))->last_x = x_root;
(GDK_DRAG_CONTEXT_PRIVATE_DATA (current_dest_drag))->last_y = y_root;
GDK_DRAG_CONTEXT_PRIVATE_DATA (context)->drag_status = GDK_DRAG_STATUS_MOTION_WAIT;
gdk_event_put (&tmp_event);
}
}
static void
local_send_drop (GdkDragContext *context,
guint32 time)
{
GdkEvent tmp_event;
if ((current_dest_drag != NULL) &&
(current_dest_drag->protocol == GDK_DRAG_PROTO_LOCAL) &&
(current_dest_drag->source_window == context->source_window))
{
GdkDragContextPrivateWin32 *private;
private = GDK_DRAG_CONTEXT_PRIVATE_DATA (current_dest_drag);
/* Pass ownership of context to the event */
tmp_event.dnd.type = GDK_DROP_START;
tmp_event.dnd.window = current_dest_drag->dest_window;
tmp_event.dnd.send_event = FALSE;
tmp_event.dnd.context = current_dest_drag;
tmp_event.dnd.time = GDK_CURRENT_TIME;
tmp_event.dnd.x_root = private->last_x;
tmp_event.dnd.y_root = private->last_y;
current_dest_drag = NULL;
gdk_event_put (&tmp_event);
}
}
static void
gdk_drag_do_leave (GdkDragContext *context,
guint32 time)
{
if (context->dest_window)
{
GDK_NOTE (DND, g_print ("gdk_drag_do_leave\n"));
switch (context->protocol)
{
case GDK_DRAG_PROTO_LOCAL:
local_send_leave (context, time);
break;
default:
break;
}
g_object_unref (context->dest_window);
context->dest_window = NULL;
}
}
GdkDragContext *
gdk_drag_begin (GdkWindow *window,
GList *targets)
{
#ifndef OLE2_DND
GList *tmp_list;
GdkDragContext *new_context;
g_return_val_if_fail (window != NULL, NULL);
new_context = gdk_drag_context_new ();
new_context->is_source = TRUE;
new_context->source_window = window;
g_object_ref (window);
tmp_list = g_list_last (targets);
new_context->targets = NULL;
while (tmp_list)
{
new_context->targets = g_list_prepend (new_context->targets,
tmp_list->data);
tmp_list = tmp_list->prev;
}
new_context->actions = 0;
return new_context;
#else
source_drag_context *ctx;
GList *tmp_list;
data_object *dobj;
HRESULT hResult;
DWORD dwEffect;
HGLOBAL global;
STGMEDIUM medium;
g_return_val_if_fail (window != NULL, NULL);
GDK_NOTE (DND, g_print ("gdk_drag_begin\n"));
ctx = source_context_new ();
ctx->context->protocol = GDK_DRAG_PROTO_OLE2;
ctx->context->source_window = window;
g_object_ref (window);
tmp_list = g_list_last (targets);
ctx->context->targets = NULL;
while (tmp_list)
{
ctx->context->targets = g_list_prepend (ctx->context->targets,
tmp_list->data);
tmp_list = tmp_list->prev;
}
ctx->context->actions = 0;
dobj = data_object_new ();
global = GlobalAlloc (GMEM_FIXED, sizeof (ctx));
memcpy (&global, ctx, sizeof (ctx));
medium.tymed = TYMED_HGLOBAL;
medium.hGlobal = global;
medium.pUnkForRelease = NULL;
dobj->ido.lpVtbl->SetData (&dobj->ido, &formats[1], &medium, TRUE);
hResult = DoDragDrop (&dobj->ido, &ctx->ids, DROPEFFECT_MOVE|DROPEFFECT_COPY, &dwEffect);
GDK_NOTE (DND, g_print ("DoDragDrop returned %s\n",
(hResult == DRAGDROP_S_DROP ? "DRAGDROP_S_DROP" :
(hResult == DRAGDROP_S_CANCEL ? "DRAGDROP_S_CANCEL" :
(hResult == E_UNEXPECTED ? "E_UNEXPECTED" :
g_strdup_printf ("%#.8lx", hResult))))));
dobj->ido.lpVtbl->Release (&dobj->ido);
ctx->ids.lpVtbl->Release (&ctx->ids);
return ctx->context;
#endif
}
guint32
gdk_drag_get_protocol_for_display (GdkDisplay *display,
guint32 xid,
GdkDragProtocol *protocol)
{
GdkWindow *window;
GDK_NOTE (DND, g_print ("gdk_drag_get_protocol\n"));
window = gdk_window_lookup (xid);
if (GPOINTER_TO_INT (gdk_drawable_get_data (window, "gdk-dnd-registered")))
{
*protocol = GDK_DRAG_PROTO_LOCAL;
return xid;
}
return 0;
}
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;
}
void
gdk_drag_find_window_for_screen (GdkDragContext *context,
GdkWindow *drag_window,
GdkScreen *screen,
gint x_root,
gint y_root,
GdkWindow **dest_window,
GdkDragProtocol *protocol)
{
find_window_enum_arg a;
a.x = x_root - _gdk_offset_x;
a.y = y_root - _gdk_offset_y;
a.ignore = drag_window ? GDK_WINDOW_HWND (drag_window) : NULL;
a.result = NULL;
EnumWindows (find_window_enum_proc, (LPARAM) &a);
if (a.result == NULL)
*dest_window = NULL;
else
{
*dest_window = gdk_win32_handle_table_lookup (GPOINTER_TO_UINT (a.result));
if (*dest_window)
{
*dest_window = gdk_window_get_toplevel (*dest_window);
g_object_ref (*dest_window);
}
if (context->source_window)
*protocol = GDK_DRAG_PROTO_LOCAL;
else
*protocol = GDK_DRAG_PROTO_WIN32_DROPFILES;
}
GDK_NOTE (DND,
g_print ("gdk_drag_find_window: %p %+d%+d: %p: %p %d\n",
(drag_window ? GDK_WINDOW_HWND (drag_window) : NULL),
x_root, y_root,
a.result,
(*dest_window ? GDK_WINDOW_HWND (*dest_window) : NULL),
*protocol));
}
gboolean
gdk_drag_motion (GdkDragContext *context,
GdkWindow *dest_window,
GdkDragProtocol protocol,
gint x_root,
gint y_root,
GdkDragAction suggested_action,
GdkDragAction possible_actions,
guint32 time)
{
GdkDragContextPrivateWin32 *private;
g_return_val_if_fail (context != NULL, FALSE);
GDK_NOTE (DND, g_print ("gdk_drag_motion\n"));
private = GDK_DRAG_CONTEXT_PRIVATE_DATA (context);
if (context->dest_window != dest_window)
{
GdkEvent temp_event;
/* Send a leave to the last destination */
gdk_drag_do_leave (context, time);
private->drag_status = GDK_DRAG_STATUS_DRAG;
/* Check if new destination accepts drags, and which protocol */
if (dest_window)
{
context->dest_window = dest_window;
g_object_ref (context->dest_window);
context->protocol = protocol;
switch (protocol)
{
case GDK_DRAG_PROTO_LOCAL:
local_send_enter (context, time);
break;
default:
break;
}
context->suggested_action = suggested_action;
}
else
{
context->dest_window = NULL;
context->action = 0;
}
/* Push a status event, to let the client know that
* the drag changed
*/
temp_event.dnd.type = GDK_DRAG_STATUS;
temp_event.dnd.window = context->source_window;
/* We use this to signal a synthetic status. Perhaps
* we should use an extra field...
*/
temp_event.dnd.send_event = TRUE;
temp_event.dnd.context = context;
temp_event.dnd.time = time;
gdk_event_put (&temp_event);
}
else
{
context->suggested_action = suggested_action;
}
/* Send a drag-motion event */
private->last_x = x_root;
private->last_y = y_root;
if (context->dest_window)
{
if (private->drag_status == GDK_DRAG_STATUS_DRAG)
{
switch (context->protocol)
{
case GDK_DRAG_PROTO_LOCAL:
local_send_motion (context, x_root, y_root, suggested_action, time);
break;
case GDK_DRAG_PROTO_NONE:
g_warning ("GDK_DRAG_PROTO_NONE is not valid in gdk_drag_motion()");
break;
default:
break;
}
}
else
return TRUE;
}
return FALSE;
}
void
gdk_drag_drop (GdkDragContext *context,
guint32 time)
{
g_return_if_fail (context != NULL);
GDK_NOTE (DND, g_print ("gdk_drag_drop\n"));
if (context->dest_window)
{
switch (context->protocol)
{
case GDK_DRAG_PROTO_LOCAL:
local_send_drop (context, time);
break;
case GDK_DRAG_PROTO_NONE:
g_warning ("GDK_DRAG_PROTO_NONE is not valid in gdk_drag_drop()");
break;
default:
break;
}
}
}
void
gdk_drag_abort (GdkDragContext *context,
guint32 time)
{
g_return_if_fail (context != NULL);
GDK_NOTE (DND, g_print ("gdk_drag_abort\n"));
gdk_drag_do_leave (context, time);
}
/* Destination side */
void
gdk_drag_status (GdkDragContext *context,
GdkDragAction action,
guint32 time)
{
GdkDragContextPrivateWin32 *private;
GdkDragContext *src_context;
GdkEvent tmp_event;
g_return_if_fail (context != NULL);
private = GDK_DRAG_CONTEXT_PRIVATE_DATA (context);
context->action = action;
src_context = gdk_drag_context_find (TRUE,
context->source_window,
context->dest_window);
if (src_context)
{
GdkDragContextPrivateWin32 *private = GDK_DRAG_CONTEXT_PRIVATE_DATA (src_context);
if (private->drag_status == GDK_DRAG_STATUS_MOTION_WAIT)
private->drag_status = GDK_DRAG_STATUS_DRAG;
tmp_event.dnd.type = GDK_DRAG_STATUS;
tmp_event.dnd.window = context->source_window;
tmp_event.dnd.send_event = FALSE;
tmp_event.dnd.context = src_context;
tmp_event.dnd.time = GDK_CURRENT_TIME; /* FIXME? */
if (action == GDK_ACTION_DEFAULT)
action = 0;
src_context->action = action;
gdk_event_put (&tmp_event);
}
}
void
gdk_drop_reply (GdkDragContext *context,
gboolean ok,
guint32 time)
{
g_return_if_fail (context != NULL);
GDK_NOTE (DND, g_print ("gdk_drop_reply\n"));
if (context->dest_window)
{
switch (context->protocol)
{
case GDK_DRAG_PROTO_WIN32_DROPFILES:
_gdk_dropfiles_store (NULL);
break;
default:
break;
}
}
}
void
gdk_drop_finish (GdkDragContext *context,
gboolean success,
guint32 time)
{
GdkDragContextPrivateWin32 *private;
GdkDragContext *src_context;
GdkEvent tmp_event;
g_return_if_fail (context != NULL);
GDK_NOTE (DND, g_print ("gdk_drop_finish"));
private = GDK_DRAG_CONTEXT_PRIVATE_DATA (context);
src_context = gdk_drag_context_find (TRUE,
context->source_window,
context->dest_window);
if (src_context)
{
tmp_event.dnd.type = GDK_DROP_FINISHED;
tmp_event.dnd.window = src_context->source_window;
tmp_event.dnd.send_event = FALSE;
tmp_event.dnd.context = src_context;
gdk_event_put (&tmp_event);
}
}
#ifdef OLE2_DND
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 (idtp, FALSE, TRUE);
}
return GDK_FILTER_CONTINUE;
}
#endif
void
gdk_window_register_dnd (GdkWindow *window)
{
#ifdef OLE2_DND
target_drag_context *ctx;
HRESULT hres;
#endif
g_return_if_fail (window != NULL);
if (GPOINTER_TO_INT (gdk_drawable_get_data (window, "gdk-dnd-registered")))
return;
gdk_drawable_set_data (window, "gdk-dnd-registered", GINT_TO_POINTER(TRUE), NULL);
GDK_NOTE (DND, g_print ("gdk_window_register_dnd: %p\n",
GDK_WINDOW_HWND (window)));
/* 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_window_add_filter (window, gdk_dropfiles_filter, NULL);
DragAcceptFiles (GDK_WINDOW_HWND (window), TRUE);
#ifdef OLE2_DND
/* Register for OLE2 d&d */
ctx = target_context_new ();
ctx->context->protocol = GDK_DRAG_PROTO_OLE2;
hres = CoLockObjectExternal ((IUnknown *) &ctx->idt, TRUE, FALSE);
if (!SUCCEEDED (hres))
OTHER_API_FAILED ("CoLockObjectExternal");
else
{
hres = RegisterDragDrop (GDK_WINDOW_HWND (window), &ctx->idt);
if (hres == DRAGDROP_E_ALREADYREGISTERED)
{
g_print ("DRAGDROP_E_ALREADYREGISTERED\n");
#if 0
ctx->idt.lpVtbl->Release (&ctx->idt);
#endif
CoLockObjectExternal ((IUnknown *) &ctx->idt, FALSE, FALSE);
}
else if (!SUCCEEDED (hres))
OTHER_API_FAILED ("RegisterDragDrop");
else
{
gdk_window_add_filter (window, gdk_destroy_filter, &ctx->idt);
}
}
#endif
}
/*************************************************************
* gdk_drag_get_selection:
* Returns the selection atom for the current source window
* arguments:
*
* results:
*************************************************************/
GdkAtom
gdk_drag_get_selection (GdkDragContext *context)
{
if (context->protocol == GDK_DRAG_PROTO_LOCAL)
return _local_dnd;
else if (context->protocol == GDK_DRAG_PROTO_WIN32_DROPFILES)
return _gdk_win32_dropfiles;
else if (context->protocol == GDK_DRAG_PROTO_OLE2)
return _gdk_ole2_dnd;
else
return GDK_NONE;
}
gboolean
gdk_drag_drop_succeeded (GdkDragContext *context)
{
GdkDragContextPrivateWin32 *private;
g_return_val_if_fail (context != NULL, FALSE);
private = GDK_DRAG_CONTEXT_PRIVATE_DATA (context);
/* FIXME: Can we set drop_failed when the drop has failed? */
return !private->drop_failed;
}