Windows2000/private/shell/shell32/cplobj.c
2020-09-30 17:12:32 +02:00

1225 lines
40 KiB
C

#include "shellprv.h"
#pragma hdrstop
#include "control.h"
#include "uemapp.h"
#include <limits.h>
#ifndef MAXUSHORT
#define MAXUSHORT USHRT_MAX
#endif
#define TF_CPL TF_CUSTOM2
typedef struct tagCPLAPPLETID
{
ATOM aCPL; // CPL name atom (so we can match requests)
ATOM aApplet; // applet name atom (so we can match requests, may be zero)
HWND hwndStub; // window for this dude (so we can switch to it)
UINT flags; // see PCPLIF_ flags below
} CPLAPPLETID;
// PCPLIF_DEFAULT_APPLET
// There are two ways of getting the default applet, asking for it my name and passing an empty applet name.
// This flag should be set regardless,
// so that the code which switches to an already-active applet can always find a previous instance if it exists.
#define PCPLIF_DEFAULT_APPLET (0x1)
#define CCHSZSHORT 32
#define APPLET_NAME_SIZE (ARRAYSIZE(((LPNEWCPLINFO)0)->szName)) // NB: size in chars, not bytes
typedef struct tagCPLEXECINFO{
int icon;
TCHAR cpl[CCHPATHMAX];
TCHAR applet[APPLET_NAME_SIZE];
TCHAR *params;
} CPLEXECINFO;
ATOM aCPLName = (ATOM)0;
ATOM aCPLFlags = (ATOM)0;
void CPL_ParseCommandLine(CPLEXECINFO *info, LPTSTR pszCmdLine, BOOL extract_icon);
BOOL CPL_LoadAndFindApplet(LPCPLMODULE *pcplm, HICON *phIcon, UINT *puControl, CPLEXECINFO *info);
BOOL CPL_FindCPLInfo(LPTSTR pszCmdLine, HICON *phIcon, UINT *ppapl, LPTSTR *pparm)
{
LPCPLMODULE pmod;
CPLEXECINFO info;
CPL_ParseCommandLine(&info, pszCmdLine, TRUE);
if (CPL_LoadAndFindApplet(&pmod, phIcon, ppapl, &info))
{
*pparm = info.params;
return TRUE;
}
*pparm = NULL;
return FALSE;
}
typedef struct _fcc {
LPTSTR lpszClassStub;
CPLAPPLETID *target;
HWND hwndMatch;
} FCC, *LPFCC;
BOOL _FindCPLCallback(HWND hwnd, LPARAM lParam)
{
LPFCC lpfcc = (LPFCC)lParam;
TCHAR szClass[CCHSZSHORT];
GetClassName(hwnd, szClass, ARRAYSIZE(szClass));
if (lstrcmp(szClass, lpfcc->lpszClassStub) == 0) // Must be same class...
{
// Found a stub window
if (lpfcc->target->aCPL != 0)
{
HANDLE hHandle;
ATOM aCPL;
hHandle = GetProp(hwnd, (LPCTSTR)(DWORD_PTR)aCPLName);
ASSERT((DWORD_PTR)hHandle < MAXUSHORT);
aCPL = (ATOM)(DWORD_PTR)hHandle;
if (aCPL != 0 && aCPL == lpfcc->target->aCPL)
{
ATOM aApplet;
hHandle = GetProp(hwnd, (LPCTSTR)(DWORD_PTR)aCPL);
aApplet = (ATOM)(DWORD_PTR)hHandle;
ASSERT((DWORD_PTR)hHandle < MAXUSHORT);
// users may request any applet by name
if (aApplet != 0 && aApplet == lpfcc->target->aApplet)
{
lpfcc->hwndMatch = hwnd;
return FALSE;
}
// Users may request the default w/o specifying a name
if (lpfcc->target->flags & PCPLIF_DEFAULT_APPLET)
{
UINT flags = HandleToUlong(GetProp(hwnd, MAKEINTATOM(aCPLFlags)));
if (flags & PCPLIF_DEFAULT_APPLET)
{
lpfcc->hwndMatch = hwnd;
return FALSE;
}
}
}
}
}
return TRUE;
}
HWND FindCPL(HWND hwndStub, CPLAPPLETID *target)
{
FCC fcc;
TCHAR szClassStub[CCHSZSHORT];
if (aCPLName == (ATOM)0)
{
aCPLName = GlobalAddAtom(TEXT("CPLName"));
aCPLFlags = GlobalAddAtom(TEXT("CPLFlags"));
if (aCPLName == (ATOM)0 || aCPLFlags == (ATOM)0)
return NULL;// This should never happen... didn't find hwnd
}
szClassStub[0] = '\0';// a NULL hwnd has no class
if (hwndStub)
{
GetClassName(hwndStub, szClassStub, ARRAYSIZE(szClassStub));
}
fcc.lpszClassStub = szClassStub;
fcc.target = target;
fcc.hwndMatch = (HWND)0;
EnumWindows(_FindCPLCallback, (LPARAM)&fcc);
return fcc.hwndMatch;
}
// 98/11/04 #239393 vtan: The fix for this problem is actually surgery to this file and "control1.c".
// The essential problem is that the CControlExtract struct below used to hold a LPCPLMODULE field that was a pointer to a struct in a DSA.
// The DSA was being manipulated in another thread and the data pointed to then became stale and an AV resulted. The only data
// used in the CPLMODULE struct was the HICON field. The
// LPCPLMODULE field has been replaced with an HICON field. The
// HICON is destroyed in the release method.
typedef struct
{
IExtractIcon xi;
#ifdef UNICODE
IExtractIconA xiA;
#endif
UINT cRef;
TCHAR szSubObject[MAX_PATH];
HICON hIcon;
int nControl;
} CControlExtract;
STDMETHODIMP CControlObjs_EI_QueryInterface(IExtractIcon *pxicon, REFIID riid, void **ppv)
{
CControlExtract *this = IToClass(CControlExtract, xi, pxicon);
if (IsEqualIID(riid, &IID_IExtractIcon) || IsEqualIID(riid, &IID_IUnknown)) {
*ppv = &this->xi;
}
#ifdef UNICODE
else if (IsEqualIID(riid, &IID_IExtractIconA)) {
*ppv = &this->xiA;
}
#endif
else {
*ppv = NULL;
return E_NOINTERFACE;
}
((IUnknown*)(*ppv))->lpVtbl->AddRef(*ppv);
return NOERROR;
}
STDMETHODIMP CControlObjs_EI_AddRef(IExtractIcon *pxicon)
{
CControlExtract *this = IToClass(CControlExtract, xi, pxicon);
return InterlockedIncrement(&this->cRef);
}
STDMETHODIMP CControlObjs_EI_Release(IExtractIcon *pxicon)
{
CControlExtract *this = IToClass(CControlExtract, xi, pxicon);
if (InterlockedDecrement(&this->cRef))
return this->cRef;
if (this->hIcon != NULL)
{
(BOOL)DestroyIcon(this->hIcon);
this->hIcon = NULL;
}
LocalFree((HLOCAL)this);
return(0);
}
STDMETHODIMP CControlObjs_EI_GetIconLocation(IExtractIcon *pxicon, UINT uFlags, LPTSTR szIconFile, UINT cchMax, int *piIndex, UINT *pwFlags)
{
CControlExtract *this = IToClass(CControlExtract, xi, pxicon);
LPTSTR pszComma;
if (uFlags & GIL_OPENICON)
return S_FALSE;
lstrcpyn(szIconFile, this->szSubObject, cchMax);
pszComma = StrChr(szIconFile, TEXT(','));
if (pszComma)
{
*pszComma++ = TEXT('\0');
*piIndex = StrToInt(pszComma);
*pwFlags = GIL_PERINSTANCE;
// normally the index will be negative (a resource id)
// check for some special cases like dynamic icons and bogus ids
if (*piIndex == 0) {
LPTSTR lpExtraParms = NULL;
*pwFlags |= GIL_DONTCACHE | GIL_NOTFILENAME;// this is a dynamic applet icon
// use the applet index in case there's more than one
if ((this->hIcon != NULL) || CPL_FindCPLInfo(this->szSubObject, &this->hIcon, &(UINT)this->nControl, &lpExtraParms)) {
*piIndex = this->nControl;
} else {
// we failed to load the applet all of the sudden
// use the first icon in the cpl file (*piIndex == 0)
// Assert(FALSE);
DebugMsg(DM_ERROR, TEXT("Control Panel CCEIGIL: ") TEXT("Enumeration failed \"%s\""), this->szSubObject);
}
} else if (*piIndex > 0) {
// this is an invalid icon for a control panel
// use the first icon in the file
// this may be wrong but it's better than a generic doc icon
// this fixes ODBC32 which is NOT dynamic but returns bogus ids
*piIndex = 0;
}
return(NOERROR);
}
return S_FALSE;
}
STDMETHODIMP CControlObjs_EI_ExtractIcon(IExtractIcon *pxicon,
LPCTSTR pszFile,
UINT nIconIndex,
HICON *phiconLarge,
HICON *phiconSmall,
UINT nIconSize)
{
CControlExtract *this = IToClass(CControlExtract, xi, pxicon);
LPTSTR lpExtraParms = NULL;
HRESULT result = S_FALSE;
LPCTSTR p;
// if there is no icon index then we must extract by loading the dude
// if we have an icon index then it can be extracted with ExtractIcon
// (which is much faster)
// only perform a custom extract if we have a dynamic icon
// otherwise just return S_FALSE and let our caller call ExtractIcon.
p = StrChr(this->szSubObject, TEXT(','));
if ((!p || !StrToInt(p + 1)) &&
((this->hIcon != NULL) || CPL_FindCPLInfo(this->szSubObject, &this->hIcon, &(UINT)this->nControl, &lpExtraParms)))
{
if (this->hIcon)
{
*phiconLarge = CopyIcon(this->hIcon);
*phiconSmall = NULL;
if (*phiconLarge)
result = NOERROR;
}
}
return result;
}
IExtractIconVtbl c_ControlExtractVtbl =
{
CControlObjs_EI_QueryInterface,
CControlObjs_EI_AddRef,
CControlObjs_EI_Release,
CControlObjs_EI_GetIconLocation,
CControlObjs_EI_ExtractIcon
};
#ifdef UNICODE
STDMETHODIMP CControlObjs_EIA_QueryInterface(IExtractIconA *pxiconA, REFIID riid, void **ppvOut)
{
CControlExtract *this = IToClass(CControlExtract, xiA, pxiconA);
return CControlObjs_EI_QueryInterface(&this->xi, riid, ppvOut);
}
STDMETHODIMP CControlObjs_EIA_AddRef(IExtractIconA *pxiconA)
{
CControlExtract *this = IToClass(CControlExtract, xiA, pxiconA);
return CControlObjs_EI_AddRef(&this->xi);
}
STDMETHODIMP CControlObjs_EIA_Release(IExtractIconA *pxiconA)
{
CControlExtract *this = IToClass(CControlExtract, xiA, pxiconA);
return CControlObjs_EI_Release(&this->xi);
}
STDMETHODIMP CControlObjs_EIA_GetIconLocation(IExtractIconA *pxiconA, UINT uFlags, LPSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags)
{
WCHAR szIconFile[MAX_PATH];
CControlExtract *this = IToClass(CControlExtract, xiA, pxiconA);
HRESULT hres = CControlObjs_EI_GetIconLocation(&this->xi, uFlags, szIconFile, ARRAYSIZE(szIconFile), piIndex, pwFlags);
if (SUCCEEDED(hres) && hres != S_FALSE)
{
SHUnicodeToAnsi(szIconFile, pszIconFile, cchMax);
}
return hres;
}
STDMETHODIMP CControlObjs_EIA_ExtractIcon(IExtractIconA *pxiconA,
LPCSTR pszFile,
UINT nIconIndex,
HICON *phiconLarge,
HICON *phiconSmall,
UINT nIconSize)
{
// CControlObjs_EI_ExtractIcon method doesn't use the file name.
CControlExtract *this = IToClass(CControlExtract, xiA, pxiconA);
return CControlObjs_EI_ExtractIcon(&this->xi, NULL, nIconIndex, phiconLarge, phiconSmall, nIconSize);
}
IExtractIconAVtbl c_ControlExtractAVtbl =
{
CControlObjs_EIA_QueryInterface,
CControlObjs_EIA_AddRef,
CControlObjs_EIA_Release,
CControlObjs_EIA_GetIconLocation,
CControlObjs_EIA_ExtractIcon
};
#endif
HRESULT ControlExtractIcon_CreateInstance(LPCTSTR pszSubObject, REFIID riid, void **ppv)
{
CControlExtract *this = (CControlExtract *)LocalAlloc(LPTR, sizeof(*this));
if (this)
{
HRESULT hres;
this->xi.lpVtbl = &c_ControlExtractVtbl;
#ifdef UNICODE
this->xiA.lpVtbl = &c_ControlExtractAVtbl;
#endif
this->cRef = 1;
this->hIcon = NULL;
this->nControl = -1;
lstrcpyn(this->szSubObject, pszSubObject, ARRAYSIZE(this->szSubObject));
hres = this->xi.lpVtbl->QueryInterface(&this->xi, riid, ppv);
this->xi.lpVtbl->Release(&this->xi);
return hres;
}
return E_OUTOFMEMORY;
}
// parsing helper for comma lists
TCHAR *CPL_ParseToSeparator(TCHAR *dst, TCHAR *psrc, size_t dstmax, BOOL spacedelimits)
{
if (psrc)
{
TCHAR source[CCHPATHMAX], *src;
TCHAR *delimiter, *closingquote = NULL;
lstrcpyn(source, psrc, (int)((dstmax < ARRAYSIZE(source)) ? dstmax : ARRAYSIZE(source)));
src = source;
// eat whitespace
while (*src == TEXT(' '))
src++;
delimiter = src;
// ignore stuff inside quoted strings
if (*src == TEXT('"'))
{
// start after first quote, advance src past quote
closingquote = ++src;
while (*closingquote && *closingquote != TEXT('"'))
closingquote++;
// see if loop above ended on a quote
if (*closingquote) {
*closingquote = 0;// temporary NULL termination
delimiter = closingquote + 1;// start looking for delimiter again after quotes
} else
closingquote = NULL;
}
if (spacedelimits) {
delimiter += StrCSpn(delimiter, TEXT(", "));
if (!*delimiter)
delimiter = NULL;
} else
delimiter = StrChr(delimiter, TEXT(','));
// temporary NULL termination
if (delimiter)
*delimiter = 0;
if (dst)
{
lstrcpyn(dst, src, (int)dstmax);
dst[dstmax - 1] = 0;
}
// put back stuff we terminated above
if (delimiter)
*delimiter = TEXT(',');
if (closingquote)
*closingquote = TEXT('"');
psrc = (delimiter ? (psrc + ((delimiter + 1) - source)) : NULL);// return start of next string
} else if (dst) {
*dst = 0;
}
return psrc;// new source location
}
// parse the Control_RunDLL command line
// format: "CPL name, applet name, extra params"
// format: "CPL name, icon index, applet name, extra params"
// NOTE: [stevecat] 3/10/95
// The 'extra params' do not have to be delimited by a "," in NT for the case "CPL name applet name extra params"
// A workaround for applet names that include a space in their name would be to enclose that value in
// double quotes (see the CPL_ParseToSeparator routine.)
void CPL_ParseCommandLine(CPLEXECINFO *info, LPTSTR pszCmdLine, BOOL extract_icon)
{
// parse out the CPL name, spaces are valid separators
pszCmdLine = CPL_ParseToSeparator(info->cpl, pszCmdLine, CCHPATHMAX, TRUE);
if (extract_icon) {
TCHAR icon[8];
// parse out the icon id/index, spaces are not valid separators
pszCmdLine = CPL_ParseToSeparator(icon, pszCmdLine, ARRAYSIZE(icon), FALSE);
info->icon = StrToInt(icon);
} else
info->icon = 0;
// parse out the applet name, spaces are not valid separators
info->params = CPL_ParseToSeparator(info->applet, pszCmdLine, APPLET_NAME_SIZE, FALSE);
CPL_StripAmpersand(info->applet);
}
BOOL CPL_LoadAndFindApplet(LPCPLMODULE *ppcplm, HICON *phIcon, UINT *puControl, CPLEXECINFO *info)
{
TCHAR szControl[ARRAYSIZE(((LPNEWCPLINFO)0)->szName)];
LPCPLMODULE pcplm;
LPCPLITEM pcpli;
int nControl = 0; // fall thru to default
int NumControls;
ENTERCRITICAL;
pcplm = CPL_LoadCPLModule(info->cpl);
if (!pcplm || !pcplm->hacpli)
{
DebugMsg(DM_ERROR, TEXT("Control_RunDLL: ") TEXT("CPL_LoadCPLModule failed \"%s\""), info->cpl);
LEAVECRITICAL;
goto Error0;
}
// Look for the specified applet
// no applet specified selects applet 0
if (*info->applet)
{
NumControls = DSA_GetItemCount(pcplm->hacpli);
if (info->applet[0] == TEXT('@'))
{
nControl = StrToLong(info->applet + 1);
if (nControl >= 0 && nControl < NumControls)
{
goto GotControl;
}
}
// Check for the "Setup" argument and send the special CPL_SETUP
// message to the applet to tell it we are running under Setup.
if (!lstrcmpi(TEXT("Setup"), info->params))
CPL_CallEntry(pcplm, NULL, CPL_SETUP, 0L, 0L);
for (nControl = 0; nControl < NumControls; nControl++)
{
pcpli = DSA_GetItemPtr(pcplm->hacpli, nControl);
lstrcpy(szControl, pcpli->pszName);
CPL_StripAmpersand(szControl);
// if there is only one control, then use it. This solves some compat issues with CP names changing.
if (lstrcmpi(info->applet, szControl) == 0 || 1 == NumControls)
break;
}
// If we get to the end of the list, bail out
// LEGACY WARNING: It might be necessary to handle some old applet names in a special way.
// This would be bad because the names are localized. We would need to somehow
// call into the CPL's to ask them if they support the given name. Only the CPL
// itself would know the correct legacy name mapping. Adding a new CPL message might
// cause as many legacy CPL problems as it solves so we would need to do something tricky like adding an exported function.
// We could then GetProcAddress on this exported function.
// If the export exists, we would pass it the legacy name and it would return a number.
// Example: "control mmsys.cpl,Sounds" must work even though mmsys.cpl no longer contains an applet called "Sounds".
// "control mmsys.cpl,Multimedia" must work even though mmsys.cpl no longer contains an applet called "Multimedia".
// You can't simply rename the applet becuase these two CPL's were both merged into one CPL.
// Renaming could never solve more than half the problem.
if (nControl >= NumControls)
{
DebugMsg(DM_ERROR, TEXT("Control_RunDLL: ") TEXT("Cannot find specified applet"));
LEAVECRITICAL;
goto Error1;
}
}
GotControl:
if (phIcon != NULL)
{
pcpli = DSA_GetItemPtr(pcplm->hacpli, nControl);
*phIcon = CopyIcon(pcpli->hIcon);
}
LEAVECRITICAL;
// yes, we really do want to pass negative indices through...
*puControl = (UINT)nControl;
*ppcplm = pcplm;
return TRUE;
Error1:
CPL_FreeCPLModule(pcplm);
Error0:
return FALSE;
}
void OpenControlPanelFolder(HWND hwnd, int nCmdShow, UINT nFolder)
{
LPITEMIDLIST pidl = SHCloneSpecialIDList(hwnd, nFolder, FALSE);
if (pidl)
{
CMINVOKECOMMANDINFOEX ici = {
SIZEOF(CMINVOKECOMMANDINFOEX),
CMIC_MASK_UNICODE,
hwnd,
NULL,
NULL, NULL,
nCmdShow,
};
InvokeFolderCommandUsingPidl(&ici, NULL, pidl, NULL, SEE_MASK_FLAG_DDEWAIT);
ILFree(pidl);
}
}
BOOL CPL_Identify(CPLAPPLETID *identity, CPLEXECINFO *info, HWND stub)
{
identity->aApplet = (ATOM)0;
identity->hwndStub = stub;
identity->flags = 0;
if ((identity->aCPL = GlobalAddAtom(info->cpl)) == (ATOM)0)
return FALSE;
if (*info->applet) {
if ((identity->aApplet = GlobalAddAtom(info->applet)) == (ATOM)0)
return FALSE;
} else {// no applet name means use the default
identity->flags = PCPLIF_DEFAULT_APPLET;
}
return TRUE;
}
void CPL_UnIdentify(CPLAPPLETID *identity)
{
if (identity->aCPL)
{
GlobalDeleteAtom(identity->aCPL);
identity->aCPL = (ATOM)0;
}
if (identity->aApplet)
{
GlobalDeleteAtom(identity->aApplet);
identity->aApplet = (ATOM)0;
}
identity->hwndStub = NULL;
identity->flags = 0;
}
// It's time for Legacy Crap Mode!!! In NT5 we removed a bunch of CPL files from the product.
// These files are used by name by many programs. As a result,
// the old names need to keep working even though the files no longer exist.
// We handle this by checking if the file exists. If it does not exist, we run
// the cpl name through a mapping table and then try again. The mapping table can
// potentially change the CPL name, the applet number, and the params.
typedef struct tagRUNDLLCPLMAPPING{
LPTSTR oldInfo_cpl;
LPTSTR oldInfo_applet;
LPTSTR oldInfo_params;
LPTSTR newInfo_cpl;
LPTSTR newInfo_applet;
LPTSTR newInfo_params;
} RUNDLLCPLMAPPING, *LPRUNDLLCPLMAPPING;
// For the oldInfo member, a NULL means to match any value from the pinfo structure.
// If the oldInfo structure mathces the pinfo structure then it will be updated using
// the data from newInfo structure. For the newInfo member, a NULL means to leave the corresponding pinfo member unchanged.
const RUNDLLCPLMAPPING g_rgRunDllCPLMapping[] =
{
{ TEXT("MODEM.CPL"), NULL, NULL, TEXT("TELEPHON.CPL"), TEXT("@0"), TEXT("1") },
{ TEXT("UPS.CPL"), NULL, NULL, TEXT("POWERCFG.CPL"), NULL, NULL }
};
BOOL CPL_CheckLegacyMappings(CPLEXECINFO * pinfo)
{
LPTSTR p;
int i;
TraceMsg(TF_CPL, "Attmepting Legacy CPL conversion on %s", pinfo->cpl);
// we want only the filename, strip off any path information
p = PathFindFileName(pinfo->cpl);
StrCpyN(pinfo->cpl, p, CCHPATHMAX);
for (i = 0; i < ARRAYSIZE(g_rgRunDllCPLMapping); i++)
{
if (0 == StrCmpI(pinfo->cpl, g_rgRunDllCPLMapping[i].oldInfo_cpl))
{
if (!g_rgRunDllCPLMapping[i].oldInfo_applet || 0 == StrCmpI(pinfo->applet, g_rgRunDllCPLMapping[i].oldInfo_applet))
{
if (!g_rgRunDllCPLMapping[i].oldInfo_params ||
(pinfo->params && 0 == StrCmpI(pinfo->params, g_rgRunDllCPLMapping[i].oldInfo_params)))
{
if (pinfo->params) {
TraceMsg(TF_CPL, "%s,%s,%s matches item %d", pinfo->cpl, pinfo->applet, pinfo->params, i);
} else {
TraceMsg(TF_CPL, "%s,%s matches item %d", pinfo->cpl, pinfo->applet, i);
}
// The current entry matches the request. Map to the new info and then ensure the new CPL exists.
StrCpyN(pinfo->cpl, g_rgRunDllCPLMapping[i].newInfo_cpl, CCHPATHMAX);
if (g_rgRunDllCPLMapping[i].newInfo_applet)
{
StrCpyN(pinfo->applet, g_rgRunDllCPLMapping[i].newInfo_applet, APPLET_NAME_SIZE);
}
if (g_rgRunDllCPLMapping[i].newInfo_params)
{
// the params pointer is normally a pointer into the remaining chunk of a string
// buffer. As such, we don't need to delete the memory it points to. Also, this
// argument is read only so it should be safe for us to point it at our constant data.
pinfo->params = g_rgRunDllCPLMapping[i].newInfo_params;
}
if (pinfo->params) {
TraceMsg(TF_CPL, "CPL mapped to %s,%s,%s", pinfo->cpl, pinfo->applet, pinfo->params);
} else {
TraceMsg(TF_CPL, "CPL mapped to %s,%s", pinfo->cpl, pinfo->applet);
}
return PathFindOnPath(pinfo->cpl, NULL);
}
}
}
}
return FALSE;
}
// Goes through all of the work of identifying and starting a control applet.
// Accepts a flag specifying whether or not to load a new DLL if it is not already present.
// This code will ALLWAYS switch to an existing instance of the applet if bFindExisting is specified.
// WARNING: this function butchers the command line you pass in!
BOOL CPL_RunMeBaby(HWND hwndStub, HINSTANCE hAppInstance, LPTSTR pszCmdLine, int nCmdShow, BOOL bAllowLoad, BOOL bFindExisting)
{
int nApplet;
LPCPLMODULE pcplm;
LPCPLITEM pcpli;
CPLEXECINFO info;
CPLAPPLETID identity;
TCHAR szApplet[APPLET_NAME_SIZE];
BOOL bResult = FALSE;
HWND hwndOtherStub;
if (SHRestricted(REST_NOCONTROLPANEL))
{
ShellMessageBox(HINST_THISDLL, hwndStub, MAKEINTRESOURCE(IDS_RESTRICTIONS), MAKEINTRESOURCE(IDS_RESTRICTIONSTITLE), MB_OK | MB_ICONSTOP);
return FALSE;
}
CoInitialize(0);
CPL_ParseCommandLine(&info, pszCmdLine, FALSE);// parse the command line we got
// no applet to run means open the controls folder
if (!*info.cpl)
{
OpenControlPanelFolder(hwndStub, nCmdShow, CSIDL_CONTROLS);
bResult = TRUE;
goto Error0;
}
// expand CPL name to a full path if it isn't already
if (PathIsFileSpec(info.cpl))
{
if (!PathFindOnPath(info.cpl, NULL))
{
if (!CPL_CheckLegacyMappings(&info))
goto Error0;
}
}
else if (!PathFileExists(info.cpl))
{
if (!CPL_CheckLegacyMappings(&info))
goto Error0;
}
if (!CPL_Identify(&identity, &info, hwndStub))
goto Error0;
// If we have already loaded this CPL, then jump to the existing window
hwndOtherStub = FindCPL(hwndStub, &identity);
// If we found a window and the caller says its ok to find an existing window then set the focus to it
if (bFindExisting && hwndOtherStub)
{
// try to find a CPL window on top of it
HWND hwndTarget = GetLastActivePopup(hwndOtherStub);
if (hwndTarget && IsWindow(hwndTarget))
{
DebugMsg(DM_WARNING, TEXT("Control_RunDLL: ") TEXT("Switching to already loaded CPL applet"));
SetForegroundWindow(hwndTarget);
bResult = TRUE;
goto Error1;
}
// couldn't find it, must be exiting or some sort of error...
// so ignore it.
DebugMsg(DM_WARNING, TEXT("Control_RunDLL: ") TEXT("Bogus CPL identity in array; purging after (presumed) RunDLL crash"));
}
// stop here if we're not allowed to load the cpl
if (!bAllowLoad)
goto Error1;
// i guess we didn't stop up there
if (!CPL_LoadAndFindApplet(&pcplm, NULL, &nApplet, &info))
goto Error1;
// get the name that the applet thinks it should have
pcpli = DSA_GetItemPtr(pcplm->hacpli, nApplet);
lstrcpy(szApplet, pcpli->pszName);
CPL_StripAmpersand(szApplet);
// handle "default applet" cases before running anything
if (identity.aApplet)
{
// we were started with an explicitly named applet
if (!nApplet)
{
// we were started with the name of the default applet
identity.flags |= PCPLIF_DEFAULT_APPLET;
}
}
else
{
// we were started without a name, assume the default applet
identity.flags |= PCPLIF_DEFAULT_APPLET;
// get the applet's name (now that we've loaded it's CPL)
if ((identity.aApplet = GlobalAddAtom(szApplet)) == (ATOM)0)
{
// bail 'cause we could nuke a CPL if we don't have this
goto Error2;
}
}
// mark the window so we'll be able to verify that it's really ours
if (aCPLName == (ATOM)0)
{
aCPLName = GlobalAddAtom(TEXT("CPLName"));
aCPLFlags = GlobalAddAtom(TEXT("CPLFlags"));
if (aCPLName == (ATOM)0 || aCPLFlags == (ATOM)0)
goto Error2; // This should never happen... blow off applet
}
if (!SetProp(hwndStub, // Mark its name
MAKEINTATOM(aCPLName), (HANDLE)(DWORD_PTR)identity.aCPL))
{
goto Error2;
}
if (!SetProp(hwndStub, // Mark its applet
MAKEINTATOM(identity.aCPL), (HANDLE)(DWORD_PTR)identity.aApplet))
{
goto Error2;
}
if (identity.flags)
{
if (aCPLFlags == (ATOM)0)
aCPLFlags = GlobalAddAtom(TEXT("CPLFlags"));
// Mark its flags
SetProp(hwndStub, MAKEINTATOM(aCPLFlags), (HANDLE)UIntToPtr(identity.flags));
}
// Send the stub window a message so it will have the correct title and icon in the alt-tab window, etc...
if (hwndStub) {
DWORD dwPID;
SendMessage(hwndStub, STUBM_SETICONTITLE, (WPARAM)pcpli->hIcon, (LPARAM)szApplet);
GetWindowThreadProcessId(hwndStub, &dwPID);
if (dwPID == GetCurrentProcessId()) {
RUNDLL_NOTIFY sNotify;
sNotify.hIcon = pcpli->hIcon;
sNotify.lpszTitle = szApplet;
// HACK: It will look like the stub window is sending itself
// a WM_NOTIFY message. Oh well.
SendNotify(hwndStub, hwndStub, RDN_TASKINFO, (NMHDR FAR*)&sNotify);
}
}
if (info.params)
{
DebugMsg(DM_TRACE, TEXT("Control_RunDLL: ") TEXT("Sending CPL_STARTWPARAMS to applet with: %s"), info.params);
bResult = BOOLFROMPTR(CPL_CallEntry(pcplm, hwndStub, CPL_STARTWPARMS, (LONG)nApplet, (LPARAM)info.params));
}
#ifdef WINNT // REVIEW: May need this on Nashville too...
// Check whether we need to run as a different windows version
{
PPEB Peb = NtCurrentPeb();
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pcplm->minst.hinst;
PIMAGE_NT_HEADERS pHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)pcplm->minst.hinst + pDosHeader->e_lfanew);
if (pHeader->FileHeader.SizeOfOptionalHeader != 0 && pHeader->OptionalHeader.Win32VersionValue != 0)
{
// Stolen from ntos\mm\procsup.c
Peb->OSMajorVersion = pHeader->OptionalHeader.Win32VersionValue & 0xFF;
Peb->OSMinorVersion = (pHeader->OptionalHeader.Win32VersionValue >> 8) & 0xFF;
Peb->OSBuildNumber = (USHORT)((pHeader->OptionalHeader.Win32VersionValue >> 16) & 0x3FFF);
Peb->OSPlatformId = (pHeader->OptionalHeader.Win32VersionValue >> 30) ^ 0x2;
}
}
#endif
#ifdef UNICODE
// If the cpl didn't respond to CPL_STARTWPARMSW (unicode version), maybe it is an ANSI only CPL
if (info.params && (!bResult))
{
int cchParams = WideCharToMultiByte(CP_ACP, 0, info.params, -1, NULL, 0, NULL, NULL);
LPSTR lpstrParams = LocalAlloc(LMEM_FIXED, SIZEOF(char) * cchParams);
if (lpstrParams != NULL)
{
WideCharToMultiByte(CP_ACP, 0, info.params, -1, lpstrParams, cchParams, NULL, NULL);
DebugMsg(DM_TRACE, TEXT("Control_RunDLL: ") TEXT("Sending CPL_STARTWPARAMSA to applet with: %hs"), lpstrParams);
bResult = BOOLFROMPTR(CPL_CallEntry(pcplm, hwndStub, CPL_STARTWPARMSA, (LONG)nApplet, (LPARAM)lpstrParams));
LocalFree(lpstrParams);
}
}
#endif
if (!bResult)
{
DebugMsg(DM_TRACE, TEXT("Control_RunDLL: ") TEXT("Sending CPL_DBLCLK to applet"));
CPL_CallEntry(pcplm, hwndStub, CPL_DBLCLK, (LONG)nApplet, pcpli->lData);
bResult = TRUE;// some 3x applets return the wrong value so we can't fail here
}
bResult = TRUE;// wow, we made it!
RemoveProp(hwndStub, (LPCTSTR)(UINT_PTR)identity.aCPL);
Error2:
CPL_FreeCPLModule(pcplm);
Error1:
CPL_UnIdentify(&identity);
Error0:
CoUninitialize();
return bResult;
}
// Check the following reg location and see if this CPL is registered to run in proc:
// HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ControlPanel\\InProcCPLs
STDAPI_(BOOL) CPL_IsInProc(LPCTSTR pszCmdLine)
{
BOOL bInProcCPL = FALSE;
TCHAR szTempCmdLine[2 * MAX_PATH];
CPLEXECINFO info = { 0 };
LPTSTR pszCPLFile = NULL;
ASSERT(pszCmdLine);
lstrcpy(szTempCmdLine, pszCmdLine);// Make a copy of the command line
CPL_ParseCommandLine(&info, szTempCmdLine, FALSE);// Parse the command line using standard parsing function
// Find the file name of this cpl
pszCPLFile = PathFindFileName(info.cpl);
if (pszCPLFile)
{
// Open the reg key
HKEY hkeyInProcCPL = NULL;
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,
TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ControlPanel\\InProcCPLs"),
0,
KEY_READ,
&hkeyInProcCPL))
{
// Look up in the registry for this cpl name
LONG cbData;
if (ERROR_SUCCESS == SHQueryValueEx(hkeyInProcCPL, pszCPLFile, NULL, NULL, NULL, &cbData))
bInProcCPL = TRUE;
RegCloseKey(hkeyInProcCPL);
}
}
return bInProcCPL;
}
// Starts a remote control applet on a new RunDLL process Or on another thread InProcess
STDAPI_(BOOL) CPL_RunRemote(LPCTSTR pszCmdLine, HWND hwnd, BOOL fRunAsNewUser)
{
BOOL bRet = FALSE;
TCHAR szRunParams[2 * MAX_PATH];
if (fRunAsNewUser) {
wsprintf(szRunParams, TEXT("shell32.dll,Control_RunDLLAsUser %s"), pszCmdLine);
} else {
wsprintf(szRunParams, TEXT("shell32.dll,Control_RunDLL %s"), pszCmdLine);
}
if (!fRunAsNewUser && CPL_IsInProc(pszCmdLine))
// lanuch this cpl in process from another thread
bRet = SHRunDLLThread(hwnd, szRunParams, SW_SHOWNORMAL);
else
// lanuch this cpl on another thread
bRet = SHRunDLLProcess(hwnd, szRunParams, SW_SHOWNORMAL, IDS_CONTROLPANEL, fRunAsNewUser);
return bRet;
}
// Attempts to open the specified control applet.
// Tries to switch to an existing instance before starting a new one, unless the user
// specifies the fRunAsNewUser in which case we always launch a new process.
STDAPI_(BOOL) SHRunControlPanelEx(LPCTSTR pszOrigCmdLine, HWND hwnd, BOOL fRunAsNewUser)
{
BOOL bRes = FALSE;
LPTSTR pszCmdLine = NULL;
// check to see if the caller passed a resource id instead of a string
if (!IS_INTRESOURCE(pszOrigCmdLine)) {
pszCmdLine = StrDup(pszOrigCmdLine);
} else {
TCHAR szCmdLine[MAX_PATH];
if (LoadString(HINST_THISDLL, PtrToUlong((PVOID)pszOrigCmdLine), szCmdLine, ARRAYSIZE(szCmdLine)))
pszCmdLine = StrDup(szCmdLine);
}
// CPL_RunMeBaby whacks on the command line while parsing...use a dup
if (pszCmdLine)
{
if (!fRunAsNewUser)
{
// if fRunAsNewUser is NOT specified, then try to switch to an active CPL
// which matches our pszCmdLine
bRes = CPL_RunMeBaby(NULL, NULL, pszCmdLine, SW_SHOWNORMAL, FALSE, TRUE);
}
if (!bRes)
{
// launch a new cpl in a separate process
bRes = CPL_RunRemote(pszCmdLine, hwnd, fRunAsNewUser);
}
LocalFree(pszCmdLine);
}
if (bRes && UEMIsLoaded() && !IS_INTRESOURCE(pszOrigCmdLine))
{
UEMFireEvent(&UEMIID_SHELL, UEME_RUNCPL, UEMF_XEVENT, -1, (LPARAM)pszOrigCmdLine);
}
return bRes;
}
// This function is a TCHAR export from shell32 (header defn is in shsemip.h)
// UNDOCUMENTED: You may pass a shell32 resource ID in place of a pszCmdLine
STDAPI_(BOOL) SHRunControlPanel(LPCTSTR pszOrigCmdLine, HWND hwnd)
{
return SHRunControlPanelEx(pszOrigCmdLine, hwnd, FALSE);
}
// Attempts to open the specified control applet.
// This function is intended to be called by RunDLL for isolating applets.
// Tries to switch to an existing instance before starting a new one.
// The command lines for Control_RunDLL are as follows:
// 1) rundll32 shell32.dll,Control_RunDLL fred.cpl,@n,arguments
// This launches the (n+1)th applet in fred.cpl.
// If "@n" is not supplied, the default is @0.
// 2) rundll32 shell32.dll,Control_RunDLL fred.cpl,Ba&rney,arguments
// This launches the applet in fred.cpl named "Barney". Ampersands are stripped from the name.
// 3) rundll32 shell32.dll,Control_RunDLL fred.cpl,Setup
// This loads fred.cpl and sends it a CPL_SETUP message.
// In cases (1) and (2), the "arguments" are passed to the applet via
// the CPL_STARTWPARAMS (start with parameters) message. It is the
// applet's job to parse the arguments and do something interesting.
// It is traditional for the command line of a cpl to be the index of
// the page that should initially be shown to the user, but that's just tradition.
STDAPI_(void) Control_RunDLL(HWND hwndStub, HINSTANCE hAppInstance, LPSTR pszCmdLine, int nCmdShow)
{
TCHAR szCmdLine[MAX_PATH * 2];
SHAnsiToTChar(pszCmdLine, szCmdLine, ARRAYSIZE(szCmdLine));
CPL_RunMeBaby(hwndStub, hAppInstance, szCmdLine, nCmdShow, TRUE, TRUE);
}
STDAPI_(void) Control_RunDLLW(HWND hwndStub, HINSTANCE hAppInstance, LPWSTR lpwszCmdLine, int nCmdShow)
{
TCHAR szCmdLine[MAX_PATH * 2];
SHUnicodeToTChar(lpwszCmdLine, szCmdLine, ARRAYSIZE(szCmdLine));
CPL_RunMeBaby(hwndStub, hAppInstance, szCmdLine, nCmdShow, TRUE, TRUE);
}
#ifdef WINNT
// This is the entry that gets called when we run a cpl as a new user.
STDAPI_(void) Control_RunDLLAsUserW(HWND hwndStub, HINSTANCE hAppInstance, LPWSTR lpwszCmdLine, int nCmdShow)
{
CPL_RunMeBaby(hwndStub, hAppInstance, lpwszCmdLine, nCmdShow, TRUE, FALSE);
}
#endif
#ifdef DEBUG
// Type checking
static const RUNDLLPROCA lpfnRunDLL = Control_RunDLL;
static const RUNDLLPROCW lpfnRunDLLW = Control_RunDLLW;
#endif
typedef struct{// data passed around dialog and worker thread for Control_FillCache_RunDLL
IShellFolder * psfControl;
IEnumIDList * penumControl;
HWND dialog;
} FillCacheData;
// important work of Control_FillCache_RunDLL
// jogs the control panel enumerator so it will fill the presentation cache
// also forces the applet icons to be extracted into the shell icon cache
DWORD CALLBACK Control_FillCacheThreadProc(void *pv)
{
FillCacheData *data = (FillCacheData *)pv;
LPITEMIDLIST pidlApplet;
ULONG dummy;
while (data->penumControl->lpVtbl->Next(data->penumControl, 1, &pidlApplet, &dummy) == NOERROR)
{
SHMapPIDLToSystemImageListIndex(data->psfControl, pidlApplet, NULL);
ILFree(pidlApplet);
}
if (data->dialog)
EndDialog(data->dialog, 0);
return 0;
}
// dlgproc for Control_FillCache_RunDLL UI
// just something to keep the user entertained while we load a billion DLLs
BOOL_PTR CALLBACK _Control_FillCacheDlg(HWND dialog, UINT message, WPARAM wparam, LPARAM lparam)
{
switch (message)
{
case WM_INITDIALOG:
{
DWORD dummy;
HANDLE thread;
((FillCacheData *)lparam)->dialog = dialog;
thread = CreateThread(NULL, 0, Control_FillCacheThreadProc, (void*)lparam, 0, &dummy);
if (thread)
CloseHandle(thread);
else
EndDialog(dialog, -1);
}
break;
case WM_COMMAND:
break;
default:
return FALSE;
}
return TRUE;
}
// enumerates control applets in a manner that fills the presentation cache
// this is so the first time a user opens the control panel it comes up fast intended to be called at final setup on first boot
// FUNCTION WORKS FOR BOTH ANSI/UNICODE, it never uses pszCmdLine
STDAPI_(void) Control_FillCache_RunDLL(HWND hwndStub, HINSTANCE hAppInstance, LPSTR pszCmdLine, int nCmdShow)
{
IShellFolder *psfDesktop;
HKEY hk;
// nuke the old data so that any bogus cached info from a beta goes away
if (RegOpenKey(HKEY_LOCAL_MACHINE, c_szCPLCache, &hk) == ERROR_SUCCESS)
{
RegDeleteValue(hk, c_szCPLData);
RegCloseKey(hk);
}
SHGetDesktopFolder(&psfDesktop);
Shell_GetImageLists(NULL, NULL); // make sure icon cache is around
if (psfDesktop)
{
LPITEMIDLIST pidlControl = SHCloneSpecialIDList(hwndStub, CSIDL_CONTROLS, FALSE);
if (pidlControl)
{
FillCacheData data;
if (SUCCEEDED(psfDesktop->lpVtbl->BindToObject(psfDesktop, pidlControl, NULL, &IID_IShellFolder, &data.psfControl)))
{
if (SUCCEEDED(data.psfControl->lpVtbl->EnumObjects(data.psfControl, NULL, SHCONTF_NONFOLDERS, &data.penumControl)))
{
if (nCmdShow == SW_HIDE ||
DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_CPL_FILLCACHE), hwndStub, _Control_FillCacheDlg, (LPARAM)&data) == -1)
{
Control_FillCacheThreadProc(&data);
}
data.penumControl->lpVtbl->Release(data.penumControl);
}
data.psfControl->lpVtbl->Release(data.psfControl);
}
ILFree(pidlControl);
}
}
}
STDAPI_(void) Control_FillCache_RunDLLW(HWND hwndStub, HINSTANCE hAppInstance, LPWSTR lpwszCmdLine, int nCmdShow)
{
Control_FillCache_RunDLL(hwndStub, hAppInstance, NULL, nCmdShow);
}