windows-nt/Source/XPSP1/NT/shell/ext/cscui/cscst.cpp
2020-09-26 16:20:57 +08:00

4656 lines
147 KiB
C++

//+-------------------------------------------------------------------------
//
// 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 "uihooks.h" // Self-host notifications
#include "folder.h"
#include "eventlog.h"
#include "msg.h"
#include "purge.h"
#include "security.h"
#include "syncmgr.h"
#include "strings.h"
#include "termserv.h"
#if DBG
//
// This code is used to manage the hidden window when we
// unhide it and display debug output to it via STDBGOUT().
//
#include <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];
lstrcpyn(szServer, pszPath, ARRAYSIZE(szServer));
PathAddBackslash(szServer);
PathStripToRoot(szServer);
LPTSTR pszLastBackslash = StrRChr(szServer, szServer + lstrlen(szServer), TEXT('\\'));
if (NULL != pszLastBackslash && pszLastBackslash > (szServer + 2))
*pszLastBackslash = TEXT('\0');
lstrcpyn(pszServer, szServer, cchServer);
}
bool
CServerList::Add(
LPCTSTR pszServer
)
{
if (NULL != m_hdpa)
{
if (!Exists(pszServer))
{
int cchEntry = lstrlen(pszServer) + 1;
LPTSTR pszEntry = (LPTSTR)LocalAlloc(LPTR, sizeof(TCHAR) * cchEntry);
if (NULL != pszEntry)
{
GetServerFromPath(pszServer, pszEntry, cchEntry);
if (-1 != DPA_AppendPtr(m_hdpa, pszEntry))
return true;
//
// Addition to DPA failed. Delete the string buffer.
//
LocalFree(pszEntry);
}
}
}
return false;
}
void
CServerList::Remove(
LPCTSTR pszServer
)
{
int iEntry = Find(pszServer);
if (-1 != iEntry)
{
LPTSTR pszEntry = (LPTSTR)DPA_DeletePtr(m_hdpa, iEntry);
if (NULL != pszEntry)
LocalFree(pszEntry);
}
}
LPCTSTR
CServerList::Get(
int iItem
) const
{
if (NULL != m_hdpa)
return (LPCTSTR)DPA_GetPtr(m_hdpa, iItem);
return NULL;
}
int
CServerList::Count(
void
) const
{
if (NULL != m_hdpa)
return DPA_GetPtrCount(m_hdpa);
return 0;
}
//
// Locate a server name in the "pending reconnection" list.
// pszServer can either be "\\server" or "\\server\share".
//
// Returns: Index of entry if found. -1 if not found.
//
int
CServerList::Find(
LPCTSTR pszServer
)
{
TCHAR szServer[MAX_PATH];
GetServerFromPath(pszServer, szServer, ARRAYSIZE(szServer));
if (NULL != m_hdpa)
{
int cEntries = DPA_GetPtrCount(m_hdpa);
LPTSTR pszEntry;
for (int i = 0; i < cEntries; i++)
{
pszEntry = (LPTSTR)DPA_GetPtr(m_hdpa, i);
if (NULL != pszEntry)
{
if (0 == lstrcmpi(pszEntry, szServer))
return i;
}
}
}
return -1;
}
void
CServerList::Clear(
void
)
{
if (NULL != m_hdpa)
{
int cEntries = DPA_GetPtrCount(m_hdpa);
LPTSTR pszEntry;
for (int i = 0; i < cEntries; i++)
{
pszEntry = (LPTSTR)DPA_DeletePtr(m_hdpa, i);
if (NULL != pszEntry)
{
LocalFree(pszEntry);
}
}
}
}
//-----------------------------------------------------------------------------
// CStateMachine member functions.
//-----------------------------------------------------------------------------
//
// Translates a STWM_XXXXX message from the CSC agent into a systray UI state
// code. The caller also provides a buffer to a server name. If we find
// a "single server" condition in the cache (i.e. one server is dirty, one
// server is offline etc), then we write the name of this server to this
// buffer. Otherwise, the buffer remains unchanged. The goal here is to
// end up with a buffer containing the name of the applicable server when
// we have one of these one-server conditions. Ultimately, the server name
// is included in the tray balloon text message.
//
// The function returns one of the STS_XXXXX UI status codes.
//
// This function is rather long. Much longer than I like a function to be.
// I've tried to break it up into smaller pieces but any chunks were pretty
// much arbitrary. Without a good logical breakdown, that doesn't make much
// sense. Even with it's length, it's not a complex function. It merely
// enumerates shares in the cache gathering statistics along the way. From
// these statistics, it decides what the next UI state should be.
//
eSysTrayState
CStateMachine::TranslateInput(
UINT uMsg,
LPTSTR pszServer,
UINT cchServer
)
{
//
// Since this cscui code is running all the time, we don't want to keep
// a handle to the event log open. Therefore, we use this CscuiEventLog
// object to automatically close the log for us. The ReportEvent member
// of CscuiEventLog handles all initialization of the log and determining
// if the event should actually be logged (depending upon the current CSCUI
// event logging level).
//
CscuiEventLog log;
bool bServerIsBack = false;
if (STWM_CSCNETUP == uMsg)
{
m_bNoNet = false;
if (TEXT('\0') != *pszServer)
{
STDBGOUT((1, TEXT("Translating STWM_CSCNETUP for server \"%s\""), pszServer));
//
// Server reported back by the CSC agent.
// Add it's name to a persistent (in memory) list of
// servers available for reconnection.
// Also clear the "no net" flag.
//
bServerIsBack = true;
ServerPendingReconnection(pszServer);
if (log.LoggingEnabled())
{
log.Push(pszServer);
log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SERVER_AVAILABLE, 1);
}
}
else
{
STDBGOUT((1, TEXT("Translating STWM_CSCNETUP (no associated server)")));
if (log.LoggingEnabled())
{
log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_NET_STARTED, 2);
}
}
}
else if (STWM_CSCNETDOWN == uMsg)
{
//
// This is the only place where transitions from online to
// offline state are noted in the shell process. (CSCUISetState
// and OnQueryNetDown execute in WinLogon's process).
//
if (TEXT('\0') != *pszServer)
{
STDBGOUT((1, TEXT("Translating STWM_CSCNETDOWN for server \"%s\""), pszServer));
if (!m_bNoNet)
{
LPTSTR pszTemp;
if (LocalAllocString(&pszTemp, pszServer))
{
PostToSystray(PWM_REFRESH_SHELL, 0, (LPARAM)pszTemp);
}
}
//
// Server reported down by the CSC agent.
// Remove it's name from the persistent (in memory) list
// of servers available for reconnection.
//
ServerUnavailable(pszServer);
if (log.LoggingEnabled())
{
log.Push(pszServer);
log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SERVER_OFFLINE, 1);
}
}
else
{
STDBGOUT((1, TEXT("Translating STWM_CSCNETDOWN (no associated server)")));
//
// Entire network reported down by the CSC agent.
// Remove all names from the persistent (in memory) list
// of servers available for reconnection. m_bNoNet is the only persistent
// state we have. Once it is set, the only thing that can reset it
// is a STWM_CSCNETUP message from the CSC agent.
//
if (!m_bNoNet)
PostToSystray(PWM_REFRESH_SHELL, 0, 0);
m_bNoNet = true;
AllServersUnavailable();
if (log.LoggingEnabled())
{
log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_NET_STOPPED, 2);
}
}
}
else if (STWM_STATUSCHECK == uMsg)
{
STDBGOUT((1, TEXT("Translating STWM_STATUSCHECK")));
}
else if (STWM_CACHE_CORRUPTED == uMsg)
{
//
// Note: No check for LoggingEnabled(). We always log corrupted cache
// regardless of logging level.
//
STDBGOUT((1, TEXT("Translating STWM_CACHE_CORRUPTED")));
log.ReportEvent(EVENTLOG_ERROR_TYPE, MSG_E_CACHE_CORRUPTED, 0);
}
//
// If CSC is disabled or the cache is empty, the default UI state
// is "online".
//
eSysTrayState state = STS_ONLINE;
if (IsCSCEnabled())
{
DWORD dwStatus;
DWORD dwPinCount;
DWORD dwHintFlags;
WIN32_FIND_DATA fd;
FILETIME ft;
CCscFindHandle hFind;
hFind = CacheFindFirst(NULL, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft);
if (hFind.IsValid())
{
//
// We need these three temporary name lists to reconcile a problem with
// the way the CSC cache and RDR are designed. When we enumerate the cache,
// we enumerate individual shares in the cache. Each share has some condition
// (i.e. dirty, offline etc) associated with it. The problem is that the
// redirector handles things on a server basis. So when a particular share
// is offline, in reality the entire server is offline. We've decided that
// the UI should reflect things on a server (computer) basis so we need to
// avoid including the states of multiple shares from the same server in
// our totals. These three lists are used to store the names of servers
// with shares in one of the three states (offline, dirty, pending recon).
// If we enumerate a share with one of these states and find it already
// exists in the corresponding list, we don't include this share in the
// statistics.
//
int cShares = 0;
CServerList OfflineList;
CServerList DirtyList;
CServerList BackList;
//
// If a server is back, assume we can auto-reconnect it.
//
bool bAutoReconnectServer = bServerIsBack;
TCHAR szAutoReconnectShare[MAX_PATH] = {0};
DWORD dwPathSpeed = 0;
do
{
bool bShareIsOnServer = boolify(PathIsPrefix(pszServer, fd.cFileName));
bool bShareHasModifiedFiles = false;
bool bShareHasOpenFiles = false;
//
// A share participates in the systray UI calculations only if the
// share contains files OR the share is currently "offline".
// Because of the CSC database design, CSC doesn't remove a share
// entry after all it's files have been removed from the cache.
// Therefore we need this extra check to avoid including empty shares in the UI.
//
if (ShareHasFiles(fd.cFileName, &bShareHasModifiedFiles, &bShareHasOpenFiles) ||
ShareIsOffline(dwStatus))
{
cShares++;
if (bShareIsOnServer && (bShareHasModifiedFiles || bShareHasOpenFiles))
{
//
// Auto-reconnect isn't allowed if one or more shares on the server
// have open files or files modified offline. Auto-reconnection
// would put the cache into a dirty state.
//
bAutoReconnectServer = false;
}
//
// A share can be in one of 4 states:
// Online
// Dirty
// Offline
// Pending reconnection ('back')
//
// Note that our definition of Dirty implies Online, and Pending
// Reconnection implies Offline. That is, an offline share is
// never dirty and an online share is never pending reconnection.
//
//---------------------------------------------------------------------
// Is the share online?
//---------------------------------------------------------------------
if (!ShareIsOffline(dwStatus))
{
//---------------------------------------------------------------------
// Is the share dirty? (online + offline changes)
//---------------------------------------------------------------------
if (bShareHasModifiedFiles)
{
STDBGOUT((3, TEXT("Share \"%s\" is dirty (0x%08X)"), fd.cFileName, dwStatus));
DirtyList.Add(fd.cFileName);
}
else
{
STDBGOUT((3, TEXT("Share \"%s\" is online (0x%08X)"), fd.cFileName, dwStatus));
}
}
else // Offline
{
//---------------------------------------------------------------------
// Is the server back?
//---------------------------------------------------------------------
if (IsServerPendingReconnection(fd.cFileName))
{
STDBGOUT((3, TEXT("Share \"%s\" is pending reconnection (0x%08X)"), fd.cFileName, dwStatus));
BackList.Add(fd.cFileName);
}
else
{
STDBGOUT((3, TEXT("Share \"%s\" is OFFLINE (0x%08X)"), fd.cFileName, dwStatus));
OfflineList.Add(fd.cFileName);
}
}
}
if (!ShareIsOffline(dwStatus))
{
// It's online, so it can't be pending reconnection.
ServerReconnected(fd.cFileName);
// ...and there's no need to reconnect it.
if (bShareIsOnServer)
bAutoReconnectServer = false;
}
if (FLAG_CSC_SHARE_STATUS_PINNED_OFFLINE & dwStatus)
{
//
// Finally... if the user has 'forced' the share offline
// we don't allow auto-reconnection. This allows the
// user to 'tag' a share as "always offline" from an
// auto-reconnect perspective. One might do this for a
// RAS connection.
//
bAutoReconnectServer = false;
}
if (bAutoReconnectServer && bShareIsOnServer && TEXT('\0') == szAutoReconnectShare[0])
{
//
// Remember the share name for possible auto-reconnection.
// The transition API is TransitionServerOnline but it takes a share name.
// Bad choice of names (IMO) but that's the way Shishir did it in the
// CSC APIs. It can be any share on the server.
//
// However, it's possible to have defunct shares in the
// database. Try to find one that's connectable.
//
if (CSCCheckShareOnlineEx(fd.cFileName, &dwPathSpeed))
{
STDBGOUT((3, TEXT("Share \"%s\" alive at %d00 bps"), fd.cFileName, dwPathSpeed));
lstrcpyn(szAutoReconnectShare, fd.cFileName, ARRAYSIZE(szAutoReconnectShare));
}
else
{
STDBGOUT((3, TEXT("Share \"%s\" unreachable, error = %d"), fd.cFileName, GetLastError()));
}
}
}
while(CacheFindNext(hFind, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft));
if (bAutoReconnectServer)
{
//---------------------------------------------------------------------
// Handle auto-reconnection.
//---------------------------------------------------------------------
//
if (TEXT('\0') != szAutoReconnectShare[0])
{
//
// Server was reported "BACK" by the CSC agent and it has no open files
// nor files modified offline and it's not on a slow link.
// This makes it a candidate for automatic reconnection. Try it.
//
STDBGOUT((1, TEXT("Attempting to auto-reconnect \"%s\""), szAutoReconnectShare));
if (TransitionShareOnline(szAutoReconnectShare, TRUE, TRUE, dwPathSpeed))
{
//
// The server has been reconnected. Remove it's name from the
// "pending reconnection" list.
//
ServerReconnected(pszServer);
//
// Remove this server from the temporary lists we've been keeping.
//
DirtyList.Remove(pszServer);
BackList.Remove(pszServer);
OfflineList.Remove(pszServer);
if (log.LoggingEnabled())
{
log.Push(pszServer);
log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SERVER_AUTORECONNECT, 3);
}
}
}
}
int cDirty = DirtyList.Count();
int cBack = BackList.Count();
int cOffline = OfflineList.Count();
STDBGOUT((2, TEXT("Cache check server results: cShares = %d, cDirty = %d, cBack = %d, cOffline = %d"),
cShares, cDirty, cBack, cOffline));
//
// This code path is a waterfall where lower-priority states are overwritten
// by higher-priority states as they are encountered. The order of this array
// is important. It's ordered by increasing priority (no net is
// highest priority for systray UI).
//
CServerList *pServerList = NULL;
struct Criteria
{
int cnt; // Number of applicable servers found.
eSysTrayState state; // Single-item UI state.
eSysTrayState mstate; // Multi-item UI state.
CServerList *pList; // Ptr to applicable list with server names.
} rgCriteria[] = {
{ cOffline, STS_OFFLINE, STS_MOFFLINE, &OfflineList },
{ cBack, STS_SERVERBACK, STS_MSERVERBACK, &BackList },
{ cDirty, STS_DIRTY, STS_MDIRTY, &DirtyList },
{ cShares && m_bNoNet ? 1 : 0, STS_NONET, STS_NONET, NULL }
};
for (int i = 0; i < ARRAYSIZE(rgCriteria); i++)
{
Criteria& c = rgCriteria[i];
if (0 < c.cnt)
{
state = c.mstate;
if (1 == c.cnt)
{
state = c.state;
pServerList = NULL;
if (NULL != c.pList && 1 == c.pList->Count())
{
pServerList = c.pList;
}
}
}
}
if (NULL != pServerList)
{
//
// We had a single-server condition so write the server name
// to the caller's server name buffer.
// If we didn't have a single-server condition, the buffer
// remains unchanged.
//
lstrcpyn(pszServer, pServerList->Get(0), cchServer);
}
}
}
STDBGOUT((1, TEXT("Translated to SysTray UI state %s"), SysTrayStateStr(state)));
return state;
}
//
// Ping offline servers. If any are alive, update status and
// auto-reconnect them if possible. This is typically done
// after a sync operation has completed.
//
DWORD WINAPI
_PingServersThread(LPVOID /*pThreadData*/)
{
DWORD dwStatus;
WIN32_FIND_DATA fd;
HANDLE hFind;
hFind = CacheFindFirst(NULL, &fd, &dwStatus, NULL, NULL, NULL);
if (INVALID_HANDLE_VALUE != hFind)
{
CServerList BackList;
do
{
// If the tray state becomes Online or NoNet, we can quit
eSysTrayState state = (eSysTrayState)SendToSystray(PWM_QUERY_UISTATE, 0, 0);
if (STS_ONLINE == state || STS_NONET == state)
break;
// Call BackList.Exists here to avoid extra calls to
// CSCCheckShareOnline. (Add also calls Exists)
if ((FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwStatus) &&
!BackList.Exists(fd.cFileName))
{
if (!CSCCheckShareOnline(fd.cFileName))
{
DWORD dwErr = GetLastError();
if (ERROR_ACCESS_DENIED != dwErr &&
ERROR_LOGON_FAILURE != dwErr)
{
// The share is not reachable
continue;
}
// Access denied or logon failure means the server is
// reachable, but we don't have valid credentials.
}
// The share is offline but available again.
STDBGOUT((1, TEXT("Detected server back: %s"), fd.cFileName));
BackList.Add(fd.cFileName);
// Get the \\server name (minus the sharename) and
// tell ourselves that it's back.
LPCTSTR pszServer = BackList.Get(BackList.Count() - 1);
if (pszServer)
{
CSCUISetState(STWM_CSCNETUP, 0, (LPARAM)pszServer);
}
}
}
while(CacheFindNext(hFind, &fd, &dwStatus, NULL, NULL, NULL));
CSCFindClose(hFind);
}
DllRelease();
FreeLibraryAndExitThread(g_hInstance, 0);
return 0;
}
void
CStateMachine::PingServers()
{
// Don't bother trying if there's no net.
if (!m_bNoNet)
{
DWORD dwThreadID;
// Give the thread a reference to the DLL
HINSTANCE hInstThisDll = LoadLibrary(c_szDllName);
DllAddRef();
HANDLE hThread = CreateThread(NULL,
0,
_PingServersThread,
NULL,
0,
&dwThreadID);
if (hThread)
{
CloseHandle(hThread);
}
else
{
// CreateThread failed, cleanup
DllRelease();
FreeLibrary(hInstThisDll);
}
}
}
//
// Determine if a given share has files cached in the CSC cache.
//
//
bool
CStateMachine::ShareHasFiles(
LPCTSTR pszShare,
bool *pbModified,
bool *pbOpen
) const
{
//
// Exclude the following:
// 1. Directories.
// 2. Files marked as "locally deleted".
//
// NOTE: The filtering done by this function must be the same as
// in several other places throughout the CSCUI code.
// To locate these, search the source for the comment
// string CSCUI_ITEM_FILTER.
//
const DWORD fExclude = SSEF_LOCAL_DELETED |
SSEF_DIRECTORY;
//
// Stop stats enumeration when we've found all of the following:
// 1. At least one file.
// 2. At least one modified file.
// 3. At least one file with either USER access OR GUEST access.
//
const DWORD fUnity = SSUF_TOTAL |
SSUF_MODIFIED |
SSUF_ACCUSER |
SSUF_ACCGUEST |
SSUF_ACCOR;
CSCSHARESTATS ss;
CSCGETSTATSINFO si = { fExclude, fUnity, true, false };
_GetShareStatisticsForUser(pszShare, // Share name.
&si,
&ss); // Destination buffer.
if (NULL != pbModified)
{
*pbModified = (0 < ss.cModified);
}
if (NULL != pbOpen)
{
*pbOpen = ss.bOpenFiles;
}
return 0 < ss.cTotal;
}
//-----------------------------------------------------------------------------
// CSysTrayUI member functions.
//-----------------------------------------------------------------------------
//
// This is the minimum interval (in ms) allowed between state changes of
// the systray UI. A value of 0 would result in immediate updates as
// notifications are received from the CSC agent. A value of 60000 would
// cause any state changes received less than 60 seconds after the previous
// state change to be queued. 60 seconds after the previous state change,
// if a state change is queued it is applied to the systray UI.
// Something to consider is dynamically adjusting
//
const int CSysTrayUI::s_iMinStateChangeInterval = 10000; // 10 seconds.
CSysTrayUI::CSysTrayUI(
HWND hwndNotify
) : m_idFlashingTimer(0),
m_idReminderTimer(0),
m_idStateChangeTimer(0),
m_iIconFlashTime(GetCaretBlinkTime()),
m_hIconNoOverlay(s_rgIconInfo[int(STS_OFFLINE)].hIcon), // The offline icon is used
// as the non-overlay icon for
// flashing.
m_hwndNotify(hwndNotify),
m_dwFlashingExpires(0),
m_dwNextStateChange(0),
m_state(STS_ONLINE),
m_statePrev(STS_INVALID),
m_stateQueued(STS_INVALID),
m_bFlashOverlay(false),
m_bActive(false)
{
//
// Load up the required icons.
//
for (int i = 0; i < ARRAYSIZE(s_rgIconInfo); i++)
{
IconInfo& sti = s_rgIconInfo[i];
if (NULL == sti.hIcon && 0 != sti.idIcon)
{
sti.hIcon = (HICON)LoadImage(g_hInstance,
MAKEINTRESOURCE(sti.idIcon),
IMAGE_ICON,
CSC_ICON_CX,
CSC_ICON_CY,
LR_LOADMAP3DCOLORS);
if (NULL == sti.hIcon)
{
Trace((TEXT("CSCUI ERROR %d loading Icon ID = %d"), GetLastError(), sti.idIcon));
}
}
}
m_szServer[0] = TEXT('\0');
m_szServerQueued[0] = TEXT('\0');
UpdateSysTray(UF_ICON);
}
CSysTrayUI::~CSysTrayUI(
void
)
{
if (0 != m_idStateChangeTimer)
KillTimer(m_hwndNotify, m_idStateChangeTimer);
}
//
// Singleton instance access.
//
CSysTrayUI&
CSysTrayUI::GetInstance(
void
)
{
static CSysTrayUI TheUI(_FindNotificationWindow());
return TheUI;
}
//
// Change the current state of the UI to a new state.
// Returns:
// true = state was changed.
// false = state was not changed.
//
bool
CSysTrayUI::SetState(
eSysTrayState state,
LPCTSTR pszServer // Optional. Default is NULL.
)
{
bool bResult = false;
//
// Apply a state change only if the state has actually changed.
//
if (state != m_state)
{
//
// Apply a state change only if there's not a sync in progress.
// If there is a sync in progress, we'll receive a CSCWM_DONESYNCING
// message when the sync is finished which will trigger a UI update.
//
if (!::IsSyncInProgress())
{
if (0 == m_idStateChangeTimer)
{
//
// The state change timer is not active. That means it's OK
// to update the tray UI.
//
STDBGOUT((1, TEXT("Changing SysTray UI state %s -> %s"),
SysTrayStateStr(m_state),
SysTrayStateStr(state)));
m_statePrev = m_state;
m_state = state;
UpdateSysTray(eUpdateFlags(UF_ICON | UF_BALLOON), pszServer);
//
// Reset the state change timer so that we will not produce a
// visible change in the tray UI for at least another
// s_iMinStateChangeInterval milliseconds.
// Also invalidate the queued state info so that if the update timer
// expires before we queue a state change, it will be a no-op.
//
STDBGOUT((2, TEXT("Setting state change timer")));
m_stateQueued = STS_INVALID;
m_idStateChangeTimer = SetTimer(m_hwndNotify,
ID_TIMER_STATECHANGE,
s_iMinStateChangeInterval,
StateChangeTimerProc);
bResult = true;
}
else
{
//
// The state change timer is active so we can't update the tray
// UI right now. We'll queue up the state information so when the
// timer expires this state will be applied. Note that the "queue"
// is only ONE item deep. Each successive addition to the queue
// overwrites the current content.
//
STDBGOUT((2, TEXT("Queueing state change to %s."), SysTrayStateStr(state)));
m_stateQueued = state;
if (NULL != pszServer)
{
lstrcpyn(m_szServerQueued, pszServer, ARRAYSIZE(m_szServerQueued));
}
else
{
m_szServerQueued[0] = TEXT('\0');
}
}
}
else
{
STDBGOUT((2, TEXT("Sync in progress. SysTray state not changed.")));
}
}
return bResult;
}
//
// Called each time the state change timer expires.
//
VOID CALLBACK
CSysTrayUI::StateChangeTimerProc(
HWND hwnd,
UINT uMsg,
UINT_PTR idEvent,
DWORD dwTime
)
{
//
// Call a non-static function of the singleton instance so
// we have access to private members.
//
CSysTrayUI::GetInstance().OnStateChangeTimerExpired();
}
void
CSysTrayUI::OnStateChangeTimerExpired(
void
)
{
STDBGOUT((2, TEXT("State change timer expired. Queued state = %s"),
SysTrayStateStr(m_stateQueued)));
//
// Kill the timer and set it's ID to 0.
// This will let SetState() know that the timer has expired and
// it's OK to update the tray UI.
//
if (0 != m_idStateChangeTimer)
{
KillTimer(m_hwndNotify, m_idStateChangeTimer);
m_idStateChangeTimer = 0;
}
if (int(m_stateQueued) != int(STS_INVALID))
{
//
// Call SetState ONLY if queued info is valid; meaning
// there was something in the queue.
//
SetState(m_stateQueued, m_szServerQueued);
}
}
//
// On WM_WININICHANGED update the icon flash timer.
//
void
CSysTrayUI::OnWinIniChange(
LPCTSTR pszSection
)
{
m_iIconFlashTime = GetCaretBlinkTime();
KillIconFlashTimer();
UpdateSysTray(UF_FLASHICON);
}
//
// Show the reminder balloon associated with the current UI state.
//
void
CSysTrayUI::ShowReminderBalloon(
void
)
{
UpdateSysTray(eUpdateFlags(UF_BALLOON | UF_REMINDER));
}
//
// All roads lead here.
// This function is the kitchen sink for updating the systray.
// It's kind of a long function but it centralizes all changes to
// the systray. It's divided into 3 basic parts:
//
// 1. Change the tray icon. (UF_ICON)
// 2. Flash the tray icon. (UF_FLASHICON)
// 3. Display a notification balloon. (UF_BALLOON)
//
// Part or all of these can be performed in a single call depending
// upon the content of the uFlags argument.
//
void
CSysTrayUI::UpdateSysTray(
eUpdateFlags uFlags,
LPCTSTR pszServer // optional. Default is NULL.
)
{
NOTIFYICONDATA nid = {0};
if (!IsWindow(m_hwndNotify))
return;
//
// If an icon is active, we're modifying it.
// If none active, we're adding one.
//
DWORD nimsg = NIM_MODIFY;
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.uID = PWM_TRAYCALLBACK;
nid.uFlags = NIF_MESSAGE;
nid.uCallbackMessage = PWM_TRAYCALLBACK;
nid.hWnd = m_hwndNotify;
IconInfo& sti = s_rgIconInfo[int(m_state)];
if (NULL != pszServer && TEXT('\0') != *pszServer)
{
//
// Copy the name of the server to a member variable.
// Skip passed the leading "\\".
//
while(*pszServer && TEXT('\\') == *pszServer)
pszServer++;
lstrcpyn(m_szServer, pszServer, ARRAYSIZE(m_szServer));
}
//
// Change the icon --------------------------------------------------------
//
if (UF_ICON & uFlags)
{
nid.uFlags |= NIF_ICON;
if (0 == sti.idIcon)
{
//
// This state doesn't have an icon. Delete from systray.
//
nimsg = NIM_DELETE;
}
else
{
if (!m_bActive)
nimsg = NIM_ADD;
nid.hIcon = sti.hIcon;
//
// If applicable, always flash icon when first showing it.
//
uFlags = eUpdateFlags(uFlags | UF_FLASHICON);
//
// Set the tooltip.
//
nid.uFlags |= NIF_TIP;
GetTooltipText(m_state, nid.szTip, ARRAYSIZE(nid.szTip));
}
m_bFlashOverlay = false;
KillIconFlashTimer();
}
//
// Flash the icon ---------------------------------------------------------
//
if (UF_FLASHICON & uFlags)
{
if (0 != sti.iFlashTimeout)
{
nid.uFlags |= NIF_ICON; // Flashing is actually displaying a new icon.
//
// This icon is a flashing icon.
//
if (0 == m_idFlashingTimer)
{
//
// No timer started yet. Start one.
//
STDBGOUT((2, TEXT("Starting icon flash timer. Time = %d ms"), m_iIconFlashTime));
m_idFlashingTimer = SetTimer(m_hwndNotify,
ID_TIMER_FLASHICON,
m_iIconFlashTime,
FlashTimerProc);
if (0 != m_idFlashingTimer)
{
//
// Set the tick-count when the timer expires.
// An expiration time of (-1) means it never expires.
//
if (ICONFLASH_FOREVER != sti.iFlashTimeout)
m_dwFlashingExpires = GetTickCount() + sti.iFlashTimeout;
else
m_dwFlashingExpires = ICONFLASH_FOREVER;
}
}
nid.hIcon = m_bFlashOverlay ? sti.hIcon : m_hIconNoOverlay;
m_bFlashOverlay = !m_bFlashOverlay; // Toggle flash state.
}
}
//
// Update or hide the balloon ---------------------------------------------
//
if (UF_BALLOON & uFlags)
{
//
// If there's no balloon text mapped to the current UI state and these
// balloon flags, any current balloon will be destroyed. This is because
// the tray code destroys the current balloon before displaying the new one
// and it doesn't display a new one if it's passed a blank string.
//
nid.uFlags |= NIF_INFO;
DWORD dwBalloonFlags = (UF_REMINDER & uFlags) ? BTF_REMIND : BTF_INITIAL;
GetBalloonInfo(m_state,
dwBalloonFlags,
nid.szInfoTitle,
ARRAYSIZE(nid.szInfoTitle),
nid.szInfo,
ARRAYSIZE(nid.szInfo),
&nid.dwInfoFlags,
&nid.uTimeout);
//
// Any time we show a balloon, we reset the reminder timer.
// This is so that we don't get a balloon resulting from a state change
// immediately followed by a reminder balloon because the reminder
// timer expired.
//
bool bRestartReminderTimer = (BTF_REMIND == dwBalloonFlags && TEXT('\0') != nid.szInfo[0]) ||
StateHasBalloonText(m_state, BTF_REMIND);
ResetReminderTimer(bRestartReminderTimer);
}
//
// Notify the systray -----------------------------------------------------
//
if (NIM_DELETE == nimsg)
m_bActive = false;
if (Shell_NotifyIcon(nimsg, &nid))
{
if (NIM_ADD == nimsg)
m_bActive = true;
}
}
//
// Get the balloon text associated with a given systray UI state and with
// a given set of BTF_XXXXX (Balloon Text Flag) flags. The information
// is stored in the table s_rgBalloonInfo[]. The text and balloon timeout
// are returned in caller-provided buffers.
//
// The balloon text follows this format:
//
// <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);
}
lstrcpyn(pszText, szTemp, cchText);
}
}
return pszText;
}
//
// Stop the flashing icon by killing the timer.
//
void
CSysTrayUI::KillIconFlashTimer(
void
)
{
//
// Force a final update so we're displaying the proper icon then
// kill the timer.
//
if (0 != m_idFlashingTimer)
{
KillTimer(m_hwndNotify, m_idFlashingTimer);
m_idFlashingTimer = 0;
}
}
//
// Called by the OS each time the icon flash timer period expires.
// I use this rather than handling a WM_TIMER message so that
// timer processing is contained within the CSysTrayUI class.
//
VOID CALLBACK
CSysTrayUI::FlashTimerProc(
HWND hwnd,
UINT uMsg,
UINT_PTR idEvent,
DWORD dwTime
)
{
CSysTrayUI::GetInstance().HandleFlashTimer();
}
void
CSysTrayUI::HandleFlashTimer(
void
)
{
if (IconFlashedLongEnough())
{
//
// Kill the icon flashing timer and the icon will stop flashing.
// This doesn't actually kill the timer yet.
//
STDBGOUT((2, TEXT("Killing icon flash timer")));
m_bFlashOverlay = true;
UpdateSysTray(UF_FLASHICON);
KillIconFlashTimer();
}
else
{
//
// The CSysTrayUI instance maintains all information
// needed to cycle the icon. Just tell it to update
// the icon and it'll do the right thing.
//
UpdateSysTray(UF_FLASHICON);
}
}
//
// Determine if the flashing icon has flashed enough.
//
bool
CSysTrayUI::IconFlashedLongEnough(
void
)
{
return ICONFLASH_FOREVER != m_dwFlashingExpires &&
GetTickCount() >= m_dwFlashingExpires;
}
//
// Stop and restart the reminder timer.
// If bRestart is false, the timer is killed and not restarted.
// If bRestart is true, the timer is killed and a new one restarted.
//
void
CSysTrayUI::ResetReminderTimer(
bool bRestart
)
{
CConfig& config = CConfig::GetSingleton();
if (!config.NoReminders())
{
int cReminderInterval = (config.ReminderFreqMinutes() * 1000 * 60);
//
// Force a final update so we're displaying the proper icon then
// kill the timer.
//
if (0 != m_idReminderTimer)
{
KillTimer(m_hwndNotify, m_idReminderTimer);
m_idReminderTimer = 0;
}
//
// No timer started yet. Start one.
//
if (bRestart && 0 < cReminderInterval)
{
STDBGOUT((2, TEXT("Starting reminder timer. Timeout = %d ms"), cReminderInterval));
m_idReminderTimer = SetTimer(m_hwndNotify,
ID_TIMER_REMINDER,
cReminderInterval,
ReminderTimerProc);
}
}
}
//
// Called by the OS each time the reminder timer period expires.
// I use this rather than handling a WM_TIMER message so that
// timer processing is contained within the CSysTrayUI class.
//
VOID CALLBACK
CSysTrayUI::ReminderTimerProc(
HWND hwnd,
UINT uMsg,
UINT_PTR idEvent,
DWORD dwTime
)
{
STDBGOUT((2, TEXT("Showing reminder balloon")));
CSysTrayUI::GetInstance().ShowReminderBalloon();
}
//
// Called by the systray WndProc whenever the state of the systray should be
// updated.
//
// hWnd - HWND of the systray notification window.
//
// stwmMsg - STWM_CSCNETUP (Net or server is available for reconnect)
// STWM_CSCNETDOWN (Net or server is unavailable)
// STWM_STATUSCHECK (Check cache state and update systray)
//
// pszServer - non-NULL means CSC agent passed a server name
// associated with the STWM_XXXX message.
// This means there was a single server associated with the event
// rather than multiple servers or the entire net interface.
//
void
UpdateStatus(
CStateMachine *pSM,
HWND hWnd,
UINT stwmMsg,
LPTSTR pszServer
)
{
TraceEnter(TRACE_CSCST, "UpdateStatus");
TraceAssert(NULL != hWnd);
TCHAR szServerName[MAX_PATH] = { 0 };
if (pszServer)
{
lstrcpyn(szServerName, pszServer, ARRAYSIZE(szServerName));
}
//
// Translate the CSC agent inputs into a new systray UI state.
//
eSysTrayState state = pSM->TranslateInput(stwmMsg, szServerName, ARRAYSIZE(szServerName));
//
// Get reference to the singleton UI object and tell it to set the state.
// Note that it remembers all current UI state and will only actually
// update the systray if the UI state has changed. Here we can
// blindly tell it to update state. It will only do what's necessary.
//
CSysTrayUI::GetInstance().SetState(state, szServerName);
TraceLeaveVoid();
}
///////////////////////////////////////////////////////////////////////////////
// _CreateMenu()
//
// Create context menu
//
HMENU _CreateMenu()
{
HMENU hmenu = NULL;
TraceEnter(TRACE_CSCST, "_CreateMenu");
hmenu = CreatePopupMenu();
if (NULL != hmenu)
{
CConfig& config = CConfig::GetSingleton();
TCHAR szTemp[MAX_PATH];
//
// Add the "Status" verb.
//
LoadString(g_hInstance, IDS_CSC_CM_STATUS, szTemp, ARRAYSIZE(szTemp));
AppendMenu(hmenu, MF_STRING, PWM_STATUSDLG, szTemp);
//
// Add the "Synchronize" verb
//
LoadString(g_hInstance, IDS_CSC_CM_SYNCHRONIZE, szTemp, ARRAYSIZE(szTemp));
AppendMenu(hmenu, MF_STRING, CSCWM_SYNCHRONIZE, szTemp);
if (!config.NoCacheViewer())
{
//
// Add the "View files" verb
//
LoadString(g_hInstance, IDS_CSC_CM_SHOWVIEWER, szTemp, ARRAYSIZE(szTemp));
AppendMenu(hmenu, MF_STRING, CSCWM_VIEWFILES, szTemp);
}
if (!config.NoConfigCache())
{
//
// Add the "Settings" verb
//
LoadString(g_hInstance, IDS_CSC_CM_SETTINGS, szTemp, ARRAYSIZE(szTemp));
AppendMenu(hmenu, MF_STRING, CSCWM_SETTINGS, szTemp);
}
//
// Left clicking the systray icon invokes the status dialog.
// Therefore, the "Status" verb is our default and must be in bold text.
//
SetMenuDefaultItem(hmenu, PWM_STATUSDLG, MF_BYCOMMAND);
}
TraceLeaveValue(hmenu);
}
///////////////////////////////////////////////////////////////////////////////
// _ShowMenu()
//
UINT _ShowMenu(HWND hWnd, UINT uMenuNum, UINT uButton)
{
UINT iCmd = 0;
HMENU hmenu;
TraceEnter(TRACE_CSCST, "_ShowMenu");
hmenu = _CreateMenu();
if (hmenu)
{
POINT pt;
GetCursorPos(&pt);
SetForegroundWindow(hWnd);
iCmd = TrackPopupMenu(hmenu,
uButton | TPM_RETURNCMD | TPM_NONOTIFY,
pt.x,
pt.y,
0,
hWnd,
NULL);
DestroyMenu(hmenu);
}
TraceLeaveValue(iCmd);
}
//
// This function is used to ensure that we don't try to process
// a WM_RBUTTONUP and WM_LBUTTONUP message at the same time.
// May be a little paranoid.
//
LRESULT
OnTrayIconSelected(
HWND hWnd,
UINT uMsg
)
{
static LONG bHandling = 0;
LRESULT lResult = 0;
if (0 == InterlockedCompareExchange(&bHandling, 1, 0))
{
UINT iCmd = 0;
switch (uMsg)
{
case WM_RBUTTONUP:
//
// Context menu
//
iCmd = _ShowMenu(hWnd, 1, TPM_RIGHTBUTTON);
break;
case WM_LBUTTONUP:
iCmd = PWM_STATUSDLG;
break;
default:
break;
}
if (iCmd)
{
PostMessage(hWnd, iCmd, 0, 0);
lResult = 1;
}
bHandling = 0;
}
return lResult;
}
///////////////////////////////////////////////////////////////////////////////
// _Notify() -- systray notification handler
//
LRESULT _Notify(HWND hWnd, WPARAM /*wParam*/, LPARAM lParam)
{
LRESULT lResult = 0;
switch (lParam)
{
case WM_RBUTTONUP:
case WM_LBUTTONUP:
lResult = OnTrayIconSelected(hWnd, (UINT)lParam);
break;
default:
break;
}
return lResult;
}
bool IsServerBack(CStateMachine *pSM, LPCTSTR pszServer)
{
TCHAR szServer[MAX_PATH];
if (!PathIsUNC(pszServer))
{
//
// Ensure servername uses UNC format.
//
wsprintf(szServer, TEXT("\\\\%s"), pszServer);
pszServer = szServer;
}
return pSM->IsServerPendingReconnection(pszServer);
}
//
// Query CSC policy for the sync-at-logoff (quick vs. full)
// setting. If the policy is set, we enable SyncMgr's sync-at-logoff
// setting. Without this the CSC policy could be set, the SyncMgr
// setting NOT set and the user wouldn't get sync-at-logoff as the
// admin had anticipated.
//
void
ApplyCscSyncAtLogonAndLogoffPolicies(
void
)
{
bool bSetByPolicy = false;
CConfig& config = CConfig::GetSingleton();
config.SyncAtLogoff(&bSetByPolicy);
if (bSetByPolicy)
{
RegisterForSyncAtLogonAndLogoff(SYNCMGRREGISTERFLAG_PENDINGDISCONNECT,
SYNCMGRREGISTERFLAG_PENDINGDISCONNECT);
}
config.SyncAtLogon(&bSetByPolicy);
if (bSetByPolicy)
{
RegisterForSyncAtLogonAndLogoff(SYNCMGRREGISTERFLAG_CONNECT,
SYNCMGRREGISTERFLAG_CONNECT);
}
}
//
// Encryption/Decryption callback from CSC.
//
// dwReason dwParam1 dwParam2
// ------------------------- ------------------ --------------------------
// CSCPROC_REASON_BEGIN 1 == Encrypting 0
// CSCPROC_REASON_MORE_DATA 0 Win32 error code
// CSCPROC_REASON_END 1 == Completed dwParam1 == 1 ? 0
// dwParam1 == 0 ? GetLastError()
//
DWORD CALLBACK
EncryptDecryptCallback(
LPCWSTR lpszName,
DWORD dwStatus,
DWORD dwHintFlags,
DWORD dwPinCount,
WIN32_FIND_DATAW *pFind32,
DWORD dwReason,
DWORD dwParam1,
DWORD dwParam2,
DWORD_PTR dwContext
) throw()
{
DWORD dwResult = CSCPROC_RETURN_CONTINUE;
const DWORD dwError = dwParam2;
//
// Some static data that needs to persist across callback calls.
//
static bool bEncrypting; // Encrypting or decrypting?
static bool bLoggingOff = false; // User logging off?
static int cFileErrors = 0; // How many file-specific errors reported?
static DWORD dwLastError;
static TCHAR szLastFile[MAX_PATH];
//
// If we've already detected the g_heventTerminate event
// no sense in continuing.
//
if (bLoggingOff)
return CSCPROC_RETURN_ABORT;
if (WAIT_OBJECT_0 == WaitForSingleObject(g_heventTerminate, 0))
{
//
// User is logging off. Need to end this now!
// Log an event so admin knows why encryption was incomplete.
//
// LOGGING LEVEL = 0 (always)
//
CscuiEventLog log;
log.ReportEvent(EVENTLOG_INFORMATION_TYPE,
bEncrypting ? MSG_I_ENCRYPT_USERLOGOFF : MSG_I_DECRYPT_USERLOGOFF,
0);
dwResult = CSCPROC_RETURN_ABORT;
bLoggingOff = true;
}
else
{
switch(dwReason)
{
case CSCPROC_REASON_BEGIN:
//
// Reset static variables.
//
bEncrypting = boolify(dwParam1);
bLoggingOff = false;
cFileErrors = 0;
dwLastError = ERROR_SUCCESS;
szLastFile[0] = TEXT('\0');
break;
case CSCPROC_REASON_MORE_DATA:
if (ERROR_SUCCESS != dwError)
{
//
// An error occurred for this file.
//
CscuiEventLog log;
LPTSTR pszError = NULL;
FormatSystemError(&pszError, dwError);
if (0 == cFileErrors++)
{
//
// On the first error, log an error at level 0.
// By default, this is the only error the admin will see.
// They'll need to increase the event log level to level
// 2 in order to get events for each individual file. The
// event text describes this.
//
// LOGGING_LEVEL = 0
//
log.ReportEvent(EVENTLOG_ERROR_TYPE,
bEncrypting ? MSG_E_ENCRYPTFILE_ERRORS : MSG_E_DECRYPTFILE_ERRORS,
0);
}
//
// Log the error for this file.
//
// LOGGING LEVEL = 2
//
log.Push(HRESULT(dwError), CEventLog::eFmtDec);
log.Push(lpszName);
log.Push(pszError ? pszError : TEXT(""));
if (S_OK == log.ReportEvent(EVENTLOG_ERROR_TYPE,
bEncrypting ? MSG_E_ENCRYPTFILE_FAILED : MSG_E_DECRYPTFILE_FAILED,
2))
{
//
// We logged this event.
// Clear out the last error code and last filename so that
// we don't report this error again in response to CSCPROC_REASON_END.
//
szLastFile[0] = TEXT('\0');
dwLastError = ERROR_SUCCESS;
}
else
{
//
// Event was not logged because...
//
// a) ... an error occurred while logging the event.
// b) ... EventLoggingLevel policy is too low for this event.
//
// Save this error code and file name.
// We may need to report it in response to CSCPROC_REASON_END.
//
dwLastError = dwError;
lstrcpyn(szLastFile, lpszName, ARRAYSIZE(szLastFile));
}
if (pszError)
LocalFree(pszError);
}
break;
case CSCPROC_REASON_END:
{
const DWORD fCompleted = dwParam1;
CscuiEventLog log;
if (fCompleted)
{
//
// Add an event log entry that the encryption/decryption
// completed successfully.
//
// LOGGING LEVEL = 1
//
log.ReportEvent(EVENTLOG_INFORMATION_TYPE,
bEncrypting ? MSG_I_ENCRYPT_COMPLETE : MSG_I_DECRYPT_COMPLETE,
1);
}
else
{
LPTSTR pszError = NULL;
if (ERROR_SUCCESS != dwError)
{
//
// Some general error with the process.
//
// LOGGING LEVEL = 0
//
FormatSystemError(&pszError, dwError);
log.Push(HRESULT(dwError), CEventLog::eFmtDec);
log.Push(pszError ? pszError : TEXT(""));
log.ReportEvent(EVENTLOG_ERROR_TYPE,
bEncrypting ? MSG_E_ENCRYPT_FAILED : MSG_E_DECRYPT_FAILED,
0);
}
else if (ERROR_SUCCESS != dwLastError)
{
if (0 == cFileErrors++)
{
//
// On the first error, log an error at level 0.
// By default, this is the only error the admin will see.
// They'll need to increase the event log level to level
// 2 in order to get events for each individual file. The
// event text describes this.
//
// LOGGING_LEVEL = 0
//
log.ReportEvent(EVENTLOG_ERROR_TYPE,
bEncrypting ? MSG_E_ENCRYPTFILE_ERRORS : MSG_E_DECRYPTFILE_ERRORS,
0);
}
//
// Encryption/decryption of some file failed and we did not
// log it in the "more data" callback.
//
// LOGGING LEVEL = 2
//
FormatSystemError(&pszError, dwLastError);
log.Push(HRESULT(dwLastError), CEventLog::eFmtDec);
log.Push(szLastFile);
log.Push(pszError ? pszError : TEXT(""));
log.ReportEvent(EVENTLOG_ERROR_TYPE,
bEncrypting ? MSG_E_ENCRYPTFILE_FAILED : MSG_E_DECRYPTFILE_FAILED,
2);
}
if (pszError)
LocalFree(pszError);
}
break;
}
default:
break;
}
}
return dwResult;
}
DWORD
CacheEncryptionThreadProc(
LPVOID pvParams
)
{
const DWORD fEncrypt = (DWORD)(DWORD_PTR)pvParams;
HINSTANCE hmodCSCUI = LoadLibrary(c_szDllName);
if (NULL != hmodCSCUI)
{
//
// Try to get the encryption mutex.
//
HANDLE hMutex = RequestPermissionToEncryptCache();
if (NULL != hMutex)
{
//
// Ensure release of the mutex.
//
CMutexAutoRelease auto_release_mutex(hMutex);
STDBGOUT((1, TEXT("%s started."),
fEncrypt ? TEXT("Encryption") : TEXT("Decryption")));
//
// Do the encryption/decryption. Do it at a low thread priority so
// we don't steal CPU time from the UI.
//
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST);
CSCEncryptDecryptDatabase(fEncrypt, EncryptDecryptCallback, (DWORD_PTR)0);
STDBGOUT((1, TEXT("%s complete."),
fEncrypt ? TEXT("Encryption") : TEXT("Decryption")));
}
else
{
//
// Someone else (probably the UI) is currently encrypting/decrypting
// the cache. We don't allow concurrent operations so we abort this
// one.
//
STDBGOUT((1, TEXT("%s aborted. Already in progress."),
fEncrypt ? TEXT("Encryption") : TEXT("Decryption")));
}
FreeLibraryAndExitThread(hmodCSCUI, 0);
}
return 0;
}
//
// Encrypt/Decrypt the cache according to system policy.
// This function will also correct a partial (encrypt/decrypt)
// state in the cache if necessary.
//
void
ApplyCacheEncryptionPolicy(
void
)
{
//
// Do a quick check to see if an encryption process is already in progress.
// This doesn't take the mutex but checks to see if someone else has
// it. Once we actually start the encryption on the background thread
// we'll take the mutex. Of course, if someone else sneeked in and grabbed
// the mutex between now and then we'll have to abort.
//
if (!IsEncryptionInProgress())
{
//
// Encrypt/Decrypt the cache files. Will provide progress info through
// the callback EncryptDecryptCallback. Errors are handled in the callback
// reason handlers.
//
CConfig& config = CConfig::GetSingleton();
bool bShouldBeEncrypted = config.EncryptCache();
BOOL bPartial;
const BOOL bIsEncrypted = IsCacheEncrypted(&bPartial);
if (bPartial || (boolify(bIsEncrypted) != bShouldBeEncrypted))
{
if (CscVolumeSupportsEncryption())
{
//
// Either we have a partially encrypted/decrypted cache or
// current encryption state is different from what policy wants.
// Encrypt/decrypt to rectify the situation.
// Run this on a separate thread so we don't block any processing
// on the tray UI thread (i.e. volume control).
//
DWORD dwThreadId;
HANDLE hThread = CreateThread(NULL, // Default security.
0, // Default stack size.
CacheEncryptionThreadProc,
(VOID *)(DWORD_PTR)bShouldBeEncrypted,
0, // Run immediately
&dwThreadId);
if (NULL != hThread)
{
CloseHandle(hThread);
}
}
else
{
//
// The CSC volume doesn't support encryption. Log an event so
// the admin will know why the cache wasn't encrypted by policy.
// Note that we won't hit this path in the "partial" case. Only
// if policy says to encrypt. The event log message is
// tailored for this specific scenario.
//
TraceAssert(!bIsEncrypted && bShouldBeEncrypted);
CscuiEventLog log;
log.ReportEvent(EVENTLOG_WARNING_TYPE, MSG_W_NO_ENCRYPT_VOLUME, 0);
}
}
}
else
{
STDBGOUT((1, TEXT("Encryption/decryption not allowed. Already in progress.")));
}
}
//
// Handles policy change in response to a WM_WININICHANGE
// message with lParam == "policy".
//
LRESULT OnPolicyChange(
void
)
{
ApplyCacheEncryptionPolicy();
ApplyCscSyncAtLogonAndLogoffPolicies();
ApplyAdminFolderPolicy();
return 0;
}
//
// Display the CSCUI status dialog. Invoked when user either
// left-clicks the systray icon or selects the "Show Status" option
// from the systray context menu.
//
void
ShowCSCUIStatusDlg(
HWND hwndParent
)
{
TCHAR szText[1024] = {0}; // This can be a fair amount of text.
const struct
{
eSysTrayState state; // SysTray UI state code.
UINT idsText; // Text for status dialog body.
} rgMap[] = {{ STS_OFFLINE, IDS_STATUSDLG_OFFLINE },
{ STS_MOFFLINE, IDS_STATUSDLG_OFFLINE_M },
{ STS_SERVERBACK, IDS_STATUSDLG_SERVERBACK },
{ STS_MSERVERBACK, IDS_STATUSDLG_SERVERBACK_M },
{ STS_DIRTY, IDS_STATUSDLG_DIRTY },
{ STS_MDIRTY, IDS_STATUSDLG_DIRTY_M },
{ STS_NONET, IDS_STATUSDLG_NONET }};
CSysTrayUI& stui = CSysTrayUI::GetInstance();
eSysTrayState state = stui.GetState();
for (int i = 0; i < ARRAYSIZE(rgMap); i++)
{
if (state == rgMap[i].state)
{
LoadString(g_hInstance, rgMap[i].idsText, szText, ARRAYSIZE(szText));
if (STS_DIRTY == state || STS_OFFLINE == state || STS_SERVERBACK == state)
{
LPCTSTR pszServerName = stui.GetServerName();
if (NULL != pszServerName && TEXT('\0') != *pszServerName)
{
//
// Current SysTray UI state has a single server associated
// with it. The message will have the name embedded in
// it in 2 places. Create a temp working buffer and
// re-create the original string with the server name
// embedded. If any of this fails, the string will just
// be displayed with the %1, %2 formatting characters rather
// than the server names. Not a fatal problem IMO.
//
LPTSTR pszTemp = new TCHAR[lstrlen(szText) + 1];
if (NULL != pszTemp)
{
lstrcpy(pszTemp, szText);
LPCTSTR rgpstr[] = { pszServerName, pszServerName };
FormatMessage(FORMAT_MESSAGE_FROM_STRING |
FORMAT_MESSAGE_ARGUMENT_ARRAY,
pszTemp,
0,0,
szText,
ARRAYSIZE(szText),
(va_list *)rgpstr);
delete[] pszTemp;
}
}
}
break; // Break out of loop. We have what we need.
}
}
//
// Display the dialog.
//
CStatusDlg::Create(hwndParent, szText, state);
}
//
// PWM_RESET_REMINDERTIMER handler.
//
void
OnResetReminderTimer(
void
)
{
CSysTrayUI::GetInstance().ResetReminderTimer(true);
}
//
// Whenever we reboot, it's possible that the CSCUI cache has been
// reformatted or that the cache-size policy has been set/changed.
// When reformatted, the CSC agent uses the default size of 10%. We
// need to ensure that the size reflects system policy when policy
// is defined.
//
void
InitCacheSize(
void
)
{
bool bSetByPolicy = false;
DWORD dwPctX10000 = CConfig::GetSingleton().DefaultCacheSize(&bSetByPolicy);
if (bSetByPolicy)
{
ULARGE_INTEGER ulCacheSize;
CSCSPACEUSAGEINFO sui;
GetCscSpaceUsageInfo(&sui);
if (10000 < dwPctX10000)
{
//
// If value in registry is greater than 10000, it's
// invalid. Default to 10% of total disk space.
//
dwPctX10000 = 1000; // Default to 10% (0.10 * 10,000)
}
ulCacheSize.QuadPart = (sui.llBytesOnVolume * dwPctX10000) / 10000i64;
if (!CSCSetMaxSpace(ulCacheSize.HighPart, ulCacheSize.LowPart))
{
STDBGOUT((1, TEXT("Error %d setting cache size"), GetLastError()));
}
}
}
//
// Handler for CSCWM_SYNCHRONIZE. Called when user clicks "Synchronize"
// option on systray context menu. Also invoked when user selects the
// "Synchronize" button in a folder's web view pane.
//
HRESULT
OnSynchronize(
void
)
{
//
// This will create a status dialog hidden, invoke a synchronization of
// servers that would be "checked" in the dialog then close the dialog
// when the synchronization is complete.
//
CStatusDlg::Create(g_hWndNotification,
TEXT(""),
CSysTrayUI::GetInstance().GetState(),
CStatusDlg::MODE_AUTOSYNC);
return NOERROR;
}
LRESULT
OnQueryUIState(
void
)
{
return CSysTrayUI::GetInstance().GetState();
}
//
// When a user profile has been removed from the local machine,
// the delete-profile code in userenv.dll will write the user's SID
// as a text string in the following reg key:
//
// HKLM\Software\Microsoft\Windows\CurrentVersion\NetCache\PurgeAtNextLogoff
//
// Each SID is a value name under this key.
// At logoff, we enumerate all values under this key. For each SID we
// instantiate a CCachePurger object and delete all files cached for this
// user. Once the operation is complete, the "PurgeAtNextLogoff" key
// is deleted from the registry.
//
void
DeleteFilesCachedForObsoleteProfiles(
void
)
{
HKEY hkeyNetcache;
//
// Open the "HKLM\...\NetCache" key.
//
LONG lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
c_szCSCKey,
0,
KEY_READ,
&hkeyNetcache);
if (ERROR_SUCCESS == lResult)
{
HKEY hkey;
//
// Open the "PurgeAtNextLogoff" subkey.
//
lResult = RegOpenKeyEx(hkeyNetcache,
c_szPurgeAtNextLogoff,
0,
KEY_READ,
&hkey);
if (ERROR_SUCCESS == lResult)
{
//
// Enumerate all the SID strings.
//
int iValue = 0;
TCHAR szValue[MAX_PATH];
DWORD cchValue = ARRAYSIZE(szValue);
while(ERROR_SUCCESS == SHEnumValue(hkey,
iValue,
szValue,
&cchValue,
NULL,
NULL,
NULL))
{
//
// Convert each SID string to a SID and delete
// all cached files accessed by this SID.
// Purge files ONLY if the SID is NOT for the current
// user. Here's the deal...
// While a user is NOT logged onto a system, their
// profile can be removed and their SID recorded in
// the PurgeAtNextLogoff key. The next time they log on they
// get new profile data. If they're the next person to
// logon following the removal of their profile, without
// this check, their new profile data would be purged during
// the subsequent logoff. That's bad. Therefore, we never
// purge data for the user who is logging off.
//
PSID psid;
if (ConvertStringSidToSid(szValue, &psid))
{
if (!IsSidCurrentUser(psid))
{
CCachePurgerSel sel;
sel.SetFlags(PURGE_FLAG_ALL);
sel.SetUserSid(psid);
CCachePurger purger(sel, NULL, NULL);
purger.Delete();
}
LocalFree(psid);
}
iValue++;
cchValue = ARRAYSIZE(szValue);
}
RegCloseKey(hkey);
RegDeleteKey(hkeyNetcache, c_szPurgeAtNextLogoff);
}
RegCloseKey(hkeyNetcache);
}
}
//
// This is called when the CSC hidden window is first created
// which occurs at logon. It's just a general bucket to group the
// things that need to happen each logon.
//
void
HandleLogonTasks(
void
)
{
InitCacheSize();
//
// Apply any necessary policies.
//
ApplyCacheEncryptionPolicy();
ApplyCscSyncAtLogonAndLogoffPolicies();
ApplyAdminFolderPolicy();
}
//
// This is called when the CSC Agent (running in the winlogon process)
// tells us to uninitialize the CSC UI. This happens when the user
// is logging off.
//
void
HandleLogoffTasks(
void
)
{
CConfig& config = CConfig::GetSingleton();
DeleteFilesCachedForObsoleteProfiles();
if (config.PurgeAtLogoff())
{
//
// If policy says to "purge all files cached by this user"
// delete offline-copy of all files cached by the current user.
// Respects access bits in files so that we don't delete something
// that is only used by some other user. This is the same
// behavior obtained via the "Delete Files..." button or the
// disk cleaner. Note that the UI callback ptr arg to the purger
// ctor is NULL and we don't run through a "scan" phase. This code
// is run while the user is logging off so we don't display any
// UI.
//
// Note that the policy can also indicate if this purge operation
// is for auto-cached files only.
//
DWORD dwPurgeFlags = PURGE_FLAG_UNPINNED;
if (!config.PurgeOnlyAutoCachedFilesAtLogoff())
{
dwPurgeFlags |= PURGE_FLAG_PINNED;
}
CCachePurgerSel sel;
sel.SetFlags(dwPurgeFlags);
CCachePurger purger(sel, NULL, NULL);
purger.Delete();
}
//
// IMPORTANT: We do any purging before registering for sync-at-logon/logoff.
// This is because we only register if we have something in
// the cache. Purging might remove all our cached items negating
// the need to register for synchronization.
//
ApplyCscSyncAtLogonAndLogoffPolicies();
//
// Is this the first time this user has used run CSCUI?
//
if (!IsSyncMgrInitialized())
{
CSCCACHESTATS cs;
CSCGETSTATSINFO si = { SSEF_NONE, SSUF_TOTAL, false, false };
if (_GetCacheStatisticsForUser(&si, &cs) && 0 < cs.cTotal)
{
//
// This is the first time this user has logged off with
// something in the cache. Since SyncMgr doesn't turn on sync-at-logon/logoff
// out of the box, we do it here. This is because we want people to sync
// if they have unknowingly cached files from an autocache share.
// If successful the SyncMgrInitialized reg value is set to 1.
//
RegisterSyncMgrHandler(TRUE);
const DWORD dwFlags = SYNCMGRREGISTERFLAG_CONNECT | SYNCMGRREGISTERFLAG_PENDINGDISCONNECT;
if (SUCCEEDED(RegisterForSyncAtLogonAndLogoff(dwFlags, dwFlags)))
{
SetSyncMgrInitialized();
}
}
}
}
//
// Determines the status of a share for controlling the display of the
// webview in a shell folder.
//
// Returns one of the following codes (defined in cscuiext.h):
//
// CSC_SHARESTATUS_INACTIVE
// CSC_SHARESTATUS_ONLINE
// CSC_SHARESTATUS_OFFLINE
// CSC_SHARESTATUS_SERVERBACK
// CSC_SHARESTATUS_DIRTYCACHE
//
LRESULT
GetShareStatusForWebView(
CStateMachine *pSM,
LPCTSTR pszShare
)
{
LRESULT lResult = CSC_SHARESTATUS_INACTIVE;
if (NULL != pszShare && IsCSCEnabled())
{
DWORD dwStatus;
if (CSCQueryFileStatus(pszShare, &dwStatus, NULL, NULL))
{
if ((dwStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) != FLAG_CSC_SHARE_STATUS_NO_CACHING)
{
const DWORD fExclude = SSEF_LOCAL_DELETED |
SSEF_DIRECTORY;
CSCSHARESTATS stats;
CSCGETSTATSINFO gsi = { fExclude, SSUF_MODIFIED, true, false };
lResult = CSC_SHARESTATUS_ONLINE;
if (_GetShareStatisticsForUser(pszShare, &gsi, &stats))
{
if (stats.bOffline)
{
if (IsServerBack(pSM, pszShare))
lResult = CSC_SHARESTATUS_SERVERBACK;
else
lResult = CSC_SHARESTATUS_OFFLINE;
}
else
{
if (0 < stats.cModified)
lResult = CSC_SHARESTATUS_DIRTYCACHE;
}
}
}
}
}
return lResult;
}
//-----------------------------------------------------------------------------
// Sync at Suspend/Hibernate.
//
// We synchronize the cache on a separate thread. Why use a separate
// thread?
//
// 1. We respond to WM_POWERBROADCAST.
//
// 2. WM_POWERBROADCAST is sent by win32k.sys using SendMessage.
//
// 3. As part of the sync we invoke SyncMgr which involves some
// COM operations. COM doesn't allow certain operations if they occur
// on a thread that is currently inside an interprocess SendMessage.
// This causes a call to CoCreateInstance inside mobsync.dll to fail
// with the error RPC_E_CANTCALLOUT_ININPUTSYNCCALL.
//
// 4. The solution is to place the synchronization (and COM) activity
// on a separate thread and to allow the thread servicing WM_POWERBROADCAST
// to process messages.
//
// When suspending, the main thread servicing WM_POWERBROADCAST blocks
// until the entire synchronization is complete. This is necessary to ensure
// the synchronization completes before the machine is shut down.
//
//
//
// The synchronization thread procedure for syncing on suspend/hibernate.
//
DWORD WINAPI
SuspendSync_ThreadProc(
LPVOID pvParam // DWORD_PTR holding CSC update flags.
)
{
TraceEnter(TRACE_CSCST, "SuspendSync_ThreadProc");
const DWORD dwFlags = PtrToUint(pvParam);
Trace((TEXT("Calling CscUpdateCache with flags 0x%08X"), dwFlags));
const HRESULT hr = CscUpdateCache(dwFlags);
TraceLeaveResult(hr);
}
//
// Waits on a single object while handling thread messages during the wait.
// Returns the result from MsgWaitForMultipleObjectsEx.
//
DWORD
WaitAndProcessThreadMessages(
HANDLE hObject // Handle for a Win32 synchronization object.
)
{
TraceEnter(TRACE_CSCST, "WaitAndProcessThreadMessages");
DWORD dwResult = WAIT_FAILED;
BOOL bQuit = FALSE;
while(!bQuit)
{
TraceMsg("Waiting for message or signaled object...");
dwResult = MsgWaitForMultipleObjectsEx(1,
&hObject,
INFINITE,
QS_ALLEVENTS,
MWMO_INPUTAVAILABLE);
//
// A message was received. Handle it.
//
if (WAIT_OBJECT_0 + 1 == dwResult)
{
MSG msg;
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
Trace((TEXT("Rcvd message %d"), msg.message));
if (WM_QUIT == msg.message)
{
bQuit = TRUE;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
else
{
//
// Any other result ends the loop.
//
bQuit = TRUE;
if (WAIT_OBJECT_0 == dwResult)
{
TraceMsg("Object signaled");
}
else if (WAIT_FAILED == dwResult)
{
Trace((TEXT("Wait failed with error %d"), GetLastError()));
}
}
}
TraceLeaveValue(dwResult);
}
//
// Gets the sync action (quick vs. full) from user preference and/or
// system policy. If either there is no preference/policy defined
// or we found an invalid preference/policy value in the registry,
// we return eSyncNone as a default.
//
// Returns:
// CConfig::eSyncPartial - quick sync.
// CConfig::eSyncFull - full sync.
// CConfig::eSyncNone - invalid or missing reg info.
//
CConfig::SyncAction
GetSuspendSyncAction(
void
)
{
TraceEnter(TRACE_CSCST, "GetSuspendSyncAction");
CConfig::SyncAction action = CConfig::eSyncNone;
HRESULT hr = TS_MultipleSessions();
if (S_FALSE == hr)
{
action = (CConfig::SyncAction)CConfig::GetSingleton().SyncAtSuspend();
if (CConfig::eSyncPartial != action && CConfig::eSyncFull != action)
{
//
// Either someone poked an invalid value into the registry
// or there is no preference/policy registered for this parameter.
// Either way, we want to NOT sync.
//
action = CConfig::eSyncNone;
}
}
else if (S_OK == hr)
{
Trace((TEXT("Multiple sessions prevent synchronization.")));
}
Trace((TEXT("Action = %d"), int(action)));
TraceLeaveValue(action);
}
//
// Retrieves the set of flags to pass to CscUpdateCache configured
// for a given suspend operation.
//
// Returns:
// true - Ok to sync. CscUpdateCache flags are in *pdwFlags.
// false - Don't sync. Sync action is eSyndNone.
//
bool
IsSuspendSyncRequired(
bool bOkToPromptUser,
DWORD *pdwCscUpdateFlags // optional. Can be NULL.
)
{
TraceEnter(TRACE_CSCST, "IsSuspendSyncRequired");
DWORD dwFlags = 0;
const CConfig::SyncAction action = GetSuspendSyncAction();
if (bOkToPromptUser && CConfig::eSyncNone != action)
{
dwFlags = CSC_UPDATE_STARTNOW | CSC_UPDATE_FILL_QUICK;
if (CConfig::eSyncFull == action)
{
dwFlags |= (CSC_UPDATE_REINT | CSC_UPDATE_FILL_ALL);
}
Trace((TEXT("%s sync is required. CscUpdate flags = 0x%08X"),
CConfig::eSyncFull == action ? TEXT("FULL") : TEXT("QUICK"),
dwFlags));
}
else
{
TraceMsg("No sync is required");
}
if (NULL != pdwCscUpdateFlags)
{
*pdwCscUpdateFlags = dwFlags;
}
TraceLeaveValue(0 != dwFlags);
}
//
// This function creates the sync thread, and waits for the sync operation
// to complete if required. It returns the result returned by CscUpdateCache.
//
LRESULT
SyncOnSuspend(
DWORD dwCscUpdateFlags
)
{
TraceEnter(TRACE_CSCST, "SyncOnSuspend");
HRESULT hrSyncResult = E_FAIL;
//
// Run the synchronization on a separate thread.
// See the comment above SuspendSync_ThreadProc for details.
//
// Need to create the event object BEFORE we create the sync thread
// so that the object exists before the sync starts. Only if this
// named event object exists will the CCscUpdate code signal the
// event when the operation is complete.
//
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, c_szSyncCompleteEvent);
if (NULL != hEvent)
{
HANDLE hThread = CreateThread(NULL,
0,
SuspendSync_ThreadProc,
UIntToPtr(dwCscUpdateFlags),
0,
NULL);
if (NULL != hThread)
{
//
// Wait for the sync thread to complete. This just means the call
// to CscUpdateCache has completed. We need to wait so we can
// retrieve the result from CscUpdateCache through the thread's
// exit code.
// SyncMgr will continue the sync from the mobsync.exe process
// after the thread has terminated.
//
TraceMsg("Waiting for CscUpdateCache to complete...");
WaitAndProcessThreadMessages(hThread);
//
// The thread's exit code is the HRESULT returned by CscUpdateCache.
//
DWORD dwThreadExitCode = (DWORD)E_FAIL;
GetExitCodeThread(hThread, &dwThreadExitCode);
hrSyncResult = dwThreadExitCode;
//
// We're done with the thread object.
//
CloseHandle(hThread);
hThread = NULL;
if (SUCCEEDED(hrSyncResult))
{
//
// The sync was successfully started and we're syncing prior to a
// suspend operation. Need to wait 'til the sync is complete so that
// we block the return to WM_POWERBROADCAST (PBT_APMQUERYSUSPEND).
//
TraceMsg("Waiting for sync (mobsync.exe) to complete...");
WaitAndProcessThreadMessages(hEvent);
}
}
else
{
const DWORD dwErr = GetLastError();
hrSyncResult = HRESULT_FROM_WIN32(dwErr);
Trace((TEXT("Sync thread creation failed with error %d"), dwErr));
}
CloseHandle(hEvent);
hEvent = NULL;
}
else
{
const DWORD dwErr = GetLastError();
hrSyncResult = HRESULT_FROM_WIN32(dwErr);
Trace((TEXT("Sync event creation failed with error %d"), dwErr));
}
if (FAILED(hrSyncResult))
{
CscuiEventLog log;
log.Push(hrSyncResult, CEventLog::eFmtHex);
log.ReportEvent(EVENTLOG_ERROR_TYPE,
MSG_E_SUSPEND_SYNCFAILED, 0);
}
TraceLeaveResult(hrSyncResult);
}
//
// Handles synchronization on suspend/hibernate.
// Note that we do not support sync on resume. We've
// determined that the behavior is not compelling. It is
// better to resume and let our normal UI processing
// handle any network reconnections in the normal way.
//
LRESULT
HandleSuspendSync(
CStateMachine *pSysTraySM,
HWND hWnd,
bool bOkToPromptUser
)
{
TraceEnter(TRACE_CSCST, "HandleSuspendSync");
Trace((TEXT("\tbOkToPromptUser = %d"), bOkToPromptUser));
LRESULT lResult = ERROR_SUCCESS;
BOOL bNoNet = FALSE;
CSCIsServerOffline(NULL, &bNoNet);
if (bNoNet)
{
TraceMsg("No sync performed. Network not available.");
CscuiEventLog log;
log.ReportEvent(EVENTLOG_INFORMATION_TYPE,
MSG_I_SUSPEND_NONET_NOSYNC, 2);
}
else
{
//
// Determine if we're supposed to sync or not.
// If so, we get the flags to pass to CscUpdateCache that control the
// sync behavior.
//
DWORD dwFlags = 0;
if (IsSuspendSyncRequired(bOkToPromptUser, &dwFlags))
{
lResult = SyncOnSuspend(dwFlags);
}
}
TraceLeaveValue(lResult);
}
//
// Handle any tasks that occur when the computer hibernates or is suspended.
//
LRESULT
HandleSuspendTasks(
CStateMachine *pSysTraySM,
HWND hWnd,
bool bOkToPromptUser
)
{
return HandleSuspendSync(pSysTraySM, hWnd, bOkToPromptUser);
}
#ifdef DEBUG
//
// Returns address of string corresponding to a PBT_XXXXXXX code
// sent in a WM_POWERBROADCAST message.
// Used for debug output only.
//
LPCTSTR ApmCodeName(WPARAM code)
{
static const TCHAR szUnknown[] = TEXT("<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 (NULL == g_heventTerminate)
g_heventTerminate = CreateEvent(NULL, TRUE, FALSE, NULL);
//
// This mutex is used to ensure only one admin-pin operation
// is running at a time.
//
if (NULL == g_hmutexAdminPin)
g_hmutexAdminPin = CreateMutex(NULL, FALSE, NULL);
break;
case PWM_TRAYCALLBACK:
STDBGOUT((4, TEXT("PWM_TRAYCALLBACK, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
lResult = _Notify(hWnd, wParam, lParam);
break;
#ifdef REPORT_DEVICE_CHANGES
case WM_DEVICECHANGE:
OnDeviceChange(wParam, lParam);
break;
#endif // REPORT_DEVICE_CHANGES
case WM_ENDSESSION:
TraceMsg("_HiddenWndProc: Received WM_ENDSESSION.");
if (NULL != g_heventTerminate)
{
//
// This will tell any threads that they should
// exit asap.
//
SetEvent(g_heventTerminate);
}
break;
case WM_DESTROY:
TraceMsg("_HiddenWndProc: hidden window destroyed");
#ifdef REPORT_DEVICE_CHANGES
UnregisterForDeviceNotifications();
#endif
delete pSysTraySM;
pSysTraySM = NULL;
SetWindowLongPtr(hWnd, GWLP_USERDATA, 0);
if (NULL != g_heventTerminate)
{
//
// This will tell any threads that they should
// exit asap.
//
SetEvent(g_heventTerminate);
}
DllRelease();
break;
case WM_COPYDATA:
{
// Warning: STDBGOUT here (inside WM_COPYDATA, outside the switch
// statement) would cause an infinite loop of WM_COPYDATA messages
// and blow the stack.
PCOPYDATASTRUCT pcds = (PCOPYDATASTRUCT)lParam;
if (pcds)
{
switch (pcds->dwData)
{
case STWM_CSCNETUP:
case STWM_CSCNETDOWN:
case STWM_CACHE_CORRUPTED:
{
LPTSTR pszServer = NULL;
//
// WM_COPYDATA is always sent, not posted, so copy the data
// and post a message to do the work asynchronously.
// This allocated string will be freed by the CSCWM_UPDATESTATUS
// handler UpdateStatus(). No need to free it here.
//
STDBGOUT((3, TEXT("Rcvd WM_COPYDATA, uMsg = 0x%08X, server = \"%s\""), pcds->dwData, pcds->lpData));
LocalAllocString(&pszServer, (LPTSTR)pcds->lpData);
PostMessage(hWnd, CSCWM_UPDATESTATUS, pcds->dwData, (LPARAM)pszServer);
}
break;
case CSCWM_GETSHARESTATUS:
// This one comes from outside of cscui.dll, and
// is always UNICODE.
if (pcds->lpData)
{
TCHAR szShare[MAX_PATH];
SHUnicodeToTChar((LPWSTR)pcds->lpData, szShare, ARRAYSIZE(szShare));
STDBGOUT((3, TEXT("Rcvd CSCWM_GETSHARESTATUS for \"%s\""), szShare));
lResult = GetShareStatusForWebView(pSysTraySM, szShare);
}
break;
case PWM_REFRESH_SHELL:
STDBGOUT((3, TEXT("Rcvd WM_COPYDATA, PWM_REFRESH_SHELL, server = \"%s\""), pcds->lpData));
if (pcds->lpData)
{
LPTSTR pszServer = NULL;
LocalAllocString(&pszServer, (LPTSTR)pcds->lpData);
PostMessage(hWnd, PWM_REFRESH_SHELL, 0, (LPARAM)pszServer);
}
break;
#if DBG
//
// The following messages in the "#if DBG" block are to support the
// monitoring feature of the hidden systray window.
//
case PWM_STDBGOUT:
// Warning: no STDBGOUT here
STDebugOnLogEvent(GetDlgItem(hWnd, IDC_DEBUG_LIST), (LPCTSTR)pcds->lpData);
break;
#endif // DBG
}
}
}
break;
case CSCWM_ISSERVERBACK:
STDBGOUT((2, TEXT("Rcvd CSCWM_ISSERVERBACK")));
lResult = IsServerBack(pSysTraySM, (LPCTSTR)lParam);
break;
case CSCWM_DONESYNCING:
STDBGOUT((1, TEXT("Rcvd CSCWM_DONESYNCING. wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
pSysTraySM->PingServers();
UpdateStatus(pSysTraySM, hWnd, STWM_STATUSCHECK, NULL);
break;
case CSCWM_UPDATESTATUS:
UpdateStatus(pSysTraySM, hWnd, (UINT)wParam, (LPTSTR)lParam);
if (lParam)
LocalFree((LPTSTR)lParam); // We make a copy when we get WM_COPYDATA
break;
case PWM_RESET_REMINDERTIMER:
STDBGOUT((2, TEXT("Rcvd PWM_RESET_REMINDERTIMER")));
OnResetReminderTimer();
break;
case PWM_HANDLE_LOGON_TASKS:
HandleLogonTasks();
break;
case PWM_REFRESH_SHELL:
STDBGOUT((3, TEXT("Rcvd PWM_REFRESH_SHELL, server = \"%s\""), (LPCTSTR)lParam));
_RefreshAllExplorerWindows((LPCTSTR)lParam);
//
// lParam is a server name allocated with LocalAlloc.
//
if (lParam)
LocalFree((LPTSTR)lParam);
break;
case CSCWM_VIEWFILES:
COfflineFilesFolder::Open();
break;
case PWM_STATUSDLG:
ShowCSCUIStatusDlg(hWnd);
break;
case PWM_QUERY_UISTATE:
lResult = OnQueryUIState();
break;
case CSCWM_SYNCHRONIZE:
STDBGOUT((1, TEXT("Rcvd CSCWM_SYNCHRONIZE")));
OnSynchronize();
break;
case CSCWM_SETTINGS:
COfflineFilesSheet::CreateAndRun(g_hInstance, GetDesktopWindow(), &g_cRefCount);
break;
case WM_WININICHANGE:
STDBGOUT((1, TEXT("Rcvd WM_WININICHANGE. wParam = %d, lParam = \"%s\""),
wParam, lParam ? (LPCTSTR)lParam : TEXT("<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 && (IsBadStringPtr(pszServer, 48) || !*pszServer))
pszServer = NULL;
switch(uMsg)
{
case STWM_CSCQUERYNETDOWN:
STDBGOUT((1, TEXT("Rcvd STWM_CSCQUERYNETDOWN, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
lRes = OnQueryNetDown((DWORD)wParam, pszServer);
//
// HACK! This is a hack to handle the way the redirector and the CSC
// agent work in the "net down" case. The CSC agent tells us
// about "no net" in a CSCQUERYNETDOWN rather than a CSCNETDOWN
// like I would prefer it. The problem is that the redirector
// doesn't actually transition the servers to offline until
// a server is touched. Therefore, when lParam == 0
// we need to first handle the "query" case to determine what to tell
// the CSC agent (fail, work offline, retry etc). Then, if the
// result is not "retry", we need to continue processing the message
// as if it were STWM_CSCNETDOWN. [brianau]
//
if (LRESULT_CSCRETRY == lRes || NULL != pszServer)
return lRes;
uMsg = STWM_CSCNETDOWN;
//
// Fall through...
//
case STWM_CSCNETDOWN:
STDBGOUT((1, TEXT("Rcvd STWM_CSCNETDOWN, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
CSCUI_NOTIFYHOOK((CSCH_NotifyOffline, TEXT("NetDown: %1"), pszServer ? pszServer : TEXT("<no server>")));
break;
case STWM_CSCNETUP:
STDBGOUT((1, TEXT("Rcvd STWM_CSCNETUP, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
CSCUI_NOTIFYHOOK((CSCH_NotifyAvailable, TEXT("NetBack: %1"), pszServer ? pszServer : TEXT("<no server>")));
break;
case STWM_CACHE_CORRUPTED:
STDBGOUT((1, TEXT("Rcvd STWM_CACHE_CORRUPTED, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
break;
}
//
// If we have a server name, use WM_COPYDATA to get the data
// into explorer's process.
//
if (pszServer)
{
SendCopyDataToSystray(uMsg, StringByteSize(pszServer), pszServer);
}
else
{
PostToSystray(CSCWM_UPDATESTATUS, uMsg, 0);
}
return lRes;
}
const TCHAR c_szExploreClass[] = TEXT("ExploreWClass");
const TCHAR c_szIExploreClass[] = TEXT("IEFrame");
const TCHAR c_szCabinetClass[] = TEXT("CabinetWClass");
const TCHAR c_szDesktopClass[] = TEXT(STR_DESKTOPCLASS);
BOOL IsExplorerWindow(HWND hwnd)
{
TCHAR szClass[32];
GetClassName(hwnd, szClass, ARRAYSIZE(szClass));
if ( (lstrcmp(szClass, c_szCabinetClass) == 0)
||(lstrcmp(szClass, c_szIExploreClass) == 0)
||(lstrcmp(szClass, c_szExploreClass) == 0))
return TRUE;
return FALSE;
}
//
// IsWindowBrowsingServer determines if a given window is browsing a particular
// server. The function assumes that the window is an explorer window.
// If pszServer == NULL, return TRUE if the window is browsing a remote path
// even if the window is not browsing this particular server.
//
BOOL IsWindowBrowsingServer(
HWND hwnd,
LPCTSTR pszServer
)
{
BOOL bResult = FALSE;
DWORD_PTR dwResult;
DWORD dwPID = GetCurrentProcessId();
const UINT uFlags = SMTO_NORMAL | SMTO_ABORTIFHUNG;
if (SendMessageTimeout(hwnd,
CWM_CLONEPIDL,
(WPARAM)dwPID,
0L,
uFlags,
5000,
&dwResult))
{
HANDLE hmem = (HANDLE)dwResult;
if (NULL != hmem)
{
LPITEMIDLIST pidl = (LPITEMIDLIST)SHLockShared(hmem, dwPID);
if (NULL != pidl)
{
TCHAR szPath[MAX_PATH];
if (SHGetPathFromIDList(pidl, szPath))
{
LPTSTR pszRemotePath;
if (S_OK == GetRemotePath(szPath, &pszRemotePath))
{
if (NULL == pszServer)
{
bResult = TRUE;
}
else
{
PathStripToRoot(pszRemotePath);
PathRemoveFileSpec(pszRemotePath);
bResult = (0 == lstrcmpi(pszServer, pszRemotePath));
}
LocalFreeString(&pszRemotePath);
}
}
SHUnlockShared(pidl);
}
SHFreeShared(hmem, dwPID);
}
}
return bResult;
}
void RefreshExplorerWindow(HWND hwnd, LPCTSTR pszServer)
{
PostMessage(hwnd, WM_COMMAND, FCIDM_REFRESH, 0L);
}
BOOL CALLBACK _RefreshEnum(HWND hwnd, LPARAM lParam)
{
LPCTSTR pszServer = (LPCTSTR)lParam;
if (IsExplorerWindow(hwnd) && IsWindowBrowsingServer(hwnd, pszServer))
{
STDBGOUT((2, TEXT("Refreshing explorer wnd 0x%08X for \"%s\""), hwnd, pszServer));
RefreshExplorerWindow(hwnd, pszServer);
}
return(TRUE);
}
//
// _RefreshAllExplorerWindows is called by the CSC tray wnd proc
// in response to a PWM_REFRESH_SHELL message. The pszServer argument
// is the name of the server (i.e. "\\scratch") that has transitioned
// either online or offline. The function refreshes windows that are
// currently browsing the server.
//
// If pszServer is NULL, the function refreshes all windows browsing the net.
//
void _RefreshAllExplorerWindows(LPCTSTR pszServer)
{
//
// Without initializing COM, we hit a "com not initialized" assertion
// in shdcocvw when calling SHGetPathFromIDList in IsWindowBrowsingServer.
//
if (SUCCEEDED(CoInitialize(NULL)))
{
//
// Note that the enumeration doesn't catch the desktop window,
// but we don't care. Change notifications are working now, so
// content is updated properly. We're only doing this refresh stuff
// to keep WebView up-to-date with respect to online/offline state.
// The desktop doesn't have that, so no need to refresh it.
//
EnumWindows(_RefreshEnum, (LPARAM)pszServer);
CoUninitialize();
}
}
STDAPI_(BOOL) CSCUIMsgProcess(LPMSG pMsg)
{
return IsDialogMessage(g_hwndStatusDlg, pMsg);
}
//-----------------------------------------------------------------------------
// SysTray debug monitoring code.
//
//
// This function can run in either winlogon, systray or mobsync processes.
// That's why we use WM_COPYDATA to communicate the text information.
//
#if DBG
void STDebugOut(
int iLevel,
LPCTSTR pszFmt,
...
)
{
if (STDebugLevel() >= iLevel)
{
TCHAR szText[1024];
SYSTEMTIME t;
GetLocalTime(&t);
wsprintf(szText, TEXT("[pid %d] %02d:%02d:%02d.%03d "),
GetCurrentProcessId(),
t.wHour,
t.wMinute,
t.wSecond,
t.wMilliseconds);
va_list args;
va_start(args, pszFmt);
wvsprintf(szText + lstrlen(szText), pszFmt, args);
va_end(args);
COPYDATASTRUCT cds;
cds.dwData = PWM_STDBGOUT;
cds.cbData = StringByteSize(szText);
cds.lpData = szText;
SendToSystray(WM_COPYDATA, 0, (LPARAM)&cds);
}
}
int STDebugLevel(void)
{
static DWORD dwMonitor = (DWORD)-1;
if ((DWORD)-1 == dwMonitor)
{
dwMonitor = 0;
HKEY hkey;
DWORD dwType;
DWORD cbData = sizeof(DWORD);
DWORD dwStatus = STDebugOpenNetCacheKey(KEY_QUERY_VALUE, &hkey);
if (ERROR_SUCCESS == dwStatus)
{
RegQueryValueEx(hkey,
c_szSysTrayOutput,
NULL,
&dwType,
(LPBYTE)&dwMonitor,
&cbData);
RegCloseKey(hkey);
}
}
return int(dwMonitor);
}
//
// Called in response to PWM_STDBGOUT. This occurs in the systray process only.
//
void STDebugOnLogEvent(
HWND hwndList,
LPCTSTR pszText
)
{
if (pszText && *pszText)
{
int iTop = (int)SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)pszText);
SendMessage(hwndList, LB_SETTOPINDEX, iTop - 5, 0);
}
}
typedef BOOL (WINAPI * PFNGETSAVEFILENAME)(LPOPENFILENAME);
#ifdef UNICODE
# define GETSAVEFILENAME "GetSaveFileNameW"
#else
# define GETSAVEFILENAME "GetSaveFileNameA"
#endif
//
// This function will always run on the window's thread and in the systray process.
//
void STDebugSaveListboxContent(
HWND hwndParent
)
{
static bool bSaving = false; // Re-entrancy guard.
if (bSaving)
return;
HMODULE hModComdlg = LoadLibrary(TEXT("comdlg32"));
if (NULL == hModComdlg)
return;
PFNGETSAVEFILENAME pfnSaveFileName = (PFNGETSAVEFILENAME)GetProcAddress(hModComdlg, GETSAVEFILENAME);
if (NULL != pfnSaveFileName)
{
bSaving = true;
TCHAR szFile[MAX_PATH] = TEXT("C:\\CSCUISystrayLog.txt");
OPENFILENAME ofn = {0};
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwndParent;
ofn.hInstance = g_hInstance;
ofn.lpstrFile = szFile;
ofn.nMaxFile = ARRAYSIZE(szFile);
ofn.lpstrDefExt = TEXT("txt");
if ((*pfnSaveFileName)(&ofn))
{
HANDLE hFile = CreateFile(szFile,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
int n = (int)SendDlgItemMessage(hwndParent, IDC_DEBUG_LIST, LB_GETCOUNT, 0, 0);
TCHAR szText[MAX_PATH];
for (int i = 0; i < n; i++)
{
//
// WARNING: This could potentially overwrite the szText[] buffer.
// However, since the text should be of a readable length
// in a listbox I doubt if it will exceed MAX_PATH.
//
SendDlgItemMessage(hwndParent, IDC_DEBUG_LIST, LB_GETTEXT, i, (LPARAM)szText);
lstrcat(szText, TEXT("\r\n"));
DWORD dwWritten = 0;
WriteFile(hFile, szText, lstrlen(szText) * sizeof(TCHAR), &dwWritten, NULL);
}
CloseHandle(hFile);
}
else
{
TCHAR szMsg[MAX_PATH];
wsprintf(szMsg, TEXT("Error %d creating file \"%s\""), GetLastError(), szFile);
MessageBox(hwndParent, szMsg, STR_CSCHIDDENWND_TITLE, MB_ICONERROR | MB_OK);
}
}
}
bSaving = false;
FreeLibrary(hModComdlg);
}
DWORD STDebugOpenNetCacheKey(
DWORD dwAccess,
HKEY *phkey
)
{
DWORD dwDisposition;
return RegCreateKeyEx(HKEY_LOCAL_MACHINE,
REGSTR_KEY_OFFLINEFILES,
0,
NULL,
0,
dwAccess,
NULL,
phkey,
&dwDisposition);
}
#endif // DBG