windows-nt/Source/XPSP1/NT/enduser/netmeeting/as/cpi32/im.cpp

4603 lines
160 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
#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 <20> 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);
}