diff options
author | Fabian Schillinger | 2010-11-01 17:35:27 +0100 |
---|---|---|
committer | Fabian Schillinger | 2010-11-01 17:35:27 +0100 |
commit | ea3fb17345e5f82db9f2e98a8062e95797700ace (patch) | |
tree | 1da0d1a8ec9455364386af78762d0f6fed187824 /src | |
parent | Process start/stop/view functionality (diff) | |
parent | [PVSGUI] No X required for --help and --version (diff) | |
download | pvs-ea3fb17345e5f82db9f2e98a8062e95797700ace.tar.gz pvs-ea3fb17345e5f82db9f2e98a8062e95797700ace.tar.xz pvs-ea3fb17345e5f82db9f2e98a8062e95797700ace.zip |
Merge branch 'master' of openslx.org:pvs
Conflicts:
CMakeLists.txt
src/core/pvsConnectionManager.cpp
src/pvs.cpp
src/pvs.h
Diffstat (limited to 'src')
93 files changed, 8528 insertions, 94 deletions
diff --git a/src/core/pvsClient.cpp b/src/core/pvsClient.cpp index f048b54..0448f97 100644 --- a/src/core/pvsClient.cpp +++ b/src/core/pvsClient.cpp @@ -316,6 +316,5 @@ void PVSClient::processesVectorAdd(QString msg) else if (msg.indexOf("vector ready") == 0) { emit processVectorReady(true); - ConsoleLog writeError("processVectorReady(true)!"); } } diff --git a/src/core/pvsConnectionManager.cpp b/src/core/pvsConnectionManager.cpp index d0f1479..db302c4 100644 --- a/src/core/pvsConnectionManager.cpp +++ b/src/core/pvsConnectionManager.cpp @@ -260,6 +260,25 @@ void PVSConnectionManager::onCommand(PVSMsg command) else tmp->processesVectorAdd(message); } } + else if (ident == "MCASTFTANNOUNCE") + { + _pvsServer.sendToAll(command); + } + else if (ident == "MCASTFTRETRY") + { + QStringList fields = message.split(':'); + if (!fields.size() == 2) + { + qDebug() << "Malformed MCASTFTRETRY message:" << message; + return; + } + + PVSClient* client = getClientFromUsername(fields[0]); + if (client) + { + client->sendMessage(command); + } + } } void PVSConnectionManager::onChat(PVSMsg chatMsg) { diff --git a/src/gui/clientConfigDialog.cpp b/src/gui/clientConfigDialog.cpp index 0ee5908..b7ba407 100644 --- a/src/gui/clientConfigDialog.cpp +++ b/src/gui/clientConfigDialog.cpp @@ -16,20 +16,33 @@ # ----------------------------------------------------------------------------- */ +#include <QtDebug> +#include <QNetworkInterface> +#include <QStandardItemModel> #include "clientConfigDialog.h" +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <src/net/pvsNetworkInterfaceListModel.h> + +using namespace std; ClientConfigDialog::ClientConfigDialog(QWidget *parent) : - QDialog(parent) + QDialog(parent), + _interfaceListModel(0) { setupUi(this); connect(this, SIGNAL(accepted()), this, SLOT(writeSettings())); connect(radioButtonOtherRO, SIGNAL(clicked()), this, SLOT(checkPermissions())); + _interfaceListModel = new PVSNetworkInterfaceListModel(this); + interfaceList->setModel(_interfaceListModel); + interfaceList->setModelColumn(0); + connect(reloadInterfaceListButton, SIGNAL(clicked()), _interfaceListModel, SLOT(reloadInterfaceList())); // connect to D-Bus and get interface QDBusConnection dbus = QDBusConnection::sessionBus(); _ifaceDBus = new OrgOpenslxPvsInterface("org.openslx.pvs", "/", dbus, this); - } ClientConfigDialog::~ClientConfigDialog() @@ -53,7 +66,14 @@ void ClientConfigDialog::readSettings() else comboBox->setCurrentIndex(_settings.value("Display/location").toInt()); - QDBusPendingReply<QString> reply = _ifaceDBus->getConfigValue("Permissions/vnc_lecturer"); + QDBusPendingReply<QString> reply = _ifaceDBus->getConfigValue("multicast/interface"); + reply.waitForFinished(); + if (reply.isValid()) + { + interfaceList->setEditText(reply.value()); + } + + reply = _ifaceDBus->getConfigValue("Permissions/vnc_lecturer"); reply.waitForFinished(); if (reply.isValid()) { @@ -86,7 +106,6 @@ void ClientConfigDialog::readSettings() reply.waitForFinished(); if (reply.isValid()) checkBoxAllowFiletransfer->setChecked(reply.value() == "T"); - } void ClientConfigDialog::writeSettings() @@ -110,6 +129,7 @@ void ClientConfigDialog::writeSettings() QString(checkBoxAllowChat->isChecked() ? "T" : "F")); _ifaceDBus->setConfigValue("Permissions/allow_filetransfer", QString(checkBoxAllowFiletransfer->isChecked() ? "T" : "F")); + _ifaceDBus->setConfigValue("multicast/interface", interfaceList->currentText()); _settings.sync(); emit configChanged(); diff --git a/src/gui/clientConfigDialog.h b/src/gui/clientConfigDialog.h index 98da54f..fd7529e 100644 --- a/src/gui/clientConfigDialog.h +++ b/src/gui/clientConfigDialog.h @@ -18,6 +18,8 @@ #include "pvsinterface.h" #include "ui_clientConfigDialog.h" +class QAbstractItemModel; + class ClientConfigDialog: public QDialog, private Ui::ClientConfigDialogClass { Q_OBJECT @@ -40,6 +42,7 @@ private Q_SLOTS: private: OrgOpenslxPvsInterface *_ifaceDBus; QSettings _settings; + QAbstractItemModel* _interfaceListModel; }; diff --git a/src/gui/clientFileReceiveDialog.cpp b/src/gui/clientFileReceiveDialog.cpp index 669ca81..fc6a1a3 100644 --- a/src/gui/clientFileReceiveDialog.cpp +++ b/src/gui/clientFileReceiveDialog.cpp @@ -16,6 +16,9 @@ */ #include "clientFileReceiveDialog.h" +#include <pvsinterface.h> +#include <QFileInfo> +#include <limits.h> ClientFileReceiveDialog::ClientFileReceiveDialog(QTcpSocket *socket, QWidget *parent) : QDialog(parent) @@ -37,9 +40,44 @@ ClientFileReceiveDialog::ClientFileReceiveDialog(QTcpSocket *socket, QWidget *pa connect(this, SIGNAL(finished(int)), this, SLOT(deleteLater())); } +ClientFileReceiveDialog::ClientFileReceiveDialog(QString const& sender, qulonglong transferID, + QString const& filename, qulonglong size, OrgOpenslxPvsInterface* ifaceDBus, QWidget* parent) +{ + setupUi(this); + + _transferID = transferID; + _filename = filename; + _ifaceDBus = ifaceDBus; + _bytesToRead = size; + _socket = 0; + _file = 0; + div = 1; + while((size / div) > INT_MAX) + { + div <<= 1; + } + + connect(ifaceDBus, SIGNAL(incomingMulticastTransferStarted(qulonglong)), SLOT(mcastTransferStarted(qulonglong))); + connect(ifaceDBus, SIGNAL(incomingMulticastTransferProgress(qulonglong, qulonglong, qulonglong)), + SLOT(mcastTransferProgress(qulonglong, qulonglong, qulonglong))); + connect(ifaceDBus, SIGNAL(incomingMulticastTransferFinished(qulonglong)), SLOT(mcastTransferFinished(qulonglong))); + connect(ifaceDBus, SIGNAL(incomingMulticastTransferFailed(qulonglong, QString)), SLOT(mcastTransferFailed(qulonglong, QString))); + connect(cancelButton, SIGNAL(clicked()), SLOT(cancelTransfer())); + + qDebug("[%s] New multicast incoming transfer: %s from %s", metaObject()->className(), + filename.toLocal8Bit().constData(), sender.toLocal8Bit().constData()); + + + filenameLabel->setText(QFileInfo(filename).baseName()); + labelNick->setText(sender); + progressBar->setRange(0, 0); +} + + ClientFileReceiveDialog::~ClientFileReceiveDialog() { - _socket->deleteLater(); + if(_socket) + _socket->deleteLater(); qDebug("[%s] Deleted!", metaObject()->className()); } @@ -168,6 +206,61 @@ void ClientFileReceiveDialog::error(QAbstractSocket::SocketError error) close(); } +void ClientFileReceiveDialog::mcastTransferStarted(qulonglong transferID) +{ + if(transferID != _transferID) + return; +} + +void ClientFileReceiveDialog::mcastTransferProgress(qulonglong transferID, qulonglong bytes, qulonglong of) +{ + if(transferID != _transferID) + return; + + progressBar->setRange(0, of); + progressBar->setValue(bytes); + + labelA->setText(formatSize(bytes)); + labelB->setText(formatSize(of)); +} + +void ClientFileReceiveDialog::mcastTransferFinished(qulonglong transferID) +{ + if(transferID != _transferID) + return; + + QString filename = QFileDialog::getSaveFileName(this, tr("Where should I save %1?").arg(_filename), _filename); + QFile* file = new QFile(_filename); + if(filename.isNull() || filename.isEmpty()) + { + file->remove(); + } + else + { + if(!file->rename(filename)) + { + QMessageBox::warning(this, tr("Could not rename file"), tr("Failed to rename %1 to %2").arg(_filename).arg(filename)); + } + } + accept(); + deleteLater(); +} + +void ClientFileReceiveDialog::mcastTransferFailed(qulonglong transferID, QString reason) +{ + if(transferID != _transferID) + return; + + QMessageBox::warning(this, tr("File transfer failed"), tr("File transfer failed for the following reason:\n%1").arg(reason)); + reject(); + deleteLater(); +} + +void ClientFileReceiveDialog::cancelTransfer() +{ + _ifaceDBus->cancelIncomingMulticastTransfer(_transferID); +} + QString ClientFileReceiveDialog::formatSize(qint64 size) { if (size >= 1000000000) // GB diff --git a/src/gui/clientFileReceiveDialog.h b/src/gui/clientFileReceiveDialog.h index c13d7b7..c9ed220 100644 --- a/src/gui/clientFileReceiveDialog.h +++ b/src/gui/clientFileReceiveDialog.h @@ -18,6 +18,8 @@ #include <QtNetwork> #include "ui_clientFileReceiveDialog.h" +class OrgOpenslxPvsInterface; + class ClientFileReceiveDialog: public QDialog, private Ui::ClientFileReceiveDialogClass { @@ -25,6 +27,7 @@ Q_OBJECT public: ClientFileReceiveDialog(QTcpSocket *socket, QWidget *parent = 0); + ClientFileReceiveDialog(QString const& sender, qulonglong transferID, QString const& basename, qulonglong size, OrgOpenslxPvsInterface* ifaceDBus, QWidget* parent = 0); ~ClientFileReceiveDialog(); private Q_SLOTS: @@ -33,6 +36,13 @@ private Q_SLOTS: void close(); void error(QAbstractSocket::SocketError error); + // multicast: + void mcastTransferStarted(qulonglong transferID); + void mcastTransferProgress(qulonglong transferID, qulonglong bytes, qulonglong of); + void mcastTransferFinished(qulonglong transferID); + void mcastTransferFailed(qulonglong transferID, QString reason); + void cancelTransfer(); + private: void sendAck(bool b); QString formatSize(qint64 size); @@ -42,6 +52,10 @@ private: qint64 _bytesToRead; int div; + // multicast: + OrgOpenslxPvsInterface* _ifaceDBus; + QString _filename; + qulonglong _transferID; }; #endif /* CLIENTFILERECEIVEDIALOG_H_ */ diff --git a/src/gui/clientFileSendDialog.cpp b/src/gui/clientFileSendDialog.cpp index b4512c0..93da725 100644 --- a/src/gui/clientFileSendDialog.cpp +++ b/src/gui/clientFileSendDialog.cpp @@ -22,6 +22,10 @@ ClientFileSendDialog::ClientFileSendDialog(QWidget *parent) : { setupUi(this); + _transferID = 0; + _error = false; + _isMulticast = false; + _file = NULL; _socket = NULL; _clientNicklistDialog = new ClientNicklistDialog(this); @@ -58,7 +62,15 @@ void ClientFileSendDialog::open() reject(); return; } - open(_clientNicklistDialog->getNick()); + + if (_clientNicklistDialog->isSendToAll()) + { + sendToAll(); + } + else + { + open(_clientNicklistDialog->getNick()); + } } void ClientFileSendDialog::open(QString nick) @@ -73,6 +85,70 @@ void ClientFileSendDialog::open(QString nick) open(nick, filename); } +void ClientFileSendDialog::sendToAll() +{ + QString filename = QFileDialog::getOpenFileName(this, tr("Send File"), QDir::homePath(), ""); + if (filename == "") + { + reject(); + return; + } + sendToAll(filename); +} + +void ClientFileSendDialog::sendToAll(QString filename) +{ + _isMulticast = true; + + connect(_ifaceDBus, SIGNAL(outgoingMulticastTransferStarted(qulonglong)), SLOT(multicastTransferStarted(qulonglong))); + connect(_ifaceDBus, SIGNAL(outgoingMulticastTransferProgress(qulonglong,qulonglong,qulonglong)), SLOT(multicastTransferProgress(qulonglong, qulonglong, qulonglong))); + connect(_ifaceDBus, SIGNAL(outgoingMulticastTransferFinished(qulonglong)), SLOT(multicastTransferFinished(qulonglong))); + connect(_ifaceDBus, SIGNAL(outgoingMulticastTransferFailed(qulonglong, QString const&)), SLOT(multicastTransferFailed(qulonglong, QString const&))); + + filenameLabel->setText(filename); + progressBar->setRange(0, 0); + labelNick->setText(tr("all")); + labelStatus->setText(tr("Waiting to start")); + + QString errorMessage("Backend error"); + + // We need to jump through a lot of hoops because this call is prone to time out, and + // QtDBus does not support specifying timeouts in generated interfaces. + QDBusMessage call = QDBusMessage::createMethodCall("org.openslx.pvs", "/", "org.openslx.pvs", "createMulticastTransfer"); + call << filename; + + QDBusMessage reply = _ifaceDBus->connection().call(call, QDBus::Block, 5000); + if (reply.type() == QDBusMessage::ErrorMessage) + { + QMessageBox::critical(this, tr("File Send error"), tr("Error communicating with backend: %1: %2").arg(reply.errorName()).arg(reply.errorMessage())); + reject(); + return; + } + else if (reply.type() == QDBusMessage::InvalidMessage) + { + QMessageBox::critical(this, tr("File Send error"), tr("Something went wrong while communicating with backend, but I don't know what.")); + reject(); + return; + } + else if (reply.type() == QDBusMessage::ReplyMessage) + { + QList<QVariant> args = reply.arguments(); + bool created = args.at(0).toBool(); + _transferID = args.at(1).toULongLong(); + QString errorMessage = args.at(2).toString(); + if (!created) + { + QMessageBox::critical(this, tr("File Send error"), tr("Could not create a multicast transfer: %1").arg(errorMessage)); + reject(); + return; + } + } + + connect(cancelButton, SIGNAL(clicked()), SLOT(canceled())); + + show(); +} + void ClientFileSendDialog::open(QString nick, QString filename) { // open file @@ -127,6 +203,8 @@ void ClientFileSendDialog::receiveAck() QString ack = QString::fromUtf8(_socket->readLine()); if (ack != "ok\n") { + _error = true; + _reason = tr("Receiver declined"); qDebug("[%s] Received nack!", metaObject()->className()); close(); return; @@ -158,27 +236,30 @@ void ClientFileSendDialog::sendFile() void ClientFileSendDialog::close() { - if (_file && _file->isOpen()) - { - _file->close(); - qDebug("[%s] File closed.", metaObject()->className()); - } - - if (_socket && _socket->isOpen()) + if (!_isMulticast) { - disconnect(_socket, SIGNAL(readyRead()), this, SLOT(receiveAck())); - disconnect(_socket, SIGNAL(bytesWritten(qint64)), this, SLOT(sendFile())); - disconnect(_socket, SIGNAL(disconnected()), this, SLOT(close())); - disconnect(_socket, SIGNAL(connected()), this, SLOT(sendHeader())); - disconnect(_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(error(QAbstractSocket::SocketError))); - _socket->disconnectFromHost(); - qDebug("[%s] Connection closed.", metaObject()->className()); + if (_file && _file->isOpen()) + { + _file->close(); + qDebug("[%s] File closed.", metaObject()->className()); + } + + if (_socket && _socket->isOpen()) + { + disconnect(_socket, SIGNAL(readyRead()), this, SLOT(receiveAck())); + disconnect(_socket, SIGNAL(bytesWritten(qint64)), this, SLOT(sendFile())); + disconnect(_socket, SIGNAL(disconnected()), this, SLOT(close())); + disconnect(_socket, SIGNAL(connected()), this, SLOT(sendHeader())); + disconnect(_socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(error(QAbstractSocket::SocketError))); + _socket->disconnectFromHost(); + qDebug("[%s] Connection closed.", metaObject()->className()); + } } - disconnect(cancelButton, SIGNAL(clicked()), this, SLOT(close())); + disconnect(cancelButton, SIGNAL(clicked()), this, SLOT(canceled())); - if (_bytesToWrite == 0) + if (!_error) { accept(); QMessageBox::information(0, tr("PVS - File Transfer"), @@ -188,12 +269,26 @@ void ClientFileSendDialog::close() { reject(); QMessageBox::warning(0, tr("PVS - File Transfer"), - tr("File Transfer canceled!")); + tr("File Transfer canceled: %1").arg(_reason)); } } +void ClientFileSendDialog::canceled() +{ + if(_isMulticast) + { + _ifaceDBus->cancelOutgoingMulticastTransfer(_transferID); + } + + _error = true; + _reason = tr("You clicked 'Cancel'"); + close(); +} + void ClientFileSendDialog::error(QAbstractSocket::SocketError error) { + _error = true; + _reason = tr("Socket Error"); qDebug("[%s] Socket error: %i", metaObject()->className(), error); close(); } @@ -210,6 +305,55 @@ QString ClientFileSendDialog::formatSize(qint64 size) return QString("%1B").arg((qreal)size, 0, 'f',1); } +void ClientFileSendDialog::multicastTransferStarted(qulonglong transferID) +{ + qDebug() << "multicastTransferStarted(" << transferID << ")"; + if (transferID != _transferID) + { + return; + } + labelStatus->setText("Started"); +} + +void ClientFileSendDialog::multicastTransferProgress(qulonglong transferID, qulonglong bytes, qulonglong of) +{ + qDebug() << "multicastTransferProgress(" << transferID << bytes << of << ")"; + if (transferID != _transferID) + { + return; + } + + if(bytes < of) + { + labelStatus->setText("Transferring"); + progressBar->setRange(0, of); + progressBar->setValue(bytes); + } + else + { + labelStatus->setText("Waiting to finish"); + progressBar->setRange(0, 0); + } + + labelA->setText(formatSize(bytes)); + labelB->setText(formatSize(of)); +} + +void ClientFileSendDialog::multicastTransferFinished(quint64 transferID) +{ + qDebug() << "multicastTransferFinished(" << transferID << ")"; + qDebug("[%s] MulticastTransfer finished", metaObject()->className()); + close(); +} + +void ClientFileSendDialog::multicastTransferFailed(quint64 transferID, QString const& reason) +{ + qDebug() << "multicastTransferFailed(" << transferID << reason << ")"; + _error = true; + _reason = reason; + close(); +} + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -233,6 +377,25 @@ ClientNicklistDialog::ClientNicklistDialog(QWidget *parent) : listWidget->addItems(nicknames); listWidget->setCurrentRow(0); + + connect(sendToAllCheckBox, SIGNAL(stateChanged(int)), SLOT(sendToAllStateChanged(int))); + + sendToAllCheckBox->setCheckState(Qt::Unchecked); + _isSendToAll = false; +} + +void ClientNicklistDialog::sendToAllStateChanged(int state) +{ + if (state) + { + listWidget->setEnabled(false); + _isSendToAll = true; + } + else + { + listWidget->setEnabled(true); + _isSendToAll = false; + } } ClientNicklistDialog::~ClientNicklistDialog() diff --git a/src/gui/clientFileSendDialog.h b/src/gui/clientFileSendDialog.h index d8afc3a..7abdcc7 100644 --- a/src/gui/clientFileSendDialog.h +++ b/src/gui/clientFileSendDialog.h @@ -30,9 +30,17 @@ public: ~ClientNicklistDialog(); QString getNick(); + bool isSendToAll() const + { + return _isSendToAll; + } + +private Q_SLOTS: + void sendToAllStateChanged(int state); private: OrgOpenslxPvsInterface *_ifaceDBus; + bool _isSendToAll; }; @@ -51,14 +59,22 @@ public: void open(); void open(QString nick); void open(QString nick, QString filename); + void sendToAll(); + void sendToAll(QString filename); private Q_SLOTS: void sendHeader(); void receiveAck(); void sendFile(); void close(); + void canceled(); void error(QAbstractSocket::SocketError error); + void multicastTransferStarted(qulonglong transferID); + void multicastTransferProgress(qulonglong transferID, qulonglong bytes, qulonglong of); + void multicastTransferFinished(qulonglong transferID); + void multicastTransferFailed(qulonglong transferID, QString const& reason); + private: QString formatSize(qint64 size); @@ -71,6 +87,11 @@ private: OrgOpenslxPvsInterface *_ifaceDBus; QString _nickname; + quint64 _transferID; + bool _error; + QString _reason; + bool _isMulticast; + }; #endif /* CLIENTFILESENDDIALOG_H_ */ diff --git a/src/gui/connectionWindow.h b/src/gui/connectionWindow.h index e7d0177..55442db 100644 --- a/src/gui/connectionWindow.h +++ b/src/gui/connectionWindow.h @@ -9,6 +9,7 @@ #include <iostream> #include <math.h> #include <QSettings> +#include <QPointer> #define FRAME_DELAY 1000 // to comply with the standard value in the gui @@ -110,7 +111,7 @@ protected: private: ConnectionFrame* newConFrame(PVSClient* newConnection); // returns a new frame for the given connection ConnectionFrame* currentSingleFrame; // pointer to the frame thats currently in FullScreen - ConnectionFrame* _closeupFrame; + QPointer<ConnectionFrame> _closeupFrame; QPoint currentPosition (ConnectionFrame* cF); QMenu *menu; QAction *newDummy; diff --git a/src/gui/frame.cpp b/src/gui/frame.cpp index cb79643..1b80c3d 100644 --- a/src/gui/frame.cpp +++ b/src/gui/frame.cpp @@ -18,11 +18,16 @@ # ----------------------------------------------------------------------------- */ +#include <src/input/inputEvent.h> +#include <src/input/inputHandlerChain.h> #include "frame.h" #include <src/gui/mainWindow.h> #include <iostream> #include <QPixmap> +#define MOUSE_MOTION_SEND_INTERVAL 100 /* msecs */ +#define SPECIAL_EVENT_WAIT_TIME 5000 /* msecs */ + Frame::Frame(const QString & text, QWidget * parent) : QLabel(parent), _clientVNCThread(0) { @@ -51,10 +56,30 @@ Frame::Frame(const QString & text, QWidget * parent) : button_lock = createToolButton(tr("Lock this client"), QIcon(":/lock"),SLOT(setLock())); //button_unlock = createToolButton(tr("Unlock this client"), QIcon(":/lock"),SLOT(setLock())); button_dozent = createToolButton(tr("Set as Superclient"), QIcon(":/dozent2"),SLOT(setDozent())); + button_control = createToolButton(tr("Enable Remote Control"), QIcon(":/remotecontrol"), SLOT(remoteControlClicked())); + button_control->setCheckable(true); + button_control_all = createToolButton(tr("Remote Control All Clients"), QIcon(":/remotecontrolall"), SLOT(remoteControlAllClicked())); + button_control_all->setCheckable(true); connect(this, SIGNAL(clicked()), this, SLOT(slotClicked())); ip = ""; setToolButtonListVisible(false); + + _remoteControlEnabled = false; + _remoteControlToAll = false; + + _mouseMotionEventTimer = new QTimer(this); + _mouseMotionEventTimer->setInterval(MOUSE_MOTION_SEND_INTERVAL); + _mouseMotionEventTimer->setSingleShot(false); + connect(_mouseMotionEventTimer, SIGNAL(timeout()), this, SLOT(sendMouseMotionEvent())); + + _mousePositionChanged = true; + + _specialEventTimer = new QTimer(this); + _specialEventTimer->setInterval(SPECIAL_EVENT_WAIT_TIME); + _specialEventTimer->setSingleShot(true); + connect(_specialEventTimer, SIGNAL(timeout()), this, SLOT(showSpecialEventMenu())); + } Frame::~Frame() @@ -216,23 +241,45 @@ void Frame::slotClicked() void Frame::mousePressEvent(QMouseEvent* event) { - emit clicked(); - if (event->button() == Qt::RightButton) - { - /*if (!_dummy) - DelDummy->setDisabled(true); - menu->exec(QCursor::pos());*/ - } - else - { + if(!_remoteControlEnabled) + { + emit clicked(); + if (event->button() == Qt::RightButton) + { + /*if (!_dummy) + DelDummy->setDisabled(true); + menu->exec(QCursor::pos());*/ + } + else + { - } - QLabel::mousePressEvent(event); + } + QLabel::mousePressEvent(event); + } + else + { + event->accept(); + ConsoleLog writeLine("Captured remote control mousePressEvent"); + + updateMousePosition(event); + sendInputEvent(InputEvent::mousePressRelease(event->button(), event->buttons())); + } } void Frame::mouseReleaseEvent ( QMouseEvent * event ) { - QLabel::mouseReleaseEvent(event); + if(!_remoteControlEnabled) + { + QLabel::mouseReleaseEvent(event); + } + else + { + event->accept(); + ConsoleLog writeLine("Captured remote control mouseReleaseEvent"); + + updateMousePosition(event); + sendInputEvent(InputEvent::mousePressRelease(event->button(), event->buttons())); + } } QToolButton* Frame::createToolButton(const QString &toolTip, const QIcon &icon, const char *member) @@ -330,3 +377,236 @@ void Frame::setDozent() getConFrame()->setDozent(true); } } + +void Frame::remoteControlClicked() +{ + if(_remoteControlEnabled) + { + setMouseTracking(false); + _mouseMotionEventTimer->stop(); + button_control->setToolTip(tr("Enable Remote Control")); + _remoteControlEnabled = false; + button_control->setChecked(false); + releaseKeyboard(); + } + else + { + button_control->setToolTip(tr("Disable Remote Control")); + _remoteControlEnabled = true; + button_control->setChecked(true); + _mouseMotionEventTimer->start(); + setMouseTracking(true); + if(_mouseOver) + grabKeyboard(); + } +} + +void Frame::remoteControlAllClicked() +{ + if(_remoteControlToAll) + { + button_control_all->setToolTip(tr("Remote Control only this Client")); + button_control_all->setChecked(false); + _remoteControlToAll = false; + } + else + { + button_control_all->setToolTip(tr("Remote Control All Clients")); + button_control_all->setChecked(true); + _remoteControlToAll = true; + } +} + + + +void Frame::sendMouseMotionEvent() +{ + InputEvent evt = InputEvent::mouseMotion(_lastRecordedMousePosition.x(), _lastRecordedMousePosition.y()); + + if(!_mousePositionChanged) + return; + + _mousePositionChanged = false; + sendInputEvent(evt); +} + +void Frame::sendInputEvent(InputEvent const& evt) +{ + QString str; + eventToString(evt, str); + std::string evtStr = evt.toString(); + PVSMsg msg(PVSCOMMAND, "INPUTEVENT", str); + + if(_remoteControlEnabled) + { + if(_remoteControlToAll) + { + ConsoleLog writeLine(QString("sendInputEvent(%1) to one").arg(evtStr.c_str())); + PVSConnectionManager::getManager()->getServer()->sendToAll(msg); + } + else + { + ConsoleLog writeLine(QString("sendInputEvent(%1) to all").arg(evtStr.c_str())); + _cFrame->getConnection()->sendMessage(msg); + } + } + else + { + ConsoleLog writeLine("sendMouseMotionEvent() disabled"); + } +} + +void Frame::mouseMoveEvent(QMouseEvent* event) +{ + QPoint newPosition = rescalePosition(event->posF()); + if(newPosition != _lastRecordedMousePosition) { + _lastRecordedMousePosition = newPosition; + _mousePositionChanged = true; + ConsoleLog writeLine(QString("Mouse moved to (%1,%2)").arg(_lastRecordedMousePosition.x()).arg(_lastRecordedMousePosition.y())); + } +} + +QPoint Frame::rescalePosition(QPointF guipos) +{ + if(!_clientVNCThread) + return QPoint(); + + QSize s = size(); + QSize t = _clientVNCThread->getSize(); + qreal px, py; + px = guipos.x() * t.width() / (qreal)s.width(); + py = guipos.y() * t.height() / (qreal)s.height(); + return QPoint((int)px, (int)py); +} + +void Frame::updateMousePosition(QMouseEvent* event) +{ + QPoint oldPosition = _lastRecordedMousePosition; + _lastRecordedMousePosition = rescalePosition(event->posF()); + _mousePositionChanged = oldPosition != _lastRecordedMousePosition; + sendMouseMotionEvent(); +} + +void Frame::enterEvent(QEvent* event) +{ + _mouseOver = true; + if(_remoteControlEnabled) + { + grabKeyboard(); + } +} + +void Frame::leaveEvent(QEvent* event) +{ + _mouseOver = false; + if(_remoteControlEnabled) + { + releaseKeyboard(); + } +} + +void Frame::keyPressEvent(QKeyEvent* event) +{ + if(_remoteControlEnabled) + { + if(event->key() == Qt::Key_Menu) + { + qDebug("Menu has been pressed"); + if(!event->isAutoRepeat()) + _specialEventTimer->start(); + } + else + { + // The action of the keyboard may depend on the position of the pointer + sendMouseMotionEvent(); + sendInputEvent(InputEvent::keyboardPress(event->key(), event->modifiers())); + } + } + else + { + QLabel::keyPressEvent(event); + } +} + +void Frame::keyReleaseEvent(QKeyEvent* event) +{ + if(_remoteControlEnabled) + { + sendMouseMotionEvent(); + if(event->key() == Qt::Key_Menu) + { + if(!event->isAutoRepeat()) + { + qDebug("Menu has been released"); + if(_specialEventTimer->isActive()) + { + qDebug("Pressing key on client"); + // Pressing the key has been deferred, so do it now: + sendInputEvent(InputEvent::keyboardPress(event->key(), event->modifiers())); + } + sendInputEvent(InputEvent::keyboardRelease(event->key(), event->modifiers())); + _specialEventTimer->stop(); + } + } + else + { + // The action of the keyboard may depend on the position of the pointer + sendInputEvent(InputEvent::keyboardRelease(event->key(), event->modifiers())); + } + } + else + { + QLabel::keyReleaseEvent(event); + } +} + +bool Frame::event(QEvent* event) +{ + if(_remoteControlEnabled) + { + bool recognized; + switch(event->type()) + { + case QEvent::ShortcutOverride: + recognized = true; + event->accept(); + break; + case QEvent::KeyPress: + recognized = true; + keyPressEvent(static_cast<QKeyEvent*>(event)); + break; + case QEvent::KeyRelease: + recognized = true; + keyReleaseEvent(static_cast<QKeyEvent*>(event)); + break; + default: + recognized = false; + } + if(recognized && event->isAccepted()) + return true; + } + return QLabel::event(event); +} + +void Frame::showSpecialEventMenu() +{ + qDebug("Trying to show menu..."); + QMenu* menu = new QMenu(this); + QList<SpecialInputEventDescription> specialEvents = privileged_handler_chain::describe(); + QList<SpecialInputEventDescription>::iterator iter; + int i; + for(i = 0, iter = specialEvents.begin(); + iter != specialEvents.end(); + iter++, i++) + { + QAction* act = menu->addAction((*iter).descriptionString); + act->setData(i); + } + QAction* selected = menu->exec(QCursor::pos()); + if(selected) + { + int index = selected->data().toInt(); + sendInputEvent(specialEvents.at(index).toEvent()); + } + delete menu; +} diff --git a/src/gui/frame.h b/src/gui/frame.h index 3004e0c..8271670 100644 --- a/src/gui/frame.h +++ b/src/gui/frame.h @@ -8,6 +8,7 @@ class VNCClientThread; class ConnectionWindow; class ConnectionFrame; class MainWindow; +class InputEvent; class Frame: public QLabel { @@ -48,8 +49,13 @@ public: QToolButton* button_lock; QToolButton* button_unlock; QToolButton* button_dozent; + QToolButton* button_control; + QToolButton* button_control_all; QList<QToolButton*> toolButtonList; + bool _remoteControlEnabled; + bool _remoteControlToAll; + public Q_SLOTS: void updateImage(int x, int y, int w, int h); void iamDown(); @@ -68,13 +74,24 @@ public Q_SLOTS: void setLock(); //void unlock(); void setDozent(); +private Q_SLOTS: + void remoteControlClicked(); + void remoteControlAllClicked(); + void sendMouseMotionEvent(); + void showSpecialEventMenu(); signals: - void clicked(); + void clicked(); protected: void paintEvent(QPaintEvent *event); void mousePressEvent ( QMouseEvent * event ); void mouseReleaseEvent ( QMouseEvent * event ); + void mouseMoveEvent ( QMouseEvent * event ); + void enterEvent(QEvent* event); + void leaveEvent(QEvent* event); + void keyPressEvent(QKeyEvent* event); + void keyReleaseEvent(QKeyEvent* event); + bool event(QEvent* event); private: QToolButton* createToolButton(const QString &toolTip, const QIcon &icon, const char *member); @@ -85,6 +102,16 @@ private: bool _isLocked; bool _dozent; int _ux, _uy; + + // for remote control: + QPoint _lastRecordedMousePosition; + bool _mousePositionChanged; + QTimer* _mouseMotionEventTimer; + bool _mouseOver; + QTimer* _specialEventTimer; + QPoint rescalePosition(QPointF guiPosition); + void updateMousePosition(QMouseEvent* event); + void sendInputEvent(InputEvent const&); }; #endif /* FRAME_H_ */ diff --git a/src/gui/mainWindow.cpp b/src/gui/mainWindow.cpp index 815f001..b6878e8 100644 --- a/src/gui/mainWindow.cpp +++ b/src/gui/mainWindow.cpp @@ -16,7 +16,10 @@ #include <QtGui> #include <QFileDialog> +#include <QBuffer> +#include <QDataStream> #include <src/gui/mainWindow.h> +#include <src/net/mcast/McastConfiguration.h> using namespace std; // setting the IF-DEF Block for the touchgui and the normal gui, for later use @@ -35,6 +38,7 @@ using namespace std; #include <src/gui/processesDialog.h> //#include <src/gui/dialog.h> #include <src/core/pvsConnectionManager.h> +#include <src/gui/multicastConfigDialog.h> #include <iostream> MainWindow::MainWindow(QWidget *parent) : @@ -133,6 +137,7 @@ MainWindow::MainWindow(QWidget *parent) : ui->actionFoto->setStatusTip(tr("Make a screenshot for the selected client(s)")); ui->actionLock->setStatusTip(tr("Lock or Unlock all Clients")); connect(ui->actionCreate_profile, SIGNAL(triggered()), this, SLOT(createProfile())); + connect(ui->actionConfigure_Network, SIGNAL(triggered()), this, SLOT(configureNetwork())); connect(ui->actionShowProcesses, SIGNAL(triggered()), this, SLOT(showProcesses())); ui->actionShowProcesses->setStatusTip(tr("Show Processes of the selected Client")); connect(ui->actionStartProcess, SIGNAL(triggered()), this, SLOT(startProcess())); @@ -163,7 +168,6 @@ MainWindow::MainWindow(QWidget *parent) : _serverSocket = new QTcpServer(); _serverSocket->listen(QHostAddress::Any, 29481); connect(_serverSocket, SIGNAL(newConnection()), this, SLOT(incomingFile())); - } MainWindow::~MainWindow() @@ -270,24 +274,24 @@ void MainWindow::loadSettings() if (current.compare("default") == 0) { setWindowTitle("PVSmgr - Default"); - QPoint pos1 = settings.value("default/1", QPoint(0, 0)).toPoint(); - MainWindow::getConnectionWindow()->addFrameBySettings("1", pos1); - QPoint pos2 = settings.value("default/2", QPoint(194, 0)).toPoint(); - MainWindow::getConnectionWindow()->addFrameBySettings("2", pos2); - QPoint pos3 = settings.value("default/3", QPoint(388, 0)).toPoint(); - MainWindow::getConnectionWindow()->addFrameBySettings("3", pos3); - QPoint pos4 = settings.value("default/4", QPoint(582, 0)).toPoint(); - MainWindow::getConnectionWindow()->addFrameBySettings("4", pos4); - QPoint pos5 = settings.value("default/5", QPoint(0, 173)).toPoint(); - MainWindow::getConnectionWindow()->addFrameBySettings("5", pos5); - QPoint pos6 = settings.value("default/6", QPoint(194, 173)).toPoint(); - MainWindow::getConnectionWindow()->addFrameBySettings("6", pos6); - QPoint pos7 = settings.value("default/7", QPoint(388, 173)).toPoint(); - MainWindow::getConnectionWindow()->addFrameBySettings("7", pos7); - QPoint pos8 = settings.value("default/8", QPoint(582, 173)).toPoint(); - MainWindow::getConnectionWindow()->addFrameBySettings("8", pos8); - QPoint pos9 = settings.value("default/9", QPoint(293, 346)).toPoint(); - MainWindow::getConnectionWindow()->addFrameBySettings("9", pos9); +// QPoint pos1 = settings.value("default/1", QPoint(0, 0)).toPoint(); +// MainWindow::getConnectionWindow()->addFrameBySettings("1", pos1); +// QPoint pos2 = settings.value("default/2", QPoint(194, 0)).toPoint(); +// MainWindow::getConnectionWindow()->addFrameBySettings("2", pos2); +// QPoint pos3 = settings.value("default/3", QPoint(388, 0)).toPoint(); +// MainWindow::getConnectionWindow()->addFrameBySettings("3", pos3); +// QPoint pos4 = settings.value("default/4", QPoint(582, 0)).toPoint(); +// MainWindow::getConnectionWindow()->addFrameBySettings("4", pos4); +// QPoint pos5 = settings.value("default/5", QPoint(0, 173)).toPoint(); +// MainWindow::getConnectionWindow()->addFrameBySettings("5", pos5); +// QPoint pos6 = settings.value("default/6", QPoint(194, 173)).toPoint(); +// MainWindow::getConnectionWindow()->addFrameBySettings("6", pos6); +// QPoint pos7 = settings.value("default/7", QPoint(388, 173)).toPoint(); +// MainWindow::getConnectionWindow()->addFrameBySettings("7", pos7); +// QPoint pos8 = settings.value("default/8", QPoint(582, 173)).toPoint(); +// MainWindow::getConnectionWindow()->addFrameBySettings("8", pos8); +// QPoint pos9 = settings.value("default/9", QPoint(293, 346)).toPoint(); +// MainWindow::getConnectionWindow()->addFrameBySettings("9", pos9); QString title = "PVSmgr - "; title.append(_profilName); @@ -1277,6 +1281,18 @@ void MainWindow::startChatDialog() sChatDialog.raise();//show the chat dialog on top level } +void MainWindow::configureNetwork() +{ + PVSServer* server = PVSConnectionManager::getManager()->getServer(); + McastConfiguration mc(*(server->getMulticastConfiguration())); + MulticastConfigDialog* mcd = new MulticastConfigDialog(&mc, this); + int result = mcd->exec(); + if(result == QDialog::Accepted) + { + server->multicastReconfigure(&mc); + } +} + MainWindow* MainWindow::myself = NULL; ConnectionList* MainWindow::conList = NULL; ConnectionWindow* MainWindow::conWin = NULL; diff --git a/src/gui/mainWindow.h b/src/gui/mainWindow.h index 2ab62f8..28f82f7 100644 --- a/src/gui/mainWindow.h +++ b/src/gui/mainWindow.h @@ -204,6 +204,7 @@ private slots: void setPasswordForConnection(int enabled); void combobox1(int menuindex1); // Funktion um index der combobox auszulesen und weiterzuverarbeiten s. Ticker 671 //void combobox2(int menuindex2); // Funktion um index der combobox auszulesen und weiterzuverarbeiten + void configureNetwork(); }; diff --git a/src/gui/multicastConfigDialog.cpp b/src/gui/multicastConfigDialog.cpp new file mode 100644 index 0000000..ff370c7 --- /dev/null +++ b/src/gui/multicastConfigDialog.cpp @@ -0,0 +1,170 @@ +#include <QValidator> +#include <QIntValidator> +#include <QHostAddress> +#include <QPushButton> +#include "multicastConfigDialog.h" +#include <src/net/mcast/McastConfiguration.h> +// #include "multicastValidators.h" + +MulticastConfigDialog::MulticastConfigDialog(QWidget* parent) : + QDialog(parent) +{ + setupUi(); +} + +MulticastConfigDialog::MulticastConfigDialog(McastConfiguration* config, + QWidget *parent) : + QDialog(parent) +{ + setupUi(); + _config = config; + + _ui.groupAddressEdit->setText(config->multicastAddress()); + _ui.dataPortEdit->setText(QString::number(config->multicastUDPPortBase())); + + connect(_ui.buttonBox, SIGNAL(accepted()), this, SLOT(dialogAccepted())); + connect(_ui.buttonBox, SIGNAL(rejected()), this, SLOT(reject())); +} + +void MulticastConfigDialog::setupUi() +{ + _ui.setupUi(this); + + QIntValidator* portValidator = new QIntValidator(1024, 65535, this); + _ui.dataPortEdit->setValidator(portValidator); + + connect(_ui.groupAddressEdit, SIGNAL(textChanged(QString const&)), this, + SLOT(validateGroupAddress(QString const&))); + connect(_ui.dataPortEdit, SIGNAL(textChanged(QString const&)), this, + SLOT(validateDataPort(QString const&))); + + connect(_ui.buttonBox, SIGNAL(accepted()), this, SLOT(dialogAccepted())); + connect(_ui.buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + validateGroupAddress(_ui.groupAddressEdit->text()); + validateDataPort(_ui.dataPortEdit->text()); +} + +MulticastConfigDialog::~MulticastConfigDialog() +{ +} + +void MulticastConfigDialog::dialogAccepted() +{ + QHostAddress addr; + bool addressParses = addr.setAddress(_ui.groupAddressEdit->text()); + _config->multicastAddress(_ui.groupAddressEdit->text()); + quint16 port = _ui.dataPortEdit->text().toInt(); + _config->multicastUDPPortBase(port); + _config->multicastDPort(port + 1); + _config->multicastSPort(port + 2); + _config->multicastRate(_ui.rateSpinbox->value() * 1024); + accept(); +} + +void MulticastConfigDialog::setError(QWidget* widget, + QLabel* errorMessageLabel, QString text) +{ + if (errorMessageLabel) + errorMessageLabel->setText(QString( + "<span style=\"font-weight: 600; color: #880000;\">") + text + + "</span>"); + if (widget) + widget->setStyleSheet("background-color: #ffcccc;"); +} + +void MulticastConfigDialog::setOK(QWidget* widget, QLabel* errorMessageLabel) +{ + if (errorMessageLabel) + errorMessageLabel->setText(QString( + "<span style=\"font-weight: 600; color: #008800;\">") + + tr("OK") + "</span>"); + if (widget) + widget->setStyleSheet("background-color: #ccffcc;"); +} + +void MulticastConfigDialog::validateGroupAddress(QString const& input) +{ + QHostAddress a; + + _isAddressValid = false; + + if (!a.setAddress(input)) + { + setError(_ui.groupAddressEdit, _ui.groupAddressMessage, tr( + "Not a valid IP Address")); + revalidateButtons(); + return; + } + + // check if it is IPv4 + if (a.protocol() != QAbstractSocket::IPv4Protocol) + { + setError(_ui.groupAddressEdit, _ui.groupAddressMessage, tr( + "Not a valid IPv4 Address")); + revalidateButtons(); + return; + } + + // check if it is a valid multicast address + quint32 addr = a.toIPv4Address(); + if ((addr & 0xf0000000) != 0xe0000000) + { + setError(_ui.groupAddressEdit, _ui.groupAddressMessage, tr( + "Not an IPv4 multicast address")); + revalidateButtons(); + return; + } + + _isAddressValid = true; + setOK(_ui.groupAddressEdit, _ui.groupAddressMessage); + revalidateButtons(); +} + +void MulticastConfigDialog::validateDataPort(QString const& input) +{ + bool ok; + int p = input.toInt(&ok, 0); + + _isPortValid = false; + + if (!ok) + { + setError(_ui.dataPortEdit, _ui.dataPortMessage, tr("Not a number")); + revalidateButtons(); + return; + } + + if (p < 0) + { + setError(_ui.dataPortEdit, _ui.dataPortMessage, tr("Must be positive")); + revalidateButtons(); + return; + } + + if (p < 1024) + { + setError(_ui.dataPortEdit, _ui.dataPortMessage, tr( + "Must not be a privileged port")); + revalidateButtons(); + return; + } + + if (p > 65535) + { + setError(_ui.dataPortEdit, _ui.dataPortMessage, tr( + "Port number too large")); + revalidateButtons(); + return; + } + + _isPortValid = true; + setOK(_ui.dataPortEdit, _ui.dataPortMessage); + revalidateButtons(); +} + +void MulticastConfigDialog::revalidateButtons() +{ + _ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(_isAddressValid + && _isPortValid); +} diff --git a/src/gui/multicastConfigDialog.h b/src/gui/multicastConfigDialog.h new file mode 100644 index 0000000..6421813 --- /dev/null +++ b/src/gui/multicastConfigDialog.h @@ -0,0 +1,38 @@ +#ifndef MULTICASTCONFIGDIALOG_H +#define MULTICASTCONFIGDIALOG_H + +#include <QtGui/QDialog> +#include <QPointer> +#include "ui_multicastConfigDialog.h" +#include <pvsinterface.h> + +class McastConfiguration; + +class MulticastConfigDialog : public QDialog +{ + Q_OBJECT + +public: + MulticastConfigDialog(QWidget* parent = 0); + MulticastConfigDialog(McastConfiguration* dbusIface, QWidget *parent = 0); + ~MulticastConfigDialog(); + +private: + Ui::MulticastConfigDialogClass _ui; + McastConfiguration* _config; + bool _isAddressValid; + bool _isPortValid; + + void setupUi(); + + void setError(QWidget* input, QLabel* messageLabel, QString text); + void setOK(QWidget* input, QLabel* messageLabel); + void revalidateButtons(); + +private slots: + void dialogAccepted(); + void validateGroupAddress(QString const&); + void validateDataPort(QString const&); +}; + +#endif // MULTICASTCONFIGDIALOG_H diff --git a/src/gui/ui/clientConfigDialog.ui b/src/gui/ui/clientConfigDialog.ui index 3262b6b..bb4bdc9 100644 --- a/src/gui/ui/clientConfigDialog.ui +++ b/src/gui/ui/clientConfigDialog.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>438</width> - <height>257</height> + <width>445</width> + <height>266</height> </rect> </property> <property name="windowTitle"> @@ -27,7 +27,7 @@ <enum>QTabWidget::North</enum> </property> <property name="currentIndex"> - <number>0</number> + <number>2</number> </property> <widget class="QWidget" name="tabPermissions"> <attribute name="title"> @@ -197,6 +197,64 @@ </item> </layout> </widget> + <widget class="QWidget" name="networkTab"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <attribute name="title"> + <string>Network</string> + </attribute> + <layout class="QFormLayout" name="formLayout"> + <item row="2" column="1"> + <widget class="QComboBox" name="interfaceList"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="editable"> + <bool>true</bool> + </property> + <property name="insertPolicy"> + <enum>QComboBox::NoInsert</enum> + </property> + <property name="frame"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Network Interface</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QPushButton" name="reloadInterfaceListButton"> + <property name="text"> + <string>Reload List</string> + </property> + </widget> + </item> + </layout> + </widget> </widget> </item> <item> diff --git a/src/gui/ui/clientFileReceiveDialog.ui b/src/gui/ui/clientFileReceiveDialog.ui index af3a135..a137def 100644 --- a/src/gui/ui/clientFileReceiveDialog.ui +++ b/src/gui/ui/clientFileReceiveDialog.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>208</width> - <height>108</height> + <width>528</width> + <height>117</height> </rect> </property> <property name="windowTitle"> @@ -61,6 +61,13 @@ </property> </spacer> </item> + <item> + <widget class="QLabel" name="labelStatus"> + <property name="text"> + <string/> + </property> + </widget> + </item> </layout> </item> <item> diff --git a/src/gui/ui/clientFileSendDialog.ui b/src/gui/ui/clientFileSendDialog.ui index d2d9c75..85462ba 100644 --- a/src/gui/ui/clientFileSendDialog.ui +++ b/src/gui/ui/clientFileSendDialog.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>186</width> - <height>108</height> + <width>528</width> + <height>144</height> </rect> </property> <property name="windowTitle"> @@ -33,7 +33,7 @@ </widget> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> + <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QLabel" name="label"> <property name="text"> @@ -61,10 +61,17 @@ </property> </spacer> </item> + <item> + <widget class="QLabel" name="labelStatus"> + <property name="text"> + <string/> + </property> + </widget> + </item> </layout> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <widget class="QLabel" name="labelA"> <property name="text"> diff --git a/src/gui/ui/clientNicklistDialog.ui b/src/gui/ui/clientNicklistDialog.ui index afd84f1..3679b55 100644 --- a/src/gui/ui/clientNicklistDialog.ui +++ b/src/gui/ui/clientNicklistDialog.ui @@ -29,6 +29,13 @@ <widget class="QListWidget" name="listWidget"/> </item> <item> + <widget class="QCheckBox" name="sendToAllCheckBox"> + <property name="text"> + <string>Send to &all</string> + </property> + </widget> + </item> + <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <spacer name="horizontalSpacer"> diff --git a/src/gui/ui/mainwindow.ui b/src/gui/ui/mainwindow.ui index 285d0f2..d444092 100644 --- a/src/gui/ui/mainwindow.ui +++ b/src/gui/ui/mainwindow.ui @@ -243,9 +243,16 @@ </property> <addaction name="actionAbout_pvs"/> </widget> + <widget class="QMenu" name="menuNetwork"> + <property name="title"> + <string>Network</string> + </property> + <addaction name="actionConfigure_Network"/> + </widget> <addaction name="menu_File"/> <addaction name="menuClients"/> <addaction name="menuLogging"/> + <addaction name="menuNetwork"/> <addaction name="menu_Help"/> </widget> <widget class="QStatusBar" name="statusBar"/> @@ -533,6 +540,14 @@ <string>-</string> </property> </action> + <action name="actionConfigure_Network"> + <property name="text"> + <string>&Configure...</string> + </property> + <property name="toolTip"> + <string>Configure Network Parameters</string> + </property> + </action> <action name="actionShowProcesses"> <property name="icon"> <iconset resource="../../../pvsmgr.qrc"> diff --git a/src/gui/ui/mainwindowtouch.ui b/src/gui/ui/mainwindowtouch.ui index ae1d0cf..ea6f01e 100644 --- a/src/gui/ui/mainwindowtouch.ui +++ b/src/gui/ui/mainwindowtouch.ui @@ -365,6 +365,8 @@ <addaction name="actionresetall"/> <addaction name="separator"/> <addaction name="actionDozent"/> + <addaction name="separator"/> + <addaction name="actionConfigure_Network"/> <addaction name="actionShowProcesses"/> <addaction name="actionStartProcess"/> </widget> @@ -638,6 +640,15 @@ <string>Ctrl+D</string> </property> </action> + <action name="actionConfigure_Network"> + <property name="icon"> + <iconset resource="../../../pvsmgr.qrc"> + <normaloff>:/netconf</normaloff>:/netconf</iconset> + </property> + <property name="text"> + <string>Configure Network...</string> + </property> + </action> <action name="actionShowProcesses"> <property name="icon"> <iconset resource="../../../pvsmgr.qrc"> diff --git a/src/gui/ui/multicastConfigDialog.ui b/src/gui/ui/multicastConfigDialog.ui new file mode 100644 index 0000000..1ddf02c --- /dev/null +++ b/src/gui/ui/multicastConfigDialog.ui @@ -0,0 +1,156 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MulticastConfigDialogClass</class> + <widget class="QDialog" name="MulticastConfigDialogClass"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>331</width> + <height>314</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>PVS - Multicast Configuration</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_3"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<table style="-qt-table-type: root; margin-top:4px; margin-bottom:4px; margin-left:4px; margin-right:4px;"> +<tr> +<td style="border: none;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">You need to specify connection parameters for multicast messaging on your network. These parameters will automatically be distributed to client computers, so you need to assign them only once.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">You will probably want to assign an address from the <span style=" text-decoration: underline;">239.0.0.0/8</span> &quot;Administratively Scoped&quot; range.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Do not assign arbitrary numbers without checking with your network administrator!</span></p></td></tr></table></body></html></string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Multicast Group Address</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="groupAddressEdit"> + <property name="inputMask"> + <string notr="true">009.009.009.009; </string> + </property> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="QLabel" name="groupAddressMessage"> + <property name="text"> + <string><span style=" font-weight:600; color:#008800;">OK</span></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Data Port (1024-65535)</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="dataPortEdit"> + <property name="inputMask"> + <string notr="true">00009; </string> + </property> + <property name="maxLength"> + <number>5</number> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QLabel" name="dataPortMessage"> + <property name="text"> + <string><span style=" font-weight:600; color:#008800;">OK</span></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Transmission Rate</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QSpinBox" name="rateSpinbox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="suffix"> + <string> KiB/s</string> + </property> + <property name="maximum"> + <number>10240</number> + </property> + <property name="singleStep"> + <number>10</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <layoutdefault spacing="6" margin="11"/> + <resources/> + <connections/> +</ui> diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt new file mode 100644 index 0000000..20fd531 --- /dev/null +++ b/src/input/CMakeLists.txt @@ -0,0 +1,114 @@ +include(${QT_USE_FILE}) + +set(pvsinput_SRCS + inputEvent.cpp + ) + +if(UNIX) + find_file(XINPUT2_HDR X11/extensions/XInput2.h) + if(XINPUT2_HDR) + set_property(SOURCE x11FakeKeyboardHandler.cpp + APPEND + PROPERTY COMPILE_DEFINITIONS HAVE_XINPUT2_H + ) + endif() + + set(pvsprivinputd_SRCS + pvsprivinputd.cpp + pvsPrivInputHandler.cpp + pvsCheckPrivileges.cpp + pvsSyslog.cpp + pvsPrivInputSignalHandler.cpp + rebootSystemHandler.cpp + killX11Handler.cpp + magicSysRqHandler.cpp + sayHelloHandler.cpp + inputEventHandler.cpp + ) + + set(pvsprivinputd_MOC_HDRS + pvsPrivInputHandler.h + pvsCheckPrivileges.h + pvsSyslog.h + pvsPrivInputSignalHandler.h + ) + + qt4_wrap_cpp(pvsprivinputd_MOC_SRCS + ${pvsprivinputd_MOC_HDRS} + ) + + add_executable(pvsprivinputd + ${pvsprivinputd_SRCS} + ${pvsprivinputd_MOC_SRCS} + ) + + set_property(SOURCE ${pvsprivinputd_SRCS} ${pvsprivinputd_MOC_SRCS} + APPEND + PROPERTY COMPILE_FLAGS " -I${QT_QTCORE_INCLUDE_DIR} -I${QT_QTDBUS_INCLUDE_DIR} -I${QT_QTNETWORK_INCLUDE_DIR}") + + target_link_libraries(pvsprivinputd + pvsinput + ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${QT_QTNETWORK_LIBRARY} + ) + + list(APPEND pvsinput_SRCS + pvsPrivInputSocket.cpp + x11InputUtils.cpp + x11FakeKeyboardHandler.cpp + x11FakeMouseHandler.cpp + privilegedHandlerForwarder.cpp + ) + + # we need pkg-config to find out where to install the action file: + find_package(PkgConfig) + if(NOT PKG_CONFIG_FOUND) + # we will try to make a best effort and put our policy file into + # ${PREFIX}/share/polkit-1, but only if we can find + # the pkexec executable. + find_program(PKEXEC_LOCATION pkexec) + if(PKEXEC_LOCATION OR ENABLE_POLKIT) + set(POLKIT_FOUND ON) + set(POLKIT_PREFIX ${CMAKE_INSTALL_PREFIX}) + set(POLKIT_POLICY_DIR ${POLKIT_PREFIX}/share/polkit-1/actions) + endif() + else() + pkg_check_modules(POLKIT "polkit-gobject-1") + if(POLKIT_FOUND) + execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} polkit-gobject-1 --variable=policydir + OUTPUT_VARIABLE POLKIT_POLICY_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT POLKIT_POLICY_DIR) + set(POLKIT_FOUND OFF) + endif() + endif() + endif() + + # now, arrange for policykit integration: + if(POLKIT_FOUND OR ENABLE_POLKIT) + if(NOT POLKIT_POLICY_DIR) + message(SEND_ERROR "PolicyKit integration is enabled, but cannot find PolicyKit's actions directory. Please set POLKIT_POLICY_DIR to the right value") + endif() + + install(FILES org.openslx.pvs.input.policy + DESTINATION ${POLKIT_POLICY_DIR}) + set_property(SOURCE ${pvsprivinputd_SRCS} + APPEND + PROPERTY COMPILE_DEFINITIONS HAVE_POLKIT) + message(STATUS "PolicyKit integration: enabled") + else() + message(STATUS "PolicyKit integration: disabled") + endif() + + # Install a pvsprivinputd.conf template + install(FILES pvsprivinputd.conf + DESTINATION /etc + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) +endif() + +add_library( + pvsinput + STATIC + ${pvsinput_SRCS} +) diff --git a/src/input/inputEvent.cpp b/src/input/inputEvent.cpp new file mode 100644 index 0000000..9b69a3a --- /dev/null +++ b/src/input/inputEvent.cpp @@ -0,0 +1,99 @@ +/* + * inputEvent.cpp + * + * Created on: 06.09.2010 + * Author: brs + */ + +#include <QBuffer> +#include <QByteArray> +#include <QDataStream> +#include <QKeyEvent> +#include <QMouseEvent> +#include <QString> +#include "inputEvent.h" +#include <src/util/consoleLogger.h> + +// We implement operators to serialize and load an event: +QDataStream& operator <<(QDataStream& ostrm, InputEvent const& evt) +{ + ostrm << evt.type_ << evt.code_ << evt.value_; + return ostrm; +} + +QDataStream& operator >>(QDataStream& istrm, InputEvent& evt) +{ + istrm >> evt.type_ >> evt.code_ >> evt.value_; + return istrm; +} + +void eventToString(InputEvent const& evt, QString& str) +{ + QByteArray ba; + QBuffer buf(&ba); + buf.open(QIODevice::WriteOnly); + QDataStream out(&buf); + out << evt; + str = QString::fromAscii(ba.toBase64()); +} + +bool eventFromString(QString const& str, InputEvent& evt) +{ + // TODO This does not do proper error checking. Only use from trusted sources! + QByteArray ba = QByteArray::fromBase64(str.toAscii()); + QBuffer buf(&ba); + buf.open(QIODevice::ReadOnly); + QDataStream in(&buf); + in >> evt; + return true; +} + +quint16 InputEvent::mouseButtonsFromQt(int b) +{ + quint16 ret = 0; + if(b & Qt::LeftButton) + { + ret |= EB_LEFT; + } + if(b & Qt::RightButton) + { + ret |= EB_RIGHT; + } + if(b & Qt::MidButton) + { + ret |= EB_MIDDLE; + } + return ret; +} + +InputEvent InputEvent::mousePressRelease(int qt_button, int qt_buttons) +{ + quint16 button = mouseButtonsFromQt(qt_button); + quint16 buttons = mouseButtonsFromQt(qt_buttons); + quint16 code; + + if(buttons & button) + { + code = EC_PRESS; + } + else + { + code = EC_RELEASE; + } + + quint32 value = ((quint32)button << 16) | buttons; + return InputEvent(ET_BUTTON, code, value); +} + +InputEvent InputEvent::keyboardPress(int key, int mods) +{ + quint32 value = key | mods; + return InputEvent(ET_KEY, EC_PRESS, value); +} + +InputEvent InputEvent::keyboardRelease(int key, int mods) +{ + quint32 value = key | mods; + return InputEvent(ET_KEY, EC_RELEASE, value); +} + diff --git a/src/input/inputEvent.h b/src/input/inputEvent.h new file mode 100644 index 0000000..7a64bfc --- /dev/null +++ b/src/input/inputEvent.h @@ -0,0 +1,219 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # inputEvent.h: + # - Definition of an input event + # -------------------------------------------------------------------------- + */ + +#ifndef INPUTEVENT_H_ +#define INPUTEVENT_H_ + +#include <cassert> +#include <string> +#include <sstream> +#include <stdint.h> + +#ifndef __linux +# error "This will only run on a Linux system. Porting is required for other systems." +#endif + +struct QDataStream; +struct QString; +struct QMouseEvent; +struct QKeyEvent; + +class InputEvent +{ +private: + friend QDataStream& operator<<(QDataStream&, InputEvent const&); + friend QDataStream& operator>>(QDataStream&, InputEvent&); + + friend void eventToString(InputEvent const& evt, QString& str); + friend bool eventFromString(QString const& str, InputEvent& evt); + + uint16_t type_; + uint16_t code_; + uint32_t value_; + + // InputEvents are immutable. Prohibit assignment: + InputEvent& operator=(InputEvent const&); // There intentionally is no implementation. +public: + InputEvent(uint16_t type, uint16_t code, uint32_t value) : type_(type), code_(code), value_(value) + { + } + + InputEvent() + { + } + + static InputEvent mouseMotion(uint16_t x, uint16_t y) + { + return InputEvent(ET_POINTER, 0, ((uint32_t)x << 16) | y); + } + + static uint16_t mouseButtonsFromQt(int b); + + static InputEvent mousePressRelease(int button, int buttons); + static InputEvent keyboardPress(int key, int mods); + static InputEvent keyboardRelease(int key, int mods); + + static const uint16_t ET_KEY = 0; + static const uint16_t ET_BUTTON = 1; + static const uint16_t ET_POINTER = 2; + static const uint16_t ET_SPECIAL = 3; + + static const uint16_t EC_PRESS = 0; + static const uint16_t EC_RELEASE = 1; + static const uint16_t EC_REBOOT = 2; + static const uint16_t EC_SYSRQ = 3; + static const uint16_t EC_KILL_X = 4; + static const uint16_t EC_SAY_HELLO = 5; //< for debugging purposes + + typedef uint32_t event_key; + + typedef uint32_t event_key_modifiers; + + static const uint16_t EB_LEFT = 1; + static const uint16_t EB_MIDDLE = 2; + static const uint16_t EB_RIGHT = 4; + + static const uint32_t MODIFIER_MASK = + 0x7e000000; + + uint16_t type() const + { + return type_; + } + + uint16_t code() const + { + return code_; + } + + uint32_t value() const + { + return value_; + } + + bool isKeyboard() const + { + return type_ == ET_KEY; + } + + bool isButton() const + { + return type_ == ET_BUTTON; + } + + bool isPointer() const + { + return type_ == ET_POINTER; + } + + bool isSpecial() const + { + return type_ == ET_SPECIAL; + } + + bool isPress() const + { + return code_ == EC_PRESS; + } + + bool isRelease() const + { + return code_ == EC_RELEASE; + } + + uint16_t pressedButton() const + { + assert(type_ == ET_BUTTON); + return (value_ >> 16); + } + + uint16_t heldButtons() const + { + assert(type_ == ET_BUTTON); + return (value_ & 0xffff); + } + + uint16_t xCoord() const + { + assert(type_ == ET_POINTER); + return (value_ >> 16); + } + + uint16_t yCoord() const + { + assert(type_ == ET_POINTER); + return (value_ & 0xffff); + } + + static std::string typeToString(uint16_t type) + { + switch(type) + { + case ET_BUTTON: + return "BUTTON"; + case ET_KEY: + return "KEY"; + case ET_POINTER: + return "POINTER"; + case ET_SPECIAL: + return "SPECIAL"; + default: + std::ostringstream s; + s << std::hex << type; + return s.str(); + } + } + + static std::string codeToString(uint16_t code) + { + switch(code) + { + case EC_PRESS: + return "PRESS"; + case EC_RELEASE: + return "RELEASE"; + case EC_SYSRQ: + return "SYSRQ"; + case EC_REBOOT: + return "REBOOT"; + case EC_KILL_X: + return "KILL_X"; + default: + std::ostringstream s; + s << std::hex << code; + return s.str(); + } + } + + std::string toString() const + { + std::ostringstream s; + s << typeToString(type_) << ':' << codeToString(code_) << ':' << std::hex << value_; + return s.str(); + } + + uint32_t qt_keysym() const + { + return value_ & ~MODIFIER_MASK; + } + + uint32_t qt_modifiers() const + { + return value_ & MODIFIER_MASK; + } +}; + +#endif /* INPUTEVENT_H_ */ diff --git a/src/input/inputEventHandler.cpp b/src/input/inputEventHandler.cpp new file mode 100644 index 0000000..c16c358 --- /dev/null +++ b/src/input/inputEventHandler.cpp @@ -0,0 +1,36 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # inputEventHandler.h: + # - Common definitions for input event handlers - implementation + # -------------------------------------------------------------------------- + */ + +#include "inputEventHandler.h" +#include "pvsCheckPrivileges.h" + +bool policy::allowPrivilegedUser(InputEvent const& evt, InputEventContext const* ctx) +{ + if(ctx) + return PVSCheckPrivileges::instance()->require(PVSCheckPrivileges::SESSION_UNKNOWN, PVSCheckPrivileges::USER_PRIVILEGED, + ctx); + else + return false; +} + +bool policy::allowPhysicalSeat(InputEvent const& evt, InputEventContext const* ctx) +{ + if(ctx) + return PVSCheckPrivileges::instance()->require(PVSCheckPrivileges::SESSION_LOCAL, PVSCheckPrivileges::USER_UNKNOWN, + ctx); + else + return false; +} diff --git a/src/input/inputEventHandler.h b/src/input/inputEventHandler.h new file mode 100644 index 0000000..52e3338 --- /dev/null +++ b/src/input/inputEventHandler.h @@ -0,0 +1,295 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # inputEventHandler.h: + # - Common definitions for input event handlers + # -------------------------------------------------------------------------- + */ + +#ifndef INPUTEVENTHANDLER_H_ +#define INPUTEVENTHANDLER_H_ + +#include <QtGlobal> +#include <QtDebug> +#include <QList> +#include <QString> +#include <QCoreApplication> +#include <boost/mpl/contains.hpp> +#include <boost/mpl/if.hpp> +#include <boost/mpl/vector.hpp> +#include <src/input/inputEvent.h> + +#define HANDLER_TYPE_DONT_CARE 0xffff +#define HANDLER_CODE_DONT_CARE 0xffff +#define HANDLER_VALUE_DONT_CARE 0xffffffff + +class InputEventContext +{ +public: + virtual pid_t getSenderPid() const = 0; + virtual uid_t getSenderUid() const = 0; + virtual gid_t getSenderGid() const = 0; +}; + +struct SpecialInputEventDescription +{ + SpecialInputEventDescription(QString const& d, quint16 t, quint16 c, quint32 v = 0) + : descriptionString(d), evtType(t), evtCode(c), evtValue(v) + { + } + + QString descriptionString; + quint16 evtType; + quint16 evtCode; + quint32 evtValue; + + InputEvent toEvent() const + { + return InputEvent(evtType, evtCode, evtValue); + } +}; + +template<quint16 Type = HANDLER_TYPE_DONT_CARE, + quint16 Code = HANDLER_CODE_DONT_CARE, + quint32 Value = HANDLER_VALUE_DONT_CARE> +class DefaultInputEventHandler { +protected: + static QString tr(char const* string) + { + return QCoreApplication::translate("InputEventHandler", string); + } + +public: + virtual bool matches(InputEvent const& evt, InputEventContext const*) { + if(Type != HANDLER_TYPE_DONT_CARE) { + if(evt.type() != Type) + return false; + } + if(Code != HANDLER_CODE_DONT_CARE) { + if(evt.code() != Code) + return false; + } + if(Value != HANDLER_VALUE_DONT_CARE) { + if(evt.value() != Value) + return false; + } + return true; + } + + virtual void initialize() + { + } + + virtual void handle(InputEvent const& evt, InputEventContext const*) = 0; + + static void describeInto(QList<SpecialInputEventDescription>& description) + { + } +}; + +namespace policy { + +enum SecurityFlags { + SEC_FREE_FOR_ALL, + SEC_PHYSICAL_OR_PRIVILEGED +}; + +bool allowPhysicalSeat(InputEvent const& evt, InputEventContext const* ctx); +bool allowPrivilegedUser(InputEvent const& evt, InputEventContext const* ctx); + +struct SecurityAllowAny +{ + bool allow(InputEvent const& evt, InputEventContext const* ctx) + { + return true; + } +}; + +struct SecurityAllowPhysicalOrPrivileged +{ + bool allow(InputEvent const& evt, InputEventContext const* ctx) + { + if(allowPhysicalSeat(evt, ctx)) + return true; + else if(allowPrivilegedUser(evt, ctx)) + return true; + return false; + } +}; + +struct UnixLike; +struct Linux; +struct Windows; + +#if defined(__linux) +typedef boost::mpl::vector2<UnixLike,Linux>::type Systems; +#elif defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) +typedef boost::mpl::vector1<Windows>::type Systems; +#else +# error "Porting is needed!" +#endif + +struct SystemEnabled; +struct SystemDisabled; + +template<typename System> +struct RequireSystem +{ + typedef typename boost::mpl::contains<Systems, System>::type enabled_type; + static const bool enabled = enabled_type::value; +}; + +struct RequireNoSystem +{ + typedef boost::mpl::bool_<true>::type enabled_type; + static const bool enabled = enabled_type::value; +}; + +} + +template<bool Enabled, typename Delegate, typename SecurityPolicy> +class HandlerHelper +{ +public: + bool handle(InputEvent const& evt, InputEventContext const* context = 0) { + if(!securityPolicy.allow(evt, context)) + { + std::string evtStr = evt.toString(); + qWarning("Input Event %s has been denied by security policy", evtStr.c_str()); + return true; + } + if(delegate.matches(evt, context)) { + delegate.handle(evt, context); + return true; + } else { + return false; + } + } + + void initialize() + { + delegate.initialize(); + } + + static void describeInto(QList<SpecialInputEventDescription>& list) + { + Delegate::describeInto(list); + } + +private: + Delegate delegate; + SecurityPolicy securityPolicy; +}; + +template<typename Delegate, typename SecurityPolicy> +class HandlerHelper<false, Delegate, SecurityPolicy> +{ +public: + bool handle(InputEvent const& evt, InputEventContext const* context = 0) { + return false; + } + + void initialize() + { + } + + static void describeInto(QList<SpecialInputEventDescription>&) + { + } +}; + +template<typename Delegate, typename SystemPolicy = policy::RequireNoSystem, typename SecurityPolicy = void> +struct Handler : public HandlerHelper<SystemPolicy::enabled, Delegate, SecurityPolicy> +{ +}; + +template<typename DefaultSecurityPolicy, typename HandlerType> +struct ApplyDefaultSecurityPolicy +{ + typedef HandlerType type; +}; + +template<typename DefaultSecurityPolicy, typename Delegate, typename SystemPolicy> +struct ApplyDefaultSecurityPolicy<DefaultSecurityPolicy, Handler<Delegate, SystemPolicy, void> > +{ + typedef Handler<Delegate, SystemPolicy, DefaultSecurityPolicy> type; +}; + +template<typename DefaultSecurityPolicy, typename Begin, typename End> +struct InputEventHandlerChainHelper +{ +private: + typedef typename boost::mpl::next<Begin>::type next_iterator_type; + typedef InputEventHandlerChainHelper<DefaultSecurityPolicy, next_iterator_type, End> next_in_chain; + + typedef typename boost::mpl::deref<Begin>::type handler_entry_type; + typedef typename ApplyDefaultSecurityPolicy<DefaultSecurityPolicy, handler_entry_type>::type handler_type; + + handler_type _handler; + next_in_chain _next; + +public: + void handle(InputEvent const& evt, InputEventContext const* context = 0) { + if(!_handler.handle(evt, context)) { + _next.handle(evt, context); + } + } + + void initialize() { + _handler.initialize(); + _next.initialize(); + } + + static void describeInto(QList<SpecialInputEventDescription>& list) + { + handler_type::describeInto(list); + next_in_chain::describeInto(list); + } + + static QList<SpecialInputEventDescription> describe() + { + QList<SpecialInputEventDescription> list; + describeInto(list); + return list; + } +}; + +template<typename DefaultSecurityPolicy, typename End> +struct InputEventHandlerChainHelper<DefaultSecurityPolicy, End, End> +{ + void handle(InputEvent const&, InputEventContext const* context = 0) { + // do nothing + } + + void initialize() { + // do nothing + } + + static void describeInto(QList<SpecialInputEventDescription>&) + { + // do nothing + } + + static QList<SpecialInputEventDescription> describe() + { + return QList<SpecialInputEventDescription>(); + } +}; + +template<typename DefaultSecurityPolicy, typename Collection> +struct InputEventHandlerChain : + public InputEventHandlerChainHelper<DefaultSecurityPolicy, + typename boost::mpl::begin<Collection>::type, + typename boost::mpl::end<Collection>::type> +{ +}; + +#endif /* INPUTEVENTHANDLER_H_ */ diff --git a/src/input/inputEventNonQt.cpp b/src/input/inputEventNonQt.cpp new file mode 100644 index 0000000..a7455fd --- /dev/null +++ b/src/input/inputEventNonQt.cpp @@ -0,0 +1,17 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # inputEvent.h: + # - Input Event routines that need not be linked against Qt + # -------------------------------------------------------------------------- + */ + + diff --git a/src/input/inputHandlerChain.h b/src/input/inputHandlerChain.h new file mode 100644 index 0000000..3c9446c --- /dev/null +++ b/src/input/inputHandlerChain.h @@ -0,0 +1,49 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # inputHandlerChain.h: + # - Definition of the input handler chain + # -------------------------------------------------------------------------- + */ + +#ifndef INPUTHANDLERCHAIN_H_ +#define INPUTHANDLERCHAIN_H_ + +#include <boost/mpl/list.hpp> +#include "inputEventHandler.h" + +#include "x11FakeKeyboardHandler.h" +#include "x11FakeMouseHandler.h" +#include "privilegedHandlerForwarder.h" +#include "rebootSystemHandler.h" +#include "sayHelloHandler.h" +#include "killX11Handler.h" +#include "magicSysRqHandler.h" + +typedef boost::mpl::list< + Handler<X11FakeKeyboardHandler, policy::RequireSystem<policy::UnixLike> >, + Handler<X11FakeMouseButtonHandler, policy::RequireSystem<policy::UnixLike> >, + Handler<X11FakeMouseMovementHandler, policy::RequireSystem<policy::UnixLike> >, + Handler<PrivilegedHandlerForwarder> +>::type unprivileged_handler_list; + +typedef InputEventHandlerChain<policy::SecurityAllowAny, unprivileged_handler_list> unprivileged_handler_chain; + +typedef boost::mpl::list< + Handler<SayHelloHandler, policy::RequireNoSystem, policy::SecurityAllowAny >, + Handler<KillX11Handler, policy::RequireSystem<policy::Linux> >, + Handler<RebootLinuxSystemHandler, policy::RequireSystem<policy::Linux> >, + Handler<MagicSysRqHandler, policy::RequireSystem<policy::Linux> > +>::type privileged_handler_list; + +typedef InputEventHandlerChain<policy::SecurityAllowPhysicalOrPrivileged, privileged_handler_list> privileged_handler_chain; + +#endif /* INPUTHANDLERCHAIN_H_ */ diff --git a/src/input/killX11Handler.cpp b/src/input/killX11Handler.cpp new file mode 100644 index 0000000..7ac75a1 --- /dev/null +++ b/src/input/killX11Handler.cpp @@ -0,0 +1,89 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # killX11Handler.h + # - Kill the X11 Server - implementation + # -------------------------------------------------------------------------- + */ + +#include <sys/types.h> +#include <signal.h> +#include <cerrno> +#include <QDir> +#include <QFileInfo> +#include <QStringList> +#include "pvsCheckPrivileges.h" +#include "killX11Handler.h" + +using namespace std; + +void KillX11Handler::handle(InputEvent const&, InputEventContext const* ctx) +{ + // Find out which display device is used: + QString displayDevice = PVSCheckPrivileges::instance()->getX11DisplayDevice(ctx); + QString displayDeviceAbs = QFileInfo(displayDevice).canonicalFilePath(); + + if(displayDevice.isNull()) + { + qWarning("Can not kill X server for %d,%d,%d: No Display Device", ctx->getSenderPid(), ctx->getSenderUid(), ctx->getSenderGid()); + return; + } + + QList<pid_t> pids; + + // Find all processes that have it opened: + QDir proc("/proc"); + QFileInfoList entries = proc.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + foreach(QFileInfo fi, entries) + { + // We are only interested in numerical ids: + bool ok; + pid_t pid = fi.fileName().toUInt(&ok); + if(!ok) + continue; + + // We have a pid. Now check open files: + QDir fds(QString("/proc/%1/fd").arg(pid)); + qDebug("Searching for X server in %s", fds.absolutePath().toLocal8Bit().constData()); + QFileInfoList fdfiles = fds.entryInfoList(QDir::Files); + foreach(QFileInfo fdfile, fdfiles) + { + qDebug(" Looking for terminal in %s", fdfile.absoluteFilePath().toLocal8Bit().constData()); + QFileInfo fdTarget(fdfile.readLink()); + if(fdTarget.canonicalFilePath() == displayDeviceAbs) + { + qDebug(" ... found"); + pids << pid; + } + } + } + + // If everything is well, we have exactly one pid: + if(pids.size() != 1) + { + QStringList pidStrs; + foreach(pid_t pid, pids) + { + pidStrs << QString::number(pid); + } + + qWarning("Display device %s is open by multiple or zero processes (%s). We don't know which is X. Aborting.", + displayDeviceAbs.toLocal8Bit().constData(), + pidStrs.join(", ").toLocal8Bit().constData()); + return; + } + + // We found the PID for the X server. Now kill it. + QString exe = QFileInfo(QString("/proc/%1/exe").arg(pids[0])).readLink(); + qDebug("Killing X server, PID %d, exe name %s with SIGTERM", pids[0], exe.toLocal8Bit().constData()); + +// kill(pids[0], SIGTERM); +} diff --git a/src/input/killX11Handler.h b/src/input/killX11Handler.h new file mode 100644 index 0000000..2f3ef44 --- /dev/null +++ b/src/input/killX11Handler.h @@ -0,0 +1,34 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # killX11Handler.h + # - Kill the X11 Server - interface + # -------------------------------------------------------------------------- + */ + +#ifndef KILLX11HANDLER_H_ +#define KILLX11HANDLER_H_ + +#include <QCoreApplication> +#include "inputEventHandler.h" + +class KillX11Handler : public DefaultInputEventHandler<InputEvent::ET_SPECIAL, InputEvent::EC_KILL_X> +{ +public: + void handle(InputEvent const&, InputEventContext const*); + + static void describeInto(QList<SpecialInputEventDescription>& list) + { + list << SpecialInputEventDescription(tr(QT_TRANSLATE_NOOP("InputEventHandler", "Kill X Server")), InputEvent::ET_SPECIAL, InputEvent::EC_KILL_X); + } +}; + +#endif /* KILLX11HANDLER_H_ */ diff --git a/src/input/magicSysRqHandler.cpp b/src/input/magicSysRqHandler.cpp new file mode 100644 index 0000000..108bfca --- /dev/null +++ b/src/input/magicSysRqHandler.cpp @@ -0,0 +1,28 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # inputEventHandler.h: + # - Common definitions for input event handlers + # -------------------------------------------------------------------------- + */ + +#include <QFile> +#include "magicSysRqHandler.h" + +void MagicSysRqHandler::handle(InputEvent const& evt, InputEventContext const*) +{ + QFile trigger("/proc/sysrq-trigger"); + trigger.open(QIODevice::WriteOnly); + char c = (char)(evt.value() & 0xff); + trigger.write(&c, 1); + trigger.flush(); + trigger.close(); +} diff --git a/src/input/magicSysRqHandler.h b/src/input/magicSysRqHandler.h new file mode 100644 index 0000000..563d091 --- /dev/null +++ b/src/input/magicSysRqHandler.h @@ -0,0 +1,72 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # magicSysRqHandler.h + # - Trigger Magic-SysRq functions - interface + # -------------------------------------------------------------------------- + */ + +#ifndef MAGICSYSRQHANDLER_H_ +#define MAGICSYSRQHANDLER_H_ + +#include "inputEventHandler.h" + +class MagicSysRqHandler : public DefaultInputEventHandler<InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ> +{ +public: + void handle(InputEvent const& evt, InputEventContext const* ctx); + + static void describeInto(QList<SpecialInputEventDescription>& list) + { + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Reboot immediately"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'b'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Crash system"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'c'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Show all held logs"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'd'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Send SIGTERM to all"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'e'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Activate OOM killer"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'f'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Send SIGKILL to all"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'i'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Force thaw filesystems"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'j'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Kill all on terminal"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'k'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Show stack traces"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'l'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Dump memory info"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'm'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Make real-time tasks niceable"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'n'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Power off immediately"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'o'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Dump registers and flags"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'p'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Dump timers and clockevents"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'q'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Turn off raw keyboard mode"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'r'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Sync all mounted filesystems"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 's'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Dump task list"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 't'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Remount all read-only"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'u'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Dump uninterruptible tasks"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'w'); + list << SpecialInputEventDescription(QCoreApplication::translate("InputEventHandler", "Dump ftrace buffer"), + InputEvent::ET_SPECIAL, InputEvent::EC_SYSRQ, 'z'); + } +}; + +#endif /* MAGICSYSRQHANDLER_H_ */ diff --git a/src/input/org.openslx.pvs.input.policy b/src/input/org.openslx.pvs.input.policy new file mode 100644 index 0000000..f0de9f2 --- /dev/null +++ b/src/input/org.openslx.pvs.input.policy @@ -0,0 +1,18 @@ +<?xml version="1.0" ?> +<!DOCTYPE policyconfig PUBLIC + "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" + "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd"> +<policyconfig> + <vendor>The OpenSLX project</vendor> + <vendor_url>http://lab.openslx.org/pvs</vendor_url> + + <action id="org.openslx.pvs.privilegedinput"> + <description>Use privileged input actions in PVS</description> + <message>Authentication is required to let PVS use privileged input actions</message> + <defaults> + <allow_inactive>auth_self_keep</allow_inactive> + <allow_active>auth_self_keep</allow_active> + <allow_any>auth_admin_keep</allow_any> + </defaults> + </action> +</policyconfig> diff --git a/src/input/privilegedHandlerForwarder.cpp b/src/input/privilegedHandlerForwarder.cpp new file mode 100644 index 0000000..3c0b3bd --- /dev/null +++ b/src/input/privilegedHandlerForwarder.cpp @@ -0,0 +1,82 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # inputHandlerChain.cpp: + # - Forward privileged input events to a special handler process - implementation + # -------------------------------------------------------------------------- + */ + +#include <sys/socket.h> +#include <sys/un.h> +#include <cassert> +#include <cerrno> +#include <cstring> +#include <string> +#include <QSettings> +#include <QtDebug> +#include "pvsPrivInputSocket.h" +#include "privilegedHandlerForwarder.h" + +using namespace std; + +#ifndef UNIX_PATH_MAX +# define UNIX_PATH_MAX 108 /* according to unix(7) */ +#endif + +void PrivilegedHandlerForwarder::initialize() +{ + QSettings settings(QSettings::NativeFormat, QSettings::SystemScope, "openslx", "pvsinputd"); + + QString defaultPath = "/tmp/pvsprivinputd.sock"; + QByteArray socketPath = settings.value("socketpath", defaultPath).toString().toLocal8Bit(); + + _socket = pvsPrivInputMakeClientSocket(); + if(_socket < 0) + { + return; + } +} + +void PrivilegedHandlerForwarder::handle(InputEvent const& evt, InputEventContext const*) +{ + qDebug("Trying to handle %s in PrivilegedHandlerForwarder", evt.toString().c_str()); + if(_socket < 0) + { + initialize(); + } + + QByteArray data; + QDataStream strm(&data, QIODevice::WriteOnly); + strm.setByteOrder(QDataStream::BigEndian); + strm << evt; + + assert(data.size() == 8); + + int delta = 0; + int count = 0; + do { + delta = write(_socket, data.constData(), data.size()); + if(delta < 0) + { + qWarning("Error while communicating with pvsprivinputd: %s", strerror(errno)); + close(_socket); + + // Try again: + initialize(); + } + else if(delta != 8) + { + // This should normally not happen. + qWarning("Could not send a complete packet. Only %d bytes sent", delta); + } + count++; + } while(delta != 8 && count < 3); +} diff --git a/src/input/privilegedHandlerForwarder.h b/src/input/privilegedHandlerForwarder.h new file mode 100644 index 0000000..37e8f24 --- /dev/null +++ b/src/input/privilegedHandlerForwarder.h @@ -0,0 +1,32 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # inputHandlerChain.h: + # - Forward privileged input events to a special handler process. + # -------------------------------------------------------------------------- + */ + +#ifndef PRIVILEGEDHANDLERFORWARDER_H_ +#define PRIVILEGEDHANDLERFORWARDER_H_ + +#include "inputEventHandler.h" + +class PrivilegedHandlerForwarder : public DefaultInputEventHandler<InputEvent::ET_SPECIAL> +{ +public: + void handle(InputEvent const& evt, InputEventContext const* = 0); + void initialize(); + +private: + int _socket; +}; + +#endif /* PRIVILEGEDHANDLERFORWARDER_H_ */ diff --git a/src/input/pvsCheckPrivileges.cpp b/src/input/pvsCheckPrivileges.cpp new file mode 100644 index 0000000..2026c0a --- /dev/null +++ b/src/input/pvsCheckPrivileges.cpp @@ -0,0 +1,550 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # pvsCheckPrivileges.cpp: + # - A small program that checks whether or not it is called from + # a physical seat and conditionally executes the pvs input daemon. + # Additional security-relevant conditions should be checked here. + # + # The program is called with exactly one parameter, specifying the + # number of the file descriptor which is to be passed to its child. + # -------------------------------------------------------------------------- + */ + +#include <sys/types.h> +#include <pwd.h> +#include <grp.h> +#include <unistd.h> +#include <iostream> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <QCoreApplication> +#include <QDir> +#include <QFileInfo> +#include <QFileSystemWatcher> +#include <QSettings> +#include <QTimer> +#include <QtDBus/QDBusArgument> +#include <QtDBus/QDBusConnection> +#include <QtDBus/QDBusInterface> +#include <QtDBus/QDBusMetaType> +#include <QtDBus/QDBusReply> +#include <QtGlobal> +#include <QDebug> +#include <QUuid> +#include "pvsPrivInputSocket.h" +#include "pvsCheckPrivileges.h" + +using namespace std; + +#define TIMEOUT_VALUE 10000 /* Wait for max. 10 seconds */ + +// We need these classes for PolicyKit access: +struct PolKitSubject { + QString subject_kind; + QMap<QString, QVariant> subject_details; +}; +Q_DECLARE_METATYPE(PolKitSubject); + +QDBusArgument& operator<<(QDBusArgument& arg, PolKitSubject const& subj) +{ + arg.beginStructure(); + arg << subj.subject_kind << subj.subject_details; + arg.endStructure(); + return arg; +} + +QDBusArgument const& operator>>(QDBusArgument const& arg, PolKitSubject& subj) +{ + arg.beginStructure(); + arg >> subj.subject_kind >> subj.subject_details; + arg.endStructure(); + return arg; +} + +struct PolKitAuthReply { + bool isAuthorized; + bool isChallenge; + QMap<QString, QString> details; +}; +Q_DECLARE_METATYPE(PolKitAuthReply); + +QDBusArgument& operator<<(QDBusArgument& arg, PolKitAuthReply const& reply) +{ + arg.beginStructure(); + arg << reply.isAuthorized << reply.isChallenge << reply.details; + arg.endStructure(); + return arg; +} + +QDBusArgument const& operator>>(QDBusArgument const& arg, PolKitAuthReply& reply) +{ + arg.beginStructure(); + arg >> reply.isAuthorized >> reply.isChallenge >> reply.details; + arg.endStructure(); + return arg; +} + +// We need to pass QMap<QString, QString> to QVariant: +typedef QMap<QString, QString> QStringStringMap; +Q_DECLARE_METATYPE(QStringStringMap) + +PVSCheckPrivileges* PVSCheckPrivileges::instance() +{ + static PVSCheckPrivileges* static_instance = 0; + if(!static_instance) + { + static_instance = new PVSCheckPrivileges(); + } + return static_instance; +} + +void PVSCheckPrivileges::deleteInstance() +{ + delete instance(); +} + +PVSCheckPrivileges::PVSCheckPrivileges(QObject* parent) + : QObject(parent) +{ + // initialise our cache: + updateCachedUserDatabase(); + rereadConfiguration(); + + // and make it update itself: + QStringList paths; + paths << "/etc/passwd" << "/etc/group" << pvsPrivInputGetSettingsPath(); + _watcher = new QFileSystemWatcher(paths, this); + connect(_watcher, SIGNAL(fileChanged(QString const&)), this, SLOT(updateCachedUserDatabase())); + connect(_watcher, SIGNAL(fileChanged(QString const&)), this, SLOT(rereadConfiguration())); +} + +PVSCheckPrivileges::~PVSCheckPrivileges() +{ +} + +QString PVSCheckPrivileges::getSessionReference(CachedInputContext const& sender) +{ + if(!sender.isValid()) + { + return QString(); + } + + QString sessionReference = _savedConsoleKitSession.value(sender, QString()); + if(sessionReference.isNull()) + { + QDBusConnection conn = QDBusConnection::systemBus(); + // Find the name of the current session: + QDBusInterface manager("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", conn); + QDBusReply<QDBusObjectPath> replyGetSession = manager.call(QDBus::Block, "GetSessionForUnixProcess", (quint32)sender.pid); + if(!replyGetSession.isValid()) + { + qWarning("Reply to GetSessionForUnixProcess is invalid: %s: %s", replyGetSession.error().name().toLocal8Bit().constData(), replyGetSession.error().message().toLocal8Bit().constData()); + return QString(); + } + _savedConsoleKitSession[sender] = sessionReference = replyGetSession.value().path(); + } + return sessionReference; +} + +PVSCheckPrivileges::SessionKind PVSCheckPrivileges::getSessionKind(CachedInputContext const& sender) +{ + if(!sender.isValid()) + { + return SESSION_UNKNOWN; + } + + // if the sender is root, we always suppose he works locally. + if(sender.uid == 0) + { + return SESSION_LOCAL; + } + + QString sessionReference = getSessionReference(sender); + if(sessionReference.isNull()) + { + return SESSION_LOOKUP_FAILURE; + } + + QDBusConnection conn = QDBusConnection::systemBus(); + QDBusInterface session("org.freedesktop.ConsoleKit", sessionReference, "org.freedesktop.ConsoleKit.Session", conn); + QDBusReply<bool> replyIsLocal = session.call(QDBus::Block, "IsLocal"); + + if(!replyIsLocal.isValid()) + { + qWarning("Unable to find out whether the current session is local: %s: %s", replyIsLocal.error().name().toLocal8Bit().constData(), replyIsLocal.error().message().toLocal8Bit().constData()); + return SESSION_LOOKUP_FAILURE; + } + return replyIsLocal.value() ? SESSION_LOCAL : SESSION_NONLOCAL; +} + +PVSCheckPrivileges::UserPrivilege PVSCheckPrivileges::getUserPrivilege(CachedInputContext const& sender) +{ + // Always allow root: + if(sender.uid == 0) + { + return USER_PRIVILEGED; + } + + // Or if the user is one of those enumerated in the privileged-users configuration value: + if(_privilegedUsers.contains(sender.uid)) + { + return USER_PRIVILEGED; + } + + // Or if the user is a member of one of the groups enumerated in the privileged-groups configuration value: + foreach(gid_t gid, _privilegedGroups) + { + if(_userGroupMap.contains(sender.uid, gid)) + { + return USER_PRIVILEGED; + } + } + + // User is not trivially privileged, so try to check with PolicyKit. +#ifdef HAVE_POLKIT // but only if it is present + // For PolKit, we need the start-time of the process. + // On Linux, we can get it from /proc: + QString procStat = QString("/proc/%1/stat").arg(sender.pid); + QFile procStatFile(procStat); + if(!procStatFile.exists()) + { + qWarning("Could not look up any info on process %d, its %s file does not exist", sender.pid, procStat.toLocal8Bit().constData()); + return USER_LOOKUP_FAILURE; + } + procStatFile.open(QIODevice::ReadOnly); + QByteArray procStatBytes = procStatFile.readAll(); + qDebug() << "Read stat file: " << procStatBytes; + QString procStatLine = QString::fromLocal8Bit(procStatBytes.constData(), procStatBytes.length()); + QStringList procStatFields = procStatLine.split(QRegExp("\\s+")); + qDebug() << "Found stat fields: " << procStatFields; + bool ok; + quint64 startTime = procStatFields[21].toULongLong(&ok); + if(!ok) + { + qWarning("Could not find out start time for process %d", (int)sender.pid); + return USER_LOOKUP_FAILURE; + } + + // Okay, we got the startTime. Now ask PolicyKit: + + QDBusConnection conn = QDBusConnection::systemBus(); + QDBusInterface intf("org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", "org.freedesktop.PolicyKit1.Authority", conn); + PolKitSubject subj; + subj.subject_kind = "unix-process"; + subj.subject_details["pid"] = (quint32)sender.pid; + subj.subject_details["start-time"] = startTime; + QDBusReply<PolKitAuthReply> reply = intf.call(QDBus::Block, + QLatin1String("CheckAuthorization"), + QVariant::fromValue(subj), + "org.openslx.pvs.privilegedinput", + QVariant::fromValue(QMap<QString, QString>()) /* No details */, + (quint32)1 /* Allow Interaction */, + QUuid::createUuid().toString() /* Cancellation ID */); + if(!reply.isValid()) + { + QDBusError err = reply.error(); + + qWarning("Reply to CheckAuthorization is invalid: %s: %s", err.name().toLocal8Bit().constData(), err.message().toLocal8Bit().constData()); + return USER_LOOKUP_FAILURE; + } + return reply.value().isAuthorized ? USER_PRIVILEGED : USER_UNPRIVILEGED; +#else + return USER_UNPRIVILEGED; +#endif +} + +QString PVSCheckPrivileges::getX11SessionName(CachedInputContext const& sender) +{ + QString sessionReference = getSessionReference(sender); + if(sessionReference.isNull()) + { + return QString(); + } + + QDBusConnection conn = QDBusConnection::systemBus(); + QDBusInterface intf("org.freedesktop.ConsoleKit", sessionReference, "org.freedesktop.ConsoleKit.Session", conn); + QDBusReply<QString> reply = intf.call(QDBus::Block, QLatin1String("GetX11Display")); + if(!reply.isValid()) + { + QDBusError err = reply.error(); + + qWarning("Reply to GetX11Display is invalid: %s: %s", err.name().toLocal8Bit().constData(), err.message().toLocal8Bit().constData()); + return QString(); + } + + return reply.value(); +} + +QString PVSCheckPrivileges::getX11DisplayDevice(CachedInputContext const& sender) +{ + QString sessionReference = getSessionReference(sender); + if(sessionReference.isNull()) + { + return QString(); + } + + QDBusConnection conn = QDBusConnection::systemBus(); + QDBusInterface intf("org.freedesktop.ConsoleKit", sessionReference, "org.freedesktop.ConsoleKit.Session", conn); + QDBusReply<QString> reply = intf.call(QDBus::Block, QLatin1String("GetX11DisplayDevice")); + if(!reply.isValid()) + { + QDBusError err = reply.error(); + + qWarning("Reply to GetX11DisplayDevice is invalid: %s: %s", err.name().toLocal8Bit().constData(), err.message().toLocal8Bit().constData()); + return QString(); + } + + return reply.value(); +} + +bool PVSCheckPrivileges::require(SessionKind sessionKind, CachedInputContext const& sender) +{ + SessionKind cachedSessionKind; + + if(sessionKind == SESSION_NONLOCAL) + { + // All sessions are at least non-local + return true; + } + else if(sessionKind == SESSION_LOCAL) + { + if((cachedSessionKind = _savedSessionKind.value(sender, SESSION_UNKNOWN)) == SESSION_UNKNOWN) + { + cachedSessionKind = getSessionKind(sender); + if(cachedSessionKind != SESSION_LOOKUP_FAILURE) + _savedSessionKind[sender] = cachedSessionKind; + qDebug("Got session kind: %s", toString(cachedSessionKind).toLocal8Bit().constData()); + } + + switch(cachedSessionKind) + { + case SESSION_LOOKUP_FAILURE: + case SESSION_UNKNOWN: + { + // If we cannot find out the correct session kind, look up what we should do in + // the configuration: + QSettings* config = pvsPrivInputGetSettings(); + QVariant assumeLocal = config->value("assume-session-local", false); + if(!assumeLocal.canConvert(QVariant::Bool)) + { + qWarning("There is an assume-session-local setting, but cannot convert it to boolean"); + return false; + } + return assumeLocal.toBool(); + } + case SESSION_LOCAL: + return true; + case SESSION_NONLOCAL: + return false; + default: + qWarning("Internal error: Undefined session kind %d", (int)cachedSessionKind); + return false; + } + } + else + { + qWarning("Internal error: It does not make sense to require an unknown session or undefined session kind %d", (int)sessionKind); + return false; + } +} + +bool PVSCheckPrivileges::require(UserPrivilege userPrivilege, CachedInputContext const& sender) +{ + UserPrivilege cachedUserPrivilege; + + if(userPrivilege == USER_UNPRIVILEGED) + { + // All users are unprivileged + return true; + } + else if(userPrivilege == USER_PRIVILEGED) + { + if((cachedUserPrivilege = _savedUserPrivilege.value(sender, USER_UNKNOWN)) == USER_UNKNOWN) + { + cachedUserPrivilege = getUserPrivilege(sender); + if(cachedUserPrivilege != USER_LOOKUP_FAILURE) + _savedUserPrivilege[sender] = cachedUserPrivilege; + qDebug("Got user privilege: %s", toString(cachedUserPrivilege).toLocal8Bit().constData()); + } + + switch(cachedUserPrivilege) + { + case USER_LOOKUP_FAILURE: + case USER_UNKNOWN: + { + // If we cannot find out the correct user privilege level, look up what we should do in + // the configuration: + QSettings* config = pvsPrivInputGetSettings(); + QVariant assumePrivileged = config->value("assume-user-privileged", false); + if(!assumePrivileged.canConvert(QVariant::Bool)) + { + qWarning("There is an assume-session-local setting, but cannot convert it to boolean"); + return false; + } + return assumePrivileged.toBool(); + } + case USER_PRIVILEGED: + return true; + case USER_UNPRIVILEGED: + return false; + default: + qWarning("Internal error: Found undefined user privilege level %d", (int)cachedUserPrivilege); + _savedUserPrivilege.remove(sender); + return false; + } + } + else + { + qWarning("Internal error: It does not make sense to require an unknown or undefined user privilege level %d", (int)userPrivilege); + return false; + } +} + +bool PVSCheckPrivileges::require(SessionKind sessionKind, + UserPrivilege userPrivilege, + CachedInputContext const& sender) +{ + if(!require(sessionKind, sender)) + return false; + if(!require(userPrivilege, sender)) + return false; + return true; +} + +uint qHash(CachedInputContext const& p) +{ + return qHash(qMakePair(p.pid, qMakePair(p.uid, p.gid))); +} + +void PVSCheckPrivileges::updateCachedUserDatabase() +{ + QHash<QString, uid_t> userNames; + + _userGroupMap.clear(); + + // assemble a list of known users and their primary groups: + setpwent(); // open the user database + struct passwd* userrec; + while((userrec = getpwent())) + { + userNames.insert(userrec->pw_name, userrec->pw_uid); + _userGroupMap.insert(userrec->pw_uid, userrec->pw_gid); + } + endpwent(); // close the database + + // augment with secondary groups: + setgrent(); // open the group database + struct group* grouprec; + while((grouprec = getgrent())) + { + char** membername = grouprec->gr_mem; + while(*membername) + { + uid_t uid = userNames.value(*membername, (uid_t)-1); + if(uid != (uid_t)-1) + { + _userGroupMap.insert(uid, grouprec->gr_gid); + } + membername++; + } + } + endgrent(); + + // decisions may have changed, so clear the caches: + _savedUserPrivilege.clear(); +} + +void PVSCheckPrivileges::rereadConfiguration() +{ + QSettings* settings = pvsPrivInputReopenSettings(); + + _privilegedGroups.clear(); + QVariant groupList = settings->value("privileged-groups"); + if(groupList.isValid()) + { + if(!groupList.canConvert(QVariant::StringList)) + { + qWarning("There is a privileged-groups setting, but it cannot be converted to a list of strings."); + goto endGroupListScan; + } + + QStringList groups = groupList.toStringList(); + foreach(QString groupName, groups) + { + bool ok; + gid_t gid = groupName.toUInt(&ok); + if(ok) + { + _privilegedGroups.append(gid); + } + else + { + // lookup the name: + QByteArray groupNameBytes = groupName.toLocal8Bit(); + struct group* group = getgrnam(groupNameBytes.constData()); + if(group) + { + _privilegedGroups.append(group->gr_gid); + } + else + { + qWarning("privileged-groups setting contains %s which is neither a numeric GID " + "nor a valid group name. Skipping.", + groupNameBytes.constData()); + } + } + } + } + +endGroupListScan: + _privilegedUsers.clear(); + QVariant userList = settings->value("privileged-users"); + if(userList.isValid()) + { + if(!userList.canConvert(QVariant::StringList)) + { + qWarning("There is a privileged-users setting, but it cannot be converted to a list of strings."); + goto endUserListScan; + } + + QStringList users = userList.toStringList(); + foreach(QString userName, users) + { + bool ok; + uid_t uid = userName.toUInt(&ok); + if(ok) + { + _privilegedUsers.append(uid); + } + else + { + // lookup the name: + QByteArray userNameBytes = userName.toLocal8Bit(); + struct passwd* user = getpwnam(userNameBytes.constData()); + if(user) + { + _privilegedUsers.append(user->pw_uid); + } + else + { + qWarning("privileged-users setting contains %s which is neither a numeric UID " + "nor a valid user name. Skipping.", + userNameBytes.constData()); + } + } + } + } + +endUserListScan: + return; +} diff --git a/src/input/pvsCheckPrivileges.h b/src/input/pvsCheckPrivileges.h new file mode 100644 index 0000000..62b463c --- /dev/null +++ b/src/input/pvsCheckPrivileges.h @@ -0,0 +1,151 @@ +/* + # 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/ + # ----------------------------------------------------------------------------- + # src/net/pvsCheckPrivileges_linux.h + # - Linux implementation of privilege checking + # ----------------------------------------------------------------------------- + */ + +#ifndef PVSCHECKPRIVILEGES_H_ +#define PVSCHECKPRIVILEGES_H_ + +#include <sys/types.h> +#include <QObject> +#include <QString> +#include <QList> +#include <QMultiMap> +#include <QHash> +#include "inputEventHandler.h" + +struct CachedInputContext +{ + CachedInputContext(InputEventContext const* source) + { + if(source) + { + pid = source->getSenderPid(); + uid = source->getSenderUid(); + gid = source->getSenderGid(); + } + else + { + pid = (pid_t)-1; + uid = (uid_t)-1; + gid = (gid_t)-1; + } + } + + CachedInputContext() + { + pid = (pid_t)-1; + uid = (uid_t)-1; + gid = (gid_t)-1; + } + + pid_t pid; + uid_t uid; + gid_t gid; + + bool isValid() const + { + return (pid != (pid_t)-1) && (uid != (uid_t)-1) && (gid != (gid_t)-1); + } + + bool operator==(CachedInputContext const& other) const + { + return (other.pid == pid) && (other.uid == uid) && (other.gid == gid); + } +}; +uint qHash(CachedInputContext const& p); + +class QFileSystemWatcher; + +class PVSCheckPrivileges : public QObject +{ + Q_OBJECT +public: + typedef enum { + SESSION_LOCAL, + SESSION_NONLOCAL, + SESSION_LOOKUP_FAILURE, + SESSION_UNKNOWN + } SessionKind; + static QString toString(SessionKind k) + { + switch(k) + { + case SESSION_LOOKUP_FAILURE: return "SESSION_LOOKUP_FAILURE"; + case SESSION_LOCAL: return "SESSION_LOCAL"; + case SESSION_NONLOCAL: return "SESSION_NONLOCAL"; + case SESSION_UNKNOWN: return "SESSION_UNKNOWN"; + default: return QString("unknown value (%1)").arg(k); + } + } + + typedef enum { + USER_PRIVILEGED, + USER_UNPRIVILEGED, + USER_LOOKUP_FAILURE, + USER_UNKNOWN + } UserPrivilege; + static QString toString(UserPrivilege k) + { + switch(k) + { + case USER_PRIVILEGED: return "USER_PRIVILEGED"; + case USER_UNPRIVILEGED: return "USER_UNPRIVILEGED"; + case USER_LOOKUP_FAILURE: return "USER_LOOKUP_FAILURE"; + case USER_UNKNOWN: return "USER_UNKNOWN"; + default: return QString("unknown value (%1)").arg(k); + } + } + + static PVSCheckPrivileges* instance(); + static void deleteInstance(); + + bool require(SessionKind sessionKind, CachedInputContext const& sender); + bool require(UserPrivilege userPrivilege, CachedInputContext const& sender); + bool require(SessionKind sessionKind, UserPrivilege userPrivilege, CachedInputContext const& sender); + QString getX11SessionName(CachedInputContext const& sender); + QString getX11DisplayDevice(CachedInputContext const& sender); + +public slots: + void updateCachedUserDatabase(); + void rereadConfiguration(); + +private: + PVSCheckPrivileges(QObject* parent = 0); + virtual ~PVSCheckPrivileges(); + + typedef QPair<pid_t, QPair<uid_t, gid_t> > piduidgid; + piduidgid makePidUidGid(pid_t pid, uid_t uid, gid_t gid) + { + return qMakePair(pid, qMakePair(uid, gid)); + } + + QString getSessionReference(CachedInputContext const& sender); + SessionKind getSessionKind(CachedInputContext const& sender); + UserPrivilege getUserPrivilege(CachedInputContext const& sender); + + static PVSCheckPrivileges* _instance; + + QHash<CachedInputContext, UserPrivilege> _savedUserPrivilege; + QHash<CachedInputContext, SessionKind> _savedSessionKind; + QHash<CachedInputContext, QString> _savedConsoleKitSession; + + QList<uid_t> _privilegedUsers; + QList<gid_t> _privilegedGroups; + QMultiMap<uid_t, gid_t> _userGroupMap; + QFileSystemWatcher* _watcher; +}; + +#endif /* PVSCHECKPRIVILEGES_H_ */ diff --git a/src/input/pvsPrivInputHandler.cpp b/src/input/pvsPrivInputHandler.cpp new file mode 100644 index 0000000..70ed1bc --- /dev/null +++ b/src/input/pvsPrivInputHandler.cpp @@ -0,0 +1,108 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # pvsPrivInputHandler.h + # - Handle privileged input connection requests - implementation + # -------------------------------------------------------------------------- + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include <cerrno> +#include <QtDebug> +#include <QSocketNotifier> +#include "inputEvent.h" +#include "inputEventHandler.h" +#include "pvsPrivInputSocket.h" +#include "pvsPrivInputHandler.h" + +using namespace std; + +class PrivInputContext : public InputEventContext +{ +public: + PrivInputContext(pid_t pid, uid_t uid, gid_t gid) + : _pid(pid), _uid(uid), _gid(gid) + { + } + + pid_t getSenderPid() const + { + return _pid; + } + + uid_t getSenderUid() const + { + return _uid; + } + + gid_t getSenderGid() const + { + return _gid; + } + +private: + pid_t _pid; + uid_t _uid; + gid_t _gid; +}; + +PVSPrivInputHandler::PVSPrivInputHandler(int fd, QObject* parent) : + QObject(parent) +{ + _fd = fd; + _notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); + _notifier->setEnabled(true); + connect(_notifier, SIGNAL(activated(int)), this, SLOT(canRead())); +} + +PVSPrivInputHandler::~PVSPrivInputHandler() +{ + delete _notifier; +} + +void PVSPrivInputHandler::canRead() +{ + _notifier->setEnabled(false); + + // We need to retrieve the credentials of the process: + size_t siz = 8; + _messageAssembly.clear(); + _messageAssembly.resize(8); + pid_t pid; + uid_t uid; + gid_t gid; + int err; + + if(!pvsPrivInputRecvMessage(_fd, _messageAssembly.data(), siz, pid, uid, gid, &err)) + { + close(_fd); + deleteLater(); + return; + } + else + { + if(siz != 8) + { + qWarning("Malformed PVS Input Event packet"); + return; + } + QDataStream strm(&_messageAssembly, QIODevice::ReadOnly); + InputEvent evt; + strm.setByteOrder(QDataStream::BigEndian); + strm >> evt; + PrivInputContext ctx(pid, uid, gid); + _handlerChain.handle(evt, &ctx); + } + + _notifier->setEnabled(true); +} diff --git a/src/input/pvsPrivInputHandler.h b/src/input/pvsPrivInputHandler.h new file mode 100644 index 0000000..9980cdf --- /dev/null +++ b/src/input/pvsPrivInputHandler.h @@ -0,0 +1,45 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # pvsPrivInputHandler.h + # - Handle privileged input connection requests - interface + # -------------------------------------------------------------------------- + */ + +#ifndef PVSPRIVINPUTHANDLER_H_ +#define PVSPRIVINPUTHANDLER_H_ + +#include <QObject> +#include <QHash> +#include <QPointer> +#include "inputHandlerChain.h" + +class QSocketNotifier; + +class PVSPrivInputHandler : public QObject +{ + Q_OBJECT +public: + PVSPrivInputHandler(int fd, QObject* parent = 0); + virtual ~PVSPrivInputHandler(); + +private slots: + void canRead(); + +private: + QSocketNotifier* _notifier; + QByteArray _messageAssembly; + int _bytes; + int _fd; + privileged_handler_chain _handlerChain; +}; + +#endif /* PVSPRIVINPUTHANDLER_H_ */ diff --git a/src/input/pvsPrivInputSignalHandler.cpp b/src/input/pvsPrivInputSignalHandler.cpp new file mode 100644 index 0000000..b09c335 --- /dev/null +++ b/src/input/pvsPrivInputSignalHandler.cpp @@ -0,0 +1,69 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # pvsPrivInputSignalHandler.cpp: + # - Handle signals forwarded over a file descriptor - implementation + # -------------------------------------------------------------------------- + */ + +#include <unistd.h> +#include <sys/types.h> +#include <signal.h> +#include <cerrno> +#include "pvsPrivInputSocket.h" +#include "pvsPrivInputSignalHandler.h" + +void PVSPrivInputSignalHandler::signalReceived(int sigfd) +{ + int signum; + + pid_t pid; + uid_t uid; + gid_t gid; + int err; + size_t siz = sizeof(signum); + + if(!pvsPrivInputRecvMessage(sigfd, &signum, siz, pid, uid, gid, &err)) + { + qWarning("Should have received a signal but could not read message: %s", strerror(err)); + return; + } + + if(siz != sizeof(signum)) + { + qWarning("Sould have received a message of size %d bytes but got %d bytes instead. Discarding message.", (int)sizeof(signum), (int)siz); + return; + } + + if(!_allowUnauthenticatedKilling && pid != getpid()) + { + qCritical("SOMETHING STRANGE IS GOING ON!\nReceived signal %d from PID %d\nPlease kill me instead of sending me signal notifications.", signum, (int)pid); + return; + } + + switch(signum) + { + case SIGINT: + qDebug("Caught SIGINT. Quitting."); + emit terminate(); + break; + case SIGTERM: + qDebug("Caught SIGTERM. Quitting."); + emit terminate(); + break; + case SIGHUP: + qDebug("Caught SIGHUP. Reloading configuration."); + emit reloadConfiguration(); + break; + default: + qWarning("Received signal %d, but don't know how to handle it.", signum); + } +} diff --git a/src/input/pvsPrivInputSignalHandler.h b/src/input/pvsPrivInputSignalHandler.h new file mode 100644 index 0000000..cb75c86 --- /dev/null +++ b/src/input/pvsPrivInputSignalHandler.h @@ -0,0 +1,47 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # pvsPrivInputSignalHandler.h: + # - Handle signals forwarded over a file descriptor - interface + # -------------------------------------------------------------------------- + */ + +#ifndef PVSPRIVINPUTSIGNALHANDLER_H_ +#define PVSPRIVINPUTSIGNALHANDLER_H_ + +#include <QObject> + +class PVSPrivInputSignalHandler : public QObject +{ + Q_OBJECT +public: + PVSPrivInputSignalHandler(QObject* parent = 0) + : QObject(parent) + { + } + + void allowUnauthenticatedKilling(bool value) + { + _allowUnauthenticatedKilling = value; + } + +public slots: + void signalReceived(int sigfd); + +signals: + void terminate(); + void reloadConfiguration(); + +private: + bool _allowUnauthenticatedKilling; +}; + +#endif /* PVSPRIVINPUTSIGNALHANDLER_H_ */ diff --git a/src/input/pvsPrivInputSocket.cpp b/src/input/pvsPrivInputSocket.cpp new file mode 100644 index 0000000..df5dff5 --- /dev/null +++ b/src/input/pvsPrivInputSocket.cpp @@ -0,0 +1,233 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # pvsPrivInputSocket.h: + # - Centralize knowledge of socket address and connection options + # for pvsprivinputd - implementation + # -------------------------------------------------------------------------- + */ + +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> +#include <cerrno> +#include <QtDebug> +#include <QSettings> +#include "pvsPrivInputSocket.h" + +using namespace std; + +#ifndef UNIX_PATH_MAX +# define UNIX_PATH_MAX 108 /* according to unix(7) */ +#endif + +static QSettings* pvsPrivInputSettings = 0; + +QString pvsPrivInputGetSettingsPath() +{ + return "/etc/pvsprivinputd.conf"; +} + +QSettings* pvsPrivInputGetSettings() +{ + if(!pvsPrivInputSettings) + { + pvsPrivInputSettings = new QSettings(pvsPrivInputGetSettingsPath(), QSettings::IniFormat); + } + return pvsPrivInputSettings; +} + +QSettings* pvsPrivInputReopenSettings() +{ + if(pvsPrivInputSettings) + { + delete pvsPrivInputSettings; + pvsPrivInputSettings = 0; + } + return pvsPrivInputGetSettings(); +} + +QString pvsPrivInputGetSocketAddress() +{ + return pvsPrivInputGetSettings()->value("socketpath", "/tmp/pvsprivinputd.sock").toString(); +} + +bool pvsPrivInputEnableReceiveCredentials(int sock) +{ + int passcred = 1; + if(setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &passcred, sizeof(passcred)) < 0) + { + return false; + } + else + { + return true; + } +} + +int pvsPrivInputMakeClientSocket() +{ + int sock = socket(AF_UNIX, SOCK_DGRAM, 0); + if(sock < 0) + { + qWarning("Could not create a socket: %s", strerror(errno)); + return -1; + } + + QByteArray socketPath = pvsPrivInputGetSocketAddress().toLocal8Bit(); + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socketPath.constData(), UNIX_PATH_MAX - 1); + if(connect(sock, reinterpret_cast<struct sockaddr*>(&addr), + sizeof(addr)) < 0) + { + qWarning("Could not connect to pvsprivinputd at %s: %s", socketPath.constData(), strerror(errno)); + close(sock); + return -1; + } + + return sock; +} + +int pvsPrivInputMakeServerSocket() +{ + int sock = socket(AF_UNIX, SOCK_DGRAM, 0); + if(sock < 0) + { + qCritical("Could not create a socket: %s", strerror(errno)); + return -1; + } + + // Bind to the address: + QByteArray socketPath = pvsPrivInputGetSocketAddress().toLocal8Bit(); + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socketPath.constData(), UNIX_PATH_MAX - 1); + if(bind(sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) < 0) + { + qCritical("Could not bind socket to %s", strerror(errno)); + close(sock); + return -1; + } + + // Announce that credentials are requested: + if(!pvsPrivInputEnableReceiveCredentials(sock)) + { + // We will not operate without credentials. + qCritical("Could not request peer credentials: %s", strerror(errno)); + close(sock); + return -1; + } + +#if 0 /* Only for SOCK_STREAM: */ + // Listen for connections: + if(listen(sock, 1) < 0) + { + qCritical("Could not listen for connections to %s: %s", socketPath.constData(), strerror(errno)); + close(sock); + return -1; + } +#endif + + return sock; +} + +bool pvsPrivInputSendMessage(int sock, void* buf, size_t _len, int* err) +{ + /* + * Portability note: All UNIX-like systems can transmit credentials over UNIX + * sockets, but only Linux does it automagically. + */ + + long len = (long)_len; + + // send(2) does not split messages on a SOCK_DGRAM socket. + int e = send(sock, buf, len, 0); + if(e < 0) + { + qWarning("Failed to send message of length %d over socket %d: %s", (unsigned)len, e, strerror(errno)); + if(err) + *err = errno; + return false; + } + else if(e < len) + { + qWarning("Failed to send a complete message of length %d over socket %d, only %d bytes were sent", (unsigned)len, sock, e); + if(err) + *err = errno; + return false; + } + + return true; +} + +bool pvsPrivInputRecvMessage(int sock, void* buf, size_t& len, + pid_t& pid, uid_t& uid, gid_t& gid, int* err) +{ + struct iovec iov; + struct msghdr msg; + char ctlbuf[1024]; + iov.iov_base = buf; + iov.iov_len = len; + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = &ctlbuf; + msg.msg_controllen = sizeof(ctlbuf); + msg.msg_flags = 0; + int bytes_read = recvmsg(sock, &msg, 0); + if(bytes_read < 0) + { + qWarning("Could not read from socket: %s", strerror(errno)); + if(err) + *err = errno; + return false; + } + + pid = -1; + uid = -1; + gid = -1; + + struct cmsghdr* cmsg; + for(cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + if(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) + { + struct ucred* creds = reinterpret_cast<struct ucred*>(CMSG_DATA(cmsg)); + pid = creds->pid; + uid = creds->uid; + gid = creds->gid; + break; + } + else if(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) + { + // We need to close passed file descriptors. If we don't, we + // have a denial-of-service vulnerability. + int* fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + unsigned num_fds = cmsg->cmsg_len / sizeof(int); + for(unsigned i = 0; i < num_fds; i++) + { + close(fds[i]); + } + } + } + + if(pid == (pid_t)-1 || uid == (uid_t)-1 || gid == (gid_t)-1) + { + *err = 0; + return false; + } + + return true; +} diff --git a/src/input/pvsPrivInputSocket.h b/src/input/pvsPrivInputSocket.h new file mode 100644 index 0000000..447360b --- /dev/null +++ b/src/input/pvsPrivInputSocket.h @@ -0,0 +1,37 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # pvsPrivInputSocket.h: + # - Centralize knowledge of socket address and connection options + # for pvsprivinputd - interface + # -------------------------------------------------------------------------- + */ + +#ifndef PVSPRIVINPUTSOCKET_H_ +#define PVSPRIVINPUTSOCKET_H_ + +#include <sys/types.h> +#include <cstring> +#include <QString> + +class QSettings; + +QSettings* pvsPrivInputGetSettings(); +QSettings* pvsPrivInputReopenSettings(); +QString pvsPrivInputGetSettingsPath(); +QString pvsPrivInputGetSocketAddress(); +bool pvsPrivInputEnableReceiveCredentials(int sock); +int pvsPrivInputMakeClientSocket(); +int pvsPrivInputMakeServerSocket(); +bool pvsPrivInputSendMessage(int sock, void* buf, size_t len, int* err = 0); +bool pvsPrivInputRecvMessage(int sock, void* buf, size_t& len, pid_t& pid, uid_t& uid, gid_t& gid, int* err = 0); + +#endif /* PVSPRIVINPUTSOCKET_H_ */ diff --git a/src/input/pvsSyslog.cpp b/src/input/pvsSyslog.cpp new file mode 100644 index 0000000..6a9be31 --- /dev/null +++ b/src/input/pvsSyslog.cpp @@ -0,0 +1,133 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # pvsSyslog.cpp: + # - Send output to syslog or to a file - implementation + # -------------------------------------------------------------------------- + */ + +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <cerrno> +#include <syslog.h> +#include <QFile> +#include <QTextStream> +#include "pvsSyslog.h" + +using namespace std; + +#define PVS_LOG_BUFSIZ 8192 + +void PVSLogRedirector::inputAvailableOn(int fd) +{ + QByteArray readbuf; + readbuf.resize(PVS_LOG_BUFSIZ); + ssize_t siz; + bool cont = true; + + do + { + siz = recv(fd, readbuf.data(), readbuf.size(), MSG_DONTWAIT); + if(siz > 0) + { + _buf.append(readbuf.constData(), siz); + + // Send lines: + int oldnpos = -1; + int npos = -1; + while((npos = _buf.indexOf('\n', oldnpos + 1)) >= 0) + { + if((oldnpos + 1) == npos) + { + oldnpos = npos; + continue; + } + QString line = QString::fromLocal8Bit(_buf.constData() + oldnpos + 1, npos - oldnpos - 1); + doRedirectInput(line); + oldnpos = npos; + } + + // All complete lines have been sent. Remove sent bytes from buffer. + _buf.remove(0, oldnpos + 1); + + // If line length is too large, send it now. + if(_buf.size() >= BUFSIZ) + { + QString longLine = QString::fromLocal8Bit(_buf.constData(), _buf.size()); + doRedirectInput(longLine); + _buf.clear(); + } + } + else if(siz == 0) + { + // Socket closed on other end. + break; + } + else if(siz < 0) + { + int err = errno; + switch(err) + { + case EAGAIN: +#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN) + case EWOULDBLOCK: +#endif + // We read all there is to read. + cont = false; + break; + default: + // Something happened. + // We can't inform the user via qDebug() and friends as that would loop right back to us again, + // so we emit a log line. + doRedirectInput(QString("Error in logging code: Could not read log input: %s").arg(strerror(err))); + cont = false; + } + } + } + while(cont); +} + +PVSSyslogRedirector::PVSSyslogRedirector() +{ + openlog("pvsprivinputd", LOG_PID, LOG_USER); +} + +PVSSyslogRedirector::~PVSSyslogRedirector() +{ + closelog(); +} + +void PVSSyslogRedirector::doRedirectInput(QString const& line) +{ + QByteArray bytes = line.toLocal8Bit(); + syslog(LOG_NOTICE, "%s", bytes.data()); +} + +PVSLogfileRedirector::PVSLogfileRedirector(QString const& filename) +{ + _file = new QFile(filename, this); + _file->open(QIODevice::Append); + _stream = new QTextStream(_file); +} + +PVSLogfileRedirector::~PVSLogfileRedirector() +{ + _stream->flush(); + _file->close(); + delete _stream; + delete _file; +} + +void PVSLogfileRedirector::doRedirectInput(QString const& line) +{ + *_stream << line << flush; +} diff --git a/src/input/pvsSyslog.h b/src/input/pvsSyslog.h new file mode 100644 index 0000000..8c9591a --- /dev/null +++ b/src/input/pvsSyslog.h @@ -0,0 +1,63 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # pvsSyslog.h: + # - Send output to syslog or to a file - interface + # -------------------------------------------------------------------------- + */ + +#ifndef PVSSYSLOG_H_ +#define PVSSYSLOG_H_ + +#include <QObject> +#include <QString> + +class PVSLogRedirector : public QObject +{ + Q_OBJECT +public slots: + void inputAvailableOn(int fd); + +protected: + virtual void doRedirectInput(QString const& line) = 0; + +private: + QByteArray _buf; +}; + +class PVSSyslogRedirector : public PVSLogRedirector +{ +public: + PVSSyslogRedirector(); + virtual ~PVSSyslogRedirector(); + +protected: + void doRedirectInput(QString const& line); +}; + +class QFile; +class QTextStream; + +class PVSLogfileRedirector : public PVSLogRedirector +{ +public: + PVSLogfileRedirector(QString const& filename); + virtual ~PVSLogfileRedirector(); + +protected: + void doRedirectInput(QString const& line); + +private: + QFile* _file; + QTextStream* _stream; +}; + +#endif /* PVSSYSLOG_H_ */ diff --git a/src/input/pvsprivinputd.conf b/src/input/pvsprivinputd.conf new file mode 100644 index 0000000..a62a922 --- /dev/null +++ b/src/input/pvsprivinputd.conf @@ -0,0 +1,28 @@ +;; Lines starting with `;' are comments. +;; +;; socketpath: +;; Where pvsprivinputd should put its listening socket. +socketpath = /tmp/pvsprivinputd.sock + +;; privileged-users: +;; Comma-separated list of users that are allowed to run privileged actions +privileged-users = root + +;; privileged-groups: +;; Comma-separated list of user groups that are allowed to run privileged actions +; privileged-groups = wheel + +;; assume-session-local: +;; Assume that a session is local if it can not be looked up in ConsoleKit, +;; for example, if you are not running ConsoleKit. +;; +;; WARNING: Setting this to true may be a security risk. Running ConsoleKit is +;; really recommended. +; assume-session-local = false + +;; assume-user-privileged: +;; Assume that a user is privileged if he/she can not be looked up in +;; the user database or PolicyKit fails to deliver an answer. +;; +;; WARNING: Setting this to true is most definitely a security risk. +; assume-user-privileged = false
\ No newline at end of file diff --git a/src/input/pvsprivinputd.cpp b/src/input/pvsprivinputd.cpp new file mode 100644 index 0000000..e6ae155 --- /dev/null +++ b/src/input/pvsprivinputd.cpp @@ -0,0 +1,262 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # pvsprivinputd.cpp: + # - Handle privileged input events - daemon + # -------------------------------------------------------------------------- + */ + +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <signal.h> +#include <unistd.h> +#include <cerrno> +#include <cstdlib> +#include <cstdio> +#include <cstring> +#include <QtDebug> +#include <QFileInfo> +#include <QSettings> +#include <QCoreApplication> +#include <QStringList> +#include <QSocketNotifier> +#include "pvsCheckPrivileges.h" +#include "pvsPrivInputSocket.h" +#include "pvsPrivInputHandler.h" +#include "pvsPrivInputSignalHandler.h" +#include "pvsSyslog.h" + +using namespace std; + +#ifndef UNIX_PATH_MAX +# define UNIX_PATH_MAX 108 /* according to unix(7) */ +#endif + +QByteArray socketPath; +int signalFds[2]; + +QTextStream qout(stdout, QIODevice::WriteOnly); +QTextStream qerr(stderr, QIODevice::WriteOnly); + +static void unlinkSocket() +{ + if(!socketPath.isNull()) + { + unlink(socketPath.constData()); + } +} + +static void onCaughtSignal(int signum) +{ + pvsPrivInputSendMessage(signalFds[0], &signum, sizeof(signum), 0); +} + +static void usage() +{ + qout << QObject::tr( + "Usage: %1 [--help|-h] [--daemon|-d] [--log=<Logger>|-l<Logger>]\n" + "\n" + "Options:\n" + " --help, -h Show this message\n" + " --daemon, -d Run in background\n" + " --log=<Logger>,\n" + " -l<Logger> Redirect all output to <Logger>\n" + " valid values are:\n" + " - any file name\n" + " - `syslog' to redirect output to the system log\n" + " - `null' to discard output (default)\n" + " (without quotes)\n" + "\n" + "Signals:\n" + " SIGINT, SIGTERM (or press Ctrl+C when run in foreground)\n" + " Quit\n" + " SIGHUP Reload configuration and cached user data\n").arg(qApp->arguments().at(0)) << flush; +} + +int main(int argc, char** argv) +{ + QCoreApplication app(argc, argv); + app.setApplicationName("pvsprivinputd"); + app.setOrganizationName("openslx"); + + bool no_fork = true; + QString logTarget = "null"; + + foreach(QString arg, app.arguments().mid(1)) + { + if(arg == "--daemon" || arg == "-d") + { + no_fork = false; + } + else if(arg.startsWith("-l")) + { + logTarget = arg.mid(2); + } + else if(arg.startsWith("--log=")) + { + logTarget = arg.mid(6); + } + else if(arg == "--help" || arg == "-h") + { + usage(); + return EXIT_SUCCESS; + } + else + { + qout << "Unexpected argument: " << arg << endl; + usage(); + return EXIT_FAILURE; + } + } + + if(logTarget != "null" && logTarget != "syslog") + { + logTarget = QFileInfo(logTarget).absoluteFilePath(); + qout << "Writing log to " << logTarget << endl; + } + + if(!no_fork) + { + pid_t pid; + pid_t sid; + + pid = fork(); + if(pid < 0) + { + qCritical("Could not fork: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + if(pid > 0) + { + exit(EXIT_SUCCESS); + } + + // We are now inside a child process. + // Detach from parent: + sid = setsid(); + if(sid < 0) + { + qCritical("Could not detach from parent process: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + + // Change to some benign directory: + if(chdir("/") < 0) + { + qCritical("Could not change directory: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + } + + // Okay, we are running as a daemon. Defer reopening standard file descriptors + // so we can report errors. + + // Set the file creation mask to allow all processes to connect to + // the socket (access control is handled differently): + umask(0); + + // Store the socket path before the signal handler is installed so it does not risk segfaulting + // due to bad timing. + socketPath = pvsPrivInputGetSocketAddress().toLocal8Bit(); + + // Ignore SIGPIPE. Connection errors are handled internally. + // According to signal(2), the only error is that the signal number + // is invalid. This cannot happen. + signal(SIGPIPE, SIG_IGN); + + // Create our socket: + int sock = pvsPrivInputMakeServerSocket(); + if(sock < 0) + { + exit(EXIT_FAILURE); + } + + // Install our main object + PVSPrivInputHandler handler(sock); + + // set up signal handling: + if(socketpair(AF_UNIX, SOCK_DGRAM, 0, signalFds) < 0) + { + qCritical("Could not set up signal handling. Giving up."); + exit(EXIT_FAILURE); + } + PVSPrivInputSignalHandler sigHandler; + QSocketNotifier sigintWatcher(signalFds[1], QSocketNotifier::Read); + sigHandler.allowUnauthenticatedKilling(!pvsPrivInputEnableReceiveCredentials(signalFds[1])); + QObject::connect(&sigintWatcher, SIGNAL(activated(int)), &sigHandler, SLOT(signalReceived(int))); + QObject::connect(&sigHandler, SIGNAL(terminate()), &app, SLOT(quit())); + QObject::connect(&sigHandler, SIGNAL(reloadConfiguration()), PVSCheckPrivileges::instance(), SLOT(updateCachedUserDatabase())); + QObject::connect(&sigHandler, SIGNAL(reloadConfiguration()), PVSCheckPrivileges::instance(), SLOT(rereadConfiguration())); + signal(SIGINT, onCaughtSignal); + signal(SIGTERM, onCaughtSignal); + signal(SIGHUP, onCaughtSignal); + + PVSLogRedirector* logRedir = 0; + QSocketNotifier* logNotif = 0; + // Our setup is complete. + if(!no_fork) + { + // Reopen standard file descriptors: + freopen("/dev/null", "r", stdin); + + if(logTarget == "null") + { + freopen("/dev/null", "w", stdout); + freopen("/dev/null", "w", stderr); + } + else if(logTarget == "syslog") + { + logRedir = new PVSSyslogRedirector(); + } + else + { + logRedir = new PVSLogfileRedirector(logTarget); + } + + if(logRedir) + { + int logFds[2]; + if(socketpair(AF_UNIX, SOCK_STREAM, 0, logFds) < 0) + { + qWarning("Could not open a socket pair: %s", strerror(errno)); + return EXIT_FAILURE; + } + + logNotif = new QSocketNotifier(logFds[1], QSocketNotifier::Read); + QObject::connect(logNotif, SIGNAL(activated(int)), logRedir, SLOT(inputAvailableOn(int))); + + // redirect stdout and stderr: + dup2(logFds[0], 1); + dup2(logFds[0], 2); + } + } + + atexit(unlinkSocket); + + qout << "PVS Privileged Input Daemon initialization complete. Entering main loop." << endl; + + // and run the main loop. + int ret = app.exec(); + + if(logRedir) + { + delete logNotif; + delete logRedir; + } + + PVSCheckPrivileges::deleteInstance(); + + qout << "PVS Privileged Input Daemon deinitialization complete. Exiting." << endl; + + return ret; +} diff --git a/src/input/rebootSystemHandler.cpp b/src/input/rebootSystemHandler.cpp new file mode 100644 index 0000000..b5b8f8a --- /dev/null +++ b/src/input/rebootSystemHandler.cpp @@ -0,0 +1,32 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # rebootSystemHandler.cpp + # - Handle system reboot requests - implementation + # -------------------------------------------------------------------------- + */ + +#include <sys/types.h> +#include <signal.h> +#include <cerrno> +#include <QtDebug> +#include "rebootSystemHandler.h" + +using namespace std; + +void RebootLinuxSystemHandler::handle(InputEvent const& evt, InputEventContext const* ctx) +{ + // Rebooting a linux system is particulary easy: + if(kill(1, SIGINT) < 0) + { + qWarning("Could not kill /sbin/init: %s", strerror(errno)); + } +} diff --git a/src/input/rebootSystemHandler.h b/src/input/rebootSystemHandler.h new file mode 100644 index 0000000..34fa8ae --- /dev/null +++ b/src/input/rebootSystemHandler.h @@ -0,0 +1,34 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # rebootSystemHandler.h + # - Handle system reboot requests - interface + # -------------------------------------------------------------------------- + */ + +#ifndef REBOOTSYSTEMHANDLER_H_ +#define REBOOTSYSTEMHANDLER_H_ + +#include <QCoreApplication> +#include "inputEventHandler.h" + +class RebootLinuxSystemHandler : public DefaultInputEventHandler<InputEvent::ET_SPECIAL, InputEvent::EC_REBOOT> +{ +public: + void handle(InputEvent const&, InputEventContext const*); + + static void describeInto(QList<SpecialInputEventDescription>& list) + { + list << SpecialInputEventDescription(tr(QT_TRANSLATE_NOOP("InputEventHandler", "Reboot")), InputEvent::ET_SPECIAL, InputEvent::EC_REBOOT); + } +}; + +#endif /* REBOOTSYSTEMHANDLER_H_ */ diff --git a/src/input/sayHelloHandler.cpp b/src/input/sayHelloHandler.cpp new file mode 100644 index 0000000..fc6f668 --- /dev/null +++ b/src/input/sayHelloHandler.cpp @@ -0,0 +1,25 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # rebootSystemHandler.h + # - Handle "say hello" requests - interface + # -------------------------------------------------------------------------- + */ + +#include "sayHelloHandler.h" +#include <iostream> + +using namespace std; + +void SayHelloHandler::handle(InputEvent const&, InputEventContext const* ctx) +{ + cerr << "I'm right here! You sent this message from pid " << ctx->getSenderPid() << " as user " << ctx->getSenderUid() << " with gid " << ctx->getSenderGid() << endl; +} diff --git a/src/input/sayHelloHandler.h b/src/input/sayHelloHandler.h new file mode 100644 index 0000000..b0d4c7e --- /dev/null +++ b/src/input/sayHelloHandler.h @@ -0,0 +1,34 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # rebootSystemHandler.h + # - Handle "say hello" requests - interface + # -------------------------------------------------------------------------- + */ + +#ifndef SAYHELLOHANDLER_CPP_ +#define SAYHELLOHANDLER_CPP_ + +#include <QCoreApplication> +#include "inputEventHandler.h" + +class SayHelloHandler : public DefaultInputEventHandler<InputEvent::ET_SPECIAL, InputEvent::EC_SAY_HELLO> +{ +public: + void handle(InputEvent const&, InputEventContext const*); + + static void describeInto(QList<SpecialInputEventDescription>& list) + { + list << SpecialInputEventDescription(tr(QT_TRANSLATE_NOOP("InputEventHandler", "Say Hello")), InputEvent::ET_SPECIAL, InputEvent::EC_SAY_HELLO); + } +}; + +#endif /* SAYHELLOHANDLER_CPP_ */ diff --git a/src/input/x11FakeKeyboardHandler.cpp b/src/input/x11FakeKeyboardHandler.cpp new file mode 100644 index 0000000..0c32b66 --- /dev/null +++ b/src/input/x11FakeKeyboardHandler.cpp @@ -0,0 +1,906 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # x11FakeKeyboardHandler.h: + # - Handle keyboard events on X11 - interface + # -------------------------------------------------------------------------- + */ + +#include <map> +#include <set> +#include <cassert> +// Qt headers need to be included before X11 headers +#include <QApplication> +#include <QtCore> +#include "x11FakeKeyboardHandler.h" +// #include <multimap> +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/keysym.h> +#include <X11/keysymdef.h> +#include <X11/extensions/XTest.h> +#ifdef HAVE_XINPUT2_H +# include <X11/extensions/XInput2.h> +#else +# include <X11/extensions/XInput.h> +#endif +#include <X11/XKBlib.h> +#include <src/util/consoleLogger.h> +#include "x11InputUtils.h" + +//////////////////////// INPUT EVENT TRANSLATION ///////////////////////////////// + +typedef unsigned char xmodifier_type; + +char modifiernames[][8] = { + "SHIFT", + "LOCK", + "CONTROL", + "MOD1", + "MOD2", + "MOD3", + "MOD4", + "MOD5" +}; + +QString modifiers_to_string(xmodifier_type mods) +{ + QString s; + for(int i = 0; i < 8; i++) + { + if(mods & (1<<i)) + { + s += modifiernames[i]; + s += ", "; + } + } + s.chop(2); + return s; +} + + +typedef std::map<quint32, KeySym> lookup_table_type; +typedef lookup_table_type::const_iterator lookup_table_const_iterator; +typedef lookup_table_type::iterator lookup_table_iterator; +lookup_table_type keysyms; +void initialize_keysyms() { + keysyms[Qt::Key_Escape] = XK_Escape; + keysyms[Qt::Key_Tab] = XK_Tab; +// keysyms[Qt::Key_Backtab] = XK_Backtab; + keysyms[Qt::Key_Backspace] = XK_BackSpace; + keysyms[Qt::Key_Return] = XK_Return; + keysyms[Qt::Key_Enter] = XK_KP_Enter; + keysyms[Qt::Key_Insert] = XK_Insert; + keysyms[Qt::Key_Delete] = XK_Delete; + keysyms[Qt::Key_Pause] = XK_Pause; + keysyms[Qt::Key_Print] = XK_Print; + keysyms[Qt::Key_SysReq] = XK_Sys_Req; + keysyms[Qt::Key_Clear] = XK_Clear; + keysyms[Qt::Key_Home] = XK_Home; + keysyms[Qt::Key_End] = XK_End; + keysyms[Qt::Key_Left] = XK_Left; + keysyms[Qt::Key_Up] = XK_Up; + keysyms[Qt::Key_Right] = XK_Right; + keysyms[Qt::Key_Down] = XK_Down; + keysyms[Qt::Key_PageUp] = XK_Page_Up; + keysyms[Qt::Key_PageDown] = XK_Page_Down; + keysyms[Qt::Key_Shift] = XK_Shift_L; + keysyms[Qt::Key_Control] = XK_Control_L; + keysyms[Qt::Key_Meta] = XK_Meta_L; + keysyms[Qt::Key_Alt] = XK_Alt_L; + keysyms[Qt::Key_CapsLock] = XK_Caps_Lock; + keysyms[Qt::Key_NumLock] = XK_Num_Lock; + keysyms[Qt::Key_ScrollLock] = XK_Scroll_Lock; + keysyms[Qt::Key_F1] = XK_F1; + keysyms[Qt::Key_F2] = XK_F2; + keysyms[Qt::Key_F3] = XK_F3; + keysyms[Qt::Key_F4] = XK_F4; + keysyms[Qt::Key_F5] = XK_F5; + keysyms[Qt::Key_F6] = XK_F6; + keysyms[Qt::Key_F7] = XK_F7; + keysyms[Qt::Key_F8] = XK_F8; + keysyms[Qt::Key_F9] = XK_F9; + keysyms[Qt::Key_F10] = XK_F10; + keysyms[Qt::Key_F11] = XK_F11; + keysyms[Qt::Key_F12] = XK_F12; + keysyms[Qt::Key_F13] = XK_F13; + keysyms[Qt::Key_F14] = XK_F14; + keysyms[Qt::Key_F15] = XK_F15; + keysyms[Qt::Key_F16] = XK_F16; + keysyms[Qt::Key_F17] = XK_F17; + keysyms[Qt::Key_F18] = XK_F18; + keysyms[Qt::Key_F19] = XK_F19; + keysyms[Qt::Key_F20] = XK_F20; + keysyms[Qt::Key_F21] = XK_F21; + keysyms[Qt::Key_F22] = XK_F22; + keysyms[Qt::Key_F23] = XK_F23; + keysyms[Qt::Key_F24] = XK_F24; + keysyms[Qt::Key_F25] = XK_F25; + keysyms[Qt::Key_F26] = XK_F26; + keysyms[Qt::Key_F27] = XK_F27; + keysyms[Qt::Key_F28] = XK_F28; + keysyms[Qt::Key_F29] = XK_F29; + keysyms[Qt::Key_F30] = XK_F30; + keysyms[Qt::Key_F31] = XK_F31; + keysyms[Qt::Key_F32] = XK_F32; + keysyms[Qt::Key_F33] = XK_F33; + keysyms[Qt::Key_F34] = XK_F34; + keysyms[Qt::Key_F35] = XK_F35; + keysyms[Qt::Key_Super_L] = XK_Super_L; + keysyms[Qt::Key_Super_R] = XK_Super_R; + keysyms[Qt::Key_Menu] = XK_Menu; + keysyms[Qt::Key_Hyper_L] = XK_Hyper_L; + keysyms[Qt::Key_Hyper_R] = XK_Hyper_R; + keysyms[Qt::Key_Help] = XK_Help; +// keysyms[Qt::Key_Direction_L] = XK_Direction_L; +// keysyms[Qt::Key_Direction_R] = XK_Direction_R; + keysyms[Qt::Key_Space] = XK_space; + keysyms[Qt::Key_Exclam] = XK_exclam; + keysyms[Qt::Key_QuoteDbl] = XK_quotedbl; + keysyms[Qt::Key_NumberSign] = XK_numbersign; + keysyms[Qt::Key_Dollar] = XK_dollar; + keysyms[Qt::Key_Percent] = XK_percent; + keysyms[Qt::Key_Ampersand] = XK_ampersand; + keysyms[Qt::Key_Apostrophe] = XK_apostrophe; + keysyms[Qt::Key_ParenLeft] = XK_parenleft; + keysyms[Qt::Key_ParenRight] = XK_parenright; + keysyms[Qt::Key_Asterisk] = XK_asterisk; + keysyms[Qt::Key_Plus] = XK_plus; + keysyms[Qt::Key_Comma] = XK_comma; + keysyms[Qt::Key_Minus] = XK_minus; + keysyms[Qt::Key_Period] = XK_period; + keysyms[Qt::Key_Slash] = XK_slash; + keysyms[Qt::Key_0] = XK_0; + keysyms[Qt::Key_1] = XK_1; + keysyms[Qt::Key_2] = XK_2; + keysyms[Qt::Key_3] = XK_3; + keysyms[Qt::Key_4] = XK_4; + keysyms[Qt::Key_5] = XK_5; + keysyms[Qt::Key_6] = XK_6; + keysyms[Qt::Key_7] = XK_7; + keysyms[Qt::Key_8] = XK_8; + keysyms[Qt::Key_9] = XK_9; + keysyms[Qt::Key_Colon] = XK_colon; + keysyms[Qt::Key_Semicolon] = XK_semicolon; + keysyms[Qt::Key_Less] = XK_less; + keysyms[Qt::Key_Equal] = XK_equal; + keysyms[Qt::Key_Greater] = XK_greater; + keysyms[Qt::Key_Question] = XK_question; + keysyms[Qt::Key_At] = XK_at; + keysyms[Qt::Key_A] = XK_A; + keysyms[Qt::Key_B] = XK_B; + keysyms[Qt::Key_C] = XK_C; + keysyms[Qt::Key_D] = XK_D; + keysyms[Qt::Key_E] = XK_E; + keysyms[Qt::Key_F] = XK_F; + keysyms[Qt::Key_G] = XK_G; + keysyms[Qt::Key_H] = XK_H; + keysyms[Qt::Key_I] = XK_I; + keysyms[Qt::Key_J] = XK_J; + keysyms[Qt::Key_K] = XK_K; + keysyms[Qt::Key_L] = XK_L; + keysyms[Qt::Key_M] = XK_M; + keysyms[Qt::Key_N] = XK_N; + keysyms[Qt::Key_O] = XK_O; + keysyms[Qt::Key_P] = XK_P; + keysyms[Qt::Key_Q] = XK_Q; + keysyms[Qt::Key_R] = XK_R; + keysyms[Qt::Key_S] = XK_S; + keysyms[Qt::Key_T] = XK_T; + keysyms[Qt::Key_U] = XK_U; + keysyms[Qt::Key_V] = XK_V; + keysyms[Qt::Key_W] = XK_W; + keysyms[Qt::Key_X] = XK_X; + keysyms[Qt::Key_Y] = XK_Y; + keysyms[Qt::Key_Z] = XK_Z; + keysyms[Qt::Key_BracketLeft] = XK_bracketleft; + keysyms[Qt::Key_Backslash] = XK_backslash; + keysyms[Qt::Key_BracketRight] = XK_bracketright; + keysyms[Qt::Key_AsciiCircum] = XK_asciicircum; + keysyms[Qt::Key_Underscore] = XK_underscore; + keysyms[Qt::Key_QuoteLeft] = XK_quoteleft; + keysyms[Qt::Key_BraceLeft] = XK_braceleft; + keysyms[Qt::Key_Bar] = XK_bar; + keysyms[Qt::Key_BraceRight] = XK_braceright; + keysyms[Qt::Key_AsciiTilde] = XK_asciitilde; + keysyms[Qt::Key_nobreakspace] = XK_nobreakspace; + keysyms[Qt::Key_exclamdown] = XK_exclamdown; + keysyms[Qt::Key_cent] = XK_cent; + keysyms[Qt::Key_sterling] = XK_sterling; + keysyms[Qt::Key_currency] = XK_currency; + keysyms[Qt::Key_yen] = XK_yen; + keysyms[Qt::Key_brokenbar] = XK_brokenbar; + keysyms[Qt::Key_section] = XK_section; + keysyms[Qt::Key_diaeresis] = XK_diaeresis; + keysyms[Qt::Key_copyright] = XK_copyright; + keysyms[Qt::Key_ordfeminine] = XK_ordfeminine; + keysyms[Qt::Key_guillemotleft] = XK_guillemotleft; + keysyms[Qt::Key_notsign] = XK_notsign; + keysyms[Qt::Key_hyphen] = XK_hyphen; + keysyms[Qt::Key_registered] = XK_registered; + keysyms[Qt::Key_macron] = XK_macron; + keysyms[Qt::Key_degree] = XK_degree; + keysyms[Qt::Key_plusminus] = XK_plusminus; + keysyms[Qt::Key_twosuperior] = XK_twosuperior; + keysyms[Qt::Key_threesuperior] = XK_threesuperior; + keysyms[Qt::Key_acute] = XK_acute; + keysyms[Qt::Key_mu] = XK_mu; + keysyms[Qt::Key_paragraph] = XK_paragraph; + keysyms[Qt::Key_periodcentered] = XK_periodcentered; + keysyms[Qt::Key_cedilla] = XK_cedilla; + keysyms[Qt::Key_onesuperior] = XK_onesuperior; + keysyms[Qt::Key_masculine] = XK_masculine; + keysyms[Qt::Key_guillemotright] = XK_guillemotright; + keysyms[Qt::Key_onequarter] = XK_onequarter; + keysyms[Qt::Key_onehalf] = XK_onehalf; + keysyms[Qt::Key_threequarters] = XK_threequarters; + keysyms[Qt::Key_questiondown] = XK_questiondown; + keysyms[Qt::Key_Agrave] = XK_Agrave; + keysyms[Qt::Key_Aacute] = XK_Aacute; + keysyms[Qt::Key_Acircumflex] = XK_Acircumflex; + keysyms[Qt::Key_Atilde] = XK_Atilde; + keysyms[Qt::Key_Adiaeresis] = XK_Adiaeresis; + keysyms[Qt::Key_Aring] = XK_Aring; + keysyms[Qt::Key_AE] = XK_AE; + keysyms[Qt::Key_Ccedilla] = XK_Ccedilla; + keysyms[Qt::Key_Egrave] = XK_Egrave; + keysyms[Qt::Key_Eacute] = XK_Eacute; + keysyms[Qt::Key_Ecircumflex] = XK_Ecircumflex; + keysyms[Qt::Key_Ediaeresis] = XK_Ediaeresis; + keysyms[Qt::Key_Igrave] = XK_Igrave; + keysyms[Qt::Key_Iacute] = XK_Iacute; + keysyms[Qt::Key_Icircumflex] = XK_Icircumflex; + keysyms[Qt::Key_Idiaeresis] = XK_Idiaeresis; + keysyms[Qt::Key_ETH] = XK_ETH; + keysyms[Qt::Key_Ntilde] = XK_Ntilde; + keysyms[Qt::Key_Ograve] = XK_Ograve; + keysyms[Qt::Key_Oacute] = XK_Oacute; + keysyms[Qt::Key_Ocircumflex] = XK_Ocircumflex; + keysyms[Qt::Key_Otilde] = XK_Otilde; + keysyms[Qt::Key_Odiaeresis] = XK_Odiaeresis; + keysyms[Qt::Key_multiply] = XK_multiply; + keysyms[Qt::Key_Ooblique] = XK_Ooblique; + keysyms[Qt::Key_Ugrave] = XK_Ugrave; + keysyms[Qt::Key_Uacute] = XK_Uacute; + keysyms[Qt::Key_Ucircumflex] = XK_Ucircumflex; + keysyms[Qt::Key_Udiaeresis] = XK_Udiaeresis; + keysyms[Qt::Key_Yacute] = XK_Yacute; + keysyms[Qt::Key_THORN] = XK_THORN; + keysyms[Qt::Key_ssharp] = XK_ssharp; + keysyms[Qt::Key_Agrave] = XK_Agrave; + keysyms[Qt::Key_Aacute] = XK_Aacute; + keysyms[Qt::Key_Acircumflex] = XK_Acircumflex; + keysyms[Qt::Key_Atilde] = XK_Atilde; + keysyms[Qt::Key_Adiaeresis] = XK_Adiaeresis; + keysyms[Qt::Key_Aring] = XK_Aring; + keysyms[Qt::Key_AE] = XK_AE; + keysyms[Qt::Key_Ccedilla] = XK_Ccedilla; + keysyms[Qt::Key_Egrave] = XK_Egrave; + keysyms[Qt::Key_Eacute] = XK_Eacute; + keysyms[Qt::Key_Ecircumflex] = XK_Ecircumflex; + keysyms[Qt::Key_Ediaeresis] = XK_Ediaeresis; + keysyms[Qt::Key_Igrave] = XK_Igrave; + keysyms[Qt::Key_Iacute] = XK_Iacute; + keysyms[Qt::Key_Icircumflex] = XK_Icircumflex; + keysyms[Qt::Key_Idiaeresis] = XK_Idiaeresis; + keysyms[Qt::Key_ETH] = XK_ETH; + keysyms[Qt::Key_Ntilde] = XK_Ntilde; + keysyms[Qt::Key_Ograve] = XK_Ograve; + keysyms[Qt::Key_Oacute] = XK_Oacute; + keysyms[Qt::Key_Ocircumflex] = XK_Ocircumflex; + keysyms[Qt::Key_Otilde] = XK_Otilde; + keysyms[Qt::Key_Odiaeresis] = XK_Odiaeresis; + keysyms[Qt::Key_division] = XK_division; + keysyms[Qt::Key_Ooblique] = XK_Ooblique; + keysyms[Qt::Key_Ugrave] = XK_Ugrave; + keysyms[Qt::Key_Uacute] = XK_Uacute; + keysyms[Qt::Key_Ucircumflex] = XK_Ucircumflex; + keysyms[Qt::Key_Udiaeresis] = XK_Udiaeresis; + keysyms[Qt::Key_Yacute] = XK_Yacute; + keysyms[Qt::Key_THORN] = XK_THORN; + keysyms[Qt::Key_ydiaeresis] = XK_ydiaeresis; + keysyms[Qt::Key_AltGr] = XK_ISO_Level3_Shift; + keysyms[Qt::Key_Multi_key] = XK_Multi_key; + keysyms[Qt::Key_Codeinput] = XK_Codeinput; + keysyms[Qt::Key_SingleCandidate] = XK_SingleCandidate; + keysyms[Qt::Key_MultipleCandidate] = XK_MultipleCandidate; + keysyms[Qt::Key_PreviousCandidate] = XK_PreviousCandidate; + keysyms[Qt::Key_Mode_switch] = XK_Mode_switch; +// keysyms[Qt::Key_script_switch] = XK_script_switch; + keysyms[Qt::Key_Kanji] = XK_Kanji; + keysyms[Qt::Key_Muhenkan] = XK_Muhenkan; +// keysyms[Qt::Key_Henkan_Mode] = XK_Henkan_Mode; + keysyms[Qt::Key_Henkan] = XK_Henkan; + keysyms[Qt::Key_Romaji] = XK_Romaji; + keysyms[Qt::Key_Hiragana] = XK_Hiragana; + keysyms[Qt::Key_Katakana] = XK_Katakana; + keysyms[Qt::Key_Hiragana_Katakana] = XK_Hiragana_Katakana; + keysyms[Qt::Key_Zenkaku] = XK_Zenkaku; + keysyms[Qt::Key_Hankaku] = XK_Hankaku; + keysyms[Qt::Key_Zenkaku_Hankaku] = XK_Zenkaku_Hankaku; + keysyms[Qt::Key_Touroku] = XK_Touroku; + keysyms[Qt::Key_Massyo] = XK_Massyo; + keysyms[Qt::Key_Kana_Lock] = XK_Kana_Lock; + keysyms[Qt::Key_Kana_Shift] = XK_Kana_Shift; + keysyms[Qt::Key_Eisu_Shift] = XK_Eisu_Shift; + keysyms[Qt::Key_Eisu_toggle] = XK_Eisu_toggle; +// keysyms[Qt::Key_Kanji_Bangou] = XK_Kanji_Bangou; +// keysyms[Qt::Key_Zen_Koho] = XK_Zen_Koho; +// keysyms[Qt::Key_Mae_Koho] = XK_Mae_Koho; + keysyms[Qt::Key_Hangul] = XK_Hangul; + keysyms[Qt::Key_Hangul_Hanja] = XK_Hangul_Hanja; + keysyms[Qt::Key_Hangul] = XK_Hangul; + keysyms[Qt::Key_Hangul_Start] = XK_Hangul_Start; + keysyms[Qt::Key_Hangul_End] = XK_Hangul_End; + keysyms[Qt::Key_Hangul_Hanja] = XK_Hangul_Hanja; + keysyms[Qt::Key_Hangul_Jamo] = XK_Hangul_Jamo; + keysyms[Qt::Key_Hangul_Romaja] = XK_Hangul_Romaja; +// keysyms[Qt::Key_Hangul_Codeinput] = XK_Hangul_Codeinput; + keysyms[Qt::Key_Hangul_Jeonja] = XK_Hangul_Jeonja; + keysyms[Qt::Key_Hangul_Banja] = XK_Hangul_Banja; + keysyms[Qt::Key_Hangul_PreHanja] = XK_Hangul_PreHanja; + keysyms[Qt::Key_Hangul_PostHanja] = XK_Hangul_PostHanja; +// keysyms[Qt::Key_Hangul_SingleCandidate] = XK_Hangul_SingleCandidate; +// keysyms[Qt::Key_Hangul_MultipleCandidate] = XK_Hangul_MultipleCandidate; +// keysyms[Qt::Key_Hangul_PreviousCandidate] = XK_Hangul_PreviousCandidate; + keysyms[Qt::Key_Hangul_Special] = XK_Hangul_Special; +// keysyms[Qt::Key_Hangul_switch] = XK_Hangul_switch; + keysyms[Qt::Key_Dead_Grave] = XK_dead_grave; + keysyms[Qt::Key_Dead_Acute] = XK_dead_acute; + keysyms[Qt::Key_Dead_Circumflex] = XK_dead_circumflex; + keysyms[Qt::Key_Dead_Tilde] = XK_dead_tilde; + keysyms[Qt::Key_Dead_Macron] = XK_dead_macron; + keysyms[Qt::Key_Dead_Breve] = XK_dead_breve; + keysyms[Qt::Key_Dead_Abovedot] = XK_dead_abovedot; + keysyms[Qt::Key_Dead_Diaeresis] = XK_dead_diaeresis; + keysyms[Qt::Key_Dead_Abovering] = XK_dead_abovering; + keysyms[Qt::Key_Dead_Doubleacute] = XK_dead_doubleacute; + keysyms[Qt::Key_Dead_Caron] = XK_dead_caron; + keysyms[Qt::Key_Dead_Cedilla] = XK_dead_cedilla; + keysyms[Qt::Key_Dead_Ogonek] = XK_dead_ogonek; + keysyms[Qt::Key_Dead_Iota] = XK_dead_iota; + keysyms[Qt::Key_Dead_Voiced_Sound] = XK_dead_voiced_sound; + keysyms[Qt::Key_Dead_Semivoiced_Sound] = XK_dead_semivoiced_sound; + keysyms[Qt::Key_Dead_Belowdot] = XK_dead_belowdot; + keysyms[Qt::Key_Dead_Hook] = XK_dead_hook; + keysyms[Qt::Key_Dead_Horn] = XK_dead_horn; +// keysyms[Qt::Key_Back] = XK_Back; +// keysyms[Qt::Key_Forward] = XK_Forward; +// keysyms[Qt::Key_Stop] = XK_Stop; +// keysyms[Qt::Key_Refresh] = XK_Refresh; +// keysyms[Qt::Key_VolumeDown] = XK_VolumeDown; +// keysyms[Qt::Key_VolumeMute] = XK_VolumeMute; +// keysyms[Qt::Key_VolumeUp] = XK_VolumeUp; +// keysyms[Qt::Key_BassBoost] = XK_BassBoost; +// keysyms[Qt::Key_BassUp] = XK_BassUp; +// keysyms[Qt::Key_BassDown] = XK_BassDown; +// keysyms[Qt::Key_TrebleUp] = XK_TrebleUp; +// keysyms[Qt::Key_TrebleDown] = XK_TrebleDown; +// keysyms[Qt::Key_MediaPlay] = XK_MediaPlay; +// keysyms[Qt::Key_MediaStop] = XK_MediaStop; +// keysyms[Qt::Key_MediaPrevious] = XK_MediaPrevious; +// keysyms[Qt::Key_MediaNext] = XK_MediaNext; +// keysyms[Qt::Key_MediaRecord] = XK_MediaRecord; +// keysyms[Qt::Key_HomePage] = XK_HomePage; +// keysyms[Qt::Key_Favorites] = XK_Favorites; +// keysyms[Qt::Key_Search] = XK_Search; +// keysyms[Qt::Key_Standby] = XK_Standby; +// keysyms[Qt::Key_OpenUrl] = XK_OpenUrl; +// keysyms[Qt::Key_LaunchMail] = XK_LaunchMail; +// keysyms[Qt::Key_LaunchMedia] = XK_LaunchMedia; +// keysyms[Qt::Key_Launch0] = XK_Launch0; +// keysyms[Qt::Key_Launch1] = XK_Launch1; +// keysyms[Qt::Key_Launch2] = XK_Launch2; +// keysyms[Qt::Key_Launch3] = XK_Launch3; +// keysyms[Qt::Key_Launch4] = XK_Launch4; +// keysyms[Qt::Key_Launch5] = XK_Launch5; +// keysyms[Qt::Key_Launch6] = XK_Launch6; +// keysyms[Qt::Key_Launch7] = XK_Launch7; +// keysyms[Qt::Key_Launch8] = XK_Launch8; +// keysyms[Qt::Key_Launch9] = XK_Launch9; +// keysyms[Qt::Key_LaunchA] = XK_LaunchA; +// keysyms[Qt::Key_LaunchB] = XK_LaunchB; +// keysyms[Qt::Key_LaunchC] = XK_LaunchC; +// keysyms[Qt::Key_LaunchD] = XK_LaunchD; +// keysyms[Qt::Key_LaunchE] = XK_LaunchE; +// keysyms[Qt::Key_LaunchF] = XK_LaunchF; +// keysyms[Qt::Key_Display] = XK_Display; +// keysyms[Qt::Key_MediaLast] = XK_MediaLast; + keysyms[Qt::Key_Select] = XK_Select; +// keysyms[Qt::Key_Yes] = XK_Yes; +// keysyms[Qt::Key_No] = XK_No; + keysyms[Qt::Key_Cancel] = XK_Cancel; +// keysyms[Qt::Key_Printer] = XK_Printer; + keysyms[Qt::Key_Execute] = XK_Execute; +// keysyms[Qt::Key_Sleep] = XK_Sleep; +// keysyms[Qt::Key_MediaPlay] = XK_MediaPlay; +// keysyms[Qt::Key_Zoom] = XK_Zoom; +// keysyms[Qt::Key_Jisho] = XK_Jisho; +// keysyms[Qt::Key_Oyayubi_Left] = XK_Oyayubi_Left; +// keysyms[Qt::Key_Oyayubi_Right] = XK_Oyayubi_Right; +// keysyms[Qt::Key_Context1] = XK_Context1; +// keysyms[Qt::Key_Context2] = XK_Context2; +// keysyms[Qt::Key_Context3] = XK_Context3; +// keysyms[Qt::Key_Context4] = XK_Context4; +// keysyms[Qt::Key_Call] = XK_Call; +// keysyms[Qt::Key_Hangup] = XK_Hangup; +// keysyms[Qt::Key_Flip] = XK_Flip; + keysyms[Qt::Key_unknown] = XK_VoidSymbol; +} + +/* Store Keycodes for "normal" Latin1 keys: */ +int basic_keycodes[0x100]; +int basic_modifiers[0x100]; + +int modifier_keycodes[8]; +Qt::KeyboardModifier modifier_meaning[8]; + +int xmodifier_from_qtmodifier(Qt::KeyboardModifier m) { + for(int i = 0; i < 8; i++) { + if(m == modifier_meaning[i]) + return i; + } + return -1; +} + +typedef std::map<Qt::KeyboardModifier, xmodifier_type> qt_to_xmodifier_type; +typedef std::map<xmodifier_type, Qt::KeyboardModifier> x_to_qtmodifier_type; +qt_to_xmodifier_type qt_to_xmodifier; +x_to_qtmodifier_type x_to_qtmodifier; + +typedef std::multimap<xmodifier_type, int> modifier_to_keycode_map; +modifier_to_keycode_map modifier_to_keycode; +typedef std::map<int, xmodifier_type> keycode_to_modifier_map; +keycode_to_modifier_map keycode_to_modifier; + +// We need to query the input devices, preferrable through XInput2, but +// if that is not available we will contend ourselves with XInput1: +#ifndef HAVE_XINPUT2_H +# define XIAllDevices 1 /* does not matter */ +# define XIDeviceInfo XDeviceInfo +# define XIQueryDevice(dpy, which, ninfos) XListInputDevices(dpy, ninfos) +# define XIFreeDeviceInfo(infos) XFreeDeviceList(infos) +# define XIMasterKeyboard IsXKeyboard +# define XISlaveKeyboard IsXExtensionKeyboard + static inline Atom* getDeviceProperties(Display* dpy, XIDeviceInfo* devinfo, int* nprops) + { + if(devinfo->use == IsXKeyboard) + { + // According to XOpenDevice(3X11) you cannot open the Core Keyboard. + *nprops = 0; + return 0; + } + + XDevice* dev = XOpenDevice(dpy, devinfo->id); + Atom* props = XListDeviceProperties(dpy, dev, nprops); + XCloseDevice(dpy, dev); + return props; + } +#else + static inline Atom* getDeviceProperties(Display* dpy, XIDeviceInfo* devinfo, int* nprops) + { + return XIListProperties(dpy, devinfo->deviceid, nprops); + } +#endif + + +void initialize_basic_keycodes() +{ + for(int i = 0; i < 8; i++) { + modifier_keycodes[i] = -1; + } + for(int i = 0; i < 0x100; i++) { + basic_keycodes[i] = -1; + } + + Display* dpy = X11InputUtils::display(); + int min_keycode, max_keycode; + XDisplayKeycodes(dpy, &min_keycode, &max_keycode); + + int xkb_opcode, xkb_event, xkb_error, xkb_major, xkb_minor; + bool xkb_present = XkbQueryExtension(dpy, &xkb_opcode, &xkb_event, &xkb_error, &xkb_major, &xkb_minor); + if(!xkb_present) { + int keysyms_per_code; + const int count = max_keycode - min_keycode + 1; + KeySym* mapping = XGetKeyboardMapping(dpy, min_keycode, count, &keysyms_per_code); + for(int i = 0; i < count; i++) + { + for(int j = 0; j < keysyms_per_code; j++) + { + const int idx = i * keysyms_per_code + j; + const KeySym ks = mapping[idx]; + const int keycode = min_keycode + i; + + if(ks >= ' ' && ks < 0x100) + { + if(ks == XK_at) { + ConsoleLog writeLine(QString("Keycode %1 (%2) gives `@' with modifiers `%3', but it is really `%4'").arg(keycode).arg(XKeysymToString(XKeycodeToKeysym(dpy, keycode, 0))).arg(modifiers_to_string(j)).arg(XKeysymToString(XKeycodeToKeysym(dpy, keycode, j)))); + + } + + if(basic_keycodes[ks] != -1) + continue; // already found + + basic_keycodes[ks] = keycode; + basic_modifiers[ks] = j; + } + } + } + XFree(mapping); + } + else + { + // Try to find out what device XTest will send events on: + unsigned int xkbDeviceId = XkbUseCoreKbd; + Atom xtestDeviceProp = XInternAtom(dpy, "XTEST Device", false); + int ndevinfos; + XIDeviceInfo* devinfos = XIQueryDevice(dpy, XIAllDevices, &ndevinfos); + if(devinfos) + { +#ifndef HAVE_XINPUT2_H +# define deviceid id +#endif + for(int i = 0; i < ndevinfos && xkbDeviceId == XkbUseCoreKbd; i++) + { + XIDeviceInfo* devinfo = devinfos + i; + qDebug("Found device %lu of type %d with name %s", (unsigned long)devinfo->deviceid, devinfo->use, devinfo->name); + + // We want it to be a keyboard. + if(devinfo->use != XIMasterKeyboard && devinfo->use != XISlaveKeyboard) + continue; + + int nprops; + Atom* props = getDeviceProperties(dpy, devinfo, &nprops); + if(props) + { + for(int j = 0; j < nprops && xkbDeviceId == XkbUseCoreKbd; j++) + { + Atom prop = props[j]; + if(prop == xtestDeviceProp) + { + // The device is the XTest Keyboard: + xkbDeviceId = devinfo->deviceid; + } + } + XFree(props); + } + } + XIFreeDeviceInfo(devinfos); +#ifdef deviceid /* XInput1 */ +# undef deviceid +#endif + } + + // Okay, we know which device to query. Now get its keymap: + XkbDescPtr keybDesc = XkbGetKeyboard(dpy, XkbAllComponentsMask, xkbDeviceId); + if(!keybDesc) + { + qWarning("Unable to retrieve keyboard description for device %d. Falling back to unreliable global mapping", xkbDeviceId); + } + + for(int i = min_keycode; i <= max_keycode; i++) + { + for(int j = 0; j <= 0xff; j++) + { + KeySym ks = 0; + unsigned int unconsumed; + if(keybDesc) + { + if(!XkbTranslateKeyCode(keybDesc, i, j, &unconsumed, &ks) || ks == NoSymbol) + continue; + } + else + { + if(!XkbLookupKeySym(dpy, i, j, &unconsumed, &ks) || ks == NoSymbol) + continue; + } + + if(ks && (ks < 0x100)) + { +// ConsoleLog writeLine(QString("Keycode %1 (%2) seems to give `%3' with modifiers `%4', of which `%5' are unconsumed") +// .arg(i) +// .arg(XKeysymToString(XKeycodeToKeysym(dpy, i, 0))) +// .arg(XKeysymToString(ks)) +// .arg(modifiers_to_string(j)) +// .arg(modifiers_to_string(unconsumed))); + if(basic_keycodes[ks] != -1) + continue; + + basic_keycodes[ks] = i; + basic_modifiers[ks] = unconsumed & j; + } + } + } + + // Free the keyboard description: + if(keybDesc) + { + XkbFreeKeyboard(keybDesc, XkbAllComponentsMask, true); + } + } + + // find out which keycodes cause the modifier state bits: + XModifierKeymap* modkm; + modkm = XGetModifierMapping(dpy); + + const int shift_kc = XKeysymToKeycode(dpy, XK_Shift_L); + const int control_kc = XKeysymToKeycode(dpy, XK_Control_L); + const int alt_kc = XKeysymToKeycode(dpy, XK_Alt_L); + const int meta_kc = XKeysymToKeycode(dpy, XK_Meta_L); + const int switch_kc = XKeysymToKeycode(dpy, XK_Mode_switch); + + for(int i = 0; i < 8; i++) { + for(int j = 0; j < modkm->max_keypermod; j++) { + const int idx = i * modkm->max_keypermod + j; + const int kc = modkm->modifiermap[idx]; + + modifier_to_keycode.insert(std::make_pair((1<<i), kc)); + keycode_to_modifier[kc] = (1<<i); +#define remember_modifier(qmod) do { \ + qt_to_xmodifier[Qt::qmod##Modifier] = 1<<i; \ + x_to_qtmodifier[1<<i] = Qt::qmod##Modifier; \ +} while(false) + if(kc == shift_kc) { + remember_modifier(Shift); + } else if(kc == control_kc) { + remember_modifier(Control); + } else if(kc == alt_kc) { + remember_modifier(Alt); + } else if(kc == meta_kc) { + remember_modifier(Meta); + } else if(kc == switch_kc) { + remember_modifier(GroupSwitch); + } +#undef remember_modifier + + if(modifier_keycodes[i] != -1) { + continue; // already found + } + + // select one arbitrarily + modifier_keycodes[i] = kc; + } + } + + XFreeModifiermap(modkm); +} + +xmodifier_type translate_modifiers(quint32 mods) +{ + xmodifier_type ret = 0; + + for(int j = 1; j < 8; j++) { + xmodifier_type i = 1<<j; + + if(mods & x_to_qtmodifier[i]) + { + ret |= i; + } + } + + return ret; +} + +typedef std::set<int> int_set_type; +int_set_type pressed_modifier_keys; +xmodifier_type current_modifiers; + +void trackModifiers(int keycode, bool down) +{ + // is this a modifier key? + const bool is_modifier = keycode_to_modifier.find(keycode) != keycode_to_modifier.end(); + + if(!is_modifier) + { + return; + } + + if(down) { + pressed_modifier_keys.insert(keycode); + } else { + pressed_modifier_keys.erase(keycode); + } + + int_set_type::iterator i, end = pressed_modifier_keys.end(); + xmodifier_type modifs = 0; + for(i = pressed_modifier_keys.begin(); i != end; i++) + { + keycode_to_modifier_map::iterator found_kc = keycode_to_modifier.find(*i); + if(found_kc != keycode_to_modifier.end()) + { + modifs |= (*found_kc).second; + } + } + current_modifiers = modifs; + + ConsoleLog writeLine(QString("[trackModifiers] current modifiers: %1").arg(modifiers_to_string(modifs))); +} + +typedef std::pair<int, bool> modifier_tweak_type; +typedef std::vector<modifier_tweak_type> tweak_sequence_type; + +void tweakModifiers(Display* dpy, xmodifier_type neededState, xmodifier_type actualState, tweak_sequence_type& tracker) +{ + ConsoleLog writeLine(QString("tweakModifiers: Trying to get to `%1' from `%2'").arg(modifiers_to_string(neededState)).arg(modifiers_to_string(actualState))); + for(int j = 0; j < 8; j++) + { + xmodifier_type i = 1<<j; + + ConsoleLog writeLine(QString("Checking for %1...").arg(modifiers_to_string(i))); + + if((i & neededState) && !(i & actualState)) + { + ConsoleLog writeLine(QString("tweakModifiers: Modifier %1 needs to be pressed").arg(modifiernames[j])); + + // needs to be pressed + + //find the keycode: + int kc; + modifier_to_keycode_map::iterator iter = modifier_to_keycode.find(i); + + if((iter == modifier_to_keycode.end()) || ((*iter).first != i)) + { + continue; // we don't know a key that triggers this modifier + } + + ConsoleLog writeLine(QString(" pressing key %1").arg((*iter).second)); + XTestFakeKeyEvent(dpy, (*iter).second, 1, CurrentTime); + + tracker.push_back(std::make_pair((*iter).second, true)); + } + else if((!(i & neededState)) && (i & actualState)) + { + ConsoleLog writeLine(QString("tweakModifiers: Modifier %1 needs to be released").arg(modifiernames[j])); + + // needs to be released + + int kc = -1; + // first, check whether any of the currently pressed keys has triggered this modifier: + + int_set_type::iterator iter, end = pressed_modifier_keys.end(); + for(iter = pressed_modifier_keys.begin(); iter != end; iter++) + { + keycode_to_modifier_map::iterator modmap_iter = keycode_to_modifier.find(*iter); + if(modmap_iter != keycode_to_modifier.end()) { + if(modmap_iter->second == i) { + kc = *iter; + + // release this key: + ConsoleLog writeLine(QString(" releasing key %1").arg(kc)); + XTestFakeKeyEvent(dpy, kc, 0, CurrentTime); + tracker.push_back(std::make_pair(kc, false)); + } + } + } + + if(kc == -1) { + // strange, but we need to release some other key: + // we don't know which one, so we abort this and hope for the best + continue; + } + } + } +} + +void untweakModifiers(Display* dpy, tweak_sequence_type& tracker) +{ + tweak_sequence_type::iterator i, end = tracker.end(); + for(i = tracker.begin(); i != end; i++) { + modifier_tweak_type& t = *i; + XTestFakeKeyEvent(dpy, t.first, !t.second, CurrentTime); + } +} + +void X11FakeKeyboardHandler::initialize() +{ + initialize_keysyms(); + initialize_basic_keycodes(); + pressed_modifier_keys.clear(); + current_modifiers = 0; +} + +void X11FakeKeyboardHandler::handle(InputEvent const& evt, InputEventContext const*) +{ + Display* dpy = X11InputUtils::display(); + + // find out which keysym caused this event: + lookup_table_const_iterator i = keysyms.find(evt.qt_keysym()); + if(i == keysyms.end()) { + // Special cases. We don't know how to directly translate those, so we will try to emulate them. + switch(evt.qt_keysym()) + { + case Qt::Key_Backtab: + handle(InputEvent(evt.type(), evt.code(), evt.qt_modifiers() | Qt::ShiftModifier | Qt::Key_Tab)); + break; + default: + ConsoleLog writeLine(QString("Unknown keysym received: %1").arg(evt.qt_keysym(), 8, 16)); + } + } else { + KeySym ks = (*i).second; + + // is it a "normal" key? + if(ks >= ' ' && ks < 0x100) + { + if(basic_keycodes[ks] != -1) + { + if(evt.isPress()) + { + // Try the simple way first: + // Does the keycode with current modifiers yield the requested symbol? + KeySym unmod_ks = XKeycodeToKeysym(dpy, basic_keycodes[ks], current_modifiers); + ConsoleLog writeLine(QString("Pressing the key for %1 would yield %2 right now.").arg(XKeysymToString(ks)).arg(XKeysymToString(unmod_ks))); + + if(ks == XKeycodeToKeysym(dpy, basic_keycodes[ks], current_modifiers)) + { + XTestFakeKeyEvent(dpy, basic_keycodes[ks], 1, CurrentTime); + } + else + { + // what modifier keys do we need to press? + xmodifier_type mods = translate_modifiers(evt.qt_modifiers()); + + // we may need to press additional modifiers to generate this keysym: + if(QChar(ks, 0).isLetter()) + { + // but, since we do not differentiate upper and lower case, + // and instead let the sender handle those modifiers, + // we need to AND ShiftModifier and LockModifier out. + mods |= basic_modifiers[ks] & ~(1<<ShiftMapIndex) & ~(1<<LockMapIndex); + } + else + { + // Not an alpha character. We do need to respect Shift and Lock for those: + mods |= basic_modifiers[ks]; + } + + // now, tweak the modifiers + tweak_sequence_type tweaks; + tweakModifiers(dpy, mods, current_modifiers, tweaks); + + // press the key: + XTestFakeKeyEvent(dpy, basic_keycodes[ks], 1, CurrentTime); + + // and release the modifiers: + untweakModifiers(dpy, tweaks); + } + } + else + { + // just release the key. + XTestFakeKeyEvent(dpy, basic_keycodes[ks], 0, CurrentTime); + } + } + else + { + ConsoleLog writeLine(QString("No keycode is mapped to `%1'").arg(XKeysymToString(ks))); + } + } + else + { + // It is some kind of "special" key, so we just fake that, then: + KeyCode kc = XKeysymToKeycode(dpy, ks); + if(kc != 0) + { + ConsoleLog writeLine(QString("%1 special keycode %2, hopefully giving keysym %3").arg(evt.isPress() ? "Pressed" : "Released").arg(kc).arg(XKeysymToString(ks))); + XTestFakeKeyEvent(dpy, kc, evt.isPress(), CurrentTime); + // and track it if it is a modifier key: + trackModifiers(kc, evt.isPress()); + } + else + { + ConsoleLog writeLine(QString("No keycode is mapped to %1").arg(XKeysymToString(ks))); + } + } + } + + // Since there may not be a mainloop running, we need to manually flush the event queue + XFlush(dpy); +} diff --git a/src/input/x11FakeKeyboardHandler.h b/src/input/x11FakeKeyboardHandler.h new file mode 100644 index 0000000..3dde7cc --- /dev/null +++ b/src/input/x11FakeKeyboardHandler.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/ + # -------------------------------------------------------------------------- + # x11FakeKeyboardHandler.h: + # - Handle keyboard events on X11 - interface + # -------------------------------------------------------------------------- + */ + +#ifndef X11FAKEKEYBOARDHANDLER_H_ +#define X11FAKEKEYBOARDHANDLER_H_ + +#include "inputEventHandler.h" + +class X11FakeKeyboardHandler : public DefaultInputEventHandler<InputEvent::ET_KEY> +{ +public: + void handle(InputEvent const&, InputEventContext const* = 0); + void initialize(); +}; + +#endif /* X11FAKEKEYBOARDHANDLER_H_ */ diff --git a/src/input/x11FakeMouseHandler.cpp b/src/input/x11FakeMouseHandler.cpp new file mode 100644 index 0000000..58415d5 --- /dev/null +++ b/src/input/x11FakeMouseHandler.cpp @@ -0,0 +1,59 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # x11FakeMouseHandler.h: + # - Handle mouse events on X11 - implementation + # -------------------------------------------------------------------------- + */ + +#include "x11FakeMouseHandler.h" // need to include before X headers +#include <src/util/consoleLogger.h> +#include <X11/extensions/XTest.h> +#include "x11InputUtils.h" + +void X11FakeMouseButtonHandler::handle(InputEvent const& evt, InputEventContext const*) +{ + quint16 pressedButton = evt.pressedButton(); + + Display* dpy = X11InputUtils::display(); + + XTestGrabControl(dpy, 1); + for(int i = 0; i < 16; i++) { + if((1<<i) == pressedButton) + { + ConsoleLog writeLine(QString("Got mouse button event: button %1 %2").arg(i + 1).arg(evt.isPress() ? "pressed" : "released")); + if(!XTestFakeButtonEvent(dpy, i + 1, evt.isPress(), CurrentTime)) + { + ConsoleLog writeLine("[ERROR] XTestFakeButtonEvent failed"); + } + } + } + XTestGrabControl(dpy, 0); + + // Since there may not be a mainloop running, we need to manually flush the event queue + XFlush(dpy); +} + +void X11FakeMouseMovementHandler::handle(InputEvent const& evt, InputEventContext const*) +{ + ConsoleLog writeLine(QString("Received mouse motion event (%1,%2)").arg(evt.xCoord()).arg(evt.yCoord())); + Display* dpy = X11InputUtils::display(); + int screen = 0 /* DefaultScreen(dpy) */; + XTestGrabControl(dpy, 1); + if(!XTestFakeMotionEvent(dpy, screen, evt.xCoord(), evt.yCoord(), CurrentTime)) + { + ConsoleLog writeLine("[ERROR] XTestFakeMotionEvent failed"); + } + XTestGrabControl(dpy, 0); + + // Since there may not be a mainloop running, we need to manually flush the event queue + XFlush(dpy); +} diff --git a/src/input/x11FakeMouseHandler.h b/src/input/x11FakeMouseHandler.h new file mode 100644 index 0000000..0e32256 --- /dev/null +++ b/src/input/x11FakeMouseHandler.h @@ -0,0 +1,34 @@ +/* + # 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/ + # -------------------------------------------------------------------------- + # x11FakeMouseHandler.h: + # - Handle mouse events on X11 - interface + # -------------------------------------------------------------------------- + */ + +#ifndef X11FAKEMOUSEHANDLER_H_ +#define X11FAKEMOUSEHANDLER_H_ + +#include "inputEventHandler.h" + +class X11FakeMouseButtonHandler : public DefaultInputEventHandler<InputEvent::ET_BUTTON> +{ +public: + void handle(InputEvent const&, InputEventContext const* = 0); +}; + +class X11FakeMouseMovementHandler : public DefaultInputEventHandler<InputEvent::ET_POINTER> +{ +public: + void handle(InputEvent const&, InputEventContext const* = 0); +}; + +#endif /* X11FAKEMOUSEHANDLER_H_ */ diff --git a/src/input/x11InputUtils.cpp b/src/input/x11InputUtils.cpp new file mode 100644 index 0000000..b589bd6 --- /dev/null +++ b/src/input/x11InputUtils.cpp @@ -0,0 +1,29 @@ +/* + # 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/ + # ------------------------------------------------------------------------ + # src/input/x11InputUtils.h: + # Utilities for input handling under X11 - implementation + */ + +#include "x11InputUtils.h" + +static Display* _dpy = 0; + +void X11InputUtils::setDisplay(Display* dpy) +{ + _dpy = dpy; +} + +Display* X11InputUtils::display() +{ + return _dpy; +} diff --git a/src/input/x11InputUtils.h b/src/input/x11InputUtils.h new file mode 100644 index 0000000..9c85d09 --- /dev/null +++ b/src/input/x11InputUtils.h @@ -0,0 +1,27 @@ +/* + # 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/ + # ------------------------------------------------------------------------ + # src/input/x11InputUtils.h: + # Utilities for input handling under X11 - interface + */ + +#ifndef X11INPUTUTILS_H_ +#define X11INPUTUTILS_H_ + +#include <X11/Xlib.h> + +struct X11InputUtils { + static void setDisplay(Display*); + static Display* display(); +}; + +#endif /* X11INPUTUTILS_H_ */ diff --git a/src/net/mcast/CMakeLists.txt b/src/net/mcast/CMakeLists.txt new file mode 100644 index 0000000..e92b090 --- /dev/null +++ b/src/net/mcast/CMakeLists.txt @@ -0,0 +1,61 @@ +INCLUDE(../../../OpenPGMConfig.cmake) + +ADD_DEFINITIONS( + ${LIBPGM_CXXFLAGS} + -D__STDC_CONSTANT_MACROS + -D__STDC_LIMIT_MACROS +) + +# OpenPGM uses the C99 restrict keyword which g++ does not recognize: +#IF(CMAKE_COMPILER_IS_GNUCXX) +# ADD_DEFINITIONS(${LIBPGM_CXXFLAGS}) +#ENDIF(CMAKE_COMPILER_IS_GNUCXX) + +INCLUDE(${QT_USE_FILE}) + +SET(pvsmcast_MOC_HDRS + McastConfiguration.h + McastPGMSocket.h + McastReceiver.h + McastSender.h +) + +SET(pvsmcast_HDRS + McastConfiguration.h + McastPGMSocket.h + McastReceiver.h + McastSender.h +) + +SET(pvsmcast_SRCS + McastConfiguration.cpp + McastPGMSocket.cpp + McastReceiver.cpp + McastSender.cpp +) + +QT4_WRAP_CPP( + pvsmcast_MOC_SRCS + ${pvsmcast_MOC_HDRS} +) + +SET_SOURCE_FILES_PROPERTIES(${pvsmcast_SRCS} ${pvsmcast_MOC_SRCS} + PROPERTIES + OBJECT_DEPENDS "3rdparty/libpgm.a" # Make sure libpgm gets unpacked before building C++ files +) + +ADD_LIBRARY( + pvsmcast + STATIC + ${pvsmcast_HDRS} + ${pvsmcast_SRCS} + ${pvsmcast_MOC_SRCS} +) + +TARGET_LINK_LIBRARIES( + pvsmcast + pgm + ${QT_LIBRARIES} +) + +ADD_SUBDIRECTORY(trial_programs) diff --git a/src/net/mcast/McastConfiguration.cpp b/src/net/mcast/McastConfiguration.cpp new file mode 100644 index 0000000..6c5e620 --- /dev/null +++ b/src/net/mcast/McastConfiguration.cpp @@ -0,0 +1,57 @@ +/* +# 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/mcast/McastConfiguration.cpp +# - hold Multicast protocol configuration data +# ----------------------------------------------------------------------------- +*/ + +#include <QSettings> + +#include "McastConfiguration.h" + +void McastConfiguration::loadFrom(QSettings* _settings, char const* group) +{ + if (group) + _settings->beginGroup(group); + + _multicastAddress = _settings->value("groupAddress", DEFAULT_MULTICAST_ADDRESS).toString(); + _multicastInterface = _settings->value("interface", DEFAULT_MULTICAST_INTERFACE).toString(); + _multicastMTU = _settings->value("mtu", DEFAULT_MULTICAST_MTU).value<quint16>(); + _multicastRate = _settings->value("rate", DEFAULT_MULTICAST_RATE).value<quint32>(); + _multicastUseUDP = _settings->value("use-udp", DEFAULT_MULTICAST_USEUDP).toBool(); + _multicastWinSize = _settings->value("winsize", DEFAULT_MULTICAST_WSIZ).value<quint16>(); + _multicastUDPPortBase = _settings->value("portbase", DEFAULT_MULTICAST_UDPPORT).value<quint16>(); + _multicastDPort = _settings->value("dport", DEFAULT_MULTICAST_DPORT).value<quint16>(); + _multicastSPort = _settings->value("sport", DEFAULT_MULTICAST_SPORT).value<quint16>(); + + if (group) + _settings->endGroup(); +} + +void McastConfiguration::writeTo(QSettings* _settings, char const* group) const +{ + if (group) + _settings->beginGroup(group); + + _settings->setValue("groupAddress", _multicastAddress); + _settings->setValue("interface", _multicastInterface); + _settings->setValue("mtu", _multicastMTU); + _settings->setValue("rate", _multicastRate); + _settings->setValue("use-udp", _multicastUseUDP); + _settings->setValue("winsize", _multicastWinSize); + _settings->setValue("portbase", _multicastUDPPortBase); + _settings->setValue("dport", _multicastDPort); + _settings->setValue("sport", _multicastSPort); + + if (group) + _settings->endGroup(); +} diff --git a/src/net/mcast/McastConfiguration.h b/src/net/mcast/McastConfiguration.h new file mode 100644 index 0000000..53f7a54 --- /dev/null +++ b/src/net/mcast/McastConfiguration.h @@ -0,0 +1,204 @@ +/* +# 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/mcast/McastConfiguration.h +# - hold Multicast protocol configuration data +# ----------------------------------------------------------------------------- +*/ + +#ifndef MCASTCONFIGURATION_H_ +#define MCASTCONFIGURATION_H_ + +#include <QObject> +#include <QString> +#include <QtGlobal> + +#include "McastConstants.h" + +class QSettings; + +class McastConfiguration: public QObject +{ + Q_OBJECT +public: + McastConfiguration(QObject* parent = 0) : + QObject(parent), + _multicastInterface(DEFAULT_MULTICAST_INTERFACE), + _multicastAddress(DEFAULT_MULTICAST_ADDRESS), + _multicastRate(DEFAULT_MULTICAST_RATE), + _multicastSPort(DEFAULT_MULTICAST_SPORT), + _multicastDPort(DEFAULT_MULTICAST_DPORT), + _multicastWinSize(DEFAULT_MULTICAST_WSIZ), + _multicastMTU(DEFAULT_MULTICAST_MTU), + _multicastUDPPortBase(DEFAULT_MULTICAST_UDPPORT), + _multicastUseUDP(DEFAULT_MULTICAST_USEUDP) + { + } + + McastConfiguration(McastConfiguration const& other, QObject* parent = 0) : + QObject(parent), + _multicastInterface(other._multicastInterface), + _multicastAddress(other._multicastAddress), + _multicastRate(other._multicastRate), + _multicastSPort(other._multicastSPort), + _multicastDPort(other._multicastDPort), + _multicastWinSize(other._multicastWinSize), + _multicastMTU(other._multicastMTU), + _multicastUDPPortBase(other._multicastUDPPortBase), + _multicastUseUDP(other._multicastUseUDP) + { + } + + virtual ~McastConfiguration() + { + } + + QString multicastAddress() const + { + return _multicastAddress; + } + McastConfiguration* multicastAddress(QString const& address) + { + _multicastAddress = address; + return this; + } + + quint16 multicastSPort() const + { + return _multicastSPort; + } + McastConfiguration* multicastSPort(quint16 port) + { + _multicastSPort = port; + return this; + } + + quint16 multicastDPort() const + { + return _multicastDPort; + } + McastConfiguration* multicastDPort(quint16 port) + { + _multicastDPort = port; + return this; + } + + quint32 multicastRate() const + { + return _multicastRate; + } + McastConfiguration* multicastRate(quint32 rate) + { + _multicastRate = rate; + return this; + } + + quint16 multicastWinSize() const + { + return _multicastWinSize; + } + McastConfiguration* multicastWinSize(quint16 size) + { + _multicastWinSize = size; + return this; + } + + quint16 multicastMTU() const + { + return _multicastMTU; + } + McastConfiguration* multicastMTU(quint16 mtu) + { + _multicastMTU = mtu; + return this; + } + + bool multicastUseUDP() const + { + return _multicastUseUDP; + } + McastConfiguration* multicastUseUDP(bool useUDP) + { + _multicastUseUDP = useUDP; + return this; + } + + quint16 multicastUDPPortBase() const + { + return _multicastUDPPortBase; + } + McastConfiguration* multicastUDPPortBase(quint16 port) + { + _multicastUDPPortBase = port; + return this; + } + + QString multicastInterface() const + { + return _multicastInterface; + } + McastConfiguration* multicastInterface(QString const& interface) + { + _multicastInterface = interface; + return this; + } + + quint16 multicastUDPUPort() const + { + return _multicastUDPPortBase; + } + + quint16 multicastUDPMPort() const + { + return _multicastUDPPortBase; + } + + void commit() + { + emit changed(); + } + + McastConfiguration& operator=(McastConfiguration const& source) + { + if(this != &source) + { + _multicastInterface = source._multicastInterface; + _multicastAddress = source._multicastAddress; + _multicastRate = source._multicastRate; + _multicastSPort = source._multicastSPort; + _multicastDPort = source._multicastDPort; + _multicastWinSize = source._multicastWinSize; + _multicastMTU = source._multicastMTU; + _multicastUDPPortBase = source._multicastUDPPortBase; + _multicastUseUDP = source._multicastUseUDP; + } + return *this; + } + + void loadFrom(QSettings* settings, char const* group = 0); + void writeTo(QSettings* settings, char const* group = 0) const; + +signals: + void changed(); + +private: + QString _multicastInterface; + QString _multicastAddress; + quint32 _multicastRate; + quint16 _multicastSPort; + quint16 _multicastDPort; + quint16 _multicastWinSize; + quint16 _multicastMTU; + quint16 _multicastUDPPortBase; + bool _multicastUseUDP; +}; + +#endif /* MCASTCONFIGURATION_H_ */ diff --git a/src/net/mcast/McastConstants.h b/src/net/mcast/McastConstants.h new file mode 100644 index 0000000..624e195 --- /dev/null +++ b/src/net/mcast/McastConstants.h @@ -0,0 +1,36 @@ +/* +# 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/mcast/McastMagic.h +# - Specify the magic numbers for the McastFT protocol +# ----------------------------------------------------------------------------- +*/ + +#ifndef MCASTMAGIC_H_ +#define MCASTMAGIC_H_ + +#include <stdint.h> + +#define MCASTFT_MAGIC UINT64_C(0x6d60ad83825fb7f9) +#define DEFAULT_MULTICAST_INTERFACE "" +#define DEFAULT_MULTICAST_ADDRESS "239.255.220.207" +#define DEFAULT_MULTICAST_SPORT 6964 +#define DEFAULT_MULTICAST_DPORT 6965 +#define DEFAULT_MULTICAST_USEUDP true +#define DEFAULT_MULTICAST_UDPPORT 6966 +#define DEFAULT_MULTICAST_WSIZ 30 +#define DEFAULT_MULTICAST_RATE (100*1024) +#define DEFAULT_MULTICAST_MTU 1400 +#define DEFAULT_MULTICAST_APDU 1200 +#define DEFAULT_MULTICAST_CHUNK 1024 +#define DEFAULT_MULTICAST_SHUTDOWN_TIMEOUT 10000 + +#endif /* MCASTMAGIC_H_ */ diff --git a/src/net/mcast/McastPGMSocket.cpp b/src/net/mcast/McastPGMSocket.cpp new file mode 100644 index 0000000..731fc13 --- /dev/null +++ b/src/net/mcast/McastPGMSocket.cpp @@ -0,0 +1,666 @@ +/* +# 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/mcast/McastPGMSocket.cpp +# - wrap OpenPGM Sockets in a nicer interface -- implementation +# ----------------------------------------------------------------------------- +*/ + +#include <cstdlib> + +#include <sys/poll.h> +#include <sys/socket.h> + +#include <QByteArray> +#include <QList> +#include <QtDebug> +#include <QPointer> +#include <QSocketNotifier> +#include <QTimer> + +#include <pgm/pgm.h> + +#include "McastPGMSocket.h" + +using namespace std; + +class McastPGMSocket_priv +{ +public: + McastPGMSocket_priv() : + socket(0), + send_notif(0) + { + } + ~McastPGMSocket_priv() + { + if (socket) + pgm_close(socket, 0); + Q_FOREACH(QSocketNotifier* notif, _notifs) + { + delete notif; + } + if (send_notif) + delete send_notif; + } + + pgm_sock_t* socket; + McastPGMSocket::Direction direction; + QSocketNotifier* send_notif; + QList<QSocketNotifier*> _notifs; + + QSocketNotifier* notifier_for(int fd) { + Q_FOREACH(QSocketNotifier* notif, _notifs) + { + if(notif->socket() == fd) + { + return notif; + } + } + return 0; + } +}; + +static void _ensurePGMInited() +{ + if (!pgm_supported()) + { + pgm_error_t* err; + int good = pgm_init(&err); + if (!good) + { + qCritical() << "Could not init OpenPGM library: PGM Error: " << (err->message ? err->message : "(null)"); + std::exit(1); + } + } +} + +McastPGMSocket::McastPGMSocket(QObject* parent) : + QObject(parent), + _priv(new McastPGMSocket_priv), + _opened(false), + _finished(false), + _nakTimeout(new QTimer()), + _dataTimeout(new QTimer()), + _sendTimeout(new QTimer()), + _shutdownTimer(0), + _shutdown_timeout(0) +{ + _ensurePGMInited(); + + _nakTimeout->setSingleShot(true); + _dataTimeout->setSingleShot(true); + _sendTimeout->setSingleShot(true); + + connect(_nakTimeout, SIGNAL(timeout()), this, SLOT(handleNakTimeout())); + connect(_dataTimeout, SIGNAL(timeout()), this, SLOT(handleDataTimeout())); + connect(_sendTimeout, SIGNAL(timeout()), this, SLOT(canSend())); +} + +McastPGMSocket::~McastPGMSocket() +{ + delete _priv; + delete _nakTimeout; + delete _dataTimeout; + delete _sendTimeout; +} + +bool McastPGMSocket::open(McastConfiguration const* config, Direction direction) +{ + _priv->direction = direction; + + pgm_error_t* err = 0; + int good; + + pgm_addrinfo_t* addrinfo; + // parse the address string + good = pgm_getaddrinfo((config->multicastInterface() + ";" + config->multicastAddress()).toLatin1().constData(), + 0, &addrinfo, &err); + if (!good) + { + qCritical() << "Could not parse address info: PGM Error: " + << err->message; + return false; + } + + sa_family_t family = addrinfo->ai_send_addrs[0].gsr_group.ss_family; + + if(config->multicastUseUDP()) + { + good = pgm_socket(&_priv->socket, family, SOCK_SEQPACKET, IPPROTO_UDP, &err); + } + else + { + good = pgm_socket(&_priv->socket, family, SOCK_SEQPACKET, IPPROTO_PGM, &err); + } + + if (!good) + { + qCritical() << "Could not open socket: PGM Error: " << err->message; + pgm_error_free(err); + return false; + } + + unsigned const ambient_spm = 2000 * 1000; // every one hundred milliseconds (approx.) + + /* Options for sending data */ + const int spm_heartbeat[] = + { 512 * 1000, + 1024 * 1000, + 2048 * 1000, + 4096 * 1000 }, + max_rate = 0, + max_window = config->multicastWinSize() * config->multicastRate() / config->multicastMTU(); + // const int max_window_sqns = 3000; + qDebug() << "Computed window size " << max_window << " packets"; + +// pgm_setsockopt(_priv->socket, PGM_SEND_ONLY, &send_only, +// sizeof(send_only)); + + // SPM messages + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_AMBIENT_SPM, &ambient_spm, sizeof(ambient_spm)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_HEARTBEAT_SPM, &spm_heartbeat, sizeof(spm_heartbeat)); + + // Transmit window + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_TXW_MAX_RTE, &max_rate, sizeof(max_rate)); +// pgm_setsockopt(_priv->socket, PGM_TXW_SECS, &max_window, sizeof(max_window)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_TXW_SQNS, &max_window, sizeof(max_window)); + + /* Options for receiving data */ + const int passive = 0, + spmr_expiry = 500 * 1000, + nak_bo_ivl = 200 * 1000, + nak_rpt_ivl = 500 * 1000, + nak_rdata_ivl = 500 * 1000, + nak_data_retries = 50, + nak_ncf_retries = 50; + qDebug() << "Computed window size " << max_window << " packets"; + +// pgm_setsockopt(_priv->socket, PGM_RECV_ONLY, &recv_only, sizeof(recv_only)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_PASSIVE, &passive, sizeof(passive)); +// pgm_setsockopt(_priv->socket, PGM_RXW_MAX_RTE, &max_rate, sizeof(max_rate)); +// pgm_setsockopt(_priv->socket, PGM_RXW_SECS, &max_window, sizeof(max_window)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_SPMR_EXPIRY, &spmr_expiry, sizeof(spmr_expiry)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_NAK_BO_IVL, &nak_bo_ivl, sizeof(nak_bo_ivl)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_NAK_RPT_IVL, &nak_rpt_ivl, sizeof(nak_rpt_ivl)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_NAK_RDATA_IVL, &nak_rdata_ivl, sizeof(nak_rdata_ivl)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_NAK_DATA_RETRIES, &nak_data_retries, sizeof(nak_data_retries)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_NAK_NCF_RETRIES, &nak_ncf_retries, sizeof(nak_ncf_retries)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_RXW_SQNS, &max_window, sizeof(max_window)); + + /* Try using PGMCC */ + const struct pgm_pgmccinfo_t pgmccinfo = { + 100 /* usecs */ * 1000 /* msecs */, + 75 /* from OpenPGM examples */, + 500 /* from PGMCC internet-draft */ + }; + good = pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_USE_PGMCC, &pgmccinfo, sizeof(pgmccinfo)); + if(!good) + { + qCritical() << "Could not enable PGMCC"; + return false; + } + +// /* Forward Error Correction */ +// const struct pgm_fecinfo_t pgmfecinfo = { +// 255 /* from OpenPGM examples */, +// 2 /* send two proactive packets */, +// 8 /* from OpenPGM examples */, +// 1 /* enable on-demand parity */, +// 1 /* enable variable packet length */ +// }; +// good = pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_USE_FEC, &pgmfecinfo, sizeof(pgmfecinfo)); +// if(!good) +// { +// qCritical() << "Could not enable FEC"; +// return false; +// } + + // Peer Expiry: We will give 1 minute. + int const peer_expiry = 60 /* seconds */ * 1000000 /* microseconds */; + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_PEER_EXPIRY, &peer_expiry, sizeof(peer_expiry)); + + // MTU + int const mtu = config->multicastMTU(); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_MTU, &mtu, sizeof(mtu)); + + pgm_sockaddr_t addr; + addr.sa_addr.sport = config->multicastSPort(); + addr.sa_port = config->multicastDPort(); + good = pgm_gsi_create_from_hostname(&addr.sa_addr.gsi, &err); + if (!good) + { + qCritical() << "Could not generate a GSI: PGM Error: " << err->message; + pgm_error_free(err); + return false; + } + + struct pgm_interface_req_t ifreq; + ifreq.ir_interface = addrinfo->ai_send_addrs[0].gsr_interface; + ifreq.ir_scope_id = 0; + if (AF_INET6 == family) + { + ifreq.ir_scope_id = ((struct sockaddr_in6*)&addrinfo->ai_send_addrs[0])->sin6_scope_id; + } + + // UDP Encapsulation + if(config->multicastUseUDP()) + { + const int uport = config->multicastUDPUPort(); + const int mport = config->multicastUDPMPort(); + + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_UDP_ENCAP_MCAST_PORT, &mport, sizeof(mport)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_UDP_ENCAP_UCAST_PORT, &uport, sizeof(uport)); + } + + good = pgm_bind3(_priv->socket, &addr, sizeof(addr), &ifreq , sizeof(ifreq), &ifreq, sizeof(ifreq), &err); + if (!good) + { + qCritical() << "Could not bind socket: PGM Error: " << err->message; + pgm_error_free(err); + return false; + } + + // qDebug() << "Max APDU is " << _priv->socket->max_apdu; + // qDebug() << "Max TPDU is " << _priv->socket->max_tpdu; + // qDebug() << "Max TSDU Fragment is " << _priv->socket->max_tsdu_fragment; + // qDebug() << "TXW_SQNS is " << _priv->socket->txw_sqns; + + // join the group + for (unsigned i = 0; i < addrinfo->ai_recv_addrs_len; i++) + { + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_JOIN_GROUP, + &addrinfo->ai_recv_addrs[i], sizeof(struct group_req)); + } + + // set send address + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_SEND_GROUP, &addrinfo->ai_send_addrs[0], + sizeof(struct group_req)); + + // IP parameters + const int nonblocking = 1, multicast_loop = 0, multicast_hops = 16; + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_MULTICAST_LOOP, &multicast_loop, + sizeof(multicast_loop)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_MULTICAST_HOPS, &multicast_hops, + sizeof(multicast_hops)); + pgm_setsockopt(_priv->socket, IPPROTO_PGM, PGM_NOBLOCK, &nonblocking, + sizeof(nonblocking)); + + good = pgm_connect(_priv->socket, &err); + if (!good) + { + qCritical() << "Could not connect socket: PGM Error: " << err->message; + pgm_error_free(err); + return false; + } + + _opened = true; + + setupNotifiers(); + + pgm_freeaddrinfo(addrinfo); + + /* Prime the generation of SPM packets during the waiting period */ + if(_priv->direction == PSOCK_WRITE) + QTimer::singleShot(0, this, SLOT(handleNak())); + + return true; +} + +void McastPGMSocket::setupNotifiers() +{ + int recv_sock, repair_sock, pending_sock; + char const* slotname = (_priv->direction == PSOCK_WRITE) ? SLOT(handleNak(int)) : SLOT(handleData(int)); + + struct pollfd pollin[10]; + int in_nfds = 10; + pgm_poll_info(_priv->socket, pollin, &in_nfds, POLLIN); + for(int i = 0; i < in_nfds; i++) + { + QSocketNotifier* notif = new QSocketNotifier(pollin[i].fd, QSocketNotifier::Read, this); + _priv->_notifs.append(notif); + connect(notif, SIGNAL(activated(int)), this, slotname); + } + + if(_priv->direction == PSOCK_WRITE) + { + struct pollfd pfd; + int nfds = 1; + pgm_poll_info(_priv->socket, &pfd, &nfds, POLLOUT); + _priv->send_notif = new QSocketNotifier(pfd.fd, QSocketNotifier::Write, this); + connect(_priv->send_notif, SIGNAL(activated(int)), this, SLOT(canSend())); + } +} + +void McastPGMSocket::handleNak(int fd) +{ + qDebug() << "handleNak(" << fd << ")"; + + QSocketNotifier* notif = _priv->notifier_for(fd); + notif->setEnabled(false); + + if (_shutdownTimer) + { + _shutdownTimer->start(_shutdown_timeout); + qDebug() << "Started shutdown timer"; + } + + handleNak(); + + notif->setEnabled(true); +} + +void McastPGMSocket::handleNak() +{ + if (_finished) + return; + + // QTimer::singleShot(1000, this, SLOT(handleNakTimeout())); + + // to handle NAKs in OpenPGM, we need to pgm_recv: + char buf[4096]; + pgm_error_t* err = 0; + + int status; + // while we don't block: + do + { + status = pgm_recv(_priv->socket, buf, sizeof(buf), MSG_DONTWAIT, 0, &err); + + if(status == PGM_IO_STATUS_TIMER_PENDING) + { + struct timeval tv; + socklen_t size = sizeof(tv); + pgm_getsockopt(_priv->socket, IPPROTO_PGM, PGM_TIME_REMAIN, &tv, &size); + const long usecs = tv.tv_sec * 1000000 + tv.tv_usec; + int msecs = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); + if(msecs == 0) + msecs = 1; + qDebug() << " timer pending: " << usecs << "us (rounded to " << msecs << "ms)"; + _nakTimeout->start(msecs); + break; + } + else if(status == PGM_IO_STATUS_RATE_LIMITED) + { + struct timeval tv; + socklen_t size = sizeof(tv); + pgm_getsockopt(_priv->socket, IPPROTO_PGM, PGM_RATE_REMAIN, &tv, &size); + int msecs = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); + if(msecs == 0) + msecs = 1; + qDebug() << " rate limited: " << msecs << "ms"; + _nakTimeout->start(msecs); + break; + } + else if(status == PGM_IO_STATUS_WOULD_BLOCK) + { + qDebug() << " wouldblock"; + break; + } + else + { + if(err) + { + qCritical() << "Could not handle NAKs: PGM Error: " << err->message; + pgm_error_free(err); + err = 0; + } + } + } + while (true); +} + +void McastPGMSocket::handleNakTimeout() +{ + qDebug() << "handleNakTimeout()"; + + handleNak(); +} + +void McastPGMSocket::handleData(int fd) +{ + // need to guard against destruction in finish() via signals/slots + QPointer<QSocketNotifier> notif(_priv->notifier_for(fd)); + notif->setEnabled(false); + + handleData(); + + if (notif) + notif->setEnabled(true); +} + +void McastPGMSocket::handleData() +{ + if (_finished) { + return; + } + + int status; + do + { + char buf[4096]; + size_t size; + pgm_error_t* err = 0; + + status = pgm_recv(_priv->socket, buf, sizeof(buf), MSG_DONTWAIT, &size, &err); + + if (status == PGM_IO_STATUS_NORMAL) + { + qDebug() << " normally received"; + if(size > 0) + { + QByteArray bytes(buf, size); + emit receivedPacket(bytes); + } + } + else if (status == PGM_IO_STATUS_WOULD_BLOCK) + { + qDebug() << " would block"; + // nothing more to do this time + break; + } + else if (status == PGM_IO_STATUS_TIMER_PENDING) + { + struct timeval tv; + socklen_t size = sizeof(tv); + pgm_getsockopt(_priv->socket, IPPROTO_PGM, PGM_TIME_REMAIN, &tv, &size); + const long usecs = tv.tv_sec * 1000000 + tv.tv_usec; + int msecs = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); + if(msecs == 0) + msecs = 1; + qDebug() << " timer pending: " << usecs << "us (rounded to " << msecs << "ms)"; + _dataTimeout->start(msecs); + break; + } + else if (status == PGM_IO_STATUS_RATE_LIMITED) + { + struct timeval tv; + socklen_t size = sizeof(tv); + pgm_getsockopt(_priv->socket, IPPROTO_PGM, PGM_RATE_REMAIN, &tv, &size); + int msecs = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); + if(msecs == 0) + msecs = 1; + qDebug() << " rate limit pending: " << msecs << "ms"; + _dataTimeout->start(msecs); + break; + } + else if (status == PGM_IO_STATUS_RESET) + { + qDebug() << " connection reset"; + emit connectionReset(); + qCritical() << "Connection Reset: PGM Error: " << (err ? err->message : "(null)"); + pgm_error_free(err); + break; + } + else if (status == PGM_IO_STATUS_FIN) + { + qDebug() << " connection finished"; + emit connectionFinished(); + break; + } + else + { + if(err) + { + qCritical() << "Could not read packet: PGM Error: " << (err ? err->message: "(null)"); + break; + } + } + + // the socket might have been closed from under us + if (!_priv->socket) + break; + } + while (true); +} + +void McastPGMSocket::handleDataTimeout() +{ + qDebug() << "handleDataTimeout()"; + + handleData(); +} + +void McastPGMSocket::canSend() +{ + if (_finished) + return; + + + if (_priv->send_notif) + { + _priv->send_notif->setEnabled(false); + } + + if(_q.isEmpty()) + { + emit readyToSend(); + } + else + { + QByteArray const packet(_q.head()); + int status; + + status = pgm_send(_priv->socket, packet.constData(), packet.size(), 0); + + if(status == PGM_IO_STATUS_NORMAL) + { + _q.dequeue(); + if(!_q.isEmpty()) + { + _priv->send_notif->setEnabled(true); + } + else + { + emit readyToSend(); + } + } + else if(status == PGM_IO_STATUS_WOULD_BLOCK) + { + _priv->send_notif->setEnabled(true); + } + else if(status == PGM_IO_STATUS_CONGESTION) + { + qDebug() << " congested..."; + // wait a short time (10ms?) + _sendTimeout->start(10); + } + else if(status == PGM_IO_STATUS_RATE_LIMITED) + { + struct timeval tv; + socklen_t size = sizeof(tv); + pgm_getsockopt(_priv->socket, IPPROTO_PGM, PGM_RATE_REMAIN, &tv, &size); + int msecs = (tv.tv_sec * 1000) + ((tv.tv_usec + 999) / 1000); + if(msecs == 0) + msecs = 1; + qDebug() << " rate_limited, waiting" << msecs << "ms"; + _sendTimeout->start(msecs); + } + else + { + qCritical() << "Unhandled status in canSend():" << status; + } + } + + if (_shutdownTimer) + _shutdownTimer->start(_shutdown_timeout); +} + +void McastPGMSocket::sendPacket(QByteArray const& bytes) +{ + if(_shutdownTimer) + { + qCritical() << "Logic error: sendPacket() after shutdown()"; + } + + _q.enqueue(bytes); + _priv->send_notif->setEnabled(true); +} + +void McastPGMSocket::finish() +{ + if (_finished) + { + return; + } + + qDebug() << "finish()"; + + Q_FOREACH(QSocketNotifier* notif, _priv->_notifs) + { + notif->setEnabled(false); + delete notif; + } + _priv->_notifs.clear(); + + if(_priv->send_notif) + { + delete _priv->send_notif; + _priv->send_notif = 0; + } + + if (_priv->socket) + { + pgm_close(_priv->socket, 1); + _priv->socket = 0; + } + + _finished = true; + + emit connectionFinished(); + + qDebug() << "Socket finished"; +} + +bool McastPGMSocket::finished() const +{ + return _finished; +} + +bool McastPGMSocket::isOpen() const +{ + return _opened && !_finished; +} + +void McastPGMSocket::shutdown(int interval) +{ + if(_priv->direction == PSOCK_READ) + return; + + _shutdown_timeout = interval; + _shutdownTimer = new QTimer(this); + connect(_shutdownTimer, SIGNAL(timeout()), this, SLOT(finish())); + if (_q.isEmpty()) + { + _shutdownTimer->start(_shutdown_timeout); + qDebug() << "Started shutdown timer"; + } +} diff --git a/src/net/mcast/McastPGMSocket.h b/src/net/mcast/McastPGMSocket.h new file mode 100644 index 0000000..4ccf931 --- /dev/null +++ b/src/net/mcast/McastPGMSocket.h @@ -0,0 +1,81 @@ +/* +# 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/mcast/McastPGMSocket.h +# - wrap OpenPGM Sockets in a nicer interface -- interface +# ----------------------------------------------------------------------------- +*/ + +#ifndef MCASTPGMSOCKET_H_ +#define MCASTPGMSOCKET_H_ + +#include <QByteArray> +#include <QObject> +#include <QQueue> + +#include <src/net/mcast/McastConfiguration.h> +#include <src/net/mcast/McastConstants.h> + +class McastPGMSocket_priv; +class QTimer; + +class McastPGMSocket : public QObject +{ + Q_OBJECT +public: + enum Direction { + PSOCK_READ, + PSOCK_WRITE + }; + + McastPGMSocket(QObject* parent = 0); + virtual ~McastPGMSocket(); + + bool open(McastConfiguration const* config, Direction direction); + bool finished() const; + bool isOpen() const; + void shutdown(int interval = DEFAULT_MULTICAST_SHUTDOWN_TIMEOUT); + +signals: + void readyToSend(); + void receivedPacket(QByteArray const& bytes); + void connectionReset(); + void connectionFinished(); + void shutdownComplete(); + +public slots: + void sendPacket(QByteArray const& bytes); + void finish(); + +private slots: + void handleNak(int fd); + void handleData(int fd); + void handleNak(); + void handleData(); + void handleNakTimeout(); + void handleDataTimeout(); + void canSend(); + +private: + McastPGMSocket_priv* _priv; + QQueue<QByteArray> _q; + bool _finished; + bool _opened; + QTimer* _nakTimeout; + QTimer* _dataTimeout; + QTimer* _sendTimeout; + QTimer* _shutdownTimer; + int _shutdown_timeout; + + void setupNotifiers(); +}; + +#endif /* MCASTPGMSOCKET_H_ */ diff --git a/src/net/mcast/McastReceiver.cpp b/src/net/mcast/McastReceiver.cpp new file mode 100644 index 0000000..1f27127 --- /dev/null +++ b/src/net/mcast/McastReceiver.cpp @@ -0,0 +1,179 @@ +/* +# 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/mcast/McastReceiver.h +# - implement the receiver-side multicast file transfer protocol -- implementation +# ----------------------------------------------------------------------------- +*/ + +#include <QDataStream> +#include <QtDebug> +#include <QtGlobal> + +#include <pgm/pgm.h> +// OpenPGM #defines bool. This is bad in C++. +#undef bool + +#include "McastConstants.h" +#include "McastReceiver.h" + +McastReceiver::McastReceiver(QIODevice* iodev, McastConfiguration* config, QObject* parent) : + QObject(parent), + _config(config ? new McastConfiguration(*config) : 0), + _socket(0), + _curoffs(0), + _closed(false), + _hash(QCryptographicHash::Md5), + _iodev(iodev) +{ + _config->setParent(this); +} + +McastReceiver::~McastReceiver() +{ + if (_config) + delete _config; +} + +void McastReceiver::config(McastConfiguration const* config) +{ + if (_config) + delete _config; + _config = new McastConfiguration(*config, this); +} + +bool McastReceiver::start() +{ + McastConfiguration *config = _config; + if (!config) + config = new McastConfiguration(); + + if (_socket) + { + delete _socket; + } + _socket = new McastPGMSocket(this); + + connect(_socket, SIGNAL(receivedPacket(QByteArray)), SLOT(receivedPacket(QByteArray))); + connect(_socket, SIGNAL(connectionReset()), SLOT(connectionReset())); + // connect(_socket, SIGNAL(connectionFinished()), this, SLOT(connectionFinished())); + if (_socket->open(_config, McastPGMSocket::PSOCK_READ)) + { + return true; + } + else + { + disconnect(_socket, SIGNAL(receivedPacket(QByteArray)), this, SLOT(receivedPacket(QByteArray))); + disconnect(_socket, SIGNAL(connectionReset()), this, SLOT(connectionReset())); + return false; + } +} + +void McastReceiver::abort() +{ + if (_socket) + { + delete _socket; + _socket = 0; + } + + if (_iodev) + { + _iodev->close(); + } +} + +void McastReceiver::receivedPacket(QByteArray const& bytes) +{ + if(_closed) + return; + + quint16 checksum_should = qChecksum(bytes.constData(), bytes.size() - 2); + + QDataStream strm(bytes); + strm.setByteOrder(QDataStream::BigEndian); + + // read the packet + quint64 magic; + quint64 offset; + quint16 checksum; + + + strm >> magic; + if(magic != MCASTFT_MAGIC) + { + qCritical() << "Received packet whose magic number does not match. Ignoring."; + return; + } + + strm >> offset; + qDebug() << " Received packet for offset" << offset; + + if (offset == UINT64_C(0xffffffffffffffff)) + { + // this is the end of the data stream. + QByteArray md5; + strm >> md5; + + quint16 fchecksum; + strm >> fchecksum; + + // compare the hash value + if ((fchecksum != checksum_should) || (md5 != _hash.result())) + { + _close(RES_MD5_MISMATCH); + } + else + { + _close(RES_OK); + } + + return; + } + else if (offset != _curoffs) + { + qCritical() << "Packet loss or double delivery. PGM should have prevented this. Bailing out."; + _close(RES_OFFSET_MISMATCH); + return; + } + + QByteArray contents; + strm >> contents; + _curoffs += contents.size(); + + strm >> checksum; + if(checksum != checksum_should) + { + qCritical() << "Checksum does not match. Bailing out."; + _close(RES_CHECKSUM_MISMATCH); + return; + } + + _hash.addData(contents); + + _iodev->write(contents); + + emit progress(_curoffs); +} + +void McastReceiver::connectionReset() +{ + _close(RES_CONNECTION_RESET); +} + +void McastReceiver::_close(Result result) +{ + _iodev->close(); + _socket->finish(); + + _closed = true; + emit finished(result); +} diff --git a/src/net/mcast/McastReceiver.h b/src/net/mcast/McastReceiver.h new file mode 100644 index 0000000..247733d --- /dev/null +++ b/src/net/mcast/McastReceiver.h @@ -0,0 +1,80 @@ +/* +# 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/mcast/McastReceiver.h +# - implement the receiver-side multicast file transfer protocol -- interface +# ----------------------------------------------------------------------------- +*/ + +#ifndef MCASTRECEIVER_H_ +#define MCASTRECEIVER_H_ + +#include <QByteArray> +#include <QCryptographicHash> +#include <QIODevice> +#include <QObject> +#include <QtGlobal> + +#include <src/net/mcast/McastConfiguration.h> +#include <src/net/mcast/McastPGMSocket.h> + +class McastReceiver : public QObject +{ + Q_OBJECT +public: + enum Result { + RES_OK, + RES_ABORTED, + RES_OFFSET_MISMATCH, + RES_CHECKSUM_MISMATCH, + RES_MD5_MISMATCH, + RES_CONNECTION_RESET + }; + + McastReceiver(QIODevice* iodev, McastConfiguration* config = 0, QObject* parent = 0); + virtual ~McastReceiver(); + + McastConfiguration* config() + { + return _config; + } + + void config(McastConfiguration const* config); + + static inline bool is_error(Result result) + { + return result != RES_OK; + } + +signals: + void finished(int result); + void progress(quint64 offset); + +public slots: + bool start(); + void abort(); + +private: + McastConfiguration* _config; + McastPGMSocket* _socket; + quint64 _curoffs; + bool _closed; + QCryptographicHash _hash; + QIODevice* _iodev; + +private slots: + void receivedPacket(QByteArray const& bytes); + void connectionReset(); + + void _close(Result result); +}; + +#endif /* MCASTRECEIVER_H_ */ diff --git a/src/net/mcast/McastSender.cpp b/src/net/mcast/McastSender.cpp new file mode 100644 index 0000000..3fec6a4 --- /dev/null +++ b/src/net/mcast/McastSender.cpp @@ -0,0 +1,127 @@ +/* +# 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/mcast/McastReceiver.h +# - implement the receiver-side multicast file transfer protocol -- implementation +# ----------------------------------------------------------------------------- +*/ + +#include "McastSender.h" +#include "McastConstants.h" + +#include <QDataStream> +#include <QTimer> + +#include <pgm/pgm.h> +// OpenPGM #defines bool. This is bad in C++. +#undef bool + +#define MCASTFT_START_DEFER_TIME 2000 /* msec */ + +McastSender::McastSender(QIODevice* iodev, McastConfiguration const* config, QObject* parent) : + QObject(parent), + _config(config ? new McastConfiguration(*config) : new McastConfiguration()), + _socket(0), + _iodev(iodev), + _curoffs(0), + _hash(QCryptographicHash::Md5), + _finished(false) +{ +} + +McastSender::~McastSender() +{ + delete _config; +} + +void McastSender::start() +{ + _socket = new McastPGMSocket(this); + connect(_socket, SIGNAL(readyToSend()), this, SLOT(deferredStart())); + _socket->open(_config, McastPGMSocket::PSOCK_WRITE); +} + +void McastSender::start(McastPGMSocket* socket) +{ + _socket = socket; + Q_ASSERT(_socket->isOpen()); + deferredStart(); +} + +void McastSender::deferredStart() +{ + // Wait some time, to give the PGM library the chance to generate some + // undisturbed SPM messages: + QTimer::singleShot(MCASTFT_START_DEFER_TIME, this, SLOT(readyToSend())); + disconnect(_socket, SIGNAL(readyToSend()), this, SLOT(deferredStart())); + connect(_socket, SIGNAL(readyToSend()), this, SLOT(readyToSend())); +} + +void McastSender::readyToSend() +{ + if(_finished) + return; + + if(_iodev->atEnd()) + { + QByteArray fpdu; + QDataStream strm(&fpdu, QIODevice::WriteOnly); + strm.setByteOrder(QDataStream::BigEndian); + + strm << (quint64)MCASTFT_MAGIC << (quint64)UINT64_C(0xffffffffffffffff) << _hash.result(); + strm << qChecksum(fpdu.constData(), fpdu.size()); + + _socket->sendPacket(fpdu); + connect(_socket, SIGNAL(connectionFinished()), this, SLOT(socketFinished())); + _socket->shutdown(); + + _finished = true; + + _iodev->close(); + + emit allSent(); + } + else + { + QByteArray barr(DEFAULT_MULTICAST_APDU, '\0'); + qint64 len_read; + len_read = _iodev->read(barr.data(), barr.capacity()); + barr.resize((int)len_read); + + _hash.addData(barr); + + QByteArray pdu; + QDataStream strm(&pdu, QIODevice::WriteOnly); + strm.setByteOrder(QDataStream::BigEndian); + + strm << (quint64)MCASTFT_MAGIC << _curoffs; + strm << barr; + quint16 checksum = qChecksum(pdu.constData(), pdu.size()); + strm << checksum; + + _curoffs += len_read; + + _socket->sendPacket(pdu); + + emit progress(_curoffs); + } +} + +void McastSender::close() +{ + _socket->finish(); +} + +void McastSender::socketFinished() +{ + _socket->deleteLater(); + emit finished(); +} diff --git a/src/net/mcast/McastSender.h b/src/net/mcast/McastSender.h new file mode 100644 index 0000000..0c5e29f --- /dev/null +++ b/src/net/mcast/McastSender.h @@ -0,0 +1,73 @@ +/* +# 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/mcast/McastReceiver.h +# - implement the sender-side multicast file transfer protocol -- interface +# ----------------------------------------------------------------------------- +*/ + +#ifndef MCASTSENDER_H_ +#define MCASTSENDER_H_ + +#include <QCryptographicHash> +#include <QIODevice> +#include <QObject> + +#include "McastConfiguration.h" +#include "McastPGMSocket.h" + +class McastSender : public QObject +{ + Q_OBJECT +public: + McastSender(QIODevice* iodev = 0, McastConfiguration const* config = 0, QObject* parent = 0); + virtual ~McastSender(); + + McastConfiguration* config() + { + return _config; + } + + QIODevice* iodevice() const + { + return _iodev; + } + + void setIODevice(QIODevice* iodevice) + { + _iodev = iodevice; + } + +signals: + void finished(); + void progress(quint64 offset); + void allSent(); + +public slots: + void start(); + void start(McastPGMSocket* openSocket); + void close(); + +private slots: + void deferredStart(); + void readyToSend(); + void socketFinished(); + +private: + McastConfiguration* _config; + McastPGMSocket* _socket; + QIODevice* _iodev; + quint64 _curoffs; + QCryptographicHash _hash; + bool _finished; +}; + +#endif /* MCASTSENDER_H_ */ diff --git a/src/net/mcast/trial_programs/CMakeLists.txt b/src/net/mcast/trial_programs/CMakeLists.txt new file mode 100644 index 0000000..d0f68fa --- /dev/null +++ b/src/net/mcast/trial_programs/CMakeLists.txt @@ -0,0 +1,38 @@ +INCLUDE(${QT_USE_FILE}) + +QT4_WRAP_CPP( + mcastsend_MOC + mcastsend.h +) + +QT4_WRAP_CPP( + mcastreceive_MOC + mcastreceive.h +) + +SET(argparser_SRC + McastConfigArgParser.h + McastConfigArgParser.cpp +) + +ADD_EXECUTABLE(mcastsend + mcastsend.cpp + mcastsend.h + ${argparser_SRC} + ${mcastsend_MOC} +) + +ADD_EXECUTABLE(mcastreceive + mcastreceive.cpp + mcastreceive.h + ${argparser_SRC} + ${mcastreceive_MOC} +) + +TARGET_LINK_LIBRARIES(mcastsend + pvsmcast +) + +TARGET_LINK_LIBRARIES(mcastreceive + pvsmcast +)
\ No newline at end of file diff --git a/src/net/mcast/trial_programs/McastConfigArgParser.cpp b/src/net/mcast/trial_programs/McastConfigArgParser.cpp new file mode 100644 index 0000000..881f728 --- /dev/null +++ b/src/net/mcast/trial_programs/McastConfigArgParser.cpp @@ -0,0 +1,165 @@ +/* +# 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/mcast/trial_programs/McastConfigArgParser.cpp +# - Parse common Multicast Configuration CLI arguments +# ----------------------------------------------------------------------------- +*/ + +#include <iostream> + +#include <QCoreApplication> + +#include "McastConfigArgParser.h" + +using namespace std; + +bool parseMcastConfigArg(QStringList::iterator& i, QStringList::iterator const& end, McastConfiguration* config) +{ + QString arg = *i; + + if (arg == "-addr") + { + i++; + if(i == end) + { + cerr << "Option " << arg.toLatin1().constData() << " is missing argument" << endl; + return false; + } + config->multicastAddress(*i); + } + else if (arg == "-dport") + { + i++; + if(i == end) + { + cerr << "Option " << arg.toLatin1().constData() << " is missing argument" << endl; + return false; + } + bool ok; + quint16 dport = (quint16)i->toInt(&ok); + if (!ok) + { + cerr << "Error: dport is not an integer" << endl; + return false; + } + config->multicastDPort(dport); + } + else if (arg == "-sport") + { + i++; + if(i == end) + { + cerr << "Option " << arg.toLatin1().constData() << " is missing argument" << endl; + return false; + } + bool ok; + quint16 sport = (quint16)i->toInt(&ok); + if (!ok) + { + cerr << "Error: sport is not an integer" << endl; + return false; + } + config->multicastSPort(sport); + } + else if (arg == "-mtu") + { + i++; + if(i == end) + { + cerr << "Option " << arg.toLatin1().constData() << " is missing argument" << endl; + return false; + } + bool ok; + quint16 mtu = (quint16)i->toInt(&ok); + if (!ok) + { + cerr << "Error: MTU is not an integer" << endl; + return false; + } + config->multicastMTU(mtu); + } + else if (arg == "-rate") + { + i++; + if(i == end) + { + cerr << "Option " << arg.toLatin1().constData() << " is missing argument" << endl; + return false; + } + bool ok; + quint32 rate = i->toInt(&ok); + if (!ok) + { + cerr << "Error: Rate is not an integer" << endl; + return false; + } + config->multicastRate(rate); + } + else if (arg == "-winsize") + { + i++; + if(i == end) + { + cerr << "Option " << arg.toLatin1().constData() << " is missing argument" << endl; + return false; + } + bool ok; + quint16 winsize = (quint16)i->toInt(&ok); + if (!ok) + { + cerr << "Error: Winsize is not an integer" << endl; + return false; + } + config->multicastWinSize(winsize); + } + else if (arg == "-udp") + { + config->multicastUseUDP(true); + } + else if (arg == "-no-udp") + { + config->multicastUseUDP(false); + } + else if (arg == "-udp-port") + { + i++; + if(i == end) + { + cerr << "Option " << arg.toLatin1().constData() << " is missing argument" << endl; + return false; + } + bool ok; + quint16 udpport = (quint16)i->toInt(&ok); + if (!ok) + { + cerr << "Error: UDP-Port is not an integer" << endl; + return false; + } + config->multicastUDPPortBase(udpport); + } + else if (arg == "-intf") + { + i++; + if (i == end) + { + cerr << "Option " << arg.toLatin1().constData() << "is missing argument" << endl; + return false; + } + config->multicastInterface(*i); + } + else + { + return false; + } + + return true; +} diff --git a/src/net/mcast/trial_programs/McastConfigArgParser.h b/src/net/mcast/trial_programs/McastConfigArgParser.h new file mode 100644 index 0000000..4fb18a7 --- /dev/null +++ b/src/net/mcast/trial_programs/McastConfigArgParser.h @@ -0,0 +1,26 @@ +/* +# 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/mcast/trial_programs/McastConfigArgParser.h +# - Parse common Multicast Configuration CLI arguments +# ----------------------------------------------------------------------------- +*/ + +#ifndef MCASTCONFIGARGPARSER_H_ +#define MCASTCONFIGARGPARSER_H_ + +#include <QStringList> + +#include "../McastConfiguration.h" + +bool parseMcastConfigArg(QStringList::iterator& i, QStringList::iterator const& end, McastConfiguration* config); + +#endif /* MCASTCONFIGARGPARSER_H_ */ diff --git a/src/net/mcast/trial_programs/mcastreceive.cpp b/src/net/mcast/trial_programs/mcastreceive.cpp new file mode 100644 index 0000000..48a0f10 --- /dev/null +++ b/src/net/mcast/trial_programs/mcastreceive.cpp @@ -0,0 +1,150 @@ +/* +# 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/mcast/trial_programs/mcastsend.cpp +# - Receive a file via the PVS Mcast protocol +# ----------------------------------------------------------------------------- +*/ + +#include <iostream> + +#include <QCoreApplication> +#include <QFile> +#include <QStringList> +#include <QTimer> + +#include "mcastreceive.h" +#include "McastConfigArgParser.h" +#include "../McastConfiguration.h" +#include "../McastReceiver.h" + +using namespace std; + +int main(int argc, char** argv) +{ + QCoreApplication app(argc, argv); + McastReceive me; + + QTimer::singleShot(0, &me, SLOT(run())); + + return app.exec(); +} + +void McastReceive::run() +{ + QStringList args = QCoreApplication::arguments(); + QStringList::iterator i = args.begin(); + QStringList::iterator const end = args.end(); + + QString filename(""); + + McastConfiguration config; + + ++i; + while (i != end) + { + QString arg = *i; + + cerr << "Arg: " << arg.toLatin1().constData() << endl; + + if (arg == "-file") + { + ++i; + if (i == end) + { + cerr << "Option " << arg.toLatin1().constData() << " is missing its argument" << endl; + QCoreApplication::exit(1); + return; + } + filename = *i; + } + else if (arg == "-help") + { + cerr << "Options:" << endl << endl + << " -file <FILE> Receive to file FILE" << endl + << " -addr <ADDR> Use ADDR as address specification" << endl + << " -dport <PORT> Send to port PORT" << endl + << " -sport <PORT> Send from port PORT" << endl + << " -mtu <BYTES> Set MTU to BYTES" << endl + << " -rate <BYTES> Send BYTES per second" << endl + << " -winsize <SECONDS> Set Window Size to SECONDS" << endl + << " -udp Use UDP encapsulation" << endl + << " -udp-port PORT Use UDP port PORT" << endl; + QCoreApplication::quit(); + return; + } + else + { + if (!parseMcastConfigArg(i, end, &config)) + { + cerr << "Unknown argument: " << arg.toLatin1().constData() << endl; + QCoreApplication::exit(1); + return; + } + } + + ++i; + } + + if (filename == "") + { + cerr << "No Filename given" << endl; + QCoreApplication::exit(1); + return; + } + + _target = new QFile(filename, this); + _target->open(QIODevice::WriteOnly); + + McastReceiver* recv = new McastReceiver(_target, &config, this); + + connect(recv, SIGNAL(finished(int)), this, SLOT(finished(int))); + + QTimer::singleShot(0, recv, SLOT(start())); +} + +void McastReceive::finished(int state) +{ + cerr << "finished: "; + + switch(state) + { + case McastReceiver::RES_OK: + cerr << "OK." << endl; + break; + case McastReceiver::RES_ABORTED: + cerr << "Aborted." << endl; + goto failed; + case McastReceiver::RES_CHECKSUM_MISMATCH: + cerr << "Checksum mismatch." << endl; + goto failed; + case McastReceiver::RES_CONNECTION_RESET: + cerr << "Connection reset." << endl; + goto failed; + case McastReceiver::RES_MD5_MISMATCH: + cerr << "MD5 mismatch." << endl; + goto failed; + case McastReceiver::RES_OFFSET_MISMATCH: + cerr << "Offset mismatch. Undetected packet loss?" << endl; + goto failed; + default: + cerr << "Unknown error code!" << endl; + goto failed; + } + + QCoreApplication::quit(); + return; +failed: + cerr << "Deleting file." << endl; + _target->remove(); + QCoreApplication::exit(1); + return; +} diff --git a/src/net/mcast/trial_programs/mcastreceive.h b/src/net/mcast/trial_programs/mcastreceive.h new file mode 100644 index 0000000..3e72d4c --- /dev/null +++ b/src/net/mcast/trial_programs/mcastreceive.h @@ -0,0 +1,44 @@ +/* +# 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/mcast/trial_programs/mcastsend.cpp +# - Receive a file via the PVS Mcast protocol +# ----------------------------------------------------------------------------- +*/ + +#ifndef MCASTRECEIVE_H_ +#define MCASTRECEIVE_H_ + +#include <QObject> + +class QFile; +class McastReceiver; + +class McastReceive : public QObject +{ + Q_OBJECT +public: + McastReceive() : + QObject(), + _receiver(0) + { + } + +public slots: + void run(); + void finished(int state); + +private: + McastReceiver* _receiver; + QFile* _target; +}; + +#endif /* MCASTRECEIVE_H_ */ diff --git a/src/net/mcast/trial_programs/mcastsend.cpp b/src/net/mcast/trial_programs/mcastsend.cpp new file mode 100644 index 0000000..f78a9ce --- /dev/null +++ b/src/net/mcast/trial_programs/mcastsend.cpp @@ -0,0 +1,122 @@ +/* +# 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/mcast/trial_programs/mcastsend.cpp +# - Send a file via the PVS Mcast protocol +# ----------------------------------------------------------------------------- +*/ + +#include <iostream> + +#include <QCoreApplication> +#include <QFile> +#include <QStringList> +#include <QTimer> + +#include <src/net/mcast/McastSender.h> +#include "mcastsend.h" +#include "McastConfigArgParser.h" +#include "../McastConstants.h" +#include "../McastConfiguration.h" + +using namespace std; + +int +main(int argc, char**argv) +{ + QCoreApplication app(argc, argv); + McastSend me; + + QTimer::singleShot(0, &me, SLOT(run())); + + return app.exec(); +} + +void McastSend::run() +{ + QStringList args = QCoreApplication::arguments(); + QStringList::iterator i = args.begin(); + QStringList::iterator const end = args.end(); + QString filename(""); + McastConfiguration config; + + ++i; + while(i != end) + { + // parse command line arguments + + QString arg = *i; + + cerr << "Arg: " << arg.toLatin1().constData() << endl; + + if (arg == "-file") + { + i++; + if(i == end) + { + cerr << "Option " << arg.toLatin1().constData() << " is missing argument" << endl; + QCoreApplication::exit(1); + return; + } + filename = *i; + } + else if (arg == "-help") + { + cerr << "Options:" << endl << endl + << " -file <FILE> Send FILE to the listeners" << endl + << " -addr <ADDR> Use ADDR as address specification" << endl + << " -dport <PORT> Send to port PORT" << endl + << " -sport <PORT> Send from port PORT" << endl + << " -mtu <BYTES> Set MTU to BYTES" << endl + << " -rate <BYTES> Send BYTES per second" << endl + << " -winsize <SECONDS> Set Window Size to SECONDS" << endl + << " -udp Use UDP encapsulation" << endl + << " -udp-port PORT Use UDP port PORT" << endl; + QCoreApplication::quit(); + return; + } + else + { + if (!parseMcastConfigArg(i, end, &config)) + { + cerr << "Unknown command line argument: " << arg.toLatin1().constData() << endl; + QCoreApplication::exit(1); + return; + } + } + + ++i; + } + + if(filename == "") + { + cerr << "No filename given" << endl; + QCoreApplication::exit(1); + return; + } + + // now, do it. + QFile* file = new QFile(filename); + file->open(QIODevice::ReadOnly); + + McastSender* sender = new McastSender(file, &config, this); + file->setParent(sender); + + connect(sender, SIGNAL(finished()), this, SLOT(finished())); + + QTimer::singleShot(0, sender, SLOT(start())); +} + +void McastSend::finished() +{ + cerr << "finished." << endl; + QCoreApplication::quit(); +} diff --git a/src/net/mcast/trial_programs/mcastsend.h b/src/net/mcast/trial_programs/mcastsend.h new file mode 100644 index 0000000..ae15eb4 --- /dev/null +++ b/src/net/mcast/trial_programs/mcastsend.h @@ -0,0 +1,42 @@ +/* +# 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/mcast/trial_programs/mcastsend.cpp +# - Send a file via the PVS Mcast protocol +# ----------------------------------------------------------------------------- +*/ + +#ifndef MCASTSEND_H_ +#define MCASTSEND_H_ + +#include <QObject> + +#include "../McastSender.h" + +class McastSend : public QObject +{ + Q_OBJECT +public: + McastSend() : + QObject(), + _sender(0) + { + } + +public slots: + void run(); + void finished(); + +private: + McastSender* _sender; +}; + +#endif /* MCASTSEND_H_ */ diff --git a/src/net/pvsIncomingMulticastTransfer.cpp b/src/net/pvsIncomingMulticastTransfer.cpp new file mode 100644 index 0000000..10b5307 --- /dev/null +++ b/src/net/pvsIncomingMulticastTransfer.cpp @@ -0,0 +1,133 @@ +/* +# 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/pcsIncomingMulticastTransfer.h +# - wrap McastReceiver functionality in PVS daemon +# ----------------------------------------------------------------------------- +*/ + +#include <QDir> +#include <QTemporaryFile> +#include <QTimer> + +#include "pvsIncomingMulticastTransfer.h" +#include <src/net/mcast/McastReceiver.h> + +PVSIncomingMulticastTransfer::PVSIncomingMulticastTransfer(QString const& sender, qulonglong transferID, qulonglong size, QString const& filename, + ushort port, McastConfiguration const* configTemplate, QObject* parent) : + QObject(parent), + _sender(sender), + _transferID(transferID), + _bytes(0), + _size(size), + _port(port), + _file(new QFile(filename, this)), + _receiver(0), + _config(configTemplate ? + new McastConfiguration(*configTemplate) : + new McastConfiguration()), + _progressTimer(new QTimer(this)) +{ + _file->open(QIODevice::WriteOnly); + + _config->multicastUDPPortBase(port); + // _config->multicastDPort(port+1); + // _config->multicastSPort(port+2); + + connect(_progressTimer, SIGNAL(timeout()), SLOT(updateProgress())); + connect(this, SIGNAL(failed(qulonglong, QString const&)), SLOT(removeFile())); +} + +PVSIncomingMulticastTransfer::~PVSIncomingMulticastTransfer() +{ + // TODO Auto-generated destructor stub +} + +bool PVSIncomingMulticastTransfer::start() +{ + _file->open(QIODevice::WriteOnly); + _receiver = new McastReceiver(_file, new McastConfiguration(*_config), this); + connect(_receiver, SIGNAL(finished(int)), SLOT(receiverFinished(int))); + connect(_receiver, SIGNAL(progress(quint64)), SLOT(receiverProgressed(quint64))); + + if (!_receiver->start()) + { + emit retry(_sender, _transferID); + return false; + } + else + { + _progressTimer->start(333); + return true; + } +} + +void PVSIncomingMulticastTransfer::abort() +{ + delete _receiver; + _receiver = 0; + + delete _progressTimer; + _progressTimer = 0; + + if(_file) + delete _file; +} + +void PVSIncomingMulticastTransfer::updatePort(ushort port) +{ + _config->multicastUDPPortBase(port); + _config->multicastSPort(port); + _config->multicastDPort(port); +} + +void PVSIncomingMulticastTransfer::receiverProgressed(quint64 bytes) +{ + _bytes = bytes; +} + +void PVSIncomingMulticastTransfer::receiverFinished(int how) +{ + switch(how) + { + case McastReceiver::RES_OK: + emit finished(_transferID); + break; + case McastReceiver::RES_ABORTED: + emit failed(_transferID, tr("Aborted")); + break; + case McastReceiver::RES_MD5_MISMATCH: + case McastReceiver::RES_CHECKSUM_MISMATCH: + emit failed(_transferID, tr("Unrecoverable data corruption")); + break; + case McastReceiver::RES_CONNECTION_RESET: + emit failed(_transferID, tr("Connection was reset")); + break; + case McastReceiver::RES_OFFSET_MISMATCH: + emit failed(_transferID, tr("Unrecoverable data loss. Try a lower transfer rate")); + break; + } +} + +void PVSIncomingMulticastTransfer::removeFile() +{ + if(_file) + _file->remove(); +} + +void PVSIncomingMulticastTransfer::updateProgress() +{ + if (!_started) + { + emit started(_transferID); + } + emit progress(_transferID, _bytes, _size); +} diff --git a/src/net/pvsIncomingMulticastTransfer.h b/src/net/pvsIncomingMulticastTransfer.h new file mode 100644 index 0000000..f96e176 --- /dev/null +++ b/src/net/pvsIncomingMulticastTransfer.h @@ -0,0 +1,71 @@ +/* +# 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/pcsIncomingMulticastTransfer.h +# - wrap McastReceiver functionality in PVS daemon +# ----------------------------------------------------------------------------- +*/ + +#ifndef PVSINCOMINGMULTICASTTRANSFER_H_ +#define PVSINCOMINGMULTICASTTRANSFER_H_ + +#include <QObject> +#include <QString> + +#include <src/net/pvsMsg.h> + +class McastConfiguration; +class McastReceiver; +class QFile; +class QTimer; + +class PVSIncomingMulticastTransfer : public QObject +{ + Q_OBJECT +public: + PVSIncomingMulticastTransfer(QString const& sender, qulonglong transferID, qulonglong size, QString const& filename, ushort port, + McastConfiguration const* configTemplate, QObject* parent = 0); + virtual ~PVSIncomingMulticastTransfer(); + + void setFinalFile(QString const& filename); + +signals: + void retry(QString const& sender, qulonglong transferID); + void started(qulonglong transferID); + void progress(qulonglong transferID, qulonglong bytes, qulonglong of); + void finished(qulonglong transferID); + void failed(qulonglong transferID, QString const& reason); + +public slots: + void updatePort(ushort port); + bool start(); + void abort(); + +private slots: + void receiverProgressed(quint64 bytes); + void receiverFinished(int reason); + void updateProgress(); + void removeFile(); + +private: + QString _sender; + qulonglong _transferID; + qulonglong _bytes; + qulonglong _size; + ushort _port; + QFile* _file; + McastReceiver* _receiver; + McastConfiguration* _config; + bool _started; + QTimer* _progressTimer; +}; + +#endif /* PVSINCOMINGMULTICASTTRANSFER_H_ */ diff --git a/src/net/pvsListenServer.cpp b/src/net/pvsListenServer.cpp index 58d4aee..90c2dfb 100644 --- a/src/net/pvsListenServer.cpp +++ b/src/net/pvsListenServer.cpp @@ -21,9 +21,13 @@ #include "pvsClientConnection.h" #include "src/util/consoleLogger.h" #include <QtNetwork/QSslSocket> +#include <QBuffer> +#include <QByteArray> +#include <QSettings> #include "SslServer.h" #include <cassert> //#define verbose +#include "mcast/McastConfiguration.h" // Create listener PVSListenServer::PVSListenServer(int port, int clients) @@ -36,6 +40,7 @@ PVSListenServer::PVSListenServer(int port, int clients) else _clientsMax = clients; _port = port; + _mcastConfig = 0; init(); } @@ -178,6 +183,7 @@ void PVSListenServer::onClientConnected(PVSClientConnection* connected) { connected->setServerID(_id); connected->setID(generateID()); + connected->push_back_send(mcastConfigMessage()); } void PVSListenServer::onClientDisconnected(PVSClientConnection* disconnected) @@ -227,6 +233,11 @@ PVSClientConnection* PVSListenServer::getConnectionFromID(int id) // Initialize listening socket bool PVSListenServer::init() { + if (_mcastConfig) + delete _mcastConfig; + _mcastConfig = new McastConfiguration(this); + loadMcastConfig(); + if (_listenSocket != NULL) shutdown(); @@ -305,3 +316,52 @@ bool PVSListenServer::isListening() { return _listenSocket != NULL && _listenSocket->isListening(); } + +void PVSListenServer::loadMcastConfig() +{ + QSettings settings; + _mcastConfig->loadFrom(&settings, "multicast-filetransfer"); +} + +void PVSListenServer::saveMcastConfig() +{ + QSettings settings; + _mcastConfig->writeTo(&settings, "multicast-filetransfer"); + settings.sync(); +} + +PVSMsg PVSListenServer::mcastConfigMessage() +{ + // If anything is changed here, do not forget to + // 1. assign a new version number + // 2. adapt PVS::onCommand(PVSMsg) in pvs.cpp + QByteArray ba; + QDataStream strm(&ba, QIODevice::WriteOnly); + strm << (quint16)1 // version + << _mcastConfig->multicastAddress() + << _mcastConfig->multicastUDPPortBase() + << _mcastConfig->multicastDPort() + << _mcastConfig->multicastSPort() + << _mcastConfig->multicastMTU() + << _mcastConfig->multicastWinSize() + << _mcastConfig->multicastRate() + << _mcastConfig->multicastUseUDP(); + + QByteArray b64 = ba.toBase64(); + QString message = QString::fromAscii(b64.constData(), b64.length()); + PVSMsg msg(PVSCOMMAND, "MCASTFTCONFIG", message); + return msg; +} + +void PVSListenServer::multicastReconfigure(McastConfiguration const* source) +{ + _mcastConfig->multicastAddress(source->multicastAddress()); + *_mcastConfig = *source; + saveMcastConfig(); + sendToAll(mcastConfigMessage()); +} + +McastConfiguration const* PVSListenServer::getMulticastConfiguration() +{ + return _mcastConfig; +} diff --git a/src/net/pvsListenServer.h b/src/net/pvsListenServer.h index b43b730..ca9977c 100644 --- a/src/net/pvsListenServer.h +++ b/src/net/pvsListenServer.h @@ -10,6 +10,7 @@ class SslServer; class PVSClientConnection; class PVSMsg; +class McastConfiguration; class PVSListenServer : public QObject { @@ -40,6 +41,12 @@ private: bool init(); unsigned int generateID(); + McastConfiguration* _mcastConfig; + + void loadMcastConfig(); + void saveMcastConfig(); + PVSMsg mcastConfigMessage(); + protected: void timerEvent(QTimerEvent *event); @@ -60,6 +67,9 @@ public: bool disconnectClient(PVSClientConnection* delinquent); void onConnectionRemoved(PVSClientConnection* delinquent); + void multicastReconfigure(McastConfiguration const* source); + McastConfiguration const* getMulticastConfiguration(); + std::list<PVSClientConnection*>* getClientListPtr() { return &_clients; diff --git a/src/net/pvsNetworkInterfaceListModel.cpp b/src/net/pvsNetworkInterfaceListModel.cpp new file mode 100644 index 0000000..67d0c0a --- /dev/null +++ b/src/net/pvsNetworkInterfaceListModel.cpp @@ -0,0 +1,81 @@ +/* + * pvsNetworkInterfaceListModel.cpp + * + * Created on: 04.08.2010 + * Author: brs + */ + +#include "pvsNetworkInterfaceListModel.h" +#include <QStringList> + +PVSNetworkInterfaceListModel::PVSNetworkInterfaceListModel(QObject* parent) : + QAbstractListModel(parent) +{ + reloadInterfaceList(); +} + +PVSNetworkInterfaceListModel::~PVSNetworkInterfaceListModel() +{ +} + +void PVSNetworkInterfaceListModel::reloadInterfaceList() +{ + _interfaces = QNetworkInterface::allInterfaces(); + reset(); +} + +QVariant PVSNetworkInterfaceListModel::data(QModelIndex const& index, int role) const +{ + int i = index.row(); + if(0 > i || i >= _interfaces.size()) + { + return QVariant(); + } + QNetworkInterface intf = _interfaces.at(i); + + switch(role) + { + case Qt::DisplayRole: + { + QString name = intf.humanReadableName(); + QList<QNetworkAddressEntry> addresses = intf.addressEntries(); + QStringList l; + + foreach(QNetworkAddressEntry addr, addresses) + { + l.append(addr.ip().toString()); + } + + return QString("%1 (%2)").arg(name).arg(l.join(", ")); + } + case Qt::EditRole: + case Qt::UserRole: + return intf.name(); + default: + return QVariant(); + } +} + +QVariant PVSNetworkInterfaceListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(section == 0 && orientation == Qt::Vertical && role == Qt::DisplayRole) + { + return tr("Interface"); + } + else + { + return QVariant(); + } +} + +int PVSNetworkInterfaceListModel::rowCount(QModelIndex const& parent) const +{ + if(parent.isValid()) + { + return 0; + } + else + { + return _interfaces.size(); + } +} diff --git a/src/net/pvsNetworkInterfaceListModel.h b/src/net/pvsNetworkInterfaceListModel.h new file mode 100644 index 0000000..3a9b95d --- /dev/null +++ b/src/net/pvsNetworkInterfaceListModel.h @@ -0,0 +1,35 @@ +/* + * pvsNetworkInterfaceListModel.h + * + * Created on: 04.08.2010 + * Author: brs + */ + +#ifndef PVSNETWORKINTERFACELISTMODEL_H_ +#define PVSNETWORKINTERFACELISTMODEL_H_ + +#include <QAbstractListModel> +#include <QList> +#include <QNetworkInterface> +#include <QVariant> + +class PVSNetworkInterfaceListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + PVSNetworkInterfaceListModel(QObject* parent = 0); + virtual ~PVSNetworkInterfaceListModel(); + + QVariant data(QModelIndex const& index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + int rowCount(QModelIndex const&) const; + +private: + QList<QNetworkInterface> _interfaces; + +public slots: + void reloadInterfaceList(); +}; + +#endif /* PVSNETWORKINTERFACELISTMODEL_H_ */ diff --git a/src/net/pvsOutgoingMulticastTransfer.cpp b/src/net/pvsOutgoingMulticastTransfer.cpp new file mode 100644 index 0000000..4df4986 --- /dev/null +++ b/src/net/pvsOutgoingMulticastTransfer.cpp @@ -0,0 +1,209 @@ +/* +# 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/pvsOutgoingMulticastTransfer.cpp +# - wrap McastSender functionality in PVS daemon +# ----------------------------------------------------------------------------- +*/ + +#include "pvsOutgoingMulticastTransfer.h" + +#include <QDataStream> +#include <QFile> +#include <QFileInfo> +#include <QHostInfo> +#include <QSettings> +#include <QTimer> + +#include <src/net/pvsMsg.h> +#include <src/net/mcast/McastConfiguration.h> +#include <src/net/mcast/McastPGMSocket.h> +#include <src/net/mcast/McastSender.h> +#include <src/util/consoleLogger.h> + +PVSOutgoingMulticastTransfer::PVSOutgoingMulticastTransfer(QString senderName, quint64 id, QString filename, QObject* parent) : + QObject(parent), + _file(0), + _progress(0), + _config(0), + _socket(0), + _progressTimer(0), + _prepareTimer(0), + _senderName(senderName), + _id(id), + _error(false) +{ + QFileInfo finfo(filename); + if(!finfo.exists()) + { + error("File does not exist"); + } + + if(!finfo.isReadable()) + { + error("File is not readable"); + } + + _file = new QFile(filename, this); + _length = _file->size(); +} + +PVSOutgoingMulticastTransfer::~PVSOutgoingMulticastTransfer() +{ + if(_file) + delete _file; + if(_config) + delete _config; +} + +void PVSOutgoingMulticastTransfer::prepare() +{ + if (_socket && !_socketInacceptable) + { + _prepareTimer->stop(); + delete _prepareTimer; + _prepareTimer = 0; + + QTimer::singleShot(0, this, SLOT(doStart())); + return; + } + else if (_socket) + { + delete _socket; + _socket = 0; + } + + QSettings settings; + quint16 portbase = settings.value("multicast/port-base", "6966").value<quint16>(); + quint16 portlimit = settings.value("multicast/port-limit", "7966").value<quint16>(); + + int tries_remaining = 5; + while(tries_remaining > 0) + { + quint16 port = portbase + (qrand() % (portlimit - portbase + 1)); + + if (!_config) + { + _config = new McastConfiguration(); + } + _config->loadFrom(&settings, "multicast"); + _config->multicastUDPPortBase(port); + + _socket = new McastPGMSocket(this); + if(_socket->open(_config, McastPGMSocket::PSOCK_WRITE)) + { + break; + } + else + { + delete _socket; + _socket = 0; + } + } + + if (!_socket) + { + emit failed(_id, "Could not open socket"); + delete _prepareTimer; + _prepareTimer = 0; + return; + } + else + { + _socketInacceptable = false; + // announce the transfer: + QFileInfo info(*_file); + QString message = QString("%1:%2:%3:%4:%5").arg(_senderName).arg(_id).arg(info.fileName()).arg(info.size()).arg(_config->multicastUDPPortBase()); + PVSMsg msg(PVSCOMMAND, "MCASTFTANNOUNCE", message); + emit announce(msg); + _prepareTimer->start(5000); + } +} + +void PVSOutgoingMulticastTransfer::doStart() +{ + ConsoleLog writeLine(QString("Starting multicast transfer %1").arg(_id)); + + _file->open(QIODevice::ReadOnly); + + _sender = new McastSender(_file, _config, this); + connect(_sender, SIGNAL(finished()), SLOT(senderFinished())); + connect(_sender, SIGNAL(progress(quint64)), SLOT(senderProgress(quint64))); + // connect(_sender, SIGNAL(allSent()), SIGNAL(allSent())); + _socket->setParent(_sender); + _sender->start(_socket); + + emit started(_id); + + _progressTimer = new QTimer(this); + connect(_progressTimer, SIGNAL(timeout()), SLOT(reportTimeout())); + _progressTimer->setInterval(333); + _progressTimer->start(); +} + +void PVSOutgoingMulticastTransfer::senderProgress(quint64 bytes) + +{ + _progress = bytes; +} + +void PVSOutgoingMulticastTransfer::senderFinished() +{ + if(_progressTimer) + { + _progressTimer->stop(); + delete _progressTimer; + _progressTimer = 0; + } + emit finished(_id); + _sender->close(); + delete _sender; +} + +void PVSOutgoingMulticastTransfer::reportTimeout() +{ + emit progress(_id, _progress, _length); +} + +void PVSOutgoingMulticastTransfer::retry() +{ + bool first = !_socketInacceptable; + _socketInacceptable = true; + + if(first) + { + _prepareTimer->setInterval(1000); + } +} + +void PVSOutgoingMulticastTransfer::start() +{ + if (!_prepareTimer) + { + _prepareTimer = new QTimer(this); + _prepareTimer->setSingleShot(true); + connect(_prepareTimer, SIGNAL(timeout()), SLOT(prepare())); + } + QTimer::singleShot(0, this, SLOT(prepare())); +} + +void PVSOutgoingMulticastTransfer::abort() +{ + if (_sender) + _sender->close(); +} + +void PVSOutgoingMulticastTransfer::error(QString const& reason) +{ + qCritical() << "Could not create an outgoing mcast transfer: " << reason; + _error = true; + _reason = reason; +} diff --git a/src/net/pvsOutgoingMulticastTransfer.h b/src/net/pvsOutgoingMulticastTransfer.h new file mode 100644 index 0000000..5fd6a3d --- /dev/null +++ b/src/net/pvsOutgoingMulticastTransfer.h @@ -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/net/pvsOutgoingMulticastTransfer.h +# - wrap McastSender functionality in PVS daemon +# ----------------------------------------------------------------------------- +*/ + +#ifndef PVSOUTGOINGMULTICASTTRANSFER_H_ +#define PVSOUTGOINGMULTICASTTRANSFER_H_ + +#include <QtGlobal> +#include <QObject> +#include <QString> + +#include <src/net/pvsMsg.h> + +class QFile; +class QTimer; +class McastConfiguration; +class McastPGMSocket; +class McastSender; + +class PVSOutgoingMulticastTransfer : public QObject +{ + Q_OBJECT +public: + PVSOutgoingMulticastTransfer(QString senderName, quint64 id, QString filename, QObject* parent = 0); + virtual ~PVSOutgoingMulticastTransfer(); + + quint64 id() const + { + return _id; + } + + bool isError() const + { + return _error; + } + + QString reason() const + { + return _reason; + } + +signals: + void started(qulonglong id); + void progress(qulonglong id, qulonglong bytes, qulonglong of); + void allSent(qulonglong id); + void finished(qulonglong id); + void failed(qulonglong id, QString const reason); + void announce(PVSMsg announcement); + +private slots: + void senderProgress(quint64 bytes); + void senderFinished(); + void reportTimeout(); + void doStart(); + void prepare(); + +public slots: + void start(); + void abort(); + void retry(); + +private: + QFile* _file; + quint64 _length; + quint64 _progress; + McastConfiguration* _config; + McastPGMSocket* _socket; + McastSender* _sender; + QTimer* _progressTimer; + QTimer* _prepareTimer; + QString _senderName; + quint64 _id; + bool _error; + QString _reason; + bool _socketInacceptable; + + void error(QString const& reason); +}; + +#endif /* PVSOUTGOINGMULTICASTTRANSFER_H_ */ diff --git a/src/net/pvsServerConnection.h b/src/net/pvsServerConnection.h index 0669d88..c6ef015 100644 --- a/src/net/pvsServerConnection.h +++ b/src/net/pvsServerConnection.h @@ -11,9 +11,9 @@ #define _PVSSERVERCONNECTION_H_ #include "src/util/dispatcher.h" +#include "src/net/pvsMsg.h" #include <QtNetwork/QSslSocket> -class PVSMsg; class PVS; class PVSDiscoveredServer; @@ -30,8 +30,6 @@ public: return _socket != NULL && _socket->state() == QAbstractSocket::ConnectedState; } - void sendMessage(PVSMsg newMessage); - void ping(); QString getServerName(); @@ -63,6 +61,9 @@ public: _commandDispatcher.removeListener(ident, who, func); }; +public Q_SLOTS: + void sendMessage(PVSMsg newMessage); + protected: void timerEvent(QTimerEvent *event); diff --git a/src/pvs.cpp b/src/pvs.cpp index b343f29..bda16f8 100755 --- a/src/pvs.cpp +++ b/src/pvs.cpp @@ -10,11 +10,21 @@ # General information about OpenSLX can be found at http://openslx.org/ */ +#include <QCryptographicHash> +#include <QDataStream> +#include <QDateTime> + #include "pvs.h" #include "src/util/dispatcher.h" #include "src/net/pvsMsg.h" #include "src/net/pvsServiceDiscovery.h" #include "src/net/pvsDiscoveredServer.h" +#include "src/input/inputEvent.h" +#include "src/input/inputHandlerChain.h" +#include "src/input/x11InputUtils.h" +#include "src/net/mcast/McastConfiguration.h" +#include "src/net/pvsOutgoingMulticastTransfer.h" +#include "src/net/pvsIncomingMulticastTransfer.h" // D-Bus #include "pvsadaptor.h" @@ -41,6 +51,9 @@ PVS::PVS() : /*#endif*/ _vncPort = -1; + _masterMcastConfig = new McastConfiguration(this); + _masterMcastConfig->loadFrom(&_settings, "multicast"); + // add a notify to the allow file, so we get informed when the file is changed QString watchPath(getPolicyDir()); watchPath.append(QString(".allow")); @@ -80,6 +93,8 @@ PVS::PVS() : sigaction(SIGHUP, &act, 0); sigaction(SIGINT, &act, 0); sigaction(SIGQUIT, &act, 0); + + initializeInputEventHandling(); #endif /*__WIN32__*/ } @@ -181,6 +196,67 @@ void PVS::onCommand(PVSMsg cmdMessage) unlock(); return; } + if (ident.compare("INPUTEVENT") == 0) + { + InputEvent evt; + eventFromString(message, evt); + handleInputEvent(evt); + } + if (ident.compare("MCASTFTRETRY") == 0) + { + QStringList fields = message.split(':'); + if (fields[0].compare(getUserName()) == 0) + { + quint64 id = fields[1].toULongLong(); + PVSOutgoingMulticastTransfer* transfer = _outgoingTransfers.value(id, 0); + if (transfer) + transfer->retry(); + } + } + if (ident.compare("MCASTFTANNOUNCE") == 0) + { + QStringList fields = message.split(':'); + bool ok; + QString sender; + qulonglong transferID; + QString basename; + qulonglong size; + ushort port; + + if (!fields.size() == 5) + { + goto malformedAnnounce; + } + sender = fields[0]; + transferID = fields[1].toULongLong(&ok); + if (!ok) + { + goto malformedAnnounce; + } + basename = fields[2]; + size = fields[3].toULongLong(&ok); + if (!ok) + { + goto malformedAnnounce; + } + port = fields[4].toUShort(&ok); + if (!ok) + { + goto malformedAnnounce; + } + + onIncomingMulticastTransfer(sender, transferID, basename, size, port); + return; + + malformedAnnounce: + qDebug() << "Ignoring malformed MCASTFTANNOUNCE command: " << message; + return; + } + if (ident.compare("MCASTFTCONFIG") == 0) + { + loadMcastConfig(message); + return; + } if (ident.compare("SHOWPROCESSES") == 0) { _pvsServerConnection->sendMessage(PVSMsg(PVSCOMMAND, "PROCESSES", "SHOW clear")); //tell the client that we want to clear his process-list @@ -652,6 +728,191 @@ void PVS::signalHandler(int signal) } +bool PVS::createMulticastTransfer(QString const& objectPath, quint64& transferID, QString& errorReason) +{ + transferID = generateMcastTransferID(); + + PVSOutgoingMulticastTransfer* transfer = new PVSOutgoingMulticastTransfer(getUserName(), transferID, objectPath, this); + if (transfer->isError()) + { + errorReason = transfer->reason(); + delete transfer; + return false; + } + + _outgoingTransfers.insert(transferID, transfer); + connect(transfer, SIGNAL(started(qulonglong)), SIGNAL(outgoingMulticastTransferStarted(qulonglong))); + connect(transfer, SIGNAL(finished(qulonglong)), SIGNAL(outgoingMulticastTransferFinished(qulonglong))); + connect(transfer, SIGNAL(failed(qulonglong, QString const)), SIGNAL(outgoingMulticastTransferFailed(qulonglong, QString const))); + connect(transfer, SIGNAL(progress(qulonglong, qulonglong, qulonglong)), SIGNAL(outgoingMulticastTransferProgress(qulonglong, qulonglong, qulonglong))); + connect(transfer, SIGNAL(announce(PVSMsg)), _pvsServerConnection, SLOT(sendMessage(PVSMsg))); + connect(transfer, SIGNAL(finished(qulonglong)), SLOT(outgoingMulticastTransferDelete(qulonglong))); + connect(transfer, SIGNAL(failed(qulonglong, QString const)), SLOT(outgoingMulticastTransferDelete(qulonglong))); + QTimer::singleShot(0, transfer, SLOT(start())); + errorReason = ""; + return true; +} + +// Input handling + +void PVS::handleInputEvent(InputEvent const& evt) +{ + std::string s = evt.toString(); + ConsoleLog writeLine(QString("Received input event: %1").arg(s.c_str())); + _inputEventHandlers.handle(evt); +} + +void PVS::initializeInputEventHandling() +{ + X11InputUtils::setDisplay(X11Info::display()); + _inputEventHandlers.initialize(); +} + +void PVS::cancelOutgoingMulticastTransfer(quint64 transferID) +{ + PVSOutgoingMulticastTransfer* transfer = _outgoingTransfers.value(transferID, 0); + + if (transfer) + { + _outgoingTransfers.remove(transferID); + delete transfer; + } +} + +void PVS::cancelIncomingMulticastTransfer(qulonglong transferID) +{ + PVSIncomingMulticastTransfer* transfer = _incomingTransfers.value(transferID, 0); + + if(transfer) + { + _incomingTransfers.remove(transferID); + delete transfer; + } +} + +void PVS::onIncomingMulticastTransfer(QString const& sender, qulonglong transferID, + QString const& basename, qulonglong size, ushort port) +{ + if (_outgoingTransfers.contains(transferID)) + return; + + PVSIncomingMulticastTransfer* transfer; + if (transfer = _incomingTransfers.value(transferID, 0)) + { + transfer->updatePort(port); + QTimer::singleShot(0, transfer, SLOT(start())); + } + else + { + QString filename = QFileInfo(QDir::home(), QString("%1.part.%2").arg(basename).arg(transferID, 0, 16)).absoluteFilePath(); + transfer = new PVSIncomingMulticastTransfer(sender, transferID, size, filename, port, _masterMcastConfig, this); + _incomingTransfers.insert(transferID, transfer); + + connect(transfer, SIGNAL(retry(QString const&, qulonglong)), SLOT(onIncomingMulticastTransferRetry(QString const&, qulonglong))); + connect(transfer, SIGNAL(started(qulonglong)), SIGNAL(incomingMulticastTransferStarted(qulonglong))); + connect(transfer, SIGNAL(progress(qulonglong, qulonglong, qulonglong)), SIGNAL(incomingMulticastTransferProgress(qulonglong, qulonglong, qulonglong))); + connect(transfer, SIGNAL(finished(qulonglong)), SIGNAL(incomingMulticastTransferFinished(qulonglong))); + connect(transfer, SIGNAL(failed(qulonglong, QString const&)), SIGNAL(incomingMulticastTransferFailed(qulonglong, QString))); + connect(transfer, SIGNAL(finished(qulonglong)), SLOT(incomingMulticastTransferDelete(qulonglong))); + connect(transfer, SIGNAL(failed(qulonglong, QString const&)), SLOT(incomingMulticastTransferDelete(qulonglong))); + + emit incomingMulticastTransferNew(transferID, sender, filename, size); + QTimer::singleShot(0, transfer, SLOT(start())); + } +} + +void PVS::onIncomingMulticastTransferRetry(QString const& sender, qulonglong transferID) +{ + PVSMsg retryMessage(PVSCOMMAND, "MCASTFTRETRY", QString("%1:%2").arg(sender).arg(transferID)); + _pvsServerConnection->sendMessage(retryMessage); +} + +quint64 PVS::generateMcastTransferID() +{ + static quint64 nodeID = 0; + static quint16 counter = 0; + + if (!nodeID) + { + QDateTime t = QDateTime::currentDateTime(); + QCryptographicHash h(QCryptographicHash::Md5); + h.addData(getUserName().toLocal8Bit()); + h.addData(t.toString().toLocal8Bit()); + QDataStream(h.result()) >> nodeID; + } + + return (nodeID & Q_UINT64_C(0xffffffffffff0000)) | (quint64)(++counter); +} + +void PVS::outgoingMulticastTransferDelete(qulonglong transferID) +{ + PVSOutgoingMulticastTransfer* transfer = _outgoingTransfers.value(transferID, 0); + if (!transfer) + return; + + _outgoingTransfers.remove(transferID); + transfer->deleteLater(); +} + +void PVS::incomingMulticastTransferDelete(qulonglong transferID) +{ + PVSIncomingMulticastTransfer* transfer = _incomingTransfers.value(transferID, 0); + if (!transfer) + { + return; + } + + _incomingTransfers.remove(transferID); + transfer->deleteLater(); +} + +void PVS::loadMcastConfig(QString const& message) +{ + QByteArray ba = QByteArray::fromBase64(message.toAscii()); + QDataStream d(&ba, QIODevice::ReadOnly); + quint16 ver, udpp, dp, sp, mtu, wsz; + quint32 rate; + QString addr; + bool useudp; + + d >> ver; + if(ver != 1) + { + ConsoleLog writeLine(QString("Unable to decode multicast configuration message: Unknown version %1").arg(ver)); + return; + } + + d >> addr + >> udpp + >> dp + >> sp + >> mtu + >> wsz + >> rate + >> useudp; + if(d.status() != QDataStream::Ok) + { + ConsoleLog writeLine(QString("Unable to decode multicast configuration message: There was an error reading")); + return; + } + + _masterMcastConfig->multicastUDPPortBase(udpp); + _masterMcastConfig->multicastDPort(dp); + _masterMcastConfig->multicastSPort(sp); + _masterMcastConfig->multicastMTU(mtu); + _masterMcastConfig->multicastWinSize(wsz); + _masterMcastConfig->multicastRate(rate); + _masterMcastConfig->multicastAddress(addr); + _masterMcastConfig->multicastUseUDP(useudp); + + QSettings settings; + _masterMcastConfig->writeTo(&settings, "multicast"); + settings.sync(); + + ConsoleLog writeLine(QString("Reconfigured multicast filetransfer to IP %1, UDP port base %2, destination port %3, source port %4, MTU %5, Window Size %6, rate %7, %8using UDP") + .arg(addr).arg(udpp).arg(dp).arg(sp).arg(mtu).arg(wsz).arg(rate).arg(useudp ? "" : "not ")); +} + void PVS::setConfigValue(QString key, QString value) { _settings.setValue(key, value); @@ -29,9 +29,14 @@ #include <QFile> #include <QTextStream> #include <QDir> +#include "src/input/inputHandlerChain.h" class PVSServiceDiscovery; class PVSDiscoveredServer; +class InputEvent; +class McastConfiguration; +class PVSOutgoingMulticastTransfer; +class PVSIncomingMulticastTransfer; /** * PVSClient @@ -84,6 +89,11 @@ public Q_SLOTS: QStringList getAvailableHosts(); QString getIpByNick(QString nick); + // Multicast File Transfer + bool createMulticastTransfer(QString const& objectPath, quint64& transferID, QString& errorReason); + void cancelOutgoingMulticastTransfer(quint64 transferID); + void cancelIncomingMulticastTransfer(qulonglong transferID); + void setConfigValue(QString key, QString value); QString getConfigValue(QString key); @@ -100,6 +110,17 @@ Q_SIGNALS: void addHost(QString host); void delHost(QString host); + // Multicast File Transfer + void outgoingMulticastTransferStarted(qulonglong transferID); + void outgoingMulticastTransferProgress(qulonglong transferID, qulonglong bytes, qulonglong of); + void outgoingMulticastTransferFinished(qulonglong transferID); + void outgoingMulticastTransferFailed(qulonglong transferID, QString reason); + void incomingMulticastTransferNew(qulonglong transferID, QString sender, QString filename, qulonglong size); + void incomingMulticastTransferStarted(qulonglong transferID); + void incomingMulticastTransferProgress(qulonglong transferID, qulonglong bytes, qulonglong of); + void incomingMulticastTransferFinished(qulonglong transferID); + void incomingMulticastTransferFailed(qulonglong transferID, QString reason); + protected: void timerEvent(QTimerEvent *event); @@ -151,7 +172,27 @@ private: int _timerLockTest; int _timerLockDelay; - QSettings _settings; + // input event handling: + unprivileged_handler_chain _inputEventHandlers; + void handleInputEvent(InputEvent const& evt); + void initializeInputEventHandling(); + + // multicast transfer + McastConfiguration* _masterMcastConfig; + QHash<quint64, PVSOutgoingMulticastTransfer*> _outgoingTransfers; + QHash<quint64, PVSIncomingMulticastTransfer*> _incomingTransfers; + void loadMcastConfig(QString const& encoded); + void onIncomingMulticastTransfer(QString const& sender, qulonglong transferID, QString const& basename, qulonglong size, ushort port); + static quint64 generateMcastTransferID(); + +private Q_SLOTS: + // housekeeping + void outgoingMulticastTransferDelete(qulonglong transferID); + void incomingMulticastTransferDelete(qulonglong transferID); + void onIncomingMulticastTransferRetry(QString const& sender, qulonglong transferID); + +private: + QSettings _settings; }; #endif /* PVSCLIENT_H_ */ diff --git a/src/pvsDaemon.cpp b/src/pvsDaemon.cpp index 584a65d..1784a14 100644 --- a/src/pvsDaemon.cpp +++ b/src/pvsDaemon.cpp @@ -64,6 +64,9 @@ int main(int argc, char** argv) bool _daemon = false; int frequency = 5; int port = -1; +#ifdef as_daemon + bool no_fork = false; +#endif QCoreApplication app(argc, argv); app.setOrganizationName("openslx"); @@ -119,11 +122,14 @@ int main(int argc, char** argv) { "freq", required_argument, 0, 'f' }, { "client", required_argument, 0, 'e' }, { "script", required_argument, 0, 's' }, +#ifdef as_daemon + { "no-fork", no_argument, 0, 'F' }, +#endif { 0, 0, 0, 0 }, }; /* getopt_long stores the option index here. */ - int c = getopt_long(argc, argv, "hovdc:f:e:s:p:", long_options, + int c = getopt_long(argc, argv, "hvodc:f:e:s:p:", long_options, &option_index); option_index++; if (c == -1) @@ -226,6 +232,13 @@ int main(int argc, char** argv) } break; } +#ifdef as_daemon + case 'F': + { + no_fork = true; + break; + } +#endif case '?': { ConsoleLog writeError( diff --git a/src/pvsgui.cpp b/src/pvsgui.cpp index 8617587..1520c81 100644 --- a/src/pvsgui.cpp +++ b/src/pvsgui.cpp @@ -119,6 +119,7 @@ PVSGUI::PVSGUI(QWidget *parent) : connect(_ifaceDBus, SIGNAL(disconnected()), this, SLOT(disconnected())); connect(_ifaceDBus, SIGNAL(addHost(QString)), this, SLOT(addHost(QString))); connect(_ifaceDBus, SIGNAL(delHost(QString)), this, SLOT(delHost(QString))); + connect(_ifaceDBus, SIGNAL(incomingMulticastTransferNew(qulonglong, QString, QString, qulonglong)), SLOT(incomingMulticastFile(qulonglong, QString, QString, qulonglong))); // show toolbar setWindowFlags(Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint); @@ -412,6 +413,12 @@ void PVSGUI::receiveFile() d->open(); } +void PVSGUI::incomingMulticastFile(qulonglong transferID, QString sender, QString basename, qulonglong size) +{ + ClientFileReceiveDialog *d = new ClientFileReceiveDialog(sender, transferID, basename, size, _ifaceDBus, this); + d->open(); +} + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -443,24 +450,6 @@ void printVersion() int main(int argc, char *argv[]) { - QtSingleApplication app(argc, argv); - app.setQuitOnLastWindowClosed(false); - app.setOrganizationName("openslx"); - app.setOrganizationDomain("openslx.org"); - app.setApplicationName("pvsgui"); - - // only one instance should be allowed - if (app.sendMessage("")) - { - qDebug("[PVSGUI] ERROR: Already running. Exiting"); - return 0; - } - - // use system locale as language to translate gui - QTranslator translator; - translator.load(":pvsgui"); - app.installTranslator(&translator); - bool visible = false; int position = -1; @@ -499,6 +488,25 @@ int main(int argc, char *argv[]) opt = getopt_long( argc, argv, optString, longOpts, &longIndex ); } + + QtSingleApplication app(argc, argv); + app.setQuitOnLastWindowClosed(false); + app.setOrganizationName("openslx"); + app.setOrganizationDomain("openslx.org"); + app.setApplicationName("pvsgui"); + + // only one instance should be allowed + if (app.sendMessage("")) + { + qDebug("[PVSGUI] ERROR: Already running. Exiting"); + return 0; + } + + // use system locale as language to translate gui + QTranslator translator; + translator.load(":pvsgui"); + app.installTranslator(&translator); + PVSGUI pvsgui; pvsgui.setPosition(position); pvsgui.setVisible(visible); diff --git a/src/pvsgui.h b/src/pvsgui.h index 85a129b..b96d73c 100644 --- a/src/pvsgui.h +++ b/src/pvsgui.h @@ -66,6 +66,7 @@ private Q_SLOTS: void pvsDisconnect(); void sendFile(); void receiveFile(); + void incomingMulticastFile(qulonglong, QString sender, QString basename, qulonglong size); private: void setupMenu(); diff --git a/src/util/clientGUIUtils.cpp b/src/util/clientGUIUtils.cpp index 4d4cc0d..ff002d3 100644 --- a/src/util/clientGUIUtils.cpp +++ b/src/util/clientGUIUtils.cpp @@ -2,7 +2,7 @@ BlankScreen::BlankScreen() { - dpy = XOpenDisplay(NULL); + dpy = X11Info::display(); scr = DefaultScreen(dpy); assert(dpy); blackColor = BlackPixel(dpy, DefaultScreen(dpy)); @@ -142,3 +142,4 @@ bool BlankScreen::unlock() lockMsg.clear(); return !(locked = false); } + diff --git a/src/util/clientGUIUtils.h b/src/util/clientGUIUtils.h index b04d3fe..7f7ea47 100755 --- a/src/util/clientGUIUtils.h +++ b/src/util/clientGUIUtils.h @@ -25,4 +25,14 @@ private: BlankScreen_Sysdep* _sysdep; }; -#endif
\ No newline at end of file +#ifndef __WIN32__ +# include <X11/Xlib.h> + +class X11Info +{ +public: + static Display* display(); +}; +#endif /* !__WIN32__ */ + +#endif diff --git a/src/util/clientGUIUtils_X11.cpp b/src/util/clientGUIUtils_X11.cpp index 29c96f0..4b5afe0 100755 --- a/src/util/clientGUIUtils_X11.cpp +++ b/src/util/clientGUIUtils_X11.cpp @@ -166,3 +166,14 @@ bool BlankScreen::unlock() _sysdep->lockMsg.clear(); return !(_sysdep->locked = false); } + +static Display* _dpy = 0; + +Display* X11Info::display() +{ + if(!_dpy) + { + _dpy = XOpenDisplay(0); + } + return _dpy; +} diff --git a/src/version.h b/src/version.h index de5c6aa..a08c819 100644 --- a/src/version.h +++ b/src/version.h @@ -1,2 +1,2 @@ -#define VERSION_STRING "2.0.5" -#define VERSION_NUMBER 205 +#define VERSION_STRING "2.8.0" +#define VERSION_NUMBER 280 |