1 /*
2  * Control panel folder
3  *
4  * Copyright 2003 Martin Fuchs
5  * Copyright 2009 Andrew Hill
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #include <precomp.h>
23 
24 WINE_DEFAULT_DEBUG_CHANNEL(shell);
25 
26 /***********************************************************************
27 *   control panel implementation in shell namespace
28 */
29 
30 class CControlPanelEnum :
31     public CEnumIDListBase
32 {
33     public:
34         CControlPanelEnum();
35         ~CControlPanelEnum();
36         HRESULT WINAPI Initialize(DWORD dwFlags, IEnumIDList* pRegEnumerator);
37         BOOL RegisterCPanelApp(LPCWSTR path);
38         int RegisterRegistryCPanelApps(HKEY hkey_root, LPCWSTR szRepPath);
39         BOOL CreateCPanelEnumList(DWORD dwFlags);
40 
41         BEGIN_COM_MAP(CControlPanelEnum)
42         COM_INTERFACE_ENTRY_IID(IID_IEnumIDList, IEnumIDList)
43         END_COM_MAP()
44 };
45 
46 /***********************************************************************
47 *   IShellFolder [ControlPanel] implementation
48 */
49 
50 static const shvheader ControlPanelSFHeader[] = {
51     {IDS_SHV_COLUMN_NAME, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 20},/*FIXME*/
52     {IDS_SHV_COLUMN_COMMENTS, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 80},/*FIXME*/
53 };
54 
55 enum controlpanel_columns
56 {
57     CONTROLPANEL_COL_NAME,
58     CONTROLPANEL_COL_COMMENT,
59     CONTROLPANEL_COL_COUNT,
60 };
61 
62 CControlPanelEnum::CControlPanelEnum()
63 {
64 }
65 
66 CControlPanelEnum::~CControlPanelEnum()
67 {
68 }
69 
70 HRESULT WINAPI CControlPanelEnum::Initialize(DWORD dwFlags, IEnumIDList* pRegEnumerator)
71 {
72     if (CreateCPanelEnumList(dwFlags) == FALSE)
73         return E_FAIL;
74     AppendItemsFromEnumerator(pRegEnumerator);
75     return S_OK;
76 }
77 
78 static LPITEMIDLIST _ILCreateCPanelApplet(LPCWSTR pszName, LPCWSTR pszDisplayName, LPCWSTR pszComment, int iIconIdx)
79 {
80     PIDLCPanelStruct *pCP;
81     LPITEMIDLIST pidl;
82     LPPIDLDATA pData;
83     int cchName, cchDisplayName, cchComment, cbData;
84 
85     /* Calculate lengths of given strings */
86     cchName = wcslen(pszName);
87     cchDisplayName = wcslen(pszDisplayName);
88     cchComment = wcslen(pszComment);
89 
90     /* Allocate PIDL */
91     cbData = sizeof(pidl->mkid.cb) + sizeof(pData->type) + sizeof(pData->u.cpanel) - sizeof(pData->u.cpanel.szName)
92              + (cchName + cchDisplayName + cchComment + 3) * sizeof(WCHAR);
93     pidl = (LPITEMIDLIST)SHAlloc(cbData + sizeof(WORD));
94     if (!pidl)
95         return NULL;
96 
97     /* Copy data to allocated memory */
98     pidl->mkid.cb = cbData;
99     pData = (PIDLDATA *)pidl->mkid.abID;
100     pData->type = PT_CPLAPPLET;
101 
102     pCP = &pData->u.cpanel;
103     pCP->dummy = 0;
104     pCP->iconIdx = iIconIdx;
105     wcscpy(pCP->szName, pszName);
106     pCP->offsDispName = cchName + 1;
107     wcscpy(pCP->szName + pCP->offsDispName, pszDisplayName);
108     pCP->offsComment = pCP->offsDispName + cchDisplayName + 1;
109     wcscpy(pCP->szName + pCP->offsComment, pszComment);
110 
111     /* Add PIDL NULL terminator */
112     *(WORD*)(pCP->szName + pCP->offsComment + cchComment + 1) = 0;
113 
114     pcheck(pidl);
115 
116     return pidl;
117 }
118 
119 /**************************************************************************
120  *  _ILGetCPanelPointer()
121  * gets a pointer to the control panel struct stored in the pidl
122  */
123 static PIDLCPanelStruct *_ILGetCPanelPointer(LPCITEMIDLIST pidl)
124 {
125     LPPIDLDATA pdata = _ILGetDataPointer(pidl);
126 
127     if (pdata && pdata->type == PT_CPLAPPLET)
128         return (PIDLCPanelStruct *) & (pdata->u.cpanel);
129 
130     return NULL;
131 }
132 
133 HRESULT CCPLExtractIcon_CreateInstance(IShellFolder * psf, LPCITEMIDLIST pidl, REFIID riid, LPVOID * ppvOut)
134 {
135     PIDLCPanelStruct *pData = _ILGetCPanelPointer(pidl);
136     if (!pData)
137         return E_FAIL;
138 
139     CComPtr<IDefaultExtractIconInit> initIcon;
140     HRESULT hr = SHCreateDefaultExtractIcon(IID_PPV_ARG(IDefaultExtractIconInit, &initIcon));
141     if (FAILED_UNEXPECTEDLY(hr))
142         return hr;
143 
144     initIcon->SetNormalIcon(pData->szName, (int)pData->iconIdx != -1 ? pData->iconIdx : 0);
145 
146     return initIcon->QueryInterface(riid, ppvOut);
147 }
148 
149 BOOL CControlPanelEnum::RegisterCPanelApp(LPCWSTR wpath)
150 {
151     CPlApplet* applet = Control_LoadApplet(0, wpath, NULL);
152     int iconIdx;
153 
154     if (applet)
155     {
156         for (UINT i = 0; i < applet->count; ++i)
157         {
158             if (applet->info[i].idIcon > 0)
159                 iconIdx = -applet->info[i].idIcon; /* negative icon index instead of icon number */
160             else
161                 iconIdx = 0;
162 
163             LPITEMIDLIST pidl = _ILCreateCPanelApplet(wpath,
164                                                       applet->info[i].name,
165                                                       applet->info[i].info,
166                                                       iconIdx);
167 
168             if (pidl)
169                 AddToEnumList(pidl);
170         }
171         Control_UnloadApplet(applet);
172     }
173     return TRUE;
174 }
175 
176 int CControlPanelEnum::RegisterRegistryCPanelApps(HKEY hkey_root, LPCWSTR szRepPath)
177 {
178     WCHAR name[MAX_PATH];
179     WCHAR value[MAX_PATH];
180     HKEY hkey;
181 
182     int cnt = 0;
183 
184     if (RegOpenKeyW(hkey_root, szRepPath, &hkey) == ERROR_SUCCESS)
185     {
186         int idx = 0;
187 
188         for(; ; idx++)
189         {
190             DWORD nameLen = MAX_PATH;
191             DWORD valueLen = MAX_PATH;
192             WCHAR buffer[MAX_PATH];
193 
194             if (RegEnumValueW(hkey, idx, name, &nameLen, NULL, NULL, (LPBYTE)&value, &valueLen) != ERROR_SUCCESS)
195                 break;
196 
197             if (ExpandEnvironmentStringsW(value, buffer, MAX_PATH))
198             {
199                 wcscpy(value, buffer);
200             }
201 
202             if (RegisterCPanelApp(value))
203                 ++cnt;
204         }
205         RegCloseKey(hkey);
206     }
207 
208     return cnt;
209 }
210 
211 /**************************************************************************
212  *  CControlPanelEnum::CreateCPanelEnumList()
213  */
214 BOOL CControlPanelEnum::CreateCPanelEnumList(DWORD dwFlags)
215 {
216     WCHAR szPath[MAX_PATH];
217     WIN32_FIND_DATAW wfd;
218     HANDLE hFile;
219 
220     TRACE("(%p)->(flags=0x%08x)\n", this, dwFlags);
221 
222     /* enumerate the control panel applets */
223     if (dwFlags & SHCONTF_NONFOLDERS)
224     {
225         LPWSTR p;
226 
227         GetSystemDirectoryW(szPath, MAX_PATH);
228         p = PathAddBackslashW(szPath);
229         wcscpy(p, L"*.cpl");
230 
231         hFile = FindFirstFileW(szPath, &wfd);
232 
233         if (hFile != INVALID_HANDLE_VALUE)
234         {
235             do
236             {
237                 if (!(dwFlags & SHCONTF_INCLUDEHIDDEN) && (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
238                     continue;
239 
240                 if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
241                     wcscpy(p, wfd.cFileName);
242                     if (wcscmp(wfd.cFileName, L"ncpa.cpl"))
243                         RegisterCPanelApp(szPath);
244                 }
245             } while(FindNextFileW(hFile, &wfd));
246             FindClose(hFile);
247         }
248 
249         RegisterRegistryCPanelApps(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Control Panel\\Cpls");
250         RegisterRegistryCPanelApps(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Control Panel\\Cpls");
251     }
252     return TRUE;
253 }
254 
255 CControlPanelFolder::CControlPanelFolder()
256 {
257     pidlRoot = NULL;    /* absolute pidl */
258 }
259 
260 CControlPanelFolder::~CControlPanelFolder()
261 {
262     TRACE("-- destroying IShellFolder(%p)\n", this);
263     SHFree(pidlRoot);
264 }
265 
266 /**************************************************************************
267 *    CControlPanelFolder::ParseDisplayName
268 */
269 HRESULT WINAPI CControlPanelFolder::ParseDisplayName(
270     HWND hwndOwner,
271     LPBC pbc,
272     LPOLESTR lpszDisplayName,
273     DWORD *pchEaten,
274     PIDLIST_RELATIVE *ppidl,
275     DWORD *pdwAttributes)
276 {
277     /* We only support parsing guid names */
278     return m_regFolder->ParseDisplayName(hwndOwner, pbc, lpszDisplayName, pchEaten, ppidl, pdwAttributes);
279 }
280 
281 /**************************************************************************
282 *        CControlPanelFolder::EnumObjects
283 */
284 HRESULT WINAPI CControlPanelFolder::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
285 {
286     CComPtr<IEnumIDList> pRegEnumerator;
287     m_regFolder->EnumObjects(hwndOwner, dwFlags, &pRegEnumerator);
288 
289     return ShellObjectCreatorInit<CControlPanelEnum>(dwFlags, pRegEnumerator, IID_PPV_ARG(IEnumIDList, ppEnumIDList));
290 }
291 
292 /**************************************************************************
293 *        CControlPanelFolder::BindToObject
294 */
295 HRESULT WINAPI CControlPanelFolder::BindToObject(
296     PCUIDLIST_RELATIVE pidl,
297     LPBC pbcReserved,
298     REFIID riid,
299     LPVOID *ppvOut)
300 {
301     return m_regFolder->BindToObject(pidl, pbcReserved, riid, ppvOut);
302 }
303 
304 /**************************************************************************
305 *    CControlPanelFolder::BindToStorage
306 */
307 HRESULT WINAPI CControlPanelFolder::BindToStorage(
308     PCUIDLIST_RELATIVE pidl,
309     LPBC pbcReserved,
310     REFIID riid,
311     LPVOID *ppvOut)
312 {
313     FIXME("(%p)->(pidl=%p,%p,%s,%p) stub\n", this, pidl, pbcReserved, shdebugstr_guid(&riid), ppvOut);
314 
315     *ppvOut = NULL;
316     return E_NOTIMPL;
317 }
318 
319 /**************************************************************************
320 *     CControlPanelFolder::CompareIDs
321 */
322 HRESULT WINAPI CControlPanelFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
323 {
324     /* Dont use SHELL32_CompareGuidItems because it would cause guid items to come first */
325     if (_ILIsSpecialFolder(pidl1) || _ILIsSpecialFolder(pidl2))
326     {
327         return SHELL32_CompareDetails(this, lParam, pidl1, pidl2);
328     }
329     PIDLCPanelStruct *pData1 = _ILGetCPanelPointer(pidl1);
330     PIDLCPanelStruct *pData2 = _ILGetCPanelPointer(pidl2);
331 
332     if (!pData1 || !pData2 || LOWORD(lParam) >= CONTROLPANEL_COL_COUNT)
333         return E_INVALIDARG;
334 
335     int result;
336     switch(LOWORD(lParam))
337     {
338         case CONTROLPANEL_COL_NAME:
339             result = wcsicmp(pData1->szName + pData1->offsDispName, pData2->szName + pData2->offsDispName);
340             break;
341         case CONTROLPANEL_COL_COMMENT:
342             result = wcsicmp(pData1->szName + pData1->offsComment, pData2->szName + pData2->offsComment);
343             break;
344         default:
345             ERR("Got wrong lParam!\n");
346             return E_INVALIDARG;
347     }
348 
349     return MAKE_COMPARE_HRESULT(result);
350 }
351 
352 /**************************************************************************
353 *    CControlPanelFolder::CreateViewObject
354 */
355 HRESULT WINAPI CControlPanelFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID * ppvOut)
356 {
357     CComPtr<IShellView>                    pShellView;
358     HRESULT hr = E_INVALIDARG;
359 
360     TRACE("(%p)->(hwnd=%p,%s,%p)\n", this, hwndOwner, shdebugstr_guid(&riid), ppvOut);
361 
362     if (ppvOut) {
363         *ppvOut = NULL;
364 
365         if (IsEqualIID(riid, IID_IDropTarget)) {
366             WARN("IDropTarget not implemented\n");
367             hr = E_NOTIMPL;
368         } else if (IsEqualIID(riid, IID_IContextMenu)) {
369             WARN("IContextMenu not implemented\n");
370             hr = E_NOTIMPL;
371         } else if (IsEqualIID(riid, IID_IShellView)) {
372             SFV_CREATE sfvparams = {sizeof(SFV_CREATE), this};
373             hr = SHCreateShellFolderView(&sfvparams, (IShellView**)ppvOut);
374         }
375     }
376     TRACE("--(%p)->(interface=%p)\n", this, ppvOut);
377     return hr;
378 }
379 
380 /**************************************************************************
381 *  CControlPanelFolder::GetAttributesOf
382 */
383 HRESULT WINAPI CControlPanelFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD * rgfInOut)
384 {
385     HRESULT hr = S_OK;
386     static const DWORD dwControlPanelAttributes =
387         SFGAO_HASSUBFOLDER | SFGAO_FOLDER | SFGAO_CANLINK;
388 
389     TRACE("(%p)->(cidl=%d apidl=%p mask=%p (0x%08x))\n",
390           this, cidl, apidl, rgfInOut, rgfInOut ? *rgfInOut : 0);
391 
392     if (!rgfInOut)
393         return E_INVALIDARG;
394     if (cidl && !apidl)
395         return E_INVALIDARG;
396 
397     if (*rgfInOut == 0)
398         *rgfInOut = ~0;
399 
400     if (!cidl)
401     {
402         *rgfInOut &= dwControlPanelAttributes;
403     }
404     else
405     {
406         while(cidl > 0 && *apidl)
407         {
408             pdump(*apidl);
409             if (_ILIsCPanelStruct(*apidl))
410                 *rgfInOut &= SFGAO_CANLINK;
411             else if (_ILIsSpecialFolder(*apidl))
412                 m_regFolder->GetAttributesOf(1, apidl, rgfInOut);
413             else
414                 ERR("Got unknown pidl\n");
415             apidl++;
416             cidl--;
417         }
418     }
419     /* make sure SFGAO_VALIDATE is cleared, some apps depend on that */
420     *rgfInOut &= ~SFGAO_VALIDATE;
421 
422     TRACE("-- result=0x%08x\n", *rgfInOut);
423     return hr;
424 }
425 
426 /**************************************************************************
427 *    CControlPanelFolder::GetUIObjectOf
428 *
429 * PARAMETERS
430 *  HWND           hwndOwner, //[in ] Parent window for any output
431 *  UINT           cidl,      //[in ] array size
432 *  LPCITEMIDLIST* apidl,     //[in ] simple pidl array
433 *  REFIID         riid,      //[in ] Requested Interface
434 *  UINT*          prgfInOut, //[   ] reserved
435 *  LPVOID*        ppvObject) //[out] Resulting Interface
436 *
437 */
438 HRESULT WINAPI CControlPanelFolder::GetUIObjectOf(HWND hwndOwner,
439         UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT * prgfInOut, LPVOID * ppvOut)
440 {
441     LPVOID pObj = NULL;
442     HRESULT hr = E_INVALIDARG;
443 
444     TRACE("(%p)->(%p,%u,apidl=%p,%s,%p,%p)\n",
445           this, hwndOwner, cidl, apidl, shdebugstr_guid(&riid), prgfInOut, ppvOut);
446 
447     if (ppvOut) {
448         *ppvOut = NULL;
449 
450         if (IsEqualIID(riid, IID_IContextMenu) && (cidl >= 1)) {
451             /* HACK: We should use callbacks from CDefaultContextMenu instead of creating one on our own */
452             BOOL bHasCpl = FALSE;
453             for (UINT i = 0; i < cidl; i++)
454             {
455                 if(_ILIsCPanelStruct(apidl[i]))
456                 {
457                     bHasCpl = TRUE;
458                 }
459             }
460 
461             if (bHasCpl)
462                 hr = ShellObjectCreatorInit<CCPLItemMenu>(cidl, apidl, riid, &pObj);
463             else
464                 hr = m_regFolder->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, &pObj);
465         } else if (IsEqualIID(riid, IID_IDataObject) && (cidl >= 1)) {
466             hr = IDataObject_Constructor(hwndOwner, pidlRoot, apidl, cidl, TRUE, (IDataObject **)&pObj);
467         } else if ((IsEqualIID(riid, IID_IExtractIconA) || IsEqualIID(riid, IID_IExtractIconW)) && (cidl == 1)) {
468             if (_ILGetCPanelPointer(apidl[0]))
469                 hr = CCPLExtractIcon_CreateInstance(this, apidl[0], riid, &pObj);
470             else
471                 hr = m_regFolder->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, &pObj);
472         } else {
473             hr = E_NOINTERFACE;
474         }
475 
476         if (SUCCEEDED(hr) && !pObj)
477             hr = E_OUTOFMEMORY;
478 
479         *ppvOut = pObj;
480     }
481     TRACE("(%p)->hr=0x%08x\n", this, hr);
482     return hr;
483 }
484 
485 /**************************************************************************
486 *    CControlPanelFolder::GetDisplayNameOf
487 */
488 HRESULT WINAPI CControlPanelFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET strRet)
489 {
490     if (!pidl)
491         return S_FALSE;
492 
493     PIDLCPanelStruct *pCPanel = _ILGetCPanelPointer(pidl);
494 
495     if (pCPanel)
496     {
497         return SHSetStrRet(strRet, pCPanel->szName + pCPanel->offsDispName);
498     }
499     else if (_ILIsSpecialFolder(pidl))
500     {
501         return m_regFolder->GetDisplayNameOf(pidl, dwFlags, strRet);
502     }
503 
504     return E_FAIL;
505 }
506 
507 /**************************************************************************
508 *  CControlPanelFolder::SetNameOf
509 *  Changes the name of a file object or subfolder, possibly changing its item
510 *  identifier in the process.
511 *
512 * PARAMETERS
513 *  HWND          hwndOwner,  //[in ] Owner window for output
514 *  LPCITEMIDLIST pidl,       //[in ] simple pidl of item to change
515 *  LPCOLESTR     lpszName,   //[in ] the items new display name
516 *  DWORD         dwFlags,    //[in ] SHGNO formatting flags
517 *  LPITEMIDLIST* ppidlOut)   //[out] simple pidl returned
518 */
519 HRESULT WINAPI CControlPanelFolder::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl,    /*simple pidl */
520         LPCOLESTR lpName, DWORD dwFlags, PITEMID_CHILD *pPidlOut)
521 {
522     FIXME("(%p)->(%p,pidl=%p,%s,%u,%p)\n", this, hwndOwner, pidl, debugstr_w(lpName), dwFlags, pPidlOut);
523     return E_FAIL;
524 }
525 
526 HRESULT WINAPI CControlPanelFolder::GetDefaultSearchGUID(GUID *pguid)
527 {
528     FIXME("(%p)\n", this);
529     return E_NOTIMPL;
530 }
531 
532 HRESULT WINAPI CControlPanelFolder::EnumSearches(IEnumExtraSearch **ppenum)
533 {
534     FIXME("(%p)\n", this);
535     return E_NOTIMPL;
536 }
537 
538 HRESULT WINAPI CControlPanelFolder::GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay)
539 {
540     TRACE("(%p)\n", this);
541 
542     if (pSort) *pSort = 0;
543     if (pDisplay) *pDisplay = 0;
544     return S_OK;
545 }
546 
547 HRESULT WINAPI CControlPanelFolder::GetDefaultColumnState(UINT iColumn, DWORD *pcsFlags)
548 {
549     TRACE("(%p)\n", this);
550 
551     if (!pcsFlags || iColumn >= CONTROLPANEL_COL_COUNT)
552         return E_INVALIDARG;
553     *pcsFlags = ControlPanelSFHeader[iColumn].colstate;
554     return S_OK;
555 }
556 
557 HRESULT WINAPI CControlPanelFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
558 {
559     FIXME("(%p)\n", this);
560     return E_NOTIMPL;
561 }
562 
563 HRESULT WINAPI CControlPanelFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd)
564 {
565     if (!psd || iColumn >= CONTROLPANEL_COL_COUNT)
566         return E_INVALIDARG;
567 
568     if (!pidl)
569     {
570         psd->fmt = ControlPanelSFHeader[iColumn].fmt;
571         psd->cxChar = ControlPanelSFHeader[iColumn].cxChar;
572         return SHSetStrRet(&psd->str, shell32_hInstance, ControlPanelSFHeader[iColumn].colnameid);
573     }
574     else if (_ILIsSpecialFolder(pidl))
575     {
576         return m_regFolder->GetDetailsOf(pidl, iColumn, psd);
577     }
578     else
579     {
580         PIDLCPanelStruct *pCPanel = _ILGetCPanelPointer(pidl);
581 
582         if (!pCPanel)
583             return E_FAIL;
584 
585         switch(iColumn)
586         {
587             case CONTROLPANEL_COL_NAME:
588                 return SHSetStrRet(&psd->str, pCPanel->szName + pCPanel->offsDispName);
589             case CONTROLPANEL_COL_COMMENT:
590                 return SHSetStrRet(&psd->str, pCPanel->szName + pCPanel->offsComment);
591         }
592     }
593 
594     return E_FAIL;
595 }
596 
597 HRESULT WINAPI CControlPanelFolder::MapColumnToSCID(UINT column, SHCOLUMNID *pscid)
598 {
599     FIXME("(%p)\n", this);
600     return E_NOTIMPL;
601 }
602 
603 /************************************************************************
604  *    CControlPanelFolder::GetClassID
605  */
606 HRESULT WINAPI CControlPanelFolder::GetClassID(CLSID *lpClassId)
607 {
608     TRACE("(%p)\n", this);
609 
610     if (!lpClassId)
611         return E_POINTER;
612     *lpClassId = CLSID_ControlPanel;
613 
614     return S_OK;
615 }
616 
617 /************************************************************************
618  *    CControlPanelFolder::Initialize
619  *
620  * NOTES: it makes no sense to change the pidl
621  */
622 HRESULT WINAPI CControlPanelFolder::Initialize(PCIDLIST_ABSOLUTE pidl)
623 {
624     if (pidlRoot)
625         SHFree((LPVOID)pidlRoot);
626 
627     pidlRoot = ILClone(pidl);
628 
629     /* Create the inner reg folder */
630     HRESULT hr;
631     hr = CRegFolder_CreateInstance(&CLSID_ControlPanel,
632                                    pidlRoot,
633                                    L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}",
634                                    L"ControlPanel",
635                                    IID_PPV_ARG(IShellFolder2, &m_regFolder));
636     if (FAILED_UNEXPECTEDLY(hr))
637         return hr;
638 
639     return S_OK;
640 }
641 
642 /**************************************************************************
643  *    CControlPanelFolder::GetCurFolder
644  */
645 HRESULT WINAPI CControlPanelFolder::GetCurFolder(PIDLIST_ABSOLUTE * pidl)
646 {
647     TRACE("(%p)->(%p)\n", this, pidl);
648 
649     if (!pidl)
650         return E_POINTER;
651     *pidl = ILClone(pidlRoot);
652     return S_OK;
653 }
654 
655 CCPLItemMenu::CCPLItemMenu()
656 {
657     m_apidl = NULL;
658     m_cidl = 0;
659 }
660 
661 HRESULT WINAPI CCPLItemMenu::Initialize(UINT cidl, PCUITEMID_CHILD_ARRAY apidl)
662 {
663     m_cidl = cidl;
664     m_apidl = _ILCopyaPidl(apidl, m_cidl);
665     if (m_cidl && !m_apidl)
666         return E_OUTOFMEMORY;
667 
668     return S_OK;
669 }
670 
671 CCPLItemMenu::~CCPLItemMenu()
672 {
673     _ILFreeaPidl(m_apidl, m_cidl);
674 }
675 
676 HRESULT WINAPI CCPLItemMenu::QueryContextMenu(
677     HMENU hMenu,
678     UINT indexMenu,
679     UINT idCmdFirst,
680     UINT idCmdLast,
681     UINT uFlags)
682 {
683     _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst, MFT_STRING, MAKEINTRESOURCEW(IDS_OPEN), MFS_DEFAULT);
684     _InsertMenuItemW(hMenu, indexMenu++, TRUE, IDC_STATIC, MFT_SEPARATOR, NULL, MFS_ENABLED);
685     _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst + 1, MFT_STRING, MAKEINTRESOURCEW(IDS_CREATELINK), MFS_ENABLED);
686 
687     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 2);
688 }
689 
690 EXTERN_C
691 void WINAPI Control_RunDLLW(HWND hWnd, HINSTANCE hInst, LPCWSTR cmd, DWORD nCmdShow);
692 
693 /**************************************************************************
694 * ICPanel_IContextMenu_InvokeCommand()
695 */
696 HRESULT WINAPI CCPLItemMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
697 {
698     HRESULT hResult;
699 
700     PIDLCPanelStruct *pCPanel = _ILGetCPanelPointer(m_apidl[0]);
701     if(!pCPanel)
702         return E_FAIL;
703 
704     TRACE("(%p)->(invcom=%p verb=%p wnd=%p)\n", this, lpcmi, lpcmi->lpVerb, lpcmi->hwnd);
705 
706     if (lpcmi->lpVerb == MAKEINTRESOURCEA(0))
707     {
708         /* Hardcode the command here; Executing a cpl file would be fine but we also need to run things like console.dll */
709         WCHAR wszParams[MAX_PATH];
710         PCWSTR wszFile = L"rundll32.exe";
711         PCWSTR wszFormat = L"shell32.dll,Control_RunDLL %s,%s";
712 
713         wsprintfW(wszParams, wszFormat, pCPanel->szName, pCPanel->szName + pCPanel->offsDispName);
714 
715         /* Note: we pass the applet name to Control_RunDLL to distinguish between multiple applets in one .cpl file */
716         ShellExecuteW(NULL, NULL, wszFile, wszParams, NULL, 0);
717     }
718     else if (lpcmi->lpVerb == MAKEINTRESOURCEA(1)) //FIXME
719     {
720         CComPtr<IDataObject> pDataObj;
721         LPITEMIDLIST pidl = _ILCreateControlPanel();
722 
723         hResult = SHCreateDataObject(pidl, m_cidl, m_apidl, NULL, IID_PPV_ARG(IDataObject, &pDataObj));
724         if (FAILED(hResult))
725             return hResult;
726 
727         SHFree(pidl);
728 
729         //FIXME: Use SHCreateLinks
730         CComPtr<IShellFolder> psf;
731         CComPtr<IDropTarget> pDT;
732 
733         hResult = SHGetDesktopFolder(&psf);
734         if (FAILED(hResult))
735             return hResult;
736 
737         hResult = psf->CreateViewObject(NULL, IID_PPV_ARG(IDropTarget, &pDT));
738         if (FAILED(hResult))
739             return hResult;
740 
741         SHSimulateDrop(pDT, pDataObj, MK_CONTROL|MK_SHIFT, NULL, NULL);
742     }
743     return S_OK;
744 }
745 
746 /**************************************************************************
747  *  ICPanel_IContextMenu_GetCommandString()
748  *
749  */
750 HRESULT WINAPI CCPLItemMenu::GetCommandString(
751     UINT_PTR idCommand,
752     UINT uFlags,
753     UINT* lpReserved,
754     LPSTR lpszName,
755     UINT uMaxNameLen)
756 {
757     TRACE("(%p)->(idcom=%lx flags=%x %p name=%p len=%x)\n", this, idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
758 
759     FIXME("unknown command string\n");
760     return E_FAIL;
761 }
762 
763 /**************************************************************************
764 * ICPanel_IContextMenu_HandleMenuMsg()
765 */
766 HRESULT WINAPI CCPLItemMenu::HandleMenuMsg(
767     UINT uMsg,
768     WPARAM wParam,
769     LPARAM lParam)
770 {
771     TRACE("ICPanel_IContextMenu_HandleMenuMsg (%p)->(msg=%x wp=%lx lp=%lx)\n", this, uMsg, wParam, lParam);
772 
773     return E_NOTIMPL;
774 }
775 
776 /**************************************************************************
777 * COpenControlPanel
778 */
779 
780 static HRESULT GetParsingName(PCIDLIST_ABSOLUTE pidl, PWSTR*Name)
781 {
782     PIDLIST_ABSOLUTE pidlFree = NULL;
783     if (IS_INTRESOURCE(pidl))
784     {
785         HRESULT hr = SHGetSpecialFolderLocation(NULL, (UINT)(SIZE_T)pidl, &pidlFree);
786         if (FAILED(hr))
787             return hr;
788         pidl = pidlFree;
789     }
790     HRESULT hr = SHGetNameFromIDList(pidl, SIGDN_DESKTOPABSOLUTEPARSING, Name);
791     ILFree(pidlFree);
792     return hr;
793 }
794 
795 static HRESULT CreateCplAbsoluteParsingPath(LPCWSTR Prefix, LPCWSTR InFolderParse, PWSTR Buf, UINT cchBuf)
796 {
797     PWSTR cpfolder;
798     HRESULT hr = GetParsingName((PCIDLIST_ABSOLUTE)CSIDL_CONTROLS, &cpfolder);
799     if (SUCCEEDED(hr))
800     {
801         hr = StringCchPrintfW(Buf, cchBuf, L"%s\\%s%s", cpfolder, Prefix, InFolderParse);
802         SHFree(cpfolder);
803     }
804     return hr;
805 }
806 
807 static HRESULT FindExeCplClass(LPCWSTR Canonical, HKEY hKey, BOOL Wow64, LPWSTR clsid)
808 {
809     HRESULT hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
810     HKEY hNSKey;
811     WCHAR key[MAX_PATH], buf[MAX_PATH];
812     wsprintfW(key, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\%s\\NameSpace",
813               Wow64 ? L"ControlPanelWOW64" : L"ControlPanel");
814     LSTATUS error = RegOpenKeyExW(hKey, key, 0, KEY_READ, &hNSKey);
815     if (error)
816         return HRESULT_FROM_WIN32(error);
817     for (DWORD i = 0; RegEnumKeyW(hNSKey, i, key, _countof(key)) == ERROR_SUCCESS; ++i)
818     {
819         IID validate;
820         if (SUCCEEDED(IIDFromString(key, &validate)))
821         {
822             wsprintfW(buf, L"CLSID\\%s", key);
823             DWORD cb = sizeof(buf);
824             if (RegGetValueW(HKEY_CLASSES_ROOT, buf, L"System.ApplicationName",
825                              RRF_RT_REG_SZ, NULL, buf, &cb) == ERROR_SUCCESS)
826             {
827                 if (!lstrcmpiW(buf, Canonical))
828                 {
829                     lstrcpyW(clsid, key);
830                     hr = S_OK;
831                 }
832             }
833         }
834     }
835     RegCloseKey(hNSKey);
836     return hr;
837 }
838 
839 static HRESULT FindExeCplClass(LPCWSTR Canonical, LPWSTR clsid)
840 {
841     HRESULT hr = E_FAIL;
842     if (FAILED(hr))
843         hr = FindExeCplClass(Canonical, HKEY_CURRENT_USER, FALSE, clsid);
844     if (FAILED(hr))
845         hr = FindExeCplClass(Canonical, HKEY_CURRENT_USER, TRUE, clsid);
846     if (FAILED(hr))
847         hr = FindExeCplClass(Canonical, HKEY_LOCAL_MACHINE, FALSE, clsid);
848     if (FAILED(hr))
849         hr = FindExeCplClass(Canonical, HKEY_LOCAL_MACHINE, TRUE, clsid);
850     return hr;
851 }
852 
853 HRESULT WINAPI COpenControlPanel::Open(LPCWSTR pszName, LPCWSTR pszPage, IUnknown *punkSite)
854 {
855     WCHAR path[MAX_PATH], clspath[MAX_PATH];
856     HRESULT hr = S_OK;
857     SHELLEXECUTEINFOW sei = { sizeof(sei), SEE_MASK_FLAG_DDEWAIT };
858     sei.lpFile = path;
859     sei.nShow = SW_SHOW;
860     if (!pszName)
861     {
862         GetSystemDirectoryW(path, _countof(path));
863         PathAppendW(path, L"control.exe");
864     }
865     else
866     {
867         LPWSTR clsid = clspath + wsprintfW(clspath, L"CLSID\\");
868         if (SUCCEEDED(hr = FindExeCplClass(pszName, clsid)))
869         {
870             if (SUCCEEDED(hr = CreateCplAbsoluteParsingPath(L"::", clsid, path, _countof(path))))
871             {
872                 // NT6 will execute "::{26EE0668-A00A-44D7-9371-BEB064C98683}\0\::{clsid}[\pszPage]"
873                 // but we don't support parsing that so we force the class instead.
874                 sei.fMask |= SEE_MASK_CLASSNAME;
875                 sei.lpClass = clspath;
876             }
877         }
878     }
879 
880     if (SUCCEEDED(hr))
881     {
882         DWORD error = ShellExecuteExW(&sei) ? ERROR_SUCCESS : GetLastError();
883         hr = HRESULT_FROM_WIN32(error);
884     }
885     return hr;
886 }
887 
888 HRESULT WINAPI COpenControlPanel::GetPath(LPCWSTR pszName, LPWSTR pszPath, UINT cchPath)
889 {
890     HRESULT hr;
891     if (!pszName)
892     {
893         PWSTR cpfolder;
894         if (SUCCEEDED(hr = GetParsingName((PCIDLIST_ABSOLUTE)CSIDL_CONTROLS, &cpfolder)))
895         {
896             hr = StringCchCopyW(pszPath, cchPath, cpfolder);
897             SHFree(cpfolder);
898         }
899     }
900     else
901     {
902         WCHAR clsid[38 + 1];
903         if (SUCCEEDED(hr = FindExeCplClass(pszName, clsid)))
904         {
905             hr = CreateCplAbsoluteParsingPath(L"::", clsid, pszPath, cchPath);
906         }
907     }
908     return hr;
909 }
910 
911 HRESULT WINAPI COpenControlPanel::GetCurrentView(CPVIEW *pView)
912 {
913     *pView = CPVIEW_CLASSIC;
914     return S_OK;
915 }
916