// // IM.C // Input Manager // // Copyright(c) 1997- // #include // // IM_DDInit() // This creates the resources we need for controlling // BOOL IM_DDInit(void) { UINT uSel; BOOL rc = FALSE; DebugEntry(IM_DDInit); // // Create interrupt patches for mouse_event and keybd_event // uSel = CreateFnPatch(mouse_event, ASMMouseEvent, &g_imPatches[IM_MOUSEEVENT], 0); if (!uSel) { ERROR_OUT(("Couldn't find mouse_event")); DC_QUIT; } g_imPatches[IM_MOUSEEVENT].fInterruptable = TRUE; if (!CreateFnPatch(keybd_event, ASMKeyboardEvent, &g_imPatches[IM_KEYBOARDEVENT], uSel)) { ERROR_OUT(("Couldn't find keybd_event")); DC_QUIT; } g_imPatches[IM_KEYBOARDEVENT].fInterruptable = TRUE; // // Create patch for SignalProc32 so we can find out when fault/hung // dialogs from KERNEL32 come up. // if (!CreateFnPatch(SignalProc32, DrvSignalProc32, &g_imPatches[IM_SIGNALPROC32], 0)) { ERROR_OUT(("Couldn't patch SignalProc32")); DC_QUIT; } // // Create patches for win16lock pulsing in 16-bit app modal loops // uSel = CreateFnPatch(RealGetCursorPos, DrvGetCursorPos, &g_imPatches[IM_GETCURSORPOS], 0); if (!uSel) { ERROR_OUT(("Couldn't find GetCursorPos")); DC_QUIT; } if (!CreateFnPatch(GetAsyncKeyState, DrvGetAsyncKeyState, &g_imPatches[IM_GETASYNCKEYSTATE], 0)) { ERROR_OUT(("Couldn't find GetAsyncKeyState")); DC_QUIT; } rc = TRUE; DC_EXIT_POINT: DebugExitBOOL(IM_DDInit, rc); return(rc); } // // IM_DDTerm() // This cleans up any resources we needed for controlling // void IM_DDTerm(void) { IM_PATCH imPatch; DebugEntry(IM_DDTerm); // // Force undo of hooks // OSIInstallControlledHooks16(FALSE, FALSE); // // Destroy patches // for (imPatch = IM_FIRST; imPatch < IM_MAX; imPatch++) { DestroyFnPatch(&g_imPatches[imPatch]); } DebugExitVOID(IM_DDTerm); } // // OSIInstallControlledHooks16() // // This installs/removes the input hooks we need to allow this machine to // be controlled. // BOOL WINAPI OSIInstallControlledHooks16(BOOL fEnable, BOOL fDesktop) { BOOL rc = TRUE; IM_PATCH imPatch; DebugEntry(OSIInstallControlledHooks16); if (fEnable) { if (!g_imWin95Data.imLowLevelHooks) { g_imWin95Data.imLowLevelHooks = TRUE; g_imMouseDowns = 0; // // GlobalSmartPageLock() stuff we need: // * Our code segment // * Our data segment // GlobalSmartPageLock((HGLOBAL)SELECTOROF((LPVOID)DrvMouseEvent)); GlobalSmartPageLock((HGLOBAL)SELECTOROF((LPVOID)&g_imSharedData)); // // Install hooks // for (imPatch = IM_FIRST; imPatch < IM_MAX; imPatch++) { EnableFnPatch(&g_imPatches[imPatch], PATCH_ACTIVATE); } } // // Install high-level mouse hook if (!fDesktop) { if (!g_imWin95Data.imhHighLevelMouseHook) { // // Install the mouse hook. // g_imWin95Data.imhHighLevelMouseHook = SetWindowsHookEx(WH_MOUSE, IMMouseHookProc, g_hInstAs16, 0); if (!g_imWin95Data.imhHighLevelMouseHook) { ERROR_OUT(("Failed to install mouse hook")); rc = FALSE; } } } } else { if (g_imWin95Data.imLowLevelHooks) { // // Uninstall hooks // for (imPatch = IM_MAX; imPatch > 0; imPatch--) { EnableFnPatch(&g_imPatches[imPatch-1], PATCH_DEACTIVATE); } // // GlobalSmartUnPageLock() stuff we needed // GlobalSmartPageUnlock((HGLOBAL)SELECTOROF((LPVOID)&g_imSharedData)); GlobalSmartPageUnlock((HGLOBAL)SELECTOROF((LPVOID)DrvMouseEvent)); g_imWin95Data.imLowLevelHooks = FALSE; } if (!fDesktop) { if (g_imWin95Data.imhHighLevelMouseHook) { // // Remove the mouse hook. // UnhookWindowsHookEx(g_imWin95Data.imhHighLevelMouseHook); g_imWin95Data.imhHighLevelMouseHook = NULL; } } } DebugExitBOOL(OSIInstallControlledHooks16, rc); return(rc); } #pragma optimize("gle", off) void IMInject(BOOL fOn) { if (fOn) { #ifdef DEBUG DWORD tmp; // // Disable interrupts then turn injection global on // But before we do this, we must make sure that we aren't going // to have to fault in a new stack page. Since this is on a 32-bit // thread, we will be in trouble. // tmp = GetSelectorBase(SELECTOROF(((LPVOID)&fOn))) + OFFSETOF((LPVOID)&fOn); if ((tmp & 0xFFFFF000) != ((tmp - 0x100) & 0xFFFFF000)) { ERROR_OUT(("Close to page boundary on 32-bit stack %08lx", tmp)); } #endif // DEBUG _asm cli g_imWin95Data.imInjecting = TRUE; } else { // // Turn injection global off then enable interrupts // g_imWin95Data.imInjecting = FALSE; _asm sti } } #pragma optimize("", on) // // OSIInjectMouseEvent16() // void WINAPI OSIInjectMouseEvent16 ( UINT flags, int x, int y, UINT mouseData, DWORD dwExtraInfo ) { DebugEntry(OSIInjectMouseEvent16); if (flags & IM_MOUSEEVENTF_BUTTONDOWN_FLAGS) { ++g_imMouseDowns; } // // We disable interrupts, call the real mouse_event, reenable // interrupts. That way our mouse_event patch is serialized. // And we can check imInjecting. // IMInject(TRUE); CallMouseEvent(flags, x, y, mouseData, LOWORD(dwExtraInfo), HIWORD(dwExtraInfo)); IMInject(FALSE); if (flags & IM_MOUSEEVENTF_BUTTONUP_FLAGS) { --g_imMouseDowns; ASSERT(g_imMouseDowns >= 0); } DebugExitVOID(OSIInjectMouseEvent16); } // // OSIInjectKeyboardEvent16() // void WINAPI OSIInjectKeyboardEvent16 ( UINT flags, WORD vkCode, WORD scanCode, DWORD dwExtraInfo ) { DebugEntry(OSIInjectKeyboardEvent16); // // First, fix up the flags // if (flags & KEYEVENTF_KEYUP) { // Put 0x80 in the HIBYTE of vkCode, this means a keyup vkCode = (WORD)(BYTE)vkCode | USERKEYEVENTF_KEYUP; } if (flags & KEYEVENTF_EXTENDEDKEY) { // Put 0x01 in the HIBYTE of scanCode, this means extended scanCode = (WORD)(BYTE)scanCode | USERKEYEVENTF_EXTENDEDKEY; } // // We disable interrupts, call the real keybd_event, reenable // interrupts. That way our keybd_event patch is serialized. // And we can check the imfInject variable. // IMInject(TRUE); CallKeyboardEvent(vkCode, scanCode, LOWORD(dwExtraInfo), HIWORD(dwExtraInfo)); IMInject(FALSE); DebugExitVOID(OSIInjectKeyboardEvent16); } // // Win16lock pulse points when injecting mouse down/up sequences into 16-bit // modal loop apps. // // // IMCheckWin16LockPulse() // This pulses the win16lock if we are in the middle of injecting a mouse // down-up sequence to a 16-bit app shared on this machine. We do this to // prevent deadlock, caused by 16-bit dudes going into modal loops, not // releasing the win16lock. Our 32-bit thread would get stuck on the win16 // lock trying to play back the rest of the sequence. // void IMCheckWin16LockPulse(void) { DebugEntry(IMCheckWin16LockPulse); if ((g_imMouseDowns > 0) && (GetProcessDword(0, GPD_FLAGS) & GPF_WIN16_PROCESS)) { TRACE_OUT(("Pulsing win16lock for 16-bit app; mouse down count %d", g_imMouseDowns)); _LeaveWin16Lock(); _EnterWin16Lock(); TRACE_OUT(("Pulsed win16lock for 16-bit app; mouse down count %d", g_imMouseDowns)); } DebugExitVOID(IMCheckWin16LockPulse); } int WINAPI DrvGetAsyncKeyState(int vk) { int retVal; DebugEntry(DrvGetAsyncKeyState); // Pulse BEFORE we call to USER IMCheckWin16LockPulse(); EnableFnPatch(&g_imPatches[IM_GETASYNCKEYSTATE], PATCH_DISABLE); retVal = GetAsyncKeyState(vk); EnableFnPatch(&g_imPatches[IM_GETASYNCKEYSTATE], PATCH_ENABLE); DebugExitBOOL(DrvGetAsyncKeyState, retVal); return(retVal); } // // DrvGetCursorPos() // BOOL WINAPI DrvGetCursorPos(LPPOINT lppt) { BOOL retVal; DebugEntry(DrvGetCursorPos); // Pulse BEFORE calling USER IMCheckWin16LockPulse(); EnableFnPatch(&g_imPatches[IM_GETCURSORPOS], PATCH_DISABLE); retVal = RealGetCursorPos(lppt); EnableFnPatch(&g_imPatches[IM_GETCURSORPOS], PATCH_ENABLE); DebugExitBOOL(DrvGetCursorPos, retVal); return(retVal); } // // IMMouseHookProc() // High level mouse hook to make sure mice messages are going where we // think they should when your machine is being controlled. // LRESULT CALLBACK IMMouseHookProc ( int code, WPARAM wParam, LPARAM lParam ) { LRESULT rc; BOOL fBlock = FALSE; LPMOUSEHOOKSTRUCT lpMseHook = (LPMOUSEHOOKSTRUCT)lParam; DebugEntry(IMMouseHookProc); if (code < 0) { // // Only pass along // DC_QUIT; } // // Decide if we should block this event. We will if it is not destined // for a hosted window and not destined for a screen saver. // if (!HET_WindowIsHosted(lpMseHook->hwnd) && !OSIIsWindowScreenSaver16(lpMseHook->hwnd)) { fBlock = TRUE; } TRACE_OUT(("MOUSEHOOK hwnd %04x -> block: %s", lpMseHook->hwnd, (fBlock ? (LPSTR)"YES" : (LPSTR)"NO"))); DC_EXIT_POINT: // // Call the next hook // rc = CallNextHookEx(g_imWin95Data.imhHighLevelMouseHook, code, wParam, lParam); if (fBlock) { // // To block further processing in USER, return TRUE // rc = TRUE; } DebugExitDWORD(IMMouseHookProc, rc); return(rc); } // // DrvMouseEvent() // mouse_event interrupt patch // void WINAPI DrvMouseEvent ( UINT regAX, UINT regBX, UINT regCX, UINT regDX, UINT regSI, UINT regDI ) { BOOL fAllow; // // If this is injected by us, just pass it through. // fAllow = TRUE; if (g_imWin95Data.imInjecting) { DC_QUIT; } // // NOTE: // flags is in AX // x coord is in BX // y coord is in CX // mousedata is in DX // dwExtraInfo is in DI, SI // if (g_imSharedData.imControlled && !g_imSharedData.imPaused) { // // If this is a button click, take control back // if (regAX & (MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_MIDDLEDOWN)) { if (!g_imSharedData.imUnattended) { PostMessage(g_asMainWindow, DCS_REVOKECONTROL_MSG, FALSE, 0); } } if (!g_imSharedData.imSuspended) fAllow = FALSE; } DC_EXIT_POINT: if (fAllow) { EnableFnPatch(&g_imPatches[IM_MOUSEEVENT], PATCH_DISABLE); CallMouseEvent(regAX, regBX, regCX, regDX, regSI, regDI); EnableFnPatch(&g_imPatches[IM_MOUSEEVENT], PATCH_ENABLE); } } // // DrvKeyboardEvent() // keybd_event interrupt patch // void WINAPI DrvKeyboardEvent ( UINT regAX, UINT regBX, UINT regSI, UINT regDI ) { BOOL fAllow; // // If this is injected by us, pass it through. Do the same for // critical errors, since everything is frozen and we can't play back // input if we wanted to. // // If the scan-code (in regBX) is 0 we assume that the input // is injected by an application (such as an IME) and we don't // want to block this or take control. // fAllow = TRUE; if (g_imWin95Data.imInjecting || !regBX) { DC_QUIT; } // // NOTE: // vkCode is in AX, LOBYTE is vkCode, HIBYTE is state // scanCode is in BX // dwExtraInfo is in DI, SI // if (g_imSharedData.imControlled && !g_imSharedData.imPaused) { if (!(regAX & USERKEYEVENTF_KEYUP)) { // // This is a key down. Take control back (except for ALT key), // and kill control allowability if it's the ESC key. // if (LOBYTE(regAX) == VK_ESCAPE) { PostMessage(g_asMainWindow, DCS_ALLOWCONTROL_MSG, FALSE, 0); } else if (LOBYTE(regAX != VK_MENU)) { if (!g_imSharedData.imUnattended) { PostMessage(g_asMainWindow, DCS_REVOKECONTROL_MSG, 0, 0); } } } // // Don't discard toggle keys. The enabled/disabled function // is already set before we see the keystroke. If we discard, // the lights are incorrect. // if (!IM_KEY_IS_TOGGLE(LOBYTE(regAX)) && !g_imSharedData.imSuspended) { fAllow = FALSE; } } DC_EXIT_POINT: if (fAllow) { EnableFnPatch(&g_imPatches[IM_KEYBOARDEVENT], PATCH_DISABLE); CallKeyboardEvent(regAX, regBX, regSI, regDI); EnableFnPatch(&g_imPatches[IM_KEYBOARDEVENT], PATCH_ENABLE); } } // // DrvSignalProc32() // This patches USER's SignalProc32 export and watches for the FORCE_LOCK // signals. KERNEL32 calls them before/after putting up critical error and // fault dialogs. That's how we know when one is coming up, and can // temporarily suspend remote control of your machine so you can dismiss // them. Usually the thread they are on is boosted so high priority that // nothing else can run, and so NM can't pump in input from the remote. // BOOL WINAPI DrvSignalProc32 ( DWORD dwSignal, DWORD dwID, DWORD dwFlags, WORD hTask16 ) { BOOL fRet; DebugEntry(DrvSignalProc32); if (dwSignal == SIG_PRE_FORCE_LOCK) { TRACE_OUT(("Disabling remote control before critical dialog, count %ld", g_imSharedData.imSuspended)); ++g_imSharedData.imSuspended; } EnableFnPatch(&g_imPatches[IM_SIGNALPROC32], PATCH_DISABLE); fRet = SignalProc32(dwSignal, dwID, dwFlags, hTask16); EnableFnPatch(&g_imPatches[IM_SIGNALPROC32], PATCH_ENABLE); if (dwSignal == SIG_POST_FORCE_LOCK) { --g_imSharedData.imSuspended; TRACE_OUT(("Enabling remote control after critical dialog, count %ld", g_imSharedData.imSuspended)); } DebugExitBOOL(DrvSignalProc32, fRet); return(fRet); }