diff --git a/Makefile.am b/Makefile.am index 9b5ab08f..608ea90d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -29,6 +29,7 @@ DIST_SUBDIRS = \ src/common-ssh \ src/terminal \ src/guacd \ + src/guacenc \ src/protocols/rdp \ src/protocols/ssh \ src/protocols/telnet \ @@ -38,7 +39,6 @@ DIST_SUBDIRS = \ SUBDIRS = \ src/libguac \ src/common \ - src/guacd \ tests if ENABLE_COMMON_SSH @@ -65,6 +65,14 @@ if ENABLE_VNC SUBDIRS += src/protocols/vnc endif +if ENABLE_GUACD +SUBDIRS += src/guacd +endif + +if ENABLE_GUACENC +SUBDIRS += src/guacenc +endif + EXTRA_DIST = \ LICENSE \ bin/guacctl \ diff --git a/configure.ac b/configure.ac index 1617c295..a818fee6 100644 --- a/configure.ac +++ b/configure.ac @@ -146,6 +146,63 @@ AC_ARG_WITH(guacd_conf, [guacd_conf=/etc/guacamole/guacd.conf]) AC_DEFINE_UNQUOTED([GUACD_CONF_FILE], ["$guacd_conf"], [The full path to the guacd config file]) +# +# libavcodec +# + +have_libavcodec=disabled +AC_ARG_WITH([libavcodec], + [AS_HELP_STRING([--with-libavcodec], + [use libavcodec when encoding video @<:@default=check@:>@])], + [], + [with_libavcodec=check]) + +if test "x$with_libavcodec" != "xno" +then + have_libavcodec=yes + PKG_CHECK_MODULES([AVCODEC], [libavcodec],, [have_libavcodec=no]); +fi + +AM_CONDITIONAL([ENABLE_AVCODEC], [test "x${have_libavcodec}" = "xyes"]) + +# +# libavutil +# + +have_libavutil=disabled +AC_ARG_WITH([libavutil], + [AS_HELP_STRING([--with-libavutil], + [use libavutil when encoding video @<:@default=check@:>@])], + [], + [with_libavutil=check]) + +if test "x$with_libavutil" != "xno" +then + have_libavutil=yes + PKG_CHECK_MODULES([AVUTIL], [libavutil],, [have_libavutil=no]); +fi + +AM_CONDITIONAL([ENABLE_AVUTIL], [test "x${have_libavutil}" = "xyes"]) + +# +# libswscale +# + +have_libswscale=disabled +AC_ARG_WITH([libswscale], + [AS_HELP_STRING([--with-libswscale], + [use libswscale when encoding video @<:@default=check@:>@])], + [], + [with_libswscale=check]) + +if test "x$with_libswscale" != "xno" +then + have_libswscale=yes + PKG_CHECK_MODULES([SWSCALE], [libswscale],, [have_libswscale=no]); +fi + +AM_CONDITIONAL([ENABLE_SWSCALE], [test "x${have_libswscale}" = "xyes"]) + # # libssl # @@ -966,6 +1023,36 @@ fi AM_CONDITIONAL([ENABLE_WEBP], [test "x${have_webp}" = "xyes"]) AC_SUBST(WEBP_LIBS) +# +# guacd +# + +AC_ARG_ENABLE([guacd], + [AS_HELP_STRING([--disable-guacd], + [do not build the Guacamole proxy daemon])], + [], + [enable_guacd=yes]) + +AM_CONDITIONAL([ENABLE_GUACD], [test "x${enable_guacd}" = "xyes"]) + +# +# guacenc +# + +AC_ARG_ENABLE([guacenc], + [AS_HELP_STRING([--disable-guacenc], + [do not build the Guacamole video encoding tool])], + [], + [enable_guacenc=yes]) + +AM_CONDITIONAL([ENABLE_GUACENC], [test "x${enable_guacenc}" = "xyes" \ + -a "x${have_libavcodec}" = "xyes" \ + -a "x${have_libavutil}" = "xyes" \ + -a "x${have_libswscale}" = "xyes"]) + +# +# Output Makefiles +# AC_CONFIG_FILES([Makefile tests/Makefile @@ -974,19 +1061,39 @@ AC_CONFIG_FILES([Makefile src/terminal/Makefile src/libguac/Makefile src/guacd/Makefile + src/guacenc/Makefile src/protocols/rdp/Makefile src/protocols/ssh/Makefile src/protocols/telnet/Makefile src/protocols/vnc/Makefile]) AC_OUTPUT +# +# Protocol build status +# + AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no]) AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no]) AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no]) AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no]) +# +# Service / tool build status +# + +AM_COND_IF([ENABLE_GUACD], [build_guacd=yes], [build_guacd=no]) +AM_COND_IF([ENABLE_GUACENC], [build_guacenc=yes], [build_guacenc=no]) + +# +# Init scripts +# + AM_COND_IF([ENABLE_INIT], [build_init="${init_dir}"], [build_init=no]) +# +# Display summary +# + echo " ------------------------------------------------ $PACKAGE_NAME version $PACKAGE_VERSION @@ -996,8 +1103,11 @@ $PACKAGE_NAME version $PACKAGE_VERSION freerdp ............. ${have_freerdp} pango ............... ${have_pango} + libavcodec .......... ${have_libavcodec} + libavutil ........... ${have_libavutil} libssh2 ............. ${have_libssh2} libssl .............. ${have_ssl} + libswscale .......... ${have_libswscale} libtelnet ........... ${have_libtelnet} libVNCServer ........ ${have_libvncserver} libvorbis ........... ${have_vorbis} @@ -1011,6 +1121,11 @@ $PACKAGE_NAME version $PACKAGE_VERSION Telnet .... ${build_telnet} VNC ....... ${build_vnc} + Services / tools: + + guacd ...... ${build_guacd} + guacenc .... ${build_guacenc} + Init scripts: ${build_init} Type \"make\" to compile $PACKAGE_NAME. diff --git a/src/common/Makefile.am b/src/common/Makefile.am index f04e04b8..bab82261 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -37,6 +37,7 @@ noinst_HEADERS = \ guac_json.h \ guac_list.h \ guac_pointer_cursor.h \ + guac_recording.h \ guac_rect.h \ guac_string.h \ guac_surface.h @@ -53,6 +54,7 @@ libguac_common_la_SOURCES = \ guac_json.c \ guac_list.c \ guac_pointer_cursor.c \ + guac_recording.c \ guac_rect.c \ guac_string.c \ guac_surface.c diff --git a/src/common/guac_recording.c b/src/common/guac_recording.c new file mode 100644 index 00000000..461b80e8 --- /dev/null +++ b/src/common/guac_recording.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "guac_recording.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Attempts to open a new recording within the given path and having the given + * name. If such a file already exists, sequential numeric suffixes (.1, .2, + * .3, etc.) are appended until a filename is found which does not exist (or + * until the maximum number of numeric suffixes has been tried). If the file + * absolutely cannot be opened due to an error, -1 is returned and errno is set + * appropriately. + * + * @param path + * The full path to the directory in which the data file should be created. + * + * @param name + * The name of the data file which should be crated within the given path. + * + * @param basename + * A buffer in which the path, a path separator, the filename, any + * necessary suffix, and a NULL terminator will be stored. If insufficient + * space is available, -1 will be returned, and errno will be set to + * ENAMETOOLONG. + * + * @param basename_size + * The number of bytes available within the provided basename buffer. + * + * @return + * The file descriptor of the open data file if open succeeded, or -1 on + * failure. + */ +static int guac_common_recording_open(const char* path, + const char* name, char* basename, int basename_size) { + + int i; + + /* Concatenate path and name (separated by a single slash) */ + int basename_length = snprintf(basename, + basename_size - GUAC_COMMON_RECORDING_MAX_SUFFIX_LENGTH, + "%s/%s", path, name); + + /* Abort if maximum length reached */ + if (basename_length == + basename_size - GUAC_COMMON_RECORDING_MAX_SUFFIX_LENGTH) { + errno = ENAMETOOLONG; + return -1; + } + + /* Attempt to open recording */ + int fd = open(basename, + O_CREAT | O_EXCL | O_WRONLY, + S_IRUSR | S_IWUSR); + + /* Continuously retry with alternate names on failure */ + if (fd == -1) { + + /* Prepare basename for additional suffix */ + basename[basename_length] = '.'; + char* suffix = &(basename[basename_length + 1]); + + /* Continue retrying alternative suffixes if file already exists */ + for (i = 1; fd == -1 && errno == EEXIST + && i <= GUAC_COMMON_RECORDING_MAX_SUFFIX; i++) { + + /* Append new suffix */ + sprintf(suffix, "%i", i); + + /* Retry with newly-suffixed filename */ + fd = open(basename, + O_CREAT | O_EXCL | O_WRONLY, + S_IRUSR | S_IWUSR); + + } + + } /* end if open succeeded */ + + /* Lock entire output file for writing by the current process */ + struct flock file_lock = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0, + .l_pid = getpid() + }; + + /* Abort if file cannot be locked for reading */ + if (fcntl(fd, F_SETLK, &file_lock) == -1) { + close(fd); + return -1; + } + + return fd; + +} + +int guac_common_recording_create(guac_client* client, const char* path, + const char* name, int create_path) { + + char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH]; + + /* Create path if it does not exist, fail if impossible */ + if (create_path && mkdir(path, S_IRWXU) && errno != EEXIST) { + guac_client_log(client, GUAC_LOG_ERROR, + "Creation of recording failed: %s", strerror(errno)); + return 1; + } + + /* Attempt to open recording file */ + int fd = guac_common_recording_open(path, name, filename, sizeof(filename)); + if (fd == -1) { + guac_client_log(client, GUAC_LOG_ERROR, + "Creation of recording failed: %s", strerror(errno)); + return 1; + } + + /* Replace client socket with wrapped socket */ + client->socket = guac_socket_tee(client->socket, guac_socket_open(fd)); + + /* Recording creation succeeded */ + guac_client_log(client, GUAC_LOG_INFO, + "Recording of session will be saved to \"%s\".", + filename); + + return 0; + +} + diff --git a/src/common/guac_recording.h b/src/common/guac_recording.h new file mode 100644 index 00000000..bbd6b4b1 --- /dev/null +++ b/src/common/guac_recording.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_COMMON_RECORDING_H +#define GUAC_COMMON_RECORDING_H + +#include + +/** + * The maximum numeric value allowed for the .1, .2, .3, etc. suffix appended + * to the end of the session recording filename if a recording having the + * requested name already exists. + */ +#define GUAC_COMMON_RECORDING_MAX_SUFFIX 255 + +/** + * The maximum length of the string containing a sequential numeric suffix + * between 1 and GUAC_COMMON_RECORDING_MAX_SUFFIX inclusive, in bytes, + * including NULL terminator. + */ +#define GUAC_COMMON_RECORDING_MAX_SUFFIX_LENGTH 4 + +/** + * The maximum overall length of the full path to the session recording file, + * including any additional suffix and NULL terminator, in bytes. + */ +#define GUAC_COMMON_RECORDING_MAX_NAME_LENGTH 2048 + +/** + * Replaces the socket of the given client such that all further Guacamole + * protocol output will be copied into a file within the given path and having + * the given name. If the create_path flag is non-zero, the given path will be + * created if it does not yet exist. If creation of the recording file or path + * fails, error messages will automatically be logged, and no recording will be + * written. The recording will automatically be closed once the client is + * freed. + * + * @param client + * The client whose output should be copied to a recording file. + * + * @param path + * The full absolute path to a directory in which the recording file should + * be created. + * + * @param name + * The base name to use for the recording file created within the specified + * path. + * + * @param create_path + * Zero if the specified path MUST exist for the recording file to be + * written, or non-zero if the path should be created if it does not yet + * exist. + * + * @return + * Zero if the recording file has been successfully created and a recording + * will be written, non-zero otherwise. + */ +int guac_common_recording_create(guac_client* client, const char* path, + const char* name, int create_path); + +#endif + diff --git a/src/guacenc/.gitignore b/src/guacenc/.gitignore new file mode 100644 index 00000000..d705721b --- /dev/null +++ b/src/guacenc/.gitignore @@ -0,0 +1,5 @@ + +# Compiled guacenc +guacenc +guacenc.exe + diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am new file mode 100644 index 00000000..12309920 --- /dev/null +++ b/src/guacenc/Makefile.am @@ -0,0 +1,103 @@ +# +# Copyright (C) 2016 Glyptodon, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +AUTOMAKE_OPTIONS = foreign + +bin_PROGRAMS = guacenc + +man_MANS = \ + man/guacenc.1 + +noinst_HEADERS = \ + buffer.h \ + display.h \ + encode.h \ + ffmpeg-compat.h \ + guacenc.h \ + image-stream.h \ + instructions.h \ + jpeg.h \ + layer.h \ + log.h \ + parse.h \ + png.h \ + video.h + +guacenc_SOURCES = \ + buffer.c \ + display.c \ + display-buffers.c \ + display-image-streams.c \ + display-flatten.c \ + display-layers.c \ + display-sync.c \ + encode.c \ + guacenc.c \ + image-stream.c \ + instructions.c \ + instruction-blob.c \ + instruction-cfill.c \ + instruction-copy.c \ + instruction-cursor.c \ + instruction-dispose.c \ + instruction-end.c \ + instruction-img.c \ + instruction-move.c \ + instruction-rect.c \ + instruction-shade.c \ + instruction-size.c \ + instruction-sync.c \ + instruction-transfer.c \ + jpeg.c \ + layer.c \ + log.c \ + parse.c \ + png.c \ + video.c + +# Compile WebP support if available +if ENABLE_WEBP +guacenc_SOURCES += webp.c +noinst_HEADERS += webp.h +endif + +guacenc_CFLAGS = \ + -Werror -Wall -pedantic \ + @AVCODEC_CFLAGS@ \ + @AVUTIL_CFLAGS@ \ + @LIBGUAC_INCLUDE@ \ + @SWSCALE_CFLAGS@ + +guacenc_LDADD = \ + @LIBGUAC_LTLIB@ + +guacenc_LDFLAGS = \ + @AVCODEC_LIBS@ \ + @AVUTIL_LIBS@ \ + @CAIRO_LIBS@ \ + @JPEG_LIBS@ \ + @SWSCALE_LIBS@ \ + @WEBP_LIBS@ + +EXTRA_DIST = \ + man/guacenc.1 + diff --git a/src/guacenc/buffer.c b/src/guacenc/buffer.c new file mode 100644 index 00000000..1a3481f9 --- /dev/null +++ b/src/guacenc/buffer.c @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "buffer.h" + +#include + +#include +#include + +guacenc_buffer* guacenc_buffer_alloc() { + return calloc(1, sizeof(guacenc_buffer)); +} + +/** + * Frees the underlying image data, surface, and graphics context of the given + * buffer, marking each as unallocated. + * + * @param buffer + * The guacenc_buffer whose image data, surface, and graphics context + * should be freed. + */ +static void guacenc_buffer_free_image(guacenc_buffer* buffer) { + + /* Free graphics context */ + if (buffer->cairo != NULL) { + cairo_destroy(buffer->cairo); + buffer->cairo = NULL; + } + + /* Free Cairo surface */ + if (buffer->surface != NULL) { + cairo_surface_destroy(buffer->surface); + buffer->surface = NULL; + } + + /* Free image data (previously wrapped by Cairo surface */ + free(buffer->image); + buffer->image = NULL; + +} + +void guacenc_buffer_free(guacenc_buffer* buffer) { + + /* Ignore NULL buffer */ + if (buffer == NULL) + return; + + /* Free buffer and underlying image */ + guacenc_buffer_free_image(buffer); + free(buffer); + +} + +int guacenc_buffer_resize(guacenc_buffer* buffer, int width, int height) { + + /* Ignore requests which do not change the size */ + if (buffer->width == width && buffer->height == height) + return 0; + + /* Simply deallocate if new image has absolutely no pixels */ + if (width == 0 || height == 0) { + guacenc_buffer_free_image(buffer); + buffer->width = width; + buffer->height = height; + buffer->stride = 0; + return 0; + } + + /* Allocate data for new image */ + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + unsigned char* image = calloc(1, stride*height); + + /* Wrap data in surface */ + cairo_surface_t* surface = cairo_image_surface_create_for_data(image, + CAIRO_FORMAT_ARGB32, width, height, stride); + + /* Obtain graphics context of new surface */ + cairo_t* cairo = cairo_create(surface); + + /* Copy old surface, if defined */ + if (buffer->surface != NULL) { + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(cairo, buffer->surface, 0, 0); + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); + cairo_paint(cairo); + } + + /* Update properties */ + buffer->width = width; + buffer->height = height; + buffer->stride = stride; + + /* Replace old image */ + guacenc_buffer_free_image(buffer); + buffer->image = image; + buffer->surface = surface; + buffer->cairo = cairo; + + return 0; + +} + +int guacenc_buffer_fit(guacenc_buffer* buffer, int x, int y) { + + /* Increase width to fit X (if necessary) */ + int new_width = buffer->width; + if (new_width < x+1) + new_width = x+1; + + /* Increase height to fit Y (if necessary) */ + int new_height = buffer->height; + if (new_height < y+1) + new_height = y+1; + + /* Resize buffer if size needs to change to fit X/Y coordinate */ + if (new_width != buffer->width || new_height != buffer->height) + return guacenc_buffer_resize(buffer, new_width, new_height); + + /* No change necessary */ + return 0; + +} + +int guacenc_buffer_copy(guacenc_buffer* dst, guacenc_buffer* src) { + + /* Resize destination to exactly fit source */ + if (guacenc_buffer_resize(dst, src->width, src->height)) + return 1; + + /* Copy surface contents identically */ + if (src->surface != NULL) { + + /* Destination must be non-NULL as its size is that of the source */ + assert(dst->cairo != NULL); + + /* Reset state of destination */ + cairo_t* cairo = dst->cairo; + cairo_reset_clip(cairo); + + /* Overwrite destination with contents of source */ + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(cairo, src->surface, 0, 0); + cairo_paint(cairo); + + /* Reset operator of destination to default */ + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); + + } + + return 0; + +} + diff --git a/src/guacenc/buffer.h b/src/guacenc/buffer.h new file mode 100644 index 00000000..caf11e56 --- /dev/null +++ b/src/guacenc/buffer.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_BUFFER_H +#define GUACENC_BUFFER_H + +#include "config.h" + +#include + +#include + +/** + * The image and size storage for either a buffer (a Guacamole layer with a + * negative index) or a layer (a Guacamole layer with a non-negative index). + */ +typedef struct guacenc_buffer { + + /** + * Whether this buffer should be automatically resized to fit any draw + * operation. + */ + bool autosize; + + /** + * The width of this buffer or layer, in pixels. + */ + int width; + + /** + * The height of this buffer or layer, in pixels. + */ + int height; + + /** + * The number of bytes in each row of image data. + */ + int stride; + + /** + * The underlying image data of this surface. If the width or height of + * this surface are 0, this will be NULL. + */ + unsigned char* image; + + /** + * The Cairo surface wrapping the underlying image data of this surface. If + * the width or height of this surface are 0, this will be NULL. + */ + cairo_surface_t* surface; + + /** + * The current graphics context of the Cairo surface. If the width or + * height of this surface are 0, this will be NULL. + */ + cairo_t* cairo; + +} guacenc_buffer; + +/** + * Allocates and initializes a new buffer object. This allocation is + * independent of the Guacamole video encoder display; the allocated + * guacenc_buffer will not automatically be associated with the active display. + * + * @return + * A newly-allocated and initialized guacenc_buffer, or NULL if allocation + * fails. + */ +guacenc_buffer* guacenc_buffer_alloc(); + +/** + * Frees all memory associated with the given buffer object. If the buffer + * provided is NULL, this function has no effect. + * + * @param buffer + * The buffer to free, which may be NULL. + */ +void guacenc_buffer_free(guacenc_buffer* buffer); + +/** + * Resizes the given buffer to the given dimensions, allocating or freeing + * memory as necessary, and updating the buffer's width, height, and stride + * properties. + * + * @param buffer + * The buffer to resize. + * + * @param width + * The new width of the buffer, in pixels. + * + * @param height + * The new height of the buffer, in pixels. + * + * @return + * Zero if the resize operation is successful, non-zero on error. + */ +int guacenc_buffer_resize(guacenc_buffer* buffer, int width, int height); + +/** + * Resizes the given buffer as necessary to contain at the given X/Y + * coordinate, allocating or freeing memory as necessary, and updating the + * buffer's width, height, and stride properties. If the buffer already + * contains the given coordinate, this function has no effect. + * + * @param buffer + * The buffer to resize. + * + * @param x + * The X coordinate to ensure is within the buffer. + * + * @param y + * The Y coordinate to ensure is within the buffer. + * + * @return + * Zero if the resize operation is successful or no resize was performed, + * non-zero if the resize operation failed. + */ +int guacenc_buffer_fit(guacenc_buffer* buffer, int x, int y); + +/** + * Copies the entire contents of the given source buffer to the destination + * buffer, ignoring the current contents of the destination. The destination + * buffer's contents are entirely replaced. + * + * @param dst + * The destination buffer whose contents should be replaced. + * + * @param src + * The source buffer whose contents should replace those of the destination + * buffer. + * + * @return + * Zero if the copy operation was successful, non-zero on failure. + */ +int guacenc_buffer_copy(guacenc_buffer* dst, guacenc_buffer* src); + +#endif + diff --git a/src/guacenc/display-buffers.c b/src/guacenc/display-buffers.c new file mode 100644 index 00000000..5bd68d35 --- /dev/null +++ b/src/guacenc/display-buffers.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "buffer.h" +#include "layer.h" +#include "log.h" + +#include + +#include + +guacenc_buffer* guacenc_display_get_buffer(guacenc_display* display, + int index) { + + /* Transform index to buffer space */ + int internal_index = -index - 1; + + /* Do not lookup / allocate if index is invalid */ + if (internal_index < 0 || internal_index > GUACENC_DISPLAY_MAX_BUFFERS) { + guacenc_log(GUAC_LOG_WARNING, "Buffer index out of bounds: %i", index); + return NULL; + } + + /* Lookup buffer, allocating a new buffer if necessary */ + guacenc_buffer* buffer = display->buffers[internal_index]; + if (buffer == NULL) { + + /* Attempt to allocate buffer */ + buffer = guacenc_buffer_alloc(); + if (buffer == NULL) { + guacenc_log(GUAC_LOG_WARNING, "Buffer allocation failed"); + return NULL; + } + + /* All non-layer buffers must autosize */ + buffer->autosize = true; + + /* Store buffer within display for future retrieval / management */ + display->buffers[internal_index] = buffer; + + } + + return buffer; + +} + +int guacenc_display_free_buffer(guacenc_display* display, + int index) { + + /* Transform index to buffer space */ + int internal_index = -index - 1; + + /* Do not lookup / free if index is invalid */ + if (internal_index < 0 || internal_index > GUACENC_DISPLAY_MAX_BUFFERS) { + guacenc_log(GUAC_LOG_WARNING, "Buffer index out of bounds: %i", index); + return 1; + } + + /* Free buffer (if allocated) */ + guacenc_buffer_free(display->buffers[internal_index]); + + /* Mark buffer as freed */ + display->buffers[internal_index] = NULL; + + return 0; + +} + +guacenc_buffer* guacenc_display_get_related_buffer(guacenc_display* display, + int index) { + + /* Retrieve underlying buffer of layer if a layer is requested */ + if (index >= 0) { + + /* Retrieve / allocate layer (if possible */ + guacenc_layer* layer = guacenc_display_get_layer(display, index); + if (layer == NULL) + return NULL; + + /* Return underlying buffer */ + return layer->buffer; + + } + + /* Otherwise retrieve buffer directly */ + return guacenc_display_get_buffer(display, index); + +} + diff --git a/src/guacenc/display-flatten.c b/src/guacenc/display-flatten.c new file mode 100644 index 00000000..e49bc704 --- /dev/null +++ b/src/guacenc/display-flatten.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "layer.h" + +#include + +#include +#include + +/** + * The Guacamole video encoder display related to the current qsort() + * operation. As qsort() does not provide a means of passing arbitrary data to + * the comparitor, this value must be set prior to invoking qsort() with + * guacenc_display_layer_comparator. + */ +guacenc_display* __qsort_display; + +/** + * Comparator which orders layer pointers such that (1) NULL pointers are last, + * (2) layers with the same parent_index are adjacent, and (3) layers with the + * same parent_index are ordered by Z. + * + * @see qsort() + */ +static int guacenc_display_layer_comparator(const void* a, const void* b) { + + guacenc_layer* layer_a = *((guacenc_layer**) a); + guacenc_layer* layer_b = *((guacenc_layer**) b); + + /* If a is NULL, sort it to bottom */ + if (layer_a == NULL) { + + /* ... unless b is also NULL, in which case they are equal */ + if (layer_b == NULL) + return 0; + + return 1; + } + + /* If b is NULL (and a is not NULL), sort it to bottom */ + if (layer_b == NULL) + return -1; + + /* Order such that the deepest layers are first */ + int a_depth = guacenc_display_get_depth(__qsort_display, layer_a); + int b_depth = guacenc_display_get_depth(__qsort_display, layer_b); + if (b_depth != a_depth) + return b_depth - a_depth; + + /* Order such that sibling layers are adjacent */ + if (layer_b->parent_index != layer_a->parent_index) + return layer_b->parent_index - layer_a->parent_index; + + /* Order sibling layers according to descending Z */ + return layer_b->z - layer_a->z; + +} + +int guacenc_display_flatten(guacenc_display* display) { + + int i; + guacenc_layer* render_order[GUACENC_DISPLAY_MAX_LAYERS]; + + /* Copy list of layers within display */ + memcpy(render_order, display->layers, sizeof(render_order)); + + /* Sort layers by depth, parent, and Z */ + __qsort_display = display; + qsort(render_order, GUACENC_DISPLAY_MAX_LAYERS, sizeof(guacenc_layer*), + guacenc_display_layer_comparator); + + /* Reset layer frame buffers */ + for (i = 0; i < GUACENC_DISPLAY_MAX_LAYERS; i++) { + + /* Pull current layer, ignoring unallocated layers */ + guacenc_layer* layer = render_order[i]; + if (layer == NULL) + continue; + + /* Get source buffer and destination frame buffer */ + guacenc_buffer* buffer = layer->buffer; + guacenc_buffer* frame = layer->frame; + + /* Reset frame contents */ + guacenc_buffer_copy(frame, buffer); + + } + + /* Render each layer, in order */ + for (i = 0; i < GUACENC_DISPLAY_MAX_LAYERS; i++) { + + /* Pull current layer, ignoring unallocated layers */ + guacenc_layer* layer = render_order[i]; + if (layer == NULL) + continue; + + /* Skip fully-transparent layers */ + if (layer->opacity == 0) + continue; + + /* Ignore layers without a parent */ + int parent_index = layer->parent_index; + if (parent_index == GUACENC_LAYER_NO_PARENT) + continue; + + /* Retrieve parent layer, ignoring layers with invalid parents */ + guacenc_layer* parent = guacenc_display_get_layer(display, parent_index); + if (parent == NULL) + continue; + + /* Get source and destination frame buffer */ + guacenc_buffer* src = layer->frame; + guacenc_buffer* dst = parent->frame; + + /* Ignore layers with empty buffers */ + cairo_surface_t* surface = src->surface; + if (surface == NULL) + continue; + + /* Ignore if parent has no pixels */ + cairo_t* cairo = dst->cairo; + if (cairo == NULL) + continue; + + /* Render buffer to layer */ + cairo_reset_clip(cairo); + cairo_rectangle(cairo, layer->x, layer->y, src->width, src->height); + cairo_clip(cairo); + + cairo_set_source_surface(cairo, surface, layer->x, layer->y); + cairo_paint_with_alpha(cairo, layer->opacity / 255.0); + + } + + return 0; + +} + diff --git a/src/guacenc/display-image-streams.c b/src/guacenc/display-image-streams.c new file mode 100644 index 00000000..46e4b867 --- /dev/null +++ b/src/guacenc/display-image-streams.c @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "image-stream.h" +#include "log.h" + +#include + +#include + +int guacenc_display_create_image_stream(guacenc_display* display, int index, + int mask, int layer_index, const char* mimetype, int x, int y) { + + /* Do not lookup / allocate if index is invalid */ + if (index < 0 || index > GUACENC_DISPLAY_MAX_STREAMS) { + guacenc_log(GUAC_LOG_WARNING, "Stream index out of bounds: %i", index); + return 1; + } + + /* Free existing stream (if any) */ + guacenc_image_stream_free(display->image_streams[index]); + + /* Associate new stream */ + guacenc_image_stream* stream = display->image_streams[index] = + guacenc_image_stream_alloc(mask, layer_index, mimetype, x, y); + + /* Return zero only if stream is not NULL */ + return stream == NULL; + +} + +guacenc_image_stream* guacenc_display_get_image_stream( + guacenc_display* display, int index) { + + /* Do not lookup / allocate if index is invalid */ + if (index < 0 || index > GUACENC_DISPLAY_MAX_STREAMS) { + guacenc_log(GUAC_LOG_WARNING, "Stream index out of bounds: %i", index); + return NULL; + } + + /* Return existing stream (if any) */ + return display->image_streams[index]; + +} + +int guacenc_display_free_image_stream(guacenc_display* display, int index) { + + /* Do not lookup / allocate if index is invalid */ + if (index < 0 || index > GUACENC_DISPLAY_MAX_STREAMS) { + guacenc_log(GUAC_LOG_WARNING, "Stream index out of bounds: %i", index); + return 1; + } + + /* Free stream (if allocated) */ + guacenc_image_stream_free(display->image_streams[index]); + + /* Mark stream as freed */ + display->image_streams[index] = NULL; + + return 0; + +} + diff --git a/src/guacenc/display-layers.c b/src/guacenc/display-layers.c new file mode 100644 index 00000000..e0406f8b --- /dev/null +++ b/src/guacenc/display-layers.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "layer.h" +#include "log.h" + +#include + +#include + +guacenc_layer* guacenc_display_get_layer(guacenc_display* display, + int index) { + + /* Do not lookup / allocate if index is invalid */ + if (index < 0 || index > GUACENC_DISPLAY_MAX_LAYERS) { + guacenc_log(GUAC_LOG_WARNING, "Layer index out of bounds: %i", index); + return NULL; + } + + /* Lookup layer, allocating a new layer if necessary */ + guacenc_layer* layer = display->layers[index]; + if (layer == NULL) { + + /* Attempt to allocate layer */ + layer = guacenc_layer_alloc(); + if (layer == NULL) { + guacenc_log(GUAC_LOG_WARNING, "Layer allocation failed"); + return NULL; + } + + /* The default layer has no parent */ + if (index == 0) + layer->parent_index = GUACENC_LAYER_NO_PARENT; + + /* Store layer within display for future retrieval / management */ + display->layers[index] = layer; + + } + + return layer; + +} + +int guacenc_display_get_depth(guacenc_display* display, guacenc_layer* layer) { + + /* Non-existent layers have a depth of 0 */ + if (layer == NULL) + return 0; + + /* Layers with no parent have a depth of 0 */ + if (layer->parent_index == GUACENC_LAYER_NO_PARENT) + return 0; + + /* Retrieve parent layer */ + guacenc_layer* parent = + guacenc_display_get_layer(display, layer->parent_index); + + /* Current layer depth is the depth of the parent + 1 */ + return guacenc_display_get_depth(display, parent) + 1; + +} + +int guacenc_display_free_layer(guacenc_display* display, + int index) { + + /* Do not lookup / free if index is invalid */ + if (index < 0 || index > GUACENC_DISPLAY_MAX_LAYERS) { + guacenc_log(GUAC_LOG_WARNING, "Layer index out of bounds: %i", index); + return 1; + } + + /* Free layer (if allocated) */ + guacenc_layer_free(display->layers[index]); + + /* Mark layer as freed */ + display->layers[index] = NULL; + + return 0; + +} + diff --git a/src/guacenc/display-sync.c b/src/guacenc/display-sync.c new file mode 100644 index 00000000..e365e7af --- /dev/null +++ b/src/guacenc/display-sync.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "layer.h" +#include "log.h" +#include "video.h" + +#include +#include + +#include +#include + +int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) { + + /* Verify timestamp is not decreasing */ + if (timestamp < display->last_sync) { + guacenc_log(GUAC_LOG_WARNING, "Decreasing sync timestamp"); + return 1; + } + + /* Update timestamp of display */ + display->last_sync = timestamp; + + /* Flatten display to default layer */ + if (guacenc_display_flatten(display)) + return 1; + + /* Retrieve default layer (guaranteed to not be NULL) */ + guacenc_layer* def_layer = guacenc_display_get_layer(display, 0); + assert(def_layer != NULL); + + /* Update video timeline */ + if (guacenc_video_advance_timeline(display->output, timestamp)) + return 1; + + /* Prepare frame for write upon next flush */ + guacenc_video_prepare_frame(display->output, def_layer->frame); + return 0; + +} + diff --git a/src/guacenc/display.c b/src/guacenc/display.c new file mode 100644 index 00000000..672bb261 --- /dev/null +++ b/src/guacenc/display.c @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "video.h" + +#include + +#include + +cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask) { + + /* Translate Guacamole channel mask into Cairo operator */ + switch (mask) { + + /* Source */ + case GUAC_COMP_SRC: + return CAIRO_OPERATOR_SOURCE; + + /* Over */ + case GUAC_COMP_OVER: + return CAIRO_OPERATOR_OVER; + + /* In */ + case GUAC_COMP_IN: + return CAIRO_OPERATOR_IN; + + /* Out */ + case GUAC_COMP_OUT: + return CAIRO_OPERATOR_OUT; + + /* Atop */ + case GUAC_COMP_ATOP: + return CAIRO_OPERATOR_ATOP; + + /* Over (source/destination reversed) */ + case GUAC_COMP_ROVER: + return CAIRO_OPERATOR_DEST_OVER; + + /* In (source/destination reversed) */ + case GUAC_COMP_RIN: + return CAIRO_OPERATOR_DEST_IN; + + /* Out (source/destination reversed) */ + case GUAC_COMP_ROUT: + return CAIRO_OPERATOR_DEST_OUT; + + /* Atop (source/destination reversed) */ + case GUAC_COMP_RATOP: + return CAIRO_OPERATOR_DEST_ATOP; + + /* XOR */ + case GUAC_COMP_XOR: + return CAIRO_OPERATOR_XOR; + + /* Additive */ + case GUAC_COMP_PLUS: + return CAIRO_OPERATOR_ADD; + + /* If unrecognized, just default to CAIRO_OPERATOR_OVER */ + default: + return CAIRO_OPERATOR_OVER; + + } + +} + +guacenc_display* guacenc_display_alloc(const char* path, const char* codec, + int width, int height, int bitrate) { + + /* Prepare video encoding */ + guacenc_video* video = guacenc_video_alloc(path, codec, width, height, bitrate); + if (video == NULL) + return NULL; + + /* Allocate display */ + guacenc_display* display = + (guacenc_display*) calloc(1, sizeof(guacenc_display)); + + /* Associate display with video output */ + display->output = video; + + return display; + +} + +int guacenc_display_free(guacenc_display* display) { + + int i; + + /* Ignore NULL display */ + if (display == NULL) + return 0; + + /* Finalize video */ + int retval = guacenc_video_free(display->output); + + /* Free all buffers */ + for (i = 0; i < GUACENC_DISPLAY_MAX_BUFFERS; i++) + guacenc_buffer_free(display->buffers[i]); + + /* Free all layers */ + for (i = 0; i < GUACENC_DISPLAY_MAX_LAYERS; i++) + guacenc_layer_free(display->layers[i]); + + /* Free all streams */ + for (i = 0; i < GUACENC_DISPLAY_MAX_STREAMS; i++) + guacenc_image_stream_free(display->image_streams[i]); + + free(display); + return retval; + +} + diff --git a/src/guacenc/display.h b/src/guacenc/display.h new file mode 100644 index 00000000..266c213b --- /dev/null +++ b/src/guacenc/display.h @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_DISPLAY_H +#define GUACENC_DISPLAY_H + +#include "config.h" +#include "buffer.h" +#include "image-stream.h" +#include "layer.h" +#include "video.h" + +#include +#include + +/** + * The maximum number of buffers that the Guacamole video encoder will handle + * within a single Guacamole protocol dump. + */ +#define GUACENC_DISPLAY_MAX_BUFFERS 4096 + +/** + * The maximum number of layers that the Guacamole video encoder will handle + * within a single Guacamole protocol dump. + */ +#define GUACENC_DISPLAY_MAX_LAYERS 64 + +/** + * The maximum number of streams that the Guacamole video encoder will handle + * within a single Guacamole protocol dump. + */ +#define GUACENC_DISPLAY_MAX_STREAMS 64 + +/** + * The current state of the Guacamole video encoder's internal display. + */ +typedef struct guacenc_display { + + /** + * All currently-allocated buffers. The index of the buffer corresponds to + * its position within this array, where -1 is the 0th entry. If a buffer + * has not yet been allocated, or a buffer has been freed (due to a + * "dispose" instruction), its entry here will be NULL. + */ + guacenc_buffer* buffers[GUACENC_DISPLAY_MAX_BUFFERS]; + + /** + * All currently-allocated layers. The index of the layer corresponds to + * its position within this array. If a layer has not yet been allocated, + * or a layer has been freed (due to a "dispose" instruction), its entry + * here will be NULL. + */ + guacenc_layer* layers[GUACENC_DISPLAY_MAX_LAYERS]; + + /** + * All currently-allocated image streams. The index of the stream + * corresponds to its position within this array. If a stream has not yet + * been allocated, or a stream has been freed (due to an "end" + * instruction), its entry here will be NULL. + */ + guacenc_image_stream* image_streams[GUACENC_DISPLAY_MAX_STREAMS]; + + /** + * The timestamp of the last sync instruction handled, or 0 if no sync has + * yet been read. + */ + guac_timestamp last_sync; + + /** + * The video that this display is recording to. + */ + guacenc_video* output; + +} guacenc_display; + +/** + * Handles a received "sync" instruction having the given timestamp, flushing + * the current display to the in-progress video encoding. + * + * @param display + * The display to flush to the video encoding as a new frame. + * + * @param timestamp + * The timestamp of the new frame, as dictated by the "sync" instruction + * sent at the end of the frame. + * + * @return + * Zero if the frame was successfully written, non-zero if an error occurs. + */ +int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp); + +/** + * Flattens the given display, rendering all child layers to the frame buffers + * of their parent layers. The frame buffer of the default layer of the display + * will thus contain the flattened, composited rendering of the entire display + * state after this function succeeds. The contents of the frame buffers of + * each layer are replaced by this function. + * + * @param display + * The display to flatten. + * + * @return + * Zero if the flatten operation succeeds, non-zero if an error occurs + * preventing proper rendering. + */ +int guacenc_display_flatten(guacenc_display* display); + +/** + * Allocates a new Guacamole video encoder display. This display serves as the + * representation of encoding state, as well as the state of the Guacamole + * display as instructions are read and handled. + * + * @param path + * The full path to the file in which encoded video should be written. + * + * @param codec + * The name of the codec to use for the video encoding, as defined by + * ffmpeg / libavcodec. + * + * @param width + * The width of the desired video, in pixels. + * + * @param height + * The height of the desired video, in pixels. + * + * @param bitrate + * The desired overall bitrate of the resulting encoded video, in bits per + * second. + * + * @return + * The newly-allocated Guacamole video encoder display, or NULL if the + * display could not be allocated. + */ +guacenc_display* guacenc_display_alloc(const char* path, const char* codec, + int width, int height, int bitrate); + +/** + * Frees all memory associated with the given Guacamole video encoder display, + * and finishes any underlying encoding process. If the given display is NULL, + * this function has no effect. + * + * @param display + * The Guacamole video encoder display to free, which may be NULL. + * + * @return + * Zero if the encoding process completed successfully, non-zero otherwise. + */ +int guacenc_display_free(guacenc_display* display); + +/** + * Returns the layer having the given index. A new layer will be allocated if + * necessary. If the layer having the given index already exists, it will be + * returned. + * + * @param display + * The Guacamole video encoder display to retrieve the layer from. + * + * @param index + * The index of the layer to retrieve. All valid layer indices are + * non-negative. + * + * @return + * The layer having the given index, or NULL if the index is invalid or + * a new layer cannot be allocated. + */ +guacenc_layer* guacenc_display_get_layer(guacenc_display* display, + int index); + +/** + * Returns the depth of a given layer in terms of parent layers. The layer + * depth is the number of layers above the given layer in hierarchy, where a + * layer without any parent (such as the default layer) has a depth of 0. + * + * @param layer + * The layer to check. + * + * @return + * The depth of the layer. + */ +int guacenc_display_get_depth(guacenc_display* display, guacenc_layer* layer); + +/** + * Frees all resources associated with the layer having the given index. If + * the layer has not been allocated, this function has no effect. + * + * @param display + * The Guacamole video encoder display associated with the layer being + * freed. + * + * @param index + * The index of the layer to free. All valid layer indices are + * non-negative. + * + * @return + * Zero if the layer was successfully freed or was not allocated, non-zero + * if the layer could not be freed as the index was invalid. + */ +int guacenc_display_free_layer(guacenc_display* display, int index); + +/** + * Returns the buffer having the given index. A new buffer will be allocated if + * necessary. If the buffer having the given index already exists, it will be + * returned. + * + * @param display + * The Guacamole video encoder display to retrieve the buffer from. + * + * @param index + * The index of the buffer to retrieve. All valid buffer indices are + * negative. + * + * @return + * The buffer having the given index, or NULL if the index is invalid or + * a new buffer cannot be allocated. + */ +guacenc_buffer* guacenc_display_get_buffer(guacenc_display* display, + int index); + +/** + * Frees all resources associated with the buffer having the given index. If + * the buffer has not been allocated, this function has no effect. + * + * @param display + * The Guacamole video encoder display associated with the buffer being + * freed. + * + * @param index + * The index of the buffer to free. All valid buffer indices are negative. + * + * @return + * Zero if the buffer was successfully freed or was not allocated, non-zero + * if the buffer could not be freed as the index was invalid. + */ +int guacenc_display_free_buffer(guacenc_display* display, int index); + +/** + * Returns the buffer associated with the layer or buffer having the given + * index. A new buffer or layer will be allocated if necessary. If the given + * index refers to a layer (is non-negative), the buffer underlying that layer + * will be returned. If the given index refers to a buffer (is negative), that + * buffer will be returned directly. + * + * @param display + * The Guacamole video encoder display to retrieve the buffer from. + * + * @param index + * The index of the buffer or layer whose associated buffer should be + * retrieved. + * + * @return + * The buffer associated with the buffer or layer having the given index, + * or NULL if the index is invalid. + */ +guacenc_buffer* guacenc_display_get_related_buffer(guacenc_display* display, + int index); + +/** + * Creates a new image stream having the given index. If the stream having the + * given index already exists, it will be freed and replaced. If the mimetype + * specified is not supported, the image stream will still be allocated but + * will have no associated decoder (blobs send to that stream will have no + * effect). + * + * @param display + * The Guacamole video encoder display to associate with the + * newly-created image stream. + * + * @param index + * The index of the stream to create. All valid stream indices are + * non-negative. + * + * @param mask + * The Guacamole protocol compositing operation (channel mask) to apply + * when drawing the image. + * + * @param layer_index + * The index of the layer or buffer that the image should be drawn to. + * + * @param mimetype + * The mimetype of the image data that will be received along this stream. + * + * @param x + * The X coordinate of the upper-left corner of the rectangle within the + * destination layer or buffer that the image should be drawn to. + * + * @param y + * The Y coordinate of the upper-left corner of the rectangle within the + * destination layer or buffer that the image should be drawn to. + * + * @return + * Zero if the image stream was successfully created, non-zero otherwise. + */ +int guacenc_display_create_image_stream(guacenc_display* display, int index, + int mask, int layer_index, const char* mimetype, int x, int y); + +/** + * Returns the stream having the given index. If no such stream exists, NULL + * will be returned. + * + * @param display + * The Guacamole video encoder display to retrieve the image stream from. + * + * @param index + * The index of the stream to retrieve. All valid stream indices are + * non-negative. + * + * @return + * The stream having the given index, or NULL if the index is invalid or + * a no such stream exists. + */ +guacenc_image_stream* guacenc_display_get_image_stream( + guacenc_display* display, int index); + +/** + * Frees all resources associated with the stream having the given index. If + * the stream has not been allocated, this function has no effect. + * + * @param display + * The Guacamole video encoder display associated with the image stream + * being freed. + * + * @param index + * The index of the stream to free. All valid stream indices are + * non-negative. + * + * @return + * Zero if the stream was successfully freed or was not allocated, non-zero + * if the stream could not be freed as the index was invalid. + */ +int guacenc_display_free_image_stream(guacenc_display* display, int index); + +/** + * Translates the given Guacamole protocol compositing mode (channel mask) to + * the corresponding Cairo composition operator. If no such operator exists, + * CAIRO_OPERATOR_OVER will be returned by default. + * + * @param mask + * The Guacamole protocol compositing mode (channel mask) to translate. + * + * @return + * The cairo_operator_t that corresponds to the given compositing mode + * (channel mask). CAIRO_OPERATOR_OVER will be returned by default if no + * such operator exists. + */ +cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask); + +#endif + diff --git a/src/guacenc/encode.c b/src/guacenc/encode.c new file mode 100644 index 00000000..115becba --- /dev/null +++ b/src/guacenc/encode.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "instructions.h" +#include "log.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/** + * Reads and handles all Guacamole instructions from the given guac_socket + * until end-of-stream is reached. + * + * @param display + * The current internal display of the Guacamole video encoder. + * + * @param path + * The name of the file being parsed (for logging purposes). This file + * must already be open and available through the given socket. + * + * @param socket + * The guac_socket through which instructions should be read. + * + * @return + * Zero on success, non-zero if parsing of Guacamole protocol data through + * the given socket fails. + */ +static int guacenc_read_instructions(guacenc_display* display, + const char* path, guac_socket* socket) { + + /* Obtain Guacamole protocol parser */ + guac_parser* parser = guac_parser_alloc(); + if (parser == NULL) + return 1; + + /* Continuously read and handle all instructions */ + while (!guac_parser_read(parser, socket, -1)) { + guacenc_handle_instruction(display, parser->opcode, + parser->argc, parser->argv); + } + + /* Fail on read/parse error */ + if (guac_error != GUAC_STATUS_CLOSED) { + guacenc_log(GUAC_LOG_ERROR, "%s: %s", + path, guac_status_string(guac_error)); + guac_parser_free(parser); + return 1; + } + + /* Parse complete */ + guac_parser_free(parser); + return 0; + +} + +int guacenc_encode(const char* path, const char* out_path, const char* codec, + int width, int height, int bitrate, bool force) { + + /* Open input file */ + int fd = open(path, O_RDONLY); + if (fd < 0) { + guacenc_log(GUAC_LOG_ERROR, "%s: %s", path, strerror(errno)); + return 1; + } + + /* Lock entire input file for reading by the current process */ + struct flock file_lock = { + .l_type = F_RDLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0, + .l_pid = getpid() + }; + + /* Abort if file cannot be locked for reading */ + if (!force && fcntl(fd, F_SETLK, &file_lock) == -1) { + + /* Warn if lock cannot be acquired */ + if (errno == EACCES || errno == EAGAIN) + guacenc_log(GUAC_LOG_WARNING, "Refusing to encode in-progress " + "recording \"%s\" (specify the -f option to override " + "this behavior).", path); + + /* Log an error if locking fails in an unexpected way */ + else + guacenc_log(GUAC_LOG_ERROR, "Cannot lock \"%s\" for reading: %s", + path, strerror(errno)); + + close(fd); + return 1; + } + + /* Allocate display for encoding process */ + guacenc_display* display = guacenc_display_alloc(out_path, codec, + width, height, bitrate); + if (display == NULL) { + close(fd); + return 1; + } + + /* Obtain guac_socket wrapping file descriptor */ + guac_socket* socket = guac_socket_open(fd); + if (socket == NULL) { + guacenc_log(GUAC_LOG_ERROR, "%s: %s", path, + guac_status_string(guac_error)); + close(fd); + guacenc_display_free(display); + return 1; + } + + guacenc_log(GUAC_LOG_INFO, "Encoding \"%s\" to \"%s\" ...", path, out_path); + + /* Attempt to read all instructions in the file */ + if (guacenc_read_instructions(display, path, socket)) { + guac_socket_free(socket); + guacenc_display_free(display); + return 1; + } + + /* Close input and finish encoding process */ + guac_socket_free(socket); + return guacenc_display_free(display); + +} + diff --git a/src/guacenc/encode.h b/src/guacenc/encode.h new file mode 100644 index 00000000..b80eeb00 --- /dev/null +++ b/src/guacenc/encode.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_ENCODE_H +#define GUACENC_ENCODE_H + +#include "config.h" + +#include + +/** + * Encodes the given Guacamole protocol dump as video. A read lock will be + * acquired on the input file to ensure that in-progress recordings are not + * encoded. This behavior can be overridden by specifying true for the force + * parameter. + * + * @param path + * The path to the file containing the raw Guacamole protocol dump. + * + * @param out_path + * The full path to the file in which encoded video should be written. + * + * @param codec + * The name of the codec to use for the video encoding, as defined by + * ffmpeg / libavcodec. + * + * @param width + * The width of the desired video, in pixels. + * + * @param height + * The height of the desired video, in pixels. + * + * @param bitrate + * The desired overall bitrate of the resulting encoded video, in bits per + * second. + * + * @param force + * Perform the encoding, even if the input file appears to be an + * in-progress recording (has an associated lock). + * + * @return + * Zero on success, non-zero if an error prevented successful encoding of + * the video. + */ +int guacenc_encode(const char* path, const char* out_path, const char* codec, + int width, int height, int bitrate, bool force); + +#endif + diff --git a/src/guacenc/ffmpeg-compat.h b/src/guacenc/ffmpeg-compat.h new file mode 100644 index 00000000..ba3213a5 --- /dev/null +++ b/src/guacenc/ffmpeg-compat.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_FFMPEG_COMPAT_H +#define GUACENC_FFMPEG_COMPAT_H + +#include "config.h" + +#include + +/* + * For a full list of FFmpeg API changes over the years, see: + * + * https://github.com/FFmpeg/FFmpeg/blob/master/doc/APIchanges + */ + +/* For libavcodec < 55.28.1: av_frame_*() was avcodec_*_frame(). */ +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) +#define av_frame_alloc avcodec_alloc_frame +#define av_frame_free avcodec_free_frame +#endif + +/* For libavcodec < 55.52.0: avcodec_free_context did not exist */ +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,52,0) +#define avcodec_free_context av_freep +#endif + +/* For libavcodec < 57.7.0: av_packet_unref() was av_free_packet() */ +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57,7,0) +#define av_packet_unref av_free_packet +#endif + +#endif + diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c new file mode 100644 index 00000000..f67b2084 --- /dev/null +++ b/src/guacenc/guacenc.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "encode.h" +#include "guacenc.h" +#include "log.h" +#include "parse.h" + +#include + +#include +#include +#include + +int main(int argc, char* argv[]) { + + int i; + + /* Load defaults */ + bool force = false; + int width = GUACENC_DEFAULT_WIDTH; + int height = GUACENC_DEFAULT_HEIGHT; + int bitrate = GUACENC_DEFAULT_BITRATE; + + /* Parse arguments */ + int opt; + while ((opt = getopt(argc, argv, "s:r:f")) != -1) { + + /* -s: Dimensions (WIDTHxHEIGHT) */ + if (opt == 's') { + if (guacenc_parse_dimensions(optarg, &width, &height)) { + guacenc_log(GUAC_LOG_ERROR, "Invalid dimensions."); + goto invalid_options; + } + } + + /* -r: Bitrate (bits per second) */ + else if (opt == 'r') { + if (guacenc_parse_int(optarg, &bitrate)) { + guacenc_log(GUAC_LOG_ERROR, "Invalid bitrate."); + goto invalid_options; + } + } + + /* -f: Force */ + else if (opt == 'f') + force = true; + + /* Invalid option */ + else { + goto invalid_options; + } + + } + + /* Log start */ + guacenc_log(GUAC_LOG_INFO, "Guacamole video encoder (guacenc) " + "version " VERSION); + + /* Prepare libavcodec */ + avcodec_register_all(); + + /* Track number of overall failures */ + int total_files = argc - optind; + int failures = 0; + + /* Abort if no files given */ + if (total_files <= 0) { + guacenc_log(GUAC_LOG_INFO, "No input files specified. Nothing to do."); + return 0; + } + + guacenc_log(GUAC_LOG_INFO, "%i input file(s) provided.", total_files); + + guacenc_log(GUAC_LOG_INFO, "Video will be encoded at %ix%i " + "and %i bps.", width, height, bitrate); + + /* Encode all input files */ + for (i = optind; i < argc; i++) { + + /* Get current filename */ + const char* path = argv[i]; + + /* Generate output filename */ + char out_path[4096]; + int len = snprintf(out_path, sizeof(out_path), "%s.m4v", path); + + /* Do not write if filename exceeds maximum length */ + if (len >= sizeof(out_path)) { + guacenc_log(GUAC_LOG_ERROR, "Cannot write output file for \"%s\": " + "Name too long", path); + continue; + } + + /* Attempt encoding, log granular success/failure at debug level */ + if (guacenc_encode(path, out_path, "mpeg4", + width, height, bitrate, force)) { + failures++; + guacenc_log(GUAC_LOG_DEBUG, + "%s was NOT successfully encoded.", path); + } + else + guacenc_log(GUAC_LOG_DEBUG, "%s was successfully encoded.", path); + + } + + /* Warn if at least one file failed */ + if (failures != 0) + guacenc_log(GUAC_LOG_WARNING, "Encoding failed for %i of %i file(s).", + failures, total_files); + + /* Notify of success */ + else + guacenc_log(GUAC_LOG_INFO, "All files encoded successfully."); + + /* Encoding complete */ + return 0; + + /* Display usage and exit with error if options are invalid */ +invalid_options: + + fprintf(stderr, "USAGE: %s" + " [-s WIDTHxHEIGHT]" + " [-r BITRATE]" + " [-f]" + " [FILE]...\n", argv[0]); + + return 1; + +} + diff --git a/src/guacenc/guacenc.h b/src/guacenc/guacenc.h new file mode 100644 index 00000000..e269cfdc --- /dev/null +++ b/src/guacenc/guacenc.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_H +#define GUACENC_H + +#include "config.h" + +#include + +/** + * The width of the output video, in pixels, if no other width is given on the + * command line. Note that different codecs will have different restrictions + * regarding legal widths. + */ +#define GUACENC_DEFAULT_WIDTH 640 + +/** + * The height of the output video, in pixels, if no other height is given on the + * command line. Note that different codecs will have different restrictions + * regarding legal heights. + */ +#define GUACENC_DEFAULT_HEIGHT 480 + +/** + * The desired bitrate of the output video, in bits per second, if no other + * bitrate is given on the command line. + */ +#define GUACENC_DEFAULT_BITRATE 2000000 + +/** + * The default log level below which no messages should be logged. + */ +#define GUACENC_DEFAULT_LOG_LEVEL GUAC_LOG_INFO + +#endif + diff --git a/src/guacenc/image-stream.c b/src/guacenc/image-stream.c new file mode 100644 index 00000000..81ec4d21 --- /dev/null +++ b/src/guacenc/image-stream.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "image-stream.h" +#include "jpeg.h" +#include "log.h" +#include "png.h" + +#ifdef ENABLE_WEBP +#include "webp.h" +#endif + +#include + +#include +#include + +guacenc_decoder_mapping guacenc_decoder_map[] = { + {"image/png", guacenc_png_decoder}, + {"image/jpeg", guacenc_jpeg_decoder}, +#ifdef ENABLE_WEBP + {"image/webp", guacenc_webp_decoder}, +#endif + {NULL, NULL} +}; + +guacenc_decoder* guacenc_get_decoder(const char* mimetype) { + + /* Search through mapping for the decoder having given mimetype */ + guacenc_decoder_mapping* current = guacenc_decoder_map; + while (current->mimetype != NULL) { + + /* Return decoder if mimetype matches */ + if (strcmp(current->mimetype, mimetype) == 0) + return current->decoder; + + /* Next candidate decoder */ + current++; + + } + + /* No such decoder */ + guacenc_log(GUAC_LOG_WARNING, "Support for \"%s\" not present", mimetype); + return NULL; + +} + +guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index, + const char* mimetype, int x, int y) { + + /* Allocate stream */ + guacenc_image_stream* stream = malloc(sizeof(guacenc_image_stream)); + if (stream == NULL) + return NULL; + + /* Init properties */ + stream->index = index; + stream->mask = mask; + stream->x = x; + stream->y = y; + + /* Associate with corresponding decoder */ + stream->decoder = guacenc_get_decoder(mimetype); + + /* Allocate initial buffer */ + stream->length = 0; + stream->max_length = GUACENC_IMAGE_STREAM_INITIAL_LENGTH; + stream->buffer = (unsigned char*) malloc(stream->max_length); + + return stream; + +} + +int guacenc_image_stream_receive(guacenc_image_stream* stream, + unsigned char* data, int length) { + + /* Allocate more space if necessary */ + if (stream->max_length - stream->length < length) { + + /* Calculate a reasonable new max length guaranteed to fit buffer */ + int new_max_length = stream->max_length * 2 + length; + + /* Attempt to resize buffer */ + unsigned char* new_buffer = + (unsigned char*) realloc(stream->buffer, new_max_length); + if (new_buffer == NULL) + return 1; + + /* Store updated buffer and size */ + stream->buffer = new_buffer; + stream->max_length = new_max_length; + + } + + /* Append data */ + memcpy(stream->buffer + stream->length, data, length); + stream->length += length; + return 0; + +} + +int guacenc_image_stream_end(guacenc_image_stream* stream, + guacenc_buffer* buffer) { + + /* If there is no decoder, simply return success */ + guacenc_decoder* decoder = stream->decoder; + if (decoder == NULL) + return 0; + + /* Decode received data to a Cairo surface */ + cairo_surface_t* surface = stream->decoder(stream->buffer, stream->length); + if (surface == NULL) + return 1; + + /* Get surface dimensions */ + int width = cairo_image_surface_get_width(surface); + int height = cairo_image_surface_get_height(surface); + + /* Expand the buffer as necessary to fit the draw operation */ + if (buffer->autosize) + guacenc_buffer_fit(buffer, stream->x + width, stream->y + height); + + /* Draw surface to buffer */ + if (buffer->cairo != NULL) { + cairo_set_source_surface(buffer->cairo, surface, stream->x, stream->y); + cairo_rectangle(buffer->cairo, stream->x, stream->y, width, height); + cairo_fill(buffer->cairo); + } + + cairo_surface_destroy(surface); + return 0; + +} + +int guacenc_image_stream_free(guacenc_image_stream* stream) { + + /* Ignore NULL streams */ + if (stream == NULL) + return 0; + + /* Free image buffer */ + free(stream->buffer); + + /* Free actual stream */ + free(stream); + return 0; + +} + diff --git a/src/guacenc/image-stream.h b/src/guacenc/image-stream.h new file mode 100644 index 00000000..888b15ce --- /dev/null +++ b/src/guacenc/image-stream.h @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_IMAGE_STREAM_H +#define GUACENC_IMAGE_STREAM_H + +#include "config.h" +#include "buffer.h" + +#include + +/** + * The initial number of bytes to allocate for the image data buffer. If this + * buffer is not sufficiently large, it will be dynamically reallocated as it + * grows. + */ +#define GUACENC_IMAGE_STREAM_INITIAL_LENGTH 4096 + +/** + * Callback function which is provided raw, encoded image data of the given + * length. The function is expected to return a new Cairo surface which will + * later (by guacenc) be freed via cairo_surface_destroy(). + * + * @param data + * The raw encoded image data that this function must decode. + * + * @param length + * The length of the image data, in bytes. + * + * @return + * A newly-allocated Cairo surface containing the decoded image, or NULL + * or decoding fails. + */ +typedef cairo_surface_t* guacenc_decoder(unsigned char* data, int length); + +/** + * The current state of an allocated Guacamole image stream. + */ +typedef struct guacenc_image_stream { + + /** + * The index of the destination layer or buffer. + */ + int index; + + /** + * The Guacamole protocol compositing operation (channel mask) to apply + * when drawing the image. + */ + int mask; + + /** + * The X coordinate of the upper-left corner of the rectangle within the + * destination layer or buffer that the decoded image should be drawn to. + */ + int x; + + /** + * The Y coordinate of the upper-left corner of the rectangle within the + * destination layer or buffer that the decoded image should be drawn to. + */ + int y; + + /** + * Buffer of image data which will be built up over time as chunks are + * received via "blob" instructions. This will ultimately be passed in its + * entirety to the decoder function. + */ + unsigned char* buffer; + + /** + * The number of bytes currently stored in the buffer. + */ + int length; + + /** + * The maximum number of bytes that can be stored in the current buffer + * before it must be reallocated. + */ + int max_length; + + /** + * The decoder to use when decoding the raw data received along this + * stream, or NULL if no such decoder exists. + */ + guacenc_decoder* decoder; + +} guacenc_image_stream; + +/** + * Mapping of image mimetype to corresponding decoder function. + */ +typedef struct guacenc_decoder_mapping { + + /** + * The mimetype of the image that the associated decoder can read. + */ + const char* mimetype; + + /** + * The decoder function to use when an image stream of the associated + * mimetype is received. + */ + guacenc_decoder* decoder; + +} guacenc_decoder_mapping; + +/** + * Array of all mimetype/decoder mappings for all supported image types, + * terminated by an entry with a NULL mimetype. + */ +extern guacenc_decoder_mapping guacenc_decoder_map[]; + +/** + * Returns the decoder associated with the given mimetype. If no such decoder + * exists, NULL is returned. + * + * @param mimetype + * The image mimetype to return the associated decoder of. + * + * @return + * The decoder associated with the given mimetype, or NULL if no such + * decoder exists. + */ +guacenc_decoder* guacenc_get_decoder(const char* mimetype); + +/** + * Allocates and initializes a new image stream. This allocation is independent + * of the Guacamole video encoder display; the allocated guacenc_image_stream + * will not automatically be associated with the active display, nor will the + * provided layer/buffer index be validated. + * + * @param mask + * The Guacamole protocol compositing operation (channel mask) to apply + * when drawing the image. + * + * @param index + * The index of the layer or buffer that the image should be drawn to. + * + * @param mimetype + * The mimetype of the image data that will be received along this stream. + * + * @param x + * The X coordinate of the upper-left corner of the rectangle within the + * destination layer or buffer that the image should be drawn to. + * + * @param y + * The Y coordinate of the upper-left corner of the rectangle within the + * destination layer or buffer that the image should be drawn to. + * + * @return + * A newly-allocated and initialized guacenc_image_stream, or NULL if + * allocation fails. + */ +guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index, + const char* mimetype, int x, int y); + +/** + * Appends newly-received data to the internal buffer of the given image + * stream, such that the entire received image can be fed to the decoder as one + * buffer once the stream ends. + * + * @param stream + * The image stream that received the data. + * + * @param data + * The chunk of data received along the image stream. + * + * @param length + * The length of the chunk of data received, in bytes. + * + * @return + * Zero if the given data was successfully appended to the in-progress + * image, non-zero if an error occurs. + */ +int guacenc_image_stream_receive(guacenc_image_stream* stream, + unsigned char* data, int length); + +/** + * Marks the end of the given image stream (no more data will be received) and + * invokes the associated decoder. The decoded image will be written to the + * given buffer as-is. If no decoder is associated with the given image stream, + * this function has no effect. Meta-information describing the image draw + * operation itself is pulled from the guacenc_image_stream, having been stored + * there when the image stream was created. + * + * @param stream + * The image stream that has ended. + * + * @param buffer + * The buffer that the decoded image should be written to. + * + * @return + * Zero if the image is written successfully, or non-zero if an error + * occurs. + */ +int guacenc_image_stream_end(guacenc_image_stream* stream, + guacenc_buffer* buffer); + +/** + * Frees the given image stream and all associated data. If the image stream + * has not yet ended (reached end-of-stream), no image will be drawn to the + * associated buffer or layer. + * + * @param stream + * The stream to free. + * + * @return + * Zero if freeing the stream succeeded, or non-zero if freeing the stream + * failed (for example, due to an error in the free handler of the + * decoder). + */ +int guacenc_image_stream_free(guacenc_image_stream* stream); + +#endif + diff --git a/src/guacenc/instruction-blob.c b/src/guacenc/instruction-blob.c new file mode 100644 index 00000000..9e8ad3f6 --- /dev/null +++ b/src/guacenc/instruction-blob.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "log.h" + +#include +#include + +#include + +int guacenc_handle_blob(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 2) { + guacenc_log(GUAC_LOG_WARNING, "\"blob\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int index = atoi(argv[0]); + char* data = argv[1]; + int length = guac_protocol_decode_base64(data); + + /* Retrieve image stream */ + guacenc_image_stream* stream = + guacenc_display_get_image_stream(display, index); + if (stream == NULL) + return 1; + + /* Send data to decoder within associated stream */ + return guacenc_image_stream_receive(stream, (unsigned char*) data, length); + +} + diff --git a/src/guacenc/instruction-cfill.c b/src/guacenc/instruction-cfill.c new file mode 100644 index 00000000..139df94d --- /dev/null +++ b/src/guacenc/instruction-cfill.c @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "log.h" + +#include + +#include + +int guacenc_handle_cfill(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 6) { + guacenc_log(GUAC_LOG_WARNING, "\"cfill\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int mask = atoi(argv[0]); + int index = atoi(argv[1]); + double r = atoi(argv[2]) / 255.0; + double g = atoi(argv[3]) / 255.0; + double b = atoi(argv[4]) / 255.0; + double a = atoi(argv[5]) / 255.0; + + /* Pull buffer of requested layer/buffer */ + guacenc_buffer* buffer = guacenc_display_get_related_buffer(display, index); + if (buffer == NULL) + return 1; + + /* Fill with RGBA color */ + if (buffer->cairo != NULL) { + cairo_set_operator(buffer->cairo, guacenc_display_cairo_operator(mask)); + cairo_set_source_rgba(buffer->cairo, r, g, b, a); + cairo_fill(buffer->cairo); + } + + return 0; + +} + diff --git a/src/guacenc/instruction-copy.c b/src/guacenc/instruction-copy.c new file mode 100644 index 00000000..c0abd91d --- /dev/null +++ b/src/guacenc/instruction-copy.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "log.h" + +#include + +#include + +int guacenc_handle_copy(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 9) { + guacenc_log(GUAC_LOG_WARNING, "\"copy\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int sindex = atoi(argv[0]); + int sx = atoi(argv[1]); + int sy = atoi(argv[2]); + int width = atoi(argv[3]); + int height = atoi(argv[4]); + int mask = atoi(argv[5]); + int dindex = atoi(argv[6]); + int dx = atoi(argv[7]); + int dy = atoi(argv[8]); + + /* Pull buffer of source layer/buffer */ + guacenc_buffer* src = guacenc_display_get_related_buffer(display, sindex); + if (src == NULL) + return 1; + + /* Pull buffer of destination layer/buffer */ + guacenc_buffer* dst = guacenc_display_get_related_buffer(display, dindex); + if (dst == NULL) + return 1; + + /* Expand the destination buffer as necessary to fit the draw operation */ + if (dst->autosize) + guacenc_buffer_fit(dst, dx + width, dy + height); + + /* Copy rectangle from source to destination */ + if (src->surface != NULL && dst->cairo != NULL) { + + /* If surfaces are different, no need to copy */ + cairo_surface_t* surface; + if (src != dst) + surface = src->surface; + + /* Otherwise, copy to a temporary surface */ + else { + + /* Create new surface to hold the source rect */ + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + width, height); + + /* Copy relevant rectangle from source surface */ + cairo_t* cairo = cairo_create(surface); + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(cairo, src->surface, -sx, -sy); + cairo_paint(cairo); + cairo_destroy(cairo); + + /* Source coordinates are now (0, 0) */ + sx = sy = 0; + + } + + /* Perform copy */ + cairo_set_operator(dst->cairo, guacenc_display_cairo_operator(mask)); + cairo_set_source_surface(dst->cairo, surface, dx - sx, dy - sy); + cairo_rectangle(dst->cairo, dx, dy, width, height); + cairo_fill(dst->cairo); + + /* Destroy temporary surface if it was created */ + if (surface != src->surface) + cairo_surface_destroy(surface); + + } + + return 0; + +} + diff --git a/src/guacenc/instruction-cursor.c b/src/guacenc/instruction-cursor.c new file mode 100644 index 00000000..0cd25d75 --- /dev/null +++ b/src/guacenc/instruction-cursor.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "log.h" + +#include + +#include + +int guacenc_handle_cursor(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 7) { + guacenc_log(GUAC_LOG_WARNING, "\"cursor\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int hotspot_x = atoi(argv[0]); + int hotspot_y = atoi(argv[1]); + int src_index = atoi(argv[2]); + int src_x = atoi(argv[3]); + int src_y = atoi(argv[4]); + int src_w = atoi(argv[5]); + int src_h = atoi(argv[6]); + + /* Nothing to do with cursor (yet) */ + guacenc_log(GUAC_LOG_DEBUG, "Ignoring cursor: hotspot (%i, %i) " + "src_layer=%i (%i, %i) %ix%i", hotspot_x, hotspot_y, + src_index, src_x, src_y, src_w, src_h); + + return 0; + +} + diff --git a/src/guacenc/instruction-dispose.c b/src/guacenc/instruction-dispose.c new file mode 100644 index 00000000..a6cd4203 --- /dev/null +++ b/src/guacenc/instruction-dispose.c @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "log.h" + +#include + +#include + +int guacenc_handle_dispose(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 1) { + guacenc_log(GUAC_LOG_WARNING, "\"dispose\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int index = atoi(argv[0]); + + /* If non-negative, dispose of layer */ + if (index >= 0) + return guacenc_display_free_layer(display, index); + + /* Otherwise, we're referring to a buffer */ + return guacenc_display_free_buffer(display, index); + +} + diff --git a/src/guacenc/instruction-end.c b/src/guacenc/instruction-end.c new file mode 100644 index 00000000..749ead2a --- /dev/null +++ b/src/guacenc/instruction-end.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "image-stream.h" +#include "log.h" + +#include + +#include + +int guacenc_handle_end(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 1) { + guacenc_log(GUAC_LOG_WARNING, "\"end\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int index = atoi(argv[0]); + + /* Retrieve image stream */ + guacenc_image_stream* stream = + guacenc_display_get_image_stream(display, index); + if (stream == NULL) + return 1; + + /* Retrieve destination buffer */ + guacenc_buffer* buffer = + guacenc_display_get_related_buffer(display, stream->index); + if (buffer == NULL) + return 1; + + /* End image stream, drawing final image to the buffer */ + return guacenc_image_stream_end(stream, buffer); + +} + diff --git a/src/guacenc/instruction-img.c b/src/guacenc/instruction-img.c new file mode 100644 index 00000000..e7c07188 --- /dev/null +++ b/src/guacenc/instruction-img.c @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "log.h" + +#include + +#include + +int guacenc_handle_img(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 6) { + guacenc_log(GUAC_LOG_WARNING, "\"img\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int stream_index = atoi(argv[0]); + int mask = atoi(argv[1]); + int layer_index = atoi(argv[2]); + char* mimetype = argv[3]; + int x = atoi(argv[4]); + int y = atoi(argv[5]); + + /* Create requested stream */ + return guacenc_display_create_image_stream(display, stream_index, + mask, layer_index, mimetype, x, y); + +} + diff --git a/src/guacenc/instruction-move.c b/src/guacenc/instruction-move.c new file mode 100644 index 00000000..6123e344 --- /dev/null +++ b/src/guacenc/instruction-move.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "log.h" + +#include + +#include + +int guacenc_handle_move(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 5) { + guacenc_log(GUAC_LOG_WARNING, "\"move\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int layer_index = atoi(argv[0]); + int parent_index = atoi(argv[1]); + int x = atoi(argv[2]); + int y = atoi(argv[3]); + int z = atoi(argv[4]); + + /* Retrieve requested layer */ + guacenc_layer* layer = guacenc_display_get_layer(display, layer_index); + if (layer == NULL) + return 1; + + /* Validate parent layer */ + if (guacenc_display_get_layer(display, parent_index) == NULL) + return 1; + + /* Update layer properties */ + layer->parent_index = parent_index; + layer->x = x; + layer->y = y; + layer->z = z; + + return 0; + +} + diff --git a/src/guacenc/instruction-rect.c b/src/guacenc/instruction-rect.c new file mode 100644 index 00000000..6617ecb0 --- /dev/null +++ b/src/guacenc/instruction-rect.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "buffer.h" +#include "display.h" +#include "log.h" + +#include +#include + +#include + +int guacenc_handle_rect(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 5) { + guacenc_log(GUAC_LOG_WARNING, "\"rect\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int index = atoi(argv[0]); + int x = atoi(argv[1]); + int y = atoi(argv[2]); + int width = atoi(argv[3]); + int height = atoi(argv[4]); + + /* Pull buffer of requested layer/buffer */ + guacenc_buffer* buffer = guacenc_display_get_related_buffer(display, index); + if (buffer == NULL) + return 1; + + /* Expand the buffer as necessary to fit the draw operation */ + if (buffer->autosize) + guacenc_buffer_fit(buffer, x + width, y + height); + + /* Set path to rectangle */ + if (buffer->cairo != NULL) + cairo_rectangle(buffer->cairo, x, y, width, height); + + return 0; + +} + diff --git a/src/guacenc/instruction-shade.c b/src/guacenc/instruction-shade.c new file mode 100644 index 00000000..53826231 --- /dev/null +++ b/src/guacenc/instruction-shade.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "log.h" + +#include + +#include + +int guacenc_handle_shade(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 2) { + guacenc_log(GUAC_LOG_WARNING, "\"shade\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int index = atoi(argv[0]); + int opacity = atoi(argv[1]); + + /* Retrieve requested layer */ + guacenc_layer* layer = guacenc_display_get_layer(display, index); + if (layer == NULL) + return 1; + + /* Update layer properties */ + layer->opacity = opacity; + + return 0; + +} + diff --git a/src/guacenc/instruction-size.c b/src/guacenc/instruction-size.c new file mode 100644 index 00000000..f173d33f --- /dev/null +++ b/src/guacenc/instruction-size.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "log.h" + +#include + +#include + +int guacenc_handle_size(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 3) { + guacenc_log(GUAC_LOG_WARNING, "\"size\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int index = atoi(argv[0]); + int width = atoi(argv[1]); + int height = atoi(argv[2]); + + /* Retrieve requested layer */ + guacenc_layer* layer = guacenc_display_get_layer(display, index); + if (layer == NULL) + return 1; + + /* Resize layer */ + return guacenc_buffer_resize(layer->buffer, width, height); + +} + diff --git a/src/guacenc/instruction-sync.c b/src/guacenc/instruction-sync.c new file mode 100644 index 00000000..a2e40389 --- /dev/null +++ b/src/guacenc/instruction-sync.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "log.h" + +#include +#include + +#include +#include + +/** + * Parses a guac_timestamp from the given string. The string is assumed to + * consist solely of decimal digits with an optional leading minus sign. If the + * given string contains other characters, the behavior of this function is + * undefined. + * + * @param str + * The string to parse, which must contain only decimal digits and an + * optional leading minus sign. + * + * @return + * A guac_timestamp having the same value as the provided string. + */ +static guac_timestamp guacenc_parse_timestamp(const char* str) { + + int sign = 1; + int64_t num = 0; + + for (; *str != '\0'; str++) { + + /* Flip sign for each '-' encountered */ + if (*str == '-') + sign = -sign; + + /* If not '-', assume the character is a digit */ + else + num = num * 10 + (*str - '0'); + + } + + return (guac_timestamp) (num * sign); + +} + +int guacenc_handle_sync(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 1) { + guacenc_log(GUAC_LOG_WARNING, "\"sync\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + guac_timestamp timestamp = guacenc_parse_timestamp(argv[0]); + + /* Update timestamp / flush frame */ + return guacenc_display_sync(display, timestamp); + +} + diff --git a/src/guacenc/instruction-transfer.c b/src/guacenc/instruction-transfer.c new file mode 100644 index 00000000..c2508f62 --- /dev/null +++ b/src/guacenc/instruction-transfer.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "log.h" + +#include + +#include + +int guacenc_handle_transfer(guacenc_display* display, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 9) { + guacenc_log(GUAC_LOG_WARNING, "\"transform\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int src_index = atoi(argv[0]); + int src_x = atoi(argv[1]); + int src_y = atoi(argv[2]); + int src_w = atoi(argv[3]); + int src_h = atoi(argv[4]); + int function = atoi(argv[5]); + int dst_index = atoi(argv[6]); + int dst_x = atoi(argv[7]); + int dst_y = atoi(argv[8]); + + /* TODO: Unimplemented for now (rarely used) */ + guacenc_log(GUAC_LOG_DEBUG, "transform: src_layer=%i (%i, %i) %ix%i " + "function=0x%X dst_layer=%i (%i, %i)", src_index, src_x, src_y, + src_w, src_h, function, dst_index, dst_x, dst_y); + + return 0; + +} + diff --git a/src/guacenc/instructions.c b/src/guacenc/instructions.c new file mode 100644 index 00000000..ac9fbdf6 --- /dev/null +++ b/src/guacenc/instructions.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "display.h" +#include "instructions.h" +#include "log.h" + +#include + +#include + +guacenc_instruction_handler_mapping guacenc_instruction_handler_map[] = { + {"blob", guacenc_handle_blob}, + {"img", guacenc_handle_img}, + {"end", guacenc_handle_end}, + {"sync", guacenc_handle_sync}, + {"cursor", guacenc_handle_cursor}, + {"copy", guacenc_handle_copy}, + {"transfer", guacenc_handle_transfer}, + {"size", guacenc_handle_size}, + {"rect", guacenc_handle_rect}, + {"cfill", guacenc_handle_cfill}, + {"move", guacenc_handle_move}, + {"shade", guacenc_handle_shade}, + {"dispose", guacenc_handle_dispose}, + {NULL, NULL} +}; + +int guacenc_handle_instruction(guacenc_display* display, const char* opcode, + int argc, char** argv) { + + /* Search through mapping for instruction handler having given opcode */ + guacenc_instruction_handler_mapping* current = guacenc_instruction_handler_map; + while (current->opcode != NULL) { + + /* Invoke handler if opcode matches (if defined) */ + if (strcmp(current->opcode, opcode) == 0) { + + /* Invoke defined handler */ + guacenc_instruction_handler* handler = current->handler; + if (handler != NULL) + return handler(display, argc, argv); + + /* Log defined but unimplemented instructions */ + guacenc_log(GUAC_LOG_DEBUG, "\"%s\" not implemented", opcode); + return 0; + + } + + /* Next candidate handler */ + current++; + + } /* end opcode search */ + + /* Ignore any unknown instructions */ + return 0; + +} + diff --git a/src/guacenc/instructions.h b/src/guacenc/instructions.h new file mode 100644 index 00000000..c015b271 --- /dev/null +++ b/src/guacenc/instructions.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_INSTRUCTIONS_H +#define GUACENC_INSTRUCTIONS_H + +#include "config.h" +#include "display.h" + +/** + * A callback function which, when invoked, handles a particular Guacamole + * instruction. The opcode of the instruction is implied (as it is expected + * that there will be a 1:1 mapping of opcode to callback function), while the + * arguments for that instruction are included in the parameters given to the + * callback. + * + * @param display + * The current internal display of the Guacamole video encoder. + * + * @param argc + * The number of arguments (excluding opcode) passed to the instruction + * being handled by the callback. + * + * @param argv + * All arguments (excluding opcode) associated with the instruction being + * handled by the callback. + * + * @return + * Zero if the instruction was handled successfully, non-zero if an error + * occurs. + */ +typedef int guacenc_instruction_handler(guacenc_display* display, + int argc, char** argv); + +/** + * Mapping of instruction opcode to corresponding handler function. + */ +typedef struct guacenc_instruction_handler_mapping { + + /** + * The opcode of the instruction that the associated handler function + * should be invoked for. + */ + const char* opcode; + + /** + * The handler function to invoke whenever an instruction having the + * associated opcode is parsed. + */ + guacenc_instruction_handler* handler; + +} guacenc_instruction_handler_mapping; + +/** + * Array of all opcode/handler mappings for all supported opcodes, terminated + * by an entry with a NULL opcode. All opcodes not listed here can be safely + * ignored. + */ +extern guacenc_instruction_handler_mapping guacenc_instruction_handler_map[]; + +/** + * Handles the instruction having the given opcode and arguments, encoding the + * result to the in-progress video. + * + * @param display + * The current internal display of the Guacamole video encoder. + * + * @param opcode + * The opcode of the instruction being handled. + * + * @param argc + * The number of arguments (excluding opcode) passed to the instruction + * being handled by the callback. + * + * @param argv + * All arguments (excluding opcode) associated with the instruction being + * handled by the callback. + * + * @return + * Zero if the instruction was handled successfully, non-zero if an error + * occurs. + */ +int guacenc_handle_instruction(guacenc_display* display, + const char* opcode, int argc, char** argv); + +/** + * Handler for the Guacamole "blob" instruction. + */ +guacenc_instruction_handler guacenc_handle_blob; + +/** + * Handler for the Guacamole "img" instruction. + */ +guacenc_instruction_handler guacenc_handle_img; + +/** + * Handler for the Guacamole "end" instruction. + */ +guacenc_instruction_handler guacenc_handle_end; + +/** + * Handler for the Guacamole "sync" instruction. + */ +guacenc_instruction_handler guacenc_handle_sync; + +/** + * Handler for the Guacamole "cursor" instruction. + */ +guacenc_instruction_handler guacenc_handle_cursor; + +/** + * Handler for the Guacamole "copy" instruction. + */ +guacenc_instruction_handler guacenc_handle_copy; + +/** + * Handler for the Guacamole "transfer" instruction. + */ +guacenc_instruction_handler guacenc_handle_transfer; + +/** + * Handler for the Guacamole "size" instruction. + */ +guacenc_instruction_handler guacenc_handle_size; + +/** + * Handler for the Guacamole "rect" instruction. + */ +guacenc_instruction_handler guacenc_handle_rect; + +/** + * Handler for the Guacamole "cfill" instruction. + */ +guacenc_instruction_handler guacenc_handle_cfill; + +/** + * Handler for the Guacamole "move" instruction. + */ +guacenc_instruction_handler guacenc_handle_move; + +/** + * Handler for the Guacamole "shade" instruction. + */ +guacenc_instruction_handler guacenc_handle_shade; + +/** + * Handler for the Guacamole "dispose" instruction. + */ +guacenc_instruction_handler guacenc_handle_dispose; + +#endif + diff --git a/src/guacenc/jpeg.c b/src/guacenc/jpeg.c new file mode 100644 index 00000000..dfed2d13 --- /dev/null +++ b/src/guacenc/jpeg.c @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "jpeg.h" +#include "log.h" + +#include +#include + +#include +#include + +#include + +cairo_surface_t* guacenc_jpeg_decoder(unsigned char* data, int length) { + + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + + /* Create decompressor with standard error handling */ + jpeg_create_decompress(&cinfo); + cinfo.err = jpeg_std_error(&jerr); + + /* Read JPEG directly from memory buffer */ + jpeg_mem_src(&cinfo, data, length); + + /* Read and validate JPEG header */ + if (!jpeg_read_header(&cinfo, TRUE)) { + guacenc_log(GUAC_LOG_WARNING, "Invalid JPEG data"); + jpeg_destroy_decompress(&cinfo); + return NULL; + } + + /* Begin decompression */ + cinfo.out_color_space = JCS_EXT_BGRX; + jpeg_start_decompress(&cinfo); + + /* Pull JPEG dimensions from decompressor */ + int width = cinfo.output_width; + int height = cinfo.output_height; + + /* Create blank Cairo surface (no transparency in JPEG) */ + cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, + width, height); + + /* Pull underlying buffer and its stride */ + int stride = cairo_image_surface_get_stride(surface); + unsigned char* row = cairo_image_surface_get_data(surface); + + /* Read JPEG into surface */ + while (cinfo.output_scanline < height) { + unsigned char* buffers[1] = { row }; + jpeg_read_scanlines(&cinfo, buffers, 1); + row += stride; + } + + /* End decompression */ + jpeg_finish_decompress(&cinfo); + + /* Free decompressor */ + jpeg_destroy_decompress(&cinfo); + + /* JPEG was read successfully */ + return surface; + +} + diff --git a/src/guacenc/jpeg.h b/src/guacenc/jpeg.h new file mode 100644 index 00000000..142fbd12 --- /dev/null +++ b/src/guacenc/jpeg.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_JPEG_H +#define GUACENC_JPEG_H + +#include "config.h" +#include "image-stream.h" + +/** + * Decoder implementation which handles "image/jpeg" images. + */ +guacenc_decoder guacenc_jpeg_decoder; + +#endif + diff --git a/src/guacenc/layer.c b/src/guacenc/layer.c new file mode 100644 index 00000000..f6cd12b7 --- /dev/null +++ b/src/guacenc/layer.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "buffer.h" +#include "layer.h" + +#include + +guacenc_layer* guacenc_layer_alloc() { + + /* Allocate new layer */ + guacenc_layer* layer = (guacenc_layer*) calloc(1, sizeof(guacenc_layer)); + if (layer == NULL) + return NULL; + + /* Allocate associated buffer (width, height, and image storage) */ + layer->buffer = guacenc_buffer_alloc(); + if (layer->buffer == NULL) { + free(layer); + return NULL; + } + + /* Allocate buffer for frame rendering */ + layer->frame = guacenc_buffer_alloc(); + if (layer->frame== NULL) { + guacenc_buffer_free(layer->buffer); + free(layer); + return NULL; + } + + /* Layers default to fully opaque */ + layer->opacity = 0xFF; + + /* Default parented to default layer */ + layer->parent_index = 0; + + return layer; + +} + +void guacenc_layer_free(guacenc_layer* layer) { + + /* Ignore NULL layers */ + if (layer == NULL) + return; + + /* Free internal frame buffer */ + guacenc_buffer_free(layer->frame); + + /* Free underlying buffer */ + guacenc_buffer_free(layer->buffer); + + free(layer); + +} + diff --git a/src/guacenc/layer.h b/src/guacenc/layer.h new file mode 100644 index 00000000..d2a9d5a4 --- /dev/null +++ b/src/guacenc/layer.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_LAYER_H +#define GUACENC_LAYER_H + +#include "config.h" +#include "buffer.h" + +/** + * The value assigned to the parent_index property of a guacenc_layer if it has + * no parent. + */ +#define GUACENC_LAYER_NO_PARENT -1 + +/** + * A visible Guacamole layer. + */ +typedef struct guacenc_layer { + + /** + * The actual image contents of this layer, as well as this layer's size + * (width and height). + */ + guacenc_buffer* buffer; + + /** + * The index of the layer that contains this layer. If this layer is the + * default layer (and thus has no parent), this will be + * GUACENC_LAYER_NO_PARENT. + */ + int parent_index; + + /** + * The X coordinate of the upper-left corner of this layer within the + * Guacamole display. + */ + int x; + + /** + * The Y coordinate of the upper-left corner of this layer within the + * Guacamole display. + */ + int y; + + /** + * The relative stacking order of this layer with respect to other sibling + * layers. + */ + int z; + + /** + * The opacity of this layer, where 0 is completely transparent and 255 is + * completely opaque. + */ + int opacity; + + /** + * The internal buffer used by to record the state of this layer in the + * previous frame and to render additional frames. + */ + guacenc_buffer* frame; + +} guacenc_layer; + +/** + * Allocates and initializes a new layer object. This allocation is independent + * of the Guacamole video encoder display; the allocated guacenc_layer will not + * automatically be associated with the active display. + * + * @return + * A newly-allocated and initialized guacenc_layer, or NULL if allocation + * fails. + */ +guacenc_layer* guacenc_layer_alloc(); + +/** + * Frees all memory associated with the given layer object. If the layer + * provided is NULL, this function has no effect. + * + * @param layer + * The layer to free, which may be NULL. + */ +void guacenc_layer_free(guacenc_layer* layer); + +#endif + diff --git a/src/guacenc/log.c b/src/guacenc/log.c new file mode 100644 index 00000000..221c743c --- /dev/null +++ b/src/guacenc/log.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "guacenc.h" +#include "log.h" + +#include +#include + +#include +#include + +int guacenc_log_level = GUACENC_DEFAULT_LOG_LEVEL; + +void vguacenc_log(guac_client_log_level level, const char* format, + va_list args) { + + const char* priority_name; + char message[2048]; + + /* Don't bother if the log level is too high */ + if (level > guacenc_log_level) + return; + + /* Copy log message into buffer */ + vsnprintf(message, sizeof(message), format, args); + + /* Convert log level to human-readable name */ + switch (level) { + + /* Error log level */ + case GUAC_LOG_ERROR: + priority_name = "ERROR"; + break; + + /* Warning log level */ + case GUAC_LOG_WARNING: + priority_name = "WARNING"; + break; + + /* Informational log level */ + case GUAC_LOG_INFO: + priority_name = "INFO"; + break; + + /* Debug log level */ + case GUAC_LOG_DEBUG: + priority_name = "DEBUG"; + break; + + /* Any unknown/undefined log level */ + default: + priority_name = "UNKNOWN"; + break; + } + + /* Log to STDERR */ + fprintf(stderr, GUACENC_LOG_NAME ": %s: %s\n", priority_name, message); + +} + +void guacenc_log(guac_client_log_level level, const char* format, ...) { + va_list args; + va_start(args, format); + vguacenc_log(level, format, args); + va_end(args); +} + diff --git a/src/guacenc/log.h b/src/guacenc/log.h new file mode 100644 index 00000000..417e37c3 --- /dev/null +++ b/src/guacenc/log.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_LOG_H +#define GUACENC_LOG_H + +#include "config.h" + +#include + +#include + +/** + * The maximum level at which to log messages. All other messages will be + * dropped. + */ +extern int guacenc_log_level; + +/** + * The string to prepend to all log messages. + */ +#define GUACENC_LOG_NAME "guacenc" + +/** + * Writes a message to guacenc's logs. This function takes a format and + * va_list, similar to vprintf. + * + * @param level + * The level at which to log this message. + * + * @param format + * A printf-style format string to log. + * + * @param args + * The va_list containing the arguments to be used when filling the format + * string for printing. + */ +void vguacenc_log(guac_client_log_level level, const char* format, + va_list args); + +/** + * Writes a message to guacenc's logs. This function accepts parameters + * identically to printf. + * + * @param level + * The level at which to log this message. + * + * @param format + * A printf-style format string to log. + * + * @param ... + * Arguments to use when filling the format string for printing. + */ +void guacenc_log(guac_client_log_level level, const char* format, ...); + +#endif + diff --git a/src/guacenc/man/guacenc.1 b/src/guacenc/man/guacenc.1 new file mode 100644 index 00000000..bbef7189 --- /dev/null +++ b/src/guacenc/man/guacenc.1 @@ -0,0 +1,62 @@ +.TH guacenc 1 "12 Mar 2016" "version 0.9.9" "Guacamole" +. +.SH NAME +guacenc \- Guacamole video encoder +. +.SH SYNOPSIS +.B guacenc +[\fB-s\fR \fIWIDTH\fRx\fIHEIGHT\fR] +[\fB-r\fR \fIBITRATE\fR] +[\fB-f\fR] +[\fIFILE\fR]... +. +.SH DESCRIPTION +.B guacenc +is a video encoder which accepts Guacamole protocol dumps, such as those saved +when screen recording is enabled on a Guacamole connection, writing standard +video files as output. +.B guacenc +is essentially an implementation of a Guacamole client which accepts +its input from files instead of a network connection, and renders directly to +video instead of to the user's screen. +.P +Each \fIFILE\fR specified will be encoded as a raw MPEG-4 video stream to a new +file named \fIFILE\fR.m4v, encoded according to the other options specified. By +default, the output video will be \fI640\fRx\fI480\fR pixels, and will be saved +with a bitrate of \fI2000000\fR bits per second (2 Mbps). These defaults can be +overridden with the \fB-s\fR and \fB-r\fR options respectively. Existing files +will not be overwritten; the encoding process for any input file will be +aborted if it would result in overwriting an existing file. +.P +Guacamole acquires a write lock on recordings as they are being written. By +default, +.B guacenc +will check whether the each input file is locked and will refuse to read and +encode an input file if it appears to be an in-progress recording. This +behavior can be overridden by specifying the \fB-f\fR option. Encoding an +in-progress recording will still result in a valid video; the video will simply +cover the user's session only up to the current point in time. +. +.SH OPTIONS +.TP +\fB-s\fR \fIWIDTH\fRx\fIHEIGHT\fR +Changes the resolution of the video that +.B guacenc +renders. By default, this will be \fI640\fRx\fI480\fR. +.TP +\fB-r\fR \fIBITRATE\fR +Changes the bitrate that +.B guacenc +will use for the saved video. This is specified in bits per second. By default, +this will be \fI2000000\fR (2 Mbps). Higher values will result in larger but +higher-quality video files. Lower values will result in smaller but +lower-quality video files. +.TP +\fB-f\fR +Overrides the default behavior of +.B guacenc +such that input files will be encoded even if they appear to be recordings of +in-progress Guacamole sessions. +. +.SH AUTHOR +Written by Michael Jumper diff --git a/src/guacenc/parse.c b/src/guacenc/parse.c new file mode 100644 index 00000000..689ba94e --- /dev/null +++ b/src/guacenc/parse.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +int guacenc_parse_int(char* arg, int* i) { + + char* end; + + /* Parse string as an integer */ + errno = 0; + long int value = strtol(arg, &end, 10); + + /* Ignore number if invalid / non-positive */ + if (errno != 0 || value <= 0 || value > INT_MAX || *end != '\0') + return 1; + + /* Store value */ + *i = value; + + /* Parsing successful */ + return 0; + +} + +int guacenc_parse_dimensions(char* arg, int* width, int* height) { + + /* Locate the 'x' within the dimensions string */ + char* x = strchr(arg, 'x'); + if (x == NULL) + return 1; + + /* Replace 'x' with a null terminator */ + *x = '\0'; + + /* Parse width and height */ + int w, h; + if (guacenc_parse_int(arg, &w) || guacenc_parse_int(x+1, &h)) + return 1; + + /* Width and height are both valid */ + *width = w; + *height = h; + + return 0; + +} + diff --git a/src/guacenc/parse.h b/src/guacenc/parse.h new file mode 100644 index 00000000..09dc0df8 --- /dev/null +++ b/src/guacenc/parse.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_PARSE_H +#define GUACENC_PARSE_H + +#include "config.h" + +/** + * Parses a string into a single integer. Only positive integers are accepted. + * The input string may be modified during parsing. A value will be stored in + * the provided int pointer only if valid. + * + * @param arg + * The string to parse. + * + * @param i + * A pointer to the integer in which the parsed value of the given string + * should be stored. + * + * @return + * Zero if parsing was successful, non-zero if the provided string was + * invalid. + */ +int guacenc_parse_int(char* arg, int* i); + +/** + * Parses a string of the form WIDTHxHEIGHT into individual width and height + * integers. The input string may be modified during parsing. Values will be + * stored in the provided width and height pointers only if the given + * dimensions are valid. + * + * @param arg + * The string to parse. + * + * @param width + * A pointer to the integer in which the parsed width component of the + * given string should be stored. + * + * @param height + * A pointer to the integer in which the parsed height component of the + * given string should be stored. + * + * @return + * Zero if parsing was successful, non-zero if the provided string was + * invalid. + */ +int guacenc_parse_dimensions(char* arg, int* width, int* height); + +#endif + + diff --git a/src/guacenc/png.c b/src/guacenc/png.c new file mode 100644 index 00000000..abf1e58e --- /dev/null +++ b/src/guacenc/png.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "log.h" +#include "png.h" + +#include + +#include +#include + +/** + * The current state of the PNG decoder. + */ +typedef struct guacenc_png_read_state { + + /** + * The buffer of unread image data. This pointer will be updated to point + * to the next unread byte when data is read. + */ + unsigned char* data; + + /** + * The number of bytes remaining to be read within the buffer. + */ + unsigned int length; + +} guacenc_png_read_state; + +/** + * Attempts to fill the given buffer with read image data. The behavior of + * this function is dictated by cairo_read_t. + * + * @param closure + * The current state of the PNG decoding process (an instance of + * guacenc_png_read_state). + * + * @param data + * The data buffer to fill. + * + * @param length + * The number of bytes to fill within the data buffer. + * + * @return + * CAIRO_STATUS_SUCCESS if all data was read successfully (the entire + * buffer was filled), CAIRO_STATUS_READ_ERROR otherwise. + */ +static cairo_status_t guacenc_png_read(void* closure, unsigned char* data, + unsigned int length) { + + guacenc_png_read_state* state = (guacenc_png_read_state*) closure; + + /* If more data is requested than is available in buffer, fail */ + if (length > state->length) + return CAIRO_STATUS_READ_ERROR; + + /* Read chunk into buffer */ + memcpy(data, state->data, length); + + /* Advance to next chunk */ + state->length -= length; + state->data += length; + + /* Read was successful */ + return CAIRO_STATUS_SUCCESS; + +} + +cairo_surface_t* guacenc_png_decoder(unsigned char* data, int length) { + + guacenc_png_read_state state = { + .data = data, + .length = length + }; + + /* Read PNG from data */ + cairo_surface_t* surface = + cairo_image_surface_create_from_png_stream(guacenc_png_read, &state); + + /* If surface returned with an error, just return NULL */ + if (surface != NULL && + cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { + guacenc_log(GUAC_LOG_WARNING, "Invalid PNG data"); + cairo_surface_destroy(surface); + return NULL; + } + + /* PNG was read successfully */ + return surface; + +} + diff --git a/src/guacenc/png.h b/src/guacenc/png.h new file mode 100644 index 00000000..86307a0b --- /dev/null +++ b/src/guacenc/png.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_PNG_H +#define GUACENC_PNG_H + +#include "config.h" +#include "image-stream.h" + +/** + * Decoder implementation which handles "image/png" images. + */ +guacenc_decoder guacenc_png_decoder; + +#endif + diff --git a/src/guacenc/video.c b/src/guacenc/video.c new file mode 100644 index 00000000..8c138442 --- /dev/null +++ b/src/guacenc/video.c @@ -0,0 +1,489 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "buffer.h" +#include "ffmpeg-compat.h" +#include "log.h" +#include "video.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, + int width, int height, int bitrate) { + + /* Pull codec based on name */ + AVCodec* codec = avcodec_find_encoder_by_name(codec_name); + if (codec == NULL) { + guacenc_log(GUAC_LOG_ERROR, "Failed to locate codec \"%s\".", + codec_name); + goto fail_codec; + } + + /* Retrieve encoding context */ + AVCodecContext* context = avcodec_alloc_context3(codec); + if (context == NULL) { + guacenc_log(GUAC_LOG_ERROR, "Failed to allocate context for " + "codec \"%s\".", codec_name); + goto fail_context; + } + + /* Init context with encoding parameters */ + context->bit_rate = bitrate; + context->width = width; + context->height = height; + context->time_base = (AVRational) { 1, GUACENC_VIDEO_FRAMERATE }; + context->gop_size = 10; + context->max_b_frames = 1; + context->pix_fmt = AV_PIX_FMT_YUV420P; + + /* Open codec for use */ + if (avcodec_open2(context, codec, NULL) < 0) { + guacenc_log(GUAC_LOG_ERROR, "Failed to open codec \"%s\".", codec_name); + goto fail_codec_open; + } + + /* Allocate corresponding frame */ + AVFrame* frame = av_frame_alloc(); + if (frame == NULL) { + goto fail_frame; + } + + /* Copy necessary data for frame from context */ + frame->format = context->pix_fmt; + frame->width = context->width; + frame->height = context->height; + + /* Allocate actual backing data for frame */ + if (av_image_alloc(frame->data, frame->linesize, frame->width, + frame->height, frame->format, 32) < 0) { + goto fail_frame_data; + } + + /* Open output file */ + int fd = open(path, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); + if (fd == -1) { + guacenc_log(GUAC_LOG_ERROR, "Failed to open output file \"%s\": %s", + path, strerror(errno)); + goto fail_output_fd; + } + + /* Create stream for output file */ + FILE* output = fdopen(fd, "wb"); + if (output == NULL) { + guacenc_log(GUAC_LOG_ERROR, "Failed to allocate stream for output " + "file \"%s\": %s", path, strerror(errno)); + goto fail_output_file; + } + + /* Allocate video structure */ + guacenc_video* video = malloc(sizeof(guacenc_video)); + if (video == NULL) { + goto fail_video; + } + + /* Init properties of video */ + video->output = output; + video->context = context; + video->next_frame = frame; + video->width = width; + video->height = height; + video->bitrate = bitrate; + + /* No frames have been written or prepared yet */ + video->last_timestamp = 0; + video->next_pts = 0; + + return video; + + /* Free all allocated data in case of failure */ +fail_video: + fclose(output); + +fail_output_file: + close(fd); + +fail_output_fd: + av_freep(&frame->data[0]); + +fail_frame_data: + av_frame_free(&frame); + +fail_frame: +fail_codec_open: + avcodec_free_context(&context); + +fail_context: +fail_codec: + return NULL; + +} + +/** + * Flushes the specied frame as a new frame of video, updating the internal + * video timestamp by one frame's worth of time. The pts member of the given + * frame structure will be updated with the current presentation timestamp of + * the video. If pending frames of the video are being flushed, the given frame + * may be NULL (as required by avcodec_encode_video2()). + * + * @param video + * The video to write the given frame to. + * + * @param frame + * The frame to write to the video, or NULL if previously-written frames + * are being flushed. + * + * @return + * A positive value if the frame was successfully written, zero if the + * frame has been saved for later writing / reordering, negative if an + * error occurs. + */ +static int guacenc_video_write_frame(guacenc_video* video, AVFrame* frame) { + + /* Init video packet */ + AVPacket packet; + av_init_packet(&packet); + + /* Request that encoder allocate data for packet */ + packet.data = NULL; + packet.size = 0; + + /* Set timestamp of frame, if frame given */ + if (frame != NULL) + frame->pts = video->next_pts; + + /* Write frame to video */ + int got_data; + if (avcodec_encode_video2(video->context, &packet, frame, &got_data) < 0) { + guacenc_log(GUAC_LOG_WARNING, "Error encoding frame #%" PRId64, + video->next_pts); + return -1; + } + + /* Write corresponding data to file */ + if (got_data) { + + /* Write data, logging any errors */ + if (fwrite(packet.data, 1, packet.size, video->output) == 0) { + guacenc_log(GUAC_LOG_ERROR, "Unable to write frame " + "#%" PRId64 ": %s", video->next_pts, strerror(errno)); + return -1; + } + + /* Data was written successfully */ + guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": wrote %i bytes", + video->next_pts, packet.size); + av_packet_unref(&packet); + + } + + /* Frame may have been queued for later writing / reordering */ + else + guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": queued for later", + video->next_pts); + + /* Update presentation timestamp for next frame */ + video->next_pts++; + + /* Write was successful */ + return got_data; + +} + +/** + * Flushes the frame previously specified by guacenc_video_prepare_frame() as a + * new frame of video, updating the internal video timestamp by one frame's + * worth of time. + * + * @param video + * The video to flush. + * + * @return + * Zero if flushing was successful, non-zero if an error occurs. + */ +static int guacenc_video_flush_frame(guacenc_video* video) { + + /* Write frame to video */ + return guacenc_video_write_frame(video, video->next_frame) < 0; + +} + +int guacenc_video_advance_timeline(guacenc_video* video, + guac_timestamp timestamp) { + + /* Flush frames as necessary if previously updated */ + if (video->last_timestamp != 0) { + + /* Calculate the number of frames that should have been written */ + int elapsed = (timestamp - video->last_timestamp) + * GUACENC_VIDEO_FRAMERATE / 1000; + + /* Keep previous timestamp if insufficient time has elapsed */ + if (elapsed == 0) + return 0; + + /* Flush frames to bring timeline in sync, duplicating if necessary */ + do { + guacenc_video_flush_frame(video); + } while (--elapsed != 0); + + } + + /* Update timestamp */ + video->last_timestamp = timestamp; + return 0; + +} + +/** + * Converts the given Guacamole video encoder buffer to a frame in the format + * required by libavcodec / libswscale. Black margins of the specified sizes + * will be added. No scaling is performed; the image data is copied verbatim. + * + * @param buffer + * The guacenc_buffer to copy as a new AVFrame. + * + * @param lsize + * The size of the letterboxes to add, in pixels. Letterboxes are the + * horizontal black boxes added to images which are scaled down to fit the + * destination because they are too wide (the width is scaled to exactly + * fit the destination, resulting in extra space at the top and bottom). + * + * @param psize + * The size of the pillarboxes to add, in pixels. Pillarboxes are the + * vertical black boxes added to images which are scaled down to fit the + * destination because they are too tall (the height is scaled to exactly + * fit the destination, resulting in extra space on the sides). + * + * @return + * A pointer to a newly-allocated AVFrame containing exactly the same image + * data as the given buffer. The image data within the frame and the frame + * itself must be manually freed later. + */ +static AVFrame* guacenc_video_frame_convert(guacenc_buffer* buffer, int lsize, + int psize) { + + /* Init size of left/right pillarboxes */ + int left = psize; + int right = psize; + + /* Init size of top/bottom letterboxes */ + int top = lsize; + int bottom = lsize; + + /* Prepare source frame for buffer */ + AVFrame* frame = av_frame_alloc(); + if (frame == NULL) + return NULL; + + /* Copy buffer properties to frame */ + frame->format = AV_PIX_FMT_RGB32; + frame->width = buffer->width + left + right; + frame->height = buffer->height + top + bottom; + + /* Allocate actual backing data for frame */ + if (av_image_alloc(frame->data, frame->linesize, frame->width, + frame->height, frame->format, 32) < 0) { + av_frame_free(&frame); + return NULL; + } + + /* Flush any pending operations */ + cairo_surface_flush(buffer->surface); + + /* Get pointer to source image data */ + unsigned char* src_data = buffer->image; + int src_stride = buffer->stride; + + /* Get pointer to destination image data */ + unsigned char* dst_data = frame->data[0]; + int dst_stride = frame->linesize[0]; + + /* Get source/destination dimensions */ + int width = buffer->width; + int height = buffer->height; + + /* Source buffer is guaranteed to fit within destination buffer */ + assert(width <= frame->width); + assert(height <= frame->height); + + /* Add top margin */ + while (top > 0) { + memset(dst_data, 0, frame->width * 4); + dst_data += dst_stride; + top--; + } + + /* Copy all data from source buffer to destination frame */ + while (height > 0) { + + /* Calculate size of margin and data regions */ + int left_size = left * 4; + int data_size = width * 4; + int right_size = right * 4; + + /* Add left margin */ + memset(dst_data, 0, left_size); + + /* Copy data */ + memcpy(dst_data + left_size, src_data, data_size); + + /* Add right margin */ + memset(dst_data + left_size + data_size, 0, right_size); + + dst_data += dst_stride; + src_data += src_stride; + + height--; + + } + + /* Add bottom margin */ + while (bottom > 0) { + memset(dst_data, 0, frame->width * 4); + dst_data += dst_stride; + bottom--; + } + + /* Frame converted */ + return frame; + +} + +void guacenc_video_prepare_frame(guacenc_video* video, guacenc_buffer* buffer) { + + int lsize; + int psize; + + /* Ignore NULL buffers */ + if (buffer == NULL || buffer->surface == NULL) + return; + + /* Obtain destination frame */ + AVFrame* dst = video->next_frame; + + /* Determine width of image if height is scaled to match destination */ + int scaled_width = buffer->width * dst->height / buffer->height; + + /* Determine height of image if width is scaled to match destination */ + int scaled_height = buffer->height * dst->width / buffer->width; + + /* If height-based scaling results in a fit width, add pillarboxes */ + if (scaled_width <= dst->width) { + lsize = 0; + psize = (dst->width - scaled_width) + * buffer->height / dst->height / 2; + } + + /* If width-based scaling results in a fit width, add letterboxes */ + else { + assert(scaled_height <= dst->height); + psize = 0; + lsize = (dst->height - scaled_height) + * buffer->width / dst->width / 2; + } + + /* Prepare source frame for buffer */ + AVFrame* src = guacenc_video_frame_convert(buffer, lsize, psize); + if (src == NULL) { + guacenc_log(GUAC_LOG_WARNING, "Failed to allocate source frame. " + "Frame dropped."); + return; + } + + /* Prepare scaling context */ + struct SwsContext* sws = sws_getContext(src->width, src->height, + PIX_FMT_RGB32, dst->width, dst->height, PIX_FMT_YUV420P, + SWS_BICUBIC, NULL, NULL, NULL); + + /* Abort if scaling context could not be created */ + if (sws == NULL) { + guacenc_log(GUAC_LOG_WARNING, "Failed to allocate software scaling " + "context. Frame dropped."); + av_freep(&src->data[0]); + av_frame_free(&src); + return; + } + + /* Apply scaling, copying the source frame to the destination */ + sws_scale(sws, (const uint8_t* const*) src->data, src->linesize, + 0, src->height, dst->data, dst->linesize); + + /* Free scaling context */ + sws_freeContext(sws); + + /* Free source frame */ + av_freep(&src->data[0]); + av_frame_free(&src); + +} + +int guacenc_video_free(guacenc_video* video) { + + /* Ignore NULL video */ + if (video == NULL) + return 0; + + /* Write final frame */ + guacenc_video_flush_frame(video); + + /* Init video packet for final flush of encoded data */ + AVPacket packet; + av_init_packet(&packet); + + /* Flush any unwritten frames */ + int retval; + do { + retval = guacenc_video_write_frame(video, NULL); + } while (retval > 0); + + /* File is now completely written */ + fclose(video->output); + + /* Free frame encoding data */ + av_freep(&video->next_frame->data[0]); + av_frame_free(&video->next_frame); + + /* Clean up encoding context */ + avcodec_close(video->context); + avcodec_free_context(&(video->context)); + + free(video); + return 0; + +} + diff --git a/src/guacenc/video.h b/src/guacenc/video.h new file mode 100644 index 00000000..61c7ff8f --- /dev/null +++ b/src/guacenc/video.h @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_VIDEO_H +#define GUACENC_VIDEO_H + +#include "config.h" +#include "buffer.h" + +#include +#include + +#include +#include + +/** + * The framerate at which video should be encoded, in frames per second. + */ +#define GUACENC_VIDEO_FRAMERATE 25 + +/** + * A video which is actively being encoded. Frames can be added to the video + * as they are generated, along with their associated timestamps, and the + * corresponding video will be continuously written as it is encoded. + */ +typedef struct guacenc_video { + + /** + * Output file stream. + */ + FILE* output; + + /** + * The open encoding context from libavcodec, created for the codec + * specified when this guacenc_video was created. + */ + AVCodecContext* context; + + /** + * The width of the video, in pixels. + */ + int width; + + /** + * The height of the video, in pixels. + */ + int height; + + /** + * The desired output bitrate of the video, in bits per second. + */ + int bitrate; + + /** + * An image data area containing the next frame to be written, encoded as + * YCbCr image data in the format required by avcodec_encode_video2(), for + * use and re-use as frames are rendered. + */ + AVFrame* next_frame; + + /** + * The presentation timestamp that should be used for the next frame. This + * is equivalent to the frame number. + */ + int64_t next_pts; + + /** + * The timestamp associated with the last frame, or 0 if no frames have yet + * been added. + */ + guac_timestamp last_timestamp; + +} guacenc_video; + +/** + * Allocates a new guacenc_video which encodes video according to the given + * specifications, saving the output in the given file. If the output file + * already exists, encoding will be aborted, and the original file contents + * will be preserved. Frames will be scaled up or down as necessary to fit the + * given width and height. + * + * @param path + * The full path to the file in which encoded video should be written. + * + * @param codec_name + * The name of the codec to use for the video encoding, as defined by + * ffmpeg / libavcodec. + * + * @param width + * The width of the desired video, in pixels. + * + * @param height + * The height of the desired video, in pixels. + * + * @param bitrate + * The desired overall bitrate of the resulting encoded video, in bits per + * second. + */ +guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, + int width, int height, int bitrate); + +/** + * Advances the timeline of the encoding process to the given timestamp, such + * that frames added via guacenc_video_prepare_frame() will be encoded at the + * proper frame boundaries within the video. Duplicate frames will be encoded + * as necessary to ensure that the output is correctly timed with respect to + * the given timestamp. This is particularly important as Guacamole does not + * have a framerate per se, and the time between each Guacamole "frame" will + * vary significantly. + * + * This function MUST be called prior to invoking guacenc_video_prepare_frame() + * to ensure the prepared frame will be encoded at the correct point in time. + * + * @param video + * The video whose timeline should be adjusted. + * + * @param timestamp + * The Guacamole timestamp denoting the point in time that the video + * timeline should be advanced to, as dictated by a parsed "sync" + * instruction. + * + * @return + * Zero if the timeline was adjusted successfully, non-zero if an error + * occurs (such as during the encoding of duplicate frames). + */ +int guacenc_video_advance_timeline(guacenc_video* video, + guac_timestamp timestamp); + +/** + * Stores the given buffer within the given video structure such that it will + * be written if it falls within proper frame boundaries. If the timeline of + * the video (as dictated by guacenc_video_advance_timeline()) is not at a + * frame boundary with respect to the video framerate (it occurs between frame + * boundaries), the prepared frame will only be written if another frame is not + * prepared within the same pair of frame boundaries). The prepared frame will + * not be written until it is implicitly flushed through updates to the video + * timeline or through reaching the end of the encoding process + * (guacenc_video_free()). + * + * @param video + * The video in which the given buffer should be queued for possible + * writing (depending on timing vs. video framerate). + * + * @param buffer + * The guacenc_buffer representing the image data of the frame that should + * be queued. + */ +void guacenc_video_prepare_frame(guacenc_video* video, guacenc_buffer* buffer); + +/** + * Frees all resources associated with the given video, finalizing the encoding + * process. Any buffered frames which have not yet been written will be written + * at this point. + * + * @return + * Zero if the video was successfully written and freed, non-zero if the + * video could not be written due to an error. + */ +int guacenc_video_free(guacenc_video* video); + +#endif + diff --git a/src/guacenc/webp.c b/src/guacenc/webp.c new file mode 100644 index 00000000..191d0451 --- /dev/null +++ b/src/guacenc/webp.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "log.h" +#include "webp.h" + +#include +#include +#include + +#include +#include + +cairo_surface_t* guacenc_webp_decoder(unsigned char* data, int length) { + + int width, height; + + /* Validate WebP and pull dimensions */ + if (!WebPGetInfo((uint8_t*) data, length, &width, &height)) { + guacenc_log(GUAC_LOG_WARNING, "Invalid WebP data"); + return NULL; + } + + /* Create blank Cairo surface */ + cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + width, height); + + /* Fill surface with opaque black */ + cairo_t* cairo = cairo_create(surface); + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cairo, 0.0, 0.0, 0.0, 1.0); + cairo_paint(cairo); + cairo_destroy(cairo); + + /* Finish any pending draws */ + cairo_surface_flush(surface); + + /* Pull underlying buffer and its stride */ + int stride = cairo_image_surface_get_stride(surface); + unsigned char* image = cairo_image_surface_get_data(surface); + + /* Read WebP into surface */ + uint8_t* result = WebPDecodeBGRAInto((uint8_t*) data, length, + (uint8_t*) image, stride * height, stride); + + /* Verify WebP was successfully decoded */ + if (result == NULL) { + guacenc_log(GUAC_LOG_WARNING, "Invalid WebP data"); + cairo_surface_destroy(surface); + return NULL; + } + + /* WebP was read successfully */ + return surface; + +} + diff --git a/src/guacenc/webp.h b/src/guacenc/webp.h new file mode 100644 index 00000000..a6159698 --- /dev/null +++ b/src/guacenc/webp.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUACENC_WEBP_H +#define GUACENC_WEBP_H + +#include "config.h" +#include "image-stream.h" + +/** + * Decoder implementation which handles "image/webp" images. + */ +guacenc_decoder guacenc_webp_decoder; + +#endif + diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index e18ff338..2c3d3130 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -89,6 +89,7 @@ libguac_la_SOURCES = \ socket.c \ socket-fd.c \ socket-nest.c \ + socket-tee.c \ timestamp.c \ unicode.c \ user.c \ diff --git a/src/libguac/client.c b/src/libguac/client.c index 2bfd2e2f..55efa48c 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -462,6 +462,9 @@ void guac_client_free(guac_client* client) { } + /* Free socket */ + guac_socket_free(client->socket); + /* Free layer pools */ guac_pool_free(client->__buffer_pool); guac_pool_free(client->__layer_pool); diff --git a/src/libguac/guacamole/socket.h b/src/libguac/guacamole/socket.h index 27d0be5b..adc45ac8 100644 --- a/src/libguac/guacamole/socket.h +++ b/src/libguac/guacamole/socket.h @@ -191,6 +191,38 @@ guac_socket* guac_socket_open(int fd); */ guac_socket* guac_socket_nest(guac_socket* parent, int index); +/** + * Allocates and initializes a new guac_socket which delegates all socket + * operations to the given primary socket, while simultaneously duplicating all + * written data to the secondary socket. Freeing the returned guac_socket will + * free both primary and secondary sockets. + * + * Return values (error codes) will come only from the primary socket. Locks + * (like those used by guac_socket_instruction_begin() and + * guac_socket_instruction_end()) will affect only the primary socket. + * + * If an error occurs while allocating the guac_socket object, NULL is returned, + * and guac_error is set appropriately. + * + * @param primary + * The primary guac_socket to which all socket operations should be + * delegated. The error codes returned by socket operations, if any, will + * always come from this socket. This socket will also be the only socket + * locked when instructions begin (or unlocked when instructions end). + * + * @param secondary + * The secondary guac_socket to which all data written to the primary + * guac_socket should be copied. If an error prevents the write from + * succeeding, that error will be ignored. Only errors from the primary + * guac_socket will be acknowledged. + * + * @return + * A newly allocated guac_socket object associated with the given primary + * and secondary sockets, or NULL if an error occurs while allocating the + * guac_socket object. + */ +guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary); + /** * Writes the given unsigned int to the given guac_socket object. The data * written may be buffered until the buffer is flushed automatically or diff --git a/src/libguac/socket-tee.c b/src/libguac/socket-tee.c new file mode 100644 index 00000000..d6d6402b --- /dev/null +++ b/src/libguac/socket-tee.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2016 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "socket.h" + +#include + +/** + * Data specific to the tee implementation of guac_socket. + */ +typedef struct guac_socket_tee_data { + + /** + * The guac_socket to which all socket operations should be delegated. + */ + guac_socket* primary; + + /** + * The guac_socket to which all write and flush operations should be + * duplicated. + */ + guac_socket* secondary; + +} guac_socket_tee_data; + +/** + * Callback function which reads only from the primary socket. + * + * @param socket + * The tee socket to read from. + * + * @param buf + * The buffer to read data into. + * + * @param count + * The maximum number of bytes to read into the given buffer. + * + * @return + * The value returned by guac_socket_read() when invoked on the primary + * socket with the given parameters. + */ +static ssize_t __guac_socket_tee_read_handler(guac_socket* socket, + void* buf, size_t count) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Delegate read to wrapped socket */ + return guac_socket_read(data->primary, buf, count); + +} + +/** + * Callback function which writes the given data to both underlying sockets, + * returning only the result from the primary socket. + * + * @param socket + * The tee socket to write through. + * + * @param buf + * The buffer of data to write. + * + * @param count + * The number of bytes in the buffer to be written. + * + * @return + * The number of bytes written if the write was successful, or -1 if an + * error occurs. + */ +static ssize_t __guac_socket_tee_write_handler(guac_socket* socket, + const void* buf, size_t count) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Write to secondary socket (ignoring result) */ + guac_socket_write(data->secondary, buf, count); + + /* Delegate write to wrapped socket */ + if (guac_socket_write(data->primary, buf, count)) + return -1; + + /* All data written successfully */ + return count; + +} + +/** + * Callback function which flushes both underlying sockets, returning only the + * result from the primary socket. + * + * @param socket + * The tee socket to flush. + * + * @return + * The value returned by guac_socket_flush() when invoked on the primary + * socket. + */ +static ssize_t __guac_socket_tee_flush_handler(guac_socket* socket) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Flush secondary socket (ignoring result) */ + guac_socket_flush(data->secondary); + + /* Delegate flush to wrapped socket */ + return guac_socket_flush(data->primary); + +} + +/** + * Callback function which delegates the lock operation to the primary + * socket alone. + * + * @param socket + * The tee socket on which guac_socket_instruction_begin() was invoked. + */ +static void __guac_socket_tee_lock_handler(guac_socket* socket) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Delegate lock to wrapped socket */ + guac_socket_instruction_begin(data->primary); + +} + +/** + * Callback function which delegates the unlock operation to the primary + * socket alone. + * + * @param socket + * The tee socket on which guac_socket_instruction_end() was invoked. + */ +static void __guac_socket_tee_unlock_handler(guac_socket* socket) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Delegate unlock to wrapped socket */ + guac_socket_instruction_end(data->primary); + +} + +/** + * Callback function which delegates the select operation to the primary + * socket alone. + * + * @param socket + * The tee socket on which guac_socket_select() was invoked. + * + * @param usec_timeout + * The timeout to specify when invoking guac_socket_select() on the + * primary socket. + * + * @return + * The value returned by guac_socket_select() when invoked with the + * given parameters on the primary socket. + */ +static int __guac_socket_tee_select_handler(guac_socket* socket, + int usec_timeout) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Delegate select to wrapped socket */ + return guac_socket_select(data->primary, usec_timeout); + +} + +/** + * Callback function which frees all underlying data associated with the + * given tee socket, including both primary and secondary sockets. + * + * @param socket + * The tee socket being freed. + * + * @return + * Always zero. + */ +static int __guac_socket_tee_free_handler(guac_socket* socket) { + + guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data; + + /* Free underlying sockets */ + guac_socket_free(data->primary); + guac_socket_free(data->secondary); + + /* Freeing the tee socket always succeeds */ + free(data); + return 0; + +} + +guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary) { + + /* Set up socket to split outout into a file */ + guac_socket_tee_data* data = malloc(sizeof(guac_socket_tee_data)); + data->primary = primary; + data->secondary = secondary; + + /* Associate tee-specific data with new socket */ + guac_socket* socket = guac_socket_alloc(); + socket->data = data; + + /* Assign handlers */ + socket->read_handler = __guac_socket_tee_read_handler; + socket->write_handler = __guac_socket_tee_write_handler; + socket->select_handler = __guac_socket_tee_select_handler; + socket->flush_handler = __guac_socket_tee_flush_handler; + socket->lock_handler = __guac_socket_tee_lock_handler; + socket->unlock_handler = __guac_socket_tee_unlock_handler; + socket->free_handler = __guac_socket_tee_free_handler; + + return socket; + +} + diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 47ec75c3..9dea54b5 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -25,6 +25,7 @@ #include "client.h" #include "guac_cursor.h" #include "guac_display.h" +#include "guac_recording.h" #include "rdp.h" #include "rdp_bitmap.h" #include "rdp_cliprdr.h" @@ -699,6 +700,14 @@ void* guac_rdp_client_thread(void* data) { /* Init random number generator */ srandom(time(NULL)); + /* Set up screen recording, if requested */ + if (settings->recording_path != NULL) { + guac_common_recording_create(client, + settings->recording_path, + settings->recording_name, + settings->create_recording_path); + } + /* Create display */ rdp_client->display = guac_common_display_alloc(client, rdp_client->settings->width, diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index 2159e8c2..0e90f4dc 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -89,6 +89,10 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "sftp-directory", #endif + "recording-path", + "recording-name", + "create-recording-path", + NULL }; @@ -352,6 +356,10 @@ enum RDP_ARGS_IDX { #endif + IDX_RECORDING_PATH, + IDX_RECORDING_NAME, + IDX_CREATE_RECORDING_PATH, + RDP_ARGS_COUNT }; @@ -673,6 +681,21 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, IDX_SFTP_DIRECTORY, NULL); #endif + /* Read recording path */ + settings->recording_path = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_RECORDING_PATH, NULL); + + /* Read recording name */ + settings->recording_name = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_RECORDING_NAME, GUAC_RDP_DEFAULT_RECORDING_NAME); + + /* Parse path creation flag */ + settings->create_recording_path = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_CREATE_RECORDING_PATH, 0); + /* Success */ return settings; @@ -688,6 +711,8 @@ void guac_rdp_settings_free(guac_rdp_settings* settings) { free(settings->initial_program); free(settings->password); free(settings->preconnection_blob); + free(settings->recording_name); + free(settings->recording_path); free(settings->remote_app); free(settings->remote_app_args); free(settings->remote_app_dir); diff --git a/src/protocols/rdp/rdp_settings.h b/src/protocols/rdp/rdp_settings.h index 1c197a04..f4c979ab 100644 --- a/src/protocols/rdp/rdp_settings.h +++ b/src/protocols/rdp/rdp_settings.h @@ -56,6 +56,11 @@ */ #define RDP_DEFAULT_DEPTH 16 +/** + * The filename to use for the screen recording, if not specified. + */ +#define GUAC_RDP_DEFAULT_RECORDING_NAME "recording" + /** * All supported combinations of security types. */ @@ -328,6 +333,23 @@ typedef struct guac_rdp_settings { char* sftp_directory; #endif + /** + * The path in which the screen recording should be saved, if enabled. If + * no screen recording should be saved, this will be NULL. + */ + char* recording_path; + + /** + * The filename to use for the screen recording, if enabled. + */ + char* recording_name; + + /** + * Whether the screen recording path should be automatically created if it + * does not already exist. + */ + int create_recording_path; + } guac_rdp_settings; /** diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index c3f95b94..eb001837 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -50,6 +50,9 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { "typescript-path", "typescript-name", "create-typescript-path", + "recording-path", + "recording-name", + "create-recording-path", NULL }; @@ -140,6 +143,24 @@ enum SSH_ARGS_IDX { */ IDX_CREATE_TYPESCRIPT_PATH, + /** + * The full absolute path to the directory in which screen recordings + * should be written. + */ + IDX_RECORDING_PATH, + + /** + * The name that should be given to screen recording which are written in + * the given path. + */ + IDX_RECORDING_NAME, + + /** + * Whether the specified screen recording path should automatically be + * created if it does not yet exist. + */ + IDX_CREATE_RECORDING_PATH, + SSH_ARGS_COUNT }; @@ -234,6 +255,21 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_CREATE_TYPESCRIPT_PATH, false); + /* Read recording path */ + settings->recording_path = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_RECORDING_PATH, NULL); + + /* Read recording name */ + settings->recording_name = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_RECORDING_NAME, GUAC_SSH_DEFAULT_RECORDING_NAME); + + /* Parse path creation flag */ + settings->create_recording_path = + guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_CREATE_RECORDING_PATH, false); + /* Parsing was successful */ return settings; @@ -262,6 +298,10 @@ void guac_ssh_settings_free(guac_ssh_settings* settings) { free(settings->typescript_name); free(settings->typescript_path); + /* Free screen recording settings */ + free(settings->recording_name); + free(settings->recording_path); + /* Free overall structure */ free(settings); diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h index 9ab2d4ac..9950051c 100644 --- a/src/protocols/ssh/settings.h +++ b/src/protocols/ssh/settings.h @@ -51,6 +51,11 @@ */ #define GUAC_SSH_DEFAULT_TYPESCRIPT_NAME "typescript" +/** + * The filename to use for the screen recording, if not specified. + */ +#define GUAC_SSH_DEFAULT_RECORDING_NAME "recording" + /** * Settings for the SSH connection. The values for this structure are parsed * from the arguments given during the Guacamole protocol handshake using the @@ -157,6 +162,23 @@ typedef struct guac_ssh_settings { */ bool create_typescript_path; + /** + * The path in which the screen recording should be saved, if enabled. If + * no screen recording should be saved, this will be NULL. + */ + char* recording_path; + + /** + * The filename to use for the screen recording, if enabled. + */ + char* recording_name; + + /** + * Whether the screen recording path should be automatically created if it + * does not already exist. + */ + bool create_recording_path; + } guac_ssh_settings; /** diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 6b539656..a78b784e 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -22,6 +22,7 @@ #include "config.h" +#include "guac_recording.h" #include "guac_sftp.h" #include "guac_ssh.h" #include "settings.h" @@ -183,6 +184,14 @@ void* ssh_client_thread(void* data) { if (guac_common_ssh_init(client)) return NULL; + /* Set up screen recording, if requested */ + if (settings->recording_path != NULL) { + guac_common_recording_create(client, + settings->recording_path, + settings->recording_name, + settings->create_recording_path); + } + /* Create terminal */ ssh_client->term = guac_terminal_create(client, settings->font_name, settings->font_size, diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index 38fbc22e..edb74de2 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -46,6 +46,9 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = { "typescript-path", "typescript-name", "create-typescript-path", + "recording-path", + "recording-name", + "create-recording-path", NULL }; @@ -120,6 +123,24 @@ enum TELNET_ARGS_IDX { */ IDX_CREATE_TYPESCRIPT_PATH, + /** + * The full absolute path to the directory in which screen recordings + * should be written. + */ + IDX_RECORDING_PATH, + + /** + * The name that should be given to screen recording which are written in + * the given path. + */ + IDX_RECORDING_NAME, + + /** + * Whether the specified screen recording path should automatically be + * created if it does not yet exist. + */ + IDX_CREATE_RECORDING_PATH, + TELNET_ARGS_COUNT }; @@ -239,6 +260,21 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, IDX_CREATE_TYPESCRIPT_PATH, false); + /* Read recording path */ + settings->recording_path = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_RECORDING_PATH, NULL); + + /* Read recording name */ + settings->recording_name = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_RECORDING_NAME, GUAC_TELNET_DEFAULT_RECORDING_NAME); + + /* Parse path creation flag */ + settings->create_recording_path = + guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_CREATE_RECORDING_PATH, false); + /* Parsing was successful */ return settings; @@ -274,6 +310,10 @@ void guac_telnet_settings_free(guac_telnet_settings* settings) { free(settings->typescript_name); free(settings->typescript_path); + /* Free screen recording settings */ + free(settings->recording_name); + free(settings->recording_path); + /* Free overall structure */ free(settings); diff --git a/src/protocols/telnet/settings.h b/src/protocols/telnet/settings.h index 54062206..3bf388d8 100644 --- a/src/protocols/telnet/settings.h +++ b/src/protocols/telnet/settings.h @@ -53,6 +53,11 @@ */ #define GUAC_TELNET_DEFAULT_TYPESCRIPT_NAME "typescript" +/** + * The filename to use for the screen recording, if not specified. + */ +#define GUAC_TELNET_DEFAULT_RECORDING_NAME "recording" + /** * The regular expression to use when searching for the username/login prompt * if no other regular expression is specified. @@ -157,6 +162,23 @@ typedef struct guac_telnet_settings { */ bool create_typescript_path; + /** + * The path in which the screen recording should be saved, if enabled. If + * no screen recording should be saved, this will be NULL. + */ + char* recording_path; + + /** + * The filename to use for the screen recording, if enabled. + */ + char* recording_name; + + /** + * Whether the screen recording path should be automatically created if it + * does not already exist. + */ + bool create_recording_path; + } guac_telnet_settings; /** diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index 526000d2..7ba0720e 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -21,6 +21,7 @@ */ #include "config.h" +#include "guac_recording.h" #include "telnet.h" #include "terminal.h" @@ -468,6 +469,14 @@ void* guac_telnet_client_thread(void* data) { char buffer[8192]; int wait_result; + /* Set up screen recording, if requested */ + if (settings->recording_path != NULL) { + guac_common_recording_create(client, + settings->recording_path, + settings->recording_name, + settings->create_recording_path); + } + /* Create terminal */ telnet_client->term = guac_terminal_create(client, settings->font_name, settings->font_size, diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index e4b467f3..ffff5623 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -71,6 +71,10 @@ const char* GUAC_VNC_CLIENT_ARGS[] = { "sftp-directory", #endif + "recording-path", + "recording-name", + "create-recording-path", + NULL }; @@ -228,6 +232,10 @@ enum VNC_ARGS_IDX { IDX_SFTP_DIRECTORY, #endif + IDX_RECORDING_PATH, + IDX_RECORDING_NAME, + IDX_CREATE_RECORDING_PATH, + VNC_ARGS_COUNT }; @@ -378,6 +386,20 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, IDX_SFTP_DIRECTORY, NULL); #endif + /* Read recording path */ + settings->recording_path = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_RECORDING_PATH, NULL); + + /* Read recording name */ + settings->recording_name = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_RECORDING_NAME, GUAC_VNC_DEFAULT_RECORDING_NAME); + + /* Parse path creation flag */ + settings->create_recording_path = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_CREATE_RECORDING_PATH, false); return settings; @@ -389,6 +411,8 @@ void guac_vnc_settings_free(guac_vnc_settings* settings) { free(settings->clipboard_encoding); free(settings->encodings); free(settings->hostname); + free(settings->recording_name); + free(settings->recording_path); #ifdef ENABLE_VNC_REPEATER /* Free VNC repeater settings */ diff --git a/src/protocols/vnc/settings.h b/src/protocols/vnc/settings.h index da4f3d2f..9f1b2f23 100644 --- a/src/protocols/vnc/settings.h +++ b/src/protocols/vnc/settings.h @@ -28,6 +28,11 @@ #include +/** + * The filename to use for the screen recording, if not specified. + */ +#define GUAC_VNC_DEFAULT_RECORDING_NAME "recording" + /** * VNC-specific client data. */ @@ -173,6 +178,23 @@ typedef struct guac_vnc_settings { char* sftp_directory; #endif + /** + * The path in which the screen recording should be saved, if enabled. If + * no screen recording should be saved, this will be NULL. + */ + char* recording_path; + + /** + * The filename to use for the screen recording, if enabled. + */ + char* recording_name; + + /** + * Whether the screen recording path should be automatically created if it + * does not already exist. + */ + bool create_recording_path; + } guac_vnc_settings; /** diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 2467f430..da689527 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -30,6 +30,7 @@ #include "guac_clipboard.h" #include "guac_cursor.h" #include "guac_display.h" +#include "guac_recording.h" #include "log.h" #include "settings.h" #include "vnc.h" @@ -317,6 +318,14 @@ void* guac_vnc_client_thread(void* data) { /* Set remaining client data */ vnc_client->rfb_client = rfb_client; + /* Set up screen recording, if requested */ + if (settings->recording_path != NULL) { + guac_common_recording_create(client, + settings->recording_path, + settings->recording_name, + settings->create_recording_path); + } + /* Send name */ guac_protocol_send_name(client->socket, rfb_client->desktopName);