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