#include "hyperiongrabber.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // public HyperionGrabber::HyperionGrabber(QHash opts) { QString addr = "localhost"; unsigned short port = 19444; unsigned short scale = 8; unsigned short priority = 100; QString redAdjust = "", greenAdjust = "", blueAdjust = ""; QString temperature = "", threshold = "", transform = ""; _scale_m = scale; QHashIterator i(opts); while (i.hasNext()) { i.next(); if ((i.key() == "a" || i.key() == "address") && !(i.value().isNull() && i.value().isEmpty())) { addr = i.value(); } else if ((i.key() == "p" || i.key() == "port") && i.value().toUShort()) { port = i.value().toUShort(); } else if ((i.key() == "c" || i.key() == "priority") && (i.value().toUShort() && i.value().toUShort() <= 255)) { priority = i.value().toUShort(); } else if ((i.key() == "s" || i.key() == "scale") && i.value().toUShort()) { scale = i.value().toUShort(); } else if ((i.key() == "f" || i.key() == "frameskip") && i.value().toUShort()) { _frameskip_m = i.value().toUShort(); } else if ((i.key() == "i" || i.key() == "inactive") && i.value().toInt()) { _inactiveTime_m = (i.value().toInt() * 1000); } else if (i.key() == "r" || i.key() == "redadjust") { redAdjust = _parseColorArr(i.value(), 1); } else if (i.key() == "g" || i.key() == "greenadjust") { greenAdjust = _parseColorArr(i.value(), 1); } else if (i.key() == "b" || i.key() == "blueadjust") { blueAdjust = _parseColorArr(i.value(), 1); } else if (i.key() == "t" || i.key() == "temperature") { 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) != "") { transform = i.value(); } } _hyperionPriority_m = QString::number(priority); _hclient_p = new HyperionClient(addr, port, _hyperionPriority_m); _hclient_p->ledAdjustments(redAdjust, greenAdjust, blueAdjust, temperature, threshold, transform); // Initialize OpenGL for hardware acceleration QSurfaceFormat format; format.setRenderableType(QSurfaceFormat::OpenGL); format.setProfile(QSurfaceFormat::CoreProfile); format.setVersion(3, 3); // Or higher, depending on your OpenGL version format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); format.setRedBufferSize(8); format.setGreenBufferSize(8); format.setBlueBufferSize(8); format.setAlphaBufferSize(8); format.setDepthBufferSize(24); format.setStencilBufferSize(8); _offscreenSurface = new QOffscreenSurface(); _offscreenSurface->setFormat(format); _offscreenSurface->create(); _context = new QOpenGLContext(this); _context->setFormat(format); _context->create(); _context->makeCurrent(_offscreenSurface); _functions = _context->functions(); _functions->initializeOpenGLFunctions(); // Shaders for scaling const char *vertexShaderSource = R"( attribute highp vec4 vertices; attribute highp vec2 texCoords; varying highp vec2 v_texCoords; void main() { gl_Position = vertices; v_texCoords = texCoords; } )"; const char *fragmentShaderSource = R"( uniform sampler2D textureSampler; varying highp vec2 v_texCoords; void main() { gl_FragColor = texture2D(textureSampler, v_texCoords); } )"; _shaderProgram = new QOpenGLShaderProgram(this); _shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource); _shaderProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource); _shaderProgram->link(); _fbo = new QOpenGLFramebufferObject(1, 1, QOpenGLFramebufferObject::CombinedDepthStencil); // Placeholder size, will be resized later _texture = new QOpenGLTexture(QOpenGLTexture::Target2D); // Placeholder, will be created from QVideoFrame _waylandGrabber_p = new WaylandGrabber(this); connect(_waylandGrabber_p, &WaylandGrabber::frameReady, this, &HyperionGrabber::_processFrame); _waylandGrabber_p->start(); QScreen *screen = QGuiApplication::primaryScreen(); if (screen) { _setImgSize(screen->size().width() / scale, screen->size().height() / scale); } else { qWarning() << "Could not get primary screen size for Wayland grabber."; _setImgSize(1920 / scale, 1080 / scale); // Default to 1080p if screen not found } if (_inactiveTime_m) { _timer_p = new QTimer(this); connect(_timer_p, &QTimer::timeout, this, &HyperionGrabber::_inActivity); _timer_p->start(_inactiveTime_m); } } HyperionGrabber::~HyperionGrabber() { if (_timer_p) { _timer_p->stop(); delete _timer_p; } delete _hclient_p; // OpenGL cleanup if (_context) { _context->makeCurrent(_offscreenSurface); delete _fbo; delete _shaderProgram; delete _texture; _context->doneCurrent(); delete _context; } if (_offscreenSurface) { delete _offscreenSurface; } } // private QString HyperionGrabber::_parseColorArr(QString value, bool isInt) { QStringList values = value.split(','); if (values.size() != 3) { return ""; } value = "["; for (int i = 0; i < 3; i++) { if (isInt && (values.at(i).toInt() < 0 || values.at(i).toInt() > 255)) { return ""; } if (!isInt && (values.at(i).toDouble() < 0.0 || values.at(i).toDouble() > 1.0)) { return ""; } value.append(values.at(i)); value.append(","); } value.chop(1); value.append("]"); return value; } // private slots void HyperionGrabber::_inActivity() { _hclient_p->clearLeds(); } void HyperionGrabber::_processFrame(const QVideoFrame &frame) { if (!frame.isValid()) { return; } _frameCounter_m++; if (_frameskip_m > 0 && (_frameCounter_m % (_frameskip_m + 1) != 0)) { return; } _context->makeCurrent(_offscreenSurface); // Convert QVideoFrame to QImage QImage inputImage = frame.toImage(); if (inputImage.isNull()) { qWarning() << "Failed to convert QVideoFrame to QImage."; _context->doneCurrent(); return; } // qDebug() << "Input QImage dimensions:" << inputImage.width() << "x" << inputImage.height(); // qDebug() << "Input QImage format:" << inputImage.format(); // Resize FBO if needed QSize targetSize(inputImage.width() / _scale_m, inputImage.height() / _scale_m); if (_fbo->size() != targetSize) { delete _fbo; _fbo = new QOpenGLFramebufferObject(targetSize, QOpenGLFramebufferObject::CombinedDepthStencil); } _fbo->bind(); _functions->glViewport(0, 0, _fbo->width(), _fbo->height()); // qDebug() << "FBO size:" << _fbo->size(); _functions->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); _functions->glClear(GL_COLOR_BUFFER_BIT); // Create texture from QImage if (_texture->isCreated()) { _texture->destroy(); } _texture->create(); _texture->setData(inputImage.flipped(Qt::Vertical)); // Flip vertically // qDebug() << "Texture size after setData:" << _texture->width() << "x" << _texture->height(); _shaderProgram->bind(); _shaderProgram->setUniformValue("textureSampler", 0); // Texture unit 0 // Vertices for a quad GLfloat vertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }; // Texture coordinates (flipped vertically for QImage top-left origin) GLfloat texCoords[] = { 0.0f, 1.0f, // Top-left of texture (maps to bottom-left of quad) 1.0f, 1.0f, // Top-right of texture (maps to bottom-right of quad) 0.0f, 0.0f, // Bottom-left of texture (maps to top-left of quad) 1.0f, 0.0f, // Bottom-right of texture (maps to top-right of quad) }; _shaderProgram->setAttributeArray("vertices", vertices, 2); _shaderProgram->enableAttributeArray("vertices"); _shaderProgram->setAttributeArray("texCoords", texCoords, 2); _shaderProgram->enableAttributeArray("texCoords"); _functions->glActiveTexture(GL_TEXTURE0); _texture->bind(); _functions->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); _shaderProgram->disableAttributeArray("vertices"); _shaderProgram->disableAttributeArray("texCoords"); _shaderProgram->release(); _fbo->release(); // Read pixels back from FBO QImage scaledImage = _fbo->toImage(); scaledImage = scaledImage.flipped(Qt::Vertical); // Flip vertically again if needed QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); scaledImage.save(&buffer, "PNG"); // Save as PNG for now, can be optimized later int rawDataSize = scaledImage.sizeInBytes(); _hclient_p->setImgSize(scaledImage.width(), scaledImage.height()); _hclient_p->sendImage(scaledImage.constBits(), rawDataSize); _context->doneCurrent(); } void HyperionGrabber::_setImgSize(int width, int height) { _hclient_p->setImgSize(width, height); }