1 /*
2 * PROJECT: shell32
3 * LICENSE: LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+)
4 * PURPOSE: SHOpenPropSheetW 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_GetShellExtensionRegCLSID(HKEY hKey,LPCWSTR KeyName,CLSID & clsid)13 SHELL_GetShellExtensionRegCLSID(HKEY hKey, LPCWSTR KeyName, CLSID &clsid)
14 {
15 // First try the key name
16 if (SUCCEEDED(SHCLSIDFromStringW(KeyName, &clsid)))
17 return S_OK;
18 WCHAR buf[42];
19 DWORD cb = sizeof(buf);
20 // and then the default value
21 DWORD err = RegGetValueW(hKey, KeyName, NULL, RRF_RT_REG_SZ, NULL, buf, &cb);
22 return !err ? SHCLSIDFromStringW(buf, &clsid) : HRESULT_FROM_WIN32(err);
23 }
24
25 static HRESULT
SHELL_InitializeExtension(REFCLSID clsid,PCIDLIST_ABSOLUTE pidlFolder,IDataObject * pDO,HKEY hkeyProgID,REFIID riid,void ** ppv)26 SHELL_InitializeExtension(REFCLSID clsid, PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pDO, HKEY hkeyProgID, REFIID riid, void **ppv)
27 {
28 *ppv = NULL;
29 IUnknown *pUnk;
30 HRESULT hr = SHCoCreateInstance(NULL, &clsid, NULL, riid, (void**)&pUnk);
31 if (SUCCEEDED(hr))
32 {
33 CComPtr<IShellExtInit> Init;
34 hr = pUnk->QueryInterface(IID_PPV_ARG(IShellExtInit, &Init));
35 if (SUCCEEDED(hr))
36 hr = Init->Initialize(pidlFolder, pDO, hkeyProgID);
37
38 if (SUCCEEDED(hr))
39 *ppv = (void*)pUnk;
40 else
41 pUnk->Release();
42 }
43 return hr;
44 }
45
46 static HRESULT
AddPropSheetHandlerPages(REFCLSID clsid,IDataObject * pDO,HKEY hkeyProgID,PROPSHEETHEADERW & psh)47 AddPropSheetHandlerPages(REFCLSID clsid, IDataObject *pDO, HKEY hkeyProgID, PROPSHEETHEADERW &psh)
48 {
49 CComPtr<IShellPropSheetExt> SheetExt;
50 HRESULT hr = SHELL_InitializeExtension(clsid, NULL, pDO, hkeyProgID, IID_PPV_ARG(IShellPropSheetExt, &SheetExt));
51 if (SUCCEEDED(hr))
52 {
53 UINT OldCount = psh.nPages;
54 hr = SheetExt->AddPages(AddPropSheetPageCallback, (LPARAM)&psh);
55 // The returned index is one-based (relative to this extension).
56 // See https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishellpropsheetext-addpages
57 if (hr > 0)
58 {
59 hr += OldCount;
60 psh.nStartPage = hr - 1;
61 }
62 }
63 return hr;
64 }
65
66 static HRESULT
SHELL_CreatePropSheetStubWindow(CStubWindow32 & stub,PCIDLIST_ABSOLUTE pidl,const POINT * pPt)67 SHELL_CreatePropSheetStubWindow(CStubWindow32 &stub, PCIDLIST_ABSOLUTE pidl, const POINT *pPt)
68 {
69 PWSTR Path;
70 if (!pidl || FAILED(SHGetNameFromIDList(pidl, SIGDN_DESKTOPABSOLUTEPARSING, &Path)))
71 Path = NULL; // If we can't get a path, we simply will not be able to reuse this window
72
73 HRESULT hr = stub.CreateStub(CStubWindow32::TYPE_PROPERTYSHEET, Path, pPt);
74 SHFree(Path);
75 UINT flags = SHGFI_ICON | SHGFI_ADDOVERLAYS;
76 SHFILEINFO sfi;
77 if (SUCCEEDED(hr) && SHGetFileInfoW((LPWSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_PIDL | flags))
78 stub.SendMessage(WM_SETICON, ICON_BIG, (LPARAM)sfi.hIcon);
79 return hr;
80 }
81
82 /*************************************************************************
83 * SHELL32_PropertySheet [INTERNAL]
84 * PropertySheetW with stub window.
85 */
86 static INT_PTR
SHELL32_PropertySheet(LPPROPSHEETHEADERW ppsh,IDataObject * pDO)87 SHELL32_PropertySheet(LPPROPSHEETHEADERW ppsh, IDataObject *pDO)
88 {
89 CStubWindow32 stub;
90 POINT pt, *ppt = NULL;
91 if (pDO && SUCCEEDED(DataObject_GetOffset(pDO, &pt)))
92 ppt = &pt;
93 PIDLIST_ABSOLUTE pidl = SHELL_DataObject_ILCloneFullItem(pDO, 0);
94 HRESULT hr = SHELL_CreatePropSheetStubWindow(stub, pidl, ppt);
95 ILFree(pidl);
96 if (FAILED(hr))
97 return hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) ? 0 : -1;
98
99 INT_PTR Result = -1;
100 ppsh->hwndParent = stub;
101 if (ppsh->nPages)
102 {
103 Result = PropertySheetW(ppsh);
104 }
105 else
106 {
107 WCHAR szFormat[128], szMessage[_countof(szFormat) + 42];
108 LoadStringW(shell32_hInstance, IDS_CANTSHOWPROPERTIES, szFormat, _countof(szFormat));
109 wsprintfW(szMessage, szFormat, ERROR_CAN_NOT_COMPLETE);
110 MessageBoxW(NULL, szMessage, NULL, MB_ICONERROR);
111 }
112 stub.DestroyWindow();
113 return Result;
114 }
115
116 /*************************************************************************
117 * SHELL32_OpenPropSheet [INTERNAL]
118 * The real implementation of SHOpenPropSheetW.
119 */
120 static BOOL
SHELL32_OpenPropSheet(LPCWSTR pszCaption,HKEY * ahKeys,UINT cKeys,const CLSID * pclsidDefault,IDataObject * pDO,LPCWSTR pStartPage)121 SHELL32_OpenPropSheet(LPCWSTR pszCaption, HKEY *ahKeys, UINT cKeys,
122 const CLSID *pclsidDefault, IDataObject *pDO, LPCWSTR pStartPage)
123 {
124 HKEY hKeyProgID = cKeys ? ahKeys[cKeys - 1] : NULL; // Windows uses the last key for some reason
125 HPROPSHEETPAGE hppages[MAX_PROPERTY_SHEET_PAGE];
126 PROPSHEETHEADERW psh = { sizeof(psh), PSH_PROPTITLE };
127 psh.phpage = hppages;
128 psh.hInstance = shell32_hInstance;
129 psh.pszCaption = pszCaption;
130 psh.pStartPage = pStartPage;
131
132 if (pclsidDefault)
133 AddPropSheetHandlerPages(*pclsidDefault, pDO, hKeyProgID, psh);
134
135 for (UINT i = 0; i < cKeys; ++i)
136 {
137 // Note: We can't use SHCreatePropSheetExtArrayEx because we need the AddPages() return value (see AddPropSheetHandlerPages).
138 HKEY hKey;
139 if (RegOpenKeyExW(ahKeys[i], L"shellex\\PropertySheetHandlers", 0, KEY_ENUMERATE_SUB_KEYS, &hKey))
140 continue;
141 for (UINT index = 0;; ++index)
142 {
143 WCHAR KeyName[MAX_PATH];
144 LRESULT err = RegEnumKeyW(hKey, index, KeyName, _countof(KeyName));
145 KeyName[_countof(KeyName)-1] = UNICODE_NULL;
146 if (err == ERROR_MORE_DATA)
147 continue;
148 if (err)
149 break;
150 CLSID clsid;
151 if (SUCCEEDED(SHELL_GetShellExtensionRegCLSID(hKey, KeyName, clsid)))
152 AddPropSheetHandlerPages(clsid, pDO, hKeyProgID, psh);
153 }
154 RegCloseKey(hKey);
155 }
156
157 if (pStartPage == psh.pStartPage && pStartPage)
158 psh.dwFlags |= PSH_USEPSTARTPAGE;
159 INT_PTR Result = SHELL32_PropertySheet(&psh, pDO);
160 return (Result != -1);
161 }
162
163 /*************************************************************************
164 * SHOpenPropSheetA [SHELL32.707]
165 *
166 * @see https://learn.microsoft.com/en-us/windows/win32/api/shlobj/nf-shlobj-shopenpropsheeta
167 */
168 EXTERN_C
169 BOOL WINAPI
SHOpenPropSheetA(_In_opt_ LPCSTR pszCaption,_In_opt_ HKEY * ahKeys,_In_ UINT cKeys,_In_ const CLSID * pclsidDefault,_In_ IDataObject * pDataObject,_In_opt_ IShellBrowser * pShellBrowser,_In_opt_ LPCSTR pszStartPage)170 SHOpenPropSheetA(
171 _In_opt_ LPCSTR pszCaption,
172 _In_opt_ HKEY *ahKeys,
173 _In_ UINT cKeys,
174 _In_ const CLSID *pclsidDefault,
175 _In_ IDataObject *pDataObject,
176 _In_opt_ IShellBrowser *pShellBrowser,
177 _In_opt_ LPCSTR pszStartPage)
178 {
179 CStringW strStartPageW, strCaptionW;
180 LPCWSTR pszCaptionW = NULL, pszStartPageW = NULL;
181
182 if (pszCaption)
183 {
184 strStartPageW = pszCaption;
185 pszCaptionW = strCaptionW;
186 }
187
188 if (pszStartPage)
189 {
190 strStartPageW = pszStartPage;
191 pszStartPageW = strStartPageW;
192 }
193
194 return SHOpenPropSheetW(pszCaptionW, ahKeys, cKeys, pclsidDefault,
195 pDataObject, pShellBrowser, pszStartPageW);
196 }
197
198 /*************************************************************************
199 * SHOpenPropSheetW [SHELL32.80]
200 *
201 * @see https://learn.microsoft.com/en-us/windows/win32/api/shlobj/nf-shlobj-shopenpropsheetw
202 */
203 EXTERN_C BOOL WINAPI
SHOpenPropSheetW(_In_opt_ LPCWSTR pszCaption,_In_opt_ HKEY * ahKeys,_In_ UINT cKeys,_In_ const CLSID * pclsidDefault,_In_ IDataObject * pDataObject,_In_opt_ IShellBrowser * pShellBrowser,_In_opt_ LPCWSTR pszStartPage)204 SHOpenPropSheetW(
205 _In_opt_ LPCWSTR pszCaption,
206 _In_opt_ HKEY *ahKeys,
207 _In_ UINT cKeys,
208 _In_ const CLSID *pclsidDefault,
209 _In_ IDataObject *pDataObject,
210 _In_opt_ IShellBrowser *pShellBrowser,
211 _In_opt_ LPCWSTR pszStartPage)
212 {
213 UNREFERENCED_PARAMETER(pShellBrowser); /* MSDN says "Not used". */
214 return SHELL32_OpenPropSheet(pszCaption, ahKeys, cKeys, pclsidDefault, pDataObject, pszStartPage);
215 }
216
217 /*************************************************************************
218 * SH_CreatePropertySheetPage [Internal]
219 *
220 * creates a property sheet page from a resource id
221 */
222 HPROPSHEETPAGE
SH_CreatePropertySheetPageEx(WORD wDialogId,DLGPROC pfnDlgProc,LPARAM lParam,LPCWSTR pwszTitle,LPFNPSPCALLBACK Callback)223 SH_CreatePropertySheetPageEx(WORD wDialogId, DLGPROC pfnDlgProc, LPARAM lParam,
224 LPCWSTR pwszTitle, LPFNPSPCALLBACK Callback)
225 {
226 PROPSHEETPAGEW Page = { sizeof(Page), PSP_DEFAULT, shell32_hInstance };
227 Page.pszTemplate = MAKEINTRESOURCEW(wDialogId);
228 Page.pfnDlgProc = pfnDlgProc;
229 Page.lParam = lParam;
230 Page.pszTitle = pwszTitle;
231 Page.pfnCallback = Callback;
232
233 if (pwszTitle)
234 Page.dwFlags |= PSP_USETITLE;
235
236 if (Callback)
237 Page.dwFlags |= PSP_USECALLBACK;
238
239 return CreatePropertySheetPageW(&Page);
240 }
241
242 HPROPSHEETPAGE
SH_CreatePropertySheetPage(WORD wDialogId,DLGPROC pfnDlgProc,LPARAM lParam,LPCWSTR pwszTitle)243 SH_CreatePropertySheetPage(WORD wDialogId, DLGPROC pfnDlgProc, LPARAM lParam, LPCWSTR pwszTitle)
244 {
245 return SH_CreatePropertySheetPageEx(wDialogId, pfnDlgProc, lParam, pwszTitle, NULL);
246 }
247