From: Pierre Ossman Date: Fri, 8 Jul 2016 12:11:42 +0000 (+0200) Subject: Merge branch 'clipboard' X-Git-Tag: v1.7.90~102 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=1db99eb9ec1868620d1fb49d51f3a2caa8386f77;p=tigervnc.git Merge branch 'clipboard' --- 1db99eb9ec1868620d1fb49d51f3a2caa8386f77 diff --cc unix/xserver/hw/vnc/vncExtInit.cc index 1d374938,fda2562e..0ad34a27 --- a/unix/xserver/hw/vnc/vncExtInit.cc +++ b/unix/xserver/hw/vnc/vncExtInit.cc @@@ -81,9 -70,9 +82,14 @@@ rfb::StringParameter interface("interfa rfb::BoolParameter avoidShiftNumLock("AvoidShiftNumLock", "Avoid fake Shift presses for keys affected by NumLock.", true); +rfb::StringParameter allowOverride("AllowOverride", + "Comma separated list of parameters that can be modified using VNC extension.", + "desktop,AcceptPointerEvents,SendCutText,AcceptCutText"); ++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 PixelFormat vncGetPixelFormat(int scrIdx) { @@@ -274,6 -248,11 +282,16 @@@ int vncGetAvoidShiftNumLock(void return (bool)avoidShiftNumLock; } ++int vncGetSetPrimary(void) ++{ ++ return (bool)setPrimary; ++} ++ + int vncGetSendPrimary(void) + { + return (bool)sendPrimary; + } + void vncUpdateDesktopName(void) { for (int scr = 0; scr < vncGetScreenCount(); scr++) { diff --cc unix/xserver/hw/vnc/vncExtInit.h index be6487c8,4b8568e1..a0914ad7 --- a/unix/xserver/hw/vnc/vncExtInit.h +++ b/unix/xserver/hw/vnc/vncExtInit.h @@@ -59,6 -57,8 +57,9 @@@ void vncCallWriteWakeupHandlers(fd_set int vncGetAvoidShiftNumLock(void); ++int vncGetSetPrimary(void); + int vncGetSendPrimary(void); + void vncUpdateDesktopName(void); void vncServerCutText(const char *text, size_t len); diff --cc unix/xserver/hw/vnc/vncSelection.c index 00000000,cea8ecf2..e50548a4 mode 000000,100644..100644 --- a/unix/xserver/hw/vnc/vncSelection.c +++ b/unix/xserver/hw/vnc/vncSelection.c @@@ -1,0 -1,518 +1,521 @@@ + /* Copyright 2016 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. + */ + + #ifdef HAVE_DIX_CONFIG_H + #include + #endif + + #include + + #include "propertyst.h" + #include "scrnintstr.h" + #include "selection.h" + #include "windowstr.h" + #include "xace.h" + + #include "xorg-version.h" + + #include "vncExtInit.h" + #include "vncSelection.h" + #include "RFBGlue.h" + + #define LOG_NAME "Selection" + + #define LOG_ERROR(...) vncLogError(LOG_NAME, __VA_ARGS__) + #define LOG_STATUS(...) vncLogStatus(LOG_NAME, __VA_ARGS__) + #define LOG_INFO(...) vncLogInfo(LOG_NAME, __VA_ARGS__) + #define LOG_DEBUG(...) vncLogDebug(LOG_NAME, __VA_ARGS__) + + static Atom xaPRIMARY, xaCLIPBOARD; + static Atom xaTARGETS, xaTIMESTAMP, xaSTRING, xaTEXT, xaUTF8_STRING; + + static WindowPtr pWindow; + static Window wid; + + static char* clientCutText; + static int clientCutTextLen; + + static int vncCreateSelectionWindow(void); + static int vncOwnSelection(Atom selection); + static int vncProcConvertSelection(ClientPtr client); + static int vncProcSendEvent(ClientPtr client); + static void vncSelectionCallback(CallbackListPtr *callbacks, + void * data, void * args); + + static int (*origProcConvertSelection)(ClientPtr); + static int (*origProcSendEvent)(ClientPtr); + + void vncSelectionInit(void) + { + xaPRIMARY = MakeAtom("PRIMARY", 7, TRUE); + xaCLIPBOARD = MakeAtom("CLIPBOARD", 9, TRUE); + + xaTARGETS = MakeAtom("TARGETS", 7, TRUE); + xaTIMESTAMP = MakeAtom("TIMESTAMP", 9, TRUE); + xaSTRING = MakeAtom("STRING", 6, TRUE); + xaTEXT = MakeAtom("TEXT", 4, TRUE); + xaUTF8_STRING = MakeAtom("UTF8_STRING", 11, TRUE); + + /* There are no hooks for when these are internal windows, so + * override the relevant handlers. */ + origProcConvertSelection = ProcVector[X_ConvertSelection]; + ProcVector[X_ConvertSelection] = vncProcConvertSelection; + origProcSendEvent = ProcVector[X_SendEvent]; + ProcVector[X_SendEvent] = vncProcSendEvent; + + if (!AddCallback(&SelectionCallback, vncSelectionCallback, 0)) + FatalError("Add VNC SelectionCallback failed\n"); + } + + void vncClientCutText(const char* str, int len) + { + int rc; + + if (clientCutText != NULL) + free(clientCutText); + + clientCutText = malloc(len); + if (clientCutText == NULL) { + LOG_ERROR("Could not allocate clipboard buffer"); + DeleteWindowFromAnySelections(pWindow); + return; + } + + memcpy(clientCutText, str, len); + clientCutTextLen = len; + - rc = vncOwnSelection(xaPRIMARY); - if (rc != Success) - LOG_ERROR("Could not set PRIMARY selection"); ++ if (vncGetSetPrimary()) { ++ rc = vncOwnSelection(xaPRIMARY); ++ if (rc != Success) ++ LOG_ERROR("Could not set PRIMARY selection"); ++ } ++ + vncOwnSelection(xaCLIPBOARD); + if (rc != Success) + LOG_ERROR("Could not set CLIPBOARD selection"); + } + + static int vncCreateSelectionWindow(void) + { + ScreenPtr pScreen; + int result; + + if (pWindow != NULL) + return Success; + + pScreen = screenInfo.screens[0]; + + wid = FakeClientID(0); + pWindow = CreateWindow(wid, pScreen->root, + 0, 0, 100, 100, 0, InputOnly, + 0, NULL, 0, serverClient, + CopyFromParent, &result); + if (!pWindow) + return result; + + if (!AddResource(pWindow->drawable.id, RT_WINDOW, pWindow)) + return BadAlloc; + + LOG_DEBUG("Created selection window"); + + return Success; + } + + static int vncOwnSelection(Atom selection) + { + Selection *pSel; + int rc; + + SelectionInfoRec info; + + rc = vncCreateSelectionWindow(); + if (rc != Success) + return rc; + + rc = dixLookupSelection(&pSel, selection, serverClient, DixSetAttrAccess); + if (rc == Success) { + if (pSel->client && (pSel->client != serverClient)) { + xEvent event = { + .u.selectionClear.time = currentTime.milliseconds, + .u.selectionClear.window = pSel->window, + .u.selectionClear.atom = pSel->selection + }; + event.u.u.type = SelectionClear; + WriteEventsToClient(pSel->client, 1, &event); + } + } else if (rc == BadMatch) { + pSel = dixAllocateObjectWithPrivates(Selection, PRIVATE_SELECTION); + if (!pSel) + return BadAlloc; + + pSel->selection = selection; + + rc = XaceHookSelectionAccess(serverClient, &pSel, + DixCreateAccess | DixSetAttrAccess); + if (rc != Success) { + free(pSel); + return rc; + } + + pSel->next = CurrentSelections; + CurrentSelections = pSel; + } + else + return rc; + + pSel->lastTimeChanged = currentTime; + pSel->window = wid; + pSel->pWin = pWindow; + pSel->client = serverClient; + + LOG_DEBUG("Grabbed %s selection", NameForAtom(selection)); + + info.selection = pSel; + info.client = serverClient; + info.kind = SelectionSetOwner; + CallCallbacks(&SelectionCallback, &info); + + return Success; + } + + static int vncConvertSelection(ClientPtr client, Atom selection, + Atom target, Atom property, + Window requestor, TimeStamp time) + { + Selection *pSel; + WindowPtr pWin; + int rc; + + Atom realProperty; + + xEvent event; + + LOG_DEBUG("Selection request for %s (type %s)", + NameForAtom(selection), NameForAtom(target)); + + rc = dixLookupSelection(&pSel, selection, client, DixGetAttrAccess); + if (rc != Success) + return rc; + + if (CompareTimeStamps(time, pSel->lastTimeChanged) != LATER) + return BadMatch; + + rc = dixLookupWindow(&pWin, requestor, client, DixSetAttrAccess); + if (rc != Success) + return rc; + + if (property != None) + realProperty = property; + else + realProperty = target; + + /* FIXME: MULTIPLE target */ + + if (target == xaTARGETS) { + Atom targets[] = { xaTARGETS, xaTIMESTAMP, + xaSTRING, xaTEXT, xaUTF8_STRING }; + + rc = ChangeWindowProperty(pWin, realProperty, XA_ATOM, 32, + PropModeReplace, + sizeof(targets)/sizeof(targets[0]), + targets, TRUE); + if (rc != Success) + return rc; + } else if (target == xaTIMESTAMP) { + rc = ChangeWindowProperty(pWin, realProperty, XA_INTEGER, 32, + PropModeReplace, 1, + &pSel->lastTimeChanged.milliseconds, + TRUE); + if (rc != Success) + return rc; + } else if ((target == xaSTRING) || (target == xaTEXT)) { + rc = ChangeWindowProperty(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; + + const unsigned char* in; + size_t in_len; + + buffer = malloc(clientCutTextLen*2); + if (buffer == 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--; + } else { + *out++ = *in++; + len++; + in_len--; + } + } + + rc = ChangeWindowProperty(pWin, realProperty, xaUTF8_STRING, 8, + PropModeReplace, len, buffer, TRUE); + free(buffer); + if (rc != Success) + return rc; + } else { + return BadMatch; + } + + event.u.u.type = SelectionNotify; + event.u.selectionNotify.time = time.milliseconds; + event.u.selectionNotify.requestor = requestor; + event.u.selectionNotify.selection = selection; + event.u.selectionNotify.target = target; + event.u.selectionNotify.property = property; + WriteEventsToClient(client, 1, &event); + return Success; + } + + static int vncProcConvertSelection(ClientPtr client) + { + Bool paramsOkay; + WindowPtr pWin; + Selection *pSel; + int rc; + + REQUEST(xConvertSelectionReq); + REQUEST_SIZE_MATCH(xConvertSelectionReq); + + rc = dixLookupWindow(&pWin, stuff->requestor, client, DixSetAttrAccess); + if (rc != Success) + return rc; + + paramsOkay = ValidAtom(stuff->selection) && ValidAtom(stuff->target); + paramsOkay &= (stuff->property == None) || ValidAtom(stuff->property); + if (!paramsOkay) { + client->errorValue = stuff->property; + return BadAtom; + } + + rc = dixLookupSelection(&pSel, stuff->selection, client, DixReadAccess); + if (rc == Success && pSel->client == serverClient && + pSel->window == wid) { + TimeStamp time; + time = ClientTimeToServerTime(stuff->time); + rc = vncConvertSelection(client, stuff->selection, + stuff->target, stuff->property, + stuff->requestor, time); + if (rc != Success) { + xEvent event; + + memset(&event, 0, sizeof(xEvent)); + event.u.u.type = SelectionNotify; + event.u.selectionNotify.time = stuff->time; + event.u.selectionNotify.requestor = stuff->requestor; + event.u.selectionNotify.selection = stuff->selection; + event.u.selectionNotify.target = stuff->target; + event.u.selectionNotify.property = None; + WriteEventsToClient(client, 1, &event); + } + + return Success; + } + + return origProcConvertSelection(client); + } + + static void vncSelectionRequest(Atom selection, Atom target) + { + Selection *pSel; + xEvent event; + int rc; + + rc = vncCreateSelectionWindow(); + if (rc != Success) + return; + + LOG_DEBUG("Requesting %s for %s selection", + NameForAtom(target), NameForAtom(selection)); + + rc = dixLookupSelection(&pSel, selection, serverClient, DixGetAttrAccess); + if (rc != Success) + return; + + event.u.u.type = SelectionRequest; + event.u.selectionRequest.owner = pSel->window; + event.u.selectionRequest.time = currentTime.milliseconds; + event.u.selectionRequest.requestor = wid; + event.u.selectionRequest.selection = selection; + event.u.selectionRequest.target = target; + event.u.selectionRequest.property = target; + WriteEventsToClient(pSel->client, 1, &event); + } + + static Bool vncHasAtom(Atom atom, const Atom list[], size_t size) + { + size_t i; + + for (i = 0;i < size;i++) { + if (list[i] == atom) + return TRUE; + } + + return FALSE; + } + + static void vncHandleSelection(Atom selection, Atom target, + Atom property, Atom requestor, + TimeStamp time) + { + PropertyPtr prop; + int rc; + + rc = dixLookupProperty(&prop, pWindow, property, + serverClient, DixReadAccess); + if (rc != Success) + return; + + LOG_DEBUG("Selection notification for %s (target %s, property %s, type %s)", + NameForAtom(selection), NameForAtom(target), + NameForAtom(property), NameForAtom(prop->type)); + + if (target != property) + return; + + if (target == xaTARGETS) { + if (prop->format != 32) + return; + 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); + } else if (target == xaSTRING) { + 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; + + const unsigned char* in; + size_t in_len; + + if (prop->format != 8) + return; + if (prop->type != xaUTF8_STRING) + return; + + buffer = malloc(prop->size); + if (buffer == 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)); + } + } + + vncServerCutText((const char*)buffer, len); + + free(buffer); + } + } + + #define SEND_EVENT_BIT 0x80 + + static int vncProcSendEvent(ClientPtr client) + { + REQUEST(xSendEventReq); + REQUEST_SIZE_MATCH(xSendEventReq); + + stuff->event.u.u.type &= ~(SEND_EVENT_BIT); + + if (stuff->event.u.u.type == SelectionNotify && + stuff->event.u.selectionNotify.requestor == wid) { + TimeStamp time; + time = ClientTimeToServerTime(stuff->event.u.selectionNotify.time); + vncHandleSelection(stuff->event.u.selectionNotify.selection, + stuff->event.u.selectionNotify.target, + stuff->event.u.selectionNotify.property, + stuff->event.u.selectionNotify.requestor, + time); + } + + return origProcSendEvent(client); + } + + static void vncSelectionCallback(CallbackListPtr *callbacks, + void * data, void * args) + { + SelectionInfoRec *info = (SelectionInfoRec *) args; + + if (info->kind != SelectionSetOwner) + return; + if (info->client == serverClient) + return; + + if ((info->selection->selection != xaPRIMARY) && + (info->selection->selection != xaCLIPBOARD)) + return; + + if ((info->selection->selection == xaPRIMARY) && + !vncGetSendPrimary()) + return; + + vncSelectionRequest(info->selection->selection, xaTARGETS); + }