diff options
Diffstat (limited to 'unix/x0vncserver/XSelection.cxx')
-rw-r--r-- | unix/x0vncserver/XSelection.cxx | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/unix/x0vncserver/XSelection.cxx b/unix/x0vncserver/XSelection.cxx new file mode 100644 index 00000000..72dd537f --- /dev/null +++ b/unix/x0vncserver/XSelection.cxx @@ -0,0 +1,195 @@ +/* Copyright (C) 2024 Gaurav Ujjwal. All Rights Reserved. + * + * 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. + */ + +#include <X11/Xatom.h> +#include <rfb/Configuration.h> +#include <rfb/LogWriter.h> +#include <rfb/util.h> +#include <x0vncserver/XSelection.h> + +rfb::BoolParameter setPrimary("SetPrimary", + "Set the PRIMARY as well as the CLIPBOARD selection", + true); +rfb::BoolParameter sendPrimary("SendPrimary", + "Send the PRIMARY as well as the CLIPBOARD selection", + true); + +static rfb::LogWriter vlog("XSelection"); + +XSelection::XSelection(Display* dpy_, XSelectionHandler* handler_) + : TXWindow(dpy_, 1, 1, nullptr), handler(handler_), announcedSelection(None) +{ + probeProperty = XInternAtom(dpy, "TigerVNC_ProbeProperty", False); + transferProperty = XInternAtom(dpy, "TigerVNC_TransferProperty", False); + timestampProperty = XInternAtom(dpy, "TigerVNC_TimestampProperty", False); + setName("TigerVNC Clipboard (x0vncserver)"); + addEventMask(PropertyChangeMask); // Required for PropertyNotify events +} + +static Bool PropertyEventMatcher(Display* /* dpy */, XEvent* ev, XPointer prop) +{ + if (ev->type == PropertyNotify && ev->xproperty.atom == *((Atom*)prop)) + return True; + else + return False; +} + +Time XSelection::getXServerTime() +{ + XEvent ev; + uint8_t data = 0; + + // Trigger a PropertyNotify event to extract server time + XChangeProperty(dpy, win(), timestampProperty, XA_STRING, 8, PropModeReplace, + &data, sizeof(data)); + XIfEvent(dpy, &ev, &PropertyEventMatcher, (XPointer)×tampProperty); + return ev.xproperty.time; +} + +// Takes ownership of selections, backed by given data. +void XSelection::handleClientClipboardData(const char* data) +{ + vlog.debug("Received client clipboard data, taking selection ownership"); + + Time time = getXServerTime(); + ownSelection(xaCLIPBOARD, time); + if (!selectionOwner(xaCLIPBOARD)) + vlog.error("Unable to own CLIPBOARD selection"); + + if (setPrimary) { + ownSelection(XA_PRIMARY, time); + if (!selectionOwner(XA_PRIMARY)) + vlog.error("Unable to own PRIMARY selection"); + } + + if (selectionOwner(xaCLIPBOARD) || selectionOwner(XA_PRIMARY)) + clientData = data; +} + +// We own the selection and another X app has asked for data +bool XSelection::selectionRequest(Window requestor, Atom selection, Atom target, + Atom property) +{ + if (clientData.empty() || requestor == win() || !selectionOwner(selection)) + return false; + + if (target == XA_STRING) { + std::string latin1 = rfb::utf8ToLatin1(clientData.data(), clientData.length()); + XChangeProperty(dpy, requestor, property, XA_STRING, 8, PropModeReplace, + (unsigned char*)latin1.data(), latin1.length()); + return true; + } + + if (target == xaUTF8_STRING) { + XChangeProperty(dpy, requestor, property, xaUTF8_STRING, 8, PropModeReplace, + (unsigned char*)clientData.data(), clientData.length()); + return true; + } + + return false; +} + +// Selection-owner change implies a change in selection data. +void XSelection::handleSelectionOwnerChange(Window owner, Atom selection, Time time) +{ + if (selection != XA_PRIMARY && selection != xaCLIPBOARD) + return; + if (selection == XA_PRIMARY && !sendPrimary) + return; + + if (selection == announcedSelection) + announceSelection(None); + + if (owner == None || owner == win()) + return; + + if (!selectionOwner(XA_PRIMARY) && !selectionOwner(xaCLIPBOARD)) + clientData = ""; + + XConvertSelection(dpy, selection, xaTARGETS, probeProperty, win(), time); +} + +void XSelection::announceSelection(Atom selection) +{ + announcedSelection = selection; + handler->handleXSelectionAnnounce(selection != None); +} + +void XSelection::requestSelectionData() +{ + if (announcedSelection != None) + XConvertSelection(dpy, announcedSelection, xaTARGETS, transferProperty, win(), + CurrentTime); +} + +// Some information about selection is received from current owner +void XSelection::selectionNotify(XSelectionEvent* ev, Atom type, int format, + int nitems, void* data) +{ + if (!ev || !data || type == None) + return; + + if (ev->target == xaTARGETS) { + if (format != 32 || type != XA_ATOM) + return; + + Atom* targets = (Atom*)data; + bool utf8Supported = false; + bool stringSupported = false; + + for (int i = 0; i < nitems; i++) { + if (targets[i] == xaUTF8_STRING) + utf8Supported = true; + else if (targets[i] == XA_STRING) + stringSupported = true; + } + + if (ev->property == probeProperty) { + // Only probing for now, will issue real request when client asks for data + if (stringSupported || utf8Supported) + announceSelection(ev->selection); + return; + } + + // Prefer UTF-8 if available + if (utf8Supported) + XConvertSelection(dpy, ev->selection, xaUTF8_STRING, transferProperty, win(), + ev->time); + else if (stringSupported) + XConvertSelection(dpy, ev->selection, XA_STRING, transferProperty, win(), + ev->time); + } else if (ev->target == xaUTF8_STRING || ev->target == XA_STRING) { + if (type == xaINCR) { + // Incremental transfer is not supported + vlog.debug("Selected data is too big!"); + return; + } + + if (format != 8) + return; + + if (type == xaUTF8_STRING) { + std::string result = rfb::convertLF((char*)data, nitems); + handler->handleXSelectionData(result.c_str()); + } else if (type == XA_STRING) { + std::string result = rfb::convertLF((char*)data, nitems); + result = rfb::latin1ToUTF8(result.data(), result.length()); + handler->handleXSelectionData(result.c_str()); + } + } +}
\ No newline at end of file |