From 1a5709501f94014d41987b956338bb6424b9f90c Mon Sep 17 00:00:00 2001 From: sr Date: Mon, 4 Feb 2013 19:50:31 +0100 Subject: Initial commit --- src/client/vnc/vncthread.cpp | 314 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 src/client/vnc/vncthread.cpp (limited to 'src/client/vnc/vncthread.cpp') diff --git a/src/client/vnc/vncthread.cpp b/src/client/vnc/vncthread.cpp new file mode 100644 index 0000000..492f970 --- /dev/null +++ b/src/client/vnc/vncthread.cpp @@ -0,0 +1,314 @@ +/* + # 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 + +#include + +// 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(); + + 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); +} -- cgit v1.2.3-55-g7522