blob: 5c40a707c10cd139fc9490f750b68426014461c2 [file] [log] [blame]
Pierre Ossman5156d5e2011-03-09 09:42:34 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
DRC33c15e32011-11-03 18:49:21 +00002 * Copyright (C) 2011 D. R. Commander. All Rights Reserved.
Pierre Ossmanfdba3fe2014-01-31 13:12:18 +01003 * Copyright 2009-2014 Pierre Ossman for Cendio AB
Pierre Ossman5156d5e2011-03-09 09:42:34 +00004 *
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
Peter Åstrandc359f362011-08-23 12:04:46 +000021#ifdef HAVE_CONFIG_H
22#include <config.h>
23#endif
24
Pierre Ossman5156d5e2011-03-09 09:42:34 +000025#include <assert.h>
DRCb65bb932011-06-24 03:17:00 +000026#ifndef _WIN32
Pierre Ossman5156d5e2011-03-09 09:42:34 +000027#include <unistd.h>
DRCb65bb932011-06-24 03:17:00 +000028#endif
Pierre Ossman5156d5e2011-03-09 09:42:34 +000029
30#include <rfb/CMsgWriter.h>
Pierre Ossman0068a4f2015-11-09 15:48:19 +010031#include <rfb/CSecurity.h>
Pierre Ossman5156d5e2011-03-09 09:42:34 +000032#include <rfb/Hostname.h>
33#include <rfb/LogWriter.h>
Pierre Ossman0068a4f2015-11-09 15:48:19 +010034#include <rfb/Security.h>
Pierre Ossman5156d5e2011-03-09 09:42:34 +000035#include <rfb/util.h>
36#include <rfb/screenTypes.h>
Pierre Ossmane28bdb22011-11-14 16:02:06 +000037#include <rfb/fenceTypes.h>
Pierre Ossman5156d5e2011-03-09 09:42:34 +000038#include <rfb/Timer.h>
39#include <network/TcpSocket.h>
Pierre Ossman5d055462018-05-03 14:04:38 +020040#ifndef WIN32
41#include <network/UnixSocket.h>
42#endif
Pierre Ossman5156d5e2011-03-09 09:42:34 +000043
44#include <FL/Fl.H>
45#include <FL/fl_ask.H>
46
47#include "CConn.h"
Pierre Ossmanf4f30942011-05-17 09:39:07 +000048#include "OptionsDialog.h"
Pierre Ossman947b48d2014-01-27 16:52:35 +010049#include "DesktopWindow.h"
Pierre Ossmanc9dd3a42015-11-18 16:24:16 +010050#include "PlatformPixelBuffer.h"
Pierre Ossman5156d5e2011-03-09 09:42:34 +000051#include "i18n.h"
52#include "parameters.h"
Pierre Ossman39ceb502011-07-12 15:54:25 +000053#include "vncviewer.h"
Pierre Ossman5156d5e2011-03-09 09:42:34 +000054
DRCb65bb932011-06-24 03:17:00 +000055#ifdef WIN32
56#include "win32.h"
57#endif
58
Pierre Ossman5156d5e2011-03-09 09:42:34 +000059using namespace rdr;
60using namespace rfb;
61using namespace std;
62
Pierre Ossman5156d5e2011-03-09 09:42:34 +000063static rfb::LogWriter vlog("CConn");
64
Pierre Ossmancf836f22011-07-14 14:34:09 +000065// 8 colours (1 bit per component)
66static const PixelFormat verylowColourPF(8, 3,false, true,
67 1, 1, 1, 2, 1, 0);
68// 64 colours (2 bits per component)
69static const PixelFormat lowColourPF(8, 6, false, true,
70 3, 3, 3, 4, 2, 0);
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +010071// 256 colours (2-3 bits per component)
72static const PixelFormat mediumColourPF(8, 8, false, true,
73 7, 7, 3, 5, 2, 0);
Pierre Ossmanf4f30942011-05-17 09:39:07 +000074
Pierre Ossman2a7a8d62013-02-15 08:33:39 +000075CConn::CConn(const char* vncServerName, network::Socket* socket=NULL)
76 : serverHost(0), serverPort(0), desktop(NULL),
Pierre Ossmanef6881b2018-06-20 11:26:18 +020077 updateCount(0), pixelCount(0),
78 lastServerEncoding((unsigned int)-1)
Pierre Ossman5156d5e2011-03-09 09:42:34 +000079{
80 setShared(::shared);
Pierre Ossman2a7a8d62013-02-15 08:33:39 +000081 sock = socket;
Pierre Ossman5156d5e2011-03-09 09:42:34 +000082
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +020083 server.supportsLocalCursor = true;
Pierre Ossmanda389562011-06-09 08:24:37 +000084
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +020085 server.supportsDesktopResize = true;
Pierre Ossman5156d5e2011-03-09 09:42:34 +000086
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +020087 server.supportsLEDState = true;
Pierre Ossman2fa63f82016-12-05 15:26:21 +010088
Pierre Ossmana22459d2014-03-17 14:42:10 +010089 if (customCompressLevel)
Pierre Ossmanef6881b2018-06-20 11:26:18 +020090 setCompressLevel(compressLevel);
Pierre Ossman5156d5e2011-03-09 09:42:34 +000091
Pierre Ossmana22459d2014-03-17 14:42:10 +010092 if (!noJpeg)
Pierre Ossmanef6881b2018-06-20 11:26:18 +020093 setQualityLevel(qualityLevel);
Pierre Ossman5156d5e2011-03-09 09:42:34 +000094
Pierre Ossman2a7a8d62013-02-15 08:33:39 +000095 if(sock == NULL) {
96 try {
Pierre Ossman5d055462018-05-03 14:04:38 +020097#ifndef WIN32
98 if (strchr(vncServerName, '/') != NULL) {
99 sock = new network::UnixSocket(vncServerName);
100 serverHost = sock->getPeerAddress();
Pierre Ossman4af93a92018-10-25 10:32:38 +0200101 vlog.info(_("Connected to socket %s"), serverHost);
Pierre Ossman5d055462018-05-03 14:04:38 +0200102 } else
103#endif
104 {
105 getHostAndPort(vncServerName, &serverHost, &serverPort);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000106
Pierre Ossman5d055462018-05-03 14:04:38 +0200107 sock = new network::TcpSocket(serverHost, serverPort);
Pierre Ossman4af93a92018-10-25 10:32:38 +0200108 vlog.info(_("Connected to host %s port %d"), serverHost, serverPort);
Pierre Ossman5d055462018-05-03 14:04:38 +0200109 }
Pierre Ossman2a7a8d62013-02-15 08:33:39 +0000110 } catch (rdr::Exception& e) {
111 vlog.error("%s", e.str());
Dr. David Alan Gilbertf4d1d892017-07-11 12:11:50 +0100112 if (alertOnFatalError)
113 fl_alert("%s", e.str());
Pierre Ossman2a7a8d62013-02-15 08:33:39 +0000114 exit_vncviewer();
115 return;
116 }
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000117 }
118
119 Fl::add_fd(sock->getFd(), FL_READ | FL_EXCEPT, socketEvent, this);
120
121 // See callback below
122 sock->inStream().setBlockCallback(this);
123
124 setServerName(serverHost);
125 setStreams(&sock->inStream(), &sock->outStream());
126
127 initialiseProtocol();
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000128
129 OptionsDialog::addCallback(handleOptions, this);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000130}
131
132CConn::~CConn()
133{
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000134 OptionsDialog::removeCallback(handleOptions);
Pierre Ossmand9b90032015-09-23 12:18:52 +0200135 Fl::remove_timeout(handleUpdateTimeout, this);
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000136
Pierre Ossman6a9e2e62011-05-19 14:47:43 +0000137 if (desktop)
138 delete desktop;
139
Pierre Ossman5e04c262011-11-09 11:31:12 +0000140 delete [] serverHost;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000141 if (sock)
142 Fl::remove_fd(sock->getFd());
143 delete sock;
144}
145
Pierre Ossman2eb1d112011-05-16 12:18:08 +0000146const char *CConn::connectionInfo()
147{
148 static char infoText[1024] = "";
149
Pierre Ossmanb2046432014-09-22 11:17:34 +0200150 char scratch[100];
Pierre Ossman2eb1d112011-05-16 12:18:08 +0000151 char pfStr[100];
Pierre Ossmanb2046432014-09-22 11:17:34 +0200152
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),
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200159 _("Desktop name: %.80s"), server.name());
Pierre Ossmanb2046432014-09-22 11:17:34 +0200160 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),
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200169 _("Size: %d x %d"), server.width(), server.height());
Pierre Ossmanb2046432014-09-22 11:17:34 +0200170 strcat(infoText, scratch);
171 strcat(infoText, "\n");
Pierre Ossman2eb1d112011-05-16 12:18:08 +0000172
Pierre Ossman744e55c2014-12-03 14:00:54 +0100173 // TRANSLATORS: Will be filled in with a string describing the
174 // protocol pixel format in a fairly language neutral way
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200175 server.pf().print(pfStr, 100);
Pierre Ossmanb2046432014-09-22 11:17:34 +0200176 snprintf(scratch, sizeof(scratch),
177 _("Pixel format: %s"), pfStr);
178 strcat(infoText, scratch);
179 strcat(infoText, "\n");
Pierre Ossman2eb1d112011-05-16 12:18:08 +0000180
Pierre Ossman744e55c2014-12-03 14:00:54 +0100181 // TRANSLATORS: Similar to the earlier "Pixel format" string
Pierre Ossmanb2046432014-09-22 11:17:34 +0200182 serverPF.print(pfStr, 100);
183 snprintf(scratch, sizeof(scratch),
184 _("(server default %s)"), pfStr);
185 strcat(infoText, scratch);
186 strcat(infoText, "\n");
Pierre Ossman2eb1d112011-05-16 12:18:08 +0000187
Pierre Ossmanb2046432014-09-22 11:17:34 +0200188 snprintf(scratch, sizeof(scratch),
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200189 _("Requested encoding: %s"), encodingName(getPreferredEncoding()));
Pierre Ossmanb2046432014-09-22 11:17:34 +0200190 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"), sock->inStream().kbitsPerSecond());
200 strcat(infoText, scratch);
201 strcat(infoText, "\n");
202
203 snprintf(scratch, sizeof(scratch),
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200204 _("Protocol version: %d.%d"), server.majorVersion, server.minorVersion);
Pierre Ossmanb2046432014-09-22 11:17:34 +0200205 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");
Pierre Ossman2eb1d112011-05-16 12:18:08 +0000212
213 return infoText;
214}
215
Pierre Ossmanfecf0a42018-03-26 12:22:47 +0200216unsigned CConn::getUpdateCount()
Pierre Ossman921f6c82017-02-24 12:33:09 +0100217{
Pierre Ossmanfecf0a42018-03-26 12:22:47 +0200218 return updateCount;
Pierre Ossman921f6c82017-02-24 12:33:09 +0100219}
220
221unsigned CConn::getPixelCount()
222{
223 return pixelCount;
224}
225
226unsigned CConn::getPosition()
227{
228 return sock->inStream().pos();
229}
230
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000231// The RFB core is not properly asynchronous, so it calls this callback
232// whenever it needs to block to wait for more data. Since FLTK is
233// monitoring the socket, we just make sure FLTK gets to run.
234
235void CConn::blockCallback()
236{
Pierre Ossman5102fa92015-11-18 16:23:21 +0100237 run_mainloop();
Pierre Ossman1db73242015-09-23 12:20:32 +0200238
239 if (should_exit())
240 throw rdr::Exception("Termination requested");
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000241}
242
DRC3e7ed812013-02-26 10:34:22 +0000243void CConn::socketEvent(FL_SOCKET fd, void *data)
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000244{
245 CConn *cc;
246 static bool recursing = false;
247
248 assert(data);
249 cc = (CConn*)data;
250
251 // I don't think processMsg() is recursion safe, so add this check
252 if (recursing)
253 return;
254
255 recursing = true;
256
257 try {
258 // processMsg() only processes one message, so we need to loop
259 // until the buffers are empty or things will stall.
260 do {
261 cc->processMsg();
Pierre Ossman9a73adf2016-12-29 16:52:56 +0100262
263 // Make sure that the FLTK handling and the timers gets some CPU
264 // time in case of back to back messages
265 Fl::check();
266 Timer::checkTimeouts();
267
268 // Also check if we need to stop reading and terminate
269 if (should_exit())
270 break;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000271 } while (cc->sock->inStream().checkNoWait(1));
272 } catch (rdr::EndOfStream& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000273 vlog.info("%s", e.str());
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000274 exit_vncviewer();
275 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000276 vlog.error("%s", e.str());
Pierre Ossman1db73242015-09-23 12:20:32 +0200277 // Somebody might already have requested us to terminate, and
278 // might have already provided an error message.
279 if (!should_exit())
280 exit_vncviewer(e.str());
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000281 }
282
283 recursing = false;
284}
285
286////////////////////// CConnection callback methods //////////////////////
287
Pierre Ossman2affd772018-06-20 07:03:10 +0200288// initDone() is called when the serverInit message has been received. At
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000289// this point we create the desktop window and display it. We also tell the
290// server the pixel format and encodings to use and request the first update.
Pierre Ossman2affd772018-06-20 07:03:10 +0200291void CConn::initDone()
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000292{
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000293 // If using AutoSelect with old servers, start in FullColor
294 // mode. See comment in autoSelectFormatAndEncoding.
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200295 if (server.beforeVersion(3, 8) && autoSelect)
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000296 fullColour.setParam(true);
297
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200298 serverPF = server.pf();
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000299
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200300 desktop = new DesktopWindow(server.width(), server.height(),
301 server.name(), serverPF, this);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000302 fullColourPF = desktop->getPreferredPF();
303
Pierre Ossman5d512c32011-11-04 11:42:16 +0000304 // Force a switch to the format and encoding we'd like
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200305 updatePixelFormat();
306 int encNum = encodingNum(::preferredEncoding);
307 if (encNum != -1)
308 setPreferredEncoding(encNum);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000309}
310
311// setDesktopSize() is called when the desktop size changes (including when
312// it is set initially).
313void CConn::setDesktopSize(int w, int h)
314{
315 CConnection::setDesktopSize(w,h);
316 resizeFramebuffer();
317}
318
319// setExtendedDesktopSize() is a more advanced version of setDesktopSize()
Pierre Ossman28c1d542015-03-03 16:27:44 +0100320void CConn::setExtendedDesktopSize(unsigned reason, unsigned result,
321 int w, int h, const rfb::ScreenSet& layout)
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000322{
323 CConnection::setExtendedDesktopSize(reason, result, w, h, layout);
324
325 if ((reason == reasonClient) && (result != resultSuccess)) {
326 vlog.error(_("SetDesktopSize failed: %d"), result);
327 return;
328 }
329
330 resizeFramebuffer();
331}
332
333// setName() is called when the desktop name changes
334void CConn::setName(const char* name)
335{
336 CConnection::setName(name);
Pierre Ossmandd45b442018-10-31 17:08:59 +0100337 desktop->setName(name);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000338}
339
340// framebufferUpdateStart() is called at the beginning of an update.
341// Here we try to send out a new framebuffer update request so that the
342// next update can be sent out in parallel with us decoding the current
Pierre Ossman5d512c32011-11-04 11:42:16 +0000343// one.
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000344void CConn::framebufferUpdateStart()
345{
Pierre Ossman3da238d2015-11-12 12:20:05 +0100346 CConnection::framebufferUpdateStart();
347
Pierre Ossmand9b90032015-09-23 12:18:52 +0200348 // Update the screen prematurely for very slow updates
349 Fl::add_timeout(1.0, handleUpdateTimeout, this);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000350}
351
352// framebufferUpdateEnd() is called at the end of an update.
353// For each rectangle, the FdInStream will have timed the speed
354// of the connection, allowing us to select format and encoding
355// appropriately, and then request another incremental update.
356void CConn::framebufferUpdateEnd()
357{
Pierre Ossman3da238d2015-11-12 12:20:05 +0100358 CConnection::framebufferUpdateEnd();
359
Pierre Ossmanfecf0a42018-03-26 12:22:47 +0200360 updateCount++;
Pierre Ossman921f6c82017-02-24 12:33:09 +0100361
Pierre Ossmand9b90032015-09-23 12:18:52 +0200362 Fl::remove_timeout(handleUpdateTimeout, this);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000363 desktop->updateWindow();
364
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000365 // Compute new settings based on updated bandwidth values
366 if (autoSelect)
367 autoSelectFormatAndEncoding();
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000368}
369
370// The rest of the callbacks are fairly self-explanatory...
371
372void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs)
373{
Pierre Ossman8ca4c1d2014-09-22 12:54:26 +0200374 vlog.error(_("Invalid SetColourMapEntries from server!"));
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000375}
376
377void CConn::bell()
378{
379 fl_beep();
380}
381
382void CConn::serverCutText(const char* str, rdr::U32 len)
383{
Pierre Ossman4c204232018-03-26 13:32:49 +0200384 desktop->serverCutText(str, len);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000385}
386
Pierre Ossmanfdba3fe2014-01-31 13:12:18 +0100387void CConn::dataRect(const Rect& r, int encoding)
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000388{
389 sock->inStream().startTiming();
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000390
Pierre Ossmanfdba3fe2014-01-31 13:12:18 +0100391 if (encoding != encodingCopyRect)
392 lastServerEncoding = encoding;
393
Pierre Ossman9f273e92015-11-09 16:34:54 +0100394 CConnection::dataRect(r, encoding);
Pierre Ossmanfdba3fe2014-01-31 13:12:18 +0100395
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000396 sock->inStream().stopTiming();
Pierre Ossman921f6c82017-02-24 12:33:09 +0100397
398 pixelCount += r.area();
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000399}
400
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000401void CConn::setCursor(int width, int height, const Point& hotspot,
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100402 const rdr::U8* data)
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000403{
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100404 desktop->setCursor(width, height, hotspot, data);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000405}
406
Pierre Ossmane28bdb22011-11-14 16:02:06 +0000407void CConn::fence(rdr::U32 flags, unsigned len, const char data[])
408{
409 CMsgHandler::fence(flags, len, data);
410
411 if (flags & fenceFlagRequest) {
412 // We handle everything synchronously so we trivially honor these modes
413 flags = flags & (fenceFlagBlockBefore | fenceFlagBlockAfter);
414
415 writer()->writeFence(flags, len, data);
416 return;
417 }
Pierre Ossman6eecbca2018-06-19 10:42:24 +0200418}
Pierre Ossmane28bdb22011-11-14 16:02:06 +0000419
Pierre Ossman2fa63f82016-12-05 15:26:21 +0100420void CConn::setLEDState(unsigned int state)
421{
422 CConnection::setLEDState(state);
423
424 desktop->setLEDState(state);
425}
426
DRC33c15e32011-11-03 18:49:21 +0000427
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000428////////////////////// Internal methods //////////////////////
429
430void CConn::resizeFramebuffer()
431{
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200432 desktop->resizeFramebuffer(server.width(), server.height());
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000433}
434
435// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
436// to the connection speed:
437//
438// First we wait for at least one second of bandwidth measurement.
439//
440// Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
441// which should be perceptually lossless.
442//
443// If the bandwidth is below that, we choose a more lossy JPEG quality.
444//
445// If the bandwidth drops below 256 Kbps, we switch to palette mode.
446//
447// Note: The system here is fairly arbitrary and should be replaced
448// with something more intelligent at the server end.
449//
450void CConn::autoSelectFormatAndEncoding()
451{
452 int kbitsPerSecond = sock->inStream().kbitsPerSecond();
453 unsigned int timeWaited = sock->inStream().timeWaited();
454 bool newFullColour = fullColour;
455 int newQualityLevel = qualityLevel;
456
457 // Always use Tight
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200458 setPreferredEncoding(encodingTight);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000459
460 // Check that we have a decent bandwidth measurement
461 if ((kbitsPerSecond == 0) || (timeWaited < 10000))
462 return;
463
464 // Select appropriate quality level
465 if (!noJpeg) {
466 if (kbitsPerSecond > 16000)
467 newQualityLevel = 8;
468 else
469 newQualityLevel = 6;
470
471 if (newQualityLevel != qualityLevel) {
472 vlog.info(_("Throughput %d kbit/s - changing to quality %d"),
473 kbitsPerSecond, newQualityLevel);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000474 qualityLevel.setParam(newQualityLevel);
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200475 setQualityLevel(newQualityLevel);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000476 }
477 }
478
Pierre Ossmanb14a6bc2018-06-18 15:44:26 +0200479 if (server.beforeVersion(3, 8)) {
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000480 // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
481 // cursors "asynchronously". If this happens in the middle of a
482 // pixel format change, the server will encode the cursor with
483 // the old format, but the client will try to decode it
484 // according to the new format. This will lead to a
485 // crash. Therefore, we do not allow automatic format change for
486 // old servers.
487 return;
488 }
489
490 // Select best color level
491 newFullColour = (kbitsPerSecond > 256);
492 if (newFullColour != fullColour) {
Pierre Ossman4af93a92018-10-25 10:32:38 +0200493 if (newFullColour)
494 vlog.info(_("Throughput %d kbit/s - full color is now enabled"),
495 kbitsPerSecond);
496 else
497 vlog.info(_("Throughput %d kbit/s - full color is now disabled"),
498 kbitsPerSecond);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000499 fullColour.setParam(newFullColour);
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200500 updatePixelFormat();
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000501 }
502}
503
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000504// requestNewUpdate() requests an update from the server, having set the
505// format and encoding appropriately.
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200506void CConn::updatePixelFormat()
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000507{
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200508 PixelFormat pf;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000509
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200510 if (fullColour) {
511 pf = fullColourPF;
512 } else {
513 if (lowColourLevel == 0)
514 pf = verylowColourPF;
515 else if (lowColourLevel == 1)
516 pf = lowColourPF;
517 else
518 pf = mediumColourPF;
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000519 }
Pierre Ossmand4c61ce2011-04-29 11:18:12 +0000520
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200521 char str[256];
522 pf.print(str, 256);
523 vlog.info(_("Using pixel format %s"),str);
524 setPF(pf);
Pierre Ossman5156d5e2011-03-09 09:42:34 +0000525}
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000526
527void CConn::handleOptions(void *data)
528{
529 CConn *self = (CConn*)data;
530
531 // Checking all the details of the current set of encodings is just
532 // a pain. Assume something has changed, as resending the encoding
533 // list is cheap. Avoid overriding what the auto logic has selected
534 // though.
535 if (!autoSelect) {
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200536 int encNum = encodingNum(::preferredEncoding);
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000537
538 if (encNum != -1)
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200539 self->setPreferredEncoding(encNum);
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000540 }
541
Pierre Ossmana22459d2014-03-17 14:42:10 +0100542 if (customCompressLevel)
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200543 self->setCompressLevel(compressLevel);
Pierre Ossmana22459d2014-03-17 14:42:10 +0100544 else
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200545 self->setCompressLevel(-1);
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000546
Pierre Ossmana22459d2014-03-17 14:42:10 +0100547 if (!noJpeg && !autoSelect)
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200548 self->setQualityLevel(qualityLevel);
Pierre Ossmana22459d2014-03-17 14:42:10 +0100549 else
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200550 self->setQualityLevel(-1);
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000551
Pierre Ossmanef6881b2018-06-20 11:26:18 +0200552 self->updatePixelFormat();
Pierre Ossmanf4f30942011-05-17 09:39:07 +0000553}
Pierre Ossmand9b90032015-09-23 12:18:52 +0200554
555void CConn::handleUpdateTimeout(void *data)
556{
557 CConn *self = (CConn *)data;
558
559 assert(self);
560
561 self->desktop->updateWindow();
562
563 Fl::repeat_timeout(1.0, handleUpdateTimeout, data);
564}