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