|
|
#include "shellprv.h"
#pragma hdrstop
#include <dpa.h>
#include "ids.h"
#include "idlcomm.h"
#include "recdocs.h"
#include "datautil.h"
#include "mtpt.h"
#include <cowsite.h>
typedef struct _DKAITEM { // dkai
HKEY hk; TCHAR sz[CCH_KEYMAX]; } DKAITEM, *PDKAITEM; typedef const DKAITEM * PCDKAITEM;
class CDKA : public CDSA<DKAITEM> { public: ~CDKA(); UINT AddKeys(HKEY hk, LPCTSTR pszSubKey, PCTSTR pszDefaultOrder);
PCTSTR ExposeName(int id) { return GetItemPtr(id)->sz; } HKEY ExposeKey(int id) { return GetItemPtr(id)->hk; } HRESULT GetValue(int id, PCTSTR pszSubKey, PCTSTR pszValue, DWORD *pdwType, void *pvData, DWORD *pcbData);
BOOL DeleteItem(int id); BOOL DeleteAllItems(); void Reset() { if ((HDSA)this) DestroyCallback(_ReleaseItem, NULL); } BOOL HasDefault(HKEY hkProgid);
protected: BOOL _AppendItem(HKEY hk, PDKAITEM pdkai); void _AddOrderedKeys(HKEY hk, PCTSTR pszDefOrder); void _AddEnumKeys(HKEY hk); static int CALLBACK _ReleaseItem(PDKAITEM pdkai, void *pv);
protected: TRIBIT _tbHasDefault; };
BOOL CDKA::_AppendItem(HKEY hk, PDKAITEM pdkai) { BOOL fRet = FALSE; // Verify that the key exists before adding it to the list
if (RegOpenKeyEx(hk, pdkai->sz, 0L, KEY_READ, &pdkai->hk) == ERROR_SUCCESS) { fRet = (AppendItem(pdkai) >= 0);
if (!fRet) RegCloseKey(pdkai->hk); } return fRet; }
void CDKA::_AddOrderedKeys(HKEY hk, PCTSTR pszDefOrder) { // First, add the subkeys from the value of the specified key
// This should never fail, since we just opened this key
DKAITEM dkai; TCHAR szOrder[CCH_KEYMAX * 5]; LONG cbOrder = CbFromCch(ARRAYSIZE(szOrder)); *szOrder = 0; RegQueryValue(hk, NULL, szOrder, &cbOrder); if (*szOrder) { // now we must find something in this string in order to have a default
_tbHasDefault = TRIBIT_FALSE; } else if (pszDefOrder) { // If there is no value, use the order requested
// typically "Open" or "Explore Open" in explorer mode
StrCpyN(szOrder, pszDefOrder, ARRAYSIZE(szOrder)); }
PTSTR psz = szOrder; while (psz && *psz) { // skip the space or comma characters
while(*psz==TEXT(' ') || *psz==TEXT(',')) psz++; // NLS Notes: OK to ++
if (*psz) { // Search for the space or comma character
LPTSTR pszNext = psz + StrCSpn(psz, TEXT(" ,")); if (*pszNext) { *pszNext++=0; // NLS Notes: OK to ++
} StrCpyN(dkai.sz, psz, ARRAYSIZE(dkai.sz));
if (_AppendItem(hk, &dkai)) _tbHasDefault = TRIBIT_TRUE;
psz = pszNext; } } }
void CDKA::_AddEnumKeys(HKEY hk) { DKAITEM dkai; // Then, append the rest if they are not in the list yet.
for (int i = 0; RegEnumKey(hk, i, dkai.sz, ARRAYSIZE(dkai.sz)) == ERROR_SUCCESS; i++) { // Check if the key is already in the list.
for (int idsa = 0; idsa < GetItemCount(); idsa++) { PDKAITEM pdkai = GetItemPtr(idsa); if (lstrcmpi(dkai.sz, pdkai->sz)==0) break; }
// we made it throug our array
// so this isnt in there
if (idsa == GetItemCount()) _AppendItem(hk, &dkai); } }
UINT CDKA::AddKeys(HKEY hkRoot, LPCTSTR pszSubKey, PCTSTR pszDefaultOrder) { UINT cKeys = GetItemCount(); HKEY hk; if (ERROR_SUCCESS == RegOpenKeyEx(hkRoot, pszSubKey, 0L, KEY_READ, &hk)) { _AddOrderedKeys(hk, pszDefaultOrder); _AddEnumKeys(hk); RegCloseKey(hk); } return GetItemCount() - cKeys; }
int CALLBACK CDKA::_ReleaseItem(PDKAITEM pdkai, void *pv) { if (pdkai->hk) { RegCloseKey(pdkai->hk); pdkai->hk = NULL; } return 1; }
CDKA::~CDKA() { Reset(); }
// override this DSA methods to get release
BOOL CDKA::DeleteItem(int id) { PDKAITEM p = GetItemPtr(id); if (p) { _ReleaseItem(p, NULL); return CDSA<DKAITEM>::DeleteItem(id); } return FALSE; }
// override this DSA methods to get release
BOOL CDKA::DeleteAllItems() { EnumCallback(_ReleaseItem, NULL); return CDSA<DKAITEM>::DeleteAllItems(); }
HRESULT CDKA::GetValue(int id, PCTSTR pszSubKey, PCTSTR pszValue, DWORD *pdwType, void *pvData, DWORD *pcbData) { DWORD err = SHGetValue(GetItemPtr(id)->hk, pszSubKey, pszValue, pdwType, pvData, pcbData); return HRESULT_FROM_WIN32(err); }
BOOL CDKA::HasDefault(HKEY hkProgid) { if (_tbHasDefault == TRIBIT_UNDEFINED) { HKEY hk; if (ERROR_SUCCESS== RegOpenKeyEx(hkProgid, L"ShellFolder", 0, MAXIMUM_ALLOWED, &hk)) { // APPCOMPAT - regitems need to have the open verb - ZekeL - 30-JAN-2001
// so that the IQA and ICM will behave the same,
// and regitem folders will always default to
// folder\shell\open unless they implement open
// or specify default verbs.
//
_tbHasDefault = TRIBIT_FALSE; RegCloseKey(hk); } else { _tbHasDefault = TRIBIT_TRUE; } } return _tbHasDefault == TRIBIT_TRUE; }
typedef HRESULT (__stdcall *LPFNADDPAGES)(IDataObject *, LPFNADDPROPSHEETPAGE, LPARAM);
class CShellExecMenu : public IShellExtInit, public IContextMenu, public IShellPropSheetExt, CObjectWithSite { public: // IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void);
// IShellExtInit
STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID);
// IContextMenu
STDMETHODIMP QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags); STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pici); STDMETHODIMP GetCommandString(UINT_PTR idCmd, UINT wFlags, UINT *pwRes, LPSTR pszName, UINT cchMax);
// IShellPropSheetExt
STDMETHODIMP AddPages(LPFNADDPROPSHEETPAGE, LPARAM); STDMETHODIMP ReplacePage(UINT, LPFNADDPROPSHEETPAGE, LPARAM);
CShellExecMenu(LPFNADDPAGES pfnAddPages);
protected: // methods
~CShellExecMenu(); void _Cleanup(); HRESULT _InsureVerbs(UINT idVerb = 0); UINT _VerbCount(); LPCTSTR _GetVerb(UINT id); UINT _FindIndex(LPCTSTR pszVerb); DWORD _BrowseFlagsFromVerb(UINT idVerb); BOOL _GetMenuString(UINT id, BOOL fExtended, LPTSTR pszMenu, UINT cchMax); BOOL _IsExplorerMode(); BOOL _SupportsType(UINT idVerb); BOOL _IsRestricted(UINT idVerb); BOOL _IsVisible(BOOL fExtended, UINT idVerb); BOOL _RemoveVerb(UINT idVerb); BOOL _VerbCanDrop(UINT idVerb, CLSID *pclsid); HRESULT _DoDrop(REFCLSID clsid, UINT idVerb, LPCMINVOKECOMMANDINFOEX pici);
HRESULT _MapVerbForInvoke(CMINVOKECOMMANDINFOEX *pici, UINT *pidVerb); HRESULT _TryBrowseObject(LPCITEMIDLIST pidl, DWORD uFlags); void _DoRecentStuff(LPCITEMIDLIST pidl, LPCTSTR pszPath); HRESULT _InvokeOne(CMINVOKECOMMANDINFOEX *pici, UINT idVerb, LPCITEMIDLIST pidl); HRESULT _InvokeMany(CMINVOKECOMMANDINFOEX *pici, UINT idVerb, LPIDA pida); HRESULT _InvokeEach(LPCITEMIDLIST pidl, CMINVOKECOMMANDINFOEX *pici); HRESULT _PromptUser(CMINVOKECOMMANDINFOEX *pici, UINT idVerb, LPIDA pida);
HRESULT _MapVerbForGCS(UINT_PTR idCmd, UINT uType, UINT *pidVerb); HRESULT _GetHelpText(UINT idVerb, UINT uType, LPSTR pszName, UINT cchMax);
private: // members
LONG _cRef; IDataObject *_pdtobj; HKEY _hkeyProgID; CDKA _dka; LPFNADDPAGES _pfnAddPages; UINT _uFlags; };
CShellExecMenu::CShellExecMenu(LPFNADDPAGES pfnAddPages) : _pfnAddPages(pfnAddPages), _cRef(1) { }
CShellExecMenu::~CShellExecMenu() { _Cleanup(); }
void CShellExecMenu::_Cleanup() { _dka.Reset(); if (_hkeyProgID) { RegCloseKey(_hkeyProgID); _hkeyProgID = NULL; }
ATOMICRELEASE(_pdtobj); }
STDMETHODIMP CShellExecMenu::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CShellExecMenu, IShellExtInit), QITABENT(CShellExecMenu, IContextMenu), QITABENT(CShellExecMenu, IShellPropSheetExt), QITABENT(CShellExecMenu, IObjectWithSite), { 0 }, }; return QISearch(this, qit, riid, ppvObj); }
STDMETHODIMP_(ULONG) CShellExecMenu::AddRef() { return InterlockedIncrement(&_cRef); }
STDMETHODIMP_(ULONG) CShellExecMenu::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; }
HRESULT CShellExecMenu::_InsureVerbs(UINT idVerb) { // the idVerb is the minimum verb that we need to succeed
if (!(HDSA)_dka && _hkeyProgID) { // create either "open" or "explore open"
if (_dka.Create(4)) { _dka.AddKeys(_hkeyProgID, c_szShell, _IsExplorerMode() ? TEXT("Explore open") : TEXT("open"));
// WARNING - some verbs are not valid and need to be removed
for (int id = 0; id < _dka.GetItemCount(); id++) { if (_RemoveVerb(id)) _dka.DeleteItem(id); } }
} return ((HDSA)_dka && idVerb < (UINT)_dka.GetItemCount()) ? S_OK : E_FAIL; }
// 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.
BOOL _MenuString(LPCTSTR pszVerbKey, LPTSTR pszMenuString, 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, TEXT("edit"),IDS_MENUEDIT, c_szFind, IDS_MENUFIND, c_szPrint, IDS_MENUPRINT, c_szOpenAs, IDS_MENUOPEN, TEXT("runas"),IDS_MENURUNAS };
for (int 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; } }
// Worst case: Just put '&' on the top.
pszMenuString[0] = TEXT('&'); pszMenuString++; cchMax--; lstrcpyn(pszMenuString, pszVerbKey, cchMax);
return TRUE; }
// Checks to see if there is a user policy in place that disables this key,
//
// For example, in the registry:
//
// CLSID_MyComputer
// +---Shell
// +---Manage
// (Default) = "Mana&ge"
// SuppressionPolicy = REST_NOMANAGEMYCOMPUTERVERB
//
// (Where REST_NOMANAGEMYCOMPUTERVERB is the DWORD value of that particular policy)
BOOL CShellExecMenu::_IsRestricted(UINT idVerb) { RESTRICTIONS rest; BOOL fRestrict = FALSE; if (0 == lstrcmpi(TEXT("runas"), _dka.ExposeName(idVerb))) { rest = REST_HIDERUNASVERB; fRestrict = TRUE; } else { DWORD cb = sizeof(rest); fRestrict = SUCCEEDED(_dka.GetValue(idVerb, NULL, TEXT("SuppressionPolicy"), NULL, &rest, &cb)); } return fRestrict && SHRestricted(rest); }
HRESULT _GetAppSource(HKEY hk, PCWSTR pszVerb, IQuerySource **ppqs) { CComPtr<IAssociationElement> spae; HRESULT hr = AssocElemCreateForKey(&CLSID_AssocShellElement, hk, &spae); if (SUCCEEDED(hr)) { CComPtr<IObjectWithQuerySource> spowqsApp; hr = spae->QueryObject(AQVO_APPLICATION_DELEGATE, pszVerb, IID_PPV_ARG(IObjectWithQuerySource, &spowqsApp)); if (SUCCEEDED(hr)) { hr = spowqsApp->GetSource(IID_PPV_ARG(IQuerySource, ppqs)); } } return hr; }
BOOL CShellExecMenu::_SupportsType(UINT idVerb) { BOOL fRet = TRUE; if (SUCCEEDED(_dka.GetValue(idVerb, NULL, TEXT("CheckSupportedTypes"), NULL, NULL, NULL))) { // need to check the supported types for this application
// get the first item and then check it against SupportedFileExtensions
CComPtr<IShellItem> spsi; if (SUCCEEDED(DataObj_GetIShellItem(_pdtobj, &spsi))) { SFGAOF sfgao; if (S_OK == spsi->GetAttributes(SFGAO_STREAM, &sfgao)) { CSmartCoTaskMem<OLECHAR> spszName; if (SUCCEEDED(spsi->GetDisplayName(SIGDN_PARENTRELATIVEPARSING, &spszName))) { PWSTR pszExt = PathFindExtension(spszName); if (*pszExt) { CComPtr<IQuerySource> spqs; if (SUCCEEDED(_GetAppSource(_hkeyProgID, _dka.ExposeName(idVerb), &spqs))) { fRet = SUCCEEDED(spqs->QueryValueExists(L"SupportedTypes", pszExt)); } } } } } } return fRet; } //
// LegacyDisable
// LegacyDisable is set, then the verb exists only for legacy reasons, and
// is actually superceded by a context menu extension or some other behavior
// it there only to retain legacy behavior for external clients that require
// the existence of a verb.
//
BOOL CShellExecMenu::_RemoveVerb(UINT idVerb) { if (SUCCEEDED(_dka.GetValue(idVerb, NULL, TEXT("LegacyDisable"), NULL, NULL, NULL))) return TRUE;
if (!_SupportsType(idVerb)) return TRUE; return (_IsRestricted(idVerb)); }
BOOL CShellExecMenu::_IsVisible(BOOL fExtended, UINT idVerb) { // this is not an extended verb, or
// the request includes extended verbs
if (!fExtended && SUCCEEDED(_dka.GetValue(idVerb, NULL, TEXT("Extended"), NULL, NULL, NULL))) return FALSE;
static const struct { LPCTSTR pszVerb; } sVerbIgnore[] = { c_szPrintTo };
for (int i = 0; i < ARRAYSIZE(sVerbIgnore); i++) { if (lstrcmpi(_dka.ExposeName(idVerb), sVerbIgnore[i].pszVerb) == 0) { return FALSE; } }
return TRUE; }
BOOL CShellExecMenu::_GetMenuString(UINT id, BOOL fExtended, LPTSTR pszMenu, UINT cchMax) { BOOL bRet = FALSE; // other verbs are hidden and just shouldnt be shown.
if (SUCCEEDED(_InsureVerbs(id)) && _IsVisible(fExtended, id)) { DWORD cbVerb = CbFromCch(cchMax); *pszMenu = 0; // try the MUIVerb value first
// if that fails use the default value
// either of these can actually have an MUI string
if (FAILED(_dka.GetValue(id, NULL, TEXT("MUIVerb"), NULL, pszMenu, &cbVerb))) { cbVerb = CbFromCch(cchMax); _dka.GetValue(id, NULL, NULL, NULL, pszMenu, &cbVerb); }
if (!*pszMenu || FAILED(SHLoadIndirectString(pszMenu, pszMenu, cchMax, NULL))) { // If it does not have the value, generate it.
bRet = _MenuString(_dka.ExposeName(id), pszMenu, cchMax); } else { // use the value
bRet = TRUE; } ASSERT(!bRet || *pszMenu); } return bRet; }
STDMETHODIMP CShellExecMenu::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID) { // new behavior: good context menus should interpret a NULL pidlFolder/hkeyProgID on a re-init
// as meaning they should use the ones they already have.
if (hkeyProgID) { _Cleanup(); // cleans up hkey and hdka, pdtobj too but that's ok
_hkeyProgID = SHRegDuplicateHKey(hkeyProgID); // make a copy
} IUnknown_Set((IUnknown **)&_pdtobj, pdtobj); return S_OK; }
UINT CShellExecMenu::_VerbCount() { return SUCCEEDED(_InsureVerbs()) ? _dka.GetItemCount() : 0; }
UINT CShellExecMenu::_FindIndex(LPCTSTR pszVerb) { for (UINT i = 0; i < _VerbCount(); i++) { if (!lstrcmpi(pszVerb, _dka.ExposeName(i))) return i; // found it!
} return -1; }
STDMETHODIMP CShellExecMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { UINT cVerbs = 0; _uFlags = uFlags; // caller may force explorer mode (CMF_EXPLORE) here
TCHAR szMenu[CCH_MENUMAX]; for (UINT idCmd = idCmdFirst; idCmd <= idCmdLast && (idCmd - idCmdFirst) < _VerbCount(); idCmd++) { UINT uMenuFlags = MF_BYPOSITION | MF_STRING; if (_GetMenuString(idCmd - idCmdFirst, uFlags & CMF_EXTENDEDVERBS, szMenu, ARRAYSIZE(szMenu))) { InsertMenu(hmenu, indexMenu + cVerbs, uMenuFlags, idCmd, szMenu); cVerbs++; } }
if (cVerbs && (GetMenuDefaultItem(hmenu, MF_BYPOSITION, 0) == -1)) { if (_dka.HasDefault(_hkeyProgID)) { // if there is a default verb on this key,
// trust that it was the first one that the CDKA added
SetMenuDefaultItem(hmenu, indexMenu, MF_BYPOSITION); } }
return ResultFromShort(_VerbCount()); }
LPCTSTR CShellExecMenu::_GetVerb(UINT id) { return SUCCEEDED(_InsureVerbs()) ? _dka.ExposeName(id) : NULL; }
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.
STDAPI_(void) 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 _InvokePidl(LPCMINVOKECOMMANDINFOEX pici, DWORD dwAttribs, LPCTSTR pszPath, LPCITEMIDLIST pidl, HKEY hkClass) { SHELLEXECUTEINFO ei; HRESULT hr = ICIX2SEI(pici, &ei); pszPath = (dwAttribs & SFGAO_FILESYSTEM) ? pszPath : NULL; if (SUCCEEDED(hr)) { ei.fMask |= SEE_MASK_IDLIST;
ei.lpFile = pszPath; ei.lpIDList = (void *)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 && (dwAttribs & SFGAO_FOLDER)) ei.lpDirectory = pszPath;
if (pszPath && ei.lpDirectory) { INT iDrive = PathGetDriveNumber(ei.lpDirectory);
CMountPoint* pmtpt = CMountPoint::GetMountPoint(iDrive);
if (pmtpt) { if (pmtpt->IsAudioCDNoData()) { ei.lpDirectory = NULL; }
pmtpt->Release(); } }
if (hkClass) { ei.hkeyClass = hkClass; ei.fMask |= SEE_MASK_CLASSKEY; } else ei.fMask |= SEE_MASK_INVOKEIDLIST; if (ShellExecuteEx(&ei)) hr = S_OK; else hr = HRESULT_FROM_WIN32(GetLastError()); } return hr; }
BOOL _QuitInvokeLoop() { MSG msg;
// Try to give the user a way to escape out of this
if (s_fAbortInvoke || GetAsyncKeyState(VK_ESCAPE) < 0) return TRUE;
// 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)) return TRUE; // Lets also bail..
return FALSE; }
#define CMINVOKE_VERBT(pici) (pici)->lpVerbW
HRESULT CShellExecMenu::_MapVerbForInvoke(CMINVOKECOMMANDINFOEX *pici, UINT *pidVerb) { LPCTSTR pszVerbKey; // is pici->lpVerb specifying the verb index (0-based).
if (IS_INTRESOURCE(pici->lpVerb)) { // find it in the CDKA
*pidVerb = LOWORD((ULONG_PTR)pici->lpVerb); pszVerbKey = _GetVerb(*pidVerb); CMINVOKE_VERBT(pici) = pszVerbKey; // alias into the CDKA
RIPMSG(pszVerbKey != NULL, "CShellExecMenu::InvokeCommand() passed an invalid verb id"); } else { pszVerbKey = CMINVOKE_VERBT(pici); if (pszVerbKey) { *pidVerb = _FindIndex(pszVerbKey); if (-1 == *pidVerb) pszVerbKey = NULL; // not in our list
} }
ASSERT(!pszVerbKey || *pidVerb != -1); return pszVerbKey ? S_OK : E_INVALIDARG; }
BOOL CShellExecMenu::_IsExplorerMode() { BOOL bRet = (_uFlags & CMF_EXPLORE); if (!bRet) { bRet = IsExplorerModeBrowser(_punkSite); if (bRet) _uFlags |= CMF_EXPLORE; } return bRet; }
DWORD CShellExecMenu::_BrowseFlagsFromVerb(UINT idVerb) { DWORD dwFlags = 0; DWORD cbFlags = sizeof(dwFlags); _dka.GetValue(idVerb, NULL, _IsExplorerMode() ? TEXT("ExplorerFlags") : TEXT("BrowserFlags"), NULL, &dwFlags, &cbFlags); return dwFlags; }
HRESULT CShellExecMenu::_TryBrowseObject(LPCITEMIDLIST pidl, DWORD uFlags) { HRESULT hr = S_FALSE;
IShellBrowser *psb; if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_SShellBrowser, IID_PPV_ARG(IShellBrowser, &psb)))) { hr = psb->BrowseObject(pidl, (UINT) uFlags); psb->Release(); } return hr; }
HRESULT _CanTryBrowseObject(DWORD dwAttribs, CMINVOKECOMMANDINFOEX* pici) { HRESULT hr = S_FALSE;
if (dwAttribs & SFGAO_FOLDER) { // we need to sniff the iciex here to see if there is anything special in it
// that cannot be conveyed to IShellBrowser::BrowseObject() (eg the nShow parameter)
if ((pici->nShow == SW_SHOWNORMAL) || (pici->nShow == SW_SHOW)) { // nothing special in the ICIEX, should be safe to discard it and use
// IShellBrowser::BrowseObject() instead of ShellExecuteEx
hr = S_OK; } }
return hr; }
BOOL CShellExecMenu::_VerbCanDrop(UINT idVerb, CLSID *pclsid) { TCHAR sz[GUIDSTR_MAX]; DWORD cb = sizeof(sz); return (SUCCEEDED(_dka.GetValue(idVerb, L"DropTarget", L"Clsid", NULL, sz, &cb)) && GUIDFromString(sz, pclsid)); }
HRESULT CShellExecMenu::_DoDrop(REFCLSID clsid, UINT idVerb, LPCMINVOKECOMMANDINFOEX pici) { // i think i need to persist the pici into the _pdtobj
// and probably add some values under the pqs
// we assume that the app will do something appropriate
// QueryService(_punkSite, clsid) might be useful
return SHSimulateDropOnClsid(clsid, _punkSite, _pdtobj); }
STDMETHODIMP CShellExecMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) { CMINVOKECOMMANDINFOEX ici; void *pvFree; HRESULT hr = ICI2ICIX(pici, &ici, &pvFree); // thunk incomming params
if (SUCCEEDED(hr)) { UINT idVerb; hr = _MapVerbForInvoke(&ici, &idVerb); if (SUCCEEDED(hr)) { CLSID clsid; if (_VerbCanDrop(idVerb, &clsid)) { hr = _DoDrop(clsid, idVerb, &ici); } else { STGMEDIUM medium; LPIDA pida = DataObj_GetHIDA(_pdtobj, &medium); if (pida) { if (pida->cidl == 1) { LPITEMIDLIST pidl = IDA_FullIDList(pida, 0); if (pidl) { hr = _InvokeOne(&ici, idVerb, pidl); ILFree(pidl); } else hr = E_OUTOFMEMORY; } else { hr = _InvokeMany(&ici, idVerb, pida); }
HIDA_ReleaseStgMedium(pida, &medium); } else hr = E_OUTOFMEMORY; } }
if (pvFree) LocalFree(pvFree); }
return hr; }
HRESULT CShellExecMenu::_InvokeOne(CMINVOKECOMMANDINFOEX *pici, UINT idVerb, LPCITEMIDLIST pidl) { HRESULT hr; TCHAR szPath[MAX_PATH]; DWORD dwAttrib = SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_LINK;
hr = SHGetNameAndFlags(pidl, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), &dwAttrib); if (SUCCEEDED(hr)) { hr = S_FALSE;
if (S_OK == _CanTryBrowseObject(dwAttrib, pici)) { DWORD uFlags = _BrowseFlagsFromVerb(idVerb); if (uFlags) { // if we did the site based navigation, we are done
hr = _TryBrowseObject(pidl, uFlags); } }
if (hr != S_OK) { hr = _InvokePidl(pici, dwAttrib, szPath, pidl, _hkeyProgID);
// only set recent on non-folders (SFGAO_STREAM?)
// and non-link since we know those should never be added
if (SUCCEEDED(hr) && !(dwAttrib & (SFGAO_FOLDER | SFGAO_LINK))) { AddToRecentDocs(pidl, szPath); } } }
return hr; }
BOOL _ShouldPrompt(DWORD cItems) { DWORD dwMin, cb = sizeof(dwMin); if (SHRegGetUSValue(REGSTR_PATH_EXPLORER, TEXT("MultipleInvokePromptMinimum"), NULL, &dwMin, &cb, FALSE, NULL, 0) != ERROR_SUCCESS) dwMin = 15;
return cItems > dwMin; }
HRESULT CShellExecMenu::_PromptUser(CMINVOKECOMMANDINFOEX *pici, UINT idVerb, LPIDA pida) { HRESULT hr = S_FALSE; if (pici->hwnd && !(pici->fMask & CMIC_MASK_FLAG_NO_UI) && _ShouldPrompt(pida->cidl)) { // prompt the user with the verb and count
// we make a better experience if we keyed off
// homo/hetero types and had different behaviors
// but its not worth it. instead we should
// switch to using AutoPlay sniffing and dialog.
TCHAR szVerb[64]; TCHAR szNum[10]; wnsprintf(szNum, ARRAYSIZE(szNum), TEXT("%d"), pida->cidl); hr = _GetHelpText(idVerb, GCS_HELPTEXT, (PSTR)szVerb, ARRAYSIZE(szVerb)); if (SUCCEEDED(hr)) { hr = E_OUTOFMEMORY; PTSTR pszTitle = ShellConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(IDS_MULTIINVOKEPROMPT_TITLE), szVerb); if (pszTitle) { PTSTR pszMsg = ShellConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(IDS_MULTIINVOKEPROMPT_MESSAGE), szVerb, szNum); if (pszMsg) { int iRet = SHMessageBoxCheck(pici->hwnd, pszMsg, pszTitle, (MB_OKCANCEL | MB_ICONEXCLAMATION), IDOK, TEXT("MultipleInvokePrompt")); hr = iRet == IDOK ? S_OK : HRESULT_FROM_WIN32(ERROR_CANCELLED);
LocalFree(pszMsg); } LocalFree(pszTitle); } } } return hr; }
HRESULT CShellExecMenu::_InvokeEach(LPCITEMIDLIST pidl, CMINVOKECOMMANDINFOEX *pici) { HRESULT hr = E_OUTOFMEMORY; HMENU hmenu = CreatePopupMenu(); if (hmenu) { CComPtr<IContextMenu> spcm; hr = SHGetUIObjectOf(pidl, NULL, IID_PPV_ARG(IContextMenu, &spcm)); if (SUCCEEDED(hr)) { if (_punkSite) IUnknown_SetSite(spcm, _punkSite);
hr = spcm->QueryContextMenu(hmenu, 0, CONTEXTMENU_IDCMD_FIRST, CONTEXTMENU_IDCMD_LAST, _uFlags); if (SUCCEEDED(hr)) { hr = spcm->InvokeCommand((CMINVOKECOMMANDINFO *)pici); }
if (_punkSite) IUnknown_SetSite(spcm, NULL); }
DestroyMenu(hmenu); } return hr; }
HRESULT CShellExecMenu::_InvokeMany(CMINVOKECOMMANDINFOEX *pici, UINT idVerb, LPIDA pida) { HRESULT hr = _PromptUser(pici, idVerb, pida); if (SUCCEEDED(hr)) { USES_CONVERSION; s_fAbortInvoke = FALSE; // reset this global for this run...
// we want to alter the pici
// so that each item is handled individually
pici->hwnd = NULL; pici->fMask |= CMIC_MASK_FLAG_NO_UI;
// NTBUG #502223 - MSI apps with DDE start multiple copies - ZekeL 2001-DEC-07
// ShellExec() will create a new thread for MSI apps to
// avoid a deadlock with the MSI APIs calling SHChangeNotify().
// this is described in NTBUG #200961
// however in the multiple invoke case we create one thread
// for each item in the invoke, which results in several processes
// contending for the DDE conversation.
//
// this is a half fix. we prefer to have the buggy behavior in 502223
// over the deadlock behavior in 200961 (a definite PSS call).
// since the deadlock case should only occur for the desktop,
// the rest of the time we will force a synchronous invoke.
IBindCtx *pbcRelease = NULL; if (!IsDesktopBrowser(_punkSite)) { TBCRegisterObjectParam(TBCDIDASYNC, SAFECAST(this, IContextMenu *), &pbcRelease); }
pici->lpVerb = T2A(_dka.ExposeName(idVerb)); pici->lpVerbW = _dka.ExposeName(idVerb); for (UINT iItem = 0; !_QuitInvokeLoop() && (iItem < pida->cidl); iItem++) { LPITEMIDLIST pidl = IDA_FullIDList(pida, iItem); if (pidl) { hr = _InvokeEach(pidl, pici); ILFree(pidl); } else hr = E_OUTOFMEMORY;
if (hr == E_OUTOFMEMORY) break;
}
ATOMICRELEASE(pbcRelease); } return hr; }
HRESULT CShellExecMenu::_GetHelpText(UINT idVerb, UINT uType, LPSTR pszName, UINT cchMax) { // TODO - shouldnt we let the registry override?
HRESULT hr = E_OUTOFMEMORY; TCHAR szMenuString[CCH_MENUMAX]; if (_GetMenuString(idVerb, TRUE, szMenuString, ARRAYSIZE(szMenuString))) { SHStripMneumonic(szMenuString); // NOTE on US, IDS_VERBHELP is the same as "%s"
// do we want some better description?
LPTSTR 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); hr = S_OK; } } return hr; }
HRESULT CShellExecMenu::_MapVerbForGCS(UINT_PTR idCmd, UINT uType, UINT *pidVerb) { HRESULT hr = _InsureVerbs(); if (SUCCEEDED(hr)) { if (IS_INTRESOURCE(idCmd)) *pidVerb = (UINT)idCmd; else { *pidVerb = -1; if (!(uType & GCS_UNICODE)) { USES_CONVERSION; *pidVerb = _FindIndex(A2W((LPCSTR)idCmd)); }
// we fall back to the TCHAR version regardless
// of what the caller passed in uType
if (*pidVerb == -1) { if (!IsBadStringPtrW((LPCWSTR)idCmd, (UINT)-1)) *pidVerb = _FindIndex((LPCWSTR)idCmd); } } hr = *pidVerb < _VerbCount() ? S_OK : E_INVALIDARG; }
// VALIDATE returns S_FALSE for bad verbs
if (FAILED(hr) && (uType == GCS_VALIDATEA || uType == GCS_VALIDATEW)) hr = S_FALSE; return hr; } STDMETHODIMP CShellExecMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwRes, LPSTR pszName, UINT cchMax) { UINT idVerb; HRESULT hr = _MapVerbForGCS(idCmd, uType, &idVerb); if (SUCCEEDED(hr)) { // the verb is good!
switch (uType) { case GCS_HELPTEXTA: case GCS_HELPTEXTW: hr = _GetHelpText(idVerb, uType, pszName, cchMax); break; case GCS_VERBA: case GCS_VERBW: { if (uType == GCS_VERBA) SHTCharToAnsi(_dka.ExposeName(idVerb), pszName, cchMax); else SHTCharToUnicode(_dka.ExposeName(idVerb), (LPWSTR)pszName, cchMax); hr = S_OK; } break;
case GCS_VALIDATEA: case GCS_VALIDATEW: // the hr from MapVerb is good enough
break; default: hr = E_NOTIMPL; break; } } return hr; }
STDMETHODIMP CShellExecMenu::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) { return _pfnAddPages(_pdtobj, pfnAddPage, lParam); }
STDMETHODIMP CShellExecMenu::ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplaceWith, LPARAM lParam) { return E_NOTIMPL; }
STDAPI CShellExecMenu_CreateInstance(LPFNADDPAGES pfnAddPages, REFIID riid, void **ppv) { HRESULT hr; CShellExecMenu *pdext = new CShellExecMenu(pfnAddPages); if (pdext) { hr = pdext->QueryInterface(riid, ppv); pdext->Release(); } else { *ppv = NULL; hr = E_OUTOFMEMORY; } return hr; }
// these handlers slime off of CShellExecMenu's IShellPropSheetExt implementation
STDAPI FileSystem_AddPages(IDataObject *pdtobj, LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam);
STDAPI CShellFileDefExt_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { return CShellExecMenu_CreateInstance(FileSystem_AddPages, riid, ppv); }
STDAPI CDrives_AddPages(IDataObject *pdtobj, LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam);
STDAPI CShellDrvDefExt_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { return CShellExecMenu_CreateInstance(CDrives_AddPages, riid, ppv); }
#ifdef _X86_
STDAPI PIF_AddPages(IDataObject *pdtobj, LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam);
STDAPI CProxyPage_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { return CShellExecMenu_CreateInstance(PIF_AddPages, riid, ppv); }
#endif
|