summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrediano Ziglio <fziglio@redhat.com>2017-10-20 11:28:15 +0100
committerFrediano Ziglio <fziglio@redhat.com>2017-10-20 11:28:15 +0100
commita8fc6814b656221a6d640b4675e990c3e74a2403 (patch)
treefb8c7f19ceff0a0435539093fd8bb01d485fdeb6
Initial public commit
-rw-r--r--.gitignore31
-rw-r--r--AUTHORS0
-rw-r--r--COPYING1
-rw-r--r--ChangeLog0
-rw-r--r--LICENSE9
-rw-r--r--Makefile.am23
-rw-r--r--NEWS0
-rw-r--r--README52
-rw-r--r--configure.ac109
-rw-r--r--data/90-spice-guest-streaming.rules2
-rw-r--r--data/spice-streaming.desktop.in9
-rw-r--r--include/spice-streaming-agent/Makefile.am7
-rw-r--r--include/spice-streaming-agent/frame-capture.hpp62
-rw-r--r--include/spice-streaming-agent/plugin.hpp152
-rw-r--r--m4/ac_define_dir.m434
-rw-r--r--m4/manywarnings.m4276
-rw-r--r--m4/spice-compile-warnings.m4183
-rw-r--r--m4/virt-linker-no-indirect.m432
-rw-r--r--m4/virt-linker-relro.m435
-rw-r--r--m4/warnings.m479
-rw-r--r--spice-streaming-agent.pc.in12
-rw-r--r--spice-streaming-agent.spec.in64
-rw-r--r--src/.gitignore2
-rw-r--r--src/Makefile.am59
-rw-r--r--src/concrete-agent.cpp122
-rw-r--r--src/concrete-agent.hpp46
-rw-r--r--src/hexdump.c33
-rw-r--r--src/hexdump.h21
-rw-r--r--src/jpeg.cpp91
-rw-r--r--src/jpeg.hpp14
-rw-r--r--src/mjpeg-fallback.cpp223
-rw-r--r--src/spice-streaming-agent.cpp529
-rw-r--r--src/static-plugin.cpp23
-rw-r--r--src/static-plugin.hpp35
-rw-r--r--src/unittests/.gitignore1
-rw-r--r--src/unittests/Makefile.am35
-rwxr-xr-xsrc/unittests/hexdump.sh15
-rw-r--r--src/unittests/hexdump1.in0
-rw-r--r--src/unittests/hexdump1.out1
-rw-r--r--src/unittests/hexdump2.in1
-rw-r--r--src/unittests/hexdump2.out2
-rw-r--r--src/unittests/hexdump3.inbin0 -> 123 bytes
-rw-r--r--src/unittests/hexdump3.out9
-rw-r--r--src/unittests/test-hexdump.c20
44 files changed, 2454 insertions, 0 deletions
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
--- /dev/null
+++ b/AUTHORS
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
--- /dev/null
+++ b/ChangeLog
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
--- /dev/null
+++ b/NEWS
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]
+<devices>
+ <channel type='spiceport'>
+ <source channel='com.redhat.stream.0'/>
+ <target type='virtio' name='com.redhat.stream.0'/>
+ </channel>
+</devices>
+
+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 <stdio.h>
+#undef PACKAGE
+#undef VERSION
+#undef HAVE_STDLIB_H
+#include <jpeglib.h>],
+ 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 <cstdio>
+
+#include <spice/enums.h>
+
+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 <spice/enums.h>
+
+/*!
+ * \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 <kasal@ucw.cz>
+dnl @author Andreas Schwab <schwab@suse.de>
+dnl @author Guido U. Draheim <guidod@gmx.de>
+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 <agx@sigxcpu.org>
+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 <http://www.gnu.org/licenses/>.
+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 <http://www.gnu.org/licenses/>.
+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 <fziglio@redhat.com>
+- 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 <config.h>
+#include <algorithm>
+#include <syslog.h>
+#include <glob.h>
+#include <dlfcn.h>
+
+#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>(&plugin));
+}
+
+const ConfigureOption* ConcreteAgent::Options() const
+{
+ static_assert(sizeof(ConcreteConfigureOption) == sizeof(ConfigureOption),
+ "ConcreteConfigureOption should be binary compatible with ConfigureOption");
+ return static_cast<const ConfigureOption*>(&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<pair<unsigned, shared_ptr<Plugin>>> 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 <vector>
+#include <memory>
+#include <spice-streaming-agent/plugin.hpp>
+
+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<std::shared_ptr<Plugin>> plugins;
+ std::vector<ConcreteConfigureOption> 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 <config.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#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 <stdio.h>
+
+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 <config.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <jpeglib.h>
+#include <setjmp.h>
+
+#include "jpeg.hpp"
+
+struct JpegBuffer: public jpeg_destination_mgr
+{
+ JpegBuffer(std::vector<uint8_t>& buffer);
+ ~JpegBuffer();
+
+ std::vector<uint8_t>& 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<uint8_t>& 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<uint8_t>& 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 <stdio.h>
+#include <vector>
+
+void write_JPEG_file(std::vector<uint8_t>& 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 <config.h>
+#include <cstring>
+#include <exception>
+#include <stdexcept>
+#include <sstream>
+#include <memory>
+#include <syslog.h>
+#include <X11/Xlib.h>
+
+#include <spice-streaming-agent/plugin.hpp>
+#include <spice-streaming-agent/frame-capture.hpp>
+
+#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<uint8_t> 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<MjpegPlugin> 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <poll.h>
+#include <syslog.h>
+#include <signal.h>
+#include <exception>
+#include <stdexcept>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <vector>
+#include <X11/Xlib.h>
+#include <X11/extensions/Xfixes.h>
+
+#include <spice/stream-device.h>
+#include <spice/enums.h>
+
+#include <spice-streaming-agent/frame-capture.hpp>
+#include <spice-streaming-agent/plugin.hpp>
+
+#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<std::mutex> 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<std::mutex> 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<std::mutex> 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 <options>\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<uint8_t[]> msg(new uint8_t[cursor_size]);
+
+ StreamDevHeader &dev_hdr(*reinterpret_cast<StreamDevHeader*>(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<StreamMsgCursorSet *>(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<uint32_t *>(cursor_msg.data);
+ for (unsigned i = 0; i < image.width * image.height; ++i)
+ pixels[i] = image.pixels[i];
+
+ std::lock_guard<std::mutex> 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<FrameCapture> 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 <config.h>
+#endif
+
+#include <stdlib.h>
+#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 <spice-streaming-agent/plugin.hpp>
+
+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
--- /dev/null
+++ b/src/unittests/hexdump1.in
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
--- /dev/null
+++ b/src/unittests/hexdump3.in
Binary files 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 <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+
+#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;
+}