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



