GUACAMOLE-1302: Merge support for forcing lossless compression in VNC and RDP connections.

diff --git a/src/common/common/display.h b/src/common/common/display.h
index 6707bcb..c542890 100644
--- a/src/common/common/display.h
+++ b/src/common/common/display.h
@@ -100,6 +100,13 @@
     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
      * considered threadsafe.
@@ -228,5 +235,27 @@
 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 ca8b310..b43dcaa 100644
--- a/src/common/common/surface.h
+++ b/src/common/common/surface.h
@@ -127,6 +127,13 @@
     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
      * (non-buffer) layers which are not the default layer.
@@ -510,5 +517,23 @@
 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 5d8ce9f..9b4b87f 100644
--- a/src/common/display.c
+++ b/src/common/display.c
@@ -182,6 +182,30 @@
 
 }
 
+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_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_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 183ae11..61b77c7 100644
--- a/src/common/surface.c
+++ b/src/common/surface.c
@@ -116,6 +116,15 @@
 
 }
 
+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_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 @@
         /* 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 db62bc4..485e132 100644
--- a/src/protocols/rdp/rdp.c
+++ b/src/protocols/rdp/rdp.c
@@ -435,6 +435,10 @@
             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 f834a7f..5a1d48e 100644
--- a/src/protocols/rdp/settings.c
+++ b/src/protocols/rdp/settings.c
@@ -128,6 +128,8 @@
     "wol-broadcast-addr",
     "wol-udp-port",
     "wol-wait-time",
+
+    "force-lossless",
     NULL
 };
 
@@ -639,6 +641,12 @@
      */
     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 @@
             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 c540fcc..f80cde4 100644
--- a/src/protocols/rdp/settings.h
+++ b/src/protocols/rdp/settings.h
@@ -193,6 +193,12 @@
     int resolution;
 
     /**
+     * Whether all graphical updates for this connection should use lossless
+     * compression only.
+     */
+    int lossless;
+
+    /**
      * Whether audio is enabled.
      */
     int audio_enabled;
diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c
index 691e708..a476b2c 100644
--- a/src/protocols/vnc/settings.c
+++ b/src/protocols/vnc/settings.c
@@ -91,6 +91,8 @@
     "wol-broadcast-addr",
     "wol-udp-port",
     "wol-wait-time",
+
+    "force-lossless",
     NULL
 };
 
@@ -373,6 +375,12 @@
      */
     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_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 6d1c657..76139bd 100644
--- a/src/protocols/vnc/settings.h
+++ b/src/protocols/vnc/settings.h
@@ -77,6 +77,12 @@
      */
     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 ade8278..4dcf861 100644
--- a/src/protocols/vnc/vnc.c
+++ b/src/protocols/vnc/vnc.c
@@ -435,6 +435,10 @@
     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 d148b41..e35b8a7 100644
--- a/src/terminal/display.c
+++ b/src/terminal/display.c
@@ -221,6 +221,9 @@
     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);