summaryrefslogtreecommitdiffstats
path: root/server/shadow/shadow_client.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /server/shadow/shadow_client.c
parentInitial commit. (diff)
downloadfreerdp3-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.c2612
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, &region, area, 1);
+ shadow_client_mark_invalid(client, 1, &region);
+ }
+ 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 = &caps;
+
+ 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 = &caps;
+
+ 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 = &caps;
+ 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, &regionRect, &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,
+ &regionRect, &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(&region);
+ region16_union_rect(&region, &region, &regionRect);
+ rc = progressive_compress(encoder->progressive, pSrcData, nSrcStep * nHeight, cmd.format,
+ nWidth, nHeight, nSrcStep, &region, &cmd.data, &cmd.length);
+ region16_uninit(&region);
+ 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;
+}