summaryrefslogblamecommitdiffstats
path: root/src/client/vnc/vncthread.cpp
blob: 1dbe0057e894f32a3ee42bcd0a5f018bd38f25e2 (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>

/**
 * 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(), _run(true), _started(false)
{
	_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 (_client != NULL) {
		if (_client->sock != -1)
			::close(_client->sock);
		_client->sock = -1;
		rfbClientCleanup(_client);
	}
}

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

/**
 * 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
	for (int retry = 0; retry < 5 && _run; ++retry) {
		this->msleep(1 + qrand() % 60);
		if (!_run)
			break;
		_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)) {
			break; // Success!
		}
		// Connection failed
		_client = NULL; // InitClient frees the client on failure, so make sure we don't keep an invalid pointer around
		if (!_run)
			break;
		// error, let's try again
		this->msleep(10 + qrand() % 50);
	}
	if (_client != NULL) {
		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
		if (_run) {
			_connected = true;
		}
		while (_run) {
			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;
		}
	}

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

/**
 * 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(int x, int y, int w, int h)
{
	emit imageUpdated(x, y, w, h);
}

void VncThread::emitStarted()
{
	emit projectionStarted();
}

// *** 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 = 32;
	const int size = width * height * (depth / 8);
	qDebug("[%s] Remote desktop: %ix%ix%i", t->metaObject()->className(), width, height, depth);

	t->_img = QSharedPointer<QImage>(new QImage(width, height, QImage::Format_RGB32));
	if (size > t->_img->byteCount()) {
		qDebug() << "Fatal: Created image too small:" << t->_img->byteCount() << "<" << size;
		::exit(1);
	}
	client->frameBuffer = t->_img->bits();
	memset(client->frameBuffer, '\0', size);
	client->format.trueColour = 1;
	client->format.bitsPerPixel = depth;
	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);

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

	if (!t->_started) {
		t->_started = true;
		t->emitStarted();
	}

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