399 lines
11 KiB
C++
Executable File
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
|