blob: 9525c719d924c96762168867c92887212b3c88d6 [file] [log] [blame]
Pierre Ossman5156d5e2011-03-09 09:42:34 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 * Copyright 2009-2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
3 *
4 * This is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this software; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17 * USA.
18 */
19
20#include <assert.h>
21#include <unistd.h>
22
23#include <rfb/CMsgWriter.h>
24#include <rfb/encodings.h>
25#include <rfb/Hostname.h>
26#include <rfb/LogWriter.h>
27#include <rfb/util.h>
28#include <rfb/screenTypes.h>
29#include <rfb/Timer.h>
30#include <network/TcpSocket.h>
31
32#include <FL/Fl.H>
33#include <FL/fl_ask.H>
34
35#include "CConn.h"
Pierre Ossmanf4f30942011-05-17 09:39:07 +000036#include "OptionsDialog.h"
Pierre Ossman5156d5e2011-03-09 09:42:34 +000037#include "i18n.h"
38#include "parameters.h"
39
40using namespace rdr;
41using namespace rfb;
42using namespace std;
43
44extern void exit_vncviewer();
45
46static rfb::LogWriter vlog("CConn");
47
Pierre Ossmanf4f30942011-05-17 09:39:07 +000048static const PixelFormat mediumColourPF(8,3,0,1,1,1,1,2,1,0);
49static const PixelFormat lowColourPF(8,6,0,1,3,3,3,4,2,0);
50static const PixelFormat verylowColourPF(8,8,0,0);
51
Pierre Ossman5156d5e2011-03-09 09:42:34 +000052CConn::CConn(const char* vncServerName)
53 : serverHost(0), serverPort(0), sock(NULL), desktop(NULL),
54 currentEncoding(encodingTight), lastServerEncoding((unsigned int)-1),
55 formatChange(false), encodingChange(false),
Pierre Ossmand4c61ce2011-04-29 11:18:12 +000056 firstUpdate(true), pendingUpdate(false),
57 forceNonincremental(false)
Pierre Ossman5156d5e2011-03-09 09:42:34 +000058{
59 setShared(::shared);
60
61 int encNum = encodingNum(preferredEncoding);
62 if (encNum != -1)
63 currentEncoding = encNum;
64
65 cp.supportsDesktopResize = true;
66 cp.supportsExtendedDesktopSize = true;
67 cp.supportsDesktopRename = true;
68 cp.supportsLocalCursor = useLocalCursor;
69
70 cp.customCompressLevel = customCompressLevel;
71 cp.compressLevel = compressLevel;
72
73 cp.noJpeg = noJpeg;
74 cp.qualityLevel = qualityLevel;
75
76 try {
77 getHostAndPort(vncServerName, &serverHost, &serverPort);
78
79 sock = new network::TcpSocket(serverHost, serverPort);
80 vlog.info(_("connected to host %s port %d"), serverHost, serverPort);
81 } catch (rdr::Exception& e) {
82 vlog.error(e.str());
83 fl_alert(e.str());
84 exit_vncviewer();
85 return;
86 }
87
88 Fl::add_fd(sock->getFd(), FL_READ | FL_EXCEPT, socketEvent, this);
89
90 // See callback below
91 sock->inStream().setBlockCallback(this);
92
93 setServerName(serverHost);
94 setStreams(&sock->inStream(), &sock->outStream());
95
96 initialiseProtocol();
Pierre Ossmanf4f30942011-05-17 09:39:07 +000097
98 OptionsDialog::addCallback(handleOptions, this);
Pierre Ossman5156d5e2011-03-09 09:42:34 +000099}
100
101CConn::~CConn()
102{
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000103 OptionsDialog::removeCallback(handleOptions);
104
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000105 free(serverHost);
106 if (sock)
107 Fl::remove_fd(sock->getFd());
108 delete sock;
109}
110
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000111void CConn::refreshFramebuffer()
112{
113 // FIXME: We cannot safely trigger an update request directly but must
114 // wait for the next update to arrive.
115 if (!formatChange)
116 forceNonincremental = true;
117}
118
Pierre Ossman2eb1d112011-05-16 12:18:08 +0000119const char *CConn::connectionInfo()
120{
121 static char infoText[1024] = "";
122
123 char pfStr[100];
124 char spfStr[100];
125
126 cp.pf().print(pfStr, 100);
127 serverPF.print(spfStr, 100);
128
129 int secType = csecurity->getType();
130
131 snprintf(infoText, sizeof(infoText),
132 _("Desktop name: %.80s\n"
133 "Host: %.80s port: %d\n"
134 "Size: %d x %d\n"
135 "Pixel format: %s\n"
136 "(server default %s)\n"
137 "Requested encoding: %s\n"
138 "Last used encoding: %s\n"
139 "Line speed estimate: %d kbit/s\n"
140 "Protocol version: %d.%d\n"
141 "Security method: %s\n"),
142 cp.name(), serverHost, serverPort, cp.width, cp.height,
143 pfStr, spfStr, encodingName(currentEncoding),
144 encodingName(lastServerEncoding),
145 sock->inStream().kbitsPerSecond(),
146 cp.majorVersion, cp.minorVersion,
147 secTypeName(secType));
148
149 return infoText;
150}
151
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000152// The RFB core is not properly asynchronous, so it calls this callback
153// whenever it needs to block to wait for more data. Since FLTK is
154// monitoring the socket, we just make sure FLTK gets to run.
155
156void CConn::blockCallback()
157{
158 int next_timer;
159
160 next_timer = Timer::checkTimeouts();
161 if (next_timer == 0)
162 next_timer = INT_MAX;
163
164 Fl::wait((double)next_timer / 1000.0);
165}
166
167void CConn::socketEvent(int fd, void *data)
168{
169 CConn *cc;
170 static bool recursing = false;
171
172 assert(data);
173 cc = (CConn*)data;
174
175 // I don't think processMsg() is recursion safe, so add this check
176 if (recursing)
177 return;
178
179 recursing = true;
180
181 try {
182 // processMsg() only processes one message, so we need to loop
183 // until the buffers are empty or things will stall.
184 do {
185 cc->processMsg();
186 } while (cc->sock->inStream().checkNoWait(1));
187 } catch (rdr::EndOfStream& e) {
188 vlog.info(e.str());
189 exit_vncviewer();
190 } catch (rdr::Exception& e) {
191 vlog.error(e.str());
192 fl_alert(e.str());
193 exit_vncviewer();
194 }
195
196 recursing = false;
197}
198
199////////////////////// CConnection callback methods //////////////////////
200
201// serverInit() is called when the serverInit message has been received. At
202// this point we create the desktop window and display it. We also tell the
203// server the pixel format and encodings to use and request the first update.
204void CConn::serverInit()
205{
206 CConnection::serverInit();
207
208 // If using AutoSelect with old servers, start in FullColor
209 // mode. See comment in autoSelectFormatAndEncoding.
210 if (cp.beforeVersion(3, 8) && autoSelect)
211 fullColour.setParam(true);
212
213 serverPF = cp.pf();
214
215 desktop = new DesktopWindow(cp.width, cp.height, cp.name(), serverPF, this);
216 fullColourPF = desktop->getPreferredPF();
217
218 formatChange = encodingChange = true;
219 requestNewUpdate();
220}
221
222// setDesktopSize() is called when the desktop size changes (including when
223// it is set initially).
224void CConn::setDesktopSize(int w, int h)
225{
226 CConnection::setDesktopSize(w,h);
227 resizeFramebuffer();
228}
229
230// setExtendedDesktopSize() is a more advanced version of setDesktopSize()
231void CConn::setExtendedDesktopSize(int reason, int result, int w, int h,
232 const rfb::ScreenSet& layout)
233{
234 CConnection::setExtendedDesktopSize(reason, result, w, h, layout);
235
236 if ((reason == reasonClient) && (result != resultSuccess)) {
237 vlog.error(_("SetDesktopSize failed: %d"), result);
238 return;
239 }
240
241 resizeFramebuffer();
242}
243
244// setName() is called when the desktop name changes
245void CConn::setName(const char* name)
246{
247 CConnection::setName(name);
248 if (desktop)
249 desktop->setName(name);
250}
251
252// framebufferUpdateStart() is called at the beginning of an update.
253// Here we try to send out a new framebuffer update request so that the
254// next update can be sent out in parallel with us decoding the current
255// one. We cannot do this if we're in the middle of a format change
256// though.
257void CConn::framebufferUpdateStart()
258{
259 if (!formatChange) {
260 pendingUpdate = true;
261 requestNewUpdate();
262 } else
263 pendingUpdate = false;
264}
265
266// framebufferUpdateEnd() is called at the end of an update.
267// For each rectangle, the FdInStream will have timed the speed
268// of the connection, allowing us to select format and encoding
269// appropriately, and then request another incremental update.
270void CConn::framebufferUpdateEnd()
271{
272 desktop->updateWindow();
273
274 if (firstUpdate) {
275 int width, height;
276
277 if (cp.supportsSetDesktopSize &&
278 sscanf(desktopSize.getValueStr(), "%dx%d", &width, &height) == 2) {
279 ScreenSet layout;
280
281 layout = cp.screenLayout;
282
283 if (layout.num_screens() == 0)
284 layout.add_screen(rfb::Screen());
285 else if (layout.num_screens() != 1) {
286 ScreenSet::iterator iter;
287
288 while (true) {
289 iter = layout.begin();
290 ++iter;
291
292 if (iter == layout.end())
293 break;
294
295 layout.remove_screen(iter->id);
296 }
297 }
298
299 layout.begin()->dimensions.tl.x = 0;
300 layout.begin()->dimensions.tl.y = 0;
301 layout.begin()->dimensions.br.x = width;
302 layout.begin()->dimensions.br.y = height;
303
304 writer()->writeSetDesktopSize(width, height, layout);
305 }
306
307 firstUpdate = false;
308 }
309
310 // A format change prevented us from sending this before the update,
311 // so make sure to send it now.
312 if (formatChange && !pendingUpdate)
313 requestNewUpdate();
314
315 // Compute new settings based on updated bandwidth values
316 if (autoSelect)
317 autoSelectFormatAndEncoding();
318
319 // Make sure that the FLTK handling and the timers gets some CPU time
320 // in case of back to back framebuffer updates.
321 Fl::check();
322 Timer::checkTimeouts();
323}
324
325// The rest of the callbacks are fairly self-explanatory...
326
327void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs)
328{
329 desktop->setColourMapEntries(firstColour, nColours, rgbs);
330}
331
332void CConn::bell()
333{
334 fl_beep();
335}
336
337void CConn::serverCutText(const char* str, rdr::U32 len)
338{
339// desktop->serverCutText(str,len);
340}
341
342// We start timing on beginRect and stop timing on endRect, to
343// avoid skewing the bandwidth estimation as a result of the server
344// being slow or the network having high latency
345void CConn::beginRect(const Rect& r, int encoding)
346{
347 sock->inStream().startTiming();
348 if (encoding != encodingCopyRect) {
349 lastServerEncoding = encoding;
350 }
351}
352
353void CConn::endRect(const Rect& r, int encoding)
354{
355 sock->inStream().stopTiming();
356}
357
358void CConn::fillRect(const rfb::Rect& r, rfb::Pixel p)
359{
360 desktop->fillRect(r,p);
361}
362void CConn::imageRect(const rfb::Rect& r, void* p)
363{
364 desktop->imageRect(r,p);
365}
366void CConn::copyRect(const rfb::Rect& r, int sx, int sy)
367{
368 desktop->copyRect(r,sx,sy);
369}
370void CConn::setCursor(int width, int height, const Point& hotspot,
371 void* data, void* mask)
372{
373// desktop->setCursor(width, height, hotspot, data, mask);
374}
375
376////////////////////// Internal methods //////////////////////
377
378void CConn::resizeFramebuffer()
379{
380/*
381 if (!desktop)
382 return;
383 if ((desktop->width() == cp.width) && (desktop->height() == cp.height))
384 return;
385
386 desktop->resize(cp.width, cp.height);
387*/
388}
389
390// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
391// to the connection speed:
392//
393// First we wait for at least one second of bandwidth measurement.
394//
395// Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
396// which should be perceptually lossless.
397//
398// If the bandwidth is below that, we choose a more lossy JPEG quality.
399//
400// If the bandwidth drops below 256 Kbps, we switch to palette mode.
401//
402// Note: The system here is fairly arbitrary and should be replaced
403// with something more intelligent at the server end.
404//
405void CConn::autoSelectFormatAndEncoding()
406{
407 int kbitsPerSecond = sock->inStream().kbitsPerSecond();
408 unsigned int timeWaited = sock->inStream().timeWaited();
409 bool newFullColour = fullColour;
410 int newQualityLevel = qualityLevel;
411
412 // Always use Tight
413 if (currentEncoding != encodingTight) {
414 currentEncoding = encodingTight;
415 encodingChange = true;
416 }
417
418 // Check that we have a decent bandwidth measurement
419 if ((kbitsPerSecond == 0) || (timeWaited < 10000))
420 return;
421
422 // Select appropriate quality level
423 if (!noJpeg) {
424 if (kbitsPerSecond > 16000)
425 newQualityLevel = 8;
426 else
427 newQualityLevel = 6;
428
429 if (newQualityLevel != qualityLevel) {
430 vlog.info(_("Throughput %d kbit/s - changing to quality %d"),
431 kbitsPerSecond, newQualityLevel);
432 cp.qualityLevel = newQualityLevel;
433 qualityLevel.setParam(newQualityLevel);
434 encodingChange = true;
435 }
436 }
437
438 if (cp.beforeVersion(3, 8)) {
439 // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
440 // cursors "asynchronously". If this happens in the middle of a
441 // pixel format change, the server will encode the cursor with
442 // the old format, but the client will try to decode it
443 // according to the new format. This will lead to a
444 // crash. Therefore, we do not allow automatic format change for
445 // old servers.
446 return;
447 }
448
449 // Select best color level
450 newFullColour = (kbitsPerSecond > 256);
451 if (newFullColour != fullColour) {
452 vlog.info(_("Throughput %d kbit/s - full color is now %s"),
453 kbitsPerSecond,
454 newFullColour ? _("enabled") : _("disabled"));
455 fullColour.setParam(newFullColour);
456 formatChange = true;
457 }
458}
459
460// checkEncodings() sends a setEncodings message if one is needed.
461void CConn::checkEncodings()
462{
463 if (encodingChange && writer()) {
464 vlog.info(_("Using %s encoding"),encodingName(currentEncoding));
465 writer()->writeSetEncodings(currentEncoding, true);
466 encodingChange = false;
467 }
468}
469
470// requestNewUpdate() requests an update from the server, having set the
471// format and encoding appropriately.
472void CConn::requestNewUpdate()
473{
474 if (formatChange) {
475 PixelFormat pf;
476
477 /* Catch incorrect requestNewUpdate calls */
478 assert(pendingUpdate == false);
479
480 if (fullColour) {
481 pf = fullColourPF;
482 } else {
483 if (lowColourLevel == 0)
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000484 pf = mediumColourPF;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000485 else if (lowColourLevel == 1)
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000486 pf = lowColourPF;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000487 else
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000488 pf = verylowColourPF;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000489 }
490 char str[256];
491 pf.print(str, 256);
492 vlog.info(_("Using pixel format %s"),str);
493 desktop->setServerPF(pf);
494 cp.setPF(pf);
495 writer()->writeSetPixelFormat(pf);
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000496
497 forceNonincremental = true;
498
499 formatChange = false;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000500 }
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000501
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000502 checkEncodings();
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000503
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000504 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000505 !forceNonincremental);
506
507 forceNonincremental = false;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000508}
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000509
510void CConn::handleOptions(void *data)
511{
512 CConn *self = (CConn*)data;
513
514 // Checking all the details of the current set of encodings is just
515 // a pain. Assume something has changed, as resending the encoding
516 // list is cheap. Avoid overriding what the auto logic has selected
517 // though.
518 if (!autoSelect) {
519 int encNum = encodingNum(preferredEncoding);
520
521 if (encNum != -1)
522 self->currentEncoding = encNum;
523
524 self->cp.qualityLevel = qualityLevel;
525 }
526
527 self->cp.supportsLocalCursor = useLocalCursor;
528
529 self->cp.customCompressLevel = customCompressLevel;
530 self->cp.compressLevel = compressLevel;
531
532 self->cp.noJpeg = noJpeg;
533
534 self->encodingChange = true;
535
536 // Format changes refreshes the entire screen though and are therefore
537 // very costly. It's probably worth the effort to see if it is necessary
538 // here.
539 PixelFormat pf;
540
541 if (fullColour) {
542 pf = self->fullColourPF;
543 } else {
544 if (lowColourLevel == 0)
545 pf = mediumColourPF;
546 else if (lowColourLevel == 1)
547 pf = lowColourPF;
548 else
549 pf = verylowColourPF;
550 }
551
552 if (!pf.equal(self->cp.pf()))
553 self->formatChange = true;
554}