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 /server/shadow/shadow_client.c | |
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 'server/shadow/shadow_client.c')
-rw-r--r-- | server/shadow/shadow_client.c | 2612 |
1 files changed, 2612 insertions, 0 deletions
diff --git a/server/shadow/shadow_client.c b/server/shadow/shadow_client.c new file mode 100644 index 0000000..0fd5236 --- /dev/null +++ b/server/shadow/shadow_client.c @@ -0,0 +1,2612 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2017 Armin Novak <armin.novak@thincast.com> + * Copyright 2017 Thincast Technologies GmbH + * + * 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/assert.h> +#include <winpr/file.h> +#include <winpr/path.h> +#include <winpr/synch.h> +#include <winpr/thread.h> +#include <winpr/sysinfo.h> +#include <winpr/interlocked.h> + +#include <freerdp/log.h> +#include <freerdp/channels/drdynvc.h> + +#include "shadow.h" + +#define TAG CLIENT_TAG("shadow") + +typedef struct +{ + BOOL gfxOpened; + BOOL gfxSurfaceCreated; +} SHADOW_GFX_STATUS; + +static INLINE BOOL shadow_client_rdpgfx_new_surface(rdpShadowClient* client) +{ + UINT error = CHANNEL_RC_OK; + RDPGFX_CREATE_SURFACE_PDU createSurface; + RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU surfaceToOutput; + RdpgfxServerContext* context = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(client); + context = client->rdpgfx; + WINPR_ASSERT(context); + settings = ((rdpContext*)client)->settings; + WINPR_ASSERT(settings); + + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX); + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX); + createSurface.width = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + createSurface.height = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + createSurface.pixelFormat = GFX_PIXEL_FORMAT_XRGB_8888; + createSurface.surfaceId = client->surfaceId; + surfaceToOutput.outputOriginX = 0; + surfaceToOutput.outputOriginY = 0; + surfaceToOutput.surfaceId = client->surfaceId; + surfaceToOutput.reserved = 0; + IFCALLRET(context->CreateSurface, error, context, &createSurface); + + if (error) + { + WLog_ERR(TAG, "CreateSurface failed with error %" PRIu32 "", error); + return FALSE; + } + + IFCALLRET(context->MapSurfaceToOutput, error, context, &surfaceToOutput); + + if (error) + { + WLog_ERR(TAG, "MapSurfaceToOutput failed with error %" PRIu32 "", error); + return FALSE; + } + + return TRUE; +} + +static INLINE BOOL shadow_client_rdpgfx_release_surface(rdpShadowClient* client) +{ + UINT error = CHANNEL_RC_OK; + RDPGFX_DELETE_SURFACE_PDU pdu; + RdpgfxServerContext* context = NULL; + + WINPR_ASSERT(client); + + context = client->rdpgfx; + WINPR_ASSERT(context); + + pdu.surfaceId = client->surfaceId++; + IFCALLRET(context->DeleteSurface, error, context, &pdu); + + if (error) + { + WLog_ERR(TAG, "DeleteSurface failed with error %" PRIu32 "", error); + return FALSE; + } + + return TRUE; +} + +static INLINE BOOL shadow_client_rdpgfx_reset_graphic(rdpShadowClient* client) +{ + UINT error = CHANNEL_RC_OK; + RDPGFX_RESET_GRAPHICS_PDU pdu = { 0 }; + RdpgfxServerContext* context = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(client); + WINPR_ASSERT(client->rdpgfx); + + context = client->rdpgfx; + WINPR_ASSERT(context); + + settings = client->context.settings; + WINPR_ASSERT(settings); + + pdu.width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + pdu.height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + pdu.monitorCount = client->subsystem->numMonitors; + pdu.monitorDefArray = client->subsystem->monitors; + IFCALLRET(context->ResetGraphics, error, context, &pdu); + + if (error) + { + WLog_ERR(TAG, "ResetGraphics failed with error %" PRIu32 "", error); + return FALSE; + } + + client->first_frame = TRUE; + return TRUE; +} + +static INLINE void shadow_client_free_queued_message(void* obj) +{ + wMessage* message = (wMessage*)obj; + + WINPR_ASSERT(message); + if (message->Free) + { + message->Free(message); + message->Free = NULL; + } +} + +static void shadow_client_context_free(freerdp_peer* peer, rdpContext* context) +{ + rdpShadowClient* client = (rdpShadowClient*)context; + rdpShadowServer* server = NULL; + + WINPR_UNUSED(peer); + if (!client) + return; + + server = client->server; + if (server && server->clients) + ArrayList_Remove(server->clients, (void*)client); + + shadow_encoder_free(client->encoder); + + /* Clear queued messages and free resource */ + MessageQueue_Free(client->MsgQueue); + WTSCloseServer((HANDLE)client->vcm); + region16_uninit(&(client->invalidRegion)); + DeleteCriticalSection(&(client->lock)); + + client->MsgQueue = NULL; + client->encoder = NULL; + client->vcm = NULL; +} + +static BOOL shadow_client_context_new(freerdp_peer* peer, rdpContext* context) +{ + BOOL NSCodec = 0; + const char bind_address[] = "bind-address,"; + rdpShadowClient* client = (rdpShadowClient*)context; + rdpSettings* settings = NULL; + const rdpSettings* srvSettings = NULL; + rdpShadowServer* server = NULL; + const wObject cb = { NULL, NULL, NULL, shadow_client_free_queued_message, NULL }; + + WINPR_ASSERT(client); + WINPR_ASSERT(peer); + WINPR_ASSERT(peer->context); + + server = (rdpShadowServer*)peer->ContextExtra; + WINPR_ASSERT(server); + + srvSettings = server->settings; + WINPR_ASSERT(srvSettings); + + client->surfaceId = 1; + client->server = server; + client->subsystem = server->subsystem; + WINPR_ASSERT(client->subsystem); + + settings = peer->context->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, + freerdp_settings_get_uint32(srvSettings, FreeRDP_ColorDepth))) + return FALSE; + NSCodec = freerdp_settings_get_bool(srvSettings, FreeRDP_NSCodec); + if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, NSCodec)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, + freerdp_settings_get_bool(srvSettings, FreeRDP_RemoteFxCodec))) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_FrameMarkerCommandEnabled, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_SurfaceFrameMarkerEnabled, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxH264, + freerdp_settings_get_bool(srvSettings, FreeRDP_GfxH264))) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DrawAllowSkipAlpha, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DrawAllowColorSubsampling, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DrawAllowDynamicColorFidelity, TRUE)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP8)) + return FALSE; + + if (server->ipcSocket && (strncmp(bind_address, server->ipcSocket, + strnlen(bind_address, sizeof(bind_address))) != 0)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_LyncRdpMode, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, FALSE)) + return FALSE; + } + + client->inLobby = TRUE; + client->mayView = server->mayView; + client->mayInteract = server->mayInteract; + + if (!InitializeCriticalSectionAndSpinCount(&(client->lock), 4000)) + goto fail; + + region16_init(&(client->invalidRegion)); + client->vcm = WTSOpenServerA(peer->context); + + if (!client->vcm || client->vcm == INVALID_HANDLE_VALUE) + goto fail; + + if (!(client->MsgQueue = MessageQueue_New(&cb))) + goto fail; + + if (!(client->encoder = shadow_encoder_new(client))) + goto fail; + + if (!ArrayList_Append(server->clients, (void*)client)) + goto fail; + + return TRUE; + +fail: + shadow_client_context_free(peer, context); + return FALSE; +} + +static INLINE void shadow_client_mark_invalid(rdpShadowClient* client, UINT32 numRects, + const RECTANGLE_16* rects) +{ + RECTANGLE_16 screenRegion; + rdpSettings* settings = NULL; + + WINPR_ASSERT(client); + WINPR_ASSERT(rects || (numRects == 0)); + + settings = client->context.settings; + WINPR_ASSERT(settings); + + EnterCriticalSection(&(client->lock)); + + /* Mark client invalid region. No rectangle means full screen */ + if (numRects > 0) + { + for (UINT32 index = 0; index < numRects; index++) + { + region16_union_rect(&(client->invalidRegion), &(client->invalidRegion), &rects[index]); + } + } + else + { + screenRegion.left = 0; + screenRegion.top = 0; + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX); + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX); + screenRegion.right = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + screenRegion.bottom = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + region16_union_rect(&(client->invalidRegion), &(client->invalidRegion), &screenRegion); + } + + LeaveCriticalSection(&(client->lock)); +} + +/** + * Function description + * Recalculate client desktop size and update to rdpSettings + * + * @return TRUE if width/height changed. + */ +static INLINE BOOL shadow_client_recalc_desktop_size(rdpShadowClient* client) +{ + INT32 width = 0; + INT32 height = 0; + rdpShadowServer* server = NULL; + rdpSettings* settings = NULL; + RECTANGLE_16 viewport = { 0 }; + + WINPR_ASSERT(client); + server = client->server; + settings = client->context.settings; + + WINPR_ASSERT(server); + WINPR_ASSERT(server->surface); + WINPR_ASSERT(settings); + + WINPR_ASSERT(server->surface->width <= UINT16_MAX); + WINPR_ASSERT(server->surface->height <= UINT16_MAX); + viewport.right = (UINT16)server->surface->width; + viewport.bottom = (UINT16)server->surface->height; + + if (server->shareSubRect) + { + rectangles_intersection(&viewport, &(server->subRect), &viewport); + } + + width = viewport.right - viewport.left; + height = viewport.bottom - viewport.top; + + WINPR_ASSERT(width >= 0); + WINPR_ASSERT(width <= UINT16_MAX); + WINPR_ASSERT(height >= 0); + WINPR_ASSERT(height <= UINT16_MAX); + if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != (UINT32)width || + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) != (UINT32)height) + return TRUE; + + return FALSE; +} + +static BOOL shadow_client_capabilities(freerdp_peer* peer) +{ + rdpShadowSubsystem* subsystem = NULL; + rdpShadowClient* client = NULL; + BOOL ret = TRUE; + + WINPR_ASSERT(peer); + + client = (rdpShadowClient*)peer->context; + WINPR_ASSERT(client); + WINPR_ASSERT(client->server); + + subsystem = client->server->subsystem; + WINPR_ASSERT(subsystem); + + IFCALLRET(subsystem->ClientCapabilities, ret, subsystem, client); + + if (!ret) + WLog_WARN(TAG, "subsystem->ClientCapabilities failed"); + + return ret; +} + +static void shadow_reset_desktop_resize(rdpShadowClient* client) +{ + WINPR_ASSERT(client); + client->resizeRequested = FALSE; +} + +static BOOL shadow_send_desktop_resize(rdpShadowClient* client) +{ + BOOL rc = 0; + rdpUpdate* update = NULL; + rdpSettings* settings = NULL; + const freerdp_peer* peer = NULL; + + WINPR_ASSERT(client); + + settings = client->context.settings; + peer = client->context.peer; + WINPR_ASSERT(peer); + WINPR_ASSERT(client->server); + WINPR_ASSERT(client->server->surface); + + const UINT32 resizeWidth = client->server->surface->width; + const UINT32 resizeHeight = client->server->surface->height; + + if (client->resizeRequested) + { + if ((resizeWidth == client->resizeWidth) && (resizeHeight == client->resizeHeight)) + { + const UINT32 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + const UINT32 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + WLog_WARN(TAG, + "detected previous resize request for resolution %" PRIu32 "x%" PRIu32 + ", still have %" PRIu32 "x%" PRIu32 ", disconnecting peer", + resizeWidth, resizeHeight, w, h); + return FALSE; + } + } + + update = client->context.update; + WINPR_ASSERT(update); + WINPR_ASSERT(update->DesktopResize); + + // Update peer resolution, required so that during disconnect/reconnect the correct resolution + // is sent to the client. + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, resizeWidth)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, resizeHeight)) + return FALSE; + rc = update->DesktopResize(update->context); + WLog_INFO(TAG, "Client %s resize requested (%" PRIu32 "x%" PRIu32 "@%" PRIu32 ")", + peer->hostname, resizeWidth, resizeHeight, + freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth)); + client->resizeRequested = TRUE; + client->resizeWidth = resizeWidth; + client->resizeHeight = resizeHeight; + + return rc; +} + +static BOOL shadow_client_post_connect(freerdp_peer* peer) +{ + int authStatus = 0; + rdpSettings* settings = NULL; + rdpShadowClient* client = NULL; + rdpShadowServer* server = NULL; + rdpShadowSubsystem* subsystem = NULL; + + WINPR_ASSERT(peer); + + client = (rdpShadowClient*)peer->context; + WINPR_ASSERT(client); + + settings = peer->context->settings; + WINPR_ASSERT(settings); + + server = client->server; + WINPR_ASSERT(server); + + subsystem = server->subsystem; + WINPR_ASSERT(subsystem); + + if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) == 24) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 16)) /* disable 24bpp */ + return FALSE; + } + + const UINT32 MultifragMaxRequestSize = + freerdp_settings_get_uint32(settings, FreeRDP_MultifragMaxRequestSize); + if (MultifragMaxRequestSize < 0x3F0000) + { + BOOL rc = freerdp_settings_set_bool( + settings, FreeRDP_NSCodec, + FALSE); /* NSCodec compressor does not support fragmentation yet */ + WINPR_ASSERT(rc); + } + + WLog_INFO(TAG, "Client from %s is activated (%" PRIu32 "x%" PRIu32 "@%" PRIu32 ")", + peer->hostname, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), + freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth)); + + if (shadow_client_channels_post_connect(client) != CHANNEL_RC_OK) + return FALSE; + + shadow_client_mark_invalid(client, 0, NULL); + authStatus = -1; + + const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username); + const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain); + const char* Password = freerdp_settings_get_string(settings, FreeRDP_Password); + + if (Username && Password) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_AutoLogonEnabled, TRUE)) + return FALSE; + } + + if (server->authentication && !freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity)) + { + if (subsystem->Authenticate) + { + authStatus = subsystem->Authenticate(subsystem, client, Username, Domain, Password); + } + + if (authStatus < 0) + { + WLog_ERR(TAG, "client authentication failure: %d", authStatus); + return FALSE; + } + } + + if (subsystem->ClientConnect) + { + return subsystem->ClientConnect(subsystem, client); + } + + return TRUE; +} + +/* Convert rects in sub rect coordinate to client/surface coordinate */ +static INLINE void shadow_client_convert_rects(rdpShadowClient* client, RECTANGLE_16* dst, + const RECTANGLE_16* src, UINT32 numRects) +{ + WINPR_ASSERT(client); + WINPR_ASSERT(client->server); + WINPR_ASSERT(dst); + WINPR_ASSERT(src || (numRects == 0)); + + if (client->server->shareSubRect) + { + UINT16 offsetX = client->server->subRect.left; + UINT16 offsetY = client->server->subRect.top; + + for (UINT32 i = 0; i < numRects; i++) + { + const RECTANGLE_16* s = &src[i]; + RECTANGLE_16* d = &dst[i]; + + d->left = s->left + offsetX; + d->right = s->right + offsetX; + d->top = s->top + offsetY; + d->bottom = s->bottom + offsetY; + } + } + else + { + if (src != dst) + { + CopyMemory(dst, src, numRects * sizeof(RECTANGLE_16)); + } + } +} + +static BOOL shadow_client_refresh_request(rdpShadowClient* client) +{ + wMessage message = { 0 }; + wMessagePipe* MsgPipe = NULL; + + WINPR_ASSERT(client); + WINPR_ASSERT(client->subsystem); + + MsgPipe = client->subsystem->MsgPipe; + WINPR_ASSERT(MsgPipe); + + message.id = SHADOW_MSG_IN_REFRESH_REQUEST_ID; + message.wParam = NULL; + message.lParam = NULL; + message.context = (void*)client; + message.Free = NULL; + return MessageQueue_Dispatch(MsgPipe->In, &message); +} + +static BOOL shadow_client_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas) +{ + rdpShadowClient* client = (rdpShadowClient*)context; + RECTANGLE_16* rects = NULL; + + /* It is invalid if we have area count but no actual area */ + if (count && !areas) + return FALSE; + + if (count) + { + rects = (RECTANGLE_16*)calloc(count, sizeof(RECTANGLE_16)); + + if (!rects) + { + return FALSE; + } + + shadow_client_convert_rects(client, rects, areas, count); + shadow_client_mark_invalid(client, count, rects); + free(rects); + } + else + { + shadow_client_mark_invalid(client, 0, NULL); + } + + return shadow_client_refresh_request(client); +} + +static BOOL shadow_client_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area) +{ + rdpShadowClient* client = (rdpShadowClient*)context; + RECTANGLE_16 region; + + WINPR_ASSERT(client); + + client->suppressOutput = allow ? FALSE : TRUE; + + if (allow) + { + if (area) + { + shadow_client_convert_rects(client, ®ion, area, 1); + shadow_client_mark_invalid(client, 1, ®ion); + } + else + { + shadow_client_mark_invalid(client, 0, NULL); + } + } + + return shadow_client_refresh_request(client); +} + +static BOOL shadow_client_activate(freerdp_peer* peer) +{ + rdpSettings* settings = NULL; + rdpShadowClient* client = NULL; + + WINPR_ASSERT(peer); + + client = (rdpShadowClient*)peer->context; + WINPR_ASSERT(client); + + settings = peer->context->settings; + WINPR_ASSERT(settings); + + /* Resize client if necessary */ + if (shadow_client_recalc_desktop_size(client)) + return shadow_send_desktop_resize(client); + + shadow_reset_desktop_resize(client); + client->activated = TRUE; + client->inLobby = client->mayView ? FALSE : TRUE; + + if (shadow_encoder_reset(client->encoder) < 0) + { + WLog_ERR(TAG, "Failed to reset encoder"); + return FALSE; + } + + /* Update full screen in next update */ + return shadow_client_refresh_rect(&client->context, 0, NULL); +} + +static BOOL shadow_client_logon(freerdp_peer* peer, const SEC_WINNT_AUTH_IDENTITY* identity, + BOOL automatic) +{ + BOOL rc = FALSE; + char* user = NULL; + char* domain = NULL; + char* password = NULL; + rdpSettings* settings = NULL; + + WINPR_UNUSED(automatic); + + WINPR_ASSERT(peer); + WINPR_ASSERT(identity); + + WINPR_ASSERT(peer->context); + + settings = peer->context->settings; + WINPR_ASSERT(settings); + + if (identity->Flags & SEC_WINNT_AUTH_IDENTITY_UNICODE) + { + if (identity->User) + user = ConvertWCharNToUtf8Alloc(identity->User, identity->UserLength, NULL); + + if (identity->Domain) + domain = ConvertWCharNToUtf8Alloc(identity->Domain, identity->DomainLength, NULL); + + if (identity->Password) + password = ConvertWCharNToUtf8Alloc(identity->Password, identity->PasswordLength, NULL); + } + else + { + if (identity->User) + user = _strdup((char*)identity->User); + + if (identity->Domain) + domain = _strdup((char*)identity->Domain); + + if (identity->Password) + password = _strdup((char*)identity->Password); + } + + if ((identity->User && !user) || (identity->Domain && !domain) || + (identity->Password && !password)) + goto fail; + + if (user) + freerdp_settings_set_string(settings, FreeRDP_Username, user); + + if (domain) + freerdp_settings_set_string(settings, FreeRDP_Domain, domain); + + if (password) + freerdp_settings_set_string(settings, FreeRDP_Password, password); + + rc = TRUE; +fail: + free(user); + free(domain); + free(password); + return rc; +} + +static INLINE void shadow_client_common_frame_acknowledge(rdpShadowClient* client, UINT32 frameId) +{ + /* + * Record the last client acknowledged frame id to + * calculate how much frames are in progress. + * Some rdp clients (win7 mstsc) skips frame ACK if it is + * inactive, we should not expect ACK for each frame. + * So it is OK to calculate inflight frame count according to + * a latest acknowledged frame id. + */ + WINPR_ASSERT(client); + WINPR_ASSERT(client->encoder); + client->encoder->lastAckframeId = frameId; +} + +static BOOL shadow_client_surface_frame_acknowledge(rdpContext* context, UINT32 frameId) +{ + rdpShadowClient* client = (rdpShadowClient*)context; + shadow_client_common_frame_acknowledge(client, frameId); + /* + * Reset queueDepth for legacy none RDPGFX acknowledge + */ + WINPR_ASSERT(client); + WINPR_ASSERT(client->encoder); + client->encoder->queueDepth = QUEUE_DEPTH_UNAVAILABLE; + return TRUE; +} + +static UINT +shadow_client_rdpgfx_frame_acknowledge(RdpgfxServerContext* context, + const RDPGFX_FRAME_ACKNOWLEDGE_PDU* frameAcknowledge) +{ + rdpShadowClient* client = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(frameAcknowledge); + + client = (rdpShadowClient*)context->custom; + shadow_client_common_frame_acknowledge(client, frameAcknowledge->frameId); + + WINPR_ASSERT(client); + WINPR_ASSERT(client->encoder); + client->encoder->queueDepth = frameAcknowledge->queueDepth; + return CHANNEL_RC_OK; +} + +static BOOL shadow_are_caps_filtered(const rdpSettings* settings, UINT32 caps) +{ + const UINT32 capList[] = { RDPGFX_CAPVERSION_8, RDPGFX_CAPVERSION_81, + RDPGFX_CAPVERSION_10, RDPGFX_CAPVERSION_101, + RDPGFX_CAPVERSION_102, RDPGFX_CAPVERSION_103, + RDPGFX_CAPVERSION_104, RDPGFX_CAPVERSION_105, + RDPGFX_CAPVERSION_106, RDPGFX_CAPVERSION_106_ERR, + RDPGFX_CAPVERSION_107 }; + + WINPR_ASSERT(settings); + const UINT32 filter = freerdp_settings_get_uint32(settings, FreeRDP_GfxCapsFilter); + + for (UINT32 x = 0; x < ARRAYSIZE(capList); x++) + { + if (caps == capList[x]) + return (filter & (1 << x)) != 0; + } + + return TRUE; +} + +static UINT shadow_client_send_caps_confirm(RdpgfxServerContext* context, rdpShadowClient* client, + const RDPGFX_CAPS_CONFIRM_PDU* pdu) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(client); + WINPR_ASSERT(pdu); + + WINPR_ASSERT(context->CapsConfirm); + UINT rc = context->CapsConfirm(context, pdu); + client->areGfxCapsReady = (rc == CHANNEL_RC_OK); + return rc; +} + +static BOOL shadow_client_caps_test_version(RdpgfxServerContext* context, rdpShadowClient* client, + BOOL h264, const RDPGFX_CAPSET* capsSets, + UINT32 capsSetCount, UINT32 capsVersion, UINT* rc) +{ + const rdpSettings* srvSettings = NULL; + rdpSettings* clientSettings = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(client); + WINPR_ASSERT(capsSets || (capsSetCount == 0)); + WINPR_ASSERT(rc); + + WINPR_ASSERT(context->rdpcontext); + srvSettings = context->rdpcontext->settings; + WINPR_ASSERT(srvSettings); + + clientSettings = client->context.settings; + WINPR_ASSERT(clientSettings); + + if (shadow_are_caps_filtered(srvSettings, capsVersion)) + return FALSE; + + for (UINT32 index = 0; index < capsSetCount; index++) + { + const RDPGFX_CAPSET* currentCaps = &capsSets[index]; + + if (currentCaps->version == capsVersion) + { + UINT32 flags = 0; + BOOL planar = FALSE; + BOOL rfx = FALSE; + BOOL avc444v2 = FALSE; + BOOL avc444 = FALSE; + BOOL avc420 = FALSE; + BOOL progressive = FALSE; + RDPGFX_CAPSET caps = *currentCaps; + RDPGFX_CAPS_CONFIRM_PDU pdu = { 0 }; + pdu.capsSet = ∩︀ + + flags = pdu.capsSet->flags; + + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxSmallCache, + (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) ? TRUE : FALSE)) + return FALSE; + + avc444v2 = avc444 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED); + if (!freerdp_settings_get_bool(srvSettings, FreeRDP_GfxAVC444v2) || !h264) + avc444v2 = FALSE; + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, avc444v2)) + return FALSE; + if (!freerdp_settings_get_bool(srvSettings, FreeRDP_GfxAVC444) || !h264) + avc444 = FALSE; + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, avc444)) + return FALSE; + if (!freerdp_settings_get_bool(srvSettings, FreeRDP_GfxH264) || !h264) + avc420 = FALSE; + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, avc420)) + return FALSE; + + progressive = freerdp_settings_get_bool(srvSettings, FreeRDP_GfxProgressive); + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxProgressive, progressive)) + return FALSE; + progressive = freerdp_settings_get_bool(srvSettings, FreeRDP_GfxProgressiveV2); + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxProgressiveV2, progressive)) + return FALSE; + + rfx = freerdp_settings_get_bool(srvSettings, FreeRDP_RemoteFxCodec); + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_RemoteFxCodec, rfx)) + return FALSE; + + planar = freerdp_settings_get_bool(srvSettings, FreeRDP_GfxPlanar); + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxPlanar, planar)) + return FALSE; + + if (!avc444v2 && !avc444 && !avc420) + pdu.capsSet->flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED; + + *rc = shadow_client_send_caps_confirm(context, client, &pdu); + return TRUE; + } + } + + return FALSE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT shadow_client_rdpgfx_caps_advertise(RdpgfxServerContext* context, + const RDPGFX_CAPS_ADVERTISE_PDU* capsAdvertise) +{ + UINT rc = ERROR_INTERNAL_ERROR; + const rdpSettings* srvSettings = NULL; + rdpSettings* clientSettings = NULL; + BOOL h264 = FALSE; + + UINT32 flags = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(capsAdvertise); + + rdpShadowClient* client = (rdpShadowClient*)context->custom; + WINPR_ASSERT(client); + WINPR_ASSERT(context->rdpcontext); + + srvSettings = context->rdpcontext->settings; + WINPR_ASSERT(srvSettings); + + clientSettings = client->context.settings; + WINPR_ASSERT(clientSettings); + +#ifdef WITH_GFX_H264 + h264 = + (shadow_encoder_prepare(client->encoder, FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444) >= 0); +#else + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE); +#endif + + /* Request full screen update for new gfx channel */ + if (!shadow_client_refresh_rect(&client->context, 0, NULL)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_107, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_106, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_106_ERR, + &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_105, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_104, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_103, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_102, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_101, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_10, &rc)) + return rc; + + if (!shadow_are_caps_filtered(srvSettings, RDPGFX_CAPVERSION_81)) + { + for (UINT32 index = 0; index < capsAdvertise->capsSetCount; index++) + { + const RDPGFX_CAPSET* currentCaps = &capsAdvertise->capsSets[index]; + + if (currentCaps->version == RDPGFX_CAPVERSION_81) + { + RDPGFX_CAPSET caps = *currentCaps; + RDPGFX_CAPS_CONFIRM_PDU pdu; + pdu.capsSet = ∩︀ + + flags = pdu.capsSet->flags; + + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, FALSE); + + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxThinClient, + (flags & RDPGFX_CAPS_FLAG_THINCLIENT) ? TRUE : FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxSmallCache, + (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) ? TRUE : FALSE); + +#ifndef WITH_GFX_H264 + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE); + pdu.capsSet->flags &= ~RDPGFX_CAPS_FLAG_AVC420_ENABLED; +#else + + if (h264) + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, + (flags & RDPGFX_CAPS_FLAG_AVC420_ENABLED) ? TRUE + : FALSE); + else + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE); +#endif + + return shadow_client_send_caps_confirm(context, client, &pdu); + } + } + } + + if (!shadow_are_caps_filtered(srvSettings, RDPGFX_CAPVERSION_8)) + { + for (UINT32 index = 0; index < capsAdvertise->capsSetCount; index++) + { + const RDPGFX_CAPSET* currentCaps = &capsAdvertise->capsSets[index]; + + if (currentCaps->version == RDPGFX_CAPVERSION_8) + { + RDPGFX_CAPSET caps = *currentCaps; + RDPGFX_CAPS_CONFIRM_PDU pdu; + pdu.capsSet = ∩︀ + flags = pdu.capsSet->flags; + + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE); + + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxThinClient, + (flags & RDPGFX_CAPS_FLAG_THINCLIENT) ? TRUE : FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxSmallCache, + (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) ? TRUE : FALSE); + + return shadow_client_send_caps_confirm(context, client, &pdu); + } + } + } + + return CHANNEL_RC_UNSUPPORTED_VERSION; +} + +static INLINE UINT32 rdpgfx_estimate_h264_avc420(RDPGFX_AVC420_BITMAP_STREAM* havc420) +{ + /* H264 metadata + H264 stream. See rdpgfx_write_h264_avc420 */ + WINPR_ASSERT(havc420); + return sizeof(UINT32) /* numRegionRects */ + + 10 /* regionRects + quantQualityVals */ + * havc420->meta.numRegionRects + + havc420->length; +} + +/** + * Function description + * + * @return TRUE on success + */ +static BOOL shadow_client_send_surface_gfx(rdpShadowClient* client, const BYTE* pSrcData, + UINT32 nSrcStep, UINT32 SrcFormat, UINT16 nXSrc, + UINT16 nYSrc, UINT16 nWidth, UINT16 nHeight) +{ + UINT32 id = 0; + UINT error = CHANNEL_RC_OK; + const rdpContext* context = (const rdpContext*)client; + const rdpSettings* settings = NULL; + rdpShadowEncoder* encoder = NULL; + RDPGFX_SURFACE_COMMAND cmd = { 0 }; + RDPGFX_START_FRAME_PDU cmdstart = { 0 }; + RDPGFX_END_FRAME_PDU cmdend = { 0 }; + SYSTEMTIME sTime = { 0 }; + + if (!context || !pSrcData) + return FALSE; + + settings = context->settings; + encoder = client->encoder; + + if (!settings || !encoder) + return FALSE; + + if (client->first_frame) + { + rfx_context_reset(encoder->rfx, nWidth, nHeight); + client->first_frame = FALSE; + } + + cmdstart.frameId = shadow_encoder_create_frame_id(encoder); + GetSystemTime(&sTime); + cmdstart.timestamp = (UINT32)(sTime.wHour << 22U | sTime.wMinute << 16U | sTime.wSecond << 10U | + sTime.wMilliseconds); + cmdend.frameId = cmdstart.frameId; + cmd.surfaceId = client->surfaceId; + cmd.format = PIXEL_FORMAT_BGRX32; + cmd.left = nXSrc; + cmd.top = nYSrc; + cmd.right = cmd.left + nWidth; + cmd.bottom = cmd.top + nHeight; + cmd.width = nWidth; + cmd.height = nHeight; + + id = freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId); +#ifdef WITH_GFX_H264 + const BOOL GfxH264 = freerdp_settings_get_bool(settings, FreeRDP_GfxH264); + const BOOL GfxAVC444 = freerdp_settings_get_bool(settings, FreeRDP_GfxAVC444); + const BOOL GfxAVC444v2 = freerdp_settings_get_bool(settings, FreeRDP_GfxAVC444v2); + if (GfxAVC444 || GfxAVC444v2) + { + INT32 rc = 0; + RDPGFX_AVC444_BITMAP_STREAM avc444 = { 0 }; + RECTANGLE_16 regionRect = { 0 }; + BYTE version = GfxAVC444v2 ? 2 : 1; + + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_AVC444) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_AVC444"); + return FALSE; + } + + WINPR_ASSERT(cmd.left <= UINT16_MAX); + WINPR_ASSERT(cmd.top <= UINT16_MAX); + WINPR_ASSERT(cmd.right <= UINT16_MAX); + WINPR_ASSERT(cmd.bottom <= UINT16_MAX); + regionRect.left = (UINT16)cmd.left; + regionRect.top = (UINT16)cmd.top; + regionRect.right = (UINT16)cmd.right; + regionRect.bottom = (UINT16)cmd.bottom; + rc = avc444_compress(encoder->h264, pSrcData, cmd.format, nSrcStep, nWidth, nHeight, + version, ®ionRect, &avc444.LC, &avc444.bitstream[0].data, + &avc444.bitstream[0].length, &avc444.bitstream[1].data, + &avc444.bitstream[1].length, &avc444.bitstream[0].meta, + &avc444.bitstream[1].meta); + if (rc < 0) + { + WLog_ERR(TAG, "avc420_compress failed for avc444"); + return FALSE; + } + + /* rc > 0 means new data */ + if (rc > 0) + { + avc444.cbAvc420EncodedBitstream1 = rdpgfx_estimate_h264_avc420(&avc444.bitstream[0]); + cmd.codecId = GfxAVC444v2 ? RDPGFX_CODECID_AVC444v2 : RDPGFX_CODECID_AVC444; + cmd.extra = (void*)&avc444; + IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart, + &cmdend); + } + + free_h264_metablock(&avc444.bitstream[0].meta); + free_h264_metablock(&avc444.bitstream[1].meta); + if (error) + { + WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error); + return FALSE; + } + } + else if (GfxH264) + { + INT32 rc = 0; + RDPGFX_AVC420_BITMAP_STREAM avc420 = { 0 }; + RECTANGLE_16 regionRect; + + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_AVC420) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_AVC420"); + return FALSE; + } + + WINPR_ASSERT(cmd.left <= UINT16_MAX); + WINPR_ASSERT(cmd.top <= UINT16_MAX); + WINPR_ASSERT(cmd.right <= UINT16_MAX); + WINPR_ASSERT(cmd.bottom <= UINT16_MAX); + regionRect.left = (UINT16)cmd.left; + regionRect.top = (UINT16)cmd.top; + regionRect.right = (UINT16)cmd.right; + regionRect.bottom = (UINT16)cmd.bottom; + rc = avc420_compress(encoder->h264, pSrcData, cmd.format, nSrcStep, nWidth, nHeight, + ®ionRect, &avc420.data, &avc420.length, &avc420.meta); + if (rc < 0) + { + WLog_ERR(TAG, "avc420_compress failed"); + return FALSE; + } + + /* rc > 0 means new data */ + if (rc > 0) + { + cmd.codecId = RDPGFX_CODECID_AVC420; + cmd.extra = (void*)&avc420; + + IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart, + &cmdend); + } + free_h264_metablock(&avc420.meta); + + if (error) + { + WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error); + return FALSE; + } + } + else +#endif + if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) && (id != 0)) + { + BOOL rc = 0; + wStream* s = NULL; + RFX_RECT rect; + + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_REMOTEFX) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_REMOTEFX"); + return FALSE; + } + + s = Stream_New(NULL, 1024); + WINPR_ASSERT(s); + + WINPR_ASSERT(cmd.left <= UINT16_MAX); + WINPR_ASSERT(cmd.top <= UINT16_MAX); + WINPR_ASSERT(cmd.right <= UINT16_MAX); + WINPR_ASSERT(cmd.bottom <= UINT16_MAX); + rect.x = (UINT16)cmd.left; + rect.y = (UINT16)cmd.top; + rect.width = (UINT16)cmd.right - cmd.left; + rect.height = (UINT16)cmd.bottom - cmd.top; + + rc = rfx_compose_message(encoder->rfx, s, &rect, 1, pSrcData, nWidth, nHeight, nSrcStep); + + if (!rc) + { + WLog_ERR(TAG, "rfx_compose_message failed"); + Stream_Free(s, TRUE); + return FALSE; + } + + /* rc > 0 means new data */ + if (rc > 0) + { + const size_t pos = Stream_GetPosition(s); + WINPR_ASSERT(pos <= UINT32_MAX); + + cmd.codecId = RDPGFX_CODECID_CAVIDEO; + cmd.data = Stream_Buffer(s); + cmd.length = (UINT32)pos; + + IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart, + &cmdend); + } + + Stream_Free(s, TRUE); + if (error) + { + WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error); + return FALSE; + } + } + else if (freerdp_settings_get_bool(settings, FreeRDP_GfxProgressive)) + { + INT32 rc = 0; + REGION16 region; + RECTANGLE_16 regionRect; + + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_PROGRESSIVE) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_PROGRESSIVE"); + return FALSE; + } + + WINPR_ASSERT(cmd.left <= UINT16_MAX); + WINPR_ASSERT(cmd.top <= UINT16_MAX); + WINPR_ASSERT(cmd.right <= UINT16_MAX); + WINPR_ASSERT(cmd.bottom <= UINT16_MAX); + regionRect.left = (UINT16)cmd.left; + regionRect.top = (UINT16)cmd.top; + regionRect.right = (UINT16)cmd.right; + regionRect.bottom = (UINT16)cmd.bottom; + region16_init(®ion); + region16_union_rect(®ion, ®ion, ®ionRect); + rc = progressive_compress(encoder->progressive, pSrcData, nSrcStep * nHeight, cmd.format, + nWidth, nHeight, nSrcStep, ®ion, &cmd.data, &cmd.length); + region16_uninit(®ion); + if (rc < 0) + { + WLog_ERR(TAG, "progressive_compress failed"); + return FALSE; + } + + /* rc > 0 means new data */ + if (rc > 0) + { + cmd.codecId = RDPGFX_CODECID_CAPROGRESSIVE; + + IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart, + &cmdend); + } + + if (error) + { + WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error); + return FALSE; + } + } + else if (freerdp_settings_get_bool(settings, FreeRDP_GfxPlanar)) + { + BOOL rc = 0; + const UINT32 w = cmd.right - cmd.left; + const UINT32 h = cmd.bottom - cmd.top; + const BYTE* src = + &pSrcData[cmd.top * nSrcStep + cmd.left * FreeRDPGetBytesPerPixel(SrcFormat)]; + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_PLANAR) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_PLANAR"); + return FALSE; + } + + rc = freerdp_bitmap_planar_context_reset(encoder->planar, w, h); + WINPR_ASSERT(rc); + freerdp_planar_topdown_image(encoder->planar, TRUE); + + cmd.data = freerdp_bitmap_compress_planar(encoder->planar, src, SrcFormat, w, h, nSrcStep, + NULL, &cmd.length); + WINPR_ASSERT(cmd.data || (cmd.length == 0)); + + cmd.codecId = RDPGFX_CODECID_PLANAR; + + IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart, + &cmdend); + free(cmd.data); + if (error) + { + WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error); + return FALSE; + } + } + else + { + BOOL rc = 0; + const UINT32 w = cmd.right - cmd.left; + const UINT32 h = cmd.bottom - cmd.top; + const UINT32 length = w * 4 * h; + BYTE* data = malloc(length); + + WINPR_ASSERT(data); + + rc = freerdp_image_copy(data, PIXEL_FORMAT_BGRA32, 0, 0, 0, w, h, pSrcData, SrcFormat, + nSrcStep, cmd.left, cmd.top, NULL, 0); + WINPR_ASSERT(rc); + + cmd.data = data; + cmd.length = length; + cmd.codecId = RDPGFX_CODECID_UNCOMPRESSED; + + IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart, + &cmdend); + free(data); + if (error) + { + WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error); + return FALSE; + } + } + return TRUE; +} + +/** + * Function description + * + * @return TRUE on success + */ +static BOOL shadow_client_send_surface_bits(rdpShadowClient* client, BYTE* pSrcData, + UINT32 nSrcStep, UINT16 nXSrc, UINT16 nYSrc, + UINT16 nWidth, UINT16 nHeight) +{ + BOOL ret = TRUE; + BOOL first = 0; + BOOL last = 0; + wStream* s = NULL; + size_t numMessages = 0; + UINT32 frameId = 0; + rdpUpdate* update = NULL; + rdpContext* context = (rdpContext*)client; + rdpSettings* settings = NULL; + rdpShadowEncoder* encoder = NULL; + SURFACE_BITS_COMMAND cmd = { 0 }; + UINT32 nsID = 0; + UINT32 rfxID = 0; + + if (!context || !pSrcData) + return FALSE; + + update = context->update; + settings = context->settings; + encoder = client->encoder; + + if (!update || !settings || !encoder) + return FALSE; + + if (encoder->frameAck) + frameId = shadow_encoder_create_frame_id(encoder); + + nsID = freerdp_settings_get_uint32(settings, FreeRDP_NSCodecId); + rfxID = freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId); + if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) && (rfxID != 0)) + { + RFX_RECT rect = { 0 }; + + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_REMOTEFX) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_REMOTEFX"); + return FALSE; + } + + s = encoder->bs; + rect.x = nXSrc; + rect.y = nYSrc; + rect.width = nWidth; + rect.height = nHeight; + + const UINT32 MultifragMaxRequestSize = + freerdp_settings_get_uint32(settings, FreeRDP_MultifragMaxRequestSize); + RFX_MESSAGE_LIST* messages = + rfx_encode_messages(encoder->rfx, &rect, 1, pSrcData, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), + nSrcStep, &numMessages, MultifragMaxRequestSize); + if (!messages) + { + WLog_ERR(TAG, "rfx_encode_messages failed"); + return FALSE; + } + + cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS; + WINPR_ASSERT(rfxID <= UINT16_MAX); + cmd.bmp.codecID = (UINT16)rfxID; + cmd.destLeft = 0; + cmd.destTop = 0; + cmd.destRight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + cmd.destBottom = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + cmd.bmp.bpp = 32; + cmd.bmp.flags = 0; + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX); + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX); + cmd.bmp.width = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + cmd.bmp.height = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + cmd.skipCompression = TRUE; + + for (size_t i = 0; i < numMessages; i++) + { + Stream_SetPosition(s, 0); + + const RFX_MESSAGE* msg = rfx_message_list_get(messages, i); + if (!rfx_write_message(encoder->rfx, s, msg)) + { + rfx_message_list_free(messages); + WLog_ERR(TAG, "rfx_write_message failed"); + ret = FALSE; + break; + } + + WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX); + cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s); + cmd.bmp.bitmapData = Stream_Buffer(s); + first = (i == 0) ? TRUE : FALSE; + last = ((i + 1) == numMessages) ? TRUE : FALSE; + + if (!encoder->frameAck) + IFCALLRET(update->SurfaceBits, ret, update->context, &cmd); + else + IFCALLRET(update->SurfaceFrameBits, ret, update->context, &cmd, first, last, + frameId); + + if (!ret) + { + WLog_ERR(TAG, "Send surface bits(RemoteFxCodec) failed"); + break; + } + } + + rfx_message_list_free(messages); + } + if (freerdp_settings_get_bool(settings, FreeRDP_NSCodec) && (nsID != 0)) + { + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_NSCODEC) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_NSCODEC"); + return FALSE; + } + + s = encoder->bs; + Stream_SetPosition(s, 0); + pSrcData = &pSrcData[(nYSrc * nSrcStep) + (nXSrc * 4)]; + nsc_compose_message(encoder->nsc, s, pSrcData, nWidth, nHeight, nSrcStep); + cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; + cmd.bmp.bpp = 32; + WINPR_ASSERT(nsID <= UINT16_MAX); + cmd.bmp.codecID = (UINT16)nsID; + cmd.destLeft = nXSrc; + cmd.destTop = nYSrc; + cmd.destRight = cmd.destLeft + nWidth; + cmd.destBottom = cmd.destTop + nHeight; + cmd.bmp.width = nWidth; + cmd.bmp.height = nHeight; + WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX); + cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s); + cmd.bmp.bitmapData = Stream_Buffer(s); + first = TRUE; + last = TRUE; + + if (!encoder->frameAck) + IFCALLRET(update->SurfaceBits, ret, update->context, &cmd); + else + IFCALLRET(update->SurfaceFrameBits, ret, update->context, &cmd, first, last, frameId); + + if (!ret) + { + WLog_ERR(TAG, "Send surface bits(NSCodec) failed"); + } + } + + return ret; +} + +/** + * Function description + * + * @return TRUE on success + */ +static BOOL shadow_client_send_bitmap_update(rdpShadowClient* client, BYTE* pSrcData, + UINT32 nSrcStep, UINT16 nXSrc, UINT16 nYSrc, + UINT16 nWidth, UINT16 nHeight) +{ + BOOL ret = TRUE; + BYTE* data = NULL; + BYTE* buffer = NULL; + UINT32 k = 0; + UINT32 yIdx = 0; + UINT32 xIdx = 0; + UINT32 rows = 0; + UINT32 cols = 0; + UINT32 DstSize = 0; + UINT32 SrcFormat = 0; + BITMAP_DATA* bitmap = NULL; + rdpUpdate* update = NULL; + rdpContext* context = (rdpContext*)client; + rdpSettings* settings = NULL; + UINT32 totalBitmapSize = 0; + UINT32 updateSizeEstimate = 0; + BITMAP_DATA* bitmapData = NULL; + BITMAP_UPDATE bitmapUpdate; + rdpShadowEncoder* encoder = NULL; + + if (!context || !pSrcData) + return FALSE; + + update = context->update; + settings = context->settings; + encoder = client->encoder; + + if (!update || !settings || !encoder) + return FALSE; + + const UINT32 maxUpdateSize = + freerdp_settings_get_uint32(settings, FreeRDP_MultifragMaxRequestSize); + if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) < 32) + { + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_INTERLEAVED) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_INTERLEAVED"); + return FALSE; + } + } + else + { + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_PLANAR) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_PLANAR"); + return FALSE; + } + } + + SrcFormat = PIXEL_FORMAT_BGRX32; + + if ((nXSrc % 4) != 0) + { + nWidth += (nXSrc % 4); + nXSrc -= (nXSrc % 4); + } + + if ((nYSrc % 4) != 0) + { + nHeight += (nYSrc % 4); + nYSrc -= (nYSrc % 4); + } + + rows = (nHeight / 64) + ((nHeight % 64) ? 1 : 0); + cols = (nWidth / 64) + ((nWidth % 64) ? 1 : 0); + k = 0; + totalBitmapSize = 0; + bitmapUpdate.number = rows * cols; + + if (!(bitmapData = (BITMAP_DATA*)calloc(bitmapUpdate.number, sizeof(BITMAP_DATA)))) + return FALSE; + + bitmapUpdate.rectangles = bitmapData; + + if ((nWidth % 4) != 0) + { + nWidth += (4 - (nWidth % 4)); + } + + if ((nHeight % 4) != 0) + { + nHeight += (4 - (nHeight % 4)); + } + + for (yIdx = 0; yIdx < rows; yIdx++) + { + for (xIdx = 0; xIdx < cols; xIdx++) + { + bitmap = &bitmapData[k]; + bitmap->width = 64; + bitmap->height = 64; + bitmap->destLeft = nXSrc + (xIdx * 64); + bitmap->destTop = nYSrc + (yIdx * 64); + + if ((INT64)(bitmap->destLeft + bitmap->width) > (nXSrc + nWidth)) + bitmap->width = (UINT32)(nXSrc + nWidth) - bitmap->destLeft; + + if ((INT64)(bitmap->destTop + bitmap->height) > (nYSrc + nHeight)) + bitmap->height = (UINT32)(nYSrc + nHeight) - bitmap->destTop; + + bitmap->destRight = bitmap->destLeft + bitmap->width - 1; + bitmap->destBottom = bitmap->destTop + bitmap->height - 1; + bitmap->compressed = TRUE; + + if ((bitmap->width < 4) || (bitmap->height < 4)) + continue; + + if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) < 32) + { + UINT32 bitsPerPixel = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth); + UINT32 bytesPerPixel = (bitsPerPixel + 7) / 8; + DstSize = 64 * 64 * 4; + buffer = encoder->grid[k]; + interleaved_compress(encoder->interleaved, buffer, &DstSize, bitmap->width, + bitmap->height, pSrcData, SrcFormat, nSrcStep, + bitmap->destLeft, bitmap->destTop, NULL, bitsPerPixel); + bitmap->bitmapDataStream = buffer; + bitmap->bitmapLength = DstSize; + bitmap->bitsPerPixel = bitsPerPixel; + bitmap->cbScanWidth = bitmap->width * bytesPerPixel; + bitmap->cbUncompressedSize = bitmap->width * bitmap->height * bytesPerPixel; + } + else + { + UINT32 dstSize = 0; + buffer = encoder->grid[k]; + data = &pSrcData[(bitmap->destTop * nSrcStep) + (bitmap->destLeft * 4)]; + + buffer = + freerdp_bitmap_compress_planar(encoder->planar, data, SrcFormat, bitmap->width, + bitmap->height, nSrcStep, buffer, &dstSize); + bitmap->bitmapDataStream = buffer; + bitmap->bitmapLength = dstSize; + bitmap->bitsPerPixel = 32; + bitmap->cbScanWidth = bitmap->width * 4; + bitmap->cbUncompressedSize = bitmap->width * bitmap->height * 4; + } + + bitmap->cbCompFirstRowSize = 0; + bitmap->cbCompMainBodySize = bitmap->bitmapLength; + totalBitmapSize += bitmap->bitmapLength; + k++; + } + } + + bitmapUpdate.number = k; + updateSizeEstimate = totalBitmapSize + (k * bitmapUpdate.number) + 16; + + if (updateSizeEstimate > maxUpdateSize) + { + UINT32 i = 0; + UINT32 j = 0; + UINT32 updateSize = 0; + UINT32 newUpdateSize = 0; + BITMAP_DATA* fragBitmapData = NULL; + + if (k > 0) + fragBitmapData = (BITMAP_DATA*)calloc(k, sizeof(BITMAP_DATA)); + + if (!fragBitmapData) + { + WLog_ERR(TAG, "Failed to allocate memory for fragBitmapData"); + ret = FALSE; + goto out; + } + + bitmapUpdate.rectangles = fragBitmapData; + i = j = 0; + updateSize = 1024; + + while (i < k) + { + newUpdateSize = updateSize + (bitmapData[i].bitmapLength + 16); + + if (newUpdateSize < maxUpdateSize) + { + CopyMemory(&fragBitmapData[j++], &bitmapData[i++], sizeof(BITMAP_DATA)); + updateSize = newUpdateSize; + } + + if ((newUpdateSize >= maxUpdateSize) || (i + 1) >= k) + { + bitmapUpdate.number = j; + IFCALLRET(update->BitmapUpdate, ret, context, &bitmapUpdate); + + if (!ret) + { + WLog_ERR(TAG, "BitmapUpdate failed"); + break; + } + + updateSize = 1024; + j = 0; + } + } + + free(fragBitmapData); + } + else + { + IFCALLRET(update->BitmapUpdate, ret, context, &bitmapUpdate); + + if (!ret) + { + WLog_ERR(TAG, "BitmapUpdate failed"); + } + } + +out: + free(bitmapData); + return ret; +} + +/** + * Function description + * + * @return TRUE on success (or nothing need to be updated) + */ +static BOOL shadow_client_send_surface_update(rdpShadowClient* client, SHADOW_GFX_STATUS* pStatus) +{ + BOOL ret = TRUE; + INT64 nXSrc = 0; + INT64 nYSrc = 0; + INT64 nWidth = 0; + INT64 nHeight = 0; + rdpContext* context = (rdpContext*)client; + rdpSettings* settings = NULL; + rdpShadowServer* server = NULL; + rdpShadowSurface* surface = NULL; + REGION16 invalidRegion; + RECTANGLE_16 surfaceRect; + const RECTANGLE_16* extents = NULL; + BYTE* pSrcData = NULL; + UINT32 nSrcStep = 0; + UINT32 SrcFormat = 0; + UINT32 numRects = 0; + const RECTANGLE_16* rects = NULL; + + if (!context || !pStatus) + return FALSE; + + settings = context->settings; + server = client->server; + + if (!settings || !server) + return FALSE; + + surface = client->inLobby ? server->lobby : server->surface; + + if (!surface) + return FALSE; + + EnterCriticalSection(&(client->lock)); + region16_init(&invalidRegion); + region16_copy(&invalidRegion, &(client->invalidRegion)); + region16_clear(&(client->invalidRegion)); + LeaveCriticalSection(&(client->lock)); + + EnterCriticalSection(&surface->lock); + rects = region16_rects(&(surface->invalidRegion), &numRects); + + for (UINT32 index = 0; index < numRects; index++) + region16_union_rect(&invalidRegion, &invalidRegion, &rects[index]); + + surfaceRect.left = 0; + surfaceRect.top = 0; + WINPR_ASSERT(surface->width <= UINT16_MAX); + WINPR_ASSERT(surface->height <= UINT16_MAX); + surfaceRect.right = (UINT16)surface->width; + surfaceRect.bottom = (UINT16)surface->height; + region16_intersect_rect(&invalidRegion, &invalidRegion, &surfaceRect); + + if (server->shareSubRect) + { + region16_intersect_rect(&invalidRegion, &invalidRegion, &(server->subRect)); + } + + if (region16_is_empty(&invalidRegion)) + { + /* No image region need to be updated. Success */ + goto out; + } + + extents = region16_extents(&invalidRegion); + nXSrc = extents->left; + nYSrc = extents->top; + nWidth = extents->right - extents->left; + nHeight = extents->bottom - extents->top; + pSrcData = surface->data; + nSrcStep = surface->scanline; + SrcFormat = surface->format; + + /* Move to new pSrcData / nXSrc / nYSrc according to sub rect */ + if (server->shareSubRect) + { + INT32 subX = 0; + INT32 subY = 0; + subX = server->subRect.left; + subY = server->subRect.top; + nXSrc -= subX; + nYSrc -= subY; + WINPR_ASSERT(nXSrc >= 0); + WINPR_ASSERT(nXSrc <= UINT16_MAX); + WINPR_ASSERT(nYSrc >= 0); + WINPR_ASSERT(nYSrc <= UINT16_MAX); + pSrcData = &pSrcData[((UINT16)subY * nSrcStep) + ((UINT16)subX * 4U)]; + } + + // WLog_INFO(TAG, "shadow_client_send_surface_update: x: %" PRId64 " y: %" PRId64 " width: %" + // PRId64 " height: %" PRId64 " right: %" PRId64 " bottom: %" PRId64, nXSrc, nYSrc, nWidth, + // nHeight, nXSrc + nWidth, nYSrc + nHeight); + + if (freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline)) + { + if (pStatus->gfxOpened && client->areGfxCapsReady) + { + /* GFX/h264 always full screen encoded */ + nWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + nHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + /* Create primary surface if have not */ + if (!pStatus->gfxSurfaceCreated) + { + /* Only init surface when we have h264 supported */ + if (!(ret = shadow_client_rdpgfx_reset_graphic(client))) + goto out; + + if (!(ret = shadow_client_rdpgfx_new_surface(client))) + goto out; + + pStatus->gfxSurfaceCreated = TRUE; + } + + WINPR_ASSERT(nWidth >= 0); + WINPR_ASSERT(nWidth <= UINT16_MAX); + WINPR_ASSERT(nHeight >= 0); + WINPR_ASSERT(nHeight <= UINT16_MAX); + ret = shadow_client_send_surface_gfx(client, pSrcData, nSrcStep, SrcFormat, 0, 0, + (UINT16)nWidth, (UINT16)nHeight); + } + else + { + ret = TRUE; + } + } + else if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) || + freerdp_settings_get_bool(settings, FreeRDP_NSCodec)) + { + WINPR_ASSERT(nXSrc >= 0); + WINPR_ASSERT(nXSrc <= UINT16_MAX); + WINPR_ASSERT(nYSrc >= 0); + WINPR_ASSERT(nYSrc <= UINT16_MAX); + WINPR_ASSERT(nWidth >= 0); + WINPR_ASSERT(nWidth <= UINT16_MAX); + WINPR_ASSERT(nHeight >= 0); + WINPR_ASSERT(nHeight <= UINT16_MAX); + ret = shadow_client_send_surface_bits(client, pSrcData, nSrcStep, (UINT16)nXSrc, + (UINT16)nYSrc, (UINT16)nWidth, (UINT16)nHeight); + } + else + { + WINPR_ASSERT(nXSrc >= 0); + WINPR_ASSERT(nXSrc <= UINT16_MAX); + WINPR_ASSERT(nYSrc >= 0); + WINPR_ASSERT(nYSrc <= UINT16_MAX); + WINPR_ASSERT(nWidth >= 0); + WINPR_ASSERT(nWidth <= UINT16_MAX); + WINPR_ASSERT(nHeight >= 0); + WINPR_ASSERT(nHeight <= UINT16_MAX); + ret = shadow_client_send_bitmap_update(client, pSrcData, nSrcStep, (UINT16)nXSrc, + (UINT16)nYSrc, (UINT16)nWidth, (UINT16)nHeight); + } + +out: + LeaveCriticalSection(&surface->lock); + region16_uninit(&invalidRegion); + return ret; +} + +/** + * Function description + * Notify client for resize. The new desktop width/height + * should have already been updated in rdpSettings. + * + * @return TRUE on success + */ +static BOOL shadow_client_send_resize(rdpShadowClient* client, SHADOW_GFX_STATUS* pStatus) +{ + rdpContext* context = (rdpContext*)client; + rdpSettings* settings = NULL; + freerdp_peer* peer = NULL; + + if (!context || !pStatus) + return FALSE; + + peer = context->peer; + settings = context->settings; + + if (!peer || !settings) + return FALSE; + + /** + * Unset client activated flag to avoid sending update message during + * resize. DesktopResize will reactive the client and + * shadow_client_activate would be invoked later. + */ + client->activated = FALSE; + + /* Close Gfx surfaces */ + if (pStatus->gfxSurfaceCreated) + { + if (!shadow_client_rdpgfx_release_surface(client)) + return FALSE; + + pStatus->gfxSurfaceCreated = FALSE; + } + + /* Send Resize */ + if (!shadow_send_desktop_resize(client)) + return FALSE; + shadow_reset_desktop_resize(client); + + /* Clear my invalidRegion. shadow_client_activate refreshes fullscreen */ + EnterCriticalSection(&(client->lock)); + region16_clear(&(client->invalidRegion)); + LeaveCriticalSection(&(client->lock)); + return TRUE; +} + +/** + * Function description + * Mark invalid region for client + * + * @return TRUE on success + */ +static BOOL shadow_client_surface_update(rdpShadowClient* client, REGION16* region) +{ + UINT32 numRects = 0; + const RECTANGLE_16* rects = NULL; + rects = region16_rects(region, &numRects); + shadow_client_mark_invalid(client, numRects, rects); + return TRUE; +} + +/** + * Function description + * Only union invalid region from server surface + * + * @return TRUE on success + */ +static INLINE BOOL shadow_client_no_surface_update(rdpShadowClient* client, + SHADOW_GFX_STATUS* pStatus) +{ + rdpShadowServer* server = NULL; + rdpShadowSurface* surface = NULL; + WINPR_UNUSED(pStatus); + WINPR_ASSERT(client); + server = client->server; + WINPR_ASSERT(server); + surface = client->inLobby ? server->lobby : server->surface; + return shadow_client_surface_update(client, &(surface->invalidRegion)); +} + +static int shadow_client_subsystem_process_message(rdpShadowClient* client, wMessage* message) +{ + rdpContext* context = (rdpContext*)client; + rdpUpdate* update = NULL; + + WINPR_ASSERT(message); + WINPR_ASSERT(context); + update = context->update; + WINPR_ASSERT(update); + + /* FIXME: the pointer updates appear to be broken when used with bulk compression and mstsc */ + + switch (message->id) + { + case SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID: + { + POINTER_POSITION_UPDATE pointerPosition; + const SHADOW_MSG_OUT_POINTER_POSITION_UPDATE* msg = + (const SHADOW_MSG_OUT_POINTER_POSITION_UPDATE*)message->wParam; + pointerPosition.xPos = msg->xPos; + pointerPosition.yPos = msg->yPos; + + WINPR_ASSERT(client->server); + if (client->server->shareSubRect) + { + pointerPosition.xPos -= client->server->subRect.left; + pointerPosition.yPos -= client->server->subRect.top; + } + + if (client->activated) + { + if ((msg->xPos != client->pointerX) || (msg->yPos != client->pointerY)) + { + WINPR_ASSERT(update->pointer); + IFCALL(update->pointer->PointerPosition, context, &pointerPosition); + client->pointerX = msg->xPos; + client->pointerY = msg->yPos; + } + } + + break; + } + + case SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID: + { + POINTER_NEW_UPDATE pointerNew = { 0 }; + POINTER_COLOR_UPDATE* pointerColor = { 0 }; + POINTER_CACHED_UPDATE pointerCached = { 0 }; + const SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE* msg = + (const SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)message->wParam; + + WINPR_ASSERT(msg); + pointerNew.xorBpp = 24; + pointerColor = &(pointerNew.colorPtrAttr); + pointerColor->cacheIndex = 0; + pointerColor->hotSpotX = msg->xHot; + pointerColor->hotSpotY = msg->yHot; + pointerColor->width = msg->width; + pointerColor->height = msg->height; + pointerColor->lengthAndMask = msg->lengthAndMask; + pointerColor->lengthXorMask = msg->lengthXorMask; + pointerColor->xorMaskData = msg->xorMaskData; + pointerColor->andMaskData = msg->andMaskData; + pointerCached.cacheIndex = pointerColor->cacheIndex; + + if (client->activated) + { + IFCALL(update->pointer->PointerNew, context, &pointerNew); + IFCALL(update->pointer->PointerCached, context, &pointerCached); + } + + break; + } + + case SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID: + { + const SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES* msg = + (const SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES*)message->wParam; + + WINPR_ASSERT(msg); + + if (client->activated && client->rdpsnd && client->rdpsnd->Activated) + { + client->rdpsnd->src_format = msg->audio_format; + IFCALL(client->rdpsnd->SendSamples, client->rdpsnd, msg->buf, msg->nFrames, + msg->wTimestamp); + } + + break; + } + + case SHADOW_MSG_OUT_AUDIO_OUT_VOLUME_ID: + { + const SHADOW_MSG_OUT_AUDIO_OUT_VOLUME* msg = + (const SHADOW_MSG_OUT_AUDIO_OUT_VOLUME*)message->wParam; + + if (client->activated && client->rdpsnd && client->rdpsnd->Activated) + { + IFCALL(client->rdpsnd->SetVolume, client->rdpsnd, msg->left, msg->right); + } + + break; + } + + default: + WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", message->id); + break; + } + + shadow_client_free_queued_message(message); + return 1; +} + +static DWORD WINAPI shadow_client_thread(LPVOID arg) +{ + rdpShadowClient* client = (rdpShadowClient*)arg; + BOOL rc = FALSE; + DWORD status = 0; + wMessage message = { 0 }; + wMessage pointerPositionMsg = { 0 }; + wMessage pointerAlphaMsg = { 0 }; + wMessage audioVolumeMsg = { 0 }; + HANDLE ChannelEvent = 0; + void* UpdateSubscriber = NULL; + HANDLE UpdateEvent = 0; + freerdp_peer* peer = NULL; + rdpContext* context = NULL; + rdpSettings* settings = NULL; + rdpShadowServer* server = NULL; + rdpShadowSubsystem* subsystem = NULL; + wMessageQueue* MsgQueue = NULL; + /* This should only be visited in client thread */ + SHADOW_GFX_STATUS gfxstatus = { 0 }; + rdpUpdate* update = NULL; + + WINPR_ASSERT(client); + + MsgQueue = client->MsgQueue; + WINPR_ASSERT(MsgQueue); + + server = client->server; + WINPR_ASSERT(server); + subsystem = server->subsystem; + context = (rdpContext*)client; + peer = context->peer; + WINPR_ASSERT(peer); + WINPR_ASSERT(peer->context); + + settings = peer->context->settings; + WINPR_ASSERT(settings); + + peer->Capabilities = shadow_client_capabilities; + peer->PostConnect = shadow_client_post_connect; + peer->Activate = shadow_client_activate; + peer->Logon = shadow_client_logon; + shadow_input_register_callbacks(peer->context->input); + + rc = peer->Initialize(peer); + if (!rc) + goto out; + + update = peer->context->update; + WINPR_ASSERT(update); + + update->RefreshRect = shadow_client_refresh_rect; + update->SuppressOutput = shadow_client_suppress_output; + update->SurfaceFrameAcknowledge = shadow_client_surface_frame_acknowledge; + + if ((!client->vcm) || (!subsystem->updateEvent)) + goto out; + + UpdateSubscriber = shadow_multiclient_get_subscriber(subsystem->updateEvent); + + if (!UpdateSubscriber) + goto out; + + UpdateEvent = shadow_multiclient_getevent(UpdateSubscriber); + WINPR_ASSERT(UpdateEvent); + + ChannelEvent = WTSVirtualChannelManagerGetEventHandle(client->vcm); + WINPR_ASSERT(ChannelEvent); + + rc = freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, TRUE); + WINPR_ASSERT(rc); + rc = freerdp_settings_set_bool(settings, FreeRDP_HasHorizontalWheel, TRUE); + WINPR_ASSERT(rc); + rc = freerdp_settings_set_bool(settings, FreeRDP_HasExtendedMouseEvent, TRUE); + WINPR_ASSERT(rc); + rc = freerdp_settings_set_bool(settings, FreeRDP_SupportMonitorLayoutPdu, TRUE); + WINPR_ASSERT(rc); + while (1) + { + HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD nCount = 0; + events[nCount++] = UpdateEvent; + { + DWORD tmp = peer->GetEventHandles(peer, &events[nCount], 64 - nCount); + + if (tmp == 0) + { + WLog_ERR(TAG, "Failed to get FreeRDP transport event handles"); + goto fail; + } + + nCount += tmp; + } + events[nCount++] = ChannelEvent; + events[nCount++] = MessageQueue_Event(MsgQueue); + + HANDLE gfxevent = rdpgfx_server_get_event_handle(client->rdpgfx); + + if (gfxevent) + events[nCount++] = gfxevent; + + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + goto fail; + + if (WaitForSingleObject(UpdateEvent, 0) == WAIT_OBJECT_0) + { + /* The UpdateEvent means to start sending current frame. It is + * triggered from subsystem implementation and it should ensure + * that the screen and primary surface meta data (width, height, + * scanline, invalid region, etc) is not changed until it is reset + * (at shadow_multiclient_consume). As best practice, subsystem + * implementation should invoke shadow_subsystem_frame_update which + * triggers the event and then wait for completion */ + if (client->activated && !client->suppressOutput) + { + /* Send screen update or resize to this client */ + + /* Check resize */ + if (shadow_client_recalc_desktop_size(client)) + { + /* Screen size changed, do resize */ + if (!shadow_client_send_resize(client, &gfxstatus)) + { + WLog_ERR(TAG, "Failed to send resize message"); + break; + } + } + else + { + /* Send frame */ + if (!shadow_client_send_surface_update(client, &gfxstatus)) + { + WLog_ERR(TAG, "Failed to send surface update"); + break; + } + } + } + else + { + /* Our client don't receive graphic updates. Just save the invalid region */ + if (!shadow_client_no_surface_update(client, &gfxstatus)) + { + WLog_ERR(TAG, "Failed to handle surface update"); + break; + } + } + + /* + * The return value of shadow_multiclient_consume is whether or not + * the subscriber really consumes the event. It's not cared currently. + */ + (void)shadow_multiclient_consume(UpdateSubscriber); + } + + WINPR_ASSERT(peer->CheckFileDescriptor); + if (!peer->CheckFileDescriptor(peer)) + { + WLog_ERR(TAG, "Failed to check FreeRDP file descriptor"); + goto fail; + } + + if (client->activated && + WTSVirtualChannelManagerIsChannelJoined(client->vcm, DRDYNVC_SVC_CHANNEL_NAME)) + { + switch (WTSVirtualChannelManagerGetDrdynvcState(client->vcm)) + { + /* Dynamic channel status may have been changed after processing */ + case DRDYNVC_STATE_NONE: + + /* Call this routine to Initialize drdynvc channel */ + if (!WTSVirtualChannelManagerCheckFileDescriptor(client->vcm)) + { + WLog_ERR(TAG, "Failed to initialize drdynvc channel"); + goto fail; + } + + break; + + case DRDYNVC_STATE_READY: +#if defined(CHANNEL_AUDIN_SERVER) + if (client->audin && !IFCALLRESULT(TRUE, client->audin->IsOpen, client->audin)) + { + if (!IFCALLRESULT(FALSE, client->audin->Open, client->audin)) + { + WLog_ERR(TAG, "Failed to initialize audin channel"); + goto fail; + } + } +#endif + + /* Init RDPGFX dynamic channel */ + if (freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline) && + client->rdpgfx && !gfxstatus.gfxOpened) + { + client->rdpgfx->FrameAcknowledge = shadow_client_rdpgfx_frame_acknowledge; + client->rdpgfx->CapsAdvertise = shadow_client_rdpgfx_caps_advertise; + + if (!client->rdpgfx->Open(client->rdpgfx)) + { + WLog_WARN(TAG, "Failed to open GraphicsPipeline"); + if (!freerdp_settings_set_bool(settings, + FreeRDP_SupportGraphicsPipeline, FALSE)) + goto fail; + } + else + { + gfxstatus.gfxOpened = TRUE; + WLog_INFO(TAG, "Gfx Pipeline Opened"); + } + } + + break; + + default: + break; + } + } + + if (WaitForSingleObject(ChannelEvent, 0) == WAIT_OBJECT_0) + { + if (!WTSVirtualChannelManagerCheckFileDescriptor(client->vcm)) + { + WLog_ERR(TAG, "WTSVirtualChannelManagerCheckFileDescriptor failure"); + goto fail; + } + } + + if (gfxevent) + { + if (WaitForSingleObject(gfxevent, 0) == WAIT_OBJECT_0) + { + rdpgfx_server_handle_messages(client->rdpgfx); + } + } + + if (WaitForSingleObject(MessageQueue_Event(MsgQueue), 0) == WAIT_OBJECT_0) + { + /* Drain messages. Pointer update could be accumulated. */ + pointerPositionMsg.id = 0; + pointerPositionMsg.Free = NULL; + pointerAlphaMsg.id = 0; + pointerAlphaMsg.Free = NULL; + audioVolumeMsg.id = 0; + audioVolumeMsg.Free = NULL; + + while (MessageQueue_Peek(MsgQueue, &message, TRUE)) + { + if (message.id == WMQ_QUIT) + { + break; + } + + switch (message.id) + { + case SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID: + /* Abandon previous message */ + shadow_client_free_queued_message(&pointerPositionMsg); + pointerPositionMsg = message; + break; + + case SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID: + /* Abandon previous message */ + shadow_client_free_queued_message(&pointerAlphaMsg); + pointerAlphaMsg = message; + break; + + case SHADOW_MSG_OUT_AUDIO_OUT_VOLUME_ID: + /* Abandon previous message */ + shadow_client_free_queued_message(&audioVolumeMsg); + audioVolumeMsg = message; + break; + + default: + shadow_client_subsystem_process_message(client, &message); + break; + } + } + + if (message.id == WMQ_QUIT) + { + /* Release stored message */ + shadow_client_free_queued_message(&pointerPositionMsg); + shadow_client_free_queued_message(&pointerAlphaMsg); + shadow_client_free_queued_message(&audioVolumeMsg); + goto fail; + } + else + { + /* Process accumulated messages if needed */ + if (pointerPositionMsg.id) + { + shadow_client_subsystem_process_message(client, &pointerPositionMsg); + } + + if (pointerAlphaMsg.id) + { + shadow_client_subsystem_process_message(client, &pointerAlphaMsg); + } + + if (audioVolumeMsg.id) + { + shadow_client_subsystem_process_message(client, &audioVolumeMsg); + } + } + } + } + +fail: + + /* Free channels early because we establish channels in post connect */ +#if defined(CHANNEL_AUDIN_SERVER) + if (client->audin && !IFCALLRESULT(TRUE, client->audin->IsOpen, client->audin)) + { + if (!IFCALLRESULT(FALSE, client->audin->Close, client->audin)) + { + WLog_WARN(TAG, "AUDIN shutdown failure!"); + } + } +#endif + + if (gfxstatus.gfxOpened) + { + if (gfxstatus.gfxSurfaceCreated) + { + if (!shadow_client_rdpgfx_release_surface(client)) + WLog_WARN(TAG, "GFX release surface failure!"); + } + + WINPR_ASSERT(client->rdpgfx); + WINPR_ASSERT(client->rdpgfx->Close); + rc = client->rdpgfx->Close(client->rdpgfx); + WINPR_ASSERT(rc); + } + + shadow_client_channels_free(client); + + if (UpdateSubscriber) + { + shadow_multiclient_release_subscriber(UpdateSubscriber); + UpdateSubscriber = NULL; + } + + if (peer->connected && subsystem->ClientDisconnect) + { + subsystem->ClientDisconnect(subsystem, client); + } + +out: + WINPR_ASSERT(peer->Disconnect); + peer->Disconnect(peer); + freerdp_peer_context_free(peer); + freerdp_peer_free(peer); + ExitThread(0); + return 0; +} + +BOOL shadow_client_accepted(freerdp_listener* listener, freerdp_peer* peer) +{ + rdpShadowClient* client = NULL; + rdpShadowServer* server = NULL; + + if (!listener || !peer) + return FALSE; + + server = (rdpShadowServer*)listener->info; + WINPR_ASSERT(server); + + peer->ContextExtra = (void*)server; + peer->ContextSize = sizeof(rdpShadowClient); + peer->ContextNew = shadow_client_context_new; + peer->ContextFree = shadow_client_context_free; + + if (!freerdp_peer_context_new_ex(peer, server->settings)) + return FALSE; + + client = (rdpShadowClient*)peer->context; + WINPR_ASSERT(client); + + if (!(client->thread = CreateThread(NULL, 0, shadow_client_thread, client, 0, NULL))) + { + freerdp_peer_context_free(peer); + return FALSE; + } + else + { + /* Close the thread handle to make it detached. */ + CloseHandle(client->thread); + client->thread = NULL; + } + + return TRUE; +} + +static void shadow_msg_out_addref(wMessage* message) +{ + SHADOW_MSG_OUT* msg = NULL; + + WINPR_ASSERT(message); + msg = (SHADOW_MSG_OUT*)message->wParam; + WINPR_ASSERT(msg); + + InterlockedIncrement(&(msg->refCount)); +} + +static void shadow_msg_out_release(wMessage* message) +{ + SHADOW_MSG_OUT* msg = NULL; + + WINPR_ASSERT(message); + msg = (SHADOW_MSG_OUT*)message->wParam; + WINPR_ASSERT(msg); + + if (InterlockedDecrement(&(msg->refCount)) <= 0) + { + IFCALL(msg->Free, message->id, msg); + } +} + +static BOOL shadow_client_dispatch_msg(rdpShadowClient* client, wMessage* message) +{ + if (!client || !message) + return FALSE; + + /* Add reference when it is posted */ + shadow_msg_out_addref(message); + + WINPR_ASSERT(client->MsgQueue); + if (MessageQueue_Dispatch(client->MsgQueue, message)) + return TRUE; + else + { + /* Release the reference since post failed */ + shadow_msg_out_release(message); + return FALSE; + } +} + +BOOL shadow_client_post_msg(rdpShadowClient* client, void* context, UINT32 type, + SHADOW_MSG_OUT* msg, void* lParam) +{ + wMessage message = { 0 }; + message.context = context; + message.id = type; + message.wParam = (void*)msg; + message.lParam = lParam; + message.Free = shadow_msg_out_release; + return shadow_client_dispatch_msg(client, &message); +} + +int shadow_client_boardcast_msg(rdpShadowServer* server, void* context, UINT32 type, + SHADOW_MSG_OUT* msg, void* lParam) +{ + wMessage message = { 0 }; + rdpShadowClient* client = NULL; + int count = 0; + + WINPR_ASSERT(server); + WINPR_ASSERT(msg); + + message.context = context; + message.id = type; + message.wParam = (void*)msg; + message.lParam = lParam; + message.Free = shadow_msg_out_release; + /* First add reference as we reference it in this function. + * Therefore it would not be free'ed during post. */ + shadow_msg_out_addref(&message); + + WINPR_ASSERT(server->clients); + ArrayList_Lock(server->clients); + + for (size_t index = 0; index < ArrayList_Count(server->clients); index++) + { + client = (rdpShadowClient*)ArrayList_GetItem(server->clients, index); + + if (shadow_client_dispatch_msg(client, &message)) + { + count++; + } + } + + ArrayList_Unlock(server->clients); + /* Release the reference for this function */ + shadow_msg_out_release(&message); + return count; +} + +int shadow_client_boardcast_quit(rdpShadowServer* server, int nExitCode) +{ + wMessageQueue* queue = NULL; + int count = 0; + + WINPR_ASSERT(server); + WINPR_ASSERT(server->clients); + + ArrayList_Lock(server->clients); + + for (size_t index = 0; index < ArrayList_Count(server->clients); index++) + { + queue = ((rdpShadowClient*)ArrayList_GetItem(server->clients, index))->MsgQueue; + + if (MessageQueue_PostQuit(queue, nExitCode)) + { + count++; + } + } + + ArrayList_Unlock(server->clients); + return count; +} |