diff --git a/src/common/common/display.h b/src/common/common/display.h index 33950a177..92999930b 100644 --- a/src/common/common/display.h +++ b/src/common/common/display.h @@ -28,6 +28,22 @@ #include +/** + * The minimum display size (height or width) in pixels. + */ +#define GUAC_COMMON_DISPLAY_MIN_SIZE 200 + +/** + * The maximum display size (height or width) in pixels. + */ +#define GUAC_COMMON_DISPLAY_MAX_SIZE 8192 + +/** + * The minimum amount of time that must elapse between display size updates, + * in milliseconds. + */ +#define GUAC_COMMON_DISPLAY_UPDATE_INTERVAL 500 + /** * A list element representing a pairing of a Guacamole layer with a * corresponding guac_common_surface which wraps that layer. Adjacent layers @@ -135,6 +151,19 @@ typedef struct guac_common_display { guac_common_display* guac_common_display_alloc(guac_client* client, int width, int height); +/** + * Fits a given dimension within the allowed bounds for display sizing, + * adjusting the other dimension such that aspect ratio is maintained. + * + * @param a + * The dimension to fit within allowed bounds. + * + * @param b + * The other dimension to adjust if and only if necessary to preserve + * aspect ratio. + */ +void guac_common_display_fit(int* a, int* b); + /** * Frees the given display, and any associated resources, including any * allocated buffers/layers. @@ -258,5 +287,7 @@ void guac_common_display_free_buffer(guac_common_display* display, void guac_common_display_set_lossless(guac_common_display* display, int lossless); + + #endif diff --git a/src/common/display.c b/src/common/display.c index 00cee3b37..a17b2ecca 100644 --- a/src/common/display.c +++ b/src/common/display.c @@ -187,6 +187,37 @@ void guac_common_display_dup( } +void guac_common_display_fit(int* a, int* b) { + + int a_value = *a; + int b_value = *b; + + /* Ensure first dimension is within allowed range */ + if (a_value < GUAC_COMMON_DISPLAY_MIN_SIZE) { + + /* Adjust other dimension to maintain aspect ratio */ + int adjusted_b = b_value * GUAC_COMMON_DISPLAY_MIN_SIZE / a_value; + if (adjusted_b > GUAC_COMMON_DISPLAY_MAX_SIZE) + adjusted_b = GUAC_COMMON_DISPLAY_MAX_SIZE; + + *a = GUAC_COMMON_DISPLAY_MIN_SIZE; + *b = adjusted_b; + + } + else if (a_value > GUAC_COMMON_DISPLAY_MAX_SIZE) { + + /* Adjust other dimension to maintain aspect ratio */ + int adjusted_b = b_value * GUAC_COMMON_DISPLAY_MAX_SIZE / a_value; + if (adjusted_b < GUAC_COMMON_DISPLAY_MIN_SIZE) + adjusted_b = GUAC_COMMON_DISPLAY_MIN_SIZE; + + *a = GUAC_COMMON_DISPLAY_MAX_SIZE; + *b = adjusted_b; + + } + +} + void guac_common_display_set_lossless(guac_common_display* display, int lossless) { diff --git a/src/protocols/rdp/channels/disp.c b/src/protocols/rdp/channels/disp.c index 8a9302742..81c2d3bd3 100644 --- a/src/protocols/rdp/channels/disp.c +++ b/src/protocols/rdp/channels/disp.c @@ -18,6 +18,7 @@ */ #include "channels/disp.h" +#include "common/display.h" #include "plugins/channels.h" #include "fs.h" #include "rdp.h" @@ -149,56 +150,14 @@ void guac_rdp_disp_load_plugin(rdpContext* context) { } -/** - * Fits a given dimension within the allowed bounds for Display Update - * messages, adjusting the other dimension such that aspect ratio is - * maintained. - * - * @param a The dimension to fit within allowed bounds. - * - * @param b - * The other dimension to adjust if and only if necessary to preserve - * aspect ratio. - */ -static void guac_rdp_disp_fit(int* a, int* b) { - - int a_value = *a; - int b_value = *b; - - /* Ensure first dimension is within allowed range */ - if (a_value < GUAC_RDP_DISP_MIN_SIZE) { - - /* Adjust other dimension to maintain aspect ratio */ - int adjusted_b = b_value * GUAC_RDP_DISP_MIN_SIZE / a_value; - if (adjusted_b > GUAC_RDP_DISP_MAX_SIZE) - adjusted_b = GUAC_RDP_DISP_MAX_SIZE; - - *a = GUAC_RDP_DISP_MIN_SIZE; - *b = adjusted_b; - - } - else if (a_value > GUAC_RDP_DISP_MAX_SIZE) { - - /* Adjust other dimension to maintain aspect ratio */ - int adjusted_b = b_value * GUAC_RDP_DISP_MAX_SIZE / a_value; - if (adjusted_b < GUAC_RDP_DISP_MIN_SIZE) - adjusted_b = GUAC_RDP_DISP_MIN_SIZE; - - *a = GUAC_RDP_DISP_MAX_SIZE; - *b = adjusted_b; - - } - -} - 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); + guac_common_display_fit(&width, &height); /* Fit height within bounds, adjusting width to maintain aspect ratio */ - guac_rdp_disp_fit(&height, &width); + guac_common_display_fit(&height, &width); /* Width must be even */ if (width % 2 == 1) @@ -226,7 +185,7 @@ void guac_rdp_disp_update_size(guac_rdp_disp* disp, guac_timestamp now = guac_timestamp_current(); /* Limit display update frequency */ - if (now - disp->last_request <= GUAC_RDP_DISP_UPDATE_INTERVAL) + if (now - disp->last_request <= GUAC_COMMON_DISPLAY_UPDATE_INTERVAL) return; /* Do NOT send requests unless the size will change */ diff --git a/src/protocols/rdp/channels/disp.h b/src/protocols/rdp/channels/disp.h index 54fe4ccfa..91beebad9 100644 --- a/src/protocols/rdp/channels/disp.h +++ b/src/protocols/rdp/channels/disp.h @@ -27,22 +27,6 @@ #include #include -/** - * The minimum value for width or height, in pixels. - */ -#define GUAC_RDP_DISP_MIN_SIZE 200 - -/** - * The maximum value for width or height, in pixels. - */ -#define GUAC_RDP_DISP_MAX_SIZE 8192 - -/** - * The minimum amount of time that must elapse between display size updates, - * in milliseconds. - */ -#define GUAC_RDP_DISP_UPDATE_INTERVAL 500 - /** * Display size update module. */ diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index 43ead098f..2131ae0a6 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -111,6 +111,9 @@ int guac_client_init(guac_client* client) { pthread_mutex_init(&vnc_client->tls_lock, NULL); #endif + /* Initialize the message lock. */ + pthread_mutex_init(&(vnc_client->message_lock), NULL); + /* Init clipboard */ vnc_client->clipboard = guac_common_clipboard_alloc(); @@ -209,6 +212,9 @@ int guac_vnc_client_free_handler(guac_client* client) { pthread_mutex_destroy(&(vnc_client->tls_lock)); #endif + /* Clean up the message lock. */ + pthread_mutex_destroy(&(vnc_client->message_lock)); + /* Free generic data struct */ guac_mem_free(client->data); diff --git a/src/protocols/vnc/display.c b/src/protocols/vnc/display.c index 8848e8933..25494b446 100644 --- a/src/protocols/vnc/display.c +++ b/src/protocols/vnc/display.c @@ -20,6 +20,7 @@ #include "config.h" #include "client.h" +#include "display.h" #include "common/iconv.h" #include "common/surface.h" #include "vnc.h" @@ -30,6 +31,7 @@ #include #include #include +#include #include #include @@ -153,6 +155,140 @@ void guac_vnc_copyrect(rfbClient* client, int src_x, int src_y, int w, int h, in } +/** + * This function does the actual work of sending the message to the RFB/VNC + * server to request the resize, and then makes sure that the client frame + * buffer is updated, as well. + * + * @param client + * The remote frame buffer client that is triggering the resize + * request. + * + * @param width + * The updated width of the screen. + * + * @param height + * The updated height of the screen. + * + * @return + * TRUE if the screen update was sent to the server, otherwise false. Note + * that a successful send of the resize message to the server does NOT mean + * that the server has any obligation to resize the display - it only + * indicates that the VNC library has successfully sent the request. + */ +static rfbBool guac_vnc_send_desktop_size(rfbClient* client, int width, int height) { + + /* Get the Guacamole client data */ + guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); + + /* Don't send an update if the sreen appears to be uninitialized. */ + if (client->screen.width == 0 || client->screen.height == 0) { + guac_client_log(gc, GUAC_LOG_ERROR, "Screen has not been initialized, cannot send resize."); + return FALSE; + } + + /* Don't send an update if the requested dimensions are identical to current dimensions. */ + if (client->screen.width == rfbClientSwap16IfLE(width) && client->screen.height == rfbClientSwap16IfLE(height)) { + guac_client_log(gc, GUAC_LOG_WARNING, "Screen size has not changed, not sending update."); + return FALSE; + } + + /** + * Note: The RFB protocol requires two message types to be sent during a + * resize request - the first for the desktop size (total size of all + * monitors), and then a message for each screen that is attached to the + * remote server. Both libvncclient and Guacamole only support a single + * screen, so we send the desktop resize and screen resize with (nearly) + * identical data, but if one or both of these components is updated in the + * future to support multiple screens, this will need to be re-worked. + */ + + /* Set up the messages. */ + rfbSetDesktopSizeMsg size_msg; + rfbExtDesktopScreen new_screen; + + /* Configure the desktop size update message. */ + size_msg.type = rfbSetDesktopSize; + size_msg.width = rfbClientSwap16IfLE(width); + size_msg.height = rfbClientSwap16IfLE(height); + size_msg.numberOfScreens = 1; + + /* Configure the screen update message. */ + new_screen.id = client->screen.id; + new_screen.x = client->screen.x; + new_screen.y = client->screen.y; + new_screen.flags = client->screen.flags; + new_screen.width = rfbClientSwap16IfLE(width); + new_screen.height = rfbClientSwap16IfLE(height); + + /* Stop updates while the resize is in progress. */ + client->requestedResize = TRUE; + + /* Send the resize messages to the remote server. */ + if (!WriteToRFBServer(client, (char *)&size_msg, sz_rfbSetDesktopSizeMsg) + || !WriteToRFBServer(client, (char *)&new_screen, sz_rfbExtDesktopScreen)) { + + guac_client_log(gc, GUAC_LOG_ERROR, "Failed to send new desktop and screen size to the VNC server."); + return FALSE; + + } + + /* Update the client frame buffer with the requested size. */ + client->screen.width = rfbClientSwap16IfLE(width); + client->screen.height = rfbClientSwap16IfLE(height); + + /* Request a full screen update. */ + client->requestedResize = FALSE; + if (!SendFramebufferUpdateRequest(client, 0, 0, width, height, FALSE)) { + guac_client_log(gc, GUAC_LOG_WARNING, "Failed to request a full screen update."); + } + + /* Update should be successful. */ + return TRUE; +} + +void* guac_vnc_display_set_owner_size(guac_user* owner, void* data) { + + /* Pull RFB clients from provided data. */ + rfbClient* rfb_client = (rfbClient*) data; + + guac_user_log(owner, GUAC_LOG_DEBUG, "Sending VNC display size for owner's display."); + + /* Set the display size. */ + guac_vnc_display_set_size(rfb_client, owner->info.optimal_width, owner->info.optimal_height); + + /* Always return NULL. */ + return NULL; + +} + +void guac_vnc_display_set_size(rfbClient* client, int width, int height) { + + /* Get the VNC client */ + guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); + guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; + + /* Fit width within bounds, adjusting height to maintain aspect ratio */ + guac_common_display_fit(&width, &height); + + /* Fit height within bounds, adjusting width to maintain aspect ratio */ + guac_common_display_fit(&height, &width); + + /* Acquire the lock for sending messages to server. */ + pthread_mutex_lock(&(vnc_client->message_lock)); + + /* Send the display size update. */ + guac_client_log(gc, GUAC_LOG_TRACE, "Setting VNC display size."); + if (guac_vnc_send_desktop_size(client, width, height)) + guac_client_log(gc, GUAC_LOG_TRACE, "Successfully sent desktop size message."); + else + guac_client_log(gc, GUAC_LOG_TRACE, "Failed to send desktop size message."); + + /* Release the lock. */ + pthread_mutex_unlock(&(vnc_client->message_lock)); + +} + void guac_vnc_set_pixel_format(rfbClient* client, int color_depth) { client->format.trueColour = 1; switch(color_depth) { @@ -205,4 +341,3 @@ rfbBool guac_vnc_malloc_framebuffer(rfbClient* rfb_client) { /* Use original, wrapped proc */ return vnc_client->rfb_MallocFrameBuffer(rfb_client); } - diff --git a/src/protocols/vnc/display.h b/src/protocols/vnc/display.h index 831f012f5..a5b56096c 100644 --- a/src/protocols/vnc/display.h +++ b/src/protocols/vnc/display.h @@ -26,8 +26,8 @@ #include /** - * Callback invoked by libVNCServer when it receives a new binary image data. - * the VNC server. The image itself will be stored in the designated sub- + * Callback invoked by libVNCServer when it receives a new binary image data + * from the VNC server. The image itself will be stored in the designated sub- * rectangle of client->framebuffer. * * @param client @@ -84,6 +84,39 @@ void guac_vnc_update(rfbClient* client, int x, int y, int w, int h); void guac_vnc_copyrect(rfbClient* client, int src_x, int src_y, int w, int h, int dest_x, int dest_y); +/** + * A callback for guac_client_for_owner that sets the VNC display size to the + * width and height of the owner's display. + * + * @param owner + * A pointer to the guac_user data structure that contains the owner of + * the current connection. + * + * @param data + * A pointer to the rfbClient data structure that represents the current + * VNC client for the current connection. + * + * @return + * This callback always returns NULL. + */ +void* guac_vnc_display_set_owner_size(guac_user* owner, void* data); + +/** + * Attempts to set the display size of the remote server to the size requested + * by the client, usually as part of a client (browser) resize, if supported by + * both the VNC client and the remote server. + * + * @param display + * The VNC client to which the display size update should be sent. + * + * @param width + * The width that is being requested, in pixels. + * + * @param height + * The height that is being requested, in pixels. + */ +void guac_vnc_display_set_size(rfbClient* client, int width, int height); + /** * Sets the pixel format to request of the VNC server. The request will be made * during the connection handshake with the VNC server using the values diff --git a/src/protocols/vnc/input.c b/src/protocols/vnc/input.c index 584819183..3f734cdc5 100644 --- a/src/protocols/vnc/input.c +++ b/src/protocols/vnc/input.c @@ -21,6 +21,7 @@ #include "common/cursor.h" #include "common/display.h" +#include "display.h" #include "vnc.h" #include @@ -64,3 +65,16 @@ int guac_vnc_user_key_handler(guac_user* user, int keysym, int pressed) { return 0; } +int guac_vnc_user_size_handler(guac_user* user, int width, int height) { + + guac_user_log(user, GUAC_LOG_TRACE, "Running user size handler."); + + /* Get the Guacamole VNC client */ + guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data; + + /* Send display update */ + guac_vnc_display_set_size(vnc_client->rfb_client, width, height); + + return 0; + +} diff --git a/src/protocols/vnc/input.h b/src/protocols/vnc/input.h index ced9f3079..4bfb9e2d5 100644 --- a/src/protocols/vnc/input.h +++ b/src/protocols/vnc/input.h @@ -34,5 +34,9 @@ guac_user_mouse_handler guac_vnc_user_mouse_handler; */ guac_user_key_handler guac_vnc_user_key_handler; -#endif +/** + * Handler for Guacamole user resize events. + */ +guac_user_size_handler guac_vnc_user_size_handler; +#endif // GUAC_VNC_INPUT_H diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index 09ee8a7a4..c7dda5890 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -38,6 +38,7 @@ const char* GUAC_VNC_CLIENT_ARGS[] = { "hostname", "port", "read-only", + "disable-display-resize", "encodings", GUAC_VNC_ARGV_USERNAME, GUAC_VNC_ARGV_PASSWORD, @@ -119,6 +120,13 @@ enum VNC_ARGS_IDX { */ IDX_READ_ONLY, + /** + * "true" if the VNC client should disable attempts to resize the remote + * display to the client's size, "false" or blank if those resize messages + * should be sent. + */ + IDX_DISABLE_DISPLAY_RESIZE, + /** * Space-separated list of encodings to use within the VNC session. If not * specified, this will be: @@ -469,6 +477,11 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, IDX_DISABLE_SERVER_INPUT, false); + /* Disable display resize */ + settings->disable_display_resize = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_DISABLE_DISPLAY_RESIZE, false); + /* Parse color depth */ settings->color_depth = guac_user_parse_args_int(user, GUAC_VNC_CLIENT_ARGS, argv, diff --git a/src/protocols/vnc/settings.h b/src/protocols/vnc/settings.h index 3b885816d..e5c3b7798 100644 --- a/src/protocols/vnc/settings.h +++ b/src/protocols/vnc/settings.h @@ -54,6 +54,12 @@ typedef struct guac_vnc_settings { */ char* password; + /** + * Disable the VNC client messages to request that the remote (server) + * display resize to match the client resolution. + */ + bool disable_display_resize; + /** * Space-separated list of encodings to use within the VNC session. */ diff --git a/src/protocols/vnc/user.c b/src/protocols/vnc/user.c index 42bae34aa..4cb351769 100644 --- a/src/protocols/vnc/user.c +++ b/src/protocols/vnc/user.c @@ -91,6 +91,10 @@ int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) { user->file_handler = guac_vnc_sftp_file_handler; #endif + /* If user is owner, set size handler. */ + if (user->owner && !settings->disable_display_resize) + user->size_handler = guac_vnc_user_size_handler; + } /** diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index ec2c505d4..b1bd86087 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -456,11 +456,17 @@ void* guac_vnc_client_thread(void* data) { msg.status = 1; msg.pad = 0; + /* Acquire lock for writing to server. */ + pthread_mutex_lock(&(vnc_client->message_lock)); + if (WriteToRFBServer(rfb_client, (char*)&msg, sz_rfbSetServerInputMsg)) guac_client_log(client, GUAC_LOG_DEBUG, "Successfully sent request to disable server input."); else guac_client_log(client, GUAC_LOG_WARNING, "Failed to send request to disable server input."); + + /* Release lock. */ + pthread_mutex_unlock(&(vnc_client->message_lock)); } /* Set remaining client data */ @@ -503,6 +509,10 @@ void* guac_vnc_client_thread(void* data) { } + /* Update the display with the owner's screen size. */ + if (!settings->disable_display_resize) + guac_client_for_owner(client, guac_vnc_display_set_owner_size, rfb_client); + guac_socket_flush(client->socket); guac_timestamp last_frame_end = guac_timestamp_current(); diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h index bdc62e6de..49477304b 100644 --- a/src/protocols/vnc/vnc.h +++ b/src/protocols/vnc/vnc.h @@ -26,6 +26,7 @@ #include "common/display.h" #include "common/iconv.h" #include "common/surface.h" +#include "display.h" #include "settings.h" #include @@ -63,6 +64,11 @@ typedef struct guac_vnc_client { pthread_mutex_t tls_lock; #endif + /** + * Lock which synchronizes messages sent to VNC server. + */ + pthread_mutex_t message_lock; + /** * The underlying VNC client. */