Skip to content

Commit

Permalink
GUACAMOLE-377: Migrate VNC support to guac_display API.
Browse files Browse the repository at this point in the history
  • Loading branch information
mike-jumper committed Jun 19, 2024
1 parent 483700a commit 76425f0
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 200 deletions.
5 changes: 3 additions & 2 deletions src/protocols/vnc/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#endif

#include <guacamole/client.h>
#include <guacamole/display.h>
#include <guacamole/mem.h>
#include <guacamole/recording.h>

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

Expand Down Expand Up @@ -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 */
Expand Down
95 changes: 44 additions & 51 deletions src/protocols/vnc/cursor.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,113 +20,106 @@
#include "config.h"

#include "client.h"
#include "common/cursor.h"
#include "common/display.h"
#include "common/surface.h"
#include "vnc.h"

#include <cairo/cairo.h>
#include <guacamole/client.h>
#include <guacamole/display.h>
#include <guacamole/layer.h>
#include <guacamole/mem.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <rfb/rfbclient.h>
#include <rfb/rfbproto.h>

/* 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 <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <syslog.h>

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; dy<h; dy++) {
/* VNC image buffer */
unsigned char* vnc_current_row = client->rcSource;
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<w; dx++) {
/* Get current VNC framebuffer row, advance to next */
const unsigned char* vnc_current_pixel = vnc_current_row;
vnc_current_row += vnc_stride;

unsigned char alpha, red, green, blue;
unsigned int v;
for (int dx = 0; dx < w; dx++) {

/* Read current pixel value */
switch (bpp) {
uint32_t v;
switch (vnc_bpp) {
case 4:
v = *((uint32_t*) fb_current);
v = *((uint32_t*) vnc_current_pixel);
break;

case 2:
v = *((uint16_t*) fb_current);
v = *((uint16_t*) vnc_current_pixel);
break;

default:
v = *((uint8_t*) fb_current);
v = *((uint8_t*) vnc_current_pixel);
}

/* Translate mask to alpha */
if (*(fb_mask++)) alpha = 0xFF;
else alpha = 0x00;
uint8_t alpha = *(vnc_mask++) ? 0xFF : 0x00;

/* Translate value to RGB */
red = (v >> 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;
}

}

4 changes: 2 additions & 2 deletions src/protocols/vnc/cursor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

120 changes: 54 additions & 66 deletions src/protocols/vnc/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

#include "client.h"
#include "common/iconv.h"
#include "common/surface.h"
#include "vnc.h"

#include <cairo/cairo.h>
Expand Down Expand Up @@ -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; dy<y+h; dy++) {
/* VNC framebuffer */
unsigned int vnc_bpp = client->format.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<x+w; dx++) {
/* Get current VNC framebuffer row, advance to next */
const unsigned char* vnc_current_pixel = vnc_current_row;
vnc_current_row += vnc_stride;

unsigned char red, green, blue;
unsigned int v;
for (int dx = op_bounds.left; dx < op_bounds.right; dx++) {

switch (bpp) {
/* Read current VNC pixel value */
uint32_t v;
switch (vnc_bpp) {
case 4:
v = *((uint32_t*) fb_current);
v = *((uint32_t*) vnc_current_pixel);
break;

case 2:
v = *((uint16_t*) fb_current);
v = *((uint16_t*) vnc_current_pixel);
break;

default:
v = *((uint8_t*) fb_current);
v = *((uint8_t*) vnc_current_pixel);
}

/* Translate value to RGB */
red = (v >> 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;

Expand Down Expand Up @@ -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 */
Expand Down
11 changes: 11 additions & 0 deletions src/protocols/vnc/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 76425f0

Please sign in to comment.