Path: blob/master/platform/windows/drop_target_windows.cpp
10277 views
/**************************************************************************/1/* drop_target_windows.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "drop_target_windows.h"3132#include "core/io/dir_access.h"33#include "core/math/random_pcg.h"34#include "core/os/time.h"3536#include <fileapi.h>37#include <shellapi.h>3839// Helpers4041static String create_temp_dir() {42Char16String buf;43int bufsize = GetTempPathW(0, nullptr) + 1;44buf.resize_uninitialized(bufsize);45if (GetTempPathW(bufsize, (LPWSTR)buf.ptrw()) == 0) {46return "";47}4849String tmp_dir = String::utf16((const char16_t *)buf.ptr());50RandomPCG gen(Time::get_singleton()->get_ticks_usec());5152const int attempts = 4;5354for (int i = 0; i < attempts; ++i) {55uint32_t rnd = gen.rand();56String dirname = "godot_tmp_" + String::num_uint64(rnd);57String res_dir = tmp_dir.path_join(dirname);58Char16String res_dir16 = res_dir.utf16();5960if (CreateDirectoryW((LPCWSTR)res_dir16.ptr(), nullptr)) {61return res_dir;62}63}6465return "";66}6768static bool remove_dir_recursive(const String &p_dir) {69Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);70if (dir_access->change_dir(p_dir) != OK) {71return false;72}73return dir_access->erase_contents_recursive() == OK;74}7576static bool stream2file(IStream *p_stream, FILEDESCRIPTORW *p_desc, const String &p_path) {77if (DirAccess::make_dir_recursive_absolute(p_path.get_base_dir()) != OK) {78return false;79}8081Char16String path16 = p_path.utf16();82DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;8384if (p_desc->dwFlags & FD_ATTRIBUTES) {85dwFlagsAndAttributes = p_desc->dwFileAttributes;86}8788HANDLE file = CreateFileW(89(LPCWSTR)path16.ptr(),90GENERIC_WRITE,910,92nullptr,93CREATE_NEW,94dwFlagsAndAttributes,95nullptr);9697if (!file) {98return false;99}100101const int bufsize = 4096;102char buf[bufsize];103ULONG nread = 0;104DWORD nwritten = 0;105HRESULT read_result = S_OK;106bool result = true;107108while (true) {109read_result = p_stream->Read(buf, bufsize, &nread);110if (read_result != S_OK && read_result != S_FALSE) {111result = false;112goto cleanup;113}114115if (!nread) {116break;117}118119while (nread > 0) {120if (!WriteFile(file, buf, nread, &nwritten, nullptr) || !nwritten) {121result = false;122goto cleanup;123}124nread -= nwritten;125}126}127128cleanup:129CloseHandle(file);130return result;131}132133// DropTargetWindows134135bool DropTargetWindows::is_valid_filedescriptor() {136return cf_filedescriptor != 0 && cf_filecontents != 0;137}138139HRESULT DropTargetWindows::handle_hdrop_format(Vector<String> *p_files, IDataObject *pDataObj) {140FORMATETC fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };141STGMEDIUM stg;142HRESULT res = S_OK;143144if (pDataObj->GetData(&fmt, &stg) != S_OK) {145return E_UNEXPECTED;146}147148HDROP hDropInfo = (HDROP)GlobalLock(stg.hGlobal);149150Char16String buf;151152if (hDropInfo == nullptr) {153ReleaseStgMedium(&stg);154return E_UNEXPECTED;155}156157int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, nullptr, 0);158159for (int i = 0; i < fcount; i++) {160int buffsize = DragQueryFileW(hDropInfo, i, nullptr, 0);161buf.resize_uninitialized(buffsize + 1);162if (DragQueryFileW(hDropInfo, i, (LPWSTR)buf.ptrw(), buffsize + 1) == 0) {163res = E_UNEXPECTED;164goto cleanup;165}166String file = String::utf16((const char16_t *)buf.ptr());167p_files->push_back(file);168}169170cleanup:171GlobalUnlock(stg.hGlobal);172ReleaseStgMedium(&stg);173174return res;175}176177HRESULT DropTargetWindows::handle_filedescriptor_format(Vector<String> *p_files, IDataObject *pDataObj) {178FORMATETC fmt = { cf_filedescriptor, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };179STGMEDIUM stg;180HRESULT res = S_OK;181182if (pDataObj->GetData(&fmt, &stg) != S_OK) {183return E_UNEXPECTED;184}185186FILEGROUPDESCRIPTORW *filegroup_desc = (FILEGROUPDESCRIPTORW *)GlobalLock(stg.hGlobal);187188if (!filegroup_desc) {189ReleaseStgMedium(&stg);190return E_UNEXPECTED;191}192193tmp_path = create_temp_dir();194Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);195PackedStringArray copied;196197if (dir_access->change_dir(tmp_path) != OK) {198res = E_UNEXPECTED;199goto cleanup;200}201202for (int i = 0; i < (int)filegroup_desc->cItems; ++i) {203res = save_as_file(tmp_path, filegroup_desc->fgd + i, pDataObj, i);204if (res != S_OK) {205res = E_UNEXPECTED;206goto cleanup;207}208}209210copied = dir_access->get_files();211for (const String &file : copied) {212p_files->push_back(tmp_path.path_join(file));213}214215copied = dir_access->get_directories();216for (const String &dir : copied) {217p_files->push_back(tmp_path.path_join(dir));218}219220cleanup:221GlobalUnlock(filegroup_desc);222ReleaseStgMedium(&stg);223if (res != S_OK) {224remove_dir_recursive(tmp_path);225tmp_path.clear();226}227return res;228}229230HRESULT DropTargetWindows::save_as_file(const String &p_out_dir, FILEDESCRIPTORW *p_file_desc, IDataObject *pDataObj, int p_file_idx) {231String relpath = String::utf16((const char16_t *)p_file_desc->cFileName);232String fullpath = p_out_dir.path_join(relpath);233234if (p_file_desc->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {235if (DirAccess::make_dir_recursive_absolute(fullpath) != OK) {236return E_UNEXPECTED;237}238return S_OK;239}240241FORMATETC fmt = { cf_filecontents, nullptr, DVASPECT_CONTENT, p_file_idx, TYMED_ISTREAM };242STGMEDIUM stg;243HRESULT res = S_OK;244245if (pDataObj->GetData(&fmt, &stg) != S_OK) {246return E_UNEXPECTED;247}248249IStream *stream = stg.pstm;250if (stream == nullptr) {251res = E_UNEXPECTED;252goto cleanup;253}254255if (!stream2file(stream, p_file_desc, fullpath)) {256res = E_UNEXPECTED;257goto cleanup;258}259260cleanup:261ReleaseStgMedium(&stg);262return res;263}264265DropTargetWindows::DropTargetWindows(DisplayServerWindows::WindowData *p_window_data) :266ref_count(1), window_data(p_window_data) {267cf_filedescriptor = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);268cf_filecontents = RegisterClipboardFormat(CFSTR_FILECONTENTS);269}270271HRESULT STDMETHODCALLTYPE DropTargetWindows::QueryInterface(REFIID riid, void **ppvObject) {272if (riid == IID_IUnknown || riid == IID_IDropTarget) {273*ppvObject = static_cast<IDropTarget *>(this);274AddRef();275return S_OK;276}277*ppvObject = nullptr;278return E_NOINTERFACE;279}280281ULONG STDMETHODCALLTYPE DropTargetWindows::AddRef() {282return InterlockedIncrement(&ref_count);283}284285ULONG STDMETHODCALLTYPE DropTargetWindows::Release() {286ULONG count = InterlockedDecrement(&ref_count);287if (count == 0) {288memfree(this);289}290return count;291}292293HRESULT STDMETHODCALLTYPE DropTargetWindows::DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {294(void)grfKeyState;295(void)pt;296297FORMATETC hdrop_fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };298FORMATETC filedesc_fmt = { cf_filedescriptor, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };299300if (!window_data->drop_files_callback.is_valid()) {301*pdwEffect = DROPEFFECT_NONE;302} else if (pDataObj->QueryGetData(&hdrop_fmt) == S_OK) {303*pdwEffect = DROPEFFECT_COPY;304} else if (is_valid_filedescriptor() && pDataObj->QueryGetData(&filedesc_fmt) == S_OK) {305*pdwEffect = DROPEFFECT_COPY;306} else {307*pdwEffect = DROPEFFECT_NONE;308}309310return S_OK;311}312313HRESULT STDMETHODCALLTYPE DropTargetWindows::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {314(void)grfKeyState;315(void)pt;316317*pdwEffect = DROPEFFECT_COPY;318return S_OK;319}320321HRESULT STDMETHODCALLTYPE DropTargetWindows::DragLeave() {322return S_OK;323}324325HRESULT STDMETHODCALLTYPE DropTargetWindows::Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {326(void)grfKeyState;327(void)pt;328329*pdwEffect = DROPEFFECT_NONE;330331if (!window_data->drop_files_callback.is_valid()) {332return S_OK;333}334335FORMATETC hdrop_fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };336FORMATETC filedesc_fmt = { cf_filedescriptor, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };337Vector<String> files;338339if (pDataObj->QueryGetData(&hdrop_fmt) == S_OK) {340HRESULT res = handle_hdrop_format(&files, pDataObj);341if (res != S_OK) {342return res;343}344} else if (pDataObj->QueryGetData(&filedesc_fmt) == S_OK && is_valid_filedescriptor()) {345HRESULT res = handle_filedescriptor_format(&files, pDataObj);346if (res != S_OK) {347return res;348}349} else {350return E_UNEXPECTED;351}352353if (!files.size()) {354return S_OK;355}356357Variant v_files = files;358const Variant *v_args[1] = { &v_files };359Variant ret;360Callable::CallError ce;361window_data->drop_files_callback.callp((const Variant **)&v_args, 1, ret, ce);362363if (!tmp_path.is_empty()) {364remove_dir_recursive(tmp_path);365tmp_path.clear();366}367368if (ce.error != Callable::CallError::CALL_OK) {369ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(window_data->drop_files_callback, v_args, 1, ce)));370return E_UNEXPECTED;371}372373*pdwEffect = DROPEFFECT_COPY;374return S_OK;375}376377378