From d41dec2e07f919235406ca3a4d47e8b6ae1aa588 Mon Sep 17 00:00:00 2001 From: Whindmar Saksit Date: Wed, 17 Jan 2024 17:07:21 +0100 Subject: [PATCH] [SHELL32][CONTROL] Added basic IOpenControlPanel support (#6248) Add a basic IOpenControlPanel implementation that supports Vista canonical registry names. Implements `control.exe /name company.name [/page id]` and `IOpenControlPanel` handling of Vista-style canonical registry names. The documented `Microsoft.*` names don't work because they are simply not in our registry but "[Executable Control Panel Items](https://learn.microsoft.com/en-us/windows/win32/shell/how-to-register-an-executable-control-panel-item-registration-)" registered by 3rd-party ISVs will function correctly in control.exe and the COM API. Notes: - `IOpenControlPanel` is implemented in CControlPanelFolder.cpp because it is supposed to have tighter integration with that shell folder than it does in this PR. - `IOpenControlPanel` is also supposed to handle .cpl files with canonical names registered under [`Extended Properties`](https://learn.microsoft.com/en-us/windows/win32/shell/how-to-register-dll-control-panel-item-registration-#step-3) but the control panel folder does not implement `IShellFolder2::GetDetailsEx` yet, so it will have to wait. - These "Executable Control Panel Items" are also supposed to be displayed in the control panel itself but this PR does not address that. The `ITEMIDLIST` format for those needs investigation... - The Wow64 handling is perhaps not correct but it does not matter, `ShellExecuteEx` gets to deal with whatever is in the `...\shell\open\command` key. `CControlPanelFolder` would have to take more care when it starts reading those keys so it knows when to append "(32-bit)" to the display name. - `%s%s` because .cpl canonical names don't have the `::` prefix according to Geoff Chappell. - Always returns `CPVIEW_CLASSIC` because our `CControlPanelFolder` does not support the category view. --- base/applications/control/CMakeLists.txt | 2 + base/applications/control/control.c | 63 +++++++- .../shell32/folders/CControlPanelFolder.cpp | 141 ++++++++++++++++++ .../shell32/folders/CControlPanelFolder.h | 21 +++ dll/win32/shell32/res/rgs/controlpanel.rgs | 22 +++ dll/win32/shell32/shell32.cpp | 1 + sdk/include/psdk/shlguid.h | 1 + sdk/include/psdk/shobjidl.idl | 31 ++++ 8 files changed, 281 insertions(+), 1 deletion(-) diff --git a/base/applications/control/CMakeLists.txt b/base/applications/control/CMakeLists.txt index 06e49c19229..827d6fa4be3 100644 --- a/base/applications/control/CMakeLists.txt +++ b/base/applications/control/CMakeLists.txt @@ -2,5 +2,7 @@ add_rc_deps(control.rc ${CMAKE_CURRENT_SOURCE_DIR}/resources/config.ico) add_executable(control control.c control.rc) set_module_type(control win32gui UNICODE) +add_delay_importlibs(control ole32) +target_link_libraries(control uuid) add_importlibs(control advapi32 shell32 user32 msvcrt kernel32) add_cd_file(TARGET control DESTINATION reactos/system32 FOR all) diff --git a/base/applications/control/control.c b/base/applications/control/control.c index a13000c1153..3a39694d1c9 100644 --- a/base/applications/control/control.c +++ b/base/applications/control/control.c @@ -4,12 +4,14 @@ * PURPOSE: ReactOS System Control Panel * COPYRIGHT: Copyright 2004 Gero Kuehn (reactos.filter@gkware.com) * Copyright 2008 Colin Finck (colin@reactos.org) - * Copyright 2014 Hermès Bélusca-Maïto (hermes.belusca-maito@reactos.org) + * Copyright 2014 Hermès Bélusca-Maïto (hermes.belusca-maito@reactos.org) */ #include #define WIN32_NO_STATUS +#define COBJMACROS + #include #include @@ -17,6 +19,9 @@ #include #include #include +#include +#include +#include #include "resource.h" @@ -34,6 +39,35 @@ VOID WINAPI Control_RunDLLW(HWND hWnd, HINSTANCE hInst, LPCWSTR cmd, DWORD nCmdShow); +static BOOL +IsSwitch(LPCWSTR Switch, LPCWSTR Arg) +{ + if (*Arg == '/' || *Arg == '-') + { + return !lstrcmpiW(Arg+1, Switch); + } + return FALSE; +} + +static HRESULT +OpenControlPanelItem(LPCWSTR Name, LPCWSTR Page) +{ + HRESULT hr = CoInitialize(0); + if (SUCCEEDED(hr)) + { + IOpenControlPanel *pOCP; + hr = CoCreateInstance(&CLSID_OpenControlPanel, NULL, CLSCTX_INPROC_SERVER, + &IID_IOpenControlPanel, (void**)&pOCP); + if (SUCCEEDED(hr)) + { + hr = IOpenControlPanel_Open(pOCP, Name, Page, NULL); + IOpenControlPanel_Release(pOCP); + } + CoUninitialize(); + } + return hr; +} + static INT OpenShellFolder(LPWSTR lpFolderCLSID) { @@ -73,11 +107,16 @@ wWinMain(HINSTANCE hInstance, INT nCmdShow) { HKEY hKey; + LPWSTR *argv; + int argc; /* Show the control panel window if no argument or "panel" was passed */ if (*lpCmdLine == 0 || !_wcsicmp(lpCmdLine, L"panel")) return OpenShellFolder(L""); + /* Map legacy control panels */ + if (!_wcsicmp(lpCmdLine, L"sticpl.cpl")) lpCmdLine = (LPWSTR) L"scannercamera"; + /* Check one of the built-in control panel handlers */ if (!_wcsicmp(lpCmdLine, L"admintools")) return OpenShellFolder(L"\\::{D20EA4E1-3957-11d2-A40B-0C5020524153}"); else if (!_wcsicmp(lpCmdLine, L"color")) return RunControlPanel(L"desk.cpl,,2"); @@ -99,6 +138,28 @@ wWinMain(HINSTANCE hInstance, else if (!_wcsicmp(lpCmdLine, L"userpasswords")) return RunControlPanel(L"nusrmgr.cpl"); /* Graphical User Account Manager */ else if (!_wcsicmp(lpCmdLine, L"userpasswords2")) return RUNDLL(L"netplwiz.dll,UsersRunDll"); /* Dialog based advanced User Account Manager */ + /* https://learn.microsoft.com/en-us/windows/win32/shell/executing-control-panel-items#windows-vista-canonical-names */ + argv = CommandLineToArgvW(lpCmdLine, &argc); + if (argv) + { + UINT argi = 0; + HRESULT hr = -1; + if (argc >= 2 && IsSwitch(L"name", argv[argi + 0])) + { + LPCWSTR pszPage = NULL; + if (argc >= 4 && IsSwitch(L"page", argv[argi + 2])) + { + pszPage = argv[argi + 3]; + } + hr = OpenControlPanelItem(argv[argi + 1], pszPage); + } + LocalFree(argv); + if (hr != -1) + { + return SUCCEEDED(hr); + } + } + /* It is none of them, so look for a handler in the registry */ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Control Panel\\Cpls", diff --git a/dll/win32/shell32/folders/CControlPanelFolder.cpp b/dll/win32/shell32/folders/CControlPanelFolder.cpp index be71cc8f714..085b074ee18 100644 --- a/dll/win32/shell32/folders/CControlPanelFolder.cpp +++ b/dll/win32/shell32/folders/CControlPanelFolder.cpp @@ -773,3 +773,144 @@ HRESULT WINAPI CCPLItemMenu::HandleMenuMsg( return E_NOTIMPL; } + +/************************************************************************** +* COpenControlPanel +*/ + +static HRESULT GetParsingName(PCIDLIST_ABSOLUTE pidl, PWSTR*Name) +{ + PIDLIST_ABSOLUTE pidlFree = NULL; + if (IS_INTRESOURCE(pidl)) + { + HRESULT hr = SHGetSpecialFolderLocation(NULL, (UINT)(SIZE_T)pidl, &pidlFree); + if (FAILED(hr)) + return hr; + pidl = pidlFree; + } + HRESULT hr = SHGetNameFromIDList(pidl, SIGDN_DESKTOPABSOLUTEPARSING, Name); + ILFree(pidlFree); + return hr; +} + +static HRESULT CreateCplAbsoluteParsingPath(LPCWSTR Prefix, LPCWSTR InFolderParse, PWSTR Buf, UINT cchBuf) +{ + PWSTR cpfolder; + HRESULT hr = GetParsingName((PCIDLIST_ABSOLUTE)CSIDL_CONTROLS, &cpfolder); + if (SUCCEEDED(hr)) + { + hr = StringCchPrintfW(Buf, cchBuf, L"%s\\%s%s", cpfolder, Prefix, InFolderParse); + SHFree(cpfolder); + } + return hr; +} + +static HRESULT FindExeCplClass(LPCWSTR Canonical, HKEY hKey, BOOL Wow64, LPWSTR clsid) +{ + HRESULT hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + HKEY hNSKey; + WCHAR key[MAX_PATH], buf[MAX_PATH]; + wsprintfW(key, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\%s\\NameSpace", + Wow64 ? L"ControlPanelWOW64" : L"ControlPanel"); + LSTATUS error = RegOpenKeyExW(hKey, key, 0, KEY_READ, &hNSKey); + if (error) + return HRESULT_FROM_WIN32(error); + for (DWORD i = 0; RegEnumKeyW(hNSKey, i, key, _countof(key)) == ERROR_SUCCESS; ++i) + { + IID validate; + if (SUCCEEDED(IIDFromString(key, &validate))) + { + wsprintfW(buf, L"CLSID\\%s", key); + DWORD cb = sizeof(buf); + if (RegGetValueW(HKEY_CLASSES_ROOT, buf, L"System.ApplicationName", + RRF_RT_REG_SZ, NULL, buf, &cb) == ERROR_SUCCESS) + { + if (!lstrcmpiW(buf, Canonical)) + { + lstrcpyW(clsid, key); + hr = S_OK; + } + } + } + } + RegCloseKey(hNSKey); + return hr; +} + +static HRESULT FindExeCplClass(LPCWSTR Canonical, LPWSTR clsid) +{ + HRESULT hr = E_FAIL; + if (FAILED(hr)) + hr = FindExeCplClass(Canonical, HKEY_CURRENT_USER, FALSE, clsid); + if (FAILED(hr)) + hr = FindExeCplClass(Canonical, HKEY_CURRENT_USER, TRUE, clsid); + if (FAILED(hr)) + hr = FindExeCplClass(Canonical, HKEY_LOCAL_MACHINE, FALSE, clsid); + if (FAILED(hr)) + hr = FindExeCplClass(Canonical, HKEY_LOCAL_MACHINE, TRUE, clsid); + return hr; +} + +HRESULT WINAPI COpenControlPanel::Open(LPCWSTR pszName, LPCWSTR pszPage, IUnknown *punkSite) +{ + WCHAR path[MAX_PATH], clspath[MAX_PATH]; + HRESULT hr = S_OK; + SHELLEXECUTEINFOW sei = { sizeof(sei), SEE_MASK_FLAG_DDEWAIT }; + sei.lpFile = path; + sei.nShow = SW_SHOW; + if (!pszName) + { + GetSystemDirectoryW(path, _countof(path)); + PathAppendW(path, L"control.exe"); + } + else + { + LPWSTR clsid = clspath + wsprintfW(clspath, L"CLSID\\"); + if (SUCCEEDED(hr = FindExeCplClass(pszName, clsid))) + { + if (SUCCEEDED(hr = CreateCplAbsoluteParsingPath(L"::", clsid, path, _countof(path)))) + { + // NT6 will execute "::{26EE0668-A00A-44D7-9371-BEB064C98683}\0\::{clsid}[\pszPage]" + // but we don't support parsing that so we force the class instead. + sei.fMask |= SEE_MASK_CLASSNAME; + sei.lpClass = clspath; + } + } + } + + if (SUCCEEDED(hr)) + { + DWORD error = ShellExecuteExW(&sei) ? ERROR_SUCCESS : GetLastError(); + hr = HRESULT_FROM_WIN32(error); + } + return hr; +} + +HRESULT WINAPI COpenControlPanel::GetPath(LPCWSTR pszName, LPWSTR pszPath, UINT cchPath) +{ + HRESULT hr; + if (!pszName) + { + PWSTR cpfolder; + if (SUCCEEDED(hr = GetParsingName((PCIDLIST_ABSOLUTE)CSIDL_CONTROLS, &cpfolder))) + { + hr = StringCchCopyW(pszPath, cchPath, cpfolder); + SHFree(cpfolder); + } + } + else + { + WCHAR clsid[38 + 1]; + if (SUCCEEDED(hr = FindExeCplClass(pszName, clsid))) + { + hr = CreateCplAbsoluteParsingPath(L"::", clsid, pszPath, cchPath); + } + } + return hr; +} + +HRESULT WINAPI COpenControlPanel::GetCurrentView(CPVIEW *pView) +{ + *pView = CPVIEW_CLASSIC; + return S_OK; +} diff --git a/dll/win32/shell32/folders/CControlPanelFolder.h b/dll/win32/shell32/folders/CControlPanelFolder.h index 1fa7969aff4..deb29a5f2e1 100644 --- a/dll/win32/shell32/folders/CControlPanelFolder.h +++ b/dll/win32/shell32/folders/CControlPanelFolder.h @@ -111,4 +111,25 @@ public: END_COM_MAP() }; +class COpenControlPanel : + public CComCoClass, + public CComObjectRootEx, + public IOpenControlPanel +{ + public: + // IOpenControlPanel + virtual HRESULT WINAPI Open(LPCWSTR pszName, LPCWSTR pszPage, IUnknown *punkSite); + virtual HRESULT WINAPI GetPath(LPCWSTR pszName, LPWSTR pszPath, UINT cchPath); + virtual HRESULT WINAPI GetCurrentView(CPVIEW *pView); + + static HRESULT WINAPI UpdateRegistry(BOOL bRegister) { return S_OK; } // CControlPanelFolder does it for us + DECLARE_NOT_AGGREGATABLE(COpenControlPanel) + + DECLARE_PROTECT_FINAL_CONSTRUCT() + + BEGIN_COM_MAP(COpenControlPanel) + COM_INTERFACE_ENTRY_IID(IID_IOpenControlPanel, IOpenControlPanel) + END_COM_MAP() +}; + #endif /* _SHFLDR_CPANEL_H_ */ diff --git a/dll/win32/shell32/res/rgs/controlpanel.rgs b/dll/win32/shell32/res/rgs/controlpanel.rgs index 79a1794d573..d53211d1799 100644 --- a/dll/win32/shell32/res/rgs/controlpanel.rgs +++ b/dll/win32/shell32/res/rgs/controlpanel.rgs @@ -51,3 +51,25 @@ HKLM } } } + +HKCR +{ + NoRemove CLSID + { + ForceRemove {06622D85-6856-4460-8DE1-A81921B41C4B} = s 'COpenControlPanel' + { + val AppID = s '{06622D85-6856-4460-8DE1-A81921B41C4B}' + InprocServer32 = s '%MODULE%' + { + val ThreadingModel = s 'Apartment' + } + } + } + NoRemove AppID + { + ForceRemove {06622D85-6856-4460-8DE1-A81921B41C4B} = s 'COpenControlPanel' + { + val DllSurrogate = s '' + } + } +} diff --git a/dll/win32/shell32/shell32.cpp b/dll/win32/shell32/shell32.cpp index 011d5b2980b..91e5ed22c84 100644 --- a/dll/win32/shell32/shell32.cpp +++ b/dll/win32/shell32/shell32.cpp @@ -280,6 +280,7 @@ BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_Shell, CShellDispatch) OBJECT_ENTRY(CLSID_DragDropHelper, CDropTargetHelper) OBJECT_ENTRY(CLSID_ControlPanel, CControlPanelFolder) + OBJECT_ENTRY(CLSID_OpenControlPanel, COpenControlPanel) OBJECT_ENTRY(CLSID_MyDocuments, CMyDocsFolder) OBJECT_ENTRY(CLSID_NetworkPlaces, CNetFolder) OBJECT_ENTRY(CLSID_FontsFolderShortcut, CFontsFolder) diff --git a/sdk/include/psdk/shlguid.h b/sdk/include/psdk/shlguid.h index 132f2963fdc..adada9427cd 100644 --- a/sdk/include/psdk/shlguid.h +++ b/sdk/include/psdk/shlguid.h @@ -231,6 +231,7 @@ DEFINE_GUID(CLSID_NewMenu, 0xd969A300, 0xe7FF, 0x11D0, 0xA9, 0x3B, DEFINE_GUID(IID_IShellFolderViewCB, 0x2047E320, 0xF2A9, 0x11CE, 0xAE, 0x65, 0x8, 0x00, 0x2B, 0x2E, 0x12, 0x62); DEFINE_GUID(CLSID_InternetButtons, 0x1E796980, 0x9CC5, 0x11D1, 0xA8, 0x3F, 0x0, 0xC0, 0x4F, 0xC9, 0x9D, 0x61); DEFINE_GUID(CLSID_MenuDeskBar, 0xECD4FC4F, 0x521C, 0x11D0, 0xB7, 0x92, 0x00, 0xA0, 0xC9, 0x03, 0x12, 0xE1); +DEFINE_GUID(CLSID_OpenControlPanel, 0x06622D85, 0x6856, 0x4460, 0x8D, 0xE1, 0xA8, 0x19, 0x21, 0xB4, 0x1C, 0x4B); DEFINE_GUID(SID_SMenuBandChild, 0xed9cc020, 0x08b9, 0x11d1, 0x98, 0x23, 0x0, 0xc0, 0x4f, 0xd9, 0x19, 0x72); DEFINE_GUID(SID_SMenuBandParent, 0x8c278eec, 0x3eab, 0x11d1, 0x8c, 0xb0, 0x0, 0xc0, 0x4f, 0xd9, 0x18, 0xd0); diff --git a/sdk/include/psdk/shobjidl.idl b/sdk/include/psdk/shobjidl.idl index ef9e51d16e1..7a75dff0f09 100644 --- a/sdk/include/psdk/shobjidl.idl +++ b/sdk/include/psdk/shobjidl.idl @@ -2360,6 +2360,37 @@ interface IExplorerCommandProvider : IUnknown [out, iid_is(riid)] void **ppv); }; + +/***************************************************************************** + * IOpenControlPanel interface (Vista+) + */ +[ + object, + uuid(D11AD862-66DE-4DF4-BF6C-1F5621996AF1), + pointer_default(unique) +] +interface IOpenControlPanel : IUnknown +{ + typedef [v1_enum] enum CPVIEW + { + CPVIEW_CLASSIC = 0, + CPVIEW_CATEGORY = 1, + CPVIEW_ALLITEMS = CPVIEW_CLASSIC, + CPVIEW_HOME = CPVIEW_CATEGORY + } CPVIEW; + + HRESULT Open( + [in, optional] LPCWSTR pszName, + [in, optional] LPCWSTR pszPage, + [in, optional] IUnknown *punkSite); + HRESULT GetPath( + [in, optional] LPCWSTR pszName, + [out, string, size_is(cchPath)] LPWSTR pszPath, + [in] UINT cchPath); + HRESULT GetCurrentView( + [out] CPVIEW *pView); +} + #endif // __REACTOS__ /*****************************************************************************