Sync utf8 clipboard support

This commit is contained in:
Lauri Kasanen 2021-04-12 12:38:24 +03:00
parent 04461b9d4c
commit a1cf454f06
34 changed files with 1794 additions and 181 deletions

View File

@ -20,6 +20,7 @@
#include <string.h> #include <string.h>
#include <rfb/Exception.h> #include <rfb/Exception.h>
#include <rfb/clipboardTypes.h>
#include <rfb/fenceTypes.h> #include <rfb/fenceTypes.h>
#include <rfb/CMsgReader.h> #include <rfb/CMsgReader.h>
#include <rfb/CMsgWriter.h> #include <rfb/CMsgWriter.h>
@ -42,7 +43,8 @@ CConnection::CConnection()
: csecurity(0), is(0), os(0), reader_(0), writer_(0), : csecurity(0), is(0), os(0), reader_(0), writer_(0),
shared(false), shared(false),
state_(RFBSTATE_UNINITIALISED), useProtocol3_3(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; reader_ = 0;
delete writer_; delete writer_;
writer_ = 0; writer_ = 0;
strFree(serverClipboard);
} }
void CConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_) 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); 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() void CConnection::authSuccess()
{ {
} }
@ -364,3 +440,53 @@ void CConnection::fence(rdr::U32 flags, unsigned len, const char data[])
writer()->writeFence(flags, len, 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));
}
}

View File

@ -107,6 +107,17 @@ namespace rfb {
virtual void framebufferUpdateEnd(); virtual void framebufferUpdateEnd();
virtual void dataRect(const Rect& r, int encoding); 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 // Methods to be overridden in a derived class
@ -121,9 +132,43 @@ namespace rfb {
// derived class must call on to CConnection::serverInit(). // derived class must call on to CConnection::serverInit().
virtual void 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 // 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_; } CMsgReader* reader() { return reader_; }
CMsgWriter* writer() { return writer_; } CMsgWriter* writer() { return writer_; }
@ -190,6 +235,9 @@ namespace rfb {
ModifiablePixelBuffer* framebuffer; ModifiablePixelBuffer* framebuffer;
DecodeManager decoder; DecodeManager decoder;
char* serverClipboard;
bool hasLocalClipboard;
}; };
} }
#endif #endif

View File

@ -92,3 +92,26 @@ void CMsgHandler::setLEDState(unsigned int state)
{ {
cp.setLEDState(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)
{
}

View File

@ -72,6 +72,15 @@ namespace rfb {
virtual void setLEDState(unsigned int state); 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; ConnParams cp;
}; };
} }

View File

@ -18,10 +18,14 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <rdr/OutStream.h> #include <rdr/OutStream.h>
#include <rdr/MemOutStream.h>
#include <rdr/ZlibOutStream.h>
#include <rfb/msgTypes.h> #include <rfb/msgTypes.h>
#include <rfb/fenceTypes.h> #include <rfb/fenceTypes.h>
#include <rfb/encodings.h> #include <rfb/encodings.h>
#include <rfb/qemuTypes.h> #include <rfb/qemuTypes.h>
#include <rfb/clipboardTypes.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>
@ -265,6 +269,104 @@ void CMsgWriter::writeClientCutText(const char* str, rdr::U32 len)
endMsg(); 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) void CMsgWriter::startMsg(int type)
{ {
os->writeU8(type); os->writeU8(type);

View File

@ -56,6 +56,13 @@ namespace rfb {
void writePointerEvent(const Point& pos, int buttonMask); void writePointerEvent(const Point& pos, int buttonMask);
void writeClientCutText(const char* str, rdr::U32 len); 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: protected:
void startMsg(int type); void startMsg(int type);
void endMsg(); void endMsg();

View File

@ -24,6 +24,7 @@
#include <rfb/Exception.h> #include <rfb/Exception.h>
#include <rfb/encodings.h> #include <rfb/encodings.h>
#include <rfb/ledStates.h> #include <rfb/ledStates.h>
#include <rfb/clipboardTypes.h>
#include <rfb/ConnParams.h> #include <rfb/ConnParams.h>
#include <rfb/ServerCore.h> #include <rfb/ServerCore.h>
#include <rfb/SMsgHandler.h> #include <rfb/SMsgHandler.h>
@ -42,7 +43,7 @@ ConnParams::ConnParams()
supportsLEDState(false), supportsQEMUKeyEvent(false), supportsLEDState(false), supportsQEMUKeyEvent(false),
supportsWEBP(false), supportsWEBP(false),
supportsSetDesktopSize(false), supportsFence(false), supportsSetDesktopSize(false), supportsFence(false),
supportsContinuousUpdates(false), supportsContinuousUpdates(false), supportsExtendedClipboard(false),
compressLevel(2), qualityLevel(-1), fineQualityLevel(-1), compressLevel(2), qualityLevel(-1), fineQualityLevel(-1),
subsampling(subsampleUndefined), name_(0), cursorPos_(0, 0), verStrPos(0), subsampling(subsampleUndefined), name_(0), cursorPos_(0, 0), verStrPos(0),
ledState_(ledUnknown), shandler(NULL) ledState_(ledUnknown), shandler(NULL)
@ -50,6 +51,11 @@ ConnParams::ConnParams()
memset(kasmPassed, 0, KASM_NUM_SETTINGS); memset(kasmPassed, 0, KASM_NUM_SETTINGS);
setName(""); setName("");
cursor_ = new Cursor(0, 0, Point(), NULL); 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() ConnParams::~ConnParams()
@ -177,6 +183,9 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings)
case pseudoEncodingContinuousUpdates: case pseudoEncodingContinuousUpdates:
supportsContinuousUpdates = true; supportsContinuousUpdates = true;
break; break;
case pseudoEncodingExtendedClipboard:
supportsExtendedClipboard = true;
break;
case pseudoEncodingSubsamp1X: case pseudoEncodingSubsamp1X:
subsampling = subsampleNone; subsampling = subsampleNone;
break; break;
@ -268,3 +277,17 @@ void ConnParams::setLEDState(unsigned int state)
{ {
ledState_ = 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++];
}
}

View File

@ -94,6 +94,9 @@ namespace rfb {
unsigned int ledState() { return ledState_; } unsigned int ledState() { return ledState_; }
void setLEDState(unsigned int state); void setLEDState(unsigned int state);
rdr::U32 clipboardFlags() const { return clipFlags; }
void setClipboardCaps(rdr::U32 flags, const rdr::U32* lengths);
bool useCopyRect; bool useCopyRect;
bool supportsLocalCursor; bool supportsLocalCursor;
@ -111,6 +114,7 @@ namespace rfb {
bool supportsSetDesktopSize; bool supportsSetDesktopSize;
bool supportsFence; bool supportsFence;
bool supportsContinuousUpdates; bool supportsContinuousUpdates;
bool supportsExtendedClipboard;
int compressLevel; int compressLevel;
int qualityLevel; int qualityLevel;
@ -146,6 +150,8 @@ namespace rfb {
int verStrPos; int verStrPos;
unsigned int ledState_; unsigned int ledState_;
SMsgHandler *shandler; SMsgHandler *shandler;
rdr::U32 clipFlags;
rdr::U32 clipSizes[16];
}; };
} }
#endif #endif

View File

@ -20,6 +20,7 @@
#include <string.h> #include <string.h>
#include <rfb/Exception.h> #include <rfb/Exception.h>
#include <rfb/Security.h> #include <rfb/Security.h>
#include <rfb/clipboardTypes.h>
#include <rfb/msgTypes.h> #include <rfb/msgTypes.h>
#include <rfb/fenceTypes.h> #include <rfb/fenceTypes.h>
#include <rfb/SMsgReader.h> #include <rfb/SMsgReader.h>
@ -52,7 +53,8 @@ SConnection::SConnection()
: readyForSetColourMapEntries(false), : readyForSetColourMapEntries(false),
is(0), os(0), reader_(0), writer_(0), is(0), os(0), reader_(0), writer_(0),
ssecurity(0), state_(RFBSTATE_UNINITIALISED), ssecurity(0), state_(RFBSTATE_UNINITIALISED),
preferredEncoding(encodingRaw) preferredEncoding(encodingRaw),
clientClipboard(NULL), hasLocalClipboard(false)
{ {
defaultMajorVersion = 3; defaultMajorVersion = 3;
defaultMinorVersion = 8; defaultMinorVersion = 8;
@ -69,6 +71,7 @@ SConnection::~SConnection()
reader_ = 0; reader_ = 0;
delete writer_; delete writer_;
writer_ = 0; writer_ = 0;
strFree(clientClipboard);
} }
void SConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_) 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); 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() 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) void SConnection::writeFakeColourMap(void)
{ {
int i; int i;

View File

@ -73,6 +73,15 @@ namespace rfb {
virtual void setEncodings(int nEncodings, const rdr::S32* encodings); 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(); virtual void supportsQEMUKeyEvent();
// Methods to be overridden in a derived class // Methods to be overridden in a derived class
@ -118,6 +127,25 @@ namespace rfb {
virtual void enableContinuousUpdates(bool enable, virtual void enableContinuousUpdates(bool enable,
int x, int y, int w, int h); 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() {} virtual void add_changed_all() {}
// setAccessRights() allows a security package to limit the access rights // setAccessRights() allows a security package to limit the access rights
@ -138,6 +166,22 @@ namespace rfb {
// Other methods // 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 // authenticated() returns true if the client has authenticated
// successfully. // successfully.
bool authenticated() { return (state_ == RFBSTATE_INITIALISATION || bool authenticated() { return (state_ == RFBSTATE_INITIALISATION ||
@ -203,6 +247,9 @@ namespace rfb {
SSecurity* ssecurity; SSecurity* ssecurity;
stateEnum state_; stateEnum state_;
rdr::S32 preferredEncoding; rdr::S32 preferredEncoding;
char* clientClipboard;
bool hasLocalClipboard;
}; };
} }
#endif #endif

View File

@ -77,6 +77,25 @@ namespace rfb {
// pointerEvent(), keyEvent() and clientCutText() are called in response to // pointerEvent(), keyEvent() and clientCutText() are called in response to
// the relevant RFB protocol messages from clients. // the relevant RFB protocol messages from clients.
// See InputHandler for method signatures. // 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: protected:
virtual ~SDesktop() {} virtual ~SDesktop() {}
}; };

View File

@ -64,6 +64,29 @@ void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings)
supportsQEMUKeyEvent(); 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() void SMsgHandler::supportsLocalCursor()
{ {
} }

View File

@ -54,6 +54,15 @@ namespace rfb {
virtual void enableContinuousUpdates(bool enable, virtual void enableContinuousUpdates(bool enable,
int x, int y, int w, int h) = 0; 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 void sendStats() = 0;
virtual bool canChangeKasmSettings() const = 0; virtual bool canChangeKasmSettings() const = 0;

View File

@ -18,8 +18,11 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <rdr/InStream.h> #include <rdr/InStream.h>
#include <rdr/ZlibInStream.h>
#include <rfb/msgTypes.h> #include <rfb/msgTypes.h>
#include <rfb/qemuTypes.h> #include <rfb/qemuTypes.h>
#include <rfb/clipboardTypes.h>
#include <rfb/Exception.h> #include <rfb/Exception.h>
#include <rfb/util.h> #include <rfb/util.h>
#include <rfb/SMsgHandler.h> #include <rfb/SMsgHandler.h>
@ -224,11 +227,15 @@ void SMsgReader::readPointerEvent()
void SMsgReader::readClientCutText() void SMsgReader::readClientCutText()
{ {
is->skip(3); is->skip(3);
int len = is->readU32(); rdr::U32 len = is->readU32();
if (len < 0) {
throw Exception("Cut text too long."); if (len & 0x80000000) {
rdr::S32 slen = len;
slen = -slen;
readExtendedClipboard(slen);
return;
} }
if (len > maxCutText) { if (len > (size_t)maxCutText) {
is->skip(len); is->skip(len);
vlog.error("Cut text too long (%d bytes) - ignoring", len); vlog.error("Cut text too long (%d bytes) - ignoring", len);
return; return;
@ -239,6 +246,100 @@ void SMsgReader::readClientCutText()
handler->clientCutText(ca.buf, len); 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() void SMsgReader::readRequestStats()
{ {
is->skip(3); is->skip(3);

View File

@ -55,6 +55,7 @@ namespace rfb {
void readKeyEvent(); void readKeyEvent();
void readPointerEvent(); void readPointerEvent();
void readClientCutText(); void readClientCutText();
void readExtendedClipboard(rdr::S32 len);
void readRequestStats(); void readRequestStats();
void readQEMUMessage(); void readQEMUMessage();

View File

@ -19,8 +19,12 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <rdr/OutStream.h> #include <rdr/OutStream.h>
#include <rdr/MemOutStream.h>
#include <rdr/ZlibOutStream.h>
#include <rfb/msgTypes.h> #include <rfb/msgTypes.h>
#include <rfb/fenceTypes.h> #include <rfb/fenceTypes.h>
#include <rfb/clipboardTypes.h>
#include <rfb/Exception.h> #include <rfb/Exception.h>
#include <rfb/ConnParams.h> #include <rfb/ConnParams.h>
#include <rfb/UpdateTracker.h> #include <rfb/UpdateTracker.h>
@ -89,6 +93,112 @@ void SMsgWriter::writeServerCutText(const char* str, int len)
endMsg(); 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) void SMsgWriter::writeStats(const char* str, int len)
{ {
startMsg(msgTypeStats); startMsg(msgTypeStats);

View File

@ -55,6 +55,14 @@ namespace rfb {
// writeBell() and writeServerCutText() do the obvious thing. // writeBell() and writeServerCutText() do the obvious thing.
void writeBell(); void writeBell();
void writeServerCutText(const char* str, int len); 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); void writeStats(const char* str, int len);
// writeFence() sends a new fence request or response to the client. // writeFence() sends a new fence request or response to the client.

View File

@ -403,7 +403,31 @@ static void keylog(unsigned keysym, const char *client) {
flushKeylog(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 { try {
if (!(accessRights & AccessCutText)) return; if (!(accessRights & AccessCutText)) return;
@ -413,19 +437,19 @@ void VNCSConnectionST::serverCutTextOrClose(const char *str, int len)
sock->getPeerAddress()); sock->getPeerAddress());
return; return;
} }
int len = strlen(data);
const int origlen = len; const int origlen = len;
if (rfb::Server::DLP_ClipSendMax && len > rfb::Server::DLP_ClipSendMax) if (rfb::Server::DLP_ClipSendMax && len > rfb::Server::DLP_ClipSendMax)
len = rfb::Server::DLP_ClipSendMax; len = rfb::Server::DLP_ClipSendMax;
cliplog(str, len, origlen, "sent", sock->getPeerAddress()); cliplog(data, len, origlen, "sent", sock->getPeerAddress());
if (state() == RFBSTATE_NORMAL) if (state() != RFBSTATE_NORMAL) return;
writer()->writeServerCutText(str, len); sendClipboardData(data, len);
gettimeofday(&lastClipboardOp, NULL); gettimeofday(&lastClipboardOp, NULL);
} catch(rdr::Exception& e) { } catch(rdr::Exception& e) {
close(e.str()); close(e.str());
} }
} }
void VNCSConnectionST::setDesktopNameOrClose(const char *name) void VNCSConnectionST::setDesktopNameOrClose(const char *name)
{ {
try { try {
@ -846,24 +870,6 @@ void VNCSConnectionST::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) {
server->desktop->keyEvent(keysym, keycode, 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) void VNCSConnectionST::framebufferUpdateRequest(const Rect& r,bool incremental)
{ {
Rect safeRect; 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 // supportsLocalCursor() is called whenever the status of
// cp.supportsLocalCursor has changed. If the client does now support local // 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 // cursor, we make sure that the old server-side rendered cursor is cleaned up

View File

@ -75,9 +75,11 @@ namespace rfb {
void screenLayoutChangeOrClose(rdr::U16 reason); void screenLayoutChangeOrClose(rdr::U16 reason);
void setCursorOrClose(); void setCursorOrClose();
void bellOrClose(); void bellOrClose();
void serverCutTextOrClose(const char *str, int len);
void setDesktopNameOrClose(const char *name); void setDesktopNameOrClose(const char *name);
void setLEDStateOrClose(unsigned int state); 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 // checkIdleTimeout() returns the number of milliseconds left until the
// idle timeout expires. If it has expired, the connection is closed and // 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 setPixelFormat(const PixelFormat& pf);
virtual void pointerEvent(const Point& pos, int buttonMask, const bool skipClick, const bool skipRelease); 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 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 framebufferUpdateRequest(const Rect& r, bool incremental);
virtual void setDesktopSize(int fb_width, int fb_height, virtual void setDesktopSize(int fb_width, int fb_height,
const ScreenSet& layout); const ScreenSet& layout);
virtual void fence(rdr::U32 flags, unsigned len, const char data[]); virtual void fence(rdr::U32 flags, unsigned len, const char data[]);
virtual void enableContinuousUpdates(bool enable, virtual void enableContinuousUpdates(bool enable,
int x, int y, int w, int h); 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 supportsLocalCursor();
virtual void supportsFence(); virtual void supportsFence();
virtual void supportsContinuousUpdates(); virtual void supportsContinuousUpdates();

View File

@ -52,9 +52,21 @@ namespace rfb {
// getPixelBuffer() returns a pointer to the PixelBuffer object. // getPixelBuffer() returns a pointer to the PixelBuffer object.
virtual PixelBuffer* getPixelBuffer() const = 0; virtual PixelBuffer* getPixelBuffer() const = 0;
// serverCutText() tells the server that the cut text has changed. This // requestClipboard() will result in a request to a client to
// will normally be sent to all clients. // transfer its clipboard data. A call to
virtual void serverCutText(const char* str, int len) = 0; // 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. // bell() tells the server that it should make all clients make a bell sound.
virtual void bell() = 0; virtual void bell() = 0;

View File

@ -123,8 +123,8 @@ static void parseRegionPart(const bool percents, rdr::U16 &pcdest, int &dest,
VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_)
: blHosts(&blacklist), desktop(desktop_), desktopStarted(false), : blHosts(&blacklist), desktop(desktop_), desktopStarted(false),
blockCounter(0), pb(0), blackedpb(0), ledState(ledUnknown), blockCounter(0), pb(0), blackedpb(0), ledState(ledUnknown),
name(strDup(name_)), pointerClient(0), comparer(0), name(strDup(name_)), pointerClient(0), clipboardClient(0),
cursor(new Cursor(0, 0, Point(), NULL)), comparer(0), cursor(new Cursor(0, 0, Point(), NULL)),
renderedCursorInvalid(false), renderedCursorInvalid(false),
queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance), queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance),
lastConnectionTime(0), disableclients(false), lastConnectionTime(0), disableclients(false),
@ -502,6 +502,45 @@ void VNCServerST::setScreenLayout(const ScreenSet& layout)
} }
} }
void VNCServerST::requestClipboard()
{
if (clipboardClient == NULL)
return;
clipboardClient->requestClipboard();
}
void VNCServerST::announceClipboard(bool available)
{
std::list<VNCSConnectionST*>::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)->announceClipboard(available);
}
}
void VNCServerST::sendClipboardData(const char* data)
{
std::list<VNCSConnectionST*>::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::bell() void VNCServerST::bell()
{ {
std::list<VNCSConnectionST*>::iterator ci, ci_next; std::list<VNCSConnectionST*>::iterator ci, ci_next;
@ -511,15 +550,6 @@ void VNCServerST::bell()
} }
} }
void VNCServerST::serverCutText(const char* str, int len)
{
std::list<VNCSConnectionST*>::iterator ci, ci_next;
for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
ci_next = ci; ci_next++;
(*ci)->serverCutTextOrClose(str, len);
}
}
void VNCServerST::setName(const char* name_) void VNCServerST::setName(const char* name_)
{ {
name.replaceBuf(strDup(name_)); name.replaceBuf(strDup(name_));
@ -1052,3 +1082,32 @@ bool VNCServerST::getComparerState()
} }
return false; 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);
}

View File

@ -96,7 +96,9 @@ namespace rfb {
virtual void setPixelBuffer(PixelBuffer* pb); virtual void setPixelBuffer(PixelBuffer* pb);
virtual void setScreenLayout(const ScreenSet& layout); virtual void setScreenLayout(const ScreenSet& layout);
virtual PixelBuffer* getPixelBuffer() const { if (DLPRegion.enabled && blackedpb) return blackedpb; else return pb; } 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 &region); virtual void add_changed(const Region &region);
virtual void add_copied(const Region &dest, const Point &delta); virtual void add_copied(const Region &dest, const Point &delta);
virtual void setCursor(int width, int height, const Point& hotspot, virtual void setCursor(int width, int height, const Point& hotspot,
@ -189,6 +191,10 @@ namespace rfb {
void setAPIMessager(network::GetAPIMessager *msgr) { apimessager = msgr; } 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: protected:
friend class VNCSConnectionST; friend class VNCSConnectionST;
@ -217,6 +223,8 @@ namespace rfb {
std::list<VNCSConnectionST*> clients; std::list<VNCSConnectionST*> clients;
VNCSConnectionST* pointerClient; VNCSConnectionST* pointerClient;
VNCSConnectionST* clipboardClient;
std::list<VNCSConnectionST*> clipboardRequestors;
std::list<network::Socket*> closingSockets; std::list<network::Socket*> closingSockets;
static EncCache encCache; static EncCache encCache;

View File

@ -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

View File

@ -88,6 +88,9 @@ namespace rfb {
// VMware-specific // VMware-specific
const int pseudoEncodingVMwareCursorPosition = 0x574d5666; const int pseudoEncodingVMwareCursorPosition = 0x574d5666;
// UltraVNC-specific
const int pseudoEncodingExtendedClipboard = 0xC0A1E5CE;
int encodingNum(const char* name); int encodingNum(const char* name);
const char* encodingName(int num); const char* encodingName(int num);
} }

View File

@ -1,4 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. /* 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 * 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
@ -63,6 +64,10 @@ namespace rfb {
delete [] s; delete [] s;
} }
void strFree(wchar_t* s) {
delete [] s;
}
bool strSplit(const char* src, const char limiter, char** out1, char** out2, bool fromEnd) { bool strSplit(const char* src, const char limiter, char** out1, char** out2, bool fromEnd) {
CharArray out1old, out2old; CharArray out1old, out2old;
@ -107,6 +112,444 @@ namespace rfb {
dest[src ? destlen-1 : 0] = 0; 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, unsigned msBetween(const struct timeval *first,
const struct timeval *second) const struct timeval *second)
{ {

View File

@ -1,4 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. /* 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 * 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
@ -67,6 +68,7 @@ namespace rfb {
char* strDup(const char* s); char* strDup(const char* s);
void strFree(char* s); void strFree(char* s);
void strFree(wchar_t* s);
// Returns true if split successful. Returns false otherwise. // Returns true if split successful. Returns false otherwise.
// ALWAYS *copies* first part of string to out1 buffer. // 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 // Copies src to dest, up to specified length-1, and guarantees termination
void strCopy(char* dest, const char* src, int destlen); 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 // HELPER functions for timeout handling

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. /* 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 * 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
@ -226,3 +226,35 @@ int vncIsTCPPortUsed(int port)
} }
return 0; 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);
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. /* 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 * 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
@ -50,6 +50,13 @@ void vncListParams(int width, int nameWidth);
int vncGetSocketPort(int fd); int vncGetSocketPort(int fd);
int vncIsTCPPortUsed(int port); 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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -176,6 +176,33 @@ XserverDesktop::queryConnection(network::Socket* sock,
return rfb::VNCServerST::PENDING; return rfb::VNCServerST::PENDING;
} }
void XserverDesktop::requestClipboard()
{
try {
server->requestClipboard();
} catch (rdr::Exception& e) {
vlog.error("XserverDesktop::requestClipboard: %s",e.str());
}
}
void XserverDesktop::announceClipboard(bool available)
{
try {
server->announceClipboard(available);
} catch (rdr::Exception& e) {
vlog.error("XserverDesktop::announceClipboard: %s",e.str());
}
}
void XserverDesktop::sendClipboardData(const char* data)
{
try {
server->sendClipboardData(data);
} catch (rdr::Exception& e) {
vlog.error("XserverDesktop::sendClipboardData: %s",e.str());
}
}
void XserverDesktop::bell() void XserverDesktop::bell()
{ {
server->bell(); server->bell();
@ -186,15 +213,6 @@ void XserverDesktop::setLEDState(unsigned int state)
server->setLEDState(state); server->setLEDState(state);
} }
void XserverDesktop::serverCutText(const char* str, int len)
{
try {
server->serverCutText(str, len);
} catch (rdr::Exception& e) {
vlog.error("XserverDesktop::serverCutText: %s",e.str());
}
}
void XserverDesktop::setDesktopName(const char* name) void XserverDesktop::setDesktopName(const char* name)
{ {
try { try {
@ -435,11 +453,6 @@ void XserverDesktop::pointerEvent(const Point& pos, int buttonMask,
vncPointerButtonAction(buttonMask, skipClick, skipRelease); 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, unsigned int XserverDesktop::setScreenLayout(int fb_width, int fb_height,
const rfb::ScreenSet& layout) 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); 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) void XserverDesktop::grabRegion(const rfb::Region& region)
{ {
if (directFbptr) if (directFbptr)

View File

@ -60,9 +60,11 @@ public:
void unblockUpdates(); void unblockUpdates();
void setFramebuffer(int w, int h, void* fbptr, int stride); void setFramebuffer(int w, int h, void* fbptr, int stride);
void refreshScreenLayout(); void refreshScreenLayout();
void requestClipboard();
void announceClipboard(bool available);
void sendClipboardData(const char* data);
void bell(); void bell();
void setLEDState(unsigned int state); void setLEDState(unsigned int state);
void serverCutText(const char* str, int len);
void setDesktopName(const char* name); void setDesktopName(const char* name);
void setCursor(int width, int height, int hotX, int hotY, void setCursor(int width, int height, int hotX, int hotY,
const unsigned char *rgbaData); const unsigned char *rgbaData);
@ -90,9 +92,11 @@ public:
virtual void pointerEvent(const rfb::Point& pos, int buttonMask, virtual void pointerEvent(const rfb::Point& pos, int buttonMask,
const bool skipClick, const bool skipRelease); const bool skipClick, const bool skipRelease);
virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); 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, virtual unsigned int setScreenLayout(int fb_width, int fb_height,
const rfb::ScreenSet& layout); const rfb::ScreenSet& layout);
virtual void handleClipboardRequest();
virtual void handleClipboardAnnounce(bool available);
virtual void handleClipboardData(const char* data);
// rfb::PixelBuffer callbacks // rfb::PixelBuffer callbacks
virtual void grabRegion(const rfb::Region& r); virtual void grabRegion(const rfb::Region& r);

View File

@ -315,10 +315,22 @@ void vncUpdateDesktopName(void)
desktop[scr]->setDesktopName(desktopName); desktop[scr]->setDesktopName(desktopName);
} }
void vncServerCutText(const char *text, size_t len) void vncRequestClipboard(void)
{ {
for (int scr = 0; scr < vncGetScreenCount(); scr++) 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) int vncConnectClient(const char *addr)

View File

@ -60,7 +60,9 @@ int vncGetSendPrimary(void);
void vncUpdateDesktopName(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); int vncConnectClient(const char *addr);

View File

@ -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 * 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
@ -47,15 +47,34 @@ static Atom xaTARGETS, xaTIMESTAMP, xaSTRING, xaTEXT, xaUTF8_STRING;
static WindowPtr pWindow; static WindowPtr pWindow;
static Window wid; static Window wid;
static char* clientCutText; static Bool probing;
static int clientCutTextLen; 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 vncCreateSelectionWindow(void);
static int vncOwnSelection(Atom selection); 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 int vncProcConvertSelection(ClientPtr client);
static void vncSelectionRequest(Atom selection, Atom target);
static int vncProcSendEvent(ClientPtr client); static int vncProcSendEvent(ClientPtr client);
static void vncSelectionCallback(CallbackListPtr *callbacks, static void vncSelectionCallback(CallbackListPtr *callbacks,
void * data, void * args); void * data, void * args);
static void vncClientStateCallback(CallbackListPtr * l,
void * d, void * p);
static int (*origProcConvertSelection)(ClientPtr); static int (*origProcConvertSelection)(ClientPtr);
static int (*origProcSendEvent)(ClientPtr); static int (*origProcSendEvent)(ClientPtr);
@ -80,34 +99,99 @@ void vncSelectionInit(void)
if (!AddCallback(&SelectionCallback, vncSelectionCallback, 0)) if (!AddCallback(&SelectionCallback, vncSelectionCallback, 0))
FatalError("Add VNC SelectionCallback failed\n"); 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 (activeSelection == None) {
LOG_DEBUG("Got request for local clipboard although no clipboard is active");
if (clientCutText != NULL)
free(clientCutText);
clientCutText = malloc(len);
if (clientCutText == NULL) {
LOG_ERROR("Could not allocate clipboard buffer");
DeleteWindowFromAnySelections(pWindow);
return; return;
} }
memcpy(clientCutText, str, len); LOG_DEBUG("Got request for local clipboard, re-probing formats");
clientCutTextLen = len;
if (vncGetSetPrimary()) { probing = FALSE;
rc = vncOwnSelection(xaPRIMARY); 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");
}
rc = vncOwnSelection(xaCLIPBOARD);
if (rc != Success) if (rc != Success)
LOG_ERROR("Could not set PRIMARY selection"); LOG_ERROR("Could not set CLIPBOARD selection");
} } else {
struct VncDataTarget* next;
vncOwnSelection(xaCLIPBOARD); if (pWindow == NULL)
if (rc != Success) return;
LOG_ERROR("Could not set CLIPBOARD selection");
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;
}
}
}
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) static int vncCreateSelectionWindow(void)
@ -195,7 +279,8 @@ static int vncOwnSelection(Atom selection)
static int vncConvertSelection(ClientPtr client, Atom selection, static int vncConvertSelection(ClientPtr client, Atom selection,
Atom target, Atom property, Atom target, Atom property,
Window requestor, CARD32 time) Window requestor, CARD32 time,
const char* data)
{ {
Selection *pSel; Selection *pSel;
WindowPtr pWin; WindowPtr pWin;
@ -205,8 +290,13 @@ static int vncConvertSelection(ClientPtr client, Atom selection,
xEvent event; xEvent event;
LOG_DEBUG("Selection request for %s (type %s)", if (data == NULL) {
NameForAtom(selection), NameForAtom(target)); 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); rc = dixLookupSelection(&pSel, selection, client, DixGetAttrAccess);
if (rc != Success) if (rc != Success)
@ -243,51 +333,59 @@ static int vncConvertSelection(ClientPtr client, Atom selection,
TRUE); TRUE);
if (rc != Success) if (rc != Success)
return rc; return rc;
} else if ((target == xaSTRING) || (target == xaTEXT)) { } else {
rc = dixChangeWindowProperty(serverClient, pWin, realProperty, if (data == NULL) {
XA_STRING, 8, PropModeReplace, struct VncDataTarget* vdt;
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; if ((target != xaSTRING) && (target != xaTEXT) &&
size_t in_len; (target != xaUTF8_STRING))
return BadMatch;
buffer = malloc(clientCutTextLen*2); vdt = calloc(1, sizeof(struct VncDataTarget));
if (buffer == NULL) if (vdt == NULL)
return BadAlloc; return BadAlloc;
out = buffer; vdt->client = client;
len = 0; vdt->selection = selection;
in = clientCutText; vdt->target = target;
in_len = clientCutTextLen; vdt->property = property;
while (in_len > 0) { vdt->requestor = requestor;
if (*in & 0x80) { vdt->time = time;
*out++ = 0xc0 | (*in >> 6);
*out++ = 0x80 | (*in & 0x3f); vdt->next = vncDataTargetHead;
len += 2; vncDataTargetHead = vdt;
in++;
in_len--; 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 { } else {
*out++ = *in++; return BadMatch;
len++;
in_len--;
} }
} }
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; event.u.u.type = SelectionNotify;
@ -326,7 +424,7 @@ static int vncProcConvertSelection(ClientPtr client)
pSel->window == wid) { pSel->window == wid) {
rc = vncConvertSelection(client, stuff->selection, rc = vncConvertSelection(client, stuff->selection,
stuff->target, stuff->property, stuff->target, stuff->property,
stuff->requestor, stuff->time); stuff->requestor, stuff->time, NULL);
if (rc != Success) { if (rc != Success) {
xEvent event; xEvent event;
@ -410,69 +508,61 @@ static void vncHandleSelection(Atom selection, Atom target,
if (prop->type != XA_ATOM) if (prop->type != XA_ATOM)
return; return;
if (vncHasAtom(xaSTRING, (const Atom*)prop->data, prop->size)) if (probing) {
vncSelectionRequest(selection, xaSTRING); if (vncHasAtom(xaSTRING, (const Atom*)prop->data, prop->size) ||
else if (vncHasAtom(xaUTF8_STRING, (const Atom*)prop->data, prop->size)) vncHasAtom(xaUTF8_STRING, (const Atom*)prop->data, prop->size)) {
vncSelectionRequest(selection, xaUTF8_STRING); 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) { } else if (target == xaSTRING) {
char* filtered;
char* utf8;
if (prop->format != 8) if (prop->format != 8)
return; return;
if (prop->type != xaSTRING) if (prop->type != xaSTRING)
return; return;
vncServerCutText(prop->data, prop->size); filtered = vncConvertLF(prop->data, prop->size);
} else if (target == xaUTF8_STRING) { if (filtered == NULL)
unsigned char* buffer; return;
unsigned char* out;
size_t len;
const unsigned char* in; utf8 = vncLatin1ToUTF8(filtered, (size_t)-1);
size_t in_len; vncStrFree(filtered);
if (utf8 == NULL)
return;
LOG_DEBUG("Sending clipboard to clients (%d bytes)",
(int)strlen(utf8));
vncSendClipboardData(utf8);
vncStrFree(utf8);
} else if (target == xaUTF8_STRING) {
char *filtered;
if (prop->format != 8) if (prop->format != 8)
return; return;
if (prop->type != xaUTF8_STRING) if (prop->type != xaUTF8_STRING)
return; return;
buffer = malloc(prop->size); filtered = vncConvertLF(prop->data, prop->size);
if (buffer == NULL) if (filtered == NULL)
return; return;
out = buffer; LOG_DEBUG("Sending clipboard to clients (%d bytes)",
len = 0; (int)strlen(filtered));
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));
}
}
vncServerCutText((const char*)buffer, len); vncSendClipboardData(filtered);
free(buffer); vncStrFree(filtered);
} }
} }
@ -504,6 +594,12 @@ static void vncSelectionCallback(CallbackListPtr *callbacks,
{ {
SelectionInfoRec *info = (SelectionInfoRec *) args; SelectionInfoRec *info = (SelectionInfoRec *) args;
if (info->selection->selection == activeSelection) {
LOG_DEBUG("Local clipboard lost, notifying clients");
activeSelection = None;
vncAnnounceClipboard(FALSE);
}
if (info->kind != SelectionSetOwner) if (info->kind != SelectionSetOwner)
return; return;
if (info->client == serverClient) if (info->client == serverClient)
@ -527,5 +623,25 @@ static void vncSelectionCallback(CallbackListPtr *callbacks,
!vncGetSendPrimary()) !vncGetSendPrimary())
return; return;
LOG_DEBUG("Got clipboard notification, probing for formats");
probing = TRUE;
vncSelectionRequest(info->selection->selection, xaTARGETS); 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;
}
}
}

View File

@ -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 * 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
@ -24,7 +24,9 @@ extern "C" {
void vncSelectionInit(void); void vncSelectionInit(void);
void vncClientCutText(const char* str, int len); void vncHandleClipboardRequest(void);
void vncHandleClipboardAnnounce(int available);
void vncHandleClipboardData(const char* data);
#ifdef __cplusplus #ifdef __cplusplus
} }