windows-nt/Source/XPSP1/NT/termsrv/remdsk/rds/as/cpi32.nt/im.cpp
2020-09-26 16:20:57 +08:00

608 lines
16 KiB
C++

#include "precomp.h"
//
// IM.CPP
// Input Manager, NT specific code
//
#define MLZ_FILE_ZONE ZONE_INPUT
//
// OSI_InstallControlledHooks()
//
// Installs/removes input hooks for control
//
BOOL WINAPI OSI_InstallControlledHooks(BOOL fEnable)
{
BOOL rc = FALSE;
DebugEntry(OSI_InstallControlledHooks);
if (fEnable)
{
//
// Create the service thread, it will install the hooks.
//
ASSERT(!g_imNTData.imLowLevelInputThread);
if (!DCS_StartThread(IMLowLevelInputProcessor))
{
ERROR_OUT(( "Failed to create LL IM thread"));
DC_QUIT;
}
}
else
{
if (g_imNTData.imLowLevelInputThread != 0)
{
PostThreadMessage( g_imNTData.imLowLevelInputThread, WM_QUIT, 0, 0);
g_imNTData.imLowLevelInputThread = 0;
}
}
rc = TRUE;
DC_EXIT_POINT:
DebugExitBOOL(OSI_InstallControlledHooks, rc);
return(rc);
}
// Name: IMLowLevelInputProcessor
//
// Purpose: Main function for the low-level input handler thread.
//
// Returns: wParam of the WM_QUIT message.
//
// Params: syncObject - sync object that allows this thread to signal
// the creating thread via COM_SignalThreadStarted.
//
// Operation: This function is the start point for the low-level input
// handler thread.
//
// We raise the priority of this thread to:
// (a) ensure that we avoid hitting the low-level callback
// timeout - which would cause us to miss events.
// (b) minimize visible mouse movement lag on the screen.
//
// The thread installs the low-level hooks and enters a
// GetMessage/DispatchMessage loop which handles the low-level
// callbacks.
//
// The Share Core sends the thread a WM_QUIT message to
// terminate it, which causes it to exit the message loop and
// removes the low-level hooks before it terminates.
//
DWORD WINAPI IMLowLevelInputProcessor(LPVOID hEventWait)
{
MSG msg;
UINT rc = 0;
DebugEntry(IMLowLevelInputProcessor);
TRACE_OUT(( "Thread started..."));
//
// Give ourseleves the highest possible priority (within our process
// priority class) to ensure that the low-level events are serviced as
// soon as possible.
//
SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
g_imNTData.imLowLevelInputThread = GetCurrentThreadId();
//
// Install low-level input hooks.
//
g_imNTData.imhLowLevelMouseHook = SetWindowsHookEx(
WH_MOUSE_LL,
IMLowLevelMouseProc,
g_asInstance,
0 );
g_imNTData.imhLowLevelKeyboardHook = SetWindowsHookEx(
WH_KEYBOARD_LL,
IMLowLevelKeyboardProc,
g_asInstance,
0 );
//
// We're done with our init code, for better or for worse. Let the
// calling thread continue.
//
SetEvent((HANDLE)hEventWait);
if ( (g_imNTData.imhLowLevelMouseHook == NULL) ||
(g_imNTData.imhLowLevelKeyboardHook == NULL) )
{
ERROR_OUT(( "SetWindowsHookEx failed: hMouse(%u) hKeyboard(%u)",
g_imNTData.imhLowLevelMouseHook, g_imNTData.imhLowLevelKeyboardHook ));
DC_QUIT;
}
//
// Do our message loop to get events
//
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//
// Remove hooks
//
if (g_imNTData.imhLowLevelMouseHook != NULL)
{
UnhookWindowsHookEx(g_imNTData.imhLowLevelMouseHook);
g_imNTData.imhLowLevelMouseHook = NULL;
}
if (g_imNTData.imhLowLevelKeyboardHook != NULL)
{
UnhookWindowsHookEx(g_imNTData.imhLowLevelKeyboardHook);
g_imNTData.imhLowLevelKeyboardHook = NULL;
}
DC_EXIT_POINT:
DebugExitDWORD(IMLowLevelInputProcessor, rc);
return(rc);
}
//
// Name: IMOtherDesktopProc()
//
// This allows us to inject (but not block) input into other desktops
// besides default, where the user's desktop resides. Specifically, the
// winlogon desktop and/or the screensaver desktop.
//
// This is trickier than it might seem, because the winlogon desktop is
// always around, but the screen saver one is transitory.
//
// The periodic SWL_ code, called when hosting, checks for the current
// desktop and if it's switched posts us a message so we can change our
// desktop and our hooks.
//
DWORD WINAPI IMOtherDesktopProc(LPVOID hEventWait)
{
MSG msg;
UINT rc = 0;
HDESK hDesktop;
GUIEFFECTS effects;
DebugEntry(IMOtherDesktopProc);
TRACE_OUT(("Other desktop thread started..."));
g_imNTData.imOtherDesktopThread = GetCurrentThreadId();
//
// Start out attached to the WinLogon desktop because it's always
// around.
//
// Set our desktop to the winlogon desktop
hDesktop = OpenDesktop(NAME_DESKTOP_WINLOGON,
0,
FALSE,
DESKTOP_JOURNALPLAYBACK);
if ( !hDesktop )
{
WARNING_OUT(("OpenDesktop failed: %ld", GetLastError()));
DC_QUIT;
}
else if (!SetThreadDesktop (hDesktop))
{
WARNING_OUT(("SetThreadDesktop failed: %ld", GetLastError()));
DC_QUIT;
}
//
// Attempt to load the driver dynamically on this thread also.
//
if (g_asNT5)
{
OSI_InitDriver50(TRUE);
}
// Let the calling thread continue.
SetEvent((HANDLE)hEventWait);
ZeroMemory(&effects, sizeof(effects));
while (GetMessage(&msg, NULL, 0, 0))
{
switch(msg.message)
{
case OSI_WM_MOUSEINJECT:
mouse_event(
LOWORD(msg.wParam), // flags
HIWORD(msg.lParam), // x
LOWORD(msg.lParam), // y
HIWORD(msg.wParam), // mouseData
0); // dwExtraInfo
break;
case OSI_WM_KEYBDINJECT:
keybd_event(
(BYTE)(LOWORD(msg.lParam)), // vkCode
(BYTE)(HIWORD(msg.lParam)), // scanCode
(DWORD)msg.wParam, // flags
0); // dwExtraInfo
break;
case OSI_WM_DESKTOPREPAINT:
USR_RepaintWindow(NULL);
break;
case OSI_WM_INJECTSAS:
{
HWND hwndSAS;
if ( hwndSAS = FindWindow("SAS window class",NULL))
{
PostMessage(hwndSAS,WM_HOTKEY,0,
MAKELONG(0x8000|MOD_ALT|MOD_CONTROL,VK_DELETE));
}
else
{
WARNING_OUT(("SAS window not found, on screensaver desktop"));
}
break;
}
case OSI_WM_DESKTOPSWITCH:
{
HDESK hDesktopNew;
TRACE_OUT(("OSI_WM_DESKTOPSWITCH: switching desktop from %d to %d",
msg.wParam, msg.lParam));
if (msg.lParam == DESKTOP_SCREENSAVER)
{
// We're switching TO the screensaver, attach to it.
TRACE_OUT(("Switching TO screensaver"));
hDesktopNew = OpenDesktop(NAME_DESKTOP_SCREENSAVER,
0, FALSE, DESKTOP_JOURNALPLAYBACK);
}
else if (msg.wParam == DESKTOP_SCREENSAVER)
{
//
// We're switching FROM the screensaver, reattach to
// winlogon
//
TRACE_OUT(("Switching FROM screensaver"));
hDesktopNew = OpenDesktop(NAME_DESKTOP_WINLOGON,
0, FALSE, DESKTOP_JOURNALPLAYBACK);
}
else
{
hDesktopNew = NULL;
}
if (hDesktopNew != NULL)
{
if (!SetThreadDesktop(hDesktopNew))
{
WARNING_OUT(("SetThreadDesktop to 0x%08x, type %d failed",
hDesktopNew, msg.lParam));
}
else
{
CloseHandle(hDesktop);
hDesktop = hDesktopNew;
}
}
break;
}
case OSI_WM_SETGUIEFFECTS:
{
HET_SetGUIEffects((msg.wParam != 0), &effects);
break;
}
}
}
DC_EXIT_POINT:
if (g_asNT5)
{
OSI_InitDriver50(FALSE);
}
if (hDesktop)
{
CloseHandle(hDesktop);
}
g_imNTData.imOtherDesktopThread = 0;
DebugExitDWORD(IMOtherDesktopProc, rc);
return(rc);
}
//
// IMLowLevelMouseProc()
// NT callback for low-level mouse events.
//
// It is installed and called on a secondary thread with high priority to
// service the APC call outs. It follows the windows hook conventions for
// parameters and return values--zero to accept the event, non-zero to
// discard.
//
//
LRESULT CALLBACK IMLowLevelMouseProc
(
int nCode,
WPARAM wParam,
LPARAM lParam
)
{
LRESULT rc = 0;
PMSLLHOOKSTRUCT pMouseEvent;
DebugEntry(IMLowLevelMouseProc);
pMouseEvent = (PMSLLHOOKSTRUCT)lParam;
//
// If this isn't for an event that is happening or it's one we
// injected ourself, pass it through and no need for processing.
//
if ((nCode != HC_ACTION) || (pMouseEvent->flags & LLMHF_INJECTED))
{
DC_QUIT;
}
//
// This is a local user event. If controlled, throw it away. Unless
// it's a click, in that case post a REVOKECONTROL message.
//
if (g_imSharedData.imControlled)
{
//
// If this is a button click, take control back
//
if ((wParam == WM_LBUTTONDOWN) ||
(wParam == WM_RBUTTONDOWN) ||
(wParam == WM_MBUTTONDOWN))
{
//
// Don't take control back if this is unattended.
//
if (!g_imSharedData.imUnattended)
{
PostMessage(g_asMainWindow, DCS_REVOKECONTROL_MSG, 0, 0);
}
}
// Swallow event.
rc = 1;
}
DC_EXIT_POINT:
//
// Don't pass on to the next hook (if there is one) if we are
// discarding the event.
//
if (!rc)
{
rc = CallNextHookEx(g_imNTData.imhLowLevelMouseHook, nCode,
wParam, lParam);
}
DebugExitDWORD(IMLowLevelMouseProc, rc);
return(rc);
}
// Name: IMLowLevelKeyboardProc
//
// Purpose: Windows callback function for low-level keyboard events.
//
// Returns: 0 if event is to be passed on to USER.
// 1 if event is to be discarded.
//
// Params: Low-level callback params (see Windows documentation).
//
// Operation: Determines whether to allow the given event into USER.
//
// We always pass on injected events.
// The Control Arbitrator determines whether local events are
// passed on.
//
LRESULT CALLBACK IMLowLevelKeyboardProc
(
int nCode,
WPARAM wParam,
LPARAM lParam
)
{
LRESULT rc = 0;
PKBDLLHOOKSTRUCT pKbdEvent;
DebugEntry(IMLowLevelKeyboardProc);
pKbdEvent = (PKBDLLHOOKSTRUCT)lParam;
//
// If this isn't for an action or it's an event we ourself originated,
// let it through, and do no processing.
//
if ((nCode != HC_ACTION) || (pKbdEvent->flags & LLKHF_INJECTED))
{
DC_QUIT;
}
if (g_imSharedData.imControlled)
{
if (!(pKbdEvent->flags & LLKHF_UP))
{
//
// This is a key down. Take control back, and kill control
// allowability if it's the ESC key.
//
if ((pKbdEvent->vkCode & 0x00FF) == VK_ESCAPE)
{
// ESC key always disallows control, even in unattended mode
PostMessage(g_asMainWindow, DCS_ALLOWCONTROL_MSG, FALSE, 0);
}
else 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.
//
// LAURABU: How do we fix this in new model? Post a toggle-key
// message and undo it (fake press)?
//
if (!IM_KEY_IS_TOGGLE(pKbdEvent->vkCode & 0x00FF))
rc = 1;
}
DC_EXIT_POINT:
//
// Don't pass on to the next hook if we are swallowing the event.
//
if (!rc)
{
rc = CallNextHookEx(g_imNTData.imhLowLevelKeyboardHook,
nCode, wParam, lParam);
}
DebugExitDWORD(IMLowLevelKeyboardProc, rc);
return(rc);
}
//
// IMInjectMouseEvent()
// NT-specific version to inject mouse events into the local system
//
void WINAPI OSI_InjectMouseEvent
(
DWORD flags,
LONG x,
LONG y,
DWORD mouseData,
DWORD dwExtraInfo
)
{
TRACE_OUT(("Before MOUSE inject: %08lx, %08lx %08lx",
flags, mouseData, dwExtraInfo));
mouse_event(flags, (DWORD)x, (DWORD)y, mouseData, dwExtraInfo);
if ( g_imNTData.imOtherDesktopThread )
{
// Stuff these dword parameters through WORDS
// need to make sure we don't clip anything
ASSERT(!(flags & 0xffff0000));
//ASSERT(!(mouseData & 0xffff0000)); BUGBUG possible loss
ASSERT(!(x & 0xffff0000));
ASSERT(!(y & 0xffff0000));
PostThreadMessage(
g_imNTData.imOtherDesktopThread,
OSI_WM_MOUSEINJECT,
MAKEWPARAM((WORD)flags,(WORD)mouseData),
MAKELPARAM((WORD)y, (WORD)x ));
}
TRACE_OUT(("After MOUSE inject"));
}
//
// OSI_InjectSAS()
// NT-specific version to inject ctrl+alt+del into the local system
//
void WINAPI OSI_InjectCtrlAltDel(void)
{
if ( g_imNTData.imOtherDesktopThread )
{
PostThreadMessage(
g_imNTData.imOtherDesktopThread,
OSI_WM_INJECTSAS,
0,
0 );
}
else
{
WARNING_OUT(("Ignoring SAS Injection attempt"));
}
}
//
// OSI_InjectKeyboardEvent()
// NT-specific version to inject keyboard events into the local system
//
void WINAPI OSI_InjectKeyboardEvent
(
DWORD flags,
WORD vkCode,
WORD scanCode,
DWORD dwExtraInfo
)
{
TRACE_OUT(("Before KEY inject: %04lx, {%04x, %04x}, %04lx",
flags, vkCode, scanCode, dwExtraInfo));
keybd_event((BYTE)vkCode, (BYTE)scanCode, flags, dwExtraInfo);
if ( g_imNTData.imOtherDesktopThread )
{
PostThreadMessage(
g_imNTData.imOtherDesktopThread,
OSI_WM_KEYBDINJECT,
(WPARAM)flags,
MAKELPARAM(vkCode, scanCode));
}
TRACE_OUT(("After KEY inject"));
}
//
// OSI_DesktopSwitch()
// NT-specific, called when we think the current desktop has changed.
//
void WINAPI OSI_DesktopSwitch
(
UINT desktopFrom,
UINT desktopTo
)
{
DebugEntry(OSI_DesktopSwitch);
if (g_imNTData.imOtherDesktopThread)
{
PostThreadMessage(
g_imNTData.imOtherDesktopThread,
OSI_WM_DESKTOPSWITCH,
desktopFrom,
desktopTo);
}
DebugExitVOID(OSI_DesktopSwitch);
}