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 // FIXME: Remove this declaration after the function has been fully implemented
14 EXTERN_C HRESULT
15 WINAPI
16 SHOpenFolderAndSelectItems(LPITEMIDLIST pidlFolder,
17                            UINT cidl,
18                            PCUITEMID_CHILD_ARRAY apidl,
19                            DWORD dwFlags);
20 
21 static HRESULT SHELL32_CoCreateInitSF(LPCITEMIDLIST pidlRoot, PERSIST_FOLDER_TARGET_INFO* ppfti,
22                                 LPCITEMIDLIST pidlChild, const GUID* clsid, REFIID riid, LPVOID *ppvOut)
23 {
24     HRESULT hr;
25     CComPtr<IShellFolder> pShellFolder;
26 
27     hr = SHCoCreateInstance(NULL, clsid, NULL, IID_PPV_ARG(IShellFolder, &pShellFolder));
28     if (FAILED(hr))
29         return hr;
30 
31     LPITEMIDLIST pidlAbsolute = ILCombine (pidlRoot, pidlChild);
32     CComPtr<IPersistFolder> ppf;
33     CComPtr<IPersistFolder3> ppf3;
34 
35     if (ppfti && SUCCEEDED(pShellFolder->QueryInterface(IID_PPV_ARG(IPersistFolder3, &ppf3))))
36     {
37         ppf3->InitializeEx(NULL, pidlAbsolute, ppfti);
38     }
39     else if (SUCCEEDED(pShellFolder->QueryInterface(IID_PPV_ARG(IPersistFolder, &ppf))))
40     {
41         ppf->Initialize(pidlAbsolute);
42     }
43     ILFree (pidlAbsolute);
44 
45     return pShellFolder->QueryInterface(riid, ppvOut);
46 }
47 
48 static void WINAPI _InsertMenuItemW(
49         HMENU hMenu,
50         UINT indexMenu,
51         BOOL fByPosition,
52         UINT wID,
53         UINT fType,
54         LPCWSTR dwTypeData,
55         UINT fState)
56 {
57     MENUITEMINFOW mii;
58     WCHAR wszText[100];
59 
60     ZeroMemory(&mii, sizeof(mii));
61     mii.cbSize = sizeof(mii);
62     if (fType == MFT_SEPARATOR)
63         mii.fMask = MIIM_ID | MIIM_TYPE;
64     else if (fType == MFT_STRING)
65     {
66         mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE;
67         if (IS_INTRESOURCE(dwTypeData))
68         {
69             if (LoadStringW(_AtlBaseModule.GetResourceInstance(), LOWORD((ULONG_PTR)dwTypeData), wszText, _countof(wszText)))
70                 mii.dwTypeData = wszText;
71             else
72             {
73                 ERR("failed to load string %p\n", dwTypeData);
74                 return;
75             }
76         }
77         else
78             mii.dwTypeData = (LPWSTR)dwTypeData;
79         mii.fState = fState;
80     }
81 
82     mii.wID = wID;
83     mii.fType = fType;
84     InsertMenuItemW(hMenu, indexMenu, fByPosition, &mii);
85 }
86 
87 struct FolderViewColumns
88 {
89     int iResource;
90     DWORD dwDefaultState;
91     int fmt;
92     int cxChar;
93 };
94 
95 static FolderViewColumns g_ColumnDefs[] =
96 {
97     {IDS_COL_NAME,      SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30},
98     {IDS_COL_LOCATION,  SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30},
99     {IDS_COL_RELEVANCE, SHCOLSTATE_TYPE_STR,                          LVCFMT_LEFT, 0}
100 };
101 
102 CFindFolder::CFindFolder() :
103     m_hStopEvent(NULL)
104 {
105 }
106 
107 static LPITEMIDLIST _ILCreate(LPCWSTR lpszPath)
108 {
109     CComHeapPtr<ITEMIDLIST> lpFSPidl(ILCreateFromPathW(lpszPath));
110     if (!lpFSPidl)
111     {
112         ERR("Failed to create pidl from path\n");
113         return NULL;
114     }
115     LPITEMIDLIST lpLastFSPidl = ILFindLastID(lpFSPidl);
116 
117     int pathLen = (PathFindFileNameW(lpszPath) - lpszPath) * sizeof(WCHAR);
118     int cbData = sizeof(WORD) + pathLen + lpLastFSPidl->mkid.cb;
119     LPITEMIDLIST pidl = (LPITEMIDLIST) SHAlloc(cbData + sizeof(WORD));
120     if (!pidl)
121         return NULL;
122 
123     LPBYTE p = (LPBYTE) pidl;
124     *((WORD *) p) = cbData;
125     p += sizeof(WORD);
126 
127     memcpy(p, lpszPath, pathLen);
128     p += pathLen - sizeof(WCHAR);
129     *((WCHAR *) p) = '\0';
130     p += sizeof(WCHAR);
131 
132     memcpy(p, lpLastFSPidl, lpLastFSPidl->mkid.cb);
133     p += lpLastFSPidl->mkid.cb;
134 
135     *((WORD *) p) = 0;
136 
137     return pidl;
138 }
139 
140 static LPCWSTR _ILGetPath(LPCITEMIDLIST pidl)
141 {
142     if (!pidl || !pidl->mkid.cb)
143         return NULL;
144     return (LPCWSTR) pidl->mkid.abID;
145 }
146 
147 static LPCITEMIDLIST _ILGetFSPidl(LPCITEMIDLIST pidl)
148 {
149     if (!pidl || !pidl->mkid.cb)
150         return pidl;
151     return (LPCITEMIDLIST) ((LPBYTE) pidl->mkid.abID
152                             + ((wcslen((LPCWSTR) pidl->mkid.abID) + 1) * sizeof(WCHAR)));
153 }
154 
155 struct _SearchData
156 {
157     HWND hwnd;
158     HANDLE hStopEvent;
159     CStringW szPath;
160     CStringW szFileName;
161     CStringA szQueryA;
162     CStringW szQueryW;
163     BOOL SearchHidden;
164     CComPtr<CFindFolder> pFindFolder;
165 };
166 
167 template<typename TChar, typename TString, int (&StrNCmp)(const TChar *, const TChar *, size_t)>
168 static const TChar* StrStrN(const TChar *lpFirst, const TString &lpSrch, UINT cchMax)
169 {
170     if (!lpFirst || lpSrch.IsEmpty() || !cchMax)
171         return NULL;
172 
173     for (UINT i = cchMax; i > 0 && *lpFirst; i--, lpFirst++)
174     {
175         if (!StrNCmp(lpFirst, lpSrch, lpSrch.GetLength()))
176             return (const TChar*)lpFirst;
177     }
178 
179     return NULL;
180 }
181 
182 template<typename TChar, typename TString, int (&StrNCmp)(const TChar *, const TChar *, size_t)>
183 static UINT StrStrNCount(const TChar *lpFirst, const TString &lpSrch, UINT cchMax)
184 {
185     const TChar *lpSearchEnd = lpFirst + cchMax;
186     UINT uCount = 0;
187     while (lpFirst < lpSearchEnd && (lpFirst = StrStrN<TChar, TString, StrNCmp>(lpFirst, lpSrch, cchMax)))
188     {
189         uCount++;
190         lpFirst += lpSrch.GetLength();
191         cchMax = lpSearchEnd - lpFirst;
192     }
193     return uCount;
194 }
195 
196 static UINT StrStrCountNIA(const CHAR *lpFirst, const CStringA &lpSrch, UINT cchMax)
197 {
198     return StrStrNCount<CHAR, CStringA, _strnicmp>(lpFirst, lpSrch, cchMax);
199 }
200 
201 static UINT StrStrCountNIW(const WCHAR *lpFirst, const CStringW &lpSrch, UINT cchMax)
202 {
203     return StrStrNCount<WCHAR, CStringW, _wcsnicmp>(lpFirst, lpSrch, cchMax);
204 }
205 
206 static UINT SearchFile(LPCWSTR lpFilePath, _SearchData *pSearchData)
207 {
208     HANDLE hFile = CreateFileW(lpFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
209     if (hFile == INVALID_HANDLE_VALUE)
210         return 0;
211 
212     DWORD size = GetFileSize(hFile, NULL);
213     HANDLE hFileMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
214     CloseHandle(hFile);
215     if (hFileMap == INVALID_HANDLE_VALUE)
216         return 0;
217 
218     LPBYTE lpFileContent = (LPBYTE) MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
219     CloseHandle(hFileMap);
220     if (!lpFileContent)
221         return 0;
222 
223     UINT uMatches = 0;
224     // Check for UTF-16 BOM
225     if (size >= 2 && lpFileContent[0] == 0xFF && lpFileContent[1] == 0xFE)
226     {
227         uMatches = StrStrCountNIW((LPCWSTR) lpFileContent, pSearchData->szQueryW, size / sizeof(WCHAR));
228     }
229     else
230     {
231         uMatches = StrStrCountNIA((LPCSTR) lpFileContent, pSearchData->szQueryA, size / sizeof(CHAR));
232     }
233 
234     UnmapViewOfFile(lpFileContent);
235 
236     return uMatches;
237 }
238 
239 static BOOL FileNameMatch(LPCWSTR FindDataFileName, _SearchData *pSearchData)
240 {
241     if (pSearchData->szFileName.IsEmpty() || PathMatchSpecW(FindDataFileName, pSearchData->szFileName))
242     {
243         return TRUE;
244     }
245     return FALSE;
246 }
247 
248 static BOOL ContentsMatch(LPCWSTR szPath, _SearchData *pSearchData)
249 {
250     if (pSearchData->szQueryA.IsEmpty() || SearchFile(szPath, pSearchData))
251     {
252         return TRUE;
253     }
254     return FALSE;
255 }
256 
257 static BOOL AttribHiddenMatch(DWORD FileAttributes, _SearchData *pSearchData)
258 {
259     if (!(FileAttributes & FILE_ATTRIBUTE_HIDDEN) || (pSearchData->SearchHidden))
260     {
261         return TRUE;
262     }
263     return FALSE;
264 }
265 
266 static UINT RecursiveFind(LPCWSTR lpPath, _SearchData *pSearchData)
267 {
268     if (WaitForSingleObject(pSearchData->hStopEvent, 0) != WAIT_TIMEOUT)
269         return 0;
270 
271     WCHAR szPath[MAX_PATH];
272     WIN32_FIND_DATAW FindData;
273     HANDLE hFindFile;
274     BOOL bMoreFiles = TRUE;
275     UINT uTotalFound = 0;
276 
277     PathCombineW(szPath, lpPath, L"*.*");
278 
279     for (hFindFile = FindFirstFileW(szPath, &FindData);
280         bMoreFiles && hFindFile != INVALID_HANDLE_VALUE;
281         bMoreFiles = FindNextFileW(hFindFile, &FindData))
282     {
283         if (!wcscmp(FindData.cFileName, L".") || !wcscmp(FindData.cFileName, L".."))
284             continue;
285 
286         PathCombineW(szPath, lpPath, FindData.cFileName);
287 
288         if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
289         {
290             CStringW status;
291             if (FileNameMatch(FindData.cFileName, pSearchData)
292                 && AttribHiddenMatch(FindData.dwFileAttributes, pSearchData))
293             {
294                 PostMessageW(pSearchData->hwnd, WM_SEARCH_ADD_RESULT, 0, (LPARAM) StrDupW(szPath));
295                 uTotalFound++;
296             }
297             status.Format(IDS_SEARCH_FOLDER, FindData.cFileName);
298             PostMessageW(pSearchData->hwnd, WM_SEARCH_UPDATE_STATUS, 0, (LPARAM) StrDupW(status.GetBuffer()));
299 
300             uTotalFound += RecursiveFind(szPath, pSearchData);
301         }
302         else if (FileNameMatch(FindData.cFileName, pSearchData)
303                 && AttribHiddenMatch(FindData.dwFileAttributes, pSearchData)
304                 && ContentsMatch(szPath, pSearchData))
305         {
306             uTotalFound++;
307             PostMessageW(pSearchData->hwnd, WM_SEARCH_ADD_RESULT, 0, (LPARAM) StrDupW(szPath));
308         }
309     }
310 
311     if (hFindFile != INVALID_HANDLE_VALUE)
312         FindClose(hFindFile);
313 
314     return uTotalFound;
315 }
316 
317 DWORD WINAPI CFindFolder::SearchThreadProc(LPVOID lpParameter)
318 {
319     _SearchData *data = static_cast<_SearchData*>(lpParameter);
320 
321     data->pFindFolder->NotifyConnections(DISPID_SEARCHSTART);
322 
323     UINT uTotalFound = RecursiveFind(data->szPath, data);
324 
325     data->pFindFolder->NotifyConnections(DISPID_SEARCHCOMPLETE);
326 
327     CStringW status;
328     status.Format(IDS_SEARCH_FILES_FOUND, uTotalFound);
329     ::PostMessageW(data->hwnd, WM_SEARCH_UPDATE_STATUS, 0, (LPARAM) StrDupW(status.GetBuffer()));
330     ::SendMessageW(data->hwnd, WM_SEARCH_STOP, 0, 0);
331 
332     CloseHandle(data->hStopEvent);
333     delete data;
334 
335     return 0;
336 }
337 
338 void CFindFolder::NotifyConnections(DISPID id)
339 {
340     DISPPARAMS dispatchParams = {0};
341     CComDynamicUnkArray &subscribers =
342         IConnectionPointImpl<CFindFolder, &DIID_DSearchCommandEvents>::m_vec;
343     for (IUnknown** pSubscriber = subscribers.begin(); pSubscriber < subscribers.end(); pSubscriber++)
344     {
345         if (!*pSubscriber)
346             continue;
347 
348         CComPtr<IDispatch> pDispatch;
349         HRESULT hResult = (*pSubscriber)->QueryInterface(IID_PPV_ARG(IDispatch, &pDispatch));
350         if (!FAILED_UNEXPECTEDLY(hResult))
351             pDispatch->Invoke(id, GUID_NULL, 0, DISPATCH_METHOD, &dispatchParams, NULL, NULL, NULL);
352     }
353 }
354 
355 LRESULT CFindFolder::StartSearch(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
356 {
357     HKEY hkey;
358     DWORD size = sizeof(DWORD);
359     DWORD result;
360     DWORD SearchHiddenValue = 0;
361 
362     if (!lParam)
363         return 0;
364 
365     // Clear all previous search results
366     UINT uItemIndex;
367     m_shellFolderView->RemoveObject(NULL, &uItemIndex);
368 
369     _SearchData* pSearchData = new _SearchData();
370     pSearchData->pFindFolder = this;
371     pSearchData->hwnd = m_hWnd;
372 
373     SearchStart *pSearchParams = (SearchStart *) lParam;
374     pSearchData->szPath = pSearchParams->szPath;
375     pSearchData->szFileName = pSearchParams->szFileName;
376     pSearchData->szQueryA = pSearchParams->szQuery;
377     pSearchData->szQueryW = pSearchParams->szQuery;
378     pSearchData->SearchHidden = pSearchParams->SearchHidden;
379     SHFree(pSearchParams);
380 
381     TRACE("pSearchParams->SearchHidden is '%d'.\n", pSearchData->SearchHidden);
382 
383     if (pSearchData->SearchHidden)
384         SearchHiddenValue = 1;
385     else
386         SearchHiddenValue = 0;
387 
388     /* Placing the code to save the changed settings to the registry here has the effect of not saving any changes */
389     /* to the registry unless the user clicks on the "Search" button. This is the same as what we see in Windows.  */
390     result = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer", 0, KEY_SET_VALUE, &hkey);
391     if (result == ERROR_SUCCESS)
392     {
393         if (RegSetValueExW(hkey, L"SearchHidden", NULL, REG_DWORD, (const BYTE*)&SearchHiddenValue, size) == ERROR_SUCCESS)
394         {
395             TRACE("SearchHidden is '%d'.\n", SearchHiddenValue);
396         }
397         else
398         {
399             ERR("RegSetValueEx for \"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SearchHidden\" Failed.\n");
400         }
401         RegCloseKey(hkey);
402     }
403     else
404     {
405         ERR("RegOpenKey for \"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\" Failed.\n");
406     }
407 
408     if (m_hStopEvent)
409         SetEvent(m_hStopEvent);
410     pSearchData->hStopEvent = m_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
411 
412     if (!SHCreateThread(SearchThreadProc, pSearchData, NULL, NULL))
413     {
414         SHFree(pSearchData);
415         return 0;
416     }
417 
418     return 0;
419 }
420 
421 LRESULT CFindFolder::StopSearch(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
422 {
423     if (m_hStopEvent)
424     {
425         SetEvent(m_hStopEvent);
426         m_hStopEvent = NULL;
427     }
428     return 0;
429 }
430 
431 LRESULT CFindFolder::AddResult(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
432 {
433     if (!lParam)
434         return 0;
435 
436     CComHeapPtr<WCHAR> lpPath((LPWSTR) lParam);
437 
438     CComHeapPtr<ITEMIDLIST> lpSearchPidl(_ILCreate(lpPath));
439     if (lpSearchPidl)
440     {
441         UINT uItemIndex;
442         m_shellFolderView->AddObject(lpSearchPidl, &uItemIndex);
443     }
444 
445     return 0;
446 }
447 
448 LRESULT CFindFolder::UpdateStatus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
449 {
450     CComHeapPtr<WCHAR> status((LPWSTR) lParam);
451     if (m_shellBrowser)
452     {
453         m_shellBrowser->SetStatusTextSB(status);
454     }
455 
456     return 0;
457 }
458 
459 // *** IShellFolder2 methods ***
460 STDMETHODIMP CFindFolder::GetDefaultSearchGUID(GUID *pguid)
461 {
462     UNIMPLEMENTED;
463     return E_NOTIMPL;
464 }
465 
466 STDMETHODIMP CFindFolder::EnumSearches(IEnumExtraSearch **ppenum)
467 {
468     UNIMPLEMENTED;
469     return E_NOTIMPL;
470 }
471 
472 STDMETHODIMP CFindFolder::GetDefaultColumn(DWORD, ULONG *pSort, ULONG *pDisplay)
473 {
474     if (pSort)
475         *pSort = 0;
476     if (pDisplay)
477         *pDisplay = 0;
478     return S_OK;
479 }
480 
481 STDMETHODIMP CFindFolder::GetDefaultColumnState(UINT iColumn, DWORD *pcsFlags)
482 {
483     if (!pcsFlags)
484         return E_INVALIDARG;
485     if (iColumn >= _countof(g_ColumnDefs))
486         return m_pisfInner->GetDefaultColumnState(iColumn - _countof(g_ColumnDefs) + 1, pcsFlags);
487     *pcsFlags = g_ColumnDefs[iColumn].dwDefaultState;
488     return S_OK;
489 }
490 
491 STDMETHODIMP CFindFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
492 {
493     return m_pisfInner->GetDetailsEx(pidl, pscid, pv);
494 }
495 
496 STDMETHODIMP CFindFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *pDetails)
497 {
498     if (iColumn >= _countof(g_ColumnDefs))
499         return m_pisfInner->GetDetailsOf(_ILGetFSPidl(pidl), iColumn - _countof(g_ColumnDefs) + 1, pDetails);
500 
501     pDetails->cxChar = g_ColumnDefs[iColumn].cxChar;
502     pDetails->fmt = g_ColumnDefs[iColumn].fmt;
503 
504     if (!pidl)
505         return SHSetStrRet(&pDetails->str, _AtlBaseModule.GetResourceInstance(), g_ColumnDefs[iColumn].iResource);
506 
507     if (iColumn == 1)
508     {
509         return SHSetStrRet(&pDetails->str, _ILGetPath(pidl));
510     }
511 
512     return GetDisplayNameOf(pidl, SHGDN_NORMAL, &pDetails->str);
513 }
514 
515 STDMETHODIMP CFindFolder::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
516 {
517     UNIMPLEMENTED;
518     return E_NOTIMPL;
519 }
520 
521 // *** IShellFolder methods ***
522 STDMETHODIMP CFindFolder::ParseDisplayName(HWND hwndOwner, LPBC pbc, LPOLESTR lpszDisplayName, ULONG *pchEaten,
523                                            PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes)
524 {
525     UNIMPLEMENTED;
526     return E_NOTIMPL;
527 }
528 
529 STDMETHODIMP CFindFolder::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
530 {
531     *ppEnumIDList = NULL;
532     return S_FALSE;
533 }
534 
535 STDMETHODIMP CFindFolder::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
536 {
537     UNIMPLEMENTED;
538     return E_NOTIMPL;
539 }
540 
541 STDMETHODIMP CFindFolder::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
542 {
543     UNIMPLEMENTED;
544     return E_NOTIMPL;
545 }
546 
547 STDMETHODIMP CFindFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
548 {
549     return m_pisfInner->CompareIDs(lParam, _ILGetFSPidl(pidl1), _ILGetFSPidl(pidl2));
550 }
551 
552 STDMETHODIMP CFindFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppvOut)
553 {
554     if (riid == IID_IShellView)
555     {
556         SFV_CREATE sfvparams = {};
557         sfvparams.cbSize = sizeof(SFV_CREATE);
558         sfvparams.pshf = this;
559         sfvparams.psfvcb = this;
560         HRESULT hr = SHCreateShellFolderView(&sfvparams, (IShellView **) ppvOut);
561         if (FAILED_UNEXPECTEDLY(hr))
562         {
563             return hr;
564         }
565 
566         return ((IShellView * ) * ppvOut)->QueryInterface(IID_PPV_ARG(IShellFolderView, &m_shellFolderView));
567     }
568     return E_NOINTERFACE;
569 }
570 
571 STDMETHODIMP CFindFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD *rgfInOut)
572 {
573     CComHeapPtr<PCITEMID_CHILD> aFSPidl;
574     aFSPidl.Allocate(cidl);
575     for (UINT i = 0; i < cidl; i++)
576     {
577         aFSPidl[i] = _ILGetFSPidl(apidl[i]);
578     }
579 
580     return m_pisfInner->GetAttributesOf(cidl, aFSPidl, rgfInOut);
581 }
582 
583 class CFindFolderContextMenu :
584         public IContextMenu,
585         public CComObjectRootEx<CComMultiThreadModelNoCS>
586 {
587     CComPtr<IContextMenu> m_pInner;
588     CComPtr<IShellFolderView> m_shellFolderView;
589     UINT m_firstCmdId;
590     static const UINT ADDITIONAL_MENU_ITEMS = 2;
591 
592     //// *** IContextMenu methods ***
593     STDMETHODIMP QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
594     {
595         m_firstCmdId = indexMenu;
596         _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst++, MFT_STRING, MAKEINTRESOURCEW(IDS_SEARCH_OPEN_FOLDER), MFS_ENABLED);
597         _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst++, MFT_SEPARATOR, NULL, 0);
598         return m_pInner->QueryContextMenu(hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
599     }
600 
601     STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
602     {
603         if (!IS_INTRESOURCE(lpcmi->lpVerb))
604         {
605             return m_pInner->InvokeCommand(lpcmi);
606         }
607 
608         if (LOWORD(lpcmi->lpVerb) < m_firstCmdId + ADDITIONAL_MENU_ITEMS)
609         {
610             PCUITEMID_CHILD *apidl;
611             UINT cidl;
612             HRESULT hResult = m_shellFolderView->GetSelectedObjects(&apidl, &cidl);
613             if (FAILED_UNEXPECTEDLY(hResult))
614                 return hResult;
615 
616             for (UINT i = 0; i < cidl; i++)
617             {
618                 CComHeapPtr<ITEMIDLIST> folderPidl(ILCreateFromPathW(_ILGetPath(apidl[i])));
619                 if (!folderPidl)
620                     return E_OUTOFMEMORY;
621                 CComHeapPtr<ITEMIDLIST> filePidl(ILCombine(folderPidl, _ILGetFSPidl(apidl[i])));
622                 if (!filePidl)
623                     return E_OUTOFMEMORY;
624                 SHOpenFolderAndSelectItems(folderPidl, 1, &filePidl, 0);
625             }
626             return S_OK;
627         }
628 
629         CMINVOKECOMMANDINFOEX actualCmdInfo;
630         memcpy(&actualCmdInfo, lpcmi, lpcmi->cbSize);
631         actualCmdInfo.lpVerb -= ADDITIONAL_MENU_ITEMS;
632         return m_pInner->InvokeCommand((CMINVOKECOMMANDINFO *)&actualCmdInfo);
633     }
634 
635     STDMETHODIMP GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen)
636     {
637         return m_pInner->GetCommandString(idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
638     }
639 
640 public:
641     static HRESULT Create(IShellFolderView *pShellFolderView, IContextMenu *pInnerContextMenu, IContextMenu **pContextMenu)
642     {
643         CComObject<CFindFolderContextMenu> *pObj;
644         HRESULT hResult = CComObject<CFindFolderContextMenu>::CreateInstance(&pObj);
645         if (FAILED_UNEXPECTEDLY(hResult))
646             return hResult;
647         pObj->m_shellFolderView = pShellFolderView;
648         pObj->m_pInner = pInnerContextMenu;
649         return pObj->QueryInterface(IID_PPV_ARG(IContextMenu, pContextMenu));
650     }
651 
652     BEGIN_COM_MAP(CFindFolderContextMenu)
653         COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu)
654     END_COM_MAP()
655 };
656 
657 STDMETHODIMP CFindFolder::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid,
658                                         UINT *prgfInOut, LPVOID *ppvOut)
659 {
660     if (cidl <= 0)
661     {
662         return m_pisfInner->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, ppvOut);
663     }
664 
665     CComHeapPtr<PCITEMID_CHILD> aFSPidl;
666     aFSPidl.Allocate(cidl);
667     for (UINT i = 0; i < cidl; i++)
668     {
669         aFSPidl[i] = _ILGetFSPidl(apidl[i]);
670     }
671 
672     if (riid == IID_IContextMenu)
673     {
674         CComHeapPtr<ITEMIDLIST> folderPidl(ILCreateFromPathW(_ILGetPath(apidl[0])));
675         if (!folderPidl)
676             return E_OUTOFMEMORY;
677         CComPtr<IShellFolder> pDesktopFolder;
678         HRESULT hResult = SHGetDesktopFolder(&pDesktopFolder);
679         if (FAILED_UNEXPECTEDLY(hResult))
680             return hResult;
681         CComPtr<IShellFolder> pShellFolder;
682         hResult = pDesktopFolder->BindToObject(folderPidl, NULL, IID_PPV_ARG(IShellFolder, &pShellFolder));
683         if (FAILED_UNEXPECTEDLY(hResult))
684             return hResult;
685         CComPtr<IContextMenu> pContextMenu;
686         hResult = pShellFolder->GetUIObjectOf(hwndOwner, cidl, aFSPidl, riid, prgfInOut, (LPVOID *)&pContextMenu);
687         if (FAILED_UNEXPECTEDLY(hResult))
688             return hResult;
689         return CFindFolderContextMenu::Create(m_shellFolderView, pContextMenu, (IContextMenu **)ppvOut);
690     }
691 
692     return m_pisfInner->GetUIObjectOf(hwndOwner, cidl, aFSPidl, riid, prgfInOut, ppvOut);
693 }
694 
695 STDMETHODIMP CFindFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET pName)
696 {
697     return m_pisfInner->GetDisplayNameOf(_ILGetFSPidl(pidl), dwFlags, pName);
698 }
699 
700 STDMETHODIMP CFindFolder::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags,
701                                     PITEMID_CHILD *pPidlOut)
702 {
703     UNIMPLEMENTED;
704     return E_NOTIMPL;
705 }
706 
707 //// *** IShellFolderViewCB method ***
708 STDMETHODIMP CFindFolder::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam)
709 {
710     switch (uMsg)
711     {
712         case SFVM_DEFVIEWMODE:
713         {
714             FOLDERVIEWMODE *pViewMode = (FOLDERVIEWMODE *) lParam;
715             *pViewMode = FVM_DETAILS;
716             return S_OK;
717         }
718         case SFVM_WINDOWCREATED:
719         {
720             // Subclass window to receive window messages
721             SubclassWindow((HWND) wParam);
722 
723             // Get shell browser for updating status bar text
724             CComPtr<IServiceProvider> pServiceProvider;
725             HRESULT hr = m_shellFolderView->QueryInterface(IID_PPV_ARG(IServiceProvider, &pServiceProvider));
726             if (FAILED_UNEXPECTEDLY(hr))
727                 return hr;
728             hr = pServiceProvider->QueryService(SID_SShellBrowser, IID_PPV_ARG(IShellBrowser, &m_shellBrowser));
729             if (FAILED_UNEXPECTEDLY(hr))
730                 return hr;
731 
732             // Open search bar
733             CComPtr<IWebBrowser2> pWebBrowser2;
734             hr = m_shellBrowser->QueryInterface(IID_PPV_ARG(IWebBrowser2, &pWebBrowser2));
735             if (FAILED_UNEXPECTEDLY(hr))
736                 return hr;
737             WCHAR pwszGuid[MAX_PATH];
738             StringFromGUID2(CLSID_FileSearchBand, pwszGuid, _countof(pwszGuid));
739             CComVariant searchBar(pwszGuid);
740             return pWebBrowser2->ShowBrowserBar(&searchBar, NULL, NULL);
741         }
742     }
743     return E_NOTIMPL;
744 }
745 
746 //// *** IPersistFolder2 methods ***
747 STDMETHODIMP CFindFolder::GetCurFolder(PIDLIST_ABSOLUTE *pidl)
748 {
749     *pidl = ILClone(m_pidl);
750     return S_OK;
751 }
752 
753 // *** IPersistFolder methods ***
754 STDMETHODIMP CFindFolder::Initialize(PCIDLIST_ABSOLUTE pidl)
755 {
756     m_pidl = ILClone(pidl);
757     if (!m_pidl)
758         return E_OUTOFMEMORY;
759 
760     return SHELL32_CoCreateInitSF(m_pidl,
761                                   NULL,
762                                   NULL,
763                                   &CLSID_ShellFSFolder,
764                                   IID_PPV_ARG(IShellFolder2, &m_pisfInner));
765 }
766 
767 // *** IPersist methods ***
768 STDMETHODIMP CFindFolder::GetClassID(CLSID *pClassId)
769 {
770     if (pClassId == NULL)
771         return E_INVALIDARG;
772     *pClassId = CLSID_FindFolder;
773     return S_OK;
774 }
775