Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GUACAMOLE-1196: Implement support for VNC display size updates #469

Merged
merged 7 commits into from
Aug 6, 2024
31 changes: 31 additions & 0 deletions src/common/common/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@

#include <pthread.h>

/**
* 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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

31 changes: 31 additions & 0 deletions src/common/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {

Expand Down
49 changes: 4 additions & 45 deletions src/protocols/rdp/channels/disp.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

#include "channels/disp.h"
#include "common/display.h"
#include "plugins/channels.h"
#include "fs.h"
#include "rdp.h"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 */
Expand Down
16 changes: 0 additions & 16 deletions src/protocols/rdp/channels/disp.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,6 @@
#include <guacamole/client.h>
#include <guacamole/timestamp.h>

/**
* 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.
*/
Expand Down
6 changes: 6 additions & 0 deletions src/protocols/vnc/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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);

Expand Down
137 changes: 136 additions & 1 deletion src/protocols/vnc/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "config.h"

#include "client.h"
#include "display.h"
#include "common/iconv.h"
#include "common/surface.h"
#include "vnc.h"
Expand All @@ -30,6 +31,7 @@
#include <guacamole/mem.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/timestamp.h>
#include <rfb/rfbclient.h>
#include <rfb/rfbproto.h>

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -205,4 +341,3 @@ rfbBool guac_vnc_malloc_framebuffer(rfbClient* rfb_client) {
/* Use original, wrapped proc */
return vnc_client->rfb_MallocFrameBuffer(rfb_client);
}

Loading
Loading