// Handle dde conversations. #include "stdafx.h" #pragma hdrstop #include #include #include // Window_IsLFNAware 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) { LONG cRef = InterlockedIncrement(&pddec->cRef); ASSERT( cRef > 1 ); return cRef; } LONG DDEConv_Release(DDECONV *pddec) { ASSERT( 0 != pddec->cRef ); LONG cRef = InterlockedDecrement(&pddec->cRef); if ( 0 == 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 cRef; } 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 #define ADDITEM_INDEX 2 // DDE_AddItem must have index equal to 2 DDECOMMANDINFO const c_sDDECommands[] = { { c_szCreateGroup , DDE_CreateGroup }, { c_szShowGroup , DDE_ShowGroup }, { c_szAddItem , DDE_AddItem }, // DDE_AddItem must have index equal to 2 { 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. */ #define LIST_INCREMENT 128 UINT* GetDDECommands(LPTSTR lpCmd, const DDECOMMANDINFO * lpsCommands, BOOL fLFN) { UINT cParm, cCmd = 0; LPCTSTR lpCmdStart = lpCmd; BOOL fIncludeQuotes = FALSE; UINT iList = 0; // current index in list UINT cListSize = LIST_INCREMENT; // count of UNIT allocated in list UINT *prguList = (UINT*)GlobalAlloc(GPTR, cListSize * sizeof(UINT)); if (!prguList) 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++; // Need room for both a Command ID and a Count of parameters // Can't be equal to cpWTotal size because we will later terminate the list with a -1 if (iList + 2 >= cListSize) { HGLOBAL hGlobalNew = GlobalReAlloc(prguList, (cListSize + LIST_INCREMENT) * sizeof(UINT), GMEM_MOVEABLE); if (hGlobalNew == NULL) { goto GDEErrExit; } prguList = (UINT*)hGlobalNew; cListSize += LIST_INCREMENT; } /* Get the command name. */ lpCmd = GetCommandName(lpCmd, lpsCommands, &prguList[iList]); if (prguList[iList] == (UINT)-1) goto GDEErrExit; // We need to leave quotes in for the first param of an AddItem. if (fLFN && prguList[iList] == ADDITEM_INDEX) { TraceMsg(TF_DDE, "GetDDECommands: Potential LFN AddItem command..."); fIncludeQuotes = TRUE; } // We added the command index to the list iList++; /* 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; // Do we need more memory for this parameter? +2 to reserve space for the count and this item // Can't be equal to cpWTotal size because we will later terminate the list with a -1 if (iList + cParm + 2 >= cListSize) { HGLOBAL hGlobalNew = GlobalReAlloc(prguList, (cListSize + LIST_INCREMENT) * sizeof(UINT), GMEM_MOVEABLE); if (hGlobalNew == NULL) { goto GDEErrExit; } prguList = (UINT*)hGlobalNew; cListSize += LIST_INCREMENT; } // Only the first param of the AddItem command needs to // handle quotes from LFN guys. if (fIncludeQuotes && (cParm != 0)) fIncludeQuotes = FALSE; // Get the parameter. ++cParm to get beyond the parameter count that will be stored before the parameter offsets in the list if (!(lpCmd = GetOneParameter(lpCmdStart, lpCmd, &prguList[iList] + (++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. */ prguList[iList++] = cParm; iList += 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. */ prguList[iList] = (UINT)-1; return prguList; GDEErrExit: GlobalFree(prguList); 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. // lpszPath must be >= MAX_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) { lstrcpyn(pddec->szGroup, g_pszLastGroupName, ARRAYSIZE(pddec->szGroup)); } 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; // strcpy okay, just allocated 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. // pszPath must be >= MAX_PATH 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) lstrcpyn(szGroup, c_szMediaRecNew, ARRAYSIZE(szGroup)); else lstrcpyn(szGroup, pszName, ARRAYSIZE(szGroup)); } 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)) { lstrcpyn(pddec->szGroup, szGroup, ARRAYSIZE(pddec->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. lstrcpyn(pddec->szGroup, szGroup, ARRAYSIZE(pddec->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, UINT cch) { LPTSTR lpszFilename; lpszFilename = PathFindFileName(lpszPath); lstrcpyn(lpszName, lpszFilename, cch); // 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, UINT cch) { TCHAR szPath[MAX_PATH]; if (StringCchCopy(szPath, ARRAYSIZE(szPath), pszPath)) { PrivatePathStripToRoot(szPath); LPTSTR pszSpec = pszPath + lstrlen(szPath) + 1; TCHAR szSpec[MAX_PATH]; lstrcpyn(szSpec, pszSpec, ARRAYSIZE(szSpec)); pszPath[0] = chDrive; pszPath[1] = TEXT(':'); pszPath[2] = TEXT('\\'); pszPath[3] = 0; StrCatBuff(pszPath, szSpec, cch); } } LPITEMIDLIST Pidl_CreateUsingAppPaths(LPCTSTR pszApp) { TCHAR szSubKey[MAX_PATH]; TraceMsg(TF_DDE, "Trying app paths..."); if (SUCCEEDED(StringCchCopy(szSubKey, ARRAYSIZE(szSubKey), REGSTR_PATH_APPPATHS)) && PathAppend(szSubKey, pszApp)) { TCHAR sz[MAX_PATH]; DWORD cb = sizeof(sz); if (SHRegGetValue(HKEY_LOCAL_MACHINE, szSubKey, NULL, SRRF_RT_REG_SZ, NULL, 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; TCHAR szTmp[MAX_PATH]; int nShowCmd; BOOL fIconPath = FALSE; DBG_ENTER(FTF_DDE, DDE_AddItem); // Make sure group name is setup _CheckForCurrentGroup(pddec); // Only certain param combinations are allowed. UINT 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. // TCHAR szCL[MAX_PATH*2]; // scratch for path + args. szCL[0] = 0; lpwCmd++; if( lpszBuf && lpszBuf[*lpwCmd] ) { TCHAR szTmp[MAX_PATH * 2]; lstrcpyn(szTmp, &lpszBuf[*lpwCmd], ARRAYSIZE(szTmp)); int cch = lstrlen(szTmp); // Is this string inside quotes? if ((cch > 1) && (szTmp[0] == TEXT('"')) && (szTmp[cch-1] == TEXT('"'))) { // 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 (LPTSTR 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); LPTSTR 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, ARRAYSIZE(szCL)); } else { // No TraceMsg(TF_DDE, "Creating new connection."); // Make a connection. TCHAR chDrive; if (Net_ConnectDrive(szShare, &chDrive)) { // Store the server/share. lstrcpyn(pddec->szShare, szShare, ARRAYSIZE(pddec->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, ARRAYSIZE(szCL)); } else { TraceMsg(TF_DDE, "Can't create connection."); } } TraceMsg(TF_DDE, "CL changed to %s.", szCL); } // Is there a name? TCHAR szName[MAX_PATH]; szName[0] = TEXT('\0'); if (nParams > 1) { // Yep, lpwCmd++; lstrcpyn(szName, &lpszBuf[*lpwCmd], ARRAYSIZE(szName)); } // Make absolutely sure we have a name. if (!szName[0]) BuildDefaultName(szName, szCL, ARRAYSIZE(szName)); // 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++; lstrcpyn(szTmp, &lpszBuf[*lpwCmd], ARRAYSIZE(szTmp)); 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'); } UINT 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++; lstrcpyn(szTmp, &lpszBuf[*lpwCmd], ARRAYSIZE(szTmp)); } 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. LPTSTR dirs[2]; dirs[0] = szTmp; dirs[1] = NULL; PathResolve(szCL, (LPCTSTR*)dirs, PRF_TRYPROGRAMEXTENSIONS | PRF_VERIFYEXISTS); LPITEMIDLIST 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); StrCatBuff(szTmp, c_szDotLnk, ARRAYSIZE(szTmp)); PathQualify(szTmp); // We need to handle link duplication problems on SFN drives. if (!IsLFNDrive(szTmp) && PathFileExistsAndAttributes(szTmp, NULL)) PathYetAnotherMakeUniqueName(szTmp, szTmp, NULL, NULL); IPersistFile *ppf; if (SUCCEEDED(pddec->psl->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)))) { // DDE can do anything, so its not a security weakness to use this path directly ppf->Save(szTmp, 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]); StrCatBuff(szPath, c_szDotLnk, ARRAYSIZE(szPath)); bRet = Win32DeleteFile(szPath); PathCombine(szPath, pddec->szGroup, &lpszBuf[*lpwCmd]); StrCatBuff(szPath, c_szDotPif, ARRAYSIZE(szPath)); 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) { 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) { // 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, UINT cch) { if (uFlags & COF_EXPLORE) StrCatBuff(pszParams, TEXT(",/E"), cch); if (uFlags & COF_SELECT) StrCatBuff(pszParams, TEXT(",/SELECT"), cch); if (uFlags & COF_CREATENEWWINDOW) StrCatBuff(pszParams, TEXT(",/N"), cch); if (uFlags & COF_USEOPENSETTINGS) StrCatBuff(pszParams, TEXT(",/S"), cch); } #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(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); wnsprintf(szCmdLine, ARRAYSIZE(szCmdLine), TEXT("/IDLIST,:%ld:%ld"), hIdList, dwProcess); if (!hIdList) fSuccess = FALSE; } else { lstrcpyn(szCmdLine, TEXT("/IDLIST,:0"), ARRAYSIZE(szCmdLine)); } _FlagsToParams(pfi->uFlags, szCmdLine + lstrlen(szCmdLine), ARRAYSIZE(szCmdLine) - lstrlen(szCmdLine)); if (fSuccess) { fSuccess = ShellExecuteEx(&ei); // if attacker has sent DDE commands we're already in trouble } 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) { 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 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) { // cbData is really cchData 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, ARRAYSIZE(szAGroup), 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; // strcpy/strcat okay, we just allocated it above 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, ARRAYSIZE(szAGroup), 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; // strcpy/strcat okay, we just allocated it above 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]; lstrcpyn(sz, pszCL, ARRAYSIZE(sz)); 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; ASSERT(pgi); ASSERT(pidlLink); ASSERT(psf); if (SHGetAttributes(psf, pidlLink, SFGAO_LINK)) { TCHAR szName[MAX_PATH]; // Get the relevant data. // Copy it. // Stick pointers in pgi. if (SUCCEEDED(DisplayNameOf(psf, pidlLink, SHGDN_NORMAL, szName, ARRAYSIZE(szName)))) { TCHAR sz[MAX_PATH], szCL[MAX_PATH]; TraceMsg(TF_DDE, "Link %s", szName); pgi->pszDesc = StrDup(szName); PathCombine(sz, lpszGroupPath, szName); StrCatBuff(sz, c_szDotLnk, ARRAYSIZE(sz)); // Read the link. // "name","CL",def dir,icon path,x,y,icon index,hotkey,minflag. ppf->Load(sz, 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)); lstrcpyn(sz, szCL, ARRAYSIZE(sz)); if (szArgs[0]) { StrCatBuff(sz, TEXT(" "), ARRAYSIZE(sz)); 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))) lstrcpyn(sz, szShortPath, ARRAYSIZE(sz)); } 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) { HDDEDATA hddedata = HDDENULL; int cItems = 0; TraceMsg(TF_DDE, "c.eiig: Enumerating %s.", (LPTSTR)lpszGroup); // // Get personal group location // TCHAR sz[MAX_PATH]; if (!SHGetSpecialFolderPath(NULL, sz, CSIDL_PROGRAMS, FALSE)) { return NULL; } PathAddBackslash(sz); StrCatBuff(sz, lpszGroup, ARRAYSIZE(sz)); // // Test if the group exists. // BOOL bCommon = FALSE; WIN32_FIND_DATA fd; HANDLE 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); } HDSA hdsaGroup = DSA_Create(sizeof(GROUPITEM), 0); if (hdsaGroup) { BOOL fOK = FALSE; // Get the group info. LPITEMIDLIST pidlGroup = ILCreateFromPath(sz); if (pidlGroup) { IShellFolder *psf; if (SUCCEEDED(SHBindToObjectEx(NULL, pidlGroup, NULL, IID_PPV_ARG(IShellFolder, &psf)))) { LPENUMIDLIST penum; if (S_OK == psf->EnumObjects(NULL, SHCONTF_NONFOLDERS, &penum)) { IShellLink *psl; if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellLink, &psl)))) { IPersistFile *ppf; if (SUCCEEDED(psl->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)))) { LPITEMIDLIST pidl; ULONG celt; while ((penum->Next(1, &pidl, &celt) == NOERROR) && (celt == 1)) { GROUPITEM gi; 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(); } 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); TCHAR szLine[MAX_PATH*4]; wnsprintf(szLine, ARRAYSIZE(szLine), TEXT("\"%s\",%s,%d,%d,%d\r\n"), lpszGroup, sz, cItems, SW_SHOWNORMAL, bCommon); UINT cchDDE = lstrlen(szLine)+1; LPTSTR pszDDE = (LPTSTR)LocalAlloc(LPTR, cchDDE * sizeof(TCHAR)); if (pszDDE) { lstrcpyn(pszDDE, szLine, cchDDE); cItems--; while (cItems >= 0) { PGROUPITEM pgi = (GROUPITEM*)DSA_GetItemPtr(hdsaGroup, cItems); ASSERT(pgi); // Fake up reasonable coords. int x = ((cItems%ITEMSPERROW)*64)+32; int y = ((cItems/ITEMSPERROW)*64)+32; // "name","CL",def dir,icon path,x,y,icon index,hotkey,minflag. wnsprintf(szLine, ARRAYSIZE(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) + 1; LPTSTR pszRealloc = (LPTSTR)_LocalReAlloc((HLOCAL)pszDDE, cchDDE * sizeof(TCHAR), LMEM_MOVEABLE|LMEM_ZEROINIT); if (pszRealloc) { pszDDE = pszRealloc; StrCatBuff(pszDDE, szLine, cchDDE); cItems--; } else { TraceMsg(DM_ERROR, "c.eiig: Unable to realocate DDE line."); break; } } // 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."); } 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, ARRAYSIZE(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(); }