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 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 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 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 193 static void UnInitSHCN(HWND hwnd) 194 { 195 if (s_uRegID) 196 { 197 SHChangeNotifyDeregister(s_uRegID); 198 s_uRegID = 0; 199 } 200 } 201 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 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 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 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 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 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 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