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