/****************************************************************************/ /* aimapi.cpp */ /* */ /* RDP Input Manager API functions */ /* */ /* Copyright(c) Microsoft, PictureTel 1993-1997 */ /* Copyright (c) 1997-1999 Microsoft Corporation */ /****************************************************************************/ #include #pragma hdrstop #define TRC_FILE "aimapi" #include #include #include /****************************************************************************/ /* API FUNCTION: IM_Init */ /* */ /* Called to initialize the IM */ /****************************************************************************/ void RDPCALL SHCLASS IM_Init(void) { TS_INPUT_CAPABILITYSET Caps; DC_BEGIN_FN("IM_Init"); #define DC_INIT_DATA #include #undef DC_INIT_DATA // Set up the input capabilities. Caps.capabilitySetType = TS_CAPSETTYPE_INPUT; Caps.lengthCapability = sizeof(Caps); Caps.inputFlags = TS_INPUT_FLAG_SCANCODES | TS_INPUT_FLAG_MOUSEX | TS_INPUT_FLAG_FASTPATH_INPUT2 | TS_INPUT_FLAG_VKPACKET; CPC_RegisterCapabilities((PTS_CAPABILITYHEADER)&Caps, sizeof(TS_INPUT_CAPABILITYSET)); TRC_NRM((TB, "IM initialized")); DC_END_FN(); } /****************************************************************************/ // IM_PlaybackEvents // // Called when an IM input PDU arrives. Unpacks and injects events. /****************************************************************************/ // Maximum number of batched mouse/keyboard events. We batch because sending // the events to the next higher driver (IcaChannelInput()) is more expensive // than the loop overhead and mispredicted branches incurred to create the // array of events. This constant is set to the same number found as // MAXIMUM_ITEMS_READ in ntos\w32\ntuser\kernel\ntinput.c. #define EventBatchLen 10 void __fastcall SHCLASS IM_PlaybackEvents( PTS_INPUT_PDU pInputPDU, unsigned DataLength) { PTS_INPUT_EVENT pInputEvent; unsigned i, j, MsgLimit; NTSTATUS Status; DC_BEGIN_FN("IM_PlaybackEvents"); /************************************************************************/ /* We do not handle NULL packets. */ /************************************************************************/ TRC_ASSERT((NULL != pInputPDU), (TB,"NULL input PDU")); /************************************************************************/ // Make sure we have at least enough bytes to read the header. Not having // any inputs in the packet is also considered an error. /************************************************************************/ if (DataLength >= sizeof(TS_INPUT_PDU)) { /********************************************************************/ // Convert the TS_INPUT_PDU from wire format. /********************************************************************/ TRC_NRM((TB, "Received packet of %u events", pInputPDU->numberEvents)); // Make sure we have the full packet length available. if (DataLength >= (sizeof(TS_INPUT_PDU) + (pInputPDU->numberEvents - 1) * sizeof(TS_INPUT_EVENT))) { // For each packet in the piggybacked packets array... for (i = 0; i < pInputPDU->numberEvents; i++) { // Get a pointer to the packet within the array of events. pInputEvent = &pInputPDU->eventList[i]; switch (pInputEvent->messageType) { case TS_INPUT_EVENT_SCANCODE: //intentional fallthru case TS_INPUT_EVENT_VKPACKET: //intentional fallthru { BYTE FastPathEmulate[4]; unsigned CurKbdData; KEYBOARD_INPUT_DATA KbdData[EventBatchLen]; MsgLimit = min((pInputPDU->numberEvents - i), EventBatchLen); CurKbdData = 0; for (j = 0; j < MsgLimit; j++) { if (pInputPDU->eventList[i + j].messageType == TS_INPUT_EVENT_SCANCODE) { // To coalesce code, we convert this kbd format // to fast-path and call the fast-path // event converter. Since fast-path is now // the default, extra work falls to this // path. FastPathEmulate[0] = (BYTE) ((pInputPDU->eventList[i + j].u.key. keyboardFlags & (TS_KBDFLAGS_EXTENDED | TS_KBDFLAGS_EXTENDED1)) >> 7); if (pInputPDU->eventList[i + j].u.key. keyboardFlags & TS_KBDFLAGS_RELEASE) FastPathEmulate[0] |= TS_INPUT_FASTPATH_KBD_RELEASE; FastPathEmulate[1] = (BYTE) pInputPDU->eventList[i + j].u.key. keyCode; // Convert the wire packet to a kernel mode // keyboard event. We pack into an array of // events because an IcaChannelInput is // expensive. if (IMConvertFastPathKeyboardToEvent( FastPathEmulate, &KbdData[CurKbdData])) { TRC_NRM((TB, "Add kbd evt to batch index " "%d: MakeCode(%u) flags(%#x)", CurKbdData, KbdData[CurKbdData].MakeCode, KbdData[CurKbdData].Flags)); CurKbdData++; } } else if (pInputPDU->eventList[i+j].messageType == TS_INPUT_EVENT_VKPACKET) { FastPathEmulate[0] = (BYTE) ((pInputPDU->eventList[i + j].u.key. keyboardFlags & (TS_KBDFLAGS_EXTENDED | TS_KBDFLAGS_EXTENDED1)) >> 7); FastPathEmulate[0] |= TS_INPUT_FASTPATH_EVENT_VKPACKET; if (pInputPDU->eventList[i + j].u.key. keyboardFlags & TS_KBDFLAGS_RELEASE) FastPathEmulate[0] |= TS_INPUT_FASTPATH_KBD_RELEASE; memcpy(&FastPathEmulate[1], &pInputPDU->eventList[i + j].u.key.keyCode, 2); // Convert the wire packet to a kernel mode // keyboard event. We pack into an array of // events because an IcaChannelInput is // expensive. if (IMConvertFastPathKeyboardToEvent( FastPathEmulate, &KbdData[CurKbdData])) { TRC_NRM((TB, "Add kbd evt to batch index " "%d: MakeCode(%u) flags(%#x)", CurKbdData, KbdData[CurKbdData].MakeCode, KbdData[CurKbdData].Flags)); CurKbdData++; } } else { break; } } // Advance past the used messages, taking into account // the outer loop increment. i += j - 1; // Now do the input. if (m_pTSWd->shadowState != SHADOW_CLIENT) { Status = IcaChannelInput(m_pTSWd->pContext, Channel_Keyboard, 0, NULL, (PUCHAR) KbdData, sizeof(KEYBOARD_INPUT_DATA) * CurKbdData); TRC_DBG((TB,"Return from keyboard input injection %lu", Status)); } // Else we must be shadowing, so blow the shadow if we // see the hotkey in this set of input. else { Status = IMCheckForShadowHotkey(KbdData, CurKbdData); } } break; case TS_INPUT_EVENT_MOUSE: case TS_INPUT_EVENT_MOUSEX: { unsigned CurMouseData; MOUSE_INPUT_DATA MouseData[EventBatchLen]; MsgLimit = min((pInputPDU->numberEvents - i), EventBatchLen); CurMouseData = 0; for (j = 0; j < MsgLimit; j++) { if ((pInputPDU->eventList[i + j].messageType == TS_INPUT_EVENT_MOUSE) || (pInputPDU->eventList[i + j].messageType == TS_INPUT_EVENT_MOUSEX)) { // Convert the wire packet to a kernel mode // mouse event. We pack into an array of // events because an IcaChannelInput is // expensive. if (IMConvertMousePacketToEvent( &pInputPDU->eventList[i + j].u.mouse, &MouseData[CurMouseData], (pInputPDU->eventList[i + j].messageType == TS_INPUT_EVENT_MOUSEX))) { TRC_NRM((TB, "Add mouse evt to batch " "index %u: x(%ld) y(%ld) flags(%#hx) buttonflags(%#hx)", CurMouseData, MouseData[CurMouseData].LastX, MouseData[CurMouseData].LastY, MouseData[CurMouseData].Flags, MouseData[CurMouseData].ButtonFlags)); CurMouseData++; } } else { break; } } // Advance past the used messages, taking into account // the outer loop increment. i += j - 1; // Now do the input. Status = IcaChannelInput(m_pTSWd->pContext, Channel_Mouse, 0, NULL, (unsigned char *)MouseData, sizeof(MOUSE_INPUT_DATA) * CurMouseData); TRC_DBG((TB,"Return from mouse input injection %lu", Status)); } break; case TS_INPUT_EVENT_SYNC: Status = IMDoSync(pInputEvent->u.sync.toggleFlags); break; default: { // Unknown event type - log an event and disconnect // the offending Client. TRC_ERR((TB, "Unrecognized imPacket (%d)", pInputEvent->messageType)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_InvalidInputPDUType, (PBYTE)&(pInputEvent->messageType), sizeof(pInputEvent->messageType)); DC_QUIT; } } } // Go into TURBO scheduling on user input to flush screen deltas // faster. SCH_ContinueScheduling(SCH_MODE_TURBO); } else { goto InsufficientData; } } else { goto InsufficientData; } DC_EXIT_POINT: DC_END_FN(); return; // Error handling. InsufficientData: TRC_ERR((TB,"Input PDU received, len=%u, but data is not long enough", DataLength)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_InputPDUBadLength, (PBYTE)pInputPDU, DataLength); DC_END_FN(); } /****************************************************************************/ // IMCheckForShadowHotkey // // Looks for the shadow hotkey among client keyboard input. /****************************************************************************/ NTSTATUS RDPCALL SHCLASS IMCheckForShadowHotkey( KEYBOARD_INPUT_DATA *pKbdData, unsigned NumData) { unsigned i; NTSTATUS Status; ICA_CHANNEL_COMMAND Data; BOOLEAN bHotKeyDetected = FALSE; DC_BEGIN_FN("IMCheckForShadowHotkey"); // Blow the shadow if we see the hotkey in this set of input. for (i = 0; i < NumData; i++) { bHotKeyDetected |= KeyboardHotKeyProcedure( m_pTSWd->HotkeyVk, m_pTSWd->HotkeyModifiers, &pKbdData[i], m_pTSWd->gpScancodeMap, m_pTSWd->pKbdTbl, m_pTSWd->KeyboardType101, m_pTSWd->pgafPhysKeyState); } if (!bHotKeyDetected) { Status = STATUS_SUCCESS; } else { m_pTSWd->HotkeyVk = 0; // cut off all piped data Data.Header.Command = ICA_COMMAND_SHADOW_HOTKEY; Status = IcaChannelInput(m_pTSWd->pContext, Channel_Command, 0, NULL, (PUCHAR)&Data, sizeof(Data)); TRC_ALT((TB,"Injected shadow HOTKEY command! status=%08X", Status)); } DC_END_FN(); return Status; } /****************************************************************************/ // IM_DecodeFastPathInput // // On a primary stack, decodes optimized input bytestream and injects into // the input stream. NumEvents is passed from MCS, decoded from the header // -- if zero, the first byte of the data to decode contains the number of // events. /****************************************************************************/ void RDPCALL SHCLASS IM_DecodeFastPathInput( BYTE *pData, unsigned DataLength, unsigned NumEvents) { unsigned i, j, MsgLimit; NTSTATUS Status; BYTE *pCurDecode = pData; DC_BEGIN_FN("IM_DecodeFastPathInput"); // Make sure we've been given enough data. if (NumEvents == 0) { if (DataLength >= 1) { NumEvents = *pData; pData++; DataLength--; } else { TRC_ERR((TB,"Len %u too short for DataLength", DataLength)); goto ShortData; } } // For each event... for (i = 0; i < NumEvents; i++) { if (DataLength >= 1) { switch (*pData & TS_INPUT_FASTPATH_EVENT_MASK) { case TS_INPUT_FASTPATH_EVENT_KEYBOARD: { unsigned CurKbdData; KEYBOARD_INPUT_DATA KbdData[EventBatchLen]; MsgLimit = min((NumEvents - i), EventBatchLen); CurKbdData = 0; for (j = 0; j < MsgLimit; j++) { if (DataLength >= 1) { if ((*pData & TS_INPUT_FASTPATH_EVENT_MASK) == TS_INPUT_FASTPATH_EVENT_KEYBOARD) { if (DataLength >= 2) { if (IMConvertFastPathKeyboardToEvent( pData, &KbdData[CurKbdData])) CurKbdData++; pData += 2; DataLength -= 2; } else { TRC_ERR((TB,"Ran out of space reading " "keyboard events")); goto ShortData; } } else { break; } } else { TRC_ERR((TB,"Ran out of space reading keyboard " "events")); goto ShortData; } } // Advance past the used messages, taking into account // the outer loop increment. i += j - 1; // Now do the input. if (m_pTSWd->shadowState != SHADOW_CLIENT) { Status = IcaChannelInput(m_pTSWd->pContext, Channel_Keyboard, 0, NULL, (PUCHAR)KbdData, sizeof(KEYBOARD_INPUT_DATA) * CurKbdData); TRC_DBG((TB,"Return from keyboard input injection %lu", Status)); } // Else we must be shadowing, so blow the shadow if we // see the hotkey in this set of input. else { Status = IMCheckForShadowHotkey(KbdData, CurKbdData); } break; } case TS_INPUT_FASTPATH_EVENT_VKPACKET: { unsigned CurKbdData; KEYBOARD_INPUT_DATA KbdData[EventBatchLen]; MsgLimit = min((NumEvents - i), EventBatchLen); CurKbdData = 0; for (j = 0; j < MsgLimit; j++) { if (DataLength >= 1) { if ((*pData & TS_INPUT_FASTPATH_EVENT_MASK) == TS_INPUT_FASTPATH_EVENT_VKPACKET) { if (DataLength >= 3) { if (IMConvertFastPathKeyboardToEvent( pData, &KbdData[CurKbdData])) CurKbdData++; pData += 3; DataLength -= 3; } else { TRC_ERR((TB,"Ran out of space reading " "keyboard events")); goto ShortData; } } else { break; } } else { TRC_ERR((TB,"Ran out of space reading keyboard " "events")); goto ShortData; } } // Advance past the used messages, taking into account // the outer loop increment. i += j - 1; // Now do the input. if (m_pTSWd->shadowState != SHADOW_CLIENT) { Status = IcaChannelInput(m_pTSWd->pContext, Channel_Keyboard, 0, NULL, (PUCHAR)KbdData, sizeof(KEYBOARD_INPUT_DATA) * CurKbdData); TRC_DBG((TB,"Return from keyboard input injection %lu", Status)); } break; } case TS_INPUT_FASTPATH_EVENT_MOUSE: case TS_INPUT_FASTPATH_EVENT_MOUSEX: { unsigned CurMouseData; MOUSE_INPUT_DATA MouseData[EventBatchLen]; // After the 1-byte header the following 6 bytes are // the same format as a regular mouse input. MsgLimit = min((NumEvents - i), EventBatchLen); CurMouseData = 0; for (j = 0; j < MsgLimit; j++) { if (DataLength >= 1) { if ((((*pData & TS_INPUT_FASTPATH_EVENT_MASK) == TS_INPUT_FASTPATH_EVENT_MOUSE) || (*pData & TS_INPUT_FASTPATH_EVENT_MASK) == TS_INPUT_FASTPATH_EVENT_MOUSEX)) { if (DataLength >= 7) { // Convert the wire packet to a kernel // mode mouse event. We pack into an // array of events because an // IcaChannelInput is expensive. if (IMConvertMousePacketToEvent( (TS_POINTER_EVENT UNALIGNED *) (pData + 1), &MouseData[CurMouseData], ((*pData & TS_INPUT_FASTPATH_EVENT_MASK) == TS_INPUT_FASTPATH_EVENT_MOUSEX))) CurMouseData++; pData += 7; DataLength -= 7; } else { TRC_ERR((TB,"Out of data decoding " "mouse, i=%u, j=%u, NumEvents=%u, " "DataLen=%u", i, j, NumEvents, DataLength)); goto ShortData; } } else { break; } } else { TRC_ERR((TB,"Out of data decoding " "mouse, i=%u, j=%u, NumEvents=%u, " "DataLen=%u", i, j, NumEvents, DataLength)); goto ShortData; } } // Advance past the used messages, taking into account // the outer loop increment. i += j - 1; // Now do the input. Status = IcaChannelInput(m_pTSWd->pContext, Channel_Mouse, 0, NULL, (unsigned char *)MouseData, sizeof(MOUSE_INPUT_DATA) * CurMouseData); TRC_DBG((TB,"Return from mouse input injection %lu", Status)); break; } case TS_INPUT_FASTPATH_EVENT_SYNC: Status = IMDoSync(*pData & TS_INPUT_FASTPATH_FLAGS_MASK); pData++; DataLength--; break; default: // Unknown event type - log an event and disconnect // the offending Client. TRC_ERR((TB, "Unrecognized imPacket (%d)", *pData & TS_INPUT_FASTPATH_EVENT_MASK)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_InvalidInputPDUType, pData, 1); DC_QUIT; } } else { TRC_ERR((TB,"Out of data reading input events")); goto ShortData; } } // end event loop // Go into TURBO scheduling on user input to flush screen deltas // faster. SCH_ContinueScheduling(SCH_MODE_TURBO); DC_EXIT_POINT: DC_END_FN(); return; ShortData: WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_InputPDUBadLength, (PBYTE)pData, DataLength); DC_END_FN(); } /****************************************************************************/ // IM_ConvertFastPathToShadow // // Inverse of the client IHTranslateInputToFastPath() function -- takes // a fast-path input stream and converts to the regular encoding. Used // by a passthru stack to send the resulting regular encoding over the // cross-server pipe via IcaRawInput(). /****************************************************************************/ #define MaxDefaultEvents 16 void RDPCALL SHCLASS IM_ConvertFastPathToShadow( BYTE *pData, unsigned DataLength, unsigned NumEvents) { unsigned i, j, EventsThisPDU, PDUSize; NTSTATUS Status; PTS_INPUT_PDU pInput; BYTE DefaultBuf[sizeof(TS_INPUT_PDU) + sizeof(TS_INPUT_EVENT) * (MaxDefaultEvents - 1)]; DC_BEGIN_FN("IM_ConvertFastPathToShadow"); // Make sure we've been given enough data. if (NumEvents == 0) { if (DataLength >= 1) { NumEvents = *pData; pData++; DataLength--; } else { TRC_ERR((TB,"Len %u too short for DataLength", DataLength)); goto ShortData; } } // We don't alloc memory, just send multiple input PDUs if we need to. if (NumEvents > 0) { pInput = (PTS_INPUT_PDU)DefaultBuf; // set the input pdu array to 0. memset(pInput, 0, sizeof(TS_INPUT_PDU) + sizeof(TS_INPUT_EVENT) * (MaxDefaultEvents - 1)); } else { DC_QUIT; } // Set up the input PDU header info that won't be changing. // Shadow handling does not care about the following, so we don't go to // thr trouble of making up or grabbing values: // shareDataHeader.shareControlHeader.pduSource pInput->shareDataHeader.shareControlHeader.pduType = TS_PROTOCOL_VERSION | TS_PDUTYPE_DATAPDU; pInput->shareDataHeader.shareID = scShareID; pInput->shareDataHeader.streamID = TS_STREAM_LOW; pInput->shareDataHeader.pduType2 = TS_PDUTYPE2_INPUT; // Loop while we need to send more PDUs. for (j = 0; j < NumEvents;) { // Reset the input PDU info. EventsThisPDU = min(NumEvents - j, MaxDefaultEvents); pInput->numberEvents = (TSUINT16)EventsThisPDU; PDUSize = sizeof(TS_INPUT_PDU) + sizeof(TS_INPUT_EVENT) * (EventsThisPDU - 1); pInput->shareDataHeader.shareControlHeader.totalLength = (TSUINT16)PDUSize; pInput->shareDataHeader.uncompressedLength = (TSUINT16)PDUSize; // For each event... for (i = 0; i < EventsThisPDU; i++) { if (DataLength >= 1) { switch (*pData & TS_INPUT_FASTPATH_EVENT_MASK) { case TS_INPUT_FASTPATH_EVENT_KEYBOARD: if (DataLength >= 2) { // Use a mask, shift, and OR to avoid branches for the // extended flags. pInput->eventList[i].messageType = TS_INPUT_EVENT_SCANCODE; pInput->eventList[i].u.key.keyboardFlags = (*pData & (BYTE)( TS_INPUT_FASTPATH_KBD_EXTENDED | TS_INPUT_FASTPATH_KBD_EXTENDED1)) << 7; if (*pData & TS_INPUT_FASTPATH_KBD_RELEASE) pInput->eventList[i].u.key.keyboardFlags |= TS_KBDFLAGS_RELEASE; pInput->eventList[i].u.key.keyCode = pData[1]; pData += 2; DataLength -= 2; } else { goto ShortData; } break; case TS_INPUT_FASTPATH_EVENT_VKPACKET: if (DataLength >= 3) { // Use a mask, shift, and OR to avoid branches for the // extended flags. pInput->eventList[i].messageType = TS_INPUT_EVENT_VKPACKET; pInput->eventList[i].u.key.keyboardFlags = (*pData & (BYTE)( TS_INPUT_FASTPATH_KBD_EXTENDED | TS_INPUT_FASTPATH_KBD_EXTENDED1)) << 7; if (*pData & TS_INPUT_FASTPATH_KBD_RELEASE) pInput->eventList[i].u.key.keyboardFlags |= TS_KBDFLAGS_RELEASE; memcpy(&pInput->eventList[i].u.key.keyCode, &pData[1], 2); TRC_NRM((TB,"Shadow pass: 0x%x flags:0x%x\n", pInput->eventList[i].u.key.keyCode, pInput->eventList[i].u.key.keyboardFlags)); pData += 3; DataLength -= 3; } else { goto ShortData; } break; case TS_INPUT_FASTPATH_EVENT_MOUSE: case TS_INPUT_FASTPATH_EVENT_MOUSEX: if (DataLength >= 7) { pInput->eventList[i].messageType = ((*pData & TS_INPUT_FASTPATH_EVENT_MASK) == TS_INPUT_FASTPATH_EVENT_MOUSE ? TS_INPUT_EVENT_MOUSE : TS_INPUT_EVENT_MOUSEX); memcpy(&pInput->eventList[i].u.mouse, pData + 1, sizeof(TS_POINTER_EVENT)); pData += 7; DataLength -= 7; } else { goto ShortData; } break; case TS_INPUT_FASTPATH_EVENT_SYNC: pInput->eventList[i].messageType = TS_INPUT_EVENT_SYNC; pInput->eventList[i].u.sync.toggleFlags = (*pData & (BYTE)TS_INPUT_FASTPATH_FLAGS_MASK); pData++; DataLength--; break; default: TRC_ERR((TB, "Unrecognized imPacket (%d)", *pData & TS_INPUT_FASTPATH_EVENT_MASK)); DC_QUIT; } } else { TRC_ERR((TB,"Out of data reading input events")); goto ShortData; } } // end event loop j += i; // Launch the PDU. TRC_NRM((TB, "Forwarding shadow data: %ld", DataLength)); Status = IcaRawInput(m_pTSWd->pContext, NULL, (BYTE *)pInput, PDUSize); if (!NT_SUCCESS(Status)) { TRC_ERR((TB, "Failed shadow input data [%ld]: %x", DataLength, Status)); } } DC_EXIT_POINT: DC_END_FN(); return; ShortData: TRC_ERR((TB,"Short PDU during passthru translation")); DC_END_FN(); } /****************************************************************************/ // IM_CheckUpdateCursor // // Called during output processing to check to see if we need to send // a mouse-moved packet to the client. /****************************************************************************/ void RDPCALL SHCLASS IM_CheckUpdateCursor( PPDU_PACKAGE_INFO pPkgInfo, UINT32 currentTime) { PPOINTL pCursorPos; UINT32 timeDelta; DC_BEGIN_FN("IM_CheckUpdateCursor"); // Check to see if the cursor has moved since last time we came // through - if not, then there's no point in doing any of the // following tests! if (!CM_CursorMoved()) { TRC_DBG((TB, "No move since last time through")); DC_QUIT; } // Get the current cursor position - we always need this. pCursorPos = CM_GetCursorPos(); // Check to see if the mouse has been moved at the display driver level // yet - don't do anything until it has to avoid the mouse leaping to 0,0 // on connection if (pCursorPos->x != 0xffffffff) { // Check to see if the cursor is hidden - we should do nothing // here if it is. In particular, the 'real' cursor is hidden // during dragging of a single file and a 'fake' is drawn (by // Explorer?). Ignoring the fact that it is hidden causes the // 'faked' cursor to keep leaping back to where the drag started! if (CM_IsCursorVisible()) { timeDelta = currentTime - imLastLowLevelMouseEventTime; TRC_NRM((TB, "SetCursorPos (%d:%d) lastEvent:%#lx " "delta:%#lx", pCursorPos->x, pCursorPos->y, imLastLowLevelMouseEventTime, timeDelta)); CM_SendCursorMovedPacket(pPkgInfo); } else { TRC_NRM((TB, "Cursor hidden - skipping")); } } else { TRC_NRM((TB, "No mouse updates rec'd from client - not moving")); } // Clear the cursor moved flag. CM_ClearCursorMoved(); DC_EXIT_POINT: DC_END_FN(); } /****************************************************************************/ /* API FUNCTION: IM_PartyJoiningShare */ /* */ /* Called by SC when a new party is joining the share */ /* */ /* PARAMETERS: */ /* personID - the local ID of the new party. */ /* oldShareSize - the number of the parties which were in the share (ie */ /* excludes the joining party). */ /* */ /* RETURNS: */ /* TRUE - the IM can accept the new party */ /* FALSE - the IM cannot accept the new party */ /****************************************************************************/ BOOL RDPCALL SHCLASS IM_PartyJoiningShare( LOCALPERSONID personID, unsigned oldShareSize) { BOOL rc = FALSE; PTS_INPUT_CAPABILITYSET pIMCaps; DC_BEGIN_FN("IM_PartyJoiningShare"); DC_IGNORE_PARAMETER(oldShareSize) // One-time init for each new share. if (oldShareSize == 0) { KEYBOARD_INDICATOR_PARAMETERS kip = {0}; SD_IOCTL sdIoctl; // The keys will initially all be up. memset(imKeyStates, 0, sizeof(imKeyStates)); // Reset when we last saw a low level mouse event. COM_GETTICKCOUNT(imLastLowLevelMouseEventTime); // Get the toggle key states. sdIoctl.IoControlCode = IOCTL_KEYBOARD_QUERY_INDICATORS; sdIoctl.InputBuffer = NULL; sdIoctl.InputBufferLength = 0; sdIoctl.OutputBuffer = &kip; sdIoctl.OutputBufferLength = sizeof(KEYBOARD_INDICATOR_PARAMETERS); sdIoctl.BytesReturned = 0; if (WDW_QueryKeyboardIndicators(m_pTSWd, &sdIoctl) == STATUS_SUCCESS) { TRC_NRM((TB, "Got toggle key states ok")); imKeyStates[IM_SC_CAPITAL] = kip.LedFlags & KEYBOARD_CAPS_LOCK_ON; imKeyStates[IM_SC_NUMLOCK] = kip.LedFlags & KEYBOARD_NUM_LOCK_ON; imKeyStates[IM_SC_SCROLL] = kip.LedFlags & KEYBOARD_SCROLL_LOCK_ON; } TRC_NRM((TB, "Toggle key states: Caps:%s, Num:%s, Scroll:%s", (imKeyStates[IM_SC_CAPITAL] & 0x01) ? "ON" : "OFF", (imKeyStates[IM_SC_NUMLOCK] & 0x01) ? "ON" : "OFF", (imKeyStates[IM_SC_SCROLL] & 0x01) ? "ON" : "OFF")); } // Make sure scancodes are supported by client. pIMCaps = (PTS_INPUT_CAPABILITYSET) CPC_GetCapabilitiesForPerson(personID, TS_CAPSETTYPE_INPUT); if (pIMCaps != NULL && pIMCaps->inputFlags & TS_INPUT_FLAG_SCANCODES) { rc = TRUE; } else { TRC_ERR((TB, "Rejecting join from [%u]: has no scancode support", personID)); } DC_END_FN(); return rc; } /****************************************************************************/ /* API FUNCTION: IM_PartyLeftShare */ /* */ /* Called when a party has left the share. */ /* */ /* PARAMETERS: */ /* personID - the local ID of the new party. */ /* newShareSize - the number of the parties now in the share (ie excludes */ /* the leaving party). */ /****************************************************************************/ void RDPCALL SHCLASS IM_PartyLeftShare( LOCALPERSONID personID, unsigned newShareSize) { DC_BEGIN_FN("IM_PartyLeftShare"); if (newShareSize == 0) { // Need to make sure we set all keys up, just in case we were // shadowing a console session. if (m_pTSWd->StackClass == Stack_Shadow) IMResetKeyStateArray(); } DC_END_FN(); } /****************************************************************************/ /* FUNCTION: IMConvertMousePacketToEvent */ /* */ /* Converts the TS_INPUT_EVENT format to a MOUSE_INPUT_DATA OS format */ /* */ /* PARAMETERS: */ /* pInputEvent - the TS_INPUT_EVENT to be converted */ /* pMouseData - the MOUSE_INPUT_DATA to modify */ /* */ /* RETURNS: */ /* TRUE if the packet has been recognised and converted */ /* FALSE if the packet was not recognised */ /****************************************************************************/ BOOL __fastcall SHCLASS IMConvertMousePacketToEvent( TS_POINTER_EVENT UNALIGNED *pInputEvent, MOUSE_INPUT_DATA *pMouseData, BOOL bMouseX) { BOOL rc = TRUE; DC_BEGIN_FN("IMConvertMousePacketToEvent"); /************************************************************************/ /* Set all the fields to zero */ /************************************************************************/ memset(pMouseData, 0, sizeof(MOUSE_INPUT_DATA)); // Check for a wheel rotate, since this is easy to process. // (It cannot include any mouse movement as well). // MouseX events are not used for wheel events. if (!bMouseX && (pInputEvent->pointerFlags & TS_FLAG_MOUSE_WHEEL)) { if (!(pInputEvent->pointerFlags & (TS_FLAG_MOUSE_BUTTON1 | TS_FLAG_MOUSE_BUTTON2 | TS_FLAG_MOUSE_BUTTON3))) { /****************************************************************/ /* This is a wheel movement. */ /****************************************************************/ pMouseData->ButtonFlags = MOUSE_WHEEL; pMouseData->ButtonData = pInputEvent->pointerFlags & TS_FLAG_MOUSE_ROTATION_MASK; /****************************************************************/ /* Sign extend the rotation amount up to the full 32 */ /* bits */ /****************************************************************/ if (pMouseData->ButtonData & TS_FLAG_MOUSE_DIRECTION) { pMouseData->ButtonData |= ~TS_FLAG_MOUSE_ROTATION_MASK; } } DC_QUIT; } /************************************************************************/ /* We are left now with non wheel-rotate events. Note that we could be */ /* dealing with either a TS_INPUT_EVENT_MOUSE or a */ /* TS_INPUT_EVENT_MOUSEX. Either way we must store the mouse position. */ /************************************************************************/ pMouseData->LastX = min( (int)(m_desktopWidth - 1), (int)(max(0, pInputEvent->x)) ); pMouseData->LastY = min( (int)(m_desktopHeight - 1), (int)(max(0, pInputEvent->y)) ); /************************************************************************/ /* Add flags as appropriate. */ /************************************************************************/ /************************************************************************/ /* Make all submitted events absolute moves (both clicks and moves) */ /************************************************************************/ pMouseData->Flags = MOUSE_MOVE_ABSOLUTE | MOUSE_VIRTUAL_DESKTOP; // // Set the flags to indicate if this move is originating // from a shadow client // if (m_pTSWd->StackClass == Stack_Shadow ) { // this event is coming from a shadow client pMouseData->Flags |= MOUSE_TERMSRV_SRC_SHADOW; } /************************************************************************/ /* Set click flags for click events (i.e. non-move events) */ /************************************************************************/ if (!(!bMouseX && (pInputEvent->pointerFlags & TS_FLAG_MOUSE_MOVE))) { if (!bMouseX) { /****************************************************************/ /* A standard mouse event */ /****************************************************************/ switch (pInputEvent->pointerFlags & (TS_FLAG_MOUSE_BUTTON1 | TS_FLAG_MOUSE_BUTTON2 | TS_FLAG_MOUSE_BUTTON3 | TS_FLAG_MOUSE_DOWN)) { case TS_FLAG_MOUSE_BUTTON1 | TS_FLAG_MOUSE_DOWN: { pMouseData->ButtonFlags = MOUSE_BUTTON_1_DOWN; // Update the key state array. IM_SET_KEY_DOWN(imKeyStates[IM_SC_LBUTTON]); } break; case TS_FLAG_MOUSE_BUTTON1: { pMouseData->ButtonFlags = MOUSE_BUTTON_1_UP; if (IM_KEY_STATE_IS_UP(imKeyStates[IM_SC_LBUTTON])) { /********************************************************/ /* Discard unmatched mouse button up event */ /********************************************************/ TRC_NRM((TB, "discard mouse up event")); rc = FALSE; DC_QUIT; } // Update the key state array. IM_SET_KEY_UP(imKeyStates[IM_SC_LBUTTON]); } break; case TS_FLAG_MOUSE_BUTTON2 | TS_FLAG_MOUSE_DOWN: { pMouseData->ButtonFlags = MOUSE_BUTTON_2_DOWN; // Update the key state array. IM_SET_KEY_DOWN(imKeyStates[IM_SC_RBUTTON]); } break; case TS_FLAG_MOUSE_BUTTON2: { pMouseData->ButtonFlags = MOUSE_BUTTON_2_UP; if (IM_KEY_STATE_IS_UP(imKeyStates[IM_SC_RBUTTON])) { /********************************************************/ /* Discard unmatched mouse button up event */ /********************************************************/ TRC_NRM((TB, "discard mouse up event")); rc = FALSE; DC_QUIT; } // Update the key state array. IM_SET_KEY_UP(imKeyStates[IM_SC_RBUTTON]); } break; case TS_FLAG_MOUSE_BUTTON3 | TS_FLAG_MOUSE_DOWN: { pMouseData->ButtonFlags = MOUSE_BUTTON_3_DOWN; IM_SET_KEY_DOWN(imKeyStates[IM_SC_MBUTTON]); } break; case TS_FLAG_MOUSE_BUTTON3: { pMouseData->ButtonFlags = MOUSE_BUTTON_3_UP; if (IM_KEY_STATE_IS_UP(imKeyStates[IM_SC_MBUTTON])) { /********************************************************/ /* Discard unmatched mouse button up event */ /********************************************************/ TRC_NRM((TB, "discard mouse up event")); rc = FALSE; DC_QUIT; } IM_SET_KEY_UP(imKeyStates[IM_SC_MBUTTON]); } break; default: { /************************************************************/ /* If we don't recognise this then don't play it back. This */ /* should not be possible according to the T.128 spec, */ /* which restricts the allowed flag combinations to the */ /* above */ /************************************************************/ TRC_ERR((TB, "Unrecognized mouse flags (%04X)", pInputEvent->pointerFlags)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_InvalidInputPDUMouse, (PBYTE)pInputEvent, sizeof(PTS_INPUT_EVENT)); rc = FALSE; DC_QUIT; } } } else { /****************************************************************/ /* An extended mouse event */ /****************************************************************/ switch (pInputEvent->pointerFlags & (TS_FLAG_MOUSEX_BUTTON1 | TS_FLAG_MOUSEX_BUTTON2 | TS_FLAG_MOUSEX_DOWN)) { case TS_FLAG_MOUSEX_BUTTON1 | TS_FLAG_MOUSEX_DOWN: { pMouseData->ButtonFlags = MOUSE_BUTTON_4_DOWN; // Update the key state array. IM_SET_KEY_DOWN(imKeyStates[IM_SC_XBUTTON1]); } break; case TS_FLAG_MOUSEX_BUTTON1: { pMouseData->ButtonFlags = MOUSE_BUTTON_4_UP; if (IM_KEY_STATE_IS_UP(imKeyStates[IM_SC_XBUTTON1])) { /********************************************************/ /* Discard unmatched mouse button up event */ /********************************************************/ TRC_NRM((TB, "discard mouse up event")); rc = FALSE; DC_QUIT; } // Update the key state array. IM_SET_KEY_UP(imKeyStates[IM_SC_XBUTTON1]); } break; case TS_FLAG_MOUSEX_BUTTON2 | TS_FLAG_MOUSEX_DOWN: { pMouseData->ButtonFlags = MOUSE_BUTTON_5_DOWN; // Update the key state array. IM_SET_KEY_DOWN(imKeyStates[IM_SC_XBUTTON2]); } break; case TS_FLAG_MOUSEX_BUTTON2: { pMouseData->ButtonFlags = MOUSE_BUTTON_5_UP; if (IM_KEY_STATE_IS_UP(imKeyStates[IM_SC_XBUTTON2])) { /********************************************************/ /* Discard unmatched mouse button up event */ /********************************************************/ TRC_NRM((TB, "discard mouse up event")); rc = FALSE; DC_QUIT; } // Update the key state array. IM_SET_KEY_UP(imKeyStates[IM_SC_XBUTTON2]); } break; default: { /********************************************************/ /* As for standard button clicks, if we don't recognise */ /* this then don't play it back. Capabilities should */ /* protect us from getting here. */ /********************************************************/ TRC_ERR((TB, "Unrecognized mouseX flags (%04X)", pInputEvent->pointerFlags)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_InvalidInputPDUMouse, (PBYTE)pInputEvent, sizeof(PTS_INPUT_EVENT)); rc = FALSE; DC_QUIT; } } } } /************************************************************************/ /* Store the injection time for guessing at SetCursorPos calls. */ /************************************************************************/ COM_GETTICKCOUNT(imLastLowLevelMouseEventTime); /************************************************************************/ /* Store the mouse position before conversion. */ /************************************************************************/ imLastKnownMousePos.x = pMouseData->LastX; imLastKnownMousePos.y = pMouseData->LastY; /************************************************************************/ /* Scale the logical screen co-ordinates to the full 16-bit */ /* range (0..65535). */ /************************************************************************/ TRC_DBG((TB, "Scale absolute mouse move")); pMouseData->LastX = IM_MOUSEPOS_LOG_TO_OS_ABS(pMouseData->LastX, m_desktopWidth); pMouseData->LastY = IM_MOUSEPOS_LOG_TO_OS_ABS(pMouseData->LastY, m_desktopHeight); DC_EXIT_POINT: DC_END_FN(); return rc; } /****************************************************************************/ // IMConvertFastPathKeyboardToEvent // // Converts the 2 or 3 byte representation of a fast-path keyboard event into // a kernel keyboard event, taking care to save the key states. Byte 0 is // the event code and flags byte, byte 1 is the scancode. // In the case where this is a VK_PACKET input bytes 1 and 2 are the unicode // character (contained in the scancode). // // This handles input of the form // TS_INPUT_FASTPATH_EVENT_KEYBOARD and // TS_INPUT_FASTPATH_EVENT_VKPACKET // /****************************************************************************/ BOOL __fastcall SHCLASS IMConvertFastPathKeyboardToEvent( BYTE *pData, KEYBOARD_INPUT_DATA *pKbdData) { BOOL rc = TRUE; unsigned code = 0; BOOL fHandlingVKPacket = FALSE; DC_BEGIN_FN("IMConvertFastPathKeyboardToEvent"); // Set up basic params. // We define the fastpath keyboard flags to be the same as the KEY_BREAK, // KEY_E0, and KEY_E1 to allow us to simply copy the low-order 3 bits of // the first byte into the KbdData.Flags field. pKbdData->Flags = *pData & 0x07; pKbdData->UnitId = 0; if (TS_INPUT_FASTPATH_EVENT_KEYBOARD == (*pData & TS_INPUT_FASTPATH_EVENT_MASK)) { code = pKbdData->MakeCode = pData[1]; } else if (TS_INPUT_FASTPATH_EVENT_VKPACKET == (*pData & TS_INPUT_FASTPATH_EVENT_MASK)) { fHandlingVKPacket = TRUE; // Scancode is a 2 byte unicode char in this case memcpy(&code, &pData[1], 2); pKbdData->MakeCode = (USHORT)code; pKbdData->Flags |= KEY_TERMSRV_VKPACKET; } pKbdData->ExtraInformation = 0; if (m_pTSWd->StackClass == Stack_Shadow ) { // this event is coming from a shadow client: tell the target to sync pKbdData->Flags |= KEY_TERMSRV_SHADOW; } if (fHandlingVKPacket) { TRC_NRM((TB,"IH VKpkt Unicode val: 0x%x flags:0x%x\n", code, pKbdData->Flags)); //No further processing DC_QUIT; } // Special case control/ALT keys: distinguish L & R keys in the keystate // array. if (pData[0] & TS_INPUT_FASTPATH_KBD_EXTENDED) { if (pData[1] == IM_SC_LCONTROL) code = IM_SC_RCONTROL; else if (pData[1] == IM_SC_LALT) code = IM_SC_RALT; } // Check for the RELEASE flag, which is TRUE for a key release, FALSE // for keypresses and repeats. if (pData[0] & TS_INPUT_FASTPATH_KBD_RELEASE) { #ifdef DELETE_UNMATCHED_KEYUPS // Check whether this is an unmatched key up event (rather than // a key event that's not been matched up!) if (IM_KEY_STATE_IS_UP(imKeyStates[pData[1]])) { // Discard unmatched key up event TRC_NRM((TB, "discard up event %04hX", pData[1])); rc = FALSE; DC_QUIT; } #endif // Update the key state array. TRC_DBG((TB,"set sc %u state UP (%#x)", code, imKeyStates[code])); IM_SET_KEY_UP(imKeyStates[code]); } else { // Update the key state array. TRC_DBG((TB,"set sc %u state DOWN (%#x)", code, imKeyStates[code])); IM_SET_KEY_DOWN(imKeyStates[code]); } // Compile-time assertions to make sure the flags are okay. #if (TS_INPUT_FASTPATH_KBD_RELEASE != KEY_BREAK) #error TS RELEASE definition doesn't agree with driver flag #endif #if (TS_INPUT_FASTPATH_KBD_EXTENDED != KEY_E0) #error TS EXTENDED definition doesn't agree with driver flag #endif #if (TS_INPUT_FASTPATH_KBD_EXTENDED1 != KEY_E1) #error TS EXTENDED1 definition doesn't agree with driver flag #endif DC_EXIT_POINT: DC_END_FN(); return rc; } /****************************************************************************/ // IMDoSync // // Encapsulates the actions for a sync for common use by regular and // fastpath input. /****************************************************************************/ NTSTATUS RDPCALL SHCLASS IMDoSync(unsigned ToggleFlags) { NTSTATUS Status; KEYBOARD_INPUT_DATA KbdData; DC_BEGIN_FN("IMDoSync"); // We just need to reset the key state. IMResetKeyStateArray(); // Send special "reset keylight state" injection to win32k. // The particular states to set are contained in ToggleFlags. KbdData.MakeCode = 0xFF; #ifdef _HYDRA_ KbdData.Flags = KEY_TERMSRV_SET_LED; #else KbdData.Flags = KEY_CITRIX_SET_LED; #endif if (m_pTSWd->StackClass == Stack_Shadow ) { // this event is coming from a shadow client: tell the target to sync KbdData.Flags |= KEY_TERMSRV_SHADOW; } KbdData.ExtraInformation = ToggleFlags; TRC_NRM((TB, "Injecting toggle keys sync event %lx", ToggleFlags)); Status = IcaChannelInput(m_pTSWd->pContext, Channel_Keyboard, 0, NULL, (unsigned char *)&KbdData, sizeof(KEYBOARD_INPUT_DATA)); TRC_DBG((TB, "Return from toggles input injection %lu", Status)); DC_END_FN(); return Status; } /****************************************************************************/ /* FUNCTION: IMResetKeyStateArray */ /* */ /* Called to reset the keystate array */ /****************************************************************************/ void RDPCALL SHCLASS IMResetKeyStateArray() { BOOL rc = TRUE; unsigned i; NTSTATUS Status; DC_BEGIN_FN("IMResetKeyStateArray"); /************************************************************************/ /* This function is called to reset all keys to a known state */ /* (up) before resetting the keys with new states. */ /************************************************************************/ /************************************************************************/ /* Loop through all keys looking for any that are not in a neutral */ /* state. In this case any key that is in a KEY_DOWN state is not */ /* considered neutral. */ /************************************************************************/ for (i = 0; i < IM_KEY_STATE_SIZE; i++) { if (IM_KEY_STATE_IS_DOWN(imKeyStates[i])) { TRC_NRM((TB, "Key is down %u", i)); /****************************************************************/ /* Handle the mouse buttons first */ /****************************************************************/ if ((i == IM_SC_LBUTTON) || (i == IM_SC_RBUTTON) || (i == IM_SC_MBUTTON) || (i == IM_SC_XBUTTON1) || (i == IM_SC_XBUTTON2)) { MOUSE_INPUT_DATA MouseData; /************************************************************/ /* Generate a mouse event with the particular button type */ /* and a relative mouse move of zero. */ /************************************************************/ memset(&MouseData, 0, sizeof(MOUSE_INPUT_DATA)); if (i == IM_SC_LBUTTON) { MouseData.ButtonFlags = MOUSE_LEFT_BUTTON_UP; } else if (i == IM_SC_RBUTTON) { MouseData.ButtonFlags = MOUSE_RIGHT_BUTTON_UP; } else if (i == IM_SC_MBUTTON) { MouseData.ButtonFlags = MOUSE_MIDDLE_BUTTON_UP; } else if (i == IM_SC_XBUTTON1) { MouseData.ButtonFlags = MOUSE_BUTTON_4_UP; } else /* IM_SC_XBUTTON2 */ { MouseData.ButtonFlags = MOUSE_BUTTON_5_UP; } /************************************************************/ /* Store the injection time for guessing at SetCursorPos */ /* calls. */ /************************************************************/ COM_GETTICKCOUNT(imLastLowLevelMouseEventTime); TRC_NRM((TB, "Inject mouse event: x(%ld) y(%ld) flags(%#hx)" "buttonFlags(%#hx)", MouseData.LastX, MouseData.LastY, MouseData.Flags, MouseData.ButtonFlags)); Status = IcaChannelInput(m_pTSWd->pContext, Channel_Mouse, 0, NULL, (unsigned char *)&MouseData, sizeof(MOUSE_INPUT_DATA)); TRC_DBG((TB, "Return from mouse input injection %lu", Status)); } else { KEYBOARD_INPUT_DATA KbdData; /************************************************************/ /* Generate a keyboard key up event */ /************************************************************/ KbdData.UnitId = 0; if (i == IM_SC_RCONTROL) { KbdData.Flags = KEY_BREAK | KEY_E0; KbdData.MakeCode = IM_SC_LCONTROL; } else if (i == IM_SC_RALT) { KbdData.Flags = KEY_BREAK | KEY_E0; KbdData.MakeCode = IM_SC_LALT; } else { KbdData.Flags = KEY_BREAK; KbdData.MakeCode = (unsigned short)i; } KbdData.Reserved = 0; KbdData.ExtraInformation = 0; TRC_NRM((TB, "Inject keybd event: make code (%u) flags(%#x)", KbdData.MakeCode, KbdData.Flags)); Status = IcaChannelInput(m_pTSWd->pContext, Channel_Keyboard, 0, NULL, (unsigned char *)&KbdData, sizeof(KEYBOARD_INPUT_DATA)); TRC_DBG((TB, "Return from keyboard input injection %lu", Status)); } } } // Set all keys up. memset((PVOID)imKeyStates, 0, IM_KEY_STATE_SIZE); DC_END_FN(); }