blob: e903284056a14350c3ca35967b5bdc9f8b1e1443 [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"
36#include "i18n.h"
37#include "parameters.h"
38
39using namespace rdr;
40using namespace rfb;
41using namespace std;
42
43extern void exit_vncviewer();
44
45static rfb::LogWriter vlog("CConn");
46
47CConn::CConn(const char* vncServerName)
48 : serverHost(0), serverPort(0), sock(NULL), desktop(NULL),
49 currentEncoding(encodingTight), lastServerEncoding((unsigned int)-1),
50 formatChange(false), encodingChange(false),
51 firstUpdate(true), pendingUpdate(false)
52{
53 setShared(::shared);
54
55 int encNum = encodingNum(preferredEncoding);
56 if (encNum != -1)
57 currentEncoding = encNum;
58
59 cp.supportsDesktopResize = true;
60 cp.supportsExtendedDesktopSize = true;
61 cp.supportsDesktopRename = true;
62 cp.supportsLocalCursor = useLocalCursor;
63
64 cp.customCompressLevel = customCompressLevel;
65 cp.compressLevel = compressLevel;
66
67 cp.noJpeg = noJpeg;
68 cp.qualityLevel = qualityLevel;
69
70 try {
71 getHostAndPort(vncServerName, &serverHost, &serverPort);
72
73 sock = new network::TcpSocket(serverHost, serverPort);
74 vlog.info(_("connected to host %s port %d"), serverHost, serverPort);
75 } catch (rdr::Exception& e) {
76 vlog.error(e.str());
77 fl_alert(e.str());
78 exit_vncviewer();
79 return;
80 }
81
82 Fl::add_fd(sock->getFd(), FL_READ | FL_EXCEPT, socketEvent, this);
83
84 // See callback below
85 sock->inStream().setBlockCallback(this);
86
87 setServerName(serverHost);
88 setStreams(&sock->inStream(), &sock->outStream());
89
90 initialiseProtocol();
91}
92
93CConn::~CConn()
94{
95 free(serverHost);
96 if (sock)
97 Fl::remove_fd(sock->getFd());
98 delete sock;
99}
100
101// The RFB core is not properly asynchronous, so it calls this callback
102// whenever it needs to block to wait for more data. Since FLTK is
103// monitoring the socket, we just make sure FLTK gets to run.
104
105void CConn::blockCallback()
106{
107 int next_timer;
108
109 next_timer = Timer::checkTimeouts();
110 if (next_timer == 0)
111 next_timer = INT_MAX;
112
113 Fl::wait((double)next_timer / 1000.0);
114}
115
116void CConn::socketEvent(int fd, void *data)
117{
118 CConn *cc;
119 static bool recursing = false;
120
121 assert(data);
122 cc = (CConn*)data;
123
124 // I don't think processMsg() is recursion safe, so add this check
125 if (recursing)
126 return;
127
128 recursing = true;
129
130 try {
131 // processMsg() only processes one message, so we need to loop
132 // until the buffers are empty or things will stall.
133 do {
134 cc->processMsg();
135 } while (cc->sock->inStream().checkNoWait(1));
136 } catch (rdr::EndOfStream& e) {
137 vlog.info(e.str());
138 exit_vncviewer();
139 } catch (rdr::Exception& e) {
140 vlog.error(e.str());
141 fl_alert(e.str());
142 exit_vncviewer();
143 }
144
145 recursing = false;
146}
147
148////////////////////// CConnection callback methods //////////////////////
149
150// serverInit() is called when the serverInit message has been received. At
151// this point we create the desktop window and display it. We also tell the
152// server the pixel format and encodings to use and request the first update.
153void CConn::serverInit()
154{
155 CConnection::serverInit();
156
157 // If using AutoSelect with old servers, start in FullColor
158 // mode. See comment in autoSelectFormatAndEncoding.
159 if (cp.beforeVersion(3, 8) && autoSelect)
160 fullColour.setParam(true);
161
162 serverPF = cp.pf();
163
164 desktop = new DesktopWindow(cp.width, cp.height, cp.name(), serverPF, this);
165 fullColourPF = desktop->getPreferredPF();
166
167 formatChange = encodingChange = true;
168 requestNewUpdate();
169}
170
171// setDesktopSize() is called when the desktop size changes (including when
172// it is set initially).
173void CConn::setDesktopSize(int w, int h)
174{
175 CConnection::setDesktopSize(w,h);
176 resizeFramebuffer();
177}
178
179// setExtendedDesktopSize() is a more advanced version of setDesktopSize()
180void CConn::setExtendedDesktopSize(int reason, int result, int w, int h,
181 const rfb::ScreenSet& layout)
182{
183 CConnection::setExtendedDesktopSize(reason, result, w, h, layout);
184
185 if ((reason == reasonClient) && (result != resultSuccess)) {
186 vlog.error(_("SetDesktopSize failed: %d"), result);
187 return;
188 }
189
190 resizeFramebuffer();
191}
192
193// setName() is called when the desktop name changes
194void CConn::setName(const char* name)
195{
196 CConnection::setName(name);
197 if (desktop)
198 desktop->setName(name);
199}
200
201// framebufferUpdateStart() is called at the beginning of an update.
202// Here we try to send out a new framebuffer update request so that the
203// next update can be sent out in parallel with us decoding the current
204// one. We cannot do this if we're in the middle of a format change
205// though.
206void CConn::framebufferUpdateStart()
207{
208 if (!formatChange) {
209 pendingUpdate = true;
210 requestNewUpdate();
211 } else
212 pendingUpdate = false;
213}
214
215// framebufferUpdateEnd() is called at the end of an update.
216// For each rectangle, the FdInStream will have timed the speed
217// of the connection, allowing us to select format and encoding
218// appropriately, and then request another incremental update.
219void CConn::framebufferUpdateEnd()
220{
221 desktop->updateWindow();
222
223 if (firstUpdate) {
224 int width, height;
225
226 if (cp.supportsSetDesktopSize &&
227 sscanf(desktopSize.getValueStr(), "%dx%d", &width, &height) == 2) {
228 ScreenSet layout;
229
230 layout = cp.screenLayout;
231
232 if (layout.num_screens() == 0)
233 layout.add_screen(rfb::Screen());
234 else if (layout.num_screens() != 1) {
235 ScreenSet::iterator iter;
236
237 while (true) {
238 iter = layout.begin();
239 ++iter;
240
241 if (iter == layout.end())
242 break;
243
244 layout.remove_screen(iter->id);
245 }
246 }
247
248 layout.begin()->dimensions.tl.x = 0;
249 layout.begin()->dimensions.tl.y = 0;
250 layout.begin()->dimensions.br.x = width;
251 layout.begin()->dimensions.br.y = height;
252
253 writer()->writeSetDesktopSize(width, height, layout);
254 }
255
256 firstUpdate = false;
257 }
258
259 // A format change prevented us from sending this before the update,
260 // so make sure to send it now.
261 if (formatChange && !pendingUpdate)
262 requestNewUpdate();
263
264 // Compute new settings based on updated bandwidth values
265 if (autoSelect)
266 autoSelectFormatAndEncoding();
267
268 // Make sure that the FLTK handling and the timers gets some CPU time
269 // in case of back to back framebuffer updates.
270 Fl::check();
271 Timer::checkTimeouts();
272}
273
274// The rest of the callbacks are fairly self-explanatory...
275
276void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs)
277{
278 desktop->setColourMapEntries(firstColour, nColours, rgbs);
279}
280
281void CConn::bell()
282{
283 fl_beep();
284}
285
286void CConn::serverCutText(const char* str, rdr::U32 len)
287{
288// desktop->serverCutText(str,len);
289}
290
291// We start timing on beginRect and stop timing on endRect, to
292// avoid skewing the bandwidth estimation as a result of the server
293// being slow or the network having high latency
294void CConn::beginRect(const Rect& r, int encoding)
295{
296 sock->inStream().startTiming();
297 if (encoding != encodingCopyRect) {
298 lastServerEncoding = encoding;
299 }
300}
301
302void CConn::endRect(const Rect& r, int encoding)
303{
304 sock->inStream().stopTiming();
305}
306
307void CConn::fillRect(const rfb::Rect& r, rfb::Pixel p)
308{
309 desktop->fillRect(r,p);
310}
311void CConn::imageRect(const rfb::Rect& r, void* p)
312{
313 desktop->imageRect(r,p);
314}
315void CConn::copyRect(const rfb::Rect& r, int sx, int sy)
316{
317 desktop->copyRect(r,sx,sy);
318}
319void CConn::setCursor(int width, int height, const Point& hotspot,
320 void* data, void* mask)
321{
322// desktop->setCursor(width, height, hotspot, data, mask);
323}
324
325////////////////////// Internal methods //////////////////////
326
327void CConn::resizeFramebuffer()
328{
329/*
330 if (!desktop)
331 return;
332 if ((desktop->width() == cp.width) && (desktop->height() == cp.height))
333 return;
334
335 desktop->resize(cp.width, cp.height);
336*/
337}
338
339// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
340// to the connection speed:
341//
342// First we wait for at least one second of bandwidth measurement.
343//
344// Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
345// which should be perceptually lossless.
346//
347// If the bandwidth is below that, we choose a more lossy JPEG quality.
348//
349// If the bandwidth drops below 256 Kbps, we switch to palette mode.
350//
351// Note: The system here is fairly arbitrary and should be replaced
352// with something more intelligent at the server end.
353//
354void CConn::autoSelectFormatAndEncoding()
355{
356 int kbitsPerSecond = sock->inStream().kbitsPerSecond();
357 unsigned int timeWaited = sock->inStream().timeWaited();
358 bool newFullColour = fullColour;
359 int newQualityLevel = qualityLevel;
360
361 // Always use Tight
362 if (currentEncoding != encodingTight) {
363 currentEncoding = encodingTight;
364 encodingChange = true;
365 }
366
367 // Check that we have a decent bandwidth measurement
368 if ((kbitsPerSecond == 0) || (timeWaited < 10000))
369 return;
370
371 // Select appropriate quality level
372 if (!noJpeg) {
373 if (kbitsPerSecond > 16000)
374 newQualityLevel = 8;
375 else
376 newQualityLevel = 6;
377
378 if (newQualityLevel != qualityLevel) {
379 vlog.info(_("Throughput %d kbit/s - changing to quality %d"),
380 kbitsPerSecond, newQualityLevel);
381 cp.qualityLevel = newQualityLevel;
382 qualityLevel.setParam(newQualityLevel);
383 encodingChange = true;
384 }
385 }
386
387 if (cp.beforeVersion(3, 8)) {
388 // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
389 // cursors "asynchronously". If this happens in the middle of a
390 // pixel format change, the server will encode the cursor with
391 // the old format, but the client will try to decode it
392 // according to the new format. This will lead to a
393 // crash. Therefore, we do not allow automatic format change for
394 // old servers.
395 return;
396 }
397
398 // Select best color level
399 newFullColour = (kbitsPerSecond > 256);
400 if (newFullColour != fullColour) {
401 vlog.info(_("Throughput %d kbit/s - full color is now %s"),
402 kbitsPerSecond,
403 newFullColour ? _("enabled") : _("disabled"));
404 fullColour.setParam(newFullColour);
405 formatChange = true;
406 }
407}
408
409// checkEncodings() sends a setEncodings message if one is needed.
410void CConn::checkEncodings()
411{
412 if (encodingChange && writer()) {
413 vlog.info(_("Using %s encoding"),encodingName(currentEncoding));
414 writer()->writeSetEncodings(currentEncoding, true);
415 encodingChange = false;
416 }
417}
418
419// requestNewUpdate() requests an update from the server, having set the
420// format and encoding appropriately.
421void CConn::requestNewUpdate()
422{
423 if (formatChange) {
424 PixelFormat pf;
425
426 /* Catch incorrect requestNewUpdate calls */
427 assert(pendingUpdate == false);
428
429 if (fullColour) {
430 pf = fullColourPF;
431 } else {
432 if (lowColourLevel == 0)
433 pf = PixelFormat(8,3,0,1,1,1,1,2,1,0);
434 else if (lowColourLevel == 1)
435 pf = PixelFormat(8,6,0,1,3,3,3,4,2,0);
436 else
437 pf = PixelFormat(8,8,0,0);
438 }
439 char str[256];
440 pf.print(str, 256);
441 vlog.info(_("Using pixel format %s"),str);
442 desktop->setServerPF(pf);
443 cp.setPF(pf);
444 writer()->writeSetPixelFormat(pf);
445 }
446 checkEncodings();
447 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
448 !formatChange);
449 formatChange = false;
450}