Compare commits
6 Commits
master
...
tutorial/p
Author | SHA1 | Date | |
---|---|---|---|
|
0acb1e30ca | ||
|
4a6d83d55c | ||
|
8436f2e22e | ||
|
f9aaa78768 | ||
|
428ecf2732 | ||
|
6bca037e01 |
@ -56,5 +56,5 @@ tests/test_*
|
||||
!tests/test_*.[ch]
|
||||
|
||||
# Generated docs
|
||||
doc/*/doxygen-output
|
||||
doc/doxygen-output
|
||||
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -44,7 +44,7 @@ configure
|
||||
stamp-h1
|
||||
|
||||
# Generated docs
|
||||
doc/*/doxygen-output
|
||||
doc/doxygen-output
|
||||
|
||||
# IDE metadata
|
||||
nbproject/
|
||||
|
206
Dockerfile
206
Dockerfile
@ -21,32 +21,25 @@
|
||||
# Dockerfile for guacamole-server
|
||||
#
|
||||
|
||||
# The Alpine Linux image that should be used as the basis for the guacd image
|
||||
ARG ALPINE_BASE_IMAGE=latest
|
||||
FROM alpine:${ALPINE_BASE_IMAGE} AS builder
|
||||
# The Debian image that should be used as the basis for the guacd image
|
||||
ARG DEBIAN_BASE_IMAGE=buster-slim
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache \
|
||||
autoconf \
|
||||
automake \
|
||||
build-base \
|
||||
cairo-dev \
|
||||
cmake \
|
||||
git \
|
||||
grep \
|
||||
libjpeg-turbo-dev \
|
||||
libpng-dev \
|
||||
libtool \
|
||||
libwebp-dev \
|
||||
make \
|
||||
openssl-dev \
|
||||
pango-dev \
|
||||
pulseaudio-dev \
|
||||
util-linux-dev
|
||||
# Use Debian as base for the build
|
||||
FROM debian:${DEBIAN_BASE_IMAGE} AS builder
|
||||
|
||||
# Copy source to container for sake of build
|
||||
ARG BUILD_DIR=/tmp/guacamole-server
|
||||
COPY . ${BUILD_DIR}
|
||||
#
|
||||
# The Debian repository that should be preferred for dependencies (this will be
|
||||
# added to /etc/apt/sources.list if not already present)
|
||||
#
|
||||
# NOTE: Due to limitations of the Docker image build process, this value is
|
||||
# duplicated in an ARG in the second stage of the build.
|
||||
#
|
||||
ARG DEBIAN_RELEASE=buster-backports
|
||||
|
||||
# Add repository for specified Debian release if not already present in
|
||||
# sources.list
|
||||
RUN grep " ${DEBIAN_RELEASE} " /etc/apt/sources.list || echo >> /etc/apt/sources.list \
|
||||
"deb http://deb.debian.org/debian ${DEBIAN_RELEASE} main contrib non-free"
|
||||
|
||||
#
|
||||
# Base directory for installed build artifacts.
|
||||
@ -54,99 +47,69 @@ COPY . ${BUILD_DIR}
|
||||
# NOTE: Due to limitations of the Docker image build process, this value is
|
||||
# duplicated in an ARG in the second stage of the build.
|
||||
#
|
||||
ARG PREFIX_DIR=/opt/guacamole
|
||||
ARG PREFIX_DIR=/usr/local/guacamole
|
||||
|
||||
#
|
||||
# Automatically select the latest versions of each core protocol support
|
||||
# library (these can be overridden at build time if a specific version is
|
||||
# needed)
|
||||
#
|
||||
ARG WITH_FREERDP='2(\.\d+)+'
|
||||
ARG WITH_LIBSSH2='libssh2-\d+(\.\d+)+'
|
||||
ARG WITH_LIBTELNET='\d+(\.\d+)+'
|
||||
ARG WITH_LIBVNCCLIENT='LibVNCServer-\d+(\.\d+)+'
|
||||
ARG WITH_LIBWEBSOCKETS='v\d+(\.\d+)+'
|
||||
# Build arguments
|
||||
ARG BUILD_DIR=/tmp/guacd-docker-BUILD
|
||||
ARG BUILD_DEPENDENCIES=" \
|
||||
autoconf \
|
||||
automake \
|
||||
freerdp2-dev \
|
||||
gcc \
|
||||
libcairo2-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libossp-uuid-dev \
|
||||
libpango1.0-dev \
|
||||
libpulse-dev \
|
||||
libssh2-1-dev \
|
||||
libssl-dev \
|
||||
libtelnet-dev \
|
||||
libtool \
|
||||
libvncserver-dev \
|
||||
libwebsockets-dev \
|
||||
libwebp-dev \
|
||||
make"
|
||||
|
||||
#
|
||||
# Default build options for each core protocol support library, as well as
|
||||
# guacamole-server itself (these can be overridden at build time if different
|
||||
# options are needed)
|
||||
#
|
||||
# Do not require interaction during build
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ARG FREERDP_OPTS="\
|
||||
-DBUILTIN_CHANNELS=OFF \
|
||||
-DCHANNEL_URBDRC=OFF \
|
||||
-DWITH_ALSA=OFF \
|
||||
-DWITH_CAIRO=ON \
|
||||
-DWITH_CHANNELS=ON \
|
||||
-DWITH_CLIENT=ON \
|
||||
-DWITH_CUPS=OFF \
|
||||
-DWITH_DIRECTFB=OFF \
|
||||
-DWITH_FFMPEG=OFF \
|
||||
-DWITH_GSM=OFF \
|
||||
-DWITH_GSSAPI=OFF \
|
||||
-DWITH_IPP=OFF \
|
||||
-DWITH_JPEG=ON \
|
||||
-DWITH_LIBSYSTEMD=OFF \
|
||||
-DWITH_MANPAGES=OFF \
|
||||
-DWITH_OPENH264=OFF \
|
||||
-DWITH_OPENSSL=ON \
|
||||
-DWITH_OSS=OFF \
|
||||
-DWITH_PCSC=OFF \
|
||||
-DWITH_PULSE=OFF \
|
||||
-DWITH_SERVER=OFF \
|
||||
-DWITH_SERVER_INTERFACE=OFF \
|
||||
-DWITH_SHADOW_MAC=OFF \
|
||||
-DWITH_SHADOW_X11=OFF \
|
||||
-DWITH_SSE2=ON \
|
||||
-DWITH_WAYLAND=OFF \
|
||||
-DWITH_X11=OFF \
|
||||
-DWITH_X264=OFF \
|
||||
-DWITH_XCURSOR=ON \
|
||||
-DWITH_XEXT=ON \
|
||||
-DWITH_XI=OFF \
|
||||
-DWITH_XINERAMA=OFF \
|
||||
-DWITH_XKBFILE=ON \
|
||||
-DWITH_XRENDER=OFF \
|
||||
-DWITH_XTEST=OFF \
|
||||
-DWITH_XV=OFF \
|
||||
-DWITH_ZLIB=ON"
|
||||
# Bring build environment up to date and install build dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -t ${DEBIAN_RELEASE} -y $BUILD_DEPENDENCIES && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG GUACAMOLE_SERVER_OPTS="\
|
||||
--disable-guaclog"
|
||||
# Add configuration scripts
|
||||
COPY src/guacd-docker/bin "${PREFIX_DIR}/bin/"
|
||||
|
||||
ARG LIBSSH2_OPTS="\
|
||||
-DBUILD_EXAMPLES=OFF \
|
||||
-DBUILD_SHARED_LIBS=ON"
|
||||
# Copy source to container for sake of build
|
||||
COPY . "$BUILD_DIR"
|
||||
|
||||
ARG LIBTELNET_OPTS="\
|
||||
--disable-static \
|
||||
--disable-util"
|
||||
|
||||
ARG LIBVNCCLIENT_OPTS=""
|
||||
|
||||
ARG LIBWEBSOCKETS_OPTS="\
|
||||
-DDISABLE_WERROR=ON \
|
||||
-DLWS_WITHOUT_SERVER=ON \
|
||||
-DLWS_WITHOUT_TESTAPPS=ON \
|
||||
-DLWS_WITHOUT_TEST_CLIENT=ON \
|
||||
-DLWS_WITHOUT_TEST_PING=ON \
|
||||
-DLWS_WITHOUT_TEST_SERVER=ON \
|
||||
-DLWS_WITHOUT_TEST_SERVER_EXTPOLL=ON \
|
||||
-DLWS_WITH_STATIC=OFF"
|
||||
|
||||
# Build guacamole-server and its core protocol library dependencies
|
||||
RUN ${BUILD_DIR}/src/guacd-docker/bin/build-all.sh
|
||||
# Build guacamole-server from local source
|
||||
RUN ${PREFIX_DIR}/bin/build-guacd.sh "$BUILD_DIR" "$PREFIX_DIR"
|
||||
|
||||
# Record the packages of all runtime library dependencies
|
||||
RUN ${BUILD_DIR}/src/guacd-docker/bin/list-dependencies.sh \
|
||||
RUN ${PREFIX_DIR}/bin/list-dependencies.sh \
|
||||
${PREFIX_DIR}/sbin/guacd \
|
||||
${PREFIX_DIR}/lib/libguac-client-*.so \
|
||||
${PREFIX_DIR}/lib/freerdp2/*guac*.so \
|
||||
> ${PREFIX_DIR}/DEPENDENCIES
|
||||
|
||||
# Use same Alpine version as the base for the runtime image
|
||||
FROM alpine:${ALPINE_BASE_IMAGE}
|
||||
# Use same Debian as the base for the runtime image
|
||||
FROM debian:${DEBIAN_BASE_IMAGE}
|
||||
|
||||
#
|
||||
# The Debian repository that should be preferred for dependencies (this will be
|
||||
# added to /etc/apt/sources.list if not already present)
|
||||
#
|
||||
# NOTE: Due to limitations of the Docker image build process, this value is
|
||||
# duplicated in an ARG in the first stage of the build.
|
||||
#
|
||||
ARG DEBIAN_RELEASE=buster-backports
|
||||
|
||||
# Add repository for specified Debian release if not already present in
|
||||
# sources.list
|
||||
RUN grep " ${DEBIAN_RELEASE} " /etc/apt/sources.list || echo >> /etc/apt/sources.list \
|
||||
"deb http://deb.debian.org/debian ${DEBIAN_RELEASE} main contrib non-free"
|
||||
|
||||
#
|
||||
# Base directory for installed build artifacts. See also the
|
||||
@ -155,27 +118,36 @@ FROM alpine:${ALPINE_BASE_IMAGE}
|
||||
# NOTE: Due to limitations of the Docker image build process, this value is
|
||||
# duplicated in an ARG in the first stage of the build.
|
||||
#
|
||||
ARG PREFIX_DIR=/opt/guacamole
|
||||
ARG PREFIX_DIR=/usr/local/guacamole
|
||||
|
||||
# Runtime environment
|
||||
ENV LC_ALL=C.UTF-8
|
||||
ENV LD_LIBRARY_PATH=${PREFIX_DIR}/lib
|
||||
ENV GUACD_LOG_LEVEL=info
|
||||
|
||||
ARG RUNTIME_DEPENDENCIES=" \
|
||||
netcat-openbsd \
|
||||
ca-certificates \
|
||||
ghostscript \
|
||||
fonts-liberation \
|
||||
fonts-dejavu \
|
||||
xfonts-terminus"
|
||||
|
||||
# Do not require interaction during build
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Copy build artifacts into this stage
|
||||
COPY --from=builder ${PREFIX_DIR} ${PREFIX_DIR}
|
||||
|
||||
# Bring runtime environment up to date and install runtime dependencies
|
||||
RUN apk add --no-cache \
|
||||
ca-certificates \
|
||||
ghostscript \
|
||||
netcat-openbsd \
|
||||
shadow \
|
||||
terminus-font \
|
||||
ttf-dejavu \
|
||||
ttf-liberation \
|
||||
util-linux-login && \
|
||||
xargs apk add --no-cache < ${PREFIX_DIR}/DEPENDENCIES
|
||||
RUN apt-get update && \
|
||||
apt-get install -t ${DEBIAN_RELEASE} -y --no-install-recommends $RUNTIME_DEPENDENCIES && \
|
||||
apt-get install -t ${DEBIAN_RELEASE} -y --no-install-recommends $(cat "${PREFIX_DIR}"/DEPENDENCIES) && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Link FreeRDP plugins into proper path
|
||||
RUN ${PREFIX_DIR}/bin/link-freerdp-plugins.sh \
|
||||
${PREFIX_DIR}/lib/freerdp2/libguac*.so
|
||||
|
||||
# Checks the operating status every 5 minutes with a timeout of 5 seconds
|
||||
HEALTHCHECK --interval=5m --timeout=5s CMD nc -z 127.0.0.1 4822 || exit 1
|
||||
@ -184,7 +156,7 @@ HEALTHCHECK --interval=5m --timeout=5s CMD nc -z 127.0.0.1 4822 || exit 1
|
||||
ARG UID=1000
|
||||
ARG GID=1000
|
||||
RUN groupadd --gid $GID guacd
|
||||
RUN useradd --system --create-home --shell /sbin/nologin --uid $UID --gid $GID guacd
|
||||
RUN useradd --system --create-home --shell /usr/sbin/nologin --uid $UID --gid $GID guacd
|
||||
|
||||
# Run with user guacd
|
||||
USER guacd
|
||||
@ -197,5 +169,5 @@ EXPOSE 4822
|
||||
# Note the path here MUST correspond to the value specified in the
|
||||
# PREFIX_DIR build argument.
|
||||
#
|
||||
CMD /opt/guacamole/sbin/guacd -b 0.0.0.0 -L $GUACD_LOG_LEVEL -f
|
||||
CMD /usr/local/guacamole/sbin/guacd -b 0.0.0.0 -L $GUACD_LOG_LEVEL -f
|
||||
|
||||
|
27
Makefile.am
27
Makefile.am
@ -35,15 +35,17 @@ DIST_SUBDIRS = \
|
||||
src/guacenc \
|
||||
src/guaclog \
|
||||
src/pulse \
|
||||
src/protocols/ball \
|
||||
src/protocols/kubernetes \
|
||||
src/protocols/rdp \
|
||||
src/protocols/ssh \
|
||||
src/protocols/telnet \
|
||||
src/protocols/vnc
|
||||
|
||||
SUBDIRS = \
|
||||
src/libguac \
|
||||
src/common
|
||||
SUBDIRS = \
|
||||
src/libguac \
|
||||
src/common \
|
||||
src/protocols/ball
|
||||
|
||||
if ENABLE_COMMON_SSH
|
||||
SUBDIRS += src/common-ssh
|
||||
@ -89,15 +91,14 @@ if ENABLE_GUACLOG
|
||||
SUBDIRS += src/guaclog
|
||||
endif
|
||||
|
||||
EXTRA_DIST = \
|
||||
.dockerignore \
|
||||
CONTRIBUTING \
|
||||
Dockerfile \
|
||||
LICENSE \
|
||||
NOTICE \
|
||||
bin/guacctl \
|
||||
doc/libguac/Doxyfile.in \
|
||||
doc/libguac-terminal/Doxyfile.in \
|
||||
src/guacd-docker \
|
||||
EXTRA_DIST = \
|
||||
.dockerignore \
|
||||
CONTRIBUTING \
|
||||
Dockerfile \
|
||||
LICENSE \
|
||||
NOTICE \
|
||||
bin/guacctl \
|
||||
doc/Doxyfile.in \
|
||||
src/guacd-docker \
|
||||
util/generate-test-runner.pl
|
||||
|
||||
|
@ -117,7 +117,7 @@ error() {
|
||||
##
|
||||
usage() {
|
||||
cat >&2 <<END
|
||||
guacctl 1.5.0, Apache Guacamole terminal session control utility.
|
||||
guacctl 1.3.0, Apache Guacamole terminal session control utility.
|
||||
Usage: guacctl [OPTION] [FILE or NAME]...
|
||||
|
||||
-d, --download download each of the files listed.
|
||||
|
130
configure.ac
130
configure.ac
@ -18,7 +18,7 @@
|
||||
#
|
||||
|
||||
AC_PREREQ([2.61])
|
||||
AC_INIT([guacamole-server], [1.5.0])
|
||||
AC_INIT([guacamole-server], [1.3.0])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
|
||||
AM_SILENT_RULES([yes])
|
||||
@ -75,50 +75,21 @@ AC_CHECK_LIB([dl], [dlopen],
|
||||
AC_MSG_ERROR("libdl is required on systems which do not otherwise provide dlopen()"),
|
||||
[#include <dlfcn.h>])])
|
||||
|
||||
#
|
||||
# libuuid
|
||||
#
|
||||
# OSSP UUID
|
||||
AC_CHECK_LIB([ossp-uuid], [uuid_make], [UUID_LIBS=-lossp-uuid],
|
||||
AC_CHECK_LIB([uuid], [uuid_make], [UUID_LIBS=-luuid],
|
||||
AC_MSG_ERROR("The OSSP UUID library is required")))
|
||||
|
||||
have_libuuid=disabled
|
||||
AC_ARG_WITH([libuuid],
|
||||
[AS_HELP_STRING([--with-libuuid],
|
||||
[use libuuid to generate unique identifiers @<:@default=check@:>@])],
|
||||
[],
|
||||
[with_libuuid=check])
|
||||
|
||||
if test "x$with_libuuid" != "xno"
|
||||
then
|
||||
have_libuuid=yes
|
||||
AC_CHECK_LIB([uuid], [uuid_generate],
|
||||
[UUID_LIBS=-luuid]
|
||||
[AC_DEFINE([HAVE_LIBUUID],, [Whether libuuid is available])],
|
||||
[have_libuuid=no])
|
||||
fi
|
||||
|
||||
# OSSP UUID (if libuuid is unavilable)
|
||||
if test "x${have_libuuid}" != "xyes"
|
||||
then
|
||||
|
||||
AC_CHECK_LIB([ossp-uuid], [uuid_make], [UUID_LIBS=-lossp-uuid],
|
||||
AC_CHECK_LIB([uuid], [uuid_make], [UUID_LIBS=-luuid],
|
||||
AC_MSG_ERROR([
|
||||
--------------------------------------------
|
||||
Unable to find libuuid or the OSSP UUID library.
|
||||
Either libuuid (from util-linux) or the OSSP UUID library is required for
|
||||
guacamole-server to be built.
|
||||
--------------------------------------------])))
|
||||
|
||||
# Check for and validate OSSP uuid.h header
|
||||
AC_CHECK_HEADERS([ossp/uuid.h])
|
||||
AC_CHECK_DECL([uuid_make],,
|
||||
AC_MSG_ERROR("No OSSP uuid.h found in include path"),
|
||||
[#ifdef HAVE_OSSP_UUID_H
|
||||
#include <ossp/uuid.h>
|
||||
#else
|
||||
#include <uuid.h>
|
||||
#endif
|
||||
])
|
||||
fi
|
||||
# Check for and validate OSSP uuid.h header
|
||||
AC_CHECK_HEADERS([ossp/uuid.h])
|
||||
AC_CHECK_DECL([uuid_make],,
|
||||
AC_MSG_ERROR("No OSSP uuid.h found in include path"),
|
||||
[#ifdef HAVE_OSSP_UUID_H
|
||||
#include <ossp/uuid.h>
|
||||
#else
|
||||
#include <uuid.h>
|
||||
#endif
|
||||
])
|
||||
|
||||
# cunit
|
||||
AC_CHECK_LIB([cunit], [CU_run_test], [CUNIT_LIBS=-lcunit])
|
||||
@ -160,11 +131,6 @@ AC_CHECK_DECL([strlcat],
|
||||
[Whether strlcat() is defined])],,
|
||||
[#include <string.h>])
|
||||
|
||||
AC_CHECK_DECL([strnstr],
|
||||
[AC_DEFINE([HAVE_STRNSTR],,
|
||||
[Whether strnstr() is defined])],,
|
||||
[#include <string.h>])
|
||||
|
||||
# Typedefs
|
||||
AC_TYPE_SIZE_T
|
||||
AC_TYPE_SSIZE_T
|
||||
@ -185,16 +151,12 @@ AC_SUBST([PULSE_INCLUDE], '-I$(top_srcdir)/src/pulse')
|
||||
AC_SUBST([COMMON_SSH_LTLIB], '$(top_builddir)/src/common-ssh/libguac_common_ssh.la')
|
||||
AC_SUBST([COMMON_SSH_INCLUDE], '-I$(top_srcdir)/src/common-ssh')
|
||||
|
||||
# Kubernetes support
|
||||
AC_SUBST([LIBGUAC_CLIENT_KUBERNETES_LTLIB], '$(top_builddir)/src/protocols/kubernetes/libguac-client-kubernetes.la')
|
||||
AC_SUBST([LIBGUAC_CLIENT_KUBERNETES_INCLUDE], '-I$(top_srcdir)/src/protocols/kubernetes')
|
||||
|
||||
# RDP support
|
||||
AC_SUBST([LIBGUAC_CLIENT_RDP_LTLIB], '$(top_builddir)/src/protocols/rdp/libguac-client-rdp.la')
|
||||
AC_SUBST([LIBGUAC_CLIENT_RDP_INCLUDE], '-I$(top_srcdir)/src/protocols/rdp')
|
||||
|
||||
# Terminal emulator
|
||||
AC_SUBST([TERMINAL_LTLIB], '$(top_builddir)/src/terminal/libguac-terminal.la')
|
||||
AC_SUBST([TERMINAL_LTLIB], '$(top_builddir)/src/terminal/libguac_terminal.la')
|
||||
AC_SUBST([TERMINAL_INCLUDE], '-I$(top_srcdir)/src/terminal $(PANGO_CFLAGS) $(PANGOCAIRO_CFLAGS) $(COMMON_INCLUDE)')
|
||||
|
||||
# Init directory
|
||||
@ -325,6 +287,30 @@ then
|
||||
else
|
||||
AC_DEFINE([ENABLE_SSL],, [Whether SSL-related support is enabled])
|
||||
|
||||
# OpenSSL 1.1 accessor function for DSA signature values
|
||||
AC_CHECK_DECL([DSA_SIG_get0],
|
||||
[AC_DEFINE([HAVE_DSA_SIG_GET0],,
|
||||
[Whether libssl provides DSA_SIG_get0()])],,
|
||||
[#include <openssl/dsa.h>])
|
||||
|
||||
# OpenSSL 1.1 accessor function for DSA public key parameters
|
||||
AC_CHECK_DECL([DSA_get0_pqg],
|
||||
[AC_DEFINE([HAVE_DSA_GET0_PQG],,
|
||||
[Whether libssl provides DSA_get0_pqg()])],,
|
||||
[#include <openssl/dsa.h>])
|
||||
|
||||
# OpenSSL 1.1 accessor function for DSA public/private key values
|
||||
AC_CHECK_DECL([DSA_get0_key],
|
||||
[AC_DEFINE([HAVE_DSA_GET0_KEY],,
|
||||
[Whether libssl provides DSA_get0_key()])],,
|
||||
[#include <openssl/dsa.h>])
|
||||
|
||||
# OpenSSL 1.1 accessor function for RSA public/private key values
|
||||
AC_CHECK_DECL([RSA_get0_key],
|
||||
[AC_DEFINE([HAVE_RSA_GET0_KEY],,
|
||||
[Whether libssl provides RSA_get0_key()])],,
|
||||
[#include <openssl/rsa.h>])
|
||||
|
||||
# OpenSSL 1.1 does away with explicit threading callbacks
|
||||
AC_MSG_CHECKING([whether libssl requires threading callbacks])
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
|
||||
@ -504,27 +490,6 @@ then
|
||||
AC_CHECK_LIB([vncclient], [rfbInitClient], [VNC_LIBS="$VNC_LIBS -lvncclient"], [have_libvncserver=no])
|
||||
fi
|
||||
|
||||
#
|
||||
# Underlying libvncserver usage of gcrypt
|
||||
#
|
||||
|
||||
if test "x${have_libvncserver}" = "xyes"
|
||||
then
|
||||
|
||||
# Whether libvncserver was built against libgcrypt
|
||||
AC_CHECK_DECL([LIBVNCSERVER_WITH_CLIENT_GCRYPT],
|
||||
[AC_CHECK_HEADER(gcrypt.h,,
|
||||
[AC_MSG_WARN([
|
||||
--------------------------------------------
|
||||
libvncserver appears to be built against
|
||||
libgcrypt, but the libgcrypt headers
|
||||
could not be found. VNC will be disabled.
|
||||
--------------------------------------------])
|
||||
have_libvncserver=no])],,
|
||||
[[#include <rfb/rfbconfig.h>]])
|
||||
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL([ENABLE_VNC], [test "x${have_libvncserver}" = "xyes"])
|
||||
AC_SUBST(VNC_LIBS)
|
||||
|
||||
@ -878,14 +843,6 @@ then
|
||||
[[#include <freerdp/freerdp.h>]])
|
||||
fi
|
||||
|
||||
# Updated certificate verification callback (introduced with 2.0.0, not present
|
||||
# in 2.0.0-rc4 or earlier)
|
||||
if test "x${have_freerdp2}" = "xyes"
|
||||
then
|
||||
AC_CHECK_MEMBERS([freerdp.VerifyCertificateEx],,,
|
||||
[[#include <freerdp/freerdp.h>]])
|
||||
fi
|
||||
|
||||
# Restore CPPFLAGS, removing FreeRDP-specific options needed for testing
|
||||
CPPFLAGS="$OLDCPPFLAGS"
|
||||
|
||||
@ -913,7 +870,7 @@ if test "x$with_ssh" != "xno"
|
||||
then
|
||||
have_libssh2=yes
|
||||
|
||||
AC_CHECK_LIB([ssh2], [libssh2_userauth_publickey_frommemory],
|
||||
AC_CHECK_LIB([ssh2], [libssh2_session_init_ex],
|
||||
[SSH_LIBS="$SSH_LIBS -lssh2"],
|
||||
[have_libssh2=no])
|
||||
fi
|
||||
@ -1167,8 +1124,7 @@ AM_CONDITIONAL([ENABLE_GUACLOG], [test "x${enable_guaclog}" = "xyes"])
|
||||
#
|
||||
|
||||
AC_CONFIG_FILES([Makefile
|
||||
doc/libguac/Doxyfile
|
||||
doc/libguac-terminal/Doxyfile
|
||||
doc/Doxyfile
|
||||
src/common/Makefile
|
||||
src/common/tests/Makefile
|
||||
src/common-ssh/Makefile
|
||||
@ -1184,8 +1140,8 @@ AC_CONFIG_FILES([Makefile
|
||||
src/guaclog/Makefile
|
||||
src/guaclog/man/guaclog.1
|
||||
src/pulse/Makefile
|
||||
src/protocols/ball/Makefile
|
||||
src/protocols/kubernetes/Makefile
|
||||
src/protocols/kubernetes/tests/Makefile
|
||||
src/protocols/rdp/Makefile
|
||||
src/protocols/rdp/tests/Makefile
|
||||
src/protocols/ssh/Makefile
|
||||
|
@ -52,9 +52,9 @@ SHOW_INCLUDE_FILES = NO
|
||||
CASE_SENSE_NAMES = YES
|
||||
EXCLUDE_SYMBOLS = __* guac_palette*
|
||||
FILE_PATTERNS = *.h
|
||||
INPUT = ../../src/libguac/guacamole
|
||||
INPUT = ../src/libguac/guacamole
|
||||
JAVADOC_AUTOBRIEF = YES
|
||||
STRIP_FROM_PATH = ../../src/libguac
|
||||
STRIP_FROM_PATH = ../src/libguac
|
||||
TAB_SIZE = 4
|
||||
TYPEDEF_HIDES_STRUCT = YES
|
||||
|
@ -31,6 +31,8 @@ SUBDIRS = . tests
|
||||
|
||||
libguac_common_ssh_la_SOURCES = \
|
||||
buffer.c \
|
||||
dsa-compat.c \
|
||||
rsa-compat.c \
|
||||
sftp.c \
|
||||
ssh.c \
|
||||
key.c \
|
||||
@ -38,6 +40,8 @@ libguac_common_ssh_la_SOURCES = \
|
||||
|
||||
noinst_HEADERS = \
|
||||
common-ssh/buffer.h \
|
||||
common-ssh/dsa-compat.h \
|
||||
common-ssh/rsa-compat.h \
|
||||
common-ssh/key.h \
|
||||
common-ssh/sftp.h \
|
||||
common-ssh/ssh.h \
|
||||
|
61
src/common-ssh/common-ssh/dsa-compat.h
Normal file
61
src/common-ssh/common-ssh/dsa-compat.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_COMMON_SSH_DSA_COMPAT_H
|
||||
#define GUAC_COMMON_SSH_DSA_COMPAT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/dsa.h>
|
||||
|
||||
#ifndef HAVE_DSA_GET0_PQG
|
||||
/**
|
||||
* DSA_get0_pqg() implementation for versions of OpenSSL which lack this
|
||||
* function (pre 1.1).
|
||||
*
|
||||
* See: https://www.openssl.org/docs/man1.1.0/crypto/DSA_get0_pqg.html
|
||||
*/
|
||||
void DSA_get0_pqg(const DSA* dsa_key, const BIGNUM** p,
|
||||
const BIGNUM** q, const BIGNUM** g);
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_DSA_GET0_KEY
|
||||
/**
|
||||
* DSA_get0_key() implementation for versions of OpenSSL which lack this
|
||||
* function (pre 1.1).
|
||||
*
|
||||
* See: https://www.openssl.org/docs/man1.1.0/crypto/DSA_get0_key.html
|
||||
*/
|
||||
void DSA_get0_key(const DSA* dsa_key, const BIGNUM** pub_key,
|
||||
const BIGNUM** priv_key);
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_DSA_SIG_GET0
|
||||
/**
|
||||
* DSA_SIG_get0() implementation for versions of OpenSSL which lack this
|
||||
* function (pre 1.1).
|
||||
*
|
||||
* See: https://www.openssl.org/docs/man1.1.0/crypto/DSA_SIG_get0.html
|
||||
*/
|
||||
void DSA_SIG_get0(const DSA_SIG* dsa_sig, const BIGNUM** r, const BIGNUM** s);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -25,27 +25,75 @@
|
||||
#include <guacamole/client.h>
|
||||
#include <libssh2.h>
|
||||
|
||||
/**
|
||||
* OpenSSH v1 private keys are PEM-wrapped base64-encoded blobs. The encoded data begins with:
|
||||
* "openssh-key-v1\0"
|
||||
*/
|
||||
#define OPENSSH_V1_KEY_HEADER "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEA"
|
||||
#include <openssl/ossl_typ.h>
|
||||
|
||||
/**
|
||||
* The base64-encoded prefix indicating an OpenSSH v1 private key is NOT protected by a
|
||||
* passphrase. Specifically, it is the following data fields and values:
|
||||
* pascal string: cipher name ("none")
|
||||
* pascal string: kdf name ("none")
|
||||
* pascal string: kdf params (NULL)
|
||||
* 32-bit int: number of keys (1)
|
||||
* The expected header of RSA private keys.
|
||||
*/
|
||||
#define OPENSSH_V1_UNENCRYPTED_KEY "AAAABG5vbmUAAAAEbm9uZQAAAAAAAAAB"
|
||||
#define SSH_RSA_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----"
|
||||
|
||||
/**
|
||||
* The expected header of DSA private keys.
|
||||
*/
|
||||
#define SSH_DSA_KEY_HEADER "-----BEGIN DSA PRIVATE KEY-----"
|
||||
|
||||
/**
|
||||
* The size of single number within a DSA signature, in bytes.
|
||||
*/
|
||||
#define DSA_SIG_NUMBER_SIZE 20
|
||||
|
||||
/**
|
||||
* The size of a DSA signature, in bytes.
|
||||
*/
|
||||
#define DSA_SIG_SIZE DSA_SIG_NUMBER_SIZE*2
|
||||
|
||||
/**
|
||||
* The type of an SSH key.
|
||||
*/
|
||||
typedef enum guac_common_ssh_key_type {
|
||||
|
||||
/**
|
||||
* RSA key.
|
||||
*/
|
||||
SSH_KEY_RSA,
|
||||
|
||||
/**
|
||||
* DSA key.
|
||||
*/
|
||||
SSH_KEY_DSA
|
||||
|
||||
} guac_common_ssh_key_type;
|
||||
|
||||
/**
|
||||
* Abstraction of a key used for SSH authentication.
|
||||
*/
|
||||
typedef struct guac_common_ssh_key {
|
||||
|
||||
/**
|
||||
* The type of this key.
|
||||
*/
|
||||
guac_common_ssh_key_type type;
|
||||
|
||||
/**
|
||||
* Underlying RSA private key, if any.
|
||||
*/
|
||||
RSA* rsa;
|
||||
|
||||
/**
|
||||
* Underlying DSA private key, if any.
|
||||
*/
|
||||
DSA* dsa;
|
||||
|
||||
/**
|
||||
* The associated public key, encoded as necessary for SSH.
|
||||
*/
|
||||
char* public_key;
|
||||
|
||||
/**
|
||||
* The length of the public key, in bytes.
|
||||
*/
|
||||
int public_key_length;
|
||||
|
||||
/**
|
||||
* The private key, encoded as necessary for SSH.
|
||||
*/
|
||||
@ -56,11 +104,6 @@ typedef struct guac_common_ssh_key {
|
||||
*/
|
||||
int private_key_length;
|
||||
|
||||
/**
|
||||
* The private key's passphrase, if any.
|
||||
*/
|
||||
char *passphrase;
|
||||
|
||||
} guac_common_ssh_key;
|
||||
|
||||
/**
|
||||
@ -101,6 +144,31 @@ const char* guac_common_ssh_key_error();
|
||||
*/
|
||||
void guac_common_ssh_key_free(guac_common_ssh_key* key);
|
||||
|
||||
/**
|
||||
* Signs the given data using the given key, returning the length of the
|
||||
* signature in bytes, or a value less than zero on error.
|
||||
*
|
||||
* @param key
|
||||
* The key to use when signing the given data.
|
||||
*
|
||||
* @param data
|
||||
* The arbitrary data to sign.
|
||||
*
|
||||
* @param length
|
||||
* The length of the arbitrary data being signed, in bytes.
|
||||
*
|
||||
* @param sig
|
||||
* The buffer into which the signature should be written. The buffer must
|
||||
* be at least DSA_SIG_SIZE for DSA keys. For RSA keys, the signature size
|
||||
* is dependent only on key size, and is equal to the length of the
|
||||
* modulus, in bytes.
|
||||
*
|
||||
* @return
|
||||
* The number of bytes in the resulting signature.
|
||||
*/
|
||||
int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data,
|
||||
int length, unsigned char* sig);
|
||||
|
||||
/**
|
||||
* Verifies the host key for the given hostname/port combination against
|
||||
* one or more known_hosts entries. The known_host entries can either be a
|
||||
|
40
src/common-ssh/common-ssh/rsa-compat.h
Normal file
40
src/common-ssh/common-ssh/rsa-compat.h
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_COMMON_SSH_RSA_COMPAT_H
|
||||
#define GUAC_COMMON_SSH_RSA_COMPAT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
#ifndef HAVE_RSA_GET0_KEY
|
||||
/**
|
||||
* RSA_get0_key() implementation for versions of OpenSSL which lack this
|
||||
* function (pre 1.1).
|
||||
*
|
||||
* See: https://www.openssl.org/docs/man1.1.0/crypto/RSA_get0_key.html
|
||||
*/
|
||||
void RSA_get0_key(const RSA* rsa_key, const BIGNUM** n,
|
||||
const BIGNUM** e, const BIGNUM**d);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
59
src/common-ssh/dsa-compat.c
Normal file
59
src/common-ssh/dsa-compat.c
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/dsa.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef HAVE_DSA_GET0_PQG
|
||||
void DSA_get0_pqg(const DSA* dsa_key, const BIGNUM** p,
|
||||
const BIGNUM** q, const BIGNUM** g) {
|
||||
|
||||
/* Retrieve all requested internal values */
|
||||
if (p != NULL) *p = dsa_key->p;
|
||||
if (q != NULL) *q = dsa_key->q;
|
||||
if (g != NULL) *g = dsa_key->g;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_DSA_GET0_KEY
|
||||
void DSA_get0_key(const DSA* dsa_key, const BIGNUM** pub_key,
|
||||
const BIGNUM** priv_key) {
|
||||
|
||||
/* Retrieve all requested internal values */
|
||||
if (pub_key != NULL) *pub_key = dsa_key->pub_key;
|
||||
if (priv_key != NULL) *priv_key = dsa_key->priv_key;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_DSA_SIG_GET0
|
||||
void DSA_SIG_get0(const DSA_SIG* dsa_sig, const BIGNUM** r, const BIGNUM** s) {
|
||||
|
||||
/* Retrieve all requested internal values */
|
||||
if (r != NULL) *r = dsa_sig->r;
|
||||
if (s != NULL) *s = dsa_sig->s;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
@ -20,9 +20,9 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "common-ssh/buffer.h"
|
||||
#include "common-ssh/dsa-compat.h"
|
||||
#include "common-ssh/key.h"
|
||||
|
||||
#include <guacamole/string.h>
|
||||
#include "common-ssh/rsa-compat.h"
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/bn.h>
|
||||
@ -33,118 +33,119 @@
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Check for a PKCS#1/PKCS#8 ENCRYPTED marker.
|
||||
*
|
||||
* @param data
|
||||
* The buffer to scan.
|
||||
* @param length
|
||||
* The length of the buffer.
|
||||
*
|
||||
* @return
|
||||
* True if the buffer contains the marker, false otherwise.
|
||||
*/
|
||||
static bool is_pkcs_encrypted_key(char* data, int length) {
|
||||
return guac_strnstr(data, "ENCRYPTED", length) != NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a PEM header & initial base64-encoded data indicating this is an
|
||||
* OpenSSH v1 key.
|
||||
*
|
||||
* @param data
|
||||
* The buffer to scan.
|
||||
* @param length
|
||||
* The length of the buffer.
|
||||
*
|
||||
* @return
|
||||
* True if the buffer contains a private key, false otherwise.
|
||||
*/
|
||||
static bool is_ssh_private_key(char* data, int length) {
|
||||
if (length < sizeof(OPENSSH_V1_KEY_HEADER) - 1) {
|
||||
return false;
|
||||
}
|
||||
return !strncmp(data, OPENSSH_V1_KEY_HEADER, sizeof(OPENSSH_V1_KEY_HEADER) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assuming an offset into a key past the header, check for the base64-encoded
|
||||
* data indicating this key is not protected by a passphrase.
|
||||
*
|
||||
* @param data
|
||||
* The buffer to scan.
|
||||
* @param length
|
||||
* The length of the buffer.
|
||||
*
|
||||
* @return
|
||||
* True if the buffer contains an unencrypted key, false otherwise.
|
||||
*/
|
||||
static bool is_ssh_key_unencrypted(char* data, int length) {
|
||||
if (length < sizeof(OPENSSH_V1_UNENCRYPTED_KEY) - 1) {
|
||||
return false;
|
||||
}
|
||||
return !strncmp(data, OPENSSH_V1_UNENCRYPTED_KEY, sizeof(OPENSSH_V1_UNENCRYPTED_KEY) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* A passphrase is needed if the key is an encrypted PKCS#1/PKCS#8 key OR if
|
||||
* the key is both an OpenSSH v1 key AND there isn't a marker indicating the
|
||||
* key is unprotected.
|
||||
*
|
||||
* @param data
|
||||
* The buffer to scan.
|
||||
* @param length
|
||||
* The length of the buffer.
|
||||
*
|
||||
* @return
|
||||
* True if the buffer contains a key needing a passphrase, false otherwise.
|
||||
*/
|
||||
static bool is_passphrase_needed(char* data, int length) {
|
||||
/* Is this an encrypted PKCS#1/PKCS#8 key? */
|
||||
if (is_pkcs_encrypted_key(data, length)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Is this an OpenSSH v1 key? */
|
||||
if (is_ssh_private_key(data, length)) {
|
||||
/* This is safe due to the check in is_ssh_private_key. */
|
||||
data += sizeof(OPENSSH_V1_KEY_HEADER) - 1;
|
||||
length -= sizeof(OPENSSH_V1_KEY_HEADER) - 1;
|
||||
/* If this is NOT unprotected, we need a passphrase. */
|
||||
if (!is_ssh_key_unencrypted(data, length)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
|
||||
char* passphrase) {
|
||||
|
||||
/* Because libssh2 will do the actual key parsing (to let it deal with
|
||||
* different key algorithms) we need to perform a heuristic here to check
|
||||
* if a passphrase is needed. This could allow junk keys through that
|
||||
* would never be able to auth. libssh2 should display errors to help
|
||||
* admins track down malformed keys and delete or replace them.
|
||||
*/
|
||||
guac_common_ssh_key* key;
|
||||
BIO* key_bio;
|
||||
|
||||
if (is_passphrase_needed(data, length) && (passphrase == NULL || *passphrase == '\0'))
|
||||
char* public_key;
|
||||
char* pos;
|
||||
|
||||
/* Create BIO for reading key from memory */
|
||||
key_bio = BIO_new_mem_buf(data, length);
|
||||
|
||||
/* If RSA key, load RSA */
|
||||
if (length > sizeof(SSH_RSA_KEY_HEADER)-1
|
||||
&& memcmp(SSH_RSA_KEY_HEADER, data,
|
||||
sizeof(SSH_RSA_KEY_HEADER)-1) == 0) {
|
||||
|
||||
RSA* rsa_key;
|
||||
|
||||
const BIGNUM* key_e;
|
||||
const BIGNUM* key_n;
|
||||
|
||||
/* Read key */
|
||||
rsa_key = PEM_read_bio_RSAPrivateKey(key_bio, NULL, NULL, passphrase);
|
||||
if (rsa_key == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Allocate key */
|
||||
key = malloc(sizeof(guac_common_ssh_key));
|
||||
key->rsa = rsa_key;
|
||||
|
||||
/* Set type */
|
||||
key->type = SSH_KEY_RSA;
|
||||
|
||||
/* Allocate space for public key */
|
||||
public_key = malloc(4096);
|
||||
pos = public_key;
|
||||
|
||||
/* Retrieve public key */
|
||||
RSA_get0_key(rsa_key, &key_n, &key_e, NULL);
|
||||
|
||||
/* Send public key formatted for SSH */
|
||||
guac_common_ssh_buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1);
|
||||
guac_common_ssh_buffer_write_bignum(&pos, key_e);
|
||||
guac_common_ssh_buffer_write_bignum(&pos, key_n);
|
||||
|
||||
/* Save public key to structure */
|
||||
key->public_key = public_key;
|
||||
key->public_key_length = pos - public_key;
|
||||
|
||||
}
|
||||
|
||||
/* If DSA key, load DSA */
|
||||
else if (length > sizeof(SSH_DSA_KEY_HEADER)-1
|
||||
&& memcmp(SSH_DSA_KEY_HEADER, data,
|
||||
sizeof(SSH_DSA_KEY_HEADER)-1) == 0) {
|
||||
|
||||
DSA* dsa_key;
|
||||
|
||||
const BIGNUM* key_p;
|
||||
const BIGNUM* key_q;
|
||||
const BIGNUM* key_g;
|
||||
const BIGNUM* pub_key;
|
||||
|
||||
/* Read key */
|
||||
dsa_key = PEM_read_bio_DSAPrivateKey(key_bio, NULL, NULL, passphrase);
|
||||
if (dsa_key == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Allocate key */
|
||||
key = malloc(sizeof(guac_common_ssh_key));
|
||||
key->dsa = dsa_key;
|
||||
|
||||
/* Set type */
|
||||
key->type = SSH_KEY_DSA;
|
||||
|
||||
/* Allocate space for public key */
|
||||
public_key = malloc(4096);
|
||||
pos = public_key;
|
||||
|
||||
/* Retrieve public key */
|
||||
DSA_get0_pqg(dsa_key, &key_p, &key_q, &key_g);
|
||||
DSA_get0_key(dsa_key, &pub_key, NULL);
|
||||
|
||||
/* Send public key formatted for SSH */
|
||||
guac_common_ssh_buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1);
|
||||
guac_common_ssh_buffer_write_bignum(&pos, key_p);
|
||||
guac_common_ssh_buffer_write_bignum(&pos, key_q);
|
||||
guac_common_ssh_buffer_write_bignum(&pos, key_g);
|
||||
guac_common_ssh_buffer_write_bignum(&pos, pub_key);
|
||||
|
||||
/* Save public key to structure */
|
||||
key->public_key = public_key;
|
||||
key->public_key_length = pos - public_key;
|
||||
|
||||
}
|
||||
|
||||
/* Otherwise, unsupported type */
|
||||
else {
|
||||
BIO_free(key_bio);
|
||||
return NULL;
|
||||
|
||||
guac_common_ssh_key* key = malloc(sizeof(guac_common_ssh_key));
|
||||
}
|
||||
|
||||
/* Copy private key to structure */
|
||||
key->private_key_length = length;
|
||||
key->private_key = malloc(length);
|
||||
memcpy(key->private_key, data, length);
|
||||
key->passphrase = strdup(passphrase);
|
||||
|
||||
BIO_free(key_bio);
|
||||
return key;
|
||||
|
||||
}
|
||||
@ -158,11 +159,93 @@ const char* guac_common_ssh_key_error() {
|
||||
|
||||
void guac_common_ssh_key_free(guac_common_ssh_key* key) {
|
||||
|
||||
/* Free key-specific data */
|
||||
if (key->type == SSH_KEY_RSA)
|
||||
RSA_free(key->rsa);
|
||||
else if (key->type == SSH_KEY_DSA)
|
||||
DSA_free(key->dsa);
|
||||
|
||||
free(key->private_key);
|
||||
free(key->passphrase);
|
||||
free(key->public_key);
|
||||
free(key);
|
||||
}
|
||||
|
||||
int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data,
|
||||
int length, unsigned char* sig) {
|
||||
|
||||
const EVP_MD* md;
|
||||
|
||||
unsigned char digest[EVP_MAX_MD_SIZE];
|
||||
unsigned int dlen, len;
|
||||
|
||||
/* Get SHA1 digest */
|
||||
if ((md = EVP_get_digestbynid(NID_sha1)) == NULL)
|
||||
return -1;
|
||||
|
||||
/* Allocate digest context */
|
||||
EVP_MD_CTX* md_ctx = EVP_MD_CTX_create();
|
||||
if (md_ctx == NULL)
|
||||
return -1;
|
||||
|
||||
/* Digest data */
|
||||
EVP_DigestInit(md_ctx, md);
|
||||
EVP_DigestUpdate(md_ctx, data, length);
|
||||
EVP_DigestFinal(md_ctx, digest, &dlen);
|
||||
|
||||
/* Digest context no longer needed */
|
||||
EVP_MD_CTX_destroy(md_ctx);
|
||||
|
||||
/* Sign with key */
|
||||
switch (key->type) {
|
||||
|
||||
case SSH_KEY_RSA:
|
||||
if (RSA_sign(NID_sha1, digest, dlen, sig, &len, key->rsa) == 1)
|
||||
return len;
|
||||
break;
|
||||
|
||||
case SSH_KEY_DSA: {
|
||||
|
||||
DSA_SIG* dsa_sig = DSA_do_sign(digest, dlen, key->dsa);
|
||||
if (dsa_sig != NULL) {
|
||||
|
||||
const BIGNUM* sig_r;
|
||||
const BIGNUM* sig_s;
|
||||
|
||||
/* Retrieve DSA signature values */
|
||||
DSA_SIG_get0(dsa_sig, &sig_r, &sig_s);
|
||||
|
||||
/* Compute size of each half of signature */
|
||||
int rlen = BN_num_bytes(sig_r);
|
||||
int slen = BN_num_bytes(sig_s);
|
||||
|
||||
/* Ensure each number is within the required size */
|
||||
if (rlen > DSA_SIG_NUMBER_SIZE || slen > DSA_SIG_NUMBER_SIZE)
|
||||
return -1;
|
||||
|
||||
/* Init to all zeroes */
|
||||
memset(sig, 0, DSA_SIG_SIZE);
|
||||
|
||||
/* Add R at the end of the first block of the signature */
|
||||
BN_bn2bin(sig_r, sig + DSA_SIG_SIZE
|
||||
- DSA_SIG_NUMBER_SIZE - rlen);
|
||||
|
||||
/* Add S at the end of the second block of the signature */
|
||||
BN_bn2bin(sig_s, sig + DSA_SIG_SIZE - slen);
|
||||
|
||||
/* Done */
|
||||
DSA_SIG_free(dsa_sig);
|
||||
return DSA_SIG_SIZE;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
int guac_common_ssh_verify_host_key(LIBSSH2_SESSION* session, guac_client* client,
|
||||
const char* host_key, const char* hostname, int port, const char* remote_hostkey,
|
||||
const size_t remote_hostkey_len) {
|
||||
|
38
src/common-ssh/rsa-compat.c
Normal file
38
src/common-ssh/rsa-compat.c
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef HAVE_RSA_GET0_KEY
|
||||
void RSA_get0_key(const RSA* rsa_key, const BIGNUM** n,
|
||||
const BIGNUM** e, const BIGNUM**d) {
|
||||
|
||||
/* Retrieve all requested internal values */
|
||||
if (n != NULL) *n = rsa_key->n;
|
||||
if (e != NULL) *e = rsa_key->e;
|
||||
if (d != NULL) *d = rsa_key->d;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
@ -22,7 +22,6 @@
|
||||
#include "common-ssh/user.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/fips.h>
|
||||
#include <libssh2.h>
|
||||
|
||||
#ifdef LIBSSH2_USES_GCRYPT
|
||||
@ -47,20 +46,6 @@
|
||||
GCRY_THREAD_OPTION_PTHREAD_IMPL;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A list of all key exchange algorithms that are both FIPS-compliant, and
|
||||
* OpenSSL-supported. Note that "ext-info-c" is also included. While not a key
|
||||
* exchange algorithm per se, it must be in the list to ensure that the server
|
||||
* will send a SSH_MSG_EXT_INFO response, which is required to perform RSA key
|
||||
* upgrades.
|
||||
*/
|
||||
#define FIPS_COMPLIANT_KEX_ALGORITHMS "diffie-hellman-group-exchange-sha256,ext-info-c"
|
||||
|
||||
/**
|
||||
* A list of ciphers that are both FIPS-compliant, and OpenSSL-supported.
|
||||
*/
|
||||
#define FIPS_COMPLIANT_CIPHERS "aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,aes192-cbc,aes256-cbc"
|
||||
|
||||
#ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
|
||||
/**
|
||||
* Array of mutexes, used by OpenSSL.
|
||||
@ -155,21 +140,11 @@ static void guac_common_ssh_openssl_free_locks(int count) {
|
||||
int guac_common_ssh_init(guac_client* client) {
|
||||
|
||||
#ifdef LIBSSH2_USES_GCRYPT
|
||||
|
||||
if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) {
|
||||
|
||||
/* Init threadsafety in libgcrypt */
|
||||
gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
|
||||
|
||||
/* Initialize GCrypt */
|
||||
if (!gcry_check_version(GCRYPT_VERSION)) {
|
||||
guac_client_log(client, GUAC_LOG_ERROR, "libgcrypt version mismatch.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Mark initialization as completed. */
|
||||
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
|
||||
|
||||
/* Init threadsafety in libgcrypt */
|
||||
gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
|
||||
if (!gcry_check_version(GCRYPT_VERSION)) {
|
||||
guac_client_log(client, GUAC_LOG_ERROR, "libgcrypt version mismatch.");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -180,11 +155,9 @@ int guac_common_ssh_init(guac_client* client) {
|
||||
CRYPTO_set_locking_callback(guac_common_ssh_openssl_locking_callback);
|
||||
#endif
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
/* Init OpenSSL - only required for OpenSSL Versions < 1.1.0 */
|
||||
/* Init OpenSSL */
|
||||
SSL_library_init();
|
||||
ERR_load_crypto_strings();
|
||||
#endif
|
||||
|
||||
/* Init libssh2 */
|
||||
libssh2_init(0);
|
||||
@ -200,6 +173,55 @@ void guac_common_ssh_uninit() {
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked by libssh2 when libssh2_userauth_publickkey() is invoked.
|
||||
* This callback must sign the given data, returning the signature as newly-
|
||||
* allocated buffer space.
|
||||
*
|
||||
* @param session
|
||||
* The SSH session for which the signature is being generated.
|
||||
*
|
||||
* @param sig
|
||||
* A pointer to the buffer space containing the signature. This callback
|
||||
* MUST allocate and assign this space.
|
||||
*
|
||||
* @param sig_len
|
||||
* The length of the signature within the allocated buffer space, in bytes.
|
||||
* This value must be set to the size of the signature after the signing
|
||||
* operation completes.
|
||||
*
|
||||
* @param data
|
||||
* The arbitrary data that must be signed.
|
||||
*
|
||||
* @param data_len
|
||||
* The length of the arbitrary data to be signed, in bytes.
|
||||
*
|
||||
* @param abstract
|
||||
* The value of the abstract parameter provided with the corresponding call
|
||||
* to libssh2_userauth_publickey().
|
||||
*
|
||||
* @return
|
||||
* Zero on success, non-zero if the signing operation failed.
|
||||
*/
|
||||
static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session,
|
||||
unsigned char** sig, size_t* sig_len,
|
||||
const unsigned char* data, size_t data_len, void **abstract) {
|
||||
|
||||
guac_common_ssh_key* key = (guac_common_ssh_key*) abstract;
|
||||
int length;
|
||||
|
||||
/* Allocate space for signature */
|
||||
*sig = malloc(4096);
|
||||
|
||||
/* Sign with key */
|
||||
length = guac_common_ssh_key_sign(key, (const char*) data, data_len, *sig);
|
||||
if (length < 0)
|
||||
return 1;
|
||||
|
||||
*sig_len = length;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the keyboard-interactive authentication method. Currently
|
||||
* supports just one prompt for the password. This callback is invoked as
|
||||
@ -292,9 +314,8 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
|
||||
}
|
||||
|
||||
/* Get list of supported authentication methods */
|
||||
size_t username_len = strlen(user->username);
|
||||
char* user_authlist = libssh2_userauth_list(session, user->username,
|
||||
username_len);
|
||||
strlen(user->username));
|
||||
|
||||
/* If auth list is NULL, then authentication has succeeded with NONE */
|
||||
if (user_authlist == NULL) {
|
||||
@ -318,9 +339,9 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
|
||||
}
|
||||
|
||||
/* Attempt public key auth */
|
||||
if (libssh2_userauth_publickey_frommemory(session, user->username,
|
||||
username_len, NULL, 0, key->private_key,
|
||||
key->private_key_length, key->passphrase)) {
|
||||
if (libssh2_userauth_publickey(session, user->username,
|
||||
(unsigned char*) key->public_key, key->public_key_length,
|
||||
guac_common_ssh_sign_callback, (void**) key)) {
|
||||
|
||||
/* Abort on failure */
|
||||
char* error_message;
|
||||
@ -501,17 +522,6 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* If FIPS mode is enabled, prefer only FIPS-compatible algorithms and
|
||||
* ciphers that are also supported by libssh2. For more info, see:
|
||||
* https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp2906.pdf
|
||||
*/
|
||||
if (guac_fips_enabled()) {
|
||||
libssh2_session_method_pref(session, LIBSSH2_METHOD_KEX, FIPS_COMPLIANT_KEX_ALGORITHMS);
|
||||
libssh2_session_method_pref(session, LIBSSH2_METHOD_CRYPT_CS, FIPS_COMPLIANT_CIPHERS);
|
||||
libssh2_session_method_pref(session, LIBSSH2_METHOD_CRYPT_SC, FIPS_COMPLIANT_CIPHERS);
|
||||
}
|
||||
|
||||
/* Perform handshake */
|
||||
if (libssh2_session_handshake(session, fd)) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
|
||||
|
@ -42,6 +42,7 @@ noinst_HEADERS = \
|
||||
common/json.h \
|
||||
common/list.h \
|
||||
common/pointer_cursor.h \
|
||||
common/recording.h \
|
||||
common/rect.h \
|
||||
common/string.h \
|
||||
common/surface.h
|
||||
@ -58,6 +59,7 @@ libguac_common_la_SOURCES = \
|
||||
json.c \
|
||||
list.c \
|
||||
pointer_cursor.c \
|
||||
recording.c \
|
||||
rect.c \
|
||||
string.c \
|
||||
surface.c
|
||||
|
@ -29,15 +29,15 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
guac_common_clipboard* guac_common_clipboard_alloc() {
|
||||
guac_common_clipboard* guac_common_clipboard_alloc(int size) {
|
||||
|
||||
guac_common_clipboard* clipboard = malloc(sizeof(guac_common_clipboard));
|
||||
|
||||
/* Init clipboard */
|
||||
clipboard->mimetype[0] = '\0';
|
||||
clipboard->buffer = malloc(GUAC_COMMON_CLIPBOARD_MAX_LENGTH);
|
||||
clipboard->available = GUAC_COMMON_CLIPBOARD_MAX_LENGTH;
|
||||
clipboard->buffer = malloc(size);
|
||||
clipboard->length = 0;
|
||||
clipboard->available = size;
|
||||
|
||||
pthread_mutex_init(&(clipboard->lock), NULL);
|
||||
|
||||
|
@ -31,11 +31,6 @@
|
||||
*/
|
||||
#define GUAC_COMMON_CLIPBOARD_BLOCK_SIZE 4096
|
||||
|
||||
/**
|
||||
* The maximum number of bytes to allow within the clipboard.
|
||||
*/
|
||||
#define GUAC_COMMON_CLIPBOARD_MAX_LENGTH 262144
|
||||
|
||||
/**
|
||||
* Generic clipboard structure.
|
||||
*/
|
||||
@ -71,9 +66,12 @@ typedef struct guac_common_clipboard {
|
||||
} guac_common_clipboard;
|
||||
|
||||
/**
|
||||
* Creates a new clipboard.
|
||||
* Creates a new clipboard having the given initial size.
|
||||
*
|
||||
* @param size The maximum number of bytes to allow within the clipboard.
|
||||
* @return A newly-allocated clipboard.
|
||||
*/
|
||||
guac_common_clipboard* guac_common_clipboard_alloc();
|
||||
guac_common_clipboard* guac_common_clipboard_alloc(int size);
|
||||
|
||||
/**
|
||||
* Frees the given clipboard.
|
||||
|
@ -99,13 +99,6 @@ typedef struct guac_common_display {
|
||||
*/
|
||||
guac_common_display_layer* buffers;
|
||||
|
||||
/**
|
||||
* Non-zero if all graphical updates for this display should use lossless
|
||||
* compression, 0 otherwise. By default, newly-created displays will use
|
||||
* lossy compression when heuristics determine it is appropriate.
|
||||
*/
|
||||
int lossless;
|
||||
|
||||
/**
|
||||
* Mutex which is locked internally when access to the display must be
|
||||
* synchronized. All public functions of guac_common_display should be
|
||||
@ -235,27 +228,5 @@ void guac_common_display_free_layer(guac_common_display* display,
|
||||
void guac_common_display_free_buffer(guac_common_display* display,
|
||||
guac_common_display_layer* display_buffer);
|
||||
|
||||
/**
|
||||
* Sets the overall lossless compression policy of the given display to the
|
||||
* given value, affecting all current and future layers/buffers maintained by
|
||||
* the display. By default, newly-created displays will use lossy compression
|
||||
* for graphical updates when heuristics determine that doing so is
|
||||
* appropriate. Specifying a non-zero value here will force all graphical
|
||||
* updates to always use lossless compression, whereas specifying zero will
|
||||
* restore the default policy.
|
||||
*
|
||||
* Note that this can also be adjusted on a per-layer / per-buffer basis with
|
||||
* guac_common_surface_set_lossless().
|
||||
*
|
||||
* @param display
|
||||
* The display to modify.
|
||||
*
|
||||
* @param lossless
|
||||
* Non-zero if all graphical updates for this display should use lossless
|
||||
* compression, 0 otherwise.
|
||||
*/
|
||||
void guac_common_display_set_lossless(guac_common_display* display,
|
||||
int lossless);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -76,30 +76,6 @@ guac_iconv_read GUAC_READ_CP1252;
|
||||
*/
|
||||
guac_iconv_read GUAC_READ_ISO8859_1;
|
||||
|
||||
/**
|
||||
* Read function for UTF-8 which normalizes newline character sequences like
|
||||
* "\r\n" to Unix-style newlines ('\n').
|
||||
*/
|
||||
guac_iconv_read GUAC_READ_UTF8_NORMALIZED;
|
||||
|
||||
/**
|
||||
* Read function for UTF-16 which normalizes newline character sequences like
|
||||
* "\r\n" to Unix-style newlines ('\n').
|
||||
*/
|
||||
guac_iconv_read GUAC_READ_UTF16_NORMALIZED;
|
||||
|
||||
/**
|
||||
* Read function for CP-1252 which normalizes newline character sequences like
|
||||
* "\r\n" to Unix-style newlines ('\n').
|
||||
*/
|
||||
guac_iconv_read GUAC_READ_CP1252_NORMALIZED;
|
||||
|
||||
/**
|
||||
* Read function for ISO 8859-1 which normalizes newline character sequences
|
||||
* like "\r\n" to Unix-style newlines ('\n').
|
||||
*/
|
||||
guac_iconv_read GUAC_READ_ISO8859_1_NORMALIZED;
|
||||
|
||||
/**
|
||||
* Write function for UTF8.
|
||||
*/
|
||||
@ -120,29 +96,5 @@ guac_iconv_write GUAC_WRITE_CP1252;
|
||||
*/
|
||||
guac_iconv_write GUAC_WRITE_ISO8859_1;
|
||||
|
||||
/**
|
||||
* Write function for UTF-8 which writes newline characters ('\n') as
|
||||
* Windows-style newlines ("\r\n").
|
||||
*/
|
||||
guac_iconv_write GUAC_WRITE_UTF8_CRLF;
|
||||
|
||||
/**
|
||||
* Write function for UTF-16 which writes newline characters ('\n') as
|
||||
* Windows-style newlines ("\r\n").
|
||||
*/
|
||||
guac_iconv_write GUAC_WRITE_UTF16_CRLF;
|
||||
|
||||
/**
|
||||
* Write function for CP-1252 which writes newline characters ('\n') as
|
||||
* Windows-style newlines ("\r\n").
|
||||
*/
|
||||
guac_iconv_write GUAC_WRITE_CP1252_CRLF;
|
||||
|
||||
/**
|
||||
* Write function for ISO 8859-1 which writes newline characters ('\n') as
|
||||
* Windows-style newlines ("\r\n").
|
||||
*/
|
||||
guac_iconv_write GUAC_WRITE_ISO8859_1_CRLF;
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -17,17 +17,11 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_RECORDING_H
|
||||
#define GUAC_RECORDING_H
|
||||
#ifndef GUAC_COMMON_RECORDING_H
|
||||
#define GUAC_COMMON_RECORDING_H
|
||||
|
||||
#include <guacamole/client.h>
|
||||
|
||||
/**
|
||||
* Provides functions and structures to be use for session recording.
|
||||
*
|
||||
* @file recording.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
|
||||
@ -53,7 +47,7 @@
|
||||
* that output Guacamole instructions may be dynamically intercepted and
|
||||
* written to a file.
|
||||
*/
|
||||
typedef struct guac_recording {
|
||||
typedef struct guac_common_recording {
|
||||
|
||||
/**
|
||||
* The guac_socket which writes directly to the recording file, rather than
|
||||
@ -77,15 +71,6 @@ typedef struct guac_recording {
|
||||
*/
|
||||
int include_mouse;
|
||||
|
||||
/**
|
||||
* Non-zero if multi-touch events should be included in the session
|
||||
* recording, zero otherwise. Depending on whether the remote desktop will
|
||||
* automatically provide graphical feedback for touches, including touch
|
||||
* events may be necessary for multi-touch interactions to be rendered in
|
||||
* any resulting video.
|
||||
*/
|
||||
int include_touch;
|
||||
|
||||
/**
|
||||
* Non-zero if keys pressed and released should be included in the session
|
||||
* recording, zero otherwise. Including key events within the recording may
|
||||
@ -95,7 +80,7 @@ typedef struct guac_recording {
|
||||
*/
|
||||
int include_keys;
|
||||
|
||||
} guac_recording;
|
||||
} guac_common_recording;
|
||||
|
||||
/**
|
||||
* Replaces the socket of the given client such that all further Guacamole
|
||||
@ -134,13 +119,6 @@ typedef struct guac_recording {
|
||||
* otherwise. Including mouse state is necessary for the mouse cursor to be
|
||||
* rendered in any resulting video.
|
||||
*
|
||||
* @param include_touch
|
||||
* Non-zero if touch events should be included in the session recording,
|
||||
* zero otherwise. Depending on whether the remote desktop will
|
||||
* automatically provide graphical feedback for touches, including touch
|
||||
* events may be necessary for multi-touch interactions to be rendered in
|
||||
* any resulting video.
|
||||
*
|
||||
* @param include_keys
|
||||
* Non-zero if keys pressed and released should be included in the session
|
||||
* recording, zero otherwise. Including key events within the recording may
|
||||
@ -149,14 +127,13 @@ typedef struct guac_recording {
|
||||
* passwords, credit card numbers, etc.
|
||||
*
|
||||
* @return
|
||||
* A new guac_recording structure representing the in-progress
|
||||
* A new guac_common_recording structure representing the in-progress
|
||||
* recording if the recording file has been successfully created and a
|
||||
* recording will be written, NULL otherwise.
|
||||
*/
|
||||
guac_recording* guac_recording_create(guac_client* client,
|
||||
guac_common_recording* guac_common_recording_create(guac_client* client,
|
||||
const char* path, const char* name, int create_path,
|
||||
int include_output, int include_mouse, int include_touch,
|
||||
int include_keys);
|
||||
int include_output, int include_mouse, int include_keys);
|
||||
|
||||
/**
|
||||
* Frees the resources associated with the given in-progress recording. Note
|
||||
@ -165,15 +142,15 @@ guac_recording* guac_recording_create(guac_client* client,
|
||||
* freed when the guac_client is freed.
|
||||
*
|
||||
* @param recording
|
||||
* The guac_recording to free.
|
||||
* The guac_common_recording to free.
|
||||
*/
|
||||
void guac_recording_free(guac_recording* recording);
|
||||
void guac_common_recording_free(guac_common_recording* recording);
|
||||
|
||||
/**
|
||||
* Reports the current mouse position and button state within the recording.
|
||||
*
|
||||
* @param recording
|
||||
* The guac_recording associated with the mouse that has moved.
|
||||
* The guac_common_recording associated with the mouse that has moved.
|
||||
*
|
||||
* @param x
|
||||
* The new X coordinate of the mouse cursor, in pixels.
|
||||
@ -194,52 +171,14 @@ void guac_recording_free(guac_recording* recording);
|
||||
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
|
||||
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
|
||||
*/
|
||||
void guac_recording_report_mouse(guac_recording* recording,
|
||||
void guac_common_recording_report_mouse(guac_common_recording* recording,
|
||||
int x, int y, int button_mask);
|
||||
|
||||
/**
|
||||
* Reports the current state of a touch contact within the recording.
|
||||
*
|
||||
* @param recording
|
||||
* The guac_recording associated with the touch contact that
|
||||
* has changed state.
|
||||
*
|
||||
* @param id
|
||||
* An arbitrary integer ID which uniquely identifies this contact relative
|
||||
* to other active contacts.
|
||||
*
|
||||
* @param x
|
||||
* The X coordinate of the center of the touch contact.
|
||||
*
|
||||
* @param y
|
||||
* The Y coordinate of the center of the touch contact.
|
||||
*
|
||||
* @param x_radius
|
||||
* The X radius of the ellipse covering the general area of the touch
|
||||
* contact, in pixels.
|
||||
*
|
||||
* @param y_radius
|
||||
* The Y radius of the ellipse covering the general area of the touch
|
||||
* contact, in pixels.
|
||||
*
|
||||
* @param angle
|
||||
* The rough angle of clockwise rotation of the general area of the touch
|
||||
* contact, in degrees.
|
||||
*
|
||||
* @param force
|
||||
* The relative force exerted by the touch contact, where 0 is no force
|
||||
* (the touch has been lifted) and 1 is maximum force (the maximum amount
|
||||
* of force representable by the device).
|
||||
*/
|
||||
void guac_recording_report_touch(guac_recording* recording,
|
||||
int id, int x, int y, int x_radius, int y_radius,
|
||||
double angle, double force);
|
||||
|
||||
/**
|
||||
* Reports a change in the state of an individual key within the recording.
|
||||
*
|
||||
* @param recording
|
||||
* The guac_recording associated with the key that was pressed or
|
||||
* The guac_common_recording associated with the key that was pressed or
|
||||
* released.
|
||||
*
|
||||
* @param keysym
|
||||
@ -249,7 +188,7 @@ void guac_recording_report_touch(guac_recording* recording,
|
||||
* Non-zero if the key represented by the given keysym is currently
|
||||
* pressed, zero if it is released.
|
||||
*/
|
||||
void guac_recording_report_key(guac_recording* recording,
|
||||
void guac_common_recording_report_key(guac_common_recording* recording,
|
||||
int keysym, int pressed);
|
||||
|
||||
#endif
|
@ -120,19 +120,6 @@ typedef struct guac_common_surface {
|
||||
*/
|
||||
guac_socket* socket;
|
||||
|
||||
/**
|
||||
* The number of simultaneous touches that this surface can accept, where 0
|
||||
* indicates that the surface does not support touch events at all.
|
||||
*/
|
||||
int touches;
|
||||
|
||||
/**
|
||||
* Non-zero if all graphical updates for this surface should use lossless
|
||||
* compression, 0 otherwise. By default, newly-created surfaces will use
|
||||
* lossy compression when heuristics determine it is appropriate.
|
||||
*/
|
||||
int lossless;
|
||||
|
||||
/**
|
||||
* The X coordinate of the upper-left corner of this layer, in pixels,
|
||||
* relative to its parent layer. This is only applicable to visible
|
||||
@ -499,41 +486,5 @@ void guac_common_surface_flush(guac_common_surface* surface);
|
||||
void guac_common_surface_dup(guac_common_surface* surface, guac_user* user,
|
||||
guac_socket* socket);
|
||||
|
||||
/**
|
||||
* Declares that the given surface should receive touch events. By default,
|
||||
* surfaces are assumed to not expect touch events. This value is advisory, and
|
||||
* the client is not required to honor the declared level of touch support.
|
||||
* Implementations are expected to safely handle or ignore any received touch
|
||||
* events, regardless of the level of touch support declared. regardless of
|
||||
* the level of touch support declared.
|
||||
*
|
||||
* @param surface
|
||||
* The surface to modify.
|
||||
*
|
||||
* @param touches
|
||||
* The number of simultaneous touches that this surface can accept, where 0
|
||||
* indicates that the surface does not support touch events at all.
|
||||
*/
|
||||
void guac_common_surface_set_multitouch(guac_common_surface* surface,
|
||||
int touches);
|
||||
|
||||
/**
|
||||
* Sets the lossless compression policy of the given surface to the given
|
||||
* value. By default, newly-created surfaces will use lossy compression for
|
||||
* graphical updates when heuristics determine that doing so is appropriate.
|
||||
* Specifying a non-zero value here will force all graphical updates to always
|
||||
* use lossless compression, whereas specifying zero will restore the default
|
||||
* policy.
|
||||
*
|
||||
* @param surface
|
||||
* The surface to modify.
|
||||
*
|
||||
* @param lossless
|
||||
* Non-zero if all graphical updates for this surface should use lossless
|
||||
* compression, 0 otherwise.
|
||||
*/
|
||||
void guac_common_surface_set_lossless(guac_common_surface* surface,
|
||||
int lossless);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -166,8 +166,6 @@ void guac_common_display_free(guac_common_display* display) {
|
||||
void guac_common_display_dup(guac_common_display* display, guac_user* user,
|
||||
guac_socket* socket) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
|
||||
pthread_mutex_lock(&display->_lock);
|
||||
|
||||
/* Sunchronize shared cursor */
|
||||
@ -180,33 +178,6 @@ void guac_common_display_dup(guac_common_display* display, guac_user* user,
|
||||
guac_common_display_dup_layers(display->layers, user, socket);
|
||||
guac_common_display_dup_layers(display->buffers, user, socket);
|
||||
|
||||
/* Sends a sync instruction to mark the boundary of the first frame */
|
||||
guac_protocol_send_sync(socket, client->last_sent_timestamp, 1);
|
||||
|
||||
pthread_mutex_unlock(&display->_lock);
|
||||
|
||||
}
|
||||
|
||||
void guac_common_display_set_lossless(guac_common_display* display,
|
||||
int lossless) {
|
||||
|
||||
pthread_mutex_lock(&display->_lock);
|
||||
|
||||
/* Update lossless setting to be applied to all newly-allocated
|
||||
* layers/buffers */
|
||||
display->lossless = lossless;
|
||||
|
||||
/* Update losslessness of all allocated layers/buffers */
|
||||
guac_common_display_layer* current = display->layers;
|
||||
while (current != NULL) {
|
||||
guac_common_surface_set_lossless(current->surface, lossless);
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
/* Update losslessness of default display layer (not included within layers
|
||||
* list) */
|
||||
guac_common_surface_set_lossless(display->default_surface, lossless);
|
||||
|
||||
pthread_mutex_unlock(&display->_lock);
|
||||
|
||||
}
|
||||
@ -316,9 +287,6 @@ guac_common_display_layer* guac_common_display_alloc_layer(
|
||||
guac_common_surface* surface = guac_common_surface_alloc(display->client,
|
||||
display->client->socket, layer, width, height);
|
||||
|
||||
/* Apply current display losslessness */
|
||||
guac_common_surface_set_lossless(surface, display->lossless);
|
||||
|
||||
/* Add layer and surface to list */
|
||||
guac_common_display_layer* display_layer =
|
||||
guac_common_display_add_layer(&display->layers, layer, surface);
|
||||
@ -340,9 +308,6 @@ guac_common_display_layer* guac_common_display_alloc_buffer(
|
||||
guac_common_surface* surface = guac_common_surface_alloc(display->client,
|
||||
display->client->socket, buffer, width, height);
|
||||
|
||||
/* Apply current display losslessness */
|
||||
guac_common_surface_set_lossless(surface, display->lossless);
|
||||
|
||||
/* Add buffer and surface to list */
|
||||
guac_common_display_layer* display_layer =
|
||||
guac_common_display_add_layer(&display->buffers, buffer, surface);
|
||||
@ -389,3 +354,4 @@ void guac_common_display_free_buffer(guac_common_display* display,
|
||||
pthread_mutex_unlock(&display->_lock);
|
||||
|
||||
}
|
||||
|
||||
|
@ -138,70 +138,6 @@ int GUAC_READ_ISO8859_1(const char** input, int remaining) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given reader function, automatically normalizing newline
|
||||
* sequences as Unix-style newline characters ('\n'). All other charaters are
|
||||
* read verbatim.
|
||||
*
|
||||
* @param reader
|
||||
* The reader to use to read the given character.
|
||||
*
|
||||
* @param input
|
||||
* Pointer to the location within the input buffer that the next character
|
||||
* should be read from.
|
||||
*
|
||||
* @param remaining
|
||||
* The number of bytes remaining in the input buffer.
|
||||
*
|
||||
* @return
|
||||
* The codepoint that was read, or zero if the end of the input string has
|
||||
* been reached.
|
||||
*/
|
||||
static int guac_iconv_read_normalized(guac_iconv_read* reader,
|
||||
const char** input, int remaining) {
|
||||
|
||||
/* Read requested character */
|
||||
const char* input_start = *input;
|
||||
int value = reader(input, remaining);
|
||||
|
||||
/* Automatically translate CRLF pairs to simple newlines */
|
||||
if (value == '\r') {
|
||||
|
||||
/* Peek ahead by one character, adjusting remaining bytes relative to
|
||||
* last read */
|
||||
int peek_remaining = remaining - (*input - input_start);
|
||||
const char* peek_input = *input;
|
||||
int peek_value = reader(&peek_input, peek_remaining);
|
||||
|
||||
/* Consider read value to be a newline if we have encountered a "\r\n"
|
||||
* (CRLF) pair */
|
||||
if (peek_value == '\n') {
|
||||
value = '\n';
|
||||
*input = peek_input;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
int GUAC_READ_UTF8_NORMALIZED(const char** input, int remaining) {
|
||||
return guac_iconv_read_normalized(GUAC_READ_UTF8, input, remaining);
|
||||
}
|
||||
|
||||
int GUAC_READ_UTF16_NORMALIZED(const char** input, int remaining) {
|
||||
return guac_iconv_read_normalized(GUAC_READ_UTF16, input, remaining);
|
||||
}
|
||||
|
||||
int GUAC_READ_CP1252_NORMALIZED(const char** input, int remaining) {
|
||||
return guac_iconv_read_normalized(GUAC_READ_CP1252, input, remaining);
|
||||
}
|
||||
|
||||
int GUAC_READ_ISO8859_1_NORMALIZED(const char** input, int remaining) {
|
||||
return guac_iconv_read_normalized(GUAC_READ_ISO8859_1, input, remaining);
|
||||
}
|
||||
|
||||
void GUAC_WRITE_UTF8(char** output, int remaining, int value) {
|
||||
*output += guac_utf8_write(value, *output, remaining);
|
||||
}
|
||||
@ -254,53 +190,3 @@ void GUAC_WRITE_ISO8859_1(char** output, int remaining, int value) {
|
||||
(*output)++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given writer function, automatically writing newline characters
|
||||
* ('\n') as CRLF ("\r\n"). All other charaters are written verbatim.
|
||||
*
|
||||
* @param writer
|
||||
* The writer to use to write the given character.
|
||||
*
|
||||
* @param output
|
||||
* Pointer to the location within the output buffer that the next character
|
||||
* should be written.
|
||||
*
|
||||
* @param remaining
|
||||
* The number of bytes remaining in the output buffer.
|
||||
*
|
||||
* @param value
|
||||
* The codepoint of the character to write.
|
||||
*/
|
||||
static void guac_iconv_write_crlf(guac_iconv_write* writer, char** output,
|
||||
int remaining, int value) {
|
||||
|
||||
if (value != '\n') {
|
||||
writer(output, remaining, value);
|
||||
return;
|
||||
}
|
||||
|
||||
char* output_start = *output;
|
||||
writer(output, remaining, '\r');
|
||||
|
||||
remaining -= *output - output_start;
|
||||
if (remaining > 0)
|
||||
writer(output, remaining, '\n');
|
||||
|
||||
}
|
||||
|
||||
void GUAC_WRITE_UTF8_CRLF(char** output, int remaining, int value) {
|
||||
guac_iconv_write_crlf(GUAC_WRITE_UTF8, output, remaining, value);
|
||||
}
|
||||
|
||||
void GUAC_WRITE_UTF16_CRLF(char** output, int remaining, int value) {
|
||||
guac_iconv_write_crlf(GUAC_WRITE_UTF16, output, remaining, value);
|
||||
}
|
||||
|
||||
void GUAC_WRITE_CP1252_CRLF(char** output, int remaining, int value) {
|
||||
guac_iconv_write_crlf(GUAC_WRITE_CP1252, output, remaining, value);
|
||||
}
|
||||
|
||||
void GUAC_WRITE_ISO8859_1_CRLF(char** output, int remaining, int value) {
|
||||
guac_iconv_write_crlf(GUAC_WRITE_ISO8859_1, output, remaining, value);
|
||||
}
|
||||
|
||||
|
@ -97,15 +97,15 @@ int guac_common_json_write_string(guac_user* user,
|
||||
const char* current = str;
|
||||
for (; *current != '\0'; current++) {
|
||||
|
||||
/* Escape all quotes and back-slashes */
|
||||
if (*current == '"' || *current == '\\') {
|
||||
/* Escape all quotes */
|
||||
if (*current == '"') {
|
||||
|
||||
/* Write any string content up to current character */
|
||||
if (current != str)
|
||||
blob_written |= guac_common_json_write(user, stream,
|
||||
json_state, str, current - str);
|
||||
|
||||
/* Escape the character that was just read */
|
||||
/* Escape the quote that was just read */
|
||||
blob_written |= guac_common_json_write(user, stream,
|
||||
json_state, "\\", 1);
|
||||
|
||||
|
@ -17,11 +17,12 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "guacamole/client.h"
|
||||
#include "guacamole/protocol.h"
|
||||
#include "guacamole/recording.h"
|
||||
#include "guacamole/socket.h"
|
||||
#include "guacamole/timestamp.h"
|
||||
#include "common/recording.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/socket.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#include <direct.h>
|
||||
@ -63,7 +64,7 @@
|
||||
* The file descriptor of the open data file if open succeeded, or -1 on
|
||||
* failure.
|
||||
*/
|
||||
static int guac_recording_open(const char* path,
|
||||
static int guac_common_recording_open(const char* path,
|
||||
const char* name, char* basename, int basename_size) {
|
||||
|
||||
int i;
|
||||
@ -83,7 +84,7 @@ static int guac_recording_open(const char* path,
|
||||
/* Attempt to open recording */
|
||||
int fd = open(basename,
|
||||
O_CREAT | O_EXCL | O_WRONLY,
|
||||
S_IRUSR | S_IWUSR | S_IRGRP);
|
||||
S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Continuously retry with alternate names on failure */
|
||||
if (fd == -1) {
|
||||
@ -102,7 +103,7 @@ static int guac_recording_open(const char* path,
|
||||
/* Retry with newly-suffixed filename */
|
||||
fd = open(basename,
|
||||
O_CREAT | O_EXCL | O_WRONLY,
|
||||
S_IRUSR | S_IWUSR | S_IRGRP);
|
||||
S_IRUSR | S_IWUSR);
|
||||
|
||||
}
|
||||
|
||||
@ -134,17 +135,15 @@ static int guac_recording_open(const char* path,
|
||||
|
||||
}
|
||||
|
||||
guac_recording* guac_recording_create(guac_client* client,
|
||||
guac_common_recording* guac_common_recording_create(guac_client* client,
|
||||
const char* path, const char* name, int create_path,
|
||||
int include_output, int include_mouse, int include_touch,
|
||||
int include_keys) {
|
||||
int include_output, int include_mouse, int include_keys) {
|
||||
|
||||
char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH];
|
||||
|
||||
/* Create path if it does not exist, fail if impossible */
|
||||
#ifndef __MINGW32__
|
||||
if (create_path && mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP)
|
||||
&& errno != EEXIST) {
|
||||
if (create_path && mkdir(path, S_IRWXU) && errno != EEXIST) {
|
||||
#else
|
||||
if (create_path && _mkdir(path) && errno != EEXIST) {
|
||||
#endif
|
||||
@ -154,7 +153,7 @@ guac_recording* guac_recording_create(guac_client* client,
|
||||
}
|
||||
|
||||
/* Attempt to open recording file */
|
||||
int fd = guac_recording_open(path, name, filename, sizeof(filename));
|
||||
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));
|
||||
@ -162,11 +161,10 @@ guac_recording* guac_recording_create(guac_client* client,
|
||||
}
|
||||
|
||||
/* Create recording structure with reference to underlying socket */
|
||||
guac_recording* recording = malloc(sizeof(guac_recording));
|
||||
guac_common_recording* recording = malloc(sizeof(guac_common_recording));
|
||||
recording->socket = guac_socket_open(fd);
|
||||
recording->include_output = include_output;
|
||||
recording->include_mouse = include_mouse;
|
||||
recording->include_touch = include_touch;
|
||||
recording->include_keys = include_keys;
|
||||
|
||||
/* Replace client socket with wrapped recording socket only if including
|
||||
@ -183,7 +181,7 @@ guac_recording* guac_recording_create(guac_client* client,
|
||||
|
||||
}
|
||||
|
||||
void guac_recording_free(guac_recording* recording) {
|
||||
void guac_common_recording_free(guac_common_recording* recording) {
|
||||
|
||||
/* If not including broadcast output, the output socket is not associated
|
||||
* with the client, and must be freed manually */
|
||||
@ -195,7 +193,7 @@ void guac_recording_free(guac_recording* recording) {
|
||||
|
||||
}
|
||||
|
||||
void guac_recording_report_mouse(guac_recording* recording,
|
||||
void guac_common_recording_report_mouse(guac_common_recording* recording,
|
||||
int x, int y, int button_mask) {
|
||||
|
||||
/* Report mouse location only if recording should contain mouse events */
|
||||
@ -205,18 +203,7 @@ void guac_recording_report_mouse(guac_recording* recording,
|
||||
|
||||
}
|
||||
|
||||
void guac_recording_report_touch(guac_recording* recording,
|
||||
int id, int x, int y, int x_radius, int y_radius,
|
||||
double angle, double force) {
|
||||
|
||||
/* Report touches only if recording should contain touch events */
|
||||
if (recording->include_touch)
|
||||
guac_protocol_send_touch(recording->socket, id, x, y,
|
||||
x_radius, y_radius, angle, force, guac_timestamp_current());
|
||||
|
||||
}
|
||||
|
||||
void guac_recording_report_key(guac_recording* recording,
|
||||
void guac_common_recording_report_key(guac_common_recording* recording,
|
||||
int keysym, int pressed) {
|
||||
|
||||
/* Report key state only if recording should contain key events */
|
@ -103,28 +103,6 @@
|
||||
*/
|
||||
#define GUAC_SURFACE_WEBP_BLOCK_SIZE 8
|
||||
|
||||
void guac_common_surface_set_multitouch(guac_common_surface* surface,
|
||||
int touches) {
|
||||
|
||||
pthread_mutex_lock(&surface->_lock);
|
||||
|
||||
surface->touches = touches;
|
||||
guac_protocol_send_set_int(surface->socket, surface->layer,
|
||||
GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH, touches);
|
||||
|
||||
pthread_mutex_unlock(&surface->_lock);
|
||||
|
||||
}
|
||||
|
||||
void guac_common_surface_set_lossless(guac_common_surface* surface,
|
||||
int lossless) {
|
||||
|
||||
pthread_mutex_lock(&surface->_lock);
|
||||
surface->lossless = lossless;
|
||||
pthread_mutex_unlock(&surface->_lock);
|
||||
|
||||
}
|
||||
|
||||
void guac_common_surface_move(guac_common_surface* surface, int x, int y) {
|
||||
|
||||
pthread_mutex_lock(&surface->_lock);
|
||||
@ -543,10 +521,6 @@ static int __guac_common_surface_png_optimality(guac_common_surface* surface,
|
||||
static int __guac_common_surface_should_use_jpeg(guac_common_surface* surface,
|
||||
const guac_common_rect* rect) {
|
||||
|
||||
/* Do not use JPEG if lossless quality is required */
|
||||
if (surface->lossless)
|
||||
return 0;
|
||||
|
||||
/* Calculate the average framerate for the given rect */
|
||||
int framerate = __guac_common_surface_calculate_framerate(surface, rect);
|
||||
|
||||
@ -1819,8 +1793,7 @@ static void __guac_common_surface_flush_to_webp(guac_common_surface* surface,
|
||||
/* Send WebP for rect */
|
||||
guac_client_stream_webp(surface->client, socket, GUAC_COMP_OVER, layer,
|
||||
surface->dirty_rect.x, surface->dirty_rect.y, rect,
|
||||
guac_common_surface_suggest_quality(surface->client),
|
||||
surface->lossless ? 1 : 0);
|
||||
guac_common_surface_suggest_quality(surface->client), 0);
|
||||
|
||||
cairo_surface_destroy(rect);
|
||||
surface->realized = 1;
|
||||
@ -2008,11 +1981,6 @@ void guac_common_surface_dup(guac_common_surface* surface, guac_user* user,
|
||||
guac_protocol_send_move(socket, surface->layer,
|
||||
surface->parent, surface->x, surface->y, surface->z);
|
||||
|
||||
/* Synchronize multi-touch support level */
|
||||
guac_protocol_send_set_int(surface->socket, surface->layer,
|
||||
GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH,
|
||||
surface->touches);
|
||||
|
||||
}
|
||||
|
||||
/* Sync size to new socket */
|
||||
|
@ -33,12 +33,8 @@ ACLOCAL_AMFLAGS = -I m4
|
||||
check_PROGRAMS = test_common
|
||||
TESTS = $(check_PROGRAMS)
|
||||
|
||||
noinst_HEADERS = \
|
||||
iconv/convert-test-data.h
|
||||
|
||||
test_common_SOURCES = \
|
||||
iconv/convert.c \
|
||||
iconv/convert-test-data.c \
|
||||
rect/clip_and_split.c \
|
||||
rect/constrain.c \
|
||||
rect/expand_to_grid.c \
|
||||
|
@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "common/iconv.h"
|
||||
#include "convert-test-data.h"
|
||||
|
||||
encoding_test_parameters test_params[NUM_SUPPORTED_ENCODINGS] = {
|
||||
|
||||
/*
|
||||
* UTF-8
|
||||
*/
|
||||
|
||||
{
|
||||
"UTF-8",
|
||||
GUAC_READ_UTF8, GUAC_READ_UTF8_NORMALIZED,
|
||||
GUAC_WRITE_UTF8, GUAC_WRITE_UTF8_CRLF,
|
||||
.test_mixed = TEST_STRING(
|
||||
"pap\xC3\xA0 \xC3\xA8 bello\n"
|
||||
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
|
||||
"pap\xC3\xA0 \xC3\xA8 bello\n"
|
||||
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
|
||||
"pap\xC3\xA0 \xC3\xA8 bello"
|
||||
),
|
||||
.test_unix = TEST_STRING(
|
||||
"pap\xC3\xA0 \xC3\xA8 bello\n"
|
||||
"pap\xC3\xA0 \xC3\xA8 bello\n"
|
||||
"pap\xC3\xA0 \xC3\xA8 bello\n"
|
||||
"pap\xC3\xA0 \xC3\xA8 bello\n"
|
||||
"pap\xC3\xA0 \xC3\xA8 bello"
|
||||
),
|
||||
.test_windows = TEST_STRING(
|
||||
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
|
||||
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
|
||||
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
|
||||
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
|
||||
"pap\xC3\xA0 \xC3\xA8 bello"
|
||||
)
|
||||
},
|
||||
|
||||
/*
|
||||
* UTF-16
|
||||
*/
|
||||
|
||||
{
|
||||
"UTF-16",
|
||||
GUAC_READ_UTF16, GUAC_READ_UTF16_NORMALIZED,
|
||||
GUAC_WRITE_UTF16, GUAC_WRITE_UTF16_CRLF,
|
||||
.test_mixed = TEST_STRING(
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00"
|
||||
"\x00"
|
||||
),
|
||||
.test_unix = TEST_STRING(
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00"
|
||||
"\x00"
|
||||
),
|
||||
.test_windows = TEST_STRING(
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
|
||||
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00"
|
||||
"\x00"
|
||||
)
|
||||
},
|
||||
|
||||
/*
|
||||
* ISO 8859-1
|
||||
*/
|
||||
|
||||
{
|
||||
"ISO 8859-1",
|
||||
GUAC_READ_ISO8859_1, GUAC_READ_ISO8859_1_NORMALIZED,
|
||||
GUAC_WRITE_ISO8859_1, GUAC_WRITE_ISO8859_1_CRLF,
|
||||
.test_mixed = TEST_STRING(
|
||||
"pap\xE0 \xE8 bello\n"
|
||||
"pap\xE0 \xE8 bello\r\n"
|
||||
"pap\xE0 \xE8 bello\n"
|
||||
"pap\xE0 \xE8 bello\r\n"
|
||||
"pap\xE0 \xE8 bello"
|
||||
),
|
||||
.test_unix = TEST_STRING(
|
||||
"pap\xE0 \xE8 bello\n"
|
||||
"pap\xE0 \xE8 bello\n"
|
||||
"pap\xE0 \xE8 bello\n"
|
||||
"pap\xE0 \xE8 bello\n"
|
||||
"pap\xE0 \xE8 bello"
|
||||
),
|
||||
.test_windows = TEST_STRING(
|
||||
"pap\xE0 \xE8 bello\r\n"
|
||||
"pap\xE0 \xE8 bello\r\n"
|
||||
"pap\xE0 \xE8 bello\r\n"
|
||||
"pap\xE0 \xE8 bello\r\n"
|
||||
"pap\xE0 \xE8 bello"
|
||||
)
|
||||
},
|
||||
|
||||
/*
|
||||
* CP-1252
|
||||
*/
|
||||
|
||||
{
|
||||
"CP-1252",
|
||||
GUAC_READ_CP1252, GUAC_READ_CP1252_NORMALIZED,
|
||||
GUAC_WRITE_CP1252, GUAC_WRITE_CP1252_CRLF,
|
||||
.test_mixed = TEST_STRING(
|
||||
"pap\xE0 \xE8 bello\n"
|
||||
"pap\xE0 \xE8 bello\r\n"
|
||||
"pap\xE0 \xE8 bello\n"
|
||||
"pap\xE0 \xE8 bello\r\n"
|
||||
"pap\xE0 \xE8 bello"
|
||||
),
|
||||
.test_unix = TEST_STRING(
|
||||
"pap\xE0 \xE8 bello\n"
|
||||
"pap\xE0 \xE8 bello\n"
|
||||
"pap\xE0 \xE8 bello\n"
|
||||
"pap\xE0 \xE8 bello\n"
|
||||
"pap\xE0 \xE8 bello"
|
||||
),
|
||||
.test_windows = TEST_STRING(
|
||||
"pap\xE0 \xE8 bello\r\n"
|
||||
"pap\xE0 \xE8 bello\r\n"
|
||||
"pap\xE0 \xE8 bello\r\n"
|
||||
"pap\xE0 \xE8 bello\r\n"
|
||||
"pap\xE0 \xE8 bello"
|
||||
)
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "common/iconv.h"
|
||||
|
||||
/**
|
||||
* Representation of test string data and its length in bytes.
|
||||
*/
|
||||
typedef struct test_string {
|
||||
|
||||
/**
|
||||
* The raw content of the test string.
|
||||
*/
|
||||
unsigned char* buffer;
|
||||
|
||||
/**
|
||||
* The number of bytes within the test string, including null terminator.
|
||||
*/
|
||||
int size;
|
||||
|
||||
} test_string;
|
||||
|
||||
/**
|
||||
* Convenience macro which statically-initializes a test_string with the given
|
||||
* string value, automatically calculating its size in bytes.
|
||||
*
|
||||
* @param value
|
||||
* The string value.
|
||||
*/
|
||||
#define TEST_STRING(value) { \
|
||||
.buffer = (unsigned char*) (value), \
|
||||
.size = sizeof(value) \
|
||||
}
|
||||
|
||||
/**
|
||||
* The parameters applicable to a unit test for a particular encoding supported
|
||||
* by guac_iconv().
|
||||
*/
|
||||
typedef struct encoding_test_parameters {
|
||||
|
||||
/**
|
||||
* The human-readable name of this encoding. This will be logged to the
|
||||
* test suite log to assist with debugging test failures.
|
||||
*/
|
||||
const char* name;
|
||||
|
||||
/**
|
||||
* Reader function which reads using this encoding and does not perform any
|
||||
* transformation on newline characters.
|
||||
*/
|
||||
guac_iconv_read* reader;
|
||||
|
||||
/**
|
||||
* Reader function which reads using this encoding and automatically
|
||||
* normalizes newline sequences to Unix-style newline characters.
|
||||
*/
|
||||
guac_iconv_read* reader_normalized;
|
||||
|
||||
/**
|
||||
* Writer function which writes using this encoding and does not perform
|
||||
* any transformation on newline characters.
|
||||
*/
|
||||
guac_iconv_write* writer;
|
||||
|
||||
/**
|
||||
* Writer function which writes using this encoding, but writes newline
|
||||
* characters as CRLF sequences.
|
||||
*/
|
||||
guac_iconv_write* writer_crlf;
|
||||
|
||||
/**
|
||||
* A test string having both Windows- and Unix-style line endings. Except
|
||||
* for the line endings, the characters represented within this test string
|
||||
* must be identical to all other test strings.
|
||||
*/
|
||||
test_string test_mixed;
|
||||
|
||||
/**
|
||||
* A test string having only Unix-style line endings. Except for the line
|
||||
* endings, the characters represented within this test string must be
|
||||
* identical to all other test strings.
|
||||
*/
|
||||
test_string test_unix;
|
||||
|
||||
/**
|
||||
* A test string having only Windows-style line endings. Except for the
|
||||
* line endings, the characters represented within this test string must be
|
||||
* identical to all other test strings.
|
||||
*/
|
||||
test_string test_windows;
|
||||
|
||||
} encoding_test_parameters;
|
||||
|
||||
/**
|
||||
* The total number of encodings supported by guac_iconv().
|
||||
*/
|
||||
#define NUM_SUPPORTED_ENCODINGS 4
|
||||
|
||||
/**
|
||||
* Test parameters for each supported encoding. The test strings included each
|
||||
* consist of five repeated lines of "papà è bello", omitting the line ending
|
||||
* of the final line.
|
||||
*/
|
||||
extern encoding_test_parameters test_params[NUM_SUPPORTED_ENCODINGS];
|
||||
|
@ -18,10 +18,48 @@
|
||||
*/
|
||||
|
||||
#include "common/iconv.h"
|
||||
#include "convert-test-data.h"
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* UTF8 for "papà è bello".
|
||||
*/
|
||||
unsigned char test_string_utf8[] = {
|
||||
'p', 'a', 'p', 0xC3, 0xA0, ' ',
|
||||
0xC3, 0xA8, ' ',
|
||||
'b', 'e', 'l', 'l', 'o',
|
||||
0x00
|
||||
};
|
||||
|
||||
/**
|
||||
* UTF16 for "papà è bello".
|
||||
*/
|
||||
unsigned char test_string_utf16[] = {
|
||||
'p', 0x00, 'a', 0x00, 'p', 0x00, 0xE0, 0x00, ' ', 0x00,
|
||||
0xE8, 0x00, ' ', 0x00,
|
||||
'b', 0x00, 'e', 0x00, 'l', 0x00, 'l', 0x00, 'o', 0x00,
|
||||
0x00, 0x00
|
||||
};
|
||||
|
||||
/**
|
||||
* ISO-8859-1 for "papà è bello".
|
||||
*/
|
||||
unsigned char test_string_iso8859_1[] = {
|
||||
'p', 'a', 'p', 0xE0, ' ',
|
||||
0xE8, ' ',
|
||||
'b', 'e', 'l', 'l', 'o',
|
||||
0x00
|
||||
};
|
||||
|
||||
/**
|
||||
* CP1252 for "papà è bello".
|
||||
*/
|
||||
unsigned char test_string_cp1252[] = {
|
||||
'p', 'a', 'p', 0xE0, ' ',
|
||||
0xE8, ' ',
|
||||
'b', 'e', 'l', 'l', 'o',
|
||||
0x00
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests that conversion between character sets using the given guac_iconv_read
|
||||
@ -31,20 +69,25 @@
|
||||
* The guac_iconv_read implementation to use to read the input string.
|
||||
*
|
||||
* @param in_string
|
||||
* A pointer to the test_string structure describing the input string being
|
||||
* tested.
|
||||
* A pointer to the beginning of the input string.
|
||||
*
|
||||
* @param in_length
|
||||
* The size of the input string in bytes.
|
||||
*
|
||||
* @param writer
|
||||
* The guac_iconv_write implementation to use to write the output string
|
||||
* (the converted input string).
|
||||
*
|
||||
* @param out_string
|
||||
* A pointer to the test_string structure describing the expected result of
|
||||
* the conversion.
|
||||
* A pointer to the beginning of a string which contains the expected
|
||||
* result of the conversion.
|
||||
*
|
||||
* @param out_length
|
||||
* The size of the expected result in bytes.
|
||||
*/
|
||||
static void verify_conversion(
|
||||
guac_iconv_read* reader, test_string* in_string,
|
||||
guac_iconv_write* writer, test_string* out_string) {
|
||||
guac_iconv_read* reader, unsigned char* in_string, int in_length,
|
||||
guac_iconv_write* writer, unsigned char* out_string, int out_length) {
|
||||
|
||||
char output[4096];
|
||||
char input[4096];
|
||||
@ -52,78 +95,91 @@ static void verify_conversion(
|
||||
const char* current_input = input;
|
||||
char* current_output = output;
|
||||
|
||||
memcpy(input, in_string->buffer, in_string->size);
|
||||
memcpy(input, in_string, in_length);
|
||||
guac_iconv(reader, ¤t_input, sizeof(input),
|
||||
writer, ¤t_output, sizeof(output));
|
||||
|
||||
/* Verify output length */
|
||||
CU_ASSERT_EQUAL(out_string->size, current_output - output);
|
||||
CU_ASSERT_EQUAL(out_length, current_output - output);
|
||||
|
||||
/* Verify entire input read */
|
||||
CU_ASSERT_EQUAL(in_string->size, current_input - input);
|
||||
CU_ASSERT_EQUAL(in_length, current_input - input);
|
||||
|
||||
/* Verify output content */
|
||||
CU_ASSERT_EQUAL(0, memcmp(output, out_string->buffer, out_string->size));
|
||||
CU_ASSERT_EQUAL(0, memcmp(output, out_string, out_length));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test which verifies that every supported encoding can be correctly converted
|
||||
* to every other supported encoding, with all line endings preserved verbatim
|
||||
* (not normalized).
|
||||
* Tests which verifies conversion of UTF-8 to itself.
|
||||
*/
|
||||
void test_iconv__preserve() {
|
||||
for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
|
||||
for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
|
||||
|
||||
encoding_test_parameters* from = &test_params[i];
|
||||
encoding_test_parameters* to = &test_params[j];
|
||||
|
||||
printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
|
||||
verify_conversion(from->reader, &from->test_mixed,
|
||||
to->writer, &to->test_mixed);
|
||||
|
||||
}
|
||||
}
|
||||
void test_iconv__utf8_to_utf8() {
|
||||
verify_conversion(
|
||||
GUAC_READ_UTF8, test_string_utf8, sizeof(test_string_utf8),
|
||||
GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test which verifies that every supported encoding can be correctly converted
|
||||
* to every other supported encoding, normalizing all line endings to
|
||||
* Unix-style line endings.
|
||||
* Tests which verifies conversion of UTF-16 to UTF-8.
|
||||
*/
|
||||
void test_iconv__normalize_unix() {
|
||||
for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
|
||||
for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
|
||||
|
||||
encoding_test_parameters* from = &test_params[i];
|
||||
encoding_test_parameters* to = &test_params[j];
|
||||
|
||||
printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
|
||||
verify_conversion(from->reader_normalized, &from->test_mixed,
|
||||
to->writer, &to->test_unix);
|
||||
|
||||
}
|
||||
}
|
||||
void test_iconv__utf8_to_utf16() {
|
||||
verify_conversion(
|
||||
GUAC_READ_UTF8, test_string_utf8, sizeof(test_string_utf8),
|
||||
GUAC_WRITE_UTF16, test_string_utf16, sizeof(test_string_utf16));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test which verifies that every supported encoding can be correctly converted
|
||||
* to every other supported encoding, normalizing all line endings to
|
||||
* Windows-style line endings.
|
||||
* Tests which verifies conversion of UTF-16 to itself.
|
||||
*/
|
||||
void test_iconv__normalize_crlf() {
|
||||
for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
|
||||
for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
|
||||
|
||||
encoding_test_parameters* from = &test_params[i];
|
||||
encoding_test_parameters* to = &test_params[j];
|
||||
|
||||
printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
|
||||
verify_conversion(from->reader_normalized, &from->test_mixed,
|
||||
to->writer_crlf, &to->test_windows);
|
||||
|
||||
}
|
||||
}
|
||||
void test_iconv__utf16_to_utf16() {
|
||||
verify_conversion(
|
||||
GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16),
|
||||
GUAC_WRITE_UTF16, test_string_utf16, sizeof(test_string_utf16));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests which verifies conversion of UTF-8 to UTF-16.
|
||||
*/
|
||||
void test_iconv__utf16_to_utf8() {
|
||||
verify_conversion(
|
||||
GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16),
|
||||
GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests which verifies conversion of UTF-16 to ISO 8859-1.
|
||||
*/
|
||||
void test_iconv__utf16_to_iso8859_1() {
|
||||
verify_conversion(
|
||||
GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16),
|
||||
GUAC_WRITE_ISO8859_1, test_string_iso8859_1, sizeof(test_string_iso8859_1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests which verifies conversion of UTF-16 to CP1252.
|
||||
*/
|
||||
void test_iconv__utf16_to_cp1252() {
|
||||
verify_conversion(
|
||||
GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16),
|
||||
GUAC_WRITE_CP1252, test_string_cp1252, sizeof(test_string_cp1252));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests which verifies conversion of CP1252 to UTF-8.
|
||||
*/
|
||||
void test_iconv__cp1252_to_utf8() {
|
||||
verify_conversion(
|
||||
GUAC_READ_CP1252, test_string_cp1252, sizeof(test_string_cp1252),
|
||||
GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests which verifies conversion of ISO 8859-1 to UTF-8.
|
||||
*/
|
||||
void test_iconv__iso8859_1_to_utf8() {
|
||||
verify_conversion(
|
||||
GUAC_READ_ISO8859_1, test_string_iso8859_1, sizeof(test_string_iso8859_1),
|
||||
GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8));
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,115 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
##
|
||||
## @fn build-all.sh
|
||||
##
|
||||
## Builds the source of guacamole-server and its various core protocol library
|
||||
## dependencies.
|
||||
##
|
||||
|
||||
# Pre-populate build control variables such that the custom build prefix is
|
||||
# used for C headers, locating libraries, etc.
|
||||
export CFLAGS="-I${PREFIX_DIR}/include"
|
||||
export LDFLAGS="-L${PREFIX_DIR}/lib"
|
||||
export PKG_CONFIG_PATH="${PREFIX_DIR}/lib/pkgconfig"
|
||||
|
||||
# Ensure thread stack size will be 8 MB (glibc's default on Linux) rather than
|
||||
# 128 KB (musl's default)
|
||||
export LDFLAGS="$LDFLAGS -Wl,-z,stack-size=8388608"
|
||||
|
||||
##
|
||||
## Builds and installs the source at the given git repository, automatically
|
||||
## switching to the version of the source at the tag/commit that matches the
|
||||
## given pattern.
|
||||
##
|
||||
## @param URL
|
||||
## The URL of the git repository that the source should be downloaded from.
|
||||
##
|
||||
## @param PATTERN
|
||||
## The Perl-compatible regular expression that the tag must match. If no
|
||||
## tag matches the regular expression, the pattern is assumed to be an
|
||||
## exact reference to a commit, branch, etc. acceptable by git checkout.
|
||||
##
|
||||
## @param ...
|
||||
## Any additional command-line options that should be provided to CMake or
|
||||
## the configure script.
|
||||
##
|
||||
install_from_git() {
|
||||
|
||||
URL="$1"
|
||||
PATTERN="$2"
|
||||
shift 2
|
||||
|
||||
# Calculate top-level directory name of resulting repository from the
|
||||
# provided URL
|
||||
REPO_DIR="$(basename "$URL" .git)"
|
||||
|
||||
# Allow dependencies to be manually omitted with the tag/commit pattern "NO"
|
||||
if [ "$PATTERN" = "NO" ]; then
|
||||
echo "NOT building $REPO_DIR (explicitly skipped)"
|
||||
return
|
||||
fi
|
||||
|
||||
# Clone repository and change to top-level directory of source
|
||||
cd /tmp
|
||||
git clone "$URL"
|
||||
cd $REPO_DIR/
|
||||
|
||||
# Locate tag/commit based on provided pattern
|
||||
VERSION="$(git tag -l --sort=-v:refname | grep -Px -m1 "$PATTERN" \
|
||||
|| echo "$PATTERN")"
|
||||
|
||||
# Switch to desired version of source
|
||||
echo "Building $REPO_DIR @ $VERSION ..."
|
||||
git -c advice.detachedHead=false checkout "$VERSION"
|
||||
|
||||
# Configure build using CMake or GNU Autotools, whichever happens to be
|
||||
# used by the library being built
|
||||
if [ -e CMakeLists.txt ]; then
|
||||
cmake -DCMAKE_INSTALL_PREFIX:PATH="$PREFIX_DIR" "$@" .
|
||||
else
|
||||
[ -e configure ] || autoreconf -fi
|
||||
./configure --prefix="$PREFIX_DIR" "$@"
|
||||
fi
|
||||
|
||||
# Build and install
|
||||
make && make install
|
||||
|
||||
}
|
||||
|
||||
#
|
||||
# Build and install core protocol library dependencies
|
||||
#
|
||||
|
||||
install_from_git "https://github.com/FreeRDP/FreeRDP" "$WITH_FREERDP" $FREERDP_OPTS
|
||||
install_from_git "https://github.com/libssh2/libssh2" "$WITH_LIBSSH2" $LIBSSH2_OPTS
|
||||
install_from_git "https://github.com/seanmiddleditch/libtelnet" "$WITH_LIBTELNET" $LIBTELNET_OPTS
|
||||
install_from_git "https://github.com/LibVNC/libvncserver" "$WITH_LIBVNCCLIENT" $LIBVNCCLIENT_OPTS
|
||||
install_from_git "https://libwebsockets.org/repo/libwebsockets" "$WITH_LIBWEBSOCKETS" $LIBWEBSOCKETS_OPTS
|
||||
|
||||
#
|
||||
# Build guacamole-server
|
||||
#
|
||||
|
||||
cd "$BUILD_DIR"
|
||||
autoreconf -fi && ./configure --prefix="$PREFIX_DIR" $GUACAMOLE_SERVER_OPTS
|
||||
make && make install
|
||||
|
59
doc/libguac-terminal/Doxyfile.in → src/guacd-docker/bin/build-guacd.sh
Normal file → Executable file
59
doc/libguac-terminal/Doxyfile.in → src/guacd-docker/bin/build-guacd.sh
Normal file → Executable file
@ -1,3 +1,4 @@
|
||||
#!/bin/sh -e
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
@ -17,42 +18,32 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
#
|
||||
# Project name / version
|
||||
#
|
||||
##
|
||||
## @fn build-guacd.sh
|
||||
##
|
||||
## Builds the source of guacamole-server, automatically creating any required
|
||||
## symbolic links for the proper loading of FreeRDP plugins.
|
||||
##
|
||||
## @param BUILD_DIR
|
||||
## The directory which currently contains the guacamole-server source and
|
||||
## in which the build should be performed.
|
||||
##
|
||||
## @param PREFIX_DIR
|
||||
## The directory prefix into which the build artifacts should be installed
|
||||
## in which the build should be performed. This is passed to the --prefix
|
||||
## option of `configure`.
|
||||
##
|
||||
|
||||
PROJECT_NAME = libguac-terminal
|
||||
PROJECT_NUMBER = @PACKAGE_VERSION@
|
||||
BUILD_DIR="$1"
|
||||
PREFIX_DIR="$2"
|
||||
|
||||
#
|
||||
# Warn about undocumented parameters and return values, but do not fill output
|
||||
# with verbose progress info.
|
||||
# Build guacamole-server
|
||||
#
|
||||
|
||||
QUIET = YES
|
||||
WARN_NO_PARAMDOC = YES
|
||||
|
||||
#
|
||||
# Output format
|
||||
#
|
||||
|
||||
ALPHABETICAL_INDEX = YES
|
||||
GENERATE_HTML = YES
|
||||
GENERATE_LATEX = NO
|
||||
OPTIMIZE_OUTPUT_FOR_C = YES
|
||||
OUTPUT_DIRECTORY = doxygen-output
|
||||
RECURSIVE = YES
|
||||
SHOW_INCLUDE_FILES = NO
|
||||
|
||||
#
|
||||
# Input format
|
||||
#
|
||||
|
||||
CASE_SENSE_NAMES = YES
|
||||
FILE_PATTERNS = *.h
|
||||
STRIP_FROM_PATH = ../../src/terminal
|
||||
INPUT = ../../src/terminal/terminal/terminal.h
|
||||
JAVADOC_AUTOBRIEF = YES
|
||||
TAB_SIZE = 4
|
||||
TYPEDEF_HIDES_STRUCT = YES
|
||||
|
||||
cd "$BUILD_DIR"
|
||||
autoreconf -fi
|
||||
./configure --prefix="$PREFIX_DIR" --disable-guaclog --with-freerdp-plugin-dir="$PREFIX_DIR/lib/freerdp2"
|
||||
make
|
||||
make install
|
||||
ldconfig
|
86
src/guacd-docker/bin/link-freerdp-plugins.sh
Executable file
86
src/guacd-docker/bin/link-freerdp-plugins.sh
Executable file
@ -0,0 +1,86 @@
|
||||
#!/bin/sh -e
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
##
|
||||
## @fn link-freerdp-plugins.sh
|
||||
##
|
||||
## Automatically creates any required symbolic links for the proper loading of
|
||||
## the given FreeRDP plugins. If a given plugin is already in the correct
|
||||
## directory, no link is created for that plugin.
|
||||
##
|
||||
## @param ...
|
||||
## The FreeRDP plugins to add links for.
|
||||
##
|
||||
|
||||
##
|
||||
## Given the full path to a FreeRDP plugin, locates the base directory of the
|
||||
## associated FreeRDP installation (where the FreeRDP library .so files are
|
||||
## located), printing the result to STDOUT. If the directory cannot be
|
||||
## determined, an error is printed.
|
||||
##
|
||||
## @param PLUGIN_FILE
|
||||
## The full path to the FreeRDP plugin to check.
|
||||
##
|
||||
where_is_freerdp() {
|
||||
|
||||
PLUGIN_FILE="$1"
|
||||
|
||||
# Determine the location of all libfreerdp* libraries explicitly linked
|
||||
# to given file
|
||||
PATHS="$(ldd "$PLUGIN_FILE" \
|
||||
| awk '/=>/{print $(NF-1)}' \
|
||||
| grep 'libfreerdp' \
|
||||
| xargs -r dirname \
|
||||
| xargs -r realpath \
|
||||
| sort -u)"
|
||||
|
||||
# Verify that exactly one location was found
|
||||
if [ "$(echo "$PATHS" | wc -l)" != 1 ]; then
|
||||
echo "$1: Unable to locate FreeRDP install location." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$PATHS"
|
||||
|
||||
}
|
||||
|
||||
#
|
||||
# Create symbolic links as necessary to include all given plugins within the
|
||||
# search path of FreeRDP
|
||||
#
|
||||
|
||||
while [ -n "$1" ]; do
|
||||
|
||||
# Determine correct install location for FreeRDP plugins
|
||||
FREERDP_DIR="$(where_is_freerdp "$1")"
|
||||
FREERDP_PLUGIN_DIR="${FREERDP_DIR}/freerdp2"
|
||||
|
||||
# Add symbolic link if necessary
|
||||
if [ ! -e "$FREERDP_PLUGIN_DIR/$(basename "$1")" ]; then
|
||||
mkdir -p "$FREERDP_PLUGIN_DIR"
|
||||
ln -s "$1" "$FREERDP_PLUGIN_DIR"
|
||||
else
|
||||
echo "$1: Already in correct directory." >&2
|
||||
fi
|
||||
|
||||
shift
|
||||
|
||||
done
|
||||
|
@ -21,7 +21,7 @@
|
||||
##
|
||||
## @fn list-dependencies.sh
|
||||
##
|
||||
## Lists the Alpine Linux package names for all library dependencies of the
|
||||
## Lists the Debian/Ubuntu package names for all library dependencies of the
|
||||
## given binaries. Each package is only listed once, even if multiple binaries
|
||||
## provided by the same package are given.
|
||||
##
|
||||
@ -35,17 +35,14 @@ while [ -n "$1" ]; do
|
||||
ldd "$1" | grep -v 'libguac' | awk '/=>/{print $(NF-1)}' \
|
||||
| while read LIBRARY; do
|
||||
|
||||
# List the package providing that library, if any
|
||||
apk info -W "$LIBRARY" 2> /dev/null \
|
||||
| grep 'is owned by' | grep -o '[^ ]*$' || true
|
||||
# Determine the Debian package which is associated with that
|
||||
# library, if any
|
||||
dpkg-query -S "$LIBRARY" 2> /dev/null || true
|
||||
|
||||
done
|
||||
|
||||
# Next binary
|
||||
shift
|
||||
|
||||
# Strip the "-VERSION" suffix from each package name, listing each resulting
|
||||
# package uniquely ("apk add" cannot handle package names that include the
|
||||
# version number)
|
||||
done | sed 's/\(.*\)-[0-9]\+\..*$/\1/' | sort -u
|
||||
done | cut -f1 -d: | sort -u
|
||||
|
||||
|
@ -176,8 +176,8 @@ guacd_config* guacd_conf_load() {
|
||||
return NULL;
|
||||
|
||||
/* Load defaults */
|
||||
conf->bind_host = strdup(GUACD_DEFAULT_BIND_HOST);
|
||||
conf->bind_port = strdup(GUACD_DEFAULT_BIND_PORT);
|
||||
conf->bind_host = NULL;
|
||||
conf->bind_port = strdup("4822");
|
||||
conf->pidfile = NULL;
|
||||
conf->foreground = 0;
|
||||
conf->print_version = 0;
|
||||
|
@ -24,18 +24,6 @@
|
||||
|
||||
#include <guacamole/client.h>
|
||||
|
||||
/**
|
||||
* The default host that guacd should bind to, if no other host is explicitly
|
||||
* specified.
|
||||
*/
|
||||
#define GUACD_DEFAULT_BIND_HOST "localhost"
|
||||
|
||||
/**
|
||||
* The default port that guacd should bind to, if no other port is explicitly
|
||||
* specified.
|
||||
*/
|
||||
#define GUACD_DEFAULT_BIND_PORT "4822"
|
||||
|
||||
/**
|
||||
* The contents of a guacd configuration file.
|
||||
*/
|
||||
|
@ -278,13 +278,10 @@ static int guacd_route_connection(guacd_proc_map* map, guac_socket* socket) {
|
||||
proc = guacd_proc_map_retrieve(map, identifier);
|
||||
new_process = 0;
|
||||
|
||||
/* Warn and ward off client if requested connection does not exist */
|
||||
if (proc == NULL) {
|
||||
guacd_log(GUAC_LOG_INFO, "Connection \"%s\" does not exist", identifier);
|
||||
guac_protocol_send_error(socket, "No such connection.",
|
||||
GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
|
||||
}
|
||||
|
||||
/* Warn if requested connection does not exist */
|
||||
if (proc == NULL)
|
||||
guacd_log(GUAC_LOG_INFO, "Connection \"%s\" does not exist.",
|
||||
identifier);
|
||||
else
|
||||
guacd_log(GUAC_LOG_INFO, "Joining existing connection \"%s\"",
|
||||
identifier);
|
||||
|
@ -381,15 +381,10 @@ int main(int argc, char* argv[]) {
|
||||
CRYPTO_set_locking_callback(guacd_openssl_locking_callback);
|
||||
#endif
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
/* Init OpenSSL for OpenSSL Versions < 1.1.0 */
|
||||
/* Init SSL */
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
ssl_context = SSL_CTX_new(SSLv23_server_method());
|
||||
#else
|
||||
/* Set up OpenSSL for OpenSSL Versions >= 1.1.0 */
|
||||
ssl_context = SSL_CTX_new(TLS_server_method());
|
||||
#endif
|
||||
|
||||
/* Load key */
|
||||
if (config->key_file != NULL) {
|
||||
|
@ -135,15 +135,6 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
||||
|
||||
#else
|
||||
|
||||
/* For libavcodec < 57.37.100: input/output was not decoupled and static
|
||||
* allocation of AVPacket was supported.
|
||||
*
|
||||
* NOTE: Since dynamic allocation of AVPacket was added before this point (in
|
||||
* 57.12.100) and static allocation was deprecated later (in 58.133.100), it is
|
||||
* convenient to tie static vs. dynamic allocation to the old vs. new I/O
|
||||
* mechanism and avoid further complicating the version comparison logic. */
|
||||
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 37, 100)
|
||||
|
||||
/* Init video packet */
|
||||
AVPacket packet;
|
||||
av_init_packet(&packet);
|
||||
@ -152,6 +143,9 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
||||
packet.data = NULL;
|
||||
packet.size = 0;
|
||||
|
||||
/* For libavcodec < 57.37.100: input/output was not decoupled */
|
||||
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57,37,100)
|
||||
|
||||
/* Write frame to video */
|
||||
int got_data;
|
||||
if (avcodec_encode_video2(video->context, &packet, frame, &got_data) < 0) {
|
||||
@ -182,25 +176,19 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
AVPacket* packet = av_packet_alloc();
|
||||
if (packet == NULL)
|
||||
return -1;
|
||||
|
||||
/* Flush all available packets */
|
||||
int got_data = 0;
|
||||
while (avcodec_receive_packet(video->context, packet) == 0) {
|
||||
while (avcodec_receive_packet(video->context, &packet) == 0) {
|
||||
|
||||
/* Data was received */
|
||||
got_data = 1;
|
||||
|
||||
/* Attempt to write data to output file */
|
||||
guacenc_write_packet(video, (void*) packet, packet->size);
|
||||
av_packet_unref(packet);
|
||||
guacenc_write_packet(video, (void*) &packet, packet.size);
|
||||
av_packet_unref(&packet);
|
||||
|
||||
}
|
||||
|
||||
av_packet_free(&packet);
|
||||
|
||||
#endif
|
||||
|
||||
/* Frame may have been queued for later writing / reordering */
|
||||
@ -213,7 +201,7 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
||||
#endif
|
||||
}
|
||||
|
||||
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* codec,
|
||||
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec,
|
||||
int bitrate, int width, int height, int gop_size, int qmax, int qmin,
|
||||
int pix_fmt, AVRational time_base) {
|
||||
|
||||
@ -249,7 +237,7 @@ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* co
|
||||
}
|
||||
|
||||
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
|
||||
const AVCodec *codec, AVDictionary **options,
|
||||
AVCodec *codec, AVDictionary **options,
|
||||
AVStream* stream) {
|
||||
|
||||
int ret = avcodec_open2(avcodec_context, codec, options);
|
||||
|
@ -128,7 +128,7 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame);
|
||||
* The pointer to the configured AVCodecContext.
|
||||
*
|
||||
*/
|
||||
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* codec,
|
||||
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec,
|
||||
int bitrate, int width, int height, int gop_size, int qmax, int qmin,
|
||||
int pix_fmt, AVRational time_base);
|
||||
|
||||
@ -158,7 +158,7 @@ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* co
|
||||
* Zero on success, a negative value on error.
|
||||
*/
|
||||
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
|
||||
const AVCodec *codec, AVDictionary **options,
|
||||
AVCodec *codec, AVDictionary **options,
|
||||
AVStream* stream);
|
||||
|
||||
#endif
|
||||
|
@ -47,7 +47,7 @@
|
||||
guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
|
||||
int width, int height, int bitrate) {
|
||||
|
||||
const AVOutputFormat *container_format;
|
||||
AVOutputFormat *container_format;
|
||||
AVFormatContext *container_format_context;
|
||||
AVStream *video_stream;
|
||||
int ret;
|
||||
@ -63,7 +63,7 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
|
||||
container_format = container_format_context->oformat;
|
||||
|
||||
/* Pull codec based on name */
|
||||
const AVCodec* codec = avcodec_find_encoder_by_name(codec_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);
|
||||
|
@ -44,7 +44,6 @@ libguacinc_HEADERS = \
|
||||
guacamole/client-types.h \
|
||||
guacamole/error.h \
|
||||
guacamole/error-types.h \
|
||||
guacamole/fips.h \
|
||||
guacamole/hash.h \
|
||||
guacamole/layer.h \
|
||||
guacamole/layer-types.h \
|
||||
@ -60,7 +59,6 @@ libguacinc_HEADERS = \
|
||||
guacamole/protocol.h \
|
||||
guacamole/protocol-constants.h \
|
||||
guacamole/protocol-types.h \
|
||||
guacamole/recording.h \
|
||||
guacamole/socket-constants.h \
|
||||
guacamole/socket.h \
|
||||
guacamole/socket-fntypes.h \
|
||||
@ -94,7 +92,6 @@ libguac_la_SOURCES = \
|
||||
encode-jpeg.c \
|
||||
encode-png.c \
|
||||
error.c \
|
||||
fips.c \
|
||||
hash.c \
|
||||
id.c \
|
||||
palette.c \
|
||||
@ -102,7 +99,6 @@ libguac_la_SOURCES = \
|
||||
pool.c \
|
||||
protocol.c \
|
||||
raw_encoder.c \
|
||||
recording.c \
|
||||
socket.c \
|
||||
socket-broadcast.c \
|
||||
socket-fd.c \
|
||||
@ -139,7 +135,7 @@ libguac_la_CFLAGS = \
|
||||
-Werror -Wall -pedantic
|
||||
|
||||
libguac_la_LDFLAGS = \
|
||||
-version-info 21:0:0 \
|
||||
-version-info 19:0:0 \
|
||||
-no-undefined \
|
||||
@CAIRO_LIBS@ \
|
||||
@DL_LIBS@ \
|
||||
|
@ -307,10 +307,6 @@ int guac_client_add_user(guac_client* client, guac_user* user, int argc, char**
|
||||
|
||||
pthread_rwlock_unlock(&(client->__users_lock));
|
||||
|
||||
/* Notify owner of user joining connection. */
|
||||
if (retval == 0 && !user->owner)
|
||||
guac_client_owner_notify_join(client, user);
|
||||
|
||||
return retval;
|
||||
|
||||
}
|
||||
@ -337,10 +333,6 @@ void guac_client_remove_user(guac_client* client, guac_user* user) {
|
||||
|
||||
pthread_rwlock_unlock(&(client->__users_lock));
|
||||
|
||||
/* Update owner of user having left the connection. */
|
||||
if (!user->owner)
|
||||
guac_client_owner_notify_leave(client, user);
|
||||
|
||||
/* Call handler, if defined */
|
||||
if (user->leave_handler)
|
||||
user->leave_handler(user);
|
||||
@ -421,19 +413,15 @@ void* guac_client_for_user(guac_client* client, guac_user* user,
|
||||
}
|
||||
|
||||
int guac_client_end_frame(guac_client* client) {
|
||||
return guac_client_end_multiple_frames(client, 0);
|
||||
}
|
||||
|
||||
int guac_client_end_multiple_frames(guac_client* client, int frames) {
|
||||
|
||||
/* Update and send timestamp */
|
||||
client->last_sent_timestamp = guac_timestamp_current();
|
||||
|
||||
/* Log received timestamp and calculated lag (at TRACE level only) */
|
||||
guac_client_log(client, GUAC_LOG_TRACE, "Server completed "
|
||||
"frame %" PRIu64 "ms (%i logical frames)", client->last_sent_timestamp, frames);
|
||||
"frame %" PRIu64 "ms.", client->last_sent_timestamp);
|
||||
|
||||
return guac_protocol_send_sync(client->socket, client->last_sent_timestamp, frames);
|
||||
return guac_protocol_send_sync(client->socket, client->last_sent_timestamp);
|
||||
|
||||
}
|
||||
|
||||
@ -683,36 +671,6 @@ static void* __webp_support_callback(guac_user* user, void* data) {
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A callback function which is invoked by guac_client_owner_supports_msg()
|
||||
* to determine if the owner of a client supports the "msg" instruction,
|
||||
* returning zero if the user does not support the instruction or non-zero if
|
||||
* the user supports it.
|
||||
*
|
||||
* @param user
|
||||
* The guac_user that will be checked for "msg" instruction support.
|
||||
*
|
||||
* @param data
|
||||
* Data provided to the callback. This value is never used within this
|
||||
* callback.
|
||||
*
|
||||
* @return
|
||||
* A non-zero integer if the provided user who owns the connection supports
|
||||
* the "msg" instruction, or zero if the user does not. The integer is cast
|
||||
* as a void*.
|
||||
*/
|
||||
static void* guac_owner_supports_msg_callback(guac_user* user, void* data) {
|
||||
|
||||
return (void*) ((intptr_t) guac_user_supports_msg(user));
|
||||
|
||||
}
|
||||
|
||||
int guac_client_owner_supports_msg(guac_client* client) {
|
||||
|
||||
return (int) ((intptr_t) guac_client_for_owner(client, guac_owner_supports_msg_callback, NULL));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback function which is invoked by guac_client_owner_supports_required()
|
||||
* to determine if the owner of a client supports the "required" instruction,
|
||||
@ -743,124 +701,6 @@ int guac_client_owner_supports_required(guac_client* client) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback function that is invokved by guac_client_owner_notify_join() to
|
||||
* notify the owner of a connection that another user has joined the
|
||||
* connection, returning zero if the message is sent successfully, or non-zero
|
||||
* if an error occurs.
|
||||
*
|
||||
* @param user
|
||||
* The user to send the notification to, which will be the owner of the
|
||||
* connection.
|
||||
*
|
||||
* @param data
|
||||
* The data provided to the callback, which is the user that is joining the
|
||||
* connection.
|
||||
*
|
||||
* @return
|
||||
* Zero if the message is sent successfully to the owner, otherwise
|
||||
* non-zero, cast as a void*.
|
||||
*/
|
||||
static void* guac_client_owner_notify_join_callback(guac_user* user, void* data) {
|
||||
|
||||
const guac_user* joiner = (const guac_user *) data;
|
||||
|
||||
if (user == NULL)
|
||||
return (void*) ((intptr_t) -1);
|
||||
|
||||
char* log_owner = "owner";
|
||||
if (user->info.name != NULL)
|
||||
log_owner = (char *) user->info.name;
|
||||
|
||||
char* log_joiner = "anonymous";
|
||||
char* send_joiner = "";
|
||||
if (joiner->info.name != NULL) {
|
||||
log_joiner = (char *) joiner->info.name;
|
||||
send_joiner = (char *) joiner->info.name;
|
||||
}
|
||||
|
||||
guac_user_log(user, GUAC_LOG_DEBUG, "Notifying owner \"%s\" of \"%s\" joining.",
|
||||
log_owner, log_joiner);
|
||||
|
||||
/* Send user joined notification to owner. */
|
||||
const char* args[] = { (const char*)joiner->user_id, (const char*)send_joiner, NULL };
|
||||
return (void*) ((intptr_t) guac_protocol_send_msg(user->socket, GUAC_MESSAGE_USER_JOINED, args));
|
||||
|
||||
}
|
||||
|
||||
int guac_client_owner_notify_join(guac_client* client, guac_user* joiner) {
|
||||
|
||||
/* Don't send msg instruction if client does not support it. */
|
||||
if (!guac_client_owner_supports_msg(client)) {
|
||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||
"Client does not support the \"msg\" instruction and "
|
||||
"will not be notified of the user joining the connection.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_notify_join_callback, joiner));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback function that is invokved by guac_client_owner_notify_leave() to
|
||||
* notify the owner of a connection that another user has left the connection,
|
||||
* returning zero if the message is sent successfully, or non-zero
|
||||
* if an error occurs.
|
||||
*
|
||||
* @param user
|
||||
* The user to send the notification to, which will be the owner of the
|
||||
* connection.
|
||||
*
|
||||
* @param data
|
||||
* The data provided to the callback, which is the user that is leaving the
|
||||
* connection.
|
||||
*
|
||||
* @return
|
||||
* Zero if the message is sent successfully to the owner, otherwise
|
||||
* non-zero, cast as a void*.
|
||||
*/
|
||||
static void* guac_client_owner_notify_leave_callback(guac_user* user, void* data) {
|
||||
|
||||
const guac_user* quitter = (const guac_user *) data;
|
||||
|
||||
if (user == NULL)
|
||||
return (void*) ((intptr_t) -1);
|
||||
|
||||
char* log_owner = "owner";
|
||||
if (user->info.name != NULL)
|
||||
log_owner = (char *) user->info.name;
|
||||
|
||||
char* log_quitter = "anonymous";
|
||||
char* send_quitter = "";
|
||||
if (quitter->info.name != NULL) {
|
||||
log_quitter = (char *) quitter->info.name;
|
||||
send_quitter = (char *) quitter->info.name;
|
||||
}
|
||||
|
||||
guac_user_log(user, GUAC_LOG_DEBUG, "Notifying owner \"%s\" of \"%s\" leaving.",
|
||||
log_owner, log_quitter);
|
||||
|
||||
/* Send user left notification to owner. */
|
||||
const char* args[] = { (const char*)quitter->user_id, (const char*)send_quitter, NULL };
|
||||
return (void*) ((intptr_t) guac_protocol_send_msg(user->socket, GUAC_MESSAGE_USER_LEFT, args));
|
||||
|
||||
}
|
||||
|
||||
int guac_client_owner_notify_leave(guac_client* client, guac_user* quitter) {
|
||||
|
||||
/* Don't send msg instruction if client does not support it. */
|
||||
if (!guac_client_owner_supports_msg(client)) {
|
||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||
"Client does not support the \"msg\" instruction and "
|
||||
"will not be notified of the user leaving the connection.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_notify_leave_callback, quitter));
|
||||
|
||||
}
|
||||
|
||||
int guac_client_supports_webp(guac_client* client) {
|
||||
|
||||
#ifdef ENABLE_WEBP
|
||||
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "guacamole/fips.h"
|
||||
|
||||
/* If OpenSSL is available, include header for version numbers */
|
||||
#ifdef ENABLE_SSL
|
||||
#include <openssl/opensslv.h>
|
||||
|
||||
/* OpenSSL versions prior to 0.9.7e did not have FIPS support */
|
||||
#if !defined(OPENSSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER < 0x00090705f)
|
||||
#define GUAC_FIPS_ENABLED 0
|
||||
|
||||
/* OpenSSL 3+ uses EVP_default_properties_is_fips_enabled() */
|
||||
#elif defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
|
||||
#include <openssl/evp.h>
|
||||
#define GUAC_FIPS_ENABLED EVP_default_properties_is_fips_enabled(NULL)
|
||||
|
||||
/* For OpenSSL versions between 0.9.7e and 3.0, use FIPS_mode() */
|
||||
#else
|
||||
#include <openssl/crypto.h>
|
||||
#define GUAC_FIPS_ENABLED FIPS_mode()
|
||||
#endif
|
||||
|
||||
/* FIPS support does not exist if OpenSSL is not available. */
|
||||
#else
|
||||
#define GUAC_FIPS_ENABLED 0
|
||||
#endif
|
||||
|
||||
int guac_fips_enabled() {
|
||||
|
||||
return GUAC_FIPS_ENABLED;
|
||||
|
||||
}
|
@ -509,47 +509,18 @@ void* guac_client_for_user(guac_client* client, guac_user* user,
|
||||
guac_user_callback* callback, void* data);
|
||||
|
||||
/**
|
||||
* Marks the end of the current frame by sending a "sync" instruction to all
|
||||
* connected users, where the number of input frames that were considered in
|
||||
* creating this frame is either unknown or inapplicable. This instruction will
|
||||
* contain the current timestamp. The last_sent_timestamp member of guac_client
|
||||
* will be updated accordingly.
|
||||
* Marks the end of the current frame by sending a "sync" instruction to
|
||||
* all connected users. This instruction will contain the current timestamp.
|
||||
* The last_sent_timestamp member of guac_client will be updated accordingly.
|
||||
*
|
||||
* If an error occurs sending the instruction, a non-zero value is
|
||||
* returned, and guac_error is set appropriately.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client which has finished a frame.
|
||||
*
|
||||
* @return
|
||||
* Zero on success, non-zero on error.
|
||||
* @param client The guac_client which has finished a frame.
|
||||
* @return Zero on success, non-zero on error.
|
||||
*/
|
||||
int guac_client_end_frame(guac_client* client);
|
||||
|
||||
/**
|
||||
* Marks the end of the current frame by sending a "sync" instruction to all
|
||||
* connected users, where that frame may combine or otherwise represent the
|
||||
* changes of an arbitrary number of input frames. This instruction will
|
||||
* contain the current timestamp, as well as the number of frames that were
|
||||
* considered in creating that frame. The last_sent_timestamp member of
|
||||
* guac_client will be updated accordingly.
|
||||
*
|
||||
* If an error occurs sending the instruction, a non-zero value is
|
||||
* returned, and guac_error is set appropriately.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client which has finished a frame.
|
||||
*
|
||||
* @param frames
|
||||
* The number of distinct frames that were considered or combined when
|
||||
* generating the current frame, or zero if the boundaries of relevant
|
||||
* frames are unknown.
|
||||
*
|
||||
* @return
|
||||
* Zero on success, non-zero on error.
|
||||
*/
|
||||
int guac_client_end_multiple_frames(guac_client* client, int frames);
|
||||
|
||||
/**
|
||||
* Initializes the given guac_client using the initialization routine provided
|
||||
* by the plugin corresponding to the named protocol. This will automatically
|
||||
@ -737,21 +708,6 @@ void guac_client_stream_webp(guac_client* client, guac_socket* socket,
|
||||
guac_composite_mode mode, const guac_layer* layer, int x, int y,
|
||||
cairo_surface_t* surface, int quality, int lossless);
|
||||
|
||||
/**
|
||||
* Returns whether the owner of the given client supports the "msg"
|
||||
* instruction, returning non-zero if the client owner does support the
|
||||
* instruction, or zero if the owner does not.
|
||||
*
|
||||
* @param client
|
||||
* The Guacamole client whose owner should be checked for supporting
|
||||
* the "msg" instruction.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the owner of the given client supports the "msg"
|
||||
* instruction, zero otherwise.
|
||||
*/
|
||||
int guac_client_owner_supports_msg(guac_client* client);
|
||||
|
||||
/**
|
||||
* Returns whether the owner of the given client supports the "required"
|
||||
* instruction, returning non-zero if the client owner does support the
|
||||
@ -767,42 +723,6 @@ int guac_client_owner_supports_msg(guac_client* client);
|
||||
*/
|
||||
int guac_client_owner_supports_required(guac_client* client);
|
||||
|
||||
/**
|
||||
* Notifies the owner of the given client that a user has joined the connection,
|
||||
* and returns zero if the message was sent successfully, or non-zero if the
|
||||
* notification failed.
|
||||
*
|
||||
* @param client
|
||||
* The Guacamole Client whose owner should be notified of a user joining
|
||||
* the connection.
|
||||
*
|
||||
* @param joiner
|
||||
* The Guacamole User who joined the connection.
|
||||
*
|
||||
* @return
|
||||
* Zero if the notification to the owner was sent successfully, or non-zero
|
||||
* if an error occurred.
|
||||
*/
|
||||
int guac_client_owner_notify_join(guac_client* client, guac_user* joiner);
|
||||
|
||||
/**
|
||||
* Notifies the owner of the given client that a user has left the connection,
|
||||
* and returns zero if the message was sent successfully, or non-zero if the
|
||||
* notification failed.
|
||||
*
|
||||
* @param client
|
||||
* The Guacamole Client whose owner should be notified of a user leaving
|
||||
* the connection.
|
||||
*
|
||||
* @param quitter
|
||||
* The Guacamole User who left the connection.
|
||||
*
|
||||
* @return
|
||||
* Zero if the notification to the owner was sent successfully, or non-zero
|
||||
* if an error occurred.
|
||||
*/
|
||||
int guac_client_owner_notify_leave(guac_client* client, guac_user* quitter);
|
||||
|
||||
/**
|
||||
* Returns whether all users of the given client support WebP. If any user does
|
||||
* not support WebP, or the server cannot encode WebP images, zero is returned.
|
||||
|
@ -38,7 +38,7 @@
|
||||
* This version is passed by the __guac_protocol_send_args() function from the
|
||||
* server to the client during the client/server handshake.
|
||||
*/
|
||||
#define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_5_0"
|
||||
#define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_3_0"
|
||||
|
||||
/**
|
||||
* The maximum number of bytes that should be sent in any one blob instruction
|
||||
@ -49,20 +49,5 @@
|
||||
*/
|
||||
#define GUAC_PROTOCOL_BLOB_MAX_LENGTH 6048
|
||||
|
||||
/**
|
||||
* The name of the layer parameter defining the number of simultaneous points
|
||||
* of contact supported by a layer. This parameter should be set to a non-zero
|
||||
* value if the associated layer should receive touch events ("touch"
|
||||
* instructions).
|
||||
*
|
||||
* This value specified for this parameter is advisory, and the client is not
|
||||
* required to honor the declared level of touch support. Implementations are
|
||||
* expected to safely handle or ignore any received touch events, regardless of
|
||||
* the level of touch support declared.
|
||||
*
|
||||
* @see guac_protocol_send_set_int()
|
||||
*/
|
||||
#define GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH "multi-touch"
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -306,40 +306,9 @@ typedef enum guac_protocol_version {
|
||||
* allowing connections in guacd to request information from the client and
|
||||
* await a response.
|
||||
*/
|
||||
GUAC_PROTOCOL_VERSION_1_3_0 = 0x010300,
|
||||
|
||||
/**
|
||||
* Protocol version 1.5.0, which supports the "msg" instruction, allowing
|
||||
* messages to be sent to the client, and adds support for the "name"
|
||||
* handshake instruction.
|
||||
*/
|
||||
GUAC_PROTOCOL_VERSION_1_5_0 = 0x010500
|
||||
GUAC_PROTOCOL_VERSION_1_3_0 = 0x010300
|
||||
|
||||
} guac_protocol_version;
|
||||
|
||||
/**
|
||||
* A type that represents codes for human-readable messages sent by the "msg"
|
||||
* instruction to the Client, that will be displayed in the client's browser.
|
||||
* The codes will be interpreted by the client into translatable messages, and
|
||||
* make take arguments, as noted below.
|
||||
*/
|
||||
typedef enum guac_message_type {
|
||||
|
||||
/**
|
||||
* A message that notifies the owner of a connection that another user has
|
||||
* joined their connection. There should be a single argument provided, the
|
||||
* name of the user who has joined.
|
||||
*/
|
||||
GUAC_MESSAGE_USER_JOINED = 0x0001,
|
||||
|
||||
/**
|
||||
* A message that notifies the owner of a connection that another user has
|
||||
* left their connection. There should be a single argument provided, the
|
||||
* name of the user who has left.
|
||||
*/
|
||||
GUAC_MESSAGE_USER_LEFT = 0x0002
|
||||
|
||||
} guac_message_type;
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -171,27 +171,6 @@ int guac_protocol_send_log(guac_socket* socket, const char* format, ...);
|
||||
int vguac_protocol_send_log(guac_socket* socket, const char* format,
|
||||
va_list args);
|
||||
|
||||
/**
|
||||
* Sends the given string over the socket to be displayed on the client. Returns
|
||||
* zero if the message was sent successfully or non-zero if an error occurs.
|
||||
*
|
||||
* @param socket
|
||||
* The guac_socket connection to send the message to.
|
||||
*
|
||||
* @param msg
|
||||
* The message code to send to the client.
|
||||
*
|
||||
* @param args
|
||||
* A null-terminated array of strings that will be provided to the client
|
||||
* as part of the message, that the client may then place in the message,
|
||||
* or null if the message requires no arguments.
|
||||
*
|
||||
* @return
|
||||
* Zero if the message is sent successfully; otherwise non-zero.
|
||||
*/
|
||||
int guac_protocol_send_msg(guac_socket* socket, guac_message_type msg,
|
||||
const char** args);
|
||||
|
||||
/**
|
||||
* Sends a mouse instruction over the given guac_socket connection.
|
||||
*
|
||||
@ -230,53 +209,6 @@ int guac_protocol_send_msg(guac_socket* socket, guac_message_type msg,
|
||||
int guac_protocol_send_mouse(guac_socket* socket, int x, int y,
|
||||
int button_mask, guac_timestamp timestamp);
|
||||
|
||||
/**
|
||||
* Sends a touch instruction over the given guac_socket connection.
|
||||
*
|
||||
* If an error occurs sending the instruction, a non-zero value is
|
||||
* returned, and guac_error is set appropriately.
|
||||
*
|
||||
* @param socket
|
||||
* The guac_socket connection to use.
|
||||
*
|
||||
* @param id
|
||||
* An arbitrary integer ID which uniquely identifies this contact relative
|
||||
* to other active contacts.
|
||||
*
|
||||
* @param x
|
||||
* The X coordinate of the center of the touch contact.
|
||||
*
|
||||
* @param y
|
||||
* The Y coordinate of the center of the touch contact.
|
||||
*
|
||||
* @param x_radius
|
||||
* The X radius of the ellipse covering the general area of the touch
|
||||
* contact, in pixels.
|
||||
*
|
||||
* @param y_radius
|
||||
* The Y radius of the ellipse covering the general area of the touch
|
||||
* contact, in pixels.
|
||||
*
|
||||
* @param angle
|
||||
* The rough angle of clockwise rotation of the general area of the touch
|
||||
* contact, in degrees.
|
||||
*
|
||||
* @param force
|
||||
* The relative force exerted by the touch contact, where 0 is no force
|
||||
* (the touch has been lifted) and 1 is maximum force (the maximum amount
|
||||
* of force representable by the device).
|
||||
*
|
||||
* @param timestamp
|
||||
* The server timestamp (in milliseconds) at the point in time this touch
|
||||
* event was acknowledged.
|
||||
*
|
||||
* @return
|
||||
* Zero on success, non-zero on error.
|
||||
*/
|
||||
int guac_protocol_send_touch(guac_socket* socket, int id, int x, int y,
|
||||
int x_radius, int y_radius, double angle, double force,
|
||||
guac_timestamp timestamp);
|
||||
|
||||
/**
|
||||
* Sends a nest instruction over the given guac_socket connection.
|
||||
*
|
||||
@ -339,32 +271,6 @@ int guac_protocol_send_ready(guac_socket* socket, const char* id);
|
||||
int guac_protocol_send_set(guac_socket* socket, const guac_layer* layer,
|
||||
const char* name, const char* value);
|
||||
|
||||
/**
|
||||
* Sends a set instruction over the given guac_socket connection. This function
|
||||
* behavies identically to guac_protocol_send_set() except that the provided
|
||||
* parameter value is an integer, rather than a string.
|
||||
*
|
||||
* If an error occurs sending the instruction, a non-zero value is
|
||||
* returned, and guac_error is set appropriately.
|
||||
*
|
||||
* @param socket
|
||||
* The guac_socket connection to use.
|
||||
*
|
||||
* @param layer
|
||||
* The layer to set the parameter of.
|
||||
*
|
||||
* @param name
|
||||
* The name of the parameter to set.
|
||||
*
|
||||
* @param value
|
||||
* The value to set the parameter to.
|
||||
*
|
||||
* @return
|
||||
* Zero on success, non-zero on error.
|
||||
*/
|
||||
int guac_protocol_send_set_int(guac_socket* socket, const guac_layer* layer,
|
||||
const char* name, int value);
|
||||
|
||||
/**
|
||||
* Sends a select instruction over the given guac_socket connection.
|
||||
*
|
||||
@ -384,22 +290,11 @@ int guac_protocol_send_select(guac_socket* socket, const char* protocol);
|
||||
* If an error occurs sending the instruction, a non-zero value is
|
||||
* returned, and guac_error is set appropriately.
|
||||
*
|
||||
* @param socket
|
||||
* The guac_socket connection to use.
|
||||
*
|
||||
* @param timestamp
|
||||
* The current timestamp (in milliseconds).
|
||||
*
|
||||
* @param frames
|
||||
* The number of distinct frames that were considered or combined when
|
||||
* generating the frame terminated by this instruction, or zero if the
|
||||
* boundaries of relevant frames are unknown.
|
||||
*
|
||||
* @return
|
||||
* Zero on success, non-zero on error.
|
||||
* @param socket The guac_socket connection to use.
|
||||
* @param timestamp The current timestamp (in milliseconds).
|
||||
* @return Zero on success, non-zero on error.
|
||||
*/
|
||||
int guac_protocol_send_sync(guac_socket* socket, guac_timestamp timestamp,
|
||||
int frames);
|
||||
int guac_protocol_send_sync(guac_socket* socket, guac_timestamp timestamp);
|
||||
|
||||
/* OBJECT INSTRUCTIONS */
|
||||
|
||||
|
@ -109,28 +109,6 @@ size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n);
|
||||
*/
|
||||
size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n);
|
||||
|
||||
/**
|
||||
* Search for the null-terminated string needle in the possibly null-
|
||||
* terminated haystack, looking at no more than len bytes.
|
||||
*
|
||||
* @param haystack
|
||||
* The string to search. It may or may not be null-terminated. Only the
|
||||
* first len bytes are searched.
|
||||
*
|
||||
* @param needle
|
||||
* The string to look for. It must be null-terminated.
|
||||
*
|
||||
* @param len
|
||||
* The maximum number of bytes to examine in haystack.
|
||||
*
|
||||
* @return
|
||||
* A pointer to the first instance of needle within haystack, or NULL if
|
||||
* needle does not exist in haystack. If needle is the empty string,
|
||||
* haystack is returned.
|
||||
*
|
||||
*/
|
||||
char* guac_strnstr(const char *haystack, const char *needle, size_t len);
|
||||
|
||||
/**
|
||||
* Simple wrapper for strdup() which behaves identically to standard strdup(),
|
||||
* except that NULL will be returned if the provided string is NULL.
|
||||
|
@ -95,51 +95,6 @@ typedef void* guac_user_callback(guac_user* user, void* data);
|
||||
typedef int guac_user_mouse_handler(guac_user* user, int x, int y,
|
||||
int button_mask);
|
||||
|
||||
/**
|
||||
* Handler for Guacamole touch events, invoked when a "touch" instruction has
|
||||
* been received from a user.
|
||||
*
|
||||
* @param user
|
||||
* The user that sent the touch event.
|
||||
*
|
||||
* @param id
|
||||
* An arbitrary integer ID which uniquely identifies this contact relative
|
||||
* to other active contacts.
|
||||
*
|
||||
* @param x
|
||||
* The X coordinate of the center of the touch contact within the display
|
||||
* when the event occurred, in pixels. This value is not guaranteed to be
|
||||
* within the bounds of the display area.
|
||||
*
|
||||
* @param y
|
||||
* The Y coordinate of the center of the touch contact within the display
|
||||
* when the event occurred, in pixels. This value is not guaranteed to be
|
||||
* within the bounds of the display area.
|
||||
*
|
||||
* @param x_radius
|
||||
* The X radius of the ellipse covering the general area of the touch
|
||||
* contact, in pixels.
|
||||
*
|
||||
* @param y_radius
|
||||
* The Y radius of the ellipse covering the general area of the touch
|
||||
* contact, in pixels.
|
||||
*
|
||||
* @param angle
|
||||
* The rough angle of clockwise rotation of the general area of the touch
|
||||
* contact, in degrees.
|
||||
*
|
||||
* @param force
|
||||
* The relative force exerted by the touch contact, where 0 is no force
|
||||
* (the touch has been lifted) and 1 is maximum force (the maximum amount
|
||||
* of force representable by the device).
|
||||
*
|
||||
* @return
|
||||
* Zero if the touch event was handled successfully, or non-zero if an
|
||||
* error occurred.
|
||||
*/
|
||||
typedef int guac_user_touch_handler(guac_user* user, int id, int x, int y,
|
||||
int x_radius, int y_radius, double angle, double force);
|
||||
|
||||
/**
|
||||
* Handler for Guacamole key events, invoked when a "key" event has been
|
||||
* received from a user.
|
||||
|
@ -88,7 +88,7 @@ struct guac_user_info {
|
||||
* stated resolution of the display size request is recommended.
|
||||
*/
|
||||
int optimal_resolution;
|
||||
|
||||
|
||||
/**
|
||||
* The timezone of the remote system. If the client does not provide
|
||||
* a specific timezone then this will be NULL. The format of the timezone
|
||||
@ -102,14 +102,6 @@ struct guac_user_info {
|
||||
*/
|
||||
guac_protocol_version protocol_version;
|
||||
|
||||
/**
|
||||
* The human-readable name of the Guacamole user, supplied by the client
|
||||
* during the handshake. This is an arbitrary value, with no requirements or
|
||||
* constraints, including that it need not uniquely identify the user.
|
||||
* If the client does not provide a name then this will be NULL.
|
||||
*/
|
||||
const char* name;
|
||||
|
||||
};
|
||||
|
||||
struct guac_user {
|
||||
@ -517,27 +509,6 @@ struct guac_user {
|
||||
*/
|
||||
guac_user_argv_handler* argv_handler;
|
||||
|
||||
/**
|
||||
* Handler for touch events sent by the Guacamole web-client.
|
||||
*
|
||||
* The handler takes the integer X and Y coordinates representing the
|
||||
* center of the touch contact, as well as several parameters describing
|
||||
* the general shape of the contact area. The force parameter indicates the
|
||||
* amount of force exerted by the contact, including whether the contact
|
||||
* has been lifted.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* int touch_handler(guac_user* user, int id, int x, int y,
|
||||
* int x_radius, int y_radius, double angle, double force);
|
||||
*
|
||||
* int guac_user_init(guac_user* user, int argc, char** argv) {
|
||||
* user->touch_handler = touch_handler;
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
guac_user_touch_handler* touch_handler;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@ -858,17 +829,6 @@ void guac_user_stream_webp(guac_user* user, guac_socket* socket,
|
||||
guac_composite_mode mode, const guac_layer* layer, int x, int y,
|
||||
cairo_surface_t* surface, int quality, int lossless);
|
||||
|
||||
/**
|
||||
* Returns whether the given user supports the "msg" instruction.
|
||||
*
|
||||
* @param user
|
||||
* The Guacamole user to check for support of the "msg" instruction.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the user supports the "msg" instruction, otherwise zero.
|
||||
*/
|
||||
int guac_user_supports_msg(guac_user* user);
|
||||
|
||||
/**
|
||||
* Returns whether the given user supports the "required" instruction.
|
||||
*
|
||||
|
@ -42,15 +42,11 @@
|
||||
* @param broadcast_addr
|
||||
* The broadcast address to which to send the magic Wake-on-LAN packet.
|
||||
*
|
||||
* @param udp_port
|
||||
* The UDP port to use when sending the WoL packet.
|
||||
*
|
||||
* @return
|
||||
* Zero if the packet is successfully sent to the destination; non-zero
|
||||
* if the packet cannot be sent.
|
||||
*/
|
||||
int guac_wol_wake(const char* mac_addr, const char* broadcast_addr,
|
||||
const unsigned short udp_port);
|
||||
int guac_wol_wake(const char* mac_addr, const char* broadcast_addr);
|
||||
|
||||
#endif /* GUAC_WOL_H */
|
||||
|
||||
|
@ -22,9 +22,7 @@
|
||||
#include "guacamole/error.h"
|
||||
#include "id.h"
|
||||
|
||||
#if defined(HAVE_LIBUUID)
|
||||
#include <uuid/uuid.h>
|
||||
#elif defined(HAVE_OSSP_UUID_H)
|
||||
#ifdef HAVE_OSSP_UUID_H
|
||||
#include <ossp/uuid.h>
|
||||
#else
|
||||
#include <uuid.h>
|
||||
@ -32,73 +30,54 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* The length of a UUID in bytes. All UUIDs are guaranteed to be 36 1-byte
|
||||
* characters long.
|
||||
*/
|
||||
#define GUAC_UUID_LEN 36
|
||||
|
||||
char* guac_generate_id(char prefix) {
|
||||
|
||||
char* buffer;
|
||||
char* identifier;
|
||||
size_t identifier_length;
|
||||
|
||||
/* Prepare object to receive generated UUID */
|
||||
#ifdef HAVE_LIBUUID
|
||||
uuid_t uuid;
|
||||
#else
|
||||
uuid_t* uuid;
|
||||
|
||||
/* Attempt to create UUID object */
|
||||
if (uuid_create(&uuid) != UUID_RC_OK) {
|
||||
guac_error = GUAC_STATUS_NO_MEMORY;
|
||||
guac_error_message = "Could not allocate memory for UUID";
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Generate unique identifier */
|
||||
#ifdef HAVE_LIBUUID
|
||||
uuid_generate(uuid);
|
||||
#else
|
||||
/* Generate random UUID */
|
||||
if (uuid_make(uuid, UUID_MAKE_V4) != UUID_RC_OK) {
|
||||
uuid_destroy(uuid);
|
||||
guac_error = GUAC_STATUS_NO_MEMORY;
|
||||
guac_error_message = "UUID generation failed";
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Allocate buffer for future formatted ID */
|
||||
buffer = malloc(GUAC_UUID_LEN + 2);
|
||||
buffer = malloc(UUID_LEN_STR + 2);
|
||||
if (buffer == NULL) {
|
||||
#ifndef HAVE_LIBUUID
|
||||
uuid_destroy(uuid);
|
||||
#endif
|
||||
guac_error = GUAC_STATUS_NO_MEMORY;
|
||||
guac_error_message = "Could not allocate memory for unique ID";
|
||||
guac_error_message = "Could not allocate memory for connection ID";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
identifier = &(buffer[1]);
|
||||
identifier_length = UUID_LEN_STR + 1;
|
||||
|
||||
/* Convert UUID to string to produce unique identifier */
|
||||
#ifdef HAVE_LIBUUID
|
||||
uuid_unparse_lower(uuid, identifier);
|
||||
#else
|
||||
size_t identifier_length = GUAC_UUID_LEN + 1;
|
||||
/* Build connection ID from UUID */
|
||||
if (uuid_export(uuid, UUID_FMT_STR, &identifier, &identifier_length) != UUID_RC_OK) {
|
||||
free(buffer);
|
||||
uuid_destroy(uuid);
|
||||
guac_error = GUAC_STATUS_INTERNAL_ERROR;
|
||||
guac_error_message = "Conversion of UUID to unique ID failed";
|
||||
guac_error_message = "Conversion of UUID to connection ID failed";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Clean up generated UUID */
|
||||
uuid_destroy(uuid);
|
||||
#endif
|
||||
|
||||
buffer[0] = prefix;
|
||||
buffer[GUAC_UUID_LEN + 1] = '\0';
|
||||
buffer[UUID_LEN_STR + 1] = '\0';
|
||||
return buffer;
|
||||
|
||||
}
|
||||
|
@ -65,7 +65,6 @@ guac_protocol_version_mapping guac_protocol_version_table[] = {
|
||||
{ GUAC_PROTOCOL_VERSION_1_0_0, "VERSION_1_0_0" },
|
||||
{ GUAC_PROTOCOL_VERSION_1_1_0, "VERSION_1_1_0" },
|
||||
{ GUAC_PROTOCOL_VERSION_1_3_0, "VERSION_1_3_0" },
|
||||
{ GUAC_PROTOCOL_VERSION_1_5_0, "VERSION_1_5_0" },
|
||||
{ GUAC_PROTOCOL_VERSION_UNKNOWN, NULL }
|
||||
};
|
||||
|
||||
@ -659,23 +658,6 @@ int guac_protocol_send_log(guac_socket* socket, const char* format, ...) {
|
||||
|
||||
}
|
||||
|
||||
int guac_protocol_send_msg(guac_socket* socket, guac_message_type msg,
|
||||
const char** args) {
|
||||
|
||||
int ret_val;
|
||||
|
||||
guac_socket_instruction_begin(socket);
|
||||
ret_val =
|
||||
guac_socket_write_string(socket, "3.msg,")
|
||||
|| __guac_socket_write_length_int(socket, msg)
|
||||
|| guac_socket_write_array(socket, args)
|
||||
|| guac_socket_write_string(socket, ";");
|
||||
|
||||
guac_socket_instruction_end(socket);
|
||||
return ret_val;
|
||||
|
||||
}
|
||||
|
||||
int guac_protocol_send_file(guac_socket* socket, const guac_stream* stream,
|
||||
const char* mimetype, const char* name) {
|
||||
|
||||
@ -838,37 +820,6 @@ int guac_protocol_send_mouse(guac_socket* socket, int x, int y,
|
||||
|
||||
}
|
||||
|
||||
int guac_protocol_send_touch(guac_socket* socket, int id, int x, int y,
|
||||
int x_radius, int y_radius, double angle, double force,
|
||||
guac_timestamp timestamp) {
|
||||
|
||||
int ret_val;
|
||||
|
||||
guac_socket_instruction_begin(socket);
|
||||
ret_val =
|
||||
guac_socket_write_string(socket, "5.touch,")
|
||||
|| __guac_socket_write_length_int(socket, id)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_int(socket, x)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_int(socket, y)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_int(socket, x_radius)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_int(socket, y_radius)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_double(socket, angle)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_double(socket, force)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_int(socket, timestamp)
|
||||
|| guac_socket_write_string(socket, ";");
|
||||
|
||||
guac_socket_instruction_end(socket);
|
||||
return ret_val;
|
||||
|
||||
}
|
||||
|
||||
int guac_protocol_send_move(guac_socket* socket, const guac_layer* layer,
|
||||
const guac_layer* parent, int x, int y, int z) {
|
||||
|
||||
@ -1106,26 +1057,6 @@ int guac_protocol_send_set(guac_socket* socket, const guac_layer* layer,
|
||||
|
||||
}
|
||||
|
||||
int guac_protocol_send_set_int(guac_socket* socket, const guac_layer* layer,
|
||||
const char* name, int value) {
|
||||
|
||||
int ret_val;
|
||||
|
||||
guac_socket_instruction_begin(socket);
|
||||
ret_val =
|
||||
guac_socket_write_string(socket, "3.set,")
|
||||
|| __guac_socket_write_length_int(socket, layer->index)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_string(socket, name)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_int(socket, value)
|
||||
|| guac_socket_write_string(socket, ";");
|
||||
|
||||
guac_socket_instruction_end(socket);
|
||||
return ret_val;
|
||||
|
||||
}
|
||||
|
||||
int guac_protocol_send_select(guac_socket* socket, const char* protocol) {
|
||||
|
||||
int ret_val;
|
||||
@ -1199,8 +1130,7 @@ int guac_protocol_send_start(guac_socket* socket, const guac_layer* layer,
|
||||
|
||||
}
|
||||
|
||||
int guac_protocol_send_sync(guac_socket* socket, guac_timestamp timestamp,
|
||||
int frames) {
|
||||
int guac_protocol_send_sync(guac_socket* socket, guac_timestamp timestamp) {
|
||||
|
||||
int ret_val;
|
||||
|
||||
@ -1208,8 +1138,6 @@ int guac_protocol_send_sync(guac_socket* socket, guac_timestamp timestamp,
|
||||
ret_val =
|
||||
guac_socket_write_string(socket, "4.sync,")
|
||||
|| __guac_socket_write_length_int(socket, timestamp)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_int(socket, frames)
|
||||
|| guac_socket_write_string(socket, ";");
|
||||
|
||||
guac_socket_instruction_end(socket);
|
||||
|
@ -81,38 +81,6 @@ size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n) {
|
||||
|
||||
}
|
||||
|
||||
char* guac_strnstr(const char *haystack, const char *needle, size_t len) {
|
||||
|
||||
#ifdef HAVE_STRNSTR
|
||||
return strnstr(haystack, needle, len);
|
||||
#else
|
||||
char* chr;
|
||||
size_t nlen = strlen(needle), off = 0;
|
||||
|
||||
/* Follow documented API: return haystack if needle is the empty string. */
|
||||
if (nlen == 0)
|
||||
return (char *)haystack;
|
||||
|
||||
/* Use memchr to find candidates. It might be optimized in asm. */
|
||||
while (off < len && NULL != (chr = memchr(haystack + off, needle[0], len - off))) {
|
||||
/* chr is guaranteed to be in bounds of and >= haystack. */
|
||||
off = chr - haystack;
|
||||
/* If needle would go beyond provided len, it doesn't exist in haystack. */
|
||||
if (off + nlen > len)
|
||||
return NULL;
|
||||
/* Now that we know we have at least nlen bytes, compare them. */
|
||||
if (!memcmp(chr, needle, nlen))
|
||||
return chr;
|
||||
/* Make sure we make progress. */
|
||||
off += 1;
|
||||
}
|
||||
|
||||
/* memchr ran out of candidates, needle wasn't found. */
|
||||
return NULL;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
char* guac_strdup(const char* str) {
|
||||
|
||||
/* Return NULL if no string provided */
|
||||
|
@ -36,7 +36,6 @@ TESTS = $(check_PROGRAMS)
|
||||
test_libguac_SOURCES = \
|
||||
client/buffer_pool.c \
|
||||
client/layer_pool.c \
|
||||
id/generate.c \
|
||||
parser/append.c \
|
||||
parser/read.c \
|
||||
pool/next_free.c \
|
||||
@ -48,7 +47,6 @@ test_libguac_SOURCES = \
|
||||
string/strlcat.c \
|
||||
string/strlcpy.c \
|
||||
string/strljoin.c \
|
||||
string/strnstr.c \
|
||||
unicode/charsize.c \
|
||||
unicode/read.c \
|
||||
unicode/strlen.c \
|
||||
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "id.h"
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Test which verifies that each call to guac_generate_id() produces a
|
||||
* different string.
|
||||
*/
|
||||
void test_id__unique() {
|
||||
|
||||
char* id1 = guac_generate_id('x');
|
||||
char* id2 = guac_generate_id('x');
|
||||
|
||||
/* Neither string may be NULL */
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(id1);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(id2);
|
||||
|
||||
/* Both strings should be different */
|
||||
CU_ASSERT_STRING_NOT_EQUAL(id1, id2);
|
||||
|
||||
free(id1);
|
||||
free(id2);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test which verifies that guac_generate_id() produces strings are in the
|
||||
* correc UUID-based format.
|
||||
*/
|
||||
void test_id__format() {
|
||||
|
||||
unsigned int ignore;
|
||||
|
||||
char* id = guac_generate_id('x');
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(id);
|
||||
|
||||
int items_read = sscanf(id, "x%08x-%04x-%04x-%04x-%08x%04x",
|
||||
&ignore, &ignore, &ignore, &ignore, &ignore, &ignore);
|
||||
|
||||
CU_ASSERT_EQUAL(items_read, 6);
|
||||
CU_ASSERT_EQUAL(strlen(id), 37);
|
||||
|
||||
free(id);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test which verifies that guac_generate_id() takes the specified prefix
|
||||
* character into account when generating the ID string.
|
||||
*/
|
||||
void test_id__prefix() {
|
||||
|
||||
char* id;
|
||||
|
||||
id = guac_generate_id('a');
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(id);
|
||||
CU_ASSERT_EQUAL(id[0], 'a');
|
||||
free(id);
|
||||
|
||||
id = guac_generate_id('b');
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(id);
|
||||
CU_ASSERT_EQUAL(id[0], 'b');
|
||||
free(id);
|
||||
|
||||
}
|
||||
|
@ -27,11 +27,11 @@
|
||||
*/
|
||||
void test_guac_protocol__version_to_string() {
|
||||
|
||||
guac_protocol_version version_a = GUAC_PROTOCOL_VERSION_1_5_0;
|
||||
guac_protocol_version version_a = GUAC_PROTOCOL_VERSION_1_3_0;
|
||||
guac_protocol_version version_b = GUAC_PROTOCOL_VERSION_1_0_0;
|
||||
guac_protocol_version version_c = GUAC_PROTOCOL_VERSION_UNKNOWN;
|
||||
|
||||
CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_a), "VERSION_1_5_0");
|
||||
CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_a), "VERSION_1_3_0");
|
||||
CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_b), "VERSION_1_0_0");
|
||||
CU_ASSERT_PTR_NULL(guac_protocol_version_to_string(version_c));
|
||||
|
||||
|
@ -54,7 +54,7 @@ static void write_instructions(int fd) {
|
||||
|
||||
/* Write instructions */
|
||||
guac_protocol_send_name(socket, "a" UTF8_4 "b" UTF8_4 "c");
|
||||
guac_protocol_send_sync(socket, 12345, 1);
|
||||
guac_protocol_send_sync(socket, 12345);
|
||||
guac_socket_flush(socket);
|
||||
|
||||
/* Close and free socket */
|
||||
@ -76,7 +76,7 @@ static void read_expected_instructions(int fd) {
|
||||
|
||||
char expected[] =
|
||||
"4.name,11.a" UTF8_4 "b" UTF8_4 "c;"
|
||||
"4.sync,5.12345,1.1;";
|
||||
"4.sync,5.12345;";
|
||||
|
||||
int numread;
|
||||
char buffer[1024];
|
||||
|
@ -65,7 +65,7 @@ static void write_instructions(int fd) {
|
||||
|
||||
/* Write instructions */
|
||||
guac_protocol_send_name(nested_socket, "a" UTF8_4 "b" UTF8_4 "c");
|
||||
guac_protocol_send_sync(nested_socket, 12345, 1);
|
||||
guac_protocol_send_sync(nested_socket, 12345);
|
||||
|
||||
/* Close and free sockets */
|
||||
guac_socket_free(nested_socket);
|
||||
@ -86,9 +86,9 @@ static void write_instructions(int fd) {
|
||||
static void read_expected_instructions(int fd) {
|
||||
|
||||
char expected[] =
|
||||
"4.nest,3.123,41."
|
||||
"4.nest,3.123,37."
|
||||
"4.name,11.a" UTF8_4 "b" UTF8_4 "c;"
|
||||
"4.sync,5.12345,1.1;"
|
||||
"4.sync,5.12345;"
|
||||
";";
|
||||
|
||||
int numread;
|
||||
|
@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <guacamole/string.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Verify guac_strnstr() behaviors:
|
||||
*/
|
||||
void test_string__strnstr() {
|
||||
char haystack[8] = {'a', 'h', 'i', ' ', 't', 'u', 'n', 'a'};
|
||||
char* result;
|
||||
|
||||
/* needle exists at start of haystack */
|
||||
result = guac_strnstr(haystack, "ah", sizeof(haystack));
|
||||
CU_ASSERT_EQUAL(result, haystack);
|
||||
|
||||
/* needle exists in the middle of haystack */
|
||||
result = guac_strnstr(haystack, "hi", sizeof(haystack));
|
||||
CU_ASSERT_EQUAL(result, haystack + 1);
|
||||
|
||||
/* needle exists at end of haystack */
|
||||
result = guac_strnstr(haystack, "tuna", sizeof(haystack));
|
||||
CU_ASSERT_EQUAL(result, haystack + 4);
|
||||
|
||||
/* needle doesn't exist in haystack, needle[0] isn't in haystack */
|
||||
result = guac_strnstr(haystack, "mahi", sizeof(haystack));
|
||||
CU_ASSERT_EQUAL(result, NULL);
|
||||
|
||||
/*
|
||||
* needle doesn't exist in haystack, needle[0] is in haystack,
|
||||
* length wouldn't allow needle to exist
|
||||
*/
|
||||
result = guac_strnstr(haystack, "narwhal", sizeof(haystack));
|
||||
CU_ASSERT_EQUAL(result, NULL);
|
||||
|
||||
/*
|
||||
* needle doesn't exist in haystack, needle[0] is in haystack,
|
||||
* length would allow needle to exist
|
||||
*/
|
||||
result = guac_strnstr(haystack, "taco", sizeof(haystack));
|
||||
CU_ASSERT_EQUAL(result, NULL);
|
||||
|
||||
/*
|
||||
* needle doesn't exist in haystack, needle[0] is in haystack
|
||||
* multiple times
|
||||
*/
|
||||
result = guac_strnstr(haystack, "ahha", sizeof(haystack));
|
||||
CU_ASSERT_EQUAL(result, NULL);
|
||||
|
||||
/* empty needle should return haystack according to API docs */
|
||||
result = guac_strnstr(haystack, "", sizeof(haystack));
|
||||
CU_ASSERT_EQUAL(result, haystack);
|
||||
}
|
@ -37,7 +37,6 @@
|
||||
|
||||
__guac_instruction_handler_mapping __guac_instruction_handler_map[] = {
|
||||
{"sync", __guac_handle_sync},
|
||||
{"touch", __guac_handle_touch},
|
||||
{"mouse", __guac_handle_mouse},
|
||||
{"key", __guac_handle_key},
|
||||
{"clipboard", __guac_handle_clipboard},
|
||||
@ -64,7 +63,6 @@ __guac_instruction_handler_mapping __guac_handshake_handler_map[] = {
|
||||
{"video", __guac_handshake_video_handler},
|
||||
{"image", __guac_handshake_image_handler},
|
||||
{"timezone", __guac_handshake_timezone_handler},
|
||||
{"name", __guac_handshake_name_handler},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@ -121,60 +119,37 @@ int __guac_handle_sync(guac_user* user, int argc, char** argv) {
|
||||
/* Calculate length of frame, including network and processing lag */
|
||||
frame_duration = current - timestamp;
|
||||
|
||||
/* Calculate processing lag portion of length of frame */
|
||||
int frame_processing_lag = 0;
|
||||
/* Update lag statistics if at least one frame has been rendered */
|
||||
if (user->last_frame_duration != 0) {
|
||||
|
||||
/* Calculate lag using the previous frame as a baseline */
|
||||
frame_processing_lag = frame_duration - user->last_frame_duration;
|
||||
int processing_lag = frame_duration - user->last_frame_duration;
|
||||
|
||||
/* Adjust back to zero if cumulative error leads to a negative
|
||||
* value */
|
||||
if (frame_processing_lag < 0)
|
||||
frame_processing_lag = 0;
|
||||
if (processing_lag < 0)
|
||||
processing_lag = 0;
|
||||
|
||||
user->processing_lag = processing_lag;
|
||||
|
||||
}
|
||||
|
||||
/* Record baseline duration of frame by excluding lag (this is the
|
||||
* network round-trip time) */
|
||||
int estimated_rtt = frame_duration - frame_processing_lag;
|
||||
user->last_frame_duration = estimated_rtt;
|
||||
|
||||
/* Calculate cumulative accumulated processing lag relative to server timeline */
|
||||
int processing_lag = current - user->last_received_timestamp - estimated_rtt;
|
||||
if (processing_lag < 0)
|
||||
processing_lag = 0;
|
||||
|
||||
user->processing_lag = processing_lag;
|
||||
/* Record baseline duration of frame by excluding lag */
|
||||
user->last_frame_duration = frame_duration - user->processing_lag;
|
||||
|
||||
}
|
||||
|
||||
/* Log received timestamp and calculated lag (at TRACE level only) */
|
||||
guac_user_log(user, GUAC_LOG_TRACE,
|
||||
"User confirmation of frame %" PRIu64 "ms received "
|
||||
"at %" PRIu64 "ms (processing_lag=%ims, estimated_rtt=%ims)",
|
||||
timestamp, current, user->processing_lag, user->last_frame_duration);
|
||||
"at %" PRIu64 "ms (processing_lag=%ims)",
|
||||
timestamp, current, user->processing_lag);
|
||||
|
||||
if (user->sync_handler)
|
||||
return user->sync_handler(user, timestamp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __guac_handle_touch(guac_user* user, int argc, char** argv) {
|
||||
if (user->touch_handler)
|
||||
return user->touch_handler(
|
||||
user,
|
||||
atoi(argv[0]), /* id */
|
||||
atoi(argv[1]), /* x */
|
||||
atoi(argv[2]), /* y */
|
||||
atoi(argv[3]), /* x_radius */
|
||||
atoi(argv[4]), /* y_radius */
|
||||
atof(argv[5]), /* angle */
|
||||
atof(argv[6]) /* force */
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __guac_handle_mouse(guac_user* user, int argc, char** argv) {
|
||||
if (user->mouse_handler)
|
||||
return user->mouse_handler(
|
||||
@ -685,23 +660,6 @@ int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) {
|
||||
|
||||
}
|
||||
|
||||
int __guac_handshake_name_handler(guac_user* user, int argc, char** argv) {
|
||||
|
||||
/* Free any past value for the user's name */
|
||||
free((char *) user->info.name);
|
||||
|
||||
/* If a value is provided for the name, copy it into guac_user. */
|
||||
if (argc > 0 && strcmp(argv[0], ""))
|
||||
user->info.name = (const char*) strdup(argv[0]);
|
||||
|
||||
/* No or empty value was provided, so make sure this is NULLed out. */
|
||||
else
|
||||
user->info.name = NULL;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) {
|
||||
|
||||
/* Free any past value */
|
||||
|
@ -85,13 +85,6 @@ __guac_instruction_handler __guac_handle_sync;
|
||||
*/
|
||||
__guac_instruction_handler __guac_handle_mouse;
|
||||
|
||||
/**
|
||||
* Internal initial handler for the touch instruction. When a touch instruction
|
||||
* is received, this handler will be called. The client's touch handler will
|
||||
* be invoked if defined.
|
||||
*/
|
||||
__guac_instruction_handler __guac_handle_touch;
|
||||
|
||||
/**
|
||||
* Internal initial handler for the key instruction. When a key instruction
|
||||
* is received, this handler will be called. The client's key handler will
|
||||
@ -218,13 +211,6 @@ __guac_instruction_handler __guac_handshake_video_handler;
|
||||
*/
|
||||
__guac_instruction_handler __guac_handshake_image_handler;
|
||||
|
||||
/**
|
||||
* Internal handler function that is called when the name instruction is
|
||||
* received during the handshake process, specifying the name of the Guacamole
|
||||
* user establishing the connection.
|
||||
*/
|
||||
__guac_instruction_handler __guac_handshake_name_handler;
|
||||
|
||||
/**
|
||||
* Internal handler function that is called when the timezone instruction is
|
||||
* received during the handshake process, specifying the timezone of the
|
||||
|
@ -296,7 +296,6 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
|
||||
user->info.audio_mimetypes = NULL;
|
||||
user->info.image_mimetypes = NULL;
|
||||
user->info.video_mimetypes = NULL;
|
||||
user->info.name = NULL;
|
||||
user->info.timezone = NULL;
|
||||
|
||||
/* Count number of arguments. */
|
||||
@ -371,8 +370,7 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
|
||||
guac_free_mimetypes((char **) user->info.image_mimetypes);
|
||||
guac_free_mimetypes((char **) user->info.video_mimetypes);
|
||||
|
||||
/* Free name and timezone info. */
|
||||
free((char *) user->info.name);
|
||||
/* Free timezone info. */
|
||||
free((char *) user->info.timezone);
|
||||
|
||||
guac_parser_free(parser);
|
||||
|
@ -316,15 +316,6 @@ void guac_user_stream_webp(guac_user* user, guac_socket* socket,
|
||||
|
||||
}
|
||||
|
||||
int guac_user_supports_msg(guac_user* user) {
|
||||
|
||||
if (user == NULL)
|
||||
return 0;
|
||||
|
||||
return (user->info.protocol_version >= GUAC_PROTOCOL_VERSION_1_5_0);
|
||||
|
||||
}
|
||||
|
||||
int guac_user_supports_required(guac_user* user) {
|
||||
|
||||
if (user == NULL)
|
||||
|
@ -69,9 +69,6 @@ static void __guac_wol_create_magic_packet(unsigned char packet[],
|
||||
* @param broadcast_addr
|
||||
* The broadcast address to which to send the magic WoL packet.
|
||||
*
|
||||
* @param udp_port
|
||||
* The UDP port to use when sending the WoL packet.
|
||||
*
|
||||
* @param packet
|
||||
* The magic WoL packet to send.
|
||||
*
|
||||
@ -79,13 +76,13 @@ static void __guac_wol_create_magic_packet(unsigned char packet[],
|
||||
* The number of bytes sent, or zero if nothing could be sent.
|
||||
*/
|
||||
static ssize_t __guac_wol_send_packet(const char* broadcast_addr,
|
||||
const unsigned short udp_port, unsigned char packet[]) {
|
||||
unsigned char packet[]) {
|
||||
|
||||
struct sockaddr_in wol_dest;
|
||||
int wol_socket;
|
||||
|
||||
/* Determine the IP version, starting with IPv4. */
|
||||
wol_dest.sin_port = htons(udp_port);
|
||||
wol_dest.sin_port = htons(GUAC_WOL_PORT);
|
||||
wol_dest.sin_family = AF_INET;
|
||||
int retval = inet_pton(wol_dest.sin_family, broadcast_addr, &(wol_dest.sin_addr));
|
||||
|
||||
@ -168,8 +165,7 @@ static ssize_t __guac_wol_send_packet(const char* broadcast_addr,
|
||||
|
||||
}
|
||||
|
||||
int guac_wol_wake(const char* mac_addr, const char* broadcast_addr,
|
||||
const unsigned short udp_port) {
|
||||
int guac_wol_wake(const char* mac_addr, const char* broadcast_addr) {
|
||||
|
||||
unsigned char wol_packet[GUAC_WOL_PACKET_SIZE];
|
||||
unsigned int dest_mac[6];
|
||||
@ -187,8 +183,7 @@ int guac_wol_wake(const char* mac_addr, const char* broadcast_addr,
|
||||
__guac_wol_create_magic_packet(wol_packet, dest_mac);
|
||||
|
||||
/* Send the packet and record bytes sent. */
|
||||
int bytes_sent = __guac_wol_send_packet(broadcast_addr, udp_port,
|
||||
wol_packet);
|
||||
int bytes_sent = __guac_wol_send_packet(broadcast_addr, wol_packet);
|
||||
|
||||
/* Return 0 if bytes were sent, otherwise return an error. */
|
||||
if (bytes_sent)
|
||||
|
22
src/protocols/ball/Makefile.am
Normal file
22
src/protocols/ball/Makefile.am
Normal file
@ -0,0 +1,22 @@
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
AM_FLAGS = -Werror -Wall -pedantic
|
||||
|
||||
lib_LTLIBRARIES = libguac-client-ball.la
|
||||
|
||||
noinst_HEADERS = ball.h
|
||||
|
||||
libguac_client_ball_la_SOURCES = \
|
||||
ball.c
|
||||
|
||||
libguac_client_ball_la_CFLAGS = \
|
||||
-Werror -Wall -Iinclude \
|
||||
@LIBGUAC_INCLUDE@
|
||||
|
||||
libguac_client_ball_la_LIBADD = \
|
||||
@COMMON_LTLIB@ \
|
||||
@LIBGUAC_LTLIB@
|
||||
|
||||
libguac_client_ball_la_LDFLAGS = \
|
||||
-version-info 0:0:0
|
224
src/protocols/ball/ball.c
Normal file
224
src/protocols/ball/ball.c
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "ball.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/layer.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/socket.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
const char* TUTORIAL_ARGS[] = { NULL };
|
||||
|
||||
|
||||
void* ball_render_thread(void* arg) {
|
||||
|
||||
/* Get data */
|
||||
guac_client* client = (guac_client*) arg;
|
||||
ball_client_data* data = (ball_client_data*) client->data;
|
||||
|
||||
/* Init time of last frame to current time */
|
||||
guac_timestamp last_frame = guac_timestamp_current();
|
||||
|
||||
/* Update ball position as long as client is running */
|
||||
while (client->state == GUAC_CLIENT_RUNNING) {
|
||||
|
||||
/* Default to 30ms frames */
|
||||
int frame_duration = 30;
|
||||
|
||||
/* Lengthen frame duration if client is lagging */
|
||||
int processing_lag = guac_client_get_processing_lag(client);
|
||||
if (processing_lag > frame_duration)
|
||||
frame_duration = processing_lag;
|
||||
|
||||
/* Sleep for duration of frame, then get timestamp */
|
||||
usleep(frame_duration);
|
||||
guac_timestamp current = guac_timestamp_current();
|
||||
|
||||
/* Calculate change in time */
|
||||
int delta_t = current - last_frame;
|
||||
|
||||
/* Update position */
|
||||
data->ball_x += data->ball_velocity_x * delta_t / 1000;
|
||||
data->ball_y += data->ball_velocity_y * delta_t / 1000;
|
||||
|
||||
/* Bounce if necessary */
|
||||
if (data->ball_x < 0) {
|
||||
data->ball_x = -data->ball_x;
|
||||
data->ball_velocity_x = -data->ball_velocity_x;
|
||||
}
|
||||
else if (data->ball_x >= 1024 - 128) {
|
||||
data->ball_x = (2 * (1024 - 128)) - data->ball_x;
|
||||
data->ball_velocity_x = -data->ball_velocity_x;
|
||||
}
|
||||
|
||||
if (data->ball_y < 0) {
|
||||
data->ball_y = -data->ball_y;
|
||||
data->ball_velocity_y = -data->ball_velocity_y;
|
||||
}
|
||||
else if (data->ball_y >= 768 - 128) {
|
||||
data->ball_y = (2 * (768 - 128)) - data->ball_y;
|
||||
data->ball_velocity_y = -data->ball_velocity_y;
|
||||
}
|
||||
|
||||
guac_protocol_send_move(client->socket, data->ball,
|
||||
GUAC_DEFAULT_LAYER, data->ball_x, data->ball_y, 0);
|
||||
|
||||
/* End frame and flush socket */
|
||||
guac_client_end_frame(client);
|
||||
guac_socket_flush(client->socket);
|
||||
|
||||
/* Update timestamp */
|
||||
last_frame = current;
|
||||
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
int ball_join_handler(guac_user* user, int argc, char** argv) {
|
||||
|
||||
/* Get client associated with user */
|
||||
guac_client* client = user->client;
|
||||
|
||||
/* Get ball layer from client data */
|
||||
ball_client_data* data = (ball_client_data*) client->data;
|
||||
guac_layer* ball = data->ball;
|
||||
|
||||
/* Get user-specific socket */
|
||||
guac_socket* socket = user->socket;
|
||||
|
||||
/* Send the display size */
|
||||
guac_protocol_send_size(socket, GUAC_DEFAULT_LAYER, 1024, 768);
|
||||
|
||||
/* Create background tile */
|
||||
guac_layer* texture = guac_client_alloc_buffer(client);
|
||||
|
||||
guac_protocol_send_rect(socket, texture, 0, 0, 64, 64);
|
||||
guac_protocol_send_cfill(socket, GUAC_COMP_OVER, texture,
|
||||
0x88, 0x88, 0x88, 0xFF);
|
||||
|
||||
guac_protocol_send_rect(socket, texture, 0, 0, 32, 32);
|
||||
guac_protocol_send_cfill(socket, GUAC_COMP_OVER, texture,
|
||||
0xDD, 0xDD, 0xDD, 0xFF);
|
||||
|
||||
guac_protocol_send_rect(socket, texture, 32, 32, 32, 32);
|
||||
guac_protocol_send_cfill(socket, GUAC_COMP_OVER, texture,
|
||||
0xDD, 0xDD, 0xDD, 0xFF);
|
||||
|
||||
/* Prepare a curve which covers the entire layer */
|
||||
guac_protocol_send_rect(socket, GUAC_DEFAULT_LAYER,
|
||||
0, 0, 1024, 768);
|
||||
|
||||
/* Fill curve with texture */
|
||||
guac_protocol_send_lfill(socket,
|
||||
GUAC_COMP_OVER, GUAC_DEFAULT_LAYER,
|
||||
texture);
|
||||
|
||||
/* Set up ball layer */
|
||||
guac_protocol_send_size(socket, ball, 128, 128);
|
||||
|
||||
/* Prepare a circular curve */
|
||||
guac_protocol_send_arc(socket, data->ball,
|
||||
64, 64, 62, 0, 6.28, 0);
|
||||
|
||||
guac_protocol_send_close(socket, data->ball);
|
||||
|
||||
/* Draw a 4-pixel black border */
|
||||
guac_protocol_send_cstroke(socket,
|
||||
GUAC_COMP_OVER, data->ball,
|
||||
GUAC_LINE_CAP_ROUND, GUAC_LINE_JOIN_ROUND, 4,
|
||||
0x00, 0x00, 0x00, 0xFF);
|
||||
|
||||
/* Fill the circle with color */
|
||||
guac_protocol_send_cfill(socket,
|
||||
GUAC_COMP_OVER, data->ball,
|
||||
0x00, 0x80, 0x80, 0x80);
|
||||
|
||||
/* Free texture (no longer needed) */
|
||||
guac_client_free_buffer(client, texture);
|
||||
|
||||
/* Mark end-of-frame */
|
||||
guac_protocol_send_sync(socket, client->last_sent_timestamp);
|
||||
|
||||
/* Flush buffer */
|
||||
guac_socket_flush(socket);
|
||||
|
||||
/* User successfully initialized */
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int ball_free_handler(guac_client* client) {
|
||||
|
||||
ball_client_data* data = (ball_client_data*) client->data;
|
||||
|
||||
/* Wait for render thread to terminate */
|
||||
pthread_join(data->render_thread, NULL);
|
||||
|
||||
/* Free client-level ball layer */
|
||||
guac_client_free_layer(client, data->ball);
|
||||
|
||||
/* Free client-specific data */
|
||||
free(data);
|
||||
|
||||
/* Data successfully freed */
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
int guac_client_init(guac_client* client) {
|
||||
|
||||
/* Allocate storage for client-specific data */
|
||||
ball_client_data* data = malloc(sizeof(ball_client_data));
|
||||
|
||||
/* Set up client data and handlers */
|
||||
client->data = data;
|
||||
|
||||
/* Allocate layer at the client level */
|
||||
data->ball = guac_client_alloc_layer(client);
|
||||
|
||||
/* This example does not implement any arguments */
|
||||
client->args = TUTORIAL_ARGS;
|
||||
|
||||
/* Client-level handlers */
|
||||
client->join_handler = ball_join_handler;
|
||||
client->free_handler = ball_free_handler;
|
||||
|
||||
/* Start ball at upper left */
|
||||
data->ball_x = 0;
|
||||
data->ball_y = 0;
|
||||
|
||||
/* Move at a reasonable pace to the lower right */
|
||||
data->ball_velocity_x = 200; /* pixels per second */
|
||||
data->ball_velocity_y = 200; /* pixels per second */
|
||||
|
||||
/* Start render thread */
|
||||
pthread_create(&data->render_thread, NULL, ball_render_thread, client);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
@ -17,17 +17,26 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_FIPS_H
|
||||
#define GUAC_FIPS_H
|
||||
#ifndef BALL_H
|
||||
#define BALL_H
|
||||
|
||||
/**
|
||||
* Returns a non-zero value if FIPS mode is enabled, or zero if FIPS mode
|
||||
* is not enabled.
|
||||
*
|
||||
* @return
|
||||
* A non-zero value if FIPS mode is enabled, or zero if FIPS mode is
|
||||
* not enabled.
|
||||
*/
|
||||
int guac_fips_enabled();
|
||||
#include <guacamole/layer.h>
|
||||
|
||||
#endif
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct ball_client_data {
|
||||
|
||||
guac_layer* ball;
|
||||
|
||||
int ball_x;
|
||||
int ball_y;
|
||||
|
||||
int ball_velocity_x;
|
||||
int ball_velocity_y;
|
||||
|
||||
pthread_t render_thread;
|
||||
|
||||
|
||||
} ball_client_data;
|
||||
|
||||
#endif
|
5
src/protocols/kubernetes/.gitignore
vendored
5
src/protocols/kubernetes/.gitignore
vendored
@ -1,5 +0,0 @@
|
||||
|
||||
# Auto-generated test runner and binary
|
||||
_generated_runner.c
|
||||
test_kubernetes
|
||||
|
@ -27,7 +27,6 @@ AUTOMAKE_OPTIONS = foreign
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
lib_LTLIBRARIES = libguac-client-kubernetes.la
|
||||
SUBDIRS = . tests
|
||||
|
||||
libguac_client_kubernetes_la_SOURCES = \
|
||||
argv.c \
|
||||
|
@ -53,9 +53,8 @@ int guac_kubernetes_argv_callback(guac_user* user, const char* mimetype,
|
||||
}
|
||||
|
||||
/* Update Kubernetes terminal size */
|
||||
guac_kubernetes_resize(client,
|
||||
guac_terminal_get_rows(terminal),
|
||||
guac_terminal_get_columns(terminal));
|
||||
guac_kubernetes_resize(client, terminal->term_height,
|
||||
terminal->term_width);
|
||||
|
||||
return 0;
|
||||
|
||||
@ -68,20 +67,20 @@ void* guac_kubernetes_send_current_argv(guac_user* user, void* data) {
|
||||
|
||||
/* Send current color scheme */
|
||||
guac_user_stream_argv(user, user->socket, "text/plain",
|
||||
GUAC_KUBERNETES_ARGV_COLOR_SCHEME,
|
||||
guac_terminal_get_color_scheme(terminal));
|
||||
GUAC_KUBERNETES_ARGV_COLOR_SCHEME, terminal->color_scheme);
|
||||
|
||||
/* Send current font name */
|
||||
guac_user_stream_argv(user, user->socket, "text/plain",
|
||||
GUAC_KUBERNETES_ARGV_FONT_NAME,
|
||||
guac_terminal_get_font_name(terminal));
|
||||
GUAC_KUBERNETES_ARGV_FONT_NAME, terminal->font_name);
|
||||
|
||||
/* Send current font size */
|
||||
char font_size[64];
|
||||
sprintf(font_size, "%i", guac_terminal_get_font_size(terminal));
|
||||
sprintf(font_size, "%i", terminal->font_size);
|
||||
guac_user_stream_argv(user, user->socket, "text/plain",
|
||||
GUAC_KUBERNETES_ARGV_FONT_SIZE, font_size);
|
||||
|
||||
return NULL;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "argv.h"
|
||||
#include "client.h"
|
||||
#include "common/clipboard.h"
|
||||
#include "kubernetes.h"
|
||||
#include "settings.h"
|
||||
#include "user.h"
|
||||
@ -94,10 +95,12 @@ int guac_client_init(guac_client* client) {
|
||||
guac_kubernetes_client* kubernetes_client = calloc(1, sizeof(guac_kubernetes_client));
|
||||
client->data = kubernetes_client;
|
||||
|
||||
/* Init clipboard */
|
||||
kubernetes_client->clipboard = guac_common_clipboard_alloc(GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH);
|
||||
|
||||
/* Set handlers */
|
||||
client->join_handler = guac_kubernetes_user_join_handler;
|
||||
client->free_handler = guac_kubernetes_client_free_handler;
|
||||
client->leave_handler = guac_kubernetes_user_leave_handler;
|
||||
|
||||
/* Register handlers for argument values that may be sent after the handshake */
|
||||
guac_argv_register(GUAC_KUBERNETES_ARGV_COLOR_SCHEME, guac_kubernetes_argv_callback, NULL, GUAC_ARGV_OPTION_ECHO);
|
||||
@ -129,6 +132,7 @@ int guac_kubernetes_client_free_handler(guac_client* client) {
|
||||
if (kubernetes_client->settings != NULL)
|
||||
guac_kubernetes_settings_free(kubernetes_client->settings);
|
||||
|
||||
guac_common_clipboard_free(kubernetes_client->clipboard);
|
||||
free(kubernetes_client);
|
||||
return 0;
|
||||
|
||||
|
@ -22,6 +22,11 @@
|
||||
|
||||
#include <guacamole/client.h>
|
||||
|
||||
/**
|
||||
* The maximum number of bytes to allow within the clipboard.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH 262144
|
||||
|
||||
/**
|
||||
* Static reference to the guac_client associated with the active Kubernetes
|
||||
* connection. While libwebsockets provides some means of storing and
|
||||
|
@ -18,8 +18,8 @@
|
||||
*/
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "common/clipboard.h"
|
||||
#include "kubernetes.h"
|
||||
#include "terminal/terminal.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/stream.h>
|
||||
@ -33,7 +33,7 @@ int guac_kubernetes_clipboard_handler(guac_user* user, guac_stream* stream,
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Clear clipboard and prepare for new data */
|
||||
guac_terminal_clipboard_reset(kubernetes_client->term, mimetype);
|
||||
guac_common_clipboard_reset(kubernetes_client->clipboard, mimetype);
|
||||
|
||||
/* Set handlers for clipboard stream */
|
||||
stream->blob_handler = guac_kubernetes_clipboard_blob_handler;
|
||||
@ -50,7 +50,7 @@ int guac_kubernetes_clipboard_blob_handler(guac_user* user,
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Append new data */
|
||||
guac_terminal_clipboard_append(kubernetes_client->term, data, length);
|
||||
guac_common_clipboard_append(kubernetes_client->clipboard, data, length);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -17,12 +17,12 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "common/recording.h"
|
||||
#include "input.h"
|
||||
#include "kubernetes.h"
|
||||
#include "terminal/terminal.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/recording.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
@ -41,7 +41,7 @@ int guac_kubernetes_user_mouse_handler(guac_user* user,
|
||||
|
||||
/* Report mouse position within recording */
|
||||
if (kubernetes_client->recording != NULL)
|
||||
guac_recording_report_mouse(kubernetes_client->recording, x, y,
|
||||
guac_common_recording_report_mouse(kubernetes_client->recording, x, y,
|
||||
mask);
|
||||
|
||||
guac_terminal_send_mouse(term, user, x, y, mask);
|
||||
@ -57,7 +57,7 @@ int guac_kubernetes_user_key_handler(guac_user* user, int keysym, int pressed) {
|
||||
|
||||
/* Report key state within recording */
|
||||
if (kubernetes_client->recording != NULL)
|
||||
guac_recording_report_key(kubernetes_client->recording,
|
||||
guac_common_recording_report_key(kubernetes_client->recording,
|
||||
keysym, pressed);
|
||||
|
||||
/* Skip if terminal not yet ready */
|
||||
@ -86,9 +86,8 @@ int guac_kubernetes_user_size_handler(guac_user* user, int width, int height) {
|
||||
guac_terminal_resize(terminal, width, height);
|
||||
|
||||
/* Update Kubernetes terminal window size if connected */
|
||||
guac_kubernetes_resize(client,
|
||||
guac_terminal_get_rows(terminal),
|
||||
guac_terminal_get_columns(terminal));
|
||||
guac_kubernetes_resize(client, terminal->term_height,
|
||||
terminal->term_width);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include "argv.h"
|
||||
#include "client.h"
|
||||
#include "common/recording.h"
|
||||
#include "io.h"
|
||||
#include "kubernetes.h"
|
||||
#include "ssl.h"
|
||||
@ -29,7 +30,6 @@
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/recording.h>
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <pthread.h>
|
||||
@ -213,11 +213,10 @@ void* guac_kubernetes_client_thread(void* data) {
|
||||
}
|
||||
|
||||
/* Generate endpoint for attachment URL */
|
||||
if (guac_kubernetes_endpoint_uri(endpoint_path, sizeof(endpoint_path),
|
||||
if (guac_kubernetes_endpoint_attach(endpoint_path, sizeof(endpoint_path),
|
||||
settings->kubernetes_namespace,
|
||||
settings->kubernetes_pod,
|
||||
settings->kubernetes_container,
|
||||
settings->exec_command)) {
|
||||
settings->kubernetes_container)) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Unable to generate path for Kubernetes API endpoint: "
|
||||
"Resulting path too long");
|
||||
@ -229,33 +228,21 @@ void* guac_kubernetes_client_thread(void* data) {
|
||||
|
||||
/* Set up screen recording, if requested */
|
||||
if (settings->recording_path != NULL) {
|
||||
kubernetes_client->recording = guac_recording_create(client,
|
||||
kubernetes_client->recording = guac_common_recording_create(client,
|
||||
settings->recording_path,
|
||||
settings->recording_name,
|
||||
settings->create_recording_path,
|
||||
!settings->recording_exclude_output,
|
||||
!settings->recording_exclude_mouse,
|
||||
0, /* Touch events not supported */
|
||||
settings->recording_include_keys);
|
||||
}
|
||||
|
||||
/* Create terminal options with required parameters */
|
||||
guac_terminal_options* options = guac_terminal_options_create(
|
||||
settings->width, settings->height, settings->resolution);
|
||||
|
||||
/* Set optional parameters */
|
||||
options->disable_copy = settings->disable_copy;
|
||||
options->max_scrollback = settings->max_scrollback;
|
||||
options->font_name = settings->font_name;
|
||||
options->font_size = settings->font_size;
|
||||
options->color_scheme = settings->color_scheme;
|
||||
options->backspace = settings->backspace;
|
||||
|
||||
/* Create terminal */
|
||||
kubernetes_client->term = guac_terminal_create(client, options);
|
||||
|
||||
/* Free options struct now that it's been used */
|
||||
free(options);
|
||||
kubernetes_client->term = guac_terminal_create(client,
|
||||
kubernetes_client->clipboard, settings->disable_copy,
|
||||
settings->max_scrollback, settings->font_name, settings->font_size,
|
||||
settings->resolution, settings->width, settings->height,
|
||||
settings->color_scheme, settings->backspace);
|
||||
|
||||
/* Fail if terminal init failed */
|
||||
if (kubernetes_client->term == NULL) {
|
||||
@ -369,7 +356,7 @@ fail:
|
||||
|
||||
/* Clean up recording, if in progress */
|
||||
if (kubernetes_client->recording != NULL)
|
||||
guac_recording_free(kubernetes_client->recording);
|
||||
guac_common_recording_free(kubernetes_client->recording);
|
||||
|
||||
/* Free WebSocket context if successfully allocated */
|
||||
if (kubernetes_client->context != NULL)
|
||||
@ -413,8 +400,8 @@ void guac_kubernetes_force_redraw(guac_client* client) {
|
||||
|
||||
/* Get current terminal dimensions */
|
||||
guac_terminal* term = kubernetes_client->term;
|
||||
int rows = guac_terminal_get_rows(term);
|
||||
int columns = guac_terminal_get_columns(term);
|
||||
int rows = term->term_height;
|
||||
int columns = term->term_width;
|
||||
|
||||
/* Force a redraw by increasing the terminal size by one character in
|
||||
* each dimension and then resizing it back to normal (the same technique
|
||||
|
@ -21,12 +21,12 @@
|
||||
#define GUAC_KUBERNETES_H
|
||||
|
||||
#include "common/clipboard.h"
|
||||
#include "common/recording.h"
|
||||
#include "io.h"
|
||||
#include "settings.h"
|
||||
#include "terminal/terminal.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/recording.h>
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <pthread.h>
|
||||
@ -102,6 +102,11 @@ typedef struct guac_kubernetes_client {
|
||||
*/
|
||||
pthread_t client_thread;
|
||||
|
||||
/**
|
||||
* The current clipboard contents.
|
||||
*/
|
||||
guac_common_clipboard* clipboard;
|
||||
|
||||
/**
|
||||
* The terminal which will render all output from the Kubernetes pod.
|
||||
*/
|
||||
@ -123,7 +128,7 @@ typedef struct guac_kubernetes_client {
|
||||
* The in-progress session recording, or NULL if no recording is in
|
||||
* progress.
|
||||
*/
|
||||
guac_recording* recording;
|
||||
guac_common_recording* recording;
|
||||
|
||||
} guac_kubernetes_client;
|
||||
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
#include "argv.h"
|
||||
#include "settings.h"
|
||||
#include "terminal/terminal.h"
|
||||
|
||||
#include <guacamole/user.h>
|
||||
|
||||
@ -32,7 +31,6 @@ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = {
|
||||
"namespace",
|
||||
"pod",
|
||||
"container",
|
||||
"exec-command",
|
||||
"use-ssl",
|
||||
"client-cert",
|
||||
"client-key",
|
||||
@ -88,11 +86,6 @@ enum KUBERNETES_ARGS_IDX {
|
||||
*/
|
||||
IDX_CONTAINER,
|
||||
|
||||
/**
|
||||
* The command used by exec call. If omitted, attach call will be used.
|
||||
*/
|
||||
IDX_EXEC_COMMAND,
|
||||
|
||||
/**
|
||||
* Whether SSL/TLS should be used. If omitted, SSL/TLS will not be used.
|
||||
*/
|
||||
@ -216,8 +209,8 @@ enum KUBERNETES_ARGS_IDX {
|
||||
IDX_READ_ONLY,
|
||||
|
||||
/**
|
||||
* ASCII code, as an integer to use for the backspace key, or
|
||||
* GUAC_TERMINAL_DEFAULT_BACKSPACE if not specified.
|
||||
* ASCII code, as an integer to use for the backspace key, or 127
|
||||
* if not specified.
|
||||
*/
|
||||
IDX_BACKSPACE,
|
||||
|
||||
@ -282,11 +275,6 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user,
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_CONTAINER, NULL);
|
||||
|
||||
/* Read exec command (optional) */
|
||||
settings->exec_command =
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_EXEC_COMMAND, NULL);
|
||||
|
||||
/* Parse whether SSL should be used */
|
||||
settings->use_ssl =
|
||||
guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
@ -321,22 +309,22 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user,
|
||||
/* Read maximum scrollback size */
|
||||
settings->max_scrollback =
|
||||
guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_SCROLLBACK, GUAC_TERMINAL_DEFAULT_MAX_SCROLLBACK);
|
||||
IDX_SCROLLBACK, GUAC_KUBERNETES_DEFAULT_MAX_SCROLLBACK);
|
||||
|
||||
/* Read font name */
|
||||
settings->font_name =
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_FONT_NAME, GUAC_TERMINAL_DEFAULT_FONT_NAME);
|
||||
IDX_FONT_NAME, GUAC_KUBERNETES_DEFAULT_FONT_NAME);
|
||||
|
||||
/* Read font size */
|
||||
settings->font_size =
|
||||
guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_FONT_SIZE, GUAC_TERMINAL_DEFAULT_FONT_SIZE);
|
||||
IDX_FONT_SIZE, GUAC_KUBERNETES_DEFAULT_FONT_SIZE);
|
||||
|
||||
/* Copy requested color scheme */
|
||||
settings->color_scheme =
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_COLOR_SCHEME, GUAC_TERMINAL_DEFAULT_COLOR_SCHEME);
|
||||
IDX_COLOR_SCHEME, "");
|
||||
|
||||
/* Pull width/height/resolution directly from user */
|
||||
settings->width = user->info.optimal_width;
|
||||
@ -391,7 +379,7 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user,
|
||||
/* Parse backspace key code */
|
||||
settings->backspace =
|
||||
guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_BACKSPACE, GUAC_TERMINAL_DEFAULT_BACKSPACE);
|
||||
IDX_BACKSPACE, 127);
|
||||
|
||||
/* Parse clipboard copy disable flag */
|
||||
settings->disable_copy =
|
||||
@ -418,9 +406,6 @@ void guac_kubernetes_settings_free(guac_kubernetes_settings* settings) {
|
||||
free(settings->kubernetes_pod);
|
||||
free(settings->kubernetes_container);
|
||||
|
||||
/* Free Kubernetes exec command */
|
||||
free(settings->exec_command);
|
||||
|
||||
/* Free SSL/TLS details */
|
||||
free(settings->client_cert);
|
||||
free(settings->client_key);
|
||||
|
@ -24,6 +24,17 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* The name of the font to use for the terminal if no name is specified.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_DEFAULT_FONT_NAME "monospace"
|
||||
|
||||
/**
|
||||
* The size of the font to use for the terminal if no font size is specified,
|
||||
* in points.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_DEFAULT_FONT_SIZE 12
|
||||
|
||||
/**
|
||||
* The port to connect to when initiating any Kubernetes connection, if no
|
||||
* other port is specified.
|
||||
@ -46,6 +57,11 @@
|
||||
*/
|
||||
#define GUAC_KUBERNETES_DEFAULT_RECORDING_NAME "recording"
|
||||
|
||||
/**
|
||||
* The default maximum scrollback size in rows.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_DEFAULT_MAX_SCROLLBACK 1000
|
||||
|
||||
/**
|
||||
* Settings for the Kubernetes connection. The values for this structure are
|
||||
* parsed from the arguments given during the Guacamole protocol handshake
|
||||
@ -81,12 +97,6 @@ typedef struct guac_kubernetes_settings {
|
||||
*/
|
||||
char* kubernetes_container;
|
||||
|
||||
/**
|
||||
* The command to generate api endpoint for call exec.
|
||||
* If omitted call attach will be used.
|
||||
*/
|
||||
char* exec_command;
|
||||
|
||||
/**
|
||||
* Whether SSL/TLS should be used.
|
||||
*/
|
||||
|
@ -1,66 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
|
||||
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
|
||||
# its own license boilerplate to the generated Makefile.in, that boilerplate
|
||||
# does not apply to the transcluded portions of Makefile.am which are licensed
|
||||
# to you by the ASF under the Apache License, Version 2.0, as described above.
|
||||
#
|
||||
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
#
|
||||
# Unit tests for Kubernetes support
|
||||
#
|
||||
|
||||
check_PROGRAMS = test_kubernetes
|
||||
TESTS = $(check_PROGRAMS)
|
||||
|
||||
test_kubernetes_SOURCES = \
|
||||
url/append.c \
|
||||
url/escape.c
|
||||
|
||||
test_kubernetes_CFLAGS = \
|
||||
-Werror -Wall -pedantic \
|
||||
@LIBGUAC_CLIENT_KUBERNETES_INCLUDE@ \
|
||||
@LIBGUAC_INCLUDE@
|
||||
|
||||
test_kubernetes_LDADD = \
|
||||
@CUNIT_LIBS@ \
|
||||
@LIBGUAC_CLIENT_KUBERNETES_LTLIB@
|
||||
|
||||
#
|
||||
# Autogenerate test runner
|
||||
#
|
||||
|
||||
GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl
|
||||
CLEANFILES = _generated_runner.c
|
||||
|
||||
_generated_runner.c: $(test_kubernetes_SOURCES)
|
||||
$(AM_V_GEN) $(GEN_RUNNER) $(test_kubernetes_SOURCES) > $@
|
||||
|
||||
nodist_test_kubernetes_SOURCES = \
|
||||
_generated_runner.c
|
||||
|
||||
# Use automake's TAP test driver for running any tests
|
||||
LOG_DRIVER = \
|
||||
env AM_TAP_AWK='$(AWK)' \
|
||||
$(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
|
||||
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "url.h"
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* Verifies that guac_kubernetes_append_endpoint_param() correctly appends
|
||||
* parameters to URLs that do not already have a query string.
|
||||
*/
|
||||
void test_url__append_no_query() {
|
||||
|
||||
char url[256] = "http://example.net";
|
||||
|
||||
CU_ASSERT(!guac_kubernetes_append_endpoint_param(url, sizeof(url), "foo", "100% test value"));
|
||||
CU_ASSERT_STRING_EQUAL(url, "http://example.net?foo=100%25%20test%20value");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that guac_kubernetes_append_endpoint_param() correctly appends
|
||||
* parameters to URLs that already have a query string.
|
||||
*/
|
||||
void test_url__append_existing_query() {
|
||||
|
||||
char url[256] = "http://example.net?foo=test%20value";
|
||||
|
||||
CU_ASSERT(!guac_kubernetes_append_endpoint_param(url, sizeof(url), "foo2", "yet&another/test\\value"));
|
||||
CU_ASSERT_STRING_EQUAL(url, "http://example.net?foo=test%20value&foo2=yet%26another%2Ftest%5Cvalue");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that guac_kubernetes_append_endpoint_param() refuses to overflow
|
||||
* the bounds of the provided buffer.
|
||||
*/
|
||||
void test_url__append_bounds() {
|
||||
|
||||
char url[256];
|
||||
|
||||
/* Appending "?a=1" to the 18-character string "http://example.net" should
|
||||
* fail for all buffer sizes with 22 bytes or less, with a 22-byte buffer
|
||||
* lacking space for the null terminator */
|
||||
for (int length = 18; length <= 22; length++) {
|
||||
strcpy(url, "http://example.net");
|
||||
printf("Testing buffer with length %i ...\n", length);
|
||||
CU_ASSERT(guac_kubernetes_append_endpoint_param(url, length, "a", "1"));
|
||||
}
|
||||
|
||||
/* A 23-byte buffer should be sufficient */
|
||||
strcpy(url, "http://example.net");
|
||||
CU_ASSERT(!guac_kubernetes_append_endpoint_param(url, 23, "a", "1"));
|
||||
|
||||
}
|
||||
|
@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "url.h"
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* Verifies that guac_kubernetes_escape_url_component() correctly escapes
|
||||
* characters that would otherwise have special meaning within URLs.
|
||||
*/
|
||||
void test_url__escape_special() {
|
||||
|
||||
char value[256];
|
||||
|
||||
CU_ASSERT(!guac_kubernetes_escape_url_component(value, sizeof(value), "?foo%20bar\\1/2&3=4"));
|
||||
CU_ASSERT_STRING_EQUAL(value, "%3Ffoo%2520bar%5C1%2F2%263%3D4");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that guac_kubernetes_escape_url_component() leaves strings
|
||||
* untouched if they contain no characters requiring escaping.
|
||||
*/
|
||||
void test_url__escape_nospecial() {
|
||||
|
||||
char value[256];
|
||||
|
||||
CU_ASSERT(!guac_kubernetes_escape_url_component(value, sizeof(value), "potato"));
|
||||
CU_ASSERT_STRING_EQUAL(value, "potato");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that guac_kubernetes_escape_url_component() refuses to overflow the
|
||||
* bounds of the provided buffer.
|
||||
*/
|
||||
void test_url__escape_bounds() {
|
||||
|
||||
char value[256];
|
||||
|
||||
/* Escaping "?potato" (or "potato?") should fail for all buffer sizes with
|
||||
* 9 bytes or less, with a 9-byte buffer lacking space for the null
|
||||
* terminator */
|
||||
for (int length = 0; length <= 9; length++) {
|
||||
printf("Testing buffer with length %i ...\n", length);
|
||||
CU_ASSERT(guac_kubernetes_escape_url_component(value, length, "?potato"));
|
||||
CU_ASSERT(guac_kubernetes_escape_url_component(value, length, "potato?"));
|
||||
}
|
||||
|
||||
/* A 10-byte buffer should be sufficient */
|
||||
CU_ASSERT(!guac_kubernetes_escape_url_component(value, 10, "?potato"));
|
||||
|
||||
}
|
||||
|
@ -89,60 +89,15 @@ int guac_kubernetes_escape_url_component(char* output, int length,
|
||||
|
||||
}
|
||||
|
||||
int guac_kubernetes_append_endpoint_param(char* buffer, int length,
|
||||
const char* param_name, const char* param_value) {
|
||||
|
||||
char escaped_param_value[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
|
||||
|
||||
/* Escape value */
|
||||
if (guac_kubernetes_escape_url_component(escaped_param_value,
|
||||
sizeof(escaped_param_value), param_value))
|
||||
return 1;
|
||||
|
||||
char* str = buffer;
|
||||
|
||||
int str_len = 0;
|
||||
int qmark = 0;
|
||||
|
||||
while (*str != '\0') {
|
||||
|
||||
/* Look for a question mark */
|
||||
if (*str=='?') qmark = 1;
|
||||
|
||||
/* Compute the buffer string length */
|
||||
str_len++;
|
||||
|
||||
/* Verify the buffer null terminated */
|
||||
if (str_len >= length) return 1;
|
||||
|
||||
/* Next character */
|
||||
str++;
|
||||
}
|
||||
|
||||
/* Determine the parameter delimiter */
|
||||
char delimiter = '?';
|
||||
if (qmark) delimiter = '&';
|
||||
|
||||
/* Advance to end of buffer, where the new parameter and delimiter need to
|
||||
* be appended */
|
||||
buffer += str_len;
|
||||
length -= str_len;
|
||||
|
||||
/* Write the parameter and delimiter to the buffer */
|
||||
int written = snprintf(buffer, length, "%c%s=%s", delimiter,
|
||||
param_name, escaped_param_value);
|
||||
|
||||
/* The parameter was successfully added if it was written to the given
|
||||
* buffer without truncation */
|
||||
return (written < 0 || written >= length);
|
||||
}
|
||||
|
||||
int guac_kubernetes_endpoint_uri(char* buffer, int length,
|
||||
int guac_kubernetes_endpoint_attach(char* buffer, int length,
|
||||
const char* kubernetes_namespace, const char* kubernetes_pod,
|
||||
const char* kubernetes_container, const char* exec_command) {
|
||||
const char* kubernetes_container) {
|
||||
|
||||
int written;
|
||||
|
||||
char escaped_namespace[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
|
||||
char escaped_pod[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
|
||||
char escaped_container[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
|
||||
|
||||
/* Escape Kubernetes namespace */
|
||||
if (guac_kubernetes_escape_url_component(escaped_namespace,
|
||||
@ -154,38 +109,29 @@ int guac_kubernetes_endpoint_uri(char* buffer, int length,
|
||||
sizeof(escaped_pod), kubernetes_pod))
|
||||
return 1;
|
||||
|
||||
/* Determine the call type */
|
||||
char* call = "attach";
|
||||
if (exec_command != NULL)
|
||||
call = "exec";
|
||||
|
||||
int written;
|
||||
|
||||
/* Generate the endpoint path and write to the buffer */
|
||||
written = snprintf(buffer, length,
|
||||
"/api/v1/namespaces/%s/pods/%s/%s", escaped_namespace, escaped_pod, call);
|
||||
|
||||
/* Operation successful if the endpoint path was written to the given
|
||||
* buffer without truncation */
|
||||
if (written < 0 || written >= length)
|
||||
return 1;
|
||||
|
||||
/* Append exec command parameter */
|
||||
if (exec_command != NULL) {
|
||||
if (guac_kubernetes_append_endpoint_param(buffer,
|
||||
length, "command", exec_command))
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Append kubernetes container parameter */
|
||||
/* Generate attachment endpoint URL */
|
||||
if (kubernetes_container != NULL) {
|
||||
if (guac_kubernetes_append_endpoint_param(buffer,
|
||||
length, "container", kubernetes_container))
|
||||
|
||||
/* Escape container name */
|
||||
if (guac_kubernetes_escape_url_component(escaped_container,
|
||||
sizeof(escaped_container), kubernetes_container))
|
||||
return 1;
|
||||
|
||||
written = snprintf(buffer, length,
|
||||
"/api/v1/namespaces/%s/pods/%s/attach"
|
||||
"?container=%s&stdin=true&stdout=true&tty=true",
|
||||
escaped_namespace, escaped_pod, escaped_container);
|
||||
}
|
||||
else {
|
||||
written = snprintf(buffer, length,
|
||||
"/api/v1/namespaces/%s/pods/%s/attach"
|
||||
"?stdin=true&stdout=true&tty=true",
|
||||
escaped_namespace, escaped_pod);
|
||||
}
|
||||
|
||||
/* Append stdin, stdout and tty parameters */
|
||||
return (guac_kubernetes_append_endpoint_param(buffer, length, "stdin", "true"))
|
||||
|| (guac_kubernetes_append_endpoint_param(buffer, length, "stdout", "true"))
|
||||
|| (guac_kubernetes_append_endpoint_param(buffer, length, "tty", "true"));
|
||||
/* Endpoint URL was successfully generated if it was written to the given
|
||||
* buffer without truncation */
|
||||
return !(written < length - 1);
|
||||
|
||||
}
|
||||
|
||||
|
@ -49,35 +49,6 @@
|
||||
int guac_kubernetes_escape_url_component(char* output, int length,
|
||||
const char* str);
|
||||
|
||||
/**
|
||||
* Appends the given query parameter and value to the given buffer. If the
|
||||
* buffer does not already contain the '?' character denoting the start of the
|
||||
* query string, it will be added. If the buffer already contains a query
|
||||
* string, a '&' character will be added before the new parameter. The
|
||||
* parameter value will automatically be URL-escaped as necessary.
|
||||
*
|
||||
* @param buffer
|
||||
* The buffer which should receive the parameter. It could contain the endpoint path.
|
||||
* The parameter will be written to the end of the buffer.
|
||||
*
|
||||
* @param length
|
||||
* The number of bytes available in the given buffer.
|
||||
*
|
||||
* @param param_name
|
||||
* The name of the parameter. If the parameter name contains characters
|
||||
* with special meaning to URLs, it must already be URL-escaped.
|
||||
*
|
||||
* @param param_value
|
||||
* The value of the parameter.
|
||||
*
|
||||
* @return
|
||||
* Zero if the parameter was successfully attached to the buffer,
|
||||
* non-zero if insufficient space exists within the buffer or
|
||||
* buffer not null terminated.
|
||||
*/
|
||||
int guac_kubernetes_append_endpoint_param(char* buffer, int length,
|
||||
const char* param_name, const char* param_value);
|
||||
|
||||
/**
|
||||
* Generates the full path to the Kubernetes API endpoint which handles
|
||||
* attaching to running containers within specific pods. Values within the path
|
||||
@ -101,18 +72,14 @@ int guac_kubernetes_append_endpoint_param(char* buffer, int length,
|
||||
* @param kubernetes_container
|
||||
* The name of the container to attach to, or NULL to arbitrarily attach
|
||||
* to the first container in the pod.
|
||||
*
|
||||
* @param exec_command
|
||||
* The command used to run a new process and attach to it,
|
||||
* instead of the main container process.
|
||||
*
|
||||
* @return
|
||||
* Zero if the endpoint path was successfully written to the provided
|
||||
* buffer, non-zero if insufficient space exists within the buffer.
|
||||
*/
|
||||
int guac_kubernetes_endpoint_uri(char* buffer, int length,
|
||||
int guac_kubernetes_endpoint_attach(char* buffer, int length,
|
||||
const char* kubernetes_namespace, const char* kubernetes_pod,
|
||||
const char* kubernetes_container, const char* exec_command);
|
||||
const char* kubernetes_container);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -109,8 +109,8 @@ int guac_kubernetes_user_leave_handler(guac_user* user) {
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) user->client->data;
|
||||
|
||||
/* Remove the user from the terminal */
|
||||
guac_terminal_remove_user(kubernetes_client->term, user);
|
||||
/* Update shared cursor state */
|
||||
guac_common_cursor_remove_user(kubernetes_client->term->cursor, user);
|
||||
|
||||
/* Free settings if not owner (owner settings will be freed with client) */
|
||||
if (!user->owner) {
|
||||
|
@ -56,8 +56,6 @@ libguac_client_rdp_la_SOURCES = \
|
||||
channels/rdpdr/rdpdr-messages.c \
|
||||
channels/rdpdr/rdpdr-printer.c \
|
||||
channels/rdpdr/rdpdr.c \
|
||||
channels/rdpei.c \
|
||||
channels/rdpgfx.c \
|
||||
channels/rdpsnd/rdpsnd-messages.c \
|
||||
channels/rdpsnd/rdpsnd.c \
|
||||
client.c \
|
||||
@ -103,8 +101,6 @@ noinst_HEADERS = \
|
||||
channels/rdpdr/rdpdr-messages.h \
|
||||
channels/rdpdr/rdpdr-printer.h \
|
||||
channels/rdpdr/rdpdr.h \
|
||||
channels/rdpei.h \
|
||||
channels/rdpgfx.h \
|
||||
channels/rdpsnd/rdpsnd-messages.h \
|
||||
channels/rdpsnd/rdpsnd.h \
|
||||
client.h \
|
||||
@ -230,7 +226,6 @@ BUILT_SOURCES = \
|
||||
rdp_keymaps = \
|
||||
$(srcdir)/keymaps/base.keymap \
|
||||
$(srcdir)/keymaps/failsafe.keymap \
|
||||
$(srcdir)/keymaps/cs-cz-qwertz.keymap \
|
||||
$(srcdir)/keymaps/de_de_qwertz.keymap \
|
||||
$(srcdir)/keymaps/de_ch_qwertz.keymap \
|
||||
$(srcdir)/keymaps/en_gb_qwerty.keymap \
|
||||
@ -238,14 +233,11 @@ rdp_keymaps = \
|
||||
$(srcdir)/keymaps/es_es_qwerty.keymap \
|
||||
$(srcdir)/keymaps/es_latam_qwerty.keymap \
|
||||
$(srcdir)/keymaps/fr_be_azerty.keymap \
|
||||
$(srcdir)/keymaps/fr_ca_qwerty.keymap \
|
||||
$(srcdir)/keymaps/fr_ch_qwertz.keymap \
|
||||
$(srcdir)/keymaps/fr_fr_azerty.keymap \
|
||||
$(srcdir)/keymaps/hu_hu_qwertz.keymap \
|
||||
$(srcdir)/keymaps/it_it_qwerty.keymap \
|
||||
$(srcdir)/keymaps/ja_jp_qwerty.keymap \
|
||||
$(srcdir)/keymaps/no_no_qwerty.keymap \
|
||||
$(srcdir)/keymaps/pl_pl_qwerty.keymap \
|
||||
$(srcdir)/keymaps/pt_br_qwerty.keymap \
|
||||
$(srcdir)/keymaps/sv_se_qwerty.keymap \
|
||||
$(srcdir)/keymaps/da_dk_qwerty.keymap \
|
||||
|
@ -149,7 +149,7 @@ BOOL guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL pri
|
||||
|
||||
else {
|
||||
|
||||
/* Make sure that the received bitmap is not NULL before processing */
|
||||
/* Make sure that the recieved bitmap is not NULL before processing */
|
||||
if (bitmap == NULL) {
|
||||
guac_client_log(client, GUAC_LOG_INFO, "NULL bitmap found in bitmap_setsurface instruction.");
|
||||
return TRUE;
|
||||
|
@ -24,253 +24,16 @@
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/socket.h>
|
||||
#include <guacamole/stream.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
/**
|
||||
* The number of nanoseconds in one second.
|
||||
*/
|
||||
#define NANOS_PER_SECOND 1000000000L
|
||||
|
||||
/**
|
||||
* Returns whether the given timespec represents a point in time in the future
|
||||
* relative to the current system time.
|
||||
*
|
||||
* @param ts
|
||||
* The timespec to test.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the given timespec is in the future relative to the current
|
||||
* system time, zero otherwise.
|
||||
*/
|
||||
static int guac_rdp_audio_buffer_is_future(const struct timespec* ts) {
|
||||
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
|
||||
if (now.tv_sec != ts->tv_sec)
|
||||
return now.tv_sec < ts->tv_sec;
|
||||
|
||||
return now.tv_nsec < ts->tv_nsec;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given audio buffer may be flushed. An audio buffer may
|
||||
* be flushed if the audio buffer is not currently being freed, at least one
|
||||
* packet of audio data is available within the buffer, and flushing the next
|
||||
* packet of audio data now would not violate scheduling/throttling rules for
|
||||
* outbound audio data.
|
||||
*
|
||||
* IMPORTANT: The guac_rdp_audio_buffer's lock MUST already be held when
|
||||
* invoking this function.
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The guac_rdp_audio_buffer to test.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the given audio buffer may be flushed, zero if the audio
|
||||
* buffer cannot be flushed for any reason.
|
||||
*/
|
||||
static int guac_rdp_audio_buffer_may_flush(guac_rdp_audio_buffer* audio_buffer) {
|
||||
return !audio_buffer->stopping
|
||||
&& audio_buffer->packet_size > 0
|
||||
&& audio_buffer->bytes_written >= audio_buffer->packet_size
|
||||
&& !guac_rdp_audio_buffer_is_future(&audio_buffer->next_flush);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration of the given quantity of audio data in milliseconds.
|
||||
*
|
||||
* @param format
|
||||
* The format of the audio data in question.
|
||||
*
|
||||
* @param length
|
||||
* The number of bytes of audio data.
|
||||
*
|
||||
* @return
|
||||
* The duration of the audio data in milliseconds.
|
||||
*/
|
||||
static int guac_rdp_audio_buffer_duration(const guac_rdp_audio_format* format, int length) {
|
||||
return length * 1000 / format->rate / format->bps / format->channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes required to store audio data in the given format
|
||||
* covering the given length of time.
|
||||
*
|
||||
* @param format
|
||||
* The format of the audio data in question.
|
||||
*
|
||||
* @param duration
|
||||
* The duration of the audio data in milliseconds.
|
||||
*
|
||||
* @return
|
||||
* The number of bytes required to store audio data in the given format
|
||||
* covering the given length of time.
|
||||
*/
|
||||
static int guac_rdp_audio_buffer_length(const guac_rdp_audio_format* format, int duration) {
|
||||
return duration * format->rate * format->bps * format->channels / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the given guac_rdp_audio_buffer that a single packet of audio data
|
||||
* has just been flushed, updating the scheduled time of the next flush. The
|
||||
* timing of the next flush will be set such that the overall real time audio
|
||||
* generation rate is not exceeded, but will be adjusted as necessary to
|
||||
* compensate for latency induced by differences in audio packet size/duration.
|
||||
*
|
||||
* IMPORTANT: The guac_rdp_audio_buffer's lock MUST already be held when
|
||||
* invoking this function.
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The guac_rdp_audio_buffer to update.
|
||||
*/
|
||||
static void guac_rdp_audio_buffer_schedule_flush(guac_rdp_audio_buffer* audio_buffer) {
|
||||
|
||||
struct timespec next_flush;
|
||||
clock_gettime(CLOCK_REALTIME, &next_flush);
|
||||
|
||||
/* Calculate the point in time that the next flush would be allowed,
|
||||
* assuming that the remote server processes data no faster than
|
||||
* real time */
|
||||
uint64_t delta_nsecs = audio_buffer->packet_size * NANOS_PER_SECOND
|
||||
/ audio_buffer->out_format.rate
|
||||
/ audio_buffer->out_format.bps
|
||||
/ audio_buffer->out_format.channels;
|
||||
|
||||
/* Amortize the additional latency from packet data buffered beyond the
|
||||
* desired packet size over each remaining packet such that we gradually
|
||||
* approach an effective additional latency of 0 */
|
||||
int packets_remaining = audio_buffer->bytes_written / audio_buffer->packet_size;
|
||||
if (packets_remaining > 1)
|
||||
delta_nsecs = delta_nsecs * (packets_remaining - 1) / packets_remaining;
|
||||
|
||||
uint64_t nsecs = next_flush.tv_nsec + delta_nsecs;
|
||||
|
||||
next_flush.tv_sec += nsecs / NANOS_PER_SECOND;
|
||||
next_flush.tv_nsec = nsecs % NANOS_PER_SECOND;
|
||||
|
||||
audio_buffer->next_flush = next_flush;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for additional data to be available for flush within the given audio
|
||||
* buffer. If data is available but insufficient time has elapsed since the
|
||||
* last flush, this function may block until sufficient time has elapsed. If
|
||||
* the state of the audio buffer changes in any way while waiting for
|
||||
* additional data, or if the audio buffer is being freed, this function will
|
||||
* return immediately.
|
||||
*
|
||||
* It is the responsibility of the caller to check the state of the audio
|
||||
* buffer after this function returns to verify whether the desired state
|
||||
* change has occurred and re-invoke the function if needed.
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The guac_rdp_audio_buffer to wait for.
|
||||
*/
|
||||
static void guac_rdp_audio_buffer_wait(guac_rdp_audio_buffer* audio_buffer) {
|
||||
|
||||
pthread_mutex_lock(&(audio_buffer->lock));
|
||||
|
||||
/* Do not wait if audio_buffer is already closed */
|
||||
if (!audio_buffer->stopping) {
|
||||
|
||||
/* If sufficient data exists for a flush, wait until next possible
|
||||
* flush OR until some other state change occurs (such as the buffer
|
||||
* being closed) */
|
||||
if (audio_buffer->bytes_written && audio_buffer->bytes_written >= audio_buffer->packet_size)
|
||||
pthread_cond_timedwait(&audio_buffer->modified, &audio_buffer->lock,
|
||||
&audio_buffer->next_flush);
|
||||
|
||||
/* If sufficient data DOES NOT exist, we should wait indefinitely */
|
||||
else
|
||||
pthread_cond_wait(&audio_buffer->modified, &audio_buffer->lock);
|
||||
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Regularly and automatically flushes audio packets by invoking the flush
|
||||
* handler of the associated audio buffer. Packets are scheduled automatically
|
||||
* to avoid potentially exceeding the processing and buffering capabilities of
|
||||
* the software running within the RDP server. Once started, this thread runs
|
||||
* until the associated audio buffer is freed via guac_rdp_audio_buffer_free().
|
||||
*
|
||||
* @param data
|
||||
* A pointer to the guac_rdp_audio_buffer that should be flushed.
|
||||
*
|
||||
* @return
|
||||
* Always NULL.
|
||||
*/
|
||||
static void* guac_rdp_audio_buffer_flush_thread(void* data) {
|
||||
|
||||
guac_rdp_audio_buffer* audio_buffer = (guac_rdp_audio_buffer*) data;
|
||||
while (!audio_buffer->stopping) {
|
||||
|
||||
pthread_mutex_lock(&(audio_buffer->lock));
|
||||
|
||||
if (!guac_rdp_audio_buffer_may_flush(audio_buffer)) {
|
||||
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
/* Wait for additional data if we aren't able to flush */
|
||||
guac_rdp_audio_buffer_wait(audio_buffer);
|
||||
|
||||
/* We might still not be able to flush (buffer might be closed,
|
||||
* some other state change might occur that isn't receipt of data,
|
||||
* data might be received but not enough for a flush, etc.) */
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
guac_client_log(audio_buffer->client, GUAC_LOG_TRACE, "Current audio input latency: %i ms (%i bytes waiting in buffer)",
|
||||
guac_rdp_audio_buffer_duration(&audio_buffer->out_format, audio_buffer->bytes_written),
|
||||
audio_buffer->bytes_written);
|
||||
|
||||
/* Only actually invoke if defined */
|
||||
if (audio_buffer->flush_handler) {
|
||||
guac_rdp_audio_buffer_schedule_flush(audio_buffer);
|
||||
audio_buffer->flush_handler(audio_buffer,
|
||||
audio_buffer->packet_size);
|
||||
}
|
||||
|
||||
/* Shift buffer back by one packet */
|
||||
audio_buffer->bytes_written -= audio_buffer->packet_size;
|
||||
memmove(audio_buffer->packet, audio_buffer->packet + audio_buffer->packet_size, audio_buffer->bytes_written);
|
||||
|
||||
pthread_cond_broadcast(&(audio_buffer->modified));
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(guac_client* client) {
|
||||
|
||||
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() {
|
||||
guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer));
|
||||
|
||||
pthread_mutex_init(&(buffer->lock), NULL);
|
||||
pthread_cond_init(&(buffer->modified), NULL);
|
||||
buffer->client = client;
|
||||
|
||||
/* Begin automated, throttled flush of future data */
|
||||
pthread_create(&(buffer->flush_thread), NULL,
|
||||
guac_rdp_audio_buffer_flush_thread, (void*) buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@ -282,9 +45,6 @@ guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(guac_client* client) {
|
||||
* instruction nor been associated with an "ack" having an error code), and is
|
||||
* associated with an active RDP AUDIO_INPUT channel.
|
||||
*
|
||||
* IMPORTANT: The guac_rdp_audio_buffer's lock MUST already be held when
|
||||
* invoking this function.
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The audio buffer associated with the guac_stream for which the "ack"
|
||||
* instruction should be sent, if any. If there is no associated
|
||||
@ -337,7 +97,6 @@ void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer,
|
||||
audio_buffer->in_format.rate,
|
||||
audio_buffer->in_format.bps);
|
||||
|
||||
pthread_cond_broadcast(&(audio_buffer->modified));
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
}
|
||||
@ -352,7 +111,6 @@ void guac_rdp_audio_buffer_set_output(guac_rdp_audio_buffer* audio_buffer,
|
||||
audio_buffer->out_format.channels = channels;
|
||||
audio_buffer->out_format.bps = bps;
|
||||
|
||||
pthread_cond_broadcast(&(audio_buffer->modified));
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
}
|
||||
@ -373,30 +131,14 @@ void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer,
|
||||
* audio_buffer->out_format.channels
|
||||
* audio_buffer->out_format.bps;
|
||||
|
||||
/* Ensure outbound buffer includes enough space for at least 250ms of
|
||||
* audio */
|
||||
int ideal_size = guac_rdp_audio_buffer_length(&audio_buffer->out_format,
|
||||
GUAC_RDP_AUDIO_BUFFER_MIN_DURATION);
|
||||
|
||||
/* Round up to nearest whole packet */
|
||||
int ideal_packets = (ideal_size + audio_buffer->packet_size - 1) / audio_buffer->packet_size;
|
||||
|
||||
/* Allocate new buffer */
|
||||
audio_buffer->packet_buffer_size = ideal_packets * audio_buffer->packet_size;
|
||||
audio_buffer->packet = malloc(audio_buffer->packet_buffer_size);
|
||||
|
||||
guac_client_log(audio_buffer->client, GUAC_LOG_DEBUG, "Output buffer for "
|
||||
"audio input is %i bytes (up to %i ms).", audio_buffer->packet_buffer_size,
|
||||
guac_rdp_audio_buffer_duration(&audio_buffer->out_format, audio_buffer->packet_buffer_size));
|
||||
|
||||
/* Next flush can occur as soon as data is received */
|
||||
clock_gettime(CLOCK_REALTIME, &audio_buffer->next_flush);
|
||||
free(audio_buffer->packet);
|
||||
audio_buffer->packet = malloc(audio_buffer->packet_size);
|
||||
|
||||
/* Acknowledge stream creation (if stream is ready to receive) */
|
||||
guac_rdp_audio_buffer_ack(audio_buffer,
|
||||
"OK", GUAC_PROTOCOL_STATUS_SUCCESS);
|
||||
|
||||
pthread_cond_broadcast(&(audio_buffer->modified));
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
}
|
||||
@ -409,9 +151,6 @@ void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer,
|
||||
* input and output formats, the number of bytes sent thus far, and the
|
||||
* number of bytes received (excluding the contents of the buffer).
|
||||
*
|
||||
* IMPORTANT: The guac_rdp_audio_buffer's lock MUST already be held when
|
||||
* invoking this function.
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The audio buffer dictating the format of the given data buffer, as
|
||||
* well as the offset from which the sample should be read.
|
||||
@ -498,26 +237,12 @@ void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer,
|
||||
|
||||
pthread_mutex_lock(&(audio_buffer->lock));
|
||||
|
||||
guac_client_log(audio_buffer->client, GUAC_LOG_TRACE, "Received %i bytes (%i ms) of audio data",
|
||||
length, guac_rdp_audio_buffer_duration(&audio_buffer->in_format, length));
|
||||
|
||||
/* Ignore packet if there is no buffer */
|
||||
if (audio_buffer->packet_buffer_size == 0 || audio_buffer->packet == NULL) {
|
||||
guac_client_log(audio_buffer->client, GUAC_LOG_DEBUG, "Dropped %i "
|
||||
"bytes of received audio data (buffer full or closed).", length);
|
||||
if (audio_buffer->packet_size == 0 || audio_buffer->packet == NULL) {
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Truncate received samples if exceeding size of buffer */
|
||||
int available = audio_buffer->packet_buffer_size - audio_buffer->bytes_written;
|
||||
if (length > available) {
|
||||
guac_client_log(audio_buffer->client, GUAC_LOG_DEBUG, "Truncating %i "
|
||||
"bytes of received audio data to %i bytes (insufficient space "
|
||||
"in buffer).", length, available);
|
||||
length = available;
|
||||
}
|
||||
|
||||
int out_bps = audio_buffer->out_format.bps;
|
||||
|
||||
/* Continuously write packets until no data remains */
|
||||
@ -540,12 +265,24 @@ void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer,
|
||||
audio_buffer->bytes_written += out_bps;
|
||||
audio_buffer->total_bytes_sent += out_bps;
|
||||
|
||||
/* Invoke flush handler if full */
|
||||
if (audio_buffer->bytes_written == audio_buffer->packet_size) {
|
||||
|
||||
/* Only actually invoke if defined */
|
||||
if (audio_buffer->flush_handler)
|
||||
audio_buffer->flush_handler(audio_buffer->packet,
|
||||
audio_buffer->bytes_written, audio_buffer->data);
|
||||
|
||||
/* Reset buffer in all cases */
|
||||
audio_buffer->bytes_written = 0;
|
||||
|
||||
}
|
||||
|
||||
} /* end packet write loop */
|
||||
|
||||
/* Track current position in audio stream */
|
||||
audio_buffer->total_bytes_received += length;
|
||||
|
||||
pthread_cond_broadcast(&(audio_buffer->modified));
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
}
|
||||
@ -554,12 +291,6 @@ void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) {
|
||||
|
||||
pthread_mutex_lock(&(audio_buffer->lock));
|
||||
|
||||
/* Ignore if stream is already closed */
|
||||
if (audio_buffer->stream == NULL) {
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
return;
|
||||
}
|
||||
|
||||
/* The stream is now closed */
|
||||
guac_rdp_audio_buffer_ack(audio_buffer,
|
||||
"CLOSED", GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED);
|
||||
@ -571,7 +302,6 @@ void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) {
|
||||
/* Reset buffer state */
|
||||
audio_buffer->bytes_written = 0;
|
||||
audio_buffer->packet_size = 0;
|
||||
audio_buffer->packet_buffer_size = 0;
|
||||
audio_buffer->flush_handler = NULL;
|
||||
|
||||
/* Reset I/O counters */
|
||||
@ -582,27 +312,13 @@ void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) {
|
||||
free(audio_buffer->packet);
|
||||
audio_buffer->packet = NULL;
|
||||
|
||||
pthread_cond_broadcast(&(audio_buffer->modified));
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer) {
|
||||
|
||||
guac_rdp_audio_buffer_end(audio_buffer);
|
||||
|
||||
/* Signal termination of flush thread */
|
||||
pthread_mutex_lock(&(audio_buffer->lock));
|
||||
audio_buffer->stopping = 1;
|
||||
pthread_cond_broadcast(&(audio_buffer->modified));
|
||||
pthread_mutex_unlock(&(audio_buffer->lock));
|
||||
|
||||
/* Clean up flush thread */
|
||||
pthread_join(audio_buffer->flush_thread, NULL);
|
||||
|
||||
pthread_mutex_destroy(&(audio_buffer->lock));
|
||||
pthread_cond_destroy(&(audio_buffer->modified));
|
||||
free(audio_buffer->packet);
|
||||
free(audio_buffer);
|
||||
|
||||
}
|
||||
|
||||
|
@ -23,38 +23,25 @@
|
||||
#include <guacamole/stream.h>
|
||||
#include <guacamole/user.h>
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
|
||||
/**
|
||||
* The minimum number of milliseconds of audio data that each instance of
|
||||
* guac_rdp_audio_buffer should provide storage for. This buffer space does not
|
||||
* induce additional latency, but is required to compensate for latency and
|
||||
* functions as an upper limit on the amount of latency the buffer will
|
||||
* compensate for.
|
||||
*/
|
||||
#define GUAC_RDP_AUDIO_BUFFER_MIN_DURATION 250
|
||||
|
||||
/**
|
||||
* A buffer of arbitrary audio data. Received audio data can be written to this
|
||||
* buffer, and will automatically be flushed via a given handler once the
|
||||
* internal buffer reaches capacity.
|
||||
*/
|
||||
typedef struct guac_rdp_audio_buffer guac_rdp_audio_buffer;
|
||||
|
||||
/**
|
||||
* Handler which is invoked when a guac_rdp_audio_buffer's internal packet
|
||||
* buffer has reached capacity and must be flushed.
|
||||
*
|
||||
* @param audio_buffer
|
||||
* The guac_rdp_audio_buffer that has reached capacity and needs to be
|
||||
* flushed.
|
||||
* @param buffer
|
||||
* The buffer which needs to be flushed as an audio packet.
|
||||
*
|
||||
* @param length
|
||||
* The number of bytes stored within the buffer. This is guaranteed to be
|
||||
* identical to the packet_size value specified when the audio buffer was
|
||||
* initialized.
|
||||
*
|
||||
* @param data
|
||||
* The arbitrary data pointer provided when the audio buffer was
|
||||
* initialized.
|
||||
*/
|
||||
typedef void guac_rdp_audio_buffer_flush_handler(guac_rdp_audio_buffer* audio_buffer, int length);
|
||||
typedef void guac_rdp_audio_buffer_flush_handler(char* buffer, int length,
|
||||
void* data);
|
||||
|
||||
/**
|
||||
* A description of an arbitrary PCM audio format.
|
||||
@ -79,38 +66,28 @@ typedef struct guac_rdp_audio_format {
|
||||
|
||||
} guac_rdp_audio_format;
|
||||
|
||||
struct guac_rdp_audio_buffer {
|
||||
/**
|
||||
* A buffer of arbitrary audio data. Received audio data can be written to this
|
||||
* buffer, and will automatically be flushed via a given handler once the
|
||||
* internal buffer reaches capacity.
|
||||
*/
|
||||
typedef struct guac_rdp_audio_buffer {
|
||||
|
||||
/**
|
||||
* Lock which is acquired/released to ensure accesses to the audio buffer
|
||||
* are atomic. This lock is also bound to the modified pthread_cond_t,
|
||||
* which should be signalled whenever the audio buffer structure has been
|
||||
* modified.
|
||||
* are atomic.
|
||||
*/
|
||||
pthread_mutex_t lock;
|
||||
|
||||
/**
|
||||
* Condition which is signalled when any part of the audio buffer structure
|
||||
* has been modified.
|
||||
*/
|
||||
pthread_cond_t modified;
|
||||
|
||||
/**
|
||||
* The guac_client instance handling the relevant RDP connection.
|
||||
*/
|
||||
guac_client* client;
|
||||
|
||||
/**
|
||||
* The user from which this audio buffer will receive data. If no user has
|
||||
* yet opened an associated audio stream, or if the audio stream has been
|
||||
* closed, this will be NULL.
|
||||
* yet opened an associated audio stream, this will be NULL.
|
||||
*/
|
||||
guac_user* user;
|
||||
|
||||
/**
|
||||
* The stream from which this audio buffer will receive data. If no user
|
||||
* has yet opened an associated audio stream, or if the audio stream has
|
||||
* been closed, this will be NULL.
|
||||
* has yet opened an associated audio stream, this will be NULL.
|
||||
*/
|
||||
guac_stream* stream;
|
||||
|
||||
@ -134,11 +111,6 @@ struct guac_rdp_audio_buffer {
|
||||
*/
|
||||
int packet_size;
|
||||
|
||||
/**
|
||||
* The total number of bytes available within the packet buffer.
|
||||
*/
|
||||
int packet_buffer_size;
|
||||
|
||||
/**
|
||||
* The number of bytes currently stored within the packet buffer.
|
||||
*/
|
||||
@ -161,20 +133,6 @@ struct guac_rdp_audio_buffer {
|
||||
*/
|
||||
char* packet;
|
||||
|
||||
/**
|
||||
* Thread which flushes the audio buffer at a rate that does not exceed the
|
||||
* the audio sample rate (which might result in dropped samples due to
|
||||
* overflow of the remote audio buffer).
|
||||
*/
|
||||
pthread_t flush_thread;
|
||||
|
||||
/**
|
||||
* The absolute point in time that the next packet of audio data sould be
|
||||
* flushed. Another packet of received data should not be flushed prior to
|
||||
* this time.
|
||||
*/
|
||||
struct timespec next_flush;
|
||||
|
||||
/**
|
||||
* Handler function which will be invoked when a full audio packet is
|
||||
* ready to be flushed to the AUDIO_INPUT channel, if defined. If NULL,
|
||||
@ -182,31 +140,22 @@ struct guac_rdp_audio_buffer {
|
||||
*/
|
||||
guac_rdp_audio_buffer_flush_handler* flush_handler;
|
||||
|
||||
/**
|
||||
* Whether guac_rdp_audio_buffer_free() has been invoked and the audio
|
||||
* buffer is being cleaned up.
|
||||
*/
|
||||
int stopping;
|
||||
|
||||
/**
|
||||
* Arbitrary data assigned by the AUDIO_INPUT plugin implementation.
|
||||
*/
|
||||
void* data;
|
||||
|
||||
};
|
||||
} guac_rdp_audio_buffer;
|
||||
|
||||
/**
|
||||
* Allocates a new audio buffer. The new audio buffer will ignore any received
|
||||
* data until guac_rdp_audio_buffer_begin() is invoked, and will resume
|
||||
* ignoring received data once guac_rdp_audio_buffer_end() is invoked.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client instance handling the relevant RDP connection.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated audio buffer.
|
||||
*/
|
||||
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(guac_client* client);
|
||||
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc();
|
||||
|
||||
/**
|
||||
* Associates the given audio buffer with the underlying audio stream which
|
||||
|
@ -76,9 +76,6 @@ static UINT guac_rdp_cliprdr_send_format_list(CliprdrClientContext* cliprdr) {
|
||||
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
|
||||
assert(clipboard != NULL);
|
||||
|
||||
guac_client* client = clipboard->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
/* We support CP-1252 and UTF-16 text */
|
||||
CLIPRDR_FORMAT_LIST format_list = {
|
||||
.msgType = CB_FORMAT_LIST,
|
||||
@ -89,12 +86,10 @@ static UINT guac_rdp_cliprdr_send_format_list(CliprdrClientContext* cliprdr) {
|
||||
.numFormats = 2
|
||||
};
|
||||
|
||||
guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Sending format list");
|
||||
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Sending "
|
||||
"format list");
|
||||
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
int retval = cliprdr->ClientFormatList(cliprdr, &format_list);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
return retval;
|
||||
return cliprdr->ClientFormatList(cliprdr, &format_list);
|
||||
|
||||
}
|
||||
|
||||
@ -112,17 +107,6 @@ static UINT guac_rdp_cliprdr_send_format_list(CliprdrClientContext* cliprdr) {
|
||||
*/
|
||||
static UINT guac_rdp_cliprdr_send_capabilities(CliprdrClientContext* cliprdr) {
|
||||
|
||||
/* This function is only invoked within FreeRDP-specific handlers for
|
||||
* CLIPRDR, which are not assigned, and thus not callable, until after the
|
||||
* relevant guac_rdp_clipboard structure is allocated and associated with
|
||||
* the CliprdrClientContext */
|
||||
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
|
||||
assert(clipboard != NULL);
|
||||
|
||||
guac_client* client = clipboard->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
/* We support CP-1252 and UTF-16 text */
|
||||
CLIPRDR_GENERAL_CAPABILITY_SET cap_set = {
|
||||
.capabilitySetType = CB_CAPSTYPE_GENERAL, /* CLIPRDR specification requires that this is CB_CAPSTYPE_GENERAL, the only defined set type */
|
||||
.capabilitySetLength = 12, /* The size of the capability set within the PDU - for CB_CAPSTYPE_GENERAL, this is ALWAYS 12 bytes */
|
||||
@ -135,11 +119,7 @@ static UINT guac_rdp_cliprdr_send_capabilities(CliprdrClientContext* cliprdr) {
|
||||
.capabilitySets = (CLIPRDR_CAPABILITY_SET*) &cap_set
|
||||
};
|
||||
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
int retval = cliprdr->ClientCapabilities(cliprdr, &caps);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
|
||||
return retval;
|
||||
return cliprdr->ClientCapabilities(cliprdr, &caps);
|
||||
|
||||
}
|
||||
|
||||
@ -210,9 +190,6 @@ static UINT guac_rdp_cliprdr_send_format_data_request(
|
||||
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
|
||||
assert(clipboard != NULL);
|
||||
|
||||
guac_client* client = clipboard->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
/* Create new data request */
|
||||
CLIPRDR_FORMAT_DATA_REQUEST data_request = {
|
||||
.requestedFormatId = format
|
||||
@ -222,14 +199,11 @@ static UINT guac_rdp_cliprdr_send_format_data_request(
|
||||
* data is received via a Format Data Response PDU */
|
||||
clipboard->requested_format = format;
|
||||
|
||||
guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Sending format data request.");
|
||||
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Sending "
|
||||
"format data request.");
|
||||
|
||||
/* Send request */
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
int retval = cliprdr->ClientFormatDataRequest(cliprdr, &data_request);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
|
||||
return retval;
|
||||
return cliprdr->ClientFormatDataRequest(cliprdr, &data_request);
|
||||
|
||||
}
|
||||
|
||||
@ -291,19 +265,15 @@ static UINT guac_rdp_cliprdr_format_list(CliprdrClientContext* cliprdr,
|
||||
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
|
||||
assert(clipboard != NULL);
|
||||
|
||||
guac_client* client = clipboard->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Received format list.");
|
||||
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Received "
|
||||
"format list.");
|
||||
|
||||
CLIPRDR_FORMAT_LIST_RESPONSE format_list_response = {
|
||||
.msgFlags = CB_RESPONSE_OK
|
||||
};
|
||||
|
||||
/* Report successful processing of format list */
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
cliprdr->ClientFormatListResponse(cliprdr, &format_list_response);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
|
||||
/* Prefer Unicode (in this case, UTF-16) */
|
||||
if (guac_rdp_cliprdr_format_supported(format_list, CF_UNICODETEXT))
|
||||
@ -314,9 +284,9 @@ static UINT guac_rdp_cliprdr_format_list(CliprdrClientContext* cliprdr,
|
||||
return guac_rdp_cliprdr_send_format_data_request(cliprdr, CF_TEXT);
|
||||
|
||||
/* Ignore any unsupported data */
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "Ignoring unsupported clipboard "
|
||||
"data. Only Unicode and text clipboard formats are currently "
|
||||
"supported.");
|
||||
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Ignoring unsupported "
|
||||
"clipboard data. Only Unicode and text clipboard formats are "
|
||||
"currently supported.");
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
|
||||
@ -350,35 +320,32 @@ static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr,
|
||||
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
|
||||
assert(clipboard != NULL);
|
||||
|
||||
guac_client* client = clipboard->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
guac_rdp_settings* settings = rdp_client->settings;
|
||||
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Received "
|
||||
"format data request.");
|
||||
|
||||
guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Received format data request.");
|
||||
|
||||
guac_iconv_write* remote_writer;
|
||||
guac_iconv_write* writer;
|
||||
const char* input = clipboard->clipboard->buffer;
|
||||
char* output = malloc(GUAC_COMMON_CLIPBOARD_MAX_LENGTH);
|
||||
char* output = malloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH);
|
||||
|
||||
/* Map requested clipboard format to a guac_iconv writer */
|
||||
switch (format_data_request->requestedFormatId) {
|
||||
|
||||
case CF_TEXT:
|
||||
remote_writer = settings->clipboard_crlf ? GUAC_WRITE_CP1252_CRLF : GUAC_WRITE_CP1252;
|
||||
writer = GUAC_WRITE_CP1252;
|
||||
break;
|
||||
|
||||
case CF_UNICODETEXT:
|
||||
remote_writer = settings->clipboard_crlf ? GUAC_WRITE_UTF16_CRLF : GUAC_WRITE_UTF16;
|
||||
writer = GUAC_WRITE_UTF16;
|
||||
break;
|
||||
|
||||
/* Warn if clipboard data cannot be sent as intended due to a violation
|
||||
* of the CLIPRDR spec */
|
||||
default:
|
||||
guac_client_log(client, GUAC_LOG_WARNING, "Received clipboard "
|
||||
"data cannot be sent to the RDP server because the RDP "
|
||||
"server has requested a clipboard format which was not "
|
||||
"declared as available. This violates the specification "
|
||||
"for the CLIPRDR channel.");
|
||||
guac_client_log(clipboard->client, GUAC_LOG_WARNING, "Received "
|
||||
"clipboard data cannot be sent to the RDP server because "
|
||||
"the RDP server has requested a clipboard format which "
|
||||
"was not declared as available. This violates the "
|
||||
"specification for the CLIPRDR channel.");
|
||||
free(output);
|
||||
return CHANNEL_RC_OK;
|
||||
|
||||
@ -387,9 +354,8 @@ static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr,
|
||||
/* Send received clipboard data to the RDP server in the format
|
||||
* requested */
|
||||
BYTE* start = (BYTE*) output;
|
||||
guac_iconv_read* local_reader = settings->normalize_clipboard ? GUAC_READ_UTF8_NORMALIZED : GUAC_READ_UTF8;
|
||||
guac_iconv(local_reader, &input, clipboard->clipboard->length,
|
||||
remote_writer, &output, GUAC_COMMON_CLIPBOARD_MAX_LENGTH);
|
||||
guac_iconv(GUAC_READ_UTF8, &input, clipboard->clipboard->length,
|
||||
writer, &output, GUAC_RDP_CLIPBOARD_MAX_LENGTH);
|
||||
|
||||
CLIPRDR_FORMAT_DATA_RESPONSE data_response = {
|
||||
.requestedFormatData = (BYTE*) start,
|
||||
@ -397,12 +363,10 @@ static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr,
|
||||
.msgFlags = CB_RESPONSE_OK
|
||||
};
|
||||
|
||||
guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Sending format data response.");
|
||||
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Sending "
|
||||
"format data response.");
|
||||
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
UINT result = cliprdr->ClientFormatDataResponse(cliprdr, &data_response);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
|
||||
free(start);
|
||||
return result;
|
||||
|
||||
@ -443,15 +407,15 @@ static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr,
|
||||
|
||||
/* Ignore received data if copy has been disabled */
|
||||
if (settings->disable_copy) {
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "Ignoring received clipboard "
|
||||
"data as copying from within the remote desktop has been "
|
||||
"explicitly disabled.");
|
||||
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Ignoring received "
|
||||
"clipboard data as copying from within the remote desktop has "
|
||||
"been explicitly disabled.");
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
char received_data[GUAC_COMMON_CLIPBOARD_MAX_LENGTH];
|
||||
char received_data[GUAC_RDP_CLIPBOARD_MAX_LENGTH];
|
||||
|
||||
guac_iconv_read* remote_reader;
|
||||
guac_iconv_read* reader;
|
||||
const char* input = (char*) format_data_response->requestedFormatData;
|
||||
char* output = received_data;
|
||||
|
||||
@ -460,12 +424,12 @@ static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr,
|
||||
|
||||
/* Non-Unicode (Windows CP-1252) */
|
||||
case CF_TEXT:
|
||||
remote_reader = settings->normalize_clipboard ? GUAC_READ_CP1252_NORMALIZED : GUAC_READ_CP1252;
|
||||
reader = GUAC_READ_CP1252;
|
||||
break;
|
||||
|
||||
/* Unicode (UTF-16) */
|
||||
case CF_UNICODETEXT:
|
||||
remote_reader = settings->normalize_clipboard ? GUAC_READ_UTF16_NORMALIZED : GUAC_READ_UTF16;
|
||||
reader = GUAC_READ_UTF16;
|
||||
break;
|
||||
|
||||
/* If the format ID stored within the guac_rdp_clipboard structure is actually
|
||||
@ -475,20 +439,21 @@ static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr,
|
||||
* here. The values which may be stored within requested_format are
|
||||
* completely within our control. */
|
||||
default:
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "Requested clipboard data "
|
||||
"in unsupported format (0x%X).", clipboard->requested_format);
|
||||
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Requested "
|
||||
"clipboard data in unsupported format (0x%X).",
|
||||
clipboard->requested_format);
|
||||
return CHANNEL_RC_OK;
|
||||
|
||||
}
|
||||
|
||||
/* Convert, store, and forward the clipboard data received from RDP
|
||||
* server */
|
||||
if (guac_iconv(remote_reader, &input, format_data_response->dataLen,
|
||||
if (guac_iconv(reader, &input, format_data_response->dataLen,
|
||||
GUAC_WRITE_UTF8, &output, sizeof(received_data))) {
|
||||
int length = strnlen(received_data, sizeof(received_data));
|
||||
guac_common_clipboard_reset(clipboard->clipboard, "text/plain");
|
||||
guac_common_clipboard_append(clipboard->clipboard, received_data, length);
|
||||
guac_common_clipboard_send(clipboard->clipboard, client);
|
||||
guac_common_clipboard_send(clipboard->clipboard, clipboard->client);
|
||||
}
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
@ -509,12 +474,12 @@ static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr,
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*
|
||||
* @param args
|
||||
* @param e
|
||||
* Event-specific arguments, mainly the name of the channel, and a
|
||||
* reference to the associated plugin loaded for that channel by FreeRDP.
|
||||
*/
|
||||
static void guac_rdp_cliprdr_channel_connected(rdpContext* context,
|
||||
ChannelConnectedEventArgs* args) {
|
||||
ChannelConnectedEventArgs* e) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
@ -526,12 +491,12 @@ static void guac_rdp_cliprdr_channel_connected(rdpContext* context,
|
||||
assert(clipboard != NULL);
|
||||
|
||||
/* Ignore connection event if it's not for the CLIPRDR channel */
|
||||
if (strcmp(args->name, CLIPRDR_SVC_CHANNEL_NAME) != 0)
|
||||
if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) != 0)
|
||||
return;
|
||||
|
||||
/* The structure pointed to by pInterface is guaranteed to be a
|
||||
* CliprdrClientContext if the channel is CLIPRDR */
|
||||
CliprdrClientContext* cliprdr = (CliprdrClientContext*) args->pInterface;
|
||||
CliprdrClientContext* cliprdr = (CliprdrClientContext*) e->pInterface;
|
||||
|
||||
/* Associate FreeRDP CLIPRDR context and its Guacamole counterpart with
|
||||
* eachother */
|
||||
@ -548,54 +513,12 @@ static void guac_rdp_cliprdr_channel_connected(rdpContext* context,
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback which disassociates Guacamole from the CliprdrClientContext
|
||||
* instance that was originally allocated by FreeRDP and is about to be
|
||||
* deallocated.
|
||||
*
|
||||
* This function is called whenever a channel disconnects via the PubSub event
|
||||
* system within FreeRDP, but only has any effect if the disconnected channel
|
||||
* is the CLIPRDR channel. This specific callback is registered with the PubSub
|
||||
* system of the relevant rdpContext when guac_rdp_clipboard_load_plugin() is
|
||||
* called.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*
|
||||
* @param args
|
||||
* Event-specific arguments, mainly the name of the channel, and a
|
||||
* reference to the associated plugin loaded for that channel by FreeRDP.
|
||||
*/
|
||||
static void guac_rdp_cliprdr_channel_disconnected(rdpContext* context,
|
||||
ChannelDisconnectedEventArgs* args) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
guac_rdp_clipboard* clipboard = rdp_client->clipboard;
|
||||
|
||||
/* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not
|
||||
* callable, until after the relevant guac_rdp_clipboard structure is
|
||||
* allocated and associated with the guac_rdp_client */
|
||||
assert(clipboard != NULL);
|
||||
|
||||
/* Ignore disconnection event if it's not for the CLIPRDR channel */
|
||||
if (strcmp(args->name, CLIPRDR_SVC_CHANNEL_NAME) != 0)
|
||||
return;
|
||||
|
||||
/* Channel is no longer connected */
|
||||
clipboard->cliprdr = NULL;
|
||||
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "CLIPRDR (clipboard redirection) "
|
||||
"channel disconnected.");
|
||||
|
||||
}
|
||||
|
||||
guac_rdp_clipboard* guac_rdp_clipboard_alloc(guac_client* client) {
|
||||
|
||||
/* Allocate clipboard and underlying storage */
|
||||
guac_rdp_clipboard* clipboard = calloc(1, sizeof(guac_rdp_clipboard));
|
||||
clipboard->client = client;
|
||||
clipboard->clipboard = guac_common_clipboard_alloc();
|
||||
clipboard->clipboard = guac_common_clipboard_alloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH);
|
||||
clipboard->requested_format = CF_TEXT;
|
||||
|
||||
return clipboard;
|
||||
@ -619,10 +542,6 @@ void guac_rdp_clipboard_load_plugin(guac_rdp_clipboard* clipboard,
|
||||
PubSub_SubscribeChannelConnected(context->pubSub,
|
||||
(pChannelConnectedEventHandler) guac_rdp_cliprdr_channel_connected);
|
||||
|
||||
/* Clean up any RDP-specific resources when channel is disconnected */
|
||||
PubSub_SubscribeChannelDisconnected(context->pubSub,
|
||||
(pChannelDisconnectedEventHandler) guac_rdp_cliprdr_channel_disconnected);
|
||||
|
||||
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Support for CLIPRDR "
|
||||
"(clipboard redirection) registered. Awaiting channel "
|
||||
"connection.");
|
||||
|
@ -89,16 +89,12 @@ void guac_rdp_common_svc_write(guac_rdp_common_svc* svc,
|
||||
return;
|
||||
}
|
||||
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) svc->client->data;
|
||||
|
||||
/* NOTE: The wStream sent via pVirtualChannelWriteEx will automatically be
|
||||
* freed later with a call to Stream_Free() when handling the
|
||||
* corresponding write cancel/completion event. */
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
svc->_entry_points.pVirtualChannelWriteEx(svc->_init_handle,
|
||||
svc->_open_handle, Stream_Buffer(output_stream),
|
||||
Stream_GetPosition(output_stream), output_stream);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ struct guac_rdp_common_svc {
|
||||
guac_rdp_common_svc_receive_handler* _receive_handler;
|
||||
|
||||
/**
|
||||
* Handler which is involved when the SVC has been disconnected and is
|
||||
* Handler which is invokved when the SVC has been disconnected and is
|
||||
* about to be freed.
|
||||
*/
|
||||
guac_rdp_common_svc_terminate_handler* _terminate_handler;
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
#include "channels/disp.h"
|
||||
#include "plugins/channels.h"
|
||||
#include "fs.h"
|
||||
#include "rdp.h"
|
||||
#include "settings.h"
|
||||
|
||||
@ -32,10 +31,9 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
guac_rdp_disp* guac_rdp_disp_alloc(guac_client* client) {
|
||||
guac_rdp_disp* guac_rdp_disp_alloc() {
|
||||
|
||||
guac_rdp_disp* disp = malloc(sizeof(guac_rdp_disp));
|
||||
disp->client = client;
|
||||
|
||||
/* Not yet connected */
|
||||
disp->disp = NULL;
|
||||
@ -68,19 +66,19 @@ void guac_rdp_disp_free(guac_rdp_disp* disp) {
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*
|
||||
* @param args
|
||||
* @param e
|
||||
* Event-specific arguments, mainly the name of the channel, and a
|
||||
* reference to the associated plugin loaded for that channel by FreeRDP.
|
||||
*/
|
||||
static void guac_rdp_disp_channel_connected(rdpContext* context,
|
||||
ChannelConnectedEventArgs* args) {
|
||||
ChannelConnectedEventArgs* e) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
guac_rdp_disp* guac_disp = rdp_client->disp;
|
||||
|
||||
/* Ignore connection event if it's not for the Display Update channel */
|
||||
if (strcmp(args->name, DISP_DVC_CHANNEL_NAME) != 0)
|
||||
if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) != 0)
|
||||
return;
|
||||
|
||||
/* Init module with current display size */
|
||||
@ -89,7 +87,7 @@ static void guac_rdp_disp_channel_connected(rdpContext* context,
|
||||
guac_rdp_get_height(context->instance));
|
||||
|
||||
/* Store reference to the display update plugin once it's connected */
|
||||
DispClientContext* disp = (DispClientContext*) args->pInterface;
|
||||
DispClientContext* disp = (DispClientContext*) e->pInterface;
|
||||
guac_disp->disp = disp;
|
||||
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "Display update channel "
|
||||
@ -97,52 +95,12 @@ static void guac_rdp_disp_channel_connected(rdpContext* context,
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback which disassociates Guacamole from the DispClientContext instance
|
||||
* that was originally allocated by FreeRDP and is about to be deallocated.
|
||||
*
|
||||
* This function is called whenever a channel disconnects via the PubSub event
|
||||
* system within FreeRDP, but only has any effect if the disconnected channel
|
||||
* is the Display Update channel. This specific callback is registered with the
|
||||
* PubSub system of the relevant rdpContext when guac_rdp_disp_load_plugin() is
|
||||
* called.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*
|
||||
* @param args
|
||||
* Event-specific arguments, mainly the name of the channel, and a
|
||||
* reference to the associated plugin loaded for that channel by FreeRDP.
|
||||
*/
|
||||
static void guac_rdp_disp_channel_disconnected(rdpContext* context,
|
||||
ChannelDisconnectedEventArgs* args) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
guac_rdp_disp* guac_disp = rdp_client->disp;
|
||||
|
||||
/* Ignore disconnection event if it's not for the Display Update channel */
|
||||
if (strcmp(args->name, DISP_DVC_CHANNEL_NAME) != 0)
|
||||
return;
|
||||
|
||||
/* Channel is no longer connected */
|
||||
guac_disp->disp = NULL;
|
||||
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "Display update channel "
|
||||
"disconnected.");
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_disp_load_plugin(rdpContext* context) {
|
||||
|
||||
/* Subscribe to and handle channel connected events */
|
||||
PubSub_SubscribeChannelConnected(context->pubSub,
|
||||
(pChannelConnectedEventHandler) guac_rdp_disp_channel_connected);
|
||||
|
||||
/* Subscribe to and handle channel disconnected events */
|
||||
PubSub_SubscribeChannelDisconnected(context->pubSub,
|
||||
(pChannelDisconnectedEventHandler) guac_rdp_disp_channel_disconnected);
|
||||
|
||||
/* Add "disp" channel */
|
||||
guac_freerdp_dynamic_channel_collection_add(context->settings, "disp", NULL);
|
||||
|
||||
@ -262,34 +220,13 @@ void guac_rdp_disp_update_size(guac_rdp_disp* disp,
|
||||
}};
|
||||
|
||||
/* Send display update notification if display channel is connected */
|
||||
if (disp->disp != NULL) {
|
||||
|
||||
guac_client* client = disp->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
if (disp->disp != NULL)
|
||||
disp->disp->SendMonitorLayout(disp->disp, 1, monitors);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_disp_reconnect_needed(guac_rdp_disp* disp) {
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) disp->client->data;
|
||||
|
||||
/* Do not reconnect if files are open. */
|
||||
if (rdp_client->filesystem != NULL
|
||||
&& rdp_client->filesystem->open_files > 0)
|
||||
return 0;
|
||||
|
||||
/* Do not reconnect if an active print job is present */
|
||||
if (rdp_client->active_job != NULL)
|
||||
return 0;
|
||||
|
||||
|
||||
return disp->reconnect_needed;
|
||||
}
|
||||
|
||||
|
@ -48,11 +48,6 @@
|
||||
*/
|
||||
typedef struct guac_rdp_disp {
|
||||
|
||||
/**
|
||||
* The guac_client instance handling the relevant RDP connection.
|
||||
*/
|
||||
guac_client* client;
|
||||
|
||||
/**
|
||||
* Display control interface.
|
||||
*/
|
||||
@ -86,13 +81,10 @@ typedef struct guac_rdp_disp {
|
||||
* Allocates a new display update module, which will ultimately control the
|
||||
* display update channel once connected.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client instance handling the relevant RDP connection.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated display update module.
|
||||
*/
|
||||
guac_rdp_disp* guac_rdp_disp_alloc(guac_client* client);
|
||||
guac_rdp_disp* guac_rdp_disp_alloc();
|
||||
|
||||
/**
|
||||
* Frees the resources associated with support for the RDP Display Update
|
||||
@ -109,7 +101,7 @@ void guac_rdp_disp_free(guac_rdp_disp* disp);
|
||||
/**
|
||||
* Adds FreeRDP's "disp" plugin to the list of dynamic virtual channel plugins
|
||||
* to be loaded by FreeRDP's "drdynvc" plugin. The context of the plugin will
|
||||
* automatically be associated with the guac_rdp_disp instance pointed to by the
|
||||
* automatically be assicated with the guac_rdp_disp instance pointed to by the
|
||||
* current guac_rdp_client. The plugin will only be loaded once the "drdynvc"
|
||||
* plugin is loaded. The "disp" plugin ultimately adds support for the Display
|
||||
* Update channel.
|
||||
|
@ -86,10 +86,7 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
|
||||
};
|
||||
|
||||
/* Send client handshake response */
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
status = rail->ClientHandshake(rail, &handshake);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
|
||||
if (status != CHANNEL_RC_OK)
|
||||
return status;
|
||||
|
||||
@ -98,10 +95,7 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
|
||||
};
|
||||
|
||||
/* Send client status */
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
status = rail->ClientInformation(rail, &client_status);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
|
||||
if (status != CHANNEL_RC_OK)
|
||||
return status;
|
||||
|
||||
@ -145,10 +139,7 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
|
||||
};
|
||||
|
||||
/* Send client system parameters */
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
status = rail->ClientSystemParam(rail, &sysparam);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
|
||||
if (status != CHANNEL_RC_OK)
|
||||
return status;
|
||||
|
||||
@ -160,11 +151,7 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
|
||||
};
|
||||
|
||||
/* Execute desired RemoteApp command */
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
status = rail->ClientExecute(rail, &exec);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
|
||||
return status;
|
||||
return rail->ClientExecute(rail, &exec);
|
||||
|
||||
}
|
||||
|
||||
@ -230,22 +217,22 @@ static UINT guac_rdp_rail_handshake_ex(RailClientContext* rail,
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*
|
||||
* @param args
|
||||
* @param e
|
||||
* Event-specific arguments, mainly the name of the channel, and a
|
||||
* reference to the associated plugin loaded for that channel by FreeRDP.
|
||||
*/
|
||||
static void guac_rdp_rail_channel_connected(rdpContext* context,
|
||||
ChannelConnectedEventArgs* args) {
|
||||
ChannelConnectedEventArgs* e) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
|
||||
/* Ignore connection event if it's not for the RAIL channel */
|
||||
if (strcmp(args->name, RAIL_SVC_CHANNEL_NAME) != 0)
|
||||
if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) != 0)
|
||||
return;
|
||||
|
||||
/* The structure pointed to by pInterface is guaranteed to be a
|
||||
* RailClientContext if the channel is RAIL */
|
||||
RailClientContext* rail = (RailClientContext*) args->pInterface;
|
||||
RailClientContext* rail = (RailClientContext*) e->pInterface;
|
||||
|
||||
/* Init FreeRDP RAIL context, ensuring the guac_client can be accessed from
|
||||
* within any RAIL-specific callbacks */
|
||||
|
@ -1,224 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "channels/rdpei.h"
|
||||
#include "common/surface.h"
|
||||
#include "plugins/channels.h"
|
||||
#include "rdp.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <freerdp/client/rdpei.h>
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/event.h>
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
guac_rdp_rdpei* guac_rdp_rdpei_alloc(guac_client* client) {
|
||||
|
||||
guac_rdp_rdpei* rdpei = malloc(sizeof(guac_rdp_rdpei));
|
||||
rdpei->client = client;
|
||||
|
||||
/* Not yet connected */
|
||||
rdpei->rdpei = NULL;
|
||||
|
||||
/* No active touches */
|
||||
for (int i = 0; i < GUAC_RDP_RDPEI_MAX_TOUCHES; i++)
|
||||
rdpei->touch[i].active = 0;
|
||||
|
||||
return rdpei;
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_rdpei_free(guac_rdp_rdpei* rdpei) {
|
||||
free(rdpei);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback which associates handlers specific to Guacamole with the
|
||||
* RdpeiClientContext instance allocated by FreeRDP to deal with received
|
||||
* RDPEI (multi-touch input) messages.
|
||||
*
|
||||
* This function is called whenever a channel connects via the PubSub event
|
||||
* system within FreeRDP, but only has any effect if the connected channel is
|
||||
* the RDPEI channel. This specific callback is registered with the
|
||||
* PubSub system of the relevant rdpContext when guac_rdp_rdpei_load_plugin() is
|
||||
* called.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*
|
||||
* @param args
|
||||
* Event-specific arguments, mainly the name of the channel, and a
|
||||
* reference to the associated plugin loaded for that channel by FreeRDP.
|
||||
*/
|
||||
static void guac_rdp_rdpei_channel_connected(rdpContext* context,
|
||||
ChannelConnectedEventArgs* args) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
guac_rdp_rdpei* guac_rdpei = rdp_client->rdpei;
|
||||
|
||||
/* Ignore connection event if it's not for the RDPEI channel */
|
||||
if (strcmp(args->name, RDPEI_DVC_CHANNEL_NAME) != 0)
|
||||
return;
|
||||
|
||||
/* Store reference to the RDPEI plugin once it's connected */
|
||||
RdpeiClientContext* rdpei = (RdpeiClientContext*) args->pInterface;
|
||||
guac_rdpei->rdpei = rdpei;
|
||||
|
||||
/* Declare level of multi-touch support */
|
||||
guac_common_surface_set_multitouch(rdp_client->display->default_surface,
|
||||
GUAC_RDP_RDPEI_MAX_TOUCHES);
|
||||
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "RDPEI channel will be used for "
|
||||
"multi-touch support.");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback which disassociates Guacamole from the RdpeiClientContext instance
|
||||
* that was originally allocated by FreeRDP and is about to be deallocated.
|
||||
*
|
||||
* This function is called whenever a channel disconnects via the PubSub event
|
||||
* system within FreeRDP, but only has any effect if the disconnected channel
|
||||
* is the RDPEI channel. This specific callback is registered with the PubSub
|
||||
* system of the relevant rdpContext when guac_rdp_rdpei_load_plugin() is
|
||||
* called.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*
|
||||
* @param args
|
||||
* Event-specific arguments, mainly the name of the channel, and a
|
||||
* reference to the associated plugin loaded for that channel by FreeRDP.
|
||||
*/
|
||||
static void guac_rdp_rdpei_channel_disconnected(rdpContext* context,
|
||||
ChannelDisconnectedEventArgs* args) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
guac_rdp_rdpei* guac_rdpei = rdp_client->rdpei;
|
||||
|
||||
/* Ignore disconnection event if it's not for the RDPEI channel */
|
||||
if (strcmp(args->name, RDPEI_DVC_CHANNEL_NAME) != 0)
|
||||
return;
|
||||
|
||||
/* Channel is no longer connected */
|
||||
guac_rdpei->rdpei = NULL;
|
||||
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "RDPDI channel disconnected.");
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_rdpei_load_plugin(rdpContext* context) {
|
||||
|
||||
/* Subscribe to and handle channel connected events */
|
||||
PubSub_SubscribeChannelConnected(context->pubSub,
|
||||
(pChannelConnectedEventHandler) guac_rdp_rdpei_channel_connected);
|
||||
|
||||
/* Subscribe to and handle channel disconnected events */
|
||||
PubSub_SubscribeChannelDisconnected(context->pubSub,
|
||||
(pChannelDisconnectedEventHandler) guac_rdp_rdpei_channel_disconnected);
|
||||
|
||||
/* Add "rdpei" channel */
|
||||
guac_freerdp_dynamic_channel_collection_add(context->settings, "rdpei", NULL);
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_rdpei_touch_update(guac_rdp_rdpei* rdpei, int id, int x, int y,
|
||||
double force) {
|
||||
|
||||
guac_client* client = rdpei->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
int contact_id; /* Ignored */
|
||||
|
||||
/* Track touches only if channel is connected */
|
||||
RdpeiClientContext* context = rdpei->rdpei;
|
||||
if (context == NULL)
|
||||
return 1;
|
||||
|
||||
/* Locate active touch having provided ID */
|
||||
guac_rdp_rdpei_touch* touch = NULL;
|
||||
for (int i = 0; i < GUAC_RDP_RDPEI_MAX_TOUCHES; i++) {
|
||||
if (rdpei->touch[i].active && rdpei->touch[i].id == id) {
|
||||
touch = &rdpei->touch[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If no such touch exists, add it */
|
||||
if (touch == NULL) {
|
||||
for (int i = 0; i < GUAC_RDP_RDPEI_MAX_TOUCHES; i++) {
|
||||
if (!rdpei->touch[i].active) {
|
||||
touch = &rdpei->touch[i];
|
||||
touch->id = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If the touch couldn't be added, we're already at maximum touch capacity.
|
||||
* Drop the event. */
|
||||
if (touch == NULL)
|
||||
return 1;
|
||||
|
||||
/* Signal the end of an established touch if touch force has become zero
|
||||
* (this should be a safe comparison, as zero has an exact representation
|
||||
* in floating point, and the client side will use an exact value to
|
||||
* represent the absence of a touch) */
|
||||
if (force == 0.0) {
|
||||
|
||||
/* Ignore release of touches that we aren't tracking */
|
||||
if (!touch->active)
|
||||
return 1;
|
||||
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
context->TouchEnd(context, id, x, y, &contact_id);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
|
||||
touch->active = 0;
|
||||
|
||||
}
|
||||
|
||||
/* Signal the start of a touch if this is the first we've seen it */
|
||||
else if (!touch->active) {
|
||||
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
context->TouchBegin(context, id, x, y, &contact_id);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
|
||||
touch->active = 1;
|
||||
|
||||
}
|
||||
|
||||
/* Established touches need only be updated */
|
||||
else {
|
||||
pthread_mutex_lock(&(rdp_client->message_lock));
|
||||
context->TouchUpdate(context, id, x, y, &contact_id);
|
||||
pthread_mutex_unlock(&(rdp_client->message_lock));
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_RDP_CHANNELS_RDPEI_H
|
||||
#define GUAC_RDP_CHANNELS_RDPEI_H
|
||||
|
||||
#include "settings.h"
|
||||
|
||||
#include <freerdp/client/rdpei.h>
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
/**
|
||||
* The maximum number of simultaneously-tracked touches.
|
||||
*/
|
||||
#define GUAC_RDP_RDPEI_MAX_TOUCHES 10
|
||||
|
||||
/**
|
||||
* A single, tracked touch contact.
|
||||
*/
|
||||
typedef struct guac_rdp_rdpei_touch {
|
||||
|
||||
/**
|
||||
* Whether this touch is active (1) or inactive (0). An active touch is
|
||||
* being tracked, while an inactive touch is simple an empty space awaiting
|
||||
* use by some future touch event.
|
||||
*/
|
||||
int active;
|
||||
|
||||
/**
|
||||
* The unique ID representing this touch contact.
|
||||
*/
|
||||
int id;
|
||||
|
||||
/**
|
||||
* The X-coordinate of this touch, in pixels.
|
||||
*/
|
||||
int x;
|
||||
|
||||
/**
|
||||
* The Y-coordinate of this touch, in pixels.
|
||||
*/
|
||||
int y;
|
||||
|
||||
} guac_rdp_rdpei_touch;
|
||||
|
||||
/**
|
||||
* Multi-touch input module.
|
||||
*/
|
||||
typedef struct guac_rdp_rdpei {
|
||||
|
||||
/**
|
||||
* The guac_client instance handling the relevant RDP connection.
|
||||
*/
|
||||
guac_client* client;
|
||||
|
||||
/**
|
||||
* RDPEI control interface.
|
||||
*/
|
||||
RdpeiClientContext* rdpei;
|
||||
|
||||
/**
|
||||
* All currently-tracked touches.
|
||||
*/
|
||||
guac_rdp_rdpei_touch touch[GUAC_RDP_RDPEI_MAX_TOUCHES];
|
||||
|
||||
} guac_rdp_rdpei;
|
||||
|
||||
/**
|
||||
* Allocates a new RDPEI module, which will ultimately control the RDPEI
|
||||
* channel once connected. The RDPEI channel allows multi-touch input
|
||||
* events to be sent to the RDP server.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client instance handling the relevant RDP connection.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated RDPEI module.
|
||||
*/
|
||||
guac_rdp_rdpei* guac_rdp_rdpei_alloc(guac_client* client);
|
||||
|
||||
/**
|
||||
* Frees the resources associated with support for the RDPEI channel. Only
|
||||
* resources specific to Guacamole are freed. Resources specific to FreeRDP's
|
||||
* handling of the RDPEI channel will be freed by FreeRDP. If no resources are
|
||||
* currently allocated for RDPEI, this function has no effect.
|
||||
*
|
||||
* @param rdpei
|
||||
* The RDPEI module to free.
|
||||
*/
|
||||
void guac_rdp_rdpei_free(guac_rdp_rdpei* rdpei);
|
||||
|
||||
/**
|
||||
* Adds FreeRDP's "rdpei" plugin to the list of dynamic virtual channel plugins
|
||||
* to be loaded by FreeRDP's "drdynvc" plugin. The context of the plugin will
|
||||
* automatically be associated with the guac_rdp_rdpei instance pointed to by the
|
||||
* current guac_rdp_client. The plugin will only be loaded once the "drdynvc"
|
||||
* plugin is loaded. The "rdpei" plugin ultimately adds support for multi-touch
|
||||
* input via the RDPEI channel.
|
||||
*
|
||||
* If failures occur, messages noting the specifics of those failures will be
|
||||
* logged, and the RDP side of multi-touch support will not be functional.
|
||||
*
|
||||
* This MUST be called within the PreConnect callback of the freerdp instance
|
||||
* for multi-touch support to be loaded.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*/
|
||||
void guac_rdp_rdpei_load_plugin(rdpContext* context);
|
||||
|
||||
/**
|
||||
* Reports to the RDP server that the status of a single touch contact has
|
||||
* changed. Depending on the amount of force associated with the touch and
|
||||
* whether the touch has been encountered before, this will result a new touch
|
||||
* contact, updates to an existing contact, or removal of an existing contact.
|
||||
* If the RDPEI channel has not yet been connected, touches will be ignored and
|
||||
* dropped until it is connected.
|
||||
*
|
||||
* @param rdpei
|
||||
* The RDPEI module associated with the RDP session.
|
||||
*
|
||||
* @param id
|
||||
* An arbitrary integer ID unique to the touch being updated.
|
||||
*
|
||||
* @param x
|
||||
* The X-coordinate of the touch, in pixels.
|
||||
*
|
||||
* @param y
|
||||
* The Y-coordinate of the touch, in pixels.
|
||||
*
|
||||
* @param force
|
||||
* The amount of force currently being exerted on the device by the touch
|
||||
* contact in question, where 1.0 is the maximum amount of force
|
||||
* representable and 0.0 indicates the contact has been lifted.
|
||||
*
|
||||
* @return
|
||||
* Zero if the touch event was successfully processed, non-zero if the
|
||||
* touch event had to be dropped.
|
||||
*/
|
||||
int guac_rdp_rdpei_touch_update(guac_rdp_rdpei* rdpei, int id, int x, int y,
|
||||
double force);
|
||||
|
||||
#endif
|
||||
|
@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "channels/rdpgfx.h"
|
||||
#include "plugins/channels.h"
|
||||
#include "rdp.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <freerdp/client/rdpgfx.h>
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/gdi/gfx.h>
|
||||
#include <freerdp/event.h>
|
||||
#include <guacamole/client.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Callback which associates handlers specific to Guacamole with the
|
||||
* RdpgfxClientContext instance allocated by FreeRDP to deal with received
|
||||
* RDPGFX (Graphics Pipeline) messages.
|
||||
*
|
||||
* This function is called whenever a channel connects via the PubSub event
|
||||
* system within FreeRDP, but only has any effect if the connected channel is
|
||||
* the RDPGFX channel. This specific callback is registered with the
|
||||
* PubSub system of the relevant rdpContext when guac_rdp_rdpgfx_load_plugin() is
|
||||
* called.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*
|
||||
* @param args
|
||||
* Event-specific arguments, mainly the name of the channel, and a
|
||||
* reference to the associated plugin loaded for that channel by FreeRDP.
|
||||
*/
|
||||
static void guac_rdp_rdpgfx_channel_connected(rdpContext* context,
|
||||
ChannelConnectedEventArgs* args) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
|
||||
/* Ignore connection event if it's not for the RDPGFX channel */
|
||||
if (strcmp(args->name, RDPGFX_DVC_CHANNEL_NAME) != 0)
|
||||
return;
|
||||
|
||||
/* Init GDI-backed support for the Graphics Pipeline */
|
||||
RdpgfxClientContext* rdpgfx = (RdpgfxClientContext*) args->pInterface;
|
||||
rdpGdi* gdi = context->gdi;
|
||||
|
||||
if (!gdi_graphics_pipeline_init(gdi, rdpgfx))
|
||||
guac_client_log(client, GUAC_LOG_WARNING, "Rendering backend for RDPGFX "
|
||||
"channel could not be loaded. Graphics may not render at all!");
|
||||
else
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "RDPGFX channel will be used for "
|
||||
"the RDP Graphics Pipeline Extension.");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback which handles any RDPGFX cleanup specific to Guacamole.
|
||||
*
|
||||
* This function is called whenever a channel disconnects via the PubSub event
|
||||
* system within FreeRDP, but only has any effect if the disconnected channel
|
||||
* is the RDPGFX channel. This specific callback is registered with the PubSub
|
||||
* system of the relevant rdpContext when guac_rdp_rdpgfx_load_plugin() is
|
||||
* called.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*
|
||||
* @param args
|
||||
* Event-specific arguments, mainly the name of the channel, and a
|
||||
* reference to the associated plugin loaded for that channel by FreeRDP.
|
||||
*/
|
||||
static void guac_rdp_rdpgfx_channel_disconnected(rdpContext* context,
|
||||
ChannelDisconnectedEventArgs* args) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
|
||||
/* Ignore disconnection event if it's not for the RDPGFX channel */
|
||||
if (strcmp(args->name, RDPGFX_DVC_CHANNEL_NAME) != 0)
|
||||
return;
|
||||
|
||||
/* Un-init GDI-backed support for the Graphics Pipeline */
|
||||
RdpgfxClientContext* rdpgfx = (RdpgfxClientContext*) args->pInterface;
|
||||
rdpGdi* gdi = context->gdi;
|
||||
gdi_graphics_pipeline_uninit(gdi, rdpgfx);
|
||||
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "RDPGFX channel support unloaded.");
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_rdpgfx_load_plugin(rdpContext* context) {
|
||||
|
||||
/* Subscribe to and handle channel connected events */
|
||||
PubSub_SubscribeChannelConnected(context->pubSub,
|
||||
(pChannelConnectedEventHandler) guac_rdp_rdpgfx_channel_connected);
|
||||
|
||||
/* Subscribe to and handle channel disconnected events */
|
||||
PubSub_SubscribeChannelDisconnected(context->pubSub,
|
||||
(pChannelDisconnectedEventHandler) guac_rdp_rdpgfx_channel_disconnected);
|
||||
|
||||
/* Add "rdpgfx" channel */
|
||||
guac_freerdp_dynamic_channel_collection_add(context->settings, "rdpgfx", NULL);
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user