diff --git a/src/common/common/display.h b/src/common/common/display.h index 6707bcb1..c5428902 100644 --- a/src/common/common/display.h +++ b/src/common/common/display.h @@ -99,6 +99,13 @@ typedef struct guac_common_display { */ guac_common_display_layer* buffers; + /** + * Non-zero if all graphical updates for this display should use lossless + * compression, 0 otherwise. By default, newly-created displays will use + * lossy compression when heuristics determine it is appropriate. + */ + int lossless; + /** * Mutex which is locked internally when access to the display must be * synchronized. All public functions of guac_common_display should be @@ -228,5 +235,27 @@ void guac_common_display_free_layer(guac_common_display* display, void guac_common_display_free_buffer(guac_common_display* display, guac_common_display_layer* display_buffer); +/** + * Sets the overall lossless compression policy of the given display to the + * given value, affecting all current and future layers/buffers maintained by + * the display. By default, newly-created displays will use lossy compression + * for graphical updates when heuristics determine that doing so is + * appropriate. Specifying a non-zero value here will force all graphical + * updates to always use lossless compression, whereas specifying zero will + * restore the default policy. + * + * Note that this can also be adjusted on a per-layer / per-buffer basis with + * guac_common_surface_set_lossless(). + * + * @param display + * The display to modify. + * + * @param lossless + * Non-zero if all graphical updates for this display should use lossless + * compression, 0 otherwise. + */ +void guac_common_display_set_lossless(guac_common_display* display, + int lossless); + #endif diff --git a/src/common/common/surface.h b/src/common/common/surface.h index ca8b3106..b43dcaaf 100644 --- a/src/common/common/surface.h +++ b/src/common/common/surface.h @@ -126,6 +126,13 @@ typedef struct guac_common_surface { */ int touches; + /** + * Non-zero if all graphical updates for this surface should use lossless + * compression, 0 otherwise. By default, newly-created surfaces will use + * lossy compression when heuristics determine it is appropriate. + */ + int lossless; + /** * The X coordinate of the upper-left corner of this layer, in pixels, * relative to its parent layer. This is only applicable to visible @@ -510,5 +517,23 @@ void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, void guac_common_surface_set_multitouch(guac_common_surface* surface, int touches); +/** + * Sets the lossless compression policy of the given surface to the given + * value. By default, newly-created surfaces will use lossy compression for + * graphical updates when heuristics determine that doing so is appropriate. + * Specifying a non-zero value here will force all graphical updates to always + * use lossless compression, whereas specifying zero will restore the default + * policy. + * + * @param surface + * The surface to modify. + * + * @param lossless + * Non-zero if all graphical updates for this surface should use lossless + * compression, 0 otherwise. + */ +void guac_common_surface_set_lossless(guac_common_surface* surface, + int lossless); + #endif diff --git a/src/common/display.c b/src/common/display.c index 5d8ce9f1..9b4b87f0 100644 --- a/src/common/display.c +++ b/src/common/display.c @@ -182,6 +182,30 @@ void guac_common_display_dup(guac_common_display* display, guac_user* user, } +void guac_common_display_set_lossless(guac_common_display* display, + int lossless) { + + pthread_mutex_lock(&display->_lock); + + /* Update lossless setting to be applied to all newly-allocated + * layers/buffers */ + display->lossless = lossless; + + /* Update losslessness of all allocated layers/buffers */ + guac_common_display_layer* current = display->layers; + while (current != NULL) { + guac_common_surface_set_lossless(current->surface, lossless); + current = current->next; + } + + /* Update losslessness of default display layer (not included within layers + * list) */ + guac_common_surface_set_lossless(display->default_surface, lossless); + + pthread_mutex_unlock(&display->_lock); + +} + void guac_common_display_flush(guac_common_display* display) { pthread_mutex_lock(&display->_lock); @@ -287,6 +311,9 @@ guac_common_display_layer* guac_common_display_alloc_layer( guac_common_surface* surface = guac_common_surface_alloc(display->client, display->client->socket, layer, width, height); + /* Apply current display losslessness */ + guac_common_surface_set_lossless(surface, display->lossless); + /* Add layer and surface to list */ guac_common_display_layer* display_layer = guac_common_display_add_layer(&display->layers, layer, surface); @@ -308,6 +335,9 @@ guac_common_display_layer* guac_common_display_alloc_buffer( guac_common_surface* surface = guac_common_surface_alloc(display->client, display->client->socket, buffer, width, height); + /* Apply current display losslessness */ + guac_common_surface_set_lossless(surface, display->lossless); + /* Add buffer and surface to list */ guac_common_display_layer* display_layer = guac_common_display_add_layer(&display->buffers, buffer, surface); diff --git a/src/common/surface.c b/src/common/surface.c index 183ae11e..61b77c7d 100644 --- a/src/common/surface.c +++ b/src/common/surface.c @@ -116,6 +116,15 @@ void guac_common_surface_set_multitouch(guac_common_surface* surface, } +void guac_common_surface_set_lossless(guac_common_surface* surface, + int lossless) { + + pthread_mutex_lock(&surface->_lock); + surface->lossless = lossless; + pthread_mutex_unlock(&surface->_lock); + +} + void guac_common_surface_move(guac_common_surface* surface, int x, int y) { pthread_mutex_lock(&surface->_lock); @@ -534,6 +543,10 @@ static int __guac_common_surface_png_optimality(guac_common_surface* surface, static int __guac_common_surface_should_use_jpeg(guac_common_surface* surface, const guac_common_rect* rect) { + /* Do not use JPEG if lossless quality is required */ + if (surface->lossless) + return 0; + /* Calculate the average framerate for the given rect */ int framerate = __guac_common_surface_calculate_framerate(surface, rect); @@ -1806,7 +1819,8 @@ static void __guac_common_surface_flush_to_webp(guac_common_surface* surface, /* Send WebP for rect */ guac_client_stream_webp(surface->client, socket, GUAC_COMP_OVER, layer, surface->dirty_rect.x, surface->dirty_rect.y, rect, - guac_common_surface_suggest_quality(surface->client), 0); + guac_common_surface_suggest_quality(surface->client), + surface->lossless ? 1 : 0); cairo_surface_destroy(rect); surface->realized = 1; diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index db62bc4b..485e132b 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -435,6 +435,10 @@ static int guac_rdp_handle_connection(guac_client* client) { rdp_client->settings->width, rdp_client->settings->height); + /* Use lossless compression only if requested (otherwise, use default + * heuristics) */ + guac_common_display_set_lossless(rdp_client->display, settings->lossless); + rdp_client->current_surface = rdp_client->display->default_surface; rdp_client->available_svc = guac_common_list_alloc(); diff --git a/src/protocols/rdp/settings.c b/src/protocols/rdp/settings.c index f834a7fa..5a1d48e9 100644 --- a/src/protocols/rdp/settings.c +++ b/src/protocols/rdp/settings.c @@ -128,6 +128,8 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "wol-broadcast-addr", "wol-udp-port", "wol-wait-time", + + "force-lossless", NULL }; @@ -639,6 +641,12 @@ enum RDP_ARGS_IDX { */ IDX_WOL_WAIT_TIME, + /** + * "true" if all graphical updates for this connection should use lossless + * compresion only, "false" or blank otherwise. + */ + IDX_FORCE_LOSSLESS, + RDP_ARGS_COUNT }; @@ -779,6 +787,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, settings->height, settings->resolution); + /* Lossless compression */ + settings->lossless = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_FORCE_LOSSLESS, 0); + /* Domain */ settings->domain = guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, diff --git a/src/protocols/rdp/settings.h b/src/protocols/rdp/settings.h index c540fcc8..f80cde48 100644 --- a/src/protocols/rdp/settings.h +++ b/src/protocols/rdp/settings.h @@ -192,6 +192,12 @@ typedef struct guac_rdp_settings { */ int resolution; + /** + * Whether all graphical updates for this connection should use lossless + * compression only. + */ + int lossless; + /** * Whether audio is enabled. */ diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index 691e7083..a476b2c1 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -91,6 +91,8 @@ const char* GUAC_VNC_CLIENT_ARGS[] = { "wol-broadcast-addr", "wol-udp-port", "wol-wait-time", + + "force-lossless", NULL }; @@ -373,6 +375,12 @@ enum VNC_ARGS_IDX { */ IDX_WOL_WAIT_TIME, + /** + * "true" if all graphical updates for this connection should use lossless + * compresion only, "false" or blank otherwise. + */ + IDX_FORCE_LOSSLESS, + VNC_ARGS_COUNT }; @@ -432,6 +440,11 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, guac_user_parse_args_int(user, GUAC_VNC_CLIENT_ARGS, argv, IDX_COLOR_DEPTH, 0); + /* Lossless compression */ + settings->lossless = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_FORCE_LOSSLESS, false); + #ifdef ENABLE_VNC_REPEATER /* Set repeater parameters if specified */ settings->dest_host = diff --git a/src/protocols/vnc/settings.h b/src/protocols/vnc/settings.h index 6d1c6575..76139bd4 100644 --- a/src/protocols/vnc/settings.h +++ b/src/protocols/vnc/settings.h @@ -77,6 +77,12 @@ typedef struct guac_vnc_settings { */ bool read_only; + /** + * Whether all graphical updates for this connection should use lossless + * compression only. + */ + bool lossless; + #ifdef ENABLE_VNC_REPEATER /** * The VNC host to connect to, if using a repeater. diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index ade8278b..4dcf861d 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -435,6 +435,10 @@ void* guac_vnc_client_thread(void* data) { vnc_client->display = guac_common_display_alloc(client, rfb_client->width, rfb_client->height); + /* Use lossless compression only if requested (otherwise, use default + * heuristics) */ + guac_common_display_set_lossless(vnc_client->display, settings->lossless); + /* If not read-only, set an appropriate cursor */ if (settings->read_only == 0) { if (settings->remote_cursor) diff --git a/src/terminal/display.c b/src/terminal/display.c index d148b411..e35b8a74 100644 --- a/src/terminal/display.c +++ b/src/terminal/display.c @@ -221,6 +221,9 @@ guac_terminal_display* guac_terminal_display_alloc(guac_client* client, display->display_surface = guac_common_surface_alloc(client, client->socket, display->display_layer, 0, 0); + /* Never use lossy compression for terminal contents */ + guac_common_surface_set_lossless(display->display_surface, 1); + /* Select layer is a child of the display layer */ guac_protocol_send_move(client->socket, display->select_layer, display->display_layer, 0, 0, 0);