/*
# 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 = nullptr;
_connected = false;
moveToThread(this);
}
VncThread::~VncThread()
{
qDebug("VNC worker destructor called.");
Q_ASSERT(_run == false);
if (_client != nullptr) {
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 = nullptr;
_client->serverPort = _port;
_client->GetPassword = &passwdHandler;
_client->GotFrameBufferUpdate = &updateImage;
_client->frameBuffer = nullptr;
// save this instance in vnc-struct for callbacks
rfbClientSetClientData(_client, nullptr, this);
// start client
if (rfbInitClient(_client, nullptr, nullptr)) {
break; // Success!
}
// Connection failed
_client = nullptr; // 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 != nullptr) {
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 == nullptr || _client->desktopName == nullptr)
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 = reinterpret_cast<VncThread*>(rfbClientGetClientData(client, nullptr));
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 = reinterpret_cast<VncThread*>(rfbClientGetClientData(client, nullptr));
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);
if (width < 0 || height < 0 || size < 0) {
qWarning() << "IGNORING INVALID FRAMEBUFFER SIZE";
client->frameBuffer = nullptr;
return false;
}
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_t(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);
}