GUAC-1164: Automatically reconnect when display size changes.

This commit is contained in:
Michael Jumper 2016-03-15 21:23:19 -07:00
parent f5f77fea35
commit fc40e9f14c
6 changed files with 235 additions and 152 deletions

View File

@ -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 <guac_ssh_user.h>
#endif
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
#include "rdp_disp.h"
#endif
#include <freerdp/cache/cache.h>
#include <freerdp/channels/channels.h>
#include <freerdp/freerdp.h>
@ -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;

View File

@ -25,27 +25,19 @@
#include "client.h"
#include "input.h"
#include "rdp.h"
#include "rdp_disp.h"
#include "rdp_keymap.h"
#include <freerdp/freerdp.h>
#include <freerdp/input.h>
#include <guacamole/client.h>
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
#include "rdp_disp.h"
#endif
#include <pthread.h>
#include <stdlib.h>
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;

View File

@ -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 <guac_ssh_user.h>
#endif
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
#include "rdp_disp.h"
#endif
#include <freerdp/cache/bitmap.h>
#include <freerdp/cache/brush.h>
#include <freerdp/cache/glyph.h>
@ -183,8 +180,8 @@ static void guac_rdp_channel_connected(rdpContext* context,
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Init module with current display size */
guac_rdp_disp_set_size(rdp_client->disp, context,
guac_rdp_get_width(context->instance),
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));
/* Store connected channel */
@ -223,17 +220,14 @@ 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();
/* Init display update plugin (if available) */
guac_rdp_disp_load_plugin(instance->context);
#endif
/* Load clipboard plugin */
if (freerdp_channels_load_plugin(channels, instance->settings,
@ -691,9 +685,8 @@ static int rdp_guac_client_wait_for_messages(guac_client* client,
}
void* guac_rdp_client_thread(void* data) {
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 +708,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 +746,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 +765,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 +790,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 +807,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 +829,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 +838,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 +871,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 +879,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 +908,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 +953,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;
}

View File

@ -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 <pthread.h>
#include <stdint.h>
@ -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;

View File

@ -24,6 +24,7 @@
#include "client.h"
#include "rdp.h"
#include "rdp_disp.h"
#include "rdp_settings.h"
#include <freerdp/freerdp.h>
#include <freerdp/client/disp.h>
@ -41,6 +42,7 @@ guac_rdp_disp* guac_rdp_disp_alloc() {
disp->last_request = 0;
disp->requested_width = 0;
disp->requested_height = 0;
disp->reconnect_needed = 0;
return disp;
@ -52,6 +54,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,6 +65,7 @@ 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
}
@ -111,8 +115,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,21 +133,47 @@ 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;
/* 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)
return;
/* Do NOT send requests unless the size will change */
if (rdp_inst != NULL
&& width == guac_rdp_get_width(rdp_inst)
&& height == guac_rdp_get_height(rdp_inst))
return;
disp->last_request = now;
if (1) {
/* Update settings with new dimensions */
settings->width = width;
settings->height = height;
/* Signal reconnect */
disp->reconnect_needed = 1;
disp->disp = NULL;
return;
}
#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT
DISPLAY_CONTROL_MONITOR_LAYOUT monitors[1] = {{
.Flags = 0x1, /* DISPLAYCONTROL_MONITOR_PRIMARY */
.Left = 0,
@ -157,24 +187,18 @@ void guac_rdp_disp_update_size(guac_rdp_disp* disp, rdpContext* context) {
.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);
/* 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;
}

View File

@ -23,6 +23,8 @@
#ifndef GUAC_RDP_DISP_H
#define GUAC_RDP_DISP_H
#include "rdp_settings.h"
#include <freerdp/client/disp.h>
#include <freerdp/freerdp.h>
@ -68,6 +70,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;
/**
@ -113,30 +121,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