From 652ea5ddf9953d404e681145fa384c5ffa15bf4a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 15:04:30 -0800 Subject: [PATCH 01/92] GUAC-236: Add stub guacenc utility (encode Guacamole protocol to video). --- Makefile.am | 2 + configure.ac | 1 + src/guacenc/.gitignore | 5 +++ src/guacenc/Makefile.am | 42 ++++++++++++++++++++ src/guacenc/encode.c | 54 +++++++++++++++++++++++++ src/guacenc/encode.h | 41 +++++++++++++++++++ src/guacenc/guacenc.c | 75 +++++++++++++++++++++++++++++++++++ src/guacenc/log.c | 87 +++++++++++++++++++++++++++++++++++++++++ src/guacenc/log.h | 76 +++++++++++++++++++++++++++++++++++ 9 files changed, 383 insertions(+) create mode 100644 src/guacenc/.gitignore create mode 100644 src/guacenc/Makefile.am create mode 100644 src/guacenc/encode.c create mode 100644 src/guacenc/encode.h create mode 100644 src/guacenc/guacenc.c create mode 100644 src/guacenc/log.c create mode 100644 src/guacenc/log.h diff --git a/Makefile.am b/Makefile.am index 9b5ab08f..57dc3bda 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 \ @@ -39,6 +40,7 @@ SUBDIRS = \ src/libguac \ src/common \ src/guacd \ + src/guacenc \ tests if ENABLE_COMMON_SSH diff --git a/configure.ac b/configure.ac index 1617c295..d4d2644e 100644 --- a/configure.ac +++ b/configure.ac @@ -974,6 +974,7 @@ 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 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..87f17cba --- /dev/null +++ b/src/guacenc/Makefile.am @@ -0,0 +1,42 @@ +# +# 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 + +noinst_HEADERS = \ + encode.h \ + log.h + +guacenc_SOURCES = \ + encode.c \ + guacenc.c \ + log.c + +guacenc_CFLAGS = \ + -Werror -Wall -pedantic \ + @LIBGUAC_INCLUDE@ + +guacenc_LDADD = \ + @LIBGUAC_LTLIB@ + diff --git a/src/guacenc/encode.c b/src/guacenc/encode.c new file mode 100644 index 00000000..e4d8eca3 --- /dev/null +++ b/src/guacenc/encode.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 "log.h" + +#include + +#include +#include +#include +#include +#include +#include + +int guacenc_encode(const char* path) { + + /* STUB */ + guacenc_log(GUAC_LOG_INFO, "STUB: Encoding \"%s\" ...", path); + + /* Open input file */ + int fd = open(path, O_RDONLY); + if (fd < 0) { + guacenc_log(GUAC_LOG_ERROR, "%s: %s", path, strerror(errno)); + return 1; + } + + /* Close input file */ + close(fd); + + /* Encoding was successful */ + return 0; + +} + diff --git a/src/guacenc/encode.h b/src/guacenc/encode.h new file mode 100644 index 00000000..66f12573 --- /dev/null +++ b/src/guacenc/encode.h @@ -0,0 +1,41 @@ +/* + * 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" + +/** + * Encodes the given Guacamole protocol dump as video. + * + * @param path + * The path to the file containing the raw Guacamole protocol dump. + * + * @return + * Zero on success, non-zero if an error prevented successful encoding of + * the video. + */ +int guacenc_encode(const char* path); + +#endif + diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c new file mode 100644 index 00000000..5fe057ac --- /dev/null +++ b/src/guacenc/guacenc.c @@ -0,0 +1,75 @@ +/* + * 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 "log.h" + +int main(int argc, char* argv[]) { + + int i; + + /* Log start */ + guacenc_log(GUAC_LOG_INFO, "Guacamole video encoder (guacenc) " + "version " VERSION); + + /* Abort if no files given */ + if (argc == 1) { + guacenc_log(GUAC_LOG_INFO, "No input files specified. Nothing to do."); + return 0; + } + + /* Track number of overall failures */ + int failures = 0; + + /* Encode all input files */ + for (i = 1; i < argc; i++) { + + /* Get current filename */ + const char* path = argv[i]; + + /* Attempt encoding, log granular success/failure at debug level */ + if (guacenc_encode(path)) { + 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 file(s).", + failures); + + /* Notify of success */ + else + guacenc_log(GUAC_LOG_INFO, "All files encoded successfully."); + + /* Encoding complete */ + return 0; + +} + diff --git a/src/guacenc/log.c b/src/guacenc/log.c new file mode 100644 index 00000000..f8050930 --- /dev/null +++ b/src/guacenc/log.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 "log.h" + +#include +#include + +#include +#include + +int guacenc_log_level = GUAC_LOG_INFO; + +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 + From 5e5186be07102c24b77ec25be473777de2c4e271 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 15:59:33 -0800 Subject: [PATCH 02/92] GUAC-236: Log failures vs. total files. --- src/guacenc/guacenc.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index 5fe057ac..c5b00efc 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -34,12 +34,13 @@ int main(int argc, char* argv[]) { "version " VERSION); /* Abort if no files given */ - if (argc == 1) { + if (argc <= 1) { guacenc_log(GUAC_LOG_INFO, "No input files specified. Nothing to do."); return 0; } /* Track number of overall failures */ + int total_files = argc - 1; int failures = 0; /* Encode all input files */ @@ -61,8 +62,8 @@ int main(int argc, char* argv[]) { /* Warn if at least one file failed */ if (failures != 0) - guacenc_log(GUAC_LOG_WARNING, "Encoding failed for %i file(s).", - failures); + guacenc_log(GUAC_LOG_WARNING, "Encoding failed for %i of %i file(s).", + failures, total_files); /* Notify of success */ else From c850744faaa05396412ff9fa0b6b280ed15e7496 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 16:05:13 -0800 Subject: [PATCH 03/92] GUAC-236: Actually parse input files. --- src/guacenc/encode.c | 71 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/src/guacenc/encode.c b/src/guacenc/encode.c index e4d8eca3..584fae94 100644 --- a/src/guacenc/encode.c +++ b/src/guacenc/encode.c @@ -24,6 +24,9 @@ #include "log.h" #include +#include +#include +#include #include #include @@ -32,10 +35,51 @@ #include #include -int guacenc_encode(const char* path) { +/** + * Reads and handles all Guacamole instructions from the given guac_socket + * until end-of-stream is reached. + * + * @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(const char* path, guac_socket* socket) { - /* STUB */ - guacenc_log(GUAC_LOG_INFO, "STUB: Encoding \"%s\" ...", path); + /* Obtain Guacamole protocol parser */ + guac_parser* parser = guac_parser_alloc(); + if (parser == NULL) + return 1; + + /* Continuously read instructions */ + while (!guac_parser_read(parser, socket, -1)) { + + /* STUB: Handle instruction */ + guacenc_log(GUAC_LOG_DEBUG, "STUB: \"%s\" ...", parser->opcode); + + } + + /* 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) { /* Open input file */ int fd = open(path, O_RDONLY); @@ -44,10 +88,25 @@ int guacenc_encode(const char* path) { return 1; } - /* Close input file */ - close(fd); + /* 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); + return 1; + } - /* Encoding was successful */ + guacenc_log(GUAC_LOG_INFO, "Encoding \"%s\" ...", path); + + /* Attempt to read all instructions in the file */ + if (guacenc_read_instructions(path, socket)) { + guac_socket_free(socket); + return 1; + } + + /* Parsing/encoding was successful */ + guac_socket_free(socket); return 0; } From 1f54ea10dde65ecc360854c52cee2720d4bdc37b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 16:07:00 -0800 Subject: [PATCH 04/92] GUAC-236: Display DEBUG-level messages for now. --- src/guacenc/log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/guacenc/log.c b/src/guacenc/log.c index f8050930..ba81314f 100644 --- a/src/guacenc/log.c +++ b/src/guacenc/log.c @@ -29,7 +29,7 @@ #include #include -int guacenc_log_level = GUAC_LOG_INFO; +int guacenc_log_level = GUAC_LOG_DEBUG; void vguacenc_log(guac_client_log_level level, const char* format, va_list args) { From a926a6dc3b5a0974498cf999565f1a8e94744710 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 16:59:55 -0800 Subject: [PATCH 05/92] GUAC-236: Handle specific instructions via an opcode/handler mapping. --- src/guacenc/Makefile.am | 12 +++-- src/guacenc/encode.c | 9 ++-- src/guacenc/instructions.c | 77 ++++++++++++++++++++++++++++++ src/guacenc/instructions.h | 98 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 src/guacenc/instructions.c create mode 100644 src/guacenc/instructions.h diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 87f17cba..8c21b4b6 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -24,13 +24,15 @@ AUTOMAKE_OPTIONS = foreign bin_PROGRAMS = guacenc -noinst_HEADERS = \ - encode.h \ +noinst_HEADERS = \ + encode.h \ + instructions.h \ log.h -guacenc_SOURCES = \ - encode.c \ - guacenc.c \ +guacenc_SOURCES = \ + encode.c \ + guacenc.c \ + instructions.c \ log.c guacenc_CFLAGS = \ diff --git a/src/guacenc/encode.c b/src/guacenc/encode.c index 584fae94..ba4050c7 100644 --- a/src/guacenc/encode.c +++ b/src/guacenc/encode.c @@ -21,6 +21,7 @@ */ #include "config.h" +#include "instructions.h" #include "log.h" #include @@ -57,12 +58,10 @@ static int guacenc_read_instructions(const char* path, guac_socket* socket) { if (parser == NULL) return 1; - /* Continuously read instructions */ + /* Continuously read and handle all instructions */ while (!guac_parser_read(parser, socket, -1)) { - - /* STUB: Handle instruction */ - guacenc_log(GUAC_LOG_DEBUG, "STUB: \"%s\" ...", parser->opcode); - + guacenc_handle_instruction(parser->opcode, parser->argc, + (const char**) parser->argv); } /* Fail on read/parse error */ diff --git a/src/guacenc/instructions.c b/src/guacenc/instructions.c new file mode 100644 index 00000000..f05d09c6 --- /dev/null +++ b/src/guacenc/instructions.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 "instructions.h" +#include "log.h" + +#include + +#include + +guacenc_instruction_handler_mapping guacenc_instruction_handler_map[] = { + {"blob", NULL}, + {"img", NULL}, + {"end", NULL}, + {"cursor", NULL}, + {"copy", NULL}, + {"transfer", NULL}, + {"size", NULL}, + {"rect", NULL}, + {"cfill", NULL}, + {"move", NULL}, + {"shade", NULL}, + {"dispose", NULL}, + {NULL, NULL} +}; + +int guacenc_handle_instruction(const char* opcode, + int argc, const 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(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..82b5ad5c --- /dev/null +++ b/src/guacenc/instructions.h @@ -0,0 +1,98 @@ +/* + * 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" + +/** + * 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 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(int argc, const 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 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(const char* opcode, int argc, + const char** argv); + +#endif + From d530d92651fa5b301c403264a138fa47bb4e5e8a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 17:37:54 -0800 Subject: [PATCH 06/92] GUAC-236: Add stub handlers for each required instruction. --- src/guacenc/Makefile.am | 20 ++++++++-- src/guacenc/instruction-blob.c | 29 +++++++++++++++ src/guacenc/instruction-cfill.c | 29 +++++++++++++++ src/guacenc/instruction-copy.c | 29 +++++++++++++++ src/guacenc/instruction-cursor.c | 29 +++++++++++++++ src/guacenc/instruction-dispose.c | 29 +++++++++++++++ src/guacenc/instruction-end.c | 29 +++++++++++++++ src/guacenc/instruction-img.c | 29 +++++++++++++++ src/guacenc/instruction-move.c | 29 +++++++++++++++ src/guacenc/instruction-rect.c | 29 +++++++++++++++ src/guacenc/instruction-shade.c | 29 +++++++++++++++ src/guacenc/instruction-size.c | 29 +++++++++++++++ src/guacenc/instruction-transfer.c | 29 +++++++++++++++ src/guacenc/instructions.c | 24 ++++++------ src/guacenc/instructions.h | 60 ++++++++++++++++++++++++++++++ 15 files changed, 436 insertions(+), 16 deletions(-) create mode 100644 src/guacenc/instruction-blob.c create mode 100644 src/guacenc/instruction-cfill.c create mode 100644 src/guacenc/instruction-copy.c create mode 100644 src/guacenc/instruction-cursor.c create mode 100644 src/guacenc/instruction-dispose.c create mode 100644 src/guacenc/instruction-end.c create mode 100644 src/guacenc/instruction-img.c create mode 100644 src/guacenc/instruction-move.c create mode 100644 src/guacenc/instruction-rect.c create mode 100644 src/guacenc/instruction-shade.c create mode 100644 src/guacenc/instruction-size.c create mode 100644 src/guacenc/instruction-transfer.c diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 8c21b4b6..102d579d 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -29,10 +29,22 @@ noinst_HEADERS = \ instructions.h \ log.h -guacenc_SOURCES = \ - encode.c \ - guacenc.c \ - instructions.c \ +guacenc_SOURCES = \ + encode.c \ + guacenc.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-transfer.c \ log.c guacenc_CFLAGS = \ diff --git a/src/guacenc/instruction-blob.c b/src/guacenc/instruction-blob.c new file mode 100644 index 00000000..a3356414 --- /dev/null +++ b/src/guacenc/instruction-blob.c @@ -0,0 +1,29 @@ +/* + * 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" + +int guacenc_handle_blob(int argc, const char** argv) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/instruction-cfill.c b/src/guacenc/instruction-cfill.c new file mode 100644 index 00000000..419be042 --- /dev/null +++ b/src/guacenc/instruction-cfill.c @@ -0,0 +1,29 @@ +/* + * 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" + +int guacenc_handle_cfill(int argc, const char** argv) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/instruction-copy.c b/src/guacenc/instruction-copy.c new file mode 100644 index 00000000..22589111 --- /dev/null +++ b/src/guacenc/instruction-copy.c @@ -0,0 +1,29 @@ +/* + * 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" + +int guacenc_handle_copy(int argc, const char** argv) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/instruction-cursor.c b/src/guacenc/instruction-cursor.c new file mode 100644 index 00000000..83a2f7f8 --- /dev/null +++ b/src/guacenc/instruction-cursor.c @@ -0,0 +1,29 @@ +/* + * 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" + +int guacenc_handle_cursor(int argc, const char** argv) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/instruction-dispose.c b/src/guacenc/instruction-dispose.c new file mode 100644 index 00000000..f70319e6 --- /dev/null +++ b/src/guacenc/instruction-dispose.c @@ -0,0 +1,29 @@ +/* + * 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" + +int guacenc_handle_dispose(int argc, const char** argv) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/instruction-end.c b/src/guacenc/instruction-end.c new file mode 100644 index 00000000..fe0aa74c --- /dev/null +++ b/src/guacenc/instruction-end.c @@ -0,0 +1,29 @@ +/* + * 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" + +int guacenc_handle_end(int argc, const char** argv) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/instruction-img.c b/src/guacenc/instruction-img.c new file mode 100644 index 00000000..26a3d6d9 --- /dev/null +++ b/src/guacenc/instruction-img.c @@ -0,0 +1,29 @@ +/* + * 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" + +int guacenc_handle_img(int argc, const char** argv) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/instruction-move.c b/src/guacenc/instruction-move.c new file mode 100644 index 00000000..5fab5715 --- /dev/null +++ b/src/guacenc/instruction-move.c @@ -0,0 +1,29 @@ +/* + * 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" + +int guacenc_handle_move(int argc, const char** argv) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/instruction-rect.c b/src/guacenc/instruction-rect.c new file mode 100644 index 00000000..32fc45d6 --- /dev/null +++ b/src/guacenc/instruction-rect.c @@ -0,0 +1,29 @@ +/* + * 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" + +int guacenc_handle_rect(int argc, const char** argv) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/instruction-shade.c b/src/guacenc/instruction-shade.c new file mode 100644 index 00000000..4d5eeb9c --- /dev/null +++ b/src/guacenc/instruction-shade.c @@ -0,0 +1,29 @@ +/* + * 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" + +int guacenc_handle_shade(int argc, const char** argv) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/instruction-size.c b/src/guacenc/instruction-size.c new file mode 100644 index 00000000..a4520f30 --- /dev/null +++ b/src/guacenc/instruction-size.c @@ -0,0 +1,29 @@ +/* + * 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" + +int guacenc_handle_size(int argc, const char** argv) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/instruction-transfer.c b/src/guacenc/instruction-transfer.c new file mode 100644 index 00000000..c2123985 --- /dev/null +++ b/src/guacenc/instruction-transfer.c @@ -0,0 +1,29 @@ +/* + * 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" + +int guacenc_handle_transfer(int argc, const char** argv) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/instructions.c b/src/guacenc/instructions.c index f05d09c6..015ed929 100644 --- a/src/guacenc/instructions.c +++ b/src/guacenc/instructions.c @@ -29,18 +29,18 @@ #include guacenc_instruction_handler_mapping guacenc_instruction_handler_map[] = { - {"blob", NULL}, - {"img", NULL}, - {"end", NULL}, - {"cursor", NULL}, - {"copy", NULL}, - {"transfer", NULL}, - {"size", NULL}, - {"rect", NULL}, - {"cfill", NULL}, - {"move", NULL}, - {"shade", NULL}, - {"dispose", NULL}, + {"blob", guacenc_handle_blob}, + {"img", guacenc_handle_img}, + {"end", guacenc_handle_end}, + {"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} }; diff --git a/src/guacenc/instructions.h b/src/guacenc/instructions.h index 82b5ad5c..0f7951d6 100644 --- a/src/guacenc/instructions.h +++ b/src/guacenc/instructions.h @@ -94,5 +94,65 @@ extern guacenc_instruction_handler_mapping guacenc_instruction_handler_map[]; int guacenc_handle_instruction(const char* opcode, int argc, const 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 "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 From 0e5a7bb5c2a54d587c6872c221e75cd7a82ec39d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 18:35:15 -0800 Subject: [PATCH 07/92] GUAC-236: Add argument parsing stubs to instruction handlers. --- src/guacenc/instruction-blob.c | 20 ++++++++++++++++++++ src/guacenc/instruction-cfill.c | 23 +++++++++++++++++++++++ src/guacenc/instruction-copy.c | 27 +++++++++++++++++++++++++++ src/guacenc/instruction-cursor.c | 25 +++++++++++++++++++++++++ src/guacenc/instruction-dispose.c | 17 +++++++++++++++++ src/guacenc/instruction-end.c | 17 +++++++++++++++++ src/guacenc/instruction-img.c | 25 +++++++++++++++++++++++++ src/guacenc/instruction-move.c | 22 ++++++++++++++++++++++ src/guacenc/instruction-rect.c | 22 ++++++++++++++++++++++ src/guacenc/instruction-shade.c | 18 ++++++++++++++++++ src/guacenc/instruction-size.c | 19 +++++++++++++++++++ src/guacenc/instruction-transfer.c | 27 +++++++++++++++++++++++++++ 12 files changed, 262 insertions(+) diff --git a/src/guacenc/instruction-blob.c b/src/guacenc/instruction-blob.c index a3356414..e69fe491 100644 --- a/src/guacenc/instruction-blob.c +++ b/src/guacenc/instruction-blob.c @@ -21,9 +21,29 @@ */ #include "config.h" +#include "log.h" + +#include + +#include +#include int guacenc_handle_blob(int argc, const char** argv) { + + /* Verify argument count */ + if (argc < 2) { + guacenc_log(GUAC_LOG_DEBUG, "\"blob\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int index = atoi(argv[0]); + const char* data = argv[1]; + /* STUB */ + guacenc_log(GUAC_LOG_DEBUG, "blob: stream=%i data=[%i chars]", + index, strlen(data)); return 0; + } diff --git a/src/guacenc/instruction-cfill.c b/src/guacenc/instruction-cfill.c index 419be042..2042173c 100644 --- a/src/guacenc/instruction-cfill.c +++ b/src/guacenc/instruction-cfill.c @@ -21,9 +21,32 @@ */ #include "config.h" +#include "log.h" + +#include + +#include int guacenc_handle_cfill(int argc, const char** argv) { + + /* Verify argument count */ + if (argc < 6) { + guacenc_log(GUAC_LOG_DEBUG, "\"cfill\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int mask = atoi(argv[0]); + int index = atoi(argv[1]); + int r = atoi(argv[2]); + int g = atoi(argv[3]); + int b = atoi(argv[4]); + int a = atoi(argv[5]); + /* STUB */ + guacenc_log(GUAC_LOG_DEBUG, "cfill: mask=0x%X layer=%i " + "rgba(%i, %i, %i, %i)", mask, index, r, g, b, a); return 0; + } diff --git a/src/guacenc/instruction-copy.c b/src/guacenc/instruction-copy.c index 22589111..ce82e82b 100644 --- a/src/guacenc/instruction-copy.c +++ b/src/guacenc/instruction-copy.c @@ -21,9 +21,36 @@ */ #include "config.h" +#include "log.h" + +#include + +#include int guacenc_handle_copy(int argc, const char** argv) { + + /* Verify argument count */ + if (argc < 9) { + guacenc_log(GUAC_LOG_DEBUG, "\"copy\" 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 mask = atoi(argv[5]); + int dst_index = atoi(argv[6]); + int dst_x = atoi(argv[7]); + int dst_y = atoi(argv[8]); + /* STUB */ + guacenc_log(GUAC_LOG_DEBUG, "copy: src_layer=%i (%i, %i) %ix%i mask=0x%X " + "dst_layer=%i (%i, %i)", src_index, src_x, src_y, src_w, src_h, + mask, dst_index, dst_x, dst_y); return 0; + } diff --git a/src/guacenc/instruction-cursor.c b/src/guacenc/instruction-cursor.c index 83a2f7f8..8d3a5b34 100644 --- a/src/guacenc/instruction-cursor.c +++ b/src/guacenc/instruction-cursor.c @@ -21,9 +21,34 @@ */ #include "config.h" +#include "log.h" + +#include + +#include int guacenc_handle_cursor(int argc, const char** argv) { + + /* Verify argument count */ + if (argc < 7) { + guacenc_log(GUAC_LOG_DEBUG, "\"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]); + /* STUB */ + guacenc_log(GUAC_LOG_DEBUG, "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 index f70319e6..0b280a0f 100644 --- a/src/guacenc/instruction-dispose.c +++ b/src/guacenc/instruction-dispose.c @@ -21,9 +21,26 @@ */ #include "config.h" +#include "log.h" + +#include + +#include int guacenc_handle_dispose(int argc, const char** argv) { + + /* Verify argument count */ + if (argc < 1) { + guacenc_log(GUAC_LOG_DEBUG, "\"dispose\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int index = atoi(argv[0]); + /* STUB */ + guacenc_log(GUAC_LOG_DEBUG, "dispose: layer=%i", index); return 0; + } diff --git a/src/guacenc/instruction-end.c b/src/guacenc/instruction-end.c index fe0aa74c..a99a0e45 100644 --- a/src/guacenc/instruction-end.c +++ b/src/guacenc/instruction-end.c @@ -21,9 +21,26 @@ */ #include "config.h" +#include "log.h" + +#include + +#include int guacenc_handle_end(int argc, const char** argv) { + + /* Verify argument count */ + if (argc < 1) { + guacenc_log(GUAC_LOG_DEBUG, "\"end\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int index = atoi(argv[0]); + /* STUB */ + guacenc_log(GUAC_LOG_DEBUG, "end: stream=%i", index); return 0; + } diff --git a/src/guacenc/instruction-img.c b/src/guacenc/instruction-img.c index 26a3d6d9..9e2dd312 100644 --- a/src/guacenc/instruction-img.c +++ b/src/guacenc/instruction-img.c @@ -21,9 +21,34 @@ */ #include "config.h" +#include "log.h" + +#include + +#include int guacenc_handle_img(int argc, const char** argv) { + + /* Verify argument count */ + if (argc < 6) { + guacenc_log(GUAC_LOG_DEBUG, "\"img\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int stream_index = atoi(argv[0]); + int mask = atoi(argv[1]); + int layer_index = atoi(argv[2]); + const char* mimetype = argv[3]; + int x = atoi(argv[4]); + int y = atoi(argv[5]); + /* STUB */ + guacenc_log(GUAC_LOG_DEBUG, "img: stream=%i mask=0x%X layer=%i " + "mimetype=%s (%i, %i)", stream_index, mask, layer_index, + mimetype, x, y); + return 0; + } diff --git a/src/guacenc/instruction-move.c b/src/guacenc/instruction-move.c index 5fab5715..2e916929 100644 --- a/src/guacenc/instruction-move.c +++ b/src/guacenc/instruction-move.c @@ -21,9 +21,31 @@ */ #include "config.h" +#include "log.h" + +#include + +#include int guacenc_handle_move(int argc, const char** argv) { + + /* Verify argument count */ + if (argc < 5) { + guacenc_log(GUAC_LOG_DEBUG, "\"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]); + /* STUB */ + guacenc_log(GUAC_LOG_DEBUG, "move: layer=%i parent=%i (%i, %i) z=%i", + layer_index, parent_index, x, y, z); return 0; + } diff --git a/src/guacenc/instruction-rect.c b/src/guacenc/instruction-rect.c index 32fc45d6..85e229f0 100644 --- a/src/guacenc/instruction-rect.c +++ b/src/guacenc/instruction-rect.c @@ -21,9 +21,31 @@ */ #include "config.h" +#include "log.h" + +#include + +#include int guacenc_handle_rect(int argc, const char** argv) { + + /* Verify argument count */ + if (argc < 5) { + guacenc_log(GUAC_LOG_DEBUG, "\"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]); + /* STUB */ + guacenc_log(GUAC_LOG_DEBUG, "rect: layer=%i (%i, %i) %ix%i", + index, x, y, width, height); return 0; + } diff --git a/src/guacenc/instruction-shade.c b/src/guacenc/instruction-shade.c index 4d5eeb9c..48e6d7aa 100644 --- a/src/guacenc/instruction-shade.c +++ b/src/guacenc/instruction-shade.c @@ -21,9 +21,27 @@ */ #include "config.h" +#include "log.h" + +#include + +#include int guacenc_handle_shade(int argc, const char** argv) { + + /* Verify argument count */ + if (argc < 2) { + guacenc_log(GUAC_LOG_DEBUG, "\"shade\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int index = atoi(argv[0]); + int opacity = atoi(argv[1]); + /* STUB */ + guacenc_log(GUAC_LOG_DEBUG, "shade: layer=%i opacity=0x%X", index, opacity); return 0; + } diff --git a/src/guacenc/instruction-size.c b/src/guacenc/instruction-size.c index a4520f30..7057bcc4 100644 --- a/src/guacenc/instruction-size.c +++ b/src/guacenc/instruction-size.c @@ -21,9 +21,28 @@ */ #include "config.h" +#include "log.h" + +#include + +#include int guacenc_handle_size(int argc, const char** argv) { + + /* Verify argument count */ + if (argc < 3) { + guacenc_log(GUAC_LOG_DEBUG, "\"size\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int index = atoi(argv[0]); + int width = atoi(argv[1]); + int height = atoi(argv[2]); + /* STUB */ + guacenc_log(GUAC_LOG_DEBUG, "size: layer=%i %ix%i", index, width, height); return 0; + } diff --git a/src/guacenc/instruction-transfer.c b/src/guacenc/instruction-transfer.c index c2123985..ae0f4ea7 100644 --- a/src/guacenc/instruction-transfer.c +++ b/src/guacenc/instruction-transfer.c @@ -21,9 +21,36 @@ */ #include "config.h" +#include "log.h" + +#include + +#include int guacenc_handle_transfer(int argc, const char** argv) { + + /* Verify argument count */ + if (argc < 9) { + guacenc_log(GUAC_LOG_DEBUG, "\"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]); + /* STUB */ + 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; + } From 91197ffad22cb301ff26804790b148f3da2a9e8b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 21:19:10 -0800 Subject: [PATCH 08/92] GUAC-236: Allow instruction handlers to touch the argument values. --- src/guacenc/encode.c | 3 +-- src/guacenc/instruction-blob.c | 4 ++-- src/guacenc/instruction-cfill.c | 2 +- src/guacenc/instruction-copy.c | 2 +- src/guacenc/instruction-cursor.c | 2 +- src/guacenc/instruction-dispose.c | 2 +- src/guacenc/instruction-end.c | 2 +- src/guacenc/instruction-img.c | 4 ++-- src/guacenc/instruction-move.c | 2 +- src/guacenc/instruction-rect.c | 2 +- src/guacenc/instruction-shade.c | 2 +- src/guacenc/instruction-size.c | 2 +- src/guacenc/instruction-transfer.c | 2 +- src/guacenc/instructions.c | 2 +- src/guacenc/instructions.h | 5 ++--- 15 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/guacenc/encode.c b/src/guacenc/encode.c index ba4050c7..5c2820c6 100644 --- a/src/guacenc/encode.c +++ b/src/guacenc/encode.c @@ -60,8 +60,7 @@ static int guacenc_read_instructions(const char* path, guac_socket* socket) { /* Continuously read and handle all instructions */ while (!guac_parser_read(parser, socket, -1)) { - guacenc_handle_instruction(parser->opcode, parser->argc, - (const char**) parser->argv); + guacenc_handle_instruction(parser->opcode, parser->argc, parser->argv); } /* Fail on read/parse error */ diff --git a/src/guacenc/instruction-blob.c b/src/guacenc/instruction-blob.c index e69fe491..10270e23 100644 --- a/src/guacenc/instruction-blob.c +++ b/src/guacenc/instruction-blob.c @@ -28,7 +28,7 @@ #include #include -int guacenc_handle_blob(int argc, const char** argv) { +int guacenc_handle_blob(int argc, char** argv) { /* Verify argument count */ if (argc < 2) { @@ -38,7 +38,7 @@ int guacenc_handle_blob(int argc, const char** argv) { /* Parse arguments */ int index = atoi(argv[0]); - const char* data = argv[1]; + char* data = argv[1]; /* STUB */ guacenc_log(GUAC_LOG_DEBUG, "blob: stream=%i data=[%i chars]", diff --git a/src/guacenc/instruction-cfill.c b/src/guacenc/instruction-cfill.c index 2042173c..ff35f756 100644 --- a/src/guacenc/instruction-cfill.c +++ b/src/guacenc/instruction-cfill.c @@ -27,7 +27,7 @@ #include -int guacenc_handle_cfill(int argc, const char** argv) { +int guacenc_handle_cfill(int argc, char** argv) { /* Verify argument count */ if (argc < 6) { diff --git a/src/guacenc/instruction-copy.c b/src/guacenc/instruction-copy.c index ce82e82b..56f90fa2 100644 --- a/src/guacenc/instruction-copy.c +++ b/src/guacenc/instruction-copy.c @@ -27,7 +27,7 @@ #include -int guacenc_handle_copy(int argc, const char** argv) { +int guacenc_handle_copy(int argc, char** argv) { /* Verify argument count */ if (argc < 9) { diff --git a/src/guacenc/instruction-cursor.c b/src/guacenc/instruction-cursor.c index 8d3a5b34..2b237a25 100644 --- a/src/guacenc/instruction-cursor.c +++ b/src/guacenc/instruction-cursor.c @@ -27,7 +27,7 @@ #include -int guacenc_handle_cursor(int argc, const char** argv) { +int guacenc_handle_cursor(int argc, char** argv) { /* Verify argument count */ if (argc < 7) { diff --git a/src/guacenc/instruction-dispose.c b/src/guacenc/instruction-dispose.c index 0b280a0f..17bf320e 100644 --- a/src/guacenc/instruction-dispose.c +++ b/src/guacenc/instruction-dispose.c @@ -27,7 +27,7 @@ #include -int guacenc_handle_dispose(int argc, const char** argv) { +int guacenc_handle_dispose(int argc, char** argv) { /* Verify argument count */ if (argc < 1) { diff --git a/src/guacenc/instruction-end.c b/src/guacenc/instruction-end.c index a99a0e45..55e11a39 100644 --- a/src/guacenc/instruction-end.c +++ b/src/guacenc/instruction-end.c @@ -27,7 +27,7 @@ #include -int guacenc_handle_end(int argc, const char** argv) { +int guacenc_handle_end(int argc, char** argv) { /* Verify argument count */ if (argc < 1) { diff --git a/src/guacenc/instruction-img.c b/src/guacenc/instruction-img.c index 9e2dd312..5cf2c884 100644 --- a/src/guacenc/instruction-img.c +++ b/src/guacenc/instruction-img.c @@ -27,7 +27,7 @@ #include -int guacenc_handle_img(int argc, const char** argv) { +int guacenc_handle_img(int argc, char** argv) { /* Verify argument count */ if (argc < 6) { @@ -39,7 +39,7 @@ int guacenc_handle_img(int argc, const char** argv) { int stream_index = atoi(argv[0]); int mask = atoi(argv[1]); int layer_index = atoi(argv[2]); - const char* mimetype = argv[3]; + char* mimetype = argv[3]; int x = atoi(argv[4]); int y = atoi(argv[5]); diff --git a/src/guacenc/instruction-move.c b/src/guacenc/instruction-move.c index 2e916929..5472361b 100644 --- a/src/guacenc/instruction-move.c +++ b/src/guacenc/instruction-move.c @@ -27,7 +27,7 @@ #include -int guacenc_handle_move(int argc, const char** argv) { +int guacenc_handle_move(int argc, char** argv) { /* Verify argument count */ if (argc < 5) { diff --git a/src/guacenc/instruction-rect.c b/src/guacenc/instruction-rect.c index 85e229f0..f9609ab7 100644 --- a/src/guacenc/instruction-rect.c +++ b/src/guacenc/instruction-rect.c @@ -27,7 +27,7 @@ #include -int guacenc_handle_rect(int argc, const char** argv) { +int guacenc_handle_rect(int argc, char** argv) { /* Verify argument count */ if (argc < 5) { diff --git a/src/guacenc/instruction-shade.c b/src/guacenc/instruction-shade.c index 48e6d7aa..f796c064 100644 --- a/src/guacenc/instruction-shade.c +++ b/src/guacenc/instruction-shade.c @@ -27,7 +27,7 @@ #include -int guacenc_handle_shade(int argc, const char** argv) { +int guacenc_handle_shade(int argc, char** argv) { /* Verify argument count */ if (argc < 2) { diff --git a/src/guacenc/instruction-size.c b/src/guacenc/instruction-size.c index 7057bcc4..1817bfb1 100644 --- a/src/guacenc/instruction-size.c +++ b/src/guacenc/instruction-size.c @@ -27,7 +27,7 @@ #include -int guacenc_handle_size(int argc, const char** argv) { +int guacenc_handle_size(int argc, char** argv) { /* Verify argument count */ if (argc < 3) { diff --git a/src/guacenc/instruction-transfer.c b/src/guacenc/instruction-transfer.c index ae0f4ea7..ff37d282 100644 --- a/src/guacenc/instruction-transfer.c +++ b/src/guacenc/instruction-transfer.c @@ -27,7 +27,7 @@ #include -int guacenc_handle_transfer(int argc, const char** argv) { +int guacenc_handle_transfer(int argc, char** argv) { /* Verify argument count */ if (argc < 9) { diff --git a/src/guacenc/instructions.c b/src/guacenc/instructions.c index 015ed929..81329cf2 100644 --- a/src/guacenc/instructions.c +++ b/src/guacenc/instructions.c @@ -45,7 +45,7 @@ guacenc_instruction_handler_mapping guacenc_instruction_handler_map[] = { }; int guacenc_handle_instruction(const char* opcode, - int argc, const char** argv) { + int argc, char** argv) { /* Search through mapping for instruction handler having given opcode */ guacenc_instruction_handler_mapping* current = guacenc_instruction_handler_map; diff --git a/src/guacenc/instructions.h b/src/guacenc/instructions.h index 0f7951d6..5f8d6728 100644 --- a/src/guacenc/instructions.h +++ b/src/guacenc/instructions.h @@ -44,7 +44,7 @@ * Zero if the instruction was handled successfully, non-zero if an error * occurs. */ -typedef int guacenc_instruction_handler(int argc, const char** argv); +typedef int guacenc_instruction_handler(int argc, char** argv); /** * Mapping of instruction opcode to corresponding handler function. @@ -91,8 +91,7 @@ extern guacenc_instruction_handler_mapping guacenc_instruction_handler_map[]; * Zero if the instruction was handled successfully, non-zero if an error * occurs. */ -int guacenc_handle_instruction(const char* opcode, int argc, - const char** argv); +int guacenc_handle_instruction(const char* opcode, int argc, char** argv); /** * Handler for the Guacamole "blob" instruction. From f842d1c0a4081ea94d1be7ae23f9695dc7e7693b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 21:22:45 -0800 Subject: [PATCH 09/92] GUAC-236: Parse base64 from blobs. --- src/guacenc/instruction-blob.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/guacenc/instruction-blob.c b/src/guacenc/instruction-blob.c index 10270e23..a149bfd8 100644 --- a/src/guacenc/instruction-blob.c +++ b/src/guacenc/instruction-blob.c @@ -24,9 +24,9 @@ #include "log.h" #include +#include #include -#include int guacenc_handle_blob(int argc, char** argv) { @@ -39,10 +39,11 @@ int guacenc_handle_blob(int argc, char** argv) { /* Parse arguments */ int index = atoi(argv[0]); char* data = argv[1]; + int data_length = guac_protocol_decode_base64(data); /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "blob: stream=%i data=[%i chars]", - index, strlen(data)); + guacenc_log(GUAC_LOG_DEBUG, "blob: stream=%i data=[%i bytes]", + index, data_length); return 0; } From 23af2d30412f80672510470b4c913b106ca19ecb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 23:25:10 -0800 Subject: [PATCH 10/92] GUAC-236: Handle sync instruction. --- src/guacenc/Makefile.am | 1 + src/guacenc/instruction-sync.c | 82 ++++++++++++++++++++++++++++++++++ src/guacenc/instructions.c | 1 + src/guacenc/instructions.h | 5 +++ 4 files changed, 89 insertions(+) create mode 100644 src/guacenc/instruction-sync.c diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 102d579d..7824cf14 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -44,6 +44,7 @@ guacenc_SOURCES = \ instruction-rect.c \ instruction-shade.c \ instruction-size.c \ + instruction-sync.c \ instruction-transfer.c \ log.c diff --git a/src/guacenc/instruction-sync.c b/src/guacenc/instruction-sync.c new file mode 100644 index 00000000..5f818743 --- /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 "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(int argc, char** argv) { + + /* Verify argument count */ + if (argc < 1) { + guacenc_log(GUAC_LOG_DEBUG, "\"sync\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + guac_timestamp timestamp = guacenc_parse_timestamp(argv[0]); + + /* STUB */ + guacenc_log(GUAC_LOG_DEBUG, "sync: timestamp=%" PRId64, timestamp); + return 0; + +} + diff --git a/src/guacenc/instructions.c b/src/guacenc/instructions.c index 81329cf2..d2324096 100644 --- a/src/guacenc/instructions.c +++ b/src/guacenc/instructions.c @@ -32,6 +32,7 @@ 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}, diff --git a/src/guacenc/instructions.h b/src/guacenc/instructions.h index 5f8d6728..0139ddb4 100644 --- a/src/guacenc/instructions.h +++ b/src/guacenc/instructions.h @@ -108,6 +108,11 @@ guacenc_instruction_handler guacenc_handle_img; */ guacenc_instruction_handler guacenc_handle_end; +/** + * Handler for the Guacamole "sync" instruction. + */ +guacenc_instruction_handler guacenc_handle_sync; + /** * Handler for the Guacamole "cursor" instruction. */ From f286bd92c73a352c740649270e517bff830fe5f4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 23:26:00 -0800 Subject: [PATCH 11/92] GUAC-236: Add display, buffer, image, and stream abstractions. --- src/guacenc/Makefile.am | 5 ++ src/guacenc/buffer.h | 47 +++++++++++++++++ src/guacenc/display.c | 32 +++++++++++ src/guacenc/display.h | 105 +++++++++++++++++++++++++++++++++++++ src/guacenc/image-stream.h | 42 +++++++++++++++ src/guacenc/layer.h | 80 ++++++++++++++++++++++++++++ 6 files changed, 311 insertions(+) create mode 100644 src/guacenc/buffer.h create mode 100644 src/guacenc/display.c create mode 100644 src/guacenc/display.h create mode 100644 src/guacenc/image-stream.h create mode 100644 src/guacenc/layer.h diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 7824cf14..39b31e0e 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -25,11 +25,16 @@ AUTOMAKE_OPTIONS = foreign bin_PROGRAMS = guacenc noinst_HEADERS = \ + buffer.h \ + display.h \ encode.h \ + image-stream.h \ instructions.h \ + layer.h \ log.h guacenc_SOURCES = \ + display.c \ encode.c \ guacenc.c \ instructions.c \ diff --git a/src/guacenc/buffer.h b/src/guacenc/buffer.h new file mode 100644 index 00000000..6bd3ce3c --- /dev/null +++ b/src/guacenc/buffer.h @@ -0,0 +1,47 @@ +/* + * 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" + +/** + * 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 { + + /** + * The width of this buffer or layer, in pixels. + */ + int width; + + /** + * The height of this buffer or layer, in pixels. + */ + int height; + +} guacenc_buffer; + +#endif + diff --git a/src/guacenc/display.c b/src/guacenc/display.c new file mode 100644 index 00000000..fa800804 --- /dev/null +++ b/src/guacenc/display.c @@ -0,0 +1,32 @@ +/* + * 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 + +int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) { + /* STUB */ + return 0; +} + diff --git a/src/guacenc/display.h b/src/guacenc/display.h new file mode 100644 index 00000000..3f438a4b --- /dev/null +++ b/src/guacenc/display.h @@ -0,0 +1,105 @@ +/* + * 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 + +/** + * 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; + +} 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); + +#endif + diff --git a/src/guacenc/image-stream.h b/src/guacenc/image-stream.h new file mode 100644 index 00000000..d88897d4 --- /dev/null +++ b/src/guacenc/image-stream.h @@ -0,0 +1,42 @@ +/* + * 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" + +/** + * The current state of an allocated Guacamole image stream. + */ +typedef struct guacenc_image_stream { + + /** + * STUB: Placeholder property. This property exists only so that the + * guacenc_image_stream struct can be defined prior to implementation. + */ + int __PLACEHOLDER; + +} guacenc_image_stream; + +#endif + diff --git a/src/guacenc/layer.h b/src/guacenc/layer.h new file mode 100644 index 00000000..739d2aa1 --- /dev/null +++ b/src/guacenc/layer.h @@ -0,0 +1,80 @@ +/* + * 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; + +} guacenc_layer; + +#endif + From 4634ce391a79c87afde266467c68b9bdd19f0d32 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 23:41:11 -0800 Subject: [PATCH 12/92] GUAC-236: Allocate and pass display to all instruction handlers. --- src/guacenc/display.c | 11 +++++++++++ src/guacenc/display.h | 23 +++++++++++++++++++++++ src/guacenc/encode.c | 24 +++++++++++++++++++----- src/guacenc/instruction-blob.c | 3 ++- src/guacenc/instruction-cfill.c | 3 ++- src/guacenc/instruction-copy.c | 3 ++- src/guacenc/instruction-cursor.c | 3 ++- src/guacenc/instruction-dispose.c | 3 ++- src/guacenc/instruction-end.c | 3 ++- src/guacenc/instruction-img.c | 3 ++- src/guacenc/instruction-move.c | 3 ++- src/guacenc/instruction-rect.c | 3 ++- src/guacenc/instruction-shade.c | 3 ++- src/guacenc/instruction-size.c | 3 ++- src/guacenc/instruction-sync.c | 3 ++- src/guacenc/instruction-transfer.c | 3 ++- src/guacenc/instructions.c | 5 +++-- src/guacenc/instructions.h | 13 +++++++++++-- 18 files changed, 93 insertions(+), 22 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index fa800804..6a1e32c8 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -25,8 +25,19 @@ #include +#include + int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) { /* STUB */ return 0; } +guacenc_display* guacenc_display_alloc() { + return (guacenc_display*) calloc(1, sizeof(guacenc_display)); +} + +int guacenc_display_free(guacenc_display* display) { + free(display); + return 0; +} + diff --git a/src/guacenc/display.h b/src/guacenc/display.h index 3f438a4b..e4638db0 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -101,5 +101,28 @@ typedef struct guacenc_display { */ int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp); +/** + * 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. + * + * @return + * The newly-allocated Guacamole video encoder display, or NULL if the + * display could not be allocated. + */ +guacenc_display* guacenc_display_alloc(); + +/** + * Frees all memory associated with the given Guacamole video encoder display, + * and finishes any underlying encoding process. + * + * @param display + * The Guacamole video encoder display to free. + * + * @return + * Zero if the encoding process completed successfully, non-zero otherwise. + */ +int guacenc_display_free(guacenc_display* display); + #endif diff --git a/src/guacenc/encode.c b/src/guacenc/encode.c index 5c2820c6..91cfc0df 100644 --- a/src/guacenc/encode.c +++ b/src/guacenc/encode.c @@ -21,6 +21,7 @@ */ #include "config.h" +#include "display.h" #include "instructions.h" #include "log.h" @@ -40,6 +41,9 @@ * 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. @@ -51,7 +55,8 @@ * Zero on success, non-zero if parsing of Guacamole protocol data through * the given socket fails. */ -static int guacenc_read_instructions(const char* path, guac_socket* socket) { +static int guacenc_read_instructions(guacenc_display* display, + const char* path, guac_socket* socket) { /* Obtain Guacamole protocol parser */ guac_parser* parser = guac_parser_alloc(); @@ -60,7 +65,8 @@ static int guacenc_read_instructions(const char* path, guac_socket* socket) { /* Continuously read and handle all instructions */ while (!guac_parser_read(parser, socket, -1)) { - guacenc_handle_instruction(parser->opcode, parser->argc, parser->argv); + guacenc_handle_instruction(display, parser->opcode, + parser->argc, parser->argv); } /* Fail on read/parse error */ @@ -79,10 +85,16 @@ static int guacenc_read_instructions(const char* path, guac_socket* socket) { int guacenc_encode(const char* path) { + /* Allocate display for encoding process */ + guacenc_display* display = guacenc_display_alloc(); + if (display == NULL) + return 1; + /* Open input file */ int fd = open(path, O_RDONLY); if (fd < 0) { guacenc_log(GUAC_LOG_ERROR, "%s: %s", path, strerror(errno)); + guacenc_display_free(display); return 1; } @@ -92,20 +104,22 @@ int guacenc_encode(const char* path) { 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\" ...", path); /* Attempt to read all instructions in the file */ - if (guacenc_read_instructions(path, socket)) { + if (guacenc_read_instructions(display, path, socket)) { guac_socket_free(socket); + guacenc_display_free(display); return 1; } - /* Parsing/encoding was successful */ + /* Close input and finish encoding process */ guac_socket_free(socket); - return 0; + return guacenc_display_free(display); } diff --git a/src/guacenc/instruction-blob.c b/src/guacenc/instruction-blob.c index a149bfd8..f608e036 100644 --- a/src/guacenc/instruction-blob.c +++ b/src/guacenc/instruction-blob.c @@ -21,6 +21,7 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include @@ -28,7 +29,7 @@ #include -int guacenc_handle_blob(int argc, char** argv) { +int guacenc_handle_blob(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 2) { diff --git a/src/guacenc/instruction-cfill.c b/src/guacenc/instruction-cfill.c index ff35f756..5f83c59f 100644 --- a/src/guacenc/instruction-cfill.c +++ b/src/guacenc/instruction-cfill.c @@ -21,13 +21,14 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include #include -int guacenc_handle_cfill(int argc, char** argv) { +int guacenc_handle_cfill(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 6) { diff --git a/src/guacenc/instruction-copy.c b/src/guacenc/instruction-copy.c index 56f90fa2..5dbfcb1d 100644 --- a/src/guacenc/instruction-copy.c +++ b/src/guacenc/instruction-copy.c @@ -21,13 +21,14 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include #include -int guacenc_handle_copy(int argc, char** argv) { +int guacenc_handle_copy(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 9) { diff --git a/src/guacenc/instruction-cursor.c b/src/guacenc/instruction-cursor.c index 2b237a25..dc66c896 100644 --- a/src/guacenc/instruction-cursor.c +++ b/src/guacenc/instruction-cursor.c @@ -21,13 +21,14 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include #include -int guacenc_handle_cursor(int argc, char** argv) { +int guacenc_handle_cursor(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 7) { diff --git a/src/guacenc/instruction-dispose.c b/src/guacenc/instruction-dispose.c index 17bf320e..8d8333f3 100644 --- a/src/guacenc/instruction-dispose.c +++ b/src/guacenc/instruction-dispose.c @@ -21,13 +21,14 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include #include -int guacenc_handle_dispose(int argc, char** argv) { +int guacenc_handle_dispose(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 1) { diff --git a/src/guacenc/instruction-end.c b/src/guacenc/instruction-end.c index 55e11a39..3e300e8b 100644 --- a/src/guacenc/instruction-end.c +++ b/src/guacenc/instruction-end.c @@ -21,13 +21,14 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include #include -int guacenc_handle_end(int argc, char** argv) { +int guacenc_handle_end(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 1) { diff --git a/src/guacenc/instruction-img.c b/src/guacenc/instruction-img.c index 5cf2c884..d67fc402 100644 --- a/src/guacenc/instruction-img.c +++ b/src/guacenc/instruction-img.c @@ -21,13 +21,14 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include #include -int guacenc_handle_img(int argc, char** argv) { +int guacenc_handle_img(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 6) { diff --git a/src/guacenc/instruction-move.c b/src/guacenc/instruction-move.c index 5472361b..88e5d606 100644 --- a/src/guacenc/instruction-move.c +++ b/src/guacenc/instruction-move.c @@ -21,13 +21,14 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include #include -int guacenc_handle_move(int argc, char** argv) { +int guacenc_handle_move(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 5) { diff --git a/src/guacenc/instruction-rect.c b/src/guacenc/instruction-rect.c index f9609ab7..9202edf8 100644 --- a/src/guacenc/instruction-rect.c +++ b/src/guacenc/instruction-rect.c @@ -21,13 +21,14 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include #include -int guacenc_handle_rect(int argc, char** argv) { +int guacenc_handle_rect(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 5) { diff --git a/src/guacenc/instruction-shade.c b/src/guacenc/instruction-shade.c index f796c064..466568c9 100644 --- a/src/guacenc/instruction-shade.c +++ b/src/guacenc/instruction-shade.c @@ -21,13 +21,14 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include #include -int guacenc_handle_shade(int argc, char** argv) { +int guacenc_handle_shade(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 2) { diff --git a/src/guacenc/instruction-size.c b/src/guacenc/instruction-size.c index 1817bfb1..24375bc7 100644 --- a/src/guacenc/instruction-size.c +++ b/src/guacenc/instruction-size.c @@ -21,13 +21,14 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include #include -int guacenc_handle_size(int argc, char** argv) { +int guacenc_handle_size(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 3) { diff --git a/src/guacenc/instruction-sync.c b/src/guacenc/instruction-sync.c index 5f818743..ab789724 100644 --- a/src/guacenc/instruction-sync.c +++ b/src/guacenc/instruction-sync.c @@ -21,6 +21,7 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include @@ -63,7 +64,7 @@ static guac_timestamp guacenc_parse_timestamp(const char* str) { } -int guacenc_handle_sync(int argc, char** argv) { +int guacenc_handle_sync(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 1) { diff --git a/src/guacenc/instruction-transfer.c b/src/guacenc/instruction-transfer.c index ff37d282..f95a2a8f 100644 --- a/src/guacenc/instruction-transfer.c +++ b/src/guacenc/instruction-transfer.c @@ -21,13 +21,14 @@ */ #include "config.h" +#include "display.h" #include "log.h" #include #include -int guacenc_handle_transfer(int argc, char** argv) { +int guacenc_handle_transfer(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 9) { diff --git a/src/guacenc/instructions.c b/src/guacenc/instructions.c index d2324096..ac9fbdf6 100644 --- a/src/guacenc/instructions.c +++ b/src/guacenc/instructions.c @@ -21,6 +21,7 @@ */ #include "config.h" +#include "display.h" #include "instructions.h" #include "log.h" @@ -45,7 +46,7 @@ guacenc_instruction_handler_mapping guacenc_instruction_handler_map[] = { {NULL, NULL} }; -int guacenc_handle_instruction(const char* opcode, +int guacenc_handle_instruction(guacenc_display* display, const char* opcode, int argc, char** argv) { /* Search through mapping for instruction handler having given opcode */ @@ -58,7 +59,7 @@ int guacenc_handle_instruction(const char* opcode, /* Invoke defined handler */ guacenc_instruction_handler* handler = current->handler; if (handler != NULL) - return handler(argc, argv); + return handler(display, argc, argv); /* Log defined but unimplemented instructions */ guacenc_log(GUAC_LOG_DEBUG, "\"%s\" not implemented", opcode); diff --git a/src/guacenc/instructions.h b/src/guacenc/instructions.h index 0139ddb4..c015b271 100644 --- a/src/guacenc/instructions.h +++ b/src/guacenc/instructions.h @@ -24,6 +24,7 @@ #define GUACENC_INSTRUCTIONS_H #include "config.h" +#include "display.h" /** * A callback function which, when invoked, handles a particular Guacamole @@ -32,6 +33,9 @@ * 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. @@ -44,7 +48,8 @@ * Zero if the instruction was handled successfully, non-zero if an error * occurs. */ -typedef int guacenc_instruction_handler(int argc, char** argv); +typedef int guacenc_instruction_handler(guacenc_display* display, + int argc, char** argv); /** * Mapping of instruction opcode to corresponding handler function. @@ -76,6 +81,9 @@ 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. * @@ -91,7 +99,8 @@ extern guacenc_instruction_handler_mapping guacenc_instruction_handler_map[]; * Zero if the instruction was handled successfully, non-zero if an error * occurs. */ -int guacenc_handle_instruction(const char* opcode, int argc, char** argv); +int guacenc_handle_instruction(guacenc_display* display, + const char* opcode, int argc, char** argv); /** * Handler for the Guacamole "blob" instruction. From 899cdb4c001ccaab4d40b7a33d6b584b5c7eb903 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Feb 2016 23:44:58 -0800 Subject: [PATCH 13/92] GUAC-236: Fully implement sync instruction. Stub timestamp update for display. --- src/guacenc/display.c | 14 ++++++++++++++ src/guacenc/instruction-sync.c | 5 ++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index 6a1e32c8..9e18bd01 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -22,14 +22,28 @@ #include "config.h" #include "display.h" +#include "log.h" +#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_DEBUG, "Decreasing sync timestamp"); + return 1; + } + + /* Update timestamp of display */ + display->last_sync = timestamp; + /* STUB */ + return 0; + } guacenc_display* guacenc_display_alloc() { diff --git a/src/guacenc/instruction-sync.c b/src/guacenc/instruction-sync.c index ab789724..64255100 100644 --- a/src/guacenc/instruction-sync.c +++ b/src/guacenc/instruction-sync.c @@ -75,9 +75,8 @@ int guacenc_handle_sync(guacenc_display* display, int argc, char** argv) { /* Parse arguments */ guac_timestamp timestamp = guacenc_parse_timestamp(argv[0]); - /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "sync: timestamp=%" PRId64, timestamp); - return 0; + /* Update timestamp / flush frame */ + return guacenc_display_sync(display, timestamp); } From 314d66727a6b2fbb8dfe21003fb4e360bebfaaaa Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 00:17:05 -0800 Subject: [PATCH 14/92] GUAC-236: Implement alloc/free of layers. Implement shade, move, and dispose. --- src/guacenc/Makefile.am | 1 + src/guacenc/display.c | 68 +++++++++++++++++++++++++++++++ src/guacenc/display.h | 34 +++++++++++++++- src/guacenc/instruction-dispose.c | 5 +-- src/guacenc/instruction-move.c | 18 ++++++-- src/guacenc/instruction-shade.c | 10 ++++- src/guacenc/layer.c | 45 ++++++++++++++++++++ src/guacenc/layer.h | 22 +++++++++- 8 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 src/guacenc/layer.c diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 39b31e0e..7a6ec48f 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -51,6 +51,7 @@ guacenc_SOURCES = \ instruction-size.c \ instruction-sync.c \ instruction-transfer.c \ + layer.c \ log.c guacenc_CFLAGS = \ diff --git a/src/guacenc/display.c b/src/guacenc/display.c index 9e18bd01..8a17eadf 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -46,12 +46,80 @@ int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) { } +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_DEBUG, "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_DEBUG, "Layer allocation failed"); + return NULL; + } + + /* Store layer within display for future retrieval / management */ + display->layers[index] = layer; + + } + + return layer; + +} + +int guacenc_display_free_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_DEBUG, "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; + +} + guacenc_display* guacenc_display_alloc() { return (guacenc_display*) calloc(1, sizeof(guacenc_display)); } int guacenc_display_free(guacenc_display* display) { + + int i; + + /* Ignore NULL display */ + if (display == NULL) + return 0; + + /* Free all buffers */ + for (i = 0; i < GUACENC_DISPLAY_MAX_BUFFERS; i++) + 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++) + free(display->image_streams[i]); + free(display); return 0; + } diff --git a/src/guacenc/display.h b/src/guacenc/display.h index e4638db0..b5fb1ca4 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -114,15 +114,45 @@ guacenc_display* guacenc_display_alloc(); /** * Frees all memory associated with the given Guacamole video encoder display, - * and finishes any underlying encoding process. + * 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. + * 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 index + * The index of the layer to retrieve. + * + * @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); + +/** + * Frees all resources associated with the layer having the given index. If + * the layer has not been allocated, this function has no effect. + * + * @param index + * The index of the layer to free. + * + * @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); + #endif diff --git a/src/guacenc/instruction-dispose.c b/src/guacenc/instruction-dispose.c index 8d8333f3..fef6f363 100644 --- a/src/guacenc/instruction-dispose.c +++ b/src/guacenc/instruction-dispose.c @@ -39,9 +39,8 @@ int guacenc_handle_dispose(guacenc_display* display, int argc, char** argv) { /* Parse arguments */ int index = atoi(argv[0]); - /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "dispose: layer=%i", index); - return 0; + /* Dispose of layer */ + return guacenc_display_free_layer(display, index); } diff --git a/src/guacenc/instruction-move.c b/src/guacenc/instruction-move.c index 88e5d606..f929ee83 100644 --- a/src/guacenc/instruction-move.c +++ b/src/guacenc/instruction-move.c @@ -43,9 +43,21 @@ int guacenc_handle_move(guacenc_display* display, int argc, char** argv) { int y = atoi(argv[3]); int z = atoi(argv[4]); - /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "move: layer=%i parent=%i (%i, %i) z=%i", - layer_index, parent_index, x, y, z); + /* 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-shade.c b/src/guacenc/instruction-shade.c index 466568c9..414eafbc 100644 --- a/src/guacenc/instruction-shade.c +++ b/src/guacenc/instruction-shade.c @@ -40,8 +40,14 @@ int guacenc_handle_shade(guacenc_display* display, int argc, char** argv) { int index = atoi(argv[0]); int opacity = atoi(argv[1]); - /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "shade: layer=%i opacity=0x%X", index, opacity); + /* 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/layer.c b/src/guacenc/layer.c new file mode 100644 index 00000000..1265622b --- /dev/null +++ b/src/guacenc/layer.c @@ -0,0 +1,45 @@ +/* + * 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 "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; + + /* Layers default to fully opaque */ + layer->opacity = 0xFF; + + return layer; + +} + +void guacenc_layer_free(guacenc_layer* layer) { + free(layer); +} + diff --git a/src/guacenc/layer.h b/src/guacenc/layer.h index 739d2aa1..50f47040 100644 --- a/src/guacenc/layer.h +++ b/src/guacenc/layer.h @@ -41,7 +41,7 @@ typedef struct guacenc_layer { * The actual image contents of this layer, as well as this layer's size * (width and height). */ - guacenc_buffer buffer; + guacenc_buffer* buffer; /** * The index of the layer that contains this layer. If this layer is the @@ -76,5 +76,25 @@ typedef struct guacenc_layer { } 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 From 696c3c3184d3970f1b11533e999d973896fc8462 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 00:29:22 -0800 Subject: [PATCH 15/92] GUAC-236: Stub resize of buffers. Implement size. --- src/guacenc/Makefile.am | 1 + src/guacenc/buffer.c | 46 ++++++++++++++++++++++++++++++++++ src/guacenc/buffer.h | 44 ++++++++++++++++++++++++++++++++ src/guacenc/instruction-size.c | 10 +++++--- src/guacenc/layer.c | 17 +++++++++++++ 5 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 src/guacenc/buffer.c diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 7a6ec48f..35a149e4 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -34,6 +34,7 @@ noinst_HEADERS = \ log.h guacenc_SOURCES = \ + buffer.c \ display.c \ encode.c \ guacenc.c \ diff --git a/src/guacenc/buffer.c b/src/guacenc/buffer.c new file mode 100644 index 00000000..56cc409b --- /dev/null +++ b/src/guacenc/buffer.c @@ -0,0 +1,46 @@ +/* + * 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 + +guacenc_buffer* guacenc_buffer_alloc() { + return calloc(1, sizeof(guacenc_buffer)); +} + +void guacenc_buffer_free(guacenc_buffer* buffer) { + free(buffer); +} + +int guacenc_buffer_resize(guacenc_buffer* buffer, int width, int height) { + + /* STUB */ + + buffer->width = width; + buffer->height = height; + + return 0; + +} + diff --git a/src/guacenc/buffer.h b/src/guacenc/buffer.h index 6bd3ce3c..ed6f6ac2 100644 --- a/src/guacenc/buffer.h +++ b/src/guacenc/buffer.h @@ -41,7 +41,51 @@ typedef struct guacenc_buffer { */ int height; + /** + * The number of bytes in each row of image data. + */ + int stride; + } 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); + #endif diff --git a/src/guacenc/instruction-size.c b/src/guacenc/instruction-size.c index 24375bc7..c650c218 100644 --- a/src/guacenc/instruction-size.c +++ b/src/guacenc/instruction-size.c @@ -41,9 +41,13 @@ int guacenc_handle_size(guacenc_display* display, int argc, char** argv) { int width = atoi(argv[1]); int height = atoi(argv[2]); - /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "size: layer=%i %ix%i", index, width, height); - return 0; + /* 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/layer.c b/src/guacenc/layer.c index 1265622b..b0ec9696 100644 --- a/src/guacenc/layer.c +++ b/src/guacenc/layer.c @@ -21,6 +21,7 @@ */ #include "config.h" +#include "buffer.h" #include "layer.h" #include @@ -32,6 +33,13 @@ guacenc_layer* guacenc_layer_alloc() { 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; + } + /* Layers default to fully opaque */ layer->opacity = 0xFF; @@ -40,6 +48,15 @@ guacenc_layer* guacenc_layer_alloc() { } void guacenc_layer_free(guacenc_layer* layer) { + + /* Ignore NULL layers */ + if (layer == NULL) + return; + + /* Free underlying buffer */ + guacenc_buffer_free(layer->buffer); + free(layer); + } From a0197ee2c2fb21dcd1fd8f81033bd08296ecc7a2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 10:07:55 -0800 Subject: [PATCH 16/92] GUAC-236: Implement buffer resize. --- src/guacenc/buffer.c | 66 +++++++++++++++++++++++++++++++++++++++++++- src/guacenc/buffer.h | 20 ++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/guacenc/buffer.c b/src/guacenc/buffer.c index 56cc409b..999893f9 100644 --- a/src/guacenc/buffer.c +++ b/src/guacenc/buffer.c @@ -23,22 +23,86 @@ #include "config.h" #include "buffer.h" +#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) { + guacenc_buffer_free_image(buffer); free(buffer); } int guacenc_buffer_resize(guacenc_buffer* buffer, int width, int height) { - /* STUB */ + /* 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_source_surface(cairo, buffer->surface, 0, 0); + cairo_paint(cairo); + cairo_set_source_rgba(cairo, 0.0, 0.0, 0.0, 1.0); + } + + /* 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; diff --git a/src/guacenc/buffer.h b/src/guacenc/buffer.h index ed6f6ac2..d2d7f152 100644 --- a/src/guacenc/buffer.h +++ b/src/guacenc/buffer.h @@ -25,6 +25,8 @@ #include "config.h" +#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). @@ -46,6 +48,24 @@ typedef struct guacenc_buffer { */ 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; /** From 9407f8bcde0faaeaeab18c2aaac8225b18347f25 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 14:51:33 -0800 Subject: [PATCH 17/92] GUAC-236: Layers must default to unparented. --- src/guacenc/layer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/guacenc/layer.c b/src/guacenc/layer.c index b0ec9696..721f3602 100644 --- a/src/guacenc/layer.c +++ b/src/guacenc/layer.c @@ -43,6 +43,9 @@ guacenc_layer* guacenc_layer_alloc() { /* Layers default to fully opaque */ layer->opacity = 0xFF; + /* Default to unparented */ + layer->parent_index = GUACENC_LAYER_NO_PARENT; + return layer; } From 160453c3e969ebcc6962551b81a3ded8b6390f0e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 14:52:05 -0800 Subject: [PATCH 18/92] GUAC-236: Implement buffer retrieval functions. --- src/guacenc/display.c | 75 +++++++++++++++++++++++++++++++++++++++++++ src/guacenc/display.h | 54 +++++++++++++++++++++++++++++-- 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index 8a17eadf..1fca853b 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -94,6 +94,81 @@ int guacenc_display_free_layer(guacenc_display* display, } +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_DEBUG, "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_DEBUG, "Buffer allocation failed"); + return NULL; + } + + /* 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 / allocate if index is invalid */ + if (internal_index < 0 || internal_index > GUACENC_DISPLAY_MAX_BUFFERS) { + guacenc_log(GUAC_LOG_DEBUG, "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); + +} + guacenc_display* guacenc_display_alloc() { return (guacenc_display*) calloc(1, sizeof(guacenc_display)); } diff --git a/src/guacenc/display.h b/src/guacenc/display.h index b5fb1ca4..528159b3 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -131,7 +131,8 @@ int guacenc_display_free(guacenc_display* display); * returned. * * @param index - * The index of the layer to retrieve. + * 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 @@ -145,7 +146,8 @@ guacenc_layer* guacenc_display_get_layer(guacenc_display* display, * the layer has not been allocated, this function has no effect. * * @param index - * The index of the layer to free. + * 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 @@ -154,5 +156,53 @@ guacenc_layer* guacenc_display_get_layer(guacenc_display* display, 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 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 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 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); + #endif From ac78b7a7a58f3c50a75e9f31e3dd6f742b41a080 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 15:15:17 -0800 Subject: [PATCH 19/92] GUAC-236: Implement cfill and rect. --- src/guacenc/display.c | 58 +++++++++++++++++++++++++++++++++ src/guacenc/display.h | 16 +++++++++ src/guacenc/instruction-cfill.c | 23 +++++++++---- src/guacenc/instruction-rect.c | 14 ++++++-- 4 files changed, 101 insertions(+), 10 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index 1fca853b..24f62d0d 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -25,6 +25,7 @@ #include "log.h" #include +#include #include #include @@ -169,6 +170,63 @@ guacenc_buffer* guacenc_display_get_related_buffer(guacenc_display* display, } +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() { return (guacenc_display*) calloc(1, sizeof(guacenc_display)); } diff --git a/src/guacenc/display.h b/src/guacenc/display.h index 528159b3..f3e296ac 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -28,6 +28,7 @@ #include "image-stream.h" #include "layer.h" +#include #include /** @@ -204,5 +205,20 @@ int guacenc_display_free_buffer(guacenc_display* display, guacenc_buffer* guacenc_display_get_related_buffer(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/instruction-cfill.c b/src/guacenc/instruction-cfill.c index 5f83c59f..cda8778e 100644 --- a/src/guacenc/instruction-cfill.c +++ b/src/guacenc/instruction-cfill.c @@ -39,14 +39,23 @@ int guacenc_handle_cfill(guacenc_display* display, int argc, char** argv) { /* Parse arguments */ int mask = atoi(argv[0]); int index = atoi(argv[1]); - int r = atoi(argv[2]); - int g = atoi(argv[3]); - int b = atoi(argv[4]); - int a = atoi(argv[5]); + 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); + } - /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "cfill: mask=0x%X layer=%i " - "rgba(%i, %i, %i, %i)", mask, index, r, g, b, a); return 0; } diff --git a/src/guacenc/instruction-rect.c b/src/guacenc/instruction-rect.c index 9202edf8..2e3caa00 100644 --- a/src/guacenc/instruction-rect.c +++ b/src/guacenc/instruction-rect.c @@ -21,9 +21,11 @@ */ #include "config.h" +#include "buffer.h" #include "display.h" #include "log.h" +#include #include #include @@ -43,9 +45,15 @@ int guacenc_handle_rect(guacenc_display* display, int argc, char** argv) { int width = atoi(argv[3]); int height = atoi(argv[4]); - /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "rect: layer=%i (%i, %i) %ix%i", - index, x, y, width, height); + /* Pull buffer of requested layer/buffer */ + guacenc_buffer* buffer = guacenc_display_get_related_buffer(display, index); + if (buffer == NULL) + return 1; + + /* Set path to rectangle */ + if (buffer->cairo != NULL) + cairo_rectangle(buffer->cairo, x, y, width, height); + return 0; } From d1642cbcbab2a48d8b8f324b9b22f51dce3d4954 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 15:17:14 -0800 Subject: [PATCH 20/92] GUAC-236: Cursor must be ignored for now (no mouse position information in recording). --- src/guacenc/instruction-cursor.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/guacenc/instruction-cursor.c b/src/guacenc/instruction-cursor.c index dc66c896..bccbacb3 100644 --- a/src/guacenc/instruction-cursor.c +++ b/src/guacenc/instruction-cursor.c @@ -45,10 +45,11 @@ int guacenc_handle_cursor(guacenc_display* display, int argc, char** argv) { int src_w = atoi(argv[5]); int src_h = atoi(argv[6]); - /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "cursor: hotspot (%i, %i) " + /* 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; } From 36b625818d60423aca761f58529a7d0f492c63ce Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 15:24:38 -0800 Subject: [PATCH 21/92] GUAC-236: Log protocol violations at WARNING level. --- src/guacenc/display.c | 14 +++++++------- src/guacenc/instruction-blob.c | 2 +- src/guacenc/instruction-cfill.c | 2 +- src/guacenc/instruction-copy.c | 2 +- src/guacenc/instruction-cursor.c | 2 +- src/guacenc/instruction-dispose.c | 2 +- src/guacenc/instruction-end.c | 2 +- src/guacenc/instruction-img.c | 2 +- src/guacenc/instruction-move.c | 2 +- src/guacenc/instruction-rect.c | 2 +- src/guacenc/instruction-shade.c | 2 +- src/guacenc/instruction-size.c | 2 +- src/guacenc/instruction-sync.c | 2 +- src/guacenc/instruction-transfer.c | 2 +- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index 24f62d0d..e0dec033 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -34,7 +34,7 @@ int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) { /* Verify timestamp is not decreasing */ if (timestamp < display->last_sync) { - guacenc_log(GUAC_LOG_DEBUG, "Decreasing sync timestamp"); + guacenc_log(GUAC_LOG_WARNING, "Decreasing sync timestamp"); return 1; } @@ -52,7 +52,7 @@ guacenc_layer* guacenc_display_get_layer(guacenc_display* display, /* Do not lookup / allocate if index is invalid */ if (index < 0 || index > GUACENC_DISPLAY_MAX_LAYERS) { - guacenc_log(GUAC_LOG_DEBUG, "Layer index out of bounds: %i", index); + guacenc_log(GUAC_LOG_WARNING, "Layer index out of bounds: %i", index); return NULL; } @@ -63,7 +63,7 @@ guacenc_layer* guacenc_display_get_layer(guacenc_display* display, /* Attempt to allocate layer */ layer = guacenc_layer_alloc(); if (layer == NULL) { - guacenc_log(GUAC_LOG_DEBUG, "Layer allocation failed"); + guacenc_log(GUAC_LOG_WARNING, "Layer allocation failed"); return NULL; } @@ -81,7 +81,7 @@ int guacenc_display_free_layer(guacenc_display* display, /* Do not lookup / allocate if index is invalid */ if (index < 0 || index > GUACENC_DISPLAY_MAX_LAYERS) { - guacenc_log(GUAC_LOG_DEBUG, "Layer index out of bounds: %i", index); + guacenc_log(GUAC_LOG_WARNING, "Layer index out of bounds: %i", index); return 1; } @@ -103,7 +103,7 @@ guacenc_buffer* guacenc_display_get_buffer(guacenc_display* display, /* Do not lookup / allocate if index is invalid */ if (internal_index < 0 || internal_index > GUACENC_DISPLAY_MAX_BUFFERS) { - guacenc_log(GUAC_LOG_DEBUG, "Buffer index out of bounds: %i", index); + guacenc_log(GUAC_LOG_WARNING, "Buffer index out of bounds: %i", index); return NULL; } @@ -114,7 +114,7 @@ guacenc_buffer* guacenc_display_get_buffer(guacenc_display* display, /* Attempt to allocate buffer */ buffer = guacenc_buffer_alloc(); if (buffer == NULL) { - guacenc_log(GUAC_LOG_DEBUG, "Buffer allocation failed"); + guacenc_log(GUAC_LOG_WARNING, "Buffer allocation failed"); return NULL; } @@ -135,7 +135,7 @@ int guacenc_display_free_buffer(guacenc_display* display, /* Do not lookup / allocate if index is invalid */ if (internal_index < 0 || internal_index > GUACENC_DISPLAY_MAX_BUFFERS) { - guacenc_log(GUAC_LOG_DEBUG, "Buffer index out of bounds: %i", index); + guacenc_log(GUAC_LOG_WARNING, "Buffer index out of bounds: %i", index); return 1; } diff --git a/src/guacenc/instruction-blob.c b/src/guacenc/instruction-blob.c index f608e036..67b4fbb2 100644 --- a/src/guacenc/instruction-blob.c +++ b/src/guacenc/instruction-blob.c @@ -33,7 +33,7 @@ int guacenc_handle_blob(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 2) { - guacenc_log(GUAC_LOG_DEBUG, "\"blob\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"blob\" instruction incomplete"); return 1; } diff --git a/src/guacenc/instruction-cfill.c b/src/guacenc/instruction-cfill.c index cda8778e..139df94d 100644 --- a/src/guacenc/instruction-cfill.c +++ b/src/guacenc/instruction-cfill.c @@ -32,7 +32,7 @@ int guacenc_handle_cfill(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 6) { - guacenc_log(GUAC_LOG_DEBUG, "\"cfill\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"cfill\" instruction incomplete"); return 1; } diff --git a/src/guacenc/instruction-copy.c b/src/guacenc/instruction-copy.c index 5dbfcb1d..aada48c2 100644 --- a/src/guacenc/instruction-copy.c +++ b/src/guacenc/instruction-copy.c @@ -32,7 +32,7 @@ int guacenc_handle_copy(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 9) { - guacenc_log(GUAC_LOG_DEBUG, "\"copy\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"copy\" instruction incomplete"); return 1; } diff --git a/src/guacenc/instruction-cursor.c b/src/guacenc/instruction-cursor.c index bccbacb3..0cd25d75 100644 --- a/src/guacenc/instruction-cursor.c +++ b/src/guacenc/instruction-cursor.c @@ -32,7 +32,7 @@ int guacenc_handle_cursor(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 7) { - guacenc_log(GUAC_LOG_DEBUG, "\"cursor\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"cursor\" instruction incomplete"); return 1; } diff --git a/src/guacenc/instruction-dispose.c b/src/guacenc/instruction-dispose.c index fef6f363..d6204905 100644 --- a/src/guacenc/instruction-dispose.c +++ b/src/guacenc/instruction-dispose.c @@ -32,7 +32,7 @@ int guacenc_handle_dispose(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 1) { - guacenc_log(GUAC_LOG_DEBUG, "\"dispose\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"dispose\" instruction incomplete"); return 1; } diff --git a/src/guacenc/instruction-end.c b/src/guacenc/instruction-end.c index 3e300e8b..fbc9e284 100644 --- a/src/guacenc/instruction-end.c +++ b/src/guacenc/instruction-end.c @@ -32,7 +32,7 @@ int guacenc_handle_end(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 1) { - guacenc_log(GUAC_LOG_DEBUG, "\"end\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"end\" instruction incomplete"); return 1; } diff --git a/src/guacenc/instruction-img.c b/src/guacenc/instruction-img.c index d67fc402..bbd0c33f 100644 --- a/src/guacenc/instruction-img.c +++ b/src/guacenc/instruction-img.c @@ -32,7 +32,7 @@ int guacenc_handle_img(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 6) { - guacenc_log(GUAC_LOG_DEBUG, "\"img\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"img\" instruction incomplete"); return 1; } diff --git a/src/guacenc/instruction-move.c b/src/guacenc/instruction-move.c index f929ee83..6123e344 100644 --- a/src/guacenc/instruction-move.c +++ b/src/guacenc/instruction-move.c @@ -32,7 +32,7 @@ int guacenc_handle_move(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 5) { - guacenc_log(GUAC_LOG_DEBUG, "\"move\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"move\" instruction incomplete"); return 1; } diff --git a/src/guacenc/instruction-rect.c b/src/guacenc/instruction-rect.c index 2e3caa00..94fea8f3 100644 --- a/src/guacenc/instruction-rect.c +++ b/src/guacenc/instruction-rect.c @@ -34,7 +34,7 @@ int guacenc_handle_rect(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 5) { - guacenc_log(GUAC_LOG_DEBUG, "\"rect\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"rect\" instruction incomplete"); return 1; } diff --git a/src/guacenc/instruction-shade.c b/src/guacenc/instruction-shade.c index 414eafbc..53826231 100644 --- a/src/guacenc/instruction-shade.c +++ b/src/guacenc/instruction-shade.c @@ -32,7 +32,7 @@ int guacenc_handle_shade(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 2) { - guacenc_log(GUAC_LOG_DEBUG, "\"shade\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"shade\" instruction incomplete"); return 1; } diff --git a/src/guacenc/instruction-size.c b/src/guacenc/instruction-size.c index c650c218..f173d33f 100644 --- a/src/guacenc/instruction-size.c +++ b/src/guacenc/instruction-size.c @@ -32,7 +32,7 @@ int guacenc_handle_size(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 3) { - guacenc_log(GUAC_LOG_DEBUG, "\"size\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"size\" instruction incomplete"); return 1; } diff --git a/src/guacenc/instruction-sync.c b/src/guacenc/instruction-sync.c index 64255100..a2e40389 100644 --- a/src/guacenc/instruction-sync.c +++ b/src/guacenc/instruction-sync.c @@ -68,7 +68,7 @@ int guacenc_handle_sync(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 1) { - guacenc_log(GUAC_LOG_DEBUG, "\"sync\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"sync\" instruction incomplete"); return 1; } diff --git a/src/guacenc/instruction-transfer.c b/src/guacenc/instruction-transfer.c index f95a2a8f..3643404f 100644 --- a/src/guacenc/instruction-transfer.c +++ b/src/guacenc/instruction-transfer.c @@ -32,7 +32,7 @@ int guacenc_handle_transfer(guacenc_display* display, int argc, char** argv) { /* Verify argument count */ if (argc < 9) { - guacenc_log(GUAC_LOG_DEBUG, "\"transform\" instruction incomplete"); + guacenc_log(GUAC_LOG_WARNING, "\"transform\" instruction incomplete"); return 1; } From dea754d846087b0a2cb097e107f2f8020fe234bd Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 15:26:47 -0800 Subject: [PATCH 22/92] GUAC-236: Handle dispose of buffers. --- src/guacenc/instruction-dispose.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/guacenc/instruction-dispose.c b/src/guacenc/instruction-dispose.c index d6204905..a6cd4203 100644 --- a/src/guacenc/instruction-dispose.c +++ b/src/guacenc/instruction-dispose.c @@ -39,8 +39,12 @@ int guacenc_handle_dispose(guacenc_display* display, int argc, char** argv) { /* Parse arguments */ int index = atoi(argv[0]); - /* Dispose of layer */ - return guacenc_display_free_layer(display, index); + /* 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); } From 96104c099b246095fa89cedaa701d7e33d4cf698 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 15:28:24 -0800 Subject: [PATCH 23/92] GUAC-236: Ignore free attempts on NULL buffers. --- src/guacenc/buffer.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/guacenc/buffer.c b/src/guacenc/buffer.c index 999893f9..379cd9ee 100644 --- a/src/guacenc/buffer.c +++ b/src/guacenc/buffer.c @@ -60,8 +60,15 @@ static void guacenc_buffer_free_image(guacenc_buffer* buffer) { } 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) { From 088f1dfeded8374665575d3a16d0a5b9eaa30d38 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 15:46:57 -0800 Subject: [PATCH 24/92] GUAC-236: Implement copy. --- src/guacenc/instruction-copy.c | 38 +++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/guacenc/instruction-copy.c b/src/guacenc/instruction-copy.c index aada48c2..b79fdd08 100644 --- a/src/guacenc/instruction-copy.c +++ b/src/guacenc/instruction-copy.c @@ -37,20 +37,34 @@ int guacenc_handle_copy(guacenc_display* display, int argc, char** argv) { } /* 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 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 dst_index = atoi(argv[6]); - int dst_x = atoi(argv[7]); - int dst_y = atoi(argv[8]); + 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; + + /* Copy rectangle from source to destination */ + if (src->surface != NULL && dst->cairo != NULL) { + cairo_set_operator(dst->cairo, guacenc_display_cairo_operator(mask)); + cairo_set_source_surface(dst->cairo, src->surface, dx - sx, dy - sy); + cairo_rectangle(dst->cairo, dx, dy, width, height); + cairo_fill(dst->cairo); + } - /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "copy: src_layer=%i (%i, %i) %ix%i mask=0x%X " - "dst_layer=%i (%i, %i)", src_index, src_x, src_y, src_w, src_h, - mask, dst_index, dst_x, dst_y); return 0; } From 8c0a9b8bc56ad4a3cc29f10ae747a7cd0f1eb531 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 15:57:42 -0800 Subject: [PATCH 25/92] GUAC-236: Replace placeholder with required mask and index properties. --- src/guacenc/image-stream.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/guacenc/image-stream.h b/src/guacenc/image-stream.h index d88897d4..f485c1bd 100644 --- a/src/guacenc/image-stream.h +++ b/src/guacenc/image-stream.h @@ -31,10 +31,15 @@ typedef struct guacenc_image_stream { /** - * STUB: Placeholder property. This property exists only so that the - * guacenc_image_stream struct can be defined prior to implementation. + * The index of the destination layer or buffer. */ - int __PLACEHOLDER; + int index; + + /** + * The Guacamole protocol compositing operation (channel mask) to apply + * when drawing the image. + */ + int mask; } guacenc_image_stream; From 083e48d089d77f7a13a7dc4ab1e3bafa836382b6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 17:52:07 -0800 Subject: [PATCH 26/92] GUAC-236: Add stub structure for image decoding. --- src/guacenc/Makefile.am | 15 +++- src/guacenc/image-stream.c | 42 +++++++++++ src/guacenc/image-stream.h | 150 +++++++++++++++++++++++++++++++++++++ src/guacenc/jpeg.c | 57 ++++++++++++++ src/guacenc/jpeg.h | 35 +++++++++ src/guacenc/png.c | 57 ++++++++++++++ src/guacenc/png.h | 35 +++++++++ src/guacenc/webp.c | 57 ++++++++++++++ src/guacenc/webp.h | 35 +++++++++ 9 files changed, 481 insertions(+), 2 deletions(-) create mode 100644 src/guacenc/image-stream.c create mode 100644 src/guacenc/jpeg.c create mode 100644 src/guacenc/jpeg.h create mode 100644 src/guacenc/png.c create mode 100644 src/guacenc/png.h create mode 100644 src/guacenc/webp.c create mode 100644 src/guacenc/webp.h diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 35a149e4..2baa6003 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -30,14 +30,17 @@ noinst_HEADERS = \ encode.h \ image-stream.h \ instructions.h \ + jpeg.h \ layer.h \ - log.h + log.h \ + png.h guacenc_SOURCES = \ buffer.c \ display.c \ encode.c \ guacenc.c \ + image-stream.c \ instructions.c \ instruction-blob.c \ instruction-cfill.c \ @@ -52,8 +55,16 @@ guacenc_SOURCES = \ instruction-size.c \ instruction-sync.c \ instruction-transfer.c \ + jpeg.c \ layer.c \ - log.c + log.c \ + png.c + +# Compile WebP support if available +if ENABLE_WEBP +guacenc_SOURCES += webp.c +noinst_HEADERS += webp.h +endif guacenc_CFLAGS = \ -Werror -Wall -pedantic \ diff --git a/src/guacenc/image-stream.c b/src/guacenc/image-stream.c new file mode 100644 index 00000000..261232b6 --- /dev/null +++ b/src/guacenc/image-stream.c @@ -0,0 +1,42 @@ +/* + * 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 "png.h" + +#ifdef ENABLE_WEBP +#include "webp.h" +#endif + +#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} +}; + diff --git a/src/guacenc/image-stream.h b/src/guacenc/image-stream.h index f485c1bd..1f03dc19 100644 --- a/src/guacenc/image-stream.h +++ b/src/guacenc/image-stream.h @@ -24,6 +24,13 @@ #define GUACENC_IMAGE_STREAM_H #include "config.h" +#include "buffer.h" + +/** + * A decoder implementation which processes arbitrary image data of a + * particular type. Image data is fed explicitly into the decoder as chunks. + */ +typedef struct guacenc_decoder guacenc_decoder; /** * The current state of an allocated Guacamole image stream. @@ -41,7 +48,150 @@ typedef struct guacenc_image_stream { */ 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; + + /** + * The decoder to use when decoding the raw data received along this + * stream, or NULL if no such decoder exists. + */ + guacenc_decoder* decoder; + + /** + * Arbitrary implementation-specific data associated with the stream. + */ + void* data; + } guacenc_image_stream; +/** + * Callback function which is invoked when a decoder has been assigned to an + * image stream. + * + * @param stream + * The image stream that the decoder has been assigned to. + * + * @return + * Zero if initialization was successful, non-zero otherwise. + */ +typedef int guacenc_decoder_init_handler(guacenc_image_stream* stream); + +/** + * Callback function which is invoked when data has been received along an + * image stream with an associated decoder. + * + * @param stream + * The image stream that the decoder was assigned to. + * + * @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 provided data was processed successfully, non-zero + * otherwise. + */ +typedef int guacenc_decoder_data_handler(guacenc_image_stream* stream, + unsigned char* data, int length); + +/** + * Callback function which is invoked when an image stream with an associated + * decoder has ended (reached end-of-stream). The image stream will contain + * the required meta-information describing the drawing operation, including + * the destination X/Y coordinates. + * + * @param stream + * The image stream that has ended. + * + * @param buffer + * The buffer to which the decoded image should be drawn. + * + * @return + * Zero if the end of the stream has been processed successfully and the + * resulting image has been rendered to the given buffer, non-zero + * otherwise. + */ +typedef int guacenc_decoder_end_handler(guacenc_image_stream* stream, + guacenc_buffer* buffer); + +/** + * Callback function which will be invoked when the data associated with an + * image stream must be freed. This may happen at any time, and will not + * necessarily occur only after the image stream has ended. It is possible + * that an image stream will be in-progress at the end of a protocol dump, thus + * the memory associated with the stream will need to be freed without ever + * ending. + * + * @param stream + * The stream whose associated data must be freed. + * + * @return + * Zero if the data was successfully freed, non-zero otherwise. + */ +typedef int guacenc_decoder_free_handler(guacenc_image_stream* stream); + +struct guacenc_decoder { + + /** + * Callback invoked when this decoder has just been assigned to an image + * stream. + */ + guacenc_decoder_init_handler* init_handler; + + /** + * Callback invoked when data has been received along an image stream to + * which this decoder has been assigned. + */ + guacenc_decoder_data_handler* data_handler; + + /** + * Callback invoked when an image stream to which this decoder has been + * assigned has ended (reached end-of-stream). + */ + guacenc_decoder_end_handler* end_handler; + + /** + * Callback invoked when data associated with an image stream by this + * decoder must be freed. + */ + guacenc_decoder_free_handler* free_handler; + +}; + +/** + * Mapping of image mimetype to corresponding decoder. + */ +typedef struct guacenc_decoder_mapping { + + /** + * The mimetype of the image that the associated decoder can read. + */ + const char* mimetype; + + /** + * The decoder 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[]; + #endif diff --git a/src/guacenc/jpeg.c b/src/guacenc/jpeg.c new file mode 100644 index 00000000..397589ef --- /dev/null +++ b/src/guacenc/jpeg.c @@ -0,0 +1,57 @@ +/* + * 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 "image-stream.h" +#include "jpeg.h" + +#include + +int guacenc_jpeg_init(guacenc_image_stream* stream) { + /* STUB */ + return 0; +} + +int guacenc_jpeg_data(guacenc_image_stream* stream, unsigned char* data, + int length) { + /* STUB */ + return 0; +} + +int guacenc_jpeg_end(guacenc_image_stream* stream, guacenc_buffer* buffer) { + /* STUB */ + return 0; +} + +int guacenc_jpeg_free(guacenc_image_stream* stream) { + /* STUB */ + return 0; +} + +guacenc_decoder guacenc_jpeg_decoder = { + .init_handler = guacenc_jpeg_init, + .data_handler = guacenc_jpeg_data, + .end_handler = guacenc_jpeg_end, + .free_handler = guacenc_jpeg_free +}; + diff --git a/src/guacenc/jpeg.h b/src/guacenc/jpeg.h new file mode 100644 index 00000000..718da3ad --- /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. + */ +extern guacenc_decoder guacenc_jpeg_decoder; + +#endif + diff --git a/src/guacenc/png.c b/src/guacenc/png.c new file mode 100644 index 00000000..d77b59eb --- /dev/null +++ b/src/guacenc/png.c @@ -0,0 +1,57 @@ +/* + * 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 "image-stream.h" +#include "png.h" + +#include + +int guacenc_png_init(guacenc_image_stream* stream) { + /* STUB */ + return 0; +} + +int guacenc_png_data(guacenc_image_stream* stream, unsigned char* data, + int length) { + /* STUB */ + return 0; +} + +int guacenc_png_end(guacenc_image_stream* stream, guacenc_buffer* buffer) { + /* STUB */ + return 0; +} + +int guacenc_png_free(guacenc_image_stream* stream) { + /* STUB */ + return 0; +} + +guacenc_decoder guacenc_png_decoder = { + .init_handler = guacenc_png_init, + .data_handler = guacenc_png_data, + .end_handler = guacenc_png_end, + .free_handler = guacenc_png_free +}; + diff --git a/src/guacenc/png.h b/src/guacenc/png.h new file mode 100644 index 00000000..9e3f68ac --- /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. + */ +extern guacenc_decoder guacenc_png_decoder; + +#endif + diff --git a/src/guacenc/webp.c b/src/guacenc/webp.c new file mode 100644 index 00000000..2a375669 --- /dev/null +++ b/src/guacenc/webp.c @@ -0,0 +1,57 @@ +/* + * 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 "image-stream.h" +#include "webp.h" + +#include + +int guacenc_webp_init(guacenc_image_stream* stream) { + /* STUB */ + return 0; +} + +int guacenc_webp_data(guacenc_image_stream* stream, unsigned char* data, + int length) { + /* STUB */ + return 0; +} + +int guacenc_webp_end(guacenc_image_stream* stream, guacenc_buffer* buffer) { + /* STUB */ + return 0; +} + +int guacenc_webp_free(guacenc_image_stream* stream) { + /* STUB */ + return 0; +} + +guacenc_decoder guacenc_webp_decoder = { + .init_handler = guacenc_webp_init, + .data_handler = guacenc_webp_data, + .end_handler = guacenc_webp_end, + .free_handler = guacenc_webp_free +}; + diff --git a/src/guacenc/webp.h b/src/guacenc/webp.h new file mode 100644 index 00000000..a56f67d6 --- /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. + */ +extern guacenc_decoder guacenc_webp_decoder; + +#endif + From 5149d6d5c4b76a9258327a32e33e844c12a2a3bd Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 17:58:07 -0800 Subject: [PATCH 27/92] GUAC-236: Add decoder search function. --- src/guacenc/image-stream.c | 23 +++++++++++++++++++++++ src/guacenc/image-stream.h | 13 +++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/guacenc/image-stream.c b/src/guacenc/image-stream.c index 261232b6..5830722f 100644 --- a/src/guacenc/image-stream.c +++ b/src/guacenc/image-stream.c @@ -23,6 +23,7 @@ #include "config.h" #include "image-stream.h" #include "jpeg.h" +#include "log.h" #include "png.h" #ifdef ENABLE_WEBP @@ -30,6 +31,7 @@ #endif #include +#include guacenc_decoder_mapping guacenc_decoder_map[] = { {"image/png", &guacenc_png_decoder}, @@ -40,3 +42,24 @@ guacenc_decoder_mapping guacenc_decoder_map[] = { {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; + +} + diff --git a/src/guacenc/image-stream.h b/src/guacenc/image-stream.h index 1f03dc19..5cd8f4cd 100644 --- a/src/guacenc/image-stream.h +++ b/src/guacenc/image-stream.h @@ -193,5 +193,18 @@ typedef struct guacenc_decoder_mapping { */ 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); + #endif From 8ff8ccc92bb60442050fa752478068e197df7658 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 18:43:41 -0800 Subject: [PATCH 28/92] GUAC-236: Free all buffers with appropriate function. --- src/guacenc/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index e0dec033..560da430 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -241,7 +241,7 @@ int guacenc_display_free(guacenc_display* display) { /* Free all buffers */ for (i = 0; i < GUACENC_DISPLAY_MAX_BUFFERS; i++) - free(display->buffers[i]); + guacenc_buffer_free(display->buffers[i]); /* Free all layers */ for (i = 0; i < GUACENC_DISPLAY_MAX_LAYERS; i++) From 578bedcd070df85abdaf50faab946e9ec684bc7a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 19:09:33 -0800 Subject: [PATCH 29/92] GUAC-236: Add image stream allocation and management functions. --- src/guacenc/display.c | 55 ++++++++++++++++++++- src/guacenc/display.h | 98 ++++++++++++++++++++++++++++++++++++-- src/guacenc/image-stream.c | 37 ++++++++++++++ src/guacenc/image-stream.h | 46 ++++++++++++++++++ 4 files changed, 231 insertions(+), 5 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index 560da430..6bd4c411 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -170,6 +170,59 @@ guacenc_buffer* guacenc_display_get_related_buffer(guacenc_display* display, } +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; + +} + cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask) { /* Translate Guacamole channel mask into Cairo operator */ @@ -249,7 +302,7 @@ int guacenc_display_free(guacenc_display* display) { /* Free all streams */ for (i = 0; i < GUACENC_DISPLAY_MAX_STREAMS; i++) - free(display->image_streams[i]); + guacenc_image_stream_free(display->image_streams[i]); free(display); return 0; diff --git a/src/guacenc/display.h b/src/guacenc/display.h index f3e296ac..68d6012e 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -131,6 +131,9 @@ int guacenc_display_free(guacenc_display* display); * 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. @@ -146,6 +149,10 @@ guacenc_layer* guacenc_display_get_layer(guacenc_display* display, * 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. @@ -154,14 +161,16 @@ guacenc_layer* guacenc_display_get_layer(guacenc_display* display, * 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); +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. @@ -177,6 +186,10 @@ guacenc_buffer* guacenc_display_get_buffer(guacenc_display* display, * 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. * @@ -184,8 +197,7 @@ guacenc_buffer* guacenc_display_get_buffer(guacenc_display* display, * 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); +int guacenc_display_free_buffer(guacenc_display* display, int index); /** * Returns the buffer associated with the layer or buffer having the given @@ -194,6 +206,9 @@ int guacenc_display_free_buffer(guacenc_display* display, * 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. @@ -205,6 +220,81 @@ int guacenc_display_free_buffer(guacenc_display* display, 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 bugger 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, diff --git a/src/guacenc/image-stream.c b/src/guacenc/image-stream.c index 5830722f..0872effc 100644 --- a/src/guacenc/image-stream.c +++ b/src/guacenc/image-stream.c @@ -63,3 +63,40 @@ guacenc_decoder* guacenc_get_decoder(const char* mimetype) { } +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); + + return stream; + +} + +int guacenc_image_stream_free(guacenc_image_stream* stream) { + + /* Ignore NULL streams */ + if (stream == NULL) + return 0; + + /* Invoke free handler for decoder (if associated) */ + if (stream->decoder != NULL) + stream->decoder->free_handler(stream); + + /* Free actual stream */ + free(stream); + return 0; + +} + diff --git a/src/guacenc/image-stream.h b/src/guacenc/image-stream.h index 5cd8f4cd..6275cade 100644 --- a/src/guacenc/image-stream.h +++ b/src/guacenc/image-stream.h @@ -206,5 +206,51 @@ extern guacenc_decoder_mapping guacenc_decoder_map[]; */ 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 bugger 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); + +/** + * 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 From f535ccfb54411816109264cc203ee4ecca8847e8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 19:14:42 -0800 Subject: [PATCH 30/92] GUAC-236: Invoke init/free handlers as required. --- src/guacenc/image-stream.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/guacenc/image-stream.c b/src/guacenc/image-stream.c index 0872effc..7475d471 100644 --- a/src/guacenc/image-stream.c +++ b/src/guacenc/image-stream.c @@ -78,7 +78,9 @@ guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index, stream->y = y; /* Associate with corresponding decoder */ - stream->decoder = guacenc_get_decoder(mimetype); + guacenc_decoder* decoder = stream->decoder = guacenc_get_decoder(mimetype); + if (decoder != NULL) + decoder->init_handler(stream); return stream; @@ -91,8 +93,9 @@ int guacenc_image_stream_free(guacenc_image_stream* stream) { return 0; /* Invoke free handler for decoder (if associated) */ - if (stream->decoder != NULL) - stream->decoder->free_handler(stream); + guacenc_decoder* decoder = stream->decoder; + if (decoder != NULL) + decoder->free_handler(stream); /* Free actual stream */ free(stream); From d29d5bbc9943d9f15df5c28fe906d215b2271941 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 19:27:47 -0800 Subject: [PATCH 31/92] GUAC-236: Add helper functions for invoking stream data/end handlers. --- src/guacenc/image-stream.c | 26 ++++++++++++++++++++++++ src/guacenc/image-stream.h | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/guacenc/image-stream.c b/src/guacenc/image-stream.c index 7475d471..371fa6ba 100644 --- a/src/guacenc/image-stream.c +++ b/src/guacenc/image-stream.c @@ -86,6 +86,32 @@ guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index, } +int guacenc_image_stream_receive(guacenc_image_stream* stream, + unsigned char* data, int length) { + + /* Invoke data handler of corresponding decoder (if any) */ + guacenc_decoder* decoder = stream->decoder; + if (decoder != NULL) + return decoder->data_handler(stream, data, length); + + /* If there is no decoder, simply return success */ + return 0; + +} + +int guacenc_image_stream_end(guacenc_image_stream* stream, + guacenc_buffer* buffer) { + + /* Invoke end handler of corresponding decoder (if any) */ + guacenc_decoder* decoder = stream->decoder; + if (decoder != NULL) + return decoder->end_handler(stream, buffer); + + /* If there is no decoder, simply return success */ + return 0; + +} + int guacenc_image_stream_free(guacenc_image_stream* stream) { /* Ignore NULL streams */ diff --git a/src/guacenc/image-stream.h b/src/guacenc/image-stream.h index 6275cade..22b72f1d 100644 --- a/src/guacenc/image-stream.h +++ b/src/guacenc/image-stream.h @@ -237,6 +237,47 @@ guacenc_decoder* guacenc_get_decoder(const char* mimetype); guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index, const char* mimetype, int x, int y); +/** + * Signals the decoder of the given image stream that a chunk of image data + * has been received. If no decoder is associated with the given image stream, + * this function has no effect. + * + * @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 handled successfully by the decoder, or + * non-zero if an error occurs. + */ +int guacenc_image_stream_receive(guacenc_image_stream* stream, + unsigned char* data, int length); + +/** + * Signals the decoder of the given image stream that no more data will be + * received and the image should 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 + * stored within the guacenc_image_stream. + * + * @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 From 3661cadf4e24c1cc417e3ec49da1863d7267a804 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 19:28:11 -0800 Subject: [PATCH 32/92] GUAC-236: Implement blob, end, and img instructions. --- src/guacenc/instruction-blob.c | 14 +++++++++----- src/guacenc/instruction-end.c | 18 +++++++++++++++--- src/guacenc/instruction-img.c | 9 +++------ 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/guacenc/instruction-blob.c b/src/guacenc/instruction-blob.c index 67b4fbb2..9e8ad3f6 100644 --- a/src/guacenc/instruction-blob.c +++ b/src/guacenc/instruction-blob.c @@ -40,12 +40,16 @@ int guacenc_handle_blob(guacenc_display* display, int argc, char** argv) { /* Parse arguments */ int index = atoi(argv[0]); char* data = argv[1]; - int data_length = guac_protocol_decode_base64(data); + int length = guac_protocol_decode_base64(data); - /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "blob: stream=%i data=[%i bytes]", - index, data_length); - return 0; + /* 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-end.c b/src/guacenc/instruction-end.c index fbc9e284..749ead2a 100644 --- a/src/guacenc/instruction-end.c +++ b/src/guacenc/instruction-end.c @@ -22,6 +22,7 @@ #include "config.h" #include "display.h" +#include "image-stream.h" #include "log.h" #include @@ -39,9 +40,20 @@ int guacenc_handle_end(guacenc_display* display, int argc, char** argv) { /* Parse arguments */ int index = atoi(argv[0]); - /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "end: stream=%i", index); - return 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 index bbd0c33f..e7c07188 100644 --- a/src/guacenc/instruction-img.c +++ b/src/guacenc/instruction-img.c @@ -44,12 +44,9 @@ int guacenc_handle_img(guacenc_display* display, int argc, char** argv) { int x = atoi(argv[4]); int y = atoi(argv[5]); - /* STUB */ - guacenc_log(GUAC_LOG_DEBUG, "img: stream=%i mask=0x%X layer=%i " - "mimetype=%s (%i, %i)", stream_index, mask, layer_index, - mimetype, x, y); - - return 0; + /* Create requested stream */ + return guacenc_display_create_image_stream(display, stream_index, + mask, layer_index, mimetype, x, y); } From a15a86ed005a7260732d1be8b30990025cff47e6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 27 Feb 2016 23:31:07 -0800 Subject: [PATCH 33/92] GUAC-236: Assemble entire image data prior to decoding. Switch to simple decoder function (no struct). --- src/guacenc/image-stream.c | 66 +++++++++----- src/guacenc/image-stream.h | 172 ++++++++++++------------------------- src/guacenc/jpeg.c | 31 +------ src/guacenc/jpeg.h | 2 +- src/guacenc/png.c | 31 +------ src/guacenc/png.h | 2 +- src/guacenc/webp.c | 31 +------ src/guacenc/webp.h | 2 +- 8 files changed, 116 insertions(+), 221 deletions(-) diff --git a/src/guacenc/image-stream.c b/src/guacenc/image-stream.c index 371fa6ba..fd442e1d 100644 --- a/src/guacenc/image-stream.c +++ b/src/guacenc/image-stream.c @@ -30,14 +30,16 @@ #include "webp.h" #endif +#include + #include #include guacenc_decoder_mapping guacenc_decoder_map[] = { - {"image/png", &guacenc_png_decoder}, - {"image/jpeg", &guacenc_jpeg_decoder}, + {"image/png", guacenc_png_decoder}, + {"image/jpeg", guacenc_jpeg_decoder}, #ifdef ENABLE_WEBP - {"image/webp", &guacenc_webp_decoder}, + {"image/webp", guacenc_webp_decoder}, #endif {NULL, NULL} }; @@ -78,9 +80,12 @@ guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index, stream->y = y; /* Associate with corresponding decoder */ - guacenc_decoder* decoder = stream->decoder = guacenc_get_decoder(mimetype); - if (decoder != NULL) - decoder->init_handler(stream); + 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; @@ -89,12 +94,27 @@ guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index, int guacenc_image_stream_receive(guacenc_image_stream* stream, unsigned char* data, int length) { - /* Invoke data handler of corresponding decoder (if any) */ - guacenc_decoder* decoder = stream->decoder; - if (decoder != NULL) - return decoder->data_handler(stream, data, length); + /* Allocate more space if necessary */ + if (stream->max_length - stream->length < length) { - /* If there is no decoder, simply return success */ + /* 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; } @@ -102,12 +122,20 @@ int guacenc_image_stream_receive(guacenc_image_stream* stream, int guacenc_image_stream_end(guacenc_image_stream* stream, guacenc_buffer* buffer) { - /* Invoke end handler of corresponding decoder (if any) */ - guacenc_decoder* decoder = stream->decoder; - if (decoder != NULL) - return decoder->end_handler(stream, 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; + + /* Draw surface to buffer */ + cairo_set_source_surface(buffer->cairo, surface, stream->x, stream->y); + cairo_fill(buffer->cairo); + cairo_surface_destroy(surface); return 0; } @@ -118,10 +146,8 @@ int guacenc_image_stream_free(guacenc_image_stream* stream) { if (stream == NULL) return 0; - /* Invoke free handler for decoder (if associated) */ - guacenc_decoder* decoder = stream->decoder; - if (decoder != NULL) - decoder->free_handler(stream); + /* Free image buffer */ + free(stream->buffer); /* Free actual stream */ free(stream); diff --git a/src/guacenc/image-stream.h b/src/guacenc/image-stream.h index 22b72f1d..26660eba 100644 --- a/src/guacenc/image-stream.h +++ b/src/guacenc/image-stream.h @@ -26,11 +26,31 @@ #include "config.h" #include "buffer.h" +#include + /** - * A decoder implementation which processes arbitrary image data of a - * particular type. Image data is fed explicitly into the decoder as chunks. + * The initial number of bytes to allocate for the image data buffer. If this + * buffer is not sufficiently larged, it will be dynamically reallocated as it + * grows. */ -typedef struct guacenc_decoder guacenc_decoder; +#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. @@ -60,117 +80,34 @@ typedef struct guacenc_image_stream { */ 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; - /** - * Arbitrary implementation-specific data associated with the stream. - */ - void* data; - } guacenc_image_stream; /** - * Callback function which is invoked when a decoder has been assigned to an - * image stream. - * - * @param stream - * The image stream that the decoder has been assigned to. - * - * @return - * Zero if initialization was successful, non-zero otherwise. - */ -typedef int guacenc_decoder_init_handler(guacenc_image_stream* stream); - -/** - * Callback function which is invoked when data has been received along an - * image stream with an associated decoder. - * - * @param stream - * The image stream that the decoder was assigned to. - * - * @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 provided data was processed successfully, non-zero - * otherwise. - */ -typedef int guacenc_decoder_data_handler(guacenc_image_stream* stream, - unsigned char* data, int length); - -/** - * Callback function which is invoked when an image stream with an associated - * decoder has ended (reached end-of-stream). The image stream will contain - * the required meta-information describing the drawing operation, including - * the destination X/Y coordinates. - * - * @param stream - * The image stream that has ended. - * - * @param buffer - * The buffer to which the decoded image should be drawn. - * - * @return - * Zero if the end of the stream has been processed successfully and the - * resulting image has been rendered to the given buffer, non-zero - * otherwise. - */ -typedef int guacenc_decoder_end_handler(guacenc_image_stream* stream, - guacenc_buffer* buffer); - -/** - * Callback function which will be invoked when the data associated with an - * image stream must be freed. This may happen at any time, and will not - * necessarily occur only after the image stream has ended. It is possible - * that an image stream will be in-progress at the end of a protocol dump, thus - * the memory associated with the stream will need to be freed without ever - * ending. - * - * @param stream - * The stream whose associated data must be freed. - * - * @return - * Zero if the data was successfully freed, non-zero otherwise. - */ -typedef int guacenc_decoder_free_handler(guacenc_image_stream* stream); - -struct guacenc_decoder { - - /** - * Callback invoked when this decoder has just been assigned to an image - * stream. - */ - guacenc_decoder_init_handler* init_handler; - - /** - * Callback invoked when data has been received along an image stream to - * which this decoder has been assigned. - */ - guacenc_decoder_data_handler* data_handler; - - /** - * Callback invoked when an image stream to which this decoder has been - * assigned has ended (reached end-of-stream). - */ - guacenc_decoder_end_handler* end_handler; - - /** - * Callback invoked when data associated with an image stream by this - * decoder must be freed. - */ - guacenc_decoder_free_handler* free_handler; - -}; - -/** - * Mapping of image mimetype to corresponding decoder. + * Mapping of image mimetype to corresponding decoder function. */ typedef struct guacenc_decoder_mapping { @@ -180,8 +117,8 @@ typedef struct guacenc_decoder_mapping { const char* mimetype; /** - * The decoder to use when an image stream of the associated mimetype is - * received. + * The decoder function to use when an image stream of the associated + * mimetype is received. */ guacenc_decoder* decoder; @@ -238,9 +175,9 @@ guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index, const char* mimetype, int x, int y); /** - * Signals the decoder of the given image stream that a chunk of image data - * has been received. If no decoder is associated with the given image stream, - * this function has no effect. + * 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. @@ -252,18 +189,19 @@ guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index, * The length of the chunk of data received, in bytes. * * @return - * Zero if the given data was handled successfully by the decoder, or - * non-zero if an error occurs. + * 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); /** - * Signals the decoder of the given image stream that no more data will be - * received and the image should 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 - * stored within the guacenc_image_stream. + * 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. diff --git a/src/guacenc/jpeg.c b/src/guacenc/jpeg.c index 397589ef..706e21c7 100644 --- a/src/guacenc/jpeg.c +++ b/src/guacenc/jpeg.c @@ -21,37 +21,14 @@ */ #include "config.h" -#include "buffer.h" -#include "image-stream.h" #include "jpeg.h" +#include + #include -int guacenc_jpeg_init(guacenc_image_stream* stream) { +cairo_surface_t* guacenc_jpeg_decoder(unsigned char* data, int length) { /* STUB */ - return 0; + return NULL; } -int guacenc_jpeg_data(guacenc_image_stream* stream, unsigned char* data, - int length) { - /* STUB */ - return 0; -} - -int guacenc_jpeg_end(guacenc_image_stream* stream, guacenc_buffer* buffer) { - /* STUB */ - return 0; -} - -int guacenc_jpeg_free(guacenc_image_stream* stream) { - /* STUB */ - return 0; -} - -guacenc_decoder guacenc_jpeg_decoder = { - .init_handler = guacenc_jpeg_init, - .data_handler = guacenc_jpeg_data, - .end_handler = guacenc_jpeg_end, - .free_handler = guacenc_jpeg_free -}; - diff --git a/src/guacenc/jpeg.h b/src/guacenc/jpeg.h index 718da3ad..142fbd12 100644 --- a/src/guacenc/jpeg.h +++ b/src/guacenc/jpeg.h @@ -29,7 +29,7 @@ /** * Decoder implementation which handles "image/jpeg" images. */ -extern guacenc_decoder guacenc_jpeg_decoder; +guacenc_decoder guacenc_jpeg_decoder; #endif diff --git a/src/guacenc/png.c b/src/guacenc/png.c index d77b59eb..68b4d741 100644 --- a/src/guacenc/png.c +++ b/src/guacenc/png.c @@ -21,37 +21,14 @@ */ #include "config.h" -#include "buffer.h" -#include "image-stream.h" #include "png.h" +#include + #include -int guacenc_png_init(guacenc_image_stream* stream) { +cairo_surface_t* guacenc_png_decoder(unsigned char* data, int length) { /* STUB */ - return 0; + return NULL; } -int guacenc_png_data(guacenc_image_stream* stream, unsigned char* data, - int length) { - /* STUB */ - return 0; -} - -int guacenc_png_end(guacenc_image_stream* stream, guacenc_buffer* buffer) { - /* STUB */ - return 0; -} - -int guacenc_png_free(guacenc_image_stream* stream) { - /* STUB */ - return 0; -} - -guacenc_decoder guacenc_png_decoder = { - .init_handler = guacenc_png_init, - .data_handler = guacenc_png_data, - .end_handler = guacenc_png_end, - .free_handler = guacenc_png_free -}; - diff --git a/src/guacenc/png.h b/src/guacenc/png.h index 9e3f68ac..86307a0b 100644 --- a/src/guacenc/png.h +++ b/src/guacenc/png.h @@ -29,7 +29,7 @@ /** * Decoder implementation which handles "image/png" images. */ -extern guacenc_decoder guacenc_png_decoder; +guacenc_decoder guacenc_png_decoder; #endif diff --git a/src/guacenc/webp.c b/src/guacenc/webp.c index 2a375669..63024373 100644 --- a/src/guacenc/webp.c +++ b/src/guacenc/webp.c @@ -21,37 +21,14 @@ */ #include "config.h" -#include "buffer.h" -#include "image-stream.h" #include "webp.h" +#include + #include -int guacenc_webp_init(guacenc_image_stream* stream) { +cairo_surface_t* guacenc_webp_decoder(unsigned char* data, int length) { /* STUB */ - return 0; + return NULL; } -int guacenc_webp_data(guacenc_image_stream* stream, unsigned char* data, - int length) { - /* STUB */ - return 0; -} - -int guacenc_webp_end(guacenc_image_stream* stream, guacenc_buffer* buffer) { - /* STUB */ - return 0; -} - -int guacenc_webp_free(guacenc_image_stream* stream) { - /* STUB */ - return 0; -} - -guacenc_decoder guacenc_webp_decoder = { - .init_handler = guacenc_webp_init, - .data_handler = guacenc_webp_data, - .end_handler = guacenc_webp_end, - .free_handler = guacenc_webp_free -}; - diff --git a/src/guacenc/webp.h b/src/guacenc/webp.h index a56f67d6..a6159698 100644 --- a/src/guacenc/webp.h +++ b/src/guacenc/webp.h @@ -29,7 +29,7 @@ /** * Decoder implementation which handles "image/webp" images. */ -extern guacenc_decoder guacenc_webp_decoder; +guacenc_decoder guacenc_webp_decoder; #endif From bd843158247d2aa76e2c2ba882b287957f94912f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 00:23:16 -0800 Subject: [PATCH 34/92] GUAC-236: Implement PNG decoding using Cairo's built-in PNG functions. --- src/guacenc/image-stream.c | 7 +++- src/guacenc/png.c | 84 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/src/guacenc/image-stream.c b/src/guacenc/image-stream.c index fd442e1d..02872cd1 100644 --- a/src/guacenc/image-stream.c +++ b/src/guacenc/image-stream.c @@ -133,8 +133,11 @@ int guacenc_image_stream_end(guacenc_image_stream* stream, return 1; /* Draw surface to buffer */ - cairo_set_source_surface(buffer->cairo, surface, stream->x, stream->y); - cairo_fill(buffer->cairo); + if (buffer->cairo != NULL) { + cairo_set_source_surface(buffer->cairo, surface, stream->x, stream->y); + cairo_fill(buffer->cairo); + } + cairo_surface_destroy(surface); return 0; diff --git a/src/guacenc/png.c b/src/guacenc/png.c index 68b4d741..abf1e58e 100644 --- a/src/guacenc/png.c +++ b/src/guacenc/png.c @@ -21,14 +21,92 @@ */ #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; -cairo_surface_t* guacenc_png_decoder(unsigned char* data, int length) { - /* STUB */ - return NULL; } From 83beccf7e44b93e426d3b30de54156240bcdfd40 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 00:52:57 -0800 Subject: [PATCH 35/92] GUAC-236: Ensure rectangle is explicitly set when painting buffers. --- src/guacenc/buffer.c | 2 +- src/guacenc/image-stream.c | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/guacenc/buffer.c b/src/guacenc/buffer.c index 379cd9ee..716a0411 100644 --- a/src/guacenc/buffer.c +++ b/src/guacenc/buffer.c @@ -96,8 +96,8 @@ int guacenc_buffer_resize(guacenc_buffer* buffer, int width, int height) { /* Copy old surface, if defined */ if (buffer->surface != NULL) { cairo_set_source_surface(cairo, buffer->surface, 0, 0); + cairo_rectangle(cairo, 0, 0, width, height); cairo_paint(cairo); - cairo_set_source_rgba(cairo, 0.0, 0.0, 0.0, 1.0); } /* Update properties */ diff --git a/src/guacenc/image-stream.c b/src/guacenc/image-stream.c index 02872cd1..b59ec71e 100644 --- a/src/guacenc/image-stream.c +++ b/src/guacenc/image-stream.c @@ -134,8 +134,16 @@ int guacenc_image_stream_end(guacenc_image_stream* stream, /* Draw surface to buffer */ if (buffer->cairo != NULL) { + + /* Get surface dimensions */ + int width = cairo_image_surface_get_width(surface); + int height = cairo_image_surface_get_height(surface); + + /* Paint surface contents to buffer */ 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); From ba9c1a2efd4f05d1815ec5e91dcfd0fdefeb5635 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 01:36:50 -0800 Subject: [PATCH 36/92] GUAC-236: Ensure buffers are resized to fit draw operations (unless they are within layers). --- src/guacenc/buffer.c | 22 ++++++++++++++++++++++ src/guacenc/buffer.h | 29 +++++++++++++++++++++++++++++ src/guacenc/image-stream.c | 15 ++++++++------- src/guacenc/instruction-copy.c | 4 ++++ src/guacenc/instruction-rect.c | 4 ++++ 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/guacenc/buffer.c b/src/guacenc/buffer.c index 716a0411..22de51e0 100644 --- a/src/guacenc/buffer.c +++ b/src/guacenc/buffer.c @@ -25,6 +25,7 @@ #include +#include #include guacenc_buffer* guacenc_buffer_alloc() { @@ -115,3 +116,24 @@ int guacenc_buffer_resize(guacenc_buffer* buffer, int width, int height) { } +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; + +} + diff --git a/src/guacenc/buffer.h b/src/guacenc/buffer.h index d2d7f152..1cb0c7ae 100644 --- a/src/guacenc/buffer.h +++ b/src/guacenc/buffer.h @@ -27,12 +27,20 @@ #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. */ @@ -107,5 +115,26 @@ void guacenc_buffer_free(guacenc_buffer* buffer); */ 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); + #endif diff --git a/src/guacenc/image-stream.c b/src/guacenc/image-stream.c index b59ec71e..81ec4d21 100644 --- a/src/guacenc/image-stream.c +++ b/src/guacenc/image-stream.c @@ -132,18 +132,19 @@ int guacenc_image_stream_end(guacenc_image_stream* stream, 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) { - - /* Get surface dimensions */ - int width = cairo_image_surface_get_width(surface); - int height = cairo_image_surface_get_height(surface); - - /* Paint surface contents to buffer */ 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); diff --git a/src/guacenc/instruction-copy.c b/src/guacenc/instruction-copy.c index b79fdd08..63b15700 100644 --- a/src/guacenc/instruction-copy.c +++ b/src/guacenc/instruction-copy.c @@ -57,6 +57,10 @@ int guacenc_handle_copy(guacenc_display* display, int argc, char** argv) { 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) { cairo_set_operator(dst->cairo, guacenc_display_cairo_operator(mask)); diff --git a/src/guacenc/instruction-rect.c b/src/guacenc/instruction-rect.c index 94fea8f3..6617ecb0 100644 --- a/src/guacenc/instruction-rect.c +++ b/src/guacenc/instruction-rect.c @@ -50,6 +50,10 @@ int guacenc_handle_rect(guacenc_display* display, int argc, char** argv) { 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); From e3d0c5e5ef16e3732552e90d74a97d9479539850 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 12:14:55 -0800 Subject: [PATCH 37/92] GUAC-236: Do not resize buffers if unnecessary. --- src/guacenc/buffer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/guacenc/buffer.c b/src/guacenc/buffer.c index 22de51e0..69531499 100644 --- a/src/guacenc/buffer.c +++ b/src/guacenc/buffer.c @@ -74,6 +74,10 @@ void guacenc_buffer_free(guacenc_buffer* 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); From 19814a4c8a3db3c875f49d917aad2b3b0e42e202 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 12:15:38 -0800 Subject: [PATCH 38/92] GUAC-236: Layers should be parented to default layer by default. --- src/guacenc/layer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/guacenc/layer.c b/src/guacenc/layer.c index 721f3602..890ad149 100644 --- a/src/guacenc/layer.c +++ b/src/guacenc/layer.c @@ -43,8 +43,8 @@ guacenc_layer* guacenc_layer_alloc() { /* Layers default to fully opaque */ layer->opacity = 0xFF; - /* Default to unparented */ - layer->parent_index = GUACENC_LAYER_NO_PARENT; + /* Default parented to default layer */ + layer->parent_index = 0; return layer; From 2e934993838b5473583833784306ac0a1a408102 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 12:21:35 -0800 Subject: [PATCH 39/92] GUAC-236: Render layers in order (position not yet correct). Output as PNG frames for verification. --- src/guacenc/display.c | 122 +++++++++++++++++++++++++++++++++++++++++- src/guacenc/display.h | 6 +++ 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index 6bd4c411..affcbb31 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -24,11 +24,51 @@ #include "display.h" #include "log.h" +#include #include #include #include +#include +#include +#include #include +#include + +/** + * 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) { + + /* If a is NULL, sort it to bottom */ + if (a == NULL) { + + /* ... unless b is also NULL, in which case they are equal */ + if (b == NULL) + return 0; + + return 1; + } + + /* If b is NULL (and a is not NULL), sort it to bottom */ + if (b == NULL) + return -1; + + guacenc_layer* layer_a = (guacenc_layer*) a; + guacenc_layer* layer_b = (guacenc_layer*) b; + + /* 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_sync(guacenc_display* display, guac_timestamp timestamp) { @@ -38,10 +78,69 @@ int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) { return 1; } + /* Get buffer for display frame rendering */ + guacenc_buffer* frame = display->frame; + /* Update timestamp of display */ display->last_sync = timestamp; - /* STUB */ + /* Ensure frame is the same size as the default layer */ + guacenc_buffer* def_layer = guacenc_display_get_related_buffer(display, 0); + guacenc_buffer_resize(frame, def_layer->width, def_layer->height); + + /* Render all layers to frame */ + cairo_t* cairo = frame->cairo; + if (cairo != NULL) { + + int i; + guacenc_layer* render_order[GUACENC_DISPLAY_MAX_LAYERS]; + + /* Copy and sort layers (ensuring layer #0 is always first) */ + memcpy(render_order, display->layers, sizeof(render_order)); + qsort(render_order + 1, GUACENC_DISPLAY_MAX_LAYERS - 1, + sizeof(guacenc_layer*), guacenc_display_layer_comparator); + + /* 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; + + /* Pull underlying buffer */ + guacenc_buffer* buffer = layer->buffer; + + /* Ignore layers with empty buffers */ + if (buffer->surface == NULL) + continue; + + /* TODO: Determine actual location relative to parent layer */ + int x = layer->x; + int y = layer->y; + + /* Render buffer to layer */ + cairo_reset_clip(cairo); + cairo_rectangle(cairo, x, y, buffer->width, buffer->height); + cairo_clip(cairo); + + cairo_set_source_surface(cairo, buffer->surface, x, y); + cairo_paint_with_alpha(cairo, layer->opacity / 255.0); + + } + + } + + /* STUB: Write frame as PNG */ + char filename[256]; + sprintf(filename, "frame-%" PRId64 ".png", timestamp); + cairo_surface_t* surface = frame->surface; + if (surface != NULL) + cairo_surface_write_to_png(surface, filename); return 0; @@ -67,6 +166,10 @@ guacenc_layer* guacenc_display_get_layer(guacenc_display* display, 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; @@ -118,6 +221,9 @@ guacenc_buffer* guacenc_display_get_buffer(guacenc_display* display, return NULL; } + /* All non-layer buffers must autosize */ + buffer->autosize = true; + /* Store buffer within display for future retrieval / management */ display->buffers[internal_index] = buffer; @@ -281,7 +387,16 @@ cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask) { } guacenc_display* guacenc_display_alloc() { - return (guacenc_display*) calloc(1, sizeof(guacenc_display)); + + /* Allocate display */ + guacenc_display* display = + (guacenc_display*) calloc(1, sizeof(guacenc_display)); + + /* Allocate buffer for frame rendering */ + display->frame = guacenc_buffer_alloc(); + + return display; + } int guacenc_display_free(guacenc_display* display) { @@ -292,6 +407,9 @@ int guacenc_display_free(guacenc_display* display) { if (display == NULL) return 0; + /* Free internal frame buffer */ + guacenc_buffer_free(display->frame); + /* Free all buffers */ for (i = 0; i < GUACENC_DISPLAY_MAX_BUFFERS; i++) guacenc_buffer_free(display->buffers[i]); diff --git a/src/guacenc/display.h b/src/guacenc/display.h index 68d6012e..bbd1db8c 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -84,6 +84,12 @@ typedef struct guacenc_display { */ guac_timestamp last_sync; + /** + * The internal buffer used by the display to record the previous frame + * and to render additional frames. + */ + guacenc_buffer* frame; + } guacenc_display; /** From 1d4e6ce92462f224670824c70c2932ee5b3aad6d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 14:07:10 -0800 Subject: [PATCH 40/92] GUAC-236: Fix comparator. Sort in descending order of depth. --- src/guacenc/display.c | 53 ++++++++++++++++++++++++++++++++++++------- src/guacenc/display.h | 13 +++++++++++ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index affcbb31..3d085568 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -22,6 +22,7 @@ #include "config.h" #include "display.h" +#include "layer.h" #include "log.h" #include @@ -35,6 +36,14 @@ #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 @@ -44,22 +53,28 @@ */ 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 (a == NULL) { + if (layer_a == NULL) { /* ... unless b is also NULL, in which case they are equal */ - if (b == NULL) + if (layer_b == NULL) return 0; return 1; } /* If b is NULL (and a is not NULL), sort it to bottom */ - if (b == NULL) + if (layer_b == NULL) return -1; - guacenc_layer* layer_a = (guacenc_layer*) a; - guacenc_layer* layer_b = (guacenc_layer*) b; + /* 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) @@ -95,10 +110,13 @@ int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) { int i; guacenc_layer* render_order[GUACENC_DISPLAY_MAX_LAYERS]; - /* Copy and sort layers (ensuring layer #0 is always first) */ + /* Copy list of layers within display */ memcpy(render_order, display->layers, sizeof(render_order)); - qsort(render_order + 1, GUACENC_DISPLAY_MAX_LAYERS - 1, - sizeof(guacenc_layer*), guacenc_display_layer_comparator); + + /* Sort layers by depth, parent, and Z */ + __qsort_display = display; + qsort(render_order, GUACENC_DISPLAY_MAX_LAYERS, sizeof(guacenc_layer*), + guacenc_display_layer_comparator); /* Render each layer, in order */ for (i = 0; i < GUACENC_DISPLAY_MAX_LAYERS; i++) { @@ -179,6 +197,25 @@ guacenc_layer* guacenc_display_get_layer(guacenc_display* display, } +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) { diff --git a/src/guacenc/display.h b/src/guacenc/display.h index bbd1db8c..e300cfb5 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -151,6 +151,19 @@ int guacenc_display_free(guacenc_display* display); 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. From 80b3d51a491a21d3a1f73ce683950a1e952be522 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 14:54:44 -0800 Subject: [PATCH 41/92] GUAC-236: Add function for copying buffer state. --- src/guacenc/buffer.c | 31 +++++++++++++++++++++++++++++++ src/guacenc/buffer.h | 17 +++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/guacenc/buffer.c b/src/guacenc/buffer.c index 69531499..a4776289 100644 --- a/src/guacenc/buffer.c +++ b/src/guacenc/buffer.c @@ -25,6 +25,7 @@ #include +#include #include #include @@ -141,3 +142,33 @@ int guacenc_buffer_fit(guacenc_buffer* buffer, int x, int y) { } +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 index 1cb0c7ae..caf11e56 100644 --- a/src/guacenc/buffer.h +++ b/src/guacenc/buffer.h @@ -136,5 +136,22 @@ int guacenc_buffer_resize(guacenc_buffer* buffer, int width, int height); */ 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 From 79181567e5aeca740ede016692b9d35bdc773bf7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 14:55:29 -0800 Subject: [PATCH 42/92] GUAC-236: Store and preseve frame buffer within each layer. --- src/guacenc/layer.c | 11 +++++++++++ src/guacenc/layer.h | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/src/guacenc/layer.c b/src/guacenc/layer.c index 890ad149..f6cd12b7 100644 --- a/src/guacenc/layer.c +++ b/src/guacenc/layer.c @@ -40,6 +40,14 @@ guacenc_layer* guacenc_layer_alloc() { 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; @@ -56,6 +64,9 @@ void guacenc_layer_free(guacenc_layer* layer) { if (layer == NULL) return; + /* Free internal frame buffer */ + guacenc_buffer_free(layer->frame); + /* Free underlying buffer */ guacenc_buffer_free(layer->buffer); diff --git a/src/guacenc/layer.h b/src/guacenc/layer.h index 50f47040..d2a9d5a4 100644 --- a/src/guacenc/layer.h +++ b/src/guacenc/layer.h @@ -74,6 +74,12 @@ typedef struct guacenc_layer { */ 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; /** From a24152df02a93a3e953d8ab7f980dd8169a55a8e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 14:59:33 -0800 Subject: [PATCH 43/92] GUAC-236: Flatten and render all layers upon sync. --- src/guacenc/display.c | 134 +++++++++++++++++++++++------------------- src/guacenc/display.h | 6 -- 2 files changed, 72 insertions(+), 68 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index 3d085568..86398253 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -93,70 +94,91 @@ int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) { return 1; } - /* Get buffer for display frame rendering */ - guacenc_buffer* frame = display->frame; - /* Update timestamp of display */ display->last_sync = timestamp; - /* Ensure frame is the same size as the default layer */ - guacenc_buffer* def_layer = guacenc_display_get_related_buffer(display, 0); - guacenc_buffer_resize(frame, def_layer->width, def_layer->height); + int i; + guacenc_layer* render_order[GUACENC_DISPLAY_MAX_LAYERS]; - /* Render all layers to frame */ - cairo_t* cairo = frame->cairo; - if (cairo != NULL) { + /* Copy list of layers within display */ + memcpy(render_order, display->layers, sizeof(render_order)); - int i; - guacenc_layer* render_order[GUACENC_DISPLAY_MAX_LAYERS]; + /* Sort layers by depth, parent, and Z */ + __qsort_display = display; + qsort(render_order, GUACENC_DISPLAY_MAX_LAYERS, sizeof(guacenc_layer*), + guacenc_display_layer_comparator); - /* Copy list of layers within display */ - memcpy(render_order, display->layers, sizeof(render_order)); + /* Reset layer frame buffers */ + for (i = 0; i < GUACENC_DISPLAY_MAX_LAYERS; i++) { - /* Sort layers by depth, parent, and Z */ - __qsort_display = display; - qsort(render_order, GUACENC_DISPLAY_MAX_LAYERS, sizeof(guacenc_layer*), - guacenc_display_layer_comparator); + /* Pull current layer, ignoring unallocated layers */ + guacenc_layer* layer = render_order[i]; + if (layer == NULL) + continue; - /* Render each layer, in order */ - for (i = 0; i < GUACENC_DISPLAY_MAX_LAYERS; i++) { + /* Get source buffer and destination frame buffer */ + guacenc_buffer* buffer = layer->buffer; + guacenc_buffer* frame = layer->frame; - /* 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; - - /* Pull underlying buffer */ - guacenc_buffer* buffer = layer->buffer; - - /* Ignore layers with empty buffers */ - if (buffer->surface == NULL) - continue; - - /* TODO: Determine actual location relative to parent layer */ - int x = layer->x; - int y = layer->y; - - /* Render buffer to layer */ - cairo_reset_clip(cairo); - cairo_rectangle(cairo, x, y, buffer->width, buffer->height); - cairo_clip(cairo); - - cairo_set_source_surface(cairo, buffer->surface, x, y); - cairo_paint_with_alpha(cairo, layer->opacity / 255.0); - - } + /* 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); + + } + + /* Retrieve default layer (guaranteed to not be NULL) */ + guacenc_layer* def_layer = guacenc_display_get_layer(display, 0); + assert(def_layer != NULL); + /* STUB: Write frame as PNG */ char filename[256]; sprintf(filename, "frame-%" PRId64 ".png", timestamp); - cairo_surface_t* surface = frame->surface; + cairo_surface_t* surface = def_layer->frame->surface; if (surface != NULL) cairo_surface_write_to_png(surface, filename); @@ -424,16 +446,7 @@ cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask) { } guacenc_display* guacenc_display_alloc() { - - /* Allocate display */ - guacenc_display* display = - (guacenc_display*) calloc(1, sizeof(guacenc_display)); - - /* Allocate buffer for frame rendering */ - display->frame = guacenc_buffer_alloc(); - - return display; - + return (guacenc_display*) calloc(1, sizeof(guacenc_display)); } int guacenc_display_free(guacenc_display* display) { @@ -444,9 +457,6 @@ int guacenc_display_free(guacenc_display* display) { if (display == NULL) return 0; - /* Free internal frame buffer */ - guacenc_buffer_free(display->frame); - /* Free all buffers */ for (i = 0; i < GUACENC_DISPLAY_MAX_BUFFERS; i++) guacenc_buffer_free(display->buffers[i]); diff --git a/src/guacenc/display.h b/src/guacenc/display.h index e300cfb5..a67b861f 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -84,12 +84,6 @@ typedef struct guacenc_display { */ guac_timestamp last_sync; - /** - * The internal buffer used by the display to record the previous frame - * and to render additional frames. - */ - guacenc_buffer* frame; - } guacenc_display; /** From 960ee263e8757ca009a9bf407d89b0b16233ddd6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 15:45:04 -0800 Subject: [PATCH 44/92] GUAC-236: Split massive display code into more reasonable files. --- src/guacenc/Makefile.am | 51 ++-- src/guacenc/buffer.c | 1 - src/guacenc/display-buffers.c | 110 +++++++++ src/guacenc/display-flatten.c | 160 ++++++++++++ src/guacenc/display-image-streams.c | 84 +++++++ src/guacenc/display-layers.c | 102 ++++++++ src/guacenc/display-sync.c | 66 +++++ src/guacenc/display.c | 361 ---------------------------- src/guacenc/display.h | 16 ++ 9 files changed, 566 insertions(+), 385 deletions(-) create mode 100644 src/guacenc/display-buffers.c create mode 100644 src/guacenc/display-flatten.c create mode 100644 src/guacenc/display-image-streams.c create mode 100644 src/guacenc/display-layers.c create mode 100644 src/guacenc/display-sync.c diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 2baa6003..54f1df24 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -35,29 +35,34 @@ noinst_HEADERS = \ log.h \ png.h -guacenc_SOURCES = \ - buffer.c \ - display.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 \ +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 \ png.c # Compile WebP support if available diff --git a/src/guacenc/buffer.c b/src/guacenc/buffer.c index a4776289..f7387997 100644 --- a/src/guacenc/buffer.c +++ b/src/guacenc/buffer.c @@ -26,7 +26,6 @@ #include #include -#include #include guacenc_buffer* guacenc_buffer_alloc() { diff --git a/src/guacenc/display-buffers.c b/src/guacenc/display-buffers.c new file mode 100644 index 00000000..9fd8663d --- /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 / 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 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..23b43e75 --- /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 / 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 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..33a358b5 --- /dev/null +++ b/src/guacenc/display-sync.c @@ -0,0 +1,66 @@ +/* + * 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 +#include + +#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); + + /* STUB: Write frame as PNG */ + char filename[256]; + sprintf(filename, "frame-%" PRId64 ".png", timestamp); + cairo_surface_t* surface = def_layer->frame->surface; + if (surface != NULL) + cairo_surface_write_to_png(surface, filename); + + return 0; + +} + diff --git a/src/guacenc/display.c b/src/guacenc/display.c index 86398253..fdfb93a0 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -22,371 +22,10 @@ #include "config.h" #include "display.h" -#include "layer.h" -#include "log.h" #include -#include -#include -#include -#include -#include -#include -#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_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; - - 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); - - } - - /* Retrieve default layer (guaranteed to not be NULL) */ - guacenc_layer* def_layer = guacenc_display_get_layer(display, 0); - assert(def_layer != NULL); - - /* STUB: Write frame as PNG */ - char filename[256]; - sprintf(filename, "frame-%" PRId64 ".png", timestamp); - cairo_surface_t* surface = def_layer->frame->surface; - if (surface != NULL) - cairo_surface_write_to_png(surface, filename); - - return 0; - -} - -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 / 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 1; - } - - /* Free layer (if allocated) */ - guacenc_layer_free(display->layers[index]); - - /* Mark layer as freed */ - display->layers[index] = NULL; - - return 0; - -} - -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 / 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 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); - -} - -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; - -} cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask) { diff --git a/src/guacenc/display.h b/src/guacenc/display.h index a67b861f..cfbc5377 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -102,6 +102,22 @@ typedef struct guacenc_display { */ 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 From 21c568c0b4d2b2d1aed5a23674404c772a7acc28 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 16:43:25 -0800 Subject: [PATCH 45/92] GUAC-236: Implement WebP decoding. --- src/guacenc/webp.c | 47 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/guacenc/webp.c b/src/guacenc/webp.c index 63024373..191d0451 100644 --- a/src/guacenc/webp.c +++ b/src/guacenc/webp.c @@ -21,14 +21,57 @@ */ #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) { - /* STUB */ - return NULL; + + 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; + } From 3a972d4845670daf8903fce19f081fda1f83bd17 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 19:07:10 -0800 Subject: [PATCH 46/92] GUAC-236: Use temporary surface if source rect might intersect dest rect. --- src/guacenc/instruction-copy.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/guacenc/instruction-copy.c b/src/guacenc/instruction-copy.c index 63b15700..c0abd91d 100644 --- a/src/guacenc/instruction-copy.c +++ b/src/guacenc/instruction-copy.c @@ -63,10 +63,41 @@ int guacenc_handle_copy(guacenc_display* display, int argc, char** argv) { /* 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, src->surface, dx - sx, dy - sy); + 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; From a5ec24c17bc15bc56ce3d3e0d145ec610550cbd4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 19:21:14 -0800 Subject: [PATCH 47/92] GUAC-236: Fix buffer resize (contents were being cleared). --- src/guacenc/buffer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/guacenc/buffer.c b/src/guacenc/buffer.c index f7387997..1a3481f9 100644 --- a/src/guacenc/buffer.c +++ b/src/guacenc/buffer.c @@ -100,8 +100,9 @@ int guacenc_buffer_resize(guacenc_buffer* buffer, int width, int height) { /* 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_rectangle(cairo, 0, 0, width, height); + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); cairo_paint(cairo); } From bd5bd29ec0db89c763de3f1fc4792961a346d013 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 20:23:52 -0800 Subject: [PATCH 48/92] GUAC-236: Ensure client socket is always freed. --- src/libguac/client.c | 3 +++ 1 file changed, 3 insertions(+) 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); From dc2feba72c4d11715fd5b384927cc31b79c1f609 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 20:43:13 -0800 Subject: [PATCH 49/92] GUAC-236: Add guac_socket_tee() function which allocates a socket that delegates all operations to a primary socket while duplicating write operations to a secondary socket. --- src/libguac/Makefile.am | 1 + src/libguac/guacamole/socket.h | 32 +++++ src/libguac/socket-tee.c | 234 +++++++++++++++++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 src/libguac/socket-tee.c 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/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; + +} + From a3fef4c1fc960bb0d9827d52b9c4fa5dad6d5ee6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 21:31:50 -0800 Subject: [PATCH 50/92] GUAC-236: Add common functions for creating client-level session recordings (Guacamole protocol dumps). --- src/common/Makefile.am | 2 + src/common/guac_recording.c | 143 ++++++++++++++++++++++++++++++++++++ src/common/guac_recording.h | 81 ++++++++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 src/common/guac_recording.c create mode 100644 src/common/guac_recording.h 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..1b8d513a --- /dev/null +++ b/src/common/guac_recording.c @@ -0,0 +1,143 @@ +/* + * 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 + +/** + * 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 */ + + 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 + From 6fc208554d89a915be448957ca2105d6ed290ceb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Feb 2016 23:51:46 -0800 Subject: [PATCH 51/92] GUAC-236: Add session recording parameters to VNC, RDP, and SSH. --- src/protocols/rdp/rdp.c | 9 +++++++ src/protocols/rdp/rdp_settings.c | 25 ++++++++++++++++++++ src/protocols/rdp/rdp_settings.h | 22 ++++++++++++++++++ src/protocols/ssh/settings.c | 40 ++++++++++++++++++++++++++++++++ src/protocols/ssh/settings.h | 22 ++++++++++++++++++ src/protocols/ssh/ssh.c | 9 +++++++ src/protocols/vnc/settings.c | 24 +++++++++++++++++++ src/protocols/vnc/settings.h | 22 ++++++++++++++++++ src/protocols/vnc/vnc.c | 9 +++++++ 9 files changed, 182 insertions(+) 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/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); From 0676a7a51cf564ed4b065932f92ef91041e3dfd5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 29 Feb 2016 10:40:12 -0800 Subject: [PATCH 52/92] GUAC-236: Add missing LDFLAGS for guacenc. --- src/guacenc/Makefile.am | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 54f1df24..d448c613 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -78,3 +78,8 @@ guacenc_CFLAGS = \ guacenc_LDADD = \ @LIBGUAC_LTLIB@ +guacenc_LDFLAGS = \ + @CAIRO_LIBS@ \ + @JPEG_LIBS@ \ + @WEBP_LIBS@ + From 09a4f4da39e73c59cd4f7f1c13432ac4007e53cd Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 9 Mar 2016 15:43:57 -0800 Subject: [PATCH 53/92] GUAC-236: Add libavcodec to guacenc build. Conditionally compile guacenc and guacd. --- Makefile.am | 10 ++++-- configure.ac | 72 +++++++++++++++++++++++++++++++++++++++++ src/guacenc/Makefile.am | 8 +++-- 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/Makefile.am b/Makefile.am index 57dc3bda..608ea90d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,8 +39,6 @@ DIST_SUBDIRS = \ SUBDIRS = \ src/libguac \ src/common \ - src/guacd \ - src/guacenc \ tests if ENABLE_COMMON_SSH @@ -67,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 d4d2644e..b30db0a7 100644 --- a/configure.ac +++ b/configure.ac @@ -146,6 +146,25 @@ 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"]) + # # libssl # @@ -966,6 +985,34 @@ 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"]) + +# +# Output Makefiles +# AC_CONFIG_FILES([Makefile tests/Makefile @@ -981,13 +1028,32 @@ AC_CONFIG_FILES([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 @@ -997,6 +1063,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION freerdp ............. ${have_freerdp} pango ............... ${have_pango} + libavcodec .......... ${have_libavcodec} libssh2 ............. ${have_libssh2} libssl .............. ${have_ssl} libtelnet ........... ${have_libtelnet} @@ -1012,6 +1079,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/guacenc/Makefile.am b/src/guacenc/Makefile.am index d448c613..a2c807df 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -73,13 +73,15 @@ endif guacenc_CFLAGS = \ -Werror -Wall -pedantic \ + @AVCODEC_CFLAGS@ \ @LIBGUAC_INCLUDE@ guacenc_LDADD = \ @LIBGUAC_LTLIB@ -guacenc_LDFLAGS = \ - @CAIRO_LIBS@ \ - @JPEG_LIBS@ \ +guacenc_LDFLAGS = \ + @AVCODEC_LIBS@ \ + @CAIRO_LIBS@ \ + @JPEG_LIBS@ \ @WEBP_LIBS@ From 19ac6e82868056dfc2f6420cf7be968099b92c2d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 10 Mar 2016 16:33:26 -0800 Subject: [PATCH 54/92] GUAC-236: Abstract video encoding process as guacenc_video struct. Move PNG-writing stub therein. --- src/guacenc/Makefile.am | 6 +- src/guacenc/display-sync.c | 15 ++-- src/guacenc/display.c | 18 +++- src/guacenc/display.h | 6 ++ src/guacenc/video.c | 132 ++++++++++++++++++++++++++++ src/guacenc/video.h | 173 +++++++++++++++++++++++++++++++++++++ 6 files changed, 337 insertions(+), 13 deletions(-) create mode 100644 src/guacenc/video.c create mode 100644 src/guacenc/video.h diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index a2c807df..e42a8401 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -33,7 +33,8 @@ noinst_HEADERS = \ jpeg.h \ layer.h \ log.h \ - png.h + png.h \ + video.h guacenc_SOURCES = \ buffer.c \ @@ -63,7 +64,8 @@ guacenc_SOURCES = \ jpeg.c \ layer.c \ log.c \ - png.c + png.c \ + video.c # Compile WebP support if available if ENABLE_WEBP diff --git a/src/guacenc/display-sync.c b/src/guacenc/display-sync.c index 33a358b5..e365e7af 100644 --- a/src/guacenc/display-sync.c +++ b/src/guacenc/display-sync.c @@ -24,14 +24,12 @@ #include "display.h" #include "layer.h" #include "log.h" +#include "video.h" -#include #include #include #include -#include -#include #include int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) { @@ -53,13 +51,12 @@ int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) { guacenc_layer* def_layer = guacenc_display_get_layer(display, 0); assert(def_layer != NULL); - /* STUB: Write frame as PNG */ - char filename[256]; - sprintf(filename, "frame-%" PRId64 ".png", timestamp); - cairo_surface_t* surface = def_layer->frame->surface; - if (surface != NULL) - cairo_surface_write_to_png(surface, filename); + /* 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 index fdfb93a0..f83bbb4a 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -22,6 +22,7 @@ #include "config.h" #include "display.h" +#include "video.h" #include @@ -85,7 +86,17 @@ cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask) { } guacenc_display* guacenc_display_alloc() { - return (guacenc_display*) calloc(1, sizeof(guacenc_display)); + + /* Allocate display */ + guacenc_display* display = + (guacenc_display*) calloc(1, sizeof(guacenc_display)); + + /* STUB: Prepare video encoding */ + display->output = guacenc_video_alloc("/tmp/test.mpg", + 640, 480, 25, 400000); + + return display; + } int guacenc_display_free(guacenc_display* display) { @@ -96,6 +107,9 @@ int guacenc_display_free(guacenc_display* 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]); @@ -109,7 +123,7 @@ int guacenc_display_free(guacenc_display* display) { guacenc_image_stream_free(display->image_streams[i]); free(display); - return 0; + return retval; } diff --git a/src/guacenc/display.h b/src/guacenc/display.h index cfbc5377..3ca51397 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -27,6 +27,7 @@ #include "buffer.h" #include "image-stream.h" #include "layer.h" +#include "video.h" #include #include @@ -84,6 +85,11 @@ typedef struct guacenc_display { */ guac_timestamp last_sync; + /** + * The video that this display is recording to. + */ + guacenc_video* output; + } guacenc_display; /** diff --git a/src/guacenc/video.c b/src/guacenc/video.c new file mode 100644 index 00000000..6c803e7a --- /dev/null +++ b/src/guacenc/video.c @@ -0,0 +1,132 @@ +/* + * 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 "video.h" + +#include + +#include +#include +#include + +guacenc_video* guacenc_video_alloc(const char* path, int width, int height, + int framerate, int bitrate) { + + /* Allocate video structure */ + guacenc_video* video = malloc(sizeof(guacenc_video)); + if (video == NULL) + return NULL; + + /* Init properties of video */ + video->width = width; + video->height = height; + video->frame_duration = 1000 / framerate; + video->bitrate = bitrate; + + /* No frames have been written or prepared yet */ + video->last_timestamp = 0; + video->current_time = 0; + video->next_frame = NULL; + + return video; + +} + +/** + * 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) { + + /* Ignore empty frames */ + guacenc_buffer* buffer = video->next_frame; + if (buffer == NULL) + return 0; + + /* STUB: Write frame as PNG */ + char filename[256]; + sprintf(filename, "frame-%012" PRId64 ".png", video->current_time); + cairo_surface_t* surface = buffer->surface; + if (surface != NULL) + cairo_surface_write_to_png(surface, filename); + + /* Update internal timestamp */ + video->current_time += video->frame_duration; + + return 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) + / video->frame_duration; + + /* 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; + +} + +void guacenc_video_prepare_frame(guacenc_video* video, guacenc_buffer* buffer) { + video->next_frame = buffer; +} + +int guacenc_video_free(guacenc_video* video) { + + /* Ignore NULL video */ + if (video == NULL) + return 0; + + /* Write final frame */ + guacenc_video_flush_frame(video); + + free(video); + return 0; + +} + diff --git a/src/guacenc/video.h b/src/guacenc/video.h new file mode 100644 index 00000000..f333df70 --- /dev/null +++ b/src/guacenc/video.h @@ -0,0 +1,173 @@ +/* + * 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 + +/** + * 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 { + + /** + * The width of the video, in pixels. + */ + int width; + + /** + * The height of the video, in pixels. + */ + int height; + + /** + * The duration of a single frame in milliseconds. + */ + int frame_duration; + + /** + * The desired output bitrate of the video, in bits per second. + */ + int bitrate; + + /** + * A pointer to the buffer containing the most recent frame submitted via + * guacenc_video_prepare_frame(). This buffer MUST not be freed prior to + * the call to guacenc_video_free(). + */ + guacenc_buffer* next_frame; + + /** + * The timestamp associated with the last frame, or 0 if no frames have yet + * been added. + */ + guac_timestamp last_timestamp; + + /** + * The relative position within the current video timeline, where 0 is the + * first frame of video, in milliseconds. This value will be incremented as + * frames are output. + */ + guac_timestamp current_time; + +} guacenc_video; + +/** + * Allocates a new guacenc_video which encodes video according to the given + * specifications, saving the output in the given file. The output file will be + * created if necessary and truncated if it already exists. Frames will be + * scaled up or down as necessary to fit the given width and height. Note that + * frames written to this guacenc_video may be buffered, and are not guaranteed + * to be written until guacenc_video_free() is called. + * + * @param path + * The full path to the file in which encoded video should be written. + * + * @param width + * The width of the desired video, in pixels. + * + * @param height + * The height of the desired video, in pixels. + * + * @param framerate + * The rate at which frames should be encoded within the video, in frames + * per second. + * + * @param bitrate + * The desired overall bitrate of the resulting encoded video, in kilobits + * per second. + */ +guacenc_video* guacenc_video_alloc(const char* path, int width, int height, + int framerate, 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()). + * + * Any given buffer MUST NOT be freed prior to the call to guacenc_video_free() + * which ultimately ends the encoding process. + * + * @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. This buffer MUST NOT be freed prior to the call to + * guacenc_video_free() which ultimately ends the encoding process. + */ +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. Once this function is invoked, it is safe to resume freeing + * any buffers provided to guacenc_video_prepare_frame(). + * + * @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 + From d94915c5150d9262b6d44cdac9982df38951ff8f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 10 Mar 2016 17:31:35 -0800 Subject: [PATCH 55/92] GUAC-236: Ensure display allocation can handle video preparation failures. --- src/guacenc/display.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index f83bbb4a..baf6845d 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -87,13 +87,18 @@ cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask) { guacenc_display* guacenc_display_alloc() { + /* STUB: Prepare video encoding */ + guacenc_video* video = + guacenc_video_alloc("/tmp/test.mpg", 640, 480, 25, 400000); + if (video == NULL) + return NULL; + /* Allocate display */ guacenc_display* display = (guacenc_display*) calloc(1, sizeof(guacenc_display)); - /* STUB: Prepare video encoding */ - display->output = guacenc_video_alloc("/tmp/test.mpg", - 640, 480, 25, 400000); + /* Associate display with video output */ + display->output = video; return display; From a8cba53537e2739bea9c8f714719660506d7d604 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 10 Mar 2016 17:44:07 -0800 Subject: [PATCH 56/92] GUAC-236: Perform codec lookup via libavcodec. --- src/guacenc/display.c | 4 ++-- src/guacenc/guacenc.c | 5 +++++ src/guacenc/video.c | 14 ++++++++++++-- src/guacenc/video.h | 8 ++++++-- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index baf6845d..21af2183 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -88,8 +88,8 @@ cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask) { guacenc_display* guacenc_display_alloc() { /* STUB: Prepare video encoding */ - guacenc_video* video = - guacenc_video_alloc("/tmp/test.mpg", 640, 480, 25, 400000); + guacenc_video* video = guacenc_video_alloc("/tmp/test.mpg", "mpeg4", + 640, 480, 25, 400000); if (video == NULL) return NULL; diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index c5b00efc..79b7d61d 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -25,6 +25,8 @@ #include "encode.h" #include "log.h" +#include + int main(int argc, char* argv[]) { int i; @@ -39,6 +41,9 @@ int main(int argc, char* argv[]) { return 0; } + /* Prepare libavcodec */ + avcodec_register_all(); + /* Track number of overall failures */ int total_files = argc - 1; int failures = 0; diff --git a/src/guacenc/video.c b/src/guacenc/video.c index 6c803e7a..e7a4dfd3 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -22,22 +22,32 @@ #include "config.h" #include "buffer.h" +#include "log.h" #include "video.h" +#include +#include #include #include #include #include -guacenc_video* guacenc_video_alloc(const char* path, int width, int height, - int framerate, int bitrate) { +guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, + int width, int height, int framerate, int bitrate) { /* Allocate video structure */ guacenc_video* video = malloc(sizeof(guacenc_video)); if (video == NULL) return NULL; + AVCodec* codec = avcodec_find_encoder_by_name(codec_name); + if (codec == NULL) { + guacenc_log(GUAC_LOG_ERROR, "Failed to locate codec: \"%s\"", + codec_name); + return NULL; + } + /* Init properties of video */ video->width = width; video->height = height; diff --git a/src/guacenc/video.h b/src/guacenc/video.h index f333df70..b5a27498 100644 --- a/src/guacenc/video.h +++ b/src/guacenc/video.h @@ -88,6 +88,10 @@ typedef struct guacenc_video { * @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. * @@ -102,8 +106,8 @@ typedef struct guacenc_video { * The desired overall bitrate of the resulting encoded video, in kilobits * per second. */ -guacenc_video* guacenc_video_alloc(const char* path, int width, int height, - int framerate, int bitrate); +guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, + int width, int height, int framerate, int bitrate); /** * Advances the timeline of the encoding process to the given timestamp, such From 17df235c4e1a74bf29c4604fae49a6876fd2b87d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 10 Mar 2016 18:29:03 -0800 Subject: [PATCH 57/92] GUAC-236: Actually open and init codec. Replace PNG output with stub. --- src/guacenc/video.c | 53 +++++++++++++++++++++++++++++++++++---------- src/guacenc/video.h | 7 ++++++ 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/guacenc/video.c b/src/guacenc/video.c index e7a4dfd3..f4fd1fcd 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -36,19 +36,47 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, int width, int height, int framerate, int bitrate) { - /* Allocate video structure */ - guacenc_video* video = malloc(sizeof(guacenc_video)); - if (video == NULL) - return NULL; - + /* 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\"", + guacenc_log(GUAC_LOG_ERROR, "Failed to locate codec \"%s\".", codec_name); return NULL; } + /* 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); + return NULL; + } + + /* Init context with encoding parameters */ + context->bit_rate = bitrate; + context->width = width; + context->height = height; + context->time_base = (AVRational) { 1, 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); + avcodec_free_context(&context); + return NULL; + } + + /* Allocate video structure */ + guacenc_video* video = malloc(sizeof(guacenc_video)); + if (video == NULL) { + avcodec_free_context(&context); + return NULL; + } + /* Init properties of video */ + video->context = context; video->width = width; video->height = height; video->frame_duration = 1000 / framerate; @@ -81,12 +109,9 @@ static int guacenc_video_flush_frame(guacenc_video* video) { if (buffer == NULL) return 0; - /* STUB: Write frame as PNG */ - char filename[256]; - sprintf(filename, "frame-%012" PRId64 ".png", video->current_time); - cairo_surface_t* surface = buffer->surface; - if (surface != NULL) - cairo_surface_write_to_png(surface, filename); + /* STUB: Write frame to video */ + guacenc_log(GUAC_LOG_DEBUG, "Writing frame @ %" PRId64 "ms", + video->current_time); /* Update internal timestamp */ video->current_time += video->frame_duration; @@ -135,6 +160,10 @@ int guacenc_video_free(guacenc_video* video) { /* Write final frame */ guacenc_video_flush_frame(video); + /* 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 index b5a27498..21787bbc 100644 --- a/src/guacenc/video.h +++ b/src/guacenc/video.h @@ -27,6 +27,7 @@ #include "buffer.h" #include +#include /** * A video which is actively being encoded. Frames can be added to the video @@ -35,6 +36,12 @@ */ typedef struct guacenc_video { + /** + * 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. */ From 9a5b503da5a52b7b4fb05ba4cb56681f7d1a7b0f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 12:28:21 -0800 Subject: [PATCH 58/92] GUAC-236: Add libavutil to build. --- configure.ac | 24 +++++++++++++++++++++++- src/guacenc/Makefile.am | 2 ++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index b30db0a7..a64b9cb1 100644 --- a/configure.ac +++ b/configure.ac @@ -165,6 +165,26 @@ 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"]) + # # libssl # @@ -1008,7 +1028,8 @@ AC_ARG_ENABLE([guacenc], [enable_guacenc=yes]) AM_CONDITIONAL([ENABLE_GUACENC], [test "x${enable_guacenc}" = "xyes" \ - -a "x${have_libavcodec}" = "xyes"]) + -a "x${have_libavcodec}" = "xyes" \ + -a "x${have_libavutil}" = "xyes"]) # # Output Makefiles @@ -1064,6 +1085,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION freerdp ............. ${have_freerdp} pango ............... ${have_pango} libavcodec .......... ${have_libavcodec} + libavutil ........... ${have_libavutil} libssh2 ............. ${have_libssh2} libssl .............. ${have_ssl} libtelnet ........... ${have_libtelnet} diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index e42a8401..3f4ac424 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -76,6 +76,7 @@ endif guacenc_CFLAGS = \ -Werror -Wall -pedantic \ @AVCODEC_CFLAGS@ \ + @AVUTIL_CFLAGS@ \ @LIBGUAC_INCLUDE@ guacenc_LDADD = \ @@ -83,6 +84,7 @@ guacenc_LDADD = \ guacenc_LDFLAGS = \ @AVCODEC_LIBS@ \ + @AVUTIL_LIBS@ \ @CAIRO_LIBS@ \ @JPEG_LIBS@ \ @WEBP_LIBS@ From 9eddaeee3dae1380d661683b2f46cacbedd82e6a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 12:31:39 -0800 Subject: [PATCH 59/92] GUAC-236: Allocate and maintain frame data for encoding. --- src/guacenc/video.c | 49 +++++++++++++++++++++++++++++++++++++++++---- src/guacenc/video.h | 6 ++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/guacenc/video.c b/src/guacenc/video.c index f4fd1fcd..d0915af9 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -26,6 +26,8 @@ #include "video.h" #include +#include +#include #include #include @@ -64,19 +66,35 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, /* Open codec for use */ if (avcodec_open2(context, codec, NULL) < 0) { guacenc_log(GUAC_LOG_ERROR, "Failed to open codec \"%s\".", codec_name); - avcodec_free_context(&context); - return NULL; + goto fail_context; + } + + /* Allocate corresponding frame */ + AVFrame* frame = av_frame_alloc(); + if (frame == NULL) { + goto fail_context; + } + + /* 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; } /* Allocate video structure */ guacenc_video* video = malloc(sizeof(guacenc_video)); if (video == NULL) { - avcodec_free_context(&context); - return NULL; + goto fail_frame_data; } /* Init properties of video */ video->context = context; + video->frame = frame; video->width = width; video->height = height; video->frame_duration = 1000 / framerate; @@ -89,6 +107,17 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, return video; + /* Free all allocated data in case of failure */ +fail_frame_data: + av_freep(&frame->data[0]); + +fail_frame: + av_frame_free(&frame); + +fail_context: + avcodec_free_context(&context); + return NULL; + } /** @@ -109,6 +138,14 @@ static int guacenc_video_flush_frame(guacenc_video* video) { if (buffer == NULL) return 0; + /* Init video packet */ + AVPacket packet; + av_init_packet(&packet); + + /* Request that encoder allocate data for packet */ + packet.data = NULL; + packet.size = 0; + /* STUB: Write frame to video */ guacenc_log(GUAC_LOG_DEBUG, "Writing frame @ %" PRId64 "ms", video->current_time); @@ -160,6 +197,10 @@ int guacenc_video_free(guacenc_video* video) { /* Write final frame */ guacenc_video_flush_frame(video); + /* Free frame encoding data */ + av_freep(&video->frame->data[0]); + av_frame_free(&video->frame); + /* Clean up encoding context */ avcodec_close(video->context); avcodec_free_context(&(video->context)); diff --git a/src/guacenc/video.h b/src/guacenc/video.h index 21787bbc..23879186 100644 --- a/src/guacenc/video.h +++ b/src/guacenc/video.h @@ -42,6 +42,12 @@ typedef struct guacenc_video { */ AVCodecContext* context; + /** + * An image data area, containing YCbCr image data in the format required + * by avcodec_encode_video2(), for use and re-use as frames are rendered. + */ + AVFrame* frame; + /** * The width of the video, in pixels. */ From 38c431e8a2e313976d960a188bb306cb8ecf0e70 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 13:27:36 -0800 Subject: [PATCH 60/92] GUAC-236: Record internal presentation timestamp in same format as libavcodec. --- src/guacenc/video.c | 11 +++++------ src/guacenc/video.h | 15 ++++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/guacenc/video.c b/src/guacenc/video.c index d0915af9..569a7053 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -102,7 +102,7 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, /* No frames have been written or prepared yet */ video->last_timestamp = 0; - video->current_time = 0; + video->next_pts = 0; video->next_frame = NULL; return video; @@ -146,12 +146,11 @@ static int guacenc_video_flush_frame(guacenc_video* video) { packet.data = NULL; packet.size = 0; - /* STUB: Write frame to video */ - guacenc_log(GUAC_LOG_DEBUG, "Writing frame @ %" PRId64 "ms", - video->current_time); + /* TODO: Write frame to video */ + guacenc_log(GUAC_LOG_DEBUG, "Encoding frame #%08" PRId64, video->next_pts); - /* Update internal timestamp */ - video->current_time += video->frame_duration; + /* Update presentation timestamp for next frame */ + video->next_pts++; return 0; diff --git a/src/guacenc/video.h b/src/guacenc/video.h index 23879186..630cfa79 100644 --- a/src/guacenc/video.h +++ b/src/guacenc/video.h @@ -29,6 +29,8 @@ #include #include +#include + /** * 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 @@ -75,19 +77,18 @@ typedef struct guacenc_video { */ guacenc_buffer* 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; - /** - * The relative position within the current video timeline, where 0 is the - * first frame of video, in milliseconds. This value will be incremented as - * frames are output. - */ - guac_timestamp current_time; - } guacenc_video; /** From c37eda37fdc95273f1aaba844306f9ab8272bc3d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 13:35:36 -0800 Subject: [PATCH 61/92] GUAC-236: Define framerate with macro. --- src/guacenc/display.c | 2 +- src/guacenc/video.c | 7 +++---- src/guacenc/video.h | 16 ++++++---------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index 21af2183..6892d24a 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -89,7 +89,7 @@ guacenc_display* guacenc_display_alloc() { /* STUB: Prepare video encoding */ guacenc_video* video = guacenc_video_alloc("/tmp/test.mpg", "mpeg4", - 640, 480, 25, 400000); + 640, 480, 400000); if (video == NULL) return NULL; diff --git a/src/guacenc/video.c b/src/guacenc/video.c index 569a7053..8c7dd45f 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -36,7 +36,7 @@ #include guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, - int width, int height, int framerate, int bitrate) { + int width, int height, int bitrate) { /* Pull codec based on name */ AVCodec* codec = avcodec_find_encoder_by_name(codec_name); @@ -58,7 +58,7 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, context->bit_rate = bitrate; context->width = width; context->height = height; - context->time_base = (AVRational) { 1, framerate }; + context->time_base = (AVRational) { 1, GUACENC_VIDEO_FRAMERATE }; context->gop_size = 10; context->max_b_frames = 1; context->pix_fmt = AV_PIX_FMT_YUV420P; @@ -97,7 +97,6 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, video->frame = frame; video->width = width; video->height = height; - video->frame_duration = 1000 / framerate; video->bitrate = bitrate; /* No frames have been written or prepared yet */ @@ -164,7 +163,7 @@ int guacenc_video_advance_timeline(guacenc_video* video, /* Calculate the number of frames that should have been written */ int elapsed = (timestamp - video->last_timestamp) - / video->frame_duration; + * GUACENC_VIDEO_FRAMERATE / 1000; /* Keep previous timestamp if insufficient time has elapsed */ if (elapsed == 0) diff --git a/src/guacenc/video.h b/src/guacenc/video.h index 630cfa79..398ce410 100644 --- a/src/guacenc/video.h +++ b/src/guacenc/video.h @@ -31,6 +31,11 @@ #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 @@ -60,11 +65,6 @@ typedef struct guacenc_video { */ int height; - /** - * The duration of a single frame in milliseconds. - */ - int frame_duration; - /** * The desired output bitrate of the video, in bits per second. */ @@ -112,16 +112,12 @@ typedef struct guacenc_video { * @param height * The height of the desired video, in pixels. * - * @param framerate - * The rate at which frames should be encoded within the video, in frames - * per second. - * * @param bitrate * The desired overall bitrate of the resulting encoded video, in kilobits * per second. */ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, - int width, int height, int framerate, int bitrate); + int width, int height, int bitrate); /** * Advances the timeline of the encoding process to the given timestamp, such From 1a3e1465faed94d2f34bb6fb890889a4d786de66 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 14:01:16 -0800 Subject: [PATCH 62/92] GUAC-236: Pass encoding parameters from root level of utility. Declare defaults in header. --- src/guacenc/Makefile.am | 1 + src/guacenc/display.c | 6 ++-- src/guacenc/display.h | 20 ++++++++++++- src/guacenc/encode.c | 8 ++++-- src/guacenc/encode.h | 20 ++++++++++++- src/guacenc/guacenc.c | 23 ++++++++++++++- src/guacenc/guacenc.h | 62 +++++++++++++++++++++++++++++++++++++++++ src/guacenc/video.h | 4 +-- 8 files changed, 133 insertions(+), 11 deletions(-) create mode 100644 src/guacenc/guacenc.h diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 3f4ac424..e5588b06 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -28,6 +28,7 @@ noinst_HEADERS = \ buffer.h \ display.h \ encode.h \ + guacenc.h \ image-stream.h \ instructions.h \ jpeg.h \ diff --git a/src/guacenc/display.c b/src/guacenc/display.c index 6892d24a..d4bba518 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -85,11 +85,11 @@ cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask) { } -guacenc_display* guacenc_display_alloc() { +guacenc_display* guacenc_display_alloc(const char* path, const char* codec, + int width, int height, int bitrate) { /* STUB: Prepare video encoding */ - guacenc_video* video = guacenc_video_alloc("/tmp/test.mpg", "mpeg4", - 640, 480, 400000); + guacenc_video* video = guacenc_video_alloc(path, codec, width, height, bitrate); if (video == NULL) return NULL; diff --git a/src/guacenc/display.h b/src/guacenc/display.h index 3ca51397..b8956d76 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -129,11 +129,29 @@ int guacenc_display_flatten(guacenc_display* display); * 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(); +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, diff --git a/src/guacenc/encode.c b/src/guacenc/encode.c index 91cfc0df..680bb31d 100644 --- a/src/guacenc/encode.c +++ b/src/guacenc/encode.c @@ -83,10 +83,12 @@ static int guacenc_read_instructions(guacenc_display* display, } -int guacenc_encode(const char* path) { +int guacenc_encode(const char* path, const char* out_path, const char* codec, + int width, int height, int bitrate) { /* Allocate display for encoding process */ - guacenc_display* display = guacenc_display_alloc(); + guacenc_display* display = guacenc_display_alloc(out_path, codec, + width, height, bitrate); if (display == NULL) return 1; @@ -108,7 +110,7 @@ int guacenc_encode(const char* path) { return 1; } - guacenc_log(GUAC_LOG_INFO, "Encoding \"%s\" ...", path); + 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)) { diff --git a/src/guacenc/encode.h b/src/guacenc/encode.h index 66f12573..ee6be9ab 100644 --- a/src/guacenc/encode.h +++ b/src/guacenc/encode.h @@ -31,11 +31,29 @@ * @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. + * * @return * Zero on success, non-zero if an error prevented successful encoding of * the video. */ -int guacenc_encode(const char* path); +int guacenc_encode(const char* path, const char* out_path, const char* codec, + int width, int height, int bitrate); #endif diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index 79b7d61d..997e7497 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -23,6 +23,7 @@ #include "config.h" #include "encode.h" +#include "guacenc.h" #include "log.h" #include @@ -41,6 +42,15 @@ int main(int argc, char* argv[]) { return 0; } + /* Load defaults */ + const char* codec = GUACENC_DEFAULT_CODEC; + const char* suffix = GUACENC_DEFAULT_SUFFIX; + int width = GUACENC_DEFAULT_WIDTH; + int height = GUACENC_DEFAULT_HEIGHT; + int bitrate = GUACENC_DEFAULT_BITRATE; + + /* TODO: Override defaults via command-line arguments */ + /* Prepare libavcodec */ avcodec_register_all(); @@ -54,8 +64,19 @@ int main(int argc, char* argv[]) { /* Get current filename */ const char* path = argv[i]; + /* Generate output filename */ + char out_path[4096]; + int len = snprintf(out_path, sizeof(out_path), "%s.%s", path, suffix); + + /* Do not write if filename exceeds maximum length */ + if (len >= sizeof(out_path)) { + guacenc_log(GUAC_LOG_ERROR, "Cannot write output file \"%s.%s\": " + "Name too long", path, suffix); + continue; + } + /* Attempt encoding, log granular success/failure at debug level */ - if (guacenc_encode(path)) { + if (guacenc_encode(path, out_path, codec, width, height, bitrate)) { failures++; guacenc_log(GUAC_LOG_DEBUG, "%s was NOT successfully encoded.", path); diff --git a/src/guacenc/guacenc.h b/src/guacenc/guacenc.h new file mode 100644 index 00000000..68da005c --- /dev/null +++ b/src/guacenc/guacenc.h @@ -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. + */ + +#ifndef GUACENC_H +#define GUACENC_H + +#include "config.h" + +/** + * The name of the codec to use by default, if no other codec is specified on + * the command line. This name is dictated by ffmpeg / libavcodec. + */ +#define GUACENC_DEFAULT_CODEC "mpeg4" + +/** + * The extension to append to the end of the input file to produce the output + * file name, excluding the separating period, if no other suffix is specified + * on the command line. + */ +#define GUACENC_DEFAULT_SUFFIX "mpg" + +/** + * 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 400000 + +#endif + diff --git a/src/guacenc/video.h b/src/guacenc/video.h index 398ce410..e72af3e8 100644 --- a/src/guacenc/video.h +++ b/src/guacenc/video.h @@ -113,8 +113,8 @@ typedef struct guacenc_video { * The height of the desired video, in pixels. * * @param bitrate - * The desired overall bitrate of the resulting encoded video, in kilobits - * per second. + * 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); From 1ce39306cb2d66dde8be717a196cce7e9c6c9ee0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 15:23:03 -0800 Subject: [PATCH 63/92] GUAC-236: Do not store buffer - encode directly. Flush frames at end. --- src/guacenc/video.c | 100 +++++++++++++++++++++++++++++++++----------- src/guacenc/video.h | 27 ++++-------- 2 files changed, 82 insertions(+), 45 deletions(-) diff --git a/src/guacenc/video.c b/src/guacenc/video.c index 8c7dd45f..fccc9204 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -94,7 +94,7 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, /* Init properties of video */ video->context = context; - video->frame = frame; + video->next_frame = frame; video->width = width; video->height = height; video->bitrate = bitrate; @@ -102,7 +102,6 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, /* No frames have been written or prepared yet */ video->last_timestamp = 0; video->next_pts = 0; - video->next_frame = NULL; return video; @@ -119,6 +118,63 @@ fail_context: } +/** + * 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) { + guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": wrote %i bytes", + video->next_pts, packet.size); + /* TODO: Write frame to file */ + av_packet_unref(&packet); + } + + /* 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 @@ -132,26 +188,8 @@ fail_context: */ static int guacenc_video_flush_frame(guacenc_video* video) { - /* Ignore empty frames */ - guacenc_buffer* buffer = video->next_frame; - if (buffer == NULL) - return 0; - - /* Init video packet */ - AVPacket packet; - av_init_packet(&packet); - - /* Request that encoder allocate data for packet */ - packet.data = NULL; - packet.size = 0; - - /* TODO: Write frame to video */ - guacenc_log(GUAC_LOG_DEBUG, "Encoding frame #%08" PRId64, video->next_pts); - - /* Update presentation timestamp for next frame */ - video->next_pts++; - - return 0; + /* Write frame to video */ + return guacenc_video_write_frame(video, video->next_frame) < 0; } @@ -183,7 +221,9 @@ int guacenc_video_advance_timeline(guacenc_video* video, } void guacenc_video_prepare_frame(guacenc_video* video, guacenc_buffer* buffer) { - video->next_frame = buffer; + + /* TODO: Convert frame buffer to video->next_frame */ + } int guacenc_video_free(guacenc_video* video) { @@ -195,9 +235,19 @@ int guacenc_video_free(guacenc_video* video) { /* 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); + /* Free frame encoding data */ - av_freep(&video->frame->data[0]); - av_frame_free(&video->frame); + av_freep(&video->next_frame->data[0]); + av_frame_free(&video->next_frame); /* Clean up encoding context */ avcodec_close(video->context); diff --git a/src/guacenc/video.h b/src/guacenc/video.h index e72af3e8..0427990e 100644 --- a/src/guacenc/video.h +++ b/src/guacenc/video.h @@ -49,12 +49,6 @@ typedef struct guacenc_video { */ AVCodecContext* context; - /** - * An image data area, containing YCbCr image data in the format required - * by avcodec_encode_video2(), for use and re-use as frames are rendered. - */ - AVFrame* frame; - /** * The width of the video, in pixels. */ @@ -71,11 +65,11 @@ typedef struct guacenc_video { int bitrate; /** - * A pointer to the buffer containing the most recent frame submitted via - * guacenc_video_prepare_frame(). This buffer MUST not be freed prior to - * the call to guacenc_video_free(). + * 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. */ - guacenc_buffer* next_frame; + AVFrame* next_frame; /** * The presentation timestamp that should be used for the next frame. This @@ -95,9 +89,7 @@ typedef struct guacenc_video { * Allocates a new guacenc_video which encodes video according to the given * specifications, saving the output in the given file. The output file will be * created if necessary and truncated if it already exists. Frames will be - * scaled up or down as necessary to fit the given width and height. Note that - * frames written to this guacenc_video may be buffered, and are not guaranteed - * to be written until guacenc_video_free() is called. + * 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. @@ -157,25 +149,20 @@ int guacenc_video_advance_timeline(guacenc_video* video, * timeline or through reaching the end of the encoding process * (guacenc_video_free()). * - * Any given buffer MUST NOT be freed prior to the call to guacenc_video_free() - * which ultimately ends the encoding process. - * * @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. This buffer MUST NOT be freed prior to the call to - * guacenc_video_free() which ultimately ends the encoding process. + * 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. Once this function is invoked, it is safe to resume freeing - * any buffers provided to guacenc_video_prepare_frame(). + * at this point. * * @return * Zero if the video was successfully written and freed, non-zero if the From f9f08627ffbc05f7de004550ef49612880337b06 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 16:07:31 -0800 Subject: [PATCH 64/92] GUAC-286: Actually write video output to file. --- src/guacenc/display.c | 2 +- src/guacenc/video.c | 53 +++++++++++++++++++++++++++++++++++-------- src/guacenc/video.h | 6 +++++ 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/guacenc/display.c b/src/guacenc/display.c index d4bba518..672bb261 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -88,7 +88,7 @@ cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask) { guacenc_display* guacenc_display_alloc(const char* path, const char* codec, int width, int height, int bitrate) { - /* STUB: Prepare video encoding */ + /* Prepare video encoding */ guacenc_video* video = guacenc_video_alloc(path, codec, width, height, bitrate); if (video == NULL) return NULL; diff --git a/src/guacenc/video.c b/src/guacenc/video.c index fccc9204..7f0e7ae0 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -43,7 +43,7 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, if (codec == NULL) { guacenc_log(GUAC_LOG_ERROR, "Failed to locate codec \"%s\".", codec_name); - return NULL; + goto fail_codec; } /* Retrieve encoding context */ @@ -51,7 +51,7 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, if (context == NULL) { guacenc_log(GUAC_LOG_ERROR, "Failed to allocate context for " "codec \"%s\".", codec_name); - return NULL; + goto fail_context; } /* Init context with encoding parameters */ @@ -66,13 +66,13 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, /* 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_context; + goto fail_codec_open; } /* Allocate corresponding frame */ AVFrame* frame = av_frame_alloc(); if (frame == NULL) { - goto fail_context; + goto fail_frame; } /* Copy necessary data for frame from context */ @@ -83,16 +83,25 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, /* 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; + goto fail_frame_data; + } + + /* Open output file */ + FILE* output = fopen(path, "wb"); + if (output == NULL) { + guacenc_log(GUAC_LOG_ERROR, "Failed to open 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_frame_data; + goto fail_video; } /* Init properties of video */ + video->output = output; video->context = context; video->next_frame = frame; video->width = width; @@ -106,14 +115,21 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, return video; /* Free all allocated data in case of failure */ -fail_frame_data: +fail_video: + fclose(output); + +fail_output_file: av_freep(&frame->data[0]); -fail_frame: +fail_frame_data: av_frame_free(&frame); -fail_context: +fail_frame: +fail_codec_open: avcodec_free_context(&context); + +fail_context: +fail_codec: return NULL; } @@ -161,12 +177,26 @@ static int guacenc_video_write_frame(guacenc_video* video, AVFrame* frame) { /* 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); - /* TODO: Write frame to file */ 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++; @@ -245,6 +275,9 @@ int guacenc_video_free(guacenc_video* video) { 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); diff --git a/src/guacenc/video.h b/src/guacenc/video.h index 0427990e..b9cbf139 100644 --- a/src/guacenc/video.h +++ b/src/guacenc/video.h @@ -30,6 +30,7 @@ #include #include +#include /** * The framerate at which video should be encoded, in frames per second. @@ -43,6 +44,11 @@ */ 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. From 96b3dd4e7c38f8e3807ce99bc5e54fcef639af30 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 17:42:49 -0800 Subject: [PATCH 65/92] GUAC-236: Add libswscale to build. --- configure.ac | 24 ++++++++++++++++++++++-- src/guacenc/Makefile.am | 4 +++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index a64b9cb1..a818fee6 100644 --- a/configure.ac +++ b/configure.ac @@ -165,7 +165,6 @@ fi AM_CONDITIONAL([ENABLE_AVCODEC], [test "x${have_libavcodec}" = "xyes"]) - # # libavutil # @@ -185,6 +184,25 @@ 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 # @@ -1029,7 +1047,8 @@ AC_ARG_ENABLE([guacenc], AM_CONDITIONAL([ENABLE_GUACENC], [test "x${enable_guacenc}" = "xyes" \ -a "x${have_libavcodec}" = "xyes" \ - -a "x${have_libavutil}" = "xyes"]) + -a "x${have_libavutil}" = "xyes" \ + -a "x${have_libswscale}" = "xyes"]) # # Output Makefiles @@ -1088,6 +1107,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION libavutil ........... ${have_libavutil} libssh2 ............. ${have_libssh2} libssl .............. ${have_ssl} + libswscale .......... ${have_libswscale} libtelnet ........... ${have_libtelnet} libVNCServer ........ ${have_libvncserver} libvorbis ........... ${have_vorbis} diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index e5588b06..9a9015a5 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -78,7 +78,8 @@ guacenc_CFLAGS = \ -Werror -Wall -pedantic \ @AVCODEC_CFLAGS@ \ @AVUTIL_CFLAGS@ \ - @LIBGUAC_INCLUDE@ + @LIBGUAC_INCLUDE@ \ + @SWSCALE_CFLAGS@ guacenc_LDADD = \ @LIBGUAC_LTLIB@ @@ -88,5 +89,6 @@ guacenc_LDFLAGS = \ @AVUTIL_LIBS@ \ @CAIRO_LIBS@ \ @JPEG_LIBS@ \ + @SWSCALE_LIBS@ \ @WEBP_LIBS@ From 0f467a5d51201a843d24f92660850dae16b72a9e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 17:50:00 -0800 Subject: [PATCH 66/92] GUAC-236: Convert each Guacamole frame to a proper AVFrame. --- src/guacenc/video.c | 106 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/src/guacenc/video.c b/src/guacenc/video.c index 7f0e7ae0..69db9522 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -25,12 +25,15 @@ #include "log.h" #include "video.h" +#include #include #include #include +#include #include #include +#include #include #include #include @@ -250,9 +253,110 @@ int guacenc_video_advance_timeline(guacenc_video* video, } +/** + * Converts the given Guacamole video encoder buffer to a frame in the format + * required by libavcodec / libswscale. No scaling is performed; the image data + * is copied verbatim. + * + * @param buffer + * The guacenc_buffer to copy as a new AVFrame. + * + * @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) { + + /* 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; + frame->height = buffer->height; + + /* 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 and destination frame dimensions are identical */ + assert(width == frame->width); + assert(height == frame->height); + + /* Copy all data from source buffer to destination frame */ + while (height > 0) { + memcpy(dst_data, src_data, width * 4); + dst_data += dst_stride; + src_data += src_stride; + height--; + } + + /* Frame converted */ + return frame; + +} + void guacenc_video_prepare_frame(guacenc_video* video, guacenc_buffer* buffer) { - /* TODO: Convert frame buffer to video->next_frame */ + /* Ignore NULL buffers */ + if (buffer == NULL || buffer->surface == NULL) + return; + + /* Prepare source frame for buffer */ + AVFrame* src = guacenc_video_frame_convert(buffer); + if (src == NULL) { + guacenc_log(GUAC_LOG_WARNING, "Failed to allocate source frame. " + "Frame dropped."); + return; + } + + /* Obtain destination frame */ + AVFrame* dst = video->next_frame; + + /* 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; + } + + 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); } From cb1d5e03b53f15f83421edbc5283468e9950ccc8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 17:53:35 -0800 Subject: [PATCH 67/92] GUAC-236: Increase default bitrate. --- src/guacenc/guacenc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/guacenc/guacenc.h b/src/guacenc/guacenc.h index 68da005c..15f01553 100644 --- a/src/guacenc/guacenc.h +++ b/src/guacenc/guacenc.h @@ -56,7 +56,7 @@ * 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 400000 +#define GUACENC_DEFAULT_BITRATE 2000000 #endif From 7575506443d963e5e0befacbb3283c28162f5b24 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 18:22:31 -0800 Subject: [PATCH 68/92] GUAC-236: Add missing comment for use of sws_scale(). --- src/guacenc/video.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/guacenc/video.c b/src/guacenc/video.c index 69db9522..7c6c0c2e 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -348,6 +348,7 @@ void guacenc_video_prepare_frame(guacenc_video* video, guacenc_buffer* buffer) { 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); From 59844d8e59d7d9d6b6629e6e1a951c60e0c37d6a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 19:22:20 -0800 Subject: [PATCH 69/92] GUAC-236: Reset log level back to INFO. Define default elsewhere. --- src/guacenc/guacenc.h | 7 +++++++ src/guacenc/log.c | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/guacenc/guacenc.h b/src/guacenc/guacenc.h index 15f01553..a462f910 100644 --- a/src/guacenc/guacenc.h +++ b/src/guacenc/guacenc.h @@ -25,6 +25,8 @@ #include "config.h" +#include + /** * The name of the codec to use by default, if no other codec is specified on * the command line. This name is dictated by ffmpeg / libavcodec. @@ -58,5 +60,10 @@ */ #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/log.c b/src/guacenc/log.c index ba81314f..221c743c 100644 --- a/src/guacenc/log.c +++ b/src/guacenc/log.c @@ -21,6 +21,7 @@ */ #include "config.h" +#include "guacenc.h" #include "log.h" #include @@ -29,7 +30,7 @@ #include #include -int guacenc_log_level = GUAC_LOG_DEBUG; +int guacenc_log_level = GUACENC_DEFAULT_LOG_LEVEL; void vguacenc_log(guac_client_log_level level, const char* format, va_list args) { From 3d67598ec94a17ed9cefbcf7e163670c1d1b84de Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 22:12:25 -0800 Subject: [PATCH 70/92] GUAC-236: Parse command-line arguments. --- src/guacenc/Makefile.am | 2 ++ src/guacenc/guacenc.c | 80 ++++++++++++++++++++++++++++++++++------- src/guacenc/parse.c | 72 +++++++++++++++++++++++++++++++++++++ src/guacenc/parse.h | 71 ++++++++++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+), 13 deletions(-) create mode 100644 src/guacenc/parse.c create mode 100644 src/guacenc/parse.h diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 9a9015a5..e22c86fb 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -34,6 +34,7 @@ noinst_HEADERS = \ jpeg.h \ layer.h \ log.h \ + parse.h \ png.h \ video.h @@ -65,6 +66,7 @@ guacenc_SOURCES = \ jpeg.c \ layer.c \ log.c \ + parse.c \ png.c \ video.c diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index 997e7497..1286bbab 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -25,23 +25,17 @@ #include "encode.h" #include "guacenc.h" #include "log.h" +#include "parse.h" #include +#include +#include + int main(int argc, char* argv[]) { int i; - /* Log start */ - guacenc_log(GUAC_LOG_INFO, "Guacamole video encoder (guacenc) " - "version " VERSION); - - /* Abort if no files given */ - if (argc <= 1) { - guacenc_log(GUAC_LOG_INFO, "No input files specified. Nothing to do."); - return 0; - } - /* Load defaults */ const char* codec = GUACENC_DEFAULT_CODEC; const char* suffix = GUACENC_DEFAULT_SUFFIX; @@ -49,17 +43,65 @@ int main(int argc, char* argv[]) { int height = GUACENC_DEFAULT_HEIGHT; int bitrate = GUACENC_DEFAULT_BITRATE; - /* TODO: Override defaults via command-line arguments */ + /* Parse arguments */ + int opt; + while ((opt = getopt(argc, argv, "V:s:d:r:")) != -1) { + + /* -V: Video codec */ + if (opt == 'V') + codec = optarg; + + /* -s: Output file suffix */ + else if (opt == 's') + suffix = optarg; + + /* -d: Dimensions */ + else if (opt == 'd') { + 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; + } + } + + /* Invalid option */ + else { + goto invalid_options; + } + + } + + /* Log start */ + guacenc_log(GUAC_LOG_INFO, "Guacamole video encoder (guacenc) " + "version " VERSION); + + guacenc_log(GUAC_LOG_INFO, "Video will be encoded as \"%s\" at %ix%i " + "and %i bps.", codec, width, height, bitrate); + + guacenc_log(GUAC_LOG_INFO, "Output files will end with \".%s\".", suffix); /* Prepare libavcodec */ avcodec_register_all(); /* Track number of overall failures */ - int total_files = argc - 1; + 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; + } + /* Encode all input files */ - for (i = 1; i < argc; i++) { + for (i = optind; i < argc; i++) { /* Get current filename */ const char* path = argv[i]; @@ -98,5 +140,17 @@ int main(int argc, char* argv[]) { /* Encoding complete */ return 0; + /* Display usage and exit with error if options are invalid */ +invalid_options: + + fprintf(stderr, "USAGE: %s" + " [-d WIDTHxHEIGHT]" + " [-s SUFFIX]" + " [-V CODEC]" + " [-b BITRATE]" + " [-f]\n", argv[0]); + + return 1; + } 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 + + From c4f7bae10b335409999cc2e6013ae238327906e1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 22:15:02 -0800 Subject: [PATCH 71/92] GUAC-236: Do not create output file until after everything has been validated. --- src/guacenc/encode.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/guacenc/encode.c b/src/guacenc/encode.c index 680bb31d..d7fe8eba 100644 --- a/src/guacenc/encode.c +++ b/src/guacenc/encode.c @@ -86,17 +86,18 @@ static int guacenc_read_instructions(guacenc_display* display, int guacenc_encode(const char* path, const char* out_path, const char* codec, int width, int height, int bitrate) { - /* Allocate display for encoding process */ - guacenc_display* display = guacenc_display_alloc(out_path, codec, - width, height, bitrate); - if (display == NULL) - return 1; - /* Open input file */ int fd = open(path, O_RDONLY); if (fd < 0) { guacenc_log(GUAC_LOG_ERROR, "%s: %s", path, strerror(errno)); - guacenc_display_free(display); + 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; } From 2f93e6ce6782f134c12d28815b76949ce9091d17 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 22:19:27 -0800 Subject: [PATCH 72/92] GUAC-236: Output sensible messages at sensible times. --- src/guacenc/guacenc.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index 1286bbab..8de7f007 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -82,11 +82,6 @@ int main(int argc, char* argv[]) { guacenc_log(GUAC_LOG_INFO, "Guacamole video encoder (guacenc) " "version " VERSION); - guacenc_log(GUAC_LOG_INFO, "Video will be encoded as \"%s\" at %ix%i " - "and %i bps.", codec, width, height, bitrate); - - guacenc_log(GUAC_LOG_INFO, "Output files will end with \".%s\".", suffix); - /* Prepare libavcodec */ avcodec_register_all(); @@ -100,6 +95,14 @@ int main(int argc, char* argv[]) { return 0; } + guacenc_log(GUAC_LOG_INFO, "%i input file(s) provided.", total_files); + + guacenc_log(GUAC_LOG_INFO, "Video will be encoded as \"%s\" at %ix%i " + "and %i bps.", codec, width, height, bitrate); + + guacenc_log(GUAC_LOG_INFO, "Output files will end with \".%s\".", suffix); + + /* Encode all input files */ for (i = optind; i < argc; i++) { From ecf3a0302a38ef109e76617d4b181446080e46f3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 22:21:34 -0800 Subject: [PATCH 73/92] GUAC-236: There actually isn't a -f option. --- src/guacenc/guacenc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index 8de7f007..cc0acc8a 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -150,8 +150,7 @@ invalid_options: " [-d WIDTHxHEIGHT]" " [-s SUFFIX]" " [-V CODEC]" - " [-b BITRATE]" - " [-f]\n", argv[0]); + " [-b BITRATE]\n", argv[0]); return 1; From 710edc03ff2355ffb380d0e538cd99f4969f6e73 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 12 Mar 2016 00:32:13 -0800 Subject: [PATCH 74/92] GUAC-236: Simplify command-line options, restricting to known-good combination. Can add other formats as possible later. --- src/guacenc/guacenc.c | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index cc0acc8a..0fffa0e3 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -37,26 +37,16 @@ int main(int argc, char* argv[]) { int i; /* Load defaults */ - const char* codec = GUACENC_DEFAULT_CODEC; - const char* suffix = GUACENC_DEFAULT_SUFFIX; int width = GUACENC_DEFAULT_WIDTH; int height = GUACENC_DEFAULT_HEIGHT; int bitrate = GUACENC_DEFAULT_BITRATE; /* Parse arguments */ int opt; - while ((opt = getopt(argc, argv, "V:s:d:r:")) != -1) { - - /* -V: Video codec */ - if (opt == 'V') - codec = optarg; - - /* -s: Output file suffix */ - else if (opt == 's') - suffix = optarg; + while ((opt = getopt(argc, argv, "s:r:")) != -1) { /* -d: Dimensions */ - else if (opt == 'd') { + if (opt == 's') { if (guacenc_parse_dimensions(optarg, &width, &height)) { guacenc_log(GUAC_LOG_ERROR, "Invalid dimensions."); goto invalid_options; @@ -97,11 +87,8 @@ int main(int argc, char* argv[]) { guacenc_log(GUAC_LOG_INFO, "%i input file(s) provided.", total_files); - guacenc_log(GUAC_LOG_INFO, "Video will be encoded as \"%s\" at %ix%i " - "and %i bps.", codec, width, height, bitrate); - - guacenc_log(GUAC_LOG_INFO, "Output files will end with \".%s\".", suffix); - + 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++) { @@ -111,17 +98,17 @@ int main(int argc, char* argv[]) { /* Generate output filename */ char out_path[4096]; - int len = snprintf(out_path, sizeof(out_path), "%s.%s", path, suffix); + int len = snprintf(out_path, sizeof(out_path), "%s.mpg", path); /* Do not write if filename exceeds maximum length */ if (len >= sizeof(out_path)) { - guacenc_log(GUAC_LOG_ERROR, "Cannot write output file \"%s.%s\": " - "Name too long", path, suffix); + 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, codec, width, height, bitrate)) { + if (guacenc_encode(path, out_path, "mpeg4", width, height, bitrate)) { failures++; guacenc_log(GUAC_LOG_DEBUG, "%s was NOT successfully encoded.", path); @@ -147,10 +134,8 @@ int main(int argc, char* argv[]) { invalid_options: fprintf(stderr, "USAGE: %s" - " [-d WIDTHxHEIGHT]" - " [-s SUFFIX]" - " [-V CODEC]" - " [-b BITRATE]\n", argv[0]); + " [-s WIDTHxHEIGHT]" + " [-r BITRATE]\n", argv[0]); return 1; From e74ea54eb97ef12f8b87078ef71a23cee928e064 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 12 Mar 2016 00:44:04 -0800 Subject: [PATCH 75/92] GUAC-236: Add FILE to usage descriptions. --- src/guacenc/guacenc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index 0fffa0e3..b1b63de6 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -135,7 +135,8 @@ invalid_options: fprintf(stderr, "USAGE: %s" " [-s WIDTHxHEIGHT]" - " [-r BITRATE]\n", argv[0]); + " [-r BITRATE]" + " [FILE]...\n", argv[0]); return 1; From e2989759187d1cb7a82936c3f078b02004d204fe Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 12 Mar 2016 17:56:31 -0800 Subject: [PATCH 76/92] GUAC-236: Document usage. Use correct extension (.m4v). --- src/guacenc/Makefile.am | 3 +++ src/guacenc/guacenc.c | 2 +- src/guacenc/man/guacenc.1 | 44 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/guacenc/man/guacenc.1 diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index e22c86fb..bdc760d3 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -24,6 +24,9 @@ AUTOMAKE_OPTIONS = foreign bin_PROGRAMS = guacenc +man_MANS = \ + man/guacenc.1 + noinst_HEADERS = \ buffer.h \ display.h \ diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index b1b63de6..1c825bb5 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -98,7 +98,7 @@ int main(int argc, char* argv[]) { /* Generate output filename */ char out_path[4096]; - int len = snprintf(out_path, sizeof(out_path), "%s.mpg", path); + int len = snprintf(out_path, sizeof(out_path), "%s.m4v", path); /* Do not write if filename exceeds maximum length */ if (len >= sizeof(out_path)) { diff --git a/src/guacenc/man/guacenc.1 b/src/guacenc/man/guacenc.1 new file mode 100644 index 00000000..adc76dd5 --- /dev/null +++ b/src/guacenc/man/guacenc.1 @@ -0,0 +1,44 @@ +.TH guacenc 8 "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] +[\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. +. +.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. +. +.SH AUTHOR +Written by Michael Jumper From c16832f11a28421277406bb7e012a485db2d5b43 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 12 Mar 2016 18:05:24 -0800 Subject: [PATCH 77/92] GUAC-236: Fix manual section number. --- src/guacenc/man/guacenc.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/guacenc/man/guacenc.1 b/src/guacenc/man/guacenc.1 index adc76dd5..3f6f1799 100644 --- a/src/guacenc/man/guacenc.1 +++ b/src/guacenc/man/guacenc.1 @@ -1,4 +1,4 @@ -.TH guacenc 8 "12 Mar 2016" "version 0.9.9" "Guacamole" +.TH guacenc 1 "12 Mar 2016" "version 0.9.9" "Guacamole" . .SH NAME guacenc \- Guacamole video encoder From be0a9e728f5f06aa8b4277e13e053140627e0b54 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 12 Mar 2016 19:55:17 -0800 Subject: [PATCH 78/92] GUAC-236: Implement JPEG. --- src/guacenc/jpeg.c | 57 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/src/guacenc/jpeg.c b/src/guacenc/jpeg.c index 706e21c7..dfed2d13 100644 --- a/src/guacenc/jpeg.c +++ b/src/guacenc/jpeg.c @@ -22,13 +22,66 @@ #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) { - /* STUB */ - return NULL; + + 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; + } From 2798536a7afe09deac5d92f917cb6391001b6b59 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 12 Mar 2016 19:56:30 -0800 Subject: [PATCH 79/92] GUAC-236: Document transfer as currently unimplemented (rarely used). --- src/guacenc/instruction-transfer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/guacenc/instruction-transfer.c b/src/guacenc/instruction-transfer.c index 3643404f..c2508f62 100644 --- a/src/guacenc/instruction-transfer.c +++ b/src/guacenc/instruction-transfer.c @@ -47,10 +47,11 @@ int guacenc_handle_transfer(guacenc_display* display, int argc, char** argv) { int dst_x = atoi(argv[7]); int dst_y = atoi(argv[8]); - /* STUB */ + /* 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; } From 8ed0cd5f16fdeec3c9d841801ad04c3b45aa7c0d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 12 Mar 2016 21:46:45 -0800 Subject: [PATCH 80/92] GUAC-236: Maintain aspect ratio by adding letterboxes / pillarboxes as necessary. --- src/guacenc/video.c | 101 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 88 insertions(+), 13 deletions(-) diff --git a/src/guacenc/video.c b/src/guacenc/video.c index 7c6c0c2e..fbf02e03 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -255,18 +255,39 @@ int guacenc_video_advance_timeline(guacenc_video* video, /** * Converts the given Guacamole video encoder buffer to a frame in the format - * required by libavcodec / libswscale. No scaling is performed; the image data - * is copied verbatim. + * 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) { +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(); @@ -275,8 +296,8 @@ static AVFrame* guacenc_video_frame_convert(guacenc_buffer* buffer) { /* Copy buffer properties to frame */ frame->format = AV_PIX_FMT_RGB32; - frame->width = buffer->width; - frame->height = buffer->height; + 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, @@ -300,16 +321,46 @@ static AVFrame* guacenc_video_frame_convert(guacenc_buffer* buffer) { int width = buffer->width; int height = buffer->height; - /* Source buffer and destination frame dimensions are identical */ - assert(width == frame->width); - assert(height == frame->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) { - memcpy(dst_data, src_data, width * 4); + + /* 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 */ @@ -319,21 +370,45 @@ static AVFrame* guacenc_video_frame_convert(guacenc_buffer* buffer) { 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); + 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; } - /* Obtain destination frame */ - AVFrame* dst = video->next_frame; - /* Prepare scaling context */ struct SwsContext* sws = sws_getContext(src->width, src->height, PIX_FMT_RGB32, dst->width, dst->height, PIX_FMT_YUV420P, From 812f8b8cbf0d42f1c4f943480b43327115c93ee4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 13 Mar 2016 23:52:38 -0700 Subject: [PATCH 81/92] GUAC-236: Include guacenc.1 in EXTRA_DIST. --- src/guacenc/Makefile.am | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index bdc760d3..72199116 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -97,3 +97,6 @@ guacenc_LDFLAGS = \ @SWSCALE_LIBS@ \ @WEBP_LIBS@ +EXTRA_DIST = \ + man/guacenc.1 + From 770fec0d88b26800b3310e3f0b350600735692da Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 13 Mar 2016 23:59:09 -0700 Subject: [PATCH 82/92] GUAC-236: Define av_frame_free() / av_frame_alloc() if not present. --- src/guacenc/video.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/guacenc/video.c b/src/guacenc/video.c index fbf02e03..a99cd4ee 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -38,6 +38,12 @@ #include #include +/* Define av_frame_alloc() / av_frame_free() if libavcodec is old */ +#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 + guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, int width, int height, int bitrate) { From 570bcc3822dc5ee07d0fb0260b36ad32240720ce Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 14 Mar 2016 01:33:27 -0700 Subject: [PATCH 83/92] GUAC-236: Add generalized ffmpeg-compat.h header to cover various API changes. --- src/guacenc/Makefile.am | 25 ++++++++--------- src/guacenc/ffmpeg-compat.h | 53 +++++++++++++++++++++++++++++++++++++ src/guacenc/video.c | 7 +---- 3 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 src/guacenc/ffmpeg-compat.h diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 72199116..12309920 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -27,18 +27,19 @@ bin_PROGRAMS = guacenc man_MANS = \ man/guacenc.1 -noinst_HEADERS = \ - buffer.h \ - display.h \ - encode.h \ - guacenc.h \ - image-stream.h \ - instructions.h \ - jpeg.h \ - layer.h \ - log.h \ - parse.h \ - png.h \ +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 = \ 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/video.c b/src/guacenc/video.c index a99cd4ee..99d990e8 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -22,6 +22,7 @@ #include "config.h" #include "buffer.h" +#include "ffmpeg-compat.h" #include "log.h" #include "video.h" @@ -38,12 +39,6 @@ #include #include -/* Define av_frame_alloc() / av_frame_free() if libavcodec is old */ -#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 - guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, int width, int height, int bitrate) { From e3d1af1953c238c6add5e44db317607ea5611df4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 14 Mar 2016 20:26:31 -0700 Subject: [PATCH 84/92] GUAC-236: Add screen recording support to telnet. --- src/protocols/telnet/settings.c | 40 +++++++++++++++++++++++++++++++++ src/protocols/telnet/settings.h | 22 ++++++++++++++++++ src/protocols/telnet/telnet.c | 9 ++++++++ 3 files changed, 71 insertions(+) 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, From acf2d2b0044980ad2f5a3d29053351c73df281e7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 15 Mar 2016 12:26:13 -0700 Subject: [PATCH 85/92] GUAC-236: Correct typos in comments. --- src/guacenc/guacenc.c | 2 +- src/guacenc/image-stream.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index 1c825bb5..c5525056 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -45,7 +45,7 @@ int main(int argc, char* argv[]) { int opt; while ((opt = getopt(argc, argv, "s:r:")) != -1) { - /* -d: Dimensions */ + /* -s: Dimensions (WIDTHxHEIGHT) */ if (opt == 's') { if (guacenc_parse_dimensions(optarg, &width, &height)) { guacenc_log(GUAC_LOG_ERROR, "Invalid dimensions."); diff --git a/src/guacenc/image-stream.h b/src/guacenc/image-stream.h index 26660eba..ff36ee1f 100644 --- a/src/guacenc/image-stream.h +++ b/src/guacenc/image-stream.h @@ -30,7 +30,7 @@ /** * The initial number of bytes to allocate for the image data buffer. If this - * buffer is not sufficiently larged, it will be dynamically reallocated as it + * buffer is not sufficiently large, it will be dynamically reallocated as it * grows. */ #define GUACENC_IMAGE_STREAM_INITIAL_LENGTH 4096 From 73bf5ce6f4ea16c0030b02d9801d9046a13a4058 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 15 Mar 2016 12:26:35 -0700 Subject: [PATCH 86/92] GUAC-236: Remove unused macros. --- src/guacenc/guacenc.h | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/guacenc/guacenc.h b/src/guacenc/guacenc.h index a462f910..e269cfdc 100644 --- a/src/guacenc/guacenc.h +++ b/src/guacenc/guacenc.h @@ -27,19 +27,6 @@ #include -/** - * The name of the codec to use by default, if no other codec is specified on - * the command line. This name is dictated by ffmpeg / libavcodec. - */ -#define GUACENC_DEFAULT_CODEC "mpeg4" - -/** - * The extension to append to the end of the input file to produce the output - * file name, excluding the separating period, if no other suffix is specified - * on the command line. - */ -#define GUACENC_DEFAULT_SUFFIX "mpg" - /** * 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 From c50561ef10af1d40e7aa3bf1388ba79c2904567d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 15 Mar 2016 16:41:47 -0700 Subject: [PATCH 87/92] GUAC-236: Open output stream using file descriptor. Only write output file if it does not yet exist. --- src/guacenc/video.c | 19 +++++++++++++++++-- src/guacenc/video.h | 7 ++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/guacenc/video.c b/src/guacenc/video.c index 99d990e8..8c138442 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -34,10 +34,14 @@ #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) { @@ -91,10 +95,18 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, } /* Open output file */ - FILE* output = fopen(path, "wb"); - if (output == NULL) { + 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; } @@ -123,6 +135,9 @@ fail_video: fclose(output); fail_output_file: + close(fd); + +fail_output_fd: av_freep(&frame->data[0]); fail_frame_data: diff --git a/src/guacenc/video.h b/src/guacenc/video.h index b9cbf139..61c7ff8f 100644 --- a/src/guacenc/video.h +++ b/src/guacenc/video.h @@ -93,9 +93,10 @@ typedef struct guacenc_video { /** * Allocates a new guacenc_video which encodes video according to the given - * specifications, saving the output in the given file. The output file will be - * created if necessary and truncated if it already exists. Frames will be - * scaled up or down as necessary to fit the given width and height. + * 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. From 0361dd23921f36b961a8d01b0e033463efc1cdb3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 15 Mar 2016 17:06:52 -0700 Subject: [PATCH 88/92] GUAC-236: Acquire write lock on output file for in-progress screen recordings. --- src/common/guac_recording.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/common/guac_recording.c b/src/common/guac_recording.c index 1b8d513a..461b80e8 100644 --- a/src/common/guac_recording.c +++ b/src/common/guac_recording.c @@ -25,13 +25,14 @@ #include #include +#include +#include #include #include #include #include #include -#include -#include +#include /** * Attempts to open a new recording within the given path and having the given @@ -105,6 +106,21 @@ static int guac_common_recording_open(const char* path, } /* 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; } From 9d43e2259281dd8525e8339b6cec5d34bfaf0bcb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 15 Mar 2016 17:08:39 -0700 Subject: [PATCH 89/92] GUAC-236: Acquire read lock on input files for guacenc. Refuse to encode in-progress recordings. --- src/guacenc/encode.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/guacenc/encode.c b/src/guacenc/encode.c index d7fe8eba..6e5fd022 100644 --- a/src/guacenc/encode.c +++ b/src/guacenc/encode.c @@ -30,10 +30,10 @@ #include #include -#include -#include #include #include +#include +#include #include #include @@ -93,6 +93,32 @@ int guacenc_encode(const char* path, const char* out_path, const char* codec, 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 (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\".", 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); From 32779ee15ff4cc87b08074ffa3200458646e09ed Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 15 Mar 2016 17:24:25 -0700 Subject: [PATCH 90/92] GUAC-236: Provide -f option for overriding locking behavior. --- src/guacenc/encode.c | 8 +++++--- src/guacenc/encode.h | 13 +++++++++++-- src/guacenc/guacenc.c | 12 ++++++++++-- src/guacenc/man/guacenc.1 | 20 +++++++++++++++++++- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/guacenc/encode.c b/src/guacenc/encode.c index 6e5fd022..115becba 100644 --- a/src/guacenc/encode.c +++ b/src/guacenc/encode.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -84,7 +85,7 @@ static int guacenc_read_instructions(guacenc_display* display, } int guacenc_encode(const char* path, const char* out_path, const char* codec, - int width, int height, int bitrate) { + int width, int height, int bitrate, bool force) { /* Open input file */ int fd = open(path, O_RDONLY); @@ -103,12 +104,13 @@ int guacenc_encode(const char* path, const char* out_path, const char* codec, }; /* Abort if file cannot be locked for reading */ - if (fcntl(fd, F_SETLK, &file_lock) == -1) { + 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\".", path); + "recording \"%s\" (specify the -f option to override " + "this behavior).", path); /* Log an error if locking fails in an unexpected way */ else diff --git a/src/guacenc/encode.h b/src/guacenc/encode.h index ee6be9ab..b80eeb00 100644 --- a/src/guacenc/encode.h +++ b/src/guacenc/encode.h @@ -25,8 +25,13 @@ #include "config.h" +#include + /** - * Encodes the given Guacamole protocol dump as video. + * 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. @@ -48,12 +53,16 @@ * 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); + int width, int height, int bitrate, bool force); #endif diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index c5525056..f67b2084 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -30,6 +30,7 @@ #include #include +#include #include int main(int argc, char* argv[]) { @@ -37,13 +38,14 @@ 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:")) != -1) { + while ((opt = getopt(argc, argv, "s:r:f")) != -1) { /* -s: Dimensions (WIDTHxHEIGHT) */ if (opt == 's') { @@ -61,6 +63,10 @@ int main(int argc, char* argv[]) { } } + /* -f: Force */ + else if (opt == 'f') + force = true; + /* Invalid option */ else { goto invalid_options; @@ -108,7 +114,8 @@ int main(int argc, char* argv[]) { } /* Attempt encoding, log granular success/failure at debug level */ - if (guacenc_encode(path, out_path, "mpeg4", width, height, bitrate)) { + if (guacenc_encode(path, out_path, "mpeg4", + width, height, bitrate, force)) { failures++; guacenc_log(GUAC_LOG_DEBUG, "%s was NOT successfully encoded.", path); @@ -136,6 +143,7 @@ invalid_options: fprintf(stderr, "USAGE: %s" " [-s WIDTHxHEIGHT]" " [-r BITRATE]" + " [-f]" " [FILE]...\n", argv[0]); return 1; diff --git a/src/guacenc/man/guacenc.1 b/src/guacenc/man/guacenc.1 index 3f6f1799..bbef7189 100644 --- a/src/guacenc/man/guacenc.1 +++ b/src/guacenc/man/guacenc.1 @@ -7,6 +7,7 @@ guacenc \- Guacamole video encoder .B guacenc [\fB-s\fR \fIWIDTH\fRx\fIHEIGHT\fR] [\fB-r\fR \fIBITRATE\fR] +[\fB-f\fR] [\fIFILE\fR]... . .SH DESCRIPTION @@ -23,7 +24,18 @@ 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. +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 @@ -39,6 +51,12 @@ 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 From 86eb9c4b8abd75b42ec5f3315cba4ce1c22f2a70 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 15 Mar 2016 19:35:06 -0700 Subject: [PATCH 91/92] GUAC-236: Fix copypasto in guacenc_display_free_*() - we are freeing, not allocating. --- src/guacenc/display-buffers.c | 2 +- src/guacenc/display-layers.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/guacenc/display-buffers.c b/src/guacenc/display-buffers.c index 9fd8663d..5bd68d35 100644 --- a/src/guacenc/display-buffers.c +++ b/src/guacenc/display-buffers.c @@ -71,7 +71,7 @@ int guacenc_display_free_buffer(guacenc_display* display, /* Transform index to buffer space */ int internal_index = -index - 1; - /* Do not lookup / allocate if index is invalid */ + /* 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; diff --git a/src/guacenc/display-layers.c b/src/guacenc/display-layers.c index 23b43e75..e0406f8b 100644 --- a/src/guacenc/display-layers.c +++ b/src/guacenc/display-layers.c @@ -84,7 +84,7 @@ int guacenc_display_get_depth(guacenc_display* display, guacenc_layer* layer) { int guacenc_display_free_layer(guacenc_display* display, int index) { - /* Do not lookup / allocate if index is invalid */ + /* 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; From e2030c03d020612881029e8df9c385eedea14e0a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 15 Mar 2016 20:34:22 -0700 Subject: [PATCH 92/92] GUAC-236: They're buffers, not buggers. --- src/guacenc/display.h | 2 +- src/guacenc/image-stream.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/guacenc/display.h b/src/guacenc/display.h index b8956d76..266c213b 100644 --- a/src/guacenc/display.h +++ b/src/guacenc/display.h @@ -293,7 +293,7 @@ guacenc_buffer* guacenc_display_get_related_buffer(guacenc_display* display, * when drawing the image. * * @param layer_index - * The index of the layer or bugger that the image should be drawn to. + * 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. diff --git a/src/guacenc/image-stream.h b/src/guacenc/image-stream.h index ff36ee1f..888b15ce 100644 --- a/src/guacenc/image-stream.h +++ b/src/guacenc/image-stream.h @@ -154,7 +154,7 @@ guacenc_decoder* guacenc_get_decoder(const char* mimetype); * when drawing the image. * * @param index - * The index of the layer or bugger that the image should be drawn to. + * 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.