|
|
// 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(); }
|