/*
# 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 <QtGui>
#include <QFileDialog>
// 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_StopProjection, SIGNAL(triggered()), this, SLOT(onButtonStopProjection()));
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<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it) {
(*it)->setSize(100 + qrand() % 200);
}
*/
}
bool MainWindow::isValidClient(Client* client)
{
for (QList<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::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::onButtonStopProjection()
{
const qint64 now = QDateTime::currentMSecsSinceEpoch();
if (now < _buttonBlockTime)
return;
_buttonBlockTime = now + 3000;
//
NetworkMessage msg;
msg.setField(_ID, _VNCCLIENT);
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
{
Client *c = (**it).client();
if (c == NULL)
continue;
c->sendMessage(msg);
}
}
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<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::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<ConnectionFrame*>::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 && QDateTime::currentMSecsSinceEpoch() < _buttonBlockTime)
{
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<ConnectionFrame*>::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();
}