xref: /reactos/dll/win32/shell32/propsheet.cpp (revision 3bd9ddca)
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
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
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
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
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
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
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  *  SHOpenPropSheetW [SHELL32.80]
165  *
166  * @see https://learn.microsoft.com/en-us/windows/win32/api/shlobj/nf-shlobj-shopenpropsheetw
167  */
168 EXTERN_C BOOL WINAPI
169 SHOpenPropSheetW(
170     _In_opt_ LPCWSTR pszCaption,
171     _In_opt_ HKEY *ahKeys,
172     _In_ UINT cKeys,
173     _In_ const CLSID *pclsidDefault,
174     _In_ IDataObject *pDataObject,
175     _In_opt_ IShellBrowser *pShellBrowser,
176     _In_opt_ LPCWSTR pszStartPage)
177 {
178     UNREFERENCED_PARAMETER(pShellBrowser); /* MSDN says "Not used". */
179     return SHELL32_OpenPropSheet(pszCaption, ahKeys, cKeys, pclsidDefault, pDataObject, pszStartPage);
180 }
181 
182 /*************************************************************************
183  * SH_CreatePropertySheetPage [Internal]
184  *
185  * creates a property sheet page from a resource id
186  */
187 HPROPSHEETPAGE
188 SH_CreatePropertySheetPageEx(WORD wDialogId, DLGPROC pfnDlgProc, LPARAM lParam,
189                              LPCWSTR pwszTitle, LPFNPSPCALLBACK Callback)
190 {
191     PROPSHEETPAGEW Page = { sizeof(Page), PSP_DEFAULT, shell32_hInstance };
192     Page.pszTemplate = MAKEINTRESOURCEW(wDialogId);
193     Page.pfnDlgProc = pfnDlgProc;
194     Page.lParam = lParam;
195     Page.pszTitle = pwszTitle;
196     Page.pfnCallback = Callback;
197 
198     if (pwszTitle)
199         Page.dwFlags |= PSP_USETITLE;
200 
201     if (Callback)
202         Page.dwFlags |= PSP_USECALLBACK;
203 
204     return CreatePropertySheetPageW(&Page);
205 }
206 
207 HPROPSHEETPAGE
208 SH_CreatePropertySheetPage(WORD wDialogId, DLGPROC pfnDlgProc, LPARAM lParam, LPCWSTR pwszTitle)
209 {
210     return SH_CreatePropertySheetPageEx(wDialogId, pfnDlgProc, lParam, pwszTitle, NULL);
211 }
212