#include "precomp.h"


//
// IM.CPP
// Input Manager
//
// Copyright(c) Microsoft 1997-
//
#include <confreg.h>

#define MLZ_FILE_ZONE  ZONE_INPUT




//
// IM_ShareStarting()
//
BOOL ASShare::IM_ShareStarting(void)
{
    BOOL    rc = FALSE;
    HKEY    hkeyBandwidth;
    UINT    i;
    BYTE    tmpVK;

    DebugEntry(ASShare::IM_ShareStarting);

    //
    // Find out the scan codes for the left and right shift keys.
    //

    //
    // SFR 2537: Get the scan codes for this keyboard for the left-right
    // variants of SHIFT.
    //
    // We do not do this for the left-right variants of CONTROL and ALT (ie
    // menu) because they are extended keys.
    //
    // The scan codes are used in the keyboard hook (when sending) and in
    // the network translate to OS routine (when receiving), to
    // distinguish between the left-right variants of VK_SHIFT, where
    // Windows only reports a single value.
    //
    // This method is pretty long
    //
    m_imScanVKLShift = (BYTE) MapVirtualKey(VK_SHIFT, 0);
    for (i = 0; i < 256; i++)
    {
        tmpVK = (BYTE)MapVirtualKey(i, 1);
        if ( (tmpVK == VK_SHIFT) &&  (i != m_imScanVKLShift) )
        {
            m_imScanVKRShift = (BYTE)i;
            break;
        }
    }

    TRACE_OUT(( "Left/Right VK_SHIFT: scan codes = %02X, %02X",
        m_imScanVKLShift, m_imScanVKRShift));

    //
    // Check the user-reported bandwidth to decide if we should optimize
    // input for bandwidth or latency.
    // BUGBUG will want to vary this via flow control instead in future
    //
    m_imInControlMouseWithhold = 0;
    if (ERROR_SUCCESS == RegOpenKey(HKEY_CURRENT_USER,AUDIO_KEY,&hkeyBandwidth))
    {
        DWORD dwBandwidth = BW_DEFAULT;
        DWORD dwType = REG_DWORD;
        DWORD cbData = sizeof(dwBandwidth);

        if ( ERROR_SUCCESS == RegQueryValueEx(hkeyBandwidth,
                             REGVAL_TYPICALBANDWIDTH, NULL, &dwType,
                             (LPBYTE)&dwBandwidth, &cbData) )
        {
            if ( BW_144KBS == dwBandwidth )
            {
                m_imInControlMouseWithhold = IM_LOCAL_MOUSE_WITHHOLD;
            }
        }

        RegCloseKey(hkeyBandwidth);
    }

    //
    // Find out if this is a DBCS enabled system - if it is then we'll need
    // to load IMM32.DLL.
    //
    ASSERT(m_imImmLib == NULL);
    ASSERT(m_imImmGVK == NULL);

    if (GetSystemMetrics(SM_DBCSENABLED))
    {
        //
        // DBCS system, so load IMM32.DLL
        //
        m_imImmLib = LoadLibrary("imm32.dll");
        if (!m_imImmLib)
        {
            ERROR_OUT(( "Failed to load imm32.dll"));
            DC_QUIT;
        }

        //
        // Now attempt to find the entry point in this DLL.
        //
        m_imImmGVK = (IMMGVK) GetProcAddress(m_imImmLib, "ImmGetVirtualKey");
        if (!m_imImmGVK)
        {
            ERROR_OUT(( "Failed to fixup <ImmGetVirtualKey>"));
            DC_QUIT;
        }
    }

    rc = TRUE;

DC_EXIT_POINT:
    DebugExitBOOL(ASShare::IM_ShareStarting, rc);
    return(rc);
}



//
// IM_ShareEnded()
//
void ASShare::IM_ShareEnded(void)
{
    DebugEntry(ASShare::IM_ShareEnded);

    // Free imm32 dll
    m_imImmGVK = NULL;

    if (m_imImmLib)
    {
        FreeLibrary(m_imImmLib);
        m_imImmLib = NULL;
    }

    DebugExitVOID(ASShare::IM_ShareEnded);
}



//
// IM_Controlled()
//
// Called when we start/stop being controlled.
//
BOOL ASShare::IM_Controlled(ASPerson * pasControlledBy)
{
    BOOL    rc;

    DebugEntry(ASShare::IM_Controlled);

    if (pasControlledBy)
    {
        // Incoming injected input queues should be empty
        ASSERT(m_imControlledEventQ.numEvents == 0);
        ASSERT(m_imControlledEventQ.head == 0);
        ASSERT(m_imControlledOSQ.numEvents == 0);
        ASSERT(m_imControlledOSQ.head == 0);

        //
        // Reset CONTROLLED vars
        //
        m_imfControlledMouseButtonsReversed = (GetSystemMetrics(SM_SWAPBUTTON) != 0);
        m_imfControlledMouseClipped             = FALSE;
        m_imfControlledPaceInjection            = FALSE;
        m_imfControlledNewEvent                 = TRUE;
        m_imControlledNumEventsPending          = 0;
        m_imControlledNumEventsReturned         = 0;

        m_imControlledLastLowLevelMouseEventTime  = GetTickCount();
        m_imControlledLastMouseRemoteTime       = 0;
        m_imControlledLastMouseLocalTime        = 0;
        m_imControlledLastIncompleteConversion  = 0;
        m_imControlledMouseBacklog              = 0;
        GetCursorPos(&m_imControlledLastMousePos);

        // Get current keyboard state
        GetKeyboardState(m_aimControlledKeyStates);

        // Save it so we can put it back when done being controlled
        ASSERT(sizeof(m_aimControlledSavedKeyStates) == sizeof(m_aimControlledKeyStates));
        CopyMemory(m_aimControlledSavedKeyStates, m_aimControlledKeyStates, sizeof(m_aimControlledKeyStates));

        // Clear original keyboard state
        ZeroMemory(m_aimControlledKeyStates, sizeof(m_aimControlledKeyStates));
        SetKeyboardState(m_aimControlledKeyStates);

        //
        // On the other side, the remote will start sending us events to
        // bring our keyboard in sync with his.  Then real input events.
        //
    }
    else
    {
        //
        // We're no longer controlled.  Clear the remote queues.
        //
        m_imControlledOSQ.head = 0;
        m_imControlledOSQ.numEvents = 0;

        m_imControlledEventQ.numEvents = 0;

        //
        // Put back our saved keyboard state
        //
        SetKeyboardState(m_aimControlledSavedKeyStates);
    }

    // Install controlled input hooks
    rc = OSI_InstallControlledHooks((pasControlledBy != NULL), (m_pasLocal->hetCount == HET_DESKTOPSHARED));
    if (!rc)
    {
        ERROR_OUT(("IM_Controlled:  Couldn't install controlled hooks"));
        DC_QUIT;
    }
    g_lpimSharedData->imControlled = (pasControlledBy != NULL);

DC_EXIT_POINT:
    DebugExitBOOL(ASShare:IM_Controlled, rc);
    return(rc);
}



//
// IM_InControl()
//
// Called when we start/stop being in control.  We must observe high-level
// keyboard events.
//
void ASShare::IM_InControl(ASPerson * pasInControlOf)
{
    DebugEntry(ASShare::IM_InControl);

    if (pasInControlOf)
    {
        //
        // Set up InControl vars.
        //

        // Get current key state
        GetKeyboardState(m_aimInControlKeyStates);

        m_imfInControlEventIsPending        = FALSE;
        m_imfInControlCtrlDown              = FALSE;
        m_imfInControlShiftDown             = FALSE;
        m_imfInControlMenuDown              = FALSE;
        m_imfInControlCapsLock              = FALSE;
        m_imfInControlNumLock               = FALSE;
        m_imfInControlScrollLock            = FALSE;
        m_imfInControlConsumeMenuUp         = FALSE;
        m_imfInControlConsumeEscapeUp       = FALSE;
        m_imfInControlNewEvent              = TRUE;
        m_imInControlMouseDownCount         = 0;
        m_imInControlMouseDownTime          = 0;
        m_imInControlMouseSpoilRate         = 0;
        m_imInControlNumEventsPending       = 0;
        m_imInControlNumEventsReturned      = 0;
        m_imInControlNextHotKeyEntry        = 0;

        //
        // Send mouse move with our current position to the dude we're in
        // control of.
        //
        ValidateView(pasInControlOf);
        ASSERT(pasInControlOf->m_caControlledBy == m_pasLocal);
    }
    else
    {
        // Clear outgoing queues
        m_imInControlEventQ.head      = 0;
        m_imInControlEventQ.numEvents = 0;
    }

    DebugExitVOID(ASShare::IM_InControl);
}


//
// IM_Periodic
//
void ASShare::IM_Periodic(void)
{
    POINT      cursorPos;
    UINT       timeDelta;

    DebugEntry(ASShare::IM_Periodic);

    if (m_pasLocal->m_caInControlOf)
    {
        //
        // Send outgoing input to person we're in control of
        //
        IMFlushOutgoingEvents();
    }
    else if (m_pasLocal->m_caControlledBy)
    {
        ASSERT(m_pHost);

        //
        // Playback input from person in control of us
        //
        IMMaybeInjectEvents();

        //
        // Get the current cursor position - we always need this.
        //
        GetCursorPos(&cursorPos);

        //
        // First check if we think that a cursor clip will have affected the
        // position when we replayed a remote event.
        //
        if (m_imfControlledMouseClipped)
        {
            RECT cursorClip;

            //
            // Get the current clip and the current cursor position.
            //
            GetClipCursor(&cursorClip);

            if ((cursorPos.x == cursorClip.left) ||
                (cursorPos.x == (cursorClip.right-1)) ||
                (cursorPos.y == cursorClip.top) ||
                (cursorPos.y == (cursorClip.bottom-1)))
            {
                WARNING_OUT(("CM_ApplicationMovedCursor {%04d, %04d}",
                    cursorPos.x, cursorPos.y));

                //
                // We thought the cursor was going to be clipped and now we
                // find it is right at the edge of the clip so tell the CM to
                // tell its peers about the cursor being moved.
                //
                m_pHost->CM_ApplicationMovedCursor();
                m_imfControlledMouseClipped = FALSE;
            }
        }

        // We are being controlled by somebody else.
        // So now's the time to decide if a SetCursorPos has
        // happened.  For us to believe that a SetCursorPos has actually
        // occurred, the elapsed time since the last low-level input event
        // was injected must be greater than IM_EVENT_PERCOLATE_TIME
        // and the cursor must be in a different position to that which we
        // currently believe it to be.
        //
        if ((cursorPos.x != m_imControlledLastMousePos.x) ||
            (cursorPos.y != m_imControlledLastMousePos.y))
        {
            TRACE_OUT(( "GCP gives (%d,%d), last mouse event is (%d,%d)",
                     cursorPos.x,
                     cursorPos.y,
                     m_imControlledLastMousePos.x,
                     m_imControlledLastMousePos.y));

            //
            // Get the current tick count.
            //
            timeDelta = GetTickCount() - m_imControlledLastLowLevelMouseEventTime;

            if (timeDelta > IM_EVENT_PERCOLATE_TIME)
            {
                //
                // Looks like a SetCursorPos has occured - tell CM.
                //
                WARNING_OUT(("CM_ApplicationMovedCursor {%04d, %04d}",
                    cursorPos.x, cursorPos.y));
                m_pHost->CM_ApplicationMovedCursor();

                //
                // Update the last high level mouse position.
                //
                m_imControlledLastMousePos.x = cursorPos.x;
                m_imControlledLastMousePos.y = cursorPos.y;
            }
        }
    }

    DebugExitVOID(ASShare::IM_Periodic);
}



//
// IM_ReceivedPacket()
//
// A null packet pointer can be used to trigger the injection of another
// pending event
//
//
// DESCRIPTION:
//
// Called when an IM events packet arrives at the PR.  The IM will accept
// the incoming packet.  It may copy it to an internal queue rather than
// process it immediately.  IM events packets contain a series of
// piggybacked IM events.
//
// PARAMETERS:
//
// personID - the source of the packet
//
// pPacket - a pointer to the packet
//
// RETURNS: NONE
//
void ASShare::IM_ReceivedPacket
(
    ASPerson *      pasFrom,
    PS20DATAPACKET  pPacket
)
{
    LPIMPACKET      pIMPacket;
    UINT            i;

    DebugEntry(ASShare::IM_ReceivedPacket);

    if (!pasFrom)
    {
        TRACE_OUT(("Simply inject any pending events in"));
        DC_QUIT;
    }

    ValidatePerson(pasFrom);

    pIMPacket = (PIMPACKET)pPacket;

    // If this person isn't in control of us, blow this off
    if (pasFrom->m_caInControlOf != m_pasLocal)
    {
        PIMEVENT        pimEvent;

        if (pasFrom->cpcCaps.general.version >= CAPS_VERSION_30)
        {
            WARNING_OUT(("Ignoring IM packet from [%d], not in control of us", pasFrom->mcsID));
            DC_QUIT;
        }

        //
        // 2.x COMPAT:  not-controlled folks send IM packets as broadcasts.
        // Fake a mouse move move.  Skip through all the events in the
        // packet to the last mouse move/click/pos info.
        //
        // Note that we don't have to fill in all the S20, S20DATAPACKET,
        // and DATAPACKET header info.
        //

        pimEvent = NULL;
        for (i = 0; i < pIMPacket->numEvents; i++)
        {
            if (pIMPacket->aEvents[i].type == IM_TYPE_3BUTTON)
            {
                pimEvent = &(pIMPacket->aEvents[i]);
            }

        }

        if (pimEvent)
        {
            // Pass fake packet with mouse pos to cursor manager
            TRACE_OUT(("Handling 2.x mouse event to {%04d, %04d}",
                pimEvent->data.mouse.x, pimEvent->data.mouse.y));
            CM_UpdateShadowCursor(pasFrom, pasFrom->cmShadowOff,
                pimEvent->data.mouse.x, pimEvent->data.mouse.y,
                pasFrom->cmHotSpot.x, pasFrom->cmHotSpot.y);
        }

        // Now we're done.
        DC_QUIT;
    }

    //
    // For each packet in the piggybacked packets array...
    //
    TRACE_OUT(("IM_ReceivedPacket:  Processing packet with %d events",
        pIMPacket->numEvents));
    for (i = 0; i < pIMPacket->numEvents; i++)
    {
        switch (pIMPacket->aEvents[i].type)
        {
            case IM_TYPE_ASCII:
            case IM_TYPE_VK1:
            case IM_TYPE_VK2:
            case IM_TYPE_3BUTTON:
            {
                IMAppendNetEvent(&(pIMPacket->aEvents[i]));
                break;
            }

            default:
                //
                // Unexpected events are not error - we just ignore then
                // for future compatibility
                //
                TRACE_OUT(("Person [%d] unrecognised IM type (%04X) - event discarded",
                    pasFrom->mcsID, pIMPacket->aEvents[i].type));
                break;
        }
    }

DC_EXIT_POINT:

    //
    // Our final action is to feed one of the new events into USER.
    // We do NOT feed them all in at once because we want to simulate
    // typing them in, otherwise the amount of spoiling we see is
    // totally dependent upon the network latency and piggybacking.
    //
    ValidatePerson(m_pasLocal);
    if (m_pasLocal->m_caControlledBy)
    {
        //
        // @@@JPB: Temporary - want to inject as many events as possible -
        // this should be moved to a loop within IMMaybeInjectEvents...
        //
        // This greatly improves responsiveness when handling a large
        // number of input events in a short space of time (e.g. pounding
        // on the keyboard) - very little overrun.
        //
        for (i = 0; i < 10; i++)
        {
            IMMaybeInjectEvents();
        }

        //
        // Go into TURBO scheduling if this is a real input packet.
        //
        if (pPacket != NULL)
        {
            SCH_ContinueScheduling(SCH_MODE_TURBO);
        }
    }

    DebugExitVOID(ASShare::IM_ReceivedPacket);
}




//
// IMGetHighLevelKeyState
//
// DESCRIPTION:
//
// Called by the IEM when it is converting a local event to a network event
// to determine the state of the local keyboard when the event was
// generated.
//
// PARAMETERS:
//
// vk - the key
//
// RETURNS:
//
// Flags - bit 7 set/reset key down/up, bit 0 toggle
//
//
BYTE  ASShare::IMGetHighLevelKeyState(UINT  vk)
{
    int     keyState;
    BYTE    rc;

    DebugEntry(ASShare::IMGetHighLevelKeyState);

    keyState = GetKeyState(vk);

    rc = (BYTE) (((keyState & 0x8000) >> 8) | keyState & 0x0001);

    DebugExitDWORD(ASShare::IMGetHighLevelKeyState, rc);
    return(rc);
}



//
// FUNCTION: IMFlushOutgoingEvents
//
// DESCRIPTION:
//
// Called to send new IMEVENTs (as they are generated and periodically).
// This function will send as many IMEVENTs from the current backlog as
// possible.
//
// PARAMETERS: NONE
//
// RETURNS: NONE
//
//
void ASShare::IMFlushOutgoingEvents(void)
{
    UINT        i;
    UINT        sizeOfPacket;
    PIMPACKET   pIMPacket;
    UINT        lastEvent;
    UINT        secondLastEvent;
    UINT        elapsedTime;
    UINT        time;
    UINT        eventsToSend;
    UINT        curTime;
    BOOL        holdPacket;
#ifdef _DEBUG
    UINT        sentSize;
#endif // _DEBUG

    DebugEntry(ASShare::IMFlushOutgoingEvents);

    ValidateView(m_pasLocal->m_caInControlOf);

    //
    // Try to convert the input into a bunch of IMEVENTs
    //
    while (m_imfInControlEventIsPending && (m_imInControlEventQ.numEvents < IM_SIZE_EVENTQ))
    {
        //
        // There is space to try and convert the pending packet.
        //
        m_imfInControlEventIsPending = (IMTranslateOutgoing(&m_imInControlPendingEvent,
                      &m_imInControlEventQ.events[CIRCULAR_INDEX(m_imInControlEventQ.head,
                      m_imInControlEventQ.numEvents, IM_SIZE_EVENTQ)]) != FALSE);
        if (m_imfInControlEventIsPending)
        {
            //
            // We have added a packet to the queue - update our queue
            // tracking variables.
            //
            m_imInControlEventQ.numEvents++;
        }
    }

    //
    // Mouse handling has been improved in the following ways
    //   - withhold generation of packets while we are purely handling
    //     mouse moves and we are within the LOCAL_MOUSE_WITHHOLD range
    //     While we are doing this spoil them to the highest frequency
    //     we are permitted to generate (SAMPLING_GAP_HIGH)
    //   - if we exceed the withholding threshhold but remain within queue
    //     size/2 spoil down to the intermediate range
    //     (SAMPLING_GAP_MEDIUM)
    //   - otherwise spoil down to the low range
    //
    // We spoil the events by hanging on to the last event for a while, if
    // it was a mouse move, so that we can use it for subsequent spoiling.
    // Whenever we get a non-mouse message then we spoil the lot to
    // eliminate latency, on clicks, for example.
    //

    //
    // Calculate the mouse spoil rate - do we need more than just the high
    // rate spoiling?
    //
    if (m_imInControlEventQ.numEvents > m_imInControlMouseWithhold + 1)
    {
        //
        // Are we into intermediate or low spoiling?
        //
        if (m_imInControlEventQ.numEvents < (IM_SIZE_EVENTQ +
                               m_imInControlMouseWithhold) / 2)
        {
            TRACE_OUT(( "Mouse spoil rate to MEDIUM"));
            m_imInControlMouseSpoilRate = IM_LOCAL_MOUSE_SAMPLING_GAP_MEDIUM_MS;
        }
        else
        {
            TRACE_OUT(( "Mouse spoil rate to LOW"));
            m_imInControlMouseSpoilRate = IM_LOCAL_MOUSE_SAMPLING_GAP_LOW_MS;
        }
    }
    else
    {
        //
        // Spoil at the normal high rate
        //
        if (m_imInControlMouseSpoilRate != IM_LOCAL_MOUSE_SAMPLING_GAP_HIGH_MS)
        {
            TRACE_OUT(( "Mouse spoil rate to HIGH"));
            m_imInControlMouseSpoilRate = IM_LOCAL_MOUSE_SAMPLING_GAP_HIGH_MS;
        }
    }

    //
    // Firstly get a pointer to lastEvent for use here and in send arm
    // below (We wont use it if m_imInControlEventQ.numEvents == 0)
    //
    lastEvent = CIRCULAR_INDEX(m_imInControlEventQ.head,
        m_imInControlEventQ.numEvents - 1, IM_SIZE_EVENTQ);

    //
    // Now perform the spoiling, if necessary
    //
    if (m_imInControlEventQ.numEvents > 1)
    {
        if (lastEvent == 0)
        {
            secondLastEvent = IM_SIZE_EVENTQ - 1;
        }
        else
        {
            secondLastEvent = lastEvent - 1;
        }

        elapsedTime = m_imInControlEventQ.events[lastEvent].timeMS
                    - m_imInControlEventQ.events[secondLastEvent].timeMS;
        TRACE_OUT(( "Inter packet time %d, sampling gap %ld",
                    elapsedTime,m_imInControlMouseSpoilRate));

        if ((elapsedTime < m_imInControlMouseSpoilRate) &&
            (m_imInControlEventQ.events[lastEvent].type == IM_TYPE_3BUTTON) &&
            (m_imInControlEventQ.events[secondLastEvent].type == IM_TYPE_3BUTTON) &&
            (m_imInControlEventQ.events[lastEvent].data.mouse.flags &
                                                      IM_FLAG_MOUSE_MOVE) &&
            (m_imInControlEventQ.events[secondLastEvent].data.mouse.flags &
                                                          IM_FLAG_MOUSE_MOVE))
        {
            TRACE_OUT(( "spoil mouse move from pos %u", secondLastEvent));
            time = m_imInControlEventQ.events[secondLastEvent].timeMS;
            m_imInControlEventQ.events[secondLastEvent] =
                                                m_imInControlEventQ.events[lastEvent];
            m_imInControlEventQ.events[secondLastEvent].timeMS = time;
            m_imInControlEventQ.numEvents--;
            lastEvent = secondLastEvent;
        }
    }

    //
    // If we have any events queued up and we are not waiting for a mouse
    // button up event then try to send them.  (Note we do not wait for a
    // mouse up event if the queue is full because if we got a mouse up
    // when the queue was full then we would have nowhere to put it!)
    //
    curTime = GetTickCount();

    if ((m_imInControlEventQ.numEvents != 0) &&
        ((m_imfInControlEventIsPending ||
         (m_imInControlMouseDownCount == 0) ||
         (curTime - m_imInControlMouseDownTime > IM_MOUSE_UP_WAIT_TIME))))
    {
        //
        // If there are mouse move messages on the queue and they are not
        // so old that we should send them anyway then hold them to allow
        // some spoiling to take place.
        //
        holdPacket = FALSE;

        if (m_imInControlEventQ.numEvents <= m_imInControlMouseWithhold)
        {
            if ((m_imInControlEventQ.events[lastEvent].type == IM_TYPE_3BUTTON) &&
                (m_imInControlEventQ.events[lastEvent].data.mouse.flags &
                                                          IM_FLAG_MOUSE_MOVE))
            {
                if (curTime < (m_imInControlEventQ.events[m_imInControlEventQ.head].timeMS +
                                                     IM_LOCAL_WITHHOLD_DELAY))
                {
                    holdPacket = TRUE;
                }
            }
        }

        if (m_imInControlEventQ.numEvents <= IM_LOCAL_KEYBOARD_WITHHOLD)
        {
            //
            // If the message indicates the key is down then wait, either
            // for the release we know is coming, or intil it has auto
            // repeated for a while or until the buffer is full.
            //
            if (((m_imInControlEventQ.events[lastEvent].type == IM_TYPE_ASCII) ||
                 (m_imInControlEventQ.events[lastEvent].type == IM_TYPE_VK1)   ||
                 (m_imInControlEventQ.events[lastEvent].type == IM_TYPE_VK2))  &&
                 (m_imInControlEventQ.events[lastEvent].data.keyboard.flags &
                                                   IM_FLAG_KEYBOARD_DOWN))
            {
                curTime = GetTickCount();
                if (curTime < (m_imInControlEventQ.events[m_imInControlEventQ.head].timeMS +
                                                  IM_LOCAL_WITHHOLD_DELAY))
                {
                    holdPacket = TRUE;
                }
            }
        }

        if (!holdPacket)
        {
            UINT_PTR    destID;

            TRACE_OUT(( "Sending all %d packets",m_imInControlEventQ.numEvents));
            eventsToSend                    = m_imInControlEventQ.numEvents;
            m_imInControlEventQ.numEvents    = 0;

            destID = m_pasLocal->m_caInControlOf->mcsID;

            sizeOfPacket = sizeof(IMPACKET) + (eventsToSend-1)*sizeof(IMEVENT);
            pIMPacket = (PIMPACKET)SC_AllocPkt(PROT_STR_INPUT, destID, sizeOfPacket);
            if (!pIMPacket)
            {
                //
                // Failed to send this packet - keep the data on the queue
                // until the next time we are called.  To prevent the loss
                // of data, just make sure that the local packet list is
                // not overwritten by restoring the current out packets
                // count.
                //
                WARNING_OUT(("Failed to alloc IM packet, size %u", sizeOfPacket));
                m_imInControlEventQ.numEvents = eventsToSend;
            }
            else
            {
                TRACE_OUT(( "NetAllocPkt successful for %d packets size %d",
                           eventsToSend, sizeOfPacket));

                //
                // Fill in the packet header.
                //
                pIMPacket->header.data.dataType = DT_IM;

                //
                // Construct the contents of the IM specific part of the
                // packet.
                //
                pIMPacket->numEvents = (TSHR_UINT16)eventsToSend;
                for (i = 0; i < eventsToSend; i++)
                {
                    pIMPacket->aEvents[i] = m_imInControlEventQ.events[m_imInControlEventQ.head];
                    m_imInControlEventQ.head =
                        CIRCULAR_INDEX(m_imInControlEventQ.head, 1,
                            IM_SIZE_EVENTQ);
                }

                //
                // Now send the packet.
                //
#ifdef _DEBUG
                sentSize =
#endif // _DEBUG
                DCS_CompressAndSendPacket(PROT_STR_INPUT, destID,
                    &(pIMPacket->header), sizeOfPacket);

                TRACE_OUT(("IM packet size: %08d, sent %08d", sizeOfPacket, sentSize));
            }
        }
    }

    DebugExitVOID(ASShare::IMFlushOutgoingEvents);
}



//
// IMSpoilEvents()
//
// Called when outgoing IM packets get backlogged, we spoil every other
// mouse move to shrink the number of events and therefore the size of the
// IM packet(s).
//
void ASShare::IMSpoilEvents(void)
{
    UINT      lastEvent;
    UINT      i;
    UINT      j;
    UINT      k;
    BOOL      discard = TRUE;

    DebugEntry(ASShare::IMSpoilEvents);

    WARNING_OUT(( "Major spoiling due to IM packet queue backlog!"));

    i = CIRCULAR_INDEX(m_imInControlEventQ.head,
        m_imInControlEventQ.numEvents - 1, IM_SIZE_EVENTQ);
    while (i != m_imInControlEventQ.head)
    {
        if ((m_imInControlEventQ.events[i].type == IM_TYPE_3BUTTON) &&
            (m_imInControlEventQ.events[i].data.mouse.flags & IM_FLAG_MOUSE_MOVE))
        {
            if (discard)
            {
                TRACE_OUT(( "spoil mouse move from pos %u", i));
                j = CIRCULAR_INDEX(i, 1, IM_SIZE_EVENTQ);
                k = i;
                lastEvent = CIRCULAR_INDEX(m_imInControlEventQ.head,
                    m_imInControlEventQ.numEvents - 1, IM_SIZE_EVENTQ);
                while (k != lastEvent)
                {
                    //
                    // Shuffle the entries along the queue.
                    //
                    m_imInControlEventQ.events[k] = m_imInControlEventQ.events[j];

                    k = CIRCULAR_INDEX(k, 1, IM_SIZE_EVENTQ);
                    j = CIRCULAR_INDEX(j, 1, IM_SIZE_EVENTQ);
                }

                m_imInControlEventQ.numEvents--;
                discard = FALSE;
            }
            else
            {
                discard = TRUE;
            }
        }

        //
        // Move on to the next event infront of this one.
        //
        if (i > 0)
        {
            i = i - 1;
        }
        else
        {
            i = IM_SIZE_EVENTQ - 1;
        }
    }

    DebugExitVOID(ASShare::IMSpoilEvents);
}


//
// IMAppendNetEvent()
//
// Add the incoming event to the remote network queue, doing basic
// translation like mouse button swapping.  Ignore unrecognized events.
//
void ASShare::IMAppendNetEvent(PIMEVENT pIMEvent)
{
    int   i;
    BOOL  discard = TRUE;

    DebugEntry(ASShare::IMAppendNetEvent);

    switch (pIMEvent->type)
    {
        case IM_TYPE_3BUTTON:
            if (!(pIMEvent->data.mouse.flags & IM_FLAG_MOUSE_MOVE))
            {
                //
                // Swap the mouse buttons if necessary.
                //
                if (m_imfControlledMouseButtonsReversed &&
                    (pIMEvent->data.mouse.flags &
                            (TSHR_UINT16)(IM_FLAG_MOUSE_BUTTON1 |
                                       IM_FLAG_MOUSE_BUTTON2)))
                {
                    pIMEvent->data.mouse.flags ^=
                            (TSHR_UINT16)(IM_FLAG_MOUSE_BUTTON1 |
                                       IM_FLAG_MOUSE_BUTTON2);
                }
            }
            break;
    }


    //
    // Now put the IMEVENT into our queue.
    // Before we try to add the current packet we will try to inject some
    // more events (and therefore make space on the network event queue)
    //

    if (m_imControlledEventQ.numEvents >= IM_SIZE_EVENTQ)
    {
        //
        // Our network event queue is full - discard every other mouse
        // move event in the queue.
        //
        WARNING_OUT(( "Major spoiling due to network event queue backlog!"));

        for (i = m_imControlledEventQ.numEvents - 1; i >= 0; i--)
        {
            if (IM_IS_MOUSE_MOVE(m_imControlledEventQ.events[i].data.mouse.flags))
            {
                if (discard)
                {
                    //
                    // Remove this mouse move event by moving all events
                    // after it down one.
                    //
                    WARNING_OUT(("Discard mouse move to {%d, %d}",
                      (UINT)(m_imControlledEventQ.events[i].data.mouse.x),
                      (UINT)(m_imControlledEventQ.events[i].data.mouse.y)));

                    UT_MoveMemory(&(m_imControlledEventQ.events[i]),
                       &(m_imControlledEventQ.events[i+1]),
                       sizeof(IMEVENT) *
                            (m_imControlledEventQ.numEvents-1-i) );

                    m_imControlledEventQ.numEvents--;
                    discard = FALSE;
                }
                else
                {
                    discard = TRUE;
                }
            }
        }
    }

    if (m_imControlledEventQ.numEvents + 1 >= IM_SIZE_EVENTQ)
    {
        //
        // We've done our best and can't find any space.
        //
        WARNING_OUT(( "IM packet dropped %04X", pIMEvent->type));
    }
    else
    {
        //
        // Add this event to the queue
        //
        m_imControlledEventQ.events[m_imControlledEventQ.numEvents] = *pIMEvent;
        m_imControlledEventQ.numEvents++;
    }

    DebugExitVOID(ASShare::IMAppendNetEvent);
}




//
// IM_OutgoingMouseInput()
//
// Called to send mouse moves and clicks to the remote host.
// Called from the view window code.
//
void  ASShare::IM_OutgoingMouseInput
(
    ASPerson *  pasHost,
    LPPOINT     pMousePos,
    UINT        message,
    UINT        dwExtra
)
{
    IMEVENT     imEvent;

    DebugEntry(ASShare::IM_OutgoingMouseInput);

    ValidateView(pasHost);
    ASSERT(pasHost->m_caControlledBy == m_pasLocal);
    ASSERT(!pasHost->m_caControlPaused);

    GetKeyboardState(m_aimInControlKeyStates);

    //
    // Create the event.
    //
    imEvent.type = IM_TYPE_3BUTTON;

    //
    // We should only get WM_MOUSE* messages.
    //
    ASSERT(message >= WM_MOUSEFIRST);
    ASSERT(message <= WM_MOUSELAST);

    //
    // Convert to bit flags.
    //
    switch (message)
    {
        case WM_MOUSEMOVE:
            imEvent.data.mouse.flags = IM_FLAG_MOUSE_MOVE;
            break;

        case WM_LBUTTONDOWN:
            imEvent.data.mouse.flags = IM_FLAG_MOUSE_BUTTON1 |
                                        IM_FLAG_MOUSE_DOWN;
            break;

        case WM_LBUTTONDBLCLK:
            imEvent.data.mouse.flags = IM_FLAG_MOUSE_BUTTON1 |
                                        IM_FLAG_MOUSE_DOUBLE  |
                                        IM_FLAG_MOUSE_DOWN;
            break;

        case WM_LBUTTONUP:
            imEvent.data.mouse.flags = IM_FLAG_MOUSE_BUTTON1;
            break;

        case WM_RBUTTONDOWN:
            imEvent.data.mouse.flags = IM_FLAG_MOUSE_BUTTON2 |
                                        IM_FLAG_MOUSE_DOWN;
            break;

        case WM_RBUTTONDBLCLK:
            imEvent.data.mouse.flags = IM_FLAG_MOUSE_BUTTON2 |
                                        IM_FLAG_MOUSE_DOUBLE  |
                                        IM_FLAG_MOUSE_DOWN;
            break;

        case WM_RBUTTONUP:
            imEvent.data.mouse.flags = IM_FLAG_MOUSE_BUTTON2;
            break;

        case WM_MBUTTONDOWN:
            imEvent.data.mouse.flags = IM_FLAG_MOUSE_BUTTON3 |
                                        IM_FLAG_MOUSE_DOWN;
            break;

        case WM_MBUTTONDBLCLK:
            imEvent.data.mouse.flags = IM_FLAG_MOUSE_BUTTON3 |
                                        IM_FLAG_MOUSE_DOUBLE  |
                                        IM_FLAG_MOUSE_DOWN;
            break;

        case WM_MBUTTONUP:
            imEvent.data.mouse.flags = IM_FLAG_MOUSE_BUTTON3;
            break;

        case WM_MOUSEWHEEL:
            //
            // LAURABU BOGUSBOGUS
            //
            // The HIWORD of wParam represents the # of clicks the wheel
            // has turned.
            //
            // But what about Win95?  NT and Win95 Magellan mouse work
            // differently.
            //
            imEvent.data.mouse.flags = IM_FLAG_MOUSE_WHEEL;

            //
            // Check for overflows.  If the wheel delta is outside the
            // values that can be sent by the protocol, send the maximum
            // values.
            //
            if ((TSHR_INT16)HIWORD(dwExtra) >
                   (IM_FLAG_MOUSE_ROTATION_MASK - IM_FLAG_MOUSE_DIRECTION))
            {
                ERROR_OUT(( "Mouse wheel overflow %hd", HIWORD(dwExtra)));
                imEvent.data.mouse.flags |=
                      (IM_FLAG_MOUSE_ROTATION_MASK - IM_FLAG_MOUSE_DIRECTION);
            }
            else if ((TSHR_INT16)HIWORD(dwExtra) < -IM_FLAG_MOUSE_DIRECTION)
            {
                ERROR_OUT(( "Mouse wheel underflow %hd", HIWORD(dwExtra)));
                imEvent.data.mouse.flags |= IM_FLAG_MOUSE_DIRECTION;
            }
            else
            {
                imEvent.data.mouse.flags |=
                             (HIWORD(dwExtra) & IM_FLAG_MOUSE_ROTATION_MASK);
            }

            //
            // Win95 boxes need to know whether the middle mouse button is
            // up or down.
            //
            if (LOWORD(dwExtra) & MK_MBUTTON)
            {
                imEvent.data.mouse.flags |= IM_FLAG_MOUSE_DOWN;
            }
            break;

        default:
            imEvent.data.mouse.flags = IM_FLAG_MOUSE_MOVE;
            ERROR_OUT(( "Unrecognised mouse event - %#x", message));
            break;
    }

    TRACE_OUT(( "Mouse event flags %hx", imEvent.data.mouse.flags));

    imEvent.data.mouse.x = (TSHR_INT16)(pMousePos->x);
    imEvent.data.mouse.y = (TSHR_INT16)(pMousePos->y);
    imEvent.timeMS       = GetTickCount();

    //
    // If this is a mouse down event then we will wait a while before
    // sending the packet for a mouse up event so that a single click
    // can be sent in one packet to avoid timing problems on the remote
    // side - with for example a scroll bar scrolling multiple lines
    // instead of just one line.
    //

    if ((message == WM_LBUTTONDOWN) ||
        (message == WM_RBUTTONDOWN) ||
        (message == WM_MBUTTONDOWN) ||
        (message == WM_LBUTTONDBLCLK) ||
        (message == WM_RBUTTONDBLCLK) ||
        (message == WM_MBUTTONDBLCLK))
    {
        m_imInControlMouseDownCount++;
        m_imInControlMouseDownTime = GetTickCount();
    }
    else if ((message == WM_LBUTTONUP) ||
             (message == WM_RBUTTONUP) ||
             (message == WM_MBUTTONUP))
    {
        --m_imInControlMouseDownCount;
        if (m_imInControlMouseDownCount < 0)
        {
            TRACE_OUT(("Unmatched button down for %d", message));
            m_imInControlMouseDownCount = 0;
        }
    }

    //
    // Try to send the packet.
    //
    if (!IMConvertAndSendEvent(pasHost, &imEvent))
    {
        WARNING_OUT(("Couldn't send mouse packet from local node"));
    }

    DebugExitVOID(ASShare::IM_OutgoingMouseInput);
}



//
// IM_OutgoingKeyboardInput()
//
// Called to key downs, ups, and chars to the remote host.
// Called from the view window code.
//
void  ASShare::IM_OutgoingKeyboardInput
(
    ASPerson *  pasHost,
    UINT        wParam,
    UINT        lParam
)
{
    IMEVENT     imEvent;
    int         rc;
    int         retFlags;
    WORD        result[2];
    UINT        i;
    BOOL        fSwallowDeadKey;
    UINT        mainVK;

    DebugEntry(ASShare::IM_OutgoingKeyboardInput);

    ValidateView(pasHost);

    ASSERT(pasHost->m_caControlledBy = m_pasLocal);
    ASSERT(!pasHost->m_caControlPaused);

    GetKeyboardState(m_aimInControlKeyStates);

    //
    // Trace out the parameters once we've got this far.
    //
    TRACE_OUT(( "wParam - %04X, lParam - %08lX", wParam, lParam));

    //
    // Create the event.
    //
    imEvent.data.keyboard.flags = (TSHR_UINT16)
                                 (HIWORD(lParam) & IM_MASK_KEYBOARD_SYSFLAGS);
    imEvent.timeMS = GetTickCount();
    imEvent.data.keyboard.keyCode = LOBYTE(wParam);

    retFlags = CA_SEND_EVENT | CA_ALLOW_EVENT;

    if ((wParam == VK_LWIN) || (wParam == VK_RWIN))
    {
        //
        // The Windows keys give control to the local user interface.
        //
        // The keys are defined to do the following by the spec "New key
        // support for Microsoft Windows Operating Systems and
        // Applications"
        //
        //   Left Windows key - set focus to Win95 user interface
        //   Right Windows key - as left
        //   Both Windows keys - Log-on key for Windows NT
        //   Windows key + any other - reserved for system hot keys
        //
        // Thus it does not make any sense to send these keys to the remote
        // system at all.
        //
		retFlags &= ~CA_SEND_EVENT;
    }
    else if ((wParam == VK_PROCESSKEY) && (m_imImmGVK != NULL))
    {
        //
        // An IME has processed this key - we want to find out what the
        // original key was so call <ImmGetVirtualKey>.
        //
        ValidateView(pasHost);
        wParam = m_imImmGVK(pasHost->m_pView->m_viewClient);

        TRACE_OUT(( "Translated wP from VK_PROCESSKEY to %#lx", wParam));
    }

    if (retFlags & CA_SEND_EVENT)
    {
        //
        // First check if this is a dead-key up stroke - if it is then
        // don't call ToAscii as the shift state may have changed and we'll
        // get the wrong accent or no accent at all.  Assume that if the VK
        // is a potential dead key VK (disregarding shift state) and
        // m_imInControlNumDeadKeysDown is > 0 that this is a dead key - swallow
        // it.
        //
        fSwallowDeadKey = FALSE;

        if ((m_imInControlNumDeadKeysDown != 0) &&
            (imEvent.data.keyboard.flags & IM_FLAG_KEYBOARD_RELEASE))
        {
            for (i = 0; i < m_imInControlNumDeadKeys; i++)
            {
                if (m_aimInControlDeadKeys[i] == (BYTE)imEvent.data.keyboard.keyCode)
                {
                    //
                    // Assume this is a dead key up and therefore we don't
                    // want to pass it through ToAscii or generate any
                    // events based on it.
                    //
                    m_imInControlNumDeadKeysDown--;
                    TRACE_OUT(( "m_imInControlNumDeadKeysDown - %d",
                             m_imInControlNumDeadKeysDown));
                    fSwallowDeadKey = TRUE;
                }
            }
        }

        if (!fSwallowDeadKey)
        {
            //
            // Find out if we can translate this virtual key into the
            // Windows character set.
            //

            //
            // Now try to convert this to an Ascii character.
            //
            rc = ToAscii(wParam,
                         LOBYTE(HIWORD(lParam)),
                         m_aimInControlKeyStates,
                         &result[0],
                         !(!(HIWORD(lParam) & KF_MENUMODE)));

            if ((rc == 1) && (LOBYTE(result[0]) <= ' '))
            {
                //
                // Don't use the results of ToAscii if its less than space
                // (32) or space itself as Windows claims that the
                // characters below this in the Windows character set are
                // not supported and ToAscii will convert space plus
                // modifiers to an ascii space and when we replay it
                // VkKeyScan will tell us that ascii space shouldn't have
                // any modifiers so we will undo any modifiers.  This will
                // clobber apps which interpret Ctrl-Space, Shift-Space.
                //
                rc = 0;
            }

            //
            // Some Ascii characters can be generated from more than one
            // key.  (Eg '-' is on the main keyboard and the number pad).
            // Convert this ASCII character back to a VK_ value.  If it is
            // different from the VK_ we started with, then do not send the
            // key press as ASCII (Ie only send the 'main' way of entering
            // an ASCII value as ASCII).
            //
            // Oprah1943: revert to the VK only if the ASCII code is less
            // than 0x80.  This avoids losing the diacritic in a dead-key
            // sequence.  VkKeyScan for the key down following the dead-key
            // up returns the dead-key VK rather than that of the keystroke
            // (wParam).
            //
            if (rc == 1)
            {
                mainVK = VkKeyScan(LOBYTE(result[0]));

                if ( (LOBYTE(mainVK) != LOBYTE(wParam)) &&
                     (LOBYTE(result[0]) < 0x80) )
                {
                    TRACE_OUT((
                      "Not MAIN VK pressed=0x%02hx main=0x%02hx ('%c'/%02hx)",
                             (TSHR_UINT16)LOBYTE(wParam),
                             (TSHR_UINT16)LOBYTE(mainVK),
                             (char)LOBYTE(result[0]),
                             (UINT)LOBYTE(result[0])));
                    rc = 0;
                }
            }

            //
            // If ToAscii converts this to a dead key then don't send any
            // packets at all.
            //
            if (rc != -1)
            {
                if (rc == 1)
                {
                    TRACE_OUT(( "ToAscii rc=1, result - %02X",
                             LOBYTE(result[0])));

                    //
                    // Succesfully converted to an Ascii key.
                    //
                    imEvent.type = IM_TYPE_ASCII;
                    imEvent.data.keyboard.keyCode = LOBYTE(result[0]);

                    //
                    // Try to send the packet.
                    //
                    if (!IMConvertAndSendEvent(pasHost, &imEvent))
                    {
                        WARNING_OUT(( "dropped local key press %u",
                                 (UINT)imEvent.data.keyboard.keyCode));
                    }
                }
                else if (rc == 2)
                {
                    TRACE_OUT(( "ToAscii rc=2, result - %04X", result[0]));

                    //
                    // Succesfully converted to two Ascii keys.  If this is
                    // a key down then we will return a key down and key up
                    // for the `dead' character first then the key down.
                    // If its a key up then just return the key up.
                    //
                    if (!(imEvent.data.keyboard.flags &
                                               IM_FLAG_KEYBOARD_RELEASE))
                    {
                        //
                        // This is the key down - so generate a fake
                        // keyboard press for the dead key.
                        //
                        IMGenerateFakeKeyPress(IM_TYPE_ASCII,
                                               LOBYTE(result[0]),
                                               imEvent.data.keyboard.flags);
                    }

                    //
                    // Now return the current keystroke.
                    //
                    imEvent.type = IM_TYPE_ASCII;
                    imEvent.data.keyboard.keyCode = LOBYTE(result[1]);

                    //
                    // Try to send the packet.
                    //
                    if (!IMConvertAndSendEvent(pasHost, &imEvent))
                    {
                        WARNING_OUT(( "dropped local key press %u",
                                 (UINT)imEvent.data.keyboard.keyCode));
                    }
                }
                else
                {
                    //
                    // Check for keys that we want to convert.
                    //
                    if (LOBYTE(wParam) == VK_KANJI)
                    {
                        //
                        // We only see a down press for VK_KANJI so we
                        // fake a complete key press so that the remote
                        // does not get confused.
                        //
                        IMGenerateFakeKeyPress(IM_TYPE_VK1,
                                               VK_KANJI,
                                               imEvent.data.keyboard.flags);
                    }
                    else
                    {
                        //
                        // No conversion - use the VK itself.
                        //
                        imEvent.type = IM_TYPE_VK1;
                        imEvent.data.keyboard.keyCode = LOBYTE(wParam);

                        //
                        // SFR 2537: If this is a right shift VK (which we
                        // can detect via the scan code in lParam), set the
                        // right_variant keyboard flag.  We do not do this
                        // for the right-variants of CONTROL and ALT (ie
                        // menu) because they are extended keys - already
                        // catered for by the extended flag.
                        //
                        if ( (m_imScanVKRShift != 0) &&
                             (m_imScanVKRShift == LOBYTE(HIWORD(lParam))) )
                        {
                            imEvent.data.keyboard.flags |=
                                                       IM_FLAG_KEYBOARD_RIGHT;
                        }

                        //
                        // Try to send the packet.
                        //
                        if (!IMConvertAndSendEvent(pasHost, &imEvent))
                        {
                            WARNING_OUT(( "dropped local key press %u",
                                     (UINT)imEvent.data.keyboard.keyCode));
                        }
                    }
                }
            }
            else
            {
                //
                // This is a dead key - add it to our array of dead keys if
                // we haven't already heard about it.
                //
                IMMaybeAddDeadKey(
                                (BYTE)imEvent.data.keyboard.keyCode);
                m_imInControlNumDeadKeysDown++;
                TRACE_OUT(( "m_imInControlNumDeadKeysDown - %d",
                         m_imInControlNumDeadKeysDown));
            }
        }
    }

    DebugExitVOID(ASShare::IM_OutgoingKeyboardInput);
}


//
// FUNCTION: IMGenerateFakeKeyPress(...)
//
// DESCRIPTION:
//
// Generates a fake keyboard press.
//
// PARAMETERS:
//
// type   - packet type to generate.
// key    - key to generate press for.
// flags  - flags on keyboard press.
//
// RETURNS:
//
// Nothing.
//
//
void  ASShare::IMGenerateFakeKeyPress
(
    TSHR_UINT16     type,
    TSHR_UINT16     key,
    TSHR_UINT16     flags
)
{
    IMEVENT         imEventFake;

    DebugEntry(ASShare::IMGenerateFakeKeyPress);

    TRACE_OUT(( "Faking keyboard press:%#hx type:%#hx", key, type));

    //
    // Generate the key down first of all.
    //
    ZeroMemory(&imEventFake, sizeof(imEventFake));

    imEventFake.type                  = type;
    imEventFake.timeMS                = GetTickCount();
    imEventFake.data.keyboard.keyCode = key;

    //
    // Try to send the packet.
    //
    if (!IMConvertAndSendEvent(m_pasLocal->m_caInControlOf, &imEventFake))
    {
        WARNING_OUT(( "Dropped local key press %hu (flags: %#hx)",
                 imEventFake.data.keyboard.keyCode,
                 imEventFake.data.keyboard.flags));
    }

    //
    // Set the release and down flags in order to fake the up.
    //
    imEventFake.data.keyboard.flags = IM_FLAG_KEYBOARD_DOWN | IM_FLAG_KEYBOARD_RELEASE;

    //
    // Try to send the packet.
    //
    if (!IMConvertAndSendEvent(m_pasLocal->m_caInControlOf, &imEventFake))
    {
        WARNING_OUT(( "Dropped local key press %hu (flags: %#hx)",
                 imEventFake.data.keyboard.keyCode,
                 imEventFake.data.keyboard.flags));
    }

    DebugExitVOID(ASShare::IMGenerateFakeKeyPress);
}








//
// FUNCTION: IMConvertAndSendEvent
//
// DESCRIPTION:
//
// Called with an IMEVENT this function will try to queue (and even send
// if possible) the packet.  If it fails it will return FALSE - the caller
// should discard the packet.  If it succeeds it will return TRUE.
//
// If pasFor is us, it means to send to everybody (and coords are relative
// to  sender's screen).
//
// If pasFor is a remote, it means that the IM packet is meant for just
// that person and the coords are relative to pasFor's screen.
//
//
// PARAMETERS:
//
// pIMEvent - the IMEVENT to convert and send
//
// RETURNS: TRUE or FALSE - success or failure
//
//
BOOL  ASShare::IMConvertAndSendEvent
(
    ASPerson *      pasFor,
    PIMEVENT        pIMEvent
)
{
    BOOL rc = FALSE;

    DebugEntry(ASShare::IMConvertAndSendEvent);

    //
    // If there is already a pending packet then see if we can flush some
    // packets onto the network.
    //
    if (m_imfInControlEventIsPending)
    {
        IMFlushOutgoingEvents();
    }

    //
    // If there is still a pending packet then see if we can spoil some
    // events.
    //
    if (m_imfInControlEventIsPending)
    {
        TRACE_OUT(( "trying to drop mouse move events"));
        IMSpoilEvents();
        IMFlushOutgoingEvents();
    }

    //
    // Now see if we are able to accept a new packet
    //
    if (m_imfInControlEventIsPending)
    {
        //
        // If there is still a previous IMEVENT which we are in the
        // process of converting then we are not ready to receive any more
        // packets.
        //
        TRACE_OUT(( "can't queue packet"));
        DC_QUIT;
    }

    //
    // Now set up the new packet and try to flush the packets again.
    //
    m_imfInControlEventIsPending = TRUE;
    m_imInControlPendingEvent = *pIMEvent;
    IMFlushOutgoingEvents();

    rc = TRUE;

DC_EXIT_POINT:
    DebugExitBOOL(ASShare::IMConvertAndSendEvent, rc);
    return(rc);
}


//
// FUNCTION: IMMaybeAddDeadKey
//
// DESCRIPTION:
//
// Called whenever ToAscii tells us about a dead key.  If we haven't
// got it in our table already then we will add it.  We create the table
// incrementally because we have found that some keyboard drivers don't
// cope very well with being queried with all possible VKs to find the
// dead keys.  Note that this will not cope with someone switching their
// keyboard driver whilst DC-Share is running.
//
// PARAMETERS:
//
// vk - the VK in question
//
// RETURNS: NONE
//
//
void  ASShare::IMMaybeAddDeadKey(BYTE     vk)
{
    UINT  i;

    DebugEntry(IMMaybeAddDeadKey);

    //
    // First see if we already know about this key.
    //
    for (i = 0; i < m_imInControlNumDeadKeys; i++)
    {
        if (m_aimInControlDeadKeys[i] == vk)
        {
            DC_QUIT;
        }
    }

    //
    // Add this key if there's space in the array.
    //
    if (m_imInControlNumDeadKeys < IM_MAX_DEAD_KEYS)
    {
        TRACE_OUT(( "Add %02X", (TSHR_UINT16)vk));
        m_aimInControlDeadKeys[m_imInControlNumDeadKeys++] = vk;
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::IMMaybeAddDeadKey);
}



//
// IMConvertIMEventToOSEvent()
// Converts incoming event to something we can playback.
//
// PARAMETERS:
//
// pIMEvent -   the IMEVENT to be converted
//
// pOSEvent - the IMOSEVENT to be created
//
//
UINT  ASShare::IMConvertIMEventToOSEvent
(
    PIMEVENT        pIMEvent,
    LPIMOSEVENT     pOSEvent
)
{
    int             mouseX;
    int             mouseY;
    int             realMouseX;
    int             realMouseY;
    RECT            cursorClip;
    UINT            rc = (IM_IMQUEUEREMOVE | IM_OSQUEUEINJECT);

    DebugEntry(ASShare::IMConvertIMEventToOSEvent);

    switch (pIMEvent->type)
    {
        case IM_TYPE_3BUTTON:
            //
            // Fill in common fields.  Note that we claim to be a 3 button
            // mouse so that we can replay events from remote three button
            // mice and we always give absolute coordinates.
            //
            pOSEvent->type                    = IM_MOUSE_EVENT;
            pOSEvent->flags                   = 0;
            pOSEvent->time                    = pIMEvent->timeMS;
            pOSEvent->event.mouse.cButtons    = 3;
            pOSEvent->event.mouse.mouseData   = 0;
            pOSEvent->event.mouse.dwExtraInfo = 0;

            //
            // First check for a wheel rotate, since this is easy to
            // process.  (It cannot include any mouse movement as well).
            //
            if (pIMEvent->data.mouse.flags & IM_FLAG_MOUSE_WHEEL)
            {
                if (pIMEvent->data.mouse.flags &
                        (IM_FLAG_MOUSE_BUTTON1 |
                         IM_FLAG_MOUSE_BUTTON2 |
                         IM_FLAG_MOUSE_BUTTON3))
                {
                    //
                    // Using any of the button flags along with the wheel
                    // flag is currently undefined - for forward
                    // compatability we therefore ignore such an event by
                    // converting it into a NULL injected event.
                    //
                    // (We do not sg_lpimSharedData->imply discard it, since the logic to
                    // discard events does not seem to work).
                    //
                    pOSEvent->event.mouse.flags = 0;
                    pOSEvent->event.mouse.pt.x = 0;
                    pOSEvent->event.mouse.pt.y = 0;
                }
                else
                {
                    //
                    // This is a wheel movement.
                    //
                    // Note that the protocol has sent whether the mouse's
                    // middle button is depressed or released, but we don't
                    // need that info for NT, so just ignore it.
                    //
                    pOSEvent->event.mouse.flags = MOUSEEVENTF_WHEEL;

                    pOSEvent->event.mouse.mouseData =
                        (pIMEvent->data.mouse.flags & IM_FLAG_MOUSE_ROTATION_MASK);
                    pOSEvent->event.mouse.pt.x = 0;
                    pOSEvent->event.mouse.pt.y = 0;

                    //
                    // Sign extend the rotation amount up to the full 32
                    // bits
                    //
                    if (pOSEvent->event.mouse.mouseData & IM_FLAG_MOUSE_DIRECTION)
                    {
                        pOSEvent->event.mouse.mouseData |=
                                           ~IM_FLAG_MOUSE_ROTATION_MASK;
                    }
                }

                break;
            }

            //
            // We are left now with non wheel-rotate events.
            //
            pOSEvent->event.mouse.flags = MOUSEEVENTF_ABSOLUTE;

            //
            // We must convert from virtual desktop coordinates to local
            // screen coordinates here and we must also prevent the
            // position wrapping if we try to replay a mouse move to an
            // off-screen position.
            //

            realMouseX = pIMEvent->data.mouse.x;
            realMouseY = pIMEvent->data.mouse.y;

            //
            // Now lg_lpimSharedData->imit to the size of the real screen.
            //
            mouseX = min((m_pasLocal->cpcCaps.screen.capsScreenWidth-1), max(0, realMouseX));
            mouseY = min((m_pasLocal->cpcCaps.screen.capsScreenHeight-1), max(0, realMouseY));

            //
            // Work out if this event will be clipped by the clip cursor
            //
            GetClipCursor(&cursorClip);

            if ((mouseX < cursorClip.left) ||
                (mouseX >= cursorClip.right) ||
                (mouseY < cursorClip.top) ||
                (mouseY >= cursorClip.bottom))
            {
                //
                // This event will actually be clipped because of the
                // current clip cursor.  Remember this.
                //
                m_imfControlledMouseClipped = TRUE;
            }
            else
            {
                m_imfControlledMouseClipped = FALSE;

                //
                // If we clamp the mouse position before replaying then we
                // must remember the real packet and make the current
                // packet into a move so that we don't click down/up at the
                // wrong place.
                //
                if ((mouseX != realMouseX) || (mouseY != realMouseY))
                {
                    //
                    // The mouse position we've recieved is off the
                    // local physical screen.  Now that we no longer have
                    // desktop scrolling, we simply clamp it rather than
                    // inject it at the edge and wait for the scroll.
                    //
                    // We turn mouse down-clicks into moves and let
                    // up-clicks pass through (in case the mouse button
                    // has been pressed within the real screen).
                    //
                    // Note that the mouse position has already been
                    // adjusted so that it is within the real screen.
                    //
                    if (pIMEvent->data.mouse.flags & IM_FLAG_MOUSE_DOWN)
                    {
                        pIMEvent->data.mouse.flags = IM_FLAG_MOUSE_MOVE;
                    }
                }
            }

            //
            // Store the mouse position.
            //
            pOSEvent->event.mouse.pt.x = mouseX;
            pOSEvent->event.mouse.pt.y = mouseY;

            //
            // Add more flags as appropriate.
            //
            if (pIMEvent->data.mouse.flags & IM_FLAG_MOUSE_MOVE)
            {
                pOSEvent->event.mouse.flags |= MOUSEEVENTF_MOVE;
            }
            else
            {
                switch (pIMEvent->data.mouse.flags &
                                                   ( IM_FLAG_MOUSE_BUTTON1 |
                                                     IM_FLAG_MOUSE_BUTTON2 |
                                                     IM_FLAG_MOUSE_BUTTON3 |
                                                     IM_FLAG_MOUSE_DOWN ))
                {
                    case IM_FLAG_MOUSE_BUTTON1 | IM_FLAG_MOUSE_DOWN:
                        pOSEvent->event.mouse.flags |= MOUSEEVENTF_LEFTDOWN;
                        break;

                    case IM_FLAG_MOUSE_BUTTON1:
                        pOSEvent->event.mouse.flags |= MOUSEEVENTF_LEFTUP;
                        break;

                    case IM_FLAG_MOUSE_BUTTON2 | IM_FLAG_MOUSE_DOWN:
                        pOSEvent->event.mouse.flags |= MOUSEEVENTF_RIGHTDOWN;
                        break;

                    case IM_FLAG_MOUSE_BUTTON2:
                        pOSEvent->event.mouse.flags |= MOUSEEVENTF_RIGHTUP;
                        break;

                    case IM_FLAG_MOUSE_BUTTON3 | IM_FLAG_MOUSE_DOWN:
                        pOSEvent->event.mouse.flags |= MOUSEEVENTF_MIDDLEDOWN;
                        break;

                    case IM_FLAG_MOUSE_BUTTON3:
                        pOSEvent->event.mouse.flags |= MOUSEEVENTF_MIDDLEUP;
                        break;

                    default:
                        //
                        // If we don't recognise this then don't play it
                        // back
                        //
                        ERROR_OUT(("Unrecognised mouse flags (%04X)",
                                 pIMEvent->data.mouse.flags));
                        rc = IM_IMQUEUEREMOVE;
                        break;
                }
            }
            break;

        case IM_TYPE_VK1:
            //
            // Common fields.
            //
            pOSEvent->flags     = 0;
            if (pIMEvent->data.keyboard.flags & IM_FLAG_KEYBOARD_UPDATESTATE)
                pOSEvent->flags |= IM_FLAG_UPDATESTATE;

            pOSEvent->time      = pIMEvent->timeMS;

            //
            // Now handle normal keyboard events.
            //
            pOSEvent->type      = IM_KEYBOARD_EVENT;

            //
            // AX is the scancode in AL and 00h (press) or 80h (release) in
            // AH. Map the DC protocol VK to the equivalent OS VK.
            // AL = the scancode for the VK).
            //
            pOSEvent->event.keyboard.vkCode = LOBYTE(pIMEvent->data.keyboard.keyCode);

            pOSEvent->event.keyboard.flags = 0;
            if (IS_IM_KEY_RELEASE(pIMEvent->data.keyboard.flags))
            {
                pOSEvent->event.keyboard.flags |= KEYEVENTF_KEYUP;
            }

            //
            // SFR 2537: If the flags indicate that the received VK is the
            // right-variant, do not map the VK to a scan code, but rather
            // directly use the already acquired right-variant scan code
            // for the VK.  (For the moment, the only case we support is
            // for Windows, where this is an issue for SHIFT).
            //
            if ( IS_IM_KEY_RIGHT(pIMEvent->data.keyboard.flags) &&
                 (pIMEvent->data.keyboard.keyCode == VK_SHIFT)   )
            {
                pOSEvent->event.keyboard.scanCode = m_imScanVKRShift;
            }
            else
            {
                pOSEvent->event.keyboard.scanCode =
                         (WORD)MapVirtualKey(pIMEvent->data.keyboard.keyCode, 0);
            }

            if (pIMEvent->data.keyboard.flags & IM_FLAG_KEYBOARD_EXTENDED)
            {
                pOSEvent->event.keyboard.flags |= KEYEVENTF_EXTENDEDKEY;
            }

            pOSEvent->event.keyboard.dwExtraInfo = 0;
            break;

        default:
            ERROR_OUT(("Unrecognized imEvent (%d)", pIMEvent->type));
            //
            // Discard the event (remove from the IM queue and don't inject
            // into the OS).
            //
            rc = IM_IMQUEUEREMOVE;
            break;
    }


    DebugExitDWORD(ASShare::IMConvertIMEventToOSEvent, rc);
    return(rc);
}



//
// IMTranslateOutgoing()
//
// DESCRIPTION:
//
// Converts locally generated sequences of IMEVENTs into transmitted
// sequences of IMEVENTs.  Does a 1 to (0-n) translation.  Handles
// buffering modifier keys and translating DC-Share hot-key sequences.
//
// When the CA has decided an IMEVENT should be sent this function is
// called by the IM with a pointer to that packet in pIMEventIn.
// IMTranslateOutgoing can then return TRUE and fill in the packet at
// pIMEventOut or return FALSE.  If IMTranslateOutgoing returns TRUE the IM
// will call it again with the same packet.  The IMEVENTs returned are
// sent across the network by the IM.
//
// PARAMETERS:
//
// pIMEventIn - pointer to IMEVENT
//
// pIMEventOut - pointer to IMEVENT
//
// RETURNS:
//
// TRUE - packet returned (call function again)
//
// FALSE - no packet returned (don't call function again)
//
//

BOOL ASShare::IMTranslateOutgoing
(
    LPIMEVENT pIMEventIn,
    LPIMEVENT pIMEventOut
)
{
    UINT      hotKeyArrayIndex;
    UINT      hotKeyValue;
    BOOL      fHotKeyFound;
    BOOL      rc = FALSE;

    DebugEntry(ASShare::IMTranslateOutgoing);

    //
    // Here we need to tell the remote system about certain keys which are
    // consumed locally so that it can make good decisions about whether
    // and how to replay them.  We want to keep the remote system in step
    // with the current modifier and toggle key state on our system (as it
    // is possible that either a modifier/toggle key event occurred whilst
    // a local app was active and was therefore never sent) We also want to
    // recognise certain `hot key' sequences and send further packets as a
    // result of these.
    //
    // The keys we comsume locally are:
    //
    // Esc down or up when Ctrl is down - operates task list locally
    //
    // Tab down or up when Alt is down - operates task switcher locally
    //
    // Esc down or up when Alt is pressed - switches to next window locally
    //
    // Esc up when corresponding Esc down occurred when Alt was down - as
    // above
    //
    // The sequences we want to produce hot keys from are:
    //
    // Alt + 9??  on the numeric keypad
    //
    // To detect hotkeys we keep a record of the last four keypresses and
    // when we detect an Alt up we check if they form a valid sequence.
    //
    // The keystrokes which form part of the hotkey are sent to the remote
    // system so if they have some meaning on a remote system then that
    // system must decide whether to buffer them to determine if they are
    // part of a hotkey or play them back anyway - on Windows we play them
    // back anyway as they are a legitimate key sequence when controlling a
    // Windows app - the number typed on the numeric keypad has a % 256
    // applied to it.
    //
    // This means that for each incoming event we may want to generate 0 or
    // more outgoing events.  To do this we have a structure which looks
    // roughly like this:
    //
    //  IF m_m_imfInControlNewEvent
    //      calculate an array of events which we want to return
    //      set m_m_imfInControlNewEvent to FALSE
    //      set number of events returned to 0
    //  ENDIF
    //
    //  IF !m_m_imfInControlNewEvent
    //      IF this is the last event to return
    //          set m_m_imfInControlNewEvent to TRUE
    //      ENDIF
    //      return current event
    //  ENDIF
    //
    //

    if (m_imfInControlNewEvent)
    {
        //
        // This is the first time we have seen this event so accumulate
        // our list of events to generate.
        //

        //
        // Do tracing
        //
        if (pIMEventIn->type == IM_TYPE_ASCII)
        {
            TRACE_OUT(( "IN  ASCII code 0x%04X, flags 0x%04X",
                pIMEventIn->data.keyboard.keyCode, pIMEventIn->data.keyboard.flags));
        }
        else if (pIMEventIn->type == IM_TYPE_VK1)
        {
            TRACE_OUT(( "IN  VKEY  code %04X, flags %04X",
                pIMEventIn->data.keyboard.keyCode, pIMEventIn->data.keyboard.flags));
        }
        else if ((pIMEventIn->type == IM_TYPE_3BUTTON) &&
                 !(pIMEventIn->data.mouse.flags & IM_FLAG_MOUSE_MOVE))
        {
            TRACE_OUT(( "IN  3BTTN flags %04X (%d,%d)",
                pIMEventIn->data.mouse.flags, pIMEventIn->data.mouse.x,
                pIMEventIn->data.mouse.y));
        }
        else if (pIMEventIn->type == IM_TYPE_3BUTTON)
        {
            TRACE_OUT(( "IN  3BTTN flags %04X (%d,%d)",
                pIMEventIn->data.mouse.flags, pIMEventIn->data.mouse.x,
                pIMEventIn->data.mouse.y));
        }
        else if (pIMEventIn->type == IM_TYPE_VK_ASCII)
        {
            TRACE_OUT(("IN VK_ASC code %04X, flags %04X",
                pIMEventIn->data.keyboard.keyCode, pIMEventIn->data.keyboard.flags));
        }
        else
        {
            ERROR_OUT(("Invalid IM type %d", pIMEventIn->type));
        }

        //
        // Start from the beginning of our returned events array.
        //
        m_imInControlNumEventsPending = 0;
        m_imInControlNumEventsReturned = 0;

        //
        // First get our flags for the modifiers and locks we think we have
        // sent to the remote side up to date allowing for this event.
        //
        if (pIMEventIn->type == IM_TYPE_VK1)
        {
            switch (pIMEventIn->data.keyboard.keyCode)
            {
                case VK_CONTROL:
                    if (IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
                    {
                        m_imfInControlCtrlDown = FALSE;
                    }
                    else
                    {
                        m_imfInControlCtrlDown = TRUE;
                    }
                    break;

                case VK_SHIFT:
                    if (IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
                    {
                        m_imfInControlShiftDown = FALSE;
                    }
                    else
                    {
                        m_imfInControlShiftDown = TRUE;
                    }
                    break;

                case VK_MENU:
                    if (IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
                    {
                        m_imfInControlMenuDown = FALSE;
                    }
                    else
                    {
                        m_imfInControlMenuDown = TRUE;
                    }
                    break;

                case VK_CAPITAL:
                    if (IS_IM_KEY_PRESS(pIMEventIn->data.keyboard.flags))
                    {
                        m_imfInControlCapsLock = !m_imfInControlCapsLock;
                    }
                    break;

                case VK_NUMLOCK:
                    if (IS_IM_KEY_PRESS(pIMEventIn->data.keyboard.flags))
                    {
                        m_imfInControlNumLock = !m_imfInControlNumLock;
                    }
                    break;

                case VK_SCROLL:
                    if (IS_IM_KEY_PRESS(pIMEventIn->data.keyboard.flags))
                    {
                        m_imfInControlScrollLock = !m_imfInControlScrollLock;
                    }
                    break;

                default:
                    break;
            }
        }

        //
        // Now check the current state versus our remembered state and
        // prepare to insert events if necessary.  Do this for any events
        // (ie including mouse events) as mouse clicks can have different
        // effects depending on the current modifer state.
        //

        //
        // First the modifiers.  IMGetHighLevelKeyState will return us the
        // keyboard state including the event we are currently processing
        // because it is adjusted before the keyboard hook.  The top most
        // bit is set of the key is down otherwise it is reset.
        //
        if (IMGetHighLevelKeyState(VK_CONTROL) & 0x80)
        {
            if (!m_imfInControlCtrlDown)
            {
                //
                // The key is down locally but we last told the remote
                // machine it was up.
                //
                m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                          IEM_EVENT_CTRL_DOWN;
                m_imfInControlCtrlDown = TRUE;
            }
        }
        else
        {
            if (m_imfInControlCtrlDown)
            {
                //
                // The key is up locally but we last told the remote
                // machine it was down.
                //
                m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                            IEM_EVENT_CTRL_UP;
                m_imfInControlCtrlDown = FALSE;
            }
        }

        //
        // Do the same for shift and menu (alt).
        //
        if (IMGetHighLevelKeyState(VK_SHIFT) & 0x80)
        {
            if (!m_imfInControlShiftDown)
            {
                m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                         IEM_EVENT_SHIFT_DOWN;
                m_imfInControlShiftDown = TRUE;
            }
        }
        else
        {
            if (m_imfInControlShiftDown)
            {
                m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                           IEM_EVENT_SHIFT_UP;
                m_imfInControlShiftDown = FALSE;
            }
        }

        if (IMGetHighLevelKeyState(VK_MENU) & 0x80)
        {
            if (!m_imfInControlMenuDown)
            {
                m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                          IEM_EVENT_MENU_DOWN;
                m_imfInControlMenuDown = TRUE;
            }
        }
        else
        {
            if (m_imfInControlMenuDown)
            {
                m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                            IEM_EVENT_MENU_UP;
                m_imfInControlMenuDown = FALSE;
            }
        }

        //
        // Now handle the toggles.  The least significant bit is set when
        // the toggle is on, reset otherwise.
        //
        if ((IMGetHighLevelKeyState(VK_CAPITAL) & IM_KEY_STATE_FLAG_TOGGLE) ?
             !m_imfInControlCapsLock : m_imfInControlCapsLock)
        {
            //
            // The current caps lock state and what we've sent to the
            // remote system are out of synch - fix it.
            //
            m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                     IEM_EVENT_CAPS_LOCK_DOWN;
            m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                       IEM_EVENT_CAPS_LOCK_UP;
            m_imfInControlCapsLock = !m_imfInControlCapsLock;
        }

        //
        // Do the same for Num lock and Scroll lock.
        //
        if ((IMGetHighLevelKeyState(VK_NUMLOCK) & 0x01) ?
            !m_imfInControlNumLock : m_imfInControlNumLock)
        {
            m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                      IEM_EVENT_NUM_LOCK_DOWN;
            m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                        IEM_EVENT_NUM_LOCK_UP;
            m_imfInControlNumLock = !m_imfInControlNumLock;
        }

        if ((IMGetHighLevelKeyState(VK_SCROLL) & 0x01) ?
            !m_imfInControlScrollLock : m_imfInControlScrollLock)
        {
            m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                   IEM_EVENT_SCROLL_LOCK_DOWN;
            m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                     IEM_EVENT_SCROLL_LOCK_UP;
            m_imfInControlScrollLock = !m_imfInControlScrollLock;
        }

        //
        // Now we will do the appropriate processing for each type of
        // packet we expect.  We only expect to receive
        //
        //  IM_TYPE_VK1
        //  IM_TYPE_ASCII
        //  IM_TYPE_3BUTTON
        //
        //

        if (pIMEventIn->type == IM_TYPE_VK1)
        {
            //
            // Now process a VK packet generated from the real keyboard.
            // Check for Escape, Tab and Menu and decide whether to forward
            // them or consume them first.
            //

            if (pIMEventIn->data.keyboard.keyCode == VK_ESCAPE)
            {
                //
                // This is the escape key - check the current shift status
                // to see whether we should flag this as consumed locally.
                //
                if (IMGetHighLevelKeyState(VK_MENU) & 0x80)
                {
                    m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                           IEM_EVENT_CONSUMED;

                    //
                    // Also remember to consume the next Menu Up keystroke.
                    //
                    m_imfInControlConsumeMenuUp = TRUE;

                    if (!IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
                    {
                        //
                        // If this is an escape press then remember that we
                        // should consume the corresponding up stroke
                        // regardless of shift state.
                        //
                        m_imfInControlConsumeEscapeUp = TRUE;
                    }
                }
                else if (m_imfInControlConsumeEscapeUp &&
                         IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
                {
                    //
                    // This is the up stroke corresponding to a down
                    // stroke we consumed so consume it too.
                    //
                    m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                           IEM_EVENT_CONSUMED;
                    m_imfInControlConsumeEscapeUp = FALSE;
                }
                else
                {
                    //
                    // This Escape is not one of our special cases so
                    // forward it unchanged.
                    //
                    m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                            IEM_EVENT_FORWARD;
                }
            }
            else if (pIMEventIn->data.keyboard.keyCode == VK_TAB)
            {
                //
                // This is the Tab key - check for current shift status to
                // see whether we should flag this as consumed locally.
                //
                if (IMGetHighLevelKeyState(VK_MENU) & 0x80)
                {
                    m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                           IEM_EVENT_CONSUMED;

                    //
                    // Also remember to consume the next Menu Up keystroke.
                    //
                    m_imfInControlConsumeMenuUp = TRUE;
                }
                else
                {
                    //
                    // This Tab is not our special case so forward it
                    // unchanged.
                    //
                    m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                            IEM_EVENT_FORWARD;
                }
            }
            else if ((pIMEventIn->data.keyboard.keyCode == VK_MENU) &&
                         IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
            {
                //
                // This is a menu up - check for one we should consume or
                // for hotkeys.
                //
                if (m_imfInControlConsumeMenuUp)
                {
                    //
                    // This is a menu up we want to consume - do so.
                    //
                    m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                           IEM_EVENT_CONSUMED;
                    m_imfInControlConsumeMenuUp = FALSE;
                }
                else
                {
                    //
                    // This is a VK_MENU release
                    // hot key sequence in our array of last four key
                    // presses.  Start looking at the next entry (the array
                    // is circular).  A valid sequence is
                    //
                    //  VK_MENU
                    //  numeric pad 9
                    //  numeric pad number
                    //  numeric pad number
                    //
                    //
                    fHotKeyFound = FALSE;
                    hotKeyArrayIndex = m_imInControlNextHotKeyEntry;
                    if (m_aimInControlHotKeyArray[hotKeyArrayIndex] == VK_MENU)
                    {
                        hotKeyArrayIndex = (hotKeyArrayIndex+1)%4;
                        if (m_aimInControlHotKeyArray[hotKeyArrayIndex] == 9)
                        {
                            hotKeyArrayIndex = (hotKeyArrayIndex+1)%4;
                            if (m_aimInControlHotKeyArray[hotKeyArrayIndex] <= 9)
                            {
                                hotKeyValue =
                                         10*m_aimInControlHotKeyArray[hotKeyArrayIndex];
                                hotKeyArrayIndex = (hotKeyArrayIndex+1)%4;
                                if (m_aimInControlHotKeyArray[hotKeyArrayIndex] <= 9)
                                {
                                    //
                                    // This is a valid hot key - add a
                                    // consumed VK_MENU and then a hot key
                                    // packet.
                                    //
                                    hotKeyValue +=
                                             m_aimInControlHotKeyArray[hotKeyArrayIndex];
                                    m_aimInControlEventsToReturn[
                                                    m_imInControlNumEventsPending++] =
                                                           IEM_EVENT_CONSUMED;
                                    m_aimInControlEventsToReturn[
                                                    m_imInControlNumEventsPending++] =
                                          IEM_EVENT_HOTKEY_BASE + hotKeyValue;
                                    TRACE_OUT(("Hotkey found %d", hotKeyValue));
                                    fHotKeyFound = TRUE;
                                }
                            }
                        }
                    }

                    if (!fHotKeyFound)
                    {
                        //
                        // This was not a hotkey so send the menu up as
                        // normal.
                        //
                        m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                            IEM_EVENT_FORWARD;
                    }
                }
            }
            else if (IS_IM_KEY_PRESS(pIMEventIn->data.keyboard.flags))
            {
                //
                // Keep a record of the last four key presses (not
                // including auto
                // VK_MENU up event to determine if we have found a hotkey
                // sequence.
                //

                //
                // This is a key press and it is not a repeat.  Throw out
                // extended keys here so that we're not confused by the
                // grey cursor keys.
                //
                if (pIMEventIn->data.keyboard.flags &
                                                    IM_FLAG_KEYBOARD_EXTENDED)
                {
                    //
                    // An extended key breaks the sequence.
                    //
                    m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 0xFF;
                }
                else
                {
                    //
                    // Add an entry to our array for this key.  We add
                    // VK_MENUs and add and translate numeric keypad keys
                    // anything else breaks the sequencs.
                    //
                    switch (pIMEventIn->data.keyboard.keyCode)
                    {
                        case VK_MENU:
                            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = VK_MENU;
                            break;

                        case VK_NUMPAD0:
                        case VK_INSERT:
                            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 0;
                            break;

                        case VK_NUMPAD1:
                        case VK_END:
                            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 1;
                            break;

                        case VK_NUMPAD2:
                        case VK_DOWN:
                            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 2;
                            break;

                        case VK_NUMPAD3:
                        case VK_NEXT:
                            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 3;
                            break;

                        case VK_NUMPAD4:
                        case VK_LEFT:
                            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 4;
                            break;

                        case VK_NUMPAD5:
                        case VK_CLEAR:
                            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 5;
                            break;

                        case VK_NUMPAD6:
                        case VK_RIGHT:
                            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 6;
                            break;

                        case VK_NUMPAD7:
                        case VK_HOME:
                            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 7;
                            break;

                        case VK_NUMPAD8:
                        case VK_UP:
                            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 8;
                            break;

                        case VK_NUMPAD9:
                        case VK_PRIOR:
                            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 9;
                            break;

                        default:
                            //
                            // Any unrecognised key breaks a sequence.
                            //
                            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 0xFF;
                            break;
                    }
                }

                //
                // Wrap the hot key array at 4 entries.
                //
                m_imInControlNextHotKeyEntry = (m_imInControlNextHotKeyEntry+1)%4;

                //
                // Forward the event.
                //
                m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                            IEM_EVENT_FORWARD;
            }
            else
            {
                //
                // Just forward the event as its not any of our special
                // cases.
                //
                m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                            IEM_EVENT_FORWARD;
            }
        }
        else if (pIMEventIn->type == IM_TYPE_VK_ASCII)
        {
            m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                        IEM_EVENT_FORWARD;
        }
        else if (pIMEventIn->type == IM_TYPE_ASCII)
        {
            //
            // Any IM_TYPE_ASCII breaks the hot key sequence.
            //
            m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 0xFF;
            m_imInControlNextHotKeyEntry = (m_imInControlNextHotKeyEntry+1)%4;

            //
            // Then just forward the thing without doing anything clever.
            //
            m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                            IEM_EVENT_FORWARD;
        }
        else if (pIMEventIn->type == IM_TYPE_3BUTTON)
        {
            //
            // To be nice and clean we would ideally have a completely new
            // event for the wheeled Microsoft mouse.  However to maintain
            // backwards compatibility, we send the event out in such a way
            // that old incompatible systems interpret it as a NULL mouse
            // move.
            //
            if (pIMEventIn->data.mouse.flags & IM_FLAG_MOUSE_WHEEL)
            {
                //
                // This is a wheel rotatation.
                //
                // We massage this event so that new systems can see it for
                // what it truly is - a wheel rotation, but old systems
                // (which check the MOUSE_MOVE flag first, and ignore all
                // other flags if set) see it as a mouse move.
                //
                // We did not set the MOUSE_MOVE flag when we first
                // generated this event, since we did not want to trigger
                // any of the sending side mouse move processing which
                // would otherwise have been invoked.
                //
                pIMEventIn->data.mouse.flags |= IM_FLAG_MOUSE_MOVE;
            }

            //
            // Forward the event
            //
            m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
                                                            IEM_EVENT_FORWARD;
        }

        //
        // Now we are going into a loop to return the m_iemLocalEvents we
        // have queued up.  We will return the first one below and then be
        // called again until we have returned them all and return FALSE.
        //
        m_imfInControlNewEvent = FALSE;
        m_imInControlNumEventsReturned = 0;
    }

    if (!m_imfInControlNewEvent)
    {
        if (m_imInControlNumEventsReturned == m_imInControlNumEventsPending)
        {
            //
            // There are no more m_aiemLocalEvents to return.
            //
            TRACE_OUT(( "NO MORE EVENTS"));
            m_imfInControlNewEvent = TRUE;
            DC_QUIT;
        }
        else
        {
            //
            // Return the next event.
            //

            if (m_aimInControlEventsToReturn[m_imInControlNumEventsReturned] >=
                                                        IEM_EVENT_HOTKEY_BASE)
            {
                TRACE_OUT(( "HOTKEY  "));
                //
                // Return a hotkey event.
                //
                pIMEventOut->type = IM_TYPE_VK2;
                pIMEventOut->data.keyboard.keyCode = (TSHR_UINT16)
                     (m_aimInControlEventsToReturn[m_imInControlNumEventsReturned] -
                                                       IEM_EVENT_HOTKEY_BASE);
                pIMEventOut->data.keyboard.flags = 0;
            }
            else
            {
                //
                // Return a non-hotkey event.
                //
                switch (m_aimInControlEventsToReturn[m_imInControlNumEventsReturned])
                {
                    case IEM_EVENT_CTRL_DOWN:
                        TRACE_OUT(( "CTRL DWN"));
                        //
                        // Set up a Ctrl down event.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_CONTROL;
                        pIMEventOut->data.keyboard.flags = 0;
                        break;

                    case IEM_EVENT_CTRL_UP:
                        TRACE_OUT(( "CTRL UP "));
                        //
                        // Set up a Ctrl up event with the quiet flag set
                        // - this means it should have no effect (other
                        // than to release the control key).
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_CONTROL;
                        pIMEventOut->data.keyboard.flags =
                                                       IM_FLAG_KEYBOARD_DOWN |
                                                    IM_FLAG_KEYBOARD_RELEASE |
                                                       IM_FLAG_KEYBOARD_QUIET;
                        break;

                    case IEM_EVENT_SHIFT_DOWN:
                        TRACE_OUT(( "SHFT DWN"));
                        //
                        // Set up a Shift down event.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_SHIFT;
                        pIMEventOut->data.keyboard.flags = 0;
                        break;

                    case IEM_EVENT_SHIFT_UP:
                        TRACE_OUT(( "SHFT UP "));
                        //
                        // Set up a Shift up event with the quiet flag set
                        // - this means it should have no effect (other
                        // than to release the shift key).
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_SHIFT;
                        pIMEventOut->data.keyboard.flags =
                                                       IM_FLAG_KEYBOARD_DOWN |
                                                    IM_FLAG_KEYBOARD_RELEASE |
                                                       IM_FLAG_KEYBOARD_QUIET;
                        break;

                    case IEM_EVENT_MENU_DOWN:
                        TRACE_OUT(( "MENU DWN"));
                        //
                        // Set up a Menu down event.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_MENU;
                        break;

                    case IEM_EVENT_MENU_UP:
                        TRACE_OUT(( "MENU UP "));
                        //
                        // Set up a Ctrl down event with the quiet flag set
                        // - ths is means it should have no effect (other
                        // than to release the menu key).
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_MENU;
                        pIMEventOut->data.keyboard.flags =
                                                       IM_FLAG_KEYBOARD_DOWN |
                                                    IM_FLAG_KEYBOARD_RELEASE |
                                                       IM_FLAG_KEYBOARD_QUIET;
                        break;

                    case IEM_EVENT_CAPS_LOCK_DOWN:
                        TRACE_OUT(( "CAPS DWN"));
                        //
                        // Send a caps lock down.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_CAPITAL;
                        pIMEventOut->data.keyboard.flags = 0;
                        break;

                    case IEM_EVENT_CAPS_LOCK_UP:
                        TRACE_OUT(( "CAPS UP "));
                        //
                        // Send a caps lock up.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_CAPITAL;
                        pIMEventOut->data.keyboard.flags =
                                                       IM_FLAG_KEYBOARD_DOWN |
                                                     IM_FLAG_KEYBOARD_RELEASE;
                        break;

                    case IEM_EVENT_NUM_LOCK_DOWN:
                        TRACE_OUT(( "NUM DOWN"));
                        //
                        // Send a num lock down - num lock is an extended
                        // key.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_NUMLOCK;
                        pIMEventOut->data.keyboard.flags =
                                                    IM_FLAG_KEYBOARD_EXTENDED;
                        break;

                    case IEM_EVENT_NUM_LOCK_UP:
                        //
                        // Send a num lock up - num lock is an extended
                        // key.
                        //
                        TRACE_OUT(( "NUM UP  "));
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_NUMLOCK;
                        pIMEventOut->data.keyboard.flags =
                                                       IM_FLAG_KEYBOARD_DOWN |
                                                    IM_FLAG_KEYBOARD_RELEASE |
                                                    IM_FLAG_KEYBOARD_EXTENDED;
                        break;

                    case IEM_EVENT_SCROLL_LOCK_DOWN:
                        //
                        // Send a scroll lock down.
                        //
                        TRACE_OUT(( "SCROLDWN"));
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_SCROLL;
                        pIMEventOut->data.keyboard.flags = 0;
                        break;

                    case IEM_EVENT_SCROLL_LOCK_UP:
                        //
                        // Send a scroll lock up.
                        //
                        TRACE_OUT(( "SCROLLUP"));
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_SCROLL;
                        pIMEventOut->data.keyboard.flags =
                                                       IM_FLAG_KEYBOARD_DOWN |
                                                     IM_FLAG_KEYBOARD_RELEASE;
                        break;

                    case IEM_EVENT_FORWARD:
                        //
                        // Just copy the packet.
                        //
                        TRACE_OUT(( "FORWARD"));
                        *pIMEventOut = *pIMEventIn;
                        break;

                    case IEM_EVENT_CONSUMED:
                        //
                        // Copy the packet and set the flag.
                        //
                        TRACE_OUT(( "CONSUMED"));
                        *pIMEventOut = *pIMEventIn;
                        pIMEventOut->data.keyboard.flags |=
                                                       IM_FLAG_KEYBOARD_QUIET;
                        break;

                    default:
                        ERROR_OUT(( "Invalid code path"));
                        break;
                }
            }
            m_imInControlNumEventsReturned++;

            //
            // Do tracing
            //
            if (pIMEventOut->type == IM_TYPE_ASCII)
            {
                TRACE_OUT(( "OUT ASCII code %04X, flags %04X",
                    pIMEventOut->data.keyboard.keyCode, pIMEventOut->data.keyboard.flags));
            }
            else if (pIMEventOut->type == IM_TYPE_VK1)
            {
                TRACE_OUT(( "OUT VK1   code %04X, flags %04X",
                    pIMEventOut->data.keyboard.keyCode, pIMEventOut->data.keyboard.flags));
            }
            else if (pIMEventOut->type == IM_TYPE_VK2)
            {
                TRACE_OUT(( "OUT VK2   code - %04X, flags - %04X",
                    pIMEventOut->data.keyboard.keyCode, pIMEventOut->data.keyboard.flags));
            }
            else if ((pIMEventOut->type == IM_TYPE_3BUTTON) &&
                       !(pIMEventOut->data.mouse.flags & IM_FLAG_MOUSE_MOVE))
            {
                TRACE_OUT(( "OUT 3BTTN flags - %04X (%d,%d)",
                    pIMEventOut->data.mouse.flags, pIMEventOut->data.mouse.x,
                    pIMEventOut->data.mouse.y));
            }
            else if (pIMEventOut->type == IM_TYPE_3BUTTON)
            {
                TRACE_OUT(( "OUT 3BTTN flags - %04X (%d,%d)",
                    pIMEventOut->data.mouse.flags, pIMEventOut->data.mouse.x,
                    pIMEventOut->data.mouse.y));
            }
            else
            {
                ERROR_OUT(("Invalid IM type %d", pIMEventOut->type));
            }

            rc = TRUE;
        }
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::IMTranslateOutgoing);
    return(rc);
}



//
// IMTranslateIncoming()
//
// DESCRIPTION:
//
// Converts remotely generated sequences of IMEVENTs into sequences of
// IMEVENTs for replay.  Does a 1 to (0-n) translation.  Handles faking
// keys using ALT and keypad.
//
// When an IMEVENT is received and is ready to be replayed this function
// is called with a pointer to that packet in pIMEventIn.
// IMTranslateIncoming can then return TRUE and fill in the packet at
// pIMEventOut or return FALSE.  If IMTranslateIncoming returns TRUE the
// IM will call it again with the same packet.  The IMEVENTs returned are
// played back on the local machine using the journal playback hook by the
// IM.
//
// PARAMETERS:
//
// pIMEventIn - pointer to IMEVENT
//
// pIMEventOut - pointer to IMEVENT
//
// personID - the ID of the person this event was received from
//
// RETURNS:
//
// TRUE - packet returned (call function again)
//
// FALSE - no packet returned (don't call function again)
//
//
//
BOOL ASShare::IMTranslateIncoming
(
    PIMEVENT    pIMEventIn,
    PIMEVENT    pIMEventOut
)
{
    BYTE        curKbState;
    BYTE        rcVkKeyScanKbState;
    UINT        keyCode;
    TSHR_UINT16 rcVkKeyScan;
    BOOL        bTranslateOEM;
    char        chAnsi;
    char        chOEM;
    char        chNewAnsi;
    UINT        position;
    UINT        digit;
    UINT        i;

    DebugEntry(ASShare::IMTranslateIncoming);

    //
    // In this function we will receive several types of events
    //
    //  IM_TYPE_VK1 - processed
    //  IM_TYPE_ASCII - processed
    //  IM_TYPE_VK2 - ignored (discarded)
    //  IM_TYPE_3BUTTON - processed
    //
    // For IM_TYPE_VK1:
    //
    // If it has the consumed locally flag set then try and play it back
    // without anything happening.  This means that for an Alt up we make
    // sure that there have been some keyboard events between the Alt down
    // and this event.
    //
    // For IM_TYPE_ASCII:
    //
    // Try to convert this to a VK to playback.  If we are succesful then
    // playback one or more key strokes to get into the correct shift state
    // then play back the VK and then undo any shift states.  If we can't
    // convert to a VK then fake a sequence of Alt + numeric keypad keys to
    // get the key in.
    //
    // For IM_TYPE_VK2:
    //
    // Discard unceremoniously.
    //
    // For IM_TYPE_3BUTTON:
    //
    // Play back directly.
    //
    //
    keyCode = pIMEventIn->data.keyboard.keyCode;

    if (m_imfControlledNewEvent)
    {
        //
        // The first time we have seen a new event - accumulate an array
        // of events we want to return.
        //

        //
        // Start from the beginning of our returned events array.
        //
        m_imControlledNumEventsPending = 0;
        m_imControlledNumEventsReturned = 0;

        if (pIMEventIn->type == IM_TYPE_VK1)
        {
            //
            // Handle VK1s first.  Special cases are VK_MENU, VK_TAB and
            // VK_ESC.  We recognise VK_MENU down key strokes and remember
            // when they happened so that we can possibly fiddle with
            // VK_MENU up keystrokes later to go into menu mode.  We check
            // on VK_TAB for the IM_FLAG_KEYBOARD_QUIET flag and if it is
            // set then we don't replay anything
            // First translate the virtual key code from the DC-Share
            // protocol code to the OS virtual key code
            //
            if (keyCode == VK_MENU)
            {
                if (!IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
                {
                    //
                    // This is a VK_MENU press - return it without
                    // interfering.
                    //
                    m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                             IEM_EVENT_REPLAY;
                }
                else
                {
                    //
                    // Handle VK_MENU up events
                    //
                    // If the menu up has the `quiet' flag set then
                    // insert a couple of shift key events to prevent it
                    // having any effect.  There are two cases we're
                    // covering here where an Alt-UP can have some effect.
                    //
                    // 1. Alt-Down, Alt-Up causes the system menu button to
                    // be highlighted.
                    //
                    // 2. Entering characters from the numeric keypad takes
                    // effect on the Alt-Up.
                    //
                    // Both of these effects can be negated by adding the
                    // shift key strokes.
                    //
                    if (pIMEventIn->data.keyboard.flags &
                                                       IM_FLAG_KEYBOARD_QUIET)
                    {
                        //
                        // We need to `silence' this key - to do this we
                        // will insert to shift key strokes first
                        //
                        if (m_aimControlledControllerKeyStates[VK_SHIFT] & 0x80)
                        {
                            //
                            // Shift is currently down - insert an up then
                            // a down
                            //
                            m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                           IEM_EVENT_SHIFT_UP;
                            m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                         IEM_EVENT_SHIFT_DOWN;

                        }
                        else
                        {
                            //
                            // Shift is currently up - insert a down then
                            // an up
                            //
                            m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                         IEM_EVENT_SHIFT_DOWN;
                            m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                           IEM_EVENT_SHIFT_UP;
                        }
                    }

                    //
                    // Replay the menu up key stroke.
                    //
                    m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                             IEM_EVENT_REPLAY;

                }
            }
            else if ((pIMEventIn->data.keyboard.flags &
                                                   IM_FLAG_KEYBOARD_QUIET) &&
                     ((keyCode == VK_TAB) ||
                      (keyCode == VK_ESCAPE)))
            {
                //
                // Just get out of here - we don't want to play this back
                //
                return(FALSE);
            }
            else
            {
                //
                // All other VKs just get replayed
                //
                m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                             IEM_EVENT_REPLAY;
            }
        }
        else if (pIMEventIn->type == IM_TYPE_ASCII)
        {
            //
            // For ASCII packets we need to find out how we can replay them
            // on our local keyboard.  If we can replay them directly or
            // with shift or ctrl (but not with ALT), then we will do so,
            // otherwise we will simulate Alt + numeric keypad to replay
            // them.  If we have to generate fake modifier key strokes
            // ourselves then we will replay the whole key stroke on the
            // incoming key down.  If we don't need to generate fake key
            // strokes then we will play the down and up keystrokes as they
            // come in.
            //
            // We do not allow VK combinations involving ALT as this messes
            // up remote international keyboard support.  For example, if
            // the remote keyboard is UK and we are (say) Spanish,
            // VKKeyScan says we can do the "UK pound" character as
            // Ctrl+Alt+3.  While this works in Windows, and for DOS Boxes
            // on standard keyboards, DOS Boxes with enhanced keyboards
            // require ALTGR+3 (nb Windows seems to treat ALTGR as Ctrl+Alt
            // anyway - at least for VKs and Async state).  There is no VK
            // for ALTGR, so do an ALT-nnn sequence for these cases.
            //
            rcVkKeyScan = VkKeyScan((char)keyCode);
            TRACE_OUT(( "co_vk_key_scan of X%02x returns rcVkKeyScan X%02x",
                            keyCode, rcVkKeyScan));
            if ((rcVkKeyScan != 0xffff) && !(rcVkKeyScan & 0x0400))
            {
                //
                // This can be replayed using a combination of modifiers on
                // this keyboard.
                //
                rcVkKeyScanKbState = HIBYTE(rcVkKeyScan);

                //
                // The high byte of rcVkKeyScan contains three bit flags
                // which signify which modifiers ar required to generate
                // this character.  They are
                //
                //  bit 0 - Shift
                //  bit 1 - Ctrl
                //  bit 2 - Alt (Menu)
                //
                // We will construct an equivalent set of flags which
                // describes the current state of these modifiers.
                //
                curKbState = 0;

                if (m_aimControlledControllerKeyStates[VK_SHIFT] & 0x80)
                {
                    curKbState |= IEM_SHIFT_DOWN;
                }

                if (m_aimControlledControllerKeyStates[VK_CONTROL] & 0x80)
                {
                    curKbState |= IEM_CTRL_DOWN;
                }

                if (m_aimControlledControllerKeyStates[VK_MENU] & 0x80)
                {
                    curKbState |= IEM_MENU_DOWN;

                    //
                    // If the Alt key is down currently in this person's
                    // context then (in general
                    // it.  This means accelerators which need to be
                    // shifted will work as we won't release the Alt key in
                    // order to generate the key strokes.
                    //
                    // However, if the ALT key is being held down in
                    // combination with SHIFT and CTRL to generate a
                    // character (e.g.  CTRL-ALT-SHIFT-4 on a US keyboard
                    // to generate a � character) then we will allow the
                    // ALT key up before we play back the true character.
                    //
                    if ((curKbState & (IEM_SHIFT_DOWN | IEM_CTRL_DOWN)) !=
                                             (IEM_SHIFT_DOWN | IEM_CTRL_DOWN))
                    {
                        rcVkKeyScanKbState |= IEM_MENU_DOWN;
                    }
                }

                if ((m_aimControlledControllerKeyStates[VK_CAPITAL] & 0x01) &&
                    ((LOBYTE(rcVkKeyScan) >= 'A') &&
                    ((LOBYTE(rcVkKeyScan) <= 'Z'))))
                {
                    //
                    // If caps-lock is enabled then the effect of a shift
                    // down on VKs A thru Z is reversed.  This logic ( 'A'
                    // <= x <= 'Z' is encoded in the keyboard.drv so it
                    // should be pretty safe).
                    //
                    curKbState ^= IEM_SHIFT_DOWN;
                }

                if (curKbState == rcVkKeyScanKbState)
                {
                    //
                    // We are already in the correct shift state so just
                    // replay the VK.
                    //
                    m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                          IEM_EVENT_REPLAY_VK;
                    m_imControlledVKToReplay = LOBYTE(rcVkKeyScan);
                }
                else
                {
                    //
                    // We need to generate some fake modifiers - only do
                    // this on a key press.
                    //
                    if (pIMEventIn->data.keyboard.flags &
                                                     IM_FLAG_KEYBOARD_RELEASE)
                    {
                        return(FALSE);
                    }

                    //
                    // Insert modifiers to get into the correct state.
                    //
                    m_imControlledNumEventsPending += IMInsertModifierKeystrokes(
                                curKbState,
                                rcVkKeyScanKbState,
                                &(m_aimControlledEventsToReturn[m_imControlledNumEventsPending]));

                    //
                    // Now insert the VK itself - a down and up.
                    //
                    m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                     IEM_EVENT_REPLAY_VK_DOWN;
                    m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                       IEM_EVENT_REPLAY_VK_UP;

                    //
                    // Remeber the VK we want to replay when we come across
                    // IEM_EVENT_REPLAY_VK_DOWN/UP.
                    //
                    m_imControlledVKToReplay = LOBYTE(rcVkKeyScan);

                    //
                    // Now insert the modifiers to get back to the current
                    // state.
                    //
                    m_imControlledNumEventsPending += IMInsertModifierKeystrokes(
                                rcVkKeyScanKbState,
                                curKbState,
                                &(m_aimControlledEventsToReturn[m_imControlledNumEventsPending]));

                    //
                    // Now we have a complete set of events ready to replay
                    // so go for it.
                    //
                }
            }
            else
            {
                //
                // We can't replay directly, so will have to simulate an
                // Alt+keypad sequence.
                //
                TRACE_OUT(( "FAKE AN ALT-nnn SEQUENCE IF WINDOWS"));
                //
                // We only do this sort of stuff on a key-press.
                //
                if (pIMEventIn->data.keyboard.flags &
                                                     IM_FLAG_KEYBOARD_RELEASE)
                {
                    return(FALSE);
                }

                //
                // The following code relies on keyCode being less than 999
                // and we should receive a keycode > 255 so get out now if
                // we have.
                //
                if (keyCode > 255)
                {
                    return(FALSE);
                }

                //
                // First get modifiers into correct state - create bit
                // flags for current modifier state.
                //
                curKbState = 0;

                //
                // For windows we have a character to input that cannot
                // be replayed by pressing a key...replay by injecting
                // alt-nnn.
                //
                if (m_aimControlledControllerKeyStates[VK_SHIFT] & 0x80)
                {
                    curKbState |= IEM_SHIFT_DOWN;
                }

                if (m_aimControlledControllerKeyStates[VK_CONTROL] & 0x80)
                {
                    curKbState |= IEM_CTRL_DOWN;
                }

                if (m_aimControlledControllerKeyStates[VK_MENU] & 0x80)
                {
                    curKbState |= IEM_MENU_DOWN;
                }

                //
                // If necessary, reset all modifiers.
                //
                if (curKbState)
                {
                    m_imControlledNumEventsPending += IMInsertModifierKeystrokes(
                                curKbState,
                                0,
                                &(m_aimControlledEventsToReturn[m_imControlledNumEventsPending]));
                }

                //
                // Now determine whether we can do the ALT-nnn keypad
                // sequence using an OEM keycode or whether we have to use
                // an ANSI (Windows) keycode.
                //
                // The issue here is that:
                //
                // - hosted Windows applications (or rather Windows itself)
                //   can distinguish between, and handle correctly, ANSI
                //   keycodes and OEM keycodes (where the latter vary
                //   depending on the keyboard type).  For example,
                //   ALT-0163 is the ANSI "UK pound" on all keyboards,
                //   and on US national keyboards ALT-156 is the OEM
                //   keycode for "UK pound".
                //
                // - hosted DOS Boxes only understand OEM keycodes.
                //
                // So (for example), if we have a remote UK keyboard
                // controlling local Windows and DOS Box applications, and
                // we generate ALT-nnn using the OEM keycode (and without a
                // leading zero), both the Windows and DOS Box applications
                // interpret it as "UK pound" (Hoorah!).  In contrast, if
                // we generate ALT-nnn using the ANSI keycode (with a
                // leading zero), the Windows applications still do "UK
                // pound", BUT the DOS Box does an "u acute".
                //
                // As far as we can tell (eg by examining the DDK keyboard
                // driver source for AnsiToOem), there should always be a
                // translation.  However, it is possible that the ANSI to
                // OEM translation is not 1<->1.  We therefore check this
                // by doing a second translation back from OEM to ANSI.  If
                // this does not give us the original character we use the
                // original ANSI code and play it back with a ALT-0nnn
                // sequence.
                //
                chAnsi = (char)pIMEventIn->data.keyboard.keyCode;

                AnsiToOemBuff(&chAnsi, &chOEM, 1);
                OemToAnsiBuff(&chOEM, &chNewAnsi, 1);
                TRACE_OUT(( "Ansi: %02x OEM: %02x NewAnsi: %02x",
                                              (BYTE)chAnsi,
                                              (BYTE)chOEM,
                                              (BYTE)chNewAnsi ));

                bTranslateOEM = (chAnsi == chNewAnsi);

                keyCode = (bTranslateOEM)
                              ? (UINT)(BYTE)chOEM
                              : pIMEventIn->data.keyboard.keyCode;

                //
                // Now insert a VK_MENU down.
                //
                m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                          IEM_EVENT_MENU_DOWN;

                //
                // Now insert the numeric keypad keystrokes.  If we're
                // doing an ANSI ALT
                //
                if (!bTranslateOEM)
                {
                    m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                       IEM_EVENT_KEYPAD0_DOWN;
                    m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                         IEM_EVENT_KEYPAD0_UP;
                }


                //
                // Add keystrokes for hundreds, tens and units, taking care
                // to discard leading (but not trailing) zeros if we're
                // doing an OEM sequence (which would confuse Windows into
                // thinking an OEM ALT-nnn sequence was an ANSI sequence).
                //
                position = 100;
                for (i=0 ; i<3 ; i++)
                {
                    //
                    // Insert the correct digit for this position.
                    //
                    digit = keyCode / position;

                    if (!(digit == 0 && bTranslateOEM))
                    {
                        bTranslateOEM = FALSE;
                        m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                               IEM_EVENT_KEYPAD0_DOWN + digit;
                        m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                 IEM_EVENT_KEYPAD0_UP + digit;
                    }

                    //
                    // Move to next position.
                    //
                    keyCode %= position;
                    position /= 10;
                }

                //
                // Now insert a VK_MENU up.
                //
                m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
                                                            IEM_EVENT_MENU_UP;


                //
                // If necessary, get the modifiers back to the state they
                // were in previously.
                //
                if (curKbState != 0)
                {
                    m_imControlledNumEventsPending += IMInsertModifierKeystrokes(
                                0,
                                curKbState,
                                &(m_aimControlledEventsToReturn[m_imControlledNumEventsPending]));
                }

                //
                // Now we have a buffer full of keystrokes - go for it.
                //
            }
        }
        else if (pIMEventIn->type == IM_TYPE_VK2)
        {
            //
            // Hot keys are thrown away - this is easy.
            //
            return(FALSE);
        }
        else if (pIMEventIn->type == IM_TYPE_3BUTTON)
        {
            //
            // Mouse events are just replayed.
            //
            m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] = IEM_EVENT_REPLAY;
        }
        else
        {
            //
            // Unknown events are thrown away - this is easy.
            //
            return(FALSE);
        }

        //
        // Now we have events to return.
        //
        m_imfControlledNewEvent = FALSE;
        m_imControlledNumEventsReturned = 0;
    }

    if (!m_imfControlledNewEvent)
    {
        if (m_imControlledNumEventsReturned == m_imControlledNumEventsPending)
        {
            //
            // There are no more events to return.
            //
            m_imfControlledNewEvent = TRUE;
            return(FALSE);
        }
        else
        {
            TRACE_OUT(("Event to return: %u",
                m_aimControlledEventsToReturn[m_imControlledNumEventsReturned]));
            if ((m_aimControlledEventsToReturn[m_imControlledNumEventsReturned] >=
                                                    IEM_EVENT_KEYPAD0_DOWN) &&
                (m_aimControlledEventsToReturn[m_imControlledNumEventsReturned] <=
                                                  (IEM_EVENT_KEYPAD0_DOWN+9)))
            {
                //
                // Return a keypad down event.
                //
                pIMEventOut->type = IM_TYPE_VK1;
                pIMEventOut->data.keyboard.keyCode = (TSHR_UINT16)
                  (VK_NUMPAD0 +
                          (m_aimControlledEventsToReturn[m_imControlledNumEventsReturned] -
                                                     IEM_EVENT_KEYPAD0_DOWN));
                pIMEventOut->data.keyboard.flags = IM_FLAG_KEYBOARD_ALT_DOWN;
            }
            else if ((m_aimControlledEventsToReturn[m_imControlledNumEventsReturned] >=
                                                      IEM_EVENT_KEYPAD0_UP) &&
                     (m_aimControlledEventsToReturn[m_imControlledNumEventsReturned] <=
                                                    (IEM_EVENT_KEYPAD0_UP+9)))
            {
                //
                // Return a keypad up event.
                //
                pIMEventOut->type = IM_TYPE_VK1;
                pIMEventOut->data.keyboard.keyCode = (TSHR_UINT16)
                  (VK_NUMPAD0 +
                             (m_aimControlledEventsToReturn[m_imControlledNumEventsReturned] -
                                                       IEM_EVENT_KEYPAD0_UP));
                pIMEventOut->data.keyboard.flags = IM_FLAG_KEYBOARD_DOWN |
                                                   IM_FLAG_KEYBOARD_RELEASE |
                                                   IM_FLAG_KEYBOARD_ALT_DOWN;
            }
            else
            {
                switch (m_aimControlledEventsToReturn[m_imControlledNumEventsReturned])
                {
                    case IEM_EVENT_CTRL_DOWN:
                        //
                        // Set up a Ctrl down event.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode =
                                                           VK_CONTROL;
                        pIMEventOut->data.keyboard.flags = 0;
                        break;

                    case IEM_EVENT_CTRL_UP:
                        //
                        // Set up a Ctrl up event.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode =
                                                           VK_CONTROL;
                        pIMEventOut->data.keyboard.flags =
                             IM_FLAG_KEYBOARD_DOWN | IM_FLAG_KEYBOARD_RELEASE;
                        break;

                    case IEM_EVENT_SHIFT_DOWN:
                        //
                        // Set up a Shift down event.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode =
                                                             VK_SHIFT;
                        pIMEventOut->data.keyboard.flags = 0;
                        break;

                    case IEM_EVENT_SHIFT_UP:
                        //
                        // Set up a Shift up event.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode =
                                                             VK_SHIFT;
                        pIMEventOut->data.keyboard.flags =
                             IM_FLAG_KEYBOARD_DOWN | IM_FLAG_KEYBOARD_RELEASE;
                        break;

                    case IEM_EVENT_MENU_DOWN:
                        //
                        // Set up a Menu down event.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_MENU;
                        pIMEventOut->data.keyboard.flags = 0;
                        break;

                    case IEM_EVENT_MENU_UP:
                        //
                        // Set up a Menu up event.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = VK_MENU;
                        pIMEventOut->data.keyboard.flags =
                            IM_FLAG_KEYBOARD_DOWN | IM_FLAG_KEYBOARD_RELEASE;
                        break;

                    case IEM_EVENT_REPLAY:
                        //
                        // Just copy the packet.
                        //
                        *pIMEventOut = *pIMEventIn;
                        break;

                    case IEM_EVENT_REPLAY_VK:
                        //
                        // Replay the VK from m_imControlledVKToReplay using the
                        // flags on the incoming packet.
                        //
                        *pIMEventOut = *pIMEventIn;
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = (TSHR_UINT16)
                                                             m_imControlledVKToReplay;
                        break;

                    case IEM_EVENT_REPLAY_VK_UP:
                        //
                        // Replay an up key event for the VK in
                        // m_imControlledVKToReplay.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = (TSHR_UINT16)
                                                             m_imControlledVKToReplay;
                        pIMEventOut->data.keyboard.flags =
                             IM_FLAG_KEYBOARD_DOWN | IM_FLAG_KEYBOARD_RELEASE;
                        break;

                    case IEM_EVENT_REPLAY_VK_DOWN:
                        //
                        // Replay a down key event for the VK in
                        // m_imControlledVKToReplay.
                        //
                        pIMEventOut->type = IM_TYPE_VK1;
                        pIMEventOut->data.keyboard.keyCode = (TSHR_UINT16)
                                                             m_imControlledVKToReplay;
                        pIMEventOut->data.keyboard.flags = 0;
                        break;

                    case IEM_EVENT_NORMAL:
                        //
                        // Play back the event but force it to be normal.
                        //
                        *pIMEventOut = *pIMEventIn;
                        pIMEventOut->data.keyboard.flags &=
                                        (TSHR_UINT16)~IM_FLAG_KEYBOARD_ALT_DOWN;
                        break;

                    case IEM_EVENT_SYSTEM:
                        //
                        // Play back the event but force it to be system.
                        //
                        *pIMEventOut = *pIMEventIn;
                        pIMEventOut->data.keyboard.flags |=
                                                   IM_FLAG_KEYBOARD_ALT_DOWN;
                        break;

                    default:
                        ERROR_OUT(( "Invalid code path"));
                        break;
                }
            }
        }

        m_imControlledNumEventsReturned++;

        //
        // If we're going to playback a NUMLOCK event, make sure we force
        // the keyboard LEDs to be accurate.
        //
        if ((pIMEventOut->type == IM_TYPE_VK1) &&
            (pIMEventOut->data.keyboard.keyCode == VK_NUMLOCK) &&
            IS_IM_KEY_PRESS(pIMEventOut->data.keyboard.flags))
        {
            TRACE_OUT(("Playing back NUMLOCK; add IM_FLAG_KEYBOARD_UPDATESTATE"));
            pIMEventOut->data.keyboard.flags |= IM_FLAG_KEYBOARD_UPDATESTATE;
        }

        return(TRUE);
    }

    DebugExitBOOL(ASShare::IMTranslateIncoming, FALSE);
    return(FALSE);
}


//
// FUNCTION: IMInsertModifierKeystrokes
//
// DESCRIPTION:
//
// This function inserts various modifier keystrokes into the supplied
// buffer to move from one modifier state to another.
//
// PARAMETERS:
//
// curKbState - the current modifier state (bit 0 - Shift, bit 1 - Ctrl,
// bit 2 - Menu).
//
// targetKbState - the state we want the modifiers to be in
//
// pEventQueue - a pointer to an array where the required events can be
// inserted
//
// RETURNS: the number of events inserted
//
//
UINT ASShare::IMInsertModifierKeystrokes
(
    BYTE    curKbState,
    BYTE    targetKbState,
    LPUINT  pEventQueue
)
{

    UINT  kbDelta;
    UINT  events = 0;

    DebugEntry(ASShare::IMInsertModifierKeystrokes);

    //
    // Find out which modifiers are different.
    //
    kbDelta = curKbState ^ targetKbState;
    TRACE_OUT(( "Keyboard delat %x", kbDelta));

    //
    // Now generate the right events to get us into the correct modifier
    // state.
    //
    if (kbDelta & IEM_SHIFT_DOWN)
    {
        //
        // Shift state is different - do we need an up or down.
        //
        if (curKbState & IEM_SHIFT_DOWN)
        {
            //
            // We need an up.
            //
            pEventQueue[events++] = IEM_EVENT_SHIFT_UP;
        }
        else
        {
            //
            // We need a down.
            //
            pEventQueue[events++] = IEM_EVENT_SHIFT_DOWN;
        }
    }

    //
    // Same process for Ctrl and Alt.
    //
    if (kbDelta & IEM_CTRL_DOWN)
    {
        if (curKbState & IEM_CTRL_DOWN)
        {
            pEventQueue[events++] = IEM_EVENT_CTRL_UP;
        }
        else
        {
            pEventQueue[events++] = IEM_EVENT_CTRL_DOWN;
        }
    }

    if (kbDelta & IEM_MENU_DOWN)
    {
        if (curKbState & IEM_MENU_DOWN)
        {
            pEventQueue[events++] = IEM_EVENT_MENU_UP;
        }
        else
        {
            pEventQueue[events++] = IEM_EVENT_MENU_DOWN;
        }
    }

    DebugExitDWORD(ASShare::IMInsertModifierKeystrokes, events);
    return(events);
}


//
// IMInjectEvent()
//
// DESCRIPTION:
//
// Called by IMMaybeInjectEvents when it is ready to inject an event.
// Given a pointer to a IMOSEVENT this function formats it correctly and
// calls the appropriate USER callback.  It also updates the async key
// state arrays for the source queue and USER and sets m_imLastInjectTime to
// the tick count at which the event was injected.  We protect against
// injecting up key strokes/mouse buttons when USER does not think the
// key/button is down in this function.  It is quite possible (given the
// potential variety of CAs) that the IM will be asked to inject an up
// event when there has been no corresponding down event.  This should be
// harmless as it is possible for this to happen in real life (ie the
// system message queue is full when the down event happens but there is
// space when the up event happens).  However, it is quite unlikely and it
// is more likely that injecting these unmatched events will confuse
// applications.
//
// PARAMETERS:
//
// pEvent - pointer to an IMOSEVENT.
//
// THIS WORKS FOR NT AND WIN95.
//
BOOL  ASShare::IMInjectEvent(LPIMOSEVENT pEvent)
{
    UINT            clickTime;
    TSHR_UINT16     flags;
    TSHR_UINT16     flagsAfter;
    LPMSEV          pMouseEvent;

    DebugEntry(IMInjectEvent);

    //
    // Now inject the event.
    //
    switch (pEvent->type)
    {
        case IM_MOUSE_EVENT:
            //
            // Set up a pointer to the mouse event data.
            //
            pMouseEvent = &(pEvent->event.mouse);

            //
            // Check whether this is an unmatched up event
            //
            if ((IM_MEV_BUTTON1_UP(*pEvent) &&
                        IM_KEY_STATE_IS_UP(m_aimControlledKeyStates[VK_LBUTTON])) ||
                (IM_MEV_BUTTON2_UP(*pEvent) &&
                        IM_KEY_STATE_IS_UP(m_aimControlledKeyStates[VK_RBUTTON])) ||
                (IM_MEV_BUTTON3_UP(*pEvent) &&
                          IM_KEY_STATE_IS_UP(m_aimControlledKeyStates[VK_MBUTTON])))
            {
                //
                // This is an unmatched up event so just discard it here
                //
                TRACE_OUT(("IMInjectEvent: discarding unmatched mouse up event"));
                DC_QUIT;
            }

            //
            // Store the injection time of this event.
            //
            m_imControlledLastLowLevelMouseEventTime = GetTickCount();

            //
            // Store the mouse position - only consider absolute mouse
            // moves.  (Note that for the cases in which we inject a
            // relative mouse event we always set the co-ordinate change to
            // 0).
            //
            if (pMouseEvent->flags & MOUSEEVENTF_ABSOLUTE)
            {
                m_imControlledLastMousePos.x = pMouseEvent->pt.x;
                m_imControlledLastMousePos.y = pMouseEvent->pt.y;

                TRACE_OUT(( "Updating mouse position (%d:%d)",
                         m_imControlledLastMousePos.x,
                         m_imControlledLastMousePos.y));
            }

            //
            // Inject the event.
            //
            TRACE_OUT(("IMInjectEvent: MOUSE parameters are:"));
            TRACE_OUT(("      flags       0x%08x", pMouseEvent->flags));
            TRACE_OUT(("      time        0x%08x", m_imControlledLastLowLevelMouseEventTime));
            TRACE_OUT(("      position    (%d, %d)", pMouseEvent->pt.x, pMouseEvent->pt.y));
            TRACE_OUT(("      mouseData   %d", pMouseEvent->mouseData));
            TRACE_OUT(("      dwExtra     %d", pMouseEvent->dwExtraInfo));

            //
            // Finally scale the logical screen co-ordinates to the full
            // 16-bit range (0..65535).
            //

            ASSERT(m_pasLocal->cpcCaps.screen.capsScreenWidth);
            ASSERT(m_pasLocal->cpcCaps.screen.capsScreenHeight);

            pMouseEvent->pt.x = IM_MOUSEPOS_LOG_TO_OS(pMouseEvent->pt.x,
                                                      m_pasLocal->cpcCaps.screen.capsScreenWidth);
            pMouseEvent->pt.y = IM_MOUSEPOS_LOG_TO_OS(pMouseEvent->pt.y,
                                                      m_pasLocal->cpcCaps.screen.capsScreenHeight);

            OSI_InjectMouseEvent(pMouseEvent->flags, pMouseEvent->pt.x,
                pMouseEvent->pt.y, pMouseEvent->mouseData, pMouseEvent->dwExtraInfo);
            break;

        case IM_KEYBOARD_EVENT:
            //
            // Check whether this is an unmatched up event
            //
            if (IM_KEV_KEYUP(*pEvent) &&
                IM_KEY_STATE_IS_UP(m_aimControlledKeyStates[IM_KEV_VKCODE(*pEvent)]))
            {
                //
                // This is an unmatched up event so just discard it.
                //
                TRACE_OUT(("IMInjectEvent: discarding unmatched key up event %04hX",
                                                     IM_KEV_VKCODE(*pEvent)));
                DC_QUIT;
            }

            //
            // Inject the event.
            //
            TRACE_OUT(("IMInjectEvent: KEYBD parameters are:"));
            TRACE_OUT(("      flags       0x%08x", pEvent->event.keyboard.flags));
            TRACE_OUT(("      virtkey     %u", pEvent->event.keyboard.vkCode));
            TRACE_OUT(("      scan code   %u", pEvent->event.keyboard.scanCode));

            OSI_InjectKeyboardEvent(pEvent->event.keyboard.flags,
                pEvent->event.keyboard.vkCode, pEvent->event.keyboard.scanCode,
                pEvent->event.keyboard.dwExtraInfo);

            if (pEvent->flags & IM_FLAG_UPDATESTATE)
            {
                BYTE     kbState[256];

                TRACE_OUT(("Updating keyboard LED state after playing back toggle"));

                GetKeyboardState(kbState);
                SetKeyboardState(kbState);
            }
            break;

        default:
            //
            // We do nothing for unexpected events - this allow us to add
            // more events later that can be sent to back level systems
            // where they will be safely ignored
            //
            TRACE_OUT(( "Unexpected event %d", pEvent->type));
            DC_QUIT;
     }

    //
    // If we get here successfully then we want to update our copy of the
    // async key state so set the flag.
    //
    IMUpdateAsyncArray(m_aimControlledKeyStates, pEvent);

DC_EXIT_POINT:

    DebugExitBOOL(ASShare::IMInjectEvent, TRUE);
    return(TRUE);
}


//
// FUNCTION: IMInjectingEvents
//
BOOL  ASShare::IMInjectingEvents(void)
{
    LPIMOSEVENT     pNextEvent;
    IMOSEVENT       mouseMoveEvent;
    UINT            tick;
    UINT            targetTime;
    UINT            targetDelta;
    BOOL            rc = TRUE;

    DebugEntry(ASShare::IMInjectingEvents);

    if (m_pasLocal->m_caControlledBy && m_imControlledOSQ.numEvents)
    {
        pNextEvent = m_imControlledOSQ.events + m_imControlledOSQ.head;

        //
        // First check if this is a remote mouse event being injected too
        // soon after the previous one.  We used to only do this for mouse
        // move events to prevent them all being spoiled if they were
        // injected too quickly.  However, we now do it for all mouse
        // events because of a bug in Windows USER whereby if the mouse
        // press which brings up a menu is processed after the
        // corresponding mouse release has been passed to USER (so that the
        // async state of the mouse button is up) then the menu is brought
        // up in the position it is brought up in if it is selected via the
        // keyboard rather than the position it is brought up in if it is
        // selected by the mouse.  (These positions are only different when
        // the menu cannot be placed completely below or above the menu
        // bar).  This can then lead to the mouse release selecting an item
        // from the menu.
        //
        tick = GetTickCount();
        if (m_imfControlledPaceInjection &&
            (pNextEvent->type == IM_MOUSE_EVENT))
        {
            //
            // This is a remote mouse event so check that now is a good
            // time to inject it Smooth out the backlog adjustment so that
            // packet bursts do not get spoiled too much.  Set an absolute
            // lg_lpimSharedData->imit on injection delay of the low sample rate so that
            // timestamp anomolies do not cause us to withhold messages
            //

            //
            // The target delta between last and current events is
            // calculated from the remote timestamps
            //
            targetDelta = abs((int)(pNextEvent->time -
                                                m_imControlledLastMouseRemoteTime));
            if (targetDelta > IM_LOCAL_MOUSE_SAMPLING_GAP_LOW_MS)
            {
                targetDelta = IM_LOCAL_MOUSE_SAMPLING_GAP_LOW_MS;
            }

            //
            // The target injection time is based on the last injection
            // time and our target delta, adjusted for any backlog we are
            // seeing.  Because packeting gives a jerky backlog we need to
            // smooth our adjustment out (only modify by backlog/8)
            //
            targetTime = m_imControlledLastMouseLocalTime +
                         targetDelta - (m_imControlledMouseBacklog/8);

            TRACE_OUT(( "Last tremote %#lx, this tremote %#lx, backlog %#lx",
                          m_imControlledLastMouseRemoteTime,
                          pNextEvent->time,
                          m_imControlledMouseBacklog));
            TRACE_OUT(( "Last tlocal %#lx, tick %#lx, targetTime %#lx",
                          m_imControlledLastMouseLocalTime,
                          tick,
                          targetTime));

            //
            // Now inject the events - ignore them if they are too early
            //
            if (IM_MEV_ABS_MOVE(*pNextEvent) && (tick < targetTime))
            {
                //
                // If values seem wild (for example this is the first mouse
                // event ever) then reset them
                //
                if (targetTime > tick + 1000)
                {
                    m_imControlledLastMouseRemoteTime = pNextEvent->time;
                    m_imControlledLastMouseLocalTime  = tick;
                    m_imControlledMouseBacklog = 0;
                    TRACE_OUT(( "Wild values - reset"));
                }
                else
                {
                    //
                    // This is too early - get out of the loop.
                    //
                    rc = FALSE;
                    DC_QUIT;
                }
            }
            else
            {
                //
                // We will inject this event (and remember when we did it
                // so we don't inject the next one to quickly).  Calculate
                // the backlog because we may have to make up for a
                // processing delay If this event is long (1000 mS) after
                // our projected event time then assume a pause in movement
                // and reset the backlog to avoid progressive erosion.
                // Otherwise calculate the new backlog.
                //
                // Perf - don't reset backlog unless the time has expired.
                // Restting just because we see a click means that we
                // actually increase the latency by assuming that mouse
                // messages queued behind the tick are not backlogged.
                //
                if (tick < (targetTime + 1000))
                {
                    m_imControlledMouseBacklog += ( tick -
                                        m_imControlledLastMouseLocalTime -
                                        targetDelta );
                }
                else
                {
                    m_imControlledMouseBacklog = 0;
                    TRACE_OUT(( "Non move/big gap in move"));
                }
                m_imControlledLastMouseRemoteTime = pNextEvent->time;
                m_imControlledLastMouseLocalTime  = tick;
            }
        }
        else
        {
            //
            // This is not a remote mouse event.  Reset the
            // m_imNextRemoteMouseEvent to zero so we don't hold up the next
            // remote mouse event.
            //
            m_imControlledLastMouseRemoteTime   = pNextEvent->time;
            m_imControlledLastMouseLocalTime    = tick;
            m_imControlledMouseBacklog          = 0;
            TRACE_OUT(( "Local/non-paced/non-mouse - reset"));
        }

        //
        // Only inject the event if IM_FLAG_DONT_REPLAY is not set
        //
        if (!(pNextEvent->flags & IM_FLAG_DONT_REPLAY))
        {
            //
            // If the event is a mouse click then we always inject a mouse
            // move event g_lpimSharedData->immediately before it to ensure that the current
            // position is correct before the click is injected.
            //
            // This is because USER does not handle combined "move and
            // click" events correctly (it appears to treat them as "click
            // and move", generating a mouse move event AFTER the click
            // event, rather than before).  Under normal Windows operation
            // it appears (from observation) that movement events and click
            // events are generated separately (i.e.  a click event will
            // never have the movement flag set).  However, incoming mouse
            // click events may have positions that are different from the
            // last mouse move event so we must inject the extra move event
            // to keep USER happy.
            //
            if ( (pNextEvent->type == IM_MOUSE_EVENT) &&
                 (IM_MEV_BUTTON_DOWN(*pNextEvent) ||
                  IM_MEV_BUTTON_UP(*pNextEvent)) )
            {
                TRACE_OUT(( "Mouse clk: injecting extra"));

                //
                // Take a copy of the event.
                //
                mouseMoveEvent = *pNextEvent;

                //
                // Turn the mouse click event into a mouse move event with
                // the absolute/relative flag unchanged.
                //
                mouseMoveEvent.event.mouse.flags &= MOUSEEVENTF_ABSOLUTE;
                mouseMoveEvent.event.mouse.flags |= MOUSEEVENTF_MOVE;

                //
                // Inject the additional move event.
                //
                IMInjectEvent(&mouseMoveEvent);

                //
                // As the position is now correct, we turn the click into a
                // relative event with an unchanged position.
                //
                pNextEvent->event.mouse.flags &= ~MOUSEEVENTF_ABSOLUTE;
                pNextEvent->event.mouse.pt.x = 0;
                pNextEvent->event.mouse.pt.y = 0;

                //
                // If this is a mouse down click then flag the injection
                // heuristic as active.  We deactivate the heuristic when
                // the mouse is released so that dragging over menus can be
                // done without delay.  (We keep the heuristic active when
                // mouse is depressed because most drawing apps perform
                // freehand drawing in this way.
                //
                if (IM_MEV_BUTTON_DOWN(*pNextEvent))
                {
                    TRACE_OUT(( "Injection pacing active"));
                    m_imfControlledPaceInjection = TRUE;
                }
                else
                {
                    TRACE_OUT(( "Injection pacing inactive"));
                    m_imfControlledPaceInjection = FALSE;
                }
            }

            //
            // Inject the real event.
            //
            TRACE_OUT(( "Injecting the evnt now"));
            IMInjectEvent(pNextEvent);
        }

        IMUpdateAsyncArray(m_aimControlledControllerKeyStates, pNextEvent);

        ASSERT(m_imControlledOSQ.numEvents);
        m_imControlledOSQ.numEvents--;
        m_imControlledOSQ.head = CIRCULAR_INDEX(m_imControlledOSQ.head, 1,
            IM_SIZE_OSQ);

        //
        // We only inject a single keyboard event per pass to prevent
        // excessive spoiling of repeated events.  Having got them here it
        // seems a shame to spoil them.  Spoil down to 5 so we don't get
        // excessive overrun following a key repeat sequence.
        //
        if ((pNextEvent->type == IM_KEYBOARD_EVENT) &&
            (m_imControlledOSQ.numEvents < 5))
        {
            TRACE_OUT(( "Keyboard event so leaving loop"));
            rc = FALSE;
        }
    }
    else
    {
        //
        // We're done.
        //
        rc = FALSE;
    }

DC_EXIT_POINT:
    DebugExitBOOL(ASShare::IMInjectingEvents, rc);
    return(rc);
}




//
// IMMaybeInjectEvents()
//
// DESCRIPTION:
//
// This is called whenever the IM believes there may be an opportunity to
// inject more events into USER via the input event callbacks.  The two
// main reasons for this are:
//
// 1.  We have received a new event in the mouse or keyboard hooks.  This
// will normally imply that an event has been removed from the system
// message queue so there will be at least one free slot on it.
//
// 2.  We have added a new event (or events) to either the local or remote
// USER event queues.  This means there will be at least one event waiting
// to be injected.
//
// This function is also called periodically (via IM_Periodic) to keep
// things moving.
//
// In order for an event to be injected there must be
//
//  - an event waiting (with IM_FLAG_DONT_REPLAY reset)
//  - a space on the USER system message queue
//  - a new time stamp (if we are switching event sources).
//
// This function works as a state machine.  It always starts in a specified
// state and will then take various actions and then possibly enter a new
// state.  It continues to loop through this process until it cannot take
// any actions in one of its states at which point it returns.
//
// There are four states (each of which is further qualified by whether it
// refers to local or remote events).  The states are:
//
// IM_INJECTING_EVENTS - we are injecting events into USER from the
// appropriate queue.
//
// IM_WAITING_FOR_TICK - we are waiting for a timer tick to give us a new
// timestamp before injecting events
//
// IM_DEVICE_TO_NEW_SOURCE - we are injecting fake events to bring the
// state of the keyboard and mouse (as seen by USER) into line with the
// state of the new source of input.
//
void  ASShare::IMMaybeInjectEvents(void)
{
    IMEVENT     eventIn;
    IMEVENT     eventOut;
    IMOSEVENT   OSEvent;
    BOOL        replay;
    UINT        rcConvert;
    UINT        now;
    HWND        hwndDest;
    HWND        hwndParent;
    POINT       ptMousePos;
    LPIMOSEVENT pNextEvent;

    DebugEntry(IMMaybeInjectEvents);

    ASSERT(m_pasLocal->m_caControlledBy);

    //
    // Check whether we should wait before converting events.  We need to
    // do this to prevent us being swamped with mouse move events when
    // we're waiting for the desktop to scroll.
    //
    now = GetTickCount();
    if (IN_TIME_RANGE(m_imControlledLastIncompleteConversion,
           m_imControlledLastIncompleteConversion + IM_MIN_RECONVERSION_INTERVAL_MS, now))
    {
        goto IM_DISCARD;
    }

    //
    // NOW TRANSLATE NETWORK EVENTS TO OS EVENTS
    // We'll discard or inject them when the time is right.
    // But don't do translation if there are still OS events left
    // waiting to be injected from the previous packet.
    //
    if (m_imControlledEventQ.numEvents && !m_imControlledOSQ.numEvents)
    {
        //
        // Get the event from the front of the network event queue.
        //
        eventIn = m_imControlledEventQ.events[0];

        replay = FALSE;
        switch (eventIn.type)
        {
            case IM_TYPE_3BUTTON:
            {
                // Always allow mouse moves
                if (!(eventIn.data.mouse.flags & IM_FLAG_MOUSE_DOWN))
                {
                    replay = TRUE;
                }
                else
                {
                    //
                    // Allow click events to shared windows or
                    // if a different desktop/screensaver is around
                    //
                    ptMousePos.x = eventIn.data.mouse.x;
                    ptMousePos.y = eventIn.data.mouse.y;

                    hwndDest = WindowFromPoint(ptMousePos);

                    if (HET_WindowIsHosted(hwndDest) ||
                        OSI_IsWindowScreenSaver(hwndDest))
                    {
                        replay = TRUE;
                    }
                }

                break;
            }

            case IM_TYPE_VK1:
            case IM_TYPE_VK2:
            case IM_TYPE_ASCII:
            {
                hwndDest = GetForegroundWindow();

                if (HET_WindowIsHosted(hwndDest) ||
                    OSI_IsWindowScreenSaver(hwndDest))
                {
                    replay = TRUE;
                }

                break;
            }

            default:
                ERROR_OUT(("Bogus NETWORK event being translated"));
                break;
        }

        //
        // After this while loop we test rcConvert to see whether the
        // input packet can now be removed (has been fully processed).
        // We only SET rcConvert if IMTranslateIncoming returns TRUE,
        // yet IM_TR specifically returns FALSE to indicate that the
        // input packet does not contain an event and is to be
        // discarded.  To fix this - set rcConvert here.
        //
        rcConvert = IM_IMQUEUEREMOVE;
        while (IMTranslateIncoming(&eventIn, &eventOut))
        {
            rcConvert = IMConvertIMEventToOSEvent(&eventOut, &OSEvent);

            //
            // Inject the event into the OS queue (if required).
            //
            if (rcConvert & IM_OSQUEUEINJECT)
            {
                if (!replay)
                {
                    OSEvent.flags |= IM_FLAG_DONT_REPLAY;
                }

                // Add to playback queue

                // Is the queue filled up?
                if (m_imControlledOSQ.numEvents == IM_SIZE_OSQ)
                {
                    ERROR_OUT(("Failed to add OS event to queue"));
                }
                else
                {
                    // Put this element at the tail.
                    m_imControlledOSQ.events[CIRCULAR_INDEX(m_imControlledOSQ.head,
                        m_imControlledOSQ.numEvents, IM_SIZE_OSQ)] =
                        OSEvent;
                    m_imControlledOSQ.numEvents++;
                }
            }
        }

        //
        // The following test is not ideal as it relies on the fact
        // that any events for which IMConvertIMEventToUSEREvent does
        // not set IM_IMQUEUEREMOVE had a one-one mapping.
        //
        // However, we know that this is always the case with mouse
        // events, which are the only events that will be cause this
        // flag to be unset.
        //
        if (rcConvert & IM_IMQUEUEREMOVE)
        {
            //
            // Remove this from the network queue
            //
            m_imControlledEventQ.numEvents--;
            UT_MoveMemory(&(m_imControlledEventQ.events[0]),
                          &(m_imControlledEventQ.events[1]),
                          sizeof(IMEVENT) * m_imControlledEventQ.numEvents);
        }
        else
        {
            //
            // Remember this so we don't flood the input injection with
            // events when we don't remove the network event from the
            // queue.
            //
            TRACE_OUT(( "do not shuffle"));
            m_imControlledLastIncompleteConversion = GetTickCount();
        }
    }

IM_DISCARD:
    //
    // Get rid of all discarded events.  Update the remote controller's
    // key state array to reflect it.  But since we aren't going to replay
    // these, don't update our local key state table.
    //

    while (m_imControlledOSQ.numEvents > 0)
    {
        pNextEvent = m_imControlledOSQ.events + m_imControlledOSQ.head;
        if (!(pNextEvent->flags & IM_FLAG_DONT_REPLAY))
        {
            // We're done.
            break;
        }

        IMUpdateAsyncArray(m_aimControlledControllerKeyStates, pNextEvent);

        ASSERT(m_imControlledOSQ.numEvents);
        m_imControlledOSQ.numEvents--;
        m_imControlledOSQ.head = CIRCULAR_INDEX(m_imControlledOSQ.head, 1,
            IM_SIZE_OSQ);
    }


    //
    // NOW INJECT OS EVENTS into system
    //
    while (IMInjectingEvents())
    {
        ;
    }

    DebugExitVOID(ASShare::IMMaybeInjectEvents);
}


//
// FUNCTION: IMUpdateAsyncArray
//
// DESCRIPTION:
//
// Called with the address of one of our async key state arrays and a
// IMOSEVENT this function updates the async key state array according to
// the contents of the IMOSEVENT.
//
// PARAMETERS:
//
// paimKeyStates - pointer to async key state array.
//
// pEvent - pointer to IMOSEVENT.
//
// RETURNS: NONE
//
//
void  ASShare::IMUpdateAsyncArray
(
    LPBYTE          paimKeyStates,
    LPIMOSEVENT     pEvent
)
{
    UINT flags;
    UINT vkCode;

    DebugEntry(ASShare::IMUpdateAsyncArray);

    switch (pEvent->type)
    {
        case IM_MOUSE_EVENT:
            //
            // Update the async key state arrays for this event.  Note that
            // we treat each event as independent - this is how Windows
            // treats them and if all the up/down flags are set Windows
            // will generate six mouse message! (and in down,up order).
            //
            flags = pEvent->event.mouse.flags;

            if (flags & MOUSEEVENTF_LEFTDOWN)
            {
                IM_SET_VK_DOWN(paimKeyStates[VK_LBUTTON]);
            }

            if (flags & MOUSEEVENTF_LEFTUP)
            {
                IM_SET_VK_UP(paimKeyStates[VK_LBUTTON]);
            }

            if (flags & MOUSEEVENTF_RIGHTDOWN)
            {
                IM_SET_VK_DOWN(paimKeyStates[VK_RBUTTON]);
            }

            if (flags & MOUSEEVENTF_RIGHTUP)
            {
                IM_SET_VK_UP(paimKeyStates[VK_RBUTTON]);
            }

            if (flags & MOUSEEVENTF_MIDDLEDOWN)
            {
                IM_SET_VK_DOWN(paimKeyStates[VK_MBUTTON]);
            }

            if (flags & MOUSEEVENTF_MIDDLEUP)
            {
                IM_SET_VK_UP(paimKeyStates[VK_MBUTTON]);
            }
            break;

        case IM_KEYBOARD_EVENT:
            //
            // Update the async key state arrays.
            //
            vkCode = IM_KEV_VKCODE(*pEvent);

            if (IM_KEV_KEYUP(*pEvent))
            {
                IM_SET_VK_UP(paimKeyStates[vkCode]);
            }
            else
            {
                //
                // This is a key down event - check if it is a press or a
                // repeat.
                //
                if (IM_KEY_STATE_IS_UP(paimKeyStates[vkCode]))
                {
                    //
                    // This is a key press as the key was previously up -
                    // alter the toggle state.  We keep the toggle state
                    // for all keys although we currently only worry about
                    // it for the `known' toggles.
                    //
                    IM_TOGGLE_VK(paimKeyStates[vkCode]);
                }

                IM_SET_VK_DOWN(paimKeyStates[vkCode]);
            }
            break;

        default:
            //
            // Just ignore unexpected events.
            //
            ERROR_OUT(( "Unexpected event %u", pEvent->type));
            break;
    }

    DebugExitVOID(ASShare::IMUpdateAsyncArray);
}