]> source.dussan.org Git - tigervnc.git/commitdiff
Support extended clipboard transfers 834/head
authorPierre Ossman <ossman@cendio.se>
Fri, 5 Feb 2016 09:26:56 +0000 (10:26 +0100)
committerPierre Ossman <ossman@cendio.se>
Mon, 1 Jul 2019 09:18:27 +0000 (11:18 +0200)
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.

23 files changed:
common/rfb/CConnection.cxx
common/rfb/CConnection.h
common/rfb/CMsgHandler.cxx
common/rfb/CMsgHandler.h
common/rfb/CMsgReader.cxx
common/rfb/CMsgReader.h
common/rfb/CMsgWriter.cxx
common/rfb/CMsgWriter.h
common/rfb/ClientParams.cxx
common/rfb/ClientParams.h
common/rfb/SConnection.cxx
common/rfb/SConnection.h
common/rfb/SMsgHandler.cxx
common/rfb/SMsgHandler.h
common/rfb/SMsgReader.cxx
common/rfb/SMsgReader.h
common/rfb/SMsgWriter.cxx
common/rfb/SMsgWriter.h
common/rfb/ServerParams.cxx
common/rfb/ServerParams.h
common/rfb/clipboardTypes.h [new file with mode: 0644]
common/rfb/encodings.h
vncviewer/vncviewer.man

index 4e8ea4e587fb06e6438126deb5b66169f7a92c94..bdde32538271381e7b2fb80a6422f535a7dffb09 100644 (file)
@@ -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 @@ CConnection::CConnection()
     firstUpdate(true), pendingUpdate(false), continuousUpdates(false),
     forceNonincremental(true),
     framebuffer(NULL), decoder(this),
-    serverClipboard(NULL)
+    serverClipboard(NULL), hasLocalClipboard(false)
 {
 }
 
@@ -467,6 +468,8 @@ void CConnection::dataRect(const Rect& r, int encoding)
 
 void CConnection::serverCutText(const char* str)
 {
+  hasLocalClipboard = false;
+
   strFree(serverClipboard);
   serverClipboard = NULL;
 
@@ -475,6 +478,67 @@ void CConnection::serverCutText(const char* 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()
 {
 }
@@ -506,19 +570,35 @@ void CConnection::requestClipboard()
     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 @@ 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);
index 4106a1e6ea1d7a1af374ad7f3efdd8a88ec10cc9..f01d5d362101d3f2470c5e8a931f00d1131fb021 100644 (file)
@@ -111,6 +111,15 @@ namespace rfb {
 
     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 @@ namespace rfb {
     DecodeManager decoder;
 
     char* serverClipboard;
+    bool hasLocalClipboard;
   };
 }
 #endif
index c009067ab448dc9fcf8d0abd48ffe91a11f026a1..9dab5d94420061d4cf99b7a0c2a5037b413465ba 100644 (file)
@@ -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)
+{
+}
index 1581f79235ac8601e455ea16e98e9a41c985fd04..84dd115ce11336eebc7644fbde59345444f0542d 100644 (file)
@@ -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,
@@ -74,6 +75,15 @@ namespace rfb {
 
     virtual void setLEDState(unsigned int state);
 
+    virtual void handleClipboardCaps(rdr::U32 flags,
+                                     const rdr::U32* lengths);
+    virtual void handleClipboardRequest(rdr::U32 flags);
+    virtual void handleClipboardPeek(rdr::U32 flags);
+    virtual void handleClipboardNotify(rdr::U32 flags);
+    virtual void handleClipboardProvide(rdr::U32 flags,
+                                        const size_t* lengths,
+                                        const rdr::U8* const* data);
+
     ServerParams server;
   };
 }
index 86288ad92e1c17b24ac6e90e21c3e7bcd5b1350b..a9e12d703626251655fd6af2e50af570b5e4d6b2 100644 (file)
@@ -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
 #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 @@ 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;
@@ -163,6 +176,99 @@ void CMsgReader::readServerCutText()
   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;
index 03f3d8d2045af8d757a0903d0120917b580bc7a3..050990a989ab64ca4c2c2f47dbab2f83725b1930 100644 (file)
@@ -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();
 
index f1fa58dd9159733e35ee1b1c22726ae46b2c3cf4..3180391b2e36442f613e54dd01fe09cc4ec19210 100644 (file)
@@ -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
  * 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 @@ void CMsgWriter::writeClientCutText(const char* str)
   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);
index d3ac19c9f4bb6bb4cd0e7cc4dc5b5a873f19a4fc..7b839393833aa64f04739e29e24e62f07096f4c0 100644 (file)
@@ -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 @@ namespace rfb {
 
     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();
index e42d494bce49801799eb68a957bb9f97ac8b5f1a..987abe32e4392f30496ad60076a73933f6b68821 100644 (file)
@@ -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))
index f7a7044b4f56b26d7abc0704f86336a852f89c33..aab3d644a77ea780f49af4177da0093d817e6abd 100644 (file)
@@ -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
index 46f0a850d6779239920fc7e2c937ada4e96d1a19..4869199a088328ebd8d649f452ea95401000405c 100644 (file)
@@ -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 @@ SConnection::SConnection()
     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 @@ 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)
@@ -311,6 +322,49 @@ void SConnection::clientCutText(const char* 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()
 {
   writer()->writeQEMUKeyEvent();
@@ -440,19 +494,38 @@ void SConnection::requestClipboard()
     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)
index 6c80569db09122f4df1f5668e82103b559c34552..db3ab08c8fbf227d04e223d42c4f0510e7ffbf30 100644 (file)
@@ -82,6 +82,13 @@ namespace rfb {
 
     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 @@ namespace rfb {
     AccessRights accessRights;
 
     char* clientClipboard;
+    bool hasLocalClipboard;
   };
 }
 #endif
index f952ec2b1b25a35e84a2bbb1f906420737efab65..32b561e769394ae6303fa0e46b286a7281ac8ce4 100644 (file)
@@ -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()
 {
 }
index 8548d911291dcf8cd78491cf9d0e2b7725c2c54e..b290f194b77157133ad8f0bb3d59bb38b90f9e9c 100644 (file)
@@ -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.
 
index 5efbfe2b4b530792cbd0eb7220c164fd33916a09..ab42e59a66306042f581c96b4584a2f753957152 100644 (file)
@@ -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
  * 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::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;
@@ -218,6 +227,99 @@ void SMsgReader::readClientCutText()
   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();
index 146b29f8bea64aa1e2c97fae4873898afe79aec3..4991fd38ca966f0d2075f94d6c25a6b2c733d649 100644 (file)
@@ -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();
index 3d5a64caf6ff7e40b10a6a17867191af3a279a9a..becf6e708fbaa450919cc7229736cdc0dde35480 100644 (file)
@@ -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
  * 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 @@ void SMsgWriter::writeServerCutText(const char* str)
   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))
index 8cf2ae77eb5853897259846361a3e4c6541dc1e6..2cea44d1e28d8e234438df29525739ccf181a4da 100644 (file)
@@ -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 @@ 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);
 
+    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[]);
 
index bfeb80d6989fac09042caefb179f4ba7a2fcf0af..a2e3aa8354235c010720f3e519817b5a02efd3f9 100644 (file)
@@ -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++];
+  }
+}
index 7a58ea37b680726703744ca321ae43eab8310541..c84f6252ffa8050cde59e37f4a02c8a7d5532c79 100644 (file)
@@ -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/clipboardTypes.h b/common/rfb/clipboardTypes.h
new file mode 100644 (file)
index 0000000..bd3fa03
--- /dev/null
@@ -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
index acb86ecc31c252f152c266fbca9fc7f90ce0e226..cf0c8572fe7ae1e2e417e85cc07588f5a9830d64 100644 (file)
@@ -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);
 }
index ebfe77257adb54b397067252a13c4ac705858af7..f93e096a54f83156c19ae898b2a6cf26b6dc223d 100644 (file)
@@ -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.
 .