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

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