Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/platform/windows/drop_target_windows.cpp
10277 views
1
/**************************************************************************/
2
/* drop_target_windows.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "drop_target_windows.h"
32
33
#include "core/io/dir_access.h"
34
#include "core/math/random_pcg.h"
35
#include "core/os/time.h"
36
37
#include <fileapi.h>
38
#include <shellapi.h>
39
40
// Helpers
41
42
static String create_temp_dir() {
43
Char16String buf;
44
int bufsize = GetTempPathW(0, nullptr) + 1;
45
buf.resize_uninitialized(bufsize);
46
if (GetTempPathW(bufsize, (LPWSTR)buf.ptrw()) == 0) {
47
return "";
48
}
49
50
String tmp_dir = String::utf16((const char16_t *)buf.ptr());
51
RandomPCG gen(Time::get_singleton()->get_ticks_usec());
52
53
const int attempts = 4;
54
55
for (int i = 0; i < attempts; ++i) {
56
uint32_t rnd = gen.rand();
57
String dirname = "godot_tmp_" + String::num_uint64(rnd);
58
String res_dir = tmp_dir.path_join(dirname);
59
Char16String res_dir16 = res_dir.utf16();
60
61
if (CreateDirectoryW((LPCWSTR)res_dir16.ptr(), nullptr)) {
62
return res_dir;
63
}
64
}
65
66
return "";
67
}
68
69
static bool remove_dir_recursive(const String &p_dir) {
70
Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
71
if (dir_access->change_dir(p_dir) != OK) {
72
return false;
73
}
74
return dir_access->erase_contents_recursive() == OK;
75
}
76
77
static bool stream2file(IStream *p_stream, FILEDESCRIPTORW *p_desc, const String &p_path) {
78
if (DirAccess::make_dir_recursive_absolute(p_path.get_base_dir()) != OK) {
79
return false;
80
}
81
82
Char16String path16 = p_path.utf16();
83
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
84
85
if (p_desc->dwFlags & FD_ATTRIBUTES) {
86
dwFlagsAndAttributes = p_desc->dwFileAttributes;
87
}
88
89
HANDLE file = CreateFileW(
90
(LPCWSTR)path16.ptr(),
91
GENERIC_WRITE,
92
0,
93
nullptr,
94
CREATE_NEW,
95
dwFlagsAndAttributes,
96
nullptr);
97
98
if (!file) {
99
return false;
100
}
101
102
const int bufsize = 4096;
103
char buf[bufsize];
104
ULONG nread = 0;
105
DWORD nwritten = 0;
106
HRESULT read_result = S_OK;
107
bool result = true;
108
109
while (true) {
110
read_result = p_stream->Read(buf, bufsize, &nread);
111
if (read_result != S_OK && read_result != S_FALSE) {
112
result = false;
113
goto cleanup;
114
}
115
116
if (!nread) {
117
break;
118
}
119
120
while (nread > 0) {
121
if (!WriteFile(file, buf, nread, &nwritten, nullptr) || !nwritten) {
122
result = false;
123
goto cleanup;
124
}
125
nread -= nwritten;
126
}
127
}
128
129
cleanup:
130
CloseHandle(file);
131
return result;
132
}
133
134
// DropTargetWindows
135
136
bool DropTargetWindows::is_valid_filedescriptor() {
137
return cf_filedescriptor != 0 && cf_filecontents != 0;
138
}
139
140
HRESULT DropTargetWindows::handle_hdrop_format(Vector<String> *p_files, IDataObject *pDataObj) {
141
FORMATETC fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
142
STGMEDIUM stg;
143
HRESULT res = S_OK;
144
145
if (pDataObj->GetData(&fmt, &stg) != S_OK) {
146
return E_UNEXPECTED;
147
}
148
149
HDROP hDropInfo = (HDROP)GlobalLock(stg.hGlobal);
150
151
Char16String buf;
152
153
if (hDropInfo == nullptr) {
154
ReleaseStgMedium(&stg);
155
return E_UNEXPECTED;
156
}
157
158
int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, nullptr, 0);
159
160
for (int i = 0; i < fcount; i++) {
161
int buffsize = DragQueryFileW(hDropInfo, i, nullptr, 0);
162
buf.resize_uninitialized(buffsize + 1);
163
if (DragQueryFileW(hDropInfo, i, (LPWSTR)buf.ptrw(), buffsize + 1) == 0) {
164
res = E_UNEXPECTED;
165
goto cleanup;
166
}
167
String file = String::utf16((const char16_t *)buf.ptr());
168
p_files->push_back(file);
169
}
170
171
cleanup:
172
GlobalUnlock(stg.hGlobal);
173
ReleaseStgMedium(&stg);
174
175
return res;
176
}
177
178
HRESULT DropTargetWindows::handle_filedescriptor_format(Vector<String> *p_files, IDataObject *pDataObj) {
179
FORMATETC fmt = { cf_filedescriptor, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
180
STGMEDIUM stg;
181
HRESULT res = S_OK;
182
183
if (pDataObj->GetData(&fmt, &stg) != S_OK) {
184
return E_UNEXPECTED;
185
}
186
187
FILEGROUPDESCRIPTORW *filegroup_desc = (FILEGROUPDESCRIPTORW *)GlobalLock(stg.hGlobal);
188
189
if (!filegroup_desc) {
190
ReleaseStgMedium(&stg);
191
return E_UNEXPECTED;
192
}
193
194
tmp_path = create_temp_dir();
195
Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
196
PackedStringArray copied;
197
198
if (dir_access->change_dir(tmp_path) != OK) {
199
res = E_UNEXPECTED;
200
goto cleanup;
201
}
202
203
for (int i = 0; i < (int)filegroup_desc->cItems; ++i) {
204
res = save_as_file(tmp_path, filegroup_desc->fgd + i, pDataObj, i);
205
if (res != S_OK) {
206
res = E_UNEXPECTED;
207
goto cleanup;
208
}
209
}
210
211
copied = dir_access->get_files();
212
for (const String &file : copied) {
213
p_files->push_back(tmp_path.path_join(file));
214
}
215
216
copied = dir_access->get_directories();
217
for (const String &dir : copied) {
218
p_files->push_back(tmp_path.path_join(dir));
219
}
220
221
cleanup:
222
GlobalUnlock(filegroup_desc);
223
ReleaseStgMedium(&stg);
224
if (res != S_OK) {
225
remove_dir_recursive(tmp_path);
226
tmp_path.clear();
227
}
228
return res;
229
}
230
231
HRESULT DropTargetWindows::save_as_file(const String &p_out_dir, FILEDESCRIPTORW *p_file_desc, IDataObject *pDataObj, int p_file_idx) {
232
String relpath = String::utf16((const char16_t *)p_file_desc->cFileName);
233
String fullpath = p_out_dir.path_join(relpath);
234
235
if (p_file_desc->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
236
if (DirAccess::make_dir_recursive_absolute(fullpath) != OK) {
237
return E_UNEXPECTED;
238
}
239
return S_OK;
240
}
241
242
FORMATETC fmt = { cf_filecontents, nullptr, DVASPECT_CONTENT, p_file_idx, TYMED_ISTREAM };
243
STGMEDIUM stg;
244
HRESULT res = S_OK;
245
246
if (pDataObj->GetData(&fmt, &stg) != S_OK) {
247
return E_UNEXPECTED;
248
}
249
250
IStream *stream = stg.pstm;
251
if (stream == nullptr) {
252
res = E_UNEXPECTED;
253
goto cleanup;
254
}
255
256
if (!stream2file(stream, p_file_desc, fullpath)) {
257
res = E_UNEXPECTED;
258
goto cleanup;
259
}
260
261
cleanup:
262
ReleaseStgMedium(&stg);
263
return res;
264
}
265
266
DropTargetWindows::DropTargetWindows(DisplayServerWindows::WindowData *p_window_data) :
267
ref_count(1), window_data(p_window_data) {
268
cf_filedescriptor = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
269
cf_filecontents = RegisterClipboardFormat(CFSTR_FILECONTENTS);
270
}
271
272
HRESULT STDMETHODCALLTYPE DropTargetWindows::QueryInterface(REFIID riid, void **ppvObject) {
273
if (riid == IID_IUnknown || riid == IID_IDropTarget) {
274
*ppvObject = static_cast<IDropTarget *>(this);
275
AddRef();
276
return S_OK;
277
}
278
*ppvObject = nullptr;
279
return E_NOINTERFACE;
280
}
281
282
ULONG STDMETHODCALLTYPE DropTargetWindows::AddRef() {
283
return InterlockedIncrement(&ref_count);
284
}
285
286
ULONG STDMETHODCALLTYPE DropTargetWindows::Release() {
287
ULONG count = InterlockedDecrement(&ref_count);
288
if (count == 0) {
289
memfree(this);
290
}
291
return count;
292
}
293
294
HRESULT STDMETHODCALLTYPE DropTargetWindows::DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
295
(void)grfKeyState;
296
(void)pt;
297
298
FORMATETC hdrop_fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
299
FORMATETC filedesc_fmt = { cf_filedescriptor, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
300
301
if (!window_data->drop_files_callback.is_valid()) {
302
*pdwEffect = DROPEFFECT_NONE;
303
} else if (pDataObj->QueryGetData(&hdrop_fmt) == S_OK) {
304
*pdwEffect = DROPEFFECT_COPY;
305
} else if (is_valid_filedescriptor() && pDataObj->QueryGetData(&filedesc_fmt) == S_OK) {
306
*pdwEffect = DROPEFFECT_COPY;
307
} else {
308
*pdwEffect = DROPEFFECT_NONE;
309
}
310
311
return S_OK;
312
}
313
314
HRESULT STDMETHODCALLTYPE DropTargetWindows::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
315
(void)grfKeyState;
316
(void)pt;
317
318
*pdwEffect = DROPEFFECT_COPY;
319
return S_OK;
320
}
321
322
HRESULT STDMETHODCALLTYPE DropTargetWindows::DragLeave() {
323
return S_OK;
324
}
325
326
HRESULT STDMETHODCALLTYPE DropTargetWindows::Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
327
(void)grfKeyState;
328
(void)pt;
329
330
*pdwEffect = DROPEFFECT_NONE;
331
332
if (!window_data->drop_files_callback.is_valid()) {
333
return S_OK;
334
}
335
336
FORMATETC hdrop_fmt = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
337
FORMATETC filedesc_fmt = { cf_filedescriptor, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
338
Vector<String> files;
339
340
if (pDataObj->QueryGetData(&hdrop_fmt) == S_OK) {
341
HRESULT res = handle_hdrop_format(&files, pDataObj);
342
if (res != S_OK) {
343
return res;
344
}
345
} else if (pDataObj->QueryGetData(&filedesc_fmt) == S_OK && is_valid_filedescriptor()) {
346
HRESULT res = handle_filedescriptor_format(&files, pDataObj);
347
if (res != S_OK) {
348
return res;
349
}
350
} else {
351
return E_UNEXPECTED;
352
}
353
354
if (!files.size()) {
355
return S_OK;
356
}
357
358
Variant v_files = files;
359
const Variant *v_args[1] = { &v_files };
360
Variant ret;
361
Callable::CallError ce;
362
window_data->drop_files_callback.callp((const Variant **)&v_args, 1, ret, ce);
363
364
if (!tmp_path.is_empty()) {
365
remove_dir_recursive(tmp_path);
366
tmp_path.clear();
367
}
368
369
if (ce.error != Callable::CallError::CALL_OK) {
370
ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(window_data->drop_files_callback, v_args, 1, ce)));
371
return E_UNEXPECTED;
372
}
373
374
*pdwEffect = DROPEFFECT_COPY;
375
return S_OK;
376
}
377
378