diff options
author | sr | 2013-02-04 19:50:31 +0100 |
---|---|---|
committer | sr | 2013-02-04 19:50:31 +0100 |
commit | 1a5709501f94014d41987b956338bb6424b9f90c (patch) | |
tree | d3b93fe8dc406bca56aff147ef5cc4cbf9ed6be0 /src/client | |
parent | Test (diff) | |
download | pvs2-1a5709501f94014d41987b956338bb6424b9f90c.tar.gz pvs2-1a5709501f94014d41987b956338bb6424b9f90c.tar.xz pvs2-1a5709501f94014d41987b956338bb6424b9f90c.zip |
Initial commit
Diffstat (limited to 'src/client')
-rw-r--r-- | src/client/connectwindow/connectwindow.cpp | 284 | ||||
-rw-r--r-- | src/client/connectwindow/connectwindow.h | 95 | ||||
-rw-r--r-- | src/client/main.cpp | 41 | ||||
-rw-r--r-- | src/client/net/serverconnection.cpp | 348 | ||||
-rw-r--r-- | src/client/net/serverconnection.h | 62 | ||||
-rw-r--r-- | src/client/toolbar/toolbar.cpp | 200 | ||||
-rw-r--r-- | src/client/toolbar/toolbar.h | 78 | ||||
-rwxr-xr-x | src/client/util/platform/blankscreen.h | 25 | ||||
-rwxr-xr-x | src/client/util/platform/blankscreen_Win32.cpp | 41 | ||||
-rwxr-xr-x | src/client/util/platform/blankscreen_X11.cpp | 75 | ||||
-rw-r--r-- | src/client/util/util.cpp | 25 | ||||
-rw-r--r-- | src/client/util/util.h | 23 | ||||
-rw-r--r-- | src/client/vnc/vncserver.cpp | 191 | ||||
-rw-r--r-- | src/client/vnc/vncserver.h | 52 | ||||
-rw-r--r-- | src/client/vnc/vncthread.cpp | 314 | ||||
-rw-r--r-- | src/client/vnc/vncthread.h | 141 | ||||
-rw-r--r-- | src/client/vnc/vncwindow.cpp | 277 | ||||
-rw-r--r-- | src/client/vnc/vncwindow.h | 73 |
18 files changed, 2345 insertions, 0 deletions
diff --git a/src/client/connectwindow/connectwindow.cpp b/src/client/connectwindow/connectwindow.cpp new file mode 100644 index 0000000..bc4e9b2 --- /dev/null +++ b/src/client/connectwindow/connectwindow.cpp @@ -0,0 +1,284 @@ +/* + * connectwindow.cpp + * + * Created on: 28.01.2013 + * Author: sr + */ + +#include "connectwindow.h" +#include "../../shared/settings.h" +#include "../../shared/network.h" +#include "../../shared/util.h" + +#include "../net/serverconnection.h" + +#include <QNetworkInterface> + +#define UDPBUFSIZ 9000 +#define SALT_LEN 18 + +ConnectWindow::ConnectWindow(QWidget *parent) : + QDialog(parent), _connected(false), _timerDiscover(0), _timerHide(0), _connection(NULL), _state(Idle), + _hashErrorCount(0), _hashSslErrorCount(0), _certErrorCount(0), _ipErrorCount(0), _discoveryInterval(800) +{ + setupUi(this); + // + connect(cmdOK, SIGNAL(clicked()), this, SLOT(onOkClick())); + connect(cmdCancel, SIGNAL(clicked()), this, SLOT(onCancelClick())); + int tries = 10; + while (tries-- != 0) + { + const quint16 port = (quint16)(qrand() % 10000) + 10000; + if (_discoverySocket.bind(QHostAddress::Any, port)) + break; + if (tries == 0) + qFatal("Could not bind to any UDP port for server discovery."); + } + connect(&_discoverySocket, SIGNAL(readyRead()), this, SLOT(onUdpReadyRead())); + this->setState(Idle); +} + +ConnectWindow::~ConnectWindow() +{ + +} + +void ConnectWindow::setConnected(const bool connected) +{ + _connected = connected; + this->updateState(); + if (_state == Scanning) + { + killTimer(_timerDiscover); + _discoveryInterval = 1000; + _timerDiscover = startTimer(_discoveryInterval); + } +} + +void ConnectWindow::setState(const ConnectionState state) +{ + if (_state != state) + { + _state = state; + this->updateState(); + } +} + +void ConnectWindow::updateState() +{ + txtName->setEnabled(_state == Idle && !_connected); + + if (_connected) + { + cmdOK->setEnabled(true); + cmdOK->setText(tr("&Disconnect")); + lblStatus->setText(tr("Connected.")); + txtName->setEnabled(false); + return; + } + + if (_state != Idle) + cmdOK->setText(tr("&Stop")); + else + cmdOK->setText(tr("&Connect")); + + switch (_state) + { + case Idle: + lblStatus->setText(tr("Ready to connect; please enter session name.")); + break; + case Scanning: + lblStatus->setText(tr("Scanning for session %1.").arg(txtName->text())); + _timerDiscover = startTimer(_discoveryInterval); + break; + case Connecting: + lblStatus->setText(tr("Found session, connecting...")); + break; + case AwaitingChallenge: + lblStatus->setText(tr("Waiting for server challenge...")); + break; + case AwaitingChallengeResponse: + lblStatus->setText(tr("Replied to challenge, sent own...")); + break; + case LoggingIn: + lblStatus->setText(tr("Logging in...")); + break; + case Connected: + lblStatus->setText(tr("Connection established!")); + break; + case InvalidIpList: + case InvalidHash: + case InvalidCert: + case InvalidSslHash: + lblError->setText(tr("Invalid hash: %1; invalid cert: %2; invalid iplist: %3; invalid sslhash: %4") + .arg(_hashErrorCount).arg(_certErrorCount).arg(_ipErrorCount).arg(_hashSslErrorCount)); + break; + } +} + +/** + * Overrides + */ + +void ConnectWindow::timerEvent(QTimerEvent* event) +{ + if (event->timerId() == _timerDiscover) + { + killTimer(_timerDiscover); + if (_connected || _state != Scanning) // Not scanning, bail out + return; + if (_discoveryInterval < 30000) + _discoveryInterval += 100; + _timerDiscover = startTimer(_discoveryInterval); + // Don't send packet if we're trying to connect + if (_connection != NULL) + return; + // Send discovery + _packet.reset(); + QByteArray iplist(Network::interfaceAddressesToString().toUtf8()); + QByteArray salt1(SALT_LEN, 0); + if (_salt2.size() < SALT_LEN) + _salt2.resize(SALT_LEN); + for (int i = 0; i < SALT_LEN; ++i) + { + salt1[i] = qrand() & 0xff; + _salt2[i] = qrand() & 0xff; + } + _packet.reset(); + _packet.setField(_HASH, genSha1(&_nameBytes, &salt1, &iplist)); + _packet.setField(_SALT1, salt1); + _packet.setField(_SALT2, _salt2); + _packet.setField(_IPLIST, iplist); + foreach (QNetworkInterface interface, QNetworkInterface::allInterfaces()) + { + foreach (QNetworkAddressEntry entry, interface.addressEntries()) + { + if (!entry.broadcast().isNull() && entry.ip() != QHostAddress::LocalHost && entry.ip() != QHostAddress::LocalHostIPv6) + { + qDebug() << "Broadcasting to " << entry.broadcast().toString(); + if (!_packet.writeMessage(&_discoverySocket, entry.broadcast(), SERVICE_DISCOVERY_PORT)) + qDebug("FAILED"); + } + } + } + qDebug("Broadcasting to 255.255.255.255"); + if (!_packet.writeMessage(&_discoverySocket, QHostAddress::Broadcast, SERVICE_DISCOVERY_PORT)) + qDebug("FAILED"); + // End send discovery + } + else if(event->timerId() == _timerHide) + { + killTimer(_timerHide); + _timerHide = 0; + this->hide(); + } + else + // Unknown/Old timer id, kill it + killTimer(event->timerId()); +} + +void ConnectWindow::closeEvent(QCloseEvent *e) +{ + e->ignore(); + this->hide(); +} + +/** + * Slots + */ + +void ConnectWindow::onOkClick() +{ + if (_timerHide) + killTimer(_timerHide); + _timerHide = 0; + if (_timerDiscover) + killTimer(_timerDiscover); + if (_connected || _state != Idle) + { + // Stop or disconnect + _timerDiscover = 0; + emit disconnect(); + this->setState(Idle); + } + else + { + // Connect (scan for session) + _discoveryInterval = 800; + _nameBytes = txtName->text().toUtf8(); + _timerDiscover = startTimer(_discoveryInterval); + _hashErrorCount = _hashSslErrorCount = _certErrorCount = _ipErrorCount = 0; + this->setState(Scanning); + } +} + +void ConnectWindow::onCancelClick() +{ + this->hide(); +} + +void ConnectWindow::onUdpReadyRead() +{ + char data[UDPBUFSIZ]; + QHostAddress addr; + quint16 port; + while (_discoverySocket.hasPendingDatagrams()) + { + const qint64 size = _discoverySocket.readDatagram(data, UDPBUFSIZ, &addr, &port); + if (size <= 0 || _connection != NULL) + continue; + + _packet.reset(); + if (!_packet.readMessage(data, (quint32)size)) + continue; + // Valid packet, process it: + const QByteArray hash(_packet.getFieldBytes(_HASH)); + const QByteArray iplist(_packet.getFieldBytes(_IPLIST)); + const QByteArray port(_packet.getFieldBytes(_PORT)); + const QByteArray cert(_packet.getFieldBytes(_CERT)); + // Check if the source IP of the packet matches any of the addresses given in the IP list + if (!Network::isAddressInList(QString::fromUtf8(iplist), addr.toString())) + { + ++_ipErrorCount; + this->setState(InvalidIpList); + this->setState(Scanning); + continue; + } + // If so, check if the submitted hash seems valid + if (genSha1(&_nameBytes, &_salt2, &iplist, &port, &cert) != hash) + { + // did not match local session name, or other data was spoofed + ++_hashErrorCount; + this->setState(InvalidHash); + this->setState(Scanning); + continue; + } + // Otherwise it's a valid reply, try to connect + _connection = new ServerConnection(addr.toString(), (quint16)QString::fromUtf8(port).toInt(), _nameBytes, cert); + connect(_connection, SIGNAL(stateChange(ConnectWindow::ConnectionState)), this, SLOT(onConnectionStateChange(ConnectWindow::ConnectionState))); + connect(_connection, SIGNAL(destroyed(QObject*)), this, SLOT(onConnectionClosed(QObject*))); + } +} + +void ConnectWindow::onConnectionStateChange(ConnectWindow::ConnectionState state) +{ + bool reset = (_state == Scanning); + if (state == InvalidSslHash) + ++_hashSslErrorCount; + this->setState(state); + if (reset) + _state = Scanning; + if (state == Connected) + { + QObject::disconnect(_connection, SIGNAL(stateChange(ConnectWindow::ConnectionState)), this, SLOT(onConnectionStateChange(ConnectWindow::ConnectionState))); + QObject::disconnect(_connection, SIGNAL(destroyed(QObject*)), this, SLOT(onConnectionClosed(QObject*))); + emit connected(_connection); + _connection = NULL; + _timerHide = startTimer(2000); + } +} + +void ConnectWindow::onConnectionClosed(QObject* connection) +{ + _connection = NULL; +} diff --git a/src/client/connectwindow/connectwindow.h b/src/client/connectwindow/connectwindow.h new file mode 100644 index 0000000..d8f9ab9 --- /dev/null +++ b/src/client/connectwindow/connectwindow.h @@ -0,0 +1,95 @@ +/* + # Copyright (c) 2013 - OpenSLX Project, Computer Center University of + # Freiburg + # + # This program is free software distributed under the GPL version 2. + # See http://openslx.org/COPYING + # + # If you have any feedback please consult http://openslx.org/feedback and + # send your suggestions, praise, or complaints to feedback@openslx.org + # + # General information about OpenSLX can be found at http://openslx.org/ + # --------------------------------------------------------------------- + # - Allow user to connect/disconnect to/from server + */ + +#ifndef PVSCONNECTWINDOW_H_ +#define PVSCONNECTWINDOW_H_ + +#include <QtGui> +#include <QUdpSocket> +#include <QSslSocket> +#include "ui_connect.h" + +#include "../../shared/networkmessage.h" + +class ServerConnection; + +class ConnectWindow : public QDialog, private Ui_Dialog +{ +Q_OBJECT + +public: + enum ConnectionState + { + Idle, + Scanning, + Connecting, + AwaitingChallenge, + AwaitingChallengeResponse, + LoggingIn, + InvalidIpList, + InvalidHash, // Hash of UDP reply invalid + InvalidSslHash, // Hash of challenge inside SSL connection invalid + InvalidCert, + Connected + }; + +private: + bool _connected; + int _timerDiscover, _timerHide; + ServerConnection *_connection; + ConnectionState _state; + int _hashErrorCount, _hashSslErrorCount, _certErrorCount, _ipErrorCount; + int _discoveryInterval; + + QByteArray _nameBytes; + QByteArray _salt2; + QUdpSocket _discoverySocket; + NetworkMessage _packet; + + void setState(const ConnectionState state); + void updateState(); + +public: + ConnectWindow(QWidget *parent = NULL); + virtual ~ConnectWindow(); + + void setConnected(const bool connected); + +protected: + /* + void enterEvent(QEvent *e); + void leaveEvent(QEvent *e); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + */ + void timerEvent(QTimerEvent* event); + void closeEvent(QCloseEvent *e); + +protected slots: + void onOkClick(); + void onCancelClick(); + void onUdpReadyRead(); + void onConnectionStateChange(ConnectWindow::ConnectionState state); + void onConnectionClosed(QObject* connection); + //void onSsl + +signals: + void disconnect(); + void connected(ServerConnection* connection); + +}; + +#endif diff --git a/src/client/main.cpp b/src/client/main.cpp new file mode 100644 index 0000000..cea9ae8 --- /dev/null +++ b/src/client/main.cpp @@ -0,0 +1,41 @@ +#include "toolbar/toolbar.h" +#include "util/util.h" + +int main(int argc, char** argv) +{ + QApplication app(argc, argv); + app.setOrganizationName("openslx"); + app.setOrganizationDomain("openslx.org"); + app.setApplicationName("pvsclient"); + + qsrand((uint)QDateTime::currentMSecsSinceEpoch()); + + // Make sure settings directory exists + do { + USER_SETTINGS(settings); + QFileInfo fi(settings.fileName()); + QDir path(fi.path()); + qDebug() << "Settings directory is " << fi.path(); + if (!path.exists()) + path.mkpath(path.absolutePath()); + // Now check if settings file exists. If not, copy system default (if available) + if (!fi.exists()) + { + SYSTEM_SETTINGS(sys); + QFileInfo sysfi(sys.fileName()); + if (sysfi.exists()) + { + if (!QFile::copy(sys.fileName(), settings.fileName())) + qDebug() << "Copying default settings from " << sys.fileName() << " to " << settings.fileName() << " failed."; + } + } + } while (0); + + // use system locale as language to translate gui + QTranslator translator; + translator.load(":pvsclient"); + app.installTranslator(&translator); + + Toolbar pvsclient; + return app.exec(); +} diff --git a/src/client/net/serverconnection.cpp b/src/client/net/serverconnection.cpp new file mode 100644 index 0000000..849f43b --- /dev/null +++ b/src/client/net/serverconnection.cpp @@ -0,0 +1,348 @@ +#include "serverconnection.h" +#include <QtCore> +#include <QPixmap> +#include <QApplication> +#include <QDesktopWidget> +#include <QHostInfo> +#include <unistd.h> +#include <cstdlib> +//#define verbose +#include "../vnc/vncserver.h" + +#include "../../shared/util.h" +#include "../util/platform/blankscreen.h" + +#define CHALLENGE_LEN 20 + +ServerConnection::ServerConnection(const QString& host, const quint16 port, const QByteArray& sessionName, const QByteArray& certHash) : + QObject(NULL), _jpegQuality(80), _authed(0), _sessionName(sessionName), _certHash(certHash) +{ + _socket = new QSslSocket(); + _blank = new BlankScreen(); + connect(_socket, SIGNAL(encrypted()), this, SLOT(sock_connected())); + connect(_socket, SIGNAL(readyRead()), this, SLOT(sock_dataArrival())); + connect(_socket, SIGNAL(disconnected()), this, SLOT(sock_closed())); + connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(sock_error(QAbstractSocket::SocketError))); + connect(_socket, + SIGNAL(sslErrors(const QList<QSslError> &)), + this, + SLOT(sslErrors(const QList<QSslError> &)) + ); + qDebug("Connecting to %s on port %d", host.toUtf8().data(), (int)port); + _socket->connectToHostEncrypted(host, port); + _timerId = startTimer(4000); + // Connect the vnc start/stop signal to this class, so we can tell the server about successful vnc server startup + connect(VncServer::instance(), SIGNAL(started(int, QString&, QString&)), this, SLOT(onVncServerStartStop(int, QString&, QString&))); +} + +ServerConnection::~ServerConnection() +{ + this->disconnectFromServer(); + _blank->deleteLater(); + _blank = NULL; +} + +void ServerConnection::sendMessage(NetworkMessage& message) +{ + if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState) + return; + message.writeMessage(_socket); + if (!message.writeComplete()) + { + qCritical() << "SendMessage to server failed!"; + } +} + + +void ServerConnection::disconnectFromServer() +{ + if (_socket == NULL) + return; + qDebug("Closing connection to server"); + VncServer::instance()->stop(); + emit closeVnc(); + _socket->blockSignals(true); + _socket->abort(); + _socket->deleteLater(); + _socket = NULL; + this->deleteLater(); +} + +void ServerConnection::handleMsg() +{ + const QString &id = _fromServer.getFieldString(_ID); + + if (_authed == 0) + { + if (id == _CHALLENGE) + { + // Initial challenge request by server + emit stateChange(ConnectWindow::AwaitingChallengeResponse); + _myChallenge.resize(CHALLENGE_LEN); + for (int i = 0; i < CHALLENGE_LEN; ++i) + _myChallenge[i] = qrand() & 0xff; + QByteArray serverChallenge(_fromServer.getFieldBytes(_CHALLENGE)); + _toServer.reset(); + _toServer.setField(_ID, _CHALLENGE); + _toServer.setField(_HASH, genSha1(&_sessionName, &serverChallenge)); + _toServer.setField(_CHALLENGE, _myChallenge); + _toServer.writeMessage(_socket); + qDebug("Received challenge, replying, sending own challenge step <- 1"); + _authed = 1; + } + return; + } + + if (_authed == 1) + { + if (id == _CHALLENGE) + { + qDebug("Received challenge reply"); + if (_timerId != 0) + { + killTimer(_timerId); + _timerId = 0; + } + // Check challenge response + QByteArray serverhash(_fromServer.getFieldBytes(_HASH)); + if (serverhash != genSha1(&_sessionName, &_myChallenge)) + { + qDebug("invalid. STOP."); + emit stateChange(ConnectWindow::InvalidSslHash); + this->disconnectFromServer(); + return; + } + emit stateChange(ConnectWindow::LoggingIn); + char *user = getlogin(); + if (user == NULL || *user == '\0') + user = getenv("USER"); + if (user == NULL || *user == '\0') + user = getenv("USERNAME"); + if (user == NULL || *user == '\0') + user = (char*)"Hans Affe"; + _toServer.reset(); + _toServer.setField(_ID, _LOGIN); + _toServer.setField("HOST", QHostInfo::localHostName()); + _toServer.setField("NAME", QString(user)); + qDebug("Sending login request!"); + if (_toServer.writeMessage(_socket)) + { + _authed = 2; + qDebug("valid, step <- 2"); + } + else + { + this->disconnectFromServer(); + } + } + return; + } + + if (_authed == 2) + { + if (id == _LOGIN) + { + qDebug("login accepted, step <- 3"); + _authed = 3; + emit stateChange(ConnectWindow::Connected); + } + return; + } + + // message THUMB - server requests screenshot as thumbnail + if (id == _THUMB) + { + int x = _fromServer.getFieldString(_X).toInt(); + int y = _fromServer.getFieldString(_Y).toInt(); + if (x < 32) + x = 32; + else if (x > 400) + x = 400; + if (y < 18) + y = 18; + else if (y > 300) + y = 300; + QPixmap desktop(QPixmap::grabWindow(QApplication::desktop()->winId()).scaled( + x, y, + Qt::KeepAspectRatio, + Qt::SmoothTransformation)); + QByteArray bytes; + QBuffer jpgBuffer(&bytes); + jpgBuffer.open(QIODevice::WriteOnly); + qDebug("JPEG quality is %d", _jpegQuality); + if (desktop.save(&jpgBuffer, "JPG", _jpegQuality)) // writes pixmap into bytes in JPG format + { + // Try to adjust quality so we stay between 3 and 4.5 KB + if (_jpegQuality < 90 && bytes.size() < 3000) + _jpegQuality += 7; + else if (_jpegQuality > 40 && bytes.size() > 4500) + _jpegQuality -= 7; + } + else + { // FALLBACK + bytes.clear(); + QBuffer pngBuffer(&bytes); + pngBuffer.open(QIODevice::WriteOnly); + if (!desktop.save(&pngBuffer, "PNG")) // writes pixmap into bytes in PNG format + { + qDebug("Could not convert screenshot to PNG nor JPG"); + return; // Failed :-( + } + } + _toServer.reset(); + _toServer.setField(_ID, _THUMB); + _toServer.setField(_IMG, bytes); + sendMessage(_toServer); + } // message VNCSERVER - start local vncserver + else if (id == _VNCSERVER) + { + const bool enable = (_fromServer.getFieldString("ENABLE").toInt() != 0); + if (enable) + { + emit closeVnc(); // In case we are watching some other client, stop doing so + VncServer::instance()->start(); + } + else + { + VncServer::instance()->stop(); + } + } + else if (id == _VNCCLIENT) + { + const QString host(_fromServer.getFieldString("HOST")); + const int port = _fromServer.getFieldString("PORT").toInt(); + if (host.isEmpty() || port <= 0) + { + emit closeVnc(); + } + else + { + emit openVnc(host, port, _fromServer.getFieldString("ROPASS"), true, true, _fromServer.getFieldString("CAPTION"), _fromServer.getFieldString("CLIENTID").toInt()); + } + } + else if (id == _LOCK) + { + const bool enable = (_fromServer.getFieldString("ENABLE").toInt() != 0); + if (enable) + _blank->lock(_fromServer.getFieldString("MESSAGE")); + else + _blank->unlock(); + } +} + +/** + * Override + */ + +void ServerConnection::timerEvent(QTimerEvent *event) +{ + if (_timerId == 0) + return; + killTimer(_timerId); + _timerId = 0; + this->disconnectFromServer(); +} + +/** + * Slots + */ + +void ServerConnection::onVncServerStartStop(int port, QString& ropass, QString& rwpass) +{ + _toServer.reset(); + _toServer.setField(_ID, _VNCSERVER); + if (port <= 0) + { + _toServer.setField("PORT", QByteArray("0")); + } + else + { + _toServer.setField("PORT", QString::number(port)); + _toServer.setField("ROPASS", ropass); + _toServer.setField("RWPASS", rwpass); + } + sendMessage(_toServer); +} + +void ServerConnection::onVncViewerStartStop(const bool started, const int clientId) +{ + _toServer.reset(); + _toServer.setField(_ID, _VNCCLIENT); + if (started) + _toServer.setField("ENABLED", QByteArray("1")); + else + _toServer.setField("ENABLED", QByteArray("0")); + _toServer.setField("CLIENTID", QString::number(clientId)); + sendMessage(_toServer); +} + +void ServerConnection::sslErrors(const QList<QSslError> & errors) +{ + for (QList<QSslError>::const_iterator it = errors.begin(); it != errors.end(); it++) + { + QSslError err = *it; + qDebug("Connect SSL: %s", qPrintable(err.errorString())); + if (err.error() == QSslError::HostNameMismatch) + continue; // We don't pay attention to hostnames for validation + if (err.error() == QSslError::SelfSignedCertificate) + continue; // Also, this will always be the case; we check the fingerprint later + if (err.error() == QSslError::CertificateNotYetValid || err.error() == QSslError::CertificateExpired) + continue; + // Some other SSL error - do not ignore + return; + } + _socket->ignoreSslErrors(); +} + +void ServerConnection::sock_dataArrival() +{ + if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState) + { + qDebug("dataArrival called in bad state"); + return; + } + + while (_socket->bytesAvailable()) + { + bool retval; + retval = _fromServer.readMessage(_socket); // let the message read data from socket + if (!retval) // error parsing msg, disconnect client! + { + this->disconnectFromServer(); + return; + } + if (_fromServer.readComplete()) // message is complete + { + this->handleMsg(); + if (_socket == NULL) + return; + _fromServer.reset(); + } + } +} + +void ServerConnection::sock_closed() +{ + // should this be unreliable in some way i suggest using the signal "stateChanged()" instead + // and check if the state changed to unconnected. + qDebug("Socket was closed... oh well.."); + this->disconnectFromServer(); +} + +void ServerConnection::sock_error(QAbstractSocket::SocketError errcode) +{ + qDebug("Connection error: %d", (int)errcode); + this->disconnectFromServer(); +} + +void ServerConnection::sock_connected() +{ + QByteArray cert(_socket->peerCertificate().digest(QCryptographicHash::Sha1)); + if (_certHash != cert) + { + emit stateChange(ConnectWindow::InvalidCert); + this->disconnectFromServer(); + return; + } + emit stateChange(ConnectWindow::AwaitingChallenge); +} diff --git a/src/client/net/serverconnection.h b/src/client/net/serverconnection.h new file mode 100644 index 0000000..afb8204 --- /dev/null +++ b/src/client/net/serverconnection.h @@ -0,0 +1,62 @@ +#ifndef _PVSSERVERCONNECTION_H_ +#define _PVSSERVERCONNECTION_H_ + +#include <QSslSocket> +#include "../../shared/networkmessage.h" +#include "../connectwindow/connectwindow.h" + +class BlankScreen; + +class ServerConnection : public QObject +{ +Q_OBJECT + +private: + QSslSocket *_socket; + BlankScreen *_blank; + int _timerId; + int _jpegQuality; + int _authed; + + NetworkMessage _fromServer, _toServer; + QByteArray _expectedFingerprint; + QString _passwd; + QByteArray _myChallenge; + QByteArray _sessionName; + QByteArray _certHash; + + void handleMsg(); + +public: + ServerConnection(const QString& host, const quint16 port, const QByteArray& sessionName, const QByteArray& certHash); + void disconnectFromServer(); + ~ServerConnection(); + const inline bool isConnected() const + { + return _socket != NULL && _socket->state() == QAbstractSocket::ConnectedState; + } + + void sendMessage(NetworkMessage& message); + +protected: + void timerEvent(QTimerEvent *event); + +private slots: + void sslErrors(const QList<QSslError> & errors); // triggered for errors that occur during SSL negotiation + void sock_dataArrival(); // triggered if data is available for reading + void sock_closed(); // triggered if the socket is closed + void sock_error(QAbstractSocket::SocketError errcode); // triggered if an error occurs on the socket + void sock_connected(); // triggered if the connection is established and ready to use + + void onVncServerStartStop(int port, QString& ropass, QString& rwpass); // triggered if the local vnc server was started + + void onVncViewerStartStop(const bool started, const int clientId); + +signals: + void openVnc(const QString& host, int port, const QString& passwd, bool ro, bool fullscreen, const QString& caption, const int clientId); + void closeVnc(); + void stateChange(ConnectWindow::ConnectionState state); + +}; + +#endif diff --git a/src/client/toolbar/toolbar.cpp b/src/client/toolbar/toolbar.cpp new file mode 100644 index 0000000..416a68f --- /dev/null +++ b/src/client/toolbar/toolbar.cpp @@ -0,0 +1,200 @@ +/* + * toolbar.cpp + * + * Created on: 21.01.2013 + * Author: sr + */ + +#include "toolbar.h" +#include "../../shared/settings.h" +#include "../net/serverconnection.h" +#include "../vnc/vncwindow.h" +#include "../vnc/vncserver.h" + +Toolbar::Toolbar(QWidget *parent) : + QWidget(parent), _location(POSITION_TOP_CENTER), _hideTimer(0), _connection(NULL) +{ + setupUi(this); + setWindowFlags(Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint); + setAttribute(Qt::WA_AlwaysShowToolTips); + setAttribute(Qt::WA_QuitOnClose); + setVisible(true); + // VNC Window + _vnc = new VncWindow(NULL); + // Connect window + _connectWindow = new ConnectWindow(NULL); + connect(_connectWindow, SIGNAL(disconnect()), this, SLOT(onDoDisconnect())); + connect(_connectWindow, SIGNAL(connected(ServerConnection*)), this, SLOT(onConnected(ServerConnection*))); + // + setupMenu(); + setLocation(); + hideBar(); +} + +void Toolbar::setupMenu() +{ + _menu = new QMenu(this); + // setup actions + _acnDisconnect = new QAction(tr("&Connect"), this); + //_acnDisconnect->setEnabled(false); + _acnQuit = new QAction(tr("&Quit"), this); + + // setup menu + _menu->addAction(_acnDisconnect); + _menu->addSeparator(); + _menu->addAction(_acnQuit); + + cmdMenu->setMenu(_menu); + + connect(_acnQuit, SIGNAL(triggered()), this, SLOT(onQuit())); + connect(_acnDisconnect, SIGNAL(triggered()), _connectWindow, SLOT(show())); +} + +Toolbar::~Toolbar() +{ + VncServer::instance()->stop(); + _vnc->deleteLater(); + _connectWindow->deleteLater(); +} + +//###########\\/\/ + +void Toolbar::setLocation() +{ + const QDesktopWidget desktop; + const QRect primaryScreen = desktop.screenGeometry(); + switch (_location) + { + case POSITION_TOP_LEFT: + move(primaryScreen.left(), primaryScreen.top()); + break; + case POSITION_TOP_CENTER: + move((primaryScreen.width() - this->width()) / 2 + primaryScreen.left(), primaryScreen.top()); + break; + case POSITION_TOP_RIGHT: + move(primaryScreen.right() - width(), primaryScreen.top()); + break; + case POSITION_BOTTOM_LEFT: + move(primaryScreen.left(), primaryScreen.bottom() - height()); + break; + case POSITION_BOTTOM_CENTER: + move((primaryScreen.width() - this->width()) / 2 + primaryScreen.left(), primaryScreen.bottom() - height()); + break; + case POSITION_BOTTOM_RIGHT: + move(primaryScreen.right() - width(), primaryScreen.bottom() - height()); + break; + default: + break; + } +} + +void Toolbar::setBarVisible(bool shown) +{ + const QDesktopWidget desktop; + const QRect primaryScreen = desktop.screenGeometry(); + if (!shown) + { + if (_location <= POSITION_TOP_RIGHT) + move(x(), primaryScreen.top() + 2 - height()); + else + move(x(), primaryScreen.bottom() - 2); + } + else + { + if (_location <= POSITION_TOP_RIGHT) + move(x(), primaryScreen.top()); + else + move(x(), primaryScreen.bottom() - height()); + } +} + +bool Toolbar::hideBar() +{ + if (_menu->isVisible()) // Don't hide window if any menu is open + return false; + setBarVisible(false); + return true; +} + +/** + * Override + */ + +void Toolbar::leaveEvent(QEvent* e) +{ + if (_hideTimer == 0) + _hideTimer = startTimer(100); + _hideDelay = 6; + QWidget::leaveEvent(e); +} + +void Toolbar::enterEvent(QEvent* e) +{ + if (_hideTimer != 0) + { + killTimer(_hideTimer); + _hideTimer = 0; + } + setBarVisible(true); + QWidget::enterEvent(e); +} + +void Toolbar::timerEvent(QTimerEvent* event) +{ + if (event->timerId() == _hideTimer) + { + if (--_hideDelay <= 0) + { + if (hideBar()) + { + killTimer(_hideTimer); + _hideTimer = 0; + } + } + } +} + +/** + * Slots + */ + +void Toolbar::onDisconnected(QObject* connection) +{ + if (connection != _connection) + qDebug("onDisconnect pointer mismatch!"); + _connectWindow->setConnected(false); + _connection = NULL; + lblStatus->setStyleSheet("color:red"); + lblStatus->setText(tr("Offline")); +} + +void Toolbar::onConnected(ServerConnection* connection) +{ + lblStatus->setStyleSheet("color:green"); + lblStatus->setText(tr("Online")); + // + if (_connection != NULL) + { + disconnect(_connection, SIGNAL(destroyed(QObject*)), this, SLOT(onDisconnected(QObject*))); + _connection->blockSignals(true); + _connection->disconnectFromServer(); + } + _connection = connection; + connect(_connection, SIGNAL(destroyed(QObject*)), this, SLOT(onDisconnected(QObject*))); + connect(_connection, SIGNAL(openVnc(const QString&, int, const QString&, bool, bool, const QString&, const int)), + _vnc, SLOT(open(const QString&, int, const QString&, bool, bool, const QString&, const int))); + connect(_connection, SIGNAL(closeVnc()), _vnc, SLOT(close())); + connect(_vnc, SIGNAL(running(const bool, const int)), _connection, SLOT(onVncViewerStartStop(const bool, const int))); + _connectWindow->setConnected(true); +} + +void Toolbar::onDoDisconnect() +{ + if (_connection != NULL) + _connection->disconnectFromServer(); +} + +void Toolbar::onQuit() +{ + QApplication::exit(0); +} diff --git a/src/client/toolbar/toolbar.h b/src/client/toolbar/toolbar.h new file mode 100644 index 0000000..0ce4d2b --- /dev/null +++ b/src/client/toolbar/toolbar.h @@ -0,0 +1,78 @@ +/* + # Copyright (c) 2009, 2010 - OpenSLX Project, Computer Center University of + # Freiburg + # + # This program is free software distributed under the GPL version 2. + # See http://openslx.org/COPYING + # + # If you have any feedback please consult http://openslx.org/feedback and + # send your suggestions, praise, or complaints to feedback@openslx.org + # + # General information about OpenSLX can be found at http://openslx.org/ + */ + +#ifndef PVSCLIENTGUI_H_ +#define PVSCLIENTGUI_H_ + +#include <QtGui> +#include "ui_toolbar.h" + +class ServerConnection; +class VncWindow; +class ConnectWindow; +class BlankScreen; + +class Toolbar : public QWidget, private Ui_ToolbarClass +{ +Q_OBJECT + +private: + int _location; + int _hideTimer; + int _hideDelay; + QMenu *_menu; + QAction *_acnDisconnect; + QAction *_acnQuit; + ServerConnection *_connection; + + ConnectWindow *_connectWindow; + VncWindow *_vnc; + + void setLocation(); + bool hideBar(); + void setBarVisible(bool shown); + void setupMenu(); + + void leaveEvent(QEvent* e); + void enterEvent(QEvent* e); + void timerEvent(QTimerEvent* event); + +public: + Toolbar(QWidget *parent = NULL); + virtual ~Toolbar(); + + int const static POSITION_TOP_LEFT = 0; + int const static POSITION_TOP_CENTER = 1; + int const static POSITION_TOP_RIGHT = 2; + int const static POSITION_BOTTOM_LEFT = 3; + int const static POSITION_BOTTOM_CENTER = 4; + int const static POSITION_BOTTOM_RIGHT = 5; + +protected: + /* + void enterEvent(QEvent *e); + void leaveEvent(QEvent *e); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + */ + +private slots: + void onDisconnected(QObject* connection); + void onConnected(ServerConnection* connection); + void onDoDisconnect(); + void onQuit(); + +}; + +#endif /* PVSCLIENTGUI_H_ */ diff --git a/src/client/util/platform/blankscreen.h b/src/client/util/platform/blankscreen.h new file mode 100755 index 0000000..8ed4c04 --- /dev/null +++ b/src/client/util/platform/blankscreen.h @@ -0,0 +1,25 @@ +#ifndef _BLANKSCREEN_H_ +#define _BLANKSCREEN_H_ + +#include <QString> +#include <QDialog> + +class BlankScreen_Sysdep; + +class BlankScreen : public QDialog +{ + Q_OBJECT +public: + BlankScreen(); + virtual ~BlankScreen(); + void draw(bool force = false); + bool lock(const QString& message); + bool unlock(); + +private: + bool _locked; + QString _message; + BlankScreen_Sysdep* _sysdep; +}; + +#endif diff --git a/src/client/util/platform/blankscreen_Win32.cpp b/src/client/util/platform/blankscreen_Win32.cpp new file mode 100755 index 0000000..2bba1cf --- /dev/null +++ b/src/client/util/platform/blankscreen_Win32.cpp @@ -0,0 +1,41 @@ +
+#include "blankscreen.h"
+#include <qwidget.h>
+
+struct BlankScreen_Sysdep {
+
+ bool locked;
+ QWidget* blankwin;
+
+ QString lockMsg;
+ int blackColor, whiteColor;
+ int offX, offY;
+};
+
+BlankScreen::BlankScreen()
+{
+ _sysdep = new BlankScreen_Sysdep;
+ _sysdep->blankwin = new QWidget(0, Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
+ _sysdep->blankwin->setWindowState(Qt::WindowFullScreen);
+ _sysdep->blankwin->show();
+}
+
+BlankScreen::~BlankScreen()
+{
+ delete _sysdep;
+}
+
+void BlankScreen::draw(bool force)
+{
+
+}
+
+bool BlankScreen::lock(const QString& message)
+{
+ return true;
+}
+
+bool BlankScreen::unlock()
+{
+ return true;
+}
diff --git a/src/client/util/platform/blankscreen_X11.cpp b/src/client/util/platform/blankscreen_X11.cpp new file mode 100755 index 0000000..69b6d8c --- /dev/null +++ b/src/client/util/platform/blankscreen_X11.cpp @@ -0,0 +1,75 @@ +#include "blankscreen.h" + +#include <QApplication> +#include <QDesktopWidget> + +#include <X11/Xlib.h> +#include <X11/cursorfont.h> + +#include <cassert> +#include <cstring> + +struct BlankScreen_Sysdep +{ + Display *dpy; +}; + +BlankScreen::BlankScreen() : QDialog(NULL) +{ + _sysdep = new BlankScreen_Sysdep; + _sysdep->dpy = XOpenDisplay(NULL); + if (_sysdep->dpy == NULL) + return; + assert(_sysdep->dpy); + + setWindowFlags(Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint); + setStyleSheet("background-color:#000"); + + _locked = false; +} + +BlankScreen::~BlankScreen() +{ + unlock(); + delete _sysdep; +} + +bool BlankScreen::lock(const QString& message) +{ + if (_locked) + return true; + if (_sysdep->dpy == NULL) + return false; + + _message = message; + + this->setGeometry(QApplication::desktop()->geometry()); + this->showFullScreen(); + this->activateWindow(); + this->raise(); + + // grabbing of keyboard and mouse + XGrabKeyboard(_sysdep->dpy, DefaultRootWindow(_sysdep->dpy) , false, GrabModeAsync, GrabModeAsync, CurrentTime); + XGrabPointer(_sysdep->dpy, DefaultRootWindow(_sysdep->dpy) , false, 0, GrabModeAsync, GrabModeAsync, None, 0, CurrentTime); + + _locked = true; + return true; +} + +bool BlankScreen::unlock() +{ + this->hide(); + if (!_locked) + return true; + if (_sysdep->dpy == NULL) + return false; + + // ungrabbing of keyboard and mouse + XUngrabPointer(_sysdep->dpy, CurrentTime); + XUngrabKeyboard(_sysdep->dpy, CurrentTime); + + XFlush(_sysdep->dpy); + + _locked = false; + return true; +} diff --git a/src/client/util/util.cpp b/src/client/util/util.cpp new file mode 100644 index 0000000..9dcbebb --- /dev/null +++ b/src/client/util/util.cpp @@ -0,0 +1,25 @@ +/* + * Util.cpp + * + * Created on: 18.01.2013 + * Author: sr + */ + +#include "util.h" +#include <QSettings> + +namespace Util +{ +//# +//# +QDir settingsDir() +{ + USER_SETTINGS(settings); + QFileInfo fi(settings.fileName()); + QDir path(fi.path()); + return path; +} +//# +//# +} + diff --git a/src/client/util/util.h b/src/client/util/util.h new file mode 100644 index 0000000..82248c0 --- /dev/null +++ b/src/client/util/util.h @@ -0,0 +1,23 @@ +#ifndef UTIL_H_ +#define UTIL_H_ + +// Helper for getting a settings object in various places, so if you ever change the organization, location, +// file format or anything, you won't have to edit in 100 places. +// Use like this: +// USER_SETTINGS(settings) +// settings.value("somekey") +#define USER_SETTINGS(name) QSettings name (QSettings::IniFormat, QSettings::UserScope, "openslx", "pvs2client") +#define SYSTEM_SETTINGS(name) QSettings name (QSettings::IniFormat, QSettings::SystemScope, "openslx", "pvs2client") + +#include <QDir> + +namespace Util +{ +//# +//# +QDir settingsDir(); +//# +//# +} + +#endif /* UTIL_H_ */ diff --git a/src/client/vnc/vncserver.cpp b/src/client/vnc/vncserver.cpp new file mode 100644 index 0000000..2b49b8e --- /dev/null +++ b/src/client/vnc/vncserver.cpp @@ -0,0 +1,191 @@ +/* + * vncserver.cpp + * + * Created on: 24.01.2013 + * Author: sr + */ + +#include <QProcess> +#include "vncserver.h" +#include "../util/util.h" + +/******************************************* + * STATIC + *******************************************/ + +VncServer* VncServer::me = NULL; + +VncServer* VncServer::instance() +{ + if (me == NULL) + me = new VncServer(); + return me; +} + +// +static QString makePassword(int len = 10) +{ + char pass[len]; + for (int i = 0; i < len; ++i) + pass[i] = 43 + qrand() % 80; + return QString::fromUtf8(pass, len); +} + +// Ugly hack to get an el-cheapo platform independent sleep +struct Sleeper : public QThread +{ +static void msleep(unsigned long msecs) { QThread::msleep(msecs); } +}; + +/******************************************* + * INSTANCE + *******************************************/ + +VncServer::VncServer() : + _process(NULL), _port(0), _timerId(0) +{ + // TODO Auto-generated constructor stub +} + +VncServer::~VncServer() +{ + // TODO Auto-generated destructor stub +} + +void VncServer::start() +{ + // Keep things clean + if (_process != NULL) + { + disconnect(_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onError(QProcess::ProcessError))); + disconnect(_process, SIGNAL(finished(int)), this, SLOT(onFinished(int))); + } + this->stop(); + // Generate passwords + _rwpass = makePassword(); + _ropass = makePassword(); + // Create new password file + QDir path = Util::settingsDir(); + QString pwfile(path.absoluteFilePath("vncpass")); + QFile pwhandle(pwfile); + if (pwhandle.exists()) + pwhandle.remove(); + if (!pwhandle.open(QIODevice::WriteOnly)) + { + qDebug() << "Could not open " << pwfile << " for writing"; + emit started(0, _ropass, _rwpass); + return; + } + pwhandle.setPermissions(QFile::ReadOwner | QFile::WriteOwner); + pwhandle.write(_rwpass.toUtf8().constData()); + pwhandle.write("\n"); + pwhandle.write(_ropass.toUtf8().constData()); + pwhandle.write("\n"); + pwhandle.close(); + // Create new process + _process = new QProcess(this); + connect(_process, SIGNAL(readyReadStandardOutput()), this, SLOT(onStdOut())); + connect(_process, SIGNAL(readyReadStandardError()), this, SLOT(onStdErr())); + connect(_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onError(QProcess::ProcessError))); + connect(_process, SIGNAL(finished(int)), this, SLOT(onFinished(int))); + _timerId = startTimer(4000); + QStringList args; + args << "-forever"; + args << "-display" << ":0"; + args << "-passwdfile" << (QString("rm:" + pwfile)); + args << "-shared"; + args << "-autoport" << QString::number(54112); + qDebug() << "Arguments are: " << args; + _process->start("x11vnc", + args, + QIODevice::ReadOnly); +} + +void VncServer::stop() +{ + if (_timerId != 0) + { + killTimer(_timerId); + _timerId = 0; + } + if (_process == NULL) + return; + qDebug("Stopping old VNC server."); + disconnect(_process, SIGNAL(readyReadStandardOutput()), this, SLOT(onStdOut())); + disconnect(_process, SIGNAL(readyReadStandardError()), this, SLOT(onStdErr())); + QProcess *process = _process; + _process = NULL; + _port = 0; + process->terminate(); + for (int i = 0; i < 10 && process->state() != QProcess::NotRunning; ++i) + Sleeper::msleep(10); + if (process->state() == QProcess::Running) + process->kill(); + for (int i = 0; i < 10 && process->state() != QProcess::NotRunning; ++i) + Sleeper::msleep(10); + process->deleteLater(); +} + +/** + * Overrides + */ + +void VncServer::timerEvent(QTimerEvent *event) +{ + // Error timeout (3s), tell server that vnc setup failed + this->stop(); + emit started(0, _ropass, _rwpass); +} + +/** + * Slots + */ + +void VncServer::onStdOut() +{ + if (_process == NULL) + { + qDebug("VncServer::onStdOut() called in bad state."); + return; + } + QByteArray data(_process->readAllStandardOutput()); + qDebug() << "x11vnc: " << data; + if (_port <= 0) + { + const int pos = data.indexOf("PORT=", 0); + if (pos != -1) + { + _port = atoi(data.constData() + pos + 5); + qDebug() << "Got VNC port " << _port << ", ro " << _ropass << ", rw " << _rwpass; + emit started(_port, _ropass, _rwpass); + // Kill error timer, but only if port seemed valid + if (_timerId != 0 && _port > 0) + { + killTimer(_timerId); + _timerId = 0; + } + } + } +} + +void VncServer::onStdErr() +{ + if (_process == NULL) + { + qDebug("VncServer::onStdErr() called in bad state."); + return; + } + QByteArray data(_process->readAllStandardError()); +} + +void VncServer::onError(QProcess::ProcessError error) +{ + this->stop(); + emit started(0, _ropass, _rwpass); +} + +void VncServer::onFinished(int exitCode) +{ + this->stop(); + emit started(0, _ropass, _rwpass); +} diff --git a/src/client/vnc/vncserver.h b/src/client/vnc/vncserver.h new file mode 100644 index 0000000..2aae49c --- /dev/null +++ b/src/client/vnc/vncserver.h @@ -0,0 +1,52 @@ +/* + * vncserver.h + * + * Created on: 24.01.2013 + * Author: sr + */ + +#ifndef VNCSERVER_H_ +#define VNCSERVER_H_ + +#include <QtCore> + +class VncServer; + +class VncServer : public QObject +{ + Q_OBJECT + +private: + QProcess *_process; + QString _ropass; + QString _rwpass; + int _port; + int _timerId; + + VncServer(); + virtual ~VncServer(); + + static VncServer *me; + +public: + static VncServer *instance(); + + void start(); + void stop(); + +protected: + void timerEvent(QTimerEvent *event); + +signals: + // Emited when started succesfully, or if startup failed. port will be <= 0 if it failed. + void started(int port, QString& ropass, QString& rwpass); + +private slots: + void onStdOut(); + void onStdErr(); + void onFinished(int exitCode); + void onError(QProcess::ProcessError error); + +}; + +#endif /* VNCSERVER_H_ */ diff --git a/src/client/vnc/vncthread.cpp b/src/client/vnc/vncthread.cpp new file mode 100644 index 0000000..492f970 --- /dev/null +++ b/src/client/vnc/vncthread.cpp @@ -0,0 +1,314 @@ +/* + # Copyright (c) 2009, 2010 - OpenSLX Project, Computer Center University of + # Freiburg + # + # This program is free software distributed under the GPL version 2. + # See http://openslx.org/COPYING + # + # If you have any feedback please consult http://openslx.org/feedback and + # send your suggestions, praise, or complaints to feedback@openslx.org + # + # General information about OpenSLX can be found at http://openslx.org/ + # ----------------------------------------------------------------------------- + # vncClientThread.cpp + # - Connection to remove vnc server + # - Emits Qt signal on framebuffer updates + # ----------------------------------------------------------------------------- + */ + +#include "vncthread.h" +#include <QPainter> + +#include <netinet/tcp.h> + +// greatest common divisor +static int gcd(int a, int b) +{ + if (b == 0) + return a; + return gcd(b, a % b); +} + +VncThread::VncThread(QString host, int port, QString passwd, int quality) : + QThread(), _frameBuffer(NULL), _painter(NULL), _hasNewLocalSize(false), _run(true) +{ + _srcStepX = _srcStepY = _dstStepX = _dstStepY = 0; + _host = host; + _port = port; + _passwd = passwd; + _quality = quality; + _client = NULL; + _connected = false; + moveToThread(this); +} + +// ALWAYS delete this class from another thread using delete, not deleteLater, or you will deadlock the thread +VncThread::~VncThread() +{ + qDebug("VNC worker destructor called, waiting for thread finishing..."); + _run = false; + while (this->isRunning()) + this->msleep(10); + qDebug("Thread ended."); + if (_frameBuffer) + delete[] _frameBuffer; + if (_client != NULL) + { + if (_client->sock != -1) + ::close(_client->sock); + _client->frameBuffer = NULL; + rfbClientCleanup(_client); + } + if (_painter != NULL) + delete _painter; +} + +// Calc matching pixel borders for both resolutions to prevent artifacts through bilinear scaling +void VncThread::calcScaling() +{ + if (_localSize.isEmpty() || _localSize.width() == 0 || _localSize.height() == 0) + return; + if (_clientSize.isEmpty() || _clientSize.width() == 0 || _clientSize.height() == 0) + return; + const int gcdX = gcd(_localSize.width(), _clientSize.width()); + const int gcdY = gcd(_localSize.height(), _clientSize.height()); + _srcStepX = _clientSize.width() / gcdX; + _srcStepY = _clientSize.height() / gcdY; + _dstStepX = _localSize.width() / gcdX; + _dstStepY = _localSize.height() / gcdY; + qDebug() << "Scaling updated to " << _clientSize << " -> " << _localSize; + emit imageUpdated(0, 0, _localSize.width(), _localSize.height()); +} + +//////////////////////////////////////////////////////////////////////////////// +// Public + +void VncThread::setTargetSize(const QSize size) +{ + if (_localSize == size) + return; + qDebug() << "Setting target size to " << size; + QMutexLocker lock(&_mutex); + _newLocalSize = size; + _hasNewLocalSize = true; +} + +void VncThread::run() +{ + qDebug("[%s] VNC client started.", metaObject()->className()); + qDebug("[%s] Host: '%s' Port: %i Passwd: '%s' Quality: %i", metaObject()->className(), qPrintable(_host), _port, + qPrintable(_passwd), _quality); + + // setup network + _client = rfbGetClient(8, 3, 4); + _client->MallocFrameBuffer = &frameBufferHandler; + _client->canHandleNewFBSize = true; + free(_client->serverHost); // in rfbGetClient, serverHost is assigned strdup(""), so free that first. + _client->serverHost = strdup(_host.toUtf8().constData()); + _client->desktopName = NULL; + _client->serverPort = _port; + _client->GetPassword = &passwdHandler; + _client->GotFrameBufferUpdate = &updateImage; + _client->frameBuffer = NULL; + + // save this instance in vnc-struct for callbacks + rfbClientSetClientData(_client, 0, this); + + // start client + if (!rfbInitClient(_client, NULL, NULL)) + { + _client = NULL; // !!! <- if you don't do this you will get a segfault later when you try to clean up _client, as rfbInitClient already did so + this->stop(); + return; + } + + qDebug("[%s] Connection successful!", metaObject()->className()); + int one = 1; + setsockopt(_client->sock, SOL_TCP, TCP_NODELAY, &one, sizeof(one)); + one = 1; + setsockopt(_client->sock, SOL_TCP, TCP_QUICKACK, &one, sizeof(one)); + + // Main VNC event loop + emit projectionStarted(); + while (_run) + { + _connected = true; + const int i = WaitForMessage(_client, 100 * 1000); // wait 100ms for message. returns -1 on error/disconnect, 0 if nothing happened, 1 if new data arrived + if (i < 0) + break; + if (i > 0 && !HandleRFBServerMessage(_client)) + break; + + if (_hasNewLocalSize) + { + QMutexLocker lock(&_mutex); + _hasNewLocalSize = false; + _localSize = _newLocalSize; + if (_painter != NULL) + delete _painter; + _imgScaled = QImage(_localSize, QImage::Format_RGB32); + _painter = new QPainter(&_imgScaled); + this->calcScaling(); + } + + /* + //work yourself through event queue and fire every event... + while (!_eventQueue.isEmpty()) { + SomeEvent* event = _eventQueue.dequeue(); + event->fire(_client); + delete event; + }*/ + } + + _connected = false; + qDebug("[%s] VNC client stopped.", metaObject()->className()); +} + +const QString VncThread::getDesktopName() const +{ + if (_client == NULL || _client->desktopName == NULL) + return QString(); + return QString(_client->desktopName); +} + +void VncThread::mouseEvent(int x, int y, int buttonMask) +{ + //QMutexLocker lock(&mutex); + if (!_run) + return; + + _eventQueue.enqueue(new PointerEvent(x, y, buttonMask)); +} + +void VncThread::keyEvent(int key, bool pressed) +{ + //QMutexLocker lock(&mutex); + if (!_run) + return; + + _eventQueue.enqueue(new KeyEvent(key, pressed)); +} + +void VncThread::processImageUpdate(const int x, const int y, const int w, const int h) +{ + if (_srcStepX > 1 || _srcStepY > 1) + { + // Scaling is required as vnc server and client are using different resolutions + // Calc section offsets first + const int startX = x / _srcStepX; + const int startY = y / _srcStepY; + const int endX = (x + w - 1) / _srcStepX + 1; + const int endY = (y + h - 1) / _srcStepY + 1; + // Now pixel offsets for source + const int srcX = startX * _srcStepX; + const int srcY = startY * _srcStepY; + const int srcW = endX * _srcStepX - srcX; + const int srcH = endY * _srcStepY - srcY; + // Pixel offsets for destination + const int dstX = startX * _dstStepX; + const int dstY = startY * _dstStepY; + const int dstW = endX * _dstStepX - dstX; + const int dstH = endY * _dstStepY - dstY; + // Rescale + if (_painter != NULL) + { + QImage scaled( + _img.copy(srcX, srcY, srcW, srcH).scaled(dstW, dstH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + _painter->drawImage(dstX, dstY, scaled, 0, 0, dstW, dstH); + emit imageUpdated(dstX, dstY, dstW, dstH); + } + } + else + { + // Same resolution, nothing to do + emit imageUpdated(x, y, w, h); + } +} + +// *** callback stuff *** + +// the vnc lib is requesting the connection password +char* VncThread::passwdHandler(rfbClient *client) +{ + VncThread* t = (VncThread*)rfbClientGetClientData(client, 0); + return strdup(t->_passwd.toUtf8()); +} + +// the vnc lib is telling us the size and bit depth of the remote screen +rfbBool VncThread::frameBufferHandler(rfbClient *client) +{ + VncThread *t = (VncThread*)rfbClientGetClientData(client, 0); + const int width = client->width, height = client->height, depth = client->format.bitsPerPixel; + const int size = width * height * (depth / 8); + qDebug("[%s] Remote desktop: %ix%ix%i", t->metaObject()->className(), width, height, depth); + + QMutexLocker lock(&(t->_mutex)); + + if (t->_frameBuffer) + delete[] t->_frameBuffer; + + t->_frameBuffer = new uint8_t[size]; + client->frameBuffer = t->_frameBuffer; + memset(client->frameBuffer, '\0', size); + client->format.bitsPerPixel = 32; + client->format.redShift = 16; + client->format.greenShift = 8; + client->format.blueShift = 0; + client->format.redMax = 0xff; + client->format.greenMax = 0xff; + client->format.blueMax = 0xff; + + const int quality = t->_quality; + switch (quality) + { + case VncThread::HIGH: + client->appData.useBGR233 = 0; + client->appData.encodingsString = "copyrect zlib hextile raw"; + client->appData.compressLevel = 4; + client->appData.qualityLevel = 9; + client->appData.scaleSetting = 0; + break; + case VncThread::MEDIUM: + client->appData.useBGR233 = 0; + client->appData.encodingsString = "copyrect tight zrle ultra zlib hextile corre rre raw"; + client->appData.compressLevel = 7; + client->appData.qualityLevel = 8; + client->appData.scaleSetting = 0; + break; + case VncThread::LOW: + default: + client->appData.useBGR233 = 1; + client->appData.encodingsString = "copyrect tight zrle ultra zlib hextile corre rre raw"; + client->appData.compressLevel = 9; + client->appData.qualityLevel = 1; + client->appData.scaleSetting = 0; + break; + } + SetFormatAndEncodings(client); + + t->_clientSize = QSize(width, height); + + t->_img = QImage(client->frameBuffer, client->width, client->height, QImage::Format_RGB32); + + t->calcScaling(); + + return true; +} + +void VncThread::updateImage(rfbClient* client, int x, int y, int w, int h) +{ + VncThread* t = (VncThread*)rfbClientGetClientData(client, 0); + t->processImageUpdate(x, y, w, h); +} + +// *** event stuff *** + +void PointerEvent::fire(rfbClient* cl) +{ + SendPointerEvent(cl, _x, _y, _buttonMask); +} + +void KeyEvent::fire(rfbClient* cl) +{ + SendKeyEvent(cl, _key, _pressed); +} diff --git a/src/client/vnc/vncthread.h b/src/client/vnc/vncthread.h new file mode 100644 index 0000000..b6679b3 --- /dev/null +++ b/src/client/vnc/vncthread.h @@ -0,0 +1,141 @@ +/* + # Copyright (c) 2009, 2010 - OpenSLX Project, Computer Center University of + # Freiburg + # + # This program is free software distributed under the GPL version 2. + # See http://openslx.org/COPYING + # + # If you have any feedback please consult http://openslx.org/feedback and + # send your suggestions, praise, or complaints to feedback@openslx.org + # + # General information about OpenSLX can be found at http://openslx.org/ + */ + +#ifndef VNCCLIENTTHREAD_H_ +#define VNCCLIENTTHREAD_H_ + +#include <QtCore> +#include <QImage> +#include <QThread> + +class QPainter; + +extern "C" +{ +#include <rfb/rfbclient.h> +} + +/** + * - START - + * Event classes. Not my code. Might be useful to implement remote assistance via VNC later. + * Code might come from the KDE VNC client. + */ +class SomeEvent +{ +public: + virtual ~SomeEvent(){} + virtual void fire(rfbClient*) = 0; +}; + +class KeyEvent : public SomeEvent +{ +public: + KeyEvent(int key, int pressed) : + _key(key), _pressed(pressed) + { + } + + void fire(rfbClient*); + +private: + int _key; + int _pressed; +}; + +class PointerEvent : public SomeEvent +{ +public: + PointerEvent(int x, int y, int buttonMask) : + _x(x), _y(y), _buttonMask(buttonMask) + { + } + + void fire(rfbClient*); + +private: + int _x; + int _y; + int _buttonMask; +}; +/** - END - **/ + +/** + * VncThread - communicate with VNC server, scale image if necessary. + * + * As this class is derived from QThread and overrides the run() method, + * it does not have an event-loop. Do NOT try to add any slots to it. + * It will NOT be able to receive signals. Emitting signals is fine though. + */ +class VncThread : public QThread +{ +Q_OBJECT + +private: + rfbClient *_client; + quint8 *_frameBuffer; + + QString _host; + int _port; + QString _passwd; + int _quality; + QQueue<SomeEvent*> _eventQueue; + + QPainter *_painter; + QImage _img; + QImage _imgScaled; + QSize _clientSize; + QSize _localSize; + QMutex _mutex; + + QSize _newLocalSize; + volatile bool _hasNewLocalSize; + + int _srcStepX, _srcStepY, _dstStepX, _dstStepY; + + bool _connected; + volatile bool _run; + + void calcScaling(); + + // Callbacks for rfb lib. make them class members so the callbacks can access private members of the class. + static void updateImage(rfbClient *client, int x, int y, int w, int h); + static char* passwdHandler(rfbClient *client); + static rfbBool frameBufferHandler(rfbClient *client); + +public: + VncThread(QString host, int port, QString passwd, int quality); + // Do NOT delete this class directly. use VncThread::destroy() instead! + ~VncThread(); + + const QImage& getImage() const { if (_srcStepX > 1 || _srcStepY > 1) return _imgScaled; return _img; } + const QSize& getSourceSize() const { return _clientSize; } + const QString getDesktopName() const; + void processImageUpdate(const int x, const int y, const int w, const int h); + void mouseEvent(int x, int y, int buttonMask); + void keyEvent(int key, bool pressed); + const bool isConnected() const { return _connected; } + void stop() { _run = false; } + void setTargetSize(const QSize size); + void run(); + + int const static HIGH = 0; + int const static MEDIUM = 1; + int const static LOW = 2; + +signals: + void imageUpdated(const int x, const int y, const int w, const int h); + void projectionStarted(); + +}; + +#endif /* VNCCLIENTTHREAD_H_ */ diff --git a/src/client/vnc/vncwindow.cpp b/src/client/vnc/vncwindow.cpp new file mode 100644 index 0000000..d4f6d40 --- /dev/null +++ b/src/client/vnc/vncwindow.cpp @@ -0,0 +1,277 @@ +/* + # Copyright (c) 2009, 2010 - OpenSLX Project, Computer Center University of + # Freiburg + # + # This program is free software distributed under the GPL version 2. + # See http://openslx.org/COPYING + # + # If you have any feedback please consult http://openslx.org/feedback and + # send your suggestions, praise, or complaints to feedback@openslx.org + # + # General information about OpenSLX can be found at http://openslx.org/ + # ----------------------------------------------------------------------------- + # clientVNCViewer.cpp + # - connetct to vnc server and show remote screen (window/full) + # ----------------------------------------------------------------------------- + */ + +#include "vncwindow.h" +#include "vncthread.h" + +VncWindow::VncWindow(QWidget *parent) : + QDialog(parent), _vncWorker(NULL), _viewOnly(true), _buttonMask(0), _clientId(0) +{ + // +} + +VncWindow::~VncWindow() +{ + close(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Public + +void VncWindow::open(const QString& host, int port, const QString& passwd, bool ro, bool fullscreen, const QString& caption, const int clientId) +{ + // start thread for vnc-updates + this->onThreadFinished(); + _clientId = clientId; + _vncWorker = new VncThread(host, port, passwd, 1); + connect(_vncWorker, SIGNAL(finished()), this, SLOT(onThreadFinished()), Qt::QueuedConnection); + connect(_vncWorker, SIGNAL(projectionStarted()), this, SLOT(onProjectionStarted()), Qt::QueuedConnection); + _vncWorker->start(QThread::LowPriority); + //_rfbclient = _thread->getRfbClient(); + //installEventFilter(this); + setMouseTracking(true); // get mouse events even when there is no mousebutton pressed + setFocusPolicy(Qt::WheelFocus); //needed?!? + + setWindowTitle(caption); + + setAttribute(Qt::WA_OpaquePaintEvent); + + if (fullscreen) + { + setWindowFlags(Qt::WindowStaysOnTopHint); + showFullScreen(); + activateWindow(); + raise(); + } + else + { + resize(800, 600); + showNormal(); + } + + this->show(); + _vncWorker->setTargetSize(this->size()); + + connect(_vncWorker, SIGNAL(imageUpdated(const int, const int, const int, const int)), this, + SLOT(onUpdateImage(const int, const int, const int, const int)), + Qt::QueuedConnection); +} + +void VncWindow::closeEvent(QCloseEvent *e) +{ + e->ignore(); + qDebug("Closing VNC viewer window."); + this->setVisible(false); + this->onThreadFinished(); + emit running(false, _clientId); +} + +void VncWindow::onUpdateImage(const int x, const int y, const int w, const int h) +{ + this->repaint(x, y, w, h); +} + +/** + * Thread finished, clean up and close window + */ +void VncWindow::onThreadFinished() +{ + if (_vncWorker) + { + disconnect(_vncWorker, SIGNAL(imageUpdated(const int, const int, const int, const int)), this, + SLOT(onUpdateImage(const int, const int, const int, const int))); + disconnect(_vncWorker, SIGNAL(finished()), this, SLOT(onThreadFinished())); + _vncWorker->stop(); + delete _vncWorker; + _vncWorker = NULL; + this->close(); + } +} + +/** + * VNC Thread successfully connected to remote end - projection will start + */ +void VncWindow::onProjectionStarted() +{ + emit running(true, _clientId); +} + +//////////////////////////////////////////////////////////////////////////////// +// Protected + +void VncWindow::paintEvent(QPaintEvent *event) +{ + const QRect &r = event->rect(); + this->draw(r.left(), r.top(), r.width(), r.height()); + event->accept(); +} + +void VncWindow::resizeEvent(QResizeEvent* event) +{ + _vncWorker->setTargetSize(event->size()); +} + +void VncWindow::draw(const int x, const int y, const int w, const int h) +{ + if (_vncWorker == NULL) + return; + QPainter painter(this); + painter.drawImage(x, y, _vncWorker->getImage(), x, y, w, h); + //painter.drawRect(x,y,w,h); // for debugging updated area +} + +//returns true if event was processed +/*bool VncWindow::event(QEvent *event) + { + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: + + keyEventHandler(static_cast<QKeyEvent*>(event)); + return true; + break; + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + mouseEventHandler(static_cast<QMouseEvent*>(event)); + return true; + break; + case QEvent::Wheel: + wheelEventHandler(static_cast<QWheelEvent*>(event)); + return true; + break; + default: + return false; + } + }*/ + +//handles mouseevents +void VncWindow::mouseEventHandler(QMouseEvent *e) +{ + if (e->type() != QEvent::MouseMove) + { + if ((e->type() == QEvent::MouseButtonPress) || (e->type() == QEvent::MouseButtonDblClick)) + { + if (e->button() & Qt::LeftButton) + _buttonMask |= 0x01; + if (e->button() & Qt::MidButton) + _buttonMask |= 0x02; + if (e->button() & Qt::RightButton) + _buttonMask |= 0x04; + } + else if (e->type() == QEvent::MouseButtonRelease) + { + if (e->button() & Qt::LeftButton) + _buttonMask &= 0xfe; + if (e->button() & Qt::MidButton) + _buttonMask &= 0xfd; + if (e->button() & Qt::RightButton) + _buttonMask &= 0xfb; + } + } + _vncWorker->mouseEvent(e->x(), e->y(), _buttonMask); +} + +//handles mousewheel +void VncWindow::wheelEventHandler(QWheelEvent *event) +{ + int eb = 0; + if (event->delta() < 0) + eb |= 0x10; + else + eb |= 0x8; + + const int x = event->x(); + const int y = event->y(); + + _vncWorker->mouseEvent(x, y, eb | _buttonMask); + _vncWorker->mouseEvent(x, y, _buttonMask); +} + +//Handles keypress +void VncWindow::keyEventHandler(QKeyEvent *e) +{ + rfbKeySym k = e->nativeVirtualKey(); + + // do not handle Key_Backtab separately because 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) + { + _modkeys[k] = true; + } + else if (_modkeys.contains(k)) + { + _modkeys.remove(k); + } + else + { + unpressModifiers(); + } + } + + if (k) + { + _vncWorker->keyEvent(k, pressed); + } +} + +void VncWindow::keyPressEvent(QKeyEvent* event) +{ + if (event->key() == Qt::Key_Escape) + this->close(); +} + +//removes modifier keys which have been pressed +void VncWindow::unpressModifiers() +{ + const QList<unsigned int> keys = _modkeys.keys(); + QList<unsigned int>::const_iterator it = keys.constBegin(); + while (it != keys.end()) + { + _vncWorker->keyEvent(*it, false); + it++; + } + _modkeys.clear(); +} + +//(QT Function) Filters events, if _viewOnly is set, true is returned and the event is ignored +//TODO use this function when implementing viewonly switch +bool VncWindow::eventFilter(QObject *obj, QEvent *event) +{ + if (_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 false; + //return RemoteView::eventFilter(obj, event); +} diff --git a/src/client/vnc/vncwindow.h b/src/client/vnc/vncwindow.h new file mode 100644 index 0000000..3a73a8e --- /dev/null +++ b/src/client/vnc/vncwindow.h @@ -0,0 +1,73 @@ +/* + # Copyright (c) 2009, 2010 - OpenSLX Project, Computer Center University of + # Freiburg + # + # This program is free software distributed under the GPL version 2. + # See http://openslx.org/COPYING + # + # If you have any feedback please consult http://openslx.org/feedback and + # send your suggestions, praise, or complaints to feedback@openslx.org + # + # General information about OpenSLX can be found at http://openslx.org/ + */ + +#ifndef CLIENTVNCVIEWER_H_ +#define CLIENTVNCVIEWER_H_ + +#include <QtGui> +#include <QMouseEvent> + +class VncThread; +class QPainter; + +// 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 + +class VncWindow : public QDialog +{ +Q_OBJECT + +public: + VncWindow(QWidget *parent = 0); + virtual ~VncWindow(); + +protected slots: + void onUpdateImage(const int x, const int y, const int w, const int h); + void onThreadFinished(); + void onProjectionStarted(); + + void open(const QString& host, int port, const QString& passwd, bool ro, bool fullscreen, const QString& caption, const int clientId); + +signals: + void running(const bool isRunning, const int clientId); + + +protected: + void draw(const int x, const int y, const int w, const int h); + void paintEvent(QPaintEvent *event); + void resizeEvent(QResizeEvent* event); + void closeEvent(QCloseEvent *e); + //bool event(QEvent *event); + //bool eventFilter(QObject *obj, QEvent *event); + +private: + VncThread *_vncWorker; + bool _viewOnly; + int _buttonMask; + QMap<unsigned int, bool> _modkeys; + int _clientId; + + bool eventFilter(QObject *obj, QEvent *event); + void keyPressEvent(QKeyEvent* event); + void keyEventHandler(QKeyEvent *e); + void unpressModifiers(); + void wheelEventHandler(QWheelEvent *event); + void mouseEventHandler(QMouseEvent *event); + +}; + +#endif /* CLIENTVNCVIEWER_H_ */ |