File indexing completed on 2024-04-28 08:51:02

0001 /*
0002     SPDX-FileCopyrightText: 2007-2013 Urs Wolfer <uwolfer@kde.org>
0003     SPDX-FileCopyrightText: 2021 Rafał Lalik <rafallalik@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "vncview.h"
0009 #include "krdc_debug.h"
0010 
0011 #include <QApplication>
0012 #include <QImage>
0013 #include <QPainter>
0014 #include <QMouseEvent>
0015 #include <QTimer>
0016 #include <QMimeData>
0017 
0018 #ifdef QTONLY
0019     #include <QMessageBox>
0020     #include <QInputDialog>
0021     #define KMessageBox QMessageBox
0022     #define error(parent, message, caption) \
0023         critical(parent, caption, message)
0024 #else
0025     #include "settings.h"
0026     #include <KActionCollection>
0027     #include <KMainWindow>
0028     #include <KMessageBox>
0029     #include <KPasswordDialog>
0030     #include <KXMLGUIClient>
0031 #endif
0032 
0033 // Definition of key modifier mask constants
0034 #define KMOD_Alt_R  0x01
0035 #define KMOD_Alt_L  0x02
0036 #define KMOD_Meta_L     0x04
0037 #define KMOD_Control_L  0x08
0038 #define KMOD_Shift_L    0x10
0039 
0040 VncView::VncView(QWidget *parent, const QUrl &url, KConfigGroup configGroup)
0041         : RemoteView(parent),
0042         m_initDone(false),
0043         m_buttonMask(0),
0044         m_quitFlag(false),
0045         m_firstPasswordTry(true),
0046         m_dontSendClipboard(false),
0047         m_horizontalFactor(1.0),
0048         m_verticalFactor(1.0),
0049         m_wheelRemainderV(0),
0050         m_wheelRemainderH(0),
0051         m_forceLocalCursor(false)
0052 #ifdef LIBSSH_FOUND
0053         , m_sshTunnelThread(nullptr)
0054 #endif
0055 {
0056     m_url = url;
0057     m_host = url.host();
0058     m_port = url.port();
0059 
0060     if (m_port <= 0) // port is invalid or empty...
0061         m_port = 5900; // fallback: try an often used VNC port
0062 
0063     if (m_port < 100) // the user most likely used the short form (e.g. :1)
0064         m_port += 5900;
0065 
0066     // BlockingQueuedConnection can cause deadlocks when exiting, handled in startQuitting()
0067     connect(&vncThread, SIGNAL(imageUpdated(int,int,int,int)), this, SLOT(updateImage(int,int,int,int)), Qt::BlockingQueuedConnection);
0068     connect(&vncThread, SIGNAL(gotCut(QString)), this, SLOT(setCut(QString)), Qt::BlockingQueuedConnection);
0069     connect(&vncThread, SIGNAL(passwordRequest(bool)), this, SLOT(requestPassword(bool)), Qt::BlockingQueuedConnection);
0070     connect(&vncThread, SIGNAL(outputErrorMessage(QString)), this, SLOT(outputErrorMessage(QString)));
0071     connect(&vncThread, &VncClientThread::gotCursor, this, [this](QCursor cursor){ setCursor(cursor); });
0072 
0073     m_clipboard = QApplication::clipboard();
0074     connect(m_clipboard, SIGNAL(dataChanged()), this, SLOT(clipboardDataChanged()));
0075 
0076 #ifndef QTONLY
0077     m_hostPreferences = new VncHostPreferences(configGroup, this);
0078 #else
0079     Q_UNUSED(configGroup);
0080 #endif
0081 }
0082 
0083 VncView::~VncView()
0084 {
0085     if (!m_quitFlag) startQuitting();
0086 }
0087 
0088 bool VncView::eventFilter(QObject *obj, QEvent *event)
0089 {
0090     if (m_viewOnly) {
0091         if (event->type() == QEvent::KeyPress ||
0092                 event->type() == QEvent::KeyRelease ||
0093                 event->type() == QEvent::MouseButtonDblClick ||
0094                 event->type() == QEvent::MouseButtonPress ||
0095                 event->type() == QEvent::MouseButtonRelease ||
0096                 event->type() == QEvent::Wheel ||
0097                 event->type() == QEvent::MouseMove)
0098             return true;
0099     }
0100 
0101     return RemoteView::eventFilter(obj, event);
0102 }
0103 
0104 QSize VncView::framebufferSize()
0105 {
0106     return m_frame.size() / devicePixelRatioF();
0107 }
0108 
0109 QSize VncView::sizeHint() const
0110 {
0111     return size();
0112 }
0113 
0114 QSize VncView::minimumSizeHint() const
0115 {
0116     return size();
0117 }
0118 
0119 void VncView::scaleResize(int w, int h)
0120 {
0121     RemoteView::scaleResize(w, h);
0122 
0123     qCDebug(KRDC) << w << h;
0124     if (m_scale) {
0125         const QSize frameSize = m_frame.size() / m_frame.devicePixelRatio();
0126 
0127         m_verticalFactor = static_cast<qreal>(h) / frameSize.height() * m_factor;
0128         m_horizontalFactor = static_cast<qreal>(w) / frameSize.width() * m_factor;
0129 
0130 #ifndef QTONLY
0131         if (Settings::keepAspectRatio()) {
0132             m_verticalFactor = m_horizontalFactor = qMin(m_verticalFactor, m_horizontalFactor);
0133         }
0134 #else
0135         m_verticalFactor = m_horizontalFactor = qMin(m_verticalFactor, m_horizontalFactor);
0136 #endif
0137 
0138         const qreal newW = frameSize.width() * m_horizontalFactor;
0139         const qreal newH = frameSize.height() * m_verticalFactor;
0140         setMaximumSize(newW, newH); //This is a hack to force Qt to center the view in the scroll area
0141         resize(newW, newH);
0142     }
0143 }
0144 
0145 void VncView::updateConfiguration()
0146 {
0147     RemoteView::updateConfiguration();
0148 
0149     // Update the scaling mode in case KeepAspectRatio changed
0150     scaleResize(parentWidget()->width(), parentWidget()->height());
0151 }
0152 
0153 void VncView::startQuitting()
0154 {
0155     // Already quitted. No need to clean up again and also avoid triggering
0156     // `disconnected` signal below.
0157     if (m_quitFlag)
0158         return;
0159 
0160     qCDebug(KRDC) << "about to quit";
0161 
0162     setStatus(Disconnecting);
0163 
0164     m_quitFlag = true;
0165 
0166     vncThread.stop();
0167 
0168     unpressModifiers();
0169 
0170     // Disconnect all signals so that we don't get any more callbacks from the client thread
0171     vncThread.disconnect();
0172 
0173     vncThread.quit();
0174 
0175 #ifdef LIBSSH_FOUND
0176     if (m_sshTunnelThread) {
0177         delete m_sshTunnelThread;
0178         m_sshTunnelThread = nullptr;
0179     }
0180 #endif
0181 
0182     const bool quitSuccess = vncThread.wait(500);
0183     if (!quitSuccess) {
0184         // happens when vncThread wants to call a slot via BlockingQueuedConnection,
0185         // needs an event loop in this thread so execution continues after 'emit'
0186         QEventLoop loop;
0187         if (!loop.processEvents()) {
0188             qCDebug(KRDC) << "BUG: deadlocked, but no events to deliver?";
0189         }
0190         vncThread.wait(500);
0191     }
0192 
0193     qCDebug(KRDC) << "Quit VNC thread success:" << quitSuccess;
0194 
0195     // emit the disconnect siginal only after all the events are handled.
0196     // Otherwise some error messages might be thrown away without
0197     // showing to the user.
0198     Q_EMIT disconnected();
0199     setStatus(Disconnected);
0200 }
0201 
0202 bool VncView::isQuitting()
0203 {
0204     return m_quitFlag;
0205 }
0206 
0207 bool VncView::start()
0208 {
0209     // This flag is used to make sure `startQuitting` only run once.
0210     // This should not matter for now but it is an easy way to make sure
0211     // things works in case the object may get reused.
0212     m_quitFlag = false;
0213 
0214     QString vncHost = m_host;
0215 #ifdef LIBSSH_FOUND
0216     if (m_hostPreferences->useSshTunnel()) {
0217         Q_ASSERT(!m_sshTunnelThread);
0218 
0219         m_sshTunnelThread = new VncSshTunnelThread(m_host.toUtf8(),
0220                                                    m_port,
0221                                                    /* tunnelPort */ 0,
0222                                                    m_hostPreferences->sshTunnelPort(),
0223                                                    m_hostPreferences->sshTunnelUserName().toUtf8(),
0224                                                    m_hostPreferences->useSshTunnelLoopback());
0225         connect(m_sshTunnelThread, &VncSshTunnelThread::passwordRequest, this, &VncView::sshRequestPassword, Qt::BlockingQueuedConnection);
0226         connect(m_sshTunnelThread, &VncSshTunnelThread::errorMessage, this, &VncView::sshErrorMessage);
0227         m_sshTunnelThread->start();
0228 
0229         if (m_hostPreferences->useSshTunnelLoopback()) {
0230             vncHost = QStringLiteral("127.0.0.1");
0231         }
0232     }
0233 #endif
0234 
0235     vncThread.setHost(vncHost);
0236     RemoteView::Quality quality;
0237 #ifdef QTONLY
0238     quality = (RemoteView::Quality)((QCoreApplication::arguments().count() > 2) ?
0239         QCoreApplication::arguments().at(2).toInt() : 2);
0240 #else
0241     quality = m_hostPreferences->quality();
0242 #endif
0243 
0244     vncThread.setQuality(quality);
0245     vncThread.setDevicePixelRatio(devicePixelRatioF());
0246 
0247     // set local cursor on by default because low quality mostly means slow internet connection
0248     if (quality == RemoteView::Low) {
0249         showLocalCursor(RemoteView::CursorOn);
0250 #ifndef QTONLY
0251         // KRDC does always just have one main window, so at(0) is safe
0252         KXMLGUIClient *mainWindow = dynamic_cast<KXMLGUIClient*>(KMainWindow::memberList().at(0));
0253         if (mainWindow)
0254             mainWindow->actionCollection()->action(QLatin1String("show_local_cursor"))->setChecked(true);
0255 #endif
0256     }
0257 
0258     setStatus(Connecting);
0259 
0260 #ifdef LIBSSH_FOUND
0261     if (m_hostPreferences->useSshTunnel()) {
0262         connect(m_sshTunnelThread, &VncSshTunnelThread::listenReady, this, [this] {
0263             vncThread.setPort(m_sshTunnelThread->tunnelPort());
0264             vncThread.start();
0265         });
0266     }
0267     else
0268 #endif
0269     {
0270         vncThread.setPort(m_port);
0271         vncThread.start();
0272     }
0273     return true;
0274 }
0275 
0276 bool VncView::supportsScaling() const
0277 {
0278     return true;
0279 }
0280 
0281 bool VncView::supportsLocalCursor() const
0282 {
0283     return true;
0284 }
0285 
0286 bool VncView::supportsViewOnly() const
0287 {
0288     return true;
0289 }
0290 
0291 void VncView::requestPassword(bool includingUsername)
0292 {
0293     qCDebug(KRDC) << "request password";
0294 
0295     setStatus(Authenticating);
0296 
0297     if (m_firstPasswordTry && !m_url.userName().isNull()) {
0298         vncThread.setUsername(m_url.userName());
0299     }
0300 
0301 #ifndef QTONLY
0302     // just try to get the password from the wallet the first time, otherwise it will loop (see issue #226283)
0303     if (m_firstPasswordTry && m_hostPreferences->walletSupport()) {
0304         QString walletPassword = readWalletPassword();
0305 
0306         if (!walletPassword.isNull()) {
0307             vncThread.setPassword(walletPassword);
0308             m_firstPasswordTry = false;
0309             return;
0310         }
0311     }
0312 #endif
0313 
0314     if (m_firstPasswordTry && !m_url.password().isNull()) {
0315         vncThread.setPassword(m_url.password());
0316         m_firstPasswordTry = false;
0317         return;
0318     }
0319 
0320 #ifdef QTONLY
0321     bool ok;
0322     if (includingUsername) {
0323         QString username = QInputDialog::getText(this, //krazy:exclude=qclasses (code not used in kde build)
0324                                                  tr("Username required"),
0325                                                  tr("Please enter the username for the remote desktop:"),
0326                                                  QLineEdit::Normal, m_url.userName(), &ok); //krazy:exclude=qclasses
0327         if (ok)
0328             vncThread.setUsername(username);
0329         else
0330             startQuitting();
0331     }
0332 
0333     QString password = QInputDialog::getText(this, //krazy:exclude=qclasses
0334                                              tr("Password required"),
0335                                              tr("Please enter the password for the remote desktop:"),
0336                                              QLineEdit::Password, QString(), &ok); //krazy:exclude=qclasses
0337     m_firstPasswordTry = false;
0338     if (ok)
0339         vncThread.setPassword(password);
0340     else
0341         startQuitting();
0342 #else
0343     KPasswordDialog dialog(this, includingUsername ? KPasswordDialog::ShowUsernameLine : KPasswordDialog::NoFlags);
0344     dialog.setPrompt(m_firstPasswordTry ? i18n("Access to the system requires a password.")
0345                                         : i18n("Authentication failed. Please try again."));
0346     if (includingUsername) dialog.setUsername(m_url.userName());
0347     if (dialog.exec() == KPasswordDialog::Accepted) {
0348         m_firstPasswordTry = false;
0349         vncThread.setPassword(dialog.password());
0350         if (includingUsername) vncThread.setUsername(dialog.username());
0351     } else {
0352         qCDebug(KRDC) << "password dialog not accepted";
0353         startQuitting();
0354     }
0355 #endif
0356 }
0357 
0358 #ifdef LIBSSH_FOUND
0359 void VncView::sshRequestPassword(VncSshTunnelThread::PasswordRequestFlags flags)
0360 {
0361     qCDebug(KRDC) << "request ssh password";
0362 #ifndef QTONLY
0363     if (m_hostPreferences->walletSupport() && ((flags & VncSshTunnelThread::IgnoreWallet) != VncSshTunnelThread::IgnoreWallet)) {
0364         const QString walletPassword = readWalletSshPassword();
0365 
0366         if (!walletPassword.isNull()) {
0367             m_sshTunnelThread->setPassword(walletPassword, VncSshTunnelThread::PasswordFromWallet);
0368             return;
0369         }
0370     }
0371 #endif
0372     KPasswordDialog dialog(this);
0373     dialog.setPrompt(i18n("Please enter the SSH password."));
0374     if (dialog.exec() == KPasswordDialog::Accepted) {
0375         m_sshTunnelThread->setPassword(dialog.password(), VncSshTunnelThread::PasswordFromDialog);
0376     } else {
0377         qCDebug(KRDC) << "ssh password dialog not accepted";
0378         m_sshTunnelThread->userCanceledPasswordRequest();
0379         // We need to use a single shot because otherwise startQuitting deletes the thread
0380         // but we're here from a blocked queued connection and thus we deadlock
0381         QTimer::singleShot(0, this, &VncView::startQuitting);
0382     }
0383 }
0384 #endif
0385 
0386 void VncView::outputErrorMessage(const QString &message)
0387 {
0388     qCritical(KRDC) << message;
0389 
0390     if (message == QLatin1String("INTERNAL:APPLE_VNC_COMPATIBILTY")) {
0391         setCursor(localDefaultCursor());
0392         m_forceLocalCursor = true;
0393         return;
0394     }
0395 
0396     startQuitting();
0397 
0398     KMessageBox::error(this, message, i18n("VNC failure"));
0399 
0400     Q_EMIT errorMessage(i18n("VNC failure"), message);
0401 }
0402 
0403 void VncView::sshErrorMessage(const QString &message)
0404 {
0405     qCritical(KRDC) << message;
0406 
0407     startQuitting();
0408 
0409     KMessageBox::error(this, message, i18n("SSH Tunnel failure"));
0410 
0411     Q_EMIT errorMessage(i18n("SSH Tunnel failure"), message);
0412 }
0413 
0414 #ifndef QTONLY
0415 HostPreferences* VncView::hostPreferences()
0416 {
0417     return m_hostPreferences;
0418 }
0419 #endif
0420 
0421 void VncView::updateImage(int x, int y, int w, int h)
0422 {
0423 //     qCDebug(KRDC) << "got update" << width() << height();
0424 
0425     m_frame = vncThread.image();
0426 
0427     if (!m_initDone) {
0428         if (!vncThread.username().isEmpty()) {
0429             m_url.setUserName(vncThread.username());
0430         }
0431         setAttribute(Qt::WA_StaticContents);
0432         setAttribute(Qt::WA_OpaquePaintEvent);
0433         installEventFilter(this);
0434 
0435         setCursor(((m_localCursorState == CursorOn) || m_forceLocalCursor) ? localDefaultCursor() : Qt::BlankCursor);
0436 
0437         setMouseTracking(true); // get mouse events even when there is no mousebutton pressed
0438         setFocusPolicy(Qt::WheelFocus);
0439         setStatus(Connected);
0440         Q_EMIT connected();
0441 
0442         if (m_scale) {
0443 #ifndef QTONLY
0444             qCDebug(KRDC) << "Setting initial size w:" <<m_hostPreferences->width() << " h:" << m_hostPreferences->height();
0445             QSize frameSize = QSize(m_hostPreferences->width(), m_hostPreferences->height()) / devicePixelRatioF();
0446             Q_EMIT framebufferSizeChanged(frameSize.width(), frameSize.height());
0447             scaleResize(frameSize.width(), frameSize.height());
0448             qCDebug(KRDC) << "m_frame.size():" << m_frame.size() << "size()" << size();
0449 #else
0450 //TODO: qtonly alternative
0451 #endif
0452         }
0453 
0454         m_initDone = true;
0455 
0456 #ifndef QTONLY
0457         if (m_hostPreferences->walletSupport()) {
0458             saveWalletPassword(vncThread.password());
0459 #ifdef LIBSSH_FOUND
0460             if (m_hostPreferences->useSshTunnel()) {
0461                 saveWalletSshPassword();
0462             }
0463 #endif
0464         }
0465 #endif
0466     }
0467 
0468     const QSize frameSize = m_frame.size() / m_frame.devicePixelRatio();
0469     if ((y == 0 && x == 0) && (frameSize != size())) {
0470         qCDebug(KRDC) << "Updating framebuffer size";
0471         if (m_scale) {
0472             setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
0473             if (parentWidget())
0474                 scaleResize(parentWidget()->width(), parentWidget()->height());
0475         } else {
0476             qCDebug(KRDC) << "Resizing: " << m_frame.width() << m_frame.height();
0477             resize(frameSize);
0478             setMaximumSize(frameSize); //This is a hack to force Qt to center the view in the scroll area
0479             setMinimumSize(frameSize);
0480             Q_EMIT framebufferSizeChanged(frameSize.width(), frameSize.height());
0481         }
0482     }
0483 
0484     const auto dpr = m_frame.devicePixelRatio();
0485     repaint(QRectF(x / dpr * m_horizontalFactor, y / dpr * m_verticalFactor, w / dpr * m_horizontalFactor, h / dpr * m_verticalFactor).toAlignedRect().adjusted(-1,-1,1,1));
0486 }
0487 
0488 void VncView::setViewOnly(bool viewOnly)
0489 {
0490     RemoteView::setViewOnly(viewOnly);
0491 
0492     m_dontSendClipboard = viewOnly;
0493 
0494     if (viewOnly)
0495         setCursor(Qt::ArrowCursor);
0496     else
0497         setCursor(m_localCursorState == CursorOn ? localDefaultCursor() : Qt::BlankCursor);
0498 }
0499 
0500 void VncView::showLocalCursor(LocalCursorState state)
0501 {
0502     RemoteView::showLocalCursor(state);
0503 
0504     if (state == CursorOn) {
0505         // show local cursor, hide remote one
0506         setCursor(localDefaultCursor());
0507         vncThread.setShowLocalCursor(true);
0508     } else {
0509         // hide local cursor, show remote one
0510         setCursor(Qt::BlankCursor);
0511         vncThread.setShowLocalCursor(false);
0512     }
0513 }
0514 
0515 void VncView::enableScaling(bool scale)
0516 {
0517     RemoteView::enableScaling(scale);
0518 
0519     if (scale) {
0520         setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
0521         setMinimumSize(1, 1);
0522         if (parentWidget())
0523             scaleResize(parentWidget()->width(), parentWidget()->height());
0524     } else {
0525         m_verticalFactor = 1.0;
0526         m_horizontalFactor = 1.0;
0527 
0528         const QSize frameSize = m_frame.size() / m_frame.devicePixelRatio();
0529         setMaximumSize(frameSize); //This is a hack to force Qt to center the view in the scroll area
0530         setMinimumSize(frameSize);
0531         resize(frameSize);
0532     }
0533 }
0534 
0535 void VncView::setCut(const QString &text)
0536 {
0537     const bool saved_dontSendClipboard = m_dontSendClipboard;
0538     m_dontSendClipboard = true;
0539     m_clipboard->setText(text, QClipboard::Clipboard);
0540     m_dontSendClipboard = saved_dontSendClipboard;
0541 }
0542 
0543 void VncView::paintEvent(QPaintEvent *event)
0544 {
0545 //     qCDebug(KRDC) << "paint event: x: " << m_x << ", y: " << m_y << ", w: " << m_w << ", h: " << m_h;
0546     if (m_frame.isNull() || m_frame.format() == QImage::Format_Invalid) {
0547         qCDebug(KRDC) << "no valid image to paint";
0548         RemoteView::paintEvent(event);
0549         return;
0550     }
0551 
0552     event->accept();
0553 
0554     QPainter painter(this);
0555     painter.setRenderHint(QPainter::SmoothPixmapTransform);
0556 
0557     const auto dpr = m_frame.devicePixelRatio();
0558     const QRectF dstRect = event->rect();
0559     const QRectF srcRect(dstRect.x() * dpr / m_horizontalFactor, dstRect.y() * dpr / m_verticalFactor,
0560                          dstRect.width() * dpr / m_horizontalFactor, dstRect.height() * dpr / m_verticalFactor);
0561     painter.drawImage(dstRect, m_frame, srcRect);
0562 
0563     RemoteView::paintEvent(event);
0564 }
0565 
0566 void VncView::resizeEvent(QResizeEvent *event)
0567 {
0568     RemoteView::resizeEvent(event);
0569     update();
0570 }
0571 
0572 bool VncView::event(QEvent *event)
0573 {
0574     switch (event->type()) {
0575     case QEvent::KeyPress:
0576     case QEvent::KeyRelease:
0577 //         qCDebug(KRDC) << "keyEvent";
0578         keyEventHandler(static_cast<QKeyEvent*>(event));
0579         return true;
0580         break;
0581     case QEvent::MouseButtonDblClick:
0582     case QEvent::MouseButtonPress:
0583     case QEvent::MouseButtonRelease:
0584     case QEvent::MouseMove:
0585 //         qCDebug(KRDC) << "mouseEvent";
0586         mouseEventHandler(static_cast<QMouseEvent*>(event));
0587         return true;
0588         break;
0589     case QEvent::Wheel:
0590 //         qCDebug(KRDC) << "wheelEvent";
0591         wheelEventHandler(static_cast<QWheelEvent*>(event));
0592         return true;
0593         break;
0594     default:
0595         return RemoteView::event(event);
0596     }
0597 }
0598 
0599 void VncView::mouseEventHandler(QMouseEvent *e)
0600 {
0601     if (e->type() != QEvent::MouseMove) {
0602         if ((e->type() == QEvent::MouseButtonPress) ||
0603                 (e->type() == QEvent::MouseButtonDblClick)) {
0604             if (e->button() & Qt::LeftButton)
0605                 m_buttonMask |= 0x01;
0606             if (e->button() & Qt::MiddleButton)
0607                 m_buttonMask |= 0x02;
0608             if (e->button() & Qt::RightButton)
0609                 m_buttonMask |= 0x04;
0610             if (e->button() & Qt::ExtraButton1)
0611                 m_buttonMask |= 0x80;
0612         } else if (e->type() == QEvent::MouseButtonRelease) {
0613             if (e->button() & Qt::LeftButton)
0614                 m_buttonMask &= 0xfe;
0615             if (e->button() & Qt::MiddleButton)
0616                 m_buttonMask &= 0xfd;
0617             if (e->button() & Qt::RightButton)
0618                 m_buttonMask &= 0xfb;
0619             if (e->button() & Qt::ExtraButton1)
0620                 m_buttonMask &= ~0x80;
0621         }
0622     }
0623 
0624     const auto dpr = devicePixelRatioF();
0625     QPointF screenPos = e->screenPos();
0626     // We need to restore mouse position in device coordinates.
0627     // QMouseEvent::localPos() can be rounded (bug in Qt), but QMouseEvent::screenPos() is not.
0628     QPointF pos = (e->pos() + (screenPos - screenPos.toPoint())) * dpr;
0629     vncThread.mouseEvent(qRound(pos.x() / m_horizontalFactor), qRound(pos.y() / m_verticalFactor), m_buttonMask);
0630 }
0631 
0632 void VncView::wheelEventHandler(QWheelEvent *event)
0633 {
0634     const auto delta = event->angleDelta();
0635     // Reset accumulation if direction changed
0636     const int accV = (delta.y() < 0) == (m_wheelRemainderV < 0) ? m_wheelRemainderV : 0;
0637     const int accH = (delta.x() < 0) == (m_wheelRemainderH < 0) ? m_wheelRemainderH : 0;
0638     // A wheel tick is 15° or 120 eights of a degree
0639     const int verTicks = (delta.y() + accV) / 120;
0640     const int horTicks = (delta.x() + accH) / 120;
0641     m_wheelRemainderV = (delta.y() + accV) % 120;
0642     m_wheelRemainderH = (delta.x() + accH) % 120;
0643 
0644     const auto dpr = devicePixelRatioF();
0645     // We need to restore mouse position in device coordinates.
0646     const QPointF pos = event->position() * dpr;
0647 
0648     const int x = qRound(pos.x() / m_horizontalFactor);
0649     const int y = qRound(pos.y() / m_verticalFactor);
0650 
0651     // Fast movement might generate more than one tick, loop for each axis
0652     int eb = verTicks < 0 ? 0x10 : 0x08;
0653     for (int i = 0; i < std::abs(verTicks); i++) {
0654         vncThread.mouseEvent(x, y, eb | m_buttonMask);
0655         vncThread.mouseEvent(x, y, m_buttonMask);
0656     }
0657 
0658     eb = horTicks < 0 ? 0x40 : 0x20;
0659     for (int i = 0; i < std::abs(horTicks); i++) {
0660         vncThread.mouseEvent(x, y, eb | m_buttonMask);
0661         vncThread.mouseEvent(x, y, m_buttonMask);
0662     }
0663 
0664     event->accept();
0665 }
0666 
0667 #ifdef LIBSSH_FOUND
0668 QString VncView::readWalletSshPassword()
0669 {
0670     return readWalletPasswordForKey(QStringLiteral("SSHTUNNEL") + m_url.toDisplayString(QUrl::StripTrailingSlash));
0671 }
0672 
0673 void VncView::saveWalletSshPassword()
0674 {
0675     saveWalletPasswordForKey(QStringLiteral("SSHTUNNEL") + m_url.toDisplayString(QUrl::StripTrailingSlash), m_sshTunnelThread->password());
0676 }
0677 #endif
0678 
0679 void VncView::keyEventHandler(QKeyEvent *e)
0680 {
0681     // strip away autorepeating KeyRelease; see bug #206598
0682     if (e->isAutoRepeat() && (e->type() == QEvent::KeyRelease))
0683         return;
0684 
0685 // parts of this code are based on https://github.com/veyon/veyon/blob/master/core/src/VncView.cpp
0686     rfbKeySym k = e->nativeVirtualKey();
0687 
0688     // we do not handle Key_Backtab separately as the Shift-modifier
0689     // is already enabled
0690     if (e->key() == Qt::Key_Backtab) {
0691         k = XK_Tab;
0692     }
0693 
0694     const bool pressed = (e->type() == QEvent::KeyPress);
0695 
0696     // handle modifiers
0697     if (k == XK_Shift_L || k == XK_Control_L || k == XK_Meta_L || k == XK_Alt_L || XK_Super_L || XK_Hyper_L ||
0698         k == XK_Shift_R || k == XK_Control_R || k == XK_Meta_R || k == XK_Alt_R || XK_Super_R || XK_Hyper_R) {
0699         if (pressed) {
0700             m_mods[k] = true;
0701         } else if (m_mods.contains(k)) {
0702             m_mods.remove(k);
0703         } else {
0704             unpressModifiers();
0705         }
0706     }
0707 
0708     if (k) {
0709         vncThread.keyEvent(k, pressed);
0710     }
0711 }
0712 
0713 void VncView::unpressModifiers()
0714 {
0715     const QList<unsigned int> keys = m_mods.keys();
0716     QList<unsigned int>::const_iterator it = keys.constBegin();
0717     while (it != keys.end()) {
0718         qCDebug(KRDC) << "VncView::unpressModifiers key=" << (*it);
0719         vncThread.keyEvent(*it, false);
0720         it++;
0721     }
0722     m_mods.clear();
0723 }
0724 
0725 void VncView::clipboardDataChanged()
0726 {
0727     if (m_status != Connected)
0728         return;
0729 
0730     if (m_clipboard->ownsClipboard() || m_dontSendClipboard)
0731         return;
0732 #ifndef QTONLY
0733     if (m_hostPreferences->dontCopyPasswords()) {
0734         const QMimeData* data = m_clipboard->mimeData();
0735         if (data && data->hasFormat(QLatin1String("x-kde-passwordManagerHint"))) {
0736             qCDebug(KRDC) << "VncView::clipboardDataChanged data hasFormat x-kde-passwordManagerHint -- ignoring";
0737             return;
0738         }
0739     }
0740 #endif
0741     const QString text = m_clipboard->text(QClipboard::Clipboard);
0742 
0743     vncThread.clientCut(text);
0744 }
0745 
0746 void VncView::focusOutEvent(QFocusEvent *event)
0747 {
0748     qCDebug(KRDC) << "VncView::focusOutEvent";
0749     unpressModifiers();
0750 
0751     RemoteView::focusOutEvent(event);
0752 }
0753 
0754 #include "moc_vncview.cpp"