From 4aa7578cf7c8719869be30a7a8f554a9a7cd8f9d Mon Sep 17 00:00:00 2001 From: kevin Date: Sat, 22 Jun 2019 22:34:40 -0400 Subject: [PATCH] Adding files --- CMakeLists.txt | 17 ++++ hgx11.cpp | 68 ++++++++++++++++ hgx11.h | 41 ++++++++++ hgx11damage.cpp | 34 ++++++++ hgx11damage.h | 34 ++++++++ hgx11grab.cpp | 206 ++++++++++++++++++++++++++++++++++++++++++++++++ hgx11grab.h | 61 ++++++++++++++ hgx11net.cpp | 86 ++++++++++++++++++++ hgx11net.h | 39 +++++++++ main.cpp | 67 ++++++++++++++++ 10 files changed, 653 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 hgx11.cpp create mode 100644 hgx11.h create mode 100644 hgx11damage.cpp create mode 100644 hgx11damage.h create mode 100644 hgx11grab.cpp create mode 100644 hgx11grab.h create mode 100644 hgx11net.cpp create mode 100644 hgx11net.h create mode 100644 main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7964242 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.0.0) + +project(Hyperion_Grabber_X11_QT VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_BUILD_TYPE Debug) + +find_package(Qt5Core REQUIRED) +find_package(Qt5Network REQUIRED) +find_package(Qt5Widgets REQUIRED) +find_package(X11 REQUIRED) + +set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lXrender -lXdamage") +include_directories(${X11_INCLUDE_DIR}) +add_executable(${PROJECT_NAME} "main.cpp" "hgx11.h" "hgx11.cpp" "hgx11net.h" "hgx11net.cpp" "hgx11damage.h" "hgx11damage.cpp" "hgx11grab.h" "hgx11grab.cpp") +target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Network Qt5::Widgets ${X11_LIBRARIES}) diff --git a/hgx11.cpp b/hgx11.cpp new file mode 100644 index 0000000..efc360d --- /dev/null +++ b/hgx11.cpp @@ -0,0 +1,68 @@ +#include "hgx11.h" + +// public + +hgx11::hgx11(QString addr, QString port, QString scale, QString frameskip, QString inactiveTime) +{ + _addr_m = (addr.isEmpty() || addr.isNull()) ? "localhost" : addr; + _port_m = (port.toInt() > 65535 || port.toInt() < 1) ? "19444" : port; + _scale_m = (scale.toInt() < 1 || scale.toInt() > 1000) ? "4" : scale; + _frameskip_m = (frameskip.toInt() > 0 && frameskip.toInt() < 256) ? frameskip : "0"; + _inactiveTime_m = (inactiveTime.toInt() > 0 ? QString::number(inactiveTime.toInt() * 1000) : "0"); + + _grabber_p = new hgx11grab(_scale_m.toInt(), _frameskip_m.toUShort()); + _hclient_p = new hgx11net(_addr_m.toStdString().c_str(), _port_m.toUShort()); + _damage_p = new hgx11damage(); + + _hclient_p->imgWidth = QString::number(_grabber_p->getDest_width()); + _hclient_p->imgHeight = QString::number(_grabber_p->getDest_height()); + + _damage_p->start(); + + connect(_damage_p, SIGNAL(damageDetected()), _grabber_p, SLOT(grabFrame())); + connect(_grabber_p, SIGNAL(scaleChanged()), this, SLOT(_setImgSize())); + connect(_grabber_p, SIGNAL(imageCreated()), this, SLOT(_sendImage())); + + if (_inactiveTime_m.toInt()) { + _timer_p = new QTimer(this); + connect(_timer_p, SIGNAL(timeout()), this, SLOT(_inActivity())); + _timer_p->start(_inactiveTime_m.toInt()); + connect(_damage_p, SIGNAL(damageDetected()), this, SLOT(_activity())); + } +} + +hgx11::~hgx11() +{ + delete _hclient_p; + _hclient_p = nullptr; + _damage_p->terminate(); + while (!_damage_p->isFinished()) {} + delete _damage_p; + _damage_p = nullptr; + delete _grabber_p; + _grabber_p = nullptr; +} + +// private slots + +void hgx11::_sendImage() +{ + _hclient_p->sendImage(&_grabber_p->imgdata_m); +} + +void hgx11::_inActivity() +{ + _hclient_p->clearLeds(); +} + +void hgx11::_activity() +{ + _timer_p->stop(); + _timer_p->start(_inactiveTime_m.toInt()); +} + +void hgx11::_setImgSize() +{ + _hclient_p->imgWidth = QString::number(_grabber_p->getDest_width()); + _hclient_p->imgHeight = QString::number(_grabber_p->getDest_height()); +} diff --git a/hgx11.h b/hgx11.h new file mode 100644 index 0000000..86a777c --- /dev/null +++ b/hgx11.h @@ -0,0 +1,41 @@ +#ifndef HGX11_H +#define HGX11_H + +#include +#include +#include +#include "hgx11grab.h" +#include "hgx11net.h" +#include "hgx11damage.h" + + +class hgx11 : public QObject +{ + Q_OBJECT +public: + hgx11(QString, QString, QString, QString, QString); + ~hgx11(); + +private: + hgx11damage *_damage_p; + hgx11grab *_grabber_p; + hgx11net *_hclient_p; + QTimer *_timer_p; + + QString _addr_m; + QString _port_m; + QString _scale_m; + QString _frameskip_m; + QString _inactiveTime_m; + QString _destWidth_m; + QString _destHeight_m; + +private slots: + void _sendImage(); + void _inActivity(); + void _activity(); + void _setImgSize(); + +}; + +#endif // HGX11_H diff --git a/hgx11damage.cpp b/hgx11damage.cpp new file mode 100644 index 0000000..27f4771 --- /dev/null +++ b/hgx11damage.cpp @@ -0,0 +1,34 @@ +#include "hgx11damage.h" + +// public + +hgx11damage::hgx11damage() +{ + _display_p = XOpenDisplay(nullptr); + if(!_display_p){ + qCritical() << "failed to open x display"; + return; + } + Window window = DefaultRootWindow(_display_p); + _damage_m = XDamageCreate(_display_p, window, XDamageReportNonEmpty); + +} + +hgx11damage::~hgx11damage() +{ + _loop_m = false; +} + +// protected + +void hgx11damage::_monitor() +{ + if(!_display_p){ + return; + } + while (_loop_m) { + XNextEvent(_display_p, &_event_m); + emit damageDetected(); + XDamageSubtract(_display_p ,_damage_m, None, None); + } +} diff --git a/hgx11damage.h b/hgx11damage.h new file mode 100644 index 0000000..a5ba021 --- /dev/null +++ b/hgx11damage.h @@ -0,0 +1,34 @@ +#ifndef HGX11DAMAGE_H +#define HGX11DAMAGE_H + +#include +#include +#include +#include + +class hgx11damage : public QThread +{ + Q_OBJECT +public: + hgx11damage(); + ~hgx11damage(); + +private: + Display *_display_p; + XEvent _event_m; + Damage _damage_m; + bool _loop_m = true; + + void _monitor(); + +protected: + void run() { + _monitor(); + } + +signals: + void damageDetected(); + +}; + +#endif // HGX11DAMAGE_H diff --git a/hgx11grab.cpp b/hgx11grab.cpp new file mode 100644 index 0000000..14cb523 --- /dev/null +++ b/hgx11grab.cpp @@ -0,0 +1,206 @@ +#include "hgx11grab.h" + +// public + +hgx11grab::hgx11grab(int scaleDivisor, ushort frameSkip) +{ + _frameSkip_m = frameSkip; + _x11Display_p = XOpenDisplay(":0"); + if (_x11Display_p == nullptr) { + qCritical() << "Failed to open X11 display."; + return; + } + + _window_m = DefaultRootWindow(_x11Display_p); + + bool _XShmAvailable = XShmQueryExtension(_x11Display_p); + if (!_XShmAvailable) { + qCritical() << "Xshm is: not available."; + return; + } + + int dummy, pixmaps_supported; + + bool _XRenderAvailable = XRenderQueryExtension(_x11Display_p, &dummy, &dummy); + if (!_XRenderAvailable) { + qCritical() << "XRender is not available."; + return; + } + + XShmQueryVersion(_x11Display_p, &dummy, &dummy, &pixmaps_supported); + bool _XShmPixmapAvailable = pixmaps_supported && XShmPixmapFormat(_x11Display_p) == ZPixmap; + if (!_XShmPixmapAvailable) { + qCritical() << "XshmPixMap is: not available."; + return; + } + + if (!_getWinAttr()) { + return; + } + + _destWidth_m = int(_srcWidth_m / scaleDivisor); + _destHeight_m = int(_srcHeight_m / scaleDivisor); + + _imgSize_m = _destHeight_m * _destWidth_m * 4; + + memset(&_pictAttr_m, 0, sizeof(_pictAttr_m)); + _pictAttr_m.repeat = RepeatNone; + + _mTransform_m.matrix[0][0] = XDoubleToFixed(1.0); + _mTransform_m.matrix[0][1] = 0; + _mTransform_m.matrix[0][2] = 0; + _mTransform_m.matrix[1][0] = 0; + _mTransform_m.matrix[1][1] = XDoubleToFixed(1.0); + _mTransform_m.matrix[1][2] = 0; + _mTransform_m.matrix[2][0] = 0; + _mTransform_m.matrix[2][1] = 0; + _setScale(); + + connect(this, SIGNAL(scaleChanged()), this, SLOT(_changeScale())); +} + +hgx11grab::~hgx11grab() +{ + if (_x11Display_p != nullptr) { + _freeResources(); + XCloseDisplay(_x11Display_p); + } +} + +int hgx11grab::getDest_height() const +{ + return _destHeight_m; +} + +int hgx11grab::getDest_width() const +{ + return _destWidth_m; +} + +// private + +void hgx11grab::_grabFrame() +{ + if (!_getWinAttr()) { + return; + } + + _xImage_p = XShmCreateImage( + _x11Display_p, _windowAttr_m.visual, + uint(_windowAttr_m.depth), ZPixmap, nullptr, &_shminfo_m, + uint(_destWidth_m), uint(_destHeight_m) + ); + + _shminfo_m.shmid = shmget(IPC_PRIVATE, ulong(_xImage_p->bytes_per_line * _xImage_p->height), IPC_CREAT|0777); + _xImage_p->data = reinterpret_cast(shmat(_shminfo_m.shmid, nullptr, 0)); + _shminfo_m.shmaddr = _xImage_p->data; + _shminfo_m.readOnly = false; + + XShmAttach(_x11Display_p, &_shminfo_m); + _pixmap_m = XShmCreatePixmap(_x11Display_p, _window_m, _xImage_p->data, &_shminfo_m, uint(_destWidth_m), uint(_destHeight_m), uint(_windowAttr_m.depth)); + + _dstFormat_p = XRenderFindVisualFormat(_x11Display_p, _windowAttr_m.visual); + _srcFormat_p = XRenderFindVisualFormat(_x11Display_p, _windowAttr_m.visual); + + _srcPicture_m = XRenderCreatePicture(_x11Display_p, _window_m, _srcFormat_p, CPRepeat, &_pictAttr_m); + _dstPicture_m = XRenderCreatePicture(_x11Display_p, _pixmap_m, _dstFormat_p, CPRepeat, &_pictAttr_m); + + XRenderSetPictureFilter(_x11Display_p, _srcPicture_m, "bilinear", nullptr, 0); + XRenderSetPictureTransform (_x11Display_p, _srcPicture_m, &_mTransform_m); + + XRenderComposite( + _x11Display_p, // *dpy, + PictOpSrc, // op, + _srcPicture_m, // src + None, // mask + _dstPicture_m, // dst + 0, // src_x + 0, // src_y + 0, // mask_x + 0, // mask_y + 0, // dst_x + 0, // dst_y + uint(_destWidth_m), // width + uint(_destHeight_m) // height + ); + + XSync(_x11Display_p, false); + + XShmGetImage(_x11Display_p, _pixmap_m, _xImage_p, 0, 0, 0x00FFFFFF); + + if (_xImage_p == nullptr) { + qWarning() << "Failed to get image from X11 server."; + return; + } + + imgdata_m.clear(); + + if (_xImage_p->byte_order == 0) { // BGR + for (int i = 0; i < _imgSize_m; i += 4) { + imgdata_m.append(_xImage_p->data[i+2]); + imgdata_m.append(_xImage_p->data[i+1]); + imgdata_m.append(_xImage_p->data[i]); + } + } else { // RGB + for (int i = 0; i < _imgSize_m; i += 4) { + imgdata_m.append(_xImage_p->data[i]); + imgdata_m.append(_xImage_p->data[i+1]); + imgdata_m.append(_xImage_p->data[i+2]); + } + } + + imageCreated(); +} + +bool hgx11grab::_getWinAttr() +{ + if (XGetWindowAttributes(_x11Display_p, _window_m, &_windowAttr_m) == 0) { + qWarning() << "Failed to obtain X11 window attributes."; + return false; + } + if (_srcWidth_m != _windowAttr_m.width || _srcHeight_m != _windowAttr_m.height) { + if (0 != _srcWidth_m) { + _freeResources(); + } + _srcWidth_m = _windowAttr_m.width; + _srcHeight_m = _windowAttr_m.height; + emit scaleChanged(); + } + return true; +} + +void hgx11grab::_freeResources() +{ + XDestroyImage(_xImage_p); + XShmDetach(_x11Display_p, &_shminfo_m); + shmdt(_shminfo_m.shmaddr); + shmctl(_shminfo_m.shmid, IPC_RMID, nullptr); + XRenderFreePicture(_x11Display_p, _srcPicture_m); + XRenderFreePicture(_x11Display_p, _dstPicture_m); + XFreePixmap(_x11Display_p, _pixmap_m); +} + +void hgx11grab::_setScale() +{ + _scale_m = (1.0 / (double(_srcWidth_m) / double(_destWidth_m))); + _mTransform_m.matrix[2][2] = XDoubleToFixed(_scale_m); +} + +// public slots + +void hgx11grab::grabFrame() +{ + if (_frameCount_m++ < _frameSkip_m) { + return; + } + _frameCount_m = 0; + _grabFrame(); +} + +// private slots + +void hgx11grab::_changeScale() +{ + _imgSize_m = _destHeight_m * _destWidth_m * 4; + _setScale(); +} diff --git a/hgx11grab.h b/hgx11grab.h new file mode 100644 index 0000000..7891c0d --- /dev/null +++ b/hgx11grab.h @@ -0,0 +1,61 @@ +#ifndef HGX11GRAB_H +#define HGX11GRAB_H + +#include +#include +#include +#include +#include +#include + +class hgx11grab : public QObject +{ + Q_OBJECT +public: + QByteArray imgdata_m; + + hgx11grab(int, ushort); + ~hgx11grab(); + + int getDest_width() const; + int getDest_height() const; + +private: + Display *_x11Display_p; + Window _window_m; + Picture _srcPicture_m; + Picture _dstPicture_m; + Pixmap _pixmap_m; + XImage *_xImage_p; + XRenderPictFormat *_srcFormat_p; + XRenderPictFormat *_dstFormat_p; + XRenderPictureAttributes _pictAttr_m; + XShmSegmentInfo _shminfo_m; + XTransform _mTransform_m; + XWindowAttributes _windowAttr_m; + int _srcWidth_m = 0; + int _srcHeight_m = 0; + int _destWidth_m; + int _destHeight_m; + int _imgSize_m; + double _scale_m; + ushort _frameCount_m = 255; + ushort _frameSkip_m = 0; + + void _grabFrame(); + bool _getWinAttr(); + void _freeResources(); + void _setScale(); + +signals: + void imageCreated(); + void scaleChanged(); + +public slots: + void grabFrame(); + +private slots: + void _changeScale(); +}; + +#endif // HGX11GRAB_H diff --git a/hgx11net.cpp b/hgx11net.cpp new file mode 100644 index 0000000..0c2779a --- /dev/null +++ b/hgx11net.cpp @@ -0,0 +1,86 @@ +#include "hgx11net.h" + +// public + +hgx11net::hgx11net(const char *host, ushort port) +{ + _sock_p = new QTcpSocket(this); + connect(_sock_p, SIGNAL(disconnected()), this, SLOT(_disconnected())); + _host_m = host; + _port_m = port; + this->_connectHost(); +} + +hgx11net::~hgx11net() +{ + clearLeds(); + _sock_p->disconnectFromHost(); +} + +void hgx11net::clearLeds() +{ + if (!_isConnected()) { + return; + } + _sock_p->write("{\"command\": \"clearall\"}\n"); +} + +void hgx11net::setLedColor(quint8 R, quint8 G, quint8 B) +{ + if (!_isConnected()) { + return; + } + _cmd_m.clear(); + _cmd_m.append("{\"color\":["); + _cmd_m.append(QString::number(R)); + _cmd_m.append(","); + _cmd_m.append(QString::number(G)); + _cmd_m.append(","); + _cmd_m.append(QString::number(B)); + _cmd_m.append("],\"command\":\"color\",\"priority\":100}}\n"); + _sock_p->write(_cmd_m); +} + +// private + +bool hgx11net::_isConnected() +{ + return (_sock_p->state() == QAbstractSocket::ConnectedState); +} + +void hgx11net::_connectHost() +{ + _sock_p->connectToHost(_host_m, _port_m); + if(!_sock_p->waitForConnected(5000)) { + qWarning() << "hxgfnet Error: " << _sock_p->errorString(); + return; + } +} + +// public slots + +void hgx11net::sendImage(QByteArray *imgdata) +{ + if (!_isConnected()) { + return; + } + _cmd_m.clear(); + _cmd_m.append("{\"command\":\"image\",\"imagedata\":\""); + _cmd_m.append(imgdata->toBase64()); + _cmd_m.append("\",\"imageheight\":"); + _cmd_m.append(imgHeight); + _cmd_m.append(",\"imagewidth\":"); + _cmd_m.append(imgWidth); + _cmd_m.append(",\"priority\":100}\n"); + _sock_p->write(_cmd_m); +} + +// private slots + +void hgx11net::_disconnected() +{ + while (!_isConnected()) { + _connectHost(); + } +} + diff --git a/hgx11net.h b/hgx11net.h new file mode 100644 index 0000000..2cce20f --- /dev/null +++ b/hgx11net.h @@ -0,0 +1,39 @@ +#ifndef HGX11NET_H +#define HGX11NET_H + +#include +#include +#include +#include +#include + +class hgx11net : public QObject +{ + Q_OBJECT +public: + QString imgWidth; + QString imgHeight; + + hgx11net(const char *, ushort); + ~hgx11net(); + + void clearLeds(); + void setLedColor(quint8, quint8, quint8); + +private: + QTcpSocket *_sock_p; + QByteArray _cmd_m; + const char *_host_m; + quint16 _port_m; + + void _connectHost(); + bool _isConnected(); + +public slots: + void sendImage(QByteArray *); + +private slots: + void _disconnected(); +}; + +#endif // HGX11NET_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..d2e06de --- /dev/null +++ b/main.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include "hgx11.h" + +static hgx11 *grab; +static QCoreApplication *qapp; + +static void quit(int sig) +{ + grab->~hgx11(); + qapp->exit(sig); +} + +int main(int argc, char *argv[]) +{ + qapp = new QCoreApplication(argc, argv); + signal(SIGINT, quit); + signal(SIGTERM, quit); + + QCoreApplication::setApplicationName("hgx11"); + QCoreApplication::setApplicationVersion("0.1"); + QCommandLineParser parser; + parser.setApplicationDescription("X11 grabber for Hyperion using QT"); + parser.addHelpOption(); + parser.addVersionOption(); + QCommandLineOption address(QStringList() << "a" << "address", + QCoreApplication::translate("main", "Address to the hyperion json server. (ex. 127.0.0.1)"), + QCoreApplication::translate("main", "hyperion address") + ); + parser.addOption(address); + QCommandLineOption port(QStringList() << "p" << "port", + QCoreApplication::translate("main", "Port for the hyperion json server. (ex. 19444)"), + QCoreApplication::translate("main", "hyperion port") + ); + parser.addOption(port); + QCommandLineOption scale(QStringList() << "s" << "scale", + QCoreApplication::translate("main", "Divisor used to scale your screen resolution (ex. 8 ; if your screen is 1920x1080, the result image sent to hyperion is 240x135"), + QCoreApplication::translate("main", "scale dividor") + ); + parser.addOption(scale); + QCommandLineOption frameskip(QStringList() << "f" << "frameskip", + QCoreApplication::translate("main", "How many X11 frames to skip over. (ex. 4 ; 1 frame will be scaled and sent to hyperion and 4 will be ignored)"), + QCoreApplication::translate("main", "X11 frames to skip") + ); + parser.addOption(frameskip); + QCommandLineOption inactiveTime(QStringList() << "i" << "inactive", + QCoreApplication::translate("main", "How many seconds after the screen has not changed to turn off the LED's. Set to 0 to disable."), + QCoreApplication::translate("main", "seconds before turning off LEDs") + ); + parser.addOption(inactiveTime); + parser.process(*qapp); + + if (!parser.isSet("address")) { + qWarning() << "Hyperion address missing."; + parser.showHelp(); + } + + if (!parser.isSet("port")) { + qWarning() << "Hyperion port missing."; + parser.showHelp(); + } + + grab = new hgx11(parser.value("address"), parser.value("port"), parser.value("scale"), parser.value("frameskip"), parser.value("inactive")); + return qapp->exec(); +} +