diff --git a/core/hostpreferences.cpp b/core/hostpreferences.cpp index 15d123f..03b4801 100644 --- a/core/hostpreferences.cpp +++ b/core/hostpreferences.cpp @@ -1,230 +1,230 @@ /**************************************************************************** ** ** Copyright (C) 2007 - 2010 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 "hostpreferences.h" #include "krdc_debug.h" #include "settings.h" #include #include #include #include #include #include #include #include HostPreferences::HostPreferences(KConfigGroup configGroup, QObject *parent) : QObject(parent), m_configGroup(configGroup), m_connected(false), showAgainCheckBox(nullptr), walletSupportCheckBox(nullptr) { m_hostConfigured = m_configGroup.hasKey("showConfigAgain"); } HostPreferences::~HostPreferences() { } KConfigGroup HostPreferences::configGroup() { return m_configGroup; } void HostPreferences::acceptConfig() { setShowConfigAgain(showAgainCheckBox->isChecked()); setWalletSupport(walletSupportCheckBox->isChecked()); } bool HostPreferences::hostConfigured() { return m_hostConfigured; } void HostPreferences::setShowConfigAgain(bool show) { m_configGroup.writeEntry("showConfigAgain", show); } bool HostPreferences::showConfigAgain() { return m_configGroup.readEntry("showConfigAgain", true); } void HostPreferences::setWalletSupport(bool walletSupport) { m_configGroup.writeEntry("walletSupport", walletSupport); } bool HostPreferences::walletSupport() { return m_configGroup.readEntry("walletSupport", true); } void HostPreferences::setHeight(int height) { if (height >= 0) m_configGroup.writeEntry("height", height); } int HostPreferences::height() { return m_configGroup.readEntry("height", Settings::height()); } void HostPreferences::setWidth(int width) { if (width >= 0) m_configGroup.writeEntry("width", width); } int HostPreferences::width() { return m_configGroup.readEntry("width", Settings::width()); } bool HostPreferences::fullscreenScale() { return m_configGroup.readEntry("fullscreenScale", false); } void HostPreferences::setFullscreenScale(bool scale) { m_configGroup.writeEntry("fullscreenScale", scale); } bool HostPreferences::windowedScale() { return m_configGroup.readEntry("windowedScale", false); } void HostPreferences::setWindowedScale(bool scale) { m_configGroup.writeEntry("windowedScale", scale); } bool HostPreferences::grabAllKeys() { return m_configGroup.readEntry("grabAllKeys", false); } void HostPreferences::setGrabAllKeys(bool grab) { m_configGroup.writeEntry("grabAllKeys", grab); } bool HostPreferences::showLocalCursor() { return m_configGroup.readEntry("showLocalCursor", false); } void HostPreferences::setShowLocalCursor(bool show) { m_configGroup.writeEntry("showLocalCursor", show); } bool HostPreferences::viewOnly() { return m_configGroup.readEntry("viewOnly", false); } void HostPreferences::setViewOnly(bool view) { m_configGroup.writeEntry("viewOnly", view); } bool HostPreferences::showDialogIfNeeded(QWidget *parent) { if (hostConfigured()) { if (showConfigAgain()) { qCDebug(KRDC) << "Show config dialog again"; return showDialog(parent); } else return true; // no changes, no need to save } else { qCDebug(KRDC) << "No config found, create new"; if (Settings::showPreferencesForNewConnections()) return showDialog(parent); else return true; } } bool HostPreferences::showDialog(QWidget *parent) { // Prepare dialog KPageDialog *dialog = new KPageDialog(parent); dialog->setWindowTitle(i18n("Host Configuration")); QWidget *mainWidget = new QWidget(parent); QVBoxLayout *layout = new QVBoxLayout(mainWidget); dialog->addPage(mainWidget, i18n("Host Configuration")); if (m_connected) { const QString noteText = i18n("Note that settings might only apply when you connect next time to this host."); const QString format = QLatin1String("%1"); QLabel *commentLabel = new QLabel(format.arg(noteText), mainWidget); layout->addWidget(commentLabel); } QWidget* widget = createProtocolSpecificConfigPage(); if (widget) { if (widget->layout()) - widget->layout()->setMargin(0); + widget->layout()->setContentsMargins(0, 0, 0, 0); layout->addWidget(widget); } showAgainCheckBox = new QCheckBox(mainWidget); showAgainCheckBox->setText(i18n("Show this dialog again for this host")); showAgainCheckBox->setChecked(showConfigAgain()); walletSupportCheckBox = new QCheckBox(mainWidget); walletSupportCheckBox->setText(i18n("Remember password (KWallet)")); walletSupportCheckBox->setChecked(walletSupport()); layout->addWidget(showAgainCheckBox); layout->addWidget(walletSupportCheckBox); layout->addStretch(1); // Show dialog if (dialog->exec() == QDialog::Accepted) { qCDebug(KRDC) << "HostPreferences config dialog accepted"; acceptConfig(); return true; } else { return false; } } void HostPreferences::setShownWhileConnected(bool connected) { m_connected = connected; } diff --git a/floatingtoolbar.cpp b/floatingtoolbar.cpp index 92418eb..a243d0b 100644 --- a/floatingtoolbar.cpp +++ b/floatingtoolbar.cpp @@ -1,483 +1,483 @@ /**************************************************************************** ** ** Copyright (C) 2007-2008 Urs Wolfer ** Parts of this file have been take from okular: ** Copyright (C) 2004-2005 Enrico Ros ** ** 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 "floatingtoolbar.h" #include "krdc_debug.h" #include #include #include #include #include #include static const int actionIconSize = 22; static const int toolBarRBMargin = 2; static const double toolBarOpacity = 0.8; static const int visiblePixelWhenAutoHidden = 6; static const int autoHideTimeout = 500; static const int initialAutoHideTimeout = 2000; /** * Denotes the various states of the animation. */ enum AnimState { Hiding, Showing, Still }; class FloatingToolBarPrivate { public: FloatingToolBarPrivate(FloatingToolBar *qq) : q(qq) , anchorSide(FloatingToolBar::Left) , offsetPlaceHolder(new QWidget(qq)) , animState(Still) , toDelete(false) , visible(false) , sticky(false) , opacity(toolBarOpacity) // set queuedShow to true so we show the toolbar if we get a resize event on the anchorWidget , queuedShow(true) { } // rebuild contents and reposition then widget void buildToolBar(); void reposition(); // compute the visible and hidden positions along current side QPoint getInnerPoint() const; QPoint getOuterPoint() const; FloatingToolBar *q; QWidget *anchorWidget; FloatingToolBar::Side anchorSide; QWidget *offsetPlaceHolder; QTimer *animTimer; QTimer *autoHideTimer; QPoint currentPosition; QPoint endPosition; AnimState animState; bool toDelete; bool visible; bool sticky; qreal opacity; bool queuedShow; QPixmap backgroundPixmap; }; FloatingToolBar::FloatingToolBar(QWidget *parent, QWidget *anchorWidget) : QToolBar(parent), d(new FloatingToolBarPrivate(this)) { ; addWidget(d->offsetPlaceHolder); setMouseTracking(true); setIconSize(QSize(actionIconSize, actionIconSize)); d->anchorWidget = anchorWidget; d->animTimer = new QTimer(this); connect(d->animTimer, SIGNAL(timeout()), this, SLOT(animate())); d->autoHideTimer = new QTimer(this); connect(d->autoHideTimer, SIGNAL(timeout()), this, SLOT(hide())); // apply a filter to get notified when anchor changes geometry d->anchorWidget->installEventFilter(this); } FloatingToolBar::~FloatingToolBar() { delete d; } void FloatingToolBar::addAction(QAction *action) { QToolBar::addAction(action); // rebuild toolbar shape and contents only if the toolbar is already visible, // otherwise it will be done in showAndAnimate() if (isVisible()) d->reposition(); } void FloatingToolBar::setSide(Side side) { d->anchorSide = side; if (isVisible()) d->reposition(); } void FloatingToolBar::setSticky(bool sticky) { d->sticky = sticky; if (sticky) d->autoHideTimer->stop(); } void FloatingToolBar::showAndAnimate() { if (d->animState == Showing) return; d->animState = Showing; show(); // force update for case when toolbar has not been built yet d->reposition(); // start scrolling in d->animTimer->start(20); // This permits to show the toolbar for a while when going full screen. if (!d->sticky) d->autoHideTimer->start(initialAutoHideTimeout); } void FloatingToolBar::hideAndDestroy() { if (d->animState == Hiding) return; // set parameters for sliding out d->animState = Hiding; d->toDelete = true; d->endPosition = d->getOuterPoint(); // start scrolling out d->animTimer->start(20); } void FloatingToolBar::hide() { if (underMouse()) return; if (d->visible) { QPoint diff; switch (d->anchorSide) { case Left: diff = QPoint(visiblePixelWhenAutoHidden, 0); break; case Right: diff = QPoint(-visiblePixelWhenAutoHidden, 0); break; case Top: diff = QPoint(0, visiblePixelWhenAutoHidden); break; case Bottom: diff = QPoint(0, -visiblePixelWhenAutoHidden); break; } d->animState = Hiding; d->endPosition = d->getOuterPoint() + diff; // start scrolling out d->animTimer->start(20); } } bool FloatingToolBar::eventFilter(QObject *obj, QEvent *e) { if (obj == d->anchorWidget && e->type() == QEvent::Resize) { if (d->queuedShow) { // if the toolbar is not visible yet, try to show it if the anchor widget is in fullscreen already d->queuedShow = false; showAndAnimate(); return true; } // if anchorWidget changed geometry reposition toolbar d->animTimer->stop(); if ((d->animState == Hiding || !d->visible) && d->toDelete) deleteLater(); else d->reposition(); } return QToolBar::eventFilter(obj, e); } void FloatingToolBar::paintEvent(QPaintEvent *e) { QToolBar::paintEvent(e); // paint the internal pixmap over the widget QPainter p(this); p.setOpacity(d->opacity); p.drawImage(e->rect().topLeft(), d->backgroundPixmap.toImage(), e->rect()); } void FloatingToolBar::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) setCursor(Qt::SizeAllCursor); QToolBar::mousePressEvent(e); } void FloatingToolBar::mouseMoveEvent(QMouseEvent *e) { // show the toolbar again when it is auto-hidden if (!d->visible) { showAndAnimate(); return; } if ((QApplication::mouseButtons() & Qt::LeftButton) != Qt::LeftButton) return; // compute the nearest side to attach the widget to const QPoint parentPos = mapToParent(e->pos()); const float nX = (float)parentPos.x() / (float)d->anchorWidget->width(); const float nY = (float)parentPos.y() / (float)d->anchorWidget->height(); if (nX > 0.3 && nX < 0.7 && nY > 0.3 && nY < 0.7) return; bool LT = nX < (1.0 - nY); bool LB = nX < (nY); Side side = LT ? (LB ? Left : Top) : (LB ? Bottom : Right); // check if side changed if (side == d->anchorSide) return; d->anchorSide = side; d->reposition(); emit orientationChanged((int)side); QToolBar::mouseMoveEvent(e); } void FloatingToolBar::enterEvent(QEvent *e) { // Stop the autohide timer while the mouse is inside d->autoHideTimer->stop(); if (!d->visible) showAndAnimate(); QToolBar::enterEvent(e); } void FloatingToolBar::leaveEvent(QEvent *e) { if (!d->sticky) d->autoHideTimer->start(autoHideTimeout); QToolBar::leaveEvent(e); } void FloatingToolBar::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) setCursor(Qt::ArrowCursor); QToolBar::mouseReleaseEvent(e); } void FloatingToolBar::wheelEvent(QWheelEvent *e) { e->accept(); - const qreal diff = e->delta() / 100.0 / 15.0; + const qreal diff = e->angleDelta().y() / 100.0 / 15.0; // qCDebug(KRDC) << diff; if (((d->opacity <= 1) && (diff > 0)) || ((d->opacity >= 0) && (diff < 0))) d->opacity += diff; update(); QToolBar::wheelEvent(e); } void FloatingToolBarPrivate::buildToolBar() { const bool prevUpdates = q->updatesEnabled(); q->setUpdatesEnabled(false); // 1. init numbers we are going to use const bool topLeft = anchorSide == FloatingToolBar::Left || anchorSide == FloatingToolBar::Top; const bool vertical = anchorSide == FloatingToolBar::Left || anchorSide == FloatingToolBar::Right; if (vertical) { offsetPlaceHolder->setFixedSize(1, 7); q->setOrientation(Qt::Vertical); } else { offsetPlaceHolder->setFixedSize(7, 1); q->setOrientation(Qt::Horizontal); } // 2. compute widget size const int myWidth = q->sizeHint().width() - 1; const int myHeight = q->sizeHint().height() - 1; // 3. resize pixmap, mask and widget QBitmap mask(myWidth + 1, myHeight + 1); backgroundPixmap = QPixmap(myWidth + 1, myHeight + 1); backgroundPixmap.fill(Qt::transparent); q->resize(myWidth + 1, myHeight + 1); // 4. create and set transparency mask QPainter maskPainter(&mask); mask.fill(Qt::white); maskPainter.setBrush(Qt::black); if (vertical) maskPainter.drawRoundRect(topLeft ? -10 : 0, 0, myWidth + 10, myHeight, 2000 / (myWidth + 10), 2000 / myHeight); else maskPainter.drawRoundRect(0, topLeft ? -10 : 0, myWidth, myHeight + 10, 2000 / myWidth, 2000 / (myHeight + 10)); maskPainter.end(); q->setMask(mask); // 5. draw background QPainter bufferPainter(&backgroundPixmap); bufferPainter.translate(0.5, 0.5); QPalette pal = q->palette(); // 5.1. draw horizontal/vertical gradient QLinearGradient grad; switch (anchorSide) { case FloatingToolBar::Left: grad = QLinearGradient(0, 1, myWidth + 1, 1); break; case FloatingToolBar::Right: grad = QLinearGradient(myWidth + 1, 1, 0, 1); break; case FloatingToolBar::Top: grad = QLinearGradient(1, 0, 1, myHeight + 1); break; case FloatingToolBar::Bottom: grad = QLinearGradient(1, myHeight + 1, 0, 1); break; } grad.setColorAt(0, pal.color(QPalette::Active, QPalette::Button)); grad.setColorAt(1, pal.color(QPalette::Active, QPalette::Light)); bufferPainter.setBrush(QBrush(grad)); // 5.2. draw rounded border bufferPainter.setPen( pal.color(QPalette::Active, QPalette::Dark).lighter(40)); bufferPainter.setRenderHints(QPainter::Antialiasing); if (vertical) bufferPainter.drawRoundRect(topLeft ? -10 : 0, 0, myWidth + 10, myHeight, 2000 / (myWidth + 10), 2000 / myHeight); else bufferPainter.drawRoundRect(0, topLeft ? -10 : 0, myWidth, myHeight + 10, 2000 / myWidth, 2000 / (myHeight + 10)); // 5.3. draw handle bufferPainter.translate(-0.5, -0.5); bufferPainter.setPen(pal.color(QPalette::Active, QPalette::Mid)); if (vertical) { int dx = anchorSide == FloatingToolBar::Left ? 2 : 4; bufferPainter.drawLine(dx, 6, dx + myWidth - 8, 6); bufferPainter.drawLine(dx, 9, dx + myWidth - 8, 9); bufferPainter.setPen(pal.color(QPalette::Active, QPalette::Light)); bufferPainter.drawLine(dx + 1, 7, dx + myWidth - 7, 7); bufferPainter.drawLine(dx + 1, 10, dx + myWidth - 7, 10); } else { int dy = anchorSide == FloatingToolBar::Top ? 2 : 4; bufferPainter.drawLine(6, dy, 6, dy + myHeight - 8); bufferPainter.drawLine(9, dy, 9, dy + myHeight - 8); bufferPainter.setPen(pal.color(QPalette::Active, QPalette::Light)); bufferPainter.drawLine(7, dy + 1, 7, dy + myHeight - 7); bufferPainter.drawLine(10, dy + 1, 10, dy + myHeight - 7); } q->setUpdatesEnabled(prevUpdates); } void FloatingToolBarPrivate::reposition() { // note: hiding widget here will gives better gfx, but ends drag operation // rebuild widget and move it to its final place buildToolBar(); if (!visible) { currentPosition = getOuterPoint(); endPosition = getInnerPoint(); } else { currentPosition = getInnerPoint(); endPosition = getOuterPoint(); } q->move(currentPosition); } QPoint FloatingToolBarPrivate::getInnerPoint() const { // returns the final position of the widget if (anchorSide == FloatingToolBar::Left) return QPoint(0, (anchorWidget->height() - q->height()) / 2); if (anchorSide == FloatingToolBar::Top) return QPoint((anchorWidget->width() - q->width()) / 2, 0); if (anchorSide == FloatingToolBar::Right) return QPoint(anchorWidget->width() - q->width() + toolBarRBMargin, (anchorWidget->height() - q->height()) / 2); return QPoint((anchorWidget->width() - q->width()) / 2, anchorWidget->height() - q->height() + toolBarRBMargin); } QPoint FloatingToolBarPrivate::getOuterPoint() const { // returns the point from which the transition starts if (anchorSide == FloatingToolBar::Left) return QPoint(-q->width(), (anchorWidget->height() - q->height()) / 2); if (anchorSide == FloatingToolBar::Top) return QPoint((anchorWidget->width() - q->width()) / 2, -q->height()); if (anchorSide == FloatingToolBar::Right) return QPoint(anchorWidget->width() + toolBarRBMargin, (anchorWidget->height() - q->height()) / 2); return QPoint((anchorWidget->width() - q->width()) / 2, anchorWidget->height() + toolBarRBMargin); } void FloatingToolBar::animate() { if (style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this)) { // move currentPosition towards endPosition int dX = d->endPosition.x() - d->currentPosition.x(); int dY = d->endPosition.y() - d->currentPosition.y(); dX = dX / 6 + qMax(-1, qMin(1, dX)); dY = dY / 6 + qMax(-1, qMin(1, dY)); d->currentPosition.setX(d->currentPosition.x() + dX); d->currentPosition.setY(d->currentPosition.y() + dY); } else { d->currentPosition = d->endPosition; } move(d->currentPosition); // handle arrival to the end if (d->currentPosition == d->endPosition) { d->animTimer->stop(); switch (d->animState) { case Hiding: d->visible = false; d->animState = Still; if (d->toDelete) deleteLater(); break; case Showing: d->visible = true; d->animState = Still; break; default: qCDebug(KRDC) << "Illegal state"; } } } diff --git a/konsole/konsoleview.cpp b/konsole/konsoleview.cpp index ba5315a..a7554cc 100644 --- a/konsole/konsoleview.cpp +++ b/konsole/konsoleview.cpp @@ -1,128 +1,128 @@ /**************************************************************************** ** ** Copyright (C) 2009 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 "konsoleview.h" #include #include #include #include #include #include #include #include #include #include KonsoleView::KonsoleView(QWidget *parent, const QUrl &url, KConfigGroup configGroup) : RemoteView(parent) { m_url = url; m_host = url.host(); m_port = url.port(); m_hostPreferences = new KonsoleHostPreferences(configGroup, this); // QSize size = QSize(640, 480); const QSize size = (qobject_cast(parent))->size(); setStatus(Connected); setFixedSize(size); setFixedSize(size); emit framebufferSizeChanged(size.width(), size.height()); KPluginFactory* factory = 0; KService::Ptr service = KService::serviceByDesktopName("konsolepart"); if (service) { factory = KPluginLoader(service->library()).factory(); } KParts::ReadOnlyPart* part = factory ? (factory->create(this)) : 0; if (part != 0) { // connect(part, SIGNAL(destroyed(QObject*)), this, SLOT(terminalExited())); QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(0); m_terminalWidget = part->widget(); mainLayout->addWidget(m_terminalWidget); m_terminal = qobject_cast(part); m_terminal->showShellInDir(QDir::homePath()); m_terminal->sendInput("echo " + url.userName() + '@' + url.host()/* + ':' + url.port()*/ + '\n'); // m_terminal->sendInput("clear\n"); m_terminalWidget->resize(size); } } KonsoleView::~KonsoleView() { emit disconnected(); setStatus(Disconnected); } bool KonsoleView::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::MouseMove) return true; } return RemoteView::eventFilter(obj, event); } QSize KonsoleView::framebufferSize() { return minimumSizeHint(); } QSize KonsoleView::sizeHint() const { return RemoteView::sizeHint(); return maximumSize(); } bool KonsoleView::isQuitting() { return false; } bool KonsoleView::start() { setStatus(Connected); emit connected(); m_terminalWidget->setFocus(); return true; } HostPreferences* KonsoleView::hostPreferences() { return m_hostPreferences; } void KonsoleView::switchFullscreen(bool on) { Q_UNUSED(on); } diff --git a/main.cpp b/main.cpp index 9048578..3adbc64 100644 --- a/main.cpp +++ b/main.cpp @@ -1,135 +1,135 @@ /**************************************************************************** ** ** Copyright (C) 2001-2003 Tim Jansen ** Copyright (C) 2007 - 2012 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 "mainwindow.h" #include "krdc_debug.h" #include "krdc_version.h" #include "settings.h" #include #include #include #include #include #include -#include +#include #include #include #include #include int main(int argc, char **argv) { const QString appName = QStringLiteral("krdc"); QApplication app(argc, argv); KLocalizedString::setApplicationDomain("krdc"); - QTime startupTimer; + QElapsedTimer startupTimer; startupTimer.start(); app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); Kdelibs4ConfigMigrator migrate(appName); migrate.setConfigFiles(QStringList() << QStringLiteral("krdcrc")); if (migrate.migrate()) { Kdelibs4Migration dataMigrator; const QString sourceBasePath = dataMigrator.saveLocation("data", QStringLiteral("krdc")); const QString targetBasePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/krdc/"); QString targetFilePath; QDir sourceDir(sourceBasePath); QDir targetDir(targetBasePath); if (sourceDir.exists()) { if (!targetDir.exists()) { QDir().mkpath(targetBasePath); } QStringList fileNames = sourceDir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks); foreach (const QString &fileName, fileNames) { targetFilePath = targetBasePath + fileName; if (!QFile::exists(targetFilePath)) { QFile::copy(sourceBasePath + fileName, targetFilePath); } } } } KAboutData aboutData(appName, i18n("KRDC"), QStringLiteral(KRDC_VERSION_STRING), i18n("KDE Remote Desktop Client"), KAboutLicense::LicenseKey::GPL); aboutData.setCopyrightStatement(i18n("(c) 2007-2016, Urs Wolfer\n" "(c) 2001-2003, Tim Jansen\n" "(c) 2002-2003, Arend van Beelen jr.\n" "(c) 2000-2002, Const Kaplinsky\n" "(c) 2000, Tridia Corporation\n" "(c) 1999, AT&T Laboratories Boston\n" "(c) 1999-2003, Matthew Chapman\n" "(c) 2009, Collabora Ltd")); aboutData.addAuthor(i18n("Urs Wolfer"), i18n("Developer, Maintainer"), QStringLiteral("uwolfer@kde.org")); aboutData.addAuthor(i18n("Tony Murray"), i18n("Developer"), QStringLiteral("murraytony@gmail.com")); aboutData.addAuthor(i18n("Tim Jansen"), i18n("Former Developer"), QStringLiteral("tim@tjansen.de")); aboutData.addAuthor(i18n("Arend van Beelen jr."), i18n("Initial RDP backend"), QStringLiteral("arend@auton.nl")); aboutData.addCredit(i18n("Brad Hards"), i18n("Google Summer of Code 2007 KRDC project mentor"), QStringLiteral("bradh@frogmouth.net")); aboutData.addCredit(i18n("LibVNCServer / LibVNCClient developers"), i18n("VNC client library"), QStringLiteral("libvncserver-common@lists.sf.net"), QStringLiteral("http://libvncserver.sourceforge.net/")); aboutData.addAuthor(i18n("Abner Silva"), i18n("Telepathy Tubes Integration"), QStringLiteral("abner.silva@kdemail.net")); aboutData.setOrganizationDomain("kde.org"); KAboutData::setApplicationData(aboutData); app.setWindowIcon(QIcon::fromTheme(appName)); QCommandLineParser parser; aboutData.setupCommandLine(&parser); // command line options parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("fullscreen"), i18n("Start KRDC with the provided URL in fullscreen mode (works only with one URL)"))); parser.addPositionalArgument(QStringLiteral("url"), i18n("URLs to connect after startup")); parser.process(app); aboutData.processCommandLine(&parser); MainWindow *mainwindow = new MainWindow; mainwindow->show(); const QStringList args = parser.positionalArguments(); if (args.length() > 0) { for (int i = 0; i < args.length(); ++i) { QUrl url = QUrl(args.at(i)); // no URL scheme, assume argument is only a hostname if (url.scheme().isEmpty()) { QString defaultProto = Settings::defaultProtocol(); url.setScheme(defaultProto); url.setHost(args.at(i)); url.setPath(QString()); } if (!url.isValid()) { continue; } mainwindow->newConnection(url, parser.isSet(QStringLiteral("fullscreen"))); } } qCDebug(KRDC) << "########## KRDC ready:" << startupTimer.elapsed() << "ms ##########"; return app.exec(); } diff --git a/mainwindow.cpp b/mainwindow.cpp index b8bea13..5488fbc 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,1205 +1,1205 @@ /**************************************************************************** ** ** Copyright (C) 2007 - 2013 Urs Wolfer ** Copyright (C) 2009 - 2010 Tony Murray ** ** 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 "mainwindow.h" #include "krdc_debug.h" #include "remoteview.h" #include "settings.h" #include "config/preferencesdialog.h" #include "floatingtoolbar.h" #include "bookmarkmanager.h" #include "connectiondelegate.h" #include "remotedesktopsmodel.h" #include "systemtrayicon.h" #include "tabbedviewwidget.h" #include "hostpreferences.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MainWindow::MainWindow(QWidget *parent) : KXmlGuiWindow(parent), m_fullscreenWindow(nullptr), m_protocolInput(nullptr), m_addressInput(nullptr), m_toolBar(nullptr), m_currentRemoteView(-1), m_systemTrayIcon(nullptr), m_dockWidgetTableView(nullptr), m_newConnectionTableView(nullptr), m_newConnectionWidget(nullptr) { loadAllPlugins(); setupActions(); setStandardToolBarMenuEnabled(true); m_tabWidget = new TabbedViewWidget(this); m_tabWidget->setAutoFillBackground(true); m_tabWidget->setMovable(true); m_tabWidget->setTabPosition((QTabWidget::TabPosition) Settings::tabPosition()); m_tabWidget->setTabsClosable(Settings::tabCloseButton()); connect(m_tabWidget, SIGNAL(tabCloseRequested(int)), SLOT(closeTab(int))); if (Settings::tabMiddleClick()) connect(m_tabWidget, SIGNAL(mouseMiddleClick(int)), SLOT(closeTab(int))); connect(m_tabWidget, SIGNAL(tabBarDoubleClicked(int)), SLOT(openTabSettings(int))); m_tabWidget->tabBar()->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_tabWidget->tabBar(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(tabContextMenu(QPoint))); m_tabWidget->setMinimumSize(600, 400); setCentralWidget(m_tabWidget); createDockWidget(); setupGUI(ToolBar | Keys | Save | Create); if (Settings::systemTrayIcon()) { m_systemTrayIcon = new SystemTrayIcon(this); if(m_fullscreenWindow) m_systemTrayIcon->setAssociatedWidget(m_fullscreenWindow); } connect(m_tabWidget, SIGNAL(currentChanged(int)), SLOT(tabChanged(int))); if (Settings::showStatusBar()) statusBar()->showMessage(i18n("KDE Remote Desktop Client started")); updateActionStatus(); // disable remote view actions if (Settings::openSessions().count() == 0) // just create a new connection tab if there are no open sessions m_tabWidget->addTab(newConnectionWidget(), i18n("New Connection")); if (Settings::rememberSessions()) // give some time to create and show the window first QTimer::singleShot(100, this, SLOT(restoreOpenSessions())); } MainWindow::~MainWindow() { } void MainWindow::setupActions() { QAction *connectionAction = actionCollection()->addAction(QStringLiteral("new_connection")); connectionAction->setText(i18n("New Connection")); connectionAction->setIcon(QIcon::fromTheme(QStringLiteral("network-connect"))); actionCollection()->setDefaultShortcuts(connectionAction, KStandardShortcut::openNew()); connect(connectionAction, SIGNAL(triggered()), SLOT(newConnectionPage())); QAction *screenshotAction = actionCollection()->addAction(QStringLiteral("take_screenshot")); screenshotAction->setText(i18n("Copy Screenshot to Clipboard")); screenshotAction->setIconText(i18n("Screenshot")); screenshotAction->setIcon(QIcon::fromTheme(QStringLiteral("ksnapshot"))); connect(screenshotAction, SIGNAL(triggered()), SLOT(takeScreenshot())); QAction *fullscreenAction = actionCollection()->addAction(QStringLiteral("switch_fullscreen")); // note: please do not switch to KStandardShortcut unless you know what you are doing (see history of this file) fullscreenAction->setText(i18n("Switch to Full Screen Mode")); fullscreenAction->setIconText(i18n("Full Screen")); fullscreenAction->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); actionCollection()->setDefaultShortcuts(fullscreenAction, KStandardShortcut::fullScreen()); connect(fullscreenAction, SIGNAL(triggered()), SLOT(switchFullscreen())); QAction *viewOnlyAction = actionCollection()->addAction(QStringLiteral("view_only")); viewOnlyAction->setCheckable(true); viewOnlyAction->setText(i18n("View Only")); viewOnlyAction->setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); connect(viewOnlyAction, SIGNAL(triggered(bool)), SLOT(viewOnly(bool))); QAction *disconnectAction = actionCollection()->addAction(QStringLiteral("disconnect")); disconnectAction->setText(i18n("Disconnect")); disconnectAction->setIcon(QIcon::fromTheme(QStringLiteral("network-disconnect"))); actionCollection()->setDefaultShortcuts(disconnectAction, KStandardShortcut::close()); connect(disconnectAction, SIGNAL(triggered()), SLOT(disconnectHost())); QAction *showLocalCursorAction = actionCollection()->addAction(QStringLiteral("show_local_cursor")); showLocalCursorAction->setCheckable(true); showLocalCursorAction->setIcon(QIcon::fromTheme(QStringLiteral("input-mouse"))); showLocalCursorAction->setText(i18n("Show Local Cursor")); showLocalCursorAction->setIconText(i18n("Local Cursor")); connect(showLocalCursorAction, SIGNAL(triggered(bool)), SLOT(showLocalCursor(bool))); QAction *grabAllKeysAction = actionCollection()->addAction(QStringLiteral("grab_all_keys")); grabAllKeysAction->setCheckable(true); grabAllKeysAction->setIcon(QIcon::fromTheme(QStringLiteral("configure-shortcuts"))); grabAllKeysAction->setText(i18n("Grab All Possible Keys")); grabAllKeysAction->setIconText(i18n("Grab Keys")); connect(grabAllKeysAction, SIGNAL(triggered(bool)), SLOT(grabAllKeys(bool))); QAction *scaleAction = actionCollection()->addAction(QStringLiteral("scale")); scaleAction->setCheckable(true); scaleAction->setIcon(QIcon::fromTheme(QStringLiteral("zoom-fit-best"))); scaleAction->setText(i18n("Scale Remote Screen to Fit Window Size")); scaleAction->setIconText(i18n("Scale")); connect(scaleAction, SIGNAL(triggered(bool)), SLOT(scale(bool))); KStandardAction::quit(this, SLOT(quit()), actionCollection()); KStandardAction::preferences(this, SLOT(preferences()), actionCollection()); QAction *configNotifyAction = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection()); configNotifyAction->setVisible(false); m_menubarAction = KStandardAction::showMenubar(this, SLOT(showMenubar()), actionCollection()); m_menubarAction->setChecked(!menuBar()->isHidden()); KActionMenu *bookmarkMenu = new KActionMenu(i18n("Bookmarks"), actionCollection()); m_bookmarkManager = new BookmarkManager(actionCollection(), bookmarkMenu->menu(), this); actionCollection()->addAction(QStringLiteral("bookmark") , bookmarkMenu); connect(m_bookmarkManager, SIGNAL(openUrl(QUrl)), SLOT(newConnection(QUrl))); } void MainWindow::loadAllPlugins() { const KPluginInfo::List offers = KPluginTrader::self()->query(QStringLiteral("krdc")); const KConfigGroup conf = KSharedConfig::openConfig()->group(QStringLiteral("Plugins")); qCDebug(KRDC) << "Loading Plugins "; for (int i = 0; i < offers.size(); i++) { KPluginInfo info = offers[i]; info.load(conf); const bool enabled = info.isPluginEnabled(); if (enabled) { RemoteViewFactory *component = createPluginFromInfo(info); if (component != nullptr) { const int sorting = info.property(QStringLiteral("X-KDE-KRDC-Sorting")).toInt(); m_remoteViewFactories.insert(sorting, component); } else { qCDebug(KRDC) << "Error loading KRDC plugin (" << info.pluginName() << ')'; } } else { qCDebug(KRDC) << "# Plugin " << info.name() << " found, however it's not activated, skipping..."; continue; } } } RemoteViewFactory *MainWindow::createPluginFromInfo(const KPluginInfo &info) { RemoteViewFactory *plugin = nullptr; KPluginLoader loader(info.libraryPath()); KPluginFactory *factory = loader.factory(); if (factory) { plugin = factory->create(); } return plugin; } void MainWindow::restoreOpenSessions() { const QStringList list = Settings::openSessions(); QListIterator it(list); while (it.hasNext()) { newConnection(QUrl(it.next())); } } QUrl MainWindow::getInputUrl() { QString userInput = m_addressInput->text(); qCDebug(KRDC) << "input url " << userInput; // percent encode usernames so QUrl can parse it int lastAtIndex = userInput.indexOf(QRegExp(QStringLiteral("@[^@]+$"))); if (lastAtIndex >0) { userInput = QString::fromLatin1(QUrl::toPercentEncoding(userInput.left(lastAtIndex))) + userInput.mid(lastAtIndex); qCDebug(KRDC) << "input url " << userInput; } return QUrl(m_protocolInput->currentText() + QStringLiteral("://") + userInput); } void MainWindow::newConnection(const QUrl &newUrl, bool switchFullscreenWhenConnected, const QString &tabName) { m_switchFullscreenWhenConnected = switchFullscreenWhenConnected; const QUrl url = newUrl.isEmpty() ? getInputUrl() : newUrl; if (!url.isValid() || (url.host().isEmpty() && url.port() < 0) || (!url.path().isEmpty() && url.path() != QStringLiteral("/"))) { KMessageBox::error(this, i18n("The entered address does not have the required form.\n Syntax: [username@]host[:port]"), i18n("Malformed URL")); return; } if (m_protocolInput && m_addressInput) { int index = m_protocolInput->findText(url.scheme()); if (index>=0) m_protocolInput->setCurrentIndex(index); m_addressInput->setText(url.authority()); } RemoteView *view = nullptr; KConfigGroup configGroup = Settings::self()->config()->group(QStringLiteral("hostpreferences")).group(url.toDisplayString(QUrl::StripTrailingSlash)); foreach(RemoteViewFactory *factory, m_remoteViewFactories) { if (factory->supportsUrl(url)) { view = factory->createView(this, url, configGroup); qCDebug(KRDC) << "Found plugin to handle url (" << url.url() << "): " << view->metaObject()->className(); break; } } if (!view) { KMessageBox::error(this, i18n("The entered address cannot be handled."), i18n("Unusable URL")); return; } // Configure the view HostPreferences* prefs = view->hostPreferences(); // if the user press cancel if (! prefs->showDialogIfNeeded(this)) return; view->showDotCursor(prefs->showLocalCursor() ? RemoteView::CursorOn : RemoteView::CursorOff); view->setViewOnly(prefs->viewOnly()); if (! switchFullscreenWhenConnected) view->enableScaling(prefs->windowedScale()); connect(view, SIGNAL(framebufferSizeChanged(int,int)), this, SLOT(resizeTabWidget(int,int))); connect(view, SIGNAL(statusChanged(RemoteView::RemoteStatus)), this, SLOT(statusChanged(RemoteView::RemoteStatus))); connect(view, SIGNAL(disconnected()), this, SLOT(disconnectHost())); view->winId(); // native widget workaround for bug 253365 QScrollArea *scrollArea = createScrollArea(m_tabWidget, view); const int indexOfNewConnectionWidget = m_tabWidget->indexOf(m_newConnectionWidget); if (indexOfNewConnectionWidget >= 0) m_tabWidget->removeTab(indexOfNewConnectionWidget); const int newIndex = m_tabWidget->addTab(scrollArea, QIcon::fromTheme(QStringLiteral("krdc")), tabName.isEmpty() ? url.toDisplayString(QUrl::StripTrailingSlash) : tabName); m_tabWidget->setCurrentIndex(newIndex); m_remoteViewMap.insert(m_tabWidget->widget(newIndex), view); tabChanged(newIndex); // force to update m_currentRemoteView (tabChanged is not emitted when start page has been disabled) view->start(); } void MainWindow::openFromRemoteDesktopsModel(const QModelIndex &index) { const QString urlString = index.data(10001).toString(); const QString nameString = index.data(10003).toString(); if (!urlString.isEmpty()) { const QUrl url(urlString); // first check if url has already been opened; in case show the tab foreach (QWidget *widget, m_remoteViewMap.keys()) { if (m_remoteViewMap.value(widget)->url() == url) { m_tabWidget->setCurrentWidget(widget); return; } } newConnection(url, false, nameString); } } void MainWindow::selectFromRemoteDesktopsModel(const QModelIndex &index) { const QString urlString = index.data(10001).toString(); if (!urlString.isEmpty() && m_protocolInput && m_addressInput) { const QUrl url(urlString); m_addressInput->setText(url.authority()); int index = m_protocolInput->findText(url.scheme()); if (index>=0) m_protocolInput->setCurrentIndex(index); } } void MainWindow::resizeTabWidget(int w, int h) { qCDebug(KRDC) << "tabwidget resize, view size: w: " << w << ", h: " << h; if (m_fullscreenWindow) { qCDebug(KRDC) << "in fullscreen mode, refusing to resize"; return; } const QSize viewSize = QSize(w,h); QDesktopWidget *desktop = QApplication::desktop(); if (Settings::fullscreenOnConnect()) { int currentScreen = desktop->screenNumber(this); const QSize screenSize = desktop->screenGeometry(currentScreen).size(); if (screenSize == viewSize) { qCDebug(KRDC) << "screen size equal to target view size -> switch to fullscreen mode"; switchFullscreen(); return; } } if (Settings::resizeOnConnect()) { QWidget* currentWidget = m_tabWidget->currentWidget(); const QSize newWindowSize = size() - currentWidget->frameSize() + viewSize; const QSize desktopSize = desktop->availableGeometry().size(); qCDebug(KRDC) << "new window size: " << newWindowSize << " available space:" << desktopSize; if ((newWindowSize.width() >= desktopSize.width()) || (newWindowSize.height() >= desktopSize.height())) { qCDebug(KRDC) << "remote desktop needs more space than available -> show window maximized"; setWindowState(windowState() | Qt::WindowMaximized); return; } setWindowState(windowState() & ~ Qt::WindowMaximized); resize(newWindowSize); } } void MainWindow::statusChanged(RemoteView::RemoteStatus status) { qCDebug(KRDC) << status; // the remoteview is already deleted, so don't show it; otherwise it would crash if (status == RemoteView::Disconnecting || status == RemoteView::Disconnected) return; RemoteView *view = qobject_cast(QObject::sender()); const QString host = view->host(); QString iconName = QStringLiteral("krdc"); QString message; switch (status) { case RemoteView::Connecting: iconName = QStringLiteral("network-connect"); message = i18n("Connecting to %1", host); break; case RemoteView::Authenticating: iconName = QStringLiteral("dialog-password"); message = i18n("Authenticating at %1", host); break; case RemoteView::Preparing: iconName = QStringLiteral("view-history"); message = i18n("Preparing connection to %1", host); break; case RemoteView::Connected: iconName = QStringLiteral("krdc"); message = i18n("Connected to %1", host); if (view->grabAllKeys() != view->hostPreferences()->grabAllKeys()) { view->setGrabAllKeys(view->hostPreferences()->grabAllKeys()); updateActionStatus(); } // when started with command line fullscreen argument if (m_switchFullscreenWhenConnected) { m_switchFullscreenWhenConnected = false; switchFullscreen(); } if (Settings::rememberHistory()) { m_bookmarkManager->addHistoryBookmark(view); } break; default: break; } m_tabWidget->setTabIcon(m_tabWidget->indexOf(view), QIcon::fromTheme(iconName)); if (Settings::showStatusBar()) statusBar()->showMessage(message); } void MainWindow::takeScreenshot() { const QPixmap snapshot = currentRemoteView()->takeScreenshot(); QApplication::clipboard()->setPixmap(snapshot); } void MainWindow::switchFullscreen() { qCDebug(KRDC); if (m_fullscreenWindow) { // Leaving full screen mode m_fullscreenWindow->setWindowState(nullptr); m_fullscreenWindow->hide(); m_tabWidget->tabBar()->setHidden(m_tabWidget->count() <= 1 && !Settings::showTabBar()); m_tabWidget->setDocumentMode(false); setCentralWidget(m_tabWidget); show(); restoreGeometry(m_mainWindowGeometry); if (m_systemTrayIcon) m_systemTrayIcon->setAssociatedWidget(this); foreach (RemoteView * view, m_remoteViewMap) { view->enableScaling(view->hostPreferences()->windowedScale()); } if (m_toolBar) { m_toolBar->hideAndDestroy(); m_toolBar->deleteLater(); m_toolBar = nullptr; } actionCollection()->action(QStringLiteral("switch_fullscreen"))->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); actionCollection()->action(QStringLiteral("switch_fullscreen"))->setText(i18n("Switch to Full Screen Mode")); actionCollection()->action(QStringLiteral("switch_fullscreen"))->setIconText(i18n("Full Screen")); m_fullscreenWindow->deleteLater(); m_fullscreenWindow = nullptr; } else { // Entering full screen mode m_fullscreenWindow = new QWidget(this, Qt::Window); m_fullscreenWindow->setWindowTitle(i18nc("window title when in full screen mode (for example displayed in tasklist)", "KDE Remote Desktop Client (Full Screen)")); m_mainWindowGeometry = saveGeometry(); m_tabWidget->tabBar()->hide(); m_tabWidget->setDocumentMode(true); foreach(RemoteView *currentView, m_remoteViewMap) { currentView->enableScaling(currentView->hostPreferences()->fullscreenScale()); } QVBoxLayout *fullscreenLayout = new QVBoxLayout(m_fullscreenWindow); fullscreenLayout->setContentsMargins(QMargins(0, 0, 0, 0)); fullscreenLayout->addWidget(m_tabWidget); KToggleFullScreenAction::setFullScreen(m_fullscreenWindow, true); MinimizePixel *minimizePixel = new MinimizePixel(m_fullscreenWindow); minimizePixel->winId(); // force it to be a native widget (prevents problem with QX11EmbedContainer) connect(minimizePixel, SIGNAL(rightClicked()), m_fullscreenWindow, SLOT(showMinimized())); m_fullscreenWindow->installEventFilter(this); m_fullscreenWindow->show(); hide(); // hide after showing the new window so it stays on the same screen if (m_systemTrayIcon) m_systemTrayIcon->setAssociatedWidget(m_fullscreenWindow); actionCollection()->action(QStringLiteral("switch_fullscreen"))->setIcon(QIcon::fromTheme(QStringLiteral("view-restore"))); actionCollection()->action(QStringLiteral("switch_fullscreen"))->setText(i18n("Switch to Window Mode")); actionCollection()->action(QStringLiteral("switch_fullscreen"))->setIconText(i18n("Window Mode")); showRemoteViewToolbar(); } if (m_tabWidget->currentWidget() == m_newConnectionWidget) { m_addressInput->setFocus(); } } QScrollArea *MainWindow::createScrollArea(QWidget *parent, RemoteView *remoteView) { RemoteViewScrollArea *scrollArea = new RemoteViewScrollArea(parent); scrollArea->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); connect(scrollArea, SIGNAL(resized(int,int)), remoteView, SLOT(scaleResize(int,int))); QPalette palette = scrollArea->palette(); - palette.setColor(QPalette::Background, Settings::backgroundColor()); + palette.setColor(QPalette::Window, Settings::backgroundColor()); scrollArea->setPalette(palette); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setAutoFillBackground(true); scrollArea->setWidget(remoteView); return scrollArea; } void MainWindow::disconnectHost() { qCDebug(KRDC); RemoteView *view = qobject_cast(QObject::sender()); QWidget *widgetToDelete; if (view) { widgetToDelete = (QWidget*) view->parent()->parent(); m_remoteViewMap.remove(m_remoteViewMap.key(view)); } else { widgetToDelete = m_tabWidget->currentWidget(); view = currentRemoteView(); m_remoteViewMap.remove(m_remoteViewMap.key(view)); } saveHostPrefs(view); view->startQuitting(); // some deconstructors can't properly quit, so quit early m_tabWidget->removePage(widgetToDelete); widgetToDelete->deleteLater(); // if closing the last connection, create new connection tab if (m_tabWidget->count() == 0) { newConnectionPage(false); } // if the newConnectionWidget is the only tab and we are fullscreen, switch to window mode if (m_fullscreenWindow && m_tabWidget->count() == 1 && m_tabWidget->currentWidget() == m_newConnectionWidget) { switchFullscreen(); } } void MainWindow::closeTab(int index) { if (index == -1) { return; } QWidget *widget = m_tabWidget->widget(index); bool isNewConnectionPage = widget == m_newConnectionWidget; if (!isNewConnectionPage) { RemoteView *view = m_remoteViewMap.value(widget); m_remoteViewMap.remove(m_remoteViewMap.key(view)); view->startQuitting(); widget->deleteLater(); } m_tabWidget->removePage(widget); // if closing the last connection, create new connection tab if (m_tabWidget->count() == 0) { newConnectionPage(false); } // if the newConnectionWidget is the only tab and we are fullscreen, switch to window mode if (m_fullscreenWindow && m_tabWidget->count() == 1 && m_tabWidget->currentWidget() == m_newConnectionWidget) { switchFullscreen(); } } void MainWindow::openTabSettings(int index) { if (index == -1) { newConnectionPage(); return; } QWidget *widget = m_tabWidget->widget(index); RemoteViewScrollArea *scrollArea = qobject_cast(widget); if (!scrollArea) return; RemoteView *view = qobject_cast(scrollArea->widget()); if (!view) return; const QString url = view->url().url(); qCDebug(KRDC) << url; showSettingsDialog(url); } void MainWindow::showSettingsDialog(const QString &url) { HostPreferences *prefs = nullptr; foreach(RemoteViewFactory *factory, remoteViewFactoriesList()) { if (factory->supportsUrl(QUrl(url))) { prefs = factory->createHostPreferences(Settings::self()->config()->group(QStringLiteral("hostpreferences")).group(url), this); if (prefs) { qCDebug(KRDC) << "Found plugin to handle url (" << url << "): " << prefs->metaObject()->className(); } else { qCDebug(KRDC) << "Found plugin to handle url (" << url << "), but plugin does not provide preferences"; } } } if (prefs) { prefs->setShownWhileConnected(true); prefs->showDialog(this); } else { KMessageBox::error(this, i18n("The selected host cannot be handled."), i18n("Unusable URL")); } } void MainWindow::showConnectionContextMenu(const QPoint &pos) { // QTableView does not take headers into account when it does mapToGlobal(), so calculate the offset QPoint offset = QPoint(m_newConnectionTableView->verticalHeader()->size().width(), m_newConnectionTableView->horizontalHeader()->size().height()); QModelIndex index = m_newConnectionTableView->indexAt(pos); if (!index.isValid()) return; const QString url = index.data(10001).toString(); const QString title = index.model()->index(index.row(), RemoteDesktopsModel::Title).data(Qt::DisplayRole).toString(); const QString source = index.model()->index(index.row(), RemoteDesktopsModel::Source).data(Qt::DisplayRole).toString(); QMenu *menu = new QMenu(url, m_newConnectionTableView); QAction *connectAction = menu->addAction(QIcon::fromTheme(QStringLiteral("network-connect")), i18n("Connect")); QAction *renameAction = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename")); QAction *settingsAction = menu->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Settings")); QAction *deleteAction = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete")); // not very clean, but it works, if (!(source == i18nc("Where each displayed link comes from", "Bookmarks") || source == i18nc("Where each displayed link comes from", "History"))) { renameAction->setEnabled(false); deleteAction->setEnabled(false); } QAction *selectedAction = menu->exec(m_newConnectionTableView->mapToGlobal(pos + offset)); if (selectedAction == connectAction) { openFromRemoteDesktopsModel(index); } else if (selectedAction == renameAction) { //TODO: use inline editor if possible bool ok = false; const QString newTitle = QInputDialog::getText(this, i18n("Rename %1", title), i18n("Rename %1 to", title), QLineEdit::EchoMode::Normal, title, &ok); if (ok && !newTitle.isEmpty()) { BookmarkManager::updateTitle(m_bookmarkManager->getManager(), url, newTitle); } } else if (selectedAction == settingsAction) { showSettingsDialog(url); } else if (selectedAction == deleteAction) { if (KMessageBox::warningContinueCancel(this, i18n("Are you sure you want to delete %1?", url), i18n("Delete %1", title), KStandardGuiItem::del()) == KMessageBox::Continue) { BookmarkManager::removeByUrl(m_bookmarkManager->getManager(), url); } } menu->deleteLater(); } void MainWindow::tabContextMenu(const QPoint &point) { int index = m_tabWidget->tabBar()->tabAt(point); QWidget *widget = m_tabWidget->widget(index); RemoteViewScrollArea *scrollArea = qobject_cast(widget); if (!scrollArea) return; RemoteView *view = qobject_cast(scrollArea->widget()); if (!view) return; const QString url = view->url().toDisplayString(QUrl::StripTrailingSlash); qCDebug(KRDC) << url; QMenu *menu = new QMenu(url, this); QAction *bookmarkAction = menu->addAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("Add Bookmark")); QAction *closeAction = menu->addAction(QIcon::fromTheme(QStringLiteral("tab-close")), i18n("Close Tab")); QAction *selectedAction = menu->exec(QCursor::pos()); if (selectedAction) { if (selectedAction == closeAction) { closeTab(m_tabWidget->indexOf(widget)); } else if (selectedAction == bookmarkAction) { m_bookmarkManager->addManualBookmark(view->url(), url); } } menu->deleteLater(); } void MainWindow::showLocalCursor(bool showLocalCursor) { qCDebug(KRDC) << showLocalCursor; RemoteView* view = currentRemoteView(); view->showDotCursor(showLocalCursor ? RemoteView::CursorOn : RemoteView::CursorOff); view->hostPreferences()->setShowLocalCursor(showLocalCursor); saveHostPrefs(view); } void MainWindow::viewOnly(bool viewOnly) { qCDebug(KRDC) << viewOnly; RemoteView* view = currentRemoteView(); view->setViewOnly(viewOnly); view->hostPreferences()->setViewOnly(viewOnly); saveHostPrefs(view); } void MainWindow::grabAllKeys(bool grabAllKeys) { qCDebug(KRDC); RemoteView* view = currentRemoteView(); view->setGrabAllKeys(grabAllKeys); view->hostPreferences()->setGrabAllKeys(grabAllKeys); saveHostPrefs(view); } void MainWindow::scale(bool scale) { qCDebug(KRDC); RemoteView* view = currentRemoteView(); view->enableScaling(scale); if (m_fullscreenWindow) view->hostPreferences()->setFullscreenScale(scale); else view->hostPreferences()->setWindowedScale(scale); saveHostPrefs(view); } void MainWindow::showRemoteViewToolbar() { qCDebug(KRDC); if (!m_toolBar) { m_toolBar = new FloatingToolBar(m_fullscreenWindow, m_fullscreenWindow); m_toolBar->winId(); // force it to be a native widget (prevents problem with QX11EmbedContainer) m_toolBar->setSide(FloatingToolBar::Top); KComboBox *sessionComboBox = new KComboBox(m_toolBar); sessionComboBox->setStyleSheet(QStringLiteral("QComboBox:!editable{background:transparent;}")); sessionComboBox->setModel(m_tabWidget->getModel()); sessionComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); sessionComboBox->setCurrentIndex(m_tabWidget->currentIndex()); connect(sessionComboBox, SIGNAL(activated(int)), m_tabWidget, SLOT(setCurrentIndex(int))); connect(m_tabWidget, SIGNAL(currentChanged(int)), sessionComboBox, SLOT(setCurrentIndex(int))); m_toolBar->addWidget(sessionComboBox); QToolBar *buttonBox = new QToolBar(m_toolBar); buttonBox->addAction(actionCollection()->action(QStringLiteral("new_connection"))); buttonBox->addAction(actionCollection()->action(QStringLiteral("switch_fullscreen"))); QAction *minimizeAction = new QAction(m_toolBar); minimizeAction->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); minimizeAction->setText(i18n("Minimize Full Screen Window")); connect(minimizeAction, SIGNAL(triggered()), m_fullscreenWindow, SLOT(showMinimized())); buttonBox->addAction(minimizeAction); buttonBox->addAction(actionCollection()->action(QStringLiteral("take_screenshot"))); buttonBox->addAction(actionCollection()->action(QStringLiteral("view_only"))); buttonBox->addAction(actionCollection()->action(QStringLiteral("show_local_cursor"))); buttonBox->addAction(actionCollection()->action(QStringLiteral("grab_all_keys"))); buttonBox->addAction(actionCollection()->action(QStringLiteral("scale"))); buttonBox->addAction(actionCollection()->action(QStringLiteral("disconnect"))); buttonBox->addAction(actionCollection()->action(QStringLiteral("file_quit"))); QAction *stickToolBarAction = new QAction(m_toolBar); stickToolBarAction->setCheckable(true); stickToolBarAction->setIcon(QIcon::fromTheme(QStringLiteral("object-locked"))); stickToolBarAction->setText(i18n("Stick Toolbar")); connect(stickToolBarAction, SIGNAL(triggered(bool)), m_toolBar, SLOT(setSticky(bool))); buttonBox->addAction(stickToolBarAction); m_toolBar->addWidget(buttonBox); } } void setActionStatus(QAction* action, bool enabled, bool visible, bool checked) { action->setEnabled(enabled); action->setVisible(visible); action->setChecked(checked); } void MainWindow::updateActionStatus() { qCDebug(KRDC) << m_tabWidget->currentIndex(); bool enabled = true; if (m_tabWidget->currentWidget() == m_newConnectionWidget) enabled = false; RemoteView* view = (m_currentRemoteView >= 0 && enabled) ? currentRemoteView() : nullptr; actionCollection()->action(QStringLiteral("take_screenshot"))->setEnabled(enabled); actionCollection()->action(QStringLiteral("disconnect"))->setEnabled(enabled); setActionStatus(actionCollection()->action(QStringLiteral("view_only")), enabled, view ? view->supportsViewOnly() : false, view ? view->viewOnly() : false); setActionStatus(actionCollection()->action(QStringLiteral("show_local_cursor")), enabled, view ? view->supportsLocalCursor() : false, view ? view->dotCursorState() == RemoteView::CursorOn : false); setActionStatus(actionCollection()->action(QStringLiteral("scale")), enabled, view ? view->supportsScaling() : false, view ? view->scaling() : false); setActionStatus(actionCollection()->action(QStringLiteral("grab_all_keys")), enabled, enabled, view ? view->grabAllKeys() : false); } void MainWindow::preferences() { // An instance of your dialog could be already created and could be // cached, in which case you want to display the cached dialog // instead of creating another one if (PreferencesDialog::showDialog(QStringLiteral("preferences"))) return; // KConfigDialog didn't find an instance of this dialog, so lets // create it: PreferencesDialog *dialog = new PreferencesDialog(this, Settings::self()); // User edited the configuration - update your local copies of the // configuration data connect(dialog, SIGNAL(settingsChanged(QString)), this, SLOT(updateConfiguration())); dialog->show(); } void MainWindow::updateConfiguration() { if (!Settings::showStatusBar()) statusBar()->deleteLater(); else statusBar()->showMessage(QStringLiteral("")); // force creation of statusbar m_tabWidget->tabBar()->setHidden((m_tabWidget->count() <= 1 && !Settings::showTabBar()) || m_fullscreenWindow); m_tabWidget->setTabPosition((QTabWidget::TabPosition) Settings::tabPosition()); m_tabWidget->setTabsClosable(Settings::tabCloseButton()); disconnect(m_tabWidget, SIGNAL(mouseMiddleClick(int)), this, SLOT(closeTab(int))); // just be sure it is not connected twice if (Settings::tabMiddleClick()) connect(m_tabWidget, SIGNAL(mouseMiddleClick(int)), SLOT(closeTab(int))); if (Settings::systemTrayIcon() && !m_systemTrayIcon) { m_systemTrayIcon = new SystemTrayIcon(this); if(m_fullscreenWindow) m_systemTrayIcon->setAssociatedWidget(m_fullscreenWindow); } else if (m_systemTrayIcon) { delete m_systemTrayIcon; m_systemTrayIcon = nullptr; } // update the scroll areas background color for (int i = 0; i < m_tabWidget->count(); ++i) { QPalette palette = m_tabWidget->widget(i)->palette(); palette.setColor(QPalette::Dark, Settings::backgroundColor()); m_tabWidget->widget(i)->setPalette(palette); } // Send update configuration message to all views foreach (RemoteView *view, m_remoteViewMap) { view->updateConfiguration(); } } void MainWindow::quit(bool systemEvent) { const bool haveRemoteConnections = !m_remoteViewMap.isEmpty(); if (systemEvent || !haveRemoteConnections || KMessageBox::warningContinueCancel(this, i18n("Are you sure you want to quit the KDE Remote Desktop Client?"), i18n("Confirm Quit"), KStandardGuiItem::quit(), KStandardGuiItem::cancel(), QStringLiteral("DoNotAskBeforeExit")) == KMessageBox::Continue) { if (Settings::rememberSessions()) { // remember open remote views for next startup QStringList list; foreach (RemoteView *view, m_remoteViewMap) { qCDebug(KRDC) << view->url(); list.append(view->url().toDisplayString(QUrl::StripTrailingSlash)); } Settings::setOpenSessions(list); } saveHostPrefs(); foreach (RemoteView *view, m_remoteViewMap) { view->startQuitting(); } Settings::self()->save(); qApp->quit(); } } void MainWindow::configureNotifications() { KNotifyConfigWidget::configure(this); } void MainWindow::showMenubar() { if (m_menubarAction->isChecked()) menuBar()->show(); else menuBar()->hide(); } bool MainWindow::eventFilter(QObject *obj, QEvent *event) { // check for close events from the fullscreen window. if (obj == m_fullscreenWindow && event->type() == QEvent::Close) { quit(true); } // allow other events to pass through. return QObject::eventFilter(obj, event); } void MainWindow::closeEvent(QCloseEvent *event) { if (event->spontaneous()) { // Returns true if the event originated outside the application (a system event); otherwise returns false. event->ignore(); if (Settings::systemTrayIcon()) { hide(); // just hide the mainwindow, keep it in systemtray } else { quit(); } } else { quit(true); } } void MainWindow::saveProperties(KConfigGroup &group) { qCDebug(KRDC); KMainWindow::saveProperties(group); saveHostPrefs(); } void MainWindow::saveHostPrefs() { foreach (RemoteView *view, m_remoteViewMap) { saveHostPrefs(view); } } void MainWindow::saveHostPrefs(RemoteView* view) { // should saving this be a user option? if (view && view->scaling()) { QSize viewSize = m_tabWidget->currentWidget()->size(); qCDebug(KRDC) << "saving window size:" << viewSize; view->hostPreferences()->setWidth(viewSize.width()); view->hostPreferences()->setHeight(viewSize.height()); } Settings::self()->config()->sync(); } void MainWindow::tabChanged(int index) { qCDebug(KRDC) << index; m_tabWidget->tabBar()->setHidden((m_tabWidget->count() <= 1 && !Settings::showTabBar()) || m_fullscreenWindow); m_currentRemoteView = index; if (m_tabWidget->currentWidget() == m_newConnectionWidget) { m_currentRemoteView = -1; if(m_addressInput) m_addressInput->setFocus(); } const QString tabTitle = m_tabWidget->tabText(index).remove(QLatin1Char('&')); setCaption(tabTitle == i18n("New Connection") ? QString() : tabTitle); updateActionStatus(); } QWidget* MainWindow::newConnectionWidget() { if (m_newConnectionWidget) return m_newConnectionWidget; m_newConnectionWidget = new QWidget(this); QVBoxLayout *startLayout = new QVBoxLayout(m_newConnectionWidget); startLayout->setContentsMargins(QMargins(8, 4, 8, 4)); QSortFilterProxyModel *remoteDesktopsModelProxy = new QSortFilterProxyModel(this); remoteDesktopsModelProxy->setSourceModel(m_remoteDesktopsModel); remoteDesktopsModelProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); remoteDesktopsModelProxy->setFilterRole(10002); { QHBoxLayout *connectLayout = new QHBoxLayout; QLabel *addressLabel = new QLabel(i18n("Connect to:"), m_newConnectionWidget); m_protocolInput = new KComboBox(m_newConnectionWidget); m_addressInput = new KLineEdit(m_newConnectionWidget); m_addressInput->setClearButtonShown(true); m_addressInput->setPlaceholderText(i18n("Type here to connect to an address and filter the list.")); connect(m_addressInput, SIGNAL(textChanged(QString)), remoteDesktopsModelProxy, SLOT(setFilterFixedString(QString))); foreach(RemoteViewFactory *factory, m_remoteViewFactories) { m_protocolInput->addItem(factory->scheme()); } connect(m_addressInput, SIGNAL(returnPressed()), SLOT(newConnection())); m_addressInput->setToolTip(i18n("Type an IP or DNS Name here. Clear the line to get a list of connection methods.")); QPushButton *connectButton = new QPushButton(m_newConnectionWidget); connectButton->setToolTip(i18n("Goto Address")); connectButton->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-locationbar"))); connect(connectButton, SIGNAL(clicked()), SLOT(newConnection())); connectLayout->addWidget(addressLabel); connectLayout->addWidget(m_protocolInput); connectLayout->addWidget(m_addressInput, 1); connectLayout->addWidget(connectButton); connectLayout->setContentsMargins(QMargins(0, 6, 0, 10)); startLayout->addLayout(connectLayout); } { m_newConnectionTableView = new QTableView(m_newConnectionWidget); m_newConnectionTableView->setModel(remoteDesktopsModelProxy); // set up the view so it looks nice m_newConnectionTableView->setItemDelegate(new ConnectionDelegate(m_newConnectionTableView)); m_newConnectionTableView->setShowGrid(false); m_newConnectionTableView->setSelectionMode(QAbstractItemView::NoSelection); m_newConnectionTableView->verticalHeader()->hide(); m_newConnectionTableView->verticalHeader()->setDefaultSectionSize( m_newConnectionTableView->fontMetrics().height() + 3); m_newConnectionTableView->horizontalHeader()->setStretchLastSection(true); m_newConnectionTableView->setAlternatingRowColors(true); // set up sorting and actions (double click open, right click custom menu) m_newConnectionTableView->setSortingEnabled(true); m_newConnectionTableView->sortByColumn(Settings::connectionListSortColumn(), Qt::SortOrder(Settings::connectionListSortOrder())); m_newConnectionTableView->resizeColumnsToContents(); connect(m_newConnectionTableView->horizontalHeader(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), SLOT(saveConnectionListSort(int,Qt::SortOrder))); connect(m_newConnectionTableView, SIGNAL(doubleClicked(QModelIndex)), SLOT(openFromRemoteDesktopsModel(QModelIndex))); // useful to edit similar address connect(m_newConnectionTableView, SIGNAL(clicked(QModelIndex)), SLOT(selectFromRemoteDesktopsModel(QModelIndex))); m_newConnectionTableView->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_newConnectionTableView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showConnectionContextMenu(QPoint))); startLayout->addWidget(m_newConnectionTableView); } return m_newConnectionWidget; } void MainWindow::saveConnectionListSort(const int logicalindex, const Qt::SortOrder order) { Settings::setConnectionListSortColumn(logicalindex); Settings::setConnectionListSortOrder(order); Settings::self()->save(); } void MainWindow::newConnectionPage(bool clearInput) { const int indexOfNewConnectionWidget = m_tabWidget->indexOf(m_newConnectionWidget); if (indexOfNewConnectionWidget >= 0) m_tabWidget->setCurrentIndex(indexOfNewConnectionWidget); else { const int index = m_tabWidget->addTab(newConnectionWidget(), i18n("New Connection")); m_tabWidget->setCurrentIndex(index); } if(clearInput) { m_addressInput->clear(); } else { m_addressInput->selectAll(); } m_addressInput->setFocus(); } QMap MainWindow::remoteViewList() const { return m_remoteViewMap; } QList MainWindow::remoteViewFactoriesList() const { return m_remoteViewFactories.values(); } RemoteView* MainWindow::currentRemoteView() const { if (m_currentRemoteView >= 0) { return m_remoteViewMap.value(m_tabWidget->widget(m_currentRemoteView)); } else { return nullptr; } } void MainWindow::createDockWidget() { QDockWidget *remoteDesktopsDockWidget = new QDockWidget(this); QWidget *remoteDesktopsDockLayoutWidget = new QWidget(remoteDesktopsDockWidget); QVBoxLayout *remoteDesktopsDockLayout = new QVBoxLayout(remoteDesktopsDockLayoutWidget); remoteDesktopsDockWidget->setObjectName(QStringLiteral("remoteDesktopsDockWidget")); // required for saving position / state remoteDesktopsDockWidget->setWindowTitle(i18n("Remote Desktops")); QFontMetrics fontMetrics(remoteDesktopsDockWidget->font()); remoteDesktopsDockWidget->setMinimumWidth(fontMetrics.width(QStringLiteral("vnc://192.168.100.100:6000"))); actionCollection()->addAction(QStringLiteral("remote_desktop_dockwidget"), remoteDesktopsDockWidget->toggleViewAction()); m_dockWidgetTableView = new QTableView(remoteDesktopsDockLayoutWidget); m_remoteDesktopsModel = new RemoteDesktopsModel(this, m_bookmarkManager->getManager()); QSortFilterProxyModel *remoteDesktopsModelProxy = new QSortFilterProxyModel(this); remoteDesktopsModelProxy->setSourceModel(m_remoteDesktopsModel); remoteDesktopsModelProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); remoteDesktopsModelProxy->setFilterRole(10002); m_dockWidgetTableView->setModel(remoteDesktopsModelProxy); m_dockWidgetTableView->setShowGrid(false); m_dockWidgetTableView->verticalHeader()->hide(); m_dockWidgetTableView->verticalHeader()->setDefaultSectionSize( m_dockWidgetTableView->fontMetrics().height() + 2); m_dockWidgetTableView->horizontalHeader()->hide(); m_dockWidgetTableView->horizontalHeader()->setStretchLastSection(true); // hide all columns, then show the one we want for (int i=0; i < remoteDesktopsModelProxy->columnCount(); i++) { m_dockWidgetTableView->hideColumn(i); } m_dockWidgetTableView->showColumn(RemoteDesktopsModel::Title); m_dockWidgetTableView->sortByColumn(RemoteDesktopsModel::Title, Qt::AscendingOrder); connect(m_dockWidgetTableView, SIGNAL(doubleClicked(QModelIndex)), SLOT(openFromRemoteDesktopsModel(QModelIndex))); KLineEdit *filterLineEdit = new KLineEdit(remoteDesktopsDockLayoutWidget); filterLineEdit->setPlaceholderText(i18n("Filter")); filterLineEdit->setClearButtonShown(true); connect(filterLineEdit, SIGNAL(textChanged(QString)), remoteDesktopsModelProxy, SLOT(setFilterFixedString(QString))); remoteDesktopsDockLayout->addWidget(filterLineEdit); remoteDesktopsDockLayout->addWidget(m_dockWidgetTableView); remoteDesktopsDockWidget->setWidget(remoteDesktopsDockLayoutWidget); addDockWidget(Qt::LeftDockWidgetArea, remoteDesktopsDockWidget); } diff --git a/test/testview.cpp b/test/testview.cpp index 10ddfcc..14f6407 100644 --- a/test/testview.cpp +++ b/test/testview.cpp @@ -1,105 +1,105 @@ /**************************************************************************** ** ** Copyright (C) 2008 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 "testview.h" #include #include TestView::TestView(QWidget *parent, const QUrl &url, KConfigGroup configGroup) : RemoteView(parent) { m_hostPreferences = new TestHostPreferences(configGroup, this); Q_UNUSED(url); } TestView::~TestView() { emit disconnected(); setStatus(Disconnected); } bool TestView::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::MouseMove) return true; } return RemoteView::eventFilter(obj, event); } void TestView::asyncConnect() { QPalette pal = palette(); - pal.setColor(QPalette::Background, Qt::yellow); + pal.setColor(QPalette::Window, Qt::yellow); setPalette(pal); setAutoFillBackground(true); const QSize size = QSize(640, 480); setFixedSize(size); resize(size); setStatus(Connected); emit framebufferSizeChanged(size.width(), size.height()); emit connected(); setFocus(); } QSize TestView::framebufferSize() { return minimumSizeHint(); } QSize TestView::sizeHint() const { return maximumSize(); } bool TestView::isQuitting() { return false; } bool TestView::start() { setStatus(Connecting); // call it async in order to simulate real world behavior QTimer::singleShot(1000, this, SLOT(asyncConnect())); return true; } HostPreferences* TestView::hostPreferences() { return m_hostPreferences; } void TestView::switchFullscreen(bool on) { Q_UNUSED(on); } diff --git a/vnc/vncclientthread.cpp b/vnc/vncclientthread.cpp index b6b6ab1..78365ca 100644 --- a/vnc/vncclientthread.cpp +++ b/vnc/vncclientthread.cpp @@ -1,677 +1,677 @@ /**************************************************************************** ** ** 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 "vncclientthread.h" #include "krdc_debug.h" #include #include #include #include #include #include #include #include //for detecting intel AMT KVM vnc server static const QString INTEL_AMT_KVM_STRING= QLatin1String("Intel(r) AMT KVM"); static QThreadStorage instances; // Dispatch from this static callback context to the member context. rfbBool VncClientThread::newclientStatic(rfbClient *cl) { VncClientThread *t = (VncClientThread *)rfbClientGetClientData(cl, nullptr); Q_ASSERT(t); return t->newclient(); } // Dispatch from this static callback context to the member context. void VncClientThread::updatefbStatic(rfbClient *cl, int x, int y, int w, int h) { VncClientThread *t = (VncClientThread *)rfbClientGetClientData(cl, nullptr); Q_ASSERT(t); return t->updatefb(x, y, w, h); } // Dispatch from this static callback context to the member context. void VncClientThread::cuttextStatic(rfbClient *cl, const char *text, int textlen) { VncClientThread *t = (VncClientThread *)rfbClientGetClientData(cl, nullptr); Q_ASSERT(t); t->cuttext(text, textlen); } // Dispatch from this static callback context to the member context. char *VncClientThread::passwdHandlerStatic(rfbClient *cl) { VncClientThread *t = (VncClientThread *)rfbClientGetClientData(cl, nullptr); Q_ASSERT(t); return t->passwdHandler(); } // Dispatch from this static callback context to the member context. rfbCredential *VncClientThread::credentialHandlerStatic(rfbClient *cl, int credentialType) { VncClientThread *t = (VncClientThread *)rfbClientGetClientData(cl, nullptr); Q_ASSERT(t); return t->credentialHandler(credentialType); } // Dispatch from this static callback context to the member context. void VncClientThread::outputHandlerStatic(const char *format, ...) { VncClientThread **t = instances.localData(); va_list args; va_start(args, format); (*t)->outputHandler(format, args); va_end(args); } void VncClientThread::setClientColorDepth(rfbClient* cl, VncClientThread::ColorDepth cd) { switch(cd) { case bpp8: if (m_colorTable.isEmpty()) { m_colorTable.resize(256); int r,g,b; for (int i = 0; i < 256; ++i) { //pick out the red (3 bits), green (3 bits) and blue (2 bits) bits and make them maximum significant in 8bits //this gives a colortable for 8bit true colors r= (i & 0x07) << 5; g= (i & 0x38) << 2; b= i & 0xc0; m_colorTable[i] = qRgb(r, g, b); } } cl->format.depth = 8; cl->format.bitsPerPixel = 8; cl->format.redShift = 0; cl->format.greenShift = 3; cl->format.blueShift = 6; cl->format.redMax = 7; cl->format.greenMax = 7; cl->format.blueMax = 3; break; case bpp16: cl->format.depth = 16; cl->format.bitsPerPixel = 16; cl->format.redShift = 11; cl->format.greenShift = 5; cl->format.blueShift = 0; cl->format.redMax = 0x1f; cl->format.greenMax = 0x3f; cl->format.blueMax = 0x1f; break; case bpp32: default: cl->format.depth = 24; cl->format.bitsPerPixel = 32; cl->format.redShift = 16; cl->format.greenShift = 8; cl->format.blueShift = 0; cl->format.redMax = 0xff; cl->format.greenMax = 0xff; cl->format.blueMax = 0xff; } } rfbBool VncClientThread::newclient() { //8bit color hack for Intel(r) AMT KVM "classic vnc" = vnc server built in in Intel Vpro chipsets. if (INTEL_AMT_KVM_STRING == QLatin1String(cl->desktopName)) { qCDebug(KRDC) << "Intel(R) AMT KVM: switching to 8 bit color depth (workaround, recent libvncserver needed)"; setColorDepth(bpp8); } setClientColorDepth(cl, colorDepth()); const int width = cl->width, height = cl->height, depth = cl->format.bitsPerPixel; const int size = width * height * (depth / 8); if (frameBuffer) delete [] frameBuffer; // do not leak if we get a new framebuffer size frameBuffer = new uint8_t[size]; cl->frameBuffer = frameBuffer; memset(cl->frameBuffer, '\0', size); switch (quality()) { case RemoteView::High: cl->appData.encodingsString = "copyrect zlib hextile raw"; cl->appData.compressLevel = 0; cl->appData.qualityLevel = 9; break; case RemoteView::Medium: cl->appData.encodingsString = "copyrect tight zrle ultra zlib hextile corre rre raw"; cl->appData.compressLevel = 5; cl->appData.qualityLevel = 7; break; case RemoteView::Low: case RemoteView::Unknown: default: cl->appData.encodingsString = "copyrect tight zrle ultra zlib hextile corre rre raw"; cl->appData.compressLevel = 9; cl->appData.qualityLevel = 1; } SetFormatAndEncodings(cl); qCDebug(KRDC) << "Client created"; return true; } void VncClientThread::updatefb(int x, int y, int w, int h) { // qCDebug(KRDC) << "updated client: x: " << x << ", y: " << y << ", w: " << w << ", h: " << h; const int width = cl->width, height = cl->height; QImage img; switch(colorDepth()) { case bpp8: img = QImage(cl->frameBuffer, width, height, width, QImage::Format_Indexed8); img.setColorTable(m_colorTable); break; case bpp16: img = QImage(cl->frameBuffer, width, height, 2*width, QImage::Format_RGB16); break; case bpp32: img = QImage(cl->frameBuffer, width, height, 4*width, QImage::Format_RGB32); break; } if (img.isNull()) { qCDebug(KRDC) << "image not loaded"; } if (m_stopped) { return; // sending data to a stopped thread is not a good idea } setImage(img); emitUpdated(x, y, w, h); } void VncClientThread::cuttext(const char *text, int textlen) { const QString cutText = QString::fromUtf8(text, textlen); qCDebug(KRDC) << cutText; if (!cutText.isEmpty()) { emitGotCut(cutText); } } char *VncClientThread::passwdHandler() { qCDebug(KRDC) << "password request"; // Never request a password during a reconnect attempt. if (!m_keepalive.failed) { passwordRequest(); m_passwordError = true; } return strdup(m_password.toUtf8().constData()); } rfbCredential *VncClientThread::credentialHandler(int credentialType) { qCDebug(KRDC) << "credential request" << credentialType; rfbCredential *cred = nullptr; switch (credentialType) { case rfbCredentialTypeUser: passwordRequest(true); m_passwordError = true; cred = new rfbCredential; cred->userCredential.username = strdup(username().toUtf8().constData()); cred->userCredential.password = strdup(password().toUtf8().constData()); break; default: qCritical(KRDC) << "credential request failed, unsupported credentialType:" << credentialType; outputErrorMessage(i18n("VNC authentication type is not supported.")); break; } return cred; } void VncClientThread::outputHandler(const char *format, va_list args) { QString message; message.vsprintf(format, args); message = message.trimmed(); qCDebug(KRDC) << message; if ((message.contains(QLatin1String("Couldn't convert "))) || (message.contains(QLatin1String("Unable to connect to VNC server")))) { // Don't show a dialog if a reconnection is needed. Never contemplate // reconnection if we don't have a password. QString tmp = i18n("Server not found."); if (m_keepalive.set && !m_password.isNull()) { m_keepalive.failed = true; if (m_previousDetails != tmp) { m_previousDetails = tmp; clientStateChange(RemoteView::Disconnected, tmp); } } else { outputErrorMessageString = tmp; } } // Process general authentication failures before more specific authentication // failures. All authentication failures cancel any auto-reconnection that // may be in progress. if (message.contains(QLatin1String("VNC connection failed: Authentication failed"))) { m_keepalive.failed = false; outputErrorMessageString = i18n("VNC authentication failed."); } if ((message.contains(QLatin1String("VNC connection failed: Authentication failed, too many tries"))) || (message.contains(QLatin1String("VNC connection failed: Too many authentication failures")))) { m_keepalive.failed = false; outputErrorMessageString = i18n("VNC authentication failed because of too many authentication tries."); } if (message.contains(QLatin1String("VNC server closed connection"))) outputErrorMessageString = i18n("VNC server closed connection."); // If we are not going to attempt a reconnection, at least tell the user // the connection went away. if (message.contains(QLatin1String("read ("))) { // Don't show a dialog if a reconnection is needed. Never contemplate // reconnection if we don't have a password. QString tmp = i18n("Disconnected: %1.", message); if (m_keepalive.set && !m_password.isNull()) { m_keepalive.failed = true; clientStateChange(RemoteView::Disconnected, tmp); } else { outputErrorMessageString = tmp; } } // internal messages, not displayed to user if (message.contains(QLatin1String("VNC server supports protocol version 3.889"))) // see https://bugs.kde.org/162640 outputErrorMessageString = QLatin1String("INTERNAL:APPLE_VNC_COMPATIBILTY"); } VncClientThread::VncClientThread(QObject *parent) : QThread(parent) , frameBuffer(nullptr) , cl(nullptr) , m_stopped(false) { // We choose a small value for interval...after all if the connection is // supposed to sustain a VNC session, a reasonably frequent ping should // be perfectly supportable. m_keepalive.intervalSeconds = 1; m_keepalive.failedProbes = 3; m_keepalive.set = false; m_keepalive.failed = false; - m_previousDetails = QString::null; + m_previousDetails = QString(); outputErrorMessageString.clear(); //don't deliver error messages of old instances... QMutexLocker locker(&mutex); QTimer *outputErrorMessagesCheckTimer = new QTimer(this); outputErrorMessagesCheckTimer->setInterval(500); connect(outputErrorMessagesCheckTimer, SIGNAL(timeout()), this, SLOT(checkOutputErrorMessage())); outputErrorMessagesCheckTimer->start(); } VncClientThread::~VncClientThread() { if(isRunning()) { stop(); terminate(); const bool quitSuccess = wait(1000); qCDebug(KRDC) << "Attempting to stop in deconstructor, will crash if this fails:" << quitSuccess; } clientDestroy(); delete [] frameBuffer; } void VncClientThread::checkOutputErrorMessage() { if (!outputErrorMessageString.isEmpty()) { qCDebug(KRDC) << outputErrorMessageString; QString errorMessage = outputErrorMessageString; outputErrorMessageString.clear(); // show authentication failure error only after the 3rd unsuccessful try if ((errorMessage != i18n("VNC authentication failed.")) || m_passwordError) outputErrorMessage(errorMessage); } } void VncClientThread::setHost(const QString &host) { QMutexLocker locker(&mutex); m_host = host; } void VncClientThread::setPort(int port) { QMutexLocker locker(&mutex); m_port = port; } void VncClientThread::setQuality(RemoteView::Quality quality) { m_quality = quality; //set color depth dependent on quality switch(quality) { case RemoteView::Low: setColorDepth(bpp8); break; case RemoteView::High: setColorDepth(bpp32); break; case RemoteView::Medium: default: setColorDepth(bpp16); } } void VncClientThread::setColorDepth(ColorDepth colorDepth) { m_colorDepth= colorDepth; } RemoteView::Quality VncClientThread::quality() const { return m_quality; } VncClientThread::ColorDepth VncClientThread::colorDepth() const { return m_colorDepth; } void VncClientThread::setImage(const QImage &img) { QMutexLocker locker(&mutex); m_image = img; } const QImage VncClientThread::image(int x, int y, int w, int h) { QMutexLocker locker(&mutex); if (w == 0) // full image requested return m_image; else return m_image.copy(x, y, w, h); } void VncClientThread::emitUpdated(int x, int y, int w, int h) { emit imageUpdated(x, y, w, h); } void VncClientThread::emitGotCut(const QString &text) { emit gotCut(text); } void VncClientThread::stop() { QMutexLocker locker(&mutex); m_stopped = true; } void VncClientThread::run() { QMutexLocker locker(&mutex); VncClientThread **threadTls = new VncClientThread *(); *threadTls = this; instances.setLocalData(threadTls); while (!m_stopped) { // try to connect as long as the server allows locker.relock(); m_passwordError = false; locker.unlock(); if (clientCreate(false)) { // The initial connection attempt worked! break; } locker.relock(); if (m_passwordError) { locker.unlock(); // Try again. continue; } // The initial connection attempt failed, and not because of a // password problem. Bail out. m_stopped = true; locker.unlock(); } locker.relock(); qCDebug(KRDC) << "--------------------- Starting main VNC event loop ---------------------"; while (!m_stopped) { locker.unlock(); const int i = WaitForMessage(cl, 500); if (m_stopped || i < 0) { break; } if (i) { if (!HandleRFBServerMessage(cl)) { if (m_keepalive.failed) { do { // Reconnect after a short delay. That way, if the // attempt fails very quickly, we don't sit in a very // tight loop. clientDestroy(); msleep(1000); clientStateChange(RemoteView::Connecting, i18n("Reconnecting.")); } while (!clientCreate(true)); continue; } qCritical(KRDC) << "HandleRFBServerMessage failed"; break; } } locker.relock(); while (!m_eventQueue.isEmpty()) { ClientEvent* clientEvent = m_eventQueue.dequeue(); locker.unlock(); clientEvent->fire(cl); delete clientEvent; locker.relock(); } } m_stopped = true; } /** * Factor out the initialisation of the VNC client library. Notice this has * both static parts as in @see rfbClientLog and @see rfbClientErr, * as well as instance local data @see rfbGetClient(). * * On return from here, if cl is set, the connection will have been made else * cl will not be set. */ bool VncClientThread::clientCreate(bool reinitialising) { rfbClientLog = outputHandlerStatic; rfbClientErr = outputHandlerStatic; //24bit color dept in 32 bits per pixel = default. Will change colordepth and bpp later if needed cl = rfbGetClient(8, 3, 4); setClientColorDepth(cl, this->colorDepth()); cl->MallocFrameBuffer = newclientStatic; cl->canHandleNewFBSize = true; cl->GetPassword = passwdHandlerStatic; cl->GetCredential = credentialHandlerStatic; cl->GotFrameBufferUpdate = updatefbStatic; cl->GotXCutText = cuttextStatic; rfbClientSetClientData(cl, nullptr, this); cl->serverHost = strdup(m_host.toUtf8().constData()); cl->serverPort = m_port; qCDebug(KRDC) << "--------------------- trying init ---------------------"; if (!rfbInitClient(cl, nullptr, nullptr)) { if (!reinitialising) { // Don't whine on reconnection failure: presumably the network // is simply still down. qCritical(KRDC) << "rfbInitClient failed"; } cl = nullptr; return false; } if (reinitialising) { clientStateChange(RemoteView::Connected, i18n("Reconnected.")); } else { clientStateChange(RemoteView::Connected, i18n("Connected.")); } clientSetKeepalive(); return true; } /** * Undo @see clientCreate(). */ void VncClientThread::clientDestroy() { if (cl) { // Disconnect from vnc server & cleanup allocated resources rfbClientCleanup(cl); cl = nullptr; } } /** * The VNC client library does not make use of keepalives. We go behind its * back to set it up. */ void VncClientThread::clientSetKeepalive() { // If keepalive is disabled, do nothing. m_keepalive.set = false; m_keepalive.failed = false; if (!m_keepalive.intervalSeconds) { return; } int optval; socklen_t optlen = sizeof(optval); // Try to set the option active optval = 1; if (setsockopt(cl->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) { qCritical(KRDC) << "setsockopt(SO_KEEPALIVE)" << strerror(errno); return; } optval = m_keepalive.intervalSeconds; if (setsockopt(cl->sock, IPPROTO_TCP, TCP_KEEPIDLE, &optval, optlen) < 0) { qCritical(KRDC) << "setsockopt(TCP_KEEPIDLE)" << strerror(errno); return; } optval = m_keepalive.intervalSeconds; if (setsockopt(cl->sock, IPPROTO_TCP, TCP_KEEPINTVL, &optval, optlen) < 0) { qCritical(KRDC) << "setsockopt(TCP_KEEPINTVL)" << strerror(errno); return; } optval = m_keepalive.failedProbes; if(setsockopt(cl->sock, IPPROTO_TCP, TCP_KEEPCNT, &optval, optlen) < 0) { qCritical(KRDC) << "setsockopt(TCP_KEEPCNT)" << strerror(errno); return; } m_keepalive.set = true; qCDebug(KRDC) << "TCP keepalive set"; } /** * The VNC client state changed. */ void VncClientThread::clientStateChange(RemoteView::RemoteStatus status, const QString &details) { qCDebug(KRDC) << status << details << m_host << ":" << m_port; emit clientStateChanged(status, details); } ClientEvent::~ClientEvent() { } void PointerClientEvent::fire(rfbClient* cl) { SendPointerEvent(cl, m_x, m_y, m_buttonMask); } void KeyClientEvent::fire(rfbClient* cl) { SendKeyEvent(cl, m_key, m_pressed); } void ClientCutEvent::fire(rfbClient* cl) { QByteArray toUtf8Converted = text.toUtf8(); SendClientCutText(cl, toUtf8Converted.data(), toUtf8Converted.length()); } void VncClientThread::mouseEvent(int x, int y, int buttonMask) { QMutexLocker lock(&mutex); if (m_stopped) return; m_eventQueue.enqueue(new PointerClientEvent(x, y, buttonMask)); } void VncClientThread::keyEvent(int key, bool pressed) { QMutexLocker lock(&mutex); if (m_stopped) return; m_eventQueue.enqueue(new KeyClientEvent(key, pressed)); } void VncClientThread::clientCut(const QString &text) { QMutexLocker lock(&mutex); if (m_stopped) return; m_eventQueue.enqueue(new ClientCutEvent(text)); } #include "moc_vncclientthread.cpp" diff --git a/vnc/vncview.cpp b/vnc/vncview.cpp index a0036fb..bc4bb8f 100644 --- a/vnc/vncview.cpp +++ b/vnc/vncview.cpp @@ -1,689 +1,689 @@ /**************************************************************************** ** ** 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_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"; 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_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()); } } repaint(QRectF(x * m_horizontalFactor, y * m_verticalFactor, w * m_horizontalFactor, h * m_verticalFactor).toAlignedRect()); } 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); painter.setRenderHint(QPainter::SmoothPixmapTransform); const QRectF dstRect = event->rect(); const QRectF srcRect(dstRect.x() / m_horizontalFactor, dstRect.y() / m_verticalFactor, dstRect.width() / m_horizontalFactor, dstRect.height() / m_verticalFactor); painter.drawImage(dstRect, m_frame, srcRect); 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) + if (event->angleDelta().y() < 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"