|
|
//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 1997 - 1999
//
// File: cscst.cpp
//
//--------------------------------------------------------------------------
#include "pch.h"
#pragma hdrstop
#include <shellp.h> // STR_DESKTOPCLASS
#ifdef REPORT_DEVICE_CHANGES
# include <dbt.h> // Device change notifications.
#endif // REPORT_DEVICE_CHANGES
#include <sddl.h> // For ConvertStringSidToSid
#include "cscst.h"
#include "options.h"
#include "statdlg.h" // CStatusDlg
#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 <commdlg.h>
#include <stdarg.h>
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]; // Truncation is probably OK here since we're stripping down to "\\server",
// which can only be at most 17 chars (see UNCLEN in lmcons.h).
StringCchCopy(szServer, ARRAYSIZE(szServer), pszPath); PathAddBackslash(szServer); PathStripToRoot(szServer); LPTSTR pszLastBackslash = StrRChr(szServer, szServer + lstrlen(szServer), TEXT('\\')); if (NULL != pszLastBackslash && pszLastBackslash > (szServer + 2)) *pszLastBackslash = TEXT('\0'); StringCchCopy(pszServer, cchServer, szServer); }
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)); StringCchCopy(szAutoReconnectShare, ARRAYSIZE(szAutoReconnectShare), fd.cFileName); } 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.
//
StringCchCopy(pszServer, cchServer, pServerList->Get(0)); } } }
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) { StringCchCopy(m_szServerQueued, ARRAYSIZE(m_szServerQueued), pszServer); } 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++;
StringCchCopy(m_szServer, ARRAYSIZE(m_szServer), pszServer); }
//
// 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:
//
// <Header> <Status> \n
//
// <Body>
//
// <Directive>
//
// 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); } StringCchCopy(pszText, cchText, szTemp); } } 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) { StringCchCopy(szServerName, ARRAYSIZE(szServerName), pszServer); }
//
// 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.
//
szServer[0] = TEXT('\\'); szServer[1] = TEXT('\\'); StringCchCopy(szServer+2, ARRAYSIZE(szServer)-2, 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 ) { 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 the wait fails for some reason, e.g. g_heventTerminate
// is NULL, then we will continue. Is that OK?
//
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; StringCchCopy(szLastFile, ARRAYSIZE(szLastFile), lpszName); }
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 ) { LPTSTR pszText = NULL;
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) { LoadStringAlloc(&pszText, g_hInstance, rgMap[i].idsText); 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 = NULL; FormatString(&pszTemp, pszText, pszServerName, pszServerName); if (NULL != pszTemp) { LocalFree(pszText); pszText = pszTemp; } } } break; // Break out of loop. We have what we need.
} } if (NULL != pszText) { //
// Display the dialog.
//
CStatusDlg::Create(hwndParent, pszText, state); LocalFree(pszText); } }
//
// 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); if (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("<unknown PBT code>"); 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("<null>"); 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 CreateEvent fails, the cache encryption thread will still run
// (non-interruptable), but the admin-pin thread won't run at all.
//
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 CreateMutex fails, the admin-pin code won't run.
//
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) { STDBGOUT((3, TEXT("Rcvd CSCWM_GETSHARESTATUS for \"%s\""), (LPWSTR)pcds->lpData)); lResult = GetShareStatusForWebView(pSysTraySM, (LPWSTR)pcds->lpData); } 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("<null>")));
//
// 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("<null>"))); 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 && !*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)); break;
case STWM_CSCNETUP: STDBGOUT((1, TEXT("Rcvd STWM_CSCNETUP, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam)); 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; }
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)); PostMessage(hwnd, WM_COMMAND, FCIDM_REFRESH, 0L); } 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);
wnsprintf(szText, ARRAYSIZE(szText), TEXT("[pid %d] %02d:%02d:%02d.%03d "), GetCurrentProcessId(), t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);
UINT cch = lstrlen(szText);
va_list args; va_start(args, pszFmt); wvnsprintf(szText + cch, ARRAYSIZE(szText)-cch, 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);
//
// 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, "GetSaveFileNameW"); 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); StringCchCat(szText, ARRAYSIZE(szText), TEXT("\r\n")); DWORD dwWritten = 0; WriteFile(hFile, szText, lstrlen(szText) * sizeof(TCHAR), &dwWritten, NULL); }
CloseHandle(hFile); } else { TCHAR szMsg[MAX_PATH]; wnsprintf(szMsg, ARRAYSIZE(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
|