[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.
This commit is contained in:
Whindmar Saksit 2024-01-17 17:07:21 +01:00 committed by GitHub
parent fb43301bad
commit d41dec2e07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 281 additions and 1 deletions

View file

@ -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)

View file

@ -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 <stdio.h>
#define WIN32_NO_STATUS
#define COBJMACROS
#include <windef.h>
#include <winbase.h>
@ -17,6 +19,9 @@
#include <winreg.h>
#include <shellapi.h>
#include <strsafe.h>
#include <objbase.h>
#include <shobjidl.h>
#include <shlguid.h>
#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",

View file

@ -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;
}

View file

@ -111,4 +111,25 @@ public:
END_COM_MAP()
};
class COpenControlPanel :
public CComCoClass<COpenControlPanel, &CLSID_OpenControlPanel>,
public CComObjectRootEx<CComMultiThreadModelNoCS>,
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_ */

View file

@ -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 ''
}
}
}

View file

@ -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)

View file

@ -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);

View file

@ -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__
/*****************************************************************************