diff --git a/configure.ac b/configure.ac index a818fee6..9d36bb81 100644 --- a/configure.ac +++ b/configure.ac @@ -437,7 +437,6 @@ if test "x$with_rdp" != "xno" then have_winpr=yes have_freerdp=yes - have_disp=yes legacy_freerdp_extensions=no rdpsettings_interface=unknown rdpsettings_audioplayback=yes @@ -554,8 +553,7 @@ then [AC_DEFINE([HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT],, [Whether FreeRDP supports the display update channel])] [AC_CHECK_MEMBERS([rdpSettings.SupportDisplayControl],,, - [[#include ]])], - have_disp=no, + [[#include ]])],, [#include #include ]) fi @@ -869,7 +867,6 @@ then fi AM_CONDITIONAL([LEGACY_FREERDP_EXTENSIONS], [test "x${legacy_freerdp_extensions}" = "xyes"]) -AM_CONDITIONAL([ENABLE_DISPLAY_UPDATE], [test "x${have_disp}" = "xyes"]) AM_CONDITIONAL([ENABLE_WINPR], [test "x${have_winpr}" = "xyes"]) AM_CONDITIONAL([ENABLE_RDP], [test "x${have_freerdp}" = "xyes"]) diff --git a/src/common/guac_cursor.c b/src/common/guac_cursor.c index 55a01bf8..7c309b9f 100644 --- a/src/common/guac_cursor.c +++ b/src/common/guac_cursor.c @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -70,13 +71,20 @@ guac_common_cursor* guac_common_cursor_alloc(guac_client* client) { void guac_common_cursor_free(guac_common_cursor* cursor) { + guac_client* client = cursor->client; + guac_layer* layer = cursor->layer; + cairo_surface_t* surface = cursor->surface; + /* Free image buffer and surface */ free(cursor->image_buffer); - if (cursor->surface != NULL) - cairo_surface_destroy(cursor->surface); + if (surface != NULL) + cairo_surface_destroy(surface); + + /* Destroy layer within remotely-connected client */ + guac_protocol_send_dispose(client->socket, layer); /* Return layer to pool */ - guac_client_free_layer(cursor->client, cursor->layer); + guac_client_free_layer(client, layer); free(cursor); diff --git a/src/common/guac_display.c b/src/common/guac_display.c index 5a918e7d..4ea9652d 100644 --- a/src/common/guac_display.c +++ b/src/common/guac_display.c @@ -84,6 +84,9 @@ static void guac_common_display_free_layers(guac_common_display_layer* layers, /* Free surface */ guac_common_surface_free(current->surface); + /* Destroy layer within remotely-connected client */ + guac_protocol_send_dispose(client->socket, layer); + /* Free layer or buffer depending on index */ if (layer->index < 0) guac_client_free_buffer(client, layer); diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 69492a16..3160b57a 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -33,6 +33,7 @@ libguac_client_rdp_la_SOURCES = \ rdp_bitmap.c \ rdp_cliprdr.c \ rdp_color.c \ + rdp_disp.c \ rdp_fs.c \ rdp_gdi.c \ rdp_glyph.c \ @@ -87,6 +88,7 @@ noinst_HEADERS = \ rdp_bitmap.h \ rdp_cliprdr.h \ rdp_color.h \ + rdp_disp.h \ rdp_fs.h \ rdp_gdi.h \ rdp_glyph.h \ @@ -110,12 +112,6 @@ guacsnd_sources += compat/winpr-stream.c guacdr_sources += compat/winpr-stream.c endif -# Add display update channel support, if supported by FreeRDP -if ENABLE_DISPLAY_UPDATE -noinst_HEADERS += rdp_disp.h -libguac_client_rdp_la_SOURCES += rdp_disp.c -endif - # # Main RDP client library # diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 940635f5..4efb6956 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -24,6 +24,7 @@ #include "client.h" #include "rdp.h" +#include "rdp_disp.h" #include "rdp_keymap.h" #include "user.h" @@ -33,10 +34,6 @@ #include #endif -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT -#include "rdp_disp.h" -#endif - #include #include #include @@ -66,10 +63,11 @@ int guac_client_init(guac_client* client, int argc, char** argv) { guac_rdp_client* rdp_client = calloc(1, sizeof(guac_rdp_client)); client->data = rdp_client; - /* Init clipboard and shared mouse */ + /* Init clipboard */ rdp_client->clipboard = guac_common_clipboard_alloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH); - rdp_client->requested_clipboard_format = CB_FORMAT_TEXT; - rdp_client->available_svc = guac_common_list_alloc(); + + /* Init display update module */ + rdp_client->disp = guac_rdp_disp_alloc(); /* Recursive attribute for locks */ pthread_mutexattr_init(&(rdp_client->attributes)); @@ -98,54 +96,15 @@ int guac_rdp_client_free_handler(guac_client* client) { /* Wait for client thread */ pthread_join(rdp_client->client_thread, NULL); - freerdp* rdp_inst = rdp_client->rdp_inst; - if (rdp_inst != NULL) { - rdpChannels* channels = rdp_inst->context->channels; - - /* Clean up RDP client */ - freerdp_channels_close(channels, rdp_inst); - freerdp_channels_free(channels); - freerdp_disconnect(rdp_inst); - freerdp_clrconv_free(((rdp_freerdp_context*) rdp_inst->context)->clrconv); - cache_free(rdp_inst->context->cache); - freerdp_free(rdp_inst); - } - - /* Clean up filesystem, if allocated */ - if (rdp_client->filesystem != NULL) - guac_rdp_fs_free(rdp_client->filesystem); - -#ifdef ENABLE_COMMON_SSH - /* Free SFTP filesystem, if loaded */ - if (rdp_client->sftp_filesystem) - guac_common_ssh_destroy_sftp_filesystem(rdp_client->sftp_filesystem); - - /* Free SFTP session */ - if (rdp_client->sftp_session) - guac_common_ssh_destroy_session(rdp_client->sftp_session); - - /* Free SFTP user */ - if (rdp_client->sftp_user) - guac_common_ssh_destroy_user(rdp_client->sftp_user); - - guac_common_ssh_uninit(); -#endif - -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT - /* Free display update module */ - guac_rdp_disp_free(rdp_client->disp); -#endif - - /* Free SVC list */ - guac_common_list_free(rdp_client->available_svc); - /* Free parsed settings */ if (rdp_client->settings != NULL) guac_rdp_settings_free(rdp_client->settings); + /* Free display update module */ + guac_rdp_disp_free(rdp_client->disp); + /* Free client data */ guac_common_clipboard_free(rdp_client->clipboard); - guac_common_display_free(rdp_client->display); free(rdp_client); return 0; diff --git a/src/protocols/rdp/input.c b/src/protocols/rdp/input.c index e87440a3..8a8cbaf6 100644 --- a/src/protocols/rdp/input.c +++ b/src/protocols/rdp/input.c @@ -25,27 +25,19 @@ #include "client.h" #include "input.h" #include "rdp.h" +#include "rdp_disp.h" #include "rdp_keymap.h" #include #include #include -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT -#include "rdp_disp.h" -#endif - #include #include int guac_rdp_send_keysym(guac_client* client, int keysym, int pressed) { guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - freerdp* rdp_inst = rdp_client->rdp_inst; - - /* Skip if not yet connected */ - if (rdp_inst == NULL) - return 0; /* If keysym can be in lookup table */ if (GUAC_RDP_KEYSYM_STORABLE(keysym)) { @@ -75,6 +67,13 @@ int guac_rdp_send_keysym(guac_client* client, int keysym, int pressed) { else pressed_flags = KBD_FLAGS_RELEASE; + /* Skip if not yet connected */ + freerdp* rdp_inst = rdp_client->rdp_inst; + if (rdp_inst == NULL) { + pthread_mutex_unlock(&(rdp_client->rdp_lock)); + return 0; + } + /* Send actual key */ rdp_inst->input->KeyboardEvent(rdp_inst->input, keysym_desc->flags | pressed_flags, keysym_desc->scancode); @@ -118,6 +117,13 @@ int guac_rdp_send_keysym(guac_client* client, int keysym, int pressed) { pthread_mutex_lock(&(rdp_client->rdp_lock)); + /* Skip if not yet connected */ + freerdp* rdp_inst = rdp_client->rdp_inst; + if (rdp_inst == NULL) { + pthread_mutex_unlock(&(rdp_client->rdp_lock)); + return 0; + } + /* Send Unicode event */ rdp_inst->input->UnicodeKeyboardEvent( rdp_inst->input, @@ -156,16 +162,18 @@ int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) { guac_client* client = user->client; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - freerdp* rdp_inst = rdp_client->rdp_inst; + + pthread_mutex_lock(&(rdp_client->rdp_lock)); /* Store current mouse location */ guac_common_cursor_move(rdp_client->display->cursor, user, x, y); /* Skip if not yet connected */ - if (rdp_inst == NULL) + freerdp* rdp_inst = rdp_client->rdp_inst; + if (rdp_inst == NULL) { + pthread_mutex_unlock(&(rdp_client->rdp_lock)); return 0; - - pthread_mutex_lock(&(rdp_client->rdp_lock)); + } /* If button mask unchanged, just send move event */ if (mask == rdp_client->mouse_button_mask) @@ -251,28 +259,19 @@ int guac_rdp_user_key_handler(guac_user* user, int keysym, int pressed) { int guac_rdp_user_size_handler(guac_user* user, int width, int height) { -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT guac_client* client = user->client; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - + guac_rdp_settings* settings = rdp_client->settings; freerdp* rdp_inst = rdp_client->rdp_inst; - /* Skip if not yet connected */ - if (rdp_inst == NULL) - return 0; - /* Convert client pixels to remote pixels */ - width = width * rdp_client->settings->resolution - / user->info.optimal_resolution; - - height = height * rdp_client->settings->resolution - / user->info.optimal_resolution; + width = width * settings->resolution / user->info.optimal_resolution; + height = height * settings->resolution / user->info.optimal_resolution; /* Send display update */ pthread_mutex_lock(&(rdp_client->rdp_lock)); - guac_rdp_disp_set_size(rdp_client->disp, rdp_inst->context, width, height); + guac_rdp_disp_set_size(rdp_client->disp, settings, rdp_inst, width, height); pthread_mutex_unlock(&(rdp_client->rdp_lock)); -#endif return 0; diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 9dea54b5..083ca0df 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -29,6 +29,7 @@ #include "rdp.h" #include "rdp_bitmap.h" #include "rdp_cliprdr.h" +#include "rdp_disp.h" #include "rdp_gdi.h" #include "rdp_glyph.h" #include "rdp_keymap.h" @@ -43,10 +44,6 @@ #include #endif -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT -#include "rdp_disp.h" -#endif - #include #include #include @@ -173,27 +170,30 @@ static int __guac_receive_channel_data(freerdp* rdp_inst, int channelId, static void guac_rdp_channel_connected(rdpContext* context, ChannelConnectedEventArgs* e) { + guac_client* client = ((rdp_freerdp_context*) context)->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdp_settings* settings = rdp_client->settings; + + if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) { #ifdef HAVE_RDPSETTINGS_SUPPORTDISPLAYCONTROL - /* Store reference to the display update plugin once it's connected */ - if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) { + /* 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; + DispClientContext* disp = (DispClientContext*) e->pInterface; - guac_client* client = ((rdp_freerdp_context*) context)->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + /* Init module with current display size */ + guac_rdp_disp_set_size(rdp_client->disp, rdp_client->settings, + context->instance, guac_rdp_get_width(context->instance), + guac_rdp_get_height(context->instance)); - /* Init module with current display size */ - guac_rdp_disp_set_size(rdp_client->disp, context, - guac_rdp_get_width(context->instance), - guac_rdp_get_height(context->instance)); + /* Store connected channel */ + guac_rdp_disp_connect(rdp_client->disp, disp); + guac_client_log(client, GUAC_LOG_DEBUG, + "Display update channel connected."); - /* Store connected channel */ - guac_rdp_disp_connect(rdp_client->disp, disp); - guac_client_log(client, GUAC_LOG_DEBUG, - "Display update channel connected."); - - } + } #endif + } } #endif @@ -201,16 +201,18 @@ static void guac_rdp_channel_connected(rdpContext* context, BOOL rdp_freerdp_pre_connect(freerdp* instance) { rdpContext* context = instance->context; - guac_client* client = ((rdp_freerdp_context*) context)->client; rdpChannels* channels = context->channels; + + guac_client* client = ((rdp_freerdp_context*) context)->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdp_settings* settings = rdp_client->settings; + rdpBitmap* bitmap; rdpGlyph* glyph; rdpPointer* pointer; rdpPrimaryUpdate* primary; CLRCONV* clrconv; - guac_rdp_client* rdp_client = - (guac_rdp_client*) client->data; #ifdef HAVE_FREERDP_REGISTER_ADDIN_PROVIDER /* Init FreeRDP add-in provider */ @@ -223,17 +225,15 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { (pChannelConnectedEventHandler) guac_rdp_channel_connected); #endif -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /* Load virtual channel management plugin */ if (freerdp_channels_load_plugin(channels, instance->settings, "drdynvc", instance->settings)) guac_client_log(client, GUAC_LOG_WARNING, "Failed to load drdynvc plugin."); - /* Init display update plugin */ - rdp_client->disp = guac_rdp_disp_alloc(); - guac_rdp_disp_load_plugin(instance->context); -#endif + /* Init display update plugin (if available and required) */ + if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) + guac_rdp_disp_load_plugin(instance->context); /* Load clipboard plugin */ if (freerdp_channels_load_plugin(channels, instance->settings, @@ -242,7 +242,7 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { "Failed to load cliprdr plugin. Clipboard will not work."); /* If audio enabled, choose an encoder */ - if (rdp_client->settings->audio_enabled) { + if (settings->audio_enabled) { rdp_client->audio = guac_audio_stream_alloc(client, NULL, GUAC_RDP_AUDIO_RATE, @@ -257,15 +257,15 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { } /* end if audio enabled */ /* Load filesystem if drive enabled */ - if (rdp_client->settings->drive_enabled) + if (settings->drive_enabled) rdp_client->filesystem = - guac_rdp_fs_alloc(client, rdp_client->settings->drive_path, - rdp_client->settings->create_drive_path); + guac_rdp_fs_alloc(client, settings->drive_path, + settings->create_drive_path); /* If RDPSND/RDPDR required, load them */ - if (rdp_client->settings->printing_enabled - || rdp_client->settings->drive_enabled - || rdp_client->settings->audio_enabled) { + if (settings->printing_enabled + || settings->drive_enabled + || settings->audio_enabled) { /* Load RDPDR plugin */ if (freerdp_channels_load_plugin(channels, instance->settings, @@ -285,15 +285,15 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { } /* Load RAIL plugin if RemoteApp in use */ - if (rdp_client->settings->remote_app != NULL) { + if (settings->remote_app != NULL) { #ifdef LEGACY_FREERDP RDP_PLUGIN_DATA* plugin_data = malloc(sizeof(RDP_PLUGIN_DATA) * 2); plugin_data[0].size = sizeof(RDP_PLUGIN_DATA); - plugin_data[0].data[0] = rdp_client->settings->remote_app; - plugin_data[0].data[1] = rdp_client->settings->remote_app_dir; - plugin_data[0].data[2] = rdp_client->settings->remote_app_args; + plugin_data[0].data[0] = settings->remote_app; + plugin_data[0].data[1] = settings->remote_app_dir; + plugin_data[0].data[2] = settings->remote_app_args; plugin_data[0].data[3] = NULL; plugin_data[1].size = 0; @@ -314,9 +314,9 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { } /* Load SVC plugin instances for all static channels */ - if (rdp_client->settings->svc_names != NULL) { + if (settings->svc_names != NULL) { - char** current = rdp_client->settings->svc_names; + char** current = settings->svc_names; do { guac_rdp_svc* svc = guac_rdp_alloc_svc(client, *current); @@ -691,9 +691,27 @@ static int rdp_guac_client_wait_for_messages(guac_client* client, } -void* guac_rdp_client_thread(void* data) { +/** + * Connects to an RDP server as described by the guac_rdp_settings structure + * associated with the given client, allocating and freeing all objects + * directly related to the RDP connection. It is expected that all objects + * which are independent of FreeRDP's state (the clipboard, display update + * management, etc.) will already be allocated and associated with the + * guac_rdp_client associated with the given guac_client. This function blocks + * for the duration of the RDP session, returning only after the session has + * completely disconnected. + * + * @param client + * The guac_client associated with the RDP settings describing the + * connection that should be established. + * + * @return + * Zero if the connection successfully terminated and a reconnect is + * desired, non-zero if an error occurs or the connection was disconnected + * and a reconnect is NOT desired. + */ +static int guac_rdp_handle_connection(guac_client* client) { - guac_client* client = (guac_client*) data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_settings* settings = rdp_client->settings; @@ -715,6 +733,9 @@ void* guac_rdp_client_thread(void* data) { rdp_client->current_surface = rdp_client->display->default_surface; + rdp_client->requested_clipboard_format = CB_FORMAT_TEXT; + rdp_client->available_svc = guac_common_list_alloc(); + #ifdef HAVE_FREERDP_CHANNELS_GLOBAL_INIT freerdp_channels_global_init(); #endif @@ -750,7 +771,7 @@ void* guac_rdp_client_thread(void* data) { /* Abort if username is missing */ if (settings->sftp_username == NULL) - return NULL; + return 1; guac_client_log(client, GUAC_LOG_DEBUG, "Connecting via SSH for SFTP filesystem access."); @@ -769,7 +790,7 @@ void* guac_rdp_client_thread(void* data) { settings->sftp_private_key, settings->sftp_passphrase)) { guac_common_ssh_destroy_user(rdp_client->sftp_user); - return NULL; + return 1; } } @@ -794,7 +815,7 @@ void* guac_rdp_client_thread(void* data) { if (rdp_client->sftp_session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ guac_common_ssh_destroy_user(rdp_client->sftp_user); - return NULL; + return 1; } /* Load and expose filesystem */ @@ -811,7 +832,7 @@ void* guac_rdp_client_thread(void* data) { if (rdp_client->sftp_filesystem == NULL) { guac_common_ssh_destroy_session(rdp_client->sftp_session); guac_common_ssh_destroy_user(rdp_client->sftp_user); - return NULL; + return 1; } guac_client_log(client, GUAC_LOG_DEBUG, @@ -833,7 +854,7 @@ void* guac_rdp_client_thread(void* data) { if (!freerdp_connect(rdp_inst)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Error connecting to RDP server"); - return NULL; + return 1; } /* Connection complete */ @@ -842,15 +863,17 @@ void* guac_rdp_client_thread(void* data) { guac_timestamp last_frame_end = guac_timestamp_current(); - /* Handle messages from RDP server while client is running */ - while (client->state == GUAC_CLIENT_RUNNING) { + /* Signal that reconnect has been completed */ + guac_rdp_disp_reconnect_complete(rdp_client->disp); + + /* Handle messages from RDP server while client is running */ + while (client->state == GUAC_CLIENT_RUNNING + && !guac_rdp_disp_reconnect_needed(rdp_client->disp)) { -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /* Update remote display size */ pthread_mutex_lock(&(rdp_client->rdp_lock)); - guac_rdp_disp_update_size(rdp_client->disp, rdp_inst->context); + guac_rdp_disp_update_size(rdp_client->disp, settings, rdp_inst); pthread_mutex_unlock(&(rdp_client->rdp_lock)); -#endif /* Wait for data and construct a reasonable frame */ int wait_result = rdp_guac_client_wait_for_messages(client, @@ -873,7 +896,7 @@ void* guac_rdp_client_thread(void* data) { guac_client_log(client, GUAC_LOG_DEBUG, "Error handling RDP file descriptors"); pthread_mutex_unlock(&(rdp_client->rdp_lock)); - return NULL; + return 1; } /* Check channel fds */ @@ -881,7 +904,7 @@ void* guac_rdp_client_thread(void* data) { guac_client_log(client, GUAC_LOG_DEBUG, "Error handling RDP channel file descriptors"); pthread_mutex_unlock(&(rdp_client->rdp_lock)); - return NULL; + return 1; } /* Check for channel events */ @@ -910,7 +933,7 @@ void* guac_rdp_client_thread(void* data) { guac_client_log(client, GUAC_LOG_INFO, "RDP server closed connection"); pthread_mutex_unlock(&(rdp_client->rdp_lock)); - return NULL; + return 1; } pthread_mutex_unlock(&(rdp_client->rdp_lock)); @@ -955,6 +978,62 @@ void* guac_rdp_client_thread(void* data) { } guac_client_log(client, GUAC_LOG_INFO, "Internal RDP client disconnected"); + + pthread_mutex_lock(&(rdp_client->rdp_lock)); + + /* Clean up RDP client */ + freerdp_channels_close(channels, rdp_inst); + freerdp_channels_free(channels); + freerdp_disconnect(rdp_inst); + freerdp_clrconv_free(((rdp_freerdp_context*) rdp_inst->context)->clrconv); + cache_free(rdp_inst->context->cache); + freerdp_free(rdp_inst); + + /* Clean up filesystem, if allocated */ + if (rdp_client->filesystem != NULL) + guac_rdp_fs_free(rdp_client->filesystem); + +#ifdef ENABLE_COMMON_SSH + /* Free SFTP filesystem, if loaded */ + if (rdp_client->sftp_filesystem) + guac_common_ssh_destroy_sftp_filesystem(rdp_client->sftp_filesystem); + + /* Free SFTP session */ + if (rdp_client->sftp_session) + guac_common_ssh_destroy_session(rdp_client->sftp_session); + + /* Free SFTP user */ + if (rdp_client->sftp_user) + guac_common_ssh_destroy_user(rdp_client->sftp_user); + + guac_common_ssh_uninit(); +#endif + + /* Free SVC list */ + guac_common_list_free(rdp_client->available_svc); + + /* Free display */ + guac_common_display_free(rdp_client->display); + + /* Mark FreeRDP instance as freed */ + rdp_client->rdp_inst = NULL; + + pthread_mutex_unlock(&(rdp_client->rdp_lock)); + return 0; + +} + +void* guac_rdp_client_thread(void* data) { + + guac_client* client = (guac_client*) data; + + while (client->state == GUAC_CLIENT_RUNNING) { + + if (guac_rdp_handle_connection(client)) + return NULL; + + } + return NULL; } diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index b648216c..fac0435d 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -29,6 +29,7 @@ #include "guac_display.h" #include "guac_surface.h" #include "guac_list.h" +#include "rdp_disp.h" #include "rdp_fs.h" #include "rdp_keymap.h" #include "rdp_settings.h" @@ -44,10 +45,6 @@ #include "guac_ssh_user.h" #endif -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT -#include "rdp_disp.h" -#endif - #include #include @@ -148,12 +145,10 @@ typedef struct guac_rdp_client { guac_common_ssh_sftp_filesystem* sftp_filesystem; #endif -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /** * Display size update module. */ guac_rdp_disp* disp; -#endif /** * List of all available static virtual channels. @@ -161,7 +156,9 @@ typedef struct guac_rdp_client { guac_common_list* available_svc; /** - * Lock which is locked and unlocked for each RDP message. + * Lock which is locked and unlocked for each RDP message, and for each + * part of the RDP client instance which may be dynamically freed and + * reallocated during reconnection. */ pthread_mutex_t rdp_lock; diff --git a/src/protocols/rdp/rdp_disp.c b/src/protocols/rdp/rdp_disp.c index a94d5949..bf316336 100644 --- a/src/protocols/rdp/rdp_disp.c +++ b/src/protocols/rdp/rdp_disp.c @@ -24,23 +24,30 @@ #include "client.h" #include "rdp.h" #include "rdp_disp.h" +#include "rdp_settings.h" #include -#include #include #include +#ifdef HAVE_FREERDP_CLIENT_DISP_H +#include +#endif + guac_rdp_disp* guac_rdp_disp_alloc() { guac_rdp_disp* disp = malloc(sizeof(guac_rdp_disp)); +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /* Not yet connected */ disp->disp = NULL; +#endif /* No requests have been made */ - disp->last_request = 0; + disp->last_request = guac_timestamp_current(); disp->requested_width = 0; disp->requested_height = 0; + disp->reconnect_needed = 0; return disp; @@ -52,6 +59,7 @@ void guac_rdp_disp_free(guac_rdp_disp* disp) { void guac_rdp_disp_load_plugin(rdpContext* context) { +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT #ifdef HAVE_RDPSETTINGS_SUPPORTDISPLAYCONTROL context->settings->SupportDisplayControl = TRUE; #endif @@ -62,12 +70,15 @@ void guac_rdp_disp_load_plugin(rdpContext* context) { args->argv = malloc(sizeof(char**) * 1); args->argv[0] = strdup("disp"); freerdp_dynamic_channel_collection_add(context->settings, args); +#endif } +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT void guac_rdp_disp_connect(guac_rdp_disp* guac_disp, DispClientContext* disp) { guac_disp->disp = disp; } +#endif /** * Fits a given dimension within the allowed bounds for Display Update @@ -111,8 +122,8 @@ static void guac_rdp_disp_fit(int* a, int* b) { } -void guac_rdp_disp_set_size(guac_rdp_disp* disp, rdpContext* context, - int width, int height) { +void guac_rdp_disp_set_size(guac_rdp_disp* disp, guac_rdp_settings* settings, + freerdp* rdp_inst, int width, int height) { /* Fit width within bounds, adjusting height to maintain aspect ratio */ guac_rdp_disp_fit(&width, &height); @@ -129,52 +140,74 @@ void guac_rdp_disp_set_size(guac_rdp_disp* disp, rdpContext* context, disp->requested_height = height; /* Send display update notification if possible */ - guac_rdp_disp_update_size(disp, context); + guac_rdp_disp_update_size(disp, settings, rdp_inst); } -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; +void guac_rdp_disp_update_size(guac_rdp_disp* disp, + guac_rdp_settings* settings, freerdp* rdp_inst) { 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 - }}; + /* Do not update size if no requests have been received */ + if (width == 0 || height == 0) + return; guac_timestamp now = guac_timestamp_current(); /* Limit display update frequency */ - if (disp->last_request != 0 - && now - disp->last_request <= GUAC_RDP_DISP_UPDATE_INTERVAL) + if (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)) + if (rdp_inst != NULL + && width == guac_rdp_get_width(rdp_inst) + && height == guac_rdp_get_height(rdp_inst)) 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); + + if (settings->resize_method == GUAC_RESIZE_RECONNECT) { + + /* Update settings with new dimensions */ + settings->width = width; + settings->height = height; + + /* Signal reconnect */ + disp->reconnect_needed = 1; + + } + + else if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) { +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT + 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 + }}; + + /* Send display update notification if display channel is connected */ + if (disp->disp != NULL) + disp->disp->SendMonitorLayout(disp->disp, 1, monitors); +#endif + } } +int guac_rdp_disp_reconnect_needed(guac_rdp_disp* disp) { + return disp->reconnect_needed; +} + +void guac_rdp_disp_reconnect_complete(guac_rdp_disp* disp) { + disp->reconnect_needed = 0; + disp->last_request = guac_timestamp_current(); +} + diff --git a/src/protocols/rdp/rdp_disp.h b/src/protocols/rdp/rdp_disp.h index 375f476f..d2462714 100644 --- a/src/protocols/rdp/rdp_disp.h +++ b/src/protocols/rdp/rdp_disp.h @@ -23,9 +23,14 @@ #ifndef GUAC_RDP_DISP_H #define GUAC_RDP_DISP_H -#include +#include "rdp_settings.h" + #include +#ifdef HAVE_FREERDP_CLIENT_DISP_H +#include +#endif + /** * The minimum value for width or height, in pixels. */ @@ -47,10 +52,12 @@ */ typedef struct guac_rdp_disp { +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /** * Display control interface. */ DispClientContext* disp; +#endif /** * The timestamp of the last display update request, or 0 if no request @@ -68,6 +75,12 @@ typedef struct guac_rdp_disp { */ int requested_height; + /** + * Whether the size has changed and the RDP connection must be closed and + * reestablished. + */ + int reconnect_needed; + } guac_rdp_disp; /** @@ -94,6 +107,7 @@ void guac_rdp_disp_free(guac_rdp_disp* disp); */ void guac_rdp_disp_load_plugin(rdpContext* context); +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /** * 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 @@ -105,6 +119,7 @@ void guac_rdp_disp_load_plugin(rdpContext* context); * display update channel. */ void guac_rdp_disp_connect(guac_rdp_disp* guac_disp, DispClientContext* disp); +#endif /** * Requests a display size update, which may then be sent immediately to the @@ -113,30 +128,72 @@ void guac_rdp_disp_connect(guac_rdp_disp* guac_disp, DispClientContext* disp); * be automatically altered to comply with the restrictions imposed by the * display update channel. * - * @param disp The display update module which should maintain the requested - * size, sending the corresponding display update request when - * appropriate. - * @param context The rdpContext associated with the active RDP session. - * @param width The desired display width, in pixels. Due to the restrictions - * of the RDP display update channel, this will be contrained to - * the range of 200 through 8192 inclusive, and rounded down to - * the nearest even number. - * @param height The desired display height, in pixels. Due to the restrictions - * of the RDP display update channel, this will be contrained to - * the range of 200 through 8192 inclusive. + * @param disp + * The display update module which should maintain the requested size, + * sending the corresponding display update request when appropriate. + * + * @param settings + * The RDP client settings associated with the current or pending RDP + * session. These settings will be automatically adjusted to match the new + * screen size. + * + * @param rdp_inst + * The FreeRDP instance associated with the current or pending RDP session, + * if any. If no RDP session is active, this should be NULL. + * + * @param width + * The desired display width, in pixels. Due to the restrictions of the RDP + * display update channel, this will be contrained to the range of 200 + * through 8192 inclusive, and rounded down to the nearest even number. + * + * @param height + * The desired display height, in pixels. Due to the restrictions of the + * RDP display update channel, this will be contrained to the range of 200 + * through 8192 inclusive. */ -void guac_rdp_disp_set_size(guac_rdp_disp* disp, rdpContext* context, - int width, int height); +void guac_rdp_disp_set_size(guac_rdp_disp* disp, guac_rdp_settings* settings, + freerdp* rdp_inst, 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. + * update may be delayed until a future call to this function. If the RDP + * session has not yet been established, the request will be delayed until the + * session exists. * - * @param disp The display update module which should track the update request. - * @param context The rdpContext associated with the active RDP session. + * @param disp + * The display update module which should track the update request. + * + * @param settings + * The RDP client settings associated with the current or pending RDP + * session. These settings will be automatically adjusted to match the new + * screen size. + * + * @param rdp_inst + * The FreeRDP instance associated with the current or pending RDP session, + * if any. If no RDP session is active, this should be NULL. */ -void guac_rdp_disp_update_size(guac_rdp_disp* disp, rdpContext* context); +void guac_rdp_disp_update_size(guac_rdp_disp* disp, + guac_rdp_settings* settings, freerdp* rdp_inst); + +/** + * Signals the given display update module that the requested reconnect has + * been performed. + * + * @param disp + * The display update module that should be signaled regarding the state + * of reconnection. + */ +void guac_rdp_disp_reconnect_complete(guac_rdp_disp* disp); + +/** + * Returns whether a full RDP reconnect is required for display update changes + * to take effect. + * + * @return + * Non-zero if a reconnect is needed, zero otherwise. + */ +int guac_rdp_disp_reconnect_needed(guac_rdp_disp* disp); #endif diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index 70902f8c..133da61e 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -92,6 +92,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "recording-path", "recording-name", "create-recording-path", + "resize-method", NULL }; @@ -374,6 +375,12 @@ enum RDP_ARGS_IDX { */ IDX_CREATE_RECORDING_PATH, + /** + * The method to use to apply screen size changes requested by the user. + * Valid values are blank, "display-update", and "reconnect". + */ + IDX_RESIZE_METHOD, + RDP_ARGS_COUNT }; @@ -710,6 +717,31 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, IDX_CREATE_RECORDING_PATH, 0); + /* No resize method */ + if (strcmp(argv[IDX_RESIZE_METHOD], "") == 0) { + guac_user_log(user, GUAC_LOG_INFO, "Resize method: none"); + settings->resize_method = GUAC_RESIZE_NONE; + } + + /* Resize method: "reconnect" */ + else if (strcmp(argv[IDX_RESIZE_METHOD], "reconnect") == 0) { + guac_user_log(user, GUAC_LOG_INFO, "Resize method: reconnect"); + settings->resize_method = GUAC_RESIZE_RECONNECT; + } + + /* Resize method: "display-update" */ + else if (strcmp(argv[IDX_RESIZE_METHOD], "display-update") == 0) { + guac_user_log(user, GUAC_LOG_INFO, "Resize method: display-update"); + settings->resize_method = GUAC_RESIZE_DISPLAY_UPDATE; + } + + /* Default to no resize method if invalid */ + else { + guac_user_log(user, GUAC_LOG_INFO, "Resize method \"%s\" invalid. ", + "Defaulting to no resize method.", argv[IDX_RESIZE_METHOD]); + settings->resize_method = GUAC_RESIZE_NONE; + } + /* Success */ return settings; diff --git a/src/protocols/rdp/rdp_settings.h b/src/protocols/rdp/rdp_settings.h index f4c979ab..80c15b90 100644 --- a/src/protocols/rdp/rdp_settings.h +++ b/src/protocols/rdp/rdp_settings.h @@ -88,6 +88,32 @@ typedef enum guac_rdp_security { } guac_rdp_security; +/** + * All supported combinations screen resize methods. + */ +typedef enum guac_rdp_resize_method { + + /** + * Dynamic resizing of the display will not be attempted. + */ + GUAC_RESIZE_NONE, + + /** + * Dynamic resizing will be attempted through sending requests along the + * Display Update channel. This will only work with recent versions of + * Windows and relatively-recent versions of FreeRDP. + */ + GUAC_RESIZE_DISPLAY_UPDATE, + + /** + * Guacamole will automatically disconnect and reconnect to the RDP server + * whenever the screen size changes, requesting the new size during + * reconnect. + */ + GUAC_RESIZE_RECONNECT + +} guac_rdp_resize_method; + /** * All settings supported by the Guacamole RDP client. */ @@ -350,6 +376,11 @@ typedef struct guac_rdp_settings { */ int create_recording_path; + /** + * The method to apply when the user's display changes size. + */ + guac_rdp_resize_method resize_method; + } guac_rdp_settings; /**