/* 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 * 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 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); 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"); if (!AddCallback(&ClientStateCallback, vncClientStateCallback, 0)) FatalError("Add VNC ClientStateCallback failed\n"); } void vncHandleClipboardRequest(void) { if (activeSelection == None) { LOG_DEBUG("Got request for local clipboard although no clipboard is active"); return; } 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 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; } } } 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) { 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, CARD32 time, const char* data) { Selection *pSel; WindowPtr pWin; int rc; Atom realProperty; xEvent event; 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) return rc; /* We do not validate the time argument because neither does * dix/selection.c and some clients (e.g. Qt) relies on this */ 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 = dixChangeWindowProperty(serverClient, pWin, realProperty, XA_ATOM, 32, PropModeReplace, sizeof(targets)/sizeof(targets[0]), targets, TRUE); if (rc != Success) return rc; } else if (target == xaTIMESTAMP) { rc = dixChangeWindowProperty(serverClient, pWin, realProperty, XA_INTEGER, 32, PropModeReplace, 1, &pSel->lastTimeChanged.milliseconds, TRUE); if (rc != Success) return rc; } else { 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)) { 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 { return BadMatch; } } } event.u.u.type = SelectionNotify; event.u.selectionNotify.time = time; 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) { rc = vncConvertSelection(client, stuff->selection, stuff->target, stuff->property, stuff->requestor, stuff->time, NULL); 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 (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; filtered = vncConvertLF(prop->data, prop->size); if (filtered == NULL) return; 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; filtered = vncConvertLF(prop->data, prop->size); if (filtered == NULL) return; LOG_DEBUG("Sending clipboard to clients (%d bytes)", (int)strlen(filtered)); vncSendClipboardData(filtered); vncStrFree(filtered); } } #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->selection->selection == activeSelection) { LOG_DEBUG("Local clipboard lost, notifying clients"); activeSelection = None; vncAnnounceClipboard(FALSE); } if (info->kind != SelectionSetOwner) return; if (info->client == serverClient) return; LOG_DEBUG("Selection owner change for %s", NameForAtom(info->selection->selection)); if ((info->selection->selection != xaPRIMARY) && (info->selection->selection != xaCLIPBOARD)) return; if ((info->selection->selection == xaPRIMARY) && !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; } } }