Support extended clipboard transfers
Implements support in both client and server for the extended
clipboard format first seen in UltraVNC. Currently only implements
text handling, but that is still an improvement as it extends the
clipboard from ISO 8859-1 to full Unicode.
diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx
index 4e8ea4e..bdde325 100644
--- a/common/rfb/CConnection.cxx
+++ b/common/rfb/CConnection.cxx
@@ -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>
@@ -53,7 +54,7 @@
firstUpdate(true), pendingUpdate(false), continuousUpdates(false),
forceNonincremental(true),
framebuffer(NULL), decoder(this),
- serverClipboard(NULL)
+ serverClipboard(NULL), hasLocalClipboard(false)
{
}
@@ -467,6 +468,8 @@
void CConnection::serverCutText(const char* str)
{
+ hasLocalClipboard = false;
+
strFree(serverClipboard);
serverClipboard = NULL;
@@ -475,6 +478,67 @@
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()
{
}
@@ -506,19 +570,35 @@
handleClipboardData(serverClipboard);
return;
}
+
+ if (server.clipboardFlags() & rfb::clipboardRequest)
+ writer()->writeClipboardRequest(rfb::clipboardUTF8);
}
void CConnection::announceClipboard(bool available)
{
- if (available)
- handleClipboardRequest();
+ hasLocalClipboard = available;
+
+ if (server.clipboardFlags() & rfb::clipboardNotify)
+ writer()->writeClipboardNotify(available ? rfb::clipboardUTF8 : 0);
+ else {
+ if (available)
+ handleClipboardRequest();
+ }
}
void CConnection::sendClipboardData(const char* data)
{
- CharArray latin1(utf8ToLatin1(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);
+ writer()->writeClientCutText(latin1.buf);
+ }
}
void CConnection::refreshFramebuffer()
@@ -656,6 +736,7 @@
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 4106a1e..f01d5d3 100644
--- a/common/rfb/CConnection.h
+++ b/common/rfb/CConnection.h
@@ -111,6 +111,15 @@
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
@@ -277,6 +286,7 @@
DecodeManager decoder;
char* serverClipboard;
+ bool hasLocalClipboard;
};
}
#endif
diff --git a/common/rfb/CMsgHandler.cxx b/common/rfb/CMsgHandler.cxx
index c009067..9dab5d9 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 @@
{
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 1581f79..84dd115 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 @@
// 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,
@@ -74,6 +75,15 @@
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 86288ad..a9e12d7 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,7 +157,15 @@
{
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;
@@ -163,6 +176,99 @@
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()
{
rdr::U32 flags;
diff --git a/common/rfb/CMsgReader.h b/common/rfb/CMsgReader.h
index 03f3d8d..050990a 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 @@
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 f1fa58d..3180391 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>
@@ -194,6 +199,104 @@
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 d3ac19c..7b83939 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,8 +55,16 @@
void writeKeyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down);
void writePointerEvent(const Point& pos, int buttonMask);
+
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);
void endMsg();
diff --git a/common/rfb/ClientParams.cxx b/common/rfb/ClientParams.cxx
index e42d494..987abe3 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 @@
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 @@
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 f7a7044..aab3d64 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 @@
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 @@
Cursor* cursor_;
std::set<rdr::S32> encodings_;
unsigned int ledState_;
+ rdr::U32 clipFlags;
+ rdr::U32 clipSizes[16];
};
}
#endif
diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx
index 46f0a85..4869199 100644
--- a/common/rfb/SConnection.cxx
+++ b/common/rfb/SConnection.cxx
@@ -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>
@@ -53,7 +54,7 @@
is(0), os(0), reader_(0), writer_(0),
ssecurity(0), state_(RFBSTATE_UNINITIALISED),
preferredEncoding(encodingRaw),
- clientClipboard(NULL)
+ clientClipboard(NULL), hasLocalClipboard(false)
{
defaultMajorVersion = 3;
defaultMinorVersion = 8;
@@ -299,6 +300,16 @@
}
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)
@@ -311,6 +322,49 @@
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()
{
writer()->writeQEMUKeyEvent();
@@ -440,19 +494,38 @@
handleClipboardData(clientClipboard);
return;
}
+
+ if (client.supportsEncoding(pseudoEncodingExtendedClipboard) &&
+ (client.clipboardFlags() & rfb::clipboardRequest))
+ writer()->writeClipboardRequest(rfb::clipboardUTF8);
}
void SConnection::announceClipboard(bool available)
{
- if (available)
- handleClipboardRequest();
+ 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)
{
- CharArray latin1(utf8ToLatin1(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);
+ writer()->writeServerCutText(latin1.buf);
+ }
}
void SConnection::writeFakeColourMap(void)
diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h
index 6c80569..db3ab08 100644
--- a/common/rfb/SConnection.h
+++ b/common/rfb/SConnection.h
@@ -82,6 +82,13 @@
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();
@@ -247,6 +254,7 @@
AccessRights accessRights;
char* clientClipboard;
+ bool hasLocalClipboard;
};
}
#endif
diff --git a/common/rfb/SMsgHandler.cxx b/common/rfb/SMsgHandler.cxx
index f952ec2..32b561e 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 @@
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 8548d91..b290f19 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 @@
// 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 @@
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 5efbfe2..ab42e59 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,11 +207,16 @@
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;
@@ -218,6 +227,99 @@
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()
{
int subType = is->readU8();
diff --git a/common/rfb/SMsgReader.h b/common/rfb/SMsgReader.h
index 146b29f..4991fd3 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 @@
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 3d5a64c..becf6e7 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>
@@ -93,6 +98,112 @@
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 8cf2ae7..2cea44d 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,10 +54,18 @@
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);
+ 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 bfeb80d..a2e3aa8 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 @@
ledState_(ledUnknown)
{
setName("");
+
cursor_ = new Cursor(0, 0, Point(), NULL);
+
+ clipFlags = 0;
+ memset(clipSizes, 0, sizeof(clipSizes));
}
ServerParams::~ServerParams()
@@ -82,3 +86,17 @@
{
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 7a58ea3..c84f625 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 @@
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 @@
char* name_;
Cursor* cursor_;
unsigned int ledState_;
+ rdr::U32 clipFlags;
+ rdr::U32 clipSizes[16];
};
}
#endif
diff --git a/common/rfb/clipboardTypes.h b/common/rfb/clipboardTypes.h
new file mode 100644
index 0000000..bd3fa03
--- /dev/null
+++ b/common/rfb/clipboardTypes.h
@@ -0,0 +1,41 @@
+/* Copyright 2019 Pierre Ossman for Cendio AB
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+#ifndef __RFB_CLIPBOARDTYPES_H__
+#define __RFB_CLIPBOARDTYPES_H__
+
+namespace rfb {
+
+ // Formats
+ const unsigned int clipboardUTF8 = 1 << 0;
+ const unsigned int clipboardRTF = 1 << 1;
+ const unsigned int clipboardHTML = 1 << 2;
+ const unsigned int clipboardDIB = 1 << 3;
+ const unsigned int clipboardFiles = 1 << 4;
+
+ const unsigned int clipboardFormatMask = 0x0000ffff;
+
+ // Actions
+ const unsigned int clipboardCaps = 1 << 24;
+ const unsigned int clipboardRequest = 1 << 25;
+ const unsigned int clipboardPeek = 1 << 26;
+ const unsigned int clipboardNotify = 1 << 27;
+ const unsigned int clipboardProvide = 1 << 28;
+
+ const unsigned int clipboardActionMask = 0xff000000;
+}
+#endif
diff --git a/common/rfb/encodings.h b/common/rfb/encodings.h
index acb86ec..cf0c857 100644
--- a/common/rfb/encodings.h
+++ b/common/rfb/encodings.h
@@ -63,6 +63,9 @@
const int pseudoEncodingVMwareCursor = 0x574d5664;
const int pseudoEncodingVMwareLEDState = 0x574d5668;
+ // UltraVNC-specific
+ const int pseudoEncodingExtendedClipboard = 0xC0A1E5CE;
+
int encodingNum(const char* name);
const char* encodingName(int num);
}