windows-nt/Source/XPSP1/NT/shell/shell32/unicpp/dde.cpp

3663 lines
108 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
// 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();
}