diff --git a/CMakeLists.txt b/CMakeLists.txt index 1aa20fa..cbef61b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ find_package(Qt5Network REQUIRED) find_package(Qt5Widgets REQUIRED) find_package(X11 REQUIRED) -set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lXrender -lXdamage") +set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lXrender -lXdamage -lXss") 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") +add_executable(${PROJECT_NAME} "main.cpp" "hgx11.h" "hgx11.cpp" "hgx11net.h" "hgx11net.cpp" "hgx11damage.h" "hgx11damage.cpp" "hgx11grab.h" "hgx11grab.cpp" "hgx11screensaver.h" "hgx11screensaver.cpp") target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Network Qt5::Widgets ${X11_LIBRARIES}) diff --git a/README.md b/README.md index 4e41868..76bc0fe 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,12 @@ to start it run `systemctl --user enable hgx11.service`. ## Info -The -i option can turn off the Hyperion LED's after configurable amount of time the X11 display has not changed. +The -i option can turn off the Hyperion LED's after a configurable amount of time. + +The -j option determines how to know when to turn off the Hyperion LED's after user inactivity, 0 (Default) uses Xdamage which detects screen changes while 1 will use Xscreensaver, which detects user input (mouse / keyboard /etc). +Xdamage should be superior, the disadvantage is if you leave a window open with anything that changes (like a console with a blinking cursor), the LED's will stay on. +Xscreensaver has the disadvantage that if a program doesn't tell it there is activity (like if you play a video for example), the LED's will turn off. +Ideally there would be a way to determine if the screen is inactive if a video is not playing, maybe someone knows? The -f option will skip grabbing frames, it can be used to limit the amount of images sent to hyperion. Since the images sent to hyperion is based on the amount of X11 display activity, if this option is set high and not much is going on, there may be a long delay between when images are sent to Hyperion, this option is useful if you're watching videos for example and want to reduce the CPU usage of the grabbing. diff --git a/hgx11.cpp b/hgx11.cpp index 85ffdf8..bab8926 100644 --- a/hgx11.cpp +++ b/hgx11.cpp @@ -23,6 +23,8 @@ hgx11::hgx11(QHash opts) frameskip = i.value().toUShort(); } else if ((i.key() == "i" || i.key() == "inactive") && i.value().toInt()) { _inactiveTime_m = (i.value().toInt() * 1000); + } else if ((i.key() == "j" || i.key() == "inactivetype") && i.value().toInt() == 1) { + _inactiveXss_m = true; } else if (i.key() == "r" || i.key() == "redadjust") { redAdjust = _parseColorArr(i.value(), 1); } else if (i.key() == "g" || i.key() == "greenadjust") { @@ -33,16 +35,24 @@ hgx11::hgx11(QHash opts) temperature = _parseColorArr(i.value(), 1); } else if (i.key() == "d" || i.key() == "threshold") { threshold = _parseColorArr(i.value(), 0); - }else if ((i.key() == "l" || i.key() == "transform") && _parseColorArr(i.value(), 0) != "") { + } else if ((i.key() == "l" || i.key() == "transform") && _parseColorArr(i.value(), 0) != "") { transform = i.value(); } } - _grabber_p = new hgx11grab(scale); + _display_p = XOpenDisplay(nullptr); + if(_display_p == nullptr){ + qCritical() << "Failed to open X11 display"; + return; + } + _grabber_p = new hgx11grab(_display_p, scale); _hclient_p = new hgx11net(addr, port); - _damage_p = new hgx11damage(frameskip); + if (_inactiveXss_m) { + _screensaver_p = new hgx11screensaver(_display_p); + _inactiveTimeXss_m = ulong(_inactiveTime_m); + } _setImgSize(); _hclient_p->ledAdjustments(redAdjust, greenAdjust, blueAdjust, temperature, threshold, transform); @@ -52,25 +62,44 @@ hgx11::hgx11(QHash opts) 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())); + _grabActive_m = true; if (_inactiveTime_m) { _timer_p = new QTimer(this); - connect(_timer_p, SIGNAL(timeout()), this, SLOT(_inActivity())); - _timer_p->start(_inactiveTime_m); - connect(_damage_p, SIGNAL(damageDetected()), this, SLOT(_activity())); + if (!_inactiveXss_m) { + connect(_timer_p, SIGNAL(timeout()), this, SLOT(_inActivity())); + connect(_damage_p, SIGNAL(damageDetected()), this, SLOT(_activity())); + _timer_p->start(_inactiveTime_m); + } else { + connect(_timer_p, SIGNAL(timeout()), this, SLOT(_checkXss())); + _timer_p->start(1000); + } } } hgx11::~hgx11() { + _timer_p->stop(); + disconnect(_damage_p, SIGNAL(damageDetected()), _grabber_p, SLOT(grabFrame())); + disconnect(_grabber_p, SIGNAL(scaleChanged()), this, SLOT(_setImgSize())); + disconnect(_grabber_p, SIGNAL(imageCreated()), this, SLOT(_sendImage())); + if (!_inactiveXss_m) { + disconnect(_timer_p, SIGNAL(timeout()), this, SLOT(_inActivity())); + disconnect(_damage_p, SIGNAL(damageDetected()), this, SLOT(_activity())); + + } else { + disconnect(_timer_p, SIGNAL(timeout()), this, SLOT(_checkXss())); + delete _screensaver_p; + } + if (_display_p != nullptr) { + XCloseDisplay(_display_p); + } 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; + delete _timer_p; } // private @@ -124,3 +153,19 @@ void hgx11::_setImgSize() _hclient_p->imgCmdBuf.append(QString::number(_grabber_p->getDest_width())); _hclient_p->imgCmdBuf.append(",\"imagedata\":\""); } + +void hgx11::_checkXss() +{ + if (_screensaver_p->lastX11ActivitySeconds() < _inactiveTimeXss_m) { + if (!_grabActive_m) { + connect(_damage_p, SIGNAL(damageDetected()), _grabber_p, SLOT(grabFrame())); + _grabActive_m = true; + } + return; + } + if (_grabActive_m) { + disconnect(_damage_p, SIGNAL(damageDetected()), _grabber_p, SLOT(grabFrame())); + _grabActive_m = false; + _inActivity(); + } +} diff --git a/hgx11.h b/hgx11.h index dddf3ec..2419a61 100644 --- a/hgx11.h +++ b/hgx11.h @@ -4,10 +4,11 @@ #include #include #include + #include "hgx11grab.h" #include "hgx11net.h" #include "hgx11damage.h" - +#include "hgx11screensaver.h" class hgx11 : public QObject { @@ -17,12 +18,17 @@ public: ~hgx11(); private: + Display *_display_p; hgx11damage *_damage_p; hgx11grab *_grabber_p; hgx11net *_hclient_p; + hgx11screensaver *_screensaver_p; QTimer *_timer_p; int _inactiveTime_m = 0; + unsigned long _inactiveTimeXss_m = 0; + bool _inactiveXss_m = false; + bool _grabActive_m; QString _parseColorArr(QString, bool); @@ -31,6 +37,7 @@ private slots: void _inActivity(); void _activity(); void _setImgSize(); + void _checkXss(); }; #endif // HGX11_H diff --git a/hgx11damage.cpp b/hgx11damage.cpp index 4e5ee17..4518eda 100644 --- a/hgx11damage.cpp +++ b/hgx11damage.cpp @@ -6,25 +6,26 @@ hgx11damage::hgx11damage(unsigned short frameSkip) { _frameSkip_m = frameSkip; _display_p = XOpenDisplay(nullptr); - if(!_display_p){ - qCritical() << "failed to open x display"; + if(_display_p == nullptr){ + qCritical() << "Failed to open X11 display"; return; } - Window window = DefaultRootWindow(_display_p); - _damage_m = XDamageCreate(_display_p, window, XDamageReportNonEmpty); - + _damage_m = XDamageCreate(_display_p, DefaultRootWindow(_display_p), XDamageReportNonEmpty); } hgx11damage::~hgx11damage() { _loop_m = false; + if (_damage_m) { + XDamageDestroy(_display_p, _damage_m); + } } // protected void hgx11damage::_monitor() { - if(!_display_p){ + if(_display_p == nullptr){ return; } while (_loop_m) { diff --git a/hgx11grab.cpp b/hgx11grab.cpp index f60cbbb..9cf440f 100644 --- a/hgx11grab.cpp +++ b/hgx11grab.cpp @@ -2,39 +2,35 @@ // public -hgx11grab::hgx11grab(unsigned short scaleDivisor) +hgx11grab::hgx11grab(Display *display, unsigned short scaleDivisor) { - _x11Display_p = XOpenDisplay(nullptr); - if (_x11Display_p == nullptr) { - qCritical() << "Failed to open X11 display."; - return; - } + _display_p = display; - _window_m = DefaultRootWindow(_x11Display_p); - - if (!XShmQueryExtension(_x11Display_p)) { + if (!XShmQueryExtension(_display_p)) { qCritical() << "Xshm is: not available."; return; } int dummy, pixmaps_supported; - if (!XRenderQueryExtension(_x11Display_p, &dummy, &dummy)) { + if (!XRenderQueryExtension(_display_p, &dummy, &dummy)) { qCritical() << "XRender is not available."; return; } - if (!XShmQueryVersion(_x11Display_p, &dummy, &dummy, &pixmaps_supported)) { + if (!XShmQueryVersion(_display_p, &dummy, &dummy, &pixmaps_supported)) { qCritical("Could not get x shared memory version."); return; } - bool _XShmPixmapAvailable = pixmaps_supported && XShmPixmapFormat(_x11Display_p) == ZPixmap; + bool _XShmPixmapAvailable = pixmaps_supported && XShmPixmapFormat(_display_p) == ZPixmap; if (!_XShmPixmapAvailable) { qCritical() << "XshmPixMap is: not available."; return; } + _window_m = DefaultRootWindow(_display_p); + if (!_getWinAttr()) { return; } @@ -62,9 +58,8 @@ hgx11grab::hgx11grab(unsigned short scaleDivisor) hgx11grab::~hgx11grab() { - if (_x11Display_p != nullptr) { + if (_display_p != nullptr) { _freeResources(); - XCloseDisplay(_x11Display_p); } } @@ -82,7 +77,7 @@ int hgx11grab::getDest_width() const bool hgx11grab::_getWinAttr() { - if (XGetWindowAttributes(_x11Display_p, _window_m, &_windowAttr_m) == 0) { + if (XGetWindowAttributes(_display_p, _window_m, &_windowAttr_m) == 0) { qWarning() << "Failed to obtain X11 window attributes."; return false; } @@ -103,12 +98,12 @@ void hgx11grab::_freeResources() return; } XDestroyImage(_xImage_p); - XShmDetach(_x11Display_p, &_shminfo_m); + XShmDetach(_display_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); + XRenderFreePicture(_display_p, _srcPicture_m); + XRenderFreePicture(_display_p, _dstPicture_m); + XFreePixmap(_display_p, _pixmap_m); _freed_m = 1; } @@ -128,7 +123,7 @@ void hgx11grab::grabFrame() _freed_m = 0; _xImage_p = XShmCreateImage( - _x11Display_p, _windowAttr_m.visual, + _display_p, _windowAttr_m.visual, uint(_windowAttr_m.depth), ZPixmap, nullptr, &_shminfo_m, uint(_destWidth_m), uint(_destHeight_m) ); @@ -138,20 +133,20 @@ void hgx11grab::grabFrame() _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)); + XShmAttach(_display_p, &_shminfo_m); + _pixmap_m = XShmCreatePixmap(_display_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); + _dstFormat_p = XRenderFindVisualFormat(_display_p, _windowAttr_m.visual); + _srcFormat_p = XRenderFindVisualFormat(_display_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); + _srcPicture_m = XRenderCreatePicture(_display_p, _window_m, _srcFormat_p, CPRepeat, &_pictAttr_m); + _dstPicture_m = XRenderCreatePicture(_display_p, _pixmap_m, _dstFormat_p, CPRepeat, &_pictAttr_m); - XRenderSetPictureFilter(_x11Display_p, _srcPicture_m, FilterBilinear, nullptr, 0); - XRenderSetPictureTransform(_x11Display_p, _srcPicture_m, &_mTransform_m); + XRenderSetPictureFilter(_display_p, _srcPicture_m, FilterBilinear, nullptr, 0); + XRenderSetPictureTransform(_display_p, _srcPicture_m, &_mTransform_m); XRenderComposite( - _x11Display_p, // *dpy, + _display_p, // *dpy, PictOpSrc, // op, _srcPicture_m, // src None, // mask @@ -166,16 +161,16 @@ void hgx11grab::grabFrame() uint(_destHeight_m) // height ); - XSync(_x11Display_p, false); + XSync(_display_p, false); - XShmGetImage(_x11Display_p, _pixmap_m, _xImage_p, 0, 0, 0xFFFFFFFF); + XShmGetImage(_display_p, _pixmap_m, _xImage_p, 0, 0, 0xFFFFFFFF); if (_xImage_p == nullptr) { qWarning() << "Failed to get image from X11 server."; return; } - QImage qimg = *new QImage(reinterpret_cast(_xImage_p->data), _destWidth_m, _destHeight_m, _xImage_p->bytes_per_line, QImage::Format_ARGB32); + QImage qimg(reinterpret_cast(_xImage_p->data), _destWidth_m, _destHeight_m, _xImage_p->bytes_per_line, QImage::Format_ARGB32); qimg = qimg.convertToFormat(QImage::Format_RGB888); imgdata_m = QByteArray::fromRawData(reinterpret_cast(qimg.bits()), qimg.byteCount()); diff --git a/hgx11grab.h b/hgx11grab.h index 9c12c0f..426e15d 100644 --- a/hgx11grab.h +++ b/hgx11grab.h @@ -14,14 +14,14 @@ class hgx11grab : public QObject public: QByteArray imgdata_m; - hgx11grab(unsigned short); + hgx11grab(Display *, unsigned short); ~hgx11grab(); int getDest_width() const; int getDest_height() const; private: - Display *_x11Display_p; + Display *_display_p; Window _window_m; Picture _srcPicture_m; Picture _dstPicture_m; diff --git a/hgx11net.cpp b/hgx11net.cpp index b9b667a..f629894 100644 --- a/hgx11net.cpp +++ b/hgx11net.cpp @@ -17,6 +17,7 @@ hgx11net::~hgx11net() while(_sock_p->waitForBytesWritten()) {} _sock_p->disconnectFromHost(); } + delete _sock_p; } void hgx11net::clearLeds() diff --git a/hgx11screensaver.cpp b/hgx11screensaver.cpp new file mode 100644 index 0000000..cb7f310 --- /dev/null +++ b/hgx11screensaver.cpp @@ -0,0 +1,25 @@ +#include "hgx11screensaver.h" + +// public + +hgx11screensaver::hgx11screensaver(Display *display) +{ + _display_p = display; + _window_m = DefaultRootWindow(_display_p); + _info_p = XScreenSaverAllocInfo(); +} + +hgx11screensaver::~hgx11screensaver() +{ + XFree(_info_p); +} + +unsigned long hgx11screensaver::lastX11ActivitySeconds() +{ + if (!_info_p) { + return 0; + } + XScreenSaverQueryInfo(_display_p, _window_m, _info_p); + return _info_p->idle; +} + diff --git a/hgx11screensaver.h b/hgx11screensaver.h new file mode 100644 index 0000000..1bc33ad --- /dev/null +++ b/hgx11screensaver.h @@ -0,0 +1,24 @@ +#ifndef HGX11SCREENSAVER_H +#define HGX11SCREENSAVER_H + +#include +#include +#include +#include + +class hgx11screensaver : public QObject +{ + Q_OBJECT +public: + hgx11screensaver(Display *); + ~hgx11screensaver(); + unsigned long lastX11ActivitySeconds(); + +private: + Display *_display_p; + Window _window_m; + XScreenSaverInfo *_info_p; + +}; + +#endif // HGX11SCREENSAVER_H diff --git a/main.cpp b/main.cpp index e282431..a1760b7 100644 --- a/main.cpp +++ b/main.cpp @@ -47,10 +47,15 @@ int main(int argc, char *argv[]) ); 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", "How many seconds after the screen is inactive to turn off the LED's. Set to 0 to disable."), QCoreApplication::translate("main", "seconds before turning off LEDs") ); parser.addOption(inactiveTime); + QCommandLineOption inactiveType(QStringList() << "j" << "inactivetype", + QCoreApplication::translate("main", "If `i` or `inactive` is set, how to determine activity, using (1) Xscreensaver (based on amount of time since user input activity) or (0) Xdamage (based on amount of time since screen activity change)"), + QCoreApplication::translate("main", "1 to use Xscreensaver or 0 to use Xdamage for inactivity tracking") + ); + parser.addOption(inactiveType); QCommandLineOption redAdjust(QStringList() << "r" << "redadjust", QCoreApplication::translate("main", "Adjustment of the red color (requires 3 space seperated values between 0 and 255) (ex. \"255,10,0\")"), QCoreApplication::translate("main", "adjusts red color of LEDs")