summaryrefslogblamecommitdiffstats
path: root/src/client/vnc/vncwindow.cpp
blob: 79352ae130f4a084f1c77d537cb372d89ba4db63 (plain) (tree)


















                                                                                
                                   
 

                 













                                             
                                       

                                                                                                                  
 


                                                                         



                       
          


                                                                                




                                                                 
                                                                            




                                                                              
                               

                       



                                                                                                      

                           
                                


                                        
                                    




                                            





                                                           

                                                                  









                                                                        




                                                                     
                               
























                                                                                                                                  


                                                                  


































                                                                                
                                                                                

         












                                                                                                     
                                                                                                                                                                                   
 
                                   

                                                          
                                                                                                                  
                                                                                                                  
                                                                                                   

                                                                                
                                                                                                     




                                              

                                            
                   
                         
                                                                                                                                                                                       
                                                      
                                                                  








                                                                      
                                                  
                                   
                       
                                  
                        



                                         
                
                                                                        

                                       


                             
                       
 
                                             
                                                

 

                                                                               



                                                                             


                            
                                                  
 
                                             
                                   


                                       





                                                                                


                                                         





                                            

                                                                                 














                                                                                                         


   


                                                                           


                                     
                                 



                                            
                                      
                                       

 





                                                                         

                                     

                                   

 



                                 


                                 


                

                                                                                








                                                                  

                                              
                                               

                                        
                               
                                                          
                                            
                                     
                                                                       
                                      
                 
              
                                            

 







                                                                            

                                              
                                                               
                                       
                                                                          
                                                                                              
                        





                                                                                             
                


                                                                     


                        
   



                                                                   
                                                                                                                  





                                                
/*
 # 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 <QTimer>

/**
 * Calc greatest common divisor.
 *
 * @param a one number
 * @param b another number
 * @return greatest common divisor of a and b
 */
static int gcd(int a, int b)
{
	if (b == 0)
		return a;
	return gcd(b, a % b);
}

VncWindow::VncWindow(QWidget *parent) :
	QWidget(parent), _srcStepX(1), _srcStepY(1), _dstStepX(1), _dstStepY(1),
	_vncWorker(NULL), _viewOnly(true), _multiScreen(false), _clientId(0), _redrawTimer(0), _tcpTimeoutTimer(0)
{
	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.
 *
 * @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;
	QSharedPointer<QImage> buffer = _vncWorker->getFrameBuffer();
	if (buffer.isNull())
		return;
	this->calcScaling(buffer.data());

	QPainter painter(this);
	if (_dstStepX > 1 || _dstStepY > 1) {
		// Scaling is required as vnc server and client are using different resolutions
		// Calc section offsets first
		const int startX = x / _dstStepX;
		const int startY = y / _dstStepY;
		const int endX = (x + w - 1) / _dstStepX + 1;
		const int endY = (y + h - 1) / _dstStepY + 1;
		// Now pixel offsets for source
		const int dstX = startX * _dstStepX;
		const int dstY = startY * _dstStepY;
		const int dstW = endX * _dstStepX - dstX;
		const int dstH = endY * _dstStepY - dstY;
		// Pixel offsets for destination
		const int srcX = startX * _srcStepX;
		const int srcY = startY * _srcStepY;
		const int srcW = endX * _srcStepX - srcX;
		const int srcH = endY * _srcStepY - srcY;
		// Rescale
		QImage scaled(
			buffer->copy(srcX, srcY, srcW, srcH).scaled(dstW, dstH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
		painter.drawImage(dstX, dstY, scaled, 0, 0, dstW, dstH);
	} else {
		// Same resolution, nothing to do
		painter.drawImage(x, y, *buffer, x, y, w, h);
	}
	//painter.drawRect(x,y,w,h); // for debugging updated area
}

/**
 * When using image scaling, calc matching pixel borders for both
 * resolutions to prevent artifacts through bilinear scaling.
 * If you simply round to the nearest number when scaling, you would
 * slightly stretch or shrink the image area, which leads to strange
 * sub-pixel-movements of parts of the image when being updated.
 * The values calculated here are used to determine a larger area around
 * the area that should actually be updated, that will not cause any
 * artifacts when scaling down/up.
 *
 * @return whether scaling factors changed
 */
bool VncWindow::calcScaling(const QImage *remote)
{
	if (this->size() == _desiredSize &&
			(remote == NULL || remote->size() == _remoteSize))
		return false;
	const QSize mySize = this->size();
	const QSize remoteSize = remote == NULL ? _remoteSize : remote->size();
	if (mySize.isEmpty())
		return false;
	if (remoteSize.isEmpty())
		return false;
	_remoteSize = remoteSize;
	_desiredSize = mySize;
	const int gcdX = gcd(_desiredSize.width(), _remoteSize.width());
	const int gcdY = gcd(_desiredSize.height(), _remoteSize.height());
	_srcStepX = _remoteSize.width() / gcdX;
	_srcStepY = _remoteSize.height() / gcdY;
	_dstStepX = _desiredSize.width() / gcdX;
	_dstStepY = _desiredSize.height() / gcdY;
	qDebug() << "Scaling updated to" << _remoteSize << "->" << _desiredSize;
	return true;
}

////////////////////////////////////////////////////////////////////////////////
// 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->update();

	_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.
 * Since these are client coordinates we might have to do
 * some scaling.
 *
 * @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)
{
	if (_vncWorker == NULL)
		return;
	if (!this->calcScaling(_vncWorker->getFrameBuffer().data()) && !_remoteSize.isEmpty()) {
		if (_srcStepX > 1 || _srcStepY > 1) {
			this->update((x * _desiredSize.width()) / _remoteSize.width() - 2,
						 (y * _desiredSize.height()) / _remoteSize.height() - 2,
						 (w * _desiredSize.width()) / _remoteSize.width() + 4,
						 (h * _desiredSize.height()) / _remoteSize.height() + 4);
		} else {
			this->update(x, y, w, h);
		}
	} else {
		// Changed, redraw everything
		this->update();
	}
}

/**
 * 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 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);
	}
}