/* # 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(onButtonStudentToAll())); connect(ui->action_TutorToAll, SIGNAL(triggered()), this, SLOT(onButtonTutorToAll())); connect(ui->action_StudentToTutor, SIGNAL(triggered()), this, SLOT(onButtonStudentToTutor())); connect(ui->action_TutorToStudent, SIGNAL(triggered()), this, SLOT(onButtonTutorToStudent())); 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); } */ } bool MainWindow::isValidClient(Client* client) { for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) { Client *c = (**it).client(); if (c == client) return true; } return false; } void MainWindow::prepareForProjection(Client * const from, Client * const to) { // Projection source is never allowed to be an active VNC viewer if (from->isActiveVncClient()) { qDebug("From is active client, stopping..."); from->stopVncClient(); } if (to == NULL) { // One to many qDebug("One to many requested..."); from->setProjectionSource(true); if (from->isActiveVncServer()) { // From is already active, if there is at least one active client, assume it is not // shutting down, so we can directly tell the new client to connect to it qDebug("Source is already running a VNC server...."); for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) { Client *c = (**it).client(); if (c == NULL || c->id() == from->id()) continue; if (c->currentProjectionSource() != from->id()) continue; // Yep :-) qDebug("Reusing because of active client"); this->onVncServerStateChange(from); return; } qDebug("But no active client found...."); } // Could not take shortcut, (re)start VNC server on source qDebug("Starting VNC server on source machine"); from->startVncServer(); return; } // One to one is desired, figure out what to do with current client to->setProjectionSource(false); if (to->isActiveVncServer()) { if (to->currentProjectionSource() == from->id()) return; // Nothing to do to->stopVncServer(); } to->setDesiredProjectionSource(from->id()); if (from->isActiveVncServer()) { // From is already active, if there is at least one active client, assume it is not // shutting down, so we can directly tell the new client to connect to it for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) { Client *c = (**it).client(); if (c == NULL || c->id() == from->id()) continue; if (c->currentProjectionSource() != from->id()) continue; // Yep :-) this->onVncServerStateChange(from); return; } } // Could not take shortcut, (re)start VNC server on source from->startVncServer(); } void MainWindow::onButtonStudentToAll() { const qint64 now = QDateTime::currentMSecsSinceEpoch(); if (now < _buttonBlockTime) return; _buttonBlockTime = now + 3000; // Client *from = NULL; for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) { Client *c = (**it).client(); if (c == NULL) continue; if ((**it).selected()) from = c; else c->setProjectionSource(false); } if (from == NULL) QMessageBox::critical(this, tr("Projection"), tr("No projection source selected.") ); else prepareForProjection(from, NULL); } void MainWindow::onButtonStudentToTutor() { const qint64 now = QDateTime::currentMSecsSinceEpoch(); if (now < _buttonBlockTime) return; _buttonBlockTime = now + 3000; // Client *from = NULL; Client *to = NULL; for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) { Client *c = (**it).client(); if (c == NULL) continue; if ((**it).selected()) from = c; else if ((**it).isTutor()) to = c; } if (from == NULL) QMessageBox::critical(this, tr("Projection"), tr("No projection source selected.") ); else if (to == NULL) QMessageBox::critical(this, tr("Projection"), tr("No tutor defined, or tutor is offline.") ); else if (to == from) QMessageBox::critical(this, tr("Projection"), tr("Selected projection target is tutor.") ); else prepareForProjection(from, to); } void MainWindow::onButtonTutorToAll() { const qint64 now = QDateTime::currentMSecsSinceEpoch(); if (now < _buttonBlockTime) return; _buttonBlockTime = now + 3000; // Client *from = NULL; for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) { Client *c = (**it).client(); if (c == NULL) continue; if ((**it).isTutor()) from = c; else c->setProjectionSource(false); } if (from == NULL) QMessageBox::critical(this, tr("Projection"), tr("No tutor defined, or tutor is offline.") ); else prepareForProjection(from, NULL); } void MainWindow::onButtonTutorToStudent() { const qint64 now = QDateTime::currentMSecsSinceEpoch(); if (now < _buttonBlockTime) return; _buttonBlockTime = now + 3000; // Client *from = NULL; Client *to = NULL; for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) { Client *c = (**it).client(); if (c == NULL) continue; if ((**it).selected()) to = c; else if ((**it).isTutor()) from = c; } if (from == NULL) QMessageBox::critical(this, tr("Projection"), tr("No projection source selected.") ); else if (to == NULL) QMessageBox::critical(this, tr("Projection"), tr("No tutor defined, or tutor is offline.") ); else if (to == from) QMessageBox::critical(this, tr("Projection"), tr("Selected projection source is tutor.") ); else prepareForProjection(from, to); } 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*))); } 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*)), Qt::QueuedConnection); connect(client, SIGNAL(vncClientStateChange(Client*, int)), this, SLOT(onVncClientStateChange(Client*, int)), Qt::QueuedConnection); 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; 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); } // Make first active client tutor if (!anyClient && !hasActiveTutor) cf->setTutor(true); // Assign client instance cf->assignClient(client); // ################ 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 (!isValidClient(client)) // Check here because this slot is connected queued return; 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("CLIENTID", QString::number(client->id())); msg.setField("CAPTION", client->name() + " @ " + client->host()); for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) { Client *c = (**it).client(); if (c == NULL || c->id() == client->id()) continue; // Offline or self if (c->currentProjectionSource() == client->id()) continue; // Already watching this client if (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, int lastProjectionSource) { if (!isValidClient(client)) // Check here because this slot is connected queued return; // If last source is not known or not existent, we cannot do anything meaningful here if (lastProjectionSource == 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) continue; if (c->id() == lastProjectionSource) server = c; if (c != client && c->currentProjectionSource() == lastProjectionSource) { inUse = true; break; } } // 3. kill server if applicable if (!inUse && server != NULL) server->stopVncServer(); }