1 /*
2  * PROJECT:         ReactOS api tests
3  * LICENSE:         LGPLv2.1+ - See COPYING.LIB in the top level directory
4  * PURPOSE:         Test for Drag & Drop
5  * PROGRAMMER:      Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6  */
7 
8 #include "shelltest.h"
9 #include <shlwapi.h>
10 
11 #define NDEBUG
12 #include <debug.h>
13 #include <stdio.h>
14 
15 #define TESTFILENAME L"DragDropTest.txt"
16 #define DROPPED_ON_FILE L"DragDroppedOn.lnk"
17 
18 static CComPtr<IShellFolder> s_pDesktop;
19 
20 static WCHAR s_szSrcTestFile[MAX_PATH];
21 static WCHAR s_szDestFolder[MAX_PATH];
22 static WCHAR s_szDestTestFile[MAX_PATH];
23 static WCHAR s_szDestLinkSpec[MAX_PATH];
24 static WCHAR s_szDroppedToItem[MAX_PATH];
25 
26 enum OP
27 {
28     OP_NONE,
29     OP_COPY,
30     OP_MOVE,
31     OP_LINK,
32     OP_NONE_OR_COPY,
33     OP_NONE_OR_MOVE,
34     OP_NONE_OR_LINK
35 };
36 
37 #define D_NONE DROPEFFECT_NONE
38 #define D_COPY DROPEFFECT_COPY
39 #define D_MOVE DROPEFFECT_MOVE
40 #define D_LINK DROPEFFECT_LINK
41 #define D_NONE_OR_COPY 0xAABBCCDD
42 #define D_NONE_OR_MOVE 0x11223344
43 #define D_NONE_OR_LINK 0x55667788
44 
45 struct TEST_ENTRY
46 {
47     int line;
48     OP op;
49     HRESULT hr1;
50     HRESULT hr2;
51     DWORD dwKeyState;
52     DWORD dwEffects1;
53     DWORD dwEffects2;
54     DWORD dwEffects3;
55 };
56 
57 static const TEST_ENTRY s_TestEntries[] =
58 {
59     // MK_LBUTTON
60     { __LINE__, OP_NONE, S_OK, S_OK, MK_LBUTTON, D_NONE, D_NONE, D_NONE },
61     { __LINE__, OP_COPY, S_OK, S_OK, MK_LBUTTON, D_COPY, D_COPY, D_COPY },
62     { __LINE__, OP_MOVE, S_OK, S_OK, MK_LBUTTON, D_COPY | D_MOVE, D_MOVE, D_NONE },
63     { __LINE__, OP_MOVE, S_OK, S_OK, MK_LBUTTON, D_COPY | D_MOVE | D_LINK, D_MOVE, D_NONE },
64     { __LINE__, OP_COPY, S_OK, S_OK, MK_LBUTTON, D_COPY | D_LINK, D_COPY, D_COPY },
65     { __LINE__, OP_MOVE, S_OK, S_OK, MK_LBUTTON, D_MOVE, D_MOVE, D_NONE },
66     { __LINE__, OP_MOVE, S_OK, S_OK, MK_LBUTTON, D_MOVE | D_LINK, D_MOVE, D_NONE },
67     { __LINE__, OP_LINK, S_OK, S_OK, MK_LBUTTON, D_LINK, D_LINK, D_LINK },
68 
69     // MK_LBUTTON | MK_SHIFT
70     { __LINE__, OP_NONE, S_OK, S_OK, MK_LBUTTON | MK_SHIFT, D_NONE, D_NONE, D_NONE },
71     { __LINE__, OP_NONE_OR_COPY, S_OK, S_OK, MK_LBUTTON | MK_SHIFT, D_COPY, D_NONE_OR_COPY, D_NONE_OR_COPY },
72     { __LINE__, OP_MOVE, S_OK, S_OK, MK_LBUTTON | MK_SHIFT, D_COPY | D_MOVE, D_MOVE, D_NONE },
73     { __LINE__, OP_MOVE, S_OK, S_OK, MK_LBUTTON | MK_SHIFT, D_COPY | D_MOVE | D_LINK, D_MOVE, D_NONE },
74     { __LINE__, OP_NONE_OR_COPY, S_OK, S_OK, MK_LBUTTON | MK_SHIFT, D_COPY | D_LINK, D_NONE_OR_COPY, D_NONE_OR_COPY },
75     { __LINE__, OP_MOVE, S_OK, S_OK, MK_LBUTTON | MK_SHIFT, D_MOVE, D_MOVE, D_NONE },
76     { __LINE__, OP_MOVE, S_OK, S_OK, MK_LBUTTON | MK_SHIFT, D_MOVE | D_LINK, D_MOVE, D_NONE },
77     { __LINE__, OP_NONE_OR_LINK, S_OK, S_OK, MK_LBUTTON | MK_SHIFT, D_LINK, D_NONE_OR_LINK, D_NONE_OR_LINK },
78 
79     // MK_LBUTTON | MK_SHIFT | MK_CONTROL
80 #define MK_LBUTTON_SHIFT_CTRL (MK_LBUTTON | MK_SHIFT | MK_CONTROL)
81     { __LINE__, OP_NONE, S_OK, S_OK, MK_LBUTTON_SHIFT_CTRL, D_NONE, D_NONE, D_NONE },
82     { __LINE__, OP_NONE_OR_COPY, S_OK, S_OK, MK_LBUTTON_SHIFT_CTRL, D_COPY, D_NONE_OR_COPY, D_NONE_OR_COPY },
83     { __LINE__, OP_NONE_OR_COPY, S_OK, S_OK, MK_LBUTTON_SHIFT_CTRL, D_COPY | D_MOVE, D_NONE_OR_COPY, D_NONE_OR_COPY },
84     { __LINE__, OP_LINK, S_OK, S_OK, MK_LBUTTON_SHIFT_CTRL, D_COPY | D_MOVE | D_LINK, D_LINK, D_LINK },
85     { __LINE__, OP_LINK, S_OK, S_OK, MK_LBUTTON_SHIFT_CTRL, D_COPY | D_LINK, D_LINK, D_LINK },
86     { __LINE__, OP_NONE_OR_MOVE, S_OK, S_OK, MK_LBUTTON_SHIFT_CTRL, D_MOVE, D_NONE_OR_MOVE, D_NONE },
87     { __LINE__, OP_LINK, S_OK, S_OK, MK_LBUTTON_SHIFT_CTRL, D_MOVE | D_LINK, D_LINK, D_LINK },
88     { __LINE__, OP_LINK, S_OK, S_OK, MK_LBUTTON_SHIFT_CTRL, D_LINK, D_LINK, D_LINK },
89 #undef MK_LBUTTON_SHIFT_CTRL
90 
91     // MK_LBUTTON | MK_CONTROL
92     { __LINE__, OP_NONE, S_OK, S_OK, MK_LBUTTON | MK_CONTROL, D_NONE, D_NONE, D_NONE },
93     { __LINE__, OP_COPY, S_OK, S_OK, MK_LBUTTON | MK_CONTROL, D_COPY, D_COPY, D_COPY },
94     { __LINE__, OP_COPY, S_OK, S_OK, MK_LBUTTON | MK_CONTROL, D_COPY | D_MOVE, D_COPY, D_COPY },
95     { __LINE__, OP_COPY, S_OK, S_OK, MK_LBUTTON | MK_CONTROL, D_COPY | D_MOVE | D_LINK, D_COPY, D_COPY },
96     { __LINE__, OP_COPY, S_OK, S_OK, MK_LBUTTON | MK_CONTROL, D_COPY | D_LINK, D_COPY, D_COPY },
97     { __LINE__, OP_NONE_OR_MOVE, S_OK, S_OK, MK_LBUTTON | MK_CONTROL, D_MOVE, D_NONE_OR_MOVE, D_NONE },
98     { __LINE__, OP_NONE_OR_MOVE, S_OK, S_OK, MK_LBUTTON | MK_CONTROL, D_MOVE | D_LINK, D_NONE_OR_MOVE, D_NONE },
99     { __LINE__, OP_NONE_OR_LINK, S_OK, S_OK, MK_LBUTTON | MK_CONTROL, D_LINK, D_NONE_OR_LINK, D_NONE_OR_LINK },
100 };
101 
DoCreateTestFile(LPCWSTR pszFileName)102 static void DoCreateTestFile(LPCWSTR pszFileName)
103 {
104     FILE *fp = _wfopen(pszFileName, L"wb");
105     ok(fp != NULL, "fp is NULL for '%S'\n", pszFileName);
106     fclose(fp);
107 }
108 
DoCreateShortcut(LPCWSTR pszLnkFileName,LPCWSTR pszTargetPathName)109 HRESULT DoCreateShortcut(
110     LPCWSTR pszLnkFileName,
111     LPCWSTR pszTargetPathName)
112 {
113     CComPtr<IPersistFile> ppf;
114     CComPtr<IShellLinkW> psl;
115     HRESULT hr;
116 
117     hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
118                           IID_IShellLinkW, (LPVOID *)&psl);
119     if (SUCCEEDED(hr))
120     {
121         psl->SetPath(pszTargetPathName);
122 
123         hr = psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf);
124         if (SUCCEEDED(hr))
125         {
126             hr = ppf->Save(pszLnkFileName, TRUE);
127         }
128     }
129 
130     return hr;
131 }
132 
133 static HRESULT
GetUIObjectOfAbsPidl(PIDLIST_ABSOLUTE pidl,REFIID riid,LPVOID * ppvOut)134 GetUIObjectOfAbsPidl(PIDLIST_ABSOLUTE pidl, REFIID riid, LPVOID *ppvOut)
135 {
136     *ppvOut = NULL;
137 
138     LPCITEMIDLIST pidlLast;
139     CComPtr<IShellFolder> psf;
140     HRESULT hr = SHBindToParent(pidl, IID_IShellFolder, (LPVOID *)&psf,
141                                 &pidlLast);
142     if (FAILED(hr))
143         return hr;
144 
145     hr = psf->GetUIObjectOf(NULL, 1, &pidlLast, riid, NULL, ppvOut);
146     return hr;
147 }
148 
149 static HRESULT
GetUIObjectOfPath(LPCWSTR pszPath,REFIID riid,LPVOID * ppvOut)150 GetUIObjectOfPath(LPCWSTR pszPath, REFIID riid, LPVOID *ppvOut)
151 {
152     *ppvOut = NULL;
153 
154     PIDLIST_ABSOLUTE pidl = ILCreateFromPathW(pszPath);
155     if (!pidl)
156         return E_FAIL;
157 
158     HRESULT hr = GetUIObjectOfAbsPidl(pidl, riid, ppvOut);
159 
160     CoTaskMemFree(pidl);
161 
162     return hr;
163 }
164 
DoSpecExistsW(LPCWSTR pszSpec)165 BOOL DoSpecExistsW(LPCWSTR pszSpec)
166 {
167     WIN32_FIND_DATAW find;
168     HANDLE hFind = FindFirstFileW(pszSpec, &find);
169     if (hFind != INVALID_HANDLE_VALUE)
170     {
171         FindClose(hFind);
172         return TRUE;
173     }
174     return FALSE;
175 }
176 
DoDeleteSpecW(LPCWSTR pszSpec)177 void DoDeleteSpecW(LPCWSTR pszSpec)
178 {
179     WCHAR szPath[MAX_PATH], szFile[MAX_PATH];
180     lstrcpyW(szPath, pszSpec);
181     PathRemoveFileSpecW(szPath);
182 
183     WIN32_FIND_DATAW find;
184     HANDLE hFind = FindFirstFileW(pszSpec, &find);
185     if (hFind != INVALID_HANDLE_VALUE)
186     {
187         do
188         {
189             lstrcpyW(szFile, szPath);
190             PathAppendW(szFile, find.cFileName);
191             DeleteFileW(szFile);
192         } while (FindNextFileW(hFind, &find));
193 
194         FindClose(hFind);
195     }
196 }
197 
DoTestEntry(const TEST_ENTRY * pEntry)198 static void DoTestEntry(const TEST_ENTRY *pEntry)
199 {
200     int line = pEntry->line;
201     HRESULT hr;
202     PIDLIST_ABSOLUTE pidlDesktop = NULL;
203     CComPtr<IDropTarget> pDropTarget;
204     CComPtr<IDataObject> pDataObject;
205 
206     // get the desktop PIDL
207     SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidlDesktop);
208     ok(!!pidlDesktop, "pidlDesktop is NULL\n");
209 
210     // build paths
211     //
212     SHGetPathFromIDListW(pidlDesktop, s_szDroppedToItem);
213     PathAppendW(s_szDroppedToItem, DROPPED_ON_FILE);
214 
215     GetModuleFileNameW(NULL, s_szSrcTestFile, _countof(s_szSrcTestFile));
216     PathRemoveFileSpecW(s_szSrcTestFile);
217     PathAppendW(s_szSrcTestFile, TESTFILENAME);
218 
219     lstrcpyW(s_szDestTestFile, s_szDestFolder);
220     PathAppendW(s_szDestTestFile, TESTFILENAME);
221 
222     lstrcpyW(s_szDestLinkSpec, s_szDestFolder);
223     PathAppendW(s_szDestLinkSpec, L"*DragDropTest*.lnk");
224 
225     //trace("s_szSrcTestFile: '%S'\n", s_szSrcTestFile);
226     //trace("s_szDestTestFile: '%S'\n", s_szDestTestFile);
227     //trace("s_szDestLinkSpec: '%S'\n", s_szDestLinkSpec);
228     //trace("s_szDroppedToItem: '%S'\n", s_szDroppedToItem);
229 
230     // create or delete files
231     //
232     DoCreateTestFile(s_szSrcTestFile);
233     DeleteFileW(s_szDestTestFile);
234     DoDeleteSpecW(s_szDestLinkSpec);
235     DeleteFileW(s_szDroppedToItem);
236     DoCreateShortcut(s_szDroppedToItem, s_szDestFolder);
237 
238     // check file existence
239     //
240     ok(PathIsDirectoryW(s_szDestFolder), "s_szDestFolder is not directory\n");
241     ok(PathFileExistsW(s_szSrcTestFile), "s_szSrcTestFile doesn't exist\n");
242     ok(!DoSpecExistsW(s_szDestLinkSpec), "s_szDestLinkSpec doesn't exist\n");
243     ok(!PathFileExistsW(s_szDestTestFile), "s_szDestTestFile exists\n");
244 
245     // get an IDataObject
246     pDataObject = NULL;
247     hr = GetUIObjectOfPath(s_szSrcTestFile, IID_IDataObject, (LPVOID *)&pDataObject);
248     ok_long(hr, S_OK);
249 
250     // get an IDropTarget
251     CComPtr<IEnumIDList> pEnumIDList;
252     PIDLIST_ABSOLUTE pidl = NULL;
253     hr = s_pDesktop->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS,
254                                  &pEnumIDList);
255     ok_long(hr, S_OK);
256     while (pEnumIDList->Next(1, &pidl, NULL) == S_OK)
257     {
258         WCHAR szText[MAX_PATH];
259         SHGetPathFromIDListW(pidl, szText);
260         if (wcsstr(szText, DROPPED_ON_FILE) != NULL)
261         {
262             break;
263         }
264         CoTaskMemFree(pidl);
265         pidl = NULL;
266     }
267     ok(pidl != NULL, "Line %d: pidl is NULL\n", line);
268     pDropTarget = NULL;
269     PITEMID_CHILD pidlLast = ILFindLastID(pidl);
270     hr = s_pDesktop->GetUIObjectOf(NULL, 1, &pidlLast, IID_IDropTarget,
271                                    NULL, (LPVOID *)&pDropTarget);
272     CoTaskMemFree(pidl);
273     ok_long(hr, S_OK);
274 
275     if (!pDropTarget)
276     {
277         skip("Line %d: pDropTarget was NULL\n", line);
278 
279         // clean up
280         DeleteFileW(s_szSrcTestFile);
281         DeleteFileW(s_szDestTestFile);
282         DoDeleteSpecW(s_szDestLinkSpec);
283         ILFree(pidlDesktop);
284 
285         return;
286     }
287 
288     // DragEnter
289     POINTL ptl = { 0, 0 };
290     DWORD dwKeyState = pEntry->dwKeyState;
291     DWORD dwEffects = pEntry->dwEffects1;
292     hr = pDropTarget->DragEnter(pDataObject, dwKeyState, ptl, &dwEffects);
293 
294     ok(hr == pEntry->hr1, "Line %d: hr1 was %08lX\n", line, hr);
295 
296     switch (pEntry->dwEffects2)
297     {
298     case D_NONE_OR_COPY:
299         ok((dwEffects == D_NONE || dwEffects == D_COPY),
300            "Line %d: dwEffects2 was %08lX\n", line, dwEffects);
301         break;
302     case D_NONE_OR_MOVE:
303         ok((dwEffects == D_NONE || dwEffects == D_MOVE),
304            "Line %d: dwEffects2 was %08lX\n", line, dwEffects);
305         break;
306     case D_NONE_OR_LINK:
307         ok((dwEffects == D_NONE || dwEffects == D_LINK),
308            "Line %d: dwEffects2 was %08lX\n", line, dwEffects);
309         break;
310     default:
311         ok(dwEffects == pEntry->dwEffects2,
312            "Line %d: dwEffects2 was %08lX\n", line, dwEffects);
313         break;
314     }
315 
316     // Drop
317     hr = pDropTarget->Drop(pDataObject, dwKeyState, ptl, &dwEffects);
318     ok(hr == pEntry->hr2, "Line %d: hr2 was %08lX\n", line, hr);
319 
320     switch (pEntry->dwEffects3)
321     {
322     case D_NONE_OR_COPY:
323         ok((dwEffects == D_NONE || dwEffects == D_COPY),
324            "Line %d: dwEffects3 was %08lX\n", line, dwEffects);
325         break;
326     case D_NONE_OR_MOVE:
327         ok((dwEffects == D_NONE || dwEffects == D_MOVE),
328            "Line %d: dwEffects3 was %08lX\n", line, dwEffects);
329         break;
330     case D_NONE_OR_LINK:
331         ok((dwEffects == D_NONE || dwEffects == D_LINK),
332            "Line %d: dwEffects3 was %08lX\n", line, dwEffects);
333         break;
334     default:
335         ok(dwEffects == pEntry->dwEffects3,
336            "Line %d: dwEffects3 was %08lX\n", line, dwEffects);
337         break;
338     }
339 
340     // check file existence by pEntry->op
341     switch (pEntry->op)
342     {
343     case OP_NONE:
344         ok(PathFileExistsW(s_szSrcTestFile), "Line %d: src not exists\n", line);
345         ok(!PathFileExistsW(s_szDestTestFile), "Line %d: dest exists\n", line);
346         ok(!DoSpecExistsW(s_szDestLinkSpec), "Line %d: link exists\n", line);
347         break;
348     case OP_COPY:
349         ok(PathFileExistsW(s_szSrcTestFile), "Line %d: src not exists\n", line);
350         ok(PathFileExistsW(s_szDestTestFile), "Line %d: dest not exists\n", line);
351         ok(!DoSpecExistsW(s_szDestLinkSpec), "Line %d: link exists\n", line);
352         break;
353     case OP_MOVE:
354         ok(!PathFileExistsW(s_szSrcTestFile), "Line %d: src exists\n", line);
355         ok(PathFileExistsW(s_szDestTestFile), "Line %d: dest not exists\n", line);
356         ok(!DoSpecExistsW(s_szDestLinkSpec), "Line %d: link exists\n", line);
357         break;
358     case OP_LINK:
359         ok(PathFileExistsW(s_szSrcTestFile), "Line %d: src not exists\n", line);
360         ok(!PathFileExistsW(s_szDestTestFile), "Line %d: dest not exists\n", line);
361         ok(DoSpecExistsW(s_szDestLinkSpec), "Line %d: link not exists\n", line);
362         break;
363     case OP_NONE_OR_COPY:
364         ok(PathFileExistsW(s_szSrcTestFile), "Line %d: src not exists\n", line);
365         ok(!DoSpecExistsW(s_szDestLinkSpec), "Line %d: link exists\n", line);
366         break;
367     case OP_NONE_OR_MOVE:
368         ok(PathFileExistsW(s_szSrcTestFile) != PathFileExistsW(s_szDestTestFile),
369            "Line %d: It must be either None or Move\n", line);
370         break;
371     case OP_NONE_OR_LINK:
372         ok(PathFileExistsW(s_szSrcTestFile), "Line %d: src not exists\n", line);
373         ok(!PathFileExistsW(s_szDestTestFile), "Line %d: dest not exists\n", line);
374         break;
375     }
376 
377     // clean up
378     DeleteFileW(s_szSrcTestFile);
379     DeleteFileW(s_szDestTestFile);
380     DoDeleteSpecW(s_szDestLinkSpec);
381     ILFree(pidlDesktop);
382 }
383 
START_TEST(DragDrop)384 START_TEST(DragDrop)
385 {
386     if (!winetest_interactive)
387     {
388         skip("Skipping the test due to crash. CORE-17057\n");
389         return;
390     }
391 
392     HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
393     ok_int(SUCCEEDED(hr), TRUE);
394 
395     SHGetDesktopFolder(&s_pDesktop);
396     ok(!!s_pDesktop, "s_pDesktop is NULL\n");
397 
398     BOOL ret = SHGetSpecialFolderPathW(NULL, s_szDestFolder, CSIDL_DESKTOP, FALSE);
399     ok_int(ret, TRUE);
400 
401     for (size_t i = 0; i < _countof(s_TestEntries); ++i)
402     {
403         DoTestEntry(&s_TestEntries[i]);
404     }
405 
406     DeleteFileW(s_szSrcTestFile);
407     DeleteFileW(s_szDestTestFile);
408     DoDeleteSpecW(s_szDestLinkSpec);
409     DeleteFileW(s_szDroppedToItem);
410 
411     CoUninitialize();
412 }
413