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