Leaked source code of windows server 2003
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.

3947 lines
117 KiB

  1. #include "stdafx.h"
  2. #include "proglist.h"
  3. #include "uemapp.h"
  4. #include <shdguid.h>
  5. #include "shguidp.h" // IID_IInitializeObject
  6. #include <pshpack4.h>
  7. #include <idhidden.h> // Note! idhidden.h requires pack4
  8. #include <poppack.h>
  9. #include <userenv.h> // GetProfileType
  10. #include <desktray.h>
  11. #include "tray.h"
  12. #define STRSAFE_NO_CB_FUNCTIONS
  13. #define STRSAFE_NO_DEPRECATE
  14. #include <strsafe.h>
  15. // Global cache item being passed from the background task to the start panel ByUsage.
  16. CMenuItemsCache *g_pMenuCache;
  17. // From startmnu.cpp
  18. HRESULT Tray_RegisterHotKey(WORD wHotKey, LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidl);
  19. #define TF_PROGLIST 0x00000020
  20. #define CH_DARWINMARKER TEXT('\1') // Illegal filename character
  21. #define IsDarwinPath(psz) ((psz)[0] == CH_DARWINMARKER)
  22. // Largest date representable in FILETIME - rolls over in the year 30828
  23. static const FILETIME c_ftNever = { 0xFFFFFFFF, 0x7FFFFFFF };
  24. void GetStartTime(FILETIME *pft)
  25. {
  26. //
  27. // If policy says "Don't offer new apps", then set the New App
  28. // threshold to some impossible time in the future.
  29. //
  30. if (!SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, REGSTR_VAL_DV2_NOTIFYNEW, FALSE, TRUE))
  31. {
  32. *pft = c_ftNever;
  33. return;
  34. }
  35. FILETIME ftNow;
  36. GetSystemTimeAsFileTime(&ftNow);
  37. DWORD dwSize = sizeof(*pft);
  38. //Check to see if we have the StartMenu start Time for this user saved in the registry.
  39. if(SHRegGetUSValue(DV2_REGPATH, DV2_SYSTEM_START_TIME, NULL,
  40. pft, &dwSize, FALSE, NULL, 0) != ERROR_SUCCESS)
  41. {
  42. // Get the current system time as the start time. If any app is launched after
  43. // this time, that will result in the OOBE message going away!
  44. *pft = ftNow;
  45. dwSize = sizeof(*pft);
  46. //Save this time as the StartMenu start time for this user.
  47. SHRegSetUSValue(DV2_REGPATH, DV2_SYSTEM_START_TIME, REG_BINARY,
  48. pft, dwSize, SHREGSET_FORCE_HKCU);
  49. }
  50. //
  51. // Thanks to roaming and reinstalls, the user may have installed a new
  52. // OS since they first turned on the Start Menu, so bump forwards to
  53. // the time the OS was installed (plus some fudge to account for the
  54. // time it takes to run Setup) so all the Accessories don't get marked
  55. // as "New".
  56. //
  57. DWORD dwTime;
  58. dwSize = sizeof(dwTime);
  59. if (SHGetValue(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion"),
  60. TEXT("InstallDate"), NULL, &dwTime, &dwSize) == ERROR_SUCCESS)
  61. {
  62. // Sigh, InstallDate is stored in UNIX time format, not FILETIME,
  63. // so convert it to FILETIME. Q167296 shows how to do this.
  64. LONGLONG ll = Int32x32To64(dwTime, 10000000) + 116444736000000000;
  65. // Add some fudge to account for how long it takes to run Setup
  66. ll += FT_ONEHOUR * 5; // five hours should be plenty
  67. FILETIME ft;
  68. SetFILETIMEfromInt64(&ft, ll);
  69. //
  70. // But don't jump forwards into the future
  71. //
  72. if (::CompareFileTime(&ft, &ftNow) > 0)
  73. {
  74. ft = ftNow;
  75. }
  76. if (::CompareFileTime(pft, &ft) < 0)
  77. {
  78. *pft = ft;
  79. }
  80. }
  81. //
  82. // If this is a roaming profile, then don't count anything that
  83. // happened before the user logged on because we don't want to mark
  84. // apps as new just because it roamed in with the user at logon.
  85. // We actually key off of the start time of Explorer, because
  86. // Explorer starts after the profiles are sync'd so we don't need
  87. // a fudge factor.
  88. //
  89. DWORD dwType;
  90. if (GetProfileType(&dwType) && (dwType & (PT_TEMPORARY | PT_ROAMING)))
  91. {
  92. FILETIME ft, ftIgnore;
  93. if (GetProcessTimes(GetCurrentProcess(), &ft, &ftIgnore, &ftIgnore, &ftIgnore))
  94. {
  95. if (::CompareFileTime(pft, &ft) < 0)
  96. {
  97. *pft = ft;
  98. }
  99. }
  100. }
  101. }
  102. //****************************************************************************
  103. //
  104. // How the pieces fit together...
  105. //
  106. //
  107. // Each ByUsageRoot consists of a ByUsageShortcutList.
  108. //
  109. // A ByUsageShortcutList is a list of shortcuts.
  110. //
  111. // Each shortcut references a ByUsageAppInfo. Multiple shortcuts can
  112. // reference the same ByUsageAppInfo if they are all shortcuts to the same
  113. // app.
  114. //
  115. // The ByUsageAppInfo::_cRef is the number of ByUsageShortcut's that
  116. // reference it.
  117. //
  118. // A master list of all ByUsageAppInfo's is kept in _dpaAppInfo.
  119. //
  120. //****************************************************************************
  121. //
  122. // Helper template for destroying DPA's.
  123. //
  124. // DPADELETEANDDESTROY(dpa);
  125. //
  126. // invokes the "delete" method on each pointer in the DPA,
  127. // then destroys the DPA.
  128. //
  129. template<class T>
  130. BOOL CALLBACK _DeleteCB(T *self, LPVOID)
  131. {
  132. delete self;
  133. return TRUE;
  134. }
  135. template<class T>
  136. void DPADELETEANDDESTROY(CDPA<T> &dpa)
  137. {
  138. if (dpa)
  139. {
  140. dpa.DestroyCallback(_DeleteCB<T>, NULL);
  141. ASSERT(dpa == NULL);
  142. }
  143. }
  144. //****************************************************************************
  145. void ByUsageRoot::Reset()
  146. {
  147. DPADELETEANDDESTROY(_sl);
  148. DPADELETEANDDESTROY(_slOld);
  149. ILFree(_pidl);
  150. _pidl = NULL;
  151. };
  152. //****************************************************************************
  153. class ByUsageDir
  154. {
  155. IShellFolder *_psf; // the folder interface
  156. LPITEMIDLIST _pidl; // the absolute pidl for this folder
  157. LONG _cRef; // Reference count
  158. ByUsageDir() : _cRef(1) { }
  159. ~ByUsageDir() { ATOMICRELEASE(_psf); ILFree(_pidl); }
  160. public:
  161. // Creates a ByUsageDir for CSIDL_DESKTOP. All other
  162. // ByUsageDir's come from this.
  163. static ByUsageDir *CreateDesktop()
  164. {
  165. ByUsageDir *self = new ByUsageDir();
  166. if (self)
  167. {
  168. ASSERT(self->_pidl == NULL);
  169. if (SUCCEEDED(SHGetDesktopFolder(&self->_psf)))
  170. {
  171. // all is well
  172. return self;
  173. }
  174. delete self;
  175. }
  176. return NULL;
  177. }
  178. // pdir = parent folder
  179. // pidl = new folder location relative to parent
  180. static ByUsageDir *Create(ByUsageDir *pdir, LPCITEMIDLIST pidl)
  181. {
  182. ByUsageDir *self = new ByUsageDir();
  183. if (self)
  184. {
  185. LPCITEMIDLIST pidlRoot = pdir->_pidl;
  186. self->_pidl = ILCombine(pidlRoot, pidl);
  187. if (self->_pidl)
  188. {
  189. IShellFolder *psfRoot = pdir->_psf;
  190. if (SUCCEEDED(SHBindToObject(psfRoot,
  191. IID_X_PPV_ARG(IShellFolder, pidl, &self->_psf))))
  192. {
  193. // all is well
  194. return self;
  195. }
  196. }
  197. delete self;
  198. }
  199. return NULL;
  200. }
  201. void AddRef();
  202. void Release();
  203. IShellFolder *Folder() const { return _psf; }
  204. LPCITEMIDLIST Pidl() const { return _pidl; }
  205. };
  206. void ByUsageDir::AddRef()
  207. {
  208. InterlockedIncrement(&_cRef);
  209. }
  210. void ByUsageDir::Release()
  211. {
  212. ASSERT( 0 != _cRef );
  213. if (InterlockedDecrement(&_cRef) == 0)
  214. {
  215. delete this;
  216. }
  217. }
  218. //****************************************************************************
  219. class ByUsageItem : public PaneItem
  220. {
  221. public:
  222. LPITEMIDLIST _pidl; // relative pidl
  223. ByUsageDir *_pdir; // Parent directory
  224. UEMINFO _uei; /* Usage info (for sorting) */
  225. static ByUsageItem *Create(ByUsageShortcut *pscut);
  226. static ByUsageItem *CreateSeparator();
  227. ByUsageItem() { EnableDropTarget(); }
  228. ~ByUsageItem();
  229. ByUsageDir *Dir() const { return _pdir; }
  230. IShellFolder *ParentFolder() const { return _pdir->Folder(); }
  231. LPCITEMIDLIST RelativePidl() const { return _pidl; }
  232. LPITEMIDLIST CreateFullPidl() const { return ILCombine(_pdir->Pidl(), _pidl); }
  233. void SetRelativePidl(LPITEMIDLIST pidlNew) { ILFree(_pidl); _pidl = pidlNew; }
  234. virtual BOOL IsEqual(PaneItem *pItem) const
  235. {
  236. ByUsageItem *pbuItem = reinterpret_cast<ByUsageItem *>(pItem);
  237. BOOL fIsEqual = FALSE;
  238. if (_pdir == pbuItem->_pdir)
  239. {
  240. // Do we have identical pidls?
  241. // Note: this test needs to be fast, and does not need to be exact, so we don't use pidl binding here.
  242. UINT usize1 = ILGetSize(_pidl);
  243. UINT usize2 = ILGetSize(pbuItem->_pidl);
  244. if (usize1 == usize2)
  245. {
  246. fIsEqual = (memcmp(_pidl, pbuItem->_pidl, usize1) == 0);
  247. }
  248. }
  249. return fIsEqual;
  250. }
  251. };
  252. inline BOOL ByUsage::_IsPinned(ByUsageItem *pitem)
  253. {
  254. return pitem->Dir() == _pdirDesktop;
  255. }
  256. //****************************************************************************
  257. // Each app referenced by a command line is saved in one of these
  258. // structures.
  259. class ByUsageAppInfo { // "papp"
  260. public:
  261. ByUsageShortcut *_pscutBest;// best candidate so far
  262. ByUsageShortcut *_pscutBestSM;// best candidate so far on Start Menu (excludes Desktop)
  263. UEMINFO _ueiBest; // info about best candidate so far
  264. UEMINFO _ueiTotal; // cumulative information
  265. LPTSTR _pszAppPath; // path to app in question
  266. FILETIME _ftCreated; // when was file created?
  267. bool _fNew; // is app new?
  268. bool _fPinned; // is app referenced by a pinned item?
  269. bool _fIgnoreTimestamp; // Darwin apps have unreliable timestamps
  270. private:
  271. LONG _cRef; // reference count
  272. public:
  273. // WARNING! Initialize() is called multiple times, so make sure
  274. // that you don't leak anything
  275. BOOL Initialize(LPCTSTR pszAppPath, CMenuItemsCache *pmenuCache, BOOL fCheckNew, bool fIgnoreTimestamp)
  276. {
  277. TraceMsg(TF_PROGLIST, "%p.ai.Init(%s)", this, pszAppPath);
  278. ASSERT(IsBlank());
  279. _fIgnoreTimestamp = fIgnoreTimestamp || IsDarwinPath(pszAppPath);
  280. // Note! Str_SetPtr is last so there's nothing to free on failure
  281. if (_fIgnoreTimestamp)
  282. {
  283. // just save the path; ignore the timestamp
  284. if (Str_SetPtr(&_pszAppPath, pszAppPath))
  285. {
  286. _fNew = TRUE;
  287. return TRUE;
  288. }
  289. }
  290. else
  291. if (Str_SetPtr(&_pszAppPath, pszAppPath))
  292. {
  293. if (fCheckNew && GetFileCreationTime(pszAppPath, &_ftCreated))
  294. {
  295. _fNew = pmenuCache->IsNewlyCreated(&_ftCreated);
  296. }
  297. return TRUE;
  298. }
  299. return FALSE;
  300. }
  301. static ByUsageAppInfo *Create()
  302. {
  303. ByUsageAppInfo *papp = new ByUsageAppInfo;
  304. if (papp)
  305. {
  306. ASSERT(papp->IsBlank()); // will be AddRef()d by caller
  307. ASSERT(papp->_pszAppPath == NULL);
  308. }
  309. return papp;
  310. }
  311. ~ByUsageAppInfo()
  312. {
  313. TraceMsg(TF_PROGLIST, "%p.ai.~", this);
  314. ASSERT(IsBlank());
  315. Str_SetPtr(&_pszAppPath, NULL);
  316. }
  317. // Notice! When the reference count goes to zero, we do not delete
  318. // the item. That's because there is still a reference to it in
  319. // the _dpaAppInfo DPA. Instead, we'll recycle items whose refcount
  320. // is zero.
  321. // NTRAID:135696 potential race condition against background enumeration
  322. void AddRef() { InterlockedIncrement(&_cRef); }
  323. void Release() { ASSERT( 0 != _cRef ); InterlockedDecrement(&_cRef); }
  324. inline BOOL IsBlank() { return _cRef == 0; }
  325. inline BOOL IsNew() { return _fNew; }
  326. inline BOOL IsAppPath(LPCTSTR pszAppPath)
  327. { return lstrcmpi(_pszAppPath, pszAppPath) == 0; }
  328. const FILETIME& GetCreatedTime() const { return _ftCreated; }
  329. inline const FILETIME *GetFileTime() const { return &_ueiTotal.ftExecute; }
  330. inline LPTSTR GetAppPath() const { return _pszAppPath; }
  331. void GetUEMInfo(OUT UEMINFO *puei)
  332. {
  333. _GetUEMPathInfo(_pszAppPath, puei);
  334. }
  335. void CombineUEMInfo(IN const UEMINFO *pueiNew, BOOL fNew = TRUE, BOOL fIsDesktop = FALSE)
  336. {
  337. // Accumulate the points
  338. _ueiTotal.cHit += pueiNew->cHit;
  339. // Take the most recent execution time
  340. if (CompareFileTime(&pueiNew->ftExecute, &_ueiTotal.ftExecute) > 0)
  341. {
  342. _ueiTotal.ftExecute = pueiNew->ftExecute;
  343. }
  344. // If anybody contributing to those app is no longer new, then
  345. // the app stops being new.
  346. // If the item is on the desktop, we don't track its newness,
  347. // but we don't want to invalidate the newness of the app either
  348. if (!fIsDesktop && !fNew) _fNew = FALSE;
  349. }
  350. //
  351. // The app UEM info is "old" if the execution time is more
  352. // than an hour after the install time.
  353. //
  354. inline BOOL _IsUEMINFONew(const UEMINFO *puei)
  355. {
  356. return FILETIMEtoInt64(puei->ftExecute) <
  357. FILETIMEtoInt64(_ftCreated) + ByUsage::FT_NEWAPPGRACEPERIOD();
  358. }
  359. //
  360. // Prepare for a new enumeration.
  361. //
  362. static BOOL CALLBACK EnumResetCB(ByUsageAppInfo *self, CMenuItemsCache *pmenuCache)
  363. {
  364. self->_pscutBest = NULL;
  365. self->_pscutBestSM = NULL;
  366. self->_fPinned = FALSE;
  367. ZeroMemory(&self->_ueiBest, sizeof(self->_ueiBest));
  368. ZeroMemory(&self->_ueiTotal, sizeof(self->_ueiTotal));
  369. if (self->_fNew && !self->_fIgnoreTimestamp)
  370. {
  371. self->_fNew = pmenuCache->IsNewlyCreated(&self->_ftCreated);
  372. }
  373. return TRUE;
  374. }
  375. static BOOL CALLBACK EnumGetFileCreationTime(ByUsageAppInfo *self, CMenuItemsCache *pmenuCache)
  376. {
  377. if (!self->IsBlank() &&
  378. !self->_fIgnoreTimestamp &&
  379. GetFileCreationTime(self->_pszAppPath, &self->_ftCreated))
  380. {
  381. self->_fNew = pmenuCache->IsNewlyCreated(&self->_ftCreated);
  382. }
  383. return TRUE;
  384. }
  385. ByUsageItem *CreateByUsageItem();
  386. };
  387. //****************************************************************************
  388. class ByUsageShortcut
  389. {
  390. #ifdef DEBUG
  391. ByUsageShortcut() { } // make constructor private
  392. #endif
  393. ByUsageDir * _pdir; // folder that contains this shortcut
  394. LPITEMIDLIST _pidl; // pidl relative to parent
  395. ByUsageAppInfo * _papp; // associated EXE
  396. FILETIME _ftCreated; // time shortcut was created
  397. bool _fNew; // new app?
  398. bool _fInteresting; // Is this a candidate for the MFU list?
  399. bool _fDarwin; // Is this a Darwin shortcut?
  400. public:
  401. // Accessors
  402. LPCITEMIDLIST RelativePidl() const { return _pidl; }
  403. ByUsageDir *Dir() const { return _pdir; }
  404. LPCITEMIDLIST ParentPidl() const { return _pdir->Pidl(); }
  405. IShellFolder *ParentFolder() const { return _pdir->Folder(); }
  406. ByUsageAppInfo *App() const { return _papp; }
  407. bool IsNew() const { return _fNew; }
  408. bool SetNew(bool fNew) { return _fNew = fNew; }
  409. const FILETIME& GetCreatedTime() const { return _ftCreated; }
  410. bool IsInteresting() const { return _fInteresting; }
  411. bool SetInteresting(bool fInteresting) { return _fInteresting = fInteresting; }
  412. bool IsDarwin() { return _fDarwin; }
  413. LPITEMIDLIST CreateFullPidl() const
  414. { return ILCombine(ParentPidl(), RelativePidl()); }
  415. LPCITEMIDLIST UpdateRelativePidl(ByUsageHiddenData *phd);
  416. void SetApp(ByUsageAppInfo *papp)
  417. {
  418. if (_papp) _papp->Release();
  419. _papp = papp;
  420. if (papp) papp->AddRef();
  421. }
  422. static ByUsageShortcut *Create(ByUsageDir *pdir, LPCITEMIDLIST pidl, ByUsageAppInfo *papp, bool fDarwin, BOOL fForce = FALSE)
  423. {
  424. ASSERT(pdir);
  425. ASSERT(pidl);
  426. ByUsageShortcut *pscut = new ByUsageShortcut;
  427. if (pscut)
  428. {
  429. TraceMsg(TF_PROGLIST, "%p.scut()", pscut);
  430. pscut->_fNew = TRUE; // will be set to FALSE later
  431. pscut->_pdir = pdir; pdir->AddRef();
  432. pscut->SetApp(papp);
  433. pscut->_fDarwin = fDarwin;
  434. pscut->_pidl = ILClone(pidl);
  435. if (pscut->_pidl)
  436. {
  437. LPTSTR pszShortcutName = _DisplayNameOf(pscut->ParentFolder(), pscut->RelativePidl(), SHGDN_FORPARSING);
  438. if (pszShortcutName &&
  439. GetFileCreationTime(pszShortcutName, &pscut->_ftCreated))
  440. {
  441. // Woo-hoo, all is well
  442. }
  443. else if (fForce)
  444. {
  445. // The item was forced -- create it even though
  446. // we don't know what it is.
  447. }
  448. else
  449. {
  450. delete pscut;
  451. pscut = NULL;
  452. }
  453. SHFree(pszShortcutName);
  454. }
  455. else
  456. {
  457. delete pscut;
  458. pscut = NULL;
  459. }
  460. }
  461. return pscut;
  462. }
  463. ~ByUsageShortcut()
  464. {
  465. TraceMsg(TF_PROGLIST, "%p.scut.~", this);
  466. if (_pdir) _pdir->Release();
  467. if (_papp) _papp->Release();
  468. ILFree(_pidl); // ILFree ignores NULL
  469. }
  470. ByUsageItem *CreatePinnedItem(int iPinPos);
  471. void GetUEMInfo(OUT UEMINFO *puei)
  472. {
  473. _GetUEMPidlInfo(_pdir->Folder(), _pidl, puei);
  474. }
  475. };
  476. //****************************************************************************
  477. ByUsageItem *ByUsageItem::Create(ByUsageShortcut *pscut)
  478. {
  479. ASSERT(pscut);
  480. ByUsageItem *pitem = new ByUsageItem;
  481. if (pitem)
  482. {
  483. pitem->_pdir = pscut->Dir();
  484. pitem->_pdir->AddRef();
  485. pitem->_pidl = ILClone(pscut->RelativePidl());
  486. if (pitem->_pidl)
  487. {
  488. return pitem;
  489. }
  490. }
  491. delete pitem; // "delete" can handle NULL
  492. return NULL;
  493. }
  494. ByUsageItem *ByUsageItem::CreateSeparator()
  495. {
  496. ByUsageItem *pitem = new ByUsageItem;
  497. if (pitem)
  498. {
  499. pitem->_iPinPos = PINPOS_SEPARATOR;
  500. }
  501. return pitem;
  502. }
  503. ByUsageItem::~ByUsageItem()
  504. {
  505. ILFree(_pidl);
  506. if (_pdir) _pdir->Release();
  507. }
  508. ByUsageItem *ByUsageAppInfo::CreateByUsageItem()
  509. {
  510. ASSERT(_pscutBest);
  511. ByUsageItem *pitem = ByUsageItem::Create(_pscutBest);
  512. if (pitem)
  513. {
  514. pitem->_uei = _ueiTotal;
  515. }
  516. return pitem;
  517. }
  518. ByUsageItem *ByUsageShortcut::CreatePinnedItem(int iPinPos)
  519. {
  520. ASSERT(iPinPos >= 0);
  521. ByUsageItem *pitem = ByUsageItem::Create(this);
  522. if (pitem)
  523. {
  524. pitem->_iPinPos = iPinPos;
  525. }
  526. return pitem;
  527. }
  528. //****************************************************************************
  529. //
  530. // The hidden data for the Programs list is of the following form:
  531. //
  532. // WORD cb; // hidden item size
  533. // WORD wPadding; // padding
  534. // IDLHID idl; // IDLHID_STARTPANEDATA
  535. // int iUnused; // (was icon index)
  536. // WCHAR LocalMSIPath[]; // variable-length string
  537. // WCHAR TargetPath[]; // variable-length string
  538. // WCHAR AltName[]; // variable-length string
  539. //
  540. // AltName is the alternate display name for the EXE.
  541. //
  542. // The hidden data is never accessed directly. We always operate on the
  543. // ByUsageHiddenData structure, and there are special methods for
  544. // transferring data between this structure and the pidl.
  545. //
  546. // The hidden data is appended to the pidl, with a "wOffset" backpointer
  547. // at the very end.
  548. //
  549. // The TargetPath is stored as an unexpanded environment string.
  550. // (I.e., you have to ExpandEnvironmentStrings before using them.)
  551. //
  552. // As an added bonus, the TargetPath might be a GUID (product code)
  553. // if it is really a Darwin shortcut.
  554. //
  555. class ByUsageHiddenData {
  556. public:
  557. WORD _wHotKey; // Hot Key
  558. LPWSTR _pwszMSIPath; // SHAlloc
  559. LPWSTR _pwszTargetPath; // SHAlloc
  560. LPWSTR _pwszAltName; // SHAlloc
  561. public:
  562. void Init()
  563. {
  564. _wHotKey = 0;
  565. _pwszMSIPath = NULL;
  566. _pwszTargetPath = NULL;
  567. _pwszAltName = NULL;
  568. }
  569. BOOL IsClear() // are all fields at initial values?
  570. {
  571. return _wHotKey == 0 &&
  572. _pwszMSIPath == NULL &&
  573. _pwszTargetPath == NULL &&
  574. _pwszAltName == NULL;
  575. }
  576. ByUsageHiddenData() { Init(); }
  577. enum {
  578. BUHD_HOTKEY = 0x0000, // so cheap we always fetch it
  579. BUHD_MSIPATH = 0x0001,
  580. BUHD_TARGETPATH = 0x0002,
  581. BUHD_ALTNAME = 0x0004,
  582. BUHD_ALL = -1,
  583. };
  584. BOOL Get(LPCITEMIDLIST pidl, UINT buhd); // Load from pidl
  585. LPITEMIDLIST Set(LPITEMIDLIST pidl); // Save to pidl
  586. void Clear()
  587. {
  588. SHFree(_pwszMSIPath);
  589. SHFree(_pwszTargetPath);
  590. SHFree(_pwszAltName);
  591. Init();
  592. }
  593. static LPTSTR GetAltName(LPCITEMIDLIST pidl);
  594. static LPITEMIDLIST SetAltName(LPITEMIDLIST pidl, LPCTSTR ptszNewName);
  595. void LoadFromShellLink(IShellLink *psl);
  596. HRESULT UpdateMSIPath();
  597. private:
  598. static LPBYTE _ParseString(LPBYTE pbHidden, LPWSTR *ppwszOut, LPITEMIDLIST pidlMax, BOOL fSave);
  599. static LPBYTE _AppendString(LPBYTE pbHidden, LPWSTR pwsz);
  600. };
  601. //
  602. // We are parsing a string out of a pidl, so we have to be extra careful
  603. // to watch out for data corruption.
  604. //
  605. // pbHidden = next byte to parse (or NULL to stop parsing)
  606. // ppwszOut receives parsed string if fSave = TRUE
  607. // pidlMax = start of next pidl; do not parse past this point
  608. // fSave = should we save the string in ppwszOut?
  609. //
  610. LPBYTE ByUsageHiddenData::_ParseString(LPBYTE pbHidden, LPWSTR *ppwszOut, LPITEMIDLIST pidlMax, BOOL fSave)
  611. {
  612. if (!pbHidden)
  613. return NULL;
  614. LPNWSTR pwszSrc = (LPNWSTR)pbHidden;
  615. LPNWSTR pwsz = pwszSrc;
  616. LPNWSTR pwszLast = (LPNWSTR)pidlMax - 1;
  617. //
  618. // We cannot use ualstrlenW because that might scan past pwszLast
  619. // and fault.
  620. //
  621. while (pwsz < pwszLast && *pwsz)
  622. {
  623. pwsz++;
  624. }
  625. // Corrupted data -- no null terminator found.
  626. if (pwsz >= pwszLast)
  627. return NULL;
  628. pwsz++; // Step pwsz over the terminating NULL
  629. UINT cb = (UINT)((LPBYTE)pwsz - (LPBYTE)pwszSrc);
  630. if (fSave)
  631. {
  632. *ppwszOut = (LPWSTR)SHAlloc(cb);
  633. if (*ppwszOut)
  634. {
  635. CopyMemory(*ppwszOut, pbHidden, cb);
  636. }
  637. else
  638. {
  639. return NULL;
  640. }
  641. }
  642. pbHidden += cb;
  643. ASSERT(pbHidden == (LPBYTE)pwsz);
  644. return pbHidden;
  645. }
  646. BOOL ByUsageHiddenData::Get(LPCITEMIDLIST pidl, UINT buhd)
  647. {
  648. ASSERT(IsClear());
  649. PCIDHIDDEN pidhid = ILFindHiddenID(pidl, IDLHID_STARTPANEDATA);
  650. if (!pidhid)
  651. {
  652. return FALSE;
  653. }
  654. // Do not access bytes after pidlMax
  655. LPITEMIDLIST pidlMax = _ILNext((LPITEMIDLIST)pidhid);
  656. LPBYTE pbHidden = ((LPBYTE)pidhid) + sizeof(HIDDENITEMID);
  657. // Skip the iUnused value
  658. // Note: if you someday choose to use it, you must read it as
  659. // _iWhatever = *(UNALIGNED int *)pbHidden;
  660. pbHidden += sizeof(int);
  661. // HotKey
  662. _wHotKey = *(UNALIGNED WORD *)pbHidden;
  663. pbHidden += sizeof(_wHotKey);
  664. pbHidden = _ParseString(pbHidden, &_pwszMSIPath, pidlMax, buhd & BUHD_MSIPATH);
  665. pbHidden = _ParseString(pbHidden, &_pwszTargetPath, pidlMax, buhd & BUHD_TARGETPATH);
  666. pbHidden = _ParseString(pbHidden, &_pwszAltName, pidlMax, buhd & BUHD_ALTNAME);
  667. if (pbHidden)
  668. {
  669. return TRUE;
  670. }
  671. else
  672. {
  673. Clear();
  674. return FALSE;
  675. }
  676. }
  677. LPBYTE ByUsageHiddenData::_AppendString(LPBYTE pbHidden, LPWSTR pwsz)
  678. {
  679. LPWSTR pwszOut = (LPWSTR)pbHidden;
  680. // The pointer had better already be aligned for WCHARs
  681. ASSERT(((ULONG_PTR)pwszOut & 1) == 0);
  682. if (pwsz)
  683. {
  684. lstrcpyW(pwszOut, pwsz);
  685. }
  686. else
  687. {
  688. pwszOut[0] = L'\0';
  689. }
  690. return (LPBYTE)(pwszOut + 1 + lstrlenW(pwszOut));
  691. }
  692. //
  693. // Note! On failure, the source pidl is freed!
  694. // (This behavior is inherited from ILAppendHiddenID.)
  695. //
  696. LPITEMIDLIST ByUsageHiddenData::Set(LPITEMIDLIST pidl)
  697. {
  698. UINT cb = sizeof(HIDDENITEMID);
  699. cb += sizeof(int);
  700. cb += sizeof(_wHotKey);
  701. cb += (UINT)(CbFromCchW(1 + (_pwszMSIPath ? lstrlenW(_pwszMSIPath) : 0)));
  702. cb += (UINT)(CbFromCchW(1 + (_pwszTargetPath ? lstrlenW(_pwszTargetPath) : 0)));
  703. cb += (UINT)(CbFromCchW(1 + (_pwszAltName ? lstrlenW(_pwszAltName) : 0)));
  704. // We can use the aligned version here since we allocated it ourselves
  705. // and didn't suck it out of a pidl.
  706. HIDDENITEMID *pidhid = (HIDDENITEMID*)alloca(cb);
  707. pidhid->cb = (WORD)cb;
  708. pidhid->wVersion = 0;
  709. pidhid->id = IDLHID_STARTPANEDATA;
  710. LPBYTE pbHidden = ((LPBYTE)pidhid) + sizeof(HIDDENITEMID);
  711. // The pointer had better already be aligned for ints
  712. ASSERT(((ULONG_PTR)pbHidden & 3) == 0);
  713. *(int *)pbHidden = 0; // iUnused
  714. pbHidden += sizeof(int);
  715. *(DWORD *)pbHidden = _wHotKey;
  716. pbHidden += sizeof(_wHotKey);
  717. pbHidden = _AppendString(pbHidden, _pwszMSIPath);
  718. pbHidden = _AppendString(pbHidden, _pwszTargetPath);
  719. pbHidden = _AppendString(pbHidden, _pwszAltName);
  720. // Make sure our math was correct
  721. ASSERT(cb == (UINT)((LPBYTE)pbHidden - (LPBYTE)pidhid));
  722. // Remove and expunge the old data
  723. ILRemoveHiddenID(pidl, IDLHID_STARTPANEDATA);
  724. ILExpungeRemovedHiddenIDs(pidl);
  725. return ILAppendHiddenID(pidl, pidhid);
  726. }
  727. LPWSTR ByUsageHiddenData::GetAltName(LPCITEMIDLIST pidl)
  728. {
  729. LPWSTR pszRet = NULL;
  730. ByUsageHiddenData hd;
  731. if (hd.Get(pidl, BUHD_ALTNAME))
  732. {
  733. pszRet = hd._pwszAltName; // Keep this string
  734. hd._pwszAltName = NULL; // Keep the upcoming assert happy
  735. }
  736. ASSERT(hd.IsClear()); // make sure we aren't leaking
  737. return pszRet;
  738. }
  739. //
  740. // Note! On failure, the source pidl is freed!
  741. // (Propagating weird behavior of ByUsageHiddenData::Set)
  742. //
  743. LPITEMIDLIST ByUsageHiddenData::SetAltName(LPITEMIDLIST pidl, LPCTSTR ptszNewName)
  744. {
  745. ByUsageHiddenData hd;
  746. // Attempt to overlay the existing values, but if they aren't available,
  747. // don't freak out.
  748. hd.Get(pidl, BUHD_ALL & ~BUHD_ALTNAME);
  749. ASSERT(hd._pwszAltName == NULL); // we excluded it from the hd.Get()
  750. hd._pwszAltName = const_cast<LPTSTR>(ptszNewName);
  751. pidl = hd.Set(pidl);
  752. hd._pwszAltName = NULL; // so hd.Clear() won't SHFree() it
  753. hd.Clear();
  754. return pidl;
  755. }
  756. //
  757. // Returns S_OK if the item changed; S_FALSE if it remained the same
  758. //
  759. HRESULT ByUsageHiddenData::UpdateMSIPath()
  760. {
  761. HRESULT hr = S_FALSE;
  762. if (_pwszTargetPath && IsDarwinPath(_pwszTargetPath))
  763. {
  764. LPWSTR pwszMSIPath = NULL;
  765. //
  766. // If we can't resolve the Darwin ID to a filename, then leave
  767. // the filename in the HiddenData alone - it's better than
  768. // nothing.
  769. //
  770. if (SUCCEEDED(SHParseDarwinIDFromCacheW(_pwszTargetPath+1, &pwszMSIPath)) && pwszMSIPath)
  771. {
  772. //
  773. // See if the MSI path has changed...
  774. //
  775. if (_pwszMSIPath == NULL ||
  776. StrCmpCW(pwszMSIPath, _pwszMSIPath) != 0)
  777. {
  778. hr = S_OK;
  779. SHFree(_pwszMSIPath);
  780. _pwszMSIPath = pwszMSIPath; // take ownership
  781. }
  782. else
  783. {
  784. // Unchanged; happy, free the path we aren't going to use
  785. SHFree(pwszMSIPath);
  786. }
  787. }
  788. }
  789. return hr;
  790. }
  791. LPCITEMIDLIST ByUsageShortcut::UpdateRelativePidl(ByUsageHiddenData *phd)
  792. {
  793. return _pidl = phd->Set(_pidl); // frees old _pidl even on failure
  794. }
  795. //
  796. // We must key off the Darwin ID and not the product code.
  797. //
  798. // The Darwin ID is unique for each app in an application suite.
  799. // For example, PowerPoint and Outlook have different Darwin IDs.
  800. //
  801. // The product code is the same for all apps in an application suite.
  802. // For example, PowerPoint and Outlook have the same product code.
  803. //
  804. // Since we want to treat PowerPoint and Outlook as two independent
  805. // applications, we want to use the Darwin ID and not the product code.
  806. //
  807. HRESULT _GetDarwinID(IShellLinkDataList *pdl, DWORD dwSig, LPWSTR pszPath, UINT cchPath)
  808. {
  809. LPEXP_DARWIN_LINK pedl;
  810. HRESULT hr;
  811. ASSERT(cchPath > 0);
  812. hr = pdl->CopyDataBlock(dwSig, (LPVOID*)&pedl);
  813. if (SUCCEEDED(hr))
  814. {
  815. pszPath[0] = CH_DARWINMARKER;
  816. hr = StringCchCopy(pszPath+1, cchPath - 1, pedl->szwDarwinID);
  817. LocalFree(pedl);
  818. }
  819. return hr;
  820. }
  821. HRESULT _GetPathOrDarwinID(IShellLink *psl, LPTSTR pszPath, UINT cchPath, DWORD dwFlags)
  822. {
  823. HRESULT hr;
  824. ASSERT(cchPath);
  825. pszPath[0] = TEXT('\0');
  826. //
  827. // See if it's a Darwin thingie.
  828. //
  829. IShellLinkDataList *pdl;
  830. hr = psl->QueryInterface(IID_PPV_ARG(IShellLinkDataList, &pdl));
  831. if (SUCCEEDED(hr))
  832. {
  833. //
  834. // Maybe this is a Darwin shortcut... If so, then
  835. // use the Darwin ID.
  836. //
  837. DWORD dwSLFlags;
  838. hr = pdl->GetFlags(&dwSLFlags);
  839. if (SUCCEEDED(hr))
  840. {
  841. if (dwSLFlags & SLDF_HAS_DARWINID)
  842. {
  843. hr = _GetDarwinID(pdl, EXP_DARWIN_ID_SIG, pszPath, cchPath);
  844. }
  845. else
  846. {
  847. hr = E_FAIL; // No Darwin ID found
  848. }
  849. pdl->Release();
  850. }
  851. }
  852. if (FAILED(hr))
  853. {
  854. hr = psl->GetPath(pszPath, cchPath, 0, dwFlags);
  855. }
  856. return hr;
  857. }
  858. void ByUsageHiddenData::LoadFromShellLink(IShellLink *psl)
  859. {
  860. ASSERT(_pwszTargetPath == NULL);
  861. HRESULT hr;
  862. TCHAR szPath[MAX_PATH];
  863. szPath[0] = TEXT('\0');
  864. hr = _GetPathOrDarwinID(psl, szPath, ARRAYSIZE(szPath), SLGP_RAWPATH);
  865. if (SUCCEEDED(hr))
  866. {
  867. SHStrDup(szPath, &_pwszTargetPath);
  868. }
  869. hr = psl->GetHotkey(&_wHotKey);
  870. }
  871. //****************************************************************************
  872. ByUsageUI::ByUsageUI() : _byUsage(this, NULL),
  873. // We want to log execs as if they were launched by the Start Menu
  874. SFTBarHost(HOSTF_FIREUEMEVENTS |
  875. HOSTF_CANDELETE |
  876. HOSTF_CANRENAME)
  877. {
  878. _iThemePart = SPP_PROGLIST;
  879. _iThemePartSep = SPP_PROGLISTSEPARATOR;
  880. }
  881. ByUsage::ByUsage(ByUsageUI *pByUsageUI, ByUsageDUI *pByUsageDUI)
  882. {
  883. _pByUsageUI = pByUsageUI;
  884. _pByUsageDUI = pByUsageDUI;
  885. GetStartTime(&_ftStartTime);
  886. _pidlBrowser = ILCreateFromPath(TEXT("shell:::{2559a1f4-21d7-11d4-bdaf-00c04f60b9f0}"));
  887. _pidlEmail = ILCreateFromPath(TEXT("shell:::{2559a1f5-21d7-11d4-bdaf-00c04f60b9f0}"));
  888. }
  889. SFTBarHost *ByUsage_CreateInstance()
  890. {
  891. return new ByUsageUI();
  892. }
  893. ByUsage::~ByUsage()
  894. {
  895. if (_fUEMRegistered)
  896. {
  897. // Unregister with UEM DB if necessary
  898. UEMRegisterNotify(NULL, NULL);
  899. }
  900. if (_dpaNew)
  901. {
  902. _dpaNew.DestroyCallback(ILFreeCallback, NULL);
  903. }
  904. // Must clear the pinned items before releasing the MenuCache,
  905. // as the pinned items point to AppInfo items in the cache.
  906. _rtPinned.Reset();
  907. if (_pMenuCache)
  908. {
  909. // Clean up the Menu cache properly.
  910. _pMenuCache->LockPopup();
  911. _pMenuCache->UnregisterNotifyAll();
  912. _pMenuCache->AttachUI(NULL);
  913. _pMenuCache->UnlockPopup();
  914. _pMenuCache->Release();
  915. }
  916. ILFree(_pidlBrowser);
  917. ILFree(_pidlEmail);
  918. ATOMICRELEASE(_psmpin);
  919. if (_pdirDesktop)
  920. {
  921. _pdirDesktop->Release();
  922. }
  923. }
  924. HRESULT ByUsage::Initialize()
  925. {
  926. HRESULT hr;
  927. hr = CoCreateInstance(CLSID_StartMenuPin, NULL, CLSCTX_INPROC_SERVER,
  928. IID_PPV_ARG(IStartMenuPin, &_psmpin));
  929. if (FAILED(hr))
  930. {
  931. return hr;
  932. }
  933. if (!(_pdirDesktop = ByUsageDir::CreateDesktop())) {
  934. return E_OUTOFMEMORY;
  935. }
  936. // Use already initialized MenuCache if available
  937. if (g_pMenuCache)
  938. {
  939. _pMenuCache = g_pMenuCache;
  940. _pMenuCache->AttachUI(_pByUsageUI);
  941. g_pMenuCache = NULL; // We take ownership here.
  942. }
  943. else
  944. {
  945. hr = CMenuItemsCache::ReCreateMenuItemsCache(_pByUsageUI, &_ftStartTime, &_pMenuCache);
  946. if (FAILED(hr))
  947. {
  948. return hr;
  949. }
  950. }
  951. _ulPinChange = -1; // Force first query to re-enumerate
  952. _dpaNew = NULL;
  953. if (_pByUsageUI)
  954. {
  955. _hwnd = _pByUsageUI->_hwnd;
  956. //
  957. // Register for the "pin list change" event. This is an extended
  958. // event (hence global), so listen in a location that contains
  959. // no objects so the system doesn't waste time sending
  960. // us stuff we don't care about. Our choice: _pidlBrowser.
  961. // It's not even a folder, so it can't contain any objects!
  962. //
  963. ASSERT(!_pMenuCache->IsLocked());
  964. _pByUsageUI->RegisterNotify(NOTIFY_PINCHANGE, SHCNE_EXTENDED_EVENT, _pidlBrowser, FALSE);
  965. }
  966. return S_OK;
  967. }
  968. void CMenuItemsCache::_InitStringList(HKEY hk, LPCTSTR pszSub, CDPA<TCHAR> dpa)
  969. {
  970. ASSERT(static_cast<HDPA>(dpa));
  971. LONG lRc;
  972. DWORD cb = 0;
  973. lRc = RegQueryValueEx(hk, pszSub, NULL, NULL, NULL, &cb);
  974. if (lRc == ERROR_SUCCESS)
  975. {
  976. // Add an extra TCHAR just to be extra-safe. That way, we don't
  977. // barf if there is a non-null-terminated string in the registry.
  978. cb += sizeof(TCHAR);
  979. LPTSTR pszKillList = (LPTSTR)LocalAlloc(LPTR, cb);
  980. if (pszKillList)
  981. {
  982. lRc = SHGetValue(hk, NULL, pszSub, NULL, pszKillList, &cb);
  983. if (lRc == ERROR_SUCCESS)
  984. {
  985. // A semicolon-separated list of application names.
  986. LPTSTR psz = pszKillList;
  987. LPTSTR pszSemi;
  988. while ((pszSemi = StrChr(psz, TEXT(';'))) != NULL)
  989. {
  990. *pszSemi = TEXT('\0');
  991. if (*psz)
  992. {
  993. AppendString(dpa, psz);
  994. }
  995. psz = pszSemi+1;
  996. }
  997. if (*psz)
  998. {
  999. AppendString(dpa, psz);
  1000. }
  1001. }
  1002. LocalFree(pszKillList);
  1003. }
  1004. }
  1005. }
  1006. //
  1007. // Fill the kill list with the programs that should be ignored
  1008. // should they be encountered in the Start Menu or elsewhere.
  1009. //
  1010. #define REGSTR_PATH_FILEASSOCIATION TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileAssociation")
  1011. void CMenuItemsCache::_InitKillList()
  1012. {
  1013. HKEY hk;
  1014. LONG lRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_FILEASSOCIATION, 0,
  1015. KEY_READ, &hk);
  1016. if (lRc == ERROR_SUCCESS)
  1017. {
  1018. _InitStringList(hk, TEXT("AddRemoveApps"), _dpaKill);
  1019. _InitStringList(hk, TEXT("AddRemoveNames"), _dpaKillLink);
  1020. RegCloseKey(hk);
  1021. }
  1022. }
  1023. //****************************************************************************
  1024. //
  1025. // Filling the ByUsageShortcutList
  1026. //
  1027. int CALLBACK PidlSortCallback(LPITEMIDLIST pidl1, LPITEMIDLIST pidl2, IShellFolder *psf)
  1028. {
  1029. HRESULT hr = psf->CompareIDs(0, pidl1, pidl2);
  1030. // We got them from the ShellFolder; they should still be valid!
  1031. ASSERT(SUCCEEDED(hr));
  1032. return ShortFromResult(hr);
  1033. }
  1034. // {06C59536-1C66-4301-8387-82FBA3530E8D}
  1035. static const GUID TOID_STARTMENUCACHE =
  1036. { 0x6c59536, 0x1c66, 0x4301, { 0x83, 0x87, 0x82, 0xfb, 0xa3, 0x53, 0xe, 0x8d } };
  1037. /*
  1038. * Background cache creation stuff...
  1039. */
  1040. class CCreateMenuItemCacheTask : public CRunnableTask {
  1041. CMenuItemsCache *_pMenuCache;
  1042. IShellTaskScheduler *_pScheduler;
  1043. public:
  1044. CCreateMenuItemCacheTask(CMenuItemsCache *pMenuCache, IShellTaskScheduler *pScheduler)
  1045. : CRunnableTask(RTF_DEFAULT), _pMenuCache(pMenuCache), _pScheduler(pScheduler)
  1046. {
  1047. if (_pScheduler)
  1048. _pScheduler->AddRef();
  1049. }
  1050. ~CCreateMenuItemCacheTask()
  1051. {
  1052. if (_pScheduler)
  1053. _pScheduler->Release();
  1054. }
  1055. static void DummyCallBack(LPCITEMIDLIST pidl, LPVOID pvData, LPVOID pvHint, INT iIconIndex, INT iOpenIconIndex){}
  1056. STDMETHODIMP RunInitRT()
  1057. {
  1058. _pMenuCache->DelayGetFileCreationTimes();
  1059. _pMenuCache->DelayGetDarwinInfo();
  1060. _pMenuCache->LockPopup();
  1061. _pMenuCache->InitCache();
  1062. _pMenuCache->UpdateCache();
  1063. _pMenuCache->StartEnum();
  1064. ByUsageHiddenData hd; // construct once
  1065. while (TRUE)
  1066. {
  1067. ByUsageShortcut *pscut = _pMenuCache->GetNextShortcut();
  1068. if (!pscut)
  1069. break;
  1070. hd.Get(pscut->RelativePidl(), ByUsageHiddenData::BUHD_HOTKEY | ByUsageHiddenData::BUHD_TARGETPATH);
  1071. if (hd._wHotKey)
  1072. {
  1073. Tray_RegisterHotKey(hd._wHotKey, pscut->ParentPidl(), pscut->RelativePidl());
  1074. }
  1075. // Pre-load the icons in the cache
  1076. int iIndex;
  1077. SHMapIDListToImageListIndexAsync(_pScheduler, pscut->ParentFolder(), pscut->RelativePidl(), 0,
  1078. DummyCallBack, NULL, NULL, &iIndex, NULL);
  1079. // Register Darwin shortcut so that they can be grayed out if not installed
  1080. // and so we can map them to local paths as necessary
  1081. if (hd._pwszTargetPath && IsDarwinPath(hd._pwszTargetPath))
  1082. {
  1083. SHRegisterDarwinLink(pscut->CreateFullPidl(),
  1084. hd._pwszTargetPath +1 /* Exclude the Darwin marker! */,
  1085. FALSE /* Don't update the Darwin state now, we'll do it later */);
  1086. }
  1087. hd.Clear();
  1088. }
  1089. _pMenuCache->EndEnum();
  1090. _pMenuCache->UnlockPopup();
  1091. // Now determine all new items
  1092. // Note: this is safe to do after the Unlock because we never remove anything from the _dpaAppInfo
  1093. _pMenuCache->GetFileCreationTimes();
  1094. _pMenuCache->AllowGetDarwinInfo();
  1095. SHReValidateDarwinCache();
  1096. // Refreshing Darwin shortcuts must be done under the popup lock
  1097. // to avoid another thread doing a re-enumeration while we are
  1098. // studying the dpa. Keep SHReValidateDarwinCache outside of the
  1099. // lock since it is slow. (All we do is query the cache that
  1100. // SHReValidateDarwinCache created for us.)
  1101. _pMenuCache->LockPopup();
  1102. _pMenuCache->RefreshCachedDarwinShortcuts();
  1103. _pMenuCache->UnlockPopup();
  1104. _pMenuCache->Release();
  1105. return S_OK;
  1106. }
  1107. };
  1108. HRESULT AddMenuItemsCacheTask(IShellTaskScheduler* pSystemScheduler, BOOL fKeepCacheWhenFinished)
  1109. {
  1110. HRESULT hr;
  1111. CMenuItemsCache *pMenuCache = new CMenuItemsCache;
  1112. FILETIME ftStart;
  1113. // Initialize with something.
  1114. GetStartTime(&ftStart);
  1115. if (pMenuCache)
  1116. {
  1117. hr = pMenuCache->Initialize(NULL, &ftStart);
  1118. if (fKeepCacheWhenFinished)
  1119. {
  1120. g_pMenuCache = pMenuCache;
  1121. g_pMenuCache->AddRef();
  1122. }
  1123. }
  1124. else
  1125. {
  1126. hr = E_OUTOFMEMORY;
  1127. }
  1128. if (SUCCEEDED(hr))
  1129. {
  1130. CCreateMenuItemCacheTask *pTask = new CCreateMenuItemCacheTask(pMenuCache, pSystemScheduler);
  1131. if (pTask)
  1132. {
  1133. hr = pSystemScheduler->AddTask(pTask, TOID_STARTMENUCACHE, 0, ITSAT_DEFAULT_PRIORITY);
  1134. }
  1135. else
  1136. {
  1137. hr = E_OUTOFMEMORY;
  1138. }
  1139. }
  1140. return hr;
  1141. }
  1142. DWORD WINAPI CMenuItemsCache::ReInitCacheThreadProc(void *pv)
  1143. {
  1144. HRESULT hr = SHCoInitialize();
  1145. if (SUCCEEDED(hr))
  1146. {
  1147. CMenuItemsCache *pMenuCache = reinterpret_cast<CMenuItemsCache *>(pv);
  1148. pMenuCache->DelayGetFileCreationTimes();
  1149. pMenuCache->LockPopup();
  1150. pMenuCache->InitCache();
  1151. pMenuCache->UpdateCache();
  1152. pMenuCache->UnlockPopup();
  1153. // Now determine all new items
  1154. // Note: this is safe to do after the Unlock because we never remove anything from the _dpaAppInfo
  1155. pMenuCache->GetFileCreationTimes();
  1156. pMenuCache->Release();
  1157. }
  1158. SHCoUninitialize(hr);
  1159. return 0;
  1160. }
  1161. HRESULT CMenuItemsCache::ReCreateMenuItemsCache(ByUsageUI *pbuUI, FILETIME *ftOSInstall, CMenuItemsCache **ppMenuCache)
  1162. {
  1163. HRESULT hr = E_OUTOFMEMORY;
  1164. CMenuItemsCache *pMenuCache;
  1165. // Create a CMenuItemsCache with ref count 1.
  1166. pMenuCache = new CMenuItemsCache;
  1167. if (pMenuCache)
  1168. {
  1169. hr = pMenuCache->Initialize(pbuUI, ftOSInstall);
  1170. }
  1171. if (SUCCEEDED(hr))
  1172. {
  1173. pMenuCache->AddRef();
  1174. if (!SHQueueUserWorkItem(ReInitCacheThreadProc, pMenuCache, 0, 0, NULL, NULL, 0))
  1175. {
  1176. // No big deal if we fail here, we'll get another chance at enumerating later.
  1177. pMenuCache->Release();
  1178. }
  1179. *ppMenuCache = pMenuCache;
  1180. }
  1181. return hr;
  1182. }
  1183. HRESULT CMenuItemsCache::GetFileCreationTimes()
  1184. {
  1185. if (CompareFileTime(&_ftOldApps, &c_ftNever) != 0)
  1186. {
  1187. // Get all file creation times for our app list.
  1188. _dpaAppInfo.EnumCallbackEx(ByUsageAppInfo::EnumGetFileCreationTime, this);
  1189. // From now on, we will be checkin newness when we create the app object.
  1190. _fCheckNew = TRUE;
  1191. }
  1192. return S_OK;
  1193. }
  1194. //
  1195. // Enumerate the contents of the folder specified by psfParent.
  1196. // pidlParent represents the location of psfParent.
  1197. //
  1198. // Note that we cannot do a depth-first walk into subfolders because
  1199. // many (most?) machines have a timeout on FindFirst handles; if you don't
  1200. // call FindNextFile for a few minutes, they secretly do a FindClose for
  1201. // you on the assumption that you are a bad app that leaked a handle.
  1202. // (There is also a valid DOS-compatibility reason for this behavior:
  1203. // The DOS FindFirstFile API doesn't have a FindClose, so the server can
  1204. // never tell if you are finished or not, so it has to guess that if you
  1205. // don't do a FindNext for a long time, you're probably finished.)
  1206. //
  1207. // So we have to save all the folders we find into a DPA and then walk
  1208. // the folders after we close the enumeration.
  1209. //
  1210. void CMenuItemsCache::_FillFolderCache(ByUsageDir *pdir, ByUsageRoot *prt)
  1211. {
  1212. // Caller should've initialized us
  1213. ASSERT(prt->_sl);
  1214. //
  1215. // Note that we must use a namespace walk instead of FindFirst/FindNext,
  1216. // because there might be folder shortcuts in the user's Start Menu.
  1217. //
  1218. // We do not specify SHCONTF_INCLUDEHIDDEN, so hidden objects are
  1219. // automatically excluded
  1220. IEnumIDList *peidl;
  1221. if (S_OK == pdir->Folder()->EnumObjects(NULL, SHCONTF_FOLDERS |
  1222. SHCONTF_NONFOLDERS, &peidl))
  1223. {
  1224. CDPAPidl dpaDirs;
  1225. if (dpaDirs.Create(4))
  1226. {
  1227. CDPAPidl dpaFiles;
  1228. if (dpaFiles.Create(4))
  1229. {
  1230. LPITEMIDLIST pidl;
  1231. while (peidl->Next(1, &pidl, NULL) == S_OK)
  1232. {
  1233. // _IsExcludedDirectory carse about SFGAO_FILESYSTEM and SFGAO_LINK
  1234. DWORD dwAttributes = SFGAO_FOLDER | SFGAO_FILESYSTEM | SFGAO_LINK;
  1235. if (SUCCEEDED(pdir->Folder()->GetAttributesOf(1, (LPCITEMIDLIST*)(&pidl),
  1236. &dwAttributes)))
  1237. {
  1238. if (dwAttributes & SFGAO_FOLDER)
  1239. {
  1240. if (_IsExcludedDirectory(pdir->Folder(), pidl, dwAttributes) ||
  1241. dpaDirs.AppendPtr(pidl) < 0)
  1242. {
  1243. ILFree(pidl);
  1244. }
  1245. }
  1246. else
  1247. {
  1248. if (dpaFiles.AppendPtr(pidl) < 0)
  1249. {
  1250. ILFree(pidl);
  1251. }
  1252. }
  1253. }
  1254. }
  1255. dpaDirs.SortEx(PidlSortCallback, pdir->Folder());
  1256. if (dpaFiles.GetPtrCount() > 0)
  1257. {
  1258. dpaFiles.SortEx(PidlSortCallback, pdir->Folder());
  1259. //
  1260. // Now merge the enumerated items with the ones
  1261. // in the cache.
  1262. //
  1263. _MergeIntoFolderCache(prt, pdir, dpaFiles);
  1264. }
  1265. dpaFiles.DestroyCallback(ILFreeCallback, NULL);
  1266. }
  1267. // Must release now to force the FindClose to happen
  1268. peidl->Release();
  1269. // Now go back and handle all the folders we collected
  1270. ENUMFOLDERINFO info;
  1271. info.self = this;
  1272. info.pdir = pdir;
  1273. info.prt = prt;
  1274. dpaDirs.DestroyCallbackEx(FolderEnumCallback, &info);
  1275. }
  1276. }
  1277. }
  1278. BOOL CMenuItemsCache::FolderEnumCallback(LPITEMIDLIST pidl, ENUMFOLDERINFO *pinfo)
  1279. {
  1280. ByUsageDir *pdir = ByUsageDir::Create(pinfo->pdir, pidl);
  1281. if (pdir)
  1282. {
  1283. pinfo->self->_FillFolderCache(pdir, pinfo->prt);
  1284. pdir->Release();
  1285. }
  1286. ILFree(pidl);
  1287. return TRUE;
  1288. }
  1289. //
  1290. // Returns the next element in prt->_slOld that still belongs to the
  1291. // directory "pdir", or NULL if no more.
  1292. //
  1293. ByUsageShortcut *CMenuItemsCache::_NextFromCacheInDir(ByUsageRoot *prt, ByUsageDir *pdir)
  1294. {
  1295. if (prt->_iOld < prt->_cOld)
  1296. {
  1297. ByUsageShortcut *pscut = prt->_slOld.FastGetPtr(prt->_iOld);
  1298. if (pscut->Dir() == pdir)
  1299. {
  1300. prt->_iOld++;
  1301. return pscut;
  1302. }
  1303. }
  1304. return NULL;
  1305. }
  1306. void CMenuItemsCache::_MergeIntoFolderCache(ByUsageRoot *prt, ByUsageDir *pdir, CDPAPidl dpaFiles)
  1307. {
  1308. //
  1309. // Look at prt->_slOld to see if we have cached information about
  1310. // this directory already.
  1311. //
  1312. // If we find directories that are less than us, skip over them.
  1313. // These correspond to directories that have been deleted.
  1314. //
  1315. // For example, if we are "D" and we run across directories
  1316. // "B" and "C" in the old cache, that means that directories "B"
  1317. // and "C" were deleted and we should continue scanning until we
  1318. // find "D" (or maybe we find "E" and stop since E > D).
  1319. //
  1320. //
  1321. ByUsageDir *pdirPrev = NULL;
  1322. while (prt->_iOld < prt->_cOld)
  1323. {
  1324. ByUsageDir *pdirT = prt->_slOld.FastGetPtr(prt->_iOld)->Dir();
  1325. HRESULT hr = _pdirDesktop->Folder()->CompareIDs(0, pdirT->Pidl(), pdir->Pidl());
  1326. if (hr == ResultFromShort(0))
  1327. {
  1328. pdirPrev = pdirT;
  1329. break;
  1330. }
  1331. else if (FAILED(hr) || ShortFromResult(hr) < 0)
  1332. {
  1333. //
  1334. // Skip over this directory
  1335. //
  1336. while (_NextFromCacheInDir(prt, pdirT)) { }
  1337. }
  1338. else
  1339. {
  1340. break;
  1341. }
  1342. }
  1343. if (pdirPrev)
  1344. {
  1345. //
  1346. // If we have a cached previous directory, then recycle him.
  1347. // This keeps us from creating lots of copies of the same IShellFolder.
  1348. // It is also essential that all entries from the same directory
  1349. // have the same pdir; that's how _NextFromCacheInDir knows when
  1350. // to stop.
  1351. //
  1352. pdir = pdirPrev;
  1353. //
  1354. // Make sure that this IShellFolder supports SHCIDS_ALLFIELDS.
  1355. // If not, then we just have to assume that they all changed.
  1356. //
  1357. IShellFolder2 *psf2;
  1358. if (SUCCEEDED(pdir->Folder()->QueryInterface(IID_PPV_ARG(IShellFolder2, &psf2))))
  1359. {
  1360. psf2->Release();
  1361. }
  1362. else
  1363. {
  1364. pdirPrev = NULL;
  1365. }
  1366. }
  1367. //
  1368. // Now add all the items in dpaFiles to prt->_sl. If we find a match
  1369. // in prt->_slOld, then use that information instead of hitting the disk.
  1370. //
  1371. int iNew;
  1372. ByUsageShortcut *pscutNext = _NextFromCacheInDir(prt, pdirPrev);
  1373. for (iNew = 0; iNew < dpaFiles.GetPtrCount(); iNew++)
  1374. {
  1375. LPITEMIDLIST pidl = dpaFiles.FastGetPtr(iNew);
  1376. // Look for a match in the cache.
  1377. HRESULT hr = S_FALSE;
  1378. while (pscutNext &&
  1379. (FAILED(hr = pdir->Folder()->CompareIDs(SHCIDS_ALLFIELDS, pscutNext->RelativePidl(), pidl)) ||
  1380. ShortFromResult(hr) < 0))
  1381. {
  1382. pscutNext = _NextFromCacheInDir(prt, pdirPrev);
  1383. }
  1384. // pscutNext, if non-NULL, is the item that made us stop searching.
  1385. // If hr == S_OK, then it was a match and we should use the data
  1386. // from the cache. Otherwise, we have a new item and should
  1387. // fill it in the slow way.
  1388. if (hr == ResultFromShort(0))
  1389. {
  1390. // A match from the cache; move it over
  1391. _TransferShortcutToCache(prt, pscutNext);
  1392. pscutNext = _NextFromCacheInDir(prt, pdirPrev);
  1393. }
  1394. else
  1395. {
  1396. // Brand new item, fill in from scratch
  1397. _AddShortcutToCache(pdir, pidl, prt->_sl);
  1398. dpaFiles.FastGetPtr(iNew) = NULL; // took ownership
  1399. }
  1400. }
  1401. }
  1402. //****************************************************************************
  1403. bool CMenuItemsCache::_SetInterestingLink(ByUsageShortcut *pscut)
  1404. {
  1405. bool fInteresting = true;
  1406. if (pscut->App() && !_PathIsInterestingExe(pscut->App()->GetAppPath())) {
  1407. fInteresting = false;
  1408. }
  1409. else if (!_IsInterestingDirectory(pscut->Dir())) {
  1410. fInteresting = false;
  1411. }
  1412. else
  1413. {
  1414. LPTSTR pszDisplayName = _DisplayNameOf(pscut->ParentFolder(), pscut->RelativePidl(), SHGDN_NORMAL | SHGDN_INFOLDER);
  1415. if (pszDisplayName)
  1416. {
  1417. // SFGDN_INFOLDER should've returned a relative path
  1418. ASSERT(pszDisplayName == PathFindFileName(pszDisplayName));
  1419. int i;
  1420. for (i = 0; i < _dpaKillLink.GetPtrCount(); i++)
  1421. {
  1422. if (StrStrI(pszDisplayName, _dpaKillLink.GetPtr(i)) != NULL)
  1423. {
  1424. fInteresting = false;
  1425. break;
  1426. }
  1427. }
  1428. SHFree(pszDisplayName);
  1429. }
  1430. }
  1431. pscut->SetInteresting(fInteresting);
  1432. return fInteresting;
  1433. }
  1434. BOOL CMenuItemsCache::_PathIsInterestingExe(LPCTSTR pszPath)
  1435. {
  1436. //
  1437. // Darwin shortcuts are always interesting.
  1438. //
  1439. if (IsDarwinPath(pszPath))
  1440. {
  1441. return TRUE;
  1442. }
  1443. LPCTSTR pszExt = PathFindExtension(pszPath);
  1444. //
  1445. // *.msc files are also always interesting. They aren't
  1446. // strictly-speaking EXEs, but they act like EXEs and administrators
  1447. // really use them a lot.
  1448. //
  1449. if (StrCmpICW(pszExt, TEXT(".msc")) == 0)
  1450. {
  1451. return TRUE;
  1452. }
  1453. return StrCmpICW(pszExt, TEXT(".exe")) == 0 && !_IsExcludedExe(pszPath);
  1454. }
  1455. BOOL CMenuItemsCache::_IsExcludedExe(LPCTSTR pszPath)
  1456. {
  1457. pszPath = PathFindFileName(pszPath);
  1458. int i;
  1459. for (i = 0; i < _dpaKill.GetPtrCount(); i++)
  1460. {
  1461. if (StrCmpI(pszPath, _dpaKill.GetPtr(i)) == 0)
  1462. {
  1463. return TRUE;
  1464. }
  1465. }
  1466. HKEY hk;
  1467. BOOL fRc = FALSE;
  1468. if (SUCCEEDED(_pqa->Init(ASSOCF_OPEN_BYEXENAME, pszPath, NULL, NULL)) &&
  1469. SUCCEEDED(_pqa->GetKey(0, ASSOCKEY_APP, NULL, &hk)))
  1470. {
  1471. fRc = ERROR_SUCCESS == SHQueryValueEx(hk, TEXT("NoStartPage"), NULL, NULL, NULL, NULL);
  1472. RegCloseKey(hk);
  1473. }
  1474. return fRc;
  1475. }
  1476. HRESULT ByUsage::_GetShortcutExeTarget(IShellFolder *psf, LPCITEMIDLIST pidl, LPTSTR pszPath, UINT cchPath)
  1477. {
  1478. HRESULT hr;
  1479. IShellLink *psl;
  1480. hr = psf->GetUIObjectOf(_hwnd, 1, &pidl, IID_PPV_ARG_NULL(IShellLink, &psl));
  1481. if (SUCCEEDED(hr))
  1482. {
  1483. hr = psl->GetPath(pszPath, cchPath, 0, 0);
  1484. psl->Release();
  1485. }
  1486. return hr;
  1487. }
  1488. void _GetUEMInfo(const GUID *pguidGrp, int eCmd, WPARAM wParam, LPARAM lParam, UEMINFO *pueiOut)
  1489. {
  1490. ZeroMemory(pueiOut, sizeof(UEMINFO));
  1491. pueiOut->cbSize = sizeof(UEMINFO);
  1492. pueiOut->dwMask = UEIM_HIT | UEIM_FILETIME;
  1493. //
  1494. // If this call fails (app / pidl was never run), then we'll
  1495. // just use the zeros we pre-initialized with.
  1496. //
  1497. UEMQueryEvent(pguidGrp, eCmd, wParam, lParam, pueiOut);
  1498. //
  1499. // The UEM code invents a default usage count if the shortcut
  1500. // was never used. We don't want that.
  1501. //
  1502. if (FILETIMEtoInt64(pueiOut->ftExecute) == 0)
  1503. {
  1504. pueiOut->cHit = 0;
  1505. }
  1506. }
  1507. //
  1508. // Returns S_OK if the item changed, S_FALSE if the item stayed the same,
  1509. // or an error code
  1510. //
  1511. HRESULT CMenuItemsCache::_UpdateMSIPath(ByUsageShortcut *pscut)
  1512. {
  1513. HRESULT hr = S_FALSE; // Assume nothing happened
  1514. if (pscut->IsDarwin())
  1515. {
  1516. ByUsageHiddenData hd;
  1517. hd.Get(pscut->RelativePidl(), ByUsageHiddenData::BUHD_ALL);
  1518. if (hd.UpdateMSIPath() == S_OK)
  1519. {
  1520. // Redirect to the new target (user may have
  1521. // uninstalled then reinstalled to a new location)
  1522. ByUsageAppInfo *papp = GetAppInfoFromHiddenData(&hd);
  1523. pscut->SetApp(papp);
  1524. if (papp) papp->Release();
  1525. if (pscut->UpdateRelativePidl(&hd))
  1526. {
  1527. hr = S_OK; // We changed stuff
  1528. }
  1529. else
  1530. {
  1531. hr = E_OUTOFMEMORY; // Couldn't update the relative pidl
  1532. }
  1533. }
  1534. hd.Clear();
  1535. }
  1536. return hr;
  1537. }
  1538. //
  1539. // Take pscut (which is the ByUsageShortcut most recently enumerated from
  1540. // the old cache) and move it to the new cache. NULL out the entry in the
  1541. // old cache so that DPADELETEANDDESTROY(prt->_slOld) won't free it.
  1542. //
  1543. void CMenuItemsCache::_TransferShortcutToCache(ByUsageRoot *prt, ByUsageShortcut *pscut)
  1544. {
  1545. ASSERT(pscut);
  1546. ASSERT(pscut == prt->_slOld.FastGetPtr(prt->_iOld - 1));
  1547. if (SUCCEEDED(_UpdateMSIPath(pscut)) &&
  1548. prt->_sl.AppendPtr(pscut) >= 0) {
  1549. // Take ownership
  1550. prt->_slOld.FastGetPtr(prt->_iOld - 1) = NULL;
  1551. }
  1552. }
  1553. ByUsageAppInfo *CMenuItemsCache::GetAppInfoFromHiddenData(ByUsageHiddenData *phd)
  1554. {
  1555. ByUsageAppInfo *papp = NULL;
  1556. bool fIgnoreTimestamp = false;
  1557. TCHAR szPath[MAX_PATH];
  1558. LPTSTR pszPath = szPath;
  1559. szPath[0] = TEXT('\0');
  1560. if (phd->_pwszMSIPath && phd->_pwszMSIPath[0])
  1561. {
  1562. pszPath = phd->_pwszMSIPath;
  1563. // When MSI installs an app, the timestamp is applies to the app
  1564. // is the timestamp on the source media, *not* the time the user
  1565. // user installed the app. So ignore the timestamp entirely since
  1566. // it's useless information (and in fact makes us think the app
  1567. // is older than it really is).
  1568. fIgnoreTimestamp = true;
  1569. }
  1570. else if (phd->_pwszTargetPath)
  1571. {
  1572. if (IsDarwinPath(phd->_pwszTargetPath))
  1573. {
  1574. pszPath = phd->_pwszTargetPath;
  1575. }
  1576. else
  1577. {
  1578. //
  1579. // Need to expand the path because it may contain environment
  1580. // variables.
  1581. //
  1582. SHExpandEnvironmentStrings(phd->_pwszTargetPath, szPath, ARRAYSIZE(szPath));
  1583. }
  1584. }
  1585. return GetAppInfo(pszPath, fIgnoreTimestamp);
  1586. }
  1587. ByUsageShortcut *CMenuItemsCache::CreateShortcutFromHiddenData(ByUsageDir *pdir, LPCITEMIDLIST pidl, ByUsageHiddenData *phd, BOOL fForce)
  1588. {
  1589. ByUsageAppInfo *papp = GetAppInfoFromHiddenData(phd);
  1590. bool fDarwin = phd->_pwszTargetPath && IsDarwinPath(phd->_pwszTargetPath);
  1591. ByUsageShortcut *pscut = ByUsageShortcut::Create(pdir, pidl, papp, fDarwin, fForce);
  1592. if (papp) papp->Release();
  1593. return pscut;
  1594. }
  1595. void CMenuItemsCache::_AddShortcutToCache(ByUsageDir *pdir, LPITEMIDLIST pidl, ByUsageShortcutList slFiles)
  1596. {
  1597. HRESULT hr;
  1598. ByUsageHiddenData hd;
  1599. if (pidl)
  1600. {
  1601. //
  1602. // Juice-up this pidl with cool info about the shortcut target
  1603. //
  1604. IShellLink *psl;
  1605. hr = pdir->Folder()->GetUIObjectOf(NULL, 1, const_cast<LPCITEMIDLIST *>(&pidl),
  1606. IID_PPV_ARG_NULL(IShellLink, &psl));
  1607. if (SUCCEEDED(hr))
  1608. {
  1609. hd.LoadFromShellLink(psl);
  1610. psl->Release();
  1611. if (hd._pwszTargetPath && IsDarwinPath(hd._pwszTargetPath))
  1612. {
  1613. SHRegisterDarwinLink(ILCombine(pdir->Pidl(), pidl),
  1614. hd._pwszTargetPath +1 /* Exclude the Darwin marker! */,
  1615. _fCheckDarwin);
  1616. SHParseDarwinIDFromCacheW(hd._pwszTargetPath+1, &hd._pwszMSIPath);
  1617. }
  1618. // ByUsageHiddenData::Set frees the source pidl on failure
  1619. pidl = hd.Set(pidl);
  1620. }
  1621. }
  1622. if (pidl)
  1623. {
  1624. ByUsageShortcut *pscut = CreateShortcutFromHiddenData(pdir, pidl, &hd);
  1625. if (pscut)
  1626. {
  1627. if (slFiles.AppendPtr(pscut) >= 0)
  1628. {
  1629. _SetInterestingLink(pscut);
  1630. }
  1631. else
  1632. {
  1633. // Couldn't append; oh well
  1634. delete pscut; // "delete" can handle NULL pointer
  1635. }
  1636. }
  1637. ILFree(pidl);
  1638. }
  1639. hd.Clear();
  1640. }
  1641. //
  1642. // Find an entry in the AppInfo list that matches this application.
  1643. // If not found, create a new entry. In either case, bump the
  1644. // reference count and return the item.
  1645. //
  1646. ByUsageAppInfo* CMenuItemsCache::GetAppInfo(LPTSTR pszAppPath, bool fIgnoreTimestamp)
  1647. {
  1648. Lock();
  1649. ByUsageAppInfo *pappBlank = NULL;
  1650. int i;
  1651. for (i = _dpaAppInfo.GetPtrCount() - 1; i >= 0; i--)
  1652. {
  1653. ByUsageAppInfo *papp = _dpaAppInfo.FastGetPtr(i);
  1654. if (papp->IsBlank())
  1655. { // Remember that we found a blank entry we can recycle
  1656. pappBlank = papp;
  1657. }
  1658. else if (lstrcmpi(papp->_pszAppPath, pszAppPath) == 0)
  1659. {
  1660. papp->AddRef();
  1661. Unlock();
  1662. return papp;
  1663. }
  1664. }
  1665. // Not found in the list. Try to recycle a blank entry.
  1666. if (!pappBlank)
  1667. {
  1668. // No blank entries found; must make a new one.
  1669. pappBlank = ByUsageAppInfo::Create();
  1670. if (pappBlank && _dpaAppInfo.AppendPtr(pappBlank) < 0)
  1671. {
  1672. delete pappBlank;
  1673. pappBlank = NULL;
  1674. }
  1675. }
  1676. if (pappBlank && pappBlank->Initialize(pszAppPath, this, _fCheckNew, fIgnoreTimestamp))
  1677. {
  1678. ASSERT(pappBlank->IsBlank());
  1679. pappBlank->AddRef();
  1680. }
  1681. else
  1682. {
  1683. pappBlank = NULL;
  1684. }
  1685. Unlock();
  1686. return pappBlank;
  1687. }
  1688. // A shortcut is new if...
  1689. //
  1690. // the shortcut is newly created, and
  1691. // the target is newly created, and
  1692. // neither the shortcut nor the target has been run "in an interesting
  1693. // way".
  1694. //
  1695. // An "interesting way" is "more than one hour after the shortcut/target
  1696. // was created."
  1697. //
  1698. // Note that we test the easiest things first, to avoid hitting
  1699. // the disk too much.
  1700. bool ByUsage::_IsShortcutNew(ByUsageShortcut *pscut, ByUsageAppInfo *papp, const UEMINFO *puei)
  1701. {
  1702. //
  1703. // Shortcut is new if...
  1704. //
  1705. // It was run less than an hour after the app was installed.
  1706. // It was created relatively recently.
  1707. //
  1708. //
  1709. bool fNew = FILETIMEtoInt64(puei->ftExecute) < FILETIMEtoInt64(papp->_ftCreated) + FT_NEWAPPGRACEPERIOD() &&
  1710. _pMenuCache->IsNewlyCreated(&pscut->GetCreatedTime());
  1711. return fNew;
  1712. }
  1713. //****************************************************************************
  1714. // See how many pinned items there are, so we can tell our dad
  1715. // how big we want to be.
  1716. void ByUsage::PrePopulate()
  1717. {
  1718. _FillPinnedItemsCache();
  1719. _NotifyDesiredSize();
  1720. }
  1721. //
  1722. // Enumerating out of cache.
  1723. //
  1724. void ByUsage::EnumFolderFromCache()
  1725. {
  1726. if(SHRestricted(REST_NOSMMFUPROGRAMS)) //If we don't need MFU list,...
  1727. return; // don't enumerate this!
  1728. _pMenuCache->StartEnum();
  1729. LPITEMIDLIST pidlDesktop, pidlCommonDesktop;
  1730. (void)SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOPDIRECTORY, &pidlDesktop);
  1731. (void)SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, &pidlCommonDesktop);
  1732. while (TRUE)
  1733. {
  1734. ByUsageShortcut *pscut = _pMenuCache->GetNextShortcut();
  1735. if (!pscut)
  1736. break;
  1737. if (!pscut->IsInteresting())
  1738. continue;
  1739. // Find out if the item is on the desktop, because we don't track new items on the desktop.
  1740. BOOL fIsDesktop = FALSE;
  1741. if ((pidlDesktop && ILIsEqual(pscut->ParentPidl(), pidlDesktop)) ||
  1742. (pidlCommonDesktop && ILIsEqual(pscut->ParentPidl(), pidlCommonDesktop)) )
  1743. {
  1744. fIsDesktop = TRUE;
  1745. pscut->SetNew(FALSE);
  1746. }
  1747. TraceMsg(TF_PROGLIST, "%p.scut.enum", pscut);
  1748. ByUsageAppInfo *papp = pscut->App();
  1749. if (papp)
  1750. {
  1751. // Now enumerate the item itself. Enumerating an item consists
  1752. // of extracting its UEM data, updating the totals, and possibly
  1753. // marking ourselves as the "best" representative of the associated
  1754. // application.
  1755. //
  1756. //
  1757. UEMINFO uei;
  1758. pscut->GetUEMInfo(&uei);
  1759. // See if this shortcut is still new. If the app is no longer new,
  1760. // then there's no point in keeping track of the shortcut's new-ness.
  1761. if (pscut->IsNew() && papp->_fNew)
  1762. {
  1763. pscut->SetNew(_IsShortcutNew(pscut, papp, &uei));
  1764. }
  1765. //
  1766. // Maybe we are the "best"... Note that we win ties.
  1767. // This ensures that even if an app is never run, *somebody*
  1768. // will be chosen as the "best".
  1769. //
  1770. if (CompareUEMInfo(&uei, &papp->_ueiBest) <= 0)
  1771. {
  1772. papp->_ueiBest = uei;
  1773. papp->_pscutBest = pscut;
  1774. if (!fIsDesktop)
  1775. {
  1776. // Best Start Menu (i.e., non-desktop) item
  1777. papp->_pscutBestSM = pscut;
  1778. }
  1779. TraceMsg(TF_PROGLIST, "%p.scut.winner papp=%p", pscut, papp);
  1780. }
  1781. // Include this file's UEM info in the total
  1782. papp->CombineUEMInfo(&uei, pscut->IsNew(), fIsDesktop);
  1783. }
  1784. }
  1785. _pMenuCache->EndEnum();
  1786. ILFree(pidlCommonDesktop);
  1787. ILFree(pidlDesktop);
  1788. }
  1789. BOOL IsPidlInDPA(LPCITEMIDLIST pidl, CDPAPidl dpa)
  1790. {
  1791. int i;
  1792. for (i = dpa.GetPtrCount()-1; i >= 0; i--)
  1793. {
  1794. if (ILIsEqual(pidl, dpa.FastGetPtr(i)))
  1795. {
  1796. return TRUE;
  1797. }
  1798. }
  1799. return FALSE;
  1800. }
  1801. BOOL ByUsage::_AfterEnumCB(ByUsageAppInfo *papp, AFTERENUMINFO *paei)
  1802. {
  1803. // A ByUsageAppInfo doesn't exist unless there's a ByUsageShortcut
  1804. // that references it or it is pinned...
  1805. if (!papp->IsBlank() && papp->_pscutBest)
  1806. {
  1807. UEMINFO uei;
  1808. papp->GetUEMInfo(&uei);
  1809. papp->CombineUEMInfo(&uei, papp->_IsUEMINFONew(&uei));
  1810. // A file counts on the list only if it has been used
  1811. // and is not pinned. (Pinned items are added to the list
  1812. // elsewhere.)
  1813. //
  1814. // Note that "new" apps are *not* placed on the list until
  1815. // they are used. ("new" apps are highlighted on the
  1816. // Start Menu.)
  1817. if (!papp->_fPinned &&
  1818. papp->_ueiTotal.cHit && FILETIMEtoInt64(papp->_ueiTotal.ftExecute))
  1819. {
  1820. TraceMsg(TF_PROGLIST, "%p.app.add", papp);
  1821. ByUsageItem *pitem = papp->CreateByUsageItem();
  1822. if (pitem)
  1823. {
  1824. LPITEMIDLIST pidl = pitem->CreateFullPidl();
  1825. if (paei->self->_pByUsageUI)
  1826. {
  1827. paei->self->_pByUsageUI->AddItem(pitem, NULL, pidl);
  1828. }
  1829. if (paei->self->_pByUsageDUI)
  1830. {
  1831. paei->self->_pByUsageDUI->AddItem(pitem, NULL, pidl);
  1832. }
  1833. ILFree(pidl);
  1834. }
  1835. }
  1836. else
  1837. {
  1838. TraceMsg(TF_PROGLIST, "%p.app.skip", papp);
  1839. }
  1840. #if 0
  1841. //
  1842. // If you enable this code, then holding down Ctrl and Alt
  1843. // will cause us to pick a program to be new. This is for
  1844. // testing the "new apps" balloon tip.
  1845. //
  1846. #define DEBUG_ForceNewApp() \
  1847. (paei->dpaNew && paei->dpaNew.GetPtrCount() == 0 && \
  1848. GetAsyncKeyState(VK_CONTROL) < 0 && GetAsyncKeyState(VK_MENU) < 0)
  1849. #else
  1850. #define DEBUG_ForceNewApp() FALSE
  1851. #endif
  1852. //
  1853. // Must also check _pscutBestSM because if an app is represented
  1854. // only on the desktop and not on the start menu, then
  1855. // _pscutBestSM will be NULL.
  1856. //
  1857. if (paei->dpaNew && (papp->IsNew() || DEBUG_ForceNewApp()) && papp->_pscutBestSM)
  1858. {
  1859. // NTRAID:193226 We mistakenly treat apps on the desktop
  1860. // as if they were "new".
  1861. // we should only care about apps in the start menu
  1862. TraceMsg(TF_PROGLIST, "%p.app.new(%s)", papp, papp->_pszAppPath);
  1863. LPITEMIDLIST pidl = papp->_pscutBestSM->CreateFullPidl();
  1864. while (pidl)
  1865. {
  1866. LPITEMIDLIST pidlParent = NULL;
  1867. if (paei->dpaNew.AppendPtr(pidl) >= 0)
  1868. {
  1869. pidlParent = ILClone(pidl);
  1870. pidl = NULL; // ownership of pidl transferred to DPA
  1871. if (!ILRemoveLastID(pidlParent) || ILIsEmpty(pidlParent) || IsPidlInDPA(pidlParent, paei->dpaNew))
  1872. {
  1873. // If failure or if we already have it in the list
  1874. ILFree(pidlParent);
  1875. pidlParent = NULL;
  1876. }
  1877. // Remember the creation time of the most recent app
  1878. if (CompareFileTime(&paei->self->_ftNewestApp, &papp->GetCreatedTime()) < 0)
  1879. {
  1880. paei->self->_ftNewestApp = papp->GetCreatedTime();
  1881. }
  1882. // If the shortcut is even newer, then use that.
  1883. // This happens in the "freshly installed Darwin app"
  1884. // case, because Darwin is kinda reluctant to tell
  1885. // us where the EXE is so all we have to go on is
  1886. // the shortcut.
  1887. if (CompareFileTime(&paei->self->_ftNewestApp, &papp->_pscutBestSM->GetCreatedTime()) < 0)
  1888. {
  1889. paei->self->_ftNewestApp = papp->_pscutBestSM->GetCreatedTime();
  1890. }
  1891. }
  1892. ILFree(pidl);
  1893. // Now add the parent to the list also.
  1894. pidl = pidlParent;
  1895. }
  1896. }
  1897. }
  1898. return TRUE;
  1899. }
  1900. BOOL ByUsage::IsSpecialPinnedPidl(LPCITEMIDLIST pidl)
  1901. {
  1902. return _pdirDesktop->Folder()->CompareIDs(0, pidl, _pidlEmail) == S_OK ||
  1903. _pdirDesktop->Folder()->CompareIDs(0, pidl, _pidlBrowser) == S_OK;
  1904. }
  1905. BOOL ByUsage::IsSpecialPinnedItem(ByUsageItem *pitem)
  1906. {
  1907. return IsSpecialPinnedPidl(pitem->RelativePidl());
  1908. }
  1909. //
  1910. // For each app we found, add it to the list.
  1911. //
  1912. void ByUsage::AfterEnumItems()
  1913. {
  1914. //
  1915. // First, all pinned items are enumerated unconditionally.
  1916. //
  1917. if (_rtPinned._sl && _rtPinned._sl.GetPtrCount())
  1918. {
  1919. int i;
  1920. for (i = 0; i < _rtPinned._sl.GetPtrCount(); i++)
  1921. {
  1922. ByUsageShortcut *pscut = _rtPinned._sl.FastGetPtr(i);
  1923. ByUsageItem *pitem = pscut->CreatePinnedItem(i);
  1924. if (pitem)
  1925. {
  1926. // Pinned items are relative to the desktop, so we can
  1927. // save ourselves an ILClone because the relative pidl
  1928. // is equal to the absolute pidl.
  1929. ASSERT(pitem->Dir() == _pdirDesktop);
  1930. //
  1931. // Special handling for E-mail and Internet pinned items
  1932. //
  1933. if (IsSpecialPinnedItem(pitem))
  1934. {
  1935. pitem->EnableSubtitle();
  1936. }
  1937. if (_pByUsageUI)
  1938. _pByUsageUI->AddItem(pitem, NULL, pscut->RelativePidl());
  1939. }
  1940. }
  1941. }
  1942. //
  1943. // Now add the separator after the pinned items.
  1944. //
  1945. ByUsageItem *pitem = ByUsageItem::CreateSeparator();
  1946. if (pitem && _pByUsageUI)
  1947. {
  1948. _pByUsageUI->AddItem(pitem, NULL, NULL);
  1949. }
  1950. //
  1951. // Now walk through all the regular items.
  1952. //
  1953. // PERF: Can skip this if _cMFUDesired==0 and "highlight new apps" is off
  1954. //
  1955. AFTERENUMINFO aei;
  1956. aei.self = this;
  1957. aei.dpaNew.Create(4); // Will check failure in callback
  1958. ByUsageAppInfoList *pdpaAppInfo = _pMenuCache->GetAppList();
  1959. pdpaAppInfo->EnumCallbackEx(_AfterEnumCB, &aei);
  1960. // Now that we have the official list of new items, tell the
  1961. // foreground thread to pick it up. We don't update the master
  1962. // copy in-place for three reasons.
  1963. //
  1964. // 1. It generates contention since both the foreground and
  1965. // background threads would be accessing it simultaneously.
  1966. // This means more critical sections (yuck).
  1967. // 2. It means that items that were new and are still new have
  1968. // a brief period where they are no longer new because we
  1969. // are rebuilding the list.
  1970. // 3. By having only one thread access the master copy, we avoid
  1971. // synchronization issues.
  1972. if (aei.dpaNew && _pByUsageUI && _pByUsageUI->_hwnd && SendNotifyMessage(_pByUsageUI->_hwnd, BUM_SETNEWITEMS, 0, (LPARAM)(HDPA)aei.dpaNew))
  1973. {
  1974. aei.dpaNew.Detach(); // Successfully delivered
  1975. }
  1976. // If we were unable to deliver the new HDPA, then destroy it here
  1977. // so we don't leak.
  1978. if (aei.dpaNew)
  1979. {
  1980. aei.dpaNew.DestroyCallback(ILFreeCallback, NULL);
  1981. }
  1982. if (!_fUEMRegistered)
  1983. {
  1984. // Register with UEM DB if we haven't done it yet
  1985. ASSERT(!_pMenuCache->IsLocked());
  1986. _fUEMRegistered = SUCCEEDED(UEMRegisterNotify(UEMNotifyCB, static_cast<void *>(this)));
  1987. }
  1988. }
  1989. int ByUsage::UEMNotifyCB(void *param, const GUID *pguidGrp, int eCmd)
  1990. {
  1991. ByUsage *pbu = reinterpret_cast<ByUsage *>(param);
  1992. // Refresh our list whenever a new app is started.
  1993. // or when the session changes (because that changes all the usage counts)
  1994. switch (eCmd)
  1995. {
  1996. case UEME_CTLSESSION:
  1997. if (IsEqualGUID(*pguidGrp, UEMIID_BROWSER))
  1998. break;
  1999. // Fall thru
  2000. case UEME_RUNPIDL:
  2001. case UEME_RUNPATH:
  2002. if (pbu && pbu->_pByUsageUI)
  2003. {
  2004. pbu->_pByUsageUI->Invalidate();
  2005. pbu->_pByUsageUI->StartRefreshTimer();
  2006. }
  2007. break;
  2008. default:
  2009. // Do nothing
  2010. ;
  2011. }
  2012. return 0;
  2013. }
  2014. BOOL CreateExcludedDirectoriesDPA(const int rgcsidlExclude[], CDPA<TCHAR> *pdpaExclude)
  2015. {
  2016. if (*pdpaExclude)
  2017. {
  2018. pdpaExclude->EnumCallback(LocalFreeCallback, NULL);
  2019. pdpaExclude->DeleteAllPtrs();
  2020. }
  2021. else if (!pdpaExclude->Create(4))
  2022. {
  2023. return FALSE;
  2024. }
  2025. ASSERT(*pdpaExclude);
  2026. ASSERT(pdpaExclude->GetPtrCount() == 0);
  2027. int i = 0;
  2028. while (rgcsidlExclude[i] != -1)
  2029. {
  2030. TCHAR szPath[MAX_PATH];
  2031. // Note: This call can legitimately fail if the corresponding
  2032. // folder does not exist, so don't get upset. Less work for us!
  2033. if (SUCCEEDED(SHGetFolderPath(NULL, rgcsidlExclude[i], NULL, SHGFP_TYPE_CURRENT, szPath)))
  2034. {
  2035. AppendString(*pdpaExclude, szPath);
  2036. }
  2037. i++;
  2038. }
  2039. return TRUE;
  2040. }
  2041. BOOL CMenuItemsCache::_GetExcludedDirectories()
  2042. {
  2043. //
  2044. // The directories we exclude from enumeration - Shortcuts in these
  2045. // folders are never candidates for inclusion.
  2046. //
  2047. static const int c_rgcsidlUninterestingDirectories[] = {
  2048. CSIDL_ALTSTARTUP,
  2049. CSIDL_STARTUP,
  2050. CSIDL_COMMON_ALTSTARTUP,
  2051. CSIDL_COMMON_STARTUP,
  2052. -1 // End marker
  2053. };
  2054. return CreateExcludedDirectoriesDPA(c_rgcsidlUninterestingDirectories, &_dpaNotInteresting);
  2055. }
  2056. BOOL CMenuItemsCache::_IsExcludedDirectory(IShellFolder *psf, LPCITEMIDLIST pidl, DWORD dwAttributes)
  2057. {
  2058. if (_enumfl & ENUMFL_NORECURSE)
  2059. return TRUE;
  2060. if (!(dwAttributes & SFGAO_FILESYSTEM))
  2061. return TRUE;
  2062. // SFGAO_LINK | SFGAO_FOLDER = folder shortcut.
  2063. // We want to exclude those because we can get blocked
  2064. // on network stuff
  2065. if (dwAttributes & SFGAO_LINK)
  2066. return TRUE;
  2067. return FALSE;
  2068. }
  2069. BOOL CMenuItemsCache::_IsInterestingDirectory(ByUsageDir *pdir)
  2070. {
  2071. STRRET str;
  2072. TCHAR szPath[MAX_PATH];
  2073. if (SUCCEEDED(_pdirDesktop->Folder()->GetDisplayNameOf(pdir->Pidl(), SHGDN_FORPARSING, &str)) &&
  2074. SUCCEEDED(StrRetToBuf(&str, pdir->Pidl(), szPath, ARRAYSIZE(szPath))))
  2075. {
  2076. int i;
  2077. for (i = _dpaNotInteresting.GetPtrCount() - 1; i >= 0; i--)
  2078. {
  2079. if (lstrcmpi(_dpaNotInteresting.FastGetPtr(i), szPath) == 0)
  2080. {
  2081. return FALSE;
  2082. }
  2083. }
  2084. }
  2085. return TRUE;
  2086. }
  2087. void ByUsage::OnPinListChange()
  2088. {
  2089. _pByUsageUI->Invalidate();
  2090. PostMessage(_pByUsageUI->_hwnd, ByUsageUI::SFTBM_REFRESH, TRUE, 0);
  2091. }
  2092. void ByUsage::OnChangeNotify(UINT id, LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
  2093. {
  2094. if (id == NOTIFY_PINCHANGE)
  2095. {
  2096. if (lEvent == SHCNE_EXTENDED_EVENT && pidl1)
  2097. {
  2098. SHChangeDWORDAsIDList *pdwidl = (SHChangeDWORDAsIDList *)pidl1;
  2099. if (pdwidl->dwItem1 == SHCNEE_PINLISTCHANGED)
  2100. {
  2101. OnPinListChange();
  2102. }
  2103. }
  2104. }
  2105. else if (_pMenuCache)
  2106. {
  2107. _pMenuCache->OnChangeNotify(id, lEvent, pidl1, pidl2);
  2108. }
  2109. }
  2110. void CMenuItemsCache::OnChangeNotify(UINT id, LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
  2111. {
  2112. ASSERT(id < min(MAXNOTIFY, NUM_PROGLIST_ROOTS));
  2113. if (id < NUM_PROGLIST_ROOTS)
  2114. {
  2115. _rgrt[id].SetNeedRefresh();
  2116. _fIsCacheUpToDate = FALSE;
  2117. // Once we get one notification, there's no point in listening to further
  2118. // notifications until our next enumeration. This keeps us from churning
  2119. // while Winstones are running.
  2120. if (_pByUsageUI)
  2121. {
  2122. ASSERT(!IsLocked());
  2123. _pByUsageUI->UnregisterNotify(id);
  2124. _rgrt[id].ClearRegistered();
  2125. _pByUsageUI->Invalidate();
  2126. _pByUsageUI->RefreshNow();
  2127. }
  2128. }
  2129. }
  2130. void CMenuItemsCache::UnregisterNotifyAll()
  2131. {
  2132. if (_pByUsageUI)
  2133. {
  2134. UINT id;
  2135. for (id = 0; id < NUM_PROGLIST_ROOTS; id++)
  2136. {
  2137. _rgrt[id].ClearRegistered();
  2138. _pByUsageUI->UnregisterNotify(id);
  2139. }
  2140. }
  2141. }
  2142. inline LRESULT ByUsage::_OnNotify(LPNMHDR pnm)
  2143. {
  2144. switch (pnm->code)
  2145. {
  2146. case SMN_MODIFYSMINFO:
  2147. return _ModifySMInfo(CONTAINING_RECORD(pnm, SMNMMODIFYSMINFO, hdr));
  2148. }
  2149. return 0;
  2150. }
  2151. //
  2152. // We need this message to avoid a race condition between the background
  2153. // thread (the enumerator) and the foreground thread. So the rule is
  2154. // that only the foreground thread is allowd to mess with _dpaNew.
  2155. // The background thread collects the information it wants into a
  2156. // separate DPA and hands it to us on the foreground thread, where we
  2157. // can safely set it into _dpaNew without encountering a race condition.
  2158. //
  2159. inline LRESULT ByUsage::_OnSetNewItems(HDPA hdpaNew)
  2160. {
  2161. CDPAPidl dpaNew(hdpaNew);
  2162. //
  2163. // Most of the time, there are no new apps and there were no new apps
  2164. // last time either. Short-circuit this case...
  2165. //
  2166. int cNew = _dpaNew ? _dpaNew.GetPtrCount() : 0;
  2167. if (cNew == 0 && dpaNew.GetPtrCount() == 0)
  2168. {
  2169. // Both old and new are empty. We're finished.
  2170. // (Since we own dpaNew, free it to avoid a memory leak.)
  2171. dpaNew.DestroyCallback(ILFreeCallback, NULL);
  2172. return 0;
  2173. }
  2174. // Now swap the new DPA in
  2175. if (_dpaNew)
  2176. {
  2177. _dpaNew.DestroyCallback(ILFreeCallback, NULL);
  2178. }
  2179. _dpaNew.Attach(hdpaNew);
  2180. // Tell our dad that we can identify new items
  2181. // Also tell him the timestamp of the most recent app
  2182. // (so he can tell whether or not to restart the "offer new apps" counter)
  2183. SMNMHAVENEWITEMS nmhni;
  2184. nmhni.ftNewestApp = _ftNewestApp;
  2185. _SendNotify(_pByUsageUI->_hwnd, SMN_HAVENEWITEMS, &nmhni.hdr);
  2186. return 0;
  2187. }
  2188. LRESULT ByUsage::OnWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  2189. {
  2190. switch (uMsg)
  2191. {
  2192. case WM_NOTIFY:
  2193. return _OnNotify(reinterpret_cast<LPNMHDR>(lParam));
  2194. case BUM_SETNEWITEMS:
  2195. return _OnSetNewItems(reinterpret_cast<HDPA>(lParam));
  2196. case WM_SETTINGCHANGE:
  2197. static const TCHAR c_szClients[] = TEXT("Software\\Clients");
  2198. if ((wParam == 0 && lParam == 0) || // wildcard
  2199. (lParam && StrCmpNIC((LPCTSTR)lParam, c_szClients, ARRAYSIZE(c_szClients) - 1) == 0)) // client change
  2200. {
  2201. _pByUsageUI->ForceChange(); // even though the pidls didn't change, their targets did
  2202. _ulPinChange = -1; // Force reload even if list didn't change
  2203. OnPinListChange(); // reload the pin list (since a client changed)
  2204. }
  2205. break;
  2206. }
  2207. // Else fall back to parent implementation
  2208. return _pByUsageUI->SFTBarHost::OnWndProc(hwnd, uMsg, wParam, lParam);
  2209. }
  2210. LRESULT ByUsage::_ModifySMInfo(PSMNMMODIFYSMINFO pmsi)
  2211. {
  2212. LPSMDATA psmd = pmsi->psmd;
  2213. // Do this only if there is a ShellFolder. We don't want to fault
  2214. // on the static menu items.
  2215. if ((psmd->dwMask & SMDM_SHELLFOLDER) && _dpaNew)
  2216. {
  2217. // NTRAID:135699: this needs big-time optimization
  2218. // E.g., remember the previous folder if there was nothing found
  2219. LPITEMIDLIST pidl = NULL;
  2220. IAugmentedShellFolder2* pasf2;
  2221. if (SUCCEEDED(psmd->psf->QueryInterface(IID_PPV_ARG(IAugmentedShellFolder2, &pasf2))))
  2222. {
  2223. LPITEMIDLIST pidlFolder;
  2224. LPITEMIDLIST pidlItem;
  2225. if (SUCCEEDED(pasf2->UnWrapIDList(psmd->pidlItem, 1, NULL, &pidlFolder, &pidlItem, NULL)))
  2226. {
  2227. pidl = ILCombine(pidlFolder, pidlItem);
  2228. ILFree(pidlFolder);
  2229. ILFree(pidlItem);
  2230. }
  2231. pasf2->Release();
  2232. }
  2233. if (!pidl)
  2234. {
  2235. pidl = ILCombine(psmd->pidlFolder, psmd->pidlItem);
  2236. }
  2237. if (pidl)
  2238. {
  2239. if (IsPidlInDPA(pidl, _dpaNew))
  2240. {
  2241. // Designers say: New items should never be demoted
  2242. pmsi->psminfo->dwFlags |= SMIF_NEW;
  2243. pmsi->psminfo->dwFlags &= ~SMIF_DEMOTED;
  2244. }
  2245. ILFree(pidl);
  2246. }
  2247. }
  2248. return 0;
  2249. }
  2250. void ByUsage::_FillPinnedItemsCache()
  2251. {
  2252. if(SHRestricted(REST_NOSMPINNEDLIST)) //If no pinned list is allowed,.....
  2253. return; //....there is nothing to do!
  2254. ULONG ulPinChange;
  2255. _psmpin->GetChangeCount(&ulPinChange);
  2256. if (_ulPinChange == ulPinChange)
  2257. {
  2258. // No change in pin list; do not need to reload
  2259. return;
  2260. }
  2261. _ulPinChange = ulPinChange;
  2262. _rtPinned.Reset();
  2263. if (_rtPinned._sl.Create(4))
  2264. {
  2265. IEnumIDList *penum;
  2266. if (SUCCEEDED(_psmpin->EnumObjects(&penum)))
  2267. {
  2268. LPITEMIDLIST pidl;
  2269. while (penum->Next(1, &pidl, NULL) == S_OK)
  2270. {
  2271. IShellLink *psl;
  2272. HRESULT hr;
  2273. ByUsageHiddenData hd;
  2274. //
  2275. // If we have a shortcut, do bookkeeping based on the shortcut
  2276. // target. Otherwise do it based on the pinned object itself.
  2277. // Note that we do not go through _PathIsInterestingExe
  2278. // because all pinned items are interesting.
  2279. hr = SHGetUIObjectFromFullPIDL(pidl, NULL, IID_PPV_ARG(IShellLink, &psl));
  2280. if (SUCCEEDED(hr))
  2281. {
  2282. hd.LoadFromShellLink(psl);
  2283. psl->Release();
  2284. // We do not need to SHRegisterDarwinLink because the only
  2285. // reason for getting the MSI path is so pinned items can
  2286. // prevent items on the Start Menu from appearing in the MFU.
  2287. // So let the shortcut on the Start Menu do the registration.
  2288. // (If there is none, then that's even better - no work to do!)
  2289. hd.UpdateMSIPath();
  2290. }
  2291. if (FAILED(hr))
  2292. {
  2293. hr = DisplayNameOfAsOLESTR(_pdirDesktop->Folder(), pidl, SHGDN_FORPARSING, &hd._pwszTargetPath);
  2294. }
  2295. //
  2296. // If we were able to figure out what the pinned object is,
  2297. // use that information to block the app from also appearing
  2298. // in the MFU.
  2299. //
  2300. // Inability to identify the pinned
  2301. // object is not grounds for rejection. A pinned items is
  2302. // of great sentimental value to the user.
  2303. //
  2304. if (FAILED(hr))
  2305. {
  2306. ASSERT(hd.IsClear());
  2307. }
  2308. ByUsageShortcut *pscut = _pMenuCache->CreateShortcutFromHiddenData(_pdirDesktop, pidl, &hd, TRUE);
  2309. if (pscut)
  2310. {
  2311. if (_rtPinned._sl.AppendPtr(pscut) >= 0)
  2312. {
  2313. pscut->SetInteresting(true); // Pinned items are always interesting
  2314. if (IsSpecialPinnedPidl(pidl))
  2315. {
  2316. ByUsageAppInfo *papp = _pMenuCache->GetAppInfoFromSpecialPidl(pidl);
  2317. pscut->SetApp(papp);
  2318. if (papp) papp->Release();
  2319. }
  2320. }
  2321. else
  2322. {
  2323. // Couldn't append; oh well
  2324. delete pscut; // "delete" can handle NULL pointer
  2325. }
  2326. }
  2327. hd.Clear();
  2328. ILFree(pidl);
  2329. }
  2330. penum->Release();
  2331. }
  2332. }
  2333. }
  2334. IAssociationElement *GetAssociationElementFromSpecialPidl(IShellFolder *psf, LPCITEMIDLIST pidlItem)
  2335. {
  2336. IAssociationElement *pae = NULL;
  2337. // There is no way to get the IAssociationElement directly, so
  2338. // we get the IExtractIcon and then ask him for the IAssociationElement.
  2339. IExtractIcon *pxi;
  2340. if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, &pidlItem, IID_PPV_ARG_NULL(IExtractIcon, &pxi))))
  2341. {
  2342. IUnknown_QueryService(pxi, IID_IAssociationElement, IID_PPV_ARG(IAssociationElement, &pae));
  2343. pxi->Release();
  2344. }
  2345. return pae;
  2346. }
  2347. //
  2348. // On success, the returned ByUsageAppInfo has been AddRef()d
  2349. //
  2350. ByUsageAppInfo *CMenuItemsCache::GetAppInfoFromSpecialPidl(LPCITEMIDLIST pidl)
  2351. {
  2352. ByUsageAppInfo *papp = NULL;
  2353. IAssociationElement *pae = GetAssociationElementFromSpecialPidl(_pdirDesktop->Folder(), pidl);
  2354. if (pae)
  2355. {
  2356. LPWSTR pszData;
  2357. if (SUCCEEDED(pae->QueryString(AQVS_APPLICATION_PATH, L"open", &pszData)))
  2358. {
  2359. //
  2360. // HACK! Outlook puts the short file name in the registry.
  2361. // Convert to long file name (if it won't cost too much) so
  2362. // people who select Outlook as their default mail client
  2363. // won't get a dup copy in the MFU.
  2364. //
  2365. LPTSTR pszPath = pszData;
  2366. TCHAR szLFN[MAX_PATH];
  2367. if (!PathIsNetworkPath(pszData))
  2368. {
  2369. DWORD dwLen = GetLongPathName(pszData, szLFN, ARRAYSIZE(szLFN));
  2370. if (dwLen && dwLen < ARRAYSIZE(szLFN))
  2371. {
  2372. pszPath = szLFN;
  2373. }
  2374. }
  2375. papp = GetAppInfo(pszPath, true);
  2376. SHFree(pszData);
  2377. }
  2378. pae->Release();
  2379. }
  2380. return papp;
  2381. }
  2382. void ByUsage::_EnumPinnedItemsFromCache()
  2383. {
  2384. if (_rtPinned._sl)
  2385. {
  2386. int i;
  2387. for (i = 0; i < _rtPinned._sl.GetPtrCount(); i++)
  2388. {
  2389. ByUsageShortcut *pscut = _rtPinned._sl.FastGetPtr(i);
  2390. TraceMsg(TF_PROGLIST, "%p.scut.enumC", pscut);
  2391. // Enumerating a pinned item consists of marking the corresponding
  2392. // application as "I am pinned, do not mess with me!"
  2393. ByUsageAppInfo *papp = pscut->App();
  2394. if (papp)
  2395. {
  2396. papp->_fPinned = TRUE;
  2397. TraceMsg(TF_PROGLIST, "%p.scut.pin papp=%p", pscut, papp);
  2398. }
  2399. }
  2400. }
  2401. }
  2402. const struct CMenuItemsCache::ROOTFOLDERINFO CMenuItemsCache::c_rgrfi[] = {
  2403. { CSIDL_STARTMENU, ENUMFL_RECURSE | ENUMFL_CHECKNEW | ENUMFL_ISSTARTMENU },
  2404. { CSIDL_PROGRAMS, ENUMFL_RECURSE | ENUMFL_CHECKNEW | ENUMFL_CHECKISCHILDOFPREVIOUS },
  2405. { CSIDL_COMMON_STARTMENU, ENUMFL_RECURSE | ENUMFL_CHECKNEW | ENUMFL_ISSTARTMENU },
  2406. { CSIDL_COMMON_PROGRAMS, ENUMFL_RECURSE | ENUMFL_CHECKNEW | ENUMFL_CHECKISCHILDOFPREVIOUS },
  2407. { CSIDL_DESKTOPDIRECTORY, ENUMFL_NORECURSE | ENUMFL_NOCHECKNEW },
  2408. { CSIDL_COMMON_DESKTOPDIRECTORY, ENUMFL_NORECURSE | ENUMFL_NOCHECKNEW }, // The limit for register notify is currently 5 (slots 0 through 4)
  2409. // Changing this requires changing ByUsageUI::SFTHOST_MAXNOTIFY
  2410. };
  2411. //
  2412. // Here's where we decide all the things that should be enumerated
  2413. // in the "My Programs" list.
  2414. //
  2415. void ByUsage::EnumItems()
  2416. {
  2417. _FillPinnedItemsCache();
  2418. _NotifyDesiredSize();
  2419. _pMenuCache->LockPopup();
  2420. _pMenuCache->InitCache();
  2421. BOOL fNeedUpdateDarwin = !_pMenuCache->IsCacheUpToDate();
  2422. // Note! UpdateCache() must occur before _EnumPinnedItemsFromCache()
  2423. // because UpdateCache() resets _fPinned.
  2424. _pMenuCache->UpdateCache();
  2425. if (fNeedUpdateDarwin)
  2426. {
  2427. SHReValidateDarwinCache();
  2428. }
  2429. _pMenuCache->RefreshDarwinShortcuts(&_rtPinned);
  2430. _EnumPinnedItemsFromCache();
  2431. EnumFolderFromCache();
  2432. // Finished collecting data; do some postprocessing...
  2433. AfterEnumItems();
  2434. // Do not unlock before this point, as AfterEnumItems depends on the cache to stay put.
  2435. _pMenuCache->UnlockPopup();
  2436. }
  2437. void ByUsage::_NotifyDesiredSize()
  2438. {
  2439. if (_pByUsageUI)
  2440. {
  2441. int cPinned = _rtPinned._sl ? _rtPinned._sl.GetPtrCount() : 0;
  2442. int cNormal;
  2443. DWORD cb = sizeof(cNormal);
  2444. if (SHGetValue(HKEY_CURRENT_USER, REGSTR_PATH_STARTPANE_SETTINGS,
  2445. REGSTR_VAL_DV2_MINMFU, NULL, &cNormal, &cb) != ERROR_SUCCESS)
  2446. {
  2447. cNormal = REGSTR_VAL_DV2_MINMFU_DEFAULT;
  2448. }
  2449. _cMFUDesired = cNormal;
  2450. _pByUsageUI->SetDesiredSize(cPinned, cNormal);
  2451. }
  2452. }
  2453. //****************************************************************************
  2454. // CMenuItemsCache
  2455. CMenuItemsCache::CMenuItemsCache() : _cref(1)
  2456. {
  2457. }
  2458. LONG CMenuItemsCache::AddRef()
  2459. {
  2460. return InterlockedIncrement(&_cref);
  2461. }
  2462. LONG CMenuItemsCache::Release()
  2463. {
  2464. ASSERT( 0 != _cref );
  2465. LONG cRef = InterlockedDecrement(&_cref);
  2466. if ( 0 == cRef )
  2467. {
  2468. delete this;
  2469. }
  2470. return cRef;
  2471. }
  2472. HRESULT CMenuItemsCache::Initialize(ByUsageUI *pbuUI, FILETIME * pftOSInstall)
  2473. {
  2474. HRESULT hr = S_OK;
  2475. // Must do this before any of the operations that can fail
  2476. // because we unconditionally call DeleteCriticalSection in destructor
  2477. _fCSInited = InitializeCriticalSectionAndSpinCount(&_csInUse, 0);
  2478. if (!_fCSInited)
  2479. {
  2480. return E_OUTOFMEMORY;
  2481. }
  2482. hr = AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &_pqa));
  2483. if (FAILED(hr))
  2484. {
  2485. return hr;
  2486. }
  2487. _pByUsageUI = pbuUI;
  2488. _ftOldApps = *pftOSInstall;
  2489. _pdirDesktop = ByUsageDir::CreateDesktop();
  2490. if (!_dpaAppInfo.Create(4))
  2491. {
  2492. hr = E_OUTOFMEMORY;
  2493. }
  2494. if (!_GetExcludedDirectories())
  2495. {
  2496. hr = E_OUTOFMEMORY;
  2497. }
  2498. if (!_dpaKill.Create(4) ||
  2499. !_dpaKillLink.Create(4)) {
  2500. return E_OUTOFMEMORY;
  2501. }
  2502. _InitKillList();
  2503. _hPopupReady = CreateMutex(NULL, FALSE, NULL);
  2504. if (!_hPopupReady)
  2505. {
  2506. return E_OUTOFMEMORY;
  2507. }
  2508. // By default, we want to check applications for newness.
  2509. _fCheckNew = TRUE;
  2510. return hr;
  2511. }
  2512. HRESULT CMenuItemsCache::AttachUI(ByUsageUI *pbuUI)
  2513. {
  2514. // We do not AddRef here so that destruction always happens on the same thread that created the object
  2515. // but beware of lifetime issues: we need to synchronize attachUI/detachUI operations with LockPopup and UnlockPopup.
  2516. LockPopup();
  2517. _pByUsageUI = pbuUI;
  2518. UnlockPopup();
  2519. return S_OK;
  2520. }
  2521. CMenuItemsCache::~CMenuItemsCache()
  2522. {
  2523. if (_fIsCacheUpToDate)
  2524. {
  2525. _SaveCache();
  2526. }
  2527. if (_dpaNotInteresting)
  2528. {
  2529. _dpaNotInteresting.DestroyCallback(LocalFreeCallback, NULL);
  2530. }
  2531. if (_dpaKill)
  2532. {
  2533. _dpaKill.DestroyCallback(LocalFreeCallback, NULL);
  2534. }
  2535. if (_dpaKillLink)
  2536. {
  2537. _dpaKillLink.DestroyCallback(LocalFreeCallback, NULL);
  2538. }
  2539. // Must delete the roots before destroying _dpaAppInfo.
  2540. int i;
  2541. for (i = 0; i < ARRAYSIZE(_rgrt); i++)
  2542. {
  2543. _rgrt[i].Reset();
  2544. }
  2545. ATOMICRELEASE(_pqa);
  2546. DPADELETEANDDESTROY(_dpaAppInfo);
  2547. if (_pdirDesktop)
  2548. {
  2549. _pdirDesktop->Release();
  2550. }
  2551. if (_hPopupReady)
  2552. {
  2553. CloseHandle(_hPopupReady);
  2554. }
  2555. if (_fCSInited)
  2556. {
  2557. DeleteCriticalSection(&_csInUse);
  2558. }
  2559. }
  2560. BOOL CMenuItemsCache::_ShouldProcessRoot(int iRoot)
  2561. {
  2562. BOOL fRet = TRUE;
  2563. if (!_rgrt[iRoot]._pidl)
  2564. {
  2565. fRet = FALSE;
  2566. }
  2567. else if ((c_rgrfi[iRoot]._enumfl & ENUMFL_CHECKISCHILDOFPREVIOUS) && !SHRestricted(REST_NOSTARTMENUSUBFOLDERS) )
  2568. {
  2569. ASSERT(iRoot >= 1);
  2570. if (_rgrt[iRoot-1]._pidl && ILIsParent(_rgrt[iRoot-1]._pidl, _rgrt[iRoot]._pidl, FALSE))
  2571. {
  2572. fRet = FALSE;
  2573. }
  2574. }
  2575. return fRet;
  2576. }
  2577. //****************************************************************************
  2578. //
  2579. // The format of the ProgramsCache is as follows:
  2580. //
  2581. // [DWORD] dwVersion
  2582. //
  2583. // If the version is wrong, then ignore. Not worth trying to design
  2584. // a persistence format that is forward-compatible since it's just
  2585. // a cache.
  2586. //
  2587. // Don't be stingy about incrementing the dwVersion. We've got room
  2588. // for four billion revs.
  2589. #define PROGLIST_VERSION 9
  2590. //
  2591. //
  2592. // For each special folder we persist:
  2593. //
  2594. // [BYTE] CSIDL_xxx (as a sanity check)
  2595. //
  2596. // Followed by a sequence of segments; either...
  2597. //
  2598. // [BYTE] 0x00 -- Change directory
  2599. // [pidl] directory (relative to CSIDL_xxx)
  2600. //
  2601. // or
  2602. //
  2603. // [BYTE] 0x01 -- Add shortcut
  2604. // [pidl] item (relative to current directory)
  2605. //
  2606. // or
  2607. //
  2608. // [BYTE] 0x02 -- end
  2609. //
  2610. #define CACHE_CHDIR 0
  2611. #define CACHE_ITEM 1
  2612. #define CACHE_END 2
  2613. BOOL CMenuItemsCache::InitCache()
  2614. {
  2615. COMPILETIME_ASSERT(ARRAYSIZE(c_rgrfi) == NUM_PROGLIST_ROOTS);
  2616. // Make sure we don't use more than MAXNOTIFY notify slots for the cache
  2617. COMPILETIME_ASSERT( NUM_PROGLIST_ROOTS <= MAXNOTIFY);
  2618. if (_fIsInited)
  2619. return TRUE;
  2620. BOOL fSuccess = FALSE;
  2621. int irfi;
  2622. IStream *pstm = SHOpenRegStream2(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS, REGSTR_VAL_PROGLIST, STGM_READ);
  2623. if (pstm)
  2624. {
  2625. ByUsageDir *pdirRoot = NULL;
  2626. ByUsageDir *pdir = NULL;
  2627. DWORD dwVersion;
  2628. if (FAILED(IStream_Read(pstm, &dwVersion, sizeof(dwVersion))) ||
  2629. dwVersion != PROGLIST_VERSION)
  2630. {
  2631. goto panic;
  2632. }
  2633. for (irfi = 0; irfi < ARRAYSIZE(c_rgrfi); irfi++)
  2634. {
  2635. ByUsageRoot *prt = &_rgrt[irfi];
  2636. // If SHGetSpecialFolderLocation fails, it could mean that
  2637. // the directory was recently restricted. We *could* just
  2638. // skip over this block and go to the next csidl, but that
  2639. // would be actual work, and this is just a cache, so we may
  2640. // as well just panic and re-enumerate from scratch.
  2641. //
  2642. if (FAILED(SHGetSpecialFolderLocation(NULL, c_rgrfi[irfi]._csidl, &prt->_pidl)))
  2643. {
  2644. goto panic;
  2645. }
  2646. if (!_ShouldProcessRoot(irfi))
  2647. continue;
  2648. if (!prt->_sl.Create(4))
  2649. {
  2650. goto panic;
  2651. }
  2652. BYTE csidl;
  2653. if (FAILED(IStream_Read(pstm, &csidl, sizeof(csidl))) ||
  2654. csidl != c_rgrfi[irfi]._csidl)
  2655. {
  2656. goto panic;
  2657. }
  2658. pdirRoot = ByUsageDir::Create(_pdirDesktop, prt->_pidl);
  2659. if (!pdirRoot)
  2660. {
  2661. goto panic;
  2662. }
  2663. BYTE bCmd;
  2664. do
  2665. {
  2666. LPITEMIDLIST pidl;
  2667. if (FAILED(IStream_Read(pstm, &bCmd, sizeof(bCmd))))
  2668. {
  2669. goto panic;
  2670. }
  2671. switch (bCmd)
  2672. {
  2673. case CACHE_CHDIR:
  2674. // Toss the old directory
  2675. if (pdir)
  2676. {
  2677. pdir->Release();
  2678. pdir = NULL;
  2679. }
  2680. // Figure out where the new directory is
  2681. if (FAILED(IStream_ReadPidl(pstm, &pidl)))
  2682. {
  2683. goto panic;
  2684. }
  2685. // and create it
  2686. pdir = ByUsageDir::Create(pdirRoot, pidl);
  2687. ILFree(pidl);
  2688. if (!pdir)
  2689. {
  2690. goto panic;
  2691. }
  2692. break;
  2693. case CACHE_ITEM:
  2694. {
  2695. // Must set a directory befor creating an item
  2696. if (!pdir)
  2697. {
  2698. goto panic;
  2699. }
  2700. // Get the new item
  2701. if (FAILED(IStream_ReadPidl(pstm, &pidl)))
  2702. {
  2703. goto panic;
  2704. }
  2705. // Create it
  2706. ByUsageShortcut *pscut = _CreateFromCachedPidl(prt, pdir, pidl);
  2707. ILFree(pidl);
  2708. if (!pscut)
  2709. {
  2710. goto panic;
  2711. }
  2712. }
  2713. break;
  2714. case CACHE_END:
  2715. break;
  2716. default:
  2717. goto panic;
  2718. }
  2719. }
  2720. while (bCmd != CACHE_END);
  2721. pdirRoot->Release();
  2722. pdirRoot = NULL;
  2723. if (pdir)
  2724. {
  2725. pdir->Release();
  2726. pdir = NULL;
  2727. }
  2728. prt->SetNeedRefresh();
  2729. }
  2730. fSuccess = TRUE;
  2731. panic:
  2732. if (!fSuccess)
  2733. {
  2734. for (irfi = 0; irfi < ARRAYSIZE(c_rgrfi); irfi++)
  2735. {
  2736. _rgrt[irfi].Reset();
  2737. }
  2738. }
  2739. if (pdirRoot)
  2740. {
  2741. pdirRoot->Release();
  2742. }
  2743. if (pdir)
  2744. {
  2745. pdir->Release();
  2746. }
  2747. pstm->Release();
  2748. }
  2749. _fIsInited = TRUE;
  2750. return fSuccess;
  2751. }
  2752. HRESULT CMenuItemsCache::UpdateCache()
  2753. {
  2754. FILETIME ft;
  2755. // Apps are "new" only if installed less than 1 week ago.
  2756. // They also must postdate the user's first use of the new Start Menu.
  2757. GetSystemTimeAsFileTime(&ft);
  2758. DecrementFILETIME(&ft, FT_ONEDAY * 7);
  2759. // _ftOldApps is the more recent of OS install time, or last week.
  2760. if (CompareFileTime(&ft, &_ftOldApps) >= 0)
  2761. {
  2762. _ftOldApps = ft;
  2763. }
  2764. _dpaAppInfo.EnumCallbackEx(ByUsageAppInfo::EnumResetCB, this);
  2765. if(!SHRestricted(REST_NOSMMFUPROGRAMS))
  2766. {
  2767. int i;
  2768. for (i = 0; i < ARRAYSIZE(c_rgrfi); i++)
  2769. {
  2770. ByUsageRoot *prt = &_rgrt[i];
  2771. int csidl = c_rgrfi[i]._csidl;
  2772. _enumfl = c_rgrfi[i]._enumfl;
  2773. if (!prt->_pidl)
  2774. {
  2775. (void)SHGetSpecialFolderLocation(NULL, csidl, &prt->_pidl); // void cast to keep prefast happy
  2776. prt->SetNeedRefresh();
  2777. }
  2778. if (!_ShouldProcessRoot(i))
  2779. continue;
  2780. // Restrictions might deny recursing into subfolders
  2781. if ((_enumfl & ENUMFL_ISSTARTMENU) && SHRestricted(REST_NOSTARTMENUSUBFOLDERS))
  2782. {
  2783. _enumfl &= ~ENUMFL_RECURSE;
  2784. _enumfl |= ENUMFL_NORECURSE;
  2785. }
  2786. // Fill the cache if it is stale
  2787. LPITEMIDLIST pidl;
  2788. if (!IsRestrictedCsidl(csidl) &&
  2789. SUCCEEDED(SHGetSpecialFolderLocation(NULL, csidl, &pidl)))
  2790. {
  2791. if (prt->_pidl == NULL || !ILIsEqual(prt->_pidl, pidl) ||
  2792. prt->NeedsRefresh() || prt->NeedsRegister())
  2793. {
  2794. if (!prt->_pidl || prt->NeedsRefresh())
  2795. {
  2796. prt->ClearNeedRefresh();
  2797. ASSERT(prt->_slOld == NULL);
  2798. prt->_slOld = prt->_sl;
  2799. prt->_cOld = prt->_slOld ? prt->_slOld.GetPtrCount() : 0;
  2800. prt->_iOld = 0;
  2801. // Free previous pidl
  2802. ILFree(prt->_pidl);
  2803. prt->_pidl = NULL;
  2804. if (prt->_sl.Create(4))
  2805. {
  2806. ByUsageDir *pdir = ByUsageDir::Create(_pdirDesktop, pidl);
  2807. if (pdir)
  2808. {
  2809. prt->_pidl = pidl; // Take ownership
  2810. pidl = NULL; // So ILFree won't nuke it
  2811. _FillFolderCache(pdir, prt);
  2812. pdir->Release();
  2813. }
  2814. }
  2815. DPADELETEANDDESTROY(prt->_slOld);
  2816. }
  2817. if (_pByUsageUI && prt->NeedsRegister() && prt->_pidl)
  2818. {
  2819. ASSERT(i < ByUsageUI::SFTHOST_MAXNOTIFY);
  2820. prt->SetRegistered();
  2821. ASSERT(!IsLocked());
  2822. _pByUsageUI->RegisterNotify(i, SHCNE_DISKEVENTS, prt->_pidl, TRUE);
  2823. }
  2824. }
  2825. ILFree(pidl);
  2826. }
  2827. else
  2828. {
  2829. // Special folder doesn't exist; erase the file list
  2830. prt->Reset();
  2831. }
  2832. } // for loop!
  2833. } // Restriction!
  2834. _fIsCacheUpToDate = TRUE;
  2835. return S_OK;
  2836. }
  2837. void CMenuItemsCache::RefreshDarwinShortcuts(ByUsageRoot *prt)
  2838. {
  2839. if (prt->_sl)
  2840. {
  2841. int j = prt->_sl.GetPtrCount();
  2842. while (--j >= 0)
  2843. {
  2844. ByUsageShortcut *pscut = prt->_sl.FastGetPtr(j);
  2845. if (FAILED(_UpdateMSIPath(pscut)))
  2846. {
  2847. prt->_sl.DeletePtr(j); // remove the bad shortcut so we don't fault
  2848. }
  2849. }
  2850. }
  2851. }
  2852. void CMenuItemsCache::RefreshCachedDarwinShortcuts()
  2853. {
  2854. if(!SHRestricted(REST_NOSMMFUPROGRAMS))
  2855. {
  2856. Lock();
  2857. for (int i = 0; i < ARRAYSIZE(c_rgrfi); i++)
  2858. {
  2859. RefreshDarwinShortcuts(&_rgrt[i]);
  2860. }
  2861. Unlock();
  2862. }
  2863. }
  2864. ByUsageShortcut *CMenuItemsCache::_CreateFromCachedPidl(ByUsageRoot *prt, ByUsageDir *pdir, LPITEMIDLIST pidl)
  2865. {
  2866. ByUsageHiddenData hd;
  2867. UINT buhd = ByUsageHiddenData::BUHD_TARGETPATH | ByUsageHiddenData::BUHD_MSIPATH;
  2868. hd.Get(pidl, buhd);
  2869. ByUsageShortcut *pscut = CreateShortcutFromHiddenData(pdir, pidl, &hd);
  2870. if (pscut)
  2871. {
  2872. if (prt->_sl.AppendPtr(pscut) >= 0)
  2873. {
  2874. _SetInterestingLink(pscut);
  2875. }
  2876. else
  2877. {
  2878. // Couldn't append; oh well
  2879. delete pscut; // "delete" can handle NULL pointer
  2880. }
  2881. }
  2882. hd.Clear();
  2883. return pscut;
  2884. }
  2885. HRESULT IStream_WriteByte(IStream *pstm, BYTE b)
  2886. {
  2887. return IStream_Write(pstm, &b, sizeof(b));
  2888. }
  2889. #ifdef DEBUG
  2890. //
  2891. // Like ILIsParent, but defaults to TRUE if we don't have enough memory
  2892. // to determine for sure. (ILIsParent defaults to FALSE on error.)
  2893. //
  2894. BOOL ILIsProbablyParent(LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidlChild)
  2895. {
  2896. BOOL fRc = TRUE;
  2897. LPITEMIDLIST pidlT = ILClone(pidlChild);
  2898. if (pidlT)
  2899. {
  2900. // Truncate pidlT to the same depth as pidlParent.
  2901. LPCITEMIDLIST pidlParentT = pidlParent;
  2902. LPITEMIDLIST pidlChildT = pidlT;
  2903. while (!ILIsEmpty(pidlParentT))
  2904. {
  2905. pidlChildT = _ILNext(pidlChildT);
  2906. pidlParentT = _ILNext(pidlParentT);
  2907. }
  2908. pidlChildT->mkid.cb = 0;
  2909. // Okay, at this point pidlT should equal pidlParent.
  2910. IShellFolder *psfDesktop;
  2911. if (SUCCEEDED(SHGetDesktopFolder(&psfDesktop)))
  2912. {
  2913. HRESULT hr = psfDesktop->CompareIDs(0, pidlT, pidlParent);
  2914. if (SUCCEEDED(hr) && ShortFromResult(hr) != 0)
  2915. {
  2916. // Definitely, conclusively different.
  2917. fRc = FALSE;
  2918. }
  2919. psfDesktop->Release();
  2920. }
  2921. ILFree(pidlT);
  2922. }
  2923. return fRc;
  2924. }
  2925. #endif
  2926. inline LPITEMIDLIST ILFindKnownChild(LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidlChild)
  2927. {
  2928. #ifdef DEBUG
  2929. // ILIsParent will give wrong answers in low-memory situations
  2930. // (which testers like to simulate) so we roll our own.
  2931. // ASSERT(ILIsParent(pidlParent, pidlChild, FALSE));
  2932. ASSERT(ILIsProbablyParent(pidlParent, pidlChild));
  2933. #endif
  2934. while (!ILIsEmpty(pidlParent))
  2935. {
  2936. pidlChild = _ILNext(pidlChild);
  2937. pidlParent = _ILNext(pidlParent);
  2938. }
  2939. return const_cast<LPITEMIDLIST>(pidlChild);
  2940. }
  2941. void CMenuItemsCache::_SaveCache()
  2942. {
  2943. int irfi;
  2944. BOOL fSuccess = FALSE;
  2945. IStream *pstm = SHOpenRegStream2(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS, REGSTR_VAL_PROGLIST, STGM_WRITE);
  2946. if (pstm)
  2947. {
  2948. DWORD dwVersion = PROGLIST_VERSION;
  2949. if (FAILED(IStream_Write(pstm, &dwVersion, sizeof(dwVersion))))
  2950. {
  2951. goto panic;
  2952. }
  2953. for (irfi = 0; irfi < ARRAYSIZE(c_rgrfi); irfi++)
  2954. {
  2955. if (!_ShouldProcessRoot(irfi))
  2956. continue;
  2957. ByUsageRoot *prt = &_rgrt[irfi];
  2958. if (FAILED(IStream_WriteByte(pstm, (BYTE)c_rgrfi[irfi]._csidl)))
  2959. {
  2960. goto panic;
  2961. }
  2962. if (prt->_sl && prt->_pidl)
  2963. {
  2964. int i;
  2965. ByUsageDir *pdir = NULL;
  2966. for (i = 0; i < prt->_sl.GetPtrCount(); i++)
  2967. {
  2968. ByUsageShortcut *pscut = prt->_sl.FastGetPtr(i);
  2969. // If the directory changed, write out a chdir entry
  2970. if (pdir != pscut->Dir())
  2971. {
  2972. pdir = pscut->Dir();
  2973. // Write the new directory
  2974. if (FAILED(IStream_WriteByte(pstm, CACHE_CHDIR)) ||
  2975. FAILED(IStream_WritePidl(pstm, ILFindKnownChild(prt->_pidl, pdir->Pidl()))))
  2976. {
  2977. goto panic;
  2978. }
  2979. }
  2980. // Now write out the shortcut
  2981. if (FAILED(IStream_WriteByte(pstm, CACHE_ITEM)) ||
  2982. FAILED(IStream_WritePidl(pstm, pscut->RelativePidl())))
  2983. {
  2984. goto panic;
  2985. }
  2986. }
  2987. }
  2988. // Now write out the terminator
  2989. if (FAILED(IStream_WriteByte(pstm, CACHE_END)))
  2990. {
  2991. goto panic;
  2992. }
  2993. }
  2994. fSuccess = TRUE;
  2995. panic:
  2996. pstm->Release();
  2997. if (!fSuccess)
  2998. {
  2999. SHDeleteValue(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS, REGSTR_VAL_PROGLIST);
  3000. }
  3001. }
  3002. }
  3003. void CMenuItemsCache::StartEnum()
  3004. {
  3005. _iCurrentRoot = 0;
  3006. _iCurrentIndex = 0;
  3007. }
  3008. void CMenuItemsCache::EndEnum()
  3009. {
  3010. }
  3011. ByUsageShortcut *CMenuItemsCache::GetNextShortcut()
  3012. {
  3013. ByUsageShortcut *pscut = NULL;
  3014. if (_iCurrentRoot < NUM_PROGLIST_ROOTS)
  3015. {
  3016. if (_rgrt[_iCurrentRoot]._sl && _iCurrentIndex < _rgrt[_iCurrentRoot]._sl.GetPtrCount())
  3017. {
  3018. pscut = _rgrt[_iCurrentRoot]._sl.FastGetPtr(_iCurrentIndex);
  3019. _iCurrentIndex++;
  3020. }
  3021. else
  3022. {
  3023. // Go to next root
  3024. _iCurrentIndex = 0;
  3025. _iCurrentRoot++;
  3026. pscut = GetNextShortcut();
  3027. }
  3028. }
  3029. return pscut;
  3030. }
  3031. //****************************************************************************
  3032. void AppendString(CDPA<TCHAR> dpa, LPCTSTR psz)
  3033. {
  3034. LPTSTR pszDup = StrDup(psz);
  3035. if (pszDup && dpa.AppendPtr(pszDup) < 0)
  3036. {
  3037. LocalFree(pszDup); // Append failed
  3038. }
  3039. }
  3040. BOOL LocalFreeCallback(LPTSTR psz, LPVOID)
  3041. {
  3042. LocalFree(psz);
  3043. return TRUE;
  3044. }
  3045. BOOL ILFreeCallback(LPITEMIDLIST pidl, LPVOID)
  3046. {
  3047. ILFree(pidl);
  3048. return TRUE;
  3049. }
  3050. int ByUsage::CompareItems(PaneItem *p1, PaneItem *p2)
  3051. {
  3052. //
  3053. // The separator comes before regular items.
  3054. //
  3055. if (p1->IsSeparator())
  3056. {
  3057. return -1;
  3058. }
  3059. if (p2->IsSeparator())
  3060. {
  3061. return +1;
  3062. }
  3063. ByUsageItem *pitem1 = static_cast<ByUsageItem *>(p1);
  3064. ByUsageItem *pitem2 = static_cast<ByUsageItem *>(p2);
  3065. return CompareUEMInfo(&pitem1->_uei, &pitem2->_uei);
  3066. }
  3067. // Sort by most frequently used - break ties by most recently used
  3068. int ByUsage::CompareUEMInfo(UEMINFO *puei1, UEMINFO *puei2)
  3069. {
  3070. int iResult = puei2->cHit - puei1->cHit;
  3071. if (iResult == 0)
  3072. {
  3073. iResult = ::CompareFileTime(&puei2->ftExecute, &puei1->ftExecute);
  3074. }
  3075. return iResult;
  3076. }
  3077. LPITEMIDLIST ByUsage::GetFullPidl(PaneItem *p)
  3078. {
  3079. ByUsageItem *pitem = static_cast<ByUsageItem *>(p);
  3080. return pitem->CreateFullPidl();
  3081. }
  3082. HRESULT ByUsage::GetFolderAndPidl(PaneItem *p,
  3083. IShellFolder **ppsfOut, LPCITEMIDLIST *ppidlOut)
  3084. {
  3085. ByUsageItem *pitem = static_cast<ByUsageItem *>(p);
  3086. // If a single-level child pidl, then we can short-circuit the
  3087. // SHBindToFolderIDListParent
  3088. if (_ILNext(pitem->_pidl)->mkid.cb == 0)
  3089. {
  3090. *ppsfOut = pitem->_pdir->Folder(); (*ppsfOut)->AddRef();
  3091. *ppidlOut = pitem->_pidl;
  3092. return S_OK;
  3093. }
  3094. else
  3095. {
  3096. // Multi-level child pidl
  3097. return SHBindToFolderIDListParent(pitem->_pdir->Folder(), pitem->_pidl,
  3098. IID_PPV_ARG(IShellFolder, ppsfOut), ppidlOut);
  3099. }
  3100. }
  3101. HRESULT ByUsage::ContextMenuDeleteItem(PaneItem *p, IContextMenu *pcm, CMINVOKECOMMANDINFOEX *pici)
  3102. {
  3103. IShellFolder *psf;
  3104. LPCITEMIDLIST pidlItem;
  3105. ByUsageItem *pitem = static_cast<ByUsageItem *>(p);
  3106. HRESULT hr = GetFolderAndPidl(pitem, &psf, &pidlItem);
  3107. if (SUCCEEDED(hr))
  3108. {
  3109. // Unpin the item - we go directly to the IStartMenuPin because
  3110. // the context menu handler might decide not to support pin/unpin
  3111. // for this item because it doesn't satisfy some criteria or other.
  3112. LPITEMIDLIST pidlFull = pitem->CreateFullPidl();
  3113. if (pidlFull)
  3114. {
  3115. _psmpin->Modify(pidlFull, NULL); // delete from pin list
  3116. ILFree(pidlFull);
  3117. }
  3118. // Set hit count for shortcut to zero
  3119. UEMINFO uei;
  3120. ZeroMemory(&uei, sizeof(UEMINFO));
  3121. uei.cbSize = sizeof(UEMINFO);
  3122. uei.dwMask = UEIM_HIT;
  3123. uei.cHit = 0;
  3124. _SetUEMPidlInfo(psf, pidlItem, &uei);
  3125. // Set hit count for target app to zero
  3126. TCHAR szPath[MAX_PATH];
  3127. if (SUCCEEDED(_GetShortcutExeTarget(psf, pidlItem, szPath, ARRAYSIZE(szPath))))
  3128. {
  3129. _SetUEMPathInfo(szPath, &uei);
  3130. }
  3131. // Set hit count for Darwin target to zero
  3132. ByUsageHiddenData hd;
  3133. hd.Get(pidlItem, ByUsageHiddenData::BUHD_MSIPATH);
  3134. if (hd._pwszMSIPath && hd._pwszMSIPath[0])
  3135. {
  3136. _SetUEMPathInfo(hd._pwszMSIPath, &uei);
  3137. }
  3138. hd.Clear();
  3139. psf->Release();
  3140. if (IsSpecialPinnedItem(pitem))
  3141. {
  3142. c_tray.CreateStartButtonBalloon(0, IDS_STARTPANE_SPECIALITEMSTIP);
  3143. }
  3144. // If the item wasn't pinned, then all we did was dork some usage
  3145. // counts, which does not trigger an automatic refresh. So do a
  3146. // manual one.
  3147. _pByUsageUI->Invalidate();
  3148. PostMessage(_pByUsageUI->_hwnd, ByUsageUI::SFTBM_REFRESH, TRUE, 0);
  3149. }
  3150. return hr;
  3151. }
  3152. HRESULT ByUsage::ContextMenuInvokeItem(PaneItem *pitem, IContextMenu *pcm, CMINVOKECOMMANDINFOEX *pici, LPCTSTR pszVerb)
  3153. {
  3154. ASSERT(_pByUsageUI);
  3155. HRESULT hr;
  3156. if (StrCmpIC(pszVerb, TEXT("delete")) == 0)
  3157. {
  3158. hr = ContextMenuDeleteItem(pitem, pcm, pici);
  3159. }
  3160. else
  3161. {
  3162. // Don't need to refresh explicitly if the command is pin/unpin
  3163. // because the changenotify will do it for us
  3164. hr = _pByUsageUI->SFTBarHost::ContextMenuInvokeItem(pitem, pcm, pici, pszVerb);
  3165. }
  3166. return hr;
  3167. }
  3168. int ByUsage::ReadIconSize()
  3169. {
  3170. COMPILETIME_ASSERT(SFTBarHost::ICONSIZE_SMALL == 0);
  3171. COMPILETIME_ASSERT(SFTBarHost::ICONSIZE_LARGE == 1);
  3172. return SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, REGSTR_VAL_DV2_LARGEICONS, FALSE, TRUE /* default to large*/);
  3173. }
  3174. BOOL ByUsage::_IsPinnedExe(ByUsageItem *pitem, IShellFolder *psf, LPCITEMIDLIST pidlItem)
  3175. {
  3176. //
  3177. // Early-out: Not even pinned.
  3178. //
  3179. if (!_IsPinned(pitem))
  3180. {
  3181. return FALSE;
  3182. }
  3183. //
  3184. // See if it's an EXE.
  3185. //
  3186. BOOL fIsExe;
  3187. LPTSTR pszFileName = _DisplayNameOf(psf, pidlItem, SHGDN_INFOLDER | SHGDN_FORPARSING);
  3188. if (pszFileName)
  3189. {
  3190. LPCTSTR pszExt = PathFindExtension(pszFileName);
  3191. fIsExe = StrCmpICW(pszExt, TEXT(".exe")) == 0;
  3192. SHFree(pszFileName);
  3193. }
  3194. else
  3195. {
  3196. fIsExe = FALSE;
  3197. }
  3198. return fIsExe;
  3199. }
  3200. HRESULT ByUsage::ContextMenuRenameItem(PaneItem *p, LPCTSTR ptszNewName)
  3201. {
  3202. ByUsageItem *pitem = static_cast<ByUsageItem *>(p);
  3203. IShellFolder *psf;
  3204. LPCITEMIDLIST pidlItem;
  3205. HRESULT hr;
  3206. hr = GetFolderAndPidl(pitem, &psf, &pidlItem);
  3207. if (SUCCEEDED(hr))
  3208. {
  3209. if (_IsPinnedExe(pitem, psf, pidlItem))
  3210. {
  3211. // Renaming a pinned exe consists merely of changing the
  3212. // display name inside the pidl.
  3213. //
  3214. // Note! SetAltName frees the pidl on failure.
  3215. LPITEMIDLIST pidlNew;
  3216. if ((pidlNew = ILClone(pitem->RelativePidl())) &&
  3217. (pidlNew = ByUsageHiddenData::SetAltName(pidlNew, ptszNewName)))
  3218. {
  3219. hr = _psmpin->Modify(pitem->RelativePidl(), pidlNew);
  3220. if (SUCCEEDED(hr))
  3221. {
  3222. pitem->SetRelativePidl(pidlNew);
  3223. }
  3224. else
  3225. {
  3226. ILFree(pidlNew);
  3227. }
  3228. }
  3229. else
  3230. {
  3231. hr = E_OUTOFMEMORY;
  3232. }
  3233. }
  3234. else
  3235. {
  3236. LPITEMIDLIST pidlNew;
  3237. hr = psf->SetNameOf(_hwnd, pidlItem, ptszNewName, SHGDN_INFOLDER, &pidlNew);
  3238. //
  3239. // Warning! SetNameOf can set pidlNew == NULL if the rename
  3240. // was handled by some means outside of the pidl (so the pidl
  3241. // is unchanged). This means that the rename succeeded and
  3242. // we can keep using the old pidl.
  3243. //
  3244. if (SUCCEEDED(hr) && pidlNew)
  3245. {
  3246. //
  3247. // The old Start Menu renames the UEM data when we rename
  3248. // the shortcut, but we cannot guarantee that the old
  3249. // Start Menu is around, so we do it ourselves. Fortunately,
  3250. // the old Start Menu does not attempt to move the data if
  3251. // the hit count is zero, so if it gets moved twice, the
  3252. // second person who does the move sees cHit=0 and skips
  3253. // the operation.
  3254. //
  3255. UEMINFO uei;
  3256. _GetUEMPidlInfo(psf, pidlItem, &uei);
  3257. if (uei.cHit > 0)
  3258. {
  3259. _SetUEMPidlInfo(psf, pidlNew, &uei);
  3260. uei.cHit = 0;
  3261. _SetUEMPidlInfo(psf, pidlItem, &uei);
  3262. }
  3263. //
  3264. // Update the pitem with the new pidl.
  3265. //
  3266. if (_IsPinned(pitem))
  3267. {
  3268. LPITEMIDLIST pidlDad = ILCloneParent(pitem->RelativePidl());
  3269. if (pidlDad)
  3270. {
  3271. LPITEMIDLIST pidlFullNew = ILCombine(pidlDad, pidlNew);
  3272. if (pidlFullNew)
  3273. {
  3274. _psmpin->Modify(pitem->RelativePidl(), pidlFullNew);
  3275. pitem->SetRelativePidl(pidlFullNew); // takes ownership
  3276. }
  3277. ILFree(pidlDad);
  3278. }
  3279. ILFree(pidlNew);
  3280. }
  3281. else
  3282. {
  3283. ASSERT(pidlItem == pitem->RelativePidl());
  3284. pitem->SetRelativePidl(pidlNew);
  3285. }
  3286. }
  3287. }
  3288. psf->Release();
  3289. }
  3290. return hr;
  3291. }
  3292. //
  3293. // If asking for the display (not for parsing) name of a pinned EXE,
  3294. // we need to return the "secret display name". Otherwise, we can
  3295. // use the default implementation.
  3296. //
  3297. LPTSTR ByUsage::DisplayNameOfItem(PaneItem *p, IShellFolder *psf, LPCITEMIDLIST pidlItem, SHGNO shgno)
  3298. {
  3299. ByUsageItem *pitem = static_cast<ByUsageItem *>(p);
  3300. LPTSTR pszName = NULL;
  3301. // Only display (not for-parsing) names of EXEs need to be hooked.
  3302. if (!(shgno & SHGDN_FORPARSING) && _IsPinnedExe(pitem, psf, pidlItem))
  3303. {
  3304. //
  3305. // EXEs get their name from the hidden data.
  3306. //
  3307. pszName = ByUsageHiddenData::GetAltName(pidlItem);
  3308. }
  3309. return pszName ? pszName
  3310. : _pByUsageUI->SFTBarHost::DisplayNameOfItem(p, psf, pidlItem, shgno);
  3311. }
  3312. //
  3313. // "Internet" and "Email" get subtitles consisting of the friendly app name.
  3314. //
  3315. LPTSTR ByUsage::SubtitleOfItem(PaneItem *p, IShellFolder *psf, LPCITEMIDLIST pidlItem)
  3316. {
  3317. ASSERT(p->HasSubtitle());
  3318. LPTSTR pszName = NULL;
  3319. IAssociationElement *pae = GetAssociationElementFromSpecialPidl(psf, pidlItem);
  3320. if (pae)
  3321. {
  3322. // We detect error by looking at pszName
  3323. pae->QueryString(AQS_FRIENDLYTYPENAME, NULL, &pszName);
  3324. pae->Release();
  3325. }
  3326. return pszName ? pszName
  3327. : _pByUsageUI->SFTBarHost::SubtitleOfItem(p, psf, pidlItem);
  3328. }
  3329. HRESULT ByUsage::MovePinnedItem(PaneItem *p, int iInsert)
  3330. {
  3331. ByUsageItem *pitem = static_cast<ByUsageItem *>(p);
  3332. ASSERT(_IsPinned(pitem));
  3333. return _psmpin->Modify(pitem->RelativePidl(), SMPIN_POS(iInsert));
  3334. }
  3335. //
  3336. // For drag-drop purposes, we let you drop anything, not just EXEs.
  3337. // We just reject slow media.
  3338. //
  3339. BOOL ByUsage::IsInsertable(IDataObject *pdto)
  3340. {
  3341. return _psmpin->IsPinnable(pdto, SMPINNABLE_REJECTSLOWMEDIA, NULL) == S_OK;
  3342. }
  3343. HRESULT ByUsage::InsertPinnedItem(IDataObject *pdto, int iInsert)
  3344. {
  3345. HRESULT hr = E_FAIL;
  3346. LPITEMIDLIST pidlItem;
  3347. if (_psmpin->IsPinnable(pdto, SMPINNABLE_REJECTSLOWMEDIA, &pidlItem) == S_OK)
  3348. {
  3349. if (SUCCEEDED(hr = _psmpin->Modify(NULL, pidlItem)) &&
  3350. SUCCEEDED(hr = _psmpin->Modify(pidlItem, SMPIN_POS(iInsert))))
  3351. {
  3352. // Woo-hoo!
  3353. }
  3354. ILFree(pidlItem);
  3355. }
  3356. return hr;
  3357. }