summaryrefslogblamecommitdiffstats
path: root/src/client/vnc/vncthread.cpp
blob: fe5228386839bdd68e569239fc068aa3568d535d (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/
 # -----------------------------------------------------------------------------
 # vncClientThread.cpp
 #  - Connection to remove vnc server
 #  - Emits Qt signal on framebuffer updates
 # -----------------------------------------------------------------------------
 */

#include "vncthread.h"
#include <QPainter>

#include <netinet/tcp.h>

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

/**
 * Initialize this VNC client connection worker thread.
 *
 * @param host The IP address of the VNC server we're going to connect to
 * @param port The port of the VNC server
 * @param passwd The password of the VNC server
 * @param quality The desired quality level for the VNC stream
 */
VncThread::VncThread(QString host, int port, QString passwd, int quality) :
	QThread(), _frameBuffer(NULL), _painter(NULL), _hasNewLocalSize(false), _run(true)
{
	_srcStepX = _srcStepY = _dstStepX = _dstStepY = 0;
	_host = host;
	_port = port;
	_passwd = passwd;
	_quality = quality;
	_client = NULL;
	_connected = false;
	moveToThread(this);
}

VncThread::~VncThread()
{
	qDebug("VNC worker destructor called.");
	Q_ASSERT(_run == false);
	if (_frameBuffer)
		delete[] _frameBuffer;
	if (_client != NULL)
	{
		if (_client->sock != -1)
			::close(_client->sock);
		_client->frameBuffer = NULL;
		rfbClientCleanup(_client);
	}
	if (_painter != NULL)
		delete _painter;
}

/**
 * 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.
 */
void VncThread::calcScaling()
{
	if (_localSize.isEmpty() || _localSize.width() == 0 || _localSize.height() == 0)
		return;
	if (_clientSize.isEmpty() || _clientSize.width() == 0 || _clientSize.height() == 0)
		return;
	const int gcdX = gcd(_localSize.width(), _clientSize.width());
	const int gcdY = gcd(_localSize.height(), _clientSize.height());
	_srcStepX = _clientSize.width() / gcdX;
	_srcStepY = _clientSize.height() / gcdY;
	_dstStepX = _localSize.width() / gcdX;
	_dstStepY = _localSize.height() / gcdY;
	qDebug() << "Scaling updated to " << _clientSize << " -> " << _localSize;
	emit imageUpdated(0, 0, _localSize.width(), _localSize.height());
}

////////////////////////////////////////////////////////////////////////////////
// Public

/**
 * Tell the client that the size of the viewer window has changed.
 *
 * @param size The new size of the viewer window
 */
void VncThread::setTargetSize(const QSize size)
{
	if (_localSize == size)
		return;
	qDebug() << "Setting target size to " << size;
	QMutexLocker lock(&_mutex);
	_newLocalSize = size;
	_hasNewLocalSize = true;
}

/**
 * Worker thread's mainloop, connecting to the VNC server and handling the connection.
 * The vnc client library is written in a blocking manner, so we just do everything in
 * our own thread and just hand over the image updates to the vnc client window, which
 * resides in the application's main thread.
 *
 * This thread checks if the vnc window signaled us to stop and if so, it deletes itself.
 * This means that you should *never* delete this thread from anywhere. Just call VncThread::stop()
 * and wait for it to delete itself.
 */
void VncThread::run()
{
	qDebug("[%s] VNC client started.", metaObject()->className());
	qDebug("[%s] Host: '%s' Port: %i Passwd: '%s' Quality: %i", metaObject()->className(), qPrintable(_host), _port,
	   qPrintable(_passwd), _quality);

	// setup network
	_client = rfbGetClient(8, 3, 4);
	_client->MallocFrameBuffer = &frameBufferHandler;
	_client->canHandleNewFBSize = true;
	free(_client->serverHost); // in rfbGetClient, serverHost is assigned strdup(""), so free that first.
	_client->serverHost = strdup(_host.toUtf8().constData());
	_client->desktopName = NULL;
	_client->serverPort = _port;
	_client->GetPassword = &passwdHandler;
	_client->GotFrameBufferUpdate = &updateImage;
	_client->frameBuffer = NULL;

	// save this instance in vnc-struct for callbacks
	rfbClientSetClientData(_client, 0, this);

	// start client
	if (!rfbInitClient(_client, NULL, NULL))
	{
		_client = NULL; // !!! <- if you don't do this you will get a segfault later when you try to clean up _client, as rfbInitClient already did so
	}
	else
	{
		qDebug("[%s] Connection successful!", metaObject()->className());
		int one = 1;
		setsockopt(_client->sock, SOL_TCP, TCP_NODELAY, &one, sizeof(one));
		one = 1;
		setsockopt(_client->sock, SOL_TCP, TCP_QUICKACK, &one, sizeof(one));

		// Main VNC event loop
		emit projectionStarted();
	}

	if(_client != NULL)
		while (_run)
		{
			_connected = true;
			const int i = WaitForMessage(_client, 100 * 1000); // wait 100ms for message. returns -1 on error/disconnect, 0 if nothing happened, 1 if new data arrived
			if (i < 0)
				break;
			if (i > 0 && !HandleRFBServerMessage(_client))
				break;

			if (_hasNewLocalSize)
			{
				QMutexLocker lock(&_mutex);
				_hasNewLocalSize = false;
				_localSize = _newLocalSize;
				if (_painter != NULL)
					delete _painter;
				_imgScaled = QImage(_localSize, QImage::Format_RGB32);
				_painter = new QPainter(&_imgScaled);
				this->calcScaling();
			}
		}

	_connected = false;
	emit projectionStopped();
	while (_run)
		this->msleep(100);
	qDebug("[%s] VNC client stopped.", metaObject()->className());
	this->deleteLater();
	QThread::run();
}

/**
 * Get name of the VNC server's desktop.
 *
 * @return Name of the remote desktop
 */
const QString VncThread::getDesktopName() const
{
	if (_client == NULL || _client->desktopName == NULL)
		return QString();
	return QString(_client->desktopName);
}

/**
 * Handle update of an area of the VNC framebuffer. Will do any scaling
 * if necessary and then emit the imageUpdated signal, so the VncWindow
 * knows that it needs to redraw.
 *
 * @param x X offset of the area in the framebuffer that changed
 * @param y Y offset of the area in the framebuffer that changed
 * @param w width of the area in the framebuffer that changed
 * @param h height of the area in the framebuffer that changed
 */
void VncThread::processImageUpdate(const int x, const int y, const int w, const int h)
{
	if (_srcStepX > 1 || _srcStepY > 1)
	{
		// Scaling is required as vnc server and client are using different resolutions
		// Calc section offsets first
		const int startX = x / _srcStepX;
		const int startY = y / _srcStepY;
		const int endX = (x + w - 1) / _srcStepX + 1;
		const int endY = (y + h - 1) / _srcStepY + 1;
		// Now pixel offsets for source
		const int srcX = startX * _srcStepX;
		const int srcY = startY * _srcStepY;
		const int srcW = endX * _srcStepX - srcX;
		const int srcH = endY * _srcStepY - srcY;
		// Pixel offsets for destination
		const int dstX = startX * _dstStepX;
		const int dstY = startY * _dstStepY;
		const int dstW = endX * _dstStepX - dstX;
		const int dstH = endY * _dstStepY - dstY;
		// Rescale
		if (_painter != NULL)
		{
			QImage scaled(
			   _img.copy(srcX, srcY, srcW, srcH).scaled(dstW, dstH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
			_painter->drawImage(dstX, dstY, scaled, 0, 0, dstW, dstH);
			emit imageUpdated(dstX, dstY, dstW, dstH);
		}
	}
	else
	{
		// Same resolution, nothing to do
		emit imageUpdated(x, y, w, h);
	}
}

// *** callback stuff ***

/**
 * Callback for the vnc client lib: The VNC server is requesting a password.
 *
 * @param client Struct representing the connection to the server.
 * @return A strdup()ed copy of the password we'll try to connect with.
 * 			The memory if free()d by the vnc client library.
 */
char* VncThread::passwdHandler(rfbClient *client)
{
	VncThread* t = (VncThread*)rfbClientGetClientData(client, 0);
	return strdup(t->_passwd.toUtf8());
}

/**
 * Callback for the vnc client lib: The size of the remote screen has
 * been changed, so we're supposed to reallocate the local frame buffer.
 *
 * @param client Struct representing the connection to the server.
 * @return true (signaling the vnc client that we successfully allocated a buffer)
 */
rfbBool VncThread::frameBufferHandler(rfbClient *client)
{
	VncThread *t = (VncThread*)rfbClientGetClientData(client, 0);
	const int width = client->width, height = client->height, depth = client->format.bitsPerPixel;
	const int size = width * height * (depth / 8);
	qDebug("[%s] Remote desktop: %ix%ix%i", t->metaObject()->className(), width, height, depth);

	QMutexLocker lock(&(t->_mutex));

	if (t->_frameBuffer)
		delete[] t->_frameBuffer;

	t->_frameBuffer = new uint8_t[size];
	client->frameBuffer = t->_frameBuffer;
	memset(client->frameBuffer, '\0', size);
	client->format.bitsPerPixel = 32;
	client->format.redShift = 16;
	client->format.greenShift = 8;
	client->format.blueShift = 0;
	client->format.redMax = 0xff;
	client->format.greenMax = 0xff;
	client->format.blueMax = 0xff;

	const int quality = t->_quality;
	switch (quality)
	{
	case VncThread::HIGH:
		client->appData.useBGR233 = 0;
		client->appData.encodingsString = "copyrect zlib hextile raw";
		client->appData.compressLevel = 4;
		client->appData.qualityLevel = 9;
		client->appData.scaleSetting = 0;
		break;
	case VncThread::MEDIUM:
		client->appData.useBGR233 = 0;
		client->appData.encodingsString = "copyrect tight zrle ultra zlib hextile corre rre raw";
		client->appData.compressLevel = 7;
		client->appData.qualityLevel = 8;
		client->appData.scaleSetting = 0;
		break;
	case VncThread::LOW:
	default:
		client->appData.useBGR233 = 1;
		client->appData.encodingsString = "copyrect tight zrle ultra zlib hextile corre rre raw";
		client->appData.compressLevel = 9;
		client->appData.qualityLevel = 1;
		client->appData.scaleSetting = 0;
		break;
	}
	SetFormatAndEncodings(client);

	t->_clientSize = QSize(width, height);

	t->_img = QImage(client->frameBuffer, client->width, client->height, QImage::Format_RGB32);

	t->calcScaling();

	SendFramebufferUpdateRequest(client, 0, 0, width, height, false);

	return true;
}

/**
 * Callback for the vnc client lib: A part of the frame buffer has changed. That means we have to
 * apply scaling if necessary, and then cause the vnc window to redraw the corresponding area.
 *
 * @param client Struct representing the connection to the server.
 * @param x X offset of the area in the framebuffer that changed
 * @param y Y offset of the area in the framebuffer that changed
 * @param w width of the area in the framebuffer that changed
 * @param h height of the area in the framebuffer that changed
 */
void VncThread::updateImage(rfbClient* client, int x, int y, int w, int h)
{
	VncThread* t = (VncThread*)rfbClientGetClientData(client, 0);
	t->processImageUpdate(x, y, w, h);
}