/* # Copyright (c) 2009, 2010 - OpenSLX Project, Computer Center University of # Freiburg # # This program is free software distributed under the GPL version 2. # See http://openslx.org/COPYING # # If you have any feedback please consult http://openslx.org/feedback and # send your suggestions, praise, or complaints to feedback@openslx.org # # General information about OpenSLX can be found at http://openslx.org/ # ----------------------------------------------------------------------------- # clientVNCViewer.cpp # - connetct to vnc server and show remote screen (window/full) # ----------------------------------------------------------------------------- */ #include "vncwindow.h" #include "vncthread.h" #include "../clientapp/clientapp.h" #include VncWindow::VncWindow(QWidget *parent) : QWidget(parent), _vncWorker(NULL), _viewOnly(true), _multiScreen(false), _clientId(0), _redrawTimer(0), _tcpTimeoutTimer(0), _image(new QImage) { QTimer *upper = new QTimer(this); connect(upper, SIGNAL(timeout()), this, SLOT(timer_moveToTop())); upper->start(1111); } VncWindow::~VncWindow() { // } //////////////////////////////////////////////////////////////////////////////// // Private /** * Terminates the vnc worker thread and stops all related timers. * The thread will be signalled to stop, but we don't wait for it * to actually terminate. All signals of the thread are blocked, and we NULL * our reference to it. It will finish running in a detached state and finally * delete itself upon completion. */ void VncWindow::terminateVncThread() { if (_vncWorker == NULL) return; disconnect(_vncWorker, SIGNAL(projectionStopped()), this, SLOT(onProjectionStopped())); disconnect(_vncWorker, SIGNAL(projectionStarted()), this, SLOT(onProjectionStarted())); disconnect(_vncWorker, SIGNAL(imageUpdated(const int, const int, const int, const int)), this, SLOT(onUpdateImage(const int, const int, const int, const int))); _vncWorker->stop(); _vncWorker = NULL; if (_redrawTimer != 0) { killTimer(_redrawTimer); _redrawTimer = 0; } if (_tcpTimeoutTimer != 0) { killTimer(_tcpTimeoutTimer); _tcpTimeoutTimer = 0; } } void VncWindow::deleteVncThread() { qDebug() << "Deleting thread" << QObject::sender(); delete QObject::sender(); } /** * Draws given part of the current VNC frame buffer to the window. * Gets the image from the _vncWorker thread in an unsafe way. :( * * @param x X offset of the region to draw * @param y Y offset of the region to draw * @param w width of the region to draw * @param h height of the region to draw */ void VncWindow::draw(const int x, const int y, const int w, const int h) { if (_vncWorker == NULL) return; QPainter painter(this); painter.drawImage(x, y, *_image, x, y, w, h); //painter.drawRect(x,y,w,h); // for debugging updated area } //////////////////////////////////////////////////////////////////////////////// // Public /** * Show the VNC client window and connect to the given VNC server. * Any currently active VNC client connection is signaled to terminate, * and a new VNC client worker thread is created for the new connection. * * @param host IP address of VNC server to connect to * @param port Port of VNC server to connect to * @param passwd (view only) password of VNC server to connect to * @param ro currently unused * @param fullscreen display VNC image in fullscreen mode * @param caption caption of window (only visible if not running in fullscreen mode) * @param clientId the ID of the client we're connecting to (echoed back to server, not used locally) */ void VncWindow::open(const QString& host, int port, const QString& passwd, bool /* ro */ , bool fullscreen, const QString& caption, const int clientId, const QByteArray& rawThumb) { this->terminateVncThread(); _clientId = clientId; _vncWorker = new VncThread(host, port, passwd, 1); connect(_vncWorker, SIGNAL(projectionStopped()), this, SLOT(onProjectionStopped()), Qt::QueuedConnection); connect(_vncWorker, SIGNAL(projectionStarted()), this, SLOT(onProjectionStarted()), Qt::QueuedConnection); connect(_vncWorker, SIGNAL(imageUpdated(const int, const int, const int, const int)), this, SLOT(onUpdateImage(const int, const int, const int, const int)), Qt::QueuedConnection); connect(_vncWorker, SIGNAL(finished()), this, SLOT(deleteVncThread()), Qt::QueuedConnection); setWindowTitle(caption); setAttribute(Qt::WA_OpaquePaintEvent); _remoteThumb.loadFromData(rawThumb); QSize size; if (fullscreen) { setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool); // | Qt::X11BypassWindowManagerHint); <- better, but window won't get any keyboard input // Show projection on rightmost screen QDesktopWidget *desktop = QApplication::desktop(); int ns = desktop->numScreens(); QRect best; for (int i = 0; i < ns; ++i) { QRect r = desktop->screenGeometry(i); if (best.isNull() || r.left() > best.left()) { best = r; } } _multiScreen = ns > 1; qDebug() << "Spawning at" << best; size = best.size(); show(); setGeometry(best); raise(); if (!_multiScreen) { activateWindow(); } move(best.topLeft()); } else { setWindowFlags(Qt::Tool | Qt::WindowMaximizeButtonHint); size = QSize(800, 600); resize(size); showNormal(); } this->repaint(); _vncWorker->setTargetBuffer(_image); _tcpTimeoutTimer = startTimer(10000); _vncWorker->start(QThread::LowPriority); } /** * Called by Qt if the window is requested to be closed. This can either happen * through code (this->close()), or a keypress (Alt-F4). As long as * Qt::WA_DeleteOnClose is not set the QWidget gets just hidden and not quit. * See http://qt-project.org/doc/qt-4.8/qcloseevent.html#details for more * details. * * @param e close event data */ void VncWindow::closeEvent(QCloseEvent * /* e */ ) { qDebug("Closing VNC viewer window."); this->terminateVncThread(); emit running(false, _clientId); } //////////////////////////////////////////////////////////////////////////////// // Slots /** * Triggered by imageUpdate signal from the _vncWorker thread telling * that the given region of the image changed. Simply repaints the * given area of the window. Coordinates have already been scaled * accordingly. * * @param x X offset of the region to update * @param y Y offset of the region to update * @param w width of the region to update * @param h height of the region to update */ void VncWindow::onUpdateImage(const int x, const int y, const int w, const int h) { this->repaint(x, y, w, h); } /** * Triggered by _vncWorker after successfully connecting to the VNC server. * This emits a signal that will eventually lead to the ServerConnection * telling the server that we're now watching another client. */ void VncWindow::onProjectionStarted() { _remoteThumb = QPixmap(); if (_tcpTimeoutTimer != 0) { killTimer(_tcpTimeoutTimer); _tcpTimeoutTimer = 0; } emit running(true, _clientId); _redrawTimer = startTimer(500); } /** * Triggered by _vncWorker when the connection to the VNC server is lost. * We'll terminate the thread (detached) and close the window. A signal * telling the server that we're not watching a VNC stream anymore will * eventually be emited by the closeEvent. */ void VncWindow::onProjectionStopped() { this->terminateVncThread(); this->close(); } void VncWindow::timer_moveToTop() { if (this->isHidden()) return; if (!_multiScreen) { activateWindow(); } raise(); } //////////////////////////////////////////////////////////////////////////////// // Protected /** * Called when a Qt timer fires. * * redrawTimer: Redraw whole viewer window. * * tcpTimeoutTimer: Check if we're connected, close window if not. * * @param event the timer event */ void VncWindow::timerEvent(QTimerEvent *event) { if (event->timerId() == _redrawTimer) { killTimer(_redrawTimer); _redrawTimer = 0; this->update(); } else if (event->timerId() == _tcpTimeoutTimer) { killTimer(_tcpTimeoutTimer); _tcpTimeoutTimer = 0; if (_vncWorker != NULL && !_vncWorker->isConnected()) { this->close(); } } else killTimer(event->timerId()); } /** * Called by Qt when a part of the window should be redrawn. This can either * be caused by Qt (or the underlying graphical subsystem), or by * an explicit call to QWidget::repaint() * * @param event the paint event, containing data like location and * size area that should be repainted */ void VncWindow::paintEvent(QPaintEvent *event) { if (_vncWorker == NULL || !_vncWorker->isConnected()) { QPainter painter(this); if (!_remoteThumb.isNull() && _remoteThumb.height() > 0) { painter.drawPixmap(0, 0, this->width(), this->height(), _remoteThumb); } else { painter.fillRect(event->rect(), QColor(60, 63, 66)); } QFontInfo fi = painter.fontInfo(); painter.setPen(QColor(200, 100, 10)); painter.setFont(QFont(fi.family(), 28, fi.weight(), fi.italic())); painter.drawText(this->contentsRect(), Qt::AlignCenter, tr("Connecting...")); } else { const QRect &r = event->rect(); this->draw(r.left(), r.top(), r.width(), r.height()); } event->accept(); } /** * Called by Qt if the window is being resized. * * @param event the resize event */ void VncWindow::resizeEvent(QResizeEvent* event) { const QSize size = event->size(); if (size != _image->size()) { _image = QSharedPointer(new QImage(size, QImage::Format_RGB32)); } if (_vncWorker != NULL) { _vncWorker->setTargetBuffer(_image); } } /** * Called when user releases a pressed key and the window has focus */ void VncWindow::keyReleaseEvent(QKeyEvent* event) { if (event->modifiers() == 0 && event->key() == Qt::Key_Escape && clientApp->isConnectedToLocalManager()) { this->close(); } else { QWidget::keyReleaseEvent(event); } }