From 9d59da5cb0a5a4daee53c4e0316dd89a0bdb806b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 3 Jun 2024 17:22:47 -0700 Subject: [PATCH] GUACAMOLE-377: Migrate VNC support to guac_display API. --- src/protocols/vnc/client.c | 5 +- src/protocols/vnc/cursor.c | 95 +++++++++++++--------------- src/protocols/vnc/cursor.h | 4 +- src/protocols/vnc/display.c | 120 ++++++++++++++++-------------------- src/protocols/vnc/display.h | 11 ++++ src/protocols/vnc/input.c | 5 +- src/protocols/vnc/log.c | 1 - src/protocols/vnc/log.h | 1 - src/protocols/vnc/user.c | 8 +-- src/protocols/vnc/vnc.c | 85 ++++++------------------- src/protocols/vnc/vnc.h | 5 +- 11 files changed, 140 insertions(+), 200 deletions(-) diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index 43ead098f..4ec8c7b93 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -34,6 +34,7 @@ #endif #include +#include #include #include @@ -89,7 +90,7 @@ static int guac_vnc_join_pending_handler(guac_client* client) { /* Synchronize with current display */ if (vnc_client->display != NULL) { - guac_common_display_dup(vnc_client->display, client, broadcast_socket); + guac_display_dup(vnc_client->display, broadcast_socket); guac_socket_flush(broadcast_socket); } @@ -192,7 +193,7 @@ int guac_vnc_client_free_handler(guac_client* client) { /* Free display */ if (vnc_client->display != NULL) - guac_common_display_free(vnc_client->display); + guac_display_free(vnc_client->display); #ifdef ENABLE_PULSE /* If audio enabled, stop streaming */ diff --git a/src/protocols/vnc/cursor.c b/src/protocols/vnc/cursor.c index 449fea382..7a1bc1518 100644 --- a/src/protocols/vnc/cursor.c +++ b/src/protocols/vnc/cursor.c @@ -20,13 +20,10 @@ #include "config.h" #include "client.h" -#include "common/cursor.h" -#include "common/display.h" -#include "common/surface.h" #include "vnc.h" -#include #include +#include #include #include #include @@ -34,99 +31,95 @@ #include #include -/* Define cairo_format_stride_for_width() if missing */ -#ifndef HAVE_CAIRO_FORMAT_STRIDE_FOR_WIDTH -#define cairo_format_stride_for_width(format, width) (width*4) -#endif - #include #include #include #include #include -void guac_vnc_cursor(rfbClient* client, int x, int y, int w, int h, int bpp) { +void guac_vnc_cursor(rfbClient* client, int x, int y, int w, int h, int vnc_bpp) { guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; - /* Cairo image buffer */ - int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w); - unsigned char* buffer = guac_mem_alloc(h, stride); - unsigned char* buffer_row_current = buffer; + /* Begin drawing operation directly to cursor layer */ + guac_display_layer* cursor_layer = guac_display_cursor(vnc_client->display); + guac_display_layer_resize(cursor_layer, w, h); + guac_display_set_cursor_hotspot(vnc_client->display, x, y); + guac_display_layer_raw_context* context = guac_display_layer_open_raw(cursor_layer); - /* VNC image buffer */ - unsigned int fb_stride = bpp * w; - unsigned char* fb_row_current = client->rcSource; - unsigned char* fb_mask = client->rcMask; + /* Convert operation coordinates to guac_rect for easier manipulation */ + guac_rect op_bounds; + guac_rect_init(&op_bounds, 0, 0, w, h); - int dx, dy; + /* Ensure draw is within current bounds of the pending frame */ + guac_rect_constrain(&op_bounds, &context->bounds); - /* Copy image data from VNC client to RGBA buffer */ - for (dy = 0; dyrcSource; + unsigned char* vnc_mask = client->rcMask; + size_t vnc_stride = guac_mem_ckd_mul_or_die(vnc_bpp, w); - unsigned int* buffer_current; - unsigned char* fb_current; - - /* Get current buffer row, advance to next */ - buffer_current = (unsigned int*) buffer_row_current; - buffer_row_current += stride; + /* Copy image data from VNC client to RGBA buffer */ + unsigned char* layer_current_row = GUAC_RECT_MUTABLE_BUFFER(op_bounds, context->buffer, context->stride, GUAC_DISPLAY_LAYER_RAW_BPP); + for (int dy = 0; dy < h; dy++) { - /* Get current framebuffer row, advance to next */ - fb_current = fb_row_current; - fb_row_current += fb_stride; + /* Get current Guacamole buffer row, advance to next */ + uint32_t* layer_current_pixel = (uint32_t*) layer_current_row; + layer_current_row += context->stride; - for (dx = 0; dx> client->format.redShift) * 0x100 / (client->format.redMax + 1); - green = (v >> client->format.greenShift) * 0x100 / (client->format.greenMax+ 1); - blue = (v >> client->format.blueShift) * 0x100 / (client->format.blueMax + 1); + uint8_t red = (v >> client->format.redShift) * 0x100 / (client->format.redMax + 1); + uint8_t green = (v >> client->format.greenShift) * 0x100 / (client->format.greenMax + 1); + uint8_t blue = (v >> client->format.blueShift) * 0x100 / (client->format.blueMax + 1); /* Output ARGB */ if (vnc_client->settings->swap_red_blue) - *(buffer_current++) = (alpha << 24) | (blue << 16) | (green << 8) | red; + *(layer_current_pixel++) = (alpha << 24) | (blue << 16) | (green << 8) | red; else - *(buffer_current++) = (alpha << 24) | (red << 16) | (green << 8) | blue; + *(layer_current_pixel++) = (alpha << 24) | (red << 16) | (green << 8) | blue; - /* Next VNC pixel */ - fb_current += bpp; + /* Advance to next pixel in VNC framebuffer */ + vnc_current_pixel += vnc_bpp; } } - /* Update stored cursor information */ - guac_common_cursor_set_argb(vnc_client->display->cursor, x, y, - buffer, w, h, stride); + /* Mark modified region as dirty */ + guac_rect_extend(&context->dirty, &op_bounds); - /* Free surface */ - guac_mem_free(buffer); + /* Draw operation is now complete */ + guac_display_layer_close_raw(cursor_layer, context); /* libvncclient does not free rcMask as it does rcSource */ if (client->rcMask != NULL) { free(client->rcMask); client->rcMask = NULL; } + } diff --git a/src/protocols/vnc/cursor.h b/src/protocols/vnc/cursor.h index 193b8749b..18fe7e3cd 100644 --- a/src/protocols/vnc/cursor.h +++ b/src/protocols/vnc/cursor.h @@ -49,10 +49,10 @@ * @param h * The height of the cursor image, in pixels. * - * @param bpp + * @param vnc_bpp * The number of bytes in each pixel, which must be either 4, 2, or 1. */ -void guac_vnc_cursor(rfbClient* client, int x, int y, int w, int h, int bpp); +void guac_vnc_cursor(rfbClient* client, int x, int y, int w, int h, int vnc_bpp); #endif diff --git a/src/protocols/vnc/display.c b/src/protocols/vnc/display.c index 8848e8933..d31db4f68 100644 --- a/src/protocols/vnc/display.c +++ b/src/protocols/vnc/display.c @@ -21,7 +21,6 @@ #include "client.h" #include "common/iconv.h" -#include "common/surface.h" #include "vnc.h" #include @@ -49,105 +48,94 @@ void guac_vnc_update(rfbClient* client, int x, int y, int w, int h) { guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; - int dx, dy; + /* Begin drawing operation directly to default layer */ + guac_display_layer* default_layer = guac_display_default_layer(vnc_client->display); + guac_display_layer_raw_context* context = guac_display_layer_open_raw(default_layer); - /* Cairo image buffer */ - int stride; - unsigned char* buffer; - unsigned char* buffer_row_current; - cairo_surface_t* surface; + /* Convert operation coordinates to guac_rect for easier manipulation */ + guac_rect op_bounds; + guac_rect_init(&op_bounds, x, y, w, h); - /* VNC framebuffer */ - unsigned int bpp; - unsigned int fb_stride; - unsigned char* fb_row_current; - - /* Ignore extra update if already handled by copyrect */ - if (vnc_client->copy_rect_used) { - vnc_client->copy_rect_used = 0; - return; - } + /* Ensure draw is within current bounds of the pending frame */ + guac_rect_constrain(&op_bounds, &context->bounds); - /* Init Cairo buffer */ - stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w); - buffer = guac_mem_alloc(h, stride); - buffer_row_current = buffer; - - bpp = client->format.bitsPerPixel/8; - fb_stride = bpp * client->width; - fb_row_current = client->frameBuffer + (y * fb_stride) + (x * bpp); - - /* Copy image data from VNC client to PNG */ - for (dy = y; dyformat.bitsPerPixel / 8; + size_t vnc_stride = guac_mem_ckd_mul_or_die(vnc_bpp, client->width); + const unsigned char* vnc_current_row = GUAC_RECT_CONST_BUFFER(op_bounds, client->frameBuffer, vnc_stride, vnc_bpp); - unsigned int* buffer_current; - unsigned char* fb_current; - - /* Get current buffer row, advance to next */ - buffer_current = (unsigned int*) buffer_row_current; - buffer_row_current += stride; + unsigned char* layer_current_row = GUAC_RECT_MUTABLE_BUFFER(op_bounds, context->buffer, context->stride, GUAC_DISPLAY_LAYER_RAW_BPP); + for (int dy = op_bounds.top; dy < op_bounds.bottom; dy++) { - /* Get current framebuffer row, advance to next */ - fb_current = fb_row_current; - fb_row_current += fb_stride; + /* Get current Guacamole buffer row, advance to next */ + uint32_t* layer_current_pixel = (uint32_t*) layer_current_row; + layer_current_row += context->stride; - for (dx = x; dx> client->format.redShift) * 0x100 / (client->format.redMax + 1); - green = (v >> client->format.greenShift) * 0x100 / (client->format.greenMax+ 1); - blue = (v >> client->format.blueShift) * 0x100 / (client->format.blueMax + 1); + /* Translate value to 32-bit RGB */ + uint8_t red = (v >> client->format.redShift) * 0x100 / (client->format.redMax + 1); + uint8_t green = (v >> client->format.greenShift) * 0x100 / (client->format.greenMax + 1); + uint8_t blue = (v >> client->format.blueShift) * 0x100 / (client->format.blueMax + 1); /* Output RGB */ if (vnc_client->settings->swap_red_blue) - *(buffer_current++) = (blue << 16) | (green << 8) | red; + *(layer_current_pixel++) = 0xFF000000 | (blue << 16) | (green << 8) | red; else - *(buffer_current++) = (red << 16) | (green << 8) | blue; + *(layer_current_pixel++) = 0xFF000000 | (red << 16) | (green << 8) | blue; - fb_current += bpp; + /* Advance to next pixel in VNC framebuffer */ + vnc_current_pixel += vnc_bpp; } } - /* Create surface from decoded buffer */ - surface = cairo_image_surface_create_for_data(buffer, CAIRO_FORMAT_RGB24, - w, h, stride); + /* Mark modified region as dirty */ + guac_rect_extend(&context->dirty, &op_bounds); - /* Draw directly to default layer */ - guac_common_surface_draw(vnc_client->display->default_surface, - x, y, surface); + /* Hint at source of copied data if this update involved CopyRect */ + if (vnc_client->copy_rect_used) { + context->hint_from = default_layer; + vnc_client->copy_rect_used = 0; + } - /* Free surface */ - cairo_surface_destroy(surface); - guac_mem_free(buffer); + /* Draw operation is now complete */ + guac_display_layer_close_raw(default_layer, context); } -void guac_vnc_copyrect(rfbClient* client, int src_x, int src_y, int w, int h, int dest_x, int dest_y) { +void guac_vnc_update_finished(rfbClient* client) { guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; - /* Copy specified rectangle within default layer */ - guac_common_surface_copy(vnc_client->display->default_surface, - src_x, src_y, w, h, - vnc_client->display->default_surface, dest_x, dest_y); + guac_display_end_multiple_frames(vnc_client->display, 1); + +} + +void guac_vnc_copyrect(rfbClient* client, int src_x, int src_y, int w, int h, int dest_x, int dest_y) { + + guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); + guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; vnc_client->copy_rect_used = 1; @@ -199,7 +187,7 @@ rfbBool guac_vnc_malloc_framebuffer(rfbClient* rfb_client) { /* Resize surface */ if (vnc_client->display != NULL) - guac_common_surface_resize(vnc_client->display->default_surface, + guac_display_layer_resize(guac_display_default_layer(vnc_client->display), rfb_client->width, rfb_client->height); /* Use original, wrapped proc */ diff --git a/src/protocols/vnc/display.h b/src/protocols/vnc/display.h index 831f012f5..1ef550bb9 100644 --- a/src/protocols/vnc/display.h +++ b/src/protocols/vnc/display.h @@ -50,6 +50,17 @@ */ void guac_vnc_update(rfbClient* client, int x, int y, int w, int h); +/** + * Callback invoked by libVNCServer when all binary image data for the current + * frame has been received from the VNC server. The image data that frame will + * have been exposed via previous calls to guac_vnc_update(). + * + * @param client + * The VNC client associated with the VNC session in which the new image + * was received. + */ +void guac_vnc_update_finished(rfbClient* client); + /** * Callback invoked by libVNCServer when it receives a CopyRect message. * CopyRect specified a rectangle of source data within the display and a diff --git a/src/protocols/vnc/input.c b/src/protocols/vnc/input.c index 584819183..166bb6ca4 100644 --- a/src/protocols/vnc/input.c +++ b/src/protocols/vnc/input.c @@ -19,10 +19,9 @@ #include "config.h" -#include "common/cursor.h" -#include "common/display.h" #include "vnc.h" +#include #include #include #include @@ -34,7 +33,7 @@ int guac_vnc_user_mouse_handler(guac_user* user, int x, int y, int mask) { rfbClient* rfb_client = vnc_client->rfb_client; /* Store current mouse location/state */ - guac_common_cursor_update(vnc_client->display->cursor, user, x, y, mask); + guac_display_notify_user_moved_mouse(vnc_client->display, user, x, y, mask); /* Report mouse position within recording */ if (vnc_client->recording != NULL) diff --git a/src/protocols/vnc/log.c b/src/protocols/vnc/log.c index ba593e81d..afb93d0e7 100644 --- a/src/protocols/vnc/log.c +++ b/src/protocols/vnc/log.c @@ -21,7 +21,6 @@ #include "client.h" #include "common/iconv.h" -#include "common/surface.h" #include #include diff --git a/src/protocols/vnc/log.h b/src/protocols/vnc/log.h index 65f3dc2d4..3f348605c 100644 --- a/src/protocols/vnc/log.h +++ b/src/protocols/vnc/log.h @@ -24,7 +24,6 @@ #include "client.h" #include "common/iconv.h" -#include "common/surface.h" #include #include diff --git a/src/protocols/vnc/user.c b/src/protocols/vnc/user.c index 42bae34aa..61616e196 100644 --- a/src/protocols/vnc/user.c +++ b/src/protocols/vnc/user.c @@ -21,7 +21,6 @@ #include "clipboard.h" #include "input.h" -#include "common/display.h" #include "common/dot_cursor.h" #include "common/pointer_cursor.h" #include "user.h" @@ -35,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -121,10 +121,8 @@ int guac_vnc_user_leave_handler(guac_user* user) { guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data; - if (vnc_client->display) { - /* Update shared cursor state */ - guac_common_cursor_remove_user(vnc_client->display->cursor, user); - } + if (vnc_client->display) + guac_display_notify_user_left(vnc_client->display, user); /* Free settings if not owner (owner settings will be freed with client) */ if (!user->owner) { diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index ec2c505d4..9d90fc39b 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -23,8 +23,6 @@ #include "client.h" #include "clipboard.h" #include "common/clipboard.h" -#include "common/cursor.h" -#include "common/display.h" #include "cursor.h" #include "display.h" #include "log.h" @@ -42,6 +40,7 @@ #endif #include +#include #include #include #include @@ -140,6 +139,7 @@ rfbClient* guac_vnc_get_client(guac_client* client) { /* Framebuffer update handler */ rfb_client->GotFrameBufferUpdate = guac_vnc_update; + rfb_client->FinishedFrameBufferUpdate = guac_vnc_update_finished; rfb_client->GotCopyRect = guac_vnc_copyrect; #ifdef ENABLE_VNC_TLS_LOCKING @@ -480,12 +480,13 @@ void* guac_vnc_client_thread(void* data) { } /* Create display */ - vnc_client->display = guac_common_display_alloc(client, - rfb_client->width, rfb_client->height); + vnc_client->display = guac_display_alloc(client); + guac_display_layer_resize(guac_display_default_layer(vnc_client->display), 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); + guac_display_layer_set_lossless(guac_display_default_layer(vnc_client->display), + settings->lossless); /* If compression and display quality have been configured, set those. */ if (settings->compress_level >= 0 && settings->compress_level <= 9) @@ -497,81 +498,33 @@ void* guac_vnc_client_thread(void* data) { /* If not read-only, set an appropriate cursor */ if (settings->read_only == 0) { if (settings->remote_cursor) - guac_common_cursor_set_dot(vnc_client->display->cursor); + guac_display_set_cursor(vnc_client->display, GUAC_DISPLAY_CURSOR_DOT); else - guac_common_cursor_set_pointer(vnc_client->display->cursor); - + guac_display_set_cursor(vnc_client->display, GUAC_DISPLAY_CURSOR_POINTER); } - guac_socket_flush(client->socket); - - guac_timestamp last_frame_end = guac_timestamp_current(); + guac_display_end_frame(vnc_client->display); /* Handle messages from VNC server while client is running */ while (client->state == GUAC_CLIENT_RUNNING) { /* Wait for start of frame */ - int wait_result = guac_vnc_wait_for_messages(rfb_client, - GUAC_VNC_FRAME_START_TIMEOUT); - if (wait_result > 0) { - - int processing_lag = guac_client_get_processing_lag(client); - guac_timestamp frame_start = guac_timestamp_current(); - - /* Read server messages until frame is built */ - do { - - guac_timestamp frame_end; - int frame_remaining; - - /* Handle any message received */ - if (!HandleRFBServerMessage(rfb_client)) { - guac_client_abort(client, - GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, - "Error handling message from VNC server."); - break; - } - - /* Calculate time remaining in frame */ - frame_end = guac_timestamp_current(); - frame_remaining = frame_start + GUAC_VNC_FRAME_DURATION - - frame_end; - - /* Calculate time that client needs to catch up */ - int time_elapsed = frame_end - last_frame_end; - int required_wait = processing_lag - time_elapsed; - - /* Increase the duration of this frame if client is lagging */ - if (required_wait > GUAC_VNC_FRAME_TIMEOUT) - wait_result = guac_vnc_wait_for_messages(rfb_client, - required_wait*1000); - - /* Wait again if frame remaining */ - else if (frame_remaining > 0) - wait_result = guac_vnc_wait_for_messages(rfb_client, - GUAC_VNC_FRAME_TIMEOUT*1000); - else - break; - - } while (wait_result > 0); - - /* Record end of frame, excluding server-side rendering time (we - * assume server-side rendering time will be consistent between any - * two subsequent frames, and that this time should thus be - * excluded from the required wait period of the next frame). */ - last_frame_end = frame_start; - + int wait_result = guac_vnc_wait_for_messages(rfb_client, GUAC_VNC_FRAME_START_TIMEOUT); + if (wait_result == 0) + continue; + + /* Handle any message received */ + if (!HandleRFBServerMessage(rfb_client)) { + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + "Error handling message from VNC server."); + break; } /* If an error occurs, log it and fail */ if (wait_result < 0) guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Connection closed."); - /* Flush frame */ - guac_common_surface_flush(vnc_client->display->default_surface); - guac_client_end_frame(client); - guac_socket_flush(client->socket); - } /* Kill client and finish connection */ diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h index bdc62e6de..a35cddec3 100644 --- a/src/protocols/vnc/vnc.h +++ b/src/protocols/vnc/vnc.h @@ -23,12 +23,11 @@ #include "config.h" #include "common/clipboard.h" -#include "common/display.h" #include "common/iconv.h" -#include "common/surface.h" #include "settings.h" #include +#include #include #include @@ -88,7 +87,7 @@ typedef struct guac_vnc_client { /** * The current display state. */ - guac_common_display* display; + guac_display* display; /** * Internal clipboard.