#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); }