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

1193 lines
36 KiB
C

// link.c linke property page implementation
#include "shellprv.h"
#pragma hdrstop
#include "fstreex.h"
#include "docfind.h"
#include "lnkcon.h"
#include "trayp.h" // for WMTRAY_ messages
#include "util.h" // for GetIconLocationFromExt
#define LNKM_ACTIVATEOTHER (WM_USER + 100) // don't conflict with DM_ messages
// This string defined in shlink.c - hack to allow user to set working dir to $$
// and have it map to whatever "My Documents" is mapped to.
void _UpdateLinkIcon(LPLINKPROP_DATA plpd, HICON hIcon)
{
if (!hIcon)
hIcon = SHGetFileIcon(NULL, plpd->szFile, 0, SHGFI_LARGEICON);
if (hIcon)
{
HICON hOldIcon = (HICON)SendDlgItemMessage(plpd->hDlg, IDD_ITEMICON, STM_SETICON, (WPARAM)hIcon, 0L);
if (hOldIcon)
DestroyIcon(hOldIcon);
}
}
// make sure LFN paths are nicly quoted and have args at the end
void PathComposeWithArgs(LPTSTR pszPath, LPTSTR pszArgs)
{
PathQuoteSpaces(pszPath);
if (pszArgs[0])
{
int len = lstrlen(pszPath);
if (len < (MAX_PATH - 3))
{ // 1 for null, 1 for space, 1 for arg
pszPath[len++] = TEXT(' ');
lstrcpyn(pszPath + len, pszArgs, MAX_PATH - len);
}
}
}
// do the inverse of the above, parse pszPath into a unquoted
// path string and put the args in pszArgs
// returns:
// TRUE we verified the thing exists
// FALSE it may not exist
BOOL PathSeperateArgs(LPTSTR pszPath, LPTSTR pszArgs)
{
LPTSTR pszT;
PathRemoveBlanks(pszPath);
// if the unquoted sting exists as a file just use it
if (PathFileExistsAndAttributes(pszPath, NULL))
{
*pszArgs = 0;
return TRUE;
}
pszT = PathGetArgs(pszPath);
if (*pszT)
*(pszT - 1) = 0;
lstrcpy(pszArgs, pszT);
PathUnquoteSpaces(pszPath);
return FALSE;
}
// put a path into an edit field, doing quoting as necessary
void SetDlgItemPath(HWND hdlg, int id, LPTSTR pszPath)
{
PathQuoteSpaces(pszPath);
SetDlgItemText(hdlg, id, pszPath);
}
// get a path from an edit field, unquoting as possible
void GetDlgItemPath(HWND hdlg, int id, LPTSTR pszPath)
{
GetDlgItemText(hdlg, id, pszPath, MAX_PATH);
PathRemoveBlanks(pszPath);
PathUnquoteSpaces(pszPath);
}
const int c_iShowCmds[] = {
SW_SHOWNORMAL,
SW_SHOWMINNOACTIVE,
SW_SHOWMAXIMIZED,
};
void _DisableAllChildren(HWND hwnd)
{
HWND hwndChild;
for (hwndChild = GetWindow(hwnd, GW_CHILD); hwndChild != NULL; hwndChild = GetWindow(hwndChild, GW_HWNDNEXT))
{
// we don't want to disable the static text controls (makes the dlg look bad)
if (!(SendMessage(hwndChild, WM_GETDLGCODE, 0, 0) & DLGC_STATIC))
{
EnableWindow(hwndChild, FALSE);
}
}
}
void _GetPathAndArgs(LPLINKPROP_DATA plpd, LPTSTR pszPath, LPTSTR pszArgs)
{
GetDlgItemText(plpd->hDlg, IDD_FILENAME, pszPath, MAX_PATH);
PathSeperateArgs(pszPath, pszArgs);
}
#ifdef WINNT
// Returns fully qualified path to target of link, and # of characters
// in fully qualifed path as return value
INT _GetTargetOfLink(LPLINKPROP_DATA plpd, LPTSTR pszTarget )
{
TCHAR szFile[MAX_PATH], szArgs[MAX_PATH];
INT cch = 0;
*pszTarget = 0;
_GetPathAndArgs(plpd, szFile, szArgs);
if (szFile[0])
{
LPTSTR psz;
TCHAR szExp[MAX_PATH];
if (SHExpandEnvironmentStrings(szFile, szExp, ARRAYSIZE(szExp)))
{
cch = SearchPath(NULL, szExp, TEXT(".EXE"), MAX_PATH, pszTarget, &psz);
}
}
return cch;
}
// Do checking of the .exe type in the background so the UI doesn't
// get hung up while we scan. This is particularly important with
// the .exe is over the network or on a floppy.
STDAPI_(DWORD) _LinkCheckThreadProc(void *pv)
{
LINKPROP_DATA *plpd = (LINKPROP_DATA *)pv;
BOOL fCheck = TRUE, fEnable = FALSE;
DebugMsg(DM_TRACE, TEXT("_LinkCheckThreadProc created and running"));
while (plpd->bCheckRunInSep)
{
WaitForSingleObject( plpd->hCheckNow, INFINITE);
ResetEvent(plpd->hCheckNow);
if (plpd->bCheckRunInSep)
{
TCHAR szFullFile[MAX_PATH];
DWORD cch = _GetTargetOfLink(plpd, szFullFile);
if ((cch != 0) && (cch < ARRAYSIZE(szFullFile)))
{
LONG lBinaryType;
if (PathIsUNC( szFullFile ) || IsRemoteDrive(DRIVEID(szFullFile)))
{
// Net Path, let the user decide...
fCheck = FALSE;
fEnable = TRUE;
}
else if (GetBinaryType( szFullFile, &lBinaryType) && (lBinaryType==SCS_WOW_BINARY))
{
// 16-bit binary, let the user decide, default to same VDM
fCheck = FALSE;
fEnable = TRUE;
}
else
{
// 32-bit binary, or non-net path. don't enable the control
fCheck = TRUE;
fEnable = FALSE;
}
}
else
{
// Error getting target of the link. don't enable the control
fCheck = TRUE;
fEnable = FALSE;
}
CheckDlgButton(plpd->hDlg, IDD_RUNINSEPARATE, fCheck ? 1 : 0);
EnableWindow(GetDlgItem(plpd->hDlg, IDD_RUNINSEPARATE), fEnable);
}
}
CloseHandle(plpd->hCheckNow);
DebugMsg(DM_TRACE, TEXT("_LinkCheckThreadProc exiting now..."));
return 0;
}
// shut down the thread
void _StopThread(LINKPROP_DATA *plpd)
{
if (plpd->hThread)
{
plpd->bCheckRunInSep = FALSE;
SetEvent(plpd->hCheckNow);
if (WaitForSingleObject(plpd->hThread, 2000) == WAIT_TIMEOUT)
TerminateThread(plpd->hThread, (DWORD)-1); // Blow it away!
CloseHandle(plpd->hThread);
plpd->hThread = NULL;
}
}
#endif // WINNT
LPVOID _GetLinkExtraData(IShellLink* psl, DWORD dwSig)
{
LPVOID pDataBlock = NULL;
IShellLinkDataList *psld;
if (SUCCEEDED(psl->lpVtbl->QueryInterface(psl, &IID_IShellLinkDataList, (void **)&psld)))
{
psld->lpVtbl->CopyDataBlock(psld, dwSig, &pDataBlock);
psld->lpVtbl->Release(psld);
}
return pDataBlock;
}
DWORD _GetLinkFlags(IShellLink *psl)
{
DWORD dw = 0;
IShellLinkDataList *psld;
if (SUCCEEDED(psl->lpVtbl->QueryInterface(psl, &IID_IShellLinkDataList, (void **)&psld)))
{
psld->lpVtbl->GetFlags(psld, &dw);
psld->lpVtbl->Release(psld);
}
return dw;
}
void _SetLinkFlags(IShellLink *psl, DWORD dwFlags)
{
IShellLinkDataList *psld;
if (SUCCEEDED(psl->lpVtbl->QueryInterface(psl, &IID_IShellLinkDataList, (void **)&psld)))
{
psld->lpVtbl->SetFlags(psld, dwFlags);
psld->lpVtbl->Release(psld);
}
}
// Initializes the generic link dialog box.
void _UpdateLinkDlg(LPLINKPROP_DATA plpd, BOOL bUpdatePath)
{
WORD wHotkey;
int i, iShowCmd;
TCHAR szBuffer[MAX_PATH];
TCHAR szCommand[MAX_PATH];
HRESULT hres;
SHFILEINFO sfi;
BOOL fIsDarwinLink = _GetLinkFlags(plpd->psl) & SLDF_HAS_DARWINID;
// do this here so we don't slow down the loading
// of other pages
if (!bUpdatePath)
{
IPersistFile *ppf;
if (SUCCEEDED(plpd->psl->lpVtbl->QueryInterface(plpd->psl, &IID_IPersistFile, &ppf)))
{
WCHAR wszPath[MAX_PATH];
SHTCharToUnicode(plpd->szFile, wszPath, ARRAYSIZE(wszPath));
hres = ppf->lpVtbl->Load(ppf, wszPath, 0);
ppf->lpVtbl->Release(ppf);
if (FAILED(hres))
{
LoadString(HINST_THISDLL, IDS_LINKNOTLINK, szBuffer, ARRAYSIZE(szBuffer));
SetDlgItemText(plpd->hDlg, IDD_FILETYPE, szBuffer);
_DisableAllChildren(plpd->hDlg);
DebugMsg(DM_TRACE, TEXT("Shortcut IPersistFile::Load() failed %x"), hres);
return;
}
}
}
SHGetFileInfo(plpd->szFile, 0, &sfi, SIZEOF(sfi), SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES);
SetDlgItemText(plpd->hDlg, IDD_NAME, sfi.szDisplayName);
// we need to check for darwin links here so that we can gray out
// things that don't apply to darwin
if (fIsDarwinLink)
{
LPEXP_DARWIN_LINK pDarwinData;
TCHAR szAppState[MAX_PATH];
DWORD cchAppState = ARRAYSIZE(szAppState);
HWND hwndTargetType = GetDlgItem(plpd->hDlg, IDD_FILETYPE);
// disable the children
_DisableAllChildren(plpd->hDlg);
// then special case the icon and the "Target type:" text
_UpdateLinkIcon(plpd, NULL);
pDarwinData = _GetLinkExtraData(plpd->psl, EXP_DARWIN_ID_SIG);
if (pDarwinData && IsDarwinAdW(pDarwinData->szwDarwinID))
{
// the app is advertised (e.g. not installed), but will be faulted in on first use
LoadString(HINST_THISDLL, IDS_APP_NOT_FAULTED_IN, szAppState, ARRAYSIZE(szAppState));
}
else
{
// the darwin app is installed
LoadString(HINST_THISDLL, IDS_APP_FAULTED_IN, szAppState, ARRAYSIZE(szAppState));
}
SetWindowText(hwndTargetType, szAppState);
EnableWindow(hwndTargetType, TRUE);
// if we can ge the package name, put that in the Target field
if (pDarwinData &&
#ifdef UNICODE
MsiGetProductInfo(pDarwinData->szwDarwinID,
INSTALLPROPERTY_PRODUCTNAME,
szAppState,
&cchAppState) == ERROR_SUCCESS)
#else
MsiGetProductInfo(pDarwinData->szDarwinID,
INSTALLPROPERTY_PRODUCTNAME,
szAppState,
&cchAppState) == ERROR_SUCCESS)
#endif
{
SetWindowText(GetDlgItem(plpd->hDlg, IDD_FILENAME), szAppState);
}
if (pDarwinData)
LocalFree(pDarwinData);
// we disabled everything in _DisableAllChildren, so re-enable the ones we still apply for darwin
EnableWindow(GetDlgItem(plpd->hDlg, IDD_NAME), TRUE);
EnableWindow(GetDlgItem(plpd->hDlg, IDD_PATH), TRUE);
EnableWindow(GetDlgItem(plpd->hDlg, IDD_LINK_HOTKEY), TRUE);
EnableWindow(GetDlgItem(plpd->hDlg, IDD_LINK_SHOWCMD), TRUE);
EnableWindow(GetDlgItem(plpd->hDlg, IDD_LINK_DESCRIPTION), TRUE);
// we skip all of the gook below if we are darwin since we only support the IDD_NAME, IDD_PATH, IDD_LINK_HOTKEY,
// IDD_LINK_SHOWCMD, and IDD_LINK_DESCRIPTION fields
}
else
{
hres = plpd->psl->lpVtbl->GetPath(plpd->psl, szCommand, ARRAYSIZE(szCommand), NULL, SLGP_RAWPATH);
if (FAILED(hres))
hres = plpd->psl->lpVtbl->GetPath(plpd->psl, szCommand, ARRAYSIZE(szCommand), NULL, 0);
if (SUCCEEDED(hres) && (hres != S_FALSE))
{
plpd->bIsFile = TRUE;
// get type
if (!SHGetFileInfo(szCommand, 0, &sfi, SIZEOF(sfi), SHGFI_TYPENAME))
{
TCHAR szExp[MAX_PATH];
// Let's see if the string has expandable environment strings
if (SHExpandEnvironmentStrings(szCommand, szExp, ARRAYSIZE(szExp))
&& lstrcmp(szCommand, szExp)) // don't hit the disk a second time if the string hasn't changed
{
SHGetFileInfo(szExp, 0, &sfi, SIZEOF(sfi), SHGFI_TYPENAME );
}
}
SetDlgItemText(plpd->hDlg, IDD_FILETYPE, sfi.szTypeName);
// location
lstrcpy(szBuffer, szCommand);
PathRemoveFileSpec(szBuffer);
SetDlgItemText(plpd->hDlg, IDD_LOCATION, PathFindFileName(szBuffer));
// command
plpd->psl->lpVtbl->GetArguments(plpd->psl, szBuffer, ARRAYSIZE(szBuffer));
PathComposeWithArgs(szCommand, szBuffer);
GetDlgItemText(plpd->hDlg, IDD_FILENAME, szBuffer, ARRAYSIZE(szBuffer));
// Conditionally change to prevent "Apply" button from enabling
if (lstrcmp(szCommand, szBuffer) != 0)
SetDlgItemText(plpd->hDlg, IDD_FILENAME, szCommand);
}
else
{
LPITEMIDLIST pidl;
plpd->bIsFile = FALSE;
EnableWindow(GetDlgItem(plpd->hDlg, IDD_FILENAME), FALSE);
EnableWindow(GetDlgItem(plpd->hDlg, IDD_PATH), FALSE);
plpd->psl->lpVtbl->GetIDList(plpd->psl, &pidl);
if (pidl)
{
SHGetNameAndFlags(pidl, SHGDN_FORPARSING | SHGDN_FORADDRESSBAR, szCommand, SIZECHARS(szCommand), NULL);
ILRemoveLastID(pidl);
SHGetNameAndFlags(pidl, SHGDN_NORMAL, szBuffer, SIZECHARS(szBuffer), NULL);
ILFree(pidl);
SetDlgItemText(plpd->hDlg, IDD_LOCATION, szBuffer);
SetDlgItemText(plpd->hDlg, IDD_FILETYPE, szCommand);
SetDlgItemText(plpd->hDlg, IDD_FILENAME, szCommand);
}
}
#ifdef WINNT
{
TCHAR szFullFile[MAX_PATH];
DWORD cchVerb;
UINT cch = _GetTargetOfLink(plpd, szFullFile);
if ((cch != 0) && (cch < ARRAYSIZE(szFullFile)))
{
LONG lBinaryType;
if (GetBinaryType( szFullFile, &lBinaryType) && (lBinaryType == SCS_WOW_BINARY))
{
if (_GetLinkFlags(plpd->psl) & SLDF_RUN_IN_SEPARATE)
{
// check it
EnableWindow(GetDlgItem(plpd->hDlg, IDD_RUNINSEPARATE), TRUE);
CheckDlgButton( plpd->hDlg, IDD_RUNINSEPARATE, 1 );
}
else
{
// Uncheck it
EnableWindow(GetDlgItem(plpd->hDlg, IDD_RUNINSEPARATE), TRUE);
CheckDlgButton( plpd->hDlg, IDD_RUNINSEPARATE, 0 );
}
}
else
{
// check it
CheckDlgButton( plpd->hDlg, IDD_RUNINSEPARATE, 1 );
EnableWindow(GetDlgItem( plpd->hDlg, IDD_RUNINSEPARATE ), FALSE);
}
}
else
{
// check it
CheckDlgButton( plpd->hDlg, IDD_RUNINSEPARATE, 1 );
EnableWindow( GetDlgItem( plpd->hDlg, IDD_RUNINSEPARATE ), FALSE );
}
// enable "runas" if the link target has that verb
if (SUCCEEDED(AssocQueryString(0, ASSOCSTR_COMMAND, szFullFile, TEXT("runas"), NULL, &cchVerb)) &&
cchVerb)
{
EnableWindow(GetDlgItem(plpd->hDlg, IDD_LINK_RUNASUSER), TRUE);
CheckDlgButton(plpd->hDlg, IDD_LINK_RUNASUSER, (_GetLinkFlags(plpd->psl) & SLDF_RUNAS_USER) ? BST_CHECKED : BST_UNCHECKED);
}
else
{
EnableWindow(GetDlgItem(plpd->hDlg, IDD_LINK_RUNASUSER), FALSE);
CheckDlgButton(plpd->hDlg, IDD_LINK_RUNASUSER, BST_UNCHECKED);
_SetLinkFlags(plpd->psl, _GetLinkFlags(plpd->psl) & ~SLDF_RUNAS_USER);
}
}
#endif
}
if (bUpdatePath)
return;
plpd->psl->lpVtbl->GetWorkingDirectory(plpd->psl, szBuffer, ARRAYSIZE(szBuffer));
SetDlgItemPath(plpd->hDlg, IDD_PATH, szBuffer);
plpd->psl->lpVtbl->GetDescription(plpd->psl, szBuffer, ARRAYSIZE(szBuffer));
SetDlgItemText(plpd->hDlg, IDD_LINK_DESCRIPTION, szBuffer);
plpd->psl->lpVtbl->GetHotkey(plpd->psl, &wHotkey);
SendDlgItemMessage(plpd->hDlg, IDD_LINK_HOTKEY, HKM_SETHOTKEY, wHotkey, 0);
// Now initialize the Run SHOW Command combo box
for (iShowCmd = IDS_RUN_NORMAL; iShowCmd <= IDS_RUN_MAXIMIZED; iShowCmd++)
{
LoadString(HINST_THISDLL, iShowCmd, szBuffer, ARRAYSIZE(szBuffer));
SendDlgItemMessage(plpd->hDlg, IDD_LINK_SHOWCMD, CB_ADDSTRING, 0, (LPARAM)(LPTSTR)szBuffer);
}
// Now setup the Show Command - Need to map to index numbers...
plpd->psl->lpVtbl->GetShowCmd(plpd->psl, &iShowCmd);
for (i = 0; i < ARRAYSIZE(c_iShowCmds); i++)
{
if (c_iShowCmds[i] == iShowCmd)
break;
}
if (i == ARRAYSIZE(c_iShowCmds))
{
ASSERT(0); // bogus link show cmd
i = 0; // SW_SHOWNORMAL
}
SendDlgItemMessage(plpd->hDlg, IDD_LINK_SHOWCMD, CB_SETCURSEL, i, 0);
// the icon
_UpdateLinkIcon(plpd, NULL);
}
// Opens a folder window with the target of the link selected
void _FindTarget(LPLINKPROP_DATA plpd)
{
USHORT uSave;
LPITEMIDLIST pidl, pidlDesk, pidlLast;
if (plpd->psl->lpVtbl->Resolve(plpd->psl, plpd->hDlg, 0) != NOERROR)
return; // above already did UI if needed
_UpdateLinkDlg(plpd, TRUE);
plpd->psl->lpVtbl->GetIDList(plpd->psl, &pidl);
if (!pidl)
return;
pidlLast = ILFindLastID(pidl);
// get the folder, special case for root objects (My Computer, Network)
// hack off the end if it is not the root item
if (pidl != pidlLast)
{
uSave = pidlLast->mkid.cb;
pidlLast->mkid.cb = 0;
}
else
uSave = 0;
pidlDesk = SHCloneSpecialIDList(NULL, CSIDL_DESKTOPDIRECTORY, FALSE);
if (pidlDesk)
{
BOOL fIsDesktopDir = ILIsEqual(pidl, pidlDesk);
if (fIsDesktopDir || !uSave) // if it's in the desktop dir or pidl == pidlLast (uSave == 0 from above)
{
ShellMessageBox(HINST_THISDLL, plpd->hDlg, MAKEINTRESOURCE(IDS_ORIGINALONDESKTOP), NULL, MB_OK);
// get keyboard focus to the desktop (away from this prop sheet)
PostMessage(plpd->hDlg, LNKM_ACTIVATEOTHER, 0, (LPARAM)FindWindow(TEXT(STR_DESKTOPCLASS), NULL));
}
else
{
IShellFolderViewDual *psfv;
if (SUCCEEDED(OpenContainingFolderAndGetShellFolderView(uSave ? pidl : pidlDesk, &psfv)))
{
if (uSave)
pidlLast->mkid.cb = uSave;
SelectPidlInSFV(psfv, pidlLast, SVSI_SELECT | SVSI_FOCUSED | SVSI_DESELECTOTHERS | SVSI_ENSUREVISIBLE);
psfv->lpVtbl->Release(psfv);
}
}
ILFree(pidlDesk);
}
ILFree(pidl);
}
// let the user pick a new icon for a link...
BOOL _DoPickIcon(LPLINKPROP_DATA plpd)
{
int iIconIndex;
SHFILEINFO sfi;
TCHAR *pszIconPath = sfi.szDisplayName;
IShellLinkDataList *psldl;
EXP_SZ_LINK *esli;
HRESULT hr;
*pszIconPath = 0;
// if the user has picked a icon before use it.
if (plpd->szIconPath[0] != 0 && plpd->iIconIndex >= 0)
{
lstrcpy(pszIconPath, plpd->szIconPath);
iIconIndex = plpd->iIconIndex;
}
else
{
// if this link has a icon use that.
plpd->psl->lpVtbl->GetIconLocation(plpd->psl, pszIconPath, MAX_PATH, &iIconIndex);
// check for an escaped version, if its there, use that
if (SUCCEEDED(hr = plpd->psl->lpVtbl->QueryInterface(plpd->psl, &IID_IShellLinkDataList, (LPVOID*)&psldl)))
{
if (SUCCEEDED(hr = psldl->lpVtbl->CopyDataBlock(psldl, EXP_SZ_ICON_SIG, (LPVOID*)&esli)))
{
ASSERT(esli);
#ifdef UNICODE
lstrcpyn(pszIconPath, esli->swzTarget, MAX_PATH);
#else
lstrcpyn(pszIconPath, esli->szTarget, MAX_PATH);
#endif
LocalFree(esli);
}
psldl->lpVtbl->Release(psldl);
}
if (pszIconPath[0] == TEXT('.'))
{
TCHAR szFullIconPath[MAX_PATH];
// We now allow ".txt" for the icon path, but since the user is clicking
// on the "Change Icon..." button, we show the current icon that ".txt" is
// associated with
GetIconLocationFromExt(pszIconPath, szFullIconPath, ARRAYSIZE(szFullIconPath), &iIconIndex);
lstrcpyn(pszIconPath, szFullIconPath, ARRAYSIZE(sfi.szDisplayName));
}
else if (pszIconPath[0] == TEXT('\0'))
{
// link does not have a icon, if it is a link to a file
// use the file name
TCHAR szArgs[MAX_PATH];
_GetPathAndArgs(plpd, pszIconPath, szArgs);
iIconIndex = 0;
if (!plpd->bIsFile || !PathIsExe(pszIconPath))
{
// link is not to a file, go get the icon
SHGetFileInfo(plpd->szFile, 0, &sfi, SIZEOF(sfi), SHGFI_ICONLOCATION);
iIconIndex = sfi.iIcon;
ASSERT(pszIconPath == sfi.szDisplayName);
}
}
}
if (PickIconDlg(plpd->hDlg, pszIconPath, MAX_PATH, &iIconIndex))
{
HICON hIcon = ExtractIcon(HINST_THISDLL, pszIconPath, iIconIndex);
_UpdateLinkIcon(plpd, hIcon);
// don't save it out to the link yet, just store it in our instance data
plpd->iIconIndex = iIconIndex;
lstrcpy(plpd->szIconPath, pszIconPath);
PropSheet_Changed(GetParent(plpd->hDlg), plpd->hDlg);
return TRUE;
}
return FALSE;
}
HRESULT _SaveLink(LPLINKDATA pld)
{
WORD wHotkey;
int iShowCmd;
IPersistFile *ppf;
HRESULT hres;
TCHAR szBuffer[MAX_PATH];
#ifdef WINNT
if (!(pld->lpd.bIsDirty || (pld->cpd.lpConsole && pld->cpd.bConDirty)))
#else
if (!pld->lpd.bIsDirty)
#endif
return S_OK;
if (pld->lpd.bIsFile)
{
TCHAR szBuffer[MAX_PATH];
TCHAR szArgs[MAX_PATH];
_GetPathAndArgs(&pld->lpd, szBuffer, szArgs);
// set the path (and pidl) of the link
pld->lpd.psl->lpVtbl->SetPath(pld->lpd.psl, szBuffer);
// may be null
pld->lpd.psl->lpVtbl->SetArguments(pld->lpd.psl, szArgs);
#ifdef WINNT
{
DWORD dwOldFlags = _GetLinkFlags(pld->lpd.psl);
// Set whether to run in separate memory space
dwOldFlags &= ~SLDF_RUN_IN_SEPARATE;
_SetLinkFlags(pld->lpd.psl, dwOldFlags);
if (IsWindowEnabled( GetDlgItem( pld->lpd.hDlg, IDD_RUNINSEPARATE )))
{
if (IsDlgButtonChecked( pld->lpd.hDlg, IDD_RUNINSEPARATE ))
_SetLinkFlags(pld->lpd.psl, dwOldFlags | SLDF_RUN_IN_SEPARATE);
}
dwOldFlags = _GetLinkFlags(pld->lpd.psl);
if (IsWindowEnabled(GetDlgItem(pld->lpd.hDlg, IDD_LINK_RUNASUSER)) &&
IsDlgButtonChecked(pld->lpd.hDlg, IDD_LINK_RUNASUSER))
{
_SetLinkFlags(pld->lpd.psl, dwOldFlags | SLDF_RUNAS_USER);
}
else
{
_SetLinkFlags(pld->lpd.psl, dwOldFlags & ~SLDF_RUNAS_USER);
}
}
#endif
}
if (pld->lpd.bIsFile || (_GetLinkFlags(pld->lpd.psl) & SLDF_HAS_DARWINID))
{
// set the working directory of the link
GetDlgItemPath(pld->lpd.hDlg, IDD_PATH, szBuffer);
pld->lpd.psl->lpVtbl->SetWorkingDirectory(pld->lpd.psl, szBuffer);
}
// set the description of the link
GetDlgItemText(pld->lpd.hDlg, IDD_LINK_DESCRIPTION, szBuffer, MAX_PATH);
pld->lpd.psl->lpVtbl->SetDescription(pld->lpd.psl, szBuffer);
// the hotkey
wHotkey = (WORD)SendDlgItemMessage(pld->lpd.hDlg, IDD_LINK_HOTKEY , HKM_GETHOTKEY, 0, 0);
pld->lpd.psl->lpVtbl->SetHotkey(pld->lpd.psl, wHotkey);
// the show command combo box
iShowCmd = (int)SendDlgItemMessage(pld->lpd.hDlg, IDD_LINK_SHOWCMD, CB_GETCURSEL, 0, 0L);
if ((iShowCmd >= 0) && (iShowCmd < ARRAYSIZE(c_iShowCmds)))
pld->lpd.psl->lpVtbl->SetShowCmd(pld->lpd.psl, c_iShowCmds[iShowCmd]);
// If the user explicitly selected a new icon, invalidate
// the icon cache entry for this link and then send around a file
// sys refresh message to all windows in case they are looking at
// this link.
if (pld->lpd.iIconIndex >= 0)
pld->lpd.psl->lpVtbl->SetIconLocation(pld->lpd.psl, pld->lpd.szIconPath, pld->lpd.iIconIndex);
#ifdef WINNT
// Update/Save the console information in the pExtraData section of
// the shell link.
if (pld->cpd.lpConsole && pld->cpd.bConDirty)
LinkConsolePagesSave( pld );
#endif
hres = pld->lpd.psl->lpVtbl->QueryInterface(pld->lpd.psl, &IID_IPersistFile, &ppf);
if (SUCCEEDED(hres))
{
if (ppf->lpVtbl->IsDirty(ppf) == NOERROR)
{
// save using existing file name (pld->lpd.szFile)
hres = ppf->lpVtbl->Save(ppf, NULL, TRUE);
if (FAILED(hres))
{
SHSysErrorMessageBox(pld->lpd.hDlg, NULL, IDS_LINKCANTSAVE,
hres & 0xFFF, PathFindFileName(pld->lpd.szFile),
MB_OK | MB_ICONEXCLAMATION);
}
else
{
pld->lpd.bIsDirty = FALSE;
}
}
ppf->lpVtbl->Release(ppf);
}
return hres;
}
void SetEditFocus(HWND hwnd)
{
SetFocus(hwnd);
Edit_SetSel(hwnd, 0, -1);
}
// returns:
// TRUE all link fields are valid
// FALSE some thing is wrong with what the user has entered
BOOL _ValidateLink(LPLINKPROP_DATA plpd)
{
TCHAR szDir[MAX_PATH], szPath[MAX_PATH], szArgs[MAX_PATH];
TCHAR szExpPath[MAX_PATH];
LPTSTR dirs[2];
BOOL bValidPath = FALSE;
if (!plpd->bIsFile)
return TRUE;
// validate the working directory field
GetDlgItemPath(plpd->hDlg, IDD_PATH, szDir);
if (*szDir &&
StrChr(szDir, TEXT('%')) == NULL && // has environement var %USER%
!IsRemovableDrive(DRIVEID(szDir)) &&
!PathIsDirectory(szDir))
{
ShellMessageBox(HINST_THISDLL, plpd->hDlg, MAKEINTRESOURCE(IDS_LINKBADWORKDIR),
MAKEINTRESOURCE(IDS_LINKERROR), MB_OK | MB_ICONEXCLAMATION, szDir);
SetEditFocus(GetDlgItem(plpd->hDlg, IDD_PATH));
return FALSE;
}
// validate the path (with arguments) field
_GetPathAndArgs(plpd, szPath, szArgs);
if (szPath[0] == 0)
return TRUE;
if (PathIsRoot(szPath) && IsRemovableDrive(DRIVEID(szPath)))
return TRUE;
if (PathIsLnk(szPath))
{
ShellMessageBox(HINST_THISDLL, plpd->hDlg, MAKEINTRESOURCE(IDS_LINKTOLINK),
MAKEINTRESOURCE(IDS_LINKERROR), MB_OK | MB_ICONEXCLAMATION);
SetEditFocus(GetDlgItem(plpd->hDlg, IDD_FILENAME));
return FALSE;
}
dirs[0] = szDir;
dirs[1] = NULL;
bValidPath = PathResolve(szPath, dirs, PRF_DONTFINDLNK | PRF_TRYPROGRAMEXTENSIONS);
if (!bValidPath)
{
// The path "as is" was invalid. See if it has environment variables
// which need to be expanded.
_GetPathAndArgs(plpd, szPath, szArgs);
if (SHExpandEnvironmentStrings(szPath, szExpPath, ARRAYSIZE(szExpPath)))
{
if (PathIsRoot(szExpPath) && IsRemovableDrive(DRIVEID(szDir)))
return TRUE;
bValidPath = PathResolve(szExpPath, dirs, PRF_DONTFINDLNK | PRF_TRYPROGRAMEXTENSIONS);
}
}
if (bValidPath)
{
#ifdef WINNT
BOOL bSave;
if (plpd->hThread)
{
bSave = plpd->bCheckRunInSep;
plpd->bCheckRunInSep = FALSE;
}
#endif
PathComposeWithArgs(szPath, szArgs);
GetDlgItemText(plpd->hDlg, IDD_FILENAME, szExpPath, ARRAYSIZE(szExpPath));
// only do this if something changed... that way we avoid having the PSM_CHANGED
// for nothing
if (lstrcmpi(szPath, szExpPath))
SetDlgItemText(plpd->hDlg, IDD_FILENAME, szPath);
#ifdef WINNT
if (plpd->hThread)
{
plpd->bCheckRunInSep = bSave;
}
#endif
return TRUE;
}
ShellMessageBox(HINST_THISDLL, plpd->hDlg, MAKEINTRESOURCE(IDS_LINKBADPATH),
MAKEINTRESOURCE(IDS_LINKERROR), MB_OK | MB_ICONEXCLAMATION, szPath);
SetEditFocus(GetDlgItem(plpd->hDlg, IDD_FILENAME));
return FALSE;
}
// Array for context help:
const DWORD aLinkHelpIDs[] = {
IDD_LINE_1, NO_HELP,
IDD_LINE_2, NO_HELP,
IDD_ITEMICON, IDH_FCAB_LINK_ICON,
IDD_NAME, IDH_FCAB_LINK_NAME,
IDD_FILETYPE_TXT, IDH_FCAB_LINK_LINKTYPE,
IDD_FILETYPE, IDH_FCAB_LINK_LINKTYPE,
IDD_LOCATION_TXT, IDH_FCAB_LINK_LOCATION,
IDD_LOCATION, IDH_FCAB_LINK_LOCATION,
IDD_FILENAME, IDH_FCAB_LINK_LINKTO,
IDD_PATH, IDH_FCAB_LINK_WORKING,
IDD_LINK_HOTKEY, IDH_FCAB_LINK_HOTKEY,
IDD_LINK_SHOWCMD, IDH_FCAB_LINK_RUN,
IDD_LINK_DESCRIPTION, IDH_FCAB_LINK_DESCRIPTION,
IDD_FINDORIGINAL, IDH_FCAB_LINK_FIND,
IDD_LINKDETAILS, IDH_FCAB_LINK_CHANGEICON,
#ifdef WINNT
IDD_RUNINSEPARATE, IDH_TRAY_RUN_SEPMEM,
IDD_LINK_RUNASUSER, IDH_FCAB_LINK_RUNASUSER,
#endif
0, 0
};
// Dialog proc for the generic link property sheet
// uses DLG_LINKPROP template
BOOL_PTR CALLBACK _LinkDlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
LPLINKDATA pld = (LPLINKDATA)GetWindowLongPtr(hdlg, DWLP_USER);
switch (msg) {
case WM_INITDIALOG:
pld = (LPLINKDATA)((PROPSHEETPAGE *)lParam)->lParam;
SetWindowLongPtr(hdlg, DWLP_USER, (LPARAM)pld);
// setup dialog state variables
pld->lpd.hDlg = hdlg;
SendDlgItemMessage(hdlg, IDD_FILENAME, EM_LIMITTEXT, MAX_PATH-1, 0);
SetPathWordBreakProc(GetDlgItem(hdlg, IDD_FILENAME), TRUE);
SendDlgItemMessage(hdlg, IDD_PATH, EM_LIMITTEXT, MAX_PATH-1, 0);
SetPathWordBreakProc(GetDlgItem(hdlg, IDD_PATH), TRUE);
SendDlgItemMessage(hdlg, IDD_LINK_DESCRIPTION, EM_LIMITTEXT, MAX_PATH-1, 0);
// set valid combinations for the hotkey
SendDlgItemMessage(hdlg, IDD_LINK_HOTKEY, HKM_SETRULES,
HKCOMB_NONE | HKCOMB_A | HKCOMB_S | HKCOMB_C,
HOTKEYF_CONTROL | HOTKEYF_ALT);
SHAutoComplete(GetDlgItem(hdlg, IDD_FILENAME), 0);
SHAutoComplete(GetDlgItem(hdlg, IDD_PATH), 0);
#ifdef WINNT
ASSERT(pld->lpd.hThread == NULL);
#endif
_UpdateLinkDlg(&pld->lpd, FALSE);
// Set up background thread to handle "Run In Separate Memory Space"
// check box.
#ifdef WINNT
pld->lpd.bCheckRunInSep = TRUE;
pld->lpd.hCheckNow = CreateEvent( NULL, TRUE, FALSE, NULL );
if (pld->lpd.hCheckNow)
{
DWORD dwDummy;
pld->lpd.hThread = CreateThread(NULL, 0, _LinkCheckThreadProc, &pld->lpd, 0, &dwDummy);
if (pld->lpd.hThread == NULL)
{
CloseHandle(pld->lpd.hCheckNow);
pld->lpd.hCheckNow = NULL;
}
}
#endif
// start off clean.
// do this here because we call some stuff above which generates
// wm_command/en_changes which we then think makes it dirty
pld->lpd.bIsDirty = FALSE;
break;
#ifdef WINNT
case WM_DESTROY:
_StopThread(&pld->lpd);
break;
#endif
case WM_NOTIFY:
switch (((NMHDR *)lParam)->code) {
#ifdef WINNT
case PSN_RESET:
_StopThread(&pld->lpd);
break;
#endif
case PSN_APPLY:
#ifdef WINNT
if ((((PSHNOTIFY *)lParam)->lParam))
_StopThread(&pld->lpd);
#endif
if (FAILED(_SaveLink(pld)))
SetWindowLongPtr(hdlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
break;
case PSN_KILLACTIVE:
// we implement the save on page change model, so
// validate and save changes here. this works for
// Apply Now, OK, and Page chagne.
SetWindowLongPtr(hdlg, DWLP_MSGRESULT, !_ValidateLink(&pld->lpd)); // don't allow close
break;
}
break;
case WM_COMMAND:
switch (GET_WM_COMMAND_ID(wParam, lParam)) {
case IDD_FINDORIGINAL:
_FindTarget(&pld->lpd);
break;
case IDD_LINKDETAILS:
if (_DoPickIcon(&pld->lpd))
pld->lpd.bIsDirty = TRUE;
break;
case IDD_LINK_SHOWCMD:
if (GET_WM_COMMAND_CMD(wParam, lParam) == LBN_SELCHANGE)
{
PropSheet_Changed(GetParent(hdlg), hdlg);
pld->lpd.bIsDirty = TRUE;
}
break;
#ifdef WINNT
case IDD_RUNINSEPARATE:
if (IsWindowEnabled( GetDlgItem( hdlg, IDD_RUNINSEPARATE )) )
{
PropSheet_Changed(GetParent(hdlg), hdlg);
pld->lpd.bIsDirty = TRUE;
}
break;
case IDD_LINK_RUNASUSER:
if (IsWindowEnabled(GetDlgItem(hdlg, IDD_LINK_RUNASUSER)))
{
PropSheet_Changed(GetParent(hdlg), hdlg);
pld->lpd.bIsDirty = TRUE;
}
break;
#endif
case IDD_LINK_HOTKEY:
case IDD_FILENAME:
case IDD_PATH:
case IDD_LINK_DESCRIPTION:
if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_CHANGE)
{
PropSheet_Changed(GetParent(hdlg), hdlg);
pld->lpd.bIsDirty = TRUE;
#ifdef WINNT
if (pld->lpd.hThread && pld->lpd.bCheckRunInSep)
SetEvent( pld->lpd.hCheckNow );
#endif
}
break;
default:
return FALSE;
}
break;
case LNKM_ACTIVATEOTHER:
SwitchToThisWindow(GetLastActivePopup((HWND)lParam), TRUE);
SetForegroundWindow((HWND)lParam);
break;
case WM_HELP:
WinHelp(((LPHELPINFO)lParam)->hItemHandle, NULL, HELP_WM_HELP, (ULONG_PTR)(LPTSTR) aLinkHelpIDs);
break;
case WM_CONTEXTMENU:
WinHelp((HWND) wParam, NULL, HELP_CONTEXTMENU, (ULONG_PTR)(LPVOID)aLinkHelpIDs);
break;
default:
return FALSE;
}
return TRUE;
}
// Release the link object allocated during the initialize
UINT CALLBACK _LinkPrshtCallback(HWND hwnd, UINT uMsg, LPPROPSHEETPAGE ppsp)
{
LPLINKDATA pld = (LPLINKDATA)((PROPSHEETPAGE *)ppsp->lParam);
switch (uMsg) {
case PSPCB_RELEASE:
#ifdef WINNT
if (pld->cpd.lpConsole)
{
LocalFree(pld->cpd.lpConsole );
}
if (pld->cpd.lpFEConsole)
{
LocalFree(pld->cpd.lpFEConsole );
}
DestroyFonts( &pld->cpd );
#endif
pld->lpd.psl->lpVtbl->Release(pld->lpd.psl);
LocalFree(pld);
break;
}
return 1;
}
BOOL AddLinkPage(LPCTSTR pszFile, LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
{
IShellLink *psl;
// HACK: call constuctor directly (should use CoCreateInstance())
// NOTE: do not call CoCreateInstance, because then you are not guranteed
// to get our CShellLink implementation, and we pass this object
// on to AddLinkConsolePages which assumes it is a CShellLink object.
if (PathIsLnk(pszFile) && SUCCEEDED(CShellLink_CreateInstance(NULL, &IID_IShellLink, &psl)))
{
// alloc this data, since is it shared across several pages
// instead of putting it in as extra data in the page header
LPLINKDATA pld = LocalAlloc(LPTR, SIZEOF(LINKDATA));
if (pld)
{
HPROPSHEETPAGE hpage;
PROPSHEETPAGE psp;
psp.dwSize = SIZEOF( psp );
psp.dwFlags = PSP_DEFAULT | PSP_USECALLBACK;
psp.hInstance = HINST_THISDLL;
psp.pszTemplate = MAKEINTRESOURCE(DLG_LINKPROP);
psp.pfnDlgProc = _LinkDlgProc;
psp.pfnCallback = _LinkPrshtCallback;
psp.lParam = (LPARAM)pld; // pass to all dlg procs
lstrcpyn(pld->lpd.szFile, pszFile, ARRAYSIZE(pld->lpd.szFile));
// pld->lpd.hThread = NULL; // zero-init allo
// pld->lpd.szIconPath[0] = 0;
pld->lpd.iIconIndex = -1;
pld->lpd.psl = psl;
ASSERT(!pld->lpd.szIconPath[0]);
hpage = CreatePropertySheetPage( &psp );
if (hpage)
{
if (pfnAddPage(hpage, lParam))
{
#ifdef WINNT
// Add console property pages if appropriate...
AddLinkConsolePages( pld, psl, pszFile, pfnAddPage, lParam );
#endif
return TRUE; // we added the link page
}
else
{
DestroyPropertySheetPage(hpage);
}
}
LocalFree(pld);
}
psl->lpVtbl->Release(psl);
}
return FALSE;
}