From 669e02b4dc15d8e4b14139e94eb10de08fefffbb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 2 Sep 2021 17:07:58 -0700 Subject: [PATCH] GUACAMOLE-377: Leverage RDPGFX to report remote frame statistics to the client. --- src/protocols/rdp/gdi.c | 33 +++++++++++++++++++++++++++++++++ src/protocols/rdp/gdi.h | 18 ++++++++++++++++-- src/protocols/rdp/rdp.c | 21 ++++++++++++++++++--- src/protocols/rdp/rdp.h | 21 +++++++++++++++++++++ 4 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/protocols/rdp/gdi.c b/src/protocols/rdp/gdi.c index 4ecbe396..61bb5476 100644 --- a/src/protocols/rdp/gdi.c +++ b/src/protocols/rdp/gdi.c @@ -372,12 +372,27 @@ BOOL guac_rdp_gdi_set_bounds(rdpContext* context, const rdpBounds* bounds) { } +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; + + return TRUE; + +} + BOOL guac_rdp_gdi_end_paint(rdpContext* context) { guac_client* client = ((rdp_freerdp_context*) context)->client; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; rdpGdi* gdi = context->gdi; + /* The current frame has ended */ + rdp_client->in_frame = 0; + /* Ignore paint if GDI output is suppressed */ if (gdi->suppressOutput) return TRUE; @@ -396,12 +411,30 @@ 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; + + } + return TRUE; } diff --git a/src/protocols/rdp/gdi.h b/src/protocols/rdp/gdi.h index 76735aa5..1cb23952 100644 --- a/src/protocols/rdp/gdi.h +++ b/src/protocols/rdp/gdi.h @@ -156,8 +156,22 @@ BOOL guac_rdp_gdi_opaquerect(rdpContext* context, BOOL guac_rdp_gdi_set_bounds(rdpContext* context, const rdpBounds* bounds); /** - * Handler called when a paint operation is complete. We don't actually - * use this, but FreeRDP requires it. Calling this function has no effect. + * 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. + * + * @param context + * The rdpContext associated with the current RDP session. + * + * @return + * TRUE if successful, FALSE otherwise. + */ +BOOL guac_rdp_gdi_begin_paint(rdpContext* context); + +/** + * Handler called when a paint operation is complete. This function is + * expected to be called by the FreeRDP GDI implementation of RemoteFX when a + * new frame has been completed. * * @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 322465ac..3fc4468b 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -179,6 +179,7 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { /* Set up GDI */ instance->update->DesktopResize = guac_rdp_gdi_desktop_resize; + instance->update->BeginPaint = guac_rdp_gdi_begin_paint; instance->update->EndPaint = guac_rdp_gdi_end_paint; instance->update->SetBounds = guac_rdp_gdi_set_bounds; @@ -530,6 +531,7 @@ static int guac_rdp_handle_connection(guac_client* client) { rdp_client->rdp_inst = rdp_inst; guac_timestamp last_frame_end = guac_timestamp_current(); + rdp_client->frame_start = guac_timestamp_current(); /* Signal that reconnect has been completed */ guac_rdp_disp_reconnect_complete(rdp_client->disp); @@ -569,6 +571,13 @@ static int guac_rdp_handle_connection(guac_client* client) { break; } + /* Continue handling inbound data if we are in the middle of an RDP frame */ + if (rdp_client->in_frame) { + wait_result = rdp_guac_client_wait_for_messages(client, GUAC_RDP_FRAME_START_TIMEOUT); + if (wait_result >= 0) + continue; + } + /* Calculate time remaining in frame */ frame_end = guac_timestamp_current(); frame_remaining = frame_start + GUAC_RDP_FRAME_DURATION @@ -612,11 +621,17 @@ static int guac_rdp_handle_connection(guac_client* client) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE, "Connection closed."); - /* Flush frame only if successful */ - else { + /* Flush frame only if successful and an RDP frame is not known to be + * in progress */ + else if (rdp_client->frames_received) { + guac_common_display_flush(rdp_client->display); - guac_client_end_frame(client); + 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 9daef80f..fe571c8b 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -91,6 +91,27 @@ typedef struct guac_rdp_client { */ guac_common_surface* current_surface; + /** + * Whether the RDP server has reported that a new frame is in progress, and + * we are now receiving updates relevant to that frame. + */ + int in_frame; + + /** + * The number of distinct frames received from the RDP server since last + * flush, if the RDP server supports reporting frame boundaries. If the RDP + * server does not support tracking frames, this will be zero. + */ + int frames_received; + + /** + * The server timestamp of the end of the last frame received from the RDP + * server, as returned by guac_timestamp_current(), if the RDP server + * supports reporting frame boundaries. If the RDP server does not support + * tracking frames, this will be zero. + */ + guac_timestamp frame_start; + /** * The current state of the keyboard with respect to the RDP session. */