kde-extraapps/krdc/vnc/vncview.cpp
Ivailo Monev 038b6c5c05 generic: prepare for Katie changes
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2022-06-15 00:16:23 +03:00

615 lines
19 KiB
C++

/****************************************************************************
**
** Copyright (C) 2007 - 2013 Urs Wolfer <uwolfer @ kde.org>
**
** 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 <QApplication>
#include <QImage>
#include <QPainter>
#include <QtGui/qevent.h>
#ifdef QTONLY
#include <QMessageBox>
#include <QInputDialog>
#define KMessageBox QMessageBox
#define error(parent, message, caption) \
critical(parent, caption, message)
#else
#include "settings.h"
#include <KActionCollection>
#include <KMainWindow>
#include <KMessageBox>
#include <KPasswordDialog>
#include <KXMLGUIClient>
#endif
// Definition of key modifier mask constants
#define KMOD_Alt_R 0x01
#define KMOD_Alt_L 0x02
#define KMOD_Meta_L 0x04
#define KMOD_Control_L 0x08
#define KMOD_Shift_L 0x10
VncView::VncView(QWidget *parent, const KUrl &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)
{
m_url = url;
m_host = url.host();
m_port = url.port();
// 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);
kDebug(5011) << 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()
{
kDebug(5011) << "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();
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()) {
kDebug(5011) << "BUG: deadlocked, but no events to deliver?";
}
vncThread.wait(500);
}
kDebug(5011) << "Quit VNC thread success:" << quitSuccess;
setStatus(Disconnected);
}
bool VncView::isQuitting()
{
return m_quitFlag;
}
bool VncView::start()
{
vncThread.setHost(m_host);
vncThread.setPort(m_port);
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<KXMLGUIClient*>(KMainWindow::memberList().at(0));
if (mainWindow)
mainWindow->actionCollection()->action("show_local_cursor")->setChecked(true);
#endif
}
setStatus(Connecting);
vncThread.start();
return true;
}
bool VncView::supportsScaling() const
{
return true;
}
bool VncView::supportsLocalCursor() const
{
return true;
}
void VncView::requestPassword(bool includingUsername)
{
kDebug(5011) << "request password";
setStatus(Authenticating);
if (m_firstPasswordTry && !m_url.userName().isNull()) {
vncThread.setUsername(m_url.userName());
}
#ifndef QTONLY
// just try to get the passwort 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 {
kDebug(5011) << "password dialog not accepted";
startQuitting();
}
#endif
}
void VncView::outputErrorMessage(const QString &message)
{
kDebug(5011) << message;
if (message == "INTERNAL:APPLE_VNC_COMPATIBILTY") {
setCursor(localDotCursor());
m_forceLocalCursor = true;
return;
}
startQuitting();
KMessageBox::error(this, message, i18n("VNC failure"));
emit errorMessage(i18n("VNC failure"), message);
}
#ifndef QTONLY
HostPreferences* VncView::hostPreferences()
{
return m_hostPreferences;
}
#endif
void VncView::updateImage(int x, int y, int w, int h)
{
// kDebug(5011) << "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_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
kDebug(5011) << "Setting initial size w:" <<m_hostPreferences->width() << " h:" << m_hostPreferences->height();
emit framebufferSizeChanged(m_hostPreferences->width(), m_hostPreferences->height());
scaleResize(m_hostPreferences->width(), m_hostPreferences->height());
kDebug() << "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());
}
#endif
}
if ((y == 0 && x == 0) && (m_frame.size() != size())) {
kDebug(5011) << "Updating framebuffer size";
if (m_scale) {
setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
if (parentWidget())
scaleResize(parentWidget()->width(), parentWidget()->height());
} else {
kDebug(5011) << "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)
{
// kDebug(5011) << "paint event: x: " << m_x << ", y: " << m_y << ", w: " << m_w << ", h: " << m_h;
if (m_frame.isNull() || m_frame.format() == QImage::Format_Invalid) {
kDebug(5011) << "no valid image to paint";
RemoteView::paintEvent(event);
return;
}
event->accept();
QPainter painter(this);
if (m_repaint) {
// kDebug(5011) << "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 {
// kDebug(5011) << "resize repaint";
QRect rect = event->rect();
if (rect.width() != width() || rect.height() != height()) {
// kDebug(5011) << "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 {
// kDebug(5011) << "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:
// kDebug(5011) << "keyEvent";
keyEventHandler(static_cast<QKeyEvent*>(event));
return true;
break;
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove:
// kDebug(5011) << "mouseEvent";
mouseEventHandler(static_cast<QMouseEvent*>(event));
return true;
break;
case QEvent::Wheel:
// kDebug(5011) << "wheelEvent";
wheelEventHandler(static_cast<QWheelEvent*>(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::MiddleButton)
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::MiddleButton)
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);
}
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<unsigned int> keys = m_mods.keys();
QList<unsigned int>::const_iterator it = keys.constBegin();
while (it != keys.end()) {
vncThread.keyEvent(*it, false);
it++;
}
m_mods.clear();
}
void VncView::clipboardDataChanged()
{
kDebug(5011);
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"