mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4510 lines
156 KiB
4510 lines
156 KiB
#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;
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
|
|
rc = OSI_InstallControlledHooks(pasControlledBy != NULL);
|
|
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)
|
|
{
|
|
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 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);
|
|
|
|
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);
|
|
|
|
GetKeyboardState(m_aimInControlKeyStates);
|
|
|
|
//
|
|
// Trace out the parameters once we've got this far.
|
|
//
|
|
TRACE_OUT(( "wParam - %04X, lParam - %08lX", wParam, lParam));
|
|
|
|
//
|
|
// Create the event.
|
|
//
|
|
imEvent.data.keyboard.flags = (TSHR_UINT16)
|
|
(HIWORD(lParam) & IM_MASK_KEYBOARD_SYSFLAGS);
|
|
imEvent.timeMS = GetTickCount();
|
|
imEvent.data.keyboard.keyCode = LOBYTE(wParam);
|
|
|
|
retFlags = CA_SEND_EVENT | CA_ALLOW_EVENT;
|
|
|
|
if ((wParam == VK_LWIN) || (wParam == VK_RWIN))
|
|
{
|
|
//
|
|
// The Windows keys give control to the local user interface.
|
|
//
|
|
// The keys are defined to do the following by the spec "New key
|
|
// support for Microsoft Windows Operating Systems and
|
|
// Applications"
|
|
//
|
|
// Left Windows key - set focus to Win95 user interface
|
|
// Right Windows key - as left
|
|
// Both Windows keys - Log-on key for Windows NT
|
|
// Windows key + any other - reserved for system hot keys
|
|
//
|
|
// Thus it does not make any sense to send these keys to the remote
|
|
// system at all.
|
|
//
|
|
retFlags &= ~CA_SEND_EVENT;
|
|
}
|
|
else if ((wParam == VK_PROCESSKEY) && (m_imImmGVK != NULL))
|
|
{
|
|
//
|
|
// An IME has processed this key - we want to find out what the
|
|
// original key was so call <ImmGetVirtualKey>.
|
|
//
|
|
ValidateView(pasHost);
|
|
wParam = m_imImmGVK(pasHost->m_pView->m_viewClient);
|
|
|
|
TRACE_OUT(( "Translated wP from VK_PROCESSKEY to %#lx", wParam));
|
|
}
|
|
|
|
if (retFlags & CA_SEND_EVENT)
|
|
{
|
|
//
|
|
// First check if this is a dead-key up stroke - if it is then
|
|
// don't call ToAscii as the shift state may have changed and we'll
|
|
// get the wrong accent or no accent at all. Assume that if the VK
|
|
// is a potential dead key VK (disregarding shift state) and
|
|
// m_imInControlNumDeadKeysDown is > 0 that this is a dead key - swallow
|
|
// it.
|
|
//
|
|
fSwallowDeadKey = FALSE;
|
|
|
|
if ((m_imInControlNumDeadKeysDown != 0) &&
|
|
(imEvent.data.keyboard.flags & IM_FLAG_KEYBOARD_RELEASE))
|
|
{
|
|
for (i = 0; i < m_imInControlNumDeadKeys; i++)
|
|
{
|
|
if (m_aimInControlDeadKeys[i] == (BYTE)imEvent.data.keyboard.keyCode)
|
|
{
|
|
//
|
|
// Assume this is a dead key up and therefore we don't
|
|
// want to pass it through ToAscii or generate any
|
|
// events based on it.
|
|
//
|
|
m_imInControlNumDeadKeysDown--;
|
|
TRACE_OUT(( "m_imInControlNumDeadKeysDown - %d",
|
|
m_imInControlNumDeadKeysDown));
|
|
fSwallowDeadKey = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fSwallowDeadKey)
|
|
{
|
|
//
|
|
// Find out if we can translate this virtual key into the
|
|
// Windows character set.
|
|
//
|
|
|
|
//
|
|
// Now try to convert this to an Ascii character.
|
|
//
|
|
rc = ToAscii(wParam,
|
|
LOBYTE(HIWORD(lParam)),
|
|
m_aimInControlKeyStates,
|
|
&result[0],
|
|
!(!(HIWORD(lParam) & KF_MENUMODE)));
|
|
|
|
if ((rc == 1) && (LOBYTE(result[0]) <= ' '))
|
|
{
|
|
//
|
|
// Don't use the results of ToAscii if its less than space
|
|
// (32) or space itself as Windows claims that the
|
|
// characters below this in the Windows character set are
|
|
// not supported and ToAscii will convert space plus
|
|
// modifiers to an ascii space and when we replay it
|
|
// VkKeyScan will tell us that ascii space shouldn't have
|
|
// any modifiers so we will undo any modifiers. This will
|
|
// clobber apps which interpret Ctrl-Space, Shift-Space.
|
|
//
|
|
rc = 0;
|
|
}
|
|
|
|
//
|
|
// Some Ascii characters can be generated from more than one
|
|
// key. (Eg '-' is on the main keyboard and the number pad).
|
|
// Convert this ASCII character back to a VK_ value. If it is
|
|
// different from the VK_ we started with, then do not send the
|
|
// key press as ASCII (Ie only send the 'main' way of entering
|
|
// an ASCII value as ASCII).
|
|
//
|
|
// Oprah1943: revert to the VK only if the ASCII code is less
|
|
// than 0x80. This avoids losing the diacritic in a dead-key
|
|
// sequence. VkKeyScan for the key down following the dead-key
|
|
// up returns the dead-key VK rather than that of the keystroke
|
|
// (wParam).
|
|
//
|
|
if (rc == 1)
|
|
{
|
|
mainVK = VkKeyScan(LOBYTE(result[0]));
|
|
|
|
if ( (LOBYTE(mainVK) != LOBYTE(wParam)) &&
|
|
(LOBYTE(result[0]) < 0x80) )
|
|
{
|
|
TRACE_OUT((
|
|
"Not MAIN VK pressed=0x%02hx main=0x%02hx ('%c'/%02hx)",
|
|
(TSHR_UINT16)LOBYTE(wParam),
|
|
(TSHR_UINT16)LOBYTE(mainVK),
|
|
(char)LOBYTE(result[0]),
|
|
(UINT)LOBYTE(result[0])));
|
|
rc = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If ToAscii converts this to a dead key then don't send any
|
|
// packets at all.
|
|
//
|
|
if (rc != -1)
|
|
{
|
|
if (rc == 1)
|
|
{
|
|
TRACE_OUT(( "ToAscii rc=1, result - %02X",
|
|
LOBYTE(result[0])));
|
|
|
|
//
|
|
// Succesfully converted to an Ascii key.
|
|
//
|
|
imEvent.type = IM_TYPE_ASCII;
|
|
imEvent.data.keyboard.keyCode = LOBYTE(result[0]);
|
|
|
|
//
|
|
// Try to send the packet.
|
|
//
|
|
if (!IMConvertAndSendEvent(pasHost, &imEvent))
|
|
{
|
|
WARNING_OUT(( "dropped local key press %u",
|
|
(UINT)imEvent.data.keyboard.keyCode));
|
|
}
|
|
}
|
|
else if (rc == 2)
|
|
{
|
|
TRACE_OUT(( "ToAscii rc=2, result - %04X", result[0]));
|
|
|
|
//
|
|
// Succesfully converted to two Ascii keys. If this is
|
|
// a key down then we will return a key down and key up
|
|
// for the `dead' character first then the key down.
|
|
// If its a key up then just return the key up.
|
|
//
|
|
if (!(imEvent.data.keyboard.flags &
|
|
IM_FLAG_KEYBOARD_RELEASE))
|
|
{
|
|
//
|
|
// This is the key down - so generate a fake
|
|
// keyboard press for the dead key.
|
|
//
|
|
IMGenerateFakeKeyPress(IM_TYPE_ASCII,
|
|
LOBYTE(result[0]),
|
|
imEvent.data.keyboard.flags);
|
|
}
|
|
|
|
//
|
|
// Now return the current keystroke.
|
|
//
|
|
imEvent.type = IM_TYPE_ASCII;
|
|
imEvent.data.keyboard.keyCode = LOBYTE(result[1]);
|
|
|
|
//
|
|
// Try to send the packet.
|
|
//
|
|
if (!IMConvertAndSendEvent(pasHost, &imEvent))
|
|
{
|
|
WARNING_OUT(( "dropped local key press %u",
|
|
(UINT)imEvent.data.keyboard.keyCode));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Check for keys that we want to convert.
|
|
//
|
|
if (LOBYTE(wParam) == VK_KANJI)
|
|
{
|
|
//
|
|
// We only see a down press for VK_KANJI so we
|
|
// fake a complete key press so that the remote
|
|
// does not get confused.
|
|
//
|
|
IMGenerateFakeKeyPress(IM_TYPE_VK1,
|
|
VK_KANJI,
|
|
imEvent.data.keyboard.flags);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No conversion - use the VK itself.
|
|
//
|
|
imEvent.type = IM_TYPE_VK1;
|
|
imEvent.data.keyboard.keyCode = LOBYTE(wParam);
|
|
|
|
//
|
|
// SFR 2537: If this is a right shift VK (which we
|
|
// can detect via the scan code in lParam), set the
|
|
// right_variant keyboard flag. We do not do this
|
|
// for the right-variants of CONTROL and ALT (ie
|
|
// menu) because they are extended keys - already
|
|
// catered for by the extended flag.
|
|
//
|
|
if ( (m_imScanVKRShift != 0) &&
|
|
(m_imScanVKRShift == LOBYTE(HIWORD(lParam))) )
|
|
{
|
|
imEvent.data.keyboard.flags |=
|
|
IM_FLAG_KEYBOARD_RIGHT;
|
|
}
|
|
|
|
//
|
|
// Try to send the packet.
|
|
//
|
|
if (!IMConvertAndSendEvent(pasHost, &imEvent))
|
|
{
|
|
WARNING_OUT(( "dropped local key press %u",
|
|
(UINT)imEvent.data.keyboard.keyCode));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is a dead key - add it to our array of dead keys if
|
|
// we haven't already heard about it.
|
|
//
|
|
IMMaybeAddDeadKey(
|
|
(BYTE)imEvent.data.keyboard.keyCode);
|
|
m_imInControlNumDeadKeysDown++;
|
|
TRACE_OUT(( "m_imInControlNumDeadKeysDown - %d",
|
|
m_imInControlNumDeadKeysDown));
|
|
}
|
|
}
|
|
}
|
|
|
|
DebugExitVOID(ASShare::IM_OutgoingKeyboardInput);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: IMGenerateFakeKeyPress(...)
|
|
//
|
|
// DESCRIPTION:
|
|
//
|
|
// Generates a fake keyboard press.
|
|
//
|
|
// PARAMETERS:
|
|
//
|
|
// type - packet type to generate.
|
|
// key - key to generate press for.
|
|
// flags - flags on keyboard press.
|
|
//
|
|
// RETURNS:
|
|
//
|
|
// Nothing.
|
|
//
|
|
//
|
|
void ASShare::IMGenerateFakeKeyPress
|
|
(
|
|
TSHR_UINT16 type,
|
|
TSHR_UINT16 key,
|
|
TSHR_UINT16 flags
|
|
)
|
|
{
|
|
IMEVENT imEventFake;
|
|
|
|
DebugEntry(ASShare::IMGenerateFakeKeyPress);
|
|
|
|
TRACE_OUT(( "Faking keyboard press:%#hx type:%#hx", key, type));
|
|
|
|
//
|
|
// Generate the key down first of all.
|
|
//
|
|
ZeroMemory(&imEventFake, sizeof(imEventFake));
|
|
|
|
imEventFake.type = type;
|
|
imEventFake.timeMS = GetTickCount();
|
|
imEventFake.data.keyboard.keyCode = key;
|
|
|
|
//
|
|
// Try to send the packet.
|
|
//
|
|
if (!IMConvertAndSendEvent(m_pasLocal->m_caInControlOf, &imEventFake))
|
|
{
|
|
WARNING_OUT(( "Dropped local key press %hu (flags: %#hx)",
|
|
imEventFake.data.keyboard.keyCode,
|
|
imEventFake.data.keyboard.flags));
|
|
}
|
|
|
|
//
|
|
// Set the release and down flags in order to fake the up.
|
|
//
|
|
imEventFake.data.keyboard.flags = IM_FLAG_KEYBOARD_DOWN | IM_FLAG_KEYBOARD_RELEASE;
|
|
|
|
//
|
|
// Try to send the packet.
|
|
//
|
|
if (!IMConvertAndSendEvent(m_pasLocal->m_caInControlOf, &imEventFake))
|
|
{
|
|
WARNING_OUT(( "Dropped local key press %hu (flags: %#hx)",
|
|
imEventFake.data.keyboard.keyCode,
|
|
imEventFake.data.keyboard.flags));
|
|
}
|
|
|
|
DebugExitVOID(ASShare::IMGenerateFakeKeyPress);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// FUNCTION: IMConvertAndSendEvent
|
|
//
|
|
// DESCRIPTION:
|
|
//
|
|
// Called with an IMEVENT this function will try to queue (and even send
|
|
// if possible) the packet. If it fails it will return FALSE - the caller
|
|
// should discard the packet. If it succeeds it will return TRUE.
|
|
//
|
|
// If pasFor is us, it means to send to everybody (and coords are relative
|
|
// to sender's screen).
|
|
//
|
|
// If pasFor is a remote, it means that the IM packet is meant for just
|
|
// that person and the coords are relative to pasFor's screen.
|
|
//
|
|
//
|
|
// PARAMETERS:
|
|
//
|
|
// pIMEvent - the IMEVENT to convert and send
|
|
//
|
|
// RETURNS: TRUE or FALSE - success or failure
|
|
//
|
|
//
|
|
BOOL ASShare::IMConvertAndSendEvent
|
|
(
|
|
ASPerson * pasFor,
|
|
PIMEVENT pIMEvent
|
|
)
|
|
{
|
|
BOOL rc = FALSE;
|
|
|
|
DebugEntry(ASShare::IMConvertAndSendEvent);
|
|
|
|
//
|
|
// If there is already a pending packet then see if we can flush some
|
|
// packets onto the network.
|
|
//
|
|
if (m_imfInControlEventIsPending)
|
|
{
|
|
IMFlushOutgoingEvents();
|
|
}
|
|
|
|
//
|
|
// If there is still a pending packet then see if we can spoil some
|
|
// events.
|
|
//
|
|
if (m_imfInControlEventIsPending)
|
|
{
|
|
TRACE_OUT(( "trying to drop mouse move events"));
|
|
IMSpoilEvents();
|
|
IMFlushOutgoingEvents();
|
|
}
|
|
|
|
//
|
|
// Now see if we are able to accept a new packet
|
|
//
|
|
if (m_imfInControlEventIsPending)
|
|
{
|
|
//
|
|
// If there is still a previous IMEVENT which we are in the
|
|
// process of converting then we are not ready to receive any more
|
|
// packets.
|
|
//
|
|
TRACE_OUT(( "can't queue packet"));
|
|
DC_QUIT;
|
|
}
|
|
|
|
//
|
|
// Now set up the new packet and try to flush the packets again.
|
|
//
|
|
m_imfInControlEventIsPending = TRUE;
|
|
m_imInControlPendingEvent = *pIMEvent;
|
|
IMFlushOutgoingEvents();
|
|
|
|
rc = TRUE;
|
|
|
|
DC_EXIT_POINT:
|
|
DebugExitBOOL(ASShare::IMConvertAndSendEvent, rc);
|
|
return(rc);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: IMMaybeAddDeadKey
|
|
//
|
|
// DESCRIPTION:
|
|
//
|
|
// Called whenever ToAscii tells us about a dead key. If we haven't
|
|
// got it in our table already then we will add it. We create the table
|
|
// incrementally because we have found that some keyboard drivers don't
|
|
// cope very well with being queried with all possible VKs to find the
|
|
// dead keys. Note that this will not cope with someone switching their
|
|
// keyboard driver whilst DC-Share is running.
|
|
//
|
|
// PARAMETERS:
|
|
//
|
|
// vk - the VK in question
|
|
//
|
|
// RETURNS: NONE
|
|
//
|
|
//
|
|
void ASShare::IMMaybeAddDeadKey(BYTE vk)
|
|
{
|
|
UINT i;
|
|
|
|
DebugEntry(IMMaybeAddDeadKey);
|
|
|
|
//
|
|
// First see if we already know about this key.
|
|
//
|
|
for (i = 0; i < m_imInControlNumDeadKeys; i++)
|
|
{
|
|
if (m_aimInControlDeadKeys[i] == vk)
|
|
{
|
|
DC_QUIT;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add this key if there's space in the array.
|
|
//
|
|
if (m_imInControlNumDeadKeys < IM_MAX_DEAD_KEYS)
|
|
{
|
|
TRACE_OUT(( "Add %02X", (TSHR_UINT16)vk));
|
|
m_aimInControlDeadKeys[m_imInControlNumDeadKeys++] = vk;
|
|
}
|
|
|
|
DC_EXIT_POINT:
|
|
DebugExitVOID(ASShare::IMMaybeAddDeadKey);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// IMConvertIMEventToOSEvent()
|
|
// Converts incoming event to something we can playback.
|
|
//
|
|
// PARAMETERS:
|
|
//
|
|
// pIMEvent - the IMEVENT to be converted
|
|
//
|
|
// pOSEvent - the IMOSEVENT to be created
|
|
//
|
|
//
|
|
UINT ASShare::IMConvertIMEventToOSEvent
|
|
(
|
|
PIMEVENT pIMEvent,
|
|
LPIMOSEVENT pOSEvent
|
|
)
|
|
{
|
|
int mouseX;
|
|
int mouseY;
|
|
int realMouseX;
|
|
int realMouseY;
|
|
RECT cursorClip;
|
|
UINT rc = (IM_IMQUEUEREMOVE | IM_OSQUEUEINJECT);
|
|
|
|
DebugEntry(ASShare::IMConvertIMEventToOSEvent);
|
|
|
|
switch (pIMEvent->type)
|
|
{
|
|
case IM_TYPE_3BUTTON:
|
|
//
|
|
// Fill in common fields. Note that we claim to be a 3 button
|
|
// mouse so that we can replay events from remote three button
|
|
// mice and we always give absolute coordinates.
|
|
//
|
|
pOSEvent->type = IM_MOUSE_EVENT;
|
|
pOSEvent->flags = 0;
|
|
pOSEvent->time = pIMEvent->timeMS;
|
|
pOSEvent->event.mouse.cButtons = 3;
|
|
pOSEvent->event.mouse.mouseData = 0;
|
|
pOSEvent->event.mouse.dwExtraInfo = 0;
|
|
|
|
//
|
|
// First check for a wheel rotate, since this is easy to
|
|
// process. (It cannot include any mouse movement as well).
|
|
//
|
|
if (pIMEvent->data.mouse.flags & IM_FLAG_MOUSE_WHEEL)
|
|
{
|
|
if (pIMEvent->data.mouse.flags &
|
|
(IM_FLAG_MOUSE_BUTTON1 |
|
|
IM_FLAG_MOUSE_BUTTON2 |
|
|
IM_FLAG_MOUSE_BUTTON3))
|
|
{
|
|
//
|
|
// Using any of the button flags along with the wheel
|
|
// flag is currently undefined - for forward
|
|
// compatability we therefore ignore such an event by
|
|
// converting it into a NULL injected event.
|
|
//
|
|
// (We do not sg_lpimSharedData->imply discard it, since the logic to
|
|
// discard events does not seem to work).
|
|
//
|
|
pOSEvent->event.mouse.flags = 0;
|
|
pOSEvent->event.mouse.pt.x = 0;
|
|
pOSEvent->event.mouse.pt.y = 0;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is a wheel movement.
|
|
//
|
|
// Note that the protocol has sent whether the mouse's
|
|
// middle button is depressed or released, but we don't
|
|
// need that info for NT, so just ignore it.
|
|
//
|
|
pOSEvent->event.mouse.flags = MOUSEEVENTF_WHEEL;
|
|
|
|
pOSEvent->event.mouse.mouseData =
|
|
(pIMEvent->data.mouse.flags & IM_FLAG_MOUSE_ROTATION_MASK);
|
|
pOSEvent->event.mouse.pt.x = 0;
|
|
pOSEvent->event.mouse.pt.y = 0;
|
|
|
|
//
|
|
// Sign extend the rotation amount up to the full 32
|
|
// bits
|
|
//
|
|
if (pOSEvent->event.mouse.mouseData & IM_FLAG_MOUSE_DIRECTION)
|
|
{
|
|
pOSEvent->event.mouse.mouseData |=
|
|
~IM_FLAG_MOUSE_ROTATION_MASK;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// We are left now with non wheel-rotate events.
|
|
//
|
|
pOSEvent->event.mouse.flags = MOUSEEVENTF_ABSOLUTE;
|
|
|
|
//
|
|
// We must convert from virtual desktop coordinates to local
|
|
// screen coordinates here and we must also prevent the
|
|
// position wrapping if we try to replay a mouse move to an
|
|
// off-screen position.
|
|
//
|
|
|
|
realMouseX = pIMEvent->data.mouse.x;
|
|
realMouseY = pIMEvent->data.mouse.y;
|
|
|
|
//
|
|
// Now lg_lpimSharedData->imit to the size of the real screen.
|
|
//
|
|
mouseX = min((m_pasLocal->cpcCaps.screen.capsScreenWidth-1), max(0, realMouseX));
|
|
mouseY = min((m_pasLocal->cpcCaps.screen.capsScreenHeight-1), max(0, realMouseY));
|
|
|
|
//
|
|
// Work out if this event will be clipped by the clip cursor
|
|
//
|
|
GetClipCursor(&cursorClip);
|
|
|
|
if ((mouseX < cursorClip.left) ||
|
|
(mouseX >= cursorClip.right) ||
|
|
(mouseY < cursorClip.top) ||
|
|
(mouseY >= cursorClip.bottom))
|
|
{
|
|
//
|
|
// This event will actually be clipped because of the
|
|
// current clip cursor. Remember this.
|
|
//
|
|
m_imfControlledMouseClipped = TRUE;
|
|
}
|
|
else
|
|
{
|
|
m_imfControlledMouseClipped = FALSE;
|
|
|
|
//
|
|
// If we clamp the mouse position before replaying then we
|
|
// must remember the real packet and make the current
|
|
// packet into a move so that we don't click down/up at the
|
|
// wrong place.
|
|
//
|
|
if ((mouseX != realMouseX) || (mouseY != realMouseY))
|
|
{
|
|
//
|
|
// The mouse position we've recieved is off the
|
|
// local physical screen. Now that we no longer have
|
|
// desktop scrolling, we simply clamp it rather than
|
|
// inject it at the edge and wait for the scroll.
|
|
//
|
|
// We turn mouse down-clicks into moves and let
|
|
// up-clicks pass through (in case the mouse button
|
|
// has been pressed within the real screen).
|
|
//
|
|
// Note that the mouse position has already been
|
|
// adjusted so that it is within the real screen.
|
|
//
|
|
if (pIMEvent->data.mouse.flags & IM_FLAG_MOUSE_DOWN)
|
|
{
|
|
pIMEvent->data.mouse.flags = IM_FLAG_MOUSE_MOVE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Store the mouse position.
|
|
//
|
|
pOSEvent->event.mouse.pt.x = mouseX;
|
|
pOSEvent->event.mouse.pt.y = mouseY;
|
|
|
|
//
|
|
// Add more flags as appropriate.
|
|
//
|
|
if (pIMEvent->data.mouse.flags & IM_FLAG_MOUSE_MOVE)
|
|
{
|
|
pOSEvent->event.mouse.flags |= MOUSEEVENTF_MOVE;
|
|
}
|
|
else
|
|
{
|
|
switch (pIMEvent->data.mouse.flags &
|
|
( IM_FLAG_MOUSE_BUTTON1 |
|
|
IM_FLAG_MOUSE_BUTTON2 |
|
|
IM_FLAG_MOUSE_BUTTON3 |
|
|
IM_FLAG_MOUSE_DOWN ))
|
|
{
|
|
case IM_FLAG_MOUSE_BUTTON1 | IM_FLAG_MOUSE_DOWN:
|
|
pOSEvent->event.mouse.flags |= MOUSEEVENTF_LEFTDOWN;
|
|
break;
|
|
|
|
case IM_FLAG_MOUSE_BUTTON1:
|
|
pOSEvent->event.mouse.flags |= MOUSEEVENTF_LEFTUP;
|
|
break;
|
|
|
|
case IM_FLAG_MOUSE_BUTTON2 | IM_FLAG_MOUSE_DOWN:
|
|
pOSEvent->event.mouse.flags |= MOUSEEVENTF_RIGHTDOWN;
|
|
break;
|
|
|
|
case IM_FLAG_MOUSE_BUTTON2:
|
|
pOSEvent->event.mouse.flags |= MOUSEEVENTF_RIGHTUP;
|
|
break;
|
|
|
|
case IM_FLAG_MOUSE_BUTTON3 | IM_FLAG_MOUSE_DOWN:
|
|
pOSEvent->event.mouse.flags |= MOUSEEVENTF_MIDDLEDOWN;
|
|
break;
|
|
|
|
case IM_FLAG_MOUSE_BUTTON3:
|
|
pOSEvent->event.mouse.flags |= MOUSEEVENTF_MIDDLEUP;
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// If we don't recognise this then don't play it
|
|
// back
|
|
//
|
|
ERROR_OUT(("Unrecognised mouse flags (%04X)",
|
|
pIMEvent->data.mouse.flags));
|
|
rc = IM_IMQUEUEREMOVE;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case IM_TYPE_VK1:
|
|
//
|
|
// Common fields.
|
|
//
|
|
pOSEvent->flags = 0;
|
|
if (pIMEvent->data.keyboard.flags & IM_FLAG_KEYBOARD_UPDATESTATE)
|
|
pOSEvent->flags |= IM_FLAG_UPDATESTATE;
|
|
|
|
pOSEvent->time = pIMEvent->timeMS;
|
|
|
|
//
|
|
// Now handle normal keyboard events.
|
|
//
|
|
pOSEvent->type = IM_KEYBOARD_EVENT;
|
|
|
|
//
|
|
// AX is the scancode in AL and 00h (press) or 80h (release) in
|
|
// AH. Map the DC protocol VK to the equivalent OS VK.
|
|
// AL = the scancode for the VK).
|
|
//
|
|
pOSEvent->event.keyboard.vkCode = LOBYTE(pIMEvent->data.keyboard.keyCode);
|
|
|
|
pOSEvent->event.keyboard.flags = 0;
|
|
if (IS_IM_KEY_RELEASE(pIMEvent->data.keyboard.flags))
|
|
{
|
|
pOSEvent->event.keyboard.flags |= KEYEVENTF_KEYUP;
|
|
}
|
|
|
|
//
|
|
// SFR 2537: If the flags indicate that the received VK is the
|
|
// right-variant, do not map the VK to a scan code, but rather
|
|
// directly use the already acquired right-variant scan code
|
|
// for the VK. (For the moment, the only case we support is
|
|
// for Windows, where this is an issue for SHIFT).
|
|
//
|
|
if ( IS_IM_KEY_RIGHT(pIMEvent->data.keyboard.flags) &&
|
|
(pIMEvent->data.keyboard.keyCode == VK_SHIFT) )
|
|
{
|
|
pOSEvent->event.keyboard.scanCode = m_imScanVKRShift;
|
|
}
|
|
else
|
|
{
|
|
pOSEvent->event.keyboard.scanCode =
|
|
(WORD)MapVirtualKey(pIMEvent->data.keyboard.keyCode, 0);
|
|
}
|
|
|
|
if (pIMEvent->data.keyboard.flags & IM_FLAG_KEYBOARD_EXTENDED)
|
|
{
|
|
pOSEvent->event.keyboard.flags |= KEYEVENTF_EXTENDEDKEY;
|
|
}
|
|
|
|
pOSEvent->event.keyboard.dwExtraInfo = 0;
|
|
break;
|
|
|
|
default:
|
|
ERROR_OUT(("Unrecognized imEvent (%d)", pIMEvent->type));
|
|
//
|
|
// Discard the event (remove from the IM queue and don't inject
|
|
// into the OS).
|
|
//
|
|
rc = IM_IMQUEUEREMOVE;
|
|
break;
|
|
}
|
|
|
|
|
|
DebugExitDWORD(ASShare::IMConvertIMEventToOSEvent, rc);
|
|
return(rc);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// IMTranslateOutgoing()
|
|
//
|
|
// DESCRIPTION:
|
|
//
|
|
// Converts locally generated sequences of IMEVENTs into transmitted
|
|
// sequences of IMEVENTs. Does a 1 to (0-n) translation. Handles
|
|
// buffering modifier keys and translating DC-Share hot-key sequences.
|
|
//
|
|
// When the CA has decided an IMEVENT should be sent this function is
|
|
// called by the IM with a pointer to that packet in pIMEventIn.
|
|
// IMTranslateOutgoing can then return TRUE and fill in the packet at
|
|
// pIMEventOut or return FALSE. If IMTranslateOutgoing returns TRUE the IM
|
|
// will call it again with the same packet. The IMEVENTs returned are
|
|
// sent across the network by the IM.
|
|
//
|
|
// PARAMETERS:
|
|
//
|
|
// pIMEventIn - pointer to IMEVENT
|
|
//
|
|
// pIMEventOut - pointer to IMEVENT
|
|
//
|
|
// RETURNS:
|
|
//
|
|
// TRUE - packet returned (call function again)
|
|
//
|
|
// FALSE - no packet returned (don't call function again)
|
|
//
|
|
//
|
|
|
|
BOOL ASShare::IMTranslateOutgoing
|
|
(
|
|
LPIMEVENT pIMEventIn,
|
|
LPIMEVENT pIMEventOut
|
|
)
|
|
{
|
|
UINT hotKeyArrayIndex;
|
|
UINT hotKeyValue;
|
|
BOOL fHotKeyFound;
|
|
BOOL rc = FALSE;
|
|
|
|
DebugEntry(ASShare::IMTranslateOutgoing);
|
|
|
|
//
|
|
// Here we need to tell the remote system about certain keys which are
|
|
// consumed locally so that it can make good decisions about whether
|
|
// and how to replay them. We want to keep the remote system in step
|
|
// with the current modifier and toggle key state on our system (as it
|
|
// is possible that either a modifier/toggle key event occurred whilst
|
|
// a local app was active and was therefore never sent) We also want to
|
|
// recognise certain `hot key' sequences and send further packets as a
|
|
// result of these.
|
|
//
|
|
// The keys we comsume locally are:
|
|
//
|
|
// Esc down or up when Ctrl is down - operates task list locally
|
|
//
|
|
// Tab down or up when Alt is down - operates task switcher locally
|
|
//
|
|
// Esc down or up when Alt is pressed - switches to next window locally
|
|
//
|
|
// Esc up when corresponding Esc down occurred when Alt was down - as
|
|
// above
|
|
//
|
|
// The sequences we want to produce hot keys from are:
|
|
//
|
|
// Alt + 9?? on the numeric keypad
|
|
//
|
|
// To detect hotkeys we keep a record of the last four keypresses and
|
|
// when we detect an Alt up we check if they form a valid sequence.
|
|
//
|
|
// The keystrokes which form part of the hotkey are sent to the remote
|
|
// system so if they have some meaning on a remote system then that
|
|
// system must decide whether to buffer them to determine if they are
|
|
// part of a hotkey or play them back anyway - on Windows we play them
|
|
// back anyway as they are a legitimate key sequence when controlling a
|
|
// Windows app - the number typed on the numeric keypad has a % 256
|
|
// applied to it.
|
|
//
|
|
// This means that for each incoming event we may want to generate 0 or
|
|
// more outgoing events. To do this we have a structure which looks
|
|
// roughly like this:
|
|
//
|
|
// IF m_m_imfInControlNewEvent
|
|
// calculate an array of events which we want to return
|
|
// set m_m_imfInControlNewEvent to FALSE
|
|
// set number of events returned to 0
|
|
// ENDIF
|
|
//
|
|
// IF !m_m_imfInControlNewEvent
|
|
// IF this is the last event to return
|
|
// set m_m_imfInControlNewEvent to TRUE
|
|
// ENDIF
|
|
// return current event
|
|
// ENDIF
|
|
//
|
|
//
|
|
|
|
if (m_imfInControlNewEvent)
|
|
{
|
|
//
|
|
// This is the first time we have seen this event so accumulate
|
|
// our list of events to generate.
|
|
//
|
|
|
|
//
|
|
// Do tracing
|
|
//
|
|
if (pIMEventIn->type == IM_TYPE_ASCII)
|
|
{
|
|
TRACE_OUT(( "IN ASCII code 0x%04X, flags 0x%04X",
|
|
pIMEventIn->data.keyboard.keyCode, pIMEventIn->data.keyboard.flags));
|
|
}
|
|
else if (pIMEventIn->type == IM_TYPE_VK1)
|
|
{
|
|
TRACE_OUT(( "IN VKEY code %04X, flags %04X",
|
|
pIMEventIn->data.keyboard.keyCode, pIMEventIn->data.keyboard.flags));
|
|
}
|
|
else if ((pIMEventIn->type == IM_TYPE_3BUTTON) &&
|
|
!(pIMEventIn->data.mouse.flags & IM_FLAG_MOUSE_MOVE))
|
|
{
|
|
TRACE_OUT(( "IN 3BTTN flags %04X (%d,%d)",
|
|
pIMEventIn->data.mouse.flags, pIMEventIn->data.mouse.x,
|
|
pIMEventIn->data.mouse.y));
|
|
}
|
|
else if (pIMEventIn->type == IM_TYPE_3BUTTON)
|
|
{
|
|
TRACE_OUT(( "IN 3BTTN flags %04X (%d,%d)",
|
|
pIMEventIn->data.mouse.flags, pIMEventIn->data.mouse.x,
|
|
pIMEventIn->data.mouse.y));
|
|
}
|
|
else if (pIMEventIn->type == IM_TYPE_VK_ASCII)
|
|
{
|
|
TRACE_OUT(("IN VK_ASC code %04X, flags %04X",
|
|
pIMEventIn->data.keyboard.keyCode, pIMEventIn->data.keyboard.flags));
|
|
}
|
|
else
|
|
{
|
|
ERROR_OUT(("Invalid IM type %d", pIMEventIn->type));
|
|
}
|
|
|
|
//
|
|
// Start from the beginning of our returned events array.
|
|
//
|
|
m_imInControlNumEventsPending = 0;
|
|
m_imInControlNumEventsReturned = 0;
|
|
|
|
//
|
|
// First get our flags for the modifiers and locks we think we have
|
|
// sent to the remote side up to date allowing for this event.
|
|
//
|
|
if (pIMEventIn->type == IM_TYPE_VK1)
|
|
{
|
|
switch (pIMEventIn->data.keyboard.keyCode)
|
|
{
|
|
case VK_CONTROL:
|
|
if (IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
|
|
{
|
|
m_imfInControlCtrlDown = FALSE;
|
|
}
|
|
else
|
|
{
|
|
m_imfInControlCtrlDown = TRUE;
|
|
}
|
|
break;
|
|
|
|
case VK_SHIFT:
|
|
if (IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
|
|
{
|
|
m_imfInControlShiftDown = FALSE;
|
|
}
|
|
else
|
|
{
|
|
m_imfInControlShiftDown = TRUE;
|
|
}
|
|
break;
|
|
|
|
case VK_MENU:
|
|
if (IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
|
|
{
|
|
m_imfInControlMenuDown = FALSE;
|
|
}
|
|
else
|
|
{
|
|
m_imfInControlMenuDown = TRUE;
|
|
}
|
|
break;
|
|
|
|
case VK_CAPITAL:
|
|
if (IS_IM_KEY_PRESS(pIMEventIn->data.keyboard.flags))
|
|
{
|
|
m_imfInControlCapsLock = !m_imfInControlCapsLock;
|
|
}
|
|
break;
|
|
|
|
case VK_NUMLOCK:
|
|
if (IS_IM_KEY_PRESS(pIMEventIn->data.keyboard.flags))
|
|
{
|
|
m_imfInControlNumLock = !m_imfInControlNumLock;
|
|
}
|
|
break;
|
|
|
|
case VK_SCROLL:
|
|
if (IS_IM_KEY_PRESS(pIMEventIn->data.keyboard.flags))
|
|
{
|
|
m_imfInControlScrollLock = !m_imfInControlScrollLock;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now check the current state versus our remembered state and
|
|
// prepare to insert events if necessary. Do this for any events
|
|
// (ie including mouse events) as mouse clicks can have different
|
|
// effects depending on the current modifer state.
|
|
//
|
|
|
|
//
|
|
// First the modifiers. IMGetHighLevelKeyState will return us the
|
|
// keyboard state including the event we are currently processing
|
|
// because it is adjusted before the keyboard hook. The top most
|
|
// bit is set of the key is down otherwise it is reset.
|
|
//
|
|
if (IMGetHighLevelKeyState(VK_CONTROL) & 0x80)
|
|
{
|
|
if (!m_imfInControlCtrlDown)
|
|
{
|
|
//
|
|
// The key is down locally but we last told the remote
|
|
// machine it was up.
|
|
//
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_CTRL_DOWN;
|
|
m_imfInControlCtrlDown = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_imfInControlCtrlDown)
|
|
{
|
|
//
|
|
// The key is up locally but we last told the remote
|
|
// machine it was down.
|
|
//
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_CTRL_UP;
|
|
m_imfInControlCtrlDown = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Do the same for shift and menu (alt).
|
|
//
|
|
if (IMGetHighLevelKeyState(VK_SHIFT) & 0x80)
|
|
{
|
|
if (!m_imfInControlShiftDown)
|
|
{
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_SHIFT_DOWN;
|
|
m_imfInControlShiftDown = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_imfInControlShiftDown)
|
|
{
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_SHIFT_UP;
|
|
m_imfInControlShiftDown = FALSE;
|
|
}
|
|
}
|
|
|
|
if (IMGetHighLevelKeyState(VK_MENU) & 0x80)
|
|
{
|
|
if (!m_imfInControlMenuDown)
|
|
{
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_MENU_DOWN;
|
|
m_imfInControlMenuDown = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_imfInControlMenuDown)
|
|
{
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_MENU_UP;
|
|
m_imfInControlMenuDown = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now handle the toggles. The least significant bit is set when
|
|
// the toggle is on, reset otherwise.
|
|
//
|
|
if ((IMGetHighLevelKeyState(VK_CAPITAL) & IM_KEY_STATE_FLAG_TOGGLE) ?
|
|
!m_imfInControlCapsLock : m_imfInControlCapsLock)
|
|
{
|
|
//
|
|
// The current caps lock state and what we've sent to the
|
|
// remote system are out of synch - fix it.
|
|
//
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_CAPS_LOCK_DOWN;
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_CAPS_LOCK_UP;
|
|
m_imfInControlCapsLock = !m_imfInControlCapsLock;
|
|
}
|
|
|
|
//
|
|
// Do the same for Num lock and Scroll lock.
|
|
//
|
|
if ((IMGetHighLevelKeyState(VK_NUMLOCK) & 0x01) ?
|
|
!m_imfInControlNumLock : m_imfInControlNumLock)
|
|
{
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_NUM_LOCK_DOWN;
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_NUM_LOCK_UP;
|
|
m_imfInControlNumLock = !m_imfInControlNumLock;
|
|
}
|
|
|
|
if ((IMGetHighLevelKeyState(VK_SCROLL) & 0x01) ?
|
|
!m_imfInControlScrollLock : m_imfInControlScrollLock)
|
|
{
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_SCROLL_LOCK_DOWN;
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_SCROLL_LOCK_UP;
|
|
m_imfInControlScrollLock = !m_imfInControlScrollLock;
|
|
}
|
|
|
|
//
|
|
// Now we will do the appropriate processing for each type of
|
|
// packet we expect. We only expect to receive
|
|
//
|
|
// IM_TYPE_VK1
|
|
// IM_TYPE_ASCII
|
|
// IM_TYPE_3BUTTON
|
|
//
|
|
//
|
|
|
|
if (pIMEventIn->type == IM_TYPE_VK1)
|
|
{
|
|
//
|
|
// Now process a VK packet generated from the real keyboard.
|
|
// Check for Escape, Tab and Menu and decide whether to forward
|
|
// them or consume them first.
|
|
//
|
|
|
|
if (pIMEventIn->data.keyboard.keyCode == VK_ESCAPE)
|
|
{
|
|
//
|
|
// This is the escape key - check the current shift status
|
|
// to see whether we should flag this as consumed locally.
|
|
//
|
|
if (IMGetHighLevelKeyState(VK_MENU) & 0x80)
|
|
{
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_CONSUMED;
|
|
|
|
//
|
|
// Also remember to consume the next Menu Up keystroke.
|
|
//
|
|
m_imfInControlConsumeMenuUp = TRUE;
|
|
|
|
if (!IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
|
|
{
|
|
//
|
|
// If this is an escape press then remember that we
|
|
// should consume the corresponding up stroke
|
|
// regardless of shift state.
|
|
//
|
|
m_imfInControlConsumeEscapeUp = TRUE;
|
|
}
|
|
}
|
|
else if (m_imfInControlConsumeEscapeUp &&
|
|
IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
|
|
{
|
|
//
|
|
// This is the up stroke corresponding to a down
|
|
// stroke we consumed so consume it too.
|
|
//
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_CONSUMED;
|
|
m_imfInControlConsumeEscapeUp = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This Escape is not one of our special cases so
|
|
// forward it unchanged.
|
|
//
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_FORWARD;
|
|
}
|
|
}
|
|
else if (pIMEventIn->data.keyboard.keyCode == VK_TAB)
|
|
{
|
|
//
|
|
// This is the Tab key - check for current shift status to
|
|
// see whether we should flag this as consumed locally.
|
|
//
|
|
if (IMGetHighLevelKeyState(VK_MENU) & 0x80)
|
|
{
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_CONSUMED;
|
|
|
|
//
|
|
// Also remember to consume the next Menu Up keystroke.
|
|
//
|
|
m_imfInControlConsumeMenuUp = TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This Tab is not our special case so forward it
|
|
// unchanged.
|
|
//
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_FORWARD;
|
|
}
|
|
}
|
|
else if ((pIMEventIn->data.keyboard.keyCode == VK_MENU) &&
|
|
IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
|
|
{
|
|
//
|
|
// This is a menu up - check for one we should consume or
|
|
// for hotkeys.
|
|
//
|
|
if (m_imfInControlConsumeMenuUp)
|
|
{
|
|
//
|
|
// This is a menu up we want to consume - do so.
|
|
//
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_CONSUMED;
|
|
m_imfInControlConsumeMenuUp = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is a VK_MENU release
|
|
// hot key sequence in our array of last four key
|
|
// presses. Start looking at the next entry (the array
|
|
// is circular). A valid sequence is
|
|
//
|
|
// VK_MENU
|
|
// numeric pad 9
|
|
// numeric pad number
|
|
// numeric pad number
|
|
//
|
|
//
|
|
fHotKeyFound = FALSE;
|
|
hotKeyArrayIndex = m_imInControlNextHotKeyEntry;
|
|
if (m_aimInControlHotKeyArray[hotKeyArrayIndex] == VK_MENU)
|
|
{
|
|
hotKeyArrayIndex = (hotKeyArrayIndex+1)%4;
|
|
if (m_aimInControlHotKeyArray[hotKeyArrayIndex] == 9)
|
|
{
|
|
hotKeyArrayIndex = (hotKeyArrayIndex+1)%4;
|
|
if (m_aimInControlHotKeyArray[hotKeyArrayIndex] <= 9)
|
|
{
|
|
hotKeyValue =
|
|
10*m_aimInControlHotKeyArray[hotKeyArrayIndex];
|
|
hotKeyArrayIndex = (hotKeyArrayIndex+1)%4;
|
|
if (m_aimInControlHotKeyArray[hotKeyArrayIndex] <= 9)
|
|
{
|
|
//
|
|
// This is a valid hot key - add a
|
|
// consumed VK_MENU and then a hot key
|
|
// packet.
|
|
//
|
|
hotKeyValue +=
|
|
m_aimInControlHotKeyArray[hotKeyArrayIndex];
|
|
m_aimInControlEventsToReturn[
|
|
m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_CONSUMED;
|
|
m_aimInControlEventsToReturn[
|
|
m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_HOTKEY_BASE + hotKeyValue;
|
|
TRACE_OUT(("Hotkey found %d", hotKeyValue));
|
|
fHotKeyFound = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fHotKeyFound)
|
|
{
|
|
//
|
|
// This was not a hotkey so send the menu up as
|
|
// normal.
|
|
//
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_FORWARD;
|
|
}
|
|
}
|
|
}
|
|
else if (IS_IM_KEY_PRESS(pIMEventIn->data.keyboard.flags))
|
|
{
|
|
//
|
|
// Keep a record of the last four key presses (not
|
|
// including auto
|
|
// VK_MENU up event to determine if we have found a hotkey
|
|
// sequence.
|
|
//
|
|
|
|
//
|
|
// This is a key press and it is not a repeat. Throw out
|
|
// extended keys here so that we're not confused by the
|
|
// grey cursor keys.
|
|
//
|
|
if (pIMEventIn->data.keyboard.flags &
|
|
IM_FLAG_KEYBOARD_EXTENDED)
|
|
{
|
|
//
|
|
// An extended key breaks the sequence.
|
|
//
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 0xFF;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Add an entry to our array for this key. We add
|
|
// VK_MENUs and add and translate numeric keypad keys
|
|
// anything else breaks the sequencs.
|
|
//
|
|
switch (pIMEventIn->data.keyboard.keyCode)
|
|
{
|
|
case VK_MENU:
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = VK_MENU;
|
|
break;
|
|
|
|
case VK_NUMPAD0:
|
|
case VK_INSERT:
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 0;
|
|
break;
|
|
|
|
case VK_NUMPAD1:
|
|
case VK_END:
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 1;
|
|
break;
|
|
|
|
case VK_NUMPAD2:
|
|
case VK_DOWN:
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 2;
|
|
break;
|
|
|
|
case VK_NUMPAD3:
|
|
case VK_NEXT:
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 3;
|
|
break;
|
|
|
|
case VK_NUMPAD4:
|
|
case VK_LEFT:
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 4;
|
|
break;
|
|
|
|
case VK_NUMPAD5:
|
|
case VK_CLEAR:
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 5;
|
|
break;
|
|
|
|
case VK_NUMPAD6:
|
|
case VK_RIGHT:
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 6;
|
|
break;
|
|
|
|
case VK_NUMPAD7:
|
|
case VK_HOME:
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 7;
|
|
break;
|
|
|
|
case VK_NUMPAD8:
|
|
case VK_UP:
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 8;
|
|
break;
|
|
|
|
case VK_NUMPAD9:
|
|
case VK_PRIOR:
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 9;
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// Any unrecognised key breaks a sequence.
|
|
//
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 0xFF;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Wrap the hot key array at 4 entries.
|
|
//
|
|
m_imInControlNextHotKeyEntry = (m_imInControlNextHotKeyEntry+1)%4;
|
|
|
|
//
|
|
// Forward the event.
|
|
//
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_FORWARD;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Just forward the event as its not any of our special
|
|
// cases.
|
|
//
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_FORWARD;
|
|
}
|
|
}
|
|
else if (pIMEventIn->type == IM_TYPE_VK_ASCII)
|
|
{
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_FORWARD;
|
|
}
|
|
else if (pIMEventIn->type == IM_TYPE_ASCII)
|
|
{
|
|
//
|
|
// Any IM_TYPE_ASCII breaks the hot key sequence.
|
|
//
|
|
m_aimInControlHotKeyArray[m_imInControlNextHotKeyEntry] = 0xFF;
|
|
m_imInControlNextHotKeyEntry = (m_imInControlNextHotKeyEntry+1)%4;
|
|
|
|
//
|
|
// Then just forward the thing without doing anything clever.
|
|
//
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_FORWARD;
|
|
}
|
|
else if (pIMEventIn->type == IM_TYPE_3BUTTON)
|
|
{
|
|
//
|
|
// To be nice and clean we would ideally have a completely new
|
|
// event for the wheeled Microsoft mouse. However to maintain
|
|
// backwards compatibility, we send the event out in such a way
|
|
// that old incompatible systems interpret it as a NULL mouse
|
|
// move.
|
|
//
|
|
if (pIMEventIn->data.mouse.flags & IM_FLAG_MOUSE_WHEEL)
|
|
{
|
|
//
|
|
// This is a wheel rotatation.
|
|
//
|
|
// We massage this event so that new systems can see it for
|
|
// what it truly is - a wheel rotation, but old systems
|
|
// (which check the MOUSE_MOVE flag first, and ignore all
|
|
// other flags if set) see it as a mouse move.
|
|
//
|
|
// We did not set the MOUSE_MOVE flag when we first
|
|
// generated this event, since we did not want to trigger
|
|
// any of the sending side mouse move processing which
|
|
// would otherwise have been invoked.
|
|
//
|
|
pIMEventIn->data.mouse.flags |= IM_FLAG_MOUSE_MOVE;
|
|
}
|
|
|
|
//
|
|
// Forward the event
|
|
//
|
|
m_aimInControlEventsToReturn[m_imInControlNumEventsPending++] =
|
|
IEM_EVENT_FORWARD;
|
|
}
|
|
|
|
//
|
|
// Now we are going into a loop to return the m_iemLocalEvents we
|
|
// have queued up. We will return the first one below and then be
|
|
// called again until we have returned them all and return FALSE.
|
|
//
|
|
m_imfInControlNewEvent = FALSE;
|
|
m_imInControlNumEventsReturned = 0;
|
|
}
|
|
|
|
if (!m_imfInControlNewEvent)
|
|
{
|
|
if (m_imInControlNumEventsReturned == m_imInControlNumEventsPending)
|
|
{
|
|
//
|
|
// There are no more m_aiemLocalEvents to return.
|
|
//
|
|
TRACE_OUT(( "NO MORE EVENTS"));
|
|
m_imfInControlNewEvent = TRUE;
|
|
DC_QUIT;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Return the next event.
|
|
//
|
|
|
|
if (m_aimInControlEventsToReturn[m_imInControlNumEventsReturned] >=
|
|
IEM_EVENT_HOTKEY_BASE)
|
|
{
|
|
TRACE_OUT(( "HOTKEY "));
|
|
//
|
|
// Return a hotkey event.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK2;
|
|
pIMEventOut->data.keyboard.keyCode = (TSHR_UINT16)
|
|
(m_aimInControlEventsToReturn[m_imInControlNumEventsReturned] -
|
|
IEM_EVENT_HOTKEY_BASE);
|
|
pIMEventOut->data.keyboard.flags = 0;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Return a non-hotkey event.
|
|
//
|
|
switch (m_aimInControlEventsToReturn[m_imInControlNumEventsReturned])
|
|
{
|
|
case IEM_EVENT_CTRL_DOWN:
|
|
TRACE_OUT(( "CTRL DWN"));
|
|
//
|
|
// Set up a Ctrl down event.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_CONTROL;
|
|
pIMEventOut->data.keyboard.flags = 0;
|
|
break;
|
|
|
|
case IEM_EVENT_CTRL_UP:
|
|
TRACE_OUT(( "CTRL UP "));
|
|
//
|
|
// Set up a Ctrl up event with the quiet flag set
|
|
// - this means it should have no effect (other
|
|
// than to release the control key).
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_CONTROL;
|
|
pIMEventOut->data.keyboard.flags =
|
|
IM_FLAG_KEYBOARD_DOWN |
|
|
IM_FLAG_KEYBOARD_RELEASE |
|
|
IM_FLAG_KEYBOARD_QUIET;
|
|
break;
|
|
|
|
case IEM_EVENT_SHIFT_DOWN:
|
|
TRACE_OUT(( "SHFT DWN"));
|
|
//
|
|
// Set up a Shift down event.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_SHIFT;
|
|
pIMEventOut->data.keyboard.flags = 0;
|
|
break;
|
|
|
|
case IEM_EVENT_SHIFT_UP:
|
|
TRACE_OUT(( "SHFT UP "));
|
|
//
|
|
// Set up a Shift up event with the quiet flag set
|
|
// - this means it should have no effect (other
|
|
// than to release the shift key).
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_SHIFT;
|
|
pIMEventOut->data.keyboard.flags =
|
|
IM_FLAG_KEYBOARD_DOWN |
|
|
IM_FLAG_KEYBOARD_RELEASE |
|
|
IM_FLAG_KEYBOARD_QUIET;
|
|
break;
|
|
|
|
case IEM_EVENT_MENU_DOWN:
|
|
TRACE_OUT(( "MENU DWN"));
|
|
//
|
|
// Set up a Menu down event.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_MENU;
|
|
break;
|
|
|
|
case IEM_EVENT_MENU_UP:
|
|
TRACE_OUT(( "MENU UP "));
|
|
//
|
|
// Set up a Ctrl down event with the quiet flag set
|
|
// - ths is means it should have no effect (other
|
|
// than to release the menu key).
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_MENU;
|
|
pIMEventOut->data.keyboard.flags =
|
|
IM_FLAG_KEYBOARD_DOWN |
|
|
IM_FLAG_KEYBOARD_RELEASE |
|
|
IM_FLAG_KEYBOARD_QUIET;
|
|
break;
|
|
|
|
case IEM_EVENT_CAPS_LOCK_DOWN:
|
|
TRACE_OUT(( "CAPS DWN"));
|
|
//
|
|
// Send a caps lock down.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_CAPITAL;
|
|
pIMEventOut->data.keyboard.flags = 0;
|
|
break;
|
|
|
|
case IEM_EVENT_CAPS_LOCK_UP:
|
|
TRACE_OUT(( "CAPS UP "));
|
|
//
|
|
// Send a caps lock up.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_CAPITAL;
|
|
pIMEventOut->data.keyboard.flags =
|
|
IM_FLAG_KEYBOARD_DOWN |
|
|
IM_FLAG_KEYBOARD_RELEASE;
|
|
break;
|
|
|
|
case IEM_EVENT_NUM_LOCK_DOWN:
|
|
TRACE_OUT(( "NUM DOWN"));
|
|
//
|
|
// Send a num lock down - num lock is an extended
|
|
// key.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_NUMLOCK;
|
|
pIMEventOut->data.keyboard.flags =
|
|
IM_FLAG_KEYBOARD_EXTENDED;
|
|
break;
|
|
|
|
case IEM_EVENT_NUM_LOCK_UP:
|
|
//
|
|
// Send a num lock up - num lock is an extended
|
|
// key.
|
|
//
|
|
TRACE_OUT(( "NUM UP "));
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_NUMLOCK;
|
|
pIMEventOut->data.keyboard.flags =
|
|
IM_FLAG_KEYBOARD_DOWN |
|
|
IM_FLAG_KEYBOARD_RELEASE |
|
|
IM_FLAG_KEYBOARD_EXTENDED;
|
|
break;
|
|
|
|
case IEM_EVENT_SCROLL_LOCK_DOWN:
|
|
//
|
|
// Send a scroll lock down.
|
|
//
|
|
TRACE_OUT(( "SCROLDWN"));
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_SCROLL;
|
|
pIMEventOut->data.keyboard.flags = 0;
|
|
break;
|
|
|
|
case IEM_EVENT_SCROLL_LOCK_UP:
|
|
//
|
|
// Send a scroll lock up.
|
|
//
|
|
TRACE_OUT(( "SCROLLUP"));
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_SCROLL;
|
|
pIMEventOut->data.keyboard.flags =
|
|
IM_FLAG_KEYBOARD_DOWN |
|
|
IM_FLAG_KEYBOARD_RELEASE;
|
|
break;
|
|
|
|
case IEM_EVENT_FORWARD:
|
|
//
|
|
// Just copy the packet.
|
|
//
|
|
TRACE_OUT(( "FORWARD"));
|
|
*pIMEventOut = *pIMEventIn;
|
|
break;
|
|
|
|
case IEM_EVENT_CONSUMED:
|
|
//
|
|
// Copy the packet and set the flag.
|
|
//
|
|
TRACE_OUT(( "CONSUMED"));
|
|
*pIMEventOut = *pIMEventIn;
|
|
pIMEventOut->data.keyboard.flags |=
|
|
IM_FLAG_KEYBOARD_QUIET;
|
|
break;
|
|
|
|
default:
|
|
ERROR_OUT(( "Invalid code path"));
|
|
break;
|
|
}
|
|
}
|
|
m_imInControlNumEventsReturned++;
|
|
|
|
//
|
|
// Do tracing
|
|
//
|
|
if (pIMEventOut->type == IM_TYPE_ASCII)
|
|
{
|
|
TRACE_OUT(( "OUT ASCII code %04X, flags %04X",
|
|
pIMEventOut->data.keyboard.keyCode, pIMEventOut->data.keyboard.flags));
|
|
}
|
|
else if (pIMEventOut->type == IM_TYPE_VK1)
|
|
{
|
|
TRACE_OUT(( "OUT VK1 code %04X, flags %04X",
|
|
pIMEventOut->data.keyboard.keyCode, pIMEventOut->data.keyboard.flags));
|
|
}
|
|
else if (pIMEventOut->type == IM_TYPE_VK2)
|
|
{
|
|
TRACE_OUT(( "OUT VK2 code - %04X, flags - %04X",
|
|
pIMEventOut->data.keyboard.keyCode, pIMEventOut->data.keyboard.flags));
|
|
}
|
|
else if ((pIMEventOut->type == IM_TYPE_3BUTTON) &&
|
|
!(pIMEventOut->data.mouse.flags & IM_FLAG_MOUSE_MOVE))
|
|
{
|
|
TRACE_OUT(( "OUT 3BTTN flags - %04X (%d,%d)",
|
|
pIMEventOut->data.mouse.flags, pIMEventOut->data.mouse.x,
|
|
pIMEventOut->data.mouse.y));
|
|
}
|
|
else if (pIMEventOut->type == IM_TYPE_3BUTTON)
|
|
{
|
|
TRACE_OUT(( "OUT 3BTTN flags - %04X (%d,%d)",
|
|
pIMEventOut->data.mouse.flags, pIMEventOut->data.mouse.x,
|
|
pIMEventOut->data.mouse.y));
|
|
}
|
|
else
|
|
{
|
|
ERROR_OUT(("Invalid IM type %d", pIMEventOut->type));
|
|
}
|
|
|
|
rc = TRUE;
|
|
}
|
|
}
|
|
|
|
DC_EXIT_POINT:
|
|
DebugExitVOID(ASShare::IMTranslateOutgoing);
|
|
return(rc);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// IMTranslateIncoming()
|
|
//
|
|
// DESCRIPTION:
|
|
//
|
|
// Converts remotely generated sequences of IMEVENTs into sequences of
|
|
// IMEVENTs for replay. Does a 1 to (0-n) translation. Handles faking
|
|
// keys using ALT and keypad.
|
|
//
|
|
// When an IMEVENT is received and is ready to be replayed this function
|
|
// is called with a pointer to that packet in pIMEventIn.
|
|
// IMTranslateIncoming can then return TRUE and fill in the packet at
|
|
// pIMEventOut or return FALSE. If IMTranslateIncoming returns TRUE the
|
|
// IM will call it again with the same packet. The IMEVENTs returned are
|
|
// played back on the local machine using the journal playback hook by the
|
|
// IM.
|
|
//
|
|
// PARAMETERS:
|
|
//
|
|
// pIMEventIn - pointer to IMEVENT
|
|
//
|
|
// pIMEventOut - pointer to IMEVENT
|
|
//
|
|
// personID - the ID of the person this event was received from
|
|
//
|
|
// RETURNS:
|
|
//
|
|
// TRUE - packet returned (call function again)
|
|
//
|
|
// FALSE - no packet returned (don't call function again)
|
|
//
|
|
//
|
|
//
|
|
BOOL ASShare::IMTranslateIncoming
|
|
(
|
|
PIMEVENT pIMEventIn,
|
|
PIMEVENT pIMEventOut
|
|
)
|
|
{
|
|
BYTE curKbState;
|
|
BYTE rcVkKeyScanKbState;
|
|
UINT keyCode;
|
|
TSHR_UINT16 rcVkKeyScan;
|
|
BOOL bTranslateOEM;
|
|
char chAnsi;
|
|
char chOEM;
|
|
char chNewAnsi;
|
|
UINT position;
|
|
UINT digit;
|
|
UINT i;
|
|
|
|
DebugEntry(ASShare::IMTranslateIncoming);
|
|
|
|
//
|
|
// In this function we will receive several types of events
|
|
//
|
|
// IM_TYPE_VK1 - processed
|
|
// IM_TYPE_ASCII - processed
|
|
// IM_TYPE_VK2 - ignored (discarded)
|
|
// IM_TYPE_3BUTTON - processed
|
|
//
|
|
// For IM_TYPE_VK1:
|
|
//
|
|
// If it has the consumed locally flag set then try and play it back
|
|
// without anything happening. This means that for an Alt up we make
|
|
// sure that there have been some keyboard events between the Alt down
|
|
// and this event.
|
|
//
|
|
// For IM_TYPE_ASCII:
|
|
//
|
|
// Try to convert this to a VK to playback. If we are succesful then
|
|
// playback one or more key strokes to get into the correct shift state
|
|
// then play back the VK and then undo any shift states. If we can't
|
|
// convert to a VK then fake a sequence of Alt + numeric keypad keys to
|
|
// get the key in.
|
|
//
|
|
// For IM_TYPE_VK2:
|
|
//
|
|
// Discard unceremoniously.
|
|
//
|
|
// For IM_TYPE_3BUTTON:
|
|
//
|
|
// Play back directly.
|
|
//
|
|
//
|
|
keyCode = pIMEventIn->data.keyboard.keyCode;
|
|
|
|
if (m_imfControlledNewEvent)
|
|
{
|
|
//
|
|
// The first time we have seen a new event - accumulate an array
|
|
// of events we want to return.
|
|
//
|
|
|
|
//
|
|
// Start from the beginning of our returned events array.
|
|
//
|
|
m_imControlledNumEventsPending = 0;
|
|
m_imControlledNumEventsReturned = 0;
|
|
|
|
if (pIMEventIn->type == IM_TYPE_VK1)
|
|
{
|
|
//
|
|
// Handle VK1s first. Special cases are VK_MENU, VK_TAB and
|
|
// VK_ESC. We recognise VK_MENU down key strokes and remember
|
|
// when they happened so that we can possibly fiddle with
|
|
// VK_MENU up keystrokes later to go into menu mode. We check
|
|
// on VK_TAB for the IM_FLAG_KEYBOARD_QUIET flag and if it is
|
|
// set then we don't replay anything
|
|
// First translate the virtual key code from the DC-Share
|
|
// protocol code to the OS virtual key code
|
|
//
|
|
if (keyCode == VK_MENU)
|
|
{
|
|
if (!IS_IM_KEY_RELEASE(pIMEventIn->data.keyboard.flags))
|
|
{
|
|
//
|
|
// This is a VK_MENU press - return it without
|
|
// interfering.
|
|
//
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_REPLAY;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Handle VK_MENU up events
|
|
//
|
|
// If the menu up has the `quiet' flag set then
|
|
// insert a couple of shift key events to prevent it
|
|
// having any effect. There are two cases we're
|
|
// covering here where an Alt-UP can have some effect.
|
|
//
|
|
// 1. Alt-Down, Alt-Up causes the system menu button to
|
|
// be highlighted.
|
|
//
|
|
// 2. Entering characters from the numeric keypad takes
|
|
// effect on the Alt-Up.
|
|
//
|
|
// Both of these effects can be negated by adding the
|
|
// shift key strokes.
|
|
//
|
|
if (pIMEventIn->data.keyboard.flags &
|
|
IM_FLAG_KEYBOARD_QUIET)
|
|
{
|
|
//
|
|
// We need to `silence' this key - to do this we
|
|
// will insert to shift key strokes first
|
|
//
|
|
if (m_aimControlledControllerKeyStates[VK_SHIFT] & 0x80)
|
|
{
|
|
//
|
|
// Shift is currently down - insert an up then
|
|
// a down
|
|
//
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_SHIFT_UP;
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_SHIFT_DOWN;
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Shift is currently up - insert a down then
|
|
// an up
|
|
//
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_SHIFT_DOWN;
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_SHIFT_UP;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Replay the menu up key stroke.
|
|
//
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_REPLAY;
|
|
|
|
}
|
|
}
|
|
else if ((pIMEventIn->data.keyboard.flags &
|
|
IM_FLAG_KEYBOARD_QUIET) &&
|
|
((keyCode == VK_TAB) ||
|
|
(keyCode == VK_ESCAPE)))
|
|
{
|
|
//
|
|
// Just get out of here - we don't want to play this back
|
|
//
|
|
return(FALSE);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// All other VKs just get replayed
|
|
//
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_REPLAY;
|
|
}
|
|
}
|
|
else if (pIMEventIn->type == IM_TYPE_ASCII)
|
|
{
|
|
//
|
|
// For ASCII packets we need to find out how we can replay them
|
|
// on our local keyboard. If we can replay them directly or
|
|
// with shift or ctrl (but not with ALT), then we will do so,
|
|
// otherwise we will simulate Alt + numeric keypad to replay
|
|
// them. If we have to generate fake modifier key strokes
|
|
// ourselves then we will replay the whole key stroke on the
|
|
// incoming key down. If we don't need to generate fake key
|
|
// strokes then we will play the down and up keystrokes as they
|
|
// come in.
|
|
//
|
|
// We do not allow VK combinations involving ALT as this messes
|
|
// up remote international keyboard support. For example, if
|
|
// the remote keyboard is UK and we are (say) Spanish,
|
|
// VKKeyScan says we can do the "UK pound" character as
|
|
// Ctrl+Alt+3. While this works in Windows, and for DOS Boxes
|
|
// on standard keyboards, DOS Boxes with enhanced keyboards
|
|
// require ALTGR+3 (nb Windows seems to treat ALTGR as Ctrl+Alt
|
|
// anyway - at least for VKs and Async state). There is no VK
|
|
// for ALTGR, so do an ALT-nnn sequence for these cases.
|
|
//
|
|
rcVkKeyScan = VkKeyScan((char)keyCode);
|
|
TRACE_OUT(( "co_vk_key_scan of X%02x returns rcVkKeyScan X%02x",
|
|
keyCode, rcVkKeyScan));
|
|
if ((rcVkKeyScan != 0xffff) && !(rcVkKeyScan & 0x0400))
|
|
{
|
|
//
|
|
// This can be replayed using a combination of modifiers on
|
|
// this keyboard.
|
|
//
|
|
rcVkKeyScanKbState = HIBYTE(rcVkKeyScan);
|
|
|
|
//
|
|
// The high byte of rcVkKeyScan contains three bit flags
|
|
// which signify which modifiers ar required to generate
|
|
// this character. They are
|
|
//
|
|
// bit 0 - Shift
|
|
// bit 1 - Ctrl
|
|
// bit 2 - Alt (Menu)
|
|
//
|
|
// We will construct an equivalent set of flags which
|
|
// describes the current state of these modifiers.
|
|
//
|
|
curKbState = 0;
|
|
|
|
if (m_aimControlledControllerKeyStates[VK_SHIFT] & 0x80)
|
|
{
|
|
curKbState |= IEM_SHIFT_DOWN;
|
|
}
|
|
|
|
if (m_aimControlledControllerKeyStates[VK_CONTROL] & 0x80)
|
|
{
|
|
curKbState |= IEM_CTRL_DOWN;
|
|
}
|
|
|
|
if (m_aimControlledControllerKeyStates[VK_MENU] & 0x80)
|
|
{
|
|
curKbState |= IEM_MENU_DOWN;
|
|
|
|
//
|
|
// If the Alt key is down currently in this person's
|
|
// context then (in general
|
|
// it. This means accelerators which need to be
|
|
// shifted will work as we won't release the Alt key in
|
|
// order to generate the key strokes.
|
|
//
|
|
// However, if the ALT key is being held down in
|
|
// combination with SHIFT and CTRL to generate a
|
|
// character (e.g. CTRL-ALT-SHIFT-4 on a US keyboard
|
|
// to generate a œ character) then we will allow the
|
|
// ALT key up before we play back the true character.
|
|
//
|
|
if ((curKbState & (IEM_SHIFT_DOWN | IEM_CTRL_DOWN)) !=
|
|
(IEM_SHIFT_DOWN | IEM_CTRL_DOWN))
|
|
{
|
|
rcVkKeyScanKbState |= IEM_MENU_DOWN;
|
|
}
|
|
}
|
|
|
|
if ((m_aimControlledControllerKeyStates[VK_CAPITAL] & 0x01) &&
|
|
((LOBYTE(rcVkKeyScan) >= 'A') &&
|
|
((LOBYTE(rcVkKeyScan) <= 'Z'))))
|
|
{
|
|
//
|
|
// If caps-lock is enabled then the effect of a shift
|
|
// down on VKs A thru Z is reversed. This logic ( 'A'
|
|
// <= x <= 'Z' is encoded in the keyboard.drv so it
|
|
// should be pretty safe).
|
|
//
|
|
curKbState ^= IEM_SHIFT_DOWN;
|
|
}
|
|
|
|
if (curKbState == rcVkKeyScanKbState)
|
|
{
|
|
//
|
|
// We are already in the correct shift state so just
|
|
// replay the VK.
|
|
//
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_REPLAY_VK;
|
|
m_imControlledVKToReplay = LOBYTE(rcVkKeyScan);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We need to generate some fake modifiers - only do
|
|
// this on a key press.
|
|
//
|
|
if (pIMEventIn->data.keyboard.flags &
|
|
IM_FLAG_KEYBOARD_RELEASE)
|
|
{
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Insert modifiers to get into the correct state.
|
|
//
|
|
m_imControlledNumEventsPending += IMInsertModifierKeystrokes(
|
|
curKbState,
|
|
rcVkKeyScanKbState,
|
|
&(m_aimControlledEventsToReturn[m_imControlledNumEventsPending]));
|
|
|
|
//
|
|
// Now insert the VK itself - a down and up.
|
|
//
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_REPLAY_VK_DOWN;
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_REPLAY_VK_UP;
|
|
|
|
//
|
|
// Remeber the VK we want to replay when we come across
|
|
// IEM_EVENT_REPLAY_VK_DOWN/UP.
|
|
//
|
|
m_imControlledVKToReplay = LOBYTE(rcVkKeyScan);
|
|
|
|
//
|
|
// Now insert the modifiers to get back to the current
|
|
// state.
|
|
//
|
|
m_imControlledNumEventsPending += IMInsertModifierKeystrokes(
|
|
rcVkKeyScanKbState,
|
|
curKbState,
|
|
&(m_aimControlledEventsToReturn[m_imControlledNumEventsPending]));
|
|
|
|
//
|
|
// Now we have a complete set of events ready to replay
|
|
// so go for it.
|
|
//
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We can't replay directly, so will have to simulate an
|
|
// Alt+keypad sequence.
|
|
//
|
|
TRACE_OUT(( "FAKE AN ALT-nnn SEQUENCE IF WINDOWS"));
|
|
//
|
|
// We only do this sort of stuff on a key-press.
|
|
//
|
|
if (pIMEventIn->data.keyboard.flags &
|
|
IM_FLAG_KEYBOARD_RELEASE)
|
|
{
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// The following code relies on keyCode being less than 999
|
|
// and we should receive a keycode > 255 so get out now if
|
|
// we have.
|
|
//
|
|
if (keyCode > 255)
|
|
{
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// First get modifiers into correct state - create bit
|
|
// flags for current modifier state.
|
|
//
|
|
curKbState = 0;
|
|
|
|
//
|
|
// For windows we have a character to input that cannot
|
|
// be replayed by pressing a key...replay by injecting
|
|
// alt-nnn.
|
|
//
|
|
if (m_aimControlledControllerKeyStates[VK_SHIFT] & 0x80)
|
|
{
|
|
curKbState |= IEM_SHIFT_DOWN;
|
|
}
|
|
|
|
if (m_aimControlledControllerKeyStates[VK_CONTROL] & 0x80)
|
|
{
|
|
curKbState |= IEM_CTRL_DOWN;
|
|
}
|
|
|
|
if (m_aimControlledControllerKeyStates[VK_MENU] & 0x80)
|
|
{
|
|
curKbState |= IEM_MENU_DOWN;
|
|
}
|
|
|
|
//
|
|
// If necessary, reset all modifiers.
|
|
//
|
|
if (curKbState)
|
|
{
|
|
m_imControlledNumEventsPending += IMInsertModifierKeystrokes(
|
|
curKbState,
|
|
0,
|
|
&(m_aimControlledEventsToReturn[m_imControlledNumEventsPending]));
|
|
}
|
|
|
|
//
|
|
// Now determine whether we can do the ALT-nnn keypad
|
|
// sequence using an OEM keycode or whether we have to use
|
|
// an ANSI (Windows) keycode.
|
|
//
|
|
// The issue here is that:
|
|
//
|
|
// - hosted Windows applications (or rather Windows itself)
|
|
// can distinguish between, and handle correctly, ANSI
|
|
// keycodes and OEM keycodes (where the latter vary
|
|
// depending on the keyboard type). For example,
|
|
// ALT-0163 is the ANSI "UK pound" on all keyboards,
|
|
// and on US national keyboards ALT-156 is the OEM
|
|
// keycode for "UK pound".
|
|
//
|
|
// - hosted DOS Boxes only understand OEM keycodes.
|
|
//
|
|
// So (for example), if we have a remote UK keyboard
|
|
// controlling local Windows and DOS Box applications, and
|
|
// we generate ALT-nnn using the OEM keycode (and without a
|
|
// leading zero), both the Windows and DOS Box applications
|
|
// interpret it as "UK pound" (Hoorah!). In contrast, if
|
|
// we generate ALT-nnn using the ANSI keycode (with a
|
|
// leading zero), the Windows applications still do "UK
|
|
// pound", BUT the DOS Box does an "u acute".
|
|
//
|
|
// As far as we can tell (eg by examining the DDK keyboard
|
|
// driver source for AnsiToOem), there should always be a
|
|
// translation. However, it is possible that the ANSI to
|
|
// OEM translation is not 1<->1. We therefore check this
|
|
// by doing a second translation back from OEM to ANSI. If
|
|
// this does not give us the original character we use the
|
|
// original ANSI code and play it back with a ALT-0nnn
|
|
// sequence.
|
|
//
|
|
chAnsi = (char)pIMEventIn->data.keyboard.keyCode;
|
|
|
|
AnsiToOemBuff(&chAnsi, &chOEM, 1);
|
|
OemToAnsiBuff(&chOEM, &chNewAnsi, 1);
|
|
TRACE_OUT(( "Ansi: %02x OEM: %02x NewAnsi: %02x",
|
|
(BYTE)chAnsi,
|
|
(BYTE)chOEM,
|
|
(BYTE)chNewAnsi ));
|
|
|
|
bTranslateOEM = (chAnsi == chNewAnsi);
|
|
|
|
keyCode = (bTranslateOEM)
|
|
? (UINT)(BYTE)chOEM
|
|
: pIMEventIn->data.keyboard.keyCode;
|
|
|
|
//
|
|
// Now insert a VK_MENU down.
|
|
//
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_MENU_DOWN;
|
|
|
|
//
|
|
// Now insert the numeric keypad keystrokes. If we're
|
|
// doing an ANSI ALT
|
|
//
|
|
if (!bTranslateOEM)
|
|
{
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_KEYPAD0_DOWN;
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_KEYPAD0_UP;
|
|
}
|
|
|
|
|
|
//
|
|
// Add keystrokes for hundreds, tens and units, taking care
|
|
// to discard leading (but not trailing) zeros if we're
|
|
// doing an OEM sequence (which would confuse Windows into
|
|
// thinking an OEM ALT-nnn sequence was an ANSI sequence).
|
|
//
|
|
position = 100;
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
//
|
|
// Insert the correct digit for this position.
|
|
//
|
|
digit = keyCode / position;
|
|
|
|
if (!(digit == 0 && bTranslateOEM))
|
|
{
|
|
bTranslateOEM = FALSE;
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_KEYPAD0_DOWN + digit;
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_KEYPAD0_UP + digit;
|
|
}
|
|
|
|
//
|
|
// Move to next position.
|
|
//
|
|
keyCode %= position;
|
|
position /= 10;
|
|
}
|
|
|
|
//
|
|
// Now insert a VK_MENU up.
|
|
//
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] =
|
|
IEM_EVENT_MENU_UP;
|
|
|
|
|
|
//
|
|
// If necessary, get the modifiers back to the state they
|
|
// were in previously.
|
|
//
|
|
if (curKbState != 0)
|
|
{
|
|
m_imControlledNumEventsPending += IMInsertModifierKeystrokes(
|
|
0,
|
|
curKbState,
|
|
&(m_aimControlledEventsToReturn[m_imControlledNumEventsPending]));
|
|
}
|
|
|
|
//
|
|
// Now we have a buffer full of keystrokes - go for it.
|
|
//
|
|
}
|
|
}
|
|
else if (pIMEventIn->type == IM_TYPE_VK2)
|
|
{
|
|
//
|
|
// Hot keys are thrown away - this is easy.
|
|
//
|
|
return(FALSE);
|
|
}
|
|
else if (pIMEventIn->type == IM_TYPE_3BUTTON)
|
|
{
|
|
//
|
|
// Mouse events are just replayed.
|
|
//
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsPending++] = IEM_EVENT_REPLAY;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Unknown events are thrown away - this is easy.
|
|
//
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Now we have events to return.
|
|
//
|
|
m_imfControlledNewEvent = FALSE;
|
|
m_imControlledNumEventsReturned = 0;
|
|
}
|
|
|
|
if (!m_imfControlledNewEvent)
|
|
{
|
|
if (m_imControlledNumEventsReturned == m_imControlledNumEventsPending)
|
|
{
|
|
//
|
|
// There are no more events to return.
|
|
//
|
|
m_imfControlledNewEvent = TRUE;
|
|
return(FALSE);
|
|
}
|
|
else
|
|
{
|
|
TRACE_OUT(("Event to return: %u",
|
|
m_aimControlledEventsToReturn[m_imControlledNumEventsReturned]));
|
|
if ((m_aimControlledEventsToReturn[m_imControlledNumEventsReturned] >=
|
|
IEM_EVENT_KEYPAD0_DOWN) &&
|
|
(m_aimControlledEventsToReturn[m_imControlledNumEventsReturned] <=
|
|
(IEM_EVENT_KEYPAD0_DOWN+9)))
|
|
{
|
|
//
|
|
// Return a keypad down event.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = (TSHR_UINT16)
|
|
(VK_NUMPAD0 +
|
|
(m_aimControlledEventsToReturn[m_imControlledNumEventsReturned] -
|
|
IEM_EVENT_KEYPAD0_DOWN));
|
|
pIMEventOut->data.keyboard.flags = IM_FLAG_KEYBOARD_ALT_DOWN;
|
|
}
|
|
else if ((m_aimControlledEventsToReturn[m_imControlledNumEventsReturned] >=
|
|
IEM_EVENT_KEYPAD0_UP) &&
|
|
(m_aimControlledEventsToReturn[m_imControlledNumEventsReturned] <=
|
|
(IEM_EVENT_KEYPAD0_UP+9)))
|
|
{
|
|
//
|
|
// Return a keypad up event.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = (TSHR_UINT16)
|
|
(VK_NUMPAD0 +
|
|
(m_aimControlledEventsToReturn[m_imControlledNumEventsReturned] -
|
|
IEM_EVENT_KEYPAD0_UP));
|
|
pIMEventOut->data.keyboard.flags = IM_FLAG_KEYBOARD_DOWN |
|
|
IM_FLAG_KEYBOARD_RELEASE |
|
|
IM_FLAG_KEYBOARD_ALT_DOWN;
|
|
}
|
|
else
|
|
{
|
|
switch (m_aimControlledEventsToReturn[m_imControlledNumEventsReturned])
|
|
{
|
|
case IEM_EVENT_CTRL_DOWN:
|
|
//
|
|
// Set up a Ctrl down event.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode =
|
|
VK_CONTROL;
|
|
pIMEventOut->data.keyboard.flags = 0;
|
|
break;
|
|
|
|
case IEM_EVENT_CTRL_UP:
|
|
//
|
|
// Set up a Ctrl up event.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode =
|
|
VK_CONTROL;
|
|
pIMEventOut->data.keyboard.flags =
|
|
IM_FLAG_KEYBOARD_DOWN | IM_FLAG_KEYBOARD_RELEASE;
|
|
break;
|
|
|
|
case IEM_EVENT_SHIFT_DOWN:
|
|
//
|
|
// Set up a Shift down event.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode =
|
|
VK_SHIFT;
|
|
pIMEventOut->data.keyboard.flags = 0;
|
|
break;
|
|
|
|
case IEM_EVENT_SHIFT_UP:
|
|
//
|
|
// Set up a Shift up event.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode =
|
|
VK_SHIFT;
|
|
pIMEventOut->data.keyboard.flags =
|
|
IM_FLAG_KEYBOARD_DOWN | IM_FLAG_KEYBOARD_RELEASE;
|
|
break;
|
|
|
|
case IEM_EVENT_MENU_DOWN:
|
|
//
|
|
// Set up a Menu down event.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_MENU;
|
|
pIMEventOut->data.keyboard.flags = 0;
|
|
break;
|
|
|
|
case IEM_EVENT_MENU_UP:
|
|
//
|
|
// Set up a Menu up event.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = VK_MENU;
|
|
pIMEventOut->data.keyboard.flags =
|
|
IM_FLAG_KEYBOARD_DOWN | IM_FLAG_KEYBOARD_RELEASE;
|
|
break;
|
|
|
|
case IEM_EVENT_REPLAY:
|
|
//
|
|
// Just copy the packet.
|
|
//
|
|
*pIMEventOut = *pIMEventIn;
|
|
break;
|
|
|
|
case IEM_EVENT_REPLAY_VK:
|
|
//
|
|
// Replay the VK from m_imControlledVKToReplay using the
|
|
// flags on the incoming packet.
|
|
//
|
|
*pIMEventOut = *pIMEventIn;
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = (TSHR_UINT16)
|
|
m_imControlledVKToReplay;
|
|
break;
|
|
|
|
case IEM_EVENT_REPLAY_VK_UP:
|
|
//
|
|
// Replay an up key event for the VK in
|
|
// m_imControlledVKToReplay.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = (TSHR_UINT16)
|
|
m_imControlledVKToReplay;
|
|
pIMEventOut->data.keyboard.flags =
|
|
IM_FLAG_KEYBOARD_DOWN | IM_FLAG_KEYBOARD_RELEASE;
|
|
break;
|
|
|
|
case IEM_EVENT_REPLAY_VK_DOWN:
|
|
//
|
|
// Replay a down key event for the VK in
|
|
// m_imControlledVKToReplay.
|
|
//
|
|
pIMEventOut->type = IM_TYPE_VK1;
|
|
pIMEventOut->data.keyboard.keyCode = (TSHR_UINT16)
|
|
m_imControlledVKToReplay;
|
|
pIMEventOut->data.keyboard.flags = 0;
|
|
break;
|
|
|
|
case IEM_EVENT_NORMAL:
|
|
//
|
|
// Play back the event but force it to be normal.
|
|
//
|
|
*pIMEventOut = *pIMEventIn;
|
|
pIMEventOut->data.keyboard.flags &=
|
|
(TSHR_UINT16)~IM_FLAG_KEYBOARD_ALT_DOWN;
|
|
break;
|
|
|
|
case IEM_EVENT_SYSTEM:
|
|
//
|
|
// Play back the event but force it to be system.
|
|
//
|
|
*pIMEventOut = *pIMEventIn;
|
|
pIMEventOut->data.keyboard.flags |=
|
|
IM_FLAG_KEYBOARD_ALT_DOWN;
|
|
break;
|
|
|
|
default:
|
|
ERROR_OUT(( "Invalid code path"));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_imControlledNumEventsReturned++;
|
|
|
|
//
|
|
// If we're going to playback a NUMLOCK event, make sure we force
|
|
// the keyboard LEDs to be accurate.
|
|
//
|
|
if ((pIMEventOut->type == IM_TYPE_VK1) &&
|
|
(pIMEventOut->data.keyboard.keyCode == VK_NUMLOCK) &&
|
|
IS_IM_KEY_PRESS(pIMEventOut->data.keyboard.flags))
|
|
{
|
|
TRACE_OUT(("Playing back NUMLOCK; add IM_FLAG_KEYBOARD_UPDATESTATE"));
|
|
pIMEventOut->data.keyboard.flags |= IM_FLAG_KEYBOARD_UPDATESTATE;
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
DebugExitBOOL(ASShare::IMTranslateIncoming, FALSE);
|
|
return(FALSE);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: IMInsertModifierKeystrokes
|
|
//
|
|
// DESCRIPTION:
|
|
//
|
|
// This function inserts various modifier keystrokes into the supplied
|
|
// buffer to move from one modifier state to another.
|
|
//
|
|
// PARAMETERS:
|
|
//
|
|
// curKbState - the current modifier state (bit 0 - Shift, bit 1 - Ctrl,
|
|
// bit 2 - Menu).
|
|
//
|
|
// targetKbState - the state we want the modifiers to be in
|
|
//
|
|
// pEventQueue - a pointer to an array where the required events can be
|
|
// inserted
|
|
//
|
|
// RETURNS: the number of events inserted
|
|
//
|
|
//
|
|
UINT ASShare::IMInsertModifierKeystrokes
|
|
(
|
|
BYTE curKbState,
|
|
BYTE targetKbState,
|
|
LPUINT pEventQueue
|
|
)
|
|
{
|
|
|
|
UINT kbDelta;
|
|
UINT events = 0;
|
|
|
|
DebugEntry(ASShare::IMInsertModifierKeystrokes);
|
|
|
|
//
|
|
// Find out which modifiers are different.
|
|
//
|
|
kbDelta = curKbState ^ targetKbState;
|
|
TRACE_OUT(( "Keyboard delat %x", kbDelta));
|
|
|
|
//
|
|
// Now generate the right events to get us into the correct modifier
|
|
// state.
|
|
//
|
|
if (kbDelta & IEM_SHIFT_DOWN)
|
|
{
|
|
//
|
|
// Shift state is different - do we need an up or down.
|
|
//
|
|
if (curKbState & IEM_SHIFT_DOWN)
|
|
{
|
|
//
|
|
// We need an up.
|
|
//
|
|
pEventQueue[events++] = IEM_EVENT_SHIFT_UP;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We need a down.
|
|
//
|
|
pEventQueue[events++] = IEM_EVENT_SHIFT_DOWN;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Same process for Ctrl and Alt.
|
|
//
|
|
if (kbDelta & IEM_CTRL_DOWN)
|
|
{
|
|
if (curKbState & IEM_CTRL_DOWN)
|
|
{
|
|
pEventQueue[events++] = IEM_EVENT_CTRL_UP;
|
|
}
|
|
else
|
|
{
|
|
pEventQueue[events++] = IEM_EVENT_CTRL_DOWN;
|
|
}
|
|
}
|
|
|
|
if (kbDelta & IEM_MENU_DOWN)
|
|
{
|
|
if (curKbState & IEM_MENU_DOWN)
|
|
{
|
|
pEventQueue[events++] = IEM_EVENT_MENU_UP;
|
|
}
|
|
else
|
|
{
|
|
pEventQueue[events++] = IEM_EVENT_MENU_DOWN;
|
|
}
|
|
}
|
|
|
|
DebugExitDWORD(ASShare::IMInsertModifierKeystrokes, events);
|
|
return(events);
|
|
}
|
|
|
|
|
|
//
|
|
// IMInjectEvent()
|
|
//
|
|
// DESCRIPTION:
|
|
//
|
|
// Called by IMMaybeInjectEvents when it is ready to inject an event.
|
|
// Given a pointer to a IMOSEVENT this function formats it correctly and
|
|
// calls the appropriate USER callback. It also updates the async key
|
|
// state arrays for the source queue and USER and sets m_imLastInjectTime to
|
|
// the tick count at which the event was injected. We protect against
|
|
// injecting up key strokes/mouse buttons when USER does not think the
|
|
// key/button is down in this function. It is quite possible (given the
|
|
// potential variety of CAs) that the IM will be asked to inject an up
|
|
// event when there has been no corresponding down event. This should be
|
|
// harmless as it is possible for this to happen in real life (ie the
|
|
// system message queue is full when the down event happens but there is
|
|
// space when the up event happens). However, it is quite unlikely and it
|
|
// is more likely that injecting these unmatched events will confuse
|
|
// applications.
|
|
//
|
|
// PARAMETERS:
|
|
//
|
|
// pEvent - pointer to an IMOSEVENT.
|
|
//
|
|
// THIS WORKS FOR NT AND WIN95.
|
|
//
|
|
BOOL ASShare::IMInjectEvent(LPIMOSEVENT pEvent)
|
|
{
|
|
UINT clickTime;
|
|
TSHR_UINT16 flags;
|
|
TSHR_UINT16 flagsAfter;
|
|
LPMSEV pMouseEvent;
|
|
|
|
DebugEntry(IMInjectEvent);
|
|
|
|
//
|
|
// Now inject the event.
|
|
//
|
|
switch (pEvent->type)
|
|
{
|
|
case IM_MOUSE_EVENT:
|
|
//
|
|
// Set up a pointer to the mouse event data.
|
|
//
|
|
pMouseEvent = &(pEvent->event.mouse);
|
|
|
|
//
|
|
// Check whether this is an unmatched up event
|
|
//
|
|
if ((IM_MEV_BUTTON1_UP(*pEvent) &&
|
|
IM_KEY_STATE_IS_UP(m_aimControlledKeyStates[VK_LBUTTON])) ||
|
|
(IM_MEV_BUTTON2_UP(*pEvent) &&
|
|
IM_KEY_STATE_IS_UP(m_aimControlledKeyStates[VK_RBUTTON])) ||
|
|
(IM_MEV_BUTTON3_UP(*pEvent) &&
|
|
IM_KEY_STATE_IS_UP(m_aimControlledKeyStates[VK_MBUTTON])))
|
|
{
|
|
//
|
|
// This is an unmatched up event so just discard it here
|
|
//
|
|
TRACE_OUT(("IMInjectEvent: discarding unmatched mouse up event"));
|
|
DC_QUIT;
|
|
}
|
|
|
|
//
|
|
// Store the injection time of this event.
|
|
//
|
|
m_imControlledLastLowLevelMouseEventTime = GetTickCount();
|
|
|
|
//
|
|
// Store the mouse position - only consider absolute mouse
|
|
// moves. (Note that for the cases in which we inject a
|
|
// relative mouse event we always set the co-ordinate change to
|
|
// 0).
|
|
//
|
|
if (pMouseEvent->flags & MOUSEEVENTF_ABSOLUTE)
|
|
{
|
|
m_imControlledLastMousePos.x = pMouseEvent->pt.x;
|
|
m_imControlledLastMousePos.y = pMouseEvent->pt.y;
|
|
|
|
TRACE_OUT(( "Updating mouse position (%d:%d)",
|
|
m_imControlledLastMousePos.x,
|
|
m_imControlledLastMousePos.y));
|
|
}
|
|
|
|
//
|
|
// Inject the event.
|
|
//
|
|
TRACE_OUT(("IMInjectEvent: MOUSE parameters are:"));
|
|
TRACE_OUT((" flags 0x%08x", pMouseEvent->flags));
|
|
TRACE_OUT((" time 0x%08x", m_imControlledLastLowLevelMouseEventTime));
|
|
TRACE_OUT((" position (%d, %d)", pMouseEvent->pt.x, pMouseEvent->pt.y));
|
|
TRACE_OUT((" mouseData %d", pMouseEvent->mouseData));
|
|
TRACE_OUT((" dwExtra %d", pMouseEvent->dwExtraInfo));
|
|
|
|
//
|
|
// Finally scale the logical screen co-ordinates to the full
|
|
// 16-bit range (0..65535).
|
|
//
|
|
|
|
ASSERT(m_pasLocal->cpcCaps.screen.capsScreenWidth);
|
|
ASSERT(m_pasLocal->cpcCaps.screen.capsScreenHeight);
|
|
|
|
pMouseEvent->pt.x = IM_MOUSEPOS_LOG_TO_OS(pMouseEvent->pt.x,
|
|
m_pasLocal->cpcCaps.screen.capsScreenWidth);
|
|
pMouseEvent->pt.y = IM_MOUSEPOS_LOG_TO_OS(pMouseEvent->pt.y,
|
|
m_pasLocal->cpcCaps.screen.capsScreenHeight);
|
|
|
|
OSI_InjectMouseEvent(pMouseEvent->flags, pMouseEvent->pt.x,
|
|
pMouseEvent->pt.y, pMouseEvent->mouseData, pMouseEvent->dwExtraInfo);
|
|
break;
|
|
|
|
case IM_KEYBOARD_EVENT:
|
|
//
|
|
// Check whether this is an unmatched up event
|
|
//
|
|
if (IM_KEV_KEYUP(*pEvent) &&
|
|
IM_KEY_STATE_IS_UP(m_aimControlledKeyStates[IM_KEV_VKCODE(*pEvent)]))
|
|
{
|
|
//
|
|
// This is an unmatched up event so just discard it.
|
|
//
|
|
TRACE_OUT(("IMInjectEvent: discarding unmatched key up event %04hX",
|
|
IM_KEV_VKCODE(*pEvent)));
|
|
DC_QUIT;
|
|
}
|
|
|
|
//
|
|
// Inject the event.
|
|
//
|
|
TRACE_OUT(("IMInjectEvent: KEYBD parameters are:"));
|
|
TRACE_OUT((" flags 0x%08x", pEvent->event.keyboard.flags));
|
|
TRACE_OUT((" virtkey %u", pEvent->event.keyboard.vkCode));
|
|
TRACE_OUT((" scan code %u", pEvent->event.keyboard.scanCode));
|
|
|
|
OSI_InjectKeyboardEvent(pEvent->event.keyboard.flags,
|
|
pEvent->event.keyboard.vkCode, pEvent->event.keyboard.scanCode,
|
|
pEvent->event.keyboard.dwExtraInfo);
|
|
|
|
if (pEvent->flags & IM_FLAG_UPDATESTATE)
|
|
{
|
|
BYTE kbState[256];
|
|
|
|
TRACE_OUT(("Updating keyboard LED state after playing back toggle"));
|
|
|
|
GetKeyboardState(kbState);
|
|
SetKeyboardState(kbState);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// We do nothing for unexpected events - this allow us to add
|
|
// more events later that can be sent to back level systems
|
|
// where they will be safely ignored
|
|
//
|
|
TRACE_OUT(( "Unexpected event %d", pEvent->type));
|
|
DC_QUIT;
|
|
}
|
|
|
|
//
|
|
// If we get here successfully then we want to update our copy of the
|
|
// async key state so set the flag.
|
|
//
|
|
IMUpdateAsyncArray(m_aimControlledKeyStates, pEvent);
|
|
|
|
DC_EXIT_POINT:
|
|
|
|
DebugExitBOOL(ASShare::IMInjectEvent, TRUE);
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: IMInjectingEvents
|
|
//
|
|
BOOL ASShare::IMInjectingEvents(void)
|
|
{
|
|
LPIMOSEVENT pNextEvent;
|
|
IMOSEVENT mouseMoveEvent;
|
|
UINT tick;
|
|
UINT targetTime;
|
|
UINT targetDelta;
|
|
BOOL rc = TRUE;
|
|
|
|
DebugEntry(ASShare::IMInjectingEvents);
|
|
|
|
if (m_pasLocal->m_caControlledBy && m_imControlledOSQ.numEvents)
|
|
{
|
|
pNextEvent = m_imControlledOSQ.events + m_imControlledOSQ.head;
|
|
|
|
//
|
|
// First check if this is a remote mouse event being injected too
|
|
// soon after the previous one. We used to only do this for mouse
|
|
// move events to prevent them all being spoiled if they were
|
|
// injected too quickly. However, we now do it for all mouse
|
|
// events because of a bug in Windows USER whereby if the mouse
|
|
// press which brings up a menu is processed after the
|
|
// corresponding mouse release has been passed to USER (so that the
|
|
// async state of the mouse button is up) then the menu is brought
|
|
// up in the position it is brought up in if it is selected via the
|
|
// keyboard rather than the position it is brought up in if it is
|
|
// selected by the mouse. (These positions are only different when
|
|
// the menu cannot be placed completely below or above the menu
|
|
// bar). This can then lead to the mouse release selecting an item
|
|
// from the menu.
|
|
//
|
|
tick = GetTickCount();
|
|
if (m_imfControlledPaceInjection &&
|
|
(pNextEvent->type == IM_MOUSE_EVENT))
|
|
{
|
|
//
|
|
// This is a remote mouse event so check that now is a good
|
|
// time to inject it Smooth out the backlog adjustment so that
|
|
// packet bursts do not get spoiled too much. Set an absolute
|
|
// lg_lpimSharedData->imit on injection delay of the low sample rate so that
|
|
// timestamp anomolies do not cause us to withhold messages
|
|
//
|
|
|
|
//
|
|
// The target delta between last and current events is
|
|
// calculated from the remote timestamps
|
|
//
|
|
targetDelta = abs((int)(pNextEvent->time -
|
|
m_imControlledLastMouseRemoteTime));
|
|
if (targetDelta > IM_LOCAL_MOUSE_SAMPLING_GAP_LOW_MS)
|
|
{
|
|
targetDelta = IM_LOCAL_MOUSE_SAMPLING_GAP_LOW_MS;
|
|
}
|
|
|
|
//
|
|
// The target injection time is based on the last injection
|
|
// time and our target delta, adjusted for any backlog we are
|
|
// seeing. Because packeting gives a jerky backlog we need to
|
|
// smooth our adjustment out (only modify by backlog/8)
|
|
//
|
|
targetTime = m_imControlledLastMouseLocalTime +
|
|
targetDelta - (m_imControlledMouseBacklog/8);
|
|
|
|
TRACE_OUT(( "Last tremote %#lx, this tremote %#lx, backlog %#lx",
|
|
m_imControlledLastMouseRemoteTime,
|
|
pNextEvent->time,
|
|
m_imControlledMouseBacklog));
|
|
TRACE_OUT(( "Last tlocal %#lx, tick %#lx, targetTime %#lx",
|
|
m_imControlledLastMouseLocalTime,
|
|
tick,
|
|
targetTime));
|
|
|
|
//
|
|
// Now inject the events - ignore them if they are too early
|
|
//
|
|
if (IM_MEV_ABS_MOVE(*pNextEvent) && (tick < targetTime))
|
|
{
|
|
//
|
|
// If values seem wild (for example this is the first mouse
|
|
// event ever) then reset them
|
|
//
|
|
if (targetTime > tick + 1000)
|
|
{
|
|
m_imControlledLastMouseRemoteTime = pNextEvent->time;
|
|
m_imControlledLastMouseLocalTime = tick;
|
|
m_imControlledMouseBacklog = 0;
|
|
TRACE_OUT(( "Wild values - reset"));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is too early - get out of the loop.
|
|
//
|
|
rc = FALSE;
|
|
DC_QUIT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We will inject this event (and remember when we did it
|
|
// so we don't inject the next one to quickly). Calculate
|
|
// the backlog because we may have to make up for a
|
|
// processing delay If this event is long (1000 mS) after
|
|
// our projected event time then assume a pause in movement
|
|
// and reset the backlog to avoid progressive erosion.
|
|
// Otherwise calculate the new backlog.
|
|
//
|
|
// Perf - don't reset backlog unless the time has expired.
|
|
// Restting just because we see a click means that we
|
|
// actually increase the latency by assuming that mouse
|
|
// messages queued behind the tick are not backlogged.
|
|
//
|
|
if (tick < (targetTime + 1000))
|
|
{
|
|
m_imControlledMouseBacklog += ( tick -
|
|
m_imControlledLastMouseLocalTime -
|
|
targetDelta );
|
|
}
|
|
else
|
|
{
|
|
m_imControlledMouseBacklog = 0;
|
|
TRACE_OUT(( "Non move/big gap in move"));
|
|
}
|
|
m_imControlledLastMouseRemoteTime = pNextEvent->time;
|
|
m_imControlledLastMouseLocalTime = tick;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is not a remote mouse event. Reset the
|
|
// m_imNextRemoteMouseEvent to zero so we don't hold up the next
|
|
// remote mouse event.
|
|
//
|
|
m_imControlledLastMouseRemoteTime = pNextEvent->time;
|
|
m_imControlledLastMouseLocalTime = tick;
|
|
m_imControlledMouseBacklog = 0;
|
|
TRACE_OUT(( "Local/non-paced/non-mouse - reset"));
|
|
}
|
|
|
|
//
|
|
// Only inject the event if IM_FLAG_DONT_REPLAY is not set
|
|
//
|
|
if (!(pNextEvent->flags & IM_FLAG_DONT_REPLAY))
|
|
{
|
|
//
|
|
// If the event is a mouse click then we always inject a mouse
|
|
// move event g_lpimSharedData->immediately before it to ensure that the current
|
|
// position is correct before the click is injected.
|
|
//
|
|
// This is because USER does not handle combined "move and
|
|
// click" events correctly (it appears to treat them as "click
|
|
// and move", generating a mouse move event AFTER the click
|
|
// event, rather than before). Under normal Windows operation
|
|
// it appears (from observation) that movement events and click
|
|
// events are generated separately (i.e. a click event will
|
|
// never have the movement flag set). However, incoming mouse
|
|
// click events may have positions that are different from the
|
|
// last mouse move event so we must inject the extra move event
|
|
// to keep USER happy.
|
|
//
|
|
if ( (pNextEvent->type == IM_MOUSE_EVENT) &&
|
|
(IM_MEV_BUTTON_DOWN(*pNextEvent) ||
|
|
IM_MEV_BUTTON_UP(*pNextEvent)) )
|
|
{
|
|
TRACE_OUT(( "Mouse clk: injecting extra"));
|
|
|
|
//
|
|
// Take a copy of the event.
|
|
//
|
|
mouseMoveEvent = *pNextEvent;
|
|
|
|
//
|
|
// Turn the mouse click event into a mouse move event with
|
|
// the absolute/relative flag unchanged.
|
|
//
|
|
mouseMoveEvent.event.mouse.flags &= MOUSEEVENTF_ABSOLUTE;
|
|
mouseMoveEvent.event.mouse.flags |= MOUSEEVENTF_MOVE;
|
|
|
|
//
|
|
// Inject the additional move event.
|
|
//
|
|
IMInjectEvent(&mouseMoveEvent);
|
|
|
|
//
|
|
// As the position is now correct, we turn the click into a
|
|
// relative event with an unchanged position.
|
|
//
|
|
pNextEvent->event.mouse.flags &= ~MOUSEEVENTF_ABSOLUTE;
|
|
pNextEvent->event.mouse.pt.x = 0;
|
|
pNextEvent->event.mouse.pt.y = 0;
|
|
|
|
//
|
|
// If this is a mouse down click then flag the injection
|
|
// heuristic as active. We deactivate the heuristic when
|
|
// the mouse is released so that dragging over menus can be
|
|
// done without delay. (We keep the heuristic active when
|
|
// mouse is depressed because most drawing apps perform
|
|
// freehand drawing in this way.
|
|
//
|
|
if (IM_MEV_BUTTON_DOWN(*pNextEvent))
|
|
{
|
|
TRACE_OUT(( "Injection pacing active"));
|
|
m_imfControlledPaceInjection = TRUE;
|
|
}
|
|
else
|
|
{
|
|
TRACE_OUT(( "Injection pacing inactive"));
|
|
m_imfControlledPaceInjection = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Inject the real event.
|
|
//
|
|
TRACE_OUT(( "Injecting the evnt now"));
|
|
IMInjectEvent(pNextEvent);
|
|
}
|
|
|
|
IMUpdateAsyncArray(m_aimControlledControllerKeyStates, pNextEvent);
|
|
|
|
ASSERT(m_imControlledOSQ.numEvents);
|
|
m_imControlledOSQ.numEvents--;
|
|
m_imControlledOSQ.head = CIRCULAR_INDEX(m_imControlledOSQ.head, 1,
|
|
IM_SIZE_OSQ);
|
|
|
|
//
|
|
// We only inject a single keyboard event per pass to prevent
|
|
// excessive spoiling of repeated events. Having got them here it
|
|
// seems a shame to spoil them. Spoil down to 5 so we don't get
|
|
// excessive overrun following a key repeat sequence.
|
|
//
|
|
if ((pNextEvent->type == IM_KEYBOARD_EVENT) &&
|
|
(m_imControlledOSQ.numEvents < 5))
|
|
{
|
|
TRACE_OUT(( "Keyboard event so leaving loop"));
|
|
rc = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We're done.
|
|
//
|
|
rc = FALSE;
|
|
}
|
|
|
|
DC_EXIT_POINT:
|
|
DebugExitBOOL(ASShare::IMInjectingEvents, rc);
|
|
return(rc);
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// IMMaybeInjectEvents()
|
|
//
|
|
// DESCRIPTION:
|
|
//
|
|
// This is called whenever the IM believes there may be an opportunity to
|
|
// inject more events into USER via the input event callbacks. The two
|
|
// main reasons for this are:
|
|
//
|
|
// 1. We have received a new event in the mouse or keyboard hooks. This
|
|
// will normally imply that an event has been removed from the system
|
|
// message queue so there will be at least one free slot on it.
|
|
//
|
|
// 2. We have added a new event (or events) to either the local or remote
|
|
// USER event queues. This means there will be at least one event waiting
|
|
// to be injected.
|
|
//
|
|
// This function is also called periodically (via IM_Periodic) to keep
|
|
// things moving.
|
|
//
|
|
// In order for an event to be injected there must be
|
|
//
|
|
// - an event waiting (with IM_FLAG_DONT_REPLAY reset)
|
|
// - a space on the USER system message queue
|
|
// - a new time stamp (if we are switching event sources).
|
|
//
|
|
// This function works as a state machine. It always starts in a specified
|
|
// state and will then take various actions and then possibly enter a new
|
|
// state. It continues to loop through this process until it cannot take
|
|
// any actions in one of its states at which point it returns.
|
|
//
|
|
// There are four states (each of which is further qualified by whether it
|
|
// refers to local or remote events). The states are:
|
|
//
|
|
// IM_INJECTING_EVENTS - we are injecting events into USER from the
|
|
// appropriate queue.
|
|
//
|
|
// IM_WAITING_FOR_TICK - we are waiting for a timer tick to give us a new
|
|
// timestamp before injecting events
|
|
//
|
|
// IM_DEVICE_TO_NEW_SOURCE - we are injecting fake events to bring the
|
|
// state of the keyboard and mouse (as seen by USER) into line with the
|
|
// state of the new source of input.
|
|
//
|
|
void ASShare::IMMaybeInjectEvents(void)
|
|
{
|
|
IMEVENT eventIn;
|
|
IMEVENT eventOut;
|
|
IMOSEVENT OSEvent;
|
|
BOOL replay;
|
|
UINT rcConvert;
|
|
UINT now;
|
|
HWND hwndDest;
|
|
HWND hwndParent;
|
|
POINT ptMousePos;
|
|
LPIMOSEVENT pNextEvent;
|
|
|
|
DebugEntry(IMMaybeInjectEvents);
|
|
|
|
ASSERT(m_pasLocal->m_caControlledBy);
|
|
|
|
//
|
|
// Check whether we should wait before converting events. We need to
|
|
// do this to prevent us being swamped with mouse move events when
|
|
// we're waiting for the desktop to scroll.
|
|
//
|
|
now = GetTickCount();
|
|
if (IN_TIME_RANGE(m_imControlledLastIncompleteConversion,
|
|
m_imControlledLastIncompleteConversion + IM_MIN_RECONVERSION_INTERVAL_MS, now))
|
|
{
|
|
goto IM_DISCARD;
|
|
}
|
|
|
|
//
|
|
// NOW TRANSLATE NETWORK EVENTS TO OS EVENTS
|
|
// We'll discard or inject them when the time is right.
|
|
// But don't do translation if there are still OS events left
|
|
// waiting to be injected from the previous packet.
|
|
//
|
|
if (m_imControlledEventQ.numEvents && !m_imControlledOSQ.numEvents)
|
|
{
|
|
//
|
|
// Get the event from the front of the network event queue.
|
|
//
|
|
eventIn = m_imControlledEventQ.events[0];
|
|
|
|
replay = FALSE;
|
|
switch (eventIn.type)
|
|
{
|
|
case IM_TYPE_3BUTTON:
|
|
case IM_TYPE_VK1:
|
|
case IM_TYPE_VK2:
|
|
case IM_TYPE_ASCII:
|
|
{
|
|
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);
|
|
}
|
|
|