#include "rdp_display_widget.h" #include #include #include #include #include #include #include #include #include namespace { QSize sanitizeSize(const QSize& size) { return QSize(qMax(1, size.width()), qMax(1, size.height())); } } RdpDisplayWidget::RdpDisplayWidget(QWidget* parent) : QWidget(parent), m_remoteSize(1280, 720), m_cursorMode(CursorMode::Default) { setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); setAutoFillBackground(false); setMinimumSize(320, 200); QTimer::singleShot(0, this, [this]() { const QSize size = sanitizeSize(this->size()); emit viewportSizeChanged(size.width(), size.height()); }); } void RdpDisplayWidget::setFrame(const QImage& frame) { if (frame.isNull()) { return; } m_frame = frame; m_remoteSize = sanitizeSize(frame.size()); update(); } void RdpDisplayWidget::setRemoteDesktopSize(int width, int height) { if (width < 1 || height < 1) { return; } const QSize nextSize(width, height); if (m_remoteSize == nextSize) { return; } m_remoteSize = nextSize; update(); } void RdpDisplayWidget::clearFrame() { m_frame = QImage(); update(); } void RdpDisplayWidget::setCursorImage(const QImage& image, const QPoint& hotspot) { m_cursorImage = image; m_cursorHotspot = hotspot; m_cursorMode = CursorMode::Custom; applyCursor(); } void RdpDisplayWidget::setCursorHidden() { m_cursorMode = CursorMode::Hidden; applyCursor(); } void RdpDisplayWidget::setCursorDefault() { m_cursorMode = CursorMode::Default; applyCursor(); } void RdpDisplayWidget::applyCursor() { if (m_cursorMode == CursorMode::Hidden) { setCursor(Qt::BlankCursor); return; } if (m_cursorMode == CursorMode::Default || m_cursorImage.isNull()) { unsetCursor(); return; } const QSize remote = effectiveRemoteSize(); const QRectF target = renderRect(); if (remote.isEmpty() || target.isEmpty()) { setCursor(QCursor(QPixmap::fromImage(m_cursorImage), m_cursorHotspot.x(), m_cursorHotspot.y())); return; } const qreal scale = target.width() / remote.width(); QImage scaledImage = m_cursorImage; if (!qFuzzyCompare(scale, 1.0)) { scaledImage = m_cursorImage.scaled( qMax(1, qRound(m_cursorImage.width() * scale)), qMax(1, qRound(m_cursorImage.height() * scale)), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } const int hotX = qBound(0, qRound(m_cursorHotspot.x() * scale), scaledImage.width()); const int hotY = qBound(0, qRound(m_cursorHotspot.y() * scale), scaledImage.height()); setCursor(QCursor(QPixmap::fromImage(scaledImage), hotX, hotY)); } void RdpDisplayWidget::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QPainter painter(this); painter.fillRect(rect(), QColor(QStringLiteral("#101214"))); const QRectF target = renderRect(); if (!m_frame.isNull()) { painter.drawImage(target, m_frame); } else { painter.setPen(QColor(QStringLiteral("#b0bec5"))); painter.drawText(rect(), Qt::AlignCenter, QStringLiteral("Waiting for remote desktop frame...")); } } void RdpDisplayWidget::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); const QSize size = sanitizeSize(event->size()); emit viewportSizeChanged(size.width(), size.height()); applyCursor(); } void RdpDisplayWidget::keyPressEvent(QKeyEvent* event) { if (event == nullptr) { return; } // Auto-repeat presses must reach the remote server so it can perform // its own typematic repeat, exactly as a physical keyboard held down // would. Only release events filter out isAutoRepeat() (below), since // Qt uses a synthetic release/press pair purely to normalize platform // auto-repeat quirks -- forwarding that synthetic release would send a // spurious key-up for a key that is still physically held. emit keyInput(event->key(), event->nativeScanCode(), event->text(), true, static_cast(event->modifiers())); event->accept(); } void RdpDisplayWidget::keyReleaseEvent(QKeyEvent* event) { if (event == nullptr || event->isAutoRepeat()) { return; } emit keyInput(event->key(), event->nativeScanCode(), event->text(), false, static_cast(event->modifiers())); event->accept(); } bool RdpDisplayWidget::focusNextPrevChild(bool next) { Q_UNUSED(next); // Tab/Shift+Tab must reach keyPressEvent() and be forwarded to the // remote session instead of moving focus to the next local widget. return false; } void RdpDisplayWidget::mousePressEvent(QMouseEvent* event) { if (event == nullptr) { return; } setFocus(Qt::MouseFocusReason); const QPoint mapped = mapToRemote(event->position()); emit mouseButtonInput(mapped.x(), mapped.y(), static_cast(event->button()), true); event->accept(); } void RdpDisplayWidget::mouseReleaseEvent(QMouseEvent* event) { if (event == nullptr) { return; } const QPoint mapped = mapToRemote(event->position()); emit mouseButtonInput(mapped.x(), mapped.y(), static_cast(event->button()), false); event->accept(); } void RdpDisplayWidget::mouseMoveEvent(QMouseEvent* event) { if (event == nullptr) { return; } const QPoint mapped = mapToRemote(event->position()); emit mouseMoveInput(mapped.x(), mapped.y()); event->accept(); } void RdpDisplayWidget::wheelEvent(QWheelEvent* event) { if (event == nullptr) { return; } const QPoint mapped = mapToRemote(event->position()); const QPoint angle = event->angleDelta(); emit mouseWheelInput(mapped.x(), mapped.y(), angle.x(), angle.y()); event->accept(); } QRectF RdpDisplayWidget::renderRect() const { const QSize remote = effectiveRemoteSize(); const QRectF area = rect(); if (area.isEmpty()) { return QRectF(); } const qreal scale = qMin(area.width() / remote.width(), area.height() / remote.height()); const qreal drawWidth = remote.width() * scale; const qreal drawHeight = remote.height() * scale; const qreal x = area.x() + ((area.width() - drawWidth) * 0.5); const qreal y = area.y() + ((area.height() - drawHeight) * 0.5); return QRectF(x, y, drawWidth, drawHeight); } QPoint RdpDisplayWidget::mapToRemote(const QPointF& pos) const { const QSize remote = effectiveRemoteSize(); const QRectF target = renderRect(); if (target.isEmpty()) { return QPoint(0, 0); } const qreal clampedX = qBound(target.left(), pos.x(), target.right()); const qreal clampedY = qBound(target.top(), pos.y(), target.bottom()); const qreal normalizedX = (clampedX - target.left()) / qMax(1.0, target.width()); const qreal normalizedY = (clampedY - target.top()) / qMax(1.0, target.height()); const int remoteX = qBound(0, static_cast(normalizedX * remote.width()), remote.width() - 1); const int remoteY = qBound(0, static_cast(normalizedY * remote.height()), remote.height() - 1); return QPoint(remoteX, remoteY); } QSize RdpDisplayWidget::effectiveRemoteSize() const { if (m_remoteSize.width() > 0 && m_remoteSize.height() > 0) { return m_remoteSize; } if (!m_frame.isNull()) { return sanitizeSize(m_frame.size()); } return QSize(1280, 720); }