From f3935619259771ff1514c8e0a037ab2f7c491ff3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 29 Feb 2016 21:38:51 -0800 Subject: [PATCH] GUAC-1389: Bring common up to date with screen sharing changes. --- Makefile.am | 1 + src/common/Makefile.am | 8 + src/common/guac_blank_cursor.c | 78 ++++++++ src/common/guac_blank_cursor.h | 67 +++++++ src/common/guac_clipboard.c | 33 +++- src/common/guac_cursor.c | 289 +++++++++++++++++++++++++++++ src/common/guac_cursor.h | 234 ++++++++++++++++++++++++ src/common/guac_display.c | 301 +++++++++++++++++++++++++++++++ src/common/guac_display.h | 203 +++++++++++++++++++++ src/common/guac_dot_cursor.c | 8 +- src/common/guac_dot_cursor.h | 6 +- src/common/guac_ibar_cursor.c | 101 +++++++++++ src/common/guac_ibar_cursor.h | 65 +++++++ src/common/guac_json.c | 40 ++-- src/common/guac_json.h | 54 +++--- src/common/guac_pointer_cursor.c | 8 +- src/common/guac_pointer_cursor.h | 6 +- src/common/guac_surface.c | 61 +++++-- src/common/guac_surface.h | 16 ++ tests/Makefile.am | 7 + 20 files changed, 1502 insertions(+), 84 deletions(-) create mode 100644 src/common/guac_blank_cursor.c create mode 100644 src/common/guac_blank_cursor.h create mode 100644 src/common/guac_cursor.c create mode 100644 src/common/guac_cursor.h create mode 100644 src/common/guac_display.c create mode 100644 src/common/guac_display.h create mode 100644 src/common/guac_ibar_cursor.c create mode 100644 src/common/guac_ibar_cursor.h diff --git a/Makefile.am b/Makefile.am index 6d4e8676..b2c18ab5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -37,6 +37,7 @@ DIST_SUBDIRS = \ SUBDIRS = \ src/libguac \ + src/common \ tests if ENABLE_COMMON_SSH diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 4f4b02c8..f04e04b8 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -27,8 +27,12 @@ noinst_LTLIBRARIES = libguac_common.la noinst_HEADERS = \ guac_io.h \ + guac_blank_cursor.h \ guac_clipboard.h \ + guac_cursor.h \ + guac_display.h \ guac_dot_cursor.h \ + guac_ibar_cursor.h \ guac_iconv.h \ guac_json.h \ guac_list.h \ @@ -39,8 +43,12 @@ noinst_HEADERS = \ libguac_common_la_SOURCES = \ guac_io.c \ + guac_blank_cursor.c \ guac_clipboard.c \ + guac_cursor.c \ + guac_display.c \ guac_dot_cursor.c \ + guac_ibar_cursor.c \ guac_iconv.c \ guac_json.c \ guac_list.c \ diff --git a/src/common/guac_blank_cursor.c b/src/common/guac_blank_cursor.c new file mode 100644 index 00000000..d0d65080 --- /dev/null +++ b/src/common/guac_blank_cursor.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +/* Dimensions */ +const int guac_common_blank_cursor_width = 1; +const int guac_common_blank_cursor_height = 1; + +/* Format */ +const cairo_format_t guac_common_blank_cursor_format = CAIRO_FORMAT_ARGB32; +const int guac_common_blank_cursor_stride = 4; + +/* Embedded blank cursor graphic */ +unsigned char guac_common_blank_cursor[] = { + + 0x00,0x00,0x00,0x00 + +}; + +void guac_common_set_blank_cursor(guac_user* user) { + + guac_client* client = user->client; + guac_socket* socket = user->socket; + + /* Draw to buffer */ + guac_layer* cursor = guac_client_alloc_buffer(client); + + cairo_surface_t* graphic = cairo_image_surface_create_for_data( + guac_common_blank_cursor, + guac_common_blank_cursor_format, + guac_common_blank_cursor_width, + guac_common_blank_cursor_height, + guac_common_blank_cursor_stride); + + guac_user_stream_png(user, socket, GUAC_COMP_SRC, cursor, + 0, 0, graphic); + cairo_surface_destroy(graphic); + + /* Set cursor */ + guac_protocol_send_cursor(socket, 0, 0, cursor, 0, 0, + guac_common_blank_cursor_width, + guac_common_blank_cursor_height); + + /* Free buffer */ + guac_client_free_buffer(client, cursor); + + guac_client_log(client, GUAC_LOG_DEBUG, + "Client cursor image set to generic transparent (blank) cursor."); + +} + diff --git a/src/common/guac_blank_cursor.h b/src/common/guac_blank_cursor.h new file mode 100644 index 00000000..591a58d8 --- /dev/null +++ b/src/common/guac_blank_cursor.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_COMMON_BLANK_CURSOR_H +#define GUAC_COMMON_BLANK_CURSOR_H + +#include "config.h" + +#include +#include + +/** + * Width of the embedded transparent (blank) mouse cursor graphic. + */ +extern const int guac_common_blank_cursor_width; + +/** + * Height of the embedded transparent (blank) mouse cursor graphic. + */ +extern const int guac_common_blank_cursor_height; + +/** + * Number of bytes in each row of the embedded transparent (blank) mouse cursor + * graphic. + */ +extern const int guac_common_blank_cursor_stride; + +/** + * The Cairo grapic format of the transparent (blank) mouse cursor graphic. + */ +extern const cairo_format_t guac_common_blank_cursor_format; + +/** + * Embedded transparent (blank) mouse cursor graphic. + */ +extern unsigned char guac_common_blank_cursor[]; + +/** + * Sets the cursor of the remote display to the embedded transparent (blank) + * cursor graphic. + * + * @param user + * The guac_user to send the cursor to. + */ +void guac_common_set_blank_cursor(guac_user* user); + +#endif + diff --git a/src/common/guac_clipboard.c b/src/common/guac_clipboard.c index 0f880bc9..302df367 100644 --- a/src/common/guac_clipboard.c +++ b/src/common/guac_clipboard.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -48,16 +49,22 @@ void guac_common_clipboard_free(guac_common_clipboard* clipboard) { free(clipboard); } -void guac_common_clipboard_send(guac_common_clipboard* clipboard, guac_client* client) { +/** + * Callback for guac_client_foreach_user() which sends clipboard data to each + * connected client. + */ +static void* __send_user_clipboard(guac_user* user, void* data) { + + guac_common_clipboard* clipboard = (guac_common_clipboard*) data; char* current = clipboard->buffer; int remaining = clipboard->length; /* Begin stream */ - guac_stream* stream = guac_client_alloc_stream(client); - guac_protocol_send_clipboard(client->socket, stream, clipboard->mimetype); + guac_stream* stream = guac_user_alloc_stream(user); + guac_protocol_send_clipboard(user->socket, stream, clipboard->mimetype); - guac_client_log(client, GUAC_LOG_DEBUG, + guac_user_log(user, GUAC_LOG_DEBUG, "Created stream %i for %s clipboard data.", stream->index, clipboard->mimetype); @@ -70,8 +77,8 @@ void guac_common_clipboard_send(guac_common_clipboard* clipboard, guac_client* c block_size = remaining; /* Send block */ - guac_protocol_send_blob(client->socket, stream, current, block_size); - guac_client_log(client, GUAC_LOG_DEBUG, + guac_protocol_send_blob(user->socket, stream, current, block_size); + guac_user_log(user, GUAC_LOG_DEBUG, "Sent %i bytes of clipboard data on stream %i.", block_size, stream->index); @@ -81,14 +88,22 @@ void guac_common_clipboard_send(guac_common_clipboard* clipboard, guac_client* c } - guac_client_log(client, GUAC_LOG_DEBUG, + guac_user_log(user, GUAC_LOG_DEBUG, "Clipboard stream %i complete.", stream->index); /* End stream */ - guac_protocol_send_end(client->socket, stream); - guac_client_free_stream(client, stream); + guac_protocol_send_end(user->socket, stream); + guac_user_free_stream(user, stream); + return NULL; + +} + +void guac_common_clipboard_send(guac_common_clipboard* clipboard, guac_client* client) { + guac_client_log(client, GUAC_LOG_DEBUG, "Broadcasting clipboard to all connected users."); + guac_client_foreach_user(client, __send_user_clipboard, clipboard); + guac_client_log(client, GUAC_LOG_DEBUG, "Broadcast of clipboard complete."); } void guac_common_clipboard_reset(guac_common_clipboard* clipboard, const char* mimetype) { diff --git a/src/common/guac_cursor.c b/src/common/guac_cursor.c new file mode 100644 index 00000000..19d4bc2d --- /dev/null +++ b/src/common/guac_cursor.c @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "guac_blank_cursor.h" +#include "guac_dot_cursor.h" +#include "guac_cursor.h" +#include "guac_ibar_cursor.h" +#include "guac_pointer_cursor.h" +#include "guac_surface.h" + +#include +#include +#include +#include +#include + +#include +#include + +guac_common_cursor* guac_common_cursor_alloc(guac_client* client) { + + guac_common_cursor* cursor = malloc(sizeof(guac_common_cursor)); + if (cursor == NULL) + return NULL; + + /* Associate cursor with client and allocate cursor layer */ + cursor->client = client; + cursor->layer= guac_client_alloc_layer(client); + + /* Allocate initial image buffer */ + cursor->image_buffer_size = GUAC_COMMON_CURSOR_DEFAULT_SIZE; + cursor->image_buffer = malloc(cursor->image_buffer_size); + + /* No cursor image yet */ + cursor->width = 0; + cursor->height = 0; + cursor->surface = NULL; + cursor->hotspot_x = 0; + cursor->hotspot_y = 0; + + /* No user has moved the mouse yet */ + cursor->user = NULL; + + /* Start cursor in upper-left */ + cursor->x = 0; + cursor->y = 0; + + return cursor; + +} + +void guac_common_cursor_free(guac_common_cursor* cursor) { + + /* Free image buffer and surface */ + free(cursor->image_buffer); + if (cursor->surface != NULL) + cairo_surface_destroy(cursor->surface); + + /* Return layer to pool */ + guac_client_free_layer(cursor->client, cursor->layer); + + free(cursor); + +} + +void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, + guac_socket* socket) { + + /* Synchronize location */ + guac_protocol_send_move(socket, cursor->layer, GUAC_DEFAULT_LAYER, + cursor->x - cursor->hotspot_x, + cursor->y - cursor->hotspot_y, + 0); + + /* Synchronize cursor image */ + if (cursor->surface != NULL) { + guac_protocol_send_size(socket, cursor->layer, + cursor->width, cursor->height); + + guac_user_stream_png(user, socket, GUAC_COMP_SRC, + cursor->layer, 0, 0, cursor->surface); + } + + guac_socket_flush(socket); + +} + +void guac_common_cursor_move(guac_common_cursor* cursor, guac_user* user, + int x, int y) { + + guac_user* last_user = cursor->user; + + /* Update current user of cursor */ + if (last_user != user) { + + cursor->user = user; + + /* Make cursor layer visible to previous user */ + if (last_user != NULL) { + guac_protocol_send_shade(last_user->socket, cursor->layer, 255); + guac_socket_flush(last_user->socket); + } + + /* Show hardware cursor */ + guac_protocol_send_cursor(user->socket, + cursor->hotspot_x, cursor->hotspot_y, + cursor->layer, 0, 0, cursor->width, cursor->height); + + /* Hide cursor layer from new user */ + guac_protocol_send_shade(user->socket, cursor->layer, 0); + guac_socket_flush(user->socket); + + } + + /* Update cursor position */ + cursor->x = x; + cursor->y = y; + + guac_protocol_send_move(cursor->client->socket, cursor->layer, + GUAC_DEFAULT_LAYER, + x - cursor->hotspot_x, + y - cursor->hotspot_y, + 0); + + guac_socket_flush(cursor->client->socket); + +} + +/** + * Ensures the cursor image buffer has enough room to fit an image with the + * given characteristics. Existing image buffer data may be destroyed. + */ +static void guac_common_cursor_resize(guac_common_cursor* cursor, + int width, int height, int stride) { + + int minimum_size = height * stride; + + /* Grow image buffer if necessary */ + if (cursor->image_buffer_size < minimum_size) { + + /* Calculate new size */ + cursor->image_buffer_size = minimum_size*2; + + /* Destructively reallocate image buffer */ + free(cursor->image_buffer); + cursor->image_buffer = malloc(cursor->image_buffer_size); + + } + +} + +/** + * Callback for guac_client_foreach_user() which sends the current cursor image + * as PNG data to each connected client. + */ +static void* __send_user_cursor_image(guac_user* user, void* data) { + + guac_common_cursor* cursor = (guac_common_cursor*) data; + + guac_user_stream_png(user, user->socket, GUAC_COMP_SRC, + cursor->layer, 0, 0, cursor->surface); + + return NULL; + +} + +void guac_common_cursor_set_argb(guac_common_cursor* cursor, int hx, int hy, + unsigned const char* data, int width, int height, int stride) { + + /* Copy image data */ + guac_common_cursor_resize(cursor, width, height, stride); + memcpy(cursor->image_buffer, data, height * stride); + + if (cursor->surface != NULL) + cairo_surface_destroy(cursor->surface); + + cursor->surface = cairo_image_surface_create_for_data(cursor->image_buffer, + CAIRO_FORMAT_ARGB32, width, height, stride); + + /* Set new cursor parameters */ + cursor->width = width; + cursor->height = height; + cursor->hotspot_x = hx; + cursor->hotspot_y = hy; + + /* Update location based on new hotspot */ + guac_protocol_send_move(cursor->client->socket, cursor->layer, + GUAC_DEFAULT_LAYER, + cursor->x - hx, + cursor->y - hy, + 0); + + /* Broadcast new cursor image to all users */ + guac_protocol_send_size(cursor->client->socket, cursor->layer, + width, height); + + guac_client_foreach_user(cursor->client, __send_user_cursor_image, cursor); + + guac_socket_flush(cursor->client->socket); + + /* Update hardware cursor of current user */ + if (cursor->user != NULL) { + guac_protocol_send_cursor(cursor->user->socket, hx, hy, + cursor->layer, 0, 0, width, height); + + guac_socket_flush(cursor->user->socket); + } + +} + +void guac_common_cursor_set_surface(guac_common_cursor* cursor, int hx, int hy, + guac_common_surface* surface) { + + /* Set cursor to surface contents */ + guac_common_cursor_set_argb(cursor, hx, hy, surface->buffer, + surface->width, surface->height, surface->stride); + +} + +void guac_common_cursor_set_pointer(guac_common_cursor* cursor) { + + guac_common_cursor_set_argb(cursor, 0, 0, + guac_common_pointer_cursor, + guac_common_pointer_cursor_width, + guac_common_pointer_cursor_height, + guac_common_pointer_cursor_stride); + +} + +void guac_common_cursor_set_dot(guac_common_cursor* cursor) { + + guac_common_cursor_set_argb(cursor, 2, 2, + guac_common_dot_cursor, + guac_common_dot_cursor_width, + guac_common_dot_cursor_height, + guac_common_dot_cursor_stride); + +} + +void guac_common_cursor_set_ibar(guac_common_cursor* cursor) { + + guac_common_cursor_set_argb(cursor, + guac_common_ibar_cursor_width / 2, + guac_common_ibar_cursor_height / 2, + guac_common_ibar_cursor, + guac_common_ibar_cursor_width, + guac_common_ibar_cursor_height, + guac_common_ibar_cursor_stride); + +} + +void guac_common_cursor_set_blank(guac_common_cursor* cursor) { + + guac_common_cursor_set_argb(cursor, 0, 0, + guac_common_blank_cursor, + guac_common_blank_cursor_width, + guac_common_blank_cursor_height, + guac_common_blank_cursor_stride); + +} + +void guac_common_cursor_remove_user(guac_common_cursor* cursor, + guac_user* user) { + + /* Disassociate from given user */ + if (cursor->user == user) + cursor->user = NULL; + +} + diff --git a/src/common/guac_cursor.h b/src/common/guac_cursor.h new file mode 100644 index 00000000..be4fefe9 --- /dev/null +++ b/src/common/guac_cursor.h @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef GUAC_COMMON_CURSOR_H +#define GUAC_COMMON_CURSOR_H + +#include "guac_surface.h" + +#include +#include +#include +#include + +/** + * The default size of the cursor image buffer. + */ +#define GUAC_COMMON_CURSOR_DEFAULT_SIZE 64*64*4 + +/** + * Cursor object which maintains and synchronizes the current mouse cursor + * state across all users of a specific client. + */ +typedef struct guac_common_cursor { + + /** + * The client to maintain the mouse cursor for. + */ + guac_client* client; + + /** + * The cursor layer. This layer will be available to all connected users, + * but will be visible only to those users who are not moving the mouse. + */ + guac_layer* layer; + + /** + * The width of the cursor image, in pixels. + */ + int width; + + /** + * The height of the cursor image, in pixels. + */ + int height; + + /** + * Arbitrary image data buffer, backing the Cairo surface used to store + * the cursor image. + */ + unsigned char* image_buffer; + + /** + * The size of the image data buffer, in bytes. + */ + int image_buffer_size; + + /** + * The current cursor image, if any. If the mouse cursor has not yet been + * set, this will be NULL. + */ + cairo_surface_t* surface; + + /** + * The X coordinate of the hotspot of the mouse cursor. + */ + int hotspot_x; + + /** + * The Y coordinate of the hotspot of the mouse cursor. + */ + int hotspot_y; + + /** + * The last user to move the mouse, or NULL if no user has moved the + * mouse yet. + */ + guac_user* user; + + /** + * The X coordinate of the current mouse cursor location. + */ + int x; + + /** + * The Y coordinate of the current mouse cursor location. + */ + int y; + +} guac_common_cursor; + +/** + * Allocates a new cursor object which maintains and synchronizes the current + * mouse cursor state across all users of the given client. + * + * @param client The client for which this object shall maintain the mouse + * cursor. + */ +guac_common_cursor* guac_common_cursor_alloc(guac_client* client); + +/** + * Frees the given cursor. + * + * @param cursor The cursor to free. + */ +void guac_common_cursor_free(guac_common_cursor* cursor); + +/** + * Sends the current state of this cursor across the given socket, including + * the current cursor image. The resulting cursor on the remote display will + * be visible. + * + * @param cursor + * The cursor to send. + * + * @param user + * The user receiving the updated cursor. + * + * @param socket + * The socket over which the updated cursor should be sent. + */ +void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, + guac_socket* socket); + +/** + * Moves the mouse cursor, marking the given user as the most recent user of + * the mouse. The remote mouse cursor will be hidden for this user and shown + * for all others. + * + * @param cursor The cursor being moved. + * @param user The user that moved the cursor. + * @param x The new X coordinate of the cursor. + * @param y The new Y coordinate of the cursor. + */ +void guac_common_cursor_move(guac_common_cursor* cursor, guac_user* user, + int x, int y); + +/** + * Sets the cursor image to the given raw image data. This raw image data must + * be in 32-bit ARGB format, having 8 bits per color component, where the + * alpha component is stored in the high-order 8 bits, and blue is stored + * in the low-order 8 bits. + * + * @param cursor The cursor to set the image of. + * @param hx The X coordinate of the hotspot of the new cursor image. + * @param hy The Y coordinate of the hotspot of the new cursor image. + * @param data A pointer to raw 32-bit ARGB image data. + * @param width The width of the given image data, in pixels. + * @param height The height of the given image data, in pixels. + * @param stride The number of bytes in a single row of image data. + */ +void guac_common_cursor_set_argb(guac_common_cursor* cursor, int hx, int hy, + unsigned const char* data, int width, int height, int stride); + +/** + * Sets the cursor image to the contents of the given surface. The entire + * contents of the surface are used, and the dimensions of the resulting + * cursor will be the dimensions of the given surface. + * + * @param cursor The cursor to set the image of. + * @param hx The X coordinate of the hotspot of the new cursor image. + * @param hy The Y coordinate of the hotspot of the new cursor image. + * @param surface The surface containing the cursor image. + */ +void guac_common_cursor_set_surface(guac_common_cursor* cursor, int hx, int hy, + guac_common_surface* surface); + +/** + * Set the cursor of the remote display to the embedded "pointer" graphic. The + * pointer graphic is a black arrow with white border. + * + * @param cursor The cursor to set the image of. + */ +void guac_common_cursor_set_pointer(guac_common_cursor* cursor); + +/** + * Set the cursor of the remote display to the embedded "dot" graphic. The dot + * graphic is a small black square with white border. + * + * @param cursor The cursor to set the image of. + */ +void guac_common_cursor_set_dot(guac_common_cursor* cursor); + +/** + * Sets the cursor of the remote display to the embedded "I-bar" graphic. The + * I-bar graphic is a small black "I" shape with white border, used to indicate + * the presence of selectable or editable text. + * + * @param cursor + * The cursor to set the image of. + */ +void guac_common_cursor_set_ibar(guac_common_cursor* cursor); + +/** + * Sets the cursor of the remote display to the embedded transparent (blank) + * graphic, effectively hiding the mouse cursor. + * + * @param cursor + * The cursor to set the image of. + */ +void guac_common_cursor_set_blank(guac_common_cursor* cursor); + +/** + * Removes the given user, such that future synchronization will not occur. + * This is necessary when a user leaves the connection. If a user leaves the + * connection and this is not called, the corresponding guac_user and socket + * may cease to be valid, and future synchronization attempts will segfault. + * + * @param cursor The cursor to remove the user from. + * @param user The user to remove. + */ +void guac_common_cursor_remove_user(guac_common_cursor* cursor, + guac_user* user); + +#endif diff --git a/src/common/guac_display.c b/src/common/guac_display.c new file mode 100644 index 00000000..5a918e7d --- /dev/null +++ b/src/common/guac_display.c @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "guac_cursor.h" +#include "guac_display.h" +#include "guac_surface.h" + +#include +#include + +#include +#include + +/** + * Synchronizes all surfaces within the given linked list to the given socket. + * If the provided pointer to the linked list is NULL, this function has no + * effect. + * + * @param layers + * The head element of the linked list of layers to synchronize, which may + * be NULL if the list is currently empty. + * + * @param user + * The user receiving the layers. + * + * @param socket + * The socket over which each layer should be sent. + */ +static void guac_common_display_dup_layers(guac_common_display_layer* layers, + guac_user* user, guac_socket* socket) { + + guac_common_display_layer* current = layers; + + /* Synchronize all surfaces in given list */ + while (current != NULL) { + guac_common_surface_dup(current->surface, user, socket); + current = current->next; + } + +} + +/** + * Frees all layers and associated surfaces within the given list, as well as + * their corresponding list elements. If the provided pointer to the linked + * list is NULL, this function has no effect. + * + * @param layers + * The head element of the linked list of layers to free, which may be NULL + * if the list is currently empty. + * + * @param client + * The client owning the layers wrapped by each of the layers in the list. + */ +static void guac_common_display_free_layers(guac_common_display_layer* layers, + guac_client* client) { + + guac_common_display_layer* current = layers; + + /* Free each surface in given list */ + while (current != NULL) { + + guac_common_display_layer* next = current->next; + guac_layer* layer = current->layer; + + /* Free surface */ + guac_common_surface_free(current->surface); + + /* Free layer or buffer depending on index */ + if (layer->index < 0) + guac_client_free_buffer(client, layer); + else if (layer->index > 0) + guac_client_free_layer(client, layer); + + /* Free current element and advance to next */ + free(current); + current = next; + + } + +} + +guac_common_display* guac_common_display_alloc(guac_client* client, + int width, int height) { + + /* Allocate display */ + guac_common_display* display = malloc(sizeof(guac_common_display)); + if (display == NULL) + return NULL; + + /* Associate display with given client */ + display->client = client; + + /* Allocate shared cursor */ + display->cursor = guac_common_cursor_alloc(client); + + display->default_surface = guac_common_surface_alloc(client, + client->socket, GUAC_DEFAULT_LAYER, width, height); + + /* No initial layers or buffers */ + display->layers = NULL; + display->buffers = NULL; + + return display; + +} + +void guac_common_display_free(guac_common_display* display) { + + /* Free shared cursor */ + guac_common_cursor_free(display->cursor); + + /* Free default surface */ + guac_common_surface_free(display->default_surface); + + /* Free all layers and buffers */ + guac_common_display_free_layers(display->buffers, display->client); + guac_common_display_free_layers(display->layers, display->client); + + free(display); + +} + +void guac_common_display_dup(guac_common_display* display, guac_user* user, + guac_socket* socket) { + + /* Sunchronize shared cursor */ + guac_common_cursor_dup(display->cursor, user, socket); + + /* Synchronize default surface */ + guac_common_surface_dup(display->default_surface, user, socket); + + /* Synchronize all layers and buffers */ + guac_common_display_dup_layers(display->layers, user, socket); + guac_common_display_dup_layers(display->buffers, user, socket); + +} + +void guac_common_display_flush(guac_common_display* display) { + guac_common_surface_flush(display->default_surface); +} + +/** + * Allocates and inserts a new element into the given linked list of display + * layers, associating it with the given layer and surface. + * + * @param head + * A pointer to the head pointer of the list of layers. The head pointer + * will be updated by this function to point to the newly-allocated + * display layer. + * + * @param layer + * The Guacamole layer to associated with the new display layer. + * + * @param surface + * The surface associated with the given Guacamole layer and which should + * be associated with the new display layer. + * + * @return + * The newly-allocated display layer, which has been associated with the + * provided layer and surface. + */ +static guac_common_display_layer* guac_common_display_add_layer( + guac_common_display_layer** head, guac_layer* layer, + guac_common_surface* surface) { + + guac_common_display_layer* old_head = *head; + + guac_common_display_layer* display_layer = + malloc(sizeof(guac_common_display_layer)); + + /* Init layer/surface pair */ + display_layer->layer = layer; + display_layer->surface = surface; + + /* Insert list element as the new head */ + display_layer->prev = NULL; + display_layer->next = old_head; + *head = display_layer; + + /* Update old head to point to new element, if it existed */ + if (old_head != NULL) + old_head->prev = display_layer; + + return display_layer; + +} + +/** + * Removes the given display layer from the linked list whose head pointer is + * provided. + * + * @param head + * A pointer to the head pointer of the list of layers. The head pointer + * will be updated by this function if necessary, and will be set to NULL + * if the display layer being removed is the only layer in the list. + * + * @param display_layer + * The display layer to remove from the given list. + */ +static void guac_common_display_remove_layer(guac_common_display_layer** head, + guac_common_display_layer* display_layer) { + + /* Update previous element, if it exists */ + if (display_layer->prev != NULL) + display_layer->prev->next = display_layer->next; + + /* If there is no previous element, update the list head */ + else + *head = display_layer->next; + + /* Update next element, if it exists */ + if (display_layer->next != NULL) + display_layer->next->prev = display_layer->prev; + +} + +guac_common_display_layer* guac_common_display_alloc_layer( + guac_common_display* display, int width, int height) { + + guac_layer* layer; + guac_common_surface* surface; + + /* Allocate Guacamole layer */ + layer = guac_client_alloc_layer(display->client); + + /* Allocate corresponding surface */ + surface = guac_common_surface_alloc(display->client, + display->client->socket, layer, width, height); + + /* Add layer and surface to list */ + return guac_common_display_add_layer(&display->layers, layer, surface); + +} + +guac_common_display_layer* guac_common_display_alloc_buffer( + guac_common_display* display, int width, int height) { + + guac_layer* buffer; + guac_common_surface* surface; + + /* Allocate Guacamole buffer */ + buffer = guac_client_alloc_buffer(display->client); + + /* Allocate corresponding surface */ + surface = guac_common_surface_alloc(display->client, + display->client->socket, buffer, width, height); + + /* Add buffer and surface to list */ + return guac_common_display_add_layer(&display->buffers, buffer, surface); + +} + +void guac_common_display_free_layer(guac_common_display* display, + guac_common_display_layer* display_layer) { + + /* Remove list element from list */ + guac_common_display_remove_layer(&display->layers, display_layer); + + /* Free associated layer and surface */ + guac_common_surface_free(display_layer->surface); + guac_client_free_layer(display->client, display_layer->layer); + + /* Free list element */ + free(display_layer); + +} + +void guac_common_display_free_buffer(guac_common_display* display, + guac_common_display_layer* display_buffer) { + + /* Remove list element from list */ + guac_common_display_remove_layer(&display->buffers, display_buffer); + + /* Free associated layer and surface */ + guac_common_surface_free(display_buffer->surface); + guac_client_free_buffer(display->client, display_buffer->layer); + + /* Free list element */ + free(display_buffer); + +} + diff --git a/src/common/guac_display.h b/src/common/guac_display.h new file mode 100644 index 00000000..e1a05f9c --- /dev/null +++ b/src/common/guac_display.h @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_COMMON_DISPLAY_H +#define GUAC_COMMON_DISPLAY_H + +#include "guac_cursor.h" +#include "guac_surface.h" + +#include +#include + +/** + * A list element representing a pairing of a Guacamole layer with a + * corresponding guac_common_surface which wraps that layer. Adjacent layers + * within the same list are pointed to with traditional prev/next pointers. The + * order of layers in lists need not correspond in any way to the natural + * ordering of those layers' indexes nor their stacking order (Z-order) within + * the display. + */ +typedef struct guac_common_display_layer guac_common_display_layer; + +struct guac_common_display_layer { + + /** + * A Guacamole layer. + */ + guac_layer* layer; + + /** + * The surface which wraps the associated layer. + */ + guac_common_surface* surface; + + /** + * The layer immediately prior to this layer within the list containing + * this layer, or NULL if this is the first layer/buffer in the list. + */ + guac_common_display_layer* prev; + + /** + * The layer immediately following this layer within the list containing + * this layer, or NULL if this is the last layer/buffer in the list. + */ + guac_common_display_layer* next; + +}; + +/** + * Abstracts a remote Guacamole display, having an associated client, + * default surface, mouse cursor, and various allocated buffers and layers. + */ +typedef struct guac_common_display { + + /** + * The client associate with this display. + */ + guac_client* client; + + /** + * The default surface of the client display. + */ + guac_common_surface* default_surface; + + /** + * Client-wide cursor, synchronized across all users. + */ + guac_common_cursor* cursor; + + /** + * The first element within a linked list of all currently-allocated + * layers, or NULL if no layers are currently allocated. The default layer, + * layer #0, is stored within default_surface and will not have a + * corresponding element within this list. + */ + guac_common_display_layer* layers; + + /** + * The first element within a linked list of all currently-allocated + * buffers, or NULL if no buffers are currently allocated. + */ + guac_common_display_layer* buffers; + +} guac_common_display; + +/** + * Allocates a new display, abstracting the cursor and buffer/layer allocation + * operations of the given guac_client such that client state can be easily + * synchronized to joining users. + * + * @param client The guac_client to associate with this display. + * @param width The initial width of the display, in pixels. + * @param height The initial height of the display, in pixels. + * @return The newly-allocated display. + */ +guac_common_display* guac_common_display_alloc(guac_client* client, + int width, int height); + +/** + * Frees the given display, and any associated resources, including any + * allocated buffers/layers. + * + * @param display The display to free. + */ +void guac_common_display_free(guac_common_display* display); + +/** + * Duplicates the state of the given display to the given socket. Any pending + * changes to buffers, layers, or the default layer are not flushed. + * + * @param display + * The display whose state should be sent along the given socket. + * + * @param user + * The user receiving the display state. + * + * @param socket + * The socket over which the display state should be sent. + */ +void guac_common_display_dup(guac_common_display* display, guac_user* user, + guac_socket* socket); + +/** + * Flushes pending changes to the given display. All pending operations will + * become visible to any connected users. + * + * @param display The display to flush. + */ +void guac_common_display_flush(guac_common_display* display); + +/** + * Allocates a new layer, returning a new wrapped layer and corresponding + * surface. The layer may be reused from a previous allocation, if that layer + * has since been freed. + * + * @param display The display to allocate a new layer from. + * @param width The width of the layer to allocate, in pixels. + * @param height The height of the layer to allocate, in pixels. + * @return A newly-allocated layer. + */ +guac_common_display_layer* guac_common_display_alloc_layer( + guac_common_display* display, int width, int height); + +/** + * Allocates a new buffer, returning a new wrapped buffer and corresponding + * surface. The buffer may be reused from a previous allocation, if that buffer + * has since been freed. + * + * @param display The display to allocate a new buffer from. + * @param width The width of the buffer to allocate, in pixels. + * @param height The height of the buffer to allocate, in pixels. + * @return A newly-allocated buffer. + */ +guac_common_display_layer* guac_common_display_alloc_buffer( + guac_common_display* display, int width, int height); + +/** + * Frees the given surface and associated layer, returning the layer to the + * given display for future use. + * + * @param display + * The display originally allocating the layer. + * + * @param display_layer + * The layer to free. + */ +void guac_common_display_free_layer(guac_common_display* display, + guac_common_display_layer* display_layer); + +/** + * Frees the given surface and associated buffer, returning the buffer to the + * given display for future use. + * + * @param display + * The display originally allocating the buffer. + * + * @param display_buffer + * The buffer to free. + */ +void guac_common_display_free_buffer(guac_common_display* display, + guac_common_display_layer* display_buffer); + +#endif + diff --git a/src/common/guac_dot_cursor.c b/src/common/guac_dot_cursor.c index cf672a46..f328ec9a 100644 --- a/src/common/guac_dot_cursor.c +++ b/src/common/guac_dot_cursor.c @@ -27,6 +27,7 @@ #include #include #include +#include /* Macros for prettying up the embedded image. */ #define X 0x00,0x00,0x00,0xFF @@ -52,9 +53,10 @@ unsigned char guac_common_dot_cursor[] = { }; -void guac_common_set_dot_cursor(guac_client* client) { +void guac_common_set_dot_cursor(guac_user* user) { - guac_socket* socket = client->socket; + guac_client* client = user->client; + guac_socket* socket = user->socket; /* Draw to buffer */ guac_layer* cursor = guac_client_alloc_buffer(client); @@ -66,7 +68,7 @@ void guac_common_set_dot_cursor(guac_client* client) { guac_common_dot_cursor_height, guac_common_dot_cursor_stride); - guac_client_stream_png(client, socket, GUAC_COMP_SRC, cursor, + guac_user_stream_png(user, socket, GUAC_COMP_SRC, cursor, 0, 0, graphic); cairo_surface_destroy(graphic); diff --git a/src/common/guac_dot_cursor.h b/src/common/guac_dot_cursor.h index d3ab2b28..d02f9ef2 100644 --- a/src/common/guac_dot_cursor.h +++ b/src/common/guac_dot_cursor.h @@ -27,7 +27,7 @@ #include "config.h" #include -#include +#include /** * Width of the embedded mouse cursor graphic. @@ -57,8 +57,8 @@ extern unsigned char guac_common_dot_cursor[]; /** * Set the cursor of the remote display to the embedded cursor graphic. * - * @param client The guac_client to send the cursor to. + * @param user The guac_user to send the cursor to. */ -void guac_common_set_dot_cursor(guac_client* client); +void guac_common_set_dot_cursor(guac_user* user); #endif diff --git a/src/common/guac_ibar_cursor.c b/src/common/guac_ibar_cursor.c new file mode 100644 index 00000000..1c6f0488 --- /dev/null +++ b/src/common/guac_ibar_cursor.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +/* Macros for prettying up the embedded image. */ +#define X 0x00,0x00,0x00,0xFF +#define U 0x80,0x80,0x80,0xFF +#define O 0xFF,0xFF,0xFF,0xFF +#define _ 0x00,0x00,0x00,0x00 + +/* Dimensions */ +const int guac_common_ibar_cursor_width = 7; +const int guac_common_ibar_cursor_height = 16; + +/* Format */ +const cairo_format_t guac_common_ibar_cursor_format = CAIRO_FORMAT_ARGB32; +const int guac_common_ibar_cursor_stride = 28; + +/* Embedded I-bar graphic */ +unsigned char guac_common_ibar_cursor[] = { + + X,X,X,X,X,X,X, + X,O,O,U,O,O,X, + X,X,X,O,X,X,X, + _,_,X,O,X,_,_, + _,_,X,O,X,_,_, + _,_,X,O,X,_,_, + _,_,X,O,X,_,_, + _,_,X,O,X,_,_, + _,_,X,O,X,_,_, + _,_,X,O,X,_,_, + _,_,X,O,X,_,_, + _,_,X,O,X,_,_, + _,_,X,O,X,_,_, + X,X,X,O,X,X,X, + X,O,O,U,O,O,X, + X,X,X,X,X,X,X + +}; + +void guac_common_set_ibar_cursor(guac_user* user) { + + guac_client* client = user->client; + guac_socket* socket = user->socket; + + /* Draw to buffer */ + guac_layer* cursor = guac_client_alloc_buffer(client); + + cairo_surface_t* graphic = cairo_image_surface_create_for_data( + guac_common_ibar_cursor, + guac_common_ibar_cursor_format, + guac_common_ibar_cursor_width, + guac_common_ibar_cursor_height, + guac_common_ibar_cursor_stride); + + guac_user_stream_png(user, socket, GUAC_COMP_SRC, cursor, + 0, 0, graphic); + cairo_surface_destroy(graphic); + + /* Set cursor */ + guac_protocol_send_cursor(socket, 0, 0, cursor, + guac_common_ibar_cursor_width / 2, + guac_common_ibar_cursor_height / 2, + guac_common_ibar_cursor_width, + guac_common_ibar_cursor_height); + + /* Free buffer */ + guac_client_free_buffer(client, cursor); + + guac_client_log(client, GUAC_LOG_DEBUG, + "Client cursor image set to generic built-in I-bar."); + +} + diff --git a/src/common/guac_ibar_cursor.h b/src/common/guac_ibar_cursor.h new file mode 100644 index 00000000..beb87f1c --- /dev/null +++ b/src/common/guac_ibar_cursor.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_COMMON_IBAR_CURSOR_H +#define GUAC_COMMON_IBAR_CURSOR_H + +#include "config.h" + +#include +#include + +/** + * Width of the embedded I-bar mouse cursor graphic. + */ +extern const int guac_common_ibar_cursor_width; + +/** + * Height of the embedded I-bar mouse cursor graphic. + */ +extern const int guac_common_ibar_cursor_height; + +/** + * Number of bytes in each row of the embedded I-bar mouse cursor graphic. + */ +extern const int guac_common_ibar_cursor_stride; + +/** + * The Cairo grapic format of the I-bar mouse cursor graphic. + */ +extern const cairo_format_t guac_common_ibar_cursor_format; + +/** + * Embedded I-bar mouse cursor graphic. + */ +extern unsigned char guac_common_ibar_cursor[]; + +/** + * Sets the cursor of the remote display to the embedded I-bar cursor graphic. + * + * @param user + * The guac_user to send the cursor to. + */ +void guac_common_set_ibar_cursor(guac_user* user); + +#endif + diff --git a/src/common/guac_json.c b/src/common/guac_json.c index 4359d89c..de5a57e1 100644 --- a/src/common/guac_json.c +++ b/src/common/guac_json.c @@ -28,17 +28,17 @@ #include #include -#include #include #include #include +#include -void guac_common_json_flush(guac_client* client, guac_stream* stream, +void guac_common_json_flush(guac_user* user, guac_stream* stream, guac_common_json_state* json_state) { /* If JSON buffer is non-empty, write contents to blob and reset */ if (json_state->size > 0) { - guac_protocol_send_blob(client->socket, stream, + guac_protocol_send_blob(user->socket, stream, json_state->buffer, json_state->size); /* Reset JSON buffer size */ @@ -48,7 +48,7 @@ void guac_common_json_flush(guac_client* client, guac_stream* stream, } -int guac_common_json_write(guac_client* client, guac_stream* stream, +int guac_common_json_write(guac_user* user, guac_stream* stream, guac_common_json_state* json_state, const char* buffer, int length) { int blob_written = 0; @@ -66,7 +66,7 @@ int guac_common_json_write(guac_client* client, guac_stream* stream, /* Flush if more room is needed */ if (json_state->size + blob_length > sizeof(json_state->buffer)) { - guac_common_json_flush(client, stream, json_state); + guac_common_json_flush(user, stream, json_state); blob_written = 1; } @@ -86,14 +86,14 @@ int guac_common_json_write(guac_client* client, guac_stream* stream, } -int guac_common_json_write_string(guac_client* client, +int guac_common_json_write_string(guac_user* user, guac_stream* stream, guac_common_json_state* json_state, const char* str) { int blob_written = 0; /* Write starting quote */ - blob_written |= guac_common_json_write(client, stream, + blob_written |= guac_common_json_write(user, stream, json_state, "\"", 1); /* Write given string, escaping as necessary */ @@ -105,11 +105,11 @@ int guac_common_json_write_string(guac_client* client, /* Write any string content up to current character */ if (current != str) - blob_written |= guac_common_json_write(client, stream, + blob_written |= guac_common_json_write(user, stream, json_state, str, current - str); /* Escape the quote that was just read */ - blob_written |= guac_common_json_write(client, stream, + blob_written |= guac_common_json_write(user, stream, json_state, "\\", 1); /* Reset string */ @@ -121,18 +121,18 @@ int guac_common_json_write_string(guac_client* client, /* Write any remaining string content */ if (current != str) - blob_written |= guac_common_json_write(client, stream, + blob_written |= guac_common_json_write(user, stream, json_state, str, current - str); /* Write ending quote */ - blob_written |= guac_common_json_write(client, stream, + blob_written |= guac_common_json_write(user, stream, json_state, "\"", 1); return blob_written; } -int guac_common_json_write_property(guac_client* client, guac_stream* stream, +int guac_common_json_write_property(guac_user* user, guac_stream* stream, guac_common_json_state* json_state, const char* name, const char* value) { @@ -140,19 +140,19 @@ int guac_common_json_write_property(guac_client* client, guac_stream* stream, /* Write leading comma if not first property */ if (json_state->properties_written != 0) - blob_written |= guac_common_json_write(client, stream, + blob_written |= guac_common_json_write(user, stream, json_state, ",", 1); /* Write property name */ - blob_written |= guac_common_json_write_string(client, stream, + blob_written |= guac_common_json_write_string(user, stream, json_state, name); /* Separate name from value with colon */ - blob_written |= guac_common_json_write(client, stream, + blob_written |= guac_common_json_write(user, stream, json_state, ":", 1); /* Write property value */ - blob_written |= guac_common_json_write_string(client, stream, + blob_written |= guac_common_json_write_string(user, stream, json_state, value); json_state->properties_written++; @@ -161,7 +161,7 @@ int guac_common_json_write_property(guac_client* client, guac_stream* stream, } -void guac_common_json_begin_object(guac_client* client, guac_stream* stream, +void guac_common_json_begin_object(guac_user* user, guac_stream* stream, guac_common_json_state* json_state) { /* Init JSON state */ @@ -169,15 +169,15 @@ void guac_common_json_begin_object(guac_client* client, guac_stream* stream, json_state->properties_written = 0; /* Write leading brace - no blob can possibly be written by this */ - assert(!guac_common_json_write(client, stream, json_state, "{", 1)); + assert(!guac_common_json_write(user, stream, json_state, "{", 1)); } -int guac_common_json_end_object(guac_client* client, guac_stream* stream, +int guac_common_json_end_object(guac_user* user, guac_stream* stream, guac_common_json_state* json_state) { /* Write final brace of JSON object */ - return guac_common_json_write(client, stream, json_state, "}", 1); + return guac_common_json_write(user, stream, json_state, "}", 1); } diff --git a/src/common/guac_json.h b/src/common/guac_json.h index 6e0d82d8..a7845572 100644 --- a/src/common/guac_json.h +++ b/src/common/guac_json.h @@ -25,8 +25,8 @@ #include "config.h" -#include #include +#include /** * The current streaming state of an arbitrary JSON object, consisting of @@ -55,13 +55,13 @@ typedef struct guac_common_json_state { } guac_common_json_state; /** - * Given a stream, the client to which it belongs, and the current stream state + * Given a stream, the user to which it belongs, and the current stream state * of a JSON object, flushes the contents of the JSON buffer to a blob * instruction. Note that this will flush the JSON buffer only, and will not - * necessarily flush the underlying guac_socket of the client. + * necessarily flush the underlying guac_socket of the user. * - * @param client - * The client to which the data will be flushed. + * @param user + * The user to which the data will be flushed. * * @param stream * The stream through which the flushed data should be sent as a blob. @@ -69,16 +69,16 @@ typedef struct guac_common_json_state { * @param json_state * The state object whose buffer should be flushed. */ -void guac_common_json_flush(guac_client* client, guac_stream* stream, +void guac_common_json_flush(guac_user* user, guac_stream* stream, guac_common_json_state* json_state); /** - * Given a stream, the client to which it belongs, and the current stream state + * Given a stream, the user to which it belongs, and the current stream state * of a JSON object, writes the contents of the given buffer to the JSON buffer * of the stream state, flushing as necessary. * - * @param client - * The client to which the data will be flushed as necessary. + * @param user + * The user to which the data will be flushed as necessary. * * @param stream * The stream through which the flushed data should be sent as a blob, if @@ -97,17 +97,17 @@ void guac_common_json_flush(guac_client* client, guac_stream* stream, * @return * Non-zero if at least one blob was written, zero otherwise. */ -int guac_common_json_write(guac_client* client, guac_stream* stream, +int guac_common_json_write(guac_user* user, guac_stream* stream, guac_common_json_state* json_state, const char* buffer, int length); /** - * Given a stream, the client to which it belongs, and the current stream state + * Given a stream, the user to which it belongs, and the current stream state * of a JSON object state, writes the given string as a proper JSON string, * including starting and ending quotes. The contents of the string will be * escaped as necessary. * - * @param client - * The client to which the data will be flushed as necessary. + * @param user + * The user to which the data will be flushed as necessary. * * @param stream * The stream through which the flushed data should be sent as a blob, if @@ -123,17 +123,17 @@ int guac_common_json_write(guac_client* client, guac_stream* stream, * @return * Non-zero if at least one blob was written, zero otherwise. */ -int guac_common_json_write_string(guac_client* client, +int guac_common_json_write_string(guac_user* user, guac_stream* stream, guac_common_json_state* json_state, const char* str); /** - * Given a stream, the client to which it belongs, and the current stream state + * Given a stream, the user to which it belongs, and the current stream state * of a JSON object, writes the given JSON property name/value pair. The * name and value will be written as proper JSON strings separated by a colon. * - * @param client - * The client to which the data will be flushed as necessary. + * @param user + * The user to which the data will be flushed as necessary. * * @param stream * The stream through which the flushed data should be sent as a blob, if @@ -152,18 +152,18 @@ int guac_common_json_write_string(guac_client* client, * @return * Non-zero if at least one blob was written, zero otherwise. */ -int guac_common_json_write_property(guac_client* client, guac_stream* stream, +int guac_common_json_write_property(guac_user* user, guac_stream* stream, guac_common_json_state* json_state, const char* name, const char* value); /** - * Given a stream, the client to which it belongs, and the current stream state + * Given a stream, the user to which it belongs, and the current stream state * of a JSON object, initializes the state for writing a new JSON object. Note - * that although the client and stream must be provided, no instruction or + * that although the user and stream must be provided, no instruction or * blobs will be written due to any call to this function. * - * @param client - * The client associated with the given stream. + * @param user + * The user associated with the given stream. * * @param stream * The stream associated with the JSON object being written. @@ -171,17 +171,17 @@ int guac_common_json_write_property(guac_client* client, guac_stream* stream, * @param json_state * The state object to initialize. */ -void guac_common_json_begin_object(guac_client* client, guac_stream* stream, +void guac_common_json_begin_object(guac_user* user, guac_stream* stream, guac_common_json_state* json_state); /** - * Given a stream, the client to which it belongs, and the current stream state + * Given a stream, the user to which it belongs, and the current stream state * of a JSON object, completes writing that JSON object by writing the final * terminating brace. This function must only be called following a * corresponding call to guac_common_json_begin_object(). * - * @param client - * The client associated with the given stream. + * @param user + * The user associated with the given stream. * * @param stream * The stream associated with the JSON object being written. @@ -192,7 +192,7 @@ void guac_common_json_begin_object(guac_client* client, guac_stream* stream, * @return * Non-zero if at least one blob was written, zero otherwise. */ -int guac_common_json_end_object(guac_client* client, guac_stream* stream, +int guac_common_json_end_object(guac_user* user, guac_stream* stream, guac_common_json_state* json_state); #endif diff --git a/src/common/guac_pointer_cursor.c b/src/common/guac_pointer_cursor.c index 65fd43f2..b7a0c945 100644 --- a/src/common/guac_pointer_cursor.c +++ b/src/common/guac_pointer_cursor.c @@ -27,6 +27,7 @@ #include #include #include +#include /* Macros for prettying up the embedded image. */ #define X 0x00,0x00,0x00,0xFF @@ -63,9 +64,10 @@ unsigned char guac_common_pointer_cursor[] = { }; -void guac_common_set_pointer_cursor(guac_client* client) { +void guac_common_set_pointer_cursor(guac_user* user) { - guac_socket* socket = client->socket; + guac_client* client = user->client; + guac_socket* socket = user->socket; /* Draw to buffer */ guac_layer* cursor = guac_client_alloc_buffer(client); @@ -77,7 +79,7 @@ void guac_common_set_pointer_cursor(guac_client* client) { guac_common_pointer_cursor_height, guac_common_pointer_cursor_stride); - guac_client_stream_png(client, socket, GUAC_COMP_SRC, cursor, + guac_user_stream_png(user, socket, GUAC_COMP_SRC, cursor, 0, 0, graphic); cairo_surface_destroy(graphic); diff --git a/src/common/guac_pointer_cursor.h b/src/common/guac_pointer_cursor.h index 5c3dcd0d..03764044 100644 --- a/src/common/guac_pointer_cursor.h +++ b/src/common/guac_pointer_cursor.h @@ -27,7 +27,7 @@ #include "config.h" #include -#include +#include /** * Width of the embedded mouse cursor graphic. @@ -57,8 +57,8 @@ extern unsigned char guac_common_pointer_cursor[]; /** * Set the cursor of the remote display to the embedded cursor graphic. * - * @param client The guac_client to send the cursor to. + * @param user The guac_user to send the cursor to. */ -void guac_common_set_pointer_cursor(guac_client* client); +void guac_common_set_pointer_cursor(guac_user* user); #endif diff --git a/src/common/guac_surface.c b/src/common/guac_surface.c index 40965d9e..6e78ffe3 100644 --- a/src/common/guac_surface.c +++ b/src/common/guac_surface.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -269,7 +270,7 @@ static void __guac_common_mark_dirty(guac_common_surface* surface, const guac_co * The surface on which the framerate will be calculated. * * @param rect - * The rect containing the area for which the average framerate will be + * The rect containing the area for which the average framerate will be * calculated. * * @return @@ -1303,15 +1304,18 @@ static void __guac_common_surface_flush_to_png(guac_common_surface* surface) { const guac_layer* layer = surface->layer; /* Get Cairo surface for specified rect */ - unsigned char* buffer = surface->buffer + surface->dirty_rect.y * surface->stride + surface->dirty_rect.x * 4; - cairo_surface_t* rect = cairo_image_surface_create_for_data(buffer, CAIRO_FORMAT_RGB24, - surface->dirty_rect.width, - surface->dirty_rect.height, - surface->stride); + unsigned char* buffer = surface->buffer + + surface->dirty_rect.y * surface->stride + + surface->dirty_rect.x * 4; + + cairo_surface_t* rect = cairo_image_surface_create_for_data(buffer, + CAIRO_FORMAT_RGB24, surface->dirty_rect.width, + surface->dirty_rect.height, surface->stride); /* Send PNG for rect */ guac_client_stream_png(surface->client, socket, GUAC_COMP_OVER, layer, surface->dirty_rect.x, surface->dirty_rect.y, rect); + cairo_surface_destroy(rect); surface->realized = 1; @@ -1347,16 +1351,19 @@ static void __guac_common_surface_flush_to_jpeg(guac_common_surface* surface) { &surface->dirty_rect, &max); /* Get Cairo surface for specified rect */ - unsigned char* buffer = surface->buffer + surface->dirty_rect.y * surface->stride + surface->dirty_rect.x * 4; - cairo_surface_t* rect = cairo_image_surface_create_for_data(buffer, CAIRO_FORMAT_RGB24, - surface->dirty_rect.width, - surface->dirty_rect.height, - surface->stride); + unsigned char* buffer = surface->buffer + + surface->dirty_rect.y * surface->stride + + surface->dirty_rect.x * 4; + + cairo_surface_t* rect = cairo_image_surface_create_for_data(buffer, + CAIRO_FORMAT_RGB24, surface->dirty_rect.width, + surface->dirty_rect.height, surface->stride); /* Send JPEG for rect */ guac_client_stream_jpeg(surface->client, socket, GUAC_COMP_OVER, layer, surface->dirty_rect.x, surface->dirty_rect.y, rect, GUAC_SURFACE_JPEG_IMAGE_QUALITY); + cairo_surface_destroy(rect); surface->realized = 1; @@ -1393,18 +1400,18 @@ static void __guac_common_surface_flush_to_webp(guac_common_surface* surface) { /* Get Cairo surface for specified rect */ unsigned char* buffer = surface->buffer - + surface->dirty_rect.y * surface->stride - + surface->dirty_rect.x * 4; + + surface->dirty_rect.y * surface->stride + + surface->dirty_rect.x * 4; cairo_surface_t* rect = cairo_image_surface_create_for_data(buffer, - CAIRO_FORMAT_RGB24, - surface->dirty_rect.width, surface->dirty_rect.height, - surface->stride); + CAIRO_FORMAT_RGB24, surface->dirty_rect.width, + surface->dirty_rect.height, surface->stride); /* 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_SURFACE_WEBP_IMAGE_QUALITY, 0); + cairo_surface_destroy(rect); surface->realized = 1; @@ -1524,3 +1531,25 @@ void guac_common_surface_flush(guac_common_surface* surface) { } +void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, + guac_socket* socket) { + + /* Do nothing if not realized */ + if (!surface->realized) + return; + + /* Sync size to new socket */ + guac_protocol_send_size(socket, surface->layer, surface->width, surface->height); + + /* Get entire surface */ + cairo_surface_t* rect = cairo_image_surface_create_for_data( + surface->buffer, CAIRO_FORMAT_RGB24, + surface->width, surface->height, surface->stride); + + /* Send PNG for rect */ + guac_user_stream_png(user, socket, GUAC_COMP_OVER, surface->layer, + 0, 0, rect); + cairo_surface_destroy(rect); + +} + diff --git a/src/common/guac_surface.h b/src/common/guac_surface.h index 93d7c8bf..a710bd12 100644 --- a/src/common/guac_surface.h +++ b/src/common/guac_surface.h @@ -337,5 +337,21 @@ void guac_common_surface_flush(guac_common_surface* surface); */ void guac_common_surface_flush_deferred(guac_common_surface* surface); +/** + * Duplicates the contents of the current surface to the given socket. Pending + * changes are not flushed. + * + * @param surface + * The surface to duplicate. + * + * @param user + * The user receiving the surface. + * + * @param socket + * The socket over which the surface contents should be sent. + */ +void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, + guac_socket* socket); + #endif diff --git a/tests/Makefile.am b/tests/Makefile.am index 4e0396c4..b9a2bf4d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -28,6 +28,7 @@ check_PROGRAMS = test_libguac noinst_HEADERS = \ client/client_suite.h \ + common/common_suite.h \ protocol/suite.h \ util/util_suite.h @@ -36,6 +37,10 @@ test_libguac_SOURCES = \ client/client_suite.c \ client/buffer_pool.c \ client/layer_pool.c \ + common/common_suite.c \ + common/guac_iconv.c \ + common/guac_string.c \ + common/guac_rect.c \ protocol/suite.c \ protocol/base64_decode.c \ protocol/instruction_parse.c \ @@ -48,9 +53,11 @@ test_libguac_SOURCES = \ test_libguac_CFLAGS = \ -Werror -Wall -pedantic \ + @COMMON_INCLUDE@ \ @LIBGUAC_INCLUDE@ test_libguac_LDADD = \ + @COMMON_LTLIB@ \ @CUNIT_LIBS@ \ @LIBGUAC_LTLIB@