diff --git a/src/hyperion-x11/CMakeLists.txt b/src/hyperion-x11/CMakeLists.txt index 5b4fd821..43cc9a1d 100644 --- a/src/hyperion-x11/CMakeLists.txt +++ b/src/hyperion-x11/CMakeLists.txt @@ -10,19 +10,30 @@ find_package(Protobuf REQUIRED) # find Qt4 find_package(Qt4 REQUIRED QtCore QtGui QtNetwork) +# Find X11 +find_package(X11 REQUIRED) + include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${PROTOBUF_INCLUDE_DIRS} ${QT_INCLUDES} + ${X11_INCLUDES} ) +set(Hyperion_X11_QT_HEADERS + ProtoWrapper.h + X11Wrapper.h) + set(Hyperion_X11_HEADERS X11Grabber.h ../hyperion-v4l2/ProtoConnection.h ) set(Hyperion_X11_SOURCES + hyperion-x11.cpp + ProtoWrapper.cpp X11Grabber.cpp + X11Wrapper.cpp ../hyperion-v4l2/ProtoConnection.cpp ) @@ -30,15 +41,16 @@ set(Hyperion_X11_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/../../libsrc/protoserver/message.proto ) -protobuf_generate_cpp(Hyperion_X11_PROTO_SRCS Hyperion_X11_PROTO_HDRS - ${Hyperion_X11_PROTOS} -) +QT4_WRAP_CPP(Hyperion_X11_HEADERS_MOC ${Hyperion_X11_QT_HEADERS}) + +protobuf_generate_cpp(Hyperion_X11_PROTO_SRCS Hyperion_X11_PROTO_HDRS ${Hyperion_X11_PROTOS}) add_executable(hyperion-x11 ${Hyperion_X11_HEADERS} ${Hyperion_X11_SOURCES} ${Hyperion_X11_PROTO_SRCS} ${Hyperion_X11_PROTO_HDRS} + ${Hyperion_X11_HEADERS_MOC} ) target_link_libraries(hyperion-x11 @@ -46,6 +58,7 @@ target_link_libraries(hyperion-x11 blackborder hyperion-utils ${PROTOBUF_LIBRARIES} + ${X11_LIBRARIES} pthread ) diff --git a/src/hyperion-x11/ProtoWrapper.cpp b/src/hyperion-x11/ProtoWrapper.cpp new file mode 100644 index 00000000..89710257 --- /dev/null +++ b/src/hyperion-x11/ProtoWrapper.cpp @@ -0,0 +1,17 @@ + +// +#include "ProtoWrapper.h" + +ProtoWrapper::ProtoWrapper(const std::string & protoAddress, const bool skipReply) : + _priority(200), + _duration_ms(2000), + _connection(protoAddress) +{ + _connection.setSkipReply(skipReply); +} + +void ProtoWrapper::process(const Image & image) +{ + std::cout << "Attempt to send screenshot" << std::endl; + _connection.setImage(image, _priority, _duration_ms); +} diff --git a/src/hyperion-x11/ProtoWrapper.h b/src/hyperion-x11/ProtoWrapper.h new file mode 100644 index 00000000..4d184868 --- /dev/null +++ b/src/hyperion-x11/ProtoWrapper.h @@ -0,0 +1,23 @@ + +// QT includes +#include + + +#include "../hyperion-v4l2/ProtoConnection.h" + +class ProtoWrapper : public QObject +{ + Q_OBJECT +public: + ProtoWrapper(const std::string & protoAddress, const bool skipReply); + +public slots: + void process(const Image & image); + +private: + + int _priority; + int _duration_ms; + + ProtoConnection _connection; +}; diff --git a/src/hyperion-x11/X11Grabber.cpp b/src/hyperion-x11/X11Grabber.cpp index d1331743..cd6d0fda 100644 --- a/src/hyperion-x11/X11Grabber.cpp +++ b/src/hyperion-x11/X11Grabber.cpp @@ -1,14 +1,115 @@ +// STL includes +#include +#include -#include +// X11 includes +#include -int main(int argc, char ** argv) +// X11Grabber includes +#include "X11Grabber.h" + +X11Grabber::X11Grabber(const unsigned cropHorizontal, const unsigned cropVertical, const unsigned pixelDecimation) : + _pixelDecimation(pixelDecimation), + _cropWidth(cropHorizontal), + _cropHeight(cropVertical), + _x11Display(nullptr), + _screenWidth(0), + _screenHeight(0), + _image(0,0) { - Display * dspl; - Drawable drwbl; - int x = 0; - int y = 0; - int width = 0; - int height = 0; - - XImage * xImage = XGetImage(dspl, drwbl, x, y, width, height, AllPlanes, ZPixmap); + // empty +} + +X11Grabber::~X11Grabber() +{ + if (_x11Display != nullptr) + { + XCloseDisplay(_x11Display); + } +} + +int X11Grabber::open() +{ + const char * display_name = nullptr; + _x11Display = XOpenDisplay(display_name); + + if (_x11Display == nullptr) + { + std::cerr << "Failed to open the default X11-display" << std::endl; + return -1; + } + + return 0; +} + +Image & X11Grabber::grab() +{ + if (_x11Display == nullptr) + { + open(); + } + + updateScreenDimensions(); + + const int croppedWidth = _screenWidth - 2*_cropWidth; + const int croppedHeight = _screenHeight - 2*_cropHeight; + + // Capture the current screen + XImage * xImage = XGetImage(_x11Display, DefaultRootWindow(_x11Display), _cropWidth, _cropHeight, croppedWidth, croppedHeight, AllPlanes, ZPixmap); + if (xImage == nullptr) + { + std::cerr << "Grab failed" << std::endl; + return _image; + } + + // Copy the capture XImage to the local image (and apply required decimation) + ColorRgb * outputPtr = _image.memptr(); + for (int iY=(_pixelDecimation/2); iYred = uint8_t((pixel >> 16) & 0xff); + outputPtr->green = uint8_t((pixel >> 8) & 0xff); + outputPtr->blue = uint8_t((pixel >> 0) & 0xff); + + // Move to the next output pixel + ++outputPtr; + } + } + // Cleanup allocated resources of the X11 grab + XDestroyImage(xImage); + + return _image; +} + +int X11Grabber::updateScreenDimensions() +{ + XWindowAttributes window_attributes_return; + const Status status = XGetWindowAttributes(_x11Display, DefaultRootWindow(_x11Display), &window_attributes_return); + if (status == 0) + { + std::cerr << "Failed to obtain window attributes" << std::endl; + return -1; + } + + if (_screenWidth == unsigned(window_attributes_return.width) && _screenHeight == unsigned(window_attributes_return.height)) + { + // No update required + return 0; + } + std::cout << "Update of screen resolution: [" << _screenWidth << "x" << _screenHeight <<"] => "; + _screenWidth = window_attributes_return.width; + _screenHeight = window_attributes_return.height; + std::cout << "[" << _screenWidth << "x" << _screenHeight <<"]" << std::endl; + + // Update the size of the buffer used to transfer the screenshot + int width = (_screenWidth - 2 * _cropWidth + _pixelDecimation/2) / _pixelDecimation; + int height = (_screenHeight - 2 * _cropHeight + _pixelDecimation/2) / _pixelDecimation; + _image.resize(width, height); + + return 0; } diff --git a/src/hyperion-x11/X11Grabber.h b/src/hyperion-x11/X11Grabber.h index e69de29b..21d36c47 100644 --- a/src/hyperion-x11/X11Grabber.h +++ b/src/hyperion-x11/X11Grabber.h @@ -0,0 +1,37 @@ + +// Hyperion-utils includes +#include +#include + +// X11 includes +#include + +class X11Grabber +{ +public: + + X11Grabber(const unsigned cropHorizontal, const unsigned cropVertical, const unsigned pixelDecimation); + + virtual ~X11Grabber(); + + int open(); + + Image & grab(); + +private: + + const unsigned _pixelDecimation; + + const unsigned _cropWidth; + const unsigned _cropHeight; + + /// Reference to the X11 display (nullptr if not opened) + Display * _x11Display; + + unsigned _screenWidth; + unsigned _screenHeight; + + Image _image; + + int updateScreenDimensions(); +}; diff --git a/src/hyperion-x11/X11Wrapper.cpp b/src/hyperion-x11/X11Wrapper.cpp new file mode 100644 index 00000000..5bf4a97f --- /dev/null +++ b/src/hyperion-x11/X11Wrapper.cpp @@ -0,0 +1,33 @@ + +// Hyperion-X11 includes +#include "X11Wrapper.h" + +X11Wrapper::X11Wrapper(const unsigned cropHorizontal, const unsigned cropVertical, const unsigned pixelDecimation) : + _timer(this), + _grabber(cropHorizontal, cropVertical, pixelDecimation) +{ + // Connect capturing to the timeout signal of the timer + connect(&_timer, SIGNAL(timeout()), this, SLOT(capture())); +} + +const Image & X11Wrapper::getScreenshot() +{ + const Image & screenshot = _grabber.grab(); + return screenshot; +} + +void X11Wrapper::start() +{ + _timer.start(200); +} + +void X11Wrapper::stop() +{ + _timer.stop(); +} + +void X11Wrapper::capture() +{ + const Image & screenshot = _grabber.grab(); + emit sig_screenshot(screenshot); +} diff --git a/src/hyperion-x11/X11Wrapper.h b/src/hyperion-x11/X11Wrapper.h new file mode 100644 index 00000000..405cbce9 --- /dev/null +++ b/src/hyperion-x11/X11Wrapper.h @@ -0,0 +1,39 @@ + +// QT includes +#include + +// Hyperion-X11 includes +#include "X11Grabber.h" + +class X11Wrapper : public QObject +{ + Q_OBJECT +public: + X11Wrapper(const unsigned cropHorizontal, const unsigned cropVertical, const unsigned pixelDecimation); + + const Image & getScreenshot(); + + /// + /// Starts the timed capturing of screenshots + /// + void start(); + + void stop(); + +signals: + void sig_screenshot(const Image & screenshot); + +private slots: + /// + /// Performs a single screenshot capture and publishes the capture screenshot on the screenshot + /// signal. + /// + void capture(); + +private: + /// The QT timer to generate capture-publish events + QTimer _timer; + + /// The grabber for creating screenshots + X11Grabber _grabber; +}; diff --git a/src/hyperion-x11/hyperion-x11.cpp b/src/hyperion-x11/hyperion-x11.cpp new file mode 100644 index 00000000..ef9b5ab4 --- /dev/null +++ b/src/hyperion-x11/hyperion-x11.cpp @@ -0,0 +1,90 @@ + +// QT includes +#include +#include + +// getoptPlusPLus includes +#include + +#include "ProtoWrapper.h" +#include "X11Wrapper.h" + +using namespace vlofgren; + +// save the image as screenshot +void saveScreenshot(const char * filename, const Image & image) +{ + // store as PNG + QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); + pngImage.save(filename); +} + +int main(int argc, char ** argv) +{ + QCoreApplication app(argc, argv); + + try + { + // create the option parser and initialize all parameters + OptionsParser optionParser("X11 capture application for Hyperion"); + ParameterSet & parameters = optionParser.getParameters(); + + IntParameter & argCropWidth = parameters.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides in the picture before decimation [default=0]"); + IntParameter & argCropHeight = parameters.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom in the picture before decimation [default=0]"); + IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=1]"); + SwitchParameter<> & argScreenshot = parameters.add> (0x0, "screenshot", "Take a single screenshot, save it to file and quit"); + StringParameter & argAddress = parameters.add ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]"); + IntParameter & argPriority = parameters.add ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]"); + SwitchParameter<> & argSkipReply = parameters.add> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion"); + SwitchParameter<> & argHelp = parameters.add> ('h', "help", "Show this help message and exit"); + + // set defaults + argCropWidth.setDefault(0); + argCropHeight.setDefault(0); + argSizeDecimation.setDefault(1); + argAddress.setDefault("127.0.0.1:19445"); + argPriority.setDefault(800); + + // parse all options + optionParser.parse(argc, const_cast(argv)); + + // check if we need to display the usage. exit if we do. + if (argHelp.isSet()) + { + optionParser.usage(); + return 0; + } + + // Create the X11 grabbing stuff + X11Wrapper x11Wrapper(argCropWidth.getValue(), argCropHeight.getValue(), argSizeDecimation.getValue()); + + if (argScreenshot.isSet()) + { + // Capture a single screenshot and finish + const Image & screenshot = x11Wrapper.getScreenshot(); + saveScreenshot("screenshot.png", screenshot); + } + else + { + // Create the Proto-connection with hyperiond + ProtoWrapper protoWrapper("127.0.0.1:19445", argSkipReply.isSet()); + + // Connect the screen capturing to the proto processing + QObject::connect(&x11Wrapper, SIGNAL(sig_screenshot(const Image &)), &protoWrapper, SLOT(process(Image))); + + // Start the capturing + x11Wrapper.start(); + + // Start the application + app.exec(); + } + } + catch (const std::runtime_error & e) + { + // An error occured. Display error and quit + std::cerr << e.what() << std::endl; + return -1; + } + + return 0; +}