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"