diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx index ce489b1..ffb05c1 100644 --- a/common/rfb/CConnection.cxx +++ b/common/rfb/CConnection.cxx @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -42,7 +43,8 @@ CConnection::CConnection() : csecurity(0), is(0), os(0), reader_(0), writer_(0), shared(false), state_(RFBSTATE_UNINITIALISED), useProtocol3_3(false), - framebuffer(NULL), decoder(this) + framebuffer(NULL), decoder(this), + serverClipboard(NULL), hasLocalClipboard(false) { } @@ -54,6 +56,7 @@ CConnection::~CConnection() reader_ = 0; delete writer_; writer_ = 0; + strFree(serverClipboard); } void CConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_) @@ -342,6 +345,79 @@ void CConnection::dataRect(const Rect& r, int encoding) decoder.decodeRect(r, encoding, framebuffer); } +void CConnection::serverCutText(const char* str) +{ + hasLocalClipboard = false; + + strFree(serverClipboard); + serverClipboard = NULL; + + serverClipboard = latin1ToUTF8(str); + + handleClipboardAnnounce(true); +} + +void CConnection::handleClipboardCaps(rdr::U32 flags, + const rdr::U32* lengths) +{ + rdr::U32 sizes[] = { 0 }; + + CMsgHandler::handleClipboardCaps(flags, lengths); + + writer()->writeClipboardCaps(rfb::clipboardUTF8 | + rfb::clipboardRequest | + rfb::clipboardPeek | + rfb::clipboardNotify | + rfb::clipboardProvide, + sizes); +} + +void CConnection::handleClipboardRequest(rdr::U32 flags) +{ + if (!(flags & rfb::clipboardUTF8)) + return; + if (!hasLocalClipboard) + return; + handleClipboardRequest(); +} + +void CConnection::handleClipboardPeek(rdr::U32 flags) +{ + if (!hasLocalClipboard) + return; + if (cp.clipboardFlags() & rfb::clipboardNotify) + writer()->writeClipboardNotify(rfb::clipboardUTF8); +} + +void CConnection::handleClipboardNotify(rdr::U32 flags) +{ + strFree(serverClipboard); + serverClipboard = NULL; + + if (flags & rfb::clipboardUTF8) { + hasLocalClipboard = false; + handleClipboardAnnounce(true); + } else { + handleClipboardAnnounce(false); + } +} + +void CConnection::handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data) +{ + if (!(flags & rfb::clipboardUTF8)) + return; + + strFree(serverClipboard); + serverClipboard = NULL; + + serverClipboard = convertLF((const char*)data[0], lengths[0]); + + // FIXME: Should probably verify that this data was actually requested + handleClipboardData(serverClipboard); +} + void CConnection::authSuccess() { } @@ -364,3 +440,53 @@ void CConnection::fence(rdr::U32 flags, unsigned len, const char data[]) writer()->writeFence(flags, len, data); } + +void CConnection::handleClipboardRequest() +{ +} + +void CConnection::handleClipboardAnnounce(bool available) +{ +} + +void CConnection::handleClipboardData(const char* data) +{ +} + +void CConnection::requestClipboard() +{ + if (serverClipboard != NULL) { + handleClipboardData(serverClipboard); + return; + } + + if (cp.clipboardFlags() & rfb::clipboardRequest) + writer()->writeClipboardRequest(rfb::clipboardUTF8); +} + +void CConnection::announceClipboard(bool available) +{ + hasLocalClipboard = available; + + if (cp.clipboardFlags() & rfb::clipboardNotify) + writer()->writeClipboardNotify(available ? rfb::clipboardUTF8 : 0); + else { + if (available) + handleClipboardRequest(); + } +} + +void CConnection::sendClipboardData(const char* data) +{ + if (cp.clipboardFlags() & rfb::clipboardProvide) { + CharArray filtered(convertCRLF(data)); + size_t sizes[1] = { strlen(filtered.buf) + 1 }; + const rdr::U8* data[1] = { (const rdr::U8*)filtered.buf }; + writer()->writeClipboardProvide(rfb::clipboardUTF8, sizes, data); + } else { + CharArray latin1(utf8ToLatin1(data)); + + writer()->writeClientCutText(latin1.buf, strlen(latin1.buf)); + } +} + diff --git a/common/rfb/CConnection.h b/common/rfb/CConnection.h index e29c033..742c50a 100644 --- a/common/rfb/CConnection.h +++ b/common/rfb/CConnection.h @@ -107,6 +107,17 @@ namespace rfb { virtual void framebufferUpdateEnd(); virtual void dataRect(const Rect& r, int encoding); + virtual void serverCutText(const char* str); + + virtual void handleClipboardCaps(rdr::U32 flags, + const rdr::U32* lengths); + virtual void handleClipboardRequest(rdr::U32 flags); + virtual void handleClipboardPeek(rdr::U32 flags); + virtual void handleClipboardNotify(rdr::U32 flags); + virtual void handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data); + // Methods to be overridden in a derived class @@ -121,9 +132,43 @@ namespace rfb { // derived class must call on to CConnection::serverInit(). virtual void serverInit(); + // handleClipboardRequest() is called whenever the server requests + // the client to send over its clipboard data. It will only be + // called after the client has first announced a clipboard change + // via announceClipboard(). + virtual void handleClipboardRequest(); + + // handleClipboardAnnounce() is called to indicate a change in the + // clipboard on the server. Call requestClipboard() to access the + // actual data. + virtual void handleClipboardAnnounce(bool available); + + // handleClipboardData() is called when the server has sent over + // the clipboard data as a result of a previous call to + // requestClipboard(). Note that this function might never be + // called if the clipboard data was no longer available when the + // server received the request. + virtual void handleClipboardData(const char* data); + // Other methods + // requestClipboard() will result in a request to the server to + // transfer its clipboard data. A call to handleClipboardData() + // will be made once the data is available. + virtual void requestClipboard(); + + // announceClipboard() informs the server of changes to the + // clipboard on the client. The server may later request the + // clipboard data via handleClipboardRequest(). + virtual void announceClipboard(bool available); + + // sendClipboardData() transfers the clipboard data to the server + // and should be called whenever the server has requested the + // clipboard via handleClipboardRequest(). + virtual void sendClipboardData(const char* data); + + CMsgReader* reader() { return reader_; } CMsgWriter* writer() { return writer_; } @@ -190,6 +235,9 @@ namespace rfb { ModifiablePixelBuffer* framebuffer; DecodeManager decoder; + + char* serverClipboard; + bool hasLocalClipboard; }; } #endif diff --git a/common/rfb/CMsgHandler.cxx b/common/rfb/CMsgHandler.cxx index b89bc18..b831e24 100644 --- a/common/rfb/CMsgHandler.cxx +++ b/common/rfb/CMsgHandler.cxx @@ -92,3 +92,26 @@ void CMsgHandler::setLEDState(unsigned int state) { cp.setLEDState(state); } + +void CMsgHandler::handleClipboardCaps(rdr::U32 flags, const rdr::U32* lengths) +{ + cp.setClipboardCaps(flags, lengths); +} + +void CMsgHandler::handleClipboardRequest(rdr::U32 flags) +{ +} + +void CMsgHandler::handleClipboardPeek(rdr::U32 flags) +{ +} + +void CMsgHandler::handleClipboardNotify(rdr::U32 flags) +{ +} + +void CMsgHandler::handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data) +{ +} diff --git a/common/rfb/CMsgHandler.h b/common/rfb/CMsgHandler.h index 903ee15..8e640f4 100644 --- a/common/rfb/CMsgHandler.h +++ b/common/rfb/CMsgHandler.h @@ -72,6 +72,15 @@ namespace rfb { virtual void setLEDState(unsigned int state); + virtual void handleClipboardCaps(rdr::U32 flags, + const rdr::U32* lengths); + virtual void handleClipboardRequest(rdr::U32 flags); + virtual void handleClipboardPeek(rdr::U32 flags); + virtual void handleClipboardNotify(rdr::U32 flags); + virtual void handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data); + ConnParams cp; }; } diff --git a/common/rfb/CMsgWriter.cxx b/common/rfb/CMsgWriter.cxx index ddef733..60174ea 100644 --- a/common/rfb/CMsgWriter.cxx +++ b/common/rfb/CMsgWriter.cxx @@ -18,10 +18,14 @@ */ #include #include +#include +#include + #include #include #include #include +#include #include #include #include @@ -265,6 +269,104 @@ void CMsgWriter::writeClientCutText(const char* str, rdr::U32 len) endMsg(); } +void CMsgWriter::writeClipboardCaps(rdr::U32 caps, + const rdr::U32* lengths) +{ + size_t i, count; + + if (!(cp->clipboardFlags() & clipboardCaps)) + throw Exception("Server does not support clipboard \"caps\" action"); + + count = 0; + for (i = 0;i < 16;i++) { + if (caps & (1 << i)) + count++; + } + + startMsg(msgTypeClientCutText); + os->pad(3); + os->writeS32(-(4 + 4 * count)); + + os->writeU32(caps | clipboardCaps); + + count = 0; + for (i = 0;i < 16;i++) { + if (caps & (1 << i)) + os->writeU32(lengths[count++]); + } + + endMsg(); +} + +void CMsgWriter::writeClipboardRequest(rdr::U32 flags) +{ + if (!(cp->clipboardFlags() & clipboardRequest)) + throw Exception("Server does not support clipboard \"request\" action"); + + startMsg(msgTypeClientCutText); + os->pad(3); + os->writeS32(-4); + os->writeU32(flags | clipboardRequest); + endMsg(); +} + +void CMsgWriter::writeClipboardPeek(rdr::U32 flags) +{ + if (!(cp->clipboardFlags() & clipboardPeek)) + throw Exception("Server does not support clipboard \"peek\" action"); + + startMsg(msgTypeClientCutText); + os->pad(3); + os->writeS32(-4); + os->writeU32(flags | clipboardPeek); + endMsg(); +} + +void CMsgWriter::writeClipboardNotify(rdr::U32 flags) +{ + if (!(cp->clipboardFlags() & clipboardNotify)) + throw Exception("Server does not support clipboard \"notify\" action"); + + startMsg(msgTypeClientCutText); + os->pad(3); + os->writeS32(-4); + os->writeU32(flags | clipboardNotify); + endMsg(); +} + +void CMsgWriter::writeClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data) +{ + rdr::MemOutStream mos; + rdr::ZlibOutStream zos; + + int i, count; + + if (!(cp->clipboardFlags() & clipboardProvide)) + throw Exception("Server does not support clipboard \"provide\" action"); + + zos.setUnderlying(&mos); + + count = 0; + for (i = 0;i < 16;i++) { + if (!(flags & (1 << i))) + continue; + zos.writeU32(lengths[count]); + zos.writeBytes(data[count], lengths[count]); + count++; + } + + zos.flush(); + + startMsg(msgTypeClientCutText); + os->pad(3); + os->writeS32(-(4 + mos.length())); + os->writeU32(flags | clipboardProvide); + os->writeBytes(mos.data(), mos.length()); + endMsg(); +} + void CMsgWriter::startMsg(int type) { os->writeU8(type); diff --git a/common/rfb/CMsgWriter.h b/common/rfb/CMsgWriter.h index 1322186..bd999c2 100644 --- a/common/rfb/CMsgWriter.h +++ b/common/rfb/CMsgWriter.h @@ -56,6 +56,13 @@ namespace rfb { void writePointerEvent(const Point& pos, int buttonMask); void writeClientCutText(const char* str, rdr::U32 len); + void writeClipboardCaps(rdr::U32 caps, const rdr::U32* lengths); + void writeClipboardRequest(rdr::U32 flags); + void writeClipboardPeek(rdr::U32 flags); + void writeClipboardNotify(rdr::U32 flags); + void writeClipboardProvide(rdr::U32 flags, const size_t* lengths, + const rdr::U8* const* data); + protected: void startMsg(int type); void endMsg(); diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx index 4ef46c6..848712f 100644 --- a/common/rfb/ConnParams.cxx +++ b/common/rfb/ConnParams.cxx @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -42,7 +43,7 @@ ConnParams::ConnParams() supportsLEDState(false), supportsQEMUKeyEvent(false), supportsWEBP(false), supportsSetDesktopSize(false), supportsFence(false), - supportsContinuousUpdates(false), + supportsContinuousUpdates(false), supportsExtendedClipboard(false), compressLevel(2), qualityLevel(-1), fineQualityLevel(-1), subsampling(subsampleUndefined), name_(0), cursorPos_(0, 0), verStrPos(0), ledState_(ledUnknown), shandler(NULL) @@ -50,6 +51,11 @@ ConnParams::ConnParams() memset(kasmPassed, 0, KASM_NUM_SETTINGS); setName(""); cursor_ = new Cursor(0, 0, Point(), NULL); + + clipFlags = clipboardUTF8 | clipboardRTF | clipboardHTML | + clipboardRequest | clipboardNotify | clipboardProvide; + memset(clipSizes, 0, sizeof(clipSizes)); + clipSizes[0] = 20 * 1024 * 1024; } ConnParams::~ConnParams() @@ -177,6 +183,9 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) case pseudoEncodingContinuousUpdates: supportsContinuousUpdates = true; break; + case pseudoEncodingExtendedClipboard: + supportsExtendedClipboard = true; + break; case pseudoEncodingSubsamp1X: subsampling = subsampleNone; break; @@ -268,3 +277,17 @@ void ConnParams::setLEDState(unsigned int state) { ledState_ = state; } + +void ConnParams::setClipboardCaps(rdr::U32 flags, const rdr::U32* lengths) +{ + int i, num; + + clipFlags = flags; + + num = 0; + for (i = 0;i < 16;i++) { + if (!(flags & (1 << i))) + continue; + clipSizes[i] = lengths[num++]; + } +} diff --git a/common/rfb/ConnParams.h b/common/rfb/ConnParams.h index 844f4bc..39a0de0 100644 --- a/common/rfb/ConnParams.h +++ b/common/rfb/ConnParams.h @@ -94,6 +94,9 @@ namespace rfb { unsigned int ledState() { return ledState_; } void setLEDState(unsigned int state); + rdr::U32 clipboardFlags() const { return clipFlags; } + void setClipboardCaps(rdr::U32 flags, const rdr::U32* lengths); + bool useCopyRect; bool supportsLocalCursor; @@ -111,6 +114,7 @@ namespace rfb { bool supportsSetDesktopSize; bool supportsFence; bool supportsContinuousUpdates; + bool supportsExtendedClipboard; int compressLevel; int qualityLevel; @@ -146,6 +150,8 @@ namespace rfb { int verStrPos; unsigned int ledState_; SMsgHandler *shandler; + rdr::U32 clipFlags; + rdr::U32 clipSizes[16]; }; } #endif diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx index 6b81055..13a62b6 100644 --- a/common/rfb/SConnection.cxx +++ b/common/rfb/SConnection.cxx @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -52,7 +53,8 @@ SConnection::SConnection() : readyForSetColourMapEntries(false), is(0), os(0), reader_(0), writer_(0), ssecurity(0), state_(RFBSTATE_UNINITIALISED), - preferredEncoding(encodingRaw) + preferredEncoding(encodingRaw), + clientClipboard(NULL), hasLocalClipboard(false) { defaultMajorVersion = 3; defaultMinorVersion = 8; @@ -69,6 +71,7 @@ SConnection::~SConnection() reader_ = 0; delete writer_; writer_ = 0; + strFree(clientClipboard); } void SConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_) @@ -281,6 +284,69 @@ void SConnection::setEncodings(int nEncodings, const rdr::S32* encodings) } SMsgHandler::setEncodings(nEncodings, encodings); + + if (cp.supportsExtendedClipboard) { + rdr::U32 sizes[] = { 0 }; + writer()->writeClipboardCaps(rfb::clipboardUTF8 | + rfb::clipboardRequest | + rfb::clipboardPeek | + rfb::clipboardNotify | + rfb::clipboardProvide, + sizes); + } +} + +void SConnection::clientCutText(const char* str) +{ + strFree(clientClipboard); + clientClipboard = NULL; + + clientClipboard = latin1ToUTF8(str); + + handleClipboardAnnounce(true); +} + +void SConnection::handleClipboardRequest(rdr::U32 flags) +{ + if (!(flags & rfb::clipboardUTF8)) + return; + if (!hasLocalClipboard) + return; + handleClipboardRequest(); +} + +void SConnection::handleClipboardPeek(rdr::U32 flags) +{ + if (!hasLocalClipboard) + return; + if (cp.clipboardFlags() & rfb::clipboardNotify) + writer()->writeClipboardNotify(rfb::clipboardUTF8); +} + +void SConnection::handleClipboardNotify(rdr::U32 flags) +{ + strFree(clientClipboard); + clientClipboard = NULL; + + if (flags & rfb::clipboardUTF8) + handleClipboardAnnounce(true); + else + handleClipboardAnnounce(false); +} + +void SConnection::handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data) +{ + if (!(flags & rfb::clipboardUTF8)) + return; + + strFree(clientClipboard); + clientClipboard = NULL; + + clientClipboard = convertLF((const char*)data[0], lengths[0]); + + handleClipboardData(clientClipboard, strlen(clientClipboard)); } void SConnection::supportsQEMUKeyEvent() @@ -375,6 +441,58 @@ void SConnection::enableContinuousUpdates(bool enable, { } +void SConnection::handleClipboardRequest() +{ +} + +void SConnection::handleClipboardAnnounce(bool available) +{ +} + +void SConnection::handleClipboardData(const char* data, int len) +{ +} + +void SConnection::requestClipboard() +{ + if (clientClipboard != NULL) { + handleClipboardData(clientClipboard, strlen(clientClipboard)); + return; + } + + if (cp.supportsExtendedClipboard && + (cp.clipboardFlags() & rfb::clipboardRequest)) + writer()->writeClipboardRequest(rfb::clipboardUTF8); +} + +void SConnection::announceClipboard(bool available) +{ + hasLocalClipboard = available; + + if (cp.supportsExtendedClipboard && + (cp.clipboardFlags() & rfb::clipboardNotify)) + writer()->writeClipboardNotify(available ? rfb::clipboardUTF8 : 0); + else { + if (available) + handleClipboardRequest(); + } +} + +void SConnection::sendClipboardData(const char* data, int len) +{ + if (cp.supportsExtendedClipboard && + (cp.clipboardFlags() & rfb::clipboardProvide)) { + CharArray filtered(convertCRLF(data)); + size_t sizes[1] = { strlen(filtered.buf) + 1 }; + const rdr::U8* data[1] = { (const rdr::U8*)filtered.buf }; + writer()->writeClipboardProvide(rfb::clipboardUTF8, sizes, data); + } else { + CharArray latin1(utf8ToLatin1(data)); + + writer()->writeServerCutText(latin1.buf, strlen(latin1.buf)); + } +} + void SConnection::writeFakeColourMap(void) { int i; diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h index 392182e..41039c9 100644 --- a/common/rfb/SConnection.h +++ b/common/rfb/SConnection.h @@ -73,6 +73,15 @@ namespace rfb { virtual void setEncodings(int nEncodings, const rdr::S32* encodings); + virtual void clientCutText(const char* str); + + virtual void handleClipboardRequest(rdr::U32 flags); + virtual void handleClipboardPeek(rdr::U32 flags); + virtual void handleClipboardNotify(rdr::U32 flags); + virtual void handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data); + virtual void supportsQEMUKeyEvent(); // Methods to be overridden in a derived class @@ -118,6 +127,25 @@ namespace rfb { virtual void enableContinuousUpdates(bool enable, int x, int y, int w, int h); + // handleClipboardRequest() is called whenever the client requests + // the server to send over its clipboard data. It will only be + // called after the server has first announced a clipboard change + // via announceClipboard(). + virtual void handleClipboardRequest(); + + // handleClipboardAnnounce() is called to indicate a change in the + // clipboard on the client. Call requestClipboard() to access the + // actual data. + virtual void handleClipboardAnnounce(bool available); + + // handleClipboardData() is called when the client has sent over + // the clipboard data as a result of a previous call to + // requestClipboard(). Note that this function might never be + // called if the clipboard data was no longer available when the + // client received the request. + virtual void handleClipboardData(const char* data, int len); + + virtual void add_changed_all() {} // setAccessRights() allows a security package to limit the access rights @@ -138,6 +166,22 @@ namespace rfb { // Other methods + // requestClipboard() will result in a request to the client to + // transfer its clipboard data. A call to handleClipboardData() + // will be made once the data is available. + virtual void requestClipboard(); + + // announceClipboard() informs the client of changes to the + // clipboard on the server. The client may later request the + // clipboard data via handleClipboardRequest(). + virtual void announceClipboard(bool available); + + // sendClipboardData() transfers the clipboard data to the client + // and should be called whenever the client has requested the + // clipboard via handleClipboardRequest(). + virtual void sendClipboardData(const char* data, int len); + + // authenticated() returns true if the client has authenticated // successfully. bool authenticated() { return (state_ == RFBSTATE_INITIALISATION || @@ -203,6 +247,9 @@ namespace rfb { SSecurity* ssecurity; stateEnum state_; rdr::S32 preferredEncoding; + + char* clientClipboard; + bool hasLocalClipboard; }; } #endif diff --git a/common/rfb/SDesktop.h b/common/rfb/SDesktop.h index 717ddbc..ec833c3 100644 --- a/common/rfb/SDesktop.h +++ b/common/rfb/SDesktop.h @@ -77,6 +77,25 @@ namespace rfb { // pointerEvent(), keyEvent() and clientCutText() are called in response to // the relevant RFB protocol messages from clients. // See InputHandler for method signatures. + + // handleClipboardRequest() is called whenever a client requests + // the server to send over its clipboard data. It will only be + // called after the server has first announced a clipboard change + // via VNCServer::announceClipboard(). + virtual void handleClipboardRequest() {} + + // handleClipboardAnnounce() is called to indicate a change in the + // clipboard on a client. Call VNCServer::requestClipboard() to + // access the actual data. + virtual void handleClipboardAnnounce(bool __unused_attr available) {} + + // handleClipboardData() is called when a client has sent over + // the clipboard data as a result of a previous call to + // VNCServer::requestClipboard(). Note that this function might + // never be called if the clipboard data was no longer available + // when the client received the request. + virtual void handleClipboardData(const char* __unused_attr data, int len __unused_attr) {} + protected: virtual ~SDesktop() {} }; diff --git a/common/rfb/SMsgHandler.cxx b/common/rfb/SMsgHandler.cxx index 78ece78..29c33ea 100644 --- a/common/rfb/SMsgHandler.cxx +++ b/common/rfb/SMsgHandler.cxx @@ -64,6 +64,29 @@ void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings) supportsQEMUKeyEvent(); } +void SMsgHandler::handleClipboardCaps(rdr::U32 flags, const rdr::U32* lengths) +{ + cp.setClipboardCaps(flags, lengths); +} + +void SMsgHandler::handleClipboardRequest(rdr::U32 flags) +{ +} + +void SMsgHandler::handleClipboardPeek(rdr::U32 flags) +{ +} + +void SMsgHandler::handleClipboardNotify(rdr::U32 flags) +{ +} + +void SMsgHandler::handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data) +{ +} + void SMsgHandler::supportsLocalCursor() { } diff --git a/common/rfb/SMsgHandler.h b/common/rfb/SMsgHandler.h index a169621..8b7a9ad 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -54,6 +54,15 @@ namespace rfb { virtual void enableContinuousUpdates(bool enable, int x, int y, int w, int h) = 0; + virtual void handleClipboardCaps(rdr::U32 flags, + const rdr::U32* lengths); + virtual void handleClipboardRequest(rdr::U32 flags); + virtual void handleClipboardPeek(rdr::U32 flags); + virtual void handleClipboardNotify(rdr::U32 flags); + virtual void handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data); + virtual void sendStats() = 0; virtual bool canChangeKasmSettings() const = 0; diff --git a/common/rfb/SMsgReader.cxx b/common/rfb/SMsgReader.cxx index a71272a..11655b6 100644 --- a/common/rfb/SMsgReader.cxx +++ b/common/rfb/SMsgReader.cxx @@ -18,8 +18,11 @@ */ #include #include +#include + #include #include +#include #include #include #include @@ -224,11 +227,15 @@ void SMsgReader::readPointerEvent() void SMsgReader::readClientCutText() { is->skip(3); - int len = is->readU32(); - if (len < 0) { - throw Exception("Cut text too long."); + rdr::U32 len = is->readU32(); + + if (len & 0x80000000) { + rdr::S32 slen = len; + slen = -slen; + readExtendedClipboard(slen); + return; } - if (len > maxCutText) { + if (len > (size_t)maxCutText) { is->skip(len); vlog.error("Cut text too long (%d bytes) - ignoring", len); return; @@ -239,6 +246,100 @@ void SMsgReader::readClientCutText() handler->clientCutText(ca.buf, len); } +void SMsgReader::readExtendedClipboard(rdr::S32 len) +{ + rdr::U32 flags; + rdr::U32 action; + + if (len < 4) + throw Exception("Invalid extended clipboard message"); + if (len > maxCutText) { + vlog.error("Extended clipboard message too long (%d bytes) - ignoring", len); + is->skip(len); + return; + } + + flags = is->readU32(); + action = flags & clipboardActionMask; + + if (action & clipboardCaps) { + int i; + size_t num; + rdr::U32 lengths[16]; + + num = 0; + for (i = 0;i < 16;i++) { + if (flags & (1 << i)) + num++; + } + + if (len < (rdr::S32)(4 + 4*num)) + throw Exception("Invalid extended clipboard message"); + + num = 0; + for (i = 0;i < 16;i++) { + if (flags & (1 << i)) + lengths[num++] = is->readU32(); + } + + handler->handleClipboardCaps(flags, lengths); + } else if (action == clipboardProvide) { + rdr::ZlibInStream zis; + + int i; + size_t num; + size_t lengths[16]; + rdr::U8* buffers[16]; + + zis.setUnderlying(is, len - 4); + + num = 0; + for (i = 0;i < 16;i++) { + if (!(flags & 1 << i)) + continue; + + lengths[num] = zis.readU32(); + if (lengths[num] > (size_t)maxCutText) { + vlog.error("Extended clipboard data too long (%d bytes) - ignoring", + (unsigned)lengths[num]); + zis.skip(lengths[num]); + flags &= ~(1 << i); + continue; + } + + buffers[num] = new rdr::U8[lengths[num]]; + zis.readBytes(buffers[num], lengths[num]); + num++; + } + + zis.flushUnderlying(); + zis.setUnderlying(NULL, 0); + + handler->handleClipboardProvide(flags, lengths, buffers); + + num = 0; + for (i = 0;i < 16;i++) { + if (!(flags & 1 << i)) + continue; + delete [] buffers[num++]; + } + } else { + switch (action) { + case clipboardRequest: + handler->handleClipboardRequest(flags); + break; + case clipboardPeek: + handler->handleClipboardPeek(flags); + break; + case clipboardNotify: + handler->handleClipboardNotify(flags); + break; + default: + throw Exception("Invalid extended clipboard action"); + } + } +} + void SMsgReader::readRequestStats() { is->skip(3); diff --git a/common/rfb/SMsgReader.h b/common/rfb/SMsgReader.h index ebeee84..ec0035b 100644 --- a/common/rfb/SMsgReader.h +++ b/common/rfb/SMsgReader.h @@ -55,6 +55,7 @@ namespace rfb { void readKeyEvent(); void readPointerEvent(); void readClientCutText(); + void readExtendedClipboard(rdr::S32 len); void readRequestStats(); void readQEMUMessage(); diff --git a/common/rfb/SMsgWriter.cxx b/common/rfb/SMsgWriter.cxx index 6002d18..3d46e2d 100644 --- a/common/rfb/SMsgWriter.cxx +++ b/common/rfb/SMsgWriter.cxx @@ -19,8 +19,12 @@ */ #include #include +#include +#include + #include #include +#include #include #include #include @@ -89,6 +93,112 @@ void SMsgWriter::writeServerCutText(const char* str, int len) endMsg(); } +void SMsgWriter::writeClipboardCaps(rdr::U32 caps, + const rdr::U32* lengths) +{ + size_t i, count; + + if (!cp->supportsExtendedClipboard) + throw Exception("Client does not support extended clipboard"); + + count = 0; + for (i = 0;i < 16;i++) { + if (caps & (1 << i)) + count++; + } + + startMsg(msgTypeServerCutText); + os->pad(3); + os->writeS32(-(4 + 4 * count)); + + os->writeU32(caps | clipboardCaps); + + count = 0; + for (i = 0;i < 16;i++) { + if (caps & (1 << i)) + os->writeU32(lengths[count++]); + } + + endMsg(); +} + +void SMsgWriter::writeClipboardRequest(rdr::U32 flags) +{ + if (!cp->supportsExtendedClipboard) + throw Exception("Client does not support extended clipboard"); + if (!(cp->clipboardFlags() & clipboardRequest)) + throw Exception("Client does not support clipboard \"request\" action"); + + startMsg(msgTypeServerCutText); + os->pad(3); + os->writeS32(-4); + os->writeU32(flags | clipboardRequest); + endMsg(); +} + +void SMsgWriter::writeClipboardPeek(rdr::U32 flags) +{ + if (!cp->supportsExtendedClipboard) + throw Exception("Client does not support extended clipboard"); + if (!(cp->clipboardFlags() & clipboardPeek)) + throw Exception("Client does not support clipboard \"peek\" action"); + + startMsg(msgTypeServerCutText); + os->pad(3); + os->writeS32(-4); + os->writeU32(flags | clipboardPeek); + endMsg(); +} + +void SMsgWriter::writeClipboardNotify(rdr::U32 flags) +{ + if (!cp->supportsExtendedClipboard) + throw Exception("Client does not support extended clipboard"); + if (!(cp->clipboardFlags() & clipboardNotify)) + throw Exception("Client does not support clipboard \"notify\" action"); + + startMsg(msgTypeServerCutText); + os->pad(3); + os->writeS32(-4); + os->writeU32(flags | clipboardNotify); + endMsg(); +} + +void SMsgWriter::writeClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data) +{ + rdr::MemOutStream mos; + rdr::ZlibOutStream zos; + + int i, count; + + if (!cp->supportsExtendedClipboard) + throw Exception("Client does not support extended clipboard"); + if (!(cp->clipboardFlags() & clipboardProvide)) + throw Exception("Client does not support clipboard \"provide\" action"); + + zos.setUnderlying(&mos); + + count = 0; + for (i = 0;i < 16;i++) { + if (!(flags & (1 << i))) + continue; + zos.writeU32(lengths[count]); + zos.writeBytes(data[count], lengths[count]); + count++; + } + + zos.flush(); + + startMsg(msgTypeServerCutText); + os->pad(3); + os->writeS32(-(4 + mos.length())); + os->writeU32(flags | clipboardProvide); + os->writeBytes(mos.data(), mos.length()); + endMsg(); +} + void SMsgWriter::writeStats(const char* str, int len) { startMsg(msgTypeStats); diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h index 5d5e9ae..f561136 100644 --- a/common/rfb/SMsgWriter.h +++ b/common/rfb/SMsgWriter.h @@ -55,6 +55,14 @@ namespace rfb { // writeBell() and writeServerCutText() do the obvious thing. void writeBell(); void writeServerCutText(const char* str, int len); + + void writeClipboardCaps(rdr::U32 caps, const rdr::U32* lengths); + void writeClipboardRequest(rdr::U32 flags); + void writeClipboardPeek(rdr::U32 flags); + void writeClipboardNotify(rdr::U32 flags); + void writeClipboardProvide(rdr::U32 flags, const size_t* lengths, + const rdr::U8* const* data); + void writeStats(const char* str, int len); // writeFence() sends a new fence request or response to the client. diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 6c96ff5..e1dae79 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -403,7 +403,31 @@ static void keylog(unsigned keysym, const char *client) { flushKeylog(client); } -void VNCSConnectionST::serverCutTextOrClose(const char *str, int len) +void VNCSConnectionST::requestClipboardOrClose() +{ + try { + if (!(accessRights & AccessCutText)) return; + if (!rfb::Server::acceptCutText) return; + if (state() != RFBSTATE_NORMAL) return; + requestClipboard(); + } catch(rdr::Exception& e) { + close(e.str()); + } +} + +void VNCSConnectionST::announceClipboardOrClose(bool available) +{ + try { + if (!(accessRights & AccessCutText)) return; + if (!rfb::Server::sendCutText) return; + if (state() != RFBSTATE_NORMAL) return; + announceClipboard(available); + } catch(rdr::Exception& e) { + close(e.str()); + } +} + +void VNCSConnectionST::sendClipboardDataOrClose(const char* data) { try { if (!(accessRights & AccessCutText)) return; @@ -413,19 +437,19 @@ void VNCSConnectionST::serverCutTextOrClose(const char *str, int len) sock->getPeerAddress()); return; } + int len = strlen(data); const int origlen = len; if (rfb::Server::DLP_ClipSendMax && len > rfb::Server::DLP_ClipSendMax) len = rfb::Server::DLP_ClipSendMax; - cliplog(str, len, origlen, "sent", sock->getPeerAddress()); - if (state() == RFBSTATE_NORMAL) - writer()->writeServerCutText(str, len); + cliplog(data, len, origlen, "sent", sock->getPeerAddress()); + if (state() != RFBSTATE_NORMAL) return; + sendClipboardData(data, len); gettimeofday(&lastClipboardOp, NULL); } catch(rdr::Exception& e) { close(e.str()); } } - void VNCSConnectionST::setDesktopNameOrClose(const char *name) { try { @@ -846,24 +870,6 @@ void VNCSConnectionST::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) { server->desktop->keyEvent(keysym, keycode, down); } -void VNCSConnectionST::clientCutText(const char* str, int len) -{ - if (!(accessRights & AccessCutText)) return; - if (!rfb::Server::acceptCutText) return; - if (msSince(&lastClipboardOp) < (unsigned) rfb::Server::DLP_ClipDelay) { - vlog.info("DLP: client %s: refused to receive clipboard, too soon", - sock->getPeerAddress()); - return; - } - const int origlen = len; - if (rfb::Server::DLP_ClipAcceptMax && len > rfb::Server::DLP_ClipAcceptMax) - len = rfb::Server::DLP_ClipAcceptMax; - cliplog(str, len, origlen, "received", sock->getPeerAddress()); - - gettimeofday(&lastClipboardOp, NULL); - server->desktop->clientCutText(str, len); -} - void VNCSConnectionST::framebufferUpdateRequest(const Rect& r,bool incremental) { Rect safeRect; @@ -997,6 +1003,39 @@ void VNCSConnectionST::enableContinuousUpdates(bool enable, } } +void VNCSConnectionST::handleClipboardRequest() +{ + if (!(accessRights & AccessCutText)) return; + server->handleClipboardRequest(this); +} + +void VNCSConnectionST::handleClipboardAnnounce(bool available) +{ + if (!(accessRights & AccessCutText)) return; + if (!rfb::Server::acceptCutText) return; + server->handleClipboardAnnounce(this, available); +} + +void VNCSConnectionST::handleClipboardData(const char* data) +{ + if (!(accessRights & AccessCutText)) return; + if (!rfb::Server::acceptCutText) return; + if (msSince(&lastClipboardOp) < (unsigned) rfb::Server::DLP_ClipDelay) { + vlog.info("DLP: client %s: refused to receive clipboard, too soon", + sock->getPeerAddress()); + return; + } + int len = strlen(data); + const int origlen = len; + if (rfb::Server::DLP_ClipAcceptMax && len > rfb::Server::DLP_ClipAcceptMax) + len = rfb::Server::DLP_ClipAcceptMax; + cliplog(data, len, origlen, "received", sock->getPeerAddress()); + + gettimeofday(&lastClipboardOp, NULL); + server->handleClipboardData(this, data, len); +} + + // supportsLocalCursor() is called whenever the status of // cp.supportsLocalCursor has changed. If the client does now support local // cursor, we make sure that the old server-side rendered cursor is cleaned up diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index f03db37..13c5570 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -75,9 +75,11 @@ namespace rfb { void screenLayoutChangeOrClose(rdr::U16 reason); void setCursorOrClose(); void bellOrClose(); - void serverCutTextOrClose(const char *str, int len); void setDesktopNameOrClose(const char *name); void setLEDStateOrClose(unsigned int state); + void requestClipboardOrClose(); + void announceClipboardOrClose(bool available); + void sendClipboardDataOrClose(const char* data); // checkIdleTimeout() returns the number of milliseconds left until the // idle timeout expires. If it has expired, the connection is closed and @@ -175,13 +177,15 @@ namespace rfb { virtual void setPixelFormat(const PixelFormat& pf); virtual void pointerEvent(const Point& pos, int buttonMask, const bool skipClick, const bool skipRelease); virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); - virtual void clientCutText(const char* str, int len); virtual void framebufferUpdateRequest(const Rect& r, bool incremental); virtual void setDesktopSize(int fb_width, int fb_height, const ScreenSet& layout); virtual void fence(rdr::U32 flags, unsigned len, const char data[]); virtual void enableContinuousUpdates(bool enable, int x, int y, int w, int h); + virtual void handleClipboardRequest(); + virtual void handleClipboardAnnounce(bool available); + virtual void handleClipboardData(const char* data); virtual void supportsLocalCursor(); virtual void supportsFence(); virtual void supportsContinuousUpdates(); diff --git a/common/rfb/VNCServer.h b/common/rfb/VNCServer.h index 1f36d8f..d2e0fa2 100644 --- a/common/rfb/VNCServer.h +++ b/common/rfb/VNCServer.h @@ -52,9 +52,21 @@ namespace rfb { // getPixelBuffer() returns a pointer to the PixelBuffer object. virtual PixelBuffer* getPixelBuffer() const = 0; - // serverCutText() tells the server that the cut text has changed. This - // will normally be sent to all clients. - virtual void serverCutText(const char* str, int len) = 0; + // requestClipboard() will result in a request to a client to + // transfer its clipboard data. A call to + // SDesktop::handleClipboardData() will be made once the data is + // available. + virtual void requestClipboard() = 0; + + // announceClipboard() informs all clients of changes to the + // clipboard on the server. A client may later request the + // clipboard data via SDesktop::handleClipboardRequest(). + virtual void announceClipboard(bool available) = 0; + + // sendClipboardData() transfers the clipboard data to a client + // and should be called whenever a client has requested the + // clipboard via SDesktop::handleClipboardRequest(). + virtual void sendClipboardData(const char* data) = 0; // bell() tells the server that it should make all clients make a bell sound. virtual void bell() = 0; diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index f60e091..296f3c9 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -123,8 +123,8 @@ static void parseRegionPart(const bool percents, rdr::U16 &pcdest, int &dest, VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) : blHosts(&blacklist), desktop(desktop_), desktopStarted(false), blockCounter(0), pb(0), blackedpb(0), ledState(ledUnknown), - name(strDup(name_)), pointerClient(0), comparer(0), - cursor(new Cursor(0, 0, Point(), NULL)), + name(strDup(name_)), pointerClient(0), clipboardClient(0), + comparer(0), cursor(new Cursor(0, 0, Point(), NULL)), renderedCursorInvalid(false), queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance), lastConnectionTime(0), disableclients(false), @@ -502,21 +502,51 @@ void VNCServerST::setScreenLayout(const ScreenSet& layout) } } -void VNCServerST::bell() +void VNCServerST::requestClipboard() +{ + if (clipboardClient == NULL) + return; + + clipboardClient->requestClipboard(); +} + +void VNCServerST::announceClipboard(bool available) { std::list::iterator ci, ci_next; + + if (available) + clipboardClient = NULL; + + clipboardRequestors.clear(); + for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { ci_next = ci; ci_next++; - (*ci)->bellOrClose(); + (*ci)->announceClipboard(available); + } +} + +void VNCServerST::sendClipboardData(const char* data) +{ + std::list::iterator ci, ci_next; + + if (strchr(data, '\r') != NULL) + throw Exception("Invalid carriage return in clipboard data"); + + for (ci = clipboardRequestors.begin(); + ci != clipboardRequestors.end(); ci = ci_next) { + ci_next = ci; ci_next++; + (*ci)->sendClipboardData(data, strlen(data)); } + + clipboardRequestors.clear(); } -void VNCServerST::serverCutText(const char* str, int len) +void VNCServerST::bell() { std::list::iterator ci, ci_next; for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { ci_next = ci; ci_next++; - (*ci)->serverCutTextOrClose(str, len); + (*ci)->bellOrClose(); } } @@ -1052,3 +1082,32 @@ bool VNCServerST::getComparerState() } return false; } + +void VNCServerST::handleClipboardRequest(VNCSConnectionST* client) +{ + clipboardRequestors.push_back(client); + if (clipboardRequestors.size() == 1) + desktop->handleClipboardRequest(); +} + +void VNCServerST::handleClipboardAnnounce(VNCSConnectionST* client, + bool available) +{ + if (available) + clipboardClient = client; + else { + if (client != clipboardClient) + return; + clipboardClient = NULL; + } + desktop->handleClipboardAnnounce(available); +} + +void VNCServerST::handleClipboardData(VNCSConnectionST* client, + const char* data, int len) +{ + if (client != clipboardClient) + return; + desktop->handleClipboardData(data, len); +} + diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index d572813..285c4fd 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -96,7 +96,9 @@ namespace rfb { virtual void setPixelBuffer(PixelBuffer* pb); virtual void setScreenLayout(const ScreenSet& layout); virtual PixelBuffer* getPixelBuffer() const { if (DLPRegion.enabled && blackedpb) return blackedpb; else return pb; } - virtual void serverCutText(const char* str, int len); + virtual void requestClipboard(); + virtual void announceClipboard(bool available); + virtual void sendClipboardData(const char* data); virtual void add_changed(const Region ®ion); virtual void add_copied(const Region &dest, const Point &delta); virtual void setCursor(int width, int height, const Point& hotspot, @@ -189,6 +191,10 @@ namespace rfb { void setAPIMessager(network::GetAPIMessager *msgr) { apimessager = msgr; } + void handleClipboardRequest(VNCSConnectionST* client); + void handleClipboardAnnounce(VNCSConnectionST* client, bool available); + void handleClipboardData(VNCSConnectionST* client, const char* data, int len); + protected: friend class VNCSConnectionST; @@ -217,6 +223,8 @@ namespace rfb { std::list clients; VNCSConnectionST* pointerClient; + VNCSConnectionST* clipboardClient; + std::list clipboardRequestors; std::list closingSockets; static EncCache encCache; diff --git a/common/rfb/clipboardTypes.h b/common/rfb/clipboardTypes.h new file mode 100644 index 0000000..bd3fa03 --- /dev/null +++ b/common/rfb/clipboardTypes.h @@ -0,0 +1,41 @@ +/* Copyright 2019 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. + */ +#ifndef __RFB_CLIPBOARDTYPES_H__ +#define __RFB_CLIPBOARDTYPES_H__ + +namespace rfb { + + // Formats + const unsigned int clipboardUTF8 = 1 << 0; + const unsigned int clipboardRTF = 1 << 1; + const unsigned int clipboardHTML = 1 << 2; + const unsigned int clipboardDIB = 1 << 3; + const unsigned int clipboardFiles = 1 << 4; + + const unsigned int clipboardFormatMask = 0x0000ffff; + + // Actions + const unsigned int clipboardCaps = 1 << 24; + const unsigned int clipboardRequest = 1 << 25; + const unsigned int clipboardPeek = 1 << 26; + const unsigned int clipboardNotify = 1 << 27; + const unsigned int clipboardProvide = 1 << 28; + + const unsigned int clipboardActionMask = 0xff000000; +} +#endif diff --git a/common/rfb/encodings.h b/common/rfb/encodings.h index b32b375..fda4582 100644 --- a/common/rfb/encodings.h +++ b/common/rfb/encodings.h @@ -88,6 +88,9 @@ namespace rfb { // VMware-specific const int pseudoEncodingVMwareCursorPosition = 0x574d5666; + // UltraVNC-specific + const int pseudoEncodingExtendedClipboard = 0xC0A1E5CE; + int encodingNum(const char* name); const char* encodingName(int num); } diff --git a/common/rfb/util.cxx b/common/rfb/util.cxx index f52213b..649eb0b 100644 --- a/common/rfb/util.cxx +++ b/common/rfb/util.cxx @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright 2011-2019 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 @@ -63,6 +64,10 @@ namespace rfb { delete [] s; } + void strFree(wchar_t* s) { + delete [] s; + } + bool strSplit(const char* src, const char limiter, char** out1, char** out2, bool fromEnd) { CharArray out1old, out2old; @@ -107,6 +112,444 @@ namespace rfb { dest[src ? destlen-1 : 0] = 0; } + char* convertLF(const char* src, size_t bytes) + { + char* buffer; + size_t sz; + + char* out; + const char* in; + size_t in_len; + + // Always include space for a NULL + sz = 1; + + // Compute output size + in = src; + in_len = bytes; + while ((in_len > 0) && (*in != '\0')) { + if (*in != '\r') { + sz++; + in++; + in_len--; + continue; + } + + if ((in_len < 2) || (*(in+1) != '\n')) + sz++; + + in++; + in_len--; + } + + // Alloc + buffer = new char[sz]; + memset(buffer, 0, sz); + + // And convert + out = buffer; + in = src; + in_len = bytes; + while ((in_len > 0) && (*in != '\0')) { + if (*in != '\r') { + *out++ = *in++; + in_len--; + continue; + } + + if ((in_len < 2) || (*(in+1) != '\n')) + *out++ = '\n'; + + in++; + in_len--; + } + + return buffer; + } + + char* convertCRLF(const char* src, size_t bytes) + { + char* buffer; + size_t sz; + + char* out; + const char* in; + size_t in_len; + + // Always include space for a NULL + sz = 1; + + // Compute output size + in = src; + in_len = bytes; + while ((in_len > 0) && (*in != '\0')) { + sz++; + + if (*in == '\r') { + if ((in_len < 2) || (*(in+1) != '\n')) + sz++; + } else if (*in == '\n') { + if ((in == src) || (*(in-1) != '\r')) + sz++; + } + + in++; + in_len--; + } + + // Alloc + buffer = new char[sz]; + memset(buffer, 0, sz); + + // And convert + out = buffer; + in = src; + in_len = bytes; + while ((in_len > 0) && (*in != '\0')) { + if (*in == '\n') { + if ((in == src) || (*(in-1) != '\r')) + *out++ = '\r'; + } + + *out = *in; + + if (*in == '\r') { + if ((in_len < 2) || (*(in+1) != '\n')) { + out++; + *out = '\n'; + } + } + + out++; + in++; + in_len--; + } + + return buffer; + } + + size_t ucs4ToUTF8(unsigned src, char* dst) { + if (src < 0x80) { + *dst++ = src; + *dst++ = '\0'; + return 1; + } else if (src < 0x800) { + *dst++ = 0xc0 | (src >> 6); + *dst++ = 0x80 | (src & 0x3f); + *dst++ = '\0'; + return 2; + } else if (src < 0x10000) { + *dst++ = 0xe0 | (src >> 12); + *dst++ = 0x80 | ((src >> 6) & 0x3f); + *dst++ = 0x80 | (src & 0x3f); + *dst++ = '\0'; + return 3; + } else if (src < 0x110000) { + *dst++ = 0xf0 | (src >> 18); + *dst++ = 0x80 | ((src >> 12) & 0x3f); + *dst++ = 0x80 | ((src >> 6) & 0x3f); + *dst++ = 0x80 | (src & 0x3f); + *dst++ = '\0'; + return 4; + } else { + return ucs4ToUTF8(0xfffd, dst); + } + } + + size_t utf8ToUCS4(const char* src, size_t max, unsigned* dst) { + size_t count, consumed; + + *dst = 0xfffd; + + if (max == 0) + return 0; + + consumed = 1; + + if ((*src & 0x80) == 0) { + *dst = *src; + count = 0; + } else if ((*src & 0xe0) == 0xc0) { + *dst = *src & 0x1f; + count = 1; + } else if ((*src & 0xf0) == 0xe0) { + *dst = *src & 0x0f; + count = 2; + } else if ((*src & 0xf8) == 0xf0) { + *dst = *src & 0x07; + count = 3; + } else { + // Invalid sequence, consume all continuation characters + src++; + max--; + while ((max-- > 0) && ((*src++ & 0xc0) == 0x80)) + consumed++; + return consumed; + } + + src++; + max--; + + while (count--) { + consumed++; + + // Invalid or truncated sequence? + if ((max == 0) || ((*src & 0xc0) != 0x80)) { + *dst = 0xfffd; + return consumed; + } + + *dst <<= 6; + *dst |= *src & 0x3f; + + src++; + max--; + } + + return consumed; + } + + size_t ucs4ToUTF16(unsigned src, wchar_t* dst) { + if ((src < 0xd800) || ((src >= 0xe000) && (src < 0x10000))) { + *dst++ = src; + *dst++ = L'\0'; + return 1; + } else if ((src >= 0x10000) && (src < 0x110000)) { + src -= 0x10000; + *dst++ = 0xd800 | ((src >> 10) & 0x03ff); + *dst++ = 0xdc00 | (src & 0x03ff); + *dst++ = L'\0'; + return 2; + } else { + return ucs4ToUTF16(0xfffd, dst); + } + } + + size_t utf16ToUCS4(const wchar_t* src, size_t max, unsigned* dst) { + *dst = 0xfffd; + + if (max == 0) + return 0; + + if ((*src < 0xd800) || (*src >= 0xe000)) { + *dst = *src; + return 1; + } + + if (*src & 0x0400) { + size_t consumed; + + // Invalid sequence, consume all continuation characters + consumed = 0; + while ((max > 0) && (*src & 0x0400)) { + src++; + max--; + consumed++; + } + + return consumed; + } + + *dst = *src++; + max--; + + // Invalid or truncated sequence? + if ((max == 0) || ((*src & 0xfc00) != 0xdc00)) { + *dst = 0xfffd; + return 1; + } + + *dst = 0x10000 + ((*dst & 0x03ff) << 10); + *dst |= *src & 0x3ff; + + return 2; + } + + char* latin1ToUTF8(const char* src, size_t bytes) { + char* buffer; + size_t sz; + + char* out; + const char* in; + size_t in_len; + + // Always include space for a NULL + sz = 1; + + // Compute output size + in = src; + in_len = bytes; + while ((in_len > 0) && (*in != '\0')) { + char buf[5]; + sz += ucs4ToUTF8(*(const unsigned char*)in, buf); + in++; + in_len--; + } + + // Alloc + buffer = new char[sz]; + memset(buffer, 0, sz); + + // And convert + out = buffer; + in = src; + in_len = bytes; + while ((in_len > 0) && (*in != '\0')) { + out += ucs4ToUTF8(*(const unsigned char*)in, out); + in++; + in_len--; + } + + return buffer; + } + + char* utf8ToLatin1(const char* src, size_t bytes) { + char* buffer; + size_t sz; + + char* out; + const char* in; + size_t in_len; + + // Always include space for a NULL + sz = 1; + + // Compute output size + in = src; + in_len = bytes; + while ((in_len > 0) && (*in != '\0')) { + size_t len; + unsigned ucs; + + len = utf8ToUCS4(in, in_len, &ucs); + in += len; + in_len -= len; + sz++; + } + + // Alloc + buffer = new char[sz]; + memset(buffer, 0, sz); + + // And convert + out = buffer; + in = src; + in_len = bytes; + while ((in_len > 0) && (*in != '\0')) { + size_t len; + unsigned ucs; + + len = utf8ToUCS4(in, in_len, &ucs); + in += len; + in_len -= len; + + if (ucs > 0xff) + *out++ = '?'; + else + *out++ = (unsigned char)ucs; + } + + return buffer; + } + + char* utf16ToUTF8(const wchar_t* src, size_t units) + { + char* buffer; + size_t sz; + + char* out; + const wchar_t* in; + size_t in_len; + + // Always include space for a NULL + sz = 1; + + // Compute output size + in = src; + in_len = units; + while ((in_len > 0) && (*in != '\0')) { + size_t len; + unsigned ucs; + char buf[5]; + + len = utf16ToUCS4(in, in_len, &ucs); + in += len; + in_len -= len; + + sz += ucs4ToUTF8(ucs, buf); + } + + // Alloc + buffer = new char[sz]; + memset(buffer, 0, sz); + + // And convert + out = buffer; + in = src; + in_len = units; + while ((in_len > 0) && (*in != '\0')) { + size_t len; + unsigned ucs; + + len = utf16ToUCS4(in, in_len, &ucs); + in += len; + in_len -= len; + + out += ucs4ToUTF8(ucs, out); + } + + return buffer; + } + + wchar_t* utf8ToUTF16(const char* src, size_t bytes) + { + wchar_t* buffer; + size_t sz; + + wchar_t* out; + const char* in; + size_t in_len; + + // Always include space for a NULL + sz = 1; + + // Compute output size + in = src; + in_len = bytes; + while ((in_len > 0) && (*in != '\0')) { + size_t len; + unsigned ucs; + wchar_t buf[3]; + + len = utf8ToUCS4(in, in_len, &ucs); + in += len; + in_len -= len; + + sz += ucs4ToUTF16(ucs, buf); + } + + // Alloc + buffer = new wchar_t[sz]; + memset(buffer, 0, sz * sizeof(wchar_t)); + + // And convert + out = buffer; + in = src; + in_len = bytes; + while ((in_len > 0) && (*in != '\0')) { + size_t len; + unsigned ucs; + + len = utf8ToUCS4(in, in_len, &ucs); + in += len; + in_len -= len; + + out += ucs4ToUTF16(ucs, out); + } + + return buffer; + } + unsigned msBetween(const struct timeval *first, const struct timeval *second) { diff --git a/common/rfb/util.h b/common/rfb/util.h index 8f90ec4..3100f90 100644 --- a/common/rfb/util.h +++ b/common/rfb/util.h @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright 2011-2019 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 @@ -67,6 +68,7 @@ namespace rfb { char* strDup(const char* s); void strFree(char* s); + void strFree(wchar_t* s); // Returns true if split successful. Returns false otherwise. // ALWAYS *copies* first part of string to out1 buffer. @@ -83,6 +85,25 @@ namespace rfb { // Copies src to dest, up to specified length-1, and guarantees termination void strCopy(char* dest, const char* src, int destlen); + // Makes sure line endings are in a certain format + + char* convertLF(const char* src, size_t bytes = (size_t)-1); + char* convertCRLF(const char* src, size_t bytes = (size_t)-1); + + // Convertions between various Unicode formats. The returned strings are + // always null terminated and must be freed using strFree(). + + size_t ucs4ToUTF8(unsigned src, char* dst); + size_t utf8ToUCS4(const char* src, size_t max, unsigned* dst); + + size_t ucs4ToUTF16(unsigned src, wchar_t* dst); + size_t utf16ToUCS4(const wchar_t* src, size_t max, unsigned* dst); + + char* latin1ToUTF8(const char* src, size_t bytes = (size_t)-1); + char* utf8ToLatin1(const char* src, size_t bytes = (size_t)-1); + + char* utf16ToUTF8(const wchar_t* src, size_t units = (size_t)-1); + wchar_t* utf8ToUTF16(const char* src, size_t bytes = (size_t)-1); // HELPER functions for timeout handling diff --git a/unix/xserver/hw/vnc/RFBGlue.cc b/unix/xserver/hw/vnc/RFBGlue.cc index 6086025..7c32bea 100644 --- a/unix/xserver/hw/vnc/RFBGlue.cc +++ b/unix/xserver/hw/vnc/RFBGlue.cc @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2015 Pierre Ossman for Cendio AB + * Copyright 2011-2019 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 @@ -226,3 +226,35 @@ int vncIsTCPPortUsed(int port) } return 0; } + +char* vncConvertLF(const char* src, size_t bytes) +{ + try { + return convertLF(src, bytes); + } catch (...) { + return NULL; + } +} + +char* vncLatin1ToUTF8(const char* src, size_t bytes) +{ + try { + return latin1ToUTF8(src, bytes); + } catch (...) { + return NULL; + } +} + +char* vncUTF8ToLatin1(const char* src, size_t bytes) +{ + try { + return utf8ToLatin1(src, bytes); + } catch (...) { + return NULL; + } +} + +void vncStrFree(char* str) +{ + strFree(str); +} diff --git a/unix/xserver/hw/vnc/RFBGlue.h b/unix/xserver/hw/vnc/RFBGlue.h index d62236a..695cea1 100644 --- a/unix/xserver/hw/vnc/RFBGlue.h +++ b/unix/xserver/hw/vnc/RFBGlue.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2015 Pierre Ossman for Cendio AB + * Copyright 2011-2019 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 @@ -50,6 +50,13 @@ void vncListParams(int width, int nameWidth); int vncGetSocketPort(int fd); int vncIsTCPPortUsed(int port); +char* vncConvertLF(const char* src, size_t bytes); + +char* vncLatin1ToUTF8(const char* src, size_t bytes); +char* vncUTF8ToLatin1(const char* src, size_t bytes); + +void vncStrFree(char* str); + #ifdef __cplusplus } #endif diff --git a/unix/xserver/hw/vnc/XserverDesktop.cc b/unix/xserver/hw/vnc/XserverDesktop.cc index 6a1bb93..691e3a6 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.cc +++ b/unix/xserver/hw/vnc/XserverDesktop.cc @@ -176,25 +176,43 @@ XserverDesktop::queryConnection(network::Socket* sock, return rfb::VNCServerST::PENDING; } -void XserverDesktop::bell() +void XserverDesktop::requestClipboard() { - server->bell(); + try { + server->requestClipboard(); + } catch (rdr::Exception& e) { + vlog.error("XserverDesktop::requestClipboard: %s",e.str()); + } } -void XserverDesktop::setLEDState(unsigned int state) +void XserverDesktop::announceClipboard(bool available) { - server->setLEDState(state); + try { + server->announceClipboard(available); + } catch (rdr::Exception& e) { + vlog.error("XserverDesktop::announceClipboard: %s",e.str()); + } } -void XserverDesktop::serverCutText(const char* str, int len) +void XserverDesktop::sendClipboardData(const char* data) { try { - server->serverCutText(str, len); + server->sendClipboardData(data); } catch (rdr::Exception& e) { - vlog.error("XserverDesktop::serverCutText: %s",e.str()); + vlog.error("XserverDesktop::sendClipboardData: %s",e.str()); } } +void XserverDesktop::bell() +{ + server->bell(); +} + +void XserverDesktop::setLEDState(unsigned int state) +{ + server->setLEDState(state); +} + void XserverDesktop::setDesktopName(const char* name) { try { @@ -435,11 +453,6 @@ void XserverDesktop::pointerEvent(const Point& pos, int buttonMask, vncPointerButtonAction(buttonMask, skipClick, skipRelease); } -void XserverDesktop::clientCutText(const char* str, int len) -{ - vncClientCutText(str, len); -} - unsigned int XserverDesktop::setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout) { @@ -453,6 +466,21 @@ unsigned int XserverDesktop::setScreenLayout(int fb_width, int fb_height, return ::setScreenLayout(fb_width, fb_height, layout, &outputIdMap); } +void XserverDesktop::handleClipboardRequest() +{ + vncHandleClipboardRequest(); +} + +void XserverDesktop::handleClipboardAnnounce(bool available) +{ + vncHandleClipboardAnnounce(available); +} + +void XserverDesktop::handleClipboardData(const char* data_) +{ + vncHandleClipboardData(data_); +} + void XserverDesktop::grabRegion(const rfb::Region& region) { if (directFbptr) diff --git a/unix/xserver/hw/vnc/XserverDesktop.h b/unix/xserver/hw/vnc/XserverDesktop.h index d78216e..3e0a298 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.h +++ b/unix/xserver/hw/vnc/XserverDesktop.h @@ -60,9 +60,11 @@ public: void unblockUpdates(); void setFramebuffer(int w, int h, void* fbptr, int stride); void refreshScreenLayout(); + void requestClipboard(); + void announceClipboard(bool available); + void sendClipboardData(const char* data); void bell(); void setLEDState(unsigned int state); - void serverCutText(const char* str, int len); void setDesktopName(const char* name); void setCursor(int width, int height, int hotX, int hotY, const unsigned char *rgbaData); @@ -90,9 +92,11 @@ public: virtual void pointerEvent(const rfb::Point& pos, int buttonMask, const bool skipClick, const bool skipRelease); virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); - virtual void clientCutText(const char* str, int len); virtual unsigned int setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout); + virtual void handleClipboardRequest(); + virtual void handleClipboardAnnounce(bool available); + virtual void handleClipboardData(const char* data); // rfb::PixelBuffer callbacks virtual void grabRegion(const rfb::Region& r); diff --git a/unix/xserver/hw/vnc/vncExtInit.cc b/unix/xserver/hw/vnc/vncExtInit.cc index cbc8b6b..6b60c0a 100644 --- a/unix/xserver/hw/vnc/vncExtInit.cc +++ b/unix/xserver/hw/vnc/vncExtInit.cc @@ -315,10 +315,22 @@ void vncUpdateDesktopName(void) desktop[scr]->setDesktopName(desktopName); } -void vncServerCutText(const char *text, size_t len) +void vncRequestClipboard(void) { for (int scr = 0; scr < vncGetScreenCount(); scr++) - desktop[scr]->serverCutText(text, len); + desktop[scr]->requestClipboard(); +} + +void vncAnnounceClipboard(int available) +{ + for (int scr = 0; scr < vncGetScreenCount(); scr++) + desktop[scr]->announceClipboard(available); +} + +void vncSendClipboardData(const char* data) +{ + for (int scr = 0; scr < vncGetScreenCount(); scr++) + desktop[scr]->sendClipboardData(data); } int vncConnectClient(const char *addr) diff --git a/unix/xserver/hw/vnc/vncExtInit.h b/unix/xserver/hw/vnc/vncExtInit.h index 5db6d4b..943537d 100644 --- a/unix/xserver/hw/vnc/vncExtInit.h +++ b/unix/xserver/hw/vnc/vncExtInit.h @@ -60,7 +60,9 @@ int vncGetSendPrimary(void); void vncUpdateDesktopName(void); -void vncServerCutText(const char *text, size_t len); +void vncRequestClipboard(void); +void vncAnnounceClipboard(int available); +void vncSendClipboardData(const char* data); int vncConnectClient(const char *addr); diff --git a/unix/xserver/hw/vnc/vncSelection.c b/unix/xserver/hw/vnc/vncSelection.c index 57e13e7..a1af467 100644 --- a/unix/xserver/hw/vnc/vncSelection.c +++ b/unix/xserver/hw/vnc/vncSelection.c @@ -1,4 +1,4 @@ -/* Copyright 2016 Pierre Ossman for Cendio AB +/* Copyright 2016-2019 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 @@ -47,15 +47,34 @@ static Atom xaTARGETS, xaTIMESTAMP, xaSTRING, xaTEXT, xaUTF8_STRING; static WindowPtr pWindow; static Window wid; -static char* clientCutText; -static int clientCutTextLen; +static Bool probing; +static Atom activeSelection = None; + +struct VncDataTarget { + ClientPtr client; + Atom selection; + Atom target; + Atom property; + Window requestor; + CARD32 time; + struct VncDataTarget* next; +}; + +static struct VncDataTarget* vncDataTargetHead; static int vncCreateSelectionWindow(void); static int vncOwnSelection(Atom selection); +static int vncConvertSelection(ClientPtr client, Atom selection, + Atom target, Atom property, + Window requestor, CARD32 time, + const char* data); static int vncProcConvertSelection(ClientPtr client); +static void vncSelectionRequest(Atom selection, Atom target); static int vncProcSendEvent(ClientPtr client); static void vncSelectionCallback(CallbackListPtr *callbacks, void * data, void * args); +static void vncClientStateCallback(CallbackListPtr * l, + void * d, void * p); static int (*origProcConvertSelection)(ClientPtr); static int (*origProcSendEvent)(ClientPtr); @@ -80,34 +99,99 @@ void vncSelectionInit(void) if (!AddCallback(&SelectionCallback, vncSelectionCallback, 0)) FatalError("Add VNC SelectionCallback failed\n"); + if (!AddCallback(&ClientStateCallback, vncClientStateCallback, 0)) + FatalError("Add VNC ClientStateCallback failed\n"); } -void vncClientCutText(const char* str, int len) +void vncHandleClipboardRequest(void) { - int rc; - - if (clientCutText != NULL) - free(clientCutText); - - clientCutText = malloc(len); - if (clientCutText == NULL) { - LOG_ERROR("Could not allocate clipboard buffer"); - DeleteWindowFromAnySelections(pWindow); + if (activeSelection == None) { + LOG_DEBUG("Got request for local clipboard although no clipboard is active"); return; } - memcpy(clientCutText, str, len); - clientCutTextLen = len; + LOG_DEBUG("Got request for local clipboard, re-probing formats"); + + probing = FALSE; + vncSelectionRequest(activeSelection, xaTARGETS); +} + +void vncHandleClipboardAnnounce(int available) +{ + if (available) { + int rc; + + LOG_DEBUG("Remote clipboard announced, grabbing local ownership"); + + if (vncGetSetPrimary()) { + rc = vncOwnSelection(xaPRIMARY); + if (rc != Success) + LOG_ERROR("Could not set PRIMARY selection"); + } - if (vncGetSetPrimary()) { - rc = vncOwnSelection(xaPRIMARY); + rc = vncOwnSelection(xaCLIPBOARD); if (rc != Success) - LOG_ERROR("Could not set PRIMARY selection"); + LOG_ERROR("Could not set CLIPBOARD selection"); + } else { + struct VncDataTarget* next; + + if (pWindow == NULL) + return; + + LOG_DEBUG("Remote clipboard lost, removing local ownership"); + + DeleteWindowFromAnySelections(pWindow); + + /* Abort any pending transfer */ + while (vncDataTargetHead != NULL) { + xEvent event; + + event.u.u.type = SelectionNotify; + event.u.selectionNotify.time = vncDataTargetHead->time; + event.u.selectionNotify.requestor = vncDataTargetHead->requestor; + event.u.selectionNotify.selection = vncDataTargetHead->selection; + event.u.selectionNotify.target = vncDataTargetHead->target; + event.u.selectionNotify.property = None; + WriteEventsToClient(vncDataTargetHead->client, 1, &event); + + next = vncDataTargetHead->next; + free(vncDataTargetHead); + vncDataTargetHead = next; + } } +} - vncOwnSelection(xaCLIPBOARD); - if (rc != Success) - LOG_ERROR("Could not set CLIPBOARD selection"); +void vncHandleClipboardData(const char* data) +{ + struct VncDataTarget* next; + + LOG_DEBUG("Got remote clipboard data, sending to X11 clients"); + + while (vncDataTargetHead != NULL) { + int rc; + xEvent event; + + rc = vncConvertSelection(vncDataTargetHead->client, + vncDataTargetHead->selection, + vncDataTargetHead->target, + vncDataTargetHead->property, + vncDataTargetHead->requestor, + vncDataTargetHead->time, + data); + if (rc != Success) { + event.u.u.type = SelectionNotify; + event.u.selectionNotify.time = vncDataTargetHead->time; + event.u.selectionNotify.requestor = vncDataTargetHead->requestor; + event.u.selectionNotify.selection = vncDataTargetHead->selection; + event.u.selectionNotify.target = vncDataTargetHead->target; + event.u.selectionNotify.property = None; + WriteEventsToClient(vncDataTargetHead->client, 1, &event); + } + + next = vncDataTargetHead->next; + free(vncDataTargetHead); + vncDataTargetHead = next; + } } static int vncCreateSelectionWindow(void) @@ -195,7 +279,8 @@ static int vncOwnSelection(Atom selection) static int vncConvertSelection(ClientPtr client, Atom selection, Atom target, Atom property, - Window requestor, CARD32 time) + Window requestor, CARD32 time, + const char* data) { Selection *pSel; WindowPtr pWin; @@ -205,8 +290,13 @@ static int vncConvertSelection(ClientPtr client, Atom selection, xEvent event; - LOG_DEBUG("Selection request for %s (type %s)", - NameForAtom(selection), NameForAtom(target)); + if (data == NULL) { + LOG_DEBUG("Selection request for %s (type %s)", + NameForAtom(selection), NameForAtom(target)); + } else { + LOG_DEBUG("Sending data for selection request for %s (type %s)", + NameForAtom(selection), NameForAtom(target)); + } rc = dixLookupSelection(&pSel, selection, client, DixGetAttrAccess); if (rc != Success) @@ -243,51 +333,59 @@ static int vncConvertSelection(ClientPtr client, Atom selection, TRUE); if (rc != Success) return rc; - } else if ((target == xaSTRING) || (target == xaTEXT)) { - rc = dixChangeWindowProperty(serverClient, pWin, realProperty, - XA_STRING, 8, PropModeReplace, - clientCutTextLen, clientCutText, - TRUE); - if (rc != Success) - return rc; - } else if (target == xaUTF8_STRING) { - unsigned char* buffer; - unsigned char* out; - size_t len; - - const unsigned char* in; - size_t in_len; - - buffer = malloc(clientCutTextLen*2); - if (buffer == NULL) - return BadAlloc; - - out = buffer; - len = 0; - in = clientCutText; - in_len = clientCutTextLen; - while (in_len > 0) { - if (*in & 0x80) { - *out++ = 0xc0 | (*in >> 6); - *out++ = 0x80 | (*in & 0x3f); - len += 2; - in++; - in_len--; + } else { + if (data == NULL) { + struct VncDataTarget* vdt; + + if ((target != xaSTRING) && (target != xaTEXT) && + (target != xaUTF8_STRING)) + return BadMatch; + + vdt = calloc(1, sizeof(struct VncDataTarget)); + if (vdt == NULL) + return BadAlloc; + + vdt->client = client; + vdt->selection = selection; + vdt->target = target; + vdt->property = property; + vdt->requestor = requestor; + vdt->time = time; + + vdt->next = vncDataTargetHead; + vncDataTargetHead = vdt; + + LOG_DEBUG("Requesting clipboard data from client"); + + vncRequestClipboard(); + + return Success; + } else { + if ((target == xaSTRING) || (target == xaTEXT)) { + char* latin1; + + latin1 = vncUTF8ToLatin1(data, (size_t)-1); + if (latin1 == NULL) + return BadAlloc; + + rc = dixChangeWindowProperty(serverClient, pWin, realProperty, + XA_STRING, 8, PropModeReplace, + strlen(latin1), latin1, TRUE); + + vncStrFree(latin1); + + if (rc != Success) + return rc; + } else if (target == xaUTF8_STRING) { + rc = dixChangeWindowProperty(serverClient, pWin, realProperty, + xaUTF8_STRING, 8, PropModeReplace, + strlen(data), data, TRUE); + if (rc != Success) + return rc; } else { - *out++ = *in++; - len++; - in_len--; + return BadMatch; } } - - rc = dixChangeWindowProperty(serverClient, pWin, realProperty, - xaUTF8_STRING, 8, PropModeReplace, - len, buffer, TRUE); - free(buffer); - if (rc != Success) - return rc; - } else { - return BadMatch; } event.u.u.type = SelectionNotify; @@ -326,7 +424,7 @@ static int vncProcConvertSelection(ClientPtr client) pSel->window == wid) { rc = vncConvertSelection(client, stuff->selection, stuff->target, stuff->property, - stuff->requestor, stuff->time); + stuff->requestor, stuff->time, NULL); if (rc != Success) { xEvent event; @@ -410,69 +508,61 @@ static void vncHandleSelection(Atom selection, Atom target, if (prop->type != XA_ATOM) return; - if (vncHasAtom(xaSTRING, (const Atom*)prop->data, prop->size)) - vncSelectionRequest(selection, xaSTRING); - else if (vncHasAtom(xaUTF8_STRING, (const Atom*)prop->data, prop->size)) - vncSelectionRequest(selection, xaUTF8_STRING); + if (probing) { + if (vncHasAtom(xaSTRING, (const Atom*)prop->data, prop->size) || + vncHasAtom(xaUTF8_STRING, (const Atom*)prop->data, prop->size)) { + LOG_DEBUG("Compatible format found, notifying clients"); + activeSelection = selection; + vncAnnounceClipboard(TRUE); + } + } else { + if (vncHasAtom(xaUTF8_STRING, (const Atom*)prop->data, prop->size)) + vncSelectionRequest(selection, xaUTF8_STRING); + else if (vncHasAtom(xaSTRING, (const Atom*)prop->data, prop->size)) + vncSelectionRequest(selection, xaSTRING); + } } else if (target == xaSTRING) { + char* filtered; + char* utf8; + if (prop->format != 8) return; if (prop->type != xaSTRING) return; - vncServerCutText(prop->data, prop->size); - } else if (target == xaUTF8_STRING) { - unsigned char* buffer; - unsigned char* out; - size_t len; + filtered = vncConvertLF(prop->data, prop->size); + if (filtered == NULL) + return; + + utf8 = vncLatin1ToUTF8(filtered, (size_t)-1); + vncStrFree(filtered); + if (utf8 == NULL) + return; + + LOG_DEBUG("Sending clipboard to clients (%d bytes)", + (int)strlen(utf8)); - const unsigned char* in; - size_t in_len; + vncSendClipboardData(utf8); + + vncStrFree(utf8); + } else if (target == xaUTF8_STRING) { + char *filtered; if (prop->format != 8) return; if (prop->type != xaUTF8_STRING) return; - buffer = malloc(prop->size); - if (buffer == NULL) + filtered = vncConvertLF(prop->data, prop->size); + if (filtered == NULL) return; - out = buffer; - len = 0; - in = prop->data; - in_len = prop->size; - while (in_len > 0) { - if ((*in & 0x80) == 0x00) { - *out++ = *in++; - len++; - in_len--; - } else if ((*in & 0xe0) == 0xc0) { - unsigned ucs; - ucs = (*in++ & 0x1f) << 6; - in_len--; - if (in_len > 0) { - ucs |= (*in++ & 0x3f); - in_len--; - } - if (ucs <= 0xff) - *out++ = ucs; - else - *out++ = '?'; - len++; - } else { - *out++ = '?'; - len++; - do { - in++; - in_len--; - } while ((in_len > 0) && ((*in & 0xc0) == 0x80)); - } - } + LOG_DEBUG("Sending clipboard to clients (%d bytes)", + (int)strlen(filtered)); - vncServerCutText((const char*)buffer, len); + vncSendClipboardData(filtered); - free(buffer); + vncStrFree(filtered); } } @@ -504,6 +594,12 @@ static void vncSelectionCallback(CallbackListPtr *callbacks, { SelectionInfoRec *info = (SelectionInfoRec *) args; + if (info->selection->selection == activeSelection) { + LOG_DEBUG("Local clipboard lost, notifying clients"); + activeSelection = None; + vncAnnounceClipboard(FALSE); + } + if (info->kind != SelectionSetOwner) return; if (info->client == serverClient) @@ -527,5 +623,25 @@ static void vncSelectionCallback(CallbackListPtr *callbacks, !vncGetSendPrimary()) return; + LOG_DEBUG("Got clipboard notification, probing for formats"); + + probing = TRUE; vncSelectionRequest(info->selection->selection, xaTARGETS); } + +static void vncClientStateCallback(CallbackListPtr * l, + void * d, void * p) +{ + ClientPtr client = ((NewClientInfoRec*)p)->client; + if (client->clientState == ClientStateGone) { + struct VncDataTarget** nextPtr = &vncDataTargetHead; + for (struct VncDataTarget* cur = vncDataTargetHead; cur; cur = *nextPtr) { + if (cur->client == client) { + *nextPtr = cur->next; + free(cur); + continue; + } + nextPtr = &cur->next; + } + } +} diff --git a/unix/xserver/hw/vnc/vncSelection.h b/unix/xserver/hw/vnc/vncSelection.h index 969f895..ea52bf2 100644 --- a/unix/xserver/hw/vnc/vncSelection.h +++ b/unix/xserver/hw/vnc/vncSelection.h @@ -1,4 +1,4 @@ -/* Copyright 2016 Pierre Ossman for Cendio AB +/* Copyright 2016-2019 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 @@ -24,7 +24,9 @@ extern "C" { void vncSelectionInit(void); -void vncClientCutText(const char* str, int len); +void vncHandleClipboardRequest(void); +void vncHandleClipboardAnnounce(int available); +void vncHandleClipboardData(const char* data); #ifdef __cplusplus }