From 1a5709501f94014d41987b956338bb6424b9f90c Mon Sep 17 00:00:00 2001 From: sr Date: Mon, 4 Feb 2013 19:50:31 +0100 Subject: Initial commit --- src/server/mainwindow/mainwindow.cpp | 633 +++++++++++++++++++++++++++++++++++ 1 file changed, 633 insertions(+) create mode 100644 src/server/mainwindow/mainwindow.cpp (limited to 'src/server/mainwindow/mainwindow.cpp') diff --git a/src/server/mainwindow/mainwindow.cpp b/src/server/mainwindow/mainwindow.cpp new file mode 100644 index 0000000..8c57b91 --- /dev/null +++ b/src/server/mainwindow/mainwindow.cpp @@ -0,0 +1,633 @@ +/* + # Copyright (c) 2009 - OpenSLX Project, Computer Center University of Freiburg + # + # This program is free software distributed under the GPL version 2. + # See http://openslx.org/COPYING + # + # If you have any feedback please consult http://openslx.org/feedback and + # send your suggestions, praise, or complaints to feedback@openslx.org + # + # General information about OpenSLX can be found at http://openslx.org/ + # ----------------------------------------------------------------------------- + # mainWindow.cpp + This is the Main class for the pvsManager. The GUI is constructed here. + # ----------------------------------------------------------------------------- + */ +// Self +#include "mainwindow.h" +// QT stuff +#include +#include +// Other custom UI elements +#include "../clicklabel/clicklabel.h" +#include "../sessionnamewindow/sessionnamewindow.h" +#include "../connectionframe/connectionframe.h" +// Network +#include "../net/listenserver.h" +#include "../net/client.h" +#include "../net/discoverylistener.h" +#include "../../shared/networkmessage.h" +// Others +#include "../../shared/settings.h" +#include "../util/util.h" +#include "../util/global.h" +// Auto-generated ui class +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget* parent) : + QMainWindow(parent), ui(new Ui::MainWindow), _tbIconSize(24), _tbArea(Qt::LeftToolBarArea), + _buttonBlockTime(0) + +{ + _sessionNameWindow = new SessionNameWindow(this); + ui->setupUi(this); + + Global::setSessionName("1234"); + + //conWin = new ConnectionWindow(ui->widget); + //ui->VconWinLayout->addWidget(conWin); + //conList = new ConnectionList(ui->ClWidget); + //ui->ClientGLayout->addWidget(conList); + + // Show only session name label, not textbox + _sessionNameLabel = new ClickLabel(this); + ui->mainLayout->addWidget(_sessionNameLabel); + + ui->frmRoom->setStyleSheet("background-color:#444"); + + // toolbar and actions in pvsmgr + ui->action_Exit->setStatusTip(tr("Exit")); + ui->action_Lock->setStatusTip(tr("Lock or Unlock all Clients")); + + // Close button in tool bar + connect(ui->action_Exit, SIGNAL(triggered()), this, SLOT(onButtonExit())); + connect(ui->action_BroadcastScreen, SIGNAL(triggered()), this, SLOT(onButtonBroadcast())); + connect(ui->action_Lock, SIGNAL(toggled(bool)), this, SLOT(onButtonLock(bool))); + // Clicking the session name label shows the edit field for it + connect(_sessionNameLabel, SIGNAL(clicked()), this, SLOT(onSessionNameClick())); + // Listen to updates to the session name through the session name window + connect(_sessionNameWindow, SIGNAL(updateSessionName()), this, + SLOT(onSessionNameUpdate())); + + setAttribute(Qt::WA_QuitOnClose); + setUnifiedTitleAndToolBarOnMac(true); + this->showMaximized(); // show the Mainwindow maximized + + _tilesX = 9; + _tilesY = 7; + _tileWidth = 10; + _tileHeight = 10; + + _timerId = 0; + _timerTimeout = 0; + + _listenServer = new ListenServer(CLIENT_PORT); + connect(_listenServer, SIGNAL(newClient(Client*)), this, SLOT(onClientConnected(Client*))); + _discoveryListener = new DiscoveryListener(); + + // Finally + this->onSessionNameUpdate(); + _timerId = startTimer(10); + _timerTimeout = 8; +} + +MainWindow::~MainWindow() +{ + _sessionNameLabel->deleteLater(); + delete ui; +} + +void MainWindow::placeFrameInFreeSlot(ConnectionFrame* frame) +{ + // Get occupied cell of each frame and store status in an array + const int elems = _tilesX * _tilesY; + int currentIndex = 0; + bool grid[elems]; + memset(grid, 0, sizeof(bool) * elems); // set everything to false + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + const QPoint &oldpos = (*it)->frameGeometry().topLeft(); + const int tx = oldpos.x() / _tileWidth; + const int ty = oldpos.y() / _tileHeight; + const int index = tx + ty * _tilesX; + if (frame == *it) + { + // Current frame is the frame to place, remember its old index + currentIndex = index; + continue; + } + if (index < 0 || index >= elems) + { + // Current frame is out of bounds, place in top left corner + qDebug("Move A"); + (*it)->move(0, 0); + grid[0] = true; + continue; + } + // Just mark cell as occupied in array + grid[index] = true; + } + // Safety + if (currentIndex < 0 || currentIndex >= elems) + currentIndex = 0; + // Now place in next free cell, start looking at current position of frame + for (int i = 0; i < elems; ++i) + { + const int index = (i + currentIndex) % elems; + if (grid[index]) + continue; + const int x = (index % _tilesX) * _tileWidth; + const int y = (index / _tilesX) * _tileHeight; + //qDebug() << "Index=" << index << " i=" << i << " cI=" << currentIndex << " elems=" << elems << " x=" << x << " y=" << y; + qDebug("Move B"); + frame->move(x, y); + break; + } +} + +ConnectionFrame* MainWindow::createFrame() +{ + // Allocate and resize + ConnectionFrame *cf = new ConnectionFrame(ui->frmRoom, _tileWidth, _tileHeight); + _clientFrames.append(cf); + cf->show(); + connect(cf, SIGNAL(frameMoved(ConnectionFrame *)), this, SLOT(onPlaceFrame(ConnectionFrame *))); + connect(cf, SIGNAL(clicked(ConnectionFrame *)), this, SLOT(onFrameClicked(ConnectionFrame *))); + return cf; +} + +bool MainWindow::loadPosition(QSettings& settings, const QString& id, int& x, int& y) +{ + settings.beginGroup("client_position"); + const QVariant retval(settings.value(id)); + settings.endGroup(); + if (retval.type() != QVariant::Point) + return false; + const QPoint point(retval.toPoint()); + x = point.x(); + y = point.y(); + return true; +} + +void MainWindow::savePosition(ConnectionFrame *cf) +{ + Client *client = cf->client(); + if (client == NULL) + return; + QPoint pos(cf->pos().x() / _tileWidth, cf->pos().y() / _tileHeight); + USER_SETTINGS(settings); + settings.beginGroup("client_position"); + settings.setValue(client->computerId(), pos); + settings.endGroup(); +} + +/* + * Overridden methods + */ + +void MainWindow::closeEvent(QCloseEvent* e) +{ + int ret = QMessageBox::question(this, "Test", "Beenden?", 0, 1, 2); + if (ret == 1) + QApplication::exit(0); + else + e->ignore(); +} + +void MainWindow::changeEvent(QEvent* e) +{ + QMainWindow::changeEvent(e); +} + +void MainWindow::resizeEvent(QResizeEvent* e) +{ + QMainWindow::resizeEvent(e); + if (_timerId == 0) + { + _timerId = startTimer(5); + } + _timerTimeout = 8; +} + +void MainWindow::mouseReleaseEvent(QMouseEvent* e) +{ + // Try to figure out if the user clicked inside the "room" frame + // No idea if there is a more elegant way to do this without + // sub-classing that frame and overriding its mouseReleaseEvent + const QPoint pos(ui->frmRoom->mapFrom(this, e->pos())); + if (pos.x() < 0 || pos.y() < 0) + return; + const QSize frame(ui->frmRoom->size()); + if (frame.width() > pos.x() && frame.height() > pos.y()) + { + for (QList::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it) + { + (**it).setSelection(false); + } + } +} + +void MainWindow::timerEvent(QTimerEvent* event) +{ + if (event->timerId() == _timerId) + { + _timerTimeout -= 1; + if (_timerTimeout == 1) + { + // Move toolbar if necessary + const int iconSize = this->size().width() / 30 + 14; + const int barSpace = ui->toolBar->actions().size() * (iconSize + 8); + const Qt::ToolBarArea area = + (barSpace < this->size().height() ? Qt::LeftToolBarArea : Qt::TopToolBarArea); + if (_tbIconSize != iconSize) + { + _tbIconSize = iconSize; + ui->toolBar->setIconSize(QSize(iconSize, iconSize)); + } + if (_tbArea != area) + { + _tbArea = area; + this->addToolBar(area, ui->toolBar); + } + return; + } + // Kill timer when done + if (_timerTimeout <= 0) + { + killTimer(_timerId); + _timerId = 0; + // Resize all connection windows + if (ui->frmRoom->size().width() < 100 || ui->frmRoom->size().height() < 100 || _tilesX <= 0 || _tilesY <= 0) + return; + const int nw = ui->frmRoom->size().width() / _tilesX; + const int nh = ui->frmRoom->size().height() / _tilesY; + for (QList::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it) + { + const QPoint &oldpos = (*it)->frameGeometry().topLeft(); + qDebug("Move C"); + (*it)->move((oldpos.x() / _tileWidth) * nw, (oldpos.y() / _tileHeight) * nh); + (*it)->setSize(nw, nh); + } + _tileWidth = nw; + _tileHeight = nh; + } + } + else + { + // Unknown timer ID, kill + killTimer(event->timerId()); + } +} + +/* + * Slots + */ + +void MainWindow::onPlaceFrame(ConnectionFrame* frame) +{ + if (_tilesX <= 0 || _tilesY <= 0) + return; + const QPoint &p = frame->frameGeometry().center(); + const QSize &s = ui->frmRoom->geometry().size(); + int x = (p.x() / _tileWidth) * _tileWidth; + int y = (p.y() / _tileHeight) * _tileHeight; + if (x < 0) + x = 0; + else if (x > s.width() - _tileWidth) + x = (_tilesX - 1) * _tileWidth; + if (y < 0) + y = 0; + else if (y > s.height() - _tileHeight) + y = (_tilesY - 1) * _tileHeight; + qDebug("Move D"); + frame->move(x, y); + savePosition(frame); + const QPoint &newpos = frame->frameGeometry().topLeft(); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + if (*it == frame) + continue; + if ((*it)->frameGeometry().topLeft() == newpos) + { + placeFrameInFreeSlot(*it); + } + } +} + +void MainWindow::onFrameClicked(ConnectionFrame* frame) +{ + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + if (*it == frame) + frame->setSelection(true); + else + (**it).setSelection(false); + } +} + +void MainWindow::onSessionNameClick() +{ + _sessionNameWindow->show(Global::sessionName()); +} + +void MainWindow::onSessionNameUpdate() +{ + _sessionNameLabel->setText(tr("Session Name: %1 [click to edit]").arg(Global::sessionName())); +} + +void MainWindow::onButtonSettings() +{ + // Move to any free tile + placeFrameInFreeSlot(createFrame()); +} + +void MainWindow::onButtonChat() +{ + /* + for (QList::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it) { + (*it)->setSize(100 + qrand() % 200); + } + */ +} + +void MainWindow::onButtonBroadcast() +{ + const qint64 now = QDateTime::currentMSecsSinceEpoch(); + if (now < _buttonBlockTime) + return; + _buttonBlockTime = now + 3000; + Client *source = NULL; // the desired source (selected frame) + Client *oldSource = NULL; // if there is a client that is already broadcaster, save it here + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if ((**it).selected()) + source = c; + if (c != NULL && c->isProjectionSource()) + oldSource = c; + } + if (source == NULL && oldSource == NULL) + { + QMessageBox::information(this, tr("No Projection Source selected"), tr("You did not select any active client as the connection source.")); + return; + } + if (oldSource != NULL) + { + // Is already broadcast source, disable + oldSource->setProjectionSource(false); + oldSource->stopVncServer(); + if (source == NULL || source->id() == oldSource->id()) + return; + } + source->setProjectionSource(true); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL || c->id() == source->id()) + continue; + //c->setDesiredProjectionSource(source->id()); + c->setProjectionSource(false); + } + source->startVncServer(); +} + +void MainWindow::onButtonLock(bool checked) +{ + NetworkMessage msg; + msg.setField(_ID, _LOCK); + if (checked) + msg.setField("ENABLE", QByteArray("1")); + else + msg.setField("ENABLE", QByteArray("0")); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL || (**it).isTutor()) + continue; // Don't lock the tutor + c->sendMessage(msg); + } +} + +void MainWindow::onButtonExit() +{ + this->close(); +} + +void MainWindow::onClientConnected(Client* client) +{ + qDebug("ListenServer told MainWindow about new connection"); + connect(client, SIGNAL(authenticating(Client*, ClientLogin*)), this, SLOT(onClientAuthenticating(Client*, ClientLogin*))); + connect(client, SIGNAL(authenticated(Client*)), this, SLOT(onClientAuthenticated(Client*))); + connect(client, SIGNAL(vncClientStateChange(Client*)), this, SLOT(onVncClientStateChange(Client*))); +} + +void MainWindow::onClientAuthenticating(Client* client, ClientLogin* request) +{ + disconnect(client, SIGNAL(authenticating(Client*, ClientLogin*)), this, SLOT(onClientAuthenticating(Client*, ClientLogin*))); + if (!request->accept) // Another receiver of that signal already rejected the client, so nothing to do + return; + bool inuse; + QString check = request->name; + int addnum = 1; + do + { + inuse = false; + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL) + continue; + if (!c->isAuthed()) + continue; + if (c->ip() == request->ip) + { + request->accept = false; + return; + } + if (c->name() == check) + { + inuse = true; + check = request->name + " (" + QString::number(++addnum) + ")"; + break; + } + } + } while (inuse && addnum < 100); + if (inuse) + request->accept = false; + else + request->name = check; +} + +void MainWindow::onClientAuthenticated(Client* client) +{ + disconnect(client, SIGNAL(authenticated(Client*)), this, SLOT(onClientAuthenticated(Client*))); + connect(client, SIGNAL(vncServerStateChange(Client*)), this, SLOT(onVncServerStateChange(Client*))); + bool hasActiveTutor = false; + ConnectionFrame *deadTutor = NULL; + bool anyClient = false; + ConnectionFrame *existing = NULL; + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + if ((*it)->computerId() == client->computerId()) + existing = *it; + if ((*it)->client() != NULL) + anyClient = true; + if ((*it)->isTutor()) + { + if ((*it)->client() == NULL) + deadTutor = (*it)->client(); + else + hasActiveTutor = true; + } + } + if (!anyClient && deadTutor != NULL) + deadTutor->setTutor(false); + if (existing != NULL) + { + if (!anyClient && !hasActiveTutor) + existing->setTutor(true); + existing->assignClient(client); + return; + } + // New one, create + ConnectionFrame *cf = createFrame(); + // Try to load last known position + int x, y; + bool ok; + USER_SETTINGS(usr); + ok = loadPosition(usr, client->computerId(), x, y); + if (!ok) + { + SYSTEM_SETTINGS(sys); + ok = loadPosition(sys, client->computerId(), x, y); + } + if (x >= _tilesX || y >= _tilesY) + ok = false; + if (ok) + { + if (x < 0) + x = 0; + if (y < 0) + y = 0; + qDebug("Move E"); + cf->move(x * _tileWidth, y * _tileHeight); + onPlaceFrame(cf); + } + else + { + // Move to any free tile + placeFrameInFreeSlot(cf); + } + // Assign client instance + cf->assignClient(client); + // Make first active client tutor + if (!anyClient && !hasActiveTutor) + cf->setTutor(true); + // ################ + NetworkMessage msg; + // If clients are currently locked, tell this new client + if (ui->action_Lock->isChecked()) + { + msg.reset(); + msg.setField(_ID, _LOCK); + msg.setField("ENABLE", QByteArray("1")); + client->sendMessage(msg); + } + // Same for VNC projections + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c != NULL && c != client && c->isActiveVncServer() && c->isProjectionSource()) + { + msg.reset(); + msg.setField(_ID, _VNCCLIENT); + msg.setField("HOST", c->ip()); + msg.setField("PORT", QString::number(c->vncPort())); + msg.setField("ROPASS", c->vncRoPass()); + msg.setField("CAPTION", c->name() + " @ " + c->host()); + client->sendMessage(msg); + break; + } + } +} + +void MainWindow::onVncServerStateChange(Client* client) +{ + if (client->vncPort() > 0) + { + // VNC Server started on some client - start projection on all clients interested in that client's screen + NetworkMessage msg; + msg.setField(_ID, _VNCCLIENT); + msg.setField("HOST", client->ip()); + msg.setField("PORT", QString::number(client->vncPort())); + msg.setField("ROPASS", client->vncRoPass()); + msg.setField("CAPTION", client->name() + " @ " + client->host()); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL) + continue; // Offline + if (c->id() == client->id() || + (c->desiredProjectionSource() != client->id() && !client->isProjectionSource())) + continue; // Not interested + c->sendMessage(msg); + } + } + else + { + // VNC server stopped on some client or failed to start - reset local pending projection information + // If at least one other client seemed to be waiting for this client's screen, pop up a message + // on the console + bool wasInterested = client->isProjectionSource(); + client->setProjectionSource(true); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL) + continue; + if (c->desiredProjectionSource() == client->id()) + { + wasInterested = true; + c->setDesiredProjectionSource(0); + } + } + if (wasInterested) + { + QMessageBox::information(this, + tr("Projection Error"), + tr("Could not send screen contents of %1 to other clients: VNC Startup failed.").arg(client->name()) + ); + } + } +} + +void MainWindow::onVncClientStateChange(Client* client) +{ + // If the client started projection, ignore event. Also if + // the source is not known (anymore), we cannot do anything meaningful here + if (client->isActiveVncClient() || client->currentProjectionSource() == 0) + return; + // Client is not a vnc client anymore. Check if the VNC server it was (supposedly) connected + // to is still running, and kill it if there are no more clients connected to it. + // (client->currentProjectionSource() will still contain the id of the server it was connected to during this event) + // 1. check if vnc server is still running + // 2. check if there are any other clients "using" that server + bool inUse = false; + Client *server = NULL; + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL || c == client) + continue; + if (c->currentProjectionSource() == client->currentProjectionSource()) + { + inUse = true; + break; + } + if (c->id() == client->currentProjectionSource()) + server = c; + } + // 3. kill server if applicable + if (!inUse && server != NULL) + server->stopVncServer(); +} -- cgit v1.2.3-55-g7522