Merge pull request #125 from glyptodon/screen-recording

GUAC-246: Implement screen recording
This commit is contained in:
James Muehlner 2016-03-15 20:41:28 -07:00
commit f5f77fea35
69 changed files with 5904 additions and 1 deletions

View File

@ -29,6 +29,7 @@ DIST_SUBDIRS = \
src/common-ssh \
src/terminal \
src/guacd \
src/guacenc \
src/protocols/rdp \
src/protocols/ssh \
src/protocols/telnet \
@ -38,7 +39,6 @@ DIST_SUBDIRS = \
SUBDIRS = \
src/libguac \
src/common \
src/guacd \
tests
if ENABLE_COMMON_SSH
@ -65,6 +65,14 @@ if ENABLE_VNC
SUBDIRS += src/protocols/vnc
endif
if ENABLE_GUACD
SUBDIRS += src/guacd
endif
if ENABLE_GUACENC
SUBDIRS += src/guacenc
endif
EXTRA_DIST = \
LICENSE \
bin/guacctl \

View File

@ -146,6 +146,63 @@ AC_ARG_WITH(guacd_conf,
[guacd_conf=/etc/guacamole/guacd.conf])
AC_DEFINE_UNQUOTED([GUACD_CONF_FILE], ["$guacd_conf"], [The full path to the guacd config file])
#
# libavcodec
#
have_libavcodec=disabled
AC_ARG_WITH([libavcodec],
[AS_HELP_STRING([--with-libavcodec],
[use libavcodec when encoding video @<:@default=check@:>@])],
[],
[with_libavcodec=check])
if test "x$with_libavcodec" != "xno"
then
have_libavcodec=yes
PKG_CHECK_MODULES([AVCODEC], [libavcodec],, [have_libavcodec=no]);
fi
AM_CONDITIONAL([ENABLE_AVCODEC], [test "x${have_libavcodec}" = "xyes"])
#
# libavutil
#
have_libavutil=disabled
AC_ARG_WITH([libavutil],
[AS_HELP_STRING([--with-libavutil],
[use libavutil when encoding video @<:@default=check@:>@])],
[],
[with_libavutil=check])
if test "x$with_libavutil" != "xno"
then
have_libavutil=yes
PKG_CHECK_MODULES([AVUTIL], [libavutil],, [have_libavutil=no]);
fi
AM_CONDITIONAL([ENABLE_AVUTIL], [test "x${have_libavutil}" = "xyes"])
#
# libswscale
#
have_libswscale=disabled
AC_ARG_WITH([libswscale],
[AS_HELP_STRING([--with-libswscale],
[use libswscale when encoding video @<:@default=check@:>@])],
[],
[with_libswscale=check])
if test "x$with_libswscale" != "xno"
then
have_libswscale=yes
PKG_CHECK_MODULES([SWSCALE], [libswscale],, [have_libswscale=no]);
fi
AM_CONDITIONAL([ENABLE_SWSCALE], [test "x${have_libswscale}" = "xyes"])
#
# libssl
#
@ -966,6 +1023,36 @@ fi
AM_CONDITIONAL([ENABLE_WEBP], [test "x${have_webp}" = "xyes"])
AC_SUBST(WEBP_LIBS)
#
# guacd
#
AC_ARG_ENABLE([guacd],
[AS_HELP_STRING([--disable-guacd],
[do not build the Guacamole proxy daemon])],
[],
[enable_guacd=yes])
AM_CONDITIONAL([ENABLE_GUACD], [test "x${enable_guacd}" = "xyes"])
#
# guacenc
#
AC_ARG_ENABLE([guacenc],
[AS_HELP_STRING([--disable-guacenc],
[do not build the Guacamole video encoding tool])],
[],
[enable_guacenc=yes])
AM_CONDITIONAL([ENABLE_GUACENC], [test "x${enable_guacenc}" = "xyes" \
-a "x${have_libavcodec}" = "xyes" \
-a "x${have_libavutil}" = "xyes" \
-a "x${have_libswscale}" = "xyes"])
#
# Output Makefiles
#
AC_CONFIG_FILES([Makefile
tests/Makefile
@ -974,19 +1061,39 @@ AC_CONFIG_FILES([Makefile
src/terminal/Makefile
src/libguac/Makefile
src/guacd/Makefile
src/guacenc/Makefile
src/protocols/rdp/Makefile
src/protocols/ssh/Makefile
src/protocols/telnet/Makefile
src/protocols/vnc/Makefile])
AC_OUTPUT
#
# Protocol build status
#
AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no])
AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no])
AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no])
AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no])
#
# Service / tool build status
#
AM_COND_IF([ENABLE_GUACD], [build_guacd=yes], [build_guacd=no])
AM_COND_IF([ENABLE_GUACENC], [build_guacenc=yes], [build_guacenc=no])
#
# Init scripts
#
AM_COND_IF([ENABLE_INIT], [build_init="${init_dir}"], [build_init=no])
#
# Display summary
#
echo "
------------------------------------------------
$PACKAGE_NAME version $PACKAGE_VERSION
@ -996,8 +1103,11 @@ $PACKAGE_NAME version $PACKAGE_VERSION
freerdp ............. ${have_freerdp}
pango ............... ${have_pango}
libavcodec .......... ${have_libavcodec}
libavutil ........... ${have_libavutil}
libssh2 ............. ${have_libssh2}
libssl .............. ${have_ssl}
libswscale .......... ${have_libswscale}
libtelnet ........... ${have_libtelnet}
libVNCServer ........ ${have_libvncserver}
libvorbis ........... ${have_vorbis}
@ -1011,6 +1121,11 @@ $PACKAGE_NAME version $PACKAGE_VERSION
Telnet .... ${build_telnet}
VNC ....... ${build_vnc}
Services / tools:
guacd ...... ${build_guacd}
guacenc .... ${build_guacenc}
Init scripts: ${build_init}
Type \"make\" to compile $PACKAGE_NAME.

View File

@ -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

159
src/common/guac_recording.c Normal file
View File

@ -0,0 +1,159 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "guac_recording.h"
#include <guacamole/client.h>
#include <guacamole/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/**
* Attempts to open a new recording within the given path and having the given
* name. If such a file already exists, sequential numeric suffixes (.1, .2,
* .3, etc.) are appended until a filename is found which does not exist (or
* until the maximum number of numeric suffixes has been tried). If the file
* absolutely cannot be opened due to an error, -1 is returned and errno is set
* appropriately.
*
* @param path
* The full path to the directory in which the data file should be created.
*
* @param name
* The name of the data file which should be crated within the given path.
*
* @param basename
* A buffer in which the path, a path separator, the filename, any
* necessary suffix, and a NULL terminator will be stored. If insufficient
* space is available, -1 will be returned, and errno will be set to
* ENAMETOOLONG.
*
* @param basename_size
* The number of bytes available within the provided basename buffer.
*
* @return
* The file descriptor of the open data file if open succeeded, or -1 on
* failure.
*/
static int guac_common_recording_open(const char* path,
const char* name, char* basename, int basename_size) {
int i;
/* Concatenate path and name (separated by a single slash) */
int basename_length = snprintf(basename,
basename_size - GUAC_COMMON_RECORDING_MAX_SUFFIX_LENGTH,
"%s/%s", path, name);
/* Abort if maximum length reached */
if (basename_length ==
basename_size - GUAC_COMMON_RECORDING_MAX_SUFFIX_LENGTH) {
errno = ENAMETOOLONG;
return -1;
}
/* Attempt to open recording */
int fd = open(basename,
O_CREAT | O_EXCL | O_WRONLY,
S_IRUSR | S_IWUSR);
/* Continuously retry with alternate names on failure */
if (fd == -1) {
/* Prepare basename for additional suffix */
basename[basename_length] = '.';
char* suffix = &(basename[basename_length + 1]);
/* Continue retrying alternative suffixes if file already exists */
for (i = 1; fd == -1 && errno == EEXIST
&& i <= GUAC_COMMON_RECORDING_MAX_SUFFIX; i++) {
/* Append new suffix */
sprintf(suffix, "%i", i);
/* Retry with newly-suffixed filename */
fd = open(basename,
O_CREAT | O_EXCL | O_WRONLY,
S_IRUSR | S_IWUSR);
}
} /* end if open succeeded */
/* Lock entire output file for writing by the current process */
struct flock file_lock = {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0,
.l_pid = getpid()
};
/* Abort if file cannot be locked for reading */
if (fcntl(fd, F_SETLK, &file_lock) == -1) {
close(fd);
return -1;
}
return fd;
}
int guac_common_recording_create(guac_client* client, const char* path,
const char* name, int create_path) {
char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH];
/* Create path if it does not exist, fail if impossible */
if (create_path && mkdir(path, S_IRWXU) && errno != EEXIST) {
guac_client_log(client, GUAC_LOG_ERROR,
"Creation of recording failed: %s", strerror(errno));
return 1;
}
/* Attempt to open recording file */
int fd = guac_common_recording_open(path, name, filename, sizeof(filename));
if (fd == -1) {
guac_client_log(client, GUAC_LOG_ERROR,
"Creation of recording failed: %s", strerror(errno));
return 1;
}
/* Replace client socket with wrapped socket */
client->socket = guac_socket_tee(client->socket, guac_socket_open(fd));
/* Recording creation succeeded */
guac_client_log(client, GUAC_LOG_INFO,
"Recording of session will be saved to \"%s\".",
filename);
return 0;
}

View File

@ -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 <guacamole/client.h>
/**
* 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

5
src/guacenc/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Compiled guacenc
guacenc
guacenc.exe

103
src/guacenc/Makefile.am Normal file
View File

@ -0,0 +1,103 @@
#
# Copyright (C) 2016 Glyptodon, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
AUTOMAKE_OPTIONS = foreign
bin_PROGRAMS = guacenc
man_MANS = \
man/guacenc.1
noinst_HEADERS = \
buffer.h \
display.h \
encode.h \
ffmpeg-compat.h \
guacenc.h \
image-stream.h \
instructions.h \
jpeg.h \
layer.h \
log.h \
parse.h \
png.h \
video.h
guacenc_SOURCES = \
buffer.c \
display.c \
display-buffers.c \
display-image-streams.c \
display-flatten.c \
display-layers.c \
display-sync.c \
encode.c \
guacenc.c \
image-stream.c \
instructions.c \
instruction-blob.c \
instruction-cfill.c \
instruction-copy.c \
instruction-cursor.c \
instruction-dispose.c \
instruction-end.c \
instruction-img.c \
instruction-move.c \
instruction-rect.c \
instruction-shade.c \
instruction-size.c \
instruction-sync.c \
instruction-transfer.c \
jpeg.c \
layer.c \
log.c \
parse.c \
png.c \
video.c
# Compile WebP support if available
if ENABLE_WEBP
guacenc_SOURCES += webp.c
noinst_HEADERS += webp.h
endif
guacenc_CFLAGS = \
-Werror -Wall -pedantic \
@AVCODEC_CFLAGS@ \
@AVUTIL_CFLAGS@ \
@LIBGUAC_INCLUDE@ \
@SWSCALE_CFLAGS@
guacenc_LDADD = \
@LIBGUAC_LTLIB@
guacenc_LDFLAGS = \
@AVCODEC_LIBS@ \
@AVUTIL_LIBS@ \
@CAIRO_LIBS@ \
@JPEG_LIBS@ \
@SWSCALE_LIBS@ \
@WEBP_LIBS@
EXTRA_DIST = \
man/guacenc.1

174
src/guacenc/buffer.c Normal file
View File

@ -0,0 +1,174 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "buffer.h"
#include <cairo/cairo.h>
#include <assert.h>
#include <stdlib.h>
guacenc_buffer* guacenc_buffer_alloc() {
return calloc(1, sizeof(guacenc_buffer));
}
/**
* Frees the underlying image data, surface, and graphics context of the given
* buffer, marking each as unallocated.
*
* @param buffer
* The guacenc_buffer whose image data, surface, and graphics context
* should be freed.
*/
static void guacenc_buffer_free_image(guacenc_buffer* buffer) {
/* Free graphics context */
if (buffer->cairo != NULL) {
cairo_destroy(buffer->cairo);
buffer->cairo = NULL;
}
/* Free Cairo surface */
if (buffer->surface != NULL) {
cairo_surface_destroy(buffer->surface);
buffer->surface = NULL;
}
/* Free image data (previously wrapped by Cairo surface */
free(buffer->image);
buffer->image = NULL;
}
void guacenc_buffer_free(guacenc_buffer* buffer) {
/* Ignore NULL buffer */
if (buffer == NULL)
return;
/* Free buffer and underlying image */
guacenc_buffer_free_image(buffer);
free(buffer);
}
int guacenc_buffer_resize(guacenc_buffer* buffer, int width, int height) {
/* Ignore requests which do not change the size */
if (buffer->width == width && buffer->height == height)
return 0;
/* Simply deallocate if new image has absolutely no pixels */
if (width == 0 || height == 0) {
guacenc_buffer_free_image(buffer);
buffer->width = width;
buffer->height = height;
buffer->stride = 0;
return 0;
}
/* Allocate data for new image */
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
unsigned char* image = calloc(1, stride*height);
/* Wrap data in surface */
cairo_surface_t* surface = cairo_image_surface_create_for_data(image,
CAIRO_FORMAT_ARGB32, width, height, stride);
/* Obtain graphics context of new surface */
cairo_t* cairo = cairo_create(surface);
/* Copy old surface, if defined */
if (buffer->surface != NULL) {
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface(cairo, buffer->surface, 0, 0);
cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
cairo_paint(cairo);
}
/* Update properties */
buffer->width = width;
buffer->height = height;
buffer->stride = stride;
/* Replace old image */
guacenc_buffer_free_image(buffer);
buffer->image = image;
buffer->surface = surface;
buffer->cairo = cairo;
return 0;
}
int guacenc_buffer_fit(guacenc_buffer* buffer, int x, int y) {
/* Increase width to fit X (if necessary) */
int new_width = buffer->width;
if (new_width < x+1)
new_width = x+1;
/* Increase height to fit Y (if necessary) */
int new_height = buffer->height;
if (new_height < y+1)
new_height = y+1;
/* Resize buffer if size needs to change to fit X/Y coordinate */
if (new_width != buffer->width || new_height != buffer->height)
return guacenc_buffer_resize(buffer, new_width, new_height);
/* No change necessary */
return 0;
}
int guacenc_buffer_copy(guacenc_buffer* dst, guacenc_buffer* src) {
/* Resize destination to exactly fit source */
if (guacenc_buffer_resize(dst, src->width, src->height))
return 1;
/* Copy surface contents identically */
if (src->surface != NULL) {
/* Destination must be non-NULL as its size is that of the source */
assert(dst->cairo != NULL);
/* Reset state of destination */
cairo_t* cairo = dst->cairo;
cairo_reset_clip(cairo);
/* Overwrite destination with contents of source */
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface(cairo, src->surface, 0, 0);
cairo_paint(cairo);
/* Reset operator of destination to default */
cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
}
return 0;
}

157
src/guacenc/buffer.h Normal file
View File

@ -0,0 +1,157 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACENC_BUFFER_H
#define GUACENC_BUFFER_H
#include "config.h"
#include <cairo/cairo.h>
#include <stdbool.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 {
/**
* Whether this buffer should be automatically resized to fit any draw
* operation.
*/
bool autosize;
/**
* The width of this buffer or layer, in pixels.
*/
int width;
/**
* The height of this buffer or layer, in pixels.
*/
int height;
/**
* The number of bytes in each row of image data.
*/
int stride;
/**
* The underlying image data of this surface. If the width or height of
* this surface are 0, this will be NULL.
*/
unsigned char* image;
/**
* The Cairo surface wrapping the underlying image data of this surface. If
* the width or height of this surface are 0, this will be NULL.
*/
cairo_surface_t* surface;
/**
* The current graphics context of the Cairo surface. If the width or
* height of this surface are 0, this will be NULL.
*/
cairo_t* cairo;
} guacenc_buffer;
/**
* Allocates and initializes a new buffer object. This allocation is
* independent of the Guacamole video encoder display; the allocated
* guacenc_buffer will not automatically be associated with the active display.
*
* @return
* A newly-allocated and initialized guacenc_buffer, or NULL if allocation
* fails.
*/
guacenc_buffer* guacenc_buffer_alloc();
/**
* Frees all memory associated with the given buffer object. If the buffer
* provided is NULL, this function has no effect.
*
* @param buffer
* The buffer to free, which may be NULL.
*/
void guacenc_buffer_free(guacenc_buffer* buffer);
/**
* Resizes the given buffer to the given dimensions, allocating or freeing
* memory as necessary, and updating the buffer's width, height, and stride
* properties.
*
* @param buffer
* The buffer to resize.
*
* @param width
* The new width of the buffer, in pixels.
*
* @param height
* The new height of the buffer, in pixels.
*
* @return
* Zero if the resize operation is successful, non-zero on error.
*/
int guacenc_buffer_resize(guacenc_buffer* buffer, int width, int height);
/**
* Resizes the given buffer as necessary to contain at the given X/Y
* coordinate, allocating or freeing memory as necessary, and updating the
* buffer's width, height, and stride properties. If the buffer already
* contains the given coordinate, this function has no effect.
*
* @param buffer
* The buffer to resize.
*
* @param x
* The X coordinate to ensure is within the buffer.
*
* @param y
* The Y coordinate to ensure is within the buffer.
*
* @return
* Zero if the resize operation is successful or no resize was performed,
* non-zero if the resize operation failed.
*/
int guacenc_buffer_fit(guacenc_buffer* buffer, int x, int y);
/**
* Copies the entire contents of the given source buffer to the destination
* buffer, ignoring the current contents of the destination. The destination
* buffer's contents are entirely replaced.
*
* @param dst
* The destination buffer whose contents should be replaced.
*
* @param src
* The source buffer whose contents should replace those of the destination
* buffer.
*
* @return
* Zero if the copy operation was successful, non-zero on failure.
*/
int guacenc_buffer_copy(guacenc_buffer* dst, guacenc_buffer* src);
#endif

View File

@ -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 <guacamole/client.h>
#include <stdlib.h>
guacenc_buffer* guacenc_display_get_buffer(guacenc_display* display,
int index) {
/* Transform index to buffer space */
int internal_index = -index - 1;
/* Do not lookup / allocate if index is invalid */
if (internal_index < 0 || internal_index > GUACENC_DISPLAY_MAX_BUFFERS) {
guacenc_log(GUAC_LOG_WARNING, "Buffer index out of bounds: %i", index);
return NULL;
}
/* Lookup buffer, allocating a new buffer if necessary */
guacenc_buffer* buffer = display->buffers[internal_index];
if (buffer == NULL) {
/* Attempt to allocate buffer */
buffer = guacenc_buffer_alloc();
if (buffer == NULL) {
guacenc_log(GUAC_LOG_WARNING, "Buffer allocation failed");
return NULL;
}
/* All non-layer buffers must autosize */
buffer->autosize = true;
/* Store buffer within display for future retrieval / management */
display->buffers[internal_index] = buffer;
}
return buffer;
}
int guacenc_display_free_buffer(guacenc_display* display,
int index) {
/* Transform index to buffer space */
int internal_index = -index - 1;
/* Do not lookup / free if index is invalid */
if (internal_index < 0 || internal_index > GUACENC_DISPLAY_MAX_BUFFERS) {
guacenc_log(GUAC_LOG_WARNING, "Buffer index out of bounds: %i", index);
return 1;
}
/* Free buffer (if allocated) */
guacenc_buffer_free(display->buffers[internal_index]);
/* Mark buffer as freed */
display->buffers[internal_index] = NULL;
return 0;
}
guacenc_buffer* guacenc_display_get_related_buffer(guacenc_display* display,
int index) {
/* Retrieve underlying buffer of layer if a layer is requested */
if (index >= 0) {
/* Retrieve / allocate layer (if possible */
guacenc_layer* layer = guacenc_display_get_layer(display, index);
if (layer == NULL)
return NULL;
/* Return underlying buffer */
return layer->buffer;
}
/* Otherwise retrieve buffer directly */
return guacenc_display_get_buffer(display, index);
}

View File

@ -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 <cairo/cairo.h>
#include <stdlib.h>
#include <string.h>
/**
* 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;
}

View File

@ -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 <guacamole/client.h>
#include <stdlib.h>
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;
}

View File

@ -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 <guacamole/client.h>
#include <stdlib.h>
guacenc_layer* guacenc_display_get_layer(guacenc_display* display,
int index) {
/* Do not lookup / allocate if index is invalid */
if (index < 0 || index > GUACENC_DISPLAY_MAX_LAYERS) {
guacenc_log(GUAC_LOG_WARNING, "Layer index out of bounds: %i", index);
return NULL;
}
/* Lookup layer, allocating a new layer if necessary */
guacenc_layer* layer = display->layers[index];
if (layer == NULL) {
/* Attempt to allocate layer */
layer = guacenc_layer_alloc();
if (layer == NULL) {
guacenc_log(GUAC_LOG_WARNING, "Layer allocation failed");
return NULL;
}
/* The default layer has no parent */
if (index == 0)
layer->parent_index = GUACENC_LAYER_NO_PARENT;
/* Store layer within display for future retrieval / management */
display->layers[index] = layer;
}
return layer;
}
int guacenc_display_get_depth(guacenc_display* display, guacenc_layer* layer) {
/* Non-existent layers have a depth of 0 */
if (layer == NULL)
return 0;
/* Layers with no parent have a depth of 0 */
if (layer->parent_index == GUACENC_LAYER_NO_PARENT)
return 0;
/* Retrieve parent layer */
guacenc_layer* parent =
guacenc_display_get_layer(display, layer->parent_index);
/* Current layer depth is the depth of the parent + 1 */
return guacenc_display_get_depth(display, parent) + 1;
}
int guacenc_display_free_layer(guacenc_display* display,
int index) {
/* Do not lookup / free if index is invalid */
if (index < 0 || index > GUACENC_DISPLAY_MAX_LAYERS) {
guacenc_log(GUAC_LOG_WARNING, "Layer index out of bounds: %i", index);
return 1;
}
/* Free layer (if allocated) */
guacenc_layer_free(display->layers[index]);
/* Mark layer as freed */
display->layers[index] = NULL;
return 0;
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "layer.h"
#include "log.h"
#include "video.h"
#include <guacamole/client.h>
#include <guacamole/timestamp.h>
#include <assert.h>
#include <stdlib.h>
int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) {
/* Verify timestamp is not decreasing */
if (timestamp < display->last_sync) {
guacenc_log(GUAC_LOG_WARNING, "Decreasing sync timestamp");
return 1;
}
/* Update timestamp of display */
display->last_sync = timestamp;
/* Flatten display to default layer */
if (guacenc_display_flatten(display))
return 1;
/* Retrieve default layer (guaranteed to not be NULL) */
guacenc_layer* def_layer = guacenc_display_get_layer(display, 0);
assert(def_layer != NULL);
/* Update video timeline */
if (guacenc_video_advance_timeline(display->output, timestamp))
return 1;
/* Prepare frame for write upon next flush */
guacenc_video_prepare_frame(display->output, def_layer->frame);
return 0;
}

134
src/guacenc/display.c Normal file
View File

@ -0,0 +1,134 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "video.h"
#include <cairo/cairo.h>
#include <stdlib.h>
cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask) {
/* Translate Guacamole channel mask into Cairo operator */
switch (mask) {
/* Source */
case GUAC_COMP_SRC:
return CAIRO_OPERATOR_SOURCE;
/* Over */
case GUAC_COMP_OVER:
return CAIRO_OPERATOR_OVER;
/* In */
case GUAC_COMP_IN:
return CAIRO_OPERATOR_IN;
/* Out */
case GUAC_COMP_OUT:
return CAIRO_OPERATOR_OUT;
/* Atop */
case GUAC_COMP_ATOP:
return CAIRO_OPERATOR_ATOP;
/* Over (source/destination reversed) */
case GUAC_COMP_ROVER:
return CAIRO_OPERATOR_DEST_OVER;
/* In (source/destination reversed) */
case GUAC_COMP_RIN:
return CAIRO_OPERATOR_DEST_IN;
/* Out (source/destination reversed) */
case GUAC_COMP_ROUT:
return CAIRO_OPERATOR_DEST_OUT;
/* Atop (source/destination reversed) */
case GUAC_COMP_RATOP:
return CAIRO_OPERATOR_DEST_ATOP;
/* XOR */
case GUAC_COMP_XOR:
return CAIRO_OPERATOR_XOR;
/* Additive */
case GUAC_COMP_PLUS:
return CAIRO_OPERATOR_ADD;
/* If unrecognized, just default to CAIRO_OPERATOR_OVER */
default:
return CAIRO_OPERATOR_OVER;
}
}
guacenc_display* guacenc_display_alloc(const char* path, const char* codec,
int width, int height, int bitrate) {
/* Prepare video encoding */
guacenc_video* video = guacenc_video_alloc(path, codec, width, height, bitrate);
if (video == NULL)
return NULL;
/* Allocate display */
guacenc_display* display =
(guacenc_display*) calloc(1, sizeof(guacenc_display));
/* Associate display with video output */
display->output = video;
return display;
}
int guacenc_display_free(guacenc_display* display) {
int i;
/* Ignore NULL display */
if (display == NULL)
return 0;
/* Finalize video */
int retval = guacenc_video_free(display->output);
/* Free all buffers */
for (i = 0; i < GUACENC_DISPLAY_MAX_BUFFERS; i++)
guacenc_buffer_free(display->buffers[i]);
/* Free all layers */
for (i = 0; i < GUACENC_DISPLAY_MAX_LAYERS; i++)
guacenc_layer_free(display->layers[i]);
/* Free all streams */
for (i = 0; i < GUACENC_DISPLAY_MAX_STREAMS; i++)
guacenc_image_stream_free(display->image_streams[i]);
free(display);
return retval;
}

367
src/guacenc/display.h Normal file
View File

@ -0,0 +1,367 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACENC_DISPLAY_H
#define GUACENC_DISPLAY_H
#include "config.h"
#include "buffer.h"
#include "image-stream.h"
#include "layer.h"
#include "video.h"
#include <guacamole/protocol.h>
#include <guacamole/timestamp.h>
/**
* The maximum number of buffers that the Guacamole video encoder will handle
* within a single Guacamole protocol dump.
*/
#define GUACENC_DISPLAY_MAX_BUFFERS 4096
/**
* The maximum number of layers that the Guacamole video encoder will handle
* within a single Guacamole protocol dump.
*/
#define GUACENC_DISPLAY_MAX_LAYERS 64
/**
* The maximum number of streams that the Guacamole video encoder will handle
* within a single Guacamole protocol dump.
*/
#define GUACENC_DISPLAY_MAX_STREAMS 64
/**
* The current state of the Guacamole video encoder's internal display.
*/
typedef struct guacenc_display {
/**
* All currently-allocated buffers. The index of the buffer corresponds to
* its position within this array, where -1 is the 0th entry. If a buffer
* has not yet been allocated, or a buffer has been freed (due to a
* "dispose" instruction), its entry here will be NULL.
*/
guacenc_buffer* buffers[GUACENC_DISPLAY_MAX_BUFFERS];
/**
* All currently-allocated layers. The index of the layer corresponds to
* its position within this array. If a layer has not yet been allocated,
* or a layer has been freed (due to a "dispose" instruction), its entry
* here will be NULL.
*/
guacenc_layer* layers[GUACENC_DISPLAY_MAX_LAYERS];
/**
* All currently-allocated image streams. The index of the stream
* corresponds to its position within this array. If a stream has not yet
* been allocated, or a stream has been freed (due to an "end"
* instruction), its entry here will be NULL.
*/
guacenc_image_stream* image_streams[GUACENC_DISPLAY_MAX_STREAMS];
/**
* The timestamp of the last sync instruction handled, or 0 if no sync has
* yet been read.
*/
guac_timestamp last_sync;
/**
* The video that this display is recording to.
*/
guacenc_video* output;
} guacenc_display;
/**
* Handles a received "sync" instruction having the given timestamp, flushing
* the current display to the in-progress video encoding.
*
* @param display
* The display to flush to the video encoding as a new frame.
*
* @param timestamp
* The timestamp of the new frame, as dictated by the "sync" instruction
* sent at the end of the frame.
*
* @return
* Zero if the frame was successfully written, non-zero if an error occurs.
*/
int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp);
/**
* Flattens the given display, rendering all child layers to the frame buffers
* of their parent layers. The frame buffer of the default layer of the display
* will thus contain the flattened, composited rendering of the entire display
* state after this function succeeds. The contents of the frame buffers of
* each layer are replaced by this function.
*
* @param display
* The display to flatten.
*
* @return
* Zero if the flatten operation succeeds, non-zero if an error occurs
* preventing proper rendering.
*/
int guacenc_display_flatten(guacenc_display* display);
/**
* Allocates a new Guacamole video encoder display. This display serves as the
* representation of encoding state, as well as the state of the Guacamole
* display as instructions are read and handled.
*
* @param path
* The full path to the file in which encoded video should be written.
*
* @param codec
* The name of the codec to use for the video encoding, as defined by
* ffmpeg / libavcodec.
*
* @param width
* The width of the desired video, in pixels.
*
* @param height
* The height of the desired video, in pixels.
*
* @param bitrate
* The desired overall bitrate of the resulting encoded video, in bits per
* second.
*
* @return
* The newly-allocated Guacamole video encoder display, or NULL if the
* display could not be allocated.
*/
guacenc_display* guacenc_display_alloc(const char* path, const char* codec,
int width, int height, int bitrate);
/**
* Frees all memory associated with the given Guacamole video encoder display,
* and finishes any underlying encoding process. If the given display is NULL,
* this function has no effect.
*
* @param display
* The Guacamole video encoder display to free, which may be NULL.
*
* @return
* Zero if the encoding process completed successfully, non-zero otherwise.
*/
int guacenc_display_free(guacenc_display* display);
/**
* Returns the layer having the given index. A new layer will be allocated if
* necessary. If the layer having the given index already exists, it will be
* returned.
*
* @param display
* The Guacamole video encoder display to retrieve the layer from.
*
* @param index
* The index of the layer to retrieve. All valid layer indices are
* non-negative.
*
* @return
* The layer having the given index, or NULL if the index is invalid or
* a new layer cannot be allocated.
*/
guacenc_layer* guacenc_display_get_layer(guacenc_display* display,
int index);
/**
* Returns the depth of a given layer in terms of parent layers. The layer
* depth is the number of layers above the given layer in hierarchy, where a
* layer without any parent (such as the default layer) has a depth of 0.
*
* @param layer
* The layer to check.
*
* @return
* The depth of the layer.
*/
int guacenc_display_get_depth(guacenc_display* display, guacenc_layer* layer);
/**
* Frees all resources associated with the layer having the given index. If
* the layer has not been allocated, this function has no effect.
*
* @param display
* The Guacamole video encoder display associated with the layer being
* freed.
*
* @param index
* The index of the layer to free. All valid layer indices are
* non-negative.
*
* @return
* Zero if the layer was successfully freed or was not allocated, non-zero
* if the layer could not be freed as the index was invalid.
*/
int guacenc_display_free_layer(guacenc_display* display, int index);
/**
* Returns the buffer having the given index. A new buffer will be allocated if
* necessary. If the buffer having the given index already exists, it will be
* returned.
*
* @param display
* The Guacamole video encoder display to retrieve the buffer from.
*
* @param index
* The index of the buffer to retrieve. All valid buffer indices are
* negative.
*
* @return
* The buffer having the given index, or NULL if the index is invalid or
* a new buffer cannot be allocated.
*/
guacenc_buffer* guacenc_display_get_buffer(guacenc_display* display,
int index);
/**
* Frees all resources associated with the buffer having the given index. If
* the buffer has not been allocated, this function has no effect.
*
* @param display
* The Guacamole video encoder display associated with the buffer being
* freed.
*
* @param index
* The index of the buffer to free. All valid buffer indices are negative.
*
* @return
* Zero if the buffer was successfully freed or was not allocated, non-zero
* if the buffer could not be freed as the index was invalid.
*/
int guacenc_display_free_buffer(guacenc_display* display, int index);
/**
* Returns the buffer associated with the layer or buffer having the given
* index. A new buffer or layer will be allocated if necessary. If the given
* index refers to a layer (is non-negative), the buffer underlying that layer
* will be returned. If the given index refers to a buffer (is negative), that
* buffer will be returned directly.
*
* @param display
* The Guacamole video encoder display to retrieve the buffer from.
*
* @param index
* The index of the buffer or layer whose associated buffer should be
* retrieved.
*
* @return
* The buffer associated with the buffer or layer having the given index,
* or NULL if the index is invalid.
*/
guacenc_buffer* guacenc_display_get_related_buffer(guacenc_display* display,
int index);
/**
* Creates a new image stream having the given index. If the stream having the
* given index already exists, it will be freed and replaced. If the mimetype
* specified is not supported, the image stream will still be allocated but
* will have no associated decoder (blobs send to that stream will have no
* effect).
*
* @param display
* The Guacamole video encoder display to associate with the
* newly-created image stream.
*
* @param index
* The index of the stream to create. All valid stream indices are
* non-negative.
*
* @param mask
* The Guacamole protocol compositing operation (channel mask) to apply
* when drawing the image.
*
* @param layer_index
* The index of the layer or buffer that the image should be drawn to.
*
* @param mimetype
* The mimetype of the image data that will be received along this stream.
*
* @param x
* The X coordinate of the upper-left corner of the rectangle within the
* destination layer or buffer that the image should be drawn to.
*
* @param y
* The Y coordinate of the upper-left corner of the rectangle within the
* destination layer or buffer that the image should be drawn to.
*
* @return
* Zero if the image stream was successfully created, non-zero otherwise.
*/
int guacenc_display_create_image_stream(guacenc_display* display, int index,
int mask, int layer_index, const char* mimetype, int x, int y);
/**
* Returns the stream having the given index. If no such stream exists, NULL
* will be returned.
*
* @param display
* The Guacamole video encoder display to retrieve the image stream from.
*
* @param index
* The index of the stream to retrieve. All valid stream indices are
* non-negative.
*
* @return
* The stream having the given index, or NULL if the index is invalid or
* a no such stream exists.
*/
guacenc_image_stream* guacenc_display_get_image_stream(
guacenc_display* display, int index);
/**
* Frees all resources associated with the stream having the given index. If
* the stream has not been allocated, this function has no effect.
*
* @param display
* The Guacamole video encoder display associated with the image stream
* being freed.
*
* @param index
* The index of the stream to free. All valid stream indices are
* non-negative.
*
* @return
* Zero if the stream was successfully freed or was not allocated, non-zero
* if the stream could not be freed as the index was invalid.
*/
int guacenc_display_free_image_stream(guacenc_display* display, int index);
/**
* Translates the given Guacamole protocol compositing mode (channel mask) to
* the corresponding Cairo composition operator. If no such operator exists,
* CAIRO_OPERATOR_OVER will be returned by default.
*
* @param mask
* The Guacamole protocol compositing mode (channel mask) to translate.
*
* @return
* The cairo_operator_t that corresponds to the given compositing mode
* (channel mask). CAIRO_OPERATOR_OVER will be returned by default if no
* such operator exists.
*/
cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask);
#endif

156
src/guacenc/encode.c Normal file
View File

@ -0,0 +1,156 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "instructions.h"
#include "log.h"
#include <guacamole/client.h>
#include <guacamole/error.h>
#include <guacamole/parser.h>
#include <guacamole/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
/**
* Reads and handles all Guacamole instructions from the given guac_socket
* until end-of-stream is reached.
*
* @param display
* The current internal display of the Guacamole video encoder.
*
* @param path
* The name of the file being parsed (for logging purposes). This file
* must already be open and available through the given socket.
*
* @param socket
* The guac_socket through which instructions should be read.
*
* @return
* Zero on success, non-zero if parsing of Guacamole protocol data through
* the given socket fails.
*/
static int guacenc_read_instructions(guacenc_display* display,
const char* path, guac_socket* socket) {
/* Obtain Guacamole protocol parser */
guac_parser* parser = guac_parser_alloc();
if (parser == NULL)
return 1;
/* Continuously read and handle all instructions */
while (!guac_parser_read(parser, socket, -1)) {
guacenc_handle_instruction(display, parser->opcode,
parser->argc, parser->argv);
}
/* Fail on read/parse error */
if (guac_error != GUAC_STATUS_CLOSED) {
guacenc_log(GUAC_LOG_ERROR, "%s: %s",
path, guac_status_string(guac_error));
guac_parser_free(parser);
return 1;
}
/* Parse complete */
guac_parser_free(parser);
return 0;
}
int guacenc_encode(const char* path, const char* out_path, const char* codec,
int width, int height, int bitrate, bool force) {
/* Open input file */
int fd = open(path, O_RDONLY);
if (fd < 0) {
guacenc_log(GUAC_LOG_ERROR, "%s: %s", path, strerror(errno));
return 1;
}
/* Lock entire input file for reading by the current process */
struct flock file_lock = {
.l_type = F_RDLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0,
.l_pid = getpid()
};
/* Abort if file cannot be locked for reading */
if (!force && fcntl(fd, F_SETLK, &file_lock) == -1) {
/* Warn if lock cannot be acquired */
if (errno == EACCES || errno == EAGAIN)
guacenc_log(GUAC_LOG_WARNING, "Refusing to encode in-progress "
"recording \"%s\" (specify the -f option to override "
"this behavior).", path);
/* Log an error if locking fails in an unexpected way */
else
guacenc_log(GUAC_LOG_ERROR, "Cannot lock \"%s\" for reading: %s",
path, strerror(errno));
close(fd);
return 1;
}
/* Allocate display for encoding process */
guacenc_display* display = guacenc_display_alloc(out_path, codec,
width, height, bitrate);
if (display == NULL) {
close(fd);
return 1;
}
/* Obtain guac_socket wrapping file descriptor */
guac_socket* socket = guac_socket_open(fd);
if (socket == NULL) {
guacenc_log(GUAC_LOG_ERROR, "%s: %s", path,
guac_status_string(guac_error));
close(fd);
guacenc_display_free(display);
return 1;
}
guacenc_log(GUAC_LOG_INFO, "Encoding \"%s\" to \"%s\" ...", path, out_path);
/* Attempt to read all instructions in the file */
if (guacenc_read_instructions(display, path, socket)) {
guac_socket_free(socket);
guacenc_display_free(display);
return 1;
}
/* Close input and finish encoding process */
guac_socket_free(socket);
return guacenc_display_free(display);
}

68
src/guacenc/encode.h Normal file
View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACENC_ENCODE_H
#define GUACENC_ENCODE_H
#include "config.h"
#include <stdbool.h>
/**
* Encodes the given Guacamole protocol dump as video. A read lock will be
* acquired on the input file to ensure that in-progress recordings are not
* encoded. This behavior can be overridden by specifying true for the force
* parameter.
*
* @param path
* The path to the file containing the raw Guacamole protocol dump.
*
* @param out_path
* The full path to the file in which encoded video should be written.
*
* @param codec
* The name of the codec to use for the video encoding, as defined by
* ffmpeg / libavcodec.
*
* @param width
* The width of the desired video, in pixels.
*
* @param height
* The height of the desired video, in pixels.
*
* @param bitrate
* The desired overall bitrate of the resulting encoded video, in bits per
* second.
*
* @param force
* Perform the encoding, even if the input file appears to be an
* in-progress recording (has an associated lock).
*
* @return
* Zero on success, non-zero if an error prevented successful encoding of
* the video.
*/
int guacenc_encode(const char* path, const char* out_path, const char* codec,
int width, int height, int bitrate, bool force);
#endif

View File

@ -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 <libavcodec/avcodec.h>
/*
* 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

152
src/guacenc/guacenc.c Normal file
View File

@ -0,0 +1,152 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "encode.h"
#include "guacenc.h"
#include "log.h"
#include "parse.h"
#include <libavcodec/avcodec.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
int i;
/* Load defaults */
bool force = false;
int width = GUACENC_DEFAULT_WIDTH;
int height = GUACENC_DEFAULT_HEIGHT;
int bitrate = GUACENC_DEFAULT_BITRATE;
/* Parse arguments */
int opt;
while ((opt = getopt(argc, argv, "s:r:f")) != -1) {
/* -s: Dimensions (WIDTHxHEIGHT) */
if (opt == 's') {
if (guacenc_parse_dimensions(optarg, &width, &height)) {
guacenc_log(GUAC_LOG_ERROR, "Invalid dimensions.");
goto invalid_options;
}
}
/* -r: Bitrate (bits per second) */
else if (opt == 'r') {
if (guacenc_parse_int(optarg, &bitrate)) {
guacenc_log(GUAC_LOG_ERROR, "Invalid bitrate.");
goto invalid_options;
}
}
/* -f: Force */
else if (opt == 'f')
force = true;
/* Invalid option */
else {
goto invalid_options;
}
}
/* Log start */
guacenc_log(GUAC_LOG_INFO, "Guacamole video encoder (guacenc) "
"version " VERSION);
/* Prepare libavcodec */
avcodec_register_all();
/* Track number of overall failures */
int total_files = argc - optind;
int failures = 0;
/* Abort if no files given */
if (total_files <= 0) {
guacenc_log(GUAC_LOG_INFO, "No input files specified. Nothing to do.");
return 0;
}
guacenc_log(GUAC_LOG_INFO, "%i input file(s) provided.", total_files);
guacenc_log(GUAC_LOG_INFO, "Video will be encoded at %ix%i "
"and %i bps.", width, height, bitrate);
/* Encode all input files */
for (i = optind; i < argc; i++) {
/* Get current filename */
const char* path = argv[i];
/* Generate output filename */
char out_path[4096];
int len = snprintf(out_path, sizeof(out_path), "%s.m4v", path);
/* Do not write if filename exceeds maximum length */
if (len >= sizeof(out_path)) {
guacenc_log(GUAC_LOG_ERROR, "Cannot write output file for \"%s\": "
"Name too long", path);
continue;
}
/* Attempt encoding, log granular success/failure at debug level */
if (guacenc_encode(path, out_path, "mpeg4",
width, height, bitrate, force)) {
failures++;
guacenc_log(GUAC_LOG_DEBUG,
"%s was NOT successfully encoded.", path);
}
else
guacenc_log(GUAC_LOG_DEBUG, "%s was successfully encoded.", path);
}
/* Warn if at least one file failed */
if (failures != 0)
guacenc_log(GUAC_LOG_WARNING, "Encoding failed for %i of %i file(s).",
failures, total_files);
/* Notify of success */
else
guacenc_log(GUAC_LOG_INFO, "All files encoded successfully.");
/* Encoding complete */
return 0;
/* Display usage and exit with error if options are invalid */
invalid_options:
fprintf(stderr, "USAGE: %s"
" [-s WIDTHxHEIGHT]"
" [-r BITRATE]"
" [-f]"
" [FILE]...\n", argv[0]);
return 1;
}

56
src/guacenc/guacenc.h Normal file
View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACENC_H
#define GUACENC_H
#include "config.h"
#include <guacamole/client.h>
/**
* The width of the output video, in pixels, if no other width is given on the
* command line. Note that different codecs will have different restrictions
* regarding legal widths.
*/
#define GUACENC_DEFAULT_WIDTH 640
/**
* The height of the output video, in pixels, if no other height is given on the
* command line. Note that different codecs will have different restrictions
* regarding legal heights.
*/
#define GUACENC_DEFAULT_HEIGHT 480
/**
* The desired bitrate of the output video, in bits per second, if no other
* bitrate is given on the command line.
*/
#define GUACENC_DEFAULT_BITRATE 2000000
/**
* The default log level below which no messages should be logged.
*/
#define GUACENC_DEFAULT_LOG_LEVEL GUAC_LOG_INFO
#endif

169
src/guacenc/image-stream.c Normal file
View File

@ -0,0 +1,169 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "image-stream.h"
#include "jpeg.h"
#include "log.h"
#include "png.h"
#ifdef ENABLE_WEBP
#include "webp.h"
#endif
#include <cairo/cairo.h>
#include <stdlib.h>
#include <string.h>
guacenc_decoder_mapping guacenc_decoder_map[] = {
{"image/png", guacenc_png_decoder},
{"image/jpeg", guacenc_jpeg_decoder},
#ifdef ENABLE_WEBP
{"image/webp", guacenc_webp_decoder},
#endif
{NULL, NULL}
};
guacenc_decoder* guacenc_get_decoder(const char* mimetype) {
/* Search through mapping for the decoder having given mimetype */
guacenc_decoder_mapping* current = guacenc_decoder_map;
while (current->mimetype != NULL) {
/* Return decoder if mimetype matches */
if (strcmp(current->mimetype, mimetype) == 0)
return current->decoder;
/* Next candidate decoder */
current++;
}
/* No such decoder */
guacenc_log(GUAC_LOG_WARNING, "Support for \"%s\" not present", mimetype);
return NULL;
}
guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index,
const char* mimetype, int x, int y) {
/* Allocate stream */
guacenc_image_stream* stream = malloc(sizeof(guacenc_image_stream));
if (stream == NULL)
return NULL;
/* Init properties */
stream->index = index;
stream->mask = mask;
stream->x = x;
stream->y = y;
/* Associate with corresponding decoder */
stream->decoder = guacenc_get_decoder(mimetype);
/* Allocate initial buffer */
stream->length = 0;
stream->max_length = GUACENC_IMAGE_STREAM_INITIAL_LENGTH;
stream->buffer = (unsigned char*) malloc(stream->max_length);
return stream;
}
int guacenc_image_stream_receive(guacenc_image_stream* stream,
unsigned char* data, int length) {
/* Allocate more space if necessary */
if (stream->max_length - stream->length < length) {
/* Calculate a reasonable new max length guaranteed to fit buffer */
int new_max_length = stream->max_length * 2 + length;
/* Attempt to resize buffer */
unsigned char* new_buffer =
(unsigned char*) realloc(stream->buffer, new_max_length);
if (new_buffer == NULL)
return 1;
/* Store updated buffer and size */
stream->buffer = new_buffer;
stream->max_length = new_max_length;
}
/* Append data */
memcpy(stream->buffer + stream->length, data, length);
stream->length += length;
return 0;
}
int guacenc_image_stream_end(guacenc_image_stream* stream,
guacenc_buffer* buffer) {
/* If there is no decoder, simply return success */
guacenc_decoder* decoder = stream->decoder;
if (decoder == NULL)
return 0;
/* Decode received data to a Cairo surface */
cairo_surface_t* surface = stream->decoder(stream->buffer, stream->length);
if (surface == NULL)
return 1;
/* Get surface dimensions */
int width = cairo_image_surface_get_width(surface);
int height = cairo_image_surface_get_height(surface);
/* Expand the buffer as necessary to fit the draw operation */
if (buffer->autosize)
guacenc_buffer_fit(buffer, stream->x + width, stream->y + height);
/* Draw surface to buffer */
if (buffer->cairo != NULL) {
cairo_set_source_surface(buffer->cairo, surface, stream->x, stream->y);
cairo_rectangle(buffer->cairo, stream->x, stream->y, width, height);
cairo_fill(buffer->cairo);
}
cairo_surface_destroy(surface);
return 0;
}
int guacenc_image_stream_free(guacenc_image_stream* stream) {
/* Ignore NULL streams */
if (stream == NULL)
return 0;
/* Free image buffer */
free(stream->buffer);
/* Free actual stream */
free(stream);
return 0;
}

235
src/guacenc/image-stream.h Normal file
View File

@ -0,0 +1,235 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACENC_IMAGE_STREAM_H
#define GUACENC_IMAGE_STREAM_H
#include "config.h"
#include "buffer.h"
#include <cairo/cairo.h>
/**
* The initial number of bytes to allocate for the image data buffer. If this
* buffer is not sufficiently large, it will be dynamically reallocated as it
* grows.
*/
#define GUACENC_IMAGE_STREAM_INITIAL_LENGTH 4096
/**
* Callback function which is provided raw, encoded image data of the given
* length. The function is expected to return a new Cairo surface which will
* later (by guacenc) be freed via cairo_surface_destroy().
*
* @param data
* The raw encoded image data that this function must decode.
*
* @param length
* The length of the image data, in bytes.
*
* @return
* A newly-allocated Cairo surface containing the decoded image, or NULL
* or decoding fails.
*/
typedef cairo_surface_t* guacenc_decoder(unsigned char* data, int length);
/**
* The current state of an allocated Guacamole image stream.
*/
typedef struct guacenc_image_stream {
/**
* The index of the destination layer or buffer.
*/
int index;
/**
* The Guacamole protocol compositing operation (channel mask) to apply
* when drawing the image.
*/
int mask;
/**
* The X coordinate of the upper-left corner of the rectangle within the
* destination layer or buffer that the decoded image should be drawn to.
*/
int x;
/**
* The Y coordinate of the upper-left corner of the rectangle within the
* destination layer or buffer that the decoded image should be drawn to.
*/
int y;
/**
* Buffer of image data which will be built up over time as chunks are
* received via "blob" instructions. This will ultimately be passed in its
* entirety to the decoder function.
*/
unsigned char* buffer;
/**
* The number of bytes currently stored in the buffer.
*/
int length;
/**
* The maximum number of bytes that can be stored in the current buffer
* before it must be reallocated.
*/
int max_length;
/**
* The decoder to use when decoding the raw data received along this
* stream, or NULL if no such decoder exists.
*/
guacenc_decoder* decoder;
} guacenc_image_stream;
/**
* Mapping of image mimetype to corresponding decoder function.
*/
typedef struct guacenc_decoder_mapping {
/**
* The mimetype of the image that the associated decoder can read.
*/
const char* mimetype;
/**
* The decoder function to use when an image stream of the associated
* mimetype is received.
*/
guacenc_decoder* decoder;
} guacenc_decoder_mapping;
/**
* Array of all mimetype/decoder mappings for all supported image types,
* terminated by an entry with a NULL mimetype.
*/
extern guacenc_decoder_mapping guacenc_decoder_map[];
/**
* Returns the decoder associated with the given mimetype. If no such decoder
* exists, NULL is returned.
*
* @param mimetype
* The image mimetype to return the associated decoder of.
*
* @return
* The decoder associated with the given mimetype, or NULL if no such
* decoder exists.
*/
guacenc_decoder* guacenc_get_decoder(const char* mimetype);
/**
* Allocates and initializes a new image stream. This allocation is independent
* of the Guacamole video encoder display; the allocated guacenc_image_stream
* will not automatically be associated with the active display, nor will the
* provided layer/buffer index be validated.
*
* @param mask
* The Guacamole protocol compositing operation (channel mask) to apply
* when drawing the image.
*
* @param index
* The index of the layer or buffer that the image should be drawn to.
*
* @param mimetype
* The mimetype of the image data that will be received along this stream.
*
* @param x
* The X coordinate of the upper-left corner of the rectangle within the
* destination layer or buffer that the image should be drawn to.
*
* @param y
* The Y coordinate of the upper-left corner of the rectangle within the
* destination layer or buffer that the image should be drawn to.
*
* @return
* A newly-allocated and initialized guacenc_image_stream, or NULL if
* allocation fails.
*/
guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index,
const char* mimetype, int x, int y);
/**
* Appends newly-received data to the internal buffer of the given image
* stream, such that the entire received image can be fed to the decoder as one
* buffer once the stream ends.
*
* @param stream
* The image stream that received the data.
*
* @param data
* The chunk of data received along the image stream.
*
* @param length
* The length of the chunk of data received, in bytes.
*
* @return
* Zero if the given data was successfully appended to the in-progress
* image, non-zero if an error occurs.
*/
int guacenc_image_stream_receive(guacenc_image_stream* stream,
unsigned char* data, int length);
/**
* Marks the end of the given image stream (no more data will be received) and
* invokes the associated decoder. The decoded image will be written to the
* given buffer as-is. If no decoder is associated with the given image stream,
* this function has no effect. Meta-information describing the image draw
* operation itself is pulled from the guacenc_image_stream, having been stored
* there when the image stream was created.
*
* @param stream
* The image stream that has ended.
*
* @param buffer
* The buffer that the decoded image should be written to.
*
* @return
* Zero if the image is written successfully, or non-zero if an error
* occurs.
*/
int guacenc_image_stream_end(guacenc_image_stream* stream,
guacenc_buffer* buffer);
/**
* Frees the given image stream and all associated data. If the image stream
* has not yet ended (reached end-of-stream), no image will be drawn to the
* associated buffer or layer.
*
* @param stream
* The stream to free.
*
* @return
* Zero if freeing the stream succeeded, or non-zero if freeing the stream
* failed (for example, due to an error in the free handler of the
* decoder).
*/
int guacenc_image_stream_free(guacenc_image_stream* stream);
#endif

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "log.h"
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <stdlib.h>
int guacenc_handle_blob(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 2) {
guacenc_log(GUAC_LOG_WARNING, "\"blob\" instruction incomplete");
return 1;
}
/* Parse arguments */
int index = atoi(argv[0]);
char* data = argv[1];
int length = guac_protocol_decode_base64(data);
/* Retrieve image stream */
guacenc_image_stream* stream =
guacenc_display_get_image_stream(display, index);
if (stream == NULL)
return 1;
/* Send data to decoder within associated stream */
return guacenc_image_stream_receive(stream, (unsigned char*) data, length);
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "log.h"
#include <guacamole/client.h>
#include <stdlib.h>
int guacenc_handle_cfill(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 6) {
guacenc_log(GUAC_LOG_WARNING, "\"cfill\" instruction incomplete");
return 1;
}
/* Parse arguments */
int mask = atoi(argv[0]);
int index = atoi(argv[1]);
double r = atoi(argv[2]) / 255.0;
double g = atoi(argv[3]) / 255.0;
double b = atoi(argv[4]) / 255.0;
double a = atoi(argv[5]) / 255.0;
/* Pull buffer of requested layer/buffer */
guacenc_buffer* buffer = guacenc_display_get_related_buffer(display, index);
if (buffer == NULL)
return 1;
/* Fill with RGBA color */
if (buffer->cairo != NULL) {
cairo_set_operator(buffer->cairo, guacenc_display_cairo_operator(mask));
cairo_set_source_rgba(buffer->cairo, r, g, b, a);
cairo_fill(buffer->cairo);
}
return 0;
}

View File

@ -0,0 +1,106 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "log.h"
#include <guacamole/client.h>
#include <stdlib.h>
int guacenc_handle_copy(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 9) {
guacenc_log(GUAC_LOG_WARNING, "\"copy\" instruction incomplete");
return 1;
}
/* Parse arguments */
int sindex = atoi(argv[0]);
int sx = atoi(argv[1]);
int sy = atoi(argv[2]);
int width = atoi(argv[3]);
int height = atoi(argv[4]);
int mask = atoi(argv[5]);
int dindex = atoi(argv[6]);
int dx = atoi(argv[7]);
int dy = atoi(argv[8]);
/* Pull buffer of source layer/buffer */
guacenc_buffer* src = guacenc_display_get_related_buffer(display, sindex);
if (src == NULL)
return 1;
/* Pull buffer of destination layer/buffer */
guacenc_buffer* dst = guacenc_display_get_related_buffer(display, dindex);
if (dst == NULL)
return 1;
/* Expand the destination buffer as necessary to fit the draw operation */
if (dst->autosize)
guacenc_buffer_fit(dst, dx + width, dy + height);
/* Copy rectangle from source to destination */
if (src->surface != NULL && dst->cairo != NULL) {
/* If surfaces are different, no need to copy */
cairo_surface_t* surface;
if (src != dst)
surface = src->surface;
/* Otherwise, copy to a temporary surface */
else {
/* Create new surface to hold the source rect */
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
width, height);
/* Copy relevant rectangle from source surface */
cairo_t* cairo = cairo_create(surface);
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface(cairo, src->surface, -sx, -sy);
cairo_paint(cairo);
cairo_destroy(cairo);
/* Source coordinates are now (0, 0) */
sx = sy = 0;
}
/* Perform copy */
cairo_set_operator(dst->cairo, guacenc_display_cairo_operator(mask));
cairo_set_source_surface(dst->cairo, surface, dx - sx, dy - sy);
cairo_rectangle(dst->cairo, dx, dy, width, height);
cairo_fill(dst->cairo);
/* Destroy temporary surface if it was created */
if (surface != src->surface)
cairo_surface_destroy(surface);
}
return 0;
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "log.h"
#include <guacamole/client.h>
#include <stdlib.h>
int guacenc_handle_cursor(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 7) {
guacenc_log(GUAC_LOG_WARNING, "\"cursor\" instruction incomplete");
return 1;
}
/* Parse arguments */
int hotspot_x = atoi(argv[0]);
int hotspot_y = atoi(argv[1]);
int src_index = atoi(argv[2]);
int src_x = atoi(argv[3]);
int src_y = atoi(argv[4]);
int src_w = atoi(argv[5]);
int src_h = atoi(argv[6]);
/* Nothing to do with cursor (yet) */
guacenc_log(GUAC_LOG_DEBUG, "Ignoring cursor: hotspot (%i, %i) "
"src_layer=%i (%i, %i) %ix%i", hotspot_x, hotspot_y,
src_index, src_x, src_y, src_w, src_h);
return 0;
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "log.h"
#include <guacamole/client.h>
#include <stdlib.h>
int guacenc_handle_dispose(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 1) {
guacenc_log(GUAC_LOG_WARNING, "\"dispose\" instruction incomplete");
return 1;
}
/* Parse arguments */
int index = atoi(argv[0]);
/* If non-negative, dispose of layer */
if (index >= 0)
return guacenc_display_free_layer(display, index);
/* Otherwise, we're referring to a buffer */
return guacenc_display_free_buffer(display, index);
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "image-stream.h"
#include "log.h"
#include <guacamole/client.h>
#include <stdlib.h>
int guacenc_handle_end(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 1) {
guacenc_log(GUAC_LOG_WARNING, "\"end\" instruction incomplete");
return 1;
}
/* Parse arguments */
int index = atoi(argv[0]);
/* Retrieve image stream */
guacenc_image_stream* stream =
guacenc_display_get_image_stream(display, index);
if (stream == NULL)
return 1;
/* Retrieve destination buffer */
guacenc_buffer* buffer =
guacenc_display_get_related_buffer(display, stream->index);
if (buffer == NULL)
return 1;
/* End image stream, drawing final image to the buffer */
return guacenc_image_stream_end(stream, buffer);
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "log.h"
#include <guacamole/client.h>
#include <stdlib.h>
int guacenc_handle_img(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 6) {
guacenc_log(GUAC_LOG_WARNING, "\"img\" instruction incomplete");
return 1;
}
/* Parse arguments */
int stream_index = atoi(argv[0]);
int mask = atoi(argv[1]);
int layer_index = atoi(argv[2]);
char* mimetype = argv[3];
int x = atoi(argv[4]);
int y = atoi(argv[5]);
/* Create requested stream */
return guacenc_display_create_image_stream(display, stream_index,
mask, layer_index, mimetype, x, y);
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "log.h"
#include <guacamole/client.h>
#include <stdlib.h>
int guacenc_handle_move(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 5) {
guacenc_log(GUAC_LOG_WARNING, "\"move\" instruction incomplete");
return 1;
}
/* Parse arguments */
int layer_index = atoi(argv[0]);
int parent_index = atoi(argv[1]);
int x = atoi(argv[2]);
int y = atoi(argv[3]);
int z = atoi(argv[4]);
/* Retrieve requested layer */
guacenc_layer* layer = guacenc_display_get_layer(display, layer_index);
if (layer == NULL)
return 1;
/* Validate parent layer */
if (guacenc_display_get_layer(display, parent_index) == NULL)
return 1;
/* Update layer properties */
layer->parent_index = parent_index;
layer->x = x;
layer->y = y;
layer->z = z;
return 0;
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "buffer.h"
#include "display.h"
#include "log.h"
#include <cairo/cairo.h>
#include <guacamole/client.h>
#include <stdlib.h>
int guacenc_handle_rect(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 5) {
guacenc_log(GUAC_LOG_WARNING, "\"rect\" instruction incomplete");
return 1;
}
/* Parse arguments */
int index = atoi(argv[0]);
int x = atoi(argv[1]);
int y = atoi(argv[2]);
int width = atoi(argv[3]);
int height = atoi(argv[4]);
/* Pull buffer of requested layer/buffer */
guacenc_buffer* buffer = guacenc_display_get_related_buffer(display, index);
if (buffer == NULL)
return 1;
/* Expand the buffer as necessary to fit the draw operation */
if (buffer->autosize)
guacenc_buffer_fit(buffer, x + width, y + height);
/* Set path to rectangle */
if (buffer->cairo != NULL)
cairo_rectangle(buffer->cairo, x, y, width, height);
return 0;
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "log.h"
#include <guacamole/client.h>
#include <stdlib.h>
int guacenc_handle_shade(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 2) {
guacenc_log(GUAC_LOG_WARNING, "\"shade\" instruction incomplete");
return 1;
}
/* Parse arguments */
int index = atoi(argv[0]);
int opacity = atoi(argv[1]);
/* Retrieve requested layer */
guacenc_layer* layer = guacenc_display_get_layer(display, index);
if (layer == NULL)
return 1;
/* Update layer properties */
layer->opacity = opacity;
return 0;
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "log.h"
#include <guacamole/client.h>
#include <stdlib.h>
int guacenc_handle_size(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 3) {
guacenc_log(GUAC_LOG_WARNING, "\"size\" instruction incomplete");
return 1;
}
/* Parse arguments */
int index = atoi(argv[0]);
int width = atoi(argv[1]);
int height = atoi(argv[2]);
/* Retrieve requested layer */
guacenc_layer* layer = guacenc_display_get_layer(display, index);
if (layer == NULL)
return 1;
/* Resize layer */
return guacenc_buffer_resize(layer->buffer, width, height);
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "log.h"
#include <guacamole/client.h>
#include <guacamole/timestamp.h>
#include <inttypes.h>
#include <stdlib.h>
/**
* Parses a guac_timestamp from the given string. The string is assumed to
* consist solely of decimal digits with an optional leading minus sign. If the
* given string contains other characters, the behavior of this function is
* undefined.
*
* @param str
* The string to parse, which must contain only decimal digits and an
* optional leading minus sign.
*
* @return
* A guac_timestamp having the same value as the provided string.
*/
static guac_timestamp guacenc_parse_timestamp(const char* str) {
int sign = 1;
int64_t num = 0;
for (; *str != '\0'; str++) {
/* Flip sign for each '-' encountered */
if (*str == '-')
sign = -sign;
/* If not '-', assume the character is a digit */
else
num = num * 10 + (*str - '0');
}
return (guac_timestamp) (num * sign);
}
int guacenc_handle_sync(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 1) {
guacenc_log(GUAC_LOG_WARNING, "\"sync\" instruction incomplete");
return 1;
}
/* Parse arguments */
guac_timestamp timestamp = guacenc_parse_timestamp(argv[0]);
/* Update timestamp / flush frame */
return guacenc_display_sync(display, timestamp);
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "log.h"
#include <guacamole/client.h>
#include <stdlib.h>
int guacenc_handle_transfer(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 9) {
guacenc_log(GUAC_LOG_WARNING, "\"transform\" instruction incomplete");
return 1;
}
/* Parse arguments */
int src_index = atoi(argv[0]);
int src_x = atoi(argv[1]);
int src_y = atoi(argv[2]);
int src_w = atoi(argv[3]);
int src_h = atoi(argv[4]);
int function = atoi(argv[5]);
int dst_index = atoi(argv[6]);
int dst_x = atoi(argv[7]);
int dst_y = atoi(argv[8]);
/* TODO: Unimplemented for now (rarely used) */
guacenc_log(GUAC_LOG_DEBUG, "transform: src_layer=%i (%i, %i) %ix%i "
"function=0x%X dst_layer=%i (%i, %i)", src_index, src_x, src_y,
src_w, src_h, function, dst_index, dst_x, dst_y);
return 0;
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "display.h"
#include "instructions.h"
#include "log.h"
#include <guacamole/client.h>
#include <string.h>
guacenc_instruction_handler_mapping guacenc_instruction_handler_map[] = {
{"blob", guacenc_handle_blob},
{"img", guacenc_handle_img},
{"end", guacenc_handle_end},
{"sync", guacenc_handle_sync},
{"cursor", guacenc_handle_cursor},
{"copy", guacenc_handle_copy},
{"transfer", guacenc_handle_transfer},
{"size", guacenc_handle_size},
{"rect", guacenc_handle_rect},
{"cfill", guacenc_handle_cfill},
{"move", guacenc_handle_move},
{"shade", guacenc_handle_shade},
{"dispose", guacenc_handle_dispose},
{NULL, NULL}
};
int guacenc_handle_instruction(guacenc_display* display, const char* opcode,
int argc, char** argv) {
/* Search through mapping for instruction handler having given opcode */
guacenc_instruction_handler_mapping* current = guacenc_instruction_handler_map;
while (current->opcode != NULL) {
/* Invoke handler if opcode matches (if defined) */
if (strcmp(current->opcode, opcode) == 0) {
/* Invoke defined handler */
guacenc_instruction_handler* handler = current->handler;
if (handler != NULL)
return handler(display, argc, argv);
/* Log defined but unimplemented instructions */
guacenc_log(GUAC_LOG_DEBUG, "\"%s\" not implemented", opcode);
return 0;
}
/* Next candidate handler */
current++;
} /* end opcode search */
/* Ignore any unknown instructions */
return 0;
}

171
src/guacenc/instructions.h Normal file
View File

@ -0,0 +1,171 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACENC_INSTRUCTIONS_H
#define GUACENC_INSTRUCTIONS_H
#include "config.h"
#include "display.h"
/**
* A callback function which, when invoked, handles a particular Guacamole
* instruction. The opcode of the instruction is implied (as it is expected
* that there will be a 1:1 mapping of opcode to callback function), while the
* arguments for that instruction are included in the parameters given to the
* callback.
*
* @param display
* The current internal display of the Guacamole video encoder.
*
* @param argc
* The number of arguments (excluding opcode) passed to the instruction
* being handled by the callback.
*
* @param argv
* All arguments (excluding opcode) associated with the instruction being
* handled by the callback.
*
* @return
* Zero if the instruction was handled successfully, non-zero if an error
* occurs.
*/
typedef int guacenc_instruction_handler(guacenc_display* display,
int argc, char** argv);
/**
* Mapping of instruction opcode to corresponding handler function.
*/
typedef struct guacenc_instruction_handler_mapping {
/**
* The opcode of the instruction that the associated handler function
* should be invoked for.
*/
const char* opcode;
/**
* The handler function to invoke whenever an instruction having the
* associated opcode is parsed.
*/
guacenc_instruction_handler* handler;
} guacenc_instruction_handler_mapping;
/**
* Array of all opcode/handler mappings for all supported opcodes, terminated
* by an entry with a NULL opcode. All opcodes not listed here can be safely
* ignored.
*/
extern guacenc_instruction_handler_mapping guacenc_instruction_handler_map[];
/**
* Handles the instruction having the given opcode and arguments, encoding the
* result to the in-progress video.
*
* @param display
* The current internal display of the Guacamole video encoder.
*
* @param opcode
* The opcode of the instruction being handled.
*
* @param argc
* The number of arguments (excluding opcode) passed to the instruction
* being handled by the callback.
*
* @param argv
* All arguments (excluding opcode) associated with the instruction being
* handled by the callback.
*
* @return
* Zero if the instruction was handled successfully, non-zero if an error
* occurs.
*/
int guacenc_handle_instruction(guacenc_display* display,
const char* opcode, int argc, char** argv);
/**
* Handler for the Guacamole "blob" instruction.
*/
guacenc_instruction_handler guacenc_handle_blob;
/**
* Handler for the Guacamole "img" instruction.
*/
guacenc_instruction_handler guacenc_handle_img;
/**
* Handler for the Guacamole "end" instruction.
*/
guacenc_instruction_handler guacenc_handle_end;
/**
* Handler for the Guacamole "sync" instruction.
*/
guacenc_instruction_handler guacenc_handle_sync;
/**
* Handler for the Guacamole "cursor" instruction.
*/
guacenc_instruction_handler guacenc_handle_cursor;
/**
* Handler for the Guacamole "copy" instruction.
*/
guacenc_instruction_handler guacenc_handle_copy;
/**
* Handler for the Guacamole "transfer" instruction.
*/
guacenc_instruction_handler guacenc_handle_transfer;
/**
* Handler for the Guacamole "size" instruction.
*/
guacenc_instruction_handler guacenc_handle_size;
/**
* Handler for the Guacamole "rect" instruction.
*/
guacenc_instruction_handler guacenc_handle_rect;
/**
* Handler for the Guacamole "cfill" instruction.
*/
guacenc_instruction_handler guacenc_handle_cfill;
/**
* Handler for the Guacamole "move" instruction.
*/
guacenc_instruction_handler guacenc_handle_move;
/**
* Handler for the Guacamole "shade" instruction.
*/
guacenc_instruction_handler guacenc_handle_shade;
/**
* Handler for the Guacamole "dispose" instruction.
*/
guacenc_instruction_handler guacenc_handle_dispose;
#endif

87
src/guacenc/jpeg.c Normal file
View File

@ -0,0 +1,87 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "jpeg.h"
#include "log.h"
#include <stdio.h>
#include <unistd.h>
#include <cairo/cairo.h>
#include <jpeglib.h>
#include <stdlib.h>
cairo_surface_t* guacenc_jpeg_decoder(unsigned char* data, int length) {
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
/* Create decompressor with standard error handling */
jpeg_create_decompress(&cinfo);
cinfo.err = jpeg_std_error(&jerr);
/* Read JPEG directly from memory buffer */
jpeg_mem_src(&cinfo, data, length);
/* Read and validate JPEG header */
if (!jpeg_read_header(&cinfo, TRUE)) {
guacenc_log(GUAC_LOG_WARNING, "Invalid JPEG data");
jpeg_destroy_decompress(&cinfo);
return NULL;
}
/* Begin decompression */
cinfo.out_color_space = JCS_EXT_BGRX;
jpeg_start_decompress(&cinfo);
/* Pull JPEG dimensions from decompressor */
int width = cinfo.output_width;
int height = cinfo.output_height;
/* Create blank Cairo surface (no transparency in JPEG) */
cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
width, height);
/* Pull underlying buffer and its stride */
int stride = cairo_image_surface_get_stride(surface);
unsigned char* row = cairo_image_surface_get_data(surface);
/* Read JPEG into surface */
while (cinfo.output_scanline < height) {
unsigned char* buffers[1] = { row };
jpeg_read_scanlines(&cinfo, buffers, 1);
row += stride;
}
/* End decompression */
jpeg_finish_decompress(&cinfo);
/* Free decompressor */
jpeg_destroy_decompress(&cinfo);
/* JPEG was read successfully */
return surface;
}

35
src/guacenc/jpeg.h Normal file
View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACENC_JPEG_H
#define GUACENC_JPEG_H
#include "config.h"
#include "image-stream.h"
/**
* Decoder implementation which handles "image/jpeg" images.
*/
guacenc_decoder guacenc_jpeg_decoder;
#endif

76
src/guacenc/layer.c Normal file
View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "buffer.h"
#include "layer.h"
#include <stdlib.h>
guacenc_layer* guacenc_layer_alloc() {
/* Allocate new layer */
guacenc_layer* layer = (guacenc_layer*) calloc(1, sizeof(guacenc_layer));
if (layer == NULL)
return NULL;
/* Allocate associated buffer (width, height, and image storage) */
layer->buffer = guacenc_buffer_alloc();
if (layer->buffer == NULL) {
free(layer);
return NULL;
}
/* Allocate buffer for frame rendering */
layer->frame = guacenc_buffer_alloc();
if (layer->frame== NULL) {
guacenc_buffer_free(layer->buffer);
free(layer);
return NULL;
}
/* Layers default to fully opaque */
layer->opacity = 0xFF;
/* Default parented to default layer */
layer->parent_index = 0;
return layer;
}
void guacenc_layer_free(guacenc_layer* layer) {
/* Ignore NULL layers */
if (layer == NULL)
return;
/* Free internal frame buffer */
guacenc_buffer_free(layer->frame);
/* Free underlying buffer */
guacenc_buffer_free(layer->buffer);
free(layer);
}

106
src/guacenc/layer.h Normal file
View File

@ -0,0 +1,106 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACENC_LAYER_H
#define GUACENC_LAYER_H
#include "config.h"
#include "buffer.h"
/**
* The value assigned to the parent_index property of a guacenc_layer if it has
* no parent.
*/
#define GUACENC_LAYER_NO_PARENT -1
/**
* A visible Guacamole layer.
*/
typedef struct guacenc_layer {
/**
* The actual image contents of this layer, as well as this layer's size
* (width and height).
*/
guacenc_buffer* buffer;
/**
* The index of the layer that contains this layer. If this layer is the
* default layer (and thus has no parent), this will be
* GUACENC_LAYER_NO_PARENT.
*/
int parent_index;
/**
* The X coordinate of the upper-left corner of this layer within the
* Guacamole display.
*/
int x;
/**
* The Y coordinate of the upper-left corner of this layer within the
* Guacamole display.
*/
int y;
/**
* The relative stacking order of this layer with respect to other sibling
* layers.
*/
int z;
/**
* The opacity of this layer, where 0 is completely transparent and 255 is
* completely opaque.
*/
int opacity;
/**
* The internal buffer used by to record the state of this layer in the
* previous frame and to render additional frames.
*/
guacenc_buffer* frame;
} guacenc_layer;
/**
* Allocates and initializes a new layer object. This allocation is independent
* of the Guacamole video encoder display; the allocated guacenc_layer will not
* automatically be associated with the active display.
*
* @return
* A newly-allocated and initialized guacenc_layer, or NULL if allocation
* fails.
*/
guacenc_layer* guacenc_layer_alloc();
/**
* Frees all memory associated with the given layer object. If the layer
* provided is NULL, this function has no effect.
*
* @param layer
* The layer to free, which may be NULL.
*/
void guacenc_layer_free(guacenc_layer* layer);
#endif

88
src/guacenc/log.c Normal file
View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "guacenc.h"
#include "log.h"
#include <guacamole/client.h>
#include <guacamole/error.h>
#include <stdarg.h>
#include <stdio.h>
int guacenc_log_level = GUACENC_DEFAULT_LOG_LEVEL;
void vguacenc_log(guac_client_log_level level, const char* format,
va_list args) {
const char* priority_name;
char message[2048];
/* Don't bother if the log level is too high */
if (level > guacenc_log_level)
return;
/* Copy log message into buffer */
vsnprintf(message, sizeof(message), format, args);
/* Convert log level to human-readable name */
switch (level) {
/* Error log level */
case GUAC_LOG_ERROR:
priority_name = "ERROR";
break;
/* Warning log level */
case GUAC_LOG_WARNING:
priority_name = "WARNING";
break;
/* Informational log level */
case GUAC_LOG_INFO:
priority_name = "INFO";
break;
/* Debug log level */
case GUAC_LOG_DEBUG:
priority_name = "DEBUG";
break;
/* Any unknown/undefined log level */
default:
priority_name = "UNKNOWN";
break;
}
/* Log to STDERR */
fprintf(stderr, GUACENC_LOG_NAME ": %s: %s\n", priority_name, message);
}
void guacenc_log(guac_client_log_level level, const char* format, ...) {
va_list args;
va_start(args, format);
vguacenc_log(level, format, args);
va_end(args);
}

76
src/guacenc/log.h Normal file
View File

@ -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 <guacamole/client.h>
#include <stdarg.h>
/**
* 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

62
src/guacenc/man/guacenc.1 Normal file
View File

@ -0,0 +1,62 @@
.TH guacenc 1 "12 Mar 2016" "version 0.9.9" "Guacamole"
.
.SH NAME
guacenc \- Guacamole video encoder
.
.SH SYNOPSIS
.B guacenc
[\fB-s\fR \fIWIDTH\fRx\fIHEIGHT\fR]
[\fB-r\fR \fIBITRATE\fR]
[\fB-f\fR]
[\fIFILE\fR]...
.
.SH DESCRIPTION
.B guacenc
is a video encoder which accepts Guacamole protocol dumps, such as those saved
when screen recording is enabled on a Guacamole connection, writing standard
video files as output.
.B guacenc
is essentially an implementation of a Guacamole client which accepts
its input from files instead of a network connection, and renders directly to
video instead of to the user's screen.
.P
Each \fIFILE\fR specified will be encoded as a raw MPEG-4 video stream to a new
file named \fIFILE\fR.m4v, encoded according to the other options specified. By
default, the output video will be \fI640\fRx\fI480\fR pixels, and will be saved
with a bitrate of \fI2000000\fR bits per second (2 Mbps). These defaults can be
overridden with the \fB-s\fR and \fB-r\fR options respectively. Existing files
will not be overwritten; the encoding process for any input file will be
aborted if it would result in overwriting an existing file.
.P
Guacamole acquires a write lock on recordings as they are being written. By
default,
.B guacenc
will check whether the each input file is locked and will refuse to read and
encode an input file if it appears to be an in-progress recording. This
behavior can be overridden by specifying the \fB-f\fR option. Encoding an
in-progress recording will still result in a valid video; the video will simply
cover the user's session only up to the current point in time.
.
.SH OPTIONS
.TP
\fB-s\fR \fIWIDTH\fRx\fIHEIGHT\fR
Changes the resolution of the video that
.B guacenc
renders. By default, this will be \fI640\fRx\fI480\fR.
.TP
\fB-r\fR \fIBITRATE\fR
Changes the bitrate that
.B guacenc
will use for the saved video. This is specified in bits per second. By default,
this will be \fI2000000\fR (2 Mbps). Higher values will result in larger but
higher-quality video files. Lower values will result in smaller but
lower-quality video files.
.TP
\fB-f\fR
Overrides the default behavior of
.B guacenc
such that input files will be encoded even if they appear to be recordings of
in-progress Guacamole sessions.
.
.SH AUTHOR
Written by Michael Jumper <mike.jumper@guac-dev.org>

72
src/guacenc/parse.c Normal file
View File

@ -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 <errno.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
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;
}

71
src/guacenc/parse.h Normal file
View File

@ -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

112
src/guacenc/png.c Normal file
View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "log.h"
#include "png.h"
#include <cairo/cairo.h>
#include <stdlib.h>
#include <string.h>
/**
* 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;
}

35
src/guacenc/png.h Normal file
View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACENC_PNG_H
#define GUACENC_PNG_H
#include "config.h"
#include "image-stream.h"
/**
* Decoder implementation which handles "image/png" images.
*/
guacenc_decoder guacenc_png_decoder;
#endif

489
src/guacenc/video.c Normal file
View File

@ -0,0 +1,489 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "buffer.h"
#include "ffmpeg-compat.h"
#include "log.h"
#include "video.h"
#include <cairo/cairo.h>
#include <libavcodec/avcodec.h>
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <guacamole/client.h>
#include <guacamole/timestamp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
int width, int height, int bitrate) {
/* Pull codec based on name */
AVCodec* codec = avcodec_find_encoder_by_name(codec_name);
if (codec == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Failed to locate codec \"%s\".",
codec_name);
goto fail_codec;
}
/* Retrieve encoding context */
AVCodecContext* context = avcodec_alloc_context3(codec);
if (context == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Failed to allocate context for "
"codec \"%s\".", codec_name);
goto fail_context;
}
/* Init context with encoding parameters */
context->bit_rate = bitrate;
context->width = width;
context->height = height;
context->time_base = (AVRational) { 1, GUACENC_VIDEO_FRAMERATE };
context->gop_size = 10;
context->max_b_frames = 1;
context->pix_fmt = AV_PIX_FMT_YUV420P;
/* Open codec for use */
if (avcodec_open2(context, codec, NULL) < 0) {
guacenc_log(GUAC_LOG_ERROR, "Failed to open codec \"%s\".", codec_name);
goto fail_codec_open;
}
/* Allocate corresponding frame */
AVFrame* frame = av_frame_alloc();
if (frame == NULL) {
goto fail_frame;
}
/* Copy necessary data for frame from context */
frame->format = context->pix_fmt;
frame->width = context->width;
frame->height = context->height;
/* Allocate actual backing data for frame */
if (av_image_alloc(frame->data, frame->linesize, frame->width,
frame->height, frame->format, 32) < 0) {
goto fail_frame_data;
}
/* Open output file */
int fd = open(path, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
if (fd == -1) {
guacenc_log(GUAC_LOG_ERROR, "Failed to open output file \"%s\": %s",
path, strerror(errno));
goto fail_output_fd;
}
/* Create stream for output file */
FILE* output = fdopen(fd, "wb");
if (output == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Failed to allocate stream for output "
"file \"%s\": %s", path, strerror(errno));
goto fail_output_file;
}
/* Allocate video structure */
guacenc_video* video = malloc(sizeof(guacenc_video));
if (video == NULL) {
goto fail_video;
}
/* Init properties of video */
video->output = output;
video->context = context;
video->next_frame = frame;
video->width = width;
video->height = height;
video->bitrate = bitrate;
/* No frames have been written or prepared yet */
video->last_timestamp = 0;
video->next_pts = 0;
return video;
/* Free all allocated data in case of failure */
fail_video:
fclose(output);
fail_output_file:
close(fd);
fail_output_fd:
av_freep(&frame->data[0]);
fail_frame_data:
av_frame_free(&frame);
fail_frame:
fail_codec_open:
avcodec_free_context(&context);
fail_context:
fail_codec:
return NULL;
}
/**
* Flushes the specied frame as a new frame of video, updating the internal
* video timestamp by one frame's worth of time. The pts member of the given
* frame structure will be updated with the current presentation timestamp of
* the video. If pending frames of the video are being flushed, the given frame
* may be NULL (as required by avcodec_encode_video2()).
*
* @param video
* The video to write the given frame to.
*
* @param frame
* The frame to write to the video, or NULL if previously-written frames
* are being flushed.
*
* @return
* A positive value if the frame was successfully written, zero if the
* frame has been saved for later writing / reordering, negative if an
* error occurs.
*/
static int guacenc_video_write_frame(guacenc_video* video, AVFrame* frame) {
/* Init video packet */
AVPacket packet;
av_init_packet(&packet);
/* Request that encoder allocate data for packet */
packet.data = NULL;
packet.size = 0;
/* Set timestamp of frame, if frame given */
if (frame != NULL)
frame->pts = video->next_pts;
/* Write frame to video */
int got_data;
if (avcodec_encode_video2(video->context, &packet, frame, &got_data) < 0) {
guacenc_log(GUAC_LOG_WARNING, "Error encoding frame #%" PRId64,
video->next_pts);
return -1;
}
/* Write corresponding data to file */
if (got_data) {
/* Write data, logging any errors */
if (fwrite(packet.data, 1, packet.size, video->output) == 0) {
guacenc_log(GUAC_LOG_ERROR, "Unable to write frame "
"#%" PRId64 ": %s", video->next_pts, strerror(errno));
return -1;
}
/* Data was written successfully */
guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": wrote %i bytes",
video->next_pts, packet.size);
av_packet_unref(&packet);
}
/* Frame may have been queued for later writing / reordering */
else
guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": queued for later",
video->next_pts);
/* Update presentation timestamp for next frame */
video->next_pts++;
/* Write was successful */
return got_data;
}
/**
* Flushes the frame previously specified by guacenc_video_prepare_frame() as a
* new frame of video, updating the internal video timestamp by one frame's
* worth of time.
*
* @param video
* The video to flush.
*
* @return
* Zero if flushing was successful, non-zero if an error occurs.
*/
static int guacenc_video_flush_frame(guacenc_video* video) {
/* Write frame to video */
return guacenc_video_write_frame(video, video->next_frame) < 0;
}
int guacenc_video_advance_timeline(guacenc_video* video,
guac_timestamp timestamp) {
/* Flush frames as necessary if previously updated */
if (video->last_timestamp != 0) {
/* Calculate the number of frames that should have been written */
int elapsed = (timestamp - video->last_timestamp)
* GUACENC_VIDEO_FRAMERATE / 1000;
/* Keep previous timestamp if insufficient time has elapsed */
if (elapsed == 0)
return 0;
/* Flush frames to bring timeline in sync, duplicating if necessary */
do {
guacenc_video_flush_frame(video);
} while (--elapsed != 0);
}
/* Update timestamp */
video->last_timestamp = timestamp;
return 0;
}
/**
* Converts the given Guacamole video encoder buffer to a frame in the format
* required by libavcodec / libswscale. Black margins of the specified sizes
* will be added. No scaling is performed; the image data is copied verbatim.
*
* @param buffer
* The guacenc_buffer to copy as a new AVFrame.
*
* @param lsize
* The size of the letterboxes to add, in pixels. Letterboxes are the
* horizontal black boxes added to images which are scaled down to fit the
* destination because they are too wide (the width is scaled to exactly
* fit the destination, resulting in extra space at the top and bottom).
*
* @param psize
* The size of the pillarboxes to add, in pixels. Pillarboxes are the
* vertical black boxes added to images which are scaled down to fit the
* destination because they are too tall (the height is scaled to exactly
* fit the destination, resulting in extra space on the sides).
*
* @return
* A pointer to a newly-allocated AVFrame containing exactly the same image
* data as the given buffer. The image data within the frame and the frame
* itself must be manually freed later.
*/
static AVFrame* guacenc_video_frame_convert(guacenc_buffer* buffer, int lsize,
int psize) {
/* Init size of left/right pillarboxes */
int left = psize;
int right = psize;
/* Init size of top/bottom letterboxes */
int top = lsize;
int bottom = lsize;
/* Prepare source frame for buffer */
AVFrame* frame = av_frame_alloc();
if (frame == NULL)
return NULL;
/* Copy buffer properties to frame */
frame->format = AV_PIX_FMT_RGB32;
frame->width = buffer->width + left + right;
frame->height = buffer->height + top + bottom;
/* Allocate actual backing data for frame */
if (av_image_alloc(frame->data, frame->linesize, frame->width,
frame->height, frame->format, 32) < 0) {
av_frame_free(&frame);
return NULL;
}
/* Flush any pending operations */
cairo_surface_flush(buffer->surface);
/* Get pointer to source image data */
unsigned char* src_data = buffer->image;
int src_stride = buffer->stride;
/* Get pointer to destination image data */
unsigned char* dst_data = frame->data[0];
int dst_stride = frame->linesize[0];
/* Get source/destination dimensions */
int width = buffer->width;
int height = buffer->height;
/* Source buffer is guaranteed to fit within destination buffer */
assert(width <= frame->width);
assert(height <= frame->height);
/* Add top margin */
while (top > 0) {
memset(dst_data, 0, frame->width * 4);
dst_data += dst_stride;
top--;
}
/* Copy all data from source buffer to destination frame */
while (height > 0) {
/* Calculate size of margin and data regions */
int left_size = left * 4;
int data_size = width * 4;
int right_size = right * 4;
/* Add left margin */
memset(dst_data, 0, left_size);
/* Copy data */
memcpy(dst_data + left_size, src_data, data_size);
/* Add right margin */
memset(dst_data + left_size + data_size, 0, right_size);
dst_data += dst_stride;
src_data += src_stride;
height--;
}
/* Add bottom margin */
while (bottom > 0) {
memset(dst_data, 0, frame->width * 4);
dst_data += dst_stride;
bottom--;
}
/* Frame converted */
return frame;
}
void guacenc_video_prepare_frame(guacenc_video* video, guacenc_buffer* buffer) {
int lsize;
int psize;
/* Ignore NULL buffers */
if (buffer == NULL || buffer->surface == NULL)
return;
/* Obtain destination frame */
AVFrame* dst = video->next_frame;
/* Determine width of image if height is scaled to match destination */
int scaled_width = buffer->width * dst->height / buffer->height;
/* Determine height of image if width is scaled to match destination */
int scaled_height = buffer->height * dst->width / buffer->width;
/* If height-based scaling results in a fit width, add pillarboxes */
if (scaled_width <= dst->width) {
lsize = 0;
psize = (dst->width - scaled_width)
* buffer->height / dst->height / 2;
}
/* If width-based scaling results in a fit width, add letterboxes */
else {
assert(scaled_height <= dst->height);
psize = 0;
lsize = (dst->height - scaled_height)
* buffer->width / dst->width / 2;
}
/* Prepare source frame for buffer */
AVFrame* src = guacenc_video_frame_convert(buffer, lsize, psize);
if (src == NULL) {
guacenc_log(GUAC_LOG_WARNING, "Failed to allocate source frame. "
"Frame dropped.");
return;
}
/* Prepare scaling context */
struct SwsContext* sws = sws_getContext(src->width, src->height,
PIX_FMT_RGB32, dst->width, dst->height, PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);
/* Abort if scaling context could not be created */
if (sws == NULL) {
guacenc_log(GUAC_LOG_WARNING, "Failed to allocate software scaling "
"context. Frame dropped.");
av_freep(&src->data[0]);
av_frame_free(&src);
return;
}
/* Apply scaling, copying the source frame to the destination */
sws_scale(sws, (const uint8_t* const*) src->data, src->linesize,
0, src->height, dst->data, dst->linesize);
/* Free scaling context */
sws_freeContext(sws);
/* Free source frame */
av_freep(&src->data[0]);
av_frame_free(&src);
}
int guacenc_video_free(guacenc_video* video) {
/* Ignore NULL video */
if (video == NULL)
return 0;
/* Write final frame */
guacenc_video_flush_frame(video);
/* Init video packet for final flush of encoded data */
AVPacket packet;
av_init_packet(&packet);
/* Flush any unwritten frames */
int retval;
do {
retval = guacenc_video_write_frame(video, NULL);
} while (retval > 0);
/* File is now completely written */
fclose(video->output);
/* Free frame encoding data */
av_freep(&video->next_frame->data[0]);
av_frame_free(&video->next_frame);
/* Clean up encoding context */
avcodec_close(video->context);
avcodec_free_context(&(video->context));
free(video);
return 0;
}

181
src/guacenc/video.h Normal file
View File

@ -0,0 +1,181 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACENC_VIDEO_H
#define GUACENC_VIDEO_H
#include "config.h"
#include "buffer.h"
#include <guacamole/timestamp.h>
#include <libavcodec/avcodec.h>
#include <stdint.h>
#include <stdio.h>
/**
* The framerate at which video should be encoded, in frames per second.
*/
#define GUACENC_VIDEO_FRAMERATE 25
/**
* A video which is actively being encoded. Frames can be added to the video
* as they are generated, along with their associated timestamps, and the
* corresponding video will be continuously written as it is encoded.
*/
typedef struct guacenc_video {
/**
* Output file stream.
*/
FILE* output;
/**
* The open encoding context from libavcodec, created for the codec
* specified when this guacenc_video was created.
*/
AVCodecContext* context;
/**
* The width of the video, in pixels.
*/
int width;
/**
* The height of the video, in pixels.
*/
int height;
/**
* The desired output bitrate of the video, in bits per second.
*/
int bitrate;
/**
* An image data area containing the next frame to be written, encoded as
* YCbCr image data in the format required by avcodec_encode_video2(), for
* use and re-use as frames are rendered.
*/
AVFrame* next_frame;
/**
* The presentation timestamp that should be used for the next frame. This
* is equivalent to the frame number.
*/
int64_t next_pts;
/**
* The timestamp associated with the last frame, or 0 if no frames have yet
* been added.
*/
guac_timestamp last_timestamp;
} guacenc_video;
/**
* Allocates a new guacenc_video which encodes video according to the given
* specifications, saving the output in the given file. If the output file
* already exists, encoding will be aborted, and the original file contents
* will be preserved. Frames will be scaled up or down as necessary to fit the
* given width and height.
*
* @param path
* The full path to the file in which encoded video should be written.
*
* @param codec_name
* The name of the codec to use for the video encoding, as defined by
* ffmpeg / libavcodec.
*
* @param width
* The width of the desired video, in pixels.
*
* @param height
* The height of the desired video, in pixels.
*
* @param bitrate
* The desired overall bitrate of the resulting encoded video, in bits per
* second.
*/
guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
int width, int height, int bitrate);
/**
* Advances the timeline of the encoding process to the given timestamp, such
* that frames added via guacenc_video_prepare_frame() will be encoded at the
* proper frame boundaries within the video. Duplicate frames will be encoded
* as necessary to ensure that the output is correctly timed with respect to
* the given timestamp. This is particularly important as Guacamole does not
* have a framerate per se, and the time between each Guacamole "frame" will
* vary significantly.
*
* This function MUST be called prior to invoking guacenc_video_prepare_frame()
* to ensure the prepared frame will be encoded at the correct point in time.
*
* @param video
* The video whose timeline should be adjusted.
*
* @param timestamp
* The Guacamole timestamp denoting the point in time that the video
* timeline should be advanced to, as dictated by a parsed "sync"
* instruction.
*
* @return
* Zero if the timeline was adjusted successfully, non-zero if an error
* occurs (such as during the encoding of duplicate frames).
*/
int guacenc_video_advance_timeline(guacenc_video* video,
guac_timestamp timestamp);
/**
* Stores the given buffer within the given video structure such that it will
* be written if it falls within proper frame boundaries. If the timeline of
* the video (as dictated by guacenc_video_advance_timeline()) is not at a
* frame boundary with respect to the video framerate (it occurs between frame
* boundaries), the prepared frame will only be written if another frame is not
* prepared within the same pair of frame boundaries). The prepared frame will
* not be written until it is implicitly flushed through updates to the video
* timeline or through reaching the end of the encoding process
* (guacenc_video_free()).
*
* @param video
* The video in which the given buffer should be queued for possible
* writing (depending on timing vs. video framerate).
*
* @param buffer
* The guacenc_buffer representing the image data of the frame that should
* be queued.
*/
void guacenc_video_prepare_frame(guacenc_video* video, guacenc_buffer* buffer);
/**
* Frees all resources associated with the given video, finalizing the encoding
* process. Any buffered frames which have not yet been written will be written
* at this point.
*
* @return
* Zero if the video was successfully written and freed, non-zero if the
* video could not be written due to an error.
*/
int guacenc_video_free(guacenc_video* video);
#endif

77
src/guacenc/webp.c Normal file
View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "log.h"
#include "webp.h"
#include <cairo/cairo.h>
#include <guacamole/client.h>
#include <webp/decode.h>
#include <stdint.h>
#include <stdlib.h>
cairo_surface_t* guacenc_webp_decoder(unsigned char* data, int length) {
int width, height;
/* Validate WebP and pull dimensions */
if (!WebPGetInfo((uint8_t*) data, length, &width, &height)) {
guacenc_log(GUAC_LOG_WARNING, "Invalid WebP data");
return NULL;
}
/* Create blank Cairo surface */
cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
width, height);
/* Fill surface with opaque black */
cairo_t* cairo = cairo_create(surface);
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgba(cairo, 0.0, 0.0, 0.0, 1.0);
cairo_paint(cairo);
cairo_destroy(cairo);
/* Finish any pending draws */
cairo_surface_flush(surface);
/* Pull underlying buffer and its stride */
int stride = cairo_image_surface_get_stride(surface);
unsigned char* image = cairo_image_surface_get_data(surface);
/* Read WebP into surface */
uint8_t* result = WebPDecodeBGRAInto((uint8_t*) data, length,
(uint8_t*) image, stride * height, stride);
/* Verify WebP was successfully decoded */
if (result == NULL) {
guacenc_log(GUAC_LOG_WARNING, "Invalid WebP data");
cairo_surface_destroy(surface);
return NULL;
}
/* WebP was read successfully */
return surface;
}

35
src/guacenc/webp.h Normal file
View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACENC_WEBP_H
#define GUACENC_WEBP_H
#include "config.h"
#include "image-stream.h"
/**
* Decoder implementation which handles "image/webp" images.
*/
guacenc_decoder guacenc_webp_decoder;
#endif

View File

@ -89,6 +89,7 @@ libguac_la_SOURCES = \
socket.c \
socket-fd.c \
socket-nest.c \
socket-tee.c \
timestamp.c \
unicode.c \
user.c \

View File

@ -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);

View File

@ -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

234
src/libguac/socket-tee.c Normal file
View File

@ -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 <stdlib.h>
/**
* 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;
}

View File

@ -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,

View File

@ -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);

View File

@ -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;
/**

View File

@ -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);

View File

@ -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;
/**

View File

@ -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,

View File

@ -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);

View File

@ -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;
/**

View File

@ -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,

View File

@ -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 */

View File

@ -28,6 +28,11 @@
#include <stdbool.h>
/**
* 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;
/**

View File

@ -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);