Create an AgentRunner (TODO: needs a better name) class to encapsulate the streaming and communication with the server. The basic setup (cmd arg parsing, signal handling, ...) is moved to main.cpp. The rest of the functions is moved to the AgentRunner class and modified as little as possible: - The cursor updating code is moved into a functor called CursorThread - Some initialization and cleanup is moved to AgentRunner's constructor and destructor - Some error handling moved over to exceptions, mainly what was in main() and do_capture() - A couple of variables gently renamed. Signed-off-by: Lukáš Hrázký <lhrazky@xxxxxxxxxx> --- src/Makefile.am | 2 + src/main.cpp | 127 ++++++++++++ src/spice-streaming-agent.cpp | 455 +++++++++++++++++------------------------- src/spice-streaming-agent.hpp | 56 ++++++ 4 files changed, 370 insertions(+), 270 deletions(-) create mode 100644 src/main.cpp create mode 100644 src/spice-streaming-agent.hpp diff --git a/src/Makefile.am b/src/Makefile.am index 8d5c5bd..3a6fee7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -49,6 +49,7 @@ spice_streaming_agent_LDADD = \ spice_streaming_agent_SOURCES = \ spice-streaming-agent.cpp \ + spice-streaming-agent.hpp \ static-plugin.cpp \ static-plugin.hpp \ concrete-agent.cpp \ @@ -56,4 +57,5 @@ spice_streaming_agent_SOURCES = \ mjpeg-fallback.cpp \ jpeg.cpp \ jpeg.hpp \ + main.cpp \ $(NULL) diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a309011 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,127 @@ +/* An implementation of a SPICE streaming agent + * + * \copyright + * Copyright 2016-2018 Red Hat Inc. All rights reserved. + */ + +#include <config.h> +#include "spice-streaming-agent.hpp" + +#include <string.h> +#include <getopt.h> +#include <unistd.h> +#include <syslog.h> +#include <signal.h> + +using namespace std; +using namespace SpiceStreamingAgent; + + +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\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); +} + +void handle_interrupt(int intr) +{ + syslog(LOG_INFO, "Got signal %d, exiting", intr); + AgentRunner::quit = true; +} + +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"); + } +} + +int main(int argc, char* argv[]) +{ + string stream_port = "/dev/virtio-ports/com.redhat.stream.0"; + char opt; + string log_filename; + int log_binary = 0; + bool stdin_ok = false; + 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); + + std::vector<std::pair<std::string, std::string>> options; + + 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': + stream_port = optarg; + break; + case 'c': { + char *p = strchr(optarg, '='); + if (p == NULL) { + syslog(LOG_ERR, "wrong 'c' argument %s\n", optarg); + usage(argv[0]); + } + *p++ = '\0'; + options.push_back(std::make_pair(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; + } + } + + register_interrupts(); + + try { + AgentRunner runner(stream_port, log_filename, log_binary != 0, stdin_ok); + // TODO fix overcomplicated passing of options to the agent + runner.add_options(options); + runner.run(); + } catch (const std::exception &e) { + syslog(LOG_ERR, "Error: %s\n", e.what()); + return EXIT_FAILURE; + } + + closelog(); + return EXIT_SUCCESS; +} diff --git a/src/spice-streaming-agent.cpp b/src/spice-streaming-agent.cpp index 94d9d25..8f97489 100644 --- a/src/spice-streaming-agent.cpp +++ b/src/spice-streaming-agent.cpp @@ -1,44 +1,38 @@ /* An implementation of a SPICE streaming agent * * \copyright - * Copyright 2016-2017 Red Hat Inc. All rights reserved. + * Copyright 2016-2018 Red Hat Inc. All rights reserved. */ -#include <stdio.h> -#include <stdlib.h> -#include <stdint.h> +#include <config.h> +#include "spice-streaming-agent.hpp" + +#include "concrete-agent.hpp" +#include "hexdump.h" + +#include <spice-streaming-agent/frame-capture.hpp> +#include <spice-streaming-agent/plugin.hpp> +#include <spice/stream-device.h> +#include <spice/enums.h> +#include <X11/Xlib.h> +#include <X11/extensions/Xfixes.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 <functional> -#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; + +bool AgentRunner::quit = false; + +namespace { struct SpiceStreamFormatMessage { @@ -52,15 +46,155 @@ struct SpiceStreamDataMessage StreamMsgData msg; }; -static int streaming_requested; -static std::set<SpiceVideoCodecType> client_codecs; -static bool quit; -static int streamfd = -1; -static bool stdin_ok; -static int log_binary = 0; -static std::mutex stream_mtx; +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 have_something_to_read(int *pfd, int timeout) +class CursorThread { +public: + CursorThread(int streamfd, std::mutex &stream_mtx) : + streamfd(streamfd), + stream_mtx(stream_mtx) + { + display = XOpenDisplay(NULL); + if (display == NULL) { + throw std::runtime_error("Failed to open display."); + } + int error_base; + if (!XFixesQueryExtension(display, &event_base, &error_base)) { + throw std::runtime_error("XFixesQueryExtension failed"); + } + Window rootwindow = DefaultRootWindow(display); + XFixesSelectCursorInput(display, rootwindow, XFixesDisplayCursorNotifyMask); + } + + void operator()() + { + unsigned long last_serial = 0; + + while (1) { + syslog(LOG_ERR, "CURSOR LOOP"); + 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; + auto fill_cursor = [&](uint32_t *pixels) { + for (unsigned i = 0; i < cursor->width * cursor->height; ++i) + pixels[i] = cursor->pixels[i]; + }; + send_cursor(cursor->width, cursor->height, cursor->xhot, cursor->yhot, + fill_cursor, streamfd, stream_mtx); + } + } + +private: + void send_cursor(unsigned width, unsigned height, int hotspot_x, int hotspot_y, + std::function<void(uint32_t *)> fill_cursor, + int streamfd, std::mutex &stream_mtx) + { + if (width >= STREAM_MSG_CURSOR_SET_MAX_WIDTH || + height >= STREAM_MSG_CURSOR_SET_MAX_HEIGHT) + return; + + size_t cursor_size = + sizeof(StreamDevHeader) + sizeof(StreamMsgCursorSet) + + width * 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 = width; + cursor_msg.height = height; + cursor_msg.hot_spot_x = hotspot_x; + cursor_msg.hot_spot_y = hotspot_y; + + uint32_t *pixels = reinterpret_cast<uint32_t *>(cursor_msg.data); + fill_cursor(pixels); + + std::lock_guard<std::mutex> stream_guard(stream_mtx); + write_all(streamfd, msg.get(), cursor_size); + } + + Display* display; + int event_base = 0; + int streamfd = -1; + std::mutex &stream_mtx; +}; + +} // namespace + +AgentRunner::AgentRunner(const std::string &stream_port, + const std::string &log_filename, + bool log_binary, + bool stdin_ok) +: + stream_port(stream_port), + log_binary(log_binary), + stdin_ok(stdin_ok) +{ + agent.LoadPlugins(PLUGINSDIR); + + // TODO proper cleanup on exceptions thrown here - wrap in classes? + streamfd = open(stream_port.c_str(), O_RDWR); + if (streamfd < 0) + throw std::runtime_error("failed to open the streaming device (" + + stream_port + "): " + strerror(errno)); + + if (!log_filename.empty()) { + log_file = fopen(log_filename.c_str(), "wb"); + if (!log_file) { + throw std::runtime_error("Failed to open log file '" + log_filename + + "': " + strerror(errno) + "'"); + } + } +} + +AgentRunner::~AgentRunner() { + if (streamfd >= 0) { + close(streamfd); + streamfd = -1; + } + + if (log_file) { + fclose(log_file); + log_file = nullptr; + } +} + +int AgentRunner::have_something_to_read(int *pfd, int timeout) { int nfds; struct pollfd pollfds[2] = { @@ -82,7 +216,7 @@ static int have_something_to_read(int *pfd, int timeout) return *pfd != -1; } -static int read_command_from_stdin(void) +int AgentRunner::read_command_from_stdin() { char buffer[64], *p, *save = NULL; @@ -106,7 +240,7 @@ static int read_command_from_stdin(void) return 1; } -static int read_command_from_device(void) +int AgentRunner::read_command_from_device() { StreamDevHeader hdr; uint8_t msg[64]; @@ -151,7 +285,7 @@ static int read_command_from_device(void) return 1; } -static int read_command(bool blocking) +int AgentRunner::read_command(bool blocking) { int fd, n=1; int timeout = blocking?-1:0; @@ -173,28 +307,8 @@ static int read_command(bool blocking) 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) +int AgentRunner::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); @@ -213,7 +327,7 @@ static int spice_stream_send_format(unsigned w, unsigned h, unsigned c) return EXIT_SUCCESS; } -static int spice_stream_send_frame(const void *buf, const unsigned size) +int AgentRunner::spice_stream_send_frame(const void *buf, const unsigned size) { SpiceStreamDataMessage msg; const size_t msgsize = sizeof(msg); @@ -244,7 +358,7 @@ static int spice_stream_send_frame(const void *buf, const unsigned size) } /* returns current time in micro-seconds */ -static uint64_t get_time(void) +uint64_t AgentRunner::get_time(void) { struct timeval now; @@ -254,116 +368,13 @@ static uint64_t get_time(void) } -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\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(unsigned width, unsigned height, int hotspot_x, int hotspot_y, - std::function<void(uint32_t *)> fill_cursor) -{ - if (width >= STREAM_MSG_CURSOR_SET_MAX_WIDTH || - height >= STREAM_MSG_CURSOR_SET_MAX_HEIGHT) - return; - - size_t cursor_size = - sizeof(StreamDevHeader) + sizeof(StreamMsgCursorSet) + - width * 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 = width; - cursor_msg.height = height; - cursor_msg.hot_spot_x = hotspot_x; - cursor_msg.hot_spot_y = hotspot_y; - - uint32_t *pixels = reinterpret_cast<uint32_t *>(cursor_msg.data); - fill_cursor(pixels); - - 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) +void AgentRunner::do_capture() { - 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; - auto fill_cursor = [&](uint32_t *pixels) { - for (unsigned i = 0; i < cursor->width * cursor->height; ++i) - pixels[i] = cursor->pixels[i]; - }; - send_cursor(cursor->width, cursor->height, cursor->xhot, cursor->yhot, fill_cursor); - } -} - -static void -do_capture(const string &streamport, FILE *f_log) -{ - streamfd = open(streamport.c_str(), O_RDWR); - if (streamfd < 0) - throw std::runtime_error("failed to open the streaming device (" + - streamport + "): " + strerror(errno)); - unsigned int frame_count = 0; while (! quit) { while (!quit && !streaming_requested) { if (read_command(true) < 0) { - syslog(LOG_ERR, "FAILED to read command\n"); - goto done; + throw std::runtime_error("FAILED to read command"); } } @@ -406,12 +417,12 @@ do_capture(const string &streamport, FILE *f_log) if (spice_stream_send_format(width, height, codec) == EXIT_FAILURE) throw std::runtime_error("FAILED to send format message"); } - if (f_log) { + if (log_file) { if (log_binary) { - fwrite(frame.buffer, frame.buffer_size, 1, f_log); + fwrite(frame.buffer, frame.buffer_size, 1, log_file); } else { - fprintf(f_log, "%lu: Frame of %zu bytes:\n", get_time(), frame.buffer_size); - hexdump(frame.buffer, frame.buffer_size, f_log); + fprintf(log_file, "%lu: Frame of %zu bytes:\n", get_time(), frame.buffer_size); + hexdump(frame.buffer, frame.buffer_size, log_file); } } if (spice_stream_send_frame(frame.buffer, frame.buffer_size) == EXIT_FAILURE) { @@ -420,118 +431,22 @@ do_capture(const string &streamport, FILE *f_log) } //usleep(1); if (read_command(false) < 0) { - syslog(LOG_ERR, "FAILED to read command\n"); - goto done; + throw std::runtime_error("FAILED to read command"); } } } +} -done: - if (streamfd >= 0) { - close(streamfd); - streamfd = -1; +void AgentRunner::add_options(const std::vector<std::pair<std::string, std::string>> &options) { + for (const auto& option : options) { + agent.AddOption(option.first.c_str(), option.second.c_str()); } } -#define arg_error(...) syslog(LOG_ERR, ## __VA_ARGS__); - -int main(int argc, char* argv[]) +void AgentRunner::run() { - string 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); + std::thread cursor_th(CursorThread(streamfd, stream_mtx)); 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; + do_capture(); } diff --git a/src/spice-streaming-agent.hpp b/src/spice-streaming-agent.hpp new file mode 100644 index 0000000..d6cbfbb --- /dev/null +++ b/src/spice-streaming-agent.hpp @@ -0,0 +1,56 @@ +/* An implementation of a SPICE streaming agent + * + * \copyright + * Copyright 2016-2018 Red Hat Inc. All rights reserved. + */ + +#ifndef SPICE_STREAMING_AGENT_SPICE_STREAMING_AGENT_HPP +#define SPICE_STREAMING_AGENT_SPICE_STREAMING_AGENT_HPP + +#include "concrete-agent.hpp" + +#include <cstdint> +#include <mutex> +#include <set> + + +namespace SpiceStreamingAgent { + +class AgentRunner +{ +public: + AgentRunner(const std::string &stream_port, + const std::string &log_filename, + bool log_binary, + bool stdin_ok); + ~AgentRunner(); + + void do_capture(); + void add_options(const std::vector<std::pair<std::string, std::string>> &options); + void run(); + + static bool quit; + +private: + int have_something_to_read(int *pfd, int timeout); + int read_command_from_stdin(); + int read_command_from_device(); + int read_command(bool blocking); + int spice_stream_send_format(unsigned w, unsigned h, unsigned c); + int spice_stream_send_frame(const void *buf, const unsigned size); + uint64_t get_time(void); + + std::string stream_port; + FILE *log_file = nullptr; + ConcreteAgent agent; + int streaming_requested = 0; + std::set<SpiceVideoCodecType> client_codecs; + int streamfd = -1; + bool log_binary = false; + bool stdin_ok = false; + std::mutex stream_mtx; +}; + +} // SpiceStreamingAgent + +#endif // SPICE_STREAMING_AGENT_SPICE_STREAMING_AGENT_HPP -- 2.16.1 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel