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