blob: cdd87b135db4ad46047102dc0866861961634b46 [file] [log] [blame] [edit]
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright 2009-2019 Pierre Ossman for Cendio AB
* Copyright 2018 Peter Astrand 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 <network/TcpSocket.h>
#include <rfb/ComparingUpdateTracker.h>
#include <rfb/Encoder.h>
#include <rfb/KeyRemapper.h>
#include <rfb/LogWriter.h>
#include <rfb/Security.h>
#include <rfb/ServerCore.h>
#include <rfb/SMsgWriter.h>
#include <rfb/VNCServerST.h>
#include <rfb/VNCSConnectionST.h>
#include <rfb/screenTypes.h>
#include <rfb/fenceTypes.h>
#include <rfb/ledStates.h>
#define XK_LATIN1
#define XK_MISCELLANY
#define XK_XKB_KEYS
#include <rfb/keysymdef.h>
using namespace rfb;
static LogWriter vlog("VNCSConnST");
static Cursor emptyCursor(0, 0, Point(0, 0), NULL);
VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
bool reverse)
: sock(s), reverseConnection(reverse),
inProcessMessages(false),
pendingSyncFence(false), syncFence(false), fenceFlags(0),
fenceDataLen(0), fenceData(NULL), congestionTimer(this),
losslessTimer(this), server(server_),
updateRenderedCursor(false), removeRenderedCursor(false),
continuousUpdates(false), encodeManager(this), idleTimer(this),
pointerEventTime(0), clientHasCursor(false),
authFailureTimer(this)
{
setStreams(&sock->inStream(), &sock->outStream());
peerEndpoint.buf = sock->getPeerEndpoint();
// Configure the socket
setSocketTimeouts();
// Kick off the idle timer
if (rfb::Server::idleTimeout) {
// minimum of 15 seconds while authenticating
if (rfb::Server::idleTimeout < 15)
idleTimer.start(secsToMillis(15));
else
idleTimer.start(secsToMillis(rfb::Server::idleTimeout));
}
}
VNCSConnectionST::~VNCSConnectionST()
{
// If we reach here then VNCServerST is deleting us!
if (closeReason.buf)
vlog.info("closing %s: %s", peerEndpoint.buf, closeReason.buf);
// Release any keys the client still had pressed
while (!pressedKeys.empty()) {
rdr::U32 keysym, keycode;
keysym = pressedKeys.begin()->second;
keycode = pressedKeys.begin()->first;
pressedKeys.erase(pressedKeys.begin());
vlog.debug("Releasing key 0x%x / 0x%x on client disconnect",
keysym, keycode);
server->keyEvent(keysym, keycode, false);
}
delete [] fenceData;
}
// SConnection methods
bool VNCSConnectionST::accessCheck(AccessRights ar) const
{
// Reverse connections are user initiated, so they are implicitly
// allowed to bypass the query
if (reverseConnection)
ar &= ~AccessNoQuery;
return SConnection::accessCheck(ar);
}
void VNCSConnectionST::close(const char* reason)
{
// Log the reason for the close
if (!closeReason.buf)
closeReason.buf = strDup(reason);
else
vlog.debug("second close: %s (%s)", peerEndpoint.buf, reason);
// Just shutdown the socket and mark our state as closing. Eventually the
// calling code will call VNCServerST's removeSocket() method causing us to
// be deleted.
sock->shutdown();
SConnection::close(reason);
}
// Methods called from VNCServerST
bool VNCSConnectionST::init()
{
try {
initialiseProtocol();
} catch (rdr::Exception& e) {
close(e.str());
return false;
}
return true;
}
void VNCSConnectionST::processMessages()
{
if (state() == RFBSTATE_CLOSING) return;
try {
// - Now set appropriate socket timeouts and process data
setSocketTimeouts();
inProcessMessages = true;
// Get the underlying TCP layer to build large packets if we send
// multiple small responses.
sock->cork(true);
while (getInStream()->checkNoWait(1)) {
// Silently drop any data if we are currently delaying an
// authentication failure response as otherwise we would close
// the connection on unexpected data, and an attacker could use
// that to detect our delayed state.
if (state() == RFBSTATE_SECURITY_FAILURE) {
getInStream()->skip(1);
continue;
}
if (pendingSyncFence) {
syncFence = true;
pendingSyncFence = false;
}
processMsg();
if (syncFence) {
writer()->writeFence(fenceFlags, fenceDataLen, fenceData);
syncFence = false;
}
}
// Flush out everything in case we go idle after this.
sock->cork(false);
inProcessMessages = false;
// If there were anything requiring an update, try to send it here.
// We wait until now with this to aggregate responses and to give
// higher priority to user actions such as keyboard and pointer events.
writeFramebufferUpdate();
} catch (rdr::EndOfStream&) {
close("Clean disconnection");
} catch (rdr::Exception &e) {
close(e.str());
}
}
void VNCSConnectionST::flushSocket()
{
if (state() == RFBSTATE_CLOSING) return;
try {
setSocketTimeouts();
sock->outStream().flush();
// Flushing the socket might release an update that was previously
// delayed because of congestion.
if (sock->outStream().bufferUsage() == 0)
writeFramebufferUpdate();
} catch (rdr::Exception &e) {
close(e.str());
}
}
void VNCSConnectionST::pixelBufferChange()
{
try {
if (!authenticated()) return;
if (client.width() && client.height() &&
(server->getPixelBuffer()->width() != client.width() ||
server->getPixelBuffer()->height() != client.height()))
{
// We need to clip the next update to the new size, but also add any
// extra bits if it's bigger. If we wanted to do this exactly, something
// like the code below would do it, but at the moment we just update the
// entire new size. However, we do need to clip the damagedCursorRegion
// because that might be added to updates in writeFramebufferUpdate().
//updates.intersect(server->pb->getRect());
//
//if (server->pb->width() > client.width())
// updates.add_changed(Rect(client.width(), 0, server->pb->width(),
// server->pb->height()));
//if (server->pb->height() > client.height())
// updates.add_changed(Rect(0, client.height(), client.width(),
// server->pb->height()));
damagedCursorRegion.assign_intersect(server->getPixelBuffer()->getRect());
client.setDimensions(server->getPixelBuffer()->width(),
server->getPixelBuffer()->height(),
server->getScreenLayout());
if (state() == RFBSTATE_NORMAL) {
if (!client.supportsDesktopSize()) {
close("Client does not support desktop resize");
return;
}
writer()->writeDesktopSize(reasonServer);
}
// Drop any lossy tracking that is now outside the framebuffer
encodeManager.pruneLosslessRefresh(Region(server->getPixelBuffer()->getRect()));
}
// Just update the whole screen at the moment because we're too lazy to
// work out what's actually changed.
updates.clear();
updates.add_changed(server->getPixelBuffer()->getRect());
writeFramebufferUpdate();
} catch(rdr::Exception &e) {
close(e.str());
}
}
void VNCSConnectionST::writeFramebufferUpdateOrClose()
{
try {
writeFramebufferUpdate();
} catch(rdr::Exception &e) {
close(e.str());
}
}
void VNCSConnectionST::screenLayoutChangeOrClose(rdr::U16 reason)
{
try {
screenLayoutChange(reason);
writeFramebufferUpdate();
} catch(rdr::Exception &e) {
close(e.str());
}
}
void VNCSConnectionST::bellOrClose()
{
try {
if (state() == RFBSTATE_NORMAL) writer()->writeBell();
} catch(rdr::Exception& e) {
close(e.str());
}
}
void VNCSConnectionST::setDesktopNameOrClose(const char *name)
{
try {
setDesktopName(name);
writeFramebufferUpdate();
} catch(rdr::Exception& e) {
close(e.str());
}
}
void VNCSConnectionST::setCursorOrClose()
{
try {
setCursor();
writeFramebufferUpdate();
} catch(rdr::Exception& e) {
close(e.str());
}
}
void VNCSConnectionST::setLEDStateOrClose(unsigned int state)
{
try {
setLEDState(state);
writeFramebufferUpdate();
} catch(rdr::Exception& e) {
close(e.str());
}
}
void VNCSConnectionST::requestClipboardOrClose()
{
try {
if (!accessCheck(AccessCutText)) return;
if (!rfb::Server::acceptCutText) return;
if (state() != RFBSTATE_NORMAL) return;
requestClipboard();
} catch(rdr::Exception& e) {
close(e.str());
}
}
void VNCSConnectionST::announceClipboardOrClose(bool available)
{
try {
if (!accessCheck(AccessCutText)) return;
if (!rfb::Server::sendCutText) return;
if (state() != RFBSTATE_NORMAL) return;
announceClipboard(available);
} catch(rdr::Exception& e) {
close(e.str());
}
}
void VNCSConnectionST::sendClipboardDataOrClose(const char* data)
{
try {
if (!accessCheck(AccessCutText)) return;
if (!rfb::Server::sendCutText) return;
if (state() != RFBSTATE_NORMAL) return;
sendClipboardData(data);
} catch(rdr::Exception& e) {
close(e.str());
}
}
bool VNCSConnectionST::getComparerState()
{
// We interpret a low compression level as an indication that the client
// wants to prioritise CPU usage over bandwidth, and hence disable the
// comparing update tracker.
return (client.compressLevel == -1) || (client.compressLevel > 1);
}
// renderedCursorChange() is called whenever the server-side rendered cursor
// changes shape or position. It ensures that the next update will clean up
// the old rendered cursor and if necessary draw the new rendered cursor.
void VNCSConnectionST::renderedCursorChange()
{
if (state() != RFBSTATE_NORMAL) return;
// Are we switching between client-side and server-side cursor?
if (clientHasCursor == needRenderedCursor())
setCursorOrClose();
bool hasRenderedCursor = !damagedCursorRegion.is_empty();
if (hasRenderedCursor)
removeRenderedCursor = true;
if (needRenderedCursor()) {
updateRenderedCursor = true;
writeFramebufferUpdateOrClose();
}
}
// needRenderedCursor() returns true if this client needs the server-side
// rendered cursor. This may be because it does not support local cursor or
// because the current cursor position has not been set by this client.
// Unfortunately we can't know for sure when the current cursor position has
// been set by this client. We guess that this is the case when the current
// cursor position is the same as the last pointer event from this client, or
// if it is a very short time since this client's last pointer event (up to a
// second). [ Ideally we should do finer-grained timing here and make the time
// configurable, but I don't think it's that important. ]
bool VNCSConnectionST::needRenderedCursor()
{
if (state() != RFBSTATE_NORMAL)
return false;
if (!client.supportsLocalCursor())
return true;
if (!server->getCursorPos().equals(pointerEventPos) &&
(time(0) - pointerEventTime) > 0)
return true;
return false;
}
void VNCSConnectionST::approveConnectionOrClose(bool accept,
const char* reason)
{
try {
approveConnection(accept, reason);
} catch (rdr::Exception& e) {
close(e.str());
}
}
// -=- Callbacks from SConnection
void VNCSConnectionST::authSuccess()
{
if (rfb::Server::idleTimeout)
idleTimer.start(secsToMillis(rfb::Server::idleTimeout));
// - Set the connection parameters appropriately
client.setDimensions(server->getPixelBuffer()->width(),
server->getPixelBuffer()->height(),
server->getScreenLayout());
client.setName(server->getName());
client.setLEDState(server->getLEDState());
// - Set the default pixel format
client.setPF(server->getPixelBuffer()->getPF());
char buffer[256];
client.pf().print(buffer, 256);
vlog.info("Server default pixel format %s", buffer);
// - Mark the entire display as "dirty"
updates.add_changed(server->getPixelBuffer()->getRect());
}
void VNCSConnectionST::authFailure(const char* reason)
{
// Introduce a slight delay of the authentication failure response
// to make it difficult to brute force a password
authFailureMsg.replaceBuf(strDup(reason));
authFailureTimer.start(100);
}
void VNCSConnectionST::queryConnection(const char* userName)
{
server->queryConnection(this, userName);
}
void VNCSConnectionST::clientInit(bool shared)
{
if (rfb::Server::idleTimeout)
idleTimer.start(secsToMillis(rfb::Server::idleTimeout));
if (rfb::Server::alwaysShared || reverseConnection) shared = true;
if (!accessCheck(AccessNonShared)) shared = true;
if (rfb::Server::neverShared) shared = false;
SConnection::clientInit(shared);
server->clientReady(this, shared);
}
void VNCSConnectionST::setPixelFormat(const PixelFormat& pf)
{
SConnection::setPixelFormat(pf);
char buffer[256];
pf.print(buffer, 256);
vlog.info("Client pixel format %s", buffer);
setCursor();
}
void VNCSConnectionST::pointerEvent(const Point& pos, int buttonMask)
{
if (rfb::Server::idleTimeout)
idleTimer.start(secsToMillis(rfb::Server::idleTimeout));
pointerEventTime = time(0);
if (!accessCheck(AccessPtrEvents)) return;
if (!rfb::Server::acceptPointerEvents) return;
pointerEventPos = pos;
server->pointerEvent(this, pointerEventPos, buttonMask);
}
class VNCSConnectionSTShiftPresser {
public:
VNCSConnectionSTShiftPresser(VNCServerST* server_)
: server(server_), pressed(false) {}
~VNCSConnectionSTShiftPresser() {
if (pressed) {
vlog.debug("Releasing fake Shift_L");
server->keyEvent(XK_Shift_L, 0, false);
}
}
void press() {
vlog.debug("Pressing fake Shift_L");
server->keyEvent(XK_Shift_L, 0, true);
pressed = true;
}
VNCServerST* server;
bool pressed;
};
// keyEvent() - record in the pressedKeys which keys were pressed. Allow
// multiple down events (for autorepeat), but only allow a single up event.
void VNCSConnectionST::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) {
rdr::U32 lookup;
if (rfb::Server::idleTimeout)
idleTimer.start(secsToMillis(rfb::Server::idleTimeout));
if (!accessCheck(AccessKeyEvents)) return;
if (!rfb::Server::acceptKeyEvents) return;
if (down)
vlog.debug("Key pressed: 0x%x / 0x%x", keysym, keycode);
else
vlog.debug("Key released: 0x%x / 0x%x", keysym, keycode);
// Avoid lock keys if we don't know the server state
if ((server->getLEDState() == ledUnknown) &&
((keysym == XK_Caps_Lock) ||
(keysym == XK_Num_Lock) ||
(keysym == XK_Scroll_Lock))) {
vlog.debug("Ignoring lock key (e.g. caps lock)");
return;
}
// Lock key heuristics
// (only for clients that do not support the LED state extension)
if (!client.supportsLEDState()) {
// Always ignore ScrollLock as we don't have a heuristic
// for that
if (keysym == XK_Scroll_Lock) {
vlog.debug("Ignoring lock key (e.g. caps lock)");
return;
}
if (down && (server->getLEDState() != ledUnknown)) {
// CapsLock synchronisation heuristic
// (this assumes standard interaction between CapsLock the Shift
// keys and normal characters)
if (((keysym >= XK_A) && (keysym <= XK_Z)) ||
((keysym >= XK_a) && (keysym <= XK_z))) {
bool uppercase, shift, lock;
uppercase = (keysym >= XK_A) && (keysym <= XK_Z);
shift = isShiftPressed();
lock = server->getLEDState() & ledCapsLock;
if (lock == (uppercase == shift)) {
vlog.debug("Inserting fake CapsLock to get in sync with client");
server->keyEvent(XK_Caps_Lock, 0, true);
server->keyEvent(XK_Caps_Lock, 0, false);
}
}
// NumLock synchronisation heuristic
// (this is more cautious because of the differences between Unix,
// Windows and macOS)
if (((keysym >= XK_KP_Home) && (keysym <= XK_KP_Delete)) ||
((keysym >= XK_KP_0) && (keysym <= XK_KP_9)) ||
(keysym == XK_KP_Separator) || (keysym == XK_KP_Decimal)) {
bool number, shift, lock;
number = ((keysym >= XK_KP_0) && (keysym <= XK_KP_9)) ||
(keysym == XK_KP_Separator) || (keysym == XK_KP_Decimal);
shift = isShiftPressed();
lock = server->getLEDState() & ledNumLock;
if (shift) {
// We don't know the appropriate NumLock state for when Shift
// is pressed as it could be one of:
//
// a) A Unix client where Shift negates NumLock
//
// b) A Windows client where Shift only cancels NumLock
//
// c) A macOS client where Shift doesn't have any effect
//
} else if (lock == (number == shift)) {
vlog.debug("Inserting fake NumLock to get in sync with client");
server->keyEvent(XK_Num_Lock, 0, true);
server->keyEvent(XK_Num_Lock, 0, false);
}
}
}
}
// Turn ISO_Left_Tab into shifted Tab.
VNCSConnectionSTShiftPresser shiftPresser(server);
if (keysym == XK_ISO_Left_Tab) {
if (!isShiftPressed())
shiftPresser.press();
keysym = XK_Tab;
}
// We need to be able to track keys, so generate a fake index when we
// aren't given a keycode
if (keycode == 0)
lookup = 0x80000000 | keysym;
else
lookup = keycode;
// We force the same keysym for an already down key for the
// sake of sanity
if (pressedKeys.find(lookup) != pressedKeys.end())
keysym = pressedKeys[lookup];
if (down) {
pressedKeys[lookup] = keysym;
} else {
if (!pressedKeys.erase(lookup))
return;
}
server->keyEvent(keysym, keycode, down);
}
void VNCSConnectionST::framebufferUpdateRequest(const Rect& r,bool incremental)
{
Rect safeRect;
if (!accessCheck(AccessView)) return;
SConnection::framebufferUpdateRequest(r, incremental);
// Check that the client isn't sending crappy requests
if (!r.enclosed_by(Rect(0, 0, client.width(), client.height()))) {
vlog.error("FramebufferUpdateRequest %dx%d at %d,%d exceeds framebuffer %dx%d",
r.width(), r.height(), r.tl.x, r.tl.y,
client.width(), client.height());
safeRect = r.intersect(Rect(0, 0, client.width(), client.height()));
} else {
safeRect = r;
}
// Just update the requested region.
// Framebuffer update will be sent a bit later, see processMessages().
Region reqRgn(r);
if (!incremental || !continuousUpdates)
requested.assign_union(reqRgn);
if (!incremental) {
// Non-incremental update - treat as if area requested has changed
updates.add_changed(reqRgn);
// And send the screen layout to the client (which, unlike the
// framebuffer dimensions, the client doesn't get during init)
if (client.supportsEncoding(pseudoEncodingExtendedDesktopSize))
writer()->writeDesktopSize(reasonServer);
// We do not send a DesktopSize since it only contains the
// framebuffer size (which the client already should know) and
// because some clients don't handle extra DesktopSize events
// very well.
}
}
void VNCSConnectionST::setDesktopSize(int fb_width, int fb_height,
const ScreenSet& layout)
{
unsigned int result;
if (!accessCheck(AccessSetDesktopSize)) return;
if (!rfb::Server::acceptSetDesktopSize) return;
result = server->setDesktopSize(this, fb_width, fb_height, layout);
writer()->writeDesktopSize(reasonClient, result);
}
void VNCSConnectionST::fence(rdr::U32 flags, unsigned len, const char data[])
{
rdr::U8 type;
if (flags & fenceFlagRequest) {
if (flags & fenceFlagSyncNext) {
pendingSyncFence = true;
fenceFlags = flags & (fenceFlagBlockBefore | fenceFlagBlockAfter | fenceFlagSyncNext);
fenceDataLen = len;
delete [] fenceData;
fenceData = NULL;
if (len > 0) {
fenceData = new char[len];
memcpy(fenceData, data, len);
}
return;
}
// We handle everything synchronously so we trivially honor these modes
flags = flags & (fenceFlagBlockBefore | fenceFlagBlockAfter);
writer()->writeFence(flags, len, data);
return;
}
if (len < 1)
vlog.error("Fence response of unexpected size received");
type = data[0];
switch (type) {
case 0:
// Initial dummy fence;
break;
case 1:
congestion.gotPong();
break;
default:
vlog.error("Fence response of unexpected type received");
}
}
void VNCSConnectionST::enableContinuousUpdates(bool enable,
int x, int y, int w, int h)
{
Rect rect;
if (!client.supportsFence() || !client.supportsContinuousUpdates())
throw Exception("Client tried to enable continuous updates when not allowed");
continuousUpdates = enable;
rect.setXYWH(x, y, w, h);
cuRegion.reset(rect);
if (enable) {
requested.clear();
} else {
writer()->writeEndOfContinuousUpdates();
}
}
void VNCSConnectionST::handleClipboardRequest()
{
if (!accessCheck(AccessCutText)) return;
server->handleClipboardRequest(this);
}
void VNCSConnectionST::handleClipboardAnnounce(bool available)
{
if (!accessCheck(AccessCutText)) return;
if (!rfb::Server::acceptCutText) return;
server->handleClipboardAnnounce(this, available);
}
void VNCSConnectionST::handleClipboardData(const char* data)
{
if (!accessCheck(AccessCutText)) return;
if (!rfb::Server::acceptCutText) return;
server->handleClipboardData(this, data);
}
// supportsLocalCursor() is called whenever the status of
// client.supportsLocalCursor() has changed. If the client does now support local
// cursor, we make sure that the old server-side rendered cursor is cleaned up
// and the cursor is sent to the client.
void VNCSConnectionST::supportsLocalCursor()
{
bool hasRenderedCursor = !damagedCursorRegion.is_empty();
if (hasRenderedCursor && !needRenderedCursor())
removeRenderedCursor = true;
setCursor();
}
void VNCSConnectionST::supportsFence()
{
char type = 0;
writer()->writeFence(fenceFlagRequest, sizeof(type), &type);
}
void VNCSConnectionST::supportsContinuousUpdates()
{
// We refuse to use continuous updates if we cannot monitor the buffer
// usage using fences.
if (!client.supportsFence())
return;
writer()->writeEndOfContinuousUpdates();
}
void VNCSConnectionST::supportsLEDState()
{
if (client.ledState() == ledUnknown)
return;
writer()->writeLEDState();
}
bool VNCSConnectionST::handleTimeout(Timer* t)
{
try {
if ((t == &congestionTimer) ||
(t == &losslessTimer))
writeFramebufferUpdate();
else if (t == &authFailureTimer)
SConnection::authFailure(authFailureMsg.buf);
} catch (rdr::Exception& e) {
close(e.str());
}
if (t == &idleTimer)
close("Idle timeout");
return false;
}
bool VNCSConnectionST::isShiftPressed()
{
std::map<rdr::U32, rdr::U32>::const_iterator iter;
for (iter = pressedKeys.begin(); iter != pressedKeys.end(); ++iter) {
if (iter->second == XK_Shift_L)
return true;
if (iter->second == XK_Shift_R)
return true;
}
return false;
}
void VNCSConnectionST::writeRTTPing()
{
char type;
if (!client.supportsFence())
return;
congestion.updatePosition(sock->outStream().length());
// We need to make sure any old update are already processed by the
// time we get the response back. This allows us to reliably throttle
// back on client overload, as well as network overload.
type = 1;
writer()->writeFence(fenceFlagRequest | fenceFlagBlockBefore,
sizeof(type), &type);
congestion.sentPing();
}
bool VNCSConnectionST::isCongested()
{
int eta;
congestionTimer.stop();
// Stuff still waiting in the send buffer?
sock->outStream().flush();
congestion.debugTrace("congestion-trace.csv", sock->getFd());
if (sock->outStream().bufferUsage() > 0)
return true;
if (!client.supportsFence())
return false;
congestion.updatePosition(sock->outStream().length());
if (!congestion.isCongested())
return false;
eta = congestion.getUncongestedETA();
if (eta >= 0)
congestionTimer.start(eta);
return true;
}
void VNCSConnectionST::writeFramebufferUpdate()
{
congestion.updatePosition(sock->outStream().length());
// We're in the middle of processing a command that's supposed to be
// synchronised. Allowing an update to slip out right now might violate
// that synchronisation.
if (syncFence)
return;
// We try to aggregate responses, so don't send out anything whilst we
// still have incoming messages. processMessages() will give us another
// chance to run once things are idle.
if (inProcessMessages)
return;
if (state() != RFBSTATE_NORMAL)
return;
if (requested.is_empty() && !continuousUpdates)
return;
// Check that we actually have some space on the link and retry in a
// bit if things are congested.
if (isCongested())
return;
// Updates often consists of many small writes, and in continuous
// mode, we will also have small fence messages around the update. We
// need to aggregate these in order to not clog up TCP's congestion
// window.
sock->cork(true);
// First take care of any updates that cannot contain framebuffer data
// changes.
writeNoDataUpdate();
// Then real data (if possible)
writeDataUpdate();
sock->cork(false);
congestion.updatePosition(sock->outStream().length());
}
void VNCSConnectionST::writeNoDataUpdate()
{
if (!writer()->needNoDataUpdate())
return;
writer()->writeNoDataUpdate();
// Make sure no data update is sent until next request
requested.clear();
}
void VNCSConnectionST::writeDataUpdate()
{
Region req;
UpdateInfo ui;
bool needNewUpdateInfo;
const RenderedCursor *cursor;
// See what the client has requested (if anything)
if (continuousUpdates)
req = cuRegion.union_(requested);
else
req = requested;
if (req.is_empty())
return;
// Get the lists of updates. Prior to exporting the data to the `ui' object,
// getUpdateInfo() will normalize the `updates' object such way that its
// `changed' and `copied' regions would not intersect.
updates.getUpdateInfo(&ui, req);
needNewUpdateInfo = false;
// If the previous position of the rendered cursor overlaps the source of the
// copy, then when the copy happens the corresponding rectangle in the
// destination will be wrong, so add it to the changed region.
if (!ui.copied.is_empty() && !damagedCursorRegion.is_empty()) {
Region bogusCopiedCursor;
bogusCopiedCursor = damagedCursorRegion;
bogusCopiedCursor.translate(ui.copy_delta);
bogusCopiedCursor.assign_intersect(server->getPixelBuffer()->getRect());
if (!ui.copied.intersect(bogusCopiedCursor).is_empty()) {
updates.add_changed(bogusCopiedCursor);
needNewUpdateInfo = true;
}
}
// If we need to remove the old rendered cursor, just add the region to
// the changed region.
if (removeRenderedCursor) {
updates.add_changed(damagedCursorRegion);
needNewUpdateInfo = true;
damagedCursorRegion.clear();
removeRenderedCursor = false;
}
// If we need a full cursor update then make sure its entire region
// is marked as changed.
if (updateRenderedCursor) {
updates.add_changed(server->getRenderedCursor()->getEffectiveRect());
needNewUpdateInfo = true;
updateRenderedCursor = false;
}
// The `updates' object could change, make sure we have valid update info.
if (needNewUpdateInfo)
updates.getUpdateInfo(&ui, req);
// If there are queued updates then we cannot safely send an update
// without risking a partially updated screen
if (!server->getPendingRegion().is_empty()) {
req.clear();
ui.changed.clear();
ui.copied.clear();
}
// Does the client need a server-side rendered cursor?
cursor = NULL;
if (needRenderedCursor()) {
Rect renderedCursorRect;
cursor = server->getRenderedCursor();
renderedCursorRect = cursor->getEffectiveRect();
// Check that we don't try to copy over the cursor area, and
// if that happens we need to treat it as changed so that we can
// re-render it
if (!ui.copied.intersect(renderedCursorRect).is_empty()) {
ui.changed.assign_union(ui.copied.intersect(renderedCursorRect));
ui.copied.assign_subtract(renderedCursorRect);
}
// Track where we've rendered the cursor
damagedCursorRegion.assign_union(ui.changed.intersect(renderedCursorRect));
}
// If we don't have a normal update, then try a lossless refresh
if (ui.is_empty() && !writer()->needFakeUpdate()) {
writeLosslessRefresh();
return;
}
// We have something to send, so let's get to it
writeRTTPing();
encodeManager.writeUpdate(ui, server->getPixelBuffer(), cursor);
writeRTTPing();
// The request might be for just part of the screen, so we cannot
// just clear the entire update tracker.
updates.subtract(req);
requested.clear();
}
void VNCSConnectionST::writeLosslessRefresh()
{
Region req, pending;
const RenderedCursor *cursor;
int nextRefresh, nextUpdate;
size_t bandwidth, maxUpdateSize;
if (continuousUpdates)
req = cuRegion.union_(requested);
else
req = requested;
// If there are queued updates then we could not safely send an
// update without risking a partially updated screen, however we
// might still be able to send a lossless refresh
pending = server->getPendingRegion();
if (!pending.is_empty()) {
UpdateInfo ui;
// Don't touch the updates pending in the server core
req.assign_subtract(pending);
// Or any updates pending just for this connection
updates.getUpdateInfo(&ui, req);
req.assign_subtract(ui.changed);
req.assign_subtract(ui.copied);
}
// Any lossy area we can refresh?
if (!encodeManager.needsLosslessRefresh(req))
return;
// Right away? Or later?
nextRefresh = encodeManager.getNextLosslessRefresh(req);
if (nextRefresh > 0) {
losslessTimer.start(nextRefresh);
return;
}
// Prepare the cursor in case it overlaps with a region getting
// refreshed
cursor = NULL;
if (needRenderedCursor())
cursor = server->getRenderedCursor();
// FIXME: If continuous updates aren't used then the client might
// be slower than frameRate in its requests and we could
// afford a larger update size
nextUpdate = server->msToNextUpdate();
// Don't bother if we're about to send a real update
if (nextUpdate == 0)
return;
// FIXME: Bandwidth estimation without congestion control
bandwidth = congestion.getBandwidth();
// FIXME: Hard coded value for maximum CPU throughput
if (bandwidth > 5000000)
bandwidth = 5000000;
maxUpdateSize = bandwidth * nextUpdate / 1000;
writeRTTPing();
encodeManager.writeLosslessRefresh(req, server->getPixelBuffer(),
cursor, maxUpdateSize);
writeRTTPing();
requested.clear();
}
void VNCSConnectionST::screenLayoutChange(rdr::U16 reason)
{
if (!authenticated())
return;
client.setDimensions(client.width(), client.height(),
server->getScreenLayout());
if (state() != RFBSTATE_NORMAL)
return;
writer()->writeDesktopSize(reason);
}
// setCursor() is called whenever the cursor has changed shape or pixel format.
// If the client supports local cursor then it will arrange for the cursor to
// be sent to the client.
void VNCSConnectionST::setCursor()
{
if (state() != RFBSTATE_NORMAL)
return;
// We need to blank out the client's cursor or there will be two
if (needRenderedCursor()) {
client.setCursor(emptyCursor);
clientHasCursor = false;
} else {
client.setCursor(*server->getCursor());
clientHasCursor = true;
}
if (client.supportsLocalCursor())
writer()->writeCursor();
}
void VNCSConnectionST::setDesktopName(const char *name)
{
client.setName(name);
if (state() != RFBSTATE_NORMAL)
return;
if (client.supportsEncoding(pseudoEncodingDesktopName))
writer()->writeSetDesktopName();
}
void VNCSConnectionST::setLEDState(unsigned int ledstate)
{
if (state() != RFBSTATE_NORMAL)
return;
client.setLEDState(ledstate);
if (client.supportsLEDState())
writer()->writeLEDState();
}
void VNCSConnectionST::setSocketTimeouts()
{
int timeoutms = rfb::Server::clientWaitTimeMillis;
if (timeoutms == 0)
timeoutms = -1;
sock->inStream().setTimeout(timeoutms);
sock->outStream().setTimeout(timeoutms);
}