From a8fc6814b656221a6d640b4675e990c3e74a2403 Mon Sep 17 00:00:00 2001 From: Frediano Ziglio Date: Fri, 20 Oct 2017 11:28:15 +0100 Subject: Initial public commit --- .gitignore | 31 ++ AUTHORS | 0 COPYING | 1 + ChangeLog | 0 LICENSE | 9 + Makefile.am | 23 ++ NEWS | 0 README | 52 +++ configure.ac | 109 +++++ data/90-spice-guest-streaming.rules | 2 + data/spice-streaming.desktop.in | 9 + include/spice-streaming-agent/Makefile.am | 7 + include/spice-streaming-agent/frame-capture.hpp | 62 +++ include/spice-streaming-agent/plugin.hpp | 152 +++++++ m4/ac_define_dir.m4 | 34 ++ m4/manywarnings.m4 | 276 +++++++++++++ m4/spice-compile-warnings.m4 | 183 ++++++++ m4/virt-linker-no-indirect.m4 | 32 ++ m4/virt-linker-relro.m4 | 35 ++ m4/warnings.m4 | 79 ++++ spice-streaming-agent.pc.in | 12 + spice-streaming-agent.spec.in | 64 +++ src/.gitignore | 2 + src/Makefile.am | 59 +++ src/concrete-agent.cpp | 122 ++++++ src/concrete-agent.hpp | 46 +++ src/hexdump.c | 33 ++ src/hexdump.h | 21 + src/jpeg.cpp | 91 ++++ src/jpeg.hpp | 14 + src/mjpeg-fallback.cpp | 223 ++++++++++ src/spice-streaming-agent.cpp | 529 ++++++++++++++++++++++++ src/static-plugin.cpp | 23 ++ src/static-plugin.hpp | 35 ++ src/unittests/.gitignore | 1 + src/unittests/Makefile.am | 35 ++ src/unittests/hexdump.sh | 15 + src/unittests/hexdump1.in | 0 src/unittests/hexdump1.out | 1 + src/unittests/hexdump2.in | 1 + src/unittests/hexdump2.out | 2 + src/unittests/hexdump3.in | Bin 0 -> 123 bytes src/unittests/hexdump3.out | 9 + src/unittests/test-hexdump.c | 20 + 44 files changed, 2454 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 LICENSE create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 configure.ac create mode 100644 data/90-spice-guest-streaming.rules create mode 100644 data/spice-streaming.desktop.in create mode 100644 include/spice-streaming-agent/Makefile.am create mode 100644 include/spice-streaming-agent/frame-capture.hpp create mode 100644 include/spice-streaming-agent/plugin.hpp create mode 100644 m4/ac_define_dir.m4 create mode 100644 m4/manywarnings.m4 create mode 100644 m4/spice-compile-warnings.m4 create mode 100644 m4/virt-linker-no-indirect.m4 create mode 100644 m4/virt-linker-relro.m4 create mode 100644 m4/warnings.m4 create mode 100644 spice-streaming-agent.pc.in create mode 100644 spice-streaming-agent.spec.in create mode 100644 src/.gitignore create mode 100644 src/Makefile.am create mode 100644 src/concrete-agent.cpp create mode 100644 src/concrete-agent.hpp create mode 100644 src/hexdump.c create mode 100644 src/hexdump.h create mode 100644 src/jpeg.cpp create mode 100644 src/jpeg.hpp create mode 100644 src/mjpeg-fallback.cpp create mode 100644 src/spice-streaming-agent.cpp create mode 100644 src/static-plugin.cpp create mode 100644 src/static-plugin.hpp create mode 100644 src/unittests/.gitignore create mode 100644 src/unittests/Makefile.am create mode 100755 src/unittests/hexdump.sh create mode 100644 src/unittests/hexdump1.in create mode 100644 src/unittests/hexdump1.out create mode 100644 src/unittests/hexdump2.in create mode 100644 src/unittests/hexdump2.out create mode 100644 src/unittests/hexdump3.in create mode 100644 src/unittests/hexdump3.out create mode 100644 src/unittests/test-hexdump.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..601cc9f --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +*~ +*.o +*.la +*.lo +*.tar.bz2 +*.tar.gz +.*.sw? +.deps +.dirstamp +.libs +Makefile +Makefile.in +/aclocal.m4 +/autom4te.cache +/build-aux/ +/config.h +/config.h.in +/config.log +/config.status +/configure +/INSTALL +/stamp-h1 +/spice-streaming-agent.spec +/spice-streaming-agent.pc +/data/spice-streaming.desktop +/libtool +/m4/libtool.m4 +/m4/ltoptions.m4 +/m4/ltsugar.m4 +/m4/ltversion.m4 +/m4/lt~obsolete.m4 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..e69de29 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..f086a5f --- /dev/null +++ b/COPYING @@ -0,0 +1 @@ +TO BE DEFINED diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7e61e7c --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +A Proprietary Red Hat License + +Copyright Red Hat Inc. + +This software follows a GridSDK sample program by NVidia. +Please see LICENSE_NVIDIA for GridSDK. + +Exact License is TBD. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..9224646 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,23 @@ +NULL = +SUBDIRS = \ + include/spice-streaming-agent \ + src \ + $(NULL) + +# this will start the program for each user session +xdgautostartdir = $(sysconfdir)/xdg/autostart +xdgautostart_DATA = $(top_srcdir)/data/spice-streaming.desktop + +# this will start the program for the login session +gdmautostartdir = $(datadir)/gdm/greeter/autostart +gdmautostart_DATA = $(top_srcdir)/data/spice-streaming.desktop + +pkgconfigdir = $(datadir)/pkgconfig +pkgconfig_DATA = spice-streaming-agent.pc + +EXTRA_DIST = \ + spice-streaming-agent.spec \ + spice-streaming-agent.pc \ + LICENSE \ + data/spice-streaming.desktop \ + $(NULL) diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..d07c014 --- /dev/null +++ b/README @@ -0,0 +1,52 @@ +Introduction +============ + +The SPICE Streaming Agent is a guest-side daemon which captures the +guest X.Org output, encodes it to H.264 using hardware encoding (NVIDIA-only at +the moment), and forwards the resulting stream to the host to be sent through +SPICE. + + +Virtual Machine Configuration +============================= + +In order to set up streaming, qemu needs to expose a +`com.redhat.stream.0` virtio port, associated with a +corresponding Spice port. + +Using virt-manager +------------------ + +In the hardware details, click on "Add Hardware", then select +"Channel". Add a "Spice port" device type with the +"com.redhat.stream.0" name. You also need to set "Channel" to +"com.redhat.stream.0" + + +Using libvirt +------------- + +[source,xml] + + + + + + + +Using QEMU +---------- + +[source,sh] +-device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel1,id=channel1,name=com.redhat.stream.0 -chardev spiceport,name=com.redhat.stream.0,id=charchannel1 + + +NVIDIA-specific Configuration +============================= + +In order to use NVIDIA hardware-accelerated encoding, you will need to +configure the virtual machine to use mdev/vfio. An NVIDIA card has to be listed +in lspci output on the guest. + +The guest also needs to have the NVIDIA proprietary drivers installed and in +use. diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..c3467cb --- /dev/null +++ b/configure.ac @@ -0,0 +1,109 @@ +AC_PREREQ([2.57]) + +AC_INIT(spice-streaming-agent, 0.1, + [spice-devel@lists.freedesktop.org]) + +AM_CONFIG_HEADER([config.h]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_AUX_DIR([build-aux]) + +AM_INIT_AUTOMAKE([dist-bzip2 no-dist-gzip subdir-objects]) +AM_MAINTAINER_MODE + +m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) + +AC_PROG_CC +AC_PROG_CC_C99 +if test x"$ac_cv_prog_cc_c99" = xno; then + AC_MSG_ERROR([C99 compiler is required.]) +fi +AC_PROG_CXX +AX_CXX_COMPILE_STDCXX_11 +AC_PROG_INSTALL +AC_CANONICAL_HOST +LT_INIT([disable-static]) +AM_PROG_CC_C_O +AC_C_BIGENDIAN +PKG_PROG_PKG_CONFIG + +dnl ========================================================================= +dnl Check deps + +SPICE_PROTOCOL_MIN_VER=0.12.14 +PKG_CHECK_MODULES([SPICE_PROTOCOL], [spice-protocol >= $SPICE_PROTOCOL_MIN_VER]) +AC_SUBST([SPICE_PROTOCOL_MIN_VER]) +SAVE_CFLAGS="$CFLAGS" +CFLAGS="$SPICE_PROTOCOL_CFLAGS $CFLAGS" +AC_CHECK_HEADER([spice/stream-device.h],,[AC_MSG_ERROR([Could not locate spice-protocol stream-device.h header])]) +CFLAGS="$SAVE_CFLAGS" + +PKG_CHECK_MODULES(X11, x11) +PKG_CHECK_MODULES(XFIXES, xfixes) + +AC_CHECK_LIB(jpeg, jpeg_destroy_decompress, + AC_MSG_CHECKING([for jpeglib.h]) + AC_TRY_CPP( +[#include +#undef PACKAGE +#undef VERSION +#undef HAVE_STDLIB_H +#include ], + JPEG_LIBS='-ljpeg' + AC_MSG_RESULT($jpeg_ok), + AC_MSG_ERROR([jpeglib.h not found])), + AC_MSG_ERROR([libjpeg not found])) +AC_SUBST(JPEG_LIBS) + +dnl =========================================================================== +dnl check compiler flags + +SPICE_COMPILE_WARNINGS +LIBVIRT_LINKER_RELRO +LIBVIRT_LINKER_NO_INDIRECT + +AC_SUBST(WARN_CFLAGS) +AC_SUBST(WARN_CXXFLAGS) + +dnl ========================================================================= +dnl -fvisibility stuff + +have_gcc4=no +AC_MSG_CHECKING(for -fvisibility) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([ +#if defined(__GNUC__) && (__GNUC__ >= 4) +#else +error Need GCC 4.0 for visibility +#endif +int main () { return 0; } +])], have_gcc4=yes) + +if test "x$have_gcc4" = "xyes" && test ! "$os_win32" = "yes" ; then + VISIBILITY_HIDDEN_CFLAGS="-fvisibility=hidden" +fi +AC_MSG_RESULT($have_gcc4) +AC_SUBST(VISIBILITY_HIDDEN_CFLAGS) + +AC_DEFINE_DIR([BINDIR], [bindir], [Where data are placed to.]) + +AC_OUTPUT([ +spice-streaming-agent.spec +data/spice-streaming.desktop +Makefile +src/Makefile +src/unittests/Makefile +include/spice-streaming-agent/Makefile +spice-streaming-agent.pc +]) + +dnl ========================================================================== +AC_MSG_NOTICE([ + + SPICE streaming agent $VERSION + ============================== + + prefix: ${prefix} + C compiler: ${CC} + C++ compiler: ${CXX} + + Now type 'make' to build $PACKAGE +]) diff --git a/data/90-spice-guest-streaming.rules b/data/90-spice-guest-streaming.rules new file mode 100644 index 0000000..6cedd5c --- /dev/null +++ b/data/90-spice-guest-streaming.rules @@ -0,0 +1,2 @@ +ACTION=="add", SUBSYSTEM=="virtio-ports", ENV{DEVLINKS}=="/dev/virtio-ports/com.redhat.stream.0", MODE="0666" + diff --git a/data/spice-streaming.desktop.in b/data/spice-streaming.desktop.in new file mode 100644 index 0000000..dcb30a3 --- /dev/null +++ b/data/spice-streaming.desktop.in @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=SPICE Streaming Agent +Comment=Agent for Streaming the framebuffer to Spice +Exec=@BINDIR@/spice-streaming-agent +#TryExec=/usr/bin/spice-streaming-agent +Terminal=false +Type=Application +Categories= +X-GNOME-Autostart-Phase=Application diff --git a/include/spice-streaming-agent/Makefile.am b/include/spice-streaming-agent/Makefile.am new file mode 100644 index 0000000..844f791 --- /dev/null +++ b/include/spice-streaming-agent/Makefile.am @@ -0,0 +1,7 @@ +NULL = +public_includedir = $(includedir)/spice-streaming-agent +public_include_HEADERS = \ + frame-capture.hpp \ + plugin.hpp \ + $(NULL) + diff --git a/include/spice-streaming-agent/frame-capture.hpp b/include/spice-streaming-agent/frame-capture.hpp new file mode 100644 index 0000000..f226e99 --- /dev/null +++ b/include/spice-streaming-agent/frame-capture.hpp @@ -0,0 +1,62 @@ +/* Common interface for all streaming / capture cards + * used by SPICE streaming-agent. + * + * \copyright + * Copyright 2016-2017 Red Hat Inc. All rights reserved. + */ +#ifndef SPICE_STREAMING_AGENT_FRAME_CAPTURE_HPP +#define SPICE_STREAMING_AGENT_FRAME_CAPTURE_HPP +#include + +#include + +namespace SpiceStreamingAgent { + +struct FrameSize +{ + unsigned width; + unsigned height; +}; + +struct FrameInfo +{ + FrameSize size; + /*! Memory buffer, valid till next frame is read */ + const void *buffer; + size_t buffer_size; + /*! Start of a new stream */ + bool stream_start; +}; + +/*! + * Pure base class implementing the frame capture + */ +class FrameCapture +{ +public: + virtual ~FrameCapture()=default; + + /*! Grab a frame + * This function will wait for next frame. + * Capture is started if needed. + */ + virtual FrameInfo CaptureFrame()=0; + + /*! Reset capturing + * This will reset to beginning state + */ + virtual void Reset()=0; + + /*! + * Get video codec used to encode last frame + */ + virtual SpiceVideoCodecType VideoCodecType() const=0; +protected: + FrameCapture()=default; + FrameCapture(const FrameCapture&)=delete; + void operator=(const FrameCapture&)=delete; +}; + +} + +#endif // SPICE_STREAMING_AGENT_FRAME_CAPTURE_HPP diff --git a/include/spice-streaming-agent/plugin.hpp b/include/spice-streaming-agent/plugin.hpp new file mode 100644 index 0000000..727cb3b --- /dev/null +++ b/include/spice-streaming-agent/plugin.hpp @@ -0,0 +1,152 @@ +/* Plugin interface for all streaming / capture cards + * used by SPICE streaming-agent. + * + * \copyright + * Copyright 2017 Red Hat Inc. All rights reserved. + */ +#ifndef SPICE_STREAMING_AGENT_PLUGIN_HPP +#define SPICE_STREAMING_AGENT_PLUGIN_HPP + +#include + +/*! + * \file + * \brief Plugin interface + * + * Each module loaded by the agent should implement one or more + * Plugins and register them. + */ + +namespace SpiceStreamingAgent { + +class FrameCapture; + +/*! + * Plugin version, only using few bits, schema is 0xMMmm + * where MM is major and mm is the minor, can be easily expanded + * using more bits in the future + */ +enum Constants : unsigned { PluginVersion = 0x100u }; + +enum Ranks : unsigned { + /// this plugin should not be used + DontUse = 0, + /// use plugin only as a fallback + FallBackMin = 1, + /// plugin supports encoding in software + SoftwareMin = 0x40000000, + /// plugin supports encoding in hardware + HardwareMin = 0x80000000, + /// plugin provides access to specific card hardware not only for compression + SpecificHardwareMin = 0xC0000000 +}; + +/*! + * Configuration option. + * An array of these will be passed to the plugin. + * Simply a couple name and value passed as string. + * For instance "framerate" and "20". + */ +struct ConfigureOption +{ + const char *name; + const char *value; +}; + +/*! + * Interface a plugin should implement and register to the Agent. + * + * A plugin module can register multiple Plugin interfaces to handle + * multiple codecs. In this case each Plugin will report data for a + * specific codec. + */ +class Plugin +{ +public: + /*! + * Allows to free the plugin when not needed + */ + virtual ~Plugin() {}; + + /*! + * Request an object for getting frames. + * Plugin should return proper object or nullptr if not possible + * to initialize. + * Plugin can also raise std::runtime_error which will be logged. + */ + virtual FrameCapture *CreateCapture()=0; + + /*! + * Request to rank the plugin. + * See Ranks enumeration for details on ranges. + * \return Ranks::DontUse if not possible to use the plugin, this + * is necessary as the condition for capturing frames can change + * from the time the plugin decided to register and now. + */ + virtual unsigned Rank()=0; + + /*! + * Get video codec used to encode last frame + */ + virtual SpiceVideoCodecType VideoCodecType() const=0; +}; + +/*! + * Interface the plugin should use to interact with the agent. + * The agent will pass it to the entry point. + * Exporting functions from an executable in Windows OS is not easy + * and a standard way to do it so better to implement the interface + * that way for compatibility. + */ +class Agent +{ +public: + /*! + * Get agent version. + * Plugin should check the version for compatibility before doing + * everything. + * \return version specified like PluginVersion + */ + virtual unsigned Version() const=0; + + /*! + * Check if a given plugin version is compatible with this agent + * \return true is compatible + */ + virtual bool PluginVersionIsCompatible(unsigned pluginVersion) const=0; + + /*! + * Register a plugin in the system. + */ + virtual void Register(Plugin& plugin)=0; + + /*! + * Get options array. + * Array is terminated with {nullptr, nullptr}. + * Never nullptr. + * \todo passing options to entry point instead? + */ + virtual const ConfigureOption* Options() const=0; +}; + +typedef bool PluginInitFunc(SpiceStreamingAgent::Agent* agent); + +} + +#ifndef SPICE_STREAMING_AGENT_PROGRAM +/*! + * Plugin main entry point. + * Plugins should check if the version of the agent is compatible. + * If is compatible should register itself to the agent and return + * true. + * If is not compatible can decide to stay in memory or not returning + * true (do not unload) or false (safe to unload). This is necessary + * if the plugin uses some library which are not safe to be unloaded. + * This public interface is also designed to avoid exporting data from + * the plugin which could be a problem in some systems. + * \return true if plugin should stay loaded, false otherwise + */ +extern "C" SpiceStreamingAgent::PluginInitFunc spice_streaming_agent_plugin_init; +#endif + +#endif // SPICE_STREAMING_AGENT_PLUGIN_HPP diff --git a/m4/ac_define_dir.m4 b/m4/ac_define_dir.m4 new file mode 100644 index 0000000..e15cea2 --- /dev/null +++ b/m4/ac_define_dir.m4 @@ -0,0 +1,34 @@ +dnl @synopsis AC_DEFINE_DIR(VARNAME, DIR [, DESCRIPTION]) +dnl +dnl This macro sets VARNAME to the expansion of the DIR variable, +dnl taking care of fixing up ${prefix} and such. +dnl +dnl VARNAME is then offered as both an output variable and a C +dnl preprocessor symbol. +dnl +dnl Example: +dnl +dnl AC_DEFINE_DIR([DATADIR], [datadir], [Where data are placed to.]) +dnl +dnl @category Misc +dnl @author Stepan Kasal +dnl @author Andreas Schwab +dnl @author Guido U. Draheim +dnl @author Alexandre Oliva +dnl @version 2006-10-13 +dnl @license AllPermissive + +AC_DEFUN([AC_DEFINE_DIR], [ + prefix_NONE= + exec_prefix_NONE= + test "x$prefix" = xNONE && prefix_NONE=yes && prefix=$ac_default_prefix + test "x$exec_prefix" = xNONE && exec_prefix_NONE=yes && exec_prefix=$prefix +dnl In Autoconf 2.60, ${datadir} refers to ${datarootdir}, which in turn +dnl refers to ${prefix}. Thus we have to use `eval' twice. + eval ac_define_dir="\"[$]$2\"" + eval ac_define_dir="\"$ac_define_dir\"" + AC_SUBST($1, "$ac_define_dir") + AC_DEFINE_UNQUOTED($1, "$ac_define_dir", [$3]) + test "$prefix_NONE" && prefix=NONE + test "$exec_prefix_NONE" && exec_prefix=NONE +]) diff --git a/m4/manywarnings.m4 b/m4/manywarnings.m4 new file mode 100644 index 0000000..4f701f4 --- /dev/null +++ b/m4/manywarnings.m4 @@ -0,0 +1,276 @@ +# manywarnings.m4 serial 8 +dnl Copyright (C) 2008-2016 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Simon Josefsson + +# gl_MANYWARN_COMPLEMENT(OUTVAR, LISTVAR, REMOVEVAR) +# -------------------------------------------------- +# Copy LISTVAR to OUTVAR except for the entries in REMOVEVAR. +# Elements separated by whitespace. In set logic terms, the function +# does OUTVAR = LISTVAR \ REMOVEVAR. +AC_DEFUN([gl_MANYWARN_COMPLEMENT], +[ + gl_warn_set= + set x $2; shift + for gl_warn_item + do + case " $3 " in + *" $gl_warn_item "*) + ;; + *) + gl_warn_set="$gl_warn_set $gl_warn_item" + ;; + esac + done + $1=$gl_warn_set +]) + +# gl_MANYWARN_ALL_GCC(VARIABLE) +# ----------------------------- +# Add all documented GCC warning parameters to variable VARIABLE. +# Note that you need to test them using gl_WARN_ADD if you want to +# make sure your gcc understands it. +AC_DEFUN([gl_MANYWARN_ALL_GCC], +[ + dnl First, check for some issues that only occur when combining multiple + dnl gcc warning categories. + AC_REQUIRE([AC_PROG_CC]) + if test -n "$GCC"; then + + dnl Check if -W -Werror -Wno-missing-field-initializers is supported + dnl with the current $CC $CFLAGS $CPPFLAGS. + AC_MSG_CHECKING([whether -Wno-missing-field-initializers is supported]) + AC_CACHE_VAL([gl_cv_cc_nomfi_supported], [ + gl_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -W -Werror -Wno-missing-field-initializers" + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[]], [[]])], + [gl_cv_cc_nomfi_supported=yes], + [gl_cv_cc_nomfi_supported=no]) + CFLAGS="$gl_save_CFLAGS"]) + AC_MSG_RESULT([$gl_cv_cc_nomfi_supported]) + + if test "$gl_cv_cc_nomfi_supported" = yes; then + dnl Now check whether -Wno-missing-field-initializers is needed + dnl for the { 0, } construct. + AC_MSG_CHECKING([whether -Wno-missing-field-initializers is needed]) + AC_CACHE_VAL([gl_cv_cc_nomfi_needed], [ + gl_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -W -Werror" + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[int f (void) + { + typedef struct { int a; int b; } s_t; + s_t s1 = { 0, }; + return s1.b; + } + ]], + [[]])], + [gl_cv_cc_nomfi_needed=no], + [gl_cv_cc_nomfi_needed=yes]) + CFLAGS="$gl_save_CFLAGS" + ]) + AC_MSG_RESULT([$gl_cv_cc_nomfi_needed]) + fi + + dnl Next, check if -Werror -Wuninitialized is useful with the + dnl user's choice of $CFLAGS; some versions of gcc warn that it + dnl has no effect if -O is not also used + AC_MSG_CHECKING([whether -Wuninitialized is supported]) + AC_CACHE_VAL([gl_cv_cc_uninitialized_supported], [ + gl_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -Werror -Wuninitialized" + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[]], [[]])], + [gl_cv_cc_uninitialized_supported=yes], + [gl_cv_cc_uninitialized_supported=no]) + CFLAGS="$gl_save_CFLAGS"]) + AC_MSG_RESULT([$gl_cv_cc_uninitialized_supported]) + + fi + + # List all gcc warning categories. + # To compare this list to your installed GCC's, run this Bash command: + # + # comm -3 \ + # <(sed -n 's/^ *\(-[^ ]*\) .*/\1/p' manywarnings.m4 | sort) \ + # <(gcc --help=warnings | sed -n 's/^ \(-[^ ]*\) .*/\1/p' | sort | + # grep -v -x -f <( + # awk '/^[^#]/ {print $1}' ../build-aux/gcc-warning.spec)) + + gl_manywarn_set= + for gl_manywarn_item in \ + -fno-common \ + -W \ + -Wabi \ + -Waddress \ + -Waggressive-loop-optimizations \ + -Wall \ + -Wattributes \ + -Wbad-function-cast \ + -Wbool-compare \ + -Wbuiltin-macro-redefined \ + -Wcast-align \ + -Wchar-subscripts \ + -Wchkp \ + -Wclobbered \ + -Wcomment \ + -Wcomments \ + -Wcoverage-mismatch \ + -Wcpp \ + -Wdate-time \ + -Wdeprecated \ + -Wdeprecated-declarations \ + -Wdesignated-init \ + -Wdisabled-optimization \ + -Wdiscarded-array-qualifiers \ + -Wdiscarded-qualifiers \ + -Wdiv-by-zero \ + -Wdouble-promotion \ + -Wduplicated-cond \ + -Wempty-body \ + -Wendif-labels \ + -Wenum-compare \ + -Wextra \ + -Wformat-contains-nul \ + -Wformat-extra-args \ + -Wformat-nonliteral \ + -Wformat-security \ + -Wformat-signedness \ + -Wformat-y2k \ + -Wformat-zero-length \ + -Wframe-address \ + -Wfree-nonheap-object \ + -Whsa \ + -Wignored-attributes \ + -Wignored-qualifiers \ + -Wimplicit \ + -Wimplicit-function-declaration \ + -Wimplicit-int \ + -Wincompatible-pointer-types \ + -Winit-self \ + -Winline \ + -Wint-conversion \ + -Wint-to-pointer-cast \ + -Winvalid-memory-model \ + -Winvalid-pch \ + -Wjump-misses-init \ + -Wlogical-not-parentheses \ + -Wlogical-op \ + -Wmain \ + -Wmaybe-uninitialized \ + -Wmemset-transposed-args \ + -Wmisleading-indentation \ + -Wmissing-braces \ + -Wmissing-declarations \ + -Wmissing-field-initializers \ + -Wmissing-include-dirs \ + -Wmissing-parameter-type \ + -Wmissing-prototypes \ + -Wmultichar \ + -Wnarrowing \ + -Wnested-externs \ + -Wnonnull \ + -Wnonnull-compare \ + -Wnull-dereference \ + -Wodr \ + -Wold-style-declaration \ + -Wold-style-definition \ + -Wopenmp-simd \ + -Woverflow \ + -Woverlength-strings \ + -Woverride-init \ + -Wpacked \ + -Wpacked-bitfield-compat \ + -Wparentheses \ + -Wpointer-arith \ + -Wpointer-sign \ + -Wpointer-to-int-cast \ + -Wpragmas \ + -Wreturn-local-addr \ + -Wreturn-type \ + -Wscalar-storage-order \ + -Wsequence-point \ + -Wshadow \ + -Wshift-count-negative \ + -Wshift-count-overflow \ + -Wshift-negative-value \ + -Wsizeof-array-argument \ + -Wsizeof-pointer-memaccess \ + -Wstack-protector \ + -Wstrict-aliasing \ + -Wstrict-overflow \ + -Wstrict-prototypes \ + -Wsuggest-attribute=const \ + -Wsuggest-attribute=format \ + -Wsuggest-attribute=noreturn \ + -Wsuggest-attribute=pure \ + -Wsuggest-final-methods \ + -Wsuggest-final-types \ + -Wswitch \ + -Wswitch-bool \ + -Wswitch-default \ + -Wsync-nand \ + -Wsystem-headers \ + -Wtautological-compare \ + -Wtrampolines \ + -Wtrigraphs \ + -Wtype-limits \ + -Wuninitialized \ + -Wunknown-pragmas \ + -Wunsafe-loop-optimizations \ + -Wunused \ + -Wunused-but-set-parameter \ + -Wunused-but-set-variable \ + -Wunused-function \ + -Wunused-label \ + -Wunused-local-typedefs \ + -Wunused-macros \ + -Wunused-parameter \ + -Wunused-result \ + -Wunused-value \ + -Wunused-variable \ + -Wvarargs \ + -Wvariadic-macros \ + -Wvector-operation-performance \ + -Wvla \ + -Wvolatile-register-var \ + -Wwrite-strings \ + \ + ; do + gl_manywarn_set="$gl_manywarn_set $gl_manywarn_item" + done + + # gcc --help=warnings outputs an unusual form for these options; list + # them here so that the above 'comm' command doesn't report a false match. + gl_manywarn_set="$gl_manywarn_set -Warray-bounds=2" + gl_manywarn_set="$gl_manywarn_set -Wnormalized=nfc" + gl_manywarn_set="$gl_manywarn_set -Wshift-overflow=2" + gl_manywarn_set="$gl_manywarn_set -Wunused-const-variable=2" + + # These are needed for older GCC versions. + if test -n "$GCC"; then + case `($CC --version) 2>/dev/null` in + 'gcc (GCC) '[[0-3]].* | \ + 'gcc (GCC) '4.[[0-7]].*) + gl_manywarn_set="$gl_manywarn_set -fdiagnostics-show-option" + gl_manywarn_set="$gl_manywarn_set -funit-at-a-time" + ;; + esac + fi + + # Disable specific options as needed. + if test "$gl_cv_cc_nomfi_needed" = yes; then + gl_manywarn_set="$gl_manywarn_set -Wno-missing-field-initializers" + fi + + if test "$gl_cv_cc_uninitialized_supported" = no; then + gl_manywarn_set="$gl_manywarn_set -Wno-uninitialized" + fi + + $1=$gl_manywarn_set +]) diff --git a/m4/spice-compile-warnings.m4 b/m4/spice-compile-warnings.m4 new file mode 100644 index 0000000..66d7179 --- /dev/null +++ b/m4/spice-compile-warnings.m4 @@ -0,0 +1,183 @@ +dnl +dnl Enable all known GCC compiler warnings, except for those +dnl we can't yet cope with +dnl +AC_DEFUN([SPICE_COMPILE_WARNINGS],[ + dnl ****************************** + dnl More compiler warnings + dnl ****************************** + + AC_ARG_ENABLE([werror], + AS_HELP_STRING([--enable-werror], [Use -Werror (if supported)]), + [set_werror="$enableval"], + [if test -d $srcdir/.git; then + is_git_version=true + set_werror=yes + else + set_werror=no + fi]) + + # List of warnings that are not relevant / wanted + + # Don't care about C++ compiler compat + dontwarn="$dontwarn -Wc++-compat" + dontwarn="$dontwarn -Wabi" + dontwarn="$dontwarn -Wdeprecated" + # For older gcc versions, -Wenum-compare is "C++ and Objective-C++ only" + # For newer gcc versions, -Wenum-compare is "enabled by -Wall" + dontwarn="$dontwarn -Wenum-compare" + # Don't care about ancient C standard compat + dontwarn="$dontwarn -Wtraditional" + # Don't care about ancient C standard compat + dontwarn="$dontwarn -Wtraditional-conversion" + # Ignore warnings in /usr/include + dontwarn="$dontwarn -Wsystem-headers" + # Happy for compiler to add struct padding + dontwarn="$dontwarn -Wpadded" + # GCC very confused with -O2 + dontwarn="$dontwarn -Wunreachable-code" + + + dontwarn="$dontwarn -Wconversion" + dontwarn="$dontwarn -Wsign-conversion" + dontwarn="$dontwarn -Wvla" + dontwarn="$dontwarn -Wundef" + dontwarn="$dontwarn -Wcast-qual" + dontwarn="$dontwarn -Wlong-long" + dontwarn="$dontwarn -Wswitch-default" + dontwarn="$dontwarn -Wswitch-enum" + dontwarn="$dontwarn -Wstrict-overflow" + dontwarn="$dontwarn -Wunsafe-loop-optimizations" + dontwarn="$dontwarn -Wformat-nonliteral" + dontwarn="$dontwarn -Wfloat-equal" + dontwarn="$dontwarn -Wdeclaration-after-statement" + dontwarn="$dontwarn -Wcast-qual" + dontwarn="$dontwarn -Wconversion" + dontwarn="$dontwarn -Wsign-conversion" + dontwarn="$dontwarn -Wpacked" + dontwarn="$dontwarn -Wunused-macros" + dontwarn="$dontwarn -Woverlength-strings" + dontwarn="$dontwarn -Wstack-protector" + dontwarn="$dontwarn -Winline" + dontwarn="$dontwarn -Wbad-function-cast" + dontwarn="$dontwarn -Wshadow" + dontwarn="$dontwarn -Wformat-signedness" + dontwarn="$dontwarn -Wnull-dereference" + + # This causes an error to be detected in glib headers + dontwarn="$dontwarn -Wshift-overflow=2" + + # Stuff that C++ won't allow. Turn them back on later + dontwarn="$dontwarn -Wdesignated-init" + dontwarn="$dontwarn -Wdiscarded-array-qualifiers" + dontwarn="$dontwarn -Wdiscarded-qualifiers" + dontwarn="$dontwarn -Wimplicit-function-declaration" + dontwarn="$dontwarn -Wimplicit-int" + dontwarn="$dontwarn -Wimplicit" + dontwarn="$dontwarn -Wincompatible-pointer-types" + dontwarn="$dontwarn -Wint-conversion" + dontwarn="$dontwarn -Wjump-misses-init" + dontwarn="$dontwarn -Wmissing-parameter-type" + dontwarn="$dontwarn -Wmissing-prototypes" + dontwarn="$dontwarn -Wnested-externs" + dontwarn="$dontwarn -Wold-style-declaration" + dontwarn="$dontwarn -Wold-style-definition" + dontwarn="$dontwarn -Woverride-init" + dontwarn="$dontwarn -Wpointer-sign" + dontwarn="$dontwarn -Wpointer-to-int-cast" + dontwarn="$dontwarn -Wstrict-prototypes" + + # We want to enable these, but need to sort out the + # decl mess with gtk/generated_*.c + dontwarn="$dontwarn -Wmissing-declarations" + + # Get all possible GCC warnings + gl_MANYWARN_ALL_GCC([maybewarn]) + + # Remove the ones we don't want, blacklisted earlier + gl_MANYWARN_COMPLEMENT([wantwarn], [$maybewarn], [$dontwarn]) + + # Check for $CC support of each warning + for w in $wantwarn; do + gl_WARN_ADD([$w]) + done + + # GNULIB uses '-W' (aka -Wextra) which includes a bunch of stuff. + # Unfortunately, this means you can't simply use '-Wsign-compare' + # with gl_MANYWARN_COMPLEMENT + # So we have -W enabled, and then have to explicitly turn off... + gl_WARN_ADD([-Wno-sign-compare]) + gl_WARN_ADD([-Wno-unused-parameter]) + # We can't enable this due to horrible spice_usb_device_get_description + # signature + gl_WARN_ADD([-Wno-format-nonliteral]) + + # This should be < 1024 really. pixman_utils is the blackspot + # preventing lower usage + gl_WARN_ADD([-Wframe-larger-than=20460]) + + # Use improved glibc headers + AH_VERBATIM([FORTIFY_SOURCE], + [/* Enable compile-time and run-time bounds-checking, and some warnings. */ +#if !defined _FORTIFY_SOURCE && defined __OPTIMIZE__ && __OPTIMIZE__ +# define _FORTIFY_SOURCE 2 +#endif +]) + + # Extra special flags + dnl -fstack-protector stuff passes gl_WARN_ADD with gcc + dnl on Mingw32, but fails when actually used + case $host in + *-*-linux*) + dnl Fedora only uses -fstack-protector, but doesn't seem to + dnl be great overhead in adding -fstack-protector-all instead + dnl gl_WARN_ADD([-fstack-protector]) + gl_WARN_ADD([-fstack-protector-all]) + gl_WARN_ADD([--param=ssp-buffer-size=4]) + ;; + esac + gl_WARN_ADD([-fexceptions]) + gl_WARN_ADD([-fasynchronous-unwind-tables]) + gl_WARN_ADD([-fdiagnostics-show-option]) + gl_WARN_ADD([-funit-at-a-time]) + + # Need -fipa-pure-const in order to make -Wsuggest-attribute=pure + # fire even without -O. + gl_WARN_ADD([-fipa-pure-const]) + + # We should eventually enable this, but right now there are at + # least 75 functions triggering warnings. + gl_WARN_ADD([-Wno-suggest-attribute=pure]) + gl_WARN_ADD([-Wno-suggest-attribute=const]) + + if test "$set_werror" = "yes" + then + gl_WARN_ADD([-Werror]) + fi + WARN_CXXFLAGS=$WARN_CFLAGS + AC_SUBST([WARN_CXXFLAGS]) + + # These are C-only warnings + gl_WARN_ADD([-Wdesignated-init]) + gl_WARN_ADD([-Wdiscarded-array-qualifiers]) + gl_WARN_ADD([-Wdiscarded-qualifiers]) + gl_WARN_ADD([-Wimplicit-function-declaration]) + gl_WARN_ADD([-Wimplicit-int]) + gl_WARN_ADD([-Wimplicit]) + gl_WARN_ADD([-Wincompatible-pointer-types]) + gl_WARN_ADD([-Wint-conversion]) + gl_WARN_ADD([-Wjump-misses-init]) + gl_WARN_ADD([-Wmissing-parameter-type]) + gl_WARN_ADD([-Wmissing-prototypes]) + gl_WARN_ADD([-Wnested-externs]) + gl_WARN_ADD([-Wold-style-declaration]) + gl_WARN_ADD([-Wold-style-definition]) + gl_WARN_ADD([-Woverride-init]) + gl_WARN_ADD([-Wpointer-sign]) + gl_WARN_ADD([-Wpointer-to-int-cast]) + gl_WARN_ADD([-Wstrict-prototypes]) + + WARN_LDFLAGS=$WARN_CFLAGS + AC_SUBST([WARN_CFLAGS]) + AC_SUBST([WARN_LDFLAGS]) +]) diff --git a/m4/virt-linker-no-indirect.m4 b/m4/virt-linker-no-indirect.m4 new file mode 100644 index 0000000..b344f70 --- /dev/null +++ b/m4/virt-linker-no-indirect.m4 @@ -0,0 +1,32 @@ +dnl +dnl Check for --no-copy-dt-needed-entries +dnl +dnl Copyright (C) 2013 Guido Günther +dnl +dnl This library is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU Lesser General Public +dnl License as published by the Free Software Foundation; either +dnl version 2.1 of the License, or (at your option) any later version. +dnl +dnl This library is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl Lesser General Public License for more details. +dnl +dnl You should have received a copy of the GNU Lesser General Public +dnl License along with this library. If not, see +dnl . +dnl + +AC_DEFUN([LIBVIRT_LINKER_NO_INDIRECT],[ + AC_MSG_CHECKING([for how to avoid indirect lib deps]) + + NO_INDIRECT_LDFLAGS= + case `$LD --help 2>&1` in + *"--no-copy-dt-needed-entries"*) + NO_INDIRECT_LDFLAGS="-Wl,--no-copy-dt-needed-entries" ;; + esac + AC_SUBST([NO_INDIRECT_LDFLAGS]) + + AC_MSG_RESULT([$NO_INDIRECT_LDFLAGS]) +]) diff --git a/m4/virt-linker-relro.m4 b/m4/virt-linker-relro.m4 new file mode 100644 index 0000000..079a095 --- /dev/null +++ b/m4/virt-linker-relro.m4 @@ -0,0 +1,35 @@ +dnl +dnl Check for -z now and -z relro linker flags +dnl +dnl Copyright (C) 2013 Red Hat, Inc. +dnl +dnl This library is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU Lesser General Public +dnl License as published by the Free Software Foundation; either +dnl version 2.1 of the License, or (at your option) any later version. +dnl +dnl This library is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl Lesser General Public License for more details. +dnl +dnl You should have received a copy of the GNU Lesser General Public +dnl License along with this library. If not, see +dnl . +dnl + +AC_DEFUN([LIBVIRT_LINKER_RELRO],[ + AC_MSG_CHECKING([for how to force completely read-only GOT table]) + + RELRO_LDFLAGS= + ld_help=`$LD --help 2>&1` + case $ld_help in + *"-z relro"*) RELRO_LDFLAGS="-Wl,-z -Wl,relro" ;; + esac + case $ld_help in + *"-z now"*) RELRO_LDFLAGS="$RELRO_LDFLAGS -Wl,-z -Wl,now" ;; + esac + AC_SUBST([RELRO_LDFLAGS]) + + AC_MSG_RESULT([$RELRO_LDFLAGS]) +]) diff --git a/m4/warnings.m4 b/m4/warnings.m4 new file mode 100644 index 0000000..e3d239b --- /dev/null +++ b/m4/warnings.m4 @@ -0,0 +1,79 @@ +# warnings.m4 serial 11 +dnl Copyright (C) 2008-2013 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Simon Josefsson + +# gl_AS_VAR_APPEND(VAR, VALUE) +# ---------------------------- +# Provide the functionality of AS_VAR_APPEND if Autoconf does not have it. +m4_ifdef([AS_VAR_APPEND], +[m4_copy([AS_VAR_APPEND], [gl_AS_VAR_APPEND])], +[m4_define([gl_AS_VAR_APPEND], +[AS_VAR_SET([$1], [AS_VAR_GET([$1])$2])])]) + + +# gl_COMPILER_OPTION_IF(OPTION, [IF-SUPPORTED], [IF-NOT-SUPPORTED], +# [PROGRAM = AC_LANG_PROGRAM()]) +# ----------------------------------------------------------------- +# Check if the compiler supports OPTION when compiling PROGRAM. +# +# FIXME: gl_Warn must be used unquoted until we can assume Autoconf +# 2.64 or newer. +AC_DEFUN([gl_COMPILER_OPTION_IF], +[AS_VAR_PUSHDEF([gl_Warn], [gl_cv_warn_[]_AC_LANG_ABBREV[]_$1])dnl +AS_VAR_PUSHDEF([gl_Flags], [_AC_LANG_PREFIX[]FLAGS])dnl +AS_LITERAL_IF([$1], + [m4_pushdef([gl_Positive], m4_bpatsubst([$1], [^-Wno-], [-W]))], + [gl_positive="$1" +case $gl_positive in + -Wno-*) gl_positive=-W`expr "X$gl_positive" : 'X-Wno-\(.*\)'` ;; +esac +m4_pushdef([gl_Positive], [$gl_positive])])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler handles $1], m4_defn([gl_Warn]), [ + gl_save_compiler_FLAGS="$gl_Flags" + gl_AS_VAR_APPEND(m4_defn([gl_Flags]), + [" $gl_unknown_warnings_are_errors ]m4_defn([gl_Positive])["]) + AC_LINK_IFELSE([m4_default([$4], [AC_LANG_PROGRAM([])])], + [AS_VAR_SET(gl_Warn, [yes])], + [AS_VAR_SET(gl_Warn, [no])]) + gl_Flags="$gl_save_compiler_FLAGS" +]) +AS_VAR_IF(gl_Warn, [yes], [$2], [$3]) +m4_popdef([gl_Positive])dnl +AS_VAR_POPDEF([gl_Flags])dnl +AS_VAR_POPDEF([gl_Warn])dnl +]) + +# gl_UNKNOWN_WARNINGS_ARE_ERRORS +# ------------------------------ +# Clang doesn't complain about unknown warning options unless one also +# specifies -Wunknown-warning-option -Werror. Detect this. +AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS], +[gl_COMPILER_OPTION_IF([-Werror -Wunknown-warning-option], + [gl_unknown_warnings_are_errors='-Wunknown-warning-option -Werror'], + [gl_unknown_warnings_are_errors=])]) + +# gl_WARN_ADD(OPTION, [VARIABLE = WARN_CFLAGS], +# [PROGRAM = AC_LANG_PROGRAM()]) +# --------------------------------------------- +# Adds parameter to WARN_CFLAGS if the compiler supports it when +# compiling PROGRAM. For example, gl_WARN_ADD([-Wparentheses]). +# +# If VARIABLE is a variable name, AC_SUBST it. +AC_DEFUN([gl_WARN_ADD], +[AC_REQUIRE([gl_UNKNOWN_WARNINGS_ARE_ERRORS]) +gl_COMPILER_OPTION_IF([$1], + [gl_AS_VAR_APPEND(m4_if([$2], [], [[WARN_CFLAGS]], [[$2]]), [" $1"])], + [], + [$3]) +m4_ifval([$2], + [AS_LITERAL_IF([$2], [AC_SUBST([$2])])], + [AC_SUBST([WARN_CFLAGS])])dnl +]) + +# Local Variables: +# mode: autoconf +# End: diff --git a/spice-streaming-agent.pc.in b/spice-streaming-agent.pc.in new file mode 100644 index 0000000..74128d3 --- /dev/null +++ b/spice-streaming-agent.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +includedir=@includedir@ +exec_prefix=@exec_prefix@ +plugindir=@libdir@/@PACKAGE@/plugins + +Name: spice-streaming-agent +Description: SPICE streaming agent headers +Version: @VERSION@ +Requires: spice-protocol >= @SPICE_PROTOCOL_MIN_VER@ + +Libs: +Cflags: -I${includedir} diff --git a/spice-streaming-agent.spec.in b/spice-streaming-agent.spec.in new file mode 100644 index 0000000..3aff328 --- /dev/null +++ b/spice-streaming-agent.spec.in @@ -0,0 +1,64 @@ +Name: spice-streaming-agent +Version: @VERSION@ +Release: 1%{?dist} +Summary: SPICE streaming agent +Group: Applications/System +License: Proprietary +URL: https://www.redhat.com +Source0: %{name}-%{version}.tar.bz2 +BuildRequires: spice-protocol >= @SPICE_PROTOCOL_MIN_VER@ +BuildRequires: libX11-devel libXfixes-devel +BuildRequires: libjpeg-turbo-devel +# we need /usr/sbin/semanage program which is available on different +# packages depending on distribution +Requires(post): /usr/sbin/semanage +Requires(postun): /usr/sbin/semanage + +%description +An agent, running on a guest, sending video streams of the X display to a remote client (over Spice) + +%package devel +Requires: spice-protocol >= @SPICE_PROTOCOL_MIN_VER@ +Summary: SPICE streaming agent development files + +%description devel +This package contains necessary header files to build SPICE streaming +agent plugins. + +%prep +%setup -q + +%build +%configure +make %{?_smp_mflags} V=1 + +%install +make install DESTDIR=$RPM_BUILD_ROOT V=1 +if test -d "$RPM_BUILD_ROOT/%{_libdir}/%{name}/plugins"; then + find $RPM_BUILD_ROOT/%{_libdir}/%{name}/plugins -name '*.la' -delete +fi + +%post +semanage fcontext -a -t xserver_exec_t %{_bindir}/spice-streaming-agent 2>/dev/null || : +restorecon %{_bindir}/spice-streaming-agent || : + +%postun +if [ $1 -eq 0 ] ; then # final removal +semanage fcontext -d -t xserver_exec_t %{_bindir}/spice-streaming-agent 2>/dev/null || : +fi + + +%files +%doc COPYING ChangeLog README LICENSE +%{_bindir}/spice-streaming-agent +%{_sysconfdir}/xdg/autostart/spice-streaming.desktop +%{_datadir}/gdm/greeter/autostart/spice-streaming.desktop + +%files devel +%defattr(-,root,root,-) +%{_includedir} +%{_datadir}/pkgconfig + +%changelog +* Wed Aug 16 2017 Frediano Ziglio +- Initial package diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..f6e5531 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,2 @@ +/spice-streaming-agent +/libstreaming-utils.a diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..8d5c5bd --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,59 @@ +# Makefile configuration for SPICE streaming agent +# +# \copyright +# Copyright 2016-2017 Red Hat Inc. All rights reserved. + +NULL = +SUBDIRS = . unittests + +AM_CPPFLAGS = \ + -DSPICE_STREAMING_AGENT_PROGRAM \ + -I$(top_srcdir)/include \ + -DPLUGINSDIR=\"$(pkglibdir)/plugins\" \ + $(SPICE_PROTOCOL_CFLAGS) \ + $(X11_CFLAGS) \ + $(XFIXES_CFLAGS) \ + $(NULL) + +AM_CFLAGS = \ + $(VISIBILITY_HIDDEN_CFLAGS) \ + $(WARN_CFLAGS) \ + $(NULL) + +AM_CXXFLAGS = \ + $(VISIBILITY_HIDDEN_CFLAGS) \ + $(WARN_CXXFLAGS) \ + $(NULL) + +bin_PROGRAMS = spice-streaming-agent +noinst_LIBRARIES = libstreaming-utils.a + +libstreaming_utils_a_SOURCES = \ + hexdump.c \ + hexdump.h \ + $(NULL) + +spice_streaming_agent_LDFLAGS = \ + $(RELRO_LDFLAGS) \ + $(NO_INDIRECT_LDFLAGS) \ + $(NULL) + +spice_streaming_agent_LDADD = \ + -ldl \ + -lpthread \ + libstreaming-utils.a \ + $(X11_LIBS) \ + $(XFIXES_LIBS) \ + $(JPEG_LIBS) \ + $(NULL) + +spice_streaming_agent_SOURCES = \ + spice-streaming-agent.cpp \ + static-plugin.cpp \ + static-plugin.hpp \ + concrete-agent.cpp \ + concrete-agent.hpp \ + mjpeg-fallback.cpp \ + jpeg.cpp \ + jpeg.hpp \ + $(NULL) diff --git a/src/concrete-agent.cpp b/src/concrete-agent.cpp new file mode 100644 index 0000000..192054a --- /dev/null +++ b/src/concrete-agent.cpp @@ -0,0 +1,122 @@ +/* Implementation of the agent + * + * \copyright + * Copyright 2017 Red Hat Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#include "concrete-agent.hpp" +#include "static-plugin.hpp" + +using namespace std; +using namespace SpiceStreamingAgent; + +static inline unsigned MajorVersion(unsigned version) +{ + return version >> 8; +} + +static inline unsigned MinorVersion(unsigned version) +{ + return version & 0xffu; +} + +ConcreteAgent::ConcreteAgent() +{ + options.push_back(ConcreteConfigureOption(nullptr, nullptr)); +} + +bool ConcreteAgent::PluginVersionIsCompatible(unsigned pluginVersion) const +{ + unsigned version = Version(); + return MajorVersion(version) == MajorVersion(pluginVersion) && + MinorVersion(version) >= MinorVersion(pluginVersion); +} + +void ConcreteAgent::Register(Plugin& plugin) +{ + plugins.push_back(shared_ptr(&plugin)); +} + +const ConfigureOption* ConcreteAgent::Options() const +{ + static_assert(sizeof(ConcreteConfigureOption) == sizeof(ConfigureOption), + "ConcreteConfigureOption should be binary compatible with ConfigureOption"); + return static_cast(&options[0]); +} + +void ConcreteAgent::AddOption(const char *name, const char *value) +{ + // insert before the last {nullptr, nullptr} value + options.insert(--options.end(), ConcreteConfigureOption(name, value)); +} + +void ConcreteAgent::LoadPlugins(const char *directory) +{ + StaticPlugin::InitAll(*this); + + string pattern = string(directory) + "/*.so"; + glob_t globbuf; + + int glob_result = glob(pattern.c_str(), 0, NULL, &globbuf); + if (glob_result == GLOB_NOMATCH) + return; + if (glob_result != 0) { + syslog(LOG_ERR, "glob FAILED with %d", glob_result); + return; + } + + for (size_t n = 0; n < globbuf.gl_pathc; ++n) { + LoadPlugin(globbuf.gl_pathv[n]); + } + globfree(&globbuf); +} + +void ConcreteAgent::LoadPlugin(const char *plugin_filename) +{ + void *dl = dlopen(plugin_filename, RTLD_LOCAL|RTLD_NOW); + if (!dl) { + syslog(LOG_ERR, "error loading plugin %s", plugin_filename); + return; + } + + try { + PluginInitFunc* init_func = + (PluginInitFunc *) dlsym(dl, "spice_streaming_agent_plugin_init"); + if (!init_func || !init_func(this)) { + dlclose(dl); + } + } + catch (std::runtime_error &err) { + syslog(LOG_ERR, "%s", err.what()); + dlclose(dl); + } +} + +FrameCapture *ConcreteAgent::GetBestFrameCapture() +{ + vector>> sorted_plugins; + + // sort plugins base on ranking, reverse order + for (const auto& plugin: plugins) { + sorted_plugins.push_back(make_pair(plugin->Rank(), plugin)); + } + sort(sorted_plugins.rbegin(), sorted_plugins.rend()); + + // return first not null + for (const auto& plugin: sorted_plugins) { + if (plugin.first == DontUse) { + break; + } + FrameCapture *capture = plugin.second->CreateCapture(); + if (capture) { + return capture; + } + } + return nullptr; +} diff --git a/src/concrete-agent.hpp b/src/concrete-agent.hpp new file mode 100644 index 0000000..828368b --- /dev/null +++ b/src/concrete-agent.hpp @@ -0,0 +1,46 @@ +/* Agent implementation + * + * \copyright + * Copyright 2017 Red Hat Inc. All rights reserved. + */ +#ifndef SPICE_STREAMING_AGENT_CONCRETE_AGENT_HPP +#define SPICE_STREAMING_AGENT_CONCRETE_AGENT_HPP + +#include +#include +#include + +namespace SpiceStreamingAgent { + +struct ConcreteConfigureOption: ConfigureOption +{ + ConcreteConfigureOption(const char *name, const char *value) + { + this->name = name; + this->value = value; + } +}; + +class ConcreteAgent final : public Agent +{ +public: + ConcreteAgent(); + unsigned Version() const override { + return PluginVersion; + } + void Register(Plugin& plugin) override; + const ConfigureOption* Options() const override; + void LoadPlugins(const char *directory); + // pointer must remain valid + void AddOption(const char *name, const char *value); + FrameCapture *GetBestFrameCapture(); + bool PluginVersionIsCompatible(unsigned pluginVersion) const override; +private: + void LoadPlugin(const char *plugin_filename); + std::vector> plugins; + std::vector options; +}; + +} + +#endif // SPICE_STREAMING_AGENT_CONCRETE_AGENT_HPP diff --git a/src/hexdump.c b/src/hexdump.c new file mode 100644 index 0000000..7654396 --- /dev/null +++ b/src/hexdump.c @@ -0,0 +1,33 @@ +/* Hex dump utility + * + * \copyright + * Copyright 2016-2017 Red Hat Inc. All rights reserved. + */ +#include +#include +#include + +#include "hexdump.h" + +void hexdump(const void *ptr, size_t size, FILE *f_out) +{ + const uint8_t *buffer = (const uint8_t *) ptr; + unsigned long sum = 0; + + for (size_t n = 0; n < size;) { + int i; + enum { BYTES_PER_LINE = 16 }; + char s[BYTES_PER_LINE + 1], hexstring[BYTES_PER_LINE * 3 + 1]; + + fprintf(f_out, "%04X ", (unsigned) n); + for (i = 0; n < size && i < BYTES_PER_LINE; i++, n++) { + uint8_t c = buffer[n]; + sum += c; + sprintf(hexstring + i * 3, "%02X ", c); + s[i] = isprint(c) ? c : '.'; + } + s[i] = '\0'; + fprintf(f_out, "%*s\t%s\n", (int) (-3 * BYTES_PER_LINE), hexstring, s); + } + fprintf(f_out, "sum = %lu\n", sum); +} diff --git a/src/hexdump.h b/src/hexdump.h new file mode 100644 index 0000000..b8542a2 --- /dev/null +++ b/src/hexdump.h @@ -0,0 +1,21 @@ +/* Hex dump utility + * + * \copyright + * Copyright 2016-2017 Red Hat Inc. All rights reserved. + */ +#ifndef RH_STREAMING_AGENT_HEXDUMP_H_ +#define RH_STREAMING_AGENT_HEXDUMP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +void hexdump(const void *buffer, size_t size, FILE *f_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/jpeg.cpp b/src/jpeg.cpp new file mode 100644 index 0000000..ceee359 --- /dev/null +++ b/src/jpeg.cpp @@ -0,0 +1,91 @@ +/* Jpeg functions + * + * \copyright + * Copyright 2017 Red Hat Inc. All rights reserved. + */ +#include +#include +#include +#include +#include +#include + +#include "jpeg.hpp" + +struct JpegBuffer: public jpeg_destination_mgr +{ + JpegBuffer(std::vector& buffer); + ~JpegBuffer(); + + std::vector& buffer; +}; + +static boolean buf_empty_output_buffer(j_compress_ptr cinfo) +{ + JpegBuffer *buf = (JpegBuffer *) cinfo->dest; + size_t size = buf->next_output_byte - &buf->buffer[0]; + buf->buffer.resize(buf->buffer.capacity() * 2); + buf->next_output_byte = &buf->buffer[0] + size; + buf->free_in_buffer = buf->buffer.size() - size; + return TRUE; +} + +static void dummy_destination(j_compress_ptr cinfo) +{ +} + +JpegBuffer::JpegBuffer(std::vector& buffer): + buffer(buffer) +{ + if (buffer.capacity() < 32 * 1024) { + buffer.resize(32 * 1024); + } else { + buffer.resize(buffer.capacity()); + } + next_output_byte = &buffer[0]; + free_in_buffer = buffer.size(); + init_destination = dummy_destination; + empty_output_buffer = buf_empty_output_buffer; + term_destination = dummy_destination; +} + +JpegBuffer::~JpegBuffer() +{ + buffer.resize(next_output_byte - &buffer[0]); +} + +/* from https://github.com/LuaDist/libjpeg/blob/master/example.c */ +void write_JPEG_file(std::vector& buffer, int quality, uint8_t *data, unsigned width, unsigned height) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + JSAMPROW row_pointer[1]; + int row_stride; + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + + JpegBuffer buf(buffer); + cinfo.dest = &buf; + + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.input_components = 4; + cinfo.in_color_space = JCS_EXT_BGRX; + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE); + + jpeg_start_compress(&cinfo, TRUE); + + row_stride = width * 4; + + while (cinfo.next_scanline < cinfo.image_height) { + row_pointer[0] = &data[cinfo.next_scanline * row_stride]; + // TODO check error + (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + jpeg_finish_compress(&cinfo); + + jpeg_destroy_compress(&cinfo); +} diff --git a/src/jpeg.hpp b/src/jpeg.hpp new file mode 100644 index 0000000..dd59405 --- /dev/null +++ b/src/jpeg.hpp @@ -0,0 +1,14 @@ +/* Jpeg functions + * + * \copyright + * Copyright 2017 Red Hat Inc. All rights reserved. + */ +#ifndef RH_STREAMING_AGENT_JPEG_HPP_ +#define RH_STREAMING_AGENT_JPEG_HPP_ + +#include +#include + +void write_JPEG_file(std::vector& buffer, int quality, uint8_t *data, unsigned width, unsigned height); + +#endif diff --git a/src/mjpeg-fallback.cpp b/src/mjpeg-fallback.cpp new file mode 100644 index 0000000..f41e68a --- /dev/null +++ b/src/mjpeg-fallback.cpp @@ -0,0 +1,223 @@ +/* Plugin implementation for Mjpeg + * + * \copyright + * Copyright 2017 Red Hat Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "static-plugin.hpp" +#include "jpeg.hpp" + +using namespace std; +using namespace SpiceStreamingAgent; + +#define ERROR(args) do { \ + std::ostringstream _s; \ + _s << args; \ + throw std::runtime_error(_s.str()); \ +} while(0) + +#define FBC_ERROR(function) \ + ERROR(function " failed(" << fbcStatus << "): " << pFn.nvFBCGetLastErrorStr(fbcHandle)) + +static inline uint64_t get_time() +{ + timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + + return (uint64_t)now.tv_sec * 10000000000u + (uint64_t)now.tv_nsec; +} + +namespace { +struct MjpegSettings +{ + int fps; + int quality; +}; + +class MjpegFrameCapture final: public FrameCapture +{ +public: + MjpegFrameCapture(const MjpegSettings &settings); + ~MjpegFrameCapture(); + FrameInfo CaptureFrame() override; + void Reset() override; + SpiceVideoCodecType VideoCodecType() const { + return SPICE_VIDEO_CODEC_TYPE_MJPEG; + } +private: + MjpegSettings settings; + Display *dpy; + + vector frame; + + // last frame sizes + uint32_t last_width = ~0u, last_height = ~0u; + // last time before capture + uint64_t last_time = 0; +}; + +class MjpegPlugin final: public Plugin +{ +public: + FrameCapture *CreateCapture() override; + unsigned Rank() override; + void ParseOptions(const ConfigureOption *options); + SpiceVideoCodecType VideoCodecType() const { + return SPICE_VIDEO_CODEC_TYPE_MJPEG; + } +private: + MjpegSettings settings = { 10, 80 }; +}; +} + +MjpegFrameCapture::MjpegFrameCapture(const MjpegSettings& settings): + settings(settings) +{ + dpy = XOpenDisplay(NULL); + if (!dpy) + ERROR("Unable to initialize X11"); +} + +MjpegFrameCapture::~MjpegFrameCapture() +{ + XCloseDisplay(dpy); +} + +void MjpegFrameCapture::Reset() +{ + frame.clear(); + last_width = last_height = ~0u; +} + +FrameInfo MjpegFrameCapture::CaptureFrame() +{ + FrameInfo info; + + // reduce speed considering FPS + auto now = get_time(); + if (last_time == 0) { + last_time = now; + } else { + const uint64_t delta = 1000000000u / settings.fps; + if (now >= last_time + delta) { + last_time = now; + } else { + uint64_t wait_time = last_time + delta - now; + // mathematically wait_time must be less than a second as + // delta would be 1 seconds only for FPS == 1 but in this + // side of the if now < last_time + delta + // but is also true that now > last_time so + // last_time + delta > now > last_time so + // 1s >= delta > now - last_time > 0 so + // wait_time = delta - (now - last_time) < delta <= 1s + timespec delay = { 0, (long) wait_time }; + nanosleep(&delay, NULL); + last_time += delta; + } + } + + int screen = XDefaultScreen(dpy); + + Window win = RootWindow(dpy, screen); + + XWindowAttributes win_info; + XGetWindowAttributes(dpy, win, &win_info); + + bool is_first = false; + if (win_info.width != last_width || win_info.height != last_height) { + last_width = win_info.width; + last_height = win_info.height; + is_first = true; + } + + info.size.width = win_info.width; + info.size.height = win_info.height; + + int format = ZPixmap; + // TODO handle errors + XImage *image = XGetImage(dpy, win, win_info.x, win_info.y, + win_info.width, win_info.height, AllPlanes, format); + + // TODO handle errors + // TODO multiple formats (only 32 bit) + write_JPEG_file(frame, settings.quality, (uint8_t*) image->data, + image->width, image->height); + + image->f.destroy_image(image); + + info.buffer = &frame[0]; + info.buffer_size = frame.size(); + + info.stream_start = is_first; + + return info; +} + +FrameCapture *MjpegPlugin::CreateCapture() +{ + return new MjpegFrameCapture(settings); +} + +unsigned MjpegPlugin::Rank() +{ + return FallBackMin; +} + +void MjpegPlugin::ParseOptions(const ConfigureOption *options) +{ +#define arg_error(...) syslog(LOG_ERR, ## __VA_ARGS__); + + for (; options->name; ++options) { + const char *name = options->name; + const char *value = options->value; + + if (strcmp(name, "framerate") == 0) { + int val = atoi(value); + if (val > 0) { + settings.fps = val; + } + else { + arg_error("wrong framerate arg %s\n", value); + } + } + if (strcmp(name, "mjpeg.quality") == 0) { + int val = atoi(value); + if (val > 0) { + settings.quality = val; + } + else { + arg_error("wrong mjpeg.quality arg %s\n", value); + } + } + } +} + +static bool +mjpeg_plugin_init(Agent* agent) +{ + if (agent->Version() != PluginVersion) + return false; + + std::unique_ptr plugin(new MjpegPlugin()); + + plugin->ParseOptions(agent->Options()); + + agent->Register(*plugin.release()); + + return true; +} + +static StaticPlugin mjpeg_plugin(mjpeg_plugin_init); diff --git a/src/spice-streaming-agent.cpp b/src/spice-streaming-agent.cpp new file mode 100644 index 0000000..ed7ddb9 --- /dev/null +++ b/src/spice-streaming-agent.cpp @@ -0,0 +1,529 @@ +/* An implementation of a SPICE streaming agent + * + * \copyright + * Copyright 2016-2017 Red Hat Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "hexdump.h" +#include "concrete-agent.hpp" + +using namespace std; +using namespace SpiceStreamingAgent; + +static ConcreteAgent agent; + +typedef struct { + StreamDevHeader hdr; + StreamMsgFormat msg; +} SpiceStreamFormatMessage; + +typedef struct { + StreamDevHeader hdr; + StreamMsgData msg; +} SpiceStreamDataMessage; + +static int streaming_requested; +static bool quit; +static int streamfd = -1; +static bool stdin_ok; +static int log_binary = 0; +static std::mutex stream_mtx; + +static int have_something_to_read(int *pfd, int timeout) +{ + int nfds; + struct pollfd pollfds[2] = { + {streamfd, POLLIN, 0}, + {0, POLLIN, 0} + }; + *pfd = -1; + nfds = (stdin_ok ? 2 : 1); + if (poll(pollfds, nfds, timeout) < 0) { + syslog(LOG_ERR, "poll FAILED\n"); + return -1; + } + if (pollfds[0].revents == POLLIN) { + *pfd = streamfd; + } + if (pollfds[1].revents == POLLIN) { + *pfd = 0; + } + return *pfd != -1; +} + +static int read_command_from_stdin(void) +{ + char buffer[64], *p, *save = NULL; + + p = fgets(buffer, sizeof(buffer), stdin); + if (p == NULL) { + syslog(LOG_ERR, "Failed to read from stdin\n"); + return -1; + } + const char *cmd = strtok_r(buffer, " \t\n\r", &save); + if (!cmd) + return 1; + if (strcmp(cmd, "quit") == 0) { + quit = true; + } else if (strcmp(cmd, "start") == 0) { + streaming_requested = 1; + } else if (strcmp(cmd, "stop") == 0) { + streaming_requested = 0; + } else { + syslog(LOG_WARNING, "unknown command %s\n", cmd); + } + return 1; +} + +static int read_command_from_device(void) +{ + StreamDevHeader hdr; + uint8_t msg[64]; + int n; + + std::lock_guard stream_guard(stream_mtx); + n = read(streamfd, &hdr, sizeof(hdr)); + if (n != sizeof(hdr)) { + syslog(LOG_WARNING, + "read command from device FAILED -- read %d expected %lu\n", + n, sizeof(hdr)); + return -1; + } + if (hdr.protocol_version != STREAM_DEVICE_PROTOCOL) { + syslog(LOG_WARNING, "BAD VERSION %d (expected is %d)\n", hdr.protocol_version, + STREAM_DEVICE_PROTOCOL); + return 0; // return -1; -- fail over this ? + } + if (hdr.type != STREAM_TYPE_START_STOP) { + syslog(LOG_WARNING, "UNKNOWN msg of type %d\n", hdr.type); + return 0; // return -1; + } + if (hdr.size >= sizeof(msg)) { + syslog(LOG_WARNING, + "msg size (%d) is too long (longer than %lu)\n", + hdr.size, sizeof(msg)); + return 0; // return -1; + } + n = read(streamfd, &msg, hdr.size); + if (n != hdr.size) { + syslog(LOG_WARNING, + "read command from device FAILED -- read %d expected %d\n", + n, hdr.size); + return -1; + } + streaming_requested = msg[0]; /* num_codecs */ + syslog(LOG_INFO, "GOT START_STOP message -- request to %s streaming\n", + streaming_requested ? "START" : "STOP"); + return 1; +} + +static int read_command(int blocking) +{ + int fd, n=1; + int timeout = blocking?-1:0; + while ( !quit ) { + if (!have_something_to_read(&fd, timeout)) { + if (!blocking) { + return 0; + } + sleep(1); + continue; + } + if (fd) { + n = read_command_from_device(); + } else { + n = read_command_from_stdin(); + } + break; + } + return n; +} + +static size_t +write_all(int fd, const void *buf, const size_t len) +{ + size_t written = 0; + while (written < len) { + int l = write(fd, (const char *) buf + written, len - written); + if (l < 0 && errno == EINTR) { + continue; + } + if (l < 0) { + syslog(LOG_ERR, "write failed - %m"); + return l; + } + written += l; + } + syslog(LOG_DEBUG, "write_all -- %u bytes written\n", (unsigned)written); + return written; +} + + +static int spice_stream_send_format(unsigned w, unsigned h, unsigned c) +{ + + SpiceStreamFormatMessage msg; + const size_t msgsize = sizeof(msg); + const size_t hdrsize = sizeof(msg.hdr); + memset(&msg, 0, msgsize); + msg.hdr.protocol_version = STREAM_DEVICE_PROTOCOL; + msg.hdr.type = STREAM_TYPE_FORMAT; + msg.hdr.size = msgsize - hdrsize; /* includes only the body? */ + msg.msg.width = w; + msg.msg.height = h; + msg.msg.codec = c; + syslog(LOG_DEBUG, "writing format\n"); + std::lock_guard stream_guard(stream_mtx); + if (write_all(streamfd, &msg, msgsize) != msgsize) { + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +static int spice_stream_send_frame(const void *buf, const unsigned size) +{ + SpiceStreamDataMessage msg; + const size_t msgsize = sizeof(msg); + ssize_t n; + + memset(&msg, 0, msgsize); + msg.hdr.protocol_version = STREAM_DEVICE_PROTOCOL; + msg.hdr.type = STREAM_TYPE_DATA; + msg.hdr.size = size; /* includes only the body? */ + std::lock_guard stream_guard(stream_mtx); + n = write_all(streamfd, &msg, msgsize); + syslog(LOG_DEBUG, + "wrote %ld bytes of header of data msg with frame of size %u bytes\n", + n, msg.hdr.size); + if (n != msgsize) { + syslog(LOG_WARNING, "write_all header: wrote %ld expected %lu\n", + n, msgsize); + return EXIT_FAILURE; + } + n = write_all(streamfd, buf, size); + syslog(LOG_DEBUG, "wrote data msg body of size %ld\n", n); + if (n != size) { + syslog(LOG_WARNING, "write_all header: wrote %ld expected %u\n", + n, size); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +/* returns current time in micro-seconds */ +static uint64_t get_time(void) +{ + struct timeval now; + + gettimeofday(&now, NULL); + + return (uint64_t)now.tv_sec * 1000000 + (uint64_t)now.tv_usec; + +} + +static void handle_interrupt(int intr) +{ + syslog(LOG_INFO, "Got signal %d, exiting", intr); + quit = true; +} + +static void register_interrupts(void) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handle_interrupt; + if ((sigaction(SIGINT, &sa, NULL) != 0) && + (sigaction(SIGTERM, &sa, NULL) != 0)) { + syslog(LOG_WARNING, "failed to register signal handler %m"); + } +} + +static void usage(const char *progname) +{ + printf("usage: %s \n", progname); + printf("options are:\n"); + printf("\t-p portname -- virtio-serial port to use\n"); + printf("\t-i accept commands from stdin\n"); + printf("\t-l file -- log frames to file\n"); + printf("\t--log-binary -- log binary frames (following -l)\n"); + printf("\t-d -- enable debug logs\n"); + printf("\t-c variable=value -- change settings\n"); + printf("\t\tprofile = [0, 1, 66, 77, 100, 244]\n"); + printf("\t\tratecontrol = constqp/vbr/cbr/2passq/2passf/2passi\n"); + printf("\t\tdwqp = 0-51\n"); + printf("\t\tframerate = 1-100 (check 10,20,30,40,50,60)\n"); + printf("\n"); + printf("\t-h or --help -- print this help message\n"); + + exit(1); +} + +static void send_cursor(const XFixesCursorImage &image) +{ + if (image.width >= 1024 || image.height >= 1024) + return; + + size_t cursor_size = + sizeof(StreamDevHeader) + sizeof(StreamMsgCursorSet) + + image.width * image.height * sizeof(uint32_t); + std::unique_ptr msg(new uint8_t[cursor_size]); + + StreamDevHeader &dev_hdr(*reinterpret_cast(msg.get())); + memset(&dev_hdr, 0, sizeof(dev_hdr)); + dev_hdr.protocol_version = STREAM_DEVICE_PROTOCOL; + dev_hdr.type = STREAM_TYPE_CURSOR_SET; + dev_hdr.size = cursor_size - sizeof(StreamDevHeader); + + StreamMsgCursorSet &cursor_msg(*reinterpret_cast(msg.get() + sizeof(StreamDevHeader))); + memset(&cursor_msg, 0, sizeof(cursor_msg)); + + cursor_msg.type = SPICE_CURSOR_TYPE_ALPHA; + cursor_msg.width = image.width; + cursor_msg.height = image.height; + cursor_msg.hot_spot_x = image.xhot; + cursor_msg.hot_spot_y = image.yhot; + + uint32_t *pixels = reinterpret_cast(cursor_msg.data); + for (unsigned i = 0; i < image.width * image.height; ++i) + pixels[i] = image.pixels[i]; + + std::lock_guard stream_guard(stream_mtx); + write_all(streamfd, msg.get(), cursor_size); +} + +static void cursor_changes(Display *display, int event_base) +{ + unsigned long last_serial = 0; + + while (1) { + XEvent event; + XNextEvent(display, &event); + if (event.type != event_base + 1) + continue; + + XFixesCursorImage *cursor = XFixesGetCursorImage(display); + if (!cursor) + continue; + + if (cursor->cursor_serial == last_serial) + continue; + + last_serial = cursor->cursor_serial; + send_cursor(*cursor); + } +} + +static void +do_capture(const char *streamport, FILE *f_log) +{ + std::unique_ptr capture(agent.GetBestFrameCapture()); + if (!capture) + throw std::runtime_error("cannot find a suitable capture system"); + + streamfd = open(streamport, O_RDWR); + if (streamfd < 0) + // TODO was syslog(LOG_ERR, "Failed to open %s: %s\n", streamport, strerror(errno)); + throw std::runtime_error("failed to open streaming device"); + + unsigned int frame_count = 0; + while (! quit) { + while (!quit && !streaming_requested) { + if (read_command(1) < 0) { + syslog(LOG_ERR, "FAILED to read command\n"); + goto done; + } + } + + syslog(LOG_INFO, "streaming starts now\n"); + uint64_t time_last = 0; + + while (!quit && streaming_requested) { + if (++frame_count % 100 == 0) { + syslog(LOG_DEBUG, "SENT %d frames\n", frame_count); + } + uint64_t time_before = get_time(); + + FrameInfo frame = capture->CaptureFrame(); + + uint64_t time_after = get_time(); + syslog(LOG_DEBUG, + "got a frame -- size is %zu (%lu ms) (%lu ms from last frame)(%lu us)\n", + frame.buffer_size, (time_after - time_before)/1000, + (time_after - time_last)/1000, + (time_before - time_last)); + time_last = time_after; + + if (frame.stream_start) { + unsigned width, height; + unsigned char codec; + + width = frame.size.width; + height = frame.size.height; + codec = capture->VideoCodecType(); + + syslog(LOG_DEBUG, "wXh %uX%u codec=%u\n", width, height, codec); + + if (spice_stream_send_format(width, height, codec) == EXIT_FAILURE) + throw std::runtime_error("FAILED to send format message"); + } + if (f_log) { + if (log_binary) { + fwrite(frame.buffer, frame.buffer_size, 1, f_log); + } else { + fprintf(f_log, "%lu: Frame of %zu bytes:\n", get_time(), frame.buffer_size); + hexdump(frame.buffer, frame.buffer_size, f_log); + } + } + if (spice_stream_send_frame(frame.buffer, frame.buffer_size) == EXIT_FAILURE) { + syslog(LOG_ERR, "FAILED to send a frame\n"); + break; + } + //usleep(1); + if (read_command(0) < 0) { + syslog(LOG_ERR, "FAILED to read command\n"); + goto done; + } + if (!streaming_requested) { + capture->Reset(); + } + } + } + +done: + if (streamfd >= 0) { + close(streamfd); + streamfd = -1; + } +} + +#define arg_error(...) syslog(LOG_ERR, ## __VA_ARGS__); + +int main(int argc, char* argv[]) +{ + const char *streamport = "/dev/virtio-ports/com.redhat.stream.0"; + char opt; + const char *log_filename = NULL; + int logmask = LOG_UPTO(LOG_WARNING); + struct option long_options[] = { + { "log-binary", no_argument, &log_binary, 1}, + { "help", no_argument, NULL, 'h'}, + { 0, 0, 0, 0} + }; + + if (isatty(fileno(stderr)) && isatty(fileno(stdin))) { + stdin_ok = true; + } + + openlog("spice-streaming-agent", stdin_ok? (LOG_PERROR|LOG_PID) : LOG_PID, LOG_USER); + setlogmask(logmask); + + while ((opt = getopt_long(argc, argv, "hip:c:l:d", long_options, NULL)) != -1) { + switch (opt) { + case 0: + /* Handle long options if needed */ + break; + case 'i': + stdin_ok = true; + openlog("spice-streaming-agent", LOG_PERROR|LOG_PID, LOG_USER); + break; + case 'p': + streamport = optarg; + break; + case 'c': { + char *p = strchr(optarg, '='); + if (p == NULL) { + arg_error("wrong 'c' argument %s\n", optarg); + usage(argv[0]); + } + *p++ = '\0'; + agent.AddOption(optarg, p); + break; + } + case 'l': + log_filename = optarg; + break; + case 'd': + logmask = LOG_UPTO(LOG_DEBUG); + setlogmask(logmask); + break; + case 'h': + usage(argv[0]); + break; + } + } + + agent.LoadPlugins(PLUGINSDIR); + + register_interrupts(); + + FILE *f_log = NULL; + if (log_filename) { + f_log = fopen(log_filename, "wb"); + if (!f_log) { + syslog(LOG_ERR, "Failed to open log file '%s': %s\n", + log_filename, strerror(errno)); + return EXIT_FAILURE; + } + } + + Display *display = XOpenDisplay(NULL); + if (display == NULL) { + syslog(LOG_ERR, "failed to open display\n"); + return EXIT_FAILURE; + } + int event_base, error_base; + if (!XFixesQueryExtension(display, &event_base, &error_base)) { + syslog(LOG_ERR, "XFixesQueryExtension failed\n"); + return EXIT_FAILURE; + } + Window rootwindow = DefaultRootWindow(display); + XFixesSelectCursorInput(display, rootwindow, XFixesDisplayCursorNotifyMask); + + std::thread cursor_th(cursor_changes, display, event_base); + cursor_th.detach(); + + int ret = EXIT_SUCCESS; + try { + do_capture(streamport, f_log); + } + catch (std::runtime_error &err) { + syslog(LOG_ERR, "%s\n", err.what()); + ret = EXIT_FAILURE; + } + + if (f_log) { + fclose(f_log); + f_log = NULL; + } + closelog(); + return ret; +} + diff --git a/src/static-plugin.cpp b/src/static-plugin.cpp new file mode 100644 index 0000000..d5feb22 --- /dev/null +++ b/src/static-plugin.cpp @@ -0,0 +1,23 @@ +/* Utility to manage registration of plugins compiled statically + * + * \copyright + * Copyright 2017 Red Hat Inc. All rights reserved. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include "static-plugin.hpp" + +using namespace SpiceStreamingAgent; + +const StaticPlugin *StaticPlugin::list = nullptr; + +void StaticPlugin::InitAll(Agent& agent) +{ + for (const StaticPlugin* plugin = list; plugin; plugin = plugin->next) { + plugin->init_func(&agent); + } +} diff --git a/src/static-plugin.hpp b/src/static-plugin.hpp new file mode 100644 index 0000000..5436b41 --- /dev/null +++ b/src/static-plugin.hpp @@ -0,0 +1,35 @@ +/* Utility to manage registration of plugins compiled statically + * + * \copyright + * Copyright 2017 Red Hat Inc. All rights reserved. + */ +#ifndef SPICE_STREAMING_AGENT_STATIC_PLUGIN_HPP +#define SPICE_STREAMING_AGENT_STATIC_PLUGIN_HPP + +#include + +namespace SpiceStreamingAgent { + +class StaticPlugin final { +public: + StaticPlugin(PluginInitFunc init_func): + next(list), + init_func(init_func) + { + list = this; + } + static void InitAll(Agent& agent); +private: + // this should be instantiated statically + void *operator new(size_t s); + void *operator new[](size_t s); + + const StaticPlugin *const next; + const PluginInitFunc* const init_func; + + static const StaticPlugin *list; +}; + +} + +#endif // SPICE_STREAMING_AGENT_STATIC_PLUGIN_HPP diff --git a/src/unittests/.gitignore b/src/unittests/.gitignore new file mode 100644 index 0000000..36548a1 --- /dev/null +++ b/src/unittests/.gitignore @@ -0,0 +1 @@ +/test-hexdump diff --git a/src/unittests/Makefile.am b/src/unittests/Makefile.am new file mode 100644 index 0000000..0dc2328 --- /dev/null +++ b/src/unittests/Makefile.am @@ -0,0 +1,35 @@ +NULL = + +AM_CPPFLAGS = \ + -DRH_TOP_SRCDIR=\"$(abs_top_srcdir)\" \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/unittests \ + $(SPICE_PROTOCOL_CFLAGS) \ + $(NULL) + +AM_CFLAGS = \ + $(VISIBILITY_HIDDEN_CFLAGS) \ + $(WARN_CFLAGS) \ + $(NULL) + +check_PROGRAMS = \ + test-hexdump \ + $(NULL) + +TESTS = \ + hexdump.sh \ + $(NULL) + +noinst_PROGRAMS = \ + $(check_PROGRAMS) \ + $(NULL) + +test_hexdump_SOURCES = \ + test-hexdump.c \ + $(NULL) + +test_hexdump_LDADD = \ + ../libstreaming-utils.a \ + $(NULL) + +EXTRA_DIST = hexdump.sh hexdump*.in hexdump*.out diff --git a/src/unittests/hexdump.sh b/src/unittests/hexdump.sh new file mode 100755 index 0000000..602b501 --- /dev/null +++ b/src/unittests/hexdump.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +set -e + +# avoid weird language handling which could affect +# ascii part of the dump +export LANG=C + +for f in hexdump*.in; do + out=`echo $f | sed 's,\.in,.out,'` + rm -f $out.test + ./test-hexdump $out.test < $f + cmp $out.test $out + rm -f $out.test +done diff --git a/src/unittests/hexdump1.in b/src/unittests/hexdump1.in new file mode 100644 index 0000000..e69de29 diff --git a/src/unittests/hexdump1.out b/src/unittests/hexdump1.out new file mode 100644 index 0000000..00335b3 --- /dev/null +++ b/src/unittests/hexdump1.out @@ -0,0 +1 @@ +sum = 0 diff --git a/src/unittests/hexdump2.in b/src/unittests/hexdump2.in new file mode 100644 index 0000000..887ae93 --- /dev/null +++ b/src/unittests/hexdump2.in @@ -0,0 +1 @@ +ciao diff --git a/src/unittests/hexdump2.out b/src/unittests/hexdump2.out new file mode 100644 index 0000000..5db593c --- /dev/null +++ b/src/unittests/hexdump2.out @@ -0,0 +1,2 @@ +0000 63 69 61 6F 0A ciao. +sum = 422 diff --git a/src/unittests/hexdump3.in b/src/unittests/hexdump3.in new file mode 100644 index 0000000..5f55f17 Binary files /dev/null and b/src/unittests/hexdump3.in differ diff --git a/src/unittests/hexdump3.out b/src/unittests/hexdump3.out new file mode 100644 index 0000000..27ba4ff --- /dev/null +++ b/src/unittests/hexdump3.out @@ -0,0 +1,9 @@ +0000 7F 45 4C 46 02 01 01 00 00 00 00 00 00 00 00 00 .ELF............ +0010 02 00 3E 00 01 00 00 00 20 09 40 00 00 00 00 00 ..>..... .@..... +0020 40 00 00 00 00 00 00 00 80 68 00 00 00 00 00 00 @........h...... +0030 00 00 00 00 40 00 38 00 09 00 40 00 25 00 22 00 ....@.8...@.%.". +0040 06 00 00 00 05 00 00 00 40 00 00 00 00 00 00 00 ........@....... +0050 40 00 40 00 00 00 00 00 40 00 40 00 00 00 00 00 @.@.....@.@..... +0060 F8 01 00 00 00 00 00 00 F8 01 00 00 00 00 00 00 ................ +0070 08 00 00 00 00 00 00 00 03 00 00 ........... +sum = 1916 diff --git a/src/unittests/test-hexdump.c b/src/unittests/test-hexdump.c new file mode 100644 index 0000000..275fbc0 --- /dev/null +++ b/src/unittests/test-hexdump.c @@ -0,0 +1,20 @@ +#undef NDEBUG +#include +#include +#include + +#include "hexdump.h" + +static char buffer[64 * 1024]; + +int main(int argc, const char **argv) +{ + assert(argc >= 2); + size_t s = fread(buffer, 1, sizeof(buffer), stdin); + assert(feof(stdin) && !ferror(stdin) && s <= sizeof(buffer)); + FILE *f = fopen(argv[1], "w"); + assert(f); + hexdump(buffer, s, f); + fclose(f); + return 0; +} -- cgit v1.2.3