/* # 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 /** * 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), _run(true), _started(false) { _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 != NULL) delete[] _frameBuffer; if (_client != NULL) { if (_client->sock != -1) ::close(_client->sock); _client->sock = -1; _client->frameBuffer = NULL; rfbClientCleanup(_client); } } /** * 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()) return; if (_clientSize.isEmpty()) 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::setTargetBuffer(QSharedPointer &buffer) { _imgScaled = buffer; } /** * 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) { QSharedPointer buffer = _imgScaled; QPainter painter(buffer.data()); if (buffer->size() != _localSize) { _localSize = buffer->size(); this->calcScaling(); x = 0; y = 0; w = _img.width(); h = _img.height(); } 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 x = startX * _dstStepX; y = startY * _dstStepY; w = endX * _dstStepX - x; h = endY * _dstStepY - y; // Rescale QImage scaled( _img.copy(srcX, srcY, srcW, srcH).scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); painter.drawImage(x, y, scaled, 0, 0, w, h); } else { // Same resolution, nothing to do painter.drawImage(x, y, _img, x, y, w, 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; // client->format.bitsPerPixel; const int size = width * height * (depth / 8); qDebug("[%s] Remote desktop: %ix%ix%i", t->metaObject()->className(), width, height, depth); if (t->_frameBuffer != NULL) delete[] t->_frameBuffer; t->_frameBuffer = new uint8_t[size]; client->frameBuffer = t->_frameBuffer; memset(client->frameBuffer, '\0', size); 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); 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); 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); }