mirror of
https://invent.kde.org/network/krdc
synced 2024-07-08 20:05:56 +00:00
675 lines
21 KiB
C++
675 lines
21 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2007-2013 Urs Wolfer <uwolfer@kde.org>
|
|
SPDX-FileCopyrightText: 2021 Rafał Lalik <rafallalik@gmail.com>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "vncview.h"
|
|
#include "krdc_debug.h"
|
|
|
|
#include <QApplication>
|
|
#include <QImage>
|
|
#include <QMimeData>
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QTimer>
|
|
|
|
#ifdef QTONLY
|
|
#include <QInputDialog>
|
|
#include <QMessageBox>
|
|
#define KMessageBox QMessageBox
|
|
#define error(parent, message, caption) critical(parent, caption, message)
|
|
#else
|
|
#include "settings.h"
|
|
#include <KActionCollection>
|
|
#include <KMainWindow>
|
|
#include <KMessageBox>
|
|
#include <KPasswordDialog>
|
|
#include <KXMLGUIClient>
|
|
#endif
|
|
|
|
// Definition of key modifier mask constants
|
|
#define KMOD_Alt_R 0x01
|
|
#define KMOD_Alt_L 0x02
|
|
#define KMOD_Meta_L 0x04
|
|
#define KMOD_Control_L 0x08
|
|
#define KMOD_Shift_L 0x10
|
|
|
|
VncView::VncView(QWidget *parent, const QUrl &url, KConfigGroup configGroup)
|
|
: RemoteView(parent)
|
|
, m_initDone(false)
|
|
, m_buttonMask(0)
|
|
, m_quitFlag(false)
|
|
, m_firstPasswordTry(true)
|
|
, m_horizontalFactor(1.0)
|
|
, m_verticalFactor(1.0)
|
|
, m_wheelRemainderV(0)
|
|
, m_wheelRemainderH(0)
|
|
, m_forceLocalCursor(false)
|
|
#ifdef LIBSSH_FOUND
|
|
, m_sshTunnelThread(nullptr)
|
|
#endif
|
|
{
|
|
m_url = url;
|
|
m_host = url.host();
|
|
m_port = url.port();
|
|
|
|
if (m_port <= 0) // port is invalid or empty...
|
|
m_port = 5900; // fallback: try an often used VNC port
|
|
|
|
if (m_port < 100) // the user most likely used the short form (e.g. :1)
|
|
m_port += 5900;
|
|
|
|
// BlockingQueuedConnection can cause deadlocks when exiting, handled in startQuitting()
|
|
connect(&vncThread, SIGNAL(imageUpdated(int, int, int, int)), this, SLOT(updateImage(int, int, int, int)), Qt::BlockingQueuedConnection);
|
|
connect(&vncThread, SIGNAL(gotCut(QString)), this, SLOT(setCut(QString)), Qt::BlockingQueuedConnection);
|
|
connect(&vncThread, SIGNAL(passwordRequest(bool)), this, SLOT(requestPassword(bool)), Qt::BlockingQueuedConnection);
|
|
connect(&vncThread, SIGNAL(outputErrorMessage(QString)), this, SLOT(outputErrorMessage(QString)));
|
|
connect(&vncThread, &VncClientThread::gotCursor, this, [this](QCursor cursor) {
|
|
setCursor(cursor);
|
|
});
|
|
|
|
#ifndef QTONLY
|
|
m_hostPreferences = new VncHostPreferences(configGroup, this);
|
|
#else
|
|
Q_UNUSED(configGroup);
|
|
#endif
|
|
}
|
|
|
|
VncView::~VncView()
|
|
{
|
|
if (!m_quitFlag)
|
|
startQuitting();
|
|
}
|
|
|
|
QSize VncView::framebufferSize()
|
|
{
|
|
return m_frame.size() / devicePixelRatioF();
|
|
}
|
|
|
|
QSize VncView::sizeHint() const
|
|
{
|
|
return size();
|
|
}
|
|
|
|
QSize VncView::minimumSizeHint() const
|
|
{
|
|
return size();
|
|
}
|
|
|
|
void VncView::scaleResize(int w, int h)
|
|
{
|
|
RemoteView::scaleResize(w, h);
|
|
|
|
qCDebug(KRDC) << w << h;
|
|
if (m_scale) {
|
|
const QSize frameSize = m_frame.size() / m_frame.devicePixelRatio();
|
|
|
|
m_verticalFactor = static_cast<qreal>(h) / frameSize.height() * m_factor;
|
|
m_horizontalFactor = static_cast<qreal>(w) / frameSize.width() * m_factor;
|
|
|
|
#ifndef QTONLY
|
|
if (Settings::keepAspectRatio()) {
|
|
m_verticalFactor = m_horizontalFactor = qMin(m_verticalFactor, m_horizontalFactor);
|
|
}
|
|
#else
|
|
m_verticalFactor = m_horizontalFactor = qMin(m_verticalFactor, m_horizontalFactor);
|
|
#endif
|
|
|
|
const qreal newW = frameSize.width() * m_horizontalFactor;
|
|
const qreal newH = frameSize.height() * m_verticalFactor;
|
|
setMaximumSize(newW, newH); // This is a hack to force Qt to center the view in the scroll area
|
|
resize(newW, newH);
|
|
}
|
|
}
|
|
|
|
void VncView::updateConfiguration()
|
|
{
|
|
RemoteView::updateConfiguration();
|
|
|
|
// Update the scaling mode in case KeepAspectRatio changed
|
|
scaleResize(parentWidget()->width(), parentWidget()->height());
|
|
}
|
|
|
|
void VncView::startQuitting()
|
|
{
|
|
// Already quitted. No need to clean up again and also avoid triggering
|
|
// `disconnected` signal below.
|
|
if (m_quitFlag)
|
|
return;
|
|
|
|
qCDebug(KRDC) << "about to quit";
|
|
|
|
setStatus(Disconnecting);
|
|
|
|
m_quitFlag = true;
|
|
|
|
vncThread.stop();
|
|
|
|
unpressModifiers();
|
|
|
|
// Disconnect all signals so that we don't get any more callbacks from the client thread
|
|
vncThread.disconnect();
|
|
|
|
vncThread.quit();
|
|
|
|
#ifdef LIBSSH_FOUND
|
|
if (m_sshTunnelThread) {
|
|
delete m_sshTunnelThread;
|
|
m_sshTunnelThread = nullptr;
|
|
}
|
|
#endif
|
|
|
|
const bool quitSuccess = vncThread.wait(500);
|
|
if (!quitSuccess) {
|
|
// happens when vncThread wants to call a slot via BlockingQueuedConnection,
|
|
// needs an event loop in this thread so execution continues after 'emit'
|
|
QEventLoop loop;
|
|
if (!loop.processEvents()) {
|
|
qCDebug(KRDC) << "BUG: deadlocked, but no events to deliver?";
|
|
}
|
|
vncThread.wait(500);
|
|
}
|
|
|
|
qCDebug(KRDC) << "Quit VNC thread success:" << quitSuccess;
|
|
|
|
// emit the disconnect siginal only after all the events are handled.
|
|
// Otherwise some error messages might be thrown away without
|
|
// showing to the user.
|
|
Q_EMIT disconnected();
|
|
setStatus(Disconnected);
|
|
}
|
|
|
|
bool VncView::isQuitting()
|
|
{
|
|
return m_quitFlag;
|
|
}
|
|
|
|
bool VncView::start()
|
|
{
|
|
// This flag is used to make sure `startQuitting` only run once.
|
|
// This should not matter for now but it is an easy way to make sure
|
|
// things works in case the object may get reused.
|
|
m_quitFlag = false;
|
|
|
|
QString vncHost = m_host;
|
|
#ifdef LIBSSH_FOUND
|
|
if (m_hostPreferences->useSshTunnel()) {
|
|
Q_ASSERT(!m_sshTunnelThread);
|
|
|
|
m_sshTunnelThread = new VncSshTunnelThread(m_host.toUtf8(),
|
|
m_port,
|
|
/* tunnelPort */ 0,
|
|
m_hostPreferences->sshTunnelPort(),
|
|
m_hostPreferences->sshTunnelUserName().toUtf8(),
|
|
m_hostPreferences->useSshTunnelLoopback());
|
|
connect(m_sshTunnelThread, &VncSshTunnelThread::passwordRequest, this, &VncView::sshRequestPassword, Qt::BlockingQueuedConnection);
|
|
connect(m_sshTunnelThread, &VncSshTunnelThread::errorMessage, this, &VncView::sshErrorMessage);
|
|
m_sshTunnelThread->start();
|
|
|
|
if (m_hostPreferences->useSshTunnelLoopback()) {
|
|
vncHost = QStringLiteral("127.0.0.1");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
vncThread.setHost(vncHost);
|
|
RemoteView::Quality quality;
|
|
#ifdef QTONLY
|
|
quality = (RemoteView::Quality)((QCoreApplication::arguments().count() > 2) ? QCoreApplication::arguments().at(2).toInt() : 2);
|
|
#else
|
|
quality = m_hostPreferences->quality();
|
|
#endif
|
|
|
|
vncThread.setQuality(quality);
|
|
vncThread.setDevicePixelRatio(devicePixelRatioF());
|
|
|
|
// set local cursor on by default because low quality mostly means slow internet connection
|
|
if (quality == RemoteView::Low) {
|
|
showLocalCursor(RemoteView::CursorOn);
|
|
#ifndef QTONLY
|
|
// KRDC does always just have one main window, so at(0) is safe
|
|
KXMLGUIClient *mainWindow = dynamic_cast<KXMLGUIClient *>(KMainWindow::memberList().at(0));
|
|
if (mainWindow)
|
|
mainWindow->actionCollection()->action(QLatin1String("show_local_cursor"))->setChecked(true);
|
|
#endif
|
|
}
|
|
|
|
setStatus(Connecting);
|
|
|
|
#ifdef LIBSSH_FOUND
|
|
if (m_hostPreferences->useSshTunnel()) {
|
|
connect(m_sshTunnelThread, &VncSshTunnelThread::listenReady, this, [this] {
|
|
vncThread.setPort(m_sshTunnelThread->tunnelPort());
|
|
vncThread.start();
|
|
});
|
|
} else
|
|
#endif
|
|
{
|
|
vncThread.setPort(m_port);
|
|
vncThread.start();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool VncView::supportsScaling() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool VncView::supportsLocalCursor() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool VncView::supportsViewOnly() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void VncView::requestPassword(bool includingUsername)
|
|
{
|
|
qCDebug(KRDC) << "request password";
|
|
|
|
setStatus(Authenticating);
|
|
|
|
if (m_firstPasswordTry && !m_url.userName().isNull()) {
|
|
vncThread.setUsername(m_url.userName());
|
|
}
|
|
|
|
#ifndef QTONLY
|
|
// just try to get the password from the wallet the first time, otherwise it will loop (see issue #226283)
|
|
if (m_firstPasswordTry && m_hostPreferences->walletSupport()) {
|
|
QString walletPassword = readWalletPassword();
|
|
|
|
if (!walletPassword.isNull()) {
|
|
vncThread.setPassword(walletPassword);
|
|
m_firstPasswordTry = false;
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (m_firstPasswordTry && !m_url.password().isNull()) {
|
|
vncThread.setPassword(m_url.password());
|
|
m_firstPasswordTry = false;
|
|
return;
|
|
}
|
|
|
|
#ifdef QTONLY
|
|
bool ok;
|
|
if (includingUsername) {
|
|
QString username = QInputDialog::getText(this, // krazy:exclude=qclasses (code not used in kde build)
|
|
tr("Username required"),
|
|
tr("Please enter the username for the remote desktop:"),
|
|
QLineEdit::Normal,
|
|
m_url.userName(),
|
|
&ok); // krazy:exclude=qclasses
|
|
if (ok)
|
|
vncThread.setUsername(username);
|
|
else
|
|
startQuitting();
|
|
}
|
|
|
|
QString password = QInputDialog::getText(this, // krazy:exclude=qclasses
|
|
tr("Password required"),
|
|
tr("Please enter the password for the remote desktop:"),
|
|
QLineEdit::Password,
|
|
QString(),
|
|
&ok); // krazy:exclude=qclasses
|
|
m_firstPasswordTry = false;
|
|
if (ok)
|
|
vncThread.setPassword(password);
|
|
else
|
|
startQuitting();
|
|
#else
|
|
KPasswordDialog dialog(this, includingUsername ? KPasswordDialog::ShowUsernameLine : KPasswordDialog::NoFlags);
|
|
dialog.setPrompt(m_firstPasswordTry ? i18n("Access to the system requires a password.") : i18n("Authentication failed. Please try again."));
|
|
if (includingUsername)
|
|
dialog.setUsername(m_url.userName());
|
|
if (dialog.exec() == KPasswordDialog::Accepted) {
|
|
m_firstPasswordTry = false;
|
|
vncThread.setPassword(dialog.password());
|
|
if (includingUsername)
|
|
vncThread.setUsername(dialog.username());
|
|
} else {
|
|
qCDebug(KRDC) << "password dialog not accepted";
|
|
startQuitting();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef LIBSSH_FOUND
|
|
void VncView::sshRequestPassword(VncSshTunnelThread::PasswordRequestFlags flags)
|
|
{
|
|
qCDebug(KRDC) << "request ssh password";
|
|
#ifndef QTONLY
|
|
if (m_hostPreferences->walletSupport() && ((flags & VncSshTunnelThread::IgnoreWallet) != VncSshTunnelThread::IgnoreWallet)) {
|
|
const QString walletPassword = readWalletSshPassword();
|
|
|
|
if (!walletPassword.isNull()) {
|
|
m_sshTunnelThread->setPassword(walletPassword, VncSshTunnelThread::PasswordFromWallet);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
KPasswordDialog dialog(this);
|
|
dialog.setPrompt(i18n("Please enter the SSH password."));
|
|
if (dialog.exec() == KPasswordDialog::Accepted) {
|
|
m_sshTunnelThread->setPassword(dialog.password(), VncSshTunnelThread::PasswordFromDialog);
|
|
} else {
|
|
qCDebug(KRDC) << "ssh password dialog not accepted";
|
|
m_sshTunnelThread->userCanceledPasswordRequest();
|
|
// We need to use a single shot because otherwise startQuitting deletes the thread
|
|
// but we're here from a blocked queued connection and thus we deadlock
|
|
QTimer::singleShot(0, this, &VncView::startQuitting);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void VncView::outputErrorMessage(const QString &message)
|
|
{
|
|
qCritical(KRDC) << message;
|
|
|
|
if (message == QLatin1String("INTERNAL:APPLE_VNC_COMPATIBILTY")) {
|
|
setCursor(localDefaultCursor());
|
|
m_forceLocalCursor = true;
|
|
return;
|
|
}
|
|
|
|
startQuitting();
|
|
|
|
KMessageBox::error(this, message, i18n("VNC failure"));
|
|
|
|
Q_EMIT errorMessage(i18n("VNC failure"), message);
|
|
}
|
|
|
|
void VncView::sshErrorMessage(const QString &message)
|
|
{
|
|
qCritical(KRDC) << message;
|
|
|
|
startQuitting();
|
|
|
|
KMessageBox::error(this, message, i18n("SSH Tunnel failure"));
|
|
|
|
Q_EMIT errorMessage(i18n("SSH Tunnel failure"), message);
|
|
}
|
|
|
|
#ifndef QTONLY
|
|
HostPreferences *VncView::hostPreferences()
|
|
{
|
|
return m_hostPreferences;
|
|
}
|
|
#endif
|
|
|
|
void VncView::updateImage(int x, int y, int w, int h)
|
|
{
|
|
// qCDebug(KRDC) << "got update" << width() << height();
|
|
|
|
m_frame = vncThread.image();
|
|
|
|
if (!m_initDone) {
|
|
if (!vncThread.username().isEmpty()) {
|
|
m_url.setUserName(vncThread.username());
|
|
}
|
|
setAttribute(Qt::WA_StaticContents);
|
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
|
|
|
setCursor(((m_localCursorState == CursorOn) || m_forceLocalCursor) ? localDefaultCursor() : Qt::BlankCursor);
|
|
|
|
setFocusPolicy(Qt::WheelFocus);
|
|
setStatus(Connected);
|
|
Q_EMIT connected();
|
|
|
|
if (m_scale) {
|
|
#ifndef QTONLY
|
|
qCDebug(KRDC) << "Setting initial size w:" << m_hostPreferences->width() << " h:" << m_hostPreferences->height();
|
|
QSize frameSize = QSize(m_hostPreferences->width(), m_hostPreferences->height()) / devicePixelRatioF();
|
|
Q_EMIT framebufferSizeChanged(frameSize.width(), frameSize.height());
|
|
scaleResize(frameSize.width(), frameSize.height());
|
|
qCDebug(KRDC) << "m_frame.size():" << m_frame.size() << "size()" << size();
|
|
#else
|
|
// TODO: qtonly alternative
|
|
#endif
|
|
}
|
|
|
|
m_initDone = true;
|
|
|
|
#ifndef QTONLY
|
|
if (m_hostPreferences->walletSupport()) {
|
|
saveWalletPassword(vncThread.password());
|
|
#ifdef LIBSSH_FOUND
|
|
if (m_hostPreferences->useSshTunnel()) {
|
|
saveWalletSshPassword();
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
|
|
const QSize frameSize = m_frame.size() / m_frame.devicePixelRatio();
|
|
if ((y == 0 && x == 0) && (frameSize != size())) {
|
|
qCDebug(KRDC) << "Updating framebuffer size";
|
|
if (m_scale) {
|
|
setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
|
|
if (parentWidget())
|
|
scaleResize(parentWidget()->width(), parentWidget()->height());
|
|
} else {
|
|
qCDebug(KRDC) << "Resizing: " << m_frame.width() << m_frame.height();
|
|
resize(frameSize);
|
|
setMaximumSize(frameSize); // This is a hack to force Qt to center the view in the scroll area
|
|
setMinimumSize(frameSize);
|
|
Q_EMIT framebufferSizeChanged(frameSize.width(), frameSize.height());
|
|
}
|
|
}
|
|
|
|
const auto dpr = m_frame.devicePixelRatio();
|
|
repaint(QRectF(x / dpr * m_horizontalFactor, y / dpr * m_verticalFactor, w / dpr * m_horizontalFactor, h / dpr * m_verticalFactor)
|
|
.toAlignedRect()
|
|
.adjusted(-1, -1, 1, 1));
|
|
}
|
|
|
|
void VncView::setViewOnly(bool viewOnly)
|
|
{
|
|
RemoteView::setViewOnly(viewOnly);
|
|
|
|
if (viewOnly)
|
|
setCursor(Qt::ArrowCursor);
|
|
else
|
|
setCursor(m_localCursorState == CursorOn ? localDefaultCursor() : Qt::BlankCursor);
|
|
}
|
|
|
|
void VncView::showLocalCursor(LocalCursorState state)
|
|
{
|
|
RemoteView::showLocalCursor(state);
|
|
|
|
if (state == CursorOn) {
|
|
// show local cursor, hide remote one
|
|
setCursor(localDefaultCursor());
|
|
vncThread.setShowLocalCursor(true);
|
|
} else {
|
|
// hide local cursor, show remote one
|
|
setCursor(Qt::BlankCursor);
|
|
vncThread.setShowLocalCursor(false);
|
|
}
|
|
}
|
|
|
|
void VncView::enableScaling(bool scale)
|
|
{
|
|
RemoteView::enableScaling(scale);
|
|
|
|
if (scale) {
|
|
setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
|
|
setMinimumSize(1, 1);
|
|
if (parentWidget())
|
|
scaleResize(parentWidget()->width(), parentWidget()->height());
|
|
} else {
|
|
m_verticalFactor = 1.0;
|
|
m_horizontalFactor = 1.0;
|
|
|
|
const QSize frameSize = m_frame.size() / m_frame.devicePixelRatio();
|
|
setMaximumSize(frameSize); // This is a hack to force Qt to center the view in the scroll area
|
|
setMinimumSize(frameSize);
|
|
resize(frameSize);
|
|
}
|
|
}
|
|
|
|
void VncView::setCut(const QString &text)
|
|
{
|
|
QMimeData *data = new QMimeData;
|
|
data->setText(text);
|
|
remoteClipboardChanged(data);
|
|
}
|
|
|
|
void VncView::paintEvent(QPaintEvent *event)
|
|
{
|
|
// qCDebug(KRDC) << "paint event: x: " << m_x << ", y: " << m_y << ", w: " << m_w << ", h: " << m_h;
|
|
if (m_frame.isNull() || m_frame.format() == QImage::Format_Invalid) {
|
|
qCDebug(KRDC) << "no valid image to paint";
|
|
RemoteView::paintEvent(event);
|
|
return;
|
|
}
|
|
|
|
event->accept();
|
|
|
|
QPainter painter(this);
|
|
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
|
|
|
const auto dpr = m_frame.devicePixelRatio();
|
|
const QRectF dstRect = event->rect();
|
|
const QRectF srcRect(dstRect.x() * dpr / m_horizontalFactor,
|
|
dstRect.y() * dpr / m_verticalFactor,
|
|
dstRect.width() * dpr / m_horizontalFactor,
|
|
dstRect.height() * dpr / m_verticalFactor);
|
|
painter.drawImage(dstRect, m_frame, srcRect);
|
|
|
|
RemoteView::paintEvent(event);
|
|
}
|
|
|
|
void VncView::resizeEvent(QResizeEvent *event)
|
|
{
|
|
RemoteView::resizeEvent(event);
|
|
update();
|
|
}
|
|
|
|
void VncView::handleMouseEvent(QMouseEvent *e)
|
|
{
|
|
if (e->type() != QEvent::MouseMove) {
|
|
if ((e->type() == QEvent::MouseButtonPress) || (e->type() == QEvent::MouseButtonDblClick)) {
|
|
if (e->button() & Qt::LeftButton)
|
|
m_buttonMask |= 0x01;
|
|
if (e->button() & Qt::MiddleButton)
|
|
m_buttonMask |= 0x02;
|
|
if (e->button() & Qt::RightButton)
|
|
m_buttonMask |= 0x04;
|
|
if (e->button() & Qt::ExtraButton1)
|
|
m_buttonMask |= 0x80;
|
|
} else if (e->type() == QEvent::MouseButtonRelease) {
|
|
if (e->button() & Qt::LeftButton)
|
|
m_buttonMask &= 0xfe;
|
|
if (e->button() & Qt::MiddleButton)
|
|
m_buttonMask &= 0xfd;
|
|
if (e->button() & Qt::RightButton)
|
|
m_buttonMask &= 0xfb;
|
|
if (e->button() & Qt::ExtraButton1)
|
|
m_buttonMask &= ~0x80;
|
|
}
|
|
}
|
|
|
|
const auto dpr = devicePixelRatioF();
|
|
QPointF screenPos = e->screenPos();
|
|
// We need to restore mouse position in device coordinates.
|
|
// QMouseEvent::localPos() can be rounded (bug in Qt), but QMouseEvent::screenPos() is not.
|
|
QPointF pos = (e->pos() + (screenPos - screenPos.toPoint())) * dpr;
|
|
vncThread.mouseEvent(qRound(pos.x() / m_horizontalFactor), qRound(pos.y() / m_verticalFactor), m_buttonMask);
|
|
}
|
|
|
|
void VncView::handleWheelEvent(QWheelEvent *event)
|
|
{
|
|
const auto delta = event->angleDelta();
|
|
// Reset accumulation if direction changed
|
|
const int accV = (delta.y() < 0) == (m_wheelRemainderV < 0) ? m_wheelRemainderV : 0;
|
|
const int accH = (delta.x() < 0) == (m_wheelRemainderH < 0) ? m_wheelRemainderH : 0;
|
|
// A wheel tick is 15° or 120 eights of a degree
|
|
const int verTicks = (delta.y() + accV) / 120;
|
|
const int horTicks = (delta.x() + accH) / 120;
|
|
m_wheelRemainderV = (delta.y() + accV) % 120;
|
|
m_wheelRemainderH = (delta.x() + accH) % 120;
|
|
|
|
const auto dpr = devicePixelRatioF();
|
|
// We need to restore mouse position in device coordinates.
|
|
const QPointF pos = event->position() * dpr;
|
|
|
|
const int x = qRound(pos.x() / m_horizontalFactor);
|
|
const int y = qRound(pos.y() / m_verticalFactor);
|
|
|
|
// Fast movement might generate more than one tick, loop for each axis
|
|
int eb = verTicks < 0 ? 0x10 : 0x08;
|
|
for (int i = 0; i < std::abs(verTicks); i++) {
|
|
vncThread.mouseEvent(x, y, eb | m_buttonMask);
|
|
vncThread.mouseEvent(x, y, m_buttonMask);
|
|
}
|
|
|
|
eb = horTicks < 0 ? 0x40 : 0x20;
|
|
for (int i = 0; i < std::abs(horTicks); i++) {
|
|
vncThread.mouseEvent(x, y, eb | m_buttonMask);
|
|
vncThread.mouseEvent(x, y, m_buttonMask);
|
|
}
|
|
|
|
event->accept();
|
|
}
|
|
|
|
#ifdef LIBSSH_FOUND
|
|
QString VncView::readWalletSshPassword()
|
|
{
|
|
return readWalletPasswordForKey(QStringLiteral("SSHTUNNEL") + m_url.toDisplayString(QUrl::StripTrailingSlash));
|
|
}
|
|
|
|
void VncView::saveWalletSshPassword()
|
|
{
|
|
saveWalletPasswordForKey(QStringLiteral("SSHTUNNEL") + m_url.toDisplayString(QUrl::StripTrailingSlash), m_sshTunnelThread->password());
|
|
}
|
|
#endif
|
|
|
|
void VncView::handleKeyEvent(QKeyEvent *e)
|
|
{
|
|
// strip away autorepeating KeyRelease; see bug #206598
|
|
if (e->isAutoRepeat() && (e->type() == QEvent::KeyRelease))
|
|
return;
|
|
|
|
// parts of this code are based on https://github.com/veyon/veyon/blob/master/core/src/VncView.cpp
|
|
rfbKeySym k = e->nativeVirtualKey();
|
|
|
|
// we do not handle Key_Backtab separately as the Shift-modifier
|
|
// is already enabled
|
|
if (e->key() == Qt::Key_Backtab) {
|
|
k = XK_Tab;
|
|
}
|
|
|
|
const bool pressed = (e->type() == QEvent::KeyPress);
|
|
if (k) {
|
|
vncThread.keyEvent(k, pressed);
|
|
}
|
|
}
|
|
|
|
void VncView::handleLocalClipboardChanged(const QMimeData *data)
|
|
{
|
|
#ifndef QTONLY
|
|
if (m_hostPreferences->dontCopyPasswords()) {
|
|
if (data->hasFormat(QLatin1String("x-kde-passwordManagerHint"))) {
|
|
qCDebug(KRDC) << "VncView::clipboardDataChanged data hasFormat x-kde-passwordManagerHint -- ignoring";
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// TODO: VNC backend doesn't support other formats like hasImage(), hasHtml()
|
|
if (data->hasText()) {
|
|
const QString text = data->text();
|
|
vncThread.clientCut(text);
|
|
}
|
|
}
|
|
|
|
#include "moc_vncview.cpp"
|