/* # 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 /** * 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(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); }