Merge branch 'exclipboard' of https://github.com/CendioOssman/tigervnc
diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx
index d6960dd..bdde325 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 @@
     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 @@
   reader_ = 0;
   delete writer_;
   writer_ = 0;
+  strFree(serverClipboard);
 }
 
 void CConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_)
@@ -463,6 +466,79 @@
   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 @@
   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 @@
 
   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 66a71a2..f01d5d3 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 @@
     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 @@
     // 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 @@
 
     ModifiablePixelBuffer* framebuffer;
     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 effdaab..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,
@@ -70,10 +71,19 @@
     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 2b5b9fb..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,15 +157,116 @@
 {
   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 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 d357c97..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>
@@ -179,8 +184,14 @@
 }
 
 
-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 @@
   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 4d533d4..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,7 +55,15 @@
 
     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 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/InputHandler.h b/common/rfb/InputHandler.h
index 6c07284..b91f0e4 100644
--- a/common/rfb/InputHandler.h
+++ b/common/rfb/InputHandler.h
@@ -37,8 +37,7 @@
                           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 4e224aa..4869199 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 @@
   : 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 @@
   reader_ = 0;
   delete writer_;
   writer_ = 0;
+  strFree(clientClipboard);
 }
 
 void SConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_)
@@ -297,6 +300,69 @@
   }
 
   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::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 31d1cb2..db3ab08 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 @@
 
     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 @@
     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 @@
     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 0060aa2..b4104ea 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 @@
     // 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 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 200350c..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,19 +207,117 @@
 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 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 6a2c2ba..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>
@@ -78,8 +83,14 @@
   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 @@
   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 4f4c9cc..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,9 +54,17 @@
                                   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 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/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx
index fe00dab..cdd87b1 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,19 +282,6 @@
   }
 }
 
-void VNCSConnectionST::serverCutTextOrClose(const char *str, int len)
-{
-  try {
-    if (!accessCheck(AccessCutText)) return;
-    if (!rfb::Server::sendCutText) return;
-    if (state() == RFBSTATE_NORMAL)
-      writer()->writeServerCutText(str, len);
-  } catch(rdr::Exception& e) {
-    close(e.str());
-  }
-}
-
-
 void VNCSConnectionST::setDesktopNameOrClose(const char *name)
 {
   try {
@@ -305,7 +292,6 @@
   }
 }
 
-
 void VNCSConnectionST::setCursorOrClose()
 {
   try {
@@ -316,7 +302,6 @@
   }
 }
 
-
 void VNCSConnectionST::setLEDStateOrClose(unsigned int state)
 {
   try {
@@ -327,6 +312,41 @@
   }
 }
 
+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::announceClipboardOrClose(bool available)
+{
+  try {
+    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 @@
   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::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 @@
   writer()->writeLEDState();
 }
 
-
 bool VNCSConnectionST::handleTimeout(Timer* t)
 {
   try {
diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h
index a9a8d3a..c8f4c24 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 @@
     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 @@
     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 298326f..5d04da5 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 @@
     // 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 c95c14f..a3655bc 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 @@
 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 @@
     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,6 +334,45 @@
   }
 }
 
+void VNCServerST::requestClipboard()
+{
+  if (clipboardClient == NULL)
+    return;
+
+  clipboardClient->requestClipboard();
+}
+
+void VNCServerST::announceClipboard(bool available)
+{
+  std::list<VNCSConnectionST*>::iterator ci, ci_next;
+
+  if (available)
+    clipboardClient = NULL;
+
+  clipboardRequestors.clear();
+
+  for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
+    ci_next = ci; ci_next++;
+    (*ci)->announceClipboard(available);
+  }
+}
+
+void VNCServerST::sendClipboardData(const char* data)
+{
+  std::list<VNCSConnectionST*>::iterator ci, ci_next;
+
+  if (strchr(data, '\r') != NULL)
+    throw Exception("Invalid carriage return in clipboard data");
+
+  for (ci = clipboardRequestors.begin();
+       ci != clipboardRequestors.end(); ci = ci_next) {
+    ci_next = ci; ci_next++;
+    (*ci)->sendClipboardData(data);
+  }
+
+  clipboardRequestors.clear();
+}
+
 void VNCServerST::bell()
 {
   std::list<VNCSConnectionST*>::iterator ci, ci_next;
@@ -340,15 +382,6 @@
   }
 }
 
-void VNCServerST::serverCutText(const char* str, int len)
-{
-  std::list<VNCSConnectionST*>::iterator ci, ci_next;
-  for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
-    ci_next = ci; ci_next++;
-    (*ci)->serverCutTextOrClose(str, len);
-  }
-}
-
 void VNCServerST::setName(const char* name_)
 {
   name.replaceBuf(strDup(name_));
@@ -459,9 +492,32 @@
   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 43a3bb9..fd20cc3 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 @@
     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 @@
     // 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 @@
 
     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 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);
 }
diff --git a/common/rfb/util.cxx b/common/rfb/util.cxx
index f52213b..fc4f4ca 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 @@
     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 @@
     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 9e59bd3..8503519 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 @@
 
   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 @@
   // 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 301e45e..df5214f 100644
--- a/tests/decperf.cxx
+++ b/tests/decperf.cxx
@@ -54,7 +54,7 @@
   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::serverCutText(const char*, rdr::U32)
+void CConn::serverCutText(const char*)
 {
 }
 
diff --git a/tests/encperf.cxx b/tests/encperf.cxx
index 6523eb7..e461197 100644
--- a/tests/encperf.cxx
+++ b/tests/encperf.cxx
@@ -96,7 +96,7 @@
   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::serverCutText(const char*, rdr::U32)
+void CConn::serverCutText(const char*)
 {
 }
 
diff --git a/unix/x0vncserver/XDesktop.cxx b/unix/x0vncserver/XDesktop.cxx
index 564b2d5..8be9aa3 100644
--- a/unix/x0vncserver/XDesktop.cxx
+++ b/unix/x0vncserver/XDesktop.cxx
@@ -406,7 +406,7 @@
 #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 3e85aac..840d433 100644
--- a/unix/x0vncserver/XDesktop.h
+++ b/unix/x0vncserver/XDesktop.h
@@ -56,7 +56,7 @@
   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 160177b..f108fae 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 @@
   }
   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 a63afd0..112405b 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 @@
 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 d8b3a4d..4edffec 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,6 +182,33 @@
   queryConnectTimer.start(queryConnectTimeout * 1000);
 }
 
+void XserverDesktop::requestClipboard()
+{
+  try {
+    server->requestClipboard();
+  } catch (rdr::Exception& e) {
+    vlog.error("XserverDesktop::requestClipboard: %s",e.str());
+  }
+}
+
+void XserverDesktop::announceClipboard(bool available)
+{
+  try {
+    server->announceClipboard(available);
+  } catch (rdr::Exception& e) {
+    vlog.error("XserverDesktop::announceClipboard: %s",e.str());
+  }
+}
+
+void XserverDesktop::sendClipboardData(const char* data)
+{
+  try {
+    server->sendClipboardData(data);
+  } catch (rdr::Exception& e) {
+    vlog.error("XserverDesktop::sendClipboardData: %s",e.str());
+  }
+}
+
 void XserverDesktop::bell()
 {
   server->bell();
@@ -192,15 +219,6 @@
   server->setLEDState(state);
 }
 
-void XserverDesktop::serverCutText(const char* str, int len)
-{
-  try {
-    server->serverCutText(str, len);
-  } catch (rdr::Exception& e) {
-    vlog.error("XserverDesktop::serverCutText: %s",e.str());
-  }
-}
-
 void XserverDesktop::setDesktopName(const char* name)
 {
   try {
@@ -436,11 +454,6 @@
   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 @@
   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 1253935..6c67068 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 @@
   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 @@
                                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 20072f4..6ab306b 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 @@
     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 5f97f96..1fb87c1 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 @@
 
 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 4f3538d..5191bb9 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 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 @@
 
   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");
 
-  if (vncGetSetPrimary()) {
-    rc = vncOwnSelection(xaPRIMARY);
+  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");
+    }
+
+    rc = vncOwnSelection(xaCLIPBOARD);
     if (rc != Success)
-      LOG_ERROR("Could not set PRIMARY selection");
-  }
+      LOG_ERROR("Could not set CLIPBOARD selection");
+  } else {
+    struct VncDataTarget* next;
 
-  rc = vncOwnSelection(xaCLIPBOARD);
-  if (rc != Success)
-    LOG_ERROR("Could not set CLIPBOARD selection");
+    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;
+    }
+  }
+}
+
+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 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 @@
 
   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 @@
                                  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;
+  } else {
+    if (data == NULL) {
+      struct VncDataTarget* vdt;
 
-    const unsigned char* in;
-    size_t in_len;
+      if ((target != xaSTRING) && (target != xaTEXT) &&
+          (target != xaUTF8_STRING))
+        return BadMatch;
 
-    buffer = malloc(clientCutTextLen*2);
-    if (buffer == NULL)
-      return BadAlloc;
+      vdt = calloc(1, sizeof(struct VncDataTarget));
+      if (vdt == 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--;
+      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 @@
       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 @@
     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;
 
-    const unsigned char* in;
-    size_t in_len;
+    utf8 = vncLatin1ToUTF8(filtered, (size_t)-1);
+    vncStrFree(filtered);
+    if (utf8 == NULL)
+      return;
+
+    LOG_DEBUG("Sending clipboard to clients (%d bytes)",
+              (int)strlen(utf8));
+
+    vncSendClipboardData(utf8);
+
+    vncStrFree(utf8);
+  } else if (target == xaUTF8_STRING) {
+    char *filtered;
 
     if (prop->format != 8)
       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 @@
 {
   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 @@
       !vncGetSendPrimary())
     return;
 
+  LOG_DEBUG("Got clipboard notification, probing for formats");
+
+  probing = TRUE;
   vncSelectionRequest(info->selection->selection, xaTARGETS);
 }
+
+static void vncClientStateCallback(CallbackListPtr * l,
+                                   void * d, void * p)
+{
+  ClientPtr client = ((NewClientInfoRec*)p)->client;
+  if (client->clientState == ClientStateGone) {
+    struct VncDataTarget** nextPtr = &vncDataTargetHead;
+    for (struct VncDataTarget* cur = vncDataTargetHead; cur; cur = *nextPtr) {
+      if (cur->client == client) {
+        *nextPtr = cur->next;
+        free(cur);
+        continue;
+      }
+      nextPtr = &cur->next;
+    }
+  }
+}
diff --git a/unix/xserver/hw/vnc/vncSelection.h b/unix/xserver/hw/vnc/vncSelection.h
index 969f895..ea52bf2 100644
--- a/unix/xserver/hw/vnc/vncSelection.h
+++ b/unix/xserver/hw/vnc/vncSelection.h
@@ -1,4 +1,4 @@
-/* Copyright 2016 Pierre Ossman for Cendio AB
+/* Copyright 2016-2019 Pierre Ossman for Cendio AB
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,7 +24,9 @@
 
 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 b4610e6..6ba3276 100644
--- a/vncviewer/CConn.cxx
+++ b/vncviewer/CConn.cxx
@@ -377,11 +377,6 @@
   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 @@
   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 2e3362c..4d935c9 100644
--- a/vncviewer/CConn.h
+++ b/vncviewer/CConn.h
@@ -61,8 +61,6 @@
 
   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 @@
 
   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 c52a915..1b666f9 100644
--- a/vncviewer/DesktopWindow.cxx
+++ b/vncviewer/DesktopWindow.cxx
@@ -276,12 +276,6 @@
 }
 
 
-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::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 97a8178..ef3dbb0 100644
--- a/vncviewer/DesktopWindow.h
+++ b/vncviewer/DesktopWindow.h
@@ -62,9 +62,6 @@
   // 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 @@
   // 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 5df5c79..cd61327 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 @@
     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 @@
     delete cursor;
   }
 
-  clearPendingClipboard();
-
   // FLTK automatically deletes all child widgets, so we shouldn't touch
   // them ourselves here
 }
@@ -232,45 +230,6 @@
   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 @@
     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 @@
 
 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];
+    filtered = convertLF(Fl::event_text(), Fl::event_length());
 
-    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));
-
-    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 @@
     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 (pendingClientCutText) {
-    size_t len = strlen(pendingClientCutText);
-    vlog.debug("Sending pending clipboard data (%d bytes)", (int)len);
+  if (pendingServerClipboard) {
     try {
-      cc->writer()->writeClientCutText(pendingClientCutText, len);
+      cc->requestClipboard();
+    } catch (rdr::Exception& e) {
+      vlog.error("%s", e.str());
+      exit_vncviewer(e.str());
+    }
+  }
+  if (pendingClientClipboard) {
+    try {
+      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 be2192b..1fb93c6 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 @@
   // 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 @@
 
   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 @@
 
   static void handleClipboardChange(int source, void *data);
 
-  void clearPendingClipboard();
   void flushPendingClipboard();
 
   void handlePointerEvent(const rfb::Point& pos, int buttonMask);
@@ -114,8 +115,10 @@
 
   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 ebfe772..f93e096 100644
--- a/vncviewer/vncviewer.man
+++ b/vncviewer/vncviewer.man
@@ -182,6 +182,11 @@
 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 0e29062..1196367 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 @@
 
 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 @@
       } 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 @@
   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 @@
     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 3da7bfc..1dead82 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 @@
       // -=- 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 @@
       // - 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 2cedc4a..be33ff1 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::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 @@
   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 6dbfabb..8e38edb 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 @@
       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