From 12c98e331c1dc85b36f2c194fc067ba7fbd29cfe Mon Sep 17 00:00:00 2001 From: Virtually Nick Date: Mon, 27 Nov 2023 17:36:51 -0500 Subject: [PATCH] GUACAMOLE-1196: Add VNC protocol support for updating display size. --- src/protocols/vnc/client.c | 17 +++++++ src/protocols/vnc/display.c | 89 ++++++++++++++++++++++++++++++++++++- src/protocols/vnc/display.h | 82 +++++++++++++++++++++++++++++++++- src/protocols/vnc/input.c | 16 +++++++ src/protocols/vnc/input.h | 10 ++++- src/protocols/vnc/user.c | 5 +++ src/protocols/vnc/vnc.h | 11 +++++ 7 files changed, 226 insertions(+), 4 deletions(-) diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index 43ead098f..b53fc0087 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -106,11 +106,21 @@ int guac_client_init(guac_client* client) { guac_vnc_client* vnc_client = guac_mem_zalloc(sizeof(guac_vnc_client)); client->data = vnc_client; +#ifdef ENABLE_VNC_DESKTOP_SIZE + guac_client_log(client, GUAC_LOG_DEBUG, "Support for desktop sizing enabled."); + vnc_client->vnc_display = guac_vnc_display_update_alloc(client); +#else + guac_client_log(client, GUAC_LOG_WARNING, "VNC client does not support desktop sizing."); +#endif + #ifdef ENABLE_VNC_TLS_LOCKING /* Initialize the TLS write lock */ 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 +219,13 @@ 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)); + +#ifdef ENABLE_VNC_DESKTOP_SIZE + guac_vnc_display_update_free(vnc_client->vnc_display); +#endif + /* 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..b244bad3e 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,92 @@ void guac_vnc_copyrect(rfbClient* client, int src_x, int src_y, int w, int h, in } +#ifdef ENABLE_VNC_DESKTOP_SIZE + +guac_vnc_display* guac_vnc_display_update_alloc(guac_client* client) { + + guac_vnc_display* display = guac_mem_alloc(sizeof(guac_vnc_display)); + display->client = client; + + /* No requests have been made */ + display->last_request = guac_timestamp_current(); + display->requested_width = 0; + display->requested_height = 0; + + return display; + +} + +void guac_vnc_display_update_free(guac_vnc_display* display) { + guac_mem_free(display); +} + +/** + * This function does the actual work of updating the display size if adequate + * time has passed since the last update and the size is actually different. +* + * @param display + * The data structure that contains information used for determing if and + * when display updates should be allowed. + * + * @param rfb_client + * A pointer to the VNC (RFB) client. + */ +static void guac_vnc_display_update_size(guac_vnc_display* display, + rfbClient* rfb_client) { + + int width = display->requested_width; + int height = display->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 (now - display->last_request <= GUAC_COMMON_DISPLAY_UPDATE_INTERVAL) + return; + + /* Do NOT send requests unless the size will change */ + if (rfb_client != NULL + && width == rfb_client->screen.width + && height == rfb_client->screen.height) + return; + + /* Send the display size update. */ + guac_vnc_client* vnc_client = (guac_vnc_client*) display->client->data; + pthread_mutex_lock(&(vnc_client->message_lock)); + SendExtDesktopSize(rfb_client, width, height); + display->last_request = now; + pthread_mutex_unlock(&(vnc_client->message_lock)); + +} + +void guac_vnc_display_set_size(guac_vnc_display* display, + rfbClient* rfb_client, int width, int height) { + + /* 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); + + /* Width must be even */ + if (width % 2 == 1) + width -= 1; + + /* Store deferred size */ + display->requested_width = width; + display->requested_height = height; + + /* Send display update notification if possible */ + guac_vnc_display_update_size(display, rfb_client); + +} + +#endif + void guac_vnc_set_pixel_format(rfbClient* client, int color_depth) { client->format.trueColour = 1; switch(color_depth) { @@ -205,4 +293,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..7221167fe 100644 --- a/src/protocols/vnc/display.h +++ b/src/protocols/vnc/display.h @@ -26,8 +26,36 @@ #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- + * Display size update module for VNC. + */ +typedef struct guac_vnc_display { + + /** + * The guac_client instance handling the relevant VNC connection. + */ + guac_client* client; + + /** + * 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_vnc_display; + +/** + * 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 +112,56 @@ 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); +#ifdef ENABLE_VNC_DESKTOP_SIZE + +/** + * Allocates a new VNC display update module, which will keep track of the data + * needed to handle display updates. + * + * @param client + * The guac_client instance handling the relevant VNC connection. + * + * @return + * A newly-allocated VNC display update module. + */ +guac_vnc_display* guac_vnc_display_update_alloc(guac_client* client); + +/** + * Frees the resources associated with the data structure that keeps track of + * items related to VNC display updates. Only resources specific to Guacamole + * are freed. Resources that are part of the rfbClient will be freed separately. + * If no resources are currently allocated for Display Update support, this + * function has no effect. + * + * @param display + * The display update module to free. + */ +void guac_vnc_display_update_free(guac_vnc_display* display); + +/** + * 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 display update object that tracks information related to display + * update requests. + * + * @param rfb_client + * The data structure containing the VNC client that is used by this + * connection. + * + * @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(guac_vnc_display* display, rfbClient* rfb_client, + int width, int height); + +#endif + /** * 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..47f896f6a 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,18 @@ int guac_vnc_user_key_handler(guac_user* user, int keysym, int pressed) { return 0; } +#ifdef ENABLE_VNC_DESKTOP_SIZE + +int guac_vnc_user_size_handler(guac_user* user, int width, int height) { + + guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data; + rfbClient* rfb_client = vnc_client->rfb_client; + + /* Send display update */ + guac_vnc_display_set_size(vnc_client->vnc_display, rfb_client, width, height); + + return 0; + +} + +#endif \ No newline at end of file diff --git a/src/protocols/vnc/input.h b/src/protocols/vnc/input.h index ced9f3079..f8494d1c7 100644 --- a/src/protocols/vnc/input.h +++ b/src/protocols/vnc/input.h @@ -34,5 +34,13 @@ guac_user_mouse_handler guac_vnc_user_mouse_handler; */ guac_user_key_handler guac_vnc_user_key_handler; -#endif +#ifdef ENABLE_VNC_DESKTOP_SIZE +/** + * Handler for Guacamole user resize events. + */ +guac_user_size_handler guac_vnc_user_size_handler; + +#endif // ENABLE_VNC_DESKTOP_SIZE + +#endif // GUAC_VNC_INPUT_H diff --git a/src/protocols/vnc/user.c b/src/protocols/vnc/user.c index 42bae34aa..8993a8353 100644 --- a/src/protocols/vnc/user.c +++ b/src/protocols/vnc/user.c @@ -91,6 +91,11 @@ int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) { user->file_handler = guac_vnc_sftp_file_handler; #endif +#ifdef ENABLE_VNC_DESKTOP_SIZE + /* Handle display size changes. */ + user->size_handler = guac_vnc_user_size_handler; +#endif + } /** diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h index bdc62e6de..447318051 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. */ @@ -135,6 +141,11 @@ typedef struct guac_vnc_client { */ guac_iconv_write* clipboard_writer; + /** + * VNC display update module. + */ + guac_vnc_display* vnc_display; + } guac_vnc_client; /**