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.

4652 lines
152 KiB

  1. //+-------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. //
  5. // Copyright (C) Microsoft Corporation, 1997 - 1999
  6. //
  7. // File: cscst.cpp
  8. //
  9. //--------------------------------------------------------------------------
  10. #include "pch.h"
  11. #pragma hdrstop
  12. #include <shellp.h> // STR_DESKTOPCLASS
  13. #ifdef REPORT_DEVICE_CHANGES
  14. # include <dbt.h> // Device change notifications.
  15. #endif // REPORT_DEVICE_CHANGES
  16. #include <sddl.h> // For ConvertStringSidToSid
  17. #include "cscst.h"
  18. #include "options.h"
  19. #include "statdlg.h" // CStatusDlg
  20. #include "folder.h"
  21. #include "eventlog.h"
  22. #include "msg.h"
  23. #include "purge.h"
  24. #include "security.h"
  25. #include "syncmgr.h"
  26. #include "strings.h"
  27. #include "termserv.h"
  28. #if DBG
  29. //
  30. // This code is used to manage the hidden window when we
  31. // unhide it and display debug output to it via STDBGOUT().
  32. //
  33. #include <commdlg.h>
  34. #include <stdarg.h>
  35. const TCHAR c_szSysTrayOutput[] = TEXT("SysTrayOutput");
  36. int STDebugLevel(void);
  37. void STDebugOnLogEvent(HWND hwndList, LPCTSTR pszText);
  38. void STDebugSaveListboxContent(HWND hwndParent);
  39. DWORD STDebugOpenNetCacheKey(DWORD dwAccess, HKEY *phkey);
  40. #endif // DBG
  41. //
  42. // Size of systray icons.
  43. //
  44. #define CSC_ICON_CX 16
  45. #define CSC_ICON_CY 16
  46. //
  47. // Timer IDs are arbitrary.
  48. //
  49. #define ID_TIMER_FLASHICON 2953
  50. #define ID_TIMER_REMINDER 2954
  51. #define ID_TIMER_STATECHANGE 2955
  52. // Prototypes
  53. void ApplyAdminFolderPolicy(void); // in admin.cpp
  54. void _RefreshAllExplorerWindows(LPCTSTR pszServer);
  55. // Globals
  56. static HWND g_hWndNotification = NULL;
  57. extern HWND g_hwndStatusDlg; // in statdlg.cpp
  58. HANDLE g_hToken = NULL;
  59. #ifdef REPORT_DEVICE_CHANGES
  60. HDEVNOTIFY g_hDevNotify = NULL;
  61. #endif // REPORT_DEVICE_CHANGES
  62. //
  63. // RAS Autodial API.
  64. //
  65. typedef BOOL (WINAPI * PFNHLPNBCONNECTION)(LPCTSTR);
  66. #if DBG
  67. //
  68. // Provide some text-form names for state and input values
  69. // to support debug output. The order of these corresponds
  70. // to the STS_XXXXX enumeration.
  71. //
  72. LPCTSTR g_pszSysTrayStates[] = { TEXT("STS_INVALID"),
  73. TEXT("STS_ONLINE"),
  74. TEXT("STS_DIRTY"),
  75. TEXT("STS_MDIRTY"),
  76. TEXT("STS_SERVERBACK"),
  77. TEXT("STS_MSERVERBACK"),
  78. TEXT("STS_OFFLINE"),
  79. TEXT("STS_MOFFLINE"),
  80. TEXT("STS_NONET") };
  81. //
  82. // A simple function to translate a state value to a string.
  83. //
  84. LPCTSTR SysTrayStateStr(eSysTrayState s)
  85. {
  86. return g_pszSysTrayStates[int(s)];
  87. }
  88. #endif
  89. //
  90. // A simple dynamic list of server names. A name can be provided
  91. // as either a "\\server" or "\\server\share" and only the server
  92. // part "\\server" is stored.
  93. //
  94. class CServerList
  95. {
  96. public:
  97. CServerList(void)
  98. : m_hdpa(DPA_Create(10)) { }
  99. ~CServerList(void);
  100. bool Add(LPCTSTR pszServer);
  101. void Remove(LPCTSTR pszServer);
  102. void Clear(void);
  103. int Find(LPCTSTR pszServer);
  104. int Count(void) const;
  105. LPCTSTR Get(int iItem) const;
  106. bool Exists(LPCTSTR pszServer)
  107. { return -1 != Find(pszServer); }
  108. private:
  109. HDPA m_hdpa;
  110. void GetServerFromPath(LPCTSTR pszPath, LPTSTR pszServer, int cchServer);
  111. //
  112. // Prevent copy.
  113. //
  114. CServerList(const CServerList& rhs);
  115. CServerList& operator = (const CServerList& rhs);
  116. };
  117. //
  118. // The class that translates CSC agent input and cache status into a subsequent
  119. // systray UI state. Originally this was a table-driven state machine
  120. // (hence the name). It later proved sufficient to do a simple scan of cache
  121. // status and determine UI state based on the statistics obtained. The name
  122. // has been retained for lack of something better.
  123. //
  124. class CStateMachine
  125. {
  126. public:
  127. CStateMachine(bool bNoNet) : m_bNoNet(bNoNet) { }
  128. //
  129. // This is THE function for converting CSC agent input (or a
  130. // simple status check) into a systray icon state.
  131. //
  132. eSysTrayState TranslateInput(UINT uMsg, LPTSTR pszShare, UINT cchShare);
  133. void PingServers();
  134. bool ServerPendingReconnection(LPCTSTR pszServer)
  135. { return m_PendingReconList.Add(pszServer); }
  136. void ServerReconnected(LPCTSTR pszServer)
  137. { m_PendingReconList.Remove(pszServer); }
  138. void ServerUnavailable(LPCTSTR pszServer)
  139. { m_PendingReconList.Remove(pszServer); }
  140. void AllServersUnavailable(void)
  141. { m_PendingReconList.Clear(); }
  142. bool IsServerPendingReconnection(LPCTSTR pszServer)
  143. { return m_PendingReconList.Exists(pszServer); }
  144. private:
  145. CServerList m_PendingReconList;
  146. bool m_bNoNet;
  147. //
  148. // Some helper functions for decoding CSC share status values.
  149. //
  150. bool ShareIsOffline(DWORD dwCscStatus) const
  151. {
  152. return (0 != (FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwCscStatus));
  153. }
  154. bool ShareHasFiles(LPCTSTR pszShare, bool *pbModified = NULL, bool *pbOpen = NULL) const;
  155. //
  156. // Prevent copy.
  157. //
  158. CStateMachine(const CStateMachine& rhs);
  159. CStateMachine& operator = (const CStateMachine& rhs);
  160. };
  161. //
  162. // The CSysTrayUI class encapsulates the manipulation of the systray icon
  163. // so that the rest of the CSCUI code is exposed to only a narrow interface
  164. // to the systray. It also maintains state information to control flashing
  165. // of the systray icon. All flashing processing is provided by this class.
  166. //
  167. class CSysTrayUI
  168. {
  169. public:
  170. ~CSysTrayUI(void);
  171. //
  172. // Set the state of the systray icon. This will only change the
  173. // icon if the state has changed. Therefore this function can be
  174. // called without worrying about excessive redundant updates to
  175. // the display.
  176. //
  177. bool SetState(eSysTrayState state, LPCTSTR pszServer = NULL);
  178. //
  179. // Retrieve the current "state" of the systray UI. The state
  180. // is one of the STS_XXXXX codes.
  181. //
  182. eSysTrayState GetState(void) const
  183. { return m_state; }
  184. //
  185. // Retrieve the server name to be used in CSCUI elements.
  186. // If the server name string is empty, that means there are
  187. // multiple servers in the given state.
  188. //
  189. LPCTSTR GetServerName(void) const
  190. { return m_szServer; }
  191. //
  192. // Show the balloon text for the current systray state.
  193. //
  194. void ShowReminderBalloon(void);
  195. //
  196. // Reset the reminder timer.
  197. //
  198. void ResetReminderTimer(bool bRestart);
  199. //
  200. // Make any adjustments when a WM_WININICHANGE is received.
  201. //
  202. void OnWinIniChange(LPCTSTR pszSection);
  203. //
  204. //
  205. // Get a reference to THE singleton instance.
  206. //
  207. static CSysTrayUI& GetInstance(void);
  208. private:
  209. //
  210. // A minimal autoptr class to ensure the singleton instance
  211. // is deleted.
  212. //
  213. class autoptr
  214. {
  215. public:
  216. autoptr(void)
  217. : m_ptr(NULL) { }
  218. ~autoptr(void)
  219. { delete m_ptr; }
  220. CSysTrayUI* Get(void) const
  221. { return m_ptr; }
  222. void Set(CSysTrayUI *p)
  223. { delete m_ptr; m_ptr = p; }
  224. private:
  225. CSysTrayUI *m_ptr;
  226. autoptr(const autoptr& rhs);
  227. autoptr& operator = (const autoptr& rhs);
  228. };
  229. //
  230. // Icon info maintained for each UI state.
  231. //
  232. struct IconInfo
  233. {
  234. HICON hIcon; // Handle to icon to display in this state.
  235. UINT idIcon; // ID of icon to display in this state.
  236. int iFlashTimeout; // 0 == No icon flash. Time is in millisec.
  237. };
  238. //
  239. // Info maintained to describe the various balloon text messages.
  240. // Combination of state and dwTextFlags are the table keys.
  241. //
  242. struct BalloonInfo
  243. {
  244. eSysTrayState state; // SysTray state value.
  245. DWORD dwTextFlags; // BTF_XXXXX flags.
  246. DWORD dwInfoFlags; // NIIF_XXXXX flag.
  247. UINT idHeader; // Res id for header part.
  248. UINT idStatus; // Res id for status part.
  249. UINT idBody; // Res id for body part.
  250. UINT idDirective; // Res id for directive part.
  251. };
  252. //
  253. // Info maintained to describe the various tooltip text messages.
  254. //
  255. struct TooltipInfo
  256. {
  257. eSysTrayState state; // SysTray state value.
  258. UINT idTooltip; // Tooltip text resource ID.
  259. };
  260. //
  261. // Info maintained for special-case supression of systray balloons.
  262. // There are some state transitions that shouldn't generate a balloon.
  263. // This structure describes each entry in an array of supression info.
  264. //
  265. struct BalloonSupression
  266. {
  267. eSysTrayState stateFrom; // Transitioning from this state.
  268. eSysTrayState stateTo; // Transitioning to this state.
  269. };
  270. //
  271. // Enumeration for controlling what's done to the systray on update.
  272. //
  273. enum eUpdateFlags { UF_ICON = 0x00000001, // Update the icon.
  274. UF_FLASHICON = 0x00000002, // Flash the icon.
  275. UF_BALLOON = 0x00000004, // Show the balloon.
  276. UF_REMINDER = 0x00000008 }; // Balloon is a reminder.
  277. //
  278. // These flags relate a cache state to balloon text message.
  279. // They fit into an encoded mask where the lowest 4 bits
  280. // contain the eSysTrayState (STS_XXXXXX) code.
  281. //
  282. // (STS_OFFLINE | BTF_INITIAL)
  283. //
  284. // would indicate the condition where the state is "offline" for
  285. // a single server and the text to be displayed is for the initial
  286. // notification.
  287. //
  288. enum eBalloonTextFlags {
  289. BTF_INITIAL = 0x00000010, // Initial notification
  290. BTF_REMIND = 0x00000020 // Reminder
  291. };
  292. static IconInfo s_rgIconInfo[]; // The icon info
  293. static BalloonInfo s_rgBalloonInfo[]; // Balloon configuration info.
  294. static TooltipInfo s_rgTooltipInfo[]; // Tooltip configuration info.
  295. static BalloonSupression s_rgBalloonSupression[];
  296. static const int s_iMinStateChangeInterval;
  297. UINT_PTR m_idFlashingTimer; // Flash timer id.
  298. UINT_PTR m_idReminderTimer; // Timer for showing reminder balloons.
  299. UINT_PTR m_idStateChangeTimer; // Timer for queued state changes.
  300. UINT m_iIconFlashTime; // Period of icon flashes (ms).
  301. HICON& m_hIconNoOverlay; // Icon used for flashing.
  302. HWND m_hwndNotify; // Notification window.
  303. DWORD m_dwFlashingExpires; // Tick count when flash timer expires.
  304. DWORD m_dwNextStateChange; // Tick count for next queued state change.
  305. TCHAR m_szServer[MAX_PATH]; // Servername for balloon messages.
  306. TCHAR m_szServerQueued[MAX_PATH];
  307. eSysTrayState m_state; // Remember current state.
  308. eSysTrayState m_statePrev;
  309. eSysTrayState m_stateQueued;
  310. bool m_bFlashOverlay; // Alternates 0,1 (1 == display overlay, 0 == don't)
  311. bool m_bActive; // 1 == we have an active icon in systray.
  312. //
  313. // Enforce singleton existance by making construction
  314. // and copy operations private.
  315. //
  316. CSysTrayUI(HWND hwndNotify);
  317. CSysTrayUI(const CSysTrayUI& rhs);
  318. CSysTrayUI& operator = (const CSysTrayUI& rhs);
  319. void UpdateSysTray(eUpdateFlags uFlags, LPCTSTR pszServer = NULL);
  320. int GetBalloonInfoIndex(eSysTrayState state, DWORD dwTextFlags);
  321. bool StateHasBalloonText(eSysTrayState state, DWORD dwTextFlags);
  322. void GetBalloonInfo(eSysTrayState state,
  323. DWORD dwTextFlags,
  324. LPTSTR pszTextHdr,
  325. int cchTextHdr,
  326. LPTSTR pszTextBody,
  327. int cchTextBody,
  328. DWORD *pdwInfoFlags,
  329. UINT *puTimeout);
  330. bool SupressBalloon(eSysTrayState statePrev, eSysTrayState state);
  331. LPTSTR GetTooltipText(eSysTrayState state,
  332. LPTSTR pszText,
  333. int cchText);
  334. bool IconFlashedLongEnough(void);
  335. void KillIconFlashTimer(void);
  336. void HandleFlashTimer(void);
  337. void OnStateChangeTimerExpired(void);
  338. static VOID CALLBACK FlashTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
  339. static VOID CALLBACK ReminderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
  340. static VOID CALLBACK StateChangeTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
  341. };
  342. #define ICONFLASH_FOREVER (UINT(-1))
  343. #define ICONFLASH_NONE 0
  344. //
  345. // These rows must stay in the same order as the STS_XXXXX enumeration members.
  346. // For flash timeout values, 0 == no flash, -1 == never stop.
  347. // Everything else is a timeout in milliseconds.
  348. //
  349. CSysTrayUI::IconInfo
  350. CSysTrayUI::s_rgIconInfo[] = {
  351. { NULL, 0, ICONFLASH_NONE }, /* STS_INVALID */
  352. { NULL, 0, ICONFLASH_NONE }, /* STS_ONLINE */
  353. { NULL, IDI_CSCWARNING, ICONFLASH_FOREVER }, /* STS_DIRTY */
  354. { NULL, IDI_CSCWARNING, ICONFLASH_FOREVER }, /* STS_MDIRTY */
  355. { NULL, IDI_CSCINFORMATION, ICONFLASH_NONE }, /* STS_SERVERBACK */
  356. { NULL, IDI_CSCINFORMATION, ICONFLASH_NONE }, /* STS_MSERVERBACK */
  357. { NULL, IDI_CSCNORMAL, ICONFLASH_NONE }, /* STS_OFFLINE */
  358. { NULL, IDI_CSCNORMAL, ICONFLASH_NONE }, /* STS_MOFFLINE */
  359. { NULL, IDI_CSCNORMAL, ICONFLASH_NONE }}; /* STS_NONET */
  360. //
  361. // This table describes all information related to displaying the systray balloons.
  362. // The first two columns are the keys to each record; those being a systray UI state
  363. // and a mask of balloon-text flags.
  364. // Notes:
  365. // 1. There's no balloon for STS_NONET. We found that the user's response is
  366. // duh, I know I have no net.
  367. //
  368. //
  369. CSysTrayUI::BalloonInfo
  370. CSysTrayUI::s_rgBalloonInfo[] = {
  371. { STS_INVALID, BTF_INITIAL, NIIF_NONE, 0, 0, 0, 0, },
  372. { STS_INVALID, BTF_REMIND, NIIF_NONE, 0, 0, 0, 0, },
  373. { STS_OFFLINE, BTF_INITIAL, NIIF_INFO, IDS_BTHDR_INITIAL, IDS_BTSTA_OFFLINE, IDS_BTBOD_OFFLINE, IDS_BTDIR_VIEWSTATUS },
  374. { STS_MOFFLINE, BTF_INITIAL, NIIF_INFO, IDS_BTHDR_INITIAL, IDS_BTSTA_OFFLINE, IDS_BTBOD_OFFLINE_M, IDS_BTDIR_VIEWSTATUS },
  375. { STS_OFFLINE, BTF_REMIND, NIIF_INFO, IDS_BTHDR_REMIND, IDS_BTSTA_OFFLINE, IDS_BTBOD_STILLOFFLINE, IDS_BTDIR_VIEWSTATUS },
  376. { STS_MOFFLINE, BTF_REMIND, NIIF_INFO, IDS_BTHDR_REMIND, IDS_BTSTA_OFFLINE, IDS_BTBOD_STILLOFFLINE_M, IDS_BTDIR_VIEWSTATUS },
  377. // { STS_SERVERBACK, BTF_INITIAL, NIIF_INFO, IDS_BTHDR_INITIAL, IDS_BTSTA_SERVERBACK,IDS_BTBOD_SERVERBACK, IDS_BTDIR_RECONNECT },
  378. // { STS_MSERVERBACK,BTF_INITIAL, NIIF_INFO, IDS_BTHDR_INITIAL, IDS_BTSTA_SERVERBACK,IDS_BTBOD_SERVERBACK_M, IDS_BTDIR_RECONNECT },
  379. { STS_SERVERBACK, BTF_REMIND, NIIF_INFO, IDS_BTHDR_REMIND, IDS_BTSTA_SERVERBACK,IDS_BTBOD_STILLBACK, IDS_BTDIR_RECONNECT },
  380. { STS_MSERVERBACK,BTF_REMIND, NIIF_INFO, IDS_BTHDR_REMIND, IDS_BTSTA_SERVERBACK,IDS_BTBOD_STILLBACK_M, IDS_BTDIR_RECONNECT },
  381. { STS_DIRTY, BTF_INITIAL, NIIF_WARNING, IDS_BTHDR_INITIAL, IDS_BTSTA_DIRTY, IDS_BTBOD_DIRTY, IDS_BTDIR_SYNC },
  382. { STS_MDIRTY, BTF_INITIAL, NIIF_WARNING, IDS_BTHDR_INITIAL, IDS_BTSTA_DIRTY, IDS_BTBOD_DIRTY_M, IDS_BTDIR_SYNC },
  383. { STS_DIRTY, BTF_REMIND, NIIF_WARNING, IDS_BTHDR_REMIND, IDS_BTSTA_DIRTY, IDS_BTBOD_STILLDIRTY, IDS_BTDIR_SYNC },
  384. { STS_MDIRTY, BTF_REMIND, NIIF_WARNING, IDS_BTHDR_REMIND, IDS_BTSTA_DIRTY, IDS_BTBOD_STILLDIRTY_M, IDS_BTDIR_SYNC }
  385. };
  386. //
  387. // This table lists all of the state transitions that do not generate balloons.
  388. // Ideally, I would have a true state machine to control the UI for any given state transition.
  389. // However, since we have quite a few states and since you can transition from any state
  390. // to almost any other state, the state transition table would be large and confusing
  391. // to read. Instead, I've taken the position to assume all state transitions generate
  392. // the balloon UI associated with the "to" state unless the transition is listed
  393. // in this table.
  394. //
  395. CSysTrayUI::BalloonSupression
  396. CSysTrayUI::s_rgBalloonSupression[] = {
  397. { STS_MOFFLINE, STS_OFFLINE },
  398. { STS_NONET, STS_OFFLINE },
  399. { STS_NONET, STS_MOFFLINE }
  400. };
  401. //
  402. // This table describes all information related to displaying tooltip text
  403. // for the systray icon.
  404. //
  405. CSysTrayUI::TooltipInfo
  406. CSysTrayUI::s_rgTooltipInfo[] = {
  407. { STS_INVALID, 0 },
  408. { STS_OFFLINE, IDS_TT_OFFLINE },
  409. { STS_MOFFLINE, IDS_TT_OFFLINE_M },
  410. { STS_SERVERBACK, IDS_TT_SERVERBACK },
  411. { STS_MSERVERBACK, IDS_TT_SERVERBACK_M },
  412. { STS_DIRTY, IDS_TT_DIRTY },
  413. { STS_MDIRTY, IDS_TT_DIRTY_M },
  414. { STS_NONET, IDS_TT_NONET }
  415. };
  416. //-----------------------------------------------------------------------------
  417. // CServerList member functions.
  418. //-----------------------------------------------------------------------------
  419. CServerList::~CServerList(
  420. void
  421. )
  422. {
  423. if (NULL != m_hdpa)
  424. {
  425. int cEntries = DPA_GetPtrCount(m_hdpa);
  426. LPTSTR pszEntry;
  427. for (int i = 0; i < cEntries; i++)
  428. {
  429. pszEntry = (LPTSTR)DPA_GetPtr(m_hdpa, i);
  430. if (NULL != pszEntry)
  431. LocalFree(pszEntry);
  432. }
  433. DPA_Destroy(m_hdpa);
  434. }
  435. }
  436. void
  437. CServerList::GetServerFromPath(
  438. LPCTSTR pszPath,
  439. LPTSTR pszServer,
  440. int cchServer
  441. )
  442. {
  443. TCHAR szServer[MAX_PATH];
  444. // Truncation is probably OK here since we're stripping down to "\\server",
  445. // which can only be at most 17 chars (see UNCLEN in lmcons.h).
  446. StringCchCopy(szServer, ARRAYSIZE(szServer), pszPath);
  447. PathAddBackslash(szServer);
  448. PathStripToRoot(szServer);
  449. LPTSTR pszLastBackslash = StrRChr(szServer, szServer + lstrlen(szServer), TEXT('\\'));
  450. if (NULL != pszLastBackslash && pszLastBackslash > (szServer + 2))
  451. *pszLastBackslash = TEXT('\0');
  452. StringCchCopy(pszServer, cchServer, szServer);
  453. }
  454. bool
  455. CServerList::Add(
  456. LPCTSTR pszServer
  457. )
  458. {
  459. if (NULL != m_hdpa)
  460. {
  461. if (!Exists(pszServer))
  462. {
  463. int cchEntry = lstrlen(pszServer) + 1;
  464. LPTSTR pszEntry = (LPTSTR)LocalAlloc(LPTR, sizeof(TCHAR) * cchEntry);
  465. if (NULL != pszEntry)
  466. {
  467. GetServerFromPath(pszServer, pszEntry, cchEntry);
  468. if (-1 != DPA_AppendPtr(m_hdpa, pszEntry))
  469. return true;
  470. //
  471. // Addition to DPA failed. Delete the string buffer.
  472. //
  473. LocalFree(pszEntry);
  474. }
  475. }
  476. }
  477. return false;
  478. }
  479. void
  480. CServerList::Remove(
  481. LPCTSTR pszServer
  482. )
  483. {
  484. int iEntry = Find(pszServer);
  485. if (-1 != iEntry)
  486. {
  487. LPTSTR pszEntry = (LPTSTR)DPA_DeletePtr(m_hdpa, iEntry);
  488. if (NULL != pszEntry)
  489. LocalFree(pszEntry);
  490. }
  491. }
  492. LPCTSTR
  493. CServerList::Get(
  494. int iItem
  495. ) const
  496. {
  497. if (NULL != m_hdpa)
  498. return (LPCTSTR)DPA_GetPtr(m_hdpa, iItem);
  499. return NULL;
  500. }
  501. int
  502. CServerList::Count(
  503. void
  504. ) const
  505. {
  506. if (NULL != m_hdpa)
  507. return DPA_GetPtrCount(m_hdpa);
  508. return 0;
  509. }
  510. //
  511. // Locate a server name in the "pending reconnection" list.
  512. // pszServer can either be "\\server" or "\\server\share".
  513. //
  514. // Returns: Index of entry if found. -1 if not found.
  515. //
  516. int
  517. CServerList::Find(
  518. LPCTSTR pszServer
  519. )
  520. {
  521. TCHAR szServer[MAX_PATH];
  522. GetServerFromPath(pszServer, szServer, ARRAYSIZE(szServer));
  523. if (NULL != m_hdpa)
  524. {
  525. int cEntries = DPA_GetPtrCount(m_hdpa);
  526. LPTSTR pszEntry;
  527. for (int i = 0; i < cEntries; i++)
  528. {
  529. pszEntry = (LPTSTR)DPA_GetPtr(m_hdpa, i);
  530. if (NULL != pszEntry)
  531. {
  532. if (0 == lstrcmpi(pszEntry, szServer))
  533. return i;
  534. }
  535. }
  536. }
  537. return -1;
  538. }
  539. void
  540. CServerList::Clear(
  541. void
  542. )
  543. {
  544. if (NULL != m_hdpa)
  545. {
  546. int cEntries = DPA_GetPtrCount(m_hdpa);
  547. LPTSTR pszEntry;
  548. for (int i = 0; i < cEntries; i++)
  549. {
  550. pszEntry = (LPTSTR)DPA_DeletePtr(m_hdpa, i);
  551. if (NULL != pszEntry)
  552. {
  553. LocalFree(pszEntry);
  554. }
  555. }
  556. }
  557. }
  558. //-----------------------------------------------------------------------------
  559. // CStateMachine member functions.
  560. //-----------------------------------------------------------------------------
  561. //
  562. // Translates a STWM_XXXXX message from the CSC agent into a systray UI state
  563. // code. The caller also provides a buffer to a server name. If we find
  564. // a "single server" condition in the cache (i.e. one server is dirty, one
  565. // server is offline etc), then we write the name of this server to this
  566. // buffer. Otherwise, the buffer remains unchanged. The goal here is to
  567. // end up with a buffer containing the name of the applicable server when
  568. // we have one of these one-server conditions. Ultimately, the server name
  569. // is included in the tray balloon text message.
  570. //
  571. // The function returns one of the STS_XXXXX UI status codes.
  572. //
  573. // This function is rather long. Much longer than I like a function to be.
  574. // I've tried to break it up into smaller pieces but any chunks were pretty
  575. // much arbitrary. Without a good logical breakdown, that doesn't make much
  576. // sense. Even with it's length, it's not a complex function. It merely
  577. // enumerates shares in the cache gathering statistics along the way. From
  578. // these statistics, it decides what the next UI state should be.
  579. //
  580. eSysTrayState
  581. CStateMachine::TranslateInput(
  582. UINT uMsg,
  583. LPTSTR pszServer,
  584. UINT cchServer
  585. )
  586. {
  587. //
  588. // Since this cscui code is running all the time, we don't want to keep
  589. // a handle to the event log open. Therefore, we use this CscuiEventLog
  590. // object to automatically close the log for us. The ReportEvent member
  591. // of CscuiEventLog handles all initialization of the log and determining
  592. // if the event should actually be logged (depending upon the current CSCUI
  593. // event logging level).
  594. //
  595. CscuiEventLog log;
  596. bool bServerIsBack = false;
  597. if (STWM_CSCNETUP == uMsg)
  598. {
  599. m_bNoNet = false;
  600. if (TEXT('\0') != *pszServer)
  601. {
  602. STDBGOUT((1, TEXT("Translating STWM_CSCNETUP for server \"%s\""), pszServer));
  603. //
  604. // Server reported back by the CSC agent.
  605. // Add it's name to a persistent (in memory) list of
  606. // servers available for reconnection.
  607. // Also clear the "no net" flag.
  608. //
  609. bServerIsBack = true;
  610. ServerPendingReconnection(pszServer);
  611. if (log.LoggingEnabled())
  612. {
  613. log.Push(pszServer);
  614. log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SERVER_AVAILABLE, 1);
  615. }
  616. }
  617. else
  618. {
  619. STDBGOUT((1, TEXT("Translating STWM_CSCNETUP (no associated server)")));
  620. if (log.LoggingEnabled())
  621. {
  622. log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_NET_STARTED, 2);
  623. }
  624. }
  625. }
  626. else if (STWM_CSCNETDOWN == uMsg)
  627. {
  628. //
  629. // This is the only place where transitions from online to
  630. // offline state are noted in the shell process. (CSCUISetState
  631. // and OnQueryNetDown execute in WinLogon's process).
  632. //
  633. if (TEXT('\0') != *pszServer)
  634. {
  635. STDBGOUT((1, TEXT("Translating STWM_CSCNETDOWN for server \"%s\""), pszServer));
  636. if (!m_bNoNet)
  637. {
  638. LPTSTR pszTemp;
  639. if (LocalAllocString(&pszTemp, pszServer))
  640. {
  641. PostToSystray(PWM_REFRESH_SHELL, 0, (LPARAM)pszTemp);
  642. }
  643. }
  644. //
  645. // Server reported down by the CSC agent.
  646. // Remove it's name from the persistent (in memory) list
  647. // of servers available for reconnection.
  648. //
  649. ServerUnavailable(pszServer);
  650. if (log.LoggingEnabled())
  651. {
  652. log.Push(pszServer);
  653. log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SERVER_OFFLINE, 1);
  654. }
  655. }
  656. else
  657. {
  658. STDBGOUT((1, TEXT("Translating STWM_CSCNETDOWN (no associated server)")));
  659. //
  660. // Entire network reported down by the CSC agent.
  661. // Remove all names from the persistent (in memory) list
  662. // of servers available for reconnection. m_bNoNet is the only persistent
  663. // state we have. Once it is set, the only thing that can reset it
  664. // is a STWM_CSCNETUP message from the CSC agent.
  665. //
  666. if (!m_bNoNet)
  667. PostToSystray(PWM_REFRESH_SHELL, 0, 0);
  668. m_bNoNet = true;
  669. AllServersUnavailable();
  670. if (log.LoggingEnabled())
  671. {
  672. log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_NET_STOPPED, 2);
  673. }
  674. }
  675. }
  676. else if (STWM_STATUSCHECK == uMsg)
  677. {
  678. STDBGOUT((1, TEXT("Translating STWM_STATUSCHECK")));
  679. }
  680. else if (STWM_CACHE_CORRUPTED == uMsg)
  681. {
  682. //
  683. // Note: No check for LoggingEnabled(). We always log corrupted cache
  684. // regardless of logging level.
  685. //
  686. STDBGOUT((1, TEXT("Translating STWM_CACHE_CORRUPTED")));
  687. log.ReportEvent(EVENTLOG_ERROR_TYPE, MSG_E_CACHE_CORRUPTED, 0);
  688. }
  689. //
  690. // If CSC is disabled or the cache is empty, the default UI state
  691. // is "online".
  692. //
  693. eSysTrayState state = STS_ONLINE;
  694. if (IsCSCEnabled())
  695. {
  696. DWORD dwStatus;
  697. DWORD dwPinCount;
  698. DWORD dwHintFlags;
  699. WIN32_FIND_DATA fd;
  700. FILETIME ft;
  701. CCscFindHandle hFind;
  702. hFind = CacheFindFirst(NULL, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft);
  703. if (hFind.IsValid())
  704. {
  705. //
  706. // We need these three temporary name lists to reconcile a problem with
  707. // the way the CSC cache and RDR are designed. When we enumerate the cache,
  708. // we enumerate individual shares in the cache. Each share has some condition
  709. // (i.e. dirty, offline etc) associated with it. The problem is that the
  710. // redirector handles things on a server basis. So when a particular share
  711. // is offline, in reality the entire server is offline. We've decided that
  712. // the UI should reflect things on a server (computer) basis so we need to
  713. // avoid including the states of multiple shares from the same server in
  714. // our totals. These three lists are used to store the names of servers
  715. // with shares in one of the three states (offline, dirty, pending recon).
  716. // If we enumerate a share with one of these states and find it already
  717. // exists in the corresponding list, we don't include this share in the
  718. // statistics.
  719. //
  720. int cShares = 0;
  721. CServerList OfflineList;
  722. CServerList DirtyList;
  723. CServerList BackList;
  724. //
  725. // If a server is back, assume we can auto-reconnect it.
  726. //
  727. bool bAutoReconnectServer = bServerIsBack;
  728. TCHAR szAutoReconnectShare[MAX_PATH] = {0};
  729. DWORD dwPathSpeed = 0;
  730. do
  731. {
  732. bool bShareIsOnServer = boolify(PathIsPrefix(pszServer, fd.cFileName));
  733. bool bShareHasModifiedFiles = false;
  734. bool bShareHasOpenFiles = false;
  735. //
  736. // A share participates in the systray UI calculations only if the
  737. // share contains files OR the share is currently "offline".
  738. // Because of the CSC database design, CSC doesn't remove a share
  739. // entry after all it's files have been removed from the cache.
  740. // Therefore we need this extra check to avoid including empty shares in the UI.
  741. //
  742. if (ShareHasFiles(fd.cFileName, &bShareHasModifiedFiles, &bShareHasOpenFiles) ||
  743. ShareIsOffline(dwStatus))
  744. {
  745. cShares++;
  746. if (bShareIsOnServer && (bShareHasModifiedFiles || bShareHasOpenFiles))
  747. {
  748. //
  749. // Auto-reconnect isn't allowed if one or more shares on the server
  750. // have open files or files modified offline. Auto-reconnection
  751. // would put the cache into a dirty state.
  752. //
  753. bAutoReconnectServer = false;
  754. }
  755. //
  756. // A share can be in one of 4 states:
  757. // Online
  758. // Dirty
  759. // Offline
  760. // Pending reconnection ('back')
  761. //
  762. // Note that our definition of Dirty implies Online, and Pending
  763. // Reconnection implies Offline. That is, an offline share is
  764. // never dirty and an online share is never pending reconnection.
  765. //
  766. //---------------------------------------------------------------------
  767. // Is the share online?
  768. //---------------------------------------------------------------------
  769. if (!ShareIsOffline(dwStatus))
  770. {
  771. //---------------------------------------------------------------------
  772. // Is the share dirty? (online + offline changes)
  773. //---------------------------------------------------------------------
  774. if (bShareHasModifiedFiles)
  775. {
  776. STDBGOUT((3, TEXT("Share \"%s\" is dirty (0x%08X)"), fd.cFileName, dwStatus));
  777. DirtyList.Add(fd.cFileName);
  778. }
  779. else
  780. {
  781. STDBGOUT((3, TEXT("Share \"%s\" is online (0x%08X)"), fd.cFileName, dwStatus));
  782. }
  783. }
  784. else // Offline
  785. {
  786. //---------------------------------------------------------------------
  787. // Is the server back?
  788. //---------------------------------------------------------------------
  789. if (IsServerPendingReconnection(fd.cFileName))
  790. {
  791. STDBGOUT((3, TEXT("Share \"%s\" is pending reconnection (0x%08X)"), fd.cFileName, dwStatus));
  792. BackList.Add(fd.cFileName);
  793. }
  794. else
  795. {
  796. STDBGOUT((3, TEXT("Share \"%s\" is OFFLINE (0x%08X)"), fd.cFileName, dwStatus));
  797. OfflineList.Add(fd.cFileName);
  798. }
  799. }
  800. }
  801. if (!ShareIsOffline(dwStatus))
  802. {
  803. // It's online, so it can't be pending reconnection.
  804. ServerReconnected(fd.cFileName);
  805. // ...and there's no need to reconnect it.
  806. if (bShareIsOnServer)
  807. bAutoReconnectServer = false;
  808. }
  809. if (FLAG_CSC_SHARE_STATUS_PINNED_OFFLINE & dwStatus)
  810. {
  811. //
  812. // Finally... if the user has 'forced' the share offline
  813. // we don't allow auto-reconnection. This allows the
  814. // user to 'tag' a share as "always offline" from an
  815. // auto-reconnect perspective. One might do this for a
  816. // RAS connection.
  817. //
  818. bAutoReconnectServer = false;
  819. }
  820. if (bAutoReconnectServer && bShareIsOnServer && TEXT('\0') == szAutoReconnectShare[0])
  821. {
  822. //
  823. // Remember the share name for possible auto-reconnection.
  824. // The transition API is TransitionServerOnline but it takes a share name.
  825. // Bad choice of names (IMO) but that's the way Shishir did it in the
  826. // CSC APIs. It can be any share on the server.
  827. //
  828. // However, it's possible to have defunct shares in the
  829. // database. Try to find one that's connectable.
  830. //
  831. if (CSCCheckShareOnlineEx(fd.cFileName, &dwPathSpeed))
  832. {
  833. STDBGOUT((3, TEXT("Share \"%s\" alive at %d00 bps"), fd.cFileName, dwPathSpeed));
  834. StringCchCopy(szAutoReconnectShare, ARRAYSIZE(szAutoReconnectShare), fd.cFileName);
  835. }
  836. else
  837. {
  838. STDBGOUT((3, TEXT("Share \"%s\" unreachable, error = %d"), fd.cFileName, GetLastError()));
  839. }
  840. }
  841. }
  842. while(CacheFindNext(hFind, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft));
  843. if (bAutoReconnectServer)
  844. {
  845. //---------------------------------------------------------------------
  846. // Handle auto-reconnection.
  847. //---------------------------------------------------------------------
  848. //
  849. if (TEXT('\0') != szAutoReconnectShare[0])
  850. {
  851. //
  852. // Server was reported "BACK" by the CSC agent and it has no open files
  853. // nor files modified offline and it's not on a slow link.
  854. // This makes it a candidate for automatic reconnection. Try it.
  855. //
  856. STDBGOUT((1, TEXT("Attempting to auto-reconnect \"%s\""), szAutoReconnectShare));
  857. if (TransitionShareOnline(szAutoReconnectShare, TRUE, TRUE, dwPathSpeed))
  858. {
  859. //
  860. // The server has been reconnected. Remove it's name from the
  861. // "pending reconnection" list.
  862. //
  863. ServerReconnected(pszServer);
  864. //
  865. // Remove this server from the temporary lists we've been keeping.
  866. //
  867. DirtyList.Remove(pszServer);
  868. BackList.Remove(pszServer);
  869. OfflineList.Remove(pszServer);
  870. if (log.LoggingEnabled())
  871. {
  872. log.Push(pszServer);
  873. log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SERVER_AUTORECONNECT, 3);
  874. }
  875. }
  876. }
  877. }
  878. int cDirty = DirtyList.Count();
  879. int cBack = BackList.Count();
  880. int cOffline = OfflineList.Count();
  881. STDBGOUT((2, TEXT("Cache check server results: cShares = %d, cDirty = %d, cBack = %d, cOffline = %d"),
  882. cShares, cDirty, cBack, cOffline));
  883. //
  884. // This code path is a waterfall where lower-priority states are overwritten
  885. // by higher-priority states as they are encountered. The order of this array
  886. // is important. It's ordered by increasing priority (no net is
  887. // highest priority for systray UI).
  888. //
  889. CServerList *pServerList = NULL;
  890. struct Criteria
  891. {
  892. int cnt; // Number of applicable servers found.
  893. eSysTrayState state; // Single-item UI state.
  894. eSysTrayState mstate; // Multi-item UI state.
  895. CServerList *pList; // Ptr to applicable list with server names.
  896. } rgCriteria[] = {
  897. { cOffline, STS_OFFLINE, STS_MOFFLINE, &OfflineList },
  898. { cBack, STS_SERVERBACK, STS_MSERVERBACK, &BackList },
  899. { cDirty, STS_DIRTY, STS_MDIRTY, &DirtyList },
  900. { cShares && m_bNoNet ? 1 : 0, STS_NONET, STS_NONET, NULL }
  901. };
  902. for (int i = 0; i < ARRAYSIZE(rgCriteria); i++)
  903. {
  904. Criteria& c = rgCriteria[i];
  905. if (0 < c.cnt)
  906. {
  907. state = c.mstate;
  908. if (1 == c.cnt)
  909. {
  910. state = c.state;
  911. pServerList = NULL;
  912. if (NULL != c.pList && 1 == c.pList->Count())
  913. {
  914. pServerList = c.pList;
  915. }
  916. }
  917. }
  918. }
  919. if (NULL != pServerList)
  920. {
  921. //
  922. // We had a single-server condition so write the server name
  923. // to the caller's server name buffer.
  924. // If we didn't have a single-server condition, the buffer
  925. // remains unchanged.
  926. //
  927. StringCchCopy(pszServer, cchServer, pServerList->Get(0));
  928. }
  929. }
  930. }
  931. STDBGOUT((1, TEXT("Translated to SysTray UI state %s"), SysTrayStateStr(state)));
  932. return state;
  933. }
  934. //
  935. // Ping offline servers. If any are alive, update status and
  936. // auto-reconnect them if possible. This is typically done
  937. // after a sync operation has completed.
  938. //
  939. DWORD WINAPI
  940. _PingServersThread(LPVOID /*pThreadData*/)
  941. {
  942. DWORD dwStatus;
  943. WIN32_FIND_DATA fd;
  944. HANDLE hFind;
  945. hFind = CacheFindFirst(NULL, &fd, &dwStatus, NULL, NULL, NULL);
  946. if (INVALID_HANDLE_VALUE != hFind)
  947. {
  948. CServerList BackList;
  949. do
  950. {
  951. // If the tray state becomes Online or NoNet, we can quit
  952. eSysTrayState state = (eSysTrayState)SendToSystray(PWM_QUERY_UISTATE, 0, 0);
  953. if (STS_ONLINE == state || STS_NONET == state)
  954. break;
  955. // Call BackList.Exists here to avoid extra calls to
  956. // CSCCheckShareOnline. (Add also calls Exists)
  957. if ((FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwStatus) &&
  958. !BackList.Exists(fd.cFileName))
  959. {
  960. if (!CSCCheckShareOnline(fd.cFileName))
  961. {
  962. DWORD dwErr = GetLastError();
  963. if (ERROR_ACCESS_DENIED != dwErr &&
  964. ERROR_LOGON_FAILURE != dwErr)
  965. {
  966. // The share is not reachable
  967. continue;
  968. }
  969. // Access denied or logon failure means the server is
  970. // reachable, but we don't have valid credentials.
  971. }
  972. // The share is offline but available again.
  973. STDBGOUT((1, TEXT("Detected server back: %s"), fd.cFileName));
  974. BackList.Add(fd.cFileName);
  975. // Get the \\server name (minus the sharename) and
  976. // tell ourselves that it's back.
  977. LPCTSTR pszServer = BackList.Get(BackList.Count() - 1);
  978. if (pszServer)
  979. {
  980. CSCUISetState(STWM_CSCNETUP, 0, (LPARAM)pszServer);
  981. }
  982. }
  983. }
  984. while(CacheFindNext(hFind, &fd, &dwStatus, NULL, NULL, NULL));
  985. CSCFindClose(hFind);
  986. }
  987. DllRelease();
  988. FreeLibraryAndExitThread(g_hInstance, 0);
  989. return 0;
  990. }
  991. void
  992. CStateMachine::PingServers()
  993. {
  994. // Don't bother trying if there's no net.
  995. if (!m_bNoNet)
  996. {
  997. DWORD dwThreadID;
  998. // Give the thread a reference to the DLL
  999. HINSTANCE hInstThisDll = LoadLibrary(c_szDllName);
  1000. DllAddRef();
  1001. HANDLE hThread = CreateThread(NULL,
  1002. 0,
  1003. _PingServersThread,
  1004. NULL,
  1005. 0,
  1006. &dwThreadID);
  1007. if (hThread)
  1008. {
  1009. CloseHandle(hThread);
  1010. }
  1011. else
  1012. {
  1013. // CreateThread failed, cleanup
  1014. DllRelease();
  1015. FreeLibrary(hInstThisDll);
  1016. }
  1017. }
  1018. }
  1019. //
  1020. // Determine if a given share has files cached in the CSC cache.
  1021. //
  1022. //
  1023. bool
  1024. CStateMachine::ShareHasFiles(
  1025. LPCTSTR pszShare,
  1026. bool *pbModified,
  1027. bool *pbOpen
  1028. ) const
  1029. {
  1030. //
  1031. // Exclude the following:
  1032. // 1. Directories.
  1033. // 2. Files marked as "locally deleted".
  1034. //
  1035. // NOTE: The filtering done by this function must be the same as
  1036. // in several other places throughout the CSCUI code.
  1037. // To locate these, search the source for the comment
  1038. // string CSCUI_ITEM_FILTER.
  1039. //
  1040. const DWORD fExclude = SSEF_LOCAL_DELETED |
  1041. SSEF_DIRECTORY;
  1042. //
  1043. // Stop stats enumeration when we've found all of the following:
  1044. // 1. At least one file.
  1045. // 2. At least one modified file.
  1046. // 3. At least one file with either USER access OR GUEST access.
  1047. //
  1048. const DWORD fUnity = SSUF_TOTAL |
  1049. SSUF_MODIFIED |
  1050. SSUF_ACCUSER |
  1051. SSUF_ACCGUEST |
  1052. SSUF_ACCOR;
  1053. CSCSHARESTATS ss;
  1054. CSCGETSTATSINFO si = { fExclude, fUnity, true, false };
  1055. _GetShareStatisticsForUser(pszShare, // Share name.
  1056. &si,
  1057. &ss); // Destination buffer.
  1058. if (NULL != pbModified)
  1059. {
  1060. *pbModified = (0 < ss.cModified);
  1061. }
  1062. if (NULL != pbOpen)
  1063. {
  1064. *pbOpen = ss.bOpenFiles;
  1065. }
  1066. return 0 < ss.cTotal;
  1067. }
  1068. //-----------------------------------------------------------------------------
  1069. // CSysTrayUI member functions.
  1070. //-----------------------------------------------------------------------------
  1071. //
  1072. // This is the minimum interval (in ms) allowed between state changes of
  1073. // the systray UI. A value of 0 would result in immediate updates as
  1074. // notifications are received from the CSC agent. A value of 60000 would
  1075. // cause any state changes received less than 60 seconds after the previous
  1076. // state change to be queued. 60 seconds after the previous state change,
  1077. // if a state change is queued it is applied to the systray UI.
  1078. // Something to consider is dynamically adjusting
  1079. //
  1080. const int CSysTrayUI::s_iMinStateChangeInterval = 10000; // 10 seconds.
  1081. CSysTrayUI::CSysTrayUI(
  1082. HWND hwndNotify
  1083. ) : m_idFlashingTimer(0),
  1084. m_idReminderTimer(0),
  1085. m_idStateChangeTimer(0),
  1086. m_iIconFlashTime(GetCaretBlinkTime()),
  1087. m_hIconNoOverlay(s_rgIconInfo[int(STS_OFFLINE)].hIcon), // The offline icon is used
  1088. // as the non-overlay icon for
  1089. // flashing.
  1090. m_hwndNotify(hwndNotify),
  1091. m_dwFlashingExpires(0),
  1092. m_dwNextStateChange(0),
  1093. m_state(STS_ONLINE),
  1094. m_statePrev(STS_INVALID),
  1095. m_stateQueued(STS_INVALID),
  1096. m_bFlashOverlay(false),
  1097. m_bActive(false)
  1098. {
  1099. //
  1100. // Load up the required icons.
  1101. //
  1102. for (int i = 0; i < ARRAYSIZE(s_rgIconInfo); i++)
  1103. {
  1104. IconInfo& sti = s_rgIconInfo[i];
  1105. if (NULL == sti.hIcon && 0 != sti.idIcon)
  1106. {
  1107. sti.hIcon = (HICON)LoadImage(g_hInstance,
  1108. MAKEINTRESOURCE(sti.idIcon),
  1109. IMAGE_ICON,
  1110. CSC_ICON_CX,
  1111. CSC_ICON_CY,
  1112. LR_LOADMAP3DCOLORS);
  1113. if (NULL == sti.hIcon)
  1114. {
  1115. Trace((TEXT("CSCUI ERROR %d loading Icon ID = %d"), GetLastError(), sti.idIcon));
  1116. }
  1117. }
  1118. }
  1119. m_szServer[0] = TEXT('\0');
  1120. m_szServerQueued[0] = TEXT('\0');
  1121. UpdateSysTray(UF_ICON);
  1122. }
  1123. CSysTrayUI::~CSysTrayUI(
  1124. void
  1125. )
  1126. {
  1127. if (0 != m_idStateChangeTimer)
  1128. KillTimer(m_hwndNotify, m_idStateChangeTimer);
  1129. }
  1130. //
  1131. // Singleton instance access.
  1132. //
  1133. CSysTrayUI&
  1134. CSysTrayUI::GetInstance(
  1135. void
  1136. )
  1137. {
  1138. static CSysTrayUI TheUI(_FindNotificationWindow());
  1139. return TheUI;
  1140. }
  1141. //
  1142. // Change the current state of the UI to a new state.
  1143. // Returns:
  1144. // true = state was changed.
  1145. // false = state was not changed.
  1146. //
  1147. bool
  1148. CSysTrayUI::SetState(
  1149. eSysTrayState state,
  1150. LPCTSTR pszServer // Optional. Default is NULL.
  1151. )
  1152. {
  1153. bool bResult = false;
  1154. //
  1155. // Apply a state change only if the state has actually changed.
  1156. //
  1157. if (state != m_state)
  1158. {
  1159. //
  1160. // Apply a state change only if there's not a sync in progress.
  1161. // If there is a sync in progress, we'll receive a CSCWM_DONESYNCING
  1162. // message when the sync is finished which will trigger a UI update.
  1163. //
  1164. if (!::IsSyncInProgress())
  1165. {
  1166. if (0 == m_idStateChangeTimer)
  1167. {
  1168. //
  1169. // The state change timer is not active. That means it's OK
  1170. // to update the tray UI.
  1171. //
  1172. STDBGOUT((1, TEXT("Changing SysTray UI state %s -> %s"),
  1173. SysTrayStateStr(m_state),
  1174. SysTrayStateStr(state)));
  1175. m_statePrev = m_state;
  1176. m_state = state;
  1177. UpdateSysTray(eUpdateFlags(UF_ICON | UF_BALLOON), pszServer);
  1178. //
  1179. // Reset the state change timer so that we will not produce a
  1180. // visible change in the tray UI for at least another
  1181. // s_iMinStateChangeInterval milliseconds.
  1182. // Also invalidate the queued state info so that if the update timer
  1183. // expires before we queue a state change, it will be a no-op.
  1184. //
  1185. STDBGOUT((2, TEXT("Setting state change timer")));
  1186. m_stateQueued = STS_INVALID;
  1187. m_idStateChangeTimer = SetTimer(m_hwndNotify,
  1188. ID_TIMER_STATECHANGE,
  1189. s_iMinStateChangeInterval,
  1190. StateChangeTimerProc);
  1191. bResult = true;
  1192. }
  1193. else
  1194. {
  1195. //
  1196. // The state change timer is active so we can't update the tray
  1197. // UI right now. We'll queue up the state information so when the
  1198. // timer expires this state will be applied. Note that the "queue"
  1199. // is only ONE item deep. Each successive addition to the queue
  1200. // overwrites the current content.
  1201. //
  1202. STDBGOUT((2, TEXT("Queueing state change to %s."), SysTrayStateStr(state)));
  1203. m_stateQueued = state;
  1204. if (NULL != pszServer)
  1205. {
  1206. StringCchCopy(m_szServerQueued, ARRAYSIZE(m_szServerQueued), pszServer);
  1207. }
  1208. else
  1209. {
  1210. m_szServerQueued[0] = TEXT('\0');
  1211. }
  1212. }
  1213. }
  1214. else
  1215. {
  1216. STDBGOUT((2, TEXT("Sync in progress. SysTray state not changed.")));
  1217. }
  1218. }
  1219. return bResult;
  1220. }
  1221. //
  1222. // Called each time the state change timer expires.
  1223. //
  1224. VOID CALLBACK
  1225. CSysTrayUI::StateChangeTimerProc(
  1226. HWND hwnd,
  1227. UINT uMsg,
  1228. UINT_PTR idEvent,
  1229. DWORD dwTime
  1230. )
  1231. {
  1232. //
  1233. // Call a non-static function of the singleton instance so
  1234. // we have access to private members.
  1235. //
  1236. CSysTrayUI::GetInstance().OnStateChangeTimerExpired();
  1237. }
  1238. void
  1239. CSysTrayUI::OnStateChangeTimerExpired(
  1240. void
  1241. )
  1242. {
  1243. STDBGOUT((2, TEXT("State change timer expired. Queued state = %s"),
  1244. SysTrayStateStr(m_stateQueued)));
  1245. //
  1246. // Kill the timer and set it's ID to 0.
  1247. // This will let SetState() know that the timer has expired and
  1248. // it's OK to update the tray UI.
  1249. //
  1250. if (0 != m_idStateChangeTimer)
  1251. {
  1252. KillTimer(m_hwndNotify, m_idStateChangeTimer);
  1253. m_idStateChangeTimer = 0;
  1254. }
  1255. if (int(m_stateQueued) != int(STS_INVALID))
  1256. {
  1257. //
  1258. // Call SetState ONLY if queued info is valid; meaning
  1259. // there was something in the queue.
  1260. //
  1261. SetState(m_stateQueued, m_szServerQueued);
  1262. }
  1263. }
  1264. //
  1265. // On WM_WININICHANGED update the icon flash timer.
  1266. //
  1267. void
  1268. CSysTrayUI::OnWinIniChange(
  1269. LPCTSTR pszSection
  1270. )
  1271. {
  1272. m_iIconFlashTime = GetCaretBlinkTime();
  1273. KillIconFlashTimer();
  1274. UpdateSysTray(UF_FLASHICON);
  1275. }
  1276. //
  1277. // Show the reminder balloon associated with the current UI state.
  1278. //
  1279. void
  1280. CSysTrayUI::ShowReminderBalloon(
  1281. void
  1282. )
  1283. {
  1284. UpdateSysTray(eUpdateFlags(UF_BALLOON | UF_REMINDER));
  1285. }
  1286. //
  1287. // All roads lead here.
  1288. // This function is the kitchen sink for updating the systray.
  1289. // It's kind of a long function but it centralizes all changes to
  1290. // the systray. It's divided into 3 basic parts:
  1291. //
  1292. // 1. Change the tray icon. (UF_ICON)
  1293. // 2. Flash the tray icon. (UF_FLASHICON)
  1294. // 3. Display a notification balloon. (UF_BALLOON)
  1295. //
  1296. // Part or all of these can be performed in a single call depending
  1297. // upon the content of the uFlags argument.
  1298. //
  1299. void
  1300. CSysTrayUI::UpdateSysTray(
  1301. eUpdateFlags uFlags,
  1302. LPCTSTR pszServer // optional. Default is NULL.
  1303. )
  1304. {
  1305. NOTIFYICONDATA nid = {0};
  1306. if (!IsWindow(m_hwndNotify))
  1307. return;
  1308. //
  1309. // If an icon is active, we're modifying it.
  1310. // If none active, we're adding one.
  1311. //
  1312. DWORD nimsg = NIM_MODIFY;
  1313. nid.cbSize = sizeof(NOTIFYICONDATA);
  1314. nid.uID = PWM_TRAYCALLBACK;
  1315. nid.uFlags = NIF_MESSAGE;
  1316. nid.uCallbackMessage = PWM_TRAYCALLBACK;
  1317. nid.hWnd = m_hwndNotify;
  1318. IconInfo& sti = s_rgIconInfo[int(m_state)];
  1319. if (NULL != pszServer && TEXT('\0') != *pszServer)
  1320. {
  1321. //
  1322. // Copy the name of the server to a member variable.
  1323. // Skip passed the leading "\\".
  1324. //
  1325. while(*pszServer && TEXT('\\') == *pszServer)
  1326. pszServer++;
  1327. StringCchCopy(m_szServer, ARRAYSIZE(m_szServer), pszServer);
  1328. }
  1329. //
  1330. // Change the icon --------------------------------------------------------
  1331. //
  1332. if (UF_ICON & uFlags)
  1333. {
  1334. nid.uFlags |= NIF_ICON;
  1335. if (0 == sti.idIcon)
  1336. {
  1337. //
  1338. // This state doesn't have an icon. Delete from systray.
  1339. //
  1340. nimsg = NIM_DELETE;
  1341. }
  1342. else
  1343. {
  1344. if (!m_bActive)
  1345. nimsg = NIM_ADD;
  1346. nid.hIcon = sti.hIcon;
  1347. //
  1348. // If applicable, always flash icon when first showing it.
  1349. //
  1350. uFlags = eUpdateFlags(uFlags | UF_FLASHICON);
  1351. //
  1352. // Set the tooltip.
  1353. //
  1354. nid.uFlags |= NIF_TIP;
  1355. GetTooltipText(m_state, nid.szTip, ARRAYSIZE(nid.szTip));
  1356. }
  1357. m_bFlashOverlay = false;
  1358. KillIconFlashTimer();
  1359. }
  1360. //
  1361. // Flash the icon ---------------------------------------------------------
  1362. //
  1363. if (UF_FLASHICON & uFlags)
  1364. {
  1365. if (0 != sti.iFlashTimeout)
  1366. {
  1367. nid.uFlags |= NIF_ICON; // Flashing is actually displaying a new icon.
  1368. //
  1369. // This icon is a flashing icon.
  1370. //
  1371. if (0 == m_idFlashingTimer)
  1372. {
  1373. //
  1374. // No timer started yet. Start one.
  1375. //
  1376. STDBGOUT((2, TEXT("Starting icon flash timer. Time = %d ms"), m_iIconFlashTime));
  1377. m_idFlashingTimer = SetTimer(m_hwndNotify,
  1378. ID_TIMER_FLASHICON,
  1379. m_iIconFlashTime,
  1380. FlashTimerProc);
  1381. if (0 != m_idFlashingTimer)
  1382. {
  1383. //
  1384. // Set the tick-count when the timer expires.
  1385. // An expiration time of (-1) means it never expires.
  1386. //
  1387. if (ICONFLASH_FOREVER != sti.iFlashTimeout)
  1388. m_dwFlashingExpires = GetTickCount() + sti.iFlashTimeout;
  1389. else
  1390. m_dwFlashingExpires = ICONFLASH_FOREVER;
  1391. }
  1392. }
  1393. nid.hIcon = m_bFlashOverlay ? sti.hIcon : m_hIconNoOverlay;
  1394. m_bFlashOverlay = !m_bFlashOverlay; // Toggle flash state.
  1395. }
  1396. }
  1397. //
  1398. // Update or hide the balloon ---------------------------------------------
  1399. //
  1400. if (UF_BALLOON & uFlags)
  1401. {
  1402. //
  1403. // If there's no balloon text mapped to the current UI state and these
  1404. // balloon flags, any current balloon will be destroyed. This is because
  1405. // the tray code destroys the current balloon before displaying the new one
  1406. // and it doesn't display a new one if it's passed a blank string.
  1407. //
  1408. nid.uFlags |= NIF_INFO;
  1409. DWORD dwBalloonFlags = (UF_REMINDER & uFlags) ? BTF_REMIND : BTF_INITIAL;
  1410. GetBalloonInfo(m_state,
  1411. dwBalloonFlags,
  1412. nid.szInfoTitle,
  1413. ARRAYSIZE(nid.szInfoTitle),
  1414. nid.szInfo,
  1415. ARRAYSIZE(nid.szInfo),
  1416. &nid.dwInfoFlags,
  1417. &nid.uTimeout);
  1418. //
  1419. // Any time we show a balloon, we reset the reminder timer.
  1420. // This is so that we don't get a balloon resulting from a state change
  1421. // immediately followed by a reminder balloon because the reminder
  1422. // timer expired.
  1423. //
  1424. bool bRestartReminderTimer = (BTF_REMIND == dwBalloonFlags && TEXT('\0') != nid.szInfo[0]) ||
  1425. StateHasBalloonText(m_state, BTF_REMIND);
  1426. ResetReminderTimer(bRestartReminderTimer);
  1427. }
  1428. //
  1429. // Notify the systray -----------------------------------------------------
  1430. //
  1431. if (NIM_DELETE == nimsg)
  1432. m_bActive = false;
  1433. if (Shell_NotifyIcon(nimsg, &nid))
  1434. {
  1435. if (NIM_ADD == nimsg)
  1436. m_bActive = true;
  1437. }
  1438. }
  1439. //
  1440. // Get the balloon text associated with a given systray UI state and with
  1441. // a given set of BTF_XXXXX (Balloon Text Flag) flags. The information
  1442. // is stored in the table s_rgBalloonInfo[]. The text and balloon timeout
  1443. // are returned in caller-provided buffers.
  1444. //
  1445. // The balloon text follows this format:
  1446. //
  1447. // <Header> <Status> \n
  1448. //
  1449. // <Body>
  1450. //
  1451. // <Directive>
  1452. //
  1453. // An example would be:
  1454. //
  1455. // Offline Files - Network Connection Lost
  1456. //
  1457. // The network connection to '\\worf' has been lost.
  1458. //
  1459. // Click here to view status.
  1460. //
  1461. // state is one of the STS_XXXXX flags.
  1462. // dwTextFlags is a mask of BTF_XXXXX flag bits.
  1463. //
  1464. void
  1465. CSysTrayUI::GetBalloonInfo(
  1466. eSysTrayState state,
  1467. DWORD dwTextFlags,
  1468. LPTSTR pszTextHdr,
  1469. int cchTextHdr,
  1470. LPTSTR pszTextBody,
  1471. int cchTextBody,
  1472. DWORD *pdwInfoFlags,
  1473. UINT *puTimeout
  1474. )
  1475. {
  1476. *pszTextHdr = TEXT('\0');
  1477. *pszTextBody = TEXT('\0');
  1478. if (SupressBalloon(m_statePrev, state))
  1479. {
  1480. STDBGOUT((3, TEXT("Balloon supressed")));
  1481. return;
  1482. }
  1483. int i = GetBalloonInfoIndex(state, dwTextFlags);
  1484. if (-1 != i)
  1485. {
  1486. BalloonInfo& bi = s_rgBalloonInfo[i];
  1487. TCHAR szHeader[80];
  1488. TCHAR szStatus[80];
  1489. TCHAR szDirective[80];
  1490. TCHAR szBody[MAX_PATH];
  1491. TCHAR szFmt[MAX_PATH];
  1492. if (STS_OFFLINE == state || STS_DIRTY == state || STS_SERVERBACK == state)
  1493. {
  1494. //
  1495. // State has only one server associated with it so that means we'll
  1496. // be including it in the balloon text body. Load the format
  1497. // string from a text resource and embed the server name in it.
  1498. //
  1499. LPTSTR rgpstr[] = { m_szServer };
  1500. LoadString(g_hInstance, bi.idBody, szFmt, ARRAYSIZE(szFmt));
  1501. FormatMessage(FORMAT_MESSAGE_FROM_STRING |
  1502. FORMAT_MESSAGE_ARGUMENT_ARRAY,
  1503. szFmt,
  1504. 0,0,
  1505. szBody,
  1506. ARRAYSIZE(szBody),
  1507. (va_list *)rgpstr);
  1508. }
  1509. else
  1510. {
  1511. //
  1512. // State has multiple servers associated with it so that means
  1513. // there's no name embedded in the body. It's just a simple string
  1514. // loaded from a text resource.
  1515. //
  1516. LoadString(g_hInstance, bi.idBody, szBody, ARRAYSIZE(szBody));
  1517. }
  1518. //
  1519. // Create the header text.
  1520. //
  1521. LoadString(g_hInstance, IDS_BALLOONHDR_FORMAT, szFmt, ARRAYSIZE(szFmt));
  1522. LoadString(g_hInstance, bi.idHeader, szHeader, ARRAYSIZE(szHeader));
  1523. LoadString(g_hInstance, bi.idStatus, szStatus, ARRAYSIZE(szStatus));
  1524. LPTSTR rgpstrHdr[] = { szHeader,
  1525. szStatus };
  1526. FormatMessage(FORMAT_MESSAGE_FROM_STRING |
  1527. FORMAT_MESSAGE_ARGUMENT_ARRAY,
  1528. szFmt,
  1529. 0,0,
  1530. pszTextHdr,
  1531. cchTextHdr,
  1532. (va_list *)rgpstrHdr);
  1533. //
  1534. // Create the body text.
  1535. //
  1536. LoadString(g_hInstance, IDS_BALLOONBODY_FORMAT, szFmt, ARRAYSIZE(szFmt));
  1537. LoadString(g_hInstance, bi.idDirective, szDirective, ARRAYSIZE(szDirective));
  1538. LPTSTR rgpstrBody[] = { szBody,
  1539. szDirective };
  1540. FormatMessage(FORMAT_MESSAGE_FROM_STRING |
  1541. FORMAT_MESSAGE_ARGUMENT_ARRAY,
  1542. szFmt,
  1543. 0,0,
  1544. pszTextBody,
  1545. cchTextBody,
  1546. (va_list *)rgpstrBody);
  1547. if (NULL != pdwInfoFlags)
  1548. {
  1549. *pdwInfoFlags = bi.dwInfoFlags;
  1550. }
  1551. if (NULL != puTimeout)
  1552. {
  1553. CConfig& config = CConfig::GetSingleton();
  1554. //
  1555. // Balloon timeout is stored in the registry.
  1556. //
  1557. UINT uTimeout = (BTF_INITIAL & dwTextFlags) ? config.InitialBalloonTimeoutSeconds() :
  1558. config.ReminderBalloonTimeoutSeconds();
  1559. *puTimeout = uTimeout * 1000;
  1560. }
  1561. }
  1562. }
  1563. //
  1564. // Find the index in s_rgBalloonInfo[] for a given state
  1565. // and BTF_XXXXXX flag.
  1566. // Returns -1 if no match in array.
  1567. //
  1568. int
  1569. CSysTrayUI::GetBalloonInfoIndex(
  1570. eSysTrayState state,
  1571. DWORD dwTextFlags
  1572. )
  1573. {
  1574. //
  1575. // Scan the balloon info table until we find a record for the
  1576. // specified systray UI state and BTF flags.
  1577. //
  1578. for (int i = 0; i < ARRAYSIZE(s_rgBalloonInfo); i++)
  1579. {
  1580. BalloonInfo& bi = s_rgBalloonInfo[i];
  1581. if (bi.state == state &&
  1582. bi.dwTextFlags == dwTextFlags &&
  1583. 0 != bi.idHeader &&
  1584. 0 != bi.idStatus &&
  1585. 0 != bi.idBody &&
  1586. 0 != bi.idDirective)
  1587. {
  1588. return i;
  1589. }
  1590. }
  1591. return -1;
  1592. }
  1593. //
  1594. // Determine if a balloon should not be displayed for a particular
  1595. // UI state transition.
  1596. //
  1597. bool
  1598. CSysTrayUI::SupressBalloon(
  1599. eSysTrayState statePrev,
  1600. eSysTrayState state
  1601. )
  1602. {
  1603. for (int i = 0; i < ARRAYSIZE(s_rgBalloonSupression); i++)
  1604. {
  1605. if (statePrev == s_rgBalloonSupression[i].stateFrom &&
  1606. state == s_rgBalloonSupression[i].stateTo)
  1607. {
  1608. return true;
  1609. }
  1610. }
  1611. return false;
  1612. }
  1613. //
  1614. // Do we have balloon text for a given state and balloon style?
  1615. // state is one of the STS_XXXXX flags.
  1616. // dwTextFlags is a mask of BTF_XXXXX flag bits.
  1617. //
  1618. bool
  1619. CSysTrayUI::StateHasBalloonText(
  1620. eSysTrayState state,
  1621. DWORD dwTextFlags
  1622. )
  1623. {
  1624. return (-1 != GetBalloonInfoIndex(state, dwTextFlags));
  1625. }
  1626. LPTSTR
  1627. CSysTrayUI::GetTooltipText(
  1628. eSysTrayState state,
  1629. LPTSTR pszText,
  1630. int cchText
  1631. )
  1632. {
  1633. *pszText = TEXT('\0');
  1634. //
  1635. // Scan the tooltip info table until we find a record for the
  1636. // specified systray UI state.
  1637. //
  1638. for (int i = 0; i < ARRAYSIZE(s_rgTooltipInfo); i++)
  1639. {
  1640. TooltipInfo& tti = s_rgTooltipInfo[i];
  1641. if (tti.state == state && 0 != tti.idTooltip)
  1642. {
  1643. TCHAR szTemp[MAX_PATH];
  1644. szTemp[0] = TEXT('\0');
  1645. int cchHeader = LoadString(g_hInstance, IDS_TT_HEADER, szTemp, ARRAYSIZE(szTemp));
  1646. if (STS_OFFLINE == state || STS_DIRTY == state || STS_SERVERBACK == state)
  1647. {
  1648. //
  1649. // State has only one server associated with it so that means we'll
  1650. // be including it in the tooltip text. Embed the server name in it.
  1651. //
  1652. TCHAR szFmt[160];
  1653. LPTSTR rgpstr[] = { m_szServer };
  1654. LoadString(g_hInstance, tti.idTooltip, szFmt, ARRAYSIZE(szFmt));
  1655. FormatMessage(FORMAT_MESSAGE_FROM_STRING |
  1656. FORMAT_MESSAGE_ARGUMENT_ARRAY,
  1657. szFmt,
  1658. 0,0,
  1659. szTemp + cchHeader,
  1660. ARRAYSIZE(szTemp) - cchHeader,
  1661. (va_list *)rgpstr);
  1662. }
  1663. else
  1664. {
  1665. //
  1666. // State has multiple servers associated with it so that means
  1667. // there's no name embedded in the tooltip. It's just a simple string
  1668. // loaded from a text resource.
  1669. //
  1670. LoadString(g_hInstance,
  1671. tti.idTooltip,
  1672. szTemp + cchHeader,
  1673. ARRAYSIZE(szTemp) - cchHeader);
  1674. }
  1675. StringCchCopy(pszText, cchText, szTemp);
  1676. }
  1677. }
  1678. return pszText;
  1679. }
  1680. //
  1681. // Stop the flashing icon by killing the timer.
  1682. //
  1683. void
  1684. CSysTrayUI::KillIconFlashTimer(
  1685. void
  1686. )
  1687. {
  1688. //
  1689. // Force a final update so we're displaying the proper icon then
  1690. // kill the timer.
  1691. //
  1692. if (0 != m_idFlashingTimer)
  1693. {
  1694. KillTimer(m_hwndNotify, m_idFlashingTimer);
  1695. m_idFlashingTimer = 0;
  1696. }
  1697. }
  1698. //
  1699. // Called by the OS each time the icon flash timer period expires.
  1700. // I use this rather than handling a WM_TIMER message so that
  1701. // timer processing is contained within the CSysTrayUI class.
  1702. //
  1703. VOID CALLBACK
  1704. CSysTrayUI::FlashTimerProc(
  1705. HWND hwnd,
  1706. UINT uMsg,
  1707. UINT_PTR idEvent,
  1708. DWORD dwTime
  1709. )
  1710. {
  1711. CSysTrayUI::GetInstance().HandleFlashTimer();
  1712. }
  1713. void
  1714. CSysTrayUI::HandleFlashTimer(
  1715. void
  1716. )
  1717. {
  1718. if (IconFlashedLongEnough())
  1719. {
  1720. //
  1721. // Kill the icon flashing timer and the icon will stop flashing.
  1722. // This doesn't actually kill the timer yet.
  1723. //
  1724. STDBGOUT((2, TEXT("Killing icon flash timer")));
  1725. m_bFlashOverlay = true;
  1726. UpdateSysTray(UF_FLASHICON);
  1727. KillIconFlashTimer();
  1728. }
  1729. else
  1730. {
  1731. //
  1732. // The CSysTrayUI instance maintains all information
  1733. // needed to cycle the icon. Just tell it to update
  1734. // the icon and it'll do the right thing.
  1735. //
  1736. UpdateSysTray(UF_FLASHICON);
  1737. }
  1738. }
  1739. //
  1740. // Determine if the flashing icon has flashed enough.
  1741. //
  1742. bool
  1743. CSysTrayUI::IconFlashedLongEnough(
  1744. void
  1745. )
  1746. {
  1747. return ICONFLASH_FOREVER != m_dwFlashingExpires &&
  1748. GetTickCount() >= m_dwFlashingExpires;
  1749. }
  1750. //
  1751. // Stop and restart the reminder timer.
  1752. // If bRestart is false, the timer is killed and not restarted.
  1753. // If bRestart is true, the timer is killed and a new one restarted.
  1754. //
  1755. void
  1756. CSysTrayUI::ResetReminderTimer(
  1757. bool bRestart
  1758. )
  1759. {
  1760. CConfig& config = CConfig::GetSingleton();
  1761. if (!config.NoReminders())
  1762. {
  1763. int cReminderInterval = (config.ReminderFreqMinutes() * 1000 * 60);
  1764. //
  1765. // Force a final update so we're displaying the proper icon then
  1766. // kill the timer.
  1767. //
  1768. if (0 != m_idReminderTimer)
  1769. {
  1770. KillTimer(m_hwndNotify, m_idReminderTimer);
  1771. m_idReminderTimer = 0;
  1772. }
  1773. //
  1774. // No timer started yet. Start one.
  1775. //
  1776. if (bRestart && 0 < cReminderInterval)
  1777. {
  1778. STDBGOUT((2, TEXT("Starting reminder timer. Timeout = %d ms"), cReminderInterval));
  1779. m_idReminderTimer = SetTimer(m_hwndNotify,
  1780. ID_TIMER_REMINDER,
  1781. cReminderInterval,
  1782. ReminderTimerProc);
  1783. }
  1784. }
  1785. }
  1786. //
  1787. // Called by the OS each time the reminder timer period expires.
  1788. // I use this rather than handling a WM_TIMER message so that
  1789. // timer processing is contained within the CSysTrayUI class.
  1790. //
  1791. VOID CALLBACK
  1792. CSysTrayUI::ReminderTimerProc(
  1793. HWND hwnd,
  1794. UINT uMsg,
  1795. UINT_PTR idEvent,
  1796. DWORD dwTime
  1797. )
  1798. {
  1799. STDBGOUT((2, TEXT("Showing reminder balloon")));
  1800. CSysTrayUI::GetInstance().ShowReminderBalloon();
  1801. }
  1802. //
  1803. // Called by the systray WndProc whenever the state of the systray should be
  1804. // updated.
  1805. //
  1806. // hWnd - HWND of the systray notification window.
  1807. //
  1808. // stwmMsg - STWM_CSCNETUP (Net or server is available for reconnect)
  1809. // STWM_CSCNETDOWN (Net or server is unavailable)
  1810. // STWM_STATUSCHECK (Check cache state and update systray)
  1811. //
  1812. // pszServer - non-NULL means CSC agent passed a server name
  1813. // associated with the STWM_XXXX message.
  1814. // This means there was a single server associated with the event
  1815. // rather than multiple servers or the entire net interface.
  1816. //
  1817. void
  1818. UpdateStatus(
  1819. CStateMachine *pSM,
  1820. HWND hWnd,
  1821. UINT stwmMsg,
  1822. LPTSTR pszServer
  1823. )
  1824. {
  1825. TraceEnter(TRACE_CSCST, "UpdateStatus");
  1826. TraceAssert(NULL != hWnd);
  1827. TCHAR szServerName[MAX_PATH] = { 0 };
  1828. if (pszServer)
  1829. {
  1830. StringCchCopy(szServerName, ARRAYSIZE(szServerName), pszServer);
  1831. }
  1832. //
  1833. // Translate the CSC agent inputs into a new systray UI state.
  1834. //
  1835. eSysTrayState state = pSM->TranslateInput(stwmMsg, szServerName, ARRAYSIZE(szServerName));
  1836. //
  1837. // Get reference to the singleton UI object and tell it to set the state.
  1838. // Note that it remembers all current UI state and will only actually
  1839. // update the systray if the UI state has changed. Here we can
  1840. // blindly tell it to update state. It will only do what's necessary.
  1841. //
  1842. CSysTrayUI::GetInstance().SetState(state, szServerName);
  1843. TraceLeaveVoid();
  1844. }
  1845. ///////////////////////////////////////////////////////////////////////////////
  1846. // _CreateMenu()
  1847. //
  1848. // Create context menu
  1849. //
  1850. HMENU _CreateMenu()
  1851. {
  1852. HMENU hmenu = NULL;
  1853. TraceEnter(TRACE_CSCST, "_CreateMenu");
  1854. hmenu = CreatePopupMenu();
  1855. if (NULL != hmenu)
  1856. {
  1857. CConfig& config = CConfig::GetSingleton();
  1858. TCHAR szTemp[MAX_PATH];
  1859. //
  1860. // Add the "Status" verb.
  1861. //
  1862. LoadString(g_hInstance, IDS_CSC_CM_STATUS, szTemp, ARRAYSIZE(szTemp));
  1863. AppendMenu(hmenu, MF_STRING, PWM_STATUSDLG, szTemp);
  1864. //
  1865. // Add the "Synchronize" verb
  1866. //
  1867. LoadString(g_hInstance, IDS_CSC_CM_SYNCHRONIZE, szTemp, ARRAYSIZE(szTemp));
  1868. AppendMenu(hmenu, MF_STRING, CSCWM_SYNCHRONIZE, szTemp);
  1869. if (!config.NoCacheViewer())
  1870. {
  1871. //
  1872. // Add the "View files" verb
  1873. //
  1874. LoadString(g_hInstance, IDS_CSC_CM_SHOWVIEWER, szTemp, ARRAYSIZE(szTemp));
  1875. AppendMenu(hmenu, MF_STRING, CSCWM_VIEWFILES, szTemp);
  1876. }
  1877. if (!config.NoConfigCache())
  1878. {
  1879. //
  1880. // Add the "Settings" verb
  1881. //
  1882. LoadString(g_hInstance, IDS_CSC_CM_SETTINGS, szTemp, ARRAYSIZE(szTemp));
  1883. AppendMenu(hmenu, MF_STRING, CSCWM_SETTINGS, szTemp);
  1884. }
  1885. //
  1886. // Left clicking the systray icon invokes the status dialog.
  1887. // Therefore, the "Status" verb is our default and must be in bold text.
  1888. //
  1889. SetMenuDefaultItem(hmenu, PWM_STATUSDLG, MF_BYCOMMAND);
  1890. }
  1891. TraceLeaveValue(hmenu);
  1892. }
  1893. ///////////////////////////////////////////////////////////////////////////////
  1894. // _ShowMenu()
  1895. //
  1896. UINT _ShowMenu(HWND hWnd, UINT uMenuNum, UINT uButton)
  1897. {
  1898. UINT iCmd = 0;
  1899. HMENU hmenu;
  1900. TraceEnter(TRACE_CSCST, "_ShowMenu");
  1901. hmenu = _CreateMenu();
  1902. if (hmenu)
  1903. {
  1904. POINT pt;
  1905. GetCursorPos(&pt);
  1906. SetForegroundWindow(hWnd);
  1907. iCmd = TrackPopupMenu(hmenu,
  1908. uButton | TPM_RETURNCMD | TPM_NONOTIFY,
  1909. pt.x,
  1910. pt.y,
  1911. 0,
  1912. hWnd,
  1913. NULL);
  1914. DestroyMenu(hmenu);
  1915. }
  1916. TraceLeaveValue(iCmd);
  1917. }
  1918. //
  1919. // This function is used to ensure that we don't try to process
  1920. // a WM_RBUTTONUP and WM_LBUTTONUP message at the same time.
  1921. // May be a little paranoid.
  1922. //
  1923. LRESULT
  1924. OnTrayIconSelected(
  1925. HWND hWnd,
  1926. UINT uMsg
  1927. )
  1928. {
  1929. static LONG bHandling = 0;
  1930. LRESULT lResult = 0;
  1931. if (0 == InterlockedCompareExchange(&bHandling, 1, 0))
  1932. {
  1933. UINT iCmd = 0;
  1934. switch (uMsg)
  1935. {
  1936. case WM_RBUTTONUP:
  1937. //
  1938. // Context menu
  1939. //
  1940. iCmd = _ShowMenu(hWnd, 1, TPM_RIGHTBUTTON);
  1941. break;
  1942. case WM_LBUTTONUP:
  1943. iCmd = PWM_STATUSDLG;
  1944. break;
  1945. default:
  1946. break;
  1947. }
  1948. if (iCmd)
  1949. {
  1950. PostMessage(hWnd, iCmd, 0, 0);
  1951. lResult = 1;
  1952. }
  1953. bHandling = 0;
  1954. }
  1955. return lResult;
  1956. }
  1957. ///////////////////////////////////////////////////////////////////////////////
  1958. // _Notify() -- systray notification handler
  1959. //
  1960. LRESULT _Notify(HWND hWnd, WPARAM /*wParam*/, LPARAM lParam)
  1961. {
  1962. LRESULT lResult = 0;
  1963. switch (lParam)
  1964. {
  1965. case WM_RBUTTONUP:
  1966. case WM_LBUTTONUP:
  1967. lResult = OnTrayIconSelected(hWnd, (UINT)lParam);
  1968. break;
  1969. default:
  1970. break;
  1971. }
  1972. return lResult;
  1973. }
  1974. bool IsServerBack(CStateMachine *pSM, LPCTSTR pszServer)
  1975. {
  1976. TCHAR szServer[MAX_PATH];
  1977. if (!PathIsUNC(pszServer))
  1978. {
  1979. //
  1980. // Ensure servername uses UNC format.
  1981. //
  1982. szServer[0] = TEXT('\\');
  1983. szServer[1] = TEXT('\\');
  1984. StringCchCopy(szServer+2, ARRAYSIZE(szServer)-2, pszServer);
  1985. pszServer = szServer;
  1986. }
  1987. return pSM->IsServerPendingReconnection(pszServer);
  1988. }
  1989. //
  1990. // Query CSC policy for the sync-at-logoff (quick vs. full)
  1991. // setting. If the policy is set, we enable SyncMgr's sync-at-logoff
  1992. // setting. Without this the CSC policy could be set, the SyncMgr
  1993. // setting NOT set and the user wouldn't get sync-at-logoff as the
  1994. // admin had anticipated.
  1995. //
  1996. void
  1997. ApplyCscSyncAtLogonAndLogoffPolicies(
  1998. void
  1999. )
  2000. {
  2001. bool bSetByPolicy = false;
  2002. CConfig& config = CConfig::GetSingleton();
  2003. config.SyncAtLogoff(&bSetByPolicy);
  2004. if (bSetByPolicy)
  2005. {
  2006. RegisterForSyncAtLogonAndLogoff(SYNCMGRREGISTERFLAG_PENDINGDISCONNECT,
  2007. SYNCMGRREGISTERFLAG_PENDINGDISCONNECT);
  2008. }
  2009. config.SyncAtLogon(&bSetByPolicy);
  2010. if (bSetByPolicy)
  2011. {
  2012. RegisterForSyncAtLogonAndLogoff(SYNCMGRREGISTERFLAG_CONNECT,
  2013. SYNCMGRREGISTERFLAG_CONNECT);
  2014. }
  2015. }
  2016. //
  2017. // Encryption/Decryption callback from CSC.
  2018. //
  2019. // dwReason dwParam1 dwParam2
  2020. // ------------------------- ------------------ --------------------------
  2021. // CSCPROC_REASON_BEGIN 1 == Encrypting 0
  2022. // CSCPROC_REASON_MORE_DATA 0 Win32 error code
  2023. // CSCPROC_REASON_END 1 == Completed dwParam1 == 1 ? 0
  2024. // dwParam1 == 0 ? GetLastError()
  2025. //
  2026. DWORD CALLBACK
  2027. EncryptDecryptCallback(
  2028. LPCWSTR lpszName,
  2029. DWORD dwStatus,
  2030. DWORD dwHintFlags,
  2031. DWORD dwPinCount,
  2032. WIN32_FIND_DATAW *pFind32,
  2033. DWORD dwReason,
  2034. DWORD dwParam1,
  2035. DWORD dwParam2,
  2036. DWORD_PTR dwContext
  2037. )
  2038. {
  2039. DWORD dwResult = CSCPROC_RETURN_CONTINUE;
  2040. const DWORD dwError = dwParam2;
  2041. //
  2042. // Some static data that needs to persist across callback calls.
  2043. //
  2044. static bool bEncrypting; // Encrypting or decrypting?
  2045. static bool bLoggingOff = false; // User logging off?
  2046. static int cFileErrors = 0; // How many file-specific errors reported?
  2047. static DWORD dwLastError;
  2048. static TCHAR szLastFile[MAX_PATH];
  2049. //
  2050. // If we've already detected the g_heventTerminate event
  2051. // no sense in continuing.
  2052. //
  2053. if (bLoggingOff)
  2054. return CSCPROC_RETURN_ABORT;
  2055. //
  2056. // If the wait fails for some reason, e.g. g_heventTerminate
  2057. // is NULL, then we will continue. Is that OK?
  2058. //
  2059. if (WAIT_OBJECT_0 == WaitForSingleObject(g_heventTerminate, 0))
  2060. {
  2061. //
  2062. // User is logging off. Need to end this now!
  2063. // Log an event so admin knows why encryption was incomplete.
  2064. //
  2065. // LOGGING LEVEL = 0 (always)
  2066. //
  2067. CscuiEventLog log;
  2068. log.ReportEvent(EVENTLOG_INFORMATION_TYPE,
  2069. bEncrypting ? MSG_I_ENCRYPT_USERLOGOFF : MSG_I_DECRYPT_USERLOGOFF,
  2070. 0);
  2071. dwResult = CSCPROC_RETURN_ABORT;
  2072. bLoggingOff = true;
  2073. }
  2074. else
  2075. {
  2076. switch(dwReason)
  2077. {
  2078. case CSCPROC_REASON_BEGIN:
  2079. //
  2080. // Reset static variables.
  2081. //
  2082. bEncrypting = boolify(dwParam1);
  2083. bLoggingOff = false;
  2084. cFileErrors = 0;
  2085. dwLastError = ERROR_SUCCESS;
  2086. szLastFile[0] = TEXT('\0');
  2087. break;
  2088. case CSCPROC_REASON_MORE_DATA:
  2089. if (ERROR_SUCCESS != dwError)
  2090. {
  2091. //
  2092. // An error occurred for this file.
  2093. //
  2094. CscuiEventLog log;
  2095. LPTSTR pszError = NULL;
  2096. FormatSystemError(&pszError, dwError);
  2097. if (0 == cFileErrors++)
  2098. {
  2099. //
  2100. // On the first error, log an error at level 0.
  2101. // By default, this is the only error the admin will see.
  2102. // They'll need to increase the event log level to level
  2103. // 2 in order to get events for each individual file. The
  2104. // event text describes this.
  2105. //
  2106. // LOGGING_LEVEL = 0
  2107. //
  2108. log.ReportEvent(EVENTLOG_ERROR_TYPE,
  2109. bEncrypting ? MSG_E_ENCRYPTFILE_ERRORS : MSG_E_DECRYPTFILE_ERRORS,
  2110. 0);
  2111. }
  2112. //
  2113. // Log the error for this file.
  2114. //
  2115. // LOGGING LEVEL = 2
  2116. //
  2117. log.Push(HRESULT(dwError), CEventLog::eFmtDec);
  2118. log.Push(lpszName);
  2119. log.Push(pszError ? pszError : TEXT(""));
  2120. if (S_OK == log.ReportEvent(EVENTLOG_ERROR_TYPE,
  2121. bEncrypting ? MSG_E_ENCRYPTFILE_FAILED : MSG_E_DECRYPTFILE_FAILED,
  2122. 2))
  2123. {
  2124. //
  2125. // We logged this event.
  2126. // Clear out the last error code and last filename so that
  2127. // we don't report this error again in response to CSCPROC_REASON_END.
  2128. //
  2129. szLastFile[0] = TEXT('\0');
  2130. dwLastError = ERROR_SUCCESS;
  2131. }
  2132. else
  2133. {
  2134. //
  2135. // Event was not logged because...
  2136. //
  2137. // a) ... an error occurred while logging the event.
  2138. // b) ... EventLoggingLevel policy is too low for this event.
  2139. //
  2140. // Save this error code and file name.
  2141. // We may need to report it in response to CSCPROC_REASON_END.
  2142. //
  2143. dwLastError = dwError;
  2144. StringCchCopy(szLastFile, ARRAYSIZE(szLastFile), lpszName);
  2145. }
  2146. if (pszError)
  2147. LocalFree(pszError);
  2148. }
  2149. break;
  2150. case CSCPROC_REASON_END:
  2151. {
  2152. const DWORD fCompleted = dwParam1;
  2153. CscuiEventLog log;
  2154. if (fCompleted)
  2155. {
  2156. //
  2157. // Add an event log entry that the encryption/decryption
  2158. // completed successfully.
  2159. //
  2160. // LOGGING LEVEL = 1
  2161. //
  2162. log.ReportEvent(EVENTLOG_INFORMATION_TYPE,
  2163. bEncrypting ? MSG_I_ENCRYPT_COMPLETE : MSG_I_DECRYPT_COMPLETE,
  2164. 1);
  2165. }
  2166. else
  2167. {
  2168. LPTSTR pszError = NULL;
  2169. if (ERROR_SUCCESS != dwError)
  2170. {
  2171. //
  2172. // Some general error with the process.
  2173. //
  2174. // LOGGING LEVEL = 0
  2175. //
  2176. FormatSystemError(&pszError, dwError);
  2177. log.Push(HRESULT(dwError), CEventLog::eFmtDec);
  2178. log.Push(pszError ? pszError : TEXT(""));
  2179. log.ReportEvent(EVENTLOG_ERROR_TYPE,
  2180. bEncrypting ? MSG_E_ENCRYPT_FAILED : MSG_E_DECRYPT_FAILED,
  2181. 0);
  2182. }
  2183. else if (ERROR_SUCCESS != dwLastError)
  2184. {
  2185. if (0 == cFileErrors++)
  2186. {
  2187. //
  2188. // On the first error, log an error at level 0.
  2189. // By default, this is the only error the admin will see.
  2190. // They'll need to increase the event log level to level
  2191. // 2 in order to get events for each individual file. The
  2192. // event text describes this.
  2193. //
  2194. // LOGGING_LEVEL = 0
  2195. //
  2196. log.ReportEvent(EVENTLOG_ERROR_TYPE,
  2197. bEncrypting ? MSG_E_ENCRYPTFILE_ERRORS : MSG_E_DECRYPTFILE_ERRORS,
  2198. 0);
  2199. }
  2200. //
  2201. // Encryption/decryption of some file failed and we did not
  2202. // log it in the "more data" callback.
  2203. //
  2204. // LOGGING LEVEL = 2
  2205. //
  2206. FormatSystemError(&pszError, dwLastError);
  2207. log.Push(HRESULT(dwLastError), CEventLog::eFmtDec);
  2208. log.Push(szLastFile);
  2209. log.Push(pszError ? pszError : TEXT(""));
  2210. log.ReportEvent(EVENTLOG_ERROR_TYPE,
  2211. bEncrypting ? MSG_E_ENCRYPTFILE_FAILED : MSG_E_DECRYPTFILE_FAILED,
  2212. 2);
  2213. }
  2214. if (pszError)
  2215. LocalFree(pszError);
  2216. }
  2217. break;
  2218. }
  2219. default:
  2220. break;
  2221. }
  2222. }
  2223. return dwResult;
  2224. }
  2225. DWORD
  2226. CacheEncryptionThreadProc(
  2227. LPVOID pvParams
  2228. )
  2229. {
  2230. const DWORD fEncrypt = (DWORD)(DWORD_PTR)pvParams;
  2231. HINSTANCE hmodCSCUI = LoadLibrary(c_szDllName);
  2232. if (NULL != hmodCSCUI)
  2233. {
  2234. //
  2235. // Try to get the encryption mutex.
  2236. //
  2237. HANDLE hMutex = RequestPermissionToEncryptCache();
  2238. if (NULL != hMutex)
  2239. {
  2240. //
  2241. // Ensure release of the mutex.
  2242. //
  2243. CMutexAutoRelease auto_release_mutex(hMutex);
  2244. STDBGOUT((1, TEXT("%s started."),
  2245. fEncrypt ? TEXT("Encryption") : TEXT("Decryption")));
  2246. //
  2247. // Do the encryption/decryption. Do it at a low thread priority so
  2248. // we don't steal CPU time from the UI.
  2249. //
  2250. SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST);
  2251. CSCEncryptDecryptDatabase(fEncrypt, EncryptDecryptCallback, (DWORD_PTR)0);
  2252. STDBGOUT((1, TEXT("%s complete."),
  2253. fEncrypt ? TEXT("Encryption") : TEXT("Decryption")));
  2254. }
  2255. else
  2256. {
  2257. //
  2258. // Someone else (probably the UI) is currently encrypting/decrypting
  2259. // the cache. We don't allow concurrent operations so we abort this
  2260. // one.
  2261. //
  2262. STDBGOUT((1, TEXT("%s aborted. Already in progress."),
  2263. fEncrypt ? TEXT("Encryption") : TEXT("Decryption")));
  2264. }
  2265. FreeLibraryAndExitThread(hmodCSCUI, 0);
  2266. }
  2267. return 0;
  2268. }
  2269. //
  2270. // Encrypt/Decrypt the cache according to system policy.
  2271. // This function will also correct a partial (encrypt/decrypt)
  2272. // state in the cache if necessary.
  2273. //
  2274. void
  2275. ApplyCacheEncryptionPolicy(
  2276. void
  2277. )
  2278. {
  2279. //
  2280. // Do a quick check to see if an encryption process is already in progress.
  2281. // This doesn't take the mutex but checks to see if someone else has
  2282. // it. Once we actually start the encryption on the background thread
  2283. // we'll take the mutex. Of course, if someone else sneeked in and grabbed
  2284. // the mutex between now and then we'll have to abort.
  2285. //
  2286. if (!IsEncryptionInProgress())
  2287. {
  2288. //
  2289. // Encrypt/Decrypt the cache files. Will provide progress info through
  2290. // the callback EncryptDecryptCallback. Errors are handled in the callback
  2291. // reason handlers.
  2292. //
  2293. CConfig& config = CConfig::GetSingleton();
  2294. bool bShouldBeEncrypted = config.EncryptCache();
  2295. BOOL bPartial;
  2296. const BOOL bIsEncrypted = IsCacheEncrypted(&bPartial);
  2297. if (bPartial || (boolify(bIsEncrypted) != bShouldBeEncrypted))
  2298. {
  2299. if (CscVolumeSupportsEncryption())
  2300. {
  2301. //
  2302. // Either we have a partially encrypted/decrypted cache or
  2303. // current encryption state is different from what policy wants.
  2304. // Encrypt/decrypt to rectify the situation.
  2305. // Run this on a separate thread so we don't block any processing
  2306. // on the tray UI thread (i.e. volume control).
  2307. //
  2308. DWORD dwThreadId;
  2309. HANDLE hThread = CreateThread(NULL, // Default security.
  2310. 0, // Default stack size.
  2311. CacheEncryptionThreadProc,
  2312. (VOID *)(DWORD_PTR)bShouldBeEncrypted,
  2313. 0, // Run immediately
  2314. &dwThreadId);
  2315. if (NULL != hThread)
  2316. {
  2317. CloseHandle(hThread);
  2318. }
  2319. }
  2320. else
  2321. {
  2322. //
  2323. // The CSC volume doesn't support encryption. Log an event so
  2324. // the admin will know why the cache wasn't encrypted by policy.
  2325. // Note that we won't hit this path in the "partial" case. Only
  2326. // if policy says to encrypt. The event log message is
  2327. // tailored for this specific scenario.
  2328. //
  2329. TraceAssert(!bIsEncrypted && bShouldBeEncrypted);
  2330. CscuiEventLog log;
  2331. log.ReportEvent(EVENTLOG_WARNING_TYPE, MSG_W_NO_ENCRYPT_VOLUME, 0);
  2332. }
  2333. }
  2334. }
  2335. else
  2336. {
  2337. STDBGOUT((1, TEXT("Encryption/decryption not allowed. Already in progress.")));
  2338. }
  2339. }
  2340. //
  2341. // Handles policy change in response to a WM_WININICHANGE
  2342. // message with lParam == "policy".
  2343. //
  2344. LRESULT OnPolicyChange(
  2345. void
  2346. )
  2347. {
  2348. ApplyCacheEncryptionPolicy();
  2349. ApplyCscSyncAtLogonAndLogoffPolicies();
  2350. ApplyAdminFolderPolicy();
  2351. return 0;
  2352. }
  2353. //
  2354. // Display the CSCUI status dialog. Invoked when user either
  2355. // left-clicks the systray icon or selects the "Show Status" option
  2356. // from the systray context menu.
  2357. //
  2358. void
  2359. ShowCSCUIStatusDlg(
  2360. HWND hwndParent
  2361. )
  2362. {
  2363. LPTSTR pszText = NULL;
  2364. const struct
  2365. {
  2366. eSysTrayState state; // SysTray UI state code.
  2367. UINT idsText; // Text for status dialog body.
  2368. } rgMap[] = {{ STS_OFFLINE, IDS_STATUSDLG_OFFLINE },
  2369. { STS_MOFFLINE, IDS_STATUSDLG_OFFLINE_M },
  2370. { STS_SERVERBACK, IDS_STATUSDLG_SERVERBACK },
  2371. { STS_MSERVERBACK, IDS_STATUSDLG_SERVERBACK_M },
  2372. { STS_DIRTY, IDS_STATUSDLG_DIRTY },
  2373. { STS_MDIRTY, IDS_STATUSDLG_DIRTY_M },
  2374. { STS_NONET, IDS_STATUSDLG_NONET }};
  2375. CSysTrayUI& stui = CSysTrayUI::GetInstance();
  2376. eSysTrayState state = stui.GetState();
  2377. for (int i = 0; i < ARRAYSIZE(rgMap); i++)
  2378. {
  2379. if (state == rgMap[i].state)
  2380. {
  2381. LoadStringAlloc(&pszText, g_hInstance, rgMap[i].idsText);
  2382. if (STS_DIRTY == state || STS_OFFLINE == state || STS_SERVERBACK == state)
  2383. {
  2384. LPCTSTR pszServerName = stui.GetServerName();
  2385. if (NULL != pszServerName && TEXT('\0') != *pszServerName)
  2386. {
  2387. //
  2388. // Current SysTray UI state has a single server associated
  2389. // with it. The message will have the name embedded in
  2390. // it in 2 places. Create a temp working buffer and
  2391. // re-create the original string with the server name
  2392. // embedded. If any of this fails, the string will just
  2393. // be displayed with the %1, %2 formatting characters rather
  2394. // than the server names. Not a fatal problem IMO.
  2395. //
  2396. LPTSTR pszTemp = NULL;
  2397. FormatString(&pszTemp, pszText, pszServerName, pszServerName);
  2398. if (NULL != pszTemp)
  2399. {
  2400. LocalFree(pszText);
  2401. pszText = pszTemp;
  2402. }
  2403. }
  2404. }
  2405. break; // Break out of loop. We have what we need.
  2406. }
  2407. }
  2408. if (NULL != pszText)
  2409. {
  2410. //
  2411. // Display the dialog.
  2412. //
  2413. CStatusDlg::Create(hwndParent, pszText, state);
  2414. LocalFree(pszText);
  2415. }
  2416. }
  2417. //
  2418. // PWM_RESET_REMINDERTIMER handler.
  2419. //
  2420. void
  2421. OnResetReminderTimer(
  2422. void
  2423. )
  2424. {
  2425. CSysTrayUI::GetInstance().ResetReminderTimer(true);
  2426. }
  2427. //
  2428. // Whenever we reboot, it's possible that the CSCUI cache has been
  2429. // reformatted or that the cache-size policy has been set/changed.
  2430. // When reformatted, the CSC agent uses the default size of 10%. We
  2431. // need to ensure that the size reflects system policy when policy
  2432. // is defined.
  2433. //
  2434. void
  2435. InitCacheSize(
  2436. void
  2437. )
  2438. {
  2439. bool bSetByPolicy = false;
  2440. DWORD dwPctX10000 = CConfig::GetSingleton().DefaultCacheSize(&bSetByPolicy);
  2441. if (bSetByPolicy)
  2442. {
  2443. ULARGE_INTEGER ulCacheSize;
  2444. CSCSPACEUSAGEINFO sui;
  2445. GetCscSpaceUsageInfo(&sui);
  2446. if (10000 < dwPctX10000)
  2447. {
  2448. //
  2449. // If value in registry is greater than 10000, it's
  2450. // invalid. Default to 10% of total disk space.
  2451. //
  2452. dwPctX10000 = 1000; // Default to 10% (0.10 * 10,000)
  2453. }
  2454. ulCacheSize.QuadPart = (sui.llBytesOnVolume * dwPctX10000) / 10000i64;
  2455. if (!CSCSetMaxSpace(ulCacheSize.HighPart, ulCacheSize.LowPart))
  2456. {
  2457. STDBGOUT((1, TEXT("Error %d setting cache size"), GetLastError()));
  2458. }
  2459. }
  2460. }
  2461. //
  2462. // Handler for CSCWM_SYNCHRONIZE. Called when user clicks "Synchronize"
  2463. // option on systray context menu. Also invoked when user selects the
  2464. // "Synchronize" button in a folder's web view pane.
  2465. //
  2466. HRESULT
  2467. OnSynchronize(
  2468. void
  2469. )
  2470. {
  2471. //
  2472. // This will create a status dialog hidden, invoke a synchronization of
  2473. // servers that would be "checked" in the dialog then close the dialog
  2474. // when the synchronization is complete.
  2475. //
  2476. CStatusDlg::Create(g_hWndNotification,
  2477. TEXT(""),
  2478. CSysTrayUI::GetInstance().GetState(),
  2479. CStatusDlg::MODE_AUTOSYNC);
  2480. return NOERROR;
  2481. }
  2482. LRESULT
  2483. OnQueryUIState(
  2484. void
  2485. )
  2486. {
  2487. return CSysTrayUI::GetInstance().GetState();
  2488. }
  2489. //
  2490. // When a user profile has been removed from the local machine,
  2491. // the delete-profile code in userenv.dll will write the user's SID
  2492. // as a text string in the following reg key:
  2493. //
  2494. // HKLM\Software\Microsoft\Windows\CurrentVersion\NetCache\PurgeAtNextLogoff
  2495. //
  2496. // Each SID is a value name under this key.
  2497. // At logoff, we enumerate all values under this key. For each SID we
  2498. // instantiate a CCachePurger object and delete all files cached for this
  2499. // user. Once the operation is complete, the "PurgeAtNextLogoff" key
  2500. // is deleted from the registry.
  2501. //
  2502. void
  2503. DeleteFilesCachedForObsoleteProfiles(
  2504. void
  2505. )
  2506. {
  2507. HKEY hkeyNetcache;
  2508. //
  2509. // Open the "HKLM\...\NetCache" key.
  2510. //
  2511. LONG lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  2512. c_szCSCKey,
  2513. 0,
  2514. KEY_READ,
  2515. &hkeyNetcache);
  2516. if (ERROR_SUCCESS == lResult)
  2517. {
  2518. HKEY hkey;
  2519. //
  2520. // Open the "PurgeAtNextLogoff" subkey.
  2521. //
  2522. lResult = RegOpenKeyEx(hkeyNetcache,
  2523. c_szPurgeAtNextLogoff,
  2524. 0,
  2525. KEY_READ,
  2526. &hkey);
  2527. if (ERROR_SUCCESS == lResult)
  2528. {
  2529. //
  2530. // Enumerate all the SID strings.
  2531. //
  2532. int iValue = 0;
  2533. TCHAR szValue[MAX_PATH];
  2534. DWORD cchValue = ARRAYSIZE(szValue);
  2535. while(ERROR_SUCCESS == SHEnumValue(hkey,
  2536. iValue,
  2537. szValue,
  2538. &cchValue,
  2539. NULL,
  2540. NULL,
  2541. NULL))
  2542. {
  2543. //
  2544. // Convert each SID string to a SID and delete
  2545. // all cached files accessed by this SID.
  2546. // Purge files ONLY if the SID is NOT for the current
  2547. // user. Here's the deal...
  2548. // While a user is NOT logged onto a system, their
  2549. // profile can be removed and their SID recorded in
  2550. // the PurgeAtNextLogoff key. The next time they log on they
  2551. // get new profile data. If they're the next person to
  2552. // logon following the removal of their profile, without
  2553. // this check, their new profile data would be purged during
  2554. // the subsequent logoff. That's bad. Therefore, we never
  2555. // purge data for the user who is logging off.
  2556. //
  2557. PSID psid;
  2558. if (ConvertStringSidToSid(szValue, &psid))
  2559. {
  2560. if (!IsSidCurrentUser(psid))
  2561. {
  2562. CCachePurgerSel sel;
  2563. sel.SetFlags(PURGE_FLAG_ALL);
  2564. if (sel.SetUserSid(psid))
  2565. {
  2566. CCachePurger purger(sel, NULL, NULL);
  2567. purger.Delete();
  2568. }
  2569. }
  2570. LocalFree(psid);
  2571. }
  2572. iValue++;
  2573. cchValue = ARRAYSIZE(szValue);
  2574. }
  2575. RegCloseKey(hkey);
  2576. RegDeleteKey(hkeyNetcache, c_szPurgeAtNextLogoff);
  2577. }
  2578. RegCloseKey(hkeyNetcache);
  2579. }
  2580. }
  2581. //
  2582. // This is called when the CSC hidden window is first created
  2583. // which occurs at logon. It's just a general bucket to group the
  2584. // things that need to happen each logon.
  2585. //
  2586. void
  2587. HandleLogonTasks(
  2588. void
  2589. )
  2590. {
  2591. InitCacheSize();
  2592. //
  2593. // Apply any necessary policies.
  2594. //
  2595. ApplyCacheEncryptionPolicy();
  2596. ApplyCscSyncAtLogonAndLogoffPolicies();
  2597. ApplyAdminFolderPolicy();
  2598. }
  2599. //
  2600. // This is called when the CSC Agent (running in the winlogon process)
  2601. // tells us to uninitialize the CSC UI. This happens when the user
  2602. // is logging off.
  2603. //
  2604. void
  2605. HandleLogoffTasks(
  2606. void
  2607. )
  2608. {
  2609. CConfig& config = CConfig::GetSingleton();
  2610. DeleteFilesCachedForObsoleteProfiles();
  2611. if (config.PurgeAtLogoff())
  2612. {
  2613. //
  2614. // If policy says to "purge all files cached by this user"
  2615. // delete offline-copy of all files cached by the current user.
  2616. // Respects access bits in files so that we don't delete something
  2617. // that is only used by some other user. This is the same
  2618. // behavior obtained via the "Delete Files..." button or the
  2619. // disk cleaner. Note that the UI callback ptr arg to the purger
  2620. // ctor is NULL and we don't run through a "scan" phase. This code
  2621. // is run while the user is logging off so we don't display any
  2622. // UI.
  2623. //
  2624. // Note that the policy can also indicate if this purge operation
  2625. // is for auto-cached files only.
  2626. //
  2627. DWORD dwPurgeFlags = PURGE_FLAG_UNPINNED;
  2628. if (!config.PurgeOnlyAutoCachedFilesAtLogoff())
  2629. {
  2630. dwPurgeFlags |= PURGE_FLAG_PINNED;
  2631. }
  2632. CCachePurgerSel sel;
  2633. sel.SetFlags(dwPurgeFlags);
  2634. CCachePurger purger(sel, NULL, NULL);
  2635. purger.Delete();
  2636. }
  2637. //
  2638. // IMPORTANT: We do any purging before registering for sync-at-logon/logoff.
  2639. // This is because we only register if we have something in
  2640. // the cache. Purging might remove all our cached items negating
  2641. // the need to register for synchronization.
  2642. //
  2643. ApplyCscSyncAtLogonAndLogoffPolicies();
  2644. //
  2645. // Is this the first time this user has used run CSCUI?
  2646. //
  2647. if (!IsSyncMgrInitialized())
  2648. {
  2649. CSCCACHESTATS cs;
  2650. CSCGETSTATSINFO si = { SSEF_NONE, SSUF_TOTAL, false, false };
  2651. if (_GetCacheStatisticsForUser(&si, &cs) && 0 < cs.cTotal)
  2652. {
  2653. //
  2654. // This is the first time this user has logged off with
  2655. // something in the cache. Since SyncMgr doesn't turn on sync-at-logon/logoff
  2656. // out of the box, we do it here. This is because we want people to sync
  2657. // if they have unknowingly cached files from an autocache share.
  2658. // If successful the SyncMgrInitialized reg value is set to 1.
  2659. //
  2660. RegisterSyncMgrHandler(TRUE);
  2661. const DWORD dwFlags = SYNCMGRREGISTERFLAG_CONNECT | SYNCMGRREGISTERFLAG_PENDINGDISCONNECT;
  2662. if (SUCCEEDED(RegisterForSyncAtLogonAndLogoff(dwFlags, dwFlags)))
  2663. {
  2664. SetSyncMgrInitialized();
  2665. }
  2666. }
  2667. }
  2668. }
  2669. //
  2670. // Determines the status of a share for controlling the display of the
  2671. // webview in a shell folder.
  2672. //
  2673. // Returns one of the following codes (defined in cscuiext.h):
  2674. //
  2675. // CSC_SHARESTATUS_INACTIVE
  2676. // CSC_SHARESTATUS_ONLINE
  2677. // CSC_SHARESTATUS_OFFLINE
  2678. // CSC_SHARESTATUS_SERVERBACK
  2679. // CSC_SHARESTATUS_DIRTYCACHE
  2680. //
  2681. LRESULT
  2682. GetShareStatusForWebView(
  2683. CStateMachine *pSM,
  2684. LPCTSTR pszShare
  2685. )
  2686. {
  2687. LRESULT lResult = CSC_SHARESTATUS_INACTIVE;
  2688. if (NULL != pszShare && IsCSCEnabled())
  2689. {
  2690. DWORD dwStatus;
  2691. if (CSCQueryFileStatus(pszShare, &dwStatus, NULL, NULL))
  2692. {
  2693. if ((dwStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) != FLAG_CSC_SHARE_STATUS_NO_CACHING)
  2694. {
  2695. const DWORD fExclude = SSEF_LOCAL_DELETED |
  2696. SSEF_DIRECTORY;
  2697. CSCSHARESTATS stats;
  2698. CSCGETSTATSINFO gsi = { fExclude, SSUF_MODIFIED, true, false };
  2699. lResult = CSC_SHARESTATUS_ONLINE;
  2700. if (_GetShareStatisticsForUser(pszShare, &gsi, &stats))
  2701. {
  2702. if (stats.bOffline)
  2703. {
  2704. if (IsServerBack(pSM, pszShare))
  2705. lResult = CSC_SHARESTATUS_SERVERBACK;
  2706. else
  2707. lResult = CSC_SHARESTATUS_OFFLINE;
  2708. }
  2709. else
  2710. {
  2711. if (0 < stats.cModified)
  2712. lResult = CSC_SHARESTATUS_DIRTYCACHE;
  2713. }
  2714. }
  2715. }
  2716. }
  2717. }
  2718. return lResult;
  2719. }
  2720. //-----------------------------------------------------------------------------
  2721. // Sync at Suspend/Hibernate.
  2722. //
  2723. // We synchronize the cache on a separate thread. Why use a separate
  2724. // thread?
  2725. //
  2726. // 1. We respond to WM_POWERBROADCAST.
  2727. //
  2728. // 2. WM_POWERBROADCAST is sent by win32k.sys using SendMessage.
  2729. //
  2730. // 3. As part of the sync we invoke SyncMgr which involves some
  2731. // COM operations. COM doesn't allow certain operations if they occur
  2732. // on a thread that is currently inside an interprocess SendMessage.
  2733. // This causes a call to CoCreateInstance inside mobsync.dll to fail
  2734. // with the error RPC_E_CANTCALLOUT_ININPUTSYNCCALL.
  2735. //
  2736. // 4. The solution is to place the synchronization (and COM) activity
  2737. // on a separate thread and to allow the thread servicing WM_POWERBROADCAST
  2738. // to process messages.
  2739. //
  2740. // When suspending, the main thread servicing WM_POWERBROADCAST blocks
  2741. // until the entire synchronization is complete. This is necessary to ensure
  2742. // the synchronization completes before the machine is shut down.
  2743. //
  2744. //
  2745. //
  2746. // The synchronization thread procedure for syncing on suspend/hibernate.
  2747. //
  2748. DWORD WINAPI
  2749. SuspendSync_ThreadProc(
  2750. LPVOID pvParam // DWORD_PTR holding CSC update flags.
  2751. )
  2752. {
  2753. TraceEnter(TRACE_CSCST, "SuspendSync_ThreadProc");
  2754. const DWORD dwFlags = PtrToUint(pvParam);
  2755. Trace((TEXT("Calling CscUpdateCache with flags 0x%08X"), dwFlags));
  2756. const HRESULT hr = CscUpdateCache(dwFlags);
  2757. TraceLeaveResult(hr);
  2758. }
  2759. //
  2760. // Waits on a single object while handling thread messages during the wait.
  2761. // Returns the result from MsgWaitForMultipleObjectsEx.
  2762. //
  2763. DWORD
  2764. WaitAndProcessThreadMessages(
  2765. HANDLE hObject // Handle for a Win32 synchronization object.
  2766. )
  2767. {
  2768. TraceEnter(TRACE_CSCST, "WaitAndProcessThreadMessages");
  2769. DWORD dwResult = WAIT_FAILED;
  2770. BOOL bQuit = FALSE;
  2771. while(!bQuit)
  2772. {
  2773. TraceMsg("Waiting for message or signaled object...");
  2774. dwResult = MsgWaitForMultipleObjectsEx(1,
  2775. &hObject,
  2776. INFINITE,
  2777. QS_ALLEVENTS,
  2778. MWMO_INPUTAVAILABLE);
  2779. //
  2780. // A message was received. Handle it.
  2781. //
  2782. if (WAIT_OBJECT_0 + 1 == dwResult)
  2783. {
  2784. MSG msg;
  2785. while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  2786. {
  2787. Trace((TEXT("Rcvd message %d"), msg.message));
  2788. if (WM_QUIT == msg.message)
  2789. {
  2790. bQuit = TRUE;
  2791. }
  2792. else
  2793. {
  2794. TranslateMessage(&msg);
  2795. DispatchMessage(&msg);
  2796. }
  2797. }
  2798. }
  2799. else
  2800. {
  2801. //
  2802. // Any other result ends the loop.
  2803. //
  2804. bQuit = TRUE;
  2805. if (WAIT_OBJECT_0 == dwResult)
  2806. {
  2807. TraceMsg("Object signaled");
  2808. }
  2809. else if (WAIT_FAILED == dwResult)
  2810. {
  2811. Trace((TEXT("Wait failed with error %d"), GetLastError()));
  2812. }
  2813. }
  2814. }
  2815. TraceLeaveValue(dwResult);
  2816. }
  2817. //
  2818. // Gets the sync action (quick vs. full) from user preference and/or
  2819. // system policy. If either there is no preference/policy defined
  2820. // or we found an invalid preference/policy value in the registry,
  2821. // we return eSyncNone as a default.
  2822. //
  2823. // Returns:
  2824. // CConfig::eSyncPartial - quick sync.
  2825. // CConfig::eSyncFull - full sync.
  2826. // CConfig::eSyncNone - invalid or missing reg info.
  2827. //
  2828. CConfig::SyncAction
  2829. GetSuspendSyncAction(
  2830. void
  2831. )
  2832. {
  2833. TraceEnter(TRACE_CSCST, "GetSuspendSyncAction");
  2834. CConfig::SyncAction action = CConfig::eSyncNone;
  2835. HRESULT hr = TS_MultipleSessions();
  2836. if (S_FALSE == hr)
  2837. {
  2838. action = (CConfig::SyncAction)CConfig::GetSingleton().SyncAtSuspend();
  2839. if (CConfig::eSyncPartial != action && CConfig::eSyncFull != action)
  2840. {
  2841. //
  2842. // Either someone poked an invalid value into the registry
  2843. // or there is no preference/policy registered for this parameter.
  2844. // Either way, we want to NOT sync.
  2845. //
  2846. action = CConfig::eSyncNone;
  2847. }
  2848. }
  2849. else if (S_OK == hr)
  2850. {
  2851. Trace((TEXT("Multiple sessions prevent synchronization.")));
  2852. }
  2853. Trace((TEXT("Action = %d"), int(action)));
  2854. TraceLeaveValue(action);
  2855. }
  2856. //
  2857. // Retrieves the set of flags to pass to CscUpdateCache configured
  2858. // for a given suspend operation.
  2859. //
  2860. // Returns:
  2861. // true - Ok to sync. CscUpdateCache flags are in *pdwFlags.
  2862. // false - Don't sync. Sync action is eSyndNone.
  2863. //
  2864. bool
  2865. IsSuspendSyncRequired(
  2866. bool bOkToPromptUser,
  2867. DWORD *pdwCscUpdateFlags // optional. Can be NULL.
  2868. )
  2869. {
  2870. TraceEnter(TRACE_CSCST, "IsSuspendSyncRequired");
  2871. DWORD dwFlags = 0;
  2872. const CConfig::SyncAction action = GetSuspendSyncAction();
  2873. if (bOkToPromptUser && CConfig::eSyncNone != action)
  2874. {
  2875. dwFlags = CSC_UPDATE_STARTNOW | CSC_UPDATE_FILL_QUICK;
  2876. if (CConfig::eSyncFull == action)
  2877. {
  2878. dwFlags |= (CSC_UPDATE_REINT | CSC_UPDATE_FILL_ALL);
  2879. }
  2880. Trace((TEXT("%s sync is required. CscUpdate flags = 0x%08X"),
  2881. CConfig::eSyncFull == action ? TEXT("FULL") : TEXT("QUICK"),
  2882. dwFlags));
  2883. }
  2884. else
  2885. {
  2886. TraceMsg("No sync is required");
  2887. }
  2888. if (NULL != pdwCscUpdateFlags)
  2889. {
  2890. *pdwCscUpdateFlags = dwFlags;
  2891. }
  2892. TraceLeaveValue(0 != dwFlags);
  2893. }
  2894. //
  2895. // This function creates the sync thread, and waits for the sync operation
  2896. // to complete if required. It returns the result returned by CscUpdateCache.
  2897. //
  2898. LRESULT
  2899. SyncOnSuspend(
  2900. DWORD dwCscUpdateFlags
  2901. )
  2902. {
  2903. TraceEnter(TRACE_CSCST, "SyncOnSuspend");
  2904. HRESULT hrSyncResult = E_FAIL;
  2905. //
  2906. // Run the synchronization on a separate thread.
  2907. // See the comment above SuspendSync_ThreadProc for details.
  2908. //
  2909. // Need to create the event object BEFORE we create the sync thread
  2910. // so that the object exists before the sync starts. Only if this
  2911. // named event object exists will the CCscUpdate code signal the
  2912. // event when the operation is complete.
  2913. //
  2914. HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, c_szSyncCompleteEvent);
  2915. if (NULL != hEvent)
  2916. {
  2917. HANDLE hThread = CreateThread(NULL,
  2918. 0,
  2919. SuspendSync_ThreadProc,
  2920. UIntToPtr(dwCscUpdateFlags),
  2921. 0,
  2922. NULL);
  2923. if (NULL != hThread)
  2924. {
  2925. //
  2926. // Wait for the sync thread to complete. This just means the call
  2927. // to CscUpdateCache has completed. We need to wait so we can
  2928. // retrieve the result from CscUpdateCache through the thread's
  2929. // exit code.
  2930. // SyncMgr will continue the sync from the mobsync.exe process
  2931. // after the thread has terminated.
  2932. //
  2933. TraceMsg("Waiting for CscUpdateCache to complete...");
  2934. WaitAndProcessThreadMessages(hThread);
  2935. //
  2936. // The thread's exit code is the HRESULT returned by CscUpdateCache.
  2937. //
  2938. DWORD dwThreadExitCode = (DWORD)E_FAIL;
  2939. GetExitCodeThread(hThread, &dwThreadExitCode);
  2940. hrSyncResult = dwThreadExitCode;
  2941. //
  2942. // We're done with the thread object.
  2943. //
  2944. CloseHandle(hThread);
  2945. hThread = NULL;
  2946. if (SUCCEEDED(hrSyncResult))
  2947. {
  2948. //
  2949. // The sync was successfully started and we're syncing prior to a
  2950. // suspend operation. Need to wait 'til the sync is complete so that
  2951. // we block the return to WM_POWERBROADCAST (PBT_APMQUERYSUSPEND).
  2952. //
  2953. TraceMsg("Waiting for sync (mobsync.exe) to complete...");
  2954. WaitAndProcessThreadMessages(hEvent);
  2955. }
  2956. }
  2957. else
  2958. {
  2959. const DWORD dwErr = GetLastError();
  2960. hrSyncResult = HRESULT_FROM_WIN32(dwErr);
  2961. Trace((TEXT("Sync thread creation failed with error %d"), dwErr));
  2962. }
  2963. CloseHandle(hEvent);
  2964. hEvent = NULL;
  2965. }
  2966. else
  2967. {
  2968. const DWORD dwErr = GetLastError();
  2969. hrSyncResult = HRESULT_FROM_WIN32(dwErr);
  2970. Trace((TEXT("Sync event creation failed with error %d"), dwErr));
  2971. }
  2972. if (FAILED(hrSyncResult))
  2973. {
  2974. CscuiEventLog log;
  2975. log.Push(hrSyncResult, CEventLog::eFmtHex);
  2976. log.ReportEvent(EVENTLOG_ERROR_TYPE,
  2977. MSG_E_SUSPEND_SYNCFAILED, 0);
  2978. }
  2979. TraceLeaveResult(hrSyncResult);
  2980. }
  2981. //
  2982. // Handles synchronization on suspend/hibernate.
  2983. // Note that we do not support sync on resume. We've
  2984. // determined that the behavior is not compelling. It is
  2985. // better to resume and let our normal UI processing
  2986. // handle any network reconnections in the normal way.
  2987. //
  2988. LRESULT
  2989. HandleSuspendSync(
  2990. CStateMachine *pSysTraySM,
  2991. HWND hWnd,
  2992. bool bOkToPromptUser
  2993. )
  2994. {
  2995. TraceEnter(TRACE_CSCST, "HandleSuspendSync");
  2996. Trace((TEXT("\tbOkToPromptUser = %d"), bOkToPromptUser));
  2997. LRESULT lResult = ERROR_SUCCESS;
  2998. BOOL bNoNet = FALSE;
  2999. CSCIsServerOffline(NULL, &bNoNet);
  3000. if (bNoNet)
  3001. {
  3002. TraceMsg("No sync performed. Network not available.");
  3003. CscuiEventLog log;
  3004. log.ReportEvent(EVENTLOG_INFORMATION_TYPE,
  3005. MSG_I_SUSPEND_NONET_NOSYNC, 2);
  3006. }
  3007. else
  3008. {
  3009. //
  3010. // Determine if we're supposed to sync or not.
  3011. // If so, we get the flags to pass to CscUpdateCache that control the
  3012. // sync behavior.
  3013. //
  3014. DWORD dwFlags = 0;
  3015. if (IsSuspendSyncRequired(bOkToPromptUser, &dwFlags))
  3016. {
  3017. lResult = SyncOnSuspend(dwFlags);
  3018. }
  3019. }
  3020. TraceLeaveValue(lResult);
  3021. }
  3022. //
  3023. // Handle any tasks that occur when the computer hibernates or is suspended.
  3024. //
  3025. LRESULT
  3026. HandleSuspendTasks(
  3027. CStateMachine *pSysTraySM,
  3028. HWND hWnd,
  3029. bool bOkToPromptUser
  3030. )
  3031. {
  3032. return HandleSuspendSync(pSysTraySM, hWnd, bOkToPromptUser);
  3033. }
  3034. #ifdef DEBUG
  3035. //
  3036. // Returns address of string corresponding to a PBT_XXXXXXX code
  3037. // sent in a WM_POWERBROADCAST message.
  3038. // Used for debug output only.
  3039. //
  3040. LPCTSTR ApmCodeName(WPARAM code)
  3041. {
  3042. static const TCHAR szUnknown[] = TEXT("<unknown PBT code>");
  3043. static const struct
  3044. {
  3045. WPARAM code;
  3046. LPCTSTR pszName;
  3047. } rgMap[] = {
  3048. { PBT_APMBATTERYLOW, TEXT("PBT_APMBATTERYLOW") },
  3049. { PBT_APMOEMEVENT, TEXT("PBT_APMOEMEVENT") },
  3050. { PBT_APMPOWERSTATUSCHANGE, TEXT("PBT_APMPOWERSTATUSCHANGE") },
  3051. { PBT_APMQUERYSUSPEND, TEXT("PBT_APMQUERYSUSPEND") },
  3052. { PBT_APMQUERYSUSPENDFAILED, TEXT("PBT_APMQUERYSUSPENDFAILED") },
  3053. { PBT_APMRESUMEAUTOMATIC, TEXT("PBT_APMRESUMEAUTOMATIC") },
  3054. { PBT_APMRESUMECRITICAL, TEXT("PBT_APMRESUMECRITICAL") },
  3055. { PBT_APMRESUMESUSPEND, TEXT("PBT_APMRESUMESUSPEND") },
  3056. { PBT_APMSUSPEND, TEXT("PBT_APMSUSPEND") }
  3057. };
  3058. for (int i = 0; i < ARRAYSIZE(rgMap); i++)
  3059. {
  3060. if (rgMap[i].code == code)
  3061. {
  3062. return rgMap[i].pszName;
  3063. }
  3064. }
  3065. return szUnknown;
  3066. }
  3067. #endif
  3068. //
  3069. // Handle WM_POWERBROADCAST message.
  3070. // We handle this message so that we can synchronize when the computer
  3071. // hibernates/suspends.
  3072. //
  3073. LRESULT
  3074. OnPowerBroadcast(
  3075. CStateMachine *pSysTraySM,
  3076. HWND hWnd,
  3077. WPARAM wParam,
  3078. LPARAM lParam
  3079. )
  3080. {
  3081. Trace((TEXT("OnPowerBroadcast %s (%d), lParam = 0x%08X"), ApmCodeName(wParam), wParam, lParam));
  3082. LRESULT lResult = TRUE;
  3083. switch(wParam)
  3084. {
  3085. case PBT_APMQUERYSUSPEND: // Ok to suspend/hibernate?
  3086. {
  3087. const bool bOkToPromptUser = (0 != (1 & lParam));
  3088. HandleSuspendTasks(pSysTraySM, hWnd, bOkToPromptUser);
  3089. //
  3090. // Note that we never return BROADCAST_QUERY_DENY.
  3091. // Therefore, we always approve the suspend.
  3092. //
  3093. }
  3094. break;
  3095. //
  3096. // The remaining PBT_APMXXXXX codes are included here to show that
  3097. // all were considered and explicitly not handled.
  3098. //
  3099. case PBT_APMRESUMESUSPEND: // Resuming from a previous suspend/hibernate..
  3100. case PBT_APMBATTERYLOW: // Battery getting low.
  3101. case PBT_APMOEMEVENT: // Special OEM events.
  3102. case PBT_APMPOWERSTATUSCHANGE: // Power switched (i.e. from AC -> battery)
  3103. case PBT_APMQUERYSUSPENDFAILED: // Some process denied a suspend request.
  3104. case PBT_APMRESUMEAUTOMATIC: // Resuming. Likely no user available.
  3105. case PBT_APMRESUMECRITICAL: // Resuming from critical event (i.e. low battery).
  3106. case PBT_APMSUSPEND: // System is suspending now.
  3107. default:
  3108. break;
  3109. }
  3110. return lResult;
  3111. }
  3112. //
  3113. // This device-change code is an experiment to see what
  3114. // WM_DEVICECHANGE activity we can receive while docking and
  3115. // undocking a portable machine. If we decide to not use
  3116. // any of this, just delete it. Note there are several
  3117. // sections of code that use this conditional compilation.
  3118. // [brianau - 12/23/98]
  3119. //
  3120. #ifdef REPORT_DEVICE_CHANGES
  3121. DWORD
  3122. RegisterForDeviceNotifications(
  3123. HWND hwndNotify
  3124. )
  3125. {
  3126. DWORD dwResult = ERROR_SUCCESS;
  3127. DEV_BROADCAST_DEVICEINTERFACE dbdi;
  3128. ZeroMemory(&dbdi, sizeof(dbdi));
  3129. dbdi.dbcc_size = sizeof(dbdi);
  3130. dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
  3131. dbdi.dbcc_classguid = GUID_DEVNODE_CHANGE;
  3132. g_hDevNotify = RegisterDeviceNotification(hwndNotify, &dbdi, DEVICE_NOTIFY_WINDOW_HANDLE);
  3133. if (NULL == g_hDevNotify)
  3134. dwResult = GetLastError();
  3135. return dwResult;
  3136. }
  3137. void
  3138. UnregisterForDeviceNotifications(
  3139. void
  3140. )
  3141. {
  3142. if (NULL != g_hDevNotify)
  3143. {
  3144. UnregisterDeviceNotification(g_hDevNotify);
  3145. g_hDevNotify = NULL;
  3146. }
  3147. }
  3148. void
  3149. OnDeviceChange(
  3150. WPARAM wParam,
  3151. LPARAM lParam
  3152. )
  3153. {
  3154. PDEV_BROADCAST_DEVICEINTERFACE pdbdi = (PDEV_BROADCAST_DEVICEINTERFACE)lParam;
  3155. TCHAR szNull[] = TEXT("<null>");
  3156. LPCTSTR pszName = pdbdi ? pdbdi->dbcc_name : szNull;
  3157. switch(wParam)
  3158. {
  3159. case DBT_DEVICEARRIVAL:
  3160. STDBGOUT((3, TEXT("Device Arrival for : \"%s\""), pszName));
  3161. break;
  3162. case DBT_DEVICEREMOVEPENDING:
  3163. STDBGOUT((3, TEXT("Device Remove pending for \"%s\""), pszName));
  3164. break;
  3165. case DBT_DEVICEREMOVECOMPLETE:
  3166. STDBGOUT((3, TEXT("Device Removal complete for \"%s\""), pszName));
  3167. break;
  3168. case DBT_DEVICEQUERYREMOVE:
  3169. STDBGOUT((3, TEXT("Device query remove for \"%s\""), pszName));
  3170. break;
  3171. case DBT_DEVICEQUERYREMOVEFAILED:
  3172. STDBGOUT((3, TEXT("Device query remove FAILED for \"%s\""), pszName));
  3173. break;
  3174. case DBT_DEVICETYPESPECIFIC:
  3175. STDBGOUT((3, TEXT("Device type specific for \"%s\""), pszName));
  3176. break;
  3177. case DBT_QUERYCHANGECONFIG:
  3178. STDBGOUT((3, TEXT("Query change config for \"%s\""), pszName));
  3179. break;
  3180. case DBT_CONFIGCHANGED:
  3181. STDBGOUT((3, TEXT("Config changed for \"%s\""), pszName));
  3182. break;
  3183. default:
  3184. STDBGOUT((3, TEXT("Unknown device notification %d"), wParam));
  3185. break;
  3186. }
  3187. }
  3188. #endif // REPORT_DEVICE_CHANGES
  3189. LRESULT CALLBACK _HiddenWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  3190. {
  3191. LRESULT lResult = 0;
  3192. CStateMachine *pSysTraySM = (CStateMachine*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
  3193. TraceEnter(TRACE_CSCST, "_HiddenWndProc");
  3194. switch(uMsg)
  3195. {
  3196. case WM_CREATE:
  3197. DllAddRef();
  3198. #if DBG
  3199. CreateWindow(TEXT("listbox"),
  3200. NULL,
  3201. WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL |
  3202. LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT,
  3203. 0,0,0,0,
  3204. hWnd,
  3205. (HMENU)IDC_DEBUG_LIST,
  3206. g_hInstance,
  3207. NULL);
  3208. #endif
  3209. #ifdef REPORT_DEVICE_CHANGES
  3210. RegisterForDeviceNotifications(hWnd);
  3211. #endif
  3212. {
  3213. BOOL bNoNet = FALSE;
  3214. // Check whether the entire net is offline or not
  3215. if (!CSCIsServerOffline(NULL, &bNoNet))
  3216. bNoNet = TRUE; // RDR is dead, so net is down
  3217. pSysTraySM = new CStateMachine(boolify(bNoNet));
  3218. if (!pSysTraySM)
  3219. TraceLeaveValue((LRESULT)-1);
  3220. SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pSysTraySM);
  3221. if (bNoNet)
  3222. {
  3223. // Set initial status to NoNet
  3224. PostMessage(hWnd, CSCWM_UPDATESTATUS, STWM_CSCNETDOWN, 0);
  3225. }
  3226. else
  3227. {
  3228. //
  3229. // Calculate initial status as if the logon sync has just
  3230. // completed.
  3231. //
  3232. // There's a race condition, so we can't count on getting
  3233. // this message from the logon sync. If logon sync is still
  3234. // proceeding, we will get another CSCWM_DONESYNCING which
  3235. // is OK.
  3236. //
  3237. PostMessage(hWnd, CSCWM_DONESYNCING, 0, 0);
  3238. }
  3239. }
  3240. //
  3241. // Handle several things that happen at logon.
  3242. //
  3243. PostMessage(hWnd, PWM_HANDLE_LOGON_TASKS, 0, 0);
  3244. //
  3245. // This event is used to terminate any threads when the
  3246. // hidden notification window is destroyed.
  3247. //
  3248. // If CreateEvent fails, the cache encryption thread will still run
  3249. // (non-interruptable), but the admin-pin thread won't run at all.
  3250. //
  3251. if (NULL == g_heventTerminate)
  3252. g_heventTerminate = CreateEvent(NULL, TRUE, FALSE, NULL);
  3253. //
  3254. // This mutex is used to ensure only one admin-pin operation
  3255. // is running at a time.
  3256. //
  3257. // If CreateMutex fails, the admin-pin code won't run.
  3258. //
  3259. if (NULL == g_hmutexAdminPin)
  3260. g_hmutexAdminPin = CreateMutex(NULL, FALSE, NULL);
  3261. break;
  3262. case PWM_TRAYCALLBACK:
  3263. STDBGOUT((4, TEXT("PWM_TRAYCALLBACK, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
  3264. lResult = _Notify(hWnd, wParam, lParam);
  3265. break;
  3266. #ifdef REPORT_DEVICE_CHANGES
  3267. case WM_DEVICECHANGE:
  3268. OnDeviceChange(wParam, lParam);
  3269. break;
  3270. #endif // REPORT_DEVICE_CHANGES
  3271. case WM_ENDSESSION:
  3272. TraceMsg("_HiddenWndProc: Received WM_ENDSESSION.");
  3273. if (NULL != g_heventTerminate)
  3274. {
  3275. //
  3276. // This will tell any threads that they should
  3277. // exit asap.
  3278. //
  3279. SetEvent(g_heventTerminate);
  3280. }
  3281. break;
  3282. case WM_DESTROY:
  3283. TraceMsg("_HiddenWndProc: hidden window destroyed");
  3284. #ifdef REPORT_DEVICE_CHANGES
  3285. UnregisterForDeviceNotifications();
  3286. #endif
  3287. delete pSysTraySM;
  3288. pSysTraySM = NULL;
  3289. SetWindowLongPtr(hWnd, GWLP_USERDATA, 0);
  3290. if (NULL != g_heventTerminate)
  3291. {
  3292. //
  3293. // This will tell any threads that they should
  3294. // exit asap.
  3295. //
  3296. SetEvent(g_heventTerminate);
  3297. }
  3298. DllRelease();
  3299. break;
  3300. case WM_COPYDATA:
  3301. {
  3302. // Warning: STDBGOUT here (inside WM_COPYDATA, outside the switch
  3303. // statement) would cause an infinite loop of WM_COPYDATA messages
  3304. // and blow the stack.
  3305. PCOPYDATASTRUCT pcds = (PCOPYDATASTRUCT)lParam;
  3306. if (pcds)
  3307. {
  3308. switch (pcds->dwData)
  3309. {
  3310. case STWM_CSCNETUP:
  3311. case STWM_CSCNETDOWN:
  3312. case STWM_CACHE_CORRUPTED:
  3313. {
  3314. LPTSTR pszServer = NULL;
  3315. //
  3316. // WM_COPYDATA is always sent, not posted, so copy the data
  3317. // and post a message to do the work asynchronously.
  3318. // This allocated string will be freed by the CSCWM_UPDATESTATUS
  3319. // handler UpdateStatus(). No need to free it here.
  3320. //
  3321. STDBGOUT((3, TEXT("Rcvd WM_COPYDATA, uMsg = 0x%08X, server = \"%s\""), pcds->dwData, pcds->lpData));
  3322. LocalAllocString(&pszServer, (LPTSTR)pcds->lpData);
  3323. PostMessage(hWnd, CSCWM_UPDATESTATUS, pcds->dwData, (LPARAM)pszServer);
  3324. }
  3325. break;
  3326. case CSCWM_GETSHARESTATUS:
  3327. // This one comes from outside of cscui.dll, and
  3328. // is always UNICODE.
  3329. if (pcds->lpData)
  3330. {
  3331. STDBGOUT((3, TEXT("Rcvd CSCWM_GETSHARESTATUS for \"%s\""), (LPWSTR)pcds->lpData));
  3332. lResult = GetShareStatusForWebView(pSysTraySM, (LPWSTR)pcds->lpData);
  3333. }
  3334. break;
  3335. case PWM_REFRESH_SHELL:
  3336. STDBGOUT((3, TEXT("Rcvd WM_COPYDATA, PWM_REFRESH_SHELL, server = \"%s\""), pcds->lpData));
  3337. if (pcds->lpData)
  3338. {
  3339. LPTSTR pszServer = NULL;
  3340. LocalAllocString(&pszServer, (LPTSTR)pcds->lpData);
  3341. PostMessage(hWnd, PWM_REFRESH_SHELL, 0, (LPARAM)pszServer);
  3342. }
  3343. break;
  3344. #if DBG
  3345. //
  3346. // The following messages in the "#if DBG" block are to support the
  3347. // monitoring feature of the hidden systray window.
  3348. //
  3349. case PWM_STDBGOUT:
  3350. // Warning: no STDBGOUT here
  3351. STDebugOnLogEvent(GetDlgItem(hWnd, IDC_DEBUG_LIST), (LPCTSTR)pcds->lpData);
  3352. break;
  3353. #endif // DBG
  3354. }
  3355. }
  3356. }
  3357. break;
  3358. case CSCWM_ISSERVERBACK:
  3359. STDBGOUT((2, TEXT("Rcvd CSCWM_ISSERVERBACK")));
  3360. lResult = IsServerBack(pSysTraySM, (LPCTSTR)lParam);
  3361. break;
  3362. case CSCWM_DONESYNCING:
  3363. STDBGOUT((1, TEXT("Rcvd CSCWM_DONESYNCING. wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
  3364. pSysTraySM->PingServers();
  3365. UpdateStatus(pSysTraySM, hWnd, STWM_STATUSCHECK, NULL);
  3366. break;
  3367. case CSCWM_UPDATESTATUS:
  3368. UpdateStatus(pSysTraySM, hWnd, (UINT)wParam, (LPTSTR)lParam);
  3369. if (lParam)
  3370. LocalFree((LPTSTR)lParam); // We make a copy when we get WM_COPYDATA
  3371. break;
  3372. case PWM_RESET_REMINDERTIMER:
  3373. STDBGOUT((2, TEXT("Rcvd PWM_RESET_REMINDERTIMER")));
  3374. OnResetReminderTimer();
  3375. break;
  3376. case PWM_HANDLE_LOGON_TASKS:
  3377. HandleLogonTasks();
  3378. break;
  3379. case PWM_REFRESH_SHELL:
  3380. STDBGOUT((3, TEXT("Rcvd PWM_REFRESH_SHELL, server = \"%s\""), (LPCTSTR)lParam));
  3381. _RefreshAllExplorerWindows((LPCTSTR)lParam);
  3382. //
  3383. // lParam is a server name allocated with LocalAlloc.
  3384. //
  3385. if (lParam)
  3386. LocalFree((LPTSTR)lParam);
  3387. break;
  3388. case CSCWM_VIEWFILES:
  3389. COfflineFilesFolder::Open();
  3390. break;
  3391. case PWM_STATUSDLG:
  3392. ShowCSCUIStatusDlg(hWnd);
  3393. break;
  3394. case PWM_QUERY_UISTATE:
  3395. lResult = OnQueryUIState();
  3396. break;
  3397. case CSCWM_SYNCHRONIZE:
  3398. STDBGOUT((1, TEXT("Rcvd CSCWM_SYNCHRONIZE")));
  3399. OnSynchronize();
  3400. break;
  3401. case CSCWM_SETTINGS:
  3402. COfflineFilesSheet::CreateAndRun(g_hInstance, GetDesktopWindow(), &g_cRefCount);
  3403. break;
  3404. case WM_WININICHANGE:
  3405. STDBGOUT((1, TEXT("Rcvd WM_WININICHANGE. wParam = %d, lParam = \"%s\""),
  3406. wParam, lParam ? (LPCTSTR)lParam : TEXT("<null>")));
  3407. //
  3408. // Let the tray UI thread respond to a possible change in caret
  3409. // blink rate.
  3410. //
  3411. CSysTrayUI::GetInstance().OnWinIniChange((LPCTSTR)lParam);
  3412. if (!lstrcmpi((LPTSTR)lParam, c_szPolicy))
  3413. {
  3414. //
  3415. // Post a message to ourselves so we get the policy processing off
  3416. // of the calling thread. Otherwise COM will fail because this
  3417. // message is SENT by userenv as an inter-process SendMessage.
  3418. //
  3419. PostMessage(hWnd, PWM_HANDLE_POLICY_CHANGE, 0, 0);
  3420. }
  3421. break;
  3422. case PWM_HANDLE_POLICY_CHANGE:
  3423. OnPolicyChange();
  3424. break;
  3425. case WM_POWERBROADCAST:
  3426. lResult = OnPowerBroadcast(pSysTraySM, hWnd, wParam, lParam);
  3427. break;
  3428. #if DBG
  3429. //
  3430. // The following messages in the "#if DBG" block are to support the
  3431. // monitoring feature of the hidden systray window.
  3432. //
  3433. case WM_GETDLGCODE:
  3434. lResult = DLGC_WANTALLKEYS;
  3435. break;
  3436. case WM_VKEYTOITEM:
  3437. wParam = LOWORD(wParam); // Extract the virtual key code.
  3438. //
  3439. // Fall through.
  3440. //
  3441. case WM_KEYDOWN:
  3442. if (0x8000 & GetAsyncKeyState(VK_CONTROL))
  3443. {
  3444. if (TEXT('S') == wParam || TEXT('s') == wParam)
  3445. {
  3446. //
  3447. // Ctrl-S saves the contents to a file.
  3448. //
  3449. STDebugSaveListboxContent(hWnd);
  3450. }
  3451. else if (TEXT('U') == wParam || TEXT('u') == wParam)
  3452. {
  3453. //
  3454. // Ctrl-U forces an update to match the current cache state.
  3455. //
  3456. UpdateStatus(pSysTraySM, hWnd, STWM_STATUSCHECK, NULL);
  3457. }
  3458. else if (TEXT('B') == wParam || TEXT('b') == wParam)
  3459. {
  3460. //
  3461. // Ctrl-B pings offline servers to see if they are back.
  3462. //
  3463. pSysTraySM->PingServers();
  3464. }
  3465. else if (TEXT('P') == wParam || TEXT('p') == wParam)
  3466. {
  3467. //
  3468. // Ctrl-P triggers the policy code
  3469. //
  3470. PostMessage(hWnd, PWM_HANDLE_POLICY_CHANGE, 0, 0);
  3471. }
  3472. }
  3473. else if (VK_DELETE == wParam)
  3474. {
  3475. //
  3476. // [Delete] clears the contents of the listbox.
  3477. //
  3478. if (IDOK == MessageBox(hWnd,
  3479. TEXT("Clear the list?"),
  3480. STR_CSCHIDDENWND_TITLE,
  3481. MB_OKCANCEL))
  3482. {
  3483. SendDlgItemMessage(hWnd, IDC_DEBUG_LIST, LB_RESETCONTENT, 0, 0);
  3484. }
  3485. }
  3486. lResult = (WM_VKEYTOITEM == uMsg) ? -1 : 0;
  3487. break;
  3488. case WM_SIZE:
  3489. {
  3490. RECT rc;
  3491. GetClientRect(hWnd, &rc);
  3492. SetWindowPos(GetDlgItem(hWnd, IDC_DEBUG_LIST),
  3493. NULL,
  3494. rc.left,
  3495. rc.top,
  3496. rc.right - rc.left,
  3497. rc.bottom - rc.top,
  3498. SWP_NOZORDER);
  3499. }
  3500. break;
  3501. #endif // DBG
  3502. default:
  3503. lResult = DefWindowProc(hWnd, uMsg, wParam, lParam);
  3504. break;
  3505. }
  3506. TraceLeaveValue(lResult);
  3507. }
  3508. HWND _CreateHiddenWnd(void)
  3509. {
  3510. WNDCLASS wc;
  3511. HWND hwnd;
  3512. DWORD dwStyle = WS_OVERLAPPED;
  3513. TraceEnter(TRACE_CSCST, "_CreateHiddenWnd");
  3514. GetClassInfo(NULL, WC_DIALOG, &wc);
  3515. wc.style |= CS_NOCLOSE;
  3516. wc.lpfnWndProc = _HiddenWndProc;
  3517. wc.hInstance = g_hInstance;
  3518. wc.lpszClassName = STR_CSCHIDDENWND_CLASSNAME;
  3519. RegisterClass(&wc);
  3520. #if DBG
  3521. if (0 < STDebugLevel())
  3522. {
  3523. // This includes WS_CAPTION, which turns on theming, so we
  3524. // only want this when the window is visible.
  3525. dwStyle = WS_OVERLAPPEDWINDOW;
  3526. }
  3527. #endif // DBG
  3528. //
  3529. // Note that we can't use HWND_MESSAGE as the parent since we need
  3530. // to receive certain broadcast messages.
  3531. //
  3532. hwnd = CreateWindow(STR_CSCHIDDENWND_CLASSNAME,
  3533. NULL,
  3534. dwStyle,
  3535. CW_USEDEFAULT, CW_USEDEFAULT,
  3536. CW_USEDEFAULT, CW_USEDEFAULT,
  3537. NULL,
  3538. NULL,
  3539. g_hInstance,
  3540. NULL);
  3541. if (hwnd)
  3542. {
  3543. #if DBG
  3544. //
  3545. // In debug builds, if registry is set up to display
  3546. // systray debug output, create the CSCUI "hidden" window
  3547. // as visible.
  3548. //
  3549. if (0 < STDebugLevel())
  3550. {
  3551. ShowWindow(hwnd, SW_NORMAL);
  3552. UpdateWindow(hwnd);
  3553. }
  3554. #endif // DBG
  3555. }
  3556. else
  3557. {
  3558. Trace((TEXT("CSCSysTrayThreadProc: CreateWindow failed GLE: %Xh"), GetLastError()));
  3559. }
  3560. TraceLeaveValue(hwnd);
  3561. }
  3562. HWND _FindNotificationWindow()
  3563. {
  3564. g_hWndNotification = FindWindow(STR_CSCHIDDENWND_CLASSNAME, NULL);
  3565. return g_hWndNotification;
  3566. }
  3567. BOOL _CheckNotificationWindow()
  3568. {
  3569. SetLastError(ERROR_SUCCESS);
  3570. if (!IsWindow(g_hWndNotification))
  3571. {
  3572. // Search for the window and try again
  3573. _FindNotificationWindow();
  3574. if (!IsWindow(g_hWndNotification))
  3575. {
  3576. SetLastError(ERROR_INVALID_WINDOW_HANDLE);
  3577. return FALSE;
  3578. }
  3579. }
  3580. return TRUE;
  3581. }
  3582. BOOL
  3583. PostToSystray(
  3584. UINT uMsg,
  3585. WPARAM wParam,
  3586. LPARAM lParam
  3587. )
  3588. {
  3589. if (_CheckNotificationWindow())
  3590. {
  3591. return PostMessage(g_hWndNotification, uMsg, wParam, lParam);
  3592. }
  3593. return FALSE;
  3594. }
  3595. #define SYSTRAY_MSG_TIMEOUT 10000
  3596. LRESULT
  3597. SendToSystray(
  3598. UINT uMsg,
  3599. WPARAM wParam,
  3600. LPARAM lParam
  3601. )
  3602. {
  3603. DWORD_PTR dwResult = 0;
  3604. if (_CheckNotificationWindow())
  3605. {
  3606. SendMessageTimeout(g_hWndNotification,
  3607. uMsg,
  3608. wParam,
  3609. lParam,
  3610. SMTO_ABORTIFHUNG,
  3611. SYSTRAY_MSG_TIMEOUT,
  3612. &dwResult);
  3613. }
  3614. return dwResult;
  3615. }
  3616. LRESULT SendCopyDataToSystray(DWORD dwData, DWORD cbData, PVOID pData)
  3617. {
  3618. COPYDATASTRUCT cds;
  3619. cds.dwData = dwData;
  3620. cds.cbData = cbData;
  3621. cds.lpData = pData;
  3622. return SendToSystray(WM_COPYDATA, 0, (LPARAM)&cds);
  3623. }
  3624. STDAPI_(HWND) CSCUIInitialize(HANDLE hToken, DWORD dwFlags)
  3625. {
  3626. TraceEnter(TRACE_CSCST, "CSCUIInitialize");
  3627. _FindNotificationWindow();
  3628. //
  3629. // We get initialization and shutdown messages from cscdll and also
  3630. // from the systray code in stobject.dll.
  3631. //
  3632. // Cscdll calls from within the winlogon process, at logon and logoff.
  3633. // At logon, cscdll provides us with the user's token, which we duplicate
  3634. // for accessing HKEY_CURRENT_USER (in OnQueryNetDown and at logoff).
  3635. //
  3636. // The systray code calls from within the explorer process, at explorer
  3637. // load and unload (usually just after logon and just before logoff).
  3638. // This is where we create and destroy our hidden window.
  3639. //
  3640. // Note: At one time we kept certain registry keys open (cached). This
  3641. // caused problems at logoff since the profile couldn't be saved until
  3642. // all reg keys were closed. Think carefully before deciding to hold
  3643. // any reg keys open.
  3644. //
  3645. if (dwFlags & CI_INITIALIZE)
  3646. {
  3647. if (hToken)
  3648. {
  3649. DuplicateToken(hToken, SecurityImpersonation, &g_hToken);
  3650. Trace((TEXT("CSCUIInitialize: Using new token handle:%Xh"), g_hToken));
  3651. }
  3652. if (dwFlags & CI_CREATEWINDOW)
  3653. {
  3654. BOOL bCSCEnabled = IsCSCEnabled();
  3655. //
  3656. // The CI_CREATEWINDOW bit is set by systray/explorer
  3657. //
  3658. if (!bCSCEnabled || CConfig::GetSingleton().NoCacheViewer())
  3659. {
  3660. //
  3661. // If CSC is currently disabled, or system policy prevents the
  3662. // user from viewing the cache contents, remove the Offline Files
  3663. // folder shortcut from the user's desktop.
  3664. //
  3665. DeleteOfflineFilesFolderLink_PerfSensitive();
  3666. }
  3667. if (g_hWndNotification)
  3668. {
  3669. Trace((TEXT("CSCUIInitialize: returning existing hWnd:%Xh"), g_hWndNotification));
  3670. }
  3671. else if (!bCSCEnabled)
  3672. {
  3673. ExitGracefully(g_hWndNotification, NULL, "CSCUIInitialize: CSC not enabled");
  3674. }
  3675. else
  3676. {
  3677. g_hWndNotification = _CreateHiddenWnd();
  3678. Trace((TEXT("CSCUIInitialize: Created new hWnd:%Xh"), g_hWndNotification));
  3679. }
  3680. }
  3681. }
  3682. else if (dwFlags & CI_TERMINATE)
  3683. {
  3684. if (dwFlags & CI_DESTROYWINDOW)
  3685. {
  3686. //
  3687. // The CI_DESTROYWINDOW bit is set by systray.exe
  3688. //
  3689. if (g_hWndNotification)
  3690. {
  3691. TraceMsg("CSCUIInitialize: Destroying hidden window");
  3692. DestroyWindow(g_hWndNotification);
  3693. g_hWndNotification = NULL;
  3694. }
  3695. UnregisterClass(STR_CSCHIDDENWND_CLASSNAME, g_hInstance);
  3696. }
  3697. else
  3698. {
  3699. //
  3700. // This call is a result of a logoff notification from the
  3701. // CSC agent running within winlogon.exe.
  3702. //
  3703. if (g_hToken)
  3704. {
  3705. if (ImpersonateLoggedOnUser(g_hToken))
  3706. {
  3707. HandleLogoffTasks();
  3708. RevertToSelf();
  3709. }
  3710. }
  3711. }
  3712. if (g_hToken)
  3713. {
  3714. TraceMsg("CSCUIInitialize: Freeing token handle");
  3715. CloseHandle(g_hToken);
  3716. g_hToken = NULL;
  3717. }
  3718. }
  3719. exit_gracefully:
  3720. TraceLeaveValue(g_hWndNotification);
  3721. }
  3722. LRESULT
  3723. AttemptRasConnect(
  3724. LPCTSTR pszServer
  3725. )
  3726. {
  3727. LRESULT lRes = LRESULT_CSCFAIL;
  3728. HMODULE hMod = LoadLibrary(TEXT("rasadhlp.dll"));
  3729. if (hMod)
  3730. {
  3731. PFNHLPNBCONNECTION pfn;
  3732. pfn = (PFNHLPNBCONNECTION)GetProcAddress(hMod, (LPCSTR)"AcsHlpNbConnection");
  3733. STDBGOUT((1, TEXT("Attempting RAS connection to \"%s\""), pszServer ? pszServer : TEXT("<null>")));
  3734. if (pfn)
  3735. {
  3736. if ((*pfn)(pszServer))
  3737. {
  3738. STDBGOUT((1, TEXT("RAS connection successful. Action is LRESULT_CSCRETRY.")));
  3739. lRes = LRESULT_CSCRETRY;
  3740. }
  3741. else
  3742. {
  3743. STDBGOUT((2, TEXT("AttemptRasConnect: AcsHlpNbConnection() failed.")));
  3744. }
  3745. }
  3746. else
  3747. {
  3748. STDBGOUT((2, TEXT("AttemptRasConnect: Error %d getting addr of AcsHlpNbConnection()"), GetLastError()));
  3749. }
  3750. FreeLibrary(hMod);
  3751. }
  3752. else
  3753. {
  3754. STDBGOUT((2, TEXT("AttemptRasConnect: Error %d loading rasadhlp.dll. Action is LRESULT_CSCFAIL"), GetLastError()));
  3755. }
  3756. if (LRESULT_CSCFAIL == lRes)
  3757. {
  3758. STDBGOUT((1, TEXT("RAS connection failed.")));
  3759. }
  3760. return lRes;
  3761. }
  3762. //////////////////////////////////////////////////////////////////////////////
  3763. // _OnQueryNetDown
  3764. //
  3765. // Handler for STWM_CSCQUERYNETDOWN
  3766. //
  3767. // Returns:
  3768. //
  3769. // LRESULT_CSCFAIL - Fail the connection NT4-style.
  3770. // LRESULT_CSCWORKOFFLINE - Transition this server to "offline" mode.
  3771. // LRESULT_CSCRETRY - We have a RAS connection. Retry.
  3772. //
  3773. LRESULT OnQueryNetDown(
  3774. DWORD dwAutoDialFlags,
  3775. LPCTSTR pszServer
  3776. )
  3777. {
  3778. LRESULT lResult = LRESULT_CSCFAIL;
  3779. if (CSCUI_NO_AUTODIAL != dwAutoDialFlags)
  3780. {
  3781. //
  3782. // The server is not in the CSC database and CSCDLL wants us
  3783. // to offer the USER a RAS connection.
  3784. //
  3785. lResult = AttemptRasConnect(pszServer);
  3786. }
  3787. //
  3788. // CSC is not available on 'Personal' SKU.
  3789. //
  3790. if (!IsOS(OS_PERSONAL))
  3791. {
  3792. //
  3793. // lResult will be LRESULT_CSCFAIL under two conditions:
  3794. //
  3795. // 1. dwAutoDialFlags is CSCUI_NO_AUTODIAL so lResult has it's initial value.
  3796. // 2. AttemptRasConnect() failed and returned LRESULT_CSCFAIL.
  3797. // In this case we now want to determine if we really want to
  3798. // fail the request or if we should transition offline.
  3799. //
  3800. // Also, only execute this if the server is in the cache. If not,
  3801. // we don't want to go offline on the server; we just want to fail
  3802. // it.
  3803. //
  3804. if ((LRESULT_CSCFAIL == lResult) &&
  3805. (CSCUI_AUTODIAL_FOR_UNCACHED_SHARE != dwAutoDialFlags))
  3806. {
  3807. //
  3808. // This code is called from within the winlogon process. Because
  3809. // it's winlogon, there's some funky stuff going on with user tokens
  3810. // and registry keys. In order to read the user preference for
  3811. // "offline action" we need to temporarily impersonate the currently
  3812. // logged on user.
  3813. //
  3814. int iAction = CConfig::eGoOfflineSilent; // default if impersonation fails.
  3815. if (g_hToken)
  3816. {
  3817. if (ImpersonateLoggedOnUser(g_hToken))
  3818. {
  3819. iAction = CConfig::GetSingleton().GoOfflineAction(pszServer);
  3820. RevertToSelf();
  3821. }
  3822. }
  3823. switch(iAction)
  3824. {
  3825. case CConfig::eGoOfflineSilent:
  3826. STDBGOUT((1, TEXT("Action is LRESULT_CSCWORKOFFLINE")));
  3827. lResult = LRESULT_CSCWORKOFFLINE;
  3828. break;
  3829. case CConfig::eGoOfflineFail:
  3830. STDBGOUT((1, TEXT("Action is LRESULT_CSCFAIL")));
  3831. lResult = LRESULT_CSCFAIL;
  3832. break;
  3833. default:
  3834. STDBGOUT((1, TEXT("Invalid action (%d), defaulting to LRESULT_CSCWORKOFFLINE"), iAction));
  3835. //
  3836. // An invalid action code defaults to "work offline".
  3837. //
  3838. lResult = LRESULT_CSCWORKOFFLINE;
  3839. break;
  3840. }
  3841. }
  3842. }
  3843. return lResult;
  3844. }
  3845. //
  3846. // This function is typically called from the CSC Agent (cscdll) in winlogon.
  3847. // The Agent asks us whether to transition offline or not, and also notifies
  3848. // us of status changes (net-up, net-down, etc.). Status changes are passed
  3849. // on to the hidden systray window.
  3850. //
  3851. // Special care must be taken to not call SendMessage back to the UI thread,
  3852. // since it is possible, although unlikely, that the UI thread is hitting
  3853. // the net and blocked waiting for a response from this function (deadlock).
  3854. //
  3855. // The debug-only STDBGOUT is exempted from the SendMessage ban. If you hit
  3856. // a deadlock due to STDBGOUT, reboot and turn off SysTrayOutput.
  3857. //
  3858. STDAPI_(LRESULT) CSCUISetState(UINT uMsg, WPARAM wParam, LPARAM lParam)
  3859. {
  3860. LRESULT lRes = 0;
  3861. LPTSTR pszServer = (LPTSTR)lParam;
  3862. if (pszServer && !*pszServer)
  3863. pszServer = NULL;
  3864. switch(uMsg)
  3865. {
  3866. case STWM_CSCQUERYNETDOWN:
  3867. STDBGOUT((1, TEXT("Rcvd STWM_CSCQUERYNETDOWN, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
  3868. lRes = OnQueryNetDown((DWORD)wParam, pszServer);
  3869. //
  3870. // HACK! This is a hack to handle the way the redirector and the CSC
  3871. // agent work in the "net down" case. The CSC agent tells us
  3872. // about "no net" in a CSCQUERYNETDOWN rather than a CSCNETDOWN
  3873. // like I would prefer it. The problem is that the redirector
  3874. // doesn't actually transition the servers to offline until
  3875. // a server is touched. Therefore, when lParam == 0
  3876. // we need to first handle the "query" case to determine what to tell
  3877. // the CSC agent (fail, work offline, retry etc). Then, if the
  3878. // result is not "retry", we need to continue processing the message
  3879. // as if it were STWM_CSCNETDOWN. [brianau]
  3880. //
  3881. if (LRESULT_CSCRETRY == lRes || NULL != pszServer)
  3882. return lRes;
  3883. uMsg = STWM_CSCNETDOWN;
  3884. //
  3885. // Fall through...
  3886. //
  3887. case STWM_CSCNETDOWN:
  3888. STDBGOUT((1, TEXT("Rcvd STWM_CSCNETDOWN, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
  3889. break;
  3890. case STWM_CSCNETUP:
  3891. STDBGOUT((1, TEXT("Rcvd STWM_CSCNETUP, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
  3892. break;
  3893. case STWM_CACHE_CORRUPTED:
  3894. STDBGOUT((1, TEXT("Rcvd STWM_CACHE_CORRUPTED, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
  3895. break;
  3896. }
  3897. //
  3898. // If we have a server name, use WM_COPYDATA to get the data
  3899. // into explorer's process.
  3900. //
  3901. if (pszServer)
  3902. {
  3903. SendCopyDataToSystray(uMsg, StringByteSize(pszServer), pszServer);
  3904. }
  3905. else
  3906. {
  3907. PostToSystray(CSCWM_UPDATESTATUS, uMsg, 0);
  3908. }
  3909. return lRes;
  3910. }
  3911. const TCHAR c_szExploreClass[] = TEXT("ExploreWClass");
  3912. const TCHAR c_szIExploreClass[] = TEXT("IEFrame");
  3913. const TCHAR c_szCabinetClass[] = TEXT("CabinetWClass");
  3914. const TCHAR c_szDesktopClass[] = TEXT(STR_DESKTOPCLASS);
  3915. BOOL IsExplorerWindow(HWND hwnd)
  3916. {
  3917. TCHAR szClass[32];
  3918. GetClassName(hwnd, szClass, ARRAYSIZE(szClass));
  3919. if ( (lstrcmp(szClass, c_szCabinetClass) == 0)
  3920. ||(lstrcmp(szClass, c_szIExploreClass) == 0)
  3921. ||(lstrcmp(szClass, c_szExploreClass) == 0))
  3922. return TRUE;
  3923. return FALSE;
  3924. }
  3925. //
  3926. // IsWindowBrowsingServer determines if a given window is browsing a particular
  3927. // server. The function assumes that the window is an explorer window.
  3928. // If pszServer == NULL, return TRUE if the window is browsing a remote path
  3929. // even if the window is not browsing this particular server.
  3930. //
  3931. BOOL IsWindowBrowsingServer(
  3932. HWND hwnd,
  3933. LPCTSTR pszServer
  3934. )
  3935. {
  3936. BOOL bResult = FALSE;
  3937. DWORD_PTR dwResult;
  3938. DWORD dwPID = GetCurrentProcessId();
  3939. const UINT uFlags = SMTO_NORMAL | SMTO_ABORTIFHUNG;
  3940. if (SendMessageTimeout(hwnd,
  3941. CWM_CLONEPIDL,
  3942. (WPARAM)dwPID,
  3943. 0L,
  3944. uFlags,
  3945. 5000,
  3946. &dwResult))
  3947. {
  3948. HANDLE hmem = (HANDLE)dwResult;
  3949. if (NULL != hmem)
  3950. {
  3951. LPITEMIDLIST pidl = (LPITEMIDLIST)SHLockShared(hmem, dwPID);
  3952. if (NULL != pidl)
  3953. {
  3954. TCHAR szPath[MAX_PATH];
  3955. if (SHGetPathFromIDList(pidl, szPath))
  3956. {
  3957. LPTSTR pszRemotePath;
  3958. if (S_OK == GetRemotePath(szPath, &pszRemotePath))
  3959. {
  3960. if (NULL == pszServer)
  3961. {
  3962. bResult = TRUE;
  3963. }
  3964. else
  3965. {
  3966. PathStripToRoot(pszRemotePath);
  3967. PathRemoveFileSpec(pszRemotePath);
  3968. bResult = (0 == lstrcmpi(pszServer, pszRemotePath));
  3969. }
  3970. LocalFreeString(&pszRemotePath);
  3971. }
  3972. }
  3973. SHUnlockShared(pidl);
  3974. }
  3975. SHFreeShared(hmem, dwPID);
  3976. }
  3977. }
  3978. return bResult;
  3979. }
  3980. BOOL CALLBACK _RefreshEnum(HWND hwnd, LPARAM lParam)
  3981. {
  3982. LPCTSTR pszServer = (LPCTSTR)lParam;
  3983. if (IsExplorerWindow(hwnd) && IsWindowBrowsingServer(hwnd, pszServer))
  3984. {
  3985. STDBGOUT((2, TEXT("Refreshing explorer wnd 0x%08X for \"%s\""), hwnd, pszServer));
  3986. PostMessage(hwnd, WM_COMMAND, FCIDM_REFRESH, 0L);
  3987. }
  3988. return(TRUE);
  3989. }
  3990. //
  3991. // _RefreshAllExplorerWindows is called by the CSC tray wnd proc
  3992. // in response to a PWM_REFRESH_SHELL message. The pszServer argument
  3993. // is the name of the server (i.e. "\\scratch") that has transitioned
  3994. // either online or offline. The function refreshes windows that are
  3995. // currently browsing the server.
  3996. //
  3997. // If pszServer is NULL, the function refreshes all windows browsing the net.
  3998. //
  3999. void _RefreshAllExplorerWindows(LPCTSTR pszServer)
  4000. {
  4001. //
  4002. // Without initializing COM, we hit a "com not initialized" assertion
  4003. // in shdcocvw when calling SHGetPathFromIDList in IsWindowBrowsingServer.
  4004. //
  4005. if (SUCCEEDED(CoInitialize(NULL)))
  4006. {
  4007. //
  4008. // Note that the enumeration doesn't catch the desktop window,
  4009. // but we don't care. Change notifications are working now, so
  4010. // content is updated properly. We're only doing this refresh stuff
  4011. // to keep WebView up-to-date with respect to online/offline state.
  4012. // The desktop doesn't have that, so no need to refresh it.
  4013. //
  4014. EnumWindows(_RefreshEnum, (LPARAM)pszServer);
  4015. CoUninitialize();
  4016. }
  4017. }
  4018. STDAPI_(BOOL) CSCUIMsgProcess(LPMSG pMsg)
  4019. {
  4020. return IsDialogMessage(g_hwndStatusDlg, pMsg);
  4021. }
  4022. //-----------------------------------------------------------------------------
  4023. // SysTray debug monitoring code.
  4024. //
  4025. //
  4026. // This function can run in either winlogon, systray or mobsync processes.
  4027. // That's why we use WM_COPYDATA to communicate the text information.
  4028. //
  4029. #if DBG
  4030. void STDebugOut(
  4031. int iLevel,
  4032. LPCTSTR pszFmt,
  4033. ...
  4034. )
  4035. {
  4036. if (STDebugLevel() >= iLevel)
  4037. {
  4038. TCHAR szText[1024];
  4039. SYSTEMTIME t;
  4040. GetLocalTime(&t);
  4041. wnsprintf(szText, ARRAYSIZE(szText), TEXT("[pid %d] %02d:%02d:%02d.%03d "),
  4042. GetCurrentProcessId(),
  4043. t.wHour,
  4044. t.wMinute,
  4045. t.wSecond,
  4046. t.wMilliseconds);
  4047. UINT cch = lstrlen(szText);
  4048. va_list args;
  4049. va_start(args, pszFmt);
  4050. wvnsprintf(szText + cch, ARRAYSIZE(szText)-cch, pszFmt, args);
  4051. va_end(args);
  4052. COPYDATASTRUCT cds;
  4053. cds.dwData = PWM_STDBGOUT;
  4054. cds.cbData = StringByteSize(szText);
  4055. cds.lpData = szText;
  4056. SendToSystray(WM_COPYDATA, 0, (LPARAM)&cds);
  4057. }
  4058. }
  4059. int STDebugLevel(void)
  4060. {
  4061. static DWORD dwMonitor = (DWORD)-1;
  4062. if ((DWORD)-1 == dwMonitor)
  4063. {
  4064. dwMonitor = 0;
  4065. HKEY hkey;
  4066. DWORD dwType;
  4067. DWORD cbData = sizeof(DWORD);
  4068. DWORD dwStatus = STDebugOpenNetCacheKey(KEY_QUERY_VALUE, &hkey);
  4069. if (ERROR_SUCCESS == dwStatus)
  4070. {
  4071. RegQueryValueEx(hkey,
  4072. c_szSysTrayOutput,
  4073. NULL,
  4074. &dwType,
  4075. (LPBYTE)&dwMonitor,
  4076. &cbData);
  4077. RegCloseKey(hkey);
  4078. }
  4079. }
  4080. return int(dwMonitor);
  4081. }
  4082. //
  4083. // Called in response to PWM_STDBGOUT. This occurs in the systray process only.
  4084. //
  4085. void STDebugOnLogEvent(
  4086. HWND hwndList,
  4087. LPCTSTR pszText
  4088. )
  4089. {
  4090. if (pszText && *pszText)
  4091. {
  4092. int iTop = (int)SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)pszText);
  4093. SendMessage(hwndList, LB_SETTOPINDEX, iTop - 5, 0);
  4094. }
  4095. }
  4096. typedef BOOL (WINAPI * PFNGETSAVEFILENAME)(LPOPENFILENAME);
  4097. //
  4098. // This function will always run on the window's thread and in the systray process.
  4099. //
  4100. void STDebugSaveListboxContent(
  4101. HWND hwndParent
  4102. )
  4103. {
  4104. static bool bSaving = false; // Re-entrancy guard.
  4105. if (bSaving)
  4106. return;
  4107. HMODULE hModComdlg = LoadLibrary(TEXT("comdlg32"));
  4108. if (NULL == hModComdlg)
  4109. return;
  4110. PFNGETSAVEFILENAME pfnSaveFileName = (PFNGETSAVEFILENAME)GetProcAddress(hModComdlg, "GetSaveFileNameW");
  4111. if (NULL != pfnSaveFileName)
  4112. {
  4113. bSaving = true;
  4114. TCHAR szFile[MAX_PATH] = TEXT("C:\\CSCUISystrayLog.txt");
  4115. OPENFILENAME ofn = {0};
  4116. ofn.lStructSize = sizeof(ofn);
  4117. ofn.hwndOwner = hwndParent;
  4118. ofn.hInstance = g_hInstance;
  4119. ofn.lpstrFile = szFile;
  4120. ofn.nMaxFile = ARRAYSIZE(szFile);
  4121. ofn.lpstrDefExt = TEXT("txt");
  4122. if ((*pfnSaveFileName)(&ofn))
  4123. {
  4124. HANDLE hFile = CreateFile(szFile,
  4125. GENERIC_WRITE,
  4126. FILE_SHARE_READ,
  4127. NULL,
  4128. CREATE_ALWAYS,
  4129. FILE_ATTRIBUTE_NORMAL,
  4130. NULL);
  4131. if (INVALID_HANDLE_VALUE != hFile)
  4132. {
  4133. int n = (int)SendDlgItemMessage(hwndParent, IDC_DEBUG_LIST, LB_GETCOUNT, 0, 0);
  4134. TCHAR szText[MAX_PATH];
  4135. for (int i = 0; i < n; i++)
  4136. {
  4137. //
  4138. // WARNING: This could potentially overwrite the szText[] buffer.
  4139. // However, since the text should be of a readable length
  4140. // in a listbox I doubt if it will exceed MAX_PATH.
  4141. //
  4142. SendDlgItemMessage(hwndParent, IDC_DEBUG_LIST, LB_GETTEXT, i, (LPARAM)szText);
  4143. StringCchCat(szText, ARRAYSIZE(szText), TEXT("\r\n"));
  4144. DWORD dwWritten = 0;
  4145. WriteFile(hFile, szText, lstrlen(szText) * sizeof(TCHAR), &dwWritten, NULL);
  4146. }
  4147. CloseHandle(hFile);
  4148. }
  4149. else
  4150. {
  4151. TCHAR szMsg[MAX_PATH];
  4152. wnsprintf(szMsg, ARRAYSIZE(szMsg), TEXT("Error %d creating file \"%s\""), GetLastError(), szFile);
  4153. MessageBox(hwndParent, szMsg, STR_CSCHIDDENWND_TITLE, MB_ICONERROR | MB_OK);
  4154. }
  4155. }
  4156. }
  4157. bSaving = false;
  4158. FreeLibrary(hModComdlg);
  4159. }
  4160. DWORD STDebugOpenNetCacheKey(
  4161. DWORD dwAccess,
  4162. HKEY *phkey
  4163. )
  4164. {
  4165. DWORD dwDisposition;
  4166. return RegCreateKeyEx(HKEY_LOCAL_MACHINE,
  4167. REGSTR_KEY_OFFLINEFILES,
  4168. 0,
  4169. NULL,
  4170. 0,
  4171. dwAccess,
  4172. NULL,
  4173. phkey,
  4174. &dwDisposition);
  4175. }
  4176. #endif // DBG