Leaked source code of windows server 2003
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.
 
 
 
 
 
 

7083 lines
237 KiB

/****************************** Module Header ******************************\
* Module Name: input.c
*
* Copyright (c) 1985 - 1999, Microsoft Corporation
*
* This module contains the core functions of the input sub-system
*
* History:
* 10-18-90 DavidPe Created.
* 02-14-91 mikeke Added Revalidation code
\***************************************************************************/
#include "precomp.h"
#pragma hdrstop
//#define MARKPATH
#ifdef MARKPATH
BOOL gfMarkPath;
#endif
#define IsOnInputDesktop(pti) (pti->rpdesk == grpdeskRitInput)
#if DBG
int gnSysPeekSearch;
VOID CheckPtiSysPeek(
int where, PQ pq,
ULONG_PTR newIdSysPeek)
{
PTHREADINFO ptiCurrent = PtiCurrent();
DWORD dwRip;
dwRip = (newIdSysPeek > 1) ? RIP_THERESMORE : 0;
TAGMSG5(DBGTAG_SysPeek | dwRip,
"%d pti %#p sets id %#p to pq %#p ; old id %#p",
where, ptiCurrent, newIdSysPeek, pq, pq->idSysPeek);
if (newIdSysPeek > 1) {
PQMSG pqmsg = (PQMSG)newIdSysPeek;
TAGMSG5(DBGTAG_SysPeek | RIP_NONAME,
"-> msg %lx hwnd %#p w %#p l %#p pti %#p",
pqmsg->msg.message, pqmsg->msg.hwnd, pqmsg->msg.wParam,
pqmsg->msg.lParam, pqmsg->pti);
}
}
VOID CheckSysLock(
int where,
PQ pq,
PTHREADINFO ptiSysLock)
{
PTHREADINFO ptiCurrent = PtiCurrent();
TAGMSG5(DBGTAG_SysPeek,
"%d pti 0x%p sets ptiSL 0x%p to pq 0x%p ; old ptiSL 0x%p",
where,
ptiCurrent,
ptiSysLock,
pq,
pq->ptiSysLock);
}
#endif
#if DBG
BOOL gfLogPlayback;
LPCSTR aszMouse[] = {
"WM_MOUSEMOVE",
"WM_LBUTTONDOWN",
"WM_LBUTTONUP",
"WM_LBUTTONDBLCLK",
"WM_RBUTTONDOWN",
"WM_RBUTTONUP",
"WM_RBUTTONDBLCLK",
"WM_MBUTTONDOWN",
"WM_MBUTTONUP",
"WM_MBUTTONDBLCLK"
"WM_MOUSEWHEEL",
"WM_XBUTTONDOWN",
"WM_XBUTTONUP",
"WM_XBUTTONDBLCLK",
};
LPCSTR aszKey[] = {
"WM_KEYDOWN",
"WM_KEYUP",
"WM_CHAR",
"WM_DEADCHAR",
"WM_SYSKEYDOWN",
"WM_SYSKEYUP",
"WM_SYSCHAR",
"WM_SYSDEADCHAR",
"WM_CONVERTREQUESTEX",
"WM_YOMICHAR",
"WM_UNICHAR"
};
#endif // DBG
#define CANCEL_ACTIVESTATE 0
#define CANCEL_FOCUSSTATE 1
#define CANCEL_CAPTURESTATE 2
#define KEYSTATESIZE (CBKEYSTATE + CBKEYSTATERECENTDOWN)
/*
* xxxGetNextSysMsg return values
*/
#define PQMSG_PLAYBACK ((PQMSG)1)
BOOL xxxScanSysQueue(PTHREADINFO ptiCurrent, LPMSG lpMsg, PWND pwndFilter,
UINT msgMinFilter, UINT msgMaxFilter, DWORD flags, DWORD fsReason);
BOOL xxxReadPostMessage(PTHREADINFO pti, LPMSG lpMsg, PWND pwndFilter,
UINT msgMin, UINT msgMax, BOOL fRemoveMsg);
void CleanEventMessage(PQMSG pqmsg);
#ifdef MESSAGE_PUMP_HOOK
/***************************************************************************\
* xxxWaitMessageEx (API)
*
* This API will block until an input message is received on
* the current queue.
*
* History:
* 10-25-1990 DavidPe Created.
* 06-12-2000 JStall Changed to "Ex"
\***************************************************************************/
BOOL xxxWaitMessageEx(
UINT fsWakeMask,
DWORD Timeout)
{
PCLIENTTHREADINFO pcti = gptiCurrent->pcti;
if (IsInsideMPH()) {
/*
* This thread is has MPH's installed, so we need to callback into User
* mode to allow the application to provide an implementation
*/
return ClientWaitMessageExMPH(fsWakeMask, Timeout);
} else {
/*
* This thread does not have any MPH's installed, so we can just
* directly process.
*/
return xxxRealWaitMessageEx(fsWakeMask, Timeout);
}
}
BOOL xxxRealWaitMessageEx(
UINT fsWakeMask,
DWORD Timeout)
{
return xxxSleepThread(fsWakeMask, Timeout, TRUE);
}
#else // MESSAGE_PUMP_HOOK
/***************************************************************************\
* xxxWaitMessage (API)
*
* This API will block until an input message is received on
* the current queue.
*
* History:
* 10-25-90 DavidPe Created.
\***************************************************************************/
BOOL xxxWaitMessage(
VOID)
{
return xxxSleepThread(QS_ALLINPUT | QS_EVENT, 0, TRUE);
}
#endif // MESSAGE_PUMP_HOOK
/***************************************************************************\
* CheckProcessBackground/Foreground
*
* This checks to see if the process is at the right priority. If CSPINS is
* greater than CSPINBACKGROUND and the process isn't at the background
* priority, put it there. If it is less than this and should be foreground
* and isn't put it there.
*
* Need to put foreground spinning apps in the background (make it the same
* priority as all other background apps) so that bad apps can still communicate
* with apps in the background via dde, for example. There are other cases
* where spinning foreground apps affect the server, the printer spooler, and
* mstest scenarios. On Win3.1, calling PeekMessage() involves making a trip
* through the scheduler, forcing other apps to run. Processes run with
* priority on NT, where the foreground process gets foreground priority for
* greater responsiveness.
*
* If an app calls peek/getmessage without idling, count how many times this
* happens - if it happens CSPINBACKGROUND or more times, make the process
* background. This handles most of the win3.1 app compatibility spinning
* cases. If there is no priority contention, the app continues to run at
* full speed (no performance scenarios should be adversely affected by this).
*
* This solves these cases:
*
* - high speed timer not allowing app to go idle
* - post/peek loop (receiving a WM_ENTERIDLE, and posting a msg, for example)
* - peek no remove loop (winword "idle" state, most dde loops, as examples)
*
* But doesn't protect against these sort of cases:
*
* - app calls getmessage, then goes into a tight loop
* - non-gui threads in tight cpu loops
*
* 02-08-93 ScottLu Created.
\***************************************************************************/
NTSTATUS CheckProcessForeground(
PTHREADINFO pti)
{
PTHREADINFO ptiT;
/*
* Check to see if we need to move this process into foreground
* priority.
*/
try {
pti->pClientInfo->cSpins = 0;
pti->pClientInfo->dwTIFlags = pti->TIF_flags & ~TIF_SPINNING;
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
return GetExceptionCode();
}
pti->TIF_flags &= ~TIF_SPINNING;
if (pti->ppi->W32PF_Flags & W32PF_FORCEBACKGROUNDPRIORITY) {
/*
* See if any thread of this process is spinning. If none
* are, we can remove the force to background.
*/
for (ptiT = pti->ppi->ptiList; ptiT != NULL; ptiT = ptiT->ptiSibling) {
if (ptiT->TIF_flags & TIF_SPINNING)
return STATUS_SUCCESS;
}
pti->ppi->W32PF_Flags &= ~W32PF_FORCEBACKGROUNDPRIORITY;
if (pti->ppi == gppiWantForegroundPriority) {
SetForegroundPriority(pti, TRUE);
}
}
return STATUS_SUCCESS;
}
/***************************************************************************\
* xxxInternalGetMessage
*
* This routine is the worker for both xxxGetMessage() and xxxPeekMessage()
* and is modelled after its win3.1 counterpart. From Win3.1:
*
* Get msg from the app queue or sys queue if there is one that matches
* hwndFilter and matches msgMin/msgMax. If no messages in either queue, check
* the QS_PAINT and QS_TIMER bits, call DoPaint or DoTimer to Post the
* appropriate message to the application queue, and then Read that message.
* Otherwise, if in GetMessage, Sleep until a wake bit is set indicating there
* is something we need to do. If in PeekMessage, return to caller. Before
* reading messages from the queues, check to see if the QS_SENDMESSAGE bit
* is set, and if so, call ReceiveMessage().
*
* New for NT5: HIWORD(flags) contains a wake mask provided by the caller.
* This mask is passed to CalcWakeMask to be combined with the mask generated
* from msgMin and MsgMax. The default mask includes QS_SENDMESSAGE
* now; we won't call xxxReceiveMessages (directly) unless this bit is set;
* however, to avoid potentail deadlocks and maintain NT4 compatibility as
* much as possible, we fail the call if QS_SENDMESSAGE is set in fsWakeBits
* but not requested by the caller. The same applies to QS_EVENT which we would
* always process in NT4.
*
*
* 10-19-92 ScottLu Created.
\***************************************************************************/
#ifdef MARKPATH
#define PATHTAKEN(x) pathTaken |= x
#define DUMPPATHTAKEN() if (gfMarkPath) DbgPrint("xxxInternalGetMessage path:%08x\n", pathTaken)
#else
#define PATHTAKEN(x)
#define DUMPPATHTAKEN()
#endif
BOOL xxxInternalGetMessage(
LPMSG lpMsg,
HWND hwndFilter,
UINT msgMin,
UINT msgMax,
UINT flags,
BOOL fGetMessage)
{
#ifdef MESSAGE_PUMP_HOOK
PCLIENTTHREADINFO pcti = gptiCurrent->pcti;
if (IsInsideMPH()) {
/*
* This thread has MPH's installed, so we need to callback into User
* mode to allow the application to provide an implementation
*/
return ClientGetMessageMPH(lpMsg, hwndFilter, msgMin, msgMax, flags, fGetMessage);
} else {
/*
* This thread does not have any MPH's installed, so we can just
* directly process.
*/
return xxxRealInternalGetMessage(lpMsg, hwndFilter, msgMin, msgMax, flags, fGetMessage);
}
}
BOOL xxxRealInternalGetMessage(
LPMSG lpMsg,
HWND hwndFilter,
UINT msgMin,
UINT msgMax,
UINT flags,
BOOL fGetMessage)
{
#endif MESSAGE_PUMP_HOOK
UINT fsWakeBits;
UINT fsWakeMask;
UINT fsRemoveBits;
PTHREADINFO ptiCurrent;
PW32PROCESS W32Process;
PWND pwndFilter;
BOOL fLockPwndFilter;
TL tlpwndFilter;
BOOL fRemove;
BOOL fExit;
PQ pq;
#ifdef MARKPATH
DWORD pathTaken = 0;
#endif
BOOL bBackground;
CheckCritIn();
UserAssert(IsWinEventNotifyDeferredOK());
ptiCurrent = PtiCurrent();
/*
* PeekMessage accepts NULL, 0x0000FFFF, and -1 as valid HWNDs.
* If hwndFilter is invalid we can't just return FALSE because that will
* hose existing badly behaved apps who might attempt to dispatch
* the random contents of pmsg.
*/
if ((hwndFilter == (HWND)-1) || (hwndFilter == (HWND)0x0000FFFF)) {
hwndFilter = (HWND)1;
}
if ((hwndFilter != NULL) && (hwndFilter != (HWND)1)) {
if ((pwndFilter = ValidateHwnd(hwndFilter)) == NULL) {
lpMsg->hwnd = NULL;
lpMsg->message = WM_NULL;
PATHTAKEN(1);
DUMPPATHTAKEN();
if (fGetMessage)
return -1;
else
return 0;
}
ThreadLockAlwaysWithPti(ptiCurrent, pwndFilter, &tlpwndFilter);
fLockPwndFilter = TRUE;
} else {
pwndFilter = (PWND)hwndFilter;
fLockPwndFilter = FALSE;
}
/*
* Add one to our spin count. At this end of this routine we'll check
* to see if the spin count gets >= CSPINBACKGROUND. If so we'll put this
* process into the background.
*/
try {
ptiCurrent->pClientInfo->cSpins++;
} except (W32ExceptionHandler(TRUE, RIP_WARNING)) {
return -1;
}
/*
* Check to see if the startglass is on, and if so turn it off and update.
*/
W32Process = W32GetCurrentProcess();
if (W32Process->W32PF_Flags & W32PF_STARTGLASS) {
/*
* This app is no longer in "starting" mode. Recalc when to hide
* the app starting cursor.
*/
W32Process->W32PF_Flags &= ~W32PF_STARTGLASS;
/*
* Don't need DeferWinEventNotify() - xxxDoSysExpunge below doesn't
*/
zzzCalcStartCursorHide(NULL, 0);
}
/*
* Next check to see if any .dlls need freeing in
* the context of this client (used for windows hooks).
*/
if (ptiCurrent->ppi->cSysExpunge != gcSysExpunge) {
ptiCurrent->ppi->cSysExpunge = gcSysExpunge;
if (ptiCurrent->ppi->dwhmodLibLoadedMask & gdwSysExpungeMask)
xxxDoSysExpunge(ptiCurrent);
}
/*
* Set up BOOL fRemove local variable from for ReadMessage()
*/
fRemove = flags & PM_REMOVE;
/*
* Unlock the system queue if it's owned by us.
*/
/*
* If we're currently processing a message, unlock the input queue
* because the sender, who is blocked, might be the owner, and in order
* to reply, the receiver may need to read keyboard / mouse input.
*/
/*
* If this thread has the input queue locked and the last message removed
* is the last message we looked at, then unlock - we're ready for anyone
* to get the next message.
*/
pq = ptiCurrent->pq;
if ( (ptiCurrent->psmsCurrent != NULL)
|| (pq->ptiSysLock == ptiCurrent && pq->idSysLock == ptiCurrent->idLast)
) {
CheckSysLock(1, pq, NULL);
pq->ptiSysLock = NULL;
PATHTAKEN(2);
} else if (pq->ptiSysLock
&& (pq->ptiSysLock->cVisWindows == 0)
&& (PhkFirstGlobalValid(ptiCurrent, WH_JOURNALPLAYBACK) != NULL)) {
/*
* If the thread that has the system queue lock has no windows visible
* (can happen if it just hid its last window), don't expect it to call
* GetMessage() again! - unlock the system queue. --- ScottLu
* This condition creates a hole by which a second thread attached to
* the same queue as thread 1 can alter pq->idSysPeek during a callback
* made by thread 1 so that thread 1 will delete the wrong message
* (losing keystrokes - causing Shift to appear be stuck down editing a
* Graph5 caption embedded in Word32 document #5032. However, MSTEST
* requires this hole, so allow it if Journal Playback is occurring
* #8850 (yes, a hack) Chicago also has this behavior. --- IanJa
*/
CheckSysLock(2, pq, NULL);
pq->ptiSysLock = NULL;
PATHTAKEN(3);
}
if (pq->ptiSysLock != ptiCurrent) {
ptiCurrent->pcti->CTIF_flags &= ~CTIF_SYSQUEUELOCKED;
}
/*
* If msgMax == 0 then msgMax = -1: that makes our range checking only
* have to deal with msgMin < msgMax.
*/
if (msgMax == 0)
msgMax--;
/*
* Compute the QS* mask that corresponds to the message range
* and the wake mask filter (HIWORD(flags))
*/
fsWakeMask = CalcWakeMask(msgMin, msgMax, HIWORD(flags));
ptiCurrent->fsChangeBitsRemoved = 0;
/*
* If we can yield and one or more events were skipped,
* set the wakebits for event
*/
if (!(flags & PM_NOYIELD) && ptiCurrent->TIF_flags & TIF_DELAYEDEVENT) {
try {
ptiCurrent->pClientInfo->dwTIFlags = ptiCurrent->TIF_flags & ~TIF_DELAYEDEVENT;
} except (W32ExceptionHandler(TRUE, RIP_WARNING)) {
return -1;
}
ptiCurrent->pcti->fsWakeBits |= QS_EVENT;
ptiCurrent->pcti->fsChangeBits |= QS_EVENT;
ptiCurrent->TIF_flags &= ~TIF_DELAYEDEVENT;
}
while (TRUE) {
/*
* Restore any wake bits saved while journalling
*/
ptiCurrent->pcti->fsWakeBits |= ptiCurrent->pcti->fsWakeBitsJournal;
/*
* If we need to recalc queue attachments, do it here. Do it on the
* right desktop or else the queues will get created in the wrong
* heap.
*/
if (ptiCurrent->rpdesk == gpdeskRecalcQueueAttach) {
gpdeskRecalcQueueAttach = NULL;
if (ptiCurrent->rpdesk != NULL && !FJOURNALRECORD() && !FJOURNALPLAYBACK()) {
/*
* No need to DeferWinEventNotify(): a call to
* xxxReceiveMessages is made just below
*/
zzzReattachThreads(FALSE);
PATHTAKEN(4);
}
}
/*
* Remember what change bits we're clearing. This is important to
* fix a bug in the input model: If an app receives a sent message
* from within SleepThread(), then does PostMessage() (which sets
* QS_POSTMESSAGE), then does a PeekMessage(...) for some different
* posted message (clears QS_POSTMESSAGE in fsChangeBits), then returns
* back into SleepThread(), it won't wake up to retrieve that newly
* posted message because the change bits are cleared.
*
* What we do is remember the change bits that are being cleared.
* Then, when we return to SleepThread(), we put these remembered
* bits back into the change bits that also have corresponding
* bits in the wakebits (so we don't set changebits that represent
* input that isn't there anymore). This way, the app will retrieve
* the newly posted message refered to earlier.
* - scottlu
*
* New for NT5: Since QS_SENDMESSAGE was never set it fsWakeMask before (NT4),
* it was never cleared from fsChangeBits. For compatibility, we won't clear
* it now even if specified in fsWakeMask; hence we won't affect any one
* checking for QS_SENDMESSAGE in pcti->fsChangeBits.
*/
fsRemoveBits = fsWakeMask & ~QS_SENDMESSAGE;
ptiCurrent->fsChangeBitsRemoved |= ptiCurrent->pcti->fsChangeBits & fsRemoveBits;
/*
* Clear the change bits that we're looking at, in order to detect
* incoming events that may occur the last time we checked the wake
* bits.
*/
ptiCurrent->pcti->fsChangeBits &= ~fsRemoveBits;
/*
* Check for sent messages. Check the the actual wake bits (i.e, from pcti)
* so we know for real.
*/
if (ptiCurrent->pcti->fsWakeBits & fsWakeMask & QS_SENDMESSAGE) {
xxxReceiveMessages(ptiCurrent);
} else if (ptiCurrent->pcti->fsWakeBits & QS_SENDMESSAGE) {
RIPMSG2(RIP_WARNING, "xxxInternalGetMessage:(1st test) sendmsgs pending. Bits:%#lx Mask:%#lx",
ptiCurrent->pcti->fsWakeBits, fsWakeMask);
goto NoMessages;
}
/*
* Check to see if we have any input we want.
*/
if ((ptiCurrent->pcti->fsWakeBits & fsWakeMask) == 0) {
PATHTAKEN(8);
goto NoMessages;
}
fsWakeBits = ptiCurrent->pcti->fsWakeBits;
/*
* If the queue lock is != NULL (ptiSysLock) and it is this thread that
* locked it, then go get the message from the system queue. This is
* to prevent messages posted after a PeekMessage/no-remove from being
* seen before the original message from the system queue. (Aldus
* Pagemaker requires this) (bobgu 8/5/87).
*/
if (ptiCurrent->pq->ptiSysLock == ptiCurrent &&
(ptiCurrent->pq->QF_flags & QF_LOCKNOREMOVE)) {
/*
* Does the caller want mouse / keyboard?
*/
if (fsWakeBits & fsWakeMask & (QS_INPUT | QS_EVENT)) {
/*
* It should never get here during exit.
*/
UserAssert(gbExitInProgress == FALSE);
if (xxxScanSysQueue(ptiCurrent, lpMsg, pwndFilter,
msgMin, msgMax, flags,
fsWakeBits & fsWakeMask & (QS_INPUT | QS_EVENT))) {
PATHTAKEN(0x10);
break;
}
} else if (fsWakeBits & QS_EVENT) {
RIPMSG2(RIP_WARNING,
"xxxInternalGetMessage:(1st test)events pending. Bits:%#lx Mask:%#lx",
fsWakeBits, fsWakeMask);
goto NoMessages;
}
}
/*
* See if there's a message in the application queue.
*/
if (fsWakeBits & fsWakeMask & QS_POSTMESSAGE) {
if (xxxReadPostMessage(ptiCurrent, lpMsg, pwndFilter,
msgMin, msgMax, fRemove)) {
PATHTAKEN(0x20);
break;
}
}
/*
* If pwndFilter == 1, this app was only interested in messages
* that were posted via PostThreadMessage. Since we checked the
* posted message queue above, let's skip doing needless work.
*/
if (pwndFilter == (PWND)1) {
goto NoMessages;
}
/*
* Time to scan the raw input queue for input. First check to see
* if the caller wants mouse / keyboard input.
*/
if (fsWakeBits & fsWakeMask & (QS_INPUT | QS_EVENT)) {
/*
* It should never get here during exit.
*/
UserAssert(gbExitInProgress == FALSE);
if (xxxScanSysQueue(ptiCurrent, lpMsg, pwndFilter,
msgMin, msgMax, flags,
fsWakeBits & fsWakeMask & (QS_INPUT | QS_EVENT))) {
PATHTAKEN(0x40);
break;
}
} else if (fsWakeBits & QS_EVENT) {
RIPMSG2(RIP_WARNING, "xxxInternalGetMessage:(2nd test)events pending. Bits:%#lx Mask:%#lx",
fsWakeBits, fsWakeMask);
goto NoMessages;
}
/*
* Check for sent messages. Check the the actual wake bits (i.e, from pcti)
* so we know for real.
*/
if (ptiCurrent->pcti->fsWakeBits & fsWakeMask & QS_SENDMESSAGE) {
xxxReceiveMessages(ptiCurrent);
} else if (ptiCurrent->pcti->fsWakeBits & QS_SENDMESSAGE) {
RIPMSG2(RIP_WARNING, "xxxInternalGetMessage:(2nd test)sendmsgs pending. Bits:%#lx Mask:%#lx",
ptiCurrent->pcti->fsWakeBits, fsWakeMask);
goto NoMessages;
}
/*
* Get new input bits.
*/
if ((ptiCurrent->pcti->fsWakeBits & fsWakeMask) == 0) {
PATHTAKEN(0x80);
goto NoMessages;
}
fsWakeBits = ptiCurrent->pcti->fsWakeBits;
/*
* Does the caller want paint messages? If so, try to find a paint.
*/
if (fsWakeBits & fsWakeMask & QS_PAINT) {
if (xxxDoPaint(pwndFilter, lpMsg)) {
PATHTAKEN(0x100);
break;
}
}
/*
* We must yield for 16 bit apps before checking timers or an app
* that has a fast timer could chew up all the time and never let
* anyone else run.
*
* NOTE: This could cause PeekMessage() to yield TWICE, if the user
* is filtering with a window handle. If the DoTimer() call fails
* then we end up yielding again.
*/
if (!(flags & PM_NOYIELD)) {
/*
* This is the point where windows would yield. Here we wait to wake
* up any threads waiting for this thread to hit "idle state".
*/
zzzWakeInputIdle(ptiCurrent);
/*
* Yield and receive pending messages.
*/
xxxUserYield(ptiCurrent);
/*
* Check new input buts and receive pending messages.
*/
if (ptiCurrent->pcti->fsWakeBits & fsWakeMask & QS_SENDMESSAGE) {
xxxReceiveMessages(ptiCurrent);
} else if (ptiCurrent->pcti->fsWakeBits & QS_SENDMESSAGE) {
RIPMSG2(RIP_WARNING, "xxxInternalGetMessage:(3rd test) sendmsgs pending. Bits:%#lx Mask:%#lx",
ptiCurrent->pcti->fsWakeBits, fsWakeMask);
goto NoMessages;
}
if ((ptiCurrent->pcti->fsWakeBits & fsWakeMask) == 0) {
PATHTAKEN(0x200);
goto NoMessages;
}
fsWakeBits = ptiCurrent->pcti->fsWakeBits;
}
/*
* Does the app want timer messages, and if there one pending?
*/
if (fsWakeBits & fsWakeMask & QS_TIMER) {
if (DoTimer(pwndFilter)) {
/*
* DoTimer() posted the message into the app's queue,
* so start over and we'll grab it from there.
*/
PATHTAKEN(0x400);
continue;
}
}
NoMessages:
/*
* Looks like we have no input. If we're being called from GetMessage()
* then go to sleep until we find something.
*/
if (!fGetMessage) {
/*
* This is one last check for pending sent messages. It also
* yields. Win3.1 does this.
*/
if (!(flags & PM_NOYIELD)) {
/*
* This is the point where windows yields. Here we wait to wake
* up any threads waiting for this thread to hit "idle state".
*/
zzzWakeInputIdle(ptiCurrent);
/*
* Yield and receive pending messages.
*/
xxxUserYield(ptiCurrent);
}
PATHTAKEN(0x800);
goto FalseExit;
}
/*
* This is a getmessage not a peekmessage, so sleep. When we sleep,
* zzzWakeInputIdle() is called to wake up any apps waiting on this
* app to go idle.
*/
if (!xxxSleepThread(fsWakeMask, 0, TRUE))
goto FalseExit;
} /* while (TRUE) */
/*
* If we're here then we have input for this queue. Call the
* GetMessage() hook with this input.
*/
if (IsHooked(ptiCurrent, WHF_GETMESSAGE))
xxxCallHook(HC_ACTION, flags, (LPARAM)lpMsg, WH_GETMESSAGE);
/*
* If called from PeekMessage(), return TRUE.
*/
if (!fGetMessage) {
PATHTAKEN(0x1000);
goto TrueExit;
}
/*
* Being called from GetMessage(): return FALSE if the message is WM_QUIT,
* TRUE otherwise.
*/
if (lpMsg->message == WM_QUIT) {
PATHTAKEN(0x2000);
goto FalseExit;
}
/*
* Fall through to TrueExit...
*/
TrueExit:
/*
* Update timeLastRead. We use this for hung app calculations.
*/
SET_TIME_LAST_READ(ptiCurrent);
fExit = TRUE;
#ifdef GENERIC_INPUT
if (fRemove) {
/*
* This version simply frees the previous HIDDATA.
*/
if (ptiCurrent->hPrevHidData) {
PHIDDATA pPrevHidData = HMValidateHandleNoRip(ptiCurrent->hPrevHidData, TYPE_HIDDATA);
TAGMSG1(DBGTAG_PNP, "xxxInternalGetMessage: WM_INPUT prev=%p", ptiCurrent->hPrevHidData);
if (pPrevHidData) {
FreeHidData(pPrevHidData);
} else {
RIPMSG1(RIP_WARNING, "xxxInternalGetMessage: WM_INPUT bogus hPrev=%p",
ptiCurrent->hPrevHidData);
}
ptiCurrent->hPrevHidData = NULL;
}
if (lpMsg->message == WM_INPUT) {
if (lpMsg->wParam == RIM_INPUT
#ifdef GI_SINK
|| lpMsg->wParam == RIM_INPUTSINK
#endif
) {
ptiCurrent->hPrevHidData = (HANDLE)lpMsg->lParam;
#if DBG
{
PHIDDATA pHidData = HMValidateHandle((HANDLE)lpMsg->lParam, TYPE_HIDDATA);
TAGMSG1(DBGTAG_PNP, "xxxInternalGetMessage: WM_INPUT new=%p", PtoH(pHidData));
if (pHidData == NULL) {
RIPMSG2(RIP_WARNING, "xxxInternalGetMessage: WM_INPUT bogus parameter wp=%x, lp=%x",
lpMsg->wParam, lpMsg->lParam);
}
}
#endif
} else {
RIPMSG1(RIP_WARNING, "xxxInternalGetMessage: WM_INPUT bogus wParam %x",
lpMsg->wParam);
}
}
}
#endif
PATHTAKEN(0x4000);
goto Exit;
FalseExit:
fExit = FALSE;
Exit:
if (fLockPwndFilter)
ThreadUnlock(&tlpwndFilter);
/*
* see CheckProcessBackground() comment above
* Check to see if we need to move this process into background
* priority.
*/
try {
bBackground = ((ptiCurrent->pClientInfo->cSpins >= CSPINBACKGROUND) != 0);
if (bBackground) {
ptiCurrent->pClientInfo->cSpins = 0;
if (!(ptiCurrent->TIF_flags & TIF_SPINNING)) {
ptiCurrent->pClientInfo->dwTIFlags = ptiCurrent->TIF_flags | TIF_SPINNING;
}
}
} except (W32ExceptionHandler(TRUE, RIP_WARNING)) {
fExit = FALSE;
goto Error;
}
if (bBackground) {
if (!(ptiCurrent->TIF_flags & TIF_SPINNING)) {
ptiCurrent->TIF_flags |= TIF_SPINNING;
if (!(ptiCurrent->ppi->W32PF_Flags & W32PF_FORCEBACKGROUNDPRIORITY)) {
ptiCurrent->ppi->W32PF_Flags |= W32PF_FORCEBACKGROUNDPRIORITY;
if (ptiCurrent->ppi == gppiWantForegroundPriority) {
SetForegroundPriority(ptiCurrent, FALSE);
}
}
}
/*
* For spinning Message loops, we need to take the 16bit-thread out
* of the scheduler temporarily so that other processes can get a chance
* to run. This is appearent in OLE operations where a 16bit foreground
* thread starts an OLE activation on a 32bit process. The 32bit process
* gets starved of CPU while the 16bit thread spins.
*/
if (ptiCurrent->TIF_flags & TIF_16BIT) {
/*
* Take the 16bit thread out of the scheduler. This wakes any
* other 16bit thread needing time, and takes the current thread
* out. We will do a brief sleep so that apps can respond in time.
* When done, we will reschedule the thread. The zzzWakeInputIdle()
* should have been called in the no-messages section, so we have
* already set the Idle-Event.
*/
xxxSleepTask(FALSE, HEVENT_REMOVEME);
LeaveCrit();
ZwYieldExecution();
EnterCrit();
xxxDirectedYield(DY_OLDYIELD);
}
}
Error:
PATHTAKEN(0x8000);
DUMPPATHTAKEN();
return fExit;
}
#undef PATHTAKEN
#undef DUMPPATHTAKEN
__inline PTIMER FindSystemTimer(
PMSG pmsg)
{
PTIMER ptmr;
const BOOL fWow64 =
#ifdef _WIN64
PtiCurrent()->TIF_flags & TIF_WOW64;
#else
FALSE;
#endif
for (ptmr = gptmrFirst; ptmr; ptmr = ptmr->ptmrNext) {
if (ptmr->flags & TMRF_SYSTEM) {
if (pmsg->lParam == (LPARAM)ptmr->pfn) {
return ptmr;
}
/*
* 64bit only: lParam might be truncated, if the application is 32bit.
* We do our best to pick up the right guy, by comparing the
* lower 32bit in the pointer and the timer id.
*/
if (fWow64 && (ULONG)pmsg->lParam == PtrToUlong(ptmr->pfn) && pmsg->wParam == ptmr->nID) {
return ptmr;
}
}
}
return NULL;
}
/***************************************************************************\
* ValidateTimerCallback
*
* Checks if the timer callback (with lParam != 0) is legitimate,
* in order to avoid mulicious applications to break the other applications.
*
* History:
* 08-10-2002 Hiro Created.
\***************************************************************************/
BOOL ValidateTimerCallback(
PTHREADINFO pti,
LPARAM pfnCallback)
{
PTIMER pTimer;
UserAssert(pti);
/*
* AppCompat: if the flag is set, skip the checking and allow
* lParam based callbacks.
*/
if (GetAppCompatFlags2ForPti(pti, VER51) & GACF2_NOTIMERCBPROTECTION) {
/*
* But we always protect CSRSS and WinLogon.
*/
if ((pti->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD)) == 0 &&
PsGetProcessId(pti->ppi->Process) != gpidLogon) {
return TRUE;
}
}
for (pTimer = gptmrFirst; pTimer; pTimer = pTimer->ptmrNext) {
/*
* Is this the timer we're looking for?
*/
if (pTimer->pti->ppi == pti->ppi &&
(pTimer->flags & (TMRF_SYSTEM | TMRF_RIT)) == 0 &&
pTimer->pfn == (TIMERPROC_PWND)pfnCallback) {
/*
* Found the timer, tell the caller so.
*/
return TRUE;
}
}
/*
* No, we didn't find a matching timer.
*/
return FALSE;
}
/***************************************************************************\
* xxxDispatchMessage (API)
*
* Calls the appropriate window procedure or function with pmsg.
*
* History:
* 10-25-90 DavidPe Created.
\***************************************************************************/
LRESULT xxxDispatchMessage(
LPMSG pmsg)
{
LRESULT lRet;
PWND pwnd;
WNDPROC_PWND lpfnWndProc;
TL tlpwnd;
pwnd = NULL;
if (pmsg->hwnd != NULL) {
if ((pwnd = ValidateHwnd(pmsg->hwnd)) == NULL)
return 0;
}
/*
* If this is a synchronous-only message (takes a pointer in wParam or
* lParam), then don't allow this message to go through since those
* parameters have not been thunked, and are pointing into outer-space
* (which would case exceptions to occur).
*
* (This api is only called in the context of a message loop, and you
* don't get synchronous-only messages in a message loop).
*/
if (TESTSYNCONLYMESSAGE(pmsg->message, pmsg->wParam)) {
/*
* Fail if 32 bit app is calling.
*/
if (!(PtiCurrent()->TIF_flags & TIF_16BIT)) {
RIPERR1(ERROR_MESSAGE_SYNC_ONLY, RIP_WARNING, "xxxDispatchMessage: Sync only message 0x%lX",
pmsg->message);
return 0;
}
/*
* For wow apps, allow it to go through (for compatibility). Change
* the message id so our code doesn't understand the message - wow
* will get the message and strip out this bit before dispatching
* the message to the application.
*/
pmsg->message |= MSGFLAG_WOW_RESERVED;
}
ThreadLock(pwnd, &tlpwnd);
/*
* Is this a timer? If there's a proc address, call it,
* otherwise send it to the wndproc.
*/
if ((pmsg->message == WM_TIMER) || (pmsg->message == WM_SYSTIMER)) {
if (pmsg->lParam != 0) {
/*
* System timers must be executed on the server's context.
*/
if (pmsg->message == WM_SYSTIMER) {
/*
* Verify that it's a valid timer proc. If so,
* don't leave the critsect to call server-side procs
* and pass a PWND, not HWND.
*/
PTIMER ptmr;
lRet = 0;
ptmr = FindSystemTimer(pmsg);
if (ptmr) {
ptmr->pfn(pwnd, WM_SYSTIMER, (UINT)pmsg->wParam,
NtGetTickCount());
}
goto Exit;
} else {
/*
* WM_TIMER is the same for Unicode/ANSI.
*/
PTHREADINFO ptiCurrent = PtiCurrent();
if (ptiCurrent->TIF_flags & TIF_SYSTEMTHREAD) {
lRet = 0;
goto Exit;
}
/*
* Check the legitimacy of this WM_TIMER callback,
* and bail out if it's not valid.
*/
if (!ValidateTimerCallback(ptiCurrent, pmsg->lParam)) {
RIPMSGF2(RIP_WARNING, "Bogus WM_TIMER callback: nID=%p, pfn=%p", pmsg->wParam, pmsg->lParam);
lRet = 0;
goto Exit;
}
lRet = CallClientProcA(pwnd, WM_TIMER,
pmsg->wParam, NtGetTickCount(), pmsg->lParam);
goto Exit;
}
}
}
/*
* Check to see if pwnd is NULL AFTER the timer check. Apps can set
* timers with NULL hwnd's, that's totally legal. But NULL hwnd messages
* don't get dispatched, so check here after the timer case but before
* dispatching - if it's NULL, just return 0.
*/
if (pwnd == NULL) {
lRet = 0;
goto Exit;
}
/*
* If we're dispatching a WM_PAINT message, set a flag to be used to
* determine whether it was processed properly.
*/
if (pmsg->message == WM_PAINT)
SetWF(pwnd, WFPAINTNOTPROCESSED);
/*
* If this window's proc is meant to be executed from the server side
* we'll just stay inside the semaphore and call it directly. Note
* how we don't convert the pwnd into an hwnd before calling the proc.
*/
if (TestWF(pwnd, WFSERVERSIDEPROC)) {
ULONG_PTR fnMessageType;
fnMessageType = pmsg->message >= WM_USER ? (ULONG_PTR)SfnDWORD :
(ULONG_PTR)gapfnScSendMessage[MessageTable[pmsg->message].iFunction];
/*
* Convert the WM_CHAR from ANSI to UNICODE if the source was ANSI
*/
if (fnMessageType == (ULONG_PTR)SfnINWPARAMCHAR && TestWF(pwnd, WFANSIPROC)) {
UserAssert(PtiCurrent() == GETPTI(pwnd)); // use receiver's codepage
RtlMBMessageWParamCharToWCS(pmsg->message, &pmsg->wParam);
}
lRet = pwnd->lpfnWndProc(pwnd, pmsg->message, pmsg->wParam,
pmsg->lParam);
goto Exit;
}
/*
* Cool people dereference any window structure members before they
* leave the critsect.
*/
lpfnWndProc = pwnd->lpfnWndProc;
{
/*
* If we're dispatching the message to an ANSI wndproc we need to
* convert the character messages from Unicode to Ansi.
*/
if (TestWF(pwnd, WFANSIPROC)) {
UserAssert(PtiCurrent() == GETPTI(pwnd)); // use receiver's codepage
RtlWCSMessageWParamCharToMB(pmsg->message, &pmsg->wParam);
lRet = CallClientProcA(pwnd, pmsg->message,
pmsg->wParam, pmsg->lParam, (ULONG_PTR)lpfnWndProc);
} else {
lRet = CallClientProcW(pwnd, pmsg->message,
pmsg->wParam, pmsg->lParam, (ULONG_PTR)lpfnWndProc);
}
}
/*
* If we dispatched a WM_PAINT message and it wasn't properly
* processed, do the drawing here.
*/
if (pmsg->message == WM_PAINT && RevalidateHwnd(pmsg->hwnd) &&
TestWF(pwnd, WFPAINTNOTPROCESSED)) {
//RIPMSG0(RIP_WARNING,
// "Missing BeginPaint or GetUpdateRect/Rgn(fErase == TRUE) in WM_PAINT");
ClrWF(pwnd, WFWMPAINTSENT);
xxxSimpleDoSyncPaint(pwnd);
}
Exit:
ThreadUnlock(&tlpwnd);
return lRet;
}
/***************************************************************************\
* AdjustForCoalescing
*
* If message is in the coalesce message range, and it's message and hwnd
* equals the last message in the queue, then coalesce these two messages
* by simple deleting the last one.
*
* 11-12-92 ScottLu Created.
\***************************************************************************/
void AdjustForCoalescing(
PMLIST pml,
HWND hwnd,
UINT message)
{
/*
* First see if this message is in that range.
*/
if (!CheckMsgFilter(message, WM_COALESCE_FIRST, WM_COALESCE_LAST) &&
(message != WM_TIMECHANGE))
return;
if (pml->pqmsgWriteLast == NULL)
return;
if (pml->pqmsgWriteLast->msg.message != message)
return;
if (pml->pqmsgWriteLast->msg.hwnd != hwnd)
return;
/*
* The message and hwnd are the same, so delete this message and
* the new one will added later.
*/
DelQEntry(pml, pml->pqmsgWriteLast);
}
/***************************************************************************\
* _PostMessage (API)
*
* Writes a message to the message queue for pwnd. If pwnd == -1, the message
* is broadcasted to all windows.
*
* History:
* 11-06-90 DavidPe Created.
\***************************************************************************/
BOOL _PostMessage(
PWND pwnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
PQMSG pqmsg;
BOOL fPwndUnlock;
BOOL fRet;
DWORD dwPostCode;
TL tlpwnd;
PTHREADINFO pti;
/*
* First check to see if this message takes DWORDs only. If it does not,
* fail the post. Cannot allow an app to post a message with pointers or
* handles in it - this can cause the server to fault and cause other
* problems - such as causing apps in separate address spaces to fault.
* (or even an app in the same address space to fault!)
*
* Block certain messages cross LUIDs to avoid security threats.
*/
if (TESTSYNCONLYMESSAGE(message, wParam) ||
BLOCKMESSAGECROSSLUID(message,
PpiCurrent(),
GETPTI(pwnd)->ppi)) {
RIPERR1(ERROR_MESSAGE_SYNC_ONLY,
RIP_WARNING,
"Invalid parameter \"message\" (%ld) to _PostMessage",
message);
return FALSE;
}
/*
* Is this a BroadcastMsg()?
*/
if (pwnd == PWND_BROADCAST) {
xxxBroadcastMessage(NULL, message, wParam, lParam, BMSG_POSTMSG, NULL);
return TRUE;
}
pti = PtiCurrent();
/*
* Is this posting to the current thread info?
*/
if (pwnd == NULL) {
return _PostThreadMessage(pti, message, wParam, lParam);
}
fPwndUnlock = FALSE;
if (message >= WM_DDE_FIRST && message <= WM_DDE_LAST) {
ThreadLockAlwaysWithPti(pti, pwnd, &tlpwnd);
dwPostCode = xxxDDETrackPostHook(&message, pwnd, wParam, &lParam, FALSE);
if (dwPostCode != DO_POST) {
ThreadUnlock(&tlpwnd);
return (BOOL)dwPostCode;
}
fPwndUnlock = TRUE;
}
pti = GETPTI(pwnd);
/*
* Check to see if this message is in the multimedia coalescing range.
* If so, see if it can be coalesced with the previous message.
*/
AdjustForCoalescing(&pti->mlPost, HWq(pwnd), message);
#ifdef GENERIC_INPUT
#if LOCK_HIDDATA
/*
* If someone is posting this message, we need to bump up the reference
* count of the HID data so it doesn't get freed too early.
*/
if (message == WM_INPUT) {
// lParam is an HRAWINPUT
PHIDDATA pHidData = HMValidateHandle((HANDLE)lParam, TYPE_HIDDATA);
TAGMSG1(DBGTAG_PNP, "_PostMessage: Got WM_INPUT pHidData=%p", pHidData);
if (pHidData != NULL) {
HMLockObject(pHidData);
} else {
RIPMSG1(RIP_WARNING, "_PostMessage: invalid handle %p for WM_INPUT", lParam);
return FALSE;
}
} else
#endif
#endif // GENERIC_INPUT
/*
* Allocate a key state update event if needed.
*/
if (message >= WM_KEYFIRST && message <= WM_KEYLAST) {
PostUpdateKeyStateEvent(pti->pq);
}
/*
* Put this message on the 'post' list.
*/
fRet = FALSE;
if ((pqmsg = AllocQEntry(&pti->mlPost)) != NULL) {
/*
* Set the QS_POSTMESSAGE bit so the thread knows it has a message.
*/
StoreQMessage(pqmsg, pwnd, message, wParam, lParam, 0, 0, 0);
SetWakeBit(pti, QS_POSTMESSAGE | QS_ALLPOSTMESSAGE);
/*
* If it's a hotkey, set the QS_HOTKEY bit since we have a separate
* bit for those messages.
*/
if (message == WM_HOTKEY)
SetWakeBit(pti, QS_HOTKEY);
fRet = TRUE;
}
/*
* Are we posting to the thread currently reading from the input queue?
* If so, update idSysLock with this pqmsg so that the input queue will
* not be unlocked until this message is read.
*/
if (pti == pti->pq->ptiSysLock)
pti->pq->idSysLock = (ULONG_PTR)pqmsg;
if (fPwndUnlock)
ThreadUnlock(&tlpwnd);
return fRet;
}
/***************************************************************************\
* _PostQuitMessage (API)
*
* Writes a message to the message queue for pwnd. If pwnd == -1, the message
* is broadcasted to all windows.
*
* History:
* 11-06-90 DavidPe Created.
* 05-16-91 mikeke Changed to return BOOL
\***************************************************************************/
BOOL IPostQuitMessage(PTHREADINFO pti, int nExitCode)
{
pti->TIF_flags |= TIF_QUITPOSTED;
pti->exitCode = nExitCode;
SetWakeBit(pti, QS_POSTMESSAGE | QS_ALLPOSTMESSAGE);
return TRUE;
}
BOOL _PostQuitMessage(int nExitCode)
{
return IPostQuitMessage(PtiCurrent(), nExitCode);
}
/***************************************************************************\
* _PostThreadMessage (API)
*
* Given a thread ID, the function will post the specified message to this
* thread with pmsg->hwnd == NULL..
*
* History:
* 11-21-90 DavidPe Created.
\***************************************************************************/
BOOL _PostThreadMessage(
PTHREADINFO pti,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
PQMSG pqmsg;
if ((pti == NULL) ||
!(pti->TIF_flags & TIF_GUITHREADINITIALIZED) ||
(pti->TIF_flags & TIF_INCLEANUP)) {
RIPERR0(ERROR_INVALID_THREAD_ID, RIP_VERBOSE, "");
return FALSE;
}
/*
* First check to see if this message takes DWORDs only. If it does not,
* fail the post. Cannot allow an app to post a message with pointers or
* handles in it - this can cause the server to fault and cause other
* problems - such as causing apps in separate address spaces to fault.
* (or even an app in the same address space to fault!)
*
* Block certain messages cross LUIDs to avoid security threats.
*/
if (TESTSYNCONLYMESSAGE(message, wParam) ||
BLOCKMESSAGECROSSLUID(message,
PpiCurrent(),
pti->ppi)) {
RIPERR1(ERROR_MESSAGE_SYNC_ONLY,
RIP_WARNING,
"Invalid parameter \"message\" (%ld) to _PostThreadMessage",
message);
return FALSE;
}
/*
* Check to see if this message is in the multimedia coalescing range.
* If so, see if it can be coalesced with the previous message.
*/
AdjustForCoalescing(&pti->mlPost, NULL, message);
/*
* Put this message on the 'post' list.
*/
if ((pqmsg = AllocQEntry(&pti->mlPost)) == NULL) {
RIPMSG1(RIP_WARNING, "_PostThreadMessage: Failed to alloc Q entry: Target pti=0x%p",
pti);
return FALSE;
}
/*
* Set the QS_POSTMESSAGE bit so the thread knows it has a message.
*/
StoreQMessage(pqmsg, NULL, message, wParam, lParam, 0, 0, 0);
SetWakeBit(pti, QS_POSTMESSAGE | QS_ALLPOSTMESSAGE);
/*
* If it's a hotkey, set the QS_HOTKEY bit since we have a separate
* bit for those messages.
*/
if (message == WM_HOTKEY)
SetWakeBit(pti, QS_HOTKEY);
/*
* Are we posting to the thread currently reading from the input queue?
* If so, update idSysLock with this pqmsg so that the input queue will
* not be unlocked until this message is read.
*/
if (pti == pti->pq->ptiSysLock)
pti->pq->idSysLock = (ULONG_PTR)pqmsg;
return TRUE;
}
/***************************************************************************\
* _GetMessagePos (API)
*
* This API returns the cursor position when the last message was read from
* the current message queue.
*
* History:
* 11-19-90 DavidPe Created.
\***************************************************************************/
DWORD _GetMessagePos(VOID)
{
PTHREADINFO pti;
pti = PtiCurrent();
return MAKELONG((SHORT)pti->ptLast.x, (SHORT)pti->ptLast.y);
}
#ifdef SYSMODALWINDOWS
/***************************************************************************\
* _SetSysModalWindow (API)
*
* History:
* 01-25-91 DavidPe Created stub.
\***************************************************************************/
PWND APIENTRY _SetSysModalWindow(
PWND pwnd)
{
pwnd;
return NULL;
}
/***************************************************************************\
* _GetSysModalWindow (API)
*
* History:
* 01-25-91 DavidPe Created stub.
\***************************************************************************/
PWND APIENTRY _GetSysModalWindow(VOID)
{
return NULL;
}
#endif //LATER
/***************************************************************************\
* PostMove
*
* This routine gets called when it is detected that the QF_MOUSEMOVED bit
* is set in a particular queue.
*
* 11-03-92 ScottLu Created.
\***************************************************************************/
VOID PostMove(
PQ pq)
{
#ifdef REDIRECTION
POINT pt;
#endif // REDIRECTION
CheckCritIn();
/*
* set gdwMouseMoveTimeStamp to 0 after posting the move so
* subsequent calls to SetFMouseMove doesn't use the same value
* of gdwMouseMoveTimeStamp. Bug 74508.
*/
if (gdwMouseMoveTimeStamp == 0) {
gdwMouseMoveTimeStamp = NtGetTickCount();
}
#ifdef GENERIC_INPUT
if (TestRawInputMode(pq->ptiMouse, NoLegacyMouse)) {
goto nopost;
}
#endif
#ifdef REDIRECTION
PopMouseMove(pq, &pt);
PostInputMessage(pq, NULL, WM_MOUSEMOVE, 0,
MAKELONG((SHORT)pt.x, (SHORT)pt.y),
gdwMouseMoveTimeStamp, gdwMouseMoveExtraInfo);
#else
PostInputMessage(pq, NULL, WM_MOUSEMOVE, 0,
MAKELONG((SHORT)gpsi->ptCursor.x, (SHORT)gpsi->ptCursor.y),
gdwMouseMoveTimeStamp, gdwMouseMoveExtraInfo);
#endif // REDIRECTION
#ifdef GENERIC_INPUT
nopost:
#endif
gdwMouseMoveTimeStamp = 0;
pq->QF_flags &= ~QF_MOUSEMOVED;
}
#ifdef REDIRECTION
typedef struct tagQMOUSEMOVE {
PQ pq;
POINT pt;
} QMOUSEMOVE;
#define MAX_QMOUSEMOVE 16
QMOUSEMOVE gqMouseMove[MAX_QMOUSEMOVE];
int gnLastMouseMove;
VOID PushMouseMove(
PQ pq,
POINT pt)
{
int ind;
CheckCritIn();
UserAssert(gnLastMouseMove < MAX_QMOUSEMOVE - 1);
for (ind = 0; ind < gnLastMouseMove; ind++) {
if (pq == gqMouseMove[ind].pq) {
gqMouseMove[ind].pt = pt;
return;
}
}
gqMouseMove[gnLastMouseMove].pq = pq;
gqMouseMove[gnLastMouseMove].pt = pt;
gnLastMouseMove++;
}
VOID PopMouseMove(
PQ pq,
POINT* ppt)
{
int ind;
CheckCritIn();
for (ind = 0; ind < gnLastMouseMove; ind++) {
if (pq == gqMouseMove[ind].pq) {
*ppt = gqMouseMove[ind].pt;
RtlMoveMemory(&gqMouseMove[ind],
&gqMouseMove[ind + 1],
(gnLastMouseMove - ind - 1) * sizeof(QMOUSEMOVE));
gnLastMouseMove--;
return;
}
}
UserAssert(0);
}
#endif // REDIRECTION
/***************************************************************************\
* zzzSetFMouseMoved
*
* Send a mouse move through the system. This usually occurs when doing
* window management to be sure that the mouse shape accurately reflects
* the part of the window it is currently over (window managment may have
* changed this).
*
* 11-02-92 ScottLu Created.
\***************************************************************************/
VOID zzzSetFMouseMoved(
VOID)
{
PWND pwnd;
PWND pwndOldCursor;
PQ pq;
#ifdef REDIRECTION
PWND pwndStart;
POINT ptMouse = gpsi->ptCursor;
#endif // REDIRECTION
/*
* Need to first figure out what queue this mouse event is in. Do NOT
* check for mouse capture here !! Talk to scottlu.
*/
if ((pwnd = gspwndScreenCapture) == NULL) {
#ifdef REDIRECTION
/*
* Call the speed hit test hook
*/
pwndStart = xxxCallSpeedHitTestHook(&ptMouse);
#endif // REDIRECTION
if ((pwnd = gspwndMouseOwner) == NULL) {
if ((pwnd = gspwndInternalCapture) == NULL) {
UserAssert(grpdeskRitInput != NULL);
#ifdef REDIRECTION
if (pwndStart == NULL) {
pwndStart = grpdeskRitInput->pDeskInfo->spwnd;
}
pwnd = SpeedHitTest(pwndStart, ptMouse);
#else
pwnd = SpeedHitTest(grpdeskRitInput->pDeskInfo->spwnd, gpsi->ptCursor);
#endif // REDIRECTION
}
}
}
if (pwnd == NULL)
return;
/*
* This is apparently needed by the attach/unattach code for some
* reason. I'd like to get rid of it - scottlu.
*/
pwndOldCursor = Lock(&gspwndCursor, pwnd);
/*
* If we're giving a mouse move to a new queue, be sure the cursor
* image represents what this queue thinks it should be.
*/
pq = GETPTI(pwnd)->pq;
/*
* Protect pq by deferring WinEvent notification
*/
DeferWinEventNotify();
if (pq != gpqCursor) {
/*
* If the old queue had the mouse captured, let him know that
* the mouse moved first. Need this to fix tooltips in
* WordPerfect Office. Do the same for mouse tracking.
*/
if (gpqCursor != NULL) {
if (gpqCursor->spwndCapture != NULL) {
gpqCursor->QF_flags |= QF_MOUSEMOVED;
SetWakeBit(GETPTI(gpqCursor->spwndCapture), QS_MOUSEMOVE);
#ifdef REDIRECTION
PushMouseMove(gpqCursor, ptMouse);
#endif // REDIRECTION
}
if ((pwndOldCursor != NULL) && (PtoHq(pwndOldCursor) != PtoHq(pwnd))) {
PDESKTOP pdesk = GETPDESK(pwndOldCursor);
if (pdesk->dwDTFlags & DF_MOUSEMOVETRK) {
PTHREADINFO pti = GETPTI(pdesk->spwndTrack);
PostEventMessage(pti, pti->pq, QEVENT_CANCELMOUSEMOVETRK,
pdesk->spwndTrack, pdesk->dwDTFlags, pdesk->htEx,
DF_MOUSEMOVETRK);
pdesk->dwDTFlags &= ~DF_MOUSEMOVETRK;
}
}
}
/*
* First re-assign gpqCursor so any zzzSetCursor() calls
* will only take effect if done by the thread that
* owns the window the mouse is currently over.
*/
gpqCursor = pq;
/*
* Call zzzUpdateCursorImage() so the new gpqCursor's
* notion of the current cursor is represented.
*/
zzzUpdateCursorImage();
}
/*
* Set the mouse moved bit for this queue so we know later to post
* a move message to this queue.
*/
pq->QF_flags |= QF_MOUSEMOVED;
#ifdef REDIRECTION
PushMouseMove(pq, ptMouse);
#endif // REDIRECTION
/*
* Reassign mouse input to this thread - this indicates which thread
* to wake up when new input comes in.
*/
pq->ptiMouse = GETPTI(pwnd);
/*
* Wake some thread within this queue to process this mouse event.
*/
WakeSomeone(pq, WM_MOUSEMOVE, NULL);
/*
* We're possibly generating a fake mouse move message - it has no
* extra info associated with it - so 0 it out.
*/
gdwMouseMoveExtraInfo = 0;
zzzEndDeferWinEventNotify();
}
/***************************************************************************\
* CancelForegroundActivate
*
* This routine cancels the foreground activate that we allow apps starting
* up to have. This means that if you make a request to start an app,
* if this routine is called before the app becomes foreground, it won't
* become foreground. This routine gets called if the user down clicks or
* makes a keydown event, with the idea being that if the user did this,
* the user is using some other app and doesn't want the newly starting
* application to appear on top and force itself into the foreground.
*
* 09-15-92 ScottLu Created.
\***************************************************************************/
void CancelForegroundActivate()
{
PPROCESSINFO ppiT;
if (TEST_PUDF(PUDF_ALLOWFOREGROUNDACTIVATE)) {
for (ppiT = gppiStarting; ppiT != NULL; ppiT = ppiT->ppiNext) {
/*
* Don't cancel activation if the app is being debugged - if
* the debugger stops the application before it has created and
* activated its first window, the app will come up behind all
* others - not what you want when being debugged.
*/
if (!PsGetProcessDebugPort(ppiT->Process)) {
ppiT->W32PF_Flags &= ~W32PF_ALLOWFOREGROUNDACTIVATE;
TAGMSG1(DBGTAG_FOREGROUND, "CancelForegroundActivate clear W32PF %#p", ppiT);
}
}
CLEAR_PUDF(PUDF_ALLOWFOREGROUNDACTIVATE);
TAGMSG0(DBGTAG_FOREGROUND, "CancelForegroundActivate clear PUDF");
}
}
/***************************************************************************\
* RestoreForegroundActivate
*
* This routine re-enables an app's right to foreground activate (activate and
* come on top) if it is starting up. This is called when we minimize or when
* the last window of a thread goes away, for example.
*
* 01-26-93 ScottLu Created.
\***************************************************************************/
void RestoreForegroundActivate()
{
PPROCESSINFO ppiT;
for (ppiT = gppiStarting; ppiT != NULL; ppiT = ppiT->ppiNext) {
if (ppiT->W32PF_Flags & W32PF_APPSTARTING) {
ppiT->W32PF_Flags |= W32PF_ALLOWFOREGROUNDACTIVATE;
TAGMSG1(DBGTAG_FOREGROUND, "RestoreForegroundActivate set W32PF %#p", ppiT);
SET_PUDF(PUDF_ALLOWFOREGROUNDACTIVATE);
TAGMSG0(DBGTAG_FOREGROUND, "RestoreForegroundActivate set PUDF");
}
}
}
/***************************************************************************\
* PostInputMessage
*
* Puts a message on the 'input' linked-list of message for the specified
* queue.
*
* History:
* 10-25-90 DavidPe Created.
* 01-21-92 DavidPe Rewrote to deal with OOM errors gracefully.
\***************************************************************************/
BOOL PostInputMessage(
PQ pq,
PWND pwnd,
UINT message,
WPARAM wParam,
LPARAM lParam,
DWORD time,
ULONG_PTR dwExtraInfo)
{
PQMSG pqmsgInput, pqmsgPrev;
short sWheelDelta;
#ifdef GENERIC_INPUT
#if DBG
/*
* Verify that the wParam that'll be sent with the WM_INPUT matches
* what's stored in the RAWINPUTHEADER.
*/
if (message == WM_INPUT) {
PHIDDATA pHidData = HtoP(lParam);
UserAssert(pHidData->rid.header.wParam == wParam);
}
#endif // DBG
#endif // GENERIC_INPUT
/*
* Grab the last written message before we start allocing new ones,
* so we're sure to point to the correct message.
*/
pqmsgPrev = pq->mlInput.pqmsgWriteLast;
/*
* Allocate a key state update event if needed.
*/
if (pq->QF_flags & QF_UPDATEKEYSTATE) {
PostUpdateKeyStateEvent(pq);
}
#ifdef GENERIC_INPUT
/*
* We don't want WM_INPUT messages inhibiting the coalescing of
* WM_MOUSEMOVE and WM_MOUSEWHEEL, so if the message being posted
* is one of those we check to see if there are any previous ones
* that would be "hidden" by a WM_INPUT.
*/
if (message == WM_MOUSEMOVE || message == WM_MOUSEWHEEL) {
while (pqmsgPrev && pqmsgPrev->msg.message == WM_INPUT) {
pqmsgPrev = pqmsgPrev->pqmsgPrev;
}
}
#endif // GENERIC_INPUT
/*
* We want to coalesce sequential WM_MOUSEMOVE and WM_MOUSEWHEEL.
* WM_MOUSEMOVEs are coalesced by just storing the most recent
* event over the last one.
* WM_MOUSEWHEELs also add up the wheel rolls.
*/
if (pqmsgPrev != NULL &&
pqmsgPrev->msg.message == message &&
(message == WM_MOUSEMOVE || message == WM_MOUSEWHEEL)) {
if (message == WM_MOUSEWHEEL) {
sWheelDelta = (short)HIWORD(wParam) + (short)HIWORD(pqmsgPrev->msg.wParam);
#if 0
/*
* LATER: We can't remove a wheel message with zero delta
* unless we know it hasn't been peeked. Ideally,
* we would check idsyspeek for this, but we're too close
* to ship and idsyspeek is too fragile. Consider also
* checking to see if mouse move messages have been peeked.
*/
if (sWheelDelta == 0) {
if ((PQMSG)pq->idSysPeek == pqmsgPrev) {
RIPMSG0(RIP_VERBOSE,
"Coalescing of mouse wheel messages causing "
"idSysPeek to be reset to 0");
pq->idSysPeek = 0;
}
DelQEntry(&pq->mlInput, pqmsgPrev);
return;
}
#endif
wParam = MAKEWPARAM(LOWORD(wParam), sWheelDelta);
}
StoreQMessage(pqmsgPrev, pwnd, message, wParam, lParam, time, 0, dwExtraInfo);
WakeSomeone(pq, message, pqmsgPrev);
return TRUE;
}
/*
* Fill in pqmsgInput.
*/
pqmsgInput = AllocQEntry(&pq->mlInput);
if (pqmsgInput == NULL) {
return FALSE;
}
StoreQMessage(pqmsgInput, pwnd, message, wParam, lParam, time, 0, dwExtraInfo);
WakeSomeone(pq, message, pqmsgInput);
return TRUE;
}
/***************************************************************************\
* WakeSomeone
*
* Figures out which thread to wake up based on the queue and message.
* If the queue pointer is NULL, figures out a likely queue.
*
* 10-23-92 ScottLu Created.
\***************************************************************************/
void WakeSomeone(
PQ pq,
UINT message,
PQMSG pqmsg)
{
BOOL fSetLastWoken = FALSE;
PTHREADINFO ptiT;
/*
* Set the appropriate wakebits for this queue.
*/
ptiT = NULL;
switch (message) {
case WM_SYSKEYDOWN:
case WM_KEYDOWN:
/*
* Don't change input ownership if the user is holding down
* a modifier key. When doing a ctrl-drag operation for example,
* the ctrl key must be down when the user drops the object (ie, mouse up).
* On mouse up the RIT gives input ownership to the target; but since
* ctrl is down, on the next repeat key we used to give input ownership
* to the focus window (usually the drag source). Hence the target
* would lose owenerhip and couldn't take the foreground.
*/
if (pqmsg != NULL) {
switch (pqmsg->msg.wParam) {
case VK_SHIFT:
case VK_CONTROL:
case VK_MENU:
if (TestKeyStateDown(pq, pqmsg->msg.wParam)) {
break;
}
/* Fall through */
default:
fSetLastWoken = TRUE;
break;
}
} else {
fSetLastWoken = TRUE;
}
/* fall through */
case WM_SYSCHAR:
case WM_CHAR:
/* Freelance graphics seems to pass in WM_SYSCHARs and WM_CHARs into
* the journal playback hook, so we need to set an input bit for
* this case since that is what win3.1 does. VB2 "learning" demo does
* the same, as does Excel intro.
*
* On win3.1, the WM_CHAR would by default set the QS_MOUSEBUTTON bit.
* On NT, the WM_CHAR sets the QS_KEY bit. This is because
* ScanSysQueue() calls TransferWakeBit() with the QS_KEY bit when
* a WM_CHAR message is passed in. By using the QS_KEY bit on NT,
* we're more compatible with what win3.1 wants to be.
*
* This fixes a case where the mouse was over progman, the WM_CHAR
* would come in via journal playback, wakesomeone would be called,
* and set the mouse bit in progman. Progman would then get into
* ScanSysQueue(), callback the journal playback hook, get the WM_CHAR,
* and do it again, looping. This caught VB2 in a loop.
*/
CancelForegroundActivate();
/* fall through */
case WM_KEYUP:
case WM_SYSKEYUP:
case WM_MOUSEWHEEL:
/*
* Win3.1 first looks at what thread has the active status. This
* means that we don't depend on the thread owning ptiKeyboard
* to wake up and process this key in order to give it to the
* active window, which is potentially newly active. Case in
* point: excel bringing up CBT, cbt has an error, brings up
* a message box: since excel is filtering for CBT messages only,
* ptiKeyboard never gets reassigned to CBT so CBT doesn't get
* any key messages and appears hung.
*/
ptiT = pq->ptiKeyboard;
if (pq->spwndActive != NULL)
ptiT = GETPTI(pq->spwndActive);
#ifdef GENERIC_INPUT
UserAssert(ptiT == PtiKbdFromQ(pq));
#endif
SetWakeBit(ptiT, message == WM_MOUSEWHEEL ? QS_MOUSEBUTTON : QS_KEY);
break;
case WM_MOUSEMOVE:
/*
* Make sure we wake up the thread with the capture, if there is
* one. This fixes PC Tools screen capture program, which sets
* capture and then loops trying to remove messages from the
* queue.
*/
if (pq->spwndCapture != NULL)
ptiT = GETPTI(pq->spwndCapture);
else
ptiT = pq->ptiMouse;
#ifdef GENERIC_INPUT
UserAssert(ptiT == PtiMouseFromQ(pq));
#endif
SetWakeBit(ptiT, QS_MOUSEMOVE);
break;
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK:
case WM_RBUTTONDOWN:
case WM_RBUTTONDBLCLK:
case WM_MBUTTONDOWN:
case WM_MBUTTONDBLCLK:
case WM_XBUTTONDOWN:
case WM_XBUTTONDBLCLK:
fSetLastWoken = TRUE;
/* fall through */
default:
/*
* The default case in Win3.1 for this is QS_MOUSEBUTTON.
*/
CancelForegroundActivate();
/* fall through */
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
case WM_XBUTTONUP:
/*
* Make sure we wake up the thread with the capture, if there is
* one. This fixes PC Tools screen capture program, which sets
* capture and then loops trying to remove messages from the
* queue.
*/
if (pq->spwndCapture != NULL &&
message >= WM_MOUSEFIRST && message <= WM_MOUSELAST)
ptiT = GETPTI(pq->spwndCapture);
else
ptiT = pq->ptiMouse;
SetWakeBit(ptiT, QS_MOUSEBUTTON);
break;
#ifdef GENERIC_INPUT
case WM_INPUT:
if (pqmsg->msg.hwnd) {
PWND pwnd = ValidateHwnd(pqmsg->msg.hwnd);
if (pwnd) {
ptiT = GETPTI(pwnd);
TAGMSG2(DBGTAG_PNP, "WakeSomeone: adjusted receiver pti %p for pwndTarget %p", ptiT, pwnd);
}
}
if (ptiT == NULL) {
ptiT = PtiKbdFromQ(pq);
}
SetWakeBit(ptiT, QS_RAWINPUT);
break;
#endif
}
/*
* If a messaged was passed in, remember in it who we woke up for this
* message. We do this so each message is ownership marked. This way
* we can merge/unmerge message streams when zzzAttachThreadInput() is
* called.
*/
if (ptiT != NULL) {
if (pqmsg != NULL) {
StoreQMessagePti(pqmsg, ptiT);
UserAssert(!(ptiT->TIF_flags & TIF_INCLEANUP));
}
/*
* Remember who got the last key/click down.
*/
if (fSetLastWoken) {
glinp.ptiLastWoken = ptiT;
}
}
}
/***************************************************************************\
* PostUpdateKeyStateEvent
*
* This routine posts an event which updates the local thread's keystate
* table. This makes sure the thread's key state is always up-to-date.
*
* An example is: control-esc from cmd to taskman.
* Control goes to cmd, but taskman is activated. Control state is still down
* in cmd - switch back to cmd, start typing, nothing appears because it thinks
* the control state is still down.
*
* As events go into a particular queue (queue A), the async key state table is
* updated. As long as transition events are put into queue A, the key
* state at the logical "end of the queue" is up-to-date with the async key
* state. As soon as the user posts transition events (up/down msgs) into queue
* B, the queue A's end-of-queue key state is out of date with the user. If
* the user then again added messages to queue A, when those messages are read
* the thread specific key state would not be updated correctly unless we
* did some synchronization (which this routine helps to do).
*
* As soon as transition events change queues, we go mark all the other queues
* with the QF_UPDATEKEYSTATE flag. Before any input event is posted into
* a queue, this flag is checked, and if set, this routine is called. This
* routine makes a copy of the async key state, and a copy of the bits
* representing the keys that have changed since the last update (we need to
* keep track of which keys have changed so that any state set by the
* app with SetKeyboardState() doesn't get wiped out). We take this data
* and post a new event of the type QEVENT_UPDATEKEYSTATE, which points to this
* key state and transition information. When this message is read out of the
* queue, this key state copy is copied into the thread specific key state
* table for those keys that have changed, and the copy is deallocated.
*
* This ensures all queues are input-synchronized with key transitions no matter
* where they occur. The side affect of this is that an application may suddenly
* have a key be up without seeing the up message. If this causes any problems
* we may have to generate false transition messages (this could have more nasty
* side affects as well, so it needs to be considered closely before being
* implemented.)
*
* 06-07-91 ScottLu Created.
\***************************************************************************/
void PostUpdateKeyStateEvent(
PQ pq)
{
BYTE *pb;
PQMSG pqmsg;
if (!(pq->QF_flags & QF_UPDATEKEYSTATE))
return;
/*
* Exclude the RIT - it's queue is never read, so don't waste memory
*/
if (pq->ptiKeyboard == gptiRit) {
return;
}
/*
* If there's no mousebutton or keystroke input pending, process the
* UpdateKeyState Event now: thus saving memory allocation and giving
* applications the correct KeyState immediately.
* NOTE: There may be event/activation msgs in pq->mlInput that don't
* affect keystate, so I'd like to just test QS_KEY | QS_MOUSEBUTTON
* specifically, instead of the cMsgss. However, sometimes there are
* keystroke or mousebutton msgs in the q without those bits set! - IanJa
*/
if (pq->mlInput.cMsgs == 0) {
ProcessUpdateKeyStateEvent(pq, gafAsyncKeyState, pq->afKeyRecentDown);
goto SyncQueue;
}
#if DBG
else if ((!pq->ptiKeyboard || !(pq->ptiKeyboard->pcti->fsWakeBits & QS_KEY)) &&
(!pq->ptiMouse || !(pq->ptiMouse->pcti->fsWakeBits & QS_MOUSEBUTTON))) {
/*
* See if there are any key or mousebutton messages that aren't
* indicated by QS_KEY or QS_MOUSEBUTTON bits.
*/
PQMSG pqmsgT;
for (pqmsgT = pq->mlInput.pqmsgRead; pqmsgT; pqmsgT = pqmsgT->pqmsgNext) {
if (pqmsgT->msg.message >= WM_KEYFIRST && pqmsgT->msg.message <= WM_KEYLAST) {
TAGMSG1(DBGTAG_InputWithoutQS,
"PostUpdateKeyStateEvent() pushing in front of a keystroke: Q %#p", pq);
} else if (pqmsgT->msg.message >= WM_LBUTTONDOWN && pqmsgT->msg.message <= WM_XBUTTONDBLCLK) {
TAGMSG1(DBGTAG_InputWithoutQS,
"PostUpdateKeyStateEvent() pushing in front of a mousebutton: Q %#p", pq);
}
}
}
#endif
UserAssert(pq->mlInput.pqmsgWriteLast != NULL);
/*
* If the last input message is an UPDATEKEYSTATE event, coalesce with it.
* (Prevents big memory leaks on apps that don't read input messages)
*/
pqmsg = pq->mlInput.pqmsgWriteLast;
if (pqmsg->dwQEvent == QEVENT_UPDATEKEYSTATE) {
int i;
DWORD *pdw;
pb = (PBYTE)(pqmsg->msg.wParam);
pdw = (DWORD *) (pb + CBKEYSTATE);
/*
* Copy in the new key state
*/
RtlCopyMemory(pb, gafAsyncKeyState, CBKEYSTATE);
/*
* Or in the recent key down state (DWORD at a time)
*/
#if (CBKEYSTATERECENTDOWN % 4) != 0
#error "CBKEYSTATERECENTDOWN assumed to be an integral number of DWORDs"
#endif
for (i = 0; i < CBKEYSTATERECENTDOWN / sizeof(*pdw); i++) {
*pdw++ |= ((DWORD *)(pq->afKeyRecentDown))[i];
}
/*
* Set QS_EVENTSET as in PostEventMessage, although this is
* usually, but not always already set
*/
SetWakeBit(pq->ptiKeyboard, QS_EVENTSET);
goto SyncQueue;
}
/*
* Make a copy of the async key state buffer, point to it, and add an
* event to the end of the input queue.
*/
if ((pb = UserAllocPool(KEYSTATESIZE, TAG_KBDSTATE)) == NULL) {
return;
}
RtlCopyMemory(pb, gafAsyncKeyState, CBKEYSTATE);
RtlCopyMemory(pb + CBKEYSTATE, pq->afKeyRecentDown, CBKEYSTATERECENTDOWN);
if (!PostEventMessage(pq->ptiKeyboard, pq, QEVENT_UPDATEKEYSTATE,
NULL, 0 , (WPARAM)pb, 0)) {
UserFreePool(pb);
return;
}
/*
* The key state of the queue is input-synchronized with the user. Erase
* all 'recent down' flags.
*/
SyncQueue:
RtlZeroMemory(pq->afKeyRecentDown, CBKEYSTATERECENTDOWN);
pq->QF_flags &= ~QF_UPDATEKEYSTATE;
}
/***************************************************************************\
* ProcessUpdateKeyStateEvent
*
* This is part two of the above routine, called when the QEVENT_UPDATEKEYSTATE
* message is read out of the input queue.
*
* 06-07-91 ScottLu Created.
\***************************************************************************/
void ProcessUpdateKeyStateEvent(
PQ pq,
CONST PBYTE pbKeyState,
CONST PBYTE pbRecentDown)
{
int i, j;
BYTE *pbChange;
int vk;
pbChange = pbRecentDown;
for (i = 0; i < CBKEYSTATERECENTDOWN; i++, pbChange++) {
/*
* Find some keys that have changed.
*/
if (*pbChange == 0)
continue;
/*
* Some keys have changed in this byte. find out which key it is.
*/
for (j = 0; j < 8; j++) {
/*
* Convert our counts to a virtual key index and check to see
* if this key has changed.
*/
vk = (i << 3) + j;
if (!TestKeyRecentDownBit(pbRecentDown, vk))
continue;
/*
* This key has changed. Update it's state in the thread key
* state table.
*/
if (TestKeyDownBit(pbKeyState, vk)) {
SetKeyStateDown(pq, vk);
} else {
ClearKeyStateDown(pq, vk);
}
if (TestKeyToggleBit(pbKeyState, vk)) {
SetKeyStateToggle(pq, vk);
} else {
ClearKeyStateToggle(pq, vk);
}
}
}
/*
* Update the key cache index.
*/
gpsi->dwKeyCache++;
/*
* All updated. Free the key state table if it was posted as an Event Message
*/
if (pbKeyState != gafAsyncKeyState) {
UserFreePool(pbKeyState);
}
}
/***************************************************************************\
* PostEventMessage
*
*
* History:
* 03-04-91 DavidPe Created.
\***************************************************************************/
BOOL PostEventMessage(
PTHREADINFO pti,
PQ pq,
DWORD dwQEvent,
PWND pwnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
PQMSG pqmsgEvent;
CheckCritIn();
/*
* If the thread is in cleanup, then it's possible the queue has
* already been removed for this thread. If this is the case, then
* we should fail to post the event to a dying thread.
*/
if (pti && (pti->TIF_flags & TIF_INCLEANUP))
return FALSE;
if ((pqmsgEvent = AllocQEntry(&pq->mlInput)) == NULL)
return FALSE;
StoreQMessage(pqmsgEvent, pwnd, message, wParam, lParam, 0, dwQEvent, 0);
StoreQMessagePti(pqmsgEvent, pti);
/*
* Let this thread know it has an event message to process.
*/
if (pti == NULL) {
UserAssert(pti);
SetWakeBit(pq->ptiMouse, QS_EVENTSET);
SetWakeBit(pq->ptiKeyboard, QS_EVENTSET);
} else {
SetWakeBit(pti, QS_EVENTSET);
}
return TRUE;
}
/***************************************************************************\
* CheckOnTop
*
* Usually windows come to the top and activate all at once. Occasionally, a
* starting app will create a window, pause for awhile, then make itself
* visible. During that pause, if the user clicks down, the window won't be
* allowed to activate (because of our foreground activation model). But this
* still leaves the new window on top of the active window. When this click
* happens, we get here: if this window is active and is not on top, bring it
* to the top.
*
* Case in point: start winquote, click down. The window
* you clicked on is active, but winquote is on top.
*
* This rarely does anything because 99.99% of the time the active
* window is already where it should be - on top. Note that
* CalcForegroundInsertAfter() takes into account owner-based zordering.
*
*
* NOTE: the following was the original function written. However, this
* function has been disabled for now. in WinWord and Excel especially,
* this tends to cause savebits to be blown-away on mouse-activation of
* its dialog-boxes. This could be a problem with GW_HWNDPREV and/or
* CalcForground not being the same, which causes a SetWindowPos to be
* called, resulting in the freeing of the SPB. This also solves a
* problem with the ComboBoxes in MsMoney hiding/freeing the dropdown
* listbox on activation as well.
*
* We need this for ActiveWindowTracking support. xxxBringWindowToTop used to be a call
* to SetWindowPos but now it's gone.
*
* It returns TRUE if the window was brought the top; if no z-order change
* took place, it returns FALSE.
*
* 05-20-93 ScottLu Created.
* 10-17-94 ChrisWil Made into stub-macro.
* 05-30-96 GerardoB Brought it back to live for AWT
\***************************************************************************/
BOOL CheckOnTop(PTHREADINFO pti, PWND pwndTop, UINT message)
{
if (pwndTop != pti->pq->spwndActive)
return FALSE;
switch (message) {
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_XBUTTONDOWN:
if ( TestWF(pwndTop, WEFTOPMOST)
|| (_GetWindow(pwndTop, GW_HWNDPREV) != CalcForegroundInsertAfter(pwndTop))) {
return xxxSetWindowPos(pwndTop, PWND_TOP, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
}
break;
}
return FALSE;
}
#define MA_PASSTHRU 0
#define MA_SKIP 1
#define MA_REHITTEST 2
/***************************************************************************\
* zzzActiveCursorTracking
*
* If active window tracking is enabled, activation follows
* the mouse. If the mouse is NOT on the active window
* (i.e., it was activated by a keyboard operation),
* activation will change as soon as the mouse moves.
* So we have to make sure the mouse is on the active window.
*
* History
* 12/07/96 GerardoB Created
\***************************************************************************/
void zzzActiveCursorTracking (PWND pwnd)
{
BOOL fVisible;
POINT pt;
/*
* If the last input event wasn't from the keyboard, bail
* The user is probably moving the mouse.
*/
if (!(glinp.dwFlags & LINP_KEYBOARD)) {
return;
}
/*
* If we're already there, bail.
*/
if (PtInRect((LPRECT)&pwnd->rcWindow, gptCursorAsync)) {
return;
}
/*
* If the window the mouse is on is not "active-trackable", then
* we can leave the mouse right where it is
*/
if ((gspwndCursor != NULL) && (GetActiveTrackPwnd(gspwndCursor, NULL) == NULL)) {
return;
}
/*
* If this window doesn't have a point visible in the screen, bail
*/
pt.x = pwnd->rcWindow.left + ((pwnd->rcWindow.right - pwnd->rcWindow.left) / 2);
pt.y = pwnd->rcWindow.top + ((pwnd->rcWindow.bottom - pwnd->rcWindow.top) / 2);
BoundCursor(&pt);
if (!PtInRect((LPRECT)&pwnd->rcWindow, pt)) {
return;
}
/*
* We need to make sure that this window is marked as visible or someone
* else will be waken up to update the cursor (and might
* activate itself because of the active tracking).
*
* Later5.0 GerardoB: If the window is still not visible when
* it wakes up, then we're out of luck.
*/
fVisible = TestWF(pwnd, WFVISIBLE);
if (!fVisible) {
SetVisible(pwnd, SV_SET);
}
/*
* Move the cursor to the center of this window
*/
zzzInternalSetCursorPos(pt.x, pt.y);
/*
* Restore visible bit.
*/
if (!fVisible) {
SetVisible(pwnd, SV_UNSET);
}
}
/***************************************************************************\
* GetActiveTrackPwnd
*
* History
* 12/07/96 GerardoB Extracted from xxxActiveWindowTracking.
\***************************************************************************/
PWND GetActiveTrackPwnd(PWND pwnd, Q **ppq)
{
PWND pwndActivate;
Q *pq;
CheckCritIn();
pwndActivate = pwnd;
/*
* Find the top parent
*/
while (TestwndChild(pwndActivate)) {
pwndActivate = pwndActivate->spwndParent;
}
/*
* If disabled, get a enabled popup owned by it.
*/
if (TestWF(pwndActivate, WFDISABLED)) {
/*
* This is what we do elsewhere when someone clicks on a
* disabled non-active window. It might be cheaper to check
* pwnd->spwndLastActive first (we might need to walk up
* the owner chain though, as this is where we set spwndLastAcitve
* when activating a new window. see xxxActivateThisWindow).
* But let's do the same here; this should be fixed/improved
* in DWP_GetEnabledPopup anyway. There might be a reason
* why we don't grab spwndLastActive if OK.... perhaps it has
* something to do with nested owner windows
*/
pwndActivate = DWP_GetEnabledPopup(pwndActivate);
}
/*
* Bail if we didn't find a visible window
*/
if ((pwndActivate == NULL) || !TestWF(pwndActivate, WFVISIBLE)) {
return NULL;
}
/*
* If already active in the foreground queue, nothing to do
* Don't activate the modeless menu notification window (it would
* dismiss the menu)
*/
pq = GETPTI(pwndActivate)->pq;
if ((pq == gpqForeground)
&& ((pwndActivate == pq->spwndActive)
|| IsModelessMenuNotificationWindow(pwndActivate))) {
return NULL;
}
/*
* Don't activate the shell window.
*/
if (pwndActivate == pwndActivate->head.rpdesk->pDeskInfo->spwndShell) {
return NULL;
}
/*
* Return the queue if requested
*/
if (ppq != NULL) {
*ppq = pq;
}
return pwndActivate;
}
/***************************************************************************\
* xxxActivateWindowTracking
*
* Activates a window without z-ordering it to the top
*
* 06/05/96 GerardoB Created
\***************************************************************************/
int xxxActiveWindowTracking(
PWND pwnd,
UINT uMsg,
int iHitTest)
{
BOOL fSuccess;
int iRet;
PWND pwndActivate;
Q *pq;
TL tlpwndActivate;
CheckLock(pwnd);
UserAssert(TestUP(ACTIVEWINDOWTRACKING));
/*
* If the mouse hasn't been long enough on this queue, bail.
*/
pq = GETPTI(pwnd)->pq;
if (!(pq->QF_flags & QF_ACTIVEWNDTRACKING)) {
return MA_PASSTHRU;
}
pq->QF_flags &= ~QF_ACTIVEWNDTRACKING;
/*
* If the foreground is locked, bail
*/
if (IsForegroundLocked()) {
return MA_PASSTHRU;
}
/*
* Get the window we need to activate. If none, bail.
*/
pwndActivate = GetActiveTrackPwnd(pwnd, &pq);
if (pwndActivate == NULL) {
return MA_PASSTHRU;
}
/*
* Lock if needed because we're about to callback
*/
if (pwnd != pwndActivate) {
ThreadLockAlways(pwndActivate, &tlpwndActivate);
}
/*
* Let's ask if it's OK to do this
*
* This message is supposed to go to the window the mouse is on.
* This could be a child window which might return MA_NOACTIVATE*.
* For mouse clicks (which is what we want to emulate here)
* xxxButtonEvent calls xxxSetForegroundWindow2 so their
* pwndActivate gets brought to the foreground regardless.
* So we send the message to pwndActivate instead.
*/
iRet = (int)xxxSendMessage(pwndActivate, WM_MOUSEACTIVATE,
(WPARAM)(HWq(pwndActivate)), MAKELONG((SHORT)iHitTest, uMsg));
switch (iRet) {
case MA_ACTIVATE:
case MA_ACTIVATEANDEAT:
if (pq == gpqForeground) {
fSuccess = xxxActivateThisWindow(pwndActivate, 0,
(TestUP(ACTIVEWNDTRKZORDER) ? 0 : ATW_NOZORDER));
} else {
fSuccess = xxxSetForegroundWindow2(pwndActivate, NULL,
SFW_SWITCH | (TestUP(ACTIVEWNDTRKZORDER) ? 0 : SFW_NOZORDER));
}
/*
* Eat the message if activation failed.
*/
if (!fSuccess) {
iRet = MA_SKIP;
} else if (iRet == MA_ACTIVATEANDEAT) {
iRet = MA_SKIP;
}
break;
case MA_NOACTIVATEANDEAT:
iRet = MA_SKIP;
break;
case MA_NOACTIVATE:
default:
iRet = MA_PASSTHRU;
break;
}
if (pwnd != pwndActivate) {
ThreadUnlock(&tlpwndActivate);
}
return iRet;
}
/***************************************************************************\
* xxxMouseActivate
*
* This is where activation due to mouse clicks occurs.
*
* IMPLEMENTATION:
* The message is sent to the specified window. In xxxDefWindowProc, the
* message is sent to the window's parent. The receiving window may
* a) process the message,
* b) skip the message totally, or
* c) re-hit test message
*
* A WM_SETCURSOR message is also sent through the system to set the cursor.
*
* History:
* 11-22-90 DavidPe Ported.
\***************************************************************************/
int xxxMouseActivate(
PTHREADINFO pti,
PWND pwnd,
UINT message,
WPARAM wParam,
LPPOINT lppt,
int ht)
{
UINT x, y;
PWND pwndTop;
int result;
TL tlpwndTop;
BOOL fSend;
CheckLock(pwnd);
UserAssert(_GETPDESK(pwnd) != NULL);
/*
* No mouse activation if the mouse is captured. Must check for the capture
* ONLY here. 123W depends on it - create a graph, select Rearrange..
* flip horizontal, click outside the graph. If this code checks for
* anything beside just capture, 123w will get the below messages and
* get confused.
*/
if (pti->pq->spwndCapture != NULL) {
return MA_PASSTHRU;
}
result = MA_PASSTHRU;
pwndTop = pwnd;
ThreadLockWithPti(pti, pwndTop, &tlpwndTop);
/*
* B#1404
* Don't send WM_PARENTNOTIFY messages if the child has
* WS_EX_NOPARENTNOTIFY style.
*
* Unfortunately, this breaks people who create controls in
* MDI children, like WinMail. They don't get WM_PARENTNOTIFY
* messages, which don't get passed to DefMDIChildProc(), which
* then can't update the active MDI child. Grrr.
*/
fSend = (!TestWF(pwnd, WFWIN40COMPAT) || !TestWF(pwnd, WEFNOPARENTNOTIFY));
/*
* If it's a buttondown event, send WM_PARENTNOTIFY.
*/
switch (message) {
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_XBUTTONDOWN:
while (TestwndChild(pwndTop)) {
pwndTop = pwndTop->spwndParent;
if (fSend) {
ThreadUnlock(&tlpwndTop);
ThreadLockWithPti(pti, pwndTop, &tlpwndTop);
x = (UINT)(lppt->x - pwndTop->rcClient.left);
y = (UINT)(lppt->y - pwndTop->rcClient.top);
/* Get the xbutton from the hiword of wParam */
UserAssert(message == WM_XBUTTONDOWN || HIWORD(wParam) == 0);
UserAssert(LOWORD(wParam) == 0);
xxxSendMessage(pwndTop, WM_PARENTNOTIFY, (WPARAM)(message | wParam), MAKELPARAM(x, y));
}
}
if (!fSend) {
ThreadUnlock(&tlpwndTop);
ThreadLockAlwaysWithPti(pti, pwndTop, &tlpwndTop);
}
/*
* NOTE: We break out of this loop with pwndTop locked.
*/
break;
}
/*
* The mouse was moved onto this window: make it foreground
*/
if (TestUP(ACTIVEWINDOWTRACKING) && (message == WM_MOUSEMOVE)) {
result = xxxActiveWindowTracking(pwnd, WM_MOUSEMOVE, ht);
}
/*
* Are we hitting an inactive top-level window WHICH ISN'T THE DESKTOP(!)?
*
* craigc 7-14-89 hitting either inactive top level or any child window,
* to be compatible with 2.X. Apps apparently needs this message.
*/
else if ((pti->pq->spwndActive != pwnd || pti->pq->QF_flags & QF_EVENTDEACTIVATEREMOVED) &&
(pwndTop != PWNDDESKTOP(pwndTop))) {
switch (message) {
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_XBUTTONDOWN:
/*
* Send the MOUSEACTIVATE message.
*/
result = (int)xxxSendMessage(pwnd, WM_MOUSEACTIVATE,
(WPARAM)(HW(pwndTop)), MAKELONG((SHORT)ht, message));
switch (result) {
case 0:
case MA_ACTIVATE:
case MA_ACTIVATEANDEAT:
/*
* If activation fails, swallow the message.
*/
if ((pwndTop != pti->pq->spwndActive ||
pti->pq->QF_flags & QF_EVENTDEACTIVATEREMOVED) &&
!xxxActivateWindow(pwndTop,
(UINT)((pti->pq->codeCapture == NO_CAP_CLIENT) ?
AW_TRY2 : AW_TRY))) {
result = MA_SKIP;
} else if (TestWF(pwndTop, WFDISABLED)) {
#ifdef NEVER
/*
* Althoug this is what win3 does, it is not good: it
* can easily cause infinite loops. Returning "rehittest"
* means process this event over again - nothing causes
* anything different to happen, and we get an infinite
* loop. This case never gets executed on win3 because if
* the window is disabled, it got the HTERROR hittest
* code. This can only be done on Win32 where input is
* assigned to a window BEFORE process occurs to pull
* it out of the queue.
*/
result = MA_REHITTEST;
#endif
/*
* Someone clicked on a window before it was disabled...
* Since it is disabled now, don't send this message to
* it: instead eat it.
*/
result = MA_SKIP;
} else if (result == MA_ACTIVATEANDEAT) {
result = MA_SKIP;
} else {
result = MA_PASSTHRU;
goto ItsActiveJustCheckOnTop;
}
break;
case MA_NOACTIVATEANDEAT:
result = MA_SKIP;
break;
}
}
} else {
ItsActiveJustCheckOnTop:
/*
* Make sure this active window is on top (see comment
* in CheckOnTop).
*/
if (TestUP(ACTIVEWINDOWTRACKING)) {
if (CheckOnTop(pti, pwndTop, message)) {
/*
* The window was z-ordered to the top.
* If it is a console window, skip the message
* so it won't go into "selecting" mode
* Hard error boxes are created by csrss as well
* If we have topmost console windows someday, this
* will need to change
*/
if ((ht == HTCLIENT)
&& (GETPTI(pwndTop)->TIF_flags & TIF_CSRSSTHREAD)
&& !(TestWF(pwndTop, WEFTOPMOST))) {
RIPMSG2(RIP_WARNING, "xxxMouseActivate: Skipping msg %#lx for pwnd %#p",
message, pwndTop);
result = MA_SKIP;
}
}
} /* if (TestUP(ACTIVEWINDOWTRACKING)) */
}
/*
* Now set the cursor shape.
*/
if (pti->pq->spwndCapture == NULL) {
xxxSendMessage(pwnd, WM_SETCURSOR, (WPARAM)HW(pwnd),
MAKELONG((SHORT)ht, message));
}
ThreadUnlock(&tlpwndTop);
return result;
}
/***************************************************************************\
* ResetMouseHover()
*
* Resets mouse hover state information.
*
* 11/03/95 francish created.
* 09/04/97 GerardoB Rewritten to use per desktop tracking
\***************************************************************************/
void ResetMouseHover(PDESKTOP pdesk, POINT pt)
{
/*
* Reset the timer and hover rect
*/
InternalSetTimer(pdesk->spwndTrack, IDSYS_MOUSEHOVER,
pdesk->dwMouseHoverTime,
xxxSystemTimerProc, TMRF_SYSTEM);
SetRect(&pdesk->rcMouseHover,
pt.x - gcxMouseHover / 2,
pt.y - gcyMouseHover / 2,
pt.x + gcxMouseHover / 2,
pt.y + gcyMouseHover / 2);
}
/***************************************************************************\
* QueryTrackMouseEvent()
*
* Fills in a TRACKMOUSEEVENT structure describing current tracking state.
*
* 11/03/95 francish created.
* 09/04/97 GerardoB Rewritten to use per desktop tracking
\***************************************************************************/
BOOL QueryTrackMouseEvent(
LPTRACKMOUSEEVENT lpTME)
{
PTHREADINFO ptiCurrent = PtiCurrent();
PDESKTOP pdesk = ptiCurrent->rpdesk;
/*
* initialize the struct
*/
RtlZeroMemory(lpTME, sizeof(*lpTME));
lpTME->cbSize = sizeof(*lpTME);
/*
* Bail if not tracking any mouse event
* or if the current thread is not in spwndTrack's queue
*/
if (!(pdesk->dwDTFlags & DF_TRACKMOUSEEVENT)
|| (ptiCurrent->pq != GETPTI(pdesk->spwndTrack)->pq)) {
return TRUE;
}
/*
* fill in the requested information
*/
if (pdesk->htEx != HTCLIENT) {
lpTME->dwFlags |= TME_NONCLIENT;
}
if (pdesk->dwDTFlags & DF_TRACKMOUSELEAVE) {
lpTME->dwFlags |= TME_LEAVE;
}
if (pdesk->dwDTFlags & DF_TRACKMOUSEHOVER) {
lpTME->dwFlags |= TME_HOVER;
lpTME->dwHoverTime = pdesk->dwMouseHoverTime;
}
lpTME->hwndTrack = HWq(pdesk->spwndTrack);
return TRUE;
}
/***************************************************************************\
* TrackMouseEvent()
*
* API for requesting extended mouse notifications (hover, leave, ...)
*
* 11/03/95 francish created.
* 09/04/97 GerardoB Rewritten to use per desktop tracking
\***************************************************************************/
BOOL TrackMouseEvent(
LPTRACKMOUSEEVENT lpTME)
{
PDESKTOP pdesk = PtiCurrent()->rpdesk;
PWND pwnd;
/*
* Validate hwndTrack
*/
pwnd = ValidateHwnd(lpTME->hwndTrack);
if (pwnd == NULL) {
return FALSE;
}
/*
* If we're not tracking this window or not in correct hittest, bail
*/
if ((pwnd != pdesk->spwndTrack)
|| (!!(lpTME->dwFlags & TME_NONCLIENT) ^ (pdesk->htEx != HTCLIENT))) {
if ((lpTME->dwFlags & TME_LEAVE) && !(lpTME->dwFlags & TME_CANCEL)) {
_PostMessage(pwnd,
((lpTME->dwFlags & TME_NONCLIENT) ? WM_NCMOUSELEAVE : WM_MOUSELEAVE),
0, 0);
}
return TRUE;
}
/*
* Process cancel request
*/
if (lpTME->dwFlags & TME_CANCEL) {
if (lpTME->dwFlags & TME_LEAVE) {
pdesk->dwDTFlags &= ~DF_TRACKMOUSELEAVE;
}
if (lpTME->dwFlags & TME_HOVER) {
if (pdesk->dwDTFlags & DF_TRACKMOUSEHOVER) {
_KillSystemTimer(pwnd, IDSYS_MOUSEHOVER);
pdesk->dwDTFlags &= ~DF_TRACKMOUSEHOVER;
}
}
return TRUE;
}
/*
* Track mouse leave
*/
if (lpTME->dwFlags & TME_LEAVE) {
pdesk->dwDTFlags |= DF_TRACKMOUSELEAVE;
}
/*
* Track mouse hover
*/
if (lpTME->dwFlags & TME_HOVER) {
pdesk->dwDTFlags |= DF_TRACKMOUSEHOVER;
pdesk->dwMouseHoverTime = lpTME->dwHoverTime;
if ((pdesk->dwMouseHoverTime == 0) || (pdesk->dwMouseHoverTime == HOVER_DEFAULT)) {
pdesk->dwMouseHoverTime = gdtMouseHover;
}
ResetMouseHover(pdesk, GETPTI(pwnd)->ptLast);
}
return TRUE;
}
/***************************************************************************\
* xxxGetNextSysMsg
*
* Returns the queue pointer of the next system message or
* NULL - no more messages (may be a journal playback delay)
* PQMSG_PLAYBACK - got a journal playback message
* (Anything else is a real pointer)
*
* 10-23-92 ScottLu Created.
\***************************************************************************/
PQMSG xxxGetNextSysMsg(
PTHREADINFO pti,
PQMSG pqmsgPrev,
PQMSG pqmsg)
{
DWORD dt;
PMLIST pml;
PQMSG pqmsgT;
/*
* If there is a journal playback hook, call it to get the next message.
*/
if (PhkFirstGlobalValid(pti, WH_JOURNALPLAYBACK) != NULL && IsOnInputDesktop(pti)) {
/*
* We can't search through journal messages: we only get the current
* journal message. So if the caller has already called us once
* before, then exit with no messages.
*/
if (pqmsgPrev != 0)
return NULL;
/*
* Tell the journal playback hook that we're done
* with this message now.
*/
dt = xxxCallJournalPlaybackHook(pqmsg);
if (dt == 0xFFFFFFFF)
return NULL;
/*
* If dt == 0, then we don't need to wait: set the right wake
* bits and return this message.
*/
if (dt == 0) {
WakeSomeone(pti->pq, pqmsg->msg.message, NULL);
/*
* Remember input is coming through journalling so we'll know this is
* an automation scenario.
* Note that we don't change any of the glinp information here so it
* continues to hold what the actual last hardware or SendInput input event was.
* I'm not changing it now to avoid any unexpected side effects from it since
* there's no scenario requesting so.
* This could pontentially be reconsidered so glinp completely reflects
* what the last input event was, regardless of its source.
*/
glinp.dwFlags = glinp.dwFlags | LINP_JOURNALLING;
return PQMSG_PLAYBACK;
} else {
/*
* There is logically no more input in the "queue", so clear the
* bits so that we will sleep when GetMessage is called.
*/
pti->pcti->fsWakeBits &= ~QS_INPUT;
pti->pcti->fsChangeBits &= ~QS_INPUT;
/*
* Need to wait before processing this next message. Set
* a journal timer.
*/
SetJournalTimer(dt, pqmsg->msg.message);
return NULL;
}
}
/*
* No journalling going on... return next message in system queue.
*/
/*
* Queue up a mouse move if the mouse has moved.
*/
if (pti->pq->QF_flags & QF_MOUSEMOVED) {
PostMove(pti->pq);
}
/*
* If no messages in the input queue, return with 0.
*/
pml = &pti->pq->mlInput;
if (pml->cMsgs == 0)
return NULL;
/*
* If this is the first call to xxxGetNextSysMsg(), return the
* first message.
*/
if (pqmsgPrev == NULL || pti->pq->idSysPeek <= (ULONG_PTR)PQMSG_PLAYBACK) {
pqmsgT = pml->pqmsgRead;
} else {
/*
* Otherwise return the next message in the queue. Index with
* idSysPeek, because that is updated by recursive calls through
* this code.
*/
pqmsgT = ((PQMSG)(pti->pq->idSysPeek))->pqmsgNext;
}
/*
* Fill in the structure passed, and return the pointer to the
* current message in the message list. This will become the new
* pq->idSysPeek.
*/
if (pqmsgT != NULL)
*pqmsg = *pqmsgT;
return pqmsgT;
}
/***************************************************************************\
* UpdateKeyState
*
* Updates queue key state tables.
*
* 11-11-92 ScottLu Created.
\***************************************************************************/
void UpdateKeyState(
PQ pq,
UINT vk,
BOOL fDown)
{
if (vk != 0) {
/*
* If we're going down, toggle only if the key isn't
* already down.
*/
if (fDown && !TestKeyStateDown(pq, vk)) {
if (TestKeyStateToggle(pq, vk)) {
ClearKeyStateToggle(pq, vk);
} else {
SetKeyStateToggle(pq, vk);
}
}
/*
* Now set/clear the key down state.
*/
if (fDown) {
SetKeyStateDown(pq, vk);
} else {
ClearKeyStateDown(pq, vk);
}
/*
* If this is one of the keys we cache, update the key cache index.
*/
if (vk < CVKKEYCACHE) {
gpsi->dwKeyCache++;
}
}
}
/***************************************************************************\
* EqualMsg
*
* This routine is called in case that idSysPeek points to a message
* and we are trying to remove a different message
*
* 04-25-96 CLupu Created.
\***************************************************************************/
BOOL EqualMsg(PQMSG pqmsg1, PQMSG pqmsg2)
{
if (pqmsg1->msg.hwnd != pqmsg2->msg.hwnd ||
pqmsg1->msg.message != pqmsg2->msg.message)
return FALSE;
/*
* This might be a coalesced WM_MOUSEMOVE
*/
if (pqmsg1->msg.message == WM_MOUSEMOVE)
return TRUE;
if (pqmsg1->pti != pqmsg2->pti ||
pqmsg1->msg.time != pqmsg2->msg.time)
return FALSE;
return TRUE;
}
/***************************************************************************\
* xxxSkipSysMsg
*
* This routine "skips" an input message: either by calling the journal
* hooks if we're journalling or by "skipping" the message in the input
* queue. Internal keystate tables are updated as well.
*
* 10-23-92 ScottLu Created.
\***************************************************************************/
void xxxSkipSysMsg(
PTHREADINFO pti,
PQMSG pqmsg)
{
PQMSG pqmsgT;
BOOL fDown;
BYTE vk;
PHOOK phook;
/*
* If idSysPeek is 0, then the pqmsg that we were looking at has been
* deleted, probably because of a callout from ScanSysQueue, and that
* callout then called PeekMessage(fRemove == TRUE), and then returned.
*/
if (pti->pq->idSysPeek == 0)
return;
phook = PhkFirstGlobalValid(pti, WH_JOURNALPLAYBACK);
if (phook != NULL && IsOnInputDesktop(pti)) {
/*
* Tell the journal playback hook that we're done
* with this message now.
*/
phook->flags |= HF_NEEDHC_SKIP;
} else {
phook = PhkFirstGlobalValid(pti, WH_JOURNALRECORD);
if (phook != NULL) {
/*
* We've processed a new message: tell the journal record
* hook what the message is.
*/
xxxCallJournalRecordHook(pqmsg);
}
/*
* If idSysPeek is 0 now, it means we've been recursed into yet
* again. This would confuse a journalling app, but it would confuse
* us more because we'd fault. Return if idSysPeek is 0.
*/
if ((pqmsgT = (PQMSG)pti->pq->idSysPeek) == NULL)
return;
/*
* Delete this message from the input queue. Make sure pqmsgT isn't
* 1: this could happen if an app unhooked a journal record hook
* during a callback from xxxScanSysQueue.
*/
if (pqmsgT != PQMSG_PLAYBACK) {
/*
* There are cases when idSysPeek points to a different message
* than the one we are trying to remove. This can happen if
* two threads enters in xxxScanSysQueue, sets the idSysPeek and
* after this their queues got redistributed. The first thread
* will have the idSysPeek preserved but the second one has to
* search the queue for its message. - ask CLupu
*/
if (!EqualMsg(pqmsgT, pqmsg)) {
PQMSG pqmsgS;
#if DBG
if (IsDbgTagEnabled(DBGTAG_SysPeek)) {
gnSysPeekSearch++;
}
#endif
TAGMSG0(DBGTAG_SysPeek | RIP_THERESMORE, "Different message than idSysPeek\n");
TAGMSG2(DBGTAG_SysPeek | RIP_NONAME | RIP_THERESMORE, "pqmsg = %#p idSysPeek = %#p", pqmsg, pqmsgT);
TAGMSG2(DBGTAG_SysPeek | RIP_NONAME | RIP_THERESMORE, "pti = %#p pti = %#p", pqmsg->pti, pqmsgT->pti);
TAGMSG2(DBGTAG_SysPeek | RIP_NONAME | RIP_THERESMORE, "msg = %08lx msg = %08lx", pqmsg->msg.message, pqmsgT->msg.message);
TAGMSG2(DBGTAG_SysPeek | RIP_NONAME | RIP_THERESMORE, "hwnd = %#p hwnd = %#p", pqmsg->msg.hwnd, pqmsgT->msg.hwnd);
TAGMSG2(DBGTAG_SysPeek | RIP_NONAME | RIP_THERESMORE, "wParam = %#p wParam = %#p", pqmsg->msg.wParam, pqmsgT->msg.wParam);
TAGMSG2(DBGTAG_SysPeek | RIP_NONAME | RIP_THERESMORE, "lParam = %#p lParam = %#p", pqmsg->msg.lParam, pqmsgT->msg.lParam);
TAGMSG2(DBGTAG_SysPeek | RIP_NONAME | RIP_THERESMORE, "time = %08lx time = %08lx", pqmsg->msg.time, pqmsgT->msg.time);
TAGMSG2(DBGTAG_SysPeek | RIP_NONAME | RIP_THERESMORE, "Extra = %08lx Extra = %08lx", pqmsg->ExtraInfo, pqmsgT->ExtraInfo);
TAGMSG1(DBGTAG_SysPeek | RIP_NONAME, "\npqmsgT = %#p", pqmsgT);
/*
* Begin to search for this message
*/
pqmsgS = pti->pq->mlInput.pqmsgRead;
while (pqmsgS != NULL) {
if (EqualMsg(pqmsgS, pqmsg)) {
TAGMSG2(DBGTAG_SysPeek | RIP_THERESMORE,
"Deleting pqmsg %#p, pti %#p",
pqmsgS, pqmsgS->pti);
TAGMSG4(DBGTAG_SysPeek | RIP_NONAME,
"m %04lx, w %#p, l %#p, t %lx",
pqmsgS->msg.message, pqmsgS->msg.hwnd,
pqmsgS->msg.lParam, pqmsgS->msg.time);
pqmsgT = pqmsgS;
break;
}
pqmsgS = pqmsgS->pqmsgNext;
}
if (pqmsgS == NULL) {
TAGMSG0(DBGTAG_SysPeek, "Didn't find a matching message. No message removed.");
return;
}
}
if (pqmsgT == (PQMSG)pti->pq->idSysPeek) {
/*
* We'll remove this message from the input queue
* so set idSysPeek to 0.
*/
CheckPtiSysPeek(1, pti->pq, 0);
pti->pq->idSysPeek = 0;
}
DelQEntry(&pti->pq->mlInput, pqmsgT);
}
}
fDown = TRUE;
vk = 0;
switch (pqmsg->msg.message) {
case WM_MOUSEMOVE:
case WM_QUEUESYNC:
default:
/*
* No state change.
*/
break;
case WM_KEYUP:
case WM_SYSKEYUP:
fDown = FALSE;
/*
* Fall through.
*/
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
vk = LOBYTE(LOWORD(pqmsg->msg.wParam));
break;
case WM_LBUTTONUP:
fDown = FALSE;
/*
* Fall through.
*/
case WM_LBUTTONDOWN:
vk = VK_LBUTTON;
break;
case WM_RBUTTONUP:
fDown = FALSE;
/*
* Fall through.
*/
case WM_RBUTTONDOWN:
vk = VK_RBUTTON;
break;
case WM_MBUTTONUP:
fDown = FALSE;
/*
* Fall through.
*/
case WM_MBUTTONDOWN:
vk = VK_MBUTTON;
break;
case WM_XBUTTONUP:
fDown = FALSE;
/*
* Fall through.
*/
case WM_XBUTTONDOWN:
UserAssert(GET_XBUTTON_WPARAM(pqmsg->msg.wParam) == XBUTTON1 ||
GET_XBUTTON_WPARAM(pqmsg->msg.wParam) == XBUTTON2);
switch (GET_XBUTTON_WPARAM(pqmsg->msg.wParam)) {
case XBUTTON1:
vk = VK_XBUTTON1;
break;
case XBUTTON2:
vk = VK_XBUTTON2;
break;
}
break;
}
/*
* Set toggle and down bits appropriately.
*/
if ((vk == VK_SHIFT) || (vk == VK_MENU) || (vk == VK_CONTROL)) {
BYTE vkHanded, vkOtherHand;
/*
* Convert this virtual key into a differentiated (Left/Right) key
* depending on the extended key bit.
*/
vkHanded = (vk - VK_SHIFT) * 2 + VK_LSHIFT +
((pqmsg->msg.lParam & EXTENDED_BIT) ? 1 : 0);
vkOtherHand = vkHanded ^ 1;
if (vk == VK_SHIFT) {
/*
* Clear extended bit for r.h. Shift, since it isn't really
* extended (bit was set to indicate right-handed)
*/
pqmsg->msg.lParam &= ~EXTENDED_BIT;
}
/*
* Update the key state for the differentiated (Left/Right) key.
*/
UpdateKeyState(pti->pq, vkHanded, fDown);
/*
* Update key state for the undifferentiated (logical) key.
*/
if (fDown || !TestKeyStateDown(pti->pq, vkOtherHand)) {
UpdateKeyState(pti->pq, vk, fDown);
}
} else {
UpdateKeyState(pti->pq, vk, fDown);
}
}
#if DBG
/***************************************************************************\
* LogPlayback
*
*
* History:
* 02-13-95 JimA Created.
\***************************************************************************/
void LogPlayback(
PWND pwnd,
PMSG lpmsg)
{
static PWND pwndM = NULL, pwndK = NULL;
LPCSTR lpszMsg;
CHAR achBuf[20];
if ((lpmsg->message >= WM_MOUSEFIRST) && (lpmsg->message <= WM_MOUSELAST)) {
lpszMsg = aszMouse[lpmsg->message - WM_MOUSEFIRST];
if (pwnd != pwndM) {
DbgPrint("*** Mouse input to window \"%ws\" of class \"%s\"\n",
pwnd->strName.Length ? pwnd->strName.Buffer : L"",
pwnd->pcls->lpszAnsiClassName);
pwndM = pwnd;
}
} else if ((lpmsg->message >= WM_KEYFIRST) && (lpmsg->message <= WM_KEYLAST)) {
lpszMsg = aszKey[lpmsg->message - WM_KEYFIRST];
if (pwnd != pwndK) {
DbgPrint("*** Kbd input to window \"%ws\" of class \"%s\"\n",
pwnd->strName.Length ? pwnd->strName.Buffer : L"",
pwnd->pcls->lpszAnsiClassName);
pwndK = pwnd;
}
} else if (lpmsg->message == WM_QUEUESYNC) {
lpszMsg = "WM_QUEUESYNC";
} else {
sprintf(achBuf, "0x%4x", lpmsg->message);
lpszMsg = achBuf;
}
DbgPrint("msg = %s, wP = %x, lP = %x\n", lpszMsg,
lpmsg->wParam, lpmsg->lParam);
}
#endif // DBG
/***************************************************************************\
*
* GetMouseKeyFlags()
*
* Computes MOST of the MK_ flags given a Q.
* Does not compute MK_MOUSEENTER.
*
\***************************************************************************/
UINT GetMouseKeyFlags(
PQ pq)
{
UINT wParam = 0;
if (TestKeyStateDown(pq, VK_LBUTTON))
wParam |= MK_LBUTTON;
if (TestKeyStateDown(pq, VK_RBUTTON))
wParam |= MK_RBUTTON;
if (TestKeyStateDown(pq, VK_MBUTTON))
wParam |= MK_MBUTTON;
if (TestKeyStateDown(pq, VK_XBUTTON1))
wParam |= MK_XBUTTON1;
if (TestKeyStateDown(pq, VK_XBUTTON2))
wParam |= MK_XBUTTON2;
if (TestKeyStateDown(pq, VK_SHIFT))
wParam |= MK_SHIFT;
if (TestKeyStateDown(pq, VK_CONTROL))
wParam |= MK_CONTROL;
return wParam;
}
/***************************************************************************\
* xxxScanSysQueue
*
* This routine looks at the hardware message, determines what
* window it will be in, determines what the input message will
* be, and then checks the destination window against hwndFilter,
* and the input message against msgMinFilter and msgMaxFilter.
*
* It also updates various input synchronized states like keystate info.
*
* This is almost verbatim from Win3.1.
*
* 10-20-92 ScottLu Created.
\***************************************************************************/
#ifdef MARKPATH
#define PATHTAKEN(x) pathTaken |= x
#define PATHTAKEN2(x) pathTaken2 |= x
#define PATHTAKEN3(x) pathTaken3 |= x
#define DUMPPATHTAKEN() if (gfMarkPath) DbgPrint("xxxScanSysQueue path:%08x %08x %08x\n", pathTaken, pathTaken2, pathTaken3)
#define DUMPSUBPATHTAKEN(p, x) if (gfMarkPath && p & x) { DbgPrint(" %08x %08x %08x\n", pathTaken, pathTaken2, pathTaken3); pathTaken = pathTaken2 = pathTaken3 = 0; }
#else
#define PATHTAKEN(x)
#define PATHTAKEN2(x)
#define PATHTAKEN3(x)
#define DUMPPATHTAKEN()
#define DUMPSUBPATHTAKEN(p, x)
#endif
BOOL xxxScanSysQueue(
PTHREADINFO ptiCurrent,
LPMSG lpMsg,
PWND pwndFilter,
UINT msgMinFilter,
UINT msgMaxFilter,
DWORD flags,
DWORD fsReason)
{
QMSG qmsg;
HWND hwnd;
PWND pwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
PTHREADINFO ptiKeyWake, ptiMouseWake, ptiEventWake;
#ifdef GENERIC_INPUT
PTHREADINFO ptiRawInputWake;
#endif
POINT pt, ptScreen;
UINT codeMouseDown;
BOOL fMouseHookCalled;
BOOL fKbdHookCalled;
BOOL fOtherApp;
int part;
MOUSEHOOKSTRUCTEX mhs;
PWND pwndT;
BOOL fPrevDown;
BOOL fDown;
BOOL fAlt;
TL tlpwnd;
TL tlpwndT;
TL tlptiKeyWake;
TL tlptiMouseWake;
TL tlptiEventWake;
#ifdef GENERIC_INPUT
TL tlptiRawInputWake;
#endif
BOOL fRemove = (flags & PM_REMOVE);
DWORD dwImmRet = 0;
#ifdef MARKPATH
DWORD pathTaken = 0;
DWORD pathTaken2 = 0;
DWORD pathTaken3 = 0;
#endif
UserAssert(IsWinEventNotifyDeferredOK());
UserAssert((fsReason & ~(QS_EVENT | QS_INPUT)) == 0 &&
(fsReason & (QS_EVENT | QS_INPUT)) != 0);
/*
* If we are looking at a peeked message currently (recursion into this
* routine) and the only reason we got here was because of an event
* message (an app was filtering for a non-input message), then just
* return so we don't mess up idSysPeek. If we do enter this code
* idSysPeek will get set back to 0, and when we return back into
* the previous xxxScanSysQueue(), SkipSysMsg() will do nothing, so the
* message won't get removed. (MS Publisher 2.0 does this).
*/
if (fsReason == QS_EVENT) {
if (ptiCurrent->pq->idSysPeek != 0) {
PATHTAKEN(1);
DUMPPATHTAKEN();
return FALSE;
}
}
fDown = FALSE;
fMouseHookCalled = FALSE;
fKbdHookCalled = FALSE;
/*
* Lock the queue if it's currently unlocked.
*/
if (ptiCurrent->pq->ptiSysLock == NULL) {
CheckSysLock(3, ptiCurrent->pq, ptiCurrent);
ptiCurrent->pq->ptiSysLock = ptiCurrent;
ptiCurrent->pcti->CTIF_flags |= CTIF_SYSQUEUELOCKED;
}
/*
* Flag to tell if locker was removing messages. If not, then next time
* Get/PeekMessage is called, the input message list is scanned before the
* post msg list.
*
* Under Win3.1, this flag only gets modified for key and mouse messages.
* Since under NT ScanSysQueue() can be called to execute event messages,
* we make this check to be compatible.
*/
if (fsReason & QS_INPUT) {
if (fRemove) {
PATHTAKEN(2);
ptiCurrent->pq->QF_flags &= ~QF_LOCKNOREMOVE;
} else {
PATHTAKEN(4);
ptiCurrent->pq->QF_flags |= QF_LOCKNOREMOVE;
}
}
/*
* Return FALSE if the current thread is not the one that lock this queue.
*/
if (ptiCurrent->pq->ptiSysLock != ptiCurrent) {
PATHTAKEN(8);
DUMPPATHTAKEN();
return FALSE;
}
ptiEventWake = ptiKeyWake = ptiMouseWake = NULL;
#ifdef GENERIC_INPUT
ptiRawInputWake = NULL;
#endif
ThreadLockPti(ptiCurrent, ptiKeyWake, &tlptiKeyWake);
ThreadLockPti(ptiCurrent, ptiMouseWake, &tlptiMouseWake);
ThreadLockPti(ptiCurrent, ptiEventWake, &tlptiEventWake);
#ifdef GENERIC_INPUT
ThreadLockPti(ptiCurrent, ptiRawInputWake, &tlptiRawInputWake);
#endif
/*
* Initialize the thread lock structure here so we can unlock/lock in
* the main loop.
*/
pwnd = NULL;
ThreadLockWithPti(ptiCurrent, pwnd, &tlpwnd);
RestartScan:
CheckPtiSysPeek(2, ptiCurrent->pq, 0);
ptiCurrent->pq->idSysPeek = 0;
ContinueScan:
while (TRUE) {
ULONG_PTR idSysPeek;
DUMPSUBPATHTAKEN(pathTaken, 0xf0);
/*
* Store idSysPeek in a local which forces pq to be reloaded
* in case it changed during the xxx call (the compiler can
* evaluate the LValue at any time)
*/
idSysPeek = (ULONG_PTR)xxxGetNextSysMsg(ptiCurrent,
(PQMSG)ptiCurrent->pq->idSysPeek, &qmsg);
CheckPtiSysPeek(3, ptiCurrent->pq, idSysPeek);
ptiCurrent->pq->idSysPeek = idSysPeek;
if (ptiCurrent->pq->idSysPeek == 0) {
/*
* If we are only looking for event messages and we didn't
* find any, then clear the QS_EVENT bit
*/
if (fsReason == QS_EVENT)
ClearWakeBit(ptiCurrent, QS_EVENT, FALSE);
PATHTAKEN(0x10);
goto NoMessages;
}
/*
* pwnd should be locked for the duration of this routine.
* For most messages right out of GetNextSysMsg, this is
* NULL.
*/
ThreadUnlock(&tlpwnd);
pwnd = RevalidateHwnd(qmsg.msg.hwnd);
ThreadLockWithPti(ptiCurrent, pwnd, &tlpwnd);
/*
* See if this is an event message. If so, execute it regardless
* of message and window filters, but only if it is the first element
* of the input queue.
*/
if (qmsg.dwQEvent != 0) {
PTHREADINFO pti;
PATHTAKEN(0x20);
/*
* Most event messages can be executed out of order relative to
* its place in the queue. There are some examples were this is
* not allowed, and we check that here. For example, we would not
* want a keystate synchronization event to be processed before
* the keystrokes that came before it in the queue!
*
* We need to have most event messages be able to get processed
* out of order because apps can be filtering for message ranges
* that don't include input (like dde) - those scenarios still
* need to process events such as deactivate event messages even
* if there is input in the input queue.
*/
switch (qmsg.dwQEvent) {
case QEVENT_UPDATEKEYSTATE:
/*
* If the message is not the next message in the queue, don't
* process it.
*/
if (ptiCurrent->pq->idSysPeek !=
(ULONG_PTR)ptiCurrent->pq->mlInput.pqmsgRead) {
PATHTAKEN(0x40);
continue;
}
break;
}
/*
* If this event isn't for this thread, wake the thread it is
* for. A NULL qmsg.hti means that any thread can process
* the event.
*/
if (qmsg.pti != NULL && (pti = qmsg.pti) != ptiCurrent) {
/*
* If somehow this event message got into the wrong queue,
* then ignore it.
*/
UserAssert(pti->pq == ptiCurrent->pq);
if (pti->pq != ptiCurrent->pq) {
CleanEventMessage((PQMSG)ptiCurrent->pq->idSysPeek);
DelQEntry(&ptiCurrent->pq->mlInput,
(PQMSG)ptiCurrent->pq->idSysPeek);
PATHTAKEN(0x80);
goto RestartScan;
}
/*
* If ptiEventWake is already set, it means we've already
* found a thread to wake for event.
*/
if (ptiEventWake == NULL) {
ptiEventWake = pti;
ThreadLockExchangePti(ptiEventWake, &tlptiEventWake);
}
/*
* Clear idSysPeek so that the targeted thread
* can always get it. Look at the test at the
* start of this routine for more info.
*/
CheckPtiSysPeek(4, ptiCurrent->pq, 0);
ptiCurrent->pq->idSysPeek = 0;
PATHTAKEN(0x100);
goto NoMessages;
}
/*
* If this is called with PM_NOYIELD from a 16-bit app, skip
* processing any event that can generate activation messages. An
* example is printing from PageMaker 5.0. Bug #12662.
*/
if ((flags & PM_NOYIELD) && (ptiCurrent->TIF_flags & TIF_16BIT)) {
PATHTAKEN(0x200);
switch (qmsg.dwQEvent) {
/*
* The following events are safe to process if no yield
* is to occur.
*/
case QEVENT_UPDATEKEYSTATE:
case QEVENT_ASYNCSENDMSG:
break;
/*
* Skip all other events.
*/
default:
try {
ptiCurrent->pClientInfo->dwTIFlags = ptiCurrent->TIF_flags | TIF_DELAYEDEVENT;
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
goto ContinueScan;
}
ptiCurrent->TIF_flags |= TIF_DELAYEDEVENT;
PATHTAKEN(0x400);
goto ContinueScan;
}
}
/*
* Delete this before it gets processed so there are no
* recursion problems.
*/
DelQEntry(&ptiCurrent->pq->mlInput,
(PQMSG)ptiCurrent->pq->idSysPeek);
/*
* Clear idSysPeek before processing any events messages, because
* they may recurse and want to use idSysPeek.
*/
CheckPtiSysPeek(5, ptiCurrent->pq, 0);
ptiCurrent->pq->idSysPeek = 0;
xxxProcessEventMessage(ptiCurrent, &qmsg);
/*
* Restart the scan from the start so we start with 0 in
* pq->idSysPeek (since that message is now gone!).
*/
PATHTAKEN(0x800);
goto RestartScan;
}
/*
* If the reason we called was just to process event messages, don't
* enumerate any other mouse or key messages!
*/
if (fsReason == QS_EVENT) {
PATHTAKEN(0x1000);
continue;
}
switch (message = qmsg.msg.message) {
case WM_QUEUESYNC:
PATHTAKEN(0x2000);
/*
* This message is for CBT. Its parameters should already be
* set up correctly.
*/
wParam = 0;
lParam = qmsg.msg.lParam;
/*
* Check if this is intended for the current app. Use the mouse
* bit for WM_QUEUESYNC.
*/
if (pwnd != NULL && GETPTI(pwnd) != ptiCurrent) {
/*
* If this other app isn't going to read from this
* queue, then skip this message. This can happen with
* WM_QUEUESYNC if the app passed a window handle
* to the wrong queue. This isn't likely to happen in
* this case because WM_QUEUESYNCs come in while journalling,
* which has all threads sharing the same queue.
*/
if (GETPTI(pwnd)->pq != ptiCurrent->pq) {
PATHTAKEN(0x4000);
goto SkipMessage;
}
if (ptiMouseWake == NULL) {
ptiMouseWake = GETPTI(pwnd);
ThreadLockExchangePti(ptiMouseWake, &tlptiMouseWake);
}
PATHTAKEN(0x8000);
goto NoMessages;
}
if (!CheckMsgFilter(message, msgMinFilter, msgMaxFilter)) {
PATHTAKEN(0x10000);
goto NoMessages;
}
/*
* Eat the message.
*/
if (fRemove) {
xxxSkipSysMsg(ptiCurrent, &qmsg);
}
/*
* !!HARDWARE HOOK!! goes here.
*/
/*
* Return the message.
*/
PATHTAKEN(0x20000);
goto ReturnMessage;
break;
/*
* Mouse message or generic hardware messages
* Key messages are handled in case statements
* further down in this switch.
*/
default:
ReprocessMsg:
DUMPSUBPATHTAKEN(pathTaken, 0x40000);
PATHTAKEN(0x40000);
/*
* !!GENERIC HARDWARE MESSAGE!! support goes here.
*/
/*
* Take the mouse position out of the message.
*/
pt.x = (int)(short)LOWORD(qmsg.msg.lParam);
pt.y = (int)(short)HIWORD(qmsg.msg.lParam);
/*
* Assume we have a capture.
*/
part = HTCLIENT;
/*
* We have a special global we use for when we're full screen.
* All mouse input will go to this window.
*/
if (gspwndScreenCapture != NULL) {
/*
* Change the mouse coordinates to full screen.
*/
pwnd = gspwndScreenCapture;
lParam = MAKELONG((WORD)qmsg.msg.pt.x,
(WORD)qmsg.msg.pt.y);
PATHTAKEN(0x80000);
} else if ((pwnd = ptiCurrent->pq->spwndCapture) == NULL) {
PATHTAKEN(0x100000);
/*
* We don't have the capture. Figure out which window owns
* this message.
*
* NOTE: Use gptiRit and not ptiCurrent to get the desktop
* window because if ptiCurrent is the thread that created
* the main desktop, it's associated desktop is the logon
* desktop - don't want to hittest against the logon desktop
* while switched into the main desktop!
*/
pwndT = gptiRit->rpdesk->pDeskInfo->spwnd;
ThreadLockWithPti(ptiCurrent, pwndT, &tlpwndT);
hwnd = xxxWindowHitTest(pwndT, pt, &part, WHT_IGNOREDISABLED);
ThreadUnlock(&tlpwndT);
if ((pwnd = RevalidateHwnd(hwnd)) == NULL) {
pwnd = ptiCurrent->rpdesk->pDeskInfo->spwnd;
PATHTAKEN(0x200000);
if (pwnd == NULL) {
pwnd = gptiRit->rpdesk->pDeskInfo->spwnd;
}
}
if (part == HTCLIENT) {
/*
* Part of the client... normal mouse message.
* NO_CAP_CLIENT means "not captured, in client area
* of window".
*/
ptiCurrent->pq->codeCapture = NO_CAP_CLIENT;
PATHTAKEN(0x400000);
} else {
/*
* Not part of the client... must be an NCMOUSEMESSAGE.
* NO_CAP_SYS is a creative name by raor which means
* "not captured, in system area of window."
*/
ptiCurrent->pq->codeCapture = NO_CAP_SYS;
PATHTAKEN(0x800000);
}
}
/*
* We've reassigned pwnd, so lock it.
*/
ThreadLockExchange(pwnd, &tlpwnd);
if (fOtherApp = (GETPTI(pwnd) != ptiCurrent)) {
PATHTAKEN(0x1000000);
/*
* If this other app isn't going to read from this
* queue, then skip this message. This can happen if
* the RIT queues up a message thinking it goes to
* a particular hwnd, but then by the time GetMessage()
* is called for that thread, it doesn't go to that hwnd
* (like in the case of mouse messages, window rearrangement
* happens which changes which hwnd the mouse hits on).
*/
if (GETPTI(pwnd)->pq != ptiCurrent->pq) {
zzzSetCursor(SYSCUR(ARROW));
PATHTAKEN(0x2000000);
goto SkipMessage;
}
/*
* If we haven't already found a message that is intended
* for another app, remember that we have one.
*/
if (ptiMouseWake == NULL) {
ptiMouseWake = GETPTI(pwnd);
ThreadLockExchangePti(ptiMouseWake, &tlptiMouseWake);
PATHTAKEN(0x4000000);
}
}
/*
* Map mouse coordinates based on hit test area code.
*/
ptScreen = pt;
switch (ptiCurrent->pq->codeCapture) {
case CLIENT_CAPTURE:
case NO_CAP_CLIENT:
//Screen To Client
if (TestWF(pwnd, WEFLAYOUTRTL)) {
pt.x = pwnd->rcClient.right - pt.x;
} else {
pt.x -= pwnd->rcClient.left;
}
pt.y -= pwnd->rcClient.top;
PATHTAKEN2(2);
break;
case WINDOW_CAPTURE:
//Screen To Window
if (TestWF(pwnd, WEFLAYOUTRTL)) {
pt.x = pwnd->rcWindow.right - pt.x;
} else {
pt.x -= pwnd->rcWindow.left;
}
pt.y -= pwnd->rcWindow.top;
PATHTAKEN2(4);
break;
}
/*
* Track mouse moves when it moves to a different window or
* a different hit-test area for hot-tracking, tooltips,
* active window tracking and TrackMouseEvent.
* Mouse clicks reset tracking state too.
* Do it only if the message is for the current thread;
* otherwise, the hit test code (part) is not valid
* (it's always HTCLIENT; see xxxWindowHitTest2).
* Tracking will take place when that thread wakes up
* We also don't do it if this thread is not on pqCursor;
* that would be the case for a slow app that gets the
* input message when the mouse has already left its queue
*/
if (!fOtherApp && (ptiCurrent->pq == gpqCursor)) {
BOOL fNewpwndTrack = (ptiCurrent->rpdesk->spwndTrack != pwnd);
int htEx = FindNCHitEx(pwnd, part, pt);
if ((message != WM_MOUSEMOVE)
|| fNewpwndTrack
|| (ptiCurrent->rpdesk->htEx != htEx)) {
xxxTrackMouseMove(pwnd, htEx, message);
ValidateThreadLocks(NULL, ptiCurrent->ptl, (ULONG_PTR)&tlpwnd, TRUE);
}
/*
* Reset mouse hovering if needed.
*
*/
if (!fNewpwndTrack && (ptiCurrent->rpdesk->dwDTFlags & DF_TRACKMOUSEHOVER)) {
if ((message != WM_MOUSEMOVE)
|| !PtInRect(&ptiCurrent->rpdesk->rcMouseHover, ptScreen)) {
ResetMouseHover(ptiCurrent->rpdesk, ptScreen);
}
} else {
/*
* Hover must be canceled.
*/
UserAssert(!(ptiCurrent->rpdesk->dwDTFlags & DF_TRACKMOUSEHOVER));
}
} /* if (!fOtherApp.... */
/*
* Now see if it matches the window handle filter. If not,
* get the next message.
*/
if (!CheckPwndFilter(pwnd, pwndFilter)) {
PATHTAKEN(0x8000000);
continue;
}
/*
* See if we need to map to a double click.
*/
codeMouseDown = 0;
switch (message) {
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_XBUTTONDOWN:
if (TestCF(pwnd, CFDBLCLKS) ||
ptiCurrent->pq->codeCapture == NO_CAP_SYS ||
IsMenuStarted(ptiCurrent)) {
codeMouseDown++;
PATHTAKEN(0x10000000);
if (qmsg.msg.time <= ptiCurrent->pq->timeDblClk &&
(!gbClientDoubleClickSupport) &&
HW(pwnd) == ptiCurrent->pq->hwndDblClk &&
message == ptiCurrent->pq->msgDblClk &&
(message != WM_XBUTTONDOWN ||
GET_XBUTTON_WPARAM(qmsg.msg.wParam) == ptiCurrent->pq->xbtnDblClk)) {
RECT rcDblClk = {
ptiCurrent->pq->ptDblClk.x - SYSMET(CXDOUBLECLK) / 2,
ptiCurrent->pq->ptDblClk.y - SYSMET(CYDOUBLECLK) / 2,
ptiCurrent->pq->ptDblClk.x + SYSMET(CXDOUBLECLK) / 2,
ptiCurrent->pq->ptDblClk.y + SYSMET(CYDOUBLECLK) / 2
};
if (PtInRect(&rcDblClk, qmsg.msg.pt)) {
message += (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
codeMouseDown++;
PATHTAKEN(0x20000000);
}
}
}
// FALL THROUGH!!!
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
case WM_XBUTTONUP:
/*
* Note that the mouse button went up or down if we were
* in menu status mode of alt-key down
*/
PATHTAKEN(0x40000000);
if (ptiCurrent->pq->QF_flags & QF_FMENUSTATUS) {
ptiCurrent->pq->QF_flags |= QF_FMENUSTATUSBREAK;
PATHTAKEN(0x80000000);
}
}
/*
* Map message number based on hit test area code.
*/
if (ptiCurrent->pq->codeCapture == NO_CAP_SYS) {
message += (UINT)(WM_NCMOUSEMOVE - WM_MOUSEMOVE);
wParam = (UINT)part;
PATHTAKEN2(1);
}
/*
* Message number has been mapped: see if it fits the filter.
* If not, get the next message.
*/
if (!CheckMsgFilter(message, msgMinFilter, msgMaxFilter)) {
PATHTAKEN2(8);
continue;
}
/*
* If message is for another app but it fits our filter, then
* we should stop looking for messages: this will ensure that
* we don't keep looking and find and process a message that
* occured later than the one that should be processed by the
* other guy.
*/
if (fOtherApp) {
PATHTAKEN2(0x10);
goto NoMessages;
}
/*
* If we're doing full drag, the mouse messages should go to
* the xxxMoveSize PeekMessage loop. So we get the next message.
* This can happen when an application does a PeekMessage in
* response to a message sent inside the movesize dragging loop.
* This causes the dragging loop to not get the WM_LBUTTONUP
* message and dragging continues after the button is up
* (fix for Micrografx Draw). -johannec
*/
if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST &&
ptiCurrent->TIF_flags & TIF_MOVESIZETRACKING) {
PATHTAKEN2(0x20);
continue;
}
if (ptiCurrent->TIF_flags & TIF_MSGPOSCHANGED) {
ptiCurrent->TIF_flags &= ~TIF_MSGPOSCHANGED;
xxxWindowEvent(EVENT_OBJECT_LOCATIONCHANGE, NULL,
OBJID_CURSOR, INDEXID_CONTAINER, TRUE);
ValidateThreadLocks(NULL, ptiCurrent->ptl, (ULONG_PTR)&tlpwnd, TRUE);
}
/*
* Let us call the mouse hook to find out if this click is
* permitted by it.
*
* We want to inform the mouse hook before we test for
* HTNOWHERE and HTERROR; Otherwise, the mouse hook won't
* get these messages (sankar 12/10/91).
*/
if (IsHooked(ptiCurrent, WHF_MOUSE)) {
fMouseHookCalled = TRUE;
mhs.pt = qmsg.msg.pt;
mhs.hwnd = HW(pwnd);
mhs.wHitTestCode = (UINT)part;
mhs.dwExtraInfo = qmsg.ExtraInfo;
UserAssert(LOWORD(qmsg.msg.wParam) == 0);
mhs.mouseData = (DWORD)qmsg.msg.wParam;
if (xxxCallMouseHook(message, &mhs, fRemove)) {
/*
* Not allowed by mouse hook; so skip it.
*/
PATHTAKEN2(0x40);
goto SkipMessage;
}
PATHTAKEN2(0x80);
ValidateThreadLocks(NULL, ptiCurrent->ptl, (ULONG_PTR)&tlpwnd, TRUE);
}
/*
* If a HTERROR or HTNOWHERE occured, send the window the
* WM_SETCURSOR message so it can beep or whatever. Then skip
* the message and try the next one.
*/
switch (part) {
case HTERROR:
case HTNOWHERE:
/*
* Now set the cursor shape.
*/
xxxSendMessage(pwnd, WM_SETCURSOR, (WPARAM)HW(pwnd),
MAKELONG(part, qmsg.msg.message));
/*
* Skip the message.
*/
PATHTAKEN2(0x100);
goto SkipMessage;
break;
}
if (fRemove) {
PATHTAKEN2(0x200);
/*
* Since the processing of a down click may cause the next
* message to be interpreted as a double click, we only want
* to do the double click setup if we're actually going to
* remove the message. Otherwise, the next time we read the
* same message it would be interpreted as a double click.
*/
switch (codeMouseDown) {
case 1:
/*
* Down clock: set up for later possible double click.
*/
ptiCurrent->pq->msgDblClk = qmsg.msg.message;
/*
* Note that even if the following assertion were not true,
* we could still put bogus data in ptiCurrent->pq->xbtnDblClk
* when the message is not WM_XBUTTONDOWN, since when we check
* for dblclick we compare the message number before the xbtnDblClk.
*/
UserAssert(qmsg.msg.message == WM_XBUTTONDOWN || GET_XBUTTON_WPARAM(qmsg.msg.wParam) == 0);
ptiCurrent->pq->xbtnDblClk = GET_XBUTTON_WPARAM(qmsg.msg.wParam);
ptiCurrent->pq->timeDblClk = qmsg.msg.time + gdtDblClk;
ptiCurrent->pq->hwndDblClk = HW(pwnd);
ptiCurrent->pq->ptDblClk = qmsg.msg.pt;
PATHTAKEN2(0x400);
break;
case 2:
/*
* Double click: finish processing.
*/
ptiCurrent->pq->timeDblClk = 0L;
PATHTAKEN2(0x800);
break;
default:
PATHTAKEN2(0x1000);
break;
}
/*
* Set mouse cursor and allow app to activate window
* only if we're removing the message.
*/
switch (xxxMouseActivate(ptiCurrent, pwnd,
qmsg.msg.message, qmsg.msg.wParam, &qmsg.msg.pt, part)) {
SkipMessage:
case MA_SKIP:
DUMPSUBPATHTAKEN(pathTaken2, 0x2000);
PATHTAKEN2(0x2000);
xxxSkipSysMsg(ptiCurrent, &qmsg);
/*
* Inform the CBT hook that we skipped a mouse click.
*/
if (fMouseHookCalled) {
if (IsHooked(ptiCurrent, WHF_CBT)) {
xxxCallHook(HCBT_CLICKSKIPPED, message,
(LPARAM)&mhs, WH_CBT);
PATHTAKEN2(0x4000);
}
fMouseHookCalled = FALSE;
}
/*
* Inform the CBT hook that we skipped a key
*/
if (fKbdHookCalled) {
if (IsHooked(ptiCurrent, WHF_CBT)) {
xxxCallHook(HCBT_KEYSKIPPED, wParam, lParam,
WH_CBT);
PATHTAKEN2(0x8000);
}
fKbdHookCalled = FALSE;
}
/*
* If we aren't removing messages, don't reset idSysPeek
* otherwise we will go into an infinite loop if
* the keyboard hook says to ignore the message.
* (bobgu 4/7/87).
*/
if (!fRemove) {
PATHTAKEN2(0x10000);
goto ContinueScan;
} else {
PATHTAKEN2(0x20000);
goto RestartScan;
}
break;
case MA_REHITTEST:
/*
* Reprocess the message.
*/
PATHTAKEN2(0x40000);
goto ReprocessMsg;
}
}
/*
* Eat the message from the input queue (and set the keystate
* table).
*/
PATHTAKEN2(0x80000);
if (fRemove) {
xxxSkipSysMsg(ptiCurrent, &qmsg);
}
if (fRemove && fMouseHookCalled && IsHooked(ptiCurrent, WHF_CBT)) {
xxxCallHook(HCBT_CLICKSKIPPED, message,
(LPARAM)&mhs, WH_CBT);
}
fMouseHookCalled = FALSE;
lParam = MAKELONG((short)pt.x, (short)pt.y);
/*
* Calculate virtual key state bitmask for wParam.
*/
if (message >= WM_MOUSEFIRST) {
/*
* This is a USER mouse message. Calculate the bit mask for the
* virtual key state.
*/
wParam = GetMouseKeyFlags(ptiCurrent->pq);
PATHTAKEN2(0x100000);
}
if ( (WM_NCXBUTTONFIRST <= message && message <= WM_NCXBUTTONLAST) ||
(WM_XBUTTONFIRST <= message && message <= WM_XBUTTONLAST)) {
/*
* The hiword of wParam is assigned the xbutton number when
* the message is queued.
*/
UserAssert(LOWORD(qmsg.msg.wParam) == 0);
UserAssert(HIWORD(wParam) == 0);
wParam |= qmsg.msg.wParam;
}
PATHTAKEN2(0x200000);
/*
* If this app has a modeles menu bar,
* then the menu code should get the first shot at messages on the menu
* Note that this assumes that xxxHandleMenuMessages
* doens't need any of the stuff set after ReturnMessage
*/
if ((part == HTMENU)
&& fRemove
&& (ptiCurrent->pMenuState != NULL)
&& ptiCurrent->pMenuState->fModelessMenu
&& (ptiCurrent->pMenuState->pGlobalPopupMenu != NULL)
&& (ptiCurrent->pMenuState->pGlobalPopupMenu->fIsMenuBar)) {
if (xxxCallHandleMenuMessages(ptiCurrent->pMenuState, pwnd, message, wParam, lParam)) {
goto RestartScan;
}
}
goto ReturnMessage;
break;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
fDown = TRUE;
/*
* If we are sending keyboard input to an app that has been
* spinning then boost it back up. If we don't you use spinning
* apps like Write or Project and do two builds in the
* background. Note the app will also be unboosted again shortly
* after you stop typing by the old logic. #11188
*/
if (ptiCurrent->TIF_flags & TIF_SPINNING) {
if (!NT_SUCCESS(CheckProcessForeground(ptiCurrent))) {
goto NoMessages;
}
}
/*
* Apps doing journal playback sometimes put trash in the hiword
* of wParam... zero it out here.
*/
wParam = qmsg.msg.wParam & 0xFF;
/*
* Clear QF_FMENUSTATUS if a key other than Alt it hit
* since this means the break of the Alt wouldn't be a
* menu key anymore.
*/
if (wParam != VK_MENU)
ptiCurrent->pq->QF_flags &= ~(QF_FMENUSTATUS|QF_FMENUSTATUSBREAK);
/*
* Check for keyboard language toggle. Build the key state
* here for use during key up processing (where the layout
* switching takes place. This code is skipped if layout
* switching via the keyboard is disabled.
*/
if (gLangToggle[0].bVkey && (gLangToggleKeyState < KLT_NONE)) {
DWORD i;
BYTE scancode = LOBYTE(HIWORD(qmsg.msg.lParam));
BYTE vkey = LOBYTE(qmsg.msg.wParam);
for (i = 0; i < LANGTOGGLEKEYS_SIZE; i++) {
if (gLangToggle[i].bScan) {
if (gLangToggle[i].bScan == scancode) {
gLangToggleKeyState |= gLangToggle[i].iBitPosition;
break;
}
} else {
if (gLangToggle[i].bVkey == vkey) {
gLangToggleKeyState |= gLangToggle[i].iBitPosition;
break;
}
}
}
if (i == LANGTOGGLEKEYS_SIZE) {
gLangToggleKeyState = KLT_NONE; // not a language toggle combination
}
}
/*
* Check if it is the PrintScrn key.
*/
fAlt = TestKeyStateDown(ptiCurrent->pq, VK_MENU);
if (wParam == VK_SNAPSHOT &&
((fAlt && !(ptiCurrent->fsReserveKeys & CONSOLE_ALTPRTSC)) ||
(!fAlt && !(ptiCurrent->fsReserveKeys & CONSOLE_PRTSC)))) {
/*
* Remove this message from the input queue.
*/
PATHTAKEN2(0x400000);
xxxSkipSysMsg(ptiCurrent, &qmsg);
/*
* PRINTSCREEN -> Snap the whole screen.
* ALT-PRINTSCREEN -> Snap the current window.
*/
pwndT = ptiCurrent->pq->spwndActive;
/*
* check also the scan code to see if we got here
* through keybd_event(VK_SNAPSHOT, ...
* the scan code is in lParam bits 16-23
*/
if (!fAlt && ((qmsg.msg.lParam & 0x00FF0000) != 0x00010000)) {
pwndT = ptiCurrent->rpdesk->pDeskInfo->spwnd;
}
if (pwndT != NULL) {
ThreadLockAlwaysWithPti(ptiCurrent, pwndT, &tlpwndT);
xxxSnapWindow(pwndT);
ThreadUnlock(&tlpwndT);
}
PATHTAKEN2(0x800000);
goto RestartScan;
}
/*
* Check for hot keys being hit if any are defined.
*/
if (gcHotKey != 0 && (!gfEnableHexNumpad || (gfInNumpadHexInput & NUMPAD_HEXMODE_HL) == 0)) {
UINT key;
key = (UINT)wParam;
if (TestKeyStateDown(ptiCurrent->pq, VK_MENU))
key |= 0x0400;
if (TestKeyStateDown(ptiCurrent->pq, VK_CONTROL))
key |= 0x0200;
if (TestKeyStateDown(ptiCurrent->pq, VK_SHIFT))
key |= 0x0100;
pwndT = HotKeyToWindow(key);
if (pwndT != NULL) {
/*
* VK_PACKET shouldn't be a hot key.
*/
UserAssert((key & 0xff) != VK_PACKET);
_PostMessage(ptiCurrent->pq->spwndActive, WM_SYSCOMMAND,
(WPARAM)SC_HOTKEY, (LPARAM)HWq(pwndT));
/*
* Remove this message from the input queue.
*/
xxxSkipSysMsg(ptiCurrent, &qmsg);
PATHTAKEN2(0x1000000);
goto RestartScan;
}
PATHTAKEN2(0x2000000);
}
#if DBG
else if (gfInNumpadHexInput & NUMPAD_HEXMODE_HL) {
RIPMSG0(RIP_VERBOSE, "xxxScanSysQueue: gfInNumpadHexInput is true, so we skipped hotkey.");
}
#endif
if (wParam == VK_PACKET) {
/*
* Save the character in thread's cache for TranslateMessage
*/
ptiCurrent->wchInjected = HIWORD(qmsg.msg.wParam);
qmsg.msg.wParam = wParam;
UserAssert(qmsg.msg.wParam == VK_PACKET);
}
/*
* Fall through.
*/
case WM_SYSKEYUP:
case WM_KEYUP:
wParam = qmsg.msg.wParam & 0xFF;
if (wParam == VK_PACKET) {
qmsg.msg.wParam = wParam;
}
/*
* Special processing for thai locale toggle using grave accent key
* Remove key message irrespective of fDown otherwise it will
* generate WM_CHAR message
*/
if (gbGraveKeyToggle &&
//
// In case of mstsc.exe, should not eat Grave Accent key message.
// TS client must send Grave Accent key message to server side.
//
!(GetAppImeCompatFlags(NULL) & IMECOMPAT_HYDRACLIENT) &&
LOBYTE(HIWORD(qmsg.msg.lParam)) == SCANCODE_THAI_LAYOUT_TOGGLE &&
fRemove &&
!TestKeyStateDown(ptiCurrent->pq, VK_SHIFT) &&
!TestKeyStateDown(ptiCurrent->pq, VK_MENU) &&
!TestKeyStateDown(ptiCurrent->pq, VK_CONTROL) &&
!TestKeyStateDown(ptiCurrent->pq, VK_LWIN) &&
!TestKeyStateDown(ptiCurrent->pq, VK_RWIN)){
if ((pwnd = ptiCurrent->pq->spwndFocus) == NULL){
pwnd = ptiCurrent->pq->spwndActive;
}
/*
* Post message only on WM_KEYUP
*/
if (!fDown && pwnd){
PTHREADINFO ptiToggle = GETPTI(pwnd);
PKL pkl = ptiToggle->spklActive;
if (pkl && (pkl = HKLtoPKL(ptiToggle, (HKL)HKL_NEXT))) {
_PostMessage(
pwnd,
WM_INPUTLANGCHANGEREQUEST,
(WPARAM)(((pkl->dwFontSigs & gSystemFS) ? INPUTLANGCHANGE_SYSCHARSET : 0) | INPUTLANGCHANGE_FORWARD),
(LPARAM)pkl->hkl
);
}
}
/*
* eat Accent Grave's key msgs
*/
xxxSkipSysMsg(ptiCurrent, &qmsg);
goto RestartScan;
}
{
/*
* Process keyboard toggle keys only if this is
* a break event and fRemove == TRUE. Some apps,
* for instance Word 95, call PeekMessage with
* PM_NOREMOVE followed by a call with PM_REMOVE.
* We only want to process this once. Skip all
* of this is layout switching via the keyboard
* is disabled.
*/
#ifdef CUAS_ENABLE
BOOL bMSCTF;
try {
bMSCTF = ((ptiCurrent->pClientInfo->CI_flags & CI_CUAS_MSCTF_RUNNING) != 0);
} except (W32ExceptionHandler(TRUE, RIP_WARNING)) {
goto NoMessages;
}
#endif // CUAS_ENABLE
if (
#ifdef CUAS_ENABLE
!(bMSCTF) &&
#endif // CUAS_ENABLE
!fDown && fRemove && gLangToggle[0].bVkey) {
BOOL bDropToggle = FALSE;
DWORD dwDirection = 0;
PKL pkl;
PTHREADINFO ptiToggle;
BOOL bArabicSwitchPresent = FALSE;
LCID lcid;
ZwQueryDefaultLocale(FALSE, &lcid);
pwnd = ptiCurrent->pq->spwndFocus;
if (pwnd == NULL) {
pwnd = ptiCurrent->pq->spwndActive;
if (!pwnd) {
goto NoLayoutSwitch;
}
}
ptiToggle = GETPTI(pwnd);
pkl = ptiToggle->spklActive;
UserAssert(ptiToggle->spklActive != NULL);
/*
* Check for Arabic toggle context
*/
if (gLangToggleKeyState < KLT_NONE && PRIMARYLANGID(lcid) == LANG_ARABIC){
PKL pkl_next = HKLtoPKL (ptiToggle, (HKL)HKL_NEXT);
/*
* test if there are exactly two pkl's and at least one
* of them is arabic
*/
if (pkl && pkl_next &&
pkl->hkl != pkl_next->hkl && pkl_next == HKLtoPKL(ptiToggle, (HKL)HKL_PREV) &&
(PRIMARYLANGID(HandleToUlong(pkl->hkl)) == LANG_ARABIC || PRIMARYLANGID(HandleToUlong(pkl_next->hkl)) == LANG_ARABIC)){
bArabicSwitchPresent = TRUE;
}
}
/*
* NT has always had Alt LShift going forward (down) the list,
* and Alt RShift going backwards. Windows '95 is different.
*/
switch (gLangToggleKeyState) {
case KLT_ALTLEFTSHIFT:
bDropToggle = TRUE;
dwDirection = INPUTLANGCHANGE_FORWARD;
if (!bArabicSwitchPresent || PRIMARYLANGID(HandleToUlong(pkl->hkl)) == LANG_ARABIC){
pkl = HKLtoPKL(ptiToggle, (HKL)HKL_NEXT);
}
break;
case KLT_ALTRIGHTSHIFT:
bDropToggle = TRUE;
dwDirection = INPUTLANGCHANGE_BACKWARD;
if (!bArabicSwitchPresent || PRIMARYLANGID(HandleToUlong(pkl->hkl)) != LANG_ARABIC){
pkl = HKLtoPKL(ptiToggle, (HKL)HKL_PREV);
}
break;
case KLT_ALTBOTHSHIFTS:
pkl = gspklBaseLayout;
break;
default:
goto NoLayoutSwitch;
break;
}
if (pkl == NULL) {
pkl = GETPTI(pwnd)->spklActive;
}
/*
* If these two are not NULL, then winlogon hasn't loaded
* any keyboard layouts yet: but nobody should be getting
* input yet, so Assert but check pkl anyway. #99321
*/
UserAssert(gspklBaseLayout != NULL);
UserAssert(pkl);
if (pkl) {
/*
* Not a very satisfactory window to post to, but it's hard
* to figure out a better window. Just do as Memphis does.
* Note: The following went up too high, bypassing Word
* when using wordmail - IanJa bug #64744.
* if ((pwndTop = GetTopLevelWindow(pwnd)) != NULL) {
* pwnd = pwndTop;
* }
*/
_PostMessage(pwnd, WM_INPUTLANGCHANGEREQUEST,
(DWORD)(((pkl->dwFontSigs & gSystemFS) ? INPUTLANGCHANGE_SYSCHARSET : 0) | dwDirection),
(LPARAM)pkl->hkl);
}
NoLayoutSwitch:
if (bDropToggle) {
/*
* Clear this key from the key state so that multiple key
* presses will work (i.e., Alt+Shft+Shft). We don't do
* this when both shift keys are pressed simultaneously to
* avoid two activates.
*/
DWORD i;
BYTE scancode = LOBYTE(HIWORD(qmsg.msg.lParam));
BYTE vkey = LOBYTE(qmsg.msg.wParam);
for (i = 0; i < LANGTOGGLEKEYS_SIZE; i++) {
if (gLangToggle[i].bScan) {
if (gLangToggle[i].bScan == scancode) {
gLangToggleKeyState &= ~(gLangToggle[i].iBitPosition);
}
} else {
if (gLangToggle[i].bVkey == vkey) {
gLangToggleKeyState &= ~(gLangToggle[i].iBitPosition);
}
}
}
} else {
gLangToggleKeyState = 0;
}
}
}
/*
* Convert F10 to syskey for new apps.
*/
if (wParam == VK_F10)
message |= (WM_SYSKEYDOWN - WM_KEYDOWN);
if (TestKeyStateDown(ptiCurrent->pq, VK_CONTROL) &&
wParam == VK_ESCAPE) {
message |= (WM_SYSKEYDOWN - WM_KEYDOWN);
}
/*
* Clear the 'simulated keystroke' bit for all applications except
* console so it can pass it to 16-bit vdms. VDM keyboards need to
* distinguish between AltGr (where Ctrl keystroke is simulated)
* and a real Ctrl+Alt. Check TIF_CSRSSTHREAD for the console
* input thread because it lives in the server. This is a cheap
* way to check for it.
*/
if (!(ptiCurrent->TIF_flags & TIF_CSRSSTHREAD))
qmsg.msg.lParam &= ~FAKE_KEYSTROKE;
PATHTAKEN2(0x4000000);
/*
* Fall through.
*/
/*
* Some apps want to be able to feed WM_CHAR messages through
* the playback hook. Why? Because they want to be able to
* convert a string of characters info key messages
* and feed them to themselves or other apps. Unfortunately,
* there are no machine independent virtual key codes for
* some characters (for example '$'), so they need to send
* those through as WM_CHARs. (6/10/87).
*/
case WM_CHAR:
wParam = qmsg.msg.wParam & 0xFF;
/*
* Assign the input to the focus window. If there is no focus
* window, assign it to the active window as a SYS message.
*/
pwnd = ptiCurrent->pq->spwndFocus;
if (ptiCurrent->pq->spwndFocus == NULL) {
if ((pwnd = ptiCurrent->pq->spwndActive) != NULL) {
if (CheckMsgFilter(message, WM_KEYDOWN, WM_DEADCHAR)) {
message += (WM_SYSKEYDOWN - WM_KEYDOWN);
PATHTAKEN2(0x8000000);
}
} else {
PATHTAKEN2(0x10000000);
goto SkipMessage;
}
}
/*
* If there is no active window or focus window, eat this
* message.
*/
if (pwnd == NULL) {
PATHTAKEN2(0x20000000);
goto SkipMessage;
}
ThreadLockExchangeAlways(pwnd, &tlpwnd);
/*
* Check if this is intended for the current app.
*/
if (fOtherApp = (GETPTI(pwnd) != ptiCurrent)) {
PWND pwndModalLoop;
/*
* If this other app isn't going to read from this
* queue, then skip this message. This can happen if
* the RIT queues up a message thinking it goes to
* a particular hwnd, but then by the time GetMessage()
* is called for that thread, it doesn't go to that hwnd
* (like in the case of mouse messages, window rearrangement
* happens which changes which hwnd the mouse hits on).
*/
if (GETPTI(pwnd)->pq != ptiCurrent->pq) {
PATHTAKEN2(0x40000000);
goto SkipMessage;
}
/*
* If the current thread is in the menu or movesize loop
* then we need to give it the input
*/
if (IsInsideMenuLoop(ptiCurrent)) {
pwndModalLoop = ptiCurrent->pMenuState->pGlobalPopupMenu->spwndNotify;
} else if (ptiCurrent->pmsd != NULL) {
pwndModalLoop = ptiCurrent->pmsd->spwnd;
RIPMSG0(RIP_WARNING, "xxxScanSysQueue: returning key to movesize loop");
} else {
pwndModalLoop = NULL;
}
/*
* If we're switching windows, lock the new one
*/
if (pwndModalLoop != NULL) {
pwnd = pwndModalLoop;
fOtherApp = (GETPTI(pwnd) != ptiCurrent);
ThreadLockExchangeAlways(pwnd, &tlpwnd);
PATHTAKEN2(0x80000000);
}
/*
* If not for us, then remember who it is for.
*/
if (ptiKeyWake == NULL) {
PATHTAKEN3(1);
ptiKeyWake = GETPTI(pwnd);
ThreadLockExchangePti(ptiKeyWake, &tlptiKeyWake);
}
}
/*
* See if this thing matches our filter.
*/
if (!CheckMsgFilter(message, msgMinFilter, msgMaxFilter) ||
!CheckPwndFilter(pwnd, pwndFilter)) {
PATHTAKEN3(2);
continue;
}
/*
* This message matches our filter. If it is not for us then
* stop searching to make sure the real owner processes this
* message first.
*/
if (fOtherApp) {
PATHTAKEN3(4);
goto NoMessages;
}
/*
* Generate some special messages if we are removing and we are
* not inside the menu loop.
*/
if (fRemove && !IsInsideMenuLoop(ptiCurrent)) {
/*
* Generate a WM_CONTEXTMENU for the VK_APPS key
*/
if ((wParam == VK_APPS) && (message == WM_KEYUP)) {
_PostMessage(pwnd, WM_CONTEXTMENU, (WPARAM)PtoH(pwnd), KEYBOARD_MENU);
}
/*
* If this is a WM_KEYDOWN message for F1 key then we must generate
* the WM_KEYF1 message.
*/
if ((wParam == VK_F1) && (message == WM_KEYDOWN)) {
_PostMessage(pwnd, WM_KEYF1, 0, 0);
}
}
/*
* If one Shift key is released while the other Shift key is held
* down, this keystroke is normally skipped, presumably to prevent
* applications from thinking that the shift condition no longer
* applies.
*/
if (wParam == VK_SHIFT) {
BYTE vkHanded, vkOtherHand;
if (qmsg.msg.lParam & EXTENDED_BIT) {
vkHanded = VK_RSHIFT;
} else {
vkHanded = VK_LSHIFT;
}
vkOtherHand = vkHanded ^ 1;
if (!fDown && TestKeyStateDown(ptiCurrent->pq, vkOtherHand)) {
/*
* Unlike normal apps, Console MUST be sent a Shift break
* even when the other Shift key is still down, since it
* has to be passed on to VDM, which maintains it's own
* state. Check TIF_CSRSSTHREAD for the console input
* thread because it lives in the server. This is a cheap
* way to check for it.
*/
if ((ptiCurrent->TIF_flags & TIF_CSRSSTHREAD) == 0) {
/*
* We ignore this key event, so we must update
* it's key state whether fRemove is TRUE or not.
* (ignoring an key event is same as removing it)
*/
qmsg.msg.wParam = vkHanded;
xxxSkipSysMsg(ptiCurrent, &qmsg);
PATHTAKEN3(8);
goto RestartScan;
}
PATHTAKEN3(0x10);
}
}
/*
* Get the previous up/down state of the key here since
* SkipSysMsg() sets the key state table and destroys
* the previous state info.
*/
fPrevDown = FALSE;
if (TestKeyStateDown(ptiCurrent->pq, wParam))
fPrevDown = TRUE;
/*
* Eat the message from the input queue and set the keystate
* table.
*/
PATHTAKEN3(0x20);
if (fRemove) {
xxxSkipSysMsg(ptiCurrent, &qmsg);
}
/*
* This gets us the LOWORD of lParam, the repeat count,
* the bit in the hi byte indicating whether this is an extended
* key, and the scan code. We also need to re-get the wParam in
* case xxxSkipSysMsg called a hook which modified the message.
* AfterDark's password protection does this.
*/
lParam = qmsg.msg.lParam;
wParam = qmsg.msg.wParam;
/*
* Indicate if it was previously down.
*/
if (fPrevDown)
lParam |= 0x40000000; // KF_REPEAT
/*
* Set the transition bit.
*/
switch (message) {
case WM_KEYUP:
case WM_SYSKEYUP:
lParam |= 0x80000000; // KF_UP
break;
}
/*
* Set the alt key down bit.
*/
if (TestKeyStateDown(ptiCurrent->pq, VK_MENU)) {
lParam |= 0x20000000; // KF_ALTDOWN
}
/*
* Set the menu state flag.
*/
if (IsMenuStarted(ptiCurrent)) {
lParam |= 0x10000000; // KF_MENUMODE
}
/*
* Set the dialog state flag.
*/
if (ptiCurrent->pq->QF_flags & QF_DIALOGACTIVE) {
lParam |= 0x08000000; // KF_DLGMODE
}
/*
* 0x80000000 is set if up, clear if down
* 0x40000000 is previous up/down state of key
* 0x20000000 is whether the alt key is down
* 0x10000000 is whether currently in menumode.
* 0x08000000 is whether in dialog mode
* 0x04000000 is not used
* 0x02000000 is not used
* 0x01000000 is whether this is an extended keyboard key
*
* Low word is repeat count, low byte hiword is scan code,
* hi byte hiword is all these bits.
*/
/*
* Callback the client IME before calling the keyboard hook.
* If the vkey is one of the IME hotkeys, the vkey will not
* be passed to the keyboard hook.
* If IME needs this vkey, VK_PROCESSKEY will be put into the
* application queue instead of real vkey.
*/
UserAssert(ptiCurrent != NULL);
if (gpImeHotKeyListHeader != NULL &&
fRemove &&
!IsMenuStarted(ptiCurrent) &&
!(ptiCurrent->TIF_flags & TIF_DISABLEIME) &&
pwnd != NULL) {
WPARAM wParamTemp = wParam;
if (wParam == VK_PACKET) {
wParamTemp = MAKEWPARAM(wParam, ptiCurrent->wchInjected);
}
/*
* xxxImmProcessKey also checks the registered IME hotkeys.
*/
dwImmRet = xxxImmProcessKey( ptiCurrent->pq,
pwnd,
message,
wParamTemp,
lParam);
if ( dwImmRet & (IPHK_HOTKEY | IPHK_SKIPTHISKEY) ) {
dwImmRet = 0;
goto SkipMessage;
}
}
/*
* If we are removing the message, call the keyboard hook
* with HC_ACTION, otherwise call the hook with HC_NOREMOVE
* to let it know that the message is not being removed.
*/
if (IsHooked(ptiCurrent, WHF_KEYBOARD)) {
fKbdHookCalled = TRUE;
if (xxxCallHook(fRemove ? HC_ACTION : HC_NOREMOVE,
wParam, lParam, WH_KEYBOARD)) {
PATHTAKEN3(0x40);
goto SkipMessage;
}
}
if (fKbdHookCalled && fRemove && IsHooked(ptiCurrent, WHF_CBT)) {
xxxCallHook(HCBT_KEYSKIPPED, wParam, lParam, WH_CBT);
PATHTAKEN3(0x80);
}
fKbdHookCalled = FALSE;
PATHTAKEN3(0x100);
goto ReturnMessage;
case WM_MOUSEWHEEL:
/*
* If we are sending keyboard input to an app that has been
* spinning then boost it back up. If we don't you use spinning
* apps like Write or Project and do two builds in the
* background. Note the app will also be unboosted again shortly
* after you stop typing by the old logic. #11188
*/
if (ptiCurrent->TIF_flags & TIF_SPINNING) {
if (!NT_SUCCESS(CheckProcessForeground(ptiCurrent))) {
goto NoMessages;
}
}
/*
* Assign the input to the focus window. If there is no focus
* window, or we are in a menu loop, eat this message.
*/
pwnd = ptiCurrent->pq->spwndFocus;
if (pwnd == NULL || IsInsideMenuLoop(ptiCurrent)) {
PATHTAKEN2(0x20000000);
goto SkipMessage;
}
ThreadLockExchangeAlways(pwnd, &tlpwnd);
/*
* Check if this is intended for the current app.
*/
if (fOtherApp = (GETPTI(pwnd) != ptiCurrent)) {
/*
* If this other app isn't going to read from this
* queue, then skip this message. This can happen if
* the RIT queues up a message thinking it goes to
* a particular hwnd, but then by the time GetMessage()
* is called for that thread, it doesn't go to that hwnd
* (like in the case of mouse messages, window rearrangement
* happens which changes which hwnd the mouse hits on).
*/
if (GETPTI(pwnd)->pq != ptiCurrent->pq) {
PATHTAKEN2(0x40000000);
goto SkipMessage;
}
/*
* If not for us, then remember who it is for.
*/
if (ptiKeyWake == NULL) {
PATHTAKEN3(1);
ptiKeyWake = GETPTI(pwnd);
ThreadLockExchangePti(ptiKeyWake, &tlptiKeyWake);
}
}
/*
* See if this thing matches our filter.
* NOTE: We need to check whether the caller is filtering
* for all mouse messages - if so, we assume the caller
* wants mouse wheel messages too.
*/
if ( !CheckMsgFilter(WM_MOUSEWHEEL, msgMinFilter, msgMaxFilter) ||
!CheckPwndFilter(pwnd, pwndFilter)) {
PATHTAKEN3(2);
continue;
}
/*
* This message matches our filter. If it is not for us then
* stop searching to make sure the real owner processes this
* message first.
*/
if (fOtherApp) {
PATHTAKEN3(4);
goto NoMessages;
}
/*
* Eat the message from the input queue and set the keystate
* table.
*/
PATHTAKEN3(0x20);
if (fRemove) {
xxxSkipSysMsg(ptiCurrent, &qmsg);
}
wParam = GetMouseKeyFlags(ptiCurrent->pq);
UserAssert(LOWORD(qmsg.msg.wParam) == 0);
UserAssert(HIWORD(wParam) == 0);
wParam |= qmsg.msg.wParam;
lParam = qmsg.msg.lParam;
/*
* If we are removing the message, call the mouse hook
* with HC_ACTION, otherwise call the hook with HC_NOREM
* to let it know that the message is not being removed.
*/
if (IsHooked(ptiCurrent, WHF_MOUSE)) {
fMouseHookCalled = TRUE;
mhs.pt = qmsg.msg.pt;
mhs.hwnd = HW(pwnd);
mhs.wHitTestCode = HTNOWHERE;
mhs.dwExtraInfo = qmsg.ExtraInfo;
mhs.mouseData = (DWORD)qmsg.msg.wParam;
if (xxxCallMouseHook(message, &mhs, fRemove)) {
/*
* Not allowed by mouse hook; so skip it.
*/
PATHTAKEN3(0x40);
goto SkipMessage;
}
}
if (fMouseHookCalled && fRemove && IsHooked(ptiCurrent, WHF_CBT)) {
/*
* CONSIDER: Add new HCBT_ constant for the mouse wheel?
*/
xxxCallHook(HCBT_CLICKSKIPPED, message, (LPARAM)&mhs, WH_CBT);
PATHTAKEN3(0x80);
}
fMouseHookCalled = FALSE;
PATHTAKEN3(0x100);
goto ReturnMessage;
#ifdef GENERIC_INPUT
case WM_INPUT:
/*
* Generic Input messages.
* There is not much we should look at here. The best practice is just
* omit most of the processing and just return the current message.
*/
wParam = qmsg.msg.wParam;
lParam = qmsg.msg.lParam;
/*
* Assign the input to the focus window. If there is no focus
* window, assign it to the active window as a SYS message.
*/
pwnd = NULL;
if (lParam) {
PHIDDATA pHidData = HMValidateHandle((LPVOID)lParam, TYPE_HIDDATA);
if (pHidData) {
pwnd = pHidData->spwndTarget;
}
}
if (pwnd == NULL) {
pwnd = ptiCurrent->pq->spwndFocus;
if (pwnd == NULL) {
pwnd = ptiCurrent->pq->spwndActive;
if (pwnd == NULL) {
PATHTAKEN2(0x10000000);
goto SkipMessage;
}
}
}
TAGMSG1(DBGTAG_PNP, "xxxScanSysQueue: pwnd=%p", pwnd);
UserAssert(pwnd != NULL);
ThreadLockExchangeAlways(pwnd, &tlpwnd);
/*
* Check if this is intended for the current app.
*/
if (fOtherApp = (GETPTI(pwnd) != ptiCurrent)) {
PWND pwndModalLoop;
/*
* If this other app isn't going to read from this
* queue, then skip this message. This can happen if
* the RIT queues up a message thinking it goes to
* a particular hwnd, but then by the time GetMessage()
* is called for that thread, it doesn't go to that hwnd
* (like in the case of mouse messages, window rearrangement
* happens which changes which hwnd the mouse hits on).
*/
if (GETPTI(pwnd)->pq != ptiCurrent->pq) {
PATHTAKEN2(0x40000000);
goto SkipMessage;
}
/*
* If the current thread is in the menu or movesize loop
* then we need to give it the input
*/
if (IsInsideMenuLoop(ptiCurrent)) {
pwndModalLoop = ptiCurrent->pMenuState->pGlobalPopupMenu->spwndNotify;
} else if (ptiCurrent->pmsd != NULL) {
pwndModalLoop = ptiCurrent->pmsd->spwnd;
RIPMSG0(RIP_WARNING, "xxxScanSysQueue: returning key to movesize loop");
} else {
pwndModalLoop = NULL;
}
/*
* If we're switching windows, lock the new one
*/
if (pwndModalLoop != NULL) {
pwnd = pwndModalLoop;
fOtherApp = (GETPTI(pwnd) != ptiCurrent);
ThreadLockExchangeAlways(pwnd, &tlpwnd);
PATHTAKEN2(0x80000000);
}
/*
* If not for us, then remember who it is for.
*/
if (ptiRawInputWake == NULL) {
PATHTAKEN3(1);
ptiRawInputWake = GETPTI(pwnd);
ThreadLockExchangePti(ptiRawInputWake, &tlptiRawInputWake);
}
}
/*
* See if this thing matches our filter.
*/
if (!CheckMsgFilter(message, msgMinFilter, msgMaxFilter) ||
!CheckPwndFilter(pwnd, pwndFilter)) {
PATHTAKEN3(2);
continue;
}
/*
* This message matches our filter. If it is not for us then
* stop searching to make sure the real owner processes this
* message first.
*/
if (fOtherApp) {
PATHTAKEN3(4);
goto NoMessages;
}
/*
* Remove the message from the input queue.
*/
if (fRemove) {
#if LOCK_HIDDATA
PHIDDATA pHidData = HMValidateHandle((LPVOID)lParam, TYPE_HIDDATA);
if (pHidData) {
/*
* Lock the object so that hRawInput is not destroyed
* while the message is removed from the input queue.
*/
HMLockObject(pHidData);
}
else {
RIPMSG1(RIP_WARNING, "xxxScanSysQueue: invalid WM_INPUT's lParam %p", lParam);
}
#endif
xxxSkipSysMsg(ptiCurrent, &qmsg);
}
/*
* N.b.
* WM_INPUT is not handed to input hooks.
*/
PATHTAKEN3(0x00010000);
goto ReturnMessage;
#endif
} /* End of switch (message = qmsg.msg.message) */
} /* End of the GetNextSysMsg() loop */
ReturnMessage:
if (!RtlEqualMemory(&ptiCurrent->ptLast, &qmsg.msg.pt, sizeof(POINT))) {
ptiCurrent->TIF_flags |= TIF_MSGPOSCHANGED;
}
ptiCurrent->ptLast = qmsg.msg.pt;
ptiCurrent->timeLast = qmsg.msg.time;
ptiCurrent->pq->ExtraInfo = qmsg.ExtraInfo;
/*
* idSysLock value of 1 indicates that the message came from the input
* queue.
*/
ptiCurrent->idLast = ptiCurrent->pq->idSysLock = 1;
/*
* Now see if our input bit is set for this input. If it isn't, set ours
* and clear the guy who had it previously.
*/
TransferWakeBit(ptiCurrent, message);
/*
* Clear the input bits if no messages in the input queue.
*/
ClearWakeBit(ptiCurrent, QS_MOUSE | QS_KEY | QS_EVENT |
#ifdef GENERIC_INPUT
QS_RAWINPUT |
#endif
QS_TRANSFER, TRUE);
/*
* Get the message and split.
*/
lpMsg->hwnd = HW(pwnd);
lpMsg->message = message;
/*
* If the IME claims that it needs this vkey, replace it
* with VK_PROCESSKEY. The real vkey has been saved in
* the input context in the client side.
*/
lpMsg->wParam = (dwImmRet & IPHK_PROCESSBYIME) ? VK_PROCESSKEY : wParam;
lpMsg->lParam = lParam;
lpMsg->time = qmsg.msg.time;
lpMsg->pt = qmsg.msg.pt;
#if DBG
if (gfLogPlayback && ptiCurrent->pq->idSysPeek == (LONG_PTR)PQMSG_PLAYBACK)
LogPlayback(pwnd, lpMsg);
#endif // DBG
#ifdef GENERIC_INPUT
ThreadUnlockPti(ptiCurrent, &tlptiRawInputWake);
#endif
ThreadUnlockPti(ptiCurrent, &tlptiEventWake);
ThreadUnlockPti(ptiCurrent, &tlptiMouseWake);
ThreadUnlockPti(ptiCurrent, &tlptiKeyWake);
ThreadUnlock(&tlpwnd);
PATHTAKEN3(0x200);
DUMPPATHTAKEN();
return TRUE;
NoMessages:
/*
* The message was for another app, or none were found that fit the
* filter.
*/
/*
* Unlock the system queue.
*/
ptiCurrent->pq->idSysLock = 0;
CheckSysLock(4, ptiCurrent->pq, NULL);
ptiCurrent->pq->ptiSysLock = NULL;
ptiCurrent->pcti->CTIF_flags &= ~CTIF_SYSQUEUELOCKED;
/*
* Wake up someone else if we found a message for him. QS_TRANSFER
* signifies that the thread was woken due to input transfer
* from another thread, rather than from a real input event.
*/
if (ptiKeyWake != NULL || ptiMouseWake != NULL || ptiEventWake != NULL
#ifdef GENERIC_INPUT
|| ptiRawInputWake != NULL
#endif
) {
PATHTAKEN3(0x400);
if (ptiKeyWake != NULL) {
SetWakeBit(ptiKeyWake, QS_KEY | QS_TRANSFER);
ClearWakeBit(ptiCurrent, QS_KEY | QS_TRANSFER, FALSE);
PATHTAKEN3(0x800);
}
if (ptiMouseWake != NULL) {
SetWakeBit(ptiMouseWake, QS_MOUSE | QS_TRANSFER);
ClearWakeBit(ptiCurrent, QS_MOUSE | QS_TRANSFER, FALSE);
PATHTAKEN3(0x1000);
}
#ifdef GENERIC_INPUT
if (ptiRawInputWake != NULL) {
SetWakeBit(ptiRawInputWake, QS_RAWINPUT | QS_TRANSFER);
ClearWakeBit(ptiCurrent, QS_RAWINPUT | QS_TRANSFER, FALSE);
}
#endif
if (ptiEventWake != NULL) {
SetWakeBit(ptiEventWake, QS_EVENTSET);
ClearWakeBit(ptiCurrent, QS_EVENT, FALSE);
PATHTAKEN3(0x2000);
} else if (FJOURNALPLAYBACK()) {
/*
* If journal playback is occuring, clear the input bits. This will
* help prevent a race condition between two threads that call
* WaitMessage/PeekMessage. This can occur when embedding an OLE
* object. An example is inserting a Word object into an Excel
* spreadsheet.
* Also clear change bits else this thread might not xxxSleepThread.
*/
ptiCurrent->pcti->fsWakeBitsJournal |= (ptiCurrent->pcti->fsWakeBits &
(QS_MOUSE | QS_KEY |
#ifdef GENERIC_INPUT
QS_RAWINPUT |
#endif
QS_TRANSFER));
ClearWakeBit(ptiCurrent, QS_MOUSE | QS_KEY |
#ifdef GENERIC_INPUT
QS_RAWINPUT |
#endif
QS_TRANSFER, FALSE);
ptiCurrent->pcti->fsChangeBits &= ~(QS_MOUSE | QS_KEY |
#ifdef GENERIC_INPUT
QS_RAWINPUT |
#endif
QS_TRANSFER);
}
} else {
/*
* Clear the input bits if no messages in the input queue.
*/
ptiCurrent->pcti->fsWakeBitsJournal = 0;
ClearWakeBit(ptiCurrent, QS_MOUSE | QS_KEY | QS_EVENT |
#ifdef GENERIC_INPUT
QS_RAWINPUT |
#endif
QS_TRANSFER, TRUE);
PATHTAKEN3(0x4000);
}
#ifdef GENERIC_INPUT
ThreadUnlockPti(ptiCurrent, &tlptiRawInputWake);
#endif
ThreadUnlockPti(ptiCurrent, &tlptiEventWake);
ThreadUnlockPti(ptiCurrent, &tlptiMouseWake);
ThreadUnlockPti(ptiCurrent, &tlptiKeyWake);
ThreadUnlock(&tlpwnd);
PATHTAKEN3(0x8000);
DUMPPATHTAKEN();
return FALSE;
}
#undef PATHTAKEN
#undef PATHTAKEN2
#undef PATHTAKEN3
#undef DUMPPATHTAKEN
#undef DUMPSUBPATHTAKEN
/***************************************************************************\
* IdleTimerProc
*
* This will start the screen saver app
*
* History:
* 09-06-91 mikeke Created.
* 03-26-92 DavidPe Changed to be run from hungapp timer on RIT.
\***************************************************************************/
VOID IdleTimerProc(VOID)
{
CheckCritIn();
if ( (TestAsyncKeyStateDown(VK_LBUTTON)) ||
(TestAsyncKeyStateDown(VK_RBUTTON)) ||
(TestAsyncKeyStateDown(VK_MBUTTON)) ||
(TestAsyncKeyStateDown(VK_XBUTTON1)) ||
(TestAsyncKeyStateDown(VK_XBUTTON2))) {
return;
}
if (giScreenSaveTimeOutMs > 0) {
if (IsTimeFromLastInput((DWORD)(giScreenSaveTimeOutMs))) {
if (gppiScreenSaver != NULL) {
if (!(gppiScreenSaver->W32PF_Flags & W32PF_IDLESCREENSAVER)) {
/*
* Bump the priority of the screen saver down to idle.
*/
gppiScreenSaver->W32PF_Flags |= W32PF_IDLESCREENSAVER;
SetForegroundPriorityProcess(gppiScreenSaver, gppiScreenSaver->ptiMainThread, TRUE);
}
} else {
/*
* Tell the system that it needs to bring up a screen saver.
*
* Carefull with the case when the active window is hung. If this
* is the case the screen saver won't be started by winlogon because
* DefWindowProc won't call StartScreenSaver(FALSE).
*/
if ((gpqForeground != NULL) &&
(gpqForeground->spwndActive != NULL) &&
!FHungApp(GETPTI(gpqForeground->spwndActive), CMSHUNGAPPTIMEOUT)) {
/*
* Tell winlogon to start the screen saver if we have a secure
* screen saver. In case we do have a secure one, the next PostMessage
* will be ignored in winlogon.
*/
StartScreenSaver(TRUE);
_PostMessage(gpqForeground->spwndActive, WM_SYSCOMMAND, SC_SCREENSAVE, 0L);
} else {
StartScreenSaver(FALSE);
}
}
}
}
if ((giLowPowerTimeOutMs > 0) && ((glinp.dwFlags & LINP_LOWPOWER) == 0)) {
if (IsTimeFromLastInput((DWORD)(giLowPowerTimeOutMs))) {
if ((gpqForeground != NULL) && (gpqForeground->spwndActive != NULL)) {
_PostMessage(gpqForeground->spwndActive, WM_SYSCOMMAND, SC_MONITORPOWER, LOWPOWER_PHASE);
}
}
}
if ((giPowerOffTimeOutMs > 0) && ((glinp.dwFlags & LINP_POWEROFF) == 0)) {
if (IsTimeFromLastInput((DWORD)(giPowerOffTimeOutMs))) {
if ((gpqForeground != NULL) && (gpqForeground->spwndActive != NULL)) {
_PostMessage(gpqForeground->spwndActive, WM_SYSCOMMAND, SC_MONITORPOWER, POWEROFF_PHASE);
}
}
}
}
/***************************************************************************\
* zzzWakeInputIdle
*
* The calling thread is going "idle". Wake up any thread waiting for this.
*
* 09-24-91 ScottLu Created.
\***************************************************************************/
void zzzWakeInputIdle(
PTHREADINFO pti)
{
PW32PROCESS W32Process = W32GetCurrentProcess();
/*
* clear out the TIF_FIRSTIDLE since here we are
*/
pti->TIF_flags &= ~TIF_FIRSTIDLE;
/*
* Shared Wow Apps use the per thread idle event for synchronization.
* Separate Wow VDMs use the regular mechanism.
*/
if (pti->TIF_flags & TIF_SHAREDWOW) {
UserAssert(pti->TIF_flags & TIF_16BIT);
if (pti->ptdb->pwti) {
SET_PSEUDO_EVENT(&pti->ptdb->pwti->pIdleEvent);
}
} else {
/*
* If the main thread is NULL, set it to this queue: it is calling
* GetMessage().
*/
if (pti->ppi->ptiMainThread == NULL)
pti->ppi->ptiMainThread = pti;
/*
* Wake up anyone waiting on this event.
*/
if (pti->ppi->ptiMainThread == pti) {
SET_PSEUDO_EVENT(&W32Process->InputIdleEvent);
}
}
/*
* Check to see if the startglass is on, and if so turn it off and update.
*/
if (W32Process->W32PF_Flags & W32PF_STARTGLASS) {
/*
* This app is no longer in "starting" mode. Recalc when to hide
* the app starting cursor.
*/
W32Process->W32PF_Flags &= ~W32PF_STARTGLASS;
zzzCalcStartCursorHide(NULL, 0);
}
}
void SleepInputIdle(
PTHREADINFO pti)
{
PW32PROCESS W32Process;
/*
* Shared Wow Apps use the per thread idle event for synchronization.
* Separate Wow VDMs use the regular mechanism.
*/
if (pti->TIF_flags & TIF_SHAREDWOW) {
UserAssert(pti->TIF_flags & TIF_16BIT);
if (pti->ptdb->pwti) {
RESET_PSEUDO_EVENT(&pti->ptdb->pwti->pIdleEvent);
}
} else {
/*
* If the main thread is NULL, set it to this queue: it is calling
* GetMessage().
*/
if (pti->ppi->ptiMainThread == NULL)
pti->ppi->ptiMainThread = pti;
/*
* Put to sleep up anyone waiting on this event.
*/
if (pti->ppi->ptiMainThread == pti) {
W32Process = W32GetCurrentProcess();
RESET_PSEUDO_EVENT(&W32Process->InputIdleEvent);
}
}
}
/***************************************************************************\
* zzzRecalcThreadAttachment
* zzzRecalc2
* zzzAddAttachment
* CheckAttachment
*
* Runs through all the attachinfo fields for all threads and calculates
* which threads share which queues. Puts calculated result in pqAttach
* field in each threadinfo structure. This is a difficult problem
* whose only solution in iterative. The basic algorithm is:
*
* 0. Find next unattached thread and attach a queue to it. If none, stop.
* 1. Loop through all threads: If thread X assigned to this queue or any
* of X's attach requests assigned to this queue, assign X and all X's
* attachments to this queue. Remember if we ever attach a 16 bit thread.
* 2. If thread X is a 16 bit thread and we've already attached another
* 16 bit thread, assign X and all X's attachments to this queue.
* 3. If any change found in 1-2, goto 1
* 4. Goto 0
*
* 12-11-92 ScottLu Created.
* 01-Oct-1993 mikeke Fixed to work with MWOWs
\***************************************************************************/
void zzzAddAttachment(
PTHREADINFO pti,
PQ pqAttach,
LPBOOL pfChanged)
{
if (pti->pqAttach != pqAttach) {
/*
* LATER
* !!! This is totally messed up, The only reason that this thing
* could be non null is because two threads are going through
* zzzAttachThreadInput() at the same time. No one can predict
* what kind of problems are going to be caused by that.
* We leave the critical section in one place where we send
* WM_CANCELMODE below. We should figure out how to remove
* the sendmessage.
*
* If there already is a queue there, as there may be, destroy it.
* Note that zzzDestroyQueue() will only get rid of the queue if the
* thread reference count goes to 0.
*/
PQ pqDestroy = pti->pqAttach;
pti->pqAttach = pqAttach;
if (pqDestroy != NULL)
zzzDestroyQueue(pqDestroy, pti);
pqAttach->cThreads++;
*pfChanged = TRUE;
}
}
void zzzRecalc2(
PQ pqAttach)
{
PATTACHINFO pai;
PTHREADINFO pti;
BOOL fChanged;
PLIST_ENTRY pHead, pEntry;
/*
* Defer Win Event notifications so we can traverse PtiList with impunity
* #bug number from shiflet
*/
DeferWinEventNotify();
BEGINATOMICCHECK();
/*
* Keep adding attachments until everything that should be attached to this
* queue is attached
*/
do {
fChanged = FALSE;
/*
* If a thread is attached to this Q attach all of it's attachments
* and MWOW buddies if they aren't already attached.
*/
pHead = &PtiCurrent()->rpdesk->PtiList;
for (pEntry = pHead->Flink; pEntry != pHead; pEntry = pEntry->Flink) {
pti = CONTAINING_RECORD(pEntry, THREADINFO, PtiLink);
if (pti->pqAttach == pqAttach) {
/*
* check each of the attachments to see if this thread is attached
* to any other threads
*/
for (pai = gpai; pai != NULL; pai = pai->paiNext) {
/*
* if they weren't attached already, attach them
*/
if (pai->pti1 == pti || pai->pti2 == pti) {
zzzAddAttachment((pai->pti1 == pti) ? pai->pti2 : pai->pti1,
pqAttach, &fChanged);
}
}
/*
* If this is a 16bit thread attach to all other threads in
* it's MWOW
*/
if (pti->TIF_flags & TIF_16BIT) {
PTHREADINFO ptiAttach;
PLIST_ENTRY pHeadAttach, pEntryAttach;
pHeadAttach = &pti->rpdesk->PtiList;
for (pEntryAttach = pHeadAttach->Flink;
pEntryAttach != pHeadAttach;
pEntryAttach = pEntryAttach->Flink) {
ptiAttach = CONTAINING_RECORD(pEntryAttach, THREADINFO, PtiLink);
if (ptiAttach->TIF_flags & TIF_16BIT &&
ptiAttach->ppi == pti->ppi) {
zzzAddAttachment(ptiAttach, pqAttach, &fChanged);
}
}
}
}
}
} while (fChanged);
ENDATOMICCHECK();
zzzEndDeferWinEventNotify();
}
void zzzRecalcThreadAttachment()
{
PTHREADINFO pti;
PLIST_ENTRY pHead, pEntry;
/*
* Win Event notifications must be defered so we can traverse PtiList with impunity
*/
UserAssert(IsWinEventNotifyDeferred());
/*
* For all threads, start an attach queue if a thread hasn't been
* attached yet.
*/
pHead = &PtiCurrent()->rpdesk->PtiList;
for (pEntry = pHead->Flink; pEntry != pHead; pEntry = pEntry->Flink) {
pti = CONTAINING_RECORD(pEntry, THREADINFO, PtiLink);
/*
* Assert: We should not leave the critsect from xxxCreateThreadInfo
* with the new thread in the rpdesk->PtiList but not yet with a queue.
*/
UserAssert(pti->pq != NULL);
if (pti->pqAttach == NULL) {
/*
* Allocate a new queue for this thread if more than
* one thread references it.
*/
if (pti->pq->cThreads > 1) {
pti->pqAttach = AllocQueue(NULL, NULL);
if (pti->pqAttach == NULL) {
RIPMSG0(RIP_WARNING, "zzzRecalcThreadAttachment: AllocQueue failed");
break;
}
pti->pqAttach->cThreads++;
} else {
pti->pqAttach = pti->pq;
}
/*
* Attach every thread that is directly or indirectly attached
* to this thread.
*/
zzzRecalc2(pti->pqAttach);
}
}
}
/***************************************************************************\
* RedistributeInput
*
* This routine takes a input stream from the queue being left, and
* redistributes it. This effectively filters out the messages destined
* to the thread that left the queue.
*
* 12-10-92 ScottLu Created.
\***************************************************************************/
void RedistributeInput(
PQMSG pqmsgS,
PQ pqRedist)
{
PTHREADINFO ptiSave;
PTHREADINFO ptiT;
PQMSG *ppqmsgD;
PQMSG pqmsgT;
PMLIST pmlInput;
/*
* Since the thread attaching or unattaching may have left a queue
* shared by other threads, the messages we are going to requeue
* may have multiple destinations. On top of this, once we find
* a home queue for a message, it needs to be inserted in the
* list ordered by its time stamp (older messages go at the end).
*/
/*
* Loop through a given dest's messages to find where to insert
* the source messages, based on message time stamp. Be sure
* to deal with empty message lists (meaning, check for NULL).
*/
ptiT = NULL;
ppqmsgD = NULL;
pmlInput = NULL;
while (pqmsgS != NULL) {
/*
* Find out where this message should go.
*/
ptiSave = ptiT;
ptiT = pqmsgS->pti;
/*
* Get rid of some event messages.
*
* QEVENT_UPDATEKEYSTATE: key state already up to date
*/
if (pqmsgS->dwQEvent == QEVENT_UPDATEKEYSTATE) {
ptiT = NULL;
}
if (ptiT == NULL) {
/*
* Unlink it. pqmsgS should be the first in the list
*/
UserAssert(!pqmsgS->pqmsgPrev);
if (pqmsgS->pqmsgNext != NULL) {
pqmsgS->pqmsgNext->pqmsgPrev = NULL;
}
pqmsgT = pqmsgS;
pqmsgS = pqmsgS->pqmsgNext;
/*
* Clean it / free it.
*/
CleanEventMessage(pqmsgT);
FreeQEntry(pqmsgT);
ptiT = ptiSave;
continue;
}
/*
* Point to the pointer that points to the first message
* that this message should go to, so that pointer is easy to
* update, no matter where it is.
*/
if (ppqmsgD == NULL || ptiSave != ptiT) {
/*
* If the source is younger than the last message in the
* destination, go to the end. Otherwise, start at the
* head of the desination list and find a place to insert
* the message.
*/
if (ptiT->pq->mlInput.pqmsgWriteLast != NULL &&
pqmsgS->msg.time >= ptiT->pq->mlInput.pqmsgWriteLast->msg.time) {
ppqmsgD = &ptiT->pq->mlInput.pqmsgWriteLast->pqmsgNext;
} else {
ppqmsgD = &ptiT->pq->mlInput.pqmsgRead;
}
pmlInput = &ptiT->pq->mlInput;
}
/*
* If we're not at the end of the destination AND the destination
* message time is younger than the source time, go on to
* the next message.
*/
while (*ppqmsgD != NULL && ((*ppqmsgD)->msg.time <= pqmsgS->msg.time)) {
ppqmsgD = &((*ppqmsgD)->pqmsgNext);
}
/*
* Link in the source before the dest message. Update
* it's next and prev pointers. Update the dest prev
* pointer.
*/
pqmsgT = pqmsgS;
pqmsgS = pqmsgS->pqmsgNext;
pqmsgT->pqmsgNext = *ppqmsgD;
if (*ppqmsgD != NULL) {
pqmsgT->pqmsgPrev = (*ppqmsgD)->pqmsgPrev;
(*ppqmsgD)->pqmsgPrev = pqmsgT;
} else {
pqmsgT->pqmsgPrev = pmlInput->pqmsgWriteLast;
pmlInput->pqmsgWriteLast = pqmsgT;
}
*ppqmsgD = pqmsgT;
ppqmsgD = &pqmsgT->pqmsgNext;
pmlInput->cMsgs++;
/*
* If the thread has an event message, make sure it's going to wake
* up to process it. The QS_EVENT flag might not be set if the thread
* previously found an event message for another thread and passed
* control over to him.
*/
if (pqmsgT->dwQEvent != 0 && !(ptiT->pcti->fsWakeBits & QS_EVENT)) {
SetWakeBit(ptiT, QS_EVENTSET);
}
/*
* Preserve the 'idSysPeek' from the old queue, checking if the
* redistributed queue is the same as ptiT->pq.
*/
if (pqmsgT == (PQMSG)(pqRedist->idSysPeek) && pqRedist != ptiT->pq) {
if (ptiT->pq->idSysPeek == 0) {
CheckPtiSysPeek(6, ptiT->pq, pqRedist->idSysPeek);
ptiT->pq->idSysPeek = pqRedist->idSysPeek;
} else {
TAGMSG2(DBGTAG_SysPeek,
"idSysPeek %#p already set in pq %#p",
ptiT->pq->idSysPeek, ptiT->pq);
}
/*
* Set the 'idSysPeek' of this queue to 0 since
* we moved the idSysPeek to other queue
*/
CheckPtiSysPeek(7, pqRedist, 0);
pqRedist->idSysPeek = 0;
/*
* Preserve also 'ptiSysLock'.
* Set ptiSysLock to the ptiT->pq only if it points to
* that queue.
*/
if (ptiT->pq->ptiSysLock == NULL &&
pqRedist->ptiSysLock != NULL &&
pqRedist->ptiSysLock->pq == ptiT->pq) {
CheckSysLock(4, ptiT->pq, pqRedist->ptiSysLock);
ptiT->pq->ptiSysLock = pqRedist->ptiSysLock;
CheckSysLock(5, pqRedist, NULL);
pqRedist->ptiSysLock = NULL;
} else {
TAGMSG2(DBGTAG_SysPeek,
"ptiSysLock %#p already set in pq %#p\n",
ptiT->pq->ptiSysLock, ptiT->pq);
}
}
/*
* Don't want the prev pointer on our message list to point
* to this message which is on a different list (doesn't
* really matter because we're about to link it anyway,
* but completeness shouldn't hurt).
*/
if (pqmsgS != NULL) {
pqmsgS->pqmsgPrev = NULL;
}
}
}
/***************************************************************************\
* CancelInputState
*
* This routine takes a queue and "cancels" input state in it - i.e., if the
* app thinks it is active, make it think it is not active, etc.
*
* 12-10-92 ScottLu Created.
\***************************************************************************/
VOID CancelInputState(
PTHREADINFO pti,
DWORD cmd)
{
PTHREADINFO ptiCurrent = PtiCurrent();
PWND pwndT;
TL tlpwndT;
TL tlpwndChild;
AAS aas;
/*
* In all cases, do not leave do any send messages or any callbacks!
* This is because this code is called from
* SetWindowsHook(WH_JOURNALPLAYBACK | WH_JOURNALRECORD). No app currently
* calling this routine expects to be called before this routine returns.
* (If you do callback before it returns, you'll break at least Access
* for Windows). - scottlu
*/
switch (cmd) {
case CANCEL_ACTIVESTATE:
/*
* Active state.
*/
pwndT = pti->pq->spwndActive;
ThreadLockWithPti(ptiCurrent, pwndT, &tlpwndT);
QueueNotifyMessage(pwndT, WM_NCACTIVATE, FALSE, 0);
QueueNotifyMessage(pwndT, WM_ACTIVATE,
MAKELONG(WA_INACTIVE, TestWF(pwndT, WFMINIMIZED)),
0);
if (pwndT == pti->pq->spwndActive)
Unlock(&pti->pq->spwndActive);
aas.ptiNotify = GETPTI(pwndT);
aas.tidActDeact = TIDq(GETPTI(pwndT));
aas.fActivating = FALSE;
aas.fQueueNotify = TRUE;
/*
* Even though this in an xxx call, it does NOT leave any critical
* sections (because fQueueNotify is TRUE).
*/
ThreadLockWithPti(ptiCurrent, GETPTI(pwndT)->rpdesk->pDeskInfo->spwnd->spwndChild, &tlpwndChild);
xxxInternalEnumWindow(GETPTI(pwndT)->rpdesk->pDeskInfo->spwnd->spwndChild,
(WNDENUMPROC_PWND)xxxActivateApp, (LPARAM)&aas, BWL_ENUMLIST);
ThreadUnlock(&tlpwndChild);
ThreadUnlock(&tlpwndT);
break;
case CANCEL_FOCUSSTATE:
/*
* Focus state.
*/
pwndT = pti->pq->spwndFocus;
ThreadLockWithPti(ptiCurrent, pwndT, &tlpwndT);
QueueNotifyMessage(pwndT, WM_KILLFOCUS, 0, 0);
#ifdef FE_IME
if (IS_IME_ENABLED()) {
/*
* Even though this in an xxx call, it does NOT leave any
* critical section (because fQueueMsg is TRUE).
*/
xxxFocusSetInputContext(pwndT, FALSE, TRUE);
}
#endif
if (pwndT == pti->pq->spwndFocus)
Unlock(&pti->pq->spwndFocus);
ThreadUnlock(&tlpwndT);
break;
case CANCEL_CAPTURESTATE:
/*
* Capture state.
*/
/*
* We shouldn't be nuking the capture of a modal menu mode.
*/
UserAssert((pti->pMenuState == NULL)
|| pti->pMenuState->fModelessMenu
|| pti->pMenuState->fInDoDragDrop);
pti->pq->QF_flags &= ~QF_CAPTURELOCKED;
pwndT = pti->pq->spwndCapture;
ThreadLockWithPti(ptiCurrent, pwndT, &tlpwndT);
QueueNotifyMessage(pwndT, WM_CANCELMODE, 0, 0);
if (pwndT == pti->pq->spwndCapture)
UnlockCaptureWindow(pti->pq);
ThreadUnlock(&tlpwndT);
break;
}
}
/***************************************************************************\
* DBGValidateQueueStates
*
* Verifies that all queues point to stuff owned by a thread attached to
* the queue.
*
* 07/29/97 GerardoB Created
\***************************************************************************/
#if DBG
#define VALIDATEQSPWND(spwnd) \
if (pq-> ## spwnd != NULL) { \
ptiwnd = GETPTI(pq-> ## spwnd); \
fDestroyedOK = (TestWF(pq-> ## spwnd, WFDESTROYED) && (ptiwnd == gptiRit)); \
UserAssert((pti->rpdesk == ptiwnd->rpdesk) || fDestroyedOK); \
UserAssert((pti == ptiwnd) \
|| (fAttached && (pq == ptiwnd->pq)) \
|| fDestroyedOK); \
}
void DBGValidateQueueStates (PDESKTOP pdesk)
{
BOOL fAttached, fDestroyedOK;
PQ pq;
PLIST_ENTRY pHead, pEntry;
PTHREADINFO pti, ptiwnd;
DWORD dwInForeground = 0;
UserAssert((gpqForeground == NULL)
|| ((gpqForeground->ptiMouse->rpdesk == grpdeskRitInput)
&& (gpqForeground->cThreads != 0)));
if (pdesk == NULL) {
RIPMSG0(RIP_WARNING, "DBGValidateQueueStates: Null pdesk parameter");
return;
}
pHead = &pdesk->PtiList;
for (pEntry = pHead->Flink; pEntry != pHead; pEntry = pEntry->Flink) {
pti = CONTAINING_RECORD(pEntry, THREADINFO, PtiLink);
pq = pti->pq;
if (pq == NULL) {
RIPMSG2(RIP_WARNING, "DBGValidateQueueStates: Null pq. pti:%#p. pdesk:%#p", pti, pdesk);
continue;
}
/*
* The queue should have a non-null cThreads excepting when it is
* QF_INDESTROY
*/
if (!(pq->QF_flags & QF_INDESTROY)) {
UserAssert(pq->cThreads != 0);
}
fAttached = (pq->cThreads > 1);
if (pti->pq == gpqForeground) {
dwInForeground++;
}
/*
* pti's
*/
UserAssert((pti == pq->ptiMouse)
|| (fAttached && (pq == pq->ptiMouse->pq)));
UserAssert(pti->rpdesk == pq->ptiMouse->rpdesk);
UserAssert((pti == pq->ptiKeyboard)
|| (fAttached && (pq == pq->ptiKeyboard->pq)));
UserAssert(pti->rpdesk == pq->ptiKeyboard->rpdesk);
if (pq->ptiSysLock != NULL) {
UserAssert((pti == pq->ptiSysLock)
|| (fAttached && (pq == pq->ptiSysLock->pq)));
}
/*
* pwnd's.
*/
VALIDATEQSPWND(spwndActive);
VALIDATEQSPWND(spwndFocus);
VALIDATEQSPWND(spwndCapture);
VALIDATEQSPWND(spwndActivePrev);
}
UserAssert((gpqForeground == NULL)
|| (dwInForeground == 0)
|| (gpqForeground->cThreads == dwInForeground));
}
/***************************************************************************\
* DBGValidateQueue
*
* Verifies that queue is readable and fields are valid.
*
* 02-Sep-1999 JerrySh Created.
\***************************************************************************/
void DBGValidateQueue(PQ pq)
{
if (pq != NULL) {
Q q = *pq;
UserAssert(q.spwndActive == HtoP(PtoH(q.spwndActive)));
UserAssert(q.spwndFocus == HtoP(PtoH(q.spwndFocus)));
UserAssert(q.spwndCapture == HtoP(PtoH(q.spwndCapture)));
UserAssert(q.spwndActivePrev == HtoP(PtoH(q.spwndActivePrev)));
UserAssert(q.spcurCurrent == HtoP(PtoH(q.spcurCurrent)));
}
}
#endif /* DBG */
/***************************************************************************\
* zzzAttachThreadInput (API)
* zzzReattachThreads
* zzzAttachToQueue
* CheckTransferState
*
* Attaches a given thread to another input queue, either by attaching to
* a queue (referenced by another thread id), or detaching from one.
*
* 12-09-92 ScottLu Created.
\***************************************************************************/
#define CTS_DONOTHING 0
#define CTS_CANCELOLD 1
#define CTS_TRANSFER 2
DWORD CheckTransferState(
PTHREADINFO pti,
PQ pqAttach,
LONG offset,
BOOL fJoiningForeground)
{
PWND pwndOld, pwndNew, pwndForegroundState;
/*
* return 0: do nothing.
* return 1: cancel the old state.
* return 2: transfer the old state to the new state
*/
pwndOld = *(PWND *)(((BYTE *)pti->pq) + offset);
pwndNew = *(PWND *)(((BYTE *)pqAttach) + offset);
/*
* Make sure the old state even exists, and that the old state is
* owned by this thread. If not, nothing happens.
*/
if (pwndOld == NULL || GETPTI(pwndOld) != pti)
return CTS_DONOTHING;
/*
* If the new state already exists, cancel the old state.
*/
if (pwndNew != NULL)
return CTS_CANCELOLD;
/*
* Transfer this old state if this thread is not joining the foreground.
*/
if (gpqForeground == NULL || !fJoiningForeground)
return CTS_TRANSFER;
/*
* We're joining the foreground - only transfer the old state if we own
* that foreground state or if there is no foreground state.
*/
pwndForegroundState = *(PWND *)(((BYTE *)gpqForeground) + offset);
if (pwndForegroundState == NULL || pwndOld == pwndForegroundState)
return CTS_TRANSFER;
/*
* We're joining the foreground but we didn't set that foreground state.
* Don't allow the transfer of that state.
*/
return CTS_CANCELOLD;
}
VOID zzzAttachToQueue(
PTHREADINFO pti,
PQ pqAttach,
PQ pqJournal,
BOOL fJoiningForeground)
{
PQMSG pqmsgT;
PQ pqDestroy;
/*
* Check active state.
*/
switch (CheckTransferState(pti, pqAttach,
FIELD_OFFSET(Q, spwndActive), fJoiningForeground)) {
case CTS_CANCELOLD:
CancelInputState(pti, CANCEL_ACTIVESTATE);
break;
case CTS_TRANSFER:
Lock(&pqAttach->spwndActive, pti->pq->spwndActive);
Unlock(&pti->pq->spwndActive);
/*
* The caret usually follows the focus window, which follows
* the active window...
*/
if (pti->pq->caret.spwnd != NULL) {
if (GETPTI(pti->pq->caret.spwnd) == pti) {
/*
* Just copy the entire caret structure... that way we
* don't need to deal with locking/unlocking the spwnd.
*/
if (pqAttach->caret.spwnd == NULL) {
pqAttach->caret = pti->pq->caret;
pti->pq->caret.spwnd = NULL;
}
}
}
break;
}
/*
* Check focus state.
*/
switch (CheckTransferState(pti, pqAttach,
FIELD_OFFSET(Q, spwndFocus), fJoiningForeground)) {
case CTS_CANCELOLD:
CancelInputState(pti, CANCEL_FOCUSSTATE);
break;
case CTS_TRANSFER:
Lock(&pqAttach->spwndFocus, pti->pq->spwndFocus);
Unlock(&pti->pq->spwndFocus);
break;
}
/*
* Check capture state.
*/
switch (CheckTransferState(pti, pqAttach,
FIELD_OFFSET(Q, spwndCapture), fJoiningForeground)) {
case CTS_CANCELOLD:
CancelInputState(pti, CANCEL_CAPTURESTATE);
break;
case CTS_TRANSFER:
LockCaptureWindow(pqAttach, pti->pq->spwndCapture);
UnlockCaptureWindow(pti->pq);
pqAttach->codeCapture = pti->pq->codeCapture;
pqAttach->QF_flags ^= ((pqAttach->QF_flags ^ pti->pq->QF_flags) & QF_CAPTURELOCKED);
break;
#if DBG
case CTS_DONOTHING:
/*
* We should always transfer the capture state of a thread
* in modal menu mode.
*/
UserAssert((pti->pMenuState == NULL)
|| ExitMenuLoop(pti->pMenuState, pti->pMenuState->pGlobalPopupMenu)
|| pti->pMenuState->fModelessMenu
|| pti->pMenuState->fInDoDragDrop);
break;
#endif
}
/*
* Check spwndActivePrev state. This check has some considerations.
* If the CTS_TRANSFER is returned, it usually means there was no
* prev-active in the attach-queue, and it will use the first
* window it encounters. Since we walk the thread-list, a out-of-zorder
* window could be chosen. So, to counter this, we'll check the
* attach-queue-next-prev against the thread-previous window to see
* if it is truly the next-zorder window.
*/
switch (CheckTransferState(pti, pqAttach,
FIELD_OFFSET(Q, spwndActivePrev), fJoiningForeground)) {
case CTS_TRANSFER:
Lock(&pqAttach->spwndActivePrev, pti->pq->spwndActivePrev);
Unlock(&pti->pq->spwndActivePrev);
break;
case CTS_CANCELOLD:
/*
* Check to see if the previous window is what we would expect it
* to be.
*/
if (pqAttach->spwndActive &&
(pqAttach->spwndActivePrev && pti->pq->spwndActivePrev) &&
(pqAttach->spwndActive->spwndNext == pti->pq->spwndActivePrev)) {
Lock(&pqAttach->spwndActivePrev, pti->pq->spwndActivePrev);
Unlock(&pti->pq->spwndActivePrev);
}
break;
}
if (pti == pti->pq->ptiSysLock) {
/*
* Preserve any of the flags we might have already been set on pqAttach.
* Note that these flags might have been set on a previous call
* to this function that received the same pqAttach.
*/
pqAttach->QF_flags ^= ((pqAttach->QF_flags ^ pti->pq->QF_flags)
& ~(QF_CAPTURELOCKED));
/*
* Fix for 29967 "Start menu disappears when clicked and Office
* taskbar has focus!". Win95 uses a global counter instead of this
* flag. In NT when we click on Office taskbar and then on the Start
* Menu, MSoffice calls zzzAttachThreadInput() which changes the Start
* Menu's queue and the new queue has the QF_ACTIVATIONCHANGE flag on.
* Inside the xxxMNLoop we test if this flag is on and if it is we
* exit the menu.
*/
if (!IsInsideMenuLoop(pti)) {
pqAttach->QF_flags &= ~QF_ACTIVATIONCHANGE;
}
/*
* Unlock the queue since pti is moving to anotther queue.
*/
/* CheckSysLock(6, pq, NULL); what number? */
pti->pq->ptiSysLock = NULL;
}
if (gspwndCursor != NULL && pti == GETPTI(gspwndCursor)) {
LockQCursor(pqAttach, pti->pq->spcurCurrent);
}
/*
* Each thread has its own cursor level, which is a count of the number
* of times that app has called show/hide cursor. This gets added into
* the queue's count for a completely accurate count every time this
* queue recalculation is done.
*/
/*
* LATER
* We need to adjust the iCursorLevel of the old queue to reflect
* the fact that a thread is departing.
* FritzS
*/
pqAttach->iCursorLevel += pti->iCursorLevel;
/*
* Pump up the new queue with the right input variables.
*/
pqAttach->ptiMouse = pti;
pqAttach->ptiKeyboard = pti;
pqDestroy = pti->pq;
/*
* Don't increment the thread count here because we already incremented
* it when we put it in pti->pqAttach. Since we're moving it from pqAttach
* to pq, we don't mess with the reference count.
*/
pti->pq = pqAttach;
/*
* If the thread is using the journal queue, leave the message list
* alone. Otherwise, redistribute the messages.
*/
if (pqDestroy != pqJournal) {
/*
* Remember the current message list so it can get redistributed taking
* into account ptiAttach's new queue.
*/
pqmsgT = pqDestroy->mlInput.pqmsgRead;
pqDestroy->mlInput.pqmsgRead = NULL;
pqDestroy->mlInput.pqmsgWriteLast = NULL;
pqDestroy->mlInput.cMsgs = 0;
/*
* Now redistribute the input messages from the old queue they go into the
* right queues.
*
* Preserve the 'idSysPeek' when redistributing the queue
*/
RedistributeInput(pqmsgT, pqDestroy);
/*
* Officially attach the new queue to this thread. Note that zzzDestroyQueue()
* doesn't actually destroy anything until the thread reference count goes
* to 0.
*/
zzzDestroyQueue(pqDestroy, pti);
} else {
UserAssert(pqDestroy->cThreads);
pqDestroy->cThreads--;
}
}
BOOL zzzReattachThreads(
BOOL fJournalAttach)
{
PTHREADINFO ptiCurrent = PtiCurrent();
PTHREADINFO pti;
PQ pqForegroundPrevNew;
PQ pqForegroundNew;
PQ pqAttach;
PQ pqJournal;
PLIST_ENTRY pHead, pEntry;
BOOL bHadAnActiveForegroundWindow;
/*
* In all cases, do not leave do any send messages or any callbacks!
* This is because this code is called from
* SetWindowsHook(WH_JOURNALPLAYBACK | WH_JOURNALRECORD). No app currently
* calling this routine expects to be called before this routine returns.
* (If you do callback before it returns, you'll break at least Access
* for Windows). - scottlu
*/
#if DBG
DBGValidateQueueStates(ptiCurrent->rpdesk);
#endif
/*
* Defer Win Event notifications so we can traverse PtiList with impunity
* Also, we don't want to callback while we're half way attached
*/
DeferWinEventNotify();
BEGINATOMICCHECK();
/*
* Don't recalc attach info if this is a journal attach, because
* the journal attach code has already done this for us.
*/
if (!fJournalAttach) {
/*
* Now recalculate all the different queue groups, based on the
* attach requests. This fills in the pqAttach of each thread info
* with the new queue this thread belongs to. Always takes into
* account all attachment requests.
*/
zzzRecalcThreadAttachment();
/*
* Make a guess about which queue is the journal queue.
*/
pqJournal = gpqForeground;
if (pqJournal == NULL)
pqJournal = ptiCurrent->pq;
/*
* If the queue is only used by one thread, perform normal processing.
*/
if (pqJournal->cThreads == 1) {
pqJournal = NULL;
} else {
/*
* Lock the queue to ensure that it stays valid
* until we have redistributed the input.
*/
(pqJournal->cLockCount)++;
}
} else {
pqJournal = NULL;
}
/*
* What will be the new foreground queue?
*/
pqForegroundNew = NULL;
/*
* Remember if there is a foreground window so we don't force one
* at the end if there wasn't one before the attach
*/
if (gpqForeground != NULL && gpqForeground->spwndActive != NULL) {
bHadAnActiveForegroundWindow = TRUE;
pqForegroundNew = GETPTI(gpqForeground->spwndActive)->pqAttach;
} else {
bHadAnActiveForegroundWindow = FALSE;
}
pqForegroundPrevNew = NULL;
if (gpqForegroundPrev != NULL && gpqForegroundPrev->spwndActivePrev != NULL) {
pqForegroundPrevNew = GETPTI(gpqForegroundPrev->spwndActivePrev)->pqAttach;
}
pHead = &ptiCurrent->rpdesk->PtiList;
for (pEntry = pHead->Flink; pEntry != pHead; pEntry = pEntry->Flink) {
pti = CONTAINING_RECORD(pEntry, THREADINFO, PtiLink);
if (pti->pqAttach == pti->pq) {
pti->pqAttach = NULL;
} else if(pti->pqAttach != NULL) {
/*
* It is crucial that we NULL out pqAttach for this queue once
* we have it in a local variable because the NULL-ness of this
* field is checked in attach operations.
*/
pqAttach = pti->pqAttach;
pti->pqAttach = NULL;
zzzAttachToQueue(pti, pqAttach, pqJournal, pqForegroundNew == pqAttach);
}
}
/*
* If we are doing a journal detach, redistribute the input messages
* from the old queue.
*/
if (pqJournal != NULL) {
PQMSG pqmsgRedist;
UserAssert(pqJournal->cLockCount);
(pqJournal->cLockCount)--;
pqmsgRedist = pqJournal->mlInput.pqmsgRead;
pqJournal->mlInput.pqmsgRead = NULL;
pqJournal->mlInput.pqmsgWriteLast = NULL;
pqJournal->mlInput.cMsgs = 0;
RedistributeInput(pqmsgRedist, pqJournal);
/*
* Only destroy the queue if it is no longer is use.
*/
if (pqJournal->cThreads == 0) {
pqJournal->cThreads = 1; // prevent underflow
zzzDestroyQueue(pqJournal, pti); // DeferWinEventNotify() ?? IANJA ??
} else {
/*
* Make sure that this queue doesn't point to a pti
* no longer attached to it.
* Hopefully we'll go to zzzDestroyQueue only once
* Increment cThreads so the queue won't be destroyed
* but we'll simply reassign the pti fields.
*/
if ((pqJournal->ptiMouse != NULL)
&& (pqJournal != pqJournal->ptiMouse->pq)) {
pqJournal->cThreads++;
zzzDestroyQueue(pqJournal, pqJournal->ptiMouse);
}
if ((pqJournal->ptiKeyboard != NULL)
&& (pqJournal != pqJournal->ptiKeyboard->pq)) {
pqJournal->cThreads++;
zzzDestroyQueue(pqJournal, pqJournal->ptiKeyboard);
}
}
}
#if DBG
DBGValidateQueueStates(ptiCurrent->rpdesk);
#endif
/*
* If the current thread is not on the active desktop, do not
* change the global foreground state.
*/
if (PtiCurrent()->rpdesk != grpdeskRitInput) {
EXITATOMICCHECK();
zzzEndDeferWinEventNotify();
return TRUE;
}
/*
* We're done attaching. gptiForeground hasn't changed... but
* gpqForeground has! Try not to leave NULL as the foreground.
*/
#if DBG
DBGValidateQueue(pqForegroundNew);
#endif
gpqForeground = pqForegroundNew;
// So we can Alt-Esc xxxNextWindow without an AV
// If we have a non-NULL gpqForeground, its kbd input thread better have an rpdesk!
UserAssert(!gpqForeground || (gpqForeground->ptiKeyboard && gpqForeground->ptiKeyboard->rpdesk));
gpqForegroundPrev = pqForegroundPrevNew;
ENDATOMICCHECK();
zzzEndDeferWinEventNotify();
if ((gpqForeground == NULL) && (bHadAnActiveForegroundWindow)) {
PWND pwndNewForeground;
PTHREADINFO pti = PtiCurrent();
pwndNewForeground = _GetNextQueueWindow(pti->rpdesk->pDeskInfo->spwnd->spwndChild, 0, FALSE);
/*
* Don't use xxxSetForegroundWindow2 because we must not leave
* the critical section. There is no currently active foreground
* so all that is needed is to post an activate event to the
* new foreground queue.
*/
if (pwndNewForeground != NULL) {
PostEventMessage(GETPTI(pwndNewForeground),
GETPTI(pwndNewForeground)->pq, QEVENT_ACTIVATE, NULL, 0,
0, (LPARAM)HWq(pwndNewForeground));
}
}
zzzSetFMouseMoved();
UserAssert(gpqForeground == NULL || gpqForeground->ptiMouse->rpdesk == grpdeskRitInput);
return TRUE;
}
BOOL zzzAttachThreadInput(
PTHREADINFO ptiAttach,
PTHREADINFO ptiAttachTo,
BOOL fAttach)
{
CheckCritIn();
/*
* Attaching to yourself doesn't make any sense.
*/
if (ptiAttach == ptiAttachTo)
return FALSE;
#if defined(FE_IME)
/*
* For console IME issue
*
* Console IME do attach to console input thread message queue.
* So needs share message queue for synchronize a key state.
*/
if (IS_IME_ENABLED()) {
PTHREADINFO ptiConsoleIME;
PTHREADINFO ptiConsoleInput;
if ( ((ptiConsoleIME = PtiFromThreadId(ptiAttach->rpdesk->dwConsoleIMEThreadId)) != NULL) &&
((ptiConsoleInput = PtiFromThreadId(ptiAttach->rpdesk->dwConsoleThreadId)) != NULL) &&
(ptiAttach == ptiConsoleIME) &&
(ptiAttachTo == ptiConsoleInput) &&
(ptiConsoleIME->TIF_flags & TIF_DONTATTACHQUEUE)
)
{
goto SkipCheck;
}
}
#endif
/*
* Will this thread allow attaching? Shell threads and system threads
* won't allow attaching.
*/
if (ptiAttachTo->TIF_flags & TIF_DONTATTACHQUEUE) {
return FALSE;
}
if (ptiAttach->TIF_flags & TIF_DONTATTACHQUEUE) {
return FALSE;
}
#if defined(FE_IME)
SkipCheck:
#endif
/*
* Don't allow attaching across desktops, either.
*/
if (ptiAttachTo->rpdesk != ptiAttach->rpdesk) {
return FALSE;
}
/*
* If attaching, make a new attachinfo structure for this thread.
* If not attaching, remove an existing attach reference.
*/
if (fAttach) {
PATTACHINFO pai;
/*
* Alloc a new attachinfo struct, fill it in, link it in.
*/
if ((pai = (PATTACHINFO)UserAllocPool(sizeof(ATTACHINFO), TAG_ATTACHINFO)) == NULL)
return FALSE;
pai->pti1 = ptiAttach;
pai->pti2 = ptiAttachTo;;
pai->paiNext = gpai;
gpai = pai;
} else {
PATTACHINFO *ppai;
BOOL fFound = FALSE;
/*
* Search for this attachinfo struct. If we can't find it, fail.
* If we do find it, unlink it and free it.
*/
for (ppai = &gpai; (*ppai) != NULL; ppai = &(*ppai)->paiNext) {
if (((*ppai)->pti2 == ptiAttachTo) && ((*ppai)->pti1 == ptiAttach)) {
PATTACHINFO paiKill = *ppai;
fFound = TRUE;
*ppai = (*ppai)->paiNext;
UserFreePool((HLOCAL)paiKill);
break;
}
}
/*
* If we couldn't find this reference, then fail.
*/
if (!fFound) {
return FALSE;
}
}
/*
* Now do the actual reattachment work for all threads - unless we're
* journalling. If we did by mistake do attachment while journalling
* was occuring, journalling would be hosed because journalling requires
* all threads to be attached - but it is also treated as a special
* case so it doesn't affect the ATTACHINFO structures. Therefore
* recalcing attach info based on ATTACHINFO structures would break
* the attachment required for journalling.
*/
if (!FJOURNALRECORD() && !FJOURNALPLAYBACK()) {
return zzzReattachThreads(FALSE);
}
return TRUE;
}
/***************************************************************************\
* _SetMessageExtraInfo (API)
*
* History:
* 1-May-1995 FritzS
\***************************************************************************/
LONG_PTR _SetMessageExtraInfo(LONG_PTR lData)
{
LONG_PTR lRet;
PTHREADINFO pti = PtiCurrent();
lRet = pti->pq->ExtraInfo;
pti->pq->ExtraInfo = lData;
return lRet;
}