From 1a5709501f94014d41987b956338bb6424b9f90c Mon Sep 17 00:00:00 2001 From: sr Date: Mon, 4 Feb 2013 19:50:31 +0100 Subject: Initial commit --- src/client/connectwindow/connectwindow.cpp | 284 +++++++++ src/client/connectwindow/connectwindow.h | 95 ++++ src/client/main.cpp | 41 ++ src/client/net/serverconnection.cpp | 348 +++++++++++ src/client/net/serverconnection.h | 62 ++ src/client/toolbar/toolbar.cpp | 200 +++++++ src/client/toolbar/toolbar.h | 78 +++ src/client/util/platform/blankscreen.h | 25 + src/client/util/platform/blankscreen_Win32.cpp | 41 ++ src/client/util/platform/blankscreen_X11.cpp | 75 +++ src/client/util/util.cpp | 25 + src/client/util/util.h | 23 + src/client/vnc/vncserver.cpp | 191 +++++++ src/client/vnc/vncserver.h | 52 ++ src/client/vnc/vncthread.cpp | 314 ++++++++++ src/client/vnc/vncthread.h | 141 +++++ src/client/vnc/vncwindow.cpp | 277 +++++++++ src/client/vnc/vncwindow.h | 73 +++ src/server/clicklabel/clicklabel.cpp | 14 + src/server/clicklabel/clicklabel.h | 25 + src/server/connectionframe/connectionframe.cpp | 278 +++++++++ src/server/connectionframe/connectionframe.h | 77 +++ src/server/main.cpp | 41 ++ src/server/mainwindow/mainwindow.cpp | 633 +++++++++++++++++++++ src/server/mainwindow/mainwindow.h | 76 +++ src/server/net/certmanager.cpp | 92 +++ src/server/net/certmanager.h | 29 + src/server/net/client.cpp | 288 ++++++++++ src/server/net/client.h | 104 ++++ src/server/net/discoverylistener.cpp | 165 ++++++ src/server/net/discoverylistener.h | 40 ++ src/server/net/listenserver.cpp | 40 ++ src/server/net/listenserver.h | 30 + src/server/net/sslserver.cpp | 113 ++++ src/server/net/sslserver.h | 50 ++ src/server/sessionnamewindow/sessionnamewindow.cpp | 68 +++ src/server/sessionnamewindow/sessionnamewindow.h | 38 ++ src/server/util/global.cpp | 17 + src/server/util/global.h | 29 + src/server/util/util.cpp | 14 + src/server/util/util.h | 17 + src/shared/network.cpp | 50 ++ src/shared/network.h | 25 + src/shared/networkmessage.cpp | 296 ++++++++++ src/shared/networkmessage.h | 69 +++ src/shared/settings.h | 12 + src/shared/util.cpp | 22 + src/shared/util.h | 15 + 48 files changed, 5112 insertions(+) create mode 100644 src/client/connectwindow/connectwindow.cpp create mode 100644 src/client/connectwindow/connectwindow.h create mode 100644 src/client/main.cpp create mode 100644 src/client/net/serverconnection.cpp create mode 100644 src/client/net/serverconnection.h create mode 100644 src/client/toolbar/toolbar.cpp create mode 100644 src/client/toolbar/toolbar.h create mode 100755 src/client/util/platform/blankscreen.h create mode 100755 src/client/util/platform/blankscreen_Win32.cpp create mode 100755 src/client/util/platform/blankscreen_X11.cpp create mode 100644 src/client/util/util.cpp create mode 100644 src/client/util/util.h create mode 100644 src/client/vnc/vncserver.cpp create mode 100644 src/client/vnc/vncserver.h create mode 100644 src/client/vnc/vncthread.cpp create mode 100644 src/client/vnc/vncthread.h create mode 100644 src/client/vnc/vncwindow.cpp create mode 100644 src/client/vnc/vncwindow.h create mode 100644 src/server/clicklabel/clicklabel.cpp create mode 100644 src/server/clicklabel/clicklabel.h create mode 100644 src/server/connectionframe/connectionframe.cpp create mode 100644 src/server/connectionframe/connectionframe.h create mode 100644 src/server/main.cpp create mode 100644 src/server/mainwindow/mainwindow.cpp create mode 100644 src/server/mainwindow/mainwindow.h create mode 100644 src/server/net/certmanager.cpp create mode 100644 src/server/net/certmanager.h create mode 100644 src/server/net/client.cpp create mode 100644 src/server/net/client.h create mode 100644 src/server/net/discoverylistener.cpp create mode 100644 src/server/net/discoverylistener.h create mode 100644 src/server/net/listenserver.cpp create mode 100644 src/server/net/listenserver.h create mode 100644 src/server/net/sslserver.cpp create mode 100644 src/server/net/sslserver.h create mode 100644 src/server/sessionnamewindow/sessionnamewindow.cpp create mode 100644 src/server/sessionnamewindow/sessionnamewindow.h create mode 100644 src/server/util/global.cpp create mode 100644 src/server/util/global.h create mode 100644 src/server/util/util.cpp create mode 100644 src/server/util/util.h create mode 100644 src/shared/network.cpp create mode 100644 src/shared/network.h create mode 100644 src/shared/networkmessage.cpp create mode 100644 src/shared/networkmessage.h create mode 100644 src/shared/settings.h create mode 100644 src/shared/util.cpp create mode 100644 src/shared/util.h (limited to 'src') 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 + +#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 +#include +#include +#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 +#include +#include +#include +#include +#include +#include +//#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 &)), + this, + SLOT(sslErrors(const QList &)) + ); + 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 & errors) +{ + for (QList::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 +#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 & 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 +#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 +#include + +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 + +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 +#include + +#include +#include + +#include +#include + +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 + +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 + +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 +#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 + +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 + +#include + +// 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 +#include +#include + +class QPainter; + +extern "C" +{ +#include +} + +/** + * - 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 _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(event)); + return true; + break; + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + mouseEventHandler(static_cast(event)); + return true; + break; + case QEvent::Wheel: + wheelEventHandler(static_cast(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 keys = _modkeys.keys(); + QList::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 +#include + +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 _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_ */ diff --git a/src/server/clicklabel/clicklabel.cpp b/src/server/clicklabel/clicklabel.cpp new file mode 100644 index 0000000..2989542 --- /dev/null +++ b/src/server/clicklabel/clicklabel.cpp @@ -0,0 +1,14 @@ +#include "clicklabel.h" + +ClickLabel::ClickLabel(QWidget *parent) : QLabel(parent) +{ + QFont f(this->font()); + f.setPixelSize(20); + this->setFont(f); + this->setMaximumHeight(22); +} + +void ClickLabel::mouseReleaseEvent(QMouseEvent* e) +{ + emit clicked(); +} diff --git a/src/server/clicklabel/clicklabel.h b/src/server/clicklabel/clicklabel.h new file mode 100644 index 0000000..6a05152 --- /dev/null +++ b/src/server/clicklabel/clicklabel.h @@ -0,0 +1,25 @@ +#ifndef _CLICKLABEL_H_ +#define _CLICKLABEL_H_ + +#include +#include + + +class ClickLabel : public QLabel +{ + Q_OBJECT + +public: + ClickLabel(QWidget *parent); + +protected: + void mouseReleaseEvent(QMouseEvent* e); + + +signals: + void clicked(); + +}; + + +#endif diff --git a/src/server/connectionframe/connectionframe.cpp b/src/server/connectionframe/connectionframe.cpp new file mode 100644 index 0000000..1544c76 --- /dev/null +++ b/src/server/connectionframe/connectionframe.cpp @@ -0,0 +1,278 @@ +/* +# Copyright (c) 2009 - 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/ +# ----------------------------------------------------------------------------- +# src/gui/connectionFrame.cpp +# ----------------------------------------------------------------------------- +*/ + + +#include "connectionframe.h" +#include "../net/client.h" +#include +#include +#include + +static QIcon *term = NULL, *cam = NULL, *eye = NULL; + +static QString backgroundStyle("background-color: %1; margin: 1px; border: 1px solid black; border-radius: 6px"); + +ConnectionFrame::ConnectionFrame(QWidget *parent, int width, int height) : + QGroupBox(parent), _client(NULL), _timerId(0), _timerCounter(0), _selected(false), _isTutor(false) +{ + + //defines the ui-stuff + // load icons first + if (term == NULL) + { + term = new QIcon(":terminal"); + cam = new QIcon(":cam32"); + eye = new QIcon(":eye"); + } + + //this->setAttribute(Qt::WA_OpaquePaintEvent); + + _mainLayout = new QBoxLayout(QBoxLayout::TopToBottom, this); + _mainLayout->setSpacing(1); + _mainLayout->setMargin(3); + _mainLayout->setAlignment(Qt::AlignHCenter); + + _iconLayout = new QBoxLayout(QBoxLayout::RightToLeft, NULL); + _iconLayout->setSpacing(1); + _iconLayout->setMargin(3); + + _lblUserName = new QLabel("Test", this); + _lblUserName->setStyleSheet(QString::fromUtf8("background-color: white; color: black;")); + _lblUserName->setAlignment(Qt::AlignHCenter); + //_lblUsername->resize(_lblUsername->width(), _lblUsername->font().pixelSize()); + + _lblHostName = new QLabel("PC", this); + _lblHostName->setStyleSheet(QString::fromUtf8("background-color: white; color: black;")); + _lblHostName->setAlignment(Qt::AlignHCenter); + + _icoCam = addIcon(cam); + _icoEye = addIcon(eye); + + _iconLayout->addWidget(_icoCam); + _iconLayout->addWidget(_icoEye); + _iconLayout->addStretch(); + + //_layout->addWidget(_imgScreen); + _mainLayout->addLayout(_iconLayout); + _mainLayout->addStretch(); + _mainLayout->addWidget(_lblUserName); + _mainLayout->addWidget(_lblHostName); + this->setLayout(_mainLayout); + this->setSize(width, height); + this->updateColor(); +} + +ConnectionFrame::~ConnectionFrame() +{ + // +} + +QLabel* ConnectionFrame::addIcon(const QIcon* icon) +{ + QLabel *label = new QLabel(this); + label->setPixmap(icon->pixmap(24, 24, QIcon::Normal, QIcon::On)); + label->setAttribute(Qt::WA_TranslucentBackground); + label->hide(); + _icons.append(label); + return label; +} + +void ConnectionFrame::setSize(int width, int height) +{ + this->resize(width, height); +} + +void ConnectionFrame::assignClient(Client* client) +{ + assert(_client == NULL); + connect(client, SIGNAL(destroyed(QObject*)), this, SLOT(onClientDisconnected(QObject*))); + connect(client, SIGNAL(thumbUpdated(Client*, const QPixmap&)), this, SLOT(onThumbUpdated(Client*, const QPixmap&))); + connect(client, SIGNAL(vncServerStateChange(Client*)), this, SLOT(onVncServerStateChange(Client*))); + connect(client, SIGNAL(vncClientStateChange(Client*)), this, SLOT(onVncClientStateChange(Client*))); + _client = client; + _computerId = client->computerId(); + _lblHostName->setText(client->ip()); + _lblHostName->setToolTip(client->host()); + _lblUserName->setText(client->name()); + showDefaultThumb(); + if (_timerId == 0) + _timerId = startTimer(1000 + qrand() % 150); + this->updateColor(); +} + +void ConnectionFrame::showDefaultThumb() +{ + const int width = this->width() - 6; + const int height = this->height() - 8 - _lblHostName->height() - _lblUserName->height(); + _remoteScreen = term->pixmap(width, height, QIcon::Normal, QIcon::On); + this->repaint(); + //_imgScreen->setPixmap(_remoteScreen); +} + +void ConnectionFrame::mouseReleaseEvent(QMouseEvent* event) +{ + event->accept(); + if (event->button() == Qt::LeftButton) + { + QApplication::setOverrideCursor(QCursor(Qt::OpenHandCursor)); + if (this->pos() != _previousPosition) { + qDebug("Moved"); + emit frameMoved(this); + } + else + { + qDebug("Clicked"); + emit clicked(this); + } + } +} + +void ConnectionFrame::enterEvent(QEvent* event) +{ + QApplication::setOverrideCursor(QCursor(Qt::OpenHandCursor)); + event->accept(); +} + +void ConnectionFrame::leaveEvent(QEvent* event) +{ + QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); + event->accept(); +} + +void ConnectionFrame::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::RightButton) + { + // Menu... + } + else + { + _clickPoint = event->pos(); + _previousPosition = this->pos(); + QApplication::setOverrideCursor(QCursor(Qt::ClosedHandCursor)); + } + // On click, the window has to be on the top-level. + activateWindow(); + raise(); + update(); + event->accept(); +} + +void ConnectionFrame::mouseMoveEvent(QMouseEvent *event) +{ + QApplication::setOverrideCursor(QCursor(Qt::ClosedHandCursor)); + move(mapToParent(event->pos()-_clickPoint)); + event->accept(); +} + +void ConnectionFrame::mouseDoubleClickEvent(QMouseEvent* event) +{ + emit doubleClicked(this); + event->accept(); +} + +void ConnectionFrame::paintEvent(QPaintEvent *event) +{ + QGroupBox::paintEvent(event); + if (_remoteScreen.isNull()) + return; + QPainter painter(this); + painter.drawPixmap((this->width() - _remoteScreen.width()) / 2, 4, _remoteScreen); + event->accept(); +} + +void ConnectionFrame::timerEvent(QTimerEvent* event) +{ + if (_client == NULL) + return; + ++_timerCounter; + if (_client->isActiveVncServer() && _timerCounter % 5 != 0) + return; + const int width = this->width() - 8; + const int height = this->height() - 9 - _lblHostName->height() - _lblUserName->height(); + _client->requestThumb(width, height); +} + +void ConnectionFrame::setSelection(bool selected) +{ + if (_selected == selected) + return; + _selected = selected; + this->updateColor(); +} + +void ConnectionFrame::updateColor() +{ + if (_client == NULL) + { + // Unconnected Frame + if (_selected) + this->setStyleSheet(backgroundStyle.arg("rgb(140, 140, 170)")); + else + this->setStyleSheet(backgroundStyle.arg("rgb(140, 140, 140)")); + for (QList::iterator it(_icons.begin()); it != _icons.end(); ++it) + (**it).hide(); + return; + } + _icoCam->setVisible(_client->isActiveVncServer()); + _icoEye->setVisible(_client->isActiveVncClient()); + + // Normal client, no special stuff active + + if (_selected && _isTutor) + this->setStyleSheet(backgroundStyle.arg("rgb(255, 220, 180)")); + else if (_isTutor) + this->setStyleSheet(backgroundStyle.arg("rgb(255, 180, 180)")); + else if (_selected) + this->setStyleSheet(backgroundStyle.arg("rgb(180, 180, 210)")); + else + this->setStyleSheet(backgroundStyle.arg("rgb(180, 180, 180)")); +} + +/** + * Slots + */ + +void ConnectionFrame::onClientDisconnected(QObject* client) +{ + assert(client == _client); + if (_timerId != 0) + { + killTimer(_timerId); + _timerId = 0; + } + _client = NULL; + _lblUserName->setText(QString()); + showDefaultThumb(); + this->updateColor(); +} + +void ConnectionFrame::onThumbUpdated(Client* client, const QPixmap& thumb) +{ + assert(client == _client); + _remoteScreen = thumb; + //_imgScreen->setPixmap(_remoteScreen); + this->repaint(); +} + +void ConnectionFrame::onVncServerStateChange(Client* client) +{ + this->updateColor(); +} + +void ConnectionFrame::onVncClientStateChange(Client* client) +{ + this->updateColor(); +} diff --git a/src/server/connectionframe/connectionframe.h b/src/server/connectionframe/connectionframe.h new file mode 100644 index 0000000..2b78056 --- /dev/null +++ b/src/server/connectionframe/connectionframe.h @@ -0,0 +1,77 @@ +#ifndef _CONNECTIONFRAME_H_ +#define _CONNECTIONFRAME_H_ +#include + +class Client; + +class ConnectionFrame : public QGroupBox +{ + +Q_OBJECT + +private: + + QString _computerId; + + QBoxLayout *_mainLayout; + QBoxLayout *_iconLayout; + QLabel *_lblUserName; + QLabel *_lblHostName; + + QLabel *_icoCam, *_icoEye; + QList _icons; + + QPixmap _remoteScreen; + + QPoint _clickPoint; + QPoint _previousPosition; + + Client *_client; + + int _timerId, _timerCounter; + bool _selected; + bool _isTutor; + + void showDefaultThumb(); + void updateColor(); + QLabel* addIcon(const QIcon* icon); + +public: + ConnectionFrame(QWidget* parent, int width, int height); + virtual ~ConnectionFrame(); + + const QPixmap& getFramePixmap() const { return _remoteScreen; } + void setSize(int width, int height); + void assignClient(Client *client); + void setSelection(bool selected); + const inline bool selected() const { return _selected; } + + const QString& computerId() const { return _computerId; } + Client* client() const { return _client; } + + inline const bool isTutor() const { return _isTutor; } + inline void setTutor(bool b) { _isTutor = b; } + +protected: + void mouseDoubleClickEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* e); + void enterEvent(QEvent* event); + void leaveEvent(QEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void paintEvent(QPaintEvent *event); + void timerEvent(QTimerEvent* event); + +signals: + void frameMoved(ConnectionFrame* frame); + void doubleClicked(ConnectionFrame* frame); + void clicked(ConnectionFrame* frame); + +private slots: + void onClientDisconnected(QObject* client); + void onThumbUpdated(Client* client, const QPixmap& thumb); + void onVncServerStateChange(Client* client); + void onVncClientStateChange(Client* client); +}; + +#endif diff --git a/src/server/main.cpp b/src/server/main.cpp new file mode 100644 index 0000000..e7c709a --- /dev/null +++ b/src/server/main.cpp @@ -0,0 +1,41 @@ +#include "mainwindow/mainwindow.h" +#include "util/util.h" + +int main(int argc, char** argv) +{ + QApplication app(argc, argv); + app.setOrganizationName("openslx"); + app.setOrganizationDomain("openslx.org"); + app.setApplicationName("pvsmgr"); + + 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(":pvsmgr"); + app.installTranslator(&translator); + + MainWindow pvsmgr; + return app.exec(); +} diff --git a/src/server/mainwindow/mainwindow.cpp b/src/server/mainwindow/mainwindow.cpp new file mode 100644 index 0000000..8c57b91 --- /dev/null +++ b/src/server/mainwindow/mainwindow.cpp @@ -0,0 +1,633 @@ +/* + # Copyright (c) 2009 - 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/ + # ----------------------------------------------------------------------------- + # mainWindow.cpp + This is the Main class for the pvsManager. The GUI is constructed here. + # ----------------------------------------------------------------------------- + */ +// Self +#include "mainwindow.h" +// QT stuff +#include +#include +// Other custom UI elements +#include "../clicklabel/clicklabel.h" +#include "../sessionnamewindow/sessionnamewindow.h" +#include "../connectionframe/connectionframe.h" +// Network +#include "../net/listenserver.h" +#include "../net/client.h" +#include "../net/discoverylistener.h" +#include "../../shared/networkmessage.h" +// Others +#include "../../shared/settings.h" +#include "../util/util.h" +#include "../util/global.h" +// Auto-generated ui class +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget* parent) : + QMainWindow(parent), ui(new Ui::MainWindow), _tbIconSize(24), _tbArea(Qt::LeftToolBarArea), + _buttonBlockTime(0) + +{ + _sessionNameWindow = new SessionNameWindow(this); + ui->setupUi(this); + + Global::setSessionName("1234"); + + //conWin = new ConnectionWindow(ui->widget); + //ui->VconWinLayout->addWidget(conWin); + //conList = new ConnectionList(ui->ClWidget); + //ui->ClientGLayout->addWidget(conList); + + // Show only session name label, not textbox + _sessionNameLabel = new ClickLabel(this); + ui->mainLayout->addWidget(_sessionNameLabel); + + ui->frmRoom->setStyleSheet("background-color:#444"); + + // toolbar and actions in pvsmgr + ui->action_Exit->setStatusTip(tr("Exit")); + ui->action_Lock->setStatusTip(tr("Lock or Unlock all Clients")); + + // Close button in tool bar + connect(ui->action_Exit, SIGNAL(triggered()), this, SLOT(onButtonExit())); + connect(ui->action_BroadcastScreen, SIGNAL(triggered()), this, SLOT(onButtonBroadcast())); + connect(ui->action_Lock, SIGNAL(toggled(bool)), this, SLOT(onButtonLock(bool))); + // Clicking the session name label shows the edit field for it + connect(_sessionNameLabel, SIGNAL(clicked()), this, SLOT(onSessionNameClick())); + // Listen to updates to the session name through the session name window + connect(_sessionNameWindow, SIGNAL(updateSessionName()), this, + SLOT(onSessionNameUpdate())); + + setAttribute(Qt::WA_QuitOnClose); + setUnifiedTitleAndToolBarOnMac(true); + this->showMaximized(); // show the Mainwindow maximized + + _tilesX = 9; + _tilesY = 7; + _tileWidth = 10; + _tileHeight = 10; + + _timerId = 0; + _timerTimeout = 0; + + _listenServer = new ListenServer(CLIENT_PORT); + connect(_listenServer, SIGNAL(newClient(Client*)), this, SLOT(onClientConnected(Client*))); + _discoveryListener = new DiscoveryListener(); + + // Finally + this->onSessionNameUpdate(); + _timerId = startTimer(10); + _timerTimeout = 8; +} + +MainWindow::~MainWindow() +{ + _sessionNameLabel->deleteLater(); + delete ui; +} + +void MainWindow::placeFrameInFreeSlot(ConnectionFrame* frame) +{ + // Get occupied cell of each frame and store status in an array + const int elems = _tilesX * _tilesY; + int currentIndex = 0; + bool grid[elems]; + memset(grid, 0, sizeof(bool) * elems); // set everything to false + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + const QPoint &oldpos = (*it)->frameGeometry().topLeft(); + const int tx = oldpos.x() / _tileWidth; + const int ty = oldpos.y() / _tileHeight; + const int index = tx + ty * _tilesX; + if (frame == *it) + { + // Current frame is the frame to place, remember its old index + currentIndex = index; + continue; + } + if (index < 0 || index >= elems) + { + // Current frame is out of bounds, place in top left corner + qDebug("Move A"); + (*it)->move(0, 0); + grid[0] = true; + continue; + } + // Just mark cell as occupied in array + grid[index] = true; + } + // Safety + if (currentIndex < 0 || currentIndex >= elems) + currentIndex = 0; + // Now place in next free cell, start looking at current position of frame + for (int i = 0; i < elems; ++i) + { + const int index = (i + currentIndex) % elems; + if (grid[index]) + continue; + const int x = (index % _tilesX) * _tileWidth; + const int y = (index / _tilesX) * _tileHeight; + //qDebug() << "Index=" << index << " i=" << i << " cI=" << currentIndex << " elems=" << elems << " x=" << x << " y=" << y; + qDebug("Move B"); + frame->move(x, y); + break; + } +} + +ConnectionFrame* MainWindow::createFrame() +{ + // Allocate and resize + ConnectionFrame *cf = new ConnectionFrame(ui->frmRoom, _tileWidth, _tileHeight); + _clientFrames.append(cf); + cf->show(); + connect(cf, SIGNAL(frameMoved(ConnectionFrame *)), this, SLOT(onPlaceFrame(ConnectionFrame *))); + connect(cf, SIGNAL(clicked(ConnectionFrame *)), this, SLOT(onFrameClicked(ConnectionFrame *))); + return cf; +} + +bool MainWindow::loadPosition(QSettings& settings, const QString& id, int& x, int& y) +{ + settings.beginGroup("client_position"); + const QVariant retval(settings.value(id)); + settings.endGroup(); + if (retval.type() != QVariant::Point) + return false; + const QPoint point(retval.toPoint()); + x = point.x(); + y = point.y(); + return true; +} + +void MainWindow::savePosition(ConnectionFrame *cf) +{ + Client *client = cf->client(); + if (client == NULL) + return; + QPoint pos(cf->pos().x() / _tileWidth, cf->pos().y() / _tileHeight); + USER_SETTINGS(settings); + settings.beginGroup("client_position"); + settings.setValue(client->computerId(), pos); + settings.endGroup(); +} + +/* + * Overridden methods + */ + +void MainWindow::closeEvent(QCloseEvent* e) +{ + int ret = QMessageBox::question(this, "Test", "Beenden?", 0, 1, 2); + if (ret == 1) + QApplication::exit(0); + else + e->ignore(); +} + +void MainWindow::changeEvent(QEvent* e) +{ + QMainWindow::changeEvent(e); +} + +void MainWindow::resizeEvent(QResizeEvent* e) +{ + QMainWindow::resizeEvent(e); + if (_timerId == 0) + { + _timerId = startTimer(5); + } + _timerTimeout = 8; +} + +void MainWindow::mouseReleaseEvent(QMouseEvent* e) +{ + // Try to figure out if the user clicked inside the "room" frame + // No idea if there is a more elegant way to do this without + // sub-classing that frame and overriding its mouseReleaseEvent + const QPoint pos(ui->frmRoom->mapFrom(this, e->pos())); + if (pos.x() < 0 || pos.y() < 0) + return; + const QSize frame(ui->frmRoom->size()); + if (frame.width() > pos.x() && frame.height() > pos.y()) + { + for (QList::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it) + { + (**it).setSelection(false); + } + } +} + +void MainWindow::timerEvent(QTimerEvent* event) +{ + if (event->timerId() == _timerId) + { + _timerTimeout -= 1; + if (_timerTimeout == 1) + { + // Move toolbar if necessary + const int iconSize = this->size().width() / 30 + 14; + const int barSpace = ui->toolBar->actions().size() * (iconSize + 8); + const Qt::ToolBarArea area = + (barSpace < this->size().height() ? Qt::LeftToolBarArea : Qt::TopToolBarArea); + if (_tbIconSize != iconSize) + { + _tbIconSize = iconSize; + ui->toolBar->setIconSize(QSize(iconSize, iconSize)); + } + if (_tbArea != area) + { + _tbArea = area; + this->addToolBar(area, ui->toolBar); + } + return; + } + // Kill timer when done + if (_timerTimeout <= 0) + { + killTimer(_timerId); + _timerId = 0; + // Resize all connection windows + if (ui->frmRoom->size().width() < 100 || ui->frmRoom->size().height() < 100 || _tilesX <= 0 || _tilesY <= 0) + return; + const int nw = ui->frmRoom->size().width() / _tilesX; + const int nh = ui->frmRoom->size().height() / _tilesY; + for (QList::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it) + { + const QPoint &oldpos = (*it)->frameGeometry().topLeft(); + qDebug("Move C"); + (*it)->move((oldpos.x() / _tileWidth) * nw, (oldpos.y() / _tileHeight) * nh); + (*it)->setSize(nw, nh); + } + _tileWidth = nw; + _tileHeight = nh; + } + } + else + { + // Unknown timer ID, kill + killTimer(event->timerId()); + } +} + +/* + * Slots + */ + +void MainWindow::onPlaceFrame(ConnectionFrame* frame) +{ + if (_tilesX <= 0 || _tilesY <= 0) + return; + const QPoint &p = frame->frameGeometry().center(); + const QSize &s = ui->frmRoom->geometry().size(); + int x = (p.x() / _tileWidth) * _tileWidth; + int y = (p.y() / _tileHeight) * _tileHeight; + if (x < 0) + x = 0; + else if (x > s.width() - _tileWidth) + x = (_tilesX - 1) * _tileWidth; + if (y < 0) + y = 0; + else if (y > s.height() - _tileHeight) + y = (_tilesY - 1) * _tileHeight; + qDebug("Move D"); + frame->move(x, y); + savePosition(frame); + const QPoint &newpos = frame->frameGeometry().topLeft(); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + if (*it == frame) + continue; + if ((*it)->frameGeometry().topLeft() == newpos) + { + placeFrameInFreeSlot(*it); + } + } +} + +void MainWindow::onFrameClicked(ConnectionFrame* frame) +{ + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + if (*it == frame) + frame->setSelection(true); + else + (**it).setSelection(false); + } +} + +void MainWindow::onSessionNameClick() +{ + _sessionNameWindow->show(Global::sessionName()); +} + +void MainWindow::onSessionNameUpdate() +{ + _sessionNameLabel->setText(tr("Session Name: %1 [click to edit]").arg(Global::sessionName())); +} + +void MainWindow::onButtonSettings() +{ + // Move to any free tile + placeFrameInFreeSlot(createFrame()); +} + +void MainWindow::onButtonChat() +{ + /* + for (QList::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it) { + (*it)->setSize(100 + qrand() % 200); + } + */ +} + +void MainWindow::onButtonBroadcast() +{ + const qint64 now = QDateTime::currentMSecsSinceEpoch(); + if (now < _buttonBlockTime) + return; + _buttonBlockTime = now + 3000; + Client *source = NULL; // the desired source (selected frame) + Client *oldSource = NULL; // if there is a client that is already broadcaster, save it here + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if ((**it).selected()) + source = c; + if (c != NULL && c->isProjectionSource()) + oldSource = c; + } + if (source == NULL && oldSource == NULL) + { + QMessageBox::information(this, tr("No Projection Source selected"), tr("You did not select any active client as the connection source.")); + return; + } + if (oldSource != NULL) + { + // Is already broadcast source, disable + oldSource->setProjectionSource(false); + oldSource->stopVncServer(); + if (source == NULL || source->id() == oldSource->id()) + return; + } + source->setProjectionSource(true); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL || c->id() == source->id()) + continue; + //c->setDesiredProjectionSource(source->id()); + c->setProjectionSource(false); + } + source->startVncServer(); +} + +void MainWindow::onButtonLock(bool checked) +{ + NetworkMessage msg; + msg.setField(_ID, _LOCK); + if (checked) + msg.setField("ENABLE", QByteArray("1")); + else + msg.setField("ENABLE", QByteArray("0")); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL || (**it).isTutor()) + continue; // Don't lock the tutor + c->sendMessage(msg); + } +} + +void MainWindow::onButtonExit() +{ + this->close(); +} + +void MainWindow::onClientConnected(Client* client) +{ + qDebug("ListenServer told MainWindow about new connection"); + connect(client, SIGNAL(authenticating(Client*, ClientLogin*)), this, SLOT(onClientAuthenticating(Client*, ClientLogin*))); + connect(client, SIGNAL(authenticated(Client*)), this, SLOT(onClientAuthenticated(Client*))); + connect(client, SIGNAL(vncClientStateChange(Client*)), this, SLOT(onVncClientStateChange(Client*))); +} + +void MainWindow::onClientAuthenticating(Client* client, ClientLogin* request) +{ + disconnect(client, SIGNAL(authenticating(Client*, ClientLogin*)), this, SLOT(onClientAuthenticating(Client*, ClientLogin*))); + if (!request->accept) // Another receiver of that signal already rejected the client, so nothing to do + return; + bool inuse; + QString check = request->name; + int addnum = 1; + do + { + inuse = false; + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL) + continue; + if (!c->isAuthed()) + continue; + if (c->ip() == request->ip) + { + request->accept = false; + return; + } + if (c->name() == check) + { + inuse = true; + check = request->name + " (" + QString::number(++addnum) + ")"; + break; + } + } + } while (inuse && addnum < 100); + if (inuse) + request->accept = false; + else + request->name = check; +} + +void MainWindow::onClientAuthenticated(Client* client) +{ + disconnect(client, SIGNAL(authenticated(Client*)), this, SLOT(onClientAuthenticated(Client*))); + connect(client, SIGNAL(vncServerStateChange(Client*)), this, SLOT(onVncServerStateChange(Client*))); + bool hasActiveTutor = false; + ConnectionFrame *deadTutor = NULL; + bool anyClient = false; + ConnectionFrame *existing = NULL; + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + if ((*it)->computerId() == client->computerId()) + existing = *it; + if ((*it)->client() != NULL) + anyClient = true; + if ((*it)->isTutor()) + { + if ((*it)->client() == NULL) + deadTutor = (*it)->client(); + else + hasActiveTutor = true; + } + } + if (!anyClient && deadTutor != NULL) + deadTutor->setTutor(false); + if (existing != NULL) + { + if (!anyClient && !hasActiveTutor) + existing->setTutor(true); + existing->assignClient(client); + return; + } + // New one, create + ConnectionFrame *cf = createFrame(); + // Try to load last known position + int x, y; + bool ok; + USER_SETTINGS(usr); + ok = loadPosition(usr, client->computerId(), x, y); + if (!ok) + { + SYSTEM_SETTINGS(sys); + ok = loadPosition(sys, client->computerId(), x, y); + } + if (x >= _tilesX || y >= _tilesY) + ok = false; + if (ok) + { + if (x < 0) + x = 0; + if (y < 0) + y = 0; + qDebug("Move E"); + cf->move(x * _tileWidth, y * _tileHeight); + onPlaceFrame(cf); + } + else + { + // Move to any free tile + placeFrameInFreeSlot(cf); + } + // Assign client instance + cf->assignClient(client); + // Make first active client tutor + if (!anyClient && !hasActiveTutor) + cf->setTutor(true); + // ################ + NetworkMessage msg; + // If clients are currently locked, tell this new client + if (ui->action_Lock->isChecked()) + { + msg.reset(); + msg.setField(_ID, _LOCK); + msg.setField("ENABLE", QByteArray("1")); + client->sendMessage(msg); + } + // Same for VNC projections + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c != NULL && c != client && c->isActiveVncServer() && c->isProjectionSource()) + { + msg.reset(); + msg.setField(_ID, _VNCCLIENT); + msg.setField("HOST", c->ip()); + msg.setField("PORT", QString::number(c->vncPort())); + msg.setField("ROPASS", c->vncRoPass()); + msg.setField("CAPTION", c->name() + " @ " + c->host()); + client->sendMessage(msg); + break; + } + } +} + +void MainWindow::onVncServerStateChange(Client* client) +{ + if (client->vncPort() > 0) + { + // VNC Server started on some client - start projection on all clients interested in that client's screen + NetworkMessage msg; + msg.setField(_ID, _VNCCLIENT); + msg.setField("HOST", client->ip()); + msg.setField("PORT", QString::number(client->vncPort())); + msg.setField("ROPASS", client->vncRoPass()); + msg.setField("CAPTION", client->name() + " @ " + client->host()); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL) + continue; // Offline + if (c->id() == client->id() || + (c->desiredProjectionSource() != client->id() && !client->isProjectionSource())) + continue; // Not interested + c->sendMessage(msg); + } + } + else + { + // VNC server stopped on some client or failed to start - reset local pending projection information + // If at least one other client seemed to be waiting for this client's screen, pop up a message + // on the console + bool wasInterested = client->isProjectionSource(); + client->setProjectionSource(true); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL) + continue; + if (c->desiredProjectionSource() == client->id()) + { + wasInterested = true; + c->setDesiredProjectionSource(0); + } + } + if (wasInterested) + { + QMessageBox::information(this, + tr("Projection Error"), + tr("Could not send screen contents of %1 to other clients: VNC Startup failed.").arg(client->name()) + ); + } + } +} + +void MainWindow::onVncClientStateChange(Client* client) +{ + // If the client started projection, ignore event. Also if + // the source is not known (anymore), we cannot do anything meaningful here + if (client->isActiveVncClient() || client->currentProjectionSource() == 0) + return; + // Client is not a vnc client anymore. Check if the VNC server it was (supposedly) connected + // to is still running, and kill it if there are no more clients connected to it. + // (client->currentProjectionSource() will still contain the id of the server it was connected to during this event) + // 1. check if vnc server is still running + // 2. check if there are any other clients "using" that server + bool inUse = false; + Client *server = NULL; + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL || c == client) + continue; + if (c->currentProjectionSource() == client->currentProjectionSource()) + { + inUse = true; + break; + } + if (c->id() == client->currentProjectionSource()) + server = c; + } + // 3. kill server if applicable + if (!inUse && server != NULL) + server->stopVncServer(); +} diff --git a/src/server/mainwindow/mainwindow.h b/src/server/mainwindow/mainwindow.h new file mode 100644 index 0000000..4f9d142 --- /dev/null +++ b/src/server/mainwindow/mainwindow.h @@ -0,0 +1,76 @@ +#ifndef _MAINWINDOW_H_ +#define _MAINWINDOW_H_ + +#include +#include + +class SessionNameWindow; +class ConnectionFrame; +class ListenServer; +class DiscoveryListener; +class Client; +class ClientLogin; + +namespace Ui +{ + class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +private: + Ui::MainWindow *ui; + SessionNameWindow *_sessionNameWindow; + QLabel *_sessionNameLabel; + QList _clientFrames; + int _tileWidth, _tileHeight; + int _tilesX, _tilesY; + int _tbIconSize; + Qt::ToolBarArea _tbArea; + int _timerId, _timerTimeout; + ListenServer *_listenServer; + DiscoveryListener *_discoveryListener; + qint64 _buttonBlockTime; + + void placeFrameInFreeSlot(ConnectionFrame* frame); + ConnectionFrame* createFrame(); + bool loadPosition(QSettings& settings, const QString& id, int& x, int& y); + void savePosition(ConnectionFrame *cf); + +public: + MainWindow(QWidget *parent = 0); + ~MainWindow(); + +protected: + void closeEvent(QCloseEvent *e); + void changeEvent(QEvent *e); + void resizeEvent(QResizeEvent *e); + void mouseReleaseEvent(QMouseEvent* e); + void timerEvent(QTimerEvent* event); + + +protected slots: + // This + void onSessionNameClick(); + void onSessionNameUpdate(); + void onButtonSettings(); + void onButtonChat(); + void onButtonBroadcast(); + void onButtonLock(bool checked); + void onButtonExit(); + // connection frame + void onPlaceFrame(ConnectionFrame* frame); + void onFrameClicked(ConnectionFrame* frame); + // Net + void onClientConnected(Client* client); + void onClientAuthenticating(Client* client, ClientLogin* request); + void onClientAuthenticated(Client* client); + void onVncServerStateChange(Client* client); + void onVncClientStateChange(Client* client); + +}; + + +#endif diff --git a/src/server/net/certmanager.cpp b/src/server/net/certmanager.cpp new file mode 100644 index 0000000..e661a70 --- /dev/null +++ b/src/server/net/certmanager.cpp @@ -0,0 +1,92 @@ +/* + # Copyright (c) 2009 - 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/ + # ----------------------------------------------------------------------------- + # src/util/CertManager.cpp + # - Manage SSL certificates + # - provide access by name + # ----------------------------------------------------------------------------- + */ + +#include "certmanager.h" +#include "../util/util.h" +#include +#include +#include +#include + +namespace CertManager +{ +static QMap _certs; +static QMap _keys; + +static void generateFiles(QString& key, QString& cert); +static bool loadFiles(QString& keyFile, QString& certFile, QSslKey &key, QSslCertificate &cert); + +bool getPrivateKeyAndCert(const QString &name, QSslKey &key, QSslCertificate &cert) +{ + if (_keys.contains(name)) + { + key = _keys[name]; + cert = _certs[name]; + return true; + } + USER_SETTINGS(settings); + QString certFile = settings.fileName().append(".").append(name); + QString keyFile = certFile; + keyFile.append(".rsa"); + certFile.append(".crt"); + // + if (!loadFiles(keyFile, certFile, key, cert)) + { + generateFiles(keyFile, certFile); + if (!loadFiles(keyFile, certFile, key, cert)) + return false; + } + _certs.insert(name, cert); + _keys.insert(name, key); + return true; +} + +static bool loadFiles(QString& keyFile, QString& certFile, QSslKey &key, QSslCertificate &cert) +{ + QFileInfo keyInfo(keyFile); + QFileInfo certInfo(certFile); + if (keyInfo.exists() && certInfo.exists()) + { // Both files exist, see if they're valid and return + QFile kf(keyFile); + kf.open(QFile::ReadOnly); + key = QSslKey(&kf, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); + QList certlist = QSslCertificate::fromPath(certFile); + if (!key.isNull() && !certlist.empty()) + { + cert = certlist.first(); + if (!cert.isNull()) + { + return true; + } + } + } + return false; +} + +static void generateFiles(QString& key, QString& cert) +{ + char tmp[1000]; + unlink(key.toLocal8Bit().data()); + unlink(cert.toLocal8Bit().data()); + snprintf(tmp, 1000, + "openssl req -x509 -nodes -days 3650 -newkey rsa:1024 -subj '/C=DE/ST=BaWue/L=Freiburg/CN=openslx.org' -keyout \"%s\" -out \"%s\"", + key.toLocal8Bit().data(), cert.toLocal8Bit().data()); + system(tmp); + snprintf(tmp, 1000, "chmod 0600 \"%s\" \"%s\"", key.toLocal8Bit().data(), cert.toLocal8Bit().data()); + system(tmp); +} +} diff --git a/src/server/net/certmanager.h b/src/server/net/certmanager.h new file mode 100644 index 0000000..c69bc23 --- /dev/null +++ b/src/server/net/certmanager.h @@ -0,0 +1,29 @@ +/* +# Copyright (c) 2009 - 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/ +# ----------------------------------------------------------------------------- +# src/util/CertManager.cpp +# - Manage SSL certificates +# - provide access by name +# ----------------------------------------------------------------------------- +*/ + +#ifndef CERTMANAGER_H_ +#define CERTMANAGER_H_ + +#include +#include + +namespace CertManager +{ + bool getPrivateKeyAndCert(const QString &name, QSslKey &key, QSslCertificate &cert); +} + +#endif /* CERTMANAGER_H_ */ diff --git a/src/server/net/client.cpp b/src/server/net/client.cpp new file mode 100644 index 0000000..ca91e76 --- /dev/null +++ b/src/server/net/client.cpp @@ -0,0 +1,288 @@ +/* + * client.cpp + * + * Created on: 18.01.2013 + * Author: sr + */ + +#include "client.h" +#include "../util/global.h" +#include "../../shared/settings.h" +#include "../../shared/util.h" +#include +#include +#include +#include + +#define CHALLENGE_LEN 20 + +ClientId Client::_clientIdCounter = 0; + +Client::Client(QSslSocket* socket) : + _socket(socket), _authed(0), _desiredProjectionSource(0), _isProjectionSource(false), + _currentProjectionSource(0), _vncPort(0), _activeVncClient(false) +{ + assert(socket != NULL); + _id = ++_clientIdCounter; + _pingTimeout = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS; + _ip = _socket->peerAddress().toString(); + qDebug("*** Client %s created.", qPrintable(_ip)); + // Connect important signals + connect(_socket, SIGNAL(disconnected()), this, SLOT(onClosed())); + connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); + connect(_socket, SIGNAL(sslErrors(const QList &)), this, SLOT(onSslErrors(const QList &))); + connect(_socket, SIGNAL(readyRead()), this, SLOT(onDataArrival())); + // Send challenge + _challenge.resize(CHALLENGE_LEN); + for (int i = 0; i < CHALLENGE_LEN; ++i) + _challenge[i] = qrand() & 0xff; + _toClient.reset(); + _toClient.setField(_ID, _CHALLENGE); + _toClient.setField(_CHALLENGE, _challenge); + _toClient.writeMessage(_socket); + // give client 3 seconds to complete handshake + _timerIdAuthTimeout = startTimer(3000); +} + +Client::~Client() +{ + if (_socket != NULL) + { + qDebug("**** DELETE IN DESTRUCTOR"); + _socket->deleteLater(); + } + qDebug("*** Client %s destroyed.", qPrintable(_ip)); +} + +void Client::timerEvent(QTimerEvent* event) +{ + if (event->timerId() == _timerIdAuthTimeout) + { + // Client did not send login request within 3 seconds + killTimer(_timerIdAuthTimeout); + _timerIdAuthTimeout = 0; + this->disconnect(); + } +} + +void Client::sendMessage(NetworkMessage& message) +{ + if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState) + return; + message.writeMessage(_socket); + if (!message.writeComplete()) + { + qCritical() << "SendMessage to client " << _name << "@" << _ip << " failed!"; + } +} + +void Client::requestThumb(const int width, const int height) +{ + if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState) + { + qDebug("requestThumb called in bad state"); + return; + } + _toClient.reset(); + _toClient.setField(_ID, _THUMB); + _toClient.setField(_X, QString::number(width)); + _toClient.setField(_Y, QString::number(height)); + _toClient.writeMessage(_socket); +} + +void Client::onDataArrival() +{ + _pingTimeout = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS; + // + if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState) + { + qDebug("dataArrival called in bad state"); + return; + } + + //qDebug() << _socket->bytesAvailable() << " bytes to read"; + bool ret; + while (_socket->bytesAvailable()) + { + ret = _fromClient.readMessage(_socket); // let the message read data from socket + if (!ret) // error parsing msg, disconnect client! + { + this->disconnect(); + return; + } + if (_fromClient.readComplete()) // message is complete + { + this->handleMsg(); + if (_socket == NULL) + return; + _fromClient.reset(); + } + } +} + +void Client::onSslErrors(const QList & errors) +{ + this->disconnect(); +} + +void Client::onClosed() +{ + this->disconnect(); +} + +void Client::onError(QAbstractSocket::SocketError errcode) +{ + this->disconnect(); +} + +void Client::handleMsg() +{ + const QString &id = _fromClient.getFieldString(_ID); + if (id.isEmpty()) + { + qDebug("Received message with empty ID field. ignored."); + return; + } + qDebug() << "Received message " << id; + + if (_authed == 2) + { + // Following messages are only valid of the client is already authenticated + if (id == _THUMB) + { + QPixmap pixmap; + if (!pixmap.loadFromData(_fromClient.getFieldBytes("IMG"))) + { + qDebug("Could not decode thumbnail image from client."); + return; + } + emit thumbUpdated(this, pixmap); + } + else if (id == _VNCSERVER) + { + // Client tells about startup of vnc server + const int port = _fromClient.getFieldString("PORT").toInt(); + if (port <= 0) + { + if (_vncPort <= 0) + { + qDebug() << "Starting VNC server on client" << _name << " (" << _ip << ") failed."; + // TODO: Show message on manager + } + } + else + { + _vncRoPass = _fromClient.getFieldString("ROPASS"); + _vncRwPass = _fromClient.getFieldString("RWPASS"); + } + _vncPort = port; + emit vncServerStateChange(this); + } + else if (id == _VNCCLIENT) + { + // Client tells us that it started or stopped displaying a remote screen via VNC + _activeVncClient = (_fromClient.getFieldString("ENABLED").toInt() != 0); + const ClientId other = (ClientId)_fromClient.getFieldString("CLIENTID").toInt(); + + if (!_activeVncClient && other == 0) + _desiredProjectionSource = 0; + + emit vncClientStateChange(this); + + if (!_activeVncClient) + _currentProjectionSource = 0; + else + _currentProjectionSource = other; + } + return; + } + + if (_authed == 1) // Not authed yet, only care about login requests + { + if (id == _LOGIN) + { + killTimer(_timerIdAuthTimeout); + _timerIdAuthTimeout = 0; + ClientLogin request; + request.accept = true; + request.host = _fromClient.getFieldString("HOST"); + request.name = _fromClient.getFieldString("NAME"); + request.ip = _socket->peerAddress().toString(); + qDebug() << "Login request by " << request.name; + // emit event, see if request is accepted + emit authenticating(this, &request); + if (!request.accept) + { + qDebug("Request denied."); + this->disconnect(); // Nope + return; + } + // Accepted + _authed = 2; + qDebug("valid, step <- 2"); + _name = request.name; + _host = request.host; + _toClient.reset(); + _toClient.setField(_ID, _LOGIN); + _toClient.writeMessage(_socket); + emit authenticated(this); + } + return; + } + + if (_authed == 0) + { + // Waiting for challenge reply by client + if (id == _CHALLENGE) + { + QByteArray hash(_fromClient.getFieldBytes(_HASH)); + QByteArray challenge(_fromClient.getFieldBytes(_CHALLENGE)); + if (genSha1(&Global::sessionNameArray(), &_challenge) != hash) + { // Challenge reply is invalid, drop client + _toClient.buildErrorMessage("Challenge reply invalid."); + _toClient.writeMessage(_socket); + this->deleteLater(); + return; + } + // Now answer to challenge by client + _toClient.reset(); + _toClient.setField(_ID, _CHALLENGE); + _toClient.setField(_HASH, genSha1(&Global::sessionNameArray(), &challenge)); + _toClient.writeMessage(_socket); + _authed = 1; + qDebug("client's challenge reply was valid, step <- 1"); + } + return; + } + +} + +void Client::startVncServer() +{ + _vncPort = 0; + _toClient.reset(); + _toClient.setField(_ID, _VNCSERVER); + _toClient.setField("ENABLE", QByteArray("1")); + sendMessage(_toClient); +} + +void Client::stopVncServer() +{ + _toClient.reset(); + _toClient.setField(_ID, _VNCSERVER); + _toClient.setField("ENABLE", QByteArray("0")); + sendMessage(_toClient); +} + +void Client::disconnect() +{ + if (_socket != NULL) + { + qDebug("*** Client %s disconnected.", qPrintable(_ip)); + _socket->blockSignals(true); + _socket->abort(); + _socket->deleteLater(); + _socket = NULL; + this->deleteLater(); + } +} diff --git a/src/server/net/client.h b/src/server/net/client.h new file mode 100644 index 0000000..8004ebb --- /dev/null +++ b/src/server/net/client.h @@ -0,0 +1,104 @@ +#ifndef CLIENT_H_ +#define CLIENT_H_ + +#include +#include +#include +#include "../../shared/networkmessage.h" + +class QSslSocket; + +struct ClientLogin +{ + bool accept; + QString name; + QString host; + QString ip; +}; + +typedef int ClientId; + +class Client : public QObject +{ + Q_OBJECT + +private: + static ClientId _clientIdCounter; + + QSslSocket *_socket; + int _authed; // 0 = challenge sent, awaiting reply 1 = challenge ok, client challenge replied, awaiting login, 2 = ESTABLISHED + QString _name; + QString _host; + QString _ip; + QByteArray _challenge; + qint64 _pingTimeout; + NetworkMessage _toClient, _fromClient; + int _timerIdAuthTimeout; + + ClientId _id; // this client's unique id + + // If this client should be projected to from another client, the other client's id is set here. 0 otherwise. + // This is not currently used and it is questionable if this makes sense, as it might just be confusing if + // several groups students watch different other students. + // Also, visualizing such a situation in the GUI in a meaningful way would be hard. + ClientId _desiredProjectionSource; + // This boolean tells whether this client is currently the VNC broadcast source. This + // version only allows "one to all others" setups + bool _isProjectionSource; + + ClientId _currentProjectionSource; + + QString _vncRwPass, _vncRoPass; + int _vncPort; + bool _activeVncClient; + + void handleMsg(); + void disconnect(); + +protected: + void timerEvent(QTimerEvent* event); + +public: + explicit Client(QSslSocket* socket); + ~Client(); + void requestThumb(const int width, const int height); + void sendMessage(NetworkMessage& message); + //void acceptData(); + const inline bool isAuthed() const { return _authed == 2; } + const inline QString& name() const { return _name; } + const inline QString& host() const { return _host; } + const inline QString& ip() const { return _ip; } + // The computer ID (used eg. for saving the frame positions) is currently the IP, but this is an extra method for easier modification later on + const inline QString& computerId() const { return _ip; } + const inline ClientId id() const { return _id; } + + inline const QString& vncRwPass() const { return _vncRwPass; } + inline const QString& vncRoPass() const { return _vncRoPass; } + inline const int vncPort() const { return _vncPort; } + inline const bool isActiveVncClient() const { return _activeVncClient; } + inline const bool isActiveVncServer() const { return _vncPort > 0; } + inline const ClientId desiredProjectionSource() const { return _desiredProjectionSource; } + inline void setDesiredProjectionSource(ClientId source) { _desiredProjectionSource = source; } + inline const bool isProjectionSource() const { return _isProjectionSource; } + inline void setProjectionSource(bool enable) { _isProjectionSource = enable; } + inline const ClientId currentProjectionSource() const { return _currentProjectionSource; } + inline void setCurrentProjectionSource(ClientId source) { _currentProjectionSource = source; } + void startVncServer(); + void stopVncServer(); + +signals: + void authenticating(Client* client, ClientLogin* request); + void authenticated(Client* client); + void thumbUpdated(Client* client, const QPixmap& thumb); + void vncServerStateChange(Client* client); + void vncClientStateChange(Client* client); + +private slots: + void onSslErrors(const QList & errors); // triggered for errors that occur during SSL negotiation + void onDataArrival(); // triggered if data is available for reading + void onClosed(); // triggered if the socket is closed + void onError(QAbstractSocket::SocketError errcode); // triggered if an error occurs on the socket + +}; + +#endif /* CLIENT_H_ */ diff --git a/src/server/net/discoverylistener.cpp b/src/server/net/discoverylistener.cpp new file mode 100644 index 0000000..051a972 --- /dev/null +++ b/src/server/net/discoverylistener.cpp @@ -0,0 +1,165 @@ +/* + * discoverylistener.cpp + * + * Created on: 25.01.2013 + * Author: sr + */ + +#include "discoverylistener.h" +#include "certmanager.h" +#include "../util/global.h" +#include "../../shared/settings.h" +#include "../../shared/network.h" +#include "../../shared/util.h" +#include +#include +#include + +#define UDPBUFSIZ 9000 +#define SPAM_CUTOFF 50 +#define SPAM_MODERATE_INTERVAL 6787 +#define SPAM_MODERATE_AT_ONCE 100 + +// static objects + + +// +++ static ++++ hash ip address +++ + +static quint16 hash(const QHostAddress& host) +{ + static quint16 seed1 = 0, seed2 = 0; + while (seed1 == 0) // Make sure the algorithm uses different seeds each time the program is + { // run to prevent hash collision attacks + seed1 = (quint16)(qrand() & 0xffff); + seed2 = (quint16)(qrand() & 0xffff); + } + quint8 data[16], len; + if (host.protocol() == QAbstractSocket::IPv4Protocol) + { + // IPv4 + quint32 addr = host.toIPv4Address(); + len = 4; + memcpy(data, &addr, len); + } + else if (QAbstractSocket::IPv6Protocol) + { + // IPv6 + len = 16; + // Fast version (might break with future qt versions) + memcpy(data, host.toIPv6Address().c, len); + /* // --- Safe version (but better try to figure out a new fast way if above stops working ;)) + Q_IPV6ADDR addr = host.toIPv6Address(); + for (int i = 0; i < len; ++i) + data[i] = addr[i]; + */ + } + else + { + // Durr? + len = 2; + data[0] = (quint16)qrand(); + data[1] = (quint16)qrand(); + } + quint16 result = 0; + quint16 mod = seed1; + for (quint8 i = 0; i < len; ++i) + { + result = ((result << 1) + data[i]) ^ mod; // because of the shift this algo is not suitable for len(input) > 8 + mod += seed2 + data[i]; + } + return result; +} + +// +++++++++++++++++++++++++++++++++++ + +DiscoveryListener::DiscoveryListener() : + _socket(this), _counterResetPos(0) +{ + if (!_socket.bind(SERVICE_DISCOVERY_PORT)) + qFatal("Could not bind to service discovery port %d", (int)SERVICE_DISCOVERY_PORT); + connect(&_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead())); + for (int i = 0; i < SD_PACKET_TABLE_SIZE; ++i) + _packetCounter[i] = 0; + startTimer((SPAM_MODERATE_AT_ONCE * SPAM_MODERATE_INTERVAL) / SD_PACKET_TABLE_SIZE + 1); +} + +DiscoveryListener::~DiscoveryListener() +{ + // TODO Auto-generated destructor stub +} + +/** + * Overrides + */ + +/** + * Decrease packet counters + */ +void DiscoveryListener::timerEvent(QTimerEvent* event) +{ + for (int i = 0; i < SPAM_MODERATE_AT_ONCE; ++i) + { + if (++_counterResetPos >= SD_PACKET_TABLE_SIZE) + _counterResetPos = 0; + if (_packetCounter[_counterResetPos] > 10) + _packetCounter[_counterResetPos] -= 10; + else if (_packetCounter[_counterResetPos] != 0) + _packetCounter[_counterResetPos] = 0; + } +} + +/** + * Slots + */ + +void DiscoveryListener::onReadyRead() +{ + char data[UDPBUFSIZ]; + QHostAddress addr; + quint16 port; + while (_socket.hasPendingDatagrams()) + { + const qint64 size = _socket.readDatagram(data, UDPBUFSIZ, &addr, &port); + if (size <= 0) + continue; + const quint16 bucket = hash(addr) % SD_PACKET_TABLE_SIZE; + if (_packetCounter[bucket] > SPAM_CUTOFF) + { + qDebug() << "SD: Potential (D)DoS from " << _socket.peerAddress().toString(); + // emit some signal and pop up a big warning that someone is flooding/ddosing the PVS SD + // ... on the other hand, will the user understand? ;) + continue; + } + ++_packetCounter[bucket]; + _packet.reset(); + if (!_packet.readMessage(data, (quint32)size)) + continue; + // Valid packet, process it: + const QByteArray iplist(_packet.getFieldBytes(_IPLIST)); + const QByteArray hash(_packet.getFieldBytes(_HASH)); + const QByteArray salt1(_packet.getFieldBytes(_SALT1)); + const QByteArray salt2(_packet.getFieldBytes(_SALT2)); + // For security, the salt has to be at least 16 bytes long + if (salt1.size() < 16 || salt2.size() < 16) + continue; // To make this more secure, you could remember the last X salts used, and ignore new packets using the same + // 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())) + continue; + // If so, check if the submitted hash seems valid + if (genSha1(&Global::sessionNameArray(), &salt1, &iplist) != hash) + continue; // did not match local session name + qDebug("Got matching discovery request..."); + QByteArray myiplist(Network::interfaceAddressesToString().toUtf8()); + QSslKey key; + QSslCertificate cert; + CertManager::getPrivateKeyAndCert("manager", key, cert); + QByteArray certhash(cert.digest(QCryptographicHash::Sha1)); + // Reply to client + _packet.reset(); + _packet.setField(_HASH, genSha1(&Global::sessionNameArray(), &salt2, &myiplist, &CLIENT_PORT_ARRAY, &certhash)); + _packet.setField(_IPLIST, myiplist); + _packet.setField(_PORT, CLIENT_PORT_ARRAY); + _packet.setField(_CERT, certhash); + _packet.writeMessage(&_socket, addr, port); + } +} diff --git a/src/server/net/discoverylistener.h b/src/server/net/discoverylistener.h new file mode 100644 index 0000000..64d4351 --- /dev/null +++ b/src/server/net/discoverylistener.h @@ -0,0 +1,40 @@ +/* + * discoverylistener.h + * + * Created on: 25.01.2013 + * Author: sr + */ + +#ifndef DISCOVERYLISTENER_H_ +#define DISCOVERYLISTENER_H_ + +#include +#include +#include "../../shared/networkmessage.h" + +#define SD_PACKET_TABLE_SIZE 20000 + +class DiscoveryListener : public QObject +{ + Q_OBJECT + +private: + QUdpSocket _socket; + NetworkMessage _packet; + int _counterResetPos; + + quint8 _packetCounter[SD_PACKET_TABLE_SIZE]; // count packets per source address to ignore spammers + +protected: + void timerEvent(QTimerEvent* event); + +public: + DiscoveryListener(); + virtual ~DiscoveryListener(); + +private slots: + void onReadyRead(); + +}; + +#endif /* DISCOVERYLISTENER_H_ */ diff --git a/src/server/net/listenserver.cpp b/src/server/net/listenserver.cpp new file mode 100644 index 0000000..706962d --- /dev/null +++ b/src/server/net/listenserver.cpp @@ -0,0 +1,40 @@ +#include "listenserver.h" +#include "client.h" +#include + +#define MAX_CLIENTS 50 + +ListenServer::ListenServer(quint16 port) +{ + if (!_server.listen(QHostAddress::Any, port) || !_server.isListening()) + qFatal("Cannot bind to TCP port %d (incoming SSL clients)", (int)port); + connect(&_server, SIGNAL(newConnection()), this, SLOT(newClientConnection())); +} + +ListenServer::~ListenServer() +{ + _server.close(); + for (int i = 0; i < _clients.size(); ++i) + _clients[i]->deleteLater(); +} + +/** + * Slots + */ + +void ListenServer::newClientConnection() +{ + QSslSocket* sock; + while ((sock = (QSslSocket*)_server.nextPendingConnection()) != NULL) + { + if (_clients.size() >= MAX_CLIENTS) + { + sock->abort(); + sock->deleteLater(); + continue; + } + Client* client = new Client(sock); + _clients.append(client); // create new client class and add to list + emit newClient(client); + } +} diff --git a/src/server/net/listenserver.h b/src/server/net/listenserver.h new file mode 100644 index 0000000..f237703 --- /dev/null +++ b/src/server/net/listenserver.h @@ -0,0 +1,30 @@ +#ifndef LISTENSERVER_H_ +#define LISTENSERVER_H_ + +#include +#include +#include "sslserver.h" + +class Client; + +class ListenServer : public QObject +{ + Q_OBJECT + +private: + SslServer _server; + QList _clients; + +public: + explicit ListenServer(quint16 port); + virtual ~ListenServer(); + +private slots: + void newClientConnection(); + +signals: + void newClient(Client* client); + +}; + +#endif /* LISTENSERVER_H_ */ diff --git a/src/server/net/sslserver.cpp b/src/server/net/sslserver.cpp new file mode 100644 index 0000000..70daea4 --- /dev/null +++ b/src/server/net/sslserver.cpp @@ -0,0 +1,113 @@ +/* + # Copyright (c) 2009 - 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/ + # ----------------------------------------------------------------------------- + # src/net/SslServer.cpp + # - provide QTcpServer-like behaviour for SSL + # ----------------------------------------------------------------------------- + */ + +#include "sslserver.h" +#include +#include +#include "certmanager.h" + +SslServer::SslServer() +{ + _tmr = startTimer(5123); + //QSslSocket::setDefaultCiphers(QSslSocket::supportedCiphers()); +} + +SslServer::~SslServer() +{ + killTimer((_tmr)); +} + +void SslServer::incomingConnection(int socketDescriptor) +{ + QSslSocket *serverSocket = new QSslSocket(NULL); + connect(serverSocket, SIGNAL(sslErrors(const QList &)), this, SLOT(sslErrors(const QList &))); + QSslKey key; + QSslCertificate cert; + CertManager::getPrivateKeyAndCert("manager", key, cert); + serverSocket->setPrivateKey(key); + serverSocket->setLocalCertificate(cert); + serverSocket->setPeerVerifyMode(QSslSocket::VerifyNone); + serverSocket->setProtocol(QSsl::SslV3); + //printf("Keylen %d\n", serverSocket->privateKey().length()); + if (serverSocket->setSocketDescriptor(socketDescriptor)) + { + // Once the connection is successfully encrypted, raise our newConnection event + connect(serverSocket, SIGNAL(encrypted()), this, SIGNAL(newConnection())); + serverSocket->startServerEncryption(); + _pending.push_back(serverSocket); + } + else + { + serverSocket->deleteLater(); + } +} + +void SslServer::sslErrors(const QList & errors) +{ + //qDebug("FIXME: SSL ERRORS on SERVER: %s", qPrintable(errors.begin()->errorString())); +} + +void SslServer::timerEvent(QTimerEvent* event) +{ + // Remove all sockets marked for deletion + while (!_delete.isEmpty()) + { + QSslSocket *sock = _delete.takeFirst(); + sock->blockSignals(true); + sock->deleteLater(); + } + _delete = _pending; + _pending.clear(); +} + +bool SslServer::hasPendingConnections() +{ + for (QList::iterator it(_pending.begin()); it != _pending.end(); it++) + { + qDebug("State: %d - Encrypted: %d", (int)(*it)->state(), (*it)->isEncrypted()); + if ((*it)->state() == QAbstractSocket::ConnectedState && (*it)->isEncrypted()) + return true; + } + return false; +} + +QTcpSocket* SslServer::nextPendingConnection() +{ + for (QList::iterator it(_pending.begin()); it != _pending.end(); it++) + { + if ((*it)->state() == QAbstractSocket::ConnectedState && (*it)->isEncrypted()) + { + QSslSocket *sock = *it; + QObject::disconnect(sock, SIGNAL(encrypted()), this, SIGNAL(newConnection())); + _pending.removeAll(sock); + _delete.removeAll(sock); + return sock; + } + } + for (QList::iterator it(_delete.begin()); it != _delete.end(); it++) + { + if ((*it)->state() == QAbstractSocket::ConnectedState && (*it)->isEncrypted()) + { + QSslSocket *sock = *it; + QObject::disconnect(sock, SIGNAL(encrypted()), this, SIGNAL(newConnection())); + _pending.removeAll(sock); + _delete.removeAll(sock); + return sock; + } + } + return NULL; +} + diff --git a/src/server/net/sslserver.h b/src/server/net/sslserver.h new file mode 100644 index 0000000..400bf6a --- /dev/null +++ b/src/server/net/sslserver.h @@ -0,0 +1,50 @@ +/* +# Copyright (c) 2009 - 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/ +# ----------------------------------------------------------------------------- +# src/net/SslServer.cpp +# - provide QTcpServer-like behaviour for SSL +# ----------------------------------------------------------------------------- +*/ + +#ifndef SSLSERVER_H_ +#define SSLSERVER_H_ + +#include +#include +#include + +class QSslSocket; + +class SslServer : public QTcpServer +{ + Q_OBJECT + +private Q_SLOTS: + void sslErrors ( const QList & errors ); + +public: + SslServer(); + virtual ~SslServer(); + + bool hasPendingConnections (); + // This one has to return a TcpSocket as we're overwriting from the base class + // just cast it to QSslSocket later + virtual QTcpSocket* nextPendingConnection(); + +protected: + void incomingConnection(int socketDescriptor); + void timerEvent (QTimerEvent* event); + QList _pending; + QList _delete; + int _tmr; +}; + +#endif /* SSLSERVER_H_ */ diff --git a/src/server/sessionnamewindow/sessionnamewindow.cpp b/src/server/sessionnamewindow/sessionnamewindow.cpp new file mode 100644 index 0000000..1d8c699 --- /dev/null +++ b/src/server/sessionnamewindow/sessionnamewindow.cpp @@ -0,0 +1,68 @@ +/* + # Copyright (c) 2009 - 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/ + # ----------------------------------------------------------------------------- + # mainWindow.cpp + This is the Main class for the pvsManager. The GUI is contructed here. + # ----------------------------------------------------------------------------- + */ + +#include +#include "sessionnamewindow.h" +#include "../util/global.h" + +#include "ui_sessionname.h" + +SessionNameWindow::SessionNameWindow(QWidget *parent) : + QDialog(parent), ui(new Ui::SessionName) + +{ + ui->setupUi(this); + connect(ui->bboxOkCancel, SIGNAL(accepted()), this, SLOT(onOkClicked())); + connect(ui->bboxOkCancel, SIGNAL(rejected()), this, SLOT(close())); + connect(ui->cmdRandom, SIGNAL(clicked(bool)), this, SLOT(onGenerateRandomName())); +} + +SessionNameWindow::~SessionNameWindow() +{ + delete ui; +} + +void SessionNameWindow::show(const QString& name) +{ + ui->txtName->setText(name); + this->showNormal(); +} + +/* + * Overridden methods + */ + +void SessionNameWindow::closeEvent(QCloseEvent *e) +{ + e->ignore(); + this->hide(); +} + +/** + * Slots + */ + +void SessionNameWindow::onOkClicked() +{ + Global::setSessionName(ui->txtName->text()); + emit updateSessionName(); + this->hide(); +} + +void SessionNameWindow::onGenerateRandomName() +{ + ui->txtName->setText(QString::number(qrand() % 9000 + 1000)); +} diff --git a/src/server/sessionnamewindow/sessionnamewindow.h b/src/server/sessionnamewindow/sessionnamewindow.h new file mode 100644 index 0000000..ed944c1 --- /dev/null +++ b/src/server/sessionnamewindow/sessionnamewindow.h @@ -0,0 +1,38 @@ +#ifndef _SESSIONNAMEWINDOW_H_ +#define _SESSIONNAMEWINDOW_H_ + +#include + + +namespace Ui +{ +class SessionName; +} + +class SessionNameWindow : public QDialog +{ + Q_OBJECT + +private: + Ui::SessionName *ui; + +public: + SessionNameWindow(QWidget *parent = 0); + ~SessionNameWindow(); + + void show(const QString& name); + +protected: + void closeEvent(QCloseEvent *e); + +private slots: + void onOkClicked(); + void onGenerateRandomName(); + +signals: + void updateSessionName(); + +}; + + +#endif diff --git a/src/server/util/global.cpp b/src/server/util/global.cpp new file mode 100644 index 0000000..9b7b4f2 --- /dev/null +++ b/src/server/util/global.cpp @@ -0,0 +1,17 @@ +/* + * global.cpp + * + * Created on: 29.01.2013 + * Author: sr + */ + +#include "global.h" + +QString Global::_sessionName = QString(); +QByteArray Global::_sessionNameArray = QByteArray(); + +void Global::setSessionName(const QString& name) +{ + Global::_sessionName = name; + Global::_sessionNameArray = name.toUtf8(); +} diff --git a/src/server/util/global.h b/src/server/util/global.h new file mode 100644 index 0000000..38eec6d --- /dev/null +++ b/src/server/util/global.h @@ -0,0 +1,29 @@ +/* + * global.h + * + * Created on: 29.01.2013 + * Author: sr + */ + +#ifndef GLOBAL_H_ +#define GLOBAL_H_ + +#include +#include + +class Global +{ +private: + Global(){} + ~Global(){} + + static QString _sessionName; + static QByteArray _sessionNameArray; + +public: + static const QString& sessionName() { return Global::_sessionName; } + static const QByteArray& sessionNameArray() { return Global::_sessionNameArray; } + static void setSessionName(const QString& name); +}; + +#endif /* GLOBAL_H_ */ diff --git a/src/server/util/util.cpp b/src/server/util/util.cpp new file mode 100644 index 0000000..7ff9404 --- /dev/null +++ b/src/server/util/util.cpp @@ -0,0 +1,14 @@ +/* + * Util.cpp + * + * Created on: 18.01.2013 + * Author: sr + */ + +#include "util.h" + +namespace Util +{ + +} + diff --git a/src/server/util/util.h b/src/server/util/util.h new file mode 100644 index 0000000..e2d6a62 --- /dev/null +++ b/src/server/util/util.h @@ -0,0 +1,17 @@ +#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", "pvs2mgr") +#define SYSTEM_SETTINGS(name) QSettings name (QSettings::IniFormat, QSettings::SystemScope, "openslx", "pvs2mgr") + +namespace Util +{ + +} + +#endif /* UTIL_H_ */ diff --git a/src/shared/network.cpp b/src/shared/network.cpp new file mode 100644 index 0000000..6dd6a73 --- /dev/null +++ b/src/shared/network.cpp @@ -0,0 +1,50 @@ +/* + * network.cpp + * + * Created on: 28.01.2013 + * Author: sr + */ + +#include "network.h" +#include +#include +#include + +namespace Network +{ + + +/** + * Returns list of all addresses assigned to the interfaces of this machine. + * Every address is surrounded by '|', eg: + * |1.2.3.4|3.66.77.88|123:34::1| + */ +QString interfaceAddressesToString() +{ + QList list(QNetworkInterface::allAddresses()); + return interfaceAddressesToString(list); +} + +QString interfaceAddressesToString(const QList& list) +{ + QString ret; + ret.reserve(500); + for (QList::const_iterator it(list.begin()); it != list.end(); ++it) + { + if (*it == QHostAddress::LocalHost || *it == QHostAddress::LocalHostIPv6) + continue; // TODO: Filter other addresses/ranges? + ret.append("|"); + ret.append(it->toString()); + } + ret.append("|"); + return ret; +} + +bool isAddressInList(const QString& list, const QString& address) +{ + static const QString find("|%1|"); + return list.contains(find.arg(address), Qt::CaseSensitive); +} + + +} diff --git a/src/shared/network.h b/src/shared/network.h new file mode 100644 index 0000000..4a763be --- /dev/null +++ b/src/shared/network.h @@ -0,0 +1,25 @@ +/* + * network.h + * + * Created on: 28.01.2013 + * Author: sr + */ + +#ifndef NETWORK_H_ +#define NETWORK_H_ + +#include +#include + +namespace Network +{ + + +QString interfaceAddressesToString(); +QString interfaceAddressesToString(const QList& list); +bool isAddressInList(const QString& list, const QString& address); + + +} + +#endif /* NETWORK_H_ */ diff --git a/src/shared/networkmessage.cpp b/src/shared/networkmessage.cpp new file mode 100644 index 0000000..9a7ba9f --- /dev/null +++ b/src/shared/networkmessage.cpp @@ -0,0 +1,296 @@ +/* + * NetworkMessage.cpp + * + * Created on: 18.01.2013 + * Author: sr + */ + +#include +#include +#include "networkmessage.h" + +#define HEADER_LEN 8 +#define MAX_MSG_LEN 60000 + +#define BYTE_SWAP4(x) \ + (((x & 0xFF000000) >> 24) | \ + ((x & 0x00FF0000) >> 8) | \ + ((x & 0x0000FF00) << 8) | \ + ((x & 0x000000FF) << 24)) + +#define BYTE_SWAP2(x) \ + (((x & 0xFF00) >> 8) | \ + ((x & 0x00FF) << 8)) + +static quint16 _htons(quint16 x) +{ + if (QSysInfo::ByteOrder == QSysInfo::BigEndian) + return x; + return BYTE_SWAP2(x); +} + +static quint16 _ntohs(quint16 x) +{ + if (QSysInfo::ByteOrder == QSysInfo::BigEndian) + return x; + return BYTE_SWAP2(x); +} + +static quint32 _htonl(quint32 x) +{ + if (QSysInfo::ByteOrder == QSysInfo::BigEndian) + return x; + return BYTE_SWAP4(x); +} + +static quint32 _ntohl(quint32 x) +{ + if (QSysInfo::ByteOrder == QSysInfo::BigEndian) + return x; + return BYTE_SWAP4(x); +} + +/* + // ####################################################### \\ ° . ° + \\ ####################################################### // \___/ + */ + +NetworkMessage::NetworkMessage() : + _buffer(NULL), _bufferSize(0), _bufferPos(0), _lastBufferSize(0), _mode(0) +{ + // +} + +NetworkMessage::~NetworkMessage() +{ + if (_buffer) + delete[] _buffer; +} + +inline void NetworkMessage::allocBuffer() +{ + if (_lastBufferSize < _bufferSize || _buffer == NULL) + { + if (_buffer) + delete[] _buffer; + _lastBufferSize = _bufferSize; + _buffer = new char[_lastBufferSize]; + } +} + +bool NetworkMessage::readMessage(QAbstractSocket* socket) +{ + // Check/Set the _mode variable, so read and write calls are not mixed + if (_mode != 1) + { + if (_mode != 0) + { + qDebug("NetworkMessage::readMessage(TCP) called when class was in mode %d!", _mode); + return false; + } + _mode = 1; + } + // buffer size == 0 means the header hasn't been received yet. do so and set things up + if (_bufferSize == 0) + { + if (socket->bytesAvailable() < HEADER_LEN) + return true; + char header[HEADER_LEN]; + if (socket->read(header, HEADER_LEN) != HEADER_LEN) + { + qDebug("FIXME: Socket said 8 bytes available, but could not read 8..."); + return false; + } + if (!this->parseHeader(header)) + return false; + //qDebug() << "Expecting message of " << _bufferSize << " bytes"; + allocBuffer(); + } + if (_bufferSize > _bufferPos) + { + while (_bufferSize > _bufferPos && socket->bytesAvailable() > 0) + { + const qint64 ret = socket->read(_buffer + _bufferPos, _bufferSize - _bufferPos); + //qDebug() << "Read " << ret << " bytes"; + if (ret < 0) + return false; + _bufferPos += ret; + //qDebug() << "Buffer has now " << _bufferPos << " of " << _bufferSize << " bytes"; + } + if (_bufferSize == _bufferPos) + { + if (!this->parseMessage(_buffer)) + return false; + } + } + return true; +} + +bool NetworkMessage::readMessage(char* data, quint32 len) +{ + // Check/Set the _mode variable, so read and write calls are not mixed + if (_mode != 1) + { + if (_mode != 0) + { + qDebug("NetworkMessage::readMessage(UDP) called when class was in mode %d!", _mode); + return false; + } + _mode = 1; + } + if (len < HEADER_LEN) + { + qDebug("UDP message shorter than 8 bytes. ignored."); + return false; + } + if (!this->parseHeader(data)) + return false; + if (len != _bufferSize + HEADER_LEN) + { + qDebug("UDP packet has wrong size. Is %d, expected %d", (int)_bufferSize, len - HEADER_LEN); + return false; + } + return this->parseMessage(data + HEADER_LEN); +} + +bool NetworkMessage::parseHeader(char *header) +{ + if (header[0] != 'P' || header[1] != 'V' || header[2] != 'S' || header[3] != '2') + { + qDebug("Protocol magic wrong."); + return false; + } + _bufferPos = 0; + _bufferSize = _ntohl(*(quint32*)(header + 4)); + if (_bufferSize > MAX_MSG_LEN) + { + qDebug("Disconnecting Client: MAX_MSG_LEN exceeded."); + return false; + } + if (_bufferSize < 4) // TODO: magic number. msg needs to be at least 4 bytes for 1 key/value pair of length 0 each. + { + qDebug("A Client sent an empty message."); + return false; + } + return true; +} + +bool NetworkMessage::parseMessage(char *buffer) +{ + char *ptr = buffer; + while (_bufferSize - (ptr - buffer) >= 4) + { + const quint16 keyLen = _ntohs(*(quint16*)(ptr)); + ptr += 2; + const quint16 valLen = _ntohs(*(quint16*)(ptr)); + ptr += 2; + if (_bufferSize - (ptr - buffer) < keyLen + valLen) + { + qDebug() << "Warning: Error parsing message. key(" << keyLen << ")+value(" << valLen + << ") length > total remaining bytes (" << (_bufferSize - (ptr - buffer)) << ")"; + return false; + } + _fields.insert(QString::fromUtf8(ptr, keyLen), QByteArray(ptr + keyLen, valLen)); + //qDebug() << "Got " << QString::fromUtf8(ptr, keyLen) << " -> " << QString::fromUtf8(ptr + keyLen, valLen); + ptr += keyLen + valLen; + } + _mode = 3; + return true; +} + +bool NetworkMessage::writeMessage(QAbstractSocket* socket) +{ + if (_mode != 2) + { + if (_mode == 1) + { + qDebug("NetworkMessage::writeMessage called when class was in mode %d!", _mode); + return false; + } + _mode = 1; + _bufferPos = 0; + } + // key/value pairs have not been serialized yet... + if (_bufferSize == 0) + { + this->serializeMessage(); + } + const qint64 ret = socket->write(_buffer + _bufferPos, _bufferSize - _bufferPos); + if (ret == 0) + return true; + if (ret < 0) + return false; + _bufferPos += ret; + if (_bufferPos == _bufferSize) + { + _bufferPos = 0; + _mode = 4; + } + return true; +} + +bool NetworkMessage::writeMessage(QUdpSocket* socket, const QHostAddress& address, quint16 port) +{ + if (_mode != 4) + { + if (_mode == 1) + { + qDebug("NetworkMessage::writeMessage called when class was in mode %d!", _mode); + return false; + } + _mode = 4; + _bufferPos = 0; + } + // key/value pairs have not been serialized yet... + if (_bufferSize == 0) + { + this->serializeMessage(); + } + const qint64 ret = socket->writeDatagram(_buffer, _bufferSize, address, port); + if (ret != _bufferSize) + return false; + if (ret < 0) + return false; + _bufferPos = 0; + return true; +} + +void NetworkMessage::serializeMessage() +{ + QByteArray buf; + //qDebug() << "Default size: " << buf.capacity(); + buf.reserve(_lastBufferSize > 0 ? _lastBufferSize : 200); + for (QHash::const_iterator it = _fields.begin(); it != _fields.end(); ++it) + { + const QByteArray &ba = it.key().toUtf8(); + const QByteArray &val = it.value(); + quint16 keyLen = _htons((quint16)ba.size()); + quint16 valLen = _htons((quint16)val.size()); + //qDebug() << "Adding to msg(" << ba.size() << "/" << val.size() << "): " << ba << " -> " << val; + buf.append((char*)&keyLen, 2); + buf.append((char*)&valLen, 2); + buf.append(ba); + buf.append(val); + } + _bufferSize = buf.length() + HEADER_LEN; + allocBuffer(); + memcpy(_buffer + HEADER_LEN, buf.data(), buf.size()); + _buffer[0] = 'P'; + _buffer[1] = 'V'; + _buffer[2] = 'S'; + _buffer[3] = '2'; + const quint32 flipped = _htonl(_bufferSize - HEADER_LEN); + memcpy(_buffer + 4, &flipped, 4); +} + +void NetworkMessage::buildErrorMessage(const QString& error) +{ + this->reset(); + this->setField(_ID, _ERROR); + this->setField(_ERROR, error); +} + +void NetworkMessage::buildErrorMessage(const char* error) +{ + this->buildErrorMessage(QString::fromUtf8(error)); +} diff --git a/src/shared/networkmessage.h b/src/shared/networkmessage.h new file mode 100644 index 0000000..5a8b2c2 --- /dev/null +++ b/src/shared/networkmessage.h @@ -0,0 +1,69 @@ +/* + * NetworkMessage.h + * + * Created on: 18.01.2013 + * Author: sr + */ + +#ifndef NETWORKMESSAGE_H_ +#define NETWORKMESSAGE_H_ + +#include + +class QAbstractSocket; +class QUdpSocket; + +// define qstrings for message ids. this prevents implicit instanciation of the same qstrings over and over again +#define MSGTYPE(name) static const QString _ ## name ( #name ) +MSGTYPE(ID); +MSGTYPE(IMG); +MSGTYPE(LOGIN); +MSGTYPE(THUMB); +MSGTYPE(X); +MSGTYPE(Y); +MSGTYPE(VNCSERVER); +MSGTYPE(VNCCLIENT); +MSGTYPE(LOCK); +MSGTYPE(HASH); +MSGTYPE(SALT1); +MSGTYPE(SALT2); +MSGTYPE(IPLIST); +MSGTYPE(PORT); +MSGTYPE(CERT); +MSGTYPE(CHALLENGE); +MSGTYPE(ERROR); + +class NetworkMessage +{ +private: + char *_buffer; + quint32 _bufferSize, _bufferPos, _lastBufferSize; + QHash _fields; + int _mode; // 0 = none, 1 = reading, 2 = writing, 3 = read complete, 4 = write complete + + void allocBuffer(); + bool parseHeader(char *header); + bool parseMessage(char *buffer); + void serializeMessage(); + +public: + NetworkMessage(); + virtual ~NetworkMessage(); + bool readMessage(QAbstractSocket* socket); + bool readMessage(char* data, quint32 len); + bool writeMessage(QAbstractSocket* socket); + bool writeMessage(QUdpSocket* socket, const QHostAddress& address, quint16 port); + void reset() { _fields.clear(); _bufferSize = 0; _mode = 0; } + const bool readComplete() const { return _mode == 3; } + const bool writeComplete() const { return _mode == 4; } + const bool hasField(const QString& key) const { return _fields.contains(key); } + const QString getFieldString(const QString& key) const { return QString::fromUtf8(_fields.value(key)); } + const QByteArray getFieldBytes(const QString& key) const { return _fields.value(key); } + void setField(const QString& key, const QByteArray& value) { if (_mode == 1 || _mode == 2) qFatal("setField called in bad state."); _fields.insert(key, value); _mode = 0; } + void setField(const QString& key, const QString& value) { setField(key, value.toUtf8()); } + // Convenience + void buildErrorMessage(const QString& error); + void buildErrorMessage(const char* error); +}; + +#endif /* NETWORKMESSAGE_H_ */ diff --git a/src/shared/settings.h b/src/shared/settings.h new file mode 100644 index 0000000..c1c45c1 --- /dev/null +++ b/src/shared/settings.h @@ -0,0 +1,12 @@ +#ifndef _SETTINGS_H_ +#define _SETTINGS_H_ + +#define CLIENT_PORT 5194 +static const QString CLIENT_PORT_STR(QString::number(CLIENT_PORT)); +static const QByteArray CLIENT_PORT_ARRAY(QString::number(CLIENT_PORT).toUtf8()); + +#define SERVICE_DISCOVERY_PORT 3492 + +#define PING_TIMEOUT_MS 30000 + +#endif diff --git a/src/shared/util.cpp b/src/shared/util.cpp new file mode 100644 index 0000000..d5c101e --- /dev/null +++ b/src/shared/util.cpp @@ -0,0 +1,22 @@ +/* + * util.cpp + * + * Created on: 30.01.2013 + * Author: sr + */ + +#include "util.h" +#include + +static QCryptographicHash sha1(QCryptographicHash::Sha1); + +QByteArray genSha1(const QByteArray* a, const QByteArray* b, const QByteArray* c, const QByteArray* d, const QByteArray* e) +{ + sha1.reset(); + sha1.addData(*a); + if (b) sha1.addData(*b); + if (c) sha1.addData(*c); + if (d) sha1.addData(*d); + if (e) sha1.addData(*e); + return sha1.result(); +} diff --git a/src/shared/util.h b/src/shared/util.h new file mode 100644 index 0000000..c2e5145 --- /dev/null +++ b/src/shared/util.h @@ -0,0 +1,15 @@ +/* + * util.h + * + * Created on: 30.01.2013 + * Author: sr + */ + +#ifndef UTIL_H_ +#define UTIL_H_ + +#include + +QByteArray genSha1(const QByteArray* a, const QByteArray* b = NULL, const QByteArray* c = NULL, const QByteArray* d = NULL, const QByteArray* e = NULL); + +#endif /* UTIL_H_ */ -- cgit v1.2.3-55-g7522