/****************************** 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; }