diff options
Diffstat (limited to 'unix/xserver/hw/vnc/vncSelection.c')
-rw-r--r-- | unix/xserver/hw/vnc/vncSelection.c | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/unix/xserver/hw/vnc/vncSelection.c b/unix/xserver/hw/vnc/vncSelection.c new file mode 100644 index 00000000..e50548a4 --- /dev/null +++ b/unix/xserver/hw/vnc/vncSelection.c @@ -0,0 +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 <dix-config.h> +#endif + +#include <X11/Xatom.h> + +#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; + + 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); +} |