blob: 81453dc37a846e3bc4e5326a21df435a38717c10 [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;
Pierre Ossman5156d5e2011-03-09 09:42:34 +000068
69 cp.customCompressLevel = customCompressLevel;
70 cp.compressLevel = compressLevel;
71
72 cp.noJpeg = noJpeg;
73 cp.qualityLevel = qualityLevel;
74
75 try {
76 getHostAndPort(vncServerName, &serverHost, &serverPort);
77
78 sock = new network::TcpSocket(serverHost, serverPort);
79 vlog.info(_("connected to host %s port %d"), serverHost, serverPort);
80 } catch (rdr::Exception& e) {
81 vlog.error(e.str());
82 fl_alert(e.str());
83 exit_vncviewer();
84 return;
85 }
86
87 Fl::add_fd(sock->getFd(), FL_READ | FL_EXCEPT, socketEvent, this);
88
89 // See callback below
90 sock->inStream().setBlockCallback(this);
91
92 setServerName(serverHost);
93 setStreams(&sock->inStream(), &sock->outStream());
94
95 initialiseProtocol();
Pierre Ossmanf4f30942011-05-17 09:39:07 +000096
97 OptionsDialog::addCallback(handleOptions, this);
Pierre Ossman5156d5e2011-03-09 09:42:34 +000098}
99
100CConn::~CConn()
101{
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000102 OptionsDialog::removeCallback(handleOptions);
103
Pierre Ossman6a9e2e62011-05-19 14:47:43 +0000104 if (desktop)
105 delete desktop;
106
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000107 free(serverHost);
108 if (sock)
109 Fl::remove_fd(sock->getFd());
110 delete sock;
111}
112
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000113void CConn::refreshFramebuffer()
114{
115 // FIXME: We cannot safely trigger an update request directly but must
116 // wait for the next update to arrive.
117 if (!formatChange)
118 forceNonincremental = true;
119}
120
Pierre Ossman2eb1d112011-05-16 12:18:08 +0000121const char *CConn::connectionInfo()
122{
123 static char infoText[1024] = "";
124
125 char pfStr[100];
126 char spfStr[100];
127
128 cp.pf().print(pfStr, 100);
129 serverPF.print(spfStr, 100);
130
131 int secType = csecurity->getType();
132
133 snprintf(infoText, sizeof(infoText),
134 _("Desktop name: %.80s\n"
135 "Host: %.80s port: %d\n"
136 "Size: %d x %d\n"
137 "Pixel format: %s\n"
138 "(server default %s)\n"
139 "Requested encoding: %s\n"
140 "Last used encoding: %s\n"
141 "Line speed estimate: %d kbit/s\n"
142 "Protocol version: %d.%d\n"
143 "Security method: %s\n"),
144 cp.name(), serverHost, serverPort, cp.width, cp.height,
145 pfStr, spfStr, encodingName(currentEncoding),
146 encodingName(lastServerEncoding),
147 sock->inStream().kbitsPerSecond(),
148 cp.majorVersion, cp.minorVersion,
149 secTypeName(secType));
150
151 return infoText;
152}
153
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000154// The RFB core is not properly asynchronous, so it calls this callback
155// whenever it needs to block to wait for more data. Since FLTK is
156// monitoring the socket, we just make sure FLTK gets to run.
157
158void CConn::blockCallback()
159{
160 int next_timer;
161
162 next_timer = Timer::checkTimeouts();
163 if (next_timer == 0)
164 next_timer = INT_MAX;
165
166 Fl::wait((double)next_timer / 1000.0);
167}
168
169void CConn::socketEvent(int fd, void *data)
170{
171 CConn *cc;
172 static bool recursing = false;
173
174 assert(data);
175 cc = (CConn*)data;
176
177 // I don't think processMsg() is recursion safe, so add this check
178 if (recursing)
179 return;
180
181 recursing = true;
182
183 try {
184 // processMsg() only processes one message, so we need to loop
185 // until the buffers are empty or things will stall.
186 do {
187 cc->processMsg();
188 } while (cc->sock->inStream().checkNoWait(1));
189 } catch (rdr::EndOfStream& e) {
190 vlog.info(e.str());
191 exit_vncviewer();
192 } catch (rdr::Exception& e) {
193 vlog.error(e.str());
194 fl_alert(e.str());
195 exit_vncviewer();
196 }
197
198 recursing = false;
199}
200
201////////////////////// CConnection callback methods //////////////////////
202
203// serverInit() is called when the serverInit message has been received. At
204// this point we create the desktop window and display it. We also tell the
205// server the pixel format and encodings to use and request the first update.
206void CConn::serverInit()
207{
208 CConnection::serverInit();
209
210 // If using AutoSelect with old servers, start in FullColor
211 // mode. See comment in autoSelectFormatAndEncoding.
212 if (cp.beforeVersion(3, 8) && autoSelect)
213 fullColour.setParam(true);
214
215 serverPF = cp.pf();
216
217 desktop = new DesktopWindow(cp.width, cp.height, cp.name(), serverPF, this);
218 fullColourPF = desktop->getPreferredPF();
219
220 formatChange = encodingChange = true;
221 requestNewUpdate();
222}
223
224// setDesktopSize() is called when the desktop size changes (including when
225// it is set initially).
226void CConn::setDesktopSize(int w, int h)
227{
228 CConnection::setDesktopSize(w,h);
229 resizeFramebuffer();
230}
231
232// setExtendedDesktopSize() is a more advanced version of setDesktopSize()
233void CConn::setExtendedDesktopSize(int reason, int result, int w, int h,
234 const rfb::ScreenSet& layout)
235{
236 CConnection::setExtendedDesktopSize(reason, result, w, h, layout);
237
238 if ((reason == reasonClient) && (result != resultSuccess)) {
239 vlog.error(_("SetDesktopSize failed: %d"), result);
240 return;
241 }
242
243 resizeFramebuffer();
244}
245
246// setName() is called when the desktop name changes
247void CConn::setName(const char* name)
248{
249 CConnection::setName(name);
250 if (desktop)
251 desktop->setName(name);
252}
253
254// framebufferUpdateStart() is called at the beginning of an update.
255// Here we try to send out a new framebuffer update request so that the
256// next update can be sent out in parallel with us decoding the current
257// one. We cannot do this if we're in the middle of a format change
258// though.
259void CConn::framebufferUpdateStart()
260{
261 if (!formatChange) {
262 pendingUpdate = true;
263 requestNewUpdate();
264 } else
265 pendingUpdate = false;
266}
267
268// framebufferUpdateEnd() is called at the end of an update.
269// For each rectangle, the FdInStream will have timed the speed
270// of the connection, allowing us to select format and encoding
271// appropriately, and then request another incremental update.
272void CConn::framebufferUpdateEnd()
273{
274 desktop->updateWindow();
275
276 if (firstUpdate) {
277 int width, height;
278
279 if (cp.supportsSetDesktopSize &&
280 sscanf(desktopSize.getValueStr(), "%dx%d", &width, &height) == 2) {
281 ScreenSet layout;
282
283 layout = cp.screenLayout;
284
285 if (layout.num_screens() == 0)
286 layout.add_screen(rfb::Screen());
287 else if (layout.num_screens() != 1) {
288 ScreenSet::iterator iter;
289
290 while (true) {
291 iter = layout.begin();
292 ++iter;
293
294 if (iter == layout.end())
295 break;
296
297 layout.remove_screen(iter->id);
298 }
299 }
300
301 layout.begin()->dimensions.tl.x = 0;
302 layout.begin()->dimensions.tl.y = 0;
303 layout.begin()->dimensions.br.x = width;
304 layout.begin()->dimensions.br.y = height;
305
306 writer()->writeSetDesktopSize(width, height, layout);
307 }
308
309 firstUpdate = false;
310 }
311
312 // A format change prevented us from sending this before the update,
313 // so make sure to send it now.
314 if (formatChange && !pendingUpdate)
315 requestNewUpdate();
316
317 // Compute new settings based on updated bandwidth values
318 if (autoSelect)
319 autoSelectFormatAndEncoding();
320
321 // Make sure that the FLTK handling and the timers gets some CPU time
322 // in case of back to back framebuffer updates.
323 Fl::check();
324 Timer::checkTimeouts();
325}
326
327// The rest of the callbacks are fairly self-explanatory...
328
329void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs)
330{
331 desktop->setColourMapEntries(firstColour, nColours, rgbs);
332}
333
334void CConn::bell()
335{
336 fl_beep();
337}
338
339void CConn::serverCutText(const char* str, rdr::U32 len)
340{
Pierre Ossman689c4582011-05-26 15:39:41 +0000341 char *buffer;
342 int size, ret;
Pierre Ossmand81e8f42011-05-19 14:47:15 +0000343
Pierre Ossman689c4582011-05-26 15:39:41 +0000344 size = fl_utf8froma(NULL, 0, str, len);
345 if (size <= 0)
Pierre Ossmand81e8f42011-05-19 14:47:15 +0000346 return;
Pierre Ossman689c4582011-05-26 15:39:41 +0000347
348 size++;
349
350 buffer = new char[size];
351
352 ret = fl_utf8froma(buffer, size, str, len);
353 assert(ret < size);
Pierre Ossmand81e8f42011-05-19 14:47:15 +0000354
355 vlog.debug("Got clipboard data: '%s'", buffer);
356
357 Fl::copy(buffer, ret, 1);
Pierre Ossman689c4582011-05-26 15:39:41 +0000358
359 delete [] buffer;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000360}
361
362// We start timing on beginRect and stop timing on endRect, to
363// avoid skewing the bandwidth estimation as a result of the server
364// being slow or the network having high latency
365void CConn::beginRect(const Rect& r, int encoding)
366{
367 sock->inStream().startTiming();
368 if (encoding != encodingCopyRect) {
369 lastServerEncoding = encoding;
370 }
371}
372
373void CConn::endRect(const Rect& r, int encoding)
374{
375 sock->inStream().stopTiming();
376}
377
378void CConn::fillRect(const rfb::Rect& r, rfb::Pixel p)
379{
380 desktop->fillRect(r,p);
381}
382void CConn::imageRect(const rfb::Rect& r, void* p)
383{
384 desktop->imageRect(r,p);
385}
386void CConn::copyRect(const rfb::Rect& r, int sx, int sy)
387{
388 desktop->copyRect(r,sx,sy);
389}
390void CConn::setCursor(int width, int height, const Point& hotspot,
391 void* data, void* mask)
392{
Pierre Ossman835b4ef2011-06-08 17:02:36 +0000393 desktop->setCursor(width, height, hotspot, data, mask);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000394}
395
396////////////////////// Internal methods //////////////////////
397
398void CConn::resizeFramebuffer()
399{
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000400 if (!desktop)
401 return;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000402
Pierre Ossman6455d852011-06-01 09:33:00 +0000403 desktop->resizeFramebuffer(cp.width, cp.height);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000404}
405
406// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
407// to the connection speed:
408//
409// First we wait for at least one second of bandwidth measurement.
410//
411// Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
412// which should be perceptually lossless.
413//
414// If the bandwidth is below that, we choose a more lossy JPEG quality.
415//
416// If the bandwidth drops below 256 Kbps, we switch to palette mode.
417//
418// Note: The system here is fairly arbitrary and should be replaced
419// with something more intelligent at the server end.
420//
421void CConn::autoSelectFormatAndEncoding()
422{
423 int kbitsPerSecond = sock->inStream().kbitsPerSecond();
424 unsigned int timeWaited = sock->inStream().timeWaited();
425 bool newFullColour = fullColour;
426 int newQualityLevel = qualityLevel;
427
428 // Always use Tight
429 if (currentEncoding != encodingTight) {
430 currentEncoding = encodingTight;
431 encodingChange = true;
432 }
433
434 // Check that we have a decent bandwidth measurement
435 if ((kbitsPerSecond == 0) || (timeWaited < 10000))
436 return;
437
438 // Select appropriate quality level
439 if (!noJpeg) {
440 if (kbitsPerSecond > 16000)
441 newQualityLevel = 8;
442 else
443 newQualityLevel = 6;
444
445 if (newQualityLevel != qualityLevel) {
446 vlog.info(_("Throughput %d kbit/s - changing to quality %d"),
447 kbitsPerSecond, newQualityLevel);
448 cp.qualityLevel = newQualityLevel;
449 qualityLevel.setParam(newQualityLevel);
450 encodingChange = true;
451 }
452 }
453
454 if (cp.beforeVersion(3, 8)) {
455 // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
456 // cursors "asynchronously". If this happens in the middle of a
457 // pixel format change, the server will encode the cursor with
458 // the old format, but the client will try to decode it
459 // according to the new format. This will lead to a
460 // crash. Therefore, we do not allow automatic format change for
461 // old servers.
462 return;
463 }
464
465 // Select best color level
466 newFullColour = (kbitsPerSecond > 256);
467 if (newFullColour != fullColour) {
468 vlog.info(_("Throughput %d kbit/s - full color is now %s"),
469 kbitsPerSecond,
470 newFullColour ? _("enabled") : _("disabled"));
471 fullColour.setParam(newFullColour);
472 formatChange = true;
473 }
474}
475
476// checkEncodings() sends a setEncodings message if one is needed.
477void CConn::checkEncodings()
478{
479 if (encodingChange && writer()) {
480 vlog.info(_("Using %s encoding"),encodingName(currentEncoding));
481 writer()->writeSetEncodings(currentEncoding, true);
482 encodingChange = false;
483 }
484}
485
486// requestNewUpdate() requests an update from the server, having set the
487// format and encoding appropriately.
488void CConn::requestNewUpdate()
489{
490 if (formatChange) {
491 PixelFormat pf;
492
493 /* Catch incorrect requestNewUpdate calls */
494 assert(pendingUpdate == false);
495
496 if (fullColour) {
497 pf = fullColourPF;
498 } else {
499 if (lowColourLevel == 0)
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000500 pf = mediumColourPF;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000501 else if (lowColourLevel == 1)
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000502 pf = lowColourPF;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000503 else
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000504 pf = verylowColourPF;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000505 }
506 char str[256];
507 pf.print(str, 256);
508 vlog.info(_("Using pixel format %s"),str);
509 desktop->setServerPF(pf);
510 cp.setPF(pf);
511 writer()->writeSetPixelFormat(pf);
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000512
513 forceNonincremental = true;
514
515 formatChange = false;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000516 }
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000517
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000518 checkEncodings();
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000519
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000520 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000521 !forceNonincremental);
522
523 forceNonincremental = false;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000524}
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000525
526void CConn::handleOptions(void *data)
527{
528 CConn *self = (CConn*)data;
529
530 // Checking all the details of the current set of encodings is just
531 // a pain. Assume something has changed, as resending the encoding
532 // list is cheap. Avoid overriding what the auto logic has selected
533 // though.
534 if (!autoSelect) {
535 int encNum = encodingNum(preferredEncoding);
536
537 if (encNum != -1)
538 self->currentEncoding = encNum;
539
540 self->cp.qualityLevel = qualityLevel;
541 }
542
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000543 self->cp.customCompressLevel = customCompressLevel;
544 self->cp.compressLevel = compressLevel;
545
546 self->cp.noJpeg = noJpeg;
547
548 self->encodingChange = true;
549
550 // Format changes refreshes the entire screen though and are therefore
551 // very costly. It's probably worth the effort to see if it is necessary
552 // here.
553 PixelFormat pf;
554
555 if (fullColour) {
556 pf = self->fullColourPF;
557 } else {
558 if (lowColourLevel == 0)
559 pf = mediumColourPF;
560 else if (lowColourLevel == 1)
561 pf = lowColourPF;
562 else
563 pf = verylowColourPF;
564 }
565
566 if (!pf.equal(self->cp.pf()))
567 self->formatChange = true;
568}