#include "stdafx.h" #pragma hdrstop #include // MSAAMENUINFO stuff #include #include "datautil.h" #include "idlcomm.h" #include "stgutil.h" #include #include "filetbl.h" #include "cdburn.h" #include "mtpt.h" #ifndef CMF_DVFILE #define CMF_DVFILE 0x00010000 // "File" pulldown #endif class CSendToMenu : public IContextMenu3, IShellExtInit, IOleWindow { public: // IUnknown STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj); STDMETHOD_(ULONG,AddRef)(void); STDMETHOD_(ULONG,Release)(void); // IContextMenu STDMETHOD(QueryContextMenu)(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags); STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpici); STDMETHOD(GetCommandString)(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax); // IContextMenu2 STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam); // IContextMenu3 STDMETHOD(HandleMenuMsg2)(UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT *lResult); // IShellExtInit STDMETHOD(Initialize)(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID); // IOleWindow STDMETHOD(GetWindow)(HWND *phwnd); STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode) {return E_NOTIMPL;}; private: CSendToMenu(); ~CSendToMenu(); LONG _cRef; HMENU _hmenu; UINT _idCmdFirst; BOOL _bFirstTime; HWND _hwnd; IDataObject *_pdtobj; LPITEMIDLIST _pidlLast; DWORD _GetKeyState(void); HRESULT _DoDragDrop(HWND hwndParent, IDropTarget *pdrop); BOOL _CanDrop(IShellFolder *psf, LPCITEMIDLIST pidl); HRESULT _MenuCallback(UINT fmm, IShellFolder *psf, LPCITEMIDLIST pidl); HRESULT _RemovableDrivesMenuCallback(UINT fmm, IShellFolder *psf, LPCITEMIDLIST pidl); static HRESULT CALLBACK s_MenuCallback(UINT fmm, LPARAM lParam, IShellFolder *psf, LPCITEMIDLIST pidl); static HRESULT CALLBACK s_RemovableDrivesMenuCallback(UINT fmm, LPARAM lParam, IShellFolder *psf, LPCITEMIDLIST pidl); friend HRESULT CSendToMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut); }; CSendToMenu::CSendToMenu() : _cRef(1) { DllAddRef(); } CSendToMenu::~CSendToMenu() { if (_hmenu) FileMenu_DeleteAllItems(_hmenu); if (_pdtobj) _pdtobj->Release(); ILFree(_pidlLast); DllRelease(); } HRESULT CSendToMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut) { HRESULT hr = E_OUTOFMEMORY; CSendToMenu *pstm = new CSendToMenu(); if (pstm) { hr = pstm->QueryInterface(riid, ppvOut); pstm->Release(); } return hr; } HRESULT CSendToMenu::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CSendToMenu, IShellExtInit), // IID_IShellExtInit QITABENT(CSendToMenu, IOleWindow), // IID_IOleWindow QITABENT(CSendToMenu, IContextMenu3), // IID_IContextMenu3 QITABENTMULTI(CSendToMenu, IContextMenu2, IContextMenu3), // IID_IContextMenu2 QITABENTMULTI(CSendToMenu, IContextMenu, IContextMenu3), // IID_IContextMenu { 0 } }; return QISearch(this, qit, riid, ppvObj); } ULONG CSendToMenu::AddRef() { return InterlockedIncrement(&_cRef); } ULONG CSendToMenu::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } HRESULT CSendToMenu::GetWindow(HWND *phwnd) { HRESULT hr = E_INVALIDARG; if (phwnd) { *phwnd = _hwnd; hr = S_OK; } return hr; } HRESULT CSendToMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { // if they want the default menu only (CMF_DEFAULTONLY) OR // this is being called for a shortcut (CMF_VERBSONLY) // we don't want to be on the context menu if (uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY)) return S_OK; UINT idMax = idCmdFirst; _hmenu = CreatePopupMenu(); if (_hmenu) { TCHAR szSendLinkTo[80]; TCHAR szSendPageTo[80]; MENUITEMINFO mii; // add a dummy item so we are identified at WM_INITMENUPOPUP time LoadString(g_hinst, IDS_SENDLINKTO, szSendLinkTo, ARRAYSIZE(szSendLinkTo)); LoadString(g_hinst, IDS_SENDPAGETO, szSendPageTo, ARRAYSIZE(szSendPageTo)); mii.cbSize = sizeof(MENUITEMINFO); mii.fMask = MIIM_ID | MIIM_TYPE; mii.fType = MFT_STRING; mii.dwTypeData = szSendLinkTo; mii.wID = idCmdFirst + 1; if (InsertMenuItem(_hmenu, 0, TRUE, &mii)) { _idCmdFirst = idCmdFirst + 1; // remember this for later mii.fType = MFT_STRING; mii.dwTypeData = szSendLinkTo; mii.wID = idCmdFirst; mii.fState = MF_DISABLED | MF_GRAYED; mii.fMask = MIIM_TYPE | MIIM_SUBMENU | MIIM_ID; mii.hSubMenu = _hmenu; if (InsertMenuItem(hmenu, indexMenu, TRUE, &mii)) { idMax += 0x40; // reserve space for this many items _bFirstTime = TRUE; // fill this at WM_INITMENUPOPUP time } else { _hmenu = NULL; } } } _hmenu = NULL; return ResultFromShort(idMax - idCmdFirst); } DWORD CSendToMenu::_GetKeyState(void) { DWORD grfKeyState = MK_LBUTTON; // default if (GetAsyncKeyState(VK_CONTROL) < 0) grfKeyState |= MK_CONTROL; if (GetAsyncKeyState(VK_SHIFT) < 0) grfKeyState |= MK_SHIFT; if (GetAsyncKeyState(VK_MENU) < 0) grfKeyState |= MK_ALT; // menu's don't really allow this return grfKeyState; } HRESULT CSendToMenu::_DoDragDrop(HWND hwndParent, IDropTarget *pdrop) { DWORD grfKeyState = _GetKeyState(); if (grfKeyState == MK_LBUTTON) { // no modifieres, change default to COPY grfKeyState = MK_LBUTTON | MK_CONTROL; DataObj_SetDWORD(_pdtobj, g_cfPreferredDropEffect, DROPEFFECT_COPY); } _hwnd = hwndParent; IUnknown_SetSite(pdrop, SAFECAST(this, IOleWindow *)); // Let them have access to our HWND. HRESULT hr = SHSimulateDrop(pdrop, _pdtobj, grfKeyState, NULL, NULL); IUnknown_SetSite(pdrop, NULL); if (hr == S_FALSE) { ShellMessageBox(g_hinst, hwndParent, MAKEINTRESOURCE(IDS_SENDTO_ERRORMSG), MAKEINTRESOURCE(IDS_CABINET), MB_OK|MB_ICONEXCLAMATION); } return hr; } HRESULT CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) { HRESULT hr; if (_pdtobj && _pidlLast) { IDropTarget *pdrop; hr = SHGetUIObjectFromFullPIDL(_pidlLast, pici->hwnd, IID_PPV_ARG(IDropTarget, &pdrop)); if (SUCCEEDED(hr)) { hr = _DoDragDrop(pici->hwnd, pdrop); pdrop->Release(); } } else hr = E_INVALIDARG; return hr; } HRESULT CSendToMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax) { return E_NOTIMPL; } HRESULT CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam) { return HandleMenuMsg2(uMsg, wParam, lParam, NULL); } BOOL CSendToMenu::_CanDrop(IShellFolder *psf, LPCITEMIDLIST pidl) { BOOL fCanDrop = FALSE; IDropTarget *pdt; if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidl, IID_X_PPV_ARG(IDropTarget, 0, &pdt)))) { POINTL pt = {0}; DWORD dwEffect = DROPEFFECT_COPY; // Do a drag enter, if they return no drop effect then we can't drop if (SUCCEEDED(pdt->DragEnter(_pdtobj, _GetKeyState(), pt, &dwEffect))) { if (dwEffect != DROPEFFECT_NONE) fCanDrop = TRUE; // show it! pdt->DragLeave(); } pdt->Release(); } return fCanDrop; } HRESULT CSendToMenu::_MenuCallback(UINT fmm, IShellFolder *psf, LPCITEMIDLIST pidl) { HRESULT hr = S_OK; switch (fmm) { case FMM_ADD: hr = _CanDrop(psf, pidl) ? S_OK : S_FALSE; break; case FMM_SETLASTPIDL: Pidl_Set(&_pidlLast, pidl); break; default: hr = E_FAIL; } return hr; } HRESULT CSendToMenu::_RemovableDrivesMenuCallback(UINT fmm, IShellFolder *psf, LPCITEMIDLIST pidl) { HRESULT hr = S_OK; switch (fmm) { case FMM_ADD: hr = S_FALSE; // assume we wont show it if (_CanDrop(psf, pidl)) { // now we know it's a removable drive. in general we dont want to display cd-rom drives. // we know this is the my computer folder so just get the parsing name, we need it for GetDriveType. WCHAR szDrive[MAX_PATH]; if (SUCCEEDED(DisplayNameOf(psf, pidl, SHGDN_FORPARSING, szDrive, ARRAYSIZE(szDrive)))) { CMountPoint *pmtpt = CMountPoint::GetMountPoint(szDrive); if (pmtpt) { if (pmtpt->IsCDROM()) { // of all cdroms, only the enabled burning folder is okay to put on sendto WCHAR szRecorder[4]; if (SUCCEEDED(CDBurn_GetRecorderDriveLetter(szRecorder, ARRAYSIZE(szRecorder))) && (lstrcmpiW(szRecorder, szDrive) == 0)) { hr = S_OK; } } else if (pmtpt->IsFloppy() || pmtpt->IsStrictRemovable() || pmtpt->IsRemovableDevice()) { // also put on removable devices. hr = S_OK; } pmtpt->Release(); } else { // if this failed it could be a memory condition but its more likely to be that the // parsing name doesnt map to a mountpoint. in that case fall back to SFGAO_REMOVABLE // to pick up portable audio devices. if this was because of lowmem its no biggie. if (SHGetAttributes(psf, pidl, SFGAO_REMOVABLE)) { hr = S_OK; } } } } break; case FMM_SETLASTPIDL: Pidl_Set(&_pidlLast, pidl); break; default: hr = E_FAIL; } return hr; } HRESULT CSendToMenu::s_MenuCallback(UINT fmm, LPARAM lParam, IShellFolder *psf, LPCITEMIDLIST pidl) { return ((CSendToMenu*)lParam)->_MenuCallback(fmm, psf, pidl); } HRESULT CSendToMenu::s_RemovableDrivesMenuCallback(UINT fmm, LPARAM lParam, IShellFolder *psf, LPCITEMIDLIST pidl) { return ((CSendToMenu*)lParam)->_RemovableDrivesMenuCallback(fmm, psf, pidl); } HRESULT GetFolder(int csidl, IShellFolder **ppsf) { LPITEMIDLIST pidl; HRESULT hr = SHGetFolderLocation(NULL, csidl, NULL, 0, &pidl); if (SUCCEEDED(hr)) { hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, pidl, ppsf)); ILFree(pidl); } return hr; } HRESULT CSendToMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult) { HRESULT hr = S_OK; LRESULT lRes = 0; switch (uMsg) { case WM_INITMENUPOPUP: if (_bFirstTime) { _bFirstTime = FALSE; //In case of Shell_MergeMenus if (_hmenu == NULL) _hmenu = (HMENU)wParam; // delete the dummy entry DeleteMenu(_hmenu, 0, MF_BYPOSITION); FMCOMPOSE fmc = {0}; if (SUCCEEDED(GetFolder(CSIDL_SENDTO, &fmc.psf))) { fmc.idCmd = _idCmdFirst; fmc.grfFlags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS; fmc.pfnCallback = s_MenuCallback; fmc.lParam = (LPARAM)this; // not reference counted FileMenu_Compose(_hmenu, FMCM_REPLACE, &fmc); fmc.psf->Release(); } if (SUCCEEDED(GetFolder(CSIDL_DRIVES, &fmc.psf))) { fmc.dwMask = FMC_NOEXPAND; fmc.idCmd = _idCmdFirst; fmc.grfFlags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS; fmc.pfnCallback = s_RemovableDrivesMenuCallback; fmc.lParam = (LPARAM)this; // not reference counted FileMenu_Compose(_hmenu, FMCM_APPEND, &fmc); fmc.psf->Release(); } } else if (_hmenu != (HMENU)wParam) { // secondary cascade menu FileMenu_InitMenuPopup((HMENU)wParam); } break; case WM_DRAWITEM: { DRAWITEMSTRUCT *pdi = (DRAWITEMSTRUCT *)lParam; if (pdi->CtlType == ODT_MENU && pdi->itemID == _idCmdFirst) { lRes = FileMenu_DrawItem(NULL, pdi); } } break; case WM_MEASUREITEM: { MEASUREITEMSTRUCT *pmi = (MEASUREITEMSTRUCT *)lParam; if (pmi->CtlType == ODT_MENU && pmi->itemID == _idCmdFirst) { lRes = FileMenu_MeasureItem(NULL, pmi); } } break; case WM_MENUCHAR: { TCHAR ch = (TCHAR)LOWORD(wParam); HMENU hmenu = (HMENU)lParam; lRes = FileMenu_HandleMenuChar(hmenu, ch); } break; default: hr = E_NOTIMPL; break; } if (plResult) *plResult = lRes; return hr; } HRESULT CSendToMenu::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID) { IUnknown_Set((IUnknown **)&_pdtobj, pdtobj); return S_OK; } #define CXIMAGEGAP 6 //This is included by shell32/shellprv.h I'm not sure where this is in shdocvw #define CCH_KEYMAX 64 typedef struct { // Accessibility info must be first MSAAMENUINFO msaa; TCHAR chPrefix; TCHAR szMenuText[CCH_KEYMAX]; TCHAR szExt[MAX_PATH]; TCHAR szClass[CCH_KEYMAX]; DWORD dwFlags; int iImage; TCHAR szUserFile[CCH_KEYMAX]; } NEWOBJECTINFO; typedef struct { int type; void *lpData; DWORD cbData; HKEY hkeyNew; } NEWFILEINFO; typedef struct { ULONG cbStruct; ULONG ver; SYSTEMTIME lastupdate; } SHELLNEW_CACHE_STAMP; // ShellNew config flags #define SNCF_DEFAULT 0x0000 #define SNCF_NOEXT 0x0001 #define SNCF_USERFILES 0x0002 #define NEWTYPE_DATA 0x0003 #define NEWTYPE_FILE 0x0004 #define NEWTYPE_NULL 0x0005 #define NEWTYPE_COMMAND 0x0006 #define NEWTYPE_FOLDER 0x0007 #define NEWTYPE_LINK 0x0008 #define NEWITEM_FOLDER 0 #define NEWITEM_LINK 1 #define NEWITEM_MAX 2 class CNewMenu : public CObjectWithSite, public IContextMenu3, public IShellExtInit { // IUnknown STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj); STDMETHOD_(ULONG,AddRef)(void); STDMETHOD_(ULONG,Release)(void); // IContextMenu STDMETHOD(QueryContextMenu)(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags); STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpici); STDMETHOD(GetCommandString)(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax); // IContextMenu2 STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam); // IContextMenu3 STDMETHOD(HandleMenuMsg2)(UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT *lResult); // IShellExtInit STDMETHOD(Initialize)(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID); LONG _cRef; HMENU _hmenu; UINT _idCmdFirst; HIMAGELIST _himlSystemImageList; IDataObject *_pdtobj; LPITEMIDLIST _pidlFolder; POINT _ptNewItem; // from the view, point of click NEWOBJECTINFO *_pnoiLast; HDPA _hdpaMenuInfo; CNewMenu(); ~CNewMenu(); friend HRESULT CNewMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut); private: //Handle Menu messages submitted to HandleMenuMsg BOOL DrawItem(DRAWITEMSTRUCT *lpdi); LRESULT MeasureItem(MEASUREITEMSTRUCT *pmi); BOOL InitMenuPopup(HMENU hMenu); //Internal Helpers NEWOBJECTINFO *GetItemData(HMENU hmenu, UINT iItem); HRESULT RunCommand(HWND hwnd, LPCTSTR pszPath, LPCTSTR pszRun); HRESULT CopyTemplate(IStream *pStream, NEWFILEINFO *pnfi); // Generates it from the Fragment and _pidlFolder BOOL _GeneratePidlFromName(LPTSTR pszName, LPITEMIDLIST* ppidl); HRESULT _GetItemName(IUnknown *punkFolder, LPWSTR pszItemName, LPWSTR pszPath, UINT cchPath); HRESULT _MatchMenuItem(TCHAR ch, LRESULT* plRes); BOOL _InsertNewMenuItem(HMENU hmenu, UINT idCmd, NEWOBJECTINFO *pnoiClone); HRESULT ConsolidateMenuItems(BOOL bForce); HANDLE _hMutex, _hEvent; }; void GetConfigFlags(HKEY hkey, DWORD * pdwFlags) { TCHAR szTemp[MAX_PATH]; DWORD cbData = ARRAYSIZE(szTemp); *pdwFlags = SNCF_DEFAULT; if (SHQueryValueEx(hkey, TEXT("NoExtension"), 0, NULL, (BYTE *)szTemp, &cbData) == ERROR_SUCCESS) { *pdwFlags |= SNCF_NOEXT; } } BOOL GetNewFileInfoForKey(HKEY hkeyExt, NEWFILEINFO *pnfi, DWORD * pdwFlags) { BOOL fRet = FALSE; HKEY hKey; // this gets the \\.ext\progid key HKEY hkeyNew; TCHAR szProgID[80]; DWORD cbProgID = sizeof(szProgID); // open the Newcommand if (SHRegGetValue(hkeyExt, NULL, NULL, SRRF_RT_REG_SZ, NULL, szProgID, &cbProgID) != ERROR_SUCCESS) { return FALSE; } if (ERROR_SUCCESS != RegOpenKeyEx(hkeyExt, szProgID, 0, KEY_QUERY_VALUE, &hKey)) { hKey = hkeyExt; } if (ERROR_SUCCESS == RegOpenKeyEx(hKey, TEXT("ShellNew"), 0, KEY_QUERY_VALUE, &hkeyNew)) { DWORD dwType, cbData; TCHAR szTemp[MAX_PATH]; HKEY hkeyConfig; // Are there any config flags? if (pdwFlags) { if (ERROR_SUCCESS == RegOpenKeyEx(hkeyNew, TEXT("Config"), 0, KEY_QUERY_VALUE, &hkeyConfig)) { GetConfigFlags(hkeyConfig, pdwFlags); RegCloseKey(hkeyConfig); } else { *pdwFlags = 0; } } if (cbData = sizeof(szTemp), (SHQueryValueEx(hkeyNew, TEXT("FileName"), 0, &dwType, (LPBYTE)szTemp, &cbData) == ERROR_SUCCESS) && ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ))) { fRet = TRUE; if (pnfi) { pnfi->type = NEWTYPE_FILE; pnfi->hkeyNew = hkeyNew; // store this away so we can find out which one held the file easily ASSERT((LPTSTR*)pnfi->lpData == NULL); pnfi->lpData = StrDup(szTemp); hkeyNew = NULL; } } else if (cbData = sizeof(szTemp), (SHQueryValueEx(hkeyNew, TEXT("command"), 0, &dwType, (LPBYTE)szTemp, &cbData) == ERROR_SUCCESS) && ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ))) { fRet = TRUE; if (pnfi) { pnfi->type = NEWTYPE_COMMAND; pnfi->hkeyNew = hkeyNew; // store this away so we can find out which one held the command easily ASSERT((LPTSTR*)pnfi->lpData == NULL); pnfi->lpData = StrDup(szTemp); hkeyNew = NULL; } } else if ((SHQueryValueEx(hkeyNew, TEXT("Data"), 0, &dwType, NULL, &cbData) == ERROR_SUCCESS) && cbData) { // yes! the data for a new file is stored in the registry fRet = TRUE; // do they want the data? if (pnfi) { pnfi->type = NEWTYPE_DATA; pnfi->cbData = cbData; pnfi->lpData = (void*)LocalAlloc(LPTR, cbData); if (pnfi->lpData) { if (dwType == REG_SZ) { // Get the Unicode data from the registry. LPWSTR pszTemp = (LPWSTR)LocalAlloc(LPTR, cbData); if (pszTemp) { SHQueryValueEx(hkeyNew, TEXT("Data"), 0, &dwType, (LPBYTE)pszTemp, &cbData); pnfi->cbData = SHUnicodeToAnsi(pszTemp, (LPSTR)pnfi->lpData, cbData); if (pnfi->cbData == 0) { LocalFree(pnfi->lpData); pnfi->lpData = NULL; } LocalFree(pszTemp); } else { LocalFree(pnfi->lpData); pnfi->lpData = NULL; } } else { SHQueryValueEx(hkeyNew, TEXT("Data"), 0, &dwType, (BYTE*)pnfi->lpData, &cbData); } } } } else if (cbData = sizeof(szTemp), (SHQueryValueEx(hkeyNew, TEXT("NullFile"), 0, &dwType, (LPBYTE)szTemp, &cbData) == ERROR_SUCCESS)) { fRet = TRUE; if (pnfi) { pnfi->type = NEWTYPE_NULL; pnfi->cbData = 0; pnfi->lpData = NULL; } } if (hkeyNew) RegCloseKey(hkeyNew); } if (hKey != hkeyExt) { RegCloseKey(hKey); } return fRet; } BOOL GetNewFileInfoForExtension(NEWOBJECTINFO *pnoi, NEWFILEINFO *pnfi, HKEY* phKey, LPINT piIndex) { TCHAR szValue[80]; DWORD lSize = sizeof(szValue); HKEY hkeyNew; BOOL fRet = FALSE;; if (phKey && ((*phKey) == (HKEY)-1)) { // we're done return FALSE; } // do the ShellNew key stuff if there's no phKey passed in (which means // use the info in pnoi to get THE one) and there's no UserFile specified. // // if there IS a UserFile specified, then it's a file, and that szUserFile points to it.. if (!phKey && !pnoi->szUserFile[0] || (phKey && !*phKey)) { // check the new keys under the class id (if any) TCHAR szSubKey[128]; HRESULT hr; hr = StringCchPrintf(szSubKey, ARRAYSIZE(szSubKey), TEXT("%s\\CLSID"), pnoi->szClass); if (SUCCEEDED(hr)) { lSize = sizeof(szValue); if (SHRegGetValue(HKEY_CLASSES_ROOT, szSubKey, NULL, SRRF_RT_REG_SZ, NULL, szValue, &lSize) == ERROR_SUCCESS) { hr = StringCchPrintf(szSubKey, ARRAYSIZE(szSubKey), TEXT("CLSID\\%s"), szValue); if (SUCCEEDED(hr)) { lSize = sizeof(szValue); if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, szSubKey, 0, KEY_QUERY_VALUE, &hkeyNew)) { fRet = GetNewFileInfoForKey(hkeyNew, pnfi, &pnoi->dwFlags); RegCloseKey(hkeyNew); } } } } // otherwise check under the type extension... do the extension, not the type // so that multi-ext to 1 type will work right if (!fRet && (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, pnoi->szExt, 0, KEY_QUERY_VALUE, &hkeyNew))) { fRet = GetNewFileInfoForKey(hkeyNew, pnfi, &pnoi->dwFlags); RegCloseKey(hkeyNew); } if (phKey) { // if we're iterating, then we've got to open the key now... hr = StringCchPrintf(szSubKey, ARRAYSIZE(szSubKey), TEXT("%s\\%s\\ShellNew\\FileName"), pnoi->szExt, pnoi->szClass); if (SUCCEEDED(hr)) { if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, szSubKey, 0, KEY_QUERY_VALUE, phKey)) { *piIndex = 0; // if we didn't find one of the default ones above, // try it now // otherwise just return success or failure on fRet if (!fRet) { goto Iterate; } } else { *phKey = (HKEY)-1; } } else { *phKey = (HKEY)-1; } } } else if (!phKey && pnoi->szUserFile[0]) { // there's no key, so just return info about szUserFile pnfi->type = NEWTYPE_FILE; pnfi->lpData = StrDup(pnoi->szUserFile); pnfi->hkeyNew = NULL; fRet = TRUE; } else if (phKey) { DWORD dwSize; DWORD dwData; DWORD dwType; // we're iterating through... Iterate: dwSize = ARRAYSIZE(pnoi->szUserFile); dwData = ARRAYSIZE(pnoi->szMenuText); if (RegEnumValue(*phKey, *piIndex, pnoi->szUserFile, &dwSize, NULL, &dwType, (LPBYTE)pnoi->szMenuText, &dwData) == ERROR_SUCCESS) { (*piIndex)++; // if there's something more than the null.. if (dwData <= 1) { HRESULT hr = StringCchCopy(pnoi->szMenuText, ARRAYSIZE(pnoi->szMenuText), PathFindFileName(pnoi->szUserFile)); if (SUCCEEDED(hr)) { PathRemoveExtension(pnoi->szMenuText); } else { pnoi->szMenuText[0] = TEXT('\0'); } } fRet = TRUE; } else { RegCloseKey(*phKey); *phKey = (HKEY)-1; fRet = FALSE; } } return fRet; } #define SHELLNEW_CONSOLIDATION_MUTEX TEXT("ShellNewConsolidationMutex") #define SHELLNEW_CONSOLIDATION_EVENT TEXT("ShellNewConsolidationEvent") CNewMenu::CNewMenu() : _cRef(1), _hMutex(CreateMutex(NULL, FALSE, SHELLNEW_CONSOLIDATION_MUTEX)), _hEvent(CreateEvent(NULL, FALSE, FALSE, SHELLNEW_CONSOLIDATION_EVENT)) { DllAddRef(); ASSERT(_pnoiLast == NULL); } CNewMenu::~CNewMenu() { if (_hdpaMenuInfo) { // we dont own the lifetime of _hmenu, and it gets destroyed before the destructor // is called. thus maintain the lifetime of our NEWOBJECTINFO data in a dpa. for (int i = 0; i < DPA_GetPtrCount(_hdpaMenuInfo); i++) { NEWOBJECTINFO *pNewObjInfo = (NEWOBJECTINFO *)DPA_GetPtr(_hdpaMenuInfo, i); LocalFree(pNewObjInfo); } DPA_Destroy(_hdpaMenuInfo); } ILFree(_pidlFolder); if (_pdtobj) _pdtobj->Release(); if (_hMutex) { CloseHandle(_hMutex); } if (_hEvent) { CloseHandle(_hEvent); } DllRelease(); } HRESULT CNewMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut) { // aggregation checking is handled in class factory *ppvOut = NULL; HRESULT hr = E_OUTOFMEMORY; CNewMenu * pShellNew = new CNewMenu(); if (pShellNew) { if (!pShellNew->_hMutex || !pShellNew->_hEvent) { hr = E_OUTOFMEMORY; } else { hr = pShellNew->QueryInterface(riid, ppvOut); } pShellNew->Release(); } return hr; } HRESULT CNewMenu::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CNewMenu, IShellExtInit), // IID_IShellExtInit QITABENT(CNewMenu, IContextMenu3), // IID_IContextMenu3 QITABENTMULTI(CNewMenu, IContextMenu2, IContextMenu3), // IID_IContextMenu2 QITABENTMULTI(CNewMenu, IContextMenu, IContextMenu3), // IID_IContextMenu QITABENT(CNewMenu, IObjectWithSite), // IID_IObjectWithSite { 0 } }; return QISearch(this, qit, riid, ppvObj); } ULONG CNewMenu::AddRef() { return InterlockedIncrement(&_cRef); } ULONG CNewMenu::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } HRESULT CNewMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { // if they want the default menu only (CMF_DEFAULTONLY) OR // this is being called for a shortcut (CMF_VERBSONLY) // we don't want to be on the context menu MENUITEMINFO mfi = {0}; if (uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY)) return S_OK; ConsolidateMenuItems(FALSE); _idCmdFirst = idCmdFirst+2; TCHAR szNewMenu[80]; LoadString(g_hinst, IDS_NEWMENU, szNewMenu, ARRAYSIZE(szNewMenu)); // HACK: I assume that they are querying during a WM_INITMENUPOPUP or equivalent GetCursorPos(&_ptNewItem); _hmenu = CreatePopupMenu(); mfi.cbSize = sizeof(MENUITEMINFO); mfi.fMask = MIIM_ID | MIIM_TYPE; mfi.wID = idCmdFirst+1; mfi.fType = MFT_STRING; mfi.dwTypeData = szNewMenu; InsertMenuItem(_hmenu, 0, TRUE, &mfi); ZeroMemory(&mfi, sizeof (mfi)); mfi.cbSize = sizeof(MENUITEMINFO); mfi.fMask = MIIM_ID | MIIM_SUBMENU | MIIM_TYPE | MIIM_DATA; mfi.fType = MFT_STRING; mfi.wID = idCmdFirst; mfi.hSubMenu = _hmenu; mfi.dwTypeData = szNewMenu; mfi.dwItemData = 0; InsertMenuItem(hmenu, indexMenu, TRUE, &mfi); _hmenu = NULL; return ResultFromShort(_idCmdFirst - idCmdFirst + 1); } // This is almost the same as ILCreatePidlFromPath, but // uses only the filename from the full path pszPath and // the _pidlFolder to generate the pidl. This is used because // when creating an item in Desktop\My Documents, it used to create a // full pidl c:\documents and Settings\lamadio\My Documents\New folder // instead of the pidl desktop\my documents\New Folder. BOOL CNewMenu::_GeneratePidlFromName(LPTSTR pszFile, LPITEMIDLIST* ppidl) { *ppidl = NULL; // Out param IShellFolder* psf; if (SUCCEEDED(SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, _pidlFolder, &psf)))) { LPITEMIDLIST pidlItem; if (SUCCEEDED(psf->ParseDisplayName(NULL, NULL, pszFile, NULL, &pidlItem, NULL))) { *ppidl = ILCombine(_pidlFolder, pidlItem); ILFree(pidlItem); } psf->Release(); } return BOOLFROMPTR(*ppidl); } HRESULT CNewMenu::_GetItemName(IUnknown *punkFolder, LPWSTR pszItemName, LPWSTR pszPath, UINT cchPath) { // we need to pick up the name by asking the folder about the item, // not by pathappending to the folder's path. IShellFolder *psf; HRESULT hr = punkFolder->QueryInterface(IID_PPV_ARG(IShellFolder, &psf)); if (SUCCEEDED(hr)) { LPITEMIDLIST pidlFile; hr = psf->ParseDisplayName(NULL, NULL, pszItemName, NULL, &pidlFile, NULL); if (SUCCEEDED(hr)) { hr = DisplayNameOf(psf, pidlFile, SHGDN_FORPARSING, pszPath, cchPath); ILFree(pidlFile); } psf->Release(); } return hr; } const ICIVERBTOIDMAP c_IDMap[] = { { L"NewFolder", "NewFolder", NEWITEM_FOLDER, NEWITEM_FOLDER, }, { L"link", "link", NEWITEM_LINK, NEWITEM_LINK, }, }; HRESULT CNewMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) { HRESULT hr = E_FAIL; DWORD dwFlags; if (IS_INTRESOURCE(pici->lpVerb) && _pnoiLast) dwFlags = _pnoiLast->dwFlags; else { UINT uID; if (SUCCEEDED(SHMapICIVerbToCmdID(pici, c_IDMap, ARRAYSIZE(c_IDMap), &uID))) { switch (uID) { case NEWITEM_FOLDER: dwFlags = NEWTYPE_FOLDER; break; case NEWITEM_LINK: dwFlags = NEWTYPE_LINK; break; default: ASSERTMSG(0, "should not get what we don't put on the menu"); return E_FAIL; } } } TCHAR szFileSpec[MAX_PATH+80]; // Add some slop incase we overflow TCHAR szTemp[MAX_PATH+80]; // Add some slop incase we overflow //See if the pidl is folder shortcut and if so get the target path. SHGetTargetFolderPath(_pidlFolder, szTemp, ARRAYSIZE(szTemp)); BOOL fLFN = IsLFNDrive(szTemp); NEWFILEINFO nfi; DWORD dwErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); nfi.lpData = NULL; nfi.hkeyNew = NULL; switch (dwFlags) { case NEWTYPE_FOLDER: LoadString(g_hinst, fLFN ? IDS_FOLDERLONGPLATE : IDS_FOLDERTEMPLATE, szFileSpec, ARRAYSIZE(szFileSpec)); break; case NEWTYPE_LINK: LoadString(g_hinst, IDS_NEWLINKTEMPLATE, szFileSpec, ARRAYSIZE(szFileSpec)); break; default: LoadString(g_hinst, IDS_NEWFILEPREFIX, szFileSpec, ARRAYSIZE(szFileSpec)); // // If we are running on a mirrored BiDi localized system, // then flip the order of concatenation so that the // string is read properly for Arabic. [samera] // if (IS_BIDI_LOCALIZED_SYSTEM()) { StringCchCopy(szTemp, ARRAYSIZE(szTemp), szFileSpec); // ok to truncate, for display only StringCchPrintf(szFileSpec, ARRAYSIZE(szFileSpec), TEXT("%s %s"), _pnoiLast->szMenuText, szTemp); // ok to truncate, for display only } else { StringCchCat(szFileSpec, ARRAYSIZE(szFileSpec), _pnoiLast->szMenuText); // ok to truncate, for display only } SHStripMneumonic(szFileSpec); if (!(dwFlags & SNCF_NOEXT)) { StringCchCat(szFileSpec, ARRAYSIZE(szFileSpec), _pnoiLast->szExt); // ok to truncate, for display only } break; } BOOL fCreateStorage = (dwFlags == NEWTYPE_FOLDER); //See if the pidl is folder shortcut and if so get the target pidl. LPITEMIDLIST pidlTarget; hr = SHGetTargetFolderIDList(_pidlFolder, &pidlTarget); if (SUCCEEDED(hr)) { IStorage * pStorage; hr = StgBindToObject(pidlTarget, STGM_READWRITE, IID_PPV_ARG(IStorage, &pStorage)); if (SUCCEEDED(hr)) { IStream *pStreamCreated = NULL; IStorage *pStorageCreated = NULL; STATSTG statstg = { 0 }; if (fCreateStorage) { hr = StgMakeUniqueName(pStorage, szFileSpec, IID_PPV_ARG(IStorage, &pStorageCreated)); if (SUCCEEDED(hr)) pStorageCreated->Stat(&statstg, STATFLAG_DEFAULT); } else { hr = StgMakeUniqueName(pStorage, szFileSpec, IID_PPV_ARG(IStream, &pStreamCreated)); if (SUCCEEDED(hr)) pStreamCreated->Stat(&statstg, STATFLAG_DEFAULT); } if (SUCCEEDED(hr)) { switch (dwFlags) { case NEWTYPE_FOLDER: // we're already done. break; case NEWTYPE_LINK: if (statstg.pwcsName) { // Lookup Command in Registry under key HKCR/.lnk/ShellNew/Command TCHAR szCommand[MAX_PATH]; DWORD dwLength = sizeof(szCommand); if (ERROR_SUCCESS == SHGetValue(HKEY_CLASSES_ROOT, TEXT(".lnk\\ShellNew"), TEXT("Command"), NULL, szCommand, &dwLength)) { TCHAR szPath[MAX_PATH]; hr = _GetItemName(SAFECAST(pStorage, IUnknown*), statstg.pwcsName, szPath, ARRAYSIZE(szPath)); if (SUCCEEDED(hr)) { hr = RunCommand(pici->hwnd, szPath, szCommand); } } } break; default: if (GetNewFileInfoForExtension(_pnoiLast, &nfi, NULL, NULL)) { switch (nfi.type) { case NEWTYPE_FILE: hr = CopyTemplate(pStreamCreated, &nfi); break; case NEWTYPE_NULL: // already created a zero-length file. break; case NEWTYPE_DATA: ULONG ulWritten; hr = pStreamCreated->Write(nfi.lpData, nfi.cbData, &ulWritten); if (SUCCEEDED(hr)) { hr = pStreamCreated->Commit(STGC_DEFAULT); } break; case NEWTYPE_COMMAND: if (statstg.pwcsName) { TCHAR szPath[MAX_PATH]; hr = _GetItemName(SAFECAST(pStorage, IUnknown*), statstg.pwcsName, szPath, ARRAYSIZE(szPath)); if (SUCCEEDED(hr)) { // oops, we already created the stream, but we actually // just wanted the filename for the RunCommand, so we // have to delete it first. ATOMICRELEASE(pStreamCreated); hr = pStorage->DestroyElement(statstg.pwcsName); // flush out any notifications from the destroy (the // destroy causes notifications). SHChangeNotifyHandleEvents(); if (SUCCEEDED(hr)) { hr = RunCommand(pici->hwnd, szPath, (LPTSTR)nfi.lpData); if (hr == S_FALSE) hr = S_OK; } } } break; default: hr = E_FAIL; break; } } else { hr = HRESULT_FROM_WIN32(ERROR_BADKEY); } break; } // these have to be released before _GeneratePidlFromName, since that opens // the storage in exclusive mode for other reasons. but we can't release // them earlier because CopyTemplate might need them. if (pStorageCreated) pStorageCreated->Release(); if (pStreamCreated) pStreamCreated->Release(); if (SUCCEEDED(hr)) hr = pStorage->Commit(STGC_DEFAULT); pStorage->Release(); LPITEMIDLIST pidlCreatedItem; if (SUCCEEDED(hr) && _GeneratePidlFromName(statstg.pwcsName, &pidlCreatedItem)) { SHChangeNotifyHandleEvents(); IShellView2 *psv2; if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_SFolderView, IID_PPV_ARG(IShellView2, &psv2)))) { DWORD dwFlagsSelFlags = SVSI_SELECT | SVSI_POSITIONITEM; if (!(dwFlags & NEWTYPE_LINK)) dwFlagsSelFlags |= SVSI_EDIT; psv2->SelectAndPositionItem(ILFindLastID(pidlCreatedItem), dwFlagsSelFlags, NULL); psv2->Release(); } ILFree(pidlCreatedItem); } CoTaskMemFree(statstg.pwcsName); } else { pStorage->Release(); } } ILFree(pidlTarget); } if (nfi.lpData) LocalFree((HLOCAL)nfi.lpData); if (nfi.hkeyNew) RegCloseKey(nfi.hkeyNew); if (FAILED_AND_NOT_CANCELED(hr) && !(pici->fMask & CMIC_MASK_FLAG_NO_UI)) { TCHAR szTitle[MAX_PATH]; LoadString(g_hinst, (fCreateStorage ? IDS_DIRCREATEFAILED_TITLE : IDS_FILECREATEFAILED_TITLE), szTitle, ARRAYSIZE(szTitle)); SHSysErrorMessageBox(pici->hwnd, szTitle, fCreateStorage ? IDS_CANNOTCREATEFOLDER : IDS_CANNOTCREATEFILE, HRESULT_CODE(hr), szFileSpec, MB_OK | MB_ICONEXCLAMATION); } SetErrorMode(dwErrorMode); return hr; } HRESULT CNewMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax) { switch (uType) { case GCS_HELPTEXT: if (idCmd < NEWITEM_MAX) { LoadString(g_hinst, (UINT)(IDS_NEWHELP_FIRST + idCmd), (LPTSTR)pszName, cchMax); return S_OK; } break; case GCS_HELPTEXTA: if (idCmd < NEWITEM_MAX) { LoadStringA(g_hinst, (UINT)(IDS_NEWHELP_FIRST + idCmd), pszName, cchMax); return S_OK; } break; case GCS_VERBW: case GCS_VERBA: return SHMapCmdIDToVerb(idCmd, c_IDMap, ARRAYSIZE(c_IDMap), pszName, cchMax, (GCS_VERBW == uType)); } return E_NOTIMPL; } //Defined in fsmenu.obj BOOL _MenuCharMatch(LPCTSTR lpsz, TCHAR ch, BOOL fIgnoreAmpersand); HRESULT CNewMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam) { return HandleMenuMsg2(uMsg,wParam,lParam,NULL); } HRESULT CNewMenu::_MatchMenuItem(TCHAR ch, LRESULT* plRes) { // If plRes is NULL we're being called on HandleMenuMsg() which // doesn't support returning an LRESULT, which is needed for WM_MENUCHAR... if (plRes == NULL) return S_FALSE; int iLastSelectedItem = -1; int iNextMatch = -1; BOOL fMoreThanOneMatch = FALSE; int c = GetMenuItemCount(_hmenu); // Pass 1: Locate the Selected Item for (int i = 0; i < c; i++) { MENUITEMINFO mii = {0}; mii.cbSize = sizeof(mii); mii.fMask = MIIM_STATE; if (GetMenuItemInfo(_hmenu, i, MF_BYPOSITION, &mii)) { if (mii.fState & MFS_HILITE) { iLastSelectedItem = i; break; } } } // Pass 2: Starting from the selected item, locate the first item with the matching name. for (i = iLastSelectedItem + 1; i < c; i++) { MENUITEMINFO mii = {0}; mii.cbSize = sizeof(mii); mii.fMask = MIIM_DATA | MIIM_STATE; if (GetMenuItemInfo(_hmenu, i, MF_BYPOSITION, &mii)) { NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)mii.dwItemData; if (pnoi && _MenuCharMatch(pnoi->szMenuText, ch, FALSE)) { _pnoiLast = pnoi; if (iNextMatch != -1) { fMoreThanOneMatch = TRUE; break; // We found all the info we need } else { iNextMatch = i; } } } } // Pass 3: If we did not find a match, or if there was only one match // Search from the first item, to the Selected Item if (iNextMatch == -1 || fMoreThanOneMatch == FALSE) { for (i = 0; i <= iLastSelectedItem; i++) { MENUITEMINFO mii = {0}; mii.cbSize = sizeof(mii); mii.fMask = MIIM_DATA | MIIM_STATE; if (GetMenuItemInfo(_hmenu, i, MF_BYPOSITION, &mii)) { NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)mii.dwItemData; if (pnoi && _MenuCharMatch(pnoi->szMenuText, ch, FALSE)) { _pnoiLast = pnoi; if (iNextMatch != -1) { fMoreThanOneMatch = TRUE; break; } else { iNextMatch = i; } } } } } if (iNextMatch != -1) { *plRes = MAKELONG(iNextMatch, fMoreThanOneMatch? MNC_SELECT : MNC_EXECUTE); } else { *plRes = MAKELONG(0, MNC_IGNORE); } return S_OK; } HRESULT CNewMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT *plResult) { HRESULT hr = S_OK; LRESULT lRes = 0; switch (uMsg) { case WM_INITMENUPOPUP: if (_hmenu == NULL) { _hmenu = (HMENU)wParam; } InitMenuPopup(_hmenu); break; case WM_DRAWITEM: DrawItem((DRAWITEMSTRUCT *)lParam); break; case WM_MEASUREITEM: lRes = MeasureItem((MEASUREITEMSTRUCT *)lParam); break; case WM_MENUCHAR: hr = _MatchMenuItem((TCHAR)LOWORD(wParam), &lRes); break; default: hr = E_NOTIMPL; break; } if (plResult) *plResult = lRes; return hr; } HRESULT CNewMenu::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID) { ASSERT(_pidlFolder == NULL); _pidlFolder = ILClone(pidlFolder); IUnknown_Set((IUnknown **)&_pdtobj, pdtobj); return S_OK; } BOOL CNewMenu::DrawItem(DRAWITEMSTRUCT *lpdi) { BOOL fFlatMenu = FALSE; BOOL fFrameRect = FALSE; SystemParametersInfo(SPI_GETFLATMENU, 0, (PVOID)&fFlatMenu, 0); if ((lpdi->itemAction & ODA_SELECT) || (lpdi->itemAction & ODA_DRAWENTIRE)) { int x, y; SIZE sz; NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)lpdi->itemData; // Draw the image (if there is one). GetTextExtentPoint(lpdi->hDC, pnoi->szMenuText, lstrlen(pnoi->szMenuText), &sz); if (lpdi->itemState & ODS_SELECTED) { // REVIEW HACK - keep track of the last selected item. _pnoiLast = pnoi; if (fFlatMenu) { fFrameRect = TRUE; SetBkColor(lpdi->hDC, GetSysColor(COLOR_MENUHILIGHT)); SetTextColor(lpdi->hDC, GetSysColor(COLOR_MENUTEXT)); FillRect(lpdi->hDC,&lpdi->rcItem,GetSysColorBrush(COLOR_MENUHILIGHT)); } else { SetBkColor(lpdi->hDC, GetSysColor(COLOR_HIGHLIGHT)); SetTextColor(lpdi->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); FillRect(lpdi->hDC,&lpdi->rcItem,GetSysColorBrush(COLOR_HIGHLIGHT)); } } else { SetTextColor(lpdi->hDC, GetSysColor(COLOR_MENUTEXT)); FillRect(lpdi->hDC,&lpdi->rcItem,GetSysColorBrush(COLOR_MENU)); } RECT rc = lpdi->rcItem; rc.left += +2*CXIMAGEGAP+g_cxSmIcon; DrawText(lpdi->hDC,pnoi->szMenuText,lstrlen(pnoi->szMenuText), &rc,DT_SINGLELINE|DT_VCENTER); if (pnoi->iImage != -1) { x = lpdi->rcItem.left+CXIMAGEGAP; y = (lpdi->rcItem.bottom+lpdi->rcItem.top-g_cySmIcon)/2; HIMAGELIST himlSmall; Shell_GetImageLists(NULL, &himlSmall); ImageList_Draw(himlSmall, pnoi->iImage, lpdi->hDC, x, y, ILD_TRANSPARENT); } else { x = lpdi->rcItem.left+CXIMAGEGAP; y = (lpdi->rcItem.bottom+lpdi->rcItem.top-g_cySmIcon)/2; } if (fFrameRect) { HBRUSH hbrFill = (HBRUSH)GetSysColorBrush(COLOR_HIGHLIGHT); HBRUSH hbrSave = (HBRUSH)SelectObject(lpdi->hDC, hbrFill); int x = lpdi->rcItem.left; int y = lpdi->rcItem.top; int cx = lpdi->rcItem.right - x - 1; int cy = lpdi->rcItem.bottom - y - 1; PatBlt(lpdi->hDC, x, y, 1, cy, PATCOPY); PatBlt(lpdi->hDC, x + 1, y, cx, 1, PATCOPY); PatBlt(lpdi->hDC, x, y + cy, cx, 1, PATCOPY); PatBlt(lpdi->hDC, x + cx, y + 1, 1, cy, PATCOPY); SelectObject(lpdi->hDC, hbrSave); } return TRUE; } return FALSE; } LRESULT CNewMenu::MeasureItem(MEASUREITEMSTRUCT *pmi) { LRESULT lres = FALSE; NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)pmi->itemData; if (pnoi) { // Get the rough height of an item so we can work out when to break the // menu. User should really do this for us but that would be useful. HDC hdc = GetDC(NULL); if (hdc) { // REVIEW cache out the menu font? NONCLIENTMETRICS ncm; ncm.cbSize = sizeof(ncm); if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, FALSE)) { HFONT hfont = CreateFontIndirect(&ncm.lfMenuFont); if (hfont) { SIZE sz; HFONT hfontOld = (HFONT)SelectObject(hdc, hfont); GetTextExtentPoint(hdc, pnoi->szMenuText, lstrlen(pnoi->szMenuText), &sz); pmi->itemHeight = max (g_cySmIcon+CXIMAGEGAP/2, ncm.iMenuHeight); pmi->itemWidth = g_cxSmIcon + 2*CXIMAGEGAP + sz.cx; //pmi->itemWidth = 2*CXIMAGEGAP + sz.cx; SelectObject(hdc, hfontOld); DeleteObject(hfont); lres = TRUE; } } ReleaseDC(NULL, hdc); } } return lres; } BOOL GetClassDisplayName(LPTSTR pszClass,LPTSTR pszDisplayName,DWORD cchDisplayName) { DWORD cch; return SUCCEEDED(AssocQueryString(0, ASSOCSTR_COMMAND, pszClass, TEXT("open"), NULL, &cch)) && SUCCEEDED(AssocQueryString(0, ASSOCSTR_FRIENDLYDOCNAME, pszClass, NULL, pszDisplayName, &cchDisplayName)); } // New Menu item consolidation worker task class CNewMenuConsolidator : public CRunnableTask { public: virtual STDMETHODIMP RunInitRT(void); static const GUID _taskid; static HRESULT CreateInstance(REFIID riid, void **ppv); private: CNewMenuConsolidator(); ~CNewMenuConsolidator(); HANDLE _hMutex, _hEvent; }; CNewMenuConsolidator::CNewMenuConsolidator() : CRunnableTask(RTF_DEFAULT), _hMutex(CreateMutex(NULL, FALSE, SHELLNEW_CONSOLIDATION_MUTEX)), _hEvent(CreateEvent(NULL, FALSE, FALSE, SHELLNEW_CONSOLIDATION_EVENT)) { DllAddRef(); } CNewMenuConsolidator::~CNewMenuConsolidator() { if (_hMutex) { CloseHandle(_hMutex); } if (_hEvent) { CloseHandle(_hEvent); } DllRelease(); } // // Instance generator. // HRESULT CNewMenuConsolidator::CreateInstance(REFIID riid, void **ppv) { HRESULT hr = E_OUTOFMEMORY; CNewMenuConsolidator *pnmc = new CNewMenuConsolidator(); if (pnmc) { if (!pnmc->_hMutex || !pnmc->_hEvent) { hr = E_FAIL; } else { hr = pnmc->QueryInterface(riid, ppv); } pnmc->Release(); } return hr; } const GUID CNewMenuConsolidator::_taskid = { 0xf87a1f28, 0xc7f, 0x11d2, { 0xbe, 0x1d, 0x0, 0xa0, 0xc9, 0xa8, 0x3d, 0xa1 } }; #define REGSTR_SESSION_SHELLNEW STRREG_DISCARDABLE STRREG_POSTSETUP TEXT("\\ShellNew") #define REGVAL_SESSION_SHELLNEW_TIMESTAMP TEXT("~reserved~") #define REGVAL_SESSION_SHELLNEW_LANG TEXT("Language") #define SHELLNEW_CACHE_CURRENTVERSION MAKELONG(1, 1) // Constructs a current New submenu cache stamp. void CNewMenu_MakeCacheStamp(SHELLNEW_CACHE_STAMP* pStamp) { pStamp->cbStruct = sizeof(*pStamp); pStamp->ver = SHELLNEW_CACHE_CURRENTVERSION; GetLocalTime(&pStamp->lastupdate); } // Determines whether the New submenu cache needs to be rebuilt. BOOL CNewMenu_ShouldUpdateCache(SHELLNEW_CACHE_STAMP* pStamp) { // Correct version? return !(sizeof(*pStamp) == pStamp->cbStruct && SHELLNEW_CACHE_CURRENTVERSION == pStamp->ver); } // Gathers up shellnew entries from HKCR into a distinct registry location // for faster enumeration of the New submenu items. // // We'll do a first time cache initialization only if we have to before showing // the menu, but will always rebuild the cache following display of the menu. HRESULT CNewMenu::ConsolidateMenuItems(BOOL bForce) { HKEY hkeyShellNew = NULL; BOOL bUpdate = TRUE; // unless we discover otherwise HRESULT hr = S_OK; // make sure that a worker thread isnt currently slamming the registry info we're inspecting. // if we timeout, then do nothing, since the worker thread is already working on it. if (WAIT_OBJECT_0 == WaitForSingleObject(_hMutex, 0)) { // If we're not being told to unconditionally update the cache and // we validate that we've already established one, then we get out of doing any // work. if (!bForce && ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, REGSTR_SESSION_SHELLNEW, 0, KEY_QUERY_VALUE, &hkeyShellNew)) { SHELLNEW_CACHE_STAMP stamp; ULONG cbVal = sizeof(stamp); if (ERROR_SUCCESS == SHQueryValueEx(hkeyShellNew, REGVAL_SESSION_SHELLNEW_TIMESTAMP, NULL, NULL, (LPBYTE)&stamp, &cbVal) && sizeof(stamp) == cbVal) { bUpdate = CNewMenu_ShouldUpdateCache(&stamp); } LCID lcid; ULONG cblcid = sizeof(lcid); if (!bUpdate && ERROR_SUCCESS == SHQueryValueEx(hkeyShellNew, REGVAL_SESSION_SHELLNEW_LANG, NULL, NULL, (LPBYTE)&lcid, &cblcid) && sizeof(lcid) == cblcid) { bUpdate = (GetUserDefaultUILanguage() != lcid); // if the languages are different, then update } RegCloseKey(hkeyShellNew); } // end synchronize ReleaseMutex(_hMutex); if (bUpdate) { IShellTaskScheduler* pScheduler; hr = CoCreateInstance(CLSID_SharedTaskScheduler, NULL, CLSCTX_INPROC, IID_PPV_ARG(IShellTaskScheduler, &pScheduler)); if (SUCCEEDED(hr)) { IRunnableTask *pTask; hr = CNewMenuConsolidator::CreateInstance(IID_PPV_ARG(IRunnableTask, &pTask)); if (SUCCEEDED(hr)) { // the background task will set _hEvent for us when it's done. hr = pScheduler->AddTask(pTask, CNewMenuConsolidator::_taskid, NULL, ITSAT_DEFAULT_PRIORITY); pTask->Release(); } pScheduler->Release(); } } if (!bUpdate || FAILED(hr)) { // if the scheduler wont activate the event, we do it ourselves. SetEvent(_hEvent); } } return hr; } // Consolidation worker. STDMETHODIMP CNewMenuConsolidator::RunInitRT() { ULONG dwErr = ERROR_SUCCESS; // the possible owners of the mutex are // - nobody, we'll own it // - other worker threads like this one // - the guy who checks to see if the cached info is in the registry. // if there's another worker thread which owns this mutex, then bail, since that one // will do all the work we're going to do. // if the guy who checks the cached info has it, then bail, since it'll spawn // another one of these soon enough. // so use a 0 timeout. if (WAIT_OBJECT_0 == WaitForSingleObject(_hMutex, 0)) { HKEY hkeyShellNew = NULL; TCHAR szExt[MAX_PATH]; ULONG dwDisposition; // Delete the existing cache; we'll build it from scratch each time. while (ERROR_SUCCESS == (dwErr = RegCreateKeyEx(HKEY_CURRENT_USER, REGSTR_SESSION_SHELLNEW, 0, NULL, 0, KEY_SET_VALUE, NULL, &hkeyShellNew, &dwDisposition)) && REG_CREATED_NEW_KEY != dwDisposition) { // Key already existed, so delete it, and loop to reopen. RegCloseKey(hkeyShellNew); SHDeleteKey(HKEY_CURRENT_USER, REGSTR_SESSION_SHELLNEW); hkeyShellNew = NULL; } if (ERROR_SUCCESS == dwErr) { // Enumerate each subkey of HKCR, looking for New menu items. for (int i = 0; RegEnumKey(HKEY_CLASSES_ROOT, i, szExt, ARRAYSIZE(szExt)) == ERROR_SUCCESS; i++) { TCHAR szClass[CCH_KEYMAX]; TCHAR szDisplayName[CCH_KEYMAX]; DWORD cbVal = sizeof(szClass); // find .ext that have proper class descriptions with them. if ((szExt[0] == TEXT('.')) && SHRegGetValue(HKEY_CLASSES_ROOT, szExt, NULL, SRRF_RT_REG_SZ, NULL, szClass, &cbVal) == ERROR_SUCCESS && GetClassDisplayName(szClass, szDisplayName, ARRAYSIZE(szDisplayName))) { NEWOBJECTINFO noi = {0}; HKEY hkeyIterate = NULL; int iIndex = 0; HRESULT hr; BOOL fOk = TRUE; hr = StringCchCopy(noi.szExt, ARRAYSIZE(noi.szExt), szExt); if (FAILED(hr)) { fOk = FALSE; } hr = StringCchCopy(noi.szClass, ARRAYSIZE(noi.szClass), szClass); if (FAILED(hr)) { fOk = FALSE; } hr = StringCchCopy(noi.szMenuText, ARRAYSIZE(noi.szMenuText), szDisplayName); if (FAILED(hr)) { fOk = FALSE; } noi.iImage = -1; if (fOk) { // Retrieve all additional information for the key. while (GetNewFileInfoForExtension(&noi, NULL, &hkeyIterate, &iIndex)) { // Stick it in the cache. RegSetValueEx(hkeyShellNew, noi.szMenuText, NULL, REG_BINARY, (LPBYTE)&noi, sizeof(noi)); } } } } // Stamp the cache. SHELLNEW_CACHE_STAMP stamp; CNewMenu_MakeCacheStamp(&stamp); RegSetValueEx(hkeyShellNew, REGVAL_SESSION_SHELLNEW_TIMESTAMP, NULL, REG_BINARY, (LPBYTE)&stamp, sizeof(stamp)); LCID lcid = GetUserDefaultUILanguage(); RegSetValueEx(hkeyShellNew, REGVAL_SESSION_SHELLNEW_LANG, NULL, REG_DWORD, (LPBYTE)&lcid, sizeof(lcid)); } if (NULL != hkeyShellNew) RegCloseKey(hkeyShellNew); // signal the event so InitMenuPopup can proceed. SetEvent(_hEvent); ReleaseMutex(_hMutex); } return HRESULT_FROM_WIN32(dwErr); } BOOL CNewMenu::_InsertNewMenuItem(HMENU hmenu, UINT idCmd, NEWOBJECTINFO *pnoiClone) { if (pnoiClone->szMenuText[0]) { NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)LocalAlloc(LPTR, sizeof(NEWOBJECTINFO)); if (pnoi) { *pnoi = *pnoiClone; pnoi->msaa.dwMSAASignature = MSAA_MENU_SIG; if (StrChr(pnoi->szMenuText, TEXT('&')) == NULL) { pnoi->chPrefix = TEXT('&'); pnoi->msaa.pszWText = &pnoi->chPrefix; } else { pnoi->msaa.pszWText = pnoi->szMenuText; } pnoi->msaa.cchWText = lstrlen(pnoi->msaa.pszWText); MENUITEMINFO mii = {0}; mii.cbSize = sizeof(mii); mii.fMask = MIIM_TYPE | MIIM_DATA | MIIM_ID; mii.fType = MFT_OWNERDRAW; mii.fState = MFS_ENABLED; mii.wID = idCmd; mii.dwItemData = (DWORD_PTR)pnoi; mii.dwTypeData = (LPTSTR)pnoi; if (-1 != DPA_AppendPtr(_hdpaMenuInfo, pnoi)) { InsertMenuItem(hmenu, -1, TRUE, &mii); } else { LocalFree(pnoi); return FALSE; } } } return TRUE; } // WM_INITMENUPOPUP handler BOOL CNewMenu::InitMenuPopup(HMENU hmenu) { UINT iStart = 3; NEWOBJECTINFO noi = {0}; if (GetItemData(hmenu, iStart)) //Position 0 is New Folder, 1 shortcut, 2 sep return FALSE; //already initialized. No need to do anything _hdpaMenuInfo = DPA_Create(4); if (!_hdpaMenuInfo) return FALSE; //Remove the place holder. DeleteMenu(hmenu,0,MF_BYPOSITION); //Insert New Folder menu item LoadString(g_hinst, IDS_NEWFOLDER, noi.szMenuText, ARRAYSIZE(noi.szMenuText)); noi.dwFlags = NEWTYPE_FOLDER; noi.iImage = Shell_GetCachedImageIndex(TEXT("shell32.dll"), II_FOLDER, 0); //Shange to indicate Folder _InsertNewMenuItem(hmenu, _idCmdFirst-NEWITEM_MAX+NEWITEM_FOLDER, &noi); TCHAR szTemp[MAX_PATH+80]; // Add some slop incase we overflow //See if the pidl is folder shortcut and if so get the target path. SHGetTargetFolderPath(_pidlFolder, szTemp, ARRAYSIZE(szTemp)); if (IsLFNDrive(szTemp)) //for short filename servers we don't support anything but new folder { //Insert New Shortcut menu item LoadString(g_hinst, IDS_NEWLINK, noi.szMenuText, ARRAYSIZE(noi.szMenuText)); noi.iImage = Shell_GetCachedImageIndex(TEXT("shell32.dll"), II_LINK, 0); //Shange to indicate Link noi.dwFlags = NEWTYPE_LINK; _InsertNewMenuItem(hmenu, _idCmdFirst-NEWITEM_MAX+NEWITEM_LINK, &noi); //Insert menu item separator AppendMenu(hmenu, MF_SEPARATOR, 0, NULL); // This may take a while, so put up the hourglass DECLAREWAITCURSOR; SetWaitCursor(); // Retrieve extension menu items from cache: // begin synchronize. // if (WAIT_OBJECT_0 == WaitForSingleObject(_hEvent, INFINITE)) { HKEY hkeyShellNew; if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, REGSTR_SESSION_SHELLNEW, 0, KEY_QUERY_VALUE, &hkeyShellNew)) { TCHAR szVal[CCH_KEYMAX]; ULONG cbVal = ARRAYSIZE(szVal); ULONG cbData = sizeof(noi); ULONG dwType = REG_BINARY; for (int i = 0; ERROR_SUCCESS == RegEnumValue(hkeyShellNew, i, szVal, &cbVal, 0, &dwType, (LPBYTE)&noi, &cbData); i++) { if (lstrcmp(szVal, REGVAL_SESSION_SHELLNEW_TIMESTAMP) != 0 && sizeof(noi) == cbData && REG_BINARY == dwType) { SHFILEINFO sfi; _himlSystemImageList = (HIMAGELIST)SHGetFileInfo(noi.szExt, FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(SHFILEINFO), SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_SMALLICON); if (_himlSystemImageList) { //pnoi->himlSmallIcons = sfi.hIcon; noi.iImage = sfi.iIcon; } else { //pnoi->himlSmallIcons = INVALID_HANDLE_VALUE; noi.iImage = -1; } _InsertNewMenuItem(hmenu, _idCmdFirst, &noi); } cbVal = ARRAYSIZE(szVal); cbData = sizeof(noi); dwType = REG_BINARY; } RegCloseKey(hkeyShellNew); } // consolidate menu items following display. ConsolidateMenuItems(TRUE); } ResetWaitCursor(); } return TRUE; } NEWOBJECTINFO *CNewMenu::GetItemData(HMENU hmenu, UINT iItem) { MENUITEMINFO mii; mii.cbSize = sizeof(MENUITEMINFO); mii.fMask = MIIM_DATA | MIIM_STATE; mii.cch = 0; // just in case... if (GetMenuItemInfo(hmenu, iItem, TRUE, &mii)) return (NEWOBJECTINFO *)mii.dwItemData; return NULL; } LPTSTR ProcessArgs(LPTSTR szArgs,...) { LPTSTR szRet; va_list ArgList; va_start(ArgList,szArgs); if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING, szArgs, 0, 0, (LPTSTR)&szRet, 0, &ArgList)) { return NULL; } va_end(ArgList); return szRet; } HRESULT CNewMenu::RunCommand(HWND hwnd, LPCTSTR pszPath, LPCTSTR pszRun) { SHELLEXECUTEINFO ei = { 0 }; TCHAR szCommand[MAX_PATH]; TCHAR szRun[MAX_PATH]; HRESULT hr; SHExpandEnvironmentStrings(pszRun, szCommand, ARRAYSIZE(szCommand)); hr = StringCchCopy(szRun, ARRAYSIZE(szRun), szCommand); if (SUCCEEDED(hr)) { PathRemoveArgs(szCommand); // // Mondo hackitude-o-rama. // // Win95, IE3, SDK: %1 - filename // // IE4: %1 - hwnd, %2 = filename // // So IE4 broken Win95 compat and broke compat with the SDK. // For IE5 we restore compat with Win95 and the SDK, while // still generating an IE4-style command if we detect that the // registry key owner tested with IE4 rather than following the // instructions in the SDK. // // The algorithm is like this: // // If we see a "%2", then use %1 - hwnd, %2 - filename // Otherwise, use %1 - filename, %2 - hwnd // LPTSTR pszArgs = PathGetArgs(szRun); LPTSTR ptszPercent2 = StrStr(pszArgs, TEXT("%2")); if (ptszPercent2 && ptszPercent2[2] != TEXT('!')) { // App wants %1 = hwnd and %2 = filename pszArgs = ProcessArgs(pszArgs, (DWORD_PTR)hwnd, pszPath); } else { // App wants %2 = hwnd and %1 = filename pszArgs = ProcessArgs(pszArgs, pszPath, (DWORD_PTR)hwnd); } if (pszArgs) { HMONITOR hMon = MonitorFromPoint(_ptNewItem, MONITOR_DEFAULTTONEAREST); if (hMon) { ei.fMask |= SEE_MASK_HMONITOR; ei.hMonitor = (HANDLE)hMon; } ei.hwnd = hwnd; ei.lpFile = szCommand; ei.lpParameters = pszArgs; ei.nShow = SW_SHOWNORMAL; ei.cbSize = sizeof(ei); if (ShellExecuteEx(&ei)) hr = S_FALSE; // Return S_FALSE because ShellExecuteEx is not atomic else hr = E_FAIL; LocalFree(pszArgs); } else hr = E_OUTOFMEMORY; } return hr; } HRESULT CNewMenu::CopyTemplate(IStream *pStream, NEWFILEINFO *pnfi) { TCHAR szSrcFolder[MAX_PATH], szSrc[MAX_PATH]; szSrc[0] = 0; // failure here is OK, we will try CSIDL_COMMON_TEMPLATES too. if (SHGetSpecialFolderPath(NULL, szSrcFolder, CSIDL_TEMPLATES, FALSE)) { if (PathCombine(szSrc, szSrcFolder, (LPTSTR)pnfi->lpData)) { if (!PathFileExistsAndAttributes(szSrc, NULL)) szSrc[0] = 0; } else { szSrc[0] = TEXT('\0'); } } if (szSrc[0] == 0) { if (SHGetSpecialFolderPath(NULL, szSrcFolder, CSIDL_COMMON_TEMPLATES, FALSE)) { if (PathCombine(szSrc, szSrcFolder, (LPTSTR)pnfi->lpData)) { if (!PathFileExistsAndAttributes(szSrc, NULL)) szSrc[0] = 0; } else { szSrc[0] = TEXT('\0'); } } } if (szSrc[0] == 0) { // work around CSIDL_TEMPLATES not being setup right or // templates that are left in the old %windir%\shellnew location UINT cch = GetWindowsDirectory(szSrcFolder, ARRAYSIZE(szSrcFolder)); if (cch != 0 && cch < ARRAYSIZE(szSrcFolder)) { if (PathAppend(szSrcFolder, TEXT("ShellNew"))) { // note: if the file spec is fully qualified szSrcFolder is ignored if (PathCombine(szSrc, szSrcFolder, (LPTSTR)pnfi->lpData)) { // groovy } else { szSrc[0] = TEXT('\0'); } } else { szSrc[0] = TEXT('\0'); } } else { szSrc[0] = TEXT('\0'); } } // // we just allow a null file to be created when the copy fails. // this is for appcompat with office97. they fail to copy winword8.doc // anywhere on the system. on win2k we succeed anyway with an empty file. // return SUCCEEDED(StgCopyFileToStream(szSrc, pStream)) ? S_OK : S_FALSE; }