mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4919 lines
143 KiB
4919 lines
143 KiB
// shell link stuff
|
|
//
|
|
// broken link to file senarios:
|
|
// file on non removeable volume:
|
|
// file renamed in place creation date
|
|
// file moved on same volume find by name or create date
|
|
//
|
|
// link to root (drives)
|
|
// verify volume type
|
|
//
|
|
|
|
#include "shellprv.h"
|
|
#pragma hdrstop
|
|
|
|
#include "lnktrack.h"
|
|
|
|
#ifdef ENABLE_TRACK
|
|
#include "tracker.h"
|
|
int g_fNewTrack = FALSE;
|
|
#endif
|
|
|
|
// this turns on unicode support for the string fields in the link persistant data
|
|
// this does not include UNICODE support in the pild, or the LINKINFO structure,
|
|
// that work needs to be done independantly
|
|
//
|
|
// #define TEST_UNICODE_LINKS 1 // make sure we can read unicode links
|
|
|
|
// #define TEST_EXTRA_DATA
|
|
// this is to make sure the link file format is extensible
|
|
#ifdef TEST_EXTRA_DATA
|
|
BOOL bTestExtra = FALSE;
|
|
#endif // TEST_EXTRA_DATA
|
|
|
|
|
|
IPersistFileVtbl c_PersistFile_Vtbl; // forward declaration
|
|
IPersistStreamVtbl c_PersistStream_Vtbl; // forward declaration
|
|
extern IShellExtInitVtbl c_ShellExtInit_Vtbl; // forward declaration
|
|
extern IContextMenu2Vtbl c_ContextMenu_Vtbl; // forward declaration
|
|
IDropTargetVtbl c_DropTarget_Vtbl; // forward declaration
|
|
|
|
#ifdef ENABLE_TRACK
|
|
extern IShellLinkTrackerVtbl c_ShellLinkTracker_Vtbl; // forward declaration
|
|
#endif
|
|
|
|
HRESULT CShellLink_Resolve(IShellLink *psl, HWND hwnd, DWORD fFlags);
|
|
HRESULT CShellLink_PS_Load(IPersistStream *pps, IStream *pstm);
|
|
HRESULT CShellLink_PS_Save(IPersistStream *pps, IStream *pstm, BOOL fClearDirty);
|
|
HRESULT CShellLink_Save(IPersistFile *psf, LPCOLESTR pwszFile, BOOL fRemember);
|
|
HRESULT Link_LoadFromFile(CShellLink *this, LPCTSTR pszPath);
|
|
HRESULT Link_SaveToFile(CShellLink *this, LPTSTR pszPath, BOOL fRemember);
|
|
BOOL Link_ResolveRelative(CShellLink *this, LPTSTR pszPath);
|
|
HRESULT PSLoadThroughFileCache(IPersistStream *pps, LPCTSTR pszPath);
|
|
HRESULT CShellLink_GetDropTarget(CShellLink *this, IDropTarget **ppdt);
|
|
void PSUpdateFileCache(IPersistStream *pps, LPCTSTR pszPath);
|
|
|
|
void Link_AddExtraDataSection( CShellLink *this, DWORD UNALIGNED * lpData );
|
|
LPVOID Link_ReadExtraDataSection( CShellLink *this, DWORD dwSig );
|
|
void Link_RemoveExtraDataSection( CShellLink *this, DWORD dwSig );
|
|
|
|
|
|
/*
|
|
** Link_FSEvent()
|
|
**
|
|
** watch FS events, and make sure our cache gets updated
|
|
*/
|
|
void Link_FSEvent(LONG lEvent, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
|
|
if (pidl == NULL || ILIsEmpty(pidl))
|
|
return;
|
|
|
|
#ifdef FULL_DEBUG
|
|
|
|
if (lEvent != SHCNE_UPDATEIMAGE && lEvent != SHCNE_FREESPACE)
|
|
{
|
|
SHGetPathFromIDList(pidl, szPath);
|
|
if (PathIsLink(szPath))
|
|
{
|
|
switch(lEvent)
|
|
{
|
|
case SHCNE_RENAMEITEM: DebugMsg(DM_TRACE, TEXT("LinkFSEvent: rename %s"), szPath); break;
|
|
case SHCNE_DELETE: DebugMsg(DM_TRACE, TEXT("LinkFSEvent: delete %s"), szPath); break;
|
|
case SHCNE_UPDATEITEM: DebugMsg(DM_TRACE, TEXT("LinkFSEvent: update %s"), szPath); break;
|
|
default: DebugMsg(DM_TRACE, TEXT("LinkFSEvent: %08lX %s"), lEvent, szPath); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
switch (lEvent)
|
|
{
|
|
case SHCNE_RENAMEITEM:
|
|
case SHCNE_DELETE:
|
|
case SHCNE_UPDATEITEM:
|
|
if (SHGetPathFromIDList(pidl, szPath) && PathIsLink(szPath))
|
|
{
|
|
PSUpdateFileCache(NULL, szPath);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
DWORD _Link_GUO_Worker( LPVOID lpv )
|
|
{
|
|
LPTSTR lpszTarget = (LPTSTR)lpv;
|
|
BOOL fRootExists;
|
|
TCHAR szRoot[MAX_PATH];
|
|
|
|
lstrcpyn(szRoot,lpszTarget,ARRAYSIZE(szRoot));
|
|
PathStripToRoot(szRoot);
|
|
|
|
if (PathIsUNC( szRoot ))
|
|
{
|
|
fRootExists = NetPathExists( szRoot, NULL );
|
|
|
|
}
|
|
else
|
|
{
|
|
fRootExists = PathFileExists( szRoot );
|
|
}
|
|
LocalFree((HLOCAL)lpszTarget);
|
|
|
|
return fRootExists;
|
|
}
|
|
|
|
static DWORD g_NetLinkTimeout = (DWORD)-1;
|
|
const TCHAR c_szNetLinkTimeout[] = TEXT("NetLinkTimeout");
|
|
|
|
DWORD _GetNetLinkTimeout( REFIID riid )
|
|
{
|
|
DWORD dwNetLinkTimeout;
|
|
|
|
if (g_NetLinkTimeout == -1)
|
|
{
|
|
HKEY hkey = SHGetExplorerHkey(HKEY_CURRENT_USER, FALSE);
|
|
|
|
if (hkey)
|
|
{
|
|
DWORD dwType;
|
|
DWORD dwSize = SIZEOF(dwNetLinkTimeout);
|
|
|
|
// read in the registry value
|
|
if (RegQueryValueEx(hkey, c_szNetLinkTimeout, NULL, &dwType,
|
|
(LPBYTE)&dwNetLinkTimeout, &dwSize) == ERROR_SUCCESS)
|
|
{
|
|
g_NetLinkTimeout = dwNetLinkTimeout;
|
|
return g_NetLinkTimeout;
|
|
}
|
|
}
|
|
g_NetLinkTimeout = 0;
|
|
}
|
|
|
|
if (g_NetLinkTimeout == 0)
|
|
{
|
|
#define TARGET_GUO_DEFAULT_TIMEOUT 7500
|
|
#define TARGET_GUO_DT_DEFAULT_TIMEOUT 1000
|
|
|
|
if (IsEqualIID(riid, &IID_IDropTarget))
|
|
dwNetLinkTimeout = TARGET_GUO_DT_DEFAULT_TIMEOUT;
|
|
else
|
|
dwNetLinkTimeout = TARGET_GUO_DEFAULT_TIMEOUT;
|
|
|
|
return dwNetLinkTimeout;
|
|
}
|
|
return g_NetLinkTimeout;
|
|
}
|
|
|
|
//
|
|
// CShellLink::GetUIObject (non-virtual)
|
|
//
|
|
// This function returns the specified UI object from the link source.
|
|
//
|
|
// Parameters:
|
|
// this -- Specifies the shell link object
|
|
// hwnd -- optional hwnd for UI (for drop target)
|
|
// riid -- Specifies the interface (IID_IDropTarget, IID_IExtractIcon, IID_IContextMenu, ...)
|
|
// ppvOut -- Specifies the place to return the pointer.
|
|
//
|
|
// Notes:
|
|
// Don't put smart-resolving code here. Such a thing should be done
|
|
// BEFORE calling this cuntion.
|
|
//
|
|
HRESULT Link_GetUIObject(CShellLink *this, HWND hwnd, REFIID riid, void **ppvOut)
|
|
{
|
|
HRESULT hres;
|
|
IShellFolder *psf;
|
|
LPITEMIDLIST pidl;
|
|
TCHAR szPath[MAX_PATH+1];
|
|
BOOL fPossiblySlow;
|
|
BOOL fSimpleIDList = FALSE;
|
|
|
|
if (this->sld.dwFlags & SLDF_HAS_EXP_SZ) {
|
|
LPEXP_SZ_LINK lpData;
|
|
|
|
for( lpData = (LPEXP_SZ_LINK)this->pExtraData;
|
|
lpData && lpData->cbSize && (lpData->dwSignature!=EXP_SZ_LINK_SIG);
|
|
lpData = (LPEXP_SZ_LINK)(((LPBYTE)lpData) + lpData->cbSize)
|
|
);
|
|
|
|
if (lpData && lpData->cbSize) {
|
|
#ifdef UNICODE
|
|
{
|
|
TCHAR szTmp[MAX_PATH];
|
|
ualstrcpy(szTmp, lpData->swzTarget);
|
|
ExpandEnvironmentStrings( szTmp, szPath, MAX_PATH );
|
|
}
|
|
#else
|
|
ExpandEnvironmentStrings( lpData->szTarget, szPath, MAX_PATH );
|
|
#endif
|
|
szPath[ MAX_PATH-1 ] = TEXT('\0');
|
|
|
|
|
|
if (this->pidl)
|
|
ILFree( this->pidl );
|
|
|
|
fSimpleIDList = PathIsUNC(szPath);
|
|
|
|
if (!fSimpleIDList)
|
|
fSimpleIDList = IsRemoteDrive(DRIVEID(szPath));
|
|
|
|
if (fSimpleIDList)
|
|
this->pidl = SHSimpleIDListFromPath( szPath );
|
|
else
|
|
this->pidl = ILCreateFromPath( szPath );
|
|
|
|
} else {
|
|
|
|
DebugMsg(DM_TRACE, TEXT("Link_GetUIObject called on EXP_SZ link without EXP_SZ section!"));
|
|
*ppvOut = NULL;
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((this->pidl == NULL) && (this->sld.dwFlags & SLDF_HAS_RELPATH))
|
|
{
|
|
Link_ResolveRelative( this, szPath );
|
|
}
|
|
|
|
if (this->pidl == NULL)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Link_GetUIObject called on un-initialized (empty) link"));
|
|
*ppvOut = NULL;
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (SHGetPathFromIDList(this->pidl,szPath) && szPath[0] != TEXT('\0'))
|
|
{
|
|
fPossiblySlow = PathIsUNC(szPath);
|
|
|
|
if (!fPossiblySlow)
|
|
fPossiblySlow = IsRemoteDrive(DRIVEID(szPath));
|
|
|
|
if (fPossiblySlow)
|
|
{
|
|
HANDLE hThread;
|
|
DWORD dwID;
|
|
LPTSTR lpszTarget = LocalAlloc(LPTR, (lstrlen(szPath) + 1) * SIZEOF(TCHAR));
|
|
|
|
if (lpszTarget)
|
|
{
|
|
lstrcpy(lpszTarget, szPath);
|
|
|
|
hThread = CreateThread(NULL, 0, _Link_GUO_Worker,
|
|
(LPVOID)lpszTarget, 0, &dwID);
|
|
if (NULL == hThread)
|
|
{
|
|
LocalFree((HLOCAL)lpszTarget);
|
|
}
|
|
else
|
|
{
|
|
DWORD dwRetVal;
|
|
DWORD dwWaitResult;
|
|
|
|
dwWaitResult = WaitForSingleObject(hThread,
|
|
_GetNetLinkTimeout( riid ));
|
|
|
|
if (WAIT_OBJECT_0 != dwWaitResult)
|
|
{
|
|
CloseHandle(hThread);
|
|
return E_FAIL;
|
|
}
|
|
GetExitCodeThread(hThread, &dwRetVal);
|
|
|
|
if (!dwRetVal)
|
|
{
|
|
CloseHandle(hThread);
|
|
return E_FAIL;
|
|
}
|
|
CloseHandle(hThread);
|
|
}
|
|
|
|
if (fSimpleIDList)
|
|
{
|
|
if (this->pidl)
|
|
ILFree( this->pidl );
|
|
|
|
this->pidl = ILCreateFromPath( szPath );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
hres = SHBindToIDListParent(this->pidl, &IID_IShellFolder, &psf, &pidl);
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
hres = psf->lpVtbl->GetUIObjectOf(psf, hwnd, 1, &pidl, riid, NULL, ppvOut);
|
|
psf->lpVtbl->Release(psf);
|
|
}
|
|
return hres;
|
|
}
|
|
|
|
|
|
|
|
|
|
HRESULT CShellLink_QueryInterface(IShellLink *psl, REFIID riid, void **ppvObj)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
HRESULT hres = E_NOINTERFACE;
|
|
#if 0
|
|
TCHAR szGUID[GUIDSTR_MAX];
|
|
StringFromGUID2A(riid, szGUID, GUIDSTR_MAX);
|
|
DebugMsg(DM_TRACE, TEXT("CShellLink::QueryInterface %s"), szGUID);
|
|
#endif
|
|
|
|
*ppvObj = NULL;
|
|
|
|
if (IsEqualIID(riid, &IID_IShellLink) || IsEqualIID(riid, &IID_IUnknown))
|
|
{
|
|
*ppvObj = this;
|
|
this->cRef++;
|
|
return S_OK;
|
|
}
|
|
#ifdef USE_DATA_OBJ
|
|
else if (IsEqualIID(riid, &IID_IDataObject))
|
|
{
|
|
Assert(this->fDataAlreadyResolved == FALSE);
|
|
|
|
*ppvObj = &this->dobj;
|
|
this->cRef++;
|
|
return S_OK;
|
|
}
|
|
#endif // USE_DATA_OBJ
|
|
else if (IsEqualIID(riid, &IID_IPersistFile))
|
|
{
|
|
*ppvObj = &this->pf;
|
|
this->cRef++;
|
|
return S_OK;
|
|
}
|
|
else if (IsEqualIID(riid, &IID_IPersistStream))
|
|
{
|
|
*ppvObj = &this->ps;
|
|
this->cRef++;
|
|
return S_OK;
|
|
}
|
|
else if (IsEqualIID(riid, &IID_IShellExtInit))
|
|
{
|
|
*ppvObj = &this->si;
|
|
this->cRef++;
|
|
return S_OK;
|
|
}
|
|
else if (IsEqualIID(riid, &IID_IContextMenu) ||
|
|
IsEqualIID(riid, &IID_IContextMenu2))
|
|
{
|
|
*ppvObj = &this->cm;
|
|
this->cRef++;
|
|
return S_OK;
|
|
}
|
|
else if (IsEqualIID(riid, &IID_IDropTarget))
|
|
{
|
|
hres = CShellLink_GetDropTarget(this, (IDropTarget**)ppvObj);
|
|
}
|
|
else if (IsEqualIID(riid, &IID_IExtractIcon)
|
|
#ifdef UNICODE
|
|
|| IsEqualIID(riid, &IID_IExtractIconA)
|
|
#endif
|
|
)
|
|
{
|
|
//
|
|
// we have a few cases:
|
|
//
|
|
// is there a icon location?
|
|
//
|
|
// is it exactly the same as the destination of the link?
|
|
// dont use the location, forward to PIDL
|
|
// else
|
|
// use the icon location in the link.
|
|
//
|
|
// no icon location so forward IExtractIcon to PIDL.
|
|
//
|
|
// no PIDL, or error return a generic DOC icon
|
|
//
|
|
if (this->pszIconLocation && this->pszIconLocation[0])
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
|
|
if (this->sld.iIcon == 0 &&
|
|
this->pidl && SHGetPathFromIDList(this->pidl, szPath) &&
|
|
lstrcmpi(szPath, this->pszIconLocation) == 0)
|
|
{
|
|
hres = Link_GetUIObject(this, NULL, riid, ppvObj);
|
|
}
|
|
else
|
|
{
|
|
hres = SHCreateDefExtIcon(this->pszIconLocation, this->sld.iIcon,
|
|
-1, GIL_PERINSTANCE,(IExtractIcon **)ppvObj);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hres = Link_GetUIObject(this, NULL, riid, ppvObj);
|
|
}
|
|
|
|
if (FAILED(hres))
|
|
{
|
|
hres = SHCreateDefExtIcon(NULL, II_DOCNOASSOC,
|
|
-1, 0, (IExtractIcon **)ppvObj);
|
|
}
|
|
#ifdef UNICODE
|
|
if (SUCCEEDED(hres) && IsEqualIID(riid, &IID_IExtractIconA))
|
|
{
|
|
LPEXTRACTICON pxicon = *ppvObj;
|
|
hres = pxicon->lpVtbl->QueryInterface(pxicon,riid,ppvObj);
|
|
pxicon->lpVtbl->Release(pxicon);
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef UNICODE
|
|
else if (IsEqualIID(riid, &IID_IShellLinkA))
|
|
{
|
|
*ppvObj = &this->slA;
|
|
this->cRef++;
|
|
return NOERROR;
|
|
}
|
|
#endif
|
|
#ifdef ENABLE_TRACK
|
|
else if( IsEqualIID(riid, &IID_IShellLinkTracker))
|
|
{
|
|
*ppvObj = &this->slt;
|
|
this->cRef++;
|
|
return NOERROR;
|
|
}
|
|
#endif // ENABLE_TRACK
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CShellLink::QueryInterface failed"));
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
ULONG CShellLink_AddRef(IShellLink *psl)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
|
|
this->cRef++;
|
|
|
|
return this->cRef;
|
|
}
|
|
|
|
// put the linkinfo into the default empty state
|
|
// free all the data in this link an reset all state to empty
|
|
|
|
void Link_ResetPersistData(CShellLink *this)
|
|
{
|
|
if (this->pidl)
|
|
{
|
|
ILFree(this->pidl);
|
|
this->pidl = NULL;
|
|
}
|
|
|
|
if (this->pli)
|
|
{
|
|
LocalFree((HLOCAL)this->pli);
|
|
this->pli = NULL;
|
|
}
|
|
|
|
Str_SetPtr(&this->pszName, NULL);
|
|
Str_SetPtr(&this->pszRelPath, NULL);
|
|
Str_SetPtr(&this->pszWorkingDir, NULL);
|
|
Str_SetPtr(&this->pszArgs, NULL);
|
|
Str_SetPtr(&this->pszIconLocation, NULL);
|
|
|
|
if (this->pExtraData)
|
|
{
|
|
LocalFree((HLOCAL)this->pExtraData);
|
|
this->pExtraData = NULL;
|
|
}
|
|
|
|
#ifdef ENABLE_TRACK
|
|
_fmemset(this->ptracker, 0, sizeof(*(this->ptracker)));
|
|
#endif
|
|
|
|
// init data members. all others are zero inited
|
|
_fmemset(&this->sld, 0, SIZEOF(this->sld));
|
|
|
|
this->sld.iShowCmd = SW_SHOWNORMAL;
|
|
}
|
|
|
|
ULONG CShellLink_Release(IShellLink *psl)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
|
|
this->cRef--;
|
|
if (this->cRef > 0)
|
|
return this->cRef;
|
|
|
|
Link_ResetPersistData(this); // free all data
|
|
|
|
Str_SetPtr(&this->pszCurFile, NULL);
|
|
Str_SetPtr(&this->pszRelSource, NULL);
|
|
|
|
if (this->pcmTarget)
|
|
this->pcmTarget->lpVtbl->Release(this->pcmTarget);
|
|
|
|
if (this->pdtSrc)
|
|
this->pdtSrc->lpVtbl->Release(this->pdtSrc);
|
|
|
|
#ifdef ENABLE_TRACK
|
|
if (this->ptracker)
|
|
{
|
|
LocalFree((HLOCAL)this->ptracker);
|
|
this->ptracker = NULL;
|
|
}
|
|
#endif
|
|
|
|
LocalFree((HLOCAL)this);
|
|
|
|
return 0;
|
|
}
|
|
|
|
HRESULT Link_LoadFromPath(LPTSTR szPath, IShellLink **ppsl)
|
|
{
|
|
IShellLink *psl;
|
|
HRESULT hres = CShellLink_CreateInstance(NULL, &IID_IShellLink, &psl);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
WCHAR wszPath[MAX_PATH];
|
|
IPersistFile *ppf;
|
|
|
|
psl->lpVtbl->QueryInterface(psl, &IID_IPersistFile, &ppf);
|
|
|
|
StrToOleStr(wszPath, szPath);
|
|
hres = ppf->lpVtbl->Load(ppf, wszPath, 0);
|
|
ppf->lpVtbl->Release(ppf);
|
|
|
|
if (FAILED(hres))
|
|
{
|
|
psl->lpVtbl->Release(psl);
|
|
psl = NULL;
|
|
}
|
|
}
|
|
*ppsl = psl;
|
|
return hres;
|
|
}
|
|
|
|
BOOL IsEqualFindData(CShellLink *this, const WIN32_FIND_DATA *pfd)
|
|
{
|
|
return (pfd->dwFileAttributes == this->sld.dwFileAttributes) &&
|
|
(CompareFileTime(&pfd->ftCreationTime, &this->sld.ftCreationTime) == 0) &&
|
|
(CompareFileTime(&pfd->ftLastWriteTime, &this->sld.ftLastWriteTime) == 0) &&
|
|
(pfd->nFileSizeLow == this->sld.nFileSizeLow);
|
|
}
|
|
|
|
void SetFindData(CShellLink *this, const WIN32_FIND_DATA *pfd, const TCHAR *ptszPath)
|
|
{
|
|
#ifdef ENABLE_TRACK
|
|
//
|
|
// initialize the tracker with identity information
|
|
//
|
|
if (g_fNewTrack && ptszPath && S_OK == Tracker_InitFromPath(this->ptracker, ptszPath))
|
|
this->bDirty = this->bDirty || Tracker_IsDirty(this->ptracker);
|
|
#else
|
|
UNREFERENCED_PARAMETER(ptszPath);
|
|
#endif
|
|
|
|
if (!IsEqualFindData(this, pfd))
|
|
{
|
|
this->sld.dwFileAttributes = pfd->dwFileAttributes;
|
|
this->sld.ftCreationTime = pfd->ftCreationTime;
|
|
this->sld.ftLastAccessTime = pfd->ftLastAccessTime;
|
|
this->sld.ftLastWriteTime = pfd->ftLastWriteTime;
|
|
this->sld.nFileSizeLow = pfd->nFileSizeLow;
|
|
this->bDirty = TRUE;
|
|
}
|
|
}
|
|
|
|
void GetFindData(CShellLink *this, WIN32_FIND_DATA *pfd)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
|
|
pfd->dwFileAttributes = this->sld.dwFileAttributes;
|
|
pfd->ftCreationTime = this->sld.ftCreationTime;
|
|
pfd->ftLastAccessTime = this->sld.ftLastAccessTime;
|
|
pfd->ftLastWriteTime = this->sld.ftLastWriteTime;
|
|
pfd->nFileSizeLow = this->sld.nFileSizeLow;
|
|
pfd->nFileSizeHigh = 0;
|
|
SHGetPathFromIDList(this->pidl, szPath);
|
|
|
|
// no one should call this on a pidl without a path
|
|
Assert(szPath[0]);
|
|
|
|
lstrcpy(pfd->cFileName, PathFindFileName(szPath));
|
|
}
|
|
|
|
#if 0
|
|
void DumpPLI(PCLINKINFO pli)
|
|
{
|
|
LPCTSTR p;
|
|
|
|
if (!pli)
|
|
return;
|
|
|
|
DebugMsg(DM_TRACE, TEXT("DumpPLI:"));
|
|
|
|
if (g_pfnGetLinkInfoData(pli, LIDT_VOLUME_SERIAL_NUMBER, &p))
|
|
DebugMsg(DM_TRACE, TEXT("\tSerial #\t%d"), *p);
|
|
|
|
if (g_pfnGetLinkInfoData(pli, LIDT_DRIVE_TYPE, &p))
|
|
DebugMsg(DM_TRACE, TEXT("\tDrive Type\t%d"), *p);
|
|
|
|
if (g_pfnGetLinkInfoData(pli, LIDT_VOLUME_LABEL, &p))
|
|
DebugMsg(DM_TRACE, TEXT("\tLabel\t%s"), p);
|
|
|
|
if (g_pfnGetLinkInfoData(pli, LIDT_LOCAL_BASE_PATH, &p))
|
|
DebugMsg(DM_TRACE, TEXT("\tBase Path\t%s"), p);
|
|
|
|
if (g_pfnGetLinkInfoData(pli, LIDT_NET_RESOURCE, &p))
|
|
DebugMsg(DM_TRACE, TEXT("\tNet Res\t%s"), p);
|
|
|
|
if (g_pfnGetLinkInfoData(pli, LIDT_COMMON_PATH_SUFFIX, &p))
|
|
DebugMsg(DM_TRACE, TEXT("\tPath Sufix\t%s"), p);
|
|
}
|
|
#else
|
|
#define DumpPLI(p)
|
|
#endif
|
|
|
|
HRESULT CShellLink_GetPath(IShellLink *psl, LPTSTR pszFile, int cchMaxPath, WIN32_FIND_DATA *pfd, DWORD fFlags)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
VDATEINPUTBUF(pszFile, TCHAR, cchMaxPath);
|
|
|
|
DumpPLI(this->pli);
|
|
|
|
|
|
if ((this->pidl == NULL) && (this->sld.dwFlags & SLDF_HAS_RELPATH))
|
|
{
|
|
TCHAR szTmp[ MAX_PATH +1 ];
|
|
|
|
Link_ResolveRelative( this, szTmp );
|
|
}
|
|
|
|
if (!this->pidl || !SHGetPathFromIDListEx(this->pidl, szPath, (fFlags & SLGP_SHORTPATH) ? GPFIDL_ALTNAME : 0))
|
|
szPath[0] = 0;
|
|
|
|
if (this->sld.dwFlags & SLDF_HAS_EXP_SZ)
|
|
{
|
|
LPEXP_SZ_LINK lpExtraData;
|
|
DWORD dwSize = 0;
|
|
|
|
// Special case where we grab the Target name from
|
|
// the extra data section of the link rather than from
|
|
// the pidl. We do this after we grab the name from the pidl
|
|
// so that if we fail, then there is still some hope that a
|
|
// name can be returned.
|
|
|
|
for( lpExtraData = (LPEXP_SZ_LINK)this->pExtraData;
|
|
lpExtraData;
|
|
lpExtraData = (LPEXP_SZ_LINK)(((LPBYTE)lpExtraData) + dwSize))
|
|
{
|
|
dwSize = lpExtraData->cbSize;
|
|
if (dwSize)
|
|
{
|
|
if (lpExtraData->dwSignature == EXP_SZ_LINK_SIG)
|
|
{
|
|
#ifdef UNICODE
|
|
ualstrcpy( szPath, lpExtraData->swzTarget );
|
|
#else
|
|
lstrcpyA( szPath, lpExtraData->szTarget );
|
|
#endif
|
|
DebugMsg( DM_TRACE, TEXT("CShellLink::GetPath() %s (from xtra data)"), szPath );
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pszFile)
|
|
{
|
|
lstrcpyn(pszFile, szPath, cchMaxPath);
|
|
// DebugMsg(DM_TRACE, "CShellLink::GetPath() %s", szPath);
|
|
}
|
|
|
|
if (pfd)
|
|
{
|
|
if (szPath[0])
|
|
GetFindData(this, pfd);
|
|
else
|
|
_fmemset(pfd, 0, SIZEOF(*pfd));
|
|
}
|
|
|
|
return (this->pidl != NULL && szPath[0] == 0) ? S_FALSE : S_OK;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
#define DumpTimes(ftCreate, ftAccessed, ftWrite) \
|
|
DebugMsg(DM_TRACE, TEXT("create %8x%8x"), ftCreate.dwLowDateTime, ftCreate.dwHighDateTime); \
|
|
DebugMsg(DM_TRACE, TEXT("accessed %8x%8x"), ftAccessed.dwLowDateTime, ftAccessed.dwHighDateTime); \
|
|
DebugMsg(DM_TRACE, TEXT("write %8x%8x"), ftWrite.dwLowDateTime, ftWrite.dwHighDateTime);
|
|
|
|
#else
|
|
|
|
#define DumpTimes(ftCreate, ftAccessed, ftWrite)
|
|
|
|
#endif
|
|
|
|
void CheckAndFixNullCreateTime(LPCTSTR pszFile, WIN32_FIND_DATA *pfd)
|
|
{
|
|
if (IsNullTime(&pfd->ftCreationTime))
|
|
{
|
|
#ifdef UNICODE
|
|
HFILE hfile = (HFILE)CreateFile( pszFile,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL);
|
|
#else
|
|
HFILE hfile = _lopen(pszFile, OF_READWRITE | OF_SHARE_DENY_NONE);
|
|
#endif
|
|
|
|
DebugMsg(DM_TRACE, TEXT("ShellLink::SetPath, NULL create time"));
|
|
// this file has a bogus create time, set it to the last accessed time
|
|
|
|
if (hfile != HFILE_ERROR)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("ShellLink::SetPath, pfd times"));
|
|
DumpTimes(pfd->ftCreationTime, pfd->ftLastAccessTime, pfd->ftLastWriteTime);
|
|
|
|
if (SetFileTime((HANDLE)hfile, &pfd->ftLastWriteTime, NULL, NULL))
|
|
{
|
|
// BUGBUG: get the time back to make sure we match the precision of the file system
|
|
pfd->ftCreationTime = pfd->ftLastWriteTime; // patch this up
|
|
#ifdef DEBUG
|
|
{
|
|
FILETIME ftCreate, ftAccessed, ftWrite;
|
|
GetFileTime((HANDLE)hfile, &ftCreate, &ftAccessed, &ftWrite);
|
|
AssertMsg(CompareFileTime(&ftCreate, &pfd->ftCreationTime) == 0, TEXT("create times don't match"));
|
|
DumpTimes(ftCreate, ftAccessed, ftWrite);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("ShellLink::SetPath, unable to set create time"));
|
|
}
|
|
_lclose(hfile);
|
|
}
|
|
}
|
|
}
|
|
|
|
HRESULT CShellLink_GetIDList(IShellLink *psl, LPITEMIDLIST *ppidl)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
|
|
if (this->pidl)
|
|
*ppidl = ILClone(this->pidl);
|
|
else
|
|
*ppidl = NULL;
|
|
return S_OK;
|
|
}
|
|
|
|
BOOL DifferentStrings(LPCTSTR psz1, LPCTSTR psz2)
|
|
{
|
|
if (psz1 && psz2)
|
|
return lstrcmp(psz1, psz2);
|
|
else
|
|
return (!psz1 && psz2) || (psz1 && !psz2);
|
|
}
|
|
|
|
// set the relative path
|
|
// in:
|
|
// pszRelSource fully qualified path to a file (must be file, not directory)
|
|
// to be used to find a relative path with the link target.
|
|
//
|
|
// returns:
|
|
// S_OK relative path is set
|
|
// S_FALSE pszPathRel is not relative to the destination or the
|
|
// destionation is not a file (could be link to a pidl only)
|
|
// notes:
|
|
// set the dirty bit if this is a new relative path
|
|
//
|
|
|
|
HRESULT Link_SetRelativePath(CShellLink *this, LPCTSTR pszRelSource)
|
|
{
|
|
TCHAR szPath[MAX_PATH], szDest[MAX_PATH];
|
|
|
|
Assert(!PathIsRelative(pszRelSource));
|
|
|
|
if (this->pidl == NULL || !SHGetPathFromIDList(this->pidl, szDest))
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("SetRelative called on non path link"));
|
|
return S_FALSE;
|
|
}
|
|
|
|
// assume pszRelSource is a file, not a directory
|
|
if (PathRelativePathTo(szPath, pszRelSource, 0, szDest, this->sld.dwFileAttributes))
|
|
{
|
|
pszRelSource = szPath;
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("paths are not relative"));
|
|
pszRelSource = NULL; // clear the stored relative path below
|
|
}
|
|
|
|
if (DifferentStrings(this->pszRelPath, pszRelSource))
|
|
{
|
|
this->bDirty = TRUE;
|
|
|
|
Str_SetPtr(&this->pszRelPath, pszRelSource);
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
// set the relative path, this is used before a link is saved so we know what
|
|
// we should use to store the link relative to as well as before the link is resolved
|
|
// so we know the new path to use with the saved relative path.
|
|
//
|
|
// in:
|
|
// pszPathRel path to make link target relative to, must be a path to
|
|
// a file, not a directory.
|
|
//
|
|
// dwReserved must be 0
|
|
//
|
|
// returns:
|
|
// S_OK relative path is set
|
|
//
|
|
HRESULT CShellLink_SetRelativePath(IShellLink *psl, LPCTSTR pszPathRel, DWORD dwReserved)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
|
|
if (dwReserved != 0)
|
|
return E_FAIL;
|
|
|
|
Str_SetPtr(&this->pszRelSource, pszPathRel);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*
|
|
** CopyLinkInfo()
|
|
**
|
|
** Copies LinkInfo into local memory.
|
|
**
|
|
** Arguments: pcliSrc - source LinkInfo
|
|
** ppliDest - pointer to PLINKINFO to be filled in with pointer
|
|
** to local copy
|
|
**
|
|
** Returns: HRESULT
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PLINKINFO CopyLinkInfo(PCLINKINFO pcliSrc)
|
|
{
|
|
DWORD dwSize;
|
|
PLINKINFO pli;
|
|
|
|
Assert(pcliSrc);
|
|
|
|
dwSize = *(UNALIGNED DWORD *)pcliSrc; // size of this thing
|
|
pli = (void*)LocalAlloc(LPTR, dwSize); // make a copy
|
|
if (pli)
|
|
CopyMemory(pli, pcliSrc, dwSize);
|
|
|
|
return pli;
|
|
}
|
|
|
|
// Creates LinkInfo for a CShellLink instance that does not yet have one
|
|
// create the LinkInfo from the pidl (assumed to be to a path) for this link
|
|
//
|
|
// returns:
|
|
//
|
|
// success, pointer to the LINKINFO
|
|
// NULL this link does not have LINKINFO
|
|
|
|
PLINKINFO GetLinkInfo(CShellLink *this)
|
|
{
|
|
PLINKINFO pliNew;
|
|
TCHAR szPath[MAX_PATH];
|
|
|
|
if ((this->pidl == NULL) && (this->sld.dwFlags & SLDF_HAS_RELPATH))
|
|
{
|
|
TCHAR szTmp[ MAX_PATH +1 ];
|
|
|
|
Link_ResolveRelative( this, szTmp );
|
|
}
|
|
|
|
if (!this->pidl || !SHGetPathFromIDList(this->pidl, szPath))
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("GetLinkInfo called for non file link"));
|
|
return NULL;
|
|
}
|
|
|
|
if (this->pli)
|
|
{
|
|
LocalFree((HLOCAL)this->pli);
|
|
this->pli = NULL;
|
|
}
|
|
|
|
if (this->sld.dwFlags & SLDF_FORCE_NO_LINKINFO)
|
|
{
|
|
DebugMsg( DM_TRACE, TEXT("Found a labotimized link, not creating LINKINFO"));
|
|
return NULL;
|
|
}
|
|
|
|
|
|
if (g_pfnCreateLinkInfo(szPath, &pliNew))
|
|
{
|
|
this->pli = CopyLinkInfo(pliNew);
|
|
this->bDirty = TRUE;
|
|
|
|
g_pfnDestroyLinkInfo(pliNew);
|
|
}
|
|
|
|
return this->pli;
|
|
}
|
|
|
|
void PathGetRelative(LPTSTR pszPath, LPCTSTR pszFrom, DWORD dwAttrFrom, LPCTSTR pszRel)
|
|
{
|
|
TCHAR szRoot[MAX_PATH];
|
|
|
|
lstrcpy(szRoot, pszFrom);
|
|
if (!(dwAttrFrom & FILE_ATTRIBUTE_DIRECTORY))
|
|
PathRemoveFileSpec(szRoot);
|
|
|
|
Assert(PathIsRelative(pszRel));
|
|
|
|
PathCombine(pszPath, szRoot, pszRel);
|
|
}
|
|
|
|
// #define TEST_RPT
|
|
|
|
#ifdef TEST_RPT
|
|
int OldPathCommonPrefix(LPCTSTR pszFile1, LPCTSTR pszFile2, LPTSTR pszPath)
|
|
{
|
|
TCHAR achFile1[MAX_PATH];
|
|
TCHAR achFile2[MAX_PATH];
|
|
int ichRet = 0;
|
|
LPCTSTR pszT1, pszT2, pszNext1, pszNext2;
|
|
TCHAR ch1, ch2;
|
|
|
|
lstrcpy(achFile1, pszFile1);
|
|
lstrcpy(achFile2, pszFile2);
|
|
|
|
CharUpper(achFile1);
|
|
CharUpper(achFile2);
|
|
|
|
for (pszT1 = achFile1, pszT2 = achFile2 ;
|
|
(ch1 = *pszT1) && (ch2 = *pszT2) ;
|
|
pszT1 = pszNext1, pszT2 = pszNext2)
|
|
{
|
|
pszNext1 = CharNext(pszT1);
|
|
pszNext2 = CharNext(pszT2);
|
|
|
|
if (pszNext1 - pszT1 != pszNext2 - pszT2)
|
|
break;
|
|
|
|
if (pszNext1 - pszT1 == 1) // if (single byte character)
|
|
{
|
|
if (ch1 != ch2)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (ch1 == TEXT('\\'))
|
|
ichRet = pszT1 - (LPCTSTR)achFile1;
|
|
}
|
|
else // (doulbe byte character)
|
|
{
|
|
if ( *(WORD *)pszT1 != *(WORD *)pszT2 )
|
|
break;
|
|
}
|
|
}
|
|
|
|
// NB - The middle expression in the for-loop above can
|
|
// short circuit so fix it here.
|
|
ch2 = *pszT2;
|
|
|
|
if ((!ch1 && (!ch2 || ch2 == TEXT('\\'))) ||
|
|
(!ch2 && (ch1 == TEXT('\\'))))
|
|
{
|
|
//
|
|
// If we've reached the end of one of one or both of the
|
|
// strings we have to update ichRet.
|
|
//
|
|
|
|
ichRet = pszT1 - (LPCTSTR)achFile1;
|
|
}
|
|
|
|
if (pszPath)
|
|
{
|
|
hmemcpy(pszPath, pszFile1, ichRet);
|
|
pszPath[ichRet] = TEXT('\0');
|
|
}
|
|
|
|
return ichRet;
|
|
}
|
|
|
|
void TPathCommonPrefix(LPCTSTR pszFrom, LPCTSTR pszTo)
|
|
{
|
|
TCHAR szPath[MAX_PATH], szPathOld[MAX_PATH];
|
|
PathCommonPrefix(pszFrom, pszTo, szPath);
|
|
|
|
DebugMsg(DM_TRACE, TEXT("PCP(%s,%s) -> %s"), pszFrom, pszTo, szPath);
|
|
|
|
OldPathCommonPrefix(pszFrom, pszTo, szPathOld);
|
|
|
|
if (!PathIsUNC(szPath))
|
|
{
|
|
if (lstrcmpi(szPath, szPathOld) != 0)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("old:%s new:%s"), szPathOld, szPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TPathRelativePathTo(LPCTSTR pszFrom, LPCTSTR pszTo)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
TCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
|
|
|
|
PathRelativePathTo(szPath, pszFrom, FILE_ATTRIBUTE_DIRECTORY, pszTo, FILE_ATTRIBUTE_DIRECTORY);
|
|
DebugMsg(DM_TRACE, TEXT("PathRelativePathTo(%s,%s) -> %s"), pszFrom, pszTo, szPath);
|
|
|
|
PathCombine(szFrom, pszFrom, TEXT("FileFrom"));
|
|
PathRelativePathTo(szPath, szFrom, 0, pszTo, FILE_ATTRIBUTE_DIRECTORY);
|
|
DebugMsg(DM_TRACE, TEXT("PathRelativePathTo(%s,%s) -> %s"), szFrom, pszTo, szPath);
|
|
|
|
PathCombine(szTo, pszTo, TEXT("FileTo"));
|
|
PathRelativePathTo(szPath, szFrom, 0, szTo, 0);
|
|
DebugMsg(DM_TRACE, TEXT("PathRelativePathTo(%s,%s) -> %s"), szFrom, szTo, szPath);
|
|
|
|
PathRelativePathTo(szPath, pszFrom, FILE_ATTRIBUTE_DIRECTORY, szTo, 0);
|
|
DebugMsg(DM_TRACE, TEXT("PathRelativePathTo(%s,%s) -> %s"), pszFrom, szTo, szPath);
|
|
}
|
|
|
|
void TestPathRelativePathTo()
|
|
{
|
|
TPathCommonPrefix(TEXT("C:\\"), TEXT("C:\\"));
|
|
TPathCommonPrefix(TEXT("C:\\a"), TEXT("C:\\"));
|
|
TPathCommonPrefix(TEXT("C:\\"), TEXT("C:\\a"));
|
|
TPathCommonPrefix(TEXT("C:\\a"), TEXT("C:\\a"));
|
|
TPathCommonPrefix(TEXT("C:\\a\\b"), TEXT("C:\\a"));
|
|
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\a"));
|
|
|
|
TPathCommonPrefix(TEXT("\\\\foo\\bar"), TEXT("C:\\a"));
|
|
TPathCommonPrefix(TEXT("\\\\foo\\bar"), TEXT("\\\\foo"));
|
|
TPathCommonPrefix(TEXT("\\\\foo\\bar"), TEXT("\\\\foo\\bar"));
|
|
TPathCommonPrefix(TEXT("\\\\foo\\bar"), TEXT("\\\\foo\\bar\\a"));
|
|
TPathCommonPrefix(TEXT("\\\\foo\\bar\\a"), TEXT("\\\\foo\\bar"));
|
|
TPathCommonPrefix(TEXT("\\\\foo\\bar\\a"), TEXT("\\\\foo\\bar\\a"));
|
|
TPathCommonPrefix(TEXT("\\\\foo\\bar\\a"), TEXT("\\\\foo\\bar\\a\\b"));
|
|
TPathCommonPrefix(TEXT("\\\\foo\\bar"), TEXT("\\\\foo\\bar\\"));
|
|
|
|
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b"));
|
|
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b\\c"));
|
|
TPathCommonPrefix(TEXT("C:\\a\\b\\c\\"), TEXT("C:\\a\\b\\c"));
|
|
TPathCommonPrefix(TEXT("C:\\a\\b\\c\\"), TEXT("C:\\a\\b\\c\\"));
|
|
|
|
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b\\c\\d"));
|
|
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b\\c\\d\\e"));
|
|
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\x"));
|
|
TPathCommonPrefix(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\x"));
|
|
|
|
TPathRelativePathTo(TEXT("C:\\"), TEXT("C:\\"));
|
|
TPathRelativePathTo(TEXT("C:\\a"), TEXT("C:\\a"));
|
|
TPathRelativePathTo(TEXT("C:\\a\\b"), TEXT("C:\\a"));
|
|
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\a"));
|
|
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b"));
|
|
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b\\c"));
|
|
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b\\c\\d"));
|
|
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\b\\c\\d\\e"));
|
|
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\x"));
|
|
TPathRelativePathTo(TEXT("C:\\a\\b\\c"), TEXT("C:\\a\\x"));
|
|
}
|
|
#endif // TEST_RPT
|
|
|
|
|
|
#ifdef USE_DATA_OBJ
|
|
|
|
|
|
HGLOBAL BuildLinkSrcDescriptor(LPCTSTR pszFile)
|
|
{
|
|
UINT cbFile = (lstrlen(pszFile) + 1) * SIZEOF(WCHAR);
|
|
|
|
LINKSRCDESCRIPTOR *plsd = GlobalAlloc(GPTR, SIZEOF(LINKSRCDESCRIPTOR) + cbFile);
|
|
if (plsd)
|
|
{
|
|
plsd->cbSize = SIZEOF(LINKSRCDESCRIPTOR) + cbFile;
|
|
// plsd->clsid = CLSID_NULL;
|
|
// plsd->dwDrawAspect;
|
|
// plsd->sizel;
|
|
// plsd->pointl;
|
|
// plsd->dwStatus;
|
|
// plsd->dwFullUserTypeName;
|
|
plsd->dwSrcOfCopy = SIZEOF(LINKSRCDESCRIPTOR);
|
|
|
|
StrToOleStr((LPWSTR)&plsd[1], pszFile); // copy and widen...
|
|
}
|
|
return (HGLOBAL)plsd;
|
|
}
|
|
|
|
|
|
HRESULT CreateLinkSrc(LPCTSTR pszPath, IStream **ppstm)
|
|
{
|
|
HRESULT hres;
|
|
|
|
IStream *pstm = CreateMemStream(NULL, 0);
|
|
if (pstm)
|
|
{
|
|
IMoniker *pmk;
|
|
WCHAR wszPath[MAX_PATH];
|
|
|
|
StrToOleStr(wszPath, pszPath);
|
|
|
|
hres = CreateFileMoniker(wszPath, &pmk);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
CLSID clsid;
|
|
|
|
// WriteClassStm()
|
|
|
|
pmk->lpVtbl->GetClassID(pmk, &clsid);
|
|
pstm->lpVtbl->Write(pstm, &clsid, SIZEOF(CLSID), NULL);
|
|
|
|
hres = pmk->lpVtbl->Save(pmk, pstm, FALSE);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
const LARGE_INTEGER liOffset = {0, 0};
|
|
|
|
// set the seek pointer to the start of the stream
|
|
pstm->lpVtbl->Seek(pstm, liOffset, STREAM_SEEK_SET, NULL);
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Save to mem stream failed"));
|
|
pstm->lpVtbl->Release(pstm);
|
|
pstm = NULL;
|
|
}
|
|
pmk->lpVtbl->Release(pmk);
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Failed CreateFileMoniker"));
|
|
pstm->lpVtbl->Release(pstm);
|
|
pstm = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_ERROR, TEXT("Failed to create mem stream"));
|
|
hres = E_OUTOFMEMORY;
|
|
}
|
|
|
|
*ppstm = pstm;
|
|
return hres;
|
|
}
|
|
|
|
HRESULT CShellLink_DO_QueryInterface(IDataObject *pdtobj, REFIID riid, void **ppvObj)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, dobj, pdtobj);
|
|
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
|
|
}
|
|
|
|
ULONG CShellLink_DO_AddRef(IDataObject *pdtobj)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, dobj, pdtobj);
|
|
return CShellLink_AddRef(&this->sl);
|
|
}
|
|
|
|
ULONG CShellLink_DO_Release(IDataObject *pdtobj)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, dobj, pdtobj);
|
|
return CShellLink_Release(&this->sl);
|
|
}
|
|
|
|
// get a path to create the file moniker to for the link data object
|
|
|
|
BOOL GetFilePathForData(CShellLink *this, LPTSTR pszPath)
|
|
{
|
|
if (this->fDataAlreadyResolved ||
|
|
SUCCEEDED(CShellLink_Resolve(&this->sl, NULL, SLR_NO_UI)))
|
|
{
|
|
// be sure to not resolve everytime GetData is called as it is slow
|
|
|
|
this->fDataAlreadyResolved = TRUE;
|
|
|
|
if (this->pidl && !(this->sld.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
|
|
SHGetPathFromIDList(this->pidl, pszPath))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
HRESULT CShellLink_GetData(IDataObject *pdtobj, FORMATETC *pformatetcIn, STGMEDIUM *pmedium)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, dobj, pdtobj);
|
|
TCHAR szPath[MAX_PATH];
|
|
HRESULT hres = DATA_E_FORMATETC;
|
|
|
|
#ifdef DEBUG
|
|
TCHAR szName[64];
|
|
if (!GetClipboardFormatName(pformatetcIn->cfFormat, szName, ARRAYSIZE(szName)))
|
|
wsprintf(szName, TEXT("#%d"), pformatetcIn->cfFormat);
|
|
|
|
DebugMsg(DM_TRACE, TEXT("CShellLink::GetData(%s)"), szName);
|
|
#endif
|
|
|
|
pmedium->hGlobal = NULL;
|
|
pmedium->pUnkForRelease = NULL;
|
|
|
|
if ((CF_LINKSOURCEDESCRIPTOR == pformatetcIn->cfFormat) &&
|
|
(pformatetcIn->tymed & TYMED_HGLOBAL))
|
|
{
|
|
if (GetFilePathForData(this, szPath))
|
|
{
|
|
pmedium->hGlobal = BuildLinkSrcDescriptor(szPath);
|
|
if (pmedium->hGlobal)
|
|
{
|
|
pmedium->tymed = TYMED_HGLOBAL;
|
|
hres = S_OK;
|
|
}
|
|
}
|
|
}
|
|
else if ((CF_LINKSOURCE == pformatetcIn->cfFormat) &&
|
|
(pformatetcIn->tymed & TYMED_ISTREAM))
|
|
{
|
|
if (GetFilePathForData(this, szPath))
|
|
{
|
|
hres = CreateLinkSrc(szPath, &pmedium->pstm);
|
|
pmedium->tymed = TYMED_ISTREAM;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (hres == S_OK)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("GetData returns S_OK"));
|
|
}
|
|
#endif
|
|
|
|
return hres;
|
|
}
|
|
|
|
HRESULT CShellLink_GetDataHere(IDataObject *pdtobj, FORMATETC *pformatetc, STGMEDIUM *pmedium )
|
|
{
|
|
DebugMsg(DM_ERROR, TEXT("CShellLink_GetDataHere not implemented"));
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT CShellLink_QueryGetData(IDataObject *pdtobj, FORMATETC *pformatetcIn)
|
|
{
|
|
#ifdef DEBUG
|
|
TCHAR szName[64];
|
|
if (!GetClipboardFormatName(pformatetcIn->cfFormat, szName, ARRAYSIZE(szName)))
|
|
wsprintf(szName, TEXT("#%d"), pformatetcIn->cfFormat);
|
|
|
|
DebugMsg(DM_TRACE, TEXT("CShellLink::QueryGetData(%s)"), szName);
|
|
#endif
|
|
|
|
if (pformatetcIn->cfFormat == CF_LINKSOURCEDESCRIPTOR ||
|
|
pformatetcIn->cfFormat == CF_LINKSOURCE)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("QueryGetData says S_OK"));
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
}
|
|
|
|
HRESULT CShellLink_GetCanonicalFormatEtc(IDataObject *pdtobj, FORMATETC *pformatetc, FORMATETC *pformatetcOut)
|
|
{
|
|
return DATA_S_SAMEFORMATETC;
|
|
}
|
|
|
|
HRESULT CShellLink_SetData(IDataObject *pdtobj, FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease)
|
|
{
|
|
DebugMsg(DM_ERROR, TEXT("CShellLink_SetData not implemented"));
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT CShellLink_EnumFormatEtc(IDataObject *pdtobj, DWORD dwDirection, LPENUMFORMATETC *ppenumFormatEtc)
|
|
{
|
|
FORMATETC fmts[2] = {
|
|
{ CF_LINKSOURCE, NULL, DVASPECT_CONTENT, -1, TYMED_ISTREAM },
|
|
{ CF_LINKSOURCEDESCRIPTOR, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }
|
|
};
|
|
|
|
return SHCreateStdEnumFmtEtc(ARRAYSIZE(fmts), fmts, ppenumFormatEtc);
|
|
}
|
|
|
|
HRESULT CShellLink_Advise(IDataObject *pdtobj, FORMATETC *pFormatetc, DWORD advf, LPADVISESINK pAdvSink, DWORD *pdwConnection)
|
|
{
|
|
return OLE_E_ADVISENOTSUPPORTED;
|
|
}
|
|
|
|
HRESULT CShellLink_Unadvise(IDataObject *pdtobj, DWORD dwConnection)
|
|
{
|
|
return OLE_E_ADVISENOTSUPPORTED;
|
|
}
|
|
|
|
HRESULT CShellLink_EnumAdvise(IDataObject *pdtobj, LPENUMSTATDATA *ppenumAdvise)
|
|
{
|
|
return OLE_E_ADVISENOTSUPPORTED;
|
|
}
|
|
|
|
#pragma data_seg(".text", "CODE")
|
|
IDataObjectVtbl c_DataObj_Vtbl = {
|
|
CShellLink_DO_QueryInterface, CShellLink_DO_AddRef, CShellLink_DO_Release,
|
|
CShellLink_GetData,
|
|
CShellLink_GetDataHere,
|
|
CShellLink_QueryGetData,
|
|
CShellLink_GetCanonicalFormatEtc,
|
|
CShellLink_SetData,
|
|
CShellLink_EnumFormatEtc,
|
|
CShellLink_Advise,
|
|
CShellLink_Unadvise,
|
|
CShellLink_EnumAdvise
|
|
};
|
|
#pragma data_seg()
|
|
|
|
#endif // USE_DATA_OBJ
|
|
|
|
|
|
//
|
|
// update the working dir to match changes being made to the link target
|
|
//
|
|
void UpdateWorkingDir(CShellLink *this, LPCITEMIDLIST pidlNew)
|
|
{
|
|
TCHAR szOld[MAX_PATH], szNew[MAX_PATH], szPath[MAX_PATH];
|
|
|
|
if (this->pszWorkingDir == NULL ||
|
|
this->pszWorkingDir[0] == 0 ||
|
|
this->pidl == NULL ||
|
|
!SHGetPathFromIDList(this->pidl, szOld) ||
|
|
StrChr(szOld, TEXT('%')) || // has environement var %USER%
|
|
!SHGetPathFromIDList(pidlNew, szNew))
|
|
return;
|
|
|
|
if (PathRelativePathTo(szPath, szOld, this->sld.dwFileAttributes, this->pszWorkingDir, FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
PathGetRelative(szOld, szNew, GetFileAttributes(szNew), szPath); // get result is szOld
|
|
|
|
if (PathIsDirectory(szOld))
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("working dir updated to %s"), szOld);
|
|
Str_SetPtr(&this->pszWorkingDir, szOld);
|
|
this->bDirty = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// set the pidl either based on a new pidl or a path
|
|
// this will set the dirty flag if this info is different from the current
|
|
//
|
|
// in:
|
|
// pidlNew if non-null, use as new PIDL for link
|
|
// pszPath if non-null, create a pidl for this and set it
|
|
// pfdNew find data for this file (if we already have it)
|
|
//
|
|
// returns:
|
|
// TRUE successfully set the pidl (or it was unchanged)
|
|
// FALSE memory failure or pszPath does not exist
|
|
|
|
BOOL SetPIDLPath(CShellLink *this, LPCITEMIDLIST pidlNew, LPCTSTR pszPath, const WIN32_FIND_DATA *pfdNew)
|
|
{
|
|
LPITEMIDLIST pidlCreated = NULL;
|
|
|
|
#ifdef TEST_RPT
|
|
TestPathRelativePathTo();
|
|
#endif
|
|
|
|
if (pszPath && !pidlNew)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
// the path is the same as the current pidl, short circuit the disk hits
|
|
if (this->pidl && SHGetPathFromIDList(this->pidl, szPath) && !lstrcmpi(szPath, pszPath))
|
|
return TRUE;
|
|
|
|
pidlCreated = ILCreateFromPath(pszPath);
|
|
|
|
if (pidlCreated == NULL)
|
|
{
|
|
TCHAR achPath[MAX_PATH];
|
|
DebugMsg(DM_TRACE, TEXT("Failed to create pidl for link (trying simple PIDL)"));
|
|
lstrcpy(achPath, pszPath);
|
|
PathResolve(achPath, NULL, PRF_TRYPROGRAMEXTENSIONS);
|
|
pidlCreated = SHSimpleIDListFromPath(achPath);
|
|
}
|
|
|
|
if (!pidlCreated)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Failed to create pidl for link"));
|
|
return FALSE;
|
|
}
|
|
pidlNew = pidlCreated;
|
|
}
|
|
|
|
if (!pidlNew)
|
|
{
|
|
if (this->pidl)
|
|
{
|
|
ILFree(this->pidl);
|
|
this->pidl = NULL;
|
|
this->bDirty = TRUE;
|
|
}
|
|
if (this->pli)
|
|
{
|
|
LocalFree((HLOCAL)this->pli);
|
|
this->pli = NULL;
|
|
}
|
|
_fmemset(&this->sld, 0, SIZEOF(this->sld));
|
|
}
|
|
else
|
|
{
|
|
// NOTE: this can result in an asser in the fs compare items if the 2 pidls
|
|
// are both simple. but since this is just an optimization to avoid resetting
|
|
// the pidl if it is the same that can be ignored.
|
|
|
|
if (!this->pidl || !ILIsEqual(this->pidl, pidlNew))
|
|
{
|
|
/* Yes. Save updated path IDL. */
|
|
LPITEMIDLIST pidlClone = ILClone(pidlNew);
|
|
if (pidlClone)
|
|
{
|
|
UpdateWorkingDir(this, pidlClone);
|
|
|
|
if (this->pidl)
|
|
ILFree(this->pidl);
|
|
|
|
this->pidl = pidlClone;
|
|
|
|
GetLinkInfo(this); // construct the LinkInfo (pli)
|
|
|
|
DumpPLI(this->pli);
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("SetPIDLPath ILClone failed"));
|
|
return FALSE;
|
|
}
|
|
this->bDirty = TRUE;
|
|
}
|
|
|
|
if (pfdNew)
|
|
SetFindData(this, pfdNew, pszPath);
|
|
}
|
|
|
|
if (pidlCreated)
|
|
ILFree(pidlCreated);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// sees if this link might have a relative path
|
|
//
|
|
//
|
|
// out:
|
|
// pszPath returned new path
|
|
//
|
|
// returns:
|
|
// TRUE we found a relative path and it exists
|
|
// FALSE outa luck.
|
|
//
|
|
|
|
BOOL Link_ResolveRelative(CShellLink *this, LPTSTR pszPath)
|
|
{
|
|
LPCTSTR pszPathRel;
|
|
TCHAR szRoot[MAX_PATH];
|
|
|
|
// pszRelSource overrides pszCurFile
|
|
|
|
pszPathRel = this->pszRelSource ? this->pszRelSource : this->pszCurFile;
|
|
|
|
if (pszPathRel == NULL || this->pszRelPath == NULL)
|
|
return FALSE;
|
|
|
|
lstrcpy(szRoot, pszPathRel);
|
|
PathRemoveFileSpec(szRoot); // pszfrom is a file (not a directory)
|
|
|
|
PathCombine(pszPath, szRoot, this->pszRelPath);
|
|
|
|
if (PathFileExists(pszPath))
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Link_ResolveRelative() returning %s"), pszPath);
|
|
return SetPIDLPath(this, NULL, pszPath, NULL);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// updates then resolves LinkInfo associated with a CShellLink instance
|
|
// if the resolve results in a new path updates the pidl to the new path
|
|
//
|
|
// in:
|
|
// hwnd to post resolve UI on (if dwFlags indicates UI)
|
|
// dwFlags to be passed to ResolveLinkInfo
|
|
//
|
|
// returns:
|
|
// TRUE we have a valid pli and pidl read to be used OR
|
|
// we should search for this path using the link search code
|
|
// FALSE we failed the update, either UI cancel or memory failure
|
|
//
|
|
|
|
BOOL UpdateAndResolveLinkInfo(CShellLink *this, HWND hwnd, DWORD dwFlags)
|
|
{
|
|
TCHAR szResolvedPath[MAX_PATH];
|
|
|
|
if (SHRestricted(REST_LINKRESOLVEIGNORELINKINFO))
|
|
{
|
|
SHGetPathFromIDList(this->pidl, szResolvedPath);
|
|
if (PathFileExists(szResolvedPath))
|
|
return TRUE;
|
|
else if (IsNetDrive(DRIVEID(szResolvedPath))==2)
|
|
{
|
|
TCHAR szDrive[4];
|
|
|
|
szDrive[0] = szResolvedPath[0];
|
|
szDrive[1] = TEXT(':');
|
|
szDrive[2] = TEXT('\0');
|
|
WNetRestoreConnection(hwnd, szDrive);
|
|
}
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
if (this->pli)
|
|
{
|
|
TCHAR szResolvedPath[MAX_PATH];
|
|
DWORD dwOutFlags;
|
|
BOOL bResolved = FALSE;
|
|
|
|
Assert(! (dwFlags & RLI_IFL_UPDATE));
|
|
|
|
if (g_pfnResolveLinkInfo(this->pli, szResolvedPath, dwFlags, hwnd,
|
|
&dwOutFlags, NULL))
|
|
{
|
|
Assert(! (dwOutFlags & RLI_OFL_UPDATED));
|
|
|
|
bResolved = TRUE;
|
|
|
|
// we have to hit the disk again to set the new path
|
|
|
|
// DebugMsg(DM_TRACE, "Resolved LinkInfo -> %s %4x", szResolvedPath, dwOutFlags);
|
|
|
|
if (PathFileExists(szResolvedPath))
|
|
return SetPIDLPath(this, NULL, szResolvedPath, NULL);
|
|
else
|
|
DebugMsg(DM_TRACE, TEXT("Link referent %s not found."), szResolvedPath);
|
|
}
|
|
else if (GetLastError() == ERROR_CANCELLED)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("ResolveLinkInfo() failed, user canceled."));
|
|
return FALSE;
|
|
}
|
|
DebugMsg(DM_TRACE, TEXT("ResolveLinkInfo() failed."));
|
|
|
|
// Resolve failed, or resolve succeeded but the file was not found.
|
|
// Try PIDL path.
|
|
|
|
SHGetPathFromIDList(this->pidl, szResolvedPath);
|
|
|
|
Assert(szResolvedPath[0]);
|
|
|
|
if (PathFileExists(szResolvedPath) || Link_ResolveRelative(this, szResolvedPath))
|
|
{
|
|
// this can happen when linkinfo can't find the original drive
|
|
// serial # on the device. could be that the drive was
|
|
// dblspaced or some disk utility wacked on it.
|
|
|
|
DebugMsg(DM_TRACE, TEXT("Link referent %s found on different volume but same path. LinkInfo will be updated."), szResolvedPath);
|
|
|
|
/* Update LinkInfo to refer to PIDL path. */
|
|
|
|
GetLinkInfo(this);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (bResolved)
|
|
return TRUE; // please search for this
|
|
|
|
// BUGBUG: UI goes here
|
|
// 1) if it is a floppy ask to insert
|
|
// 2) if on unshared media tell them that
|
|
// 3) net problems, tell 'em
|
|
|
|
if (dwFlags & RLI_IFL_ALLOW_UI)
|
|
{
|
|
LPCTSTR pszName = this->pszCurFile ? (LPCTSTR)PathFindFileName(this->pszCurFile) : c_szNULL;
|
|
|
|
ShellMessageBox(HINST_THISDLL, hwnd,
|
|
MAKEINTRESOURCE(IDS_LINKUNAVAILABLE),
|
|
MAKEINTRESOURCE(IDS_LINKERROR),
|
|
MB_OK | MB_ICONEXCLAMATION, pszName);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
else
|
|
return TRUE; // search for this
|
|
}
|
|
|
|
// pidl can be full or not... this should determine the robustness of the
|
|
// link (and the speed)
|
|
|
|
|
|
BOOL PathIsPif(LPCTSTR pszPath)
|
|
{
|
|
return lstrcmpi(PathFindExtension(pszPath), c_szDotPif) == 0;
|
|
}
|
|
|
|
|
|
HRESULT CShellLink_SetIDList(IShellLink *psl, LPCITEMIDLIST pidl)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
HRESULT hr = S_OK;
|
|
TCHAR szPath[MAX_PATH];
|
|
|
|
if (pidl != this->pidl)
|
|
SetPIDLPath(this, pidl, NULL, NULL);
|
|
|
|
// is this a pidl to a file?
|
|
if (this->pidl && SHGetPathFromIDList(this->pidl, szPath))
|
|
{
|
|
// DebugMsg(DM_TRACE, "ShellLink::SetIDList(%s)", szPath);
|
|
|
|
if (PathIsRoot(szPath))
|
|
{
|
|
_fmemset(&this->sld, 0, SIZEOF(this->sld));
|
|
this->sld.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
|
|
}
|
|
else
|
|
{
|
|
WIN32_FIND_DATA fd;
|
|
#ifdef WINNT
|
|
WIN32_FILE_ATTRIBUTE_DATA fad;
|
|
|
|
// Get the file attributes (GetFileAttributesEx is
|
|
// faster than FindFirstFile, so we'll use the former,
|
|
// and copy the data into the structure of the latter).
|
|
|
|
_fmemset( &fd, 0, sizeof(fd) );
|
|
if( GetFileAttributesEx( szPath, GetFileExInfoStandard, &fad ))
|
|
{
|
|
fd.dwFileAttributes = fad.dwFileAttributes;
|
|
fd.ftCreationTime = fad.ftCreationTime;
|
|
fd.ftLastAccessTime = fad.ftLastAccessTime;
|
|
fd.ftLastWriteTime = fad.ftLastWriteTime;
|
|
fd.nFileSizeLow = fad.nFileSizeLow;
|
|
}
|
|
#else
|
|
HANDLE hfind = FindFirstFile(szPath, &fd);
|
|
if (hfind != INVALID_HANDLE_VALUE)
|
|
{
|
|
FindClose(hfind);
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
hr = S_FALSE;
|
|
}
|
|
|
|
CheckAndFixNullCreateTime(szPath, &fd);
|
|
|
|
SetFindData(this, &fd, szPath);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CShellLink_SetPath(IShellLink *psl, LPCTSTR szPath)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
TCHAR szBuffer[ MAX_PATH ];
|
|
LPCTSTR lpszPath = szPath;
|
|
|
|
// Check to see if the target has any expandable environment strings
|
|
// in it. If so, set the appropriate information in the CShellLink
|
|
// data. BUGBUG ricktu -- this should be converted to use IPropertyStream.
|
|
|
|
ExpandEnvironmentStrings( szPath, szBuffer, MAX_PATH );
|
|
szBuffer[MAX_PATH-1] = TEXT('\0');
|
|
if (lstrcmp(szBuffer, szPath)) {
|
|
EXP_SZ_LINK expLink;
|
|
LPEXP_SZ_LINK lpNew = NULL;
|
|
|
|
// mark that link has expandable strings, and add them
|
|
this->sld.dwFlags |= SLDF_HAS_EXP_SZ;
|
|
|
|
for( lpNew = (LPEXP_SZ_LINK)this->pExtraData;
|
|
lpNew && lpNew->cbSize && lpNew->dwSignature!=EXP_SZ_LINK_SIG;
|
|
lpNew = (LPEXP_SZ_LINK)(((LPBYTE)lpNew) + lpNew->cbSize)
|
|
);
|
|
|
|
if ((!lpNew) || (lpNew->cbSize==0))
|
|
{
|
|
lpNew = &expLink;
|
|
lpNew->cbSize = 0;
|
|
}
|
|
|
|
if (lpNew) {
|
|
lpNew->dwSignature = EXP_SZ_LINK_SIG;
|
|
#ifdef UNICODE
|
|
WideCharToMultiByte( CP_ACP, 0,
|
|
szPath, -1,
|
|
(LPSTR)lpNew->szTarget, MAX_PATH,
|
|
NULL, NULL
|
|
);
|
|
lpNew->szTarget[MAX_PATH-1] = '\0';
|
|
ualstrcpy( lpNew->swzTarget, szPath );
|
|
#else
|
|
lstrcpyA( lpNew->szTarget, szPath );
|
|
|
|
// BUGBUG Fix this alignment problem
|
|
{
|
|
WCHAR wszTmp[MAX_PATH];
|
|
MultiByteToWideChar( CP_ACP, 0,
|
|
szPath, -1,
|
|
wszTmp, MAX_PATH
|
|
);
|
|
ualstrcpyW(lpNew->swzTarget, wszTmp);
|
|
}
|
|
|
|
lpNew->swzTarget[MAX_PATH-1] = L'\0';
|
|
|
|
#endif
|
|
}
|
|
|
|
// See if this is a new entry that we need to add
|
|
if (lpNew->cbSize==0)
|
|
{
|
|
lpNew->cbSize = sizeof( EXP_SZ_LINK );
|
|
Link_AddExtraDataSection( this, (DWORD UNALIGNED *)lpNew );
|
|
}
|
|
|
|
lpszPath = szBuffer;
|
|
|
|
} else {
|
|
|
|
this->sld.dwFlags &= (~SLDF_HAS_EXP_SZ);
|
|
Link_RemoveExtraDataSection( this, EXP_SZ_LINK_SIG );
|
|
|
|
}// end of BUGBUG ricktu.
|
|
|
|
|
|
SetPIDLPath(this, NULL, lpszPath, NULL);
|
|
return CShellLink_SetIDList(psl, this->pidl);
|
|
}
|
|
|
|
|
|
HRESULT CShellLink_GetDescription(IShellLink *psl, LPTSTR pszFile, int cchMaxPath)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
VDATEINPUTBUF(pszFile, TCHAR, cchMaxPath);
|
|
|
|
if (!this->pszName)
|
|
*pszFile = 0;
|
|
else
|
|
lstrcpyn(pszFile, this->pszName, cchMaxPath);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CShellLink_SetDescription(IShellLink *psl, LPCTSTR pszFile)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
|
|
if (DifferentStrings(this->pszName, pszFile)) // case sensitive
|
|
this->bDirty = TRUE;
|
|
|
|
Str_SetPtr(&this->pszName, pszFile);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CShellLink_GetWorkingDirectory(IShellLink *psl, LPTSTR pszFile, int cchMaxPath)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
VDATEINPUTBUF(pszFile, TCHAR, cchMaxPath);
|
|
|
|
if (!this->pszWorkingDir)
|
|
*pszFile = 0;
|
|
else
|
|
{
|
|
#ifdef MYDIR_LIVES
|
|
if (lstrcmp(this->pszWorkingDir, c_szMyDirTag) != 0)
|
|
{
|
|
#endif
|
|
lstrcpyn(pszFile, this->pszWorkingDir, cchMaxPath);
|
|
#ifdef MYDIR_LIVES
|
|
}
|
|
else
|
|
{
|
|
TCHAR szMyDirPath[MAX_PATH];
|
|
SHGetSpecialFolderPath(NULL, szMyDirPath, CSIDL_PERSONAL, TRUE);
|
|
lstrcpyn(pszFile, szMyDirPath, cchMaxPath);
|
|
}
|
|
#endif //MYDIR_LIVES
|
|
}
|
|
return S_OK;
|
|
}
|
|
HRESULT CShellLink_SetWorkingDirectory(IShellLink *psl, LPCTSTR pszFile)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
#ifdef MYDIR_LIVES
|
|
TCHAR szMyDirPath[MAX_PATH];
|
|
|
|
SHGetSpecialFolderPath(NULL, szMyDirPath, CSIDL_PERSONAL, TRUE);
|
|
|
|
// If string compares to special directory substitute our special
|
|
// tag back in...
|
|
if (pszFile && (lstrcmpi(pszFile, szMyDirPath) == 0))
|
|
pszFile = c_szMyDirTag;
|
|
#endif
|
|
|
|
if (DifferentStrings(this->pszWorkingDir, pszFile))
|
|
this->bDirty = TRUE;
|
|
|
|
Str_SetPtr(&this->pszWorkingDir, pszFile);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CShellLink_GetArguments(IShellLink *psl, LPTSTR pszFile, int cchMaxPath)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
VDATEINPUTBUF(pszFile, TCHAR, cchMaxPath);
|
|
|
|
if (!this->pszArgs)
|
|
*pszFile = 0;
|
|
else
|
|
lstrcpyn(pszFile, this->pszArgs, cchMaxPath);
|
|
return S_OK;
|
|
}
|
|
HRESULT CShellLink_SetArguments(IShellLink *psl, LPCTSTR pszArgs)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
|
|
if (DifferentStrings(this->pszArgs, pszArgs))
|
|
this->bDirty = TRUE;
|
|
|
|
Str_SetPtr(&this->pszArgs, pszArgs);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CShellLink_GetHotkey(IShellLink *psl, WORD *pwHotkey)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
*pwHotkey = this->sld.wHotkey;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CShellLink_SetHotkey(IShellLink *psl, WORD wHotkey)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
if (this->sld.wHotkey != wHotkey)
|
|
this->bDirty = TRUE;
|
|
this->sld.wHotkey = wHotkey;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CShellLink_GetShowCmd(IShellLink *psl, int *piShowCmd)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
*piShowCmd = this->sld.iShowCmd;
|
|
return S_OK;
|
|
}
|
|
HRESULT CShellLink_SetShowCmd(IShellLink *psl, int iShowCmd)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
if (this->sld.iShowCmd != iShowCmd)
|
|
this->bDirty = TRUE;
|
|
this->sld.iShowCmd = iShowCmd;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CShellLink_GetIconLocation(IShellLink *psl, LPTSTR pszIconPath, int cchIconPath, int *piIcon)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
VDATEINPUTBUF(pszIconPath, TCHAR, cchIconPath);
|
|
|
|
if (!this->pszIconLocation)
|
|
*pszIconPath = 0;
|
|
else
|
|
lstrcpyn(pszIconPath, this->pszIconLocation, cchIconPath);
|
|
*piIcon = this->sld.iIcon;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CShellLink_SetIconLocation(IShellLink *psl, LPCTSTR pszIconPath, int iIcon)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
if (DifferentStrings(this->pszIconLocation, pszIconPath) || (this->sld.iIcon != iIcon))
|
|
this->bDirty = TRUE;
|
|
|
|
Str_SetPtr(&this->pszIconLocation, pszIconPath);
|
|
this->sld.iIcon = iIcon;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------
|
|
//
|
|
// Function: ResolveLink
|
|
//
|
|
// Purpose: Provide the implementation for
|
|
// IShellLink::Resolve and IShellLinkTracker::Resolve
|
|
//
|
|
// Inputs: [CShellLink] *this
|
|
// - Pointer to the ShellLink object.
|
|
// [HWND] hwnd
|
|
// - The parent window (which could be the desktop).
|
|
// [DWORD] fFlags
|
|
// - Flags from the SLR_FLAGS enumeration.
|
|
// [DWORD] dwTrackFlags
|
|
// - Restrictions on the link-tracking algorithm (TRACK_* flags).
|
|
// [DWORD] dwTickCountDeadline
|
|
// - Timeout on the link-tracking algorithm, in absolute time
|
|
// (WRT GetTickCount()).
|
|
// [DWORD] dwReserved
|
|
// - Reserved for future expansion.
|
|
//
|
|
// Outputs: [HRESULT]
|
|
// - S_OK resolution was successful
|
|
// S_FALSE user canceled
|
|
//
|
|
// Algorithm: Look for the link target and update the link path and IDList.
|
|
// Check IPersistFile::IsDirty after calling this to see if the
|
|
// link info has changed as a result.
|
|
//
|
|
//+------------------------------------------------------------------
|
|
|
|
|
|
HRESULT ResolveLink( CShellLink * this,
|
|
HWND hwnd,
|
|
DWORD fFlags,
|
|
DWORD dwTrackFlags,
|
|
DWORD dwTickCountDeadline,
|
|
DWORD dwReserved)
|
|
{
|
|
|
|
HRESULT hres = S_OK;
|
|
HANDLE hfind;
|
|
WIN32_FIND_DATA fd;
|
|
TCHAR szPath[MAX_PATH];
|
|
DWORD dwResolveFlags;
|
|
|
|
// check to see whether this link has expandable environment strings
|
|
if (this->sld.dwFlags & SLDF_HAS_EXP_SZ)
|
|
{
|
|
TCHAR szExp[ MAX_PATH ];
|
|
LPEXP_SZ_LINK lpData;
|
|
|
|
// yep, so create a new pidl that points to expanded path
|
|
for( lpData = (LPEXP_SZ_LINK)this->pExtraData;
|
|
lpData && lpData->cbSize && (lpData->dwSignature!=EXP_SZ_LINK_SIG);
|
|
lpData = (LPEXP_SZ_LINK) (((LPBYTE)lpData) + lpData->cbSize)
|
|
);
|
|
if (lpData && lpData->cbSize)
|
|
{
|
|
#ifdef UNICODE
|
|
{
|
|
TCHAR szTmp[MAX_PATH]; // BUGBUG Fix this alignment problem
|
|
ualstrcpy(szTmp, lpData->swzTarget);
|
|
ExpandEnvironmentStrings( szTmp,
|
|
szExp,
|
|
ARRAYSIZE(szExp)
|
|
);
|
|
}
|
|
|
|
#else
|
|
ExpandEnvironmentStrings( lpData->szTarget,
|
|
szExp,
|
|
ARRAYSIZE(szExp)
|
|
);
|
|
#endif
|
|
szExp[ MAX_PATH-1 ] = TEXT('\0');
|
|
}
|
|
|
|
if (this->pidl)
|
|
ILFree( this->pidl );
|
|
this->pidl = ILCreateFromPath( szExp );
|
|
|
|
}
|
|
|
|
// ensure that this is a link to a file, if not assume it is ok...
|
|
|
|
if (!this->pidl || !SHGetPathFromIDList(this->pidl, szPath))
|
|
return S_OK;
|
|
|
|
dwResolveFlags = (RLI_IFL_CONNECT | RLI_IFL_TEMPORARY);
|
|
|
|
if (!PathIsRoot(szPath))
|
|
dwResolveFlags |= RLI_IFL_LOCAL_SEARCH;
|
|
|
|
if (!(fFlags & SLR_NO_UI))
|
|
dwResolveFlags |= RLI_IFL_ALLOW_UI;
|
|
|
|
if (!UpdateAndResolveLinkInfo(this, hwnd, dwResolveFlags))
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("UpdateAndResolveLinkInfo() failed"));
|
|
return S_FALSE; // they canceled or this failed
|
|
}
|
|
|
|
// UpdateAndResolveLinkInfo() may have changed the path
|
|
SHGetPathFromIDList(this->pidl, szPath);
|
|
|
|
if (PathIsRoot(szPath))
|
|
{
|
|
// DebugMsg(DM_TRACE, "ShellLink::Resolve() root path %s", szPath);
|
|
// should be golden
|
|
}
|
|
else
|
|
{
|
|
// the above code did the retry logic (UI) if needed
|
|
|
|
hfind = FindFirstFile(szPath, &fd);
|
|
if (hfind != INVALID_HANDLE_VALUE)
|
|
{
|
|
FindClose(hfind);
|
|
|
|
SetFindData(this, &fd, szPath);
|
|
}
|
|
else
|
|
{
|
|
int id;
|
|
BOOL fFound = FALSE;
|
|
// this thing is a link that is broken
|
|
id = GetLastError();
|
|
|
|
DebugMsg(DM_TRACE, TEXT("ShellLink::Resolve() file not found %s(%d)"), szPath, id);
|
|
|
|
// Some error codes we will try to recover from by trying to get the network
|
|
// to restore the connection.
|
|
if (id == ERROR_BAD_NETPATH)
|
|
{
|
|
TCHAR szDrive[4];
|
|
szDrive[0] = szPath[0];
|
|
szDrive[1] = TEXT(':');
|
|
szDrive[2] = TEXT('\0');
|
|
if (WNetRestoreConnection(hwnd, szDrive) == WN_SUCCESS)
|
|
{
|
|
hfind = FindFirstFile(szPath, &fd);
|
|
if (hfind != INVALID_HANDLE_VALUE)
|
|
{
|
|
FindClose(hfind);
|
|
SetFindData(this, &fd, szPath);
|
|
fFound = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fFound)
|
|
{
|
|
GetFindData(this, &fd);
|
|
|
|
#ifdef ENABLE_TRACK
|
|
if (g_fNewTrack)
|
|
{
|
|
|
|
id = FindInFolder2( hwnd, fFlags, szPath, &fd,
|
|
dwTrackFlags, dwTickCountDeadline, this->ptracker );
|
|
}
|
|
else
|
|
{
|
|
#endif
|
|
id = FindInFolder( hwnd, fFlags, szPath, &fd );
|
|
#ifdef ENABLE_TRACK
|
|
}
|
|
#endif
|
|
if (id == IDOK)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("ShellLink::Resolve() resolved to %s"), fd.cFileName);
|
|
|
|
// fd.cFileName comes back fully qualified
|
|
|
|
SetPIDLPath(this, NULL, fd.cFileName, &fd);
|
|
|
|
Assert(this->bDirty); // should be dirty now
|
|
}
|
|
else
|
|
{
|
|
// DebugMsg(DM_TRACE, TEXT("ShellLink::Resolve() failed"));
|
|
|
|
if ((id != IDCANCEL) && !(fFlags & SLR_NO_UI))
|
|
ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_LINKNOTFOUND), MAKEINTRESOURCE(IDS_LINKERROR),
|
|
MB_OK | MB_ICONEXCLAMATION, PathFindFileName(szPath));
|
|
|
|
Assert(!this->bDirty); // should not be dirty now
|
|
|
|
hres = S_FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// if the link is dirty update it.
|
|
//
|
|
if (this->bDirty && (fFlags & SLR_UPDATE))
|
|
CShellLink_Save(&this->pf, NULL, TRUE);
|
|
|
|
return hres;
|
|
|
|
} // ResolveLink
|
|
|
|
|
|
|
|
//
|
|
// Function: CShellLink_Resolve
|
|
//
|
|
// This routine determines the dwTickCountDeadline (the absolute time
|
|
// of the deadline), and calls ResolveLink().
|
|
//
|
|
// Check IPersistFile::IsDirty after calling this to see if the link info
|
|
// has changed as a result of this.
|
|
//
|
|
|
|
HRESULT CShellLink_Resolve(IShellLink *psl, HWND hwnd, DWORD fFlags)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, sl, psl);
|
|
DWORD dwTickCountDeadline = 0L;
|
|
|
|
return ResolveLink( this,
|
|
hwnd,
|
|
fFlags,
|
|
0L, // dwTrackFlags
|
|
0L,
|
|
0L ); // Reserved
|
|
|
|
}
|
|
|
|
|
|
#pragma data_seg(".text", "CODE")
|
|
IShellLinkVtbl c_ShellLink_Vtbl = {
|
|
CShellLink_QueryInterface, CShellLink_AddRef, CShellLink_Release,
|
|
CShellLink_GetPath,
|
|
CShellLink_GetIDList,
|
|
CShellLink_SetIDList,
|
|
CShellLink_GetDescription,
|
|
CShellLink_SetDescription,
|
|
CShellLink_GetWorkingDirectory,
|
|
CShellLink_SetWorkingDirectory,
|
|
CShellLink_GetArguments,
|
|
CShellLink_SetArguments,
|
|
CShellLink_GetHotkey,
|
|
CShellLink_SetHotkey,
|
|
CShellLink_GetShowCmd,
|
|
CShellLink_SetShowCmd,
|
|
CShellLink_GetIconLocation,
|
|
CShellLink_SetIconLocation,
|
|
CShellLink_SetRelativePath,
|
|
CShellLink_Resolve,
|
|
CShellLink_SetPath,
|
|
};
|
|
#pragma data_seg()
|
|
|
|
#ifdef UNICODE
|
|
//
|
|
// This section contains all of the methods for the ansi interface IShellLinkA
|
|
//
|
|
HRESULT CShellLinkA_QueryInterface(IShellLinkA *pslA, REFIID riid, LPVOID *ppvObj)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
|
|
}
|
|
|
|
ULONG CShellLinkA_AddRef(IShellLinkA *pslA)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
return CShellLink_AddRef(&this->sl);
|
|
}
|
|
|
|
ULONG CShellLinkA_Release(IShellLinkA *pslA)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
return CShellLink_Release(&this->sl);
|
|
}
|
|
|
|
HRESULT CShellLinkA_GetPath(IShellLinkA *pslA, LPSTR pszFile, int cchMaxPath, WIN32_FIND_DATAA *pfd, DWORD fFlags)
|
|
{
|
|
WCHAR szPath[MAX_PATH];
|
|
WIN32_FIND_DATA wfd;
|
|
HRESULT hr;
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
VDATEINPUTBUF(pszFile, CHAR, cchMaxPath);
|
|
|
|
hr = CShellLink_GetPath(&this->sl, szPath, ARRAYSIZE(szPath), &wfd, fFlags);
|
|
|
|
if (pszFile)
|
|
{
|
|
WideCharToMultiByte(CP_ACP, 0,
|
|
szPath, -1,
|
|
pszFile, cchMaxPath,
|
|
NULL, NULL);
|
|
}
|
|
if (pfd)
|
|
{
|
|
if (szPath[0])
|
|
{
|
|
pfd->dwFileAttributes = wfd.dwFileAttributes;
|
|
pfd->ftCreationTime = wfd.ftCreationTime;
|
|
pfd->ftLastAccessTime = wfd.ftLastAccessTime;
|
|
pfd->ftLastWriteTime = wfd.ftLastWriteTime;
|
|
pfd->nFileSizeLow = wfd.nFileSizeLow;
|
|
pfd->nFileSizeHigh = wfd.nFileSizeHigh;
|
|
|
|
WideCharToMultiByte(CP_ACP, 0,
|
|
wfd.cFileName, -1,
|
|
pfd->cFileName, ARRAYSIZE(pfd->cFileName),
|
|
NULL, NULL );
|
|
}
|
|
else
|
|
_fmemset(pfd, 0, SIZEOF(*pfd));
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CShellLinkA_SetPath(IShellLinkA *pslA, LPCSTR pszPath)
|
|
{
|
|
WCHAR szPath[MAX_PATH];
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
|
|
if (pszPath)
|
|
{
|
|
MultiByteToWideChar(CP_ACP, 0,
|
|
pszPath, -1,
|
|
szPath, ARRAYSIZE(szPath));
|
|
return CShellLink_SetPath(&this->sl, szPath);
|
|
}
|
|
else
|
|
{
|
|
return CShellLink_SetPath(&this->sl, NULL);
|
|
}
|
|
}
|
|
|
|
HRESULT CShellLinkA_SetRelativePath(IShellLinkA *pslA, LPCSTR pszPathRel, DWORD dwReserved)
|
|
{
|
|
WCHAR szPath[MAX_PATH];
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
|
|
MultiByteToWideChar(CP_ACP, 0,
|
|
pszPathRel, -1,
|
|
szPath, ARRAYSIZE(szPath));
|
|
return CShellLink_SetRelativePath(&this->sl, szPath, dwReserved);
|
|
}
|
|
|
|
HRESULT CShellLinkA_GetIDList(IShellLinkA *pslA, LPITEMIDLIST *ppidl)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
|
|
return CShellLink_GetIDList(&this->sl, ppidl);
|
|
}
|
|
|
|
HRESULT CShellLinkA_SetIDList(IShellLinkA *pslA, LPCITEMIDLIST pidl)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
|
|
return CShellLink_SetIDList(&this->sl, pidl);
|
|
}
|
|
|
|
HRESULT CShellLinkA_GetDescription(IShellLinkA *pslA, LPSTR pszFile, int cchMaxPath)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
VDATEINPUTBUF(pszFile, CHAR, cchMaxPath);
|
|
|
|
if (!this->pszName)
|
|
*pszFile = 0;
|
|
else
|
|
WideCharToMultiByte(CP_ACP, 0,
|
|
this->pszName, -1,
|
|
pszFile, cchMaxPath,
|
|
NULL, NULL );
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT CShellLinkA_SetDescription(IShellLinkA *pslA, LPCSTR pszFile)
|
|
{
|
|
WCHAR szName[MAX_PATH];
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
|
|
MultiByteToWideChar(CP_ACP, 0,
|
|
pszFile, -1,
|
|
szName, ARRAYSIZE(szName));
|
|
return CShellLink_SetDescription(&this->sl, szName);
|
|
}
|
|
|
|
HRESULT CShellLinkA_GetWorkingDirectory(IShellLinkA *pslA, LPSTR pszDir, int cchMaxPath)
|
|
{
|
|
WCHAR szDir[MAX_PATH];
|
|
HRESULT hr;
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
VDATEINPUTBUF(pszDir, CHAR, cchMaxPath);
|
|
|
|
hr = CShellLink_GetWorkingDirectory(&this->sl, szDir, ARRAYSIZE(szDir));
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
WideCharToMultiByte(CP_ACP, 0,
|
|
szDir, -1,
|
|
pszDir, cchMaxPath,
|
|
NULL, NULL);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CShellLinkA_SetWorkingDirectory(IShellLinkA *pslA, LPCSTR pszDir)
|
|
{
|
|
WCHAR szDir[MAX_PATH];
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
|
|
MultiByteToWideChar(CP_ACP, 0,
|
|
pszDir, -1,
|
|
szDir, ARRAYSIZE(szDir));
|
|
|
|
return CShellLink_SetWorkingDirectory(&this->sl, szDir);
|
|
}
|
|
|
|
HRESULT CShellLinkA_GetArguments(IShellLinkA *pslA, LPSTR pszArgs, int cchMaxPath)
|
|
{
|
|
WCHAR szArgs[MAX_PATH];
|
|
HRESULT hr;
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
VDATEINPUTBUF(pszArgs, CHAR, cchMaxPath);
|
|
|
|
hr = CShellLink_GetArguments(&this->sl, szArgs, ARRAYSIZE(szArgs));
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
WideCharToMultiByte(CP_ACP, 0,
|
|
szArgs, -1,
|
|
pszArgs, cchMaxPath,
|
|
NULL, NULL);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CShellLinkA_SetArguments(IShellLinkA *pslA, LPCSTR pszArgs)
|
|
{
|
|
WCHAR szArgs[MAX_PATH];
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
|
|
MultiByteToWideChar(CP_ACP, 0,
|
|
pszArgs, -1,
|
|
szArgs, ARRAYSIZE(szArgs));
|
|
|
|
return CShellLink_SetArguments(&this->sl, szArgs);
|
|
}
|
|
|
|
HRESULT CShellLinkA_GetHotkey(IShellLinkA *pslA, WORD *pwHotkey)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
return CShellLink_GetHotkey(&this->sl, pwHotkey);
|
|
}
|
|
|
|
HRESULT CShellLinkA_SetHotkey(IShellLinkA *pslA, WORD wHotkey)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
return CShellLink_SetHotkey(&this->sl, wHotkey);
|
|
}
|
|
|
|
HRESULT CShellLinkA_GetShowCmd(IShellLinkA *pslA, int *piShowCmd)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
return CShellLink_GetShowCmd(&this->sl, piShowCmd);
|
|
}
|
|
|
|
HRESULT CShellLinkA_SetShowCmd(IShellLinkA *pslA, int iShowCmd)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
return CShellLink_SetShowCmd(&this->sl, iShowCmd);
|
|
}
|
|
|
|
HRESULT CShellLinkA_GetIconLocation(IShellLinkA *pslA, LPSTR pszPath, int cchMaxPath, int *piIcon)
|
|
{
|
|
WCHAR szPath[MAX_PATH];
|
|
HRESULT hr;
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
VDATEINPUTBUF(pszPath, CHAR, cchMaxPath);
|
|
|
|
hr = CShellLink_GetIconLocation(&this->sl, szPath, ARRAYSIZE(szPath), piIcon);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
WideCharToMultiByte(CP_ACP, 0,
|
|
szPath, -1,
|
|
pszPath, cchMaxPath,
|
|
NULL, NULL);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CShellLinkA_SetIconLocation(IShellLinkA *pslA, LPCSTR pszPath, int iIcon)
|
|
{
|
|
WCHAR szPath[MAX_PATH];
|
|
LPWSTR pszPathW = szPath;
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
|
|
if (pszPath) {
|
|
MultiByteToWideChar(CP_ACP, 0,
|
|
pszPath, -1,
|
|
szPath, ARRAYSIZE(szPath));
|
|
} else {
|
|
pszPathW = NULL;
|
|
}
|
|
|
|
return CShellLink_SetIconLocation(&this->sl, pszPathW, iIcon);
|
|
}
|
|
|
|
HRESULT CShellLinkA_Resolve(IShellLinkA *pslA, HWND hwnd, DWORD fFlags)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slA, pslA);
|
|
|
|
return CShellLink_Resolve(&this->sl, hwnd, fFlags);
|
|
}
|
|
|
|
#pragma data_seg(".text", "CODE")
|
|
IShellLinkAVtbl c_ShellLinkA_Vtbl = {
|
|
CShellLinkA_QueryInterface,
|
|
CShellLinkA_AddRef,
|
|
CShellLinkA_Release,
|
|
CShellLinkA_GetPath,
|
|
CShellLinkA_GetIDList,
|
|
CShellLinkA_SetIDList,
|
|
CShellLinkA_GetDescription,
|
|
CShellLinkA_SetDescription,
|
|
CShellLinkA_GetWorkingDirectory,
|
|
CShellLinkA_SetWorkingDirectory,
|
|
CShellLinkA_GetArguments,
|
|
CShellLinkA_SetArguments,
|
|
CShellLinkA_GetHotkey,
|
|
CShellLinkA_SetHotkey,
|
|
CShellLinkA_GetShowCmd,
|
|
CShellLinkA_SetShowCmd,
|
|
CShellLinkA_GetIconLocation,
|
|
CShellLinkA_SetIconLocation,
|
|
CShellLinkA_SetRelativePath,
|
|
CShellLinkA_Resolve,
|
|
CShellLinkA_SetPath,
|
|
};
|
|
#pragma data_seg()
|
|
#endif // UNICODE
|
|
|
|
HRESULT CALLBACK CShellLink_CreateInstance(LPUNKNOWN punkOuter, REFIID riid, LPVOID *ppvOut)
|
|
{
|
|
HRESULT hres;
|
|
CShellLink *this;
|
|
|
|
*ppvOut = NULL;
|
|
|
|
// Make sure the linkinfo dll has been initialized
|
|
if (!LinkInfoDLL_Init()) {
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// does not support aggregation.
|
|
if (punkOuter)
|
|
return CLASS_E_NOAGGREGATION;
|
|
|
|
this = (CShellLink *)LocalAlloc(LPTR, SIZEOF(CShellLink));
|
|
if (!this)
|
|
return E_OUTOFMEMORY;
|
|
|
|
#ifdef ENABLE_TRACK
|
|
Tracker_InitCode();
|
|
|
|
this->ptracker = (struct CTracker *)LocalAlloc(LMEM_FIXED, sizeof(struct CTracker));
|
|
if (!this->ptracker)
|
|
{
|
|
LocalFree(this);
|
|
return(E_OUTOFMEMORY);
|
|
}
|
|
#endif
|
|
|
|
this->sl.lpVtbl = &c_ShellLink_Vtbl;
|
|
this->pf.lpVtbl = &c_PersistFile_Vtbl;
|
|
this->ps.lpVtbl = &c_PersistStream_Vtbl;
|
|
this->si.lpVtbl = &c_ShellExtInit_Vtbl;
|
|
////this->xi.lpVtbl = &c_ExtractIcon_Vtbl;
|
|
this->cm.lpVtbl = &c_ContextMenu_Vtbl;
|
|
this->dt.lpVtbl = &c_DropTarget_Vtbl;
|
|
#ifdef USE_DATA_OBJ
|
|
this->dobj.lpVtbl = &c_DataObj_Vtbl;
|
|
#endif
|
|
#ifdef UNICODE
|
|
this->slA.lpVtbl = &c_ShellLinkA_Vtbl;
|
|
#endif
|
|
#ifdef ENABLE_TRACK
|
|
this->slt.lpVtbl = &c_ShellLinkTracker_Vtbl;
|
|
#endif
|
|
|
|
this->cRef = 1;
|
|
|
|
Link_ResetPersistData(this);
|
|
|
|
//
|
|
// Note that the Release member will free the object, if QueryInterface
|
|
// failed.
|
|
//
|
|
hres = CShellLink_QueryInterface(&this->sl, riid, ppvOut);
|
|
CShellLink_Release(&this->sl);
|
|
|
|
// if we get called with something we don't expect we will
|
|
// hit this...
|
|
Assert(this->cRef == 1);
|
|
|
|
return hres; // S_OK or E_NOINTERFACE
|
|
}
|
|
|
|
#if 0
|
|
|
|
// BUGBUG: see if the retail build produces reasonable code, if not
|
|
// use something like this
|
|
|
|
#define OFF(class, itf) ((UINT)&(((class *)0)->itf))
|
|
|
|
__declspec( naked ) HRESULT CShellLink_PF_QueryInterface(IPersistFile *psf, REFIID riid, void **ppvObj)
|
|
{
|
|
_asm {
|
|
sub [esp+4], 4 // OFFSET(CShellLink, pf);
|
|
jmp CShellLink_QueryInterface
|
|
}
|
|
}
|
|
#endif
|
|
|
|
HRESULT CShellLink_PF_QueryInterface(IPersistFile *psf, REFIID riid, void **ppvObj)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, pf, psf);
|
|
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
|
|
}
|
|
|
|
ULONG CShellLink_PF_AddRef(IPersistFile *psf)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, pf, psf);
|
|
return CShellLink_AddRef(&this->sl);
|
|
}
|
|
|
|
ULONG CShellLink_PF_Release(IPersistFile *psf)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, pf, psf);
|
|
return CShellLink_Release(&this->sl);
|
|
}
|
|
|
|
HRESULT CShellLink_GetClassID(IPersistFile *psf, LPCLSID lpClassID)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, pf, psf);
|
|
*lpClassID = CLSID_ShellLink;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CShellLink_IsDirty(IPersistFile *psf)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, pf, psf);
|
|
|
|
// DebugMsg(DM_TRACE, "ShellLink::IsDirty -> %d", this->bDirty);
|
|
|
|
return this->bDirty ? S_OK : S_FALSE;
|
|
}
|
|
|
|
|
|
// link cache... when I load, save
|
|
typedef struct _PSCACHE {
|
|
struct _PSCACHE *pNext;
|
|
LPVOID pvData; // link data
|
|
UINT cbData;
|
|
TCHAR szPath[1]; // original filename
|
|
} PSCACHE;
|
|
|
|
#define PSCACHESIZE 4
|
|
|
|
// this must be SHARED so the link cache works when multiple processes
|
|
// dork with links
|
|
PSCACHE *g_pPSCache = NULL;
|
|
|
|
#if 0
|
|
void _DumpCache()
|
|
{
|
|
PSCACHE *ppsCache;
|
|
|
|
ENTERCRITICAL;
|
|
|
|
DebugMsg(DM_TRACE, TEXT("the link cache contains...."));
|
|
for (ppsCache = g_pPSCache; ppsCache; ppsCache = ppsCache->pNext)
|
|
DebugMsg(DM_TRACE, TEXT(" %s"), ppsCache->szPath);
|
|
|
|
LEAVECRITICAL;
|
|
}
|
|
#else
|
|
#define _DumpCache()
|
|
#endif
|
|
|
|
#ifdef YES_PSCACHE
|
|
PSCACHE *FindInCache(LPCTSTR pszPath, PSCACHE **ppsPrev)
|
|
{
|
|
PSCACHE *ppsCache;
|
|
|
|
*ppsPrev = NULL;
|
|
|
|
for (ppsCache = g_pPSCache; ppsCache; ppsCache = ppsCache->pNext)
|
|
{
|
|
if (!lstrcmpi(pszPath, ppsCache->szPath))
|
|
return ppsCache;
|
|
*ppsPrev = ppsCache;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
PSCACHE *FindNthItem(int n)
|
|
{
|
|
PSCACHE *ppsCache;
|
|
int i;
|
|
|
|
for (ppsCache = g_pPSCache, i = 0; ppsCache && (i < n); ppsCache = ppsCache->pNext, i++)
|
|
;
|
|
return ppsCache;
|
|
}
|
|
|
|
void FreeCacheItem(PSCACHE *ppsCache)
|
|
{
|
|
if (ppsCache->pvData)
|
|
Free(ppsCache->pvData);
|
|
|
|
Free(ppsCache);
|
|
}
|
|
#endif // YES_PSCACHE
|
|
|
|
void PSCache_Term()
|
|
{
|
|
#ifdef YES_PSCACHE
|
|
_DumpCache();
|
|
|
|
#if 0 // dont need to do this on every process detatch
|
|
ENTERCRITICAL;
|
|
|
|
while (g_pPSCache)
|
|
{
|
|
PSCACHE *pNext = g_pPSCache->pNext;
|
|
|
|
FreeCacheItem(g_pPSCache);
|
|
g_pPSCache = pNext;
|
|
}
|
|
|
|
LEAVECRITICAL;
|
|
#endif
|
|
#else
|
|
return; // Do Nothing
|
|
#endif
|
|
}
|
|
|
|
#ifdef YES_PSCACHE
|
|
PSCACHE *AllocCacheItem(LPCTSTR pszPath)
|
|
{
|
|
PSCACHE *ppsCache = (void*)Alloc(SIZEOF(PSCACHE) + ((lstrlen(pszPath)+1) * SIZEOF(TCHAR)));
|
|
if (ppsCache)
|
|
{
|
|
Assert(ppsCache->pNext == NULL);
|
|
lstrcpy(ppsCache->szPath, pszPath);
|
|
return ppsCache;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
HRESULT PSCacheSave(PSCACHE *ppsCache, IPersistStream *pps)
|
|
{
|
|
HRESULT hres = E_FAIL;
|
|
IStream *mem;
|
|
|
|
mem = CreateMemStream(NULL, 0);
|
|
|
|
if (mem == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
if (SUCCEEDED(pps->lpVtbl->Save(pps, mem, FALSE)))
|
|
{
|
|
const LARGE_INTEGER li = {0, 0};
|
|
ULARGE_INTEGER lu;
|
|
|
|
// get size of the mem stream.
|
|
mem->lpVtbl->Seek(mem, li, STREAM_SEEK_END, &lu);
|
|
mem->lpVtbl->Seek(mem, li, STREAM_SEEK_SET, NULL);
|
|
|
|
if (ppsCache->pvData)
|
|
Free(ppsCache->pvData);
|
|
|
|
ppsCache->cbData = lu.LowPart;
|
|
ppsCache->pvData = Alloc(ppsCache->cbData);
|
|
|
|
// now copy the stream to global memory
|
|
if (ppsCache->pvData != NULL)
|
|
hres = mem->lpVtbl->Read(mem, ppsCache->pvData, ppsCache->cbData, NULL);
|
|
}
|
|
|
|
mem->lpVtbl->Release(mem);
|
|
|
|
if (FAILED(hres))
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Failed to save to Link cache"));
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
#endif // YES_PSCACHE
|
|
|
|
|
|
HRESULT PSLoadThroughFileCache(IPersistStream *pps, LPCTSTR pszPath)
|
|
{
|
|
HRESULT hres;
|
|
PSCACHE *ppsCache, *ppsPrev;
|
|
|
|
#ifdef YES_PSCACHE
|
|
ENTERCRITICAL;
|
|
|
|
ppsCache = FindInCache(pszPath, &ppsPrev);
|
|
|
|
if (ppsCache)
|
|
{
|
|
IStream *mem;
|
|
|
|
Assert(ppsCache->pvData);
|
|
|
|
mem = CreateMemStream(ppsCache->pvData, ppsCache->cbData);
|
|
|
|
if (mem == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
// found the thing in the cache, load it.
|
|
// DebugMsg(DM_TRACE, "PSLoadThroughFileCache(%s) hit", pszPath);
|
|
|
|
hres = pps->lpVtbl->Load(pps, mem);
|
|
mem->lpVtbl->Release(mem);
|
|
|
|
AssertMsg(SUCCEEDED(hres), TEXT("load from cache stream failed"));
|
|
|
|
// move this item to the top of the queue
|
|
if (ppsPrev)
|
|
{
|
|
Assert(ppsCache != g_pPSCache);
|
|
|
|
ppsPrev->pNext = ppsCache->pNext;
|
|
ppsCache->pNext = g_pPSCache;
|
|
g_pPSCache = ppsCache;
|
|
}
|
|
else
|
|
{
|
|
Assert(ppsCache == g_pPSCache);
|
|
}
|
|
}
|
|
|
|
LEAVECRITICAL;
|
|
#else
|
|
ppsCache = NULL;
|
|
#endif
|
|
|
|
if (!ppsCache)
|
|
{
|
|
IStream *pstm = OpenFileStream(pszPath, OF_READ);
|
|
if (pstm)
|
|
{
|
|
hres = pps->lpVtbl->Load(pps, pstm);
|
|
pstm->lpVtbl->Release(pstm);
|
|
#define ENABLE_PS_CACHE 1
|
|
#if defined(ENABLE_PS_CACHE) && defined(YES_PSCACHE)
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
// load was successful, put it in the cache
|
|
ppsCache = AllocCacheItem(pszPath);
|
|
if (ppsCache)
|
|
{
|
|
if (SUCCEEDED(PSCacheSave(ppsCache, pps)))
|
|
{
|
|
PSCACHE *ppsLast;
|
|
|
|
ENTERCRITICAL;
|
|
|
|
ppsLast = FindNthItem(PSCACHESIZE - 1);
|
|
if (ppsLast && ppsLast->pNext)
|
|
{
|
|
Assert(ppsLast->pNext->pNext == NULL);
|
|
FreeCacheItem(ppsLast->pNext);
|
|
ppsLast->pNext = NULL;
|
|
}
|
|
|
|
ppsCache->pNext = g_pPSCache;
|
|
g_pPSCache = ppsCache;
|
|
|
|
LEAVECRITICAL;
|
|
}
|
|
else
|
|
{
|
|
FreeCacheItem(ppsCache);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Failed to create cache item"));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
hres = E_FAIL;
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
// on saves make sure the cache contents are up to date
|
|
|
|
void PSUpdateFileCache(IPersistStream *pps, LPCTSTR pszPath)
|
|
{
|
|
#ifdef YES_PSCACHE
|
|
PSCACHE *ppsCache, *ppsPrev;
|
|
|
|
ENTERCRITICAL;
|
|
|
|
ppsCache = FindInCache(pszPath, &ppsPrev);
|
|
if (ppsCache)
|
|
{
|
|
if (pps)
|
|
DebugMsg(DM_TRACE, TEXT("Link: Update cached link %s"), pszPath);
|
|
else
|
|
DebugMsg(DM_TRACE, TEXT("Link: Flush cached link %s"), pszPath);
|
|
|
|
if (pps == NULL || FAILED(PSCacheSave(ppsCache, pps)))
|
|
{
|
|
if (pps)
|
|
DebugMsg(DM_TRACE, TEXT("Link: UpdateFileCache save failed %s"), pszPath);
|
|
|
|
if (ppsPrev)
|
|
ppsPrev->pNext = ppsCache->pNext;
|
|
else
|
|
g_pPSCache = ppsCache->pNext;
|
|
|
|
FreeCacheItem(ppsCache);
|
|
}
|
|
}
|
|
|
|
LEAVECRITICAL;
|
|
#else
|
|
return;
|
|
#endif // YES_PSCACHE
|
|
}
|
|
|
|
HRESULT Link_LoadFromPIF(CShellLink *this, LPCTSTR szPath)
|
|
{
|
|
IShellLink *psl = (IShellLink*)this;
|
|
int hPif;
|
|
PROPPRG ProgramProps;
|
|
LPITEMIDLIST pidl = NULL;
|
|
|
|
//
|
|
// Too noisy, use your private flag.
|
|
//
|
|
#ifdef YOUR_PRIVATE_DEBUG_FLAG
|
|
DebugMsg(DM_TRACE, TEXT("Link_LoadFromPIF(%s)"), szPath);
|
|
#endif
|
|
|
|
hPif = PifMgr_OpenProperties(szPath, NULL, 0, 0);
|
|
|
|
if (hPif == 0)
|
|
return E_FAIL;
|
|
|
|
_fmemset(&ProgramProps, 0, SIZEOF(ProgramProps));
|
|
|
|
if (!PifMgr_GetProperties(hPif,(LPSTR)MAKEINTATOM(GROUP_PRG), &ProgramProps, SIZEOF(ProgramProps), 0))
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Link_LoadFromPIF PifMgr_GetProperties *failed*"));
|
|
return E_FAIL;
|
|
}
|
|
|
|
#if 0
|
|
DebugMsg(DM_TRACE, TEXT(" flPrg: %04X "), ProgramProps.flPrg);
|
|
DebugMsg(DM_TRACE, TEXT(" flPrgInit: %04X "), ProgramProps.flPrgInit);
|
|
DebugMsg(DM_TRACE, TEXT(" Title: %s "), ProgramProps.achTitle);
|
|
DebugMsg(DM_TRACE, TEXT(" CmdLine: %s "), ProgramProps.achCmdLine);
|
|
DebugMsg(DM_TRACE, TEXT(" WorkDir: %s "), ProgramProps.achWorkDir);
|
|
DebugMsg(DM_TRACE, TEXT(" HotKey: %04X "), ProgramProps.wHotKey);
|
|
DebugMsg(DM_TRACE, TEXT(" Icon: %s!%d "), ProgramProps.achIconFile, ProgramProps.wIconIndex);
|
|
DebugMsg(DM_TRACE, TEXT(" PIFFile: %s "), ProgramProps.achPIFFile);
|
|
#endif
|
|
|
|
#ifdef UNICODE
|
|
{
|
|
LPWSTR lpszTemp;
|
|
UINT cchTitle;
|
|
UINT cchWorkDir;
|
|
UINT cchCmdLine;
|
|
UINT cchIconFile;
|
|
UINT cchMax;
|
|
|
|
cchTitle = lstrlenA(ProgramProps.achTitle)+1;
|
|
cchWorkDir = lstrlenA(ProgramProps.achWorkDir)+1;
|
|
cchCmdLine = lstrlenA(ProgramProps.achCmdLine)+1;
|
|
cchIconFile = lstrlenA(ProgramProps.achIconFile)+1;
|
|
cchMax = cchTitle;
|
|
if ( cchWorkDir > cchMax ) cchMax = cchWorkDir;
|
|
if ( cchCmdLine > cchMax ) cchMax = cchCmdLine;
|
|
if ( cchIconFile > cchMax ) cchMax = cchIconFile;
|
|
|
|
cchMax *= SIZEOF(WCHAR); // For unicodizing
|
|
|
|
lpszTemp = alloca(cchMax); // For unicodizing
|
|
|
|
MultiByteToWideChar(CP_ACP, 0, ProgramProps.achTitle, cchTitle,
|
|
lpszTemp, cchMax);
|
|
CShellLink_SetDescription(psl, lpszTemp);
|
|
|
|
MultiByteToWideChar(CP_ACP, 0, ProgramProps.achWorkDir, cchWorkDir,
|
|
lpszTemp, cchMax);
|
|
CShellLink_SetWorkingDirectory(psl, lpszTemp);
|
|
|
|
CShellLink_SetHotkey(psl,ProgramProps.wHotKey);
|
|
|
|
MultiByteToWideChar(CP_ACP, 0, ProgramProps.achIconFile, cchIconFile,
|
|
lpszTemp, cchMax);
|
|
CShellLink_SetIconLocation(psl, lpszTemp, ProgramProps.wIconIndex);
|
|
|
|
MultiByteToWideChar(CP_ACP, 0, ProgramProps.achCmdLine, cchCmdLine,
|
|
lpszTemp, cchMax);
|
|
CShellLink_SetArguments(psl, PathGetArgs(lpszTemp));
|
|
|
|
PathRemoveArgs(lpszTemp);
|
|
|
|
// If this is a network path, we want to create a simple pidl
|
|
// instead of a full pidl to circumvent net hits
|
|
if (PathIsUNC(lpszTemp) || IsRemoteDrive(DRIVEID(lpszTemp)))
|
|
{
|
|
pidl = SHSimpleIDListFromPath( lpszTemp );
|
|
}
|
|
else
|
|
SetPIDLPath(this, NULL, lpszTemp, NULL);
|
|
}
|
|
#else
|
|
CShellLink_SetDescription(psl, ProgramProps.achTitle);
|
|
CShellLink_SetWorkingDirectory(psl, ProgramProps.achWorkDir);
|
|
CShellLink_SetArguments(psl, PathGetArgs(ProgramProps.achCmdLine));
|
|
CShellLink_SetHotkey(psl,ProgramProps.wHotKey);
|
|
CShellLink_SetIconLocation(psl, ProgramProps.achIconFile, ProgramProps.wIconIndex);
|
|
|
|
PathRemoveArgs(ProgramProps.achCmdLine);
|
|
|
|
// If this is a network path, we want to create a simple pidl
|
|
// instead of a full pidl to circumvent net hits
|
|
if (PathIsUNC(ProgramProps.achCmdLine) || IsRemoteDrive(DRIVEID(ProgramProps.achCmdLine)))
|
|
{
|
|
pidl = SHSimpleIDListFromPath( ProgramProps.achCmdLine );
|
|
}
|
|
else
|
|
SetPIDLPath(this, NULL, ProgramProps.achCmdLine, NULL);
|
|
#endif
|
|
|
|
// if a simple pidl was created, use it here...
|
|
if (pidl)
|
|
{
|
|
UpdateWorkingDir(this, pidl);
|
|
|
|
if (this->pidl)
|
|
ILFree(this->pidl);
|
|
|
|
this->pidl = pidl;
|
|
}
|
|
|
|
if (ProgramProps.flPrgInit & PRGINIT_MINIMIZED)
|
|
CShellLink_SetShowCmd(psl, SW_SHOWMINNOACTIVE);
|
|
else if (ProgramProps.flPrgInit & PRGINIT_MAXIMIZED)
|
|
CShellLink_SetShowCmd(psl, SW_SHOWMAXIMIZED);
|
|
else
|
|
CShellLink_SetShowCmd(psl, SW_SHOWNORMAL);
|
|
|
|
PifMgr_CloseProperties(hPif, 0);
|
|
|
|
this->bDirty = FALSE;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
BOOL RenameChangeExtension(LPTSTR pszPathSave, LPCTSTR pszExt, BOOL fMove)
|
|
{
|
|
TCHAR szPathSrc[MAX_PATH];
|
|
|
|
lstrcpy(szPathSrc, pszPathSave);
|
|
PathRenameExtension(pszPathSave, pszExt);
|
|
|
|
// this may fail because the source file does not exist, but we dont care
|
|
if (fMove && lstrcmpi(szPathSrc, pszPathSave) != 0)
|
|
{
|
|
DWORD dwAttrib;
|
|
|
|
PathYetAnotherMakeUniqueName(pszPathSave, pszPathSave, NULL, NULL);
|
|
dwAttrib = GetFileAttributes( szPathSrc );
|
|
if ((dwAttrib == 0xFFFFFFFF) || (dwAttrib & FILE_ATTRIBUTE_READONLY))
|
|
{
|
|
// Source file is read only, don't want to change the extension
|
|
// because we won't be able to write any changes to the file...
|
|
return FALSE;
|
|
}
|
|
Win32MoveFile(szPathSrc, pszPathSave, FALSE);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
HRESULT Link_SaveAsLink(CShellLink *this, LPCTSTR szPath)
|
|
{
|
|
HRESULT hres = E_FAIL;
|
|
IStream *pstm = OpenFileStream(szPath, OF_CREATE | OF_WRITE | OF_SHARE_DENY_WRITE);
|
|
if (pstm)
|
|
{
|
|
if (this->pszRelSource == NULL)
|
|
Link_SetRelativePath(this, szPath);
|
|
|
|
hres = CShellLink_PS_Save(&this->ps, pstm, TRUE);
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
hres = pstm->lpVtbl->Commit(pstm, 0);
|
|
}
|
|
|
|
pstm->lpVtbl->Release(pstm);
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
PSUpdateFileCache(&this->ps, szPath);
|
|
}
|
|
else
|
|
{
|
|
DeleteFile(szPath);
|
|
}
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
// out:
|
|
// pszDir MAX_PATH path to get directory, maybe with env expanded
|
|
//
|
|
// returns:
|
|
// TRUE has a working directory, pszDir filled in.
|
|
// FALSE no working dir, if the env expands to larger than the buffer size (MAX_PATH)
|
|
// this will be returned (FALSE)
|
|
//
|
|
|
|
BOOL Link_GetWorkingDir(CShellLink *psl, LPTSTR pszDir)
|
|
{
|
|
*pszDir = 0;
|
|
|
|
return psl->pszWorkingDir && psl->pszWorkingDir[0] &&
|
|
(ExpandEnvironmentStrings(psl->pszWorkingDir, pszDir, MAX_PATH) <= MAX_PATH);
|
|
}
|
|
|
|
|
|
HRESULT Link_SaveAsPIF(CShellLink *psl, LPCTSTR pszPath, BOOL fPath)
|
|
{
|
|
int hPif;
|
|
PROPPRG ProgramProps;
|
|
HRESULT hres;
|
|
TCHAR szDir[MAX_PATH];
|
|
TCHAR achPath[MAX_PATH];
|
|
|
|
//
|
|
// get filename and convert it to a short filename
|
|
//
|
|
if (fPath)
|
|
{
|
|
hres = CShellLink_GetPath(&psl->sl, achPath, ARRAYSIZE(achPath), NULL, 0);
|
|
PathGetShortPath(achPath);
|
|
|
|
Assert(!PathIsPif(achPath));
|
|
Assert(LOWORD(GetExeType(achPath)) == 0x5A4D);
|
|
Assert(PathIsPif(pszPath));
|
|
Assert(hres == S_OK);
|
|
}
|
|
else
|
|
{
|
|
lstrcpy(achPath, pszPath);
|
|
}
|
|
|
|
DebugMsg(DM_TRACE, TEXT("Link_SaveAsPIF(%s,%s)"), achPath, pszPath);
|
|
|
|
#if 0
|
|
//
|
|
// we should use OPENPROPS_INHIBITPIF to prevent PIFMGR from making a
|
|
// temp .pif file in \windows\pif but it does not work now.
|
|
//
|
|
hPif = PifMgr_OpenProperties(achPath, pszPath, 0, OPENPROPS_INHIBITPIF);
|
|
#else
|
|
hPif = PifMgr_OpenProperties(achPath, pszPath, 0, 0);
|
|
#endif
|
|
|
|
if (hPif == 0)
|
|
return E_FAIL;
|
|
|
|
if (!PifMgr_GetProperties(hPif,(LPSTR)MAKEINTATOM(GROUP_PRG), &ProgramProps, SIZEOF(ProgramProps), 0))
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Link_SaveToPIF: PifMgr_GetProperties *failed*"));
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Set a title based on the link name.
|
|
if (psl->pszName && psl->pszName[0])
|
|
#ifdef UNICODE
|
|
{
|
|
WideCharToMultiByte(CP_ACP, 0, psl->pszName, -1,
|
|
ProgramProps.achTitle, SIZEOF(ProgramProps.achTitle),
|
|
NULL, NULL);
|
|
}
|
|
#else
|
|
lstrcpyn(ProgramProps.achTitle,psl->pszName,SIZEOF(ProgramProps.achTitle));
|
|
#endif
|
|
|
|
//
|
|
// if no work dir. is given default to the dir of the app.
|
|
//
|
|
if (Link_GetWorkingDir(psl, szDir))
|
|
{
|
|
#ifdef UNICODE
|
|
WCHAR szTemp[PIFDEFPATHSIZE];
|
|
|
|
GetShortPathName(szDir, szTemp, ARRAYSIZE(szTemp));
|
|
WideCharToMultiByte(CP_ACP, 0, szTemp, -1,
|
|
ProgramProps.achWorkDir, ARRAYSIZE(ProgramProps.achWorkDir),
|
|
NULL, NULL);
|
|
#else
|
|
GetShortPathName(szDir, ProgramProps.achWorkDir, ARRAYSIZE(ProgramProps.achWorkDir));
|
|
#endif
|
|
}
|
|
else if (fPath && !PathIsUNC(achPath))
|
|
{
|
|
#ifdef UNICODE
|
|
WCHAR szTemp[PIFDEFPATHSIZE];
|
|
lstrcpyn(szTemp, achPath, ARRAYSIZE(szTemp));
|
|
PathRemoveFileSpec(szTemp);
|
|
WideCharToMultiByte(CP_ACP, 0, szTemp, -1,
|
|
ProgramProps.achWorkDir, ARRAYSIZE(ProgramProps.achWorkDir),
|
|
NULL, NULL);
|
|
#else
|
|
lstrcpyn(ProgramProps.achWorkDir, achPath, ARRAYSIZE(ProgramProps.achWorkDir));
|
|
PathRemoveFileSpec(ProgramProps.achWorkDir);
|
|
#endif
|
|
}
|
|
|
|
// And for those network share points we need to quote blanks...
|
|
PathQuoteSpaces(achPath);
|
|
|
|
//
|
|
// add the args to build the full command line
|
|
//
|
|
if (psl->pszArgs && psl->pszArgs[0])
|
|
{
|
|
lstrcat(achPath, c_szSpace);
|
|
lstrcat(achPath, psl->pszArgs);
|
|
}
|
|
|
|
if (fPath)
|
|
#ifdef UNICODE
|
|
{
|
|
WideCharToMultiByte(CP_ACP, 0, achPath, -1,
|
|
ProgramProps.achCmdLine, ARRAYSIZE(ProgramProps.achCmdLine),
|
|
NULL, NULL);
|
|
}
|
|
#else
|
|
lstrcpyn(ProgramProps.achCmdLine, achPath, ARRAYSIZE(ProgramProps.achCmdLine));
|
|
#endif
|
|
|
|
if (psl->sld.iShowCmd == SW_SHOWMAXIMIZED)
|
|
ProgramProps.flPrgInit |= PRGINIT_MAXIMIZED;
|
|
if ((psl->sld.iShowCmd == SW_SHOWMINIMIZED) || (psl->sld.iShowCmd == SW_SHOWMINNOACTIVE))
|
|
ProgramProps.flPrgInit |= PRGINIT_MINIMIZED;
|
|
|
|
if (psl->sld.wHotkey)
|
|
ProgramProps.wHotKey = psl->sld.wHotkey;
|
|
|
|
if (psl->pszIconLocation && psl->pszIconLocation[0])
|
|
{
|
|
#ifdef UNICODE
|
|
WideCharToMultiByte(CP_ACP, 0, psl->pszIconLocation, -1,
|
|
ProgramProps.achIconFile, ARRAYSIZE(ProgramProps.achIconFile),
|
|
NULL, NULL);
|
|
#else
|
|
lstrcpyn(ProgramProps.achIconFile, psl->pszIconLocation, ARRAYSIZE(ProgramProps.achIconFile));
|
|
#endif
|
|
ProgramProps.wIconIndex = psl->sld.iIcon;
|
|
}
|
|
|
|
if (!PifMgr_SetProperties(hPif, (LPSTR)MAKEINTATOM(GROUP_PRG), &ProgramProps, SIZEOF(ProgramProps), 0))
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Link_SaveToPIF: PifMgr_SetProperties *failed*"));
|
|
hres = E_FAIL;
|
|
} else {
|
|
hres = S_OK;
|
|
}
|
|
|
|
PifMgr_CloseProperties(hPif, 0);
|
|
psl->bDirty = FALSE;
|
|
|
|
return hres;
|
|
}
|
|
|
|
HRESULT Link_LoadFromFile(CShellLink *this, LPCTSTR pszPath)
|
|
{
|
|
HRESULT hres;
|
|
|
|
|
|
if (PathIsPif(pszPath))
|
|
hres = Link_LoadFromPIF(this, pszPath);
|
|
else
|
|
hres = PSLoadThroughFileCache(&this->ps, pszPath);
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
|
|
if (this->pidl && SHGetPathFromIDList(this->pidl, szPath) && !lstrcmpi(szPath, pszPath))
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Link points to itself, aaahhh!"));
|
|
hres = E_FAIL;
|
|
}
|
|
else
|
|
{
|
|
Str_SetPtr(&this->pszCurFile, pszPath);
|
|
}
|
|
}
|
|
|
|
Assert(!this->bDirty);
|
|
|
|
return hres;
|
|
}
|
|
|
|
HRESULT CShellLink_Load(IPersistFile *psf, LPCOLESTR pwszFile, DWORD grfMode)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, pf, psf);
|
|
TCHAR szPath[MAX_PATH];
|
|
|
|
OleStrToStrN(szPath, ARRAYSIZE(szPath), pwszFile, -1);
|
|
return Link_LoadFromFile(this, szPath);
|
|
}
|
|
|
|
HRESULT Link_SaveToFile(CShellLink *this, LPTSTR pszPathSave, BOOL fRemember)
|
|
{
|
|
HRESULT hres = E_FAIL;
|
|
BOOL bFileExisted;
|
|
BOOL fDosApp;
|
|
BOOL fFile;
|
|
TCHAR szPathSrc[MAX_PATH];
|
|
BOOL fWasSameFile = this->pszCurFile && (lstrcmpi(pszPathSave, this->pszCurFile) == 0);
|
|
|
|
|
|
CShellLink_GetPath(&this->sl, szPathSrc, ARRAYSIZE(szPathSrc), NULL, 0);
|
|
|
|
fFile = !(this->sld.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
|
fDosApp = fFile && LOWORD(GetExeType(szPathSrc)) == 0x5A4D;
|
|
bFileExisted = PathFileExists(pszPathSave);
|
|
|
|
//
|
|
// handle a link to link case. (or link to pif)
|
|
//
|
|
// BUGBUG we loose all new attributes, need to look into what
|
|
// a progman item to a .PIF file did in Win31. who set the hotkey,
|
|
// work dir etc. we definitly loose the icon.
|
|
//
|
|
if (fFile && (PathIsPif(szPathSrc) || PathIsLink(szPathSrc)))
|
|
{
|
|
if (RenameChangeExtension(pszPathSave, PathFindExtension(szPathSrc), fWasSameFile))
|
|
{
|
|
PSUpdateFileCache(NULL, pszPathSave); // flush cache
|
|
if (CopyFile(szPathSrc, pszPathSave, FALSE))
|
|
{
|
|
if (PathIsPif(pszPathSave))
|
|
hres = Link_SaveAsPIF(this, pszPathSave, FALSE);
|
|
else
|
|
hres = S_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hres = E_FAIL;
|
|
}
|
|
}
|
|
//
|
|
// if the linked to file is a DOS app, we need to write a .PIF file
|
|
//
|
|
else if (fDosApp)
|
|
{
|
|
if (RenameChangeExtension(pszPathSave, c_szDotPif, fWasSameFile))
|
|
{
|
|
hres = Link_SaveAsPIF(this, pszPathSave, TRUE);
|
|
}
|
|
else
|
|
{
|
|
hres = E_FAIL;
|
|
}
|
|
}
|
|
//
|
|
// else write a link file
|
|
//
|
|
else
|
|
{
|
|
if (PathIsPif(pszPathSave))
|
|
{
|
|
if (!RenameChangeExtension(pszPathSave, c_szDotLnk, fWasSameFile))
|
|
{
|
|
hres = E_FAIL;
|
|
goto Update;
|
|
}
|
|
}
|
|
|
|
|
|
hres = Link_SaveAsLink(this, pszPathSave);
|
|
}
|
|
|
|
Update:
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
// Knock out file close
|
|
SHChangeNotify(bFileExisted ? SHCNE_UPDATEITEM : SHCNE_CREATE, SHCNF_PATH, pszPathSave, NULL);
|
|
SHChangeNotify(SHCNE_FREESPACE, SHCNF_PATH, pszPathSave, NULL);
|
|
|
|
if (fRemember)
|
|
Str_SetPtr(&this->pszCurFile, pszPathSave);
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
HRESULT CShellLink_Save(IPersistFile *psf, LPCOLESTR pwszFile, BOOL fRemember)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, pf, psf);
|
|
TCHAR szSavePath[MAX_PATH];
|
|
|
|
if (pwszFile == NULL)
|
|
{
|
|
if (this->pszCurFile == NULL)
|
|
return E_FAIL; // fail
|
|
|
|
lstrcpy(szSavePath, this->pszCurFile);
|
|
}
|
|
else
|
|
{
|
|
OleStrToStrN(szSavePath, ARRAYSIZE(szSavePath), pwszFile, -1);
|
|
}
|
|
|
|
return Link_SaveToFile(this, szSavePath, fRemember);
|
|
}
|
|
|
|
HRESULT CShellLink_SaveCompleted(IPersistFile *psf, LPCOLESTR pwszFile)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, pf, psf);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CShellLink_GetCurFile(IPersistFile *psf, LPOLESTR *lplpszFileName)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, pf, psf);
|
|
// actually we have this, but we have to return IMalloc memory...
|
|
// implement this when someone actually needs this.
|
|
*lplpszFileName = NULL;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
#pragma data_seg(".text", "CODE")
|
|
IPersistFileVtbl c_PersistFile_Vtbl = {
|
|
CShellLink_PF_QueryInterface, CShellLink_PF_AddRef, CShellLink_PF_Release,
|
|
CShellLink_GetClassID,
|
|
CShellLink_IsDirty,
|
|
CShellLink_Load,
|
|
CShellLink_Save,
|
|
CShellLink_SaveCompleted,
|
|
CShellLink_GetCurFile
|
|
};
|
|
#pragma data_seg()
|
|
|
|
|
|
HRESULT CShellLink_PS_QueryInterface(IPersistStream *pps, REFIID riid, void **ppvObj)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, ps, pps);
|
|
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
|
|
}
|
|
|
|
|
|
ULONG CShellLink_PS_AddRef(IPersistStream *pps)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, ps, pps);
|
|
return CShellLink_AddRef(&this->sl);
|
|
}
|
|
|
|
ULONG CShellLink_PS_Release(IPersistStream *pps)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, ps, pps);
|
|
return CShellLink_Release(&this->sl);
|
|
}
|
|
|
|
HRESULT CShellLink_PS_GetClassID(IPersistStream *pps, CLSID *pClassID)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, ps, pps);
|
|
*pClassID = CLSID_ShellLink;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CShellLink_PS_IsDirty(IPersistStream *pps)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, ps, pps);
|
|
|
|
// DebugMsg(DM_TRACE, "ShellLink::IsDirty -> %d", this->bDirty);
|
|
|
|
return this->bDirty ? S_OK : S_FALSE;
|
|
}
|
|
|
|
|
|
HRESULT LinkInfo_LoadFromStream(IStream *pstm, PLINKINFO *ppli)
|
|
{
|
|
DWORD dwSize;
|
|
ULONG cbBytesRead;
|
|
HRESULT hres;
|
|
|
|
if (*ppli)
|
|
{
|
|
LocalFree((HLOCAL)*ppli);
|
|
*ppli = NULL;
|
|
}
|
|
|
|
hres = pstm->lpVtbl->Read(pstm, &dwSize, SIZEOF(dwSize), &cbBytesRead); // size of data
|
|
if (SUCCEEDED(hres) && (cbBytesRead == SIZEOF(dwSize)))
|
|
{
|
|
if (dwSize >= SIZEOF(dwSize)) // must be at least this big
|
|
{
|
|
/* Yes. Read remainder of LinkInfo into local memory. */
|
|
PLINKINFO pli = (void*)LocalAlloc(LPTR, dwSize);
|
|
if (pli)
|
|
{
|
|
*(DWORD *)pli = dwSize; // Copy size
|
|
|
|
dwSize -= SIZEOF(dwSize); // Read remainder of LinkInfo
|
|
|
|
hres = pstm->lpVtbl->Read(pstm, ((DWORD *)pli) + 1, dwSize, &cbBytesRead);
|
|
if (SUCCEEDED(hres) && (cbBytesRead == dwSize))
|
|
*ppli = pli; // LinkInfo read successfully
|
|
else
|
|
LocalFree((HLOCAL)pli);
|
|
}
|
|
}
|
|
}
|
|
return hres;
|
|
}
|
|
|
|
#ifdef TEST_UNICODE_LINKS
|
|
|
|
#define Stream_WriteString(pstm, psz) Stream_WriteWideString(pstm, psz)
|
|
|
|
HRESULT Stream_WriteWideString(IStream *pstm, LPCTSTR psz)
|
|
{
|
|
USHORT cch = lstrlen(psz);
|
|
HRESULT hres;
|
|
WCHAR wszBuf[MAX_PATH];
|
|
|
|
StrToOleStr(wszBuf, psz);
|
|
|
|
hres = pstm->lpVtbl->Write(pstm, &cch, SIZEOF(cch), NULL);
|
|
if (SUCCEEDED(hres))
|
|
hres = pstm->lpVtbl->Write(pstm, wszBuf, cch * SIZEOF(WCHAR), NULL);
|
|
|
|
return hres;
|
|
}
|
|
#endif
|
|
|
|
#ifdef UNICODE
|
|
HRESULT Stream_ReadAnsiString(IStream *pstm, LPSTR pwsz, UINT cchBuf)
|
|
{
|
|
USHORT cch;
|
|
HRESULT hres = pstm->lpVtbl->Read(pstm, &cch, SIZEOF(cch), NULL); // size of data
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
if (cch >= (USHORT)cchBuf)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("truncating string read(%d to %d)"), cch, cchBuf);
|
|
cch = (USHORT)cchBuf - 1; // leave room for null terminator
|
|
}
|
|
|
|
hres = pstm->lpVtbl->Read(pstm, pwsz, cch, NULL);
|
|
if (SUCCEEDED(hres))
|
|
pwsz[cch] = 0; // add NULL terminator
|
|
}
|
|
return hres;
|
|
}
|
|
#else
|
|
HRESULT Stream_ReadWideString(IStream *pstm, LPWSTR pwsz, UINT cchBuf)
|
|
{
|
|
USHORT cch;
|
|
HRESULT hres;
|
|
VDATEINPUTBUF(pwsz, WCHAR, cchBuf);
|
|
|
|
hres = pstm->lpVtbl->Read(pstm, &cch, SIZEOF(cch), NULL); // size of data
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
if (cch >= (USHORT)cchBuf)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("truncating string read(%d to %d)"), cch, cchBuf);
|
|
cch = (USHORT)cchBuf - 1; // leave room for null terminator
|
|
}
|
|
|
|
hres = pstm->lpVtbl->Read(pstm, pwsz, cch * SIZEOF(WCHAR), NULL);
|
|
if (SUCCEEDED(hres))
|
|
pwsz[cch] = 0; // add NULL terminator
|
|
}
|
|
return hres;
|
|
}
|
|
#endif
|
|
|
|
HRESULT Str_SetFromStream(IStream *pstm, LPTSTR *ppsz, BOOL bWide)
|
|
{
|
|
TCHAR szBuf[MAX_PATH];
|
|
HRESULT hres;
|
|
if (bWide)
|
|
{
|
|
#ifdef UNICODE
|
|
hres = Stream_ReadStringBuffer(pstm,szBuf,ARRAYSIZE(szBuf));
|
|
#else
|
|
WCHAR wszBuf[MAX_PATH];
|
|
hres = Stream_ReadWideString(pstm, wszBuf, ARRAYSIZE(wszBuf));
|
|
if (SUCCEEDED(hres))
|
|
OleStrToStr(szBuf, wszBuf);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef UNICODE
|
|
CHAR szAnsiBuf[MAX_PATH];
|
|
hres = Stream_ReadAnsiString(pstm, szAnsiBuf, ARRAYSIZE(szAnsiBuf));
|
|
if (SUCCEEDED(hres))
|
|
MultiByteToWideChar(CP_ACP, 0,
|
|
szAnsiBuf, -1,
|
|
szBuf, ARRAYSIZE(szBuf));
|
|
#else
|
|
hres = Stream_ReadStringBuffer(pstm, szBuf, ARRAYSIZE(szBuf));
|
|
#endif
|
|
}
|
|
if (SUCCEEDED(hres))
|
|
Str_SetPtr(ppsz, szBuf);
|
|
return hres;
|
|
}
|
|
|
|
// this is to support future extensions of link files
|
|
//
|
|
// after all the known link data is read we will read
|
|
// in blocks of data of the following format:
|
|
// [cbBytes:DWORD] [ cbBytes of data], ...
|
|
//
|
|
//
|
|
void LoadExtraData(CShellLink *this, IStream *pstm)
|
|
{
|
|
ULONG cbBytes, uTotalSize, uSizeToRead;
|
|
DWORD cbSize;
|
|
HRESULT hres;
|
|
LPBYTE pTemp = NULL;
|
|
|
|
if (this->pExtraData)
|
|
{
|
|
LocalFree((HLOCAL)this->pExtraData);
|
|
this->pExtraData = NULL;
|
|
}
|
|
|
|
// an empty extra data is null terminated with a 0 DWORD
|
|
uTotalSize = 0;
|
|
|
|
while (TRUE)
|
|
{
|
|
|
|
hres = pstm->lpVtbl->Read(pstm, &cbSize, SIZEOF(cbSize), &cbBytes);
|
|
if (SUCCEEDED(hres) && (cbBytes == SIZEOF(cbSize)))
|
|
{
|
|
LPBYTE pReadData;
|
|
|
|
if (cbSize < SIZEOF(cbSize))
|
|
break;
|
|
|
|
if (pTemp)
|
|
{
|
|
pTemp = (LPBYTE)LocalReAlloc((HLOCAL)this->pExtraData,
|
|
uTotalSize + cbSize + SIZEOF(DWORD),
|
|
LMEM_ZEROINIT | LMEM_MOVEABLE
|
|
);
|
|
if (pTemp)
|
|
{
|
|
this->pExtraData = (LPSTR)pTemp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pTemp = this->pExtraData = (void *)LocalAlloc( LPTR, uTotalSize + cbSize + SIZEOF(DWORD) );
|
|
|
|
}
|
|
if (!pTemp)
|
|
break;
|
|
|
|
uSizeToRead = cbSize - SIZEOF(cbSize);
|
|
pReadData = pTemp + uTotalSize;
|
|
|
|
|
|
DebugMsg(DM_TRACE, TEXT("Reading extra data size:%d"), cbSize);
|
|
|
|
hres = pstm->lpVtbl->Read(pstm, pReadData + SIZEOF(cbSize), uSizeToRead, &cbBytes);
|
|
if (SUCCEEDED(hres) && (cbBytes == uSizeToRead))
|
|
{
|
|
// got all of the extra data, comit it
|
|
*((UNALIGNED DWORD *)pReadData) = cbSize;
|
|
uTotalSize += cbSize;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
HRESULT CShellLink_PS_Load(IPersistStream *pps, IStream *pstm)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, ps, pps);
|
|
ULONG cbBytes;
|
|
DWORD cbSize;
|
|
HRESULT hres;
|
|
|
|
Link_ResetPersistData(this); // clear out our state
|
|
|
|
hres = pstm->lpVtbl->Read(pstm, &cbSize, SIZEOF(cbSize), &cbBytes);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
if (cbBytes == SIZEOF(cbSize))
|
|
{
|
|
if (cbSize == SIZEOF(this->sld))
|
|
{
|
|
hres = pstm->lpVtbl->Read(pstm, (LPBYTE)&this->sld + SIZEOF(cbSize), SIZEOF(this->sld) - SIZEOF(cbSize), &cbBytes);
|
|
if (SUCCEEDED(hres) && cbBytes == (SIZEOF(this->sld) - SIZEOF(cbSize)) && IsEqualGUID(&this->sld.clsid, &CLSID_ShellLink))
|
|
{
|
|
#ifdef ENABLE_TRACK
|
|
EXP_TRACKER * lpData;
|
|
DWORD dwTrackerSize = 0;
|
|
#endif
|
|
this->sld.cbSize = SIZEOF(this->sld);
|
|
|
|
switch (this->sld.iShowCmd) {
|
|
case SW_SHOWNORMAL:
|
|
case SW_SHOWMINNOACTIVE:
|
|
case SW_SHOWMAXIMIZED:
|
|
break;
|
|
|
|
default:
|
|
DebugMsg(DM_TRACE, TEXT("Shortcut Load, mapping bogus ShowCmd: %d"), this->sld.iShowCmd);
|
|
this->sld.iShowCmd = SW_SHOWNORMAL;
|
|
break;
|
|
}
|
|
|
|
// read all of the members
|
|
|
|
if (this->sld.dwFlags & SLDF_HAS_ID_LIST)
|
|
{
|
|
hres = ILLoadFromStream(pstm, &this->pidl);
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_FORCE_NO_LINKINFO) && this->pli)
|
|
{
|
|
DebugMsg( DM_TRACE, TEXT("labotimizing link"));
|
|
LocalFree((HLOCAL)this->pli);
|
|
this->pli = NULL;
|
|
}
|
|
}
|
|
|
|
// BUGBUG: this part is not unicode ready, talk to daviddi
|
|
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & (SLDF_HAS_LINK_INFO)))
|
|
{
|
|
hres = LinkInfo_LoadFromStream(pstm, &this->pli);
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_FORCE_NO_LINKINFO) && this->pli)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("labotimizing link"));
|
|
LocalFree((HLOCAL)this->pli);
|
|
this->pli = NULL;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_NAME))
|
|
hres = Str_SetFromStream(pstm, &this->pszName, this->sld.dwFlags & SLDF_UNICODE);
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_RELPATH))
|
|
hres = Str_SetFromStream(pstm, &this->pszRelPath, this->sld.dwFlags & SLDF_UNICODE);
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_WORKINGDIR))
|
|
hres = Str_SetFromStream(pstm, &this->pszWorkingDir, this->sld.dwFlags & SLDF_UNICODE);
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_ARGS))
|
|
hres = Str_SetFromStream(pstm, &this->pszArgs, this->sld.dwFlags & SLDF_UNICODE);
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_ICONLOCATION))
|
|
hres = Str_SetFromStream(pstm, &this->pszIconLocation, this->sld.dwFlags & SLDF_UNICODE);
|
|
|
|
if (SUCCEEDED(hres))
|
|
LoadExtraData(this, pstm);
|
|
|
|
#ifdef ENABLE_TRACK
|
|
if (g_fNewTrack)
|
|
{
|
|
// load the tracker from extra data
|
|
|
|
for( lpData = (EXP_TRACKER *)this->pExtraData;
|
|
lpData && lpData->cbSize && lpData->dwSignature!=EXP_TRACKER_SIG;
|
|
lpData = (EXP_TRACKER *) (((LPBYTE)lpData) + lpData->cbSize)
|
|
);
|
|
|
|
if (lpData && lpData->cbSize) {
|
|
if (SUCCEEDED(hres))
|
|
hres = Tracker_Load(this->ptracker,
|
|
lpData->abTracker,
|
|
lpData->cbSize - sizeof(EXP_TRACKER));
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CShellLink_PS_Load: no EXP_TRACKER data found."));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (SUCCEEDED(hres))
|
|
this->bDirty = FALSE;
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("failed to read link struct"));
|
|
hres = E_FAIL; // invalid file size
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("invalid length field in link:%d"), cbBytes);
|
|
hres = E_FAIL; // invalid file size
|
|
}
|
|
}
|
|
else if (cbBytes == 0)
|
|
{
|
|
// zero length file is ok
|
|
this->sld.cbSize = 0;
|
|
|
|
DebugMsg(DM_TRACE, TEXT("Empty link file, that's cool"));
|
|
}
|
|
else
|
|
{
|
|
hres = E_FAIL; // invalid file size
|
|
}
|
|
}
|
|
return hres;
|
|
}
|
|
|
|
HRESULT LinkInfo_SaveToStream(IStream *pstm, PCLINKINFO pcli)
|
|
{
|
|
DWORD dwSize;
|
|
ULONG cbBytes;
|
|
HRESULT hres;
|
|
|
|
dwSize = *(DWORD *)pcli; // Get LinkInfo size
|
|
|
|
hres = pstm->lpVtbl->Write(pstm, pcli, dwSize, &cbBytes);
|
|
if (SUCCEEDED(hres) && (cbBytes != dwSize))
|
|
hres = E_FAIL;
|
|
return hres;
|
|
}
|
|
|
|
HRESULT Link_SaveExtraData(CShellLink *this, IStream *pstm)
|
|
{
|
|
LPBYTE pData;
|
|
DWORD dwSize;
|
|
HRESULT hr = S_OK;
|
|
|
|
for (pData = (LPBYTE)this->pExtraData; pData; pData += dwSize)
|
|
{
|
|
ULONG cbBytes;
|
|
dwSize = *(UNALIGNED DWORD *)pData;
|
|
|
|
if (dwSize == 0)
|
|
break;
|
|
|
|
DebugMsg(DM_TRACE, TEXT("Writing extra data block, size:%d"), dwSize);
|
|
|
|
if (FAILED(hr = pstm->lpVtbl->Write(pstm, pData, dwSize, &cbBytes)))
|
|
break;
|
|
|
|
if (cbBytes != dwSize)
|
|
{
|
|
hr = STG_E_MEDIUMFULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return(S_OK);
|
|
}
|
|
|
|
|
|
// This will just add a section to the end of the extra data -- it does
|
|
// not check to see if the section already exists, etc.
|
|
void Link_AddExtraDataSection(CShellLink *this, DWORD UNALIGNED * lpData)
|
|
{
|
|
|
|
DWORD dwCurSize = 0;
|
|
LPBYTE lpTmp = NULL;
|
|
|
|
|
|
// Add data to the "Extra Data" section of the link.
|
|
if (!this->pExtraData) {
|
|
|
|
// allocation for block being passed in + DWORD marker
|
|
this->pExtraData = LocalAlloc( LPTR, SIZEOF( DWORD ) + *lpData );
|
|
if (this->pExtraData )
|
|
{
|
|
CopyMemory( this->pExtraData, lpData, *lpData );
|
|
this->bDirty = TRUE;
|
|
}
|
|
|
|
} else {
|
|
|
|
// find the end and add up the size as we go.
|
|
DWORD UNALIGNED * lpEntry;
|
|
|
|
for ( lpEntry = (DWORD UNALIGNED *)this->pExtraData;
|
|
lpEntry && *lpEntry!=0;
|
|
dwCurSize += *lpEntry,
|
|
lpEntry = (DWORD UNALIGNED *) (((LPBYTE)lpEntry)+ *lpEntry)
|
|
);
|
|
|
|
// size of new xtra data section is sum of all current sections +
|
|
// size of new block + sizeof DWORD marker.
|
|
lpTmp = (LPBYTE)LocalReAlloc( (HLOCAL)this->pExtraData,
|
|
dwCurSize + SIZEOF(DWORD) + *lpData,
|
|
LMEM_ZEROINIT | LMEM_MOVEABLE
|
|
);
|
|
if (lpTmp) {
|
|
|
|
this->pExtraData = lpTmp;
|
|
lpTmp += dwCurSize;
|
|
CopyMemory( lpTmp, lpData, *lpData );
|
|
*(DWORD UNALIGNED *)(lpTmp + *lpData) = 0;
|
|
this->bDirty = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This will remove the extra data section with the given signature.
|
|
void Link_RemoveExtraDataSection(CShellLink *this, DWORD dwSig)
|
|
{
|
|
LPEXP_SZ_LINK lpData = (LPEXP_SZ_LINK)this->pExtraData;
|
|
|
|
while( lpData && lpData->cbSize!=0 && (lpData->dwSignature!=dwSig) )
|
|
{
|
|
lpData = (LPEXP_SZ_LINK) (((LPBYTE)lpData) + lpData->cbSize);
|
|
}
|
|
|
|
|
|
if (lpData && (lpData->cbSize!=0))
|
|
{
|
|
LONG lNewSize;
|
|
LPVOID lpVoid;
|
|
LPEXP_SZ_LINK lpTmp;
|
|
DWORD dwSizeOfBlockToRemove;
|
|
DWORD dwSize = SIZEOF(DWORD); // There is a DWORD marker at the end
|
|
// of the xtra data section
|
|
|
|
dwSizeOfBlockToRemove = lpData->cbSize;
|
|
|
|
// Find how much xtra data exists past this section
|
|
for( lpTmp=(LPEXP_SZ_LINK)((LPBYTE)lpData + dwSizeOfBlockToRemove);
|
|
lpTmp && lpTmp->cbSize!=0;
|
|
dwSize += lpTmp->cbSize,
|
|
lpTmp = (LPEXP_SZ_LINK)(((LPBYTE)lpTmp) + lpTmp->cbSize)
|
|
);
|
|
|
|
// Move the remaining chunk to cover gap of this section,
|
|
// thus removing it
|
|
MoveMemory( lpData, (LPBYTE)lpData + lpData->cbSize, dwSize );
|
|
lNewSize = LocalSize( this->pExtraData ) - dwSizeOfBlockToRemove;
|
|
|
|
// Now, resize extra data block as appropriate
|
|
if (lNewSize > SIZEOF(DWORD))
|
|
{
|
|
if (NULL != (lpVoid = LocalReAlloc( (HLOCAL)this->pExtraData, lNewSize, LMEM_ZEROINIT | LMEM_MOVEABLE )))
|
|
{
|
|
this->pExtraData = lpVoid;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We've removed the last section, delete the whole deal
|
|
LocalFree( (HLOCAL)this->pExtraData );
|
|
this->pExtraData = NULL;
|
|
|
|
}
|
|
this->bDirty = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
// This will return a given extra data section in a link, or NULL if the section
|
|
// with the requested signature doesn't exist.
|
|
LPVOID Link_ReadExtraDataSection(CShellLink *this, DWORD dwSig)
|
|
{
|
|
LPEXP_SZ_LINK lpData = (LPEXP_SZ_LINK)this->pExtraData;
|
|
LONG dwSize = SIZEOF(DWORD);
|
|
LPEXP_SZ_LINK lpTmp;
|
|
LPVOID lpSection = NULL;
|
|
|
|
// walk through data looking for given signature
|
|
while( lpData && lpData->cbSize && (lpData->dwSignature!=dwSig) )
|
|
{
|
|
lpData = (LPEXP_SZ_LINK)(((LPBYTE)lpData) + lpData->cbSize);
|
|
}
|
|
|
|
// if we found the section, create a buffer and copy over the data
|
|
if (lpData && lpData->cbSize)
|
|
{
|
|
lpSection = HeapAlloc( GetProcessHeap(), 0, lpData->cbSize );
|
|
if (lpSection)
|
|
CopyMemory( lpSection, lpData, lpData->cbSize );
|
|
}
|
|
|
|
return lpSection;
|
|
|
|
}
|
|
#ifdef ENABLE_TRACK
|
|
//
|
|
// Replaces the tracker extra data with current tracker state
|
|
//
|
|
HRESULT Link_AddTrackerData(CShellLink *this)
|
|
{
|
|
/*typedef struct {
|
|
DWORD cbSize; // Size of this extra data block
|
|
DWORD dwSignature; // signature of this extra data block
|
|
BYTE abTracker[ 1 ]; //
|
|
} EXP_TRACKER;*/
|
|
|
|
// get the size of the tracker and get some memory
|
|
ULONG ulSize = Tracker_GetSize(this->ptracker);
|
|
EXP_TRACKER *pExpTracker = (EXP_TRACKER *)LocalAlloc(LPTR, ulSize + sizeof(EXP_TRACKER));
|
|
if (!pExpTracker)
|
|
{
|
|
return(E_OUTOFMEMORY);
|
|
}
|
|
|
|
Link_RemoveExtraDataSection( this, EXP_TRACKER_SIG );
|
|
|
|
pExpTracker->cbSize = ulSize + sizeof(EXP_TRACKER);
|
|
pExpTracker->dwSignature = EXP_TRACKER_SIG;
|
|
Tracker_Save(this->ptracker, pExpTracker->abTracker);
|
|
|
|
Link_AddExtraDataSection(this, &pExpTracker->cbSize);
|
|
DebugMsg(DM_TRACE, TEXT("Link_AddTrackerData: EXP_TRACKER at %08X."), &pExpTracker->cbSize);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
HRESULT CShellLink_PS_Save(IPersistStream *pps, IStream *pstm, BOOL fClearDirty)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, ps, pps);
|
|
ULONG cbBytes;
|
|
HRESULT hres;
|
|
|
|
this->sld.cbSize = SIZEOF(this->sld);
|
|
this->sld.clsid = CLSID_ShellLink;
|
|
// this->sld.dwFlags = 0;
|
|
// We do the following & instead of zeroing because the SLDF_HAS_EXP_SZ and
|
|
// SLDF_RUN_IN_SEPARATE are passed to us and are valid, the others can be
|
|
// reconstructed below, but these two can not, so we need to preserve it!
|
|
// (BUGBUG, this may change when we go to property stream storage for
|
|
// the xtra data -- RICKTU).
|
|
this->sld.dwFlags &= (SLDF_HAS_EXP_SZ | SLDF_RUN_IN_SEPARATE);
|
|
|
|
if (this->pszRelSource)
|
|
Link_SetRelativePath(this, this->pszRelSource);
|
|
|
|
#ifdef UNICODE
|
|
this->sld.dwFlags |= SLDF_UNICODE;
|
|
#endif
|
|
|
|
if (this->pidl)
|
|
this->sld.dwFlags |= SLDF_HAS_ID_LIST;
|
|
if (this->pli)
|
|
this->sld.dwFlags |= SLDF_HAS_LINK_INFO;
|
|
|
|
if (this->pszName && this->pszName[0])
|
|
this->sld.dwFlags |= SLDF_HAS_NAME;
|
|
if (this->pszRelPath && this->pszRelPath[0])
|
|
this->sld.dwFlags |= SLDF_HAS_RELPATH;
|
|
if (this->pszWorkingDir && this->pszWorkingDir[0])
|
|
this->sld.dwFlags |= SLDF_HAS_WORKINGDIR;
|
|
if (this->pszArgs && this->pszArgs[0])
|
|
this->sld.dwFlags |= SLDF_HAS_ARGS;
|
|
if (this->pszIconLocation && this->pszIconLocation[0])
|
|
this->sld.dwFlags |= SLDF_HAS_ICONLOCATION;
|
|
|
|
hres = pstm->lpVtbl->Write(pstm, &this->sld, SIZEOF(this->sld), &cbBytes);
|
|
|
|
if (SUCCEEDED(hres) && (cbBytes == SIZEOF(this->sld)))
|
|
{
|
|
if (this->pidl)
|
|
hres = ILSaveToStream(pstm, this->pidl);
|
|
|
|
if (SUCCEEDED(hres) && this->pli)
|
|
hres = LinkInfo_SaveToStream(pstm, this->pli);
|
|
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_NAME))
|
|
hres = Stream_WriteString(pstm, this->pszName);
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_RELPATH))
|
|
hres = Stream_WriteString(pstm, this->pszRelPath);
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_WORKINGDIR))
|
|
hres = Stream_WriteString(pstm, this->pszWorkingDir);
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_ARGS))
|
|
hres = Stream_WriteString(pstm, this->pszArgs);
|
|
if (SUCCEEDED(hres) && (this->sld.dwFlags & SLDF_HAS_ICONLOCATION))
|
|
hres = Stream_WriteString(pstm, this->pszIconLocation);
|
|
|
|
#ifdef ENABLE_TRACK
|
|
if (g_fNewTrack && SUCCEEDED(hres) && Tracker_IsDirty(this->ptracker))
|
|
hres = Link_AddTrackerData(this);
|
|
#endif
|
|
|
|
if (SUCCEEDED(hres))
|
|
hres = Link_SaveExtraData(this, pstm);
|
|
|
|
#ifdef TEST_EXTRA_DATA
|
|
if (bTestExtra && SUCCEEDED(hres))
|
|
{
|
|
DWORD dwSize;
|
|
|
|
// NULL block
|
|
dwSize = 4;
|
|
pstm->lpVtbl->Write(pstm, &dwSize, SIZEOF(dwSize), NULL);
|
|
|
|
// string block
|
|
dwSize = 6;
|
|
pstm->lpVtbl->Write(pstm, &dwSize, SIZEOF(dwSize), NULL);
|
|
pstm->lpVtbl->Write(pstm, TEXT("CG"), 2, NULL);
|
|
|
|
// string block
|
|
dwSize = 5;
|
|
pstm->lpVtbl->Write(pstm, &dwSize, SIZEOF(dwSize), NULL);
|
|
pstm->lpVtbl->Write(pstm, TEXT("X"), 1, NULL);
|
|
|
|
// string block
|
|
dwSize = 10;
|
|
pstm->lpVtbl->Write(pstm, &dwSize, SIZEOF(dwSize), NULL);
|
|
pstm->lpVtbl->Write(pstm, TEXT("123456"), 6, NULL);
|
|
}
|
|
#endif // TEST_EXTRA_DATA
|
|
|
|
if (SUCCEEDED(hres) && fClearDirty)
|
|
this->bDirty = FALSE;
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Failed to write link"));
|
|
hres = E_FAIL;
|
|
}
|
|
return hres;
|
|
}
|
|
|
|
HRESULT CShellLink_GetSizeMax(IPersistStream *pps, ULARGE_INTEGER *pcbSize)
|
|
{
|
|
// CShellLink *this = IToClass(CShellLink, ps, pps);
|
|
|
|
pcbSize->LowPart = 16 * 1024; // 16k? who knows...
|
|
pcbSize->HighPart = 0;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
#pragma data_seg(".text", "CODE")
|
|
IPersistStreamVtbl c_PersistStream_Vtbl = {
|
|
CShellLink_PS_QueryInterface, CShellLink_PS_AddRef, CShellLink_PS_Release,
|
|
CShellLink_PS_GetClassID,
|
|
CShellLink_PS_IsDirty,
|
|
CShellLink_PS_Load,
|
|
CShellLink_PS_Save,
|
|
CShellLink_GetSizeMax
|
|
};
|
|
#pragma data_seg()
|
|
|
|
|
|
|
|
#ifdef ENABLE_TRACK
|
|
|
|
//+--------------------------------------------------
|
|
//
|
|
// Functions: IUnknown for IShellLinkTracker
|
|
//
|
|
// These routines simply forward the requests to
|
|
// the ShellLink Object.
|
|
//
|
|
//+--------------------------------------------------
|
|
|
|
HRESULT CShellLink_SLT_QueryInterface(IShellLinkTracker *pslt, REFIID riid, void **ppvObj)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slt, pslt);
|
|
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
|
|
}
|
|
|
|
|
|
ULONG CShellLink_SLT_AddRef(IShellLinkTracker *pslt)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slt, pslt);
|
|
return CShellLink_AddRef(&this->sl);
|
|
}
|
|
|
|
ULONG CShellLink_SLT_Release(IShellLinkTracker *pslt)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slt, pslt);
|
|
return CShellLink_Release(&this->sl);
|
|
}
|
|
|
|
//+------------------------------------------------------------------
|
|
//
|
|
// Function: CShellLink_SLT_Initialize
|
|
//
|
|
// This function is a C=>C++ thunk routine.
|
|
//
|
|
//+------------------------------------------------------------------
|
|
|
|
|
|
HRESULT CShellLink_SLT_Initialize(IShellLinkTracker *pslt, DWORD dwTrackFlags)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slt, pslt);
|
|
|
|
Tracker_SetCreationFlags( this->ptracker, dwTrackFlags );
|
|
return S_OK;
|
|
}
|
|
|
|
//+------------------------------------------------------------------
|
|
//
|
|
// Function: CShellLink_SLT_GetTrackFlags
|
|
//
|
|
// This function is a C=>C++ thunk routine.
|
|
//
|
|
//+------------------------------------------------------------------
|
|
|
|
|
|
HRESULT CShellLink_SLT_GetTrackFlags(IShellLinkTracker *pslt, DWORD *pdwTrackFlags)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slt, pslt);
|
|
|
|
Tracker_GetCreationFlags( this->ptracker, pdwTrackFlags );
|
|
return S_OK;
|
|
}
|
|
|
|
//+------------------------------------------------------------------
|
|
//
|
|
// Function: CShellLink_SLT_Resolve
|
|
//
|
|
// Purpose: Provide the implementation for
|
|
// IShellLinkTracker::Resolve
|
|
//
|
|
// Inputs: [IShellLinkTracker*] psl
|
|
// - Pointer to the interface
|
|
// [HWND] hwnd
|
|
// - The parent window (which could be the desktop).
|
|
// [DWORD] fFlags
|
|
// - Flags from the SLR_FLAGS enumeration.
|
|
// [DWORD] dwTrackFlags
|
|
// - Restrictions on the link-tracking algorithm (TRACK_* flags).
|
|
// [DWORD] dwTickCountDeadline
|
|
// - Timeout, WRT GetTickCount(), on the link-tracking.
|
|
// 0 indicates infinity.
|
|
// [DWORD] dwReserved
|
|
// - Reserved for future expansion.
|
|
//
|
|
// Outputs: [HRESULT]
|
|
// - S_OK resolution was successful
|
|
// S_FALSE user canceled
|
|
//
|
|
// Algorithm: Call on Link_Resolve to perform the resolution.
|
|
//
|
|
// Notes: Check IPersistFile::IsDirty after calling this to see
|
|
// if the link info has changed.
|
|
//
|
|
//+------------------------------------------------------------------
|
|
|
|
|
|
HRESULT CShellLink_SLT_Resolve(IShellLinkTracker *pslt,
|
|
HWND hwnd,
|
|
DWORD fFlags,
|
|
DWORD dwTrackFlags,
|
|
DWORD dwTickCountDeadline,
|
|
DWORD dwReserved)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, slt, pslt);
|
|
|
|
return ResolveLink( this,
|
|
hwnd,
|
|
fFlags,
|
|
dwTrackFlags,
|
|
dwTickCountDeadline,
|
|
dwReserved );
|
|
|
|
} // CShellLink_SLT_Resolve
|
|
|
|
//+-------------------------------------------------
|
|
//
|
|
// The IShellLinkTracker VTable
|
|
//
|
|
//+-------------------------------------------------
|
|
|
|
#pragma data_seg(".text", "CODE")
|
|
IShellLinkTrackerVtbl c_ShellLinkTracker_Vtbl = {
|
|
CShellLink_SLT_QueryInterface, CShellLink_SLT_AddRef, CShellLink_SLT_Release,
|
|
CShellLink_SLT_Initialize,
|
|
CShellLink_SLT_GetTrackFlags,
|
|
CShellLink_SLT_Resolve
|
|
};
|
|
#pragma data_seg()
|
|
|
|
#endif // ENABLE_TRACK
|
|
|
|
HRESULT CShellLink_SI_QueryInterface(IShellExtInit *psei, REFIID riid, void **ppvObj)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, si, psei);
|
|
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
|
|
}
|
|
|
|
ULONG CShellLink_SI_AddRef(IShellExtInit *psei)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, si, psei);
|
|
return CShellLink_AddRef(&this->sl);
|
|
}
|
|
|
|
ULONG CShellLink_SI_Release(IShellExtInit *psei)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, si, psei);
|
|
return CShellLink_Release(&this->sl);
|
|
}
|
|
|
|
HRESULT CShellLink_Initialize(IShellExtInit *psei, LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, si, psei);
|
|
HRESULT hres;
|
|
|
|
Assert(this->sld.iShowCmd == SW_SHOWNORMAL);
|
|
|
|
if (pdtobj)
|
|
{
|
|
STGMEDIUM medium;
|
|
FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
|
|
hres = pdtobj->lpVtbl->GetData(pdtobj, &fmte, &medium);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
DragQueryFile(medium.hGlobal, 0, szPath, ARRAYSIZE(szPath));
|
|
hres = Link_LoadFromFile(this, szPath);
|
|
|
|
SHReleaseStgMedium(&medium);
|
|
}
|
|
}
|
|
else
|
|
hres = E_FAIL;
|
|
|
|
return hres;
|
|
}
|
|
|
|
#pragma data_seg(".text", "CODE")
|
|
IShellExtInitVtbl c_ShellExtInit_Vtbl = {
|
|
CShellLink_SI_QueryInterface, CShellLink_SI_AddRef, CShellLink_SI_Release,
|
|
CShellLink_Initialize,
|
|
};
|
|
#pragma data_seg()
|
|
|
|
HRESULT CShellLink_CM_QueryInterface(IContextMenu2 *pcm, REFIID riid, void **ppvObj)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, cm, pcm);
|
|
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
|
|
}
|
|
|
|
ULONG CShellLink_CM_AddRef(IContextMenu2 *pcm)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, cm, pcm);
|
|
return CShellLink_AddRef(&this->sl);
|
|
}
|
|
|
|
ULONG CShellLink_CM_Release(IContextMenu2 *pcm)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, cm, pcm);
|
|
return CShellLink_Release(&this->sl);
|
|
}
|
|
|
|
HRESULT CShellLink_QueryContextMenu(IContextMenu2 *pcm, HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, cm, pcm);
|
|
|
|
if (this->pcmTarget == NULL)
|
|
{
|
|
HRESULT hres = Link_GetUIObject(this, NULL, &IID_IContextMenu, &this->pcmTarget);
|
|
if (FAILED(hres))
|
|
return hres;
|
|
|
|
Assert(this->pcmTarget);
|
|
}
|
|
|
|
// save these if in case we need to rebuild the cm because the resolve change the
|
|
// target of the link
|
|
|
|
this->indexMenuSave = indexMenu;
|
|
this->idCmdFirstSave = idCmdFirst;
|
|
this->idCmdLastSave = idCmdLast;
|
|
this->uFlagsSave = uFlags;
|
|
|
|
return this->pcmTarget->lpVtbl->QueryContextMenu(this->pcmTarget, hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags | CMF_VERBSONLY);
|
|
}
|
|
|
|
// BUGBUG: CANONICAL_VERB_NAME fixes some bugs where the implementaton of the
|
|
// context menu changes when the shortcut becomes dirty. but causes some
|
|
// others where the targtets don't implement cananonical verbs... so we leave
|
|
// this turned off for win95. we should fix this later.
|
|
|
|
HRESULT CShellLink_InvokeCommandAsync(IContextMenu2 *pcm, LPCMINVOKECOMMANDINFO pici)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, cm, pcm);
|
|
HRESULT hres;
|
|
#ifdef CANONICAL_VERB_NAME
|
|
TCHAR szVerb[32];
|
|
#endif
|
|
|
|
TCHAR szWorkingDir[MAX_PATH];
|
|
LPTSTR lpDirectory = NULL;
|
|
#ifdef UNICODE
|
|
CHAR szWorkingDirAnsi[MAX_PATH];
|
|
#endif
|
|
|
|
if (this->pcmTarget == NULL)
|
|
return E_FAIL;
|
|
|
|
#ifdef CANONICAL_VERB_NAME
|
|
szVerb[0] = 0;
|
|
|
|
// if needed, get the canonical name in case the IContextMenu changes as
|
|
// a result of the resolve call BUT only do this for folders (to be safe)
|
|
// as that is the only case where this happens
|
|
// sepcifically we resolve from a D:\ -> \\SERVER\SHARE
|
|
|
|
if (HIWORD(pici->lpVerb) == 0 && (this->sld.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
this->pcmTarget->lpVtbl->GetCommandString(this->pcmTarget, LOWORD(pici->lpVerb), GCS_VERB, NULL, szVerb, ARRAYSIZE(szVerb));
|
|
#endif
|
|
|
|
Assert(!this->bDirty);
|
|
|
|
// BUGBUG: check for NOUI flag in pici
|
|
|
|
hres = CShellLink_Resolve(&this->sl, pici->hwnd, 0);
|
|
|
|
if (hres == S_OK)
|
|
{
|
|
if (this->bDirty)
|
|
{
|
|
// the context menu we have for this link is out of date, free it
|
|
this->pcmTarget->lpVtbl->Release(this->pcmTarget);
|
|
this->pcmTarget = NULL;
|
|
|
|
hres = Link_GetUIObject(this, NULL, &IID_IContextMenu, &this->pcmTarget);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
HMENU hmenu = CreatePopupMenu();
|
|
if (hmenu)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("rebuilding cm after link resolve"));
|
|
|
|
hres = this->pcmTarget->lpVtbl->QueryContextMenu(this->pcmTarget,
|
|
hmenu, this->indexMenuSave, this->idCmdFirstSave, this->idCmdLastSave, this->uFlagsSave | CMF_VERBSONLY);
|
|
|
|
DestroyMenu(hmenu);
|
|
}
|
|
}
|
|
|
|
// don't really care if this fails...
|
|
CShellLink_Save(&this->pf, NULL, TRUE);
|
|
}
|
|
#ifdef CANONICAL_VERB_NAME
|
|
else
|
|
{
|
|
szVerb[0] = 0;
|
|
Assert(SUCCEEDED(hres));
|
|
}
|
|
#endif
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
TCHAR szArgs[MAX_PATH];
|
|
TCHAR szExpArgs[MAX_PATH];
|
|
TCHAR szTitle[MAX_PATH];
|
|
CMINVOKECOMMANDINFOEX ici;
|
|
#ifdef UNICODE
|
|
CHAR szArgsAnsi[MAX_PATH];
|
|
#endif
|
|
|
|
if (pici->cbSize > SIZEOF(CMINVOKECOMMANDINFOEX))
|
|
{
|
|
hmemcpy(&ici,pici,SIZEOF(CMINVOKECOMMANDINFOEX));
|
|
// BUGBUG - We should probably alloc another use it to retain size
|
|
ici.cbSize = SIZEOF(CMINVOKECOMMANDINFOEX);
|
|
}
|
|
else
|
|
{
|
|
memset(&ici,0,SIZEOF(ici));
|
|
hmemcpy(&ici,pici,pici->cbSize);
|
|
ici.cbSize = SIZEOF(ici);
|
|
}
|
|
|
|
#ifdef CANONICAL_VERB_NAME
|
|
if (szVerb[0])
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Mapping cmd %d to verb %s"), LOWORD(pici->lpVerb), szVerb);
|
|
ici.lpVerb = szVerb;
|
|
}
|
|
#endif
|
|
// build the args from those passed in cated on the end of the the link args
|
|
|
|
lstrcpyn(szArgs, this->pszArgs ? this->pszArgs : c_szNULL, ARRAYSIZE(szArgs));
|
|
if (ici.lpParameters)
|
|
{
|
|
int nArgLen = lstrlen(szArgs);
|
|
LPCTSTR lpParameters;
|
|
#ifdef UNICODE
|
|
WCHAR szParameters[MAX_PATH];
|
|
|
|
if (ici.cbSize < SIZEOF(CMINVOKECOMMANDINFOEX)
|
|
|| (ici.fMask & CMIC_MASK_UNICODE) != CMIC_MASK_UNICODE)
|
|
{
|
|
MultiByteToWideChar(CP_ACP, 0,
|
|
ici.lpParameters, -1,
|
|
szParameters, ARRAYSIZE(szParameters));
|
|
lpParameters = szParameters;
|
|
}
|
|
else
|
|
{
|
|
lpParameters = ici.lpParametersW;
|
|
}
|
|
#else
|
|
lpParameters = ici.lpParameters;
|
|
#endif
|
|
lstrcpyn(szArgs + nArgLen, c_szSpace, ARRAYSIZE(szArgs) - nArgLen - 1);
|
|
lstrcpyn(szArgs + nArgLen + 1, lpParameters, ARRAYSIZE(szArgs) - nArgLen - 2);
|
|
}
|
|
|
|
// Expand environment strings in szArgs
|
|
ExpandEnvironmentStrings( szArgs,
|
|
szExpArgs,
|
|
ARRAYSIZE(szExpArgs)
|
|
);
|
|
szExpArgs[ARRAYSIZE(szExpArgs)-1] = TEXT('\0');
|
|
|
|
#ifdef UNICODE
|
|
WideCharToMultiByte(CP_ACP, 0,
|
|
szExpArgs, -1,
|
|
szArgsAnsi, ARRAYSIZE(szArgsAnsi),
|
|
NULL, NULL);
|
|
ici.lpParameters = szArgsAnsi;
|
|
ici.lpParametersW = szExpArgs;
|
|
ici.fMask |= CMIC_MASK_UNICODE;
|
|
#else
|
|
ici.lpParameters = szExpArgs;
|
|
#endif
|
|
|
|
// if we have a working dir in the link over ride what is passed in
|
|
|
|
if (Link_GetWorkingDir(this, szWorkingDir))
|
|
{
|
|
if (PathIsDirectory(szWorkingDir))
|
|
lpDirectory = szWorkingDir;
|
|
#ifdef MYDIR_LIVES
|
|
// We need to test for
|
|
else if (lstrcmpi (szWorkingDir, c_szMyDirTag) == 0)
|
|
{
|
|
// we need to substitute in the current setting
|
|
// for the personal folder. Note if this is UNC this
|
|
// will only work for 32 bit applications. We may want
|
|
// to check this and possibly substitute a drive in
|
|
// for the user...
|
|
if (SHGetSpecialFolderPath(NULL, szWorkingDir, CSIDL_PERSONAL, TRUE))
|
|
{
|
|
if (PathIsDirectory(szWorkingDir))
|
|
lpDirectory = szWorkingDir;
|
|
}
|
|
}
|
|
#endif
|
|
if ( lpDirectory )
|
|
{
|
|
#ifdef UNICODE
|
|
WideCharToMultiByte(CP_ACP, 0,
|
|
lpDirectory, -1,
|
|
szWorkingDirAnsi, ARRAYSIZE(szWorkingDirAnsi),
|
|
NULL, NULL );
|
|
ici.lpDirectory = szWorkingDirAnsi;
|
|
ici.lpDirectoryW = lpDirectory;
|
|
#else
|
|
ici.lpDirectory = lpDirectory;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifdef WINNT
|
|
// set RUN IN SEPARATE VDM if needed
|
|
if (this->sld.dwFlags & SLDF_RUN_IN_SEPARATE)
|
|
{
|
|
ici.fMask |= CMIC_MASK_FLAG_SEP_VDM;
|
|
}
|
|
#endif
|
|
|
|
// and of course use our hotkey
|
|
|
|
if (this->sld.wHotkey)
|
|
{
|
|
ici.dwHotKey = this->sld.wHotkey;
|
|
ici.fMask |= CMIC_MASK_HOTKEY;
|
|
}
|
|
|
|
// override normal runs, but let special show cmds through
|
|
|
|
if (ici.nShow == SW_SHOWNORMAL)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("using shorcut show cmd"));
|
|
ici.nShow = this->sld.iShowCmd;
|
|
}
|
|
|
|
#ifdef WINNT
|
|
//
|
|
// On NT we want to pass the title to the
|
|
// thing that we are about to start.
|
|
//
|
|
if (!(ici.fMask & CMIC_MASK_HASLINKNAME) && !(ici.fMask & CMIC_MASK_HASTITLE))
|
|
{
|
|
if (this->pszCurFile)
|
|
{
|
|
lstrcpyn(szTitle,PathFindFileName(this->pszCurFile),ARRAYSIZE(szTitle));
|
|
PathRemoveExtension(szTitle);
|
|
#ifdef UNICODE
|
|
ici.lpTitle = NULL; // Title is one or the other...
|
|
ici.lpTitleW = this->pszCurFile;
|
|
#else
|
|
ici.lpTitle = this->pszCurFile;
|
|
#endif
|
|
ici.fMask |= CMIC_MASK_HASLINKNAME;
|
|
}
|
|
}
|
|
#endif
|
|
Assert((ici.nShow > SW_HIDE) && (ici.nShow <= SW_MAX));
|
|
|
|
hres = this->pcmTarget->lpVtbl->InvokeCommand(this->pcmTarget,
|
|
(LPCMINVOKECOMMANDINFO)&ici);
|
|
}
|
|
}
|
|
return hres;
|
|
}
|
|
|
|
// Structure which encapsulates the paramters needed for InvokeCommand (so
|
|
// that we can pass both parameters though a single LPARAM in CreateThread)
|
|
|
|
typedef struct
|
|
{
|
|
IContextMenu2 * pcm;
|
|
CMINVOKECOMMANDINFOEX ici;
|
|
} ICMPARAMS;
|
|
|
|
#define ICM_BASE_SIZE (SIZEOF(ICMPARAMS) - SIZEOF(CMINVOKECOMMANDINFOEX))
|
|
|
|
// CShellLink_InvokeCommandWorker
|
|
//
|
|
// Runs as a separate thread, does the actual work of calling the "real"
|
|
// InvokeCommand
|
|
|
|
DWORD CShellLink_InvokeCommandWorker(LPVOID pVoid)
|
|
{
|
|
HRESULT hr;
|
|
|
|
ICMPARAMS * pParams = (ICMPARAMS *) pVoid;
|
|
|
|
hr = CShellLink_InvokeCommandAsync(pParams->pcm, (LPCMINVOKECOMMANDINFO) &pParams->ici);
|
|
|
|
pParams->pcm->lpVtbl->Release(pParams->pcm);
|
|
LocalFree(pParams);
|
|
|
|
return (DWORD) hr;
|
|
}
|
|
|
|
// CShellLink_InvokeCommand
|
|
//
|
|
// Function that spins a thread to do the real work, which has been moved into
|
|
// CShellLink_InvokeCommandSync.
|
|
|
|
HRESULT CShellLink_InvokeCommand(IContextMenu2 *pcm, LPCMINVOKECOMMANDINFO piciIn)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
HANDLE hThread;
|
|
DWORD dwID;
|
|
DWORD cbSize;
|
|
DWORD cbBaseSize;
|
|
ICMPARAMS * pParams;
|
|
CHAR * pPos;
|
|
DWORD cchVerb,
|
|
cchParameters,
|
|
cchDirectory,
|
|
cchVerbW, // Last 3 are unused ifndef Unicode
|
|
cchParametersW,
|
|
cchDirectoryW;
|
|
|
|
|
|
LPCMINVOKECOMMANDINFOEX pici = (LPCMINVOKECOMMANDINFOEX) piciIn;
|
|
|
|
#ifdef UNICODE
|
|
WCHAR * pPosW;
|
|
const BOOL fUnicode = pici->cbSize >= SIZEOF(CMINVOKECOMMANDINFOEX) &&
|
|
(pici->fMask & CMIC_MASK_UNICODE) == CMIC_MASK_UNICODE;
|
|
#endif
|
|
|
|
if (0 == (piciIn->fMask & CMIC_MASK_ASYNCOK))
|
|
{
|
|
// Caller didn't indicate that Async startup was OK, so we call
|
|
// InvokeCommandAync SYNCHRONOUSLY
|
|
|
|
return CShellLink_InvokeCommandAsync(pcm, piciIn);
|
|
}
|
|
|
|
// Calc how much space we will need to duplicate the INVOKECOMMANDINFO
|
|
|
|
cbBaseSize = ICM_BASE_SIZE + max(piciIn->cbSize, sizeof(CMINVOKECOMMANDINFOEX));
|
|
|
|
// One byte slack in case of Unicode roundup for pPosW, below
|
|
|
|
cbSize = cbBaseSize + 1;
|
|
|
|
if (HIWORD(pici->lpVerb))
|
|
{
|
|
cbSize += (cchVerb = pici->lpVerb ? (lstrlenA(pici->lpVerb) + 1) : 0 ) * SIZEOF(CHAR);
|
|
}
|
|
cbSize += (cchParameters = pici->lpParameters ? (lstrlenA(pici->lpParameters) + 1) : 0 ) * SIZEOF(CHAR);
|
|
cbSize += (cchDirectory = pici->lpDirectory ? (lstrlenA(pici->lpDirectory) + 1) : 0 ) * SIZEOF(CHAR);
|
|
|
|
#ifdef UNICODE
|
|
if (HIWORD(pici->lpVerbW))
|
|
{
|
|
cbSize += (cchVerbW = pici->lpVerbW ? (lstrlenW(pici->lpVerbW) + 1) : 0 ) * SIZEOF(WCHAR);
|
|
}
|
|
cbSize += (cchParametersW= pici->lpParametersW? (lstrlenW(pici->lpParametersW) + 1) : 0 ) * SIZEOF(WCHAR);
|
|
cbSize += (cchDirectoryW = pici->lpDirectoryW ? (lstrlenW(pici->lpDirectoryW) + 1) : 0 ) * SIZEOF(WCHAR);
|
|
#endif
|
|
|
|
pParams = (ICMPARAMS *) LocalAlloc(LPTR, cbSize);
|
|
if (NULL == pParams)
|
|
{
|
|
return (hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
// Text data will start going in right after the structure
|
|
|
|
pPos = (CHAR *)((LPBYTE)pParams + cbBaseSize);
|
|
|
|
// Start with a copy of the static fields
|
|
|
|
CopyMemory(&pParams->ici, pici, pici->cbSize);
|
|
pcm->lpVtbl->AddRef(pcm);
|
|
pParams->pcm = pcm;
|
|
|
|
// Walk along and dupe all of the string pointer fields
|
|
|
|
if (HIWORD(pici->lpVerb))
|
|
{
|
|
pPos += cchVerb ? lstrcpyA(pPos, pici->lpVerb), pParams->ici.lpVerb = pPos, cchVerb : 0;
|
|
}
|
|
pPos += cchParameters ? lstrcpyA(pPos, pici->lpParameters), pParams->ici.lpParameters = pPos, cchParameters : 0;
|
|
pPos += cchDirectory ? lstrcpyA(pPos, pici->lpDirectory), pParams->ici.lpDirectory = pPos, cchDirectory : 0;
|
|
|
|
#ifdef UNICODE
|
|
|
|
pPosW = (WCHAR *) ((DWORD)pPos & 0x1 ? pPos + 1 : pPos); // Ensure Unicode alignment
|
|
|
|
if (HIWORD(pici->lpVerbW))
|
|
{
|
|
pPosW += cchVerbW ? lstrcpyW(pPosW, pici->lpVerbW), pParams->ici.lpVerbW = pPosW, cchVerbW : 0;
|
|
}
|
|
pPosW += cchParametersW? lstrcpyW(pPosW, pici->lpParametersW),pParams->ici.lpParametersW= pPosW, cchParametersW : 0;
|
|
pPosW += cchDirectoryW ? lstrcpyW(pPosW, pici->lpDirectoryW), pParams->ici.lpDirectoryW = pPosW, cchDirectoryW : 0;
|
|
#endif
|
|
|
|
// Pass all of the info off to the worker thread that will call the actual
|
|
// InvokeCommand API for us
|
|
|
|
hThread = CreateThread(NULL, 0, CShellLink_InvokeCommandWorker, (LPVOID) pParams, 0, &dwID);
|
|
if (NULL == hThread)
|
|
{
|
|
// Couldn't start the thread, so the onus is on us to clean up
|
|
|
|
pParams->pcm->lpVtbl->Release(pParams->pcm);
|
|
LocalFree(pParams);
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
DECLAREWAITCURSOR;
|
|
SetWaitCursor();
|
|
|
|
// We give the async thread a little time to complete, during which we
|
|
// put up the busy cursor. This is solely to let the user see that
|
|
// some work is being done...
|
|
|
|
#define ASYNC_CMIC_TIMEOUT 750
|
|
|
|
if (WAIT_OBJECT_0 == WaitForSingleObject(hThread, ASYNC_CMIC_TIMEOUT))
|
|
{
|
|
// For consistency, we always return S_OK, but if you wanted to, you could
|
|
// return the actual return value from InvokeCommand in those cases where
|
|
// it completed in time like this:
|
|
//
|
|
// DWORD dwRetVal;
|
|
// if (GetExitCodeThread(hThread, &dwRetVal))
|
|
// {
|
|
// hr = (HRESULT) dwRetVal;
|
|
// }
|
|
}
|
|
|
|
CloseHandle(hThread);
|
|
ResetWaitCursor();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CShellLink_GetCommandString(IContextMenu2 *pcm, UINT idCmd, UINT wFlags, UINT *pmf, LPSTR pszName, UINT cchMax)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, cm, pcm);
|
|
VDATEINPUTBUF(pszName, TCHAR, cchMax);
|
|
|
|
if (this->pcmTarget)
|
|
return this->pcmTarget->lpVtbl->GetCommandString(this->pcmTarget, idCmd, wFlags, pmf, pszName, cchMax);
|
|
else
|
|
return E_FAIL;
|
|
}
|
|
|
|
HRESULT CShellLink_HandleMenuMsg(IContextMenu2 *pcm, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
CShellLink *this = IToClass(CShellLink, cm, pcm);
|
|
IContextMenu2 *pcm2;
|
|
|
|
if (this->pcmTarget && SUCCEEDED(this->pcmTarget->lpVtbl->QueryInterface(this->pcmTarget, &IID_IContextMenu2, &pcm2)))
|
|
{
|
|
HRESULT hres = pcm2->lpVtbl->HandleMenuMsg(pcm2, uMsg, wParam, lParam);
|
|
pcm2->lpVtbl->Release(pcm2);
|
|
return hres;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
#pragma data_seg(".text", "CODE")
|
|
IContextMenu2Vtbl c_ContextMenu_Vtbl = {
|
|
CShellLink_CM_QueryInterface, CShellLink_CM_AddRef, CShellLink_CM_Release,
|
|
CShellLink_QueryContextMenu,
|
|
CShellLink_InvokeCommand,
|
|
CShellLink_GetCommandString,
|
|
CShellLink_HandleMenuMsg,
|
|
};
|
|
#pragma data_seg()
|
|
|
|
|
|
STDMETHODIMP CShellLink_DT_QueryInterface(IDropTarget *pdropt, REFIID riid, void **ppvObj)
|
|
{
|
|
CShellLink* this = IToClass(CShellLink, dt, pdropt);
|
|
return CShellLink_QueryInterface(&this->sl, riid, ppvObj);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CShellLink_DT_AddRef(IDropTarget *pdropt)
|
|
{
|
|
CShellLink* this = IToClass(CShellLink, dt, pdropt);
|
|
return CShellLink_AddRef(&this->sl);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CShellLink_DT_Release(IDropTarget *pdropt)
|
|
{
|
|
CShellLink* this = IToClass(CShellLink, dt, pdropt);
|
|
return CShellLink_Release(&this->sl);
|
|
}
|
|
|
|
STDMETHODIMP CShellLink_DragEnter(IDropTarget *pdropt, IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
|
|
{
|
|
CShellLink* this = IToClass(CShellLink, dt, pdropt);
|
|
Assert(this->pdtSrc);
|
|
this->grfKeyStateLast = grfKeyState;
|
|
return this->pdtSrc->lpVtbl->DragEnter(this->pdtSrc, pDataObj, grfKeyState, pt, pdwEffect);
|
|
}
|
|
|
|
STDMETHODIMP CShellLink_DragOver(IDropTarget *pdropt, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
|
|
{
|
|
CShellLink* this = IToClass(CShellLink, dt, pdropt);
|
|
Assert(this->pdtSrc);
|
|
if (!this->pdtSrc)
|
|
return E_UNEXPECTED;
|
|
this->grfKeyStateLast = grfKeyState;
|
|
return this->pdtSrc->lpVtbl->DragOver(this->pdtSrc, grfKeyState, pt, pdwEffect);
|
|
}
|
|
|
|
STDMETHODIMP CShellLink_DragLeave(IDropTarget *pdropt)
|
|
{
|
|
CShellLink* this = IToClass(CShellLink, dt, pdropt);
|
|
Assert(this->pdtSrc);
|
|
if (!this->pdtSrc)
|
|
return E_UNEXPECTED;
|
|
return this->pdtSrc->lpVtbl->DragLeave(this->pdtSrc);
|
|
}
|
|
|
|
extern HWND HKGetSetUIOwner(HWND hwndOwner, BOOL fSet);
|
|
|
|
STDMETHODIMP CShellLink_Drop(IDropTarget *pdropt, IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
|
|
{
|
|
CShellLink* this = IToClass(CShellLink, dt, pdropt);
|
|
HRESULT hres;
|
|
HWND hwndOwner = HKGetSetUIOwner(NULL, FALSE); // HACK
|
|
|
|
Assert(this->pdtSrc);
|
|
if (!this->pdtSrc)
|
|
return E_UNEXPECTED;
|
|
|
|
//
|
|
// We should leave from the un-resolved drop target.
|
|
//
|
|
this->pdtSrc->lpVtbl->DragLeave(this->pdtSrc);
|
|
|
|
hres = CShellLink_Resolve(&this->sl, hwndOwner, 0);
|
|
|
|
if (hres == S_OK)
|
|
{
|
|
IDropTarget *pdtSrcResolved;
|
|
hres = Link_GetUIObject(this, hwndOwner, &IID_IDropTarget, (LPVOID*)&pdtSrcResolved);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
DWORD dwEffectOrg = *pdwEffect;
|
|
pdtSrcResolved->lpVtbl->DragEnter(pdtSrcResolved, pDataObj, this->grfKeyStateLast, pt, pdwEffect);
|
|
*pdwEffect = dwEffectOrg; // restore it
|
|
hres = pdtSrcResolved->lpVtbl->DragOver(pdtSrcResolved, this->grfKeyStateLast, pt, pdwEffect);
|
|
if (SUCCEEDED(hres) && *pdwEffect)
|
|
{
|
|
*pdwEffect = dwEffectOrg; // restore it
|
|
hres = pdtSrcResolved->lpVtbl->Drop(pdtSrcResolved, pDataObj, grfKeyState, pt, pdwEffect);
|
|
}
|
|
else
|
|
{
|
|
hres = pdtSrcResolved->lpVtbl->DragLeave(pdtSrcResolved);
|
|
}
|
|
pdtSrcResolved->lpVtbl->Release(pdtSrcResolved);
|
|
}
|
|
}
|
|
else if (FAILED(hres) && (hres != HRESULT_FROM_WIN32(ERROR_CANCELLED)))
|
|
{
|
|
//
|
|
// We can safely assume that CShellLink_Resolve never fails
|
|
// on non-file system objects.
|
|
//
|
|
TCHAR szLinkSrc[MAX_PATH];
|
|
if (SHGetPathFromIDList(this->pidl, szLinkSrc))
|
|
{
|
|
ShellMessageBox(HINST_THISDLL,
|
|
hwndOwner,
|
|
MAKEINTRESOURCE(IDS_ENUMERR_PATHNOTFOUND),
|
|
MAKEINTRESOURCE(IDS_LINKERROR),
|
|
MB_OK | MB_ICONEXCLAMATION, NULL, szLinkSrc);
|
|
|
|
}
|
|
}
|
|
return hres;
|
|
}
|
|
|
|
#pragma data_seg(DATASEG_READONLY)
|
|
IDropTargetVtbl c_DropTarget_Vtbl =
|
|
{
|
|
CShellLink_DT_QueryInterface, CShellLink_DT_AddRef, CShellLink_DT_Release,
|
|
CShellLink_DragEnter,
|
|
CShellLink_DragOver,
|
|
CShellLink_DragLeave,
|
|
CShellLink_Drop,
|
|
};
|
|
#pragma data_seg()
|
|
|
|
HRESULT CShellLink_GetDropTarget(CShellLink *this, IDropTarget **ppdt)
|
|
{
|
|
IDropTarget *pdtSrc;
|
|
HRESULT hres;
|
|
|
|
*ppdt = NULL;
|
|
|
|
hres = Link_GetUIObject(this, NULL, &IID_IDropTarget, (LPVOID*)&pdtSrc);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
if (this->pdtSrc)
|
|
this->pdtSrc->lpVtbl->Release(pdtSrc);
|
|
|
|
this->pdtSrc = pdtSrc;
|
|
*ppdt = &this->dt;
|
|
CShellLink_AddRef(&this->sl);
|
|
}
|
|
return hres;
|
|
}
|