diff options
author | Pierre Ossman <ossman@cendio.se> | 2011-03-09 09:42:34 +0000 |
---|---|---|
committer | Pierre Ossman <ossman@cendio.se> | 2011-03-09 09:42:34 +0000 |
commit | 5156d5e49b88d9a5c0ede2c81897e8a2d3c52523 (patch) | |
tree | 4ad5e2e9b04ae782dc60669fff3347b826f34fce /vncviewer/CConn.cxx | |
parent | db143c99055594b025127c2998021edf5d500b4b (diff) | |
download | tigervnc-5156d5e49b88d9a5c0ede2c81897e8a2d3c52523.tar.gz tigervnc-5156d5e49b88d9a5c0ede2c81897e8a2d3c52523.zip |
Initial commit of new FLTK based vncviewer. Most of the code comes from the
current Unix vncviewer.
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4345 3789f03b-4d11-0410-bbf8-ca57d06f2519
Diffstat (limited to 'vncviewer/CConn.cxx')
-rw-r--r-- | vncviewer/CConn.cxx | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/vncviewer/CConn.cxx b/vncviewer/CConn.cxx new file mode 100644 index 00000000..e9032840 --- /dev/null +++ b/vncviewer/CConn.cxx @@ -0,0 +1,450 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright 2009-2011 Pierre Ossman <ossman@cendio.se> for Cendio AB + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <assert.h> +#include <unistd.h> + +#include <rfb/CMsgWriter.h> +#include <rfb/encodings.h> +#include <rfb/Hostname.h> +#include <rfb/LogWriter.h> +#include <rfb/util.h> +#include <rfb/screenTypes.h> +#include <rfb/Timer.h> +#include <network/TcpSocket.h> + +#include <FL/Fl.H> +#include <FL/fl_ask.H> + +#include "CConn.h" +#include "i18n.h" +#include "parameters.h" + +using namespace rdr; +using namespace rfb; +using namespace std; + +extern void exit_vncviewer(); + +static rfb::LogWriter vlog("CConn"); + +CConn::CConn(const char* vncServerName) + : serverHost(0), serverPort(0), sock(NULL), desktop(NULL), + currentEncoding(encodingTight), lastServerEncoding((unsigned int)-1), + formatChange(false), encodingChange(false), + firstUpdate(true), pendingUpdate(false) +{ + setShared(::shared); + + int encNum = encodingNum(preferredEncoding); + if (encNum != -1) + currentEncoding = encNum; + + cp.supportsDesktopResize = true; + cp.supportsExtendedDesktopSize = true; + cp.supportsDesktopRename = true; + cp.supportsLocalCursor = useLocalCursor; + + cp.customCompressLevel = customCompressLevel; + cp.compressLevel = compressLevel; + + cp.noJpeg = noJpeg; + cp.qualityLevel = qualityLevel; + + try { + getHostAndPort(vncServerName, &serverHost, &serverPort); + + sock = new network::TcpSocket(serverHost, serverPort); + vlog.info(_("connected to host %s port %d"), serverHost, serverPort); + } catch (rdr::Exception& e) { + vlog.error(e.str()); + fl_alert(e.str()); + exit_vncviewer(); + return; + } + + Fl::add_fd(sock->getFd(), FL_READ | FL_EXCEPT, socketEvent, this); + + // See callback below + sock->inStream().setBlockCallback(this); + + setServerName(serverHost); + setStreams(&sock->inStream(), &sock->outStream()); + + initialiseProtocol(); +} + +CConn::~CConn() +{ + free(serverHost); + if (sock) + Fl::remove_fd(sock->getFd()); + delete sock; +} + +// The RFB core is not properly asynchronous, so it calls this callback +// whenever it needs to block to wait for more data. Since FLTK is +// monitoring the socket, we just make sure FLTK gets to run. + +void CConn::blockCallback() +{ + int next_timer; + + next_timer = Timer::checkTimeouts(); + if (next_timer == 0) + next_timer = INT_MAX; + + Fl::wait((double)next_timer / 1000.0); +} + +void CConn::socketEvent(int fd, void *data) +{ + CConn *cc; + static bool recursing = false; + + assert(data); + cc = (CConn*)data; + + // I don't think processMsg() is recursion safe, so add this check + if (recursing) + return; + + recursing = true; + + try { + // processMsg() only processes one message, so we need to loop + // until the buffers are empty or things will stall. + do { + cc->processMsg(); + } while (cc->sock->inStream().checkNoWait(1)); + } catch (rdr::EndOfStream& e) { + vlog.info(e.str()); + exit_vncviewer(); + } catch (rdr::Exception& e) { + vlog.error(e.str()); + fl_alert(e.str()); + exit_vncviewer(); + } + + recursing = false; +} + +////////////////////// CConnection callback methods ////////////////////// + +// serverInit() is called when the serverInit message has been received. At +// this point we create the desktop window and display it. We also tell the +// server the pixel format and encodings to use and request the first update. +void CConn::serverInit() +{ + CConnection::serverInit(); + + // If using AutoSelect with old servers, start in FullColor + // mode. See comment in autoSelectFormatAndEncoding. + if (cp.beforeVersion(3, 8) && autoSelect) + fullColour.setParam(true); + + serverPF = cp.pf(); + + desktop = new DesktopWindow(cp.width, cp.height, cp.name(), serverPF, this); + fullColourPF = desktop->getPreferredPF(); + + formatChange = encodingChange = true; + requestNewUpdate(); +} + +// setDesktopSize() is called when the desktop size changes (including when +// it is set initially). +void CConn::setDesktopSize(int w, int h) +{ + CConnection::setDesktopSize(w,h); + resizeFramebuffer(); +} + +// setExtendedDesktopSize() is a more advanced version of setDesktopSize() +void CConn::setExtendedDesktopSize(int reason, int result, int w, int h, + const rfb::ScreenSet& layout) +{ + CConnection::setExtendedDesktopSize(reason, result, w, h, layout); + + if ((reason == reasonClient) && (result != resultSuccess)) { + vlog.error(_("SetDesktopSize failed: %d"), result); + return; + } + + resizeFramebuffer(); +} + +// setName() is called when the desktop name changes +void CConn::setName(const char* name) +{ + CConnection::setName(name); + if (desktop) + desktop->setName(name); +} + +// framebufferUpdateStart() is called at the beginning of an update. +// Here we try to send out a new framebuffer update request so that the +// next update can be sent out in parallel with us decoding the current +// one. We cannot do this if we're in the middle of a format change +// though. +void CConn::framebufferUpdateStart() +{ + if (!formatChange) { + pendingUpdate = true; + requestNewUpdate(); + } else + pendingUpdate = false; +} + +// framebufferUpdateEnd() is called at the end of an update. +// For each rectangle, the FdInStream will have timed the speed +// of the connection, allowing us to select format and encoding +// appropriately, and then request another incremental update. +void CConn::framebufferUpdateEnd() +{ + desktop->updateWindow(); + + if (firstUpdate) { + int width, height; + + if (cp.supportsSetDesktopSize && + sscanf(desktopSize.getValueStr(), "%dx%d", &width, &height) == 2) { + ScreenSet layout; + + layout = cp.screenLayout; + + if (layout.num_screens() == 0) + layout.add_screen(rfb::Screen()); + else if (layout.num_screens() != 1) { + ScreenSet::iterator iter; + + while (true) { + iter = layout.begin(); + ++iter; + + if (iter == layout.end()) + break; + + layout.remove_screen(iter->id); + } + } + + layout.begin()->dimensions.tl.x = 0; + layout.begin()->dimensions.tl.y = 0; + layout.begin()->dimensions.br.x = width; + layout.begin()->dimensions.br.y = height; + + writer()->writeSetDesktopSize(width, height, layout); + } + + firstUpdate = false; + } + + // A format change prevented us from sending this before the update, + // so make sure to send it now. + if (formatChange && !pendingUpdate) + requestNewUpdate(); + + // Compute new settings based on updated bandwidth values + if (autoSelect) + autoSelectFormatAndEncoding(); + + // Make sure that the FLTK handling and the timers gets some CPU time + // in case of back to back framebuffer updates. + Fl::check(); + Timer::checkTimeouts(); +} + +// The rest of the callbacks are fairly self-explanatory... + +void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs) +{ + desktop->setColourMapEntries(firstColour, nColours, rgbs); +} + +void CConn::bell() +{ + fl_beep(); +} + +void CConn::serverCutText(const char* str, rdr::U32 len) +{ +// desktop->serverCutText(str,len); +} + +// We start timing on beginRect and stop timing on endRect, to +// avoid skewing the bandwidth estimation as a result of the server +// being slow or the network having high latency +void CConn::beginRect(const Rect& r, int encoding) +{ + sock->inStream().startTiming(); + if (encoding != encodingCopyRect) { + lastServerEncoding = encoding; + } +} + +void CConn::endRect(const Rect& r, int encoding) +{ + sock->inStream().stopTiming(); +} + +void CConn::fillRect(const rfb::Rect& r, rfb::Pixel p) +{ + desktop->fillRect(r,p); +} +void CConn::imageRect(const rfb::Rect& r, void* p) +{ + desktop->imageRect(r,p); +} +void CConn::copyRect(const rfb::Rect& r, int sx, int sy) +{ + desktop->copyRect(r,sx,sy); +} +void CConn::setCursor(int width, int height, const Point& hotspot, + void* data, void* mask) +{ +// desktop->setCursor(width, height, hotspot, data, mask); +} + +////////////////////// Internal methods ////////////////////// + +void CConn::resizeFramebuffer() +{ +/* + if (!desktop) + return; + if ((desktop->width() == cp.width) && (desktop->height() == cp.height)) + return; + + desktop->resize(cp.width, cp.height); +*/ +} + +// autoSelectFormatAndEncoding() chooses the format and encoding appropriate +// to the connection speed: +// +// First we wait for at least one second of bandwidth measurement. +// +// Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality, +// which should be perceptually lossless. +// +// If the bandwidth is below that, we choose a more lossy JPEG quality. +// +// If the bandwidth drops below 256 Kbps, we switch to palette mode. +// +// Note: The system here is fairly arbitrary and should be replaced +// with something more intelligent at the server end. +// +void CConn::autoSelectFormatAndEncoding() +{ + int kbitsPerSecond = sock->inStream().kbitsPerSecond(); + unsigned int timeWaited = sock->inStream().timeWaited(); + bool newFullColour = fullColour; + int newQualityLevel = qualityLevel; + + // Always use Tight + if (currentEncoding != encodingTight) { + currentEncoding = encodingTight; + encodingChange = true; + } + + // Check that we have a decent bandwidth measurement + if ((kbitsPerSecond == 0) || (timeWaited < 10000)) + return; + + // Select appropriate quality level + if (!noJpeg) { + if (kbitsPerSecond > 16000) + newQualityLevel = 8; + else + newQualityLevel = 6; + + if (newQualityLevel != qualityLevel) { + vlog.info(_("Throughput %d kbit/s - changing to quality %d"), + kbitsPerSecond, newQualityLevel); + cp.qualityLevel = newQualityLevel; + qualityLevel.setParam(newQualityLevel); + encodingChange = true; + } + } + + if (cp.beforeVersion(3, 8)) { + // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with + // cursors "asynchronously". If this happens in the middle of a + // pixel format change, the server will encode the cursor with + // the old format, but the client will try to decode it + // according to the new format. This will lead to a + // crash. Therefore, we do not allow automatic format change for + // old servers. + return; + } + + // Select best color level + newFullColour = (kbitsPerSecond > 256); + if (newFullColour != fullColour) { + vlog.info(_("Throughput %d kbit/s - full color is now %s"), + kbitsPerSecond, + newFullColour ? _("enabled") : _("disabled")); + fullColour.setParam(newFullColour); + formatChange = true; + } +} + +// checkEncodings() sends a setEncodings message if one is needed. +void CConn::checkEncodings() +{ + if (encodingChange && writer()) { + vlog.info(_("Using %s encoding"),encodingName(currentEncoding)); + writer()->writeSetEncodings(currentEncoding, true); + encodingChange = false; + } +} + +// requestNewUpdate() requests an update from the server, having set the +// format and encoding appropriately. +void CConn::requestNewUpdate() +{ + if (formatChange) { + PixelFormat pf; + + /* Catch incorrect requestNewUpdate calls */ + assert(pendingUpdate == false); + + if (fullColour) { + pf = fullColourPF; + } else { + if (lowColourLevel == 0) + pf = PixelFormat(8,3,0,1,1,1,1,2,1,0); + else if (lowColourLevel == 1) + pf = PixelFormat(8,6,0,1,3,3,3,4,2,0); + else + pf = PixelFormat(8,8,0,0); + } + char str[256]; + pf.print(str, 256); + vlog.info(_("Using pixel format %s"),str); + desktop->setServerPF(pf); + cp.setPF(pf); + writer()->writeSetPixelFormat(pf); + } + checkEncodings(); + writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height), + !formatChange); + formatChange = false; +} |