//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1997 - 1999 // // File: cscst.cpp // //-------------------------------------------------------------------------- #include "pch.h" #pragma hdrstop #include // STR_DESKTOPCLASS #ifdef REPORT_DEVICE_CHANGES # include // Device change notifications. #endif // REPORT_DEVICE_CHANGES #include // For ConvertStringSidToSid #include "cscst.h" #include "options.h" #include "statdlg.h" // CStatusDlg #include "uihooks.h" // Self-host notifications #include "folder.h" #include "eventlog.h" #include "msg.h" #include "purge.h" #include "security.h" #include "syncmgr.h" #include "strings.h" #include "termserv.h" #if DBG // // This code is used to manage the hidden window when we // unhide it and display debug output to it via STDBGOUT(). // #include #include const TCHAR c_szSysTrayOutput[] = TEXT("SysTrayOutput"); int STDebugLevel(void); void STDebugOnLogEvent(HWND hwndList, LPCTSTR pszText); void STDebugSaveListboxContent(HWND hwndParent); DWORD STDebugOpenNetCacheKey(DWORD dwAccess, HKEY *phkey); #endif // DBG // // Size of systray icons. // #define CSC_ICON_CX 16 #define CSC_ICON_CY 16 // // Timer IDs are arbitrary. // #define ID_TIMER_FLASHICON 2953 #define ID_TIMER_REMINDER 2954 #define ID_TIMER_STATECHANGE 2955 // Prototypes void ApplyAdminFolderPolicy(void); // in admin.cpp void _RefreshAllExplorerWindows(LPCTSTR pszServer); // Globals static HWND g_hWndNotification = NULL; extern HWND g_hwndStatusDlg; // in statdlg.cpp HANDLE g_hToken = NULL; #ifdef REPORT_DEVICE_CHANGES HDEVNOTIFY g_hDevNotify = NULL; #endif // REPORT_DEVICE_CHANGES // // RAS Autodial API. // typedef BOOL (WINAPI * PFNHLPNBCONNECTION)(LPCTSTR); #if DBG // // Provide some text-form names for state and input values // to support debug output. The order of these corresponds // to the STS_XXXXX enumeration. // LPCTSTR g_pszSysTrayStates[] = { TEXT("STS_INVALID"), TEXT("STS_ONLINE"), TEXT("STS_DIRTY"), TEXT("STS_MDIRTY"), TEXT("STS_SERVERBACK"), TEXT("STS_MSERVERBACK"), TEXT("STS_OFFLINE"), TEXT("STS_MOFFLINE"), TEXT("STS_NONET") }; // // A simple function to translate a state value to a string. // LPCTSTR SysTrayStateStr(eSysTrayState s) { return g_pszSysTrayStates[int(s)]; } #endif // // A simple dynamic list of server names. A name can be provided // as either a "\\server" or "\\server\share" and only the server // part "\\server" is stored. // class CServerList { public: CServerList(void) : m_hdpa(DPA_Create(10)) { } ~CServerList(void); bool Add(LPCTSTR pszServer); void Remove(LPCTSTR pszServer); void Clear(void); int Find(LPCTSTR pszServer); int Count(void) const; LPCTSTR Get(int iItem) const; bool Exists(LPCTSTR pszServer) { return -1 != Find(pszServer); } private: HDPA m_hdpa; void GetServerFromPath(LPCTSTR pszPath, LPTSTR pszServer, int cchServer); // // Prevent copy. // CServerList(const CServerList& rhs); CServerList& operator = (const CServerList& rhs); }; // // The class that translates CSC agent input and cache status into a subsequent // systray UI state. Originally this was a table-driven state machine // (hence the name). It later proved sufficient to do a simple scan of cache // status and determine UI state based on the statistics obtained. The name // has been retained for lack of something better. // class CStateMachine { public: CStateMachine(bool bNoNet) : m_bNoNet(bNoNet) { } // // This is THE function for converting CSC agent input (or a // simple status check) into a systray icon state. // eSysTrayState TranslateInput(UINT uMsg, LPTSTR pszShare, UINT cchShare); void PingServers(); bool ServerPendingReconnection(LPCTSTR pszServer) { return m_PendingReconList.Add(pszServer); } void ServerReconnected(LPCTSTR pszServer) { m_PendingReconList.Remove(pszServer); } void ServerUnavailable(LPCTSTR pszServer) { m_PendingReconList.Remove(pszServer); } void AllServersUnavailable(void) { m_PendingReconList.Clear(); } bool IsServerPendingReconnection(LPCTSTR pszServer) { return m_PendingReconList.Exists(pszServer); } private: CServerList m_PendingReconList; bool m_bNoNet; // // Some helper functions for decoding CSC share status values. // bool ShareIsOffline(DWORD dwCscStatus) const { return (0 != (FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwCscStatus)); } bool ShareHasFiles(LPCTSTR pszShare, bool *pbModified = NULL, bool *pbOpen = NULL) const; // // Prevent copy. // CStateMachine(const CStateMachine& rhs); CStateMachine& operator = (const CStateMachine& rhs); }; // // The CSysTrayUI class encapsulates the manipulation of the systray icon // so that the rest of the CSCUI code is exposed to only a narrow interface // to the systray. It also maintains state information to control flashing // of the systray icon. All flashing processing is provided by this class. // class CSysTrayUI { public: ~CSysTrayUI(void); // // Set the state of the systray icon. This will only change the // icon if the state has changed. Therefore this function can be // called without worrying about excessive redundant updates to // the display. // bool SetState(eSysTrayState state, LPCTSTR pszServer = NULL); // // Retrieve the current "state" of the systray UI. The state // is one of the STS_XXXXX codes. // eSysTrayState GetState(void) const { return m_state; } // // Retrieve the server name to be used in CSCUI elements. // If the server name string is empty, that means there are // multiple servers in the given state. // LPCTSTR GetServerName(void) const { return m_szServer; } // // Show the balloon text for the current systray state. // void ShowReminderBalloon(void); // // Reset the reminder timer. // void ResetReminderTimer(bool bRestart); // // Make any adjustments when a WM_WININICHANGE is received. // void OnWinIniChange(LPCTSTR pszSection); // // // Get a reference to THE singleton instance. // static CSysTrayUI& GetInstance(void); private: // // A minimal autoptr class to ensure the singleton instance // is deleted. // class autoptr { public: autoptr(void) : m_ptr(NULL) { } ~autoptr(void) { delete m_ptr; } CSysTrayUI* Get(void) const { return m_ptr; } void Set(CSysTrayUI *p) { delete m_ptr; m_ptr = p; } private: CSysTrayUI *m_ptr; autoptr(const autoptr& rhs); autoptr& operator = (const autoptr& rhs); }; // // Icon info maintained for each UI state. // struct IconInfo { HICON hIcon; // Handle to icon to display in this state. UINT idIcon; // ID of icon to display in this state. int iFlashTimeout; // 0 == No icon flash. Time is in millisec. }; // // Info maintained to describe the various balloon text messages. // Combination of state and dwTextFlags are the table keys. // struct BalloonInfo { eSysTrayState state; // SysTray state value. DWORD dwTextFlags; // BTF_XXXXX flags. DWORD dwInfoFlags; // NIIF_XXXXX flag. UINT idHeader; // Res id for header part. UINT idStatus; // Res id for status part. UINT idBody; // Res id for body part. UINT idDirective; // Res id for directive part. }; // // Info maintained to describe the various tooltip text messages. // struct TooltipInfo { eSysTrayState state; // SysTray state value. UINT idTooltip; // Tooltip text resource ID. }; // // Info maintained for special-case supression of systray balloons. // There are some state transitions that shouldn't generate a balloon. // This structure describes each entry in an array of supression info. // struct BalloonSupression { eSysTrayState stateFrom; // Transitioning from this state. eSysTrayState stateTo; // Transitioning to this state. }; // // Enumeration for controlling what's done to the systray on update. // enum eUpdateFlags { UF_ICON = 0x00000001, // Update the icon. UF_FLASHICON = 0x00000002, // Flash the icon. UF_BALLOON = 0x00000004, // Show the balloon. UF_REMINDER = 0x00000008 }; // Balloon is a reminder. // // These flags relate a cache state to balloon text message. // They fit into an encoded mask where the lowest 4 bits // contain the eSysTrayState (STS_XXXXXX) code. // // (STS_OFFLINE | BTF_INITIAL) // // would indicate the condition where the state is "offline" for // a single server and the text to be displayed is for the initial // notification. // enum eBalloonTextFlags { BTF_INITIAL = 0x00000010, // Initial notification BTF_REMIND = 0x00000020 // Reminder }; static IconInfo s_rgIconInfo[]; // The icon info static BalloonInfo s_rgBalloonInfo[]; // Balloon configuration info. static TooltipInfo s_rgTooltipInfo[]; // Tooltip configuration info. static BalloonSupression s_rgBalloonSupression[]; static const int s_iMinStateChangeInterval; UINT_PTR m_idFlashingTimer; // Flash timer id. UINT_PTR m_idReminderTimer; // Timer for showing reminder balloons. UINT_PTR m_idStateChangeTimer; // Timer for queued state changes. UINT m_iIconFlashTime; // Period of icon flashes (ms). HICON& m_hIconNoOverlay; // Icon used for flashing. HWND m_hwndNotify; // Notification window. DWORD m_dwFlashingExpires; // Tick count when flash timer expires. DWORD m_dwNextStateChange; // Tick count for next queued state change. TCHAR m_szServer[MAX_PATH]; // Servername for balloon messages. TCHAR m_szServerQueued[MAX_PATH]; eSysTrayState m_state; // Remember current state. eSysTrayState m_statePrev; eSysTrayState m_stateQueued; bool m_bFlashOverlay; // Alternates 0,1 (1 == display overlay, 0 == don't) bool m_bActive; // 1 == we have an active icon in systray. // // Enforce singleton existance by making construction // and copy operations private. // CSysTrayUI(HWND hwndNotify); CSysTrayUI(const CSysTrayUI& rhs); CSysTrayUI& operator = (const CSysTrayUI& rhs); void UpdateSysTray(eUpdateFlags uFlags, LPCTSTR pszServer = NULL); int GetBalloonInfoIndex(eSysTrayState state, DWORD dwTextFlags); bool StateHasBalloonText(eSysTrayState state, DWORD dwTextFlags); void GetBalloonInfo(eSysTrayState state, DWORD dwTextFlags, LPTSTR pszTextHdr, int cchTextHdr, LPTSTR pszTextBody, int cchTextBody, DWORD *pdwInfoFlags, UINT *puTimeout); bool SupressBalloon(eSysTrayState statePrev, eSysTrayState state); LPTSTR GetTooltipText(eSysTrayState state, LPTSTR pszText, int cchText); bool IconFlashedLongEnough(void); void KillIconFlashTimer(void); void HandleFlashTimer(void); void OnStateChangeTimerExpired(void); static VOID CALLBACK FlashTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); static VOID CALLBACK ReminderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); static VOID CALLBACK StateChangeTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); }; #define ICONFLASH_FOREVER (UINT(-1)) #define ICONFLASH_NONE 0 // // These rows must stay in the same order as the STS_XXXXX enumeration members. // For flash timeout values, 0 == no flash, -1 == never stop. // Everything else is a timeout in milliseconds. // CSysTrayUI::IconInfo CSysTrayUI::s_rgIconInfo[] = { { NULL, 0, ICONFLASH_NONE }, /* STS_INVALID */ { NULL, 0, ICONFLASH_NONE }, /* STS_ONLINE */ { NULL, IDI_CSCWARNING, ICONFLASH_FOREVER }, /* STS_DIRTY */ { NULL, IDI_CSCWARNING, ICONFLASH_FOREVER }, /* STS_MDIRTY */ { NULL, IDI_CSCINFORMATION, ICONFLASH_NONE }, /* STS_SERVERBACK */ { NULL, IDI_CSCINFORMATION, ICONFLASH_NONE }, /* STS_MSERVERBACK */ { NULL, IDI_CSCNORMAL, ICONFLASH_NONE }, /* STS_OFFLINE */ { NULL, IDI_CSCNORMAL, ICONFLASH_NONE }, /* STS_MOFFLINE */ { NULL, IDI_CSCNORMAL, ICONFLASH_NONE }}; /* STS_NONET */ // // This table describes all information related to displaying the systray balloons. // The first two columns are the keys to each record; those being a systray UI state // and a mask of balloon-text flags. // Notes: // 1. There's no balloon for STS_NONET. We found that the user's response is // duh, I know I have no net. // // CSysTrayUI::BalloonInfo CSysTrayUI::s_rgBalloonInfo[] = { { STS_INVALID, BTF_INITIAL, NIIF_NONE, 0, 0, 0, 0, }, { STS_INVALID, BTF_REMIND, NIIF_NONE, 0, 0, 0, 0, }, { STS_OFFLINE, BTF_INITIAL, NIIF_INFO, IDS_BTHDR_INITIAL, IDS_BTSTA_OFFLINE, IDS_BTBOD_OFFLINE, IDS_BTDIR_VIEWSTATUS }, { STS_MOFFLINE, BTF_INITIAL, NIIF_INFO, IDS_BTHDR_INITIAL, IDS_BTSTA_OFFLINE, IDS_BTBOD_OFFLINE_M, IDS_BTDIR_VIEWSTATUS }, { STS_OFFLINE, BTF_REMIND, NIIF_INFO, IDS_BTHDR_REMIND, IDS_BTSTA_OFFLINE, IDS_BTBOD_STILLOFFLINE, IDS_BTDIR_VIEWSTATUS }, { STS_MOFFLINE, BTF_REMIND, NIIF_INFO, IDS_BTHDR_REMIND, IDS_BTSTA_OFFLINE, IDS_BTBOD_STILLOFFLINE_M, IDS_BTDIR_VIEWSTATUS }, // { STS_SERVERBACK, BTF_INITIAL, NIIF_INFO, IDS_BTHDR_INITIAL, IDS_BTSTA_SERVERBACK,IDS_BTBOD_SERVERBACK, IDS_BTDIR_RECONNECT }, // { STS_MSERVERBACK,BTF_INITIAL, NIIF_INFO, IDS_BTHDR_INITIAL, IDS_BTSTA_SERVERBACK,IDS_BTBOD_SERVERBACK_M, IDS_BTDIR_RECONNECT }, { STS_SERVERBACK, BTF_REMIND, NIIF_INFO, IDS_BTHDR_REMIND, IDS_BTSTA_SERVERBACK,IDS_BTBOD_STILLBACK, IDS_BTDIR_RECONNECT }, { STS_MSERVERBACK,BTF_REMIND, NIIF_INFO, IDS_BTHDR_REMIND, IDS_BTSTA_SERVERBACK,IDS_BTBOD_STILLBACK_M, IDS_BTDIR_RECONNECT }, { STS_DIRTY, BTF_INITIAL, NIIF_WARNING, IDS_BTHDR_INITIAL, IDS_BTSTA_DIRTY, IDS_BTBOD_DIRTY, IDS_BTDIR_SYNC }, { STS_MDIRTY, BTF_INITIAL, NIIF_WARNING, IDS_BTHDR_INITIAL, IDS_BTSTA_DIRTY, IDS_BTBOD_DIRTY_M, IDS_BTDIR_SYNC }, { STS_DIRTY, BTF_REMIND, NIIF_WARNING, IDS_BTHDR_REMIND, IDS_BTSTA_DIRTY, IDS_BTBOD_STILLDIRTY, IDS_BTDIR_SYNC }, { STS_MDIRTY, BTF_REMIND, NIIF_WARNING, IDS_BTHDR_REMIND, IDS_BTSTA_DIRTY, IDS_BTBOD_STILLDIRTY_M, IDS_BTDIR_SYNC } }; // // This table lists all of the state transitions that do not generate balloons. // Ideally, I would have a true state machine to control the UI for any given state transition. // However, since we have quite a few states and since you can transition from any state // to almost any other state, the state transition table would be large and confusing // to read. Instead, I've taken the position to assume all state transitions generate // the balloon UI associated with the "to" state unless the transition is listed // in this table. // CSysTrayUI::BalloonSupression CSysTrayUI::s_rgBalloonSupression[] = { { STS_MOFFLINE, STS_OFFLINE }, { STS_NONET, STS_OFFLINE }, { STS_NONET, STS_MOFFLINE } }; // // This table describes all information related to displaying tooltip text // for the systray icon. // CSysTrayUI::TooltipInfo CSysTrayUI::s_rgTooltipInfo[] = { { STS_INVALID, 0 }, { STS_OFFLINE, IDS_TT_OFFLINE }, { STS_MOFFLINE, IDS_TT_OFFLINE_M }, { STS_SERVERBACK, IDS_TT_SERVERBACK }, { STS_MSERVERBACK, IDS_TT_SERVERBACK_M }, { STS_DIRTY, IDS_TT_DIRTY }, { STS_MDIRTY, IDS_TT_DIRTY_M }, { STS_NONET, IDS_TT_NONET } }; //----------------------------------------------------------------------------- // CServerList member functions. //----------------------------------------------------------------------------- CServerList::~CServerList( void ) { if (NULL != m_hdpa) { int cEntries = DPA_GetPtrCount(m_hdpa); LPTSTR pszEntry; for (int i = 0; i < cEntries; i++) { pszEntry = (LPTSTR)DPA_GetPtr(m_hdpa, i); if (NULL != pszEntry) LocalFree(pszEntry); } DPA_Destroy(m_hdpa); } } void CServerList::GetServerFromPath( LPCTSTR pszPath, LPTSTR pszServer, int cchServer ) { TCHAR szServer[MAX_PATH]; lstrcpyn(szServer, pszPath, ARRAYSIZE(szServer)); PathAddBackslash(szServer); PathStripToRoot(szServer); LPTSTR pszLastBackslash = StrRChr(szServer, szServer + lstrlen(szServer), TEXT('\\')); if (NULL != pszLastBackslash && pszLastBackslash > (szServer + 2)) *pszLastBackslash = TEXT('\0'); lstrcpyn(pszServer, szServer, cchServer); } bool CServerList::Add( LPCTSTR pszServer ) { if (NULL != m_hdpa) { if (!Exists(pszServer)) { int cchEntry = lstrlen(pszServer) + 1; LPTSTR pszEntry = (LPTSTR)LocalAlloc(LPTR, sizeof(TCHAR) * cchEntry); if (NULL != pszEntry) { GetServerFromPath(pszServer, pszEntry, cchEntry); if (-1 != DPA_AppendPtr(m_hdpa, pszEntry)) return true; // // Addition to DPA failed. Delete the string buffer. // LocalFree(pszEntry); } } } return false; } void CServerList::Remove( LPCTSTR pszServer ) { int iEntry = Find(pszServer); if (-1 != iEntry) { LPTSTR pszEntry = (LPTSTR)DPA_DeletePtr(m_hdpa, iEntry); if (NULL != pszEntry) LocalFree(pszEntry); } } LPCTSTR CServerList::Get( int iItem ) const { if (NULL != m_hdpa) return (LPCTSTR)DPA_GetPtr(m_hdpa, iItem); return NULL; } int CServerList::Count( void ) const { if (NULL != m_hdpa) return DPA_GetPtrCount(m_hdpa); return 0; } // // Locate a server name in the "pending reconnection" list. // pszServer can either be "\\server" or "\\server\share". // // Returns: Index of entry if found. -1 if not found. // int CServerList::Find( LPCTSTR pszServer ) { TCHAR szServer[MAX_PATH]; GetServerFromPath(pszServer, szServer, ARRAYSIZE(szServer)); if (NULL != m_hdpa) { int cEntries = DPA_GetPtrCount(m_hdpa); LPTSTR pszEntry; for (int i = 0; i < cEntries; i++) { pszEntry = (LPTSTR)DPA_GetPtr(m_hdpa, i); if (NULL != pszEntry) { if (0 == lstrcmpi(pszEntry, szServer)) return i; } } } return -1; } void CServerList::Clear( void ) { if (NULL != m_hdpa) { int cEntries = DPA_GetPtrCount(m_hdpa); LPTSTR pszEntry; for (int i = 0; i < cEntries; i++) { pszEntry = (LPTSTR)DPA_DeletePtr(m_hdpa, i); if (NULL != pszEntry) { LocalFree(pszEntry); } } } } //----------------------------------------------------------------------------- // CStateMachine member functions. //----------------------------------------------------------------------------- // // Translates a STWM_XXXXX message from the CSC agent into a systray UI state // code. The caller also provides a buffer to a server name. If we find // a "single server" condition in the cache (i.e. one server is dirty, one // server is offline etc), then we write the name of this server to this // buffer. Otherwise, the buffer remains unchanged. The goal here is to // end up with a buffer containing the name of the applicable server when // we have one of these one-server conditions. Ultimately, the server name // is included in the tray balloon text message. // // The function returns one of the STS_XXXXX UI status codes. // // This function is rather long. Much longer than I like a function to be. // I've tried to break it up into smaller pieces but any chunks were pretty // much arbitrary. Without a good logical breakdown, that doesn't make much // sense. Even with it's length, it's not a complex function. It merely // enumerates shares in the cache gathering statistics along the way. From // these statistics, it decides what the next UI state should be. // eSysTrayState CStateMachine::TranslateInput( UINT uMsg, LPTSTR pszServer, UINT cchServer ) { // // Since this cscui code is running all the time, we don't want to keep // a handle to the event log open. Therefore, we use this CscuiEventLog // object to automatically close the log for us. The ReportEvent member // of CscuiEventLog handles all initialization of the log and determining // if the event should actually be logged (depending upon the current CSCUI // event logging level). // CscuiEventLog log; bool bServerIsBack = false; if (STWM_CSCNETUP == uMsg) { m_bNoNet = false; if (TEXT('\0') != *pszServer) { STDBGOUT((1, TEXT("Translating STWM_CSCNETUP for server \"%s\""), pszServer)); // // Server reported back by the CSC agent. // Add it's name to a persistent (in memory) list of // servers available for reconnection. // Also clear the "no net" flag. // bServerIsBack = true; ServerPendingReconnection(pszServer); if (log.LoggingEnabled()) { log.Push(pszServer); log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SERVER_AVAILABLE, 1); } } else { STDBGOUT((1, TEXT("Translating STWM_CSCNETUP (no associated server)"))); if (log.LoggingEnabled()) { log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_NET_STARTED, 2); } } } else if (STWM_CSCNETDOWN == uMsg) { // // This is the only place where transitions from online to // offline state are noted in the shell process. (CSCUISetState // and OnQueryNetDown execute in WinLogon's process). // if (TEXT('\0') != *pszServer) { STDBGOUT((1, TEXT("Translating STWM_CSCNETDOWN for server \"%s\""), pszServer)); if (!m_bNoNet) { LPTSTR pszTemp; if (LocalAllocString(&pszTemp, pszServer)) { PostToSystray(PWM_REFRESH_SHELL, 0, (LPARAM)pszTemp); } } // // Server reported down by the CSC agent. // Remove it's name from the persistent (in memory) list // of servers available for reconnection. // ServerUnavailable(pszServer); if (log.LoggingEnabled()) { log.Push(pszServer); log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SERVER_OFFLINE, 1); } } else { STDBGOUT((1, TEXT("Translating STWM_CSCNETDOWN (no associated server)"))); // // Entire network reported down by the CSC agent. // Remove all names from the persistent (in memory) list // of servers available for reconnection. m_bNoNet is the only persistent // state we have. Once it is set, the only thing that can reset it // is a STWM_CSCNETUP message from the CSC agent. // if (!m_bNoNet) PostToSystray(PWM_REFRESH_SHELL, 0, 0); m_bNoNet = true; AllServersUnavailable(); if (log.LoggingEnabled()) { log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_NET_STOPPED, 2); } } } else if (STWM_STATUSCHECK == uMsg) { STDBGOUT((1, TEXT("Translating STWM_STATUSCHECK"))); } else if (STWM_CACHE_CORRUPTED == uMsg) { // // Note: No check for LoggingEnabled(). We always log corrupted cache // regardless of logging level. // STDBGOUT((1, TEXT("Translating STWM_CACHE_CORRUPTED"))); log.ReportEvent(EVENTLOG_ERROR_TYPE, MSG_E_CACHE_CORRUPTED, 0); } // // If CSC is disabled or the cache is empty, the default UI state // is "online". // eSysTrayState state = STS_ONLINE; if (IsCSCEnabled()) { DWORD dwStatus; DWORD dwPinCount; DWORD dwHintFlags; WIN32_FIND_DATA fd; FILETIME ft; CCscFindHandle hFind; hFind = CacheFindFirst(NULL, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft); if (hFind.IsValid()) { // // We need these three temporary name lists to reconcile a problem with // the way the CSC cache and RDR are designed. When we enumerate the cache, // we enumerate individual shares in the cache. Each share has some condition // (i.e. dirty, offline etc) associated with it. The problem is that the // redirector handles things on a server basis. So when a particular share // is offline, in reality the entire server is offline. We've decided that // the UI should reflect things on a server (computer) basis so we need to // avoid including the states of multiple shares from the same server in // our totals. These three lists are used to store the names of servers // with shares in one of the three states (offline, dirty, pending recon). // If we enumerate a share with one of these states and find it already // exists in the corresponding list, we don't include this share in the // statistics. // int cShares = 0; CServerList OfflineList; CServerList DirtyList; CServerList BackList; // // If a server is back, assume we can auto-reconnect it. // bool bAutoReconnectServer = bServerIsBack; TCHAR szAutoReconnectShare[MAX_PATH] = {0}; DWORD dwPathSpeed = 0; do { bool bShareIsOnServer = boolify(PathIsPrefix(pszServer, fd.cFileName)); bool bShareHasModifiedFiles = false; bool bShareHasOpenFiles = false; // // A share participates in the systray UI calculations only if the // share contains files OR the share is currently "offline". // Because of the CSC database design, CSC doesn't remove a share // entry after all it's files have been removed from the cache. // Therefore we need this extra check to avoid including empty shares in the UI. // if (ShareHasFiles(fd.cFileName, &bShareHasModifiedFiles, &bShareHasOpenFiles) || ShareIsOffline(dwStatus)) { cShares++; if (bShareIsOnServer && (bShareHasModifiedFiles || bShareHasOpenFiles)) { // // Auto-reconnect isn't allowed if one or more shares on the server // have open files or files modified offline. Auto-reconnection // would put the cache into a dirty state. // bAutoReconnectServer = false; } // // A share can be in one of 4 states: // Online // Dirty // Offline // Pending reconnection ('back') // // Note that our definition of Dirty implies Online, and Pending // Reconnection implies Offline. That is, an offline share is // never dirty and an online share is never pending reconnection. // //--------------------------------------------------------------------- // Is the share online? //--------------------------------------------------------------------- if (!ShareIsOffline(dwStatus)) { //--------------------------------------------------------------------- // Is the share dirty? (online + offline changes) //--------------------------------------------------------------------- if (bShareHasModifiedFiles) { STDBGOUT((3, TEXT("Share \"%s\" is dirty (0x%08X)"), fd.cFileName, dwStatus)); DirtyList.Add(fd.cFileName); } else { STDBGOUT((3, TEXT("Share \"%s\" is online (0x%08X)"), fd.cFileName, dwStatus)); } } else // Offline { //--------------------------------------------------------------------- // Is the server back? //--------------------------------------------------------------------- if (IsServerPendingReconnection(fd.cFileName)) { STDBGOUT((3, TEXT("Share \"%s\" is pending reconnection (0x%08X)"), fd.cFileName, dwStatus)); BackList.Add(fd.cFileName); } else { STDBGOUT((3, TEXT("Share \"%s\" is OFFLINE (0x%08X)"), fd.cFileName, dwStatus)); OfflineList.Add(fd.cFileName); } } } if (!ShareIsOffline(dwStatus)) { // It's online, so it can't be pending reconnection. ServerReconnected(fd.cFileName); // ...and there's no need to reconnect it. if (bShareIsOnServer) bAutoReconnectServer = false; } if (FLAG_CSC_SHARE_STATUS_PINNED_OFFLINE & dwStatus) { // // Finally... if the user has 'forced' the share offline // we don't allow auto-reconnection. This allows the // user to 'tag' a share as "always offline" from an // auto-reconnect perspective. One might do this for a // RAS connection. // bAutoReconnectServer = false; } if (bAutoReconnectServer && bShareIsOnServer && TEXT('\0') == szAutoReconnectShare[0]) { // // Remember the share name for possible auto-reconnection. // The transition API is TransitionServerOnline but it takes a share name. // Bad choice of names (IMO) but that's the way Shishir did it in the // CSC APIs. It can be any share on the server. // // However, it's possible to have defunct shares in the // database. Try to find one that's connectable. // if (CSCCheckShareOnlineEx(fd.cFileName, &dwPathSpeed)) { STDBGOUT((3, TEXT("Share \"%s\" alive at %d00 bps"), fd.cFileName, dwPathSpeed)); lstrcpyn(szAutoReconnectShare, fd.cFileName, ARRAYSIZE(szAutoReconnectShare)); } else { STDBGOUT((3, TEXT("Share \"%s\" unreachable, error = %d"), fd.cFileName, GetLastError())); } } } while(CacheFindNext(hFind, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft)); if (bAutoReconnectServer) { //--------------------------------------------------------------------- // Handle auto-reconnection. //--------------------------------------------------------------------- // if (TEXT('\0') != szAutoReconnectShare[0]) { // // Server was reported "BACK" by the CSC agent and it has no open files // nor files modified offline and it's not on a slow link. // This makes it a candidate for automatic reconnection. Try it. // STDBGOUT((1, TEXT("Attempting to auto-reconnect \"%s\""), szAutoReconnectShare)); if (TransitionShareOnline(szAutoReconnectShare, TRUE, TRUE, dwPathSpeed)) { // // The server has been reconnected. Remove it's name from the // "pending reconnection" list. // ServerReconnected(pszServer); // // Remove this server from the temporary lists we've been keeping. // DirtyList.Remove(pszServer); BackList.Remove(pszServer); OfflineList.Remove(pszServer); if (log.LoggingEnabled()) { log.Push(pszServer); log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SERVER_AUTORECONNECT, 3); } } } } int cDirty = DirtyList.Count(); int cBack = BackList.Count(); int cOffline = OfflineList.Count(); STDBGOUT((2, TEXT("Cache check server results: cShares = %d, cDirty = %d, cBack = %d, cOffline = %d"), cShares, cDirty, cBack, cOffline)); // // This code path is a waterfall where lower-priority states are overwritten // by higher-priority states as they are encountered. The order of this array // is important. It's ordered by increasing priority (no net is // highest priority for systray UI). // CServerList *pServerList = NULL; struct Criteria { int cnt; // Number of applicable servers found. eSysTrayState state; // Single-item UI state. eSysTrayState mstate; // Multi-item UI state. CServerList *pList; // Ptr to applicable list with server names. } rgCriteria[] = { { cOffline, STS_OFFLINE, STS_MOFFLINE, &OfflineList }, { cBack, STS_SERVERBACK, STS_MSERVERBACK, &BackList }, { cDirty, STS_DIRTY, STS_MDIRTY, &DirtyList }, { cShares && m_bNoNet ? 1 : 0, STS_NONET, STS_NONET, NULL } }; for (int i = 0; i < ARRAYSIZE(rgCriteria); i++) { Criteria& c = rgCriteria[i]; if (0 < c.cnt) { state = c.mstate; if (1 == c.cnt) { state = c.state; pServerList = NULL; if (NULL != c.pList && 1 == c.pList->Count()) { pServerList = c.pList; } } } } if (NULL != pServerList) { // // We had a single-server condition so write the server name // to the caller's server name buffer. // If we didn't have a single-server condition, the buffer // remains unchanged. // lstrcpyn(pszServer, pServerList->Get(0), cchServer); } } } STDBGOUT((1, TEXT("Translated to SysTray UI state %s"), SysTrayStateStr(state))); return state; } // // Ping offline servers. If any are alive, update status and // auto-reconnect them if possible. This is typically done // after a sync operation has completed. // DWORD WINAPI _PingServersThread(LPVOID /*pThreadData*/) { DWORD dwStatus; WIN32_FIND_DATA fd; HANDLE hFind; hFind = CacheFindFirst(NULL, &fd, &dwStatus, NULL, NULL, NULL); if (INVALID_HANDLE_VALUE != hFind) { CServerList BackList; do { // If the tray state becomes Online or NoNet, we can quit eSysTrayState state = (eSysTrayState)SendToSystray(PWM_QUERY_UISTATE, 0, 0); if (STS_ONLINE == state || STS_NONET == state) break; // Call BackList.Exists here to avoid extra calls to // CSCCheckShareOnline. (Add also calls Exists) if ((FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwStatus) && !BackList.Exists(fd.cFileName)) { if (!CSCCheckShareOnline(fd.cFileName)) { DWORD dwErr = GetLastError(); if (ERROR_ACCESS_DENIED != dwErr && ERROR_LOGON_FAILURE != dwErr) { // The share is not reachable continue; } // Access denied or logon failure means the server is // reachable, but we don't have valid credentials. } // The share is offline but available again. STDBGOUT((1, TEXT("Detected server back: %s"), fd.cFileName)); BackList.Add(fd.cFileName); // Get the \\server name (minus the sharename) and // tell ourselves that it's back. LPCTSTR pszServer = BackList.Get(BackList.Count() - 1); if (pszServer) { CSCUISetState(STWM_CSCNETUP, 0, (LPARAM)pszServer); } } } while(CacheFindNext(hFind, &fd, &dwStatus, NULL, NULL, NULL)); CSCFindClose(hFind); } DllRelease(); FreeLibraryAndExitThread(g_hInstance, 0); return 0; } void CStateMachine::PingServers() { // Don't bother trying if there's no net. if (!m_bNoNet) { DWORD dwThreadID; // Give the thread a reference to the DLL HINSTANCE hInstThisDll = LoadLibrary(c_szDllName); DllAddRef(); HANDLE hThread = CreateThread(NULL, 0, _PingServersThread, NULL, 0, &dwThreadID); if (hThread) { CloseHandle(hThread); } else { // CreateThread failed, cleanup DllRelease(); FreeLibrary(hInstThisDll); } } } // // Determine if a given share has files cached in the CSC cache. // // bool CStateMachine::ShareHasFiles( LPCTSTR pszShare, bool *pbModified, bool *pbOpen ) const { // // Exclude the following: // 1. Directories. // 2. Files marked as "locally deleted". // // NOTE: The filtering done by this function must be the same as // in several other places throughout the CSCUI code. // To locate these, search the source for the comment // string CSCUI_ITEM_FILTER. // const DWORD fExclude = SSEF_LOCAL_DELETED | SSEF_DIRECTORY; // // Stop stats enumeration when we've found all of the following: // 1. At least one file. // 2. At least one modified file. // 3. At least one file with either USER access OR GUEST access. // const DWORD fUnity = SSUF_TOTAL | SSUF_MODIFIED | SSUF_ACCUSER | SSUF_ACCGUEST | SSUF_ACCOR; CSCSHARESTATS ss; CSCGETSTATSINFO si = { fExclude, fUnity, true, false }; _GetShareStatisticsForUser(pszShare, // Share name. &si, &ss); // Destination buffer. if (NULL != pbModified) { *pbModified = (0 < ss.cModified); } if (NULL != pbOpen) { *pbOpen = ss.bOpenFiles; } return 0 < ss.cTotal; } //----------------------------------------------------------------------------- // CSysTrayUI member functions. //----------------------------------------------------------------------------- // // This is the minimum interval (in ms) allowed between state changes of // the systray UI. A value of 0 would result in immediate updates as // notifications are received from the CSC agent. A value of 60000 would // cause any state changes received less than 60 seconds after the previous // state change to be queued. 60 seconds after the previous state change, // if a state change is queued it is applied to the systray UI. // Something to consider is dynamically adjusting // const int CSysTrayUI::s_iMinStateChangeInterval = 10000; // 10 seconds. CSysTrayUI::CSysTrayUI( HWND hwndNotify ) : m_idFlashingTimer(0), m_idReminderTimer(0), m_idStateChangeTimer(0), m_iIconFlashTime(GetCaretBlinkTime()), m_hIconNoOverlay(s_rgIconInfo[int(STS_OFFLINE)].hIcon), // The offline icon is used // as the non-overlay icon for // flashing. m_hwndNotify(hwndNotify), m_dwFlashingExpires(0), m_dwNextStateChange(0), m_state(STS_ONLINE), m_statePrev(STS_INVALID), m_stateQueued(STS_INVALID), m_bFlashOverlay(false), m_bActive(false) { // // Load up the required icons. // for (int i = 0; i < ARRAYSIZE(s_rgIconInfo); i++) { IconInfo& sti = s_rgIconInfo[i]; if (NULL == sti.hIcon && 0 != sti.idIcon) { sti.hIcon = (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(sti.idIcon), IMAGE_ICON, CSC_ICON_CX, CSC_ICON_CY, LR_LOADMAP3DCOLORS); if (NULL == sti.hIcon) { Trace((TEXT("CSCUI ERROR %d loading Icon ID = %d"), GetLastError(), sti.idIcon)); } } } m_szServer[0] = TEXT('\0'); m_szServerQueued[0] = TEXT('\0'); UpdateSysTray(UF_ICON); } CSysTrayUI::~CSysTrayUI( void ) { if (0 != m_idStateChangeTimer) KillTimer(m_hwndNotify, m_idStateChangeTimer); } // // Singleton instance access. // CSysTrayUI& CSysTrayUI::GetInstance( void ) { static CSysTrayUI TheUI(_FindNotificationWindow()); return TheUI; } // // Change the current state of the UI to a new state. // Returns: // true = state was changed. // false = state was not changed. // bool CSysTrayUI::SetState( eSysTrayState state, LPCTSTR pszServer // Optional. Default is NULL. ) { bool bResult = false; // // Apply a state change only if the state has actually changed. // if (state != m_state) { // // Apply a state change only if there's not a sync in progress. // If there is a sync in progress, we'll receive a CSCWM_DONESYNCING // message when the sync is finished which will trigger a UI update. // if (!::IsSyncInProgress()) { if (0 == m_idStateChangeTimer) { // // The state change timer is not active. That means it's OK // to update the tray UI. // STDBGOUT((1, TEXT("Changing SysTray UI state %s -> %s"), SysTrayStateStr(m_state), SysTrayStateStr(state))); m_statePrev = m_state; m_state = state; UpdateSysTray(eUpdateFlags(UF_ICON | UF_BALLOON), pszServer); // // Reset the state change timer so that we will not produce a // visible change in the tray UI for at least another // s_iMinStateChangeInterval milliseconds. // Also invalidate the queued state info so that if the update timer // expires before we queue a state change, it will be a no-op. // STDBGOUT((2, TEXT("Setting state change timer"))); m_stateQueued = STS_INVALID; m_idStateChangeTimer = SetTimer(m_hwndNotify, ID_TIMER_STATECHANGE, s_iMinStateChangeInterval, StateChangeTimerProc); bResult = true; } else { // // The state change timer is active so we can't update the tray // UI right now. We'll queue up the state information so when the // timer expires this state will be applied. Note that the "queue" // is only ONE item deep. Each successive addition to the queue // overwrites the current content. // STDBGOUT((2, TEXT("Queueing state change to %s."), SysTrayStateStr(state))); m_stateQueued = state; if (NULL != pszServer) { lstrcpyn(m_szServerQueued, pszServer, ARRAYSIZE(m_szServerQueued)); } else { m_szServerQueued[0] = TEXT('\0'); } } } else { STDBGOUT((2, TEXT("Sync in progress. SysTray state not changed."))); } } return bResult; } // // Called each time the state change timer expires. // VOID CALLBACK CSysTrayUI::StateChangeTimerProc( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime ) { // // Call a non-static function of the singleton instance so // we have access to private members. // CSysTrayUI::GetInstance().OnStateChangeTimerExpired(); } void CSysTrayUI::OnStateChangeTimerExpired( void ) { STDBGOUT((2, TEXT("State change timer expired. Queued state = %s"), SysTrayStateStr(m_stateQueued))); // // Kill the timer and set it's ID to 0. // This will let SetState() know that the timer has expired and // it's OK to update the tray UI. // if (0 != m_idStateChangeTimer) { KillTimer(m_hwndNotify, m_idStateChangeTimer); m_idStateChangeTimer = 0; } if (int(m_stateQueued) != int(STS_INVALID)) { // // Call SetState ONLY if queued info is valid; meaning // there was something in the queue. // SetState(m_stateQueued, m_szServerQueued); } } // // On WM_WININICHANGED update the icon flash timer. // void CSysTrayUI::OnWinIniChange( LPCTSTR pszSection ) { m_iIconFlashTime = GetCaretBlinkTime(); KillIconFlashTimer(); UpdateSysTray(UF_FLASHICON); } // // Show the reminder balloon associated with the current UI state. // void CSysTrayUI::ShowReminderBalloon( void ) { UpdateSysTray(eUpdateFlags(UF_BALLOON | UF_REMINDER)); } // // All roads lead here. // This function is the kitchen sink for updating the systray. // It's kind of a long function but it centralizes all changes to // the systray. It's divided into 3 basic parts: // // 1. Change the tray icon. (UF_ICON) // 2. Flash the tray icon. (UF_FLASHICON) // 3. Display a notification balloon. (UF_BALLOON) // // Part or all of these can be performed in a single call depending // upon the content of the uFlags argument. // void CSysTrayUI::UpdateSysTray( eUpdateFlags uFlags, LPCTSTR pszServer // optional. Default is NULL. ) { NOTIFYICONDATA nid = {0}; if (!IsWindow(m_hwndNotify)) return; // // If an icon is active, we're modifying it. // If none active, we're adding one. // DWORD nimsg = NIM_MODIFY; nid.cbSize = sizeof(NOTIFYICONDATA); nid.uID = PWM_TRAYCALLBACK; nid.uFlags = NIF_MESSAGE; nid.uCallbackMessage = PWM_TRAYCALLBACK; nid.hWnd = m_hwndNotify; IconInfo& sti = s_rgIconInfo[int(m_state)]; if (NULL != pszServer && TEXT('\0') != *pszServer) { // // Copy the name of the server to a member variable. // Skip passed the leading "\\". // while(*pszServer && TEXT('\\') == *pszServer) pszServer++; lstrcpyn(m_szServer, pszServer, ARRAYSIZE(m_szServer)); } // // Change the icon -------------------------------------------------------- // if (UF_ICON & uFlags) { nid.uFlags |= NIF_ICON; if (0 == sti.idIcon) { // // This state doesn't have an icon. Delete from systray. // nimsg = NIM_DELETE; } else { if (!m_bActive) nimsg = NIM_ADD; nid.hIcon = sti.hIcon; // // If applicable, always flash icon when first showing it. // uFlags = eUpdateFlags(uFlags | UF_FLASHICON); // // Set the tooltip. // nid.uFlags |= NIF_TIP; GetTooltipText(m_state, nid.szTip, ARRAYSIZE(nid.szTip)); } m_bFlashOverlay = false; KillIconFlashTimer(); } // // Flash the icon --------------------------------------------------------- // if (UF_FLASHICON & uFlags) { if (0 != sti.iFlashTimeout) { nid.uFlags |= NIF_ICON; // Flashing is actually displaying a new icon. // // This icon is a flashing icon. // if (0 == m_idFlashingTimer) { // // No timer started yet. Start one. // STDBGOUT((2, TEXT("Starting icon flash timer. Time = %d ms"), m_iIconFlashTime)); m_idFlashingTimer = SetTimer(m_hwndNotify, ID_TIMER_FLASHICON, m_iIconFlashTime, FlashTimerProc); if (0 != m_idFlashingTimer) { // // Set the tick-count when the timer expires. // An expiration time of (-1) means it never expires. // if (ICONFLASH_FOREVER != sti.iFlashTimeout) m_dwFlashingExpires = GetTickCount() + sti.iFlashTimeout; else m_dwFlashingExpires = ICONFLASH_FOREVER; } } nid.hIcon = m_bFlashOverlay ? sti.hIcon : m_hIconNoOverlay; m_bFlashOverlay = !m_bFlashOverlay; // Toggle flash state. } } // // Update or hide the balloon --------------------------------------------- // if (UF_BALLOON & uFlags) { // // If there's no balloon text mapped to the current UI state and these // balloon flags, any current balloon will be destroyed. This is because // the tray code destroys the current balloon before displaying the new one // and it doesn't display a new one if it's passed a blank string. // nid.uFlags |= NIF_INFO; DWORD dwBalloonFlags = (UF_REMINDER & uFlags) ? BTF_REMIND : BTF_INITIAL; GetBalloonInfo(m_state, dwBalloonFlags, nid.szInfoTitle, ARRAYSIZE(nid.szInfoTitle), nid.szInfo, ARRAYSIZE(nid.szInfo), &nid.dwInfoFlags, &nid.uTimeout); // // Any time we show a balloon, we reset the reminder timer. // This is so that we don't get a balloon resulting from a state change // immediately followed by a reminder balloon because the reminder // timer expired. // bool bRestartReminderTimer = (BTF_REMIND == dwBalloonFlags && TEXT('\0') != nid.szInfo[0]) || StateHasBalloonText(m_state, BTF_REMIND); ResetReminderTimer(bRestartReminderTimer); } // // Notify the systray ----------------------------------------------------- // if (NIM_DELETE == nimsg) m_bActive = false; if (Shell_NotifyIcon(nimsg, &nid)) { if (NIM_ADD == nimsg) m_bActive = true; } } // // Get the balloon text associated with a given systray UI state and with // a given set of BTF_XXXXX (Balloon Text Flag) flags. The information // is stored in the table s_rgBalloonInfo[]. The text and balloon timeout // are returned in caller-provided buffers. // // The balloon text follows this format: // //
\n // // // // // // An example would be: // // Offline Files - Network Connection Lost // // The network connection to '\\worf' has been lost. // // Click here to view status. // // state is one of the STS_XXXXX flags. // dwTextFlags is a mask of BTF_XXXXX flag bits. // void CSysTrayUI::GetBalloonInfo( eSysTrayState state, DWORD dwTextFlags, LPTSTR pszTextHdr, int cchTextHdr, LPTSTR pszTextBody, int cchTextBody, DWORD *pdwInfoFlags, UINT *puTimeout ) { *pszTextHdr = TEXT('\0'); *pszTextBody = TEXT('\0'); if (SupressBalloon(m_statePrev, state)) { STDBGOUT((3, TEXT("Balloon supressed"))); return; } int i = GetBalloonInfoIndex(state, dwTextFlags); if (-1 != i) { BalloonInfo& bi = s_rgBalloonInfo[i]; TCHAR szHeader[80]; TCHAR szStatus[80]; TCHAR szDirective[80]; TCHAR szBody[MAX_PATH]; TCHAR szFmt[MAX_PATH]; if (STS_OFFLINE == state || STS_DIRTY == state || STS_SERVERBACK == state) { // // State has only one server associated with it so that means we'll // be including it in the balloon text body. Load the format // string from a text resource and embed the server name in it. // LPTSTR rgpstr[] = { m_szServer }; LoadString(g_hInstance, bi.idBody, szFmt, ARRAYSIZE(szFmt)); FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, szFmt, 0,0, szBody, ARRAYSIZE(szBody), (va_list *)rgpstr); } else { // // State has multiple servers associated with it so that means // there's no name embedded in the body. It's just a simple string // loaded from a text resource. // LoadString(g_hInstance, bi.idBody, szBody, ARRAYSIZE(szBody)); } // // Create the header text. // LoadString(g_hInstance, IDS_BALLOONHDR_FORMAT, szFmt, ARRAYSIZE(szFmt)); LoadString(g_hInstance, bi.idHeader, szHeader, ARRAYSIZE(szHeader)); LoadString(g_hInstance, bi.idStatus, szStatus, ARRAYSIZE(szStatus)); LPTSTR rgpstrHdr[] = { szHeader, szStatus }; FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, szFmt, 0,0, pszTextHdr, cchTextHdr, (va_list *)rgpstrHdr); // // Create the body text. // LoadString(g_hInstance, IDS_BALLOONBODY_FORMAT, szFmt, ARRAYSIZE(szFmt)); LoadString(g_hInstance, bi.idDirective, szDirective, ARRAYSIZE(szDirective)); LPTSTR rgpstrBody[] = { szBody, szDirective }; FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, szFmt, 0,0, pszTextBody, cchTextBody, (va_list *)rgpstrBody); if (NULL != pdwInfoFlags) { *pdwInfoFlags = bi.dwInfoFlags; } if (NULL != puTimeout) { CConfig& config = CConfig::GetSingleton(); // // Balloon timeout is stored in the registry. // UINT uTimeout = (BTF_INITIAL & dwTextFlags) ? config.InitialBalloonTimeoutSeconds() : config.ReminderBalloonTimeoutSeconds(); *puTimeout = uTimeout * 1000; } } } // // Find the index in s_rgBalloonInfo[] for a given state // and BTF_XXXXXX flag. // Returns -1 if no match in array. // int CSysTrayUI::GetBalloonInfoIndex( eSysTrayState state, DWORD dwTextFlags ) { // // Scan the balloon info table until we find a record for the // specified systray UI state and BTF flags. // for (int i = 0; i < ARRAYSIZE(s_rgBalloonInfo); i++) { BalloonInfo& bi = s_rgBalloonInfo[i]; if (bi.state == state && bi.dwTextFlags == dwTextFlags && 0 != bi.idHeader && 0 != bi.idStatus && 0 != bi.idBody && 0 != bi.idDirective) { return i; } } return -1; } // // Determine if a balloon should not be displayed for a particular // UI state transition. // bool CSysTrayUI::SupressBalloon( eSysTrayState statePrev, eSysTrayState state ) { for (int i = 0; i < ARRAYSIZE(s_rgBalloonSupression); i++) { if (statePrev == s_rgBalloonSupression[i].stateFrom && state == s_rgBalloonSupression[i].stateTo) { return true; } } return false; } // // Do we have balloon text for a given state and balloon style? // state is one of the STS_XXXXX flags. // dwTextFlags is a mask of BTF_XXXXX flag bits. // bool CSysTrayUI::StateHasBalloonText( eSysTrayState state, DWORD dwTextFlags ) { return (-1 != GetBalloonInfoIndex(state, dwTextFlags)); } LPTSTR CSysTrayUI::GetTooltipText( eSysTrayState state, LPTSTR pszText, int cchText ) { *pszText = TEXT('\0'); // // Scan the tooltip info table until we find a record for the // specified systray UI state. // for (int i = 0; i < ARRAYSIZE(s_rgTooltipInfo); i++) { TooltipInfo& tti = s_rgTooltipInfo[i]; if (tti.state == state && 0 != tti.idTooltip) { TCHAR szTemp[MAX_PATH]; szTemp[0] = TEXT('\0'); int cchHeader = LoadString(g_hInstance, IDS_TT_HEADER, szTemp, ARRAYSIZE(szTemp)); if (STS_OFFLINE == state || STS_DIRTY == state || STS_SERVERBACK == state) { // // State has only one server associated with it so that means we'll // be including it in the tooltip text. Embed the server name in it. // TCHAR szFmt[160]; LPTSTR rgpstr[] = { m_szServer }; LoadString(g_hInstance, tti.idTooltip, szFmt, ARRAYSIZE(szFmt)); FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, szFmt, 0,0, szTemp + cchHeader, ARRAYSIZE(szTemp) - cchHeader, (va_list *)rgpstr); } else { // // State has multiple servers associated with it so that means // there's no name embedded in the tooltip. It's just a simple string // loaded from a text resource. // LoadString(g_hInstance, tti.idTooltip, szTemp + cchHeader, ARRAYSIZE(szTemp) - cchHeader); } lstrcpyn(pszText, szTemp, cchText); } } return pszText; } // // Stop the flashing icon by killing the timer. // void CSysTrayUI::KillIconFlashTimer( void ) { // // Force a final update so we're displaying the proper icon then // kill the timer. // if (0 != m_idFlashingTimer) { KillTimer(m_hwndNotify, m_idFlashingTimer); m_idFlashingTimer = 0; } } // // Called by the OS each time the icon flash timer period expires. // I use this rather than handling a WM_TIMER message so that // timer processing is contained within the CSysTrayUI class. // VOID CALLBACK CSysTrayUI::FlashTimerProc( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime ) { CSysTrayUI::GetInstance().HandleFlashTimer(); } void CSysTrayUI::HandleFlashTimer( void ) { if (IconFlashedLongEnough()) { // // Kill the icon flashing timer and the icon will stop flashing. // This doesn't actually kill the timer yet. // STDBGOUT((2, TEXT("Killing icon flash timer"))); m_bFlashOverlay = true; UpdateSysTray(UF_FLASHICON); KillIconFlashTimer(); } else { // // The CSysTrayUI instance maintains all information // needed to cycle the icon. Just tell it to update // the icon and it'll do the right thing. // UpdateSysTray(UF_FLASHICON); } } // // Determine if the flashing icon has flashed enough. // bool CSysTrayUI::IconFlashedLongEnough( void ) { return ICONFLASH_FOREVER != m_dwFlashingExpires && GetTickCount() >= m_dwFlashingExpires; } // // Stop and restart the reminder timer. // If bRestart is false, the timer is killed and not restarted. // If bRestart is true, the timer is killed and a new one restarted. // void CSysTrayUI::ResetReminderTimer( bool bRestart ) { CConfig& config = CConfig::GetSingleton(); if (!config.NoReminders()) { int cReminderInterval = (config.ReminderFreqMinutes() * 1000 * 60); // // Force a final update so we're displaying the proper icon then // kill the timer. // if (0 != m_idReminderTimer) { KillTimer(m_hwndNotify, m_idReminderTimer); m_idReminderTimer = 0; } // // No timer started yet. Start one. // if (bRestart && 0 < cReminderInterval) { STDBGOUT((2, TEXT("Starting reminder timer. Timeout = %d ms"), cReminderInterval)); m_idReminderTimer = SetTimer(m_hwndNotify, ID_TIMER_REMINDER, cReminderInterval, ReminderTimerProc); } } } // // Called by the OS each time the reminder timer period expires. // I use this rather than handling a WM_TIMER message so that // timer processing is contained within the CSysTrayUI class. // VOID CALLBACK CSysTrayUI::ReminderTimerProc( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime ) { STDBGOUT((2, TEXT("Showing reminder balloon"))); CSysTrayUI::GetInstance().ShowReminderBalloon(); } // // Called by the systray WndProc whenever the state of the systray should be // updated. // // hWnd - HWND of the systray notification window. // // stwmMsg - STWM_CSCNETUP (Net or server is available for reconnect) // STWM_CSCNETDOWN (Net or server is unavailable) // STWM_STATUSCHECK (Check cache state and update systray) // // pszServer - non-NULL means CSC agent passed a server name // associated with the STWM_XXXX message. // This means there was a single server associated with the event // rather than multiple servers or the entire net interface. // void UpdateStatus( CStateMachine *pSM, HWND hWnd, UINT stwmMsg, LPTSTR pszServer ) { TraceEnter(TRACE_CSCST, "UpdateStatus"); TraceAssert(NULL != hWnd); TCHAR szServerName[MAX_PATH] = { 0 }; if (pszServer) { lstrcpyn(szServerName, pszServer, ARRAYSIZE(szServerName)); } // // Translate the CSC agent inputs into a new systray UI state. // eSysTrayState state = pSM->TranslateInput(stwmMsg, szServerName, ARRAYSIZE(szServerName)); // // Get reference to the singleton UI object and tell it to set the state. // Note that it remembers all current UI state and will only actually // update the systray if the UI state has changed. Here we can // blindly tell it to update state. It will only do what's necessary. // CSysTrayUI::GetInstance().SetState(state, szServerName); TraceLeaveVoid(); } /////////////////////////////////////////////////////////////////////////////// // _CreateMenu() // // Create context menu // HMENU _CreateMenu() { HMENU hmenu = NULL; TraceEnter(TRACE_CSCST, "_CreateMenu"); hmenu = CreatePopupMenu(); if (NULL != hmenu) { CConfig& config = CConfig::GetSingleton(); TCHAR szTemp[MAX_PATH]; // // Add the "Status" verb. // LoadString(g_hInstance, IDS_CSC_CM_STATUS, szTemp, ARRAYSIZE(szTemp)); AppendMenu(hmenu, MF_STRING, PWM_STATUSDLG, szTemp); // // Add the "Synchronize" verb // LoadString(g_hInstance, IDS_CSC_CM_SYNCHRONIZE, szTemp, ARRAYSIZE(szTemp)); AppendMenu(hmenu, MF_STRING, CSCWM_SYNCHRONIZE, szTemp); if (!config.NoCacheViewer()) { // // Add the "View files" verb // LoadString(g_hInstance, IDS_CSC_CM_SHOWVIEWER, szTemp, ARRAYSIZE(szTemp)); AppendMenu(hmenu, MF_STRING, CSCWM_VIEWFILES, szTemp); } if (!config.NoConfigCache()) { // // Add the "Settings" verb // LoadString(g_hInstance, IDS_CSC_CM_SETTINGS, szTemp, ARRAYSIZE(szTemp)); AppendMenu(hmenu, MF_STRING, CSCWM_SETTINGS, szTemp); } // // Left clicking the systray icon invokes the status dialog. // Therefore, the "Status" verb is our default and must be in bold text. // SetMenuDefaultItem(hmenu, PWM_STATUSDLG, MF_BYCOMMAND); } TraceLeaveValue(hmenu); } /////////////////////////////////////////////////////////////////////////////// // _ShowMenu() // UINT _ShowMenu(HWND hWnd, UINT uMenuNum, UINT uButton) { UINT iCmd = 0; HMENU hmenu; TraceEnter(TRACE_CSCST, "_ShowMenu"); hmenu = _CreateMenu(); if (hmenu) { POINT pt; GetCursorPos(&pt); SetForegroundWindow(hWnd); iCmd = TrackPopupMenu(hmenu, uButton | TPM_RETURNCMD | TPM_NONOTIFY, pt.x, pt.y, 0, hWnd, NULL); DestroyMenu(hmenu); } TraceLeaveValue(iCmd); } // // This function is used to ensure that we don't try to process // a WM_RBUTTONUP and WM_LBUTTONUP message at the same time. // May be a little paranoid. // LRESULT OnTrayIconSelected( HWND hWnd, UINT uMsg ) { static LONG bHandling = 0; LRESULT lResult = 0; if (0 == InterlockedCompareExchange(&bHandling, 1, 0)) { UINT iCmd = 0; switch (uMsg) { case WM_RBUTTONUP: // // Context menu // iCmd = _ShowMenu(hWnd, 1, TPM_RIGHTBUTTON); break; case WM_LBUTTONUP: iCmd = PWM_STATUSDLG; break; default: break; } if (iCmd) { PostMessage(hWnd, iCmd, 0, 0); lResult = 1; } bHandling = 0; } return lResult; } /////////////////////////////////////////////////////////////////////////////// // _Notify() -- systray notification handler // LRESULT _Notify(HWND hWnd, WPARAM /*wParam*/, LPARAM lParam) { LRESULT lResult = 0; switch (lParam) { case WM_RBUTTONUP: case WM_LBUTTONUP: lResult = OnTrayIconSelected(hWnd, (UINT)lParam); break; default: break; } return lResult; } bool IsServerBack(CStateMachine *pSM, LPCTSTR pszServer) { TCHAR szServer[MAX_PATH]; if (!PathIsUNC(pszServer)) { // // Ensure servername uses UNC format. // wsprintf(szServer, TEXT("\\\\%s"), pszServer); pszServer = szServer; } return pSM->IsServerPendingReconnection(pszServer); } // // Query CSC policy for the sync-at-logoff (quick vs. full) // setting. If the policy is set, we enable SyncMgr's sync-at-logoff // setting. Without this the CSC policy could be set, the SyncMgr // setting NOT set and the user wouldn't get sync-at-logoff as the // admin had anticipated. // void ApplyCscSyncAtLogonAndLogoffPolicies( void ) { bool bSetByPolicy = false; CConfig& config = CConfig::GetSingleton(); config.SyncAtLogoff(&bSetByPolicy); if (bSetByPolicy) { RegisterForSyncAtLogonAndLogoff(SYNCMGRREGISTERFLAG_PENDINGDISCONNECT, SYNCMGRREGISTERFLAG_PENDINGDISCONNECT); } config.SyncAtLogon(&bSetByPolicy); if (bSetByPolicy) { RegisterForSyncAtLogonAndLogoff(SYNCMGRREGISTERFLAG_CONNECT, SYNCMGRREGISTERFLAG_CONNECT); } } // // Encryption/Decryption callback from CSC. // // dwReason dwParam1 dwParam2 // ------------------------- ------------------ -------------------------- // CSCPROC_REASON_BEGIN 1 == Encrypting 0 // CSCPROC_REASON_MORE_DATA 0 Win32 error code // CSCPROC_REASON_END 1 == Completed dwParam1 == 1 ? 0 // dwParam1 == 0 ? GetLastError() // DWORD CALLBACK EncryptDecryptCallback( LPCWSTR lpszName, DWORD dwStatus, DWORD dwHintFlags, DWORD dwPinCount, WIN32_FIND_DATAW *pFind32, DWORD dwReason, DWORD dwParam1, DWORD dwParam2, DWORD_PTR dwContext ) throw() { DWORD dwResult = CSCPROC_RETURN_CONTINUE; const DWORD dwError = dwParam2; // // Some static data that needs to persist across callback calls. // static bool bEncrypting; // Encrypting or decrypting? static bool bLoggingOff = false; // User logging off? static int cFileErrors = 0; // How many file-specific errors reported? static DWORD dwLastError; static TCHAR szLastFile[MAX_PATH]; // // If we've already detected the g_heventTerminate event // no sense in continuing. // if (bLoggingOff) return CSCPROC_RETURN_ABORT; if (WAIT_OBJECT_0 == WaitForSingleObject(g_heventTerminate, 0)) { // // User is logging off. Need to end this now! // Log an event so admin knows why encryption was incomplete. // // LOGGING LEVEL = 0 (always) // CscuiEventLog log; log.ReportEvent(EVENTLOG_INFORMATION_TYPE, bEncrypting ? MSG_I_ENCRYPT_USERLOGOFF : MSG_I_DECRYPT_USERLOGOFF, 0); dwResult = CSCPROC_RETURN_ABORT; bLoggingOff = true; } else { switch(dwReason) { case CSCPROC_REASON_BEGIN: // // Reset static variables. // bEncrypting = boolify(dwParam1); bLoggingOff = false; cFileErrors = 0; dwLastError = ERROR_SUCCESS; szLastFile[0] = TEXT('\0'); break; case CSCPROC_REASON_MORE_DATA: if (ERROR_SUCCESS != dwError) { // // An error occurred for this file. // CscuiEventLog log; LPTSTR pszError = NULL; FormatSystemError(&pszError, dwError); if (0 == cFileErrors++) { // // On the first error, log an error at level 0. // By default, this is the only error the admin will see. // They'll need to increase the event log level to level // 2 in order to get events for each individual file. The // event text describes this. // // LOGGING_LEVEL = 0 // log.ReportEvent(EVENTLOG_ERROR_TYPE, bEncrypting ? MSG_E_ENCRYPTFILE_ERRORS : MSG_E_DECRYPTFILE_ERRORS, 0); } // // Log the error for this file. // // LOGGING LEVEL = 2 // log.Push(HRESULT(dwError), CEventLog::eFmtDec); log.Push(lpszName); log.Push(pszError ? pszError : TEXT("")); if (S_OK == log.ReportEvent(EVENTLOG_ERROR_TYPE, bEncrypting ? MSG_E_ENCRYPTFILE_FAILED : MSG_E_DECRYPTFILE_FAILED, 2)) { // // We logged this event. // Clear out the last error code and last filename so that // we don't report this error again in response to CSCPROC_REASON_END. // szLastFile[0] = TEXT('\0'); dwLastError = ERROR_SUCCESS; } else { // // Event was not logged because... // // a) ... an error occurred while logging the event. // b) ... EventLoggingLevel policy is too low for this event. // // Save this error code and file name. // We may need to report it in response to CSCPROC_REASON_END. // dwLastError = dwError; lstrcpyn(szLastFile, lpszName, ARRAYSIZE(szLastFile)); } if (pszError) LocalFree(pszError); } break; case CSCPROC_REASON_END: { const DWORD fCompleted = dwParam1; CscuiEventLog log; if (fCompleted) { // // Add an event log entry that the encryption/decryption // completed successfully. // // LOGGING LEVEL = 1 // log.ReportEvent(EVENTLOG_INFORMATION_TYPE, bEncrypting ? MSG_I_ENCRYPT_COMPLETE : MSG_I_DECRYPT_COMPLETE, 1); } else { LPTSTR pszError = NULL; if (ERROR_SUCCESS != dwError) { // // Some general error with the process. // // LOGGING LEVEL = 0 // FormatSystemError(&pszError, dwError); log.Push(HRESULT(dwError), CEventLog::eFmtDec); log.Push(pszError ? pszError : TEXT("")); log.ReportEvent(EVENTLOG_ERROR_TYPE, bEncrypting ? MSG_E_ENCRYPT_FAILED : MSG_E_DECRYPT_FAILED, 0); } else if (ERROR_SUCCESS != dwLastError) { if (0 == cFileErrors++) { // // On the first error, log an error at level 0. // By default, this is the only error the admin will see. // They'll need to increase the event log level to level // 2 in order to get events for each individual file. The // event text describes this. // // LOGGING_LEVEL = 0 // log.ReportEvent(EVENTLOG_ERROR_TYPE, bEncrypting ? MSG_E_ENCRYPTFILE_ERRORS : MSG_E_DECRYPTFILE_ERRORS, 0); } // // Encryption/decryption of some file failed and we did not // log it in the "more data" callback. // // LOGGING LEVEL = 2 // FormatSystemError(&pszError, dwLastError); log.Push(HRESULT(dwLastError), CEventLog::eFmtDec); log.Push(szLastFile); log.Push(pszError ? pszError : TEXT("")); log.ReportEvent(EVENTLOG_ERROR_TYPE, bEncrypting ? MSG_E_ENCRYPTFILE_FAILED : MSG_E_DECRYPTFILE_FAILED, 2); } if (pszError) LocalFree(pszError); } break; } default: break; } } return dwResult; } DWORD CacheEncryptionThreadProc( LPVOID pvParams ) { const DWORD fEncrypt = (DWORD)(DWORD_PTR)pvParams; HINSTANCE hmodCSCUI = LoadLibrary(c_szDllName); if (NULL != hmodCSCUI) { // // Try to get the encryption mutex. // HANDLE hMutex = RequestPermissionToEncryptCache(); if (NULL != hMutex) { // // Ensure release of the mutex. // CMutexAutoRelease auto_release_mutex(hMutex); STDBGOUT((1, TEXT("%s started."), fEncrypt ? TEXT("Encryption") : TEXT("Decryption"))); // // Do the encryption/decryption. Do it at a low thread priority so // we don't steal CPU time from the UI. // SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); CSCEncryptDecryptDatabase(fEncrypt, EncryptDecryptCallback, (DWORD_PTR)0); STDBGOUT((1, TEXT("%s complete."), fEncrypt ? TEXT("Encryption") : TEXT("Decryption"))); } else { // // Someone else (probably the UI) is currently encrypting/decrypting // the cache. We don't allow concurrent operations so we abort this // one. // STDBGOUT((1, TEXT("%s aborted. Already in progress."), fEncrypt ? TEXT("Encryption") : TEXT("Decryption"))); } FreeLibraryAndExitThread(hmodCSCUI, 0); } return 0; } // // Encrypt/Decrypt the cache according to system policy. // This function will also correct a partial (encrypt/decrypt) // state in the cache if necessary. // void ApplyCacheEncryptionPolicy( void ) { // // Do a quick check to see if an encryption process is already in progress. // This doesn't take the mutex but checks to see if someone else has // it. Once we actually start the encryption on the background thread // we'll take the mutex. Of course, if someone else sneeked in and grabbed // the mutex between now and then we'll have to abort. // if (!IsEncryptionInProgress()) { // // Encrypt/Decrypt the cache files. Will provide progress info through // the callback EncryptDecryptCallback. Errors are handled in the callback // reason handlers. // CConfig& config = CConfig::GetSingleton(); bool bShouldBeEncrypted = config.EncryptCache(); BOOL bPartial; const BOOL bIsEncrypted = IsCacheEncrypted(&bPartial); if (bPartial || (boolify(bIsEncrypted) != bShouldBeEncrypted)) { if (CscVolumeSupportsEncryption()) { // // Either we have a partially encrypted/decrypted cache or // current encryption state is different from what policy wants. // Encrypt/decrypt to rectify the situation. // Run this on a separate thread so we don't block any processing // on the tray UI thread (i.e. volume control). // DWORD dwThreadId; HANDLE hThread = CreateThread(NULL, // Default security. 0, // Default stack size. CacheEncryptionThreadProc, (VOID *)(DWORD_PTR)bShouldBeEncrypted, 0, // Run immediately &dwThreadId); if (NULL != hThread) { CloseHandle(hThread); } } else { // // The CSC volume doesn't support encryption. Log an event so // the admin will know why the cache wasn't encrypted by policy. // Note that we won't hit this path in the "partial" case. Only // if policy says to encrypt. The event log message is // tailored for this specific scenario. // TraceAssert(!bIsEncrypted && bShouldBeEncrypted); CscuiEventLog log; log.ReportEvent(EVENTLOG_WARNING_TYPE, MSG_W_NO_ENCRYPT_VOLUME, 0); } } } else { STDBGOUT((1, TEXT("Encryption/decryption not allowed. Already in progress."))); } } // // Handles policy change in response to a WM_WININICHANGE // message with lParam == "policy". // LRESULT OnPolicyChange( void ) { ApplyCacheEncryptionPolicy(); ApplyCscSyncAtLogonAndLogoffPolicies(); ApplyAdminFolderPolicy(); return 0; } // // Display the CSCUI status dialog. Invoked when user either // left-clicks the systray icon or selects the "Show Status" option // from the systray context menu. // void ShowCSCUIStatusDlg( HWND hwndParent ) { TCHAR szText[1024] = {0}; // This can be a fair amount of text. const struct { eSysTrayState state; // SysTray UI state code. UINT idsText; // Text for status dialog body. } rgMap[] = {{ STS_OFFLINE, IDS_STATUSDLG_OFFLINE }, { STS_MOFFLINE, IDS_STATUSDLG_OFFLINE_M }, { STS_SERVERBACK, IDS_STATUSDLG_SERVERBACK }, { STS_MSERVERBACK, IDS_STATUSDLG_SERVERBACK_M }, { STS_DIRTY, IDS_STATUSDLG_DIRTY }, { STS_MDIRTY, IDS_STATUSDLG_DIRTY_M }, { STS_NONET, IDS_STATUSDLG_NONET }}; CSysTrayUI& stui = CSysTrayUI::GetInstance(); eSysTrayState state = stui.GetState(); for (int i = 0; i < ARRAYSIZE(rgMap); i++) { if (state == rgMap[i].state) { LoadString(g_hInstance, rgMap[i].idsText, szText, ARRAYSIZE(szText)); if (STS_DIRTY == state || STS_OFFLINE == state || STS_SERVERBACK == state) { LPCTSTR pszServerName = stui.GetServerName(); if (NULL != pszServerName && TEXT('\0') != *pszServerName) { // // Current SysTray UI state has a single server associated // with it. The message will have the name embedded in // it in 2 places. Create a temp working buffer and // re-create the original string with the server name // embedded. If any of this fails, the string will just // be displayed with the %1, %2 formatting characters rather // than the server names. Not a fatal problem IMO. // LPTSTR pszTemp = new TCHAR[lstrlen(szText) + 1]; if (NULL != pszTemp) { lstrcpy(pszTemp, szText); LPCTSTR rgpstr[] = { pszServerName, pszServerName }; FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, pszTemp, 0,0, szText, ARRAYSIZE(szText), (va_list *)rgpstr); delete[] pszTemp; } } } break; // Break out of loop. We have what we need. } } // // Display the dialog. // CStatusDlg::Create(hwndParent, szText, state); } // // PWM_RESET_REMINDERTIMER handler. // void OnResetReminderTimer( void ) { CSysTrayUI::GetInstance().ResetReminderTimer(true); } // // Whenever we reboot, it's possible that the CSCUI cache has been // reformatted or that the cache-size policy has been set/changed. // When reformatted, the CSC agent uses the default size of 10%. We // need to ensure that the size reflects system policy when policy // is defined. // void InitCacheSize( void ) { bool bSetByPolicy = false; DWORD dwPctX10000 = CConfig::GetSingleton().DefaultCacheSize(&bSetByPolicy); if (bSetByPolicy) { ULARGE_INTEGER ulCacheSize; CSCSPACEUSAGEINFO sui; GetCscSpaceUsageInfo(&sui); if (10000 < dwPctX10000) { // // If value in registry is greater than 10000, it's // invalid. Default to 10% of total disk space. // dwPctX10000 = 1000; // Default to 10% (0.10 * 10,000) } ulCacheSize.QuadPart = (sui.llBytesOnVolume * dwPctX10000) / 10000i64; if (!CSCSetMaxSpace(ulCacheSize.HighPart, ulCacheSize.LowPart)) { STDBGOUT((1, TEXT("Error %d setting cache size"), GetLastError())); } } } // // Handler for CSCWM_SYNCHRONIZE. Called when user clicks "Synchronize" // option on systray context menu. Also invoked when user selects the // "Synchronize" button in a folder's web view pane. // HRESULT OnSynchronize( void ) { // // This will create a status dialog hidden, invoke a synchronization of // servers that would be "checked" in the dialog then close the dialog // when the synchronization is complete. // CStatusDlg::Create(g_hWndNotification, TEXT(""), CSysTrayUI::GetInstance().GetState(), CStatusDlg::MODE_AUTOSYNC); return NOERROR; } LRESULT OnQueryUIState( void ) { return CSysTrayUI::GetInstance().GetState(); } // // When a user profile has been removed from the local machine, // the delete-profile code in userenv.dll will write the user's SID // as a text string in the following reg key: // // HKLM\Software\Microsoft\Windows\CurrentVersion\NetCache\PurgeAtNextLogoff // // Each SID is a value name under this key. // At logoff, we enumerate all values under this key. For each SID we // instantiate a CCachePurger object and delete all files cached for this // user. Once the operation is complete, the "PurgeAtNextLogoff" key // is deleted from the registry. // void DeleteFilesCachedForObsoleteProfiles( void ) { HKEY hkeyNetcache; // // Open the "HKLM\...\NetCache" key. // LONG lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szCSCKey, 0, KEY_READ, &hkeyNetcache); if (ERROR_SUCCESS == lResult) { HKEY hkey; // // Open the "PurgeAtNextLogoff" subkey. // lResult = RegOpenKeyEx(hkeyNetcache, c_szPurgeAtNextLogoff, 0, KEY_READ, &hkey); if (ERROR_SUCCESS == lResult) { // // Enumerate all the SID strings. // int iValue = 0; TCHAR szValue[MAX_PATH]; DWORD cchValue = ARRAYSIZE(szValue); while(ERROR_SUCCESS == SHEnumValue(hkey, iValue, szValue, &cchValue, NULL, NULL, NULL)) { // // Convert each SID string to a SID and delete // all cached files accessed by this SID. // Purge files ONLY if the SID is NOT for the current // user. Here's the deal... // While a user is NOT logged onto a system, their // profile can be removed and their SID recorded in // the PurgeAtNextLogoff key. The next time they log on they // get new profile data. If they're the next person to // logon following the removal of their profile, without // this check, their new profile data would be purged during // the subsequent logoff. That's bad. Therefore, we never // purge data for the user who is logging off. // PSID psid; if (ConvertStringSidToSid(szValue, &psid)) { if (!IsSidCurrentUser(psid)) { CCachePurgerSel sel; sel.SetFlags(PURGE_FLAG_ALL); sel.SetUserSid(psid); CCachePurger purger(sel, NULL, NULL); purger.Delete(); } LocalFree(psid); } iValue++; cchValue = ARRAYSIZE(szValue); } RegCloseKey(hkey); RegDeleteKey(hkeyNetcache, c_szPurgeAtNextLogoff); } RegCloseKey(hkeyNetcache); } } // // This is called when the CSC hidden window is first created // which occurs at logon. It's just a general bucket to group the // things that need to happen each logon. // void HandleLogonTasks( void ) { InitCacheSize(); // // Apply any necessary policies. // ApplyCacheEncryptionPolicy(); ApplyCscSyncAtLogonAndLogoffPolicies(); ApplyAdminFolderPolicy(); } // // This is called when the CSC Agent (running in the winlogon process) // tells us to uninitialize the CSC UI. This happens when the user // is logging off. // void HandleLogoffTasks( void ) { CConfig& config = CConfig::GetSingleton(); DeleteFilesCachedForObsoleteProfiles(); if (config.PurgeAtLogoff()) { // // If policy says to "purge all files cached by this user" // delete offline-copy of all files cached by the current user. // Respects access bits in files so that we don't delete something // that is only used by some other user. This is the same // behavior obtained via the "Delete Files..." button or the // disk cleaner. Note that the UI callback ptr arg to the purger // ctor is NULL and we don't run through a "scan" phase. This code // is run while the user is logging off so we don't display any // UI. // // Note that the policy can also indicate if this purge operation // is for auto-cached files only. // DWORD dwPurgeFlags = PURGE_FLAG_UNPINNED; if (!config.PurgeOnlyAutoCachedFilesAtLogoff()) { dwPurgeFlags |= PURGE_FLAG_PINNED; } CCachePurgerSel sel; sel.SetFlags(dwPurgeFlags); CCachePurger purger(sel, NULL, NULL); purger.Delete(); } // // IMPORTANT: We do any purging before registering for sync-at-logon/logoff. // This is because we only register if we have something in // the cache. Purging might remove all our cached items negating // the need to register for synchronization. // ApplyCscSyncAtLogonAndLogoffPolicies(); // // Is this the first time this user has used run CSCUI? // if (!IsSyncMgrInitialized()) { CSCCACHESTATS cs; CSCGETSTATSINFO si = { SSEF_NONE, SSUF_TOTAL, false, false }; if (_GetCacheStatisticsForUser(&si, &cs) && 0 < cs.cTotal) { // // This is the first time this user has logged off with // something in the cache. Since SyncMgr doesn't turn on sync-at-logon/logoff // out of the box, we do it here. This is because we want people to sync // if they have unknowingly cached files from an autocache share. // If successful the SyncMgrInitialized reg value is set to 1. // RegisterSyncMgrHandler(TRUE); const DWORD dwFlags = SYNCMGRREGISTERFLAG_CONNECT | SYNCMGRREGISTERFLAG_PENDINGDISCONNECT; if (SUCCEEDED(RegisterForSyncAtLogonAndLogoff(dwFlags, dwFlags))) { SetSyncMgrInitialized(); } } } } // // Determines the status of a share for controlling the display of the // webview in a shell folder. // // Returns one of the following codes (defined in cscuiext.h): // // CSC_SHARESTATUS_INACTIVE // CSC_SHARESTATUS_ONLINE // CSC_SHARESTATUS_OFFLINE // CSC_SHARESTATUS_SERVERBACK // CSC_SHARESTATUS_DIRTYCACHE // LRESULT GetShareStatusForWebView( CStateMachine *pSM, LPCTSTR pszShare ) { LRESULT lResult = CSC_SHARESTATUS_INACTIVE; if (NULL != pszShare && IsCSCEnabled()) { DWORD dwStatus; if (CSCQueryFileStatus(pszShare, &dwStatus, NULL, NULL)) { if ((dwStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) != FLAG_CSC_SHARE_STATUS_NO_CACHING) { const DWORD fExclude = SSEF_LOCAL_DELETED | SSEF_DIRECTORY; CSCSHARESTATS stats; CSCGETSTATSINFO gsi = { fExclude, SSUF_MODIFIED, true, false }; lResult = CSC_SHARESTATUS_ONLINE; if (_GetShareStatisticsForUser(pszShare, &gsi, &stats)) { if (stats.bOffline) { if (IsServerBack(pSM, pszShare)) lResult = CSC_SHARESTATUS_SERVERBACK; else lResult = CSC_SHARESTATUS_OFFLINE; } else { if (0 < stats.cModified) lResult = CSC_SHARESTATUS_DIRTYCACHE; } } } } } return lResult; } //----------------------------------------------------------------------------- // Sync at Suspend/Hibernate. // // We synchronize the cache on a separate thread. Why use a separate // thread? // // 1. We respond to WM_POWERBROADCAST. // // 2. WM_POWERBROADCAST is sent by win32k.sys using SendMessage. // // 3. As part of the sync we invoke SyncMgr which involves some // COM operations. COM doesn't allow certain operations if they occur // on a thread that is currently inside an interprocess SendMessage. // This causes a call to CoCreateInstance inside mobsync.dll to fail // with the error RPC_E_CANTCALLOUT_ININPUTSYNCCALL. // // 4. The solution is to place the synchronization (and COM) activity // on a separate thread and to allow the thread servicing WM_POWERBROADCAST // to process messages. // // When suspending, the main thread servicing WM_POWERBROADCAST blocks // until the entire synchronization is complete. This is necessary to ensure // the synchronization completes before the machine is shut down. // // // // The synchronization thread procedure for syncing on suspend/hibernate. // DWORD WINAPI SuspendSync_ThreadProc( LPVOID pvParam // DWORD_PTR holding CSC update flags. ) { TraceEnter(TRACE_CSCST, "SuspendSync_ThreadProc"); const DWORD dwFlags = PtrToUint(pvParam); Trace((TEXT("Calling CscUpdateCache with flags 0x%08X"), dwFlags)); const HRESULT hr = CscUpdateCache(dwFlags); TraceLeaveResult(hr); } // // Waits on a single object while handling thread messages during the wait. // Returns the result from MsgWaitForMultipleObjectsEx. // DWORD WaitAndProcessThreadMessages( HANDLE hObject // Handle for a Win32 synchronization object. ) { TraceEnter(TRACE_CSCST, "WaitAndProcessThreadMessages"); DWORD dwResult = WAIT_FAILED; BOOL bQuit = FALSE; while(!bQuit) { TraceMsg("Waiting for message or signaled object..."); dwResult = MsgWaitForMultipleObjectsEx(1, &hObject, INFINITE, QS_ALLEVENTS, MWMO_INPUTAVAILABLE); // // A message was received. Handle it. // if (WAIT_OBJECT_0 + 1 == dwResult) { MSG msg; while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { Trace((TEXT("Rcvd message %d"), msg.message)); if (WM_QUIT == msg.message) { bQuit = TRUE; } else { TranslateMessage(&msg); DispatchMessage(&msg); } } } else { // // Any other result ends the loop. // bQuit = TRUE; if (WAIT_OBJECT_0 == dwResult) { TraceMsg("Object signaled"); } else if (WAIT_FAILED == dwResult) { Trace((TEXT("Wait failed with error %d"), GetLastError())); } } } TraceLeaveValue(dwResult); } // // Gets the sync action (quick vs. full) from user preference and/or // system policy. If either there is no preference/policy defined // or we found an invalid preference/policy value in the registry, // we return eSyncNone as a default. // // Returns: // CConfig::eSyncPartial - quick sync. // CConfig::eSyncFull - full sync. // CConfig::eSyncNone - invalid or missing reg info. // CConfig::SyncAction GetSuspendSyncAction( void ) { TraceEnter(TRACE_CSCST, "GetSuspendSyncAction"); CConfig::SyncAction action = CConfig::eSyncNone; HRESULT hr = TS_MultipleSessions(); if (S_FALSE == hr) { action = (CConfig::SyncAction)CConfig::GetSingleton().SyncAtSuspend(); if (CConfig::eSyncPartial != action && CConfig::eSyncFull != action) { // // Either someone poked an invalid value into the registry // or there is no preference/policy registered for this parameter. // Either way, we want to NOT sync. // action = CConfig::eSyncNone; } } else if (S_OK == hr) { Trace((TEXT("Multiple sessions prevent synchronization."))); } Trace((TEXT("Action = %d"), int(action))); TraceLeaveValue(action); } // // Retrieves the set of flags to pass to CscUpdateCache configured // for a given suspend operation. // // Returns: // true - Ok to sync. CscUpdateCache flags are in *pdwFlags. // false - Don't sync. Sync action is eSyndNone. // bool IsSuspendSyncRequired( bool bOkToPromptUser, DWORD *pdwCscUpdateFlags // optional. Can be NULL. ) { TraceEnter(TRACE_CSCST, "IsSuspendSyncRequired"); DWORD dwFlags = 0; const CConfig::SyncAction action = GetSuspendSyncAction(); if (bOkToPromptUser && CConfig::eSyncNone != action) { dwFlags = CSC_UPDATE_STARTNOW | CSC_UPDATE_FILL_QUICK; if (CConfig::eSyncFull == action) { dwFlags |= (CSC_UPDATE_REINT | CSC_UPDATE_FILL_ALL); } Trace((TEXT("%s sync is required. CscUpdate flags = 0x%08X"), CConfig::eSyncFull == action ? TEXT("FULL") : TEXT("QUICK"), dwFlags)); } else { TraceMsg("No sync is required"); } if (NULL != pdwCscUpdateFlags) { *pdwCscUpdateFlags = dwFlags; } TraceLeaveValue(0 != dwFlags); } // // This function creates the sync thread, and waits for the sync operation // to complete if required. It returns the result returned by CscUpdateCache. // LRESULT SyncOnSuspend( DWORD dwCscUpdateFlags ) { TraceEnter(TRACE_CSCST, "SyncOnSuspend"); HRESULT hrSyncResult = E_FAIL; // // Run the synchronization on a separate thread. // See the comment above SuspendSync_ThreadProc for details. // // Need to create the event object BEFORE we create the sync thread // so that the object exists before the sync starts. Only if this // named event object exists will the CCscUpdate code signal the // event when the operation is complete. // HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, c_szSyncCompleteEvent); if (NULL != hEvent) { HANDLE hThread = CreateThread(NULL, 0, SuspendSync_ThreadProc, UIntToPtr(dwCscUpdateFlags), 0, NULL); if (NULL != hThread) { // // Wait for the sync thread to complete. This just means the call // to CscUpdateCache has completed. We need to wait so we can // retrieve the result from CscUpdateCache through the thread's // exit code. // SyncMgr will continue the sync from the mobsync.exe process // after the thread has terminated. // TraceMsg("Waiting for CscUpdateCache to complete..."); WaitAndProcessThreadMessages(hThread); // // The thread's exit code is the HRESULT returned by CscUpdateCache. // DWORD dwThreadExitCode = (DWORD)E_FAIL; GetExitCodeThread(hThread, &dwThreadExitCode); hrSyncResult = dwThreadExitCode; // // We're done with the thread object. // CloseHandle(hThread); hThread = NULL; if (SUCCEEDED(hrSyncResult)) { // // The sync was successfully started and we're syncing prior to a // suspend operation. Need to wait 'til the sync is complete so that // we block the return to WM_POWERBROADCAST (PBT_APMQUERYSUSPEND). // TraceMsg("Waiting for sync (mobsync.exe) to complete..."); WaitAndProcessThreadMessages(hEvent); } } else { const DWORD dwErr = GetLastError(); hrSyncResult = HRESULT_FROM_WIN32(dwErr); Trace((TEXT("Sync thread creation failed with error %d"), dwErr)); } CloseHandle(hEvent); hEvent = NULL; } else { const DWORD dwErr = GetLastError(); hrSyncResult = HRESULT_FROM_WIN32(dwErr); Trace((TEXT("Sync event creation failed with error %d"), dwErr)); } if (FAILED(hrSyncResult)) { CscuiEventLog log; log.Push(hrSyncResult, CEventLog::eFmtHex); log.ReportEvent(EVENTLOG_ERROR_TYPE, MSG_E_SUSPEND_SYNCFAILED, 0); } TraceLeaveResult(hrSyncResult); } // // Handles synchronization on suspend/hibernate. // Note that we do not support sync on resume. We've // determined that the behavior is not compelling. It is // better to resume and let our normal UI processing // handle any network reconnections in the normal way. // LRESULT HandleSuspendSync( CStateMachine *pSysTraySM, HWND hWnd, bool bOkToPromptUser ) { TraceEnter(TRACE_CSCST, "HandleSuspendSync"); Trace((TEXT("\tbOkToPromptUser = %d"), bOkToPromptUser)); LRESULT lResult = ERROR_SUCCESS; BOOL bNoNet = FALSE; CSCIsServerOffline(NULL, &bNoNet); if (bNoNet) { TraceMsg("No sync performed. Network not available."); CscuiEventLog log; log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SUSPEND_NONET_NOSYNC, 2); } else { // // Determine if we're supposed to sync or not. // If so, we get the flags to pass to CscUpdateCache that control the // sync behavior. // DWORD dwFlags = 0; if (IsSuspendSyncRequired(bOkToPromptUser, &dwFlags)) { lResult = SyncOnSuspend(dwFlags); } } TraceLeaveValue(lResult); } // // Handle any tasks that occur when the computer hibernates or is suspended. // LRESULT HandleSuspendTasks( CStateMachine *pSysTraySM, HWND hWnd, bool bOkToPromptUser ) { return HandleSuspendSync(pSysTraySM, hWnd, bOkToPromptUser); } #ifdef DEBUG // // Returns address of string corresponding to a PBT_XXXXXXX code // sent in a WM_POWERBROADCAST message. // Used for debug output only. // LPCTSTR ApmCodeName(WPARAM code) { static const TCHAR szUnknown[] = TEXT(""); static const struct { WPARAM code; LPCTSTR pszName; } rgMap[] = { { PBT_APMBATTERYLOW, TEXT("PBT_APMBATTERYLOW") }, { PBT_APMOEMEVENT, TEXT("PBT_APMOEMEVENT") }, { PBT_APMPOWERSTATUSCHANGE, TEXT("PBT_APMPOWERSTATUSCHANGE") }, { PBT_APMQUERYSUSPEND, TEXT("PBT_APMQUERYSUSPEND") }, { PBT_APMQUERYSUSPENDFAILED, TEXT("PBT_APMQUERYSUSPENDFAILED") }, { PBT_APMRESUMEAUTOMATIC, TEXT("PBT_APMRESUMEAUTOMATIC") }, { PBT_APMRESUMECRITICAL, TEXT("PBT_APMRESUMECRITICAL") }, { PBT_APMRESUMESUSPEND, TEXT("PBT_APMRESUMESUSPEND") }, { PBT_APMSUSPEND, TEXT("PBT_APMSUSPEND") } }; for (int i = 0; i < ARRAYSIZE(rgMap); i++) { if (rgMap[i].code == code) { return rgMap[i].pszName; } } return szUnknown; } #endif // // Handle WM_POWERBROADCAST message. // We handle this message so that we can synchronize when the computer // hibernates/suspends. // LRESULT OnPowerBroadcast( CStateMachine *pSysTraySM, HWND hWnd, WPARAM wParam, LPARAM lParam ) { Trace((TEXT("OnPowerBroadcast %s (%d), lParam = 0x%08X"), ApmCodeName(wParam), wParam, lParam)); LRESULT lResult = TRUE; switch(wParam) { case PBT_APMQUERYSUSPEND: // Ok to suspend/hibernate? { const bool bOkToPromptUser = (0 != (1 & lParam)); HandleSuspendTasks(pSysTraySM, hWnd, bOkToPromptUser); // // Note that we never return BROADCAST_QUERY_DENY. // Therefore, we always approve the suspend. // } break; // // The remaining PBT_APMXXXXX codes are included here to show that // all were considered and explicitly not handled. // case PBT_APMRESUMESUSPEND: // Resuming from a previous suspend/hibernate.. case PBT_APMBATTERYLOW: // Battery getting low. case PBT_APMOEMEVENT: // Special OEM events. case PBT_APMPOWERSTATUSCHANGE: // Power switched (i.e. from AC -> battery) case PBT_APMQUERYSUSPENDFAILED: // Some process denied a suspend request. case PBT_APMRESUMEAUTOMATIC: // Resuming. Likely no user available. case PBT_APMRESUMECRITICAL: // Resuming from critical event (i.e. low battery). case PBT_APMSUSPEND: // System is suspending now. default: break; } return lResult; } // // This device-change code is an experiment to see what // WM_DEVICECHANGE activity we can receive while docking and // undocking a portable machine. If we decide to not use // any of this, just delete it. Note there are several // sections of code that use this conditional compilation. // [brianau - 12/23/98] // #ifdef REPORT_DEVICE_CHANGES DWORD RegisterForDeviceNotifications( HWND hwndNotify ) { DWORD dwResult = ERROR_SUCCESS; DEV_BROADCAST_DEVICEINTERFACE dbdi; ZeroMemory(&dbdi, sizeof(dbdi)); dbdi.dbcc_size = sizeof(dbdi); dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; dbdi.dbcc_classguid = GUID_DEVNODE_CHANGE; g_hDevNotify = RegisterDeviceNotification(hwndNotify, &dbdi, DEVICE_NOTIFY_WINDOW_HANDLE); if (NULL == g_hDevNotify) dwResult = GetLastError(); return dwResult; } void UnregisterForDeviceNotifications( void ) { if (NULL != g_hDevNotify) { UnregisterDeviceNotification(g_hDevNotify); g_hDevNotify = NULL; } } void OnDeviceChange( WPARAM wParam, LPARAM lParam ) { PDEV_BROADCAST_DEVICEINTERFACE pdbdi = (PDEV_BROADCAST_DEVICEINTERFACE)lParam; TCHAR szNull[] = TEXT(""); LPCTSTR pszName = pdbdi ? pdbdi->dbcc_name : szNull; switch(wParam) { case DBT_DEVICEARRIVAL: STDBGOUT((3, TEXT("Device Arrival for : \"%s\""), pszName)); break; case DBT_DEVICEREMOVEPENDING: STDBGOUT((3, TEXT("Device Remove pending for \"%s\""), pszName)); break; case DBT_DEVICEREMOVECOMPLETE: STDBGOUT((3, TEXT("Device Removal complete for \"%s\""), pszName)); break; case DBT_DEVICEQUERYREMOVE: STDBGOUT((3, TEXT("Device query remove for \"%s\""), pszName)); break; case DBT_DEVICEQUERYREMOVEFAILED: STDBGOUT((3, TEXT("Device query remove FAILED for \"%s\""), pszName)); break; case DBT_DEVICETYPESPECIFIC: STDBGOUT((3, TEXT("Device type specific for \"%s\""), pszName)); break; case DBT_QUERYCHANGECONFIG: STDBGOUT((3, TEXT("Query change config for \"%s\""), pszName)); break; case DBT_CONFIGCHANGED: STDBGOUT((3, TEXT("Config changed for \"%s\""), pszName)); break; default: STDBGOUT((3, TEXT("Unknown device notification %d"), wParam)); break; } } #endif // REPORT_DEVICE_CHANGES LRESULT CALLBACK _HiddenWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT lResult = 0; CStateMachine *pSysTraySM = (CStateMachine*)GetWindowLongPtr(hWnd, GWLP_USERDATA); TraceEnter(TRACE_CSCST, "_HiddenWndProc"); switch(uMsg) { case WM_CREATE: DllAddRef(); #if DBG CreateWindow(TEXT("listbox"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT, 0,0,0,0, hWnd, (HMENU)IDC_DEBUG_LIST, g_hInstance, NULL); #endif #ifdef REPORT_DEVICE_CHANGES RegisterForDeviceNotifications(hWnd); #endif { BOOL bNoNet = FALSE; // Check whether the entire net is offline or not if (!CSCIsServerOffline(NULL, &bNoNet)) bNoNet = TRUE; // RDR is dead, so net is down pSysTraySM = new CStateMachine(boolify(bNoNet)); if (!pSysTraySM) TraceLeaveValue((LRESULT)-1); SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pSysTraySM); if (bNoNet) { // Set initial status to NoNet PostMessage(hWnd, CSCWM_UPDATESTATUS, STWM_CSCNETDOWN, 0); } else { // // Calculate initial status as if the logon sync has just // completed. // // There's a race condition, so we can't count on getting // this message from the logon sync. If logon sync is still // proceeding, we will get another CSCWM_DONESYNCING which // is OK. // PostMessage(hWnd, CSCWM_DONESYNCING, 0, 0); } } // // Handle several things that happen at logon. // PostMessage(hWnd, PWM_HANDLE_LOGON_TASKS, 0, 0); // // This event is used to terminate any threads when the // hidden notification window is destroyed. // if (NULL == g_heventTerminate) g_heventTerminate = CreateEvent(NULL, TRUE, FALSE, NULL); // // This mutex is used to ensure only one admin-pin operation // is running at a time. // if (NULL == g_hmutexAdminPin) g_hmutexAdminPin = CreateMutex(NULL, FALSE, NULL); break; case PWM_TRAYCALLBACK: STDBGOUT((4, TEXT("PWM_TRAYCALLBACK, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam)); lResult = _Notify(hWnd, wParam, lParam); break; #ifdef REPORT_DEVICE_CHANGES case WM_DEVICECHANGE: OnDeviceChange(wParam, lParam); break; #endif // REPORT_DEVICE_CHANGES case WM_ENDSESSION: TraceMsg("_HiddenWndProc: Received WM_ENDSESSION."); if (NULL != g_heventTerminate) { // // This will tell any threads that they should // exit asap. // SetEvent(g_heventTerminate); } break; case WM_DESTROY: TraceMsg("_HiddenWndProc: hidden window destroyed"); #ifdef REPORT_DEVICE_CHANGES UnregisterForDeviceNotifications(); #endif delete pSysTraySM; pSysTraySM = NULL; SetWindowLongPtr(hWnd, GWLP_USERDATA, 0); if (NULL != g_heventTerminate) { // // This will tell any threads that they should // exit asap. // SetEvent(g_heventTerminate); } DllRelease(); break; case WM_COPYDATA: { // Warning: STDBGOUT here (inside WM_COPYDATA, outside the switch // statement) would cause an infinite loop of WM_COPYDATA messages // and blow the stack. PCOPYDATASTRUCT pcds = (PCOPYDATASTRUCT)lParam; if (pcds) { switch (pcds->dwData) { case STWM_CSCNETUP: case STWM_CSCNETDOWN: case STWM_CACHE_CORRUPTED: { LPTSTR pszServer = NULL; // // WM_COPYDATA is always sent, not posted, so copy the data // and post a message to do the work asynchronously. // This allocated string will be freed by the CSCWM_UPDATESTATUS // handler UpdateStatus(). No need to free it here. // STDBGOUT((3, TEXT("Rcvd WM_COPYDATA, uMsg = 0x%08X, server = \"%s\""), pcds->dwData, pcds->lpData)); LocalAllocString(&pszServer, (LPTSTR)pcds->lpData); PostMessage(hWnd, CSCWM_UPDATESTATUS, pcds->dwData, (LPARAM)pszServer); } break; case CSCWM_GETSHARESTATUS: // This one comes from outside of cscui.dll, and // is always UNICODE. if (pcds->lpData) { TCHAR szShare[MAX_PATH]; SHUnicodeToTChar((LPWSTR)pcds->lpData, szShare, ARRAYSIZE(szShare)); STDBGOUT((3, TEXT("Rcvd CSCWM_GETSHARESTATUS for \"%s\""), szShare)); lResult = GetShareStatusForWebView(pSysTraySM, szShare); } break; case PWM_REFRESH_SHELL: STDBGOUT((3, TEXT("Rcvd WM_COPYDATA, PWM_REFRESH_SHELL, server = \"%s\""), pcds->lpData)); if (pcds->lpData) { LPTSTR pszServer = NULL; LocalAllocString(&pszServer, (LPTSTR)pcds->lpData); PostMessage(hWnd, PWM_REFRESH_SHELL, 0, (LPARAM)pszServer); } break; #if DBG // // The following messages in the "#if DBG" block are to support the // monitoring feature of the hidden systray window. // case PWM_STDBGOUT: // Warning: no STDBGOUT here STDebugOnLogEvent(GetDlgItem(hWnd, IDC_DEBUG_LIST), (LPCTSTR)pcds->lpData); break; #endif // DBG } } } break; case CSCWM_ISSERVERBACK: STDBGOUT((2, TEXT("Rcvd CSCWM_ISSERVERBACK"))); lResult = IsServerBack(pSysTraySM, (LPCTSTR)lParam); break; case CSCWM_DONESYNCING: STDBGOUT((1, TEXT("Rcvd CSCWM_DONESYNCING. wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam)); pSysTraySM->PingServers(); UpdateStatus(pSysTraySM, hWnd, STWM_STATUSCHECK, NULL); break; case CSCWM_UPDATESTATUS: UpdateStatus(pSysTraySM, hWnd, (UINT)wParam, (LPTSTR)lParam); if (lParam) LocalFree((LPTSTR)lParam); // We make a copy when we get WM_COPYDATA break; case PWM_RESET_REMINDERTIMER: STDBGOUT((2, TEXT("Rcvd PWM_RESET_REMINDERTIMER"))); OnResetReminderTimer(); break; case PWM_HANDLE_LOGON_TASKS: HandleLogonTasks(); break; case PWM_REFRESH_SHELL: STDBGOUT((3, TEXT("Rcvd PWM_REFRESH_SHELL, server = \"%s\""), (LPCTSTR)lParam)); _RefreshAllExplorerWindows((LPCTSTR)lParam); // // lParam is a server name allocated with LocalAlloc. // if (lParam) LocalFree((LPTSTR)lParam); break; case CSCWM_VIEWFILES: COfflineFilesFolder::Open(); break; case PWM_STATUSDLG: ShowCSCUIStatusDlg(hWnd); break; case PWM_QUERY_UISTATE: lResult = OnQueryUIState(); break; case CSCWM_SYNCHRONIZE: STDBGOUT((1, TEXT("Rcvd CSCWM_SYNCHRONIZE"))); OnSynchronize(); break; case CSCWM_SETTINGS: COfflineFilesSheet::CreateAndRun(g_hInstance, GetDesktopWindow(), &g_cRefCount); break; case WM_WININICHANGE: STDBGOUT((1, TEXT("Rcvd WM_WININICHANGE. wParam = %d, lParam = \"%s\""), wParam, lParam ? (LPCTSTR)lParam : TEXT(""))); // // Let the tray UI thread respond to a possible change in caret // blink rate. // CSysTrayUI::GetInstance().OnWinIniChange((LPCTSTR)lParam); if (!lstrcmpi((LPTSTR)lParam, c_szPolicy)) { // // Post a message to ourselves so we get the policy processing off // of the calling thread. Otherwise COM will fail because this // message is SENT by userenv as an inter-process SendMessage. // PostMessage(hWnd, PWM_HANDLE_POLICY_CHANGE, 0, 0); } break; case PWM_HANDLE_POLICY_CHANGE: OnPolicyChange(); break; case WM_POWERBROADCAST: lResult = OnPowerBroadcast(pSysTraySM, hWnd, wParam, lParam); break; #if DBG // // The following messages in the "#if DBG" block are to support the // monitoring feature of the hidden systray window. // case WM_GETDLGCODE: lResult = DLGC_WANTALLKEYS; break; case WM_VKEYTOITEM: wParam = LOWORD(wParam); // Extract the virtual key code. // // Fall through. // case WM_KEYDOWN: if (0x8000 & GetAsyncKeyState(VK_CONTROL)) { if (TEXT('S') == wParam || TEXT('s') == wParam) { // // Ctrl-S saves the contents to a file. // STDebugSaveListboxContent(hWnd); } else if (TEXT('U') == wParam || TEXT('u') == wParam) { // // Ctrl-U forces an update to match the current cache state. // UpdateStatus(pSysTraySM, hWnd, STWM_STATUSCHECK, NULL); } else if (TEXT('B') == wParam || TEXT('b') == wParam) { // // Ctrl-B pings offline servers to see if they are back. // pSysTraySM->PingServers(); } else if (TEXT('P') == wParam || TEXT('p') == wParam) { // // Ctrl-P triggers the policy code // PostMessage(hWnd, PWM_HANDLE_POLICY_CHANGE, 0, 0); } } else if (VK_DELETE == wParam) { // // [Delete] clears the contents of the listbox. // if (IDOK == MessageBox(hWnd, TEXT("Clear the list?"), STR_CSCHIDDENWND_TITLE, MB_OKCANCEL)) { SendDlgItemMessage(hWnd, IDC_DEBUG_LIST, LB_RESETCONTENT, 0, 0); } } lResult = (WM_VKEYTOITEM == uMsg) ? -1 : 0; break; case WM_SIZE: { RECT rc; GetClientRect(hWnd, &rc); SetWindowPos(GetDlgItem(hWnd, IDC_DEBUG_LIST), NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER); } break; #endif // DBG default: lResult = DefWindowProc(hWnd, uMsg, wParam, lParam); break; } TraceLeaveValue(lResult); } HWND _CreateHiddenWnd(void) { WNDCLASS wc; HWND hwnd; DWORD dwStyle = WS_OVERLAPPED; TraceEnter(TRACE_CSCST, "_CreateHiddenWnd"); GetClassInfo(NULL, WC_DIALOG, &wc); wc.style |= CS_NOCLOSE; wc.lpfnWndProc = _HiddenWndProc; wc.hInstance = g_hInstance; wc.lpszClassName = STR_CSCHIDDENWND_CLASSNAME; RegisterClass(&wc); #if DBG if (0 < STDebugLevel()) { // This includes WS_CAPTION, which turns on theming, so we // only want this when the window is visible. dwStyle = WS_OVERLAPPEDWINDOW; } #endif // DBG // // Note that we can't use HWND_MESSAGE as the parent since we need // to receive certain broadcast messages. // hwnd = CreateWindow(STR_CSCHIDDENWND_CLASSNAME, NULL, dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, g_hInstance, NULL); if (hwnd) { #if DBG // // In debug builds, if registry is set up to display // systray debug output, create the CSCUI "hidden" window // as visible. // if (0 < STDebugLevel()) { ShowWindow(hwnd, SW_NORMAL); UpdateWindow(hwnd); } #endif // DBG } else { Trace((TEXT("CSCSysTrayThreadProc: CreateWindow failed GLE: %Xh"), GetLastError())); } TraceLeaveValue(hwnd); } HWND _FindNotificationWindow() { g_hWndNotification = FindWindow(STR_CSCHIDDENWND_CLASSNAME, NULL); return g_hWndNotification; } BOOL _CheckNotificationWindow() { SetLastError(ERROR_SUCCESS); if (!IsWindow(g_hWndNotification)) { // Search for the window and try again _FindNotificationWindow(); if (!IsWindow(g_hWndNotification)) { SetLastError(ERROR_INVALID_WINDOW_HANDLE); return FALSE; } } return TRUE; } BOOL PostToSystray( UINT uMsg, WPARAM wParam, LPARAM lParam ) { if (_CheckNotificationWindow()) { return PostMessage(g_hWndNotification, uMsg, wParam, lParam); } return FALSE; } #define SYSTRAY_MSG_TIMEOUT 10000 LRESULT SendToSystray( UINT uMsg, WPARAM wParam, LPARAM lParam ) { DWORD_PTR dwResult = 0; if (_CheckNotificationWindow()) { SendMessageTimeout(g_hWndNotification, uMsg, wParam, lParam, SMTO_ABORTIFHUNG, SYSTRAY_MSG_TIMEOUT, &dwResult); } return dwResult; } LRESULT SendCopyDataToSystray(DWORD dwData, DWORD cbData, PVOID pData) { COPYDATASTRUCT cds; cds.dwData = dwData; cds.cbData = cbData; cds.lpData = pData; return SendToSystray(WM_COPYDATA, 0, (LPARAM)&cds); } STDAPI_(HWND) CSCUIInitialize(HANDLE hToken, DWORD dwFlags) { TraceEnter(TRACE_CSCST, "CSCUIInitialize"); _FindNotificationWindow(); // // We get initialization and shutdown messages from cscdll and also // from the systray code in stobject.dll. // // Cscdll calls from within the winlogon process, at logon and logoff. // At logon, cscdll provides us with the user's token, which we duplicate // for accessing HKEY_CURRENT_USER (in OnQueryNetDown and at logoff). // // The systray code calls from within the explorer process, at explorer // load and unload (usually just after logon and just before logoff). // This is where we create and destroy our hidden window. // // Note: At one time we kept certain registry keys open (cached). This // caused problems at logoff since the profile couldn't be saved until // all reg keys were closed. Think carefully before deciding to hold // any reg keys open. // if (dwFlags & CI_INITIALIZE) { if (hToken) { DuplicateToken(hToken, SecurityImpersonation, &g_hToken); Trace((TEXT("CSCUIInitialize: Using new token handle:%Xh"), g_hToken)); } if (dwFlags & CI_CREATEWINDOW) { BOOL bCSCEnabled = IsCSCEnabled(); // // The CI_CREATEWINDOW bit is set by systray/explorer // if (!bCSCEnabled || CConfig::GetSingleton().NoCacheViewer()) { // // If CSC is currently disabled, or system policy prevents the // user from viewing the cache contents, remove the Offline Files // folder shortcut from the user's desktop. // DeleteOfflineFilesFolderLink_PerfSensitive(); } if (g_hWndNotification) { Trace((TEXT("CSCUIInitialize: returning existing hWnd:%Xh"), g_hWndNotification)); } else if (!bCSCEnabled) { ExitGracefully(g_hWndNotification, NULL, "CSCUIInitialize: CSC not enabled"); } else { g_hWndNotification = _CreateHiddenWnd(); Trace((TEXT("CSCUIInitialize: Created new hWnd:%Xh"), g_hWndNotification)); } } } else if (dwFlags & CI_TERMINATE) { if (dwFlags & CI_DESTROYWINDOW) { // // The CI_DESTROYWINDOW bit is set by systray.exe // if (g_hWndNotification) { TraceMsg("CSCUIInitialize: Destroying hidden window"); DestroyWindow(g_hWndNotification); g_hWndNotification = NULL; } UnregisterClass(STR_CSCHIDDENWND_CLASSNAME, g_hInstance); } else { // // This call is a result of a logoff notification from the // CSC agent running within winlogon.exe. // if (g_hToken) { if (ImpersonateLoggedOnUser(g_hToken)) { HandleLogoffTasks(); RevertToSelf(); } } } if (g_hToken) { TraceMsg("CSCUIInitialize: Freeing token handle"); CloseHandle(g_hToken); g_hToken = NULL; } } exit_gracefully: TraceLeaveValue(g_hWndNotification); } LRESULT AttemptRasConnect( LPCTSTR pszServer ) { LRESULT lRes = LRESULT_CSCFAIL; HMODULE hMod = LoadLibrary(TEXT("rasadhlp.dll")); if (hMod) { PFNHLPNBCONNECTION pfn; pfn = (PFNHLPNBCONNECTION)GetProcAddress(hMod, (LPCSTR)"AcsHlpNbConnection"); STDBGOUT((1, TEXT("Attempting RAS connection to \"%s\""), pszServer ? pszServer : TEXT(""))); if (pfn) { if ((*pfn)(pszServer)) { STDBGOUT((1, TEXT("RAS connection successful. Action is LRESULT_CSCRETRY."))); lRes = LRESULT_CSCRETRY; } else { STDBGOUT((2, TEXT("AttemptRasConnect: AcsHlpNbConnection() failed."))); } } else { STDBGOUT((2, TEXT("AttemptRasConnect: Error %d getting addr of AcsHlpNbConnection()"), GetLastError())); } FreeLibrary(hMod); } else { STDBGOUT((2, TEXT("AttemptRasConnect: Error %d loading rasadhlp.dll. Action is LRESULT_CSCFAIL"), GetLastError())); } if (LRESULT_CSCFAIL == lRes) { STDBGOUT((1, TEXT("RAS connection failed."))); } return lRes; } ////////////////////////////////////////////////////////////////////////////// // _OnQueryNetDown // // Handler for STWM_CSCQUERYNETDOWN // // Returns: // // LRESULT_CSCFAIL - Fail the connection NT4-style. // LRESULT_CSCWORKOFFLINE - Transition this server to "offline" mode. // LRESULT_CSCRETRY - We have a RAS connection. Retry. // LRESULT OnQueryNetDown( DWORD dwAutoDialFlags, LPCTSTR pszServer ) { LRESULT lResult = LRESULT_CSCFAIL; if (CSCUI_NO_AUTODIAL != dwAutoDialFlags) { // // The server is not in the CSC database and CSCDLL wants us // to offer the USER a RAS connection. // lResult = AttemptRasConnect(pszServer); } // // CSC is not available on 'Personal' SKU. // if (!IsOS(OS_PERSONAL)) { // // lResult will be LRESULT_CSCFAIL under two conditions: // // 1. dwAutoDialFlags is CSCUI_NO_AUTODIAL so lResult has it's initial value. // 2. AttemptRasConnect() failed and returned LRESULT_CSCFAIL. // In this case we now want to determine if we really want to // fail the request or if we should transition offline. // // Also, only execute this if the server is in the cache. If not, // we don't want to go offline on the server; we just want to fail // it. // if ((LRESULT_CSCFAIL == lResult) && (CSCUI_AUTODIAL_FOR_UNCACHED_SHARE != dwAutoDialFlags)) { // // This code is called from within the winlogon process. Because // it's winlogon, there's some funky stuff going on with user tokens // and registry keys. In order to read the user preference for // "offline action" we need to temporarily impersonate the currently // logged on user. // int iAction = CConfig::eGoOfflineSilent; // default if impersonation fails. if (g_hToken) { if (ImpersonateLoggedOnUser(g_hToken)) { iAction = CConfig::GetSingleton().GoOfflineAction(pszServer); RevertToSelf(); } } switch(iAction) { case CConfig::eGoOfflineSilent: STDBGOUT((1, TEXT("Action is LRESULT_CSCWORKOFFLINE"))); lResult = LRESULT_CSCWORKOFFLINE; break; case CConfig::eGoOfflineFail: STDBGOUT((1, TEXT("Action is LRESULT_CSCFAIL"))); lResult = LRESULT_CSCFAIL; break; default: STDBGOUT((1, TEXT("Invalid action (%d), defaulting to LRESULT_CSCWORKOFFLINE"), iAction)); // // An invalid action code defaults to "work offline". // lResult = LRESULT_CSCWORKOFFLINE; break; } } } return lResult; } // // This function is typically called from the CSC Agent (cscdll) in winlogon. // The Agent asks us whether to transition offline or not, and also notifies // us of status changes (net-up, net-down, etc.). Status changes are passed // on to the hidden systray window. // // Special care must be taken to not call SendMessage back to the UI thread, // since it is possible, although unlikely, that the UI thread is hitting // the net and blocked waiting for a response from this function (deadlock). // // The debug-only STDBGOUT is exempted from the SendMessage ban. If you hit // a deadlock due to STDBGOUT, reboot and turn off SysTrayOutput. // STDAPI_(LRESULT) CSCUISetState(UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT lRes = 0; LPTSTR pszServer = (LPTSTR)lParam; if (pszServer && (IsBadStringPtr(pszServer, 48) || !*pszServer)) pszServer = NULL; switch(uMsg) { case STWM_CSCQUERYNETDOWN: STDBGOUT((1, TEXT("Rcvd STWM_CSCQUERYNETDOWN, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam)); lRes = OnQueryNetDown((DWORD)wParam, pszServer); // // HACK! This is a hack to handle the way the redirector and the CSC // agent work in the "net down" case. The CSC agent tells us // about "no net" in a CSCQUERYNETDOWN rather than a CSCNETDOWN // like I would prefer it. The problem is that the redirector // doesn't actually transition the servers to offline until // a server is touched. Therefore, when lParam == 0 // we need to first handle the "query" case to determine what to tell // the CSC agent (fail, work offline, retry etc). Then, if the // result is not "retry", we need to continue processing the message // as if it were STWM_CSCNETDOWN. [brianau] // if (LRESULT_CSCRETRY == lRes || NULL != pszServer) return lRes; uMsg = STWM_CSCNETDOWN; // // Fall through... // case STWM_CSCNETDOWN: STDBGOUT((1, TEXT("Rcvd STWM_CSCNETDOWN, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam)); CSCUI_NOTIFYHOOK((CSCH_NotifyOffline, TEXT("NetDown: %1"), pszServer ? pszServer : TEXT(""))); break; case STWM_CSCNETUP: STDBGOUT((1, TEXT("Rcvd STWM_CSCNETUP, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam)); CSCUI_NOTIFYHOOK((CSCH_NotifyAvailable, TEXT("NetBack: %1"), pszServer ? pszServer : TEXT(""))); break; case STWM_CACHE_CORRUPTED: STDBGOUT((1, TEXT("Rcvd STWM_CACHE_CORRUPTED, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam)); break; } // // If we have a server name, use WM_COPYDATA to get the data // into explorer's process. // if (pszServer) { SendCopyDataToSystray(uMsg, StringByteSize(pszServer), pszServer); } else { PostToSystray(CSCWM_UPDATESTATUS, uMsg, 0); } return lRes; } const TCHAR c_szExploreClass[] = TEXT("ExploreWClass"); const TCHAR c_szIExploreClass[] = TEXT("IEFrame"); const TCHAR c_szCabinetClass[] = TEXT("CabinetWClass"); const TCHAR c_szDesktopClass[] = TEXT(STR_DESKTOPCLASS); BOOL IsExplorerWindow(HWND hwnd) { TCHAR szClass[32]; GetClassName(hwnd, szClass, ARRAYSIZE(szClass)); if ( (lstrcmp(szClass, c_szCabinetClass) == 0) ||(lstrcmp(szClass, c_szIExploreClass) == 0) ||(lstrcmp(szClass, c_szExploreClass) == 0)) return TRUE; return FALSE; } // // IsWindowBrowsingServer determines if a given window is browsing a particular // server. The function assumes that the window is an explorer window. // If pszServer == NULL, return TRUE if the window is browsing a remote path // even if the window is not browsing this particular server. // BOOL IsWindowBrowsingServer( HWND hwnd, LPCTSTR pszServer ) { BOOL bResult = FALSE; DWORD_PTR dwResult; DWORD dwPID = GetCurrentProcessId(); const UINT uFlags = SMTO_NORMAL | SMTO_ABORTIFHUNG; if (SendMessageTimeout(hwnd, CWM_CLONEPIDL, (WPARAM)dwPID, 0L, uFlags, 5000, &dwResult)) { HANDLE hmem = (HANDLE)dwResult; if (NULL != hmem) { LPITEMIDLIST pidl = (LPITEMIDLIST)SHLockShared(hmem, dwPID); if (NULL != pidl) { TCHAR szPath[MAX_PATH]; if (SHGetPathFromIDList(pidl, szPath)) { LPTSTR pszRemotePath; if (S_OK == GetRemotePath(szPath, &pszRemotePath)) { if (NULL == pszServer) { bResult = TRUE; } else { PathStripToRoot(pszRemotePath); PathRemoveFileSpec(pszRemotePath); bResult = (0 == lstrcmpi(pszServer, pszRemotePath)); } LocalFreeString(&pszRemotePath); } } SHUnlockShared(pidl); } SHFreeShared(hmem, dwPID); } } return bResult; } void RefreshExplorerWindow(HWND hwnd, LPCTSTR pszServer) { PostMessage(hwnd, WM_COMMAND, FCIDM_REFRESH, 0L); } BOOL CALLBACK _RefreshEnum(HWND hwnd, LPARAM lParam) { LPCTSTR pszServer = (LPCTSTR)lParam; if (IsExplorerWindow(hwnd) && IsWindowBrowsingServer(hwnd, pszServer)) { STDBGOUT((2, TEXT("Refreshing explorer wnd 0x%08X for \"%s\""), hwnd, pszServer)); RefreshExplorerWindow(hwnd, pszServer); } return(TRUE); } // // _RefreshAllExplorerWindows is called by the CSC tray wnd proc // in response to a PWM_REFRESH_SHELL message. The pszServer argument // is the name of the server (i.e. "\\scratch") that has transitioned // either online or offline. The function refreshes windows that are // currently browsing the server. // // If pszServer is NULL, the function refreshes all windows browsing the net. // void _RefreshAllExplorerWindows(LPCTSTR pszServer) { // // Without initializing COM, we hit a "com not initialized" assertion // in shdcocvw when calling SHGetPathFromIDList in IsWindowBrowsingServer. // if (SUCCEEDED(CoInitialize(NULL))) { // // Note that the enumeration doesn't catch the desktop window, // but we don't care. Change notifications are working now, so // content is updated properly. We're only doing this refresh stuff // to keep WebView up-to-date with respect to online/offline state. // The desktop doesn't have that, so no need to refresh it. // EnumWindows(_RefreshEnum, (LPARAM)pszServer); CoUninitialize(); } } STDAPI_(BOOL) CSCUIMsgProcess(LPMSG pMsg) { return IsDialogMessage(g_hwndStatusDlg, pMsg); } //----------------------------------------------------------------------------- // SysTray debug monitoring code. // // // This function can run in either winlogon, systray or mobsync processes. // That's why we use WM_COPYDATA to communicate the text information. // #if DBG void STDebugOut( int iLevel, LPCTSTR pszFmt, ... ) { if (STDebugLevel() >= iLevel) { TCHAR szText[1024]; SYSTEMTIME t; GetLocalTime(&t); wsprintf(szText, TEXT("[pid %d] %02d:%02d:%02d.%03d "), GetCurrentProcessId(), t.wHour, t.wMinute, t.wSecond, t.wMilliseconds); va_list args; va_start(args, pszFmt); wvsprintf(szText + lstrlen(szText), pszFmt, args); va_end(args); COPYDATASTRUCT cds; cds.dwData = PWM_STDBGOUT; cds.cbData = StringByteSize(szText); cds.lpData = szText; SendToSystray(WM_COPYDATA, 0, (LPARAM)&cds); } } int STDebugLevel(void) { static DWORD dwMonitor = (DWORD)-1; if ((DWORD)-1 == dwMonitor) { dwMonitor = 0; HKEY hkey; DWORD dwType; DWORD cbData = sizeof(DWORD); DWORD dwStatus = STDebugOpenNetCacheKey(KEY_QUERY_VALUE, &hkey); if (ERROR_SUCCESS == dwStatus) { RegQueryValueEx(hkey, c_szSysTrayOutput, NULL, &dwType, (LPBYTE)&dwMonitor, &cbData); RegCloseKey(hkey); } } return int(dwMonitor); } // // Called in response to PWM_STDBGOUT. This occurs in the systray process only. // void STDebugOnLogEvent( HWND hwndList, LPCTSTR pszText ) { if (pszText && *pszText) { int iTop = (int)SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)pszText); SendMessage(hwndList, LB_SETTOPINDEX, iTop - 5, 0); } } typedef BOOL (WINAPI * PFNGETSAVEFILENAME)(LPOPENFILENAME); #ifdef UNICODE # define GETSAVEFILENAME "GetSaveFileNameW" #else # define GETSAVEFILENAME "GetSaveFileNameA" #endif // // This function will always run on the window's thread and in the systray process. // void STDebugSaveListboxContent( HWND hwndParent ) { static bool bSaving = false; // Re-entrancy guard. if (bSaving) return; HMODULE hModComdlg = LoadLibrary(TEXT("comdlg32")); if (NULL == hModComdlg) return; PFNGETSAVEFILENAME pfnSaveFileName = (PFNGETSAVEFILENAME)GetProcAddress(hModComdlg, GETSAVEFILENAME); if (NULL != pfnSaveFileName) { bSaving = true; TCHAR szFile[MAX_PATH] = TEXT("C:\\CSCUISystrayLog.txt"); OPENFILENAME ofn = {0}; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hwndParent; ofn.hInstance = g_hInstance; ofn.lpstrFile = szFile; ofn.nMaxFile = ARRAYSIZE(szFile); ofn.lpstrDefExt = TEXT("txt"); if ((*pfnSaveFileName)(&ofn)) { HANDLE hFile = CreateFile(szFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE != hFile) { int n = (int)SendDlgItemMessage(hwndParent, IDC_DEBUG_LIST, LB_GETCOUNT, 0, 0); TCHAR szText[MAX_PATH]; for (int i = 0; i < n; i++) { // // WARNING: This could potentially overwrite the szText[] buffer. // However, since the text should be of a readable length // in a listbox I doubt if it will exceed MAX_PATH. // SendDlgItemMessage(hwndParent, IDC_DEBUG_LIST, LB_GETTEXT, i, (LPARAM)szText); lstrcat(szText, TEXT("\r\n")); DWORD dwWritten = 0; WriteFile(hFile, szText, lstrlen(szText) * sizeof(TCHAR), &dwWritten, NULL); } CloseHandle(hFile); } else { TCHAR szMsg[MAX_PATH]; wsprintf(szMsg, TEXT("Error %d creating file \"%s\""), GetLastError(), szFile); MessageBox(hwndParent, szMsg, STR_CSCHIDDENWND_TITLE, MB_ICONERROR | MB_OK); } } } bSaving = false; FreeLibrary(hModComdlg); } DWORD STDebugOpenNetCacheKey( DWORD dwAccess, HKEY *phkey ) { DWORD dwDisposition; return RegCreateKeyEx(HKEY_LOCAL_MACHINE, REGSTR_KEY_OFFLINEFILES, 0, NULL, 0, dwAccess, NULL, phkey, &dwDisposition); } #endif // DBG