diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /channels/rdpei | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'channels/rdpei')
-rw-r--r-- | channels/rdpei/CMakeLists.txt | 26 | ||||
-rw-r--r-- | channels/rdpei/ChannelOptions.cmake | 13 | ||||
-rw-r--r-- | channels/rdpei/client/CMakeLists.txt | 32 | ||||
-rw-r--r-- | channels/rdpei/client/rdpei_main.c | 1467 | ||||
-rw-r--r-- | channels/rdpei/client/rdpei_main.h | 89 | ||||
-rw-r--r-- | channels/rdpei/rdpei_common.c | 641 | ||||
-rw-r--r-- | channels/rdpei/rdpei_common.h | 57 | ||||
-rw-r--r-- | channels/rdpei/server/CMakeLists.txt | 32 | ||||
-rw-r--r-- | channels/rdpei/server/rdpei_main.c | 720 | ||||
-rw-r--r-- | channels/rdpei/server/rdpei_main.h | 32 |
10 files changed, 3109 insertions, 0 deletions
diff --git a/channels/rdpei/CMakeLists.txt b/channels/rdpei/CMakeLists.txt new file mode 100644 index 0000000..a93af67 --- /dev/null +++ b/channels/rdpei/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("rdpei") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif()
\ No newline at end of file diff --git a/channels/rdpei/ChannelOptions.cmake b/channels/rdpei/ChannelOptions.cmake new file mode 100644 index 0000000..d3f8743 --- /dev/null +++ b/channels/rdpei/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "rdpei" TYPE "dynamic" + DESCRIPTION "Input Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEI]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/rdpei/client/CMakeLists.txt b/channels/rdpei/client/CMakeLists.txt new file mode 100644 index 0000000..8ef4712 --- /dev/null +++ b/channels/rdpei/client/CMakeLists.txt @@ -0,0 +1,32 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("rdpei") + +set(${MODULE_PREFIX}_SRCS + rdpei_main.c + rdpei_main.h + ../rdpei_common.c + ../rdpei_common.h +) + +set(${MODULE_PREFIX}_LIBS + winpr freerdp +) +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") diff --git a/channels/rdpei/client/rdpei_main.c b/channels/rdpei/client/rdpei_main.c new file mode 100644 index 0000000..fbb255d --- /dev/null +++ b/channels/rdpei/client/rdpei_main.c @@ -0,0 +1,1467 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Input Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/thread.h> +#include <winpr/stream.h> +#include <winpr/sysinfo.h> +#include <winpr/cmdline.h> +#include <winpr/collections.h> + +#include <freerdp/addin.h> +#include <freerdp/freerdp.h> +#include <freerdp/client/channels.h> + +#include "rdpei_common.h" + +#include "rdpei_main.h" + +/** + * Touch Input + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd562197/ + * + * Windows Touch Input + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd317321/ + * + * Input: Touch injection sample + * http://code.msdn.microsoft.com/windowsdesktop/Touch-Injection-Sample-444d9bf7 + * + * Pointer Input Message Reference + * http://msdn.microsoft.com/en-us/library/hh454916/ + * + * POINTER_INFO Structure + * http://msdn.microsoft.com/en-us/library/hh454907/ + * + * POINTER_TOUCH_INFO Structure + * http://msdn.microsoft.com/en-us/library/hh454910/ + */ + +#define MAX_CONTACTS 64 +#define MAX_PEN_CONTACTS 4 + +typedef struct +{ + GENERIC_DYNVC_PLUGIN base; + + RdpeiClientContext* context; + + UINT32 version; + UINT32 features; /* SC_READY_MULTIPEN_INJECTION_SUPPORTED */ + UINT16 maxTouchContacts; + UINT64 currentFrameTime; + UINT64 previousFrameTime; + RDPINPUT_CONTACT_POINT contactPoints[MAX_CONTACTS]; + + UINT64 currentPenFrameTime; + UINT64 previousPenFrameTime; + UINT16 maxPenContacts; + RDPINPUT_PEN_CONTACT_POINT penContactPoints[MAX_PEN_CONTACTS]; + + CRITICAL_SECTION lock; + rdpContext* rdpcontext; + HANDLE thread; + HANDLE event; + BOOL running; +} RDPEI_PLUGIN; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_send_frame(RdpeiClientContext* context, RDPINPUT_TOUCH_FRAME* frame); + +#ifdef WITH_DEBUG_RDPEI +static const char* rdpei_eventid_string(UINT16 event) +{ + switch (event) + { + case EVENTID_SC_READY: + return "EVENTID_SC_READY"; + case EVENTID_CS_READY: + return "EVENTID_CS_READY"; + case EVENTID_TOUCH: + return "EVENTID_TOUCH"; + case EVENTID_SUSPEND_TOUCH: + return "EVENTID_SUSPEND_TOUCH"; + case EVENTID_RESUME_TOUCH: + return "EVENTID_RESUME_TOUCH"; + case EVENTID_DISMISS_HOVERING_CONTACT: + return "EVENTID_DISMISS_HOVERING_CONTACT"; + case EVENTID_PEN: + return "EVENTID_PEN"; + default: + return "EVENTID_UNKNOWN"; + } +} +#endif + +static RDPINPUT_CONTACT_POINT* rdpei_contact(RDPEI_PLUGIN* rdpei, INT32 externalId, BOOL active) +{ + for (UINT16 i = 0; i < rdpei->maxTouchContacts; i++) + { + RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[i]; + + if (!contactPoint->active && active) + continue; + else if (!contactPoint->active && !active) + { + contactPoint->contactId = i; + contactPoint->externalId = externalId; + contactPoint->active = TRUE; + return contactPoint; + } + else if (contactPoint->externalId == externalId) + { + return contactPoint; + } + } + return NULL; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_add_frame(RdpeiClientContext* context) +{ + RDPEI_PLUGIN* rdpei = NULL; + RDPINPUT_TOUCH_FRAME frame = { 0 }; + RDPINPUT_CONTACT_DATA contacts[MAX_CONTACTS] = { 0 }; + + if (!context || !context->handle) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)context->handle; + frame.contacts = contacts; + + for (UINT16 i = 0; i < rdpei->maxTouchContacts; i++) + { + RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[i]; + RDPINPUT_CONTACT_DATA* contact = &contactPoint->data; + + if (contactPoint->dirty) + { + contacts[frame.contactCount] = *contact; + rdpei->contactPoints[i].dirty = FALSE; + frame.contactCount++; + } + else if (contactPoint->active) + { + if (contact->contactFlags & RDPINPUT_CONTACT_FLAG_DOWN) + { + contact->contactFlags = RDPINPUT_CONTACT_FLAG_UPDATE; + contact->contactFlags |= RDPINPUT_CONTACT_FLAG_INRANGE; + contact->contactFlags |= RDPINPUT_CONTACT_FLAG_INCONTACT; + } + + contacts[frame.contactCount] = *contact; + frame.contactCount++; + } + if (contact->contactFlags & RDPINPUT_CONTACT_FLAG_UP) + { + contactPoint->active = FALSE; + contactPoint->externalId = 0; + contactPoint->contactId = 0; + } + } + + if (frame.contactCount > 0) + { + UINT error = rdpei_send_frame(context, &frame); + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rdpei_send_frame failed with error %" PRIu32 "!", error); + return error; + } + } + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_send_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s, UINT16 eventId, + UINT32 pduLength) +{ + UINT status = 0; + + if (!callback || !s || !callback->channel || !callback->channel->Write || !callback->plugin) + return ERROR_INTERNAL_ERROR; + + Stream_SetPosition(s, 0); + Stream_Write_UINT16(s, eventId); /* eventId (2 bytes) */ + Stream_Write_UINT32(s, pduLength); /* pduLength (4 bytes) */ + Stream_SetPosition(s, Stream_Length(s)); + status = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s), + NULL); +#ifdef WITH_DEBUG_RDPEI + WLog_DBG(TAG, + "rdpei_send_pdu: eventId: %" PRIu16 " (%s) length: %" PRIu32 " status: %" PRIu32 "", + eventId, rdpei_eventid_string(eventId), pduLength, status); +#endif + return status; +} + +static UINT rdpei_write_pen_frame(wStream* s, const RDPINPUT_PEN_FRAME* frame) +{ + if (!s || !frame) + return ERROR_INTERNAL_ERROR; + + if (!rdpei_write_2byte_unsigned(s, frame->contactCount)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_8byte_unsigned(s, frame->frameOffset)) + return ERROR_OUTOFMEMORY; + for (UINT16 x = 0; x < frame->contactCount; x++) + { + const RDPINPUT_PEN_CONTACT* contact = &frame->contacts[x]; + + if (!Stream_EnsureRemainingCapacity(s, 1)) + return ERROR_OUTOFMEMORY; + Stream_Write_UINT8(s, contact->deviceId); + if (!rdpei_write_2byte_unsigned(s, contact->fieldsPresent)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_4byte_signed(s, contact->x)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_4byte_signed(s, contact->y)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_4byte_unsigned(s, contact->contactFlags)) + return ERROR_OUTOFMEMORY; + if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT) + { + if (!rdpei_write_4byte_unsigned(s, contact->penFlags)) + return ERROR_OUTOFMEMORY; + } + if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT) + { + if (!rdpei_write_4byte_unsigned(s, contact->pressure)) + return ERROR_OUTOFMEMORY; + } + if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT) + { + if (!rdpei_write_2byte_unsigned(s, contact->rotation)) + return ERROR_OUTOFMEMORY; + } + if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTX_PRESENT) + { + if (!rdpei_write_2byte_signed(s, contact->tiltX)) + return ERROR_OUTOFMEMORY; + } + if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTY_PRESENT) + { + if (!rdpei_write_2byte_signed(s, contact->tiltY)) + return ERROR_OUTOFMEMORY; + } + } + return CHANNEL_RC_OK; +} + +static UINT rdpei_send_pen_event_pdu(GENERIC_CHANNEL_CALLBACK* callback, UINT32 frameOffset, + const RDPINPUT_PEN_FRAME* frames, UINT16 count) +{ + UINT status = 0; + wStream* s = NULL; + + if (!frames || (count == 0)) + return ERROR_INTERNAL_ERROR; + + s = Stream_New(NULL, 64); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Seek(s, RDPINPUT_HEADER_LENGTH); + /** + * the time that has elapsed (in milliseconds) from when the oldest touch frame + * was generated to when it was encoded for transmission by the client. + */ + rdpei_write_4byte_unsigned(s, frameOffset); /* encodeTime (FOUR_BYTE_UNSIGNED_INTEGER) */ + rdpei_write_2byte_unsigned(s, count); /* (frameCount) TWO_BYTE_UNSIGNED_INTEGER */ + + for (UINT16 x = 0; x < count; x++) + { + if ((status = rdpei_write_pen_frame(s, &frames[x]))) + { + WLog_ERR(TAG, "rdpei_write_pen_frame failed with error %" PRIu32 "!", status); + Stream_Free(s, TRUE); + return status; + } + } + Stream_SealLength(s); + + status = rdpei_send_pdu(callback, s, EVENTID_PEN, Stream_Length(s)); + Stream_Free(s, TRUE); + return status; +} + +static UINT rdpei_send_pen_frame(RdpeiClientContext* context, RDPINPUT_PEN_FRAME* frame) +{ + const UINT64 currentTime = GetTickCount64(); + RDPEI_PLUGIN* rdpei = NULL; + GENERIC_CHANNEL_CALLBACK* callback = NULL; + UINT error = 0; + + if (!context) + return ERROR_INTERNAL_ERROR; + rdpei = (RDPEI_PLUGIN*)context->handle; + if (!rdpei || !rdpei->base.listener_callback) + return ERROR_INTERNAL_ERROR; + if (!rdpei || !rdpei->rdpcontext) + return ERROR_INTERNAL_ERROR; + if (freerdp_settings_get_bool(rdpei->rdpcontext->settings, FreeRDP_SuspendInput)) + return CHANNEL_RC_OK; + + callback = rdpei->base.listener_callback->channel_callback; + /* Just ignore the event if the channel is not connected */ + if (!callback) + return CHANNEL_RC_OK; + + if (!rdpei->previousPenFrameTime && !rdpei->currentPenFrameTime) + { + rdpei->currentPenFrameTime = currentTime; + frame->frameOffset = 0; + } + else + { + rdpei->currentPenFrameTime = currentTime; + frame->frameOffset = rdpei->currentPenFrameTime - rdpei->previousPenFrameTime; + } + + if ((error = rdpei_send_pen_event_pdu(callback, frame->frameOffset, frame, 1))) + return error; + + rdpei->previousPenFrameTime = rdpei->currentPenFrameTime; + return error; +} + +static UINT rdpei_add_pen_frame(RdpeiClientContext* context) +{ + RDPEI_PLUGIN* rdpei = NULL; + RDPINPUT_PEN_FRAME penFrame = { 0 }; + RDPINPUT_PEN_CONTACT penContacts[MAX_PEN_CONTACTS] = { 0 }; + + if (!context || !context->handle) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)context->handle; + + penFrame.contacts = penContacts; + + for (UINT16 i = 0; i < rdpei->maxPenContacts; i++) + { + RDPINPUT_PEN_CONTACT_POINT* contact = &(rdpei->penContactPoints[i]); + + if (contact->dirty) + { + penContacts[penFrame.contactCount++] = contact->data; + contact->dirty = FALSE; + } + else if (contact->active) + { + if (contact->data.contactFlags & RDPINPUT_CONTACT_FLAG_DOWN) + { + contact->data.contactFlags = RDPINPUT_CONTACT_FLAG_UPDATE; + contact->data.contactFlags |= RDPINPUT_CONTACT_FLAG_INRANGE; + contact->data.contactFlags |= RDPINPUT_CONTACT_FLAG_INCONTACT; + } + + penContacts[penFrame.contactCount++] = contact->data; + } + if (contact->data.contactFlags & RDPINPUT_CONTACT_FLAG_CANCELED) + { + contact->externalId = 0; + contact->active = FALSE; + } + } + + if (penFrame.contactCount > 0) + return rdpei_send_pen_frame(context, &penFrame); + return CHANNEL_RC_OK; +} + +static UINT rdpei_update(RdpeiClientContext* context) +{ + UINT error = rdpei_add_frame(context); + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rdpei_add_frame failed with error %" PRIu32 "!", error); + return error; + } + + return rdpei_add_pen_frame(context); +} + +static DWORD WINAPI rdpei_periodic_update(LPVOID arg) +{ + DWORD status = 0; + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)arg; + UINT error = CHANNEL_RC_OK; + RdpeiClientContext* context = NULL; + + if (!rdpei) + { + error = ERROR_INVALID_PARAMETER; + goto out; + } + + context = rdpei->context; + + if (!context) + { + error = ERROR_INVALID_PARAMETER; + goto out; + } + + while (rdpei->running) + { + status = WaitForSingleObject(rdpei->event, 20); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + break; + } + + EnterCriticalSection(&rdpei->lock); + + error = rdpei_update(context); + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rdpei_add_frame failed with error %" PRIu32 "!", error); + break; + } + + if (status == WAIT_OBJECT_0) + ResetEvent(rdpei->event); + + LeaveCriticalSection(&rdpei->lock); + } + +out: + + if (error && rdpei && rdpei->rdpcontext) + setChannelError(rdpei->rdpcontext, error, "rdpei_schedule_thread reported an error"); + + if (rdpei) + rdpei->running = FALSE; + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_send_cs_ready_pdu(GENERIC_CHANNEL_CALLBACK* callback) +{ + UINT status = 0; + wStream* s = NULL; + UINT32 flags = 0; + UINT32 pduLength = 0; + RDPEI_PLUGIN* rdpei = NULL; + + if (!callback || !callback->plugin) + return ERROR_INTERNAL_ERROR; + rdpei = (RDPEI_PLUGIN*)callback->plugin; + + flags |= CS_READY_FLAGS_SHOW_TOUCH_VISUALS & rdpei->context->clientFeaturesMask; + if (rdpei->version > RDPINPUT_PROTOCOL_V10) + flags |= CS_READY_FLAGS_DISABLE_TIMESTAMP_INJECTION & rdpei->context->clientFeaturesMask; + if (rdpei->features & SC_READY_MULTIPEN_INJECTION_SUPPORTED) + flags |= CS_READY_FLAGS_ENABLE_MULTIPEN_INJECTION & rdpei->context->clientFeaturesMask; + + pduLength = RDPINPUT_HEADER_LENGTH + 10; + s = Stream_New(NULL, pduLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Seek(s, RDPINPUT_HEADER_LENGTH); + Stream_Write_UINT32(s, flags); /* flags (4 bytes) */ + Stream_Write_UINT32(s, rdpei->version); /* protocolVersion (4 bytes) */ + Stream_Write_UINT16(s, rdpei->maxTouchContacts); /* maxTouchContacts (2 bytes) */ + Stream_SealLength(s); + status = rdpei_send_pdu(callback, s, EVENTID_CS_READY, pduLength); + Stream_Free(s, TRUE); + return status; +} + +static void rdpei_print_contact_flags(UINT32 contactFlags) +{ + if (contactFlags & RDPINPUT_CONTACT_FLAG_DOWN) + WLog_DBG(TAG, " RDPINPUT_CONTACT_FLAG_DOWN"); + + if (contactFlags & RDPINPUT_CONTACT_FLAG_UPDATE) + WLog_DBG(TAG, " RDPINPUT_CONTACT_FLAG_UPDATE"); + + if (contactFlags & RDPINPUT_CONTACT_FLAG_UP) + WLog_DBG(TAG, " RDPINPUT_CONTACT_FLAG_UP"); + + if (contactFlags & RDPINPUT_CONTACT_FLAG_INRANGE) + WLog_DBG(TAG, " RDPINPUT_CONTACT_FLAG_INRANGE"); + + if (contactFlags & RDPINPUT_CONTACT_FLAG_INCONTACT) + WLog_DBG(TAG, " RDPINPUT_CONTACT_FLAG_INCONTACT"); + + if (contactFlags & RDPINPUT_CONTACT_FLAG_CANCELED) + WLog_DBG(TAG, " RDPINPUT_CONTACT_FLAG_CANCELED"); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_write_touch_frame(wStream* s, RDPINPUT_TOUCH_FRAME* frame) +{ + int rectSize = 2; + RDPINPUT_CONTACT_DATA* contact = NULL; + if (!s || !frame) + return ERROR_INTERNAL_ERROR; +#ifdef WITH_DEBUG_RDPEI + WLog_DBG(TAG, "contactCount: %" PRIu32 "", frame->contactCount); + WLog_DBG(TAG, "frameOffset: 0x%016" PRIX64 "", frame->frameOffset); +#endif + rdpei_write_2byte_unsigned(s, + frame->contactCount); /* contactCount (TWO_BYTE_UNSIGNED_INTEGER) */ + /** + * the time offset from the previous frame (in microseconds). + * If this is the first frame being transmitted then this field MUST be set to zero. + */ + rdpei_write_8byte_unsigned(s, frame->frameOffset * + 1000); /* frameOffset (EIGHT_BYTE_UNSIGNED_INTEGER) */ + + if (!Stream_EnsureRemainingCapacity(s, (size_t)frame->contactCount * 64)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT32 index = 0; index < frame->contactCount; index++) + { + contact = &frame->contacts[index]; + contact->fieldsPresent |= CONTACT_DATA_CONTACTRECT_PRESENT; + contact->contactRectLeft = contact->x - rectSize; + contact->contactRectTop = contact->y - rectSize; + contact->contactRectRight = contact->x + rectSize; + contact->contactRectBottom = contact->y + rectSize; +#ifdef WITH_DEBUG_RDPEI + WLog_DBG(TAG, "contact[%" PRIu32 "].contactId: %" PRIu32 "", index, contact->contactId); + WLog_DBG(TAG, "contact[%" PRIu32 "].fieldsPresent: %" PRIu32 "", index, + contact->fieldsPresent); + WLog_DBG(TAG, "contact[%" PRIu32 "].x: %" PRId32 "", index, contact->x); + WLog_DBG(TAG, "contact[%" PRIu32 "].y: %" PRId32 "", index, contact->y); + WLog_DBG(TAG, "contact[%" PRIu32 "].contactFlags: 0x%08" PRIX32 "", index, + contact->contactFlags); + rdpei_print_contact_flags(contact->contactFlags); +#endif + Stream_Write_UINT8(s, contact->contactId); /* contactId (1 byte) */ + /* fieldsPresent (TWO_BYTE_UNSIGNED_INTEGER) */ + rdpei_write_2byte_unsigned(s, contact->fieldsPresent); + rdpei_write_4byte_signed(s, contact->x); /* x (FOUR_BYTE_SIGNED_INTEGER) */ + rdpei_write_4byte_signed(s, contact->y); /* y (FOUR_BYTE_SIGNED_INTEGER) */ + /* contactFlags (FOUR_BYTE_UNSIGNED_INTEGER) */ + rdpei_write_4byte_unsigned(s, contact->contactFlags); + + if (contact->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT) + { + /* contactRectLeft (TWO_BYTE_SIGNED_INTEGER) */ + rdpei_write_2byte_signed(s, contact->contactRectLeft); + /* contactRectTop (TWO_BYTE_SIGNED_INTEGER) */ + rdpei_write_2byte_signed(s, contact->contactRectTop); + /* contactRectRight (TWO_BYTE_SIGNED_INTEGER) */ + rdpei_write_2byte_signed(s, contact->contactRectRight); + /* contactRectBottom (TWO_BYTE_SIGNED_INTEGER) */ + rdpei_write_2byte_signed(s, contact->contactRectBottom); + } + + if (contact->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT) + { + /* orientation (FOUR_BYTE_UNSIGNED_INTEGER) */ + rdpei_write_4byte_unsigned(s, contact->orientation); + } + + if (contact->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT) + { + /* pressure (FOUR_BYTE_UNSIGNED_INTEGER) */ + rdpei_write_4byte_unsigned(s, contact->pressure); + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_send_touch_event_pdu(GENERIC_CHANNEL_CALLBACK* callback, + RDPINPUT_TOUCH_FRAME* frame) +{ + UINT status = 0; + wStream* s = NULL; + UINT32 pduLength = 0; + RDPEI_PLUGIN* rdpei = NULL; + + WINPR_ASSERT(callback); + + rdpei = (RDPEI_PLUGIN*)callback->plugin; + if (!rdpei || !rdpei->rdpcontext) + return ERROR_INTERNAL_ERROR; + if (freerdp_settings_get_bool(rdpei->rdpcontext->settings, FreeRDP_SuspendInput)) + return CHANNEL_RC_OK; + + if (!frame) + return ERROR_INTERNAL_ERROR; + + pduLength = 64 + (frame->contactCount * 64); + s = Stream_New(NULL, pduLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Seek(s, RDPINPUT_HEADER_LENGTH); + /** + * the time that has elapsed (in milliseconds) from when the oldest touch frame + * was generated to when it was encoded for transmission by the client. + */ + rdpei_write_4byte_unsigned( + s, (UINT32)frame->frameOffset); /* encodeTime (FOUR_BYTE_UNSIGNED_INTEGER) */ + rdpei_write_2byte_unsigned(s, 1); /* (frameCount) TWO_BYTE_UNSIGNED_INTEGER */ + + if ((status = rdpei_write_touch_frame(s, frame))) + { + WLog_ERR(TAG, "rdpei_write_touch_frame failed with error %" PRIu32 "!", status); + Stream_Free(s, TRUE); + return status; + } + + Stream_SealLength(s); + pduLength = Stream_Length(s); + status = rdpei_send_pdu(callback, s, EVENTID_TOUCH, pduLength); + Stream_Free(s, TRUE); + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_recv_sc_ready_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT32 features = 0; + UINT32 protocolVersion = 0; + RDPEI_PLUGIN* rdpei = NULL; + + if (!callback || !callback->plugin) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)callback->plugin; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + Stream_Read_UINT32(s, protocolVersion); /* protocolVersion (4 bytes) */ + + if (protocolVersion >= RDPINPUT_PROTOCOL_V300) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + } + + if (Stream_GetRemainingLength(s) >= 4) + Stream_Read_UINT32(s, features); + + if (rdpei->version > protocolVersion) + rdpei->version = protocolVersion; + rdpei->features = features; +#if 0 + + if (protocolVersion != RDPINPUT_PROTOCOL_V10) + { + WLog_ERR(TAG, "Unknown [MS-RDPEI] protocolVersion: 0x%08"PRIX32"", protocolVersion); + return -1; + } + +#endif + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_recv_suspend_touch_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + RdpeiClientContext* rdpei = NULL; + + WINPR_UNUSED(s); + + if (!callback || !callback->plugin) + return ERROR_INTERNAL_ERROR; + rdpei = (RdpeiClientContext*)callback->plugin->pInterface; + if (!rdpei) + return ERROR_INTERNAL_ERROR; + + IFCALLRET(rdpei->SuspendTouch, error, rdpei); + + if (error) + WLog_ERR(TAG, "rdpei->SuspendTouch failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_recv_resume_touch_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RdpeiClientContext* rdpei = NULL; + UINT error = CHANNEL_RC_OK; + if (!s || !callback || !callback->plugin) + return ERROR_INTERNAL_ERROR; + rdpei = (RdpeiClientContext*)callback->plugin->pInterface; + if (!rdpei) + return ERROR_INTERNAL_ERROR; + + IFCALLRET(rdpei->ResumeTouch, error, rdpei); + + if (error) + WLog_ERR(TAG, "rdpei->ResumeTouch failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_recv_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT16 eventId = 0; + UINT32 pduLength = 0; + UINT error = 0; + + if (!s) + return ERROR_INTERNAL_ERROR; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 6)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, eventId); /* eventId (2 bytes) */ + Stream_Read_UINT32(s, pduLength); /* pduLength (4 bytes) */ +#ifdef WITH_DEBUG_RDPEI + WLog_DBG(TAG, "rdpei_recv_pdu: eventId: %" PRIu16 " (%s) length: %" PRIu32 "", eventId, + rdpei_eventid_string(eventId), pduLength); +#endif + + switch (eventId) + { + case EVENTID_SC_READY: + if ((error = rdpei_recv_sc_ready_pdu(callback, s))) + { + WLog_ERR(TAG, "rdpei_recv_sc_ready_pdu failed with error %" PRIu32 "!", error); + return error; + } + + if ((error = rdpei_send_cs_ready_pdu(callback))) + { + WLog_ERR(TAG, "rdpei_send_cs_ready_pdu failed with error %" PRIu32 "!", error); + return error; + } + + break; + + case EVENTID_SUSPEND_TOUCH: + if ((error = rdpei_recv_suspend_touch_pdu(callback, s))) + { + WLog_ERR(TAG, "rdpei_recv_suspend_touch_pdu failed with error %" PRIu32 "!", error); + return error; + } + + break; + + case EVENTID_RESUME_TOUCH: + if ((error = rdpei_recv_resume_touch_pdu(callback, s))) + { + WLog_ERR(TAG, "rdpei_recv_resume_touch_pdu failed with error %" PRIu32 "!", error); + return error; + } + + break; + + default: + break; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + return rdpei_recv_pdu(callback, data); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + if (callback) + { + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin; + if (rdpei && rdpei->base.listener_callback) + { + if (rdpei->base.listener_callback->channel_callback == callback) + rdpei->base.listener_callback->channel_callback = NULL; + } + } + free(callback); + return CHANNEL_RC_OK; +} + +/** + * Channel Client Interface + */ + +static UINT32 rdpei_get_version(RdpeiClientContext* context) +{ + RDPEI_PLUGIN* rdpei = NULL; + if (!context || !context->handle) + return -1; + rdpei = (RDPEI_PLUGIN*)context->handle; + return rdpei->version; +} + +static UINT32 rdpei_get_features(RdpeiClientContext* context) +{ + RDPEI_PLUGIN* rdpei = NULL; + if (!context || !context->handle) + return -1; + rdpei = (RDPEI_PLUGIN*)context->handle; + return rdpei->features; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_send_frame(RdpeiClientContext* context, RDPINPUT_TOUCH_FRAME* frame) +{ + UINT64 currentTime = GetTickCount64(); + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle; + GENERIC_CHANNEL_CALLBACK* callback = NULL; + UINT error = 0; + + callback = rdpei->base.listener_callback->channel_callback; + + /* Just ignore the event if the channel is not connected */ + if (!callback) + return CHANNEL_RC_OK; + + if (!rdpei->previousFrameTime && !rdpei->currentFrameTime) + { + rdpei->currentFrameTime = currentTime; + frame->frameOffset = 0; + } + else + { + rdpei->currentFrameTime = currentTime; + frame->frameOffset = rdpei->currentFrameTime - rdpei->previousFrameTime; + } + + if ((error = rdpei_send_touch_event_pdu(callback, frame))) + { + WLog_ERR(TAG, "rdpei_send_touch_event_pdu failed with error %" PRIu32 "!", error); + return error; + } + + rdpei->previousFrameTime = rdpei->currentFrameTime; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_add_contact(RdpeiClientContext* context, const RDPINPUT_CONTACT_DATA* contact) +{ + RDPINPUT_CONTACT_POINT* contactPoint = NULL; + RDPEI_PLUGIN* rdpei = NULL; + if (!context || !contact || !context->handle) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)context->handle; + + EnterCriticalSection(&rdpei->lock); + contactPoint = &rdpei->contactPoints[contact->contactId]; + contactPoint->data = *contact; + contactPoint->dirty = TRUE; + SetEvent(rdpei->event); + LeaveCriticalSection(&rdpei->lock); + + return CHANNEL_RC_OK; +} + +static UINT rdpei_touch_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags, + INT32 x, INT32 y, INT32* contactId, UINT32 fieldFlags, va_list ap) +{ + INT64 contactIdlocal = -1; + RDPINPUT_CONTACT_POINT* contactPoint = NULL; + RDPEI_PLUGIN* rdpei = NULL; + BOOL begin = 0; + UINT error = CHANNEL_RC_OK; + + if (!context || !contactId || !context->handle) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)context->handle; + /* Create a new contact point in an empty slot */ + EnterCriticalSection(&rdpei->lock); + begin = contactFlags & RDPINPUT_CONTACT_FLAG_DOWN; + contactPoint = rdpei_contact(rdpei, externalId, !begin); + if (contactPoint) + contactIdlocal = contactPoint->contactId; + LeaveCriticalSection(&rdpei->lock); + + if (contactIdlocal >= 0) + { + RDPINPUT_CONTACT_DATA contact = { 0 }; + contact.x = x; + contact.y = y; + contact.contactId = contactIdlocal; + contact.contactFlags = contactFlags; + contact.fieldsPresent = fieldFlags; + + if (fieldFlags & CONTACT_DATA_CONTACTRECT_PRESENT) + { + contact.contactRectLeft = va_arg(ap, INT32); + contact.contactRectTop = va_arg(ap, INT32); + contact.contactRectRight = va_arg(ap, INT32); + contact.contactRectBottom = va_arg(ap, INT32); + } + if (fieldFlags & CONTACT_DATA_ORIENTATION_PRESENT) + { + UINT32 p = va_arg(ap, UINT32); + if (p >= 360) + { + WLog_WARN(TAG, + "TouchContact %" PRIu32 ": Invalid orientation value %" PRIu32 + "degree, clamping to 359 degree", + contactIdlocal, p); + p = 359; + } + contact.orientation = p; + } + if (fieldFlags & CONTACT_DATA_PRESSURE_PRESENT) + { + UINT32 p = va_arg(ap, UINT32); + if (p > 1024) + { + WLog_WARN(TAG, + "TouchContact %" PRIu32 ": Invalid pressure value %" PRIu32 + ", clamping to 1024", + contactIdlocal, p); + p = 1024; + } + contact.pressure = p; + } + + error = context->AddContact(context, &contact); + } + + if (contactId) + *contactId = contactIdlocal; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_touch_begin(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId) +{ + UINT rc = 0; + va_list ap = { 0 }; + rc = rdpei_touch_process(context, externalId, + RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT, + x, y, contactId, 0, ap); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_touch_update(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId) +{ + UINT rc = 0; + va_list ap = { 0 }; + rc = rdpei_touch_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT, + x, y, contactId, 0, ap); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_touch_end(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId) +{ + UINT error = 0; + va_list ap = { 0 }; + error = rdpei_touch_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT, + x, y, contactId, 0, ap); + if (error != CHANNEL_RC_OK) + return error; + error = + rdpei_touch_process(context, externalId, RDPINPUT_CONTACT_FLAG_UP, x, y, contactId, 0, ap); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_touch_cancel(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId) +{ + UINT rc = 0; + va_list ap = { 0 }; + rc = rdpei_touch_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UP | RDPINPUT_CONTACT_FLAG_CANCELED, x, y, + contactId, 0, ap); + return rc; +} + +static UINT rdpei_touch_raw_event(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId, UINT32 flags, UINT32 fieldFlags, ...) +{ + UINT rc = 0; + va_list ap; + va_start(ap, fieldFlags); + rc = rdpei_touch_process(context, externalId, flags, x, y, contactId, fieldFlags, ap); + va_end(ap); + return rc; +} + +static UINT rdpei_touch_raw_event_va(RdpeiClientContext* context, INT32 externalId, INT32 x, + INT32 y, INT32* contactId, UINT32 flags, UINT32 fieldFlags, + va_list args) +{ + return rdpei_touch_process(context, externalId, flags, x, y, contactId, fieldFlags, args); +} + +static RDPINPUT_PEN_CONTACT_POINT* rdpei_pen_contact(RDPEI_PLUGIN* rdpei, INT32 externalId, + BOOL active) +{ + if (!rdpei) + return NULL; + + for (UINT32 x = 0; x < rdpei->maxPenContacts; x++) + { + RDPINPUT_PEN_CONTACT_POINT* contact = &rdpei->penContactPoints[x]; + if (active) + { + if (contact->active) + { + if (contact->externalId == externalId) + return contact; + } + } + else + { + if (!contact->active) + { + contact->externalId = externalId; + contact->active = TRUE; + return contact; + } + } + } + return NULL; +} + +static UINT rdpei_add_pen(RdpeiClientContext* context, INT32 externalId, + const RDPINPUT_PEN_CONTACT* contact) +{ + RDPEI_PLUGIN* rdpei = NULL; + RDPINPUT_PEN_CONTACT_POINT* contactPoint = NULL; + + if (!context || !contact || !context->handle) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)context->handle; + + EnterCriticalSection(&rdpei->lock); + contactPoint = rdpei_pen_contact(rdpei, externalId, TRUE); + if (contactPoint) + { + contactPoint->data = *contact; + contactPoint->dirty = TRUE; + SetEvent(rdpei->event); + } + LeaveCriticalSection(&rdpei->lock); + + return CHANNEL_RC_OK; +} + +static UINT rdpei_pen_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags, + UINT32 fieldFlags, INT32 x, INT32 y, va_list ap) +{ + RDPINPUT_PEN_CONTACT_POINT* contactPoint = NULL; + RDPEI_PLUGIN* rdpei = NULL; + UINT error = CHANNEL_RC_OK; + + if (!context || !context->handle) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)context->handle; + + EnterCriticalSection(&rdpei->lock); + // Start a new contact only when it is not active. + contactPoint = rdpei_pen_contact(rdpei, externalId, TRUE); + if (!contactPoint) + { + const UINT32 mask = RDPINPUT_CONTACT_FLAG_INRANGE; + if ((contactFlags & mask) == mask) + { + contactPoint = rdpei_pen_contact(rdpei, externalId, FALSE); + } + } + LeaveCriticalSection(&rdpei->lock); + if (contactPoint != NULL) + { + RDPINPUT_PEN_CONTACT contact = { 0 }; + + contact.x = x; + contact.y = y; + contact.fieldsPresent = fieldFlags; + + contact.contactFlags = contactFlags; + if (fieldFlags & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT) + contact.penFlags = va_arg(ap, UINT32); + if (fieldFlags & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT) + contact.pressure = va_arg(ap, UINT32); + if (fieldFlags & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT) + contact.rotation = va_arg(ap, UINT32); + if (fieldFlags & RDPINPUT_PEN_CONTACT_TILTX_PRESENT) + contact.tiltX = va_arg(ap, INT32); + if (fieldFlags & RDPINPUT_PEN_CONTACT_TILTY_PRESENT) + contact.tiltY = va_arg(ap, INT32); + + error = context->AddPen(context, externalId, &contact); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_begin(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, + INT32 x, INT32 y, ...) +{ + UINT error = 0; + va_list ap; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT, + fieldFlags, x, y, ap); + va_end(ap); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_update(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, + INT32 x, INT32 y, ...) +{ + UINT error = 0; + va_list ap; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT, + fieldFlags, x, y, ap); + va_end(ap); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_end(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, INT32 x, + INT32 y, ...) +{ + UINT error = 0; + va_list ap; + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UP | RDPINPUT_CONTACT_FLAG_INRANGE, fieldFlags, + x, y, ap); + va_end(ap); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_hover_begin(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, + INT32 x, INT32 y, ...) +{ + UINT error = 0; + va_list ap; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE, + fieldFlags, x, y, ap); + va_end(ap); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_hover_update(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, + INT32 x, INT32 y, ...) +{ + UINT error = 0; + va_list ap; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE, + fieldFlags, x, y, ap); + va_end(ap); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_hover_cancel(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, + INT32 x, INT32 y, ...) +{ + UINT error = 0; + va_list ap; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_CANCELED, + fieldFlags, x, y, ap); + va_end(ap); + + return error; +} + +static UINT rdpei_pen_raw_event(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags, + UINT32 fieldFlags, INT32 x, INT32 y, ...) +{ + UINT error = 0; + va_list ap; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, contactFlags, fieldFlags, x, y, ap); + va_end(ap); + return error; +} + +static UINT rdpei_pen_raw_event_va(RdpeiClientContext* context, INT32 externalId, + UINT32 contactFlags, UINT32 fieldFlags, INT32 x, INT32 y, + va_list args) +{ + return rdpei_pen_process(context, externalId, contactFlags, fieldFlags, x, y, args); +} + +static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings) +{ + RdpeiClientContext* context = NULL; + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)base; + + WINPR_ASSERT(base); + WINPR_UNUSED(settings); + + rdpei->version = RDPINPUT_PROTOCOL_V300; + rdpei->currentFrameTime = 0; + rdpei->previousFrameTime = 0; + rdpei->maxTouchContacts = MAX_CONTACTS; + rdpei->maxPenContacts = MAX_PEN_CONTACTS; + rdpei->rdpcontext = rcontext; + + InitializeCriticalSection(&rdpei->lock); + rdpei->event = CreateEventA(NULL, TRUE, FALSE, NULL); + if (!rdpei->event) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + context = (RdpeiClientContext*)calloc(1, sizeof(*context)); + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + context->clientFeaturesMask = UINT32_MAX; + context->handle = (void*)rdpei; + context->GetVersion = rdpei_get_version; + context->GetFeatures = rdpei_get_features; + context->AddContact = rdpei_add_contact; + context->TouchBegin = rdpei_touch_begin; + context->TouchUpdate = rdpei_touch_update; + context->TouchEnd = rdpei_touch_end; + context->TouchCancel = rdpei_touch_cancel; + context->TouchRawEvent = rdpei_touch_raw_event; + context->TouchRawEventVA = rdpei_touch_raw_event_va; + context->AddPen = rdpei_add_pen; + context->PenBegin = rdpei_pen_begin; + context->PenUpdate = rdpei_pen_update; + context->PenEnd = rdpei_pen_end; + context->PenHoverBegin = rdpei_pen_hover_begin; + context->PenHoverUpdate = rdpei_pen_hover_update; + context->PenHoverCancel = rdpei_pen_hover_cancel; + context->PenRawEvent = rdpei_pen_raw_event; + context->PenRawEventVA = rdpei_pen_raw_event_va; + + rdpei->context = context; + rdpei->base.iface.pInterface = (void*)context; + + rdpei->running = TRUE; + rdpei->thread = CreateThread(NULL, 0, rdpei_periodic_update, rdpei, 0, NULL); + if (!rdpei->thread) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + return CHANNEL_RC_OK; +} + +static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base) +{ + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)base; + WINPR_ASSERT(rdpei); + + rdpei->running = FALSE; + if (rdpei->event) + SetEvent(rdpei->event); + + if (rdpei->thread) + { + WaitForSingleObject(rdpei->thread, INFINITE); + CloseHandle(rdpei->thread); + } + + if (rdpei->event) + CloseHandle(rdpei->event); + + DeleteCriticalSection(&rdpei->lock); + free(rdpei->context); +} + +static const IWTSVirtualChannelCallback geometry_callbacks = { rdpei_on_data_received, + NULL, /* Open */ + rdpei_on_close, NULL }; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT rdpei_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, RDPEI_DVC_CHANNEL_NAME, + sizeof(RDPEI_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK), + &geometry_callbacks, init_plugin_cb, terminate_plugin_cb); +} diff --git a/channels/rdpei/client/rdpei_main.h b/channels/rdpei/client/rdpei_main.h new file mode 100644 index 0000000..34b1b81 --- /dev/null +++ b/channels/rdpei/client/rdpei_main.h @@ -0,0 +1,89 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Input Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPEI_CLIENT_MAIN_H +#define FREERDP_CHANNEL_RDPEI_CLIENT_MAIN_H + +#include <freerdp/config.h> + +#include <freerdp/dvc.h> +#include <freerdp/types.h> +#include <freerdp/addin.h> +#include <freerdp/channels/log.h> + +#include <freerdp/channels/rdpei.h> +#include <freerdp/client/rdpei.h> + +#define TAG CHANNELS_TAG("rdpei.client") + +/** + * Touch Contact State Transitions + * + * ENGAGED -> UPDATE | INRANGE | INCONTACT -> ENGAGED + * ENGAGED -> UP | INRANGE -> HOVERING + * ENGAGED -> UP -> OUT_OF_RANGE + * ENGAGED -> UP | CANCELED -> OUT_OF_RANGE + * + * HOVERING -> UPDATE | INRANGE -> HOVERING + * HOVERING -> DOWN | INRANGE | INCONTACT -> ENGAGED + * HOVERING -> UPDATE -> OUT_OF_RANGE + * HOVERING -> UPDATE | CANCELED -> OUT_OF_RANGE + * + * OUT_OF_RANGE -> DOWN | INRANGE | INCONTACT -> ENGAGED + * OUT_OF_RANGE -> UPDATE | INRANGE -> HOVERING + * + * When a contact is in the "hovering" or "engaged" state, it is referred to as being "active". + * "Hovering" contacts are in range of the digitizer, while "engaged" contacts are in range of + * the digitizer and in contact with the digitizer surface. MS-RDPEI remotes only active contacts + * and contacts that are transitioning to the "out of range" state; see section 2.2.3.3.1.1 for + * an enumeration of valid state flags combinations. + * + * When transitioning from the "engaged" state to the "hovering" state, or from the "engaged" + * state to the "out of range" state, the contact position cannot change; it is only allowed + * to change after the transition has taken place. + * + */ + +typedef struct +{ + BOOL dirty; + BOOL active; + UINT32 contactId; + INT32 externalId; + RDPINPUT_CONTACT_DATA data; +} RDPINPUT_CONTACT_POINT; + +typedef struct +{ + BOOL dirty; + BOOL active; + INT32 externalId; + RDPINPUT_PEN_CONTACT data; +} RDPINPUT_PEN_CONTACT_POINT; + +#ifdef WITH_DEBUG_DVC +#define DEBUG_DVC(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_DVC(...) \ + do \ + { \ + } while (0) +#endif + +#endif /* FREERDP_CHANNEL_RDPEI_CLIENT_MAIN_H */ diff --git a/channels/rdpei/rdpei_common.c b/channels/rdpei/rdpei_common.c new file mode 100644 index 0000000..7594672 --- /dev/null +++ b/channels/rdpei/rdpei_common.c @@ -0,0 +1,641 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Input Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2014 David Fort <contact@hardening-consulting.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/stream.h> + +#include "rdpei_common.h" + +#include <freerdp/log.h> + +#define TAG FREERDP_TAG("channels.rdpei.common") + +BOOL rdpei_read_2byte_unsigned(wStream* s, UINT16* value) +{ + BYTE byte = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, byte); + + if (byte & 0x80) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + *value = (byte & 0x7F) << 8; + Stream_Read_UINT8(s, byte); + *value |= byte; + } + else + { + *value = (byte & 0x7F); + } + + return TRUE; +} + +BOOL rdpei_write_2byte_unsigned(wStream* s, UINT16 value) +{ + BYTE byte = 0; + + if (!Stream_EnsureRemainingCapacity(s, 2)) + return FALSE; + + if (value > 0x7FFF) + return FALSE; + + if (value >= 0x7F) + { + byte = ((value & 0x7F00) >> 8); + Stream_Write_UINT8(s, byte | 0x80); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + byte = (value & 0x7F); + Stream_Write_UINT8(s, byte); + } + + return TRUE; +} + +BOOL rdpei_read_2byte_signed(wStream* s, INT16* value) +{ + BYTE byte = 0; + BOOL negative = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, byte); + + negative = (byte & 0x40) ? TRUE : FALSE; + + *value = (byte & 0x3F); + + if (byte & 0x80) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, byte); + *value = (*value << 8) | byte; + } + + if (negative) + *value *= -1; + + return TRUE; +} + +BOOL rdpei_write_2byte_signed(wStream* s, INT16 value) +{ + BYTE byte = 0; + BOOL negative = FALSE; + + if (!Stream_EnsureRemainingCapacity(s, 2)) + return FALSE; + + if (value < 0) + { + negative = TRUE; + value *= -1; + } + + if (value > 0x3FFF) + return FALSE; + + if (value >= 0x3F) + { + byte = ((value & 0x3F00) >> 8); + + if (negative) + byte |= 0x40; + + Stream_Write_UINT8(s, byte | 0x80); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + byte = (value & 0x3F); + + if (negative) + byte |= 0x40; + + Stream_Write_UINT8(s, byte); + } + + return TRUE; +} + +BOOL rdpei_read_4byte_unsigned(wStream* s, UINT32* value) +{ + BYTE byte = 0; + BYTE count = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, byte); + + count = (byte & 0xC0) >> 6; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, count)) + return FALSE; + + switch (count) + { + case 0: + *value = (byte & 0x3F); + break; + + case 1: + *value = (byte & 0x3F) << 8; + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 2: + *value = (byte & 0x3F) << 16; + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 3: + *value = (byte & 0x3F) << 24; + Stream_Read_UINT8(s, byte); + *value |= (byte << 16); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + default: + break; + } + + return TRUE; +} + +BOOL rdpei_write_4byte_unsigned(wStream* s, UINT32 value) +{ + BYTE byte = 0; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return FALSE; + + if (value <= 0x3FUL) + { + Stream_Write_UINT8(s, value); + } + else if (value <= 0x3FFFUL) + { + byte = (value >> 8) & 0x3F; + Stream_Write_UINT8(s, byte | 0x40); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x3FFFFFUL) + { + byte = (value >> 16) & 0x3F; + Stream_Write_UINT8(s, byte | 0x80); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x3FFFFFFFUL) + { + byte = (value >> 24) & 0x3F; + Stream_Write_UINT8(s, byte | 0xC0); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + return FALSE; + } + + return TRUE; +} + +BOOL rdpei_read_4byte_signed(wStream* s, INT32* value) +{ + BYTE byte = 0; + BYTE count = 0; + BOOL negative = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, byte); + + count = (byte & 0xC0) >> 6; + negative = (byte & 0x20) ? TRUE : FALSE; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, count)) + return FALSE; + + switch (count) + { + case 0: + *value = (byte & 0x1F); + break; + + case 1: + *value = (byte & 0x1F) << 8; + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 2: + *value = (byte & 0x1F) << 16; + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 3: + *value = (byte & 0x1F) << 24; + Stream_Read_UINT8(s, byte); + *value |= (byte << 16); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + default: + break; + } + + if (negative) + *value *= -1; + + return TRUE; +} + +BOOL rdpei_write_4byte_signed(wStream* s, INT32 value) +{ + BYTE byte = 0; + BOOL negative = FALSE; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return FALSE; + + if (value < 0) + { + negative = TRUE; + value *= -1; + } + + if (value <= 0x1FL) + { + byte = value & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFL) + { + byte = (value >> 8) & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte | 0x40); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFL) + { + byte = (value >> 16) & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte | 0x80); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFL) + { + byte = (value >> 24) & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte | 0xC0); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + return FALSE; + } + + return TRUE; +} + +BOOL rdpei_read_8byte_unsigned(wStream* s, UINT64* value) +{ + UINT64 byte = 0; + BYTE count = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, byte); + + count = (byte & 0xE0) >> 5; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, count)) + return FALSE; + + switch (count) + { + case 0: + *value = (byte & 0x1F); + break; + + case 1: + *value = (byte & 0x1FU) << 8U; + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 2: + *value = (byte & 0x1FU) << 16U; + Stream_Read_UINT8(s, byte); + *value |= (byte << 8U); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 3: + *value = (byte & 0x1FU) << 24U; + Stream_Read_UINT8(s, byte); + *value |= (byte << 16U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8U); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 4: + *value = ((byte & 0x1FU)) << 32U; + Stream_Read_UINT8(s, byte); + *value |= (byte << 24U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 16U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8U); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 5: + *value = ((byte & 0x1FU)) << 40U; + Stream_Read_UINT8(s, byte); + *value |= ((byte) << 32U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 24U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 16U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8U); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 6: + *value = ((byte & 0x1FU)) << 48U; + Stream_Read_UINT8(s, byte); + *value |= ((byte) << 40U); + Stream_Read_UINT8(s, byte); + *value |= ((byte) << 32U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 24U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 16U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8U); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 7: + *value = ((byte & 0x1FU)) << 56U; + Stream_Read_UINT8(s, byte); + *value |= ((byte) << 48U); + Stream_Read_UINT8(s, byte); + *value |= ((byte) << 40U); + Stream_Read_UINT8(s, byte); + *value |= ((byte) << 32U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 24U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 16U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8U); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + default: + break; + } + + return TRUE; +} + +BOOL rdpei_write_8byte_unsigned(wStream* s, UINT64 value) +{ + BYTE byte = 0; + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return FALSE; + + if (value <= 0x1FULL) + { + byte = value & 0x1F; + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFULL) + { + byte = (value >> 8) & 0x1F; + byte |= (1 << 5); + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFULL) + { + byte = (value >> 16) & 0x1F; + byte |= (2 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFULL) + { + byte = (value >> 24) & 0x1F; + byte |= (3 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFFFULL) + { + byte = (value >> 32) & 0x1F; + byte |= (4 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 24) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFFFFFULL) + { + byte = (value >> 40) & 0x1F; + byte |= (5 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 32) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 24) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFFFFFFFULL) + { + byte = (value >> 48) & 0x1F; + byte |= (6 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 40) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 32) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 24) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFFFFFFFFFULL) + { + byte = (value >> 56) & 0x1F; + byte |= (7 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 48) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 40) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 32) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 24) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + return FALSE; + } + + return TRUE; +} + +void touch_event_reset(RDPINPUT_TOUCH_EVENT* event) +{ + for (UINT16 i = 0; i < event->frameCount; i++) + touch_frame_reset(&event->frames[i]); + + free(event->frames); + event->frames = NULL; + event->frameCount = 0; +} + +void touch_frame_reset(RDPINPUT_TOUCH_FRAME* frame) +{ + free(frame->contacts); + frame->contacts = NULL; + frame->contactCount = 0; +} + +void pen_event_reset(RDPINPUT_PEN_EVENT* event) +{ + for (UINT16 i = 0; i < event->frameCount; i++) + pen_frame_reset(&event->frames[i]); + + free(event->frames); + event->frames = NULL; + event->frameCount = 0; +} + +void pen_frame_reset(RDPINPUT_PEN_FRAME* frame) +{ + free(frame->contacts); + frame->contacts = NULL; + frame->contactCount = 0; +} diff --git a/channels/rdpei/rdpei_common.h b/channels/rdpei/rdpei_common.h new file mode 100644 index 0000000..3a5362f --- /dev/null +++ b/channels/rdpei/rdpei_common.h @@ -0,0 +1,57 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Input Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2014 David Fort <contact@hardening-consulting.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPEI_COMMON_H +#define FREERDP_CHANNEL_RDPEI_COMMON_H + +#include <winpr/crt.h> +#include <winpr/stream.h> +#include <freerdp/channels/rdpei.h> + +/** @brief input event ids */ +enum +{ + EVENTID_SC_READY = 0x0001, + EVENTID_CS_READY = 0x0002, + EVENTID_TOUCH = 0x0003, + EVENTID_SUSPEND_TOUCH = 0x0004, + EVENTID_RESUME_TOUCH = 0x0005, + EVENTID_DISMISS_HOVERING_CONTACT = 0x0006, + EVENTID_PEN = 0x0008 +}; + +BOOL rdpei_read_2byte_unsigned(wStream* s, UINT16* value); +BOOL rdpei_write_2byte_unsigned(wStream* s, UINT16 value); +BOOL rdpei_read_2byte_signed(wStream* s, INT16* value); +BOOL rdpei_write_2byte_signed(wStream* s, INT16 value); +BOOL rdpei_read_4byte_unsigned(wStream* s, UINT32* value); +BOOL rdpei_write_4byte_unsigned(wStream* s, UINT32 value); +BOOL rdpei_read_4byte_signed(wStream* s, INT32* value); +BOOL rdpei_write_4byte_signed(wStream* s, INT32 value); +BOOL rdpei_read_8byte_unsigned(wStream* s, UINT64* value); +BOOL rdpei_write_8byte_unsigned(wStream* s, UINT64 value); + +void touch_event_reset(RDPINPUT_TOUCH_EVENT* event); +void touch_frame_reset(RDPINPUT_TOUCH_FRAME* frame); + +void pen_event_reset(RDPINPUT_PEN_EVENT* event); +void pen_frame_reset(RDPINPUT_PEN_FRAME* frame); + +#endif /* FREERDP_CHANNEL_RDPEI_COMMON_H */ diff --git a/channels/rdpei/server/CMakeLists.txt b/channels/rdpei/server/CMakeLists.txt new file mode 100644 index 0000000..21c96ee --- /dev/null +++ b/channels/rdpei/server/CMakeLists.txt @@ -0,0 +1,32 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2014 Thincast Technologies Gmbh. +# Copyright 2014 David FORT <contact@hardening-consulting.com> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("rdpei") + +set(${MODULE_PREFIX}_SRCS + rdpei_main.c + rdpei_main.h + ../rdpei_common.c + ../rdpei_common.h +) + +set(${MODULE_PREFIX}_LIBS + winpr +) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") diff --git a/channels/rdpei/server/rdpei_main.c b/channels/rdpei/server/rdpei_main.c new file mode 100644 index 0000000..c87a045 --- /dev/null +++ b/channels/rdpei/server/rdpei_main.c @@ -0,0 +1,720 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Extended Input channel server-side implementation + * + * Copyright 2014 Thincast Technologies Gmbh. + * Copyright 2014 David FORT <contact@hardening-consulting.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/stream.h> + +#include "rdpei_main.h" +#include "../rdpei_common.h" +#include <freerdp/channels/rdpei.h> +#include <freerdp/server/rdpei.h> + +enum RdpEiState +{ + STATE_INITIAL, + STATE_WAITING_CLIENT_READY, + STATE_WAITING_FRAME, + STATE_SUSPENDED, +}; + +struct s_rdpei_server_private +{ + HANDLE channelHandle; + HANDLE eventHandle; + + UINT32 expectedBytes; + BOOL waitingHeaders; + wStream* inputStream; + wStream* outputStream; + + UINT16 currentMsgType; + + RDPINPUT_TOUCH_EVENT touchEvent; + RDPINPUT_PEN_EVENT penEvent; + + enum RdpEiState automataState; +}; + +RdpeiServerContext* rdpei_server_context_new(HANDLE vcm) +{ + RdpeiServerContext* ret = calloc(1, sizeof(*ret)); + RdpeiServerPrivate* priv = NULL; + + if (!ret) + return NULL; + + ret->priv = priv = calloc(1, sizeof(*ret->priv)); + if (!priv) + goto fail; + + priv->inputStream = Stream_New(NULL, 256); + if (!priv->inputStream) + goto fail; + + priv->outputStream = Stream_New(NULL, 200); + if (!priv->inputStream) + goto fail; + + ret->vcm = vcm; + rdpei_server_context_reset(ret); + return ret; + +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + rdpei_server_context_free(ret); + WINPR_PRAGMA_DIAG_POP + return NULL; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_init(RdpeiServerContext* context) +{ + void* buffer = NULL; + DWORD bytesReturned = 0; + RdpeiServerPrivate* priv = context->priv; + UINT32 channelId = 0; + BOOL status = TRUE; + + priv->channelHandle = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, RDPEI_DVC_CHANNEL_NAME, + WTS_CHANNEL_OPTION_DYNAMIC); + if (!priv->channelHandle) + { + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!"); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + channelId = WTSChannelGetIdByHandle(priv->channelHandle); + + IFCALLRET(context->onChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->onChannelIdAssigned failed!"); + goto out_close; + } + + if (!WTSVirtualChannelQuery(priv->channelHandle, WTSVirtualEventHandle, &buffer, + &bytesReturned) || + (bytesReturned != sizeof(HANDLE))) + { + WLog_ERR(TAG, + "WTSVirtualChannelQuery failed or invalid invalid returned size(%" PRIu32 ")!", + bytesReturned); + if (buffer) + WTSFreeMemory(buffer); + goto out_close; + } + CopyMemory(&priv->eventHandle, buffer, sizeof(HANDLE)); + WTSFreeMemory(buffer); + + return CHANNEL_RC_OK; + +out_close: + WTSVirtualChannelClose(priv->channelHandle); + return CHANNEL_RC_INITIALIZATION_ERROR; +} + +void rdpei_server_context_reset(RdpeiServerContext* context) +{ + RdpeiServerPrivate* priv = context->priv; + + priv->channelHandle = INVALID_HANDLE_VALUE; + priv->expectedBytes = RDPINPUT_HEADER_LENGTH; + priv->waitingHeaders = TRUE; + priv->automataState = STATE_INITIAL; + Stream_SetPosition(priv->inputStream, 0); +} + +void rdpei_server_context_free(RdpeiServerContext* context) +{ + RdpeiServerPrivate* priv = NULL; + + if (!context) + return; + priv = context->priv; + if (priv) + { + if (priv->channelHandle != INVALID_HANDLE_VALUE) + WTSVirtualChannelClose(priv->channelHandle); + Stream_Free(priv->inputStream, TRUE); + } + free(priv); + free(context); +} + +HANDLE rdpei_server_get_event_handle(RdpeiServerContext* context) +{ + return context->priv->eventHandle; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_cs_ready_message(RdpeiServerContext* context, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + if (!Stream_CheckAndLogRequiredLength(TAG, s, 10)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, context->protocolFlags); + Stream_Read_UINT32(s, context->clientVersion); + Stream_Read_UINT16(s, context->maxTouchPoints); + + switch (context->clientVersion) + { + case RDPINPUT_PROTOCOL_V10: + case RDPINPUT_PROTOCOL_V101: + case RDPINPUT_PROTOCOL_V200: + case RDPINPUT_PROTOCOL_V300: + break; + default: + WLog_ERR(TAG, "unhandled RPDEI protocol version 0x%" PRIx32 "", context->clientVersion); + break; + } + + IFCALLRET(context->onClientReady, error, context); + if (error) + WLog_ERR(TAG, "context->onClientReady failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_touch_contact_data(RdpeiServerContext* context, wStream* s, + RDPINPUT_CONTACT_DATA* contactData) +{ + WINPR_UNUSED(context); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, contactData->contactId); + if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) || + !rdpei_read_4byte_signed(s, &contactData->x) || + !rdpei_read_4byte_signed(s, &contactData->y) || + !rdpei_read_4byte_unsigned(s, &contactData->contactFlags)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (contactData->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT) + { + if (!rdpei_read_2byte_signed(s, &contactData->contactRectLeft) || + !rdpei_read_2byte_signed(s, &contactData->contactRectTop) || + !rdpei_read_2byte_signed(s, &contactData->contactRectRight) || + !rdpei_read_2byte_signed(s, &contactData->contactRectBottom)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + if ((contactData->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT) && + !rdpei_read_4byte_unsigned(s, &contactData->orientation)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + if ((contactData->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT) && + !rdpei_read_4byte_unsigned(s, &contactData->pressure)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static UINT read_pen_contact(RdpeiServerContext* context, wStream* s, + RDPINPUT_PEN_CONTACT* contactData) +{ + WINPR_UNUSED(context); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, contactData->deviceId); + if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) || + !rdpei_read_4byte_signed(s, &contactData->x) || + !rdpei_read_4byte_signed(s, &contactData->y) || + !rdpei_read_4byte_unsigned(s, &contactData->contactFlags)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT) + { + if (!rdpei_read_4byte_unsigned(s, &contactData->penFlags)) + return ERROR_INVALID_DATA; + } + if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT) + { + if (!rdpei_read_4byte_unsigned(s, &contactData->pressure)) + return ERROR_INVALID_DATA; + } + if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT) + { + if (!rdpei_read_2byte_unsigned(s, &contactData->rotation)) + return ERROR_INVALID_DATA; + } + if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTX_PRESENT) + { + if (!rdpei_read_2byte_signed(s, &contactData->tiltX)) + return ERROR_INVALID_DATA; + } + if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTY_PRESENT) + { + if (!rdpei_read_2byte_signed(s, &contactData->tiltY)) + return ERROR_INVALID_DATA; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_touch_frame(RdpeiServerContext* context, wStream* s, RDPINPUT_TOUCH_FRAME* frame) +{ + RDPINPUT_CONTACT_DATA* contact = NULL; + UINT error = 0; + + if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) || + !rdpei_read_8byte_unsigned(s, &frame->frameOffset)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + frame->contacts = contact = calloc(frame->contactCount, sizeof(RDPINPUT_CONTACT_DATA)); + if (!frame->contacts) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT32 i = 0; i < frame->contactCount; i++, contact++) + { + if ((error = read_touch_contact_data(context, s, contact))) + { + WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error); + frame->contactCount = i; + touch_frame_reset(frame); + return error; + } + } + return CHANNEL_RC_OK; +} + +static UINT read_pen_frame(RdpeiServerContext* context, wStream* s, RDPINPUT_PEN_FRAME* frame) +{ + RDPINPUT_PEN_CONTACT* contact = NULL; + UINT error = 0; + + if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) || + !rdpei_read_8byte_unsigned(s, &frame->frameOffset)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + frame->contacts = contact = calloc(frame->contactCount, sizeof(RDPINPUT_PEN_CONTACT)); + if (!frame->contacts) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT32 i = 0; i < frame->contactCount; i++, contact++) + { + if ((error = read_pen_contact(context, s, contact))) + { + WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error); + frame->contactCount = i; + pen_frame_reset(frame); + return error; + } + } + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_touch_event(RdpeiServerContext* context, wStream* s) +{ + UINT16 frameCount = 0; + RDPINPUT_TOUCH_EVENT* event = &context->priv->touchEvent; + RDPINPUT_TOUCH_FRAME* frame = NULL; + UINT error = CHANNEL_RC_OK; + + if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) || + !rdpei_read_2byte_unsigned(s, &frameCount)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + event->frameCount = frameCount; + event->frames = frame = calloc(event->frameCount, sizeof(RDPINPUT_TOUCH_FRAME)); + if (!event->frames) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT32 i = 0; i < frameCount; i++, frame++) + { + if ((error = read_touch_frame(context, s, frame))) + { + WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error); + event->frameCount = i; + goto out_cleanup; + } + } + + IFCALLRET(context->onTouchEvent, error, context, event); + if (error) + WLog_ERR(TAG, "context->onTouchEvent failed with error %" PRIu32 "", error); + +out_cleanup: + touch_event_reset(event); + return error; +} + +static UINT read_pen_event(RdpeiServerContext* context, wStream* s) +{ + UINT16 frameCount = 0; + RDPINPUT_PEN_EVENT* event = &context->priv->penEvent; + RDPINPUT_PEN_FRAME* frame = NULL; + UINT error = CHANNEL_RC_OK; + + if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) || + !rdpei_read_2byte_unsigned(s, &frameCount)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + event->frameCount = frameCount; + event->frames = frame = calloc(event->frameCount, sizeof(RDPINPUT_PEN_FRAME)); + if (!event->frames) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT32 i = 0; i < frameCount; i++, frame++) + { + if ((error = read_pen_frame(context, s, frame))) + { + WLog_ERR(TAG, "read_pen_frame failed with error %" PRIu32 "!", error); + event->frameCount = i; + goto out_cleanup; + } + } + + IFCALLRET(context->onPenEvent, error, context, event); + if (error) + WLog_ERR(TAG, "context->onPenEvent failed with error %" PRIu32 "", error); + +out_cleanup: + pen_event_reset(event); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_dismiss_hovering_contact(RdpeiServerContext* context, wStream* s) +{ + BYTE contactId = 0; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, contactId); + + IFCALLRET(context->onTouchReleased, error, context, contactId); + if (error) + WLog_ERR(TAG, "context->onTouchReleased failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_handle_messages(RdpeiServerContext* context) +{ + DWORD bytesReturned = 0; + RdpeiServerPrivate* priv = context->priv; + wStream* s = priv->inputStream; + UINT error = CHANNEL_RC_OK; + + if (!WTSVirtualChannelRead(priv->channelHandle, 0, Stream_Pointer(s), priv->expectedBytes, + &bytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_READ_FAULT; + + WLog_DBG(TAG, "channel connection closed"); + return CHANNEL_RC_OK; + } + priv->expectedBytes -= bytesReturned; + Stream_Seek(s, bytesReturned); + + if (priv->expectedBytes) + return CHANNEL_RC_OK; + + Stream_SealLength(s); + Stream_SetPosition(s, 0); + + if (priv->waitingHeaders) + { + UINT32 pduLen = 0; + + /* header case */ + Stream_Read_UINT16(s, priv->currentMsgType); + Stream_Read_UINT16(s, pduLen); + + if (pduLen < RDPINPUT_HEADER_LENGTH) + { + WLog_ERR(TAG, "invalid pduLength %" PRIu32 "", pduLen); + return ERROR_INVALID_DATA; + } + priv->expectedBytes = pduLen - RDPINPUT_HEADER_LENGTH; + priv->waitingHeaders = FALSE; + Stream_SetPosition(s, 0); + if (priv->expectedBytes) + { + if (!Stream_EnsureCapacity(s, priv->expectedBytes)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + return CHANNEL_RC_OK; + } + } + + /* when here we have the header + the body */ + switch (priv->currentMsgType) + { + case EVENTID_CS_READY: + if (priv->automataState != STATE_WAITING_CLIENT_READY) + { + WLog_ERR(TAG, "not expecting a CS_READY packet in this state(%d)", + priv->automataState); + return ERROR_INVALID_STATE; + } + + if ((error = read_cs_ready_message(context, s))) + { + WLog_ERR(TAG, "read_cs_ready_message failed with error %" PRIu32 "", error); + return error; + } + break; + + case EVENTID_TOUCH: + if ((error = read_touch_event(context, s))) + { + WLog_ERR(TAG, "read_touch_event failed with error %" PRIu32 "", error); + return error; + } + break; + case EVENTID_DISMISS_HOVERING_CONTACT: + if ((error = read_dismiss_hovering_contact(context, s))) + { + WLog_ERR(TAG, "read_dismiss_hovering_contact failed with error %" PRIu32 "", error); + return error; + } + break; + case EVENTID_PEN: + if ((error = read_pen_event(context, s))) + { + WLog_ERR(TAG, "read_pen_event failed with error %" PRIu32 "", error); + return error; + } + break; + default: + WLog_ERR(TAG, "unexpected message type 0x%" PRIx16 "", priv->currentMsgType); + } + + Stream_SetPosition(s, 0); + priv->waitingHeaders = TRUE; + priv->expectedBytes = RDPINPUT_HEADER_LENGTH; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_send_sc_ready(RdpeiServerContext* context, UINT32 version, UINT32 features) +{ + ULONG written = 0; + RdpeiServerPrivate* priv = context->priv; + UINT32 pduLen = 4; + + if (priv->automataState != STATE_INITIAL) + { + WLog_ERR(TAG, "called from unexpected state %d", priv->automataState); + return ERROR_INVALID_STATE; + } + + Stream_SetPosition(priv->outputStream, 0); + + if (version >= RDPINPUT_PROTOCOL_V300) + pduLen += 4; + + if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(priv->outputStream, EVENTID_SC_READY); + Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen); + Stream_Write_UINT32(priv->outputStream, version); + if (version >= RDPINPUT_PROTOCOL_V300) + Stream_Write_UINT32(priv->outputStream, features); + + if (!WTSVirtualChannelWrite(priv->channelHandle, (PCHAR)Stream_Buffer(priv->outputStream), + Stream_GetPosition(priv->outputStream), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + priv->automataState = STATE_WAITING_CLIENT_READY; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_suspend(RdpeiServerContext* context) +{ + ULONG written = 0; + RdpeiServerPrivate* priv = context->priv; + + switch (priv->automataState) + { + case STATE_SUSPENDED: + WLog_ERR(TAG, "already suspended"); + return CHANNEL_RC_OK; + case STATE_WAITING_FRAME: + break; + default: + WLog_ERR(TAG, "called from unexpected state %d", priv->automataState); + return ERROR_INVALID_STATE; + } + + Stream_SetPosition(priv->outputStream, 0); + if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(priv->outputStream, EVENTID_SUSPEND_TOUCH); + Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH); + + if (!WTSVirtualChannelWrite(priv->channelHandle, (PCHAR)Stream_Buffer(priv->outputStream), + Stream_GetPosition(priv->outputStream), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + priv->automataState = STATE_SUSPENDED; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_resume(RdpeiServerContext* context) +{ + ULONG written = 0; + RdpeiServerPrivate* priv = context->priv; + + switch (priv->automataState) + { + case STATE_WAITING_FRAME: + WLog_ERR(TAG, "not suspended"); + return CHANNEL_RC_OK; + case STATE_SUSPENDED: + break; + default: + WLog_ERR(TAG, "called from unexpected state %d", priv->automataState); + return ERROR_INVALID_STATE; + } + + Stream_SetPosition(priv->outputStream, 0); + if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(priv->outputStream, EVENTID_RESUME_TOUCH); + Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH); + + if (!WTSVirtualChannelWrite(priv->channelHandle, (PCHAR)Stream_Buffer(priv->outputStream), + Stream_GetPosition(priv->outputStream), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + priv->automataState = STATE_WAITING_FRAME; + return CHANNEL_RC_OK; +} diff --git a/channels/rdpei/server/rdpei_main.h b/channels/rdpei/server/rdpei_main.h new file mode 100644 index 0000000..cf3e3cb --- /dev/null +++ b/channels/rdpei/server/rdpei_main.h @@ -0,0 +1,32 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Extended Input channel server-side implementation + * + * Copyright 2014 David Fort <contact@hardening-consulting.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPEI_SERVER_MAIN_H +#define FREERDP_CHANNEL_RDPEI_SERVER_MAIN_H + +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/thread.h> +#include <freerdp/channels/log.h> + +#define TAG CHANNELS_TAG("rdpei.server") + +#endif /* FREERDP_CHANNEL_RDPEI_SERVER_MAIN_H */ |