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
SHELL32_CoCreateInitSF(LPCITEMIDLIST pidlRoot,PERSIST_FOLDER_TARGET_INFO * ppfti,LPCITEMIDLIST pidlChild,const GUID * clsid,REFIID riid,LPVOID * ppvOut)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
_InsertMenuItemW(HMENU hMenu,UINT indexMenu,BOOL fByPosition,UINT wID,UINT fType,LPCWSTR dwTypeData,UINT fState)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
CFindFolder()94 CFindFolder::CFindFolder() :
95 m_hStopEvent(NULL)
96 {
97 }
98
_ILCreate(LPCWSTR lpszPath)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
_ILGetPath(LPCITEMIDLIST pidl)132 static LPCWSTR _ILGetPath(LPCITEMIDLIST pidl)
133 {
134 if (!pidl || !pidl->mkid.cb)
135 return NULL;
136 return (LPCWSTR) pidl->mkid.abID;
137 }
138
_ILGetFSPidl(LPCITEMIDLIST pidl)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)>
StrStrN(const TChar * lpFirst,const TString & lpSrch,UINT cchMax)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
StrFindNIA(const CHAR * lpFirst,const CStringA & lpSrch,UINT cchMax)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
StrFindNIW(const WCHAR * lpFirst,const CStringW & lpSrch,UINT cchMax)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
IsDataUnicode(IN PVOID Buffer,IN DWORD BufferSize,OUT ENCODING * Encoding OPTIONAL,OUT PDWORD SkipBytes OPTIONAL)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
SearchFile(LPCWSTR lpFilePath,_SearchData * pSearchData)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
FileNameMatch(LPCWSTR FindDataFileName,_SearchData * pSearchData)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
ContentsMatch(LPCWSTR szPath,_SearchData * pSearchData)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
AttribHiddenMatch(DWORD FileAttributes,_SearchData * pSearchData)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
RecursiveFind(LPCWSTR lpPath,_SearchData * pSearchData)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
SearchThreadProc(LPVOID lpParameter)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
NotifyConnections(DISPID id)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
StartSearch(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)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
StopSearch(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)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
AddResult(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)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
UpdateStatus(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)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 ***
GetDefaultSearchGUID(GUID * pguid)640 STDMETHODIMP CFindFolder::GetDefaultSearchGUID(GUID *pguid)
641 {
642 UNIMPLEMENTED;
643 return E_NOTIMPL;
644 }
645
EnumSearches(IEnumExtraSearch ** ppenum)646 STDMETHODIMP CFindFolder::EnumSearches(IEnumExtraSearch **ppenum)
647 {
648 UNIMPLEMENTED;
649 return E_NOTIMPL;
650 }
651
GetDefaultColumn(DWORD,ULONG * pSort,ULONG * pDisplay)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
GetDefaultColumnState(UINT iColumn,DWORD * pcsFlags)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
GetDetailsEx(PCUITEMID_CHILD pidl,const SHCOLUMNID * pscid,VARIANT * pv)671 STDMETHODIMP CFindFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
672 {
673 return m_pisfInner->GetDetailsEx(pidl, pscid, pv);
674 }
675
GetDetailsOf(PCUITEMID_CHILD pidl,UINT iColumn,SHELLDETAILS * pDetails)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
MapColumnToSCID(UINT iColumn,SHCOLUMNID * pscid)701 STDMETHODIMP CFindFolder::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
702 {
703 UNIMPLEMENTED;
704 return E_NOTIMPL;
705 }
706
707 // *** IShellFolder methods ***
ParseDisplayName(HWND hwndOwner,LPBC pbc,LPOLESTR lpszDisplayName,ULONG * pchEaten,PIDLIST_RELATIVE * ppidl,ULONG * pdwAttributes)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
EnumObjects(HWND hwndOwner,DWORD dwFlags,LPENUMIDLIST * ppEnumIDList)715 STDMETHODIMP CFindFolder::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
716 {
717 *ppEnumIDList = NULL;
718 return S_FALSE;
719 }
720
BindToObject(PCUIDLIST_RELATIVE pidl,LPBC pbcReserved,REFIID riid,LPVOID * ppvOut)721 STDMETHODIMP CFindFolder::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
722 {
723 UNIMPLEMENTED;
724 return E_NOTIMPL;
725 }
726
BindToStorage(PCUIDLIST_RELATIVE pidl,LPBC pbcReserved,REFIID riid,LPVOID * ppvOut)727 STDMETHODIMP CFindFolder::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
728 {
729 UNIMPLEMENTED;
730 return E_NOTIMPL;
731 }
732
CompareIDs(LPARAM lParam,PCUIDLIST_RELATIVE pidl1,PCUIDLIST_RELATIVE pidl2)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
CreateViewObject(HWND hwndOwner,REFIID riid,LPVOID * ppvOut)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
GetAttributesOf(UINT cidl,PCUITEMID_CHILD_ARRAY apidl,DWORD * rgfInOut)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 ***
QueryContextMenu(HMENU hMenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)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
InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)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
GetCommandString(UINT_PTR idCommand,UINT uFlags,UINT * lpReserved,LPSTR lpszName,UINT uMaxNameLen)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:
Create(IShellFolderView * pShellFolderView,IContextMenu * pInnerContextMenu,IContextMenu ** pContextMenu)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
GetUIObjectOf(HWND hwndOwner,UINT cidl,PCUITEMID_CHILD_ARRAY apidl,REFIID riid,UINT * prgfInOut,LPVOID * ppvOut)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
GetDisplayNameOf(PCUITEMID_CHILD pidl,DWORD dwFlags,LPSTRRET pName)893 STDMETHODIMP CFindFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET pName)
894 {
895 return m_pisfInner->GetDisplayNameOf(_ILGetFSPidl(pidl), dwFlags, pName);
896 }
897
SetNameOf(HWND hwndOwner,PCUITEMID_CHILD pidl,LPCOLESTR lpName,DWORD dwFlags,PITEMID_CHILD * pPidlOut)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 ***
MessageSFVCB(UINT uMsg,WPARAM wParam,LPARAM lParam)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 case SFVM_GETCOMMANDDIR:
941 {
942 HRESULT hr = E_FAIL;
943 if (m_shellFolderView)
944 {
945 PCUITEMID_CHILD *apidl;
946 UINT cidl = 0;
947 if (SUCCEEDED(hr = m_shellFolderView->GetSelectedObjects(&apidl, &cidl)))
948 {
949 if (cidl)
950 hr = StringCchCopyW((PWSTR)lParam, wParam, _ILGetPath(apidl[0]));
951 LocalFree(apidl);
952 }
953 }
954 return hr;
955 }
956 }
957 return E_NOTIMPL;
958 }
959
960 //// *** IPersistFolder2 methods ***
GetCurFolder(PIDLIST_ABSOLUTE * pidl)961 STDMETHODIMP CFindFolder::GetCurFolder(PIDLIST_ABSOLUTE *pidl)
962 {
963 *pidl = ILClone(m_pidl);
964 return S_OK;
965 }
966
967 // *** IPersistFolder methods ***
Initialize(PCIDLIST_ABSOLUTE pidl)968 STDMETHODIMP CFindFolder::Initialize(PCIDLIST_ABSOLUTE pidl)
969 {
970 m_pidl = ILClone(pidl);
971 if (!m_pidl)
972 return E_OUTOFMEMORY;
973
974 return SHELL32_CoCreateInitSF(m_pidl,
975 NULL,
976 NULL,
977 &CLSID_ShellFSFolder,
978 IID_PPV_ARG(IShellFolder2, &m_pisfInner));
979 }
980
981 // *** IPersist methods ***
GetClassID(CLSID * pClassId)982 STDMETHODIMP CFindFolder::GetClassID(CLSID *pClassId)
983 {
984 if (pClassId == NULL)
985 return E_INVALIDARG;
986 *pClassId = CLSID_FindFolder;
987 return S_OK;
988 }
989