From 615d16bd5ba11e89262cc5cfe94a35b6d6e7a628 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 3 May 2019 10:53:06 +0200 Subject: [PATCH] Improved clipboard API Change the internal clipboard API to use a request based model in order to be prepared for more advanced clipboard transfers. --- common/rfb/CConnection.cxx | 47 ++++- common/rfb/CConnection.h | 39 +++- common/rfb/SConnection.cxx | 47 ++++- common/rfb/SConnection.h | 41 +++- common/rfb/SDesktop.h | 20 ++ common/rfb/VNCSConnectionST.cxx | 78 +++++--- common/rfb/VNCSConnectionST.h | 10 +- common/rfb/VNCServer.h | 19 +- common/rfb/VNCServerST.cxx | 76 ++++++-- common/rfb/VNCServerST.h | 14 +- unix/xserver/hw/vnc/XserverDesktop.cc | 54 ++++-- unix/xserver/hw/vnc/XserverDesktop.h | 10 +- unix/xserver/hw/vnc/vncExtInit.cc | 18 +- unix/xserver/hw/vnc/vncExtInit.h | 6 +- unix/xserver/hw/vnc/vncSelection.c | 261 +++++++++++++++++++++----- unix/xserver/hw/vnc/vncSelection.h | 4 +- vncviewer/CConn.cxx | 20 +- vncviewer/CConn.h | 6 +- vncviewer/DesktopWindow.cxx | 22 ++- vncviewer/DesktopWindow.h | 8 +- vncviewer/Viewport.cxx | 143 ++++++++------ vncviewer/Viewport.h | 15 +- win/rfb_win32/Clipboard.cxx | 64 ++++--- win/rfb_win32/Clipboard.h | 5 +- win/rfb_win32/SDisplay.cxx | 24 ++- win/rfb_win32/SDisplay.h | 8 +- 26 files changed, 820 insertions(+), 239 deletions(-) diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx index d6960dd2..ce2741e4 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 @@ -52,7 +52,8 @@ CConnection::CConnection() formatChange(false), encodingChange(false), firstUpdate(true), pendingUpdate(false), continuousUpdates(false), forceNonincremental(true), - framebuffer(NULL), decoder(this) + framebuffer(NULL), decoder(this), + serverClipboard(NULL) { } @@ -65,6 +66,7 @@ CConnection::~CConnection() reader_ = 0; delete writer_; writer_ = 0; + strFree(serverClipboard); } void CConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_) @@ -463,6 +465,16 @@ void CConnection::dataRect(const Rect& r, int encoding) decoder.decodeRect(r, encoding, framebuffer); } +void CConnection::serverCutText(const char* str) +{ + strFree(serverClipboard); + serverClipboard = NULL; + + serverClipboard = strDup(str); + + handleClipboardAnnounce(true); +} + void CConnection::authSuccess() { } @@ -476,6 +488,37 @@ void CConnection::resizeFramebuffer() assert(false); } +void CConnection::handleClipboardRequest() +{ +} + +void CConnection::handleClipboardAnnounce(bool available) +{ +} + +void CConnection::handleClipboardData(const char* data) +{ +} + +void CConnection::requestClipboard() +{ + if (serverClipboard != NULL) { + handleClipboardData(serverClipboard); + return; + } +} + +void CConnection::announceClipboard(bool available) +{ + if (available) + handleClipboardRequest(); +} + +void CConnection::sendClipboardData(const char* data) +{ + writer()->writeClientCutText(data); +} + void CConnection::refreshFramebuffer() { forceNonincremental = true; diff --git a/common/rfb/CConnection.h b/common/rfb/CConnection.h index 66a71a24..4106a1e6 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,8 @@ namespace rfb { virtual void framebufferUpdateEnd(); virtual void dataRect(const Rect& r, int encoding); + virtual void serverCutText(const char* str); + // Methods to be overridden in a derived class @@ -128,9 +130,42 @@ namespace rfb { // sure the pixel buffer has been updated once this call returns. virtual void resizeFramebuffer(); + // handleClipboardRequest() is called whenever the server requests + // the client to send over its clipboard data. It will only be + // called after the client has first announced a clipboard change + // via announceClipboard(). + virtual void handleClipboardRequest(); + + // handleClipboardAnnounce() is called to indicate a change in the + // clipboard on the server. Call requestClipboard() to access the + // actual data. + virtual void handleClipboardAnnounce(bool available); + + // handleClipboardData() is called when the server has sent over + // the clipboard data as a result of a previous call to + // requestClipboard(). Note that this function might never be + // called if the clipboard data was no longer available when the + // server received the request. + virtual void handleClipboardData(const char* data); + // Other methods + // requestClipboard() will result in a request to the server to + // transfer its clipboard data. A call to handleClipboardData() + // will be made once the data is available. + virtual void requestClipboard(); + + // announceClipboard() informs the server of changes to the + // clipboard on the client. The server may later request the + // clipboard data via handleClipboardRequest(). + virtual void announceClipboard(bool available); + + // sendClipboardData() transfers the clipboard data to the server + // and should be called whenever the server has requested the + // clipboard via handleClipboardRequest(). + virtual void sendClipboardData(const char* data); + // refreshFramebuffer() forces a complete refresh of the entire // framebuffer void refreshFramebuffer(); @@ -240,6 +275,8 @@ namespace rfb { ModifiablePixelBuffer* framebuffer; DecodeManager decoder; + + char* serverClipboard; }; } #endif diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx index 4e224aa1..1cc330d8 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 @@ -52,7 +52,8 @@ SConnection::SConnection() : readyForSetColourMapEntries(false), is(0), os(0), reader_(0), writer_(0), ssecurity(0), state_(RFBSTATE_UNINITIALISED), - preferredEncoding(encodingRaw) + preferredEncoding(encodingRaw), + clientClipboard(NULL) { defaultMajorVersion = 3; defaultMinorVersion = 8; @@ -70,6 +71,7 @@ SConnection::~SConnection() reader_ = 0; delete writer_; writer_ = 0; + strFree(clientClipboard); } void SConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_) @@ -299,6 +301,16 @@ void SConnection::setEncodings(int nEncodings, const rdr::S32* encodings) SMsgHandler::setEncodings(nEncodings, encodings); } +void SConnection::clientCutText(const char* str) +{ + strFree(clientClipboard); + clientClipboard = NULL; + + clientClipboard = strDup(str); + + handleClipboardAnnounce(true); +} + void SConnection::supportsQEMUKeyEvent() { writer()->writeQEMUKeyEvent(); @@ -410,6 +422,37 @@ void SConnection::enableContinuousUpdates(bool enable, { } +void SConnection::handleClipboardRequest() +{ +} + +void SConnection::handleClipboardAnnounce(bool available) +{ +} + +void SConnection::handleClipboardData(const char* data) +{ +} + +void SConnection::requestClipboard() +{ + if (clientClipboard != NULL) { + handleClipboardData(clientClipboard); + return; + } +} + +void SConnection::announceClipboard(bool available) +{ + if (available) + handleClipboardRequest(); +} + +void SConnection::sendClipboardData(const char* data) +{ + writer()->writeServerCutText(data); +} + void SConnection::writeFakeColourMap(void) { int i; diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h index 31d1cb2e..6c80569d 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,11 @@ namespace rfb { virtual void setEncodings(int nEncodings, const rdr::S32* encodings); + virtual void clientCutText(const char* str); + virtual void supportsQEMUKeyEvent(); + // Methods to be overridden in a derived class // versionReceived() indicates that the version number has just been read @@ -129,8 +132,42 @@ namespace rfb { virtual void enableContinuousUpdates(bool enable, int x, int y, int w, int h); + // handleClipboardRequest() is called whenever the client requests + // the server to send over its clipboard data. It will only be + // called after the server has first announced a clipboard change + // via announceClipboard(). + virtual void handleClipboardRequest(); + + // handleClipboardAnnounce() is called to indicate a change in the + // clipboard on the client. Call requestClipboard() to access the + // actual data. + virtual void handleClipboardAnnounce(bool available); + + // handleClipboardData() is called when the client has sent over + // the clipboard data as a result of a previous call to + // requestClipboard(). Note that this function might never be + // called if the clipboard data was no longer available when the + // client received the request. + virtual void handleClipboardData(const char* data); + + // Other methods + // requestClipboard() will result in a request to the client to + // transfer its clipboard data. A call to handleClipboardData() + // will be made once the data is available. + virtual void requestClipboard(); + + // announceClipboard() informs the client of changes to the + // clipboard on the server. The client may later request the + // clipboard data via handleClipboardRequest(). + virtual void announceClipboard(bool available); + + // sendClipboardData() transfers the clipboard data to the client + // and should be called whenever the client has requested the + // clipboard via handleClipboardRequest(). + virtual void sendClipboardData(const char* data); + // setAccessRights() allows a security package to limit the access rights // of a SConnection to the server. How the access rights are treated // is up to the derived class. @@ -208,6 +245,8 @@ namespace rfb { stateEnum state_; rdr::S32 preferredEncoding; AccessRights accessRights; + + char* clientClipboard; }; } #endif diff --git a/common/rfb/SDesktop.h b/common/rfb/SDesktop.h index 0060aa23..b4104ea7 100644 --- a/common/rfb/SDesktop.h +++ b/common/rfb/SDesktop.h @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -93,6 +94,25 @@ namespace rfb { // pointerEvent(), keyEvent() and clientCutText() are called in response to // the relevant RFB protocol messages from clients. // See InputHandler for method signatures. + + // handleClipboardRequest() is called whenever a client requests + // the server to send over its clipboard data. It will only be + // called after the server has first announced a clipboard change + // via VNCServer::announceClipboard(). + virtual void handleClipboardRequest() {} + + // handleClipboardAnnounce() is called to indicate a change in the + // clipboard on a client. Call VNCServer::requestClipboard() to + // access the actual data. + virtual void handleClipboardAnnounce(bool __unused_attr available) {} + + // handleClipboardData() is called when a client has sent over + // the clipboard data as a result of a previous call to + // VNCServer::requestClipboard(). Note that this function might + // never be called if the clipboard data was no longer available + // when the client received the request. + virtual void handleClipboardData(const char* __unused_attr data) {} + protected: virtual ~SDesktop() {} }; diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 002ae925..cdd87b13 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2018 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * Copyright 2018 Peter Astrand for Cendio AB * * This is free software; you can redistribute it and/or modify @@ -282,51 +282,71 @@ void VNCSConnectionST::bellOrClose() } } -void VNCSConnectionST::serverCutTextOrClose(const char *str) +void VNCSConnectionST::setDesktopNameOrClose(const char *name) { try { - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::sendCutText) return; - if (state() == RFBSTATE_NORMAL) - writer()->writeServerCutText(str); + setDesktopName(name); + writeFramebufferUpdate(); } catch(rdr::Exception& e) { close(e.str()); } } - -void VNCSConnectionST::setDesktopNameOrClose(const char *name) +void VNCSConnectionST::setCursorOrClose() { try { - setDesktopName(name); + setCursor(); writeFramebufferUpdate(); } catch(rdr::Exception& e) { close(e.str()); } } - -void VNCSConnectionST::setCursorOrClose() +void VNCSConnectionST::setLEDStateOrClose(unsigned int state) { try { - setCursor(); + setLEDState(state); writeFramebufferUpdate(); } catch(rdr::Exception& e) { close(e.str()); } } +void VNCSConnectionST::requestClipboardOrClose() +{ + try { + if (!accessCheck(AccessCutText)) return; + if (!rfb::Server::acceptCutText) return; + if (state() != RFBSTATE_NORMAL) return; + requestClipboard(); + } catch(rdr::Exception& e) { + close(e.str()); + } +} -void VNCSConnectionST::setLEDStateOrClose(unsigned int state) +void VNCSConnectionST::announceClipboardOrClose(bool available) { try { - setLEDState(state); - writeFramebufferUpdate(); + if (!accessCheck(AccessCutText)) return; + if (!rfb::Server::sendCutText) return; + if (state() != RFBSTATE_NORMAL) return; + announceClipboard(available); } catch(rdr::Exception& e) { close(e.str()); } } +void VNCSConnectionST::sendClipboardDataOrClose(const char* data) +{ + try { + if (!accessCheck(AccessCutText)) return; + if (!rfb::Server::sendCutText) return; + if (state() != RFBSTATE_NORMAL) return; + sendClipboardData(data); + } catch(rdr::Exception& e) { + close(e.str()); + } +} bool VNCSConnectionST::getComparerState() { @@ -596,13 +616,6 @@ void VNCSConnectionST::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) { server->keyEvent(keysym, keycode, down); } -void VNCSConnectionST::clientCutText(const char* str) -{ - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::acceptCutText) return; - server->clientCutText(str); -} - void VNCSConnectionST::framebufferUpdateRequest(const Rect& r,bool incremental) { Rect safeRect; @@ -719,6 +732,26 @@ void VNCSConnectionST::enableContinuousUpdates(bool enable, } } +void VNCSConnectionST::handleClipboardRequest() +{ + if (!accessCheck(AccessCutText)) return; + server->handleClipboardRequest(this); +} + +void VNCSConnectionST::handleClipboardAnnounce(bool available) +{ + if (!accessCheck(AccessCutText)) return; + if (!rfb::Server::acceptCutText) return; + server->handleClipboardAnnounce(this, available); +} + +void VNCSConnectionST::handleClipboardData(const char* data) +{ + if (!accessCheck(AccessCutText)) return; + if (!rfb::Server::acceptCutText) return; + server->handleClipboardData(this, data); +} + // supportsLocalCursor() is called whenever the status of // client.supportsLocalCursor() has changed. If the client does now support local // cursor, we make sure that the old server-side rendered cursor is cleaned up @@ -756,7 +789,6 @@ void VNCSConnectionST::supportsLEDState() writer()->writeLEDState(); } - bool VNCSConnectionST::handleTimeout(Timer* t) { try { diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index 54266e3c..c8f4c24f 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2016 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -72,10 +72,12 @@ namespace rfb { void screenLayoutChangeOrClose(rdr::U16 reason); void setCursorOrClose(); void bellOrClose(); - void serverCutTextOrClose(const char *str); void setDesktopNameOrClose(const char *name); void setLEDStateOrClose(unsigned int state); void approveConnectionOrClose(bool accept, const char* reason); + void requestClipboardOrClose(); + void announceClipboardOrClose(bool available); + void sendClipboardDataOrClose(const char* data); // The following methods never throw exceptions @@ -115,13 +117,15 @@ namespace rfb { virtual void setPixelFormat(const PixelFormat& pf); virtual void pointerEvent(const Point& pos, int buttonMask); virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); - virtual void clientCutText(const char* str); 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 5398e9fd..5d04da53 100644 --- a/common/rfb/VNCServer.h +++ b/common/rfb/VNCServer.h @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -55,9 +56,21 @@ namespace rfb { // getPixelBuffer() returns a pointer to the PixelBuffer object. virtual const PixelBuffer* getPixelBuffer() const = 0; - // serverCutText() tells the server that the cut text has changed. This - // will normally be sent to all clients. - virtual void serverCutText(const char* str) = 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 21af0dac..a3655bca 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2018 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -77,8 +77,8 @@ static LogWriter connectionsLog("Connections"); VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) : blHosts(&blacklist), desktop(desktop_), desktopStarted(false), blockCounter(0), pb(0), ledState(ledUnknown), - name(strDup(name_)), pointerClient(0), comparer(0), - cursor(new Cursor(0, 0, Point(), NULL)), + name(strDup(name_)), pointerClient(0), clipboardClient(0), + comparer(0), cursor(new Cursor(0, 0, Point(), NULL)), renderedCursorInvalid(false), keyRemapper(&KeyRemapper::defInstance), idleTimer(this), disconnectTimer(this), connectTimer(this), @@ -167,9 +167,12 @@ void VNCServerST::removeSocket(network::Socket* sock) { if ((*ci)->getSock() == sock) { clients.remove(*ci); - // - Release the cursor if this client owns it + // - Remove any references to it if (pointerClient == *ci) pointerClient = NULL; + if (clipboardClient == *ci) + clipboardClient = NULL; + clipboardRequestors.remove(*ci); // Adjust the exit timers connectTimer.stop(); @@ -331,23 +334,51 @@ void VNCServerST::setScreenLayout(const ScreenSet& layout) } } -void VNCServerST::bell() +void VNCServerST::requestClipboard() +{ + if (clipboardClient == NULL) + return; + + clipboardClient->requestClipboard(); +} + +void VNCServerST::announceClipboard(bool available) { std::list::iterator ci, ci_next; + + if (available) + clipboardClient = NULL; + + clipboardRequestors.clear(); + for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { ci_next = ci; ci_next++; - (*ci)->bellOrClose(); + (*ci)->announceClipboard(available); } } -void VNCServerST::serverCutText(const char* str) +void VNCServerST::sendClipboardData(const char* data) { - if (strchr(str, '\r') != NULL) + std::list::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::iterator ci, ci_next; for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { ci_next = ci; ci_next++; - (*ci)->serverCutTextOrClose(str); + (*ci)->bellOrClose(); } } @@ -461,9 +492,32 @@ void VNCServerST::pointerEvent(VNCSConnectionST* client, desktop->pointerEvent(pos, buttonMask); } -void VNCServerST::clientCutText(const char* str) +void VNCServerST::handleClipboardRequest(VNCSConnectionST* client) { - desktop->clientCutText(str); + 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 5231977d..fd20cc37 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2016 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -85,7 +85,10 @@ namespace rfb { virtual void setPixelBuffer(PixelBuffer* pb); virtual void setScreenLayout(const ScreenSet& layout); virtual const PixelBuffer* getPixelBuffer() const { return pb; } - virtual void serverCutText(const char* str); + + virtual void requestClipboard(); + virtual void announceClipboard(bool available); + virtual void sendClipboardData(const char* data); virtual void approveConnection(network::Socket* sock, bool accept, const char* reason); @@ -115,7 +118,10 @@ namespace rfb { // Event handlers void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); void pointerEvent(VNCSConnectionST* client, const Point& pos, int buttonMask); - void clientCutText(const char* str); + + void handleClipboardRequest(VNCSConnectionST* client); + void handleClipboardAnnounce(VNCSConnectionST* client, bool available); + void handleClipboardData(VNCSConnectionST* client, const char* data); unsigned int setDesktopSize(VNCSConnectionST* requester, int fb_width, int fb_height, @@ -181,6 +187,8 @@ namespace rfb { std::list clients; VNCSConnectionST* pointerClient; + VNCSConnectionST* clipboardClient; + std::list clipboardRequestors; std::list closingSockets; ComparingUpdateTracker* comparer; diff --git a/unix/xserver/hw/vnc/XserverDesktop.cc b/unix/xserver/hw/vnc/XserverDesktop.cc index cd9b1f9b..4edffec7 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.cc +++ b/unix/xserver/hw/vnc/XserverDesktop.cc @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2017 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * Copyright 2014 Brian P. Hinz * * This is free software; you can redistribute it and/or modify @@ -182,25 +182,43 @@ void XserverDesktop::queryConnection(network::Socket* sock, queryConnectTimer.start(queryConnectTimeout * 1000); } -void XserverDesktop::bell() +void XserverDesktop::requestClipboard() { - server->bell(); + try { + server->requestClipboard(); + } catch (rdr::Exception& e) { + vlog.error("XserverDesktop::requestClipboard: %s",e.str()); + } } -void XserverDesktop::setLEDState(unsigned int state) +void XserverDesktop::announceClipboard(bool available) { - server->setLEDState(state); + try { + server->announceClipboard(available); + } catch (rdr::Exception& e) { + vlog.error("XserverDesktop::announceClipboard: %s",e.str()); + } } -void XserverDesktop::serverCutText(const char* str) +void XserverDesktop::sendClipboardData(const char* data) { try { - server->serverCutText(str); + server->sendClipboardData(data); } catch (rdr::Exception& e) { - vlog.error("XserverDesktop::serverCutText: %s",e.str()); + vlog.error("XserverDesktop::sendClipboardData: %s",e.str()); } } +void XserverDesktop::bell() +{ + server->bell(); +} + +void XserverDesktop::setLEDState(unsigned int state) +{ + server->setLEDState(state); +} + void XserverDesktop::setDesktopName(const char* name) { try { @@ -436,11 +454,6 @@ void XserverDesktop::pointerEvent(const Point& pos, int buttonMask) vncPointerButtonAction(buttonMask); } -void XserverDesktop::clientCutText(const char* str) -{ - vncClientCutText(str); -} - unsigned int XserverDesktop::setScreenLayout(int fb_width, int fb_height, const rfb::ScreenSet& layout) { @@ -462,6 +475,21 @@ unsigned int XserverDesktop::setScreenLayout(int fb_width, int fb_height, return result; } +void XserverDesktop::handleClipboardRequest() +{ + vncHandleClipboardRequest(); +} + +void XserverDesktop::handleClipboardAnnounce(bool available) +{ + vncHandleClipboardAnnounce(available); +} + +void XserverDesktop::handleClipboardData(const char* data_) +{ + vncHandleClipboardData(data_); +} + void XserverDesktop::grabRegion(const rfb::Region& region) { if (directFbptr) diff --git a/unix/xserver/hw/vnc/XserverDesktop.h b/unix/xserver/hw/vnc/XserverDesktop.h index c6c4eb10..6c670689 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.h +++ b/unix/xserver/hw/vnc/XserverDesktop.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2015 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -59,9 +59,11 @@ public: void unblockUpdates(); void setFramebuffer(int w, int h, void* fbptr, int stride); void refreshScreenLayout(); + void requestClipboard(); + void announceClipboard(bool available); + void sendClipboardData(const char* data); void bell(); void setLEDState(unsigned int state); - void serverCutText(const char* str); void setDesktopName(const char* name); void setCursor(int width, int height, int hotX, int hotY, const unsigned char *rgbaData); @@ -92,9 +94,11 @@ public: const char* userName); virtual void pointerEvent(const rfb::Point& pos, int buttonMask); virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); - virtual void clientCutText(const char* str); 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 10143f6f..6ab306b1 100644 --- a/unix/xserver/hw/vnc/vncExtInit.cc +++ b/unix/xserver/hw/vnc/vncExtInit.cc @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2015 Pierre Ossman for Cendio AB + * Copyright 2011-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -285,10 +285,22 @@ void vncUpdateDesktopName(void) desktop[scr]->setDesktopName(desktopName); } -void vncServerCutText(const char *text) +void vncRequestClipboard(void) { for (int scr = 0; scr < vncGetScreenCount(); scr++) - desktop[scr]->serverCutText(text); + 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 bdcce83a..1fb87c19 100644 --- a/unix/xserver/hw/vnc/vncExtInit.h +++ b/unix/xserver/hw/vnc/vncExtInit.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2015 Pierre Ossman for Cendio AB + * Copyright 2011-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -53,7 +53,9 @@ int vncGetSendPrimary(void); void vncUpdateDesktopName(void); -void vncServerCutText(const char *text); +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 3438ac86..4fac50e2 100644 --- a/unix/xserver/hw/vnc/vncSelection.c +++ b/unix/xserver/hw/vnc/vncSelection.c @@ -47,14 +47,34 @@ static Atom xaTARGETS, xaTIMESTAMP, xaSTRING, xaTEXT, xaUTF8_STRING; static WindowPtr pWindow; static Window wid; -static char* clientCutText; +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); @@ -79,31 +99,99 @@ void vncSelectionInit(void) if (!AddCallback(&SelectionCallback, vncSelectionCallback, 0)) FatalError("Add VNC SelectionCallback failed\n"); + if (!AddCallback(&ClientStateCallback, vncClientStateCallback, 0)) + FatalError("Add VNC ClientStateCallback failed\n"); } -void vncClientCutText(const char* str) +void vncHandleClipboardRequest(void) { - int rc; - - if (clientCutText != NULL) - free(clientCutText); - - clientCutText = strdup(str); - 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; } - if (vncGetSetPrimary()) { - rc = vncOwnSelection(xaPRIMARY); + LOG_DEBUG("Got request for local clipboard, re-probing formats"); + + probing = FALSE; + vncSelectionRequest(activeSelection, xaTARGETS); +} + +void vncHandleClipboardAnnounce(int available) +{ + if (available) { + int rc; + + LOG_DEBUG("Remote clipboard announced, grabbing local ownership"); + + if (vncGetSetPrimary()) { + rc = vncOwnSelection(xaPRIMARY); + if (rc != Success) + LOG_ERROR("Could not set PRIMARY selection"); + } + + rc = vncOwnSelection(xaCLIPBOARD); if (rc != Success) - LOG_ERROR("Could not set PRIMARY selection"); + LOG_ERROR("Could not set CLIPBOARD selection"); + } else { + struct VncDataTarget* next; + + if (pWindow == NULL) + return; + + LOG_DEBUG("Remote clipboard lost, removing local ownership"); + + DeleteWindowFromAnySelections(pWindow); + + /* Abort any pending transfer */ + while (vncDataTargetHead != NULL) { + xEvent event; + + event.u.u.type = SelectionNotify; + event.u.selectionNotify.time = vncDataTargetHead->time; + event.u.selectionNotify.requestor = vncDataTargetHead->requestor; + event.u.selectionNotify.selection = vncDataTargetHead->selection; + event.u.selectionNotify.target = vncDataTargetHead->target; + event.u.selectionNotify.property = None; + WriteEventsToClient(vncDataTargetHead->client, 1, &event); + + next = vncDataTargetHead->next; + free(vncDataTargetHead); + vncDataTargetHead = next; + } } +} - rc = vncOwnSelection(xaCLIPBOARD); - if (rc != Success) - LOG_ERROR("Could not set CLIPBOARD selection"); +void vncHandleClipboardData(const char* data) +{ + struct VncDataTarget* next; + + LOG_DEBUG("Got remote clipboard data, sending to X11 clients"); + + while (vncDataTargetHead != NULL) { + int rc; + xEvent event; + + rc = vncConvertSelection(vncDataTargetHead->client, + vncDataTargetHead->selection, + vncDataTargetHead->target, + vncDataTargetHead->property, + vncDataTargetHead->requestor, + vncDataTargetHead->time, + data); + if (rc != Success) { + event.u.u.type = SelectionNotify; + event.u.selectionNotify.time = vncDataTargetHead->time; + event.u.selectionNotify.requestor = vncDataTargetHead->requestor; + event.u.selectionNotify.selection = vncDataTargetHead->selection; + event.u.selectionNotify.target = vncDataTargetHead->target; + event.u.selectionNotify.property = None; + WriteEventsToClient(vncDataTargetHead->client, 1, &event); + } + + next = vncDataTargetHead->next; + free(vncDataTargetHead); + vncDataTargetHead = next; + } } static int vncCreateSelectionWindow(void) @@ -191,7 +279,8 @@ static int vncOwnSelection(Atom selection) static int vncConvertSelection(ClientPtr client, Atom selection, Atom target, Atom property, - Window requestor, CARD32 time) + Window requestor, CARD32 time, + const char* data) { Selection *pSel; WindowPtr pWin; @@ -201,8 +290,13 @@ static int vncConvertSelection(ClientPtr client, Atom selection, xEvent event; - LOG_DEBUG("Selection request for %s (type %s)", - NameForAtom(selection), NameForAtom(target)); + if (data == NULL) { + LOG_DEBUG("Selection request for %s (type %s)", + NameForAtom(selection), NameForAtom(target)); + } else { + LOG_DEBUG("Sending data for selection request for %s (type %s)", + NameForAtom(selection), NameForAtom(target)); + } rc = dixLookupSelection(&pSel, selection, client, DixGetAttrAccess); if (rc != Success) @@ -239,28 +333,58 @@ static int vncConvertSelection(ClientPtr client, Atom selection, TRUE); if (rc != Success) return rc; - } else if ((target == xaSTRING) || (target == xaTEXT)) { - rc = dixChangeWindowProperty(serverClient, pWin, realProperty, - XA_STRING, 8, PropModeReplace, - strlen(clientCutText), clientCutText, - TRUE); - if (rc != Success) - return rc; - } else if (target == xaUTF8_STRING) { - char* buffer; - - buffer = vncLatin1ToUTF8(clientCutText, (size_t)-1); - if (buffer == NULL) - return BadAlloc; - - rc = dixChangeWindowProperty(serverClient, pWin, realProperty, - xaUTF8_STRING, 8, PropModeReplace, - strlen(buffer), buffer, TRUE); - vncStrFree(buffer); - if (rc != Success) - return rc; } else { - return BadMatch; + if (data == NULL) { + struct VncDataTarget* vdt; + + if ((target != xaSTRING) && (target != xaTEXT) && + (target != xaUTF8_STRING)) + return BadMatch; + + vdt = calloc(1, sizeof(struct VncDataTarget)); + if (vdt == NULL) + return BadAlloc; + + vdt->client = client; + vdt->selection = selection; + vdt->target = target; + vdt->property = property; + vdt->requestor = requestor; + vdt->time = time; + + vdt->next = vncDataTargetHead; + vncDataTargetHead = vdt; + + LOG_DEBUG("Requesting clipboard data from client"); + + vncRequestClipboard(); + + return Success; + } else { + if ((target == xaSTRING) || (target == xaTEXT)) { + rc = dixChangeWindowProperty(serverClient, pWin, realProperty, + XA_STRING, 8, PropModeReplace, + strlen(data), (char*)data, + TRUE); + if (rc != Success) + return rc; + } else if (target == xaUTF8_STRING) { + char* buffer; + + buffer = vncLatin1ToUTF8(data, (size_t)-1); + if (buffer == NULL) + return BadAlloc; + + rc = dixChangeWindowProperty(serverClient, pWin, realProperty, + xaUTF8_STRING, 8, PropModeReplace, + strlen(buffer), buffer, TRUE); + vncStrFree(buffer); + if (rc != Success) + return rc; + } else { + return BadMatch; + } + } } event.u.u.type = SelectionNotify; @@ -299,7 +423,7 @@ static int vncProcConvertSelection(ClientPtr client) pSel->window == wid) { rc = vncConvertSelection(client, stuff->selection, stuff->target, stuff->property, - stuff->requestor, stuff->time); + stuff->requestor, stuff->time, NULL); if (rc != Success) { xEvent event; @@ -383,10 +507,19 @@ static void vncHandleSelection(Atom selection, Atom target, if (prop->type != XA_ATOM) return; - if (vncHasAtom(xaSTRING, (const Atom*)prop->data, prop->size)) - vncSelectionRequest(selection, xaSTRING); - else if (vncHasAtom(xaUTF8_STRING, (const Atom*)prop->data, prop->size)) - vncSelectionRequest(selection, xaUTF8_STRING); + if (probing) { + if (vncHasAtom(xaSTRING, (const Atom*)prop->data, prop->size) || + vncHasAtom(xaUTF8_STRING, (const Atom*)prop->data, prop->size)) { + LOG_DEBUG("Compatible format found, notifying clients"); + activeSelection = selection; + vncAnnounceClipboard(TRUE); + } + } else { + if (vncHasAtom(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); + } } else if (target == xaSTRING) { char* filtered; @@ -399,7 +532,10 @@ static void vncHandleSelection(Atom selection, Atom target, if (filtered == NULL) return; - vncServerCutText(filtered); + LOG_DEBUG("Sending clipboard to clients (%d bytes)", + (int)strlen(filtered)); + + vncSendClipboardData(filtered); vncStrFree(filtered); } else if (target == xaUTF8_STRING) { @@ -420,7 +556,10 @@ static void vncHandleSelection(Atom selection, Atom target, if (filtered == NULL) return; - vncServerCutText(filtered); + LOG_DEBUG("Sending clipboard to clients (%d bytes)", + (int)strlen(filtered)); + + vncSendClipboardData(filtered); vncStrFree(filtered); } @@ -454,6 +593,12 @@ static void vncSelectionCallback(CallbackListPtr *callbacks, { SelectionInfoRec *info = (SelectionInfoRec *) args; + if (info->selection->selection == activeSelection) { + LOG_DEBUG("Local clipboard lost, notifying clients"); + activeSelection = None; + vncAnnounceClipboard(FALSE); + } + if (info->kind != SelectionSetOwner) return; if (info->client == serverClient) @@ -470,5 +615,25 @@ static void vncSelectionCallback(CallbackListPtr *callbacks, !vncGetSendPrimary()) return; + LOG_DEBUG("Got clipboard notification, probing for formats"); + + probing = TRUE; vncSelectionRequest(info->selection->selection, xaTARGETS); } + +static void vncClientStateCallback(CallbackListPtr * l, + void * d, void * p) +{ + ClientPtr client = ((NewClientInfoRec*)p)->client; + if (client->clientState == ClientStateGone) { + struct VncDataTarget** nextPtr = &vncDataTargetHead; + for (struct VncDataTarget* cur = vncDataTargetHead; cur; cur = *nextPtr) { + if (cur->client == client) { + *nextPtr = cur->next; + free(cur); + continue; + } + nextPtr = &cur->next; + } + } +} diff --git a/unix/xserver/hw/vnc/vncSelection.h b/unix/xserver/hw/vnc/vncSelection.h index e11d4a49..ea52bf23 100644 --- a/unix/xserver/hw/vnc/vncSelection.h +++ b/unix/xserver/hw/vnc/vncSelection.h @@ -24,7 +24,9 @@ extern "C" { void vncSelectionInit(void); -void vncClientCutText(const char* str); +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 2cc1fe4c..6ba3276b 100644 --- a/vncviewer/CConn.cxx +++ b/vncviewer/CConn.cxx @@ -377,11 +377,6 @@ void CConn::bell() fl_beep(); } -void CConn::serverCutText(const char* str) -{ - desktop->serverCutText(str); -} - void CConn::dataRect(const Rect& r, int encoding) { sock->inStream().startTiming(); @@ -422,6 +417,21 @@ void CConn::setLEDState(unsigned int state) desktop->setLEDState(state); } +void CConn::handleClipboardRequest() +{ + desktop->handleClipboardRequest(); +} + +void CConn::handleClipboardAnnounce(bool available) +{ + desktop->handleClipboardAnnounce(available); +} + +void CConn::handleClipboardData(const char* data) +{ + desktop->handleClipboardData(data); +} + ////////////////////// Internal methods ////////////////////// diff --git a/vncviewer/CConn.h b/vncviewer/CConn.h index 38e09c25..4d935c96 100644 --- a/vncviewer/CConn.h +++ b/vncviewer/CConn.h @@ -61,8 +61,6 @@ public: void bell(); - void serverCutText(const char* str); - void framebufferUpdateStart(); void framebufferUpdateEnd(); void dataRect(const rfb::Rect& r, int encoding); @@ -74,6 +72,10 @@ public: void setLEDState(unsigned int state); + virtual void handleClipboardRequest(); + virtual void handleClipboardAnnounce(bool available); + virtual void handleClipboardData(const char* data); + private: void resizeFramebuffer(); diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 4429e775..4860b928 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -276,12 +276,6 @@ void DesktopWindow::resizeFramebuffer(int new_w, int new_h) } -void DesktopWindow::serverCutText(const char* str) -{ - viewport->serverCutText(str); -} - - void DesktopWindow::setCursor(int width, int height, const rfb::Point& hotspot, const rdr::U8* data) @@ -420,6 +414,22 @@ void DesktopWindow::setLEDState(unsigned int state) } +void DesktopWindow::handleClipboardRequest() +{ + viewport->handleClipboardRequest(); +} + +void DesktopWindow::handleClipboardAnnounce(bool available) +{ + viewport->handleClipboardAnnounce(available); +} + +void DesktopWindow::handleClipboardData(const char* data) +{ + viewport->handleClipboardData(data); +} + + void DesktopWindow::resize(int x, int y, int w, int h) { bool resizing; diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index fe938d9b..6b03325c 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -62,9 +62,6 @@ public: // Resize the current framebuffer, but retain the contents void resizeFramebuffer(int new_w, int new_h); - // Incoming clipboard from server - void serverCutText(const char* str); - // New image for the locally rendered cursor void setCursor(int width, int height, const rfb::Point& hotspot, const rdr::U8* data); @@ -72,6 +69,11 @@ public: // Change client LED state void setLEDState(unsigned int state); + // Clipboard events + void handleClipboardRequest(); + void handleClipboardAnnounce(bool available); + void handleClipboardData(const char* data); + // Fl_Window callback methods void draw(); void resize(int x, int y, int w, int h); diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 151ecb47..713d3648 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -119,7 +119,7 @@ Viewport::Viewport(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_) altGrArmed(false), #endif firstLEDState(true), - pendingServerCutText(NULL), pendingClientCutText(NULL), + pendingServerClipboard(false), pendingClientClipboard(false), menuCtrlKey(false), menuAltKey(false), cursor(NULL) { #if !defined(WIN32) && !defined(__APPLE__) @@ -208,8 +208,6 @@ Viewport::~Viewport() delete cursor; } - clearPendingClipboard(); - // FLTK automatically deletes all child widgets, so we shouldn't touch // them ourselves here } @@ -232,37 +230,6 @@ void Viewport::updateWindow() damage(FL_DAMAGE_USER1, r.tl.x + x(), r.tl.y + y(), r.width(), r.height()); } -void Viewport::serverCutText(const char* str) -{ - char *buffer; - size_t len; - - clearPendingClipboard(); - - if (!acceptClipboard) - return; - - buffer = latin1ToUTF8(str); - len = strlen(buffer); - - vlog.debug("Got clipboard data (%d bytes)", (int)len); - - 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, len, 0); -#endif - Fl::copy(buffer, len, 1); - - strFree(buffer); -} - static const char * dotcursor_xpm[] = { "5 5 2 1", ". c #000000", @@ -311,6 +278,59 @@ void Viewport::setCursor(int width, int height, const Point& hotspot, window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y); } +void Viewport::handleClipboardRequest() +{ + Fl::paste(*this, clipboardSource); +} + +void Viewport::handleClipboardAnnounce(bool available) +{ + if (!acceptClipboard) + return; + + if (available) + vlog.debug("Got notification of new clipboard on server"); + else + vlog.debug("Clipboard is no longer available on server"); + + if (!available) { + pendingServerClipboard = false; + return; + } + + pendingClientClipboard = false; + + if (!hasFocus()) { + pendingServerClipboard = true; + return; + } + + cc->requestClipboard(); +} + +void Viewport::handleClipboardData(const char* data) +{ + char* buffer; + size_t len; + + if (!hasFocus()) + return; + + buffer = latin1ToUTF8(data); + len = strlen(buffer); + + 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(buffer, len, 0); +#endif + Fl::copy(buffer, len, 1); + + strFree(buffer); +} void Viewport::setLEDState(unsigned int state) { @@ -547,21 +567,14 @@ int Viewport::handle(int event) switch (event) { case FL_PASTE: - clearPendingClipboard(); - buffer = utf8ToLatin1(Fl::event_text(), Fl::event_length()); filtered = convertLF(buffer); strFree(buffer); - if (!hasFocus()) { - pendingClientCutText = filtered; - return 1; - } - vlog.debug("Sending clipboard data (%d bytes)", (int)strlen(filtered)); try { - cc->writer()->writeClientCutText(filtered); + cc->sendClipboardData(filtered); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); exit_vncviewer(e.str()); @@ -725,41 +738,47 @@ void Viewport::handleClipboardChange(int source, void *data) return; #endif - Fl::paste(*self, source); -} + self->clipboardSource = source; + self->pendingServerClipboard = false; -void Viewport::clearPendingClipboard() -{ - strFree(pendingServerCutText); - pendingServerCutText = NULL; - strFree(pendingClientCutText); - pendingClientCutText = NULL; + if (!self->hasFocus()) { + self->pendingClientClipboard = true; + // Clear any older client clipboard from the server + self->cc->announceClipboard(false); + return; + } + + try { + self->cc->announceClipboard(true); + } catch (rdr::Exception& e) { + vlog.error("%s", e.str()); + exit_vncviewer(e.str()); + } } void Viewport::flushPendingClipboard() { - if (pendingServerCutText) { - size_t len = strlen(pendingServerCutText); -#if !defined(WIN32) && !defined(__APPLE__) - if (setPrimary) - Fl::copy(pendingServerCutText, len, 0); -#endif - Fl::copy(pendingServerCutText, len, 1); + if (pendingServerClipboard) { + try { + cc->requestClipboard(); + } catch (rdr::Exception& e) { + vlog.error("%s", e.str()); + exit_vncviewer(e.str()); + } } - if (pendingClientCutText) { - size_t len = strlen(pendingClientCutText); - vlog.debug("Sending pending clipboard data (%d bytes)", (int)len); + if (pendingClientClipboard) { try { - cc->writer()->writeClientCutText(pendingClientCutText); + 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 8b9b469b..1fb93c66 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -45,9 +45,6 @@ public: // Flush updates to screen void updateWindow(); - // Incoming clipboard from server - void serverCutText(const char* str); - // New image for the locally rendered cursor void setCursor(int width, int height, const rfb::Point& hotspot, const rdr::U8* data); @@ -57,6 +54,11 @@ public: void draw(Surface* dst); + // Clipboard events + void handleClipboardRequest(); + void handleClipboardAnnounce(bool available); + void handleClipboardData(const char* data); + // Fl_Widget callback methods void draw(); @@ -72,7 +74,6 @@ private: static void handleClipboardChange(int source, void *data); - void clearPendingClipboard(); void flushPendingClipboard(); void handlePointerEvent(const rfb::Point& pos, int buttonMask); @@ -114,8 +115,10 @@ private: bool firstLEDState; - char* pendingServerCutText; - char* pendingClientCutText; + bool pendingServerClipboard; + bool pendingClientClipboard; + + int clipboardSource; rdr::U32 menuKeySym; int menuKeyCode, menuKeyFLTK; diff --git a/win/rfb_win32/Clipboard.cxx b/win/rfb_win32/Clipboard.cxx index 49526956..306cfbad 100644 --- a/win/rfb_win32/Clipboard.cxx +++ b/win/rfb_win32/Clipboard.cxx @@ -103,32 +103,10 @@ Clipboard::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) { } else { vlog.debug("local clipboard changed by %p", owner); - // Open the clipboard - if (OpenClipboard(getHandle())) { - // Get the clipboard data - HGLOBAL cliphandle = GetClipboardData(CF_TEXT); - if (cliphandle) { - char* clipdata = (char*) GlobalLock(cliphandle); - - // Notify clients - if (notifier) { - if (!clipdata) { - notifier->notifyClipboardChanged(0); - } else { - CharArray unix_text(convertLF(clipdata, strlen(clipdata))); - removeNonISOLatin1Chars(unix_text.buf); - notifier->notifyClipboardChanged(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_TEXT)); } } if (next_window) @@ -139,6 +117,40 @@ Clipboard::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) { return MsgWindow::processMessage(msg, wParam, lParam); }; +char* +Clipboard::getClipText() { + HGLOBAL cliphandle; + char* clipdata; + char* filtered; + + // Open the clipboard + if (!OpenClipboard(getHandle())) + return NULL; + + // Get the clipboard data + cliphandle = GetClipboardData(CF_TEXT); + if (!cliphandle) { + CloseClipboard(); + return NULL; + } + + clipdata = (char*) GlobalLock(cliphandle); + if (!clipdata) { + CloseClipboard(); + return NULL; + } + + // Filter out anything unwanted + filtered = convertLF(clipdata, strlen(clipdata)); + removeNonISOLatin1Chars(filtered); + + // Release the buffer and close the clipboard + GlobalUnlock(cliphandle); + CloseClipboard(); + + return filtered; +} + void Clipboard::setClipText(const char* text) { HANDLE clip_handle = 0; diff --git a/win/rfb_win32/Clipboard.h b/win/rfb_win32/Clipboard.h index c69e981f..1dead82e 100644 --- a/win/rfb_win32/Clipboard.h +++ b/win/rfb_win32/Clipboard.h @@ -39,7 +39,7 @@ namespace rfb { // -=- Abstract base class for callback recipients class Notifier { public: - virtual void notifyClipboardChanged(const char* text) = 0; + virtual void notifyClipboardChanged(bool available) = 0; virtual ~Notifier() {}; }; @@ -49,6 +49,9 @@ namespace rfb { // - Set the notifier to use void setNotifier(Notifier* cbn) {notifier = cbn;} + // - Get the clipboard contents + char* getClipText(); + // - Set the clipboard contents void setClipText(const char* text); diff --git a/win/rfb_win32/SDisplay.cxx b/win/rfb_win32/SDisplay.cxx index 2c91e3f4..be33ff15 100644 --- a/win/rfb_win32/SDisplay.cxx +++ b/win/rfb_win32/SDisplay.cxx @@ -292,6 +292,22 @@ void SDisplay::restartCore() { } +void SDisplay::handleClipboardRequest() { + CharArray data(clipboard->getClipText()); + server->sendClipboardData(data.buf); +} + +void SDisplay::handleClipboardAnnounce(bool available) { + // FIXME: Wait for an application to actually request it + if (available) + server->requestClipboard(); +} + +void SDisplay::handleClipboardData(const char* data) { + clipboard->setClipText(data); +} + + void SDisplay::pointerEvent(const Point& pos, int buttonmask) { if (pb->getRect().contains(pos)) { Point screenPos = pos.translate(screenRect.tl); @@ -329,16 +345,12 @@ bool SDisplay::checkLedState() { return false; } -void SDisplay::clientCutText(const char* text) { - clipboard->setClipText(text); -} - void -SDisplay::notifyClipboardChanged(const char* text) { +SDisplay::notifyClipboardChanged(bool available) { vlog.debug("clipboard text changed"); if (server) - server->serverCutText(text); + server->announceClipboard(available); } diff --git a/win/rfb_win32/SDisplay.h b/win/rfb_win32/SDisplay.h index 1773b785..8e38edb3 100644 --- a/win/rfb_win32/SDisplay.h +++ b/win/rfb_win32/SDisplay.h @@ -76,13 +76,15 @@ namespace rfb { virtual void terminate(); virtual void queryConnection(network::Socket* sock, const char* userName); + virtual void handleClipboardRequest(); + virtual void handleClipboardAnnounce(bool available); + virtual void handleClipboardData(const char* data); virtual void pointerEvent(const Point& pos, int buttonmask); virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); - virtual void clientCutText(const char* str); - // -=- Clipboard + // -=- Clipboard events - virtual void notifyClipboardChanged(const char* text); + virtual void notifyClipboardChanged(bool available); // -=- Display events -- 2.39.5