1 /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 * Copyright (C) 2011 D. R. Commander. All Rights Reserved.
3 * Copyright 2009-2014 Pierre Ossman for Cendio AB
4 *
5 * This is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This software is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this software; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18 * USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <assert.h>
26 #ifndef _WIN32
27 #include <unistd.h>
28 #endif
29
30 #include <rfb/CMsgWriter.h>
31 #include <rfb/CSecurity.h>
32 #include <rfb/Hostname.h>
33 #include <rfb/LogWriter.h>
34 #include <rfb/Security.h>
35 #include <rfb/util.h>
36 #include <rfb/screenTypes.h>
37 #include <rfb/fenceTypes.h>
38 #include <rfb/Timer.h>
39 #include <network/TcpSocket.h>
40 #ifndef WIN32
41 #include <network/UnixSocket.h>
42 #endif
43
44 #include <FL/Fl.H>
45 #include <FL/fl_ask.H>
46
47 #include "CConn.h"
48 #include "OptionsDialog.h"
49 #include "DesktopWindow.h"
50 #include "PlatformPixelBuffer.h"
51 #include "i18n.h"
52 #include "parameters.h"
53 #include "vncviewer.h"
54
55 #ifdef WIN32
56 #include "win32.h"
57 #endif
58
59 using namespace rdr;
60 using namespace rfb;
61 using namespace std;
62
63 static rfb::LogWriter vlog("CConn");
64
65 // 8 colours (1 bit per component)
66 static const PixelFormat verylowColourPF(8, 3,false, true,
67 1, 1, 1, 2, 1, 0);
68 // 64 colours (2 bits per component)
69 static const PixelFormat lowColourPF(8, 6, false, true,
70 3, 3, 3, 4, 2, 0);
71 // 256 colours (2-3 bits per component)
72 static const PixelFormat mediumColourPF(8, 8, false, true,
73 7, 7, 3, 5, 2, 0);
74
75 // Time new bandwidth estimates are weighted against (in ms)
76 static const unsigned bpsEstimateWindow = 1000;
77
CConn(const char * vncServerName,network::Socket * socket=NULL)78 CConn::CConn(const char* vncServerName, network::Socket* socket=NULL)
79 : serverHost(0), serverPort(0), desktop(NULL),
80 updateCount(0), pixelCount(0),
81 lastServerEncoding((unsigned int)-1), bpsEstimate(20000000)
82 {
83 setShared(::shared);
84 sock = socket;
85
86 supportsLocalCursor = true;
87 supportsCursorPosition = true;
88 supportsDesktopResize = true;
89 supportsLEDState = false;
90
91 if (customCompressLevel)
92 setCompressLevel(::compressLevel);
93
94 if (!noJpeg)
95 setQualityLevel(::qualityLevel);
96
97 if(sock == NULL) {
98 try {
99 #ifndef WIN32
100 if (strchr(vncServerName, '/') != NULL) {
101 sock = new network::UnixSocket(vncServerName);
102 serverHost = sock->getPeerAddress();
103 vlog.info(_("Connected to socket %s"), serverHost);
104 } else
105 #endif
106 {
107 getHostAndPort(vncServerName, &serverHost, &serverPort);
108
109 sock = new network::TcpSocket(serverHost, serverPort);
110 vlog.info(_("Connected to host %s port %d"), serverHost, serverPort);
111 }
112 } catch (rdr::Exception& e) {
113 vlog.error("%s", e.str());
114 abort_connection(_("Failed to connect to \"%s\":\n\n%s"),
115 vncServerName, e.str());
116 return;
117 }
118 }
119
120 Fl::add_fd(sock->getFd(), FL_READ | FL_EXCEPT, socketEvent, this);
121
122 setServerName(serverHost);
123 setStreams(&sock->inStream(), &sock->outStream());
124
125 initialiseProtocol();
126
127 OptionsDialog::addCallback(handleOptions, this);
128 }
129
~CConn()130 CConn::~CConn()
131 {
132 close();
133
134 OptionsDialog::removeCallback(handleOptions);
135 Fl::remove_timeout(handleUpdateTimeout, this);
136
137 if (desktop)
138 delete desktop;
139
140 delete [] serverHost;
141 if (sock)
142 Fl::remove_fd(sock->getFd());
143 delete sock;
144 }
145
connectionInfo()146 const char *CConn::connectionInfo()
147 {
148 static char infoText[1024] = "";
149
150 char scratch[100];
151 char pfStr[100];
152
153 // Crude way of avoiding constant overflow checks
154 assert((sizeof(scratch) + 1) * 10 < sizeof(infoText));
155
156 infoText[0] = '\0';
157
158 snprintf(scratch, sizeof(scratch),
159 _("Desktop name: %.80s"), server.name());
160 strcat(infoText, scratch);
161 strcat(infoText, "\n");
162
163 snprintf(scratch, sizeof(scratch),
164 _("Host: %.80s port: %d"), serverHost, serverPort);
165 strcat(infoText, scratch);
166 strcat(infoText, "\n");
167
168 snprintf(scratch, sizeof(scratch),
169 _("Size: %d x %d"), server.width(), server.height());
170 strcat(infoText, scratch);
171 strcat(infoText, "\n");
172
173 // TRANSLATORS: Will be filled in with a string describing the
174 // protocol pixel format in a fairly language neutral way
175 server.pf().print(pfStr, 100);
176 snprintf(scratch, sizeof(scratch),
177 _("Pixel format: %s"), pfStr);
178 strcat(infoText, scratch);
179 strcat(infoText, "\n");
180
181 // TRANSLATORS: Similar to the earlier "Pixel format" string
182 serverPF.print(pfStr, 100);
183 snprintf(scratch, sizeof(scratch),
184 _("(server default %s)"), pfStr);
185 strcat(infoText, scratch);
186 strcat(infoText, "\n");
187
188 snprintf(scratch, sizeof(scratch),
189 _("Requested encoding: %s"), encodingName(getPreferredEncoding()));
190 strcat(infoText, scratch);
191 strcat(infoText, "\n");
192
193 snprintf(scratch, sizeof(scratch),
194 _("Last used encoding: %s"), encodingName(lastServerEncoding));
195 strcat(infoText, scratch);
196 strcat(infoText, "\n");
197
198 snprintf(scratch, sizeof(scratch),
199 _("Line speed estimate: %d kbit/s"), (int)(bpsEstimate/1000));
200 strcat(infoText, scratch);
201 strcat(infoText, "\n");
202
203 snprintf(scratch, sizeof(scratch),
204 _("Protocol version: %d.%d"), server.majorVersion, server.minorVersion);
205 strcat(infoText, scratch);
206 strcat(infoText, "\n");
207
208 snprintf(scratch, sizeof(scratch),
209 _("Security method: %s"), secTypeName(csecurity->getType()));
210 strcat(infoText, scratch);
211 strcat(infoText, "\n");
212
213 return infoText;
214 }
215
getUpdateCount()216 unsigned CConn::getUpdateCount()
217 {
218 return updateCount;
219 }
220
getPixelCount()221 unsigned CConn::getPixelCount()
222 {
223 return pixelCount;
224 }
225
getPosition()226 unsigned CConn::getPosition()
227 {
228 return sock->inStream().pos();
229 }
230
socketEvent(FL_SOCKET fd,void * data)231 void CConn::socketEvent(FL_SOCKET fd, void *data)
232 {
233 CConn *cc;
234 static bool recursing = false;
235 int when;
236
237 assert(data);
238 cc = (CConn*)data;
239
240 // I don't think processMsg() is recursion safe, so add this check
241 if (recursing)
242 return;
243
244 recursing = true;
245
246 try {
247 // We might have been called to flush unwritten socket data
248 cc->sock->outStream().flush();
249
250 cc->sock->outStream().cork(true);
251
252 // processMsg() only processes one message, so we need to loop
253 // until the buffers are empty or things will stall.
254 while (cc->processMsg()) {
255
256 // Make sure that the FLTK handling and the timers gets some CPU
257 // time in case of back to back messages
258 Fl::check();
259 Timer::checkTimeouts();
260
261 // Also check if we need to stop reading and terminate
262 if (should_disconnect())
263 break;
264 }
265
266 cc->sock->outStream().cork(false);
267 cc->sock->outStream().flush();
268 } catch (rdr::EndOfStream& e) {
269 vlog.info("%s", e.str());
270 if (!cc->desktop) {
271 vlog.error(_("The connection was dropped by the server before "
272 "the session could be established."));
273 abort_connection(_("The connection was dropped by the server "
274 "before the session could be established."));
275 } else {
276 disconnect();
277 }
278 } catch (rdr::Exception& e) {
279 vlog.error("%s", e.str());
280 abort_connection(_("An unexpected error occurred when communicating "
281 "with the server:\n\n%s"), e.str());
282 }
283
284 when = FL_READ | FL_EXCEPT;
285 if (cc->sock->outStream().hasBufferedData())
286 when |= FL_WRITE;
287
288 Fl::add_fd(fd, when, socketEvent, data);
289
290 recursing = false;
291 }
292
293 ////////////////////// CConnection callback methods //////////////////////
294
295 // initDone() is called when the serverInit message has been received. At
296 // this point we create the desktop window and display it. We also tell the
297 // server the pixel format and encodings to use and request the first update.
initDone()298 void CConn::initDone()
299 {
300 // If using AutoSelect with old servers, start in FullColor
301 // mode. See comment in autoSelectFormatAndEncoding.
302 if (server.beforeVersion(3, 8) && autoSelect)
303 fullColour.setParam(true);
304
305 serverPF = server.pf();
306
307 desktop = new DesktopWindow(server.width(), server.height(),
308 server.name(), serverPF, this);
309 fullColourPF = desktop->getPreferredPF();
310
311 // Force a switch to the format and encoding we'd like
312 updatePixelFormat();
313 int encNum = encodingNum(::preferredEncoding);
314 if (encNum != -1)
315 setPreferredEncoding(encNum);
316 }
317
318 // setDesktopSize() is called when the desktop size changes (including when
319 // it is set initially).
setDesktopSize(int w,int h)320 void CConn::setDesktopSize(int w, int h)
321 {
322 CConnection::setDesktopSize(w,h);
323 resizeFramebuffer();
324 }
325
326 // setExtendedDesktopSize() is a more advanced version of setDesktopSize()
setExtendedDesktopSize(unsigned reason,unsigned result,int w,int h,const rfb::ScreenSet & layout)327 void CConn::setExtendedDesktopSize(unsigned reason, unsigned result,
328 int w, int h, const rfb::ScreenSet& layout)
329 {
330 CConnection::setExtendedDesktopSize(reason, result, w, h, layout);
331
332 if ((reason == reasonClient) && (result != resultSuccess)) {
333 vlog.error(_("SetDesktopSize failed: %d"), result);
334 return;
335 }
336
337 resizeFramebuffer();
338 }
339
340 // setName() is called when the desktop name changes
setName(const char * name)341 void CConn::setName(const char* name)
342 {
343 CConnection::setName(name);
344 desktop->setName(name);
345 }
346
347 // framebufferUpdateStart() is called at the beginning of an update.
348 // Here we try to send out a new framebuffer update request so that the
349 // next update can be sent out in parallel with us decoding the current
350 // one.
framebufferUpdateStart()351 void CConn::framebufferUpdateStart()
352 {
353 CConnection::framebufferUpdateStart();
354
355 // For bandwidth estimate
356 gettimeofday(&updateStartTime, NULL);
357 updateStartPos = sock->inStream().pos();
358
359 // Update the screen prematurely for very slow updates
360 Fl::add_timeout(1.0, handleUpdateTimeout, this);
361 }
362
363 // framebufferUpdateEnd() is called at the end of an update.
364 // For each rectangle, the FdInStream will have timed the speed
365 // of the connection, allowing us to select format and encoding
366 // appropriately, and then request another incremental update.
framebufferUpdateEnd()367 void CConn::framebufferUpdateEnd()
368 {
369 unsigned long long elapsed, bps, weight;
370 struct timeval now;
371
372 CConnection::framebufferUpdateEnd();
373
374 updateCount++;
375
376 // Calculate bandwidth everything managed to maintain during this update
377 gettimeofday(&now, NULL);
378 elapsed = (now.tv_sec - updateStartTime.tv_sec) * 1000000;
379 elapsed += now.tv_usec - updateStartTime.tv_usec;
380 if (elapsed == 0)
381 elapsed = 1;
382 bps = (unsigned long long)(sock->inStream().pos() -
383 updateStartPos) * 8 *
384 1000000 / elapsed;
385 // Allow this update to influence things more the longer it took, to a
386 // maximum of 20% of the new value.
387 weight = elapsed * 1000 / bpsEstimateWindow;
388 if (weight > 200000)
389 weight = 200000;
390 bpsEstimate = ((bpsEstimate * (1000000 - weight)) +
391 (bps * weight)) / 1000000;
392
393 Fl::remove_timeout(handleUpdateTimeout, this);
394 desktop->updateWindow();
395
396 // Compute new settings based on updated bandwidth values
397 if (autoSelect)
398 autoSelectFormatAndEncoding();
399 }
400
401 // The rest of the callbacks are fairly self-explanatory...
402
setColourMapEntries(int firstColour,int nColours,rdr::U16 * rgbs)403 void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs)
404 {
405 vlog.error(_("Invalid SetColourMapEntries from server!"));
406 }
407
bell()408 void CConn::bell()
409 {
410 fl_beep();
411 }
412
dataRect(const Rect & r,int encoding)413 bool CConn::dataRect(const Rect& r, int encoding)
414 {
415 bool ret;
416
417 if (encoding != encodingCopyRect)
418 lastServerEncoding = encoding;
419
420 ret = CConnection::dataRect(r, encoding);
421
422 if (ret)
423 pixelCount += r.area();
424
425 return ret;
426 }
427
setCursor(int width,int height,const Point & hotspot,const rdr::U8 * data)428 void CConn::setCursor(int width, int height, const Point& hotspot,
429 const rdr::U8* data)
430 {
431 desktop->setCursor(width, height, hotspot, data);
432 }
433
setCursorPos(const Point & pos)434 void CConn::setCursorPos(const Point& pos)
435 {
436 desktop->setCursorPos(pos);
437 }
438
fence(rdr::U32 flags,unsigned len,const char data[])439 void CConn::fence(rdr::U32 flags, unsigned len, const char data[])
440 {
441 CMsgHandler::fence(flags, len, data);
442
443 if (flags & fenceFlagRequest) {
444 // We handle everything synchronously so we trivially honor these modes
445 flags = flags & (fenceFlagBlockBefore | fenceFlagBlockAfter);
446
447 writer()->writeFence(flags, len, data);
448 return;
449 }
450 }
451
setLEDState(unsigned int state)452 void CConn::setLEDState(unsigned int state)
453 {
454 CConnection::setLEDState(state);
455
456 desktop->setLEDState(state);
457 }
458
handleClipboardRequest()459 void CConn::handleClipboardRequest()
460 {
461 desktop->handleClipboardRequest();
462 }
463
handleClipboardAnnounce(bool available)464 void CConn::handleClipboardAnnounce(bool available)
465 {
466 desktop->handleClipboardAnnounce(available);
467 }
468
handleClipboardData(const char * data)469 void CConn::handleClipboardData(const char* data)
470 {
471 desktop->handleClipboardData(data);
472 }
473
474
475 ////////////////////// Internal methods //////////////////////
476
resizeFramebuffer()477 void CConn::resizeFramebuffer()
478 {
479 desktop->resizeFramebuffer(server.width(), server.height());
480 }
481
482 // autoSelectFormatAndEncoding() chooses the format and encoding appropriate
483 // to the connection speed:
484 //
485 // First we wait for at least one second of bandwidth measurement.
486 //
487 // Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
488 // which should be perceptually lossless.
489 //
490 // If the bandwidth is below that, we choose a more lossy JPEG quality.
491 //
492 // If the bandwidth drops below 256 Kbps, we switch to palette mode.
493 //
494 // Note: The system here is fairly arbitrary and should be replaced
495 // with something more intelligent at the server end.
496 //
autoSelectFormatAndEncoding()497 void CConn::autoSelectFormatAndEncoding()
498 {
499 bool newFullColour = fullColour;
500 int newQualityLevel = ::qualityLevel;
501
502 // Always use Tight
503 setPreferredEncoding(encodingTight);
504
505 // Select appropriate quality level
506 if (!noJpeg) {
507 if (bpsEstimate > 16000000)
508 newQualityLevel = 8;
509 else
510 newQualityLevel = 6;
511
512 if (newQualityLevel != ::qualityLevel) {
513 vlog.info(_("Throughput %d kbit/s - changing to quality %d"),
514 (int)(bpsEstimate/1000), newQualityLevel);
515 ::qualityLevel.setParam(newQualityLevel);
516 setQualityLevel(newQualityLevel);
517 }
518 }
519
520 if (server.beforeVersion(3, 8)) {
521 // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
522 // cursors "asynchronously". If this happens in the middle of a
523 // pixel format change, the server will encode the cursor with
524 // the old format, but the client will try to decode it
525 // according to the new format. This will lead to a
526 // crash. Therefore, we do not allow automatic format change for
527 // old servers.
528 return;
529 }
530
531 // Select best color level
532 newFullColour = (bpsEstimate > 256000);
533 if (newFullColour != fullColour) {
534 if (newFullColour)
535 vlog.info(_("Throughput %d kbit/s - full color is now enabled"),
536 (int)(bpsEstimate/1000));
537 else
538 vlog.info(_("Throughput %d kbit/s - full color is now disabled"),
539 (int)(bpsEstimate/1000));
540 fullColour.setParam(newFullColour);
541 updatePixelFormat();
542 }
543 }
544
545 // requestNewUpdate() requests an update from the server, having set the
546 // format and encoding appropriately.
updatePixelFormat()547 void CConn::updatePixelFormat()
548 {
549 PixelFormat pf;
550
551 if (fullColour) {
552 pf = fullColourPF;
553 } else {
554 if (lowColourLevel == 0)
555 pf = verylowColourPF;
556 else if (lowColourLevel == 1)
557 pf = lowColourPF;
558 else
559 pf = mediumColourPF;
560 }
561
562 char str[256];
563 pf.print(str, 256);
564 vlog.info(_("Using pixel format %s"),str);
565 setPF(pf);
566 }
567
handleOptions(void * data)568 void CConn::handleOptions(void *data)
569 {
570 CConn *self = (CConn*)data;
571
572 // Checking all the details of the current set of encodings is just
573 // a pain. Assume something has changed, as resending the encoding
574 // list is cheap. Avoid overriding what the auto logic has selected
575 // though.
576 if (!autoSelect) {
577 int encNum = encodingNum(::preferredEncoding);
578
579 if (encNum != -1)
580 self->setPreferredEncoding(encNum);
581 }
582
583 if (customCompressLevel)
584 self->setCompressLevel(::compressLevel);
585 else
586 self->setCompressLevel(-1);
587
588 if (!noJpeg && !autoSelect)
589 self->setQualityLevel(::qualityLevel);
590 else
591 self->setQualityLevel(-1);
592
593 self->updatePixelFormat();
594 }
595
handleUpdateTimeout(void * data)596 void CConn::handleUpdateTimeout(void *data)
597 {
598 CConn *self = (CConn *)data;
599
600 assert(self);
601
602 self->desktop->updateWindow();
603
604 Fl::repeat_timeout(1.0, handleUpdateTimeout, data);
605 }
606