/*
# 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>
// greatest common divisor
static int gcd(int a, int b)
{
if (b == 0)
return a;
return gcd(b, a % b);
}
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);
}
// ALWAYS delete this class from another thread using delete, not deleteLater, or you will deadlock the thread
VncThread::~VncThread()
{
qDebug("VNC worker destructor called, waiting for thread finishing...");
_run = false;
while (this->isRunning())
this->msleep(10);
qDebug("Thread ended.");
if (_frameBuffer)
delete[] _frameBuffer;
if (_client != NULL)
{
if (_client->sock != -1)
::close(_client->sock);
_client->frameBuffer = NULL;
rfbClientCleanup(_client);
}
if (_painter != NULL)
delete _painter;
}
// Calc matching pixel borders for both resolutions to prevent artifacts through bilinear scaling
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
void VncThread::setTargetSize(const QSize size)
{
if (_localSize == size)
return;
qDebug() << "Setting target size to " << size;
QMutexLocker lock(&_mutex);
_newLocalSize = size;
_hasNewLocalSize = true;
}
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
this->stop();
return;
}
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();
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();
}
/*
//work yourself through event queue and fire every event...
while (!_eventQueue.isEmpty()) {
SomeEvent* event = _eventQueue.dequeue();
event->fire(_client);
delete event;
}*/
}
_connected = false;
qDebug("[%s] VNC client stopped.", metaObject()->className());
}
const QString VncThread::getDesktopName() const
{
if (_client == NULL || _client->desktopName == NULL)
return QString();
return QString(_client->desktopName);
}
void VncThread::mouseEvent(int x, int y, int buttonMask)
{
//QMutexLocker lock(&mutex);
if (!_run)
return;
_eventQueue.enqueue(new PointerEvent(x, y, buttonMask));
}
void VncThread::keyEvent(int key, bool pressed)
{
//QMutexLocker lock(&mutex);
if (!_run)
return;
_eventQueue.enqueue(new KeyEvent(key, pressed));
}
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 ***
// the vnc lib is requesting the connection password
char* VncThread::passwdHandler(rfbClient *client)
{
VncThread* t = (VncThread*)rfbClientGetClientData(client, 0);
return strdup(t->_passwd.toUtf8());
}
// the vnc lib is telling us the size and bit depth of the remote screen
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;
}
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);
}
// *** event stuff ***
void PointerEvent::fire(rfbClient* cl)
{
SendPointerEvent(cl, _x, _y, _buttonMask);
}
void KeyEvent::fire(rfbClient* cl)
{
SendKeyEvent(cl, _key, _pressed);
}