From bde8cdee4666840e25bf1910fbccf4dcf947679c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 2 Sep 2021 19:05:21 -0700 Subject: [PATCH] GUACAMOLE-377: Add general RDP support for frame markers. --- src/protocols/rdp/gdi.c | 83 +++++++++++++++++++++++++++--------- src/protocols/rdp/gdi.h | 48 ++++++++++++++++++++- src/protocols/rdp/rdp.c | 7 +-- src/protocols/rdp/rdp.h | 5 +++ src/protocols/rdp/settings.c | 5 ++- 5 files changed, 123 insertions(+), 25 deletions(-) diff --git a/src/protocols/rdp/gdi.c b/src/protocols/rdp/gdi.c index 61bb5476..7e39cc4a 100644 --- a/src/protocols/rdp/gdi.c +++ b/src/protocols/rdp/gdi.c @@ -372,13 +372,67 @@ BOOL guac_rdp_gdi_set_bounds(rdpContext* context, const rdpBounds* bounds) { } +void guac_rdp_gdi_mark_frame(rdpContext* context, int starting) { + + guac_client* client = ((rdp_freerdp_context*) context)->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + /* The server supports defining explicit frames */ + rdp_client->frames_supported = 1; + + /* A new frame is beginning */ + if (starting) { + rdp_client->in_frame = 1; + return; + } + + /* The current frame has ended */ + guac_timestamp frame_end = guac_timestamp_current(); + int time_elapsed = frame_end - rdp_client->frame_start; + rdp_client->in_frame = 0; + + /* A new frame has been received from the RDP server and processed */ + rdp_client->frames_received++; + + /* Flush a new frame if the client is ready for it */ + if (time_elapsed >= guac_client_get_processing_lag(client)) { + + guac_common_display_flush(rdp_client->display); + guac_client_end_multiple_frames(client, rdp_client->frames_received); + guac_socket_flush(client->socket); + + rdp_client->frame_start = frame_end; + rdp_client->frames_received = 0; + + } + +} + +BOOL guac_rdp_gdi_frame_marker(rdpContext* context, const FRAME_MARKER_ORDER* frame_marker) { + guac_rdp_gdi_mark_frame(context, frame_marker->action == FRAME_START); + return TRUE; +} + +BOOL guac_rdp_gdi_surface_frame_marker(rdpContext* context, const SURFACE_FRAME_MARKER* surface_frame_marker) { + + guac_rdp_gdi_mark_frame(context, surface_frame_marker->frameAction == SURFACECMD_FRAMEACTION_END); + + if (context->settings->FrameAcknowledge > 0) + IFCALL(context->update->SurfaceFrameAcknowledge, context, + surface_frame_marker->frameId); + + return TRUE; + +} + BOOL guac_rdp_gdi_begin_paint(rdpContext* context) { guac_client* client = ((rdp_freerdp_context*) context)->client; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - /* A new frame is beginning */ - rdp_client->in_frame = 1; + /* Leverage BeginPaint handler to detect start of frame for RDPGFX channel */ + if (rdp_client->settings->enable_gfx) + guac_rdp_gdi_mark_frame(context, 1); return TRUE; @@ -390,8 +444,10 @@ BOOL guac_rdp_gdi_end_paint(rdpContext* context) { guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; rdpGdi* gdi = context->gdi; - /* The current frame has ended */ - rdp_client->in_frame = 0; + /* Leverage EndPaint handler to detect end of frame for RDPGFX channel + * (ignore otherwise) */ + if (!rdp_client->settings->enable_gfx) + return TRUE; /* Ignore paint if GDI output is suppressed */ if (gdi->suppressOutput) @@ -411,28 +467,15 @@ BOOL guac_rdp_gdi_end_paint(rdpContext* context) { gdi->primary_buffer + 4*x + y*gdi->stride, CAIRO_FORMAT_RGB24, w, h, gdi->stride); - guac_timestamp frame_end = guac_timestamp_current(); - int time_elapsed = frame_end - rdp_client->frame_start; - /* Send surface to buffer */ guac_common_surface_draw(rdp_client->display->default_surface, x, y, surface); /* Free surface */ cairo_surface_destroy(surface); - /* A new frame has been received from the RDP server and processed */ - rdp_client->frames_received++; - - /* Flush a new frame if the client is ready for it */ - if (time_elapsed >= guac_client_get_processing_lag(client)) { - - guac_common_display_flush(rdp_client->display); - guac_client_end_multiple_frames(client, rdp_client->frames_received); - guac_socket_flush(client->socket); - - rdp_client->frame_start = frame_end; - rdp_client->frames_received = 0; - + /* Next frame */ + if (gdi->inGfxFrame) { + guac_rdp_gdi_mark_frame(context, 0); } return TRUE; diff --git a/src/protocols/rdp/gdi.h b/src/protocols/rdp/gdi.h index 1cb23952..955bf7bd 100644 --- a/src/protocols/rdp/gdi.h +++ b/src/protocols/rdp/gdi.h @@ -155,10 +155,56 @@ BOOL guac_rdp_gdi_opaquerect(rdpContext* context, */ BOOL guac_rdp_gdi_set_bounds(rdpContext* context, const rdpBounds* bounds); +/** + * Notifies the internal GDI implementation that a frame is either starting or + * ending. If the frame is ending and the connected client is ready to receive + * a new frame, a new frame will be flushed to the client. + * + * @param context + * The rdpContext associated with the current RDP session. + * + * @param starting + * Non-zero if the frame in question is starting, zero if the frame is + * ending. + */ +void guac_rdp_gdi_mark_frame(rdpContext* context, int starting); + +/** + * Handler called when a frame boundary is received from the RDP server in the + * form of a frame marker command. Each frame boundary may be the beginning or + * the end of a frame. + * + * @param context + * The rdpContext associated with the current RDP session. + * + * @param frame_marker + * The received frame marker. + * + * @return + * TRUE if successful, FALSE otherwise. + */ +BOOL guac_rdp_gdi_frame_marker(rdpContext* context, const FRAME_MARKER_ORDER* frame_marker); + +/** + * Handler called when a frame boundary is received from the RDP server in the + * form of a surface frame marker. Each frame boundary may be the beginning or + * the end of a frame. + * + * @param context + * The rdpContext associated with the current RDP session. + * + * @param surface_frame_marker + * The received frame marker. + * + * @return + * TRUE if successful, FALSE otherwise. + */ +BOOL guac_rdp_gdi_surface_frame_marker(rdpContext* context, const SURFACE_FRAME_MARKER* surface_frame_marker); + /** * Handler called when a paint operation is beginning. This function is * expected to be called by the FreeRDP GDI implementation of RemoteFX when a - * new frame is beginning. + * new frame has started. * * @param context * The rdpContext associated with the current RDP session. diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 3fc4468b..f2a121b5 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -183,6 +183,9 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { instance->update->EndPaint = guac_rdp_gdi_end_paint; instance->update->SetBounds = guac_rdp_gdi_set_bounds; + instance->update->SurfaceFrameMarker = guac_rdp_gdi_surface_frame_marker; + instance->update->altsec->FrameMarker = guac_rdp_gdi_frame_marker; + rdpPrimaryUpdate* primary = instance->update->primary; primary->DstBlt = guac_rdp_gdi_dstblt; primary->PatBlt = guac_rdp_gdi_patblt; @@ -623,15 +626,13 @@ static int guac_rdp_handle_connection(guac_client* client) { /* Flush frame only if successful and an RDP frame is not known to be * in progress */ - else if (rdp_client->frames_received) { - + else if (!rdp_client->frames_supported || rdp_client->frames_received) { guac_common_display_flush(rdp_client->display); guac_client_end_multiple_frames(client, rdp_client->frames_received); guac_socket_flush(client->socket); rdp_client->frame_start = guac_timestamp_current(); rdp_client->frames_received = 0; - } } diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index fe571c8b..509fa1ac 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -91,6 +91,11 @@ typedef struct guac_rdp_client { */ guac_common_surface* current_surface; + /** + * Whether the RDP server supports defining explicit frame boundaries. + */ + int frames_supported; + /** * Whether the RDP server has reported that a new frame is in progress, and * we are now receiving updates relevant to that frame. diff --git a/src/protocols/rdp/settings.c b/src/protocols/rdp/settings.c index 39089562..911bc75d 100644 --- a/src/protocols/rdp/settings.c +++ b/src/protocols/rdp/settings.c @@ -1409,6 +1409,10 @@ void guac_rdp_push_settings(guac_client* client, /* Explicitly set flag value */ rdp_settings->PerformanceFlags = guac_rdp_get_performance_flags(guac_settings); + /* Always request frame markers */ + rdp_settings->FrameMarkerCommandEnabled = TRUE; + rdp_settings->SurfaceFrameMarkerEnabled = TRUE; + /* Enable RemoteFX / Graphics Pipeline */ if (guac_settings->enable_gfx) { @@ -1423,7 +1427,6 @@ void guac_rdp_push_settings(guac_client* client, /* Required for RemoteFX / Graphics Pipeline */ rdp_settings->FastPathOutput = TRUE; - rdp_settings->FrameMarkerCommandEnabled = TRUE; rdp_settings->ColorDepth = 32; rdp_settings->SoftwareGdi = TRUE;