xref: /reactos/dll/win32/shell32/propsheet.cpp (revision f482244f)
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