diff options
54 files changed, 2192 insertions, 433 deletions
diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx index d6960dd2..bdde3253 100644 --- a/common/rfb/CConnection.cxx +++ b/common/rfb/CConnection.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2017 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 @@ -21,6 +21,7 @@ #include <string.h> #include <rfb/Exception.h> +#include <rfb/clipboardTypes.h> #include <rfb/fenceTypes.h> #include <rfb/CMsgReader.h> #include <rfb/CMsgWriter.h> @@ -52,7 +53,8 @@ CConnection::CConnection() formatChange(false), encodingChange(false), firstUpdate(true), pendingUpdate(false), continuousUpdates(false), forceNonincremental(true), - framebuffer(NULL), decoder(this) + framebuffer(NULL), decoder(this), + serverClipboard(NULL), hasLocalClipboard(false) { } @@ -65,6 +67,7 @@ CConnection::~CConnection() reader_ = 0; delete writer_; writer_ = 0; + strFree(serverClipboard); } void CConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_) @@ -463,6 +466,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 (server.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() { } @@ -476,6 +552,55 @@ void CConnection::resizeFramebuffer() assert(false); } +void CConnection::handleClipboardRequest() +{ +} + +void CConnection::handleClipboardAnnounce(bool available) +{ +} + +void CConnection::handleClipboardData(const char* data) +{ +} + +void CConnection::requestClipboard() +{ + if (serverClipboard != NULL) { + handleClipboardData(serverClipboard); + return; + } + + if (server.clipboardFlags() & rfb::clipboardRequest) + writer()->writeClipboardRequest(rfb::clipboardUTF8); +} + +void CConnection::announceClipboard(bool available) +{ + hasLocalClipboard = available; + + if (server.clipboardFlags() & rfb::clipboardNotify) + writer()->writeClipboardNotify(available ? rfb::clipboardUTF8 : 0); + else { + if (available) + handleClipboardRequest(); + } +} + +void CConnection::sendClipboardData(const char* data) +{ + if (server.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); + } +} + void CConnection::refreshFramebuffer() { forceNonincremental = true; @@ -611,6 +736,7 @@ void CConnection::updateEncodings() encodings.push_back(pseudoEncodingDesktopName); encodings.push_back(pseudoEncodingLastRect); + encodings.push_back(pseudoEncodingExtendedClipboard); encodings.push_back(pseudoEncodingContinuousUpdates); encodings.push_back(pseudoEncodingFence); encodings.push_back(pseudoEncodingQEMUKeyEvent); diff --git a/common/rfb/CConnection.h b/common/rfb/CConnection.h index 66a71a24..f01d5d36 100644 --- a/common/rfb/CConnection.h +++ b/common/rfb/CConnection.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2017 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 @@ -109,6 +109,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 @@ -128,9 +139,42 @@ namespace rfb { // sure the pixel buffer has been updated once this call returns. virtual void resizeFramebuffer(); + // 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); + // refreshFramebuffer() forces a complete refresh of the entire // framebuffer void refreshFramebuffer(); @@ -240,6 +284,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 c009067a..9dab5d94 100644 --- a/common/rfb/CMsgHandler.cxx +++ b/common/rfb/CMsgHandler.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2011 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -98,3 +98,26 @@ void CMsgHandler::setLEDState(unsigned int state) { server.setLEDState(state); } + +void CMsgHandler::handleClipboardCaps(rdr::U32 flags, const rdr::U32* lengths) +{ + server.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 effdaabf..84dd115c 100644 --- a/common/rfb/CMsgHandler.h +++ b/common/rfb/CMsgHandler.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2011 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * Copyright (C) 2011 D. R. Commander. All Rights Reserved. * * This is free software; you can redistribute it and/or modify @@ -42,8 +42,9 @@ namespace rfb { // The following methods are called as corresponding messages are read. A // derived class should override these methods as desired. Note that for // the setDesktopSize(), setExtendedDesktopSize(), setPixelFormat(), - // setName() and serverInit() methods, a derived class should call on to - // CMsgHandler's methods to set the members of "server" appropriately. + // setName(), serverInit() and clipboardCaps methods, a derived class + // should call on to CMsgHandler's methods to set the members of "server" + // appropriately. virtual void setDesktopSize(int w, int h); virtual void setExtendedDesktopSize(unsigned reason, unsigned result, @@ -70,10 +71,19 @@ namespace rfb { virtual void setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs) = 0; virtual void bell() = 0; - virtual void serverCutText(const char* str, rdr::U32 len) = 0; + virtual void serverCutText(const char* str) = 0; 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); + ServerParams server; }; } diff --git a/common/rfb/CMsgReader.cxx b/common/rfb/CMsgReader.cxx index 2b5b9fbf..a9e12d70 100644 --- a/common/rfb/CMsgReader.cxx +++ b/common/rfb/CMsgReader.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2017 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -20,8 +20,11 @@ #include <assert.h> #include <stdio.h> -#include <rfb/msgTypes.h> #include <rdr/InStream.h> +#include <rdr/ZlibInStream.h> + +#include <rfb/msgTypes.h> +#include <rfb/clipboardTypes.h> #include <rfb/Exception.h> #include <rfb/LogWriter.h> #include <rfb/util.h> @@ -30,6 +33,8 @@ static rfb::LogWriter vlog("CMsgReader"); +static rfb::IntParameter maxCutText("MaxCutText", "Maximum permitted length of an incoming clipboard update", 256*1024); + using namespace rfb; CMsgReader::CMsgReader(CMsgHandler* handler_, rdr::InStream* is_) @@ -152,15 +157,116 @@ void CMsgReader::readServerCutText() { is->skip(3); rdr::U32 len = is->readU32(); - if (len > 256*1024) { + + if (len & 0x80000000) { + rdr::S32 slen = len; + slen = -slen; + readExtendedClipboard(slen); + return; + } + + if (len > (size_t)maxCutText) { is->skip(len); vlog.error("cut text too long (%d bytes) - ignoring",len); return; } - CharArray ca(len+1); - ca.buf[len] = 0; + CharArray ca(len); is->readBytes(ca.buf, len); - handler->serverCutText(ca.buf, len); + CharArray filtered(convertLF(ca.buf, len)); + handler->serverCutText(filtered.buf); +} + +void CMsgReader::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.removeUnderlying(); + + 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 CMsgReader::readFence() diff --git a/common/rfb/CMsgReader.h b/common/rfb/CMsgReader.h index 03f3d8d2..050990a9 100644 --- a/common/rfb/CMsgReader.h +++ b/common/rfb/CMsgReader.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -53,6 +53,7 @@ namespace rfb { void readSetColourMapEntries(); void readBell(); void readServerCutText(); + void readExtendedClipboard(rdr::S32 len); void readFence(); void readEndOfContinuousUpdates(); diff --git a/common/rfb/CMsgWriter.cxx b/common/rfb/CMsgWriter.cxx index d357c976..3180391b 100644 --- a/common/rfb/CMsgWriter.cxx +++ b/common/rfb/CMsgWriter.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -17,10 +17,15 @@ * USA. */ #include <stdio.h> + #include <rdr/OutStream.h> +#include <rdr/MemOutStream.h> +#include <rdr/ZlibOutStream.h> + #include <rfb/msgTypes.h> #include <rfb/fenceTypes.h> #include <rfb/qemuTypes.h> +#include <rfb/clipboardTypes.h> #include <rfb/Exception.h> #include <rfb/PixelFormat.h> #include <rfb/Rect.h> @@ -179,8 +184,14 @@ void CMsgWriter::writePointerEvent(const Point& pos, int buttonMask) } -void CMsgWriter::writeClientCutText(const char* str, rdr::U32 len) +void CMsgWriter::writeClientCutText(const char* str) { + size_t len; + + if (strchr(str, '\r') != NULL) + throw Exception("Invalid carriage return in clipboard data"); + + len = strlen(str); startMsg(msgTypeClientCutText); os->pad(3); os->writeU32(len); @@ -188,6 +199,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 (!(server->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 (!(server->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 (!(server->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 (!(server->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 (!(server->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 4d533d42..7b839393 100644 --- a/common/rfb/CMsgWriter.h +++ b/common/rfb/CMsgWriter.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -55,7 +55,15 @@ namespace rfb { void writeKeyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); void writePointerEvent(const Point& pos, int buttonMask); - void writeClientCutText(const char* str, rdr::U32 len); + + void writeClientCutText(const char* str); + + 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); diff --git a/common/rfb/ClientParams.cxx b/common/rfb/ClientParams.cxx index e42d494b..987abe32 100644 --- a/common/rfb/ClientParams.cxx +++ b/common/rfb/ClientParams.cxx @@ -1,6 +1,6 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2011 D. R. Commander. All Rights Reserved. - * Copyright 2014-2018 Pierre Ossman for Cendio AB + * Copyright 2014-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 @@ -20,6 +20,7 @@ #include <rfb/Exception.h> #include <rfb/encodings.h> #include <rfb/ledStates.h> +#include <rfb/clipboardTypes.h> #include <rfb/ClientParams.h> using namespace rfb; @@ -32,7 +33,13 @@ ClientParams::ClientParams() ledState_(ledUnknown) { 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; } ClientParams::~ClientParams() @@ -136,6 +143,20 @@ void ClientParams::setLEDState(unsigned int state) ledState_ = state; } +void ClientParams::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++]; + } +} + bool ClientParams::supportsLocalCursor() const { if (supportsEncoding(pseudoEncodingCursorWithAlpha)) diff --git a/common/rfb/ClientParams.h b/common/rfb/ClientParams.h index f7a7044b..aab3d644 100644 --- a/common/rfb/ClientParams.h +++ b/common/rfb/ClientParams.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2014-2018 Pierre Ossman for Cendio AB + * Copyright 2014-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 @@ -84,6 +84,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); + // Wrappers to check for functionality rather than specific // encodings bool supportsLocalCursor() const; @@ -108,6 +111,8 @@ namespace rfb { Cursor* cursor_; std::set<rdr::S32> encodings_; unsigned int ledState_; + rdr::U32 clipFlags; + rdr::U32 clipSizes[16]; }; } #endif diff --git a/common/rfb/InputHandler.h b/common/rfb/InputHandler.h index 6c072849..b91f0e42 100644 --- a/common/rfb/InputHandler.h +++ b/common/rfb/InputHandler.h @@ -37,8 +37,7 @@ namespace rfb { bool __unused_attr down) { } virtual void pointerEvent(const Point& __unused_attr pos, int __unused_attr buttonMask) { } - virtual void clientCutText(const char* __unused_attr str, - int __unused_attr len) { } + virtual void clientCutText(const char* __unused_attr str) { } }; } diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx index 4e224aa1..4869199a 100644 --- a/common/rfb/SConnection.cxx +++ b/common/rfb/SConnection.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011 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 @@ -20,6 +20,7 @@ #include <string.h> #include <rfb/Exception.h> #include <rfb/Security.h> +#include <rfb/clipboardTypes.h> #include <rfb/msgTypes.h> #include <rfb/fenceTypes.h> #include <rfb/SMsgReader.h> @@ -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; @@ -70,6 +72,7 @@ SConnection::~SConnection() reader_ = 0; delete writer_; writer_ = 0; + strFree(clientClipboard); } void SConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_) @@ -297,6 +300,69 @@ void SConnection::setEncodings(int nEncodings, const rdr::S32* encodings) } SMsgHandler::setEncodings(nEncodings, encodings); + + if (client.supportsEncoding(pseudoEncodingExtendedClipboard)) { + 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 (client.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); } void SConnection::supportsQEMUKeyEvent() @@ -410,6 +476,58 @@ void SConnection::enableContinuousUpdates(bool enable, { } +void SConnection::handleClipboardRequest() +{ +} + +void SConnection::handleClipboardAnnounce(bool available) +{ +} + +void SConnection::handleClipboardData(const char* data) +{ +} + +void SConnection::requestClipboard() +{ + if (clientClipboard != NULL) { + handleClipboardData(clientClipboard); + return; + } + + if (client.supportsEncoding(pseudoEncodingExtendedClipboard) && + (client.clipboardFlags() & rfb::clipboardRequest)) + writer()->writeClipboardRequest(rfb::clipboardUTF8); +} + +void SConnection::announceClipboard(bool available) +{ + hasLocalClipboard = available; + + if (client.supportsEncoding(pseudoEncodingExtendedClipboard) && + (client.clipboardFlags() & rfb::clipboardNotify)) + writer()->writeClipboardNotify(available ? rfb::clipboardUTF8 : 0); + else { + if (available) + handleClipboardRequest(); + } +} + +void SConnection::sendClipboardData(const char* data) +{ + if (client.supportsEncoding(pseudoEncodingExtendedClipboard) && + (client.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); + } +} + void SConnection::writeFakeColourMap(void) { int i; diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h index 31d1cb2e..db3ab08c 100644 --- a/common/rfb/SConnection.h +++ b/common/rfb/SConnection.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011 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 @@ -80,8 +80,18 @@ 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 // versionReceived() indicates that the version number has just been read @@ -129,8 +139,42 @@ 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); + + // 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); + // setAccessRights() allows a security package to limit the access rights // of a SConnection to the server. How the access rights are treated // is up to the derived class. @@ -208,6 +252,9 @@ namespace rfb { stateEnum state_; rdr::S32 preferredEncoding; AccessRights accessRights; + + char* clientClipboard; + bool hasLocalClipboard; }; } #endif diff --git a/common/rfb/SDesktop.h b/common/rfb/SDesktop.h index 0060aa23..b4104ea7 100644 --- a/common/rfb/SDesktop.h +++ b/common/rfb/SDesktop.h @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright 2009-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 @@ -93,6 +94,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) {} + protected: virtual ~SDesktop() {} }; diff --git a/common/rfb/SMsgHandler.cxx b/common/rfb/SMsgHandler.cxx index f952ec2b..32b561e7 100644 --- a/common/rfb/SMsgHandler.cxx +++ b/common/rfb/SMsgHandler.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2011 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -64,6 +64,29 @@ void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings) supportsQEMUKeyEvent(); } +void SMsgHandler::handleClipboardCaps(rdr::U32 flags, const rdr::U32* lengths) +{ + client.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 8548d911..b290f194 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2011 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -40,8 +40,8 @@ namespace rfb { // The following methods are called as corresponding messages are read. A // derived class should override these methods as desired. Note that for - // the setPixelFormat(), and setEncodings() methods, a derived class must - // call on to SMsgHandler's methods. + // the setPixelFormat(), setEncodings() and clipboardCaps() methods, a + // derived class must call on to SMsgHandler's methods. virtual void clientInit(bool shared); @@ -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); + // InputHandler interface // The InputHandler methods will be called for the corresponding messages. diff --git a/common/rfb/SMsgReader.cxx b/common/rfb/SMsgReader.cxx index 200350c1..ab42e59a 100644 --- a/common/rfb/SMsgReader.cxx +++ b/common/rfb/SMsgReader.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -17,9 +17,13 @@ * USA. */ #include <stdio.h> + #include <rdr/InStream.h> +#include <rdr/ZlibInStream.h> + #include <rfb/msgTypes.h> #include <rfb/qemuTypes.h> +#include <rfb/clipboardTypes.h> #include <rfb/Exception.h> #include <rfb/util.h> #include <rfb/SMsgHandler.h> @@ -203,19 +207,117 @@ 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; } - CharArray ca(len+1); - ca.buf[len] = 0; + CharArray ca(len); is->readBytes(ca.buf, len); - handler->clientCutText(ca.buf, len); + CharArray filtered(convertLF(ca.buf, len)); + handler->clientCutText(filtered.buf); +} + +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.removeUnderlying(); + + 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::readQEMUMessage() diff --git a/common/rfb/SMsgReader.h b/common/rfb/SMsgReader.h index 146b29f8..4991fd38 100644 --- a/common/rfb/SMsgReader.h +++ b/common/rfb/SMsgReader.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -54,6 +54,7 @@ namespace rfb { void readKeyEvent(); void readPointerEvent(); void readClientCutText(); + void readExtendedClipboard(rdr::S32 len); void readQEMUMessage(); void readQEMUKeyEvent(); diff --git a/common/rfb/SMsgWriter.cxx b/common/rfb/SMsgWriter.cxx index 6a2c2ba0..becf6e70 100644 --- a/common/rfb/SMsgWriter.cxx +++ b/common/rfb/SMsgWriter.cxx @@ -1,6 +1,6 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2011 D. R. Commander. All Rights Reserved. - * Copyright 2009-2017 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -18,9 +18,14 @@ * USA. */ #include <stdio.h> + #include <rdr/OutStream.h> +#include <rdr/MemOutStream.h> +#include <rdr/ZlibOutStream.h> + #include <rfb/msgTypes.h> #include <rfb/fenceTypes.h> +#include <rfb/clipboardTypes.h> #include <rfb/Exception.h> #include <rfb/ClientParams.h> #include <rfb/UpdateTracker.h> @@ -78,8 +83,14 @@ void SMsgWriter::writeBell() endMsg(); } -void SMsgWriter::writeServerCutText(const char* str, int len) +void SMsgWriter::writeServerCutText(const char* str) { + size_t len; + + if (strchr(str, '\r') != NULL) + throw Exception("Invalid carriage return in clipboard data"); + + len = strlen(str); startMsg(msgTypeServerCutText); os->pad(3); os->writeU32(len); @@ -87,6 +98,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 (!client->supportsEncoding(pseudoEncodingExtendedClipboard)) + 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 (!client->supportsEncoding(pseudoEncodingExtendedClipboard)) + throw Exception("Client does not support extended clipboard"); + if (!(client->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 (!client->supportsEncoding(pseudoEncodingExtendedClipboard)) + throw Exception("Client does not support extended clipboard"); + if (!(client->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 (!client->supportsEncoding(pseudoEncodingExtendedClipboard)) + throw Exception("Client does not support extended clipboard"); + if (!(client->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 (!client->supportsEncoding(pseudoEncodingExtendedClipboard)) + throw Exception("Client does not support extended clipboard"); + if (!(client->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::writeFence(rdr::U32 flags, unsigned len, const char data[]) { if (!client->supportsEncoding(pseudoEncodingFence)) diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h index 4f4c9cc0..2cea44d1 100644 --- a/common/rfb/SMsgWriter.h +++ b/common/rfb/SMsgWriter.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -54,9 +54,17 @@ namespace rfb { const rdr::U16 green[], const rdr::U16 blue[]); - // writeBell() and writeServerCutText() do the obvious thing. + // writeBell() does the obvious thing. void writeBell(); - void writeServerCutText(const char* str, int len); + + void writeServerCutText(const char* str); + + 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); // writeFence() sends a new fence request or response to the client. void writeFence(rdr::U32 flags, unsigned len, const char data[]); diff --git a/common/rfb/ServerParams.cxx b/common/rfb/ServerParams.cxx index bfeb80d6..a2e3aa83 100644 --- a/common/rfb/ServerParams.cxx +++ b/common/rfb/ServerParams.cxx @@ -1,6 +1,6 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2011 D. R. Commander. All Rights Reserved. - * Copyright 2014-2018 Pierre Ossman for Cendio AB + * Copyright 2014-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 @@ -32,7 +32,11 @@ ServerParams::ServerParams() ledState_(ledUnknown) { setName(""); + cursor_ = new Cursor(0, 0, Point(), NULL); + + clipFlags = 0; + memset(clipSizes, 0, sizeof(clipSizes)); } ServerParams::~ServerParams() @@ -82,3 +86,17 @@ void ServerParams::setLEDState(unsigned int state) { ledState_ = state; } + +void ServerParams::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/ServerParams.h b/common/rfb/ServerParams.h index 7a58ea37..c84f6252 100644 --- a/common/rfb/ServerParams.h +++ b/common/rfb/ServerParams.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2014-2018 Pierre Ossman for Cendio AB + * Copyright 2014-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 @@ -69,6 +69,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 supportsQEMUKeyEvent; bool supportsSetDesktopSize; bool supportsFence; @@ -84,6 +87,8 @@ namespace rfb { char* name_; Cursor* cursor_; unsigned int ledState_; + rdr::U32 clipFlags; + rdr::U32 clipSizes[16]; }; } #endif diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index fe00dab6..cdd87b13 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2018 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * Copyright 2018 Peter Astrand for Cendio AB * * This is free software; you can redistribute it and/or modify @@ -282,51 +282,71 @@ void VNCSConnectionST::bellOrClose() } } -void VNCSConnectionST::serverCutTextOrClose(const char *str, int len) +void VNCSConnectionST::setDesktopNameOrClose(const char *name) { try { - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::sendCutText) return; - if (state() == RFBSTATE_NORMAL) - writer()->writeServerCutText(str, len); + setDesktopName(name); + writeFramebufferUpdate(); } catch(rdr::Exception& e) { close(e.str()); } } - -void VNCSConnectionST::setDesktopNameOrClose(const char *name) +void VNCSConnectionST::setCursorOrClose() { try { - setDesktopName(name); + setCursor(); writeFramebufferUpdate(); } catch(rdr::Exception& e) { close(e.str()); } } - -void VNCSConnectionST::setCursorOrClose() +void VNCSConnectionST::setLEDStateOrClose(unsigned int state) { try { - setCursor(); + setLEDState(state); writeFramebufferUpdate(); } catch(rdr::Exception& e) { close(e.str()); } } +void VNCSConnectionST::requestClipboardOrClose() +{ + try { + if (!accessCheck(AccessCutText)) return; + if (!rfb::Server::acceptCutText) return; + if (state() != RFBSTATE_NORMAL) return; + requestClipboard(); + } catch(rdr::Exception& e) { + close(e.str()); + } +} -void VNCSConnectionST::setLEDStateOrClose(unsigned int state) +void VNCSConnectionST::announceClipboardOrClose(bool available) { try { - setLEDState(state); - writeFramebufferUpdate(); + if (!accessCheck(AccessCutText)) return; + if (!rfb::Server::sendCutText) return; + if (state() != RFBSTATE_NORMAL) return; + announceClipboard(available); } catch(rdr::Exception& e) { close(e.str()); } } +void VNCSConnectionST::sendClipboardDataOrClose(const char* data) +{ + try { + if (!accessCheck(AccessCutText)) return; + if (!rfb::Server::sendCutText) return; + if (state() != RFBSTATE_NORMAL) return; + sendClipboardData(data); + } catch(rdr::Exception& e) { + close(e.str()); + } +} bool VNCSConnectionST::getComparerState() { @@ -596,13 +616,6 @@ void VNCSConnectionST::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) { server->keyEvent(keysym, keycode, down); } -void VNCSConnectionST::clientCutText(const char* str, int len) -{ - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::acceptCutText) return; - server->clientCutText(str, len); -} - void VNCSConnectionST::framebufferUpdateRequest(const Rect& r,bool incremental) { Rect safeRect; @@ -719,6 +732,26 @@ void VNCSConnectionST::enableContinuousUpdates(bool enable, } } +void VNCSConnectionST::handleClipboardRequest() +{ + if (!accessCheck(AccessCutText)) return; + server->handleClipboardRequest(this); +} + +void VNCSConnectionST::handleClipboardAnnounce(bool available) +{ + if (!accessCheck(AccessCutText)) return; + if (!rfb::Server::acceptCutText) return; + server->handleClipboardAnnounce(this, available); +} + +void VNCSConnectionST::handleClipboardData(const char* data) +{ + if (!accessCheck(AccessCutText)) return; + if (!rfb::Server::acceptCutText) return; + server->handleClipboardData(this, data); +} + // supportsLocalCursor() is called whenever the status of // client.supportsLocalCursor() has changed. If the client does now support local // cursor, we make sure that the old server-side rendered cursor is cleaned up @@ -756,7 +789,6 @@ void VNCSConnectionST::supportsLEDState() writer()->writeLEDState(); } - bool VNCSConnectionST::handleTimeout(Timer* t) { try { diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index a9a8d3a4..c8f4c24f 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2016 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -72,10 +72,12 @@ 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 approveConnectionOrClose(bool accept, const char* reason); + void requestClipboardOrClose(); + void announceClipboardOrClose(bool available); + void sendClipboardDataOrClose(const char* data); // The following methods never throw exceptions @@ -115,13 +117,15 @@ namespace rfb { virtual void setPixelFormat(const PixelFormat& pf); virtual void pointerEvent(const Point& pos, int buttonMask); 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 298326f5..5d04da53 100644 --- a/common/rfb/VNCServer.h +++ b/common/rfb/VNCServer.h @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright 2009-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 @@ -55,9 +56,21 @@ namespace rfb { // getPixelBuffer() returns a pointer to the PixelBuffer object. virtual const 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 c95c14f0..a3655bca 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2018 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -77,8 +77,8 @@ static LogWriter connectionsLog("Connections"); VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) : blHosts(&blacklist), desktop(desktop_), desktopStarted(false), blockCounter(0), pb(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), keyRemapper(&KeyRemapper::defInstance), idleTimer(this), disconnectTimer(this), connectTimer(this), @@ -167,9 +167,12 @@ void VNCServerST::removeSocket(network::Socket* sock) { if ((*ci)->getSock() == sock) { clients.remove(*ci); - // - Release the cursor if this client owns it + // - Remove any references to it if (pointerClient == *ci) pointerClient = NULL; + if (clipboardClient == *ci) + clipboardClient = NULL; + clipboardRequestors.remove(*ci); // Adjust the exit timers connectTimer.stop(); @@ -331,21 +334,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<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)->bellOrClose(); + (*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); } + + clipboardRequestors.clear(); } -void VNCServerST::serverCutText(const char* str, int len) +void VNCServerST::bell() { 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); + (*ci)->bellOrClose(); } } @@ -459,9 +492,32 @@ void VNCServerST::pointerEvent(VNCSConnectionST* client, desktop->pointerEvent(pos, buttonMask); } -void VNCServerST::clientCutText(const char* str, int len) +void VNCServerST::handleClipboardRequest(VNCSConnectionST* client) { - desktop->clientCutText(str, len); + 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) +{ + if (client != clipboardClient) + return; + desktop->handleClipboardData(data); } unsigned int VNCServerST::setDesktopSize(VNCSConnectionST* requester, diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index 43a3bb95..fd20cc37 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2016 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -85,7 +85,10 @@ namespace rfb { virtual void setPixelBuffer(PixelBuffer* pb); virtual void setScreenLayout(const ScreenSet& layout); virtual const PixelBuffer* getPixelBuffer() const { 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 approveConnection(network::Socket* sock, bool accept, const char* reason); @@ -115,7 +118,10 @@ namespace rfb { // Event handlers void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); void pointerEvent(VNCSConnectionST* client, const Point& pos, int buttonMask); - void clientCutText(const char* str, int len); + + void handleClipboardRequest(VNCSConnectionST* client); + void handleClipboardAnnounce(VNCSConnectionST* client, bool available); + void handleClipboardData(VNCSConnectionST* client, const char* data); unsigned int setDesktopSize(VNCSConnectionST* requester, int fb_width, int fb_height, @@ -181,6 +187,8 @@ namespace rfb { std::list<VNCSConnectionST*> clients; VNCSConnectionST* pointerClient; + VNCSConnectionST* clipboardClient; + std::list<VNCSConnectionST*> clipboardRequestors; std::list<network::Socket*> closingSockets; ComparingUpdateTracker* comparer; diff --git a/common/rfb/clipboardTypes.h b/common/rfb/clipboardTypes.h new file mode 100644 index 00000000..bd3fa03d --- /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 acb86ecc..cf0c8572 100644 --- a/common/rfb/encodings.h +++ b/common/rfb/encodings.h @@ -63,6 +63,9 @@ namespace rfb { const int pseudoEncodingVMwareCursor = 0x574d5664; const int pseudoEncodingVMwareLEDState = 0x574d5668; + // 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 f52213b3..fc4f4ca4 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,441 @@ 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 != '\0') && (in_len > 0)) { + if (*in != '\r') { + sz++; + in++; + in_len--; + continue; + } + + if ((in_len == 0) || (*(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 != '\0') && (in_len > 0)) { + if (*in != '\r') { + *out++ = *in++; + in_len--; + continue; + } + + if ((in_len == 0) || (*(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 != '\0') && (in_len > 0)) { + sz++; + + if (*in == '\r') { + if ((in_len == 0) || (*(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 != '\0') && (in_len > 0)) { + if (*in == '\n') { + if ((in == src) || (*(in-1) != '\r')) + *out++ = '\r'; + } + + *out = *in; + + if (*in == '\r') { + if ((in_len == 0) || (*(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--) { + // 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 < 0x110000) { + *dst++ = 0xd800 | ((src >> 10) & 0x07ff); + *dst++ = 0xdc00 | (src & 0x07ff); + *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 != '\0') && (in_len > 0)) { + char buf[5]; + sz += ucs4ToUTF8(*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 != '\0') && (in_len > 0)) { + out += ucs4ToUTF8(*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 != '\0') && (in_len > 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 != '\0') && (in_len > 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 != '\0') && (in_len > 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 != '\0') && (in_len > 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 != '\0') && (in_len > 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); + + // And convert + out = buffer; + in = src; + in_len = bytes; + while ((*in != '\0') && (in_len > 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 9e59bd37..8503519d 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/tests/decperf.cxx b/tests/decperf.cxx index 301e45e0..df5214f2 100644 --- a/tests/decperf.cxx +++ b/tests/decperf.cxx @@ -54,7 +54,7 @@ public: virtual void framebufferUpdateEnd(); virtual void setColourMapEntries(int, int, rdr::U16*); virtual void bell(); - virtual void serverCutText(const char*, rdr::U32); + virtual void serverCutText(const char*); public: double cpuTime; @@ -122,7 +122,7 @@ void CConn::bell() { } -void CConn::serverCutText(const char*, rdr::U32) +void CConn::serverCutText(const char*) { } diff --git a/tests/encperf.cxx b/tests/encperf.cxx index 6523eb72..e461197e 100644 --- a/tests/encperf.cxx +++ b/tests/encperf.cxx @@ -96,7 +96,7 @@ public: virtual void dataRect(const rfb::Rect&, int); virtual void setColourMapEntries(int, int, rdr::U16*); virtual void bell(); - virtual void serverCutText(const char*, rdr::U32); + virtual void serverCutText(const char*); public: double decodeTime; @@ -254,7 +254,7 @@ void CConn::bell() { } -void CConn::serverCutText(const char*, rdr::U32) +void CConn::serverCutText(const char*) { } diff --git a/unix/x0vncserver/XDesktop.cxx b/unix/x0vncserver/XDesktop.cxx index 564b2d51..8be9aa3d 100644 --- a/unix/x0vncserver/XDesktop.cxx +++ b/unix/x0vncserver/XDesktop.cxx @@ -406,7 +406,7 @@ void XDesktop::keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) { #endif } -void XDesktop::clientCutText(const char* str, int len) { +void XDesktop::clientCutText(const char* str) { } ScreenSet XDesktop::computeScreenLayout() diff --git a/unix/x0vncserver/XDesktop.h b/unix/x0vncserver/XDesktop.h index 3e85aac3..840d4331 100644 --- a/unix/x0vncserver/XDesktop.h +++ b/unix/x0vncserver/XDesktop.h @@ -56,7 +56,7 @@ public: virtual void pointerEvent(const rfb::Point& pos, int buttonMask); KeyCode XkbKeysymToKeycode(Display* dpy, KeySym keysym); virtual void keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down); - virtual void clientCutText(const char* str, int len); + virtual void clientCutText(const char* str); virtual unsigned int setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout); diff --git a/unix/xserver/hw/vnc/RFBGlue.cc b/unix/xserver/hw/vnc/RFBGlue.cc index 160177bd..f108fae4 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 @@ -210,3 +210,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 a63afd07..112405b8 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 @@ -49,6 +49,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 d8b3a4d4..4edffec7 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.cc +++ b/unix/xserver/hw/vnc/XserverDesktop.cc @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2017 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * Copyright 2014 Brian P. Hinz * * This is free software; you can redistribute it and/or modify @@ -182,25 +182,43 @@ void XserverDesktop::queryConnection(network::Socket* sock, queryConnectTimer.start(queryConnectTimeout * 1000); } -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 { @@ -436,11 +454,6 @@ void XserverDesktop::pointerEvent(const Point& pos, int buttonMask) vncPointerButtonAction(buttonMask); } -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) { @@ -462,6 +475,21 @@ unsigned int XserverDesktop::setScreenLayout(int fb_width, int fb_height, return result; } +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 1253935f..6c670689 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.h +++ b/unix/xserver/hw/vnc/XserverDesktop.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2015 Pierre Ossman for Cendio AB + * Copyright 2009-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 @@ -59,9 +59,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); @@ -92,9 +94,11 @@ public: const char* userName); virtual void pointerEvent(const rfb::Point& pos, int buttonMask); 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 20072f48..6ab306b1 100644 --- a/unix/xserver/hw/vnc/vncExtInit.cc +++ b/unix/xserver/hw/vnc/vncExtInit.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 @@ -285,10 +285,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 5f97f96d..1fb87c19 100644 --- a/unix/xserver/hw/vnc/vncExtInit.h +++ b/unix/xserver/hw/vnc/vncExtInit.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 @@ -53,7 +53,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 4f3538d4..5191bb94 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; + } } +} - rc = 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) @@ -520,5 +616,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 969f8958..ea52bf23 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 } diff --git a/vncviewer/CConn.cxx b/vncviewer/CConn.cxx index b4610e6a..6ba3276b 100644 --- a/vncviewer/CConn.cxx +++ b/vncviewer/CConn.cxx @@ -377,11 +377,6 @@ void CConn::bell() fl_beep(); } -void CConn::serverCutText(const char* str, rdr::U32 len) -{ - desktop->serverCutText(str, len); -} - void CConn::dataRect(const Rect& r, int encoding) { sock->inStream().startTiming(); @@ -422,6 +417,21 @@ void CConn::setLEDState(unsigned int state) desktop->setLEDState(state); } +void CConn::handleClipboardRequest() +{ + desktop->handleClipboardRequest(); +} + +void CConn::handleClipboardAnnounce(bool available) +{ + desktop->handleClipboardAnnounce(available); +} + +void CConn::handleClipboardData(const char* data) +{ + desktop->handleClipboardData(data); +} + ////////////////////// Internal methods ////////////////////// diff --git a/vncviewer/CConn.h b/vncviewer/CConn.h index 2e3362ce..4d935c96 100644 --- a/vncviewer/CConn.h +++ b/vncviewer/CConn.h @@ -61,8 +61,6 @@ public: void bell(); - void serverCutText(const char* str, rdr::U32 len); - void framebufferUpdateStart(); void framebufferUpdateEnd(); void dataRect(const rfb::Rect& r, int encoding); @@ -74,6 +72,10 @@ public: void setLEDState(unsigned int state); + virtual void handleClipboardRequest(); + virtual void handleClipboardAnnounce(bool available); + virtual void handleClipboardData(const char* data); + private: void resizeFramebuffer(); diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index c52a9150..1b666f94 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -276,12 +276,6 @@ void DesktopWindow::resizeFramebuffer(int new_w, int new_h) } -void DesktopWindow::serverCutText(const char* str, rdr::U32 len) -{ - viewport->serverCutText(str, len); -} - - void DesktopWindow::setCursor(int width, int height, const rfb::Point& hotspot, const rdr::U8* data) @@ -442,6 +436,22 @@ void DesktopWindow::setLEDState(unsigned int state) } +void DesktopWindow::handleClipboardRequest() +{ + viewport->handleClipboardRequest(); +} + +void DesktopWindow::handleClipboardAnnounce(bool available) +{ + viewport->handleClipboardAnnounce(available); +} + +void DesktopWindow::handleClipboardData(const char* data) +{ + viewport->handleClipboardData(data); +} + + void DesktopWindow::resize(int x, int y, int w, int h) { bool resizing; diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index 97a8178d..ef3dbb08 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -62,9 +62,6 @@ public: // Resize the current framebuffer, but retain the contents void resizeFramebuffer(int new_w, int new_h); - // Incoming clipboard from server - void serverCutText(const char* str, rdr::U32 len); - // New image for the locally rendered cursor void setCursor(int width, int height, const rfb::Point& hotspot, const rdr::U8* data); @@ -72,6 +69,11 @@ public: // Change client LED state void setLEDState(unsigned int state); + // Clipboard events + void handleClipboardRequest(); + void handleClipboardAnnounce(bool available); + void handleClipboardData(const char* data); + // Fl_Window callback methods virtual void show(); virtual void draw(); diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 5df5c796..cd613279 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2014 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 @@ -119,7 +119,7 @@ Viewport::Viewport(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_) altGrArmed(false), #endif firstLEDState(true), - pendingServerCutText(NULL), pendingClientCutText(NULL), + pendingServerClipboard(false), pendingClientClipboard(false), menuCtrlKey(false), menuAltKey(false), cursor(NULL) { #if !defined(WIN32) && !defined(__APPLE__) @@ -208,8 +208,6 @@ Viewport::~Viewport() delete cursor; } - clearPendingClipboard(); - // FLTK automatically deletes all child widgets, so we shouldn't touch // them ourselves here } @@ -232,45 +230,6 @@ void Viewport::updateWindow() damage(FL_DAMAGE_USER1, r.tl.x + x(), r.tl.y + y(), r.width(), r.height()); } -void Viewport::serverCutText(const char* str, rdr::U32 len) -{ - char *buffer; - int size, ret; - - clearPendingClipboard(); - - if (!acceptClipboard) - return; - - size = fl_utf8froma(NULL, 0, str, len); - if (size <= 0) - return; - - size++; - - buffer = new char[size]; - - ret = fl_utf8froma(buffer, size, str, len); - assert(ret < size); - - vlog.debug("Got clipboard data (%d bytes)", (int)strlen(buffer)); - - if (!hasFocus()) { - pendingServerCutText = buffer; - return; - } - - // RFB doesn't have separate selection and clipboard concepts, so we - // dump the data into both variants. -#if !defined(WIN32) && !defined(__APPLE__) - if (setPrimary) - Fl::copy(buffer, ret, 0); -#endif - Fl::copy(buffer, ret, 1); - - delete [] buffer; -} - static const char * dotcursor_xpm[] = { "5 5 2 1", ". c #000000", @@ -319,6 +278,55 @@ void Viewport::setCursor(int width, int height, const Point& hotspot, window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y); } +void Viewport::handleClipboardRequest() +{ + Fl::paste(*this, clipboardSource); +} + +void Viewport::handleClipboardAnnounce(bool available) +{ + if (!acceptClipboard) + return; + + if (available) + vlog.debug("Got notification of new clipboard on server"); + else + vlog.debug("Clipboard is no longer available on server"); + + if (!available) { + pendingServerClipboard = false; + return; + } + + pendingClientClipboard = false; + + if (!hasFocus()) { + pendingServerClipboard = true; + return; + } + + cc->requestClipboard(); +} + +void Viewport::handleClipboardData(const char* data) +{ + size_t len; + + if (!hasFocus()) + return; + + len = strlen(data); + + vlog.debug("Got clipboard data (%d bytes)", (int)len); + + // RFB doesn't have separate selection and clipboard concepts, so we + // dump the data into both variants. +#if !defined(WIN32) && !defined(__APPLE__) + if (setPrimary) + Fl::copy(data, len, 0); +#endif + Fl::copy(data, len, 1); +} void Viewport::setLEDState(unsigned int state) { @@ -549,37 +557,24 @@ void Viewport::resize(int x, int y, int w, int h) int Viewport::handle(int event) { - char *buffer; - int ret; + char *filtered; int buttonMask, wheelMask; DownMap::const_iterator iter; switch (event) { case FL_PASTE: - buffer = new char[Fl::event_length() + 1]; - - clearPendingClipboard(); - - // This is documented as to ASCII, but actually does to 8859-1 - ret = fl_utf8toa(Fl::event_text(), Fl::event_length(), buffer, - Fl::event_length() + 1); - assert(ret < (Fl::event_length() + 1)); + filtered = convertLF(Fl::event_text(), Fl::event_length()); - if (!hasFocus()) { - pendingClientCutText = buffer; - return 1; - } - - vlog.debug("Sending clipboard data (%d bytes)", (int)strlen(buffer)); + vlog.debug("Sending clipboard data (%d bytes)", (int)strlen(filtered)); try { - cc->writer()->writeClientCutText(buffer, ret); + cc->sendClipboardData(filtered); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); exit_vncviewer(e.str()); } - delete [] buffer; + strFree(filtered); return 1; @@ -737,41 +732,47 @@ void Viewport::handleClipboardChange(int source, void *data) return; #endif - Fl::paste(*self, source); -} + self->clipboardSource = source; + self->pendingServerClipboard = false; -void Viewport::clearPendingClipboard() -{ - delete [] pendingServerCutText; - pendingServerCutText = NULL; - delete [] pendingClientCutText; - pendingClientCutText = NULL; + if (!self->hasFocus()) { + self->pendingClientClipboard = true; + // Clear any older client clipboard from the server + self->cc->announceClipboard(false); + return; + } + + try { + self->cc->announceClipboard(true); + } catch (rdr::Exception& e) { + vlog.error("%s", e.str()); + exit_vncviewer(e.str()); + } } void Viewport::flushPendingClipboard() { - if (pendingServerCutText) { - size_t len = strlen(pendingServerCutText); -#if !defined(WIN32) && !defined(__APPLE__) - if (setPrimary) - Fl::copy(pendingServerCutText, len, 0); -#endif - Fl::copy(pendingServerCutText, len, 1); + if (pendingServerClipboard) { + try { + cc->requestClipboard(); + } catch (rdr::Exception& e) { + vlog.error("%s", e.str()); + exit_vncviewer(e.str()); + } } - if (pendingClientCutText) { - size_t len = strlen(pendingClientCutText); - vlog.debug("Sending pending clipboard data (%d bytes)", (int)len); + if (pendingClientClipboard) { try { - cc->writer()->writeClientCutText(pendingClientCutText, len); + cc->announceClipboard(true); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); exit_vncviewer(e.str()); } } - clearPendingClipboard(); + pendingServerClipboard = false; + pendingClientClipboard = false; } diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index be2192b8..1fb93c66 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB + * Copyright 2011-2019 Pierre Ossman <ossman@cendio.se> for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -45,9 +45,6 @@ public: // Flush updates to screen void updateWindow(); - // Incoming clipboard from server - void serverCutText(const char* str, rdr::U32 len); - // New image for the locally rendered cursor void setCursor(int width, int height, const rfb::Point& hotspot, const rdr::U8* data); @@ -57,6 +54,11 @@ public: void draw(Surface* dst); + // Clipboard events + void handleClipboardRequest(); + void handleClipboardAnnounce(bool available); + void handleClipboardData(const char* data); + // Fl_Widget callback methods void draw(); @@ -72,7 +74,6 @@ private: static void handleClipboardChange(int source, void *data); - void clearPendingClipboard(); void flushPendingClipboard(); void handlePointerEvent(const rfb::Point& pos, int buttonMask); @@ -114,8 +115,10 @@ private: bool firstLEDState; - const char* pendingServerCutText; - const char* pendingClientCutText; + bool pendingServerClipboard; + bool pendingClientClipboard; + + int clipboardSource; rdr::U32 menuKeySym; int menuKeyCode, menuKeyFLTK; diff --git a/vncviewer/vncviewer.man b/vncviewer/vncviewer.man index ebfe7725..f93e096a 100644 --- a/vncviewer/vncviewer.man +++ b/vncviewer/vncviewer.man @@ -182,6 +182,11 @@ Set the primary selection as well as the clipboard selection. Default is on. . .TP +.B \-MaxCutText \fIbytes\fP +The maximum size of a clipboard update that will be accepted from a server. +Default is \fB262144\fP. +. +.TP .B \-SendClipboard Send clipboard changes to the server. Default is on. . diff --git a/win/rfb_win32/Clipboard.cxx b/win/rfb_win32/Clipboard.cxx index 0e290623..11963675 100644 --- a/win/rfb_win32/Clipboard.cxx +++ b/win/rfb_win32/Clipboard.cxx @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright 2012-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 @@ -29,53 +30,6 @@ using namespace rfb::win32; static LogWriter vlog("Clipboard"); - -// -// -=- CR/LF handlers -// - -char* -dos2unix(const char* text) { - int len = strlen(text)+1; - char* unix = new char[strlen(text)+1]; - int i, j=0; - for (i=0; i<len; i++) { - if (text[i] != '\x0d') - unix[j++] = text[i]; - } - return unix; -} - -char* -unix2dos(const char* text) { - int len = strlen(text)+1; - char* dos = new char[strlen(text)*2+1]; - int i, j=0; - for (i=0; i<len; i++) { - if (text[i] == '\x0a') - dos[j++] = '\x0d'; - dos[j++] = text[i]; - } - return dos; -} - - -// -// -=- ISO-8859-1 (Latin 1) filter (in-place) -// - -void -removeNonISOLatin1Chars(char* text) { - int len = strlen(text); - int i=0, j=0; - for (; i<len; i++) { - if (((text[i] >= 1) && (text[i] <= 127)) || - ((text[i] >= 160) && (text[i] <= 255))) - text[j++] = text[i]; - } - text[j] = 0; -} - // // -=- Clipboard object // @@ -114,33 +68,10 @@ Clipboard::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) { } else { vlog.debug("local clipboard changed by %p", owner); - // Open the clipboard - if (OpenClipboard(getHandle())) { - // Get the clipboard data - HGLOBAL cliphandle = GetClipboardData(CF_TEXT); - if (cliphandle) { - char* clipdata = (char*) GlobalLock(cliphandle); - - // Notify clients - if (notifier) { - if (!clipdata) { - notifier->notifyClipboardChanged(0, 0); - } else { - CharArray unix_text; - unix_text.buf = dos2unix(clipdata); - removeNonISOLatin1Chars(unix_text.buf); - notifier->notifyClipboardChanged(unix_text.buf, strlen(unix_text.buf)); - } - } else { - vlog.debug("no clipboard notifier registered"); - } - - // Release the buffer and close the clipboard - GlobalUnlock(cliphandle); - } - - CloseClipboard(); - } + if (notifier == NULL) + vlog.debug("no clipboard notifier registered"); + else + notifier->notifyClipboardChanged(IsClipboardFormatAvailable(CF_UNICODETEXT)); } } if (next_window) @@ -151,6 +82,39 @@ Clipboard::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) { return MsgWindow::processMessage(msg, wParam, lParam); }; +char* +Clipboard::getClipText() { + HGLOBAL cliphandle; + wchar_t* clipdata; + CharArray utf8; + + // Open the clipboard + if (!OpenClipboard(getHandle())) + return NULL; + + // Get the clipboard data + cliphandle = GetClipboardData(CF_UNICODETEXT); + if (!cliphandle) { + CloseClipboard(); + return NULL; + } + + clipdata = (wchar_t*) GlobalLock(cliphandle); + if (!clipdata) { + CloseClipboard(); + return NULL; + } + + // Convert it to UTF-8 + utf8.replaceBuf(utf16ToUTF8(clipdata)); + + // Release the buffer and close the clipboard + GlobalUnlock(cliphandle); + CloseClipboard(); + + return convertLF(utf8.buf); +} + void Clipboard::setClipText(const char* text) { HANDLE clip_handle = 0; @@ -161,26 +125,27 @@ Clipboard::setClipText(const char* text) { if (!OpenClipboard(getHandle())) throw rdr::SystemException("unable to open Win32 clipboard", GetLastError()); - // - Pre-process the supplied clipboard text into DOS format - CharArray dos_text; - dos_text.buf = unix2dos(text); - removeNonISOLatin1Chars(dos_text.buf); - int dos_text_len = strlen(dos_text.buf); + // - Convert the supplied clipboard text into UTF-16 format with CRLF + CharArray filtered(convertCRLF(text)); + wchar_t* utf16; + + utf16 = utf8ToUTF16(filtered.buf); // - Allocate global memory for the data - clip_handle = ::GlobalAlloc(GMEM_MOVEABLE, dos_text_len+1); + clip_handle = ::GlobalAlloc(GMEM_MOVEABLE, (wcslen(utf16) + 1) * 2); - char* data = (char*) GlobalLock(clip_handle); - memcpy(data, dos_text.buf, dos_text_len+1); - data[dos_text_len] = 0; + wchar_t* data = (wchar_t*) GlobalLock(clip_handle); + wcscpy(data, utf16); GlobalUnlock(clip_handle); + strFree(utf16); + // - Next, we must clear out any existing data if (!EmptyClipboard()) throw rdr::SystemException("unable to empty Win32 clipboard", GetLastError()); // - Set the new clipboard data - if (!SetClipboardData(CF_TEXT, clip_handle)) + if (!SetClipboardData(CF_UNICODETEXT, clip_handle)) throw rdr::SystemException("unable to set Win32 clipboard", GetLastError()); clip_handle = 0; diff --git a/win/rfb_win32/Clipboard.h b/win/rfb_win32/Clipboard.h index 3da7bfca..1dead82e 100644 --- a/win/rfb_win32/Clipboard.h +++ b/win/rfb_win32/Clipboard.h @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * 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 @@ -38,7 +39,7 @@ namespace rfb { // -=- Abstract base class for callback recipients class Notifier { public: - virtual void notifyClipboardChanged(const char* text, int len) = 0; + virtual void notifyClipboardChanged(bool available) = 0; virtual ~Notifier() {}; }; @@ -48,6 +49,9 @@ namespace rfb { // - Set the notifier to use void setNotifier(Notifier* cbn) {notifier = cbn;} + // - Get the clipboard contents + char* getClipText(); + // - Set the clipboard contents void setClipText(const char* text); diff --git a/win/rfb_win32/SDisplay.cxx b/win/rfb_win32/SDisplay.cxx index 2cedc4a8..be33ff15 100644 --- a/win/rfb_win32/SDisplay.cxx +++ b/win/rfb_win32/SDisplay.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 @@ -291,6 +292,22 @@ void SDisplay::restartCore() { } +void SDisplay::handleClipboardRequest() { + CharArray data(clipboard->getClipText()); + server->sendClipboardData(data.buf); +} + +void SDisplay::handleClipboardAnnounce(bool available) { + // FIXME: Wait for an application to actually request it + if (available) + server->requestClipboard(); +} + +void SDisplay::handleClipboardData(const char* data) { + clipboard->setClipText(data); +} + + void SDisplay::pointerEvent(const Point& pos, int buttonmask) { if (pb->getRect().contains(pos)) { Point screenPos = pos.translate(screenRect.tl); @@ -328,19 +345,12 @@ bool SDisplay::checkLedState() { return false; } -void SDisplay::clientCutText(const char* text, int len) { - CharArray clip_sz(len+1); - memcpy(clip_sz.buf, text, len); - clip_sz.buf[len] = 0; - clipboard->setClipText(clip_sz.buf); -} - void -SDisplay::notifyClipboardChanged(const char* text, int len) { +SDisplay::notifyClipboardChanged(bool available) { vlog.debug("clipboard text changed"); if (server) - server->serverCutText(text, len); + server->announceClipboard(available); } diff --git a/win/rfb_win32/SDisplay.h b/win/rfb_win32/SDisplay.h index 6dbfabbc..8e38edb3 100644 --- a/win/rfb_win32/SDisplay.h +++ b/win/rfb_win32/SDisplay.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 @@ -75,13 +76,15 @@ namespace rfb { virtual void terminate(); virtual void queryConnection(network::Socket* sock, const char* userName); + virtual void handleClipboardRequest(); + virtual void handleClipboardAnnounce(bool available); + virtual void handleClipboardData(const char* data); virtual void pointerEvent(const Point& pos, int buttonmask); virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); - virtual void clientCutText(const char* str, int len); - // -=- Clipboard + // -=- Clipboard events - virtual void notifyClipboardChanged(const char* text, int len); + virtual void notifyClipboardChanged(bool available); // -=- Display events |