FreeRDP/server/shadow/shadow_client.c
2024-04-11 12:04:07 +02:00

2612 lines
73 KiB
C

/**
* 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((LPSTR)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))
{
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;
}