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

744 lines
21 KiB
C

#include "shellprv.h"
#pragma hdrstop
#include "defext.h"
#include "recdocs.h"
#include "drives.h"
// from mtpt.cpp
STDAPI_(BOOL) CMtPt_IsAudioCD(int iDrive);
// from fsassoc.c
BOOL GetClassDescription(HKEY hkClasses, LPCTSTR pszClass, LPTSTR szDisplayName, int cbDisplayName, UINT uFlags);
// System Default Pages/Menu Extension
typedef struct
{
CCommonUnknown cunk;
CCommonShellExtInit cshx;
CCommonShellPropSheetExt cspx;
CKnownContextMenu kcxm;
HDKA hdka;
} CDefExt;
STDMETHODIMP CDefExt_QueryInterface(IUnknown *punk, REFIID riid, void **ppvObj)
{
CDefExt *this = IToClass(CDefExt, cunk.unk, punk);
if (IsEqualIID(riid, &IID_IUnknown))
{
*((IUnknown **)ppvObj) = &this->cunk.unk;
}
else if (IsEqualIID(riid, &IID_IShellExtInit) ||
IsEqualIID(riid, &CLSID_CCommonShellExtInit))
{
*((IShellExtInit **)ppvObj) = &this->cshx.kshx.unk;
}
else if (IsEqualIID(riid, &IID_IShellPropSheetExt))
{
*((IShellPropSheetExt **)ppvObj) = &this->cspx.kspx.unk;
}
else if (IsEqualIID(riid, &IID_IContextMenu))
{
*((IContextMenu **)ppvObj) = &this->kcxm.unk;
}
else
{
*ppvObj = NULL;
return E_NOINTERFACE;
}
this->cunk.cRef++;
return NOERROR;
}
STDMETHODIMP_(ULONG) CDefExt_AddRef(IUnknown *punk)
{
CDefExt *this = IToClass(CDefExt, cunk.unk, punk);
this->cunk.cRef++;
return this->cunk.cRef;
}
STDMETHODIMP_(ULONG) CDefExt_Release(IUnknown *punk)
{
CDefExt *this = IToClass(CDefExt, cunk.unk, punk);
this->cunk.cRef--;
if (this->cunk.cRef > 0)
return this->cunk.cRef;
CCommonShellExtInit_Delete(&this->cshx);
if (this->hdka)
DKA_Destroy(this->hdka);
LocalFree((HLOCAL)this);
return 0;
}
const IUnknownVtbl c_CDefExtVtbl =
{
CDefExt_QueryInterface, CDefExt_AddRef, CDefExt_Release,
};
HDKA DefExt_GetDKA(CDefExt * this, BOOL fExploreFirst)
{
if (this->hdka == NULL && this->cshx.hkeyProgID)
{
TCHAR szTemp[80];
// create either "open" or "explore open"
if (fExploreFirst)
wsprintf(szTemp, TEXT("%s %s"), c_szExplore, c_szOpen);
// Always get the whole DKA (not just the default) since we may
// need different parts of it at different times.
this->hdka = DKA_Create(this->cshx.hkeyProgID, c_szShell,
NULL, fExploreFirst ? szTemp : c_szOpen, 0);
}
return this->hdka;
}
// Descriptions:
// This function generates appropriate menu string from the given
// verb key string. This function is called if the verb key does
// not have the value.
// Arguments:
// szMenuString -- specifies a string buffer to be filled with menu string.
// pszVerbKey -- specifies the verb key string.
// Requires:
// The size of szMenuString buffer should be larger than CCH_MENUMAX
// History:
// 12-31-92 SatoNa Created
BOOL _IGenerateMenuString(LPTSTR pszMenuString, LPCTSTR pszVerbKey, UINT cchMax)
{
// Table look-up (verb key -> menu string mapping)
const static struct {
LPCTSTR pszVerb;
UINT id;
} sVerbTrans[] = {
c_szOpen, IDS_MENUOPEN,
c_szExplore, IDS_MENUEXPLORE,
c_szFind, IDS_MENUFIND,
c_szPrint, IDS_MENUPRINT,
c_szOpenAs, IDS_MENUOPEN,
TEXT("runas"),IDS_MENURUNAS
};
const struct {
LPCTSTR pszVerb;
} sVerbIgnore[] = {
c_szPrintTo
};
int i;
VDATEINPUTBUF(pszMenuString, TCHAR, cchMax);
for (i = 0; i < ARRAYSIZE(sVerbTrans); i++)
{
if (lstrcmpi(pszVerbKey, sVerbTrans[i].pszVerb) == 0)
{
if (LoadString(HINST_THISDLL, sVerbTrans[i].id, pszMenuString, cchMax))
return TRUE;
break;
}
}
for (i = 0; i < ARRAYSIZE(sVerbIgnore); i++)
{
if (lstrcmpi(pszVerbKey, sVerbIgnore[i].pszVerb) == 0)
{
return FALSE;
}
}
// Worst case: Just put '&' on the top.
if (!IsDBCSLeadByte(*pszVerbKey))
{
pszMenuString[0] = TEXT('&');
pszMenuString++;
}
lstrcpy(pszMenuString, pszVerbKey);
return TRUE;
}
BOOL _GetMenuStringFromDKA(HDKA hdka, UINT id, BOOL fExtended, LPTSTR pszMenu, UINT cchMax)
{
LONG cbVerb = CbFromCch(cchMax);
LPCTSTR pszVerbKey = DKA_GetKey(hdka, id);
VDATEINPUTBUF(pszMenu, TCHAR, cchMax);
// Get the menu string.
if (fExtended || ERROR_SUCCESS != DKA_QueryOtherValue(hdka, id, TEXT("Extended"), NULL, NULL))
{
// this is not an extended verb, or
// the request includes extended verbs
if (DKA_QueryValue(hdka, id, pszMenu, &cbVerb) != ERROR_SUCCESS || cbVerb <= SIZEOF(TCHAR))
{
// If it does not have the value, generate it.
return _IGenerateMenuString(pszMenu, pszVerbKey, cchMax);
}
return TRUE;
}
// this is an extended value on a non-extended menu
return FALSE;
}
STDMETHODIMP CDefExt_QueryContextMenu(IContextMenu *pcxm, HMENU hmenu,
UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
CDefExt * this = IToClass(CDefExt, kcxm.unk, pcxm);
HDKA hdka;
TCHAR szMenu[CCH_MENUMAX];
DWORD cb;
UINT cVerbs = 0;
BOOL fFind;
fFind = !SHRestricted(REST_NOFIND);
hdka = DefExt_GetDKA(this, uFlags & CMF_EXPLORE);
if (hdka)
{
UINT idCmd;
for (idCmd = idCmdFirst;
idCmd <= idCmdLast && (idCmd - idCmdFirst) < (UINT)DKA_GetItemCount(hdka);
idCmd++)
{
UINT uMenuFlags = MF_BYPOSITION | MF_STRING;
if (fFind || lstrcmpi(DKA_GetKey(hdka, idCmd-idCmdFirst), c_szFind) != 0)
{
if (_GetMenuStringFromDKA(hdka, idCmd-idCmdFirst, uFlags & CMF_EXTENDEDVERBS, szMenu, ARRAYSIZE(szMenu)))
{
InsertMenu(hmenu, indexMenu, uMenuFlags, idCmd, szMenu);
indexMenu++;
}
}
}
cVerbs = idCmd - idCmdFirst;
if (GetMenuDefaultItem(hmenu, MF_BYPOSITION, 0) == -1)
{
// if there is a default command make it so
if (cVerbs > 0
&& (0 != (cb = SIZEOF(szMenu)))
&& SHRegQueryValue(this->cshx.hkeyProgID, c_szShell, szMenu, &cb) == ERROR_SUCCESS
&& szMenu[0])
{
// we have to make sure the default command actually exists.
HKEY hk;
TCHAR sz[MAX_PATH];
lstrcpy(sz, TEXT("shell\\"));
StrCatBuff(sz, szMenu, SIZECHARS(sz));
if (ERROR_SUCCESS == RegOpenKeyEx(this->cshx.hkeyProgID, sz, 0, KEY_READ, &hk))
{
SetMenuDefaultItem(hmenu, 0, MF_BYPOSITION);
RegCloseKey(hk);
}
}
// if there is no default command yet, and this key has a open
// verb make that the default, if the SHIRT key is down make the
// second verb default not the first.
else if (cVerbs>0 && (0 != (cb=SIZEOF(szMenu))) &&
SHRegQueryValue(this->cshx.hkeyProgID, c_szShellOpenCmd, szMenu, &cb) == ERROR_SUCCESS && szMenu[0])
{
SetMenuDefaultItem(hmenu, 0, MF_BYPOSITION);
}
}
}
// if we added no verbs we dont need the DKA anymore, make sure
// we nuke it in case we get IShellExt::Initialize again.
if (cVerbs == 0)
{
if (this->hdka)
DKA_Destroy(this->hdka);
this->hdka = NULL;
}
return ResultFromShort(cVerbs);
}
// Thought about making this perinstance, decided not to as to allow secondary
// process to abort this out.
STATIC BOOL s_fAbortInvoke = FALSE;
// This private export allows the folder code a way to cause the main invoke
// loops processing several different files to abort.
void WINAPI SHAbortInvokeCommand()
{
DebugMsg(DM_TRACE, TEXT("AbortInvokeCommand was called"));
s_fAbortInvoke = TRUE;
}
// Call shell exec (for the folder class) using the given file and the
// given pidl. The file will be passed as %1 in the dde command and the pidl
// will be passed as %2.
STDAPI InvokeFolderCommandUsingPidl(LPCMINVOKECOMMANDINFOEX pici, LPCTSTR pszPath,
LPCITEMIDLIST pidl, HKEY hkClass, ULONG fExecuteFlags)
{
SHELLEXECUTEINFO ei;
INT iDrive = -1;
if (FAILED(ICIX2SEI(pici, &ei)))
return E_OUTOFMEMORY;
ei.fMask |= SEE_MASK_IDLIST | fExecuteFlags;
ei.lpFile = pszPath;
ei.lpIDList = (LPVOID)pidl;
// if a directory is specifed use that, else make the current
// directory be the folder it self. UNLESS it is a AUDIO CDRom, it
// should never be the current directory (causes CreateProcess errors)
if (!ei.lpDirectory)
ei.lpDirectory = pszPath;
if (pszPath)
iDrive = PathGetDriveNumber(ei.lpDirectory);
if (CMtPt_IsAudioCD(iDrive))
{
ei.lpDirectory = NULL;
}
if (hkClass)
{
ei.hkeyClass = hkClass;
ei.fMask |= SEE_MASK_CLASSKEY;
}
else
{
ei.fMask |= SEE_MASK_CLASSNAME;
ei.lpClass = c_szFolderClass;
}
if (ShellExecuteEx(&ei))
return S_OK;
return HRESULT_FROM_WIN32(GetLastError());
}
STDMETHODIMP CDefExt_InvokeCommand(IContextMenu *pcxm, LPCMINVOKECOMMANDINFO pici)
{
CDefExt * this = IToClass(CDefExt, kcxm.unk, pcxm);
HRESULT hres = E_INVALIDARG;
CMINVOKECOMMANDINFOEX ici;
LPCTSTR pszVerbKey;
LPVOID pvFree;
// thunk the incoming one...
if (FAILED(ICI2ICIX(pici, &ici, &pvFree)))
return E_OUTOFMEMORY;
// Check if ici.lpVerb specifying the verb index (0-based).
if (IS_INTRESOURCE(ici.lpVerb))
{
// Yes, map it to the verb key string.
HDKA hdka = DefExt_GetDKA(this, FALSE);
if (hdka)
{
pszVerbKey = DKA_GetKey(hdka, LOWORD((ULONG_PTR)ici.lpVerb));
}
else
{
// We come here if the registry is broken or missing critical
// information like "*" classes. We assume all the commands are
// open.
pszVerbKey = c_szOpen;
}
// it is unnecessary to thunk, because
// we only use the TCHAR version of the VERB...
if (pszVerbKey)
{
#ifdef UNICODE
ici.lpVerbW = pszVerbKey;
#else
ici.lpVerb = pszVerbKey;
#endif
}
}
else
{
#ifdef UNICODE
pszVerbKey = ici.lpVerbW;
#else
pszVerbKey = ici.lpVerb;
#endif
}
// Check if the pszVerbKey correctly points to the verb string
if (!IS_INTRESOURCE(pszVerbKey) && this->cshx.medium.hGlobal)
{
int iItem, cItems = HIDA_GetCount(this->cshx.medium.hGlobal);
HKEY hkeyFolder = NULL;
LPITEMIDLIST pidl = NULL; // allocated on first use
// Invoke that named command on all the selected objects.
s_fAbortInvoke = FALSE; // reset this global for this run...
for (iItem = 0; iItem < cItems; iItem++)
{
MSG msg;
TCHAR szFilePath[MAX_PATH];
LPITEMIDLIST pidlTemp;
DWORD dwAttrib;
// Try to give the user a way to escape out of this
if (s_fAbortInvoke || GetAsyncKeyState(VK_ESCAPE) < 0)
break;
// And the next big mondo hack to handle CAD of our window
// because the user thinks it is hung.
if (PeekMessage(&msg, NULL, WM_CLOSE, WM_CLOSE, PM_NOREMOVE))
break; // Lets also bail..
pidlTemp = HIDA_FillIDList(this->cshx.medium.hGlobal, iItem, pidl);
if (pidlTemp == NULL)
{
hres = E_OUTOFMEMORY;
break;
}
pidl = pidlTemp;
// Can we get a path from this idlist (ie is it file system stuff)?
dwAttrib = SFGAO_FILESYSTEM | SFGAO_FOLDER;
if (SUCCEEDED(SHGetNameAndFlags(pidl, SHGDN_FORPARSING, szFilePath, ARRAYSIZE(szFilePath), &dwAttrib)) &&
(dwAttrib & SFGAO_FILESYSTEM))
{
// BUGBUG: we know the contents of the pidl so we should not
// have to hit the disk with this call
SHELLEXECUTEINFO ei = {0};
if (!(dwAttrib & SFGAO_FOLDER) && SUCCEEDED(ICIX2SEI(&ici, &ei)))
{
ei.lpFile = szFilePath;
// only use the HKEY for the first file, let ShellExecute
// figure out what to do for all other files, by verb name.
if (iItem == 0)
{
ei.hkeyClass = this->cshx.hkeyProgID;
ei.fMask |= SEE_MASK_CLASSKEY;
}
#ifdef WINNT
// Shrink the shell since the user is about to run an application.
ShrinkWorkingSet();
#endif
// REVIEW: make current dir same as location?
if (ShellExecuteEx(&ei))
{
TCHAR szTemp[CCH_KEYMAX];
LPTSTR pszExt = PathFindExtension(szFilePath);
// now add it to the mru
// the GetClassDescription ensures that this is a registered object
// if it's not, then the OpenWith dialog will deal with adding it to the MRU
// (or not if the user hits cancel)
if (pszExt && !PathIsExe(szFilePath) &&
!PathIsShortcut(szFilePath) &&
GetClassDescription(HKEY_CLASSES_ROOT, pszExt,
szTemp, ARRAYSIZE(szTemp),
GCD_ALLOWPSUDEOCLASSES | GCD_MUSTHAVEOPENCMD))
{
AddToRecentDocs(pidl, szFilePath);
}
#ifdef WINNT
// Shrink the shell since the user just ran an application.
ShrinkWorkingSet();
#endif
hres = S_OK;
}
else
{
// let caller know we failed (we may be calling this
// function from within ShellExecuteEx, and the caller
// of that may care about failure!)
hres = HRESULT_FROM_WIN32(GetLastError());
}
}
else
{
// set this when iItem == 0 so that if we get back
// here for other folders, we'll reuse the key,
// but if the 0th item wasn't a folder,
// don't use it's key and try to open a folder
// with Notepad's shell\open\command or something like that.
if (iItem == 0)
hkeyFolder = this->cshx.hkeyProgID;
// Yes, we have to be careful with folders. We need to
// provide both the path (for folder extensions) and
// the pidl (so cabinet can find it quickly).
hres = InvokeFolderCommandUsingPidl(&ici, szFilePath, pidl, hkeyFolder, 0);
}
}
else
{
// Nope, no alternative but to just use the pidl.
hres = InvokeFolderCommandUsingPidl(&ici, NULL, pidl, this->cshx.hkeyProgID, 0);
}
if (hres == E_OUTOFMEMORY)
{
DebugMsg(TF_ERROR, TEXT("FileDefExt::InvokeCommand - Fail Out of Memory"));
break;
}
}
if (pidl)
ILFree(pidl);
}
if (pvFree)
LocalFree(pvFree);
return hres;
}
UINT_PTR DKA_FindIndex(HDKA hdka, LPCTSTR pszVerb)
{
int i;
for (i = DKA_GetItemCount(hdka) - 1; i >= 0; --i)
{
if (!lstrcmpi(pszVerb, DKA_GetKey(hdka, i)))
return i; // found it!
}
return MAXUINT_PTR;
}
STDAPI_(BOOL) IsFarEastPlatform();
// CDefExt::GetCommandString
STDMETHODIMP CDefExt_GetCommandString(IContextMenu *pcxm,
UINT_PTR idCmd, UINT uType, UINT *pwReserved, LPSTR pszName, UINT cchMax)
{
CDefExt * this = IToClass(CDefExt, kcxm.unk, pcxm);
HRESULT hres = E_OUTOFMEMORY;
// First, create hdka for this object.
HDKA hdka = DefExt_GetDKA(this, FALSE);
if (hdka)
{
if (HIWORD64(idCmd))
{
if (uType & GCS_UNICODE)
{
TCHAR szCmd[MAX_PATH];
if (IsBadStringPtrW((LPCWSTR)idCmd, (UINT)-1))
return E_INVALIDARG;
SHUnicodeToTChar((LPCWSTR)idCmd, szCmd, ARRAYSIZE(szCmd));
idCmd = DKA_FindIndex(hdka, szCmd);
}
else
{
TCHAR szCmd[MAX_PATH];
SHAnsiToTChar((LPCSTR)idCmd, szCmd, ARRAYSIZE(szCmd));
idCmd = DKA_FindIndex(hdka, szCmd);
}
if (idCmd == MAXUINT_PTR)
{
// that failed, try TCHAR version just in case caller messed up
idCmd = DKA_FindIndex(hdka, (LPCTSTR)idCmd);
}
if (idCmd == MAXUINT_PTR)
return E_INVALIDARG;
}
switch (uType)
{
case GCS_HELPTEXTA:
case GCS_HELPTEXTW:
{
TCHAR szMenuString[CCH_MENUMAX];
if (_GetMenuStringFromDKA(hdka, (UINT)idCmd, TRUE, szMenuString, ARRAYSIZE(szMenuString)))
{
LPTSTR pszHelp;
// skip "?)" FE specific mnemonic sequence
// we don't want to do this for non-FE platform
if (IsFarEastPlatform())
{
pszHelp = StrChr(szMenuString, TEXT('(')); // dbcs safe
if (pszHelp && *(pszHelp + 1) == TEXT('&'))
{
LPTSTR pszHelpT = pszHelp+2;
int i;
for(i=0; i<2 && *pszHelpT; i++, pszHelpT=CharNext(pszHelpT))
;
if (*pszHelpT == TEXT(')'))
{
MoveMemory(pszHelp, pszHelpT+1, lstrlen(pszHelpT) * SIZEOF(TCHAR));
}
}
}
// We need to remove first '&' for any platform
pszHelp = StrChr(szMenuString, TEXT('&'));
if (pszHelp)
MoveMemory(pszHelp, pszHelp+1, lstrlen(pszHelp) * SIZEOF(TCHAR));
pszHelp = ShellConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(IDS_VERBHELP), szMenuString);
if (pszHelp)
{
if (uType == GCS_HELPTEXTA)
SHTCharToAnsi(pszHelp, pszName, cchMax);
else
SHTCharToUnicode(pszHelp, (LPWSTR)pszName, cchMax);
LocalFree(pszHelp);
hres = NOERROR;
}
}
}
break;
case GCS_VERBA:
case GCS_VERBW:
{
LPCTSTR pszVerbKey = DKA_GetKey(hdka, (int)idCmd);
if (pszVerbKey)
{
if (uType == GCS_VERBA)
SHTCharToAnsi(pszVerbKey, pszName, cchMax);
else
SHTCharToUnicode(pszVerbKey, (LPWSTR)pszName, cchMax);
hres = NOERROR;
}
}
break;
case GCS_VALIDATEA:
case GCS_VALIDATEW:
hres = idCmd < (UINT)DKA_GetItemCount(hdka) ? NOERROR : S_FALSE;
break;
default:
hres = E_NOTIMPL;
break;
}
}
return hres;
}
IContextMenuVtbl c_CDefExtCXMVtbl =
{
Common_QueryInterface, Common_AddRef, Common_Release,
CDefExt_QueryContextMenu,
CDefExt_InvokeCommand,
CDefExt_GetCommandString
};
// CDefExt constructor
STDMETHODIMP CDefExt_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv, LPFNADDPAGES pfnAddPages)
{
HRESULT hres = E_OUTOFMEMORY;
CDefExt *pshcmd;
if (punkOuter)
{
*ppv = NULL;
return CLASS_E_NOAGGREGATION;
}
pshcmd = (CDefExt *)LocalAlloc(LPTR, SIZEOF(CDefExt));
if (pshcmd)
{
// Initialize CommonUnknown
pshcmd->cunk.unk.lpVtbl = &c_CDefExtVtbl;
pshcmd->cunk.cRef = 1;
// Initialize CCommonShellExtInit
CCommonShellExtInit_Init(&pshcmd->cshx, &pshcmd->cunk);
// Initialize CCommonShellPropSheetExt
CCommonShellPropSheetExt_Init(&pshcmd->cspx, &pshcmd->cunk, pfnAddPages);
// Initialize CKnownContextMenu
pshcmd->kcxm.unk.lpVtbl = &c_CDefExtCXMVtbl;
pshcmd->kcxm.nOffset = (int)((INT_PTR)&pshcmd->kcxm - (INT_PTR)&pshcmd->cunk);
hres = CDefExt_QueryInterface(&pshcmd->cunk.unk, riid, ppv);
CDefExt_Release(&pshcmd->cunk.unk);
}
return hres;
}
HRESULT CALLBACK CShellFileDefExt_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
{
return CDefExt_CreateInstance(punkOuter, riid, ppv, FileSystem_AddPages);
}
HRESULT CALLBACK CShellDrvDefExt_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
{
return CDefExt_CreateInstance(punkOuter, riid, ppv, CDrives_AddPages);
}