From 687ed017d3b61b1d574b7c94e9d400e0a21dc4c2 Mon Sep 17 00:00:00 2001 From: Pascal Nowack Date: Sun, 22 Oct 2023 11:38:48 +0200 Subject: [PATCH] server: Add channel handling for mouse cursor channel ([MS-RDPEMSC]) The mouse cursor channel enables remoting of the mouse cursor (bitmap) over a DVC. The main use case is UDP, as only virtual channels can be transported via UDP in RDP. --- channels/rdpemsc/CMakeLists.txt | 22 + channels/rdpemsc/ChannelOptions.cmake | 12 + channels/rdpemsc/server/CMakeLists.txt | 28 + channels/rdpemsc/server/mouse_cursor_main.c | 723 ++++++++++++++++++++ channels/server/channels.c | 12 + include/config/config.h.in | 3 + include/freerdp/channels/rdpemsc.h | 135 ++++ include/freerdp/server/rdpemsc.h | 130 ++++ 8 files changed, 1065 insertions(+) create mode 100644 channels/rdpemsc/CMakeLists.txt create mode 100644 channels/rdpemsc/ChannelOptions.cmake create mode 100644 channels/rdpemsc/server/CMakeLists.txt create mode 100644 channels/rdpemsc/server/mouse_cursor_main.c create mode 100644 include/freerdp/channels/rdpemsc.h create mode 100644 include/freerdp/server/rdpemsc.h diff --git a/channels/rdpemsc/CMakeLists.txt b/channels/rdpemsc/CMakeLists.txt new file mode 100644 index 000000000..0b5f46bb5 --- /dev/null +++ b/channels/rdpemsc/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2023 Pascal Nowack +# +# 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("rdpemsc") + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/rdpemsc/ChannelOptions.cmake b/channels/rdpemsc/ChannelOptions.cmake new file mode 100644 index 000000000..d89f37ed4 --- /dev/null +++ b/channels/rdpemsc/ChannelOptions.cmake @@ -0,0 +1,12 @@ + +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "rdpemsc" TYPE "dynamic" + DESCRIPTION "Mouse Cursor Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEMSC]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/rdpemsc/server/CMakeLists.txt b/channels/rdpemsc/server/CMakeLists.txt new file mode 100644 index 000000000..3384d70db --- /dev/null +++ b/channels/rdpemsc/server/CMakeLists.txt @@ -0,0 +1,28 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2023 Pascal Nowack +# +# 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("rdpemsc") + +set(${MODULE_PREFIX}_SRCS + mouse_cursor_main.c +) + +set(${MODULE_PREFIX}_LIBS + freerdp +) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") diff --git a/channels/rdpemsc/server/mouse_cursor_main.c b/channels/rdpemsc/server/mouse_cursor_main.c new file mode 100644 index 000000000..30d3c79ef --- /dev/null +++ b/channels/rdpemsc/server/mouse_cursor_main.c @@ -0,0 +1,723 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Mouse Cursor Virtual Channel Extension + * + * Copyright 2023 Pascal Nowack + * + * 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 + +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpemsc.server") + +typedef enum +{ + MOUSE_CURSOR_INITIAL, + MOUSE_CURSOR_OPENED, +} eMouseCursorChannelState; + +typedef struct +{ + MouseCursorServerContext context; + + HANDLE stopEvent; + + HANDLE thread; + void* mouse_cursor_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eMouseCursorChannelState state; + + wStream* buffer; +} mouse_cursor_server; + +static UINT mouse_cursor_server_initialize(MouseCursorServerContext* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + + WINPR_ASSERT(mouse_cursor); + + if (mouse_cursor->isOpened) + { + WLog_WARN(TAG, "Application error: Mouse Cursor channel already initialized, " + "calling in this state is not possible!"); + return ERROR_INVALID_STATE; + } + + mouse_cursor->externalThread = externalThread; + + return error; +} + +static UINT mouse_cursor_server_open_channel(mouse_cursor_server* mouse_cursor) +{ + MouseCursorServerContext* context; + DWORD Error = ERROR_SUCCESS; + DWORD BytesReturned = 0; + PULONG pSessionId = NULL; + UINT32 channelId; + BOOL status = TRUE; + + WINPR_ASSERT(mouse_cursor); + context = &mouse_cursor->context; + WINPR_ASSERT(context); + + if (WTSQuerySessionInformationA(mouse_cursor->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + mouse_cursor->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + + mouse_cursor->mouse_cursor_channel = WTSVirtualChannelOpenEx( + mouse_cursor->SessionId, RDPEMSC_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC); + if (!mouse_cursor->mouse_cursor_channel) + { + Error = GetLastError(); + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error); + return Error; + } + + channelId = WTSChannelGetIdByHandle(mouse_cursor->mouse_cursor_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + return Error; +} + +static BOOL read_cap_set(wStream* s, wArrayList* capsSets) +{ + RDP_MOUSE_CURSOR_CAPSET* capsSet = NULL; + UINT32 signature; + RDP_MOUSE_CURSOR_CAPVERSION version; + UINT32 size; + size_t capsDataSize; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return FALSE; + + Stream_Read_UINT32(s, signature); + Stream_Read_UINT32(s, version); + Stream_Read_UINT32(s, size); + + if (size < 12) + { + WLog_ERR(TAG, "Size of caps set is invalid: %u", size); + return FALSE; + } + + capsDataSize = size - 12; + if (!Stream_CheckAndLogRequiredLength(TAG, s, capsDataSize)) + return FALSE; + + switch (version) + { + case RDP_MOUSE_CURSOR_CAPVERSION_1: + { + RDP_MOUSE_CURSOR_CAPSET_VERSION1* capsSetV1; + + capsSetV1 = calloc(1, sizeof(RDP_MOUSE_CURSOR_CAPSET_VERSION1)); + if (!capsSetV1) + return FALSE; + + capsSet = (RDP_MOUSE_CURSOR_CAPSET*)capsSetV1; + break; + } + default: + WLog_WARN(TAG, "Received caps set with unknown version %u", version); + Stream_Seek(s, capsDataSize); + return TRUE; + } + WINPR_ASSERT(capsSet); + + capsSet->signature = signature; + capsSet->version = version; + capsSet->size = size; + + if (!ArrayList_Append(capsSets, capsSet)) + { + WLog_ERR(TAG, "Failed to append caps set to arraylist"); + free(capsSet); + return FALSE; + } + + return TRUE; +} + +static UINT mouse_cursor_server_recv_cs_caps_advertise(MouseCursorServerContext* context, + wStream* s, + const RDP_MOUSE_CURSOR_HEADER* header) +{ + RDP_MOUSE_CURSOR_CAPS_ADVERTISE_PDU pdu = { 0 }; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + pdu.header = *header; + + /* There must be at least one capability set present */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_NO_DATA; + + pdu.capsSets = ArrayList_New(FALSE); + if (!pdu.capsSets) + { + WLog_ERR(TAG, "Failed to allocate arraylist"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + wObject* aobj = ArrayList_Object(pdu.capsSets); + WINPR_ASSERT(aobj); + aobj->fnObjectFree = free; + + while (Stream_GetRemainingLength(s) > 0) + { + if (!read_cap_set(s, pdu.capsSets)) + { + ArrayList_Free(pdu.capsSets); + return ERROR_INVALID_DATA; + } + } + + IFCALLRET(context->CapsAdvertise, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->CapsAdvertise failed with error %" PRIu32 "", error); + + ArrayList_Free(pdu.capsSets); + + return error; +} + +static UINT mouse_cursor_process_message(mouse_cursor_server* mouse_cursor) +{ + BOOL rc; + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned; + RDP_MOUSE_CURSOR_HEADER header = { 0 }; + wStream* s; + + WINPR_ASSERT(mouse_cursor); + WINPR_ASSERT(mouse_cursor->mouse_cursor_channel); + + s = mouse_cursor->buffer; + WINPR_ASSERT(s); + + Stream_SetPosition(s, 0); + rc = WTSVirtualChannelRead(mouse_cursor->mouse_cursor_channel, 0, NULL, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 1) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(mouse_cursor->mouse_cursor_channel, 0, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + Stream_SetLength(s, BytesReturned); + if (!Stream_CheckAndLogRequiredLength(TAG, s, RDPEMSC_HEADER_SIZE)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, header.pduType); + Stream_Read_UINT8(s, header.updateType); + Stream_Read_UINT16(s, header.reserved); + + switch (header.pduType) + { + case PDUTYPE_CS_CAPS_ADVERTISE: + error = mouse_cursor_server_recv_cs_caps_advertise(&mouse_cursor->context, s, &header); + break; + default: + WLog_ERR(TAG, "mouse_cursor_process_message: unknown or invalid pduType %" PRIu8 "", + header.pduType); + break; + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +static UINT mouse_cursor_server_context_poll_int(MouseCursorServerContext* context) +{ + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(mouse_cursor); + + switch (mouse_cursor->state) + { + case MOUSE_CURSOR_INITIAL: + error = mouse_cursor_server_open_channel(mouse_cursor); + if (error) + WLog_ERR(TAG, "mouse_cursor_server_open_channel failed with error %" PRIu32 "!", + error); + else + mouse_cursor->state = MOUSE_CURSOR_OPENED; + break; + case MOUSE_CURSOR_OPENED: + error = mouse_cursor_process_message(mouse_cursor); + break; + } + + return error; +} + +static HANDLE mouse_cursor_server_get_channel_handle(mouse_cursor_server* mouse_cursor) +{ + void* buffer = NULL; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = NULL; + + WINPR_ASSERT(mouse_cursor); + + if (WTSVirtualChannelQuery(mouse_cursor->mouse_cursor_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI mouse_cursor_server_thread_func(LPVOID arg) +{ + DWORD nCount; + HANDLE events[2] = { 0 }; + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status; + + WINPR_ASSERT(mouse_cursor); + + nCount = 0; + events[nCount++] = mouse_cursor->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (mouse_cursor->state) + { + case MOUSE_CURSOR_INITIAL: + error = mouse_cursor_server_context_poll_int(&mouse_cursor->context); + if (error == CHANNEL_RC_OK) + { + events[1] = mouse_cursor_server_get_channel_handle(mouse_cursor); + nCount = 2; + } + break; + case MOUSE_CURSOR_OPENED: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + error = mouse_cursor_server_context_poll_int(&mouse_cursor->context); + break; + + case WAIT_FAILED: + default: + error = ERROR_INTERNAL_ERROR; + break; + } + break; + } + } + + WTSVirtualChannelClose(mouse_cursor->mouse_cursor_channel); + mouse_cursor->mouse_cursor_channel = NULL; + + if (error && mouse_cursor->context.rdpcontext) + setChannelError(mouse_cursor->context.rdpcontext, error, + "mouse_cursor_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static UINT mouse_cursor_server_open(MouseCursorServerContext* context) +{ + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + + WINPR_ASSERT(mouse_cursor); + + if (!mouse_cursor->externalThread && (mouse_cursor->thread == NULL)) + { + mouse_cursor->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!mouse_cursor->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + mouse_cursor->thread = + CreateThread(NULL, 0, mouse_cursor_server_thread_func, mouse_cursor, 0, NULL); + if (!mouse_cursor->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(mouse_cursor->stopEvent); + mouse_cursor->stopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + } + mouse_cursor->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +static UINT mouse_cursor_server_close(MouseCursorServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + + WINPR_ASSERT(mouse_cursor); + + if (!mouse_cursor->externalThread && mouse_cursor->thread) + { + SetEvent(mouse_cursor->stopEvent); + + if (WaitForSingleObject(mouse_cursor->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + CloseHandle(mouse_cursor->thread); + CloseHandle(mouse_cursor->stopEvent); + mouse_cursor->thread = NULL; + mouse_cursor->stopEvent = NULL; + } + if (mouse_cursor->externalThread) + { + if (mouse_cursor->state != MOUSE_CURSOR_INITIAL) + { + WTSVirtualChannelClose(mouse_cursor->mouse_cursor_channel); + mouse_cursor->mouse_cursor_channel = NULL; + mouse_cursor->state = MOUSE_CURSOR_INITIAL; + } + } + mouse_cursor->isOpened = FALSE; + + return error; +} + +static UINT mouse_cursor_server_context_poll(MouseCursorServerContext* context) +{ + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + + WINPR_ASSERT(mouse_cursor); + + if (!mouse_cursor->externalThread) + return ERROR_INTERNAL_ERROR; + + return mouse_cursor_server_context_poll_int(context); +} + +static BOOL mouse_cursor_server_context_handle(MouseCursorServerContext* context, HANDLE* handle) +{ + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + + WINPR_ASSERT(mouse_cursor); + WINPR_ASSERT(handle); + + if (!mouse_cursor->externalThread) + return FALSE; + if (mouse_cursor->state == MOUSE_CURSOR_INITIAL) + return FALSE; + + *handle = mouse_cursor_server_get_channel_handle(mouse_cursor); + + return TRUE; +} + +static wStream* mouse_cursor_server_packet_new(size_t size, RDP_MOUSE_CURSOR_PDUTYPE pduType, + const RDP_MOUSE_CURSOR_HEADER* header) +{ + wStream* s; + + /* Allocate what we need plus header bytes */ + s = Stream_New(NULL, size + RDPEMSC_HEADER_SIZE); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return NULL; + } + + Stream_Write_UINT8(s, pduType); + Stream_Write_UINT8(s, header->updateType); + Stream_Write_UINT16(s, header->reserved); + + return s; +} + +static UINT mouse_cursor_server_packet_send(MouseCursorServerContext* context, wStream* s) +{ + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + UINT error = CHANNEL_RC_OK; + ULONG written; + + WINPR_ASSERT(mouse_cursor); + WINPR_ASSERT(s); + + if (!WTSVirtualChannelWrite(mouse_cursor->mouse_cursor_channel, (PCHAR)Stream_Buffer(s), + Stream_GetPosition(s), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(s)); + } + +out: + Stream_Free(s, TRUE); + return error; +} + +static UINT +mouse_cursor_server_send_sc_caps_confirm(MouseCursorServerContext* context, + const RDP_MOUSE_CURSOR_CAPS_CONFIRM_PDU* capsConfirm) +{ + RDP_MOUSE_CURSOR_CAPSET* capsetHeader; + RDP_MOUSE_CURSOR_PDUTYPE pduType; + size_t caps_size; + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(capsConfirm); + + capsetHeader = capsConfirm->capsSet; + WINPR_ASSERT(capsetHeader); + + caps_size = 12; + switch (capsetHeader->version) + { + case RDP_MOUSE_CURSOR_CAPVERSION_1: + break; + default: + WINPR_ASSERT(FALSE); + break; + } + + pduType = PDUTYPE_SC_CAPS_CONFIRM; + s = mouse_cursor_server_packet_new(caps_size, pduType, &capsConfirm->header); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT32(s, capsetHeader->signature); + Stream_Write_UINT32(s, capsetHeader->version); + Stream_Write_UINT32(s, capsetHeader->size); + + /* Write capsData */ + switch (capsetHeader->version) + { + case RDP_MOUSE_CURSOR_CAPVERSION_1: + break; + default: + WINPR_ASSERT(FALSE); + break; + } + + return mouse_cursor_server_packet_send(context, s); +} + +static void write_point16(wStream* s, const TS_POINT16* point16) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(point16); + + Stream_Write_UINT16(s, point16->xPos); + Stream_Write_UINT16(s, point16->yPos); +} + +static UINT mouse_cursor_server_send_sc_mouseptr_update( + MouseCursorServerContext* context, const RDP_MOUSE_CURSOR_MOUSEPTR_UPDATE_PDU* mouseptrUpdate) +{ + TS_POINT16* position; + TS_POINTERATTRIBUTE* pointerAttribute; + TS_LARGEPOINTERATTRIBUTE* largePointerAttribute; + RDP_MOUSE_CURSOR_PDUTYPE pduType; + size_t update_size = 0; + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(mouseptrUpdate); + + position = mouseptrUpdate->position; + pointerAttribute = mouseptrUpdate->pointerAttribute; + largePointerAttribute = mouseptrUpdate->largePointerAttribute; + + switch (mouseptrUpdate->header.updateType) + { + case TS_UPDATETYPE_MOUSEPTR_SYSTEM_NULL: + case TS_UPDATETYPE_MOUSEPTR_SYSTEM_DEFAULT: + update_size = 0; + break; + case TS_UPDATETYPE_MOUSEPTR_POSITION: + WINPR_ASSERT(position); + update_size = 4; + break; + case TS_UPDATETYPE_MOUSEPTR_CACHED: + WINPR_ASSERT(mouseptrUpdate->cachedPointerIndex); + update_size = 2; + break; + case TS_UPDATETYPE_MOUSEPTR_POINTER: + WINPR_ASSERT(pointerAttribute); + update_size = 2 + 2 + 4 + 2 + 2 + 2 + 2; + update_size += pointerAttribute->lengthAndMask; + update_size += pointerAttribute->lengthXorMask; + break; + case TS_UPDATETYPE_MOUSEPTR_LARGE_POINTER: + WINPR_ASSERT(largePointerAttribute); + update_size = 2 + 2 + 4 + 2 + 2 + 4 + 4; + update_size += largePointerAttribute->lengthAndMask; + update_size += largePointerAttribute->lengthXorMask; + break; + default: + WINPR_ASSERT(FALSE); + break; + } + + pduType = PDUTYPE_SC_MOUSEPTR_UPDATE; + s = mouse_cursor_server_packet_new(update_size, pduType, &mouseptrUpdate->header); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + switch (mouseptrUpdate->header.updateType) + { + case TS_UPDATETYPE_MOUSEPTR_SYSTEM_NULL: + case TS_UPDATETYPE_MOUSEPTR_SYSTEM_DEFAULT: + break; + case TS_UPDATETYPE_MOUSEPTR_POSITION: + write_point16(s, position); + break; + case TS_UPDATETYPE_MOUSEPTR_CACHED: + Stream_Write_UINT16(s, *mouseptrUpdate->cachedPointerIndex); + break; + case TS_UPDATETYPE_MOUSEPTR_POINTER: + Stream_Write_UINT16(s, pointerAttribute->xorBpp); + Stream_Write_UINT16(s, pointerAttribute->cacheIndex); + write_point16(s, &pointerAttribute->hotSpot); + Stream_Write_UINT16(s, pointerAttribute->width); + Stream_Write_UINT16(s, pointerAttribute->height); + Stream_Write_UINT16(s, pointerAttribute->lengthAndMask); + Stream_Write_UINT16(s, pointerAttribute->lengthXorMask); + Stream_Write(s, pointerAttribute->xorMaskData, pointerAttribute->lengthXorMask); + Stream_Write(s, pointerAttribute->andMaskData, pointerAttribute->lengthAndMask); + break; + case TS_UPDATETYPE_MOUSEPTR_LARGE_POINTER: + Stream_Write_UINT16(s, largePointerAttribute->xorBpp); + Stream_Write_UINT16(s, largePointerAttribute->cacheIndex); + write_point16(s, &largePointerAttribute->hotSpot); + Stream_Write_UINT16(s, largePointerAttribute->width); + Stream_Write_UINT16(s, largePointerAttribute->height); + Stream_Write_UINT32(s, largePointerAttribute->lengthAndMask); + Stream_Write_UINT32(s, largePointerAttribute->lengthXorMask); + Stream_Write(s, largePointerAttribute->xorMaskData, + largePointerAttribute->lengthXorMask); + Stream_Write(s, largePointerAttribute->andMaskData, + largePointerAttribute->lengthAndMask); + break; + default: + WINPR_ASSERT(FALSE); + break; + } + + return mouse_cursor_server_packet_send(context, s); +} + +MouseCursorServerContext* mouse_cursor_server_context_new(HANDLE vcm) +{ + mouse_cursor_server* mouse_cursor = + (mouse_cursor_server*)calloc(1, sizeof(mouse_cursor_server)); + + if (!mouse_cursor) + return NULL; + + mouse_cursor->context.vcm = vcm; + mouse_cursor->context.Initialize = mouse_cursor_server_initialize; + mouse_cursor->context.Open = mouse_cursor_server_open; + mouse_cursor->context.Close = mouse_cursor_server_close; + mouse_cursor->context.Poll = mouse_cursor_server_context_poll; + mouse_cursor->context.ChannelHandle = mouse_cursor_server_context_handle; + + mouse_cursor->context.CapsConfirm = mouse_cursor_server_send_sc_caps_confirm; + mouse_cursor->context.MouseptrUpdate = mouse_cursor_server_send_sc_mouseptr_update; + + mouse_cursor->buffer = Stream_New(NULL, 4096); + if (!mouse_cursor->buffer) + goto fail; + + return &mouse_cursor->context; +fail: + mouse_cursor_server_context_free(&mouse_cursor->context); + return NULL; +} + +void mouse_cursor_server_context_free(MouseCursorServerContext* context) +{ + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + + if (mouse_cursor) + { + mouse_cursor_server_close(context); + Stream_Free(mouse_cursor->buffer, TRUE); + } + + free(mouse_cursor); +} diff --git a/channels/server/channels.c b/channels/server/channels.c index f1f923f8e..be1d82ac2 100644 --- a/channels/server/channels.c +++ b/channels/server/channels.c @@ -54,6 +54,10 @@ #include #include +#if defined(CHANNEL_RDPEMSC_SERVER) +#include +#endif /* CHANNEL_RDPEMSC_SERVER */ + #if defined(CHANNEL_RDPECAM_SERVER) #include #include @@ -90,6 +94,9 @@ void freerdp_channels_dummy(void) TelemetryServerContext* telemetry; RdpgfxServerContext* rdpgfx; DispServerContext* disp; +#if defined(CHANNEL_RDPEMSC_SERVER) + MouseCursorServerContext* mouse_cursor; +#endif /* CHANNEL_RDPEMSC_SERVER */ #if defined(CHANNEL_RDPECAM_SERVER) CamDevEnumServerContext* camera_enumerator; CameraDeviceServerContext* camera_device; @@ -131,6 +138,11 @@ void freerdp_channels_dummy(void) disp = disp_server_context_new(NULL); disp_server_context_free(disp); +#if defined(CHANNEL_RDPEMSC_SERVER) + mouse_cursor = mouse_cursor_server_context_new(NULL); + mouse_cursor_server_context_free(mouse_cursor); +#endif /* CHANNEL_RDPEMSC_SERVER */ + #if defined(CHANNEL_RDPECAM_SERVER) camera_enumerator = cam_dev_enum_server_context_new(NULL); cam_dev_enum_server_context_free(camera_enumerator); diff --git a/include/config/config.h.in b/include/config/config.h.in index d2bdf24bb..8a6e836cd 100644 --- a/include/config/config.h.in +++ b/include/config/config.h.in @@ -117,6 +117,9 @@ #cmakedefine CHANNEL_RDPGFX #cmakedefine CHANNEL_RDPGFX_CLIENT #cmakedefine CHANNEL_RDPGFX_SERVER +#cmakedefine CHANNEL_RDPEMSC +#cmakedefine CHANNEL_RDPEMSC_CLIENT +#cmakedefine CHANNEL_RDPEMSC_SERVER #cmakedefine CHANNEL_RDPSND #cmakedefine CHANNEL_RDPSND_CLIENT #cmakedefine CHANNEL_RDPSND_SERVER diff --git a/include/freerdp/channels/rdpemsc.h b/include/freerdp/channels/rdpemsc.h new file mode 100644 index 000000000..38ab688a4 --- /dev/null +++ b/include/freerdp/channels/rdpemsc.h @@ -0,0 +1,135 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Mouse Cursor Virtual Channel Extension + * + * Copyright 2023 Pascal Nowack + * + * 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_RDPEMSC_H +#define FREERDP_CHANNEL_RDPEMSC_H + +#include +#include +#include + +#define RDPEMSC_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::MouseCursor" + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef enum + { + PDUTYPE_CS_CAPS_ADVERTISE = 0x01, + PDUTYPE_SC_CAPS_CONFIRM = 0x02, + PDUTYPE_SC_MOUSEPTR_UPDATE = 0x03, + } RDP_MOUSE_CURSOR_PDUTYPE; + + typedef enum + { + TS_UPDATETYPE_MOUSEPTR_SYSTEM_NULL = 0x05, + TS_UPDATETYPE_MOUSEPTR_SYSTEM_DEFAULT = 0x06, + TS_UPDATETYPE_MOUSEPTR_POSITION = 0x08, + TS_UPDATETYPE_MOUSEPTR_CACHED = 0x0A, + TS_UPDATETYPE_MOUSEPTR_POINTER = 0x0B, + TS_UPDATETYPE_MOUSEPTR_LARGE_POINTER = 0x0C, + } TS_UPDATETYPE_MOUSEPTR; + +#define RDPEMSC_HEADER_SIZE 4 + + typedef struct + { + RDP_MOUSE_CURSOR_PDUTYPE pduType; + TS_UPDATETYPE_MOUSEPTR updateType; + UINT16 reserved; + } RDP_MOUSE_CURSOR_HEADER; + + typedef enum + { + RDP_MOUSE_CURSOR_CAPVERSION_1 = 0x00000001, + } RDP_MOUSE_CURSOR_CAPVERSION; + + typedef struct + { + UINT32 signature; + RDP_MOUSE_CURSOR_CAPVERSION version; + UINT32 size; + } RDP_MOUSE_CURSOR_CAPSET; + + typedef struct + { + RDP_MOUSE_CURSOR_CAPSET capsetHeader; + } RDP_MOUSE_CURSOR_CAPSET_VERSION1; + + typedef struct + { + RDP_MOUSE_CURSOR_HEADER header; + wArrayList* capsSets; + } RDP_MOUSE_CURSOR_CAPS_ADVERTISE_PDU; + + typedef struct + { + RDP_MOUSE_CURSOR_HEADER header; + RDP_MOUSE_CURSOR_CAPSET* capsSet; + } RDP_MOUSE_CURSOR_CAPS_CONFIRM_PDU; + + typedef struct + { + UINT16 xPos; + UINT16 yPos; + } TS_POINT16; + + typedef struct + { + UINT16 xorBpp; + UINT16 cacheIndex; + TS_POINT16 hotSpot; + UINT16 width; + UINT16 height; + UINT16 lengthAndMask; + UINT16 lengthXorMask; + BYTE* xorMaskData; + BYTE* andMaskData; + BYTE pad; + } TS_POINTERATTRIBUTE; + + typedef struct + { + UINT16 xorBpp; + UINT16 cacheIndex; + TS_POINT16 hotSpot; + UINT16 width; + UINT16 height; + UINT32 lengthAndMask; + UINT32 lengthXorMask; + BYTE* xorMaskData; + BYTE* andMaskData; + BYTE pad; + } TS_LARGEPOINTERATTRIBUTE; + + typedef struct + { + RDP_MOUSE_CURSOR_HEADER header; + TS_POINT16* position; + UINT16* cachedPointerIndex; + TS_POINTERATTRIBUTE* pointerAttribute; + TS_LARGEPOINTERATTRIBUTE* largePointerAttribute; + } RDP_MOUSE_CURSOR_MOUSEPTR_UPDATE_PDU; + +#ifdef __cplusplus +} +#endif +#endif /* FREERDP_CHANNEL_RDPEMSC_H */ diff --git a/include/freerdp/server/rdpemsc.h b/include/freerdp/server/rdpemsc.h new file mode 100644 index 000000000..cbfbd2fc9 --- /dev/null +++ b/include/freerdp/server/rdpemsc.h @@ -0,0 +1,130 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Mouse Cursor Virtual Channel Extension + * + * Copyright 2023 Pascal Nowack + * + * 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_RDPEMSC_SERVER_RDPEMSC_H +#define FREERDP_CHANNEL_RDPEMSC_SERVER_RDPEMSC_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct s_mouse_cursor_server_context MouseCursorServerContext; + + typedef UINT (*psMouseCursorServerOpen)(MouseCursorServerContext* context); + typedef UINT (*psMouseCursorServerClose)(MouseCursorServerContext* context); + + typedef BOOL (*psMouseCursorServerChannelIdAssigned)(MouseCursorServerContext* context, + UINT32 channelId); + + typedef UINT (*psMouseCursorServerInitialize)(MouseCursorServerContext* context, + BOOL externalThread); + typedef UINT (*psMouseCursorServerPoll)(MouseCursorServerContext* context); + typedef BOOL (*psMouseCursorServerChannelHandle)(MouseCursorServerContext* context, + HANDLE* handle); + + typedef UINT (*psMouseCursorServerCapsAdvertise)( + MouseCursorServerContext* context, + const RDP_MOUSE_CURSOR_CAPS_ADVERTISE_PDU* capsAdvertise); + typedef UINT (*psMouseCursorServerCapsConfirm)( + MouseCursorServerContext* context, const RDP_MOUSE_CURSOR_CAPS_CONFIRM_PDU* capsConfirm); + + typedef UINT (*psMouseCursorServerMouseptrUpdate)( + MouseCursorServerContext* context, + const RDP_MOUSE_CURSOR_MOUSEPTR_UPDATE_PDU* mouseptrUpdate); + + struct s_mouse_cursor_server_context + { + HANDLE vcm; + + /* Server self-defined pointer. */ + void* userdata; + + /*** APIs called by the server. ***/ + + /** + * Optional: Set thread handling. + * When externalThread=TRUE, the application is responsible to call + * Poll() periodically to process channel events. + * + * Defaults to externalThread=FALSE + */ + psMouseCursorServerInitialize Initialize; + + /** + * Open the mouse cursor channel. + */ + psMouseCursorServerOpen Open; + + /** + * Close the mouse cursor channel. + */ + psMouseCursorServerClose Close; + + /** + * Poll + * When externalThread=TRUE, call Poll() periodically from your main loop. + * If externalThread=FALSE do not call. + */ + psMouseCursorServerPoll Poll; + + /** + * Retrieve the channel handle for use in conjunction with Poll(). + * If externalThread=FALSE do not call. + */ + psMouseCursorServerChannelHandle ChannelHandle; + + /* All PDUs sent by the server don't require the pduType to be set */ + + /* + * Send a CapsConfirm PDU. + */ + psMouseCursorServerCapsConfirm CapsConfirm; + + /* + * Send a MouseptrUpdate PDU. + */ + psMouseCursorServerMouseptrUpdate MouseptrUpdate; + + /*** Callbacks registered by the server. ***/ + + /** + * Callback, when the channel got its id assigned. + */ + psMouseCursorServerChannelIdAssigned ChannelIdAssigned; + + /** + * Callback for the CapsAdvertise PDU. + */ + psMouseCursorServerCapsAdvertise CapsAdvertise; + + rdpContext* rdpcontext; + }; + + FREERDP_API MouseCursorServerContext* mouse_cursor_server_context_new(HANDLE vcm); + FREERDP_API void mouse_cursor_server_context_free(MouseCursorServerContext* context); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CHANNEL_RDPEMSC_SERVER_RDPEMSC_H */