You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1017 lines
32 KiB
1017 lines
32 KiB
/**************************** Module Header ********************************\
|
|
* Module Name: winable.c
|
|
*
|
|
* This has the stuff for WinEvents:
|
|
* NotifyWinEvent
|
|
* _SetWinEventHook
|
|
* UnhookWinEventHook
|
|
*
|
|
* All other additions to USER for Active Accessibility are in winable2.c.
|
|
*
|
|
* Copyright (c) 1985 - 1999, Microsoft Corporation
|
|
*
|
|
* History:
|
|
* Based on snapshot taken from:
|
|
* \\trango\slmro\proj\win\src\CORE\access\user_40\user32 on 8/29/96
|
|
* 08-30-96 IanJa Ported from Windows '95
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
#if DBG
|
|
int gnNotifies;
|
|
|
|
__inline VOID DBGVERIFYEVENTHOOK(
|
|
PEVENTHOOK peh)
|
|
{
|
|
HMValidateCatHandleNoSecure(PtoH(peh), TYPE_WINEVENTHOOK);
|
|
UserAssert(peh->eventMin <= peh->eventMax);
|
|
}
|
|
|
|
__inline VOID DBGVERIFYNOTIFY(
|
|
PNOTIFY pNotify)
|
|
{
|
|
UserAssert(pNotify->spEventHook != NULL);
|
|
UserAssert(pNotify->spEventHook->fSync || (pNotify->dwWEFlags & WEF_ASYNC));
|
|
}
|
|
|
|
#else
|
|
#define DBGVERIFYEVENTHOOK(peh)
|
|
#define DBGVERIFYNOTIFY(pNotify)
|
|
#endif
|
|
|
|
/*
|
|
* Pending Event Notifications (sync and async).
|
|
*/
|
|
static NOTIFY notifyCache;
|
|
static BOOL fNotifyCacheInUse;
|
|
|
|
|
|
/*
|
|
* Local to this module.
|
|
*/
|
|
WINEVENTPROC xxxGetEventProc(
|
|
PEVENTHOOK pEventOrg);
|
|
|
|
PNOTIFY CreateNotify(
|
|
PEVENTHOOK peh,
|
|
DWORD event,
|
|
PWND pwnd,
|
|
LONG idObject,
|
|
LONG idChild,
|
|
PTHREADINFO ptiEvent,
|
|
DWORD dwTime);
|
|
|
|
|
|
/*****************************************************************************\
|
|
* xxxProcessNotifyWinEvent
|
|
*
|
|
* Posts or Sends a WinEvent notification.
|
|
*
|
|
* Post: uses PostEventMesage - does not leave the critical section.
|
|
* Send: makes a callback to user-mode - does leave the critical section.
|
|
*
|
|
* If this is a system thread (RIT, Desktop or Console) then synchronously
|
|
* hooked (WINEVENT_INCONTEXT) events are forced to be asynchronous.
|
|
*
|
|
* We return the next win event hook in the list.
|
|
\*****************************************************************************/
|
|
PEVENTHOOK xxxProcessNotifyWinEvent(
|
|
PNOTIFY pNotify)
|
|
{
|
|
WINEVENTPROC pfn;
|
|
PEVENTHOOK pEventHook;
|
|
TL tlpEventHook;
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
|
|
pEventHook = pNotify->spEventHook;
|
|
DBGVERIFYEVENTHOOK(pEventHook);
|
|
UserAssert(pEventHook->head.cLockObj);
|
|
|
|
if (((pNotify->dwWEFlags & (WEF_ASYNC | WEF_POSTED)) == WEF_ASYNC)
|
|
||
|
|
(ptiCurrent->TIF_flags & (TIF_SYSTEMTHREAD | TIF_CSRSSTHREAD | TIF_INCLEANUP))
|
|
|
|
||
|
|
(!RtlEqualLuid(&GETPTI(pEventHook)->ppi->luidSession, &ptiCurrent->ppi->luidSession) &&
|
|
!(ptiCurrent->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK))
|
|
|
|
||
|
|
(GETPTI(pEventHook)->ppi != ptiCurrent->ppi &&
|
|
IsRestricted(GETPTI(pEventHook)->pEThread))
|
|
|
|
#if defined(_WIN64)
|
|
||
|
|
((GETPTI(pEventHook)->TIF_flags & TIF_WOW64) != (ptiCurrent->TIF_flags & TIF_WOW64))
|
|
#endif
|
|
) {
|
|
/*
|
|
* POST
|
|
*
|
|
* WinEvent Hook set without WINEVENT_INCONTEXT flag are posted;
|
|
* Events from system threads are posted because there is no user-mode
|
|
* part to callback to;
|
|
* Console is not permitted to load DLLs, so we must post back to the
|
|
* hooking application;
|
|
* DLLs can not be loaded cross bit type(32bit to 64bit) on 64bit NT
|
|
* so we must post(It may be usefull to let the app be aware and
|
|
* even supply both a 32bit and a 64bit DLL that are aware of each other);
|
|
* Threads in cleanup can't get called back, so turn their
|
|
* notifications into async ones. (Better late than never).
|
|
*
|
|
* If forcing these events ASYNC is unacceptable, we might consider
|
|
* doing system/console SYNC events like low-level hooks (sync with
|
|
* timeout: but may have to post it if the timeout expires) - IanJa
|
|
*/
|
|
PQ pqReceiver = GETPTI(pEventHook)->pq;
|
|
PEVENTHOOK pEventHookNext = pEventHook->pehNext;
|
|
|
|
BEGINATOMICCHECK();
|
|
|
|
DBGVERIFYNOTIFY(pNotify);
|
|
pNotify->dwWEFlags |= WEF_POSTED | WEF_ASYNC;
|
|
if (!pqReceiver || (GETPTI(pEventHook) == gptiRit) ||
|
|
pEventHook->fDestroyed ||
|
|
!PostEventMessage(GETPTI(pEventHook), pqReceiver,
|
|
QEVENT_NOTIFYWINEVENT,
|
|
NULL, 0, 0, (LPARAM)pNotify)) {
|
|
/*
|
|
* If the receiver doesn't have a queue or the
|
|
* post failed (low memory), cleanup what we just
|
|
* created.
|
|
* Note: destroying the notification may destroy pEventHook too.
|
|
*/
|
|
RIPMSG2(RIP_WARNING,
|
|
"Failed to post NOTIFY at 0x%p, time %lx",
|
|
pNotify,
|
|
pNotify->dwEventTime);
|
|
DestroyNotify(pNotify);
|
|
}
|
|
|
|
ENDATOMICCHECK();
|
|
|
|
if (pEventHookNext) {
|
|
DBGVERIFYEVENTHOOK(pEventHookNext);
|
|
}
|
|
return pEventHookNext;
|
|
}
|
|
|
|
/*
|
|
* Don't call back if the hook has been destroyed (unhooked).
|
|
*/
|
|
if (pEventHook->fDestroyed) {
|
|
/*
|
|
* Save the next hook since DestroyNotify may cause pEventHook to
|
|
* be freed by unlocking it.
|
|
*/
|
|
pEventHook = pEventHook->pehNext;
|
|
DestroyNotify(pNotify);
|
|
return pEventHook;
|
|
}
|
|
|
|
/*
|
|
* CALLBACK
|
|
*
|
|
* This leaves the critical section.
|
|
* We return the next Event Hook in the list so that the caller doesn't
|
|
* have to lock pEventHook.
|
|
*/
|
|
UserAssert((pNotify->dwWEFlags & WEF_DEFERNOTIFY) == 0);
|
|
|
|
ThreadLockAlways(pEventHook, &tlpEventHook);
|
|
|
|
UserAssertMsg1(pNotify->ptiReceiver == NULL,
|
|
"pNotify %#p is already in callback! Reentrant?", pNotify);
|
|
pNotify->ptiReceiver = ptiCurrent;
|
|
|
|
if (!pEventHook->fSync) {
|
|
UserAssert(pEventHook->ihmod == -1);
|
|
pfn = (WINEVENTPROC)pEventHook->offPfn;
|
|
} else {
|
|
pfn = xxxGetEventProc(pEventHook);
|
|
}
|
|
if (pfn) {
|
|
xxxClientCallWinEventProc(pfn, pEventHook, pNotify);
|
|
DBGVERIFYNOTIFY(pNotify);
|
|
DBGVERIFYEVENTHOOK(pEventHook);
|
|
UserAssert(pEventHook->head.cLockObj);
|
|
}
|
|
|
|
pNotify->ptiReceiver = NULL;
|
|
|
|
/*
|
|
* Save the next item in the list, ThreadUnlock() may destroy pEventHook.
|
|
* DestroyNotify() may also kill the event if it is a zombie (destroyed
|
|
* but being used, waiting for use count to go to 0 before being freed).
|
|
*/
|
|
pEventHook = pEventHook->pehNext;
|
|
ThreadUnlock(&tlpEventHook);
|
|
|
|
/*
|
|
* We are done with the notification. Kill it.
|
|
*
|
|
* NOTE that DestroyNotify does not yield, which is why we can hang on
|
|
* to the pehNext field above around this call.
|
|
*
|
|
* NOTE ALSO that DestroyNotify will kill the event it references if the
|
|
* ref count goes down to zero and it was zombied earlier.
|
|
*/
|
|
DestroyNotify(pNotify);
|
|
|
|
return pEventHook;
|
|
}
|
|
|
|
|
|
/****************************************************************************\
|
|
* xxxFlushDeferredWindowEvents
|
|
*
|
|
* Process notifications that were queued up during DeferWinEventNotify.
|
|
\****************************************************************************/
|
|
VOID xxxFlushDeferredWindowEvents(
|
|
VOID)
|
|
{
|
|
PNOTIFY pNotify;
|
|
DWORD idCurrentThread = W32GetCurrentTID();
|
|
|
|
/*
|
|
* If idCurrentThread is 0 we're not in danger of faulting, but we'll
|
|
* needlessly walk the list of pending notifications (since all will be
|
|
* ignored).
|
|
*/
|
|
UserAssert(idCurrentThread != 0);
|
|
|
|
UserAssert(IsWinEventNotifyDeferredOK());
|
|
|
|
pNotify = gpPendingNotifies;
|
|
while (pNotify) {
|
|
if (((pNotify->dwWEFlags & WEF_DEFERNOTIFY) == 0) ||
|
|
(pNotify->idSenderThread != idCurrentThread)) {
|
|
pNotify = pNotify->pNotifyNext;
|
|
} else {
|
|
/*
|
|
* Clear WEF_DEFERNOTIFY so that if we recurse in the callback
|
|
* we won't try to send this notification again.
|
|
*/
|
|
pNotify->dwWEFlags &= ~WEF_DEFERNOTIFY;
|
|
#if DBG
|
|
gnDeferredWinEvents--;
|
|
#endif
|
|
/*
|
|
* We shouldn't have deferred ASYNC notifications: we should have
|
|
* posted them immediately.
|
|
*/
|
|
UserAssert((pNotify->dwWEFlags & WEF_ASYNC) == 0);
|
|
xxxProcessNotifyWinEvent(pNotify);
|
|
/*
|
|
* Start again at the head of the list, in case it munged during
|
|
* the callback.
|
|
*/
|
|
pNotify = gpPendingNotifies;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************\
|
|
*
|
|
* xxxWindowEvent
|
|
*
|
|
* Send, Post or Defer a Win Event notification, depending on what Win Event
|
|
* hooks are installed and what the context of the caller is.
|
|
*
|
|
* The caller should test FWINABLE() and only call xxxWindowEvent if it is TRUE,
|
|
* that way only costs a few clocks if no Win Event hooks are set.
|
|
*
|
|
* Caller shouldn't lock pwnd, because xxxWindowEvent() will do it.
|
|
*
|
|
\*****************************************************************************/
|
|
VOID
|
|
xxxWindowEvent(
|
|
DWORD event,
|
|
PWND pwnd,
|
|
LONG idObject,
|
|
LONG idChild,
|
|
DWORD dwFlags)
|
|
{
|
|
PEVENTHOOK peh;
|
|
PEVENTHOOK pehNext;
|
|
PTHREADINFO ptiCurrent, ptiEvent;
|
|
DWORD dwTime;
|
|
PPROCESSINFO ppiEvent;
|
|
DWORD idEventThread;
|
|
HANDLE hEventProcess;
|
|
PNOTIFY pNotify;
|
|
TL tlpwnd;
|
|
TL tlpti;
|
|
|
|
/*
|
|
* Do not bother with CheckLock(pwnd) - we ThreadLock it below.
|
|
*/
|
|
if (!FEVENTHOOKED(event)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This thread is in startup, and has not yet had it's pti set up
|
|
* This is pretty rare, but sometimes encountered in stress.
|
|
* Test gptiCurrent to avoid the UserAssert(gptiCurrent) in PtiCurrent()
|
|
*/
|
|
if (gptiCurrent == NULL) {
|
|
RIPMSG3(RIP_WARNING, "Ignore WinEvent %lx %#p %lx... no PtiCurrent yet",
|
|
event, pwnd, idObject);
|
|
return;
|
|
}
|
|
ptiCurrent = PtiCurrent();
|
|
|
|
/*
|
|
* Don't bother with destroyed windows
|
|
*/
|
|
if (pwnd && TestWF(pwnd, WFDESTROYED)) {
|
|
RIPMSG3(RIP_WARNING,
|
|
"Ignore WinEvent %lx %#p %lx... pwnd already destroyed",
|
|
event, pwnd, idObject);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Under some special circumstances we have to defer
|
|
*/
|
|
if (ptiCurrent->TIF_flags & (TIF_DISABLEHOOKS | TIF_INCLEANUP)) {
|
|
dwFlags |= WEF_DEFERNOTIFY;
|
|
}
|
|
|
|
/*
|
|
* Determine process and thread issuing the event notification
|
|
*/
|
|
if ((dwFlags & WEF_USEPWNDTHREAD) && pwnd) {
|
|
ptiEvent = GETPTI(pwnd);
|
|
} else {
|
|
ptiEvent = ptiCurrent;
|
|
}
|
|
idEventThread = TIDq(ptiEvent);
|
|
ppiEvent = ptiEvent->ppi;
|
|
hEventProcess = PsGetThreadProcessId(ptiEvent->pEThread);
|
|
|
|
dwTime = NtGetTickCount();
|
|
|
|
ThreadLockWithPti(ptiCurrent, pwnd, &tlpwnd);
|
|
ThreadLockPti(ptiCurrent, ptiEvent, &tlpti);
|
|
|
|
/*
|
|
* If we're not deferring the current notification process any pending
|
|
* deferred notifications before proceeding with the current notification
|
|
*/
|
|
if (!(dwFlags & WEF_DEFERNOTIFY)) {
|
|
xxxFlushDeferredWindowEvents();
|
|
}
|
|
|
|
for (peh = gpWinEventHooks; peh; peh = pehNext) {
|
|
DBGVERIFYEVENTHOOK(peh);
|
|
pehNext = peh->pehNext;
|
|
|
|
//
|
|
// Is event in the right range? And is it for this process/thread?
|
|
// Note that we skip destroyed events. They will be freed any
|
|
// second now, it's just that yielding may have caused reentrancy.
|
|
//
|
|
// If the caller said to ignore events on his own thread, make sure
|
|
// we skip them.
|
|
//
|
|
if (!peh->fDestroyed &&
|
|
(peh->eventMin <= event) &&
|
|
(event <= peh->eventMax) &&
|
|
(!peh->hEventProcess || (peh->hEventProcess == hEventProcess)) &&
|
|
(!peh->fIgnoreOwnProcess || (ppiEvent != GETPTI(peh)->ppi)) &&
|
|
(!peh->idEventThread || (peh->idEventThread == idEventThread)) &&
|
|
(!peh->fIgnoreOwnThread || (ptiEvent != GETPTI(peh))) &&
|
|
// temp fix from SP3 - best to architect events on a per-desktop
|
|
// basis, with a separate pWinEventHook list per desktop. (IanJa)
|
|
(peh->head.pti->rpdesk == ptiCurrent->rpdesk))
|
|
{
|
|
/*
|
|
* Don't create new notifications for zombie event hooks.
|
|
* When an event is destroyed, it stays as a zombie until the in-use
|
|
* count goes to zero (all it's async and deferred notifies gone)
|
|
*/
|
|
if (HMIsMarkDestroy(peh)) {
|
|
break;
|
|
}
|
|
|
|
UserAssert(peh->fDestroyed == 0);
|
|
|
|
if ((pNotify = CreateNotify(peh, event, pwnd, idObject,
|
|
idChild, ptiEvent, dwTime)) == NULL) {
|
|
break;
|
|
}
|
|
pNotify->dwWEFlags |= dwFlags;
|
|
|
|
/*
|
|
* If it's async, don't defer it: post it straight away.
|
|
*/
|
|
if (pNotify->dwWEFlags & WEF_ASYNC) {
|
|
pNotify->dwWEFlags &= ~WEF_DEFERNOTIFY;
|
|
}
|
|
|
|
if (pNotify->dwWEFlags & WEF_DEFERNOTIFY) {
|
|
#if DBG
|
|
gnDeferredWinEvents++;
|
|
#endif
|
|
DBGVERIFYNOTIFY(pNotify);
|
|
} else {
|
|
pehNext = xxxProcessNotifyWinEvent(pNotify);
|
|
}
|
|
}
|
|
}
|
|
|
|
ThreadUnlockPti(ptiCurrent, &tlpti);
|
|
ThreadUnlock(&tlpwnd);
|
|
}
|
|
|
|
/****************************************************************************\
|
|
*
|
|
* CreateNotify()
|
|
*
|
|
* Gets a pointer to a NOTIFY struct that we can then propagate to our
|
|
* event window via Send/PostMessage. We have to do this since we want to
|
|
* (pass on a lot more data then can be packed in the parameters.
|
|
*
|
|
* We have one cached struct so we avoid lots of allocs and frees in the
|
|
* most common case of just one outstanding notification.
|
|
\****************************************************************************/
|
|
PNOTIFY
|
|
CreateNotify(PEVENTHOOK pEvent, DWORD event, PWND pwnd, LONG idObject,
|
|
LONG idChild, PTHREADINFO ptiSender, DWORD dwTime)
|
|
{
|
|
PNOTIFY pNotify;
|
|
UserAssert(pEvent != NULL);
|
|
|
|
//
|
|
// Get a pointer. From cache if available.
|
|
// IanJa - change this to allocate from zone a la AllocQEntry??
|
|
//
|
|
if (!fNotifyCacheInUse) {
|
|
fNotifyCacheInUse = TRUE;
|
|
pNotify = ¬ifyCache;
|
|
#if DBG
|
|
//
|
|
// Make sure we aren't forgetting to set any fields.
|
|
//
|
|
// DebugFillBuffer(pNotify, sizeof(NOTIFY));
|
|
#endif
|
|
} else {
|
|
pNotify = (PNOTIFY)UserAllocPool(sizeof(NOTIFY), TAG_NOTIFY);
|
|
if (!pNotify)
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Fill in the notify block.
|
|
*/
|
|
pNotify->spEventHook = NULL;
|
|
Lock(&pNotify->spEventHook, pEvent);
|
|
pNotify->hwnd = HW(pwnd);
|
|
pNotify->event = event;
|
|
pNotify->idObject = idObject;
|
|
pNotify->idChild = idChild;
|
|
pNotify->idSenderThread = TIDq(ptiSender);
|
|
UserAssert(pNotify->idSenderThread != 0);
|
|
pNotify->dwEventTime = dwTime;
|
|
pNotify->dwWEFlags = pEvent->fSync ? 0 : WEF_ASYNC;
|
|
pNotify->pNotifyNext = NULL;
|
|
pNotify->ptiReceiver = NULL;
|
|
#if DBG
|
|
gnNotifies++;
|
|
#endif
|
|
|
|
/*
|
|
* The order of non-deferred notifications doesn't matter; they are here
|
|
* simply for cleanup/in-use tracking. However, deferred notifications must
|
|
* be ordered with most recent at the end, so just order them all that way.
|
|
*/
|
|
if (gpPendingNotifies) {
|
|
UserAssert(gpLastPendingNotify);
|
|
UserAssert(gpLastPendingNotify->pNotifyNext == NULL);
|
|
gpLastPendingNotify->pNotifyNext = pNotify;
|
|
} else {
|
|
gpPendingNotifies = pNotify;
|
|
}
|
|
gpLastPendingNotify = pNotify;
|
|
|
|
return pNotify;
|
|
}
|
|
|
|
|
|
/****************************************************************************\
|
|
* RemoveNotify
|
|
\****************************************************************************/
|
|
VOID RemoveNotify(
|
|
PNOTIFY *ppNotify)
|
|
{
|
|
PNOTIFY pNotifyRemove;
|
|
|
|
pNotifyRemove = *ppNotify;
|
|
|
|
/*
|
|
* First, get it out of the pending list.
|
|
*/
|
|
*ppNotify = pNotifyRemove->pNotifyNext;
|
|
|
|
#if DBG
|
|
if (pNotifyRemove->dwWEFlags & WEF_DEFERNOTIFY) {
|
|
UserAssert(gnDeferredWinEvents > 0);
|
|
gnDeferredWinEvents--;
|
|
}
|
|
#endif
|
|
|
|
if (*ppNotify == NULL) {
|
|
/*
|
|
* Removing last notify, so fix up gpLastPendingNotify:
|
|
* If list now empty, there is no last item.
|
|
*/
|
|
if (gpPendingNotifies == NULL) {
|
|
gpLastPendingNotify = NULL;
|
|
} else {
|
|
gpLastPendingNotify = CONTAINING_RECORD(ppNotify, NOTIFY, pNotifyNext);
|
|
}
|
|
}
|
|
UserAssert(gpPendingNotifies == 0 || gpPendingNotifies > (PNOTIFY)100);
|
|
|
|
DBGVERIFYEVENTHOOK(pNotifyRemove->spEventHook);
|
|
|
|
/*
|
|
* This may cause the win event hook to be freed.
|
|
*/
|
|
Unlock(&pNotifyRemove->spEventHook);
|
|
|
|
/*
|
|
* Now free it. Either put it back in the cache (if it is the cache)
|
|
* or really free it.
|
|
*/
|
|
if (pNotifyRemove == ¬ifyCache) {
|
|
UserAssert(fNotifyCacheInUse);
|
|
fNotifyCacheInUse = FALSE;
|
|
} else {
|
|
UserFreePool(pNotifyRemove);
|
|
}
|
|
|
|
#if DBG
|
|
UserAssert(gnNotifies > 0);
|
|
gnNotifies--;
|
|
#endif
|
|
}
|
|
|
|
|
|
/*****************************************************************************\
|
|
* DestroyNotify
|
|
*
|
|
* This gets the notification out of our pending list and frees the local
|
|
* memory it uses.
|
|
*
|
|
* This function is called
|
|
* (1) NORMALLY: After returning from calling the notify proc
|
|
* (2) CLEANUP: When a thread goes away, we cleanup async notifies it
|
|
* hasn't received, and sync notifies it was in the middle of trying
|
|
* to call (i.e. the event proc faulted).
|
|
\*****************************************************************************/
|
|
VOID DestroyNotify(
|
|
PNOTIFY pNotifyDestroy)
|
|
{
|
|
PNOTIFY *ppNotify;
|
|
PNOTIFY pNotifyT;
|
|
|
|
DBGVERIFYNOTIFY(pNotifyDestroy);
|
|
|
|
/*
|
|
* Either this notify isn't currently in the process of calling back
|
|
* (which means ptiReceiver is NULL) or the thread destroying it
|
|
* must be the one that was calling back (which means this thread
|
|
* was destroyed during the callback and is cleaning up).
|
|
*/
|
|
UserAssert(pNotifyDestroy->ptiReceiver == NULL ||
|
|
pNotifyDestroy->ptiReceiver == PtiCurrent());
|
|
|
|
ppNotify = &gpPendingNotifies;
|
|
while (pNotifyT = *ppNotify) {
|
|
if (pNotifyT == pNotifyDestroy) {
|
|
RemoveNotify(ppNotify);
|
|
return;
|
|
} else {
|
|
ppNotify = &pNotifyT->pNotifyNext;
|
|
}
|
|
}
|
|
|
|
RIPMSG1(RIP_ERROR, "DestroyNotify 0x%p - not found", pNotifyDestroy);
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************\
|
|
* FreeThreadsWinEvents
|
|
*
|
|
* During 'exit-list' processing this function is called to free any WinEvent
|
|
* notifications and WinEvent hooks created by the current thread.
|
|
*
|
|
* Notifications that remain may be:
|
|
* o Posted notifications (async)
|
|
* o Notifications in xxxClientCallWinEventProc (sync)
|
|
* o Deferred notifications (should be sync only)
|
|
* Destroy the sync notifications, because we cannot do callbacks
|
|
* while in thread cleanup.
|
|
* Leave the posted (async) notifications alone: they're on their way already.
|
|
*
|
|
* History:
|
|
* 11-11-96 IanJa Created.
|
|
\***************************************************************************/
|
|
VOID FreeThreadsWinEvents(
|
|
PTHREADINFO pti)
|
|
{
|
|
PEVENTHOOK peh, pehNext;
|
|
PNOTIFY pn, pnNext;
|
|
DWORD idCurrentThread = W32GetCurrentTID();
|
|
|
|
/*
|
|
* Loop through all the notifications.
|
|
*/
|
|
for (pn = gpPendingNotifies; pn; pn = pnNext) {
|
|
pnNext = pn->pNotifyNext;
|
|
|
|
/*
|
|
* Only destroy sync notifications that belong to this thread
|
|
* and are not currently calling back i.e. ptiReceiver must be NULL.
|
|
* Otherwise, when we come back from the callback in
|
|
* xxxProcessNotifyWinEvent we will operate on a freed notify.
|
|
* Also destroy the notification if the receiver is going away
|
|
* or else it gets leaked as long as the sender is alive.
|
|
*/
|
|
if ((pn->idSenderThread == idCurrentThread &&
|
|
pn->ptiReceiver == NULL) ||
|
|
pn->ptiReceiver == pti) {
|
|
if ((pn->dwWEFlags & WEF_ASYNC) == 0) {
|
|
UserAssert((pn->dwWEFlags & WEF_POSTED) == 0);
|
|
DestroyNotify(pn);
|
|
}
|
|
}
|
|
}
|
|
|
|
peh = gpWinEventHooks;
|
|
while (peh) {
|
|
pehNext = peh->pehNext;
|
|
if (GETPTI(peh) == pti) {
|
|
DestroyEventHook(peh);
|
|
}
|
|
peh = pehNext;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* _SetWinEventHook()
|
|
*
|
|
* This installs a win event hook.
|
|
*
|
|
* If hEventProcess set but idEventThread = 0, hook all threads in process.
|
|
* If idEventThread set but hEventProcess = NULL, hook single thread only.
|
|
* If neither are set, hook everything.
|
|
* If both are set ??
|
|
*
|
|
\***************************************************************************/
|
|
PEVENTHOOK _SetWinEventHook(
|
|
DWORD eventMin,
|
|
DWORD eventMax,
|
|
HMODULE hmodWinEventProc,
|
|
PUNICODE_STRING pstrLib,
|
|
WINEVENTPROC pfnWinEventProc,
|
|
HANDLE hEventProcess,
|
|
DWORD idEventThread,
|
|
DWORD dwFlags)
|
|
{
|
|
PEVENTHOOK pEventNew;
|
|
PTHREADINFO ptiCurrent;
|
|
|
|
int ihmod;
|
|
|
|
ptiCurrent = PtiCurrent();
|
|
|
|
//
|
|
// If exiting, fail the call.
|
|
//
|
|
if (ptiCurrent->TIF_flags & TIF_INCLEANUP) {
|
|
RIPMSG1(RIP_ERROR,
|
|
"SetWinEventHook: Fail call - thread 0x%p in cleanup",
|
|
ptiCurrent);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Check to see if filter proc is valid.
|
|
*/
|
|
if (pfnWinEventProc == NULL) {
|
|
RIPERR0(ERROR_INVALID_FILTER_PROC,
|
|
RIP_WARNING,
|
|
"pfnWinEventProc == NULL");
|
|
return NULL;
|
|
}
|
|
|
|
if (eventMin > eventMax) {
|
|
RIPERR0(ERROR_INVALID_HOOK_FILTER,
|
|
RIP_WARNING,
|
|
"eventMin > eventMax");
|
|
return NULL;
|
|
}
|
|
|
|
if (dwFlags & WINEVENT_INCONTEXT) {
|
|
/*
|
|
* WinEventProc to be called in context of hooked thread, so needs a DLL.
|
|
*/
|
|
if (hmodWinEventProc == NULL) {
|
|
RIPERR0(ERROR_HOOK_NEEDS_HMOD,
|
|
RIP_WARNING,
|
|
"In context hook w/o DLL!");
|
|
return NULL;
|
|
} else if (pstrLib == NULL) {
|
|
/*
|
|
* If we got an hmod, we should get a DLL name too!
|
|
*/
|
|
RIPERR1(ERROR_DLL_NOT_FOUND,
|
|
RIP_WARNING,
|
|
"hmod 0x%p, but no lib name",
|
|
hmodWinEventProc);
|
|
return NULL;
|
|
}
|
|
ihmod = GetHmodTableIndex(pstrLib);
|
|
if (ihmod == -1) {
|
|
RIPERR0(ERROR_MOD_NOT_FOUND,
|
|
RIP_WARNING,
|
|
"");
|
|
return NULL;
|
|
}
|
|
} else {
|
|
ihmod = -1; // means no DLL is required
|
|
hmodWinEventProc = 0;
|
|
}
|
|
|
|
/*
|
|
* Check the thread id, check it is a GUI thread.
|
|
*/
|
|
if (idEventThread != 0) {
|
|
PTHREADINFO ptiT;
|
|
|
|
ptiT = PtiFromThreadId(idEventThread);
|
|
if (ptiT == NULL || !(ptiT->TIF_flags & TIF_GUITHREADINITIALIZED)) {
|
|
RIPERR1(ERROR_INVALID_THREAD_ID, RIP_VERBOSE, "pti %#p", ptiT);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create the window for async events first.
|
|
*
|
|
* NOTE that USER itself will not pass on window creation/destruction
|
|
* notifications for
|
|
* * IME windows
|
|
* * OLE windows
|
|
* * RPC windows
|
|
* * Event windows
|
|
*/
|
|
|
|
/*
|
|
* Get a new event.
|
|
*/
|
|
pEventNew = (PEVENTHOOK)HMAllocObject(ptiCurrent,
|
|
NULL,
|
|
TYPE_WINEVENTHOOK,
|
|
sizeof(EVENTHOOK));
|
|
if (!pEventNew) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Fill in the new event.
|
|
*/
|
|
pEventNew->eventMin = (UINT)eventMin;
|
|
pEventNew->eventMax = (UINT)eventMax;
|
|
|
|
pEventNew->fIgnoreOwnThread = ((dwFlags & WINEVENT_SKIPOWNTHREAD) != 0);
|
|
pEventNew->fIgnoreOwnProcess = ((dwFlags & WINEVENT_SKIPOWNPROCESS) != 0);
|
|
pEventNew->fDestroyed = FALSE;
|
|
pEventNew->fSync = ((dwFlags & WINEVENT_INCONTEXT) != 0);
|
|
|
|
pEventNew->hEventProcess = hEventProcess;
|
|
pEventNew->idEventThread = idEventThread;
|
|
|
|
pEventNew->ihmod = ihmod;
|
|
|
|
/*
|
|
* Add a dependency on this module - meaning, increment a count
|
|
* that simply counts the number of hooks set into this module.
|
|
*/
|
|
if (pEventNew->ihmod >= 0) {
|
|
AddHmodDependency(pEventNew->ihmod);
|
|
}
|
|
|
|
/*
|
|
* If pfnWinEventProc is in caller's process and no DLL is involved,
|
|
* then pEventNew->offPfn is the actual address.
|
|
*/
|
|
pEventNew->offPfn = ((ULONG_PTR)pfnWinEventProc) - ((ULONG_PTR)hmodWinEventProc);
|
|
|
|
/*
|
|
* Link our event into the master list.
|
|
*
|
|
* Note that we count on USER to not generate any events when installing
|
|
* our hook. The caller can't handle it yet since he hasn't got back
|
|
* his event handle from this call.
|
|
*/
|
|
pEventNew->pehNext = gpWinEventHooks;
|
|
gpWinEventHooks = pEventNew;
|
|
|
|
/*
|
|
* Update the flags that indicate what event hooks are installed. These
|
|
* flags are accessable from user mode without a kernel transition since
|
|
* they are in shared memory.
|
|
*/
|
|
SET_FLAG(gpsi->dwInstalledEventHooks, CategoryMaskFromEventRange(eventMin, eventMax));
|
|
|
|
return pEventNew;
|
|
}
|
|
|
|
/****************************************************************************\
|
|
* UnhookWinEvent
|
|
*
|
|
* Unhooks a win event hook. We of course sanity check that this thread is
|
|
* the one which installed the hook. We have to: We are going to destroy
|
|
* the IPC window and that must be in context.
|
|
\****************************************************************************/
|
|
BOOL _UnhookWinEvent(
|
|
PEVENTHOOK pEventUnhook)
|
|
{
|
|
DBGVERIFYEVENTHOOK(pEventUnhook);
|
|
|
|
if (HMIsMarkDestroy(pEventUnhook) || (GETPTI(pEventUnhook) != PtiCurrent())) {
|
|
/*
|
|
* We do this to avoid someone calling UnhookWinEvent() the first
|
|
* time, then somehow getting control again and calling it a second
|
|
* time before we've managed to free up the event since someone was
|
|
* in the middle of using it at the first UWE call.
|
|
*/
|
|
RIPERR1(ERROR_INVALID_HANDLE,
|
|
RIP_WARNING,
|
|
"_UnhookWinEvent: Invalid event hook 0x%p",
|
|
PtoHq(pEventUnhook));
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Purge this baby if all notifications are done.
|
|
*
|
|
* If there are SYNC ones pending, the caller will clean this up upon
|
|
* the return from calling the event.
|
|
*
|
|
* If there are ASYNC ones pending, the receiver will not call the event
|
|
* and clean it up when he gets it.
|
|
*/
|
|
DestroyEventHook(pEventUnhook);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*****************************************************************************\
|
|
* DestroyEventHook
|
|
*
|
|
* Destroys an event when the ref count has gone down to zero. It may
|
|
* happen:
|
|
* * In the event generator's context, after returning from a callback
|
|
* and the ref count dropped to zero, if sync.
|
|
* * In the event installer's context, after returning from a callback
|
|
* and the ref count dropped to zero if async.
|
|
* * In the event installer's context, if on _UnhookWinEvent() the event
|
|
* was not in use at all.
|
|
\*****************************************************************************/
|
|
VOID DestroyEventHook(
|
|
PEVENTHOOK pEventDestroy)
|
|
{
|
|
PEVENTHOOK *ppEvent;
|
|
PEVENTHOOK pEventT;
|
|
DWORD dwCategoryMask = 0;
|
|
|
|
DBGVERIFYEVENTHOOK(pEventDestroy);
|
|
UserAssert(gpWinEventHooks);
|
|
|
|
/*
|
|
* Mark this event as destroyed, but don't remove it from the event list
|
|
* until its lock count goes to 0 - we may be traversing the list within
|
|
* xxxWindowEvent, so we mustn't break the link to the next hook.
|
|
*/
|
|
pEventDestroy->fDestroyed = TRUE;
|
|
|
|
/*
|
|
* If the object is locked, mark it for destroy but don't free it yet.
|
|
*/
|
|
if (!HMMarkObjectDestroy(pEventDestroy)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Remove this from our event list.
|
|
*/
|
|
for (ppEvent = &gpWinEventHooks; pEventT = *ppEvent; ppEvent = &pEventT->pehNext) {
|
|
if (pEventT == pEventDestroy) {
|
|
*ppEvent = pEventDestroy->pehNext;
|
|
break;
|
|
}
|
|
}
|
|
UserAssert(pEventT);
|
|
|
|
/*
|
|
* Update the flags that indicate what event hooks are installed. These
|
|
* flags are accessable from user mode without a kernel transition since
|
|
* they are in shared memory. Note that a user could check the shared
|
|
* memory at any time, so they may get a false-positive during this
|
|
* processing. A false-positive would mean that we claim there is a
|
|
* listener, when there really isn't. We never want the user to get
|
|
* a false negative - meaning that we claim there aren't any listeners
|
|
* but there really is. That could mean bad things for accessability.
|
|
*/
|
|
for (pEventT = gpWinEventHooks; pEventT != NULL; pEventT = pEventT->pehNext) {
|
|
SET_FLAG(dwCategoryMask, CategoryMaskFromEventRange(pEventT->eventMin, pEventT->eventMax));
|
|
}
|
|
gpsi->dwInstalledEventHooks = dwCategoryMask;
|
|
|
|
/*
|
|
* Make sure each hooked thread will unload the hook proc DLL.
|
|
*/
|
|
if (pEventDestroy->ihmod >= 0) {
|
|
RemoveHmodDependency(pEventDestroy->ihmod);
|
|
}
|
|
|
|
/*
|
|
* Free this pointer.
|
|
*/
|
|
HMFreeObject(pEventDestroy);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxGetEventProc
|
|
*
|
|
* For sync events, this gets the address to call. If 16-bits, then just
|
|
* return the installed address. If 32-bits, we need to load the library
|
|
* if not in the same process as the installer.
|
|
\***************************************************************************/
|
|
WINEVENTPROC xxxGetEventProc(
|
|
PEVENTHOOK pEventOrg)
|
|
{
|
|
PTHREADINFO ptiCurrent;
|
|
|
|
UserAssert(pEventOrg);
|
|
UserAssert(pEventOrg->fSync);
|
|
UserAssert(pEventOrg->ihmod >= 0);
|
|
UserAssert(pEventOrg->offPfn != 0);
|
|
|
|
CheckLock(pEventOrg);
|
|
|
|
/*
|
|
* Make sure the hook is still around before we try and call it.
|
|
*/
|
|
if (HMIsMarkDestroy(pEventOrg)) {
|
|
return NULL;
|
|
}
|
|
|
|
ptiCurrent = PtiCurrent();
|
|
|
|
/*
|
|
* Make sure the DLL for this hook, if any, has been loaded for the
|
|
* current process.
|
|
*/
|
|
if (pEventOrg->ihmod != -1 &&
|
|
TESTHMODLOADED(ptiCurrent, pEventOrg->ihmod) == 0) {
|
|
/*
|
|
* Try loading the library, since it isn't loaded in this processes
|
|
* context. The hook is alrerady locked, so it won't go away while
|
|
* we're loading this library.
|
|
*/
|
|
if (xxxLoadHmodIndex(pEventOrg->ihmod) == NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* While we're still inside the critical section make sure the hook
|
|
* hasn't been 'freed'. If so just return NULL. Since WinEvent has
|
|
* already been called, you might think that we should pass the event
|
|
* on, but the hooker may not be expecting this after having
|
|
* cancelled the hook! In any case, we have two ways of detecting that
|
|
* this hook has been removed (see the following code).
|
|
*/
|
|
|
|
/*
|
|
* Make sure the hook is still around before we try and call it.
|
|
*/
|
|
if (HMIsMarkDestroy(pEventOrg)) {
|
|
return NULL;
|
|
}
|
|
|
|
return (WINEVENTPROC)PFNHOOK(pEventOrg);
|
|
}
|