diff --git a/protocols/rdp/include/client.h b/protocols/rdp/include/client.h index a6bb8016..306dbcec 100644 --- a/protocols/rdp/include/client.h +++ b/protocols/rdp/include/client.h @@ -47,12 +47,6 @@ #define RDP_DEFAULT_PORT 3389 -typedef struct guac_rdp_color { - int red; - int green; - int blue; -} guac_rdp_color; - typedef struct rdp_guac_client_data { freerdp* rdp_inst; @@ -60,8 +54,27 @@ typedef struct rdp_guac_client_data { int mouse_button_mask; - guac_rdp_color foreground; - guac_rdp_color background; + /** + * Cairo surface which will receive all TRANSPARENT glyphs. + */ + cairo_surface_t* trans_glyph_surface; + + /** + * Cairo surface which will receive all OPAQUE glyphs. + */ + cairo_surface_t* opaque_glyph_surface; + + /** + * The current Cairo surface which will receive all drawn glyphs, + * depending on whether we are currently drawing transparent or + * opaque glyphs. + */ + cairo_surface_t* glyph_surface; + + /** + * Cairo instance for drawing to the current glyph surface. + */ + cairo_t* glyph_cairo; const guac_layer* current_surface; diff --git a/protocols/rdp/include/rdp_bitmap.h b/protocols/rdp/include/rdp_bitmap.h index 72fa11f7..b4cdd1e0 100644 --- a/protocols/rdp/include/rdp_bitmap.h +++ b/protocols/rdp/include/rdp_bitmap.h @@ -54,8 +54,14 @@ typedef struct guac_rdp_bitmap { */ guac_layer* layer; + /** + * The number of times a bitmap has been used. + */ + int used; + } guac_rdp_bitmap; +void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap); void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap); void guac_rdp_bitmap_decompress(rdpContext* context, rdpBitmap* bitmap, uint8* data, int width, int height, int bpp, int length, boolean compressed); void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap); diff --git a/protocols/rdp/include/rdp_gdi.h b/protocols/rdp/include/rdp_gdi.h index 97cc3057..1f68352e 100644 --- a/protocols/rdp/include/rdp_gdi.h +++ b/protocols/rdp/include/rdp_gdi.h @@ -51,5 +51,6 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt); void guac_rdp_gdi_opaquerect(rdpContext* context, OPAQUE_RECT_ORDER* opaque_rect); void guac_rdp_gdi_palette_update(rdpContext* context, PALETTE_UPDATE* palette); void guac_rdp_gdi_set_bounds(rdpContext* context, rdpBounds* bounds); +void guac_rdp_gdi_end_paint(rdpContext* context); #endif diff --git a/protocols/rdp/include/rdp_glyph.h b/protocols/rdp/include/rdp_glyph.h index 8270fbbf..2c9510a3 100644 --- a/protocols/rdp/include/rdp_glyph.h +++ b/protocols/rdp/include/rdp_glyph.h @@ -50,9 +50,9 @@ typedef struct guac_rdp_glyph { rdpGlyph glyph; /** - * Guacamole layer containing cached image data. + * Cairo surface layer containing cached image data. */ - guac_layer* layer; + cairo_surface_t* surface; } guac_rdp_glyph; diff --git a/protocols/rdp/src/client.c b/protocols/rdp/src/client.c index caca83f5..87823add 100644 --- a/protocols/rdp/src/client.c +++ b/protocols/rdp/src/client.c @@ -152,6 +152,7 @@ boolean rdp_freerdp_pre_connect(freerdp* instance) { xfree(pointer); /* Set up GDI */ + instance->update->EndPaint = guac_rdp_gdi_end_paint; instance->update->Palette = guac_rdp_gdi_palette_update; instance->update->SetBounds = guac_rdp_gdi_set_bounds; @@ -199,10 +200,6 @@ boolean rdp_freerdp_post_connect(freerdp* instance) { client->mouse_handler = rdp_guac_client_mouse_handler; client->key_handler = rdp_guac_client_key_handler; - /* Send size */ - guac_protocol_send_size(client->socket, GUAC_DEFAULT_LAYER, - instance->settings->width, instance->settings->height); - return true; } @@ -400,6 +397,17 @@ int guac_client_init(guac_client* client, int argc, char** argv) { /* Send connection name */ guac_protocol_send_name(client->socket, settings->window_title); + /* Send size */ + guac_protocol_send_size(client->socket, GUAC_DEFAULT_LAYER, + settings->width, settings->height); + + /* Create glyph surfaces */ + guac_client_data->opaque_glyph_surface = cairo_image_surface_create( + CAIRO_FORMAT_RGB24, settings->width, settings->height); + + guac_client_data->trans_glyph_surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, settings->width, settings->height); + /* Success */ return 0; diff --git a/protocols/rdp/src/rdp_bitmap.c b/protocols/rdp/src/rdp_bitmap.c index 38a90fc4..2d674102 100644 --- a/protocols/rdp/src/rdp_bitmap.c +++ b/protocols/rdp/src/rdp_bitmap.c @@ -53,7 +53,7 @@ #include "client.h" #include "rdp_bitmap.h" -void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) { +void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) { guac_client* client = ((rdp_freerdp_context*) context)->client; guac_socket* socket = client->socket; @@ -61,9 +61,31 @@ void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) { /* Allocate buffer */ guac_layer* buffer = guac_client_alloc_buffer(client); + /* Cache image data if present */ + if (bitmap->data != NULL) { + + /* Create surface from image data */ + cairo_surface_t* surface = cairo_image_surface_create_for_data( + bitmap->data, CAIRO_FORMAT_RGB24, + bitmap->width, bitmap->height, 4*bitmap->width); + + /* Send surface to buffer */ + guac_protocol_send_png(socket, + GUAC_COMP_SRC, buffer, 0, 0, surface); + + /* Free surface */ + cairo_surface_destroy(surface); + + } + /* Store buffer reference in bitmap */ ((guac_rdp_bitmap*) bitmap)->layer = buffer; +} + + +void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) { + /* Convert image data if present */ if (bitmap->data != NULL) { @@ -73,40 +95,21 @@ void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) { context->instance->settings->color_depth, 32, ((rdp_freerdp_context*) context)->clrconv); - /* Create surface from image data */ - cairo_surface_t* surface = cairo_image_surface_create_for_data( - image_buffer, CAIRO_FORMAT_RGB24, - bitmap->width, bitmap->height, 4*bitmap->width); + /* Free existing image, if any */ + if (image_buffer != bitmap->data) + free(bitmap->data); - /* Send surface to buffer */ - guac_protocol_send_png(socket, GUAC_COMP_SRC, buffer, 0, 0, surface); - - /* Free surface */ - cairo_surface_destroy(surface); - - /* If ephemeral, just free image data */ - if (!bitmap->ephemeral) { - - /* Free image data if actually alloated */ - if (image_buffer != bitmap->data) - free(image_buffer); - - } - - /* Otherwise, store converted image in bitmap, free any existing */ - else { - - /* Free existing image, if any */ - if (image_buffer != bitmap->data) - free(bitmap->data); - - /* Store converted image in bitmap */ - bitmap->data = image_buffer; - - } + /* Store converted image in bitmap */ + bitmap->data = image_buffer; } + /* No corresponding layer yet - caching is deferred. */ + ((guac_rdp_bitmap*) bitmap)->layer = NULL; + + /* Start at zero usage */ + ((guac_rdp_bitmap*) bitmap)->used = 0; + } void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) { @@ -117,20 +120,49 @@ void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) { int width = bitmap->right - bitmap->left + 1; int height = bitmap->bottom - bitmap->top + 1; - guac_protocol_send_copy(socket, - ((guac_rdp_bitmap*) bitmap)->layer, - 0, 0, width, height, - GUAC_COMP_OVER, - GUAC_DEFAULT_LAYER, bitmap->left, bitmap->top); + /* If not cached, cache if necessary */ + if (((guac_rdp_bitmap*) bitmap)->layer == NULL + && ((guac_rdp_bitmap*) bitmap)->used >= 1) + guac_rdp_cache_bitmap(context, bitmap); + + /* If cached, retrieve from cache */ + if (((guac_rdp_bitmap*) bitmap)->layer != NULL) + guac_protocol_send_copy(socket, + ((guac_rdp_bitmap*) bitmap)->layer, + 0, 0, width, height, + GUAC_COMP_OVER, + GUAC_DEFAULT_LAYER, bitmap->left, bitmap->top); + + /* Otherwise, draw with stored image data */ + else if (bitmap->data != NULL) { + + /* Create surface from image data */ + cairo_surface_t* surface = cairo_image_surface_create_for_data( + bitmap->data, CAIRO_FORMAT_RGB24, + width, height, 4*bitmap->width); + + /* Send surface to buffer */ + guac_protocol_send_png(socket, + GUAC_COMP_OVER, GUAC_DEFAULT_LAYER, + bitmap->left, bitmap->top, surface); + + /* Free surface */ + cairo_surface_destroy(surface); + + } + + /* Increment usage counter */ + ((guac_rdp_bitmap*) bitmap)->used++; } void guac_rdp_bitmap_free(rdpContext* context, rdpBitmap* bitmap) { guac_client* client = ((rdp_freerdp_context*) context)->client; - /* Free layer, if any */ + /* If cached, free buffer */ if (((guac_rdp_bitmap*) bitmap)->layer != NULL) guac_client_free_buffer(client, ((guac_rdp_bitmap*) bitmap)->layer); + } void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, boolean primary) { @@ -139,10 +171,18 @@ void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, boolean if (primary) ((rdp_guac_client_data*) client->data)->current_surface = GUAC_DEFAULT_LAYER; - else + + else { + + /* If not available as a surface, make available. */ + if (((guac_rdp_bitmap*) bitmap)->layer == NULL) + guac_rdp_cache_bitmap(context, bitmap); + ((rdp_guac_client_data*) client->data)->current_surface = ((guac_rdp_bitmap*) bitmap)->layer; + } + } void guac_rdp_bitmap_decompress(rdpContext* context, rdpBitmap* bitmap, uint8* data, int width, int height, int bpp, int length, boolean compressed) { diff --git a/protocols/rdp/src/rdp_gdi.c b/protocols/rdp/src/rdp_gdi.c index e2202e73..66b3cde2 100644 --- a/protocols/rdp/src/rdp_gdi.c +++ b/protocols/rdp/src/rdp_gdi.c @@ -159,9 +159,7 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { guac_socket* socket = client->socket; guac_rdp_bitmap* bitmap = (guac_rdp_bitmap*) memblt->bitmap; - if (bitmap->layer != NULL) { - - switch (memblt->bRop) { + switch (memblt->bRop) { /* If blackness, send black rectangle */ case 0x00: @@ -180,12 +178,46 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { /* If operation is just SRC, simply copy */ case 0xCC: - guac_protocol_send_copy(socket, - bitmap->layer, - memblt->nXSrc, memblt->nYSrc, - memblt->nWidth, memblt->nHeight, - GUAC_COMP_OVER, - current_layer, memblt->nLeftRect, memblt->nTopRect); + + /* If not cached, cache if necessary */ + if (((guac_rdp_bitmap*) bitmap)->layer == NULL + && ((guac_rdp_bitmap*) bitmap)->used >= 1) + guac_rdp_cache_bitmap(context, memblt->bitmap); + + /* If not cached, send as PNG */ + if (bitmap->layer == NULL) { + if (memblt->bitmap->data != NULL) { + + /* Create surface from image data */ + cairo_surface_t* surface = cairo_image_surface_create_for_data( + memblt->bitmap->data + 4*(memblt->nXSrc + memblt->nYSrc*memblt->bitmap->width), + CAIRO_FORMAT_RGB24, + memblt->nWidth, memblt->nHeight, + 4*memblt->bitmap->width); + + /* Send surface to buffer */ + guac_protocol_send_png(socket, + GUAC_COMP_OVER, current_layer, + memblt->nLeftRect, memblt->nTopRect, surface); + + /* Free surface */ + cairo_surface_destroy(surface); + + } + } + + /* Otherwise, copy */ + else + guac_protocol_send_copy(socket, + bitmap->layer, + memblt->nXSrc, memblt->nYSrc, + memblt->nWidth, memblt->nHeight, + GUAC_COMP_OVER, + current_layer, memblt->nLeftRect, memblt->nTopRect); + + /* Increment usage counter */ + ((guac_rdp_bitmap*) bitmap)->used++; + break; /* If whiteness, send white rectangle */ @@ -201,6 +233,11 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { /* Otherwise, use transfer */ default: + + /* If not available as a surface, make available. */ + if (bitmap->layer == NULL) + guac_rdp_cache_bitmap(context, memblt->bitmap); + guac_protocol_send_transfer(socket, bitmap->layer, memblt->nXSrc, memblt->nYSrc, @@ -208,9 +245,10 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { guac_rdp_rop3_transfer_function(client, memblt->bRop), current_layer, memblt->nLeftRect, memblt->nTopRect); - } + /* Increment usage counter */ + ((guac_rdp_bitmap*) bitmap)->used++; - } /* end if layer not NULL */ + } } @@ -264,3 +302,8 @@ void guac_rdp_gdi_set_bounds(rdpContext* context, rdpBounds* bounds) { } +void guac_rdp_gdi_end_paint(rdpContext* context) { + guac_client* client = ((rdp_freerdp_context*) context)->client; + guac_socket_flush(client->socket); +} + diff --git a/protocols/rdp/src/rdp_glyph.c b/protocols/rdp/src/rdp_glyph.c index 9e33ba09..951d8ecf 100644 --- a/protocols/rdp/src/rdp_glyph.c +++ b/protocols/rdp/src/rdp_glyph.c @@ -39,17 +39,13 @@ #include #include +#include #include "client.h" #include "rdp_glyph.h" void guac_rdp_glyph_new(rdpContext* context, rdpGlyph* glyph) { - /* Allocate buffer */ - guac_client* client = ((rdp_freerdp_context*) context)->client; - guac_socket* socket = client->socket; - guac_layer* layer = guac_client_alloc_buffer(client); - int x, y, i; int stride; unsigned char* image_buffer; @@ -59,8 +55,6 @@ void guac_rdp_glyph_new(rdpContext* context, rdpGlyph* glyph) { int width = glyph->cx; int height = glyph->cy; - cairo_surface_t* surface; - /* Init Cairo buffer */ stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); image_buffer = malloc(height*stride); @@ -97,16 +91,9 @@ void guac_rdp_glyph_new(rdpContext* context, rdpGlyph* glyph) { } } - surface = cairo_image_surface_create_for_data(image_buffer, CAIRO_FORMAT_ARGB32, width, height, stride); - guac_protocol_send_png(socket, GUAC_COMP_SRC, layer, 0, 0, surface); - guac_socket_flush(socket); - - /* Free surface */ - cairo_surface_destroy(surface); - free(image_buffer); - - /* Store layer */ - ((guac_rdp_glyph*) glyph)->layer = layer; + /* Store glyph surface */ + ((guac_rdp_glyph*) glyph)->surface = cairo_image_surface_create_for_data( + image_buffer, CAIRO_FORMAT_ARGB32, width, height, stride); } @@ -114,69 +101,139 @@ void guac_rdp_glyph_draw(rdpContext* context, rdpGlyph* glyph, int x, int y) { guac_client* client = ((rdp_freerdp_context*) context)->client; rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data; - const guac_layer* current_layer = ((rdp_guac_client_data*) client->data)->current_surface; - - /* Colorize glyph */ - guac_protocol_send_rect(client->socket, ((guac_rdp_glyph*) glyph)->layer, - 0, 0, glyph->cx, glyph->cy); - guac_protocol_send_cfill(client->socket, - GUAC_COMP_ATOP, ((guac_rdp_glyph*) glyph)->layer, - guac_client_data->foreground.red, - guac_client_data->foreground.green, - guac_client_data->foreground.blue, - 255); + /* Do not attempt to draw glyphs if glyph drawing is not begun */ + if (guac_client_data->glyph_cairo == NULL) + return; - /* Draw glyph */ - guac_protocol_send_copy(client->socket, - ((guac_rdp_glyph*) glyph)->layer, 0, 0, glyph->cx, glyph->cy, - GUAC_COMP_OVER, current_layer, x, y); + /* Use glyph as mask */ + cairo_mask_surface( + guac_client_data->glyph_cairo, + ((guac_rdp_glyph*) glyph)->surface, x, y); } void guac_rdp_glyph_free(rdpContext* context, rdpGlyph* glyph) { - guac_client* client = ((rdp_freerdp_context*) context)->client; - guac_client_free_buffer(client, ((guac_rdp_glyph*) glyph)->layer); + + unsigned char* image_buffer = cairo_image_surface_get_data( + ((guac_rdp_glyph*) glyph)->surface); + + /* Free surface */ + cairo_surface_destroy(((guac_rdp_glyph*) glyph)->surface); + free(image_buffer); + } void guac_rdp_glyph_begindraw(rdpContext* context, int x, int y, int width, int height, uint32 fgcolor, uint32 bgcolor) { guac_client* client = ((rdp_freerdp_context*) context)->client; - rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data; - const guac_layer* current_layer = ((rdp_guac_client_data*) client->data)->current_surface; - - bgcolor = freerdp_color_convert_var(bgcolor, - context->instance->settings->color_depth, 32, - ((rdp_freerdp_context*) context)->clrconv); + rdp_guac_client_data* guac_client_data = + (rdp_guac_client_data*) client->data; + /* Convert foreground color */ fgcolor = freerdp_color_convert_var(fgcolor, context->instance->settings->color_depth, 32, ((rdp_freerdp_context*) context)->clrconv); - guac_client_data->foreground.blue = fgcolor & 0x0000FF; - guac_client_data->foreground.green = (fgcolor & 0x00FF00) >> 8; - guac_client_data->foreground.red = (fgcolor & 0xFF0000) >> 16; + /* Fill background with color if specified */ + if (width != 0 && height != 0) { - guac_client_data->background.blue = bgcolor & 0x0000FF; - guac_client_data->background.green = (bgcolor & 0x00FF00) >> 8; - guac_client_data->background.red = (bgcolor & 0xFF0000) >> 16; + /* Prepare for opaque glyphs */ + guac_client_data->glyph_surface = + guac_client_data->opaque_glyph_surface; - /* Paint background on destination */ - guac_protocol_send_rect(client->socket, current_layer, - x, y, width, height); + /* Create cairo instance */ + guac_client_data->glyph_cairo = cairo_create( + guac_client_data->glyph_surface); - guac_protocol_send_cfill(client->socket, - GUAC_COMP_OVER, current_layer, - guac_client_data->background.red, - guac_client_data->background.green, - guac_client_data->background.blue, - 255); + /* Convert background color */ + bgcolor = freerdp_color_convert_var(bgcolor, + context->instance->settings->color_depth, 32, + ((rdp_freerdp_context*) context)->clrconv); + + /* Fill background */ + cairo_rectangle(guac_client_data->glyph_cairo, + x, y, width, height); + + cairo_set_source_rgb(guac_client_data->glyph_cairo, + ((bgcolor & 0xFF0000) >> 16) / 255.0, + ((bgcolor & 0x00FF00) >> 8 ) / 255.0, + ( bgcolor & 0x0000FF ) / 255.0); + + cairo_fill(guac_client_data->glyph_cairo); + + } + + /* Otherwise, prepare for transparent glyphs */ + else { + + /* Select transparent glyph surface */ + guac_client_data->glyph_surface = + guac_client_data->trans_glyph_surface; + + guac_client_data->glyph_cairo = cairo_create( + guac_client_data->glyph_surface); + + /* Clear surface */ + cairo_set_operator(guac_client_data->glyph_cairo, + CAIRO_OPERATOR_SOURCE); + + cairo_set_source_rgba(guac_client_data->glyph_cairo, 0, 0, 0, 0); + cairo_paint(guac_client_data->glyph_cairo); + + /* Restore operator */ + cairo_set_operator(guac_client_data->glyph_cairo, + CAIRO_OPERATOR_OVER); + + } + + /* Prepare for glyph drawing */ + cairo_set_source_rgb(guac_client_data->glyph_cairo, + ((fgcolor & 0xFF0000) >> 16) / 255.0, + ((fgcolor & 0x00FF00) >> 8 ) / 255.0, + ( fgcolor & 0x0000FF ) / 255.0); } void guac_rdp_glyph_enddraw(rdpContext* context, int x, int y, int width, int height, uint32 fgcolor, uint32 bgcolor) { - /* UNUSED */ + + guac_client* client = ((rdp_freerdp_context*) context)->client; + rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data; + const guac_layer* current_layer = ((rdp_guac_client_data*) client->data)->current_surface; + + /* Use glyph surface to provide image data for glyph rectangle */ + cairo_surface_t* glyph_surface = guac_client_data->glyph_surface; + int stride = cairo_image_surface_get_stride(glyph_surface); + + /* Calculate bounds */ + int max_width = cairo_image_surface_get_width(glyph_surface) - x; + int max_height = cairo_image_surface_get_height(glyph_surface) - y; + + /* Ensure dimensions of glyph do not exceed bounds */ + if (width > max_width) width = max_width; + if (height > max_height) height = max_height; + + /* Ensure data is ready */ + cairo_surface_flush(glyph_surface); + + /* Create surface for subsection with text */ + cairo_surface_t* surface = cairo_image_surface_create_for_data( + cairo_image_surface_get_data(glyph_surface) + 4*x + y*stride, + cairo_image_surface_get_format(glyph_surface), + width, height, stride); + + /* Send surface with all glyphs to layer */ + guac_protocol_send_png(client->socket, + GUAC_COMP_OVER, current_layer, x, y, + surface); + + /* Destroy surface */ + cairo_surface_destroy(surface); + + /* Destroy cairo instance */ + cairo_destroy(guac_client_data->glyph_cairo); + }