Files
qt5-legatus-legacy/Lts/Plugins/ProjectionWorkstation/Gui/pws_vnc_viewer_dialog.cpp
T
2026-08-13 18:33:10 +02:00

399 lines
11 KiB
C++
Executable File

/**
* @RDE_HEADER@
* $Id$
* $HeadURL$
* $Author$
*/
#include "pws_vnc_viewer_dialog.h"
#include "ui_pws_vnc_viewer_dialog.h"
#include <LtsProjectionWorkstation/logger.h>
#include <QMutexLocker>
#include <QPainter>
#include <QOpenGLPaintDevice>
#include <QOpenGLFunctions>
#include <QThread>
#include <QTimer>
#include <cmath>
namespace ProjectionWorkstation
{
// Dialog
VncViewerDialog::VncViewerDialog(const QString &name, QWidget *parent)
: LtsWidgets::LtsDialog(parent, LtsWidgets::ButtonCombination::SeamlessDialog, false)
, m_ui(new Ui::VncViewerDialog)
, m_handler(nullptr)
, m_renderArea(nullptr)
, m_mouseButtonMask(0)
, m_mouseScrollReleaseTimer(new QTimer(this))
, m_name(name)
, m_isConnected(false)
, m_logger(LtsProjectionWorkstation::getLogger(QStringLiteral("VncViewerDialog")))
{
m_ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
}
VncViewerDialog::~VncViewerDialog()
{
delete m_ui;
}
bool VncViewerDialog::init(const QHostAddress &host, quint16 port, const QSize &remoteResolution)
{
m_handler = new VncHandler(host, port, this);
m_renderArea = new VncRenderArea(remoteResolution, this);
m_remoteResolution = remoteResolution;
m_ui->gridLayout->addWidget(m_renderArea);
connect(m_handler, &VncHandler::frameBufferUpdateReceived, m_renderArea, &VncRenderArea::frameBufferUpdateReceived);
connect(m_handler, &VncHandler::cutTextReceived, this, &VncViewerDialog::handleServerCuttext);
connect(m_handler, &VncHandler::beepReceived, this, &QApplication::beep);
connect(m_handler, &VncHandler::connected, this, &VncViewerDialog::show);
connect(m_handler, &VncHandler::disconnected, this, &VncViewerDialog::slotOnDisconnected);
connect(m_mouseScrollReleaseTimer, &QTimer::timeout, this, &VncViewerDialog::onScrollRelease);
setMouseTracking(true);
m_mouseScrollReleaseTimer->setSingleShot(true);
m_mouseScrollReleaseTimer->setInterval(5);
m_handler->connectToHost();
hide();
return true;
}
void VncViewerDialog::retranslate()
{
translate(m_ui, m_name);
}
void VncViewerDialog::handleServerCuttext(const QString &cutText)
{
DEBUG_LOG(m_logger) << "Got cuttext: " << cutText;
}
void VncViewerDialog::slotOnDisconnected()
{
// closeRequest();
// m_renderArea->clear();
emit disconnected();
}
void VncViewerDialog::focusInEvent(QFocusEvent *event)
{
Q_UNUSED(event)
grabKeyboard();
// setCursor(Qt::BlankCursor);
if (m_renderArea)
{
QByteArray data = RfbProtocol::CTS::RfbFrameBufferUpdateRequest(false, 0, 0, m_renderArea->frameSize().width(), m_renderArea->frameSize().height());
m_handler->writeData(data);
}
}
void VncViewerDialog::focusOutEvent(QFocusEvent *event)
{
Q_UNUSED(event)
releaseKeyboard();
// unsetCursor();
}
void VncViewerDialog::onScrollRelease()
{
m_mouseButtonMask &= ~(3 << 3);
QByteArray data = RfbProtocol::CTS::RfbPointerEvent(m_mouseButtonMask, m_lastScrollPos.x(), m_lastScrollPos.y());
m_handler->writeData(data);
}
void VncViewerDialog::onSlotConnected()
{
m_isConnected = true;
}
void VncViewerDialog::mousePressEvent(QMouseEvent *event)
{
event->accept();
if (event->button() == Qt::LeftButton)
m_mouseButtonMask |= (1 << 0);
else if (event->button() == Qt::MidButton)
m_mouseButtonMask |= (1 << 1);
else if (event->button() == Qt::RightButton)
m_mouseButtonMask |= (1 << 2);
QPoint mapped = mapMousePosition(event->pos());
QByteArray data = RfbProtocol::CTS::RfbPointerEvent(m_mouseButtonMask, mapped.x(), mapped.y());
m_handler->writeData(data);
}
void VncViewerDialog::mouseReleaseEvent(QMouseEvent *event)
{
event->accept();
if (event->button() == Qt::LeftButton)
m_mouseButtonMask &= ~(1 << 0);
else if (event->button() == Qt::MidButton)
m_mouseButtonMask &= ~(1 << 1);
else if (event->button() == Qt::RightButton)
m_mouseButtonMask &= ~(1 << 2);
QPoint mapped = mapMousePosition(event->pos());
QByteArray data = RfbProtocol::CTS::RfbPointerEvent(m_mouseButtonMask, mapped.x(), mapped.y());
m_handler->writeData(data);
}
void VncViewerDialog::mouseMoveEvent(QMouseEvent *event)
{
event->accept();
QPoint mapped = mapMousePosition(event->pos());
QByteArray data = RfbProtocol::CTS::RfbPointerEvent(m_mouseButtonMask, mapped.x(), mapped.y());
m_handler->writeData(data);
}
void VncViewerDialog::keyPressEvent(QKeyEvent *event)
{
event->accept();
QByteArray data = RfbProtocol::CTS::RfbKeyEvent(event->nativeVirtualKey(), true);
m_handler->writeData(data);
}
void VncViewerDialog::keyReleaseEvent(QKeyEvent *event)
{
event->accept();
QByteArray data = RfbProtocol::CTS::RfbKeyEvent(event->nativeVirtualKey(), false);
m_handler->writeData(data);
}
void VncViewerDialog::wheelEvent(QWheelEvent *event)
{
if (!m_mouseScrollReleaseTimer->isActive())
{
if (event->angleDelta().y() > 0)
m_mouseButtonMask |= (1 << 3);
else if (event->angleDelta().y() < 0)
m_mouseButtonMask |= (1 << 4);
QPoint mapped = mapMousePosition(event->pos());
QByteArray data = RfbProtocol::CTS::RfbPointerEvent(m_mouseButtonMask, mapped.x(), mapped.y());
m_handler->writeData(data);
m_lastScrollPos = mapped;
m_mouseScrollReleaseTimer->start();
}
}
void VncViewerDialog::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event)
if (m_isConnected && m_renderArea)
{
emit m_renderArea->resized();
QByteArray data = RfbProtocol::CTS::RfbFrameBufferUpdateRequest(false, 0, 0, m_renderArea->frameSize().width(), m_renderArea->frameSize().height());
m_handler->writeData(data);
}
}
void VncViewerDialog::showEvent(QShowEvent *event)
{
Q_UNUSED(event)
if (m_isConnected && m_renderArea)
{
QByteArray data = RfbProtocol::CTS::RfbFrameBufferUpdateRequest(false, 0, 0, m_renderArea->frameSize().width(), m_renderArea->frameSize().height());
m_handler->writeData(data);
}
}
static inline int mapValue(double val, double maxIn, double maxOut)
{
val = qBound(0., val, maxIn);
return qRound(val * (maxOut / maxIn));
}
QPoint VncViewerDialog::mapMousePosition(const QPoint &point) const
{
QPoint clienPoint = m_renderArea->mapFromParent(point);
int x = mapValue(clienPoint.x(), m_renderArea->scaledSize().width(), m_renderArea->frameSize().width());
int y = mapValue(clienPoint.y(), m_renderArea->scaledSize().height(), m_renderArea->frameSize().height());
return QPoint(x, y);
}
// Render Area Worker
VncRenderAreaWorker::VncRenderAreaWorker()
: m_logger(LtsProjectionWorkstation::getLogger(QStringLiteral("VncRenderArea")))
{
qRegisterMetaType<QVector<RfbProtocol::RfbRectangle>>("QVector<RfbProtocol::RfbRectangle>");
}
void VncRenderAreaWorker::onRfbFrameBufferUpdate(const QVector<RfbProtocol::RfbRectangle> &rectangles)
{
if (rectangles.isEmpty())
return;
QPainter painter(&m_renderedImage);
// no dequeue here otherwise rectangles() is dangling
for (const RfbProtocol::RfbRectangle &rect : rectangles)
{
if (rect.pixelData.isEmpty())
{
WARNING_LOG(m_logger) << "No valid vnc pixel data... skipping frame";
continue;
}
QImage img(reinterpret_cast<const unsigned char *>(rect.pixelData.data()),
rect.width,
rect.height,
QImage::Format_RGB32);
if (img.isNull())
{
WARNING_LOG(m_logger) << "Could not decode image data... skipping vnc frame";
continue;
}
painter.drawImage(QPoint(rect.xpos, rect.ypos), img);
}
painter.end();
QImage img = m_renderedImage.scaled(m_scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
if (img.size() != m_scaledSizeAspectRatio)
{
m_scaledSizeAspectRatio = img.size();
emit newScaleSizeAspectRatio(m_scaledSizeAspectRatio);
}
{
QMutexLocker l(&m_mutex);
std::swap(img, m_scaledImage);
}
emit newImageAvailable();
}
void VncRenderAreaWorker::setImageSize(QSize s)
{
QMutexLocker l(&m_mutex);
m_renderedImage = QImage(s, QImage::Format_RGB32);
}
void VncRenderAreaWorker::setScaledSize(QSize s)
{
if (m_scaledSize == s)
return;
QMutexLocker l(&m_mutex);
m_scaledSize = s;
if (!m_renderedImage.isNull())
{
m_scaledImage = m_renderedImage.scaled(m_scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
m_scaledSizeAspectRatio = m_scaledImage.size();
emit newScaleSizeAspectRatio(m_scaledSizeAspectRatio);
}
}
QImage VncRenderAreaWorker::renderedImage() const
{
QMutexLocker l(&m_mutex);
return m_scaledImage;
}
//Render Area
VncRenderArea::VncRenderArea(const QSize remoteResolution, QWidget *parent)
: QOpenGLWidget(parent)
, m_imageRenderThread(new QThread(this))
, m_renderAreaWorker(new VncRenderAreaWorker)
, m_frameSize(remoteResolution)
, m_fbo(nullptr)
, m_logger(LtsProjectionWorkstation::getLogger(QStringLiteral("VncRenderArea")))
{
setAttribute(Qt::WA_TransparentForMouseEvents);
connect(this, &VncRenderArea::frameBufferUpdateReceived,
m_renderAreaWorker, &VncRenderAreaWorker::onRfbFrameBufferUpdate);
connect(m_renderAreaWorker, &VncRenderAreaWorker::newImageAvailable,
this, &VncRenderArea::prepareRender);
connect(m_renderAreaWorker, &VncRenderAreaWorker::newScaleSizeAspectRatio,
this, [this](QSize s) { m_scaledSizeAspectRatio = s; });
m_renderAreaWorker->setImageSize(remoteResolution);
m_renderAreaWorker->moveToThread(m_imageRenderThread);
m_imageRenderThread->start();
}
VncRenderArea::~VncRenderArea()
{
delete m_fbo;
m_imageRenderThread->quit();
m_imageRenderThread->wait(2000);
delete m_imageRenderThread;
delete m_renderAreaWorker;
}
void VncRenderArea::clear()
{
if (context())
{
glClearColor(1, 1, 1, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
update();
}
}
void VncRenderArea::initializeGL()
{
initializeOpenGLFunctions();
m_blitter.create();
}
void VncRenderArea::paintGL()
{
if (context() && m_fbo)
{
m_blitter.bind();
const QRect targetRect(QPoint(0, 0), m_fbo->size());
const QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(targetRect, targetRect); // no scaling here, is not anti-aliased
m_blitter.blit(m_fbo->texture(), target, QOpenGLTextureBlitter::OriginBottomLeft);
m_blitter.release();
}
}
void VncRenderArea::maybeResized()
{
rebuildFbo(size());
m_renderAreaWorker->setScaledSize(size());
}
void VncRenderArea::prepareRender()
{
if (context())
{
if (!m_fbo || m_fbo->size() != size())
maybeResized();
m_fbo->bind();
QOpenGLPaintDevice device(size());
QPainter painter(&device);
painter.drawImage(QPoint(0, 0), m_renderAreaWorker->renderedImage());
painter.end();
m_fbo->release();
update();
}
}
void VncRenderArea::rebuildFbo(QSize newSize)
{
delete m_fbo;
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
m_fbo = new QOpenGLFramebufferObject(newSize, format);
}
} // ProjectionWorkstation