* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | ||||
* USA. | * USA. | ||||
*/ | */ | ||||
#include <assert.h> | |||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <rfb/CMsgReader.h> | #include <rfb/CMsgReader.h> | ||||
#include <rfb/CMsgWriter.h> | #include <rfb/CMsgWriter.h> | ||||
#include <rfb/CSecurity.h> | #include <rfb/CSecurity.h> | ||||
#include <rfb/Decoder.h> | |||||
#include <rfb/Security.h> | #include <rfb/Security.h> | ||||
#include <rfb/SecurityClient.h> | #include <rfb/SecurityClient.h> | ||||
#include <rfb/CConnection.h> | #include <rfb/CConnection.h> | ||||
static LogWriter vlog("CConnection"); | static LogWriter vlog("CConnection"); | ||||
CConnection::CConnection() | CConnection::CConnection() | ||||
: csecurity(0), is(0), os(0), reader_(0), writer_(0), | |||||
: csecurity(0), | |||||
supportsLocalCursor(false), supportsDesktopResize(false), | |||||
supportsLEDState(false), | |||||
is(0), os(0), reader_(0), writer_(0), | |||||
shared(false), | shared(false), | ||||
state_(RFBSTATE_UNINITIALISED), useProtocol3_3(false), | state_(RFBSTATE_UNINITIALISED), useProtocol3_3(false), | ||||
pendingPFChange(false), preferredEncoding(encodingTight), | |||||
compressLevel(2), qualityLevel(-1), | |||||
formatChange(false), encodingChange(false), | |||||
firstUpdate(true), pendingUpdate(false), continuousUpdates(false), | |||||
forceNonincremental(true), | |||||
framebuffer(NULL), decoder(this) | framebuffer(NULL), decoder(this) | ||||
{ | { | ||||
} | } | ||||
{ | { | ||||
decoder.flush(); | decoder.flush(); | ||||
if (fb) { | |||||
assert(fb->width() == server.width()); | |||||
assert(fb->height() == server.height()); | |||||
} | |||||
if ((framebuffer != NULL) && (fb != NULL)) { | if ((framebuffer != NULL) && (fb != NULL)) { | ||||
Rect rect; | Rect rect; | ||||
void CConnection::processVersionMsg() | void CConnection::processVersionMsg() | ||||
{ | { | ||||
char verStr[13]; | |||||
int majorVersion; | |||||
int minorVersion; | |||||
vlog.debug("reading protocol version"); | vlog.debug("reading protocol version"); | ||||
bool done; | |||||
if (!cp.readVersion(is, &done)) { | |||||
if (!is->checkNoWait(12)) | |||||
return; | |||||
is->readBytes(verStr, 12); | |||||
verStr[12] = '\0'; | |||||
if (sscanf(verStr, "RFB %03d.%03d\n", | |||||
&majorVersion, &minorVersion) != 2) { | |||||
state_ = RFBSTATE_INVALID; | state_ = RFBSTATE_INVALID; | ||||
throw Exception("reading version failed: not an RFB server?"); | throw Exception("reading version failed: not an RFB server?"); | ||||
} | } | ||||
if (!done) return; | |||||
server.setVersion(majorVersion, minorVersion); | |||||
vlog.info("Server supports RFB protocol version %d.%d", | vlog.info("Server supports RFB protocol version %d.%d", | ||||
cp.majorVersion, cp.minorVersion); | |||||
server.majorVersion, server.minorVersion); | |||||
// The only official RFB protocol versions are currently 3.3, 3.7 and 3.8 | // The only official RFB protocol versions are currently 3.3, 3.7 and 3.8 | ||||
if (cp.beforeVersion(3,3)) { | |||||
if (server.beforeVersion(3,3)) { | |||||
vlog.error("Server gave unsupported RFB protocol version %d.%d", | vlog.error("Server gave unsupported RFB protocol version %d.%d", | ||||
cp.majorVersion, cp.minorVersion); | |||||
server.majorVersion, server.minorVersion); | |||||
state_ = RFBSTATE_INVALID; | state_ = RFBSTATE_INVALID; | ||||
throw Exception("Server gave unsupported RFB protocol version %d.%d", | throw Exception("Server gave unsupported RFB protocol version %d.%d", | ||||
cp.majorVersion, cp.minorVersion); | |||||
} else if (useProtocol3_3 || cp.beforeVersion(3,7)) { | |||||
cp.setVersion(3,3); | |||||
} else if (cp.afterVersion(3,8)) { | |||||
cp.setVersion(3,8); | |||||
server.majorVersion, server.minorVersion); | |||||
} else if (useProtocol3_3 || server.beforeVersion(3,7)) { | |||||
server.setVersion(3,3); | |||||
} else if (server.afterVersion(3,8)) { | |||||
server.setVersion(3,8); | |||||
} | } | ||||
cp.writeVersion(os); | |||||
sprintf(verStr, "RFB %03d.%03d\n", | |||||
server.majorVersion, server.minorVersion); | |||||
os->writeBytes(verStr, 12); | |||||
os->flush(); | |||||
state_ = RFBSTATE_SECURITY_TYPES; | state_ = RFBSTATE_SECURITY_TYPES; | ||||
vlog.info("Using RFB protocol version %d.%d", | vlog.info("Using RFB protocol version %d.%d", | ||||
cp.majorVersion, cp.minorVersion); | |||||
server.majorVersion, server.minorVersion); | |||||
} | } | ||||
std::list<rdr::U8> secTypes; | std::list<rdr::U8> secTypes; | ||||
secTypes = security.GetEnabledSecTypes(); | secTypes = security.GetEnabledSecTypes(); | ||||
if (cp.isVersion(3,3)) { | |||||
if (server.isVersion(3,3)) { | |||||
// legacy 3.3 server may only offer "vnc authentication" or "none" | // legacy 3.3 server may only offer "vnc authentication" or "none" | ||||
{ | { | ||||
vlog.debug("processing security result message"); | vlog.debug("processing security result message"); | ||||
int result; | int result; | ||||
if (cp.beforeVersion(3,8) && csecurity->getType() == secTypeNone) { | |||||
if (server.beforeVersion(3,8) && csecurity->getType() == secTypeNone) { | |||||
result = secResultOK; | result = secResultOK; | ||||
} else { | } else { | ||||
if (!is->checkNoWait(1)) return; | if (!is->checkNoWait(1)) return; | ||||
throw Exception("Unknown security result from server"); | throw Exception("Unknown security result from server"); | ||||
} | } | ||||
state_ = RFBSTATE_INVALID; | state_ = RFBSTATE_INVALID; | ||||
if (cp.beforeVersion(3,8)) | |||||
if (server.beforeVersion(3,8)) | |||||
throw AuthFailureException(); | throw AuthFailureException(); | ||||
CharArray reason(is->readString()); | CharArray reason(is->readString()); | ||||
throw AuthFailureException(reason.buf); | throw AuthFailureException(reason.buf); | ||||
{ | { | ||||
state_ = RFBSTATE_INITIALISATION; | state_ = RFBSTATE_INITIALISATION; | ||||
reader_ = new CMsgReader(this, is); | reader_ = new CMsgReader(this, is); | ||||
writer_ = new CMsgWriter(&cp, os); | |||||
writer_ = new CMsgWriter(&server, os); | |||||
vlog.debug("Authentication success!"); | vlog.debug("Authentication success!"); | ||||
authSuccess(); | authSuccess(); | ||||
writer_->writeClientInit(shared); | writer_->writeClientInit(shared); | ||||
decoder.flush(); | decoder.flush(); | ||||
CMsgHandler::setDesktopSize(w,h); | CMsgHandler::setDesktopSize(w,h); | ||||
if (continuousUpdates) | |||||
writer()->writeEnableContinuousUpdates(true, 0, 0, | |||||
server.width(), | |||||
server.height()); | |||||
resizeFramebuffer(); | |||||
assert(framebuffer != NULL); | |||||
assert(framebuffer->width() == server.width()); | |||||
assert(framebuffer->height() == server.height()); | |||||
} | } | ||||
void CConnection::setExtendedDesktopSize(unsigned reason, | void CConnection::setExtendedDesktopSize(unsigned reason, | ||||
decoder.flush(); | decoder.flush(); | ||||
CMsgHandler::setExtendedDesktopSize(reason, result, w, h, layout); | CMsgHandler::setExtendedDesktopSize(reason, result, w, h, layout); | ||||
if (continuousUpdates) | |||||
writer()->writeEnableContinuousUpdates(true, 0, 0, | |||||
server.width(), | |||||
server.height()); | |||||
resizeFramebuffer(); | |||||
assert(framebuffer != NULL); | |||||
assert(framebuffer->width() == server.width()); | |||||
assert(framebuffer->height() == server.height()); | |||||
} | |||||
void CConnection::endOfContinuousUpdates() | |||||
{ | |||||
CMsgHandler::endOfContinuousUpdates(); | |||||
// We've gotten the marker for a format change, so make the pending | |||||
// one active | |||||
if (pendingPFChange) { | |||||
server.setPF(pendingPF); | |||||
pendingPFChange = false; | |||||
// We might have another change pending | |||||
if (formatChange) | |||||
requestNewUpdate(); | |||||
} | |||||
} | |||||
void CConnection::serverInit(int width, int height, | |||||
const PixelFormat& pf, | |||||
const char* name) | |||||
{ | |||||
CMsgHandler::serverInit(width, height, pf, name); | |||||
state_ = RFBSTATE_NORMAL; | |||||
vlog.debug("initialisation done"); | |||||
initDone(); | |||||
assert(framebuffer != NULL); | |||||
assert(framebuffer->width() == server.width()); | |||||
assert(framebuffer->height() == server.height()); | |||||
// We want to make sure we call SetEncodings at least once | |||||
encodingChange = true; | |||||
requestNewUpdate(); | |||||
// This initial update request is a bit of a corner case, so we need | |||||
// to help out setting the correct format here. | |||||
if (pendingPFChange) { | |||||
server.setPF(pendingPF); | |||||
pendingPFChange = false; | |||||
} | |||||
} | } | ||||
void CConnection::readAndDecodeRect(const Rect& r, int encoding, | void CConnection::readAndDecodeRect(const Rect& r, int encoding, | ||||
void CConnection::framebufferUpdateStart() | void CConnection::framebufferUpdateStart() | ||||
{ | { | ||||
CMsgHandler::framebufferUpdateStart(); | CMsgHandler::framebufferUpdateStart(); | ||||
assert(framebuffer != NULL); | |||||
// Note: This might not be true if continuous updates are supported | |||||
pendingUpdate = false; | |||||
requestNewUpdate(); | |||||
} | } | ||||
void CConnection::framebufferUpdateEnd() | void CConnection::framebufferUpdateEnd() | ||||
decoder.flush(); | decoder.flush(); | ||||
CMsgHandler::framebufferUpdateEnd(); | CMsgHandler::framebufferUpdateEnd(); | ||||
// A format change has been scheduled and we are now past the update | |||||
// with the old format. Time to active the new one. | |||||
if (pendingPFChange && !continuousUpdates) { | |||||
server.setPF(pendingPF); | |||||
pendingPFChange = false; | |||||
} | |||||
if (firstUpdate) { | |||||
if (server.supportsContinuousUpdates) { | |||||
vlog.info("Enabling continuous updates"); | |||||
continuousUpdates = true; | |||||
writer()->writeEnableContinuousUpdates(true, 0, 0, | |||||
server.width(), | |||||
server.height()); | |||||
} | |||||
firstUpdate = false; | |||||
} | |||||
} | } | ||||
void CConnection::dataRect(const Rect& r, int encoding) | void CConnection::dataRect(const Rect& r, int encoding) | ||||
{ | { | ||||
} | } | ||||
void CConnection::serverInit() | |||||
void CConnection::initDone() | |||||
{ | { | ||||
state_ = RFBSTATE_NORMAL; | |||||
vlog.debug("initialisation done"); | |||||
} | |||||
void CConnection::resizeFramebuffer() | |||||
{ | |||||
assert(false); | |||||
} | |||||
void CConnection::refreshFramebuffer() | |||||
{ | |||||
forceNonincremental = true; | |||||
// Without continuous updates we have to make sure we only have a | |||||
// single update in flight, so we'll have to wait to do the refresh | |||||
if (continuousUpdates) | |||||
requestNewUpdate(); | |||||
} | |||||
void CConnection::setPreferredEncoding(int encoding) | |||||
{ | |||||
if (preferredEncoding == encoding) | |||||
return; | |||||
preferredEncoding = encoding; | |||||
encodingChange = true; | |||||
} | |||||
int CConnection::getPreferredEncoding() | |||||
{ | |||||
return preferredEncoding; | |||||
} | |||||
void CConnection::setCompressLevel(int level) | |||||
{ | |||||
if (compressLevel == level) | |||||
return; | |||||
compressLevel = level; | |||||
encodingChange = true; | |||||
} | |||||
void CConnection::setQualityLevel(int level) | |||||
{ | |||||
if (qualityLevel == level) | |||||
return; | |||||
qualityLevel = level; | |||||
encodingChange = true; | |||||
} | |||||
void CConnection::setPF(const PixelFormat& pf) | |||||
{ | |||||
if (server.pf().equal(pf) && !formatChange) | |||||
return; | |||||
nextPF = pf; | |||||
formatChange = true; | |||||
} | } | ||||
void CConnection::fence(rdr::U32 flags, unsigned len, const char data[]) | void CConnection::fence(rdr::U32 flags, unsigned len, const char data[]) | ||||
writer()->writeFence(flags, len, data); | writer()->writeFence(flags, len, data); | ||||
} | } | ||||
// requestNewUpdate() requests an update from the server, having set the | |||||
// format and encoding appropriately. | |||||
void CConnection::requestNewUpdate() | |||||
{ | |||||
if (formatChange && !pendingPFChange) { | |||||
/* Catch incorrect requestNewUpdate calls */ | |||||
assert(!pendingUpdate || continuousUpdates); | |||||
// We have to make sure we switch the internal format at a safe | |||||
// time. For continuous updates we temporarily disable updates and | |||||
// look for a EndOfContinuousUpdates message to see when to switch. | |||||
// For classical updates we just got a new update right before this | |||||
// function was called, so we need to make sure we finish that | |||||
// update before we can switch. | |||||
pendingPFChange = true; | |||||
pendingPF = nextPF; | |||||
if (continuousUpdates) | |||||
writer()->writeEnableContinuousUpdates(false, 0, 0, 0, 0); | |||||
writer()->writeSetPixelFormat(pendingPF); | |||||
if (continuousUpdates) | |||||
writer()->writeEnableContinuousUpdates(true, 0, 0, | |||||
server.width(), | |||||
server.height()); | |||||
formatChange = false; | |||||
} | |||||
if (encodingChange) { | |||||
updateEncodings(); | |||||
encodingChange = false; | |||||
} | |||||
if (forceNonincremental || !continuousUpdates) { | |||||
pendingUpdate = true; | |||||
writer()->writeFramebufferUpdateRequest(Rect(0, 0, | |||||
server.width(), | |||||
server.height()), | |||||
!forceNonincremental); | |||||
} | |||||
forceNonincremental = false; | |||||
} | |||||
// Ask for encodings based on which decoders are supported. Assumes higher | |||||
// encoding numbers are more desirable. | |||||
void CConnection::updateEncodings() | |||||
{ | |||||
std::list<rdr::U32> encodings; | |||||
if (supportsLocalCursor) { | |||||
encodings.push_back(pseudoEncodingCursorWithAlpha); | |||||
encodings.push_back(pseudoEncodingCursor); | |||||
encodings.push_back(pseudoEncodingXCursor); | |||||
} | |||||
if (supportsDesktopResize) { | |||||
encodings.push_back(pseudoEncodingDesktopSize); | |||||
encodings.push_back(pseudoEncodingExtendedDesktopSize); | |||||
} | |||||
if (supportsLEDState) | |||||
encodings.push_back(pseudoEncodingLEDState); | |||||
encodings.push_back(pseudoEncodingDesktopName); | |||||
encodings.push_back(pseudoEncodingLastRect); | |||||
encodings.push_back(pseudoEncodingContinuousUpdates); | |||||
encodings.push_back(pseudoEncodingFence); | |||||
encodings.push_back(pseudoEncodingQEMUKeyEvent); | |||||
if (Decoder::supported(preferredEncoding)) { | |||||
encodings.push_back(preferredEncoding); | |||||
} | |||||
encodings.push_back(encodingCopyRect); | |||||
for (int i = encodingMax; i >= 0; i--) { | |||||
if ((i != preferredEncoding) && Decoder::supported(i)) | |||||
encodings.push_back(i); | |||||
} | |||||
if (compressLevel >= 0 && compressLevel <= 9) | |||||
encodings.push_back(pseudoEncodingCompressLevel0 + compressLevel); | |||||
if (qualityLevel >= 0 && qualityLevel <= 9) | |||||
encodings.push_back(pseudoEncodingQualityLevel0 + qualityLevel); | |||||
writer()->writeSetEncodings(encodings); | |||||
} |
int w, int h, | int w, int h, | ||||
const ScreenSet& layout); | const ScreenSet& layout); | ||||
virtual void endOfContinuousUpdates(); | |||||
virtual void serverInit(int width, int height, | |||||
const PixelFormat& pf, | |||||
const char* name); | |||||
virtual void readAndDecodeRect(const Rect& r, int encoding, | virtual void readAndDecodeRect(const Rect& r, int encoding, | ||||
ModifiablePixelBuffer* pb); | ModifiablePixelBuffer* pb); | ||||
// Methods to be overridden in a derived class | // Methods to be overridden in a derived class | ||||
// getIdVerifier() returns the identity verifier associated with the connection. | |||||
// Ownership of the IdentityVerifier is retained by the CConnection instance. | |||||
virtual IdentityVerifier* getIdentityVerifier() {return 0;} | |||||
// authSuccess() is called when authentication has succeeded. | // authSuccess() is called when authentication has succeeded. | ||||
virtual void authSuccess(); | virtual void authSuccess(); | ||||
// serverInit() is called when the ServerInit message is received. The | |||||
// derived class must call on to CConnection::serverInit(). | |||||
virtual void serverInit(); | |||||
// initDone() is called when the connection is fully established | |||||
// and standard messages can be sent. This is called before the | |||||
// initial FramebufferUpdateRequest giving a derived class the | |||||
// chance to modify pixel format and settings. The derived class | |||||
// must also make sure it has provided a valid framebuffer before | |||||
// returning. | |||||
virtual void initDone() = 0; | |||||
// resizeFramebuffer() is called whenever the framebuffer | |||||
// dimensions or the screen layout changes. A subclass must make | |||||
// sure the pixel buffer has been updated once this call returns. | |||||
virtual void resizeFramebuffer(); | |||||
// Other methods | // Other methods | ||||
// refreshFramebuffer() forces a complete refresh of the entire | |||||
// framebuffer | |||||
void refreshFramebuffer(); | |||||
// setPreferredEncoding()/getPreferredEncoding() adjusts which | |||||
// encoding is listed first as a hint to the server that it is the | |||||
// preferred one | |||||
void setPreferredEncoding(int encoding); | |||||
int getPreferredEncoding(); | |||||
// setCompressLevel()/setQualityLevel() controls the encoding hints | |||||
// sent to the server | |||||
void setCompressLevel(int level); | |||||
void setQualityLevel(int level); | |||||
// setPF() controls the pixel format requested from the server. | |||||
// server.pf() will automatically be adjusted once the new format | |||||
// is active. | |||||
void setPF(const PixelFormat& pf); | |||||
CMsgReader* reader() { return reader_; } | CMsgReader* reader() { return reader_; } | ||||
CMsgWriter* writer() { return writer_; } | CMsgWriter* writer() { return writer_; } | ||||
ModifiablePixelBuffer* getFramebuffer() { return framebuffer; } | ModifiablePixelBuffer* getFramebuffer() { return framebuffer; } | ||||
protected: | |||||
// Optional capabilities that a subclass is expected to set to true | |||||
// if supported | |||||
bool supportsLocalCursor; | |||||
bool supportsDesktopResize; | |||||
bool supportsLEDState; | |||||
private: | private: | ||||
// This is a default implementation of fences that automatically | // This is a default implementation of fences that automatically | ||||
// responds to requests, stating no support for synchronisation. | // responds to requests, stating no support for synchronisation. | ||||
void throwConnFailedException(); | void throwConnFailedException(); | ||||
void securityCompleted(); | void securityCompleted(); | ||||
void requestNewUpdate(); | |||||
void updateEncodings(); | |||||
rdr::InStream* is; | rdr::InStream* is; | ||||
rdr::OutStream* os; | rdr::OutStream* os; | ||||
CMsgReader* reader_; | CMsgReader* reader_; | ||||
bool useProtocol3_3; | bool useProtocol3_3; | ||||
bool pendingPFChange; | |||||
rfb::PixelFormat pendingPF; | |||||
int preferredEncoding; | |||||
int compressLevel; | |||||
int qualityLevel; | |||||
bool formatChange; | |||||
rfb::PixelFormat nextPF; | |||||
bool encodingChange; | |||||
bool firstUpdate; | |||||
bool pendingUpdate; | |||||
bool continuousUpdates; | |||||
bool forceNonincremental; | |||||
ModifiablePixelBuffer* framebuffer; | ModifiablePixelBuffer* framebuffer; | ||||
DecodeManager decoder; | DecodeManager decoder; | ||||
}; | }; |
CSecurityStack.cxx | CSecurityStack.cxx | ||||
CSecurityVeNCrypt.cxx | CSecurityVeNCrypt.cxx | ||||
CSecurityVncAuth.cxx | CSecurityVncAuth.cxx | ||||
ClientParams.cxx | |||||
ComparingUpdateTracker.cxx | ComparingUpdateTracker.cxx | ||||
Configuration.cxx | Configuration.cxx | ||||
ConnParams.cxx | |||||
CopyRectDecoder.cxx | CopyRectDecoder.cxx | ||||
Cursor.cxx | Cursor.cxx | ||||
DecodeManager.cxx | DecodeManager.cxx | ||||
SMsgReader.cxx | SMsgReader.cxx | ||||
SMsgWriter.cxx | SMsgWriter.cxx | ||||
ServerCore.cxx | ServerCore.cxx | ||||
ServerParams.cxx | |||||
Security.cxx | Security.cxx | ||||
SecurityServer.cxx | SecurityServer.cxx | ||||
SecurityClient.cxx | SecurityClient.cxx |
void CMsgHandler::setDesktopSize(int width, int height) | void CMsgHandler::setDesktopSize(int width, int height) | ||||
{ | { | ||||
cp.width = width; | |||||
cp.height = height; | |||||
server.setDimensions(width, height); | |||||
} | } | ||||
void CMsgHandler::setExtendedDesktopSize(unsigned reason, unsigned result, | void CMsgHandler::setExtendedDesktopSize(unsigned reason, unsigned result, | ||||
int width, int height, | int width, int height, | ||||
const ScreenSet& layout) | const ScreenSet& layout) | ||||
{ | { | ||||
cp.supportsSetDesktopSize = true; | |||||
server.supportsSetDesktopSize = true; | |||||
if ((reason == reasonClient) && (result != resultSuccess)) | if ((reason == reasonClient) && (result != resultSuccess)) | ||||
return; | return; | ||||
if (!layout.validate(width, height)) | |||||
fprintf(stderr, "Server sent us an invalid screen layout\n"); | |||||
cp.width = width; | |||||
cp.height = height; | |||||
cp.screenLayout = layout; | |||||
server.setDimensions(width, height, layout); | |||||
} | } | ||||
void CMsgHandler::setPixelFormat(const PixelFormat& pf) | void CMsgHandler::setPixelFormat(const PixelFormat& pf) | ||||
{ | { | ||||
cp.setPF(pf); | |||||
server.setPF(pf); | |||||
} | } | ||||
void CMsgHandler::setName(const char* name) | void CMsgHandler::setName(const char* name) | ||||
{ | { | ||||
cp.setName(name); | |||||
server.setName(name); | |||||
} | } | ||||
void CMsgHandler::fence(rdr::U32 flags, unsigned len, const char data[]) | void CMsgHandler::fence(rdr::U32 flags, unsigned len, const char data[]) | ||||
{ | { | ||||
cp.supportsFence = true; | |||||
server.supportsFence = true; | |||||
} | } | ||||
void CMsgHandler::endOfContinuousUpdates() | void CMsgHandler::endOfContinuousUpdates() | ||||
{ | { | ||||
cp.supportsContinuousUpdates = true; | |||||
server.supportsContinuousUpdates = true; | |||||
} | } | ||||
void CMsgHandler::supportsQEMUKeyEvent() | void CMsgHandler::supportsQEMUKeyEvent() | ||||
{ | { | ||||
cp.supportsQEMUKeyEvent = true; | |||||
server.supportsQEMUKeyEvent = true; | |||||
} | |||||
void CMsgHandler::serverInit(int width, int height, | |||||
const PixelFormat& pf, | |||||
const char* name) | |||||
{ | |||||
server.setDimensions(width, height); | |||||
server.setPF(pf); | |||||
server.setName(name); | |||||
} | } | ||||
void CMsgHandler::framebufferUpdateStart() | void CMsgHandler::framebufferUpdateStart() | ||||
void CMsgHandler::setLEDState(unsigned int state) | void CMsgHandler::setLEDState(unsigned int state) | ||||
{ | { | ||||
cp.setLEDState(state); | |||||
server.setLEDState(state); | |||||
} | } |
#include <rdr/types.h> | #include <rdr/types.h> | ||||
#include <rfb/Pixel.h> | #include <rfb/Pixel.h> | ||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/ServerParams.h> | |||||
#include <rfb/Rect.h> | #include <rfb/Rect.h> | ||||
#include <rfb/ScreenSet.h> | #include <rfb/ScreenSet.h> | ||||
// The following methods are called as corresponding messages are read. A | // The following methods are called as corresponding messages are read. A | ||||
// derived class should override these methods as desired. Note that for | // derived class should override these methods as desired. Note that for | ||||
// the setDesktopSize(), setExtendedDesktopSize(), setPixelFormat() and | |||||
// setName() methods, a derived class should call on to CMsgHandler's | |||||
// methods to set the members of cp appropriately. | |||||
// the setDesktopSize(), setExtendedDesktopSize(), setPixelFormat(), | |||||
// setName() and serverInit() methods, a derived class should call on to | |||||
// CMsgHandler's methods to set the members of "server" appropriately. | |||||
virtual void setDesktopSize(int w, int h); | virtual void setDesktopSize(int w, int h); | ||||
virtual void setExtendedDesktopSize(unsigned reason, unsigned result, | virtual void setExtendedDesktopSize(unsigned reason, unsigned result, | ||||
virtual void fence(rdr::U32 flags, unsigned len, const char data[]); | virtual void fence(rdr::U32 flags, unsigned len, const char data[]); | ||||
virtual void endOfContinuousUpdates(); | virtual void endOfContinuousUpdates(); | ||||
virtual void supportsQEMUKeyEvent(); | virtual void supportsQEMUKeyEvent(); | ||||
virtual void serverInit() = 0; | |||||
virtual void serverInit(int width, int height, | |||||
const PixelFormat& pf, | |||||
const char* name) = 0; | |||||
virtual void readAndDecodeRect(const Rect& r, int encoding, | virtual void readAndDecodeRect(const Rect& r, int encoding, | ||||
ModifiablePixelBuffer* pb) = 0; | ModifiablePixelBuffer* pb) = 0; | ||||
virtual void setLEDState(unsigned int state); | virtual void setLEDState(unsigned int state); | ||||
ConnParams cp; | |||||
ServerParams server; | |||||
}; | }; | ||||
} | } | ||||
#endif | #endif |
{ | { | ||||
int width = is->readU16(); | int width = is->readU16(); | ||||
int height = is->readU16(); | int height = is->readU16(); | ||||
handler->setDesktopSize(width, height); | |||||
PixelFormat pf; | PixelFormat pf; | ||||
pf.read(is); | pf.read(is); | ||||
handler->setPixelFormat(pf); | |||||
CharArray name(is->readString()); | CharArray name(is->readString()); | ||||
handler->setName(name.buf); | |||||
handler->serverInit(); | |||||
handler->serverInit(width, height, pf, name.buf); | |||||
} | } | ||||
void CMsgReader::readMsg() | void CMsgReader::readMsg() | ||||
void CMsgReader::readRect(const Rect& r, int encoding) | void CMsgReader::readRect(const Rect& r, int encoding) | ||||
{ | { | ||||
if ((r.br.x > handler->cp.width) || (r.br.y > handler->cp.height)) { | |||||
if ((r.br.x > handler->server.width()) || | |||||
(r.br.y > handler->server.height())) { | |||||
fprintf(stderr, "Rect too big: %dx%d at %d,%d exceeds %dx%d\n", | fprintf(stderr, "Rect too big: %dx%d at %d,%d exceeds %dx%d\n", | ||||
r.width(), r.height(), r.tl.x, r.tl.y, | r.width(), r.height(), r.tl.x, r.tl.y, | ||||
handler->cp.width, handler->cp.height); | |||||
handler->server.width(), handler->server.height()); | |||||
throw Exception("Rect too big"); | throw Exception("Rect too big"); | ||||
} | } | ||||
if (width > maxCursorSize || height > maxCursorSize) | if (width > maxCursorSize || height > maxCursorSize) | ||||
throw Exception("Too big cursor"); | throw Exception("Too big cursor"); | ||||
int data_len = width * height * (handler->cp.pf().bpp/8); | |||||
int data_len = width * height * (handler->server.pf().bpp/8); | |||||
int mask_len = ((width+7)/8) * height; | int mask_len = ((width+7)/8) * height; | ||||
rdr::U8Array data(data_len); | rdr::U8Array data(data_len); | ||||
rdr::U8Array mask(mask_len); | rdr::U8Array mask(mask_len); | ||||
int byte = y * maskBytesPerRow + x / 8; | int byte = y * maskBytesPerRow + x / 8; | ||||
int bit = 7 - x % 8; | int bit = 7 - x % 8; | ||||
handler->cp.pf().rgbFromBuffer(out, in, 1); | |||||
handler->server.pf().rgbFromBuffer(out, in, 1); | |||||
if (mask.buf[byte] & (1 << bit)) | if (mask.buf[byte] & (1 << bit)) | ||||
out[3] = 255; | out[3] = 255; | ||||
else | else | ||||
out[3] = 0; | out[3] = 0; | ||||
in += handler->cp.pf().bpp/8; | |||||
in += handler->server.pf().bpp/8; | |||||
out += 4; | out += 4; | ||||
} | } | ||||
} | } | ||||
encoding = is->readS32(); | encoding = is->readS32(); | ||||
origPF = handler->cp.pf(); | |||||
handler->cp.setPF(rgbaPF); | |||||
origPF = handler->server.pf(); | |||||
handler->server.setPF(rgbaPF); | |||||
handler->readAndDecodeRect(pb.getRect(), encoding, &pb); | handler->readAndDecodeRect(pb.getRect(), encoding, &pb); | ||||
handler->cp.setPF(origPF); | |||||
handler->server.setPF(origPF); | |||||
// On-wire data has pre-multiplied alpha, but we store it | // On-wire data has pre-multiplied alpha, but we store it | ||||
// non-pre-multiplied | // non-pre-multiplied |
#include <rdr/OutStream.h> | #include <rdr/OutStream.h> | ||||
#include <rfb/msgTypes.h> | #include <rfb/msgTypes.h> | ||||
#include <rfb/fenceTypes.h> | #include <rfb/fenceTypes.h> | ||||
#include <rfb/encodings.h> | |||||
#include <rfb/qemuTypes.h> | #include <rfb/qemuTypes.h> | ||||
#include <rfb/Exception.h> | #include <rfb/Exception.h> | ||||
#include <rfb/PixelFormat.h> | #include <rfb/PixelFormat.h> | ||||
#include <rfb/Rect.h> | #include <rfb/Rect.h> | ||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/Decoder.h> | |||||
#include <rfb/ServerParams.h> | |||||
#include <rfb/CMsgWriter.h> | #include <rfb/CMsgWriter.h> | ||||
using namespace rfb; | using namespace rfb; | ||||
CMsgWriter::CMsgWriter(ConnParams* cp_, rdr::OutStream* os_) | |||||
: cp(cp_), os(os_) | |||||
CMsgWriter::CMsgWriter(ServerParams* server_, rdr::OutStream* os_) | |||||
: server(server_), os(os_) | |||||
{ | { | ||||
} | } | ||||
endMsg(); | endMsg(); | ||||
} | } | ||||
void CMsgWriter::writeSetEncodings(int nEncodings, rdr::U32* encodings) | |||||
void CMsgWriter::writeSetEncodings(const std::list<rdr::U32> encodings) | |||||
{ | { | ||||
std::list<rdr::U32>::const_iterator iter; | |||||
startMsg(msgTypeSetEncodings); | startMsg(msgTypeSetEncodings); | ||||
os->skip(1); | os->skip(1); | ||||
os->writeU16(nEncodings); | |||||
for (int i = 0; i < nEncodings; i++) | |||||
os->writeU32(encodings[i]); | |||||
os->writeU16(encodings.size()); | |||||
for (iter = encodings.begin(); iter != encodings.end(); ++iter) | |||||
os->writeU32(*iter); | |||||
endMsg(); | endMsg(); | ||||
} | } | ||||
// Ask for encodings based on which decoders are supported. Assumes higher | |||||
// encoding numbers are more desirable. | |||||
void CMsgWriter::writeSetEncodings(int preferredEncoding, bool useCopyRect) | |||||
{ | |||||
int nEncodings = 0; | |||||
rdr::U32 encodings[encodingMax+3]; | |||||
if (cp->supportsLocalCursor) { | |||||
encodings[nEncodings++] = pseudoEncodingCursorWithAlpha; | |||||
encodings[nEncodings++] = pseudoEncodingCursor; | |||||
encodings[nEncodings++] = pseudoEncodingXCursor; | |||||
} | |||||
if (cp->supportsDesktopResize) | |||||
encodings[nEncodings++] = pseudoEncodingDesktopSize; | |||||
if (cp->supportsExtendedDesktopSize) | |||||
encodings[nEncodings++] = pseudoEncodingExtendedDesktopSize; | |||||
if (cp->supportsDesktopRename) | |||||
encodings[nEncodings++] = pseudoEncodingDesktopName; | |||||
if (cp->supportsLEDState) | |||||
encodings[nEncodings++] = pseudoEncodingLEDState; | |||||
encodings[nEncodings++] = pseudoEncodingLastRect; | |||||
encodings[nEncodings++] = pseudoEncodingContinuousUpdates; | |||||
encodings[nEncodings++] = pseudoEncodingFence; | |||||
encodings[nEncodings++] = pseudoEncodingQEMUKeyEvent; | |||||
if (Decoder::supported(preferredEncoding)) { | |||||
encodings[nEncodings++] = preferredEncoding; | |||||
} | |||||
if (useCopyRect) { | |||||
encodings[nEncodings++] = encodingCopyRect; | |||||
} | |||||
/* | |||||
* Prefer encodings in this order: | |||||
* | |||||
* Tight, ZRLE, Hextile, * | |||||
*/ | |||||
if ((preferredEncoding != encodingTight) && | |||||
Decoder::supported(encodingTight)) | |||||
encodings[nEncodings++] = encodingTight; | |||||
if ((preferredEncoding != encodingZRLE) && | |||||
Decoder::supported(encodingZRLE)) | |||||
encodings[nEncodings++] = encodingZRLE; | |||||
if ((preferredEncoding != encodingHextile) && | |||||
Decoder::supported(encodingHextile)) | |||||
encodings[nEncodings++] = encodingHextile; | |||||
// Remaining encodings | |||||
for (int i = encodingMax; i >= 0; i--) { | |||||
switch (i) { | |||||
case encodingCopyRect: | |||||
case encodingTight: | |||||
case encodingZRLE: | |||||
case encodingHextile: | |||||
/* These have already been sent earlier */ | |||||
break; | |||||
default: | |||||
if ((i != preferredEncoding) && Decoder::supported(i)) | |||||
encodings[nEncodings++] = i; | |||||
} | |||||
} | |||||
if (cp->compressLevel >= 0 && cp->compressLevel <= 9) | |||||
encodings[nEncodings++] = pseudoEncodingCompressLevel0 + cp->compressLevel; | |||||
if (cp->qualityLevel >= 0 && cp->qualityLevel <= 9) | |||||
encodings[nEncodings++] = pseudoEncodingQualityLevel0 + cp->qualityLevel; | |||||
writeSetEncodings(nEncodings, encodings); | |||||
} | |||||
void CMsgWriter::writeSetDesktopSize(int width, int height, | void CMsgWriter::writeSetDesktopSize(int width, int height, | ||||
const ScreenSet& layout) | const ScreenSet& layout) | ||||
{ | { | ||||
if (!cp->supportsSetDesktopSize) | |||||
if (!server->supportsSetDesktopSize) | |||||
throw Exception("Server does not support SetDesktopSize"); | throw Exception("Server does not support SetDesktopSize"); | ||||
startMsg(msgTypeSetDesktopSize); | startMsg(msgTypeSetDesktopSize); | ||||
void CMsgWriter::writeEnableContinuousUpdates(bool enable, | void CMsgWriter::writeEnableContinuousUpdates(bool enable, | ||||
int x, int y, int w, int h) | int x, int y, int w, int h) | ||||
{ | { | ||||
if (!cp->supportsContinuousUpdates) | |||||
if (!server->supportsContinuousUpdates) | |||||
throw Exception("Server does not support continuous updates"); | throw Exception("Server does not support continuous updates"); | ||||
startMsg(msgTypeEnableContinuousUpdates); | startMsg(msgTypeEnableContinuousUpdates); | ||||
void CMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[]) | void CMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[]) | ||||
{ | { | ||||
if (!cp->supportsFence) | |||||
if (!server->supportsFence) | |||||
throw Exception("Server does not support fences"); | throw Exception("Server does not support fences"); | ||||
if (len > 64) | if (len > 64) | ||||
throw Exception("Too large fence payload"); | throw Exception("Too large fence payload"); | ||||
void CMsgWriter::writeKeyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) | void CMsgWriter::writeKeyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) | ||||
{ | { | ||||
if (!cp->supportsQEMUKeyEvent || !keycode) { | |||||
if (!server->supportsQEMUKeyEvent || !keycode) { | |||||
/* This event isn't meaningful without a valid keysym */ | /* This event isn't meaningful without a valid keysym */ | ||||
if (!keysym) | if (!keysym) | ||||
return; | return; | ||||
Point p(pos); | Point p(pos); | ||||
if (p.x < 0) p.x = 0; | if (p.x < 0) p.x = 0; | ||||
if (p.y < 0) p.y = 0; | if (p.y < 0) p.y = 0; | ||||
if (p.x >= cp->width) p.x = cp->width - 1; | |||||
if (p.y >= cp->height) p.y = cp->height - 1; | |||||
if (p.x >= server->width()) p.x = server->width() - 1; | |||||
if (p.y >= server->height()) p.y = server->height() - 1; | |||||
startMsg(msgTypePointerEvent); | startMsg(msgTypePointerEvent); | ||||
os->writeU8(buttonMask); | os->writeU8(buttonMask); |
#ifndef __RFB_CMSGWRITER_H__ | #ifndef __RFB_CMSGWRITER_H__ | ||||
#define __RFB_CMSGWRITER_H__ | #define __RFB_CMSGWRITER_H__ | ||||
#include <list> | |||||
#include <rdr/types.h> | #include <rdr/types.h> | ||||
namespace rdr { class OutStream; } | namespace rdr { class OutStream; } | ||||
namespace rfb { | namespace rfb { | ||||
class PixelFormat; | class PixelFormat; | ||||
class ConnParams; | |||||
class ServerParams; | |||||
struct ScreenSet; | struct ScreenSet; | ||||
struct Point; | struct Point; | ||||
struct Rect; | struct Rect; | ||||
class CMsgWriter { | class CMsgWriter { | ||||
public: | public: | ||||
CMsgWriter(ConnParams* cp, rdr::OutStream* os); | |||||
CMsgWriter(ServerParams* server, rdr::OutStream* os); | |||||
virtual ~CMsgWriter(); | virtual ~CMsgWriter(); | ||||
void writeClientInit(bool shared); | void writeClientInit(bool shared); | ||||
void writeSetPixelFormat(const PixelFormat& pf); | void writeSetPixelFormat(const PixelFormat& pf); | ||||
void writeSetEncodings(int nEncodings, rdr::U32* encodings); | |||||
void writeSetEncodings(int preferredEncoding, bool useCopyRect); | |||||
void writeSetEncodings(const std::list<rdr::U32> encodings); | |||||
void writeSetDesktopSize(int width, int height, const ScreenSet& layout); | void writeSetDesktopSize(int width, int height, const ScreenSet& layout); | ||||
void writeFramebufferUpdateRequest(const Rect& r,bool incremental); | void writeFramebufferUpdateRequest(const Rect& r,bool incremental); | ||||
void startMsg(int type); | void startMsg(int type); | ||||
void endMsg(); | void endMsg(); | ||||
ConnParams* cp; | |||||
ServerParams* server; | |||||
rdr::OutStream* os; | rdr::OutStream* os; | ||||
}; | }; | ||||
} | } |
#endif | #endif | ||||
#include <rfb/CSecurityTLS.h> | #include <rfb/CSecurityTLS.h> | ||||
#include <rfb/SSecurityVeNCrypt.h> | |||||
#include <rfb/CConnection.h> | #include <rfb/CConnection.h> | ||||
#include <rfb/LogWriter.h> | #include <rfb/LogWriter.h> | ||||
#include <rfb/Exception.h> | #include <rfb/Exception.h> |
#endif | #endif | ||||
#include <rfb/CSecurity.h> | #include <rfb/CSecurity.h> | ||||
#include <rfb/SSecurityVeNCrypt.h> | |||||
#include <rfb/Security.h> | #include <rfb/Security.h> | ||||
#include <rfb/UserMsgBox.h> | #include <rfb/UserMsgBox.h> | ||||
#include <rdr/InStream.h> | #include <rdr/InStream.h> |
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||||
* Copyright (C) 2011 D. R. Commander. All Rights Reserved. | |||||
* Copyright 2014-2018 Pierre Ossman 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 <rfb/Exception.h> | |||||
#include <rfb/encodings.h> | |||||
#include <rfb/ledStates.h> | |||||
#include <rfb/ClientParams.h> | |||||
using namespace rfb; | |||||
ClientParams::ClientParams() | |||||
: majorVersion(0), minorVersion(0), | |||||
compressLevel(2), qualityLevel(-1), fineQualityLevel(-1), | |||||
subsampling(subsampleUndefined), | |||||
width_(0), height_(0), name_(0), | |||||
ledState_(ledUnknown) | |||||
{ | |||||
setName(""); | |||||
cursor_ = new Cursor(0, 0, Point(), NULL); | |||||
} | |||||
ClientParams::~ClientParams() | |||||
{ | |||||
delete [] name_; | |||||
delete cursor_; | |||||
} | |||||
void ClientParams::setDimensions(int width, int height) | |||||
{ | |||||
ScreenSet layout; | |||||
layout.add_screen(rfb::Screen(0, 0, 0, width, height, 0)); | |||||
setDimensions(width, height, layout); | |||||
} | |||||
void ClientParams::setDimensions(int width, int height, const ScreenSet& layout) | |||||
{ | |||||
if (!layout.validate(width, height)) | |||||
throw Exception("Attempted to configure an invalid screen layout"); | |||||
width_ = width; | |||||
height_ = height; | |||||
screenLayout_ = layout; | |||||
} | |||||
void ClientParams::setPF(const PixelFormat& pf) | |||||
{ | |||||
pf_ = pf; | |||||
if (pf.bpp != 8 && pf.bpp != 16 && pf.bpp != 32) | |||||
throw Exception("setPF: not 8, 16 or 32 bpp?"); | |||||
} | |||||
void ClientParams::setName(const char* name) | |||||
{ | |||||
delete [] name_; | |||||
name_ = strDup(name); | |||||
} | |||||
void ClientParams::setCursor(const Cursor& other) | |||||
{ | |||||
delete cursor_; | |||||
cursor_ = new Cursor(other); | |||||
} | |||||
bool ClientParams::supportsEncoding(rdr::S32 encoding) const | |||||
{ | |||||
return encodings_.count(encoding) != 0; | |||||
} | |||||
void ClientParams::setEncodings(int nEncodings, const rdr::S32* encodings) | |||||
{ | |||||
compressLevel = -1; | |||||
qualityLevel = -1; | |||||
fineQualityLevel = -1; | |||||
subsampling = subsampleUndefined; | |||||
encodings_.clear(); | |||||
encodings_.insert(encodingRaw); | |||||
for (int i = nEncodings-1; i >= 0; i--) { | |||||
switch (encodings[i]) { | |||||
case pseudoEncodingSubsamp1X: | |||||
subsampling = subsampleNone; | |||||
break; | |||||
case pseudoEncodingSubsampGray: | |||||
subsampling = subsampleGray; | |||||
break; | |||||
case pseudoEncodingSubsamp2X: | |||||
subsampling = subsample2X; | |||||
break; | |||||
case pseudoEncodingSubsamp4X: | |||||
subsampling = subsample4X; | |||||
break; | |||||
case pseudoEncodingSubsamp8X: | |||||
subsampling = subsample8X; | |||||
break; | |||||
case pseudoEncodingSubsamp16X: | |||||
subsampling = subsample16X; | |||||
break; | |||||
} | |||||
if (encodings[i] >= pseudoEncodingCompressLevel0 && | |||||
encodings[i] <= pseudoEncodingCompressLevel9) | |||||
compressLevel = encodings[i] - pseudoEncodingCompressLevel0; | |||||
if (encodings[i] >= pseudoEncodingQualityLevel0 && | |||||
encodings[i] <= pseudoEncodingQualityLevel9) | |||||
qualityLevel = encodings[i] - pseudoEncodingQualityLevel0; | |||||
if (encodings[i] >= pseudoEncodingFineQualityLevel0 && | |||||
encodings[i] <= pseudoEncodingFineQualityLevel100) | |||||
fineQualityLevel = encodings[i] - pseudoEncodingFineQualityLevel0; | |||||
encodings_.insert(encodings[i]); | |||||
} | |||||
} | |||||
void ClientParams::setLEDState(unsigned int state) | |||||
{ | |||||
ledState_ = state; | |||||
} | |||||
bool ClientParams::supportsLocalCursor() const | |||||
{ | |||||
if (supportsEncoding(pseudoEncodingCursorWithAlpha)) | |||||
return true; | |||||
if (supportsEncoding(pseudoEncodingCursor)) | |||||
return true; | |||||
if (supportsEncoding(pseudoEncodingXCursor)) | |||||
return true; | |||||
return false; | |||||
} | |||||
bool ClientParams::supportsDesktopSize() const | |||||
{ | |||||
if (supportsEncoding(pseudoEncodingExtendedDesktopSize)) | |||||
return true; | |||||
if (supportsEncoding(pseudoEncodingDesktopSize)) | |||||
return true; | |||||
return false; | |||||
} | |||||
bool ClientParams::supportsLEDState() const | |||||
{ | |||||
if (supportsEncoding(pseudoEncodingLEDState)) | |||||
return true; | |||||
return false; | |||||
} | |||||
bool ClientParams::supportsFence() const | |||||
{ | |||||
if (supportsEncoding(pseudoEncodingFence)) | |||||
return true; | |||||
return false; | |||||
} | |||||
bool ClientParams::supportsContinuousUpdates() const | |||||
{ | |||||
if (supportsEncoding(pseudoEncodingContinuousUpdates)) | |||||
return true; | |||||
return false; | |||||
} |
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | ||||
* Copyright 2014 Pierre Ossman for Cendio AB | |||||
* Copyright 2014-2018 Pierre Ossman for Cendio AB | |||||
* | * | ||||
* This is free software; you can redistribute it and/or modify | * This is free software; you can redistribute it and/or modify | ||||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | ||||
* USA. | * USA. | ||||
*/ | */ | ||||
// | // | ||||
// ConnParams - structure containing the connection parameters. | |||||
// ClientParams - structure describing the current state of the remote client | |||||
// | // | ||||
#ifndef __RFB_CONNPARAMS_H__ | |||||
#define __RFB_CONNPARAMS_H__ | |||||
#ifndef __RFB_CLIENTPARAMS_H__ | |||||
#define __RFB_CLIENTPARAMS_H__ | |||||
#include <set> | #include <set> | ||||
#include <rfb/PixelFormat.h> | #include <rfb/PixelFormat.h> | ||||
#include <rfb/ScreenSet.h> | #include <rfb/ScreenSet.h> | ||||
namespace rdr { class InStream; } | |||||
namespace rfb { | namespace rfb { | ||||
const int subsampleUndefined = -1; | const int subsampleUndefined = -1; | ||||
const int subsample8X = 4; | const int subsample8X = 4; | ||||
const int subsample16X = 5; | const int subsample16X = 5; | ||||
class ConnParams { | |||||
class ClientParams { | |||||
public: | public: | ||||
ConnParams(); | |||||
~ConnParams(); | |||||
bool readVersion(rdr::InStream* is, bool* done); | |||||
void writeVersion(rdr::OutStream* os); | |||||
ClientParams(); | |||||
~ClientParams(); | |||||
int majorVersion; | int majorVersion; | ||||
int minorVersion; | int minorVersion; | ||||
return !beforeVersion(major,minor+1); | return !beforeVersion(major,minor+1); | ||||
} | } | ||||
int width; | |||||
int height; | |||||
ScreenSet screenLayout; | |||||
const int width() const { return width_; } | |||||
const int height() const { return height_; } | |||||
const ScreenSet& screenLayout() const { return screenLayout_; } | |||||
void setDimensions(int width, int height); | |||||
void setDimensions(int width, int height, const ScreenSet& layout); | |||||
const PixelFormat& pf() const { return pf_; } | const PixelFormat& pf() const { return pf_; } | ||||
void setPF(const PixelFormat& pf); | void setPF(const PixelFormat& pf); | ||||
unsigned int ledState() { return ledState_; } | unsigned int ledState() { return ledState_; } | ||||
void setLEDState(unsigned int state); | void setLEDState(unsigned int state); | ||||
bool useCopyRect; | |||||
bool supportsLocalCursor; | |||||
bool supportsLocalXCursor; | |||||
bool supportsLocalCursorWithAlpha; | |||||
bool supportsDesktopResize; | |||||
bool supportsExtendedDesktopSize; | |||||
bool supportsDesktopRename; | |||||
bool supportsLastRect; | |||||
bool supportsLEDState; | |||||
bool supportsQEMUKeyEvent; | |||||
bool supportsSetDesktopSize; | |||||
bool supportsFence; | |||||
bool supportsContinuousUpdates; | |||||
// Wrappers to check for functionality rather than specific | |||||
// encodings | |||||
bool supportsLocalCursor() const; | |||||
bool supportsDesktopSize() const; | |||||
bool supportsLEDState() const; | |||||
bool supportsFence() const; | |||||
bool supportsContinuousUpdates() const; | |||||
int compressLevel; | int compressLevel; | ||||
int qualityLevel; | int qualityLevel; | ||||
private: | private: | ||||
int width_; | |||||
int height_; | |||||
ScreenSet screenLayout_; | |||||
PixelFormat pf_; | PixelFormat pf_; | ||||
char* name_; | char* name_; | ||||
Cursor* cursor_; | Cursor* cursor_; | ||||
std::set<rdr::S32> encodings_; | std::set<rdr::S32> encodings_; | ||||
char verStr[13]; | |||||
int verStrPos; | |||||
unsigned int ledState_; | unsigned int ledState_; | ||||
}; | }; | ||||
} | } |
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||||
* Copyright (C) 2011 D. R. Commander. All Rights Reserved. | |||||
* Copyright 2014 Pierre Ossman 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 <stdio.h> | |||||
#include <rdr/InStream.h> | |||||
#include <rdr/OutStream.h> | |||||
#include <rfb/Exception.h> | |||||
#include <rfb/encodings.h> | |||||
#include <rfb/ledStates.h> | |||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/util.h> | |||||
using namespace rfb; | |||||
ConnParams::ConnParams() | |||||
: majorVersion(0), minorVersion(0), | |||||
width(0), height(0), useCopyRect(false), | |||||
supportsLocalCursor(false), supportsLocalXCursor(false), | |||||
supportsLocalCursorWithAlpha(false), | |||||
supportsDesktopResize(false), supportsExtendedDesktopSize(false), | |||||
supportsDesktopRename(false), supportsLastRect(false), | |||||
supportsLEDState(false), supportsQEMUKeyEvent(false), | |||||
supportsSetDesktopSize(false), supportsFence(false), | |||||
supportsContinuousUpdates(false), | |||||
compressLevel(2), qualityLevel(-1), fineQualityLevel(-1), | |||||
subsampling(subsampleUndefined), name_(0), verStrPos(0), | |||||
ledState_(ledUnknown) | |||||
{ | |||||
setName(""); | |||||
cursor_ = new Cursor(0, 0, Point(), NULL); | |||||
} | |||||
ConnParams::~ConnParams() | |||||
{ | |||||
delete [] name_; | |||||
delete cursor_; | |||||
} | |||||
bool ConnParams::readVersion(rdr::InStream* is, bool* done) | |||||
{ | |||||
if (verStrPos >= 12) return false; | |||||
while (is->checkNoWait(1) && verStrPos < 12) { | |||||
verStr[verStrPos++] = is->readU8(); | |||||
} | |||||
if (verStrPos < 12) { | |||||
*done = false; | |||||
return true; | |||||
} | |||||
*done = true; | |||||
verStr[12] = 0; | |||||
return (sscanf(verStr, "RFB %03d.%03d\n", &majorVersion,&minorVersion) == 2); | |||||
} | |||||
void ConnParams::writeVersion(rdr::OutStream* os) | |||||
{ | |||||
char str[13]; | |||||
sprintf(str, "RFB %03d.%03d\n", majorVersion, minorVersion); | |||||
os->writeBytes(str, 12); | |||||
os->flush(); | |||||
} | |||||
void ConnParams::setPF(const PixelFormat& pf) | |||||
{ | |||||
pf_ = pf; | |||||
if (pf.bpp != 8 && pf.bpp != 16 && pf.bpp != 32) | |||||
throw Exception("setPF: not 8, 16 or 32 bpp?"); | |||||
} | |||||
void ConnParams::setName(const char* name) | |||||
{ | |||||
delete [] name_; | |||||
name_ = strDup(name); | |||||
} | |||||
void ConnParams::setCursor(const Cursor& other) | |||||
{ | |||||
delete cursor_; | |||||
cursor_ = new Cursor(other); | |||||
} | |||||
bool ConnParams::supportsEncoding(rdr::S32 encoding) const | |||||
{ | |||||
return encodings_.count(encoding) != 0; | |||||
} | |||||
void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) | |||||
{ | |||||
useCopyRect = false; | |||||
supportsLocalCursor = false; | |||||
supportsLocalCursorWithAlpha = false; | |||||
supportsDesktopResize = false; | |||||
supportsExtendedDesktopSize = false; | |||||
supportsLocalXCursor = false; | |||||
supportsLastRect = false; | |||||
supportsQEMUKeyEvent = false; | |||||
compressLevel = -1; | |||||
qualityLevel = -1; | |||||
fineQualityLevel = -1; | |||||
subsampling = subsampleUndefined; | |||||
encodings_.clear(); | |||||
encodings_.insert(encodingRaw); | |||||
for (int i = nEncodings-1; i >= 0; i--) { | |||||
switch (encodings[i]) { | |||||
case encodingCopyRect: | |||||
useCopyRect = true; | |||||
break; | |||||
case pseudoEncodingCursor: | |||||
supportsLocalCursor = true; | |||||
break; | |||||
case pseudoEncodingXCursor: | |||||
supportsLocalXCursor = true; | |||||
break; | |||||
case pseudoEncodingCursorWithAlpha: | |||||
supportsLocalCursorWithAlpha = true; | |||||
break; | |||||
case pseudoEncodingDesktopSize: | |||||
supportsDesktopResize = true; | |||||
break; | |||||
case pseudoEncodingExtendedDesktopSize: | |||||
supportsExtendedDesktopSize = true; | |||||
break; | |||||
case pseudoEncodingDesktopName: | |||||
supportsDesktopRename = true; | |||||
break; | |||||
case pseudoEncodingLastRect: | |||||
supportsLastRect = true; | |||||
break; | |||||
case pseudoEncodingLEDState: | |||||
supportsLEDState = true; | |||||
break; | |||||
case pseudoEncodingQEMUKeyEvent: | |||||
supportsQEMUKeyEvent = true; | |||||
break; | |||||
case pseudoEncodingFence: | |||||
supportsFence = true; | |||||
break; | |||||
case pseudoEncodingContinuousUpdates: | |||||
supportsContinuousUpdates = true; | |||||
break; | |||||
case pseudoEncodingSubsamp1X: | |||||
subsampling = subsampleNone; | |||||
break; | |||||
case pseudoEncodingSubsampGray: | |||||
subsampling = subsampleGray; | |||||
break; | |||||
case pseudoEncodingSubsamp2X: | |||||
subsampling = subsample2X; | |||||
break; | |||||
case pseudoEncodingSubsamp4X: | |||||
subsampling = subsample4X; | |||||
break; | |||||
case pseudoEncodingSubsamp8X: | |||||
subsampling = subsample8X; | |||||
break; | |||||
case pseudoEncodingSubsamp16X: | |||||
subsampling = subsample16X; | |||||
break; | |||||
} | |||||
if (encodings[i] >= pseudoEncodingCompressLevel0 && | |||||
encodings[i] <= pseudoEncodingCompressLevel9) | |||||
compressLevel = encodings[i] - pseudoEncodingCompressLevel0; | |||||
if (encodings[i] >= pseudoEncodingQualityLevel0 && | |||||
encodings[i] <= pseudoEncodingQualityLevel9) | |||||
qualityLevel = encodings[i] - pseudoEncodingQualityLevel0; | |||||
if (encodings[i] >= pseudoEncodingFineQualityLevel0 && | |||||
encodings[i] <= pseudoEncodingFineQualityLevel100) | |||||
fineQualityLevel = encodings[i] - pseudoEncodingFineQualityLevel0; | |||||
if (encodings[i] > 0) | |||||
encodings_.insert(encodings[i]); | |||||
} | |||||
} | |||||
void ConnParams::setLEDState(unsigned int state) | |||||
{ | |||||
ledState_ = state; | |||||
} |
} | } | ||||
void CopyRectDecoder::readRect(const Rect& r, rdr::InStream* is, | void CopyRectDecoder::readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os) | |||||
const ServerParams& server, rdr::OutStream* os) | |||||
{ | { | ||||
os->copyBytes(is, 4); | os->copyBytes(is, 4); | ||||
} | } | ||||
void CopyRectDecoder::getAffectedRegion(const Rect& rect, | void CopyRectDecoder::getAffectedRegion(const Rect& rect, | ||||
const void* buffer, | const void* buffer, | ||||
size_t buflen, | size_t buflen, | ||||
const ConnParams& cp, | |||||
const ServerParams& server, | |||||
Region* region) | Region* region) | ||||
{ | { | ||||
rdr::MemInStream is(buffer, buflen); | rdr::MemInStream is(buffer, buflen); | ||||
int srcX = is.readU16(); | int srcX = is.readU16(); | ||||
int srcY = is.readU16(); | int srcY = is.readU16(); | ||||
Decoder::getAffectedRegion(rect, buffer, buflen, cp, region); | |||||
Decoder::getAffectedRegion(rect, buffer, buflen, server, region); | |||||
region->assign_union(Region(rect.translate(Point(srcX-rect.tl.x, | region->assign_union(Region(rect.translate(Point(srcX-rect.tl.x, | ||||
srcY-rect.tl.y)))); | srcY-rect.tl.y)))); | ||||
} | } | ||||
void CopyRectDecoder::decodeRect(const Rect& r, const void* buffer, | void CopyRectDecoder::decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb) | ModifiablePixelBuffer* pb) | ||||
{ | { | ||||
rdr::MemInStream is(buffer, buflen); | rdr::MemInStream is(buffer, buflen); |
CopyRectDecoder(); | CopyRectDecoder(); | ||||
virtual ~CopyRectDecoder(); | virtual ~CopyRectDecoder(); | ||||
virtual void readRect(const Rect& r, rdr::InStream* is, | virtual void readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os); | |||||
const ServerParams& server, rdr::OutStream* os); | |||||
virtual void getAffectedRegion(const Rect& rect, const void* buffer, | virtual void getAffectedRegion(const Rect& rect, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
Region* region); | Region* region); | ||||
virtual void decodeRect(const Rect& r, const void* buffer, | virtual void decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb); | ModifiablePixelBuffer* pb); | ||||
}; | }; | ||||
} | } |
if (threads.empty()) { | if (threads.empty()) { | ||||
bufferStream = freeBuffers.front(); | bufferStream = freeBuffers.front(); | ||||
bufferStream->clear(); | bufferStream->clear(); | ||||
decoder->readRect(r, conn->getInStream(), conn->cp, bufferStream); | |||||
decoder->readRect(r, conn->getInStream(), conn->server, bufferStream); | |||||
decoder->decodeRect(r, bufferStream->data(), bufferStream->length(), | decoder->decodeRect(r, bufferStream->data(), bufferStream->length(), | ||||
conn->cp, pb); | |||||
conn->server, pb); | |||||
return; | return; | ||||
} | } | ||||
// Read the rect | // Read the rect | ||||
bufferStream->clear(); | bufferStream->clear(); | ||||
decoder->readRect(r, conn->getInStream(), conn->cp, bufferStream); | |||||
decoder->readRect(r, conn->getInStream(), conn->server, bufferStream); | |||||
// Then try to put it on the queue | // Then try to put it on the queue | ||||
entry = new QueueEntry; | entry = new QueueEntry; | ||||
entry->rect = r; | entry->rect = r; | ||||
entry->encoding = encoding; | entry->encoding = encoding; | ||||
entry->decoder = decoder; | entry->decoder = decoder; | ||||
entry->cp = &conn->cp; | |||||
entry->server = &conn->server; | |||||
entry->pb = pb; | entry->pb = pb; | ||||
entry->bufferStream = bufferStream; | entry->bufferStream = bufferStream; | ||||
decoder->getAffectedRegion(r, bufferStream->data(), | decoder->getAffectedRegion(r, bufferStream->data(), | ||||
bufferStream->length(), conn->cp, | |||||
bufferStream->length(), conn->server, | |||||
&entry->affectedRegion); | &entry->affectedRegion); | ||||
queueMutex->lock(); | queueMutex->lock(); | ||||
try { | try { | ||||
entry->decoder->decodeRect(entry->rect, entry->bufferStream->data(), | entry->decoder->decodeRect(entry->rect, entry->bufferStream->data(), | ||||
entry->bufferStream->length(), | entry->bufferStream->length(), | ||||
*entry->cp, entry->pb); | |||||
*entry->server, entry->pb); | |||||
} catch (rdr::Exception& e) { | } catch (rdr::Exception& e) { | ||||
manager->setThreadException(e); | manager->setThreadException(e); | ||||
} catch(...) { | } catch(...) { | ||||
(*iter2)->rect, | (*iter2)->rect, | ||||
(*iter2)->bufferStream->data(), | (*iter2)->bufferStream->data(), | ||||
(*iter2)->bufferStream->length(), | (*iter2)->bufferStream->length(), | ||||
*entry->cp)) | |||||
*entry->server)) | |||||
goto next; | goto next; | ||||
} | } | ||||
} | } |
Rect rect; | Rect rect; | ||||
int encoding; | int encoding; | ||||
Decoder* decoder; | Decoder* decoder; | ||||
const ConnParams* cp; | |||||
const ServerParams* server; | |||||
ModifiablePixelBuffer* pb; | ModifiablePixelBuffer* pb; | ||||
rdr::MemOutStream* bufferStream; | rdr::MemOutStream* bufferStream; | ||||
Region affectedRegion; | Region affectedRegion; |
} | } | ||||
void Decoder::getAffectedRegion(const Rect& rect, const void* buffer, | void Decoder::getAffectedRegion(const Rect& rect, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
Region* region) | Region* region) | ||||
{ | { | ||||
region->reset(rect); | region->reset(rect); | ||||
bool Decoder::doRectsConflict(const Rect& rectA, const void* bufferA, | bool Decoder::doRectsConflict(const Rect& rectA, const void* bufferA, | ||||
size_t buflenA, const Rect& rectB, | size_t buflenA, const Rect& rectB, | ||||
const void* bufferB, size_t buflenB, | const void* bufferB, size_t buflenB, | ||||
const ConnParams& cp) | |||||
const ServerParams& server) | |||||
{ | { | ||||
return false; | return false; | ||||
} | } |
} | } | ||||
namespace rfb { | namespace rfb { | ||||
class ConnParams; | |||||
class ServerParams; | |||||
class ModifiablePixelBuffer; | class ModifiablePixelBuffer; | ||||
class Region; | class Region; | ||||
// make it easier to decode. This function will always be called in | // make it easier to decode. This function will always be called in | ||||
// a serial manner on the main thread. | // a serial manner on the main thread. | ||||
virtual void readRect(const Rect& r, rdr::InStream* is, | virtual void readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os)=0; | |||||
const ServerParams& server, rdr::OutStream* os)=0; | |||||
// These functions will be called from any of the worker threads. | // These functions will be called from any of the worker threads. | ||||
// A lock will be held whilst these are called so it is safe to | // A lock will be held whilst these are called so it is safe to | ||||
// be either read from or written do when decoding this rect. The | // be either read from or written do when decoding this rect. The | ||||
// default implementation simply returns the given rectangle. | // default implementation simply returns the given rectangle. | ||||
virtual void getAffectedRegion(const Rect& rect, const void* buffer, | virtual void getAffectedRegion(const Rect& rect, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
Region* region); | Region* region); | ||||
// doesRectsConflict() determines if two rectangles must be decoded | // doesRectsConflict() determines if two rectangles must be decoded | ||||
const Rect& rectB, | const Rect& rectB, | ||||
const void* bufferB, | const void* bufferB, | ||||
size_t buflenB, | size_t buflenB, | ||||
const ConnParams& cp); | |||||
const ServerParams& server); | |||||
// decodeRect() decodes the given rectangle with data from the | // decodeRect() decodes the given rectangle with data from the | ||||
// given buffer, onto the ModifiablePixelBuffer. The PixelFormat of | // given buffer, onto the ModifiablePixelBuffer. The PixelFormat of | ||||
// the PixelBuffer might not match the ConnParams and it is up to | // the PixelBuffer might not match the ConnParams and it is up to | ||||
// the decoder to do any necessary conversion. | // the decoder to do any necessary conversion. | ||||
virtual void decodeRect(const Rect& r, const void* buffer, | virtual void decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb)=0; | ModifiablePixelBuffer* pb)=0; | ||||
public: | public: |
changed = changed_; | changed = changed_; | ||||
if (!conn->client.supportsEncoding(encodingCopyRect)) | |||||
changed.assign_union(copied); | |||||
/* | /* | ||||
* We need to render the cursor seperately as it has its own | * We need to render the cursor seperately as it has its own | ||||
* magical pixel buffer, so split it out from the changed region. | * magical pixel buffer, so split it out from the changed region. | ||||
changed.assign_subtract(renderedCursor->getEffectiveRect()); | changed.assign_subtract(renderedCursor->getEffectiveRect()); | ||||
} | } | ||||
if (conn->cp.supportsLastRect) | |||||
if (conn->client.supportsEncoding(pseudoEncodingLastRect)) | |||||
nRects = 0xFFFF; | nRects = 0xFFFF; | ||||
else { | else { | ||||
nRects = copied.numRects(); | nRects = copied.numRects(); | ||||
conn->writer()->writeFramebufferUpdateStart(nRects); | conn->writer()->writeFramebufferUpdateStart(nRects); | ||||
writeCopyRects(copied, copyDelta); | |||||
if (conn->client.supportsEncoding(encodingCopyRect)) | |||||
writeCopyRects(copied, copyDelta); | |||||
/* | /* | ||||
* We start by searching for solid rects, which are then removed | * We start by searching for solid rects, which are then removed | ||||
* from the changed region. | * from the changed region. | ||||
*/ | */ | ||||
if (conn->cp.supportsLastRect) | |||||
if (conn->client.supportsEncoding(pseudoEncodingLastRect)) | |||||
writeSolidRects(&changed, pb); | writeSolidRects(&changed, pb); | ||||
writeRects(changed, pb); | writeRects(changed, pb); | ||||
solid = bitmap = bitmapRLE = encoderRaw; | solid = bitmap = bitmapRLE = encoderRaw; | ||||
indexed = indexedRLE = fullColour = encoderRaw; | indexed = indexedRLE = fullColour = encoderRaw; | ||||
allowJPEG = conn->cp.pf().bpp >= 16; | |||||
allowJPEG = conn->client.pf().bpp >= 16; | |||||
if (!allowLossy) { | if (!allowLossy) { | ||||
if (encoders[encoderTightJPEG]->losslessQuality == -1) | if (encoders[encoderTightJPEG]->losslessQuality == -1) | ||||
allowJPEG = false; | allowJPEG = false; | ||||
} | } | ||||
// JPEG is the only encoder that can reduce things to grayscale | // JPEG is the only encoder that can reduce things to grayscale | ||||
if ((conn->cp.subsampling == subsampleGray) && | |||||
if ((conn->client.subsampling == subsampleGray) && | |||||
encoders[encoderTightJPEG]->isSupported() && allowLossy) { | encoders[encoderTightJPEG]->isSupported() && allowLossy) { | ||||
solid = bitmap = bitmapRLE = encoderTightJPEG; | solid = bitmap = bitmapRLE = encoderTightJPEG; | ||||
indexed = indexedRLE = fullColour = encoderTightJPEG; | indexed = indexedRLE = fullColour = encoderTightJPEG; | ||||
encoder = encoders[*iter]; | encoder = encoders[*iter]; | ||||
encoder->setCompressLevel(conn->cp.compressLevel); | |||||
encoder->setCompressLevel(conn->client.compressLevel); | |||||
if (allowLossy) { | if (allowLossy) { | ||||
encoder->setQualityLevel(conn->cp.qualityLevel); | |||||
encoder->setFineQualityLevel(conn->cp.fineQualityLevel, | |||||
conn->cp.subsampling); | |||||
encoder->setQualityLevel(conn->client.qualityLevel); | |||||
encoder->setFineQualityLevel(conn->client.fineQualityLevel, | |||||
conn->client.subsampling); | |||||
} else { | } else { | ||||
int level = __rfbmax(conn->cp.qualityLevel, | |||||
int level = __rfbmax(conn->client.qualityLevel, | |||||
encoder->losslessQuality); | encoder->losslessQuality); | ||||
encoder->setQualityLevel(level); | encoder->setQualityLevel(level); | ||||
encoder->setFineQualityLevel(-1, subsampleUndefined); | encoder->setFineQualityLevel(-1, subsampleUndefined); | ||||
stats[klass][activeType].rects++; | stats[klass][activeType].rects++; | ||||
stats[klass][activeType].pixels += rect.area(); | stats[klass][activeType].pixels += rect.area(); | ||||
equiv = 12 + rect.area() * (conn->cp.pf().bpp/8); | |||||
equiv = 12 + rect.area() * (conn->client.pf().bpp/8); | |||||
stats[klass][activeType].equivalent += equiv; | stats[klass][activeType].equivalent += equiv; | ||||
encoder = encoders[klass]; | encoder = encoders[klass]; | ||||
copyStats.rects++; | copyStats.rects++; | ||||
copyStats.pixels += rect->area(); | copyStats.pixels += rect->area(); | ||||
equiv = 12 + rect->area() * (conn->cp.pf().bpp/8); | |||||
equiv = 12 + rect->area() * (conn->client.pf().bpp/8); | |||||
copyStats.equivalent += equiv; | copyStats.equivalent += equiv; | ||||
conn->writer()->writeCopyRect(*rect, rect->tl.x - delta.x, | conn->writer()->writeCopyRect(*rect, rect->tl.x - delta.x, | ||||
rdr::U32 _buffer2; | rdr::U32 _buffer2; | ||||
rdr::U8* converted = (rdr::U8*)&_buffer2; | rdr::U8* converted = (rdr::U8*)&_buffer2; | ||||
conn->cp.pf().bufferFromBuffer(converted, pb->getPF(), | |||||
conn->client.pf().bufferFromBuffer(converted, pb->getPF(), | |||||
colourValue, 1); | colourValue, 1); | ||||
encoder->writeSolidRect(erp.width(), erp.height(), | encoder->writeSolidRect(erp.width(), erp.height(), | ||||
conn->cp.pf(), converted); | |||||
conn->client.pf(), converted); | |||||
} | } | ||||
endRect(); | endRect(); | ||||
// compression setting means spending less effort in building | // compression setting means spending less effort in building | ||||
// a palette. It might be that they figured the increase in | // a palette. It might be that they figured the increase in | ||||
// zlib setting compensated for the loss. | // zlib setting compensated for the loss. | ||||
if (conn->cp.compressLevel == -1) | |||||
if (conn->client.compressLevel == -1) | |||||
divisor = 2 * 8; | divisor = 2 * 8; | ||||
else | else | ||||
divisor = conn->cp.compressLevel * 8; | |||||
divisor = conn->client.compressLevel * 8; | |||||
if (divisor < 4) | if (divisor < 4) | ||||
divisor = 4; | divisor = 4; | ||||
// Special exception inherited from the Tight encoder | // Special exception inherited from the Tight encoder | ||||
if (activeEncoders[encoderFullColour] == encoderTightJPEG) { | if (activeEncoders[encoderFullColour] == encoderTightJPEG) { | ||||
if ((conn->cp.compressLevel != -1) && (conn->cp.compressLevel < 2)) | |||||
if ((conn->client.compressLevel != -1) && (conn->client.compressLevel < 2)) | |||||
maxColours = 24; | maxColours = 24; | ||||
else | else | ||||
maxColours = 96; | maxColours = 96; | ||||
int stride; | int stride; | ||||
// Do wo need to convert the data? | // Do wo need to convert the data? | ||||
if (convert && !conn->cp.pf().equal(pb->getPF())) { | |||||
convertedPixelBuffer.setPF(conn->cp.pf()); | |||||
if (convert && !conn->client.pf().equal(pb->getPF())) { | |||||
convertedPixelBuffer.setPF(conn->client.pf()); | |||||
convertedPixelBuffer.setSize(rect.width(), rect.height()); | convertedPixelBuffer.setSize(rect.width(), rect.height()); | ||||
buffer = pb->getBuffer(rect, &stride); | buffer = pb->getBuffer(rect, &stride); |
#include <rdr/MemInStream.h> | #include <rdr/MemInStream.h> | ||||
#include <rdr/OutStream.h> | #include <rdr/OutStream.h> | ||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/ServerParams.h> | |||||
#include <rfb/PixelBuffer.h> | #include <rfb/PixelBuffer.h> | ||||
#include <rfb/HextileDecoder.h> | #include <rfb/HextileDecoder.h> | ||||
} | } | ||||
void HextileDecoder::readRect(const Rect& r, rdr::InStream* is, | void HextileDecoder::readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os) | |||||
const ServerParams& server, rdr::OutStream* os) | |||||
{ | { | ||||
Rect t; | Rect t; | ||||
size_t bytesPerPixel; | size_t bytesPerPixel; | ||||
bytesPerPixel = cp.pf().bpp/8; | |||||
bytesPerPixel = server.pf().bpp/8; | |||||
for (t.tl.y = r.tl.y; t.tl.y < r.br.y; t.tl.y += 16) { | for (t.tl.y = r.tl.y; t.tl.y < r.br.y; t.tl.y += 16) { | ||||
} | } | ||||
void HextileDecoder::decodeRect(const Rect& r, const void* buffer, | void HextileDecoder::decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb) | ModifiablePixelBuffer* pb) | ||||
{ | { | ||||
rdr::MemInStream is(buffer, buflen); | rdr::MemInStream is(buffer, buflen); | ||||
const PixelFormat& pf = cp.pf(); | |||||
const PixelFormat& pf = server.pf(); | |||||
switch (pf.bpp) { | switch (pf.bpp) { | ||||
case 8: hextileDecode8 (r, &is, pf, pb); break; | case 8: hextileDecode8 (r, &is, pf, pb); break; | ||||
case 16: hextileDecode16(r, &is, pf, pb); break; | case 16: hextileDecode16(r, &is, pf, pb); break; |
HextileDecoder(); | HextileDecoder(); | ||||
virtual ~HextileDecoder(); | virtual ~HextileDecoder(); | ||||
virtual void readRect(const Rect& r, rdr::InStream* is, | virtual void readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os); | |||||
const ServerParams& server, rdr::OutStream* os); | |||||
virtual void decodeRect(const Rect& r, const void* buffer, | virtual void decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb); | ModifiablePixelBuffer* pb); | ||||
}; | }; | ||||
} | } |
bool HextileEncoder::isSupported() | bool HextileEncoder::isSupported() | ||||
{ | { | ||||
return conn->cp.supportsEncoding(encodingHextile); | |||||
return conn->client.supportsEncoding(encodingHextile); | |||||
} | } | ||||
void HextileEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) | void HextileEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) |
#include <rdr/Exception.h> | #include <rdr/Exception.h> | ||||
#include <rfb/Rect.h> | #include <rfb/Rect.h> | ||||
#include <rfb/PixelFormat.h> | #include <rfb/PixelFormat.h> | ||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/ClientParams.h> | |||||
#include <stdio.h> | #include <stdio.h> | ||||
extern "C" { | extern "C" { |
char buf1[4096]; | char buf1[4096]; | ||||
vsnprintf(buf1, sizeof(buf1)-1, format, ap); | vsnprintf(buf1, sizeof(buf1)-1, format, ap); | ||||
buf1[sizeof(buf1)-1] = 0; | buf1[sizeof(buf1)-1] = 0; | ||||
write(level, logname, buf1); | |||||
char *buf = buf1; | |||||
while (true) { | |||||
char *end = strchr(buf, '\n'); | |||||
if (end) | |||||
*end = '\0'; | |||||
write(level, logname, buf); | |||||
if (!end) | |||||
break; | |||||
buf = end + 1; | |||||
} | |||||
} | } | ||||
void | void |
#include <rdr/MemInStream.h> | #include <rdr/MemInStream.h> | ||||
#include <rdr/OutStream.h> | #include <rdr/OutStream.h> | ||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/ServerParams.h> | |||||
#include <rfb/PixelBuffer.h> | #include <rfb/PixelBuffer.h> | ||||
#include <rfb/RREDecoder.h> | #include <rfb/RREDecoder.h> | ||||
} | } | ||||
void RREDecoder::readRect(const Rect& r, rdr::InStream* is, | void RREDecoder::readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os) | |||||
const ServerParams& server, rdr::OutStream* os) | |||||
{ | { | ||||
rdr::U32 numRects; | rdr::U32 numRects; | ||||
numRects = is->readU32(); | numRects = is->readU32(); | ||||
os->writeU32(numRects); | os->writeU32(numRects); | ||||
os->copyBytes(is, cp.pf().bpp/8 + numRects * (cp.pf().bpp/8 + 8)); | |||||
os->copyBytes(is, server.pf().bpp/8 + numRects * (server.pf().bpp/8 + 8)); | |||||
} | } | ||||
void RREDecoder::decodeRect(const Rect& r, const void* buffer, | void RREDecoder::decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb) | ModifiablePixelBuffer* pb) | ||||
{ | { | ||||
rdr::MemInStream is(buffer, buflen); | rdr::MemInStream is(buffer, buflen); | ||||
const PixelFormat& pf = cp.pf(); | |||||
const PixelFormat& pf = server.pf(); | |||||
switch (pf.bpp) { | switch (pf.bpp) { | ||||
case 8: rreDecode8 (r, &is, pf, pb); break; | case 8: rreDecode8 (r, &is, pf, pb); break; | ||||
case 16: rreDecode16(r, &is, pf, pb); break; | case 16: rreDecode16(r, &is, pf, pb); break; |
RREDecoder(); | RREDecoder(); | ||||
virtual ~RREDecoder(); | virtual ~RREDecoder(); | ||||
virtual void readRect(const Rect& r, rdr::InStream* is, | virtual void readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os); | |||||
const ServerParams& server, rdr::OutStream* os); | |||||
virtual void decodeRect(const Rect& r, const void* buffer, | virtual void decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb); | ModifiablePixelBuffer* pb); | ||||
}; | }; | ||||
} | } |
bool RREEncoder::isSupported() | bool RREEncoder::isSupported() | ||||
{ | { | ||||
return conn->cp.supportsEncoding(encodingRRE); | |||||
return conn->client.supportsEncoding(encodingRRE); | |||||
} | } | ||||
void RREEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) | void RREEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) |
#include <assert.h> | #include <assert.h> | ||||
#include <rdr/OutStream.h> | #include <rdr/OutStream.h> | ||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/ServerParams.h> | |||||
#include <rfb/PixelBuffer.h> | #include <rfb/PixelBuffer.h> | ||||
#include <rfb/RawDecoder.h> | #include <rfb/RawDecoder.h> | ||||
} | } | ||||
void RawDecoder::readRect(const Rect& r, rdr::InStream* is, | void RawDecoder::readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os) | |||||
const ServerParams& server, rdr::OutStream* os) | |||||
{ | { | ||||
os->copyBytes(is, r.area() * (cp.pf().bpp/8)); | |||||
os->copyBytes(is, r.area() * (server.pf().bpp/8)); | |||||
} | } | ||||
void RawDecoder::decodeRect(const Rect& r, const void* buffer, | void RawDecoder::decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb) | ModifiablePixelBuffer* pb) | ||||
{ | { | ||||
assert(buflen >= (size_t)r.area() * (cp.pf().bpp/8)); | |||||
pb->imageRect(cp.pf(), r, buffer); | |||||
assert(buflen >= (size_t)r.area() * (server.pf().bpp/8)); | |||||
pb->imageRect(server.pf(), r, buffer); | |||||
} | } |
RawDecoder(); | RawDecoder(); | ||||
virtual ~RawDecoder(); | virtual ~RawDecoder(); | ||||
virtual void readRect(const Rect& r, rdr::InStream* is, | virtual void readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os); | |||||
const ServerParams& server, rdr::OutStream* os); | |||||
virtual void decodeRect(const Rect& r, const void* buffer, | virtual void decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb); | ModifiablePixelBuffer* pb); | ||||
}; | }; | ||||
} | } |
if (rfb::Server::protocol3_3) | if (rfb::Server::protocol3_3) | ||||
defaultMinorVersion = 3; | defaultMinorVersion = 3; | ||||
cp.setVersion(defaultMajorVersion, defaultMinorVersion); | |||||
client.setVersion(defaultMajorVersion, defaultMinorVersion); | |||||
} | } | ||||
SConnection::~SConnection() | SConnection::~SConnection() | ||||
void SConnection::initialiseProtocol() | void SConnection::initialiseProtocol() | ||||
{ | { | ||||
cp.writeVersion(os); | |||||
char str[13]; | |||||
sprintf(str, "RFB %03d.%03d\n", defaultMajorVersion, defaultMinorVersion); | |||||
os->writeBytes(str, 12); | |||||
os->flush(); | |||||
state_ = RFBSTATE_PROTOCOL_VERSION; | state_ = RFBSTATE_PROTOCOL_VERSION; | ||||
} | } | ||||
void SConnection::processVersionMsg() | void SConnection::processVersionMsg() | ||||
{ | { | ||||
char verStr[13]; | |||||
int majorVersion; | |||||
int minorVersion; | |||||
vlog.debug("reading protocol version"); | vlog.debug("reading protocol version"); | ||||
bool done; | |||||
if (!cp.readVersion(is, &done)) { | |||||
if (!is->checkNoWait(12)) | |||||
return; | |||||
is->readBytes(verStr, 12); | |||||
verStr[12] = '\0'; | |||||
if (sscanf(verStr, "RFB %03d.%03d\n", | |||||
&majorVersion, &minorVersion) != 2) { | |||||
state_ = RFBSTATE_INVALID; | state_ = RFBSTATE_INVALID; | ||||
throw Exception("reading version failed: not an RFB client?"); | throw Exception("reading version failed: not an RFB client?"); | ||||
} | } | ||||
if (!done) return; | |||||
client.setVersion(majorVersion, minorVersion); | |||||
vlog.info("Client needs protocol version %d.%d", | vlog.info("Client needs protocol version %d.%d", | ||||
cp.majorVersion, cp.minorVersion); | |||||
client.majorVersion, client.minorVersion); | |||||
if (cp.majorVersion != 3) { | |||||
if (client.majorVersion != 3) { | |||||
// unknown protocol version | // unknown protocol version | ||||
throwConnFailedException("Client needs protocol version %d.%d, server has %d.%d", | throwConnFailedException("Client needs protocol version %d.%d, server has %d.%d", | ||||
cp.majorVersion, cp.minorVersion, | |||||
client.majorVersion, client.minorVersion, | |||||
defaultMajorVersion, defaultMinorVersion); | defaultMajorVersion, defaultMinorVersion); | ||||
} | } | ||||
if (cp.minorVersion != 3 && cp.minorVersion != 7 && cp.minorVersion != 8) { | |||||
if (client.minorVersion != 3 && client.minorVersion != 7 && client.minorVersion != 8) { | |||||
vlog.error("Client uses unofficial protocol version %d.%d", | vlog.error("Client uses unofficial protocol version %d.%d", | ||||
cp.majorVersion,cp.minorVersion); | |||||
if (cp.minorVersion >= 8) | |||||
cp.minorVersion = 8; | |||||
else if (cp.minorVersion == 7) | |||||
cp.minorVersion = 7; | |||||
client.majorVersion,client.minorVersion); | |||||
if (client.minorVersion >= 8) | |||||
client.minorVersion = 8; | |||||
else if (client.minorVersion == 7) | |||||
client.minorVersion = 7; | |||||
else | else | ||||
cp.minorVersion = 3; | |||||
client.minorVersion = 3; | |||||
vlog.error("Assuming compatibility with version %d.%d", | vlog.error("Assuming compatibility with version %d.%d", | ||||
cp.majorVersion,cp.minorVersion); | |||||
client.majorVersion,client.minorVersion); | |||||
} | } | ||||
versionReceived(); | versionReceived(); | ||||
std::list<rdr::U8>::iterator i; | std::list<rdr::U8>::iterator i; | ||||
secTypes = security.GetEnabledSecTypes(); | secTypes = security.GetEnabledSecTypes(); | ||||
if (cp.isVersion(3,3)) { | |||||
if (client.isVersion(3,3)) { | |||||
// cope with legacy 3.3 client only if "no authentication" or "vnc | // cope with legacy 3.3 client only if "no authentication" or "vnc | ||||
// authentication" is supported. | // authentication" is supported. | ||||
} | } | ||||
if (i == secTypes.end()) { | if (i == secTypes.end()) { | ||||
throwConnFailedException("No supported security type for %d.%d client", | throwConnFailedException("No supported security type for %d.%d client", | ||||
cp.majorVersion, cp.minorVersion); | |||||
client.majorVersion, client.minorVersion); | |||||
} | } | ||||
os->writeU32(*i); | os->writeU32(*i); | ||||
} catch (AuthFailureException& e) { | } catch (AuthFailureException& e) { | ||||
vlog.error("AuthFailureException: %s", e.str()); | vlog.error("AuthFailureException: %s", e.str()); | ||||
os->writeU32(secResultFailed); | os->writeU32(secResultFailed); | ||||
if (!cp.beforeVersion(3,8)) // 3.8 onwards have failure message | |||||
if (!client.beforeVersion(3,8)) // 3.8 onwards have failure message | |||||
os->writeString(e.str()); | os->writeString(e.str()); | ||||
os->flush(); | os->flush(); | ||||
throw; | throw; | ||||
vlog.info("Connection failed: %s", str); | vlog.info("Connection failed: %s", str); | ||||
if (state_ == RFBSTATE_PROTOCOL_VERSION) { | if (state_ == RFBSTATE_PROTOCOL_VERSION) { | ||||
if (cp.majorVersion == 3 && cp.minorVersion == 3) { | |||||
if (client.majorVersion == 3 && client.minorVersion == 3) { | |||||
os->writeU32(0); | os->writeU32(0); | ||||
os->writeString(str); | os->writeString(str); | ||||
os->flush(); | os->flush(); | ||||
if (state_ != RFBSTATE_QUERYING) | if (state_ != RFBSTATE_QUERYING) | ||||
throw Exception("SConnection::approveConnection: invalid state"); | throw Exception("SConnection::approveConnection: invalid state"); | ||||
if (!cp.beforeVersion(3,8) || ssecurity->getType() != secTypeNone) { | |||||
if (!client.beforeVersion(3,8) || ssecurity->getType() != secTypeNone) { | |||||
if (accept) { | if (accept) { | ||||
os->writeU32(secResultOK); | os->writeU32(secResultOK); | ||||
} else { | } else { | ||||
os->writeU32(secResultFailed); | os->writeU32(secResultFailed); | ||||
if (!cp.beforeVersion(3,8)) { // 3.8 onwards have failure message | |||||
if (!client.beforeVersion(3,8)) { // 3.8 onwards have failure message | |||||
if (reason) | if (reason) | ||||
os->writeString(reason); | os->writeString(reason); | ||||
else | else | ||||
if (accept) { | if (accept) { | ||||
state_ = RFBSTATE_INITIALISATION; | state_ = RFBSTATE_INITIALISATION; | ||||
reader_ = new SMsgReader(this, is); | reader_ = new SMsgReader(this, is); | ||||
writer_ = new SMsgWriter(&cp, os); | |||||
writer_ = new SMsgWriter(&client, os); | |||||
authSuccess(); | authSuccess(); | ||||
} else { | } else { | ||||
state_ = RFBSTATE_INVALID; | state_ = RFBSTATE_INVALID; | ||||
void SConnection::clientInit(bool shared) | void SConnection::clientInit(bool shared) | ||||
{ | { | ||||
writer_->writeServerInit(); | |||||
writer_->writeServerInit(client.width(), client.height(), | |||||
client.pf(), client.name()); | |||||
state_ = RFBSTATE_NORMAL; | state_ = RFBSTATE_NORMAL; | ||||
} | } | ||||
{ | { | ||||
if (!readyForSetColourMapEntries) { | if (!readyForSetColourMapEntries) { | ||||
readyForSetColourMapEntries = true; | readyForSetColourMapEntries = true; | ||||
if (!cp.pf().trueColour) { | |||||
if (!client.pf().trueColour) { | |||||
writeFakeColourMap(); | writeFakeColourMap(); | ||||
} | } | ||||
} | } | ||||
rdr::U16 red[256], green[256], blue[256]; | rdr::U16 red[256], green[256], blue[256]; | ||||
for (i = 0;i < 256;i++) | for (i = 0;i < 256;i++) | ||||
cp.pf().rgbFromPixel(i, &red[i], &green[i], &blue[i]); | |||||
client.pf().rgbFromPixel(i, &red[i], &green[i], &blue[i]); | |||||
writer()->writeSetColourMapEntries(0, 256, red, green, blue); | writer()->writeSetColourMapEntries(0, 256, red, green, blue); | ||||
} | } |
#include <rfb/Exception.h> | #include <rfb/Exception.h> | ||||
#include <rfb/SMsgHandler.h> | #include <rfb/SMsgHandler.h> | ||||
#include <rfb/ScreenSet.h> | #include <rfb/ScreenSet.h> | ||||
#include <rfb/encodings.h> | |||||
using namespace rfb; | using namespace rfb; | ||||
void SMsgHandler::setPixelFormat(const PixelFormat& pf) | void SMsgHandler::setPixelFormat(const PixelFormat& pf) | ||||
{ | { | ||||
cp.setPF(pf); | |||||
client.setPF(pf); | |||||
} | } | ||||
void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings) | void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings) | ||||
bool firstFence, firstContinuousUpdates, firstLEDState, | bool firstFence, firstContinuousUpdates, firstLEDState, | ||||
firstQEMUKeyEvent; | firstQEMUKeyEvent; | ||||
firstFence = !cp.supportsFence; | |||||
firstContinuousUpdates = !cp.supportsContinuousUpdates; | |||||
firstLEDState = !cp.supportsLEDState; | |||||
firstQEMUKeyEvent = !cp.supportsQEMUKeyEvent; | |||||
firstFence = !client.supportsFence(); | |||||
firstContinuousUpdates = !client.supportsContinuousUpdates(); | |||||
firstLEDState = !client.supportsLEDState(); | |||||
firstQEMUKeyEvent = !client.supportsEncoding(pseudoEncodingQEMUKeyEvent); | |||||
cp.setEncodings(nEncodings, encodings); | |||||
client.setEncodings(nEncodings, encodings); | |||||
supportsLocalCursor(); | supportsLocalCursor(); | ||||
if (cp.supportsFence && firstFence) | |||||
if (client.supportsFence() && firstFence) | |||||
supportsFence(); | supportsFence(); | ||||
if (cp.supportsContinuousUpdates && firstContinuousUpdates) | |||||
if (client.supportsContinuousUpdates() && firstContinuousUpdates) | |||||
supportsContinuousUpdates(); | supportsContinuousUpdates(); | ||||
if (cp.supportsLEDState && firstLEDState) | |||||
if (client.supportsLEDState() && firstLEDState) | |||||
supportsLEDState(); | supportsLEDState(); | ||||
if (cp.supportsQEMUKeyEvent && firstQEMUKeyEvent) | |||||
if (client.supportsEncoding(pseudoEncodingQEMUKeyEvent) && firstQEMUKeyEvent) | |||||
supportsQEMUKeyEvent(); | supportsQEMUKeyEvent(); | ||||
} | } | ||||
void SMsgHandler::supportsQEMUKeyEvent() | void SMsgHandler::supportsQEMUKeyEvent() | ||||
{ | { | ||||
} | } | ||||
void SMsgHandler::setDesktopSize(int fb_width, int fb_height, | |||||
const ScreenSet& layout) | |||||
{ | |||||
cp.width = fb_width; | |||||
cp.height = fb_height; | |||||
cp.screenLayout = layout; | |||||
} | |||||
#include <rdr/types.h> | #include <rdr/types.h> | ||||
#include <rfb/PixelFormat.h> | #include <rfb/PixelFormat.h> | ||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/ClientParams.h> | |||||
#include <rfb/InputHandler.h> | #include <rfb/InputHandler.h> | ||||
#include <rfb/ScreenSet.h> | #include <rfb/ScreenSet.h> | ||||
// The following methods are called as corresponding messages are read. A | // The following methods are called as corresponding messages are read. A | ||||
// derived class should override these methods as desired. Note that for | // derived class should override these methods as desired. Note that for | ||||
// the setPixelFormat(), setEncodings() and setDesktopSize() methods, a | |||||
// derived class must call on to SMsgHandler's methods. | |||||
// the setPixelFormat(), and setEncodings() methods, a derived class must | |||||
// call on to SMsgHandler's methods. | |||||
virtual void clientInit(bool shared); | virtual void clientInit(bool shared); | ||||
// handler will send a pseudo-rect back, signalling server support. | // handler will send a pseudo-rect back, signalling server support. | ||||
virtual void supportsQEMUKeyEvent(); | virtual void supportsQEMUKeyEvent(); | ||||
ConnParams cp; | |||||
ClientParams client; | |||||
}; | }; | ||||
} | } | ||||
#endif | #endif |
#include <rfb/msgTypes.h> | #include <rfb/msgTypes.h> | ||||
#include <rfb/fenceTypes.h> | #include <rfb/fenceTypes.h> | ||||
#include <rfb/Exception.h> | #include <rfb/Exception.h> | ||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/ClientParams.h> | |||||
#include <rfb/UpdateTracker.h> | #include <rfb/UpdateTracker.h> | ||||
#include <rfb/Encoder.h> | #include <rfb/Encoder.h> | ||||
#include <rfb/SMsgWriter.h> | #include <rfb/SMsgWriter.h> | ||||
static LogWriter vlog("SMsgWriter"); | static LogWriter vlog("SMsgWriter"); | ||||
SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_) | |||||
: cp(cp_), os(os_), | |||||
SMsgWriter::SMsgWriter(ClientParams* client_, rdr::OutStream* os_) | |||||
: client(client_), os(os_), | |||||
nRectsInUpdate(0), nRectsInHeader(0), | nRectsInUpdate(0), nRectsInHeader(0), | ||||
needSetDesktopSize(false), needExtendedDesktopSize(false), | |||||
needSetDesktopName(false), needSetCursor(false), | |||||
needSetXCursor(false), needSetCursorWithAlpha(false), | |||||
needSetDesktopName(false), needCursor(false), | |||||
needLEDState(false), needQEMUKeyEvent(false) | needLEDState(false), needQEMUKeyEvent(false) | ||||
{ | { | ||||
} | } | ||||
{ | { | ||||
} | } | ||||
void SMsgWriter::writeServerInit() | |||||
void SMsgWriter::writeServerInit(rdr::U16 width, rdr::U16 height, | |||||
const PixelFormat& pf, const char* name) | |||||
{ | { | ||||
os->writeU16(cp->width); | |||||
os->writeU16(cp->height); | |||||
cp->pf().write(os); | |||||
os->writeString(cp->name()); | |||||
os->writeU16(width); | |||||
os->writeU16(height); | |||||
pf.write(os); | |||||
os->writeString(name); | |||||
endMsg(); | endMsg(); | ||||
} | } | ||||
void SMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[]) | void SMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[]) | ||||
{ | { | ||||
if (!cp->supportsFence) | |||||
if (!client->supportsEncoding(pseudoEncodingFence)) | |||||
throw Exception("Client does not support fences"); | throw Exception("Client does not support fences"); | ||||
if (len > 64) | if (len > 64) | ||||
throw Exception("Too large fence payload"); | throw Exception("Too large fence payload"); | ||||
void SMsgWriter::writeEndOfContinuousUpdates() | void SMsgWriter::writeEndOfContinuousUpdates() | ||||
{ | { | ||||
if (!cp->supportsContinuousUpdates) | |||||
if (!client->supportsEncoding(pseudoEncodingContinuousUpdates)) | |||||
throw Exception("Client does not support continuous updates"); | throw Exception("Client does not support continuous updates"); | ||||
startMsg(msgTypeEndOfContinuousUpdates); | startMsg(msgTypeEndOfContinuousUpdates); | ||||
endMsg(); | endMsg(); | ||||
} | } | ||||
bool SMsgWriter::writeSetDesktopSize() { | |||||
if (!cp->supportsDesktopResize) | |||||
return false; | |||||
needSetDesktopSize = true; | |||||
return true; | |||||
} | |||||
bool SMsgWriter::writeExtendedDesktopSize() { | |||||
if (!cp->supportsExtendedDesktopSize) | |||||
return false; | |||||
needExtendedDesktopSize = true; | |||||
return true; | |||||
} | |||||
bool SMsgWriter::writeExtendedDesktopSize(rdr::U16 reason, rdr::U16 result, | |||||
int fb_width, int fb_height, | |||||
const ScreenSet& layout) { | |||||
void SMsgWriter::writeDesktopSize(rdr::U16 reason, rdr::U16 result) | |||||
{ | |||||
ExtendedDesktopSizeMsg msg; | ExtendedDesktopSizeMsg msg; | ||||
if (!cp->supportsExtendedDesktopSize) | |||||
return false; | |||||
if (!client->supportsEncoding(pseudoEncodingDesktopSize) && | |||||
!client->supportsEncoding(pseudoEncodingExtendedDesktopSize)) | |||||
throw Exception("Client does not support desktop size changes"); | |||||
msg.reason = reason; | msg.reason = reason; | ||||
msg.result = result; | msg.result = result; | ||||
msg.fb_width = fb_width; | |||||
msg.fb_height = fb_height; | |||||
msg.layout = layout; | |||||
extendedDesktopSizeMsgs.push_back(msg); | extendedDesktopSizeMsgs.push_back(msg); | ||||
return true; | |||||
} | } | ||||
bool SMsgWriter::writeSetDesktopName() { | |||||
if (!cp->supportsDesktopRename) | |||||
return false; | |||||
needSetDesktopName = true; | |||||
return true; | |||||
} | |||||
bool SMsgWriter::writeSetCursor() | |||||
{ | |||||
if (!cp->supportsLocalCursor) | |||||
return false; | |||||
needSetCursor = true; | |||||
return true; | |||||
} | |||||
bool SMsgWriter::writeSetXCursor() | |||||
void SMsgWriter::writeSetDesktopName() | |||||
{ | { | ||||
if (!cp->supportsLocalXCursor) | |||||
return false; | |||||
needSetXCursor = true; | |||||
if (!client->supportsEncoding(pseudoEncodingDesktopName)) | |||||
throw Exception("Client does not support desktop name changes"); | |||||
return true; | |||||
needSetDesktopName = true; | |||||
} | } | ||||
bool SMsgWriter::writeSetCursorWithAlpha() | |||||
void SMsgWriter::writeCursor() | |||||
{ | { | ||||
if (!cp->supportsLocalCursorWithAlpha) | |||||
return false; | |||||
if (!client->supportsEncoding(pseudoEncodingCursor) && | |||||
!client->supportsEncoding(pseudoEncodingXCursor) && | |||||
!client->supportsEncoding(pseudoEncodingCursorWithAlpha)) | |||||
throw Exception("Client does not support local cursor"); | |||||
needSetCursorWithAlpha = true; | |||||
return true; | |||||
needCursor = true; | |||||
} | } | ||||
bool SMsgWriter::writeLEDState() | |||||
void SMsgWriter::writeLEDState() | |||||
{ | { | ||||
if (!cp->supportsLEDState) | |||||
return false; | |||||
if (cp->ledState() == ledUnknown) | |||||
return false; | |||||
if (!client->supportsEncoding(pseudoEncodingLEDState)) | |||||
throw Exception("Client does not support LED state"); | |||||
if (client->ledState() == ledUnknown) | |||||
throw Exception("Server has not specified LED state"); | |||||
needLEDState = true; | needLEDState = true; | ||||
return true; | |||||
} | } | ||||
bool SMsgWriter::writeQEMUKeyEvent() | |||||
void SMsgWriter::writeQEMUKeyEvent() | |||||
{ | { | ||||
if (!cp->supportsQEMUKeyEvent) | |||||
return false; | |||||
if (!client->supportsEncoding(pseudoEncodingQEMUKeyEvent)) | |||||
throw Exception("Client does not support QEMU key events"); | |||||
needQEMUKeyEvent = true; | needQEMUKeyEvent = true; | ||||
return true; | |||||
} | } | ||||
bool SMsgWriter::needFakeUpdate() | bool SMsgWriter::needFakeUpdate() | ||||
{ | { | ||||
if (needSetDesktopName) | if (needSetDesktopName) | ||||
return true; | return true; | ||||
if (needSetCursor || needSetXCursor || needSetCursorWithAlpha) | |||||
if (needCursor) | |||||
return true; | return true; | ||||
if (needLEDState) | if (needLEDState) | ||||
return true; | return true; | ||||
bool SMsgWriter::needNoDataUpdate() | bool SMsgWriter::needNoDataUpdate() | ||||
{ | { | ||||
if (needSetDesktopSize) | |||||
return true; | |||||
if (needExtendedDesktopSize || !extendedDesktopSizeMsgs.empty()) | |||||
if (!extendedDesktopSizeMsgs.empty()) | |||||
return true; | return true; | ||||
return false; | return false; | ||||
nRects = 0; | nRects = 0; | ||||
if (needSetDesktopSize) | |||||
nRects++; | |||||
if (needExtendedDesktopSize) | |||||
nRects++; | |||||
if (!extendedDesktopSizeMsgs.empty()) | |||||
nRects += extendedDesktopSizeMsgs.size(); | |||||
if (!extendedDesktopSizeMsgs.empty()) { | |||||
if (client->supportsEncoding(pseudoEncodingExtendedDesktopSize)) | |||||
nRects += extendedDesktopSizeMsgs.size(); | |||||
else | |||||
nRects++; | |||||
} | |||||
writeFramebufferUpdateStart(nRects); | writeFramebufferUpdateStart(nRects); | ||||
writeNoDataRects(); | writeNoDataRects(); | ||||
if (nRects != 0xFFFF) { | if (nRects != 0xFFFF) { | ||||
if (needSetDesktopName) | if (needSetDesktopName) | ||||
nRects++; | nRects++; | ||||
if (needSetCursor) | |||||
nRects++; | |||||
if (needSetXCursor) | |||||
nRects++; | |||||
if (needSetCursorWithAlpha) | |||||
if (needCursor) | |||||
nRects++; | nRects++; | ||||
if (needLEDState) | if (needLEDState) | ||||
nRects++; | nRects++; | ||||
void SMsgWriter::writePseudoRects() | void SMsgWriter::writePseudoRects() | ||||
{ | { | ||||
if (needSetCursor) { | |||||
const Cursor& cursor = cp->cursor(); | |||||
rdr::U8Array data(cursor.width()*cursor.height() * (cp->pf().bpp/8)); | |||||
rdr::U8Array mask(cursor.getMask()); | |||||
const rdr::U8* in; | |||||
rdr::U8* out; | |||||
in = cursor.getBuffer(); | |||||
out = data.buf; | |||||
for (int i = 0;i < cursor.width()*cursor.height();i++) { | |||||
cp->pf().bufferFromRGB(out, in, 1); | |||||
in += 4; | |||||
out += cp->pf().bpp/8; | |||||
if (needCursor) { | |||||
const Cursor& cursor = client->cursor(); | |||||
if (client->supportsEncoding(pseudoEncodingCursorWithAlpha)) { | |||||
writeSetCursorWithAlphaRect(cursor.width(), cursor.height(), | |||||
cursor.hotspot().x, cursor.hotspot().y, | |||||
cursor.getBuffer()); | |||||
} else if (client->supportsEncoding(pseudoEncodingCursor)) { | |||||
rdr::U8Array data(cursor.width()*cursor.height() * (client->pf().bpp/8)); | |||||
rdr::U8Array mask(cursor.getMask()); | |||||
const rdr::U8* in; | |||||
rdr::U8* out; | |||||
in = cursor.getBuffer(); | |||||
out = data.buf; | |||||
for (int i = 0;i < cursor.width()*cursor.height();i++) { | |||||
client->pf().bufferFromRGB(out, in, 1); | |||||
in += 4; | |||||
out += client->pf().bpp/8; | |||||
} | |||||
writeSetCursorRect(cursor.width(), cursor.height(), | |||||
cursor.hotspot().x, cursor.hotspot().y, | |||||
data.buf, mask.buf); | |||||
} else if (client->supportsEncoding(pseudoEncodingXCursor)) { | |||||
rdr::U8Array bitmap(cursor.getBitmap()); | |||||
rdr::U8Array mask(cursor.getMask()); | |||||
writeSetXCursorRect(cursor.width(), cursor.height(), | |||||
cursor.hotspot().x, cursor.hotspot().y, | |||||
bitmap.buf, mask.buf); | |||||
} else { | |||||
throw Exception("Client does not support local cursor"); | |||||
} | } | ||||
writeSetCursorRect(cursor.width(), cursor.height(), | |||||
cursor.hotspot().x, cursor.hotspot().y, | |||||
data.buf, mask.buf); | |||||
needSetCursor = false; | |||||
} | |||||
if (needSetXCursor) { | |||||
const Cursor& cursor = cp->cursor(); | |||||
rdr::U8Array bitmap(cursor.getBitmap()); | |||||
rdr::U8Array mask(cursor.getMask()); | |||||
writeSetXCursorRect(cursor.width(), cursor.height(), | |||||
cursor.hotspot().x, cursor.hotspot().y, | |||||
bitmap.buf, mask.buf); | |||||
needSetXCursor = false; | |||||
} | |||||
if (needSetCursorWithAlpha) { | |||||
const Cursor& cursor = cp->cursor(); | |||||
writeSetCursorWithAlphaRect(cursor.width(), cursor.height(), | |||||
cursor.hotspot().x, cursor.hotspot().y, | |||||
cursor.getBuffer()); | |||||
needSetCursorWithAlpha = false; | |||||
needCursor = false; | |||||
} | } | ||||
if (needSetDesktopName) { | if (needSetDesktopName) { | ||||
writeSetDesktopNameRect(cp->name()); | |||||
writeSetDesktopNameRect(client->name()); | |||||
needSetDesktopName = false; | needSetDesktopName = false; | ||||
} | } | ||||
if (needLEDState) { | if (needLEDState) { | ||||
writeLEDStateRect(cp->ledState()); | |||||
writeLEDStateRect(client->ledState()); | |||||
needLEDState = false; | needLEDState = false; | ||||
} | } | ||||
void SMsgWriter::writeNoDataRects() | void SMsgWriter::writeNoDataRects() | ||||
{ | { | ||||
// Start with specific ExtendedDesktopSize messages | |||||
if (!extendedDesktopSizeMsgs.empty()) { | if (!extendedDesktopSizeMsgs.empty()) { | ||||
std::list<ExtendedDesktopSizeMsg>::const_iterator ri; | |||||
for (ri = extendedDesktopSizeMsgs.begin();ri != extendedDesktopSizeMsgs.end();++ri) { | |||||
writeExtendedDesktopSizeRect(ri->reason, ri->result, | |||||
ri->fb_width, ri->fb_height, ri->layout); | |||||
if (client->supportsEncoding(pseudoEncodingExtendedDesktopSize)) { | |||||
std::list<ExtendedDesktopSizeMsg>::const_iterator ri; | |||||
for (ri = extendedDesktopSizeMsgs.begin();ri != extendedDesktopSizeMsgs.end();++ri) { | |||||
// FIXME: We can probably skip multiple reasonServer entries | |||||
writeExtendedDesktopSizeRect(ri->reason, ri->result, | |||||
client->width(), client->height(), | |||||
client->screenLayout()); | |||||
} | |||||
} else if (client->supportsEncoding(pseudoEncodingDesktopSize)) { | |||||
// Some clients assume this is the last rectangle so don't send anything | |||||
// more after this | |||||
writeSetDesktopSizeRect(client->width(), client->height()); | |||||
} else { | |||||
throw Exception("Client does not support desktop size changes"); | |||||
} | } | ||||
extendedDesktopSizeMsgs.clear(); | extendedDesktopSizeMsgs.clear(); | ||||
} | } | ||||
// Send this before SetDesktopSize to make life easier on the clients | |||||
if (needExtendedDesktopSize) { | |||||
writeExtendedDesktopSizeRect(0, 0, cp->width, cp->height, | |||||
cp->screenLayout); | |||||
needExtendedDesktopSize = false; | |||||
} | |||||
// Some clients assume this is the last rectangle so don't send anything | |||||
// more after this | |||||
if (needSetDesktopSize) { | |||||
writeSetDesktopSizeRect(cp->width, cp->height); | |||||
needSetDesktopSize = false; | |||||
} | |||||
} | } | ||||
void SMsgWriter::writeSetDesktopSizeRect(int width, int height) | void SMsgWriter::writeSetDesktopSizeRect(int width, int height) | ||||
{ | { | ||||
if (!cp->supportsDesktopResize) | |||||
if (!client->supportsEncoding(pseudoEncodingDesktopSize)) | |||||
throw Exception("Client does not support desktop resize"); | throw Exception("Client does not support desktop resize"); | ||||
if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | ||||
throw Exception("SMsgWriter::writeSetDesktopSizeRect: nRects out of sync"); | throw Exception("SMsgWriter::writeSetDesktopSizeRect: nRects out of sync"); | ||||
{ | { | ||||
ScreenSet::const_iterator si; | ScreenSet::const_iterator si; | ||||
if (!cp->supportsExtendedDesktopSize) | |||||
if (!client->supportsEncoding(pseudoEncodingExtendedDesktopSize)) | |||||
throw Exception("Client does not support extended desktop resize"); | throw Exception("Client does not support extended desktop resize"); | ||||
if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | ||||
throw Exception("SMsgWriter::writeExtendedDesktopSizeRect: nRects out of sync"); | throw Exception("SMsgWriter::writeExtendedDesktopSizeRect: nRects out of sync"); | ||||
void SMsgWriter::writeSetDesktopNameRect(const char *name) | void SMsgWriter::writeSetDesktopNameRect(const char *name) | ||||
{ | { | ||||
if (!cp->supportsDesktopRename) | |||||
if (!client->supportsEncoding(pseudoEncodingDesktopName)) | |||||
throw Exception("Client does not support desktop rename"); | throw Exception("Client does not support desktop rename"); | ||||
if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | ||||
throw Exception("SMsgWriter::writeSetDesktopNameRect: nRects out of sync"); | throw Exception("SMsgWriter::writeSetDesktopNameRect: nRects out of sync"); | ||||
int hotspotX, int hotspotY, | int hotspotX, int hotspotY, | ||||
const void* data, const void* mask) | const void* data, const void* mask) | ||||
{ | { | ||||
if (!cp->supportsLocalCursor) | |||||
if (!client->supportsEncoding(pseudoEncodingCursor)) | |||||
throw Exception("Client does not support local cursors"); | throw Exception("Client does not support local cursors"); | ||||
if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | ||||
throw Exception("SMsgWriter::writeSetCursorRect: nRects out of sync"); | throw Exception("SMsgWriter::writeSetCursorRect: nRects out of sync"); | ||||
os->writeU16(width); | os->writeU16(width); | ||||
os->writeU16(height); | os->writeU16(height); | ||||
os->writeU32(pseudoEncodingCursor); | os->writeU32(pseudoEncodingCursor); | ||||
os->writeBytes(data, width * height * (cp->pf().bpp/8)); | |||||
os->writeBytes(data, width * height * (client->pf().bpp/8)); | |||||
os->writeBytes(mask, (width+7)/8 * height); | os->writeBytes(mask, (width+7)/8 * height); | ||||
} | } | ||||
int hotspotX, int hotspotY, | int hotspotX, int hotspotY, | ||||
const void* data, const void* mask) | const void* data, const void* mask) | ||||
{ | { | ||||
if (!cp->supportsLocalXCursor) | |||||
if (!client->supportsEncoding(pseudoEncodingXCursor)) | |||||
throw Exception("Client does not support local cursors"); | throw Exception("Client does not support local cursors"); | ||||
if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | ||||
throw Exception("SMsgWriter::writeSetXCursorRect: nRects out of sync"); | throw Exception("SMsgWriter::writeSetXCursorRect: nRects out of sync"); | ||||
int hotspotX, int hotspotY, | int hotspotX, int hotspotY, | ||||
const rdr::U8* data) | const rdr::U8* data) | ||||
{ | { | ||||
if (!cp->supportsLocalCursorWithAlpha) | |||||
if (!client->supportsEncoding(pseudoEncodingCursorWithAlpha)) | |||||
throw Exception("Client does not support local cursors"); | throw Exception("Client does not support local cursors"); | ||||
if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | ||||
throw Exception("SMsgWriter::writeSetCursorWithAlphaRect: nRects out of sync"); | throw Exception("SMsgWriter::writeSetCursorWithAlphaRect: nRects out of sync"); | ||||
void SMsgWriter::writeLEDStateRect(rdr::U8 state) | void SMsgWriter::writeLEDStateRect(rdr::U8 state) | ||||
{ | { | ||||
if (!cp->supportsLEDState) | |||||
if (!client->supportsEncoding(pseudoEncodingLEDState)) | |||||
throw Exception("Client does not support LED state updates"); | throw Exception("Client does not support LED state updates"); | ||||
if (cp->ledState() == ledUnknown) | |||||
if (client->ledState() == ledUnknown) | |||||
throw Exception("Server does not support LED state updates"); | throw Exception("Server does not support LED state updates"); | ||||
if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | ||||
throw Exception("SMsgWriter::writeLEDStateRect: nRects out of sync"); | throw Exception("SMsgWriter::writeLEDStateRect: nRects out of sync"); | ||||
void SMsgWriter::writeQEMUKeyEventRect() | void SMsgWriter::writeQEMUKeyEventRect() | ||||
{ | { | ||||
if (!cp->supportsQEMUKeyEvent) | |||||
if (!client->supportsEncoding(pseudoEncodingQEMUKeyEvent)) | |||||
throw Exception("Client does not support QEMU extended key events"); | throw Exception("Client does not support QEMU extended key events"); | ||||
if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) | ||||
throw Exception("SMsgWriter::writeQEMUKeyEventRect: nRects out of sync"); | throw Exception("SMsgWriter::writeQEMUKeyEventRect: nRects out of sync"); |
namespace rfb { | namespace rfb { | ||||
class ConnParams; | |||||
class ClientParams; | |||||
class PixelFormat; | |||||
struct ScreenSet; | struct ScreenSet; | ||||
class SMsgWriter { | class SMsgWriter { | ||||
public: | public: | ||||
SMsgWriter(ConnParams* cp, rdr::OutStream* os); | |||||
SMsgWriter(ClientParams* client, rdr::OutStream* os); | |||||
virtual ~SMsgWriter(); | virtual ~SMsgWriter(); | ||||
// writeServerInit() must only be called at the appropriate time in the | // writeServerInit() must only be called at the appropriate time in the | ||||
// protocol initialisation. | // protocol initialisation. | ||||
void writeServerInit(); | |||||
void writeServerInit(rdr::U16 width, rdr::U16 height, | |||||
const PixelFormat& pf, const char* name); | |||||
// Methods to write normal protocol messages | // Methods to write normal protocol messages | ||||
// updates mode. | // updates mode. | ||||
void writeEndOfContinuousUpdates(); | void writeEndOfContinuousUpdates(); | ||||
// writeSetDesktopSize() won't actually write immediately, but will | |||||
// writeDesktopSize() won't actually write immediately, but will | |||||
// write the relevant pseudo-rectangle as part of the next update. | // write the relevant pseudo-rectangle as part of the next update. | ||||
bool writeSetDesktopSize(); | |||||
// Same thing for the extended version. The first version queues up a | |||||
// generic update of the current server state, but the second queues a | |||||
// specific message. | |||||
bool writeExtendedDesktopSize(); | |||||
bool writeExtendedDesktopSize(rdr::U16 reason, rdr::U16 result, | |||||
int fb_width, int fb_height, | |||||
const ScreenSet& layout); | |||||
void writeDesktopSize(rdr::U16 reason, rdr::U16 result=0); | |||||
bool writeSetDesktopName(); | |||||
void writeSetDesktopName(); | |||||
// Like setDesktopSize, we can't just write out a cursor message | // Like setDesktopSize, we can't just write out a cursor message | ||||
// immediately. | // immediately. | ||||
bool writeSetCursor(); | |||||
bool writeSetXCursor(); | |||||
bool writeSetCursorWithAlpha(); | |||||
void writeCursor(); | |||||
// Same for LED state message | // Same for LED state message | ||||
bool writeLEDState(); | |||||
void writeLEDState(); | |||||
// And QEMU keyboard event handshake | // And QEMU keyboard event handshake | ||||
bool writeQEMUKeyEvent(); | |||||
void writeQEMUKeyEvent(); | |||||
// needFakeUpdate() returns true when an immediate update is needed in | // needFakeUpdate() returns true when an immediate update is needed in | ||||
// order to flush out pseudo-rectangles to the client. | // order to flush out pseudo-rectangles to the client. | ||||
void writeLEDStateRect(rdr::U8 state); | void writeLEDStateRect(rdr::U8 state); | ||||
void writeQEMUKeyEventRect(); | void writeQEMUKeyEventRect(); | ||||
ConnParams* cp; | |||||
ClientParams* client; | |||||
rdr::OutStream* os; | rdr::OutStream* os; | ||||
int nRectsInUpdate; | int nRectsInUpdate; | ||||
int nRectsInHeader; | int nRectsInHeader; | ||||
bool needSetDesktopSize; | |||||
bool needExtendedDesktopSize; | |||||
bool needSetDesktopName; | bool needSetDesktopName; | ||||
bool needSetCursor; | |||||
bool needSetXCursor; | |||||
bool needSetCursorWithAlpha; | |||||
bool needCursor; | |||||
bool needLEDState; | bool needLEDState; | ||||
bool needQEMUKeyEvent; | bool needQEMUKeyEvent; | ||||
typedef struct { | typedef struct { | ||||
rdr::U16 reason, result; | rdr::U16 reason, result; | ||||
int fb_width, fb_height; | |||||
ScreenSet layout; | |||||
} ExtendedDesktopSizeMsg; | } ExtendedDesktopSizeMsg; | ||||
std::list<ExtendedDesktopSizeMsg> extendedDesktopSizeMsgs; | std::list<ExtendedDesktopSizeMsg> extendedDesktopSizeMsgs; |
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||||
* Copyright (C) 2011 D. R. Commander. All Rights Reserved. | |||||
* Copyright 2014-2018 Pierre Ossman 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 <rfb/Exception.h> | |||||
#include <rfb/ledStates.h> | |||||
#include <rfb/ServerParams.h> | |||||
using namespace rfb; | |||||
ServerParams::ServerParams() | |||||
: majorVersion(0), minorVersion(0), | |||||
supportsQEMUKeyEvent(false), | |||||
supportsSetDesktopSize(false), supportsFence(false), | |||||
supportsContinuousUpdates(false), | |||||
width_(0), height_(0), name_(0), | |||||
ledState_(ledUnknown) | |||||
{ | |||||
setName(""); | |||||
cursor_ = new Cursor(0, 0, Point(), NULL); | |||||
} | |||||
ServerParams::~ServerParams() | |||||
{ | |||||
delete [] name_; | |||||
delete cursor_; | |||||
} | |||||
void ServerParams::setDimensions(int width, int height) | |||||
{ | |||||
ScreenSet layout; | |||||
layout.add_screen(rfb::Screen(0, 0, 0, width, height, 0)); | |||||
setDimensions(width, height, layout); | |||||
} | |||||
void ServerParams::setDimensions(int width, int height, const ScreenSet& layout) | |||||
{ | |||||
if (!layout.validate(width, height)) | |||||
throw Exception("Attempted to configure an invalid screen layout"); | |||||
width_ = width; | |||||
height_ = height; | |||||
screenLayout_ = layout; | |||||
} | |||||
void ServerParams::setPF(const PixelFormat& pf) | |||||
{ | |||||
pf_ = pf; | |||||
if (pf.bpp != 8 && pf.bpp != 16 && pf.bpp != 32) | |||||
throw Exception("setPF: not 8, 16 or 32 bpp?"); | |||||
} | |||||
void ServerParams::setName(const char* name) | |||||
{ | |||||
delete [] name_; | |||||
name_ = strDup(name); | |||||
} | |||||
void ServerParams::setCursor(const Cursor& other) | |||||
{ | |||||
delete cursor_; | |||||
cursor_ = new Cursor(other); | |||||
} | |||||
void ServerParams::setLEDState(unsigned int state) | |||||
{ | |||||
ledState_ = state; | |||||
} |
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||||
* Copyright 2014-2018 Pierre Ossman 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. | |||||
*/ | |||||
// | |||||
// ServerParams - structure describing the current state of the remote server | |||||
// | |||||
#ifndef __RFB_SERVERPARAMS_H__ | |||||
#define __RFB_SERVERPARAMS_H__ | |||||
#include <rfb/Cursor.h> | |||||
#include <rfb/PixelFormat.h> | |||||
#include <rfb/ScreenSet.h> | |||||
namespace rfb { | |||||
class ServerParams { | |||||
public: | |||||
ServerParams(); | |||||
~ServerParams(); | |||||
int majorVersion; | |||||
int minorVersion; | |||||
void setVersion(int major, int minor) { | |||||
majorVersion = major; minorVersion = minor; | |||||
} | |||||
bool isVersion(int major, int minor) const { | |||||
return majorVersion == major && minorVersion == minor; | |||||
} | |||||
bool beforeVersion(int major, int minor) const { | |||||
return (majorVersion < major || | |||||
(majorVersion == major && minorVersion < minor)); | |||||
} | |||||
bool afterVersion(int major, int minor) const { | |||||
return !beforeVersion(major,minor+1); | |||||
} | |||||
const int width() const { return width_; } | |||||
const int height() const { return height_; } | |||||
const ScreenSet& screenLayout() const { return screenLayout_; } | |||||
void setDimensions(int width, int height); | |||||
void setDimensions(int width, int height, const ScreenSet& layout); | |||||
const PixelFormat& pf() const { return pf_; } | |||||
void setPF(const PixelFormat& pf); | |||||
const char* name() const { return name_; } | |||||
void setName(const char* name); | |||||
const Cursor& cursor() const { return *cursor_; } | |||||
void setCursor(const Cursor& cursor); | |||||
unsigned int ledState() { return ledState_; } | |||||
void setLEDState(unsigned int state); | |||||
bool supportsQEMUKeyEvent; | |||||
bool supportsSetDesktopSize; | |||||
bool supportsFence; | |||||
bool supportsContinuousUpdates; | |||||
private: | |||||
int width_; | |||||
int height_; | |||||
ScreenSet screenLayout_; | |||||
PixelFormat pf_; | |||||
char* name_; | |||||
Cursor* cursor_; | |||||
unsigned int ledState_; | |||||
}; | |||||
} | |||||
#endif |
#include <rdr/MemInStream.h> | #include <rdr/MemInStream.h> | ||||
#include <rdr/OutStream.h> | #include <rdr/OutStream.h> | ||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/ServerParams.h> | |||||
#include <rfb/Exception.h> | #include <rfb/Exception.h> | ||||
#include <rfb/PixelBuffer.h> | #include <rfb/PixelBuffer.h> | ||||
#include <rfb/TightConstants.h> | #include <rfb/TightConstants.h> | ||||
} | } | ||||
void TightDecoder::readRect(const Rect& r, rdr::InStream* is, | void TightDecoder::readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os) | |||||
const ServerParams& server, rdr::OutStream* os) | |||||
{ | { | ||||
rdr::U8 comp_ctl; | rdr::U8 comp_ctl; | ||||
// "Fill" compression type. | // "Fill" compression type. | ||||
if (comp_ctl == tightFill) { | if (comp_ctl == tightFill) { | ||||
if (cp.pf().is888()) | |||||
if (server.pf().is888()) | |||||
os->copyBytes(is, 3); | os->copyBytes(is, 3); | ||||
else | else | ||||
os->copyBytes(is, cp.pf().bpp/8); | |||||
os->copyBytes(is, server.pf().bpp/8); | |||||
return; | return; | ||||
} | } | ||||
palSize = is->readU8() + 1; | palSize = is->readU8() + 1; | ||||
os->writeU8(palSize - 1); | os->writeU8(palSize - 1); | ||||
if (cp.pf().is888()) | |||||
if (server.pf().is888()) | |||||
os->copyBytes(is, palSize * 3); | os->copyBytes(is, palSize * 3); | ||||
else | else | ||||
os->copyBytes(is, palSize * cp.pf().bpp/8); | |||||
os->copyBytes(is, palSize * server.pf().bpp/8); | |||||
break; | break; | ||||
case tightFilterGradient: | case tightFilterGradient: | ||||
if (cp.pf().bpp == 8) | |||||
if (server.pf().bpp == 8) | |||||
throw Exception("TightDecoder: invalid BPP for gradient filter"); | throw Exception("TightDecoder: invalid BPP for gradient filter"); | ||||
break; | break; | ||||
case tightFilterCopy: | case tightFilterCopy: | ||||
rowSize = (r.width() + 7) / 8; | rowSize = (r.width() + 7) / 8; | ||||
else | else | ||||
rowSize = r.width(); | rowSize = r.width(); | ||||
} else if (cp.pf().is888()) { | |||||
} else if (server.pf().is888()) { | |||||
rowSize = r.width() * 3; | rowSize = r.width() * 3; | ||||
} else { | } else { | ||||
rowSize = r.width() * cp.pf().bpp/8; | |||||
rowSize = r.width() * server.pf().bpp/8; | |||||
} | } | ||||
dataSize = r.height() * rowSize; | dataSize = r.height() * rowSize; | ||||
const Rect& rectB, | const Rect& rectB, | ||||
const void* bufferB, | const void* bufferB, | ||||
size_t buflenB, | size_t buflenB, | ||||
const ConnParams& cp) | |||||
const ServerParams& server) | |||||
{ | { | ||||
rdr::U8 comp_ctl_a, comp_ctl_b; | rdr::U8 comp_ctl_a, comp_ctl_b; | ||||
} | } | ||||
void TightDecoder::decodeRect(const Rect& r, const void* buffer, | void TightDecoder::decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb) | ModifiablePixelBuffer* pb) | ||||
{ | { | ||||
const rdr::U8* bufptr; | const rdr::U8* bufptr; | ||||
const PixelFormat& pf = cp.pf(); | |||||
const PixelFormat& pf = server.pf(); | |||||
rdr::U8 comp_ctl; | rdr::U8 comp_ctl; | ||||
TightDecoder(); | TightDecoder(); | ||||
virtual ~TightDecoder(); | virtual ~TightDecoder(); | ||||
virtual void readRect(const Rect& r, rdr::InStream* is, | virtual void readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os); | |||||
const ServerParams& server, rdr::OutStream* os); | |||||
virtual bool doRectsConflict(const Rect& rectA, | virtual bool doRectsConflict(const Rect& rectA, | ||||
const void* bufferA, | const void* bufferA, | ||||
size_t buflenA, | size_t buflenA, | ||||
const Rect& rectB, | const Rect& rectB, | ||||
const void* bufferB, | const void* bufferB, | ||||
size_t buflenB, | size_t buflenB, | ||||
const ConnParams& cp); | |||||
const ServerParams& server); | |||||
virtual void decodeRect(const Rect& r, const void* buffer, | virtual void decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb); | ModifiablePixelBuffer* pb); | ||||
private: | private: |
#include <rfb/PixelBuffer.h> | #include <rfb/PixelBuffer.h> | ||||
#include <rfb/Palette.h> | #include <rfb/Palette.h> | ||||
#include <rfb/encodings.h> | #include <rfb/encodings.h> | ||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/SConnection.h> | #include <rfb/SConnection.h> | ||||
#include <rfb/TightEncoder.h> | #include <rfb/TightEncoder.h> | ||||
#include <rfb/TightConstants.h> | #include <rfb/TightConstants.h> | ||||
bool TightEncoder::isSupported() | bool TightEncoder::isSupported() | ||||
{ | { | ||||
return conn->cp.supportsEncoding(encodingTight); | |||||
return conn->client.supportsEncoding(encodingTight); | |||||
} | } | ||||
void TightEncoder::setCompressLevel(int level) | void TightEncoder::setCompressLevel(int level) |
bool TightJPEGEncoder::isSupported() | bool TightJPEGEncoder::isSupported() | ||||
{ | { | ||||
if (!conn->cp.supportsEncoding(encodingTight)) | |||||
if (!conn->client.supportsEncoding(encodingTight)) | |||||
return false; | return false; | ||||
// Any one of these indicates support for JPEG | // Any one of these indicates support for JPEG | ||||
if (conn->cp.qualityLevel != -1) | |||||
if (conn->client.qualityLevel != -1) | |||||
return true; | return true; | ||||
if (conn->cp.fineQualityLevel != -1) | |||||
if (conn->client.fineQualityLevel != -1) | |||||
return true; | return true; | ||||
if (conn->cp.subsampling != -1) | |||||
if (conn->client.subsampling != -1) | |||||
return true; | return true; | ||||
// Tight support, but not JPEG | // Tight support, but not JPEG |
// SimpleUpdateTracker | // SimpleUpdateTracker | ||||
SimpleUpdateTracker::SimpleUpdateTracker(bool use_copyrect) { | |||||
copy_enabled = use_copyrect; | |||||
SimpleUpdateTracker::SimpleUpdateTracker() { | |||||
} | } | ||||
SimpleUpdateTracker::~SimpleUpdateTracker() { | SimpleUpdateTracker::~SimpleUpdateTracker() { | ||||
} | } | ||||
void SimpleUpdateTracker::enable_copyrect(bool enable) { | |||||
if (!enable && copy_enabled) { | |||||
add_changed(copied); | |||||
copied.clear(); | |||||
} | |||||
copy_enabled=enable; | |||||
} | |||||
void SimpleUpdateTracker::add_changed(const Region ®ion) { | void SimpleUpdateTracker::add_changed(const Region ®ion) { | ||||
changed.assign_union(region); | changed.assign_union(region); | ||||
} | } | ||||
void SimpleUpdateTracker::add_copied(const Region &dest, const Point &delta) { | void SimpleUpdateTracker::add_copied(const Region &dest, const Point &delta) { | ||||
// Do we support copyrect? | |||||
if (!copy_enabled) { | |||||
add_changed(dest); | |||||
return; | |||||
} | |||||
// Is there anything to do? | // Is there anything to do? | ||||
if (dest.is_empty()) return; | if (dest.is_empty()) return; | ||||
class SimpleUpdateTracker : public UpdateTracker { | class SimpleUpdateTracker : public UpdateTracker { | ||||
public: | public: | ||||
SimpleUpdateTracker(bool use_copyrect=true); | |||||
SimpleUpdateTracker(); | |||||
virtual ~SimpleUpdateTracker(); | virtual ~SimpleUpdateTracker(); | ||||
virtual void enable_copyrect(bool enable); | |||||
virtual void add_changed(const Region ®ion); | virtual void add_changed(const Region ®ion); | ||||
virtual void add_copied(const Region &dest, const Point &delta); | virtual void add_copied(const Region &dest, const Point &delta); | ||||
virtual void subtract(const Region& region); | virtual void subtract(const Region& region); | ||||
Region changed; | Region changed; | ||||
Region copied; | Region copied; | ||||
Point copy_delta; | Point copy_delta; | ||||
bool copy_enabled; | |||||
}; | }; | ||||
} | } |
inProcessMessages(false), | inProcessMessages(false), | ||||
pendingSyncFence(false), syncFence(false), fenceFlags(0), | pendingSyncFence(false), syncFence(false), fenceFlags(0), | ||||
fenceDataLen(0), fenceData(NULL), congestionTimer(this), | fenceDataLen(0), fenceData(NULL), congestionTimer(this), | ||||
losslessTimer(this), server(server_), updates(false), | |||||
losslessTimer(this), server(server_), | |||||
updateRenderedCursor(false), removeRenderedCursor(false), | updateRenderedCursor(false), removeRenderedCursor(false), | ||||
continuousUpdates(false), encodeManager(this), idleTimer(this), | continuousUpdates(false), encodeManager(this), idleTimer(this), | ||||
pointerEventTime(0), clientHasCursor(false) | pointerEventTime(0), clientHasCursor(false) | ||||
{ | { | ||||
try { | try { | ||||
if (!authenticated()) return; | if (!authenticated()) return; | ||||
if (cp.width && cp.height && | |||||
(server->getPixelBuffer()->width() != cp.width || | |||||
server->getPixelBuffer()->height() != cp.height)) | |||||
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 | // 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 | // extra bits if it's bigger. If we wanted to do this exactly, something | ||||
//updates.intersect(server->pb->getRect()); | //updates.intersect(server->pb->getRect()); | ||||
// | // | ||||
//if (server->pb->width() > cp.width) | |||||
// updates.add_changed(Rect(cp.width, 0, server->pb->width(), | |||||
//if (server->pb->width() > client.width()) | |||||
// updates.add_changed(Rect(client.width(), 0, server->pb->width(), | |||||
// server->pb->height())); | // server->pb->height())); | ||||
//if (server->pb->height() > cp.height) | |||||
// updates.add_changed(Rect(0, cp.height, cp.width, | |||||
//if (server->pb->height() > client.height()) | |||||
// updates.add_changed(Rect(0, client.height(), client.width(), | |||||
// server->pb->height())); | // server->pb->height())); | ||||
damagedCursorRegion.assign_intersect(server->getPixelBuffer()->getRect()); | damagedCursorRegion.assign_intersect(server->getPixelBuffer()->getRect()); | ||||
cp.width = server->getPixelBuffer()->width(); | |||||
cp.height = server->getPixelBuffer()->height(); | |||||
cp.screenLayout = server->getScreenLayout(); | |||||
client.setDimensions(server->getPixelBuffer()->width(), | |||||
server->getPixelBuffer()->height(), | |||||
server->getScreenLayout()); | |||||
if (state() == RFBSTATE_NORMAL) { | if (state() == RFBSTATE_NORMAL) { | ||||
// We should only send EDS to client asking for both | |||||
if (!writer()->writeExtendedDesktopSize()) { | |||||
if (!writer()->writeSetDesktopSize()) { | |||||
close("Client does not support desktop resize"); | |||||
return; | |||||
} | |||||
if (!client.supportsDesktopSize()) { | |||||
close("Client does not support desktop resize"); | |||||
return; | |||||
} | } | ||||
writer()->writeDesktopSize(reasonServer); | |||||
} | } | ||||
// Drop any lossy tracking that is now outside the framebuffer | // Drop any lossy tracking that is now outside the framebuffer | ||||
// We interpret a low compression level as an indication that the client | // We interpret a low compression level as an indication that the client | ||||
// wants to prioritise CPU usage over bandwidth, and hence disable the | // wants to prioritise CPU usage over bandwidth, and hence disable the | ||||
// comparing update tracker. | // comparing update tracker. | ||||
return (cp.compressLevel == -1) || (cp.compressLevel > 1); | |||||
return (client.compressLevel == -1) || (client.compressLevel > 1); | |||||
} | } | ||||
if (state() != RFBSTATE_NORMAL) | if (state() != RFBSTATE_NORMAL) | ||||
return false; | return false; | ||||
if (!cp.supportsLocalCursorWithAlpha && | |||||
!cp.supportsLocalCursor && !cp.supportsLocalXCursor) | |||||
if (!client.supportsLocalCursor()) | |||||
return true; | return true; | ||||
if (!server->getCursorPos().equals(pointerEventPos) && | if (!server->getCursorPos().equals(pointerEventPos) && | ||||
(time(0) - pointerEventTime) > 0) | (time(0) - pointerEventTime) > 0) | ||||
idleTimer.start(secsToMillis(rfb::Server::idleTimeout)); | idleTimer.start(secsToMillis(rfb::Server::idleTimeout)); | ||||
// - Set the connection parameters appropriately | // - Set the connection parameters appropriately | ||||
cp.width = server->getPixelBuffer()->width(); | |||||
cp.height = server->getPixelBuffer()->height(); | |||||
cp.screenLayout = server->getScreenLayout(); | |||||
cp.setName(server->getName()); | |||||
cp.setLEDState(server->getLEDState()); | |||||
client.setDimensions(server->getPixelBuffer()->width(), | |||||
server->getPixelBuffer()->height(), | |||||
server->getScreenLayout()); | |||||
client.setName(server->getName()); | |||||
client.setLEDState(server->getLEDState()); | |||||
// - Set the default pixel format | // - Set the default pixel format | ||||
cp.setPF(server->getPixelBuffer()->getPF()); | |||||
client.setPF(server->getPixelBuffer()->getPF()); | |||||
char buffer[256]; | char buffer[256]; | ||||
cp.pf().print(buffer, 256); | |||||
client.pf().print(buffer, 256); | |||||
vlog.info("Server default pixel format %s", buffer); | vlog.info("Server default pixel format %s", buffer); | ||||
// - Mark the entire display as "dirty" | // - Mark the entire display as "dirty" | ||||
// Lock key heuristics | // Lock key heuristics | ||||
// (only for clients that do not support the LED state extension) | // (only for clients that do not support the LED state extension) | ||||
if (!cp.supportsLEDState) { | |||||
if (!client.supportsLEDState()) { | |||||
// Always ignore ScrollLock as we don't have a heuristic | // Always ignore ScrollLock as we don't have a heuristic | ||||
// for that | // for that | ||||
if (keysym == XK_Scroll_Lock) { | if (keysym == XK_Scroll_Lock) { | ||||
SConnection::framebufferUpdateRequest(r, incremental); | SConnection::framebufferUpdateRequest(r, incremental); | ||||
// Check that the client isn't sending crappy requests | // Check that the client isn't sending crappy requests | ||||
if (!r.enclosed_by(Rect(0, 0, cp.width, cp.height))) { | |||||
if (!r.enclosed_by(Rect(0, 0, client.width(), client.height()))) { | |||||
vlog.error("FramebufferUpdateRequest %dx%d at %d,%d exceeds framebuffer %dx%d", | vlog.error("FramebufferUpdateRequest %dx%d at %d,%d exceeds framebuffer %dx%d", | ||||
r.width(), r.height(), r.tl.x, r.tl.y, cp.width, cp.height); | |||||
safeRect = r.intersect(Rect(0, 0, cp.width, cp.height)); | |||||
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 { | } else { | ||||
safeRect = r; | safeRect = r; | ||||
} | } | ||||
// And send the screen layout to the client (which, unlike the | // And send the screen layout to the client (which, unlike the | ||||
// framebuffer dimensions, the client doesn't get during init) | // framebuffer dimensions, the client doesn't get during init) | ||||
writer()->writeExtendedDesktopSize(); | |||||
if (client.supportsEncoding(pseudoEncodingExtendedDesktopSize)) | |||||
writer()->writeDesktopSize(reasonServer); | |||||
// We do not send a DesktopSize since it only contains the | // We do not send a DesktopSize since it only contains the | ||||
// framebuffer size (which the client already should know) and | // framebuffer size (which the client already should know) and | ||||
if (!rfb::Server::acceptSetDesktopSize) return; | if (!rfb::Server::acceptSetDesktopSize) return; | ||||
result = server->setDesktopSize(this, fb_width, fb_height, layout); | result = server->setDesktopSize(this, fb_width, fb_height, layout); | ||||
writer()->writeExtendedDesktopSize(reasonClient, result, | |||||
fb_width, fb_height, layout); | |||||
writer()->writeDesktopSize(reasonClient, result); | |||||
} | } | ||||
void VNCSConnectionST::fence(rdr::U32 flags, unsigned len, const char data[]) | void VNCSConnectionST::fence(rdr::U32 flags, unsigned len, const char data[]) | ||||
{ | { | ||||
Rect rect; | Rect rect; | ||||
if (!cp.supportsFence || !cp.supportsContinuousUpdates) | |||||
if (!client.supportsFence() || !client.supportsContinuousUpdates()) | |||||
throw Exception("Client tried to enable continuous updates when not allowed"); | throw Exception("Client tried to enable continuous updates when not allowed"); | ||||
continuousUpdates = enable; | continuousUpdates = enable; | ||||
} | } | ||||
// supportsLocalCursor() is called whenever the status of | // supportsLocalCursor() is called whenever the status of | ||||
// cp.supportsLocalCursor has changed. If the client does now support local | |||||
// 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 | // cursor, we make sure that the old server-side rendered cursor is cleaned up | ||||
// and the cursor is sent to the client. | // and the cursor is sent to the client. | ||||
{ | { | ||||
// We refuse to use continuous updates if we cannot monitor the buffer | // We refuse to use continuous updates if we cannot monitor the buffer | ||||
// usage using fences. | // usage using fences. | ||||
if (!cp.supportsFence) | |||||
if (!client.supportsFence()) | |||||
return; | return; | ||||
writer()->writeEndOfContinuousUpdates(); | writer()->writeEndOfContinuousUpdates(); | ||||
void VNCSConnectionST::supportsLEDState() | void VNCSConnectionST::supportsLEDState() | ||||
{ | { | ||||
if (client.ledState() == ledUnknown) | |||||
return; | |||||
writer()->writeLEDState(); | writer()->writeLEDState(); | ||||
} | } | ||||
{ | { | ||||
char type; | char type; | ||||
if (!cp.supportsFence) | |||||
if (!client.supportsFence()) | |||||
return; | return; | ||||
congestion.updatePosition(sock->outStream().length()); | congestion.updatePosition(sock->outStream().length()); | ||||
if (sock->outStream().bufferUsage() > 0) | if (sock->outStream().bufferUsage() > 0) | ||||
return true; | return true; | ||||
if (!cp.supportsFence) | |||||
if (!client.supportsFence()) | |||||
return false; | return false; | ||||
congestion.updatePosition(sock->outStream().length()); | congestion.updatePosition(sock->outStream().length()); | ||||
bool needNewUpdateInfo; | bool needNewUpdateInfo; | ||||
const RenderedCursor *cursor; | const RenderedCursor *cursor; | ||||
updates.enable_copyrect(cp.useCopyRect); | |||||
// See what the client has requested (if anything) | // See what the client has requested (if anything) | ||||
if (continuousUpdates) | if (continuousUpdates) | ||||
req = cuRegion.union_(requested); | req = cuRegion.union_(requested); | ||||
if (!authenticated()) | if (!authenticated()) | ||||
return; | return; | ||||
cp.screenLayout = server->getScreenLayout(); | |||||
client.setDimensions(client.width(), client.height(), | |||||
server->getScreenLayout()); | |||||
if (state() != RFBSTATE_NORMAL) | if (state() != RFBSTATE_NORMAL) | ||||
return; | return; | ||||
writer()->writeExtendedDesktopSize(reason, 0, cp.width, cp.height, | |||||
cp.screenLayout); | |||||
writer()->writeDesktopSize(reason); | |||||
} | } | ||||
// We need to blank out the client's cursor or there will be two | // We need to blank out the client's cursor or there will be two | ||||
if (needRenderedCursor()) { | if (needRenderedCursor()) { | ||||
cp.setCursor(emptyCursor); | |||||
client.setCursor(emptyCursor); | |||||
clientHasCursor = false; | clientHasCursor = false; | ||||
} else { | } else { | ||||
cp.setCursor(*server->getCursor()); | |||||
client.setCursor(*server->getCursor()); | |||||
clientHasCursor = true; | clientHasCursor = true; | ||||
} | } | ||||
if (!writer()->writeSetCursorWithAlpha()) { | |||||
if (!writer()->writeSetCursor()) { | |||||
if (!writer()->writeSetXCursor()) { | |||||
// No client support | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
if (client.supportsLocalCursor()) | |||||
writer()->writeCursor(); | |||||
} | } | ||||
void VNCSConnectionST::setDesktopName(const char *name) | void VNCSConnectionST::setDesktopName(const char *name) | ||||
{ | { | ||||
cp.setName(name); | |||||
client.setName(name); | |||||
if (state() != RFBSTATE_NORMAL) | if (state() != RFBSTATE_NORMAL) | ||||
return; | return; | ||||
if (!writer()->writeSetDesktopName()) { | |||||
fprintf(stderr, "Client does not support desktop rename\n"); | |||||
return; | |||||
} | |||||
if (client.supportsEncoding(pseudoEncodingDesktopName)) | |||||
writer()->writeSetDesktopName(); | |||||
} | } | ||||
void VNCSConnectionST::setLEDState(unsigned int ledstate) | void VNCSConnectionST::setLEDState(unsigned int ledstate) | ||||
if (state() != RFBSTATE_NORMAL) | if (state() != RFBSTATE_NORMAL) | ||||
return; | return; | ||||
cp.setLEDState(ledstate); | |||||
client.setLEDState(ledstate); | |||||
writer()->writeLEDState(); | |||||
if (client.supportsLEDState()) | |||||
writer()->writeLEDState(); | |||||
} | } | ||||
void VNCSConnectionST::setSocketTimeouts() | void VNCSConnectionST::setSocketTimeouts() |
delete comparer; | delete comparer; | ||||
comparer = 0; | comparer = 0; | ||||
screenLayout = layout; | |||||
if (!pb) { | if (!pb) { | ||||
screenLayout = ScreenSet(); | screenLayout = ScreenSet(); | ||||
return; | return; | ||||
} | } | ||||
if (!layout.validate(pb->width(), pb->height())) | |||||
throw Exception("setPixelBuffer: invalid screen layout"); | |||||
screenLayout = layout; | |||||
// Assume the framebuffer contents wasn't saved and reset everything | // Assume the framebuffer contents wasn't saved and reset everything | ||||
// that tracks its contents | // that tracks its contents | ||||
comparer = new ComparingUpdateTracker(pb); | comparer = new ComparingUpdateTracker(pb); | ||||
renderedCursorInvalid = true; | renderedCursorInvalid = true; | ||||
add_changed(pb->getRect()); | add_changed(pb->getRect()); | ||||
// Make sure that we have at least one screen | |||||
if (screenLayout.num_screens() == 0) | |||||
screenLayout.add_screen(Screen(0, 0, 0, pb->width(), pb->height(), 0)); | |||||
std::list<VNCSConnectionST*>::iterator ci, ci_next; | std::list<VNCSConnectionST*>::iterator ci, ci_next; | ||||
for (ci=clients.begin();ci!=clients.end();ci=ci_next) { | for (ci=clients.begin();ci!=clients.end();ci=ci_next) { | ||||
ci_next = ci; ci_next++; | ci_next = ci; ci_next++; | ||||
} | } | ||||
} | } | ||||
// Make sure that we have at least one screen | |||||
if (layout.num_screens() == 0) | |||||
layout.add_screen(Screen(0, 0, 0, pb->width(), pb->height(), 0)); | |||||
setPixelBuffer(pb_, layout); | setPixelBuffer(pb_, layout); | ||||
} | } | ||||
#include <rdr/MemInStream.h> | #include <rdr/MemInStream.h> | ||||
#include <rdr/OutStream.h> | #include <rdr/OutStream.h> | ||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/ServerParams.h> | |||||
#include <rfb/PixelBuffer.h> | #include <rfb/PixelBuffer.h> | ||||
#include <rfb/ZRLEDecoder.h> | #include <rfb/ZRLEDecoder.h> | ||||
} | } | ||||
void ZRLEDecoder::readRect(const Rect& r, rdr::InStream* is, | void ZRLEDecoder::readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os) | |||||
const ServerParams& server, rdr::OutStream* os) | |||||
{ | { | ||||
rdr::U32 len; | rdr::U32 len; | ||||
} | } | ||||
void ZRLEDecoder::decodeRect(const Rect& r, const void* buffer, | void ZRLEDecoder::decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb) | ModifiablePixelBuffer* pb) | ||||
{ | { | ||||
rdr::MemInStream is(buffer, buflen); | rdr::MemInStream is(buffer, buflen); | ||||
const rfb::PixelFormat& pf = cp.pf(); | |||||
const rfb::PixelFormat& pf = server.pf(); | |||||
switch (pf.bpp) { | switch (pf.bpp) { | ||||
case 8: zrleDecode8 (r, &is, &zis, pf, pb); break; | case 8: zrleDecode8 (r, &is, &zis, pf, pb); break; | ||||
case 16: zrleDecode16(r, &is, &zis, pf, pb); break; | case 16: zrleDecode16(r, &is, &zis, pf, pb); break; |
ZRLEDecoder(); | ZRLEDecoder(); | ||||
virtual ~ZRLEDecoder(); | virtual ~ZRLEDecoder(); | ||||
virtual void readRect(const Rect& r, rdr::InStream* is, | virtual void readRect(const Rect& r, rdr::InStream* is, | ||||
const ConnParams& cp, rdr::OutStream* os); | |||||
const ServerParams& server, rdr::OutStream* os); | |||||
virtual void decodeRect(const Rect& r, const void* buffer, | virtual void decodeRect(const Rect& r, const void* buffer, | ||||
size_t buflen, const ConnParams& cp, | |||||
size_t buflen, const ServerParams& server, | |||||
ModifiablePixelBuffer* pb); | ModifiablePixelBuffer* pb); | ||||
private: | private: | ||||
rdr::ZlibInStream zis; | rdr::ZlibInStream zis; |
#include <rdr/OutStream.h> | #include <rdr/OutStream.h> | ||||
#include <rfb/Exception.h> | #include <rfb/Exception.h> | ||||
#include <rfb/encodings.h> | #include <rfb/encodings.h> | ||||
#include <rfb/ConnParams.h> | |||||
#include <rfb/Palette.h> | #include <rfb/Palette.h> | ||||
#include <rfb/SConnection.h> | #include <rfb/SConnection.h> | ||||
#include <rfb/ZRLEEncoder.h> | #include <rfb/ZRLEEncoder.h> | ||||
bool ZRLEEncoder::isSupported() | bool ZRLEEncoder::isSupported() | ||||
{ | { | ||||
return conn->cp.supportsEncoding(encodingZRLE); | |||||
return conn->client.supportsEncoding(encodingZRLE); | |||||
} | } | ||||
void ZRLEEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) | void ZRLEEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) |
CConn(const char *filename); | CConn(const char *filename); | ||||
~CConn(); | ~CConn(); | ||||
virtual void setDesktopSize(int w, int h); | |||||
virtual void initDone(); | |||||
virtual void setPixelFormat(const rfb::PixelFormat& pf); | virtual void setPixelFormat(const rfb::PixelFormat& pf); | ||||
virtual void setCursor(int, int, const rfb::Point&, const rdr::U8*); | virtual void setCursor(int, int, const rfb::Point&, const rdr::U8*); | ||||
virtual void framebufferUpdateStart(); | virtual void framebufferUpdateStart(); | ||||
delete in; | delete in; | ||||
} | } | ||||
void CConn::setDesktopSize(int w, int h) | |||||
void CConn::initDone() | |||||
{ | { | ||||
CConnection::setDesktopSize(w, h); | |||||
setFramebuffer(new rfb::ManagedPixelBuffer(filePF, cp.width, cp.height)); | |||||
setFramebuffer(new rfb::ManagedPixelBuffer(filePF, | |||||
server.width(), | |||||
server.height())); | |||||
} | } | ||||
void CConn::setPixelFormat(const rfb::PixelFormat& pf) | void CConn::setPixelFormat(const rfb::PixelFormat& pf) |
void getStats(double& ratio, unsigned long long& bytes, | void getStats(double& ratio, unsigned long long& bytes, | ||||
unsigned long long& rawEquivalent); | unsigned long long& rawEquivalent); | ||||
virtual void setDesktopSize(int w, int h); | |||||
virtual void initDone(); | |||||
virtual void setCursor(int, int, const rfb::Point&, const rdr::U8*); | virtual void setCursor(int, int, const rfb::Point&, const rdr::U8*); | ||||
virtual void framebufferUpdateStart(); | virtual void framebufferUpdateStart(); | ||||
virtual void framebufferUpdateEnd(); | virtual void framebufferUpdateEnd(); | ||||
setDesktopSize(width, height); | setDesktopSize(width, height); | ||||
sc = new SConn(); | sc = new SConn(); | ||||
sc->cp.setPF((bool)translate ? fbPF : pf); | |||||
sc->client.setPF((bool)translate ? fbPF : pf); | |||||
sc->setEncodings(sizeof(encodings) / sizeof(*encodings), encodings); | sc->setEncodings(sizeof(encodings) / sizeof(*encodings), encodings); | ||||
} | } | ||||
sc->getStats(ratio, bytes, rawEquivalent); | sc->getStats(ratio, bytes, rawEquivalent); | ||||
} | } | ||||
void CConn::setDesktopSize(int w, int h) | |||||
void CConn::initDone() | |||||
{ | { | ||||
rfb::ModifiablePixelBuffer *pb; | rfb::ModifiablePixelBuffer *pb; | ||||
CConnection::setDesktopSize(w, h); | |||||
pb = new rfb::ManagedPixelBuffer((bool)translate ? fbPF : cp.pf(), | |||||
cp.width, cp.height); | |||||
pb = new rfb::ManagedPixelBuffer((bool)translate ? fbPF : server.pf(), | |||||
server.width(), server.height()); | |||||
setFramebuffer(pb); | setFramebuffer(pb); | ||||
} | } | ||||
out = new DummyOutStream; | out = new DummyOutStream; | ||||
setStreams(NULL, out); | setStreams(NULL, out); | ||||
setWriter(new rfb::SMsgWriter(&cp, out)); | |||||
setWriter(new rfb::SMsgWriter(&client, out)); | |||||
manager = new Manager(this); | manager = new Manager(this); | ||||
} | } |
layout = ::computeScreenLayout(&outputIdMap); | layout = ::computeScreenLayout(&outputIdMap); | ||||
XRRFreeScreenResources(res); | XRRFreeScreenResources(res); | ||||
// Adjust the layout relative to the geometry | |||||
ScreenSet::iterator iter, iter_next; | |||||
Point offset(-geometry->offsetLeft(), -geometry->offsetTop()); | |||||
for (iter = layout.begin();iter != layout.end();iter = iter_next) { | |||||
iter_next = iter; ++iter_next; | |||||
iter->dimensions = iter->dimensions.translate(offset); | |||||
if (iter->dimensions.enclosed_by(geometry->getRect())) | |||||
continue; | |||||
iter->dimensions = iter->dimensions.intersect(geometry->getRect()); | |||||
if (iter->dimensions.is_empty()) { | |||||
layout.remove_screen(iter->id); | |||||
} | |||||
} | |||||
#endif | #endif | ||||
// Make sure that we have at least one screen | |||||
if (layout.num_screens() == 0) | |||||
layout.add_screen(rfb::Screen(0, 0, 0, geometry->width(), | |||||
geometry->height(), 0)); | |||||
return layout; | return layout; | ||||
} | } | ||||
#include <rfb/screenTypes.h> | #include <rfb/screenTypes.h> | ||||
#include <rfb/fenceTypes.h> | #include <rfb/fenceTypes.h> | ||||
#include <rfb/Timer.h> | #include <rfb/Timer.h> | ||||
#include <rdr/MemInStream.h> | |||||
#include <rdr/MemOutStream.h> | |||||
#include <network/TcpSocket.h> | #include <network/TcpSocket.h> | ||||
#ifndef WIN32 | #ifndef WIN32 | ||||
#include <network/UnixSocket.h> | #include <network/UnixSocket.h> | ||||
CConn::CConn(const char* vncServerName, network::Socket* socket=NULL) | CConn::CConn(const char* vncServerName, network::Socket* socket=NULL) | ||||
: serverHost(0), serverPort(0), desktop(NULL), | : serverHost(0), serverPort(0), desktop(NULL), | ||||
updateCount(0), pixelCount(0), pendingPFChange(false), | |||||
currentEncoding(encodingTight), lastServerEncoding((unsigned int)-1), | |||||
formatChange(false), encodingChange(false), | |||||
firstUpdate(true), pendingUpdate(false), continuousUpdates(false), | |||||
forceNonincremental(true), supportsSyncFence(false) | |||||
updateCount(0), pixelCount(0), | |||||
lastServerEncoding((unsigned int)-1) | |||||
{ | { | ||||
setShared(::shared); | setShared(::shared); | ||||
sock = socket; | sock = socket; | ||||
int encNum = encodingNum(preferredEncoding); | |||||
if (encNum != -1) | |||||
currentEncoding = encNum; | |||||
cp.supportsLocalCursor = true; | |||||
cp.supportsDesktopResize = true; | |||||
cp.supportsExtendedDesktopSize = true; | |||||
cp.supportsDesktopRename = true; | |||||
cp.supportsLEDState = true; | |||||
supportsLocalCursor = true; | |||||
supportsDesktopResize = true; | |||||
supportsLEDState = true; | |||||
if (customCompressLevel) | if (customCompressLevel) | ||||
cp.compressLevel = compressLevel; | |||||
else | |||||
cp.compressLevel = -1; | |||||
setCompressLevel(::compressLevel); | |||||
if (!noJpeg) | if (!noJpeg) | ||||
cp.qualityLevel = qualityLevel; | |||||
else | |||||
cp.qualityLevel = -1; | |||||
setQualityLevel(::qualityLevel); | |||||
if(sock == NULL) { | if(sock == NULL) { | ||||
try { | try { | ||||
delete sock; | delete sock; | ||||
} | } | ||||
void CConn::refreshFramebuffer() | |||||
{ | |||||
forceNonincremental = true; | |||||
// Without fences, we cannot safely trigger an update request directly | |||||
// but must wait for the next update to arrive. | |||||
if (supportsSyncFence) | |||||
requestNewUpdate(); | |||||
} | |||||
const char *CConn::connectionInfo() | const char *CConn::connectionInfo() | ||||
{ | { | ||||
static char infoText[1024] = ""; | static char infoText[1024] = ""; | ||||
infoText[0] = '\0'; | infoText[0] = '\0'; | ||||
snprintf(scratch, sizeof(scratch), | snprintf(scratch, sizeof(scratch), | ||||
_("Desktop name: %.80s"), cp.name()); | |||||
_("Desktop name: %.80s"), server.name()); | |||||
strcat(infoText, scratch); | strcat(infoText, scratch); | ||||
strcat(infoText, "\n"); | strcat(infoText, "\n"); | ||||
strcat(infoText, "\n"); | strcat(infoText, "\n"); | ||||
snprintf(scratch, sizeof(scratch), | snprintf(scratch, sizeof(scratch), | ||||
_("Size: %d x %d"), cp.width, cp.height); | |||||
_("Size: %d x %d"), server.width(), server.height()); | |||||
strcat(infoText, scratch); | strcat(infoText, scratch); | ||||
strcat(infoText, "\n"); | strcat(infoText, "\n"); | ||||
// TRANSLATORS: Will be filled in with a string describing the | // TRANSLATORS: Will be filled in with a string describing the | ||||
// protocol pixel format in a fairly language neutral way | // protocol pixel format in a fairly language neutral way | ||||
cp.pf().print(pfStr, 100); | |||||
server.pf().print(pfStr, 100); | |||||
snprintf(scratch, sizeof(scratch), | snprintf(scratch, sizeof(scratch), | ||||
_("Pixel format: %s"), pfStr); | _("Pixel format: %s"), pfStr); | ||||
strcat(infoText, scratch); | strcat(infoText, scratch); | ||||
strcat(infoText, "\n"); | strcat(infoText, "\n"); | ||||
snprintf(scratch, sizeof(scratch), | snprintf(scratch, sizeof(scratch), | ||||
_("Requested encoding: %s"), encodingName(currentEncoding)); | |||||
_("Requested encoding: %s"), encodingName(getPreferredEncoding())); | |||||
strcat(infoText, scratch); | strcat(infoText, scratch); | ||||
strcat(infoText, "\n"); | strcat(infoText, "\n"); | ||||
strcat(infoText, "\n"); | strcat(infoText, "\n"); | ||||
snprintf(scratch, sizeof(scratch), | snprintf(scratch, sizeof(scratch), | ||||
_("Protocol version: %d.%d"), cp.majorVersion, cp.minorVersion); | |||||
_("Protocol version: %d.%d"), server.majorVersion, server.minorVersion); | |||||
strcat(infoText, scratch); | strcat(infoText, scratch); | ||||
strcat(infoText, "\n"); | strcat(infoText, "\n"); | ||||
////////////////////// CConnection callback methods ////////////////////// | ////////////////////// CConnection callback methods ////////////////////// | ||||
// serverInit() is called when the serverInit message has been received. At | |||||
// initDone() is called when the serverInit message has been received. At | |||||
// this point we create the desktop window and display it. We also tell the | // 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. | // server the pixel format and encodings to use and request the first update. | ||||
void CConn::serverInit() | |||||
void CConn::initDone() | |||||
{ | { | ||||
CConnection::serverInit(); | |||||
// If using AutoSelect with old servers, start in FullColor | // If using AutoSelect with old servers, start in FullColor | ||||
// mode. See comment in autoSelectFormatAndEncoding. | // mode. See comment in autoSelectFormatAndEncoding. | ||||
if (cp.beforeVersion(3, 8) && autoSelect) | |||||
if (server.beforeVersion(3, 8) && autoSelect) | |||||
fullColour.setParam(true); | fullColour.setParam(true); | ||||
serverPF = cp.pf(); | |||||
serverPF = server.pf(); | |||||
desktop = new DesktopWindow(cp.width, cp.height, cp.name(), serverPF, this); | |||||
desktop = new DesktopWindow(server.width(), server.height(), | |||||
server.name(), serverPF, this); | |||||
fullColourPF = desktop->getPreferredPF(); | fullColourPF = desktop->getPreferredPF(); | ||||
// Force a switch to the format and encoding we'd like | // Force a switch to the format and encoding we'd like | ||||
formatChange = encodingChange = true; | |||||
// And kick off the update cycle | |||||
requestNewUpdate(); | |||||
// This initial update request is a bit of a corner case, so we need | |||||
// to help out setting the correct format here. | |||||
assert(pendingPFChange); | |||||
cp.setPF(pendingPF); | |||||
pendingPFChange = false; | |||||
updatePixelFormat(); | |||||
int encNum = encodingNum(::preferredEncoding); | |||||
if (encNum != -1) | |||||
setPreferredEncoding(encNum); | |||||
} | } | ||||
// setDesktopSize() is called when the desktop size changes (including when | // setDesktopSize() is called when the desktop size changes (including when | ||||
void CConn::setName(const char* name) | void CConn::setName(const char* name) | ||||
{ | { | ||||
CConnection::setName(name); | CConnection::setName(name); | ||||
if (desktop) | |||||
desktop->setName(name); | |||||
desktop->setName(name); | |||||
} | } | ||||
// framebufferUpdateStart() is called at the beginning of an update. | // framebufferUpdateStart() is called at the beginning of an update. | ||||
{ | { | ||||
CConnection::framebufferUpdateStart(); | CConnection::framebufferUpdateStart(); | ||||
// Note: This might not be true if sync fences are supported | |||||
pendingUpdate = false; | |||||
requestNewUpdate(); | |||||
// Update the screen prematurely for very slow updates | // Update the screen prematurely for very slow updates | ||||
Fl::add_timeout(1.0, handleUpdateTimeout, this); | Fl::add_timeout(1.0, handleUpdateTimeout, this); | ||||
} | } | ||||
Fl::remove_timeout(handleUpdateTimeout, this); | Fl::remove_timeout(handleUpdateTimeout, this); | ||||
desktop->updateWindow(); | desktop->updateWindow(); | ||||
if (firstUpdate) { | |||||
// We need fences to make extra update requests and continuous | |||||
// updates "safe". See fence() for the next step. | |||||
if (cp.supportsFence) | |||||
writer()->writeFence(fenceFlagRequest | fenceFlagSyncNext, 0, NULL); | |||||
firstUpdate = false; | |||||
} | |||||
// A format change has been scheduled and we are now past the update | |||||
// with the old format. Time to active the new one. | |||||
if (pendingPFChange) { | |||||
cp.setPF(pendingPF); | |||||
pendingPFChange = false; | |||||
} | |||||
// Compute new settings based on updated bandwidth values | // Compute new settings based on updated bandwidth values | ||||
if (autoSelect) | if (autoSelect) | ||||
autoSelectFormatAndEncoding(); | autoSelectFormatAndEncoding(); | ||||
writer()->writeFence(flags, len, data); | writer()->writeFence(flags, len, data); | ||||
return; | return; | ||||
} | } | ||||
if (len == 0) { | |||||
// Initial probe | |||||
if (flags & fenceFlagSyncNext) { | |||||
supportsSyncFence = true; | |||||
if (cp.supportsContinuousUpdates) { | |||||
vlog.info(_("Enabling continuous updates")); | |||||
continuousUpdates = true; | |||||
writer()->writeEnableContinuousUpdates(true, 0, 0, cp.width, cp.height); | |||||
} | |||||
} | |||||
} else { | |||||
// Pixel format change | |||||
rdr::MemInStream memStream(data, len); | |||||
PixelFormat pf; | |||||
pf.read(&memStream); | |||||
cp.setPF(pf); | |||||
} | |||||
} | } | ||||
void CConn::setLEDState(unsigned int state) | void CConn::setLEDState(unsigned int state) | ||||
void CConn::resizeFramebuffer() | void CConn::resizeFramebuffer() | ||||
{ | { | ||||
if (!desktop) | |||||
return; | |||||
if (continuousUpdates) | |||||
writer()->writeEnableContinuousUpdates(true, 0, 0, cp.width, cp.height); | |||||
desktop->resizeFramebuffer(cp.width, cp.height); | |||||
desktop->resizeFramebuffer(server.width(), server.height()); | |||||
} | } | ||||
// autoSelectFormatAndEncoding() chooses the format and encoding appropriate | // autoSelectFormatAndEncoding() chooses the format and encoding appropriate | ||||
int kbitsPerSecond = sock->inStream().kbitsPerSecond(); | int kbitsPerSecond = sock->inStream().kbitsPerSecond(); | ||||
unsigned int timeWaited = sock->inStream().timeWaited(); | unsigned int timeWaited = sock->inStream().timeWaited(); | ||||
bool newFullColour = fullColour; | bool newFullColour = fullColour; | ||||
int newQualityLevel = qualityLevel; | |||||
int newQualityLevel = ::qualityLevel; | |||||
// Always use Tight | // Always use Tight | ||||
if (currentEncoding != encodingTight) { | |||||
currentEncoding = encodingTight; | |||||
encodingChange = true; | |||||
} | |||||
setPreferredEncoding(encodingTight); | |||||
// Check that we have a decent bandwidth measurement | // Check that we have a decent bandwidth measurement | ||||
if ((kbitsPerSecond == 0) || (timeWaited < 10000)) | if ((kbitsPerSecond == 0) || (timeWaited < 10000)) | ||||
else | else | ||||
newQualityLevel = 6; | newQualityLevel = 6; | ||||
if (newQualityLevel != qualityLevel) { | |||||
if (newQualityLevel != ::qualityLevel) { | |||||
vlog.info(_("Throughput %d kbit/s - changing to quality %d"), | vlog.info(_("Throughput %d kbit/s - changing to quality %d"), | ||||
kbitsPerSecond, newQualityLevel); | kbitsPerSecond, newQualityLevel); | ||||
cp.qualityLevel = newQualityLevel; | |||||
qualityLevel.setParam(newQualityLevel); | |||||
encodingChange = true; | |||||
::qualityLevel.setParam(newQualityLevel); | |||||
setQualityLevel(newQualityLevel); | |||||
} | } | ||||
} | } | ||||
if (cp.beforeVersion(3, 8)) { | |||||
if (server.beforeVersion(3, 8)) { | |||||
// Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with | // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with | ||||
// cursors "asynchronously". If this happens in the middle of a | // cursors "asynchronously". If this happens in the middle of a | ||||
// pixel format change, the server will encode the cursor with | // pixel format change, the server will encode the cursor with | ||||
vlog.info(_("Throughput %d kbit/s - full color is now disabled"), | vlog.info(_("Throughput %d kbit/s - full color is now disabled"), | ||||
kbitsPerSecond); | kbitsPerSecond); | ||||
fullColour.setParam(newFullColour); | fullColour.setParam(newFullColour); | ||||
formatChange = true; | |||||
updatePixelFormat(); | |||||
} | } | ||||
} | } | ||||
// 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 | // requestNewUpdate() requests an update from the server, having set the | ||||
// format and encoding appropriately. | // format and encoding appropriately. | ||||
void CConn::requestNewUpdate() | |||||
void CConn::updatePixelFormat() | |||||
{ | { | ||||
if (formatChange) { | |||||
PixelFormat pf; | |||||
/* Catch incorrect requestNewUpdate calls */ | |||||
assert(!pendingUpdate || supportsSyncFence); | |||||
if (fullColour) { | |||||
pf = fullColourPF; | |||||
} else { | |||||
if (lowColourLevel == 0) | |||||
pf = verylowColourPF; | |||||
else if (lowColourLevel == 1) | |||||
pf = lowColourPF; | |||||
else | |||||
pf = mediumColourPF; | |||||
} | |||||
if (supportsSyncFence) { | |||||
// We let the fence carry the pixel format and switch once we | |||||
// get the response back. That way we will be synchronised with | |||||
// when the server switches. | |||||
rdr::MemOutStream memStream; | |||||
pf.write(&memStream); | |||||
writer()->writeFence(fenceFlagRequest | fenceFlagSyncNext, | |||||
memStream.length(), (const char*)memStream.data()); | |||||
} else { | |||||
// New requests are sent out at the start of processing the last | |||||
// one, so we cannot switch our internal format right now (doing so | |||||
// would mean misdecoding the current update). | |||||
pendingPFChange = true; | |||||
pendingPF = pf; | |||||
} | |||||
char str[256]; | |||||
pf.print(str, 256); | |||||
vlog.info(_("Using pixel format %s"),str); | |||||
writer()->writeSetPixelFormat(pf); | |||||
PixelFormat pf; | |||||
formatChange = false; | |||||
if (fullColour) { | |||||
pf = fullColourPF; | |||||
} else { | |||||
if (lowColourLevel == 0) | |||||
pf = verylowColourPF; | |||||
else if (lowColourLevel == 1) | |||||
pf = lowColourPF; | |||||
else | |||||
pf = mediumColourPF; | |||||
} | } | ||||
checkEncodings(); | |||||
if (forceNonincremental || !continuousUpdates) { | |||||
pendingUpdate = true; | |||||
writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height), | |||||
!forceNonincremental); | |||||
} | |||||
forceNonincremental = false; | |||||
char str[256]; | |||||
pf.print(str, 256); | |||||
vlog.info(_("Using pixel format %s"),str); | |||||
setPF(pf); | |||||
} | } | ||||
void CConn::handleOptions(void *data) | void CConn::handleOptions(void *data) | ||||
// list is cheap. Avoid overriding what the auto logic has selected | // list is cheap. Avoid overriding what the auto logic has selected | ||||
// though. | // though. | ||||
if (!autoSelect) { | if (!autoSelect) { | ||||
int encNum = encodingNum(preferredEncoding); | |||||
int encNum = encodingNum(::preferredEncoding); | |||||
if (encNum != -1) | if (encNum != -1) | ||||
self->currentEncoding = encNum; | |||||
self->setPreferredEncoding(encNum); | |||||
} | } | ||||
self->cp.supportsLocalCursor = true; | |||||
if (customCompressLevel) | if (customCompressLevel) | ||||
self->cp.compressLevel = compressLevel; | |||||
self->setCompressLevel(::compressLevel); | |||||
else | else | ||||
self->cp.compressLevel = -1; | |||||
self->setCompressLevel(-1); | |||||
if (!noJpeg && !autoSelect) | if (!noJpeg && !autoSelect) | ||||
self->cp.qualityLevel = qualityLevel; | |||||
self->setQualityLevel(::qualityLevel); | |||||
else | else | ||||
self->cp.qualityLevel = -1; | |||||
self->encodingChange = true; | |||||
// Format changes refreshes the entire screen though and are therefore | |||||
// very costly. It's probably worth the effort to see if it is necessary | |||||
// here. | |||||
PixelFormat pf; | |||||
self->setQualityLevel(-1); | |||||
if (fullColour) { | |||||
pf = self->fullColourPF; | |||||
} else { | |||||
if (lowColourLevel == 0) | |||||
pf = verylowColourPF; | |||||
else if (lowColourLevel == 1) | |||||
pf = lowColourPF; | |||||
else | |||||
pf = mediumColourPF; | |||||
} | |||||
if (!pf.equal(self->cp.pf())) { | |||||
self->formatChange = true; | |||||
// Without fences, we cannot safely trigger an update request directly | |||||
// but must wait for the next update to arrive. | |||||
if (self->supportsSyncFence) | |||||
self->requestNewUpdate(); | |||||
} | |||||
self->updatePixelFormat(); | |||||
} | } | ||||
void CConn::handleUpdateTimeout(void *data) | void CConn::handleUpdateTimeout(void *data) |
CConn(const char* vncServerName, network::Socket* sock); | CConn(const char* vncServerName, network::Socket* sock); | ||||
~CConn(); | ~CConn(); | ||||
void refreshFramebuffer(); | |||||
const char *connectionInfo(); | const char *connectionInfo(); | ||||
unsigned getUpdateCount(); | unsigned getUpdateCount(); | ||||
static void socketEvent(FL_SOCKET fd, void *data); | static void socketEvent(FL_SOCKET fd, void *data); | ||||
// CConnection callback methods | // CConnection callback methods | ||||
void serverInit(); | |||||
void initDone(); | |||||
void setDesktopSize(int w, int h); | void setDesktopSize(int w, int h); | ||||
void setExtendedDesktopSize(unsigned reason, unsigned result, | void setExtendedDesktopSize(unsigned reason, unsigned result, | ||||
void resizeFramebuffer(); | void resizeFramebuffer(); | ||||
void autoSelectFormatAndEncoding(); | void autoSelectFormatAndEncoding(); | ||||
void checkEncodings(); | |||||
void requestNewUpdate(); | |||||
void updatePixelFormat(); | |||||
static void handleOptions(void *data); | static void handleOptions(void *data); | ||||
rfb::PixelFormat serverPF; | rfb::PixelFormat serverPF; | ||||
rfb::PixelFormat fullColourPF; | rfb::PixelFormat fullColourPF; | ||||
bool pendingPFChange; | |||||
rfb::PixelFormat pendingPF; | |||||
int currentEncoding, lastServerEncoding; | |||||
bool formatChange; | |||||
bool encodingChange; | |||||
bool firstUpdate; | |||||
bool pendingUpdate; | |||||
bool continuousUpdates; | |||||
bool forceNonincremental; | |||||
bool supportsSyncFence; | |||||
int lastServerEncoding; | |||||
}; | }; | ||||
#endif | #endif |
void DesktopWindow::updateWindow() | void DesktopWindow::updateWindow() | ||||
{ | { | ||||
if (firstUpdate) { | if (firstUpdate) { | ||||
if (cc->cp.supportsSetDesktopSize) { | |||||
if (cc->server.supportsSetDesktopSize) { | |||||
// Hack: Wait until we're in the proper mode and position until | // Hack: Wait until we're in the proper mode and position until | ||||
// resizing things, otherwise we might send the wrong thing. | // resizing things, otherwise we might send the wrong thing. | ||||
if (delayedFullscreen) | if (delayedFullscreen) | ||||
// d) We're not still waiting for startup fullscreen to kick in | // d) We're not still waiting for startup fullscreen to kick in | ||||
// | // | ||||
if (not firstUpdate and not delayedFullscreen and | if (not firstUpdate and not delayedFullscreen and | ||||
::remoteResize and cc->cp.supportsSetDesktopSize) { | |||||
::remoteResize and cc->server.supportsSetDesktopSize) { | |||||
// We delay updating the remote desktop as we tend to get a flood | // We delay updating the remote desktop as we tend to get a flood | ||||
// of resize events as the user is dragging the window. | // of resize events as the user is dragging the window. | ||||
Fl::remove_timeout(handleResizeTimeout, this); | Fl::remove_timeout(handleResizeTimeout, this); | ||||
void DesktopWindow::remoteResize(int width, int height) | void DesktopWindow::remoteResize(int width, int height) | ||||
{ | { | ||||
ScreenSet layout; | ScreenSet layout; | ||||
ScreenSet::iterator iter; | |||||
ScreenSet::const_iterator iter; | |||||
if (!fullscreen_active() || (width > w()) || (height > h())) { | if (!fullscreen_active() || (width > w()) || (height > h())) { | ||||
// In windowed mode (or the framebuffer is so large that we need | // In windowed mode (or the framebuffer is so large that we need | ||||
// to scroll) we just report a single virtual screen that covers | // to scroll) we just report a single virtual screen that covers | ||||
// the entire framebuffer. | // the entire framebuffer. | ||||
layout = cc->cp.screenLayout; | |||||
layout = cc->server.screenLayout(); | |||||
// Not sure why we have no screens, but adding a new one should be | // Not sure why we have no screens, but adding a new one should be | ||||
// safe as there is nothing to conflict with... | // safe as there is nothing to conflict with... | ||||
sy -= viewport_rect.tl.y; | sy -= viewport_rect.tl.y; | ||||
// Look for perfectly matching existing screen... | // Look for perfectly matching existing screen... | ||||
for (iter = cc->cp.screenLayout.begin(); | |||||
iter != cc->cp.screenLayout.end(); ++iter) { | |||||
for (iter = cc->server.screenLayout().begin(); | |||||
iter != cc->server.screenLayout().end(); ++iter) { | |||||
if ((iter->dimensions.tl.x == sx) && | if ((iter->dimensions.tl.x == sx) && | ||||
(iter->dimensions.tl.y == sy) && | (iter->dimensions.tl.y == sy) && | ||||
(iter->dimensions.width() == sw) && | (iter->dimensions.width() == sw) && | ||||
} | } | ||||
// Found it? | // Found it? | ||||
if (iter != cc->cp.screenLayout.end()) { | |||||
if (iter != cc->server.screenLayout().end()) { | |||||
layout.add_screen(*iter); | layout.add_screen(*iter); | ||||
continue; | continue; | ||||
} | } | ||||
// Need to add a new one, which means we need to find an unused id | // Need to add a new one, which means we need to find an unused id | ||||
while (true) { | while (true) { | ||||
id = rand(); | id = rand(); | ||||
for (iter = cc->cp.screenLayout.begin(); | |||||
iter != cc->cp.screenLayout.end(); ++iter) { | |||||
for (iter = cc->server.screenLayout().begin(); | |||||
iter != cc->server.screenLayout().end(); ++iter) { | |||||
if (iter->id == id) | if (iter->id == id) | ||||
break; | break; | ||||
} | } | ||||
if (iter == cc->cp.screenLayout.end()) | |||||
if (iter == cc->server.screenLayout().end()) | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
// Do we actually change anything? | // Do we actually change anything? | ||||
if ((width == cc->cp.width) && | |||||
(height == cc->cp.height) && | |||||
(layout == cc->cp.screenLayout)) | |||||
if ((width == cc->server.width()) && | |||||
(height == cc->server.height()) && | |||||
(layout == cc->server.screenLayout())) | |||||
return; | return; | ||||
char buffer[2048]; | char buffer[2048]; | ||||
vlog.debug("Requesting framebuffer resize from %dx%d to %dx%d", | vlog.debug("Requesting framebuffer resize from %dx%d to %dx%d", | ||||
cc->cp.width, cc->cp.height, width, height); | |||||
cc->server.width(), cc->server.height(), width, height); | |||||
layout.print(buffer, sizeof(buffer)); | layout.print(buffer, sizeof(buffer)); | ||||
vlog.debug("%s", buffer); | vlog.debug("%s", buffer); | ||||
unsigned int state; | unsigned int state; | ||||
// Server support? | // Server support? | ||||
if (cc->cp.ledState() == ledUnknown) | |||||
if (cc->server.ledState() == ledUnknown) | |||||
return; | return; | ||||
state = 0; | state = 0; | ||||
state |= ledNumLock; | state |= ledNumLock; | ||||
// No support for Scroll Lock // | // No support for Scroll Lock // | ||||
state |= (cc->cp.ledState() & ledScrollLock); | |||||
state |= (cc->server.ledState() & ledScrollLock); | |||||
#else | #else | ||||
unsigned int mask; | unsigned int mask; | ||||
state |= ledScrollLock; | state |= ledScrollLock; | ||||
#endif | #endif | ||||
if ((state & ledCapsLock) != (cc->cp.ledState() & ledCapsLock)) { | |||||
if ((state & ledCapsLock) != (cc->server.ledState() & ledCapsLock)) { | |||||
vlog.debug("Inserting fake CapsLock to get in sync with server"); | vlog.debug("Inserting fake CapsLock to get in sync with server"); | ||||
handleKeyPress(0x3a, XK_Caps_Lock); | handleKeyPress(0x3a, XK_Caps_Lock); | ||||
handleKeyRelease(0x3a); | handleKeyRelease(0x3a); | ||||
} | } | ||||
if ((state & ledNumLock) != (cc->cp.ledState() & ledNumLock)) { | |||||
if ((state & ledNumLock) != (cc->server.ledState() & ledNumLock)) { | |||||
vlog.debug("Inserting fake NumLock to get in sync with server"); | vlog.debug("Inserting fake NumLock to get in sync with server"); | ||||
handleKeyPress(0x45, XK_Num_Lock); | handleKeyPress(0x45, XK_Num_Lock); | ||||
handleKeyRelease(0x45); | handleKeyRelease(0x45); | ||||
} | } | ||||
if ((state & ledScrollLock) != (cc->cp.ledState() & ledScrollLock)) { | |||||
if ((state & ledScrollLock) != (cc->server.ledState() & ledScrollLock)) { | |||||
vlog.debug("Inserting fake ScrollLock to get in sync with server"); | vlog.debug("Inserting fake ScrollLock to get in sync with server"); | ||||
handleKeyPress(0x46, XK_Scroll_Lock); | handleKeyPress(0x46, XK_Scroll_Lock); | ||||
handleKeyRelease(0x46); | handleKeyRelease(0x46); |