trying to pinpoint crash on exit
[presencevnc] / src / vncclientthread.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2007-2008 Urs Wolfer <uwolfer @ kde.org>
4 **
5 ** This file is part of KDE.
6 **
7 ** This program is free software; you can redistribute it and/or modify
8 ** it under the terms of the GNU General Public License as published by
9 ** the Free Software Foundation; either version 2 of the License, or
10 ** (at your option) any later version.
11 **
12 ** This program is distributed in the hope that it will be useful,
13 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ** GNU General Public License for more details.
16 **
17 ** You should have received a copy of the GNU General Public License
18 ** along with this program; see the file COPYING. If not, write to
19 ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 ** Boston, MA 02110-1301, USA.
21 **
22 ****************************************************************************/
23
24 #include "vncclientthread.h"
25
26 #include <QCoreApplication>
27 #include <QMutexLocker>
28 #include <QTimer>
29
30 static QString outputErrorMessageString;
31
32 rfbBool VncClientThread::newclient(rfbClient *cl)
33 {
34     VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0);
35     Q_ASSERT(t);
36
37     const int width = cl->width, height = cl->height, depth = cl->format.bitsPerPixel;
38     const int size = width * height * (depth / 8);
39     if (t->frameBuffer)
40         delete [] t->frameBuffer; // do not leak if we get a new framebuffer size
41     t->frameBuffer = new uint8_t[size];
42     cl->frameBuffer = t->frameBuffer;
43     memset(cl->frameBuffer, '\0', size);
44     cl->format.bitsPerPixel = 32;
45     cl->format.redShift = 16;
46     cl->format.greenShift = 8;
47     cl->format.blueShift = 0;
48     cl->format.redMax = 0xff;
49     cl->format.greenMax = 0xff;
50     cl->format.blueMax = 0xff;
51
52     switch (t->quality()) {
53     case RemoteView::High:
54         cl->appData.useBGR233 = 0;
55         cl->appData.encodingsString = "copyrect hextile raw";
56         cl->appData.compressLevel = 0;
57         cl->appData.qualityLevel = 9;
58         break;
59     case RemoteView::Medium:
60         cl->appData.useBGR233 = 0;
61         cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw";
62         cl->appData.compressLevel = 5;
63         cl->appData.qualityLevel = 7;
64         break;
65     case RemoteView::Low:
66     case RemoteView::Unknown:
67     default:
68         cl->appData.useBGR233 = 1;
69         cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw";
70         cl->appData.compressLevel = 9;
71         cl->appData.qualityLevel = 1;
72     }
73
74     SetFormatAndEncodings(cl);
75
76     return true;
77 }
78
79 void VncClientThread::updatefb(rfbClient* cl, int x, int y, int w, int h)
80 {
81      //kDebug(5011) << "updated client: x: " << x << ", y: " << y << ", w: " << w << ", h: " << h;
82
83     const int width = cl->width, height = cl->height;
84
85     const QImage img(cl->frameBuffer, width, height, QImage::Format_RGB32);
86
87     if (img.isNull()) {
88         kDebug(5011) << "image not loaded";
89     }
90
91     VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0);
92     Q_ASSERT(t);
93
94     t->setImage(img);
95
96     t->emitUpdated(x, y, w, h);
97 }
98
99 void VncClientThread::cuttext(rfbClient* cl, const char *text, int textlen)
100 {
101     const QString cutText = QString::fromUtf8(text, textlen);
102     kDebug(5011) << cutText;
103
104     if (!cutText.isEmpty()) {
105         VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0);
106         Q_ASSERT(t);
107
108         t->emitGotCut(cutText);
109     }
110 }
111
112 char *VncClientThread::passwdHandler(rfbClient *cl)
113 {
114     kDebug(5011) << "password request" << kBacktrace();
115
116     VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0);
117     Q_ASSERT(t);
118
119     t->passwordRequest();
120     t->m_passwordError = true;
121
122     return strdup(t->password().toLocal8Bit());
123 }
124
125 void VncClientThread::outputHandler(const char *format, ...)
126 {
127     va_list args;
128     va_start(args, format);
129
130     QString message;
131     message.vsprintf(format, args);
132
133     va_end(args);
134
135     message = message.trimmed();
136
137     kDebug(5011) << message;
138
139     if ((message.contains("Couldn't convert ")) ||
140             (message.contains("Unable to connect to VNC server")))
141         outputErrorMessageString = i18n("Server not found.");
142
143     if ((message.contains("VNC connection failed: Authentication failed, too many tries")) ||
144             (message.contains("VNC connection failed: Too many authentication failures")))
145         outputErrorMessageString = i18n("VNC authentication failed because of too many authentication tries.");
146
147     if (message.contains("VNC connection failed: Authentication failed"))
148         outputErrorMessageString = i18n("VNC authentication failed.");
149
150     if (message.contains("VNC server closed connection"))
151         outputErrorMessageString = i18n("VNC server closed connection.");
152
153     // internal messages, not displayed to user
154     if (message.contains("VNC server supports protocol version 3.889")) // see http://bugs.kde.org/162640
155         outputErrorMessageString = "INTERNAL:APPLE_VNC_COMPATIBILTY";
156 }
157
158 VncClientThread::VncClientThread(QObject *parent)
159         : QThread(parent)
160         , frameBuffer(0)
161 {
162     QMutexLocker locker(&mutex);
163     m_stopped = false;
164
165     QTimer *outputErrorMessagesCheckTimer = new QTimer(this);
166     outputErrorMessagesCheckTimer->setInterval(500);
167     connect(outputErrorMessagesCheckTimer, SIGNAL(timeout()), this, SLOT(checkOutputErrorMessage()));
168     outputErrorMessagesCheckTimer->start();
169 }
170
171 VncClientThread::~VncClientThread()
172 {
173     stop();
174
175     const bool quitSuccess = wait(1000);
176
177     kDebug(5011) << "~VncClientThread(): Quit VNC thread success:" << quitSuccess;
178     
179     delete [] frameBuffer;
180     //cl is free()d when event loop exits.
181
182         kDebug(5011) << "inMessageHandler: " << inMessageHandler;
183
184         kDebug(5011) << "leaving ~VncClientThreod()";
185 }
186
187 void VncClientThread::checkOutputErrorMessage()
188 {
189     if (!outputErrorMessageString.isEmpty()) {
190         kDebug(5011) << outputErrorMessageString;
191         QString errorMessage = outputErrorMessageString;
192         outputErrorMessageString.clear();
193         // show authentication failure error only after the 3rd unsuccessful try
194         if ((errorMessage != i18n("VNC authentication failed.")) || m_passwordError)
195             outputErrorMessage(errorMessage);
196     }
197 }
198
199 void VncClientThread::setHost(const QString &host)
200 {
201     QMutexLocker locker(&mutex);
202     m_host = host;
203 }
204
205 void VncClientThread::setPort(int port)
206 {
207     QMutexLocker locker(&mutex);
208     m_port = port;
209 }
210
211 void VncClientThread::setQuality(RemoteView::Quality quality)
212 {
213     m_quality = quality;
214 }
215
216 RemoteView::Quality VncClientThread::quality() const
217 {
218     return m_quality;
219 }
220
221 void VncClientThread::setImage(const QImage &img)
222 {
223     QMutexLocker locker(&mutex);
224     m_image = img;
225 }
226
227 const QImage VncClientThread::image(int x, int y, int w, int h)
228 {
229     QMutexLocker locker(&mutex);
230
231     if (w == 0) // full image requested
232         return m_image;
233     else
234         return m_image.copy(x, y, w, h);
235 }
236
237 void VncClientThread::emitUpdated(int x, int y, int w, int h)
238 {
239     emit imageUpdated(x, y, w, h);
240 }
241
242 void VncClientThread::emitGotCut(const QString &text)
243 {
244     emit gotCut(text);
245 }
246
247 void VncClientThread::stop()
248 {
249     QMutexLocker locker(&mutex);
250 //TODO: not locking the mutex leads to a crash, but at least it stops.
251 //TODO: i don't see how this makes a difference
252         kDebug(5011) << "stop(): mutex locked";
253     m_stopped = true;
254 }
255
256 void VncClientThread::run()
257 {
258     QMutexLocker locker(&mutex);
259         bool clean = false;
260
261     int passwd_failures = 0;
262     while (!m_stopped) { // try to connect as long as the server allows
263         m_passwordError = false;
264
265         rfbClientLog = outputHandler;
266         rfbClientErr = outputHandler;
267         cl = rfbGetClient(8, 3, 4);
268         cl->MallocFrameBuffer = newclient;
269         cl->canHandleNewFBSize = true;
270         cl->GetPassword = passwdHandler;
271         cl->GotFrameBufferUpdate = updatefb;
272         cl->GotXCutText = cuttext;
273         rfbClientSetClientData(cl, 0, this);
274
275         cl->serverHost = strdup(m_host.toUtf8().constData());
276
277         if (m_port < 0 || !m_port) // port is invalid or empty...
278             m_port = 5900; // fallback: try an often used VNC port
279
280         if (m_port >= 0 && m_port < 100) // the user most likely used the short form (e.g. :1)
281             m_port += 5900;
282         cl->serverPort = m_port;
283
284         kDebug(5011) << "--------------------- trying init ---------------------";
285
286         if (rfbInitClient(cl, 0, 0))
287             break;
288
289         if (m_passwordError) {
290                         passwd_failures++;
291                         if(passwd_failures > 2) {
292                                 m_stopped = true;
293                                 clean = true; //rfbInitClient cleans up after itself upon failure
294                         }
295                         continue;
296                 }
297
298         return;
299     }
300
301     locker.unlock();
302
303         inMessageHandler = false;
304
305     // Main VNC event loop
306     while (!m_stopped) {
307         const int i = WaitForMessage(cl, 500);
308         if (i < 0)
309             break;
310                 if(m_stopped) {
311                         kDebug(5011) << "foo2";
312                         break;
313                 }
314         if (i) {
315                         inMessageHandler = true;
316             if (!HandleRFBServerMessage(cl))
317                 break;
318                         inMessageHandler = false;
319                 }
320
321                 if(m_stopped) {
322                         kDebug(5011) << "foo4";
323                         break;
324                 }
325
326         locker.relock();
327
328         while (!m_eventQueue.isEmpty()) {
329             ClientEvent* clientEvent = m_eventQueue.dequeue();
330             clientEvent->fire(cl);
331             delete clientEvent;
332         }
333
334         locker.unlock();
335     }
336         kDebug(5011) << "exited main loop";
337
338     // Cleanup allocated resources
339     locker.relock();
340         kDebug(5011) << "mutex relocked";
341         if(!clean)
342                 rfbClientCleanup(cl);
343     m_stopped = true;
344         kDebug(5011) << "exiting run()";
345 }
346
347 ClientEvent::~ClientEvent()
348 {
349 }
350
351 void PointerClientEvent::fire(rfbClient* cl)
352 {
353     SendPointerEvent(cl, m_x, m_y, m_buttonMask);
354 }
355
356 void KeyClientEvent::fire(rfbClient* cl)
357 {
358     SendKeyEvent(cl, m_key, m_pressed);
359 }
360
361 void ClientCutEvent::fire(rfbClient* cl)
362 {
363     SendClientCutText(cl, text.toUtf8().data(), text.size());
364 }
365
366 void VncClientThread::mouseEvent(int x, int y, int buttonMask)
367 {
368     QMutexLocker lock(&mutex);
369     if (m_stopped)
370         return;
371
372     m_eventQueue.enqueue(new PointerClientEvent(x, y, buttonMask));
373 }
374
375 void VncClientThread::keyEvent(int key, bool pressed)
376 {
377     QMutexLocker lock(&mutex);
378     if (m_stopped)
379         return;
380
381     m_eventQueue.enqueue(new KeyClientEvent(key, pressed));
382 }
383
384 void VncClientThread::clientCut(const QString &text)
385 {
386     QMutexLocker lock(&mutex);
387     if (m_stopped)
388         return;
389
390     m_eventQueue.enqueue(new ClientCutEvent(text));
391 }
392
393 #include "moc_vncclientthread.cpp"