/* 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 /* * Support for OLE-2 drag and drop added at Archaeopteryx Software, 2001 * For more information, contact Stephan R.A. Deibel (sdeibel@archaeopteryx.com) * * Notes on the implementation: * * Source drag context, IDragSource and IDataObject for it are created * (almost) simultaneously, whereas target drag context and IDropTarget * are separated in time - IDropTarget is created when a window is made * to accept drops, while target drag context is created when a dragging * cursor enters the window and is destroyed when that cursor leaves * the window. * * There's a mismatch between data types supported by W32 (W32 formats) * and by GTK+ (GDK targets). * To account for it the data is transmuted back and forth. There are two * main points of transmutation: * * GDK convert selection: transmute W32 data to GTK+ data * * GDK window property change: transmute GTK+ data to W32 data * * There are also two points where data formats are considered: * * When source drag context is created, it gets a list of GTK+ targets * that it supports, these are matched to the W32 formats they * correspond to (possibly with transmutation). New W32 formats for * GTK+-specific formats are also created here (see below). * * When target drag context is created, it queries the IDataObject * for the list of W32 formats it supports and matches these to * corresponding GTK+ formats that it will be able to provide * (possibly with transmutation) later. Missing GDK targets for * W32-specific formats are also created here (see below). * * W32 formats and GTK+ targets are both integers (CLIPFORMAT and GdkAtom * respectively), but cannot be used interchangeably. * * To accommodate advanced GTK+ applications the code allows them to * register drop targets that accept W32 data formats, and to register * drag sources that provide W32 data formats. To do that they must * register either with the string name of the format in question * (for example, "Shell IDList Array") or, for unnamed pre-defined * formats, register with the stringified constant name of the format * in question (for example, "CF_UNICODETEXT"). * If such target format is accepted/provided, GDK will not try to * transmute it to/from something else. Otherwise GDK will do the following * transmutation: * * If GTK+ application provides image/png, image/gif or image/jpeg, * GDK will claim to also provide "PNG", "GIF" or "JFIF" respectively, * and will pass these along verbatim. * * If GTK+ application provides any GdkPixbuf-compatible target, * GDK will also offer "PNG" and CF_DIB W32 formats. * * If GTK+ application provides UTF8_STRING, GDK will also offer * CF_UNICODETEXT (UTF-16-encoded) and CF_TEXT (encoded with thread- * and locale-depenant codepage), and will do the conversion when such * data is requested. * * If GTK+ application accepts image/png, image/gif or image/jpeg, * GDK will claim to also accept "PNG", "GIF" or "JFIF" respectively, * and will pass these along verbatim. * * If GTK+ application accepts image/bmp, GDK will * claim to accept CF_DIB W32 format, and will convert * it, changing the header, when such data is provided. * * If GTK+ application accepts UTF8_STRING, GDK will * claim to accept CF_UNICODETEXT and CF_TEXT, and will do * the conversion when such data is provided. * * If GTK+ application accepts text/uri-list, GDK will * claim to accept "Shell IDList Array", and will do the * conversion when such data is provided. * * Currently the conversion from text/uri-list to Shell IDList Array is not * implemented, so it's not possible to drag & drop files from GTK+ * applications to non-GTK+ applications the same way one can drag files * from Windows Explorer. * * To accommodate GTK+ application compaibility the code allows * GTK+ applications to register drop targets that accept GTK+-specific * data formats, and to register drag sources that provide GTK+-specific * data formats. This is done by simply registering target atom names * as clipboard formats. This way two GTK+ applications can exchange * data in their native formats (both well-known ones, such as UTF8_STRING, * and special, known only to specific applications). This will work just * fine as long as both applications agree on what kind of data is stored * under such format exactly. * * Note that clipboard format space is limited, there can only be 16384 * of them for a particular user session. Therefore it is highly inadvisable * to create and register such formats out of the whole cloth, dynamically. * If more flexibility is needed, register one format that has some * internal indicators of the kind of data it contains, then write the application * in such a way that it requests the data and inspects its header before deciding * whether to accept it or not. For details see GTK+ drag & drop documentation * on the "drag-motion" and "drag-data-received" signals. */ /* The mingw.org compiler does not export GUIDS in it's import library. To work * around that, define INITGUID to have the GUIDS declared. */ #if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) #define INITGUID #endif /* For C-style COM wrapper macros */ #define COBJMACROS #include "gdkdnd.h" #include "gdkproperty.h" #include "gdkinternals.h" #include "gdkprivate-win32.h" #include "gdkwin32.h" #include "gdkwin32dnd.h" #include "gdkdisplayprivate.h" #include "gdk/gdkdndprivate.h" #include "gdkwin32dnd-private.h" #include "gdkdisplay-win32.h" #include "gdkselection-win32.h" #include "gdkdeviceprivate.h" #include #include #include #include #include #include /* from gdkselection-win32.c */ extern GdkAtom *known_pixbuf_formats; extern int n_known_pixbuf_formats; typedef enum { GDK_DRAG_STATUS_DRAG, GDK_DRAG_STATUS_MOTION_WAIT, GDK_DRAG_STATUS_ACTION_WAIT, GDK_DRAG_STATUS_DROP } GdkDragStatus; static GList *contexts; static GdkDragContext *current_dest_drag = NULL; static gboolean use_ole2_dnd = FALSE; static gboolean drag_context_grab (GdkDragContext *context); G_DEFINE_TYPE (GdkWin32DragContext, gdk_win32_drag_context, GDK_TYPE_DRAG_CONTEXT) static void move_drag_window (GdkDragContext *context, guint x_root, guint y_root) { GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context); gdk_window_move (context_win32->drag_window, x_root - context_win32->hot_x, y_root - context_win32->hot_y); gdk_window_raise (context_win32->drag_window); } static void gdk_win32_drag_context_init (GdkWin32DragContext *context) { if (!use_ole2_dnd) { contexts = g_list_prepend (contexts, context); } else { } GDK_NOTE (DND, g_print ("gdk_drag_context_init %p\n", context)); } static void gdk_win32_drag_context_finalize (GObject *object) { GdkDragContext *context; GdkWin32DragContext *context_win32; GdkWindow *drag_window; GDK_NOTE (DND, g_print ("gdk_drag_context_finalize %p\n", object)); g_return_if_fail (GDK_IS_WIN32_DRAG_CONTEXT (object)); context = GDK_DRAG_CONTEXT (object); context_win32 = GDK_WIN32_DRAG_CONTEXT (context); if (!use_ole2_dnd) { contexts = g_list_remove (contexts, context); if (context == current_dest_drag) current_dest_drag = NULL; } g_set_object (&context_win32->ipc_window, NULL); drag_window = context_win32->drag_window; g_array_unref (context_win32->droptarget_format_target_map); G_OBJECT_CLASS (gdk_win32_drag_context_parent_class)->finalize (object); if (drag_window) gdk_window_destroy (drag_window); } /* Drag Contexts */ static GdkDragContext * gdk_drag_context_new (GdkDisplay *display, gboolean is_source, GdkWindow *source_window, GdkWindow *dest_window, GdkDragAction actions, GdkDevice *device, GdkDragProtocol protocol) { GdkWin32DragContext *context_win32; GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display); GdkDragContext *context; context_win32 = g_object_new (GDK_TYPE_WIN32_DRAG_CONTEXT, "display", display, NULL); context = GDK_DRAG_CONTEXT(context_win32); gdk_drag_context_set_device (context, device ? device : gdk_seat_get_pointer (gdk_display_get_default_seat (display))); if (win32_display->has_fixed_scale) context_win32->scale = win32_display->window_scale; else context_win32->scale = _gdk_win32_display_get_monitor_scale_factor (win32_display, NULL, NULL, NULL); context_win32->droptarget_format_target_map = g_array_new (FALSE, FALSE, sizeof (GdkSelTargetFormat)); context->is_source = is_source; g_set_object (&context->source_window, source_window); g_set_object (&context->dest_window, dest_window); context->actions = actions; context->protocol = protocol; return context; } static GdkDragContext * gdk_drag_context_find (gboolean is_source, GdkWindow *source, GdkWindow *dest) { GList *tmp_list = contexts; GdkDragContext *context; while (tmp_list) { context = (GdkDragContext *)tmp_list->data; 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; } #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]); typedef struct { IDropTarget idt; GdkDragContext *context; gint ref_count; GdkWindow *dest_window; } target_drag_context; typedef struct { IDropSource ids; GdkDragContext *context; gint ref_count; } source_drag_context; typedef struct { IDataObject ido; int ref_count; GdkDragContext *context; GArray *formats; } data_object; typedef struct { IEnumFORMATETC ief; int ref_count; int ix; data_object *dataobj; } enum_formats; static source_drag_context *pending_src_context = NULL; static source_drag_context *current_src_context = NULL; static data_object *current_src_object = NULL; static enum_formats *enum_formats_new (data_object *dataobj); /* map windows -> target drag contexts. The table * owns a ref to both objects. */ static GHashTable* target_ctx_for_window = NULL; static ULONG STDMETHODCALLTYPE idroptarget_addref (LPDROPTARGET This) { target_drag_context *ctx = (target_drag_context *) This; int ref_count = ++ctx->ref_count; GDK_NOTE (DND, g_print ("idroptarget_addref %p %d\n", This, ref_count)); return ref_count; } static HRESULT STDMETHODCALLTYPE idroptarget_queryinterface (LPDROPTARGET This, REFIID riid, LPVOID *ppvObject) { GDK_NOTE (DND, { g_print ("idroptarget_queryinterface %p ", This); PRINT_GUID (riid); }); *ppvObject = NULL; if (IsEqualGUID (riid, &IID_IUnknown)) { GDK_NOTE (DND, g_print ("...IUnknown S_OK\n")); idroptarget_addref (This); *ppvObject = This; return S_OK; } else if (IsEqualGUID (riid, &IID_IDropTarget)) { GDK_NOTE (DND, g_print ("...IDropTarget S_OK\n")); idroptarget_addref (This); *ppvObject = This; return S_OK; } else { GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n")); return E_NOINTERFACE; } } static ULONG STDMETHODCALLTYPE idroptarget_release (LPDROPTARGET This) { target_drag_context *ctx = (target_drag_context *) This; int ref_count = --ctx->ref_count; GDK_NOTE (DND, g_print ("idroptarget_release %p %d\n", This, ref_count)); if (ref_count == 0) { g_object_unref (ctx->context); g_clear_object (&ctx->dest_window); g_free (This); } return ref_count; } static GdkDragAction get_suggested_action (DWORD grfKeyState) { GdkWin32Selection *sel_win32 = _gdk_win32_selection_get (); /* This is the yucky Windows standard: Force link action if both * Control and Alt are down, copy if Control is down alone, move if * Alt is down alone, or use default of move within the app or copy * when origin of the drag is in another app. */ if (grfKeyState & MK_CONTROL && grfKeyState & MK_SHIFT) return GDK_ACTION_LINK; /* Link action not supported */ else if (grfKeyState & MK_CONTROL) return GDK_ACTION_COPY; else if (grfKeyState & MK_ALT) return GDK_ACTION_MOVE; else if (sel_win32->dnd_source_state == GDK_WIN32_DND_DRAGGING) return GDK_ACTION_MOVE; else return GDK_ACTION_COPY; /* Any way to determine when to add in DROPEFFECT_SCROLL? */ } /* Process pending events -- we don't want to service non-GUI events * forever so do one iteration and then do more only if there’s a * pending GDK event. */ static void process_pending_events (GdkDisplay *display) { g_main_context_iteration (NULL, FALSE); while (_gdk_event_queue_find_first (display)) g_main_context_iteration (NULL, FALSE); } static DWORD drop_effect_for_action (GdkDragAction action) { switch (action) { case GDK_ACTION_MOVE: return DROPEFFECT_MOVE; case GDK_ACTION_LINK: return DROPEFFECT_LINK; case GDK_ACTION_COPY: return DROPEFFECT_COPY; default: return DROPEFFECT_NONE; } } static GdkDragAction action_for_drop_effect (DWORD effect) { switch (effect) { case DROPEFFECT_MOVE: return GDK_ACTION_MOVE; case DROPEFFECT_LINK: return GDK_ACTION_LINK; case DROPEFFECT_COPY: return GDK_ACTION_COPY; default: return 0; } } static void dnd_event_put (GdkEventType type, GdkDragContext *context, gint pt_x, gint pt_y, gboolean to_dest_window) { GdkEvent *e; e = gdk_event_new (type); if (to_dest_window) g_set_object (&e->dnd.window, context->dest_window); else g_set_object (&e->dnd.window, context->source_window); e->dnd.send_event = FALSE; g_set_object (&e->dnd.context, context); e->dnd.time = GDK_CURRENT_TIME; e->dnd.x_root = pt_x; e->dnd.y_root = pt_y; gdk_event_set_device (e, gdk_drag_context_get_device (context)); gdk_event_set_seat (e, gdk_device_get_seat (gdk_drag_context_get_device (context))); GDK_NOTE (EVENTS, _gdk_win32_print_event (e)); gdk_event_put (e); gdk_event_free (e); } static GdkContentFormats * query_targets (LPDATAOBJECT pDataObj, GArray *format_target_map) { IEnumFORMATETC *pfmt = NULL; FORMATETC fmt; GList *result = NULL; HRESULT hr; GdkContentFormatsBuilder *builder; GdkContentFormats *result_formats; GList *p; if ((LPDATAOBJECT) current_src_object == pDataObj) return gdk_content_formats_ref (current_src_object->context->formats); hr = IDataObject_EnumFormatEtc (pDataObj, DATADIR_GET, &pfmt); if (SUCCEEDED (hr)) hr = IEnumFORMATETC_Next (pfmt, 1, &fmt, NULL); while (SUCCEEDED (hr) && hr != S_FALSE) { gboolean is_predef; gchar *registered_name = _gdk_win32_get_clipboard_format_name (fmt.cfFormat, &is_predef); if (registered_name && is_predef) GDK_NOTE (DND, g_print ("supported built-in source format 0x%x %s\n", fmt.cfFormat, registered_name)); else if (registered_name) GDK_NOTE (DND, g_print ("supported source format 0x%x %s\n", fmt.cfFormat, registered_name)); else GDK_NOTE (DND, g_print ("supported unnamed? source format 0x%x\n", fmt.cfFormat)); g_free (registered_name); _gdk_win32_add_format_to_targets (fmt.cfFormat, format_target_map, &result); hr = IEnumFORMATETC_Next (pfmt, 1, &fmt, NULL); } if (pfmt) IEnumFORMATETC_Release (pfmt); builder = gdk_content_formats_builder_new (); for (p = g_list_reverse (result); p; p = p->next) gdk_content_formats_builder_add_mime_type (builder, (const gchar *) p->data); result_formats = gdk_content_formats_builder_free_to_formats (builder); g_list_free (result); return result_formats; } static void set_data_object (LPDATAOBJECT *location, LPDATAOBJECT data_object) { if (*location != NULL) IDataObject_Release (*location); *location = data_object; if (*location != NULL) IDataObject_AddRef (*location); } static HRESULT STDMETHODCALLTYPE idroptarget_dragenter (LPDROPTARGET This, LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) { target_drag_context *ctx = (target_drag_context *) This; GdkWin32Selection *sel_win32 = _gdk_win32_selection_get (); GdkDragContext *context; GdkWin32DragContext *context_win32; gint pt_x; gint pt_y; GDK_NOTE (DND, g_print ("idroptarget_dragenter %p @ %ld : %ld for dest window 0x%p S_OK\n", This, pt.x, pt.y, ctx->dest_window)); g_clear_object (&ctx->context); context = gdk_drag_context_new (gdk_window_get_display (ctx->dest_window), FALSE, /* OLE2 DnD does not allow us to get the source window, * but we *can* find it if it's ours. This is needed to * support DnD within the same widget, for example. */ /* FIXME: Root window used to be here instead of NULL. Find a substitute? */ (current_src_context && current_src_context->context) ? current_src_context->context->source_window : NULL, ctx->dest_window, GDK_ACTION_DEFAULT | GDK_ACTION_COPY | GDK_ACTION_MOVE, NULL, GDK_DRAG_PROTO_OLE2); context_win32 = GDK_WIN32_DRAG_CONTEXT (context); context->formats = query_targets (pDataObj, context_win32->droptarget_format_target_map); ctx->context = context; g_set_object (&sel_win32->target_drag_context, context); context->suggested_action = GDK_ACTION_MOVE; context->action = GDK_ACTION_MOVE; g_array_set_size (context_win32->droptarget_format_target_map, 0); ctx->context->suggested_action = get_suggested_action (grfKeyState); set_data_object (&sel_win32->dnd_data_object_target, pDataObj); pt_x = pt.x / context_win32->scale + _gdk_offset_x; pt_y = pt.y / context_win32->scale + _gdk_offset_y; dnd_event_put (GDK_DRAG_ENTER, ctx->context, pt_x, pt_y, TRUE); dnd_event_put (GDK_DRAG_MOTION, ctx->context, pt_x, pt_y, TRUE); context_win32->last_key_state = grfKeyState; context_win32->last_x = pt_x; context_win32->last_y = pt_y; process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context))); *pdwEffect = drop_effect_for_action (ctx->context->action); GDK_NOTE (DND, g_print ("idroptarget_dragenter returns with action %d and drop effect %lu\n", ctx->context->action, *pdwEffect)); return S_OK; } static HRESULT STDMETHODCALLTYPE idroptarget_dragover (LPDROPTARGET This, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) { target_drag_context *ctx = (target_drag_context *) This; GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (ctx->context); gint pt_x = pt.x / context_win32->scale + _gdk_offset_x; gint pt_y = pt.y / context_win32->scale + _gdk_offset_y; ctx->context->suggested_action = get_suggested_action (grfKeyState); GDK_NOTE (DND, g_print ("idroptarget_dragover %p @ %ld : %ld, suggests %d action S_OK\n", This, pt.x, pt.y, ctx->context->suggested_action)); if (pt_x != context_win32->last_x || pt_y != context_win32->last_y || grfKeyState != context_win32->last_key_state) { dnd_event_put (GDK_DRAG_MOTION, ctx->context, pt_x, pt_y, TRUE); context_win32->last_key_state = grfKeyState; context_win32->last_x = pt_x; context_win32->last_y = pt_y; } process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context))); *pdwEffect = drop_effect_for_action (ctx->context->action); GDK_NOTE (DND, g_print ("idroptarget_dragover returns with action %d and effect %lu\n", ctx->context->action, *pdwEffect)); return S_OK; } static HRESULT STDMETHODCALLTYPE idroptarget_dragleave (LPDROPTARGET This) { target_drag_context *ctx = (target_drag_context *) This; GdkWin32Selection *sel_win32 = _gdk_win32_selection_get (); GDK_NOTE (DND, g_print ("idroptarget_dragleave %p S_OK\n", This)); dnd_event_put (GDK_DRAG_LEAVE, ctx->context, 0, 0, TRUE); process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context))); g_clear_object (&sel_win32->target_drag_context); g_clear_object (&ctx->context); set_data_object (&sel_win32->dnd_data_object_target, NULL); return S_OK; } static HRESULT STDMETHODCALLTYPE idroptarget_drop (LPDROPTARGET This, LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) { target_drag_context *ctx = (target_drag_context *) This; GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (ctx->context); gint pt_x = pt.x / context_win32->scale + _gdk_offset_x; gint pt_y = pt.y / context_win32->scale + _gdk_offset_y; GdkWin32Selection *sel_win32 = _gdk_win32_selection_get (); GDK_NOTE (DND, g_print ("idroptarget_drop %p ", This)); if (pDataObj == NULL) { GDK_NOTE (DND, g_print ("E_POINTER\n")); g_clear_object (&ctx->context); return E_POINTER; } ctx->context->suggested_action = get_suggested_action (grfKeyState); dnd_event_put (GDK_DROP_START, ctx->context, pt_x, pt_y, TRUE); process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context))); /* Notify OLE of copy or move */ if (sel_win32->dnd_target_state != GDK_WIN32_DND_DROPPED) *pdwEffect = DROPEFFECT_NONE; else *pdwEffect = drop_effect_for_action (ctx->context->action); g_clear_object (&sel_win32->target_drag_context); g_clear_object (&ctx->context); set_data_object (&sel_win32->dnd_data_object_target, NULL); GDK_NOTE (DND, g_print ("drop S_OK with effect %lx\n", *pdwEffect)); return S_OK; } static ULONG STDMETHODCALLTYPE idropsource_addref (LPDROPSOURCE This) { source_drag_context *ctx = (source_drag_context *) This; int ref_count = ++ctx->ref_count; GDK_NOTE (DND, g_print ("idropsource_addref %p %d\n", This, ref_count)); return ref_count; } static HRESULT STDMETHODCALLTYPE idropsource_queryinterface (LPDROPSOURCE This, REFIID riid, LPVOID *ppvObject) { GDK_NOTE (DND, { g_print ("idropsource_queryinterface %p ", This); PRINT_GUID (riid); }); *ppvObject = NULL; if (IsEqualGUID (riid, &IID_IUnknown)) { GDK_NOTE (DND, g_print ("...IUnknown S_OK\n")); idropsource_addref (This); *ppvObject = This; return S_OK; } else if (IsEqualGUID (riid, &IID_IDropSource)) { GDK_NOTE (DND, g_print ("...IDropSource S_OK\n")); idropsource_addref (This); *ppvObject = This; return S_OK; } else { GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n")); return E_NOINTERFACE; } } static ULONG STDMETHODCALLTYPE idropsource_release (LPDROPSOURCE This) { source_drag_context *ctx = (source_drag_context *) This; int ref_count = --ctx->ref_count; GDK_NOTE (DND, g_print ("idropsource_release %p %d\n", This, ref_count)); if (ref_count == 0) { g_clear_object (&ctx->context); if (current_src_context == ctx) current_src_context = NULL; g_free (This); } return ref_count; } /* Emit GDK events for any changes in mouse events or control key * state since the last recorded state. Return true if any events * have been emitted and false otherwise. */ static gboolean send_change_events (GdkDragContext *context, DWORD key_state, gboolean esc_pressed) { GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context); POINT pt; POINT pt_client; gboolean changed = FALSE; HWND hwnd = GDK_WINDOW_HWND (context->source_window); LPARAM lparam; WPARAM wparam; gint pt_x; gint pt_y; if (!API_CALL (GetCursorPos, (&pt))) return FALSE; pt_client = pt; if (!API_CALL (ScreenToClient, (hwnd, &pt_client))) return FALSE; pt_x = pt.x / context_win32->scale + _gdk_offset_x; pt_y = pt.y / context_win32->scale + _gdk_offset_y; if (pt_x != context_win32->last_x || pt_y != context_win32->last_y || key_state != context_win32->last_key_state) { lparam = MAKELPARAM (pt_client.x, pt_client.y); wparam = key_state; if (pt_x != context_win32->last_x || pt_y != context_win32->last_y) { GDK_NOTE (DND, g_print ("Sending WM_MOUSEMOVE (%ld,%ld)\n", pt.x, pt.y)); SendMessage (hwnd, WM_MOUSEMOVE, wparam, lparam); } if ((key_state & MK_LBUTTON) != (context_win32->last_key_state & MK_LBUTTON)) { if (key_state & MK_LBUTTON) SendMessage (hwnd, WM_LBUTTONDOWN, wparam, lparam); else SendMessage (hwnd, WM_LBUTTONUP, wparam, lparam); } if ((key_state & MK_MBUTTON) != (context_win32->last_key_state & MK_MBUTTON)) { if (key_state & MK_MBUTTON) SendMessage (hwnd, WM_MBUTTONDOWN, wparam, lparam); else SendMessage (hwnd, WM_MBUTTONUP, wparam, lparam); } if ((key_state & MK_RBUTTON) != (context_win32->last_key_state & MK_RBUTTON)) { if (key_state & MK_RBUTTON) SendMessage (hwnd, WM_RBUTTONDOWN, wparam, lparam); else SendMessage (hwnd, WM_RBUTTONUP, wparam, lparam); } if ((key_state & MK_CONTROL) != (context_win32->last_key_state & MK_CONTROL)) { if (key_state & MK_CONTROL) SendMessage (hwnd, WM_KEYDOWN, VK_CONTROL, 0); else SendMessage (hwnd, WM_KEYUP, VK_CONTROL, 0); } if ((key_state & MK_SHIFT) != (context_win32->last_key_state & MK_SHIFT)) { if (key_state & MK_SHIFT) SendMessage (hwnd, WM_KEYDOWN, VK_SHIFT, 0); else SendMessage (hwnd, WM_KEYUP, VK_SHIFT, 0); } changed = TRUE; context_win32->last_key_state = key_state; context_win32->last_x = pt_x; context_win32->last_y = pt_y; } if (esc_pressed) { GDK_NOTE (DND, g_print ("Sending a escape key down message to %p\n", hwnd)); SendMessage (hwnd, WM_KEYDOWN, VK_ESCAPE, 0); changed = TRUE; } return changed; } static HRESULT STDMETHODCALLTYPE idropsource_querycontinuedrag (LPDROPSOURCE This, BOOL fEscapePressed, DWORD grfKeyState) { source_drag_context *ctx = (source_drag_context *) This; GdkWin32Selection *sel_win32 = _gdk_win32_selection_get (); GDK_NOTE (DND, g_print ("idropsource_querycontinuedrag %p esc=%d keystate=0x%lx with state %d", This, fEscapePressed, grfKeyState, sel_win32->dnd_source_state)); if (send_change_events (ctx->context, grfKeyState, fEscapePressed)) process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context))); if (sel_win32->dnd_source_state == GDK_WIN32_DND_DROPPED) { GDK_NOTE (DND, g_print ("DRAGDROP_S_DROP\n")); return DRAGDROP_S_DROP; } else if (sel_win32->dnd_source_state == GDK_WIN32_DND_NONE) { GDK_NOTE (DND, g_print ("DRAGDROP_S_CANCEL\n")); return DRAGDROP_S_CANCEL; } else { GDK_NOTE (DND, g_print ("S_OK\n")); return S_OK; } } static HRESULT STDMETHODCALLTYPE idropsource_givefeedback (LPDROPSOURCE This, DWORD dwEffect) { source_drag_context *ctx = (source_drag_context *) This; GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (ctx->context); GdkDragAction suggested_action; GdkEvent *e; POINT pt; GDK_NOTE (DND, g_print ("idropsource_givefeedback %p with drop effect %lu S_OK\n", This, dwEffect)); if (!API_CALL (GetCursorPos, (&pt))) return S_OK; suggested_action = action_for_drop_effect (dwEffect); ctx->context->action = suggested_action; if (dwEffect == DROPEFFECT_NONE) g_clear_object (&ctx->context->dest_window); else if (ctx->context->dest_window == NULL) ctx->context->dest_window = NULL; /* FIXME: Root window was here originally. Find a substitute? */ context_win32->last_x = pt.x / context_win32->scale + _gdk_offset_x; context_win32->last_y = pt.y / context_win32->scale + _gdk_offset_y; e = gdk_event_new (GDK_DRAG_STATUS); g_set_object (&e->dnd.window, ctx->context->source_window); e->dnd.send_event = FALSE; g_set_object (&e->dnd.context, ctx->context); e->dnd.time = GDK_CURRENT_TIME; e->dnd.x_root = context_win32->last_x; e->dnd.y_root = context_win32->last_y; gdk_event_set_device (e, gdk_drag_context_get_device (ctx->context)); gdk_event_set_seat (e, gdk_device_get_seat (gdk_drag_context_get_device (ctx->context))); GDK_NOTE (EVENTS, _gdk_win32_print_event (e)); gdk_event_put (e); gdk_event_free (e); process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context))); GDK_NOTE (DND, g_print ("idropsource_givefeedback %p returns\n", This)); return S_OK; } static ULONG STDMETHODCALLTYPE idataobject_addref (LPDATAOBJECT This) { data_object *dobj = (data_object *) This; int ref_count = ++dobj->ref_count; GDK_NOTE (DND, g_print ("idataobject_addref %p %d\n", This, ref_count)); return ref_count; } static HRESULT STDMETHODCALLTYPE idataobject_queryinterface (LPDATAOBJECT This, REFIID riid, LPVOID *ppvObject) { GDK_NOTE (DND, { g_print ("idataobject_queryinterface %p ", This); PRINT_GUID (riid); }); *ppvObject = NULL; if (IsEqualGUID (riid, &IID_IUnknown)) { GDK_NOTE (DND, g_print ("...IUnknown S_OK\n")); idataobject_addref (This); *ppvObject = This; return S_OK; } else if (IsEqualGUID (riid, &IID_IDataObject)) { GDK_NOTE (DND, g_print ("...IDataObject S_OK\n")); idataobject_addref (This); *ppvObject = This; return S_OK; } else { GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n")); return E_NOINTERFACE; } } static ULONG STDMETHODCALLTYPE idataobject_release (LPDATAOBJECT This) { data_object *dobj = (data_object *) This; int ref_count = --dobj->ref_count; GDK_NOTE (DND, g_print ("idataobject_release %p %d\n", This, ref_count)); if (ref_count == 0) { g_array_free (dobj->formats, TRUE); g_free (This); } return ref_count; } static HRESULT query (LPDATAOBJECT This, LPFORMATETC pFormatEtc) { data_object *ctx = (data_object *) This; gint i; if (!pFormatEtc) return DV_E_FORMATETC; if (pFormatEtc->lindex != -1) return DV_E_LINDEX; if ((pFormatEtc->tymed & TYMED_HGLOBAL) == 0) return DV_E_TYMED; if ((pFormatEtc->dwAspect & DVASPECT_CONTENT) == 0) return DV_E_DVASPECT; for (i = 0; i < ctx->formats->len; i++) if (pFormatEtc->cfFormat == g_array_index (ctx->formats, GdkSelTargetFormat, i).format) return S_OK; return DV_E_FORMATETC; } static HRESULT STDMETHODCALLTYPE idataobject_getdata (LPDATAOBJECT This, LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium) { data_object *ctx = (data_object *) This; GdkWin32Selection *win32_sel = _gdk_win32_selection_get (); HRESULT hr; GdkEvent e; gint i; GdkAtom target; gint64 loopend; GDK_NOTE (DND, g_print ("idataobject_getdata %p %s ", This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat))); /* Check whether we can provide requested format */ hr = query (This, pFormatEtc); if (hr != S_OK) { GDK_NOTE (DND, g_print ("Unsupported format, returning 0x%lx\n", hr)); return hr; } /* Append a GDK_SELECTION_REQUEST event and then hope the app sets the * property associated with the _gdk_ole2_dnd atom */ win32_sel->property_change_format = pFormatEtc->cfFormat; win32_sel->property_change_data = pMedium; for (i = 0, target = NULL; i < ctx->formats->len; i++) { GdkSelTargetFormat *frec = &g_array_index (ctx->formats, GdkSelTargetFormat, i); if (frec->format == pFormatEtc->cfFormat) { target = frec->target; win32_sel->property_change_transmute = frec->transmute; win32_sel->property_change_target_atom = frec->target; } } if (target == NULL) { GDK_NOTE (EVENTS, g_print ("(target not found)")); return E_UNEXPECTED; } GDK_NOTE (DND, { const char *target_name = (const char *)target; g_print ("idataobject_getdata will request target 0x%p (%s) ", target, target_name); }); memset (&e, 0, sizeof (GdkEvent)); e.type = GDK_SELECTION_REQUEST; g_set_object (&e.selection.window, ctx->context->source_window); e.selection.send_event = FALSE; /* ??? */ /* Both selection and property are OLE2_DND, because change_property() * will only get the property and not the selection. Theoretically we * could use two different atoms (SELECTION_OLE2_DND and PROPERTY_OLE2_DND), * but there's little reason to do so. */ e.selection.selection = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND); e.selection.target = target; /* Requestor here is fake, just to allow the event to be processed */ g_set_object (&e.selection.requestor, ctx->context->source_window); e.selection.property = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND); e.selection.time = GDK_CURRENT_TIME; GDK_NOTE (EVENTS, _gdk_win32_print_event (&e)); gdk_event_put (&e); /* Don't hold up longer than one second */ loopend = g_get_monotonic_time () + 1000000000; while (win32_sel->property_change_data != 0 && g_get_monotonic_time () < loopend) process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context))); if (pMedium->hGlobal == NULL) { GDK_NOTE (DND, g_print (" E_UNEXPECTED\n")); return E_UNEXPECTED; } GDK_NOTE (DND, g_print (" S_OK\n")); return S_OK; } static HRESULT STDMETHODCALLTYPE idataobject_getdatahere (LPDATAOBJECT This, LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium) { GDK_NOTE (DND, g_print ("idataobject_getdatahere %p %s E_UNEXPECTED\n", This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat))); return E_UNEXPECTED; } static HRESULT STDMETHODCALLTYPE idataobject_querygetdata (LPDATAOBJECT This, LPFORMATETC pFormatEtc) { HRESULT hr; hr = query (This, pFormatEtc); #define CASE(x) case x: g_print (#x "\n"); break GDK_NOTE (DND, { g_print ("idataobject_querygetdata %p %s ", This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat)); switch (hr) { CASE (DV_E_FORMATETC); CASE (DV_E_LINDEX); CASE (DV_E_TYMED); CASE (DV_E_DVASPECT); CASE (S_OK); default: g_print ("%#lx", hr); } }); return hr; } static HRESULT STDMETHODCALLTYPE idataobject_getcanonicalformatetc (LPDATAOBJECT This, LPFORMATETC pFormatEtcIn, LPFORMATETC pFormatEtcOut) { GDK_NOTE (DND, g_print ("idataobject_getcanonicalformatetc %p E_UNEXPECTED\n", This)); return E_UNEXPECTED; } static HRESULT STDMETHODCALLTYPE idataobject_setdata (LPDATAOBJECT This, LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium, BOOL fRelease) { GDK_NOTE (DND, g_print ("idataobject_setdata %p %s E_UNEXPECTED\n", This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat))); return E_UNEXPECTED; } static HRESULT STDMETHODCALLTYPE idataobject_enumformatetc (LPDATAOBJECT This, DWORD dwDirection, LPENUMFORMATETC *ppEnumFormatEtc) { GDK_NOTE (DND, g_print ("idataobject_enumformatetc %p ", This)); if (dwDirection != DATADIR_GET) { GDK_NOTE (DND, g_print ("E_NOTIMPL\n")); return E_NOTIMPL; } *ppEnumFormatEtc = &enum_formats_new ((data_object *) This)->ief; GDK_NOTE (DND, g_print (" %p S_OK\n", *ppEnumFormatEtc)); return S_OK; } static HRESULT STDMETHODCALLTYPE idataobject_dadvise (LPDATAOBJECT This, LPFORMATETC pFormatetc, DWORD advf, LPADVISESINK pAdvSink, DWORD *pdwConnection) { GDK_NOTE (DND, g_print ("idataobject_dadvise %p E_NOTIMPL\n", This)); return E_NOTIMPL; } static HRESULT STDMETHODCALLTYPE idataobject_dunadvise (LPDATAOBJECT This, DWORD dwConnection) { GDK_NOTE (DND, g_print ("idataobject_dunadvise %p E_NOTIMPL\n", This)); return E_NOTIMPL; } static HRESULT STDMETHODCALLTYPE idataobject_enumdadvise (LPDATAOBJECT This, LPENUMSTATDATA *ppenumAdvise) { GDK_NOTE (DND, g_print ("idataobject_enumdadvise %p OLE_E_ADVISENOTSUPPORTED\n", This)); return OLE_E_ADVISENOTSUPPORTED; } static ULONG STDMETHODCALLTYPE ienumformatetc_addref (LPENUMFORMATETC This) { enum_formats *en = (enum_formats *) This; int ref_count = ++en->ref_count; GDK_NOTE (DND, g_print ("ienumformatetc_addref %p %d\n", This, ref_count)); return ref_count; } static HRESULT STDMETHODCALLTYPE ienumformatetc_queryinterface (LPENUMFORMATETC This, REFIID riid, LPVOID *ppvObject) { GDK_NOTE (DND, { g_print ("ienumformatetc_queryinterface %p", This); PRINT_GUID (riid); }); *ppvObject = NULL; if (IsEqualGUID (riid, &IID_IUnknown)) { GDK_NOTE (DND, g_print ("...IUnknown S_OK\n")); ienumformatetc_addref (This); *ppvObject = This; return S_OK; } else if (IsEqualGUID (riid, &IID_IEnumFORMATETC)) { GDK_NOTE (DND, g_print ("...IEnumFORMATETC S_OK\n")); ienumformatetc_addref (This); *ppvObject = This; return S_OK; } else { GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n")); return E_NOINTERFACE; } } static ULONG STDMETHODCALLTYPE ienumformatetc_release (LPENUMFORMATETC This) { enum_formats *en = (enum_formats *) This; int ref_count = --en->ref_count; GDK_NOTE (DND, g_print ("ienumformatetc_release %p %d\n", This, ref_count)); if (ref_count == 0) { idataobject_release ((LPDATAOBJECT) en->dataobj); g_free (This); } return ref_count; } static HRESULT STDMETHODCALLTYPE ienumformatetc_next (LPENUMFORMATETC This, ULONG celt, LPFORMATETC elts, ULONG *nelt) { enum_formats *en = (enum_formats *) This; ULONG i, n; ULONG formats_to_get = celt; GDK_NOTE (DND, g_print ("ienumformatetc_next %p %d %ld ", This, en->ix, celt)); n = 0; for (i = 0; i < formats_to_get; i++) { UINT fmt; if (en->ix >= en->dataobj->formats->len) break; fmt = g_array_index (en->dataobj->formats, GdkSelTargetFormat, en->ix++).format; /* skip internals */ if (fmt == 0 || fmt > 0xFFFF) { formats_to_get += 1; continue; } elts[n].cfFormat = fmt; elts[n].ptd = NULL; elts[n].dwAspect = DVASPECT_CONTENT; elts[n].lindex = -1; elts[n].tymed = TYMED_HGLOBAL; n++; } if (nelt != NULL) *nelt = n; GDK_NOTE (DND, g_print ("%s\n", (n == celt) ? "S_OK" : "S_FALSE")); if (n == celt) return S_OK; else return S_FALSE; } static HRESULT STDMETHODCALLTYPE ienumformatetc_skip (LPENUMFORMATETC This, ULONG celt) { enum_formats *en = (enum_formats *) This; GDK_NOTE (DND, g_print ("ienumformatetc_skip %p %d %ld S_OK\n", This, en->ix, celt)); en->ix += celt; return S_OK; } static HRESULT STDMETHODCALLTYPE ienumformatetc_reset (LPENUMFORMATETC This) { enum_formats *en = (enum_formats *) This; GDK_NOTE (DND, g_print ("ienumformatetc_reset %p S_OK\n", This)); en->ix = 0; return S_OK; } static HRESULT STDMETHODCALLTYPE ienumformatetc_clone (LPENUMFORMATETC This, LPENUMFORMATETC *ppEnumFormatEtc) { enum_formats *en = (enum_formats *) This; enum_formats *new; GDK_NOTE (DND, g_print ("ienumformatetc_clone %p S_OK\n", This)); new = enum_formats_new (en->dataobj); 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 (GdkWindow *window) { target_drag_context *result; result = g_new0 (target_drag_context, 1); result->idt.lpVtbl = &idt_vtbl; result->ref_count = 0; result->dest_window = g_object_ref (window); idroptarget_addref (&result->idt); GDK_NOTE (DND, g_print ("target_context_new: %p (window %p)\n", result, result->dest_window)); return result; } static source_drag_context * source_context_new (GdkDragContext *context, GdkWindow *window, GdkContentFormats *formats) { GdkWin32DragContext *context_win32; source_drag_context *result; context_win32 = GDK_WIN32_DRAG_CONTEXT (context); result = g_new0 (source_drag_context, 1); result->context = g_object_ref (context); result->ids.lpVtbl = &ids_vtbl; result->ref_count = 0; idropsource_addref (&result->ids); GDK_NOTE (DND, g_print ("source_context_new: %p (drag context %p)\n", result, result->context)); if (current_src_context == NULL) current_src_context = result; return result; } static data_object * data_object_new (GdkDragContext *context) { data_object *result; GList *p; const char * const *mime_types; gsize n_mime_types, i; result = g_new0 (data_object, 1); result->ido.lpVtbl = &ido_vtbl; result->ref_count = 1; result->context = context; result->formats = g_array_new (FALSE, FALSE, sizeof (GdkSelTargetFormat)); mime_types = gdk_content_formats_get_mime_types (context->formats, &n_mime_types); for (i = 0; i < n_mime_types; i++) { gint added_count = 0; gint j; GDK_NOTE (DND, g_print ("DataObject supports target 0x%p\n", mime_types[i])); added_count = _gdk_win32_add_target_to_selformats (mime_types[i], result->formats); for (j = 0; j < added_count && result->formats->len - 1 - j >= 0; j++) GDK_NOTE (DND, g_print ("DataObject will support format 0x%x\n", g_array_index (result->formats, GdkSelTargetFormat, j).format)); } GDK_NOTE (DND, g_print ("data_object_new: %p\n", result)); return result; } static enum_formats * enum_formats_new (data_object *dataobj) { enum_formats *result; result = g_new0 (enum_formats, 1); result->ief.lpVtbl = &ief_vtbl; result->ref_count = 1; result->ix = 0; result->dataobj = dataobj; idataobject_addref ((LPDATAOBJECT) dataobj); return result; } /* From MS Knowledge Base article Q130698 */ static gboolean resolve_link (HWND hWnd, wchar_t *link, gchar **lpszPath) { WIN32_FILE_ATTRIBUTE_DATA wfad; HRESULT hr; IShellLinkW *pslW = NULL; IPersistFile *ppf = NULL; /* Check if the file is empty first because IShellLink::Resolve for * some reason succeeds with an empty file and returns an empty * "link target". (#524151) */ if (!GetFileAttributesExW (link, GetFileExInfoStandard, &wfad) || (wfad.nFileSizeHigh == 0 && wfad.nFileSizeLow == 0)) return FALSE; /* Assume failure to start with: */ *lpszPath = 0; /* Call CoCreateInstance to obtain the IShellLink interface * pointer. This call fails if CoInitialize is not called, so it is * assumed that CoInitialize has been called. */ hr = CoCreateInstance (&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID *)&pslW); if (SUCCEEDED (hr)) { /* The IShellLink interface supports the IPersistFile * interface. Get an interface pointer to it. */ hr = pslW->lpVtbl->QueryInterface (pslW, &IID_IPersistFile, (LPVOID *) &ppf); } if (SUCCEEDED (hr)) { /* Load the file. */ hr = ppf->lpVtbl->Load (ppf, link, STGM_READ); } if (SUCCEEDED (hr)) { /* Resolve the link by calling the Resolve() * interface function. */ hr = pslW->lpVtbl->Resolve (pslW, hWnd, SLR_ANY_MATCH | SLR_NO_UI); } if (SUCCEEDED (hr)) { wchar_t wtarget[MAX_PATH]; hr = pslW->lpVtbl->GetPath (pslW, wtarget, MAX_PATH, NULL, 0); if (SUCCEEDED (hr)) *lpszPath = g_utf16_to_utf8 (wtarget, -1, NULL, NULL, NULL); } if (ppf) ppf->lpVtbl->Release (ppf); if (pslW) pslW->lpVtbl->Release (pslW); return SUCCEEDED (hr); } #if 0 /* Check for filenames like C:\Users\tml\AppData\Local\Temp\d5qtkvvs.bmp */ static gboolean filename_looks_tempish (const char *filename) { char *dirname; char *p; const char *q; gboolean retval = FALSE; dirname = g_path_get_dirname (filename); p = dirname; q = g_get_tmp_dir (); while (*p && *q && ((G_IS_DIR_SEPARATOR (*p) && G_IS_DIR_SEPARATOR (*q)) || g_ascii_tolower (*p) == g_ascii_tolower (*q))) p++, q++; if (!*p && !*q) retval = TRUE; g_free (dirname); return retval; } static gboolean close_it (gpointer data) { close (GPOINTER_TO_INT (data)); return FALSE; } #endif static GdkFilterReturn gdk_dropfiles_filter (GdkXEvent *xev, GdkEvent *event, gpointer data) { GdkDragContext *context; GdkWin32DragContext *context_win32; GString *result; MSG *msg = (MSG *) xev; HANDLE hdrop; POINT pt; gint nfiles, i; gchar *fileName, *linkedFile; GPtrArray *formats; formats = g_ptr_array_new (); if (msg->message == WM_DROPFILES) { GDK_NOTE (DND, g_print ("WM_DROPFILES: %p\n", msg->hwnd)); context = gdk_drag_context_new (gdk_window_get_display (event->any.window), FALSE, NULL, event->any.window, GDK_ACTION_COPY, NULL, GDK_DRAG_PROTO_WIN32_DROPFILES); context_win32 = GDK_WIN32_DRAG_CONTEXT (context); /* WM_DROPFILES drops are always file names */ context->formats = gdk_content_formats_new ((const char *[2]) { "text/uri-list", NULL }, 1); context->suggested_action = GDK_ACTION_COPY; current_dest_drag = context; event->dnd.type = GDK_DROP_START; event->dnd.context = current_dest_drag; gdk_event_set_device (event, gdk_drag_context_get_device (current_dest_drag)); gdk_event_set_seat (event, gdk_device_get_seat (gdk_drag_context_get_device (current_dest_drag))); hdrop = (HANDLE) msg->wParam; DragQueryPoint (hdrop, &pt); ClientToScreen (msg->hwnd, &pt); event->dnd.x_root = pt.x / context_win32->scale + _gdk_offset_x; event->dnd.y_root = pt.y / context_win32->scale + _gdk_offset_y; event->dnd.time = _gdk_win32_get_next_tick (msg->time); nfiles = DragQueryFile (hdrop, 0xFFFFFFFF, NULL, 0); result = g_string_new (NULL); for (i = 0; i < nfiles; i++) { gchar *uri; wchar_t wfn[MAX_PATH]; DragQueryFileW (hdrop, i, wfn, MAX_PATH); fileName = g_utf16_to_utf8 (wfn, -1, NULL, NULL, NULL); /* Resolve shortcuts */ if (resolve_link (msg->hwnd, wfn, &linkedFile)) { uri = g_filename_to_uri (linkedFile, NULL, NULL); if (uri != NULL) { g_string_append (result, uri); GDK_NOTE (DND, g_print ("... %s link to %s: %s\n", fileName, linkedFile, uri)); g_free (uri); } g_free (fileName); fileName = linkedFile; } else { uri = g_filename_to_uri (fileName, NULL, NULL); if (uri != NULL) { g_string_append (result, uri); GDK_NOTE (DND, g_print ("... %s: %s\n", fileName, uri)); g_free (uri); } } #if 0 /* Awful hack to recognize temp files corresponding to * images dragged from Firefox... Open the file right here * so that it is less likely that Firefox manages to delete * it before the GTK+-using app (typically GIMP) has opened * it. * * Not compiled in for now, because it means images dragged * from Firefox would stay around in the temp folder which * is not what Firefox intended. I don't feel comfortable * with that, both from a geenral sanity point of view, and * from a privacy point of view. It's better to wait for * Firefox to fix the problem, for instance by deleting the * temp file after a longer delay, or to wait until we * implement the OLE2_DND... */ if (filename_looks_tempish (fileName)) { int fd = g_open (fileName, _O_RDONLY|_O_BINARY, 0); if (fd == -1) { GDK_NOTE (DND, g_print ("Could not open %s, maybe an image dragged from Firefox that it already deleted\n", fileName)); } else { GDK_NOTE (DND, g_print ("Opened %s as %d so that Firefox won't delete it\n", fileName, fd)); g_timeout_add_seconds (1, close_it, GINT_TO_POINTER (fd)); } } #endif g_free (fileName); g_string_append (result, "\015\012"); } _gdk_dropfiles_store (result->str); g_string_free (result, FALSE); DragFinish (hdrop); return GDK_FILTER_TRANSLATE; } else return GDK_FILTER_CONTINUE; } void _gdk_dnd_init (void) { CoInitializeEx (NULL, COINIT_APARTMENTTHREADED); if (getenv ("GDK_WIN32_USE_EXPERIMENTAL_OLE2_DND")) use_ole2_dnd = TRUE; if (use_ole2_dnd) { HRESULT hr; hr = OleInitialize (NULL); if (! SUCCEEDED (hr)) g_error ("OleInitialize failed"); target_ctx_for_window = g_hash_table_new (g_direct_hash, g_direct_equal); } } void _gdk_win32_dnd_exit (void) { if (use_ole2_dnd) { OleUninitialize (); } CoUninitialize (); } /* Source side */ static void local_send_leave (GdkDragContext *context, guint32 time) { GdkEvent *tmp_event; GDK_NOTE (DND, g_print ("local_send_leave: context=%p current_dest_drag=%p\n", context, current_dest_drag)); if ((current_dest_drag != NULL) && (current_dest_drag->protocol == GDK_DRAG_PROTO_LOCAL) && (current_dest_drag->source_window == context->source_window)) { tmp_event = gdk_event_new (GDK_DRAG_LEAVE); g_set_object (&tmp_event->dnd.window, context->dest_window); /* Pass ownership of context to the event */ tmp_event->dnd.send_event = FALSE; g_set_object (&tmp_event->dnd.context, current_dest_drag); tmp_event->dnd.time = GDK_CURRENT_TIME; /* FIXME? */ gdk_event_set_device (tmp_event, gdk_drag_context_get_device (context)); gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (context))); current_dest_drag = NULL; GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event)); gdk_event_put (tmp_event); gdk_event_free (tmp_event); } } static void local_send_enter (GdkDragContext *context, guint32 time) { GdkEvent *tmp_event; GdkDragContext *new_context; GDK_NOTE (DND, g_print ("local_send_enter: context=%p current_dest_drag=%p\n", context, current_dest_drag)); if (current_dest_drag != NULL) { g_object_unref (G_OBJECT (current_dest_drag)); current_dest_drag = NULL; } new_context = gdk_drag_context_new (gdk_window_get_display (context->source_window), FALSE, context->source_window, context->dest_window, context->actions, NULL, GDK_DRAG_PROTO_LOCAL); new_context->formats = gdk_content_formats_ref (context->formats); gdk_window_set_events (new_context->source_window, gdk_window_get_events (new_context->source_window) | GDK_PROPERTY_CHANGE_MASK); tmp_event = gdk_event_new (GDK_DRAG_ENTER); g_set_object (&tmp_event->dnd.window, context->dest_window); tmp_event->dnd.send_event = FALSE; g_set_object (&tmp_event->dnd.context, new_context); tmp_event->dnd.time = GDK_CURRENT_TIME; /* FIXME? */ gdk_event_set_device (tmp_event, gdk_drag_context_get_device (context)); gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (context))); current_dest_drag = new_context; GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event)); gdk_event_put (tmp_event); gdk_event_free (tmp_event); } static void local_send_motion (GdkDragContext *context, gint x_root, gint y_root, GdkDragAction action, guint32 time) { GdkEvent *tmp_event; GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context); GDK_NOTE (DND, g_print ("local_send_motion: context=%p (%d,%d) current_dest_drag=%p\n", context, x_root, y_root, current_dest_drag)); if ((current_dest_drag != NULL) && (current_dest_drag->protocol == GDK_DRAG_PROTO_LOCAL) && (current_dest_drag->source_window == context->source_window)) { GdkWin32DragContext *current_dest_drag_win32; tmp_event = gdk_event_new (GDK_DRAG_MOTION); g_set_object (&tmp_event->dnd.window, current_dest_drag->dest_window); tmp_event->dnd.send_event = FALSE; g_set_object (&tmp_event->dnd.context, current_dest_drag); tmp_event->dnd.time = time; gdk_event_set_device (tmp_event, gdk_drag_context_get_device (current_dest_drag)); gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (current_dest_drag))); current_dest_drag->suggested_action = action; tmp_event->dnd.x_root = x_root; tmp_event->dnd.y_root = y_root; current_dest_drag_win32 = GDK_WIN32_DRAG_CONTEXT (current_dest_drag); current_dest_drag_win32->last_x = x_root; current_dest_drag_win32->last_y = y_root; context_win32->drag_status = GDK_DRAG_STATUS_MOTION_WAIT; GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event)); gdk_event_put (tmp_event); gdk_event_free (tmp_event); } } static void local_send_drop (GdkDragContext *context, guint32 time) { GdkEvent *tmp_event; GDK_NOTE (DND, g_print ("local_send_drop: context=%p current_dest_drag=%p\n", context, current_dest_drag)); if ((current_dest_drag != NULL) && (current_dest_drag->protocol == GDK_DRAG_PROTO_LOCAL) && (current_dest_drag->source_window == context->source_window)) { GdkWin32DragContext *context_win32; /* Pass ownership of context to the event */ tmp_event = gdk_event_new (GDK_DROP_START); g_set_object (&tmp_event->dnd.window, current_dest_drag->dest_window); tmp_event->dnd.send_event = FALSE; g_set_object (&tmp_event->dnd.context, current_dest_drag); tmp_event->dnd.time = GDK_CURRENT_TIME; gdk_event_set_device (tmp_event, gdk_drag_context_get_device (current_dest_drag)); gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (current_dest_drag))); context_win32 = GDK_WIN32_DRAG_CONTEXT (current_dest_drag); tmp_event->dnd.x_root = context_win32->last_x; tmp_event->dnd.y_root = context_win32->last_y; current_dest_drag = NULL; GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event)); gdk_event_put (tmp_event); gdk_event_free (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")); if (!use_ole2_dnd) { if (context->protocol == GDK_DRAG_PROTO_LOCAL) local_send_leave (context, time); } g_clear_object (&context->dest_window); } } static GdkWindow * create_drag_window (GdkDisplay *display) { GdkWindow *window; window = gdk_window_new_popup (display, &(GdkRectangle) { 0, 0, 100, 100 }); gdk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DND); return window; } GdkDragContext * _gdk_win32_window_drag_begin (GdkWindow *window, GdkDevice *device, GdkContentFormats *formats, GdkDragAction actions, gint dx, gint dy) { GdkDragContext *context; GdkWin32DragContext *context_win32; BYTE kbd_state[256]; GdkWin32Selection *sel_win32 = _gdk_win32_selection_get (); int x_root, y_root; g_return_val_if_fail (window != NULL, NULL); if (use_ole2_dnd) g_assert (pending_src_context == NULL); context = gdk_drag_context_new (gdk_window_get_display (window), TRUE, window, NULL, actions, device, use_ole2_dnd ? GDK_DRAG_PROTO_OLE2 : GDK_DRAG_PROTO_LOCAL); context->formats = gdk_content_formats_ref (formats); context_win32 = GDK_WIN32_DRAG_CONTEXT (context); GDK_NOTE (DND, g_print ("gdk_drag_begin\n")); gdk_device_get_position (device, &x_root, &y_root); x_root += dx; y_root += dy; context_win32->start_x = x_root; context_win32->start_y = y_root; context_win32->last_x = context_win32->start_x; context_win32->last_y = context_win32->start_y; g_set_object (&context_win32->ipc_window, window); context_win32->last_key_state = 0; API_CALL (GetKeyboardState, (kbd_state)); if (kbd_state[VK_CONTROL] & 0x80) context_win32->last_key_state |= MK_CONTROL; if (kbd_state[VK_SHIFT] & 0x80) context_win32->last_key_state |= MK_SHIFT; if (kbd_state[VK_LBUTTON] & 0x80) context_win32->last_key_state |= MK_LBUTTON; if (kbd_state[VK_MBUTTON] & 0x80) context_win32->last_key_state |= MK_MBUTTON; if (kbd_state[VK_RBUTTON] & 0x80) context_win32->last_key_state |= MK_RBUTTON; context_win32->drag_window = create_drag_window (gdk_window_get_display (window)); if (!drag_context_grab (context)) { g_object_unref (context); return FALSE; } if (use_ole2_dnd) { pending_src_context = source_context_new (context, window, formats); sel_win32->dnd_source_state = GDK_WIN32_DND_PENDING; } move_drag_window (context, x_root, y_root); return context; } void _gdk_win32_dnd_do_dragdrop (void) { GdkDragContext* drag_ctx; data_object *dobj; HRESULT hr; DWORD dwEffect; if (!use_ole2_dnd) return; if (pending_src_context == NULL) return; drag_ctx = pending_src_context->context; dobj = data_object_new (drag_ctx); current_src_object = dobj; /* Start dragging with mainloop inside the OLE2 API. Exits only when done */ GDK_NOTE (DND, g_print ("Calling DoDragDrop\n")); _gdk_win32_begin_modal_call (GDK_WIN32_MODAL_OP_DND); hr = DoDragDrop (&dobj->ido, &pending_src_context->ids, DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect); _gdk_win32_end_modal_call (GDK_WIN32_MODAL_OP_DND); GDK_NOTE (DND, g_print ("DoDragDrop returned %s\n", (hr == DRAGDROP_S_DROP ? "DRAGDROP_S_DROP" : (hr == DRAGDROP_S_CANCEL ? "DRAGDROP_S_CANCEL" : (hr == E_UNEXPECTED ? "E_UNEXPECTED" : g_strdup_printf ("%#.8lx", hr)))))); /* Delete dnd selection after successful move */ if (hr == DRAGDROP_S_DROP && dwEffect == DROPEFFECT_MOVE) { GdkWin32Selection *win32_sel = _gdk_win32_selection_get (); GdkEvent tmp_event; memset (&tmp_event, 0, sizeof (tmp_event)); tmp_event.type = GDK_SELECTION_REQUEST; g_set_object (&tmp_event.selection.window, drag_ctx->source_window); tmp_event.selection.send_event = FALSE; tmp_event.selection.selection = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND); tmp_event.selection.target = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_DELETE); win32_sel->property_change_target_atom = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_DELETE); tmp_event.selection.property = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND); g_set_object (&tmp_event.selection.requestor, drag_ctx->source_window); tmp_event.selection.time = GDK_CURRENT_TIME; /* ??? */ GDK_NOTE (EVENTS, _gdk_win32_print_event (&tmp_event)); gdk_event_put (&tmp_event); } { GdkEvent *tmp_event; tmp_event = gdk_event_new (GDK_DROP_FINISHED); g_set_object (&tmp_event->dnd.window, drag_ctx->source_window); tmp_event->dnd.send_event = FALSE; g_set_object (&tmp_event->dnd.context, drag_ctx); gdk_event_set_device (tmp_event, gdk_drag_context_get_device (drag_ctx)); gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (drag_ctx))); GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event)); gdk_event_put (tmp_event); gdk_event_free (tmp_event); } current_src_object = NULL; dobj->ido.lpVtbl->Release (&dobj->ido); if (pending_src_context != NULL) { pending_src_context->ids.lpVtbl->Release (&pending_src_context->ids); pending_src_context = NULL; } } typedef struct { gint x; gint y; HWND ignore; HWND result; } find_window_enum_arg; static BOOL CALLBACK find_window_enum_proc (HWND hwnd, LPARAM lparam) { RECT rect; POINT tl, br; find_window_enum_arg *a = (find_window_enum_arg *) lparam; if (hwnd == a->ignore) return TRUE; if (!IsWindowVisible (hwnd)) return TRUE; tl.x = tl.y = 0; ClientToScreen (hwnd, &tl); GetClientRect (hwnd, &rect); br.x = rect.right; br.y = rect.bottom; ClientToScreen (hwnd, &br); if (a->x >= tl.x && a->y >= tl.y && a->x < br.x && a->y < br.y) { a->result = hwnd; return FALSE; } else return TRUE; } static GdkWindow * gdk_win32_drag_context_find_window (GdkDragContext *context, GdkWindow *drag_window, gint x_root, gint y_root, GdkDragProtocol *protocol) { GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context); GdkWindow *dest_window, *dw; find_window_enum_arg a; a.x = x_root * context_win32->scale - _gdk_offset_x; a.y = y_root * context_win32->scale - _gdk_offset_y; a.ignore = drag_window ? GDK_WINDOW_HWND (drag_window) : NULL; a.result = NULL; GDK_NOTE (DND, g_print ("gdk_drag_find_window_real: %p %+d%+d\n", (drag_window ? GDK_WINDOW_HWND (drag_window) : NULL), a.x, a.y)); EnumWindows (find_window_enum_proc, (LPARAM) &a); if (a.result == NULL) dest_window = NULL; else { dw = gdk_win32_handle_table_lookup (a.result); if (dw) { dest_window = gdk_window_get_toplevel (dw); g_object_ref (dest_window); } else dest_window = gdk_win32_window_foreign_new_for_display (context->display, a.result); if (use_ole2_dnd) *protocol = GDK_DRAG_PROTO_OLE2; else 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 %s\n", (drag_window ? GDK_WINDOW_HWND (drag_window) : NULL), x_root, y_root, a.result, (dest_window ? GDK_WINDOW_HWND (dest_window) : NULL), _gdk_win32_drag_protocol_to_string (*protocol))); return dest_window; } static gboolean gdk_win32_drag_context_drag_motion (GdkDragContext *context, GdkWindow *dest_window, GdkDragProtocol protocol, gint x_root, gint y_root, GdkDragAction suggested_action, GdkDragAction possible_actions, guint32 time) { GdkWin32DragContext *context_win32; g_return_val_if_fail (context != NULL, FALSE); context->actions = possible_actions; GDK_NOTE (DND, g_print ("gdk_drag_motion: @ %+d:%+d %s suggested=%s, possible=%s\n" " context=%p:{actions=%s,suggested=%s,action=%s}\n", x_root, y_root, _gdk_win32_drag_protocol_to_string (protocol), _gdk_win32_drag_action_to_string (suggested_action), _gdk_win32_drag_action_to_string (possible_actions), context, _gdk_win32_drag_action_to_string (context->actions), _gdk_win32_drag_action_to_string (context->suggested_action), _gdk_win32_drag_action_to_string (context->action))); context_win32 = GDK_WIN32_DRAG_CONTEXT (context); if (context_win32->drag_window) move_drag_window (context, x_root, y_root); if (!use_ole2_dnd) { if (context->dest_window == dest_window) { GdkDragContext *dest_context; dest_context = gdk_drag_context_find (FALSE, context->source_window, dest_window); if (dest_context) dest_context->actions = context->actions; context->suggested_action = suggested_action; } else { GdkEvent *tmp_event; /* Send a leave to the last destination */ gdk_drag_do_leave (context, time); context_win32->drag_status = GDK_DRAG_STATUS_DRAG; /* Check if new destination accepts drags, and which protocol */ if (dest_window) { g_set_object (&context->dest_window, 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 */ tmp_event = gdk_event_new (GDK_DRAG_STATUS); g_set_object (&tmp_event->dnd.window, context->source_window); /* We use this to signal a synthetic status. Perhaps * we should use an extra field... */ tmp_event->dnd.send_event = TRUE; g_set_object (&tmp_event->dnd.context, context); tmp_event->dnd.time = time; gdk_event_set_device (tmp_event, gdk_drag_context_get_device (context)); gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (context))); GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event)); gdk_event_put (tmp_event); gdk_event_free (tmp_event); } /* Send a drag-motion event */ context_win32->last_x = x_root; context_win32->last_y = y_root; if (context->dest_window) { if (context_win32->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 { GDK_NOTE (DND, g_print (" returning TRUE\n" " context=%p:{actions=%s,suggested=%s,action=%s}\n", context, _gdk_win32_drag_action_to_string (context->actions), _gdk_win32_drag_action_to_string (context->suggested_action), _gdk_win32_drag_action_to_string (context->action))); return TRUE; } } } GDK_NOTE (DND, g_print (" returning FALSE\n" " context=%p:{actions=%s,suggested=%s,action=%s}\n", context, _gdk_win32_drag_action_to_string (context->actions), _gdk_win32_drag_action_to_string (context->suggested_action), _gdk_win32_drag_action_to_string (context->action))); return FALSE; } static void gdk_win32_drag_context_drag_drop (GdkDragContext *context, guint32 time) { GdkWin32Selection *sel_win32 = _gdk_win32_selection_get (); g_return_if_fail (context != NULL); GDK_NOTE (DND, g_print ("gdk_drag_drop\n")); if (!use_ole2_dnd) { if (context->dest_window && context->protocol == GDK_DRAG_PROTO_LOCAL) local_send_drop (context, time); } else { sel_win32->dnd_source_state = GDK_WIN32_DND_DROPPED; } } static void gdk_win32_drag_context_drag_abort (GdkDragContext *context, guint32 time) { GdkWin32Selection *sel_win32 = _gdk_win32_selection_get (); g_return_if_fail (context != NULL); GDK_NOTE (DND, g_print ("gdk_drag_abort\n")); if (use_ole2_dnd) sel_win32->dnd_source_state = GDK_WIN32_DND_NONE; } /* Destination side */ static void gdk_win32_drag_context_drag_status (GdkDragContext *context, GdkDragAction action, guint32 time) { GdkDragContext *src_context; GdkEvent *tmp_event; g_return_if_fail (context != NULL); GDK_NOTE (DND, g_print ("gdk_drag_status: %s\n" " context=%p:{actions=%s,suggested=%s,action=%s}\n", _gdk_win32_drag_action_to_string (action), context, _gdk_win32_drag_action_to_string (context->actions), _gdk_win32_drag_action_to_string (context->suggested_action), _gdk_win32_drag_action_to_string (context->action))); context->action = action; if (!use_ole2_dnd) { src_context = gdk_drag_context_find (TRUE, context->source_window, context->dest_window); if (src_context) { GdkWin32DragContext *src_context_win32 = GDK_WIN32_DRAG_CONTEXT (src_context); if (src_context_win32->drag_status == GDK_DRAG_STATUS_MOTION_WAIT) src_context_win32->drag_status = GDK_DRAG_STATUS_DRAG; tmp_event = gdk_event_new (GDK_DRAG_STATUS); g_set_object (&tmp_event->dnd.window, context->source_window); tmp_event->dnd.send_event = FALSE; g_set_object (&tmp_event->dnd.context, src_context); tmp_event->dnd.time = GDK_CURRENT_TIME; /* FIXME? */ gdk_event_set_device (tmp_event, gdk_drag_context_get_device (src_context)); gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (src_context))); if (action == GDK_ACTION_DEFAULT) action = 0; src_context->action = action; GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event)); gdk_event_put (tmp_event); gdk_event_free (tmp_event); } } } static void gdk_win32_drag_context_drop_reply (GdkDragContext *context, gboolean ok, guint32 time) { g_return_if_fail (context != NULL); GDK_NOTE (DND, g_print ("gdk_drop_reply\n")); if (!use_ole2_dnd) if (context->dest_window) { if (context->protocol == GDK_DRAG_PROTO_WIN32_DROPFILES) _gdk_dropfiles_store (NULL); } } static void gdk_win32_drag_context_drop_finish (GdkDragContext *context, gboolean success, guint32 time) { GdkDragContext *src_context; GdkEvent *tmp_event; GdkWin32Selection *sel_win32 = _gdk_win32_selection_get (); g_return_if_fail (context != NULL); GDK_NOTE (DND, g_print ("gdk_drop_finish\n")); if (!use_ole2_dnd) { src_context = gdk_drag_context_find (TRUE, context->source_window, context->dest_window); if (src_context) { tmp_event = gdk_event_new (GDK_DROP_FINISHED); g_set_object (&tmp_event->dnd.window, src_context->source_window); tmp_event->dnd.send_event = FALSE; g_set_object (&tmp_event->dnd.context, src_context); gdk_event_set_device (tmp_event, gdk_drag_context_get_device (src_context)); gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (src_context))); GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event)); gdk_event_put (tmp_event); gdk_event_free (tmp_event); } } else { gdk_drag_do_leave (context, time); if (success) sel_win32->dnd_target_state = GDK_WIN32_DND_DROPPED; else sel_win32->dnd_target_state = GDK_WIN32_DND_FAILED; } } #if 0 static GdkFilterReturn gdk_destroy_filter (GdkXEvent *xev, GdkEvent *event, gpointer data) { MSG *msg = (MSG *) xev; if (msg->message == WM_DESTROY) { IDropTarget *idtp = (IDropTarget *) data; GDK_NOTE (DND, g_print ("gdk_destroy_filter: WM_DESTROY: %p\n", msg->hwnd)); #if 0 idtp->lpVtbl->Release (idtp); #endif RevokeDragDrop (msg->hwnd); CoLockObjectExternal ((IUnknown*) idtp, FALSE, TRUE); } return GDK_FILTER_CONTINUE; } #endif void _gdk_win32_window_register_dnd (GdkWindow *window) { target_drag_context *ctx; HRESULT hr; g_return_if_fail (window != NULL); if (g_object_get_data (G_OBJECT (window), "gdk-dnd-registered") != NULL) return; else g_object_set_data (G_OBJECT (window), "gdk-dnd-registered", GINT_TO_POINTER (TRUE)); GDK_NOTE (DND, g_print ("gdk_window_register_dnd: %p\n", GDK_WINDOW_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_window_add_filter (window, gdk_dropfiles_filter, NULL); DragAcceptFiles (GDK_WINDOW_HWND (window), TRUE); } else { /* Return if window is already setup for DND. */ if (g_hash_table_lookup (target_ctx_for_window, GDK_WINDOW_HWND (window)) != NULL) return; ctx = target_context_new (window); hr = CoLockObjectExternal ((IUnknown *) &ctx->idt, TRUE, FALSE); if (!SUCCEEDED (hr)) OTHER_API_FAILED ("CoLockObjectExternal"); else { hr = RegisterDragDrop (GDK_WINDOW_HWND (window), &ctx->idt); if (hr == DRAGDROP_E_ALREADYREGISTERED) { g_print ("DRAGDROP_E_ALREADYREGISTERED\n"); CoLockObjectExternal ((IUnknown *) &ctx->idt, FALSE, FALSE); } else if (!SUCCEEDED (hr)) OTHER_API_FAILED ("RegisterDragDrop"); else { g_object_ref (window); g_hash_table_insert (target_ctx_for_window, GDK_WINDOW_HWND (window), ctx); } } } } static gboolean gdk_win32_drag_context_drop_status (GdkDragContext *context) { GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context); return ! context_win32->drop_failed; } static GdkAtom gdk_win32_drag_context_get_selection (GdkDragContext *context) { switch (context->protocol) { case GDK_DRAG_PROTO_LOCAL: return _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_LOCAL_DND_SELECTION); case GDK_DRAG_PROTO_WIN32_DROPFILES: return _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_DROPFILES_DND); case GDK_DRAG_PROTO_OLE2: return _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND); default: return NULL; } } static void gdk_win32_drag_context_set_cursor (GdkDragContext *context, GdkCursor *cursor) { GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context); GDK_NOTE (DND, g_print ("gdk_drag_context_set_cursor: 0x%p 0x%p\n", context, cursor)); if (!g_set_object (&context_win32->cursor, cursor)) return; if (context_win32->grab_seat) { G_GNUC_BEGIN_IGNORE_DEPRECATIONS; gdk_device_grab (gdk_seat_get_pointer (context_win32->grab_seat), context_win32->ipc_window, GDK_OWNERSHIP_APPLICATION, FALSE, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, cursor, GDK_CURRENT_TIME); G_GNUC_END_IGNORE_DEPRECATIONS; } } static double ease_out_cubic (double t) { double p = t - 1; return p * p * p + 1; } #define ANIM_TIME 500000 /* half a second */ typedef struct _GdkDragAnim GdkDragAnim; struct _GdkDragAnim { GdkWin32DragContext *context; GdkFrameClock *frame_clock; gint64 start_time; }; static void gdk_drag_anim_destroy (GdkDragAnim *anim) { g_object_unref (anim->context); g_slice_free (GdkDragAnim, anim); } static gboolean gdk_drag_anim_timeout (gpointer data) { GdkDragAnim *anim = data; GdkWin32DragContext *context = anim->context; GdkFrameClock *frame_clock = anim->frame_clock; gint64 current_time; double f; double t; if (!frame_clock) return G_SOURCE_REMOVE; current_time = gdk_frame_clock_get_frame_time (frame_clock); f = (current_time - anim->start_time) / (double) ANIM_TIME; if (f >= 1.0) return G_SOURCE_REMOVE; t = ease_out_cubic (f); gdk_window_show (context->drag_window); gdk_window_move (context->drag_window, context->last_x + (context->start_x - context->last_x) * t - context->hot_x, context->last_y + (context->start_y - context->last_y) * t - context->hot_y); gdk_window_set_opacity (context->drag_window, 1.0 - f); return G_SOURCE_CONTINUE; } static void gdk_win32_drag_context_drop_done (GdkDragContext *context, gboolean success) { GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context); GdkDragAnim *anim; cairo_surface_t *win_surface; cairo_surface_t *surface; cairo_t *cr; guint id; GDK_NOTE (DND, g_print ("gdk_drag_context_drop_done: 0x%p %s\n", context, success ? "dropped successfully" : "dropped unsuccessfully")); /* FIXME: This is temporary, until the code is fixed to ensure that * gdk_drop_finish () is called by GTK. */ if (use_ole2_dnd) { GdkWin32Selection *sel_win32 = _gdk_win32_selection_get (); if (success) sel_win32->dnd_source_state = GDK_WIN32_DND_DROPPED; else sel_win32->dnd_source_state = GDK_WIN32_DND_NONE; } if (success) { gdk_window_hide (win32_context->drag_window); return; } win_surface = _gdk_window_ref_cairo_surface (win32_context->drag_window); surface = gdk_window_create_similar_surface (win32_context->drag_window, cairo_surface_get_content (win_surface), gdk_window_get_width (win32_context->drag_window), gdk_window_get_height (win32_context->drag_window)); cr = cairo_create (surface); cairo_set_source_surface (cr, win_surface, 0, 0); cairo_paint (cr); cairo_destroy (cr); cairo_surface_destroy (win_surface); /* pattern = cairo_pattern_create_for_surface (surface); gdk_window_set_background_pattern (win32_context->drag_window, pattern); cairo_pattern_destroy (pattern); */ cairo_surface_destroy (surface); anim = g_slice_new0 (GdkDragAnim); g_set_object (&anim->context, win32_context); anim->frame_clock = gdk_window_get_frame_clock (win32_context->drag_window); anim->start_time = gdk_frame_clock_get_frame_time (anim->frame_clock); id = g_timeout_add_full (G_PRIORITY_DEFAULT, 17, gdk_drag_anim_timeout, anim, (GDestroyNotify) gdk_drag_anim_destroy); g_source_set_name_by_id (id, "[gtk+] gdk_drag_anim_timeout"); } static gboolean drag_context_grab (GdkDragContext *context) { GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context); GdkSeatCapabilities capabilities; GdkSeat *seat; GdkCursor *cursor; if (!context_win32->ipc_window) return FALSE; seat = gdk_device_get_seat (gdk_drag_context_get_device (context)); capabilities = GDK_SEAT_CAPABILITY_ALL; cursor = gdk_drag_get_cursor (context, gdk_drag_context_get_selected_action (context)); g_set_object (&context_win32->cursor, cursor); if (gdk_seat_grab (seat, context_win32->ipc_window, capabilities, FALSE, context_win32->cursor, NULL, NULL, NULL) != GDK_GRAB_SUCCESS) return FALSE; g_set_object (&context_win32->grab_seat, seat); /* TODO: Should be grabbing keys here, to support keynav. SetWindowsHookEx()? */ return TRUE; } static void drag_context_ungrab (GdkDragContext *context) { GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context); if (!context_win32->grab_seat) return; gdk_seat_ungrab (context_win32->grab_seat); g_clear_object (&context_win32->grab_seat); /* TODO: Should be ungrabbing keys here */ } static void gdk_win32_drag_context_cancel (GdkDragContext *context, GdkDragCancelReason reason) { const gchar *reason_str = NULL; switch (reason) { case GDK_DRAG_CANCEL_NO_TARGET: reason_str = "no target"; break; case GDK_DRAG_CANCEL_USER_CANCELLED: reason_str = "user cancelled"; break; case GDK_DRAG_CANCEL_ERROR: reason_str = "error"; break; default: reason_str = ""; break; } GDK_NOTE (DND, g_print ("gdk_drag_context_cancel: 0x%p %s\n", context, reason_str)); drag_context_ungrab (context); gdk_drag_drop_done (context, FALSE); } static void gdk_win32_drag_context_drop_performed (GdkDragContext *context, guint32 time_) { GDK_NOTE (DND, g_print ("gdk_drag_context_drop_performed: 0x%p %u\n", context, time_)); gdk_drag_drop (context, time_); drag_context_ungrab (context); } #define BIG_STEP 20 #define SMALL_STEP 1 static void gdk_drag_get_current_actions (GdkModifierType state, gint button, GdkDragAction actions, GdkDragAction *suggested_action, GdkDragAction *possible_actions) { *suggested_action = 0; *possible_actions = 0; if ((button == GDK_BUTTON_MIDDLE || button == GDK_BUTTON_SECONDARY) && (actions & GDK_ACTION_ASK)) { *suggested_action = GDK_ACTION_ASK; *possible_actions = actions; } else if (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) { if ((state & GDK_SHIFT_MASK) && (state & GDK_CONTROL_MASK)) { if (actions & GDK_ACTION_LINK) { *suggested_action = GDK_ACTION_LINK; *possible_actions = GDK_ACTION_LINK; } } else if (state & GDK_CONTROL_MASK) { if (actions & GDK_ACTION_COPY) { *suggested_action = GDK_ACTION_COPY; *possible_actions = GDK_ACTION_COPY; } } else { if (actions & GDK_ACTION_MOVE) { *suggested_action = GDK_ACTION_MOVE; *possible_actions = GDK_ACTION_MOVE; } } } else { *possible_actions = actions; if ((state & (GDK_MOD1_MASK)) && (actions & GDK_ACTION_ASK)) *suggested_action = GDK_ACTION_ASK; else if (actions & GDK_ACTION_COPY) *suggested_action = GDK_ACTION_COPY; else if (actions & GDK_ACTION_MOVE) *suggested_action = GDK_ACTION_MOVE; else if (actions & GDK_ACTION_LINK) *suggested_action = GDK_ACTION_LINK; } } static void gdk_drag_update (GdkDragContext *context, gdouble x_root, gdouble y_root, GdkModifierType mods, guint32 evtime) { GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context); GdkDragAction action, possible_actions; GdkWindow *dest_window; GdkDragProtocol protocol; gdk_drag_get_current_actions (mods, GDK_BUTTON_PRIMARY, win32_context->actions, &action, &possible_actions); gdk_drag_find_window (context, win32_context->drag_window, x_root, y_root, &dest_window, &protocol); gdk_drag_motion (context, dest_window, protocol, x_root, y_root, action, possible_actions, evtime); } static gboolean gdk_dnd_handle_motion_event (GdkDragContext *context, const GdkEventMotion *event) { GdkModifierType state; if (!gdk_event_get_state ((GdkEvent *) event, &state)) return FALSE; GDK_NOTE (DND, g_print ("gdk_dnd_handle_motion_event: 0x%p\n", context)); gdk_drag_update (context, event->x_root, event->y_root, state, gdk_event_get_time ((GdkEvent *) event)); return TRUE; } static gboolean gdk_dnd_handle_key_event (GdkDragContext *context, const GdkEventKey *event) { GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context); GdkModifierType state; GdkWindow *root_window; GdkDevice *pointer; gint dx, dy; GDK_NOTE (DND, g_print ("gdk_dnd_handle_key_event: 0x%p\n", context)); dx = dy = 0; state = event->state; pointer = gdk_device_get_associated_device (gdk_event_get_device ((GdkEvent *) event)); if (event->type == GDK_KEY_PRESS) { switch (event->keyval) { case GDK_KEY_Escape: gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_USER_CANCELLED); return TRUE; case GDK_KEY_space: case GDK_KEY_Return: case GDK_KEY_ISO_Enter: case GDK_KEY_KP_Enter: case GDK_KEY_KP_Space: if ((gdk_drag_context_get_selected_action (context) != 0) && (gdk_drag_context_get_dest_window (context) != NULL)) { g_signal_emit_by_name (context, "drop-performed", gdk_event_get_time ((GdkEvent *) event)); } else gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_NO_TARGET); return TRUE; case GDK_KEY_Up: case GDK_KEY_KP_Up: dy = (state & GDK_MOD1_MASK) ? -BIG_STEP : -SMALL_STEP; break; case GDK_KEY_Down: case GDK_KEY_KP_Down: dy = (state & GDK_MOD1_MASK) ? BIG_STEP : SMALL_STEP; break; case GDK_KEY_Left: case GDK_KEY_KP_Left: dx = (state & GDK_MOD1_MASK) ? -BIG_STEP : -SMALL_STEP; break; case GDK_KEY_Right: case GDK_KEY_KP_Right: dx = (state & GDK_MOD1_MASK) ? BIG_STEP : SMALL_STEP; break; } } /* The state is not yet updated in the event, so we need * to query it here. */ _gdk_device_query_state (pointer, NULL, NULL, NULL, NULL, NULL, NULL, &state); if (dx != 0 || dy != 0) { win32_context->last_x += dx; win32_context->last_y += dy; gdk_device_warp (pointer, win32_context->last_x, win32_context->last_y); } gdk_drag_update (context, win32_context->last_x, win32_context->last_y, state, gdk_event_get_time ((GdkEvent *) event)); return TRUE; } static gboolean gdk_dnd_handle_grab_broken_event (GdkDragContext *context, const GdkEventGrabBroken *event) { GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context); GDK_NOTE (DND, g_print ("gdk_dnd_handle_grab_broken_event: 0x%p\n", context)); /* Don't cancel if we break the implicit grab from the initial button_press. * Also, don't cancel if we re-grab on the widget or on our IPC window, for * example, when changing the drag cursor. */ if (event->implicit || event->grab_window == win32_context->drag_window || event->grab_window == win32_context->ipc_window) return FALSE; if (gdk_event_get_device ((GdkEvent *) event) != gdk_drag_context_get_device (context)) return FALSE; gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_ERROR); return TRUE; } static gboolean gdk_dnd_handle_button_event (GdkDragContext *context, const GdkEventButton *event) { GDK_NOTE (DND, g_print ("gdk_dnd_handle_button_event: 0x%p\n", context)); #if 0 /* FIXME: Check the button matches */ if (event->button != win32_context->button) return FALSE; #endif if ((gdk_drag_context_get_selected_action (context) != 0) && (gdk_drag_context_get_dest_window (context) != NULL)) { g_signal_emit_by_name (context, "drop-performed", gdk_event_get_time ((GdkEvent *) event)); } else gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_NO_TARGET); return TRUE; } gboolean gdk_dnd_handle_drag_status (GdkDragContext *context, const GdkEventDND *event) { GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context); GdkDragAction action; GDK_NOTE (DND, g_print ("gdk_dnd_handle_drag_status: 0x%p\n", context)); if (context != event->context) return FALSE; action = gdk_drag_context_get_selected_action (context); if (action != context_win32->current_action) { context_win32->current_action = action; g_signal_emit_by_name (context, "action-changed", action); } return TRUE; } static gboolean gdk_dnd_handle_drop_finished (GdkDragContext *context, const GdkEventDND *event) { GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context); GDK_NOTE (DND, g_print ("gdk_dnd_handle_drop_finihsed: 0x%p\n", context)); if (context != event->context) return FALSE; g_signal_emit_by_name (context, "dnd-finished"); gdk_drag_drop_done (context, !win32_context->drop_failed); gdk_win32_selection_clear_targets (gdk_display_get_default (), gdk_win32_drag_context_get_selection (context)); return TRUE; } gboolean gdk_win32_drag_context_handle_event (GdkDragContext *context, const GdkEvent *event) { GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context); if (!context->is_source) return FALSE; if (!win32_context->grab_seat && event->type != GDK_DROP_FINISHED) return FALSE; switch (event->type) { case GDK_MOTION_NOTIFY: return gdk_dnd_handle_motion_event (context, &event->motion); case GDK_BUTTON_RELEASE: return gdk_dnd_handle_button_event (context, &event->button); case GDK_KEY_PRESS: case GDK_KEY_RELEASE: return gdk_dnd_handle_key_event (context, &event->key); case GDK_GRAB_BROKEN: return gdk_dnd_handle_grab_broken_event (context, &event->grab_broken); case GDK_DRAG_STATUS: return gdk_dnd_handle_drag_status (context, &event->dnd); case GDK_DROP_FINISHED: return gdk_dnd_handle_drop_finished (context, &event->dnd); default: break; } return FALSE; } void gdk_win32_drag_context_action_changed (GdkDragContext *context, GdkDragAction action) { GdkCursor *cursor; cursor = gdk_drag_get_cursor (context, action); gdk_drag_context_set_cursor (context, cursor); } static GdkWindow * gdk_win32_drag_context_get_drag_window (GdkDragContext *context) { return GDK_WIN32_DRAG_CONTEXT (context)->drag_window; } static void gdk_win32_drag_context_set_hotspot (GdkDragContext *context, gint hot_x, gint hot_y) { GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context); GDK_NOTE (DND, g_print ("gdk_drag_context_set_hotspot: 0x%p %d:%d\n", context, hot_x, hot_y)); win32_context->hot_x = hot_x; win32_context->hot_y = hot_y; if (win32_context->grab_seat) { /* DnD is managed, update current position */ move_drag_window (context, win32_context->last_x, win32_context->last_y); } } static void gdk_win32_drag_context_class_init (GdkWin32DragContextClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GdkDragContextClass *context_class = GDK_DRAG_CONTEXT_CLASS (klass); object_class->finalize = gdk_win32_drag_context_finalize; context_class->find_window = gdk_win32_drag_context_find_window; context_class->drag_status = gdk_win32_drag_context_drag_status; context_class->drag_motion = gdk_win32_drag_context_drag_motion; context_class->drag_abort = gdk_win32_drag_context_drag_abort; context_class->drag_drop = gdk_win32_drag_context_drag_drop; context_class->drop_reply = gdk_win32_drag_context_drop_reply; context_class->drop_finish = gdk_win32_drag_context_drop_finish; context_class->drop_status = gdk_win32_drag_context_drop_status; context_class->get_drag_window = gdk_win32_drag_context_get_drag_window; context_class->set_hotspot = gdk_win32_drag_context_set_hotspot; context_class->drop_done = gdk_win32_drag_context_drop_done; context_class->set_cursor = gdk_win32_drag_context_set_cursor; context_class->cancel = gdk_win32_drag_context_cancel; context_class->drop_performed = gdk_win32_drag_context_drop_performed; context_class->handle_event = gdk_win32_drag_context_handle_event; context_class->action_changed = gdk_win32_drag_context_action_changed; }