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