1 /*
2  * PROJECT:     shell32
3  * LICENSE:     LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+)
4  * PURPOSE:     FileSystem PropertySheet implementation
5  * COPYRIGHT:   Copyright 2024 Whindmar Saksit <whindsaks@proton.me>
6  */
7 
8 #include "precomp.h"
9 
10 WINE_DEFAULT_DEBUG_CHANNEL(shell);
11 
12 static HRESULT
SHELL_GetCaptionFromDataObject(IDataObject * pDO,LPWSTR Buf,UINT cchBuf)13 SHELL_GetCaptionFromDataObject(IDataObject *pDO, LPWSTR Buf, UINT cchBuf)
14 {
15     HRESULT hr = E_INVALIDARG;
16     if (PIDLIST_ABSOLUTE pidl = SHELL_DataObject_ILCloneFullItem(pDO, 0))
17     {
18         hr = SHGetNameAndFlagsW(pidl, SHGDN_INFOLDER, Buf, cchBuf, NULL);
19         ILFree(pidl);
20     }
21     return hr;
22 }
23 
24 struct ShellPropSheetDialog
25 {
26     typedef void (CALLBACK*PFNINITIALIZE)(LPCWSTR InitString, IDataObject *pDO,
27                                           HKEY *hKeys, UINT *cKeys);
28 
ShowShellPropSheetDialog29     static HRESULT Show(const CLSID *pClsidDefault, IDataObject *pDO,
30                         PFNINITIALIZE InitFunc, LPCWSTR InitString)
31     {
32         HRESULT hr;
33         CRegKeyHandleArray keys;
34         if (InitFunc)
35             InitFunc(InitString, pDO, keys, keys);
36         WCHAR szCaption[MAX_PATH], *pszCaption = NULL;
37         if (SUCCEEDED(SHELL_GetCaptionFromDataObject(pDO, szCaption, _countof(szCaption))))
38             pszCaption = szCaption;
39         hr = SHOpenPropSheetW(pszCaption, keys, keys, pClsidDefault, pDO, NULL, NULL) ? S_OK : E_FAIL;
40         return hr;
41     }
42 
43     struct DATA
44     {
45         PFNINITIALIZE InitFunc;
46         LPWSTR InitString;
47         CLSID ClsidDefault;
48         const CLSID *pClsidDefault;
49         IStream *pObjStream;
50         HANDLE hEvent;
51     };
52 
FreeDataShellPropSheetDialog53     static void FreeData(DATA *pData)
54     {
55         if (pData->InitString)
56             SHFree(pData->InitString);
57         SHFree(pData);
58     }
59 
ShowAsyncShellPropSheetDialog60     static HRESULT ShowAsync(const CLSID *pClsidDefault, IDataObject *pDO,
61                              PFNINITIALIZE InitFunc, LPCWSTR InitString)
62     {
63         DATA *pData = (DATA*)SHAlloc(sizeof(*pData));
64         if (!pData)
65             return E_OUTOFMEMORY;
66         ZeroMemory(pData, sizeof(*pData));
67         pData->InitFunc = InitFunc;
68         if (InitString && FAILED(SHStrDupW(InitString, &pData->InitString)))
69         {
70             FreeData(pData);
71             return E_OUTOFMEMORY;
72         }
73         if (pClsidDefault)
74         {
75             pData->ClsidDefault = *pClsidDefault;
76             pData->pClsidDefault = &pData->ClsidDefault;
77         }
78         HANDLE hEvent = pData->hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
79 
80         HRESULT hr = S_OK;
81         if (pDO)
82             hr = CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pDO, &pData->pObjStream);
83 
84         const UINT flags = CTF_COINIT | CTF_PROCESS_REF | CTF_INSIST;
85         if (SUCCEEDED(hr) && !SHCreateThread(ShowPropertiesThread, pData, flags, NULL))
86         {
87             if (pData->pObjStream)
88                 pData->pObjStream->Release();
89             hr = E_FAIL;
90         }
91 
92         if (SUCCEEDED(hr))
93         {
94             if (hEvent)
95             {
96                 DWORD index;
97                 // Pump COM messages until the thread can create its own IDataObject (for CORE-19933).
98                 // SHOpenPropSheetW is modal and we cannot wait for it to complete.
99                 CoWaitForMultipleHandles(COWAIT_DEFAULT, INFINITE, 1, &hEvent, &index);
100                 CloseHandle(hEvent);
101             }
102         }
103         else
104         {
105             FreeData(pData);
106         }
107         return hr;
108     }
109 
ShowPropertiesThreadShellPropSheetDialog110     static DWORD CALLBACK ShowPropertiesThread(LPVOID Param)
111     {
112         DATA *pData = (DATA*)Param;
113         CComPtr<IDataObject> pDO, pLocalDO;
114         if (pData->pObjStream)
115             CoGetInterfaceAndReleaseStream(pData->pObjStream, IID_PPV_ARG(IDataObject, &pDO));
116         if (pDO && SUCCEEDED(SHELL_CloneDataObject(pDO, &pLocalDO)))
117             pDO = pLocalDO;
118         if (pData->hEvent)
119             SetEvent(pData->hEvent);
120         Show(pData->pClsidDefault, pDO, pData->InitFunc, pData->InitString);
121         FreeData(pData);
122         return 0;
123     }
124 };
125 
126 static void CALLBACK
FSFolderItemPropDialogInitCallback(LPCWSTR InitString,IDataObject * pDO,HKEY * hKeys,UINT * cKeys)127 FSFolderItemPropDialogInitCallback(LPCWSTR InitString, IDataObject *pDO, HKEY *hKeys, UINT *cKeys)
128 {
129     UNREFERENCED_PARAMETER(InitString);
130     CDataObjectHIDA cida(pDO);
131     if (SUCCEEDED(cida.hr()) && cida->cidl)
132     {
133         PCUITEMID_CHILD pidl = HIDA_GetPIDLItem(cida, 0);
134         AddFSClassKeysToArray(1, &pidl, hKeys, cKeys); // Add file-type specific pages
135     }
136 }
137 
138 static void CALLBACK
ClassPropDialogInitCallback(LPCWSTR InitString,IDataObject * pDO,HKEY * hKeys,UINT * cKeys)139 ClassPropDialogInitCallback(LPCWSTR InitString, IDataObject *pDO, HKEY *hKeys, UINT *cKeys)
140 {
141     UNREFERENCED_PARAMETER(pDO);
142     AddClassKeyToArray(InitString, hKeys, cKeys); // Add pages from HKCR\%ProgId% (with shellex\PropertySheetHandlers appended later)
143 }
144 
145 HRESULT
SHELL32_ShowFilesystemItemPropertiesDialogAsync(IDataObject * pDO)146 SHELL32_ShowFilesystemItemPropertiesDialogAsync(IDataObject *pDO)
147 {
148     CDataObjectHIDA cida(pDO);
149     HRESULT hr = cida.hr();
150     if (FAILED_UNEXPECTEDLY(hr))
151         return hr;
152 
153     const CLSID *pClsid = NULL;
154     ShellPropSheetDialog::PFNINITIALIZE InitFunc = NULL;
155     LPCWSTR InitString = NULL;
156 
157     if (_ILIsDrive(HIDA_GetPIDLItem(cida, 0)))
158     {
159         pClsid = &CLSID_ShellDrvDefExt;
160         InitFunc = ClassPropDialogInitCallback;
161         InitString = L"Drive";
162     }
163     else
164     {
165         pClsid = &CLSID_ShellFileDefExt;
166         InitFunc = FSFolderItemPropDialogInitCallback;
167     }
168     ShellPropSheetDialog Dialog;
169     return Dialog.ShowAsync(pClsid, pDO, InitFunc, InitString);
170 }
171 
172 HRESULT
SHELL32_ShowShellExtensionProperties(const CLSID * pClsid,IDataObject * pDO)173 SHELL32_ShowShellExtensionProperties(const CLSID *pClsid, IDataObject *pDO)
174 {
175     WCHAR ClassBuf[6 + 38 + 1] = L"CLSID\\";
176     StringFromGUID2(*pClsid, ClassBuf + 6, 38 + 1);
177     ShellPropSheetDialog Dialog;
178     return Dialog.ShowAsync(NULL, pDO, ClassPropDialogInitCallback, ClassBuf);
179 }
180 
181 HRESULT
SHELL_ShowItemIDListProperties(LPCITEMIDLIST pidl)182 SHELL_ShowItemIDListProperties(LPCITEMIDLIST pidl)
183 {
184     assert(pidl);
185 
186     CComHeapPtr<ITEMIDLIST> alloc;
187     if (IS_INTRESOURCE(pidl))
188     {
189         HRESULT hr = SHGetSpecialFolderLocation(NULL, LOWORD(pidl), const_cast<LPITEMIDLIST*>(&pidl));
190         if (FAILED(hr))
191             return hr;
192         alloc.Attach(const_cast<LPITEMIDLIST>(pidl));
193     }
194     SHELLEXECUTEINFOA sei = {
195         sizeof(sei), SEE_MASK_INVOKEIDLIST | SEE_MASK_ASYNCOK, NULL, "properties",
196         NULL, NULL, NULL, SW_SHOWNORMAL, NULL, const_cast<LPITEMIDLIST>(pidl)
197     };
198     return ShellExecuteExA(&sei) ? S_OK : HResultFromWin32(GetLastError());
199 }
200