#include "shellprv.h" #pragma hdrstop #include extern TCHAR g_szFileTemplate[]; // used to build type name... extern void _InitFileFolderClassNames (void); STDAPI OpenWithListRegister(DWORD dwFlags, LPCTSTR pszExt, LPCTSTR pszVerb, HKEY hkProgid); // DPA string compare function static int CALLBACK DPAStringCompare(LPVOID sz1, LPVOID sz2, LPARAM lparam) { return(lstrcmpi((LPTSTR)sz1, (LPTSTR)sz2)); } // DPA string free function static int CALLBACK DPAStringFree(LPVOID sz, LPVOID pData) { LocalFree(sz); return 0; } // This is a real hack, but for now we generate an idlist that looks // something like: C:\*.ext which is the extension for the IDList. // We use the simple IDList as to not hit the disk... void _GenerateAssociateNotify(LPCTSTR pszExt) { TCHAR szFakePath[MAX_PATH]; LPITEMIDLIST pidl; GetWindowsDirectory(szFakePath, ARRAYSIZE(szFakePath)); lstrcpy(szFakePath + 3, c_szStar); // "C:\*" lstrcat(szFakePath, pszExt); // "C:\*.foo" pidl = SHSimpleIDListFromPath(szFakePath); if (pidl) { // Now call off to the notify function. SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, pidl, NULL); ILFree(pidl); } } // Given a class key returns the shell\open\command string in szValue // and the number of chars copied in cbMaxValue. cbMaxValue should // be initialised to the max siz eof szValue. // We now expect and return the count in characters (DavePl) void GetCmdLine(LPCTSTR szKey, LPTSTR szValue, LONG cchValue) { TCHAR szTemp[MAX_PATH+40]; // Leave room for both extension plus junk on at end... VDATEINPUTBUF(szValue, TCHAR, cchValue); wsprintf(szTemp, TEXT("%s\\%s"), szKey, c_szShellOpenCmd); szValue[0] = 0; cchValue *= SIZEOF(TCHAR); SHRegQueryValue(HKEY_CLASSES_ROOT, szTemp, szValue, &cchValue); } // uFlags GCD_ flags from GetClassDescription uFlags void FillListWithClasses(HWND hwnd, BOOL fComboBox, UINT uFlags) { int i; TCHAR szClass[CCH_KEYMAX]; TCHAR szDisplayName[CCH_KEYMAX]; LONG lcb; SendMessage(hwnd, fComboBox ? CB_RESETCONTENT : LB_RESETCONTENT, 0, 0L); if (uFlags & GCD_MUSTHAVEEXTASSOC) { TCHAR szExt[CCH_KEYMAX]; // The caller stated that they only want those classes that // have have at least one extension associated with it. for (i = 0; RegEnumKey(HKEY_CLASSES_ROOT, i, szClass, ARRAYSIZE(szClass)) == ERROR_SUCCESS; i++) { // Is this an extension if (szClass[0] != TEXT('.')) continue; // go process the next one... // Get the class name lstrcpy(szExt, szClass); lcb = SIZEOF(szClass); if ((SHRegQueryValue(HKEY_CLASSES_ROOT, szExt, szClass, &lcb) != ERROR_SUCCESS) || (lcb == 0)) continue; // Again we are not interested. // use uFlags passed in to filter if (GetClassDescription(HKEY_CLASSES_ROOT, szClass, szDisplayName, ARRAYSIZE(szDisplayName), uFlags)) { INT_PTR iItem; /* If the display name is zero length then don't bother to show in the control. */ if ( !lstrlen( szDisplayName ) ) continue; // Now make sure it is not already in the list... if ((int)SendMessage(hwnd, fComboBox ? CB_FINDSTRINGEXACT : LB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)(LPTSTR)szDisplayName) >= 0) continue; // allready in the list. // sorted iItem = (INT_PTR) SendMessage(hwnd, fComboBox ? CB_ADDSTRING : LB_ADDSTRING, 0, (LONG_PTR)(LPTSTR)szDisplayName); if (iItem >= 0) SendMessage(hwnd, fComboBox ? CB_SETITEMDATA : LB_SETITEMDATA, iItem, (LONG_PTR)AddHashItem(NULL, szClass)); } } } else { for (i = 0; RegEnumKey(HKEY_CLASSES_ROOT, i, szClass, ARRAYSIZE(szClass)) == ERROR_SUCCESS; i++) { // use uFlags passed in to filter if (GetClassDescription(HKEY_CLASSES_ROOT, szClass, szDisplayName, ARRAYSIZE(szDisplayName), uFlags)) { // sorted INT_PTR iItem = (INT_PTR)SendMessage(hwnd, fComboBox ? CB_ADDSTRING : LB_ADDSTRING, 0, (LONG_PTR)(LPTSTR)szDisplayName); if (iItem >= 0) SendMessage(hwnd, fComboBox ? CB_SETITEMDATA : LB_SETITEMDATA, iItem, (LONG_PTR)AddHashItem(NULL, szClass)); } } } } // get the displayable name for file types "classes" // uFlags: // GCD_MUSTHAVEOPENCMD only returns things with open verbs // GCD_ADDEXETODISPNAME append the name of the ext that is in the open cmd // (GCD_MUSTHAVEOPENCMD) // GCD_ALLOWPSUDEOCLASSES return psudeo classes, those with stuff haning // off the .ext key BOOL GetClassDescription(HKEY hkClasses, LPCTSTR pszClass, LPTSTR szDisplayName, int cchDisplayName, UINT uFlags) { TCHAR szExe[MAX_PATH]; TCHAR szClass[CCH_KEYMAX]; LPTSTR pszExeFile; LONG lcb; // Skip things that aren't classes (extensions). if (pszClass[0] == TEXT('.')) { if (uFlags & GCD_ALLOWPSUDEOCLASSES) { lcb = SIZEOF(szClass); if ((SHRegQueryValue(hkClasses, pszClass, szClass, &lcb) != ERROR_SUCCESS) || (lcb == 0)) { // look for .ext\shell\open\command directly (hard wired association) // if this extenstion does not name a real class GetCmdLine(pszClass, szExe, ARRAYSIZE(szExe)); if (szExe[0]) { lstrcpyn(szDisplayName, PathFindFileName(szExe), cchDisplayName); return TRUE; } return FALSE; } pszClass = szClass; } else { return FALSE; // don't return psudeo class } } // REVIEW: we should really special case the OLE junk here. if pszClass is // CLSID, Interface, TypeLib, etc we should skip it // REVIEW: we really need to verify that some extension points at this type to verfy // that it is valid. perhaps the existance of a "shell" key is enough. // get the classes displayable name lcb = cchDisplayName * SIZEOF(TCHAR); if (SHRegQueryValue(hkClasses, pszClass, szDisplayName, &lcb) != ERROR_SUCCESS || (lcb < 2)) return FALSE; if (uFlags & GCD_MUSTHAVEOPENCMD) { // verify that it has an open command GetCmdLine(pszClass, szExe, ARRAYSIZE(szExe)); if (!szExe[0]) return FALSE; // BUGBUG: currently this is dead functionallity if (uFlags & GCD_ADDEXETODISPNAME) { PathRemoveArgs(szExe); // eliminate per instance type things (programs, pif, etc) // Skip things that aren't relevant to the shell. if (szExe[0] == TEXT('%')) return FALSE; // skip things with per-instance type associations pszExeFile = PathFindFileName(szExe); if ((int)((lstrlen(szDisplayName) + lstrlen(pszExeFile) + 2)) < cchDisplayName) { wsprintf(szDisplayName + lstrlen(szDisplayName), TEXT(" (%s)"), pszExeFile); } } } return TRUE; } void DeleteListAttoms(HWND hwnd, BOOL fComboBox) { int cItems; PHASHITEM phiClass; int iGetDataMsg; iGetDataMsg = fComboBox ? CB_GETITEMDATA : LB_GETITEMDATA; cItems = (int)SendMessage(hwnd, fComboBox ? CB_GETCOUNT : LB_GETCOUNT, 0, 0) - 1; /* clean out them atoms except for "(none)". */ for (; cItems >= 0; cItems--) { phiClass = (PHASHITEM)SendMessage(hwnd, iGetDataMsg, cItems, 0L); if (phiClass != (PHASHITEM)LB_ERR && phiClass) DeleteHashItem(NULL, phiClass); } } // BEGIN new stuff typedef struct { // oad // params HWND hwnd; // parent window POPENASINFO poainfo; // local data int idDlg; // open as dialog type: DLG_OPENAS_NOTYPE or DLG_OPENAS HWND hDlg; // open as dialog window handle HWND hwndList; // app list LPTSTR lpszExt; LPTSTR lpszNoOpenMsg; TCHAR szDescription[CCH_KEYMAX]; // file type description HRESULT hr; } OPENAS_DATA, *POPENAS_DATA; #define AIF_TEMPKEY 0x1 // temp class key created for the selected exe #define AIF_SHELLNEW 0x2 // class key with shellnew subkey #define AIF_KEYISAPPKEY 0x4 // the szKey should be passed to AssocApp #define MAXKEYNAME 128 typedef struct { TCHAR szApp[MAX_PATH]; TCHAR szFriendly[MAXKEYNAME]; // Friendly name TCHAR szKey[MAXKEYNAME]; // value to pass to Assoc*() APIs FILETIME ft; DWORD flags; } APPINFO, *PAPPINFO; #define IsExtension(s) (*(s) == TEXT('.')) int _AppInfoGetIconIndex(APPINFO *pai) { TCHAR sz[MAX_PATH]; int iRet = -1; ASSOCF flags = ASSOCF_VERIFY | ASSOCF_NOUSERSETTINGS; if (pai->flags & AIF_KEYISAPPKEY) flags |= ASSOCF_OPEN_BYEXENAME; if (SUCCEEDED(AssocQueryString(flags, ASSOCSTR_EXECUTABLE, pai->szKey, NULL, sz, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(sz))))) { iRet = Shell_GetCachedImageIndex(sz, 0, 0); if (-1 == iRet) { iRet = Shell_GetCachedImageIndex(c_szShell32Dll, II_APPLICATION, 0); } } return iRet; } LPTSTR _AppInfoGetFriendly(APPINFO *pai) { if (!*pai->szFriendly) { ASSOCF flags = ASSOCF_VERIFY | ASSOCF_NOUSERSETTINGS; if (pai->flags & AIF_KEYISAPPKEY) flags |= ASSOCF_OPEN_BYEXENAME; if (FAILED(AssocQueryString(flags, ASSOCSTR_FRIENDLYAPPNAME, pai->szKey, NULL, pai->szFriendly, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(pai->szFriendly))))) { StrCpyN(pai->szFriendly, pai->szApp, SIZECHARS(pai->szFriendly)); } } return pai->szFriendly; } APPINFO *_CreateAppInfo(LPCTSTR pszApp, LPCTSTR pszFriendly, LPCTSTR pszKey, DWORD flags) { APPINFO *pai = (APPINFO *) LocalAlloc(LPTR, SIZEOF(APPINFO)); if (pai) { if (pszApp) { HANDLE hFile = CreateFile(pszApp, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile != INVALID_HANDLE_VALUE) { GetFileTime(hFile, NULL, NULL, &pai->ft); CloseHandle(hFile); } // we only care about the most basic name here StrCpyN(pai->szApp, PathFindFileName(pszApp), SIZECHARS(pai->szApp)); PathRemoveExtension(pai->szApp); } if (pszKey) StrCpyN(pai->szKey, pszKey, SIZECHARS(pai->szKey)); pai->flags = flags; if (pszFriendly) StrCpyN(pai->szFriendly, pszFriendly, SIZECHARS(pai->szFriendly)); else if (*pai->szKey) { // BUGBUGREVIEW after BETA2 - ZekeL - 25-JUN-98 // this hits the disk, and we would like some // way to enum this list without hitting the disk // so that this dialog is shown quickly _AppInfoGetFriendly(pai); if (pai->szFriendly[0] == 0) { LocalFree(pai); pai = NULL; } } } return pai; } int _AddAppInfoItem(APPINFO *pai, HWND hwnd) { LV_ITEM item; item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE; item.iItem = INT_MAX; item.iSubItem = 0; item.state = 0; item.iImage = I_IMAGECALLBACK; // use it if we got it! if (*(pai->szFriendly)) item.pszText = pai->szFriendly; else item.pszText = LPSTR_TEXTCALLBACK; item.lParam = (LPARAM)pai; return ListView_InsertItem(hwnd, &item); } void _AddFromAppPaths(HWND hwndList) { HKEY hkeyAppPaths; if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, TEXT("Applications"), &hkeyAppPaths)) { // give enough space to TCHAR szApp[MAX_PATH]; int i; for (i = 0; RegEnumKey(hkeyAppPaths, i, szApp, ARRAYSIZE(szApp)) == ERROR_SUCCESS; i++) { if (!IsPathInOpenWithKillList(szApp) && SUCCEEDED(AssocQueryString(ASSOCF_INIT_BYEXENAME | ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, szApp, NULL, szApp, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(szApp))))) { // we got a winner! APPINFO *pai = _CreateAppInfo(szApp, NULL, szApp, AIF_KEYISAPPKEY); if (pai) { _AddAppInfoItem(pai, hwndList); } } } RegCloseKey(hkeyAppPaths); } } void _AddFromHKCR(HWND hwndList) { int i; TCHAR szClass[MAX_PATH]; #ifdef WINNT BOOL fInExtensions = FALSE; #endif for (i = 0; RegEnumKey(HKEY_CLASSES_ROOT, i, szClass, ARRAYSIZE(szClass)) == ERROR_SUCCESS; i++) { TCHAR szApp[MAX_PATH]; #ifdef WINNT // UNDOCUMENTED feature. the enum is sorted, // so we can just restrict ourselves to extensions // for perf and fun! if (fInExtensions && !IsExtension(szClass)) break; if (!fInExtensions && IsExtension(szClass)) fInExtensions = TRUE; #endif // if (SUCCEEDED(AssocQueryString(ASSOCF_VERIFY | ASSOCF_NOUSERSETTINGS, ASSOCSTR_EXECUTABLE, szClass, NULL, szApp, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(szApp))))) { if (!IsPathInOpenWithKillList(szApp)) { DWORD flags = 0; APPINFO *pai; DWORD cch; if (IsExtension(szClass) && SUCCEEDED(AssocQueryString(ASSOCF_NOUSERSETTINGS, ASSOCSTR_SHELLNEWVALUE, szClass, NULL, NULL, &cch))) flags |= AIF_SHELLNEW; pai = _CreateAppInfo(szApp, NULL, szClass, flags); if (pai) { _AddAppInfoItem(pai, hwndList); } } } } } int _CompareApps(APPINFO *p1, APPINFO *p2, LPARAM p) { return StrCmpI(p1->szApp, p2->szApp); } int _CompareAppPriorities(APPINFO *p1, APPINFO *p2, LPARAM p) { // might be nice to have priority in the // app keys that apps can set... // NOTE - we want the newer file (larger ft) // to have higher pri, so it needs to be // a negative value so it shows up correctly // under sorting int i = CompareFileTime(&p2->ft, &p1->ft); if (i == 0) { if (p1->flags & AIF_KEYISAPPKEY) return -1; if (p2->flags & AIF_KEYISAPPKEY) return 1; if (p1->flags & AIF_SHELLNEW) return -1; if (p2->flags & AIF_SHELLNEW) return 1; if (IsExtension(p1->szKey)) return -1; if (IsExtension(p2->szKey)) return 1; } return i; } #define _GetAppInfoFromLV(h, i) (APPINFO *)LVUtil_GetLParam((h), (i)) void _PruneDuplicates(HWND hwndList) { int i, iMax = ListView_GetItemCount(hwndList); if (iMax > 0) { APPINFO *paiLast = _GetAppInfoFromLV(hwndList, 0); for (i = 1; i < iMax; i++) { APPINFO *pai = _GetAppInfoFromLV(hwndList, i); ASSERT(pai); ASSERT(paiLast); if (0 == _CompareApps(paiLast, pai, 0)) { // we have the same app, then it // is just a matter of who is better at // registering themselves :b int iDelete; // higher pri entries will return -1 if (0 > _CompareAppPriorities(paiLast, pai, 0)) // delete the current pai iDelete = i; else { // delete paiLast, so make pai into paiLast iDelete = i - 1; paiLast = pai; } // we need to delete this item as a duplicate // after deletion the list is smaller! // this means that we need back up the // current index as well. ListView_DeleteItem(hwndList, iDelete); i--; iMax--; } else { paiLast = pai; } } } } #define RegCountSubKeys(hk, pc) (RegQueryInfoKey((hk), NULL, NULL, NULL, (pc), NULL, NULL, NULL, NULL, NULL, NULL, NULL)) #define SZCACHEVERSION TEXT("NT5OpenAsList") BOOL _VerifyCacheVersion(IStream *pstm) { BOOL fRet = FALSE; TCHAR sz[SIZEOF(SZCACHEVERSION)]; DWORD cNow, cThen; HKEY hk; if (!SendMessage(GetShellWindow(), DTM_QUERYHKCRCHANGED, QHKCRID_OPENAS, 0L) && SUCCEEDED(Stream_ReadString(pstm, sz, SIZECHARS(sz), TRUE)) && 0 == StrCmp(sz, SZCACHEVERSION) && ERROR_SUCCESS == RegCountSubKeys(HKEY_CLASSES_ROOT, &cNow) && SUCCEEDED(IStream_Read(pstm, &cThen, SIZEOF(cThen))) && cNow == cThen && ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, TEXT("Applications"), &hk)) { if (ERROR_SUCCESS == RegCountSubKeys(hk, &cNow) && SUCCEEDED(IStream_Read(pstm, &cThen, SIZEOF(cThen))) && cNow == cThen) { fRet = TRUE; } RegCloseKey(hk); } return fRet; } #define _OpenCachedOpenAsList(grf) SHOpenRegStream(HKEY_CURRENT_USER, STRREG_DISCARDABLE STRREG_POSTSETUP, TEXT("OpenAsList"), grf) #define _DeleteCachedOpenAsList() SHSetValue(HKEY_CURRENT_USER, STRREG_DISCARDABLE STRREG_POSTSETUP, TEXT("OpenAsList"), REG_SZ, (LPVOID)"", 0); APPINFO *_GetNextCachedAppInfo(IStream *pstm) { APPINFO *pai = (APPINFO *)LocalAlloc(LPTR, SIZEOF(APPINFO)); if (pai) { if (SUCCEEDED(IStream_Read(pstm, &pai->flags, SIZEOF(pai->flags))) && SUCCEEDED(Stream_ReadString(pstm, pai->szApp, SIZECHARS(pai->szApp), TRUE)) && SUCCEEDED(Stream_ReadString(pstm, pai->szFriendly, SIZECHARS(pai->szFriendly), TRUE)) && SUCCEEDED(Stream_ReadString(pstm, pai->szKey, SIZECHARS(pai->szKey), TRUE))) return pai; else LocalFree(pai); } return NULL; } BOOL _WriteCachedAppInfo(IStream *pstm, APPINFO *pai) { return (SUCCEEDED(IStream_Write(pstm, &pai->flags, SIZEOF(pai->flags))) && SUCCEEDED(Stream_WriteString(pstm, pai->szApp, TRUE)) && SUCCEEDED(Stream_WriteString(pstm, pai->szFriendly, TRUE)) && SUCCEEDED(Stream_WriteString(pstm, pai->szKey, TRUE))); } BOOL _AddFromCachedOpenAsList(HWND hwndList) { BOOL fRet = FALSE; IStream *pstm = _OpenCachedOpenAsList(STGM_READ); if (pstm) { if (_VerifyCacheVersion(pstm)) { int cItems = 0; if (SUCCEEDED(IStream_Read(pstm, &cItems, SIZEOF(cItems))) && cItems > 0) { for ( ;cItems; cItems--) { APPINFO *pai = _GetNextCachedAppInfo(pstm); if (pai) { // ignore temp entries if (pai->flags & AIF_TEMPKEY) LocalFree(pai); else _AddAppInfoItem(pai, hwndList); } else break; } // if we made it through the entire list that we cached // then we will count our selves lucky and not try anything else if (!cItems) fRet = TRUE; } } pstm->lpVtbl->Release(pstm); } // if anything went wrong, just kill the cache if (!fRet) _DeleteCachedOpenAsList(); return fRet; } void _FillListWithApps(HWND hwndList) { if (!_AddFromCachedOpenAsList(hwndList)) { _AddFromAppPaths(hwndList); _AddFromHKCR(hwndList); // use a custom sort to delay the friendly names being used ListView_SortItems(hwndList, _CompareApps, 0); _PruneDuplicates(hwndList); } // BUGBUGREVIEW after BETA2 - ZekeL - 25-JUN98 // sort using the friendly name. of course // this required hitting the disk to get the friendly name // but oh well. // ListView_SortItems(hwndList, _CompareAppPriorities, 0); ListView_SortItems(hwndList, NULL, 0); // Lets set the focus to first item, but not the focus as some users // have made the mistake and type in the name and hit return and it // runs the first guy in the list which in the current cases tries to // run the backup app... ListView_SetItemState(hwndList, 0, LVNI_FOCUSED, LVNI_FOCUSED | LVNI_SELECTED); SetFocus(hwndList); } void _InitOpenAsDlg(POPENAS_DATA poad) { TCHAR szFormat[200]; TCHAR szFileName[MAX_PATH]; TCHAR szTemp[MAX_PATH + ARRAYSIZE(szFormat)]; BOOL fDisableAssociate; HIMAGELIST himlLarge, himlSmall; LV_COLUMN col = {LVCF_FMT | LVCF_WIDTH, LVCFMT_LEFT}; BOOL fBlanketDissable; RECT rc; fBlanketDissable=FALSE; #ifdef WINNT fBlanketDissable = SHRestricted(REST_NOFILEASSOCIATE); #endif // Don't let the file name go beyond the width of one line... GetDlgItemText(poad->hDlg, IDD_TEXT, szFormat, ARRAYSIZE(szFormat)); lstrcpy(szFileName, PathFindFileName(poad->poainfo->pcszFile)); GetClientRect(GetDlgItem(poad->hDlg, IDD_TEXT), &rc); PathCompactPath(NULL, szFileName, rc.right - 4 * GetSystemMetrics(SM_CXBORDER)); wsprintf(szTemp, szFormat, szFileName); SetDlgItemText(poad->hDlg, IDD_TEXT, szTemp); // AraBern 07/20/99, specific to TS on NT, but can be used on NT without TS if ( fBlanketDissable ) { CheckDlgButton(poad->hDlg, IDD_MAKEASSOC, FALSE); EnableWindow(GetDlgItem(poad->hDlg, IDD_MAKEASSOC), FALSE); } else { // Don't allow associations to be made for things we consider exes... fDisableAssociate = (! (poad->poainfo->dwInFlags & OAIF_ALLOW_REGISTRATION) || PathIsExe(poad->poainfo->pcszFile)); // check IDD_MAKEASSOC only for unknown file type and those with OAIF_FORCE_REGISTRATION flag set if ((poad->poainfo->dwInFlags & OAIF_FORCE_REGISTRATION) || (poad->idDlg == DLG_OPENAS_NOTYPE && !fDisableAssociate)) CheckDlgButton(poad->hDlg, IDD_MAKEASSOC, TRUE); if (fDisableAssociate) EnableWindow(GetDlgItem(poad->hDlg, IDD_MAKEASSOC), FALSE); } poad->hwndList = GetDlgItem(poad->hDlg, IDD_APPLIST); Shell_GetImageLists(&himlLarge, &himlSmall); ListView_SetImageList(poad->hwndList, himlLarge, LVSIL_NORMAL); ListView_SetImageList(poad->hwndList, himlSmall, LVSIL_SMALL); SetWindowLong(poad->hwndList, GWL_EXSTYLE, GetWindowLong(poad->hwndList, GWL_EXSTYLE) | WS_EX_CLIENTEDGE); GetClientRect(poad->hwndList, &rc); col.cx = rc.right - GetSystemMetrics(SM_CXVSCROLL) - 4 * GetSystemMetrics(SM_CXEDGE); ListView_InsertColumn(poad->hwndList, 0, &col); _FillListWithApps(poad->hwndList); // initialize the OK button EnableWindow(GetDlgItem(poad->hDlg, IDOK), (ListView_GetNextItem(poad->hwndList, -1, LVNI_SELECTED) != -1)); _InitFileFolderClassNames(); } BOOL RunAs(POPENAS_DATA poad) { BOOL fRet = FALSE; APPINFO *pai; int iItemFocus; SHELLEXECUTEINFO ExecInfo = { 0 }; iItemFocus = ListView_GetNextItem(poad->hwndList, -1, LVNI_SELECTED); pai = (APPINFO *)LVUtil_GetLParam(poad->hwndList, iItemFocus); FillExecInfo(ExecInfo, poad->hwnd, NULL, poad->poainfo->pcszFile, NULL, NULL, SW_NORMAL); AssocQueryKey(pai->flags & AIF_KEYISAPPKEY ? ASSOCF_OPEN_BYEXENAME : 0, ASSOCKEY_SHELLEXECCLASS, pai->szKey, NULL, &ExecInfo.hkeyClass ); if (ExecInfo.hkeyClass) { ExecInfo.fMask |= SEE_MASK_CLASSKEY; fRet = ShellExecuteEx(&ExecInfo); RegCloseKey(ExecInfo.hkeyClass); } // else this should only happen if something really wierd happened return fRet; } // Create a new class key, and set its shell\open\command BOOL CreateTempClassForExe(PAPPINFO pai, LPCTSTR pszPath) { // if it is not an LFN app, pass unquoted args. // otherwise, AssocSet() will default correctly. LPCWSTR pszArgs = (App_IsLFNAware(pszPath) ? NULL : L"%1"); WCHAR wszProgid[MAX_PATH], wszPath[MAX_PATH]; // we should have no class key here! ASSERT(!pai->szKey[0]); wnsprintf(pai->szKey, SIZECHARS(pai->szKey), TEXT("%s_tempkey"), pai->szApp); SHTCharToUnicode(pszPath, wszPath, SIZECHARS(wszPath)); SHTCharToUnicode(pai->szKey, wszProgid, SIZECHARS(wszProgid)); { ASSOCVERB av = {L"open", NULL, NULL, NULL, pszArgs, NULL}; ASSOCSHELL as = {&av, 1, 0}; ASSOCPROGID apid = {SIZEOF(apid), wszProgid, NULL, NULL, &as, NULL}; if (SUCCEEDED(AssocMakeProgid(ASSOCMAKEF_VOLATILE, wszPath, &apid, NULL))) { // we created a temp key, pai->flags |= AIF_TEMPKEY; return TRUE; } } // FAILURE *(pai->szApp) = TEXT('\0'); return FALSE; } int _AppInfoFindInLV(HWND hwndList, APPINFO *paiIn) { int i, iMax = ListView_GetItemCount(hwndList); for (i = 0; i < iMax; i++) { APPINFO *pai = _GetAppInfoFromLV(hwndList, i); ASSERT(pai); if (0 == _CompareApps(paiIn, pai, 0)) { return i; } } return -1; } void OpenAsOther(POPENAS_DATA poad) { TCHAR szApp[MAX_PATH]; *szApp = '\0'; // do a file open browse if (GetFileNameFromBrowse(poad->hDlg, szApp, ARRAYSIZE(szApp), NULL, MAKEINTRESOURCE(IDS_EXE), MAKEINTRESOURCE(IDS_PROGRAMSFILTER), MAKEINTRESOURCE(IDS_OPENAS))) { APPINFO *pai = _CreateAppInfo(szApp, NULL, NULL, 0); if (pai) { int iItem = _AppInfoFindInLV(poad->hwndList, pai); if (-1 == iItem) { if (CreateTempClassForExe(pai, szApp)) { iItem = _AddAppInfoItem(pai, poad->hwndList); if (-1 == iItem) LocalFree(pai); } } else LocalFree(pai); // Select it ListView_SetItemState(poad->hwndList, iItem, LVNI_SELECTED | LVNI_FOCUSED, LVNI_SELECTED | LVNI_FOCUSED); ListView_EnsureVisible(poad->hwndList, iItem, FALSE); SetFocus(poad->hwndList); } } } BOOL _IsNewAssociation(LPCTSTR pszExt, LPCTSTR pszApp, BOOL fForceLM) { BOOL fRet = TRUE; TCHAR sz[MAX_PATH]; ASSOCF flags = fForceLM ? ASSOCF_VERIFY | ASSOCF_NOUSERSETTINGS : ASSOCF_VERIFY; if (SUCCEEDED(AssocQueryString(flags, ASSOCSTR_EXECUTABLE, pszExt, NULL, sz, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(sz)))) && (0 == lstrcmpi(pszApp, sz))) { // these have the same executable, trust // that when the exe installed itself, it did // it correctly, and we dont need to overwrite // their associations with themselves :) fRet = FALSE; } return fRet; } // return true if ok to continue BOOL OpenAsMakeAssociation(LPCTSTR pszExt, LPCTSTR pszApp, LPCWSTR pszDesc, HKEY hkey) { BOOL fRet = FALSE; HRESULT hr = AssocMakeApplicationByKey(ASSOCMAKEF_VERIFY, hkey, NULL); // if the user is choosing the existing association // or if we werent able to setup an Application , // then we want to leave it alone, if (SUCCEEDED(hr) && (_IsNewAssociation(pszExt, pszApp, FALSE))) { if (!_IsNewAssociation(pszExt, pszApp, TRUE)) { // if it is reverting to the machine default // then we want to eliminate the user association hr = AssocMakeFileExtsToApplication(0, pszExt, NULL); } else if (*pszDesc) { // this is a brand new association. WCHAR wszExt[MAX_PATH]; WCHAR wszProgid[MAX_PATH]; ASSERT(lstrlen(pszExt) > 1); // because we always skip the "." below SHTCharToUnicode(pszExt, wszExt, SIZECHARS(wszExt)); wnsprintfW(wszProgid, SIZECHARS(wszProgid), L"%ls_auto_file", wszExt+1); // double NULL terminate wszExt[lstrlenW(wszExt) + 1] = '\0'; { HKEY hkDst; ASSOCPROGID apid = {SIZEOF(apid), wszProgid, pszDesc, NULL, NULL, wszExt}; WCHAR wszApp[MAX_PATH]; SHTCharToUnicode(pszApp, wszApp, ARRAYSIZE(wszApp)); if (SUCCEEDED(AssocMakeProgid(0, wszApp, &apid, &hkDst))) { hr = AssocCopyVerbs(hkey, hkDst); RegCloseKey(hkDst); } } } else { hr = AssocMakeFileExtsToApplication(0, pszExt, PathFindFileName(pszApp)); } // if the application already // existed, then it will // return S_FALSE; fRet = (S_OK == hr); } _GenerateAssociateNotify(pszExt); return fRet; } VOID _InitNoOpenDlg(POPENAS_DATA poad) { SHFILEINFO sfi; HICON hIcon; TCHAR szFormat[MAX_PATH], szTemp[MAX_PATH]; GetDlgItemText(poad->hDlg, IDD_TEXT1, szFormat, ARRAYSIZE(szFormat)); wnsprintf(szTemp, SIZECHARS(szTemp), szFormat, poad->szDescription, poad->lpszExt); SetDlgItemText(poad->hDlg, IDD_TEXT1, szTemp); if (poad->lpszNoOpenMsg && *poad->lpszNoOpenMsg) SetDlgItemText(poad->hDlg, IDD_TEXT2, poad->lpszNoOpenMsg); if (SHGetFileInfo(poad->poainfo->pcszFile, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_LARGEICON) && NULL != sfi.hIcon) { hIcon = sfi.hIcon; } else { HIMAGELIST himl; Shell_GetImageLists(&himl, NULL); hIcon = ImageList_ExtractIcon(g_hinst, himl, II_DOCNOASSOC); } hIcon = (HICON)SendDlgItemMessage(poad->hDlg, IDD_ICON, STM_SETICON, (WPARAM)hIcon, 0); if ( hIcon ) { DestroyIcon(hIcon); } } BOOL_PTR CALLBACK NoOpenDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam) { POPENAS_DATA poad = (POPENAS_DATA)GetWindowLongPtr(hDlg, DWLP_USER); switch (wMsg) { case WM_INITDIALOG: SetWindowLongPtr(hDlg, DWLP_USER, lParam); poad = (POPENAS_DATA)lParam; poad->hDlg = hDlg; _InitNoOpenDlg(poad); break; case WM_COMMAND: ASSERT(poad); switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDD_OPENWITH: // this will cause the open with dialog // to follow this dialog poad->hr = E_FAIL; EndDialog(hDlg, TRUE); break; case IDCANCEL: poad->hr = NOERROR; EndDialog(hDlg, TRUE); break; } break; default: return FALSE; } return TRUE; } const static DWORD aOpenAsHelpIDs[] = { // Context Help IDs IDD_ICON, IDH_FCAB_OPENAS_APPLIST, IDD_TEXT, IDH_FCAB_OPENAS_APPLIST, IDD_DESCRIPTIONTEXT, IDH_FCAB_OPENAS_DESCRIPTION, IDD_DESCRIPTION, IDH_FCAB_OPENAS_DESCRIPTION, IDD_APPLIST, IDH_FCAB_OPENAS_APPLIST, IDD_MAKEASSOC, IDH_FCAB_OPENAS_MAKEASSOC, IDD_OTHER, IDH_FCAB_OPENAS_OTHER, 0, 0 }; void OpenAs_OnGetDispInfo(LV_DISPINFO *pdi, HWND hwnd) { BOOL fKillMe = FALSE; if (pdi->item.mask & LVIF_IMAGE) { pdi->item.iImage = _AppInfoGetIconIndex((APPINFO *)pdi->item.lParam); // if there is problem, if (-1 == pdi->item.iImage) fKillMe = TRUE; } if (!fKillMe && (pdi->item.mask & LVIF_TEXT)) { pdi->item.pszText = _AppInfoGetFriendly((APPINFO *)pdi->item.lParam); if (!pdi->item.pszText) fKillMe = TRUE; } // we dont want any uncooperative guys! // if (fKillMe) // ListView_DeleteItem(hwnd, pdi->item.iItem); // else pdi->item.mask |= LVIF_DI_SETITEM; } BOOL _WriteCacheHeader(IStream *pstm, int iMax) { BOOL fRet = FALSE; HKEY hk; DWORD cNow; if (SUCCEEDED(Stream_WriteString(pstm, SZCACHEVERSION, TRUE)) && ERROR_SUCCESS == RegCountSubKeys(HKEY_CLASSES_ROOT, &cNow) && SUCCEEDED(IStream_Write(pstm, &cNow, SIZEOF(cNow))) && ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, TEXT("Applications"), &hk)) { if (ERROR_SUCCESS == RegCountSubKeys(hk, &cNow) && SUCCEEDED(IStream_Write(pstm, &cNow, SIZEOF(cNow))) && SUCCEEDED(IStream_Write(pstm, &iMax, SIZEOF(int)))) { fRet = TRUE; } RegCloseKey(hk); } return fRet; } void _SaveCachedOpenAsList(HWND hwndList, BOOL fDelete) { if (!fDelete) { IStream *pstm = _OpenCachedOpenAsList(STGM_READWRITE); if (pstm) { // if verify returns true, then we dont need to // update the cache if (!_VerifyCacheVersion(pstm)) { ULARGE_INTEGER li; int i, iMax = ListView_GetItemCount(hwndList); // back it up so we write at the beginning IStream_Reset(pstm); // this is not an accurate assessment of the size, // but for perf reasons it is better to guess something // closer to what we expect. // 99% of all items are less than 128 bytes li.QuadPart = (ULONGLONG)((UINT)iMax * 128); pstm->lpVtbl->SetSize(pstm, li); // start with the version and total count... if (_WriteCacheHeader(pstm, iMax)) { for (i = 0; i < iMax; i++) { APPINFO *pai = _GetAppInfoFromLV(hwndList, i); ASSERT(pai); if (!_WriteCachedAppInfo(pstm, pai)) { fDelete = TRUE; break; } } } else fDelete = TRUE; } pstm->lpVtbl->Release(pstm); } } if (fDelete) _DeleteCachedOpenAsList(); } BOOL_PTR CALLBACK OpenAsDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam) { POPENAS_DATA poad = (POPENAS_DATA)GetWindowLongPtr(hDlg, DWLP_USER); APPINFO *pai; int iItemFocus; HKEY hkeyProgID; BOOL fMadeNewAssoc = FALSE; switch (wMsg) { case WM_INITDIALOG: SetWindowLongPtr(hDlg, DWLP_USER, lParam); poad = (POPENAS_DATA)lParam; poad->hDlg = hDlg; _InitOpenAsDlg(poad); break; case WM_HELP: WinHelp((HWND)((LPHELPINFO) lParam)->hItemHandle, NULL, HELP_WM_HELP, (ULONG_PTR)(LPTSTR) aOpenAsHelpIDs); break; case WM_CONTEXTMENU: if ((int)SendMessage(hDlg, WM_NCHITTEST, 0, lParam) != HTCLIENT) return FALSE; // don't process it WinHelp((HWND) wParam, NULL, HELP_CONTEXTMENU, (ULONG_PTR)(LPVOID)aOpenAsHelpIDs); break; case WM_NOTIFY: switch (((LPNMHDR)lParam)->code) { case LVN_GETDISPINFO: OpenAs_OnGetDispInfo((LV_DISPINFO *)lParam, poad->hwndList); break; case LVN_DELETEITEM: pai = (PAPPINFO)((NM_LISTVIEW *)lParam)->lParam; if (pai->flags & AIF_TEMPKEY) SHDeleteKey(HKEY_CLASSES_ROOT, pai->szKey); LocalFree(pai); break; case LVN_ITEMCHANGED: EnableWindow(GetDlgItem(hDlg, IDOK), (ListView_GetNextItem(poad->hwndList, -1, LVNI_SELECTED) != -1)); break; case NM_DBLCLK: if (IsWindowEnabled(GetDlgItem(hDlg, IDOK))) PostMessage(hDlg, WM_COMMAND, GET_WM_COMMAND_MPS(IDOK, hDlg, 0)); break; } break; case WM_COMMAND: ASSERT(poad); switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDD_OTHER: OpenAsOther(poad); break; case IDOK: iItemFocus = ListView_GetNextItem(poad->hwndList, -1, LVNI_SELECTED); pai = (APPINFO *)LVUtil_GetLParam(poad->hwndList, iItemFocus); /* add shell\open of the selected progid to openwithlist. */ AssocQueryKey(pai->flags & AIF_KEYISAPPKEY ? ASSOCF_NOUSERSETTINGS | ASSOCF_OPEN_BYEXENAME : ASSOCF_NOUSERSETTINGS, ASSOCKEY_SHELLEXECCLASS, pai->szKey, NULL, &hkeyProgID); if (hkeyProgID) { WCHAR szDesc[MAX_PATH]; AssocQueryStringByKey(ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, hkeyProgID, NULL, poad->poainfo->szApp, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(poad->poainfo->szApp))); // See if we should make an association or not... if (!GetDlgItemTextW(poad->hDlg, IDD_DESCRIPTION, szDesc, ARRAYSIZE(szDesc))) *szDesc = 0; if (*poad->poainfo->szApp && (poad->poainfo->dwInFlags & OAIF_REGISTER_EXT) && (IsDlgButtonChecked(poad->hDlg, IDD_MAKEASSOC))) fMadeNewAssoc = OpenAsMakeAssociation(poad->lpszExt, poad->poainfo->szApp, szDesc, hkeyProgID); OpenWithListRegister(0, poad->lpszExt, NULL, hkeyProgID); RegCloseKey(hkeyProgID); } /* Did we register the association? */ poad->hr = IsDlgButtonChecked(poad->hDlg, IDD_MAKEASSOC) ? S_OK : S_FALSE; /* Exec if requested. */ if (poad->poainfo->dwInFlags & OAIF_EXEC) { RunAs(poad); SHAddToRecentDocs(SHARD_PATH, poad->poainfo->pcszFile); } _SaveCachedOpenAsList(poad->hwndList, fMadeNewAssoc); EndDialog(hDlg, TRUE); break; case IDCANCEL: poad->hr = E_ABORT; _SaveCachedOpenAsList(poad->hwndList, fMadeNewAssoc); EndDialog(hDlg, FALSE); break; } break; default: return FALSE; } return TRUE; } // external API version HRESULT OpenAsDialog( HWND hwnd, POPENASINFO poainfo) { INT_PTR iRet; OPENAS_DATA oad = { 0 }; int idDlg = DLG_OPENAS_NOTYPE; DebugMsg(DM_TRACE, TEXT("Enter OpenAs for %s"), poainfo->pcszFile); #ifdef WINNT // Depending on policy, do not allow user to change file type association. if ( SHRestricted(REST_NOFILEASSOCIATE) ) { poainfo->dwInFlags &= ~OAIF_ALLOW_REGISTRATION & ~OAIF_REGISTER_EXT; } #endif oad.hwnd = hwnd; oad.poainfo = poainfo; oad.lpszExt = PathFindExtension(oad.poainfo->pcszFile); // We don't allow association for files without extension or with only "." as extension if (!oad.lpszExt || !*oad.lpszExt || !lstrcmp(oad.lpszExt, TEXT("."))) { idDlg = DLG_OPENAS; poainfo->dwInFlags &= ~OAIF_ALLOW_REGISTRATION; } // Known file type(has verb): use DLG_OPENAS // NoOpen file type(has NoOpen value): use DLG_NOOPEN // Unknown file type(All others): use DLG_OPENAS_NOTYPE if (*oad.lpszExt && *(oad.lpszExt+1)) { HRESULT hr; WCHAR wsz[MAX_PATH]; IQueryAssociations *pqa; hr = AssocCreate(CLSID_QueryAssociations, &IID_IQueryAssociations, (LPVOID *)&pqa); if (FAILED(hr)) return hr; SHTCharToUnicode(oad.lpszExt, wsz, SIZECHARS(wsz)); if (SUCCEEDED(pqa->lpVtbl->Init(pqa, 0, wsz, NULL, NULL))) { DWORD cch; idDlg = DLG_OPENAS; pqa->lpVtbl->GetString(pqa, 0, ASSOCSTR_FRIENDLYDOCNAME, NULL, wsz, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(wsz))); SHUnicodeToTChar(wsz, oad.szDescription, SIZECHARS(oad.szDescription)); if (SUCCEEDED(pqa->lpVtbl->GetString(pqa, 0, ASSOCSTR_NOOPEN, NULL, wsz, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(wsz)))) && FAILED(pqa->lpVtbl->GetString(pqa, 0, ASSOCSTR_COMMAND, NULL, NULL, &cch))) { TCHAR sz[MAX_PATH]; SHUnicodeToTChar(wsz, sz, SIZECHARS(sz)); oad.lpszNoOpenMsg = sz; iRet = DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_NOOPEN), hwnd, NoOpenDlgProc, (LPARAM)&oad); if ((-1 != iRet) && SUCCEEDED(oad.hr)) { // user selected cancel pqa->lpVtbl->Release(pqa); oad.idDlg = DLG_NOOPEN; return oad.hr; } } } pqa->lpVtbl->Release(pqa); } oad.idDlg = idDlg; iRet = DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(idDlg), hwnd, OpenAsDlgProc, (LPARAM)(POPENAS_DATA)&oad); if (-1 != iRet) return oad.hr; else return E_FAIL; } void WINAPI OpenAs_RunDLL(HWND hwnd, HINSTANCE hAppInstance, LPSTR lpszCmdLine, int nCmdShow) { OPENASINFO oainfo = { 0 }; #ifdef UNICODE UINT iLen = lstrlenA(lpszCmdLine)+1; LPWSTR lpwszCmdLine; lpwszCmdLine = (LPWSTR)LocalAlloc(LPTR,iLen*SIZEOF(WCHAR)); if (lpwszCmdLine) { MultiByteToWideChar(CP_ACP, 0, lpszCmdLine, -1, lpwszCmdLine, iLen); DebugMsg(DM_TRACE, TEXT("OpenAs_RunDLL is called with (%s)"), lpwszCmdLine); oainfo.pcszFile = lpwszCmdLine; oainfo.dwInFlags = (OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC); OpenAsDialog(hwnd, &oainfo); LocalFree(lpwszCmdLine); } #else DebugMsg(DM_TRACE, TEXT("OpenAs_RunDLL is called with (%s)"), lpszCmdLine); oainfo.pcszFile = lpszCmdLine; oainfo.dwInFlags = (OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC); OpenAsDialog(hwnd, &oainfo); #endif } void WINAPI OpenAs_RunDLLW(HWND hwnd, HINSTANCE hAppInstance, LPWSTR lpwszCmdLine, int nCmdShow) { OPENASINFO oainfo = { 0 }; #ifdef UNICODE DebugMsg(DM_TRACE, TEXT("OpenAs_RunDLL is called with (%s)"), lpwszCmdLine); oainfo.pcszFile = lpwszCmdLine; oainfo.dwInFlags = (OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC); OpenAsDialog(hwnd, &oainfo); #else UINT iLen = WideCharToMultiByte(CP_ACP, 0, lpwszCmdLine, -1, NULL, 0, NULL, NULL)+1; LPSTR lpszCmdLine; lpszCmdLine = (LPSTR)LocalAlloc(LPTR,iLen); if (lpszCmdLine) { WideCharToMultiByte(CP_ACP, 0, lpwszCmdLine, -1, lpszCmdLine, iLen, NULL, NULL); DebugMsg(DM_TRACE, TEXT("OpenAs_RunDLL is called with (%s)"), lpszCmdLine); oainfo.pcszFile = lpszCmdLine; oainfo.dwInFlags = (OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC); OpenAsDialog(hwnd, &oainfo); LocalFree(lpszCmdLine); } #endif } #ifdef DEBUG // Type checking const static RUNDLLPROCA lpfnRunDLL = OpenAs_RunDLL; const static RUNDLLPROCW lpfnRunDLLW = OpenAs_RunDLLW; #endif