diff --git a/vnc/vncsshtunnelthread.cpp b/vnc/vncsshtunnelthread.cpp index c9172b2..91280e7 100644 --- a/vnc/vncsshtunnelthread.cpp +++ b/vnc/vncsshtunnelthread.cpp @@ -1,289 +1,301 @@ /**************************************************************************** ** ** Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group ** company, info@kdab.com. Work sponsored by the ** LiMux project of the city of Munich ** ** This file is part of KRDC. ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MEresHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; see the file COPYING. If not, write to ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, ** Boston, MA 02110-1301, USA. ** ****************************************************************************/ #include "vncsshtunnelthread.h" #include "krdc_debug.h" #include #include #include #include #include #include VncSshTunnelThread::VncSshTunnelThread(const QByteArray &host, int vncPort, int tunnelPort, int sshPort, const QByteArray &sshUserName, bool loopback) : m_host(host), m_vncPort(vncPort), m_tunnelPort(tunnelPort), m_sshPort(sshPort), m_sshUserName(sshUserName), m_loopback(loopback), m_stop_thread(false) { } VncSshTunnelThread::~VncSshTunnelThread() { m_stop_thread = true; wait(); } QString VncSshTunnelThread::password() const { return m_password; } // This is called by the main thread, but from a slot connected to our signal via BlockingQueuedConnection // so this is safe even without a mutex, the semaphore in BlockingQueuedConnection takes care of the synchronization. void VncSshTunnelThread::setPassword(const QString &password, PasswordOrigin origin) { m_password = password; m_passwordOrigin = origin; } +// This is called by the main thread, but from a slot connected to our signal via BlockingQueuedConnection +// so this is safe even without a mutex, the semaphore in BlockingQueuedConnection takes care of the synchronization. +void VncSshTunnelThread::userCanceledPasswordRequest() +{ + m_passwordRequestCanceledByUser = true; +} + void VncSshTunnelThread::run() { struct CleanupHelper { int server_sock = -1; int client_sock = -1; ssh_session session = nullptr; ssh_channel forwarding_channel = nullptr; ~CleanupHelper() { // the ssh functions just return if the param is null ssh_channel_free(forwarding_channel); if (client_sock != -1) { close(client_sock); } if (server_sock != -1) { close(server_sock); } ssh_disconnect(session); ssh_free(session); } }; CleanupHelper cleanup; ssh_session session = ssh_new(); if (session == nullptr) return; cleanup.session = session; ssh_options_set(session, SSH_OPTIONS_HOST, m_host.constData()); ssh_options_set(session, SSH_OPTIONS_USER, m_sshUserName.constData()); ssh_options_set(session, SSH_OPTIONS_PORT, &m_sshPort); int res = ssh_connect(session); if (res != SSH_OK) { emit errorMessage(i18n("Error connecting to %1: %2", QString::fromUtf8(m_host), QString::fromLocal8Bit(ssh_get_error(session)))); return; } // First try authenticating via ssh agent res = ssh_userauth_agent(session, NULL); + m_passwordRequestCanceledByUser = false; if (res != SSH_AUTH_SUCCESS) { // If ssh agent didn't work, try with password emit passwordRequest(NoFlags); // This calls blockingly to the main thread which will call setPassword res = ssh_userauth_password(session, NULL, m_password.toUtf8().constData()); // If password didn't work but came from the wallet, ask the user for the password - if (res != SSH_AUTH_SUCCESS && m_passwordOrigin == PasswordFromWallet) { + if (!m_passwordRequestCanceledByUser && res != SSH_AUTH_SUCCESS && m_passwordOrigin == PasswordFromWallet) { emit passwordRequest(IgnoreWallet); // This calls blockingly to the main thread which will call setPassword res = ssh_userauth_password(session, NULL, m_password.toUtf8().constData()); } } + if (m_passwordRequestCanceledByUser) { + return; + } + if (res != SSH_AUTH_SUCCESS) { emit errorMessage(i18n("Error authenticating with password: %1", QString::fromLocal8Bit(ssh_get_error(session)))); return; } const int server_sock = socket(AF_INET, SOCK_STREAM, 0); if (server_sock == -1) { emit errorMessage(i18n("Error creating tunnel socket")); return; } cleanup.server_sock = server_sock; // so that we can bind more than once in case more than one tunnel is used int sockopt = 1; setsockopt(server_sock , SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt)); { // bind the server socket struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(m_tunnelPort); sin.sin_addr.s_addr = inet_addr("127.0.0.1"); if (bind(server_sock, (struct sockaddr*)&sin, sizeof sin) == -1) { emit errorMessage(i18n("Error creating tunnel socket")); return; } } if (listen(server_sock, 1) == -1) { emit errorMessage(i18n("Error creating tunnel socket")); return; } if (m_stop_thread) { return; } emit listenReady(); // After here we don't need to emit errorMessage anymore on error, qCDebug is enough // this is because the actual vnc thread will start because of this call and thus // any socket error here will be detected by the vnc thread and the usual error mechanisms // there will warn the user interface int client_sock; { struct sockaddr_in client_sin; socklen_t client_sin_len = sizeof client_sin; client_sock = accept(server_sock, (struct sockaddr *)&client_sin, &client_sin_len); if (client_sock == -1) { qCDebug(KRDC) << "Error on tunnel socket accept"; return; } cleanup.client_sock = client_sock; int sock_flags = fcntl(client_sock, F_GETFL, 0); fcntl(client_sock, F_SETFL, sock_flags | O_NONBLOCK); } ssh_channel forwarding_channel = ssh_channel_new(session); { const char *forward_remote_host = m_loopback ? "127.0.0.1" : m_host.constData(); res = ssh_channel_open_forward(forwarding_channel, forward_remote_host, m_vncPort, "127.0.0.1", 0); if (res != SSH_OK || !ssh_channel_is_open(forwarding_channel)) { qCDebug(KRDC) << "SSH channel open error" << ssh_get_error(session); return; } cleanup.forwarding_channel = forwarding_channel; } char client_read_buffer[40960]; char *channel_read_buffer = nullptr; char *channel_read_buffer_ptr = nullptr; int channel_read_buffer_to_write; while (!m_stop_thread && !ssh_channel_is_eof(forwarding_channel)) { struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 200000; fd_set set; FD_ZERO(&set); FD_SET(client_sock, &set); ssh_channel channels[2] = { forwarding_channel, NULL }; ssh_channel channels_out[2] = { NULL, NULL }; res = ssh_select(channels, channels_out, client_sock + 1, &set, &timeout); if (res == SSH_EINTR) continue; if (res == -1) break; bool error = false; if (FD_ISSET(client_sock, &set)) { int bytes_read; while (!error && (bytes_read = read(client_sock, client_read_buffer, sizeof client_read_buffer)) > 0) { int bytes_written = 0; int bytes_to_write = bytes_read; for (char *ptr = client_read_buffer; bytes_to_write > 0; bytes_to_write -= bytes_written, ptr += bytes_written) { bytes_written = ssh_channel_write(forwarding_channel, ptr, bytes_to_write); if (bytes_written <= 0) { error = true; qCDebug(KRDC) << "error on ssh_channel_write"; break; } } } if (bytes_read == 0) { qCDebug(KRDC) << "error on tunnel read"; error = true; } } // If on the previous iteration we successfully wrote all we read, we need to read again if (!error && !channel_read_buffer) { const int bytes_available = ssh_channel_poll(forwarding_channel, 0); if (bytes_available == SSH_ERROR || bytes_available == SSH_EOF) { qCDebug(KRDC) << "error on ssh_channel_poll"; error = true; } else if (bytes_available > 0) { channel_read_buffer = new char[bytes_available]; channel_read_buffer_ptr = channel_read_buffer; const int bytes_read = ssh_channel_read_nonblocking(forwarding_channel, channel_read_buffer, bytes_available, 0); if (bytes_read <= 0) { qCDebug(KRDC) << "error on ssh_channel_read_nonblocking"; error = true; } else { channel_read_buffer_to_write = bytes_read; } } } if (!error && channel_read_buffer) { for (int bytes_written = 0; channel_read_buffer_to_write > 0; channel_read_buffer_to_write -= bytes_written, channel_read_buffer_ptr += bytes_written) { bytes_written = write(client_sock, channel_read_buffer_ptr, channel_read_buffer_to_write); if (bytes_written == -1 && errno == EAGAIN) { // socket is full, just carry on and we will write on the next iteration // that is why the previous code does 'if (!channel_read_buffer)' break; } if (bytes_written <= 0) { qCDebug(KRDC) << "error on tunnel write"; error = true; break; } } if (channel_read_buffer_to_write <= 0) { delete[] channel_read_buffer; channel_read_buffer = nullptr; } } } delete[] channel_read_buffer; channel_read_buffer = nullptr; } diff --git a/vnc/vncsshtunnelthread.h b/vnc/vncsshtunnelthread.h index 527cb1a..67fbc1c 100644 --- a/vnc/vncsshtunnelthread.h +++ b/vnc/vncsshtunnelthread.h @@ -1,78 +1,80 @@ /**************************************************************************** ** ** Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group ** company, info@kdab.com. Work sponsored by the ** LiMux project of the city of Munich ** ** This file is part of KRDC. ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; see the file COPYING. If not, write to ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, ** Boston, MA 02110-1301, USA. ** ****************************************************************************/ #ifndef VCNCSSHTUNNELTHREAD_H #define VCNCSSHTUNNELTHREAD_H #include #include #include #include #include class VncSshTunnelThread : public QThread { Q_OBJECT public: VncSshTunnelThread(const QByteArray &host, int vncPort, int tunnelPort, int sshPort, const QByteArray &sshUserName, bool loopback); ~VncSshTunnelThread(); enum PasswordOrigin { PasswordFromWallet, PasswordFromDialog }; enum PasswordRequestFlags { NoFlags, IgnoreWallet }; QString password() const; void setPassword(const QString &password, PasswordOrigin origin); + void userCanceledPasswordRequest(); void run() override; Q_SIGNALS: void passwordRequest(PasswordRequestFlags flags); void listenReady(); void errorMessage(const QString &message); private: QByteArray m_host; int m_vncPort; int m_tunnelPort; int m_sshPort; QByteArray m_sshUserName; bool m_loopback; QString m_password; PasswordOrigin m_passwordOrigin; + bool m_passwordRequestCanceledByUser; std::atomic_bool m_stop_thread; }; #endif diff --git a/vnc/vncview.cpp b/vnc/vncview.cpp index ed823e1..04e5cf7 100644 --- a/vnc/vncview.cpp +++ b/vnc/vncview.cpp @@ -1,722 +1,726 @@ /**************************************************************************** ** ** Copyright (C) 2007 - 2013 Urs Wolfer ** ** This file is part of KDE. ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; see the file COPYING. If not, write to ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, ** Boston, MA 02110-1301, USA. ** ****************************************************************************/ #include "vncview.h" #include "krdc_debug.h" #include #include #include #include +#include #ifdef QTONLY #include #include #define KMessageBox QMessageBox #define error(parent, message, caption) \ critical(parent, caption, message) #else #include "settings.h" #include #include #include #include #include #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_repaint(false), m_quitFlag(false), m_firstPasswordTry(true), m_dontSendClipboard(false), m_horizontalFactor(1.0), m_verticalFactor(1.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))); m_clipboard = QApplication::clipboard(); connect(m_clipboard, SIGNAL(dataChanged()), this, SLOT(clipboardDataChanged())); #ifndef QTONLY m_hostPreferences = new VncHostPreferences(configGroup, this); #else Q_UNUSED(configGroup); #endif } VncView::~VncView() { if (!m_quitFlag) startQuitting(); } bool VncView::eventFilter(QObject *obj, QEvent *event) { if (m_viewOnly) { if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease || event->type() == QEvent::MouseButtonDblClick || event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::Wheel || event->type() == QEvent::MouseMove) return true; } return RemoteView::eventFilter(obj, event); } QSize VncView::framebufferSize() { return m_frame.size(); } 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) { m_verticalFactor = (qreal) h / m_frame.height(); m_horizontalFactor = (qreal) w / m_frame.width(); #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 = m_frame.width() * m_horizontalFactor; const qreal newH = m_frame.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() { 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; setStatus(Disconnected); } bool VncView::isQuitting() { return m_quitFlag; } bool VncView::start() { QString vncHost = m_host; int vncPort = m_port; #ifdef LIBSSH_FOUND if (m_hostPreferences->useSshTunnel()) { Q_ASSERT(!m_sshTunnelThread); const int tunnelPort = 58219; // Just a random port m_sshTunnelThread = new VncSshTunnelThread(m_host.toUtf8(), m_port, tunnelPort, 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"); } vncPort = tunnelPort; } #endif vncThread.setHost(vncHost); vncThread.setPort(vncPort); 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); // set local cursor on by default because low quality mostly means slow internet connection if (quality == RemoteView::Low) { showDotCursor(RemoteView::CursorOn); #ifndef QTONLY // KRDC does always just have one main window, so at(0) is safe KXMLGUIClient *mainWindow = dynamic_cast(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.start(); }); } else #endif { 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"; if (m_hostPreferences->walletSupport() && ((flags & VncSshTunnelThread::IgnoreWallet) != VncSshTunnelThread::IgnoreWallet)) { const QString walletPassword = readWalletSshPassword(); if (!walletPassword.isNull()) { m_sshTunnelThread->setPassword(walletPassword, VncSshTunnelThread::PasswordFromWallet); return; } } 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"; - startQuitting(); + 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(localDotCursor()); m_forceLocalCursor = true; return; } startQuitting(); KMessageBox::error(this, message, i18n("VNC failure")); emit errorMessage(i18n("VNC failure"), message); } void VncView::sshErrorMessage(const QString &message) { qCritical(KRDC) << message; startQuitting(); KMessageBox::error(this, message, i18n("SSH Tunnel failure")); 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_x = x; m_y = y; m_w = w; m_h = h; if (m_horizontalFactor != 1.0 || m_verticalFactor != 1.0) { // If the view is scaled, grow the update rectangle to avoid artifacts m_x-=1; m_y-=1; m_w+=2; m_h+=2; } m_frame = vncThread.image(); if (!m_initDone) { if (!vncThread.username().isEmpty()) { m_url.setUserName(vncThread.username()); } setAttribute(Qt::WA_StaticContents); setAttribute(Qt::WA_OpaquePaintEvent); installEventFilter(this); setCursor(((m_dotCursorState == CursorOn) || m_forceLocalCursor) ? localDotCursor() : Qt::BlankCursor); setMouseTracking(true); // get mouse events even when there is no mousebutton pressed setFocusPolicy(Qt::WheelFocus); setStatus(Connected); emit connected(); if (m_scale) { #ifndef QTONLY qCDebug(KRDC) << "Setting initial size w:" <width() << " h:" << m_hostPreferences->height(); emit framebufferSizeChanged(m_hostPreferences->width(), m_hostPreferences->height()); scaleResize(m_hostPreferences->width(), m_hostPreferences->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 } if ((y == 0 && x == 0) && (m_frame.size() != 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(m_frame.width(), m_frame.height()); setMaximumSize(m_frame.width(), m_frame.height()); //This is a hack to force Qt to center the view in the scroll area setMinimumSize(m_frame.width(), m_frame.height()); emit framebufferSizeChanged(m_frame.width(), m_frame.height()); } } m_repaint = true; repaint(qRound(m_x * m_horizontalFactor), qRound(m_y * m_verticalFactor), qRound(m_w * m_horizontalFactor), qRound(m_h * m_verticalFactor)); m_repaint = false; } void VncView::setViewOnly(bool viewOnly) { RemoteView::setViewOnly(viewOnly); m_dontSendClipboard = viewOnly; if (viewOnly) setCursor(Qt::ArrowCursor); else setCursor(m_dotCursorState == CursorOn ? localDotCursor() : Qt::BlankCursor); } void VncView::showDotCursor(DotCursorState state) { RemoteView::showDotCursor(state); setCursor(state == CursorOn ? localDotCursor() : Qt::BlankCursor); } 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; setMaximumSize(m_frame.width(), m_frame.height()); //This is a hack to force Qt to center the view in the scroll area setMinimumSize(m_frame.width(), m_frame.height()); resize(m_frame.width(), m_frame.height()); } } void VncView::setCut(const QString &text) { m_dontSendClipboard = true; m_clipboard->setText(text, QClipboard::Clipboard); m_dontSendClipboard = false; } 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); if (m_repaint) { // qCDebug(KRDC) << "normal repaint"; painter.drawImage(QRect(qRound(m_x*m_horizontalFactor), qRound(m_y*m_verticalFactor), qRound(m_w*m_horizontalFactor), qRound(m_h*m_verticalFactor)), m_frame.copy(m_x, m_y, m_w, m_h).scaled(qRound(m_w*m_horizontalFactor), qRound(m_h*m_verticalFactor), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else { // qCDebug(KRDC) << "resize repaint"; QRect rect = event->rect(); if (rect.width() != width() || rect.height() != height()) { // qCDebug(KRDC) << "Partial repaint"; const int sx = rect.x()/m_horizontalFactor; const int sy = rect.y()/m_verticalFactor; const int sw = rect.width()/m_horizontalFactor; const int sh = rect.height()/m_verticalFactor; painter.drawImage(rect, m_frame.copy(sx, sy, sw, sh).scaled(rect.width(), rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else { // qCDebug(KRDC) << "Full repaint" << width() << height() << m_frame.width() << m_frame.height(); painter.drawImage(QRect(0, 0, width(), height()), m_frame.scaled(m_frame.width() * m_horizontalFactor, m_frame.height() * m_verticalFactor, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } } RemoteView::paintEvent(event); } void VncView::resizeEvent(QResizeEvent *event) { RemoteView::resizeEvent(event); update(); } bool VncView::event(QEvent *event) { switch (event->type()) { case QEvent::KeyPress: case QEvent::KeyRelease: // qCDebug(KRDC) << "keyEvent"; keyEventHandler(static_cast(event)); return true; break; case QEvent::MouseButtonDblClick: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseMove: // qCDebug(KRDC) << "mouseEvent"; mouseEventHandler(static_cast(event)); return true; break; case QEvent::Wheel: // qCDebug(KRDC) << "wheelEvent"; wheelEventHandler(static_cast(event)); return true; break; default: return RemoteView::event(event); } } void VncView::mouseEventHandler(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::MidButton) m_buttonMask |= 0x02; if (e->button() & Qt::RightButton) m_buttonMask |= 0x04; } else if (e->type() == QEvent::MouseButtonRelease) { if (e->button() & Qt::LeftButton) m_buttonMask &= 0xfe; if (e->button() & Qt::MidButton) m_buttonMask &= 0xfd; if (e->button() & Qt::RightButton) m_buttonMask &= 0xfb; } } vncThread.mouseEvent(qRound(e->x() / m_horizontalFactor), qRound(e->y() / m_verticalFactor), m_buttonMask); } void VncView::wheelEventHandler(QWheelEvent *event) { int eb = 0; if (event->delta() < 0) eb |= 0x10; else eb |= 0x8; const int x = qRound(event->x() / m_horizontalFactor); const int y = qRound(event->y() / m_verticalFactor); vncThread.mouseEvent(x, y, eb | m_buttonMask); vncThread.mouseEvent(x, y, m_buttonMask); } #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::keyEventHandler(QKeyEvent *e) { // strip away autorepeating KeyRelease; see bug #206598 if (e->isAutoRepeat() && (e->type() == QEvent::KeyRelease)) return; // parts of this code are based on http://italc.sourcearchive.com/documentation/1.0.9.1/vncview_8cpp-source.html 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); // handle modifiers if (k == XK_Shift_L || k == XK_Control_L || k == XK_Meta_L || k == XK_Alt_L) { if (pressed) { m_mods[k] = true; } else if (m_mods.contains(k)) { m_mods.remove(k); } else { unpressModifiers(); } } if (k) { vncThread.keyEvent(k, pressed); } } void VncView::unpressModifiers() { const QList keys = m_mods.keys(); QList::const_iterator it = keys.constBegin(); while (it != keys.end()) { vncThread.keyEvent(*it, false); it++; } m_mods.clear(); } void VncView::clipboardDataChanged() { if (m_status != Connected) return; if (m_clipboard->ownsClipboard() || m_dontSendClipboard) return; const QString text = m_clipboard->text(QClipboard::Clipboard); vncThread.clientCut(text); } #include "moc_vncview.cpp"