Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1190 lines
32 KiB

  1. //
  2. // fassoc.cpp
  3. //
  4. // IQueryAssociations shell implementations
  5. //
  6. // New storage - move this to a simple database if possible
  7. //
  8. // ****************************** User Customizations ********************************
  9. //
  10. // HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts
  11. // |
  12. // |+ ".ext" // the extension that has been customized
  13. // | |- "Application" = "UserNotepad.AnyCo.1"
  14. // | |+ "OpenWithList" // MRU for the Open With ctx menu
  15. // |
  16. // _ ...
  17. //
  18. //
  19. // ****************************** NoRoam Store **************************
  20. //
  21. // HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\NoRoam
  22. // |
  23. // |+ ".ext" (the extension that has been customized)
  24. // | |- Application = "UserNotepad.AnyCo.1"
  25. // |
  26. // _ ...
  27. //
  28. // ***************************** Handlers **************************************
  29. // (store detailed per handler file association info)
  30. //
  31. // HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\NoRoam\Associations
  32. // |
  33. #include "shellprv.h"
  34. #include <shpriv.h>
  35. #include "clsobj.h"
  36. #include <shstr.h>
  37. #include <msi.h>
  38. #include "fassoc.h"
  39. #include <runtask.h>
  40. BOOL _PathAppend(LPCTSTR pszBase, LPCTSTR pszAppend, LPTSTR pszOut, DWORD cchOut)
  41. {
  42. DWORD cchBase = lstrlen(pszBase);
  43. // +1 is one for the whack
  44. if (cchOut > cchBase + lstrlen(pszAppend) + 1)
  45. {
  46. StrCpy(pszOut, pszBase);
  47. pszOut+=cchBase;
  48. *pszOut++ = TEXT('\\');
  49. StrCpy(pszOut, pszAppend);
  50. return TRUE;
  51. }
  52. return FALSE;
  53. }
  54. STDAPI UserAssocSet(UASET set, LPCWSTR pszExt, LPCWSTR pszSet)
  55. {
  56. HKEY hk = SHGetShellKey(SHELLKEY_HKCU_FILEEXTS, pszExt, TRUE);
  57. if (hk)
  58. {
  59. // we should always clear
  60. SHDeleteValue(hk, NULL, L"Application");
  61. SHDeleteValue(hk, NULL, L"Progid");
  62. switch (set)
  63. {
  64. case UASET_APPLICATION:
  65. SHSetValue(hk, NULL, L"Application", REG_SZ, pszSet, CbFromCch(lstrlen(pszSet)+1));
  66. break;
  67. case UASET_PROGID:
  68. SHSetValue(hk, NULL, L"Progid", REG_SZ, pszSet, CbFromCch(lstrlen(pszSet)+1));
  69. break;
  70. }
  71. RegCloseKey(hk);
  72. return S_OK;
  73. }
  74. return HRESULT_FROM_WIN32(GetLastError());
  75. }
  76. void _MakeApplicationsKey(LPCTSTR pszApp, LPTSTR pszKey, DWORD cchKey)
  77. {
  78. if (_PathAppend(TEXT("Applications"), pszApp, pszKey, cchKey))
  79. {
  80. // Currently we will only look up .EXE if an extension is not
  81. // specified
  82. if (*PathFindExtension(pszApp) == 0)
  83. {
  84. StrCatBuff(pszKey, TEXT(".exe"), cchKey);
  85. }
  86. }
  87. }
  88. DWORD _OpenApplicationKey(LPCWSTR pszApp, HKEY *phk, BOOL fCheckCommand = FALSE)
  89. {
  90. // look direct
  91. // then try indirecting
  92. // then try appending .exe
  93. WCHAR sz[MAX_PATH];
  94. _MakeApplicationsKey(pszApp, sz, ARRAYSIZE(sz));
  95. DWORD err = RegOpenKeyEx(HKEY_CLASSES_ROOT, sz, 0, MAXIMUM_ALLOWED, phk);
  96. if (err == ERROR_SUCCESS && fCheckCommand)
  97. {
  98. DWORD cch;
  99. if (ERROR_SUCCESS == SHQueryValueEx(*phk, TEXT("NoOpenWith"), NULL, NULL, NULL, NULL)
  100. || FAILED(AssocQueryStringByKey(0, ASSOCSTR_COMMAND, *phk, NULL, NULL, &cch)))
  101. {
  102. err = ERROR_ACCESS_DENIED;
  103. RegCloseKey(*phk);
  104. *phk = NULL;
  105. }
  106. }
  107. return err;
  108. }
  109. STDAPI UserAssocOpenKey(LPCWSTR pszExt, HKEY *phk)
  110. {
  111. HKEY hk = SHGetShellKey(SHELLKEY_HKCU_FILEEXTS, pszExt, FALSE);
  112. DWORD err = 0;
  113. if (hk)
  114. {
  115. WCHAR sz[MAX_PATH];
  116. DWORD cb = sizeof(sz);
  117. // first check for a progid
  118. err = SHGetValue(hk, NULL, L"Progid", NULL, sz, &cb);
  119. if (err == ERROR_SUCCESS)
  120. {
  121. err = RegOpenKeyEx(HKEY_CLASSES_ROOT, sz, 0, MAXIMUM_ALLOWED, phk);
  122. cb = sizeof(sz);
  123. // maybe need to map to CurVer??
  124. }
  125. if (err != ERROR_SUCCESS)
  126. {
  127. err = SHGetValue(hk, NULL, L"Application", NULL, sz, &cb);
  128. if (err == ERROR_SUCCESS)
  129. {
  130. err = _OpenApplicationKey(sz, phk);
  131. }
  132. }
  133. RegCloseKey(hk);
  134. }
  135. else
  136. err = GetLastError();
  137. return HRESULT_FROM_WIN32(err);
  138. }
  139. class CVersion
  140. {
  141. public:
  142. CVersion(LPCWSTR psz) : _pVer(0), _hrInit(S_FALSE) { StrCpyNW(_szPath, psz, ARRAYSIZE(_szPath)); }
  143. ~CVersion() { if (_pVer) LocalFree(_pVer); }
  144. HRESULT QueryStringValue(LPCWSTR pszValue, LPWSTR pszOut, DWORD cch);
  145. private:
  146. HRESULT _Init();
  147. HRESULT _QueryValue(WORD wLang, WORD wCP, LPCWSTR pszValue, LPWSTR pszOut, DWORD cch);
  148. WCHAR _szPath[MAX_PATH];
  149. void *_pVer;
  150. HRESULT _hrInit;
  151. };
  152. HRESULT CVersion::_Init()
  153. {
  154. if (_hrInit == S_FALSE)
  155. {
  156. _hrInit = E_FAIL;
  157. DWORD dwAttribs;
  158. if (PathFileExistsAndAttributes(_szPath, &dwAttribs))
  159. {
  160. // bail in the \\server, \\server\share, and directory case or else GetFileVersionInfo() will try
  161. // to do a LoadLibraryEx() on the path (which will fail, but not before we seach the entire include
  162. // path which can take a long time)
  163. if (!(dwAttribs & FILE_ATTRIBUTE_DIRECTORY)
  164. && !PathIsUNCServer(_szPath)
  165. && !PathIsUNCServerShare(_szPath))
  166. {
  167. DWORD dwHandle;
  168. DWORD cb = GetFileVersionInfoSizeW(_szPath, &dwHandle);
  169. if (cb)
  170. {
  171. _pVer = LocalAlloc(LPTR, cb);
  172. if (_pVer)
  173. {
  174. if (GetFileVersionInfoW(_szPath, dwHandle, cb, _pVer))
  175. _hrInit = S_OK;
  176. }
  177. }
  178. }
  179. }
  180. }
  181. return _hrInit;
  182. }
  183. inline BOOL IsAlphaDigit(WCHAR ch)
  184. {
  185. return ((ch >= L'0' && ch <= L'9')
  186. || (ch >= L'A' && ch <= L'Z')
  187. || (ch >= L'a' && ch <= L'z'));
  188. }
  189. typedef struct
  190. {
  191. WORD wLanguage;
  192. WORD wCodePage;
  193. } XLATE;
  194. const static XLATE s_px[] =
  195. {
  196. { 0, 0x04B0 }, // MLGetUILanguage, CP_UNICODE
  197. { 0, 0x04E4 }, // MLGetUILanguage, CP_USASCII
  198. { 0, 0x0000 }, // MLGetUILanguage, NULL
  199. { 0x0409, 0x04B0 }, // English, CP_UNICODE
  200. { 0x0409, 0x04E4 }, // English, CP_USASCII
  201. { 0x0409, 0x0000 }, // English, NULL
  202. // { 0x041D, 0x04B0 }, // Swedish, CP_UNICODE
  203. // { 0x0407, 0x04E4 }, // German, CP_USASCII
  204. };
  205. HRESULT CVersion::_QueryValue(WORD wLang, WORD wCP, LPCWSTR pszValue, LPWSTR pszOut, DWORD cchOut)
  206. {
  207. WCHAR szQuery[MAX_PATH];
  208. LPWSTR pszString;
  209. UINT cch;
  210. wnsprintfW(szQuery, ARRAYSIZE(szQuery), L"\\StringFileInfo\\%04X%04X\\%s",
  211. wLang, wCP, pszValue);
  212. if (VerQueryValue(_pVer, szQuery, (void **) &pszString, &cch)
  213. && cch && IsAlphaDigit(*pszString))
  214. {
  215. StrCpyN(pszOut, pszString, cchOut);
  216. return S_OK;
  217. }
  218. return E_FAIL;
  219. }
  220. HRESULT CVersion::QueryStringValue(LPCWSTR pszValue, LPWSTR pszOut, DWORD cchOut)
  221. {
  222. HRESULT hr = _Init();
  223. if (SUCCEEDED(hr))
  224. {
  225. hr = E_FAIL;
  226. for (int i = 0; FAILED(hr) && i < ARRAYSIZE(s_px); i++)
  227. {
  228. WORD wL = s_px[i].wLanguage ? s_px[i].wLanguage : MLGetUILanguage();
  229. hr = _QueryValue(wL, s_px[i].wCodePage, pszValue, pszOut, cchOut);
  230. }
  231. if (FAILED(hr))
  232. {
  233. // Try first language this supports
  234. XLATE *px;
  235. UINT cch;
  236. if (VerQueryValue(_pVer, TEXT("\\VarFileInfo\\Translation"), (void **)&px, &cch) && cch)
  237. {
  238. hr = _QueryValue(px[0].wLanguage, px[0].wCodePage, pszValue, pszOut, cchOut);
  239. }
  240. }
  241. }
  242. return hr;
  243. }
  244. void _TrimNonAlphaNum(LPWSTR psz)
  245. {
  246. while (*psz && IsAlphaDigit(*psz))
  247. psz++;
  248. *psz = 0;
  249. }
  250. HKEY _OpenSystemFileAssociationsKey(LPCWSTR pszExt)
  251. {
  252. WCHAR sz[MAX_PATH] = L"SystemFileAssociations\\";
  253. StrCatBuff(sz, pszExt, ARRAYSIZE(sz));
  254. HKEY hk = NULL;
  255. if (NOERROR != RegOpenKeyEx(HKEY_CLASSES_ROOT, sz, 0, MAXIMUM_ALLOWED, &hk))
  256. {
  257. DWORD cb = sizeof(sz) - sizeof(L"SystemFileAssociations\\");
  258. if (NOERROR == SHGetValue(HKEY_CLASSES_ROOT, pszExt, L"PerceivedType", NULL, sz+ARRAYSIZE(L"SystemFileAssociations\\")-1, &cb))
  259. {
  260. // if (PerceivedType != System)
  261. RegOpenKeyEx(HKEY_CLASSES_ROOT, sz, 0, MAXIMUM_ALLOWED, &hk);
  262. }
  263. }
  264. return hk;
  265. }
  266. BOOL _IsSystemFileAssociations(LPCWSTR pszExt)
  267. {
  268. HKEY hk = _OpenSystemFileAssociationsKey(pszExt);
  269. if (hk)
  270. RegCloseKey(hk);
  271. return hk != NULL;
  272. }
  273. class CTaskEnumHKCR : public CRunnableTask
  274. {
  275. public:
  276. CTaskEnumHKCR() : CRunnableTask(RTF_DEFAULT) {}
  277. // *** pure virtuals ***
  278. virtual STDMETHODIMP RunInitRT(void);
  279. private:
  280. virtual ~CTaskEnumHKCR() {}
  281. void _AddFromHKCR();
  282. };
  283. void _AddProgidForExt(LPCWSTR pszExt)
  284. {
  285. WCHAR szNew[MAX_PATH];
  286. DWORD cb = sizeof(szNew);
  287. if (NOERROR == SHGetValue(HKEY_CLASSES_ROOT, pszExt, NULL, NULL, szNew, &cb))
  288. {
  289. WCHAR sz[MAX_PATH];
  290. wnsprintf(sz, ARRAYSIZE(sz), L"%s\\OpenWithProgids", pszExt);
  291. SKSetValue(SHELLKEY_HKCU_FILEEXTS, sz, szNew, REG_NONE, NULL, NULL);
  292. }
  293. }
  294. #define IsExtension(s) (*(s) == TEXT('.'))
  295. void CTaskEnumHKCR::_AddFromHKCR()
  296. {
  297. int i;
  298. TCHAR szClass[MAX_PATH];
  299. BOOL fInExtensions = FALSE;
  300. for (i = 0; RegEnumKey(HKEY_CLASSES_ROOT, i, szClass, ARRAYSIZE(szClass)) == ERROR_SUCCESS; i++)
  301. {
  302. // UNDOCUMENTED feature. the enum is sorted,
  303. // so we can just restrict ourselves to extensions
  304. // for perf and fun!
  305. if (fInExtensions)
  306. {
  307. if (!IsExtension(szClass))
  308. break;
  309. }
  310. else if (IsExtension(szClass))
  311. {
  312. fInExtensions = TRUE;
  313. }
  314. else
  315. continue;
  316. if (_IsSystemFileAssociations(szClass))
  317. {
  318. _AddProgidForExt(szClass);
  319. }
  320. }
  321. }
  322. HRESULT CTaskEnumHKCR::RunInitRT()
  323. {
  324. // delete something??
  325. _AddFromHKCR();
  326. return S_OK;
  327. }
  328. STDAPI CTaskEnumHKCR_Create(IRunnableTask **pptask)
  329. {
  330. CTaskEnumHKCR *pteh = new CTaskEnumHKCR();
  331. if (pteh)
  332. {
  333. HRESULT hr = pteh->QueryInterface(IID_PPV_ARG(IRunnableTask, pptask));
  334. pteh->Release();
  335. return hr;
  336. }
  337. *pptask = NULL;
  338. return E_OUTOFMEMORY;
  339. }
  340. typedef enum
  341. {
  342. AHTYPE_USER_APPLICATION = -2,
  343. AHTYPE_ANY_APPLICATION = -1,
  344. AHTYPE_UNDEFINED = 0,
  345. AHTYPE_CURRENTDEFAULT,
  346. AHTYPE_PROGID,
  347. AHTYPE_APPLICATION,
  348. } AHTYPE;
  349. class CAssocHandler : public IAssocHandler
  350. {
  351. public:
  352. CAssocHandler() : _cRef(1) {}
  353. BOOL Init(AHTYPE type, LPCWSTR pszExt, LPCWSTR pszInit);
  354. // IUnknown methods
  355. STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
  356. STDMETHODIMP_(ULONG) AddRef(void);
  357. STDMETHODIMP_(ULONG) Release(void);
  358. // IAssocHandler methods
  359. STDMETHODIMP GetName(LPWSTR *ppsz);
  360. STDMETHODIMP GetUIName(LPWSTR *ppsz);
  361. STDMETHODIMP GetIconLocation(LPWSTR *ppszPath, int *pIndex);
  362. STDMETHODIMP IsRecommended() { return _type > AHTYPE_UNDEFINED ? S_OK : S_FALSE; }
  363. STDMETHODIMP MakeDefault(LPCWSTR pszDescription);
  364. STDMETHODIMP Exec(HWND hwnd, LPCWSTR pszFile);
  365. STDMETHODIMP Invoke(void *pici, PCWSTR pszFile);
  366. protected: // methods
  367. ~CAssocHandler();
  368. HRESULT _Exec(SHELLEXECUTEINFO *pei);
  369. BOOL _IsNewAssociation();
  370. void _GenerateAssociateNotify();
  371. HRESULT _InitKey();
  372. void _RegisterOWL();
  373. protected: // members
  374. ULONG _cRef;
  375. IQueryAssociations *_pqa;
  376. HKEY _hk;
  377. ASSOCF _flags;
  378. AHTYPE _type;
  379. LPWSTR _pszExt;
  380. LPWSTR _pszInit;
  381. BOOL _fRegistered;
  382. };
  383. STDAPI CAssocHandler::QueryInterface(REFIID riid, void **ppvObj)
  384. {
  385. static const QITAB qit[] = {
  386. QITABENT(CAssocHandler, IAssocHandler),
  387. };
  388. return QISearch(this, qit, riid, ppvObj);
  389. }
  390. STDAPI_(ULONG) CAssocHandler::AddRef()
  391. {
  392. return ++_cRef;
  393. }
  394. STDAPI_(ULONG) CAssocHandler::Release()
  395. {
  396. if (--_cRef > 0)
  397. return _cRef;
  398. delete this;
  399. return 0;
  400. }
  401. BOOL _InList(LPCWSTR pszList, LPCWSTR pszExt, WORD chDelim)
  402. {
  403. LPCWSTR pszMatch = StrStrI(pszList, pszExt);
  404. while (pszMatch)
  405. {
  406. LPCWSTR pszNext = (pszMatch+lstrlen(pszExt));
  407. if (chDelim == *pszNext || !*pszNext)
  408. return TRUE;
  409. pszMatch = StrStrI(pszNext+1, pszExt);
  410. }
  411. return FALSE;
  412. }
  413. // Create a new class key, and set its shell\open\command
  414. BOOL _CreateApplicationKey(LPCTSTR pszPath)
  415. {
  416. DWORD err = ERROR_FILE_NOT_FOUND;
  417. if (PathFileExistsAndAttributes(pszPath, NULL))
  418. {
  419. WCHAR szKey[MAX_PATH];
  420. WCHAR szCmd[MAX_PATH * 2];
  421. wnsprintf(szKey, ARRAYSIZE(szKey), L"Software\\Classes\\Applications\\%s\\shell\\open\\command", PathFindFileName(pszPath));
  422. // if it is not an LFN app, pass unquoted args.
  423. wnsprintf(szCmd, ARRAYSIZE(szCmd), App_IsLFNAware(pszPath) ? L"\"%s\" \"%%1\"" : L"\"%s\" %%1", pszPath);
  424. err = SHSetValue(HKEY_CURRENT_USER, szKey, NULL, REG_SZ, szCmd, CbFromCchW(lstrlen(szCmd)+1));
  425. }
  426. return ERROR_SUCCESS == err;
  427. }
  428. BOOL CAssocHandler::Init(AHTYPE type, LPCWSTR pszExt, LPCWSTR pszInit)
  429. {
  430. BOOL fRet = FALSE;
  431. _type = type;
  432. _pszExt = StrDup(pszExt);
  433. if (pszInit)
  434. _pszInit = StrDup(PathFindFileName(pszInit));
  435. if (_pszExt && (_pszInit || !pszInit))
  436. {
  437. if (SUCCEEDED(AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &_pqa))))
  438. {
  439. HKEY hk = NULL;
  440. _flags = ASSOCF_IGNOREBASECLASS;
  441. switch (type)
  442. {
  443. case AHTYPE_CURRENTDEFAULT:
  444. _flags |= ASSOCF_NOUSERSETTINGS;
  445. pszInit = pszExt;
  446. break;
  447. case AHTYPE_USER_APPLICATION:
  448. case AHTYPE_APPLICATION:
  449. case AHTYPE_ANY_APPLICATION:
  450. _OpenApplicationKey(_pszInit, &hk, TRUE);
  451. if (hk)
  452. {
  453. if (type == AHTYPE_APPLICATION)
  454. {
  455. // check if this type is supported
  456. HKEY hkTypes;
  457. if (ERROR_SUCCESS == RegOpenKeyEx(hk, TEXT("SupportedTypes"), 0, MAXIMUM_ALLOWED, &hkTypes))
  458. {
  459. // the app only supports specific types
  460. if (ERROR_SUCCESS != SHQueryValueEx(hkTypes, _pszExt, NULL, NULL, NULL, NULL))
  461. {
  462. // this type is not supported
  463. // so it will be relegated to the not recommended list
  464. RegCloseKey(hk);
  465. hk = NULL;
  466. }
  467. RegCloseKey(hkTypes);
  468. }
  469. }
  470. }
  471. else if (type == AHTYPE_USER_APPLICATION)
  472. {
  473. // need to make up a key
  474. if (_CreateApplicationKey(pszInit))
  475. _OpenApplicationKey(_pszInit, &hk);
  476. }
  477. pszInit = NULL;
  478. _flags |= ASSOCF_INIT_BYEXENAME;
  479. break;
  480. case AHTYPE_PROGID:
  481. default:
  482. // _flags |= ...;
  483. break;
  484. }
  485. if (hk || pszInit)
  486. {
  487. if (SUCCEEDED(_pqa->Init(_flags, pszInit , hk, NULL)))
  488. {
  489. WCHAR szExe[MAX_PATH];
  490. DWORD cchExe = ARRAYSIZE(szExe);
  491. // we want to make sure there is something at the other end
  492. fRet = SUCCEEDED(_pqa->GetString(ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, NULL, szExe, &cchExe));
  493. // however, if the EXE has been marked as superhidden,
  494. // then the consent decree UI has hidden the app
  495. // and it should not show up under the open with either
  496. if (fRet)
  497. {
  498. fRet = !(IS_SYSTEM_HIDDEN(GetFileAttributes(szExe)));
  499. }
  500. }
  501. }
  502. if (hk)
  503. RegCloseKey(hk);
  504. }
  505. }
  506. return fRet;
  507. }
  508. CAssocHandler::~CAssocHandler()
  509. {
  510. if (_pqa)
  511. _pqa->Release();
  512. if (_pszExt)
  513. LocalFree(_pszExt);
  514. if (_pszInit)
  515. LocalFree(_pszInit);
  516. if (_hk)
  517. RegCloseKey(_hk);
  518. }
  519. HRESULT CAssocHandler::GetName(LPWSTR *ppsz)
  520. {
  521. WCHAR sz[MAX_PATH];
  522. DWORD cch = ARRAYSIZE(sz);
  523. HRESULT hr = _pqa->GetString(_flags | ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, NULL, sz, &cch);
  524. if (SUCCEEDED(hr))
  525. {
  526. hr = SHStrDup(sz, ppsz);
  527. }
  528. return hr;
  529. }
  530. HRESULT CAssocHandler::GetUIName(LPWSTR *ppsz)
  531. {
  532. WCHAR sz[MAX_PATH];
  533. DWORD cch = ARRAYSIZE(sz);
  534. HRESULT hr = _pqa->GetString(_flags | ASSOCF_VERIFY, ASSOCSTR_FRIENDLYAPPNAME, NULL, sz, &cch);
  535. if (SUCCEEDED(hr))
  536. {
  537. hr = SHStrDup(sz, ppsz);
  538. }
  539. return hr;
  540. }
  541. HRESULT CAssocHandler::GetIconLocation(LPWSTR *ppszPath, int *pIndex)
  542. {
  543. // HRESULT hr = _pqa->GetString(0, ASSOCSTR_DEFAULTAPPICON, NULL, psz, &cchT);
  544. // if (FAILED(hr))
  545. WCHAR sz[MAX_PATH];
  546. DWORD cch = ARRAYSIZE(sz);
  547. HRESULT hr = _pqa->GetString(_flags | ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, NULL, sz, &cch);
  548. if (SUCCEEDED(hr))
  549. {
  550. hr = SHStrDup(sz, ppszPath);
  551. if (*ppszPath)
  552. {
  553. *pIndex = PathParseIconLocation(*ppszPath);
  554. }
  555. }
  556. return hr;
  557. }
  558. STDAPI OpenWithListRegister(DWORD dwFlags, LPCTSTR pszExt, LPCTSTR pszVerb, HKEY hkProgid);
  559. HRESULT CAssocHandler::_InitKey()
  560. {
  561. if (!_hk)
  562. {
  563. return _pqa->GetKey(_flags, ASSOCKEY_SHELLEXECCLASS, NULL, &_hk);
  564. }
  565. return S_OK;
  566. }
  567. void CAssocHandler::_RegisterOWL()
  568. {
  569. if (!_fRegistered && SUCCEEDED(_InitKey()))
  570. {
  571. OpenWithListRegister(0, _pszExt, NULL, _hk);
  572. _fRegistered = TRUE;
  573. }
  574. }
  575. HRESULT CAssocHandler::Exec(HWND hwnd, LPCWSTR pszFile)
  576. {
  577. SHELLEXECUTEINFO ei = {0};
  578. ei.cbSize = sizeof(ei);
  579. ei.hwnd = hwnd;
  580. ei.lpFile = pszFile;
  581. ei.nShow = SW_NORMAL;
  582. return _Exec(&ei);
  583. }
  584. HRESULT CAssocHandler::_Exec(SHELLEXECUTEINFO *pei)
  585. {
  586. HRESULT hr = _InitKey();
  587. if (SUCCEEDED(hr))
  588. {
  589. pei->hkeyClass = _hk;
  590. pei->fMask |= SEE_MASK_CLASSKEY;
  591. if (ShellExecuteEx(pei))
  592. {
  593. _RegisterOWL();
  594. }
  595. else
  596. {
  597. hr = HRESULT_FROM_WIN32(GetLastError());
  598. }
  599. }
  600. return hr;
  601. }
  602. HRESULT CAssocHandler::Invoke(void *pici, PCWSTR pszFile)
  603. {
  604. SHELLEXECUTEINFO ei;
  605. HRESULT hr = ICIX2SEI((CMINVOKECOMMANDINFOEX *)pici, &ei);
  606. ei.lpFile = pszFile;
  607. if (SUCCEEDED(hr))
  608. hr = _Exec(&ei);
  609. return hr;
  610. }
  611. BOOL CAssocHandler::_IsNewAssociation()
  612. {
  613. BOOL fRet = TRUE;
  614. WCHAR szOld[MAX_PATH];
  615. WCHAR szNew[MAX_PATH];
  616. if (SUCCEEDED(AssocQueryString(ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, _pszExt, NULL, szOld, (LPDWORD)MAKEINTRESOURCE(ARRAYSIZE(szOld))))
  617. && SUCCEEDED(_pqa->GetString(ASSOCF_VERIFY | _flags, ASSOCSTR_EXECUTABLE, NULL, szNew, (LPDWORD)MAKEINTRESOURCE(ARRAYSIZE(szNew))))
  618. && (0 == lstrcmpi(szNew, szOld)))
  619. {
  620. //
  621. // these have the same executable, trust
  622. // that when the exe installed itself, it did
  623. // it correctly, and we dont need to overwrite
  624. // their associations with themselves :)
  625. //
  626. fRet = FALSE;
  627. }
  628. return fRet;
  629. }
  630. //
  631. // This is a real hack, but for now we generate an idlist that looks
  632. // something like: C:\*.ext which is the extension for the IDList.
  633. // We use the simple IDList as to not hit the disk...
  634. //
  635. void CAssocHandler::_GenerateAssociateNotify()
  636. {
  637. TCHAR szFakePath[MAX_PATH];
  638. LPITEMIDLIST pidl;
  639. GetWindowsDirectory(szFakePath, ARRAYSIZE(szFakePath));
  640. lstrcpy(szFakePath + 3, c_szStar); // "C:\*"
  641. lstrcat(szFakePath, _pszExt); // "C:\*.foo"
  642. pidl = SHSimpleIDListFromPath(szFakePath);
  643. if (pidl)
  644. {
  645. // Now call off to the notify function.
  646. SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, pidl, NULL);
  647. ILFree(pidl);
  648. }
  649. }
  650. // return true if ok to continue
  651. HRESULT CAssocHandler::MakeDefault(LPCWSTR pszDesc)
  652. {
  653. HRESULT hr = E_FAIL;
  654. // if the user is choosing the existing association
  655. // or if we werent able to setup an Application ,
  656. // then we want to leave it alone,
  657. BOOL fForceUserCustomised = (AHTYPE_CURRENTDEFAULT == _type && S_FALSE == _pqa->GetData(0, ASSOCDATA_HASPERUSERASSOC, NULL, NULL, NULL));
  658. if (fForceUserCustomised || _IsNewAssociation())
  659. {
  660. switch (_type)
  661. {
  662. case AHTYPE_CURRENTDEFAULT:
  663. // if it is reverting to the machine default
  664. // then we want to eliminate the user association
  665. if (!fForceUserCustomised || !_pszInit)
  666. {
  667. hr = UserAssocSet(UASET_CLEAR, _pszExt, NULL);
  668. break;
  669. }
  670. // else fall through to AHTYPE_PROGID
  671. // this supports overriding shimgvw's (and others?)
  672. // dynamic contextmenu
  673. case AHTYPE_PROGID:
  674. hr = UserAssocSet(UASET_PROGID, _pszExt, _pszInit);
  675. break;
  676. case AHTYPE_APPLICATION:
  677. case AHTYPE_ANY_APPLICATION:
  678. case AHTYPE_USER_APPLICATION:
  679. // if there is a current association
  680. // then we just customize the user portion
  681. // otherwise we update
  682. if (ERROR_SUCCESS == SHGetValue(HKEY_CLASSES_ROOT, _pszExt, NULL, NULL, NULL, NULL))
  683. {
  684. // we don't overwrite the existing association under HKCR,
  685. // instead, we put it under HKCU. So now shell knows the new association
  686. // but third party software that mimics shell or does not use ShellExecute
  687. // will still use the old association in HKCR, which may confuse users.
  688. hr = UserAssocSet(UASET_APPLICATION, _pszExt, _pszInit);
  689. }
  690. else
  691. {
  692. if (SUCCEEDED(_InitKey()))
  693. {
  694. // there is no current progid
  695. ASSERT(lstrlen(_pszExt) > 1); // because we always skip the "." below
  696. WCHAR wszProgid[MAX_PATH];
  697. WCHAR szExts[MAX_PATH];
  698. int iLast = StrCatChainW(szExts, ARRAYSIZE(szExts) -1, 0, _pszExt);
  699. // double null term
  700. szExts[++iLast] = 0;
  701. wnsprintfW(wszProgid, ARRAYSIZE(wszProgid), L"%ls_auto_file", _pszExt+1);
  702. HKEY hkDst;
  703. ASSOCPROGID apid = {sizeof(apid), wszProgid, pszDesc, NULL, NULL, szExts};
  704. if (SUCCEEDED(AssocMakeProgid(0, _pszInit, &apid, &hkDst)))
  705. {
  706. hr = AssocCopyVerbs(_hk, hkDst);
  707. RegCloseKey(hkDst);
  708. }
  709. }
  710. }
  711. }
  712. _GenerateAssociateNotify();
  713. _RegisterOWL();
  714. }
  715. // if the application already
  716. // existed, then it will
  717. // return S_FALSE;
  718. return (S_OK == hr);
  719. }
  720. HRESULT _CreateAssocHandler(AHTYPE type, LPCWSTR pszExt, LPCWSTR pszInit, IAssocHandler **ppah)
  721. {
  722. CAssocHandler *pah = new CAssocHandler();
  723. if (pah)
  724. {
  725. if (pah->Init(type, pszExt, pszInit))
  726. {
  727. *ppah = pah;
  728. return S_OK;
  729. }
  730. else
  731. pah->Release();
  732. }
  733. return E_FAIL;
  734. }
  735. STDAPI SHCreateAssocHandler(LPCWSTR pszExt, LPCWSTR pszApp, IAssocHandler **ppah)
  736. {
  737. // path to app/handler
  738. return _CreateAssocHandler(pszApp ? AHTYPE_USER_APPLICATION : AHTYPE_CURRENTDEFAULT, pszExt, pszApp, ppah);
  739. }
  740. #define SZOPENWITHLIST TEXT("OpenWithList")
  741. #define REGSTR_PATH_EXPLORER_FILEEXTS TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts")
  742. #define _OpenWithListMaxItems() 10
  743. class CMRUEnumHandlers
  744. {
  745. public:
  746. CMRUEnumHandlers() : _index(0) {}
  747. ~CMRUEnumHandlers() { FreeMRUList(_hmru);}
  748. BOOL Init(LPCWSTR pszExt);
  749. BOOL Next();
  750. LPCWSTR Curr() { return _szHandler;}
  751. protected:
  752. HANDLE _hmru;
  753. int _index;
  754. WCHAR _szHandler[MAX_PATH];
  755. };
  756. BOOL CMRUEnumHandlers::Init(LPCWSTR pszExt)
  757. {
  758. TCHAR szSubKey[MAX_PATH];
  759. // Build up the subkey string.
  760. wnsprintf(szSubKey, SIZECHARS(szSubKey), TEXT("%s\\%s\\%s"), REGSTR_PATH_EXPLORER_FILEEXTS, pszExt, SZOPENWITHLIST);
  761. MRUINFO mi = {sizeof(mi), _OpenWithListMaxItems(), 0, HKEY_CURRENT_USER, szSubKey, NULL};
  762. _hmru = CreateMRUList(&mi);
  763. return (_hmru != NULL);
  764. }
  765. BOOL CMRUEnumHandlers::Next()
  766. {
  767. ASSERT(_hmru);
  768. return (-1 != EnumMRUListW(_hmru, _index++, _szHandler, ARRAYSIZE(_szHandler)));
  769. }
  770. typedef struct OPENWITHLIST
  771. {
  772. HKEY hk;
  773. DWORD dw;
  774. AHTYPE type;
  775. } OWL;
  776. class CEnumHandlers : public IEnumAssocHandlers
  777. {
  778. friend HRESULT SHAssocEnumHandlers(LPCTSTR pszExtra, IEnumAssocHandlers **ppEnumHandler);
  779. public:
  780. // IUnknown methods
  781. STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
  782. STDMETHODIMP_(ULONG) AddRef(void);
  783. STDMETHODIMP_(ULONG) Release(void);
  784. // IEnumAssocHandlers methods
  785. STDMETHODIMP Next(ULONG celt, IAssocHandler **rgelt, ULONG *pcelt);
  786. protected: // methods
  787. // Constructor & Destructor
  788. CEnumHandlers() : _cRef(1) {}
  789. ~CEnumHandlers();
  790. BOOL Init(LPCWSTR pszExt);
  791. BOOL _NextDefault(IAssocHandler **ppah);
  792. BOOL _NextHandler(HKEY hk, DWORD *pdw, BOOL fOpenWith, IAssocHandler **ppah);
  793. BOOL _NextProgid(HKEY *phk, DWORD *pdw, IAssocHandler **ppah);
  794. BOOL _NextMru(IAssocHandler **ppah);
  795. BOOL _NextOpenWithList(OWL *powl, IAssocHandler **ppah);
  796. protected: // members
  797. int _cRef;
  798. LPWSTR _pszExt;
  799. HKEY _hkProgids;
  800. DWORD _dwProgids;
  801. HKEY _hkUserProgids;
  802. DWORD _dwUserProgids;
  803. CMRUEnumHandlers _mru;
  804. BOOL _fMruReady;
  805. OWL _owlExt;
  806. OWL _owlType;
  807. OWL _owlAny;
  808. BOOL _fCheckedDefault;
  809. };
  810. BOOL CEnumHandlers::Init(LPCWSTR pszExt)
  811. {
  812. _AddProgidForExt(pszExt);
  813. _pszExt = StrDup(pszExt);
  814. if (_pszExt)
  815. {
  816. // known progids
  817. WCHAR szKey[MAX_PATH];
  818. wnsprintf(szKey, ARRAYSIZE(szKey), L"%s\\OpenWithProgids", pszExt);
  819. RegOpenKeyEx(HKEY_CLASSES_ROOT, szKey, 0, MAXIMUM_ALLOWED, &_hkProgids);
  820. _hkUserProgids = SHGetShellKey(SHELLKEY_HKCU_FILEEXTS, szKey, FALSE);
  821. // user's MRU
  822. _fMruReady = _mru.Init(pszExt);
  823. // HKCR\.ext\OpenWithList
  824. wnsprintf(szKey, ARRAYSIZE(szKey), L"%s\\OpenWithList", pszExt);
  825. RegOpenKeyEx(HKEY_CLASSES_ROOT, szKey, 0, MAXIMUM_ALLOWED, &_owlExt.hk);
  826. _owlExt.type = AHTYPE_APPLICATION;
  827. WCHAR sz[40];
  828. DWORD cb = sizeof(sz);
  829. if (ERROR_SUCCESS == SHGetValue(HKEY_CLASSES_ROOT, pszExt, L"PerceivedType", NULL, sz, &cb))
  830. {
  831. // HKCR\SystemFileAssociations\type\OpenWithList
  832. wnsprintf(szKey, ARRAYSIZE(szKey), L"SystemFileAssociations\\%s\\OpenWithList", sz);
  833. RegOpenKeyEx(HKEY_CLASSES_ROOT, szKey, 0, MAXIMUM_ALLOWED, &_owlType.hk);
  834. }
  835. else
  836. {
  837. ASSERT(_owlType.hk == NULL);
  838. }
  839. _owlType.type = AHTYPE_APPLICATION;
  840. // always append anytype to the end
  841. RegOpenKeyEx(HKEY_CLASSES_ROOT, L"Applications", 0, MAXIMUM_ALLOWED, &_owlAny.hk);
  842. _owlAny.type = AHTYPE_ANY_APPLICATION;
  843. return TRUE;
  844. }
  845. return FALSE;
  846. }
  847. //
  848. // CEnumHandlers implementation
  849. //
  850. CEnumHandlers::~CEnumHandlers()
  851. {
  852. if (_pszExt)
  853. LocalFree(_pszExt);
  854. if (_hkProgids)
  855. RegCloseKey(_hkProgids);
  856. if (_hkUserProgids)
  857. RegCloseKey(_hkUserProgids);
  858. if (_owlExt.hk)
  859. RegCloseKey(_owlExt.hk);
  860. if (_owlType.hk)
  861. RegCloseKey(_owlType.hk);
  862. if (_owlAny.hk)
  863. RegCloseKey(_owlAny.hk);
  864. }
  865. STDAPI CEnumHandlers::QueryInterface(REFIID riid, void **ppvObj)
  866. {
  867. static const QITAB qit[] = {
  868. QITABENT(CEnumHandlers, IEnumAssocHandlers),
  869. };
  870. return QISearch(this, qit, riid, ppvObj);
  871. }
  872. STDAPI_(ULONG) CEnumHandlers::AddRef()
  873. {
  874. return ++_cRef;
  875. }
  876. STDAPI_(ULONG) CEnumHandlers::Release()
  877. {
  878. if (--_cRef > 0)
  879. return _cRef;
  880. delete this;
  881. return 0;
  882. }
  883. BOOL CEnumHandlers::_NextDefault(IAssocHandler **ppah)
  884. {
  885. BOOL fRet = FALSE;
  886. if (!_fCheckedDefault && _pszExt)
  887. {
  888. WCHAR sz[MAX_PATH];
  889. DWORD cb = sizeof(sz);
  890. // pass the progid if we have it
  891. if (ERROR_SUCCESS != SHGetValue(HKEY_CLASSES_ROOT, _pszExt, NULL, NULL, sz, &cb))
  892. *sz = 0;
  893. fRet = SUCCEEDED(_CreateAssocHandler(AHTYPE_CURRENTDEFAULT, _pszExt, *sz ? sz : NULL, ppah));
  894. _fCheckedDefault = TRUE;
  895. }
  896. return fRet;
  897. }
  898. BOOL CEnumHandlers::_NextProgid(HKEY *phk, DWORD *pdw, IAssocHandler **ppah)
  899. {
  900. BOOL fRet = FALSE;
  901. while (*phk && !fRet)
  902. {
  903. TCHAR szProgid[MAX_PATH];
  904. DWORD cchProgid = ARRAYSIZE(szProgid);
  905. DWORD err = RegEnumValue(*phk, *pdw, szProgid, &cchProgid, NULL, NULL, NULL, NULL);
  906. if (ERROR_SUCCESS == err)
  907. {
  908. fRet = SUCCEEDED(_CreateAssocHandler(AHTYPE_PROGID, _pszExt, szProgid, ppah));
  909. (*pdw)++;
  910. }
  911. else
  912. {
  913. RegCloseKey(*phk);
  914. *phk = NULL;
  915. }
  916. }
  917. return fRet;
  918. }
  919. BOOL CEnumHandlers::_NextMru(IAssocHandler **ppah)
  920. {
  921. BOOL fRet = FALSE;
  922. while (_fMruReady && !fRet)
  923. {
  924. if (_mru.Next())
  925. {
  926. fRet = SUCCEEDED(_CreateAssocHandler(AHTYPE_APPLICATION, _pszExt, _mru.Curr(), ppah));
  927. }
  928. else
  929. {
  930. _fMruReady = FALSE;
  931. }
  932. }
  933. return fRet;
  934. }
  935. BOOL CEnumHandlers::_NextOpenWithList(OWL *powl, IAssocHandler **ppah)
  936. {
  937. BOOL fRet = FALSE;
  938. while (powl->hk && !fRet)
  939. {
  940. TCHAR szHandler[MAX_PATH];
  941. DWORD cchHandler = ARRAYSIZE(szHandler);
  942. DWORD err = RegEnumKeyEx(powl->hk, powl->dw, szHandler, &cchHandler, NULL, NULL, NULL, NULL);
  943. if (err == ERROR_SUCCESS)
  944. {
  945. (powl->dw)++;
  946. fRet = SUCCEEDED(_CreateAssocHandler(powl->type, _pszExt, szHandler, ppah));
  947. }
  948. else
  949. {
  950. RegCloseKey(powl->hk);
  951. powl->hk = NULL;
  952. }
  953. }
  954. return fRet;
  955. }
  956. STDAPI CEnumHandlers::Next(ULONG celt, IAssocHandler **rgelt, ULONG *pcelt)
  957. {
  958. UINT cNum = 0;
  959. ZeroMemory(rgelt, sizeof(rgelt[0])*celt);
  960. while (cNum < celt && _NextDefault(&rgelt[cNum]))
  961. {
  962. cNum++;
  963. }
  964. while (cNum < celt && _NextProgid(&_hkProgids, &_dwProgids, &rgelt[cNum]))
  965. {
  966. cNum++;
  967. }
  968. while (cNum < celt && _NextProgid(&_hkUserProgids, &_dwUserProgids, &rgelt[cNum]))
  969. {
  970. cNum++;
  971. }
  972. while (cNum < celt && _NextMru(&rgelt[cNum]))
  973. {
  974. cNum++;
  975. }
  976. while (cNum < celt && _NextOpenWithList(&_owlExt, &rgelt[cNum]))
  977. {
  978. cNum++;
  979. }
  980. while (cNum < celt && _NextOpenWithList(&_owlType, &rgelt[cNum]))
  981. {
  982. cNum++;
  983. }
  984. while (cNum < celt && _NextOpenWithList(&_owlAny, &rgelt[cNum]))
  985. {
  986. cNum++;
  987. }
  988. if (pcelt)
  989. *pcelt = cNum;
  990. return (0 < cNum) ? S_OK: S_FALSE;
  991. }
  992. //
  993. // pszExtra: NULL - enumerate all handlers
  994. // .xxx - enumerate handlers by file extension (we might internally map to content type)
  995. // Others - not currently supported
  996. //
  997. STDAPI SHAssocEnumHandlers(LPCTSTR pszExt, IEnumAssocHandlers **ppenum)
  998. {
  999. HRESULT hr = E_OUTOFMEMORY;
  1000. CEnumHandlers *penum = new CEnumHandlers();
  1001. *ppenum = NULL;
  1002. if (penum)
  1003. {
  1004. if (penum->Init(pszExt))
  1005. {
  1006. *ppenum = penum;
  1007. hr = S_OK;
  1008. }
  1009. else
  1010. penum->Release();
  1011. }
  1012. return hr;
  1013. }
  1014. STDAPI_(BOOL) IsPathInOpenWithKillList(LPCTSTR pszPath)
  1015. {
  1016. // return TRUE for invalid path
  1017. if (!pszPath || !*pszPath)
  1018. return TRUE;
  1019. // get file name
  1020. BOOL fRet = FALSE;
  1021. LPCTSTR pchFile = PathFindFileName(pszPath);
  1022. HKEY hkey;
  1023. // maybe should use full path for better resolution
  1024. if (ERROR_SUCCESS == _OpenApplicationKey(pchFile, &hkey))
  1025. {
  1026. // just check for the existence of the value....
  1027. if (ERROR_SUCCESS == SHQueryValueEx(hkey, TEXT("NoOpenWith"), NULL, NULL, NULL, NULL))
  1028. {
  1029. fRet = TRUE;
  1030. }
  1031. RegCloseKey(hkey);
  1032. }
  1033. LPWSTR pszKillList;
  1034. if (!fRet && SUCCEEDED(SKAllocValue(SHELLKEY_HKLM_EXPLORER, L"FileAssociation", TEXT("KillList"), NULL, (void **)&pszKillList, NULL)))
  1035. {
  1036. fRet = _InList(pszKillList, pchFile, L';');
  1037. LocalFree(pszKillList);
  1038. }
  1039. return fRet;
  1040. }