|
|
//
// fassoc.cpp
//
// IQueryAssociations shell implementations
//
// New storage - move this to a simple database if possible
//
// ****************************** User Customizations ********************************
//
// HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts
// |
// |+ ".ext" // the extension that has been customized
// | |- "Application" = "UserNotepad.AnyCo.1"
// | |+ "OpenWithList" // MRU for the Open With ctx menu
// |
// _ ...
//
//
// ****************************** NoRoam Store **************************
//
// HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\NoRoam
// |
// |+ ".ext" (the extension that has been customized)
// | |- Application = "UserNotepad.AnyCo.1"
// |
// _ ...
//
// ***************************** Handlers **************************************
// (store detailed per handler file association info)
//
// HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\NoRoam\Associations
// |
#include "shellprv.h"
#include <shpriv.h>
#include "clsobj.h"
#include <shstr.h>
#include <msi.h>
#include "fassoc.h"
#include <runtask.h>
BOOL _PathAppend(LPCTSTR pszBase, LPCTSTR pszAppend, LPTSTR pszOut, DWORD cchOut) { DWORD cchBase = lstrlen(pszBase);
// +1 is one for the whack
if (cchOut > cchBase + lstrlen(pszAppend) + 1) { StrCpy(pszOut, pszBase); pszOut+=cchBase; *pszOut++ = TEXT('\\'); StrCpy(pszOut, pszAppend); return TRUE; } return FALSE; }
STDAPI UserAssocSet(UASET set, LPCWSTR pszExt, LPCWSTR pszSet) { HKEY hk = SHGetShellKey(SHELLKEY_HKCU_FILEEXTS, pszExt, TRUE); if (hk) { // we should always clear
SHDeleteValue(hk, NULL, L"Application"); SHDeleteValue(hk, NULL, L"Progid");
switch (set) { case UASET_APPLICATION: SHSetValue(hk, NULL, L"Application", REG_SZ, pszSet, CbFromCch(lstrlen(pszSet)+1)); break;
case UASET_PROGID: SHSetValue(hk, NULL, L"Progid", REG_SZ, pszSet, CbFromCch(lstrlen(pszSet)+1)); break; } RegCloseKey(hk); return S_OK; } return HRESULT_FROM_WIN32(GetLastError()); }
void _MakeApplicationsKey(LPCTSTR pszApp, LPTSTR pszKey, DWORD cchKey) { if (_PathAppend(TEXT("Applications"), pszApp, pszKey, cchKey)) { // Currently we will only look up .EXE if an extension is not
// specified
if (*PathFindExtension(pszApp) == 0) { StrCatBuff(pszKey, TEXT(".exe"), cchKey); } } }
DWORD _OpenApplicationKey(LPCWSTR pszApp, HKEY *phk, BOOL fCheckCommand = FALSE) { // look direct
// then try indirecting
// then try appending .exe
WCHAR sz[MAX_PATH]; _MakeApplicationsKey(pszApp, sz, ARRAYSIZE(sz)); DWORD err = RegOpenKeyEx(HKEY_CLASSES_ROOT, sz, 0, MAXIMUM_ALLOWED, phk); if (err == ERROR_SUCCESS && fCheckCommand) { DWORD cch; if (ERROR_SUCCESS == SHQueryValueEx(*phk, TEXT("NoOpenWith"), NULL, NULL, NULL, NULL) || FAILED(AssocQueryStringByKey(0, ASSOCSTR_COMMAND, *phk, NULL, NULL, &cch))) { err = ERROR_ACCESS_DENIED; RegCloseKey(*phk); *phk = NULL; } } return err; }
STDAPI UserAssocOpenKey(LPCWSTR pszExt, HKEY *phk) { HKEY hk = SHGetShellKey(SHELLKEY_HKCU_FILEEXTS, pszExt, FALSE); DWORD err = 0; if (hk) { WCHAR sz[MAX_PATH]; DWORD cb = sizeof(sz); // first check for a progid
err = SHGetValue(hk, NULL, L"Progid", NULL, sz, &cb); if (err == ERROR_SUCCESS) { err = RegOpenKeyEx(HKEY_CLASSES_ROOT, sz, 0, MAXIMUM_ALLOWED, phk); cb = sizeof(sz); // maybe need to map to CurVer??
}
if (err != ERROR_SUCCESS) { err = SHGetValue(hk, NULL, L"Application", NULL, sz, &cb); if (err == ERROR_SUCCESS) { err = _OpenApplicationKey(sz, phk); } }
RegCloseKey(hk); } else err = GetLastError();
return HRESULT_FROM_WIN32(err); }
class CVersion { public: CVersion(LPCWSTR psz) : _pVer(0), _hrInit(S_FALSE) { StrCpyNW(_szPath, psz, ARRAYSIZE(_szPath)); } ~CVersion() { if (_pVer) LocalFree(_pVer); }
HRESULT QueryStringValue(LPCWSTR pszValue, LPWSTR pszOut, DWORD cch); private: HRESULT _Init(); HRESULT _QueryValue(WORD wLang, WORD wCP, LPCWSTR pszValue, LPWSTR pszOut, DWORD cch);
WCHAR _szPath[MAX_PATH]; void *_pVer; HRESULT _hrInit; };
HRESULT CVersion::_Init() { if (_hrInit == S_FALSE) { _hrInit = E_FAIL; DWORD dwAttribs; if (PathFileExistsAndAttributes(_szPath, &dwAttribs)) { // bail in the \\server, \\server\share, and directory case or else GetFileVersionInfo() will try
// to do a LoadLibraryEx() on the path (which will fail, but not before we seach the entire include
// path which can take a long time)
if (!(dwAttribs & FILE_ATTRIBUTE_DIRECTORY) && !PathIsUNCServer(_szPath) && !PathIsUNCServerShare(_szPath)) { DWORD dwHandle; DWORD cb = GetFileVersionInfoSizeW(_szPath, &dwHandle); if (cb) { _pVer = LocalAlloc(LPTR, cb); if (_pVer) { if (GetFileVersionInfoW(_szPath, dwHandle, cb, _pVer)) _hrInit = S_OK; } } } } }
return _hrInit; }
inline BOOL IsAlphaDigit(WCHAR ch) { return ((ch >= L'0' && ch <= L'9') || (ch >= L'A' && ch <= L'Z') || (ch >= L'a' && ch <= L'z')); }
typedef struct { WORD wLanguage; WORD wCodePage; } XLATE;
const static XLATE s_px[] = { { 0, 0x04B0 }, // MLGetUILanguage, CP_UNICODE
{ 0, 0x04E4 }, // MLGetUILanguage, CP_USASCII
{ 0, 0x0000 }, // MLGetUILanguage, NULL
{ 0x0409, 0x04B0 }, // English, CP_UNICODE
{ 0x0409, 0x04E4 }, // English, CP_USASCII
{ 0x0409, 0x0000 }, // English, NULL
// { 0x041D, 0x04B0 }, // Swedish, CP_UNICODE
// { 0x0407, 0x04E4 }, // German, CP_USASCII
};
HRESULT CVersion::_QueryValue(WORD wLang, WORD wCP, LPCWSTR pszValue, LPWSTR pszOut, DWORD cchOut) { WCHAR szQuery[MAX_PATH]; LPWSTR pszString; UINT cch;
wnsprintfW(szQuery, ARRAYSIZE(szQuery), L"\\StringFileInfo\\%04X%04X\\%s", wLang, wCP, pszValue);
if (VerQueryValue(_pVer, szQuery, (void **) &pszString, &cch) && cch && IsAlphaDigit(*pszString)) { StrCpyN(pszOut, pszString, cchOut); return S_OK; }
return E_FAIL; }
HRESULT CVersion::QueryStringValue(LPCWSTR pszValue, LPWSTR pszOut, DWORD cchOut) { HRESULT hr = _Init();
if (SUCCEEDED(hr)) { hr = E_FAIL; for (int i = 0; FAILED(hr) && i < ARRAYSIZE(s_px); i++) { WORD wL = s_px[i].wLanguage ? s_px[i].wLanguage : MLGetUILanguage(); hr = _QueryValue(wL, s_px[i].wCodePage, pszValue, pszOut, cchOut); }
if (FAILED(hr)) { // Try first language this supports
XLATE *px; UINT cch; if (VerQueryValue(_pVer, TEXT("\\VarFileInfo\\Translation"), (void **)&px, &cch) && cch) { hr = _QueryValue(px[0].wLanguage, px[0].wCodePage, pszValue, pszOut, cchOut); } } }
return hr; }
void _TrimNonAlphaNum(LPWSTR psz) { while (*psz && IsAlphaDigit(*psz)) psz++;
*psz = 0; }
HKEY _OpenSystemFileAssociationsKey(LPCWSTR pszExt) { WCHAR sz[MAX_PATH] = L"SystemFileAssociations\\"; StrCatBuff(sz, pszExt, ARRAYSIZE(sz)); HKEY hk = NULL; if (NOERROR != RegOpenKeyEx(HKEY_CLASSES_ROOT, sz, 0, MAXIMUM_ALLOWED, &hk)) { DWORD cb = sizeof(sz) - sizeof(L"SystemFileAssociations\\"); if (NOERROR == SHGetValue(HKEY_CLASSES_ROOT, pszExt, L"PerceivedType", NULL, sz+ARRAYSIZE(L"SystemFileAssociations\\")-1, &cb)) { // if (PerceivedType != System)
RegOpenKeyEx(HKEY_CLASSES_ROOT, sz, 0, MAXIMUM_ALLOWED, &hk); } } return hk; }
BOOL _IsSystemFileAssociations(LPCWSTR pszExt) { HKEY hk = _OpenSystemFileAssociationsKey(pszExt); if (hk) RegCloseKey(hk); return hk != NULL; }
class CTaskEnumHKCR : public CRunnableTask { public: CTaskEnumHKCR() : CRunnableTask(RTF_DEFAULT) {} // *** pure virtuals ***
virtual STDMETHODIMP RunInitRT(void);
private: virtual ~CTaskEnumHKCR() {} void _AddFromHKCR();
};
void _AddProgidForExt(LPCWSTR pszExt) { WCHAR szNew[MAX_PATH]; DWORD cb = sizeof(szNew); if (NOERROR == SHGetValue(HKEY_CLASSES_ROOT, pszExt, NULL, NULL, szNew, &cb)) { WCHAR sz[MAX_PATH]; wnsprintf(sz, ARRAYSIZE(sz), L"%s\\OpenWithProgids", pszExt); SKSetValue(SHELLKEY_HKCU_FILEEXTS, sz, szNew, REG_NONE, NULL, NULL); } } #define IsExtension(s) (*(s) == TEXT('.'))
void CTaskEnumHKCR::_AddFromHKCR() { int i; TCHAR szClass[MAX_PATH]; BOOL fInExtensions = FALSE;
for (i = 0; RegEnumKey(HKEY_CLASSES_ROOT, i, szClass, ARRAYSIZE(szClass)) == ERROR_SUCCESS; i++) { // UNDOCUMENTED feature. the enum is sorted,
// so we can just restrict ourselves to extensions
// for perf and fun!
if (fInExtensions) { if (!IsExtension(szClass)) break; } else if (IsExtension(szClass)) { fInExtensions = TRUE; } else continue;
if (_IsSystemFileAssociations(szClass)) { _AddProgidForExt(szClass); } } }
HRESULT CTaskEnumHKCR::RunInitRT() { // delete something??
_AddFromHKCR(); return S_OK; }
STDAPI CTaskEnumHKCR_Create(IRunnableTask **pptask) { CTaskEnumHKCR *pteh = new CTaskEnumHKCR(); if (pteh) { HRESULT hr = pteh->QueryInterface(IID_PPV_ARG(IRunnableTask, pptask)); pteh->Release(); return hr; } *pptask = NULL; return E_OUTOFMEMORY; } typedef enum { AHTYPE_USER_APPLICATION = -2, AHTYPE_ANY_APPLICATION = -1, AHTYPE_UNDEFINED = 0, AHTYPE_CURRENTDEFAULT, AHTYPE_PROGID, AHTYPE_APPLICATION, } AHTYPE;
class CAssocHandler : public IAssocHandler { public: CAssocHandler() : _cRef(1) {} BOOL Init(AHTYPE type, LPCWSTR pszExt, LPCWSTR pszInit); // IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void);
// IAssocHandler methods
STDMETHODIMP GetName(LPWSTR *ppsz); STDMETHODIMP GetUIName(LPWSTR *ppsz); STDMETHODIMP GetIconLocation(LPWSTR *ppszPath, int *pIndex); STDMETHODIMP IsRecommended() { return _type > AHTYPE_UNDEFINED ? S_OK : S_FALSE; } STDMETHODIMP MakeDefault(LPCWSTR pszDescription); STDMETHODIMP Exec(HWND hwnd, LPCWSTR pszFile); STDMETHODIMP Invoke(void *pici, PCWSTR pszFile);
protected: // methods
~CAssocHandler(); HRESULT _Exec(SHELLEXECUTEINFO *pei); BOOL _IsNewAssociation(); void _GenerateAssociateNotify(); HRESULT _InitKey(); void _RegisterOWL();
protected: // members
ULONG _cRef; IQueryAssociations *_pqa; HKEY _hk; ASSOCF _flags; AHTYPE _type; LPWSTR _pszExt; LPWSTR _pszInit; BOOL _fRegistered; };
STDAPI CAssocHandler::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CAssocHandler, IAssocHandler), };
return QISearch(this, qit, riid, ppvObj); }
STDAPI_(ULONG) CAssocHandler::AddRef() { return ++_cRef; }
STDAPI_(ULONG) CAssocHandler::Release() { if (--_cRef > 0) return _cRef;
delete this; return 0; }
BOOL _InList(LPCWSTR pszList, LPCWSTR pszExt, WORD chDelim) { LPCWSTR pszMatch = StrStrI(pszList, pszExt); while (pszMatch) { LPCWSTR pszNext = (pszMatch+lstrlen(pszExt)); if (chDelim == *pszNext || !*pszNext) return TRUE; pszMatch = StrStrI(pszNext+1, pszExt); } return FALSE; }
// Create a new class key, and set its shell\open\command
BOOL _CreateApplicationKey(LPCTSTR pszPath) { DWORD err = ERROR_FILE_NOT_FOUND; if (PathFileExistsAndAttributes(pszPath, NULL)) { WCHAR szKey[MAX_PATH]; WCHAR szCmd[MAX_PATH * 2]; wnsprintf(szKey, ARRAYSIZE(szKey), L"Software\\Classes\\Applications\\%s\\shell\\open\\command", PathFindFileName(pszPath)); // if it is not an LFN app, pass unquoted args.
wnsprintf(szCmd, ARRAYSIZE(szCmd), App_IsLFNAware(pszPath) ? L"\"%s\" \"%%1\"" : L"\"%s\" %%1", pszPath); err = SHSetValue(HKEY_CURRENT_USER, szKey, NULL, REG_SZ, szCmd, CbFromCchW(lstrlen(szCmd)+1)); } return ERROR_SUCCESS == err; }
BOOL CAssocHandler::Init(AHTYPE type, LPCWSTR pszExt, LPCWSTR pszInit) { BOOL fRet = FALSE; _type = type; _pszExt = StrDup(pszExt);
if (pszInit) _pszInit = StrDup(PathFindFileName(pszInit));
if (_pszExt && (_pszInit || !pszInit)) { if (SUCCEEDED(AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &_pqa)))) { HKEY hk = NULL; _flags = ASSOCF_IGNOREBASECLASS; switch (type) { case AHTYPE_CURRENTDEFAULT: _flags |= ASSOCF_NOUSERSETTINGS; pszInit = pszExt; break;
case AHTYPE_USER_APPLICATION: case AHTYPE_APPLICATION: case AHTYPE_ANY_APPLICATION: _OpenApplicationKey(_pszInit, &hk, TRUE); if (hk) { if (type == AHTYPE_APPLICATION) { // check if this type is supported
HKEY hkTypes; if (ERROR_SUCCESS == RegOpenKeyEx(hk, TEXT("SupportedTypes"), 0, MAXIMUM_ALLOWED, &hkTypes)) { // the app only supports specific types
if (ERROR_SUCCESS != SHQueryValueEx(hkTypes, _pszExt, NULL, NULL, NULL, NULL)) { // this type is not supported
// so it will be relegated to the not recommended list
RegCloseKey(hk); hk = NULL; } RegCloseKey(hkTypes); } } } else if (type == AHTYPE_USER_APPLICATION) { // need to make up a key
if (_CreateApplicationKey(pszInit)) _OpenApplicationKey(_pszInit, &hk); }
pszInit = NULL; _flags |= ASSOCF_INIT_BYEXENAME; break;
case AHTYPE_PROGID: default: // _flags |= ...;
break; }
if (hk || pszInit) { if (SUCCEEDED(_pqa->Init(_flags, pszInit , hk, NULL))) { WCHAR szExe[MAX_PATH]; DWORD cchExe = ARRAYSIZE(szExe); // we want to make sure there is something at the other end
fRet = SUCCEEDED(_pqa->GetString(ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, NULL, szExe, &cchExe)); // however, if the EXE has been marked as superhidden,
// then the consent decree UI has hidden the app
// and it should not show up under the open with either
if (fRet) { fRet = !(IS_SYSTEM_HIDDEN(GetFileAttributes(szExe))); } } }
if (hk) RegCloseKey(hk); } } return fRet; }
CAssocHandler::~CAssocHandler() { if (_pqa) _pqa->Release(); if (_pszExt) LocalFree(_pszExt); if (_pszInit) LocalFree(_pszInit); if (_hk) RegCloseKey(_hk); } HRESULT CAssocHandler::GetName(LPWSTR *ppsz) { WCHAR sz[MAX_PATH]; DWORD cch = ARRAYSIZE(sz); HRESULT hr = _pqa->GetString(_flags | ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, NULL, sz, &cch); if (SUCCEEDED(hr)) { hr = SHStrDup(sz, ppsz); } return hr; }
HRESULT CAssocHandler::GetUIName(LPWSTR *ppsz) { WCHAR sz[MAX_PATH]; DWORD cch = ARRAYSIZE(sz); HRESULT hr = _pqa->GetString(_flags | ASSOCF_VERIFY, ASSOCSTR_FRIENDLYAPPNAME, NULL, sz, &cch); if (SUCCEEDED(hr)) { hr = SHStrDup(sz, ppsz); } return hr; } HRESULT CAssocHandler::GetIconLocation(LPWSTR *ppszPath, int *pIndex) { // HRESULT hr = _pqa->GetString(0, ASSOCSTR_DEFAULTAPPICON, NULL, psz, &cchT);
// if (FAILED(hr))
WCHAR sz[MAX_PATH]; DWORD cch = ARRAYSIZE(sz); HRESULT hr = _pqa->GetString(_flags | ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, NULL, sz, &cch); if (SUCCEEDED(hr)) { hr = SHStrDup(sz, ppszPath); if (*ppszPath) { *pIndex = PathParseIconLocation(*ppszPath); } } return hr; }
STDAPI OpenWithListRegister(DWORD dwFlags, LPCTSTR pszExt, LPCTSTR pszVerb, HKEY hkProgid);
HRESULT CAssocHandler::_InitKey() { if (!_hk) { return _pqa->GetKey(_flags, ASSOCKEY_SHELLEXECCLASS, NULL, &_hk); } return S_OK; }
void CAssocHandler::_RegisterOWL() { if (!_fRegistered && SUCCEEDED(_InitKey())) { OpenWithListRegister(0, _pszExt, NULL, _hk); _fRegistered = TRUE; } }
HRESULT CAssocHandler::Exec(HWND hwnd, LPCWSTR pszFile) { SHELLEXECUTEINFO ei = {0}; ei.cbSize = sizeof(ei); ei.hwnd = hwnd; ei.lpFile = pszFile; ei.nShow = SW_NORMAL; return _Exec(&ei); }
HRESULT CAssocHandler::_Exec(SHELLEXECUTEINFO *pei) { HRESULT hr = _InitKey(); if (SUCCEEDED(hr)) { pei->hkeyClass = _hk; pei->fMask |= SEE_MASK_CLASSKEY; if (ShellExecuteEx(pei)) { _RegisterOWL(); } else { hr = HRESULT_FROM_WIN32(GetLastError()); } } return hr; }
HRESULT CAssocHandler::Invoke(void *pici, PCWSTR pszFile) { SHELLEXECUTEINFO ei; HRESULT hr = ICIX2SEI((CMINVOKECOMMANDINFOEX *)pici, &ei); ei.lpFile = pszFile; if (SUCCEEDED(hr)) hr = _Exec(&ei);
return hr; }
BOOL CAssocHandler::_IsNewAssociation() { BOOL fRet = TRUE; WCHAR szOld[MAX_PATH]; WCHAR szNew[MAX_PATH]; if (SUCCEEDED(AssocQueryString(ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, _pszExt, NULL, szOld, (LPDWORD)MAKEINTRESOURCE(ARRAYSIZE(szOld)))) && SUCCEEDED(_pqa->GetString(ASSOCF_VERIFY | _flags, ASSOCSTR_EXECUTABLE, NULL, szNew, (LPDWORD)MAKEINTRESOURCE(ARRAYSIZE(szNew)))) && (0 == lstrcmpi(szNew, szOld))) { //
// 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; }
//
// 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 CAssocHandler::_GenerateAssociateNotify() { 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); } }
// return true if ok to continue
HRESULT CAssocHandler::MakeDefault(LPCWSTR pszDesc) { HRESULT hr = E_FAIL; // if the user is choosing the existing association
// or if we werent able to setup an Application ,
// then we want to leave it alone,
BOOL fForceUserCustomised = (AHTYPE_CURRENTDEFAULT == _type && S_FALSE == _pqa->GetData(0, ASSOCDATA_HASPERUSERASSOC, NULL, NULL, NULL)); if (fForceUserCustomised || _IsNewAssociation()) { switch (_type) { case AHTYPE_CURRENTDEFAULT: // if it is reverting to the machine default
// then we want to eliminate the user association
if (!fForceUserCustomised || !_pszInit) { hr = UserAssocSet(UASET_CLEAR, _pszExt, NULL); break; } // else fall through to AHTYPE_PROGID
// this supports overriding shimgvw's (and others?)
// dynamic contextmenu
case AHTYPE_PROGID: hr = UserAssocSet(UASET_PROGID, _pszExt, _pszInit); break;
case AHTYPE_APPLICATION: case AHTYPE_ANY_APPLICATION: case AHTYPE_USER_APPLICATION: // if there is a current association
// then we just customize the user portion
// otherwise we update
if (ERROR_SUCCESS == SHGetValue(HKEY_CLASSES_ROOT, _pszExt, NULL, NULL, NULL, NULL)) { // we don't overwrite the existing association under HKCR,
// instead, we put it under HKCU. So now shell knows the new association
// but third party software that mimics shell or does not use ShellExecute
// will still use the old association in HKCR, which may confuse users.
hr = UserAssocSet(UASET_APPLICATION, _pszExt, _pszInit); } else { if (SUCCEEDED(_InitKey())) { // there is no current progid
ASSERT(lstrlen(_pszExt) > 1); // because we always skip the "." below
WCHAR wszProgid[MAX_PATH]; WCHAR szExts[MAX_PATH]; int iLast = StrCatChainW(szExts, ARRAYSIZE(szExts) -1, 0, _pszExt); // double null term
szExts[++iLast] = 0; wnsprintfW(wszProgid, ARRAYSIZE(wszProgid), L"%ls_auto_file", _pszExt+1); HKEY hkDst; ASSOCPROGID apid = {sizeof(apid), wszProgid, pszDesc, NULL, NULL, szExts}; if (SUCCEEDED(AssocMakeProgid(0, _pszInit, &apid, &hkDst))) { hr = AssocCopyVerbs(_hk, hkDst); RegCloseKey(hkDst); } } } }
_GenerateAssociateNotify(); _RegisterOWL(); }
// if the application already
// existed, then it will
// return S_FALSE;
return (S_OK == hr); }
HRESULT _CreateAssocHandler(AHTYPE type, LPCWSTR pszExt, LPCWSTR pszInit, IAssocHandler **ppah) { CAssocHandler *pah = new CAssocHandler(); if (pah) { if (pah->Init(type, pszExt, pszInit)) { *ppah = pah; return S_OK; } else pah->Release(); } return E_FAIL; }
STDAPI SHCreateAssocHandler(LPCWSTR pszExt, LPCWSTR pszApp, IAssocHandler **ppah) { // path to app/handler
return _CreateAssocHandler(pszApp ? AHTYPE_USER_APPLICATION : AHTYPE_CURRENTDEFAULT, pszExt, pszApp, ppah); }
#define SZOPENWITHLIST TEXT("OpenWithList")
#define REGSTR_PATH_EXPLORER_FILEEXTS TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts")
#define _OpenWithListMaxItems() 10
class CMRUEnumHandlers { public: CMRUEnumHandlers() : _index(0) {} ~CMRUEnumHandlers() { FreeMRUList(_hmru);}
BOOL Init(LPCWSTR pszExt); BOOL Next(); LPCWSTR Curr() { return _szHandler;}
protected: HANDLE _hmru; int _index; WCHAR _szHandler[MAX_PATH]; };
BOOL CMRUEnumHandlers::Init(LPCWSTR pszExt) { TCHAR szSubKey[MAX_PATH]; // Build up the subkey string.
wnsprintf(szSubKey, SIZECHARS(szSubKey), TEXT("%s\\%s\\%s"), REGSTR_PATH_EXPLORER_FILEEXTS, pszExt, SZOPENWITHLIST);
MRUINFO mi = {sizeof(mi), _OpenWithListMaxItems(), 0, HKEY_CURRENT_USER, szSubKey, NULL};
_hmru = CreateMRUList(&mi); return (_hmru != NULL); }
BOOL CMRUEnumHandlers::Next() { ASSERT(_hmru); return (-1 != EnumMRUListW(_hmru, _index++, _szHandler, ARRAYSIZE(_szHandler))); }
typedef struct OPENWITHLIST { HKEY hk; DWORD dw; AHTYPE type; } OWL; class CEnumHandlers : public IEnumAssocHandlers { friend HRESULT SHAssocEnumHandlers(LPCTSTR pszExtra, IEnumAssocHandlers **ppEnumHandler);
public: // IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void);
// IEnumAssocHandlers methods
STDMETHODIMP Next(ULONG celt, IAssocHandler **rgelt, ULONG *pcelt);
protected: // methods
// Constructor & Destructor
CEnumHandlers() : _cRef(1) {} ~CEnumHandlers();
BOOL Init(LPCWSTR pszExt); BOOL _NextDefault(IAssocHandler **ppah); BOOL _NextHandler(HKEY hk, DWORD *pdw, BOOL fOpenWith, IAssocHandler **ppah); BOOL _NextProgid(HKEY *phk, DWORD *pdw, IAssocHandler **ppah); BOOL _NextMru(IAssocHandler **ppah); BOOL _NextOpenWithList(OWL *powl, IAssocHandler **ppah);
protected: // members
int _cRef; LPWSTR _pszExt; HKEY _hkProgids; DWORD _dwProgids; HKEY _hkUserProgids; DWORD _dwUserProgids; CMRUEnumHandlers _mru; BOOL _fMruReady; OWL _owlExt; OWL _owlType; OWL _owlAny; BOOL _fCheckedDefault; };
BOOL CEnumHandlers::Init(LPCWSTR pszExt) { _AddProgidForExt(pszExt); _pszExt = StrDup(pszExt); if (_pszExt) { // known progids
WCHAR szKey[MAX_PATH]; wnsprintf(szKey, ARRAYSIZE(szKey), L"%s\\OpenWithProgids", pszExt); RegOpenKeyEx(HKEY_CLASSES_ROOT, szKey, 0, MAXIMUM_ALLOWED, &_hkProgids); _hkUserProgids = SHGetShellKey(SHELLKEY_HKCU_FILEEXTS, szKey, FALSE); // user's MRU
_fMruReady = _mru.Init(pszExt); // HKCR\.ext\OpenWithList
wnsprintf(szKey, ARRAYSIZE(szKey), L"%s\\OpenWithList", pszExt); RegOpenKeyEx(HKEY_CLASSES_ROOT, szKey, 0, MAXIMUM_ALLOWED, &_owlExt.hk); _owlExt.type = AHTYPE_APPLICATION;
WCHAR sz[40]; DWORD cb = sizeof(sz); if (ERROR_SUCCESS == SHGetValue(HKEY_CLASSES_ROOT, pszExt, L"PerceivedType", NULL, sz, &cb)) { // HKCR\SystemFileAssociations\type\OpenWithList
wnsprintf(szKey, ARRAYSIZE(szKey), L"SystemFileAssociations\\%s\\OpenWithList", sz); RegOpenKeyEx(HKEY_CLASSES_ROOT, szKey, 0, MAXIMUM_ALLOWED, &_owlType.hk); } else { ASSERT(_owlType.hk == NULL); } _owlType.type = AHTYPE_APPLICATION;
// always append anytype to the end
RegOpenKeyEx(HKEY_CLASSES_ROOT, L"Applications", 0, MAXIMUM_ALLOWED, &_owlAny.hk); _owlAny.type = AHTYPE_ANY_APPLICATION;
return TRUE; } return FALSE; }
//
// CEnumHandlers implementation
//
CEnumHandlers::~CEnumHandlers() { if (_pszExt) LocalFree(_pszExt);
if (_hkProgids) RegCloseKey(_hkProgids);
if (_hkUserProgids) RegCloseKey(_hkUserProgids); if (_owlExt.hk) RegCloseKey(_owlExt.hk); if (_owlType.hk) RegCloseKey(_owlType.hk); if (_owlAny.hk) RegCloseKey(_owlAny.hk); }
STDAPI CEnumHandlers::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CEnumHandlers, IEnumAssocHandlers), };
return QISearch(this, qit, riid, ppvObj); }
STDAPI_(ULONG) CEnumHandlers::AddRef() { return ++_cRef; }
STDAPI_(ULONG) CEnumHandlers::Release() { if (--_cRef > 0) return _cRef;
delete this; return 0; }
BOOL CEnumHandlers::_NextDefault(IAssocHandler **ppah) { BOOL fRet = FALSE; if (!_fCheckedDefault && _pszExt) { WCHAR sz[MAX_PATH]; DWORD cb = sizeof(sz); // pass the progid if we have it
if (ERROR_SUCCESS != SHGetValue(HKEY_CLASSES_ROOT, _pszExt, NULL, NULL, sz, &cb)) *sz = 0;
fRet = SUCCEEDED(_CreateAssocHandler(AHTYPE_CURRENTDEFAULT, _pszExt, *sz ? sz : NULL, ppah)); _fCheckedDefault = TRUE; } return fRet; }
BOOL CEnumHandlers::_NextProgid(HKEY *phk, DWORD *pdw, IAssocHandler **ppah) { BOOL fRet = FALSE; while (*phk && !fRet) { TCHAR szProgid[MAX_PATH]; DWORD cchProgid = ARRAYSIZE(szProgid); DWORD err = RegEnumValue(*phk, *pdw, szProgid, &cchProgid, NULL, NULL, NULL, NULL);
if (ERROR_SUCCESS == err) { fRet = SUCCEEDED(_CreateAssocHandler(AHTYPE_PROGID, _pszExt, szProgid, ppah)); (*pdw)++; } else { RegCloseKey(*phk); *phk = NULL; } } return fRet; }
BOOL CEnumHandlers::_NextMru(IAssocHandler **ppah) { BOOL fRet = FALSE; while (_fMruReady && !fRet) { if (_mru.Next()) { fRet = SUCCEEDED(_CreateAssocHandler(AHTYPE_APPLICATION, _pszExt, _mru.Curr(), ppah)); } else { _fMruReady = FALSE; } } return fRet; }
BOOL CEnumHandlers::_NextOpenWithList(OWL *powl, IAssocHandler **ppah) { BOOL fRet = FALSE; while (powl->hk && !fRet) { TCHAR szHandler[MAX_PATH]; DWORD cchHandler = ARRAYSIZE(szHandler); DWORD err = RegEnumKeyEx(powl->hk, powl->dw, szHandler, &cchHandler, NULL, NULL, NULL, NULL);
if (err == ERROR_SUCCESS) { (powl->dw)++; fRet = SUCCEEDED(_CreateAssocHandler(powl->type, _pszExt, szHandler, ppah)); } else { RegCloseKey(powl->hk); powl->hk = NULL; } } return fRet; }
STDAPI CEnumHandlers::Next(ULONG celt, IAssocHandler **rgelt, ULONG *pcelt) { UINT cNum = 0; ZeroMemory(rgelt, sizeof(rgelt[0])*celt); while (cNum < celt && _NextDefault(&rgelt[cNum])) { cNum++; }
while (cNum < celt && _NextProgid(&_hkProgids, &_dwProgids, &rgelt[cNum])) { cNum++; }
while (cNum < celt && _NextProgid(&_hkUserProgids, &_dwUserProgids, &rgelt[cNum])) { cNum++; }
while (cNum < celt && _NextMru(&rgelt[cNum])) { cNum++; }
while (cNum < celt && _NextOpenWithList(&_owlExt, &rgelt[cNum])) { cNum++; }
while (cNum < celt && _NextOpenWithList(&_owlType, &rgelt[cNum])) { cNum++; }
while (cNum < celt && _NextOpenWithList(&_owlAny, &rgelt[cNum])) { cNum++; }
if (pcelt) *pcelt = cNum;
return (0 < cNum) ? S_OK: S_FALSE; }
//
// pszExtra: NULL - enumerate all handlers
// .xxx - enumerate handlers by file extension (we might internally map to content type)
// Others - not currently supported
//
STDAPI SHAssocEnumHandlers(LPCTSTR pszExt, IEnumAssocHandlers **ppenum) { HRESULT hr = E_OUTOFMEMORY; CEnumHandlers *penum = new CEnumHandlers();
*ppenum = NULL;
if (penum) { if (penum->Init(pszExt)) { *ppenum = penum; hr = S_OK; } else penum->Release(); } return hr; }
STDAPI_(BOOL) IsPathInOpenWithKillList(LPCTSTR pszPath) { // return TRUE for invalid path
if (!pszPath || !*pszPath) return TRUE;
// get file name
BOOL fRet = FALSE; LPCTSTR pchFile = PathFindFileName(pszPath); HKEY hkey;
// maybe should use full path for better resolution
if (ERROR_SUCCESS == _OpenApplicationKey(pchFile, &hkey)) { // just check for the existence of the value....
if (ERROR_SUCCESS == SHQueryValueEx(hkey, TEXT("NoOpenWith"), NULL, NULL, NULL, NULL)) { fRet = TRUE; }
RegCloseKey(hkey); }
LPWSTR pszKillList; if (!fRet && SUCCEEDED(SKAllocValue(SHELLKEY_HKLM_EXPLORER, L"FileAssociation", TEXT("KillList"), NULL, (void **)&pszKillList, NULL))) { fRet = _InList(pszKillList, pchFile, L';'); LocalFree(pszKillList); }
return fRet; }
|