3663 lines
108 KiB
C++
3663 lines
108 KiB
C++
// Handle dde conversations.
|
|
|
|
#include "stdafx.h"
|
|
#pragma hdrstop
|
|
|
|
#include <iethread.h>
|
|
#include <browseui.h>
|
|
#include <shlexec.h> // Window_IsLFNAware
|
|
|
|
#define STRSAFE_NO_DEPRECATE
|
|
#include <strsafe.h>
|
|
|
|
STDAPI_(void) ShellExecCommandFile(LPCITEMIDLIST pidl); // scffile.cpp
|
|
|
|
// REARCHITECT: should this be done native for each platform?
|
|
#ifdef UNICODE
|
|
#define CP_WINNATURAL CP_WINUNICODE
|
|
#else
|
|
#define CP_WINNATURAL CP_WINANSI
|
|
#endif
|
|
|
|
#define DDECONV_NONE 0x00000000
|
|
#define DDECONV_NO_UNC 0x00000001
|
|
#define DDECONV_FORCED_CONNECTION 0x00000002
|
|
#define DDECONV_REPEAT_ACKS 0x00000004
|
|
#define DDECONV_FAIL_CONNECTS 0x00000008
|
|
#define DDECONV_MAP_MEDIA_RECORDER 0x00000010
|
|
#define DDECONV_NULL_FOR_STARTUP 0x00000020
|
|
#define DDECONV_ALLOW_INVALID_CL 0x00000040
|
|
#define DDECONV_EXPLORER_SERVICE_AND_TOPIC 0x00000080
|
|
#define DDECONV_USING_SENDMSG 0x00000100
|
|
#define DDECONV_NO_INIT 0x00000200
|
|
|
|
// PERF: this data is duplicated in all instances of the
|
|
// cabinet but is only used by the first instance!
|
|
|
|
DWORD g_dwDDEInst = 0L;
|
|
HSZ g_hszTopic = 0;
|
|
HSZ g_hszService = 0;
|
|
HSZ g_hszStar = 0;
|
|
HSZ g_hszShell = 0;
|
|
HSZ g_hszAppProps = 0;
|
|
HSZ g_hszFolders = 0;
|
|
BOOL g_LFNGroups = FALSE;
|
|
HWND g_hwndDde = NULL;
|
|
UINT_PTR g_nTimer = 0;
|
|
HWND g_hwndDDEML = NULL;
|
|
HWND g_hwndClient = NULL;
|
|
DWORD g_dwAppFlags = DDECONV_NONE;
|
|
|
|
// From shell32\nothunk.c
|
|
STDAPI_(void) SHGlobalDefect(DWORD dwHnd32);
|
|
|
|
// From Shell32\shlobjs.c
|
|
STDAPI_(void) SHAbortInvokeCommand();
|
|
|
|
#define IDT_REPEAT_ACKS 10
|
|
|
|
|
|
BOOL Net_DisconnectDrive(TCHAR chDrive)
|
|
{
|
|
TCHAR szDrive[3];
|
|
|
|
// Disconnect the given drive from it's share.
|
|
szDrive[0] = chDrive;
|
|
szDrive[1] = TEXT(':');
|
|
szDrive[2] = TEXT('\0');
|
|
return WNetCancelConnection2(szDrive, 0, FALSE) == WN_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Lets define a simple structure that handles the different converstations
|
|
// that might be happening concurently, I don't expect many conversations
|
|
// to happen at the same time, so this can be rather simple
|
|
//
|
|
struct _DDECONV;
|
|
typedef struct _DDECONV DDECONV, * PDDECONV;
|
|
struct _DDECONV
|
|
{
|
|
DWORD dwFlags; // Flags.
|
|
PDDECONV pddecNext;
|
|
LONG cRef;
|
|
HCONV hconv; // Handle to the conversation;
|
|
BOOL fDirty; // Has any changes been made;
|
|
IShellLink *psl; // temp link to work with
|
|
TCHAR szGroup[MAX_PATH]; // Group pathname
|
|
TCHAR szShare[MAX_PATH]; // Used to override UNC connections.
|
|
TCHAR chDrive; // Used to override UNC connections.
|
|
};
|
|
|
|
PDDECONV g_pddecHead = NULL; // List of current conversations.
|
|
LPTSTR g_pszLastGroupName = NULL; // Last group name used for items
|
|
// that are created by programs
|
|
// that do not setup a context
|
|
|
|
DDECONV *DDEConv_Create(void)
|
|
{
|
|
DDECONV *pddec = (DDECONV *) LocalAlloc(LPTR, sizeof(DDECONV));
|
|
if (pddec)
|
|
pddec->cRef = 1;
|
|
return pddec;
|
|
}
|
|
|
|
LONG DDEConv_AddRef(DDECONV *pddec)
|
|
{
|
|
ASSERT(pddec->cRef > 0);
|
|
return InterlockedIncrement(&pddec->cRef);
|
|
}
|
|
|
|
LONG DDEConv_Release(DDECONV *pddec)
|
|
{
|
|
ASSERT(pddec->cRef > 0);
|
|
if (InterlockedDecrement(&pddec->cRef))
|
|
return pddec->cRef;
|
|
|
|
// this needs to be deleted
|
|
if (pddec->pddecNext)
|
|
DDEConv_Release(pddec->pddecNext);
|
|
|
|
ATOMICRELEASE(pddec->psl);
|
|
|
|
// Were we forced to create a redirected drive?
|
|
if (pddec->dwFlags & DDECONV_FORCED_CONNECTION)
|
|
{
|
|
// Yep. Clean it up now.
|
|
Net_DisconnectDrive(pddec->chDrive);
|
|
}
|
|
|
|
if ((pddec->dwFlags & DDECONV_REPEAT_ACKS) && g_nTimer)
|
|
{
|
|
KillTimer(NULL, g_nTimer);
|
|
g_nTimer = 0;
|
|
}
|
|
|
|
LocalFree(pddec);
|
|
return 0;
|
|
}
|
|
|
|
|
|
typedef BOOL (*DDECOMMAND)(LPTSTR lpszBuf, UINT * lpwCmd, PDDECONV pddec);
|
|
typedef struct _DDECOMMANDINFO
|
|
{
|
|
LPCTSTR pszCommand;
|
|
DDECOMMAND lpfnCommand;
|
|
} DDECOMMANDINFO;
|
|
|
|
DWORD GetDDEAppFlagsFromWindow(HWND hwnd);
|
|
UINT* GetDDECommands(LPTSTR lpCmd, const DDECOMMANDINFO *lpsCommands, BOOL fLFN);
|
|
|
|
BOOL DDE_AddShellServices(void);
|
|
void DDE_RemoveShellServices(void);
|
|
|
|
BOOL DDE_CreateGroup(LPTSTR, UINT *, PDDECONV);
|
|
BOOL DDE_ShowGroup(LPTSTR, UINT *, PDDECONV);
|
|
BOOL DDE_AddItem(LPTSTR, UINT *, PDDECONV);
|
|
BOOL DDE_ExitProgman(LPTSTR, UINT *, PDDECONV);
|
|
BOOL DDE_DeleteGroup(LPTSTR, UINT *, PDDECONV);
|
|
BOOL DDE_DeleteItem(LPTSTR, UINT *, PDDECONV);
|
|
// BOOL NEAR PASCAL DDE_ReplaceItem(LPSTR, UINT *, PDDECONV);
|
|
#define DDE_ReplaceItem DDE_DeleteItem
|
|
BOOL DDE_Reload(LPTSTR, UINT *, PDDECONV);
|
|
BOOL DDE_ViewFolder(LPTSTR, UINT *, PDDECONV);
|
|
BOOL DDE_ExploreFolder(LPTSTR, UINT *, PDDECONV);
|
|
BOOL DDE_FindFolder(LPTSTR, UINT *, PDDECONV);
|
|
BOOL DDE_OpenFindFile(LPTSTR, UINT *, PDDECONV);
|
|
BOOL DDE_ConfirmID(LPTSTR lpszBuf, UINT * lpwCmd, PDDECONV pddec);
|
|
BOOL DDE_ShellFile(LPTSTR lpszBuf, UINT * lpwCmd, PDDECONV pddec);
|
|
#ifdef DEBUG
|
|
BOOL DDE_Beep(LPTSTR, UINT *, PDDECONV);
|
|
#endif
|
|
void MapGroupName(LPCTSTR lpszOld, LPTSTR lpszNew, ULONG cchNew);
|
|
|
|
TCHAR const c_szGroupGroup[] = TEXT("groups");
|
|
#define c_szStarDotStar TEXT("*.*")
|
|
CHAR const c_szCRLF[] = "\r\n";
|
|
|
|
TCHAR const c_szCreateGroup[] = TEXT("CreateGroup");
|
|
TCHAR const c_szShowGroup[] = TEXT("ShowGroup");
|
|
TCHAR const c_szAddItem[] = TEXT("AddItem");
|
|
TCHAR const c_szExitProgman[] = TEXT("ExitProgman");
|
|
TCHAR const c_szDeleteGroup[] = TEXT("DeleteGroup");
|
|
TCHAR const c_szDeleteItem[] = TEXT("DeleteItem");
|
|
TCHAR const c_szReplaceItem[] = TEXT("ReplaceItem");
|
|
TCHAR const c_szReload[] = TEXT("Reload");
|
|
TCHAR const c_szFindFolder[] = TEXT("FindFolder");
|
|
TCHAR const c_szOpenFindFile[] = TEXT("OpenFindFile");
|
|
#define c_szDotPif TEXT(".pif")
|
|
TCHAR const c_szTrioDataFax[] = TEXT("DDEClient");
|
|
TCHAR const c_szTalkToPlus[] = TEXT("ddeClass");
|
|
TCHAR const c_szStartUp[] = TEXT("StartUp");
|
|
TCHAR const c_szCCMail[] = TEXT("ccInsDDE");
|
|
TCHAR const c_szBodyWorks[] = TEXT("BWWFrame");
|
|
TCHAR const c_szMediaRecorder[] = TEXT("DDEClientWndClass");
|
|
TCHAR const c_szDiscis[] = TEXT("BACKSCAPE");
|
|
TCHAR const c_szMediaRecOld[] = TEXT("MediaRecorder");
|
|
TCHAR const c_szMediaRecNew[] = TEXT("Media Recorder");
|
|
TCHAR const c_szDialog[] = TEXT("#32770");
|
|
TCHAR const c_szJourneyMan[] = TEXT("Sender");
|
|
TCHAR const c_szCADDE[] = TEXT("CA_DDECLASS");
|
|
TCHAR const c_szFaxServe[] = TEXT("Install");
|
|
TCHAR const c_szMakePMG[] = TEXT("Make Program Manager Group");
|
|
TCHAR const c_szViewFolder[] = TEXT("ViewFolder");
|
|
TCHAR const c_szExploreFolder[] = TEXT("ExploreFolder");
|
|
TCHAR const c_szRUCabinet[] = TEXT("ConfirmCabinetID");
|
|
CHAR const c_szNULLA[] = "";
|
|
TCHAR const c_szGetIcon[] = TEXT("GetIcon");
|
|
TCHAR const c_szGetDescription[] = TEXT("GetDescription");
|
|
TCHAR const c_szGetWorkingDir[] = TEXT("GetWorkingDir");
|
|
|
|
TCHAR const c_szService[] = TEXT("Progman");
|
|
TCHAR const c_szTopic[] = TEXT("Progman");
|
|
#define c_szShell TEXT("Shell")
|
|
TCHAR const c_szFolders[] = TEXT("Folders");
|
|
TCHAR const c_szMapGroups[] = REGSTR_PATH_EXPLORER TEXT("\\MapGroups");
|
|
#define c_szStar TEXT("*")
|
|
TCHAR const c_szAppProps[] = TEXT("AppProperties");
|
|
#define c_szDotLnk TEXT(".lnk")
|
|
CHAR const c_szDesktopIniA[] = STR_DESKTOPINIA;
|
|
CHAR const c_szGroupsA[] = "Groups";
|
|
|
|
TCHAR const c_szShellFile[] = TEXT("ShellFile");
|
|
|
|
TCHAR const c_szMrPostman[] = TEXT("setupPmFrame");
|
|
|
|
#ifdef DEBUG
|
|
TCHAR const c_szBeep[] = TEXT("Beep");
|
|
#endif
|
|
|
|
|
|
DDECOMMANDINFO const c_sDDECommands[] =
|
|
{
|
|
{ c_szCreateGroup , DDE_CreateGroup },
|
|
{ c_szShowGroup , DDE_ShowGroup },
|
|
{ c_szAddItem , DDE_AddItem },
|
|
{ c_szExitProgman , DDE_ExitProgman },
|
|
{ c_szDeleteGroup , DDE_DeleteGroup },
|
|
{ c_szDeleteItem , DDE_DeleteItem },
|
|
{ c_szReplaceItem , DDE_ReplaceItem },
|
|
{ c_szReload , DDE_Reload },
|
|
{ c_szViewFolder , DDE_ViewFolder },
|
|
{ c_szExploreFolder, DDE_ExploreFolder },
|
|
{ c_szFindFolder, DDE_FindFolder },
|
|
{ c_szOpenFindFile, DDE_OpenFindFile },
|
|
{ c_szRUCabinet, DDE_ConfirmID},
|
|
{ c_szShellFile, DDE_ShellFile},
|
|
#ifdef DEBUG
|
|
{ c_szBeep , DDE_Beep },
|
|
#endif
|
|
{ 0, 0 },
|
|
} ;
|
|
|
|
#define HDDENULL ((HDDEDATA)NULL)
|
|
#define HSZNULL ((HSZ)NULL)
|
|
#define _DdeCreateStringHandle(dwInst, lpsz, nCP) DdeCreateStringHandle(dwInst, (LPTSTR)lpsz, nCP)
|
|
#define _DdeFreeStringHandle(dwInst, hsz) if (hsz) DdeFreeStringHandle(dwInst, hsz);
|
|
#define _LocalReAlloc(h, cb, flags) (h ? LocalReAlloc(h, cb, flags) : LocalAlloc(LPTR, cb))
|
|
|
|
//-------------------------------------------------------------------------
|
|
#define ITEMSPERROW 7
|
|
|
|
typedef struct
|
|
{
|
|
LPTSTR pszDesc;
|
|
LPTSTR pszCL;
|
|
LPTSTR pszWD;
|
|
LPTSTR pszIconPath;
|
|
int iIcon;
|
|
BOOL fMin;
|
|
WORD wHotkey;
|
|
} GROUPITEM, *PGROUPITEM;
|
|
|
|
STDAPI_(void) OpenGroup(LPCTSTR pszGroup, int nCmdShow)
|
|
{
|
|
IETHREADPARAM *piei = SHCreateIETHREADPARAM(NULL, 0, NULL, NULL);
|
|
if (piei)
|
|
{
|
|
ASSERT(*pszGroup);
|
|
piei->pidl = ILCreateFromPath(pszGroup);
|
|
piei->uFlags = COF_NORMAL | COF_WAITFORPENDING;
|
|
piei->nCmdShow = SW_NORMAL;
|
|
|
|
SHOpenFolderWindow(piei);
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Returns a pointer to the first non-whitespace character in a string.
|
|
LPTSTR SkipWhite(LPTSTR lpsz)
|
|
{
|
|
/* prevent sign extension in case of DBCS */
|
|
while (*lpsz && (TUCHAR)*lpsz <= TEXT(' '))
|
|
lpsz++;
|
|
|
|
return(lpsz);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Reads a parameter out of a string removing leading and trailing whitespace.
|
|
// Terminated by , or ). ] [ and ( are not allowed. Exception: quoted
|
|
// strings are treated as a whole parameter and may contain []() and ,.
|
|
// Places the offset of the first character of the parameter into some place
|
|
// and NULL terminates the parameter.
|
|
// If fIncludeQuotes is false it is assumed that quoted strings will contain single
|
|
// commands (the quotes will be removed and anything following the quotes will
|
|
// be ignored until the next comma). If fIncludeQuotes is TRUE, the contents of
|
|
// the quoted string will be ignored as before but the quotes won't be
|
|
// removed and anything following the quotes will remain.
|
|
LPTSTR GetOneParameter(LPCTSTR lpCmdStart, LPTSTR lpCmd,
|
|
UINT *lpW, BOOL fIncludeQuotes)
|
|
{
|
|
LPTSTR lpT;
|
|
|
|
switch (*lpCmd)
|
|
{
|
|
case TEXT(','):
|
|
*lpW = (UINT) (lpCmd - lpCmdStart); // compute offset
|
|
*lpCmd++ = 0; /* comma: becomes a NULL string */
|
|
break;
|
|
|
|
case TEXT('"'):
|
|
if (fIncludeQuotes)
|
|
{
|
|
TraceMsg(TF_DDE, "GetOneParameter: Keeping quotes.");
|
|
|
|
// quoted string... don't trim off "
|
|
*lpW = (UINT) (lpCmd - lpCmdStart); // compute offset
|
|
++lpCmd;
|
|
while (*lpCmd && *lpCmd != TEXT('"'))
|
|
lpCmd = CharNext(lpCmd);
|
|
if (!*lpCmd)
|
|
return(NULL);
|
|
lpT = lpCmd;
|
|
++lpCmd;
|
|
|
|
goto skiptocomma;
|
|
}
|
|
else
|
|
{
|
|
// quoted string... trim off "
|
|
++lpCmd;
|
|
*lpW = (UINT) (lpCmd - lpCmdStart); // compute offset
|
|
while (*lpCmd && *lpCmd != TEXT('"'))
|
|
lpCmd = CharNext(lpCmd);
|
|
if (!*lpCmd)
|
|
return(NULL);
|
|
*lpCmd++ = 0;
|
|
lpCmd = SkipWhite(lpCmd);
|
|
|
|
// If there's a comma next then skip over it, else just go on as
|
|
// normal.
|
|
if (*lpCmd == TEXT(','))
|
|
lpCmd++;
|
|
}
|
|
break;
|
|
|
|
case TEXT(')'):
|
|
return(lpCmd); /* we ought not to hit this */
|
|
|
|
case TEXT('('):
|
|
case TEXT('['):
|
|
case TEXT(']'):
|
|
return(NULL); /* these are illegal */
|
|
|
|
default:
|
|
lpT = lpCmd;
|
|
*lpW = (UINT) (lpCmd - lpCmdStart); // compute offset
|
|
skiptocomma:
|
|
while (*lpCmd && *lpCmd != TEXT(',') && *lpCmd != TEXT(')'))
|
|
{
|
|
/* Check for illegal characters. */
|
|
if (*lpCmd == TEXT(']') || *lpCmd == TEXT('[') || *lpCmd == TEXT('(') )
|
|
return(NULL);
|
|
|
|
/* Remove trailing whitespace */
|
|
/* prevent sign extension */
|
|
if ((TUCHAR)*lpCmd > TEXT(' '))
|
|
lpT = lpCmd;
|
|
|
|
lpCmd = CharNext(lpCmd);
|
|
}
|
|
|
|
/* Eat any trailing comma. */
|
|
if (*lpCmd == TEXT(','))
|
|
lpCmd++;
|
|
|
|
/* NULL terminator after last nonblank character -- may write over
|
|
* terminating ')' but the caller checks for that because this is
|
|
* a hack.
|
|
*/
|
|
|
|
#ifdef UNICODE
|
|
lpT[1] = 0;
|
|
#else
|
|
lpT[IsDBCSLeadByte(*lpT) ? 2 : 1] = 0;
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
// Return next unused character.
|
|
return(lpCmd);
|
|
}
|
|
|
|
|
|
// Extracts an alphabetic string and looks it up in a list of possible
|
|
// commands, returning a pointer to the character after the command and
|
|
// sticking the command index somewhere.
|
|
|
|
|
|
LPTSTR GetCommandName(LPTSTR lpCmd, const DDECOMMANDINFO * lpsCommands, UINT *lpW)
|
|
{
|
|
TCHAR chT;
|
|
UINT iCmd = 0;
|
|
LPTSTR lpT;
|
|
|
|
/* Eat any white space. */
|
|
lpT = lpCmd = SkipWhite(lpCmd);
|
|
|
|
/* Find the end of the token. */
|
|
while (IsCharAlpha(*lpCmd))
|
|
lpCmd = CharNext(lpCmd);
|
|
|
|
/* Temporarily NULL terminate it. */
|
|
chT = *lpCmd;
|
|
*lpCmd = 0;
|
|
|
|
/* Look up the token in a list of commands. */
|
|
*lpW = (UINT)-1;
|
|
while (lpsCommands->pszCommand)
|
|
{
|
|
if (!lstrcmpi(lpsCommands->pszCommand, lpT))
|
|
{
|
|
*lpW = iCmd;
|
|
break;
|
|
}
|
|
iCmd++;
|
|
++lpsCommands;
|
|
}
|
|
|
|
*lpCmd = chT;
|
|
|
|
return(lpCmd);
|
|
}
|
|
|
|
/* Called with: pointer to a string to parse and a pointer to a
|
|
* list of sz's containing the allowed function names.
|
|
* The function returns a global handle to an array of words containing
|
|
* one or more command definitions. A command definition consists of
|
|
* a command index, a parameter count, and that number of offsets. Each
|
|
* offset is an offset to a parameter in lpCmd which is now zero terminated.
|
|
* The list of command is terminated with -1.
|
|
* If there was a syntax error the return value is NULL.
|
|
* Caller must free block.
|
|
*/
|
|
|
|
UINT* GetDDECommands(LPTSTR lpCmd, const DDECOMMANDINFO * lpsCommands, BOOL fLFN)
|
|
{
|
|
UINT cParm, cCmd = 0;
|
|
UINT *lpW;
|
|
UINT *lpRet;
|
|
LPCTSTR lpCmdStart = lpCmd;
|
|
BOOL fIncludeQuotes = FALSE;
|
|
|
|
lpRet = lpW = (UINT*)GlobalAlloc(GPTR, 512L);
|
|
if (!lpRet)
|
|
return 0;
|
|
|
|
while (*lpCmd)
|
|
{
|
|
/* Skip leading whitespace. */
|
|
lpCmd = SkipWhite(lpCmd);
|
|
|
|
/* Are we at a NULL? */
|
|
if (!*lpCmd)
|
|
{
|
|
/* Did we find any commands yet? */
|
|
if (cCmd)
|
|
goto GDEExit;
|
|
else
|
|
goto GDEErrExit;
|
|
}
|
|
|
|
/* Each command should be inside square brackets. */
|
|
if (*lpCmd != TEXT('['))
|
|
goto GDEErrExit;
|
|
lpCmd++;
|
|
|
|
/* Get the command name. */
|
|
lpCmd = GetCommandName(lpCmd, lpsCommands, lpW);
|
|
if (*lpW == (UINT)-1)
|
|
goto GDEErrExit;
|
|
|
|
// We need to leave quotes in for the first param of an AddItem.
|
|
if (fLFN && *lpW == 2)
|
|
{
|
|
TraceMsg(TF_DDE, "GetDDECommands: Potential LFN AddItem command...");
|
|
fIncludeQuotes = TRUE;
|
|
}
|
|
|
|
lpW++;
|
|
|
|
/* Start with zero parms. */
|
|
cParm = 0;
|
|
lpCmd = SkipWhite(lpCmd);
|
|
|
|
/* Check for opening '(' */
|
|
if (*lpCmd == TEXT('('))
|
|
{
|
|
lpCmd++;
|
|
|
|
/* Skip white space and then find some parameters (may be none). */
|
|
lpCmd = SkipWhite(lpCmd);
|
|
|
|
while (*lpCmd != TEXT(')'))
|
|
{
|
|
if (!*lpCmd)
|
|
goto GDEErrExit;
|
|
|
|
// Only the first param of the AddItem command needs to
|
|
// handle quotes from LFN guys.
|
|
if (fIncludeQuotes && (cParm != 0))
|
|
fIncludeQuotes = FALSE;
|
|
|
|
/* Get the parameter. */
|
|
if (!(lpCmd = GetOneParameter(lpCmdStart, lpCmd, lpW + (++cParm), fIncludeQuotes)))
|
|
goto GDEErrExit;
|
|
|
|
/* HACK: Did GOP replace a ')' with a NULL? */
|
|
if (!*lpCmd)
|
|
break;
|
|
|
|
/* Find the next one or ')' */
|
|
lpCmd = SkipWhite(lpCmd);
|
|
}
|
|
|
|
// Skip closing bracket.
|
|
lpCmd++;
|
|
|
|
/* Skip the terminating stuff. */
|
|
lpCmd = SkipWhite(lpCmd);
|
|
}
|
|
|
|
/* Set the count of parameters and then skip the parameters. */
|
|
*lpW++ = cParm;
|
|
lpW += cParm;
|
|
|
|
/* We found one more command. */
|
|
cCmd++;
|
|
|
|
/* Commands must be in square brackets. */
|
|
if (*lpCmd != TEXT(']'))
|
|
goto GDEErrExit;
|
|
lpCmd++;
|
|
}
|
|
|
|
GDEExit:
|
|
/* Terminate the command list with -1. */
|
|
*lpW = (UINT)-1;
|
|
|
|
return lpRet;
|
|
|
|
GDEErrExit:
|
|
GlobalFree(lpW);
|
|
return(0);
|
|
}
|
|
|
|
|
|
// lpszBuf is the dde command with NULLs between the commands and the
|
|
// arguments.
|
|
// *lpwCmd is the number of paramaters.
|
|
// *(lpwCmd+n) are offsets to those paramters in lpszBuf.
|
|
|
|
|
|
|
|
|
|
// Make a long group name valid on an 8.3 machine.
|
|
// This assumes the name is already a valid LFN.
|
|
void _ShortenGroupName(LPTSTR lpName)
|
|
{
|
|
LPTSTR pCh = lpName;
|
|
|
|
ASSERT(lpName);
|
|
|
|
while (*pCh)
|
|
{
|
|
// Spaces?
|
|
if (*pCh == TEXT(' '))
|
|
*pCh = TEXT('_');
|
|
// Next
|
|
pCh = CharNext(pCh);
|
|
// Limit to 8 chars.
|
|
if (pCh-lpName >= 8)
|
|
break;
|
|
}
|
|
// Null term.
|
|
*pCh = TEXT('\0');
|
|
}
|
|
|
|
|
|
// This function will convert the name into a valid file name
|
|
void FileName_MakeLegal(LPTSTR lpName)
|
|
{
|
|
LPTSTR lpT;
|
|
|
|
ASSERT(lpName);
|
|
|
|
for (lpT = lpName; *lpT != TEXT('\0'); lpT = CharNext(lpT))
|
|
{
|
|
if (!PathIsValidChar(*lpT, g_LFNGroups ? PIVC_LFN_NAME : PIVC_SFN_NAME))
|
|
{
|
|
// Don't Allow invalid chars in names
|
|
*lpT = TEXT('_');
|
|
}
|
|
}
|
|
|
|
// Quick check to see if we support long group names.
|
|
if (!g_LFNGroups)
|
|
{
|
|
// Nope, shorten it.
|
|
_ShortenGroupName(lpName);
|
|
}
|
|
}
|
|
|
|
|
|
// Given a ptr to a path and a ptr to the start of its filename componenent
|
|
// make the filename legal and tack it on the end of the path.
|
|
void GenerateGroupName(LPTSTR lpszPath, LPTSTR lpszName)
|
|
{
|
|
ASSERT(lpszPath);
|
|
ASSERT(lpszName);
|
|
|
|
// Deal with ":" and "\" in the group name before trying to
|
|
// qualify it.
|
|
FileName_MakeLegal(lpszName);
|
|
PathAppend(lpszPath, lpszName);
|
|
PathQualify(lpszPath);
|
|
}
|
|
|
|
|
|
// Simple function used by AddItem, DeleteItem, ReplaceItem to make sure
|
|
// that our group name has been setup properly.
|
|
void _CheckForCurrentGroup(PDDECONV pddec)
|
|
{
|
|
// Need a group - if nothing is specified then we default to using
|
|
// the last group name that someone either created or viewed.
|
|
//
|
|
if (!pddec->szGroup[0])
|
|
{
|
|
// We will use the last context that was set...
|
|
// Note: after that point, we will not track the new create
|
|
// groups and the like of other contexts.
|
|
ENTERCRITICAL;
|
|
if (g_pszLastGroupName != NULL) {
|
|
lstrcpy(pddec->szGroup, g_pszLastGroupName);
|
|
} else {
|
|
CABINETSTATE cs;
|
|
if (IsUserAnAdmin() &&
|
|
(ReadCabinetState(&cs, sizeof(cs)), cs.fAdminsCreateCommonGroups)) {
|
|
SHGetSpecialFolderPath(NULL, pddec->szGroup, CSIDL_COMMON_PROGRAMS, TRUE);
|
|
} else {
|
|
SHGetSpecialFolderPath(NULL, pddec->szGroup, CSIDL_PROGRAMS, TRUE);
|
|
}
|
|
}
|
|
LEAVECRITICAL;
|
|
}
|
|
}
|
|
|
|
|
|
// For those apps that do not setup their context for where to
|
|
// add items during their processing we need to keep the path
|
|
// of the last group that was created (in g_pszLastGroupName).
|
|
void _KeepLastGroup(LPCTSTR lpszGroup)
|
|
{
|
|
LPTSTR lpGroup;
|
|
|
|
ENTERCRITICAL;
|
|
|
|
lpGroup = (LPTSTR)_LocalReAlloc(g_pszLastGroupName, (lstrlen(lpszGroup) + 1) * sizeof(TCHAR), LMEM_MOVEABLE|LMEM_ZEROINIT);
|
|
if (lpGroup != NULL) {
|
|
g_pszLastGroupName = lpGroup;
|
|
lstrcpy(g_pszLastGroupName, lpszGroup);
|
|
}
|
|
|
|
LEAVECRITICAL;
|
|
}
|
|
|
|
|
|
// NB HACK - Lots of setup apps dot lots of Create/Groups and as we're
|
|
// more async now we end up showing lots of identical group windows.
|
|
// Also, even the time delay in determining that the group is already
|
|
// open can cause some setup apps to get confused.
|
|
// So, to stop this happening we keep track of the last group created
|
|
// or shown and skip the Cabinet_OpenFolder if it's the same guy and we're
|
|
// within a X second timeout limit.
|
|
BOOL _SameLastGroup(LPCTSTR lpszGroup)
|
|
{
|
|
static DWORD dwTimeOut = 0;
|
|
BOOL fRet = FALSE;
|
|
|
|
if (lpszGroup && g_pszLastGroupName)
|
|
{
|
|
// Too soon?
|
|
if (GetTickCount() - dwTimeOut < 30*1000)
|
|
{
|
|
LPTSTR pszName1 = PathFindFileName(lpszGroup);
|
|
LPTSTR pszName2 = PathFindFileName(g_pszLastGroupName);
|
|
|
|
// Yep, same group as last time?
|
|
ENTERCRITICAL;
|
|
if (lstrcmpi(pszName1, pszName2) == 0)
|
|
{
|
|
// Yep.
|
|
fRet = TRUE;
|
|
}
|
|
LEAVECRITICAL;
|
|
}
|
|
}
|
|
|
|
dwTimeOut = GetTickCount();
|
|
return fRet;
|
|
}
|
|
|
|
|
|
// Map the group name to a proper path taking care of the startup group and
|
|
// app hacks on the way.
|
|
void GetGroupPath(LPCTSTR pszName, LPTSTR pszPath, DWORD dwFlags, INT iCommonGroup)
|
|
{
|
|
TCHAR szGroup[MAX_PATH];
|
|
BOOL bCommonGroup;
|
|
BOOL bFindPersonalGroup = FALSE;
|
|
|
|
if (pszPath)
|
|
*pszPath = TEXT('\0');
|
|
|
|
if (!pszName)
|
|
return;
|
|
|
|
//
|
|
// Determine which type of group to create.
|
|
//
|
|
if (IsUserAnAdmin()) {
|
|
if (iCommonGroup == 0) {
|
|
bCommonGroup = FALSE;
|
|
|
|
} else if (iCommonGroup == 1) {
|
|
bCommonGroup = TRUE;
|
|
|
|
} else {
|
|
//
|
|
// Administrators get common groups created by default
|
|
// when the setup application doesn't specificly state
|
|
// what kind of group to create. This feature can be
|
|
// turned off in the cabinet state flags.
|
|
//
|
|
CABINETSTATE cs;
|
|
ReadCabinetState(&cs, sizeof(cs));
|
|
if (cs.fAdminsCreateCommonGroups) {
|
|
bFindPersonalGroup = TRUE;
|
|
bCommonGroup = FALSE; // This might get turned on later
|
|
// if find is unsuccessful
|
|
} else {
|
|
bCommonGroup = FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
//
|
|
// Regular users can't create common group items.
|
|
//
|
|
bCommonGroup = FALSE;
|
|
}
|
|
|
|
// Handle NULL groups for certain apps and map Startup (non-localised)
|
|
// to the startup group.
|
|
if (((dwFlags & DDECONV_NULL_FOR_STARTUP) && !*pszName)
|
|
|| (lstrcmpi(pszName, c_szStartUp) == 0))
|
|
{
|
|
if (bCommonGroup) {
|
|
SHGetSpecialFolderPath(NULL, pszPath, CSIDL_COMMON_STARTUP, TRUE);
|
|
} else {
|
|
SHGetSpecialFolderPath(NULL, pszPath, CSIDL_STARTUP, TRUE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Hack for Media Recorder.
|
|
if (dwFlags & DDECONV_MAP_MEDIA_RECORDER)
|
|
{
|
|
if (lstrcmpi(pszName, c_szMediaRecOld) == 0)
|
|
lstrcpy(szGroup, c_szMediaRecNew);
|
|
else
|
|
lstrcpy(szGroup, pszName);
|
|
}
|
|
else
|
|
{
|
|
// Map group name for FE characters which have identical
|
|
// twins in both DBCS/SBCS. Stolen from grpconv's similar
|
|
// function.
|
|
|
|
MapGroupName(pszName, szGroup, ARRAYSIZE(szGroup));
|
|
}
|
|
|
|
// Possibly find existing group
|
|
if (bFindPersonalGroup)
|
|
{
|
|
SHGetSpecialFolderPath(NULL, pszPath, CSIDL_PROGRAMS, TRUE);
|
|
GenerateGroupName(pszPath, szGroup);
|
|
if (PathFileExistsAndAttributes(pszPath, NULL))
|
|
{
|
|
return;
|
|
}
|
|
bCommonGroup = TRUE;
|
|
}
|
|
|
|
// Get the first bit of the path for this group.
|
|
if (bCommonGroup) {
|
|
SHGetSpecialFolderPath(NULL, pszPath, CSIDL_COMMON_PROGRAMS, TRUE);
|
|
} else {
|
|
SHGetSpecialFolderPath(NULL, pszPath, CSIDL_PROGRAMS, TRUE);
|
|
}
|
|
|
|
GenerateGroupName(pszPath, szGroup);
|
|
}
|
|
}
|
|
|
|
BOOL IsParameterANumber(LPTSTR lp)
|
|
{
|
|
while (*lp) {
|
|
if (*lp < TEXT('0') || *lp > TEXT('9'))
|
|
return(FALSE);
|
|
lp++;
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
// [ CreateGroup ( Group Name [, Group File] [,Common Flag] ) ]
|
|
// REVIEW UNDONE Allow the use of a group file to be specified.
|
|
BOOL DDE_CreateGroup(LPTSTR lpszBuf, UINT *lpwCmd, PDDECONV pddec)
|
|
{
|
|
BOOL bRet;
|
|
INT iCommonGroup = -1;
|
|
TCHAR szGroup[MAX_PATH]; // Group pathname
|
|
|
|
DBG_ENTER(FTF_DDE, DDE_CreateGroup);
|
|
|
|
if ((*lpwCmd > 3) || (*lpwCmd == 0))
|
|
{
|
|
bRet = FALSE;
|
|
goto Leave;
|
|
}
|
|
|
|
if (*lpwCmd >= 2) {
|
|
|
|
//
|
|
// Need to check for common group flag
|
|
//
|
|
if (*lpwCmd == 3) {
|
|
if (lpszBuf[*(lpwCmd + 3)] == TEXT('1')) {
|
|
iCommonGroup = 1;
|
|
} else {
|
|
iCommonGroup = 0;
|
|
}
|
|
} else if (*lpwCmd == 2 && IsParameterANumber(lpszBuf + *(lpwCmd+2))) {
|
|
if (lpszBuf[*(lpwCmd + 2)] == TEXT('1')) {
|
|
iCommonGroup = 1;
|
|
} else {
|
|
iCommonGroup = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
lpwCmd++;
|
|
|
|
GetGroupPath(&lpszBuf[*lpwCmd], szGroup, pddec->dwFlags, iCommonGroup);
|
|
|
|
TraceMsg(TF_DDE, "Create Group %s", (LPTSTR) szGroup);
|
|
|
|
// Stop creating lots of identical folders.
|
|
if (!_SameLastGroup(szGroup))
|
|
{
|
|
lstrcpy(pddec->szGroup,szGroup); // Now working on this group...
|
|
|
|
// If it doesn't exist then create it.
|
|
if (!PathFileExistsAndAttributes(pddec->szGroup, NULL))
|
|
{
|
|
if (CreateDirectory(pddec->szGroup, NULL))
|
|
{
|
|
SHChangeNotify(SHCNE_MKDIR, SHCNF_PATH, pddec->szGroup, NULL);
|
|
}
|
|
else
|
|
{
|
|
bRet = FALSE;
|
|
goto Leave;
|
|
}
|
|
}
|
|
|
|
// Show it.
|
|
OpenGroup(pddec->szGroup, SW_NORMAL);
|
|
_KeepLastGroup(pddec->szGroup);
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_DDE, "Ignoring duplicate CreateGroup");
|
|
}
|
|
|
|
bRet = TRUE;
|
|
|
|
Leave:
|
|
DBG_EXIT_BOOL(FTF_DDE, DDE_CreateGroup, bRet);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
// REVIEW HACK - Don't just caste, call GetConvInfo() to get this.
|
|
#define _GetDDEWindow(hconv) ((HWND)hconv)
|
|
|
|
|
|
// Return the hwnd of the guy we're talking too.
|
|
HWND _GetDDEPartnerWindow(HCONV hconv)
|
|
{
|
|
CONVINFO ci;
|
|
|
|
ci.hwndPartner = NULL;
|
|
ci.cb = sizeof(ci);
|
|
DdeQueryConvInfo(hconv, QID_SYNC, &ci);
|
|
return ci.hwndPartner;
|
|
}
|
|
|
|
|
|
// [ ShowGroup (group_name, wShowParm) ]
|
|
// REVIEW This sets the default group - not neccessarily what progman
|
|
// used to do but probably close enough.
|
|
BOOL DDE_ShowGroup(LPTSTR lpszBuf, UINT *lpwCmd, PDDECONV pddec)
|
|
{
|
|
BOOL bRet;
|
|
int nShowCmd;
|
|
BOOL fUseStartup = FALSE;
|
|
TCHAR szGroup[MAX_PATH];
|
|
INT iCommonGroup = -1;
|
|
|
|
DBG_ENTER(FTF_DDE, DDE_ShowGroup);
|
|
|
|
if (*lpwCmd < 2 || *lpwCmd > 3)
|
|
{
|
|
bRet = FALSE;
|
|
goto Leave;
|
|
}
|
|
|
|
if (*lpwCmd == 3) {
|
|
|
|
//
|
|
// Need to check for common group flag
|
|
//
|
|
|
|
if (lpszBuf[*(lpwCmd + 3)] == TEXT('1')) {
|
|
iCommonGroup = 1;
|
|
} else {
|
|
iCommonGroup = 0;
|
|
}
|
|
}
|
|
|
|
lpwCmd++;
|
|
|
|
GetGroupPath(&lpszBuf[*lpwCmd], szGroup, pddec->dwFlags, iCommonGroup);
|
|
|
|
// NB VJE-r setup passes an invalid group name to ShowGroup command.
|
|
// Use szGroup and check it before copying it to pddec->szGroup.
|
|
if (!PathFileExistsAndAttributes(szGroup, NULL))
|
|
{
|
|
bRet = FALSE;
|
|
goto Leave;
|
|
}
|
|
|
|
// Get the show cmd.
|
|
lpwCmd++;
|
|
nShowCmd = StrToInt(&lpszBuf[*lpwCmd]);
|
|
TraceMsg(TF_DDE, "Showing %s (%d)", (LPTSTR)szGroup, nShowCmd);
|
|
|
|
// Stop lots of cabinet windows from appearing without slowing down the dde
|
|
// conversation if we're just doing a ShowNormal/ShowNA of a group we probably
|
|
// just created.
|
|
switch (nShowCmd)
|
|
{
|
|
case SW_SHOWNORMAL:
|
|
case SW_SHOWNOACTIVATE:
|
|
case SW_SHOW:
|
|
case SW_SHOWNA:
|
|
{
|
|
if (_SameLastGroup(szGroup))
|
|
{
|
|
TraceMsg(TF_DDE, "Ignoring duplicate ShowGroup.");
|
|
bRet = TRUE;
|
|
goto Leave;
|
|
}
|
|
break;
|
|
}
|
|
case SW_SHOWMINNOACTIVE:
|
|
{
|
|
nShowCmd = SW_SHOWMINIMIZED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// It's OK to use the new group.
|
|
lstrcpy(pddec->szGroup, szGroup);
|
|
|
|
// Else
|
|
_KeepLastGroup(pddec->szGroup);
|
|
|
|
OpenGroup(pddec->szGroup, nShowCmd);
|
|
|
|
bRet = TRUE;
|
|
|
|
Leave:
|
|
DBG_EXIT_BOOL(FTF_DDE, DDE_ShowGroup, bRet);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
|
|
// [ DeleteGroup (group_name) ]
|
|
BOOL DDE_DeleteGroup(LPTSTR lpszBuf, UINT *lpwCmd, PDDECONV pddec)
|
|
{
|
|
BOOL bRet;
|
|
TCHAR szGroupName[MAX_PATH];
|
|
INT iCommonGroup = -1;
|
|
|
|
DBG_ENTER(FTF_DDE, DDE_DeleteGroup);
|
|
|
|
if (*lpwCmd < 1 || *lpwCmd > 3)
|
|
{
|
|
bRet = FALSE;
|
|
goto Leave;
|
|
}
|
|
|
|
if (*lpwCmd == 2) {
|
|
//
|
|
// Need to check for common group flag
|
|
//
|
|
|
|
if (lpszBuf[*(lpwCmd + 2)] == TEXT('1')) {
|
|
iCommonGroup = 1;
|
|
} else {
|
|
iCommonGroup = 0;
|
|
}
|
|
}
|
|
|
|
lpwCmd++;
|
|
|
|
GetGroupPath(&lpszBuf[*lpwCmd], szGroupName, pddec->dwFlags, iCommonGroup);
|
|
|
|
if (!PathFileExistsAndAttributes(szGroupName, NULL))
|
|
{
|
|
bRet = FALSE;
|
|
goto Leave;
|
|
}
|
|
|
|
szGroupName[lstrlen(szGroupName) + 1] = TEXT('\0'); // double NULL terminate
|
|
|
|
// Now simply try to delete the group!
|
|
// Use copy engine that will actually move to trash can...
|
|
{
|
|
SHFILEOPSTRUCT sFileOp =
|
|
{
|
|
NULL,
|
|
FO_DELETE,
|
|
szGroupName,
|
|
NULL,
|
|
FOF_RENAMEONCOLLISION | FOF_NOCONFIRMATION | FOF_SILENT,
|
|
} ;
|
|
|
|
TraceMsg(TF_DDE, "Deleting group %s.", szGroupName);
|
|
|
|
SHFileOperation(&sFileOp);
|
|
|
|
TraceMsg(TF_DDE, "Finished deleting");
|
|
|
|
}
|
|
|
|
// Clear the last group flag so that Create+Delete+Create
|
|
// does the right thing.
|
|
_KeepLastGroup(c_szNULL);
|
|
bRet = TRUE;
|
|
|
|
Leave:
|
|
DBG_EXIT_BOOL(FTF_DDE, DDE_DeleteGroup, bRet);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
// Take the filename part of a path, copy it into lpszName and the pretty it
|
|
// up so it can be used as a link name.
|
|
void BuildDefaultName(LPTSTR lpszName, LPCTSTR lpszPath)
|
|
{
|
|
LPTSTR lpszFilename;
|
|
|
|
lpszFilename = PathFindFileName(lpszPath);
|
|
lstrcpy(lpszName, lpszFilename);
|
|
// NB Path remove extension can only remove extensions from filenames
|
|
// not paths.
|
|
PathRemoveExtension(lpszName);
|
|
CharLower(lpszName);
|
|
CharUpperBuff(lpszName, 1);
|
|
}
|
|
|
|
|
|
BOOL HConv_PartnerIsLFNAware(HCONV hconv)
|
|
{
|
|
HWND hwndPartner = _GetDDEPartnerWindow(hconv);
|
|
|
|
// If this is being forwared by the desktop then assume the app isn't
|
|
// LFN aware.
|
|
if (IsDesktopWindow(hwndPartner))
|
|
return FALSE;
|
|
else
|
|
return Window_IsLFNAware(hwndPartner);
|
|
}
|
|
|
|
|
|
BOOL PrivatePathStripToRoot(LPTSTR szRoot)
|
|
{
|
|
while(!PathIsRoot(szRoot))
|
|
{
|
|
if (!PathRemoveFileSpec(szRoot))
|
|
{
|
|
// If we didn't strip anything off,
|
|
// must be current drive
|
|
return(FALSE);
|
|
}
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
BOOL Net_ConnectDrive(LPCTSTR pszShare, TCHAR *pchDrive)
|
|
{
|
|
DWORD err;
|
|
NETRESOURCE nr;
|
|
TCHAR szAccessName[MAX_PATH];
|
|
ULONG cbAccessName = sizeof(szAccessName);
|
|
DWORD dwResult;
|
|
|
|
// Connect to the given share and return the drive that it's on.
|
|
nr.lpRemoteName = (LPTSTR)pszShare;
|
|
nr.lpLocalName = NULL;
|
|
nr.lpProvider = NULL;
|
|
nr.dwType = RESOURCETYPE_DISK;
|
|
err = WNetUseConnection(NULL, &nr, NULL, NULL, CONNECT_TEMPORARY | CONNECT_REDIRECT,
|
|
szAccessName, &cbAccessName, &dwResult);
|
|
if (err == WN_SUCCESS)
|
|
{
|
|
TraceMsg(TF_DDE, "Net_ConnextDrive: %s %s %x", pszShare, szAccessName, dwResult);
|
|
if (pchDrive)
|
|
*pchDrive = szAccessName[0];
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// Convert (\\foo\bar\some\path, X) to (X:\some\path)
|
|
void Path_ChangeUNCToDrive(LPTSTR pszPath, TCHAR chDrive)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
LPTSTR pszSpec;
|
|
|
|
lstrcpy(szPath, pszPath);
|
|
PrivatePathStripToRoot(szPath);
|
|
pszPath[0] = chDrive;
|
|
pszPath[1] = TEXT(':');
|
|
pszPath[2] = TEXT('\\');
|
|
pszSpec = pszPath + lstrlen(szPath) + 1;
|
|
if (*pszSpec)
|
|
lstrcpy(&pszPath[3],pszSpec);
|
|
}
|
|
|
|
|
|
LPITEMIDLIST Pidl_CreateUsingAppPaths(LPCTSTR pszApp)
|
|
{
|
|
TCHAR sz[MAX_PATH];
|
|
long cb = sizeof(sz);
|
|
|
|
TraceMsg(TF_DDE, "Trying app paths...");
|
|
|
|
lstrcpy(sz, REGSTR_PATH_APPPATHS);
|
|
PathAppend(sz, pszApp);
|
|
if (SHRegQueryValue(HKEY_LOCAL_MACHINE, sz, sz, &cb) == ERROR_SUCCESS)
|
|
{
|
|
return ILCreateFromPath(sz);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// [ AddItem (command,name,icopath,index,pointx,pointy, defdir,hotkey,fminimize,fsepvdm) ]
|
|
// This adds things to the current group ie what ever's currently in
|
|
// the conversations szGroup string
|
|
BOOL DDE_AddItem(LPTSTR lpszBuf, UINT *lpwCmd, PDDECONV pddec)
|
|
{
|
|
BOOL bRet;
|
|
UINT nParams;
|
|
|
|
TCHAR szTmp[MAX_PATH];
|
|
TCHAR szName[MAX_PATH];
|
|
TCHAR szCL[MAX_PATH*2]; // scratch for path + args.
|
|
WCHAR wszPath[MAX_PATH];
|
|
LPTSTR lpszArgs;
|
|
UINT iIcon;
|
|
int nShowCmd;
|
|
BOOL fIconPath = FALSE;
|
|
LPITEMIDLIST pidl;
|
|
IPersistFile *ppf;
|
|
LPTSTR dirs[2];
|
|
TCHAR chDrive;
|
|
|
|
DBG_ENTER(FTF_DDE, DDE_AddItem);
|
|
|
|
// Make sure group name is setup
|
|
_CheckForCurrentGroup(pddec);
|
|
|
|
// Only certain param combinations are allowed.
|
|
nParams = *lpwCmd;
|
|
if (nParams < 1 || nParams == 5 || nParams > 10)
|
|
{
|
|
bRet = FALSE;
|
|
goto Leave;
|
|
}
|
|
|
|
// Make a copy and do fixup on the command.
|
|
//
|
|
// This fixing up was needed for Norton Utilities 2.0 which passed
|
|
// unquoted long filenames with spaces.
|
|
//
|
|
*szCL = 0 ;
|
|
lpwCmd++;
|
|
if( lpszBuf && lpszBuf[*lpwCmd] )
|
|
{
|
|
TCHAR szTmp[MAX_PATH * 2];
|
|
int cch;
|
|
|
|
lstrcpyn(szTmp, &lpszBuf[*lpwCmd], ARRAYSIZE(szCL));
|
|
cch = lstrlen(szTmp);
|
|
|
|
// Is this string inside quotes?
|
|
if ((cch > 1) && (szTmp[0] == TEXT('"')) && (szTmp[cch-1] == TEXT('"')))
|
|
{
|
|
LPTSTR pszChar;
|
|
|
|
// HACKHACK (reinerf)
|
|
// some apps pass us quoted strings that contain both the exe and the args (eg lotus cc:mail 8.0)
|
|
// others apps pass a quoted relative path that does NOT have args, but _does_ contain spaces (eg WSFtpPro6).
|
|
// so we only strip off the outside quotes if one of the characters inside is NOT a legal filename character,
|
|
// indicating that there are args in the string
|
|
for (pszChar = &szTmp[1]; pszChar < &szTmp[cch-1]; pszChar = CharNext(pszChar))
|
|
{
|
|
if (!PathIsValidChar(*pszChar, PIVC_LFN_FULLPATH | PIVC_ALLOW_QUOTE))
|
|
{
|
|
// we found something that isint a legal path character (eg '/'), so we assume that this
|
|
// string has args within the quotes and we strip them off (eg ""c:\foo\bar.exe /s /v:1"")
|
|
PathUnquoteSpaces(szTmp);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( PathProcessCommand( szTmp, szCL, ARRAYSIZE(szCL),
|
|
PPCF_ADDQUOTES|PPCF_ADDARGUMENTS|PPCF_LONGESTPOSSIBLE ) <= 0 )
|
|
lstrcpyn( szCL, szTmp, ARRAYSIZE(szCL) ) ;
|
|
}
|
|
|
|
if( !*szCL )
|
|
{
|
|
bRet = FALSE ;
|
|
goto Leave ;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
// Separate the args.
|
|
if (HConv_PartnerIsLFNAware(pddec->hconv))
|
|
{
|
|
// Quotes will have been left in the string.
|
|
TraceMsg(TF_DDE, "Partner is LFN aware.");
|
|
}
|
|
else
|
|
{
|
|
// Quotes will have been removed from the string.
|
|
TraceMsg(TF_DDE, "Partner is not LFN aware.");
|
|
}
|
|
#endif
|
|
|
|
// We initialize the IDLIst of this shell link to NULL, such that
|
|
// when we set it later it won't mess around with the working directory
|
|
// we may have set.
|
|
pddec->psl->SetIDList(NULL);
|
|
|
|
// NB - This can deal with quoted spaces.
|
|
PathRemoveBlanks(szCL);
|
|
lpszArgs = PathGetArgs(szCL);
|
|
if (*lpszArgs)
|
|
*(lpszArgs-1) = TEXT('\0');
|
|
|
|
// Win32/Win4.0 setup apps are allowed to use paths with (quoted)
|
|
// spaces in them so we may need to remove them now.
|
|
PathUnquoteSpaces(szCL);
|
|
|
|
pddec->psl->SetArguments(lpszArgs);
|
|
|
|
// Special case UNC paths.
|
|
if ((pddec->dwFlags & DDECONV_NO_UNC) && PathIsUNC(szCL))
|
|
{
|
|
TCHAR szShare[MAX_PATH];
|
|
|
|
// CL is a UNC but we know this app can't handle UNC's, we'll need to
|
|
// fake up a drive for it.
|
|
TraceMsg(TF_DDE, "Mapping UNC to drive.");
|
|
|
|
// Get the server/share name.
|
|
StringCchCopy(szShare, ARRAYSIZE(szShare), szCL); // truncation ok since we strip to root
|
|
PrivatePathStripToRoot(szShare);
|
|
// Do we already have a cached connection to this server share?
|
|
if (lstrcmpi(szShare, pddec->szShare) == 0)
|
|
{
|
|
// Yes
|
|
TraceMsg(TF_DDE, "Using cached connection.");
|
|
// Mangle the path to use the drive instead of the UNC.
|
|
Path_ChangeUNCToDrive(szCL, pddec->chDrive);
|
|
}
|
|
else
|
|
{
|
|
// No
|
|
TraceMsg(TF_DDE, "Creating new connection.");
|
|
// Make a connection.
|
|
if (Net_ConnectDrive(szShare, &chDrive))
|
|
{
|
|
// Store the server/share.
|
|
lstrcpy(pddec->szShare, szShare);
|
|
// Store the drive.
|
|
pddec->chDrive = chDrive;
|
|
// Set the DDECONV_FORCED_CONNECTION flag so we can cleanup later.
|
|
pddec->dwFlags |= DDECONV_FORCED_CONNECTION;
|
|
// Mangle the path to use the drive instead of the UNC.
|
|
Path_ChangeUNCToDrive(szCL, pddec->chDrive);
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_DDE, "Can't create connection.");
|
|
}
|
|
}
|
|
TraceMsg(TF_DDE, "CL changed to %s.", szCL);
|
|
}
|
|
|
|
// Is there a name?
|
|
szName[0] = TEXT('\0');
|
|
if (nParams > 1)
|
|
{
|
|
// Yep,
|
|
lpwCmd++;
|
|
lstrcpy(szName, &lpszBuf[*lpwCmd]);
|
|
}
|
|
|
|
// Make absolutely sure we have a name.
|
|
if (!szName[0])
|
|
BuildDefaultName(szName, szCL);
|
|
|
|
// Make it legal.
|
|
FileName_MakeLegal(szName);
|
|
|
|
// NB Skip setting the CL until we get the WD, we may need
|
|
// it.
|
|
|
|
// Deal with the icon path.
|
|
if (nParams > 2)
|
|
{
|
|
lpwCmd++;
|
|
lstrcpy(szTmp, &lpszBuf[*lpwCmd]);
|
|
if (*szTmp)
|
|
{
|
|
// Some people try to put arguments on the icon path line.
|
|
lpszArgs = PathGetArgs(szTmp);
|
|
if (*lpszArgs)
|
|
*(lpszArgs-1) = TEXT('\0');
|
|
// Save it.
|
|
fIconPath = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
szTmp[0] = TEXT('\0');
|
|
}
|
|
|
|
iIcon = 0;
|
|
// Icon index
|
|
if (nParams > 3)
|
|
{
|
|
lpwCmd++;
|
|
// They must have had an icon path for this to make sense.
|
|
if (fIconPath)
|
|
{
|
|
iIcon = StrToInt(&lpszBuf[*lpwCmd]);
|
|
// REVIEW Don't support icon indexs > 666 hack anymore.
|
|
// It used to mark this item as the selected one. This
|
|
// won't work in the new shell.
|
|
if (iIcon >= 666)
|
|
{
|
|
iIcon -= 666;
|
|
}
|
|
}
|
|
}
|
|
|
|
pddec->psl->SetIconLocation(szTmp, iIcon);
|
|
|
|
// Get the point :-)
|
|
// REVIEW UNDONE ForcePt stuff for ReplaceItem.
|
|
if (nParams > 4)
|
|
{
|
|
POINT ptIcon;
|
|
lpwCmd++;
|
|
ptIcon.x = StrToInt(&lpszBuf[*lpwCmd]);
|
|
lpwCmd++;
|
|
ptIcon.y = StrToInt(&lpszBuf[*lpwCmd]);
|
|
}
|
|
|
|
// The working dir. Do we need a default one?
|
|
if (nParams > 6)
|
|
{
|
|
lpwCmd++;
|
|
lstrcpy(szTmp, &lpszBuf[*lpwCmd]);
|
|
}
|
|
else
|
|
{
|
|
szTmp[0] = TEXT('\0');
|
|
}
|
|
|
|
// If we don't have a default directory, try to derive one from the
|
|
// given CL (unless it's a UNC).
|
|
if (!szTmp[0])
|
|
{
|
|
// Use the command for this.
|
|
// REVIEW UNDONE It would be better fo the WD and the IP to be
|
|
// moveable like the CL.
|
|
lstrcpyn(szTmp, szCL, ARRAYSIZE(szTmp));
|
|
// Remove the last component.
|
|
PathRemoveFileSpec(szTmp);
|
|
}
|
|
|
|
// Don't use UNC paths.
|
|
if (PathIsUNC(szTmp))
|
|
pddec->psl->SetWorkingDirectory(c_szNULL);
|
|
else
|
|
pddec->psl->SetWorkingDirectory(szTmp);
|
|
|
|
// Now we have a WD we can deal with the command line better.
|
|
dirs[0] = szTmp;
|
|
dirs[1] = NULL;
|
|
PathResolve(szCL, (LPCTSTR*)dirs, PRF_TRYPROGRAMEXTENSIONS | PRF_VERIFYEXISTS);
|
|
|
|
pidl = ILCreateFromPath(szCL);
|
|
if (!pidl)
|
|
{
|
|
TraceMsg(TF_DDE, "Can't create IL from path. Using simple idlist.");
|
|
// REVIEW UNDONE Check that the file doesn't exist.
|
|
pidl = SHSimpleIDListFromPath(szCL);
|
|
// The Family Circle Cookbook tries to create a shortcut
|
|
// to wordpad.exe but since that's now not on the path
|
|
// we can't find it. The fix is to do what ShellExec does
|
|
// and check the App Paths section of the registry.
|
|
if (!pidl)
|
|
{
|
|
pidl = Pidl_CreateUsingAppPaths(szCL);
|
|
}
|
|
}
|
|
|
|
if (pidl)
|
|
{
|
|
pddec->psl->SetIDList(pidl);
|
|
ILFree(pidl);
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_DDE, "Can't create idlist for %s", szCL);
|
|
|
|
if (pddec->dwFlags & DDECONV_ALLOW_INVALID_CL)
|
|
bRet = TRUE;
|
|
else
|
|
bRet = FALSE;
|
|
|
|
goto Leave;
|
|
}
|
|
|
|
// Hotkey.
|
|
if (nParams > 7)
|
|
{
|
|
WORD wHotkey;
|
|
lpwCmd++;
|
|
wHotkey = (WORD)StrToInt(&lpszBuf[*lpwCmd]);
|
|
pddec->psl->SetHotkey(wHotkey);
|
|
}
|
|
else
|
|
{
|
|
pddec->psl->SetHotkey(0);
|
|
}
|
|
|
|
// Show command
|
|
if (nParams > 8)
|
|
{
|
|
lpwCmd++;
|
|
if (StrToInt(&lpszBuf[*lpwCmd]))
|
|
nShowCmd = SW_SHOWMINNOACTIVE;
|
|
else
|
|
nShowCmd = SW_SHOWNORMAL;
|
|
pddec->psl->SetShowCmd(nShowCmd);
|
|
}
|
|
else
|
|
{
|
|
pddec->psl->SetShowCmd(SW_SHOWNORMAL);
|
|
}
|
|
if (nParams > 9)
|
|
{
|
|
lpwCmd++;
|
|
if (StrToInt(&lpszBuf[*lpwCmd]))
|
|
{
|
|
// FEATURE - BobDay - Handle Setup of Seperate VDM flag!
|
|
// pddec->psl->SetSeperateVDM(pddec->psl, wHotkey);
|
|
}
|
|
}
|
|
|
|
pddec->fDirty = TRUE;
|
|
|
|
PathCombine(szTmp, pddec->szGroup, szName);
|
|
lstrcat(szTmp, c_szDotLnk);
|
|
PathQualify(szTmp);
|
|
|
|
// We need to handle link duplication problems on SFN drives.
|
|
if (!IsLFNDrive(szTmp) && PathFileExistsAndAttributes(szTmp, NULL))
|
|
PathYetAnotherMakeUniqueName(szTmp, szTmp, NULL, NULL);
|
|
|
|
pddec->psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
|
|
|
|
SHTCharToUnicode(szTmp, wszPath, ARRAYSIZE(wszPath));
|
|
ppf->Save(wszPath, TRUE);
|
|
ppf->Release();
|
|
// REVIEW - Sometimes links don't get the right icons. The theory is that
|
|
// a folder in the process of opening (due to a CreateGroup) will pick
|
|
// up a partially written .lnk file. When the link is finally complete
|
|
// we send a SHCNE_CREATE but this will get ignored if defview already has
|
|
// the incomplete item. To hack around this we generate an update item
|
|
// event to force an incomplete link to be re-read.
|
|
TraceMsg(TF_DDE, "Generating events.");
|
|
|
|
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, szTmp, NULL);
|
|
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szTmp, NULL);
|
|
|
|
bRet = TRUE;
|
|
|
|
Leave:
|
|
DBG_EXIT_BOOL(FTF_DDE, DDE_AddItem, bRet);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
|
|
|
|
// [ DeleteItem (ItemName)]
|
|
// This deletes the specified item from a group
|
|
BOOL DDE_DeleteItem(LPTSTR lpszBuf, UINT *lpwCmd, PDDECONV pddec)
|
|
{
|
|
BOOL bRet;
|
|
TCHAR szPath[MAX_PATH];
|
|
|
|
DBG_ENTER(FTF_DDE, DDE_DeleteItem);
|
|
|
|
if (*lpwCmd != 1)
|
|
{
|
|
bRet = FALSE;
|
|
}
|
|
else
|
|
{
|
|
lpwCmd++;
|
|
|
|
// Make sure group name is setup
|
|
_CheckForCurrentGroup(pddec);
|
|
|
|
pddec->fDirty = TRUE;
|
|
|
|
// REVIEW IANEL Hardcoded .lnk and .pif
|
|
PathCombine(szPath, pddec->szGroup, &lpszBuf[*lpwCmd]);
|
|
lstrcat(szPath, c_szDotLnk);
|
|
bRet = Win32DeleteFile(szPath);
|
|
|
|
PathCombine(szPath, pddec->szGroup, &lpszBuf[*lpwCmd]);
|
|
lstrcat(szPath, c_szDotPif);
|
|
bRet |= DeleteFile(szPath);
|
|
}
|
|
|
|
DBG_EXIT_BOOL(FTF_DDE, DDE_DeleteItem, bRet);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
// [ ExitProgman (bSaveGroups) ]
|
|
// REVIEW This doesn't do anything in the new shell. It's supported to stop
|
|
// old installations from barfing.
|
|
// REVIEW UNDONE - We should keep track of the groups we've shown
|
|
// and maybe hide them now.
|
|
BOOL DDE_ExitProgman(LPTSTR lpszBuf, UINT *lpwCmd, PDDECONV pddec)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// [ Reload (???) ]
|
|
// REVIEW Just return FALSE
|
|
BOOL DDE_Reload(LPTSTR lpszBuf, UINT *lpwCmd, PDDECONV pddec)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
PDDECONV DDE_MapHConv(HCONV hconv)
|
|
{
|
|
PDDECONV pddec;
|
|
|
|
ENTERCRITICAL;
|
|
for (pddec = g_pddecHead; pddec != NULL; pddec = pddec->pddecNext)
|
|
{
|
|
if (pddec->hconv == hconv)
|
|
break;
|
|
}
|
|
|
|
if (pddec)
|
|
DDEConv_AddRef(pddec);
|
|
|
|
LEAVECRITICAL;
|
|
|
|
TraceMsg(TF_DDE, "Mapping " SPRINTF_PTR " -> " SPRINTF_PTR , (DWORD_PTR)hconv, (ULONG_PTR)(LPVOID)pddec);
|
|
return(pddec);
|
|
}
|
|
|
|
//
|
|
// This data structure is used to return the error information from
|
|
// _GetPIDLFromDDEArgs to its caller. The caller may pop up a message
|
|
// box using this information. idMsg==0 indicates there is no such
|
|
// information.
|
|
//
|
|
typedef struct _SHDDEERR { // sde (Software Design Engineer, Not!)
|
|
UINT idMsg;
|
|
TCHAR szParam[MAX_PATH];
|
|
} SHDDEERR, *PSHDDEERR;
|
|
|
|
|
|
// Helper function to convert passed in command parameters into the
|
|
// appropriate Id list
|
|
LPITEMIDLIST _GetPIDLFromDDEArgs(UINT nArg, LPTSTR lpszBuf, UINT * lpwCmd, PSHDDEERR psde, LPCITEMIDLIST *ppidlGlobal)
|
|
{
|
|
LPTSTR lpsz;
|
|
LPITEMIDLIST pidl = NULL;
|
|
|
|
// Switch from 0-based to 1-based
|
|
++nArg;
|
|
if (*lpwCmd < nArg)
|
|
{
|
|
TraceMsg(TF_DDE, "Invalid parameter count of %d", *lpwCmd);
|
|
return NULL;
|
|
}
|
|
|
|
// Skip to the right argument
|
|
lpwCmd += nArg;
|
|
lpsz = &lpszBuf[*lpwCmd];
|
|
|
|
TraceMsg(TF_DDE, "Converting \"%s\" to pidl", lpsz);
|
|
// REVIEW: all associations will go through here. this
|
|
// is probably not what we want for normal cmd line type operations
|
|
|
|
// A colon at the begining of the path means that this is either
|
|
// a pointer to a pidl (win95 classic) or a handle:pid (all other
|
|
// platforms including win95+IE4). Otherwise, it's a regular path.
|
|
|
|
if (lpsz[0] == TEXT(':'))
|
|
{
|
|
HANDLE hMem;
|
|
DWORD dwProcId;
|
|
LPTSTR pszNextColon;
|
|
|
|
// Convert the string into a pidl.
|
|
|
|
hMem = LongToHandle(StrToLong((LPTSTR)(lpsz+1))) ;
|
|
pszNextColon = StrChr(lpsz+1,TEXT(':'));
|
|
if (pszNextColon)
|
|
{
|
|
LPITEMIDLIST pidlShared;
|
|
|
|
dwProcId = (DWORD)StrToLong(pszNextColon+1);
|
|
pidlShared = (LPITEMIDLIST)SHLockShared(hMem,dwProcId);
|
|
if (pidlShared && !IsBadReadPtr(pidlShared,1))
|
|
{
|
|
pidl = ILClone(pidlShared);
|
|
SHUnlockShared(pidlShared);
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_WARNING, "DDE SHMem failed - App probably forgot to pass SEE_MASK_FLAG_DDEWAIT");
|
|
}
|
|
SHFreeShared(hMem,dwProcId);
|
|
}
|
|
else if ( hMem && !IsBadReadPtr( hMem, sizeof(WORD)))
|
|
{
|
|
// this is likely to be browser only mode on win95 with the old pidl arguments which is
|
|
// going to be in shared memory.... (must be cloned into local memory)...
|
|
pidl = ILClone((LPITEMIDLIST) hMem);
|
|
|
|
// this will get freed if we succeed.
|
|
ASSERT( ppidlGlobal );
|
|
*ppidlGlobal = (LPITEMIDLIST) hMem;
|
|
}
|
|
|
|
return pidl;
|
|
}
|
|
else
|
|
{
|
|
TCHAR tszQual[MAX_PATH];
|
|
|
|
// We must copy to a temp buffer because the PathQualify may
|
|
// result in a string longer than our input buffer and faulting
|
|
// seems like a bad way of handling that situation.
|
|
lstrcpyn(tszQual, lpsz, ARRAYSIZE(tszQual));
|
|
lpsz = tszQual;
|
|
|
|
// Is this a URL?
|
|
if (!PathIsURL(lpsz))
|
|
{
|
|
// No; qualify it
|
|
PathQualifyDef(lpsz, NULL, PQD_NOSTRIPDOTS);
|
|
}
|
|
|
|
pidl = ILCreateFromPath(lpsz);
|
|
|
|
if (pidl==NULL && psde)
|
|
{
|
|
psde->idMsg = IDS_CANTFINDDIR;
|
|
lstrcpyn(psde->szParam, lpsz, ARRAYSIZE(psde->szParam));
|
|
}
|
|
return pidl;
|
|
}
|
|
}
|
|
|
|
|
|
LPITEMIDLIST GetPIDLFromDDEArgs(LPTSTR lpszBuf, UINT * lpwCmd, PSHDDEERR psde, LPCITEMIDLIST * ppidlGlobal)
|
|
{
|
|
LPITEMIDLIST pidl = _GetPIDLFromDDEArgs(1, lpszBuf, lpwCmd, psde, ppidlGlobal);
|
|
if (!pidl)
|
|
{
|
|
pidl = _GetPIDLFromDDEArgs(0, lpszBuf, lpwCmd, psde, ppidlGlobal);
|
|
}
|
|
|
|
return pidl;
|
|
}
|
|
|
|
void _FlagsToParams(UINT uFlags, LPTSTR pszParams)
|
|
{
|
|
if (uFlags & COF_EXPLORE)
|
|
lstrcat(pszParams, TEXT(",/E"));
|
|
|
|
if (uFlags & COF_SELECT)
|
|
lstrcat(pszParams, TEXT(",/SELECT"));
|
|
|
|
if (uFlags & COF_CREATENEWWINDOW)
|
|
lstrcat(pszParams, TEXT(",/N"));
|
|
|
|
if (uFlags & COF_USEOPENSETTINGS)
|
|
lstrcat(pszParams, TEXT(",/S"));
|
|
}
|
|
|
|
|
|
#define SZ_EXPLORER_EXE TEXT("explorer.exe")
|
|
|
|
HRESULT GetExplorerPath(LPTSTR pszExplorer, DWORD cchSize)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// This process is either iexplore.exe or explorer.exe.
|
|
// If it's explorer.exe, we want to use it's path also.
|
|
if (GetModuleFileName(NULL, pszExplorer, cchSize))
|
|
{
|
|
LPCTSTR pszFileName = PathFindFileName(pszExplorer);
|
|
|
|
// This may not be the explorer.exe process.
|
|
if (0 != StrCmpI(pszFileName, SZ_EXPLORER_EXE))
|
|
{
|
|
StrCpyN(pszExplorer, SZ_EXPLORER_EXE, cchSize);
|
|
}
|
|
}
|
|
else
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
BOOL IsDesktopProcess(HWND hwnd)
|
|
{
|
|
DWORD dwProcessID;
|
|
DWORD dwDesktopProcessID;
|
|
|
|
if (!hwnd)
|
|
return FALSE;
|
|
|
|
GetWindowThreadProcessId(GetShellWindow(), &dwDesktopProcessID);
|
|
GetWindowThreadProcessId(hwnd, &dwProcessID);
|
|
return (dwProcessID == dwDesktopProcessID);
|
|
}
|
|
|
|
// lpszBuf is a multi-string containing the various parameters.
|
|
|
|
// lpwCmd is an array of indexes, where the first
|
|
// element is the count of parameters, and each element
|
|
// after that is the starting offset into lpszBuf
|
|
// for the respective parameter.
|
|
|
|
BOOL DoDDE_ViewFolder(IShellBrowser* psb, HWND hwndParent, LPTSTR pszBuf, UINT *puCmd, BOOL fExplore, DWORD dwHotKey, HMONITOR hMonitor)
|
|
{
|
|
// used to support the older win95 (browser only mode) Global passing of pidl pointers..
|
|
LPITEMIDLIST pidlGlobal = NULL;
|
|
LPITEMIDLIST pidl;
|
|
int nCmdShow;
|
|
SHDDEERR sde = { 0 };
|
|
BOOL fSuccess = TRUE;
|
|
|
|
if (*puCmd != 3)
|
|
return FALSE; // Wrong number of arguments
|
|
|
|
// The ShowWindow parameter is the third
|
|
nCmdShow = StrToLong(&pszBuf[*(puCmd+3)]);
|
|
|
|
pidl = GetPIDLFromDDEArgs(pszBuf, puCmd, &sde, (LPCITEMIDLIST*)&pidlGlobal);
|
|
if (pidl)
|
|
{
|
|
IETHREADPARAM *pfi = SHCreateIETHREADPARAM(NULL, nCmdShow, NULL, NULL);
|
|
if (pfi)
|
|
{
|
|
pfi->hwndCaller = hwndParent;
|
|
pfi->pidl = ILClone(pidl);
|
|
pfi->wHotkey = (UINT)dwHotKey;
|
|
pfi->uFlags = COF_NORMAL;
|
|
pfi->psbCaller = psb;
|
|
if (psb)
|
|
{
|
|
psb->AddRef(); // for pfi->psbCaller
|
|
}
|
|
|
|
psb = NULL; // ownership transferred to pfi!
|
|
|
|
// Check for a :0 thing. Probably came from the command line.
|
|
if (lstrcmpi(&pszBuf[*(puCmd+2)], TEXT(":0")) != 0)
|
|
{
|
|
// we need to use COF_USEOPENSETTINGS here. this is where the open
|
|
// from within cabinets happen. if it's done via the command line
|
|
// then it will esentially turn to COF_NORMAL because the a cabinet
|
|
// window won't be the foreground window.
|
|
|
|
pfi->uFlags = COF_USEOPENSETTINGS;
|
|
}
|
|
|
|
if (hMonitor != NULL)
|
|
{
|
|
pfi->pidlRoot = reinterpret_cast<LPITEMIDLIST>(hMonitor);
|
|
pfi->uFlags |= COF_HASHMONITOR;
|
|
}
|
|
|
|
if (fExplore)
|
|
pfi->uFlags |= COF_EXPLORE;
|
|
|
|
// The REST_SEPARATEDESKTOPPROCESS restriction means that all shell windows
|
|
// should be opened in an explorer other then the desktop explorer.exe process.
|
|
// However, shell windows need to be in the same second explorer.exe instance.
|
|
BOOL bSepProcess = FALSE;
|
|
|
|
if (IsDesktopProcess(hwndParent))
|
|
{
|
|
bSepProcess = TRUE;
|
|
|
|
if (!SHRestricted(REST_SEPARATEDESKTOPPROCESS))
|
|
{
|
|
SHELLSTATE ss;
|
|
|
|
SHGetSetSettings(&ss, SSF_SEPPROCESS, FALSE);
|
|
bSepProcess = ss.fSepProcess;
|
|
}
|
|
}
|
|
|
|
if (bSepProcess)
|
|
{
|
|
TCHAR szExplorer[MAX_PATH];
|
|
TCHAR szCmdLine[MAX_PATH];
|
|
SHELLEXECUTEINFO ei = { sizeof(ei), 0, NULL, NULL, szExplorer, szCmdLine, NULL, SW_SHOWNORMAL};
|
|
|
|
DWORD dwProcess = GetCurrentProcessId();
|
|
HANDLE hIdList = NULL;
|
|
|
|
GetExplorerPath(szExplorer, ARRAYSIZE(szExplorer));
|
|
fSuccess = TRUE;
|
|
if (pfi->pidl)
|
|
{
|
|
hIdList = SHAllocShared(pfi->pidl, ILGetSize(pfi->pidl), dwProcess);
|
|
wsprintf(szCmdLine, TEXT("/IDLIST,:%ld:%ld"), hIdList, dwProcess);
|
|
if (!hIdList)
|
|
fSuccess = FALSE;
|
|
}
|
|
else
|
|
{
|
|
lstrcpy(szCmdLine, TEXT("/IDLIST,:0"));
|
|
}
|
|
|
|
_FlagsToParams(pfi->uFlags, szCmdLine + lstrlen(szCmdLine));
|
|
|
|
if (fSuccess)
|
|
{
|
|
fSuccess = ShellExecuteEx(&ei);
|
|
}
|
|
if (!fSuccess && hIdList)
|
|
SHFreeShared(hIdList, dwProcess);
|
|
|
|
SHDestroyIETHREADPARAM(pfi);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Check if this is a folder or not. If not, we always create
|
|
// a new window (even though we can browse in-place). If you
|
|
// don't like it, please talk to ChristoB. (SatoNa)
|
|
//
|
|
// I don't like it... not for the explore case.
|
|
//
|
|
if (!(pfi->uFlags & COF_EXPLORE))
|
|
{
|
|
ULONG dwAttr = SFGAO_FOLDER;
|
|
if (SUCCEEDED(SHGetAttributesOf(pidl, &dwAttr)) && !(dwAttr & SFGAO_FOLDER))
|
|
{
|
|
pfi->uFlags |= COF_CREATENEWWINDOW;
|
|
}
|
|
}
|
|
fSuccess = SHOpenFolderWindow(pfi); // takes ownership of the whole pfi thing
|
|
}
|
|
|
|
if (!fSuccess && (GetLastError() == ERROR_OUTOFMEMORY))
|
|
SHAbortInvokeCommand();
|
|
|
|
fSuccess = TRUE; // If we fail we don't want people to try
|
|
// to create process as this will blow up...
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
else
|
|
{
|
|
if (sde.idMsg)
|
|
{
|
|
ShellMessageBox(HINST_THISDLL, hwndParent,
|
|
MAKEINTRESOURCE(sde.idMsg), MAKEINTRESOURCE(IDS_CABINET),
|
|
MB_OK|MB_ICONHAND|MB_SETFOREGROUND, sde.szParam);
|
|
}
|
|
fSuccess = FALSE;
|
|
}
|
|
|
|
if (fSuccess)
|
|
ILFree(pidlGlobal);
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
|
|
BOOL DDE_ViewFolder(LPTSTR lpszBuf, UINT * puCmd, PDDECONV pddec)
|
|
{
|
|
return DoDDE_ViewFolder(NULL, NULL, lpszBuf, puCmd, FALSE, 0, NULL);
|
|
}
|
|
|
|
|
|
// FEATURE ExploreFolder and ViewFolder do the same thing right now, maybe
|
|
// they should do something different
|
|
BOOL DDE_ExploreFolder(LPTSTR lpszBuf, UINT * puCmd, PDDECONV pddec)
|
|
{
|
|
return DoDDE_ViewFolder(NULL, NULL, lpszBuf, puCmd, TRUE, 0, NULL);
|
|
}
|
|
|
|
|
|
BOOL DDE_FindFolder(LPTSTR lpszBuf, UINT * puCmd, PDDECONV pddec)
|
|
{
|
|
LPITEMIDLIST pidlGlobal = NULL;
|
|
LPITEMIDLIST pidl = GetPIDLFromDDEArgs(lpszBuf, puCmd, NULL, (LPCITEMIDLIST*)&pidlGlobal);
|
|
if (pidl)
|
|
{
|
|
// A very large hack. If the pidl is to the network neighborhood,
|
|
// we do a FindComputer instead!
|
|
LPITEMIDLIST pidlNetwork = SHCloneSpecialIDList(NULL, CSIDL_NETWORK, FALSE);
|
|
if (pidlNetwork && ILIsEqual(pidlNetwork, pidl))
|
|
SHFindComputer(pidl, NULL);
|
|
else
|
|
SHFindFiles(pidl, NULL);
|
|
ILFree(pidlNetwork);
|
|
ILFree(pidl);
|
|
ILFree(pidlGlobal);
|
|
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
// This processes the Find Folder command. It is used for both for selecting
|
|
// Find on a folders context menu as well as opening a find file.
|
|
BOOL DDE_OpenFindFile(LPTSTR lpszBuf, UINT * puCmd, PDDECONV pddec)
|
|
{
|
|
LPITEMIDLIST pidlGlobal = NULL;
|
|
LPITEMIDLIST pidl = GetPIDLFromDDEArgs(lpszBuf, puCmd, NULL, (LPCITEMIDLIST*)&pidlGlobal);
|
|
if (pidl)
|
|
{
|
|
SHFindFiles(NULL, pidl);
|
|
ILFree( pidlGlobal );
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL DDE_ConfirmID(LPTSTR lpszBuf, UINT * puCmd, PDDECONV pddec)
|
|
{
|
|
BOOL bRet;
|
|
|
|
DBG_ENTER(FTF_DDE, DDE_ConfirmID);
|
|
|
|
bRet = (*puCmd == 0);
|
|
|
|
DBG_EXIT_BOOL(FTF_DDE, DDE_ConfirmID, bRet);
|
|
return bRet;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
BOOL DDE_Beep(LPTSTR lpszBuf, UINT * puCmd, PDDECONV pddec)
|
|
{
|
|
#if 0
|
|
int i;
|
|
|
|
for (i=*puCmd; i>=0; --i)
|
|
{
|
|
MessageBeep(0);
|
|
}
|
|
return(TRUE);
|
|
#else
|
|
DWORD dwTime;
|
|
|
|
dwTime = GetTickCount();
|
|
TraceMsg(TF_DDE, "Spin...");
|
|
// Spin. Spin. Spin. Huh Huh. Cool.
|
|
while ((GetTickCount()-dwTime) < 4000)
|
|
{
|
|
// Spin.
|
|
}
|
|
TraceMsg(TF_DDE, "Spinning done.");
|
|
return TRUE;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
|
|
BOOL DDE_ShellFile(LPTSTR lpszBuf, UINT * puCmd, PDDECONV pddec)
|
|
{
|
|
LPITEMIDLIST pidlGlobal = NULL;
|
|
LPITEMIDLIST pidl = GetPIDLFromDDEArgs(lpszBuf, puCmd, NULL, (LPCITEMIDLIST*)&pidlGlobal);
|
|
if (pidl)
|
|
{
|
|
ShellExecCommandFile(pidl);
|
|
|
|
ILFree(pidl);
|
|
ILFree(pidlGlobal);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
VOID CALLBACK TimerProc_RepeatAcks(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
|
|
{
|
|
HWND hwndPartner;
|
|
|
|
if (g_hwndDde)
|
|
{
|
|
hwndPartner = _GetDDEPartnerWindow((HCONV)g_hwndDde);
|
|
if (hwndPartner)
|
|
{
|
|
TraceMsg(TF_DDE, "DDE partner (%x) appears to be stuck - repeating Ack.", hwndPartner);
|
|
PostMessage(hwndPartner, WM_DDE_ACK, (WPARAM)g_hwndDde, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
HDDEDATA HandleDDEExecute(HDDEDATA hData, HCONV hconv)
|
|
{
|
|
UINT *lpwCmd;
|
|
UINT *lpwCmdTemp;
|
|
UINT wCmd;
|
|
PDDECONV pddec;
|
|
HDDEDATA hddeRet = (HDDEDATA) DDE_FACK;
|
|
UINT nErr;
|
|
LPTSTR pszBuf;
|
|
int cbData;
|
|
|
|
DBG_ENTER(FTF_DDE, HandleDDEExecute);
|
|
|
|
pddec = DDE_MapHConv(hconv);
|
|
if (pddec == NULL)
|
|
{
|
|
// Could not find conversation
|
|
hddeRet = HDDENULL;
|
|
goto Leave;
|
|
}
|
|
|
|
if ((pddec->dwFlags & DDECONV_REPEAT_ACKS) && g_nTimer)
|
|
{
|
|
KillTimer(NULL, g_nTimer);
|
|
g_nTimer = 0;
|
|
}
|
|
|
|
// NB Living Books Installer cats all their commands together
|
|
// which requires about 300bytes - better just allocate it on
|
|
// the fly.
|
|
cbData = DdeGetData(hData, NULL, 0, 0L);
|
|
if (cbData == 0)
|
|
{
|
|
// No data?
|
|
hddeRet = HDDENULL;
|
|
goto Leave;
|
|
}
|
|
|
|
pszBuf = (LPTSTR)LocalAlloc(LPTR, cbData);
|
|
if (!pszBuf)
|
|
{
|
|
TraceMsg(TF_ERROR, "HandleDDEExecute: Can't allocate buffer (%d)", cbData);
|
|
hddeRet = HDDENULL;
|
|
goto Leave;
|
|
}
|
|
|
|
cbData = DdeGetData(hData, (LPBYTE)pszBuf, cbData, 0L);
|
|
if (cbData == 0)
|
|
{
|
|
nErr = DdeGetLastError(g_dwDDEInst);
|
|
TraceMsg(TF_ERROR, "HandleDDEExecute: Data invalid (%d).", nErr);
|
|
ASSERT(0);
|
|
LocalFree(pszBuf);
|
|
hddeRet = HDDENULL;
|
|
goto Leave;
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
//
|
|
// At this point, we may have ANSI data in pszBuf, but we need UNICODE!
|
|
// !!!HACK alert!!! We're going to poke around in the string to see if it is
|
|
// ansi or unicode. We know that DDE execute commands should only
|
|
// start with " " or "[", so we use that information...
|
|
//
|
|
// By the way, this only really happens when we get an out of order
|
|
// WM_DDE_EXECUTE (app didn't send WM_DDE_INITIATE -- Computer Associate
|
|
// apps like to do this when they setup). Most of the time DDEML will
|
|
// properly translate the data for us because they correctly determine
|
|
// ANSI/UNICODE conversions from the WM_DDE_INITIATE message.
|
|
|
|
if ((cbData>2) &&
|
|
((*((LPBYTE)pszBuf)==(BYTE)' ') || (*((LPBYTE)pszBuf)==(BYTE)'[')) &&
|
|
(*((LPBYTE)pszBuf+1)!=0 ))
|
|
{
|
|
// We think that pszBuf is an ANSI string, so convert it
|
|
LPTSTR pszUBuf;
|
|
|
|
pszUBuf = (LPTSTR)LocalAlloc(LPTR, cbData * sizeof(WCHAR));
|
|
if (pszUBuf)
|
|
{
|
|
MultiByteToWideChar( CP_ACP, 0, (LPCSTR)pszBuf, -1, pszUBuf, cbData );
|
|
LocalFree(pszBuf);
|
|
pszBuf = pszUBuf;
|
|
}
|
|
else
|
|
{
|
|
// gotos are weak but i dont really want to rewrite this function
|
|
LocalFree(pszBuf);
|
|
hddeRet = HDDENULL;
|
|
goto Leave;
|
|
}
|
|
}
|
|
#endif // UNICODE
|
|
|
|
if (pszBuf[0] == TEXT('\0'))
|
|
{
|
|
TraceMsg(TF_ERROR, "HandleDDEExecute: Empty execute command.");
|
|
ASSERT(0);
|
|
LocalFree(pszBuf);
|
|
|
|
hddeRet = HDDENULL;
|
|
goto Leave;
|
|
}
|
|
|
|
TraceMsg(TF_DDE, "Executing %s", pszBuf);
|
|
|
|
lpwCmd = GetDDECommands(pszBuf, c_sDDECommands, HConv_PartnerIsLFNAware(hconv));
|
|
if (!lpwCmd)
|
|
{
|
|
#ifdef DEBUG
|
|
// [] is allowed since it means "nop" (used alot in ifexec where we have already
|
|
// passed the info on cmdline since we had do and exec)
|
|
if (lstrcmpi(pszBuf, TEXT("[]")) != 0)
|
|
{
|
|
ASSERTMSG(FALSE, "HandleDDEExecute: recieved a bogus DDECommand %s", pszBuf);
|
|
}
|
|
#endif
|
|
LocalFree(pszBuf);
|
|
|
|
// Make sure Discis installers get the Ack they're waiting for.
|
|
if ((pddec->dwFlags & DDECONV_REPEAT_ACKS) && !g_nTimer)
|
|
{
|
|
// DebugBreak();
|
|
g_nTimer = SetTimer(NULL, IDT_REPEAT_ACKS, 1000, TimerProc_RepeatAcks);
|
|
}
|
|
|
|
hddeRet = HDDENULL;
|
|
goto Leave;
|
|
}
|
|
|
|
// Store off lpwCmd so we can free the correect addr later
|
|
lpwCmdTemp = lpwCmd;
|
|
|
|
// Execute a command.
|
|
while (*lpwCmd != (UINT)-1)
|
|
{
|
|
wCmd = *lpwCmd++;
|
|
// Subtract 1 to account for the terminating NULL
|
|
if (wCmd < ARRAYSIZE(c_sDDECommands)-1)
|
|
{
|
|
if (!c_sDDECommands[wCmd].lpfnCommand(pszBuf, lpwCmd, pddec))
|
|
{
|
|
hddeRet = HDDENULL;
|
|
}
|
|
}
|
|
|
|
// Next command.
|
|
lpwCmd += *lpwCmd + 1;
|
|
}
|
|
|
|
// Tidyup...
|
|
GlobalFree(lpwCmdTemp);
|
|
LocalFree(pszBuf);
|
|
|
|
// Make sure Discis installers get the Ack they're waiting for.
|
|
if ((pddec->dwFlags & DDECONV_REPEAT_ACKS) && !g_nTimer)
|
|
{
|
|
// DebugBreak();
|
|
g_nTimer = SetTimer(NULL, IDT_REPEAT_ACKS, 1000, TimerProc_RepeatAcks);
|
|
}
|
|
|
|
Leave:
|
|
|
|
if (pddec)
|
|
DDEConv_Release(pddec);
|
|
|
|
DBG_EXIT_DWORD(FTF_DDE, HandleDDEExecute, hddeRet);
|
|
|
|
return hddeRet;
|
|
}
|
|
|
|
|
|
// NOTE: ANSI ONLY
|
|
|
|
// Used for filtering out hidden, . and .. stuff.
|
|
|
|
BOOL FindData_FileIsNormalA(WIN32_FIND_DATAA *lpfd)
|
|
{
|
|
if ((lpfd->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ||
|
|
lstrcmpiA(lpfd->cFileName, c_szDesktopIniA) == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
else if (lpfd->cFileName[0] == '.')
|
|
{
|
|
if ((lpfd->cFileName[1] == '\0') ||
|
|
((lpfd->cFileName[1] == '.') && (lpfd->cFileName[2] == '\0')))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
HDDEDATA EnumGroups(HSZ hszItem)
|
|
{
|
|
TCHAR szGroup[MAX_PATH];
|
|
#ifdef UNICODE
|
|
CHAR szAGroup[MAX_PATH];
|
|
#endif
|
|
WIN32_FIND_DATAA fd;
|
|
HANDLE hff;
|
|
LPSTR lpszBuf = NULL;
|
|
UINT cbBuf = 0;
|
|
UINT cch;
|
|
HDDEDATA hData;
|
|
|
|
// Enumerate all the top level folders in the programs folder.
|
|
SHGetSpecialFolderPath(NULL, szGroup, CSIDL_PROGRAMS, TRUE);
|
|
PathAppend(szGroup, c_szStarDotStar);
|
|
|
|
// We do a bunch of DDE work below, all of which is ANSI only. This is
|
|
// the cleanest point to break over from UNICODE to ANSI, so the conversion
|
|
// is done here.
|
|
// REARCHITECT - BobDay - Is this right? Can't we do all in unicode?
|
|
|
|
#ifdef UNICODE
|
|
if (0 == WideCharToMultiByte(CP_ACP, 0, szGroup, -1, szAGroup, MAX_PATH, NULL, NULL))
|
|
{
|
|
return NULL;
|
|
}
|
|
hff = FindFirstFileA(szAGroup, &fd);
|
|
#else
|
|
hff = FindFirstFile(szGroup, &fd);
|
|
#endif
|
|
|
|
if (hff != INVALID_HANDLE_VALUE)
|
|
{
|
|
do
|
|
{
|
|
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
|
|
(FindData_FileIsNormalA(&fd)))
|
|
{
|
|
LPSTR lpsz;
|
|
// Data is seperated by \r\n.
|
|
cch = lstrlenA(fd.cFileName) + 2;
|
|
lpsz = (LPSTR)_LocalReAlloc(lpszBuf, cbBuf + (cch + 1) * sizeof(TCHAR), LMEM_MOVEABLE|LMEM_ZEROINIT);
|
|
if (lpsz)
|
|
{
|
|
// Copy it over.
|
|
lpszBuf = lpsz;
|
|
lstrcpyA(lpszBuf + cbBuf, fd.cFileName);
|
|
lstrcatA(lpszBuf + cbBuf, c_szCRLF);
|
|
cbBuf = cbBuf + cch ;
|
|
}
|
|
else
|
|
{
|
|
cbBuf = 0;
|
|
break;
|
|
}
|
|
}
|
|
} while (FindNextFileA(hff, &fd));
|
|
FindClose(hff);
|
|
|
|
//
|
|
// If the user is an admin, then we need to enumerate
|
|
// the common groups also.
|
|
//
|
|
|
|
if (IsUserAnAdmin()) {
|
|
|
|
SHGetSpecialFolderPath(NULL, szGroup, CSIDL_COMMON_PROGRAMS, TRUE);
|
|
PathAppend(szGroup, c_szStarDotStar);
|
|
|
|
#ifdef UNICODE
|
|
if (0 == WideCharToMultiByte(CP_ACP, 0, szGroup, -1, szAGroup, MAX_PATH, NULL, NULL))
|
|
{
|
|
return NULL;
|
|
}
|
|
hff = FindFirstFileA(szAGroup, &fd);
|
|
#else
|
|
hff = FindFirstFile(szGroup, &fd);
|
|
#endif
|
|
|
|
|
|
if (hff != INVALID_HANDLE_VALUE)
|
|
{
|
|
do
|
|
{
|
|
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
|
|
(FindData_FileIsNormalA(&fd)))
|
|
{
|
|
LPSTR lpsz;
|
|
// Data is seperated by \r\n.
|
|
cch = lstrlenA(fd.cFileName) + 2;
|
|
lpsz = (LPSTR)_LocalReAlloc(lpszBuf, cbBuf + (cch + 1) * sizeof(TCHAR), LMEM_MOVEABLE|LMEM_ZEROINIT);
|
|
if (lpsz)
|
|
{
|
|
// Copy it over.
|
|
lpszBuf = lpsz;
|
|
lstrcpyA(lpszBuf + cbBuf, fd.cFileName);
|
|
lstrcatA(lpszBuf + cbBuf, c_szCRLF);
|
|
cbBuf = cbBuf + cch ;
|
|
}
|
|
else
|
|
{
|
|
cbBuf = 0;
|
|
break;
|
|
}
|
|
}
|
|
} while (FindNextFileA(hff, &fd));
|
|
FindClose(hff);
|
|
}
|
|
}
|
|
|
|
// Now package up the data and return.
|
|
if (lpszBuf)
|
|
{
|
|
// Don't stomp on the last crlf, Word hangs while setting up
|
|
// if this isn't present, just stick a null on the end.
|
|
lpszBuf[cbBuf] = TEXT('\0');
|
|
if (hszItem)
|
|
{
|
|
hData = DdeCreateDataHandle(g_dwDDEInst, (LPBYTE)lpszBuf, cbBuf+1, 0, hszItem, CF_TEXT, 0);
|
|
}
|
|
else
|
|
{
|
|
// Handle NULL hszItems (Logitech Fotomans installer does this). We need to create
|
|
// a new hszItem otherwise DDEML gets confused (Null hszItems are only supposed to
|
|
// be for DDE_EXECUTE data handles).
|
|
TraceMsg(TF_WARNING, "EnumGroups: Invalid (NULL) hszItem used in request, creating new valid one.");
|
|
hszItem = _DdeCreateStringHandle(g_dwDDEInst, c_szGroupsA, CP_WINANSI);
|
|
hData = DdeCreateDataHandle(g_dwDDEInst, (LPBYTE)lpszBuf, cbBuf+1, 0, hszItem, CF_TEXT, 0);
|
|
DdeFreeStringHandle(g_dwDDEInst, hszItem);
|
|
}
|
|
LocalFree(lpszBuf);
|
|
return hData;
|
|
}
|
|
}
|
|
|
|
// Empty list - Progman returned a single null.
|
|
|
|
// (Davepl) I need to cast to LPBYTE since c_szNULLA is const. If this
|
|
// function doesn't really need to write to the buffer, it should be declared
|
|
// as const.
|
|
// (stephstm) This is a public documented fct, no chance it will change.
|
|
|
|
hData = DdeCreateDataHandle(g_dwDDEInst, (LPBYTE)c_szNULLA, 1, 0, hszItem, CF_TEXT, 0);
|
|
return hData;
|
|
}
|
|
|
|
// Crossties 1.0 doesn't like an empty icon path (which couldn't happen in 3.1)
|
|
// so we make one here.
|
|
void ConstructIconPath(LPTSTR pszIP, LPCTSTR pszCL, LPCTSTR pszWD)
|
|
{
|
|
TCHAR sz[MAX_PATH];
|
|
|
|
lstrcpy(sz, pszCL);
|
|
PathRemoveArgs(sz);
|
|
PathUnquoteSpaces(sz);
|
|
FindExecutable(sz, pszWD, pszIP);
|
|
}
|
|
|
|
BOOL GroupItem_GetLinkInfo(LPCTSTR lpszGroupPath, PGROUPITEM pgi, LPCITEMIDLIST pidlLink,
|
|
IShellFolder * psf, IShellLink *psl, IPersistFile *ppf)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
DWORD dwAttribs;
|
|
|
|
ASSERT(pgi);
|
|
ASSERT(pidlLink);
|
|
ASSERT(psf);
|
|
|
|
dwAttribs = SFGAO_LINK;
|
|
if (SUCCEEDED(psf->GetAttributesOf(1, &pidlLink, &dwAttribs)))
|
|
{
|
|
if (dwAttribs & SFGAO_LINK)
|
|
{
|
|
STRRET str;
|
|
TCHAR szName[MAX_PATH];
|
|
|
|
// Get the relevant data.
|
|
// Copy it.
|
|
// Stick pointers in pgi.
|
|
if (SUCCEEDED(psf->GetDisplayNameOf(pidlLink, SHGDN_NORMAL, &str)) &&
|
|
SUCCEEDED(StrRetToBuf(&str, pidlLink, szName, ARRAYSIZE(szName))))
|
|
{
|
|
TCHAR sz[MAX_PATH], szCL[MAX_PATH];
|
|
WCHAR wszPath[MAX_PATH];
|
|
|
|
TraceMsg(TF_DDE, "Link %s", szName);
|
|
|
|
pgi->pszDesc = StrDup(szName);
|
|
PathCombine(sz, lpszGroupPath, szName);
|
|
lstrcat(sz, c_szDotLnk);
|
|
SHTCharToUnicode(sz, wszPath, ARRAYSIZE(wszPath));
|
|
// Read the link.
|
|
// "name","CL",def dir,icon path,x,y,icon index,hotkey,minflag.
|
|
ppf->Load(wszPath, 0);
|
|
// Copy all the data.
|
|
szCL[0] = TEXT('\0');
|
|
if (SUCCEEDED(psl->GetPath(szCL, ARRAYSIZE(szCL), NULL, SLGP_SHORTPATH)))
|
|
{
|
|
// Valid CL?
|
|
if (szCL[0])
|
|
{
|
|
int nShowCmd;
|
|
TCHAR szArgs[MAX_PATH];
|
|
|
|
// Yep, Uses LFN's?
|
|
szArgs[0] = 0;
|
|
psl->GetArguments(szArgs, ARRAYSIZE(szArgs));
|
|
lstrcpy(sz, szCL);
|
|
if (szArgs[0])
|
|
{
|
|
lstrcat(sz, TEXT(" "));
|
|
StrCatBuff(sz, szArgs, ARRAYSIZE(sz));
|
|
}
|
|
pgi->pszCL = StrDup(sz);
|
|
TraceMsg(TF_DDE, "GroupItem_GetLinkInfo: CL %s", sz);
|
|
// WD
|
|
sz[0] = TEXT('\0');
|
|
psl->GetWorkingDirectory(sz, ARRAYSIZE(sz));
|
|
TraceMsg(TF_DDE, "GroupItem_GetLinkInfo: WD %s", sz);
|
|
if (sz[0])
|
|
{
|
|
TCHAR szShortPath[MAX_PATH];
|
|
if (GetShortPathName(sz, szShortPath, ARRAYSIZE(szShortPath)))
|
|
lstrcpy(sz, szShortPath);
|
|
}
|
|
|
|
pgi->pszWD = StrDup(sz);
|
|
// Now setup the Show Command - Need to map to index numbers...
|
|
psl->GetShowCmd(&nShowCmd);
|
|
if (nShowCmd == SW_SHOWMINNOACTIVE)
|
|
{
|
|
TraceMsg(TF_DDE, "GroupItem_GetLinkInfo: Show min.");
|
|
pgi->fMin = TRUE;
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_DDE, "GroupItem_GetLinkInfo: Show normal.");
|
|
pgi->fMin = FALSE;
|
|
}
|
|
// Icon path.
|
|
sz[0] = TEXT('\0');
|
|
pgi->iIcon = 0;
|
|
psl->GetIconLocation(sz, ARRAYSIZE(sz), &pgi->iIcon);
|
|
if (pgi->iIcon < 0)
|
|
pgi->iIcon = 0;
|
|
if (sz[0])
|
|
PathGetShortPath(sz);
|
|
else
|
|
ConstructIconPath(sz, pgi->pszCL, pgi->pszWD);
|
|
TraceMsg(TF_DDE, "GroupItem_GetLinkInfo: IL %s %d", sz, pgi->iIcon);
|
|
pgi->pszIconPath = StrDup(sz);
|
|
// Hotkey
|
|
pgi->wHotkey = 0;
|
|
psl->GetHotkey(&pgi->wHotkey);
|
|
// Success.
|
|
fRet = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// Deal with links to weird things.
|
|
TraceMsg(TF_DDE, "GroupItem_GetLinkInfo: Invalid command line.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
int DSA_DestroyGroupCallback(LPVOID p, LPVOID d)
|
|
{
|
|
PGROUPITEM pgi = (PGROUPITEM)p;
|
|
LocalFree(pgi->pszDesc);
|
|
LocalFree(pgi->pszCL);
|
|
LocalFree(pgi->pszWD);
|
|
LocalFree(pgi->pszIconPath);
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Return the links in a group.
|
|
HDDEDATA EnumItemsInGroup(HSZ hszItem, LPCTSTR lpszGroup)
|
|
{
|
|
HRESULT hres;
|
|
LPITEMIDLIST pidl, pidlGroup;
|
|
IShellFolder * psf;
|
|
TCHAR sz[MAX_PATH];
|
|
TCHAR szLine[MAX_PATH*4];
|
|
HDDEDATA hddedata = HDDENULL;
|
|
ULONG celt;
|
|
GROUPITEM gi;
|
|
int cItems = 0;
|
|
IPersistFile *ppf;
|
|
IShellLink *psl;
|
|
HDSA hdsaGroup;
|
|
UINT cbDDE;
|
|
UINT cchDDE;
|
|
int x, y;
|
|
LPTSTR pszDDE = NULL;
|
|
PGROUPITEM pgi;
|
|
BOOL fOK = FALSE;
|
|
WIN32_FIND_DATA fd;
|
|
HANDLE hFile;
|
|
BOOL bCommon = FALSE;
|
|
|
|
TraceMsg(TF_DDE, "c.eiig: Enumerating %s.", (LPTSTR)lpszGroup);
|
|
|
|
|
|
//
|
|
// Get personal group location
|
|
//
|
|
|
|
if (!SHGetSpecialFolderPath(NULL, sz, CSIDL_PROGRAMS, FALSE)) {
|
|
return NULL;
|
|
}
|
|
|
|
PathAddBackslash(sz);
|
|
StrCatBuff(sz, lpszGroup, ARRAYSIZE(sz));
|
|
|
|
//
|
|
// Test if the group exists.
|
|
//
|
|
|
|
hFile = FindFirstFile (sz, &fd);
|
|
|
|
if (hFile == INVALID_HANDLE_VALUE) {
|
|
|
|
if (SHRestricted(REST_NOCOMMONGROUPS)) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Personal group doesn't exist. Try a common group.
|
|
//
|
|
|
|
if (!SHGetSpecialFolderPath(NULL, sz, CSIDL_COMMON_PROGRAMS, FALSE)) {
|
|
return NULL;
|
|
}
|
|
|
|
PathAddBackslash(sz);
|
|
StrCatBuff(sz, lpszGroup, ARRAYSIZE(sz));
|
|
bCommon = TRUE;
|
|
|
|
} else {
|
|
FindClose (hFile);
|
|
}
|
|
|
|
hdsaGroup = DSA_Create(sizeof(GROUPITEM), 0);
|
|
if (hdsaGroup)
|
|
{
|
|
// Get the group info.
|
|
pidlGroup = ILCreateFromPath(sz);
|
|
if (pidlGroup)
|
|
{
|
|
IShellFolder* psfDesktop;
|
|
|
|
hres = SHGetDesktopFolder(&psfDesktop);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
hres = psfDesktop->BindToObject(pidlGroup, NULL, IID_IShellFolder, (LPVOID*)&psf);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
LPENUMIDLIST penum;
|
|
hres = psf->EnumObjects(NULL, SHCONTF_NONFOLDERS, &penum);
|
|
if (S_OK == hres)
|
|
{
|
|
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
|
|
ASSERT(ppf); // nobody checks it below
|
|
while ((penum->Next(1, &pidl, &celt) == NOERROR) && (celt == 1))
|
|
{
|
|
if (GroupItem_GetLinkInfo(sz, &gi, pidl, psf, psl, ppf))
|
|
{
|
|
// Add it to the list
|
|
DSA_InsertItem(hdsaGroup, cItems, &gi);
|
|
cItems++;
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
fOK = TRUE;
|
|
ppf->Release();
|
|
psl->Release();
|
|
}
|
|
penum->Release();
|
|
}
|
|
psf->Release();
|
|
}
|
|
psfDesktop->Release();
|
|
}
|
|
ILFree(pidlGroup);
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(DM_ERROR, "c.eiig: Can't create IDList for path..");
|
|
}
|
|
|
|
if (fOK)
|
|
{
|
|
// Create dde data.
|
|
TraceMsg(TF_DDE, "c.eiig: %d links", cItems);
|
|
|
|
// "Group Name",path,#items,showcmd
|
|
PathGetShortPath(sz);
|
|
wsprintf(szLine, TEXT("\"%s\",%s,%d,%d,%d\r\n"), lpszGroup, sz, cItems, SW_SHOWNORMAL, bCommon);
|
|
cchDDE = lstrlen(szLine)+1;
|
|
cbDDE = cchDDE * sizeof(TCHAR);
|
|
pszDDE = (LPTSTR)LocalAlloc(LPTR, cbDDE);
|
|
if (pszDDE)
|
|
{
|
|
lstrcpy(pszDDE, szLine);
|
|
cItems--;
|
|
while (cItems >= 0)
|
|
{
|
|
LPTSTR pszRealloc;
|
|
pgi = (GROUPITEM*)DSA_GetItemPtr(hdsaGroup, cItems);
|
|
ASSERT(pgi);
|
|
// Fake up reasonable coords.
|
|
x = ((cItems%ITEMSPERROW)*64)+32;
|
|
y = ((cItems/ITEMSPERROW)*64)+32;
|
|
// "name","CL",def dir,icon path,x,y,icon index,hotkey,minflag.
|
|
wsprintf(szLine, TEXT("\"%s\",\"%s\",%s,%s,%d,%d,%d,%d,%d\r\n"), pgi->pszDesc, pgi->pszCL,
|
|
pgi->pszWD, pgi->pszIconPath, x, y, pgi->iIcon, pgi->wHotkey, pgi->fMin);
|
|
cchDDE += lstrlen(szLine);
|
|
cbDDE = cchDDE * sizeof(TCHAR);
|
|
pszRealloc = (LPTSTR)_LocalReAlloc((HLOCAL)pszDDE, cbDDE + sizeof(TCHAR), LMEM_MOVEABLE|LMEM_ZEROINIT);
|
|
if (pszRealloc)
|
|
{
|
|
pszDDE = pszRealloc;
|
|
lstrcat(pszDDE, szLine);
|
|
cItems--;
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(DM_ERROR, "c.eiig: Unable to realocate DDE line.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
// Multiply by two, for worst case, where every char was a multibyte char
|
|
int cbADDE = lstrlen(pszDDE) * 2; // Trying to make an ANSI string!!!
|
|
LPSTR pszADDE = (LPSTR)LocalAlloc(LPTR, cbADDE + 2);
|
|
if (pszADDE)
|
|
{
|
|
WideCharToMultiByte(CP_ACP, 0, pszDDE, -1, pszADDE, cbADDE, NULL, NULL);
|
|
|
|
hddedata = DdeCreateDataHandle(g_dwDDEInst, (LPBYTE)pszADDE, cbADDE, 0, hszItem, CF_TEXT, 0);
|
|
LocalFree(pszADDE);
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(DM_ERROR, "c.eiig: Can't allocate ANSI buffer.");
|
|
}
|
|
#else
|
|
hddedata = DdeCreateDataHandle(g_dwDDEInst, (LPBYTE)pszDDE, cbDDE+1, 0, hszItem, CF_TEXT, 0);
|
|
#endif
|
|
LocalFree(pszDDE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(DM_ERROR, "c.eiig: Can't create group list.");
|
|
}
|
|
|
|
DSA_DestroyCallback(hdsaGroup, DSA_DestroyGroupCallback, 0);
|
|
}
|
|
|
|
return hddedata;
|
|
}
|
|
|
|
|
|
HDDEDATA DDE_HandleRequest(HSZ hszItem, HCONV hconv)
|
|
{
|
|
TCHAR szGroup[MAX_PATH];
|
|
PDDECONV pddec;
|
|
|
|
TraceMsg(TF_DDE, "DDEML Request(" SPRINTF_PTR ") - OK.", (DWORD_PTR)hconv);
|
|
|
|
pddec = DDE_MapHConv(hconv);
|
|
if (pddec == NULL)
|
|
return HDDENULL;
|
|
|
|
DDEConv_Release(pddec);
|
|
|
|
DdeQueryString(g_dwDDEInst, hszItem, szGroup, ARRAYSIZE(szGroup), CP_WINNATURAL);
|
|
|
|
TraceMsg(TF_DDE, "Request for item %s.", (LPTSTR) szGroup);
|
|
// There's a bug in Progman where null data returns the list of groups.
|
|
// Logitech relies on this behaviour.
|
|
if (szGroup[0] == TEXT('\0'))
|
|
{
|
|
return EnumGroups(hszItem);
|
|
}
|
|
// Special case group names of "Groups" or "Progman" and return the list
|
|
// of groups instead.
|
|
else if (lstrcmpi(szGroup, c_szGroupGroup) == 0 || lstrcmpi(szGroup, c_szTopic) == 0)
|
|
{
|
|
return EnumGroups(hszItem);
|
|
}
|
|
// Special case winoldapp properties.
|
|
else if (lstrcmpi(szGroup, c_szGetIcon) == 0 ||
|
|
lstrcmpi(szGroup, c_szGetDescription) == 0 ||
|
|
lstrcmpi(szGroup, c_szGetWorkingDir) == 0)
|
|
{
|
|
return HDDENULL;
|
|
}
|
|
// Assume it's a group name.
|
|
else
|
|
{
|
|
return EnumItemsInGroup(hszItem, szGroup);
|
|
}
|
|
}
|
|
|
|
|
|
// Support Disconnect
|
|
void DDE_HandleDisconnect(HCONV hconv)
|
|
{
|
|
PDDECONV pddecPrev = NULL;
|
|
PDDECONV pddec;
|
|
|
|
TraceMsg(TF_DDE, "DDEML Disconnect(" SPRINTF_PTR ") - OK.", (DWORD_PTR)hconv);
|
|
|
|
// Find the conversation in the list of them and free it.
|
|
ENTERCRITICAL;
|
|
for (pddec = g_pddecHead; pddec != NULL; pddec = pddec->pddecNext)
|
|
{
|
|
if (pddec->hconv == hconv)
|
|
{
|
|
// Found it, so first unlink it
|
|
// pass the next reference back up the chain.
|
|
if (pddecPrev == NULL)
|
|
g_pddecHead = pddec->pddecNext;
|
|
else
|
|
pddecPrev->pddecNext = pddec->pddecNext;
|
|
|
|
pddec->pddecNext = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
pddecPrev = pddec;
|
|
}
|
|
LEAVECRITICAL;
|
|
|
|
// Now Free it outside of critical section
|
|
if (pddec)
|
|
DDEConv_Release(pddec);
|
|
|
|
g_hwndDde = NULL;
|
|
}
|
|
|
|
|
|
// Support wildcard topics.
|
|
HDDEDATA DDE_HandleWildConnects(void)
|
|
{
|
|
HSZPAIR hszpair[4];
|
|
|
|
TraceMsg(TF_DDE, "DDEML wild connect.");
|
|
|
|
hszpair[0].hszSvc = g_hszService;
|
|
hszpair[0].hszTopic = g_hszTopic;
|
|
hszpair[1].hszSvc = g_hszShell;
|
|
hszpair[1].hszTopic = g_hszAppProps;
|
|
hszpair[2].hszSvc = g_hszFolders;
|
|
hszpair[2].hszTopic = g_hszAppProps;
|
|
hszpair[3].hszSvc = HSZNULL;
|
|
hszpair[3].hszTopic = HSZNULL;
|
|
|
|
return DdeCreateDataHandle(g_dwDDEInst, (LPBYTE)&hszpair, sizeof(hszpair), 0, HSZNULL, CF_TEXT, 0);
|
|
}
|
|
|
|
|
|
// App hack flags for DDE.
|
|
// REVIEW UNDONE - Read these from the registry so we can app hack on the fly.
|
|
|
|
// Bodyworks.
|
|
// Uses PostMessage(-1,...) to talk to the shell and DDEML
|
|
// can't handle that level of abuse. By having DDEML ignore the command
|
|
// it'll get forwarded through to the desktop which can handle it. Sigh.
|
|
|
|
// CCMail.
|
|
// Can't handle being installed via a UNC but unlike most app that have
|
|
// problems with UNC's they appear to set up fine - you'll just have
|
|
// lots of problems trying to run the app. We handle this by faking
|
|
// up a drive connection for them. We don't want to do this generally
|
|
// since the user could easily run out of drive letters.
|
|
|
|
// Discis. [There are dozens of Discis apps that use the same setup.]
|
|
// Can't handle getting activate messages out of order with DDE (which
|
|
// happens easily now). They end up spinning in a loop looking for an
|
|
// ACK they've already got. We hack around this by detecting them being
|
|
// hung and post them another ack. We keep doing that until they wake
|
|
// up and start talking to us again.
|
|
|
|
// Media Recorder.
|
|
// Their app wants to be single instance so at init they search for
|
|
// windows with the TITLE (!!!) of "MediaRecorder". If you launch
|
|
// them from their own folder (which has the title "MediaRecorder" then
|
|
// they refuse to run. We fix this by mapping their group name at
|
|
// setup time.
|
|
|
|
// Trio DataFax.
|
|
// This app wants to add something to the startup group but doesn't
|
|
// know what it's called so it tries to load the Startup string out
|
|
// of Progman. If Progman isn't running they try to create a group
|
|
// with a NULL title. We detect this case and map them to the new
|
|
// startup group name.
|
|
|
|
// TalkToPlus.
|
|
// They try to make a link to Terminal.exe and abort their setup
|
|
// if the AddItem fails. We fix this my forcing the AddItem to
|
|
// return success.
|
|
|
|
// Winfax Pro 4.0.
|
|
// They use the shell= line in win.ini for the service/topic so
|
|
// they end up talking to the shell using Explorer/Explorer!
|
|
// They also talk to the LAST responder to the init broadcast
|
|
// instead of the first AND they use SendMsg/Free instead of waiting for
|
|
// Acks. We fix this by allowing their service/topic to work, and have
|
|
// the desktop copy the data before sending it through to DDEML.
|
|
// REVIEW We key off the fact that their dde window is a dialog with no
|
|
// title - seems a bit broad to me.
|
|
|
|
// The Journeyman Project.
|
|
// This app causes damage to space-time. We fix it by generating a
|
|
// small HS-field around their installer.
|
|
|
|
// CA apps in general.
|
|
// Don't bother sending DDE_INIT's before sending the execute commands.
|
|
// We fix it by doing the init on the fly if needed.
|
|
|
|
// Faxserve.
|
|
// Broadcasts their EXEC commands. Their class name is "install" which
|
|
// is a bit too generic for my liking but since we handle this problem
|
|
// by forcing everything to go through the desktop it's not very risky.
|
|
|
|
struct {
|
|
LPCTSTR pszClass;
|
|
LPCTSTR pszTitle;
|
|
DWORD id;
|
|
} const c_DDEApps[] = {
|
|
c_szMrPostman, NULL, DDECONV_NO_INIT,
|
|
c_szBodyWorks, NULL, DDECONV_FAIL_CONNECTS,
|
|
c_szCCMail, NULL, DDECONV_NO_UNC,
|
|
c_szDiscis, NULL, DDECONV_REPEAT_ACKS,
|
|
c_szMediaRecorder, NULL, DDECONV_MAP_MEDIA_RECORDER,
|
|
c_szTrioDataFax, NULL, DDECONV_NULL_FOR_STARTUP,
|
|
c_szTalkToPlus, NULL, DDECONV_ALLOW_INVALID_CL,
|
|
c_szDialog, c_szMakePMG, DDECONV_REPEAT_ACKS,
|
|
c_szDialog, c_szNULL, DDECONV_EXPLORER_SERVICE_AND_TOPIC|DDECONV_USING_SENDMSG,
|
|
c_szJourneyMan, NULL, DDECONV_EXPLORER_SERVICE_AND_TOPIC,
|
|
c_szCADDE, NULL, DDECONV_NO_INIT,
|
|
c_szFaxServe, NULL, DDECONV_FAIL_CONNECTS
|
|
};
|
|
|
|
|
|
DWORD GetDDEAppFlagsFromWindow(HWND hwnd)
|
|
{
|
|
if (hwnd && !Window_IsLFNAware(hwnd))
|
|
{
|
|
TCHAR szClass[MAX_PATH];
|
|
|
|
GetClassName(hwnd, szClass, ARRAYSIZE(szClass));
|
|
for (int i = 0; i < ARRAYSIZE(c_DDEApps); i++)
|
|
{
|
|
// NB Keep this case sensative to narrow the scope a bit.
|
|
if (lstrcmp(szClass, c_DDEApps[i].pszClass) == 0)
|
|
{
|
|
// Do we care about the title?
|
|
if (c_DDEApps[i].pszTitle)
|
|
{
|
|
TCHAR szTitle[MAX_PATH];
|
|
|
|
GetWindowText(hwnd, szTitle, ARRAYSIZE(szTitle));
|
|
if (lstrcmp(szTitle, c_DDEApps[i].pszTitle) == 0)
|
|
{
|
|
TraceMsg(TF_DDE, "App flags 0x%x for %s %s.", c_DDEApps[i].id, c_DDEApps[i].pszClass, c_DDEApps[i].pszTitle);
|
|
return c_DDEApps[i].id;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Nope.
|
|
TraceMsg(TF_DDE, "App flags 0x%x for %s.", c_DDEApps[i].id, c_DDEApps[i].pszClass);
|
|
return c_DDEApps[i].id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return DDECONV_NONE;
|
|
}
|
|
|
|
|
|
DWORD GetDDEAppFlags(HCONV hconv)
|
|
{
|
|
return GetDDEAppFlagsFromWindow(_GetDDEPartnerWindow(hconv));
|
|
}
|
|
|
|
|
|
HDDEDATA DDE_HandleConnect(HSZ hsz1, HSZ hsz2)
|
|
{
|
|
if ((hsz1 == g_hszTopic && hsz2 == g_hszService) ||
|
|
(hsz1 == g_hszAppProps && hsz2 == g_hszShell) ||
|
|
(hsz1 == g_hszAppProps && hsz2 == g_hszFolders))
|
|
{
|
|
TraceMsg(TF_DDE, "DDEML Connect.");
|
|
return (HDDEDATA)DDE_FACK;
|
|
}
|
|
else
|
|
{
|
|
// Unknown topic/service.
|
|
TraceMsg(TF_DDE, "DDEML Connect - unknown service/topic.");
|
|
return (HDDEDATA)NULL;
|
|
}
|
|
}
|
|
|
|
|
|
// Returns TRUE if the drive where the Programs folder is supports LFNs.
|
|
BOOL _SupportLFNGroups(void)
|
|
{
|
|
TCHAR szPrograms[MAX_PATH];
|
|
DWORD dwMaxCompLen = 0;
|
|
|
|
SHGetSpecialFolderPath(NULL, szPrograms, CSIDL_PROGRAMS, TRUE);
|
|
return IsLFNDrive(szPrograms);
|
|
}
|
|
|
|
|
|
// REVIEW HACK - Don't just caste, call GetConvInfo() to get this. We can't
|
|
// do this as yet because of a bug in the thunk layer.
|
|
#define _GetDDEWindow(hconv) ((HWND)hconv)
|
|
|
|
|
|
HDDEDATA DDE_HandleConnectConfirm(HCONV hconv)
|
|
{
|
|
DWORD dwAppFlags = GetDDEAppFlags(hconv);
|
|
PDDECONV pddec;
|
|
|
|
if (dwAppFlags & DDECONV_FAIL_CONNECTS)
|
|
{
|
|
DdeDisconnect(hconv);
|
|
return FALSE;
|
|
}
|
|
|
|
pddec = DDEConv_Create();
|
|
if (pddec)
|
|
{
|
|
if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&pddec->psl)))
|
|
{
|
|
pddec->hconv = hconv;
|
|
// pddec->szGroup[0] = '\0'; // implicit
|
|
// pddec->fDirty = FALSE; // implicit
|
|
// protect access to global list
|
|
ENTERCRITICAL;
|
|
pddec->pddecNext = g_pddecHead;
|
|
g_pddecHead = pddec;
|
|
LEAVECRITICAL;
|
|
|
|
TraceMsg(TF_DDE, "DDEML Connect_CONFIRM(" SPRINTF_PTR ") - OK.", (DWORD_PTR)hconv);
|
|
|
|
// Do we support LFN groups?
|
|
g_LFNGroups = _SupportLFNGroups();
|
|
// Tell the desktops DDE code we're handling things from here.
|
|
g_hwndDde = _GetDDEWindow(hconv);
|
|
// No conversation yet (wild connect?) - signal it with a hwnd -1.
|
|
if (!g_hwndDde)
|
|
g_hwndDde = (HWND)-1;
|
|
// Keep track of the app hacks.
|
|
pddec->dwFlags = dwAppFlags;
|
|
|
|
// Success.
|
|
return (HDDEDATA)DDE_FACK;
|
|
}
|
|
TraceMsg(TF_DDE, "Unable to create IShellLink interface.");
|
|
|
|
DDEConv_Release(pddec);
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_ERROR, "Unable to allocate memory for tracking dde conversations.");
|
|
}
|
|
return (HDDEDATA)NULL;
|
|
}
|
|
|
|
|
|
HDDEDATA CALLBACK DDECallback(UINT type, UINT fmt, HCONV hconv,
|
|
HSZ hsz1, HSZ hsz2,HDDEDATA hData, ULONG_PTR dwData1, ULONG_PTR dwData2)
|
|
{
|
|
switch (type)
|
|
{
|
|
case XTYP_CONNECT:
|
|
return DDE_HandleConnect(hsz1, hsz2);
|
|
|
|
case XTYP_WILDCONNECT:
|
|
return DDE_HandleWildConnects();
|
|
|
|
case XTYP_CONNECT_CONFIRM:
|
|
return DDE_HandleConnectConfirm(hconv);
|
|
|
|
case XTYP_REGISTER:
|
|
case XTYP_UNREGISTER:
|
|
return (HDDEDATA) NULL;
|
|
|
|
case XTYP_ADVDATA:
|
|
return (HDDEDATA) DDE_FACK;
|
|
|
|
case XTYP_XACT_COMPLETE:
|
|
return (HDDEDATA) NULL;
|
|
|
|
case XTYP_DISCONNECT:
|
|
DDE_HandleDisconnect(hconv);
|
|
return (HDDEDATA) NULL;
|
|
|
|
case XTYP_EXECUTE:
|
|
return HandleDDEExecute(hData, hconv);
|
|
|
|
case XTYP_REQUEST:
|
|
if (hsz1 == g_hszTopic || hsz1 == g_hszAppProps)
|
|
{
|
|
return DDE_HandleRequest(hsz2, hconv);
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_DDE, "DDEML Request - Invalid Topic.");
|
|
return (HDDEDATA) NULL;
|
|
}
|
|
|
|
default:
|
|
return (HDDEDATA) NULL;
|
|
|
|
}
|
|
}
|
|
|
|
static BOOL s_bDDEInited = FALSE;
|
|
ATOM g_aProgman = 0;
|
|
|
|
void UnInitialiseDDE(void)
|
|
{
|
|
if (g_dwDDEInst)
|
|
{
|
|
DDE_RemoveShellServices();
|
|
|
|
DdeNameService(g_dwDDEInst, g_hszFolders, HSZNULL, DNS_UNREGISTER);
|
|
|
|
_DdeFreeStringHandle(g_dwDDEInst, g_hszTopic);
|
|
_DdeFreeStringHandle(g_dwDDEInst, g_hszService);
|
|
_DdeFreeStringHandle(g_dwDDEInst, g_hszStar);
|
|
_DdeFreeStringHandle(g_dwDDEInst, g_hszShell);
|
|
_DdeFreeStringHandle(g_dwDDEInst, g_hszAppProps);
|
|
_DdeFreeStringHandle(g_dwDDEInst, g_hszFolders);
|
|
|
|
if (!DdeUninitialize(g_dwDDEInst))
|
|
{
|
|
TraceMsg(TF_DDE, "DDE Un-Initialization failure.");
|
|
}
|
|
|
|
g_dwDDEInst = 0;
|
|
}
|
|
|
|
if (g_aProgman)
|
|
{
|
|
g_aProgman = GlobalDeleteAtom(g_aProgman);
|
|
}
|
|
|
|
s_bDDEInited = FALSE;
|
|
}
|
|
|
|
|
|
void InitialiseDDE(void)
|
|
{
|
|
DBG_ENTER(FTF_DDE, InitialiseDDE);
|
|
|
|
if (s_bDDEInited)
|
|
{
|
|
// No need to do this twice
|
|
return;
|
|
}
|
|
|
|
// Hack for Alone In the Dark 2.
|
|
// They do a case sensative comparison of the progman atom and they
|
|
// need it to be uppercase.
|
|
g_aProgman = GlobalAddAtom(TEXT("PROGMAN"));
|
|
|
|
if (DdeInitialize(&g_dwDDEInst, DDECallback, CBF_FAIL_POKES | CBF_FAIL_ADVISES, 0L) == DMLERR_NO_ERROR)
|
|
{
|
|
g_hszTopic = _DdeCreateStringHandle(g_dwDDEInst, c_szTopic, CP_WINNATURAL);
|
|
g_hszService = _DdeCreateStringHandle(g_dwDDEInst, c_szService, CP_WINNATURAL);
|
|
g_hszStar = _DdeCreateStringHandle(g_dwDDEInst, c_szStar, CP_WINNATURAL);
|
|
g_hszShell = _DdeCreateStringHandle(g_dwDDEInst, c_szShell, CP_WINNATURAL);
|
|
g_hszAppProps = _DdeCreateStringHandle(g_dwDDEInst, c_szAppProps, CP_WINNATURAL);
|
|
g_hszFolders = _DdeCreateStringHandle(g_dwDDEInst, c_szFolders, CP_WINNATURAL);
|
|
|
|
if (g_hszTopic && g_hszService && g_hszStar && g_hszShell && g_hszAppProps && g_hszFolders)
|
|
{
|
|
if (DdeNameService(g_dwDDEInst, g_hszFolders, HSZNULL, DNS_REGISTER) &&
|
|
DDE_AddShellServices())
|
|
{
|
|
s_bDDEInited = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!s_bDDEInited)
|
|
{
|
|
UnInitialiseDDE();
|
|
}
|
|
|
|
DBG_EXIT(FTF_DDE, InitialiseDDE);
|
|
}
|
|
|
|
|
|
BOOL DDE_AddShellServices(void)
|
|
{
|
|
// Only register these if we are the shell...
|
|
if (DdeNameService(g_dwDDEInst, g_hszService, HSZNULL, DNS_REGISTER) &&
|
|
DdeNameService(g_dwDDEInst, g_hszShell, HSZNULL, DNS_REGISTER))
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
void DDE_RemoveShellServices(void)
|
|
{
|
|
ASSERT(g_dwDDEInst);
|
|
|
|
DdeNameService(g_dwDDEInst, g_hszService, HSZNULL, DNS_UNREGISTER);
|
|
DdeNameService(g_dwDDEInst, g_hszShell, HSZNULL, DNS_UNREGISTER);
|
|
}
|
|
|
|
|
|
|
|
BOOL GetGroupName(LPCTSTR lpszOld, LPTSTR lpszNew, ULONG cchNew)
|
|
{
|
|
DWORD dwType;
|
|
DWORD cbNew = cchNew * sizeof(TCHAR);
|
|
|
|
return ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER, c_szMapGroups, lpszOld, &dwType, (LPVOID)lpszNew, &cbNew);
|
|
}
|
|
|
|
void MapGroupName(LPCTSTR lpszOld, LPTSTR lpszNew, ULONG cchNew)
|
|
{
|
|
if (!GetGroupName(lpszOld, lpszNew, cchNew))
|
|
{
|
|
lstrcpyn(lpszNew, lpszOld, cchNew);
|
|
}
|
|
}
|
|
|
|
STDAPI_(BOOL) DDEHandleViewFolderNotify(IShellBrowser* psb, HWND hwnd, LPNMVIEWFOLDER pnm)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
UINT *pwCmd = GetDDECommands(pnm->szCmd, c_sDDECommands, FALSE);
|
|
|
|
// -1 means there aren't any commands we understand. Oh, well
|
|
if (pwCmd && (-1 != *pwCmd))
|
|
{
|
|
UINT *pwCmdSave = pwCmd;
|
|
UINT c = *pwCmd++;
|
|
|
|
LPCTSTR pszCommand = c_sDDECommands[c].pszCommand;
|
|
|
|
ASSERT(c < ARRAYSIZE(c_sDDECommands));
|
|
|
|
if (pszCommand == c_szViewFolder ||
|
|
pszCommand == c_szExploreFolder)
|
|
{
|
|
fRet = DoDDE_ViewFolder(psb, hwnd, pnm->szCmd, pwCmd,
|
|
pszCommand == c_szExploreFolder, pnm->dwHotKey, pnm->hMonitor);
|
|
}
|
|
else if (pszCommand == c_szShellFile)
|
|
{
|
|
fRet = DDE_ShellFile(pnm->szCmd, pwCmd, 0);
|
|
}
|
|
|
|
GlobalFree(pwCmdSave);
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
STDAPI_(LPNMVIEWFOLDER) DDECreatePostNotify(LPNMVIEWFOLDER pnm)
|
|
{
|
|
LPNMVIEWFOLDER pnmPost = NULL;
|
|
TCHAR szCmd[MAX_PATH * 2];
|
|
StrCpyN(szCmd, pnm->szCmd, SIZECHARS(szCmd));
|
|
UINT *pwCmd = GetDDECommands(szCmd, c_sDDECommands, FALSE);
|
|
|
|
// -1 means there aren't any commands we understand. Oh, well
|
|
if (pwCmd && (-1 != *pwCmd))
|
|
{
|
|
LPCTSTR pszCommand = c_sDDECommands[*pwCmd].pszCommand;
|
|
|
|
ASSERT(*pwCmd < ARRAYSIZE(c_sDDECommands));
|
|
|
|
//
|
|
// these are the only commands handled by a PostNotify
|
|
if (pszCommand == c_szViewFolder
|
|
|| pszCommand == c_szExploreFolder
|
|
|| pszCommand == c_szShellFile)
|
|
{
|
|
pnmPost = (LPNMVIEWFOLDER)LocalAlloc(LPTR, sizeof(NMVIEWFOLDER));
|
|
|
|
if (pnmPost)
|
|
memcpy(pnmPost, pnm, sizeof(NMVIEWFOLDER));
|
|
}
|
|
|
|
GlobalFree(pwCmd);
|
|
}
|
|
|
|
return pnmPost;
|
|
}
|
|
|
|
|
|
|
|
LRESULT _ForwardDDEMsgs(HWND hwnd, HWND hwndForward, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL fSend)
|
|
{
|
|
TraceMsg(TF_DDE, "c.fdm: Forwarding DDE to %x", hwndForward);
|
|
|
|
if (hwndForward && IsWindow(hwndForward))
|
|
{
|
|
TraceMsg(TF_DDE, "c.fdm: %lx %lx %lx", uMsg, (WPARAM)hwnd, lParam);
|
|
if (fSend)
|
|
return SendMessage(hwndForward, uMsg, (WPARAM)hwnd, lParam);
|
|
else
|
|
return PostMessage(hwndForward, uMsg, (WPARAM)hwnd, lParam);
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_DDE, "c.fdm: Invalid DDEML window, Can't forward DDE messages.");
|
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
|
|
// Set/cleared by dde connect/disconnect.
|
|
const TCHAR c_szExplorerTopic[] = TEXT("Explorer");
|
|
const TCHAR c_szDMGFrame[] = TEXT("DMGFrame"); // This is the 16-bit/Win95 window class name
|
|
|
|
|
|
// Broadcast to all ddeml server windows.
|
|
|
|
void DDEML_Broadcast(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
HWND hwnd = GetWindow(GetDesktopWindow(), GW_CHILD);
|
|
while (hwnd)
|
|
{
|
|
TCHAR szClass[32];
|
|
if (GetClassName(hwnd, szClass, ARRAYSIZE(szClass)))
|
|
{
|
|
if ((lstrcmp(szClass, c_szDMGFrame) == 0) ||
|
|
(lstrcmp(szClass, TEXT("DDEMLMom")) == 0)) // this is the 32-bit NT window class name
|
|
SendMessage(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
hwnd = GetWindow(hwnd, GW_HWNDNEXT);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
LRESULT _HandleDDEInitiateAndAck(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
static BOOL g_fInInit = FALSE;
|
|
ATOM aProgman;
|
|
TCHAR szService[32];
|
|
TCHAR szTopic[32];
|
|
TCHAR szClass[32];
|
|
WPARAM uHigh, uLow;
|
|
BOOL fForceAccept = FALSE;
|
|
|
|
if (uMsg == WM_DDE_INITIATE)
|
|
{
|
|
TraceMsg(TF_DDE, "c.hdi: Init.");
|
|
|
|
// Don't handle DDE messages if we're already using DDEML. This happens when apps
|
|
// broadcast DDE_INIT and don't stop on the first reply. Both our DDEML window and
|
|
// the desktop end up replying. Most apps don't care and just talk to the first or
|
|
// the last one but Ventura gets confused and thinks it's finished doing DDE when it
|
|
// gets the second ACK and destroys it's internal DDE window.
|
|
if (g_hwndDde)
|
|
{
|
|
TraceMsg(TF_DDE, "c.fpwp: Not forwarding DDE, DDEML is handing it.");
|
|
KillTimer(hwnd, IDT_DDETIMEOUT);
|
|
}
|
|
// Are we re-cursing?
|
|
else if (!g_fInInit)
|
|
{
|
|
// Nope, Is this for Progman, Progman or Shell, AppProperties?
|
|
if (lParam)
|
|
{
|
|
GlobalGetAtomName(LOWORD(lParam), szService, ARRAYSIZE(szService));
|
|
GlobalGetAtomName(HIWORD(lParam), szTopic, ARRAYSIZE(szTopic));
|
|
}
|
|
else
|
|
{
|
|
// Progman allowed a null Service & a null Topic to imply Progman, Progman.
|
|
szService[0] = TEXT('\0');
|
|
szTopic[0] = TEXT('\0');
|
|
fForceAccept = TRUE;
|
|
}
|
|
|
|
// Keep track of hacks, we reset this on the disconnect.
|
|
g_dwAppFlags = GetDDEAppFlagsFromWindow((HWND)wParam);
|
|
|
|
// Hacks for WinFax and Journeyman Project.
|
|
if ((g_dwAppFlags & DDECONV_EXPLORER_SERVICE_AND_TOPIC)
|
|
&& (lstrcmpi(szTopic, c_szExplorerTopic) == 0)
|
|
&& (lstrcmpi(szService, c_szExplorerTopic) == 0))
|
|
{
|
|
fForceAccept = TRUE;
|
|
}
|
|
|
|
if (((lstrcmpi(szTopic, c_szTopic) == 0) && (lstrcmpi(szService, c_szService) == 0)) ||
|
|
fForceAccept)
|
|
{
|
|
TraceMsg(TF_DDE, "c.hdi: Init on [Progman,Progman] - needs forwarding.");
|
|
// Nope go find it.
|
|
// NB This will cause an echo on every DDE_INIT for Progman, Progman after booting.
|
|
// It shouldn't be a problem :-)
|
|
// Keep track of who to send Acks back to.
|
|
g_hwndClient = (HWND)wParam;
|
|
// Now find the real shell.
|
|
aProgman = GlobalAddAtom(c_szService);
|
|
TraceMsg(TF_DDE, "c.d_hdm: Finding shell dde handler...");
|
|
g_fInInit = TRUE;
|
|
// SendMessage(HWND_BROADCAST, WM_DDE_INITIATE, (WPARAM)hwnd, MAKELPARAM(aProgman, aProgman));
|
|
DDEML_Broadcast(WM_DDE_INITIATE, (WPARAM)hwnd, MAKELPARAM(aProgman, aProgman));
|
|
g_fInInit = FALSE;
|
|
TraceMsg(TF_DDE, "c.d_hdm: ...Done");
|
|
GlobalDeleteAtom(aProgman);
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_DDE, "c.hdi: Init on something other than [Progman,Progman] - Ignoring");
|
|
KillTimer(hwnd, IDT_DDETIMEOUT);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_DDE, "c.hdi: Recursing - Init ignored.");
|
|
}
|
|
return 0;
|
|
}
|
|
else if (uMsg == WM_DDE_ACK)
|
|
{
|
|
TraceMsg(TF_DDE, "c.hdi: Ack.");
|
|
// Is this in response to the DDE_Init above?
|
|
if (g_fInInit)
|
|
{
|
|
// Yep, keep track of who we're talking too.
|
|
GetClassName((HWND)wParam, szClass, ARRAYSIZE(szClass));
|
|
TraceMsg(TF_DDE, "c.d_hdm: Init-Ack from %x (%s).", wParam, szClass);
|
|
g_hwndDDEML = (HWND)wParam;
|
|
// The forward it back (send it, don't post it - Breaks Prodogy).
|
|
return _ForwardDDEMsgs(hwnd, g_hwndClient, uMsg, (WPARAM)hwnd, lParam, TRUE);
|
|
}
|
|
else
|
|
{
|
|
// Nope, just forward it back.
|
|
|
|
// Hack for WinFaxPro.
|
|
if (g_dwAppFlags & DDECONV_USING_SENDMSG)
|
|
{
|
|
// We copied the data before sending it on so we can free it here.
|
|
// WinFax ignores the reply so don't bother sending it.
|
|
UnpackDDElParam(uMsg, lParam, &uLow, &uHigh);
|
|
if (uHigh)
|
|
GlobalFree((HGLOBAL)uHigh);
|
|
return 0;
|
|
}
|
|
|
|
return _ForwardDDEMsgs(hwnd, g_hwndClient, uMsg, (WPARAM)hwnd, lParam, FALSE);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
LRESULT _HandleDDEForwardBiDi(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if ((HWND)wParam == g_hwndDDEML)
|
|
return _ForwardDDEMsgs(hwnd, g_hwndClient, uMsg, wParam, lParam, FALSE);
|
|
else if ((HWND)wParam == g_hwndClient)
|
|
return _ForwardDDEMsgs(hwnd, g_hwndDDEML, uMsg, wParam, lParam, FALSE);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
LRESULT _HandleDDETerminate(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
HWND hwndClient;
|
|
|
|
TraceMsg(DM_TRACE, "c.hddet: Terminate.");
|
|
|
|
if ((HWND)wParam == g_hwndDDEML)
|
|
{
|
|
// This should be the last message (a terminate from ddeml to the client).
|
|
// Cleanup now.
|
|
KillTimer(hwnd, IDT_DDETIMEOUT);
|
|
TraceMsg(DM_TRACE, "c.hddet: Cleanup.");
|
|
hwndClient = g_hwndClient;
|
|
g_hwndClient = NULL;
|
|
g_hwndDDEML = NULL;
|
|
g_dwAppFlags = DDECONV_NONE;
|
|
return _ForwardDDEMsgs(hwnd, hwndClient, uMsg, wParam, lParam, FALSE);
|
|
}
|
|
else if ((HWND)wParam == g_hwndClient)
|
|
{
|
|
return _ForwardDDEMsgs(hwnd, g_hwndDDEML, uMsg, wParam, lParam, FALSE);
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
LRESULT _HandleDDEExecute(HWND hwnd, HWND hwndForward, UINT uMsg,
|
|
WPARAM wParam, LPARAM lParam, BOOL fSend)
|
|
{
|
|
ATOM aApp, aTopic;
|
|
HANDLE hNew;
|
|
LPTSTR pNew, pOld;
|
|
|
|
// NB WinFaxPro does a Send/Free which avoids Users DDE hack
|
|
// and means they get to delete the data while we're in
|
|
// the middle of using it so we must copy it here. We'll
|
|
// clean it up on the Ack.
|
|
// NB WinFaxPro re-uses the same 16bit selector for all their
|
|
// messages which the thunk layer can't handle it. We need to
|
|
// defect the 32bit side (and free it) so the next time they
|
|
// send the 16bit handle through the thunk layer they get a
|
|
// new 32bit version.
|
|
if (g_dwAppFlags & DDECONV_USING_SENDMSG)
|
|
{
|
|
SIZE_T cb = GlobalSize((HGLOBAL)lParam);
|
|
hNew = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cb);
|
|
if (hNew)
|
|
{
|
|
// Copy the old data.
|
|
pNew = (LPTSTR)GlobalLock(hNew);
|
|
pOld = (LPTSTR)GlobalLock((HGLOBAL)lParam);
|
|
hmemcpy(pNew, pOld, cb);
|
|
GlobalUnlock((HGLOBAL)lParam);
|
|
GlobalUnlock(hNew);
|
|
GlobalFree((HGLOBAL)lParam);
|
|
// Use our copy.
|
|
lParam = (LPARAM)hNew;
|
|
}
|
|
}
|
|
|
|
// NB CA neglect to send a DDE_INIT, they just start
|
|
// throwing DDE_EXEC's at us so we fake up an init
|
|
// from them to DDEML to get things rolling.
|
|
if (!hwndForward)
|
|
{
|
|
if (!(g_dwAppFlags & DDECONV_NO_INIT))
|
|
g_dwAppFlags = GetDDEAppFlagsFromWindow((HWND)wParam);
|
|
|
|
if (g_dwAppFlags & DDECONV_NO_INIT)
|
|
{
|
|
aApp = GlobalAddAtom(c_szService);
|
|
aTopic = GlobalAddAtom(c_szTopic);
|
|
SendMessage(hwnd, WM_DDE_INITIATE, wParam, MAKELPARAM(aApp, aTopic));
|
|
GlobalDeleteAtom(aApp);
|
|
GlobalDeleteAtom(aTopic);
|
|
hwndForward = g_hwndDDEML;
|
|
}
|
|
}
|
|
|
|
return _ForwardDDEMsgs(hwnd, hwndForward, uMsg, wParam, lParam, fSend);
|
|
}
|
|
|
|
|
|
// hacks to get various apps installed (read: ATM). These are the people
|
|
// who do a FindWindow for Progman and then do dde to it directly.
|
|
// These people should not be allowed to write code.
|
|
STDAPI_(LRESULT) DDEHandleMsgs(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
TraceMsg(TF_DDE, "c.fpwp: Forwarding DDE.");
|
|
|
|
SetTimer(hwnd, IDT_DDETIMEOUT, 30*1000, NULL);
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_DDE_INITIATE:
|
|
case WM_DDE_ACK:
|
|
return _HandleDDEInitiateAndAck(hwnd, uMsg, wParam, lParam);
|
|
|
|
case WM_DDE_TERMINATE:
|
|
return _HandleDDETerminate(hwnd, uMsg, wParam, lParam);
|
|
|
|
case WM_DDE_DATA:
|
|
return _HandleDDEForwardBiDi(hwnd, uMsg, wParam, lParam);
|
|
|
|
case WM_DDE_ADVISE:
|
|
case WM_DDE_UNADVISE:
|
|
case WM_DDE_REQUEST:
|
|
case WM_DDE_POKE:
|
|
return _ForwardDDEMsgs(hwnd, g_hwndDDEML, uMsg, wParam, lParam, FALSE);
|
|
|
|
case WM_DDE_EXECUTE:
|
|
return _HandleDDEExecute(hwnd, g_hwndDDEML, uMsg, wParam, lParam, FALSE);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Some installers (Wep2) forget to Terminate a conversation so we timeout
|
|
// after not getting any dde-messages for a while. If we don't, and you run
|
|
// a Wep2 install a second time we think the installer is already talking via
|
|
// ddeml so we don't reply from the desktop. Wep2 then thinks Progman isn't
|
|
// running, does a WinExec of Progman and hangs waiting to talk to it. Progman
|
|
// never replies since it is not the shell. Nasty Nasty Nasty.
|
|
STDAPI_(void) DDEHandleTimeout(HWND hwnd)
|
|
{
|
|
HWND hwndClient, hwndDDEML;
|
|
|
|
TraceMsg(DM_TRACE, "c.hdt: DDE Timeout.");
|
|
|
|
KillTimer(hwnd, IDT_DDETIMEOUT);
|
|
|
|
// Has everything gone away yet?
|
|
if (g_hwndDDEML && g_hwndClient)
|
|
{
|
|
// Nope. Don't want to forward anymore.
|
|
hwndClient = g_hwndClient;
|
|
hwndDDEML = g_hwndDDEML;
|
|
g_hwndClient = NULL;
|
|
g_hwndDDEML = NULL;
|
|
g_dwAppFlags = DDECONV_NONE;
|
|
// Shutdown our ddeml alter-ego.
|
|
// NB If the client window has already gone away (very likely) it's not a
|
|
// problem, ddeml will skip posting the reply but will still do the
|
|
// disconnect callback.
|
|
PostMessage(hwndDDEML, WM_DDE_TERMINATE, (WPARAM)hwnd, 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// INTERNAL EXPORT FUNCTION:
|
|
// This is for explorer to call to initialize and uninitialize SHELL DDE
|
|
// services.
|
|
void WINAPI ShellDDEInit(BOOL fInit)
|
|
{
|
|
if (fInit)
|
|
InitialiseDDE();
|
|
else
|
|
UnInitialiseDDE();
|
|
}
|