diff --git a/src/common/Makefile.am b/src/common/Makefile.am index c2be3f59d..6f17b6538 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -31,20 +31,34 @@ SUBDIRS = . tests noinst_HEADERS = \ common/io.h \ + common/blank_cursor.h \ common/clipboard.h \ + common/cursor.h \ common/defaults.h \ + common/dot_cursor.h \ + common/ibar_cursor.h \ common/iconv.h \ common/json.h \ common/list.h \ - common/string.h + common/pointer_cursor.h \ + common/rect.h \ + common/string.h \ + common/surface.h libguac_common_la_SOURCES = \ io.c \ + blank_cursor.c \ clipboard.c \ + cursor.c \ + dot_cursor.c \ + ibar_cursor.c \ iconv.c \ json.c \ list.c \ - string.c + pointer_cursor.c \ + rect.c \ + string.c \ + surface.c libguac_common_la_CFLAGS = \ -Werror -Wall -pedantic \ diff --git a/src/common/blank_cursor.c b/src/common/blank_cursor.c new file mode 100644 index 000000000..c65db0cbf --- /dev/null +++ b/src/common/blank_cursor.c @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#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/common/blank_cursor.h b/src/common/common/blank_cursor.h new file mode 100644 index 000000000..86a4a9ff2 --- /dev/null +++ b/src/common/common/blank_cursor.h @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#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/common/cursor.h b/src/common/common/cursor.h new file mode 100644 index 000000000..96c1c6f23 --- /dev/null +++ b/src/common/common/cursor.h @@ -0,0 +1,307 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_COMMON_CURSOR_H +#define GUAC_COMMON_CURSOR_H + +#include "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 buffer containing the current cursor image. + */ + guac_layer* buffer; + + /** + * 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. + */ + size_t 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; + + /** + * An integer value representing the current state of each button, where + * the Nth bit within the integer is set to 1 if and only if the Nth mouse + * button is currently pressed. The lowest-order bit is the left mouse + * button, followed by the middle button, right button, and finally the up + * and down buttons of the scroll wheel. + * + * @see GUAC_CLIENT_MOUSE_LEFT + * @see GUAC_CLIENT_MOUSE_MIDDLE + * @see GUAC_CLIENT_MOUSE_RIGHT + * @see GUAC_CLIENT_MOUSE_SCROLL_UP + * @see GUAC_CLIENT_MOUSE_SCROLL_DOWN + */ + int button_mask; + + /** + * The server timestamp representing the point in time when the mouse + * location was last updated. + */ + guac_timestamp timestamp; + + /** + * Lock which restricts simultaneous access to the cursor, guaranteeing + * ordered modifications to the cursor and that incompatible operations + * do not occur simultaneously. This lock is for internal use within the + * cursor only. + */ + pthread_mutex_t _lock; + +} 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. + * + * @return + * The newly-allocated 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 client + * 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_client* client, guac_socket* socket); + +/** + * Updates the current position and button state of 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 updated. + * + * @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. + * + * @param button_mask + * An integer value representing the current state of each button, where + * the Nth bit within the integer is set to 1 if and only if the Nth mouse + * button is currently pressed. The lowest-order bit is the left mouse + * button, followed by the middle button, right button, and finally the up + * and down buttons of the scroll wheel. + * + * @see GUAC_CLIENT_MOUSE_LEFT + * @see GUAC_CLIENT_MOUSE_MIDDLE + * @see GUAC_CLIENT_MOUSE_RIGHT + * @see GUAC_CLIENT_MOUSE_SCROLL_UP + * @see GUAC_CLIENT_MOUSE_SCROLL_DOWN + */ +void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user, + int x, int y, int button_mask); + +/** + * 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 mouse cursor state may not update + * correctly in response to mouse events. + * + * @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/common/dot_cursor.h b/src/common/common/dot_cursor.h new file mode 100644 index 000000000..c5b73880e --- /dev/null +++ b/src/common/common/dot_cursor.h @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef _GUAC_COMMON_DOT_CURSOR_H +#define _GUAC_COMMON_DOT_CURSOR_H + +#include "config.h" + +#include +#include + +/** + * Width of the embedded mouse cursor graphic. + */ +extern const int guac_common_dot_cursor_width; + +/** + * Height of the embedded mouse cursor graphic. + */ +extern const int guac_common_dot_cursor_height; + +/** + * Number of bytes in each row of the embedded mouse cursor graphic. + */ +extern const int guac_common_dot_cursor_stride; + +/** + * The Cairo graphic format of the mouse cursor graphic. + */ +extern const cairo_format_t guac_common_dot_cursor_format; + +/** + * Embedded mouse cursor graphic. + */ +extern unsigned char guac_common_dot_cursor[]; + +/** + * Set the cursor of the remote display to the embedded cursor graphic. + * + * @param user The guac_user to send the cursor to. + */ +void guac_common_set_dot_cursor(guac_user* user); + +#endif diff --git a/src/common/common/ibar_cursor.h b/src/common/common/ibar_cursor.h new file mode 100644 index 000000000..ae11fff7c --- /dev/null +++ b/src/common/common/ibar_cursor.h @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#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/common/pointer_cursor.h b/src/common/common/pointer_cursor.h new file mode 100644 index 000000000..74559ba33 --- /dev/null +++ b/src/common/common/pointer_cursor.h @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef _GUAC_COMMON_POINTER_CURSOR_H +#define _GUAC_COMMON_POINTER_CURSOR_H + +#include "config.h" + +#include +#include + +/** + * Width of the embedded mouse cursor graphic. + */ +extern const int guac_common_pointer_cursor_width; + +/** + * Height of the embedded mouse cursor graphic. + */ +extern const int guac_common_pointer_cursor_height; + +/** + * Number of bytes in each row of the embedded mouse cursor graphic. + */ +extern const int guac_common_pointer_cursor_stride; + +/** + * The Cairo grapic format of the mouse cursor graphic. + */ +extern const cairo_format_t guac_common_pointer_cursor_format; + +/** + * Embedded mouse cursor graphic. + */ +extern unsigned char guac_common_pointer_cursor[]; + +/** + * Set the cursor of the remote display to the embedded cursor graphic. + * + * @param user The guac_user to send the cursor to. + */ +void guac_common_set_pointer_cursor(guac_user* user); + +#endif diff --git a/src/common/common/rect.h b/src/common/common/rect.h new file mode 100644 index 000000000..6b3104865 --- /dev/null +++ b/src/common/common/rect.h @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef __GUAC_COMMON_RECT_H +#define __GUAC_COMMON_RECT_H + +#include "config.h" + +/** + * Simple representation of a rectangle, having a defined corner and dimensions. + */ +typedef struct guac_common_rect { + + /** + * The X coordinate of the upper-left corner of this rectangle. + */ + int x; + + /** + * The Y coordinate of the upper-left corner of this rectangle. + */ + int y; + + /** + * The width of this rectangle. + */ + int width; + + /** + * The height of this rectangle. + */ + int height; + +} guac_common_rect; + +/** + * Initialize the given rect with the given coordinates and dimensions. + * + * @param rect The rect to initialize. + * @param x The X coordinate of the upper-left corner of the rect. + * @param y The Y coordinate of the upper-left corner of the rect. + * @param width The width of the rect. + * @param height The height of the rect. + */ +void guac_common_rect_init(guac_common_rect* rect, int x, int y, int width, int height); + +/** + * Expand the rectangle to fit an NxN grid. + * + * The rectangle will be shifted to the left and up, expanded and adjusted to + * fit within the max bounding rect. + * + * @param cell_size + * The (NxN) grid cell size. + * + * @param rect + * The rectangle to adjust. + * + * @param max_rect + * The bounding area in which the given rect can exist. + * + * @return + * Zero on success, non-zero on error. + */ +int guac_common_rect_expand_to_grid(int cell_size, guac_common_rect* rect, + const guac_common_rect* max_rect); + +/** + * Extend the given rect such that it contains at least the specified minimum + * rect. + * + * @param rect The rect to extend. + * @param min The minimum area which must be contained within the given rect. + */ +void guac_common_rect_extend(guac_common_rect* rect, const guac_common_rect* min); + +/** + * Collapse the given rect such that it exists only within the given maximum + * rect. + * + * @param rect The rect to extend. + * @param max The maximum area in which the given rect can exist. + */ +void guac_common_rect_constrain(guac_common_rect* rect, const guac_common_rect* max); + +/** + * Check whether a rectangle intersects another. + * + * @param rect + * Rectangle to check for intersection. + * + * @param other + * The other rectangle. + * + * @return + * Zero if no intersection, 1 if partial intersection, + * 2 if first rect is completely inside the other. + */ +int guac_common_rect_intersects(const guac_common_rect* rect, + const guac_common_rect* other); + +/** + * Clip and split a rectangle into rectangles which are not covered by the + * hole rectangle. + * + * This function will clip and split single edges when executed and must be + * invoked until it returns zero. The edges are handled counter-clockwise + * starting at the top edge. + * + * @param rect + * The rectangle to be split. This rectangle will be clipped by the + * split_rect. + * + * @param hole + * The rectangle which represents the hole. + * + * @param split_rect + * Resulting split rectangle. + * + * @return + * Zero when no splits were done, non-zero when the rectangle was split. + */ +int guac_common_rect_clip_and_split(guac_common_rect* rect, + const guac_common_rect* hole, guac_common_rect* split_rect); + +#endif + diff --git a/src/common/common/surface.h b/src/common/common/surface.h new file mode 100644 index 000000000..21859a3e1 --- /dev/null +++ b/src/common/common/surface.h @@ -0,0 +1,539 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef __GUAC_COMMON_SURFACE_H +#define __GUAC_COMMON_SURFACE_H + +#include "config.h" +#include "rect.h" + +#include +#include +#include +#include +#include + +#include + +/** + * The maximum number of updates to allow within the bitmap queue. + */ +#define GUAC_COMMON_SURFACE_QUEUE_SIZE 256 + +/** + * Heat map cell size in pixels. Each side of each heat map cell will consist + * of this many pixels. + */ +#define GUAC_COMMON_SURFACE_HEAT_CELL_SIZE 64 + +/** + * The width or height of the heat map (in cells) given the width or height of + * the image (in pixels). + */ +#define GUAC_COMMON_SURFACE_HEAT_DIMENSION(x) ( \ + (x + GUAC_COMMON_SURFACE_HEAT_CELL_SIZE - 1) \ + / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE \ +) + +/** + * The number of entries to collect within each heat map cell. Collected + * history entries are used to determine the framerate of the region associated + * with that cell. + */ +#define GUAC_COMMON_SURFACE_HEAT_CELL_HISTORY_SIZE 5 + +/** + * Representation of a cell in the refresh heat map. This cell is used to keep + * track of how often an area on a surface is refreshed. + */ +typedef struct guac_common_surface_heat_cell { + + /** + * Timestamps of each of the last N updates covering the location + * associated with this heat map cell. This is used to calculate the + * framerate. This array is structured as a ring buffer containing history + * entries in chronologically-ascending order, starting at the entry + * pointed to by oldest_entry and proceeding through all other entries, + * wrapping around if the end of the array is reached. + */ + guac_timestamp history[GUAC_COMMON_SURFACE_HEAT_CELL_HISTORY_SIZE]; + + /** + * Index of the oldest entry within the history. + */ + int oldest_entry; + +} guac_common_surface_heat_cell; + +/** + * Representation of a bitmap update, having a rectangle of image data (stored + * elsewhere) and a flushed/not-flushed state. + */ +typedef struct guac_common_surface_bitmap_rect { + + /** + * Whether this rectangle has been flushed. + */ + int flushed; + + /** + * The rectangle containing the bitmap update. + */ + guac_common_rect rect; + +} guac_common_surface_bitmap_rect; + +/** + * Surface which backs a Guacamole buffer or layer, automatically + * combining updates when possible. + */ +typedef struct guac_common_surface { + + /** + * The layer this surface will draw to. + */ + const guac_layer* layer; + + /** + * The client associated with this surface. + */ + guac_client* client; + + /** + * The socket to send instructions on when flushing. + */ + guac_socket* socket; + + /** + * The number of simultaneous touches that this surface can accept, where 0 + * indicates that the surface does not support touch events at all. + */ + int touches; + + /** + * Non-zero if all graphical updates for this surface should use lossless + * compression, 0 otherwise. By default, newly-created surfaces will use + * lossy compression when heuristics determine it is appropriate. + */ + int lossless; + + /** + * The X coordinate of the upper-left corner of this layer, in pixels, + * relative to its parent layer. This is only applicable to visible + * (non-buffer) layers which are not the default layer. + */ + int x; + + /** + * The Y coordinate of the upper-left corner of this layer, in pixels, + * relative to its parent layer. This is only applicable to visible + * (non-buffer) layers which are not the default layer. + */ + int y; + + /** + * The Z-order of this layer, relative to sibling layers. This is only + * applicable to visible (non-buffer) layers which are not the default + * layer. + */ + int z; + + /** + * The level of opacity applied to this layer. Fully opaque is 255, while + * fully transparent is 0. This is only applicable to visible (non-buffer) + * layers which are not the default layer. + */ + int opacity; + + /** + * The layer which contains this layer. This is only applicable to visible + * (non-buffer) layers which are not the default layer. + */ + const guac_layer* parent; + + /** + * The width of this layer, in pixels. + */ + int width; + + /** + * The height of this layer, in pixels. + */ + int height; + + /** + * The size of each image row, in bytes. + */ + int stride; + + /** + * The underlying buffer of the Cairo surface. + */ + unsigned char* buffer; + + /** + * Non-zero if the location or parent layer of this surface has been + * changed and needs to be flushed, 0 otherwise. + */ + int location_dirty; + + /** + * Non-zero if the opacity of this surface has been changed and needs to be + * flushed, 0 otherwise. + */ + int opacity_dirty; + + /** + * Non-zero if this surface is dirty and needs to be flushed, 0 otherwise. + */ + int dirty; + + /** + * The dirty rectangle. + */ + guac_common_rect dirty_rect; + + /** + * Whether the surface actually exists on the client. + */ + int realized; + + /** + * Whether drawing operations are currently clipped by the clipping + * rectangle. + */ + int clipped; + + /** + * The clipping rectangle. + */ + guac_common_rect clip_rect; + + /** + * The number of updates in the bitmap queue. + */ + int bitmap_queue_length; + + /** + * All queued bitmap updates. + */ + guac_common_surface_bitmap_rect bitmap_queue[GUAC_COMMON_SURFACE_QUEUE_SIZE]; + + /** + * A heat map keeping track of the refresh frequency of + * the areas of the screen. + */ + guac_common_surface_heat_cell* heat_map; + + /** + * Mutex which is locked internally when access to the surface must be + * synchronized. All public functions of guac_common_surface should be + * considered threadsafe. + */ + pthread_mutex_t _lock; + +} guac_common_surface; + +/** + * Allocates a new guac_common_surface, assigning it to the given layer. + * + * @param client + * The client associated with the given layer. + * + * @param socket + * The socket to send instructions on when flushing. + * + * @param layer + * The layer to associate with the new surface. + * + * @param w + * The width of the surface. + * + * @param h + * The height of the surface. + * + * @return + * A newly-allocated guac_common_surface. + */ +guac_common_surface* guac_common_surface_alloc(guac_client* client, + guac_socket* socket, const guac_layer* layer, int w, int h); + +/** + * Frees the given guac_common_surface. Beware that this will NOT free any + * associated layers, which must be freed manually. + * + * @param surface The surface to free. + */ +void guac_common_surface_free(guac_common_surface* surface); + + /** + * Resizes the given surface to the given size. + * + * @param surface The surface to resize. + * @param w The width of the surface. + * @param h The height of the surface. + */ +void guac_common_surface_resize(guac_common_surface* surface, int w, int h); + +/** + * Draws the given data to the given guac_common_surface. If the source surface + * is ARGB, the draw operation will be performed using the Porter-Duff "over" + * composite operator. If the source surface is RGB (no alpha channel), no + * compositing is performed and destination pixels are ignored. + * + * @param surface + * The surface to draw to. + * + * @param x + * The X coordinate of the draw location. + * + * @param y + * The Y coordinate of the draw location. + * + * @param src + * The Cairo surface to retrieve data from. + */ +void guac_common_surface_draw(guac_common_surface* surface, int x, int y, + cairo_surface_t* src); + +/** + * Paints to the given guac_common_surface using the given data as a stencil, + * filling opaque regions with the specified color, and leaving transparent + * regions untouched. + * + * @param surface The surface to draw to. + * @param x The X coordinate of the draw location. + * @param y The Y coordinate of the draw location. + * @param src The Cairo surface to retrieve data from. + * @param red The red component of the fill color. + * @param green The green component of the fill color. + * @param blue The blue component of the fill color. + */ +void guac_common_surface_paint(guac_common_surface* surface, int x, int y, cairo_surface_t* src, + int red, int green, int blue); + +/** + * Copies a rectangle of data between two surfaces. + * + * @param src The source surface. + * @param sx The X coordinate of the upper-left corner of the source rect. + * @param sy The Y coordinate of the upper-left corner of the source rect. + * @param w The width of the source rect. + * @param h The height of the source rect. + * @param dst The destination surface. + * @param dx The X coordinate of the upper-left corner of the destination rect. + * @param dy The Y coordinate of the upper-left corner of the destination rect. + */ +void guac_common_surface_copy(guac_common_surface* src, int sx, int sy, int w, int h, + guac_common_surface* dst, int dx, int dy); + +/** + * Transfers a rectangle of data between two surfaces. + * + * @param src The source surface. + * @param sx The X coordinate of the upper-left corner of the source rect. + * @param sy The Y coordinate of the upper-left corner of the source rect. + * @param w The width of the source rect. + * @param h The height of the source rect. + * @param op The transfer function. + * @param dst The destination surface. + * @param dx The X coordinate of the upper-left corner of the destination rect. + * @param dy The Y coordinate of the upper-left corner of the destination rect. + */ +void guac_common_surface_transfer(guac_common_surface* src, int sx, int sy, int w, int h, + guac_transfer_function op, guac_common_surface* dst, int dx, int dy); + +/** + * Assigns the given value to all pixels within a rectangle of the given + * surface. The color of all pixels within the rectangle, including the alpha + * component, is entirely replaced. + * + * @param surface + * The surface to draw upon. + * + * @param x + * The X coordinate of the upper-left corner of the rectangle. + * + * @param y + * The Y coordinate of the upper-left corner of the rectangle. + * + * @param w + * The width of the rectangle. + * + * @param h + * The height of the rectangle. + * + * @param red + * The red component of the color value to assign to all pixels within the + * rectangle. + * + * @param green + * The green component of the color value to assign to all pixels within + * the rectangle. + * + * @param blue + * The blue component of the color value to assign to all pixels within the + * rectangle. + * + * @param alpha + * The alpha component of the color value to assign to all pixels within + * the rectangle. + */ +void guac_common_surface_set(guac_common_surface* surface, int x, int y, + int w, int h, int red, int green, int blue, int alpha); + +/** + * Given the coordinates and dimensions of a rectangle, clips all future + * operations within that rectangle. + * + * @param surface The surface whose clipping rectangle should be changed. + * @param x The X coordinate of the upper-left corner of the bounding rectangle. + * @param y The Y coordinate of the upper-left corner of the bounding rectangle. + * @param w The width of the bounding rectangle. + * @param h The height of the bounding rectangle. + */ +void guac_common_surface_clip(guac_common_surface* surface, int x, int y, int w, int h); + +/** + * Resets the clipping rectangle, allowing drawing operations throughout the + * entire surface. + * + * @param surface The surface whose clipping rectangle should be reset. + */ +void guac_common_surface_reset_clip(guac_common_surface* surface); + +/** + * Changes the location of the surface relative to its parent layer. If the + * surface does not represent a non-default visible layer, then this function + * has no effect. + * + * @param surface + * The surface to move relative to its parent layer. + * + * @param x + * The new X coordinate for the upper-left corner of the layer, in pixels. + * + * @param y + * The new Y coordinate for the upper-left corner of the layer, in pixels. + */ +void guac_common_surface_move(guac_common_surface* surface, int x, int y); + +/** + * Changes the stacking order of the surface relative to other surfaces within + * the same parent layer. If the surface does not represent a non-default + * visible layer, then this function has no effect. + * + * @param surface + * The surface to reorder relative to sibling layers. + * + * @param z + * The new Z-order for this layer, relative to sibling layers. + */ +void guac_common_surface_stack(guac_common_surface* surface, int z); + +/** + * Changes the parent layer of ths given surface. By default, layers will be + * children of the default layer. If the surface does not represent a + * non-default visible layer, then this function has no effect. + * + * @param surface + * The surface whose parent layer should be changed. + * + * @param parent + * The layer which should be set as the new parent of the given surface. + */ +void guac_common_surface_set_parent(guac_common_surface* surface, + const guac_layer* parent); + +/** + * Sets the opacity of the surface. If the surface does not represent a + * non-default visible layer, then this function has no effect. + * + * @param surface + * The surface whose opacity should be changed. + * + * @param opacity + * The level of opacity applied to this surface, where fully opaque is 255, + * and fully transparent is 0. + */ +void guac_common_surface_set_opacity(guac_common_surface* surface, int opacity); + +/** + * Flushes the given surface, including any applicable properties, drawing any + * pending operations on the remote display. + * + * @param surface + * The surface to flush. + */ +void guac_common_surface_flush(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 client + * The client whose users are 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_client* client, guac_socket* socket); + +/** + * Declares that the given surface should receive touch events. By default, + * surfaces are assumed to not expect touch events. This value is advisory, and + * the client is not required to honor the declared level of touch support. + * Implementations are expected to safely handle or ignore any received touch + * events, regardless of the level of touch support declared. regardless of + * the level of touch support declared. + * + * @param surface + * The surface to modify. + * + * @param touches + * The number of simultaneous touches that this surface can accept, where 0 + * indicates that the surface does not support touch events at all. + */ +void guac_common_surface_set_multitouch(guac_common_surface* surface, + int touches); + +/** + * Sets the lossless compression policy of the given surface to the given + * value. By default, newly-created surfaces will use lossy compression for + * graphical updates when heuristics determine that doing so is appropriate. + * Specifying a non-zero value here will force all graphical updates to always + * use lossless compression, whereas specifying zero will restore the default + * policy. + * + * @param surface + * The surface to modify. + * + * @param lossless + * Non-zero if all graphical updates for this surface should use lossless + * compression, 0 otherwise. + */ +void guac_common_surface_set_lossless(guac_common_surface* surface, + int lossless); + +#endif + diff --git a/src/common/cursor.c b/src/common/cursor.c new file mode 100644 index 000000000..9bb51aedf --- /dev/null +++ b/src/common/cursor.c @@ -0,0 +1,327 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "common/blank_cursor.h" +#include "common/dot_cursor.h" +#include "common/cursor.h" +#include "common/ibar_cursor.h" +#include "common/pointer_cursor.h" +#include "common/surface.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/** + * Allocates a cursor as well as an image buffer where the cursor is rendered. + * + * @param client + * The client owning the cursor. + * + * @return + * The newly-allocated cursor or NULL if cursor cannot be allocated. + */ +guac_common_cursor* guac_common_cursor_alloc(guac_client* client) { + + guac_common_cursor* cursor = guac_mem_alloc(sizeof(guac_common_cursor)); + if (cursor == NULL) + return NULL; + + /* Associate cursor with client and allocate cursor buffer */ + cursor->client = client; + cursor->buffer = guac_client_alloc_buffer(client); + + /* Allocate initial image buffer */ + cursor->image_buffer_size = GUAC_COMMON_CURSOR_DEFAULT_SIZE; + cursor->image_buffer = guac_mem_alloc(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; + cursor->timestamp = guac_timestamp_current(); + + /* Start cursor in upper-left */ + cursor->x = 0; + cursor->y = 0; + + pthread_mutex_init(&(cursor->_lock), NULL); + + return cursor; + +} + +void guac_common_cursor_free(guac_common_cursor* cursor) { + + pthread_mutex_destroy(&(cursor->_lock)); + + guac_client* client = cursor->client; + guac_layer* buffer = cursor->buffer; + cairo_surface_t* surface = cursor->surface; + + /* Free image buffer and surface */ + guac_mem_free(cursor->image_buffer); + if (surface != NULL) + cairo_surface_destroy(surface); + + /* Destroy buffer within remotely-connected client */ + guac_protocol_send_dispose(client->socket, buffer); + + /* Return buffer to pool */ + guac_client_free_buffer(client, buffer); + + guac_mem_free(cursor); + +} + +void guac_common_cursor_dup( + guac_common_cursor* cursor, guac_client* client, guac_socket* socket) { + + pthread_mutex_lock(&(cursor->_lock)); + + /* Synchronize location */ + guac_protocol_send_mouse(socket, cursor->x, cursor->y, cursor->button_mask, + cursor->timestamp); + + /* Synchronize cursor image */ + if (cursor->surface != NULL) { + guac_protocol_send_size(socket, cursor->buffer, + cursor->width, cursor->height); + + guac_client_stream_png(client, socket, GUAC_COMP_SRC, + cursor->buffer, 0, 0, cursor->surface); + + guac_protocol_send_cursor(socket, + cursor->hotspot_x, cursor->hotspot_y, + cursor->buffer, 0, 0, cursor->width, cursor->height); + } + + pthread_mutex_unlock(&(cursor->_lock)); + + guac_socket_flush(socket); + +} + +/** + * Callback for guac_client_foreach_user() which sends the current cursor + * position and button state to any given user except the user that moved the + * cursor last. + * + * @param data + * A pointer to the guac_common_cursor whose state should be broadcast to + * all users except the user that moved the cursor last. + * + * @return + * Always NULL. + */ +static void* guac_common_cursor_broadcast_state(guac_user* user, + void* data) { + + guac_common_cursor* cursor = (guac_common_cursor*) data; + + /* Send cursor state only if the user is not moving the cursor */ + if (user != cursor->user) { + guac_protocol_send_mouse(user->socket, cursor->x, cursor->y, + cursor->button_mask, cursor->timestamp); + guac_socket_flush(user->socket); + } + + return NULL; + +} + +void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user, + int x, int y, int button_mask) { + + pthread_mutex_lock(&(cursor->_lock)); + + /* Update current user of cursor */ + cursor->user = user; + + /* Update cursor state */ + cursor->x = x; + cursor->y = y; + cursor->button_mask = button_mask; + + /* Store time at which cursor was updated */ + cursor->timestamp = guac_timestamp_current(); + + /* Notify all other users of change in cursor state */ + guac_client_foreach_user(cursor->client, + guac_common_cursor_broadcast_state, cursor); + + pthread_mutex_unlock(&(cursor->_lock)); + +} + +/** + * Ensures the cursor image buffer has enough room to fit an image with the + * given characteristics. Existing image buffer data may be destroyed. + * + * @param cursor + * The cursor whose buffer size should be checked. If this cursor lacks + * sufficient space to contain a cursor image of the specified width, + * height, and stride, the current contents of this cursor will be + * destroyed and replaced with an new buffer having sufficient space. + * + * @param width + * The required cursor width, in pixels. + * + * @param height + * The required cursor height, in pixels. + * + * @param stride + * The number of bytes in each row of image data. + */ +static void guac_common_cursor_resize(guac_common_cursor* cursor, + int width, int height, int stride) { + + size_t minimum_size = guac_mem_ckd_mul_or_die(height, stride); + + /* Grow image buffer if necessary */ + if (cursor->image_buffer_size < minimum_size) { + + /* Calculate new size */ + cursor->image_buffer_size = guac_mem_ckd_mul_or_die(minimum_size, 2); + + /* Destructively reallocate image buffer */ + guac_mem_free(cursor->image_buffer); + cursor->image_buffer = guac_mem_alloc(cursor->image_buffer_size); + + } + +} + +void guac_common_cursor_set_argb(guac_common_cursor* cursor, int hx, int hy, + unsigned const char* data, int width, int height, int stride) { + + pthread_mutex_lock(&(cursor->_lock)); + + /* 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; + + /* Broadcast new cursor image to all users */ + guac_protocol_send_size(cursor->client->socket, cursor->buffer, + width, height); + + guac_client_stream_png(cursor->client, cursor->client->socket, + GUAC_COMP_SRC, cursor->buffer, 0, 0, cursor->surface); + + /* Update cursor image */ + guac_protocol_send_cursor(cursor->client->socket, + cursor->hotspot_x, cursor->hotspot_y, + cursor->buffer, 0, 0, cursor->width, cursor->height); + + guac_socket_flush(cursor->client->socket); + + pthread_mutex_unlock(&(cursor->_lock)); + +} + +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) { + + pthread_mutex_lock(&(cursor->_lock)); + + /* Disassociate from given user */ + if (cursor->user == user) + cursor->user = NULL; + + pthread_mutex_unlock(&(cursor->_lock)); + +} + diff --git a/src/common/dot_cursor.c b/src/common/dot_cursor.c new file mode 100644 index 000000000..fde79b01a --- /dev/null +++ b/src/common/dot_cursor.c @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +/* Macros for prettying up the embedded image. */ +#define X 0x00,0x00,0x00,0xFF +#define O 0xFF,0xFF,0xFF,0xFF +#define _ 0x00,0x00,0x00,0x00 + +/* Dimensions */ +const int guac_common_dot_cursor_width = 5; +const int guac_common_dot_cursor_height = 5; + +/* Format */ +const cairo_format_t guac_common_dot_cursor_format = CAIRO_FORMAT_ARGB32; +const int guac_common_dot_cursor_stride = 20; + +/* Embedded pointer graphic */ +unsigned char guac_common_dot_cursor[] = { + + _,O,O,O,_, + O,X,X,X,O, + O,X,X,X,O, + O,X,X,X,O, + _,O,O,O,_ + +}; + +void guac_common_set_dot_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_dot_cursor, + guac_common_dot_cursor_format, + guac_common_dot_cursor_width, + guac_common_dot_cursor_height, + guac_common_dot_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, 2, 2, cursor, + 0, 0, + guac_common_dot_cursor_width, + guac_common_dot_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 dot."); + +} + diff --git a/src/common/ibar_cursor.c b/src/common/ibar_cursor.c new file mode 100644 index 000000000..6494591bc --- /dev/null +++ b/src/common/ibar_cursor.c @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#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/pointer_cursor.c b/src/common/pointer_cursor.c new file mode 100644 index 000000000..fbcb60230 --- /dev/null +++ b/src/common/pointer_cursor.c @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +/* Macros for prettying up the embedded image. */ +#define X 0x00,0x00,0x00,0xFF +#define O 0xFF,0xFF,0xFF,0xFF +#define _ 0x00,0x00,0x00,0x00 + +/* Dimensions */ +const int guac_common_pointer_cursor_width = 11; +const int guac_common_pointer_cursor_height = 16; + +/* Format */ +const cairo_format_t guac_common_pointer_cursor_format = CAIRO_FORMAT_ARGB32; +const int guac_common_pointer_cursor_stride = 44; + +/* Embedded pointer graphic */ +unsigned char guac_common_pointer_cursor[] = { + + O,_,_,_,_,_,_,_,_,_,_, + O,O,_,_,_,_,_,_,_,_,_, + O,X,O,_,_,_,_,_,_,_,_, + O,X,X,O,_,_,_,_,_,_,_, + O,X,X,X,O,_,_,_,_,_,_, + O,X,X,X,X,O,_,_,_,_,_, + O,X,X,X,X,X,O,_,_,_,_, + O,X,X,X,X,X,X,O,_,_,_, + O,X,X,X,X,X,X,X,O,_,_, + O,X,X,X,X,X,X,X,X,O,_, + O,X,X,X,X,X,O,O,O,O,O, + O,X,X,O,X,X,O,_,_,_,_, + O,X,O,_,O,X,X,O,_,_,_, + O,O,_,_,O,X,X,O,_,_,_, + O,_,_,_,_,O,X,X,O,_,_, + _,_,_,_,_,O,O,O,O,_,_ + +}; + +void guac_common_set_pointer_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_pointer_cursor, + guac_common_pointer_cursor_format, + guac_common_pointer_cursor_width, + guac_common_pointer_cursor_height, + guac_common_pointer_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_pointer_cursor_width, + guac_common_pointer_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 pointer."); + +} + diff --git a/src/common/rect.c b/src/common/rect.c new file mode 100644 index 000000000..a7ca7da52 --- /dev/null +++ b/src/common/rect.c @@ -0,0 +1,266 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "common/rect.h" + +void guac_common_rect_init(guac_common_rect* rect, int x, int y, int width, int height) { + rect->x = x; + rect->y = y; + rect->width = width; + rect->height = height; +} + +void guac_common_rect_extend(guac_common_rect* rect, const guac_common_rect* min) { + + /* Calculate extents of existing dirty rect */ + int left = rect->x; + int top = rect->y; + int right = left + rect->width; + int bottom = top + rect->height; + + /* Calculate missing extents of given new rect */ + int min_left = min->x; + int min_top = min->y; + int min_right = min_left + min->width; + int min_bottom = min_top + min->height; + + /* Update minimums */ + if (min_left < left) left = min_left; + if (min_top < top) top = min_top; + if (min_right > right) right = min_right; + if (min_bottom > bottom) bottom = min_bottom; + + /* Commit rect */ + guac_common_rect_init(rect, left, top, right - left, bottom - top); + +} + +void guac_common_rect_constrain(guac_common_rect* rect, const guac_common_rect* max) { + + /* Calculate extents of existing dirty rect */ + int left = rect->x; + int top = rect->y; + int right = left + rect->width; + int bottom = top + rect->height; + + /* Calculate missing extents of given new rect */ + int max_left = max->x; + int max_top = max->y; + int max_right = max_left + max->width; + int max_bottom = max_top + max->height; + + /* Update maximums */ + if (max_left > left) left = max_left; + if (max_top > top) top = max_top; + if (max_right < right) right = max_right; + if (max_bottom < bottom) bottom = max_bottom; + + /* Commit rect */ + guac_common_rect_init(rect, left, top, right - left, bottom - top); + +} + +int guac_common_rect_expand_to_grid(int cell_size, guac_common_rect* rect, + const guac_common_rect* max_rect) { + + /* Invalid cell_size received */ + if (cell_size <= 0) + return -1; + + /* Nothing to do */ + if (cell_size == 1) + return 0; + + /* Calculate how much the rectangle must be adjusted to fit within the + * given cell size. */ + int dw = cell_size - rect->width % cell_size; + int dh = cell_size - rect->height % cell_size; + + int dx = dw / 2; + int dy = dh / 2; + + /* Set initial extents of adjusted rectangle. */ + int top = rect->y - dy; + int left = rect->x - dx; + int bottom = top + rect->height + dh; + int right = left + rect->width + dw; + + /* The max rectangle */ + int max_left = max_rect->x; + int max_top = max_rect->y; + int max_right = max_left + max_rect->width; + int max_bottom = max_top + max_rect->height; + + /* If the adjusted rectangle has sides beyond the max rectangle, or is larger + * in any direction; shift or adjust the rectangle while trying to fit in + * the grid */ + + /* Adjust left/right */ + if (right > max_right) { + + /* shift to left */ + dw = right - max_right; + right -= dw; + left -= dw; + + /* clamp left if too far */ + if (left < max_left) { + left = max_left; + } + } + else if (left < max_left) { + + /* shift to right */ + dw = max_left - left; + left += dw; + right += dw; + + /* clamp right if too far */ + if (right > max_right) { + right = max_right; + } + } + + /* Adjust top/bottom */ + if (bottom > max_bottom) { + + /* shift up */ + dh = bottom - max_bottom; + bottom -= dh; + top -= dh; + + /* clamp top if too far */ + if (top < max_top) { + top = max_top; + } + } + else if (top < max_top) { + + /* shift down */ + dh = max_top - top; + top += dh; + bottom += dh; + + /* clamp bottom if too far */ + if (bottom > max_bottom) { + bottom = max_bottom; + } + } + + /* Commit rect */ + guac_common_rect_init(rect, left, top, right - left, bottom - top); + + return 0; + +} + +int guac_common_rect_intersects(const guac_common_rect* rect, + const guac_common_rect* other) { + + /* Empty (no intersection) */ + if (other->x + other->width < rect->x || rect->x + rect->width < other->x || + other->y + other->height < rect->y || rect->y + rect->height < other->y) { + return 0; + } + /* Complete */ + else if (other->x <= rect->x && (other->x + other->width) >= (rect->x + rect->width) && + other->y <= rect->y && (other->y + other->height) >= (rect->y + rect->height)) { + return 2; + } + /* Partial intersection */ + return 1; + +} + +int guac_common_rect_clip_and_split(guac_common_rect* rect, + const guac_common_rect* hole, guac_common_rect* split_rect) { + + /* Only continue if the rectangles intersects */ + if (!guac_common_rect_intersects(rect, hole)) + return 0; + + int top, left, bottom, right; + + /* Clip and split top */ + if (rect->y < hole->y) { + top = rect->y; + left = rect->x; + bottom = hole->y; + right = rect->x + rect->width; + guac_common_rect_init(split_rect, left, top, right - left, bottom - top); + + /* Re-initialize original rect */ + top = hole->y; + bottom = rect->y + rect->height; + guac_common_rect_init(rect, left, top, right - left, bottom - top); + + return 1; + } + + /* Clip and split left */ + else if (rect->x < hole->x) { + top = rect->y; + left = rect->x; + bottom = rect->y + rect->height; + right = hole->x; + guac_common_rect_init(split_rect, left, top, right - left, bottom - top); + + /* Re-initialize original rect */ + left = hole->x; + right = rect->x + rect->width; + guac_common_rect_init(rect, left, top, right - left, bottom - top); + + return 1; + } + + /* Clip and split bottom */ + else if (rect->y + rect->height > hole->y + hole->height) { + top = hole->y + hole->height; + left = rect->x; + bottom = rect->y + rect->height; + right = rect->x + rect->width; + guac_common_rect_init(split_rect, left, top, right - left, bottom - top); + + /* Re-initialize original rect */ + top = rect->y; + bottom = hole->y + hole->height; + guac_common_rect_init(rect, left, top, right - left, bottom - top); + + return 1; + } + + /* Clip and split right */ + else if (rect->x + rect->width > hole->x + hole->width) { + top = rect->y; + left = hole->x + hole->width; + bottom = rect->y + rect->height; + right = rect->x + rect->width; + guac_common_rect_init(split_rect, left, top, right - left, bottom - top); + + /* Re-initialize original rect */ + left = rect->x; + right = hole->x + hole->width; + guac_common_rect_init(rect, left, top, right - left, bottom - top); + + return 1; + } + + return 0; +} diff --git a/src/common/surface.c b/src/common/surface.c new file mode 100644 index 000000000..3ec5517e9 --- /dev/null +++ b/src/common/surface.c @@ -0,0 +1,2041 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "common/rect.h" +#include "common/surface.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/** + * The width of an update which should be considered negible and thus + * trivial overhead compared ot the cost of two updates. + */ +#define GUAC_SURFACE_NEGLIGIBLE_WIDTH 64 + +/** + * The height of an update which should be considered negible and thus + * trivial overhead compared ot the cost of two updates. + */ +#define GUAC_SURFACE_NEGLIGIBLE_HEIGHT 64 + +/** + * The proportional increase in cost contributed by transfer and processing of + * image data, compared to processing an equivalent amount of client-side + * data. + */ +#define GUAC_SURFACE_DATA_FACTOR 16 + +/** + * The base cost of every update. Each update should be considered to have + * this starting cost, plus any additional cost estimated from its + * content. + */ +#define GUAC_SURFACE_BASE_COST 4096 + +/** + * An increase in cost is negligible if it is less than + * 1/GUAC_SURFACE_NEGLIGIBLE_INCREASE of the old cost. + */ +#define GUAC_SURFACE_NEGLIGIBLE_INCREASE 4 + +/** + * If combining an update because it appears to be follow a fill pattern, + * the combined cost must not exceed + * GUAC_SURFACE_FILL_PATTERN_FACTOR * (total uncombined cost). + */ +#define GUAC_SURFACE_FILL_PATTERN_FACTOR 3 + +/* 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 + +/** + * The framerate which, if exceeded, indicates that JPEG is preferred. + */ +#define GUAC_COMMON_SURFACE_JPEG_FRAMERATE 3 + +/** + * Minimum JPEG bitmap size (area). If the bitmap is smaller than this threshold, + * it should be compressed as a PNG image to avoid the JPEG compression tax. + */ +#define GUAC_SURFACE_JPEG_MIN_BITMAP_SIZE 4096 + +/** + * The JPEG compression min block size. This defines the optimal rectangle block + * size factor for JPEG compression. Usually 8x8 would suffice, but use 16 to + * reduce the occurrence of ringing artifacts further. + */ +#define GUAC_SURFACE_JPEG_BLOCK_SIZE 16 + +/** + * The WebP compression min block size. This defines the optimal rectangle block + * size factor for WebP compression. WebP does utilize variable block size, but + * ensuring a block size factor reduces any noise on the image edges. + */ +#define GUAC_SURFACE_WEBP_BLOCK_SIZE 8 + +void guac_common_surface_set_multitouch(guac_common_surface* surface, + int touches) { + + pthread_mutex_lock(&surface->_lock); + + surface->touches = touches; + guac_protocol_send_set_int(surface->socket, surface->layer, + GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH, touches); + + pthread_mutex_unlock(&surface->_lock); + +} + +void guac_common_surface_set_lossless(guac_common_surface* surface, + int lossless) { + + pthread_mutex_lock(&surface->_lock); + surface->lossless = lossless; + pthread_mutex_unlock(&surface->_lock); + +} + +void guac_common_surface_move(guac_common_surface* surface, int x, int y) { + + pthread_mutex_lock(&surface->_lock); + + surface->x = x; + surface->y = y; + surface->location_dirty = 1; + + pthread_mutex_unlock(&surface->_lock); + +} + +void guac_common_surface_stack(guac_common_surface* surface, int z) { + + pthread_mutex_lock(&surface->_lock); + + surface->z = z; + surface->location_dirty = 1; + + pthread_mutex_unlock(&surface->_lock); + +} + +void guac_common_surface_set_parent(guac_common_surface* surface, + const guac_layer* parent) { + + pthread_mutex_lock(&surface->_lock); + + surface->parent = parent; + surface->location_dirty = 1; + + pthread_mutex_unlock(&surface->_lock); + +} + +void guac_common_surface_set_opacity(guac_common_surface* surface, + int opacity) { + + pthread_mutex_lock(&surface->_lock); + + surface->opacity = opacity; + surface->opacity_dirty = 1; + + pthread_mutex_unlock(&surface->_lock); + +} + +/** + * Updates the coordinates of the given rectangle to be within the bounds of + * the given surface. + * + * @param surface The surface to use for clipping. + * @param rect The rectangle to clip. + * @param sx The X coordinate of the source rectangle, if any. + * @param sy The Y coordinate of the source rectangle, if any. + */ +static void __guac_common_bound_rect(guac_common_surface* surface, + guac_common_rect* rect, int* sx, int* sy) { + + guac_common_rect bounds_rect = { + .x = 0, + .y = 0, + .width = surface->width, + .height = surface->height + }; + + int orig_x = rect->x; + int orig_y = rect->y; + + guac_common_rect_constrain(rect, &bounds_rect); + + /* Update source X/Y if given */ + if (sx != NULL) *sx += rect->x - orig_x; + if (sy != NULL) *sy += rect->y - orig_y; + +} + +/** + * Updates the coordinates of the given rectangle to be within the clipping + * rectangle of the given surface, which must always be within the bounding + * rectangle of the given surface. + * + * @param surface The surface to use for clipping. + * @param rect The rectangle to clip. + * @param sx The X coordinate of the source rectangle, if any. + * @param sy The Y coordinate of the source rectangle, if any. + */ +static void __guac_common_clip_rect(guac_common_surface* surface, + guac_common_rect* rect, int* sx, int* sy) { + + int orig_x = rect->x; + int orig_y = rect->y; + + /* Just bound within surface if no clipping rectangle applied */ + if (!surface->clipped) { + __guac_common_bound_rect(surface, rect, sx, sy); + return; + } + + guac_common_rect_constrain(rect, &surface->clip_rect); + + /* Update source X/Y if given */ + if (sx != NULL) *sx += rect->x - orig_x; + if (sy != NULL) *sy += rect->y - orig_y; + +} + +/** + * Returns whether a rectangle within the given surface contains only fully + * opaque pixels. + * + * @param surface + * The surface to check. + * + * @param rect + * The rectangle to check. + * + * @return + * Non-zero if the rectangle contains only fully opaque pixels, zero + * otherwise. + */ +static int __guac_common_surface_is_opaque(guac_common_surface* surface, + guac_common_rect* rect) { + + int x, y; + + int stride = surface ->stride; + unsigned char* buffer = + surface->buffer + (stride * rect->y) + (4 * rect->x); + + /* For each row */ + for (y = 0; y < rect->height; y++) { + + /* Search for a non-opaque pixel */ + uint32_t* current = (uint32_t*) buffer; + for (x=0; x < rect->width; x++) { + + /* Rectangle is non-opaque if a single non-opaque pixel is found */ + uint32_t color = *(current++); + if ((color & 0xFF000000) != 0xFF000000) + return 0; + + } + + /* Next row */ + buffer += stride; + + } + + /* Rectangle is opaque */ + return 1; + +} + +/** + * Returns whether the given rectangle should be combined into the existing + * dirty rectangle, to be eventually flushed as image data, or would be best + * kept independent of the current rectangle. + * + * @param surface + * The surface being updated. + * + * @param rect + * The bounding rectangle of the update being made to the surface. + * + * @param rect_only + * Non-zero if this update, by its nature, contains only metainformation + * about the update's bounding rectangle, zero if the update also contains + * image data. + * + * @return + * Non-zero if the update should be combined with any existing update, zero + * otherwise. + */ +static int __guac_common_should_combine(guac_common_surface* surface, const guac_common_rect* rect, int rect_only) { + + /* Always favor combining updates if surface is currently a purely + * server-side scratch area */ + if (!surface->realized) + return 1; + + if (surface->dirty) { + + int combined_cost, dirty_cost, update_cost; + + /* Simulate combination */ + guac_common_rect combined = surface->dirty_rect; + guac_common_rect_extend(&combined, rect); + + /* Combine if result is still small */ + if (combined.width <= GUAC_SURFACE_NEGLIGIBLE_WIDTH && combined.height <= GUAC_SURFACE_NEGLIGIBLE_HEIGHT) + return 1; + + /* Estimate costs of the existing update, new update, and both combined */ + combined_cost = GUAC_SURFACE_BASE_COST + combined.width * combined.height; + dirty_cost = GUAC_SURFACE_BASE_COST + surface->dirty_rect.width * surface->dirty_rect.height; + update_cost = GUAC_SURFACE_BASE_COST + rect->width * rect->height; + + /* Reduce cost if no image data */ + if (rect_only) + update_cost /= GUAC_SURFACE_DATA_FACTOR; + + /* Combine if cost estimate shows benefit */ + if (combined_cost <= update_cost + dirty_cost) + return 1; + + /* Combine if increase in cost is negligible */ + if (combined_cost - dirty_cost <= dirty_cost / GUAC_SURFACE_NEGLIGIBLE_INCREASE) + return 1; + + if (combined_cost - update_cost <= update_cost / GUAC_SURFACE_NEGLIGIBLE_INCREASE) + return 1; + + /* Combine if we anticipate further updates, as this update follows a common fill pattern */ + if (rect->x == surface->dirty_rect.x && rect->y == surface->dirty_rect.y + surface->dirty_rect.height) { + if (combined_cost <= (dirty_cost + update_cost) * GUAC_SURFACE_FILL_PATTERN_FACTOR) + return 1; + } + + } + + /* Otherwise, do not combine */ + return 0; + +} + +/** + * Expands the dirty rect of the given surface to contain the rect described by the given + * coordinates. + * + * @param surface The surface to mark as dirty. + * @param rect The rectangle of the update which is dirtying the surface. + */ +static void __guac_common_mark_dirty(guac_common_surface* surface, const guac_common_rect* rect) { + + /* Ignore empty rects */ + if (rect->width <= 0 || rect->height <= 0) + return; + + /* If already dirty, update existing rect */ + if (surface->dirty) + guac_common_rect_extend(&surface->dirty_rect, rect); + + /* Otherwise init dirty rect */ + else { + surface->dirty_rect = *rect; + surface->dirty = 1; + } + +} + +/** + * Calculate the current average framerate for a given area on the surface. + * + * @param surface + * The surface on which the framerate will be calculated. + * + * @param rect + * The rect containing the area for which the average framerate will be + * calculated. + * + * @return + * The average framerate of the given area, in frames per second. + */ +static unsigned int __guac_common_surface_calculate_framerate( + guac_common_surface* surface, const guac_common_rect* rect) { + + int x, y; + + /* Calculate heat map dimensions */ + size_t heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(surface->width); + + /* Calculate minimum X/Y coordinates intersecting given rect */ + int min_x = rect->x / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; + int min_y = rect->y / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; + + /* Calculate maximum X/Y coordinates intersecting given rect */ + int max_x = min_x + (rect->width - 1) / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; + int max_y = min_y + (rect->height - 1) / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; + + unsigned int sum_framerate = 0; + unsigned int count = 0; + + /* Get start of buffer at given coordinates */ + const guac_common_surface_heat_cell* heat_row = + surface->heat_map + min_y * heat_width + min_x; + + /* Iterate over all the heat map cells for the area + * and calculate the average framerate */ + for (y = min_y; y < max_y; y++) { + + /* Get current row of heat map */ + const guac_common_surface_heat_cell* heat_cell = heat_row; + + /* For each cell in subset of row */ + for (x = min_x; x < max_x; x++) { + + /* Calculate indicies for latest and oldest history entries */ + int oldest_entry = heat_cell->oldest_entry; + int latest_entry = oldest_entry - 1; + if (latest_entry < 0) + latest_entry = GUAC_COMMON_SURFACE_HEAT_CELL_HISTORY_SIZE - 1; + + /* Calculate elapsed time covering entire history for this cell */ + int elapsed_time = heat_cell->history[latest_entry] + - heat_cell->history[oldest_entry]; + + /* Calculate and add framerate */ + if (elapsed_time) + sum_framerate += GUAC_COMMON_SURFACE_HEAT_CELL_HISTORY_SIZE + * 1000 / elapsed_time; + + /* Next heat map cell */ + heat_cell++; + count++; + + } + + /* Next heat map row */ + heat_row += heat_width; + + } + + /* Calculate the average framerate over entire rect */ + if (count) + return sum_framerate / count; + + return 0; + +} + + /** + * Guesses whether a rectangle within a particular surface would be better + * compressed as PNG or using a lossy format like JPEG. Positive values + * indicate PNG is likely to be superior, while negative values indicate the + * opposite. + * + * @param surface + * The surface containing the image data to check. + * + * @param rect + * The rect to check within the given surface. + * + * @return + * Positive values if PNG compression is likely to perform better than + * lossy alternatives, or negative values if PNG is likely to perform + * worse. + */ +static int __guac_common_surface_png_optimality(guac_common_surface* surface, + const guac_common_rect* rect) { + + int x, y; + + int num_same = 0; + int num_different = 1; + + /* Get image/buffer metrics */ + int width = rect->width; + int height = rect->height; + int stride = surface->stride; + + /* Get buffer from surface */ + unsigned char* buffer = surface->buffer + rect->y * stride + rect->x * 4; + + /* Image must be at least 1x1 */ + if (width < 1 || height < 1) + return 0; + + /* For each row */ + for (y = 0; y < height; y++) { + + uint32_t* row = (uint32_t*) buffer; + uint32_t last_pixel = *(row++) | 0xFF000000; + + /* For each pixel in current row */ + for (x = 1; x < width; x++) { + + /* Get next pixel */ + uint32_t current_pixel = *(row++) | 0xFF000000; + + /* Update same/different counts according to pixel value */ + if (current_pixel == last_pixel) + num_same++; + else + num_different++; + + last_pixel = current_pixel; + + } + + /* Advance to next row */ + buffer += stride; + + } + + /* Return rough approximation of optimality for PNG compression */ + return 0x100 * num_same / num_different - 0x400; + +} + +/** + * Returns whether the given rectangle would be optimally encoded as JPEG + * rather than PNG. + * + * @param surface + * The surface to be queried. + * + * @param rect + * The rectangle to check. + * + * @return + * Non-zero if the rectangle would be optimally encoded as JPEG, zero + * otherwise. + */ +static int __guac_common_surface_should_use_jpeg(guac_common_surface* surface, + const guac_common_rect* rect) { + + /* Do not use JPEG if lossless quality is required */ + if (surface->lossless) + return 0; + + /* Calculate the average framerate for the given rect */ + int framerate = __guac_common_surface_calculate_framerate(surface, rect); + + int rect_size = rect->width * rect->height; + + /* JPEG is preferred if: + * - frame rate is high enough + * - image size is large enough + * - PNG is not more optimal based on image contents */ + return framerate >= GUAC_COMMON_SURFACE_JPEG_FRAMERATE + && rect_size > GUAC_SURFACE_JPEG_MIN_BITMAP_SIZE + && __guac_common_surface_png_optimality(surface, rect) < 0; + +} + +/** + * Returns whether the given rectangle would be optimally encoded as WebP + * rather than PNG. + * + * @param surface + * The surface to be queried. + * + * @param rect + * The rectangle to check. + * + * @return + * Non-zero if the rectangle would be optimally encoded as WebP, zero + * otherwise. + */ +static int __guac_common_surface_should_use_webp(guac_common_surface* surface, + const guac_common_rect* rect) { + + /* Do not use WebP if not supported */ + if (!guac_client_supports_webp(surface->client)) + return 0; + + /* Calculate the average framerate for the given rect */ + int framerate = __guac_common_surface_calculate_framerate(surface, rect); + + /* WebP is preferred if: + * - frame rate is high enough + * - PNG is not more optimal based on image contents */ + return framerate >= GUAC_COMMON_SURFACE_JPEG_FRAMERATE + && __guac_common_surface_png_optimality(surface, rect) < 0; + +} + +/** + * Updates the heat map cells which intersect the given rectangle using the + * given timestamp. This timestamp, along with timestamps from past updates, + * is used to calculate the framerate of each heat cell. + * + * @param surface + * The surface containing the heat map cells to be updated. + * + * @param rect + * The rectangle containing the heat map cells to be updated. + * + * @param time + * The timestamp to use when updating the heat map cells which intersect + * the given rectangle. + */ +static void __guac_common_surface_touch_rect(guac_common_surface* surface, + guac_common_rect* rect, guac_timestamp time) { + + int x, y; + + /* Calculate heat map dimensions */ + size_t heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(surface->width); + + /* Calculate minimum X/Y coordinates intersecting given rect */ + int min_x = rect->x / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; + int min_y = rect->y / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; + + /* Calculate maximum X/Y coordinates intersecting given rect */ + int max_x = min_x + (rect->width - 1) / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; + int max_y = min_y + (rect->height - 1) / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; + + /* Get start of buffer at given coordinates */ + guac_common_surface_heat_cell* heat_row = + surface->heat_map + min_y * heat_width + min_x; + + /* Update all heat map cells which intersect with rectangle */ + for (y = min_y; y <= max_y; y++) { + + /* Get current row of heat map */ + guac_common_surface_heat_cell* heat_cell = heat_row; + + /* For each cell in subset of row */ + for (x = min_x; x <= max_x; x++) { + + /* Replace oldest entry with new timestamp */ + heat_cell->history[heat_cell->oldest_entry] = time; + + /* Update to next oldest entry */ + heat_cell->oldest_entry++; + if (heat_cell->oldest_entry >= + GUAC_COMMON_SURFACE_HEAT_CELL_HISTORY_SIZE) + heat_cell->oldest_entry = 0; + + /* Advance to next heat map cell */ + heat_cell++; + + } + + /* Next heat map row */ + heat_row += heat_width; + + } + +} + +/** + * Flushes the bitmap update currently described by the dirty rectangle within the + * given surface to that surface's bitmap queue. There MUST be space within the + * queue. + * + * @param surface The surface to flush. + */ +static void __guac_common_surface_flush_to_queue(guac_common_surface* surface) { + + guac_common_surface_bitmap_rect* rect; + + /* Do not flush if not dirty */ + if (!surface->dirty) + return; + + /* Add new rect to queue */ + rect = &(surface->bitmap_queue[surface->bitmap_queue_length++]); + rect->rect = surface->dirty_rect; + rect->flushed = 0; + + /* Surface now flushed */ + surface->dirty = 0; + +} + +/** + * Flushes the given surface, drawing any pending operations on the remote + * display. Surface properties are not flushed. + * + * @param surface + * The surface to flush. + */ +static void __guac_common_surface_flush(guac_common_surface* surface); + +/** + * Schedules a deferred flush of the given surface. This will not immediately + * flush the surface to the client. Instead, the result of the flush is + * added to a queue which is reinspected and combined (if possible) with other + * deferred flushes during the call to guac_common_surface_flush(). + * + * @param surface The surface to flush. + */ +static void __guac_common_surface_flush_deferred(guac_common_surface* surface) { + + /* Do not flush if not dirty */ + if (!surface->dirty) + return; + + /* Flush if queue size has reached maximum (space is reserved for the final + * dirty rect, as __guac_common_surface_flush() MAY add an additional rect + * to the queue */ + if (surface->bitmap_queue_length == GUAC_COMMON_SURFACE_QUEUE_SIZE-1) + __guac_common_surface_flush(surface); + + /* Append dirty rect to queue */ + __guac_common_surface_flush_to_queue(surface); + +} + +/** + * Transfers a single uint32_t using the given transfer function. + * + * @param op The transfer function to use. + * @param src The source of the uint32_t value. + * @param dst THe destination which will hold the result of the transfer. + * @return Non-zero if the destination value was changed, zero otherwise. + */ +static int __guac_common_surface_transfer_int(guac_transfer_function op, uint32_t* src, uint32_t* dst) { + + uint32_t orig = *dst; + + switch (op) { + + case GUAC_TRANSFER_BINARY_BLACK: + *dst = 0xFF000000; + break; + + case GUAC_TRANSFER_BINARY_WHITE: + *dst = 0xFFFFFFFF; + break; + + case GUAC_TRANSFER_BINARY_SRC: + *dst = *src; + break; + + case GUAC_TRANSFER_BINARY_DEST: + /* NOP */ + break; + + case GUAC_TRANSFER_BINARY_NSRC: + *dst = *src ^ 0x00FFFFFF; + break; + + case GUAC_TRANSFER_BINARY_NDEST: + *dst = *dst ^ 0x00FFFFFF; + break; + + case GUAC_TRANSFER_BINARY_AND: + *dst = ((*dst) & (0xFF000000 | *src)); + break; + + case GUAC_TRANSFER_BINARY_NAND: + *dst = ((*dst) & (0xFF000000 | *src)) ^ 0x00FFFFFF; + break; + + case GUAC_TRANSFER_BINARY_OR: + *dst = ((*dst) | (0x00FFFFFF & *src)); + break; + + case GUAC_TRANSFER_BINARY_NOR: + *dst = ((*dst) | (0x00FFFFFF & *src)) ^ 0x00FFFFFF; + break; + + case GUAC_TRANSFER_BINARY_XOR: + *dst = ((*dst) ^ (0x00FFFFFF & *src)); + break; + + case GUAC_TRANSFER_BINARY_XNOR: + *dst = ((*dst) ^ (0x00FFFFFF & *src)) ^ 0x00FFFFFF; + break; + + case GUAC_TRANSFER_BINARY_NSRC_AND: + *dst = ((*dst) & (0xFF000000 | (*src ^ 0x00FFFFFF))); + break; + + case GUAC_TRANSFER_BINARY_NSRC_NAND: + *dst = ((*dst) & (0xFF000000 | (*src ^ 0x00FFFFFF))) ^ 0x00FFFFFF; + break; + + case GUAC_TRANSFER_BINARY_NSRC_OR: + *dst = ((*dst) | (0x00FFFFFF & (*src ^ 0x00FFFFFF))); + break; + + case GUAC_TRANSFER_BINARY_NSRC_NOR: + *dst = ((*dst) | (0x00FFFFFF & (*src ^ 0x00FFFFFF))) ^ 0x00FFFFFF; + break; + + } + + return *dst != orig; + +} + +/** + * Assigns the given value to all pixels within a rectangle of the backing + * surface of the given destination surface. The color of all pixels within the + * rectangle, including the alpha component, is entirely replaced. + * + * @param dst + * The destination surface. + * + * @param rect + * The rectangle to draw. + * + * @param red + * The red component of the color value to assign to all pixels within the + * rectangle. + * + * @param green + * The green component of the color value to assign to all pixels within + * the rectangle. + * + * @param blue + * The blue component of the color value to assign to all pixels within the + * rectangle. + * + * @param alpha + * The alpha component of the color value to assign to all pixels within + * the rectangle. + */ +static void __guac_common_surface_set(guac_common_surface* dst, + guac_common_rect* rect, int red, int green, int blue, int alpha) { + + int x, y; + + int dst_stride; + unsigned char* dst_buffer; + + uint32_t color = (alpha << 24) | (red << 16) | (green << 8) | blue; + + int min_x = rect->width - 1; + int min_y = rect->height - 1; + int max_x = 0; + int max_y = 0; + + dst_stride = dst->stride; + dst_buffer = dst->buffer + (dst_stride * rect->y) + (4 * rect->x); + + /* For each row */ + for (y=0; y < rect->height; y++) { + + uint32_t* dst_current = (uint32_t*) dst_buffer; + + /* Set row */ + for (x=0; x < rect->width; x++) { + + uint32_t old_color = *dst_current; + + if (old_color != color) { + if (x < min_x) min_x = x; + if (y < min_y) min_y = y; + if (x > max_x) max_x = x; + if (y > max_y) max_y = y; + *dst_current = color; + } + + dst_current++; + } + + /* Next row */ + dst_buffer += dst_stride; + + } + + /* Restrict destination rect to only updated pixels */ + if (max_x >= min_x && max_y >= min_y) { + rect->x += min_x; + rect->y += min_y; + rect->width = max_x - min_x + 1; + rect->height = max_y - min_y + 1; + } + else { + rect->width = 0; + rect->height = 0; + } + +} + +/** + * Applies the Porter-Duff "over" composite operator, blending the two given + * color components using the given alpha value. + * + * @param dst + * The destination color component. + * + * @param src + * The source color component. + * + * @param alpha + * The alpha value which applies to the blending operation. + * + * @return + * The result of applying the Porter-Duff "over" composite operator to the + * given source and destination components. + */ +static int guac_common_surface_blend_component(int dst, int src, int alpha) { + + int blended = src + dst * (0xFF - alpha); + + /* Do not exceed maximum component value */ + if (blended > 0xFF) + return 0xFF; + + return blended; + +} + +/** + * Applies the Porter-Duff "over" composite operator, blending each component + * of the two given ARGB colors. + * + * @param dst + * The destination ARGB color. + * + * @param src + * The source ARGB color. + * + * @return + * The result of applying the Porter-Duff "over" composite operator to the + * given source and destination colors. + */ +static uint32_t guac_common_surface_argb_blend(uint32_t dst, uint32_t src) { + + /* Separate destination ARGB color into its components */ + int dst_a = (dst >> 24) & 0xFF; + int dst_r = (dst >> 16) & 0xFF; + int dst_g = (dst >> 8) & 0xFF; + int dst_b = dst & 0xFF; + + /* Separate source ARGB color into its components */ + int src_a = (src >> 24) & 0xFF; + int src_r = (src >> 16) & 0xFF; + int src_g = (src >> 8) & 0xFF; + int src_b = src & 0xFF; + + /* If source is fully opaque (or destination is fully transparent), the + * blended result is the source */ + if (src_a == 0xFF || dst_a == 0x00) + return src; + + /* If source is fully transparent, the blended result is the destination */ + if (src_a == 0x00) + return dst; + + /* Otherwise, blend each ARGB component, assuming pre-multiplied alpha */ + int r = guac_common_surface_blend_component(dst_r, src_r, src_a); + int g = guac_common_surface_blend_component(dst_g, src_g, src_a); + int b = guac_common_surface_blend_component(dst_b, src_b, src_a); + int a = guac_common_surface_blend_component(dst_a, src_a, src_a); + + /* Recombine blended components */ + return (a << 24) | (r << 16) | (g << 8) | b; + +} + +/** + * Copies data from the given buffer to the surface at the given coordinates. + * The dimensions and location of the destination rectangle will be altered + * to remove as many unchanged pixels as possible. + * + * @param src_buffer The buffer to copy. + * @param src_stride The number of bytes in each row of the source buffer. + * @param sx The X coordinate of the source rectangle. + * @param sy The Y coordinate of the source rectangle. + * @param dst The destination surface. + * @param rect The destination rectangle. + * @param opaque Non-zero if the source surface is opaque (its alpha channel + * should be ignored), zero otherwise. + */ +static void __guac_common_surface_put(unsigned char* src_buffer, int src_stride, + int* sx, int* sy, + guac_common_surface* dst, guac_common_rect* rect, + int opaque) { + + unsigned char* dst_buffer = dst->buffer; + int dst_stride = dst->stride; + + int x, y; + + int min_x = rect->width; + int min_y = rect->height; + int max_x = 0; + int max_y = 0; + + int orig_x = rect->x; + int orig_y = rect->y; + + src_buffer += src_stride * (*sy) + 4 * (*sx); + dst_buffer += (dst_stride * rect->y) + (4 * rect->x); + + /* For each row */ + for (y=0; y < rect->height; y++) { + + uint32_t* src_current = (uint32_t*) src_buffer; + uint32_t* dst_current = (uint32_t*) dst_buffer; + + /* Copy row */ + for (x=0; x < rect->width; x++) { + + uint32_t color; + + /* Get source and destination color values */ + uint32_t src_color = *src_current; + uint32_t dst_color = *dst_current; + + /* Ignore alpha channel if opaque */ + if (opaque) + color = src_color | 0xFF000000; + + /* Otherwise, perform alpha blending operation */ + else + color = guac_common_surface_argb_blend(dst_color, src_color); + + /* If the destination color is changing, update rectangle bounds + * and store the new color */ + if (dst_color != color) { + if (x < min_x) min_x = x; + if (y < min_y) min_y = y; + if (x > max_x) max_x = x; + if (y > max_y) max_y = y; + *dst_current = color; + } + + /* Advance to next pixel */ + src_current++; + dst_current++; + + } + + /* Next row */ + src_buffer += src_stride; + dst_buffer += dst_stride; + + } + + /* Restrict destination rect to only updated pixels */ + if (max_x >= min_x && max_y >= min_y) { + rect->x += min_x; + rect->y += min_y; + rect->width = max_x - min_x + 1; + rect->height = max_y - min_y + 1; + } + else { + rect->width = 0; + rect->height = 0; + } + + /* Update source X/Y */ + *sx += rect->x - orig_x; + *sy += rect->y - orig_y; + +} + +/** + * Fills the given surface with color, using the given buffer as a mask. Color + * will be added to the given surface iff the corresponding pixel within the + * buffer is opaque. + * + * @param src_buffer The buffer to use as a mask. + * @param src_stride The number of bytes in each row of the source buffer. + * @param sx The X coordinate of the source rectangle. + * @param sy The Y coordinate of the source rectangle. + * @param dst The destination surface. + * @param rect The destination rectangle. + * @param red The red component of the color of the fill. + * @param green The green component of the color of the fill. + * @param blue The blue component of the color of the fill. + */ +static void __guac_common_surface_fill_mask(unsigned char* src_buffer, int src_stride, + int sx, int sy, + guac_common_surface* dst, guac_common_rect* rect, + int red, int green, int blue) { + + unsigned char* dst_buffer = dst->buffer; + int dst_stride = dst->stride; + + uint32_t color = 0xFF000000 | (red << 16) | (green << 8) | blue; + int x, y; + + src_buffer += src_stride*sy + 4*sx; + dst_buffer += (dst_stride * rect->y) + (4 * rect->x); + + /* For each row */ + for (y=0; y < rect->height; y++) { + + uint32_t* src_current = (uint32_t*) src_buffer; + uint32_t* dst_current = (uint32_t*) dst_buffer; + + /* Stencil row */ + for (x=0; x < rect->width; x++) { + + /* Fill with color if opaque */ + if (*src_current & 0xFF000000) + *dst_current = color; + + src_current++; + dst_current++; + } + + /* Next row */ + src_buffer += src_stride; + dst_buffer += dst_stride; + + } + +} + +/** + * Copies data from the given surface to the given destination surface using + * the specified transfer function. + * + * @param src_buffer The buffer to copy. + * @param src_stride The number of bytes in each row of the source buffer. + * @param sx The X coordinate of the source rectangle. + * @param sy The Y coordinate of the source rectangle. + * @param op The transfer function to use. + * @param dst The destination surface. + * @param rect The destination rectangle. + */ +static void __guac_common_surface_transfer(guac_common_surface* src, int* sx, int* sy, + guac_transfer_function op, + guac_common_surface* dst, guac_common_rect* rect) { + + unsigned char* src_buffer = src->buffer; + unsigned char* dst_buffer = dst->buffer; + + int x, y; + int src_stride, dst_stride; + int step = 1; + + int min_x = rect->width - 1; + int min_y = rect->height - 1; + int max_x = 0; + int max_y = 0; + + int orig_x = rect->x; + int orig_y = rect->y; + + /* Copy forwards only if destination is in a different surface or is before source */ + if (src != dst || rect->y < *sy || (rect->y == *sy && rect->x < *sx)) { + src_buffer += src->stride * (*sy) + 4 * (*sx); + dst_buffer += (dst->stride * rect->y) + (4 * rect->x); + src_stride = src->stride; + dst_stride = dst->stride; + step = 1; + } + + /* Otherwise, copy backwards */ + else { + src_buffer += src->stride * (*sy + rect->height - 1) + 4 * (*sx + rect->width - 1); + dst_buffer += dst->stride * (rect->y + rect->height - 1) + 4 * (rect->x + rect->width - 1); + src_stride = -src->stride; + dst_stride = -dst->stride; + step = -1; + } + + /* For each row */ + for (y=0; y < rect->height; y++) { + + uint32_t* src_current = (uint32_t*) src_buffer; + uint32_t* dst_current = (uint32_t*) dst_buffer; + + /* Transfer each pixel in row */ + for (x=0; x < rect->width; x++) { + + if (__guac_common_surface_transfer_int(op, src_current, dst_current)) { + if (x < min_x) min_x = x; + if (y < min_y) min_y = y; + if (x > max_x) max_x = x; + if (y > max_y) max_y = y; + } + + src_current += step; + dst_current += step; + } + + /* Next row */ + src_buffer += src_stride; + dst_buffer += dst_stride; + + } + + /* Translate X coordinate space of moving backwards */ + if (step < 0) { + int old_max_x = max_x; + max_x = rect->width - 1 - min_x; + min_x = rect->width - 1 - old_max_x; + } + + /* Translate Y coordinate space of moving backwards */ + if (dst_stride < 0) { + int old_max_y = max_y; + max_y = rect->height - 1 - min_y; + min_y = rect->height - 1 - old_max_y; + } + + /* Restrict destination rect to only updated pixels */ + if (max_x >= min_x && max_y >= min_y) { + rect->x += min_x; + rect->y += min_y; + rect->width = max_x - min_x + 1; + rect->height = max_y - min_y + 1; + } + else { + rect->width = 0; + rect->height = 0; + } + + /* Update source X/Y */ + *sx += rect->x - orig_x; + *sy += rect->y - orig_y; + +} + +guac_common_surface* guac_common_surface_alloc(guac_client* client, + guac_socket* socket, const guac_layer* layer, int w, int h) { + + /* Calculate heat map dimensions */ + size_t heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(w); + size_t heat_height = GUAC_COMMON_SURFACE_HEAT_DIMENSION(h); + + /* Init surface */ + guac_common_surface* surface = guac_mem_zalloc(sizeof(guac_common_surface)); + surface->client = client; + surface->socket = socket; + surface->layer = layer; + surface->parent = GUAC_DEFAULT_LAYER; + surface->opacity = 0xFF; + surface->width = w; + surface->height = h; + + pthread_mutex_init(&surface->_lock, NULL); + + /* Create corresponding Cairo surface */ + surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w); + surface->buffer = guac_mem_zalloc(h, surface->stride); + + /* Create corresponding heat map */ + surface->heat_map = guac_mem_zalloc(heat_width, heat_height, + sizeof(guac_common_surface_heat_cell)); + + /* Reset clipping rect */ + guac_common_surface_reset_clip(surface); + + /* Layers must initially exist */ + if (layer->index >= 0) { + guac_protocol_send_size(socket, layer, w, h); + surface->realized = 1; + } + + /* Defer creation of buffers */ + else + surface->realized = 0; + + return surface; +} + +void guac_common_surface_free(guac_common_surface* surface) { + + /* Only dispose of surface if it exists */ + if (surface->realized) + guac_protocol_send_dispose(surface->socket, surface->layer); + + pthread_mutex_destroy(&surface->_lock); + + guac_mem_free(surface->heat_map); + guac_mem_free(surface->buffer); + guac_mem_free(surface); + +} + +void guac_common_surface_resize(guac_common_surface* surface, int w, int h) { + + pthread_mutex_lock(&surface->_lock); + + /* Ignore if resize will have no effect */ + if (w == surface->width && h == surface->height) + goto complete; + + guac_socket* socket = surface->socket; + const guac_layer* layer = surface->layer; + + unsigned char* old_buffer; + int old_stride; + guac_common_rect old_rect; + + int sx = 0; + int sy = 0; + + /* Calculate heat map dimensions */ + size_t heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(w); + size_t heat_height = GUAC_COMMON_SURFACE_HEAT_DIMENSION(h); + + /* Copy old surface data */ + old_buffer = surface->buffer; + old_stride = surface->stride; + guac_common_rect_init(&old_rect, 0, 0, surface->width, surface->height); + + /* Re-initialize at new size */ + surface->width = w; + surface->height = h; + surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w); + surface->buffer = guac_mem_zalloc(h, surface->stride); + __guac_common_bound_rect(surface, &surface->clip_rect, NULL, NULL); + + /* Copy relevant old data */ + __guac_common_bound_rect(surface, &old_rect, NULL, NULL); + __guac_common_surface_put(old_buffer, old_stride, &sx, &sy, surface, &old_rect, 1); + + /* Free old data */ + guac_mem_free(old_buffer); + + /* Allocate completely new heat map (can safely discard old stats) */ + guac_mem_free(surface->heat_map); + surface->heat_map = guac_mem_zalloc(heat_width, heat_height, + sizeof(guac_common_surface_heat_cell)); + + /* Resize dirty rect to fit new surface dimensions */ + if (surface->dirty) { + __guac_common_bound_rect(surface, &surface->dirty_rect, NULL, NULL); + if (surface->dirty_rect.width <= 0 || surface->dirty_rect.height <= 0) + surface->dirty = 0; + } + + /* Update Guacamole layer */ + if (surface->realized) + guac_protocol_send_size(socket, layer, w, h); + +complete: + pthread_mutex_unlock(&surface->_lock); + +} + +void guac_common_surface_draw(guac_common_surface* surface, int x, int y, cairo_surface_t* src) { + + pthread_mutex_lock(&surface->_lock); + + unsigned char* buffer = cairo_image_surface_get_data(src); + cairo_format_t format = cairo_image_surface_get_format(src); + int stride = cairo_image_surface_get_stride(src); + int w = cairo_image_surface_get_width(src); + int h = cairo_image_surface_get_height(src); + + int sx = 0; + int sy = 0; + + guac_common_rect rect; + guac_common_rect_init(&rect, x, y, w, h); + + /* Clip operation */ + __guac_common_clip_rect(surface, &rect, &sx, &sy); + if (rect.width <= 0 || rect.height <= 0) + goto complete; + + /* Update backing surface */ + __guac_common_surface_put(buffer, stride, &sx, &sy, surface, &rect, format != CAIRO_FORMAT_ARGB32); + if (rect.width <= 0 || rect.height <= 0) + goto complete; + + /* Update the heat map for the update rectangle. */ + guac_timestamp time = guac_timestamp_current(); + __guac_common_surface_touch_rect(surface, &rect, time); + + /* Flush if not combining */ + if (!__guac_common_should_combine(surface, &rect, 0)) + __guac_common_surface_flush_deferred(surface); + + /* Always defer draws */ + __guac_common_mark_dirty(surface, &rect); + +complete: + pthread_mutex_unlock(&surface->_lock); + +} + +void guac_common_surface_paint(guac_common_surface* surface, int x, int y, + cairo_surface_t* src, int red, int green, int blue) { + + pthread_mutex_lock(&surface->_lock); + + unsigned char* buffer = cairo_image_surface_get_data(src); + int stride = cairo_image_surface_get_stride(src); + int w = cairo_image_surface_get_width(src); + int h = cairo_image_surface_get_height(src); + + int sx = 0; + int sy = 0; + + guac_common_rect rect; + guac_common_rect_init(&rect, x, y, w, h); + + /* Clip operation */ + __guac_common_clip_rect(surface, &rect, &sx, &sy); + if (rect.width <= 0 || rect.height <= 0) + goto complete; + + /* Update backing surface */ + __guac_common_surface_fill_mask(buffer, stride, sx, sy, surface, &rect, red, green, blue); + + /* Flush if not combining */ + if (!__guac_common_should_combine(surface, &rect, 0)) + __guac_common_surface_flush_deferred(surface); + + /* Always defer draws */ + __guac_common_mark_dirty(surface, &rect); + +complete: + pthread_mutex_unlock(&surface->_lock); + +} + +void guac_common_surface_copy(guac_common_surface* src, int sx, int sy, + int w, int h, guac_common_surface* dst, int dx, int dy) { + + /* Lock both surfaces */ + pthread_mutex_lock(&dst->_lock); + if (src != dst) + pthread_mutex_lock(&src->_lock); + + guac_socket* socket = dst->socket; + const guac_layer* src_layer = src->layer; + const guac_layer* dst_layer = dst->layer; + + guac_common_rect srect; + guac_common_rect_init(&srect, sx, sy, w, h); + + /* Clip operation source rect to bounds */ + __guac_common_bound_rect(src, &srect, &dx, &dy); + if (srect.width <= 0 || srect.height <= 0) + goto complete; + + guac_common_rect drect; + guac_common_rect_init(&drect, dx, dy, + srect.width, srect.height); + + /* Clip operation destination rect */ + __guac_common_clip_rect(dst, &drect, &srect.x, &srect.y); + if (drect.width <= 0 || drect.height <= 0) + goto complete; + + /* NOTE: Being the last rectangle to be adjusted, only the width/height of + * drect is now correct! */ + + /* Update backing surface first only if drect cannot intersect srect */ + if (src != dst) { + __guac_common_surface_transfer(src, &srect.x, &srect.y, + GUAC_TRANSFER_BINARY_SRC, dst, &drect); + if (drect.width <= 0 || drect.height <= 0) + goto complete; + } + + /* Defer if combining */ + if (__guac_common_should_combine(dst, &drect, 1)) + __guac_common_mark_dirty(dst, &drect); + + /* Otherwise, flush and draw immediately */ + else { + __guac_common_surface_flush(dst); + __guac_common_surface_flush(src); + guac_protocol_send_copy(socket, src_layer, srect.x, srect.y, + drect.width, drect.height, GUAC_COMP_OVER, dst_layer, + drect.x, drect.y); + dst->realized = 1; + } + + /* Update backing surface last if drect can intersect srect */ + if (src == dst) + __guac_common_surface_transfer(src, &srect.x, &srect.y, + GUAC_TRANSFER_BINARY_SRC, dst, &drect); + +complete: + + /* Unlock both surfaces */ + pthread_mutex_unlock(&dst->_lock); + if (src != dst) + pthread_mutex_unlock(&src->_lock); + +} + +void guac_common_surface_transfer(guac_common_surface* src, int sx, int sy, int w, int h, + guac_transfer_function op, guac_common_surface* dst, int dx, int dy) { + + /* Lock both surfaces */ + pthread_mutex_lock(&dst->_lock); + if (src != dst) + pthread_mutex_lock(&src->_lock); + + guac_socket* socket = dst->socket; + const guac_layer* src_layer = src->layer; + const guac_layer* dst_layer = dst->layer; + + guac_common_rect srect; + guac_common_rect_init(&srect, sx, sy, w, h); + + /* Clip operation source rect to bounds */ + __guac_common_bound_rect(src, &srect, &dx, &dy); + if (srect.width <= 0 || srect.height <= 0) + goto complete; + + guac_common_rect drect; + guac_common_rect_init(&drect, dx, dy, + srect.width, srect.height); + + /* Clip operation destination rect */ + __guac_common_clip_rect(dst, &drect, &srect.x, &srect.y); + if (drect.width <= 0 || drect.height <= 0) + goto complete; + + /* NOTE: Being the last rectangle to be adjusted, only the width/height of + * drect is now correct! */ + + /* Update backing surface first only if drect cannot intersect srect */ + if (src != dst) { + __guac_common_surface_transfer(src, &srect.x, &srect.y, op, dst, &drect); + if (drect.width <= 0 || drect.height <= 0) + goto complete; + } + + /* Defer if combining */ + if (__guac_common_should_combine(dst, &drect, 1)) + __guac_common_mark_dirty(dst, &drect); + + /* Otherwise, flush and draw immediately */ + else { + __guac_common_surface_flush(dst); + __guac_common_surface_flush(src); + guac_protocol_send_transfer(socket, src_layer, srect.x, srect.y, + drect.width, drect.height, op, dst_layer, drect.x, drect.y); + dst->realized = 1; + } + + /* Update backing surface last if drect can intersect srect */ + if (src == dst) + __guac_common_surface_transfer(src, &srect.x, &srect.y, op, dst, &drect); + +complete: + + /* Unlock both surfaces */ + pthread_mutex_unlock(&dst->_lock); + if (src != dst) + pthread_mutex_unlock(&src->_lock); + +} + +void guac_common_surface_set(guac_common_surface* surface, + int x, int y, int w, int h, int red, int green, int blue, int alpha) { + + pthread_mutex_lock(&surface->_lock); + + guac_socket* socket = surface->socket; + const guac_layer* layer = surface->layer; + + guac_common_rect rect; + guac_common_rect_init(&rect, x, y, w, h); + + /* Clip operation */ + __guac_common_clip_rect(surface, &rect, NULL, NULL); + if (rect.width <= 0 || rect.height <= 0) + goto complete; + + /* Update backing surface */ + __guac_common_surface_set(surface, &rect, red, green, blue, alpha); + if (rect.width <= 0 || rect.height <= 0) + goto complete; + + /* Handle as normal draw if non-opaque */ + if (alpha != 0xFF) { + + /* Flush if not combining */ + if (!__guac_common_should_combine(surface, &rect, 0)) + __guac_common_surface_flush_deferred(surface); + + /* Always defer draws */ + __guac_common_mark_dirty(surface, &rect); + + } + + /* Defer if combining */ + else if (__guac_common_should_combine(surface, &rect, 1)) + __guac_common_mark_dirty(surface, &rect); + + /* Otherwise, flush and draw immediately */ + else { + __guac_common_surface_flush(surface); + guac_protocol_send_rect(socket, layer, rect.x, rect.y, rect.width, rect.height); + guac_protocol_send_cfill(socket, GUAC_COMP_OVER, layer, red, green, blue, alpha); + surface->realized = 1; + } + +complete: + pthread_mutex_unlock(&surface->_lock); + +} + +void guac_common_surface_clip(guac_common_surface* surface, int x, int y, int w, int h) { + + pthread_mutex_lock(&surface->_lock); + + guac_common_rect clip; + + /* Init clipping rectangle if clipping not already applied */ + if (!surface->clipped) { + guac_common_rect_init(&surface->clip_rect, 0, 0, surface->width, surface->height); + surface->clipped = 1; + } + + guac_common_rect_init(&clip, x, y, w, h); + guac_common_rect_constrain(&surface->clip_rect, &clip); + + pthread_mutex_unlock(&surface->_lock); + +} + +void guac_common_surface_reset_clip(guac_common_surface* surface) { + pthread_mutex_lock(&surface->_lock); + surface->clipped = 0; + pthread_mutex_unlock(&surface->_lock); +} + +/** + * Flushes the bitmap update currently described by the dirty rectangle within + * the given surface directly via an "img" instruction as PNG data. The + * resulting instructions will be sent over the socket associated with the + * given surface. + * + * @param surface + * The surface to flush. + * + * @param opaque + * Whether the rectangle being flushed contains only fully-opaque pixels. + */ +static void __guac_common_surface_flush_to_png(guac_common_surface* surface, + int opaque) { + + if (surface->dirty) { + + guac_socket* socket = surface->socket; + 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; + + /* Use RGB24 if the image is fully opaque */ + if (opaque) + rect = cairo_image_surface_create_for_data(buffer, + CAIRO_FORMAT_RGB24, surface->dirty_rect.width, + surface->dirty_rect.height, surface->stride); + + /* Otherwise ARGB32 is needed */ + else { + + rect = cairo_image_surface_create_for_data(buffer, + CAIRO_FORMAT_ARGB32, surface->dirty_rect.width, + surface->dirty_rect.height, surface->stride); + + /* Clear destination rect first */ + guac_protocol_send_rect(socket, layer, + surface->dirty_rect.x, surface->dirty_rect.y, + surface->dirty_rect.width, surface->dirty_rect.height); + guac_protocol_send_cfill(socket, GUAC_COMP_ROUT, layer, + 0x00, 0x00, 0x00, 0xFF); + + } + + /* 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; + + /* Surface is no longer dirty */ + surface->dirty = 0; + + } + +} + +/** + * Returns an appropriate quality between 0 and 100 for lossy encoding + * depending on the current processing lag calculated for the given client. + * + * @param client + * The client for which the lossy quality is being calculated. + * + * @return + * A value between 0 and 100 inclusive which seems appropriate for the + * client based on lag measurements. + */ +static int guac_common_surface_suggest_quality(guac_client* client) { + + int lag = guac_client_get_processing_lag(client); + + /* Scale quality linearly from 90 to 30 as lag varies from 20ms to 80ms */ + int quality = 90 - (lag - 20); + + /* Do not exceed 90 for quality */ + if (quality > 90) + return 90; + + /* Do not go below 30 for quality */ + if (quality < 30) + return 30; + + return quality; + +} + +/** + * Flushes the bitmap update currently described by the dirty rectangle within + * the given surface directly via an "img" instruction as JPEG data. The + * resulting instructions will be sent over the socket associated with the + * given surface. + * + * @param surface + * The surface to flush. + */ +static void __guac_common_surface_flush_to_jpeg(guac_common_surface* surface) { + + if (surface->dirty) { + + guac_socket* socket = surface->socket; + const guac_layer* layer = surface->layer; + + guac_common_rect max; + guac_common_rect_init(&max, 0, 0, surface->width, surface->height); + + /* Expand the dirty rect size to fit in a grid with cells equal to the + * minimum JPEG block size */ + guac_common_rect_expand_to_grid(GUAC_SURFACE_JPEG_BLOCK_SIZE, + &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); + + /* 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_common_surface_suggest_quality(surface->client)); + + cairo_surface_destroy(rect); + surface->realized = 1; + + /* Surface is no longer dirty */ + surface->dirty = 0; + + } + +} + +/** + * Flushes the bitmap update currently described by the dirty rectangle within + * the given surface directly via an "img" instruction as WebP data. The + * resulting instructions will be sent over the socket associated with the + * given surface. + * + * @param surface + * The surface to flush. + * + * @param opaque + * Whether the rectangle being flushed contains only fully-opaque pixels. + */ +static void __guac_common_surface_flush_to_webp(guac_common_surface* surface, + int opaque) { + + if (surface->dirty) { + + guac_socket* socket = surface->socket; + const guac_layer* layer = surface->layer; + + guac_common_rect max; + guac_common_rect_init(&max, 0, 0, surface->width, surface->height); + + /* Expand the dirty rect size to fit in a grid with cells equal to the + * minimum WebP block size */ + guac_common_rect_expand_to_grid(GUAC_SURFACE_WEBP_BLOCK_SIZE, + &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; + + /* Use RGB24 if the image is fully opaque */ + if (opaque) + rect = cairo_image_surface_create_for_data(buffer, + CAIRO_FORMAT_RGB24, surface->dirty_rect.width, + surface->dirty_rect.height, surface->stride); + + /* Otherwise ARGB32 is needed */ + else + rect = cairo_image_surface_create_for_data(buffer, + CAIRO_FORMAT_ARGB32, 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_common_surface_suggest_quality(surface->client), + surface->lossless ? 1 : 0); + + cairo_surface_destroy(rect); + surface->realized = 1; + + /* Surface is no longer dirty */ + surface->dirty = 0; + + } + +} + +/** + * Comparator for instances of guac_common_surface_bitmap_rect, the elements + * which make up a surface's bitmap buffer. + * + * @see qsort + */ +static int __guac_common_surface_bitmap_rect_compare(const void* a, const void* b) { + + guac_common_surface_bitmap_rect* ra = (guac_common_surface_bitmap_rect*) a; + guac_common_surface_bitmap_rect* rb = (guac_common_surface_bitmap_rect*) b; + + /* Order roughly top to bottom, left to right */ + if (ra->rect.y != rb->rect.y) return ra->rect.y - rb->rect.y; + if (ra->rect.x != rb->rect.x) return ra->rect.x - rb->rect.x; + + /* Wider updates should come first (more likely to intersect later) */ + if (ra->rect.width != rb->rect.width) return rb->rect.width - ra->rect.width; + + /* Shorter updates should come first (less likely to increase cost) */ + return ra->rect.height - rb->rect.height; + +} + +/** + * Flushes only the properties of the given surface, such as layer location or + * opacity. Image state is not flushed. If the surface represents a buffer or + * the default layer, this function has no effect. + * + * @param surface + * The surface to flush. + */ +static void __guac_common_surface_flush_properties( + guac_common_surface* surface) { + + guac_socket* socket = surface->socket; + + /* Only applicable to non-default visible layers */ + if (surface->layer->index <= 0) + return; + + /* Flush opacity */ + if (surface->opacity_dirty) { + guac_protocol_send_shade(socket, surface->layer, surface->opacity); + surface->opacity_dirty = 0; + } + + /* Flush location and hierarchy */ + if (surface->location_dirty) { + guac_protocol_send_move(socket, surface->layer, + surface->parent, surface->x, surface->y, surface->z); + surface->location_dirty = 0; + } + +} + +static void __guac_common_surface_flush(guac_common_surface* surface) { + + /* Flush final dirty rectangle to queue. */ + __guac_common_surface_flush_to_queue(surface); + + guac_common_surface_bitmap_rect* current = surface->bitmap_queue; + int i, j; + int original_queue_length; + int flushed = 0; + + original_queue_length = surface->bitmap_queue_length; + + /* Sort updates to make combination less costly */ + qsort(surface->bitmap_queue, surface->bitmap_queue_length, sizeof(guac_common_surface_bitmap_rect), + __guac_common_surface_bitmap_rect_compare); + + /* Flush all rects in queue */ + for (i=0; i < surface->bitmap_queue_length; i++) { + + /* Get next unflushed candidate */ + guac_common_surface_bitmap_rect* candidate = current; + if (!candidate->flushed) { + + int combined = 0; + + /* Build up rect as much as possible */ + for (j=i; j < surface->bitmap_queue_length; j++) { + + if (!candidate->flushed) { + + /* Clip candidate within current bounds */ + __guac_common_bound_rect(surface, &candidate->rect, NULL, NULL); + if (candidate->rect.width <= 0 || candidate->rect.height <= 0) + candidate->flushed = 1; + + /* Combine if reasonable */ + else if (__guac_common_should_combine(surface, &candidate->rect, 0) || !surface->dirty) { + __guac_common_mark_dirty(surface, &candidate->rect); + candidate->flushed = 1; + combined++; + } + + } + + candidate++; + + } + + /* Re-add to queue if there's room and this update was modified or we expect others might be */ + if ((combined > 1 || i < original_queue_length) + && surface->bitmap_queue_length < GUAC_COMMON_SURFACE_QUEUE_SIZE) + __guac_common_surface_flush_to_queue(surface); + + /* Flush as bitmap otherwise */ + else if (surface->dirty) { + + flushed++; + + int opaque = __guac_common_surface_is_opaque(surface, + &surface->dirty_rect); + + /* Prefer WebP when reasonable */ + if (__guac_common_surface_should_use_webp(surface, + &surface->dirty_rect)) + __guac_common_surface_flush_to_webp(surface, opaque); + + /* If not WebP, JPEG is the next best (lossy) choice */ + else if (opaque && __guac_common_surface_should_use_jpeg( + surface, &surface->dirty_rect)) + __guac_common_surface_flush_to_jpeg(surface); + + /* Use PNG if no lossy formats are appropriate */ + else + __guac_common_surface_flush_to_png(surface, opaque); + + } + + } + + current++; + + } + + /* Flush complete */ + surface->bitmap_queue_length = 0; + +} + +void guac_common_surface_flush(guac_common_surface* surface) { + + pthread_mutex_lock(&surface->_lock); + + /* Flush any applicable layer properties */ + __guac_common_surface_flush_properties(surface); + + /* Flush surface contents */ + __guac_common_surface_flush(surface); + + pthread_mutex_unlock(&surface->_lock); + +} + +void guac_common_surface_dup(guac_common_surface* surface, + guac_client* client, guac_socket* socket) { + + pthread_mutex_lock(&surface->_lock); + + /* Do nothing if not realized */ + if (!surface->realized) + goto complete; + + /* Synchronize layer-specific properties if applicable */ + if (surface->layer->index > 0) { + + /* Synchronize opacity */ + guac_protocol_send_shade(socket, surface->layer, surface->opacity); + + /* Synchronize location and hierarchy */ + guac_protocol_send_move(socket, surface->layer, + surface->parent, surface->x, surface->y, surface->z); + + } + + /* Synchronize multi-touch support level */ + else if (surface->layer->index == 0) + guac_protocol_send_set_int(socket, surface->layer, + GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH, + surface->touches); + + /* Sync size to new socket */ + guac_protocol_send_size(socket, surface->layer, + surface->width, surface->height); + + /* Send contents of layer, if non-empty */ + if (surface->width > 0 && surface->height > 0) { + + /* Get entire surface */ + cairo_surface_t* rect = cairo_image_surface_create_for_data( + surface->buffer, CAIRO_FORMAT_ARGB32, + surface->width, surface->height, surface->stride); + + /* Send PNG for rect */ + guac_client_stream_png(client, socket, GUAC_COMP_OVER, surface->layer, + 0, 0, rect); + cairo_surface_destroy(rect); + + } + +complete: + pthread_mutex_unlock(&surface->_lock); + +} diff --git a/src/common/tests/Makefile.am b/src/common/tests/Makefile.am index b84dc827f..27ac75cd1 100644 --- a/src/common/tests/Makefile.am +++ b/src/common/tests/Makefile.am @@ -39,6 +39,12 @@ noinst_HEADERS = \ test_common_SOURCES = \ iconv/convert.c \ iconv/convert-test-data.c \ + rect/clip_and_split.c \ + rect/constrain.c \ + rect/expand_to_grid.c \ + rect/extend.c \ + rect/init.c \ + rect/intersects.c \ string/count_occurrences.c \ string/split.c diff --git a/src/common/tests/rect/clip_and_split.c b/src/common/tests/rect/clip_and_split.c new file mode 100644 index 000000000..e286bce9f --- /dev/null +++ b/src/common/tests/rect/clip_and_split.c @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "common/rect.h" + +#include + +/** + * Test which verifies that guac_common_rect_clip_and_split() divides a + * rectangle into subrectangles after removing a "hole" rectangle. + */ +void test_rect__clip_and_split() { + + int res; + + guac_common_rect cut; + guac_common_rect min; + guac_common_rect rect; + + guac_common_rect_init(&min, 10, 10, 10, 10); + + /* Clip top */ + guac_common_rect_init(&rect, 10, 5, 10, 10); + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(10, cut.x); + CU_ASSERT_EQUAL(5, cut.y); + CU_ASSERT_EQUAL(10, cut.width); + CU_ASSERT_EQUAL(5, cut.height); + + CU_ASSERT_EQUAL(10, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(10, rect.width); + CU_ASSERT_EQUAL(5, rect.height); + + /* Clip bottom */ + guac_common_rect_init(&rect, 10, 15, 10, 10); + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(10, cut.x); + CU_ASSERT_EQUAL(20, cut.y); + CU_ASSERT_EQUAL(10, cut.width); + CU_ASSERT_EQUAL(5, cut.height); + + CU_ASSERT_EQUAL(10, rect.x); + CU_ASSERT_EQUAL(15, rect.y); + CU_ASSERT_EQUAL(10, rect.width); + CU_ASSERT_EQUAL(5, rect.height); + + /* Clip left */ + guac_common_rect_init(&rect, 5, 10, 10, 10); + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(5, cut.x); + CU_ASSERT_EQUAL(10, cut.y); + CU_ASSERT_EQUAL(5, cut.width); + CU_ASSERT_EQUAL(10, cut.height); + + CU_ASSERT_EQUAL(10, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(5, rect.width); + CU_ASSERT_EQUAL(10, rect.height); + + /* Clip right */ + guac_common_rect_init(&rect, 15, 10, 10, 10); + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(20, cut.x); + CU_ASSERT_EQUAL(10, cut.y); + CU_ASSERT_EQUAL(5, cut.width); + CU_ASSERT_EQUAL(10, cut.height); + + CU_ASSERT_EQUAL(15, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(5, rect.width); + CU_ASSERT_EQUAL(10, rect.height); + + /* + * Test a rectangle which completely covers the hole. + * Clip and split until done. + */ + guac_common_rect_init(&rect, 5, 5, 20, 20); + + /* Clip top */ + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(5, cut.x); + CU_ASSERT_EQUAL(5, cut.y); + CU_ASSERT_EQUAL(20, cut.width); + CU_ASSERT_EQUAL(5, cut.height); + + CU_ASSERT_EQUAL(5, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(20, rect.width); + CU_ASSERT_EQUAL(15, rect.height); + + /* Clip left */ + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(5, cut.x); + CU_ASSERT_EQUAL(10, cut.y); + CU_ASSERT_EQUAL(5, cut.width); + CU_ASSERT_EQUAL(15, cut.height); + + CU_ASSERT_EQUAL(10, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(15, rect.width); + CU_ASSERT_EQUAL(15, rect.height); + + /* Clip bottom */ + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(10, cut.x); + CU_ASSERT_EQUAL(20, cut.y); + CU_ASSERT_EQUAL(15, cut.width); + CU_ASSERT_EQUAL(5, cut.height); + + CU_ASSERT_EQUAL(10, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(15, rect.width); + CU_ASSERT_EQUAL(10, rect.height); + + /* Clip right */ + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(20, cut.x); + CU_ASSERT_EQUAL(10, cut.y); + CU_ASSERT_EQUAL(5, cut.width); + CU_ASSERT_EQUAL(10, cut.height); + + CU_ASSERT_EQUAL(10, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(10, rect.width); + CU_ASSERT_EQUAL(10, rect.height); + + /* Make sure nothing is left to do */ + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(0, res); + +} + diff --git a/src/common/tests/rect/constrain.c b/src/common/tests/rect/constrain.c new file mode 100644 index 000000000..793aac224 --- /dev/null +++ b/src/common/tests/rect/constrain.c @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "common/rect.h" + +#include + +/** + * Test which verifies that guac_common_rect_constrain() restricts a given + * rectangle to arbitrary bounds. + */ +void test_rect__constrain() { + + guac_common_rect max; + guac_common_rect rect; + + guac_common_rect_init(&rect, -10, -10, 110, 110); + guac_common_rect_init(&max, 0, 0, 100, 100); + guac_common_rect_constrain(&rect, &max); + + CU_ASSERT_EQUAL(0, rect.x); + CU_ASSERT_EQUAL(0, rect.y); + CU_ASSERT_EQUAL(100, rect.width); + CU_ASSERT_EQUAL(100, rect.height); + +} + diff --git a/src/common/tests/rect/expand_to_grid.c b/src/common/tests/rect/expand_to_grid.c new file mode 100644 index 000000000..beef87d89 --- /dev/null +++ b/src/common/tests/rect/expand_to_grid.c @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "common/rect.h" + +#include + +/** + * Test which verifies guac_common_rect_expand_to_grid() properly shifts and + * resizes rectangles to fit an NxN grid. + */ +void test_rect__expand_to_grid() { + + int cell_size = 16; + + guac_common_rect max; + guac_common_rect rect; + + /* Simple adjustment */ + guac_common_rect_init(&rect, 0, 0, 25, 25); + guac_common_rect_init(&max, 0, 0, 100, 100); + guac_common_rect_expand_to_grid(cell_size, &rect, &max); + CU_ASSERT_EQUAL(0, rect.x); + CU_ASSERT_EQUAL(0, rect.y); + CU_ASSERT_EQUAL(32, rect.width); + CU_ASSERT_EQUAL(32, rect.height); + + /* Adjustment with moving of rect */ + guac_common_rect_init(&rect, 75, 75, 25, 25); + guac_common_rect_init(&max, 0, 0, 100, 100); + guac_common_rect_expand_to_grid(cell_size, &rect, &max); + CU_ASSERT_EQUAL(max.width - 32, rect.x); + CU_ASSERT_EQUAL(max.height - 32, rect.y); + CU_ASSERT_EQUAL(32, rect.width); + CU_ASSERT_EQUAL(32, rect.height); + + guac_common_rect_init(&rect, -5, -5, 25, 25); + guac_common_rect_init(&max, 0, 0, 100, 100); + guac_common_rect_expand_to_grid(cell_size, &rect, &max); + CU_ASSERT_EQUAL(0, rect.x); + CU_ASSERT_EQUAL(0, rect.y); + CU_ASSERT_EQUAL(32, rect.width); + CU_ASSERT_EQUAL(32, rect.height); + + /* Adjustment with moving and clamping of rect */ + guac_common_rect_init(&rect, 0, 0, 25, 15); + guac_common_rect_init(&max, 0, 5, 32, 15); + guac_common_rect_expand_to_grid(cell_size, &rect, &max); + CU_ASSERT_EQUAL(max.x, rect.x); + CU_ASSERT_EQUAL(max.y, rect.y); + CU_ASSERT_EQUAL(max.width, rect.width); + CU_ASSERT_EQUAL(max.height, rect.height); + +} + diff --git a/src/common/tests/rect/extend.c b/src/common/tests/rect/extend.c new file mode 100644 index 000000000..dc2a0e308 --- /dev/null +++ b/src/common/tests/rect/extend.c @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "common/rect.h" + +#include + +/** + * Test which verifies that guac_common_rect_extend() expands the given + * rectangle as necessary to contain at least the given bounds. + */ +void test_rect__extend() { + + guac_common_rect max; + guac_common_rect rect; + + guac_common_rect_init(&rect, 10, 10, 90, 90); + guac_common_rect_init(&max, 0, 0, 100, 100); + guac_common_rect_extend(&rect, &max); + CU_ASSERT_EQUAL(0, rect.x); + CU_ASSERT_EQUAL(0, rect.y); + CU_ASSERT_EQUAL(100, rect.width); + CU_ASSERT_EQUAL(100, rect.height); + +} + diff --git a/src/common/tests/rect/init.c b/src/common/tests/rect/init.c new file mode 100644 index 000000000..288cd751b --- /dev/null +++ b/src/common/tests/rect/init.c @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "common/rect.h" + +#include + +/** + * Test which verifies rectangle initialization via guac_common_rect_init(). + */ +void test_rect__init() { + + guac_common_rect max; + + guac_common_rect_init(&max, 0, 0, 100, 100); + + CU_ASSERT_EQUAL(0, max.x); + CU_ASSERT_EQUAL(0, max.y); + CU_ASSERT_EQUAL(100, max.width); + CU_ASSERT_EQUAL(100, max.height); + +} + diff --git a/src/common/tests/rect/intersects.c b/src/common/tests/rect/intersects.c new file mode 100644 index 000000000..c48026844 --- /dev/null +++ b/src/common/tests/rect/intersects.c @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "common/rect.h" + +#include + +/** + * Test which verifies intersection testing via guac_common_rect_intersects(). + */ +void test_rect__intersects() { + + int res; + + guac_common_rect min; + guac_common_rect rect; + + guac_common_rect_init(&min, 10, 10, 10, 10); + + /* Rectangle intersection - empty + * rectangle is outside */ + guac_common_rect_init(&rect, 25, 25, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(0, res); + + /* Rectangle intersection - complete + * rectangle is completely inside */ + guac_common_rect_init(&rect, 11, 11, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(2, res); + + /* Rectangle intersection - partial + * rectangle intersects UL */ + guac_common_rect_init(&rect, 8, 8, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(1, res); + + /* Rectangle intersection - partial + * rectangle intersects LR */ + guac_common_rect_init(&rect, 18, 18, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(1, res); + + /* Rectangle intersection - complete + * rect intersects along UL but inside */ + guac_common_rect_init(&rect, 10, 10, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(2, res); + + /* Rectangle intersection - partial + * rectangle intersects along L but outside */ + guac_common_rect_init(&rect, 5, 10, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(1, res); + + /* Rectangle intersection - complete + * rectangle intersects along LR but rest is inside */ + guac_common_rect_init(&rect, 15, 15, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(2, res); + + /* Rectangle intersection - partial + * rectangle intersects along R but rest is outside */ + guac_common_rect_init(&rect, 20, 10, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(1, res); + + /* Rectangle intersection - partial + * rectangle encloses min; which is a partial intersection */ + guac_common_rect_init(&rect, 5, 5, 20, 20); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(1, res); + +} +