1 /* 2 * PROJECT: ReactOS Search Shell Extension 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Search results folder 5 * COPYRIGHT: Copyright 2019 Brock Mammen 6 */ 7 8 #include "CFindFolder.h" 9 #include <exdispid.h> 10 11 WINE_DEFAULT_DEBUG_CHANNEL(shellfind); 12 13 static HRESULT SHELL32_CoCreateInitSF(LPCITEMIDLIST pidlRoot, PERSIST_FOLDER_TARGET_INFO* ppfti, 14 LPCITEMIDLIST pidlChild, const GUID* clsid, REFIID riid, LPVOID *ppvOut) 15 { 16 HRESULT hr; 17 CComPtr<IShellFolder> pShellFolder; 18 19 hr = SHCoCreateInstance(NULL, clsid, NULL, IID_PPV_ARG(IShellFolder, &pShellFolder)); 20 if (FAILED(hr)) 21 return hr; 22 23 LPITEMIDLIST pidlAbsolute = ILCombine (pidlRoot, pidlChild); 24 CComPtr<IPersistFolder> ppf; 25 CComPtr<IPersistFolder3> ppf3; 26 27 if (ppfti && SUCCEEDED(pShellFolder->QueryInterface(IID_PPV_ARG(IPersistFolder3, &ppf3)))) 28 { 29 ppf3->InitializeEx(NULL, pidlAbsolute, ppfti); 30 } 31 else if (SUCCEEDED(pShellFolder->QueryInterface(IID_PPV_ARG(IPersistFolder, &ppf)))) 32 { 33 ppf->Initialize(pidlAbsolute); 34 } 35 ILFree (pidlAbsolute); 36 37 return pShellFolder->QueryInterface(riid, ppvOut); 38 } 39 40 static void WINAPI _InsertMenuItemW( 41 HMENU hMenu, 42 UINT indexMenu, 43 BOOL fByPosition, 44 UINT wID, 45 UINT fType, 46 LPCWSTR dwTypeData, 47 UINT fState) 48 { 49 MENUITEMINFOW mii; 50 WCHAR wszText[100]; 51 52 ZeroMemory(&mii, sizeof(mii)); 53 mii.cbSize = sizeof(mii); 54 if (fType == MFT_SEPARATOR) 55 mii.fMask = MIIM_ID | MIIM_TYPE; 56 else if (fType == MFT_STRING) 57 { 58 mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE; 59 if (IS_INTRESOURCE(dwTypeData)) 60 { 61 if (LoadStringW(_AtlBaseModule.GetResourceInstance(), LOWORD((ULONG_PTR)dwTypeData), wszText, _countof(wszText))) 62 mii.dwTypeData = wszText; 63 else 64 { 65 ERR("failed to load string %p\n", dwTypeData); 66 return; 67 } 68 } 69 else 70 mii.dwTypeData = (LPWSTR)dwTypeData; 71 mii.fState = fState; 72 } 73 74 mii.wID = wID; 75 mii.fType = fType; 76 InsertMenuItemW(hMenu, indexMenu, fByPosition, &mii); 77 } 78 79 struct FolderViewColumns 80 { 81 int iResource; 82 DWORD dwDefaultState; 83 int fmt; 84 int cxChar; 85 }; 86 87 static FolderViewColumns g_ColumnDefs[] = 88 { 89 {IDS_COL_NAME, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30}, 90 {IDS_COL_LOCATION, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30}, 91 {IDS_COL_RELEVANCE, SHCOLSTATE_TYPE_STR, LVCFMT_LEFT, 0} 92 }; 93 94 CFindFolder::CFindFolder() : 95 m_hStopEvent(NULL) 96 { 97 } 98 99 static LPITEMIDLIST _ILCreate(LPCWSTR lpszPath) 100 { 101 CComHeapPtr<ITEMIDLIST> lpFSPidl(ILCreateFromPathW(lpszPath)); 102 if (!lpFSPidl) 103 { 104 ERR("Failed to create pidl from path\n"); 105 return NULL; 106 } 107 LPITEMIDLIST lpLastFSPidl = ILFindLastID(lpFSPidl); 108 109 int pathLen = (PathFindFileNameW(lpszPath) - lpszPath) * sizeof(WCHAR); 110 int cbData = sizeof(WORD) + pathLen + lpLastFSPidl->mkid.cb; 111 LPITEMIDLIST pidl = (LPITEMIDLIST) SHAlloc(cbData + sizeof(WORD)); 112 if (!pidl) 113 return NULL; 114 115 LPBYTE p = (LPBYTE) pidl; 116 *((WORD *) p) = cbData; 117 p += sizeof(WORD); 118 119 memcpy(p, lpszPath, pathLen); 120 p += pathLen - sizeof(WCHAR); 121 *((WCHAR *) p) = '\0'; 122 p += sizeof(WCHAR); 123 124 memcpy(p, lpLastFSPidl, lpLastFSPidl->mkid.cb); 125 p += lpLastFSPidl->mkid.cb; 126 127 *((WORD *) p) = 0; 128 129 return pidl; 130 } 131 132 static LPCWSTR _ILGetPath(LPCITEMIDLIST pidl) 133 { 134 if (!pidl || !pidl->mkid.cb) 135 return NULL; 136 return (LPCWSTR) pidl->mkid.abID; 137 } 138 139 static LPCITEMIDLIST _ILGetFSPidl(LPCITEMIDLIST pidl) 140 { 141 if (!pidl || !pidl->mkid.cb) 142 return pidl; 143 return (LPCITEMIDLIST) ((LPBYTE) pidl->mkid.abID 144 + ((wcslen((LPCWSTR) pidl->mkid.abID) + 1) * sizeof(WCHAR))); 145 } 146 147 struct _SearchData 148 { 149 HWND hwnd; 150 HANDLE hStopEvent; 151 CStringW szPath; 152 CStringW szFileName; 153 CStringA szQueryA; 154 CStringW szQueryW; 155 CStringW szQueryU16BE; 156 CStringA szQueryU8; 157 BOOL SearchHidden; 158 CComPtr<CFindFolder> pFindFolder; 159 }; 160 161 template<typename TChar, typename TString, int (&StrNCmp)(const TChar *, const TChar *, size_t)> 162 static const TChar* StrStrN(const TChar *lpFirst, const TString &lpSrch, UINT cchMax) 163 { 164 if (!lpFirst || lpSrch.IsEmpty() || !cchMax) 165 return NULL; 166 167 for (UINT i = cchMax; i > 0 && *lpFirst; i--, lpFirst++) 168 { 169 if (!StrNCmp(lpFirst, lpSrch, lpSrch.GetLength())) 170 return (const TChar*)lpFirst; 171 } 172 173 return NULL; 174 } 175 176 static inline BOOL 177 StrFindNIA(const CHAR *lpFirst, const CStringA &lpSrch, UINT cchMax) 178 { 179 return StrStrN<CHAR, CStringA, _strnicmp>(lpFirst, lpSrch, cchMax) != NULL; 180 } 181 182 static inline BOOL 183 StrFindNIW(const WCHAR *lpFirst, const CStringW &lpSrch, UINT cchMax) 184 { 185 return StrStrN<WCHAR, CStringW, _wcsnicmp>(lpFirst, lpSrch, cchMax) != NULL; 186 } 187 188 /* 189 * The following code is borrowed from base/applications/cmdutils/more/more.c . 190 */ 191 typedef enum 192 { 193 ENCODING_ANSI = 0, 194 ENCODING_UTF16LE = 1, 195 ENCODING_UTF16BE = 2, 196 ENCODING_UTF8 = 3 197 } ENCODING; 198 199 static BOOL 200 IsDataUnicode( 201 IN PVOID Buffer, 202 IN DWORD BufferSize, 203 OUT ENCODING* Encoding OPTIONAL, 204 OUT PDWORD SkipBytes OPTIONAL) 205 { 206 PBYTE pBytes = (PBYTE)Buffer; 207 ENCODING encFile = ENCODING_ANSI; 208 DWORD dwPos = 0; 209 210 /* 211 * See http://archives.miloush.net/michkap/archive/2007/04/22/2239345.html 212 * for more details about the algorithm and the pitfalls behind it. 213 * Of course it would be actually great to make a nice function that 214 * would work, once and for all, and put it into a library. 215 */ 216 217 /* Look for Byte Order Marks */ 218 if ((BufferSize >= 2) && (pBytes[0] == 0xFF) && (pBytes[1] == 0xFE)) 219 { 220 encFile = ENCODING_UTF16LE; 221 dwPos = 2; 222 } 223 else if ((BufferSize >= 2) && (pBytes[0] == 0xFE) && (pBytes[1] == 0xFF)) 224 { 225 encFile = ENCODING_UTF16BE; 226 dwPos = 2; 227 } 228 else if ((BufferSize >= 3) && (pBytes[0] == 0xEF) && (pBytes[1] == 0xBB) && (pBytes[2] == 0xBF)) 229 { 230 encFile = ENCODING_UTF8; 231 dwPos = 3; 232 } 233 else 234 { 235 /* 236 * Try using statistical analysis. Do not rely on the return value of 237 * IsTextUnicode as we can get FALSE even if the text is in UTF-16 BE 238 * (i.e. we have some of the IS_TEXT_UNICODE_REVERSE_MASK bits set). 239 * Instead, set all the tests we want to perform, then just check 240 * the passed tests and try to deduce the string properties. 241 */ 242 243 /* 244 * This mask contains the 3 highest bits from IS_TEXT_UNICODE_NOT_ASCII_MASK 245 * and the 1st highest bit from IS_TEXT_UNICODE_NOT_UNICODE_MASK. 246 */ 247 #define IS_TEXT_UNKNOWN_FLAGS_MASK ((7 << 13) | (1 << 11)) 248 249 /* Flag out the unknown flags here, the passed tests will not have them either */ 250 INT Tests = (IS_TEXT_UNICODE_NOT_ASCII_MASK | 251 IS_TEXT_UNICODE_NOT_UNICODE_MASK | 252 IS_TEXT_UNICODE_REVERSE_MASK | IS_TEXT_UNICODE_UNICODE_MASK) 253 & ~IS_TEXT_UNKNOWN_FLAGS_MASK; 254 INT Results; 255 256 IsTextUnicode(Buffer, BufferSize, &Tests); 257 Results = Tests; 258 259 /* 260 * As the IS_TEXT_UNICODE_NULL_BYTES or IS_TEXT_UNICODE_ILLEGAL_CHARS 261 * flags are expected to be potentially present in the result without 262 * modifying our expectations, filter them out now. 263 */ 264 Results &= ~(IS_TEXT_UNICODE_NULL_BYTES | IS_TEXT_UNICODE_ILLEGAL_CHARS); 265 266 /* 267 * NOTE: The flags IS_TEXT_UNICODE_ASCII16 and 268 * IS_TEXT_UNICODE_REVERSE_ASCII16 are not reliable. 269 * 270 * NOTE2: Check for potential "bush hid the facts" effect by also 271 * checking the original results (in 'Tests') for the absence of 272 * the IS_TEXT_UNICODE_NULL_BYTES flag, as we may presumably expect 273 * that in UTF-16 text there will be at some point some NULL bytes. 274 * If not, fall back to ANSI. This shows the limitations of using the 275 * IsTextUnicode API to perform such tests, and the usage of a more 276 * improved encoding detection algorithm would be really welcome. 277 */ 278 if (!(Results & IS_TEXT_UNICODE_NOT_UNICODE_MASK) && 279 !(Results & IS_TEXT_UNICODE_REVERSE_MASK) && 280 (Results & IS_TEXT_UNICODE_UNICODE_MASK) && 281 (Tests & IS_TEXT_UNICODE_NULL_BYTES)) 282 { 283 encFile = ENCODING_UTF16LE; 284 dwPos = (Results & IS_TEXT_UNICODE_SIGNATURE) ? 2 : 0; 285 } 286 else 287 if (!(Results & IS_TEXT_UNICODE_NOT_UNICODE_MASK) && 288 !(Results & IS_TEXT_UNICODE_UNICODE_MASK) && 289 (Results & IS_TEXT_UNICODE_REVERSE_MASK) && 290 (Tests & IS_TEXT_UNICODE_NULL_BYTES)) 291 { 292 encFile = ENCODING_UTF16BE; 293 dwPos = (Results & IS_TEXT_UNICODE_REVERSE_SIGNATURE) ? 2 : 0; 294 } 295 else 296 { 297 /* 298 * Either 'Results' has neither of those masks set, as it can be 299 * the case for UTF-8 text (or ANSI), or it has both as can be the 300 * case when analysing pure binary data chunk. This is therefore 301 * invalid and we fall back to ANSI encoding. 302 * FIXME: In case of failure, assume ANSI (as long as we do not have 303 * correct tests for UTF8, otherwise we should do them, and at the 304 * very end, assume ANSI). 305 */ 306 encFile = ENCODING_ANSI; // ENCODING_UTF8; 307 dwPos = 0; 308 } 309 } 310 311 if (Encoding) 312 *Encoding = encFile; 313 if (SkipBytes) 314 *SkipBytes = dwPos; 315 316 return (encFile != ENCODING_ANSI); 317 } 318 319 static BOOL SearchFile(LPCWSTR lpFilePath, _SearchData *pSearchData) 320 { 321 HANDLE hFile = CreateFileW(lpFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); 322 if (hFile == INVALID_HANDLE_VALUE) 323 return FALSE; 324 325 // FIXME: support large file 326 DWORD size = GetFileSize(hFile, NULL); 327 if (size == 0 || size == INVALID_FILE_SIZE) 328 { 329 CloseHandle(hFile); 330 return FALSE; 331 } 332 333 HANDLE hFileMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, size, NULL); 334 CloseHandle(hFile); 335 if (hFileMap == INVALID_HANDLE_VALUE) 336 return FALSE; 337 338 LPBYTE pbContents = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, size); 339 CloseHandle(hFileMap); 340 if (!pbContents) 341 return FALSE; 342 343 ENCODING encoding; 344 IsDataUnicode(pbContents, size, &encoding, NULL); 345 346 BOOL bFound; 347 switch (encoding) 348 { 349 case ENCODING_UTF16LE: 350 // UTF-16 351 bFound = StrFindNIW((LPCWSTR)pbContents, pSearchData->szQueryW, size / sizeof(WCHAR)); 352 break; 353 case ENCODING_UTF16BE: 354 // UTF-16 BE 355 bFound = StrFindNIW((LPCWSTR)pbContents, pSearchData->szQueryU16BE, size / sizeof(WCHAR)); 356 break; 357 case ENCODING_UTF8: 358 // UTF-8 359 bFound = StrFindNIA((LPCSTR)pbContents, pSearchData->szQueryU8, size / sizeof(CHAR)); 360 break; 361 case ENCODING_ANSI: 362 default: 363 // ANSI or UTF-8 without BOM 364 bFound = StrFindNIA((LPCSTR)pbContents, pSearchData->szQueryA, size / sizeof(CHAR)); 365 if (!bFound && pSearchData->szQueryA != pSearchData->szQueryU8) 366 bFound = StrFindNIA((LPCSTR)pbContents, pSearchData->szQueryU8, size / sizeof(CHAR)); 367 break; 368 } 369 370 UnmapViewOfFile(pbContents); 371 return bFound; 372 } 373 374 static BOOL FileNameMatch(LPCWSTR FindDataFileName, _SearchData *pSearchData) 375 { 376 if (pSearchData->szFileName.IsEmpty() || PathMatchSpecW(FindDataFileName, pSearchData->szFileName)) 377 { 378 return TRUE; 379 } 380 return FALSE; 381 } 382 383 static BOOL ContentsMatch(LPCWSTR szPath, _SearchData *pSearchData) 384 { 385 if (pSearchData->szQueryA.IsEmpty() || SearchFile(szPath, pSearchData)) 386 { 387 return TRUE; 388 } 389 return FALSE; 390 } 391 392 static BOOL AttribHiddenMatch(DWORD FileAttributes, _SearchData *pSearchData) 393 { 394 if (!(FileAttributes & FILE_ATTRIBUTE_HIDDEN) || (pSearchData->SearchHidden)) 395 { 396 return TRUE; 397 } 398 return FALSE; 399 } 400 401 static UINT RecursiveFind(LPCWSTR lpPath, _SearchData *pSearchData) 402 { 403 if (WaitForSingleObject(pSearchData->hStopEvent, 0) != WAIT_TIMEOUT) 404 return 0; 405 406 WCHAR szPath[MAX_PATH]; 407 WIN32_FIND_DATAW FindData; 408 HANDLE hFindFile; 409 BOOL bMoreFiles = TRUE; 410 UINT uTotalFound = 0; 411 412 PathCombineW(szPath, lpPath, L"*"); 413 414 for (hFindFile = FindFirstFileW(szPath, &FindData); 415 bMoreFiles && hFindFile != INVALID_HANDLE_VALUE; 416 bMoreFiles = FindNextFileW(hFindFile, &FindData)) 417 { 418 #define IS_DOTS(psz) ((psz)[0] == L'.' && ((psz)[1] == 0 || ((psz)[1] == L'.' && (psz)[2] == 0))) 419 if (IS_DOTS(FindData.cFileName)) 420 continue; 421 422 PathCombineW(szPath, lpPath, FindData.cFileName); 423 424 if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 425 { 426 CStringW status; 427 if (pSearchData->szQueryW.IsEmpty() && 428 FileNameMatch(FindData.cFileName, pSearchData) && 429 AttribHiddenMatch(FindData.dwFileAttributes, pSearchData)) 430 { 431 LPWSTR pszPathDup; 432 SHStrDupW(szPath, &pszPathDup); 433 PostMessageW(pSearchData->hwnd, WM_SEARCH_ADD_RESULT, 0, (LPARAM)pszPathDup); 434 uTotalFound++; 435 } 436 status.Format(IDS_SEARCH_FOLDER, FindData.cFileName); 437 LPWSTR pszStatusDup; 438 SHStrDupW(status.GetBuffer(), &pszStatusDup); 439 PostMessageW(pSearchData->hwnd, WM_SEARCH_UPDATE_STATUS, 0, (LPARAM)pszStatusDup); 440 441 uTotalFound += RecursiveFind(szPath, pSearchData); 442 } 443 else if (FileNameMatch(FindData.cFileName, pSearchData) 444 && AttribHiddenMatch(FindData.dwFileAttributes, pSearchData) 445 && ContentsMatch(szPath, pSearchData)) 446 { 447 uTotalFound++; 448 LPWSTR pszPathDup; 449 SHStrDupW(szPath, &pszPathDup); 450 PostMessageW(pSearchData->hwnd, WM_SEARCH_ADD_RESULT, 0, (LPARAM)pszPathDup); 451 } 452 } 453 454 if (hFindFile != INVALID_HANDLE_VALUE) 455 FindClose(hFindFile); 456 457 return uTotalFound; 458 } 459 460 DWORD WINAPI CFindFolder::SearchThreadProc(LPVOID lpParameter) 461 { 462 _SearchData *data = static_cast<_SearchData*>(lpParameter); 463 464 HRESULT hrCoInit = CoInitializeEx(NULL, COINIT_MULTITHREADED); 465 466 data->pFindFolder->NotifyConnections(DISPID_SEARCHSTART); 467 468 UINT uTotalFound = RecursiveFind(data->szPath, data); 469 470 data->pFindFolder->NotifyConnections(DISPID_SEARCHCOMPLETE); 471 472 CStringW status; 473 status.Format(IDS_SEARCH_FILES_FOUND, uTotalFound); 474 LPWSTR pszStatusDup; 475 SHStrDupW(status.GetBuffer(), &pszStatusDup); 476 ::PostMessageW(data->hwnd, WM_SEARCH_UPDATE_STATUS, 0, (LPARAM)pszStatusDup); 477 ::SendMessageW(data->hwnd, WM_SEARCH_STOP, 0, 0); 478 479 CloseHandle(data->hStopEvent); 480 delete data; 481 482 if (SUCCEEDED(hrCoInit)) 483 CoUninitialize(); 484 485 return 0; 486 } 487 488 void CFindFolder::NotifyConnections(DISPID id) 489 { 490 DISPPARAMS dispatchParams = {0}; 491 CComDynamicUnkArray &subscribers = 492 IConnectionPointImpl<CFindFolder, &DIID_DSearchCommandEvents>::m_vec; 493 for (IUnknown** pSubscriber = subscribers.begin(); pSubscriber < subscribers.end(); pSubscriber++) 494 { 495 if (!*pSubscriber) 496 continue; 497 498 CComPtr<IDispatch> pDispatch; 499 HRESULT hResult = (*pSubscriber)->QueryInterface(IID_PPV_ARG(IDispatch, &pDispatch)); 500 if (!FAILED_UNEXPECTEDLY(hResult)) 501 pDispatch->Invoke(id, GUID_NULL, 0, DISPATCH_METHOD, &dispatchParams, NULL, NULL, NULL); 502 } 503 } 504 505 LRESULT CFindFolder::StartSearch(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 506 { 507 HKEY hkey; 508 DWORD size = sizeof(DWORD); 509 DWORD result; 510 DWORD SearchHiddenValue = 0; 511 512 if (!lParam) 513 return 0; 514 515 // Clear all previous search results 516 UINT uItemIndex; 517 m_shellFolderView->RemoveObject(NULL, &uItemIndex); 518 519 _SearchData* pSearchData = new _SearchData(); 520 pSearchData->pFindFolder = this; 521 pSearchData->hwnd = m_hWnd; 522 523 SearchStart *pSearchParams = (SearchStart *) lParam; 524 pSearchData->szPath = pSearchParams->szPath; 525 pSearchData->szFileName = pSearchParams->szFileName; 526 pSearchData->szQueryA = pSearchParams->szQuery; 527 pSearchData->szQueryW = pSearchParams->szQuery; 528 529 // UTF-16 BE 530 { 531 CStringW utf16 = pSearchData->szQueryW; 532 LPWSTR psz = utf16.GetBuffer(); 533 for (SIZE_T i = 0; psz[i]; ++i) 534 { 535 psz[i] = MAKEWORD(HIBYTE(psz[i]), LOBYTE(psz[i])); 536 } 537 utf16.ReleaseBuffer(); 538 pSearchData->szQueryU16BE = utf16; 539 } 540 541 // UTF-8 542 { 543 CStringA utf8; 544 INT cch = WideCharToMultiByte(CP_UTF8, 0, pSearchData->szQueryW, -1, NULL, 0, NULL, NULL); 545 if (cch > 0) 546 { 547 LPSTR psz = utf8.GetBuffer(cch); 548 WideCharToMultiByte(CP_UTF8, 0, pSearchData->szQueryW, -1, psz, cch, NULL, NULL); 549 utf8.ReleaseBuffer(); 550 pSearchData->szQueryU8 = utf8; 551 } 552 else 553 { 554 pSearchData->szQueryU8 = pSearchData->szQueryA; 555 } 556 } 557 558 pSearchData->SearchHidden = pSearchParams->SearchHidden; 559 SHFree(pSearchParams); 560 561 TRACE("pSearchParams->SearchHidden is '%d'.\n", pSearchData->SearchHidden); 562 563 if (pSearchData->SearchHidden) 564 SearchHiddenValue = 1; 565 else 566 SearchHiddenValue = 0; 567 568 /* Placing the code to save the changed settings to the registry here has the effect of not saving any changes */ 569 /* to the registry unless the user clicks on the "Search" button. This is the same as what we see in Windows. */ 570 result = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer", 0, KEY_SET_VALUE, &hkey); 571 if (result == ERROR_SUCCESS) 572 { 573 if (RegSetValueExW(hkey, L"SearchHidden", NULL, REG_DWORD, (const BYTE*)&SearchHiddenValue, size) == ERROR_SUCCESS) 574 { 575 TRACE("SearchHidden is '%d'.\n", SearchHiddenValue); 576 } 577 else 578 { 579 ERR("RegSetValueEx for \"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SearchHidden\" Failed.\n"); 580 } 581 RegCloseKey(hkey); 582 } 583 else 584 { 585 ERR("RegOpenKey for \"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\" Failed.\n"); 586 } 587 588 if (m_hStopEvent) 589 SetEvent(m_hStopEvent); 590 pSearchData->hStopEvent = m_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 591 592 if (!SHCreateThread(SearchThreadProc, pSearchData, NULL, NULL)) 593 { 594 SHFree(pSearchData); 595 return 0; 596 } 597 598 return 0; 599 } 600 601 LRESULT CFindFolder::StopSearch(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 602 { 603 if (m_hStopEvent) 604 { 605 SetEvent(m_hStopEvent); 606 m_hStopEvent = NULL; 607 } 608 return 0; 609 } 610 611 LRESULT CFindFolder::AddResult(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 612 { 613 if (!lParam) 614 return 0; 615 616 CComHeapPtr<WCHAR> lpPath((LPWSTR) lParam); 617 618 CComHeapPtr<ITEMIDLIST> lpSearchPidl(_ILCreate(lpPath)); 619 if (lpSearchPidl) 620 { 621 UINT uItemIndex; 622 m_shellFolderView->AddObject(lpSearchPidl, &uItemIndex); 623 } 624 625 return 0; 626 } 627 628 LRESULT CFindFolder::UpdateStatus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 629 { 630 CComHeapPtr<WCHAR> status((LPWSTR) lParam); 631 if (m_shellBrowser) 632 { 633 m_shellBrowser->SetStatusTextSB(status); 634 } 635 636 return 0; 637 } 638 639 // *** IShellFolder2 methods *** 640 STDMETHODIMP CFindFolder::GetDefaultSearchGUID(GUID *pguid) 641 { 642 UNIMPLEMENTED; 643 return E_NOTIMPL; 644 } 645 646 STDMETHODIMP CFindFolder::EnumSearches(IEnumExtraSearch **ppenum) 647 { 648 UNIMPLEMENTED; 649 return E_NOTIMPL; 650 } 651 652 STDMETHODIMP CFindFolder::GetDefaultColumn(DWORD, ULONG *pSort, ULONG *pDisplay) 653 { 654 if (pSort) 655 *pSort = 0; 656 if (pDisplay) 657 *pDisplay = 0; 658 return S_OK; 659 } 660 661 STDMETHODIMP CFindFolder::GetDefaultColumnState(UINT iColumn, DWORD *pcsFlags) 662 { 663 if (!pcsFlags) 664 return E_INVALIDARG; 665 if (iColumn >= _countof(g_ColumnDefs)) 666 return m_pisfInner->GetDefaultColumnState(iColumn - _countof(g_ColumnDefs) + 1, pcsFlags); 667 *pcsFlags = g_ColumnDefs[iColumn].dwDefaultState; 668 return S_OK; 669 } 670 671 STDMETHODIMP CFindFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv) 672 { 673 return m_pisfInner->GetDetailsEx(pidl, pscid, pv); 674 } 675 676 STDMETHODIMP CFindFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *pDetails) 677 { 678 if (iColumn >= _countof(g_ColumnDefs)) 679 return m_pisfInner->GetDetailsOf(_ILGetFSPidl(pidl), iColumn - _countof(g_ColumnDefs) + 1, pDetails); 680 681 pDetails->cxChar = g_ColumnDefs[iColumn].cxChar; 682 pDetails->fmt = g_ColumnDefs[iColumn].fmt; 683 684 if (!pidl) 685 return SHSetStrRet(&pDetails->str, _AtlBaseModule.GetResourceInstance(), g_ColumnDefs[iColumn].iResource); 686 687 if (iColumn == COL_LOCATION_INDEX) 688 { 689 return SHSetStrRet(&pDetails->str, _ILGetPath(pidl)); 690 } 691 692 if (iColumn == COL_RELEVANCE_INDEX) 693 { 694 // TODO: Fill once the relevance is calculated 695 return SHSetStrRet(&pDetails->str, ""); 696 } 697 698 return GetDisplayNameOf(pidl, SHGDN_NORMAL, &pDetails->str); 699 } 700 701 STDMETHODIMP CFindFolder::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid) 702 { 703 UNIMPLEMENTED; 704 return E_NOTIMPL; 705 } 706 707 // *** IShellFolder methods *** 708 STDMETHODIMP CFindFolder::ParseDisplayName(HWND hwndOwner, LPBC pbc, LPOLESTR lpszDisplayName, ULONG *pchEaten, 709 PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes) 710 { 711 UNIMPLEMENTED; 712 return E_NOTIMPL; 713 } 714 715 STDMETHODIMP CFindFolder::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList) 716 { 717 *ppEnumIDList = NULL; 718 return S_FALSE; 719 } 720 721 STDMETHODIMP CFindFolder::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut) 722 { 723 UNIMPLEMENTED; 724 return E_NOTIMPL; 725 } 726 727 STDMETHODIMP CFindFolder::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut) 728 { 729 UNIMPLEMENTED; 730 return E_NOTIMPL; 731 } 732 733 STDMETHODIMP CFindFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2) 734 { 735 WORD wColumn = LOWORD(lParam); 736 switch (wColumn) 737 { 738 case COL_NAME_INDEX: // Name 739 break; 740 case COL_LOCATION_INDEX: // Path 741 return MAKE_COMPARE_HRESULT(StrCmpW(_ILGetPath(pidl1), _ILGetPath(pidl2))); 742 case COL_RELEVANCE_INDEX: // Relevance 743 return E_NOTIMPL; 744 default: // Default columns 745 wColumn -= _countof(g_ColumnDefs) - 1; 746 break; 747 } 748 return m_pisfInner->CompareIDs(HIWORD(lParam) | wColumn, _ILGetFSPidl(pidl1), _ILGetFSPidl(pidl2)); 749 } 750 751 STDMETHODIMP CFindFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppvOut) 752 { 753 if (riid == IID_IShellView) 754 { 755 SFV_CREATE sfvparams = {}; 756 sfvparams.cbSize = sizeof(SFV_CREATE); 757 sfvparams.pshf = this; 758 sfvparams.psfvcb = this; 759 HRESULT hr = SHCreateShellFolderView(&sfvparams, (IShellView **) ppvOut); 760 if (FAILED_UNEXPECTEDLY(hr)) 761 { 762 return hr; 763 } 764 765 return ((IShellView * ) * ppvOut)->QueryInterface(IID_PPV_ARG(IShellFolderView, &m_shellFolderView)); 766 } 767 return E_NOINTERFACE; 768 } 769 770 STDMETHODIMP CFindFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD *rgfInOut) 771 { 772 CComHeapPtr<PCITEMID_CHILD> aFSPidl; 773 aFSPidl.Allocate(cidl); 774 for (UINT i = 0; i < cidl; i++) 775 { 776 aFSPidl[i] = _ILGetFSPidl(apidl[i]); 777 } 778 779 return m_pisfInner->GetAttributesOf(cidl, aFSPidl, rgfInOut); 780 } 781 782 class CFindFolderContextMenu : 783 public IContextMenu, 784 public CComObjectRootEx<CComMultiThreadModelNoCS> 785 { 786 CComPtr<IContextMenu> m_pInner; 787 CComPtr<IShellFolderView> m_shellFolderView; 788 UINT m_firstCmdId; 789 static const UINT ADDITIONAL_MENU_ITEMS = 2; 790 791 //// *** IContextMenu methods *** 792 STDMETHODIMP QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) 793 { 794 m_firstCmdId = indexMenu; 795 _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst++, MFT_STRING, MAKEINTRESOURCEW(IDS_SEARCH_OPEN_FOLDER), MFS_ENABLED); 796 _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst++, MFT_SEPARATOR, NULL, 0); 797 return m_pInner->QueryContextMenu(hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags); 798 } 799 800 STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi) 801 { 802 if (!IS_INTRESOURCE(lpcmi->lpVerb)) 803 { 804 return m_pInner->InvokeCommand(lpcmi); 805 } 806 807 if (LOWORD(lpcmi->lpVerb) < m_firstCmdId + ADDITIONAL_MENU_ITEMS) 808 { 809 PCUITEMID_CHILD *apidl; 810 UINT cidl; 811 HRESULT hResult = m_shellFolderView->GetSelectedObjects(&apidl, &cidl); 812 if (FAILED_UNEXPECTEDLY(hResult)) 813 return hResult; 814 815 for (UINT i = 0; i < cidl; i++) 816 { 817 CComHeapPtr<ITEMIDLIST> folderPidl(ILCreateFromPathW(_ILGetPath(apidl[i]))); 818 if (!folderPidl) 819 return E_OUTOFMEMORY; 820 LPCITEMIDLIST child = _ILGetFSPidl(apidl[i]); 821 SHOpenFolderAndSelectItems(folderPidl, 1, &child, 0); 822 } 823 LocalFree(apidl); // Yes, LocalFree 824 return S_OK; 825 } 826 827 CMINVOKECOMMANDINFOEX actualCmdInfo; 828 memcpy(&actualCmdInfo, lpcmi, lpcmi->cbSize); 829 actualCmdInfo.lpVerb -= ADDITIONAL_MENU_ITEMS; 830 return m_pInner->InvokeCommand((CMINVOKECOMMANDINFO *)&actualCmdInfo); 831 } 832 833 STDMETHODIMP GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen) 834 { 835 return m_pInner->GetCommandString(idCommand, uFlags, lpReserved, lpszName, uMaxNameLen); 836 } 837 838 public: 839 static HRESULT Create(IShellFolderView *pShellFolderView, IContextMenu *pInnerContextMenu, IContextMenu **pContextMenu) 840 { 841 CComObject<CFindFolderContextMenu> *pObj; 842 HRESULT hResult = CComObject<CFindFolderContextMenu>::CreateInstance(&pObj); 843 if (FAILED_UNEXPECTEDLY(hResult)) 844 return hResult; 845 pObj->m_shellFolderView = pShellFolderView; 846 pObj->m_pInner = pInnerContextMenu; 847 return pObj->QueryInterface(IID_PPV_ARG(IContextMenu, pContextMenu)); 848 } 849 850 BEGIN_COM_MAP(CFindFolderContextMenu) 851 COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu) 852 END_COM_MAP() 853 }; 854 855 STDMETHODIMP CFindFolder::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, 856 UINT *prgfInOut, LPVOID *ppvOut) 857 { 858 if (cidl <= 0) 859 { 860 return m_pisfInner->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, ppvOut); 861 } 862 863 CComHeapPtr<PCITEMID_CHILD> aFSPidl; 864 aFSPidl.Allocate(cidl); 865 for (UINT i = 0; i < cidl; i++) 866 { 867 aFSPidl[i] = _ILGetFSPidl(apidl[i]); 868 } 869 870 if (riid == IID_IContextMenu) 871 { 872 CComHeapPtr<ITEMIDLIST> folderPidl(ILCreateFromPathW(_ILGetPath(apidl[0]))); 873 if (!folderPidl) 874 return E_OUTOFMEMORY; 875 CComPtr<IShellFolder> pDesktopFolder; 876 HRESULT hResult = SHGetDesktopFolder(&pDesktopFolder); 877 if (FAILED_UNEXPECTEDLY(hResult)) 878 return hResult; 879 CComPtr<IShellFolder> pShellFolder; 880 hResult = pDesktopFolder->BindToObject(folderPidl, NULL, IID_PPV_ARG(IShellFolder, &pShellFolder)); 881 if (FAILED_UNEXPECTEDLY(hResult)) 882 return hResult; 883 CComPtr<IContextMenu> pContextMenu; 884 hResult = pShellFolder->GetUIObjectOf(hwndOwner, cidl, aFSPidl, riid, prgfInOut, (LPVOID *)&pContextMenu); 885 if (FAILED_UNEXPECTEDLY(hResult)) 886 return hResult; 887 return CFindFolderContextMenu::Create(m_shellFolderView, pContextMenu, (IContextMenu **)ppvOut); 888 } 889 890 return m_pisfInner->GetUIObjectOf(hwndOwner, cidl, aFSPidl, riid, prgfInOut, ppvOut); 891 } 892 893 STDMETHODIMP CFindFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET pName) 894 { 895 return m_pisfInner->GetDisplayNameOf(_ILGetFSPidl(pidl), dwFlags, pName); 896 } 897 898 STDMETHODIMP CFindFolder::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags, 899 PITEMID_CHILD *pPidlOut) 900 { 901 UNIMPLEMENTED; 902 return E_NOTIMPL; 903 } 904 905 //// *** IShellFolderViewCB method *** 906 STDMETHODIMP CFindFolder::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam) 907 { 908 switch (uMsg) 909 { 910 case SFVM_DEFVIEWMODE: 911 { 912 FOLDERVIEWMODE *pViewMode = (FOLDERVIEWMODE *) lParam; 913 *pViewMode = FVM_DETAILS; 914 return S_OK; 915 } 916 case SFVM_WINDOWCREATED: 917 { 918 // Subclass window to receive window messages 919 SubclassWindow((HWND) wParam); 920 921 // Get shell browser for updating status bar text 922 CComPtr<IServiceProvider> pServiceProvider; 923 HRESULT hr = m_shellFolderView->QueryInterface(IID_PPV_ARG(IServiceProvider, &pServiceProvider)); 924 if (FAILED_UNEXPECTEDLY(hr)) 925 return hr; 926 hr = pServiceProvider->QueryService(SID_SShellBrowser, IID_PPV_ARG(IShellBrowser, &m_shellBrowser)); 927 if (FAILED_UNEXPECTEDLY(hr)) 928 return hr; 929 930 // Open search bar 931 CComPtr<IWebBrowser2> pWebBrowser2; 932 hr = m_shellBrowser->QueryInterface(IID_PPV_ARG(IWebBrowser2, &pWebBrowser2)); 933 if (FAILED_UNEXPECTEDLY(hr)) 934 return hr; 935 WCHAR pwszGuid[MAX_PATH]; 936 StringFromGUID2(CLSID_FileSearchBand, pwszGuid, _countof(pwszGuid)); 937 CComVariant searchBar(pwszGuid); 938 return pWebBrowser2->ShowBrowserBar(&searchBar, NULL, NULL); 939 } 940 } 941 return E_NOTIMPL; 942 } 943 944 //// *** IPersistFolder2 methods *** 945 STDMETHODIMP CFindFolder::GetCurFolder(PIDLIST_ABSOLUTE *pidl) 946 { 947 *pidl = ILClone(m_pidl); 948 return S_OK; 949 } 950 951 // *** IPersistFolder methods *** 952 STDMETHODIMP CFindFolder::Initialize(PCIDLIST_ABSOLUTE pidl) 953 { 954 m_pidl = ILClone(pidl); 955 if (!m_pidl) 956 return E_OUTOFMEMORY; 957 958 return SHELL32_CoCreateInitSF(m_pidl, 959 NULL, 960 NULL, 961 &CLSID_ShellFSFolder, 962 IID_PPV_ARG(IShellFolder2, &m_pisfInner)); 963 } 964 965 // *** IPersist methods *** 966 STDMETHODIMP CFindFolder::GetClassID(CLSID *pClassId) 967 { 968 if (pClassId == NULL) 969 return E_INVALIDARG; 970 *pClassId = CLSID_FindFolder; 971 return S_OK; 972 } 973