/** * @RDE_HEADER@ * $Id$ * $HeadURL$ * $Author$ */ #include "pws_vnc_viewer_dialog.h" #include "ui_pws_vnc_viewer_dialog.h" #include #include #include #include #include #include #include #include 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"); } void VncRenderAreaWorker::onRfbFrameBufferUpdate(const QVector &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(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