You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
646 lines
16 KiB
646 lines
16 KiB
//
|
|
// IM.C
|
|
// Input Manager
|
|
//
|
|
// Copyright(c) 1997-
|
|
//
|
|
|
|
#include <as16.h>
|
|
|
|
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
|
|
|
|
|