3104 lines
89 KiB
C++
3104 lines
89 KiB
C++
|
#include "shellprv.h"
|
||
|
#pragma hdrstop
|
||
|
|
||
|
#include <initguid.h>
|
||
|
|
||
|
#include <dbt.h>
|
||
|
#include "printer.h"
|
||
|
#include <dpa.h>
|
||
|
#include "idltree.h"
|
||
|
#include "scnotifyp.h"
|
||
|
#include "mtpt.h"
|
||
|
|
||
|
#include "shitemid.h"
|
||
|
|
||
|
#include <ioevent.h>
|
||
|
|
||
|
#define TF_SHELLCHANGENOTIFY 0x40000
|
||
|
|
||
|
#define SCNM_REGISTERCLIENT WM_USER + 1
|
||
|
#define SCNM_DEREGISTERCLIENT WM_USER + 2
|
||
|
#define SCNM_NOTIFYEVENT WM_USER + 3
|
||
|
#define SCNM_FLUSHEVENTS WM_USER + 4
|
||
|
#define SCNM_TERMINATE WM_USER + 5
|
||
|
#define SCNM_SUSPENDRESUME WM_USER + 6
|
||
|
#define SCNM_DEREGISTERWINDOW WM_USER + 7
|
||
|
#define SCNM_AUTOPLAYDRIVE WM_USER + 8
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
FLUSH_OVERFLOW = 1,
|
||
|
FLUSH_SOFT,
|
||
|
FLUSH_HARD,
|
||
|
FLUSH_INTERRUPT,
|
||
|
};
|
||
|
|
||
|
#define IDT_SCN_FLUSHEVENTS 1
|
||
|
#define IDT_SCN_FRESHENTREES 2
|
||
|
|
||
|
#define EVENT_OVERFLOW 10
|
||
|
|
||
|
HWND g_hwndSCN = NULL;
|
||
|
CChangeNotify *g_pscn = NULL;
|
||
|
EXTERN_C CRITICAL_SECTION g_csSCN;
|
||
|
CRITICAL_SECTION g_csSCN = {0};
|
||
|
|
||
|
#define PERFTEST(x)
|
||
|
|
||
|
EXTERN_C void SFP_FSEvent (LONG lEvent, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra);
|
||
|
EXTERN_C int WINAPI RLFSChanged (LONG lEvent, LPITEMIDLIST pidl, LPITEMIDLIST pidlExtra);
|
||
|
STDAPI CFSFolder_IconEvent(LONG lEvent, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra);
|
||
|
STDAPI_(HWND) _SCNGetWindow(BOOL fUseDesktop, BOOL fNeedsFallback);
|
||
|
|
||
|
STDAPI SHChangeNotifyAutoplayDrive(PCWSTR pszDrive)
|
||
|
{
|
||
|
ASSERT(PathIsRoot(pszDrive));
|
||
|
HWND hwnd = _SCNGetWindow(TRUE, FALSE);
|
||
|
if (hwnd)
|
||
|
{
|
||
|
DWORD dwProcessID = 0;
|
||
|
GetWindowThreadProcessId(hwnd, &dwProcessID);
|
||
|
if (dwProcessID)
|
||
|
{
|
||
|
AllowSetForegroundWindow(dwProcessID);
|
||
|
}
|
||
|
PostMessage(g_hwndSCN, SCNM_AUTOPLAYDRIVE, DRIVEID(pszDrive), 0);
|
||
|
return S_OK;
|
||
|
}
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// special folders that are aliases. these are always running
|
||
|
// csidlAlias refers to the users perceived namespace
|
||
|
// csidlReal refers to the actual filesystem folder behind the alias
|
||
|
//
|
||
|
typedef struct ALIASFOLDER {
|
||
|
int csidlAlias;
|
||
|
int csidlReal;
|
||
|
} ALIASFOLDER, *PALIASFOLDER;
|
||
|
|
||
|
static const ALIASFOLDER s_rgaf[] = {
|
||
|
{CSIDL_DESKTOP, CSIDL_DESKTOPDIRECTORY},
|
||
|
{CSIDL_DESKTOP, CSIDL_COMMON_DESKTOPDIRECTORY },
|
||
|
{CSIDL_PERSONAL, CSIDL_PERSONAL | CSIDL_FLAG_NO_ALIAS},
|
||
|
{CSIDL_NETWORK, CSIDL_NETHOOD},
|
||
|
{CSIDL_PRINTERS, CSIDL_PRINTHOOD},
|
||
|
};
|
||
|
|
||
|
void InitAliasFolderTable(void)
|
||
|
{
|
||
|
for (int i = 0; i < ARRAYSIZE(s_rgaf); i++)
|
||
|
{
|
||
|
g_pscn->AddSpecialAlias(s_rgaf[i].csidlReal, s_rgaf[i].csidlAlias);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#pragma pack(1)
|
||
|
typedef struct {
|
||
|
WORD cb;
|
||
|
LONG lEEvent;
|
||
|
} ALIASREGISTER;
|
||
|
|
||
|
typedef struct {
|
||
|
ALIASREGISTER ar;
|
||
|
WORD wNull;
|
||
|
} ALIASREGISTERLIST;
|
||
|
#pragma pack()
|
||
|
|
||
|
STDAPI_(void) SHChangeNotifyRegisterAlias(LPCITEMIDLIST pidlReal, LPCITEMIDLIST pidlAlias)
|
||
|
{
|
||
|
static const ALIASREGISTERLIST arl = { {sizeof(ALIASREGISTER), SHCNEE_ALIASINUSE}, 0};
|
||
|
LPITEMIDLIST pidlRegister = ILCombine((LPCITEMIDLIST)&arl, pidlReal);
|
||
|
|
||
|
if (pidlRegister)
|
||
|
{
|
||
|
SHChangeNotify(SHCNE_EXTENDED_EVENT, SHCNF_ONLYNOTIFYINTERNALS | SHCNF_IDLIST, pidlRegister, pidlAlias);
|
||
|
ILFree(pidlRegister);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LPCITEMIDLIST IsAliasRegisterPidl(LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
ALIASREGISTER *par = (ALIASREGISTER *)pidl;
|
||
|
|
||
|
if (par->cb == sizeof(ALIASREGISTER)
|
||
|
&& par->lEEvent == SHCNEE_ALIASINUSE)
|
||
|
return _ILNext(pidl);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
LONG g_cAliases = 0;
|
||
|
|
||
|
LPITEMIDLIST TranslateAlias(LPCITEMIDLIST pidl, LPCITEMIDLIST pidlReal, LPCITEMIDLIST pidlAlias)
|
||
|
{
|
||
|
// see if its child of one of our watched items
|
||
|
|
||
|
LPCITEMIDLIST pidlChild = pidl ? ILFindChild(pidlReal, pidl) : NULL;
|
||
|
if (pidlChild)
|
||
|
{
|
||
|
return ILCombine(pidlAlias, pidlChild);
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
CAnyAlias::~CAnyAlias()
|
||
|
{
|
||
|
ILFree(_pidlAlias);
|
||
|
|
||
|
ATOMICRELEASE(_ptscn);
|
||
|
}
|
||
|
|
||
|
BOOL CCollapsingClient::Init(LPCITEMIDLIST pidl, BOOL fRecursive)
|
||
|
{
|
||
|
_pidl = ILClone(pidl);
|
||
|
_fRecursive = fRecursive;
|
||
|
return (_pidl && _dpaPendingEvents.Create(EVENT_OVERFLOW + 1));
|
||
|
}
|
||
|
|
||
|
BOOL CAnyAlias::Init(LPCITEMIDLIST pidlReal, LPCITEMIDLIST pidlAlias)
|
||
|
{
|
||
|
ASSERT(!_fSpecial);
|
||
|
_pidlAlias = ILClone(pidlAlias);
|
||
|
|
||
|
return (_pidlAlias && CCollapsingClient::Init(pidlReal, TRUE));
|
||
|
}
|
||
|
|
||
|
BOOL CAnyAlias::_WantsEvent(LONG lEvent)
|
||
|
{
|
||
|
return (lEvent & (SHCNE_DISKEVENTS | SHCNE_DRIVEREMOVED | SHCNE_NETSHARE | SHCNE_NETUNSHARE));
|
||
|
}
|
||
|
|
||
|
BOOL CAnyAlias::InitSpecial(int csidlReal, int csidlAlias)
|
||
|
{
|
||
|
_fSpecial = TRUE;
|
||
|
_csidlReal = csidlReal;
|
||
|
_csidlAlias = csidlAlias;
|
||
|
|
||
|
LPITEMIDLIST pidlNew;
|
||
|
|
||
|
WIN32_FIND_DATA fd = {0};
|
||
|
|
||
|
fd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; // Special folders are always directories
|
||
|
SHGetSpecialFolderPath(NULL, fd.cFileName, csidlReal | CSIDL_FLAG_DONT_VERIFY, FALSE);
|
||
|
SHSimpleIDListFromFindData(fd.cFileName, &fd, &pidlNew);
|
||
|
|
||
|
SHGetSpecialFolderLocation(NULL, csidlAlias | CSIDL_FLAG_DONT_VERIFY, &_pidlAlias);
|
||
|
|
||
|
BOOL fRet = _pidlAlias && CCollapsingClient::Init(pidlNew, TRUE);
|
||
|
ILFree(pidlNew);
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
BOOL CAnyAlias::IsAlias(LPCITEMIDLIST pidlReal, LPCITEMIDLIST pidlAlias)
|
||
|
{
|
||
|
// if this hits, an alias has been registered already
|
||
|
// this means the guy doing the registration isn't doing it at the junction point like
|
||
|
// theyre supposed to
|
||
|
ASSERT((ILIsEqual(pidlReal, _pidl) && ILIsEqual(pidlAlias, _pidlAlias)) ||
|
||
|
!(ILIsParent(_pidl, pidlReal, FALSE) && ILIsParent(_pidlAlias, pidlAlias, FALSE)));
|
||
|
|
||
|
return (ILIsEqual(pidlReal, _pidl)
|
||
|
&& ILIsEqual(pidlAlias, _pidlAlias));
|
||
|
}
|
||
|
|
||
|
BOOL CAnyAlias::IsSpecial(int csidlReal, int csidlAlias)
|
||
|
{
|
||
|
return (_fSpecial && csidlReal == _csidlReal && csidlAlias == _csidlAlias);
|
||
|
}
|
||
|
|
||
|
CAnyAlias::_CustomTranslate()
|
||
|
{
|
||
|
if (!_fCheckedCustom)
|
||
|
{
|
||
|
SHBindToObjectEx(NULL, _pidlAlias, NULL, IID_PPV_ARG(ITranslateShellChangeNotify, &_ptscn));
|
||
|
_fCheckedCustom = TRUE;
|
||
|
}
|
||
|
return (_ptscn != NULL);
|
||
|
}
|
||
|
|
||
|
// some pidl translators may not translate the event. if we pass on a notifyevent thats identical,
|
||
|
// we'll get into an infinite loop. our translators are good about this so this doesnt happen, but
|
||
|
// we'll catch it here to be more robust -- we wouldnt want a bad translating shell extension to
|
||
|
// be able to spinlock the changenotify thread.
|
||
|
BOOL CAnyAlias::_OkayToNotifyTranslatedEvent(CNotifyEvent *pne, LONG lEvent, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra)
|
||
|
{
|
||
|
// mydocs has an issue where it can be removed from the desktop when
|
||
|
// it is redirected, because the alias only propagates the first
|
||
|
// half of the notification (remove). so we dont Translate the remove.
|
||
|
if (_fSpecial && _csidlAlias == CSIDL_PERSONAL)
|
||
|
{
|
||
|
if (pne->lEvent == SHCNE_RENAMEFOLDER || pne->lEvent == SHCNE_RMDIR)
|
||
|
{
|
||
|
if (ILIsEqual(pidl, _pidlAlias))
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if the original event wasn't already translated, let it proceed.
|
||
|
|
||
|
// if its a different event, its fine -- a translator could flip-flop between events but we cant detect that case.
|
||
|
|
||
|
// in addition we need to beware of aliases that translate to themselves or their children --
|
||
|
// for example a my computer shortcut in the start menu will be registered recursively, so if you try
|
||
|
// to delete it it will get into a loop.
|
||
|
// so if the events are the same, verify that both the resultant pidls aren't underneath _pidl.
|
||
|
|
||
|
return !(pne->uEventFlags & SHCNF_TRANSLATEDALIAS) ||
|
||
|
(lEvent != pne->lEvent) ||
|
||
|
!(pidl && ILIsParent(_pidl, pidl, FALSE)) && !(pidlExtra && ILIsParent(_pidl, pidlExtra, FALSE));
|
||
|
}
|
||
|
|
||
|
void CAnyAlias::_SendNotification(CNotifyEvent *pne, BOOL fNeedsCallbackEvent, SENDASYNCPROC pfncb)
|
||
|
{
|
||
|
//
|
||
|
// see if its child of one of our watched items
|
||
|
|
||
|
if (_CustomTranslate())
|
||
|
{
|
||
|
LPITEMIDLIST pidl1Alias = pne->pidl;
|
||
|
LPITEMIDLIST pidl1AliasExtra = pne->pidlExtra;
|
||
|
LPITEMIDLIST pidl2Alias = NULL, pidl2AliasExtra = NULL;
|
||
|
LONG lEvent1 = pne->lEvent & ~SHCNE_INTERRUPT; // translator shouldn't see this flag
|
||
|
LONG lEvent2 = -1;
|
||
|
if (SUCCEEDED(_ptscn->TranslateIDs(&lEvent1, pne->pidl, pne->pidlExtra, &pidl1Alias, &pidl1AliasExtra,
|
||
|
&lEvent2, &pidl2Alias, &pidl2AliasExtra)))
|
||
|
{
|
||
|
if (_OkayToNotifyTranslatedEvent(pne, lEvent1, pidl1Alias, pidl1AliasExtra))
|
||
|
{
|
||
|
g_pscn->NotifyEvent(lEvent1, SHCNF_IDLIST | SHCNF_TRANSLATEDALIAS,
|
||
|
pidl1Alias, pidl1AliasExtra,
|
||
|
pne->dwEventTime);
|
||
|
}
|
||
|
|
||
|
if ((lEvent2 != -1) && _OkayToNotifyTranslatedEvent(pne, lEvent2, pidl2Alias, pidl2AliasExtra))
|
||
|
{
|
||
|
g_pscn->NotifyEvent(lEvent2, SHCNF_IDLIST | SHCNF_TRANSLATEDALIAS,
|
||
|
pidl2Alias, pidl2AliasExtra,
|
||
|
pne->dwEventTime);
|
||
|
}
|
||
|
if (pidl1Alias != pne->pidl)
|
||
|
ILFree(pidl1Alias);
|
||
|
if (pidl1AliasExtra != pne->pidlExtra)
|
||
|
ILFree(pidl1AliasExtra);
|
||
|
ILFree(pidl2Alias);
|
||
|
ILFree(pidl2AliasExtra);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
LPITEMIDLIST pidlAlias = TranslateAlias(pne->pidl, _pidl, _pidlAlias);
|
||
|
LPITEMIDLIST pidlAliasExtra = TranslateAlias(pne->pidlExtra, _pidl, _pidlAlias);
|
||
|
|
||
|
if (pidlAlias || pidlAliasExtra)
|
||
|
{
|
||
|
LPCITEMIDLIST pidlNotify = pidlAlias ? pidlAlias : pne->pidl;
|
||
|
LPCITEMIDLIST pidlNotifyExtra = pidlAliasExtra ? pidlAliasExtra : pne->pidlExtra;
|
||
|
if (_OkayToNotifyTranslatedEvent(pne, pne->lEvent, pidlNotify, pidlNotifyExtra))
|
||
|
{
|
||
|
g_pscn->NotifyEvent(pne->lEvent, SHCNF_IDLIST | SHCNF_TRANSLATEDALIAS,
|
||
|
pidlNotify, pidlNotifyExtra,
|
||
|
pne->dwEventTime);
|
||
|
}
|
||
|
|
||
|
// do some special handling here
|
||
|
// like refresh folders or something will clean out an entry.
|
||
|
switch (pne->lEvent)
|
||
|
{
|
||
|
case SHCNE_UPDATEDIR:
|
||
|
if (!_fSpecial && ILIsEqual(pne->pidl, _pidl))
|
||
|
{
|
||
|
// this is target, and it will be refreshed.
|
||
|
// if the alias is still around, then it will
|
||
|
// have to reenum and re-register
|
||
|
// there-fore we will clean this out now.
|
||
|
_fRemove = TRUE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
ILFree(pidlAlias);
|
||
|
ILFree(pidlAliasExtra);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// this is the notify we get when a drive mapping is deleted
|
||
|
// when this happens we need to kill the aliases to that drive
|
||
|
if (pne->lEvent == SHCNE_DRIVEREMOVED)
|
||
|
{
|
||
|
if (!_fSpecial && ILIsEqual(pne->pidl, _pidlAlias))
|
||
|
{
|
||
|
// when net drives are removed
|
||
|
// pidlExtra is the UNC
|
||
|
_fRemove = TRUE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CAnyAlias::Activate(BOOL fActivate)
|
||
|
{
|
||
|
if (fActivate)
|
||
|
{
|
||
|
ASSERT(_cActivated >= 0);
|
||
|
if (!_cActivated++)
|
||
|
{
|
||
|
// turn this puppy on!
|
||
|
_fRemove = FALSE;
|
||
|
if (!_fInterrupt)
|
||
|
_fInterrupt = g_pscn->AddInterruptSource(_pidl, TRUE);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ASSERT(_cActivated > 0);
|
||
|
if (!--_cActivated)
|
||
|
{
|
||
|
// now turn it off
|
||
|
_fRemove = TRUE;
|
||
|
g_pscn->SetFlush(FLUSH_SOFT);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::_CheckAliasRollover(void)
|
||
|
{
|
||
|
static DWORD s_tick = 0;
|
||
|
DWORD tick = GetTickCount();
|
||
|
|
||
|
if (tick < s_tick)
|
||
|
{
|
||
|
// we rolled the tick count over
|
||
|
CLinkedWalk<CAnyAlias> lw(&_listAliases);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
lw.That()->_dwTime = tick;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
s_tick = tick;
|
||
|
}
|
||
|
|
||
|
CAnyAlias *CChangeNotify::_FindSpecialAlias(int csidlReal, int csidlAlias)
|
||
|
{
|
||
|
CLinkedWalk<CAnyAlias> lw(&_listAliases);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
CAnyAlias *paa = lw.That();
|
||
|
if (paa->IsSpecial(csidlReal, csidlAlias))
|
||
|
{
|
||
|
// we found it
|
||
|
return paa;
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
CAnyAlias *CChangeNotify::_FindAlias(LPCITEMIDLIST pidlReal, LPCITEMIDLIST pidlAlias)
|
||
|
{
|
||
|
CLinkedWalk<CAnyAlias> lw(&_listAliases);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
CAnyAlias *paa = lw.That();
|
||
|
if (paa->IsAlias(pidlReal, pidlAlias))
|
||
|
{
|
||
|
// we found it
|
||
|
return paa;
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::AddSpecialAlias(int csidlReal, int csidlAlias)
|
||
|
{
|
||
|
CAnyAlias *paa = _FindSpecialAlias(csidlReal, csidlAlias);
|
||
|
|
||
|
if (!paa)
|
||
|
{
|
||
|
CLinkedNode<CAnyAlias> *p = new CLinkedNode<CAnyAlias>;
|
||
|
if (p)
|
||
|
{
|
||
|
if (p->that.InitSpecial(csidlReal, csidlAlias))
|
||
|
{
|
||
|
if (_InsertAlias(p))
|
||
|
paa = &p->that;
|
||
|
}
|
||
|
|
||
|
if (!paa)
|
||
|
delete p;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::UpdateSpecialAlias(int csidlAlias)
|
||
|
{
|
||
|
for (int i = 0; i < ARRAYSIZE(s_rgaf); i++)
|
||
|
{
|
||
|
if (csidlAlias == s_rgaf[i].csidlAlias)
|
||
|
{
|
||
|
CLinkedNode<CAnyAlias> *p = new CLinkedNode<CAnyAlias>;
|
||
|
if (p)
|
||
|
{
|
||
|
if (!p->that.InitSpecial(s_rgaf[i].csidlReal, csidlAlias)
|
||
|
|| !_InsertAlias(p))
|
||
|
{
|
||
|
delete p;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// the semantic of the return value of this function is not necessarily success or failure,
|
||
|
// since it's possible to stick something in _ptreeAliases with AddData and not be able to
|
||
|
// clean up and remove it with RemoveData (if CompareIDs fails along the way).
|
||
|
// reordering our inserts won't help since g_pscn->AddClient does the same thing.
|
||
|
// so,
|
||
|
// return TRUE == do not free p, something has ownership
|
||
|
// return FALSE == free p, we dont reference it anywhere
|
||
|
BOOL CChangeNotify::_InsertAlias(CLinkedNode<CAnyAlias> *p)
|
||
|
{
|
||
|
BOOL fRet = _InitTree(&_ptreeAliases);
|
||
|
if (fRet)
|
||
|
{
|
||
|
fRet = _listAliases.Insert(p);
|
||
|
if (fRet)
|
||
|
{
|
||
|
fRet = SUCCEEDED(_ptreeAliases->AddData(IDLDATAF_MATCH_RECURSIVE, p->that._pidlAlias, (INT_PTR)&p->that));
|
||
|
if (fRet)
|
||
|
{
|
||
|
fRet = g_pscn->AddClient(IDLDATAF_MATCH_RECURSIVE, p->that._pidl, NULL, FALSE, SAFECAST(&p->that, CCollapsingClient *));
|
||
|
if (fRet)
|
||
|
{
|
||
|
if (_ptreeClients)
|
||
|
{
|
||
|
// now tell all the registered clients already waiting on this to wake up.
|
||
|
CLinkedWalk<CRegisteredClient> lw(&_listClients);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
if (ILIsParent(p->that._pidlAlias, lw.That()->_pidl, FALSE))
|
||
|
{
|
||
|
// increase activation count one time on this alias for each client that wants this one.
|
||
|
p->that.Activate(TRUE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// if we blow it, then we need to clean up.
|
||
|
// right now both the tree and _listAliases have p.
|
||
|
_listAliases.Remove(p); // the list always succeeds
|
||
|
if (FAILED(_ptreeAliases->RemoveData(p->that._pidlAlias, (INT_PTR)&p->that)))
|
||
|
{
|
||
|
// oh no! we added it to the tree but we cant find it to remove it.
|
||
|
// return TRUE to prevent freeing it later.
|
||
|
fRet = TRUE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// we only have to remove from _listAliases.
|
||
|
_listAliases.Remove(p); // the list always succeeds
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::AddAlias(LPCITEMIDLIST pidlReal, LPCITEMIDLIST pidlAlias, DWORD dwEventTime)
|
||
|
{
|
||
|
CAnyAlias *paa = _FindAlias(pidlReal, pidlAlias);
|
||
|
|
||
|
if (!paa)
|
||
|
{
|
||
|
CLinkedNode<CAnyAlias> *p = new CLinkedNode<CAnyAlias>;
|
||
|
if (p)
|
||
|
{
|
||
|
if (p->that.Init(pidlReal, pidlAlias))
|
||
|
{
|
||
|
if (_InsertAlias(p))
|
||
|
{
|
||
|
paa = &p->that;
|
||
|
g_cAliases++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!paa)
|
||
|
delete p;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (paa)
|
||
|
{
|
||
|
// we just want to update the time on the existing entry
|
||
|
paa->_dwTime = dwEventTime;
|
||
|
paa->_fRemove = FALSE;
|
||
|
_CheckAliasRollover();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BOOL CAnyAlias::Remove()
|
||
|
{
|
||
|
if (_fRemove)
|
||
|
{
|
||
|
if (_fSpecial)
|
||
|
{
|
||
|
// we dont remove the special aliases,
|
||
|
// we only quiet them a little
|
||
|
if (_fInterrupt)
|
||
|
{
|
||
|
g_pscn->ReleaseInterruptSource(_pidl);
|
||
|
_fInterrupt = FALSE;
|
||
|
}
|
||
|
_fRemove = FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return SUCCEEDED(g_pscn->RemoveClient(_pidl, _fInterrupt, SAFECAST(this, CCollapsingClient *)));
|
||
|
}
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::_FreshenAliases(void)
|
||
|
{
|
||
|
CLinkedWalk<CAnyAlias> lw(&_listAliases);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
CAnyAlias *paa = lw.That();
|
||
|
if (paa->Remove())
|
||
|
{
|
||
|
if (SUCCEEDED(_ptreeAliases->RemoveData(paa->_pidlAlias, (INT_PTR)paa)))
|
||
|
{
|
||
|
// if RemoveData failed, we have to leak the client so the tree doesnt point to freed memory.
|
||
|
lw.Delete();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AnyAlias_Change(LONG lEvent, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra, DWORD dwEventTime)
|
||
|
{
|
||
|
if (lEvent == SHCNE_EXTENDED_EVENT)
|
||
|
{
|
||
|
LPCITEMIDLIST pidlAlias = IsAliasRegisterPidl(pidl);
|
||
|
if (pidlAlias)
|
||
|
g_pscn->AddAlias(pidlAlias, pidlExtra, dwEventTime);
|
||
|
else
|
||
|
{
|
||
|
SHChangeDWORDAsIDList *pdwidl = (SHChangeDWORDAsIDList *)pidl;
|
||
|
if (pdwidl->dwItem1 == SHCNEE_UPDATEFOLDERLOCATION)
|
||
|
{
|
||
|
g_pscn->UpdateSpecialAlias(pdwidl->dwItem2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void NotifyShellInternals(LONG lEvent, UINT uFlags, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra, DWORD dwEventTime)
|
||
|
{
|
||
|
// if they are only interested in the real deal
|
||
|
// make sure we dont pass them the translated events
|
||
|
// this keeps them from getting multiple notifications
|
||
|
// about the same paths, since the alias and the non-alias
|
||
|
// pidls will generally resolve to the same parsing name
|
||
|
// for the events/pidls that these guys are interested in
|
||
|
if (!(SHCNF_TRANSLATEDALIAS & uFlags))
|
||
|
{
|
||
|
PERFTEST(RLFS_EVENT) RLFSChanged(lEvent, (LPITEMIDLIST)pidl, (LPITEMIDLIST)pidlExtra);
|
||
|
PERFTEST(SFP_EVENT) SFP_FSEvent(lEvent, pidl, pidlExtra);
|
||
|
PERFTEST(ICON_EVENT) CFSFolder_IconEvent(lEvent, pidl, pidlExtra);
|
||
|
}
|
||
|
// aliases actually can be children of other aliases, so we need
|
||
|
// them to get the translated events
|
||
|
PERFTEST(ALIAS_EVENT) AnyAlias_Change(lEvent, pidl, pidlExtra, dwEventTime);
|
||
|
}
|
||
|
|
||
|
BOOL IsMultiBitSet(LONG l)
|
||
|
{
|
||
|
return (l && (l & (l-1)));
|
||
|
}
|
||
|
|
||
|
#define CHANGELOCK_SIG 0xbabebabe
|
||
|
#define CHANGEEVENT_SIG 0xfadefade
|
||
|
#define CHANGEREGISTER_SIG 0xdeafdeaf
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
|
||
|
BOOL IsValidChangeEvent(CHANGEEVENT *pce)
|
||
|
{
|
||
|
return (pce && (pce->dwSig == CHANGEEVENT_SIG)
|
||
|
&& (!IsMultiBitSet(pce->lEvent)));
|
||
|
}
|
||
|
|
||
|
BOOL _LockSizeMatchEvent(CHANGELOCK *pcl)
|
||
|
{
|
||
|
UINT cbPidlMainAligned = (ILGetSize(pcl->pidlMain) + 3) & ~(0x0000003); // Round up to dword size
|
||
|
UINT cbPidlExtra = ILGetSize(pcl->pidlExtra);
|
||
|
DWORD cbSize = sizeof(CHANGEEVENT) + cbPidlMainAligned + cbPidlExtra;
|
||
|
return cbSize == pcl->pce->cbSize;
|
||
|
}
|
||
|
|
||
|
BOOL IsValidChangeLock(CHANGELOCK *pcl)
|
||
|
{
|
||
|
return (pcl && IsValidChangeEvent(pcl->pce)
|
||
|
&& (pcl->dwSig == CHANGELOCK_SIG)
|
||
|
&& _LockSizeMatchEvent(pcl));
|
||
|
}
|
||
|
|
||
|
BOOL IsValidChangeEventHandle(HANDLE h, DWORD id)
|
||
|
{
|
||
|
CHANGEEVENT *pce = (CHANGEEVENT *)SHLockSharedEx(h, id, FALSE);
|
||
|
#ifdef DEBUG
|
||
|
BOOL fRet = TRUE; // can fail in low memory so must default to TRUE
|
||
|
#endif // force DEBUG
|
||
|
if (pce)
|
||
|
{
|
||
|
fRet = IsValidChangeEvent(pce);
|
||
|
SHUnlockShared(pce);
|
||
|
}
|
||
|
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
#define ISVALIDCHANGEEVENTHANDLE(h, id) IsValidChangeEventHandle(h, id)
|
||
|
#define ISVALIDCHANGEEVENT(p) IsValidChangeEvent(p)
|
||
|
#define ISVALIDCHANGELOCK(p) IsValidChangeLock(p)
|
||
|
#define ISVALIDCHANGEREGISTER(p) TRUE
|
||
|
#endif
|
||
|
|
||
|
ULONG SHChangeNotification_Destroy(HANDLE hChange, DWORD dwProcId)
|
||
|
{
|
||
|
ASSERT(ISVALIDCHANGEEVENTHANDLE(hChange, dwProcId));
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "CHANGEEVENT destroyed [0x%X]", hChange);
|
||
|
|
||
|
return SHFreeShared(hChange, dwProcId);
|
||
|
}
|
||
|
|
||
|
HANDLE SHChangeNotification_Create(LONG lEvent, UINT uFlags, LPCITEMIDLIST pidlMain, LPCITEMIDLIST pidlExtra, DWORD dwProcId, DWORD dwEventTime)
|
||
|
{
|
||
|
// some bad callers send us multiple events
|
||
|
RIP(!IsMultiBitSet(lEvent));
|
||
|
if (!IsMultiBitSet(lEvent))
|
||
|
{
|
||
|
UINT cbPidlMain = ILGetSize(pidlMain);
|
||
|
UINT cbPidlMainAligned = (cbPidlMain + 3) & ~(0x0000003); // Round up to dword size
|
||
|
UINT cbPidlExtra = ILGetSize(pidlExtra);
|
||
|
DWORD cbSize = sizeof(CHANGEEVENT) + cbPidlMainAligned + cbPidlExtra;
|
||
|
HANDLE h = SHAllocShared(NULL, cbSize, dwProcId);
|
||
|
if (h)
|
||
|
{
|
||
|
CHANGEEVENT * pce = (CHANGEEVENT *) SHLockSharedEx(h, dwProcId, TRUE);
|
||
|
if (pce)
|
||
|
{
|
||
|
BYTE *lpb = (LPBYTE)(pce + 1);
|
||
|
|
||
|
pce->cbSize = cbSize;
|
||
|
pce->dwSig = CHANGEEVENT_SIG;
|
||
|
pce->lEvent = lEvent;
|
||
|
pce->uFlags = uFlags;
|
||
|
pce->dwEventTime = dwEventTime;
|
||
|
|
||
|
if (pidlMain)
|
||
|
{
|
||
|
pce->uidlMain = sizeof(CHANGEEVENT);
|
||
|
CopyMemory(lpb, pidlMain, cbPidlMain);
|
||
|
lpb += cbPidlMainAligned;
|
||
|
}
|
||
|
|
||
|
if (pidlExtra)
|
||
|
{
|
||
|
pce->uidlExtra = (UINT) (lpb - (LPBYTE)pce);
|
||
|
CopyMemory(lpb, pidlExtra, cbPidlExtra);
|
||
|
}
|
||
|
|
||
|
SHUnlockShared(pce);
|
||
|
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "CHANGEEVENT created [0x%X]", h);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SHFreeShared(h, dwProcId);
|
||
|
h = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return h;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
CHANGELOCK *_SHChangeNotification_Lock(HANDLE hChange, DWORD dwProcId)
|
||
|
{
|
||
|
CHANGEEVENT *pce = (CHANGEEVENT *) SHLockSharedEx(hChange, dwProcId, FALSE);
|
||
|
if (pce)
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
if (!ISVALIDCHANGEEVENT(pce))
|
||
|
{
|
||
|
// during shell32 development it is convenient to use .local to use
|
||
|
// a different version of shell32 than the os version. but then
|
||
|
// non-explorer processes use the old shell32 which might have
|
||
|
// a different CHANGEEVENT structure causing this assert to fire
|
||
|
// and us to fault shortly after. do this hack check to see if
|
||
|
// we are in this situation...
|
||
|
//
|
||
|
static int nExplorerIsLocalized = -1;
|
||
|
if (nExplorerIsLocalized < 1)
|
||
|
{
|
||
|
TCHAR szPath[MAX_PATH];
|
||
|
if (GetModuleFileName(HINST_THISDLL, szPath, ARRAYSIZE(szPath)))
|
||
|
{
|
||
|
PathRemoveFileSpec(szPath);
|
||
|
PathCombine(szPath, szPath, TEXT("explorer.exe.local"));
|
||
|
if (PathFileExists(szPath))
|
||
|
nExplorerIsLocalized = 1;
|
||
|
else
|
||
|
nExplorerIsLocalized = 0;
|
||
|
}
|
||
|
}
|
||
|
if (0==nExplorerIsLocalized)
|
||
|
{
|
||
|
// We should never send ourselves an invalid changeevent!
|
||
|
ASSERT(ISVALIDCHANGEEVENT(pce));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Except in this case. Rip this out once hit -- I haven't been
|
||
|
// able to repro this in a while...
|
||
|
ASSERTMSG(ISVALIDCHANGEEVENT(pce), "Press 'g', if this doesn't fault you've validated a known .local bug fix for debug only that's hard to repro but a pain when it does. Remove this assert. Thanks.");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
#endif
|
||
|
CHANGELOCK *pcl = (CHANGELOCK *)LocalAlloc(LPTR, sizeof(CHANGELOCK));
|
||
|
if (pcl)
|
||
|
{
|
||
|
pcl->dwSig = CHANGELOCK_SIG;
|
||
|
pcl->pce = pce;
|
||
|
|
||
|
if (pce->uidlMain)
|
||
|
pcl->pidlMain = _ILSkip(pce, pce->uidlMain);
|
||
|
|
||
|
if (pce->uidlExtra)
|
||
|
pcl->pidlExtra = _ILSkip(pce, pce->uidlExtra);
|
||
|
|
||
|
return pcl;
|
||
|
}
|
||
|
else
|
||
|
SHUnlockShared(pce);
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
HANDLE SHChangeNotification_Lock(HANDLE hChange, DWORD dwProcId, LPITEMIDLIST **pppidl, LONG *plEvent)
|
||
|
{
|
||
|
CHANGELOCK *pcl = _SHChangeNotification_Lock(hChange, dwProcId);
|
||
|
if (pcl)
|
||
|
{
|
||
|
//
|
||
|
// Give back some easy values (causes less code to change for now)
|
||
|
//
|
||
|
if (pppidl)
|
||
|
*pppidl = &(pcl->pidlMain);
|
||
|
|
||
|
if (plEvent)
|
||
|
*plEvent = pcl->pce->lEvent;
|
||
|
}
|
||
|
return (HANDLE) pcl;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL SHChangeNotification_Unlock(HANDLE hLock)
|
||
|
{
|
||
|
CHANGELOCK *pcl = (CHANGELOCK *)hLock;
|
||
|
|
||
|
ASSERT(ISVALIDCHANGELOCK(pcl));
|
||
|
|
||
|
BOOL fRet = SHUnlockShared(pcl->pce);
|
||
|
LocalFree(pcl);
|
||
|
|
||
|
ASSERT(fRet);
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CNotifyEvent::AddRef()
|
||
|
{
|
||
|
return InterlockedIncrement(&_cRef);
|
||
|
}
|
||
|
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CNotifyEvent::Release()
|
||
|
{
|
||
|
if (InterlockedDecrement(&_cRef))
|
||
|
return _cRef;
|
||
|
|
||
|
delete this;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
BOOL CNotifyEvent::Init(LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra)
|
||
|
{
|
||
|
if (pidl)
|
||
|
this->pidl = ILClone(pidl);
|
||
|
|
||
|
if (pidlExtra)
|
||
|
this->pidlExtra = ILClone(pidlExtra);
|
||
|
|
||
|
return ((!pidl || this->pidl) && (!pidlExtra || this->pidlExtra));
|
||
|
}
|
||
|
|
||
|
CNotifyEvent *CNotifyEvent::Create(LONG lEvent, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra, DWORD dwEventTime, UINT uEventFlags)
|
||
|
{
|
||
|
CNotifyEvent *p = new CNotifyEvent(lEvent, dwEventTime, uEventFlags);
|
||
|
|
||
|
if (p)
|
||
|
{
|
||
|
if (!p->Init(pidl, pidlExtra))
|
||
|
{
|
||
|
// we failed here
|
||
|
p->Release();
|
||
|
p = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
CCollapsingClient::CCollapsingClient()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
CCollapsingClient::~CCollapsingClient()
|
||
|
{
|
||
|
ILFree(_pidl);
|
||
|
if (_dpaPendingEvents)
|
||
|
{
|
||
|
int iCount = _dpaPendingEvents.GetPtrCount();
|
||
|
while (iCount--)
|
||
|
{
|
||
|
CNotifyEvent *pne = _dpaPendingEvents.FastGetPtr(iCount);
|
||
|
// to parallel our UsingEvent() call
|
||
|
pne->Release();
|
||
|
}
|
||
|
_dpaPendingEvents.Destroy();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ULONG g_ulNextID = 1;
|
||
|
CRegisteredClient::CRegisteredClient()
|
||
|
{
|
||
|
//
|
||
|
// Skip ID 0, as this is our error value.
|
||
|
//
|
||
|
_ulID = g_ulNextID;
|
||
|
if (!++g_ulNextID)
|
||
|
g_ulNextID = 1;
|
||
|
}
|
||
|
|
||
|
CRegisteredClient::~CRegisteredClient()
|
||
|
{
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "SCN::~CRegisteredClient() [0x%X] id = %d", this, _ulID);
|
||
|
}
|
||
|
|
||
|
BOOL CRegisteredClient::Init(HWND hwnd, int fSources, LONG fEvents, UINT wMsg, SHChangeNotifyEntry *pfsne)
|
||
|
{
|
||
|
// need one or the other
|
||
|
ASSERT(fSources & (SHCNRF_InterruptLevel | SHCNRF_ShellLevel));
|
||
|
|
||
|
_hwnd = hwnd;
|
||
|
GetWindowThreadProcessId(hwnd, &_dwProcId);
|
||
|
_fSources = fSources;
|
||
|
_fInterrupt = fSources & SHCNRF_InterruptLevel;
|
||
|
_fEvents = fEvents;
|
||
|
_wMsg = wMsg;
|
||
|
|
||
|
LPITEMIDLIST pidlNew;
|
||
|
if (pfsne->pidl)
|
||
|
pidlNew = ILClone(pfsne->pidl);
|
||
|
else
|
||
|
pidlNew = SHCloneSpecialIDList(NULL, CSIDL_DESKTOP, FALSE);
|
||
|
|
||
|
BOOL fRet = CCollapsingClient::Init(pidlNew, pfsne->fRecursive);
|
||
|
ILFree(pidlNew);
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
BOOL CRegisteredClient::_WantsEvent(LONG lEvent)
|
||
|
{
|
||
|
if (!_fDeadClient && (lEvent & _fEvents))
|
||
|
{
|
||
|
//
|
||
|
// if this event was generated by an interrupt, and the
|
||
|
// client has interrupt notification turned off, we dont want it
|
||
|
//
|
||
|
if (lEvent & SHCNE_INTERRUPT)
|
||
|
{
|
||
|
if (!(_fSources & SHCNRF_InterruptLevel))
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
else if (!(_fSources & SHCNRF_ShellLevel))
|
||
|
{
|
||
|
//
|
||
|
// This event was generated by the shell, and the
|
||
|
// client has shell notification turned off, so
|
||
|
// we skip it.
|
||
|
//
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
BOOL CCollapsingClient::_CanCollapse(LONG lEvent)
|
||
|
{
|
||
|
return (!_CheckUpdatingSelf()
|
||
|
&& (lEvent & SHCNE_DISKEVENTS)
|
||
|
&& !(lEvent & SHCNE_GLOBALEVENTS)
|
||
|
&& (_dpaPendingEvents.GetPtrCount() >= EVENT_OVERFLOW));
|
||
|
}
|
||
|
|
||
|
STDAPI_(BOOL) ILIsEqualEx(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2, BOOL fMatchDepth, LPARAM lParam);
|
||
|
|
||
|
//
|
||
|
// checks for null so we dont assert in ILIsEqual
|
||
|
//
|
||
|
BOOL ILIsEqualOrBothNull(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2, BOOL fMemCmpOnly)
|
||
|
{
|
||
|
if (!pidl1 || !pidl2)
|
||
|
{
|
||
|
return (pidl1 == pidl2);
|
||
|
}
|
||
|
|
||
|
if (!fMemCmpOnly)
|
||
|
return ILIsEqualEx(pidl1, pidl2, TRUE, SHCIDS_CANONICALONLY);
|
||
|
else
|
||
|
{
|
||
|
UINT cb1 = ILGetSize(pidl1);
|
||
|
|
||
|
return (cb1 == ILGetSize(pidl2) && 0 == memcmp(pidl1, pidl2, cb1));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#define SHCNE_ELIMINATE_DUPE_EVENTS (SHCNE_ATTRIBUTES | SHCNE_UPDATEDIR | SHCNE_UPDATEITEM | SHCNE_UPDATEIMAGE | SHCNE_FREESPACE)
|
||
|
|
||
|
BOOL CCollapsingClient::_IsDupe(CNotifyEvent *pne)
|
||
|
{
|
||
|
BOOL fRet = FALSE;
|
||
|
if (pne->lEvent & SHCNE_ELIMINATE_DUPE_EVENTS)
|
||
|
{
|
||
|
// look for duplicates starting with the last one
|
||
|
for (int i = _dpaPendingEvents.GetPtrCount() - 1; !fRet && i >= 0; i--)
|
||
|
{
|
||
|
CNotifyEvent *pneMaybe = _dpaPendingEvents.FastGetPtr(i);
|
||
|
if (pne == pneMaybe)
|
||
|
fRet = TRUE;
|
||
|
else if ((pneMaybe->lEvent == pne->lEvent)
|
||
|
&& ILIsEqualOrBothNull(pne->pidl, pneMaybe->pidl, (pne->lEvent & SHCNE_GLOBALEVENTS))
|
||
|
&& ILIsEqualOrBothNull(pne->pidlExtra, pneMaybe->pidlExtra, (pneMaybe->lEvent & SHCNE_GLOBALEVENTS)))
|
||
|
fRet = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
BOOL CCollapsingClient::_AddEvent(CNotifyEvent *pneOld, BOOL fFromExtra)
|
||
|
{
|
||
|
CNotifyEvent *pne = pneOld;
|
||
|
pne->AddRef();
|
||
|
|
||
|
BOOL fCollapse = _CanCollapse(pne->lEvent);
|
||
|
|
||
|
if (fCollapse)
|
||
|
{
|
||
|
//
|
||
|
// If we get too many messages in the queue at any given time,
|
||
|
// we set the last message in the cue to be an UPDATEDIR that will
|
||
|
// stand for all messages that we cant fit because the queue is full.
|
||
|
//
|
||
|
BOOL fAddSelf = TRUE;
|
||
|
if (_fRecursive && _dpaPendingEvents.GetPtrCount() < (EVENT_OVERFLOW *2))
|
||
|
{
|
||
|
BOOL fFreeUpdate = FALSE;
|
||
|
LPITEMIDLIST pidlUpdate = fFromExtra ? pne->pidlExtra : pne->pidl;
|
||
|
DWORD dwAttrs = SFGAO_FOLDER;
|
||
|
|
||
|
SHGetNameAndFlags(pidlUpdate, 0, NULL, 0, &dwAttrs);
|
||
|
if (!(dwAttrs & SFGAO_FOLDER))
|
||
|
{
|
||
|
pidlUpdate = ILCloneParent(pidlUpdate);
|
||
|
fFreeUpdate = TRUE;
|
||
|
}
|
||
|
|
||
|
if (pidlUpdate)
|
||
|
{
|
||
|
if (ILGetSize(pidlUpdate) > ILGetSize(_pidl))
|
||
|
{
|
||
|
pne->Release();
|
||
|
|
||
|
// then we should add this folder to the update list
|
||
|
pne = g_pscn->GetEvent(SHCNE_UPDATEDIR, pidlUpdate, NULL, pne->dwEventTime, 0);
|
||
|
if (pne)
|
||
|
{
|
||
|
fAddSelf = FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fFreeUpdate)
|
||
|
ILFree(pidlUpdate);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fAddSelf && pne)
|
||
|
{
|
||
|
pne->Release();
|
||
|
pne = g_pscn->GetEvent(SHCNE_UPDATEDIR, _pidl, NULL, pne->dwEventTime, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (pne)
|
||
|
{
|
||
|
if (!_IsDupe(pne))
|
||
|
{
|
||
|
// if this is one of our special collapsed
|
||
|
// events then we force it in even if we are full
|
||
|
if ((fCollapse || _dpaPendingEvents.GetPtrCount() < EVENT_OVERFLOW)
|
||
|
&& _dpaPendingEvents.AppendPtr(pne) != -1)
|
||
|
{
|
||
|
pne->AddRef();
|
||
|
g_pscn->SetFlush(FLUSH_SOFT);
|
||
|
|
||
|
if (!_fUpdatingSelf && (pne->lEvent & SHCNE_UPDATEDIR) && ILIsEqualEx(_pidl, pne->pidl, TRUE, SHCIDS_CANONICALONLY))
|
||
|
{
|
||
|
_fUpdatingSelf = TRUE;
|
||
|
_iUpdatingSelfIndex = _dpaPendingEvents.GetPtrCount() - 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if we are getting filesystem updates
|
||
|
// always pretend that we overflowed
|
||
|
// this is because UPDATEDIR's are the
|
||
|
// most expensive thing we do.
|
||
|
if (pne->lEvent & SHCNE_INTERRUPT)
|
||
|
{
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "SCN [0x%X]->_AddEvent adding interrupt", this);
|
||
|
_cEvents += EVENT_OVERFLOW;
|
||
|
}
|
||
|
|
||
|
// count all events even if they
|
||
|
// they werent added.
|
||
|
_cEvents++;
|
||
|
}
|
||
|
|
||
|
pne->Release();
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
void CCollapsingClient::Notify(CNotifyEvent *pne, BOOL fFromExtra)
|
||
|
{
|
||
|
if (_WantsEvent(pne->lEvent))
|
||
|
{
|
||
|
_AddEvent(pne, fFromExtra);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------
|
||
|
// Notifies hCallbackEvent when all the notification packets for
|
||
|
// all clients in this process have been handled.
|
||
|
//
|
||
|
// This function is primarily called from the FSNotifyThreadProc thread,
|
||
|
// but in flush cases, it can be called from the desktop thread
|
||
|
//
|
||
|
void CALLBACK _DispatchCallbackNoRef(HWND hwnd, UINT uiMsg,
|
||
|
DWORD_PTR dwParam, LRESULT result)
|
||
|
{
|
||
|
MSGEVENT *pme = (MSGEVENT *)dwParam;
|
||
|
SHChangeNotification_Destroy(pme->hChange, pme->dwProcId);
|
||
|
delete pme;
|
||
|
}
|
||
|
|
||
|
void CALLBACK _DispatchCallback(HWND hwnd, UINT uiMsg,
|
||
|
DWORD_PTR hChange, LRESULT result)
|
||
|
{
|
||
|
_DispatchCallbackNoRef(hwnd, uiMsg, hChange, result);
|
||
|
|
||
|
if (EVAL(g_pscn))
|
||
|
g_pscn->PendingCallbacks(FALSE);
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::PendingCallbacks(BOOL fAdd)
|
||
|
{
|
||
|
if (fAdd)
|
||
|
{
|
||
|
_cCallbacks++;
|
||
|
|
||
|
ASSERT(_cCallbacks != 0);
|
||
|
//
|
||
|
// callback count must be non-zero, we just incremented it.
|
||
|
// Put the event into the reset/false state.
|
||
|
//
|
||
|
if (!_hCallbackEvent)
|
||
|
{
|
||
|
_hCallbackEvent = CreateEvent(NULL, TRUE, FALSE, TEXT("Shell_NotificationCallbacksOutstanding"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ResetEvent(_hCallbackEvent);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// PERF: Waits like this happen on flush, but that really cares about flushing that thread
|
||
|
// only, and this hCallbackEvent is per-process. So that thread may be stuck
|
||
|
// waiting for some dead app to respond. Fortunately the wait is only 30 seconds,
|
||
|
// but some wedged window could really make the system crawl...
|
||
|
//
|
||
|
ASSERT(_cCallbacks != 0);
|
||
|
_cCallbacks--;
|
||
|
|
||
|
if (!_cCallbacks && _hCallbackEvent)
|
||
|
{
|
||
|
// we just got the last of our callbacks
|
||
|
// signal incase somebody is waiting
|
||
|
SetEvent(_hCallbackEvent);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BOOL CCollapsingClient::Flush(BOOL fNeedsCallbackEvent)
|
||
|
{
|
||
|
BOOL fRet = FALSE;
|
||
|
if (fNeedsCallbackEvent || _cEvents < EVENT_OVERFLOW)
|
||
|
{
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "SCN [0x%X]->Flush is completing", this);
|
||
|
fRet = _Flush(fNeedsCallbackEvent);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "SCN [0x%X]->Flush is deferred", this);
|
||
|
|
||
|
g_pscn->SetFlush(FLUSH_OVERFLOW);
|
||
|
}
|
||
|
|
||
|
_cEvents = 0;
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
void CRegisteredClient::_SendNotification(CNotifyEvent *pne, BOOL fNeedsCallbackEvent, SENDASYNCPROC pfncb)
|
||
|
{
|
||
|
// we could possibly reuse one in some cases
|
||
|
MSGEVENT * pme = pne->GetNotification(_dwProcId);
|
||
|
if (pme)
|
||
|
{
|
||
|
if (fNeedsCallbackEvent)
|
||
|
{
|
||
|
g_pscn->PendingCallbacks(TRUE);
|
||
|
}
|
||
|
|
||
|
if (!SendMessageCallback(_hwnd, _wMsg,
|
||
|
(WPARAM)pme->hChange,
|
||
|
(LPARAM)_dwProcId,
|
||
|
pfncb,
|
||
|
(DWORD_PTR)pme))
|
||
|
{
|
||
|
pfncb(_hwnd, _wMsg, (DWORD_PTR)pme, 0);
|
||
|
TraceMsg(TF_WARNING, "(_SHChangeNotifyHandleClientEvents) SendMessageCB timed out");
|
||
|
|
||
|
// if the hwnd is bad, the process probably died,
|
||
|
// remove the window from future notifications.
|
||
|
if (!IsWindow(_hwnd))
|
||
|
{
|
||
|
_fDeadClient = TRUE;
|
||
|
// we failed to Flush
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BOOL CCollapsingClient::_Flush(BOOL fNeedsCallbackEvent)
|
||
|
{
|
||
|
if (fNeedsCallbackEvent && _hwnd)
|
||
|
{
|
||
|
DWORD_PTR dwResult = 0;
|
||
|
fNeedsCallbackEvent = (0 != SendMessageTimeout(_hwnd, WM_NULL, 0, 0, SMTO_ABORTIFHUNG, 0, &dwResult));
|
||
|
}
|
||
|
SENDASYNCPROC pfncb = fNeedsCallbackEvent ? _DispatchCallback : _DispatchCallbackNoRef;
|
||
|
|
||
|
BOOL fProcessedAny = FALSE;
|
||
|
// as long as there are events keep pulling them out
|
||
|
while (_dpaPendingEvents.GetPtrCount())
|
||
|
{
|
||
|
//
|
||
|
// 2000JUL3 - ZekeL - remove each one from our dpa so that if we reenter
|
||
|
// a flush during the sendmessage, we wont reprocess the event
|
||
|
// this also allows for an event to be added to the dpa while
|
||
|
// we proccessing and still be flushed on this pass.
|
||
|
//
|
||
|
CNotifyEvent *pne = _dpaPendingEvents.DeletePtr(0);
|
||
|
if (pne)
|
||
|
{
|
||
|
fProcessedAny = TRUE;
|
||
|
// we never send this if we are dead
|
||
|
if (_IsValidClient())
|
||
|
{
|
||
|
//
|
||
|
// if we are about to refresh this client (_fUpdatingSelf)
|
||
|
// only send if we are looking at the UPDATEDIR of _pidl
|
||
|
// or if this event is not a disk event.
|
||
|
//
|
||
|
if (!_CheckUpdatingSelf()
|
||
|
|| (0 == _iUpdatingSelfIndex)
|
||
|
|| !(pne->lEvent & SHCNE_DISKEVENTS))
|
||
|
{
|
||
|
BOOL fPreCall = BOOLIFY(_fUpdatingSelf);
|
||
|
_SendNotification(pne, fNeedsCallbackEvent, pfncb);
|
||
|
if (_fUpdatingSelf && !fPreCall)
|
||
|
{
|
||
|
// we were re-entered while sending this notification and
|
||
|
// during the re-entered call we collapsed notifications.
|
||
|
// the _iUpdatingSelfIndex value was set without knowing
|
||
|
// that we were going to decrement it after unwinding.
|
||
|
// account for that now:
|
||
|
_iUpdatingSelfIndex++;
|
||
|
}
|
||
|
}
|
||
|
#ifdef DEBUG
|
||
|
if (_fUpdatingSelf && 0 == _iUpdatingSelfIndex)
|
||
|
{
|
||
|
// RIP because fault injection
|
||
|
// can make this fail
|
||
|
if (!ILIsEqual(_pidl, pne->pidl))
|
||
|
TraceMsg(TF_WARNING, "CCollapsingClient::_Flush() maybe mismatched _fUpdatingSelf");
|
||
|
}
|
||
|
#endif // DEBUG
|
||
|
}
|
||
|
_iUpdatingSelfIndex--;
|
||
|
pne->Release();
|
||
|
}
|
||
|
}
|
||
|
_fUpdatingSelf = FALSE;
|
||
|
return fProcessedAny;
|
||
|
}
|
||
|
|
||
|
HRESULT CChangeNotify::RemoveClient(LPCITEMIDLIST pidl, BOOL fInterrupt, CCollapsingClient *pclient)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
// remove this boy from the tree
|
||
|
if (_ptreeClients)
|
||
|
{
|
||
|
hr = _ptreeClients->RemoveData(pidl, (INT_PTR)pclient);
|
||
|
|
||
|
if (fInterrupt)
|
||
|
ReleaseInterruptSource(pidl);
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CChangeNotify::AddClient(IDLDATAF flags, LPCITEMIDLIST pidl, BOOL *pfInterrupt, BOOL fRecursive, CCollapsingClient *pclient)
|
||
|
{
|
||
|
BOOL fRet = FALSE;
|
||
|
if (_InitTree(&_ptreeClients))
|
||
|
{
|
||
|
ASSERT(pclient);
|
||
|
|
||
|
if (SUCCEEDED(_ptreeClients->AddData(flags, pidl, (INT_PTR)pclient)))
|
||
|
{
|
||
|
fRet = TRUE;
|
||
|
// set up the interrupt events if desired
|
||
|
if (pfInterrupt && *pfInterrupt)
|
||
|
{
|
||
|
*pfInterrupt = AddInterruptSource(pidl, fRecursive);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
LPITEMIDLIST _ILCloneInterruptID(LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
LPITEMIDLIST pidlRet = NULL;
|
||
|
if (pidl)
|
||
|
{
|
||
|
TCHAR sz[MAX_PATH];
|
||
|
if (SHGetPathFromIDList(pidl, sz))
|
||
|
{
|
||
|
WIN32_FIND_DATA fd = {0};
|
||
|
fd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
|
||
|
SHSimpleIDListFromFindData(sz, &fd, &pidlRet);
|
||
|
}
|
||
|
}
|
||
|
else // NULL is special for desktop
|
||
|
pidlRet = SHCloneSpecialIDList(NULL, CSIDL_DESKTOPDIRECTORY, FALSE);
|
||
|
|
||
|
return pidlRet;
|
||
|
}
|
||
|
|
||
|
CInterruptSource *CChangeNotify::_InsertInterruptSource(LPCITEMIDLIST pidl, BOOL fRecursive)
|
||
|
{
|
||
|
CLinkedNode<CInterruptSource> *p = new CLinkedNode<CInterruptSource>;
|
||
|
|
||
|
if (p)
|
||
|
{
|
||
|
IDLDATAF flags = fRecursive ? IDLDATAF_MATCH_RECURSIVE : IDLDATAF_MATCH_IMMEDIATE;
|
||
|
if (p->that.Init(pidl, fRecursive)
|
||
|
&& _listInterrupts.Insert(p))
|
||
|
{
|
||
|
if (SUCCEEDED(_ptreeInterrupts->AddData(flags, p->that.pidl, (INT_PTR)&p->that)))
|
||
|
{
|
||
|
return &p->that;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_listInterrupts.Remove(p);
|
||
|
delete p;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
delete p;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
BOOL CChangeNotify::AddInterruptSource(LPCITEMIDLIST pidlClient, BOOL fRecursive)
|
||
|
{
|
||
|
if (_InitTree(&_ptreeInterrupts))
|
||
|
{
|
||
|
LPITEMIDLIST pidl = _ILCloneInterruptID(pidlClient);
|
||
|
|
||
|
if (pidl)
|
||
|
{
|
||
|
CInterruptSource *pintc = NULL;
|
||
|
|
||
|
if (FAILED(_ptreeInterrupts->MatchOne(IDLDATAF_MATCH_EXACT, pidl, (INT_PTR*)&pintc, NULL)))
|
||
|
{
|
||
|
pintc = _InsertInterruptSource(pidl, fRecursive);
|
||
|
}
|
||
|
|
||
|
ILFree(pidl);
|
||
|
|
||
|
if (pintc)
|
||
|
{
|
||
|
pintc->cClients++;
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::ReleaseInterruptSource(LPCITEMIDLIST pidlClient)
|
||
|
{
|
||
|
if (_ptreeInterrupts)
|
||
|
{
|
||
|
LPITEMIDLIST pidl = _ILCloneInterruptID(pidlClient);
|
||
|
if (pidl)
|
||
|
{
|
||
|
CInterruptSource *pintc;
|
||
|
if (SUCCEEDED(_ptreeInterrupts->MatchOne(IDLDATAF_MATCH_EXACT, pidl, (INT_PTR*)&pintc, NULL)))
|
||
|
{
|
||
|
if (--(pintc->cClients) == 0)
|
||
|
{
|
||
|
// if RemoveData fails, we have to leak the client so the tree doesnt point to freed memory.
|
||
|
if (SUCCEEDED(_ptreeInterrupts->RemoveData(pidl, (INT_PTR)pintc)))
|
||
|
{
|
||
|
CLinkedWalk<CInterruptSource> lw(&_listInterrupts);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
if (lw.That() == pintc)
|
||
|
{
|
||
|
lw.Delete();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ILFree(pidl);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::_ActivateAliases(LPCITEMIDLIST pidl, BOOL fActivate)
|
||
|
{
|
||
|
if (_ptreeAliases)
|
||
|
{
|
||
|
CIDLMatchMany *pmany;
|
||
|
|
||
|
if (SUCCEEDED(_ptreeAliases->MatchMany(IDLDATAF_MATCH_RECURSIVE, pidl, &pmany)))
|
||
|
{
|
||
|
CAnyAlias *paa;
|
||
|
while (S_OK == pmany->Next((INT_PTR *)&paa, NULL))
|
||
|
{
|
||
|
paa->Activate(fActivate);
|
||
|
}
|
||
|
|
||
|
delete pmany;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ULONG CChangeNotify::_RegisterClient(HWND hwnd, int fSources, LONG fEvents, UINT wMsg, SHChangeNotifyEntry *pfsne)
|
||
|
{
|
||
|
ULONG ulRet = 0;
|
||
|
CLinkedNode<CRegisteredClient> *p = new CLinkedNode<CRegisteredClient>;
|
||
|
|
||
|
if (p)
|
||
|
{
|
||
|
if (p->that.Init(hwnd, fSources, fEvents, wMsg, pfsne))
|
||
|
{
|
||
|
IDLDATAF flags = IDLDATAF_MATCH_IMMEDIATE;
|
||
|
if (!pfsne->pidl || pfsne->fRecursive)
|
||
|
flags = IDLDATAF_MATCH_RECURSIVE;
|
||
|
|
||
|
if (_listClients.Insert(p)
|
||
|
&& AddClient( flags,
|
||
|
pfsne->pidl,
|
||
|
&(p->that._fInterrupt),
|
||
|
pfsne->fRecursive && (fSources & SHCNRF_RecursiveInterrupt),
|
||
|
SAFECAST(&p->that, CCollapsingClient *)))
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
TCHAR szName[MAX_PATH];
|
||
|
SHGetNameAndFlags(p->that._pidl, 0, szName, ARRAYSIZE(szName), NULL);
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "SCN::RegCli() added %s [0x%X] id = %d", szName, p, p->that._ulID);
|
||
|
#endif
|
||
|
_ActivateAliases(pfsne->pidl, TRUE);
|
||
|
ulRet = p->that._ulID;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!ulRet)
|
||
|
{
|
||
|
_listClients.Remove(p);
|
||
|
delete p;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ulRet;
|
||
|
}
|
||
|
|
||
|
BOOL CChangeNotify::_InitTree(CIDLTree**pptree)
|
||
|
{
|
||
|
if (!*pptree)
|
||
|
{
|
||
|
CIDLTree::Create(pptree);
|
||
|
}
|
||
|
|
||
|
return *pptree != NULL;
|
||
|
}
|
||
|
|
||
|
CNotifyEvent *CChangeNotify::GetEvent(LONG lEvent, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra, DWORD dwEventTime, UINT uEventFlags)
|
||
|
{
|
||
|
return CNotifyEvent::Create(lEvent, pidl, pidlExtra, dwEventTime, uEventFlags);
|
||
|
}
|
||
|
|
||
|
BOOL CChangeNotify::_DeregisterClient(CRegisteredClient *pclient)
|
||
|
{
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "SCN::RegCli() removing [0x%X] id = %d", pclient, pclient->_ulID);
|
||
|
if (SUCCEEDED(RemoveClient(pclient->_pidl, pclient->_fInterrupt, SAFECAST(pclient, CCollapsingClient *))))
|
||
|
{
|
||
|
_ActivateAliases(pclient->_pidl, FALSE);
|
||
|
return TRUE;
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
BOOL CChangeNotify::_DeregisterClientByID(ULONG ulID)
|
||
|
{
|
||
|
BOOL fRet = FALSE;
|
||
|
CLinkedWalk <CRegisteredClient> lw(&_listClients);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
if (lw.That()->_ulID == ulID)
|
||
|
{
|
||
|
// if we are flushing,
|
||
|
// then this is coming in while
|
||
|
// we are in SendMessageTimeout()
|
||
|
if (!_cFlushing)
|
||
|
{
|
||
|
fRet = _DeregisterClient(lw.That());
|
||
|
if (fRet)
|
||
|
{
|
||
|
lw.Delete();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
lw.That()->_fDeadClient = TRUE;
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
BOOL CChangeNotify::_DeregisterClientsByWindow(HWND hwnd)
|
||
|
{
|
||
|
BOOL fRet = FALSE;
|
||
|
CLinkedWalk <CRegisteredClient> lw(&_listClients);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
if (lw.That()->_hwnd == hwnd)
|
||
|
{
|
||
|
// if we are flushing,
|
||
|
// then this is coming in while
|
||
|
// we are in SendMessageTimeout()
|
||
|
if (!_cFlushing)
|
||
|
{
|
||
|
fRet = _DeregisterClient(lw.That());
|
||
|
if (fRet)
|
||
|
{
|
||
|
lw.Delete();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
lw.That()->_fDeadClient = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::_AddGlobalEvent(CNotifyEvent *pne)
|
||
|
{
|
||
|
CLinkedWalk <CRegisteredClient> lw(&_listClients);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
lw.That()->Notify(pne, FALSE);
|
||
|
}
|
||
|
|
||
|
// this is the notify we get when a drive mapping is deleted
|
||
|
// when this happens we need to kill the aliases to that drive
|
||
|
if ((pne->lEvent == SHCNE_DRIVEREMOVED) && !(pne->uEventFlags & SHCNF_TRANSLATEDALIAS))
|
||
|
{
|
||
|
CLinkedWalk<CAnyAlias> lw(&_listAliases);
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
lw.That()->Notify(pne, FALSE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CChangeNotify::_MatchAndNotify(LPCITEMIDLIST pidl, CNotifyEvent *pne, BOOL fFromExtra)
|
||
|
{
|
||
|
if (_ptreeClients)
|
||
|
{
|
||
|
CIDLMatchMany *pmany;
|
||
|
|
||
|
if (SUCCEEDED(_ptreeClients->MatchMany(IDLDATAF_MATCH_RECURSIVE, pidl, &pmany)))
|
||
|
{
|
||
|
CCollapsingClient *pclient;
|
||
|
while (S_OK == pmany->Next((INT_PTR *)&pclient, NULL))
|
||
|
{
|
||
|
pclient->Notify(pne, fFromExtra);
|
||
|
}
|
||
|
|
||
|
delete pmany;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BOOL CChangeNotify::_AddToClients(LONG lEvent, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra, DWORD dwEventTime, UINT uEventFlags)
|
||
|
{
|
||
|
BOOL bOnlyUpdateDirs = TRUE;
|
||
|
|
||
|
CNotifyEvent *pne = GetEvent(lEvent, pidl, pidlExtra, dwEventTime, uEventFlags);
|
||
|
|
||
|
if (pne)
|
||
|
{
|
||
|
if (lEvent & SHCNE_GLOBALEVENTS)
|
||
|
{
|
||
|
_AddGlobalEvent(pne);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_MatchAndNotify(pidl, pne, FALSE);
|
||
|
|
||
|
if (pidlExtra)
|
||
|
_MatchAndNotify(pidlExtra, pne, TRUE);
|
||
|
}
|
||
|
|
||
|
pne->Release();
|
||
|
}
|
||
|
|
||
|
return bOnlyUpdateDirs;
|
||
|
}
|
||
|
|
||
|
BOOL CChangeNotify::_HandleMessages(void)
|
||
|
{
|
||
|
MSG msg;
|
||
|
// There was some message put in our queue, so we need to dispose
|
||
|
// of it
|
||
|
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
|
||
|
{
|
||
|
if (msg.hwnd)
|
||
|
{
|
||
|
TranslateMessage(&msg);
|
||
|
DispatchMessage(&msg);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
switch (msg.message)
|
||
|
{
|
||
|
case SCNM_TERMINATE:
|
||
|
DestroyWindow(g_hwndSCN);
|
||
|
g_hwndSCN = NULL;
|
||
|
return TRUE;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "SCN thread proc: eating unknown message %#lx", msg.message);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
CInterruptSource::~CInterruptSource()
|
||
|
{
|
||
|
_Reset(TRUE);
|
||
|
ILFree(pidl);
|
||
|
}
|
||
|
|
||
|
BOOL CInterruptSource::Init(LPCITEMIDLIST pidl, BOOL fRecursive)
|
||
|
{
|
||
|
this->pidl = ILClone(pidl);
|
||
|
_fRecursive = fRecursive;
|
||
|
return (this->pidl != NULL);
|
||
|
}
|
||
|
|
||
|
BOOL CInterruptSource::Flush(void)
|
||
|
{
|
||
|
if (FS_SIGNAL == _ssSignal)
|
||
|
{
|
||
|
g_pscn->NotifyEvent(SHCNE_UPDATEDIR | SHCNE_INTERRUPT, SHCNF_IDLIST, pidl, NULL, GetTickCount());
|
||
|
}
|
||
|
|
||
|
_ssSignal = NO_SIGNAL;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
void CInterruptSource::_Reset(BOOL fDeviceNotify)
|
||
|
{
|
||
|
if (_hEvent && _hEvent != INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
FindCloseChangeNotification(_hEvent);
|
||
|
_hEvent = NULL;
|
||
|
}
|
||
|
|
||
|
if (fDeviceNotify && _hPNP)
|
||
|
{
|
||
|
UnregisterDeviceNotification(_hPNP);
|
||
|
_hPNP = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CInterruptSource::Reset(BOOL fSignal)
|
||
|
{
|
||
|
if (fSignal) // file system event
|
||
|
{
|
||
|
switch(_ssSignal)
|
||
|
{
|
||
|
case NO_SIGNAL: _ssSignal = FS_SIGNAL; break;
|
||
|
case SH_SIGNAL: _ssSignal = NO_SIGNAL; break;
|
||
|
}
|
||
|
|
||
|
if (!FindNextChangeNotification(_hEvent))
|
||
|
{
|
||
|
_Reset(FALSE);
|
||
|
// when we fail, we dont want
|
||
|
// to retry. which we will do
|
||
|
// in the case of _hEvent = NULL;
|
||
|
_hEvent = INVALID_HANDLE_VALUE;
|
||
|
}
|
||
|
}
|
||
|
else // shell event
|
||
|
{
|
||
|
switch(_ssSignal)
|
||
|
{
|
||
|
case NO_SIGNAL: _ssSignal = SH_SIGNAL; break;
|
||
|
case FS_SIGNAL: _ssSignal = NO_SIGNAL; break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#define FFCN_INTERESTING_EVENTS (FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES)
|
||
|
|
||
|
BOOL CInterruptSource::GetEvent(HANDLE *phEvent)
|
||
|
{
|
||
|
if (_cSuspend == 0 && cClients)
|
||
|
{
|
||
|
// create this here so that it will be owned by our global thread
|
||
|
if (!_hEvent)
|
||
|
{
|
||
|
TCHAR szPath[MAX_PATH];
|
||
|
if (SHGetPathFromIDList(pidl, szPath))
|
||
|
{
|
||
|
_hEvent = FindFirstChangeNotification(szPath, _fRecursive, FFCN_INTERESTING_EVENTS);
|
||
|
|
||
|
if (_hEvent != INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
// PERF optimization alert: RegisterDeviceNotification is being used for removable drives
|
||
|
// to ensure that the FindFirstChangeNotification call will not prevent the disk
|
||
|
// from being ejected or dismounted. However, RegisterDeviceNotification is a very expensive
|
||
|
// call to make at startup as it brings a bunch of DLLs in the address space. Besides,
|
||
|
// we really don't need to call this for the system drive since it needs to remain
|
||
|
// mounted at all times. - FabriceD
|
||
|
|
||
|
// Exclude FIXED drives too
|
||
|
int iDrive = PathGetDriveNumber(szPath);
|
||
|
int nType = DRIVE_UNKNOWN;
|
||
|
if (iDrive != -1)
|
||
|
{
|
||
|
nType = DriveType(iDrive);
|
||
|
}
|
||
|
|
||
|
// PERF: Exclude the system drive from the RegisterDeviceNotification calls.
|
||
|
TCHAR chDrive = *szPath;
|
||
|
if ((!GetEnvironmentVariable(TEXT("SystemDrive"), szPath, ARRAYSIZE(szPath)) || *szPath != chDrive) &&
|
||
|
nType != DRIVE_FIXED)
|
||
|
{
|
||
|
// DO WE NEED TO UnRegister() first?
|
||
|
DEV_BROADCAST_HANDLE dbh;
|
||
|
ZeroMemory(&dbh, sizeof(dbh));
|
||
|
dbh.dbch_size = sizeof(dbh);
|
||
|
dbh.dbch_devicetype = DBT_DEVTYP_HANDLE;
|
||
|
dbh.dbch_handle = _hEvent;
|
||
|
_hPNP = RegisterDeviceNotification(g_hwndSCN, &dbh, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
_hEvent = INVALID_HANDLE_VALUE;
|
||
|
}
|
||
|
|
||
|
if (_hEvent != INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
*phEvent = _hEvent;
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::_SignalInterrupt(HANDLE hEvent)
|
||
|
{
|
||
|
CLinkedWalk<CInterruptSource> lw(&_listInterrupts);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
// searching for valid clients
|
||
|
HANDLE h;
|
||
|
if (lw.That()->GetEvent(&h) && h == hEvent)
|
||
|
{
|
||
|
g_pscn->SetFlush(FLUSH_INTERRUPT);
|
||
|
lw.That()->Reset(TRUE);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DWORD CChangeNotify::_GetInterruptEvents(HANDLE *ahEvents, DWORD cEventsSize)
|
||
|
{
|
||
|
DWORD cEvents = 0;
|
||
|
CLinkedWalk<CInterruptSource> lw(&_listInterrupts);
|
||
|
|
||
|
while (cEvents < cEventsSize && lw.Step())
|
||
|
{
|
||
|
// go through and find all the valid
|
||
|
// clients that need waiting on
|
||
|
if (lw.That()->GetEvent(&ahEvents[cEvents]))
|
||
|
{
|
||
|
// lw.That()->Reset(FALSE);
|
||
|
cEvents++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return cEvents;
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::_MessagePump(void)
|
||
|
{
|
||
|
DWORD cFails = 0;
|
||
|
while (TRUE)
|
||
|
{
|
||
|
HANDLE ahEvents[MAXIMUM_WAIT_OBJECTS - 1];
|
||
|
DWORD cEvents = _GetInterruptEvents(ahEvents, ARRAYSIZE(ahEvents));
|
||
|
// maybe cache the events?
|
||
|
|
||
|
// NEED to handle pending Events with a Timer
|
||
|
|
||
|
DWORD dwWaitResult = MsgWaitForMultipleObjectsEx(cEvents, ahEvents,
|
||
|
INFINITE, QS_ALLINPUT, MWMO_ALERTABLE);
|
||
|
if (dwWaitResult != (DWORD)-1)
|
||
|
{
|
||
|
if (dwWaitResult != WAIT_IO_COMPLETION)
|
||
|
{
|
||
|
dwWaitResult -= WAIT_OBJECT_0;
|
||
|
if (dwWaitResult == cEvents)
|
||
|
{
|
||
|
// there is a message
|
||
|
if (_HandleMessages())
|
||
|
break;
|
||
|
}
|
||
|
else if (dwWaitResult < cEvents)
|
||
|
{
|
||
|
_SignalInterrupt(ahEvents[dwWaitResult]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cFails = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// there was some kind of error
|
||
|
TraceMsg(TF_ERROR, "SCNotify WaitForMulti() failed with %d", GetLastError());
|
||
|
// if MWFM() fails over and over, we give up.
|
||
|
if (++cFails > 10)
|
||
|
{
|
||
|
TraceMsg(TF_ERROR, "SCNotify WaitForMulti() bailing out");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SCNUninitialize(void)
|
||
|
{
|
||
|
if (g_pscn)
|
||
|
{
|
||
|
if (IsWindow(g_hwndSCN))
|
||
|
DestroyWindow(g_hwndSCN);
|
||
|
g_hwndSCN = NULL;
|
||
|
|
||
|
delete g_pscn;
|
||
|
g_pscn = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// the real thread proc, runs after CChangeNotify::ThreadStartUp runs sync
|
||
|
|
||
|
DWORD WINAPI CChangeNotify::ThreadProc(void *pv)
|
||
|
{
|
||
|
if (g_pscn)
|
||
|
{
|
||
|
CMountPoint::RegisterForHardwareNotifications();
|
||
|
|
||
|
#ifdef RESTARTSCN
|
||
|
__try
|
||
|
#endif
|
||
|
{
|
||
|
g_pscn->_MessagePump();
|
||
|
}
|
||
|
#ifdef RESTARTSCN
|
||
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
||
|
{
|
||
|
ASSERT(FALSE);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
SCNUninitialize();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
BOOL CChangeNotify::_OnChangeRegistration(HANDLE hChangeRegistration, DWORD dwProcId)
|
||
|
{
|
||
|
BOOL fResult = FALSE;
|
||
|
CHANGEREGISTER *pcr = (CHANGEREGISTER *)SHLockSharedEx(hChangeRegistration, dwProcId, TRUE);
|
||
|
if (pcr)
|
||
|
{
|
||
|
SHChangeNotifyEntry fsne;
|
||
|
|
||
|
fsne.pidl = NULL;
|
||
|
fsne.fRecursive = pcr->fRecursive;
|
||
|
if (pcr->uidlRegister)
|
||
|
fsne.pidl = _ILSkip(pcr, pcr->uidlRegister);
|
||
|
|
||
|
pcr->ulID = _RegisterClient((HWND)ULongToPtr(pcr->ulHwnd), pcr->fSources,
|
||
|
pcr->lEvents, pcr->uMsg, &fsne);
|
||
|
fResult = TRUE;
|
||
|
SHUnlockShared(pcr);
|
||
|
}
|
||
|
return fResult;
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::_ResetRelatedInterrupts(LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
if (_ptreeInterrupts)
|
||
|
{
|
||
|
// we need to match whoever listens on this pidl
|
||
|
CIDLMatchMany *pmany;
|
||
|
|
||
|
if (SUCCEEDED(_ptreeInterrupts->MatchMany(IDLDATAF_MATCH_RECURSIVE, pidl, &pmany)))
|
||
|
{
|
||
|
CInterruptSource *pintc;
|
||
|
while (S_OK == pmany->Next((INT_PTR *)&pintc, NULL))
|
||
|
{
|
||
|
// we might need WFSO(pintc->GetEvent()) here first
|
||
|
// if this is already signaled,
|
||
|
// we need to unsignal
|
||
|
pintc->Reset(FALSE);
|
||
|
}
|
||
|
delete pmany;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::_FlushInterrupts(void)
|
||
|
{
|
||
|
CLinkedWalk<CInterruptSource> lw(&_listInterrupts);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
lw.That()->Flush();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
#define CALLBACK_TIMEOUT 30000 // 30 seconds
|
||
|
void CChangeNotify::_WaitForCallbacks(void)
|
||
|
{
|
||
|
while (_cCallbacks)
|
||
|
{
|
||
|
MSG msg;
|
||
|
DWORD dwWaitResult = MsgWaitForMultipleObjects(1, &_hCallbackEvent, FALSE,
|
||
|
CALLBACK_TIMEOUT, QS_SENDMESSAGE);
|
||
|
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "FSN_WaitForCallbacks returned 0x%X", dwWaitResult);
|
||
|
if (dwWaitResult == WAIT_OBJECT_0) break; // Event completed
|
||
|
if (dwWaitResult == WAIT_TIMEOUT) break; // Ran out of time
|
||
|
|
||
|
if (dwWaitResult == WAIT_OBJECT_0+1)
|
||
|
{
|
||
|
//
|
||
|
// Some message came in, reset message event, deliver callbacks, etc.
|
||
|
//
|
||
|
PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE); // we need to do this to flush callbacks
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (_hCallbackEvent)
|
||
|
{
|
||
|
CloseHandle(_hCallbackEvent);
|
||
|
_hCallbackEvent = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::SetFlush(int idt)
|
||
|
{
|
||
|
switch (idt)
|
||
|
{
|
||
|
case FLUSH_OVERFLOW:
|
||
|
case FLUSH_SOFT:
|
||
|
SetTimer(g_hwndSCN, IDT_SCN_FLUSHEVENTS, 500, NULL);
|
||
|
break;
|
||
|
|
||
|
case FLUSH_HARD:
|
||
|
PostMessage(g_hwndSCN, SCNM_FLUSHEVENTS, 0, 0);
|
||
|
break;
|
||
|
|
||
|
case FLUSH_INTERRUPT:
|
||
|
SetTimer(g_hwndSCN, IDT_SCN_FLUSHEVENTS, 1000, NULL);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CChangeNotify::_Flush(BOOL fShouldWait)
|
||
|
{
|
||
|
_cFlushing++;
|
||
|
KillTimer(g_hwndSCN, IDT_SCN_FLUSHEVENTS);
|
||
|
// flush any pending interrupt events
|
||
|
_FlushInterrupts();
|
||
|
|
||
|
int iNumLoops = 0;
|
||
|
BOOL fProcessedAny;
|
||
|
do
|
||
|
{
|
||
|
fProcessedAny = FALSE;
|
||
|
CLinkedWalk<CAnyAlias> lwAliases(&_listAliases);
|
||
|
while (lwAliases.Step())
|
||
|
{
|
||
|
if (lwAliases.That()->Flush(TRUE))
|
||
|
{
|
||
|
fProcessedAny = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
iNumLoops++;
|
||
|
// in free builds bail out if there's a loop so we don't spin the thread.
|
||
|
// but this is pretty bad so assert anyway (the most people would usually have
|
||
|
// is 2 -- a folder shortcut to something on the desktop / mydocs)
|
||
|
ASSERTMSG(iNumLoops < 10, "we're in an alias loop, we're screwed");
|
||
|
} while (fProcessedAny && (iNumLoops < 10));
|
||
|
|
||
|
CLinkedWalk<CRegisteredClient> lwRegistered(&_listClients);
|
||
|
while (lwRegistered.Step())
|
||
|
{
|
||
|
lwRegistered.That()->Flush(fShouldWait);
|
||
|
}
|
||
|
|
||
|
if (fShouldWait)
|
||
|
{
|
||
|
// now wait for all the callbacks to empty out
|
||
|
_WaitForCallbacks();
|
||
|
}
|
||
|
_cFlushing--;
|
||
|
|
||
|
// wait until we have 10 seconds of free time
|
||
|
SetTimer(g_hwndSCN, IDT_SCN_FRESHENTREES, 10000, NULL);
|
||
|
}
|
||
|
|
||
|
BOOL IsILShared(LPCITEMIDLIST pidl, BOOL fUpdateCache)
|
||
|
{
|
||
|
TCHAR szTemp[MAXPATHLEN];
|
||
|
SHGetPathFromIDList(pidl, szTemp);
|
||
|
return IsShared(szTemp, fUpdateCache);
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::NotifyEvent(LONG lEvent, UINT uFlags, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra, DWORD dwEventTime)
|
||
|
{
|
||
|
if (!(uFlags & SHCNF_ONLYNOTIFYINTERNALS) && lEvent)
|
||
|
{
|
||
|
/// now do the actual generating of the event
|
||
|
if (lEvent & (SHCNE_NETSHARE | SHCNE_NETUNSHARE))
|
||
|
{
|
||
|
// Update the cache.
|
||
|
|
||
|
IsILShared(pidl, TRUE);
|
||
|
}
|
||
|
|
||
|
_AddToClients(lEvent, pidl, pidlExtra, dwEventTime, uFlags);
|
||
|
|
||
|
// remove any shell generated events for the file system
|
||
|
if ((lEvent & SHCNE_DISKEVENTS) &&
|
||
|
!(lEvent & (SHCNE_INTERRUPT | SHCNE_UPDATEDIR)))
|
||
|
{
|
||
|
_ResetRelatedInterrupts(pidl);
|
||
|
|
||
|
if (pidlExtra)
|
||
|
_ResetRelatedInterrupts(pidlExtra);
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// note make sure the internal events go first.
|
||
|
if (lEvent)
|
||
|
NotifyShellInternals(lEvent, uFlags, pidl, pidlExtra, dwEventTime);
|
||
|
|
||
|
//
|
||
|
// then the registered events
|
||
|
//
|
||
|
if (uFlags & (SHCNF_FLUSH))
|
||
|
{
|
||
|
if (uFlags & SHCNF_FLUSHNOWAIT)
|
||
|
{
|
||
|
SetFlush(FLUSH_HARD);
|
||
|
}
|
||
|
else
|
||
|
_Flush(TRUE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LRESULT CChangeNotify::_OnNotifyEvent(HANDLE hChange, DWORD dwProcId)
|
||
|
{
|
||
|
CHANGELOCK *pcl = _SHChangeNotification_Lock(hChange, dwProcId);
|
||
|
if (pcl)
|
||
|
{
|
||
|
NotifyEvent(pcl->pce->lEvent,
|
||
|
pcl->pce->uFlags,
|
||
|
pcl->pidlMain,
|
||
|
pcl->pidlExtra,
|
||
|
pcl->pce->dwEventTime);
|
||
|
SHChangeNotification_Unlock(pcl);
|
||
|
SHChangeNotification_Destroy(hChange, dwProcId);
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CInterruptSource::Suspend(BOOL fSuspend)
|
||
|
{
|
||
|
if (fSuspend)
|
||
|
{
|
||
|
if (!_cSuspend)
|
||
|
_Reset(FALSE);
|
||
|
|
||
|
_cSuspend++;
|
||
|
}
|
||
|
else if (_cSuspend)
|
||
|
_cSuspend--;
|
||
|
}
|
||
|
|
||
|
BOOL CChangeNotify::_SuspendResume(BOOL fSuspend, BOOL fRecursive, LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
if (_ptreeInterrupts)
|
||
|
{
|
||
|
CInterruptSource *pintc;
|
||
|
if (!fRecursive)
|
||
|
{
|
||
|
if (SUCCEEDED(_ptreeInterrupts->MatchOne(IDLDATAF_MATCH_EXACT, pidl, (INT_PTR*)&pintc, NULL)))
|
||
|
{
|
||
|
pintc->Suspend(fSuspend);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CIDLMatchMany *pmany;
|
||
|
if (SUCCEEDED(_ptreeInterrupts->MatchMany(IDLDATAF_MATCH_RECURSIVE, pidl, &pmany)))
|
||
|
{
|
||
|
while (S_OK == pmany->Next((INT_PTR *)&pintc, NULL))
|
||
|
{
|
||
|
pintc->Suspend(fSuspend);
|
||
|
}
|
||
|
delete pmany;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
#define SCNSUSPEND_SUSPEND 1
|
||
|
#define SCNSUSPEND_RECURSIVE 2
|
||
|
|
||
|
LRESULT CChangeNotify::_OnSuspendResume(HANDLE hChange, DWORD dwProcId)
|
||
|
{
|
||
|
BOOL fRet = FALSE;
|
||
|
CHANGELOCK *pcl = _SHChangeNotification_Lock(hChange, dwProcId);
|
||
|
if (pcl)
|
||
|
{
|
||
|
fRet = _SuspendResume(pcl->pce->uFlags & SCNSUSPEND_SUSPEND, pcl->pce->uFlags & SCNSUSPEND_RECURSIVE, pcl->pidlMain);
|
||
|
SHChangeNotification_Unlock((HANDLE)pcl);
|
||
|
}
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
BOOL CInterruptSource::SuspendDevice(BOOL fSuspend, HDEVNOTIFY hPNP)
|
||
|
{
|
||
|
BOOL fRet = FALSE;
|
||
|
if (hPNP)
|
||
|
{
|
||
|
if (fSuspend && _hPNP == hPNP)
|
||
|
{
|
||
|
_hSuspended = _hPNP;
|
||
|
Suspend(fSuspend);
|
||
|
_Reset(TRUE);
|
||
|
fRet = TRUE;
|
||
|
}
|
||
|
else if (!fSuspend && _hSuspended == hPNP)
|
||
|
{
|
||
|
_hSuspended = NULL;
|
||
|
Suspend(fSuspend);
|
||
|
fRet = TRUE;
|
||
|
}
|
||
|
}
|
||
|
else if (_hPNP)
|
||
|
{
|
||
|
// NULL means we are shutting down and should close all handles.
|
||
|
UnregisterDeviceNotification(_hPNP);
|
||
|
_hPNP = NULL;
|
||
|
}
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
// __HandleDevice
|
||
|
void CChangeNotify::_OnDeviceBroadcast(ULONG_PTR code, DEV_BROADCAST_HANDLE *pbhnd)
|
||
|
{
|
||
|
if (IsWindowVisible(GetShellWindow()) && pbhnd
|
||
|
&& (pbhnd->dbch_devicetype == DBT_DEVTYP_HANDLE && pbhnd->dbch_hdevnotify))
|
||
|
{
|
||
|
BOOL fSuspend;
|
||
|
switch (code)
|
||
|
{
|
||
|
|
||
|
// When PnP is finished messing with the drive (either successfully
|
||
|
// or unsuccessfully), resume notifications on that drive.
|
||
|
case DBT_DEVICEREMOVECOMPLETE:
|
||
|
case DBT_DEVICEQUERYREMOVEFAILED:
|
||
|
fSuspend = FALSE;
|
||
|
break;
|
||
|
|
||
|
// When PnP is starting to mess with the drive, suspend notifications
|
||
|
// so it can do its thing
|
||
|
case DBT_DEVICEQUERYREMOVE:
|
||
|
|
||
|
// This will wait on another thread to exit if this hdevnotify
|
||
|
// was registered for a Sniffing Dialog
|
||
|
CSniffDrive::HandleNotif(pbhnd->dbch_hdevnotify);
|
||
|
|
||
|
fSuspend = TRUE;
|
||
|
break;
|
||
|
|
||
|
case DBT_CUSTOMEVENT:
|
||
|
if (GUID_IO_VOLUME_LOCK == pbhnd->dbch_eventguid)
|
||
|
{
|
||
|
TraceMsg(TF_MOUNTPOINT, "GUID_IO_VOLUME_LOCK: Suspending!");
|
||
|
fSuspend = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (GUID_IO_VOLUME_LOCK_FAILED == pbhnd->dbch_eventguid)
|
||
|
{
|
||
|
TraceMsg(TF_MOUNTPOINT, "GUID_IO_VOLUME_LOCK_FAILED: Resuming!");
|
||
|
fSuspend = FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (GUID_IO_VOLUME_UNLOCK == pbhnd->dbch_eventguid)
|
||
|
{
|
||
|
TraceMsg(TF_MOUNTPOINT, "GUID_IO_VOLUME_UNLOCK: Resuming!");
|
||
|
fSuspend = FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// we dont handle anything else here
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CLinkedWalk<CInterruptSource> lw(&_listInterrupts);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
// returns true if found
|
||
|
if (lw.That()->SuspendDevice(fSuspend, pbhnd->dbch_hdevnotify))
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::_FreshenClients(void)
|
||
|
{
|
||
|
CLinkedWalk<CRegisteredClient> lw(&_listClients);
|
||
|
|
||
|
while (lw.Step())
|
||
|
{
|
||
|
if (lw.That()->_fDeadClient || !IsWindow(lw.That()->_hwnd))
|
||
|
{
|
||
|
if (_DeregisterClient(lw.That()))
|
||
|
{
|
||
|
lw.Delete();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CChangeNotify::_FreshenUp(void)
|
||
|
{
|
||
|
ASSERT(!_cFlushing);
|
||
|
KillTimer(g_hwndSCN, IDT_SCN_FRESHENTREES);
|
||
|
|
||
|
if (_ptreeClients)
|
||
|
_ptreeClients->Freshen();
|
||
|
|
||
|
if (_ptreeInterrupts)
|
||
|
_ptreeInterrupts->Freshen();
|
||
|
|
||
|
_FreshenAliases();
|
||
|
_FreshenClients();
|
||
|
}
|
||
|
|
||
|
LRESULT CChangeNotify::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
LRESULT lRes = 0;
|
||
|
ASSERT(g_pscn);
|
||
|
|
||
|
switch (uMsg)
|
||
|
{
|
||
|
case SCNM_REGISTERCLIENT:
|
||
|
lRes = g_pscn->_OnChangeRegistration((HANDLE)wParam, (DWORD)lParam);
|
||
|
break;
|
||
|
|
||
|
case SCNM_DEREGISTERCLIENT:
|
||
|
lRes = g_pscn->_DeregisterClientByID((ULONG)wParam);
|
||
|
break;
|
||
|
|
||
|
case SCNM_DEREGISTERWINDOW:
|
||
|
lRes = g_pscn->_DeregisterClientsByWindow((HWND)wParam);
|
||
|
break;
|
||
|
|
||
|
case SCNM_NOTIFYEVENT:
|
||
|
lRes = g_pscn->_OnNotifyEvent((HANDLE)wParam, (DWORD)lParam);
|
||
|
break;
|
||
|
|
||
|
case SCNM_SUSPENDRESUME:
|
||
|
lRes = g_pscn->_OnSuspendResume((HANDLE)wParam, (DWORD)lParam);
|
||
|
break;
|
||
|
|
||
|
case WM_TIMER:
|
||
|
if (wParam == IDT_SCN_FRESHENTREES)
|
||
|
{
|
||
|
g_pscn->_FreshenUp();
|
||
|
break;
|
||
|
}
|
||
|
// Fall through to SCNM_FLUSHEVENTS
|
||
|
case SCNM_FLUSHEVENTS:
|
||
|
g_pscn->_Flush(FALSE);
|
||
|
break;
|
||
|
|
||
|
case SCNM_AUTOPLAYDRIVE:
|
||
|
CMountPoint::DoAutorunPrompt(wParam);
|
||
|
break;
|
||
|
|
||
|
case WM_DEVICECHANGE:
|
||
|
g_pscn->_OnDeviceBroadcast(wParam, (DEV_BROADCAST_HANDLE *)lParam);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
lRes = DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return lRes;
|
||
|
}
|
||
|
|
||
|
// thread setup routine, executed before SHCreateThread() returns
|
||
|
|
||
|
DWORD WINAPI CChangeNotify::ThreadStartUp(void *pv)
|
||
|
{
|
||
|
g_pscn = new CChangeNotify();
|
||
|
if (g_pscn)
|
||
|
{
|
||
|
g_hwndSCN = SHCreateWorkerWindow(CChangeNotify::WndProc, NULL, 0, 0, NULL, g_pscn);
|
||
|
|
||
|
CSniffDrive::InitNotifyWindow(g_hwndSCN);
|
||
|
|
||
|
InitAliasFolderTable();
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// now we create the window
|
||
|
BOOL SCNInitialize()
|
||
|
{
|
||
|
EnterCriticalSection(&g_csSCN);
|
||
|
if (!IsWindow(g_hwndSCN))
|
||
|
{
|
||
|
SHCreateThread(CChangeNotify::ThreadProc, NULL, CTF_COINIT, CChangeNotify::ThreadStartUp);
|
||
|
}
|
||
|
LeaveCriticalSection(&g_csSCN);
|
||
|
return g_hwndSCN ? TRUE : FALSE; // ThreadStartUp is executed sync
|
||
|
}
|
||
|
|
||
|
BOOL _IsImpersonating()
|
||
|
{
|
||
|
HANDLE hToken;
|
||
|
if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken))
|
||
|
{
|
||
|
CloseHandle(hToken);
|
||
|
return TRUE;
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
STDAPI_(HWND) _SCNGetWindow(BOOL fUseDesktop, BOOL fNeedsFallback)
|
||
|
{
|
||
|
// if explorer is trashed
|
||
|
// then this hwnd can go bad
|
||
|
// get a new copy from the desktop
|
||
|
if (!g_hwndSCN || !IsWindow(g_hwndSCN))
|
||
|
{
|
||
|
HWND hwndDesktop = fUseDesktop ? GetShellWindow() : NULL;
|
||
|
if (hwndDesktop)
|
||
|
{
|
||
|
HWND hwndSCN = (HWND) SendMessage(hwndDesktop, CWM_GETSCNWINDOW, 0, 0);
|
||
|
if (_IsImpersonating())
|
||
|
return hwndSCN;
|
||
|
else
|
||
|
g_hwndSCN = hwndSCN;
|
||
|
}
|
||
|
else if (fNeedsFallback && SHIsCurrentThreadInteractive())
|
||
|
{
|
||
|
// there is no desktop.
|
||
|
// so we create a private desktop
|
||
|
// this will create the thread and window
|
||
|
// and set
|
||
|
SCNInitialize();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return g_hwndSCN;
|
||
|
}
|
||
|
|
||
|
STDAPI_(HWND) SCNGetWindow(BOOL fUseDesktop)
|
||
|
{
|
||
|
return _SCNGetWindow(fUseDesktop, TRUE);
|
||
|
}
|
||
|
|
||
|
HANDLE SHChangeRegistration_Create(ULONG ulID,
|
||
|
HWND hwnd, UINT uMsg,
|
||
|
DWORD fSources, LONG lEvents,
|
||
|
BOOL fRecursive, LPCITEMIDLIST pidl,
|
||
|
DWORD dwProcId)
|
||
|
{
|
||
|
UINT uidlSize = ILGetSize(pidl);
|
||
|
HANDLE hReg = SHAllocShared(NULL, sizeof(CHANGEREGISTER) + uidlSize, dwProcId);
|
||
|
if (hReg)
|
||
|
{
|
||
|
CHANGEREGISTER *pcr = (CHANGEREGISTER *) SHLockSharedEx(hReg, dwProcId, TRUE);
|
||
|
if (pcr)
|
||
|
{
|
||
|
pcr->dwSig = CHANGEREGISTER_SIG;
|
||
|
pcr->ulID = ulID;
|
||
|
pcr->ulHwnd = PtrToUlong(hwnd);
|
||
|
pcr->uMsg = uMsg;
|
||
|
pcr->fSources = fSources;
|
||
|
pcr->lEvents = lEvents;
|
||
|
pcr->fRecursive = fRecursive;
|
||
|
pcr->uidlRegister = 0;
|
||
|
|
||
|
if (pidl)
|
||
|
{
|
||
|
pcr->uidlRegister = sizeof(CHANGEREGISTER);
|
||
|
memcpy((pcr + 1), pidl, uidlSize);
|
||
|
}
|
||
|
SHUnlockShared(pcr);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SHFreeShared(hReg, dwProcId);
|
||
|
hReg = NULL;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return hReg;
|
||
|
}
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
HWND hwnd;
|
||
|
UINT wMsg;
|
||
|
} NOTIFY_PROXY_DATA;
|
||
|
#define WM_CHANGENOTIFYMSG WM_USER + 1
|
||
|
LRESULT CALLBACK _HiddenNotifyWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
LRESULT lRes = FALSE;
|
||
|
NOTIFY_PROXY_DATA *pData = (NOTIFY_PROXY_DATA *) GetWindowLongPtr( hWnd, 0 );
|
||
|
|
||
|
switch (iMessage)
|
||
|
{
|
||
|
case WM_NCDESTROY:
|
||
|
ASSERT(pData != NULL );
|
||
|
|
||
|
// clear it so it won't be in use....
|
||
|
SetWindowLongPtr( hWnd, 0, (LONG_PTR)NULL );
|
||
|
|
||
|
// free the memory ...
|
||
|
LocalFree( pData );
|
||
|
break;
|
||
|
|
||
|
case WM_CHANGENOTIFYMSG :
|
||
|
if (pData)
|
||
|
{
|
||
|
// lock and break the info structure ....
|
||
|
LPITEMIDLIST *ppidl;
|
||
|
LONG lEvent;
|
||
|
HANDLE hLock = SHChangeNotification_Lock((HANDLE)wParam, (DWORD)lParam, &ppidl, &lEvent);
|
||
|
|
||
|
if (hLock)
|
||
|
{
|
||
|
// pass on to the old style client. ...
|
||
|
lRes = SendMessage( pData->hwnd, pData->wMsg, (WPARAM) ppidl, (LPARAM) lEvent );
|
||
|
|
||
|
// new notifications ......
|
||
|
SHChangeNotification_Unlock(hLock);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
lRes = DefWindowProc(hWnd, iMessage, wParam, lParam);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return lRes;
|
||
|
}
|
||
|
|
||
|
|
||
|
HWND _CreateProxyWindow(HWND hwnd, UINT wMsg)
|
||
|
{
|
||
|
HWND hwndRet = NULL;
|
||
|
// This is an old style notification, we need to create a hidden
|
||
|
// proxy type of window to properly handle the messages...
|
||
|
|
||
|
NOTIFY_PROXY_DATA *pnpd = (NOTIFY_PROXY_DATA *)LocalAlloc(LPTR, sizeof(*pnpd));
|
||
|
|
||
|
if (pnpd)
|
||
|
{
|
||
|
pnpd->hwnd = hwnd;
|
||
|
pnpd->wMsg = wMsg;
|
||
|
|
||
|
hwndRet = SHCreateWorkerWindow(_HiddenNotifyWndProc, NULL, 0, 0, NULL, pnpd);
|
||
|
|
||
|
if (!hwndRet)
|
||
|
LocalFree(pnpd);
|
||
|
|
||
|
}
|
||
|
|
||
|
return hwndRet;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------
|
||
|
//
|
||
|
// Returns a positive integer registration ID, or 0 if out of memory or if
|
||
|
// invalid parameters were passed in.
|
||
|
//
|
||
|
// If the hwnd is != NULL we do a PostMessage(hwnd, wMsg, ...) when a
|
||
|
// relevant FS event takes place, otherwise if fsncb is != NULL we call it.
|
||
|
//
|
||
|
STDAPI_(ULONG) SHChangeNotifyRegister(HWND hwnd,
|
||
|
int fSources, LONG fEvents,
|
||
|
UINT wMsg, int cEntries,
|
||
|
SHChangeNotifyEntry *pfsne)
|
||
|
{
|
||
|
ULONG ulID = 0;
|
||
|
BOOL fResult = FALSE;
|
||
|
HWND hwndSCN = SCNGetWindow(TRUE);
|
||
|
|
||
|
if (hwndSCN)
|
||
|
{
|
||
|
if (!(fSources & SHCNRF_NewDelivery))
|
||
|
{
|
||
|
// Now setup to use the proxy window instead
|
||
|
hwnd = _CreateProxyWindow(hwnd, wMsg);
|
||
|
wMsg = WM_CHANGENOTIFYMSG;
|
||
|
}
|
||
|
|
||
|
if ((fSources & SHCNRF_RecursiveInterrupt) && !(fSources & SHCNRF_InterruptLevel))
|
||
|
{
|
||
|
// bad caller, they asked for recursive interrupt events, but not interrupt events
|
||
|
ASSERTMSG(FALSE, "SHChangeNotifyRegister: caller passed SHCNRF_RecursiveInterrupt but NOT SHCNRF_InterruptLevel !!");
|
||
|
|
||
|
// clear the flag
|
||
|
fSources = fSources & (~SHCNRF_RecursiveInterrupt);
|
||
|
}
|
||
|
|
||
|
// This same assert is CRegisteredClient::Init, caled by SCNM_REGISTERCLIENT message below
|
||
|
ASSERT(fSources & (SHCNRF_InterruptLevel | SHCNRF_ShellLevel));
|
||
|
|
||
|
// NOTE - if we have more than one registration entry here,
|
||
|
// we only support Deregister'ing the last one
|
||
|
for (int i = 0; i < cEntries; i++)
|
||
|
{
|
||
|
DWORD dwProcId;
|
||
|
GetWindowThreadProcessId(hwndSCN, &dwProcId);
|
||
|
HANDLE hChangeRegistration = SHChangeRegistration_Create(
|
||
|
ulID, hwnd, wMsg,
|
||
|
fSources, fEvents,
|
||
|
pfsne[i].fRecursive, pfsne[i].pidl,
|
||
|
dwProcId);
|
||
|
if (hChangeRegistration)
|
||
|
{
|
||
|
CHANGEREGISTER * pcr;
|
||
|
//
|
||
|
// Transmit the change regsitration
|
||
|
//
|
||
|
SendMessage(hwndSCN, SCNM_REGISTERCLIENT,
|
||
|
(WPARAM)hChangeRegistration, (LPARAM)dwProcId);
|
||
|
|
||
|
//
|
||
|
// Now get back the ulID value, for further registrations and
|
||
|
// for returning to the calling function...
|
||
|
//
|
||
|
pcr = (CHANGEREGISTER *)SHLockSharedEx(hChangeRegistration, dwProcId, FALSE);
|
||
|
if (pcr)
|
||
|
{
|
||
|
ulID = pcr->ulID;
|
||
|
SHUnlockShared(pcr);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ASSERT(0 == ulID); // Error condition initialized above
|
||
|
}
|
||
|
|
||
|
SHFreeShared(hChangeRegistration, dwProcId);
|
||
|
}
|
||
|
|
||
|
if ((ulID == 0) && !(fSources & SHCNRF_NewDelivery))
|
||
|
{
|
||
|
// this is our proxy window
|
||
|
DestroyWindow(hwnd);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return ulID;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------
|
||
|
//
|
||
|
// Returns TRUE if we found and removed the specified Client, otherwise
|
||
|
// returns FALSE.
|
||
|
//
|
||
|
STDAPI_(BOOL) SHChangeNotifyDeregister(ULONG ulID)
|
||
|
{
|
||
|
BOOL fResult = FALSE;
|
||
|
HWND hwnd = _SCNGetWindow(TRUE, FALSE);
|
||
|
|
||
|
if (hwnd)
|
||
|
{
|
||
|
//
|
||
|
// Transmit the change registration
|
||
|
//
|
||
|
fResult = (BOOL) SendMessage(hwnd, SCNM_DEREGISTERCLIENT, ulID, 0);
|
||
|
}
|
||
|
return fResult;
|
||
|
}
|
||
|
|
||
|
// send the notify to the desktop... telling it to put it in the queue.
|
||
|
// if we are in the desktop's process, we can handle it directly ourselves.
|
||
|
// the one exception is flush. we want the desktop to be one serializing flush so
|
||
|
// we send in that case as well
|
||
|
void SHChangeNotifyTransmit(LONG lEvent, UINT uFlags, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra, DWORD dwEventTime)
|
||
|
{
|
||
|
HWND hwndSCN = _SCNGetWindow(TRUE, FALSE);
|
||
|
|
||
|
if (hwndSCN)
|
||
|
{
|
||
|
DWORD dwProcId;
|
||
|
GetWindowThreadProcessId(hwndSCN, &dwProcId);
|
||
|
HANDLE hChange = SHChangeNotification_Create(lEvent, uFlags, pidl, pidlExtra, dwProcId, dwEventTime);
|
||
|
|
||
|
if (hChange)
|
||
|
{
|
||
|
BOOL fFlushNow = ((uFlags & (SHCNF_FLUSH | SHCNF_FLUSHNOWAIT)) == SHCNF_FLUSH);
|
||
|
|
||
|
// Flush but not flush no wait
|
||
|
if (fFlushNow)
|
||
|
{
|
||
|
SendMessage(hwndSCN, SCNM_NOTIFYEVENT,
|
||
|
(WPARAM)hChange, (LPARAM)dwProcId);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SendNotifyMessage(hwndSCN, SCNM_NOTIFYEVENT,
|
||
|
(WPARAM)hChange, (LPARAM)dwProcId);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FreeSpacePidlToPath(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
|
||
|
{
|
||
|
TCHAR szPath1[MAX_PATH];
|
||
|
if (SHGetPathFromIDList(pidl1, szPath1))
|
||
|
{
|
||
|
TCHAR szPath2[MAX_PATH];
|
||
|
szPath2[0] = 0;
|
||
|
if (pidl2)
|
||
|
{
|
||
|
SHGetPathFromIDList(pidl2, szPath2);
|
||
|
}
|
||
|
SHChangeNotify(SHCNE_FREESPACE, SHCNF_PATH, szPath1, szPath2[0] ? szPath2 : NULL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
STDAPI_(void) SHChangeNotify(LONG lEvent, UINT uFlags, const void * dwItem1, const void * dwItem2)
|
||
|
{
|
||
|
if (!_SCNGetWindow(TRUE, FALSE))
|
||
|
return;
|
||
|
|
||
|
LPCITEMIDLIST pidl = NULL;
|
||
|
LPCITEMIDLIST pidlExtra = NULL;
|
||
|
LPITEMIDLIST pidlFree = NULL;
|
||
|
LPITEMIDLIST pidlExtraFree = NULL;
|
||
|
UINT uType = uFlags & SHCNF_TYPE;
|
||
|
SHChangeDWORDAsIDList dwidl;
|
||
|
BOOL fPrinter = FALSE;
|
||
|
BOOL fPrintJob = FALSE;
|
||
|
DWORD dwEventTime = GetTickCount();
|
||
|
|
||
|
// first setup anything the flags request
|
||
|
switch (uType)
|
||
|
{
|
||
|
case SHCNF_PRINTJOBA:
|
||
|
fPrintJob = TRUE;
|
||
|
// fall through
|
||
|
case SHCNF_PRINTERA:
|
||
|
fPrinter = TRUE;
|
||
|
// fall through
|
||
|
case SHCNF_PATHA:
|
||
|
{
|
||
|
TCHAR szPath1[MAX_PATH], szPath2[MAX_PATH];
|
||
|
LPCVOID pvItem1 = NULL;
|
||
|
LPCVOID pvItem2 = NULL;
|
||
|
|
||
|
if (dwItem1)
|
||
|
{
|
||
|
SHAnsiToTChar((LPSTR)dwItem1, szPath1, ARRAYSIZE(szPath1));
|
||
|
pvItem1 = szPath1;
|
||
|
}
|
||
|
|
||
|
if (dwItem2)
|
||
|
{
|
||
|
if (fPrintJob)
|
||
|
pvItem2 = dwItem2; // SHCNF_PRINTJOB_DATA needs no conversion
|
||
|
else
|
||
|
{
|
||
|
SHAnsiToTChar((LPSTR)dwItem2, szPath2, ARRAYSIZE(szPath2));
|
||
|
pvItem2 = szPath2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SHChangeNotify(lEvent, (fPrintJob ? SHCNF_PRINTJOB : (fPrinter ? SHCNF_PRINTER : SHCNF_PATH)),
|
||
|
pvItem1, pvItem2);
|
||
|
goto Cleanup; // Let the recursive version do all the work
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SHCNF_PATH:
|
||
|
if (lEvent == SHCNE_FREESPACE)
|
||
|
{
|
||
|
DWORD dwItem = 0;
|
||
|
int idDrive = PathGetDriveNumber((LPCTSTR)dwItem1);
|
||
|
if (idDrive != -1)
|
||
|
dwItem = (1 << idDrive);
|
||
|
|
||
|
if (dwItem2)
|
||
|
{
|
||
|
idDrive = PathGetDriveNumber((LPCTSTR)dwItem2);
|
||
|
if (idDrive != -1)
|
||
|
dwItem |= (1 << idDrive);
|
||
|
}
|
||
|
|
||
|
dwItem1 = (LPCVOID)ULongToPtr( dwItem );
|
||
|
if (dwItem1)
|
||
|
goto DoDWORD;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (dwItem1)
|
||
|
{
|
||
|
pidl = pidlFree = SHSimpleIDListFromPath((LPCTSTR)dwItem1);
|
||
|
if (!pidl)
|
||
|
goto Cleanup;
|
||
|
|
||
|
if (dwItem2)
|
||
|
{
|
||
|
pidlExtra = pidlExtraFree = SHSimpleIDListFromPath((LPCTSTR)dwItem2);
|
||
|
if (!pidlExtra)
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SHCNF_PRINTER:
|
||
|
if (dwItem1)
|
||
|
{
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "SHChangeNotify: SHCNF_PRINTER %s", (LPTSTR)dwItem1);
|
||
|
|
||
|
if (FAILED(ParsePrinterName((LPCTSTR)dwItem1, &pidlFree)))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
pidl = pidlFree;
|
||
|
|
||
|
if (dwItem2)
|
||
|
{
|
||
|
if (FAILED(ParsePrinterName((LPCTSTR)dwItem2, &pidlExtraFree)))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
pidlExtra = pidlExtraFree;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SHCNF_PRINTJOB:
|
||
|
if (dwItem1)
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
switch (lEvent)
|
||
|
{
|
||
|
case SHCNE_CREATE:
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "SHChangeNotify: SHCNE_CREATE SHCNF_PRINTJOB %s", (LPTSTR)dwItem1);
|
||
|
break;
|
||
|
case SHCNE_DELETE:
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "SHChangeNotify: SHCNE_DELETE SHCNF_PRINTJOB %s", (LPTSTR)dwItem1);
|
||
|
break;
|
||
|
case SHCNE_UPDATEITEM:
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "SHChangeNotify: SHCNE_UPDATEITEM SHCNF_PRINTJOB %s", (LPTSTR)dwItem1);
|
||
|
break;
|
||
|
default:
|
||
|
TraceMsg(TF_SHELLCHANGENOTIFY, "SHChangeNotify: SHCNE_? SHCNF_PRINTJOB %s", (LPTSTR)dwItem1);
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
pidl = pidlFree = Printjob_GetPidl((LPCTSTR)dwItem1, (LPSHCNF_PRINTJOB_DATA)dwItem2);
|
||
|
if (!pidl)
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Caller goofed.
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SHCNF_DWORD:
|
||
|
DoDWORD:
|
||
|
ASSERT(lEvent & SHCNE_GLOBALEVENTS);
|
||
|
|
||
|
dwidl.cb = sizeof(dwidl) - sizeof(dwidl.cbZero);
|
||
|
dwidl.dwItem1 = PtrToUlong(dwItem1);
|
||
|
dwidl.dwItem2 = PtrToUlong(dwItem2);
|
||
|
dwidl.cbZero = 0;
|
||
|
pidl = (LPCITEMIDLIST)&dwidl;
|
||
|
pidlExtra = NULL;
|
||
|
break;
|
||
|
|
||
|
case 0:
|
||
|
if (lEvent == SHCNE_FREESPACE) {
|
||
|
// convert this to paths.
|
||
|
FreeSpacePidlToPath((LPCITEMIDLIST)dwItem1, (LPCITEMIDLIST)dwItem2);
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
pidl = (LPCITEMIDLIST)dwItem1;
|
||
|
pidlExtra = (LPCITEMIDLIST)dwItem2;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
TraceMsg(TF_ERROR, "SHChangeNotify: Unrecognized uFlags 0x%X", uFlags);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (lEvent && !(lEvent & SHCNE_ASSOCCHANGED) && !pidl)
|
||
|
{
|
||
|
// Caller goofed. SHChangeNotifyTransmit & clients assume pidl is
|
||
|
// non-NULL if lEvent is non-zero (except in the SHCNE_ASSOCCHANGED case),
|
||
|
// and they will crash if we try to send this bogus event. So throw out
|
||
|
// this event and rip.
|
||
|
RIP(FALSE);
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
SHChangeNotifyTransmit(lEvent, uFlags, pidl, pidlExtra, dwEventTime);
|
||
|
|
||
|
Cleanup:
|
||
|
|
||
|
if (pidlFree)
|
||
|
ILFree(pidlFree);
|
||
|
if (pidlExtraFree)
|
||
|
ILFree(pidlExtraFree);
|
||
|
}
|
||
|
|
||
|
// SHChangeNotifySuspendResume
|
||
|
//
|
||
|
// Suspends or resumes filesystem notifications on a path. If bRecursive
|
||
|
// is set, disable/enables them for all child paths as well.
|
||
|
|
||
|
STDAPI_(BOOL) SHChangeNotifySuspendResume(BOOL bSuspend,
|
||
|
LPITEMIDLIST pidlSuspend,
|
||
|
BOOL bRecursive,
|
||
|
DWORD dwReserved)
|
||
|
{
|
||
|
BOOL fRet = FALSE;
|
||
|
HWND hwndSCN = _SCNGetWindow(TRUE, FALSE);
|
||
|
|
||
|
if (hwndSCN)
|
||
|
{
|
||
|
HANDLE hChange;
|
||
|
DWORD dwProcId;
|
||
|
UINT uiFlags = bSuspend ? SCNSUSPEND_SUSPEND : 0;
|
||
|
if (bRecursive)
|
||
|
uiFlags |= SCNSUSPEND_RECURSIVE;
|
||
|
|
||
|
GetWindowThreadProcessId(hwndSCN, &dwProcId);
|
||
|
|
||
|
// overloading the structure semantics here a little bit.
|
||
|
// our two flags
|
||
|
hChange = SHChangeNotification_Create(0, uiFlags, pidlSuspend, NULL, dwProcId, 0);
|
||
|
if (hChange)
|
||
|
{
|
||
|
// Transmit to SCN
|
||
|
fRet = (BOOL)SendMessage(hwndSCN, SCNM_SUSPENDRESUME, (WPARAM)hChange, (LPARAM)dwProcId);
|
||
|
SHChangeNotification_Destroy(hChange, dwProcId);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDAPI_(void) SHChangeNotifyTerminate(BOOL bLastTerm, BOOL bProcessShutdown)
|
||
|
{
|
||
|
if (g_pscn)
|
||
|
{
|
||
|
PostThreadMessage(GetWindowThreadProcessId(g_hwndSCN, NULL), SCNM_TERMINATE, 0, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// this deregisters anything that this window might have been registered in
|
||
|
STDAPI_(void) SHChangeNotifyDeregisterWindow(HWND hwnd)
|
||
|
{
|
||
|
HWND hwndSCN = _SCNGetWindow(TRUE, FALSE);
|
||
|
|
||
|
if (hwndSCN)
|
||
|
{
|
||
|
SendMessage(hwndSCN, SCNM_DEREGISTERWINDOW, (WPARAM)hwnd, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------
|
||
|
// We changed the way that the SHChangeNotifyRegister function worked, so
|
||
|
// to prevent people from calling the old function, we stub it out here.
|
||
|
// The change we made would have broken everbody because we changed the
|
||
|
// lparam and wparam for the notification messages which are sent to the
|
||
|
// registered window.
|
||
|
//
|
||
|
STDAPI_(ULONG) NTSHChangeNotifyRegister(HWND hwnd,
|
||
|
int fSources, LONG fEvents,
|
||
|
UINT wMsg, int cEntries,
|
||
|
SHChangeNotifyEntry *pfsne)
|
||
|
{
|
||
|
return SHChangeNotifyRegister(hwnd, fSources | SHCNRF_NewDelivery , fEvents, wMsg, cEntries, pfsne);
|
||
|
}
|
||
|
STDAPI_(BOOL) NTSHChangeNotifyDeregister(ULONG ulID)
|
||
|
{
|
||
|
return SHChangeNotifyDeregister(ulID);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// NOTE: There is a copy of these functions in shdocvw util.cpp for browser only mode supprt.
|
||
|
// NOTE: functionality changes should also be reflected there.
|
||
|
STDAPI_(void) SHUpdateImageA( LPCSTR pszHashItem, int iIndex, UINT uFlags, int iImageIndex )
|
||
|
{
|
||
|
WCHAR szWHash[MAX_PATH];
|
||
|
|
||
|
SHAnsiToUnicode(pszHashItem, szWHash, ARRAYSIZE(szWHash));
|
||
|
|
||
|
SHUpdateImageW(szWHash, iIndex, uFlags, iImageIndex);
|
||
|
}
|
||
|
|
||
|
STDAPI_(void) SHUpdateImageW( LPCWSTR pszHashItem, int iIndex, UINT uFlags, int iImageIndex )
|
||
|
{
|
||
|
SHChangeUpdateImageIDList rgPidl;
|
||
|
SHChangeDWORDAsIDList rgDWord;
|
||
|
|
||
|
int cLen = MAX_PATH - (lstrlenW( pszHashItem ) + 1);
|
||
|
cLen *= sizeof( WCHAR );
|
||
|
|
||
|
if ( cLen < 0 )
|
||
|
{
|
||
|
cLen = 0;
|
||
|
}
|
||
|
|
||
|
// make sure we send a valid index
|
||
|
if ( iImageIndex == -1 )
|
||
|
{
|
||
|
iImageIndex = II_DOCUMENT;
|
||
|
}
|
||
|
|
||
|
rgPidl.dwProcessID = GetCurrentProcessId();
|
||
|
rgPidl.iIconIndex = iIndex;
|
||
|
rgPidl.iCurIndex = iImageIndex;
|
||
|
rgPidl.uFlags = uFlags;
|
||
|
StrCpyNW( rgPidl.szName, pszHashItem, MAX_PATH );
|
||
|
rgPidl.cb = (USHORT)(sizeof( rgPidl ) - cLen);
|
||
|
_ILNext( (LPITEMIDLIST) &rgPidl )->mkid.cb = 0;
|
||
|
|
||
|
rgDWord.cb = sizeof( rgDWord) - sizeof(USHORT);
|
||
|
rgDWord.dwItem1 = iImageIndex;
|
||
|
rgDWord.dwItem2 = 0;
|
||
|
rgDWord.cbZero = 0;
|
||
|
|
||
|
// pump it as an extended event
|
||
|
SHChangeNotify(SHCNE_UPDATEIMAGE, SHCNF_IDLIST, &rgDWord, &rgPidl);
|
||
|
}
|
||
|
|
||
|
// REVIEW: pretty poor implementation of handling updateimage, requiring the caller
|
||
|
// to handle the pidl case instead of passing both pidls down here.
|
||
|
//
|
||
|
STDAPI_(int) SHHandleUpdateImage( LPCITEMIDLIST pidlExtra )
|
||
|
{
|
||
|
SHChangeUpdateImageIDList * pUs = (SHChangeUpdateImageIDList*) pidlExtra;
|
||
|
|
||
|
if ( !pUs )
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// if in the same process, or an old style notification
|
||
|
if ( pUs->dwProcessID == GetCurrentProcessId())
|
||
|
{
|
||
|
return *(int UNALIGNED *)((BYTE *)&pUs->iCurIndex);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WCHAR szBuffer[MAX_PATH];
|
||
|
int iIconIndex = *(int UNALIGNED *)((BYTE *)&pUs->iIconIndex);
|
||
|
UINT uFlags = *(UINT UNALIGNED *)((BYTE *)&pUs->uFlags);
|
||
|
|
||
|
ualstrcpyW( szBuffer, pUs->szName );
|
||
|
|
||
|
// we are in a different process, look up the hash in our index to get the right one...
|
||
|
return SHLookupIconIndexW( szBuffer, iIconIndex, uFlags );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// NOTE: these are OLD APIs, new clients should use new APIs
|
||
|
//
|
||
|
// REVIEW: BobDay - SHChangeNotifyUpdateEntryList doesn't appear to be
|
||
|
// called by anybody and since we've change the notification message
|
||
|
// structure, anybody who calls it needs to be identified and fixed.
|
||
|
//
|
||
|
BOOL WINAPI SHChangeNotifyUpdateEntryList(ULONG ulID, int iUpdateType,
|
||
|
int cEntries, SHChangeNotifyEntry *pfsne)
|
||
|
{
|
||
|
ASSERT(FALSE);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
void SHChangeNotifyReceive(LONG lEvent, UINT uFlags, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra)
|
||
|
{
|
||
|
ASSERT(FALSE);
|
||
|
}
|
||
|
|
||
|
BOOL WINAPI SHChangeRegistrationReceive(HANDLE hChangeRegistration, DWORD dwProcId)
|
||
|
{
|
||
|
ASSERT(FALSE);
|
||
|
return FALSE;
|
||
|
}
|