ソースを参照

Merge pull request #97250 from Garetonchick/windows-drag-and-drop-fix

Windows: Fix dragging and dropping files from compressed files into editor
Thaddeus Crews 11 ヶ月 前
コミット
6071c7cd3b

+ 1 - 0
platform/windows/SCsub

@@ -24,6 +24,7 @@ common_win = [
     "gl_manager_windows_angle.cpp",
     "wgl_detect_version.cpp",
     "rendering_context_driver_vulkan_windows.cpp",
+    "drop_target_windows.cpp",
 ]
 
 if env.msvc:

+ 29 - 31
platform/windows/display_server_windows.cpp

@@ -30,6 +30,7 @@
 
 #include "display_server_windows.h"
 
+#include "drop_target_windows.h"
 #include "os_windows.h"
 #include "wgl_detect_version.h"
 
@@ -1617,11 +1618,17 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) {
 	}
 #endif
 
-	if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[p_window].wtctx) {
-		wintab_WTClose(windows[p_window].wtctx);
-		windows[p_window].wtctx = nullptr;
+	if ((tablet_get_current_driver() == "wintab") && wintab_available && wd.wtctx) {
+		wintab_WTClose(wd.wtctx);
+		wd.wtctx = nullptr;
+	}
+
+	if (wd.drop_target != nullptr) {
+		RevokeDragDrop(wd.hWnd);
+		wd.drop_target->Release();
 	}
-	DestroyWindow(windows[p_window].hWnd);
+
+	DestroyWindow(wd.hWnd);
 	windows.erase(p_window);
 
 	if (last_focused_window == p_window) {
@@ -1731,7 +1738,14 @@ void DisplayServerWindows::window_set_drop_files_callback(const Callable &p_call
 	_THREAD_SAFE_METHOD_
 
 	ERR_FAIL_COND(!windows.has(p_window));
-	windows[p_window].drop_files_callback = p_callable;
+	WindowData &window_data = windows[p_window];
+
+	window_data.drop_files_callback = p_callable;
+
+	if (window_data.drop_target == nullptr) {
+		window_data.drop_target = memnew(DropTargetWindows(&window_data));
+		ERR_FAIL_COND(RegisterDragDrop(window_data.hWnd, window_data.drop_target) != S_OK);
+	}
 }
 
 void DisplayServerWindows::window_set_title(const String &p_title, WindowID p_window) {
@@ -5320,32 +5334,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
 				}
 			}
 		} break;
-		case WM_DROPFILES: {
-			HDROP hDropInfo = (HDROP)wParam;
-			const int buffsize = 4096;
-			WCHAR buf[buffsize];
-
-			int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, nullptr, 0);
-
-			Vector<String> files;
-
-			for (int i = 0; i < fcount; i++) {
-				DragQueryFileW(hDropInfo, i, buf, buffsize);
-				String file = String::utf16((const char16_t *)buf);
-				files.push_back(file);
-			}
-
-			if (files.size() && windows[window_id].drop_files_callback.is_valid()) {
-				Variant v_files = files;
-				const Variant *v_args[1] = { &v_files };
-				Variant ret;
-				Callable::CallError ce;
-				windows[window_id].drop_files_callback.callp((const Variant **)&v_args, 1, ret, ce);
-				if (ce.error != Callable::CallError::CALL_OK) {
-					ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(windows[window_id].drop_files_callback, v_args, 1, ce)));
-				}
-			}
-		} break;
 		default: {
 			if (user_proc) {
 				return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam);
@@ -6174,6 +6162,8 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
 		FreeLibrary(comctl32);
 	}
 
+	OleInitialize(nullptr);
+
 	memset(&wc, 0, sizeof(WNDCLASSEXW));
 	wc.cbSize = sizeof(WNDCLASSEXW);
 	wc.style = CS_OWNDC | CS_DBLCLKS;
@@ -6606,6 +6596,12 @@ DisplayServerWindows::~DisplayServerWindows() {
 			wintab_WTClose(windows[MAIN_WINDOW_ID].wtctx);
 			windows[MAIN_WINDOW_ID].wtctx = nullptr;
 		}
+
+		if (windows[MAIN_WINDOW_ID].drop_target != nullptr) {
+			RevokeDragDrop(windows[MAIN_WINDOW_ID].hWnd);
+			windows[MAIN_WINDOW_ID].drop_target->Release();
+		}
+
 		DestroyWindow(windows[MAIN_WINDOW_ID].hWnd);
 	}
 
@@ -6637,4 +6633,6 @@ DisplayServerWindows::~DisplayServerWindows() {
 	if (tts) {
 		memdelete(tts);
 	}
+
+	OleUninitialize();
 }

+ 7 - 0
platform/windows/display_server_windows.h

@@ -357,9 +357,13 @@ typedef enum _SHC_PROCESS_DPI_AWARENESS {
 	SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2,
 } SHC_PROCESS_DPI_AWARENESS;
 
+class DropTargetWindows;
+
 class DisplayServerWindows : public DisplayServer {
 	// No need to register with GDCLASS, it's platform-specific and nothing is added.
 
+	friend class DropTargetWindows;
+
 	_THREAD_SAFE_CLASS_
 
 	// UXTheme API
@@ -521,6 +525,9 @@ class DisplayServerWindows : public DisplayServer {
 		Callable input_text_callback;
 		Callable drop_files_callback;
 
+		// OLE API
+		DropTargetWindows *drop_target = nullptr;
+
 		WindowID transient_parent = INVALID_WINDOW_ID;
 		HashSet<WindowID> transient_children;
 

+ 375 - 0
platform/windows/drop_target_windows.cpp

@@ -0,0 +1,375 @@
+/**************************************************************************/
+/*  drop_target_windows.cpp                                               */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "drop_target_windows.h"
+
+#include "core/io/dir_access.h"
+#include "core/math/random_pcg.h"
+#include "core/os/time.h"
+
+#include <fileapi.h>
+
+// Helpers
+
+static String create_temp_dir() {
+	Char16String buf;
+	int bufsize = GetTempPathW(0, nullptr) + 1;
+	buf.resize(bufsize);
+	if (GetTempPathW(bufsize, (LPWSTR)buf.ptrw()) == 0) {
+		return "";
+	}
+
+	String tmp_dir = String::utf16((const char16_t *)buf.ptr());
+	RandomPCG gen(Time::get_singleton()->get_ticks_usec());
+
+	const int attempts = 4;
+
+	for (int i = 0; i < attempts; ++i) {
+		uint32_t rnd = gen.rand();
+		String dirname = "godot_tmp_" + String::num_uint64(rnd);
+		String res_dir = tmp_dir.path_join(dirname);
+		Char16String res_dir16 = res_dir.utf16();
+
+		if (CreateDirectoryW((LPCWSTR)res_dir16.ptr(), nullptr)) {
+			return res_dir;
+		}
+	}
+
+	return "";
+}
+
+static bool remove_dir_recursive(const String &p_dir) {
+	Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+	if (dir_access->change_dir(p_dir) != OK) {
+		return false;
+	}
+	return dir_access->erase_contents_recursive() == OK;
+}
+
+static bool stream2file(IStream *p_stream, FILEDESCRIPTORW *p_desc, const String &p_path) {
+	if (DirAccess::make_dir_recursive_absolute(p_path.get_base_dir()) != OK) {
+		return false;
+	}
+
+	Char16String path16 = p_path.utf16();
+	DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
+
+	if (p_desc->dwFlags & FD_ATTRIBUTES) {
+		dwFlagsAndAttributes = p_desc->dwFileAttributes;
+	}
+
+	HANDLE file = CreateFileW(
+			(LPCWSTR)path16.ptr(),
+			GENERIC_WRITE,
+			0,
+			nullptr,
+			CREATE_NEW,
+			dwFlagsAndAttributes,
+			nullptr);
+
+	if (!file) {
+		return false;
+	}
+
+	const int bufsize = 4096;
+	char buf[bufsize];
+	ULONG nread = 0;
+	DWORD nwritten = 0;
+	HRESULT read_result = S_OK;
+	bool result = true;
+
+	while (true) {
+		read_result = p_stream->Read(buf, bufsize, &nread);
+		if (read_result != S_OK && read_result != S_FALSE) {
+			result = false;
+			goto cleanup;
+		}
+
+		if (!nread) {
+			break;
+		}
+
+		while (nread > 0) {
+			if (!WriteFile(file, buf, nread, &nwritten, nullptr) || !nwritten) {
+				result = false;
+				goto cleanup;
+			}
+			nread -= nwritten;
+		}
+	}
+
+cleanup:
+	CloseHandle(file);
+	return result;
+}
+
+// DropTargetWindows
+
+bool DropTargetWindows::is_valid_filedescriptor() {
+	return cf_filedescriptor != 0 && cf_filecontents != 0;
+}
+
+HRESULT DropTargetWindows::handle_hdrop_format(Vector<String> *p_files, IDataObject *pDataObj) {
+	FORMATETC fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+	STGMEDIUM stg;
+	HRESULT res = S_OK;
+
+	if (pDataObj->GetData(&fmt, &stg) != S_OK) {
+		return E_UNEXPECTED;
+	}
+
+	HDROP hDropInfo = (HDROP)GlobalLock(stg.hGlobal);
+
+	Char16String buf;
+
+	if (hDropInfo == nullptr) {
+		ReleaseStgMedium(&stg);
+		return E_UNEXPECTED;
+	}
+
+	int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, nullptr, 0);
+
+	for (int i = 0; i < fcount; i++) {
+		int buffsize = DragQueryFileW(hDropInfo, i, nullptr, 0);
+		buf.resize(buffsize + 1);
+		if (DragQueryFileW(hDropInfo, i, (LPWSTR)buf.ptrw(), buffsize + 1) == 0) {
+			res = E_UNEXPECTED;
+			goto cleanup;
+		}
+		String file = String::utf16((const char16_t *)buf.ptr());
+		p_files->push_back(file);
+	}
+
+cleanup:
+	GlobalUnlock(stg.hGlobal);
+	ReleaseStgMedium(&stg);
+
+	return res;
+}
+
+HRESULT DropTargetWindows::handle_filedescriptor_format(Vector<String> *p_files, IDataObject *pDataObj) {
+	FORMATETC fmt = { cf_filedescriptor, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+	STGMEDIUM stg;
+	HRESULT res = S_OK;
+
+	if (pDataObj->GetData(&fmt, &stg) != S_OK) {
+		return E_UNEXPECTED;
+	}
+
+	FILEGROUPDESCRIPTORW *filegroup_desc = (FILEGROUPDESCRIPTORW *)GlobalLock(stg.hGlobal);
+
+	if (!filegroup_desc) {
+		ReleaseStgMedium(&stg);
+		return E_UNEXPECTED;
+	}
+
+	tmp_path = create_temp_dir();
+	Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+	PackedStringArray copied;
+
+	if (dir_access->change_dir(tmp_path) != OK) {
+		res = E_UNEXPECTED;
+		goto cleanup;
+	}
+
+	for (int i = 0; i < (int)filegroup_desc->cItems; ++i) {
+		res = save_as_file(tmp_path, filegroup_desc->fgd + i, pDataObj, i);
+		if (res != S_OK) {
+			res = E_UNEXPECTED;
+			goto cleanup;
+		}
+	}
+
+	copied = dir_access->get_files();
+	for (const String &file : copied) {
+		p_files->push_back(tmp_path.path_join(file));
+	}
+
+	copied = dir_access->get_directories();
+	for (const String &dir : copied) {
+		p_files->push_back(tmp_path.path_join(dir));
+	}
+
+cleanup:
+	GlobalUnlock(filegroup_desc);
+	ReleaseStgMedium(&stg);
+	if (res != S_OK) {
+		remove_dir_recursive(tmp_path);
+		tmp_path.clear();
+	}
+	return res;
+}
+
+HRESULT DropTargetWindows::save_as_file(const String &p_out_dir, FILEDESCRIPTORW *p_file_desc, IDataObject *pDataObj, int p_file_idx) {
+	String relpath = String::utf16((const char16_t *)p_file_desc->cFileName);
+	String fullpath = p_out_dir.path_join(relpath);
+
+	if (p_file_desc->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+		if (DirAccess::make_dir_recursive_absolute(fullpath) != OK) {
+			return E_UNEXPECTED;
+		}
+		return S_OK;
+	}
+
+	FORMATETC fmt = { cf_filecontents, nullptr, DVASPECT_CONTENT, p_file_idx, TYMED_ISTREAM };
+	STGMEDIUM stg;
+	HRESULT res = S_OK;
+
+	if (pDataObj->GetData(&fmt, &stg) != S_OK) {
+		return E_UNEXPECTED;
+	}
+
+	IStream *stream = stg.pstm;
+	if (stream == nullptr) {
+		res = E_UNEXPECTED;
+		goto cleanup;
+	}
+
+	if (!stream2file(stream, p_file_desc, fullpath)) {
+		res = E_UNEXPECTED;
+		goto cleanup;
+	}
+
+cleanup:
+	ReleaseStgMedium(&stg);
+	return res;
+}
+
+DropTargetWindows::DropTargetWindows(DisplayServerWindows::WindowData *p_window_data) :
+		ref_count(1), window_data(p_window_data) {
+	cf_filedescriptor = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
+	cf_filecontents = RegisterClipboardFormat(CFSTR_FILECONTENTS);
+}
+
+HRESULT STDMETHODCALLTYPE DropTargetWindows::QueryInterface(REFIID riid, void **ppvObject) {
+	if (riid == IID_IUnknown || riid == IID_IDropTarget) {
+		*ppvObject = static_cast<IDropTarget *>(this);
+		AddRef();
+		return S_OK;
+	}
+	*ppvObject = nullptr;
+	return E_NOINTERFACE;
+}
+
+ULONG STDMETHODCALLTYPE DropTargetWindows::AddRef() {
+	return InterlockedIncrement(&ref_count);
+}
+
+ULONG STDMETHODCALLTYPE DropTargetWindows::Release() {
+	ULONG count = InterlockedDecrement(&ref_count);
+	if (count == 0) {
+		memfree(this);
+	}
+	return count;
+}
+
+HRESULT STDMETHODCALLTYPE DropTargetWindows::DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
+	(void)grfKeyState;
+	(void)pt;
+
+	FORMATETC hdrop_fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+	FORMATETC filedesc_fmt = { cf_filedescriptor, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+
+	if (!window_data->drop_files_callback.is_valid()) {
+		*pdwEffect = DROPEFFECT_NONE;
+	} else if (pDataObj->QueryGetData(&hdrop_fmt) == S_OK) {
+		*pdwEffect = DROPEFFECT_COPY;
+	} else if (is_valid_filedescriptor() && pDataObj->QueryGetData(&filedesc_fmt) == S_OK) {
+		*pdwEffect = DROPEFFECT_COPY;
+	} else {
+		*pdwEffect = DROPEFFECT_NONE;
+	}
+
+	return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE DropTargetWindows::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
+	(void)grfKeyState;
+	(void)pt;
+
+	*pdwEffect = DROPEFFECT_COPY;
+	return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE DropTargetWindows::DragLeave() {
+	return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE DropTargetWindows::Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
+	(void)grfKeyState;
+	(void)pt;
+
+	*pdwEffect = DROPEFFECT_NONE;
+
+	if (!window_data->drop_files_callback.is_valid()) {
+		return S_OK;
+	}
+
+	FORMATETC hdrop_fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+	FORMATETC filedesc_fmt = { cf_filedescriptor, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+	Vector<String> files;
+
+	if (pDataObj->QueryGetData(&hdrop_fmt) == S_OK) {
+		HRESULT res = handle_hdrop_format(&files, pDataObj);
+		if (res != S_OK) {
+			return res;
+		}
+	} else if (pDataObj->QueryGetData(&filedesc_fmt) == S_OK && is_valid_filedescriptor()) {
+		HRESULT res = handle_filedescriptor_format(&files, pDataObj);
+		if (res != S_OK) {
+			return res;
+		}
+	} else {
+		return E_UNEXPECTED;
+	}
+
+	if (!files.size()) {
+		return S_OK;
+	}
+
+	Variant v_files = files;
+	const Variant *v_args[1] = { &v_files };
+	Variant ret;
+	Callable::CallError ce;
+	window_data->drop_files_callback.callp((const Variant **)&v_args, 1, ret, ce);
+
+	if (!tmp_path.is_empty()) {
+		remove_dir_recursive(tmp_path);
+		tmp_path.clear();
+	}
+
+	if (ce.error != Callable::CallError::CALL_OK) {
+		ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(window_data->drop_files_callback, v_args, 1, ce)));
+		return E_UNEXPECTED;
+	}
+
+	*pdwEffect = DROPEFFECT_COPY;
+	return S_OK;
+}

+ 77 - 0
platform/windows/drop_target_windows.h

@@ -0,0 +1,77 @@
+/**************************************************************************/
+/*  drop_target_windows.h                                                 */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef DROP_TARGET_WINDOWS_H
+#define DROP_TARGET_WINDOWS_H
+
+#include "display_server_windows.h"
+
+#include <shlobj.h>
+
+// Silence warning due to a COM API weirdness.
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+
+// https://learn.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-dodragdrop#remarks
+class DropTargetWindows : public IDropTarget {
+	LONG ref_count;
+	DisplayServerWindows::WindowData *window_data = nullptr;
+	CLIPFORMAT cf_filedescriptor = 0;
+	CLIPFORMAT cf_filecontents = 0;
+	String tmp_path;
+
+	bool is_valid_filedescriptor();
+	HRESULT handle_hdrop_format(Vector<String> *p_files, IDataObject *pDataObj);
+	HRESULT handle_filedescriptor_format(Vector<String> *p_files, IDataObject *pDataObj);
+	HRESULT save_as_file(const String &p_out_dir, FILEDESCRIPTORW *p_file_desc, IDataObject *pDataObj, int p_file_idx);
+
+public:
+	DropTargetWindows(DisplayServerWindows::WindowData *p_window_data);
+	virtual ~DropTargetWindows() {}
+
+	// IUnknown
+	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override;
+	ULONG STDMETHODCALLTYPE AddRef() override;
+	ULONG STDMETHODCALLTYPE Release() override;
+
+	// IDropTarget
+	HRESULT STDMETHODCALLTYPE DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override;
+	HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override;
+	HRESULT STDMETHODCALLTYPE DragLeave() override;
+	HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override;
+};
+
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+#endif // DROP_TARGET_WINDOWS_H