diff --git a/configure.ac b/configure.ac index 9494a5ca..1da028a2 100644 --- a/configure.ac +++ b/configure.ac @@ -488,6 +488,15 @@ then #include ]) fi +# Support for "PubSub" event system +if test "x${have_freerdp}" = "xyes" +then + AC_CHECK_MEMBERS([rdpContext.pubSub], + [AC_DEFINE([HAVE_FREERDP_PUBSUB],, + [Whether this version of FreeRDP provides the PubSub event system])],, + [[#include ]]) +fi + # Addin registration variations if test "x${have_freerdp}" = "xyes" then diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 109b585b..c023d8ea 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -146,6 +146,39 @@ int __guac_receive_channel_data(freerdp* rdp_inst, int channelId, UINT8* data, i return freerdp_channels_data(rdp_inst, channelId, data, size, flags, total_size); } +#ifdef HAVE_FREERDP_PUBSUB +/** + * Called whenever a channel connects. + */ +static void guac_rdp_channel_connected(rdpContext* context, + ChannelConnectedEventArgs* e) { + +#ifdef HAVE_RDPSETTINGS_SUPPORTDISPLAYCONTROL + /* Store reference to the display update plugin once it's connected */ + if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) { + + DispClientContext* disp = (DispClientContext*) e->pInterface; + + guac_client* client = ((rdp_freerdp_context*) context)->client; + rdp_guac_client_data* guac_client_data = + (rdp_guac_client_data*) client->data; + + /* Init module with current display size */ + guac_rdp_disp_set_size(guac_client_data->disp, context, + guac_rdp_get_width(context->instance), + guac_rdp_get_height(context->instance)); + + /* Store connected channel */ + guac_rdp_disp_connect(guac_client_data->disp, disp); + guac_client_log(client, GUAC_LOG_DEBUG, + "Display update channel connected."); + + } +#endif + +} +#endif + BOOL rdp_freerdp_pre_connect(freerdp* instance) { rdpContext* context = instance->context; @@ -171,9 +204,15 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { guac_client_log(client, GUAC_LOG_WARNING, "Failed to load drdynvc plugin."); +#ifdef HAVE_FREERDP_PUBSUB + /* Subscribe to and handle channel connected events */ + PubSub_SubscribeChannelConnected(context->pubSub, + (pChannelConnectedEventHandler) guac_rdp_channel_connected); +#endif + #ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /* Init display update plugin */ - guac_client_data->disp = NULL; + guac_client_data->disp = guac_rdp_disp_alloc(); guac_rdp_disp_load_plugin(instance->context); #endif diff --git a/src/protocols/rdp/client.h b/src/protocols/rdp/client.h index 3b9b5c14..349c88a5 100644 --- a/src/protocols/rdp/client.h +++ b/src/protocols/rdp/client.h @@ -33,15 +33,15 @@ #include "rdp_keymap.h" #include "rdp_settings.h" +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT +#include "rdp_disp.h" +#endif + #include #include #include #include -#ifdef HAVE_FREERDP_CLIENT_DISP_H -#include -#endif - #include #include @@ -159,9 +159,9 @@ typedef struct rdp_guac_client_data { #ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /** - * Display control interface. + * Display size update module. */ - DispClientContext* disp; + guac_rdp_disp* disp; #endif /** diff --git a/src/protocols/rdp/guac_handlers.c b/src/protocols/rdp/guac_handlers.c index 99dafd0d..621b0a00 100644 --- a/src/protocols/rdp/guac_handlers.c +++ b/src/protocols/rdp/guac_handlers.c @@ -89,6 +89,11 @@ int rdp_guac_client_free_handler(guac_client* client) { if (guac_client_data->filesystem != NULL) guac_rdp_fs_free(guac_client_data->filesystem); +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT + /* Free display update module */ + guac_rdp_disp_free(guac_client_data->disp); +#endif + /* Free SVC list */ guac_common_list_free(guac_client_data->available_svc); @@ -188,6 +193,13 @@ int rdp_guac_client_handle_messages(guac_client* client) { rdpChannels* channels = rdp_inst->context->channels; wMessage* event; +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT + /* Update remote display size */ + pthread_mutex_lock(&(guac_client_data->rdp_lock)); + guac_rdp_disp_update_size(guac_client_data->disp, rdp_inst->context); + pthread_mutex_unlock(&(guac_client_data->rdp_lock)); +#endif + /* Wait for messages */ int wait_result = rdp_guac_client_wait_for_messages(client, 250000); guac_timestamp frame_start = guac_timestamp_current(); @@ -470,7 +482,8 @@ int rdp_guac_client_size_handler(guac_client* client, int width, int height) { /* Send display update */ pthread_mutex_lock(&(guac_client_data->rdp_lock)); - guac_rdp_disp_send_size(rdp_inst->context, width, height); + guac_rdp_disp_set_size(guac_client_data->disp, rdp_inst->context, + width, height); pthread_mutex_unlock(&(guac_client_data->rdp_lock)); #endif diff --git a/src/protocols/rdp/rdp_disp.c b/src/protocols/rdp/rdp_disp.c index 85d2ca77..042d67dd 100644 --- a/src/protocols/rdp/rdp_disp.c +++ b/src/protocols/rdp/rdp_disp.c @@ -26,38 +26,29 @@ #include #include #include +#include -/** - * Called whenever a channel connects. If that channel happens to be the - * display update channel, a reference to that channel will be stored within - * the guac_client data. - */ -static void guac_rdp_disp_channel_connected(rdpContext* context, - ChannelConnectedEventArgs* e) { +guac_rdp_disp* guac_rdp_disp_alloc() { - /* Store reference to the display update plugin once it's connected */ - if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) { + guac_rdp_disp* disp = malloc(sizeof(guac_rdp_disp)); - DispClientContext* disp = (DispClientContext*) e->pInterface; + /* Not yet connected */ + disp->disp = NULL; - guac_client* client = ((rdp_freerdp_context*) context)->client; - rdp_guac_client_data* guac_client_data = - (rdp_guac_client_data*) client->data; + /* No requests have been made */ + disp->last_request = 0; + disp->requested_width = 0; + disp->requested_height = 0; - guac_client_data->disp = disp; - - guac_client_log(client, GUAC_LOG_DEBUG, - "Display update channel connected."); - - } + return disp; } -void guac_rdp_disp_load_plugin(rdpContext* context) { +void guac_rdp_disp_free(guac_rdp_disp* disp) { + free(disp); +} - /* Subscribe to and handle channel connected events */ - PubSub_SubscribeChannelConnected(context->pubSub, - (pChannelConnectedEventHandler) guac_rdp_disp_channel_connected); +void guac_rdp_disp_load_plugin(rdpContext* context) { #ifdef HAVE_RDPSETTINGS_SUPPORTDISPLAYCONTROL context->settings->SupportDisplayControl = TRUE; @@ -72,37 +63,84 @@ void guac_rdp_disp_load_plugin(rdpContext* context) { } -void guac_rdp_disp_send_size(rdpContext* context, int width, int height) { +void guac_rdp_disp_connect(guac_rdp_disp* guac_disp, DispClientContext* disp) { + guac_disp->disp = disp; +} - guac_client* client = ((rdp_freerdp_context*) context)->client; +void guac_rdp_disp_set_size(guac_rdp_disp* disp, rdpContext* context, + int width, int height) { - rdp_guac_client_data* guac_client_data = - (rdp_guac_client_data*) client->data; + /* Width must be at least 200 pixels */ + if (width < 200) + width = 200; - /* Send display update notification if display channel is connected */ - if (guac_client_data->disp != NULL) { + /* Width may be no more than 8192 pixels */ + else if (width > 8192) + width = 8192; - guac_client_log(client, GUAC_LOG_DEBUG, - "Resizing remote display to %ix%i", - width, height); + /* Width must be even */ + else if (width % 2 == 1) + width -= 1; - DISPLAY_CONTROL_MONITOR_LAYOUT monitors[1] = {{ - .Flags = 0x1, /* DISPLAYCONTROL_MONITOR_PRIMARY */ - .Left = 0, - .Top = 0, - .Width = width, - .Height = height, - .PhysicalWidth = 0, - .PhysicalHeight = 0, - .Orientation = 0, - .DesktopScaleFactor = 0, - .DeviceScaleFactor = 0 - }}; + /* Height must be at least 200 pixels */ + if (height < 200) + height = 200; - guac_client_data->disp->SendMonitorLayout(guac_client_data->disp, 1, - monitors); + /* Height may be no more than 8192 pixels */ + else if (height > 8192) + height = 8192; - } + /* Store deferred size */ + disp->requested_width = width; + disp->requested_height = height; + + /* Send display update notification if possible */ + guac_rdp_disp_update_size(disp, context); + +} + +void guac_rdp_disp_update_size(guac_rdp_disp* disp, rdpContext* context) { + + guac_client* client = ((rdp_freerdp_context*) context)->client; + + /* Send display update notification if display channel is connected */ + if (disp->disp == NULL) + return; + + int width = disp->requested_width; + int height = disp->requested_height; + + DISPLAY_CONTROL_MONITOR_LAYOUT monitors[1] = {{ + .Flags = 0x1, /* DISPLAYCONTROL_MONITOR_PRIMARY */ + .Left = 0, + .Top = 0, + .Width = width, + .Height = height, + .PhysicalWidth = 0, + .PhysicalHeight = 0, + .Orientation = 0, + .DesktopScaleFactor = 0, + .DeviceScaleFactor = 0 + }}; + + guac_timestamp now = guac_timestamp_current(); + + /* Limit display update frequency */ + if (disp->last_request != 0 + && now - disp->last_request <= GUAC_RDP_DISP_UPDATE_INTERVAL) + return; + + /* Do NOT send requests unless the size will change */ + if (width == guac_rdp_get_width(context->instance) + && height == guac_rdp_get_height(context->instance)) + return; + + guac_client_log(client, GUAC_LOG_DEBUG, + "Resizing remote display to %ix%i", + width, height); + + disp->last_request = now; + disp->disp->SendMonitorLayout(disp->disp, 1, monitors); } diff --git a/src/protocols/rdp/rdp_disp.h b/src/protocols/rdp/rdp_disp.h index 89300fa1..7bf92aec 100644 --- a/src/protocols/rdp/rdp_disp.h +++ b/src/protocols/rdp/rdp_disp.h @@ -23,20 +23,83 @@ #ifndef GUAC_RDP_DISP_H #define GUAC_RDP_DISP_H +#include #include + /** - * Loads the "disp" plugin for FreeRDP. If successfully loaded, it will be - * stored within the guac_client data. + * The minimum amount of time that must elapse between display size updates, + * in milliseconds. + */ +#define GUAC_RDP_DISP_UPDATE_INTERVAL 250 + +/** + * Display size update module. + */ +typedef struct guac_rdp_disp { + + /** + * Display control interface. + */ + DispClientContext* disp; + + /** + * The timestamp of the last display update request, or 0 if no request + * has been sent yet. + */ + guac_timestamp last_request; + + /** + * The last requested screen width, in pixels. + */ + int requested_width; + + /** + * The last requested screen height, in pixels. + */ + int requested_height; + +} guac_rdp_disp; + +/** + * Allocates a new display update module, which will ultimately control the + * display update channel once conected. + */ +guac_rdp_disp* guac_rdp_disp_alloc(); + +/** + * Frees the given display update module. + */ +void guac_rdp_disp_free(guac_rdp_disp* disp); + +/** + * Loads the "disp" plugin for FreeRDP. It is still up to external code to + * detect when the "disp" channel is connected, and update the guac_rdp_disp + * with a call to guac_rdp_disp_connect(). */ void guac_rdp_disp_load_plugin(rdpContext* context); /** - * Sends a display update message to the RDP server, notifying that the - * monitor layout has changed to a single monitor of the given width and - * height (in pixels). + * Stores the given DispClientContext within the given guac_rdp_disp, such that + * display updates can be properly sent. Until this is called, changes to the + * display size will be deferred. */ -void guac_rdp_disp_send_size(rdpContext* context, int width, int height); +void guac_rdp_disp_connect(guac_rdp_disp* guac_disp, DispClientContext* disp); + +/** + * Requests a display size update, which may then be sent immediately to the + * RDP server. If an update was recently sent, this update may be delayed until + * the RDP server has had time to settle. + */ +void guac_rdp_disp_set_size(guac_rdp_disp* disp, rdpContext* context, + int width, int height); + +/** + * Sends an actual display update request to the RDP server based on previous + * calls to guac_rdp_disp_set_size(). If an update was recently sent, the + * update may be delayed until a future call to this function. + */ +void guac_rdp_disp_update_size(guac_rdp_disp* disp, rdpContext* context); #endif diff --git a/src/protocols/rdp/rdp_gdi.c b/src/protocols/rdp/rdp_gdi.c index f30b3d10..74eb5ee2 100644 --- a/src/protocols/rdp/rdp_gdi.c +++ b/src/protocols/rdp/rdp_gdi.c @@ -421,6 +421,10 @@ void guac_rdp_gdi_desktop_resize(rdpContext* context) { guac_common_surface_reset_clip(data->default_surface); + guac_client_log(client, GUAC_LOG_DEBUG, "Server resized display to %ix%i", + guac_rdp_get_width(context->instance), + guac_rdp_get_height(context->instance)); + }