1 /*
2  * PROJECT:     ReactOS api tests
3  * LICENSE:     LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:     Test for SHChangeNotify
5  * COPYRIGHT:   Copyright 2020-2024 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
6  */
7 
8 // This program is used in SHChangeNotify and ShellExecCmdLine testcases.
9 
10 #include "shelltest.h"
11 #include "shell32_apitest_sub.h"
12 #include <assert.h>
13 
14 typedef enum DIRTYPE
15 {
16     DIRTYPE_DESKTOP = 0,
17     DIRTYPE_DESKTOP_DIR,
18     DIRTYPE_DRIVES,
19     DIRTYPE_PRINTERS,
20     DIRTYPE_DIR1,
21     DIRTYPE_MAX
22 } DIRTYPE;
23 
24 static HWND s_hMainWnd = NULL, s_hSubWnd = NULL;
25 static LPITEMIDLIST s_pidl[DIRTYPE_MAX];
26 static UINT s_uRegID = 0;
27 static INT s_iStage = -1;
28 
29 #define EVENTS (SHCNE_CREATE | SHCNE_DELETE | SHCNE_MKDIR | SHCNE_RMDIR | \
30                 SHCNE_RENAMEFOLDER | SHCNE_RENAMEITEM | SHCNE_UPDATEDIR | SHCNE_UPDATEITEM)
31 
DoGetPidl(INT iDir)32 inline LPITEMIDLIST DoGetPidl(INT iDir)
33 {
34     LPITEMIDLIST ret = NULL;
35 
36     switch (iDir)
37     {
38         case DIRTYPE_DESKTOP:
39         {
40             SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &ret);
41             break;
42         }
43         case DIRTYPE_DESKTOP_DIR:
44         {
45             WCHAR szPath1[MAX_PATH];
46             SHGetSpecialFolderPathW(NULL, szPath1, CSIDL_DESKTOPDIRECTORY, FALSE);
47             ret = ILCreateFromPathW(szPath1);
48             break;
49         }
50         case DIRTYPE_DRIVES:
51         {
52             SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, &ret);
53             break;
54         }
55         case DIRTYPE_PRINTERS:
56         {
57             SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &ret);
58             break;
59         }
60         case DIRTYPE_DIR1:
61         {
62             WCHAR szPath1[MAX_PATH];
63             SHGetSpecialFolderPathW(NULL, szPath1, CSIDL_PERSONAL, FALSE); // My Documents
64             PathAppendW(szPath1, L"_TESTDIR_1_");
65             ret = ILCreateFromPathW(szPath1);
66             break;
67         }
68         default:
69         {
70             assert(0);
71             break;
72         }
73     }
74 
75     return ret;
76 }
77 
OnCreate(HWND hwnd)78 static BOOL OnCreate(HWND hwnd)
79 {
80     s_hSubWnd = hwnd;
81 
82     for (INT i = 0; i < DIRTYPE_MAX; ++i)
83         s_pidl[i] = DoGetPidl(i);
84 
85     return TRUE;
86 }
87 
InitSHCN(HWND hwnd)88 static BOOL InitSHCN(HWND hwnd)
89 {
90     assert(0 <= s_iStage);
91     assert(s_iStage < NUM_STAGE);
92 
93     SHChangeNotifyEntry entry;
94     INT sources;
95     LONG events;
96     switch (s_iStage)
97     {
98         case 0:
99         {
100             entry.fRecursive = FALSE;
101             entry.pidl = NULL;
102             sources = SHCNRF_NewDelivery | SHCNRF_ShellLevel;
103             events = EVENTS;
104             break;
105         }
106         case 1:
107         {
108             entry.fRecursive = TRUE;
109             entry.pidl = NULL;
110             sources = SHCNRF_NewDelivery | SHCNRF_ShellLevel;
111             events = EVENTS;
112             break;
113         }
114         case 2:
115         {
116             entry.fRecursive = FALSE;
117             entry.pidl = s_pidl[DIRTYPE_DESKTOP];
118             sources = SHCNRF_NewDelivery | SHCNRF_ShellLevel;
119             events = EVENTS;
120             break;
121         }
122         case 3:
123         {
124             entry.fRecursive = TRUE;
125             entry.pidl = s_pidl[DIRTYPE_DESKTOP];
126             sources = SHCNRF_NewDelivery | SHCNRF_ShellLevel;
127             events = EVENTS;
128             break;
129         }
130         case 4:
131         {
132             entry.fRecursive = TRUE;
133             entry.pidl = s_pidl[DIRTYPE_DESKTOP_DIR];
134             sources = SHCNRF_NewDelivery | SHCNRF_ShellLevel;
135             events = EVENTS;
136             break;
137         }
138         case 5:
139         {
140             entry.fRecursive = FALSE;
141             entry.pidl = s_pidl[DIRTYPE_DRIVES];
142             sources = SHCNRF_NewDelivery | SHCNRF_ShellLevel;
143             events = EVENTS;
144             break;
145         }
146         case 6:
147         {
148             entry.fRecursive = TRUE;
149             entry.pidl = s_pidl[DIRTYPE_DRIVES];
150             sources = SHCNRF_NewDelivery | SHCNRF_ShellLevel;
151             events = EVENTS;
152             break;
153         }
154         case 7:
155         {
156             entry.fRecursive = TRUE;
157             entry.pidl = s_pidl[DIRTYPE_PRINTERS];
158             sources = SHCNRF_NewDelivery | SHCNRF_ShellLevel;
159             events = EVENTS;
160             break;
161         }
162         case 8:
163         {
164             entry.fRecursive = FALSE;
165             entry.pidl = s_pidl[DIRTYPE_DIR1];
166             sources = SHCNRF_NewDelivery | SHCNRF_ShellLevel;
167             events = EVENTS;
168             break;
169         }
170         case 9:
171         {
172             entry.fRecursive = TRUE;
173             entry.pidl = s_pidl[DIRTYPE_DIR1];
174             sources = SHCNRF_NewDelivery | SHCNRF_ShellLevel | SHCNRF_InterruptLevel |
175                       SHCNRF_RecursiveInterrupt;
176             events = EVENTS;
177             break;
178         }
179         default:
180         {
181             assert(0);
182             break;
183         }
184     }
185 
186     s_uRegID = SHChangeNotifyRegister(hwnd, sources, events, WM_SHELL_NOTIFY, 1, &entry);
187     if (s_uRegID == 0)
188         return FALSE;
189 
190     return TRUE;
191 }
192 
UnInitSHCN(HWND hwnd)193 static void UnInitSHCN(HWND hwnd)
194 {
195     if (s_uRegID)
196     {
197         SHChangeNotifyDeregister(s_uRegID);
198         s_uRegID = 0;
199     }
200 }
201 
OnCommand(HWND hwnd,UINT id)202 static void OnCommand(HWND hwnd, UINT id)
203 {
204     switch (id)
205     {
206         case IDYES: // Start testing
207         {
208             s_hMainWnd = ::FindWindow(MAIN_CLASSNAME, MAIN_CLASSNAME);
209             if (!s_hMainWnd)
210             {
211                 ::DestroyWindow(hwnd);
212                 break;
213             }
214             s_iStage = 0;
215             InitSHCN(hwnd);
216             ::PostMessageW(s_hMainWnd, WM_COMMAND, IDYES, 0);
217             break;
218         }
219         case IDRETRY: // New stage
220         {
221             UnInitSHCN(hwnd);
222             ++s_iStage;
223             InitSHCN(hwnd);
224             ::PostMessageW(s_hMainWnd, WM_COMMAND, IDRETRY, 0);
225             break;
226         }
227         case IDNO: // Quit
228         {
229             s_iStage = -1;
230             UnInitSHCN(hwnd);
231             ::DestroyWindow(hwnd);
232             break;
233         }
234     }
235 }
236 
OnDestroy(HWND hwnd)237 static void OnDestroy(HWND hwnd)
238 {
239     UnInitSHCN(hwnd);
240 
241     for (auto& pidl : s_pidl)
242     {
243         CoTaskMemFree(pidl);
244         pidl = NULL;
245     }
246 
247     ::PostMessageW(s_hMainWnd, WM_COMMAND, IDNO, 0);
248 
249     PostQuitMessage(0);
250 }
251 
DoSendData(LONG lEvent,LPCITEMIDLIST pidl1,LPCITEMIDLIST pidl2)252 static BOOL DoSendData(LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
253 {
254     DWORD cbPidl1 = ILGetSize(pidl1), cbPidl2 = ILGetSize(pidl2);
255     DWORD cbTotal = sizeof(lEvent) + sizeof(cbPidl1) + sizeof(cbPidl2) + cbPidl1 + cbPidl2;
256     LPBYTE pbData = (LPBYTE)::LocalAlloc(LPTR, cbTotal);
257     if (!pbData)
258         return FALSE;
259 
260     LPBYTE pb = pbData;
261 
262     *(LONG*)pb = lEvent;
263     pb += sizeof(lEvent);
264 
265     *(DWORD*)pb = cbPidl1;
266     pb += sizeof(cbPidl1);
267 
268     *(DWORD*)pb = cbPidl2;
269     pb += sizeof(cbPidl2);
270 
271     CopyMemory(pb, pidl1, cbPidl1);
272     pb += cbPidl1;
273 
274     CopyMemory(pb, pidl2, cbPidl2);
275     pb += cbPidl2;
276 
277     assert(INT(pb - pbData) == INT(cbTotal));
278 
279     COPYDATASTRUCT CopyData;
280     CopyData.dwData = 0xBEEFCAFE;
281     CopyData.cbData = cbTotal;
282     CopyData.lpData = pbData;
283     BOOL ret = (BOOL)::SendMessageW(s_hMainWnd, WM_COPYDATA, (WPARAM)s_hSubWnd, (LPARAM)&CopyData);
284 
285     ::LocalFree(pbData);
286     return ret;
287 }
288 
DoShellNotify(HWND hwnd,PIDLIST_ABSOLUTE pidl1,PIDLIST_ABSOLUTE pidl2,LONG lEvent)289 static void DoShellNotify(HWND hwnd, PIDLIST_ABSOLUTE pidl1, PIDLIST_ABSOLUTE pidl2, LONG lEvent)
290 {
291     if (s_iStage < 0)
292         return;
293 
294     DoSendData(lEvent, pidl1, pidl2);
295 }
296 
OnShellNotify(HWND hwnd,WPARAM wParam,LPARAM lParam)297 static INT_PTR OnShellNotify(HWND hwnd, WPARAM wParam, LPARAM lParam)
298 {
299     LONG lEvent;
300     PIDLIST_ABSOLUTE *pidlAbsolute;
301     HANDLE hLock = SHChangeNotification_Lock((HANDLE)wParam, (DWORD)lParam, &pidlAbsolute, &lEvent);
302     if (hLock)
303     {
304         DoShellNotify(hwnd, pidlAbsolute[0], pidlAbsolute[1], lEvent);
305         SHChangeNotification_Unlock(hLock);
306     }
307     else
308     {
309         pidlAbsolute = (PIDLIST_ABSOLUTE *)wParam;
310         DoShellNotify(hwnd, pidlAbsolute[0], pidlAbsolute[1], lParam);
311     }
312     return TRUE;
313 }
314 
SubWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)315 static LRESULT CALLBACK SubWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
316 {
317     switch (uMsg)
318     {
319         case WM_CREATE:
320             return (OnCreate(hwnd) ? 0 : -1);
321 
322         case WM_COMMAND:
323             OnCommand(hwnd, LOWORD(wParam));
324             break;
325 
326         case WM_SHELL_NOTIFY:
327             return OnShellNotify(hwnd, wParam, lParam);
328 
329         case WM_DESTROY:
330             OnDestroy(hwnd);
331             break;
332 
333         default:
334             return ::DefWindowProcW(hwnd, uMsg, wParam, lParam);
335     }
336     return 0;
337 }
338 
339 INT APIENTRY
wWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPWSTR lpCmdLine,INT nCmdShow)340 wWinMain(
341     HINSTANCE hInstance,
342     HINSTANCE hPrevInstance,
343     LPWSTR    lpCmdLine,
344     INT       nCmdShow)
345 {
346     if (lstrcmpiW(lpCmdLine, L"") == 0 || lstrcmpiW(lpCmdLine, L"TEST") == 0)
347         return 0;
348 
349     WNDCLASSW wc = { 0, SubWindowProc };
350     wc.hInstance = hInstance;
351     wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
352     wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
353     wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
354     wc.lpszClassName = SUB_CLASSNAME;
355     if (!RegisterClassW(&wc))
356     {
357         assert(0);
358         return -1;
359     }
360 
361     HWND hwnd = CreateWindowW(SUB_CLASSNAME, SUB_CLASSNAME, WS_OVERLAPPEDWINDOW,
362                               CW_USEDEFAULT, CW_USEDEFAULT, 400, 100,
363                               NULL, NULL, hInstance, NULL);
364     if (!hwnd)
365     {
366         assert(0);
367         return -2;
368     }
369 
370     ShowWindow(hwnd, SW_SHOWNORMAL);
371     UpdateWindow(hwnd);
372 
373     MSG msg;
374     while (GetMessageW(&msg, NULL, 0, 0))
375     {
376         TranslateMessage(&msg);
377         DispatchMessageW(&msg);
378     }
379 
380     return 0;
381 }
382