#include "shellprv.h" #pragma hdrstop #include #include #include "printer.h" #include #include "idltree.h" #include "scnotifyp.h" #include "mtpt.h" #include "shitemid.h" #include #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 lw(&_listAliases); while (lw.Step()) { lw.That()->_dwTime = tick; } } s_tick = tick; } CAnyAlias *CChangeNotify::_FindSpecialAlias(int csidlReal, int csidlAlias) { CLinkedWalk 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 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 *p = new CLinkedNode; 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 *p = new CLinkedNode; 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 *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 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 *p = new CLinkedNode; 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 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 *p = new CLinkedNode; 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 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 *p = new CLinkedNode; 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 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 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 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 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 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 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 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 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 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 lw(&_listInterrupts); while (lw.Step()) { // returns true if found if (lw.That()->SuspendDevice(fSuspend, pbhnd->dbch_hdevnotify)) break; } } } void CChangeNotify::_FreshenClients(void) { CLinkedWalk 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; }