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 PostMessageW(pSearchData->hwnd, WM_SEARCH_ADD_RESULT, 0, (LPARAM) StrDupW(szPath)); 432 uTotalFound++; 433 } 434 status.Format(IDS_SEARCH_FOLDER, FindData.cFileName); 435 PostMessageW(pSearchData->hwnd, WM_SEARCH_UPDATE_STATUS, 0, (LPARAM) StrDupW(status.GetBuffer())); 436 437 uTotalFound += RecursiveFind(szPath, pSearchData); 438 } 439 else if (FileNameMatch(FindData.cFileName, pSearchData) 440 && AttribHiddenMatch(FindData.dwFileAttributes, pSearchData) 441 && ContentsMatch(szPath, pSearchData)) 442 { 443 uTotalFound++; 444 PostMessageW(pSearchData->hwnd, WM_SEARCH_ADD_RESULT, 0, (LPARAM) StrDupW(szPath)); 445 } 446 } 447 448 if (hFindFile != INVALID_HANDLE_VALUE) 449 FindClose(hFindFile); 450 451 return uTotalFound; 452 } 453 454 DWORD WINAPI CFindFolder::SearchThreadProc(LPVOID lpParameter) 455 { 456 _SearchData *data = static_cast<_SearchData*>(lpParameter); 457 458 HRESULT hrCoInit = CoInitializeEx(NULL, COINIT_MULTITHREADED); 459 460 data->pFindFolder->NotifyConnections(DISPID_SEARCHSTART); 461 462 UINT uTotalFound = RecursiveFind(data->szPath, data); 463 464 data->pFindFolder->NotifyConnections(DISPID_SEARCHCOMPLETE); 465 466 CStringW status; 467 status.Format(IDS_SEARCH_FILES_FOUND, uTotalFound); 468 ::PostMessageW(data->hwnd, WM_SEARCH_UPDATE_STATUS, 0, (LPARAM) StrDupW(status.GetBuffer())); 469 ::SendMessageW(data->hwnd, WM_SEARCH_STOP, 0, 0); 470 471 CloseHandle(data->hStopEvent); 472 delete data; 473 474 if (SUCCEEDED(hrCoInit)) 475 CoUninitialize(); 476 477 return 0; 478 } 479 480 void CFindFolder::NotifyConnections(DISPID id) 481 { 482 DISPPARAMS dispatchParams = {0}; 483 CComDynamicUnkArray &subscribers = 484 IConnectionPointImpl<CFindFolder, &DIID_DSearchCommandEvents>::m_vec; 485 for (IUnknown** pSubscriber = subscribers.begin(); pSubscriber < subscribers.end(); pSubscriber++) 486 { 487 if (!*pSubscriber) 488 continue; 489 490 CComPtr<IDispatch> pDispatch; 491 HRESULT hResult = (*pSubscriber)->QueryInterface(IID_PPV_ARG(IDispatch, &pDispatch)); 492 if (!FAILED_UNEXPECTEDLY(hResult)) 493 pDispatch->Invoke(id, GUID_NULL, 0, DISPATCH_METHOD, &dispatchParams, NULL, NULL, NULL); 494 } 495 } 496 497 LRESULT CFindFolder::StartSearch(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 498 { 499 HKEY hkey; 500 DWORD size = sizeof(DWORD); 501 DWORD result; 502 DWORD SearchHiddenValue = 0; 503 504 if (!lParam) 505 return 0; 506 507 // Clear all previous search results 508 UINT uItemIndex; 509 m_shellFolderView->RemoveObject(NULL, &uItemIndex); 510 511 _SearchData* pSearchData = new _SearchData(); 512 pSearchData->pFindFolder = this; 513 pSearchData->hwnd = m_hWnd; 514 515 SearchStart *pSearchParams = (SearchStart *) lParam; 516 pSearchData->szPath = pSearchParams->szPath; 517 pSearchData->szFileName = pSearchParams->szFileName; 518 pSearchData->szQueryA = pSearchParams->szQuery; 519 pSearchData->szQueryW = pSearchParams->szQuery; 520 521 // UTF-16 BE 522 { 523 CStringW utf16 = pSearchData->szQueryW; 524 LPWSTR psz = utf16.GetBuffer(); 525 for (SIZE_T i = 0; psz[i]; ++i) 526 { 527 psz[i] = MAKEWORD(HIBYTE(psz[i]), LOBYTE(psz[i])); 528 } 529 utf16.ReleaseBuffer(); 530 pSearchData->szQueryU16BE = utf16; 531 } 532 533 // UTF-8 534 { 535 CStringA utf8; 536 INT cch = WideCharToMultiByte(CP_UTF8, 0, pSearchData->szQueryW, -1, NULL, 0, NULL, NULL); 537 if (cch > 0) 538 { 539 LPSTR psz = utf8.GetBuffer(cch); 540 WideCharToMultiByte(CP_UTF8, 0, pSearchData->szQueryW, -1, psz, cch, NULL, NULL); 541 utf8.ReleaseBuffer(); 542 pSearchData->szQueryU8 = utf8; 543 } 544 else 545 { 546 pSearchData->szQueryU8 = pSearchData->szQueryA; 547 } 548 } 549 550 pSearchData->SearchHidden = pSearchParams->SearchHidden; 551 SHFree(pSearchParams); 552 553 TRACE("pSearchParams->SearchHidden is '%d'.\n", pSearchData->SearchHidden); 554 555 if (pSearchData->SearchHidden) 556 SearchHiddenValue = 1; 557 else 558 SearchHiddenValue = 0; 559 560 /* Placing the code to save the changed settings to the registry here has the effect of not saving any changes */ 561 /* to the registry unless the user clicks on the "Search" button. This is the same as what we see in Windows. */ 562 result = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer", 0, KEY_SET_VALUE, &hkey); 563 if (result == ERROR_SUCCESS) 564 { 565 if (RegSetValueExW(hkey, L"SearchHidden", NULL, REG_DWORD, (const BYTE*)&SearchHiddenValue, size) == ERROR_SUCCESS) 566 { 567 TRACE("SearchHidden is '%d'.\n", SearchHiddenValue); 568 } 569 else 570 { 571 ERR("RegSetValueEx for \"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SearchHidden\" Failed.\n"); 572 } 573 RegCloseKey(hkey); 574 } 575 else 576 { 577 ERR("RegOpenKey for \"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\" Failed.\n"); 578 } 579 580 if (m_hStopEvent) 581 SetEvent(m_hStopEvent); 582 pSearchData->hStopEvent = m_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 583 584 if (!SHCreateThread(SearchThreadProc, pSearchData, NULL, NULL)) 585 { 586 SHFree(pSearchData); 587 return 0; 588 } 589 590 return 0; 591 } 592 593 LRESULT CFindFolder::StopSearch(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 594 { 595 if (m_hStopEvent) 596 { 597 SetEvent(m_hStopEvent); 598 m_hStopEvent = NULL; 599 } 600 return 0; 601 } 602 603 LRESULT CFindFolder::AddResult(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 604 { 605 if (!lParam) 606 return 0; 607 608 CComHeapPtr<WCHAR> lpPath((LPWSTR) lParam); 609 610 CComHeapPtr<ITEMIDLIST> lpSearchPidl(_ILCreate(lpPath)); 611 if (lpSearchPidl) 612 { 613 UINT uItemIndex; 614 m_shellFolderView->AddObject(lpSearchPidl, &uItemIndex); 615 } 616 617 return 0; 618 } 619 620 LRESULT CFindFolder::UpdateStatus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 621 { 622 CComHeapPtr<WCHAR> status((LPWSTR) lParam); 623 if (m_shellBrowser) 624 { 625 m_shellBrowser->SetStatusTextSB(status); 626 } 627 628 return 0; 629 } 630 631 // *** IShellFolder2 methods *** 632 STDMETHODIMP CFindFolder::GetDefaultSearchGUID(GUID *pguid) 633 { 634 UNIMPLEMENTED; 635 return E_NOTIMPL; 636 } 637 638 STDMETHODIMP CFindFolder::EnumSearches(IEnumExtraSearch **ppenum) 639 { 640 UNIMPLEMENTED; 641 return E_NOTIMPL; 642 } 643 644 STDMETHODIMP CFindFolder::GetDefaultColumn(DWORD, ULONG *pSort, ULONG *pDisplay) 645 { 646 if (pSort) 647 *pSort = 0; 648 if (pDisplay) 649 *pDisplay = 0; 650 return S_OK; 651 } 652 653 STDMETHODIMP CFindFolder::GetDefaultColumnState(UINT iColumn, DWORD *pcsFlags) 654 { 655 if (!pcsFlags) 656 return E_INVALIDARG; 657 if (iColumn >= _countof(g_ColumnDefs)) 658 return m_pisfInner->GetDefaultColumnState(iColumn - _countof(g_ColumnDefs) + 1, pcsFlags); 659 *pcsFlags = g_ColumnDefs[iColumn].dwDefaultState; 660 return S_OK; 661 } 662 663 STDMETHODIMP CFindFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv) 664 { 665 return m_pisfInner->GetDetailsEx(pidl, pscid, pv); 666 } 667 668 STDMETHODIMP CFindFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *pDetails) 669 { 670 if (iColumn >= _countof(g_ColumnDefs)) 671 return m_pisfInner->GetDetailsOf(_ILGetFSPidl(pidl), iColumn - _countof(g_ColumnDefs) + 1, pDetails); 672 673 pDetails->cxChar = g_ColumnDefs[iColumn].cxChar; 674 pDetails->fmt = g_ColumnDefs[iColumn].fmt; 675 676 if (!pidl) 677 return SHSetStrRet(&pDetails->str, _AtlBaseModule.GetResourceInstance(), g_ColumnDefs[iColumn].iResource); 678 679 if (iColumn == COL_LOCATION_INDEX) 680 { 681 return SHSetStrRet(&pDetails->str, _ILGetPath(pidl)); 682 } 683 684 if (iColumn == COL_RELEVANCE_INDEX) 685 { 686 // TODO: Fill once the relevance is calculated 687 return SHSetStrRet(&pDetails->str, ""); 688 } 689 690 return GetDisplayNameOf(pidl, SHGDN_NORMAL, &pDetails->str); 691 } 692 693 STDMETHODIMP CFindFolder::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid) 694 { 695 UNIMPLEMENTED; 696 return E_NOTIMPL; 697 } 698 699 // *** IShellFolder methods *** 700 STDMETHODIMP CFindFolder::ParseDisplayName(HWND hwndOwner, LPBC pbc, LPOLESTR lpszDisplayName, ULONG *pchEaten, 701 PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes) 702 { 703 UNIMPLEMENTED; 704 return E_NOTIMPL; 705 } 706 707 STDMETHODIMP CFindFolder::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList) 708 { 709 *ppEnumIDList = NULL; 710 return S_FALSE; 711 } 712 713 STDMETHODIMP CFindFolder::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut) 714 { 715 UNIMPLEMENTED; 716 return E_NOTIMPL; 717 } 718 719 STDMETHODIMP CFindFolder::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut) 720 { 721 UNIMPLEMENTED; 722 return E_NOTIMPL; 723 } 724 725 STDMETHODIMP CFindFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2) 726 { 727 WORD wColumn = LOWORD(lParam); 728 switch (wColumn) 729 { 730 case COL_NAME_INDEX: // Name 731 break; 732 case COL_LOCATION_INDEX: // Path 733 return MAKE_COMPARE_HRESULT(StrCmpW(_ILGetPath(pidl1), _ILGetPath(pidl2))); 734 case COL_RELEVANCE_INDEX: // Relevance 735 return E_NOTIMPL; 736 default: // Default columns 737 wColumn -= _countof(g_ColumnDefs) - 1; 738 break; 739 } 740 return m_pisfInner->CompareIDs(HIWORD(lParam) | wColumn, _ILGetFSPidl(pidl1), _ILGetFSPidl(pidl2)); 741 } 742 743 STDMETHODIMP CFindFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppvOut) 744 { 745 if (riid == IID_IShellView) 746 { 747 SFV_CREATE sfvparams = {}; 748 sfvparams.cbSize = sizeof(SFV_CREATE); 749 sfvparams.pshf = this; 750 sfvparams.psfvcb = this; 751 HRESULT hr = SHCreateShellFolderView(&sfvparams, (IShellView **) ppvOut); 752 if (FAILED_UNEXPECTEDLY(hr)) 753 { 754 return hr; 755 } 756 757 return ((IShellView * ) * ppvOut)->QueryInterface(IID_PPV_ARG(IShellFolderView, &m_shellFolderView)); 758 } 759 return E_NOINTERFACE; 760 } 761 762 STDMETHODIMP CFindFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD *rgfInOut) 763 { 764 CComHeapPtr<PCITEMID_CHILD> aFSPidl; 765 aFSPidl.Allocate(cidl); 766 for (UINT i = 0; i < cidl; i++) 767 { 768 aFSPidl[i] = _ILGetFSPidl(apidl[i]); 769 } 770 771 return m_pisfInner->GetAttributesOf(cidl, aFSPidl, rgfInOut); 772 } 773 774 class CFindFolderContextMenu : 775 public IContextMenu, 776 public CComObjectRootEx<CComMultiThreadModelNoCS> 777 { 778 CComPtr<IContextMenu> m_pInner; 779 CComPtr<IShellFolderView> m_shellFolderView; 780 UINT m_firstCmdId; 781 static const UINT ADDITIONAL_MENU_ITEMS = 2; 782 783 //// *** IContextMenu methods *** 784 STDMETHODIMP QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) 785 { 786 m_firstCmdId = indexMenu; 787 _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst++, MFT_STRING, MAKEINTRESOURCEW(IDS_SEARCH_OPEN_FOLDER), MFS_ENABLED); 788 _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst++, MFT_SEPARATOR, NULL, 0); 789 return m_pInner->QueryContextMenu(hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags); 790 } 791 792 STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi) 793 { 794 if (!IS_INTRESOURCE(lpcmi->lpVerb)) 795 { 796 return m_pInner->InvokeCommand(lpcmi); 797 } 798 799 if (LOWORD(lpcmi->lpVerb) < m_firstCmdId + ADDITIONAL_MENU_ITEMS) 800 { 801 PCUITEMID_CHILD *apidl; 802 UINT cidl; 803 HRESULT hResult = m_shellFolderView->GetSelectedObjects(&apidl, &cidl); 804 if (FAILED_UNEXPECTEDLY(hResult)) 805 return hResult; 806 807 for (UINT i = 0; i < cidl; i++) 808 { 809 CComHeapPtr<ITEMIDLIST> folderPidl(ILCreateFromPathW(_ILGetPath(apidl[i]))); 810 if (!folderPidl) 811 return E_OUTOFMEMORY; 812 LPCITEMIDLIST child = _ILGetFSPidl(apidl[i]); 813 SHOpenFolderAndSelectItems(folderPidl, 1, &child, 0); 814 } 815 LocalFree(apidl); // Yes, LocalFree 816 return S_OK; 817 } 818 819 CMINVOKECOMMANDINFOEX actualCmdInfo; 820 memcpy(&actualCmdInfo, lpcmi, lpcmi->cbSize); 821 actualCmdInfo.lpVerb -= ADDITIONAL_MENU_ITEMS; 822 return m_pInner->InvokeCommand((CMINVOKECOMMANDINFO *)&actualCmdInfo); 823 } 824 825 STDMETHODIMP GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen) 826 { 827 return m_pInner->GetCommandString(idCommand, uFlags, lpReserved, lpszName, uMaxNameLen); 828 } 829 830 public: 831 static HRESULT Create(IShellFolderView *pShellFolderView, IContextMenu *pInnerContextMenu, IContextMenu **pContextMenu) 832 { 833 CComObject<CFindFolderContextMenu> *pObj; 834 HRESULT hResult = CComObject<CFindFolderContextMenu>::CreateInstance(&pObj); 835 if (FAILED_UNEXPECTEDLY(hResult)) 836 return hResult; 837 pObj->m_shellFolderView = pShellFolderView; 838 pObj->m_pInner = pInnerContextMenu; 839 return pObj->QueryInterface(IID_PPV_ARG(IContextMenu, pContextMenu)); 840 } 841 842 BEGIN_COM_MAP(CFindFolderContextMenu) 843 COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu) 844 END_COM_MAP() 845 }; 846 847 STDMETHODIMP CFindFolder::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, 848 UINT *prgfInOut, LPVOID *ppvOut) 849 { 850 if (cidl <= 0) 851 { 852 return m_pisfInner->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, ppvOut); 853 } 854 855 CComHeapPtr<PCITEMID_CHILD> aFSPidl; 856 aFSPidl.Allocate(cidl); 857 for (UINT i = 0; i < cidl; i++) 858 { 859 aFSPidl[i] = _ILGetFSPidl(apidl[i]); 860 } 861 862 if (riid == IID_IContextMenu) 863 { 864 CComHeapPtr<ITEMIDLIST> folderPidl(ILCreateFromPathW(_ILGetPath(apidl[0]))); 865 if (!folderPidl) 866 return E_OUTOFMEMORY; 867 CComPtr<IShellFolder> pDesktopFolder; 868 HRESULT hResult = SHGetDesktopFolder(&pDesktopFolder); 869 if (FAILED_UNEXPECTEDLY(hResult)) 870 return hResult; 871 CComPtr<IShellFolder> pShellFolder; 872 hResult = pDesktopFolder->BindToObject(folderPidl, NULL, IID_PPV_ARG(IShellFolder, &pShellFolder)); 873 if (FAILED_UNEXPECTEDLY(hResult)) 874 return hResult; 875 CComPtr<IContextMenu> pContextMenu; 876 hResult = pShellFolder->GetUIObjectOf(hwndOwner, cidl, aFSPidl, riid, prgfInOut, (LPVOID *)&pContextMenu); 877 if (FAILED_UNEXPECTEDLY(hResult)) 878 return hResult; 879 return CFindFolderContextMenu::Create(m_shellFolderView, pContextMenu, (IContextMenu **)ppvOut); 880 } 881 882 return m_pisfInner->GetUIObjectOf(hwndOwner, cidl, aFSPidl, riid, prgfInOut, ppvOut); 883 } 884 885 STDMETHODIMP CFindFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET pName) 886 { 887 return m_pisfInner->GetDisplayNameOf(_ILGetFSPidl(pidl), dwFlags, pName); 888 } 889 890 STDMETHODIMP CFindFolder::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags, 891 PITEMID_CHILD *pPidlOut) 892 { 893 UNIMPLEMENTED; 894 return E_NOTIMPL; 895 } 896 897 //// *** IShellFolderViewCB method *** 898 STDMETHODIMP CFindFolder::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam) 899 { 900 switch (uMsg) 901 { 902 case SFVM_DEFVIEWMODE: 903 { 904 FOLDERVIEWMODE *pViewMode = (FOLDERVIEWMODE *) lParam; 905 *pViewMode = FVM_DETAILS; 906 return S_OK; 907 } 908 case SFVM_WINDOWCREATED: 909 { 910 // Subclass window to receive window messages 911 SubclassWindow((HWND) wParam); 912 913 // Get shell browser for updating status bar text 914 CComPtr<IServiceProvider> pServiceProvider; 915 HRESULT hr = m_shellFolderView->QueryInterface(IID_PPV_ARG(IServiceProvider, &pServiceProvider)); 916 if (FAILED_UNEXPECTEDLY(hr)) 917 return hr; 918 hr = pServiceProvider->QueryService(SID_SShellBrowser, IID_PPV_ARG(IShellBrowser, &m_shellBrowser)); 919 if (FAILED_UNEXPECTEDLY(hr)) 920 return hr; 921 922 // Open search bar 923 CComPtr<IWebBrowser2> pWebBrowser2; 924 hr = m_shellBrowser->QueryInterface(IID_PPV_ARG(IWebBrowser2, &pWebBrowser2)); 925 if (FAILED_UNEXPECTEDLY(hr)) 926 return hr; 927 WCHAR pwszGuid[MAX_PATH]; 928 StringFromGUID2(CLSID_FileSearchBand, pwszGuid, _countof(pwszGuid)); 929 CComVariant searchBar(pwszGuid); 930 return pWebBrowser2->ShowBrowserBar(&searchBar, NULL, NULL); 931 } 932 } 933 return E_NOTIMPL; 934 } 935 936 //// *** IPersistFolder2 methods *** 937 STDMETHODIMP CFindFolder::GetCurFolder(PIDLIST_ABSOLUTE *pidl) 938 { 939 *pidl = ILClone(m_pidl); 940 return S_OK; 941 } 942 943 // *** IPersistFolder methods *** 944 STDMETHODIMP CFindFolder::Initialize(PCIDLIST_ABSOLUTE pidl) 945 { 946 m_pidl = ILClone(pidl); 947 if (!m_pidl) 948 return E_OUTOFMEMORY; 949 950 return SHELL32_CoCreateInitSF(m_pidl, 951 NULL, 952 NULL, 953 &CLSID_ShellFSFolder, 954 IID_PPV_ARG(IShellFolder2, &m_pisfInner)); 955 } 956 957 // *** IPersist methods *** 958 STDMETHODIMP CFindFolder::GetClassID(CLSID *pClassId) 959 { 960 if (pClassId == NULL) 961 return E_INVALIDARG; 962 *pClassId = CLSID_FindFolder; 963 return S_OK; 964 } 965